首页 > 其他分享 >3D力导向树插件 3d-force-graph

3D力导向树插件 3d-force-graph

时间:2023-09-25 17:14:55浏览次数:51  
标签:node 插件 force sprite graph link Graph id 255

3d-force-graph是什么?

一个 Web 组件,使用强制导向的迭代布局来表示 3 维空间中的图形数据结构。使用ThreeJS /WebGL 进行 3D 渲染,使用d3-force-3dngraph作为底层物理引擎。

 

3d-force-graph可以做些什么?

参考以下效果:

哔哩哔哩:https://www.bilibili.com/video/BV1WS4y1s7st/?spm_id_from=333.999.0.0&vd_source=cad25e75f283bed20b688da5f217b5d6

 

 

 

人物关系图:https://trails-game.com/relations/

 

3d-force-graph如何用?

引入

安装 npm install 3d-force-graph
import ForceGraph3D from '3d-force-graph';
//或者 const ForceGraph3D = require('3d-force-graph');

使用版本  "3d-force-graph": "^1.70.14",

容器

<div id="3d-graph" class="graph-3d"></div>

 画布初始化

this.Graph = ForceGraph3D()(document.getElementById('3d-graph'))
        .height(1000)  //画布高度
        .backgroundColor('rgba(0,0,0,0)')  //画布背景色
        .showNavInfo(false)   //禁用页脚

数据准备阶段

数据结构  选取部分数据仅作为参考,不可作为demo数据引入

this.highlightNodes = new Set()
this.highlightLinks = new Set()
this.hoverNode = null
this.nodes = [{
    "id": 2,    //节点id
    "labels": [
        "N2"   //自定义层级
    ],
    "property": {
        "name": "二十大"  //节点文本
    },
    "name": "二十大",  //节点文本
    "group": 0
},{
    "id": 3,
    "labels": [
        "N3"
    ],
    "property": {
        "name": "过去五年的工作和新时代十年的伟大变革"
    },
    "name": "过去五年的工作和新时代十年的伟大变革",
    "color": [   //节点颜色
        "rgba(215,255,171,1)",   //高亮时的颜色
        "rgba(215,255,171,0.2)"   //
    ],
    "group": 1
},{
    "id": 4,
    "labels": [
        "N4"
    ],
    "property": {
        "name": "过去五年的工作和新时代十年的伟大变革"
    },
    "name": "过去五年的工作和新时代十年的伟大变革",
    "typeNum": 0,
    "group": 2,
    "color": [
        "rgba(215,255,171,1)",
        "rgba(215,255,171,0.2)"
    ]
},{
    "id": 5,
    "labels": [
        "N4"
    ],
    "property": {
        "name": "五年的重大成就"
    },
    "name": "五年的重大成就",
    "typeNum": 0,
    "group": 2,
    "color": [
        "rgba(215,255,171,1)",
        "rgba(215,255,171,0.2)"
    ]
},
...
]
this.links = [
        {
          id: 2,   //线id
          start: 2,  //连线开始节点 id
          end: 3,   //连线结束节点的id
          type: '内容明细',  //线上文字
          identity: null,
          property: {},
          source: 2, //连线开始节点 id
          target: 3, //连线开始节点 id
          isShow: true,  //是否显示线
          group: 1,
          typeNum: 0
        },
        {
          id: 27,
          start: 2,
          end: 28,
          type: '内容明细',
          identity: null,
          property: {},
          source: 2,
          target: 28,
          isShow: true,
          group: 1,
          typeNum: 1
        },
        {
          id: 3,
          start: 3,
          end: 4,
          type: '解读',
          identity: null,
          property: {},
          source: 3,
          target: 4,
          isShow: true,
          typeNum: 0,
          group: 2
        },
        {
          id: 4,
          start: 3,
          end: 5,
          type: '解读',
          identity: null,
          property: {},
          source: 3,
          target: 5,
          isShow: true,
          typeNum: 0,
          group: 2
        },
        {
          id: 5,
          start: 3,
          end: 6,
          type: '解读',
          identity: null,
          property: {},
          source: 3,
          target: 6,
          isShow: true,
          typeNum: 0,
          group: 2
        }
      ]

 

加入数据  

this.Graph.graphData({
        nodes: this.nodes,
        links: this.links
      })
node 节点,添加周围的节点和线
this.buildNeighboursAndTestPos({
        nodes: this.nodes,
        links: this.links
      })
buildNeighboursAndTestPos(data) {
      return data.links.forEach(link => {
        const a = data.nodes.find(item => item.id == link.start)
        const b = data.nodes.find(item => item.id == link.end)
        !a.neighbors && (a.neighbors = [])
        !b.neighbors && (b.neighbors = [])
        a.neighbors.push(b)
        b.neighbors.push(a)
        !a.links && (a.links = [])
        !b.links && (b.links = [])
        a.links.push(link)
        b.links.push(link)
      })
    },

 

节点文字使用  three-spritetext

import SpriteText from 'three-spritetext'

