3D知识图谱可视化/关系可视化

最近准备搞个大活儿,将2D的知识图谱转为3D形式,现在先进行技术储备。

四个知道

论文&设计标准

网络拓扑三维可视化系统的研究与实现,把各种布局算法给讲解了,这个对我们开发实现来说,很重要:

https://xueshu.baidu.com/s?wd=paperuri%3A%287a25f46a8160b05dd9bc7dc905154224%29&filter=sc_long_sign&tn=SE_xueshusource_2kduw22v&sc_vurl=http%3A%2F%2Fwww.doc88.com%2Fp-1458576603467.html&ie=utf-8&sc_us=12546661473725591341

阿里数据的知识图谱可视化设计标准

https://www.ui.cn/detail/533594.html

(精)知识图谱可视化技术在美团的实践与探索

有设计+程序实现的介绍,很赞

https://www.51cto.com/article/706621.html

技术方案

通过解读3d-force-graph这个开源项目来分析技术实现方案。

一些技巧

将常用的某一批API封装为一个对象,比如这个就是作图常用的一批API,包括sence、camera、renderer、controls、tbControls等等:

1
import ThreeRenderObjects from 'three-render-objects';

方案设计,可以参考index.d.ts文件,先把各个API、参数类型给定好。

将this传入作图元素方法中

类似这样:

1
2
3
4
5
6
// 布局
layout.use(this.option.layout.type, this)
// 绘制节点
drawNodes(this);
// 绘制连线
drawEdges(this);

可以有效解决配置数据四处传递的代码可读性差的问题。

可以用来局部代替我的state全局对象。

球体

这个插件是支持传入Three的对象来自定义形状的,比如img-nodes这个Demo中,将节点设置为图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
// 自定义节点形状
.nodeThreeObject(({ img }) => {
// use a sphere as a drag handle
const obj = new THREE.Mesh(
new THREE.SphereGeometry(7),
new THREE.MeshBasicMaterial({
depthWrite: false,
transparent: true,
opacity: 0,
})
);

// add img sprite as child
const imgTexture = new THREE.TextureLoader().load(`./imgs/${img}`);
const material = new THREE.SpriteMaterial({ map: imgTexture });
const sprite = new THREE.Sprite(material);
sprite.scale.set(12, 12);
obj.add(sprite);

return obj;
})
// 传入数据对象
.graphData(gData);

线条

有用到THREE.BufferGeometry

这个还需要详细调试下drawEdges()这个方法。

为什么线是6个点呢,是通过画一个管子来实现么?

1
2
3
4
5
6
7
8
9
10
const lineGeometry = new THREE.BufferGeometry();
lineGeometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(2 * 3), 3));
let linePos = lineGeometry.getAttribute('position');
linePos.array[0] = fromNode.position[0]
linePos.array[1] = fromNode.position[1]
linePos.array[2] = fromNode.position[2]
linePos.array[3] = toNode.position[0]
linePos.array[4] = toNode.position[1]
linePos.array[5] = toNode.position[2]
let lineMesh = new THREE.Line(lineGeometry, lineMaterial);

流动的线条

有提供一个API:

linkDirectionalParticles([num, str or fn])

可以看下这个具体是怎么实现的。

一个炫酷的效果:

1
for(var i = 1; i < 1000; i++){flow.pipe.addPipe(0,i, {})}

光源

这个代码很值得学习:

1
2
3
4
5
6
7
8
9
10
11
let addLight = function(that){
that.option.lights.forEach(lightOpt=>{
let light = new THREE[lightOpt.type](...lightOpt.args);
if(typeof lightOpt.set == 'function'){
lightOpt.set(light)
}
that.scene.add(light);
})
}

export default addLight;

组件默认的配套光源:

1
2
3
4
5
6
7
8
9
10
11
12
lights:[
{
type:'PointLight',
args: [0xffffff],
set: function(light){
light.position.set(400, 200, 300)
}
},{
type:'AmbientLight',
args: [0x444444],
}
]

文本

A sprite based text component for ThreeJS. The text is drawn to canvas, converted into a Texture and then used as a material on a Sprite. Because a sprite is being used, the text will always face the camera, and has its orientation fixed relative to the camera.

通过linkThreeObject来进行文本自定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
.jsonUrl('../datasets/miserables.json')
.nodeLabel('id')
.nodeAutoColorBy('group')
.linkThreeObjectExtend(true)
.linkThreeObject((link) => {
// extend link with text sprite
const sprite = new SpriteText(`${link.source} > ${link.target}`);
sprite.color = 'lightgrey';
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);
});

// Spread nodes a little wider
Graph.d3Force('charge').strength(-120);

我看郑浩琦是通过CSS2DObject,用DIV画的label,这是为什么?性能更高么?

点击事件

关于事件,有个很关键的事情,就是要提供一个获取当前焦点/悬浮节点的方法,用于在各个事件中,将该节点传递给事件回调函数。

旋转

平移

拖动

ThreeDragControls

可以设定不同状态(dragstart、drag、dragend)的表现形式

猜测是将整个图放入一个group,然后给这个group设置x、y、z的坐标变更

另外拖动起止位置给作图的div设置了一个grabbable属性,这个应该还会用于其他地方,保证在拖动时其他事件不会触发?

缩放

整个页面

单个几何体

可以通过geometry.scale这个API来实现。

布局

自定义

设置每个几何体的position即可

力导向

直接使用d3-force-3d这个组件。

目前做了一些附加功能,比如支持设置停止布局的上限时间、节点三个维度坐标的成倍放大等等。

随机

给三个维度设置了一个随机上限值,然后生成随机数值,赋值给x、y、z。

数据更新

Three.js本身已经做到数据驱动了,只需要修改物体的position属性即可。

动画

个性化的球体

我们可以用不同样式的球体来标注不同的分类或者属性,因此可以参考这个Demo实现多样的球体:

https://threejs.org/examples/#webgl_sandbox

工具

awesome-3d:

https://github.com/taurenshaman/awesome-3d

AR/MR/VR is the future, and 3D model is the basics of all of them.

trackingjs(飞车游戏就是用这个做的):

https://trackingjs.com/

3D力导向关系图组件:

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

这是demo:

https://vasturiano.github.io/3d-force-graph/example/large-graph/

各种demo列表:

https://vasturiano.github.io/3d-force-graph/

D3的3D-force:

https://github.com/vasturiano/d3-force-3d

D3的力导向布局应该是我们重点琢磨的内容,我们要自己做组件,肯定得把这个摸透。

这个代码也不多,认真学习下。

D3-3D-force的调试示例:

https://bl.ocks.org/vasturiano/f59675656258d3f490e9faa40828c0e7

这个东西用来作为知识宫殿也很不错吧

经典Demo

光影效果:

https://vasturiano.github.io/3d-force-graph/example/bloom-effect/

层级结构布局:

https://vasturiano.github.io/3d-force-graph/example/tree/

应用了力导向的碉堡的大屏:

https://zhuanlan.zhihu.com/p/44566239

(精)Nature可视化:

https://www.bilibili.com/video/BV12J411Y7EY/

(牛逼)在线版本:

Nature 150 Interactive

https://www.nature.com/immersive/d41586-019-03165-4/reftree2.html?=jupiter