在Vue中使用3d-force-graph渲染neo4j图谱
最近用 3d-force-graph
做了下neo4j的可视化,3D效果很好。并总结了下 3d-force-graph
库在 vue.js
下的的简单使用,如有描述错误的地方敬请指出。
- 插件地址👉 github to: 3d-force-graph
- 演示地址👉 large-graph-example
- 开始前请先安装依赖
"neo4j-driver": "^4.2.2",
"3d-force-graph": "^1.67.7",
- 创建template标签,创建一个渲染容器标签graph,对其引用。
<template>
<div ref="graph" id="graph">div>
template>
- 在script中导入ForceGraph3D。
import ForceGraph3D from "3d-force-graph";
- 在vue中声明所需的变量
data() {
return {
myGraph: null,
graphData: null,
db:{
uri : 'bolt://111.230.233.89',
user : 'neo4j',
password : 'neo4j'
},
};
- 开始准备数据,从neo4j中读取数据。此处代码通用,读取所有属性和标签,无需手动修改。函数返回
{Promise<node_info, rel_info>}</node_info,>
。node_info
和rel_info
都是字典,存储所有节点和边信息。key
为neo4j数据库中节点的ID
和边的ID
。示例:
node_info[节点ID] = {
labels: 节点的所有labels
attrs: 节点的所有属性
}
rel_info[边ID] = {
type: 边的名称/类型
attrs: 边的所有属性
source: 边的首节点ID
target: 边的尾节点ID
}
- 完整代码如下:
async getCyperResult(limit_items) {
const start = new Date()
const neo4j = require('neo4j-driver')
const driver = neo4j.driver(this.db.uri, neo4j.auth.basic(this.db.user, this.db.password))
const session = driver.session()
const result = await session.run(
'MATCH (n)-[r]->(m) ' +
'RETURN ' +
'id(n) as source, labels(n) as source_labels, properties(n) as source_attrs, ' +
'id(m) as target, labels(m) as target_labels, properties(m) as target_attrs, ' +
'id(r) as link, type(r) as r_type, properties(r) as r_attrs ' +
'LIMIT $limit_items ',
{limit_items: neo4j.int(limit_items)}
);
const node_info = {}
const rel_info = {}
result.records.map(r => {
node_info[r.get('source').toString()] = {
labels: r.get('source_labels').toString(),
attrs: r.get('source_attrs').toString()
};
node_info[r.get('target').toString()] = {
labels: r.get('target_labels').toString(),
attrs: r.get('target_attrs')
}
rel_info[r.get('link').toString()] = {
type: r.get('r_type').toString(),
attrs: r.get('r_attrs'),
source: r.get('source').toString(),
target: r.get('target').toString()
}
});
console.log(Object.keys(node_info).length + " nodes loaded and " + Object.keys(rel_info).length + " links loaded in " + (new Date() - start) + " ms.")
return {
node_info,
rel_info
}
}
- 构建插件渲染所需要的数据格式。3d-graph渲染的数据格式为一个字典,字典中包含一个
nodes
(包含所有节点),和一个links
(包含所有边)。
nodes
是一个数组,每一个数组元素是一个节点字典(字典中至至少包含id
,id
是节点的唯一标识)。如:nodes=[ {id:1}, {id:2}, {id:3}]
,字典中也可以存放其他属性,如:nodes=[ {id:1, labels:['Stu', 'Son'], age:13}, {id:2, labels:['Stu', 'Son'], age:14}]
。 该属性在构建图动态渲染中可以通过回调函数引用。
links
是一个数组,每一个数组元素是一个边字典(字典中至少包含source
和target
,source
和target
分别代表首尾节点,source
和target
要对应nodes
中的id
)
let graph_info = await this.getCyperResult(100000)
const links = Object.values(graph_info.rel_info);
const nodes = Object.entries(graph_info.node_info).map(entry=>{
return {id:entry[0], labels:entry[1].labels, attrs:entry[1].attrs}
})
this.graphData = {
nodes: nodes,
links: links
}
- 数据构建完之后就可以创建图。这里创建ForceGraph3D对象,并设置一些基本样式。本人这里列举一些简单常用的。标注有错误还请指出。
this.myGraph = ForceGraph3D({
controlType: "trackball",
rendererConfig:{ antialias: true, alpha: true }
})(this.$refs.graph)
.backgroundColor("black")
.width(this.$refs.graph.parentElement.offsetWidth )
.height(this.$refs.graph.parentElement.offsetHeight+150)
.showNavInfo(false)
.nodeRelSize(1)
.nodeVal(node => node.size * 0.05)
.nodeAutoColorBy('id')
.nodeAutoColorBy(node => node.id)
.nodeOpacity(1)
.nodeLabel("labels")
.nodeLabel(node => node.labels+''+JSON.stringify(node.attrs))
.onNodeHover(node => this.$refs.graph.style.cursor = node ? 'pointer' : null)
.onNodeClick(node => {
const distance = 40;
const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z);
this.myGraph.cameraPosition(
{x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio},
node,
3000
)
})
.linkVisibility(true)
.linkLabel(r => r.type)
.linkDirectionalArrowLength(3.5)
.linkDirectionalArrowRelPos(1)
.linkCurvature(0.25)
.linkDirectionalParticles(5)
.linkDirectionalParticleSpeed(1)
.linkDirectionalParticleWidth(0.3)
.linkColor(()=>'RGB(170,170,170)')
.linkAutoColorBy(r => r.type)
.linkOpacity(0.5)
- 图构建完成后就可以加载处理好的数据,即可渲染图谱。
let graph_info = await this.getCyperResult(100000)
const links = Object.values(graph_info.rel_info);
const nodes = Object.entries(graph_info.node_info).map(entry=>{
return {id:entry[0], labels:entry[1].labels, attrs:entry[1].attrs}
})
this.myGraph.graphData({
nodes: nodes, links: links
})
- 这里加入图的一些动态修改。比如修改图的边长,使图进行旋转等。非必须。(注意如果这里设置图旋转后,就无法使用鼠标对图进行放大缩小,因为每次的坐标都被还原)
this.myGraph.d3Force('link').distance(400);
const distance = 500;
let angle = 0;
setInterval(() => {
this.myGraph.cameraPosition({
x: distance * Math.sin(angle),
y: distance * Math.sin(angle),
z: distance * Math.cos(angle)
});
angle += Math.PI / 1000;
}, 100);
完整代码如下:
<template>
<div ref="graph" id="graph"></div>
</template>
<script>
import ForceGraph3D from "3d-force-graph";
export default {
name: "graph",
data() {
return {
myGraph: null,
graphData: null,
db:{
uri : this.$conf.neo4j.url,
user : this.$conf.neo4j.username,
password : this.$conf.neo4j.password
},
};
},
mounted() {
this.initGraph ()
},
methods: {
async initGraph() {
this.myGraph = ForceGraph3D({
controlType: "trackball",
rendererConfig:{ antialias: true, alpha: true }
})(this.$refs.graph)
.backgroundColor("black")
.width(this.$refs.graph.parentElement.offsetWidth )
.height(this.$refs.graph.parentElement.offsetHeight+150)
.showNavInfo(false)
.nodeRelSize(1)
.nodeVal(node => node.size * 0.05)
.nodeAutoColorBy('id')
.nodeAutoColorBy(node => node.id)
.nodeOpacity(1)
.nodeLabel("labels")
.nodeLabel(node => node.labels+''+JSON.stringify(node.attrs))
.onNodeHover(node => this.$refs.graph.style.cursor = node ? 'pointer' : null)
.onNodeClick(node => {
const distance = 40;
const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z);
this.myGraph.cameraPosition(
{x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio},
node,
3000
)
})
.linkVisibility(true)
.linkLabel(r => r.type)
.linkDirectionalArrowLength(3.5)
.linkDirectionalArrowRelPos(1)
.linkCurvature(0.25)
.linkDirectionalParticles(5)
.linkDirectionalParticleSpeed(1)
.linkDirectionalParticleWidth(0.3)
.linkColor(()=>'RGB(170,170,170)')
.linkAutoColorBy(r => r.type)
.linkOpacity(0.5)
let graph_info = await this.getCyperResult(100000)
const links = Object.values(graph_info.rel_info);
const nodes = Object.entries(graph_info.node_info).map(entry=>{
return {id:entry[0], labels:entry[1].labels, attrs:entry[1].attrs}
})
this.myGraph.graphData({
nodes: nodes, links: links
})
this.myGraph.d3Force('link').distance(400);
const distance = 500;
let angle = 0;
setInterval(() => {
this.myGraph.cameraPosition({
x: distance * Math.sin(angle),
y: distance * Math.sin(angle),
z: distance * Math.cos(angle)
});
angle += Math.PI / 1000;
}, 100);
},
async getCyperResult(limit_items) {
const start = new Date()
const neo4j = require('neo4j-driver')
const driver = neo4j.driver(this.db.uri, neo4j.auth.basic(this.db.user, this.db.password))
const session = driver.session()
const result = await session.run(
'MATCH (n)-[r]->(m) ' +
'RETURN ' +
'id(n) as source, labels(n) as source_labels, properties(n) as source_attrs, ' +
'id(m) as target, labels(m) as target_labels, properties(m) as target_attrs, ' +
'id(r) as link, type(r) as r_type, properties(r) as r_attrs ' +
'LIMIT $limit_items ',
{limit_items: neo4j.int(limit_items)}
);
const node_info = {}
const rel_info = {}
result.records.map(r => {
node_info[r.get('source').toString()] = {
labels: r.get('source_labels').toString(),
attrs: r.get('source_attrs').toString()
};
node_info[r.get('target').toString()] = {
labels: r.get('target_labels').toString(),
attrs: r.get('target_attrs')
}
rel_info[r.get('link').toString()] = {
type: r.get('r_type').toString(),
attrs: r.get('r_attrs'),
source: r.get('source').toString(),
target: r.get('target').toString()
}
});
console.log(Object.keys(node_info).length + " nodes loaded and " + Object.keys(rel_info).length + " links loaded in " + (new Date() - start) + " ms.")
return {
node_info,
rel_info
}
},
}
};
</script>
<style scoped>
#graph{
background-color: rgba(0,0,0,1);
padding: 1rem;
height:100vh;
width: 100%;
border-radius: 5px;
}
</style>
Original: https://blog.csdn.net/lov_xm617/article/details/113781024
Author: Amorji
Title: 在Vue中使用3d-force-graph渲染neo4j图谱
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/555846/
转载文章受原作者版权保护。转载请注明原作者出处!