使用版本  "three-spritetext": "^1.6.5"

渲染节点

   node() {
      this.Graph.nodeThreeObject(node => {
        const sprite = new SpriteText(`${node.name}`)
        sprite.color = 'rgba(255, 255, 255)'
        sprite.textHeight = 1.5
        sprite.fontSize = 120
        sprite.fontWeight = 'bold'
        sprite.position.set(0, 13, 0)
        return sprite
      })
        .nodeThreeObjectExtend(true)
        // 节点是否隐藏
        // .nodeVisibility(node => {
        //   if (node.id == 0) return true
        //   return node.show ? true : false
        // })
        .nodeOpacity(1)
        .nodeColor(node => {
          if (!node.color) return
          if (!this.highlightNodes.size) return node.color[0]
          return this.highlightNodes.has(node) ? node.color[0] : node.color[1]
        })
        .nodeRelSize(8)
        // 节点文字
        .linkThreeObjectExtend(true)
    },
线和线上文字、箭头
   // 线和线上文字
    linkText() {
      this.Graph.linkThreeObjectExtend(true)
        .linkThreeObject(link => {
          const sprite = new SpriteText(`${link.type}`)
          sprite.color = 'rgba(255, 255, 255)'
          sprite.textHeight = 1.5
          return sprite
        })
        .linkPositionUpdate((sprite, {start, end}) => {
          const middlePos = Object.assign(
            ...['x', 'y', 'z'].map(c => ({
              [c]: start[c] + (end[c] - start[c]) / 2 // calc middle point
            }))
          )
          // Position sprite
          Object.assign(sprite.position, middlePos)
        })
        // 曲线
        .linkCurvature(0)
        // 线颜色
        .linkColor(link => {
          if (!this.highlightLinks.size) {
            return this.nodes.find(item => item.id == link.end).color[0]
          }
          return this.highlightLinks.has(link)
            ? this.nodes.find(item => item.id == link.end).color[0]
            : this.nodes.find(item => item.id == link.end).color[1]
        })
        // 线宽度
        // .linkWidth(link => 0.3)
        .linkWidth(link => (this.highlightLinks.has(link) ? 1 : 0.3))
        .linkOpacity(0.7)
    },
    // 箭头
    linkArrow() {
      this.Graph.linkDirectionalArrowLength(2).linkDirectionalArrowRelPos(1)
    },

 

问题总结:

1.节点过多或增加连线曲度,画布卡顿

问题原因:

  SpriteText 渲染文字,GPU负荷过大导致卡顿,改为透明度,来区分选中项文字高亮

//SpriteText 渲染文字
    this.Graph.linkThreeObjectExtend(true)
        .linkThreeObject(link => {
          const sprite = new SpriteText(`${link.type}`)
          // if (!this.highlightLinks.size) {
          //   sprite.color = 'rgba(255, 255, 255)'
          // } else {
          //   this.highlightLinks.has(link) ? (sprite.color = 'rgba(255, 255, 255)') : (sprite.color = 'rgba(255, 255, 255,0.1)')
          // }
          sprite.color = 'rgba(255, 255, 255)'
          sprite.textHeight = 1.5
          return sprite
        })
  //更新节点文字
updateHighlight() {
      this.links.forEach(link => {
        if (this.hoverNode && !this.highlightLinks.has(link)) {
          link.__lineObj.visible = false
          link.__arrowObj.visible = false
        } else {
          link.__lineObj.visible = true
          link.__arrowObj.visible = true
        }
      })
      this.nodes.forEach(node => {
        if (this.hoverNode && !this.highlightNodes.has(node)) {
          node.__threeObj.children[0].material.opacity = 0.1
        } else {
          node.__threeObj.children[0].material.opacity = 1
        }
      })
      this.Graph.nodeColor(this.Graph.nodeColor())
      // this.Graph.nodeThreeObject(this.Graph.nodeThreeObject())
      // this.Graph.nodeColor(this.Graph.nodeColor()).linkColor(this.Graph.linkColor())  
      // this.Graph.linkThreeObject(this.Graph.linkThreeObject()) //此方法对gpu性能消耗较高
      // this.Graph.linkWidth(this.Graph.linkWidth())
    },

2.旋转动画,开始与结束会有失帧

 解决问题:

开启动画时,无法获取当前坐标,只能曲线救国

data(){
return {
    angle: 0,
      distance: 1400,
      amiTimer: null
}
}

watch: {
//是否开启动画
    isRotationActive(newVal) {
      if (newVal) {
        let node = this.nodes.find(ii => ii.name == '十四五规划')
        this.Graph.cameraPosition(
          {
            x: this.distance * Math.sin(this.angle),
            z: this.distance * Math.cos(this.angle)
          },
          node,
          300
        )
        setTimeout(() => {
          clearInterval(this.amiTimer)
          this.amiTimer = setInterval(() => {
            this.Graph.cameraPosition(
              {
                x: this.distance * Math.sin(this.angle),
                z: this.distance * Math.cos(this.angle)
              },
              node
            )
              .enableNodeDrag(false)
              .onNodeHover(() => {})
              .enableNavigationControls(false)
            this.angle += Math.PI / 300
          }, 10)
        }, 300)
      } else {
        clearInterval(this.amiTimer)
        setTimeout(() => {
          this.Graph.enableNavigationControls(true)
          this.nodeHover()
          this.amiTimer = null
        }, 10)
      }
    }
  },

