快速构建可视化知识图谱

基本概念

知识图谱是一种知识库,其中的数据通过图结构数据模型拓扑整合而成。知识图谱通常被用来存储彼此之间具有相互联系的实体。

null
经过查阅资料发现,目前市面上已知的的可视化图表库都已支持或者直接提供知识图谱类型图表。同时也发现有专门针对关系网这类图表的JS库(Cytoscape.js)。

这里我们选取了可视化工具的佼佼者Echarts和D3来研究,最后我们再顺便研究下Cytoscape库。

什么是ECharts

由百度团队开发的开源免费JavaScript 可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。官网

基本开发

开发流程

  1. 根据需求确定图表类型(例如关系图)
  2. 查找示例找到所需图表类型配置
  3. 根据要求选择性的进行配置

官方示例中找到我们所需要的关系图,查看配置结合api进行开发

null
通过文档我们发现关系图有三种布局方式。

null
三种布局大致展示如下,基本配置项没有区别,其中只有force布局可以设置节点是否能拖拽
none:

null
circular:

null
force:

null
由此可以得知三种布局都能表示节点间多对多的关系,没有特殊要求只是展示节点间关系的话可以使用none布局;需要有动效交互可以用force布局;circluar布局就看业务需求而定。

什么是D3

D3 的全称是(Data-DrivenDocuments),顾名思义是一个被数据驱动的文档。其实是对数据进行可视化的JavaScript库。D3将强大的可视化和交互技术与数据驱动的DOM操作方法相结合,能最大限度地使用现代浏览器的性能同时为设计可视化界面保留了最大的自由度。

该库更接近底层,与 echarts 不同,d3不提供现成的图表直接使用,而是通过操作 svg进行图表绘制,所以拥有极大的自由度,几乎可以实现任何 2d 的设计需求。官网

基本开发

要绘图就要有基本的画布,所以第一步就是创建画布。HTML5提供了两种画布:SVG和Canvas。
D3库是以SVG为主,同时也支持Canvas画布,虽然没有明确要求说一定要使用SVG绘图,但是D3 提供了众多的 SVG 图形的生成器,因此,建议使用 SVG 画布。

1.创建画布

1
2
3
4
5
6
7
var width = 400;
var height = 400;

var svg = d3.select("body") // 选择器,类似于jquery语法
.append('svg') // 插入svg节点。还是类似于jquery语法
.attr('width', width) // 设置svg画布宽高
.attr('height', height)

Copy

有了画布就可以在画布上进行作画了。
2.布局(数据转换)
布局,可以理解成 “制作常见图形的函数”,有了它制作各种相对复杂的图表就方便多了。

null
从上面的图可以看到,布局的作用是:将不适合用于绘图的数据转换成了适合用于绘图的数据

对于我们需要的知识图谱D3也提供了对应的布局 :Force — 力学图、力导向图。

OK,我们先定义节点数据和连线数据

1
2
3
4
5
6
7
8
var nodes = [ { name: "桂林" }, { name: "广州" },
{ name: "厦门" }, { name: "杭州" },
{ name: "上海" }, { name: "青岛" },
{ name: "天津" } ];

var edges = [ { source : 0 , target: 1 } , { source : 0 , target: 2 } ,
{ source : 0 , target: 3 } , { source : 1 , target: 4 } ,
{ source : 1 , target: 5 } , { source : 1 , target: 6 } ];

Copy

一看就知道这些数据是不能作图的,因为不知道节点和连线的坐标,需要用到布局。

1
2
3
4
5
6
7
var force = d3.layout.force()  // force布局接口
.nodes(nodes) // 指定节点数组
.links(edges) // 指定连线数组
.size([width, height]) // 指定作用域范围
.linkDistance(150) // 指定连线长度
.charge([-400]) // 相互间的作用力
force.start()

Copy

这时再打印nodes和edges就会发现数据里多了好多新字段

null
新增字段含义如下:

  • index:节点的索引号
  • px, py:节点上一个时刻的坐标
  • x, y:节点的当前坐标
  • weight:节点的权重

有了这些数据就可以绘制图形了。
3.绘制

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
26
27
28
29
30
// 添加连线
var svg_edges = svg.selectAll('line')
.data(edges) // 绑定一个数组到选择集上,数组的各项值分别与选择集的各元素绑定
.enter() // /指定选择集的enter部分,下面具体介绍
.append('line')
.style('stroke', '#ccc')
.style('stroke-width', 1);

var color = d3.scale.category20();
// 添加节点
var svg_nodes = svg.selectAll('circle')
.data(nodes)
.enter()
.append('circle')
.attr('r', 20)
.style('fill', function(d, i) {
return color(i)
})
.call(force.drag) // 使节点能拖动

