实现neo4j数据库类似知识图谱,点击展示环形菜单

146 阅读2分钟

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

@TOC


前言

提示:这里可以添加本文要记录的大概内容:

需求:实现neo4j数据库类似知识图谱,点击展示环形菜单

参考:北极熊Leo大佬的demo:gitee.com/LeoGitLiu/k… 在大佬基础上添加一些功能


提示:以下是本篇文章正文内容,下面案例可供参考

添加清除图谱功能

core.js


// 清除图谱
const clearGraph = () => {
    // 选择特定的容器并清除其内容
    d3.select('#kg').selectAll('*').remove();
    // 将 svg 设为 null
    svg = null;
}

2.读入数据

给节点新增方法添加返回参数: menu.js

import {d3, addNodes, removeNode} from '../graph/core'
import {G_ID, MENU_LAYER, MOUSE_OVER, MOUSE_OUT, CLICK} from '../common/const';
import {overMenu, addMenuEvent, outMenu, clickMenu} from '../events/menuEvents';
import {preprocess, preprocesss} from '../common/preprocess';


const menuData = {}

let menuLayerG = null

let focusNode = null

const innerGap = 5
const outerGap = 28

const setMenuData = function (nodeType, menu) {
    menuData[nodeType] = menu
    menu.forEach(m => {
        addMenuEvent(m.type, MOUSE_OVER, (d, type) => {
            d3.selectAll('.' + d.data.subMenuClass + '-container').classed('dashOut', true);
            const subMenu = menuData[focusNode.type].filter(m => m.type === type)[0].children
            getSecondMenu(d, type, subMenu)
            d3.select(event.target).classed(d.data.menuActiveClass, true);
        })
        addMenuEvent(m.type, MOUSE_OUT, (d, type) => {
            d3.select(event.target).classed(d.data.menuActiveClass, false); // 鼠标移上的hover样式移除
        })
        if (m.event !== null) {
            addMenuEvent(m.type, CLICK, (d, type) => {
                if (!m.event)
                    return

                if (m.event.type === 'add') {
                    m.event.source(focusNode).then(res => {
                        let target = Object.assign({
                            nodes: [],
                            links: [],
                            menu: {}
                        }, res)
                        let {nodes, links, menu} = target
                        const {_nodes, _links, _menu} = preprocesss(nodes, links, menu)
                        addNodes(_nodes, _links)
                        // for (const [k, v] of Object.entries(_menu)) {
                        //   setMenuData(k, v)
                        // }
                    })
                } else if (m.event.type === 'del') {
                    m.event.source(focusNode).then(res => {
                        removeNode(focusNode.id)
                        focusNode = null
                    })
                }
            })
        }

        m.children.forEach(s => {
            if (s.event !== null) {
                addMenuEvent(s.type, CLICK, (d, type) => {
                    if (!s.event)
                        return
                    if (s.event.type === 'add') {
                        s.event.source(focusNode).then(res => {
                            let target = Object.assign({
                                nodes: [],
                                links: [],
                                menu: {}
                            }, res)
                            let {nodes, links, menu} = target
                            const {_nodes, _links, _menu} = preprocess(nodes, links, menu)
                            addNodes(_nodes, _links)
                            for (const [k, v] of Object.entries(_menu)) {
                                setMenuData(k, v)
                            }
                        })
                    } else if (s.event.type === 'del') {
                        s.event.source(focusNode).then(res => {
                            console.log(focusNode)
                            removeNode(focusNode.id)
                            focusNode = null
                        })
                    }
                })
            }

            addMenuEvent(s.type, MOUSE_OUT, (d, type) => {
                // console.log(s)
            })
        })
    })
}

const getMenuData = function (nodeType) {
    return menuData[nodeType] ? menuData[nodeType] : []
}

const getSecondMenu = function (d, type, subMenu) {
    if (d3.selectAll(`#sub-menu${type}`)._groups[0].length > 0) {
        d3.selectAll(`#sub-menu${type}`).classed('dashOut', false);
    } else {
        // 如果没有,则创建, 建立角度比例尺
        const angleScale = d3.scaleLinear()
            .domain([0, 2 * Math.PI])
            .range([d.startAngle, d.endAngle]);
        let pieMenuData = subMenu

        generateMenu({
            innerR: focusNode.r + 35,
            outerR: focusNode.r + 65,
            startAngle: l => angleScale(l.startAngle),
            endAngle: l => angleScale(l.endAngle)
        }, pieMenuData, `sub-menu${type}`);
    }
}