3.旋转方向不能统一

暂未解决。

相关资源:

文档:https://github.com/vasturiano/3d-force-graph

人物关系图demo:https://github.com/trails-game/relation-graph-3d-force

 参考:https://www.jianshu.com/p/15f5ca09ad69

标签:node,插件,force,sprite,graph,link,Graph,id,255
From: https://www.cnblogs.com/wxyblog/p/16874480.html

相关文章

  • GraphMAE阅读笔记
    GraphMAE阅读引言在摘要里,本论文提出了自监督学习有着巨大的潜力自监督学习又分为对比学习和生成学习目前比较成功的是对比学习,因为在对比学习中,有高质量的数据增强以及可以通过额外的策略来稳定训练过程而对于生成式的自监督学习,它们旨在重建数据本身的特征和信息,对图来说,图......
  • Educational Codeforces Round 155 (Rated for Div. 2)
    EducationalCodeforcesRound155(RatedforDiv.2)A.Rigged!解题思路:若存在\(s[i]>=s[1]\)并且\(e[i]>=e[i]\),那么答案为\(-1\).否则,答案为\(s[1]\).代码:#include<bits/stdc++.h>usingnamespacestd;usingll=longlong;constintN=1e6+10;con......
  • 小星星直播去重播放器更新至V1.14版本,新增片头设置和视频倍速播放设置,搭配本身的视频
    小星星去重播放器更新V1.14版本,增加倍速播放,片头10秒静音以及片头60秒0.8倍速慢放,优化左箭头重新播放,优化鼠标隐藏,让你的直播间更稳定,下面是小星星的详细介绍!1、视频设置,在播放列表里面打开预览窗口(注意预览窗口只支持MP4格式的视频,其余格式需要设置完成以后打开视频播放窗口)(1)......
  • Codeforces463-E.Team Work-组合数、DP
    Codeforces463-E.TeamWork题意:求\[\sum_{i=1}^n\binom{n}{i}i^k\]其中\(1\leqn\leq10^9\),\(1\leqk\leq5000\)。题解:其实这个题\(k\)的数据范围就已经暗示了做法的复杂度——应该是要去考虑根据\(k\)去递推了。首先为了方便,\(i=0\)这一项完全可以补上,用类似生成函......
  • Educational Codeforces Round 155 (Rated for Div. 2)
    比赛链接A.Rigged!题目链接就是一个比较简单的模拟就可以解决,如何判断能不能第一只需要考虑比他力量大的耐力是不是也比他大就行,而只要比他大,他就不可能第一,否则输出他的力量作为标杆就行,这样也可以避免比他力量小的也可以举起来。#include<bits/stdc++.h>usingnamespaces......
  • 她是 Codeforces 第四名,也是知名视频平台bilibili的“网红”
    在2023年9月24日~9月25日举办的EducationalCodeforcesRound155(RatedforDiv.2)上,以优秀成绩拿下第四名仅学了ACM一年的Nanani,成为最夺目的选手之一。而且虽然是仅学了一年的选手,但她取得优异成绩后,不少网友并不感到陌生,纷纷留言:这不是bilibili上天天直播爆切神仙题的妹子......
  • Ansible教程:chocolatey插件介绍及安装(Windows软件包管理器)
    介绍chocolatey.chocolatey是一个AnsibleGalaxy集合,提供了用于管理Windows上Chocolatey软件包管理器的模块和插件。Chocolatey是一个类似于Linux上的包管理器的工具,它允许在Windows系统上轻松安装、升级和卸载软件包。chocolatey.chocolatey集合包含以下模块和插件:chocolatey.choc......
  • 新版绿豆视频APP视频免授权源码 V6.6插件版
    简介:新版绿豆视频APP视频免授权源码插件版后端插件开源,可直接反编译修改方便对接苹果cms,自定义DIY页面布局!绿豆影视APP对接苹果cms所有页面皆可通过后端自由定制此版本后端源码+前端是壳(反编译版本)五款个人中心主题自由切换个人中心背景图后台可控后台控制幻灯片背景虚幻支持信......
  • webstorm插件分享
    插件修改选中区域背景......
  • CodeForces 1062F Upgrading Cities
    洛谷传送门CF传送门考虑一个子问题:求从某个点\(u\)能到达的点数。如果要精确地计算出来,最优解法只能是\(O(\frac{n^2}{w})\)的bitset。但是我们还没有利用到题目的性质,我们只需要判断一个点是否至多有一个点互不可达。考虑拓扑排序的过程,队列里面的点两两互不可达。维护......