在Vue中使用3d-force-graph渲染neo4j图谱

在Vue中使用3d-force-graph渲染neo4j图谱


最近用 3d-force-graph做了下neo4j的可视化,3D效果很好。并总结了下 3d-force-graph库在 vue.js下的的简单使用,如有描述错误的地方敬请指出。

  • 插件地址 github to: 3d-force-graph
  • 演示地址 large-graph-example
    在Vue中使用3d-force-graph渲染neo4j图谱_第1张图片
  • 开始前请先安装依赖
    "neo4j-driver": "^4.2.2",
    "3d-force-graph": "^1.67.7",
  1. 创建template标签,创建一个渲染容器标签graph,对其引用。
<template>
  <div ref="graph" id="graph">div>
template>
  1. 在script中导入ForceGraph3D。
import ForceGraph3D from "3d-force-graph";
  1. 在vue中声明所需的变量
data() {
    return {
      myGraph: null,            		// 3D-graph对象
      graphData: null,					// 3D-graph加载的图数据
      db:{								// 数据库连接配置:
        uri : 'bolt://111.230.233.89',  // 		neo4j地址(不给端口默认7687)
        user : 'neo4j',                 // 		数据库用户名(默认neo4j,修改成自己的)
        password : 'neo4j'				// 		数据库密码(默认ne4oj,修改成自己的)
      },
    };
  1. 开始准备数据,从neo4j中读取数据。此处代码通用,读取所有属性和标签,无需手动修改。函数返回 {Promise}node_inforel_info都是字典,存储所有节点和边信息。key为neo4j数据库中节点的ID和边的ID。示例:
node_info[节点ID] = {
	labels: 节点的所有labels	// 数据类型是一个字符串,多个标签之间使用<逗号>隔开
	attrs:  节点的所有属性	// 数据类型是一个字典,包含节点的所有属性 
}
rel_info[ID] = {
	type:	边的名称/类型	// 数据类型是一个字符串,多个标签之间使用<逗号>隔开
	attrs:	边的所有属性		// 数据类型是一个字典,包含节点的所有属性 
	source:	边的首节点ID		
	target:	边的尾节点ID
}
  • 完整代码如下:
/**
     * 读取neo4j结果
     * @param limit_items 返回的条目数量
     * @returns {Promise}
     */
    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)}
      );

      /* 存储节点和边信息
       * node_info[节点ID] = {节点标签:list, 节点属性:dict}
       *   rel_info[边ID] = {  边类别:str,   边属性:dict}
       */
      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
      }
    }
  1. 构建插件渲染所需要的数据格式。3d-graph渲染的数据格式为一个字典,字典中包含一个nodes(包含所有节点),和一个links(包含所有边)。
    nodes是一个数组,每一个数组元素是一个节点字典(字典中至至少包含idid是节点的唯一标识)。如:nodes=[ {id:1}, {id:2}, {id:3}],字典中也可以存放其他属性,如:nodes=[ {id:1, labels:['Stu', 'Son'], age:13}, {id:2, labels:['Stu', 'Son'], age:14}]。 该属性在构建图动态渲染中可以通过回调函数引用。
    links是一个数组,每一个数组元素是一个边字典(字典中至少包含sourcetarget,sourcetarget分别代表首尾节点,sourcetarget要对应nodes中的id
    let graph_info = await this.getCyperResult(100000)
      /** 构造3D-Graph数据的边 */
      const links = Object.values(graph_info.rel_info);
      /** 构造3D-Graph数据的节点 */
      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
      }
  1. 数据构建完之后就可以创建图。这里创建ForceGraph3D对象,并设置一些基本样式。本人这里列举一些简单常用的。标注有错误还请指出。
