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

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

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

    "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,
      graphData: null,
      db:{
        uri : 'bolt://111.230.233.89',
        user : 'neo4j',
        password : 'neo4j'
      },
    };
  1. 开始准备数据,从neo4j中读取数据。此处代码通用,读取所有属性和标签,无需手动修改。函数返回 {Promise<node_info, rel_info>}</node_info,>node_inforel_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
      }
    }
  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)

      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
      }
  1. 数据构建完之后就可以创建图。这里创建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)
  1. 图构建完成后就可以加载处理好的数据,即可渲染图谱。

      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
      })
  1. 这里加入图的一些动态修改。比如修改图的边长,使图进行旋转等。非必须。(注意如果这里设置图旋转后,就无法使用鼠标对图进行放大缩小,因为每次的坐标都被还原)

      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/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球