之前的博客:对Neo4j导出数据做知识图谱可视化 D3库实现
之前博客做的知识图谱可视化只是在html上直接写的页面,而且d3还使用的是v4版本的库。
但是当项目复杂的情况下(前端项目越来越复杂了),比如要在系统中加入其他图表进行综合大屏展示,显然不是一个很好的选择,于是决定用vue重构一下代码,把图可视化作为一个组件来维护,顺便再用一下新版本的d3。同时之前的项目存在一个致命的问题,就是动态更新,即当页面已经有图谱展示的情况下,查询新的数据时,无法正常展示(节点全跑到左上角去了)
下图是向基于nodejs搭建neo4j后端服务请求数据后,更新视图出现的问题:(Vue重构后,代码不变的情况下可以解决该问题)
下面附上供学习和参考的链接:
目前d3已经到v6版本了(更新是真滴快),关于新版d3的改动说明的链接我放在了上面供参考。虽然更新了很多东西,但总的来说原来的代码其实改动不大,比如新版的d3移除了d3.event,v4版本需要注意。
首先页面的整体结构如下,分为2D和3D图谱展示两个页面,其中2D知识图谱除了展示还实现了各种图交互功能;而3D知识图谱采用了第三方模块,底层使用 D3+Three 实现视图渲染,直接截图展示:
这次对代码结构作了大改,变化挺大的,文件夹对应关系如下:
components
文件夹
d3graph.vue
—— 2D图谱展示组件threeGraph.vue
—— 3D图谱展示组件gSearch.vue
—— 搜索组件,目前主要通过require代替后台请求views
文件夹
2dView.vue
—— 2D图谱展示页面3dView.vue
—— 3D图谱展示页面plugins
文件夹
d3-context-menu.js
—— 右键菜单事件注册及回调函数d3-context-menu.scss
—— 右键菜单样式文件router
文件夹
index.js
—— 路由较少,就2个页面data
文件夹store
和 assets
文件夹暂时不用接下来介绍关于图可视化的基本功能与交互事件:
d3初始化,包括数据解析、数据渲染及响应式数据初始化,在新版本代码将数据解析分离出来,放到 2dView.vue
页面中 。现在d3初始化分为数据渲染和状态初始化两个任务。
旧版本:
// d3初始化,包括数据解析、数据渲染
d3init () {
// this.graph存放json数据
this.d3jsonParser(this.graph)
this.d3render()
// 数据初始化(节点状态)
this.nodeState = 0
this.states = ['on', 'on', 'on', 'on']
}
新版本:
// d3初始化,包括数据解析、数据渲染
d3init () {
this.links = this.data.links
this.nodes = this.data.nodes
this.svgDom = d3.select('#svg') // 获取svg的DOM元素
// this.d3jsonParser(this.graph)
this.d3render()
// 数据状态初始化
this.stateInit()
},
d3jsonParser (json) {
const nodes =[]
const links = [] // 存放节点和关系
const nodeSet = [] // 存放去重后nodes的id
// 使用vue直接通过require获取本地json,不再需要使用d3.json获取数据
// d3.json('./../data/records.json', function (error, data) {
// if (error) throw error
// graph = data
// console.log(graph[0].p)
// })
for (let item of json) {
for (let segment of item.p.segments) {
// 重新更改data格式
if (nodeSet.indexOf(segment.start.identity) == -1) {
nodeSet.push(segment.start.identity)
nodes.push({
id: segment.start.identity,
label: segment.start.labels[0],
properties: segment.start.properties
})
}
if (nodeSet.indexOf(segment.end.identity) == -1) {
nodeSet.push(segment.end.identity)
nodes.push({
id: segment.end.identity,
label: segment.end.labels[0],
properties: segment.end.properties
})
}
links.push({
source: segment.relationship.start,
target: segment.relationship.end,
type: segment.relationship.type,
properties: segment.relationship.properties
})
}
}
console.log(nodes)
console.log(links)
this.links = links
this.nodes = nodes
this.data = {
nodes, links }
// return { nodes, links }
}
该按钮模拟了向后台请求数据的功能,获取新的数据后,更新图展示部分,重新渲染视图,解决了原博客在动态更新这一块的问题。
目前查询暂用require本地数据代替后台请求数据,gSearch.vue
将数据传给页面,并执行数据解析的任务;而 d3graph.vue
组件通过 watch
监听当前的数据变化,更新后通过 this.d3init() 重新渲染页面。
gSearch.vue
模拟后台查询:
query () {
// console.log(typeof this.mode)
if (this.data.length <= 20) {
this.data = require('../data/top5.json')
} else {
this.data = require('../data/records.json')
}
this.$emit('getData', this.data)
}
d3graph.vue
监听数据变化:
watch: {
// 当请求到新的数据时,重新渲染
data (newData, oldData) {
console.log(newData, oldData)
// 移除svg和元素注册事件,防止内存泄漏
this.svgDom.on('.', null)
this.svgDom.selectAll('*').on('.', null)
this.d3init()
}
}
下图展示将视图通过缩放、拖拽和平移来调整图谱布局,使展示结果更加清晰。
事件直接在svg视图部分注册,通过标签的translate和scale属性实现平移和缩放:
var svg = d3.select("#svg1")
// 给画布绑定zoom事件(缩放、平移)
.call(d3.zoom().on('zoom', function(event) {
// console.log(event)
var scale = event.transform.k,
translate = [event.transform.x, event.transform.y]
// 视图矫正,暂不使用
// if (this.svgTranslate) {
// translate[0] += t