/********************************************** 1.创建图 **********************************************/
      this.myGraph = ForceGraph3D({
        controlType: "trackball",                                                       // orbit沿2d轨迹绕着拖动,fly 固定不动
        rendererConfig:{ antialias: true, alpha: true }
      })(this.$refs.graph)
        /*------------------------------------------- 画布配置 -------------------------------------------*/
        .backgroundColor("black")                                                       // 背景颜色,支持内置颜色和RGB
        .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')                                                          // 节点颜色:根据属性划分(参数为graphData({nodes: nodes, links: links}))中nodes中每个node中的属性名称)
        .nodeAutoColorBy(node => node.id)                                               // 节点颜色:回调函数处理(功能同上)
        .nodeOpacity(1)                                                                 // 节点透明度:回调函数处理(根据label划分)
        .nodeLabel("labels")                                                            // 节点标签显示内容(鼠标滑到节点显示,支持直接写节点属性名称)
        .nodeLabel(node => node.labels+'
'
+JSON.stringify(node.attrs)) // 节点标签显示内容(鼠标滑到节点显示,也可以使用回调函数) .onNodeHover(node => this.$refs.graph.style.cursor = node ? 'pointer' : null) // 鼠标滑到节点上改变指针 .onNodeClick(node => { // 点击节点事件(视角转移到该节点) // Aim at node from outside it 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},// new position node, // lookAt ({ x, y, z }) 3000 // ms transition duration) ) }) /*------------------------------------------- 边的配置 -------------------------------------------*/ .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) // 边透明度(越小越透明)
  1. 图构建完成后就可以加载处理好的数据,即可渲染图谱。
  /********************************************** 2.加载数据 **********************************************/
      let graph_info = await this.getCyperResult(100000)
      /** 构造3D-Graph数据的边 */
      const links = Object.values(graph_info.rel_info);
      /** 构造3D-Graph数据的节点 */
      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
      })
  1. 这里加入图的一些动态修改。比如修改图的边长,使图进行旋转等。非必须。(注意如果这里设置图旋转后,就无法使用鼠标对图进行放大缩小,因为每次的坐标都被还原)
  /********************************************** 3.动态设置 **********************************************/
      /*  修改边长度,同d3引擎用法  */
      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() {
      /********************************************** 1.创建图 **********************************************/
      this.myGraph = ForceGraph3D({
        controlType: "trackball",                                                                 // orbit沿2d轨迹绕着拖动,fly 固定不动
        rendererConfig:{ antialias: true, alpha: true }
      })(this.$refs.graph)
        /*------------------------------------------- 画布配置 -------------------------------------------*/
        .backgroundColor("black")                                                           // 背景颜色,支持内置颜色和RGB
        .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')                                                                    // 节点颜色:根据属性划分(参数为graphData({nodes: nodes, links: links}))中nodes中每个node中的属性名称)
        .nodeAutoColorBy(node => node.id)                                                         // 节点颜色:回调函数处理(功能同上)
        .nodeOpacity(1)                                                                           // 节点透明度:回调函数处理(根据label划分)
        .nodeLabel("labels")                                                          // 节点标签显示内容(鼠标滑到节点显示,支持直接写节点属性名称)
        .nodeLabel(node => node.labels+'
'
+JSON.stringify(node.attrs)) // 节点标签显示内容(鼠标滑到节点显示,也可以使用回调函数) .onNodeHover(node => this.$refs.graph.style.cursor = node ? 'pointer' : null) // 鼠标滑到节点上改变指针 .onNodeClick(node => { // 点击节点事件(视角转移到该节点) // Aim at node from outside it 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}, // new position node, // lookAt ({ x, y, z }) 3000 // ms transition duration) ) }) /*------------------------------------------- 边的配置 -------------------------------------------*/ .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) // 边透明度(越小越透明) /********************************************** 2.加载数据 **********************************************/ let graph_info = await this.getCyperResult(100000) /** 构造3D-Graph数据的边 */ const links = Object.values(graph_info.rel_info); /** 构造3D-Graph数据的节点 */ 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 }) /********************************************** 3.动态设置 **********************************************/ /* 修改边长度,同d3引擎用法 */ 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); }, /** * 读取neo4j结果 * @param limit_items * @returns {Promise} */ 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)} ); /* 存储节点和边信息 * node_info[节点ID] = {节点标签:list, 节点属性:dict} * rel_info[边ID] = { 边类别:str, 边属性:dict} */ 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; /*min-width: 300px;*/ width: 100%; border-radius: 5px; } </style>

你可能感兴趣的:(neo4j,可视化,vue,知识图谱,3d渲染)