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

D3 捆布图

捆布图(Bundle Layout),即捆图(Bundle),主要实现了Danny Holten的分层边缘捆绑算法,对于每个输入的连接都会计算出树的路径,从父层次结构上达到最小公共节点,之后在返回目标节点,最终生成捆绑样条,捆布图的主要作用就是计算出连线的路径

ID DA FA
d3.layout.bundle() 创建饼布图
bundle(links) 根据数组链接的来源(source)与目标(target)来计算出路径

层级布局

层级布局(Hierarchical layout)即采用了嵌套结构(父节点与字节点)来描述信息的布局,通过层级布局总共延伸了集群图(Cluster)、打包图(Pack)、分区图(Paratition)、树图(Tree)以及矩阵树图(Treemap)

布局运算


首先通过使用集群图布局进行计算出节点,来符合捆图使用的节点数据,在通过使用mapgitLink转换为节点对象,在通过使用捆图布局转换成捆图数据。

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/*
基本 svg 样式
*/
var padding = {
width: 800,
height: 800
}

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

/* 捆图数据 */
var gitName = {
name: "",
children: [
{name: "gitee"},
{name: "Kooder"},
{name: "CEF4Delphi"},
{name: "Debian-Pi-Aarch64"},
{name: "QwidgetExe"},
{name: "debian-cn"},
{name: "PowerToys"},
{name: "FastAPI"},
{name: "vscode-drawio"},
{name: "PaddleDetection"},
{name: "QWidgetExe"},
{name: "Stduino IDE"},
{name: "react-photo-view"},
{name: "Waifu2x-Extension-GUI"},
{name: "egg-decorator-router"},
{name: "PowerToys"},
{name: "InjectFix"},
{name: "dnSpy"},
{name: "proxyer"},
{name: "wenyan-lang"},
{name: "Ventoy"}
]
}

var gitLink = [
{
source: "gitee",
target: "Kooder"
},
{
source: "gitee",
target: "CEF4Delphi"
},
{
source: "gitee",
target: "CEF4Delphi"
},
{
source: "gitee",
target: "Debian-Pi-Aarch64"
},
{
source: "gitee",
target: "QwidgetExe"
},
{
source: "gitee",
target: "debian-cn"
},
{
source: "gitee",
target: "PowerToys"
},
{
source: "gitee",
target: "FastAPI"
},
{
source: "gitee",
target: "vscode-drawio"
},
{
source: "gitee",
target: "PaddleDetection"
},
{
source: "gitee",
target: "QWidgetExe"
},
{
source: "gitee",
target: "Stduino IDE"
},
{
source: "gitee",
target: "react-photo-view"
},
{
source: "gitee",
target: "Waifu2x-Extension-GUI"
},
{
source: "gitee",
target: "egg-decorator-router"
},
{
source: "gitee",
target: "PowerToys"
},
{
source: "gitee",
target: "InjectFix"
},
{
source: "gitee",
target: "dnSpy"
},
{
source: "gitee",
target: "proxyer"
},
{
source: "gitee",
target: "wenyan-lang"
},
{
source: "gitee",
target: "Ventoy"
}
]

/*
|_ 集群图布局
|__|- 计算出集群图布局节点
|_ 捆图布局
*/
var cluster = d3.layout.cluster()
.size([360, padding.width /2 -50])
.separation(function (a, b) {
return (a.parent == b.parent ? 1 : 2) / a.depth
})

var bundle = d3.layout.bundle()

var nodes = cluster.nodes(gitName)
console.log(nodes)

/*
|_ 将连线端转换为节点对象
|_ 调用捆布图转换数据
*/
var oLinks = map(nodes, gitLink)
console.log(oLinks)

var links = bundle(oLinks)
console.log(links)

/*
将 source and target 替换为节点对象
*/
function map(nodes,links) {
var hash = []
for (var i = 0;i<nodes.length;i++) {
hash[nodes[i].name] = nodes[i]
}
var resultLinks = []
for(var i = 0;i<links.length; i++) {
resultLinks.push({
source:hash[
links[i].source
],
target:hash[
links[i].target
]
})
}
return resultLinks
}

捆布图


通过放射性线段生成器来绘制出捆图的路径(path),再使用node计算出节点位置并添加结点 <circle> and <text>最终绘制出捆图

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/*
基本 svg 样式
*/
var padding = {
width: 800,
height: 800
}

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