const generateMenu = function ({
                                   innerR = 25,
                                   outerR = 60,
                                   startAngle = d => d.startAngle,
                                   endAngle = d => d.endAngle
                               } = {}, menuDataSrc = [], id) {
    const arc = d3.arc()
        .innerRadius(innerR)
        .outerRadius(outerR)
        .padAngle(0)
        .startAngle(startAngle)
        .endAngle(endAngle)

    const pie = d3.pie()
    const arcsData = pie.value(d => d.size)(menuDataSrc)

    if (d3.selectAll('#' + MENU_LAYER).size() === 0) {
        menuLayerG = d3.select('.' + G_ID)
            .append('g')
            .attr('id', MENU_LAYER)
    }

    const g = menuLayerG.append('g')

    const arcs = g.selectAll('g')
        .data(arcsData)
        .enter()
        .append('g')

    if (id) {
        g.attr('class', 'sub-' + MENU_LAYER + ' ' + 'sub-menu-item-container').attr('id', id)
        arc.padAngle(0.01)

        arcs.append('path')
            .attr('class', d => d.data.subMenuClass)
            .attr('d', arc)
            .on('mouseover', (d) => {
                overMenu(d)
                d3.event.stopPropagation()
            })
            .on('click', d => {
                clickMenu(d)
                removeMenu()
                d3.event.stopPropagation()
            });

        arcs.append('text')
            .text(d => d.data.name)
            .attr('text-anchor', 'middle')
            .attr('class', d => d.data.subMenuTextClass)
            .attr('x', d => arc.centroid(d)[0] * 1)
            .attr('y', d => arc.centroid(d)[1] * 1)
    } else {
        g.attr('class', 'main-' + MENU_LAYER)
        arcs.append('path')
            .attr('class', d => d.data.menuClass)
            .attr('d', arc)
            .on('mouseover', d => {
                overMenu(d)
                d3.event.stopPropagation()
            })
            .on('mouseout', d => {
                outMenu(d)
                d3.event.stopPropagation()
            })
            .on('click', d => {
                clickMenu(d)
                removeMenu()
                d3.event.stopPropagation()
            })

        if (false) {
            // if (configDriver.ifUseMenuIcon()) {
            arcs.append('image')
                .attr('xlink:href', (d) => {
                    return configDriver.getMenuIcon(d.data.option);
                })
                .attr('width', '20')
                .attr('height', '20')
                .attr('class', 'ArcImage')
                .attr('pointer-events', 'none')
                .attr('x', d =>
                    (arc.centroid(d)[0] * 1) - 10)
                .attr('y', d =>
                    (arc.centroid(d)[1] * 1) - 10);
        } else {
            arcs.append('text')
                .attr('class', d => d.data.menuTextClass)
                .attr('text-anchor', 'middle')
                .attr('transform', d => {
                    const deg = (d.startAngle + d.endAngle) * 180 / (2 * Math.PI);
                    if (deg < 90 || deg > 270) {
                        return 'rotate(' + deg + ', ' + (arc.centroid(d)[0]) + ' ' + (arc.centroid(d)[1]) + ')'
                    } else {
                        return 'rotate(' + (deg + 180) + ', ' + (arc.centroid(d)[0]) + ' ' + (arc.centroid(d)[1]) + ')'
                    }
                })
                .attr('x', d => (arc.centroid(d)[0]))
                .attr('y', d => (arc.centroid(d)[1]))
                .text(d => d.data.name)
        }
    }
}


const removeMenu = () => {
    d3.selectAll('#' + MENU_LAYER).remove()
}

const placeMenu = (x, y) => {
    d3.selectAll('#' + MENU_LAYER).attr('transform', `translate(${x}, ${y})`);
}


const getMenu = function (d) {
    const {x, y} = d
    removeMenu()
    let menuData = getMenuData(d.type)
    focusNode = d

    const innerR = d.r + innerGap
    const outerR = innerR + outerGap

    generateMenu({innerR, outerR, startAngle: a => a.startAngle, endAngle: a => a.endAngle}, menuData);
    placeMenu(x, y);
}

export {getMenu, setMenuData, removeMenu}

修改点击节点事件时会多次调用 menuEvents.js

const clickMenu = d => {
  // menuEventPool.filter(i => i.trigger === CLICK && d.data.type === i.type)
  //   .forEach(i => i.handler(d, i.type))
  const event = menuEventPool.find(i => i.trigger === CLICK && d.data.type === i.type);
  if (event) {
    event.handler(d, event.type);
  }
}

总结

完整代码:gitee.com/huang_chao_…