3d-force-graph是什么?
一个 Web 组件,使用强制导向的迭代布局来表示 3 维空间中的图形数据结构。使用ThreeJS /WebGL 进行 3D 渲染,使用d3-force-3d或ngraph作为底层物理引擎。
3d-force-graph可以做些什么?
参考以下效果:
人物关系图: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