/* 捆图数据 */
var gitName = {
name: "",
children: [
{name: "gitee"},
{name: "Kooder"},
{name: "CEF4Delphi"},
{name: "Debian-Pi-Aarch64"},
{name: "QwidgetExe"},
{name: "debian-cn"},
{name: "PowerToys"},
{name: "FastAPI"},
{name: "vscode-drawio"},
{name: "PaddleDetection"},
{name: "QWidgetExe"},
{name: "Stduino IDE"},
{name: "react-photo-view"},
{name: "Waifu2x-Extension-GUI"},
{name: "egg-decorator-router"},
{name: "InjectFix"},
{name: "dnSpy"},
{name: "proxyer"},
{name: "wenyan-lang"},
{name: "Ventoy"}
]
}

var gitLink = [
{
source: "gitee",
target: "Kooder"
},
{
source: "gitee",
target: "CEF4Delphi"
},
{
source: "gitee",
target: "CEF4Delphi"
},
{
source: "gitee",
target: "Debian-Pi-Aarch64"
},
{
source: "gitee",
target: "QwidgetExe"
},
{
source: "gitee",
target: "debian-cn"
},
{
source: "gitee",
target: "PowerToys"
},
{
source: "gitee",
target: "FastAPI"
},
{
source: "gitee",
target: "vscode-drawio"
},
{
source: "gitee",
target: "PaddleDetection"
},
{
source: "gitee",
target: "QWidgetExe"
},
{
source: "gitee",
target: "Stduino IDE"
},
{
source: "gitee",
target: "react-photo-view"
},
{
source: "gitee",
target: "Waifu2x-Extension-GUI"
},
{
source: "gitee",
target: "egg-decorator-router"
},
{
source: "gitee",
target: "PowerToys"
},
{
source: "gitee",
target: "InjectFix"
},
{
source: "gitee",
target: "dnSpy"
},
{
source: "gitee",
target: "proxyer"
},
{
source: "gitee",
target: "wenyan-lang"
},
{
source: "gitee",
target: "Ventoy"
}
]

/*
|_ 集群图布局
|__|- 计算出集群图布局节点
|_ 捆图布局
*/
var cluster = d3.layout.cluster()
.size([360, padding.width /2 -50])
.separation(function (a, b) {
return (a.parent == b.parent ? 1 : 2) / a.depth
})

var bundle = d3.layout.bundle()

var nodes = cluster.nodes(gitName)
console.log(nodes)

/*
|_ 将连线端转换为节点对象
|_ 调用捆布图转换数据
*/
var oLinks = map(nodes, gitLink)
console.log(oLinks)

var links = bundle(oLinks)
console.log(links)

/*
将 source and target 替换为节点对象,
并将新的元素增加到数组之中
*/
function map(nodes,links) {
var hash = []
for (var i = 0;i<nodes.length;i++) {
hash[nodes[i].name] = nodes[i]
}
var resultLinks = []
for(var i = 0;i<links.length; i++) {
resultLinks.push({
source:hash[
links[i].source
],
target:hash[
links[i].target
]
})
}
return resultLinks
}

/*
|_ 放射性线段生成器
|__|-设置插值模式为 bundle 即捆图
|__| 将张力设置为 45
*/
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.45)
.radius(function (d) {
return d.y
})
.angle(function (d) {
return d.x / 180 * Math.PI
})

/* 坐标组 <g> 元素 */
gBundel = svg.append("g")
.attr("transform", "" +
"translate(" + (padding.width/2) + "," + (padding.height/2) + ")")

/* 颜色比例尺 */
var color = d3.scale.category20c()

/* 绘制连线路径 <path> */
var link = gBundel.selectAll(".link")
.data(links)
.enter()
.append("path")
.attr("class","link")
.attr("d", line)

/* 添加节点 */
var node = gBundel.selectAll(".node")
.data(nodes.filter(function (d) {
return !d.children
}))
.enter()
.append("g")
.attr("class","node")
.attr("transform", function (d) {
return "rotate(" + (d.x - 90) + ") translate(" +
d.y + ")" + "rotate(" + (90 -d.x) + ")"
})

/* 绘制节点圆形 <circle> */
node.append("circle")
.attr("r",1)
.style("fill", function (d,i) {
return color(i)
})

/* 绘制节点名称 <text> */
node.append("text")
.attr("dy",".12em")
.style("text-anchor","middle")
.text(function (d) {
return d.name
})

CSS

1
2
3
4
5
6
7
8
9
.link {
fill: transparent;
stroke: #e5e5e5;
}
.node {
font-weight: bold;
font-size: 12px;
fill: #242424;
}
⬅️ Go back