var svg_texts = svg.selectAll('text')
.data(nodes)
.enter()
.append('text')
.style('fill', 'black')
.attr('dx',20)
.attr('dy', 8)
.text(function(d) {
return d.name
})

Copy

打开浏览器输入地址进行访问,图形堆叠在一起,为啥???

null
原来力导向图是不断运动的,每一时刻都在发生更新,因此,必须不断更新节点和连线的位置。力导向图布局 force 有一个事件 tick,每进行到一个时刻,都要调用它,更新的内容就写在它的监听器里就好。

1
2
3
4
5
6
7
8
9
10
11
12
force.on('tick', function() {
svg_edges.attr('x1', function(d) {return d.source.x})
.attr('y1', function(d) {return d.source.y})
.attr('x2', function(d) {return d.target.x})
.attr('y2', function(d) {return d.target.y});

svg_nodes.attr('cx', function(d) {return d.x})
.attr('cy', function(d) {return d.y});

svg_texts.attr('x', function(d) {return d.x})
.attr('y', function(d) {return d.y})
})

Copy

再到浏览器里刷新页面,OK,关系图出来了。

null
里面每一个节点都是可以拖动,同时也能引发其他节点的变化。

ECharts和D3对比

在进行对比前,我们先来看下上面D3那节提到的HTML5的两种图形渲染技术:SVG和Canvas。所有的三方库底层技术都是基于这两中技术实现的。

SVG Canvas
矢量图不依赖分辨率 依赖分辨率
支持DOM事件处理器 不支持事件处理器
适合带有大型渲染区域的应用(如地图) 最适合图像密集型的应用比如游戏
复杂度高会减慢渲染速度(任何频繁操作dom的应用都快不了) 一旦图形绘制完成,就不会再得到浏览器的关注 如果位置或者大小变化整个区域都要重新绘制
可以使用CSS渲染文本 文本渲染能力较弱

对比很明显,各有优势:
SVG适用于数据量不大,应用存在大量交互的场景
Canvas比较适用于事件交互少或者数据量大刷新快的场景

OK,基于上面的分析我们来对比ECharts和D3。

ECharts D3
Canvas为主4.0+也支持SVG SVG为主4.0+也支持Canvas
提供很多图表通过简单配置可以满足大部分需求 不直接提供图表,需要手动实现绘制代码
自由度差,不可定制 自由度很大,基本可以自己绘制任何图表
提供完善中文文档,示例功能完善 提供完善英文文档,大部分示例需要完善才能使用,主要是参考
开发效率高,通过快速配置即可完成 大部分图表需要自己开发,学习成本大,太底层了,对可视化理论、数学逻辑都一定要求
大数据量性能较好 需要手动优化, 比如借助react、vue的虚拟dom
不支持事件处理器,官方也提供了事件处理机制监听用户操作行为,功能有限 支持事件处理器可直接操作dom

使用ECarts有较大的优势,开发效率高,动画、事件等实现也比较完善,正常情况下基本没有bug。而数据量不是特别大或者事件交互比较精细的场景可以采用D3 SVG来实现。

任何一款工具型产品,都会在”自由度”和”简便性”两者间进行权衡与取舍,EChart使用简单而自由度差,D3自由度高而使用门槛高,具体使用哪个看具体场景业务来定。

结论

针对我们所调研的关系网类知识图谱两种技术都能实现。如果只是展示图表给用户看,而不需要太多修改我们可以使用echarts;如果场景中存在跟图表较多的交互操作可以尝试使用D3。

最后我们再研究下Cytoscape.js库。

Cytosapce.js

Cytoscape.js是一个用于分析和可视化的图论库。这涵盖了从网络生物学到社交网络分析的各种用途。 主要是描述点和线之间的关系 。

null

基本使用

1.引入cytoscapejs

  • 直接从github上下载最新的release包本地引入
  • CND引入
  • npm 安装

更多引入方式
2.绘制图形
首先新建一个容器

1
2
3
4
5
6
7
8
9
10
<style>
#cy {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left:0;
}
</style>
<div id="cy"></div>

Copy

请注意,.js在初始化时使用HTML DOM元素容器的尺寸进行布局和渲染。因此,在任何Cytoscape.js相关代码之前将CSS样式表放在中是非常重要的。否则,可能偶尔会错误地报告尺寸,从而导致不希望的行为。 这是官方提示,DOM的样式定义需要在head标签内避免发生问题

