梦入琼楼寒有月,行过石树冻无烟

D3 力布图

力布图,也被称之为力导向图(Force),通过使用布局可以物理模拟的方式将力导向图更加灵活的实现位置的简单控制与计算。而力导向图本身是一种绘图算法,即在二位或三维空间中配置一个节点,节点之间通过直线连接,这种方式被称之为连线,每次连线的长度基本长度相等,需要注意的是这种线会显得尽量不可相交

节点和连线都被施加了力的作用,根据力的作用,来计算出节点和连线的运动轨迹,最终使得让他们呈现一种静止的画面。

ID DA FA
d3.layout.force 创建一个力布图,即力导向图布局
force.size([size]) 设置胡获取力布图的作用范围,size 主要影响里到力布图的重力中心和初始的随机位置,默认为1x1
force.nodes([node]) 设置或获取力导向图的布局节点数组,默认为返回当前节点数组
force.charge([charge]) 指定电荷强度,主要用于决定是排斥还是吸引,如果输入的值是的则导致节点互相吸引,输入负值则会导致节点互相排斥。(需要足以的是,电荷是通过使用Barnes-Hut算法进行实现的)
force.links([links]) 设置或获取力布图的连接数组,默认为返回当前的连接数组
force.linkDistance([distance]) 设置链接点间目标距离的连线长度,默认为20
force.start() 启动模拟,首次创建力布图时必须被调用,然后分配节点和链接,当节点发生变化的时候将会再次被调用,通过此函数可以及那指定从何时开始计算。
force.tick() 进入力布图的仿真步骤,主要通过配合start and stop来计算出静态布局
force.on(type,listener) type是一个事件类型,主要有:start、tick、end三种,而listener则是监听器,主要用于更新节点和链接的显示位置
force.drag() 绑定一个行为允许的交互式托转节点,drag函数主要用于实现拖拽操作

布局运算


当通过下述 code 运行之后,那么数据将会通过start()进行转换,转换后的数据将会发生变化,原有的节点将会得出index、x、y、px、py、fixed、weight等额外属性,这些属性分别意思为索引、x坐标、y坐标、上一时刻的x坐标、上一时刻的y坐标、节点是否固定、节点有多少连线并与之相连

而链接数组将会返回出的数据我们可以的出两个非常重要的点即sourcetarge,分别代表来来源与目标,就如source: 0, target: 2,那么就表述数组one链接2,在这里我们需要注意的是,D3 是从0开始计算的,或者说我不能链接我自己,source: 0, targe: 0

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
/* 基本 svg 规格 */
var padding = {
width: 800,
height: 800
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height",padding.height)


/*
定义节点数组以及连线数组
nodes and edges
*/
var nodes = [
{name: "one"},
{name: "two"},
{name: "three"},
{name: "3"},
{name: "4"},
{name: "5"},
{name: "6"}
]

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}
]

/*
| 设置节点数据
|_ nodes 设置节点数组
|_ links 设置连线数组
|_ size 设置大小
|_ linkDistance 设置连线距离
|_ charge 设置电荷数
| 开始计算
|_ start 开始布局计算
*/
var force = d3.layout.force()
.nodes(nodes)
.links(edges)
.size([padding.width,padding.height])
.linkDistance(90)
.charge(-400)

force.start()
console.log(nodes)
console.log(edges)

力布图

绘制力布图

在下述的 code 中我们主要绘制了连接线及其样式、还有的绘制与样式,在这其中,主要通过使用drag来赋予圆可以被拖拽,之后通过使用.on来重新计算出链接点的坐标以及文字的坐标。

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* 基本 svg 规格 */
var padding = {
width: 800,
height: 800
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height",padding.height)


/*
定义节点数组以及连线数组
nodes and edges
*/
var nodes = [
{name: "one"},
{name: "two"},
{name: "three"},
{name: "3"},
{name: "4"},
{name: "5"},
{name: "6"}
]

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}
]

/*
| 设置节点数据
|_ nodes 设置节点数组
|_ links 设置连线数组
|_ size 设置大小
|_ linkDistance 设置连线距离
|_ charge 设置电荷数
| 开始计算
|_ start 开始布局计算
|_ category20c 由D3 所提供的 20种颜色(第三方案)
*/
var force = d3.layout.force()
.nodes(nodes)
.links(edges)
.size([padding.width,padding.height])
.linkDistance(190)
.charge(-900)

force.start()

var color = d3.scale.category20c()

/* 绘制连线 */
var lines = svg.selectAll(".forceLine")
.data(edges)
.enter()
.append("line")
.attr("class","forceLine")

/*
|_ 绘制节点圆圈
|———— 添加 circle
|———— 设置样式为 forceCircle
|———— 设置圆半径为 40r
|———— 允许被拖动
*/
var circles = svg.selectAll(".forceCircle")
.data(nodes)
.enter()
.append("circle")
.attr("r",40)
.style("fill", function (d,i) {
return color(i)
})
.call(force.drag)

/* 绘制文字 */
var texts = svg.selectAll(".forceText")
.data(nodes)
.enter()
.append("text")
.attr("class","forceText")
.attr("x", function (d) {
return d.x
})
.attr("y", function (d) {
return d.y
})
.attr("dy",".3em")
.text(function (d) {
return d.name;
})

/* 鼠标事件监听器 */
force.on("tick", function () {
/* 更新连线端点坐标 */
lines.attr("x1", function (d) {
return d.source.x
})
lines.attr("y1", function (d) {
return d.source.y
})
lines.attr("x2", function (d) {
return d.target.x
})
lines.attr("y2", function (d) {
return d.target.y
})

/* 更新节点坐标 */
circles.attr("cx", function (d) {
return d.x
})
circles.attr("cy", function (d) {
return d.y
})

/* 更新节点的文字坐标 */
texts.attr("x", function (d) {
return d.x
})
texts.attr("y", function (d) {
return d.y
})
})

CSS

1
2
3
4
5
6
7
8
9
10
11
.forceLine {
fill: none;
stroke: #d0d0d0;
stroke-width: 2px;
}

.forceText {
text-anchor: middle;
fill: white;
font-weight: bold;
}

on

如一开始介绍的函数方法一样,force.on(type.listener)是一个监听器可通过使用on进行更新节点以及链接的显示位置,当然也可以设置拖拽后固定对象的位置,可添加:

1
2
3
4
var drag = force.drag()
.on("dragstart", function (d) {
d.fixed = true
})
⬅️ Go back