参考示例进行图表初始化

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
var cy = cytoscape({
container: document.getElementById('cy'), // 容器dom
boxSelectionEnabled: false,
autounselectify: true,
elements: [
{ // 节点a
data: {id: 'a'},
},
{ // 节点b
data: {id: 'b'}
},
{ // 节点c
data: {id: 'c'},
},
{
data: {id: 'd'}
},
{
data: {id: 'e'},
},
{ // 连线ae,源节点a,目标节点e
data: {id: 'ae', weight: 1, target: 'e', source: 'a'}
},
{
data: {id: 'ab', weight: 3, target: 'b', source: 'a'}
},
{
data: {id: 'be', weight: 4, target: 'e', source: 'b'}
},
{
data: {id: 'bc', weight: 5, target: 'c', source: 'b'}
},
{
data: {id: 'ce', weight: 6, target: 'e', source: 'c'}
},
{
data: {id: 'cd', weight: 2, target: 'd', source: 'c'}
},
{
data: {id: 'de', weight: 7, target: 'e', source: 'd'}
}
],
style: [
{ // 节点样式
selector: 'node',
style: {
'content': 'data(id)'
}
},
{ // 连线样式
selector: 'edge',
style: {
'curve-style': 'bezier',
'width': 4,
'line-color': '#ddd',
'target-arrow-color': '#ddd',
'target-arrow-shape': 'triangle'
}
}
],
layout: {
name: 'breadthfirst',
directed: true,
root: '#a',
padding: 10
}
})

Copy

OK,图表生成成功。

null
下面贴出初始化参数列表

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
26
27
28
29
30
31
32
33
34
35
36
37
var cy = cytoscape({
// 基本参数
container: undefined, // 容器
elements: [ /* ... */ ], // 指定为普通对象的元素数组
style: [ /* ... */ ], // 用于设置图形样式的样式表
layout: { name: 'grid' /* , ... */ }, // 指定布局选项的普通对象

// 初始化视图:
zoom: 1,
pan: { x: 0, y: 0 },

// 交互参数:
minZoom: 1e-50,
maxZoom: 1e50,
zoomingEnabled: true,
userZoomingEnabled: true,
panningEnabled: true,
userPanningEnabled: true,
boxSelectionEnabled: false,
selectionType: 'single',
touchTapThreshold: 8,
desktopTapThreshold: 4,
autolock: false,
autoungrabify: false,
autounselectify: false,

// 渲染参数:
headless: false,
styleEnabled: true,
hideEdgesOnViewport: false,
hideLabelsOnViewport: false,
textureOnViewport: false,
motionBlur: false,
motionBlurOpacity: 0.2,
wheelSensitivity: 1,
pixelRatio: 'auto'
});

Copy

更多API信息请查看文档 http://js.cytoscape.org/#core/graph-manipulation

后续

在调研天眼查、企查查两个网站的关系图谱源码时我们发现,天眼查不仅使用了D3.js,同时还用到了一个cola的库;企查查则是使用了d3.js + cytoscape.js来实现。

天眼查部分逻辑:

null

1
2
3
4
5
6
7
8
// 这是d3的force布局
var force = d3.layout.force()
.nodes(nodes)
.links(edges)
.size([width, height])
.linkDistance(150)
.charge([-400])
force.start()

Copy

通过cola.js确实可以查到对应的结果。cola.js

null
由此可知cola是个约束器可以很好的跟D3、Cytoscape.js等库集成,而天眼查用到的是它作为d3适配器的一个功能,作为对d3 force布局的一个扩展补充。
在Cytoscape中使用方法是通过扩展来实现

1
cy.layout({ name: 'cola' /* and maybe some other options */ });

Copy

企查查部分逻辑

null
图表父节点存在cytoscape的class,可以很明确的知道图表是用cytoscpase.js实现的,哪为什么还要用d3呢???

null
继续向上查看代码,确实找到了d3的使用逻辑。

null

null
结合cytoscape代码里的layout可以猜出它的基本逻辑应该是先通过d3计算出所有节点最终的位置信息,再通过cytoscape的preset布局绘制关系图。下图是cytoscape的官网说明。

null
调试后的数据确实比原数据多了d3的位置信息,后面数据转化也确实用到了这些信息
null

null
不管是天眼查还是企查查都没有单独使用d3,而是d3+cola.js或者d3+cytoscape.js来实现这类关系图表。我推测可能跟d3的force布局特性有关(没找到相应资料,瞎猜的)。
force布局正式名称是力导图,正常情况下所有节点都是运动中的,拖拽某个节点不单单是被拖动的节点会移动,其他受其力作用的节点也会一起移动,所以如果需要天眼查这种静态的效果,就要想办法再绘画完后固定所有节点,比如企查查用的cytoscape的preset布局和天眼查使用cola来stop动画,以及给每个节点设置fixed。

null

力导向图形绘制算法是以美观的方式绘制图形的一类算法。它们的目的是将一个图的节点定位在二维或二维三维空间中,这样所有的边或多或少都是等长的,交叉的边越少越好。方法是根据边和节点的相对位置在边和节点的集合中分配力,然后利用这些力模拟边和节点的运动