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

📖 earlier posts 📖

D3 项目构建

当了解上述D3介绍之后,我们可以通过引入 d3.js来直接帮助我们支持d3.js语法,首先我们可以通过使用min.js版本即官方所提供的在线引入方式,如:

1
<script src="https://d3js.org/d3.v6.min.js"></script>

通过使用d3.js所提供的本地js文件中,通常会有两种js,分别为.js以及min.js,一种是在开发环境中进行使用的,而min.js则是在项目发布的时候直接进行使用,两者实现了同样的功能,但唯一不同的是min.js将所有空格都合并了,而.js的code则符合js的代码格式规范,当引入之后我们可以通过绘制一个svg来证明d3.js是否引入成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>D3.js</title>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<circle></circle>
<script>
var width = 200;
var height = 200;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
svg.append("circle")
.attr("cx","50px")
.attr("cy","50px")
.attr("r","50px")
.attr("fill","blue");
</script>
</body>
</html>

D3 折线图

折线图(Line Chart)是一种常见的数据图表之一,主要可以表示随时间而变化的连续数据,可以表示数据的支持与对比。在下列例图中,我们以d3.rgb(2550,0,0) , d3.rgb(0,0,255), d3.rgb(200,100,255), d3.rgb(0,0,100)来分别表示中国、美国、日本、欧盟2010~2019的GDP增长折线图。

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
/* 定义基本结构数据 */
var padding = {
width: 1400,
height: 1000,
top: 250,
right: 50,
bottom: 50,
left: 150,
totalMax: 0,
markStep: 80
}

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

/* 数据 */
var databases = [
{
country: "China",
total: [
[2010,39959795970621.48],
[2011,49572577715343.02],
[2012,56010676972614.25],
[2013,62825888774607.08],
[2014,68768668100531.38],
[2015,72614671348154.00],
[2016,73741967153063.72],
[2017,80812913356157.63],
[2018,91213919284620.56],
[2019,93742077498697.55]
]
},
{
country: "Usa",
total: [
[2010,98416829331664.20],
[2011,102030827915318.41],
[2012,106326874443245.41],
[2013,110185821032061.61],
[2014,115058818792197.00],
[2015,119637894766824.02],
[2016,122856229947754.81],
[2017,128136749246503.20],
[2018,135100516865529.61],
[2019,140700555399600.02]
]
},
{
country: "Japan",
total: [
[2010,37418864084048.47],
[2011,40421259256175.07],
[2012,40721612856309.18],
[2013,33845220187590.04],
[2014,31841024698668.49],
[2015,28815151672041.19],
[2016,32314493883388.93],
[2017,31949018103634.34],
[2018,32526323537619.18],
[2019,33359784337901.19]
]
},
{
country: "European Union",
total: [
[2010,95476038828627.59],
[2011,103354095012906.06],
[2012,96081433361419.91],
[2013,100395059650212.97],
[2014,102621853711647.17],
[2015,88918041106989.56],
[2016,73741967153063.72],
[2017,96883204984092.13],
[2018,104804012847872.91],
[2019,102581383668424.91]
]
}
];

/* 筛选出最大数据 */
for (var i=0; i< databases.length; i++) {
var currTotal = d3.max(databases[i].total, function (d) {
return d[1]
})
if (currTotal > padding.totalMax)
padding.totalMax = currTotal
}

/* 坐标轴规格 */
var xScale = d3.scale.linear()
.domain([2010,2019])
.range([0,912]);

var yScale = d3.scale.linear()
.domain([0,140000000000000])
.range([padding.height - padding.top - padding.bottom, 0])

var linePath = d3.svg.line()
.x(function (d) {
return xScale(d[0])
})
.y(function (d) {
return yScale(d[1])
})

/* 粉分别定义颜色 */
var color = [d3.rgb(2550,0,0) , d3.rgb(0,0,255), d3.rgb(200,100,255), d3.rgb(0,0,100) ]

/* 路径 */
svg.selectAll("path")
.data(databases)
.enter()
.append("path")
.attr("transform","translate(" + padding.left + "," + padding.top +")")
.attr("d", function (d) {
return linePath(d.total)
})
.attr("fill","none")
.attr("stroke-width",2)
.attr("stroke", function (d,i) {
return color[i]
})

/* 文字标记与文字路径 */
var gMark = svg.selectAll(".gMark")
.data(databases)
.enter()
.append("g")
.attr("transform",function (d,i) {
return "translate(" + (padding.left + i * padding.markStep) + "," + (padding.height - padding.bottom + 40) + ")"
})

gMark.append("rect")
.attr("x",0)
.attr("y",0)
.attr("width",10)
.attr("height",10)
.attr("fill",function (d,i) {
return color[i]
})
gMark.append("text")
.attr("dx",15)
.attr("dy",".5em")
.attr("fill","black")
.text(function (d) {
return d.country
})

/* 坐标轴 */
var xAxis = d3.svg.axis()
.scale(xScale)
.tickFormat(d3.format("d"))
.orient("bottom")

var yAxis = d3.svg.axis()
.scale(yScale)
.tickFormat(d3.format("d"))
.orient("left")

svg.append("g")
.attr("class","axis")
.attr("transform","translate(" + padding.left + "," + (padding.height - padding.bottom) + ")")
.call(xAxis)

svg.append("g")
.attr("class","axis")
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.call(yAxis)

增加交互


下述 code中我们主要先设置givecuo,将鼠标经过时显示出数据的详细信息,并通过line自动根据鼠标位置移动参考线来增加体验:

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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/* 定义基本结构数据 */
var padding = {
width: 900,
height: 800,
top: 100,
right: 50,
bottom: 50,
left: 150,
totalMax: 0,
markStep: 80
}

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

/* 数据 */
var databases = [
{
country: "China",
total: [
[2010,39959795970621.48],
[2011,49572577715343.02],
[2012,56010676972614.25],
[2013,62825888774607.08],
[2014,68768668100531.38],
[2015,72614671348154.00],
[2016,73741967153063.72],
[2017,80812913356157.63],
[2018,91213919284620.56],
[2019,93742077498697.55]
]
},
{
country: "Usa",
total: [
[2010,98416829331664.20],
[2011,102030827915318.41],
[2012,106326874443245.41],
[2013,110185821032061.61],
[2014,115058818792197.00],
[2015,119637894766824.02],
[2016,122856229947754.81],
[2017,128136749246503.20],
[2018,135100516865529.61],
[2019,140700555399600.02]
]
},
{
country: "Japan",
total: [
[2010,37418864084048.47],
[2011,40421259256175.07],
[2012,40721612856309.18],
[2013,33845220187590.04],
[2014,31841024698668.49],
[2015,28815151672041.19],
[2016,32314493883388.93],
[2017,31949018103634.34],
[2018,32526323537619.18],
[2019,33359784337901.19]
]
},
{
country: "European Union",
total: [
[2010,95476038828627.59],
[2011,103354095012906.06],
[2012,96081433361419.91],
[2013,100395059650212.97],
[2014,102621853711647.17],
[2015,88918041106989.56],
[2016,73741967153063.72],
[2017,96883204984092.13],
[2018,104804012847872.91],
[2019,102581383668424.91]
]
}
];

/* 筛选出最大数据 */
for (var i=0; i< databases.length; i++) {
var currTotal = d3.max(databases[i].total, function (d) {
return d[1]
})
if (currTotal > padding.totalMax)
padding.totalMax = currTotal
}

/* 坐标轴规格 */
var xScale = d3.scale.linear()
.domain([2010,2019])
.range([0,912]);

var yScale = d3.scale.linear()
.domain([0,140000000000000])
.range([padding.height - padding.top - padding.bottom, 0])

var linePath = d3.svg.line()
.x(function (d) {
return xScale(d[0])
})
.y(function (d) {
return yScale(d[1])
})

/* 粉分别定义颜色 */
var color = [d3.rgb(2550,0,0) , d3.rgb(0,0,255), d3.rgb(200,100,255), d3.rgb(0,0,100) ]

/* 路径 */
svg.selectAll("path")
.data(databases)
.enter()
.append("path")
.attr("transform","translate(" + padding.left + "," + padding.top +")")
.attr("d", function (d) {
return linePath(d.total)
})
.attr("fill","none")
.attr("stroke-width",1)
.attr("stroke", function (d,i) {
return color[i]
})

/*
添加提示框
1.首先定义一个直线,默认为不可见
2.新建一个 <div> 矩形框,默认为不可见
3.其次单个 <div> 添加对应的标题,之后通过 .des 分别添加 desText、desColor
*/
var vLine = svg.append("line")
.attr("class", "focusLine")
.style("display", "none");

var hLine = svg.append("line")
.attr("class","focusLine")
.style("display", "none")

var givecuo = d3.select("body")
.append("div")
.attr("class","givecuo")
.style("opacity",0.0)

var title = givecuo.append("div")

var des = givecuo.selectAll(".des")
.data(databases)
.enter()
.append("div")

var desColor = des.append("div")
.attr("class","desColor")
var desText = des.append("div")
.attr("class","desText")


var focusCircle = svg.append("g")
.attr("class","focusCircle")
.style("display","none")

focusCircle.append("circle")
.attr("r",4.5)

focusCircle.append("text")
.attr("dx",10)
.attr("dy","1em")
/*
设置一个透明的监视鼠标事件矩形
当鼠标移出时,再次将透明度设置为0
*/
svg.append("rect")
.attr("class","overlay")
.attr("x",padding.left)
.attr("y",padding.top)
.attr("width",padding.width - padding.left - padding.right)
.attr("height", padding.height - padding.top - padding.bottom)
.on("mouseover", function () {
givecuo.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY + 20) + "px")
.style("opacity", 1.0)
focusCircle.style("display",null)

vLine.style("display",null);
hLine.style("display",null);
})
.on("mousemove", mousemove)
.on("mouseout", function (d) {
focusCircle.style("display","none")
givecuo.style("opacity",0.0)
})

/*
当鼠标经过时调用 mousemove函数
*/
function mousemove() {
// 折线的源数据
var data = databases[0].total

// 获取鼠标相对的透明矩形左上角的坐标为 (0,0)
var mouseX = d3.mouse(this)[0] - padding.left;
var mouseY = d3.mouse(this)[1] - padding.top;

// 通过比例尺的反函数计算出原数据的值 y0 是 GDP的值
var x0 = xScale.invert(mouseX)
var y0 = yScale.invert(mouseY)

// 开始四舍五路
x0 = Math.round(x0)

// 分别查找索引和数据
var bisect = d3.bisector(function (d) {
return d[0]
}).left
var index = bisect(data, x0)

/* 分别获取 年份和 total */
var x1 = data[index][0]
var y1 = data[index][1]

/* 分别通过使用 x 和 y 的比例尺来计算出焦点位置 */
var focuX = xScale(x1) + padding.left
var focuY = yScale(y1) + padding.top

// 通过评议将焦点到指定位置
focusCircle.attr("transform","translate(" + focuX + "," + focuY + ")"); // 分别获取年份以及数据
var year = x0
var total = []

for (var k=0;k<databases.length;k++) {
total[k] = {
country: databases[k].country,
value: databases[k].total[index][1]
}
}

// 设置描述文字内容
title.html("<strong>" + year + "年</strong>");
desColor.style("background-color", function (d,i) {
return color[i]
})

// 分别显示国家以及当前数据
desText.html( function (d,i) {
return total[i].country + "\t" + "<strong>" + total[i].value + "</strong>";
})

// 提示框位置
givecuo.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY + 20) + "px");


// 将垂直线对其坐标
var vlx = xScale(data[index][0]) + padding.left;
var vly = yScale(data[index][1]) + padding.top;

// 垂直线的终点和起点
vLine.attr("x1", vlx)
.attr("y1",padding.top)
.attr("x2",vlx)
.attr("y2",padding.height - padding.bottom);

hLine.attr("x1",vlx)
.attr("y1",vly)
.attr("x2",padding.left)
.attr("y2",vly)

}

/* 文字标记与文字路径 */
var gMark = svg.selectAll(".gMark")
.data(databases)
.enter()
.append("g")
.attr("transform",function (d,i) {
return "translate(" + (padding.left + i * padding.markStep) + "," + (padding.height - padding.bottom + 40) + ")"
})

gMark.append("rect")
.attr("x",0)
.attr("y",0)
.attr("width",10)
.attr("height",10)
.attr("fill",function (d,i) {
return color[i]
})
gMark.append("text")
.attr("dx",15)
.attr("dy",".5em")
.attr("fill","black")
.text(function (d) {
return d.country
})

/* 坐标轴 */
var xAxis = d3.svg.axis()
.scale(xScale)
.tickFormat(d3.format("d"))
.orient("bottom")

var yAxis = d3.svg.axis()
.scale(yScale)
.tickFormat(d3.format("d"))
.orient("left")

svg.append("g")
.attr("class","axis")
.attr("transform","translate(" + padding.left + "," + (padding.height - padding.bottom) + ")")
.call(xAxis)

svg.append("g")
.attr("class","axis")
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.call(yAxis)

CSS

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
.axis path,   /* 坐标轴路径设置 */
.axis line { /* 设置 坐标轴的线条 */
fill: none; /* 设置坐标轴宽度 */
stroke: #929292; /* 坐标轴颜色 */
shape-rendering: crispEdges; /* 将形状渲染为清晰的边缘 */
}

.axis text {
font-size: 14px;
stroke: #929292;
}

.focusLine {
stroke: #b2b2b2;
stroke-dasharray: 10,10;
}

.givecuo {
width: auto;
height: auto;
position: absolute;
font-size: 12px;
line-height: 20px;
text-align: left;
background-color: white;
border-radius: 5px;
}

.givecuo .title {
font-size: 16px;
text-align: left;
border-left: 3px solid red;
padding-left: 5px;
}

.givecuo .desColor {
width: 10px;
height: 10px;
float: left;
margin: 8px 8px 1px 8px;
}
.givecuo .desText {
display: inline;
}

.overlay {
fill: transparent;
}

D3 对角线生成器


对角线生成器(Diagonal enerator),主要使用三次贝赛尔曲线将两个点进行连接,通过使用d3.svg.diagonal()创建,共有两个访问器一个函数,分别为:“source()、target()、projection()

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

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

/* 对角线坐标 */
var databases = {
source: {
x: 2, y: 0
},
target: {
x: 300, y: 900
}
}

/* 定义对角线生成器 */
var diagonal = d3.svg.diagonal()

svg.append("path")
.attr("d",diagonal(databases))
.attr("fill","none")
.attr("stroke","#51d576")
.attr("stroke-width",4)

projection


projection() 主要是将坐标进行转换,通常target坐标会首先调用该投影进行转换,之后生成新的路径:

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

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

/* 对角线坐标 */
var databases = {
source: {
x: 2, y: 0
},
target: {
x: 300, y: 900
}
}

/* 定义对角线生成器 */
var diagonal = d3.svg.diagonal()
.projection(function(d) {
var x = d.x * 2
var y = d.y * 2
return [x,y]
})
svg.append("path")
.attr("d",diagonal(databases))
.attr("fill","none")
.attr("stroke","#51d576")
.attr("stroke-width",4)

通过使用:

1
2
3
4
5
6
var diagonal = d3.svg.diagonal()
.projection(function(d) {
var x = d.x * 2
var y = d.y * 2
return [x,y]
})

那么最终的target路径将会在原有的大小下将会被放大成2倍。

D3 弦生成器


弦生成器(Chord Generator),主要用于将两个进行连接,可通过使用d3.svg.chord()进行声明,分别有五个访问器:“source(起始弧)、target(终止弧)、radius(半径)、startAngle(起始角度)、endAngle(终止角度)”,弦生成器是生成弦图的重要组成部分之一。

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

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

/* 弦数据 */
var databases = {
source: {
startAngle: 0.2,
endAngle: Math.PI * 0.3,
radius: 100
},
target: {
startAngle: Math.PI * 1.0,
endAngle: Math.PI * 1.6,
radius: 100
}
}

/* 声明弦 */
var chord = d3.svg.chord()

/* 弦绘制 */
svg.append("path")
.attr("d",chord(databases))
.attr("transform","translate(200,200)")
.attr("fill","none")
.attr("stroke","#51d576")
.attr("stroke-width",5)

D3 弧生成器


弧生成器(Arc Generator)可以通过innerRadius(内半径访问器)、outerRadius(外半径访问器)、startAngle(起始角度)、endAngle(终止角度访问器)四类访问器来绘制圆弧以及饼状图和弦图等图表。

innerRadius and outerRadius

一弧

在下述 code 中,startAngle or startAngle即代表圆弧的度数,通常的情况下需要使用0代替,同样180°也需要通过3.1415926来进行代替。需要注意的是,他是按照顺时针方向来进行填充或增加的,其中outerRadiusinnerRadius分别表示圆弧半径以及内弧半径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var padding = {
width: 1800,
height: 500
}

var svg = d3.select('body')
.append("svg")
.attr("width",padding.width)
.attr("height",padding.height)
var databases = {
startAngle: 9,
endAngle: Math.PI * 1.10 /* 提供基本的数学功能和常数在内的对象 */
}

var archPath = d3.svg.arc()
.innerRadius(200)
.outerRadius(100)

svg.append("path")
.attr("d", archPath(databases))
.attr("transform","translate(255,255)")
.attr("stroke","#026836")
.attr("stroke-width","10px")
.attr("fill","#22a547")

多弧


在 D3 中,多弧主要用于绘制诸如饼状图等学术图表,通过使用多弧并将其startAngleendAngle形成首位呼应之势,最终他们的度数共为360°即可:

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
/* svg 创建与大小 */
var padding = {
width: 800,
height: 800,
}

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

/* 饼图数据 */
var databases = [
{ startAngle: 0, endAngle: Math.PI * 0.6 },
{ startAngle: Math.PI * 0.6, endAngle: Math.PI },
{ startAngle: Math.PI, endAngle: Math.PI * 1.7 },
{ startAngle: Math.PI * 1.7, endAngle: Math.PI * 2}
]

/* innner and outer 与 code */
var archPath = d3.svg.arc()
.innerRadius(0)
.outerRadius(200)

var color = d3.scale.category20b()

/* 圆位置与基本样式 */
svg.selectAll("path")
.data(databases)
.enter()
.append("path")
.attr("d", function(d) {
return archPath(d)
})
.attr("transform", "translate(255,255)")
.attr("stroke", "black")
.attr("stroke-width", "1px")
.attr("fill", function(d,i) {
return color(i)
})

/* 文字样式与位置 */
svg.selectAll("text")
.data(databases)
.enter()
.append("text")
.attr("transform", function(d) {
return "translate(255,255)" +
"translate(" + archPath.centroid(d) + ")"
})
.attr("text-anchor", "middle")
.attr("fill","white")
.attr("font-size","10px")
.text(function(d) {
return Math.floor((d.endAngle - d.startAngle) * 180 / Math.PI) + "°"
})

D3 区域生成器


区域生成器(Area Generator)主要作用是生成一块区域,通常区域选择器的数据访问器六个,即”x(),x0()、x1()、y0()、y1()“,可通过使用d3.svg.area()进行创建:

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
var padding = {
width: 800,
height: 800
}

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

var databases = [80,120,130,70,60,90]

var areaPath = d3.svg.area()
.x(function(d,i) {
return 50 + i * 80
})
.y0(function(d,i) {
return padding.height / 2
})
.y1(function(d,i) {
return padding.height / 2 - d
})

svg.append("path")
.attr("d",areaPath(databases))
.attr("stroke","#92f8c5")
.attr("stroke-width", "3px")
.attr("fill","#bdfedf")


通过上述的 code ,我们可以得到svg路径为:”M50,330L130,280L210,270L290,330L370,340L450,320L450,400L370,400L290,400L210,400L130,400L50,400Z“的数据,这一切归根于x、y0、y1的区域访问器,除此之外,我们依然可以使用interpolate方法,如:

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
var padding = {
width: 800,
height: 800
}

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

var databases = [80,120,130,70,60,90]

var areaPath = d3.svg.area()
.x(function(d,i) {
return 50 + i * 80
})
.y0(function(d,i) {
return padding.height / 2
})
.y1(function(d,i) {
return padding.height / 2 - d
})
.interpolate("basis")
svg.append("path")
.attr("d",areaPath(databases))
.attr("stroke","#92f8c5")
.attr("stroke-width", "3px")
.attr("fill","#bdfedf")

D3 路径与线段生成器

线段


线段生成器(Line segment generator)的主要作用就是,让原本只有两个点的线段,可以被划分成无数点,通过设置通过设置线段生成器可以使得绘画出各样形状,而仅仅通过[x1,y1],[x2,y2]的方法是无法达到的,在这个情况和需求的情况下,D3 提供了路径生成器(Path Generator)的概念,可以自动根据数据生成路径,而用于生成线段的过程被称之为线段生成器(Line Generator)

line

1
2
3
4
5
6
7
8
9
10
11
12
var svg = d3.select("body")
.append("svg")
.attr("width",800)
.attr("height",800)

svg.append("line")
.attr("x1",0)
.attr("y1",400)
.attr("x2",200)
.attr("y2",10)
.attr("stroke","black")
.attr("stroke-width","3px")

path

1
2
3
4
5
6
7
8
9
var svg = d3.select("body")
.append("svg")
.attr("width",800)
.attr("height",800)

svg.append("path")
.attr("d","M20,201L300,20")
.attr("stroke","black")
.attr("stroke-width","3px")

线段生成器

线段生成器(Line Generator),主要可以通过使用d3.svg.line()方法进行创建,通过线段的点数据,和每一项的[x,y]来进行定义坐标,对于线段生成器,D3 提供了如下方法:

ID DA FA
d3.svg.line() 创建一个线段生成器
line() 使用线段生成器绘制数据
line.x() 设置或获取线段x坐标访问器(使用什么作为线段的x坐标)
line.y() 设置或获取 y坐标的访问器
line.interpolate() 设置或获取线段的插值模式
line.tension() 设置或获取张力系数 需要插值模式为:cardinal、cardinal-open、cardinal-closed时有效
line.defined 设置或获取一个访问器,用于确定线段是否存在 只有当数据存在时会被绘制

d3.svg.line()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var svg = d3.select("body")
.append("svg")
.attr("width",800)
.attr("height",800)

var lines = [[100,100],[200,200],[400,200],[500,100]]

var linePath = d3.svg.line()

svg.append("path")
.attr("d",linePath(lines))
.attr("stroke","black")
.attr("stroke-width","1px")
.attr("fill","none")

在上述的 code 中,主要提供了四个点,即他此时我们可以通过使用 d3.svg.line(lines)让其通过使用线段生成器进行创建,之后自动生成了路径为M100,100L200,200L400,200L500,100的折线路径。

访问器与 interpolate()

访问器

在D3中,访问器即line.x()line.y(),主要通过使用:

1
2
3
4
5
6
7
function x(d) {
return d[0]
}

function y(d) {
return d[1]
}

通常访问器主要的作用就是对xy坐标进行一些修改,通过使用访问器,我们可以达到经过调教很久的坐标数据,如:

1
2
3
4
5
6
7
8
9
var lines = [100,200,300,400]

var linePath = d3.svg.line()
.x(function(d) {
return d;
})
.y(function(d,i) {
return i%2==0 ? 50 : 0
})

因此我们会得到M100,50L200,0L300,50L400,0的SVG路径:

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
var padding = {
width: 1800,
height: 1800
}

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

var lines = [100,200,300,400]

var linePath = d3.svg.line()
.x(function(d) {
return d;
})
.y(function(d,i) {
return i%2==0 ? 50 : 10
})

svg.append("path")
.attr("d", linePath(lines))
.attr("stroke", "black")
.attr("stroke-width","2px")
.attr("fill","none")

那么根据上述的lineslinePath,将会组成一组坐标即:”[100,50],[200,0],[300,50],[400,0]“,而他们的组成主要与line以及linePath有关:

interpolate

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

| ID | DA |
| --- | --- |
| interpolate("```basis"```) | ![](https://49812933408852955071488026628034-1301075051.cos.ap-nanjing.myqcloud.com/20210401014352.png) |
| interpolate("```cardinal```") | ![](https://49812933408852955071488026628034-1301075051.cos.ap-nanjing.myqcloud.com/20210401014101.png) |
| interpolate("```stop```) | ![](https://49812933408852955071488026628034-1301075051.cos.ap-nanjing.myqcloud.com/20210401014259.png) |
```js
var padding = {
width: 1800,
height: 1800
}

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

var lines = [100,200,300,400]

var linePath = d3.svg.line()
.x(function(d) {
return d;
})
.y(function(d,i) {
return i%2==0 ? 50 : 0
})
.interpolate("basis")

svg.append("path")
.attr("d", linePath(lines))
.attr("stroke", "black")
.attr("stroke-width","2px")
.attr("fill","none")

D3 颜色与插值

颜色

RGB

RGB 色彩模式 (RGB color mode),是一种工业界的颜色标准,主要有红(R,Red)、绿(G,Green)、蓝(B,Blue)三色,即RGB,通过相互之间的叠加来取得各种各样的颜色。RGB 主要覆盖了目前所有常用和人类肉眼可用的颜色,也是最为常用的颜色模式之一。

除此之外,RGB 的亮度各有 0~255个亮度,通过他们三个通道的叠加可以组合出大约1678万中颜色,通常也会被称之为1600万色或千万色,还可以被称之为24位色,在D3中他有如下方法:

ID DA FA
d3.rgb(r,g,b) 分别输出 r\g\b来创建颜色(范围都是0,255)
d3.rgb(color) 通过输出相应的字符串来创建颜色:
RGB 十进制 rgb(255, 255, 255)
RGB 十六进制 #ff0d0d
RGB 十六进制缩写 #ff0
HSL 十进制 hsl(120, 0.5, 0.5)
颜色名称 red
rgb.brighter() 使得颜色变得更加明亮 RGB的各个通道 x 0.7 ^ -k(如果 k没有,那么k的值就为1),需要注意的是只有当某通道的值的范围在 30~255 之间时,才会进行相应的计算
rgb.darker() 颜色变得更加黯淡 RGB各个值 x 0.7 ^ k
rgb.hsl() 返回该颜色对应的 HSL
rgb.toString() 以字符串的形式返回颜色值 #ffffff
brighter() darker() 返回一个新的颜色对象,当前对象并不会进行改变
hsl() 返回当前颜色所对应的 HSL

方法

1
2
3
4
var color = d3.rgb(166,12,12);
console.log(color.has())
console.log(color.darker(2).toString())
console.log(color.brighter(2))

在上述的 code中,我们可以通过使用has()来返回 colorhas颜色值,也可以通过使用toString()来返回该颜色的十六进制darker()与brighter()数值为2即亮度为2,暗度为2的颜色变值

HSL


HSL 与 RGB 的区别就是,他是通过将 RGB 色彩模式内的各个组成颜色放在圆柱坐标系的表示方法。这种表示方法会比基于笛卡尔坐标系的集合结构中的RGB更加直观。

HSL 颜色空间主要通过色相(H,Hue)、饱和度(S,Saturation)、亮度(L,Lightness)三个相互叠加来得到各种颜色,他的色相范围是0°~360°,饱和度和亮度分别为0~1。其中色相°是一个角度,每个角度都可以代表其中的一个颜色,例如代表红色,120°代表绿色,240°代表蓝色。

通常的情况下,饱和度越大,颜色就越鲜艳,而亮度值主要控制色彩的明暗变化,当亮度越大,就越接近与白色,反之值越小就越接近黑色,默认的情况下RGB与HSL的颜色是可以互相转换的

方法

ID DA
d3.hsl(h,s,l) 根据h、s、l值来创建HAL颜色
d3.hsl(color) 根据字符串来创建HSL颜色
hsl.brighter() 使得颜色变得更亮
hsl.darker() 使得颜色变得更暗
hsl.rgb() 返回对应的rgb颜色
hsl.toString 返回以RGB字符串十六进制形式输出的颜色

1
2
3
4
5
6
7
var rgb = d3.rgb(166,12,12)
console.log(rgb.hsl())

var hsl = d3.hsl(0,0.8651685393258428,0.34901960784313724)
console.log(hsl.rgb().toString())
console.log(hsl.darker(2).toString())
console.log(hsl.brighter(2).toString())

插值


插值(Interpolation)主要用于计算介于两个颜色之间的颜色,主要可以通过使用d3.interpolateRgb()来处理RGB颜色之间的插值运算。而如果是HSL颜色则可以通过使用d3.interpolateHsl()来处理HSL颜色之间的插值运算。除此之外,D3 还提供了一种更加智能的方法即:d3.interpolate(),他会自动的判断颜色类型,还可以处理字符串之间的差值。

1
2
3
4
5
6
7
var ragOne = d3.rgb(255,0,0)
var ragTwo = d3.rgb(0,255,255)

var interpolate = d3.interpolate(ragOne,ragTwo)

console.log(interpolate(0))
console.log(interpolate(0.5))

在上述的 code 中,ragOneragTwo的插值通过interpolate来自动判断颜色类型,并根据设置的插值进行计算,当插值为0时返回了#ff0000颜色,而当插值为0.5时,则返回了#808080颜色。

D3 散点图


散点图(scatterplot),主要在回归分析(regression analysis)中主要用于确定两种或两种以上的变量间互相依赖的定量关系的一种统计分析方法。散点图也可以用于表述趋势的增加的聚合数据

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
/* 定义高度与宽度,其中包括 x和y轴的宽度 */
var padding = {
width: 700,
height: 700,
xAxisWidth: 500,
yAxisWidth: 500,
right: 30,
bottom: 30,
left: 130,
}

/* 定义数据 x,y */
var center = [[0.8,1],[1,0.8],[0.4,0.3],[0.6,0.4],[0.2,0.6],[0.8,0.2],[0.2,0.1]]

/* 定义颜色序数比例尺 */
var color = d3.scale.category10()

/*
1.选择body元素,并添加一个<svg>元素
2.并设置<svg>的高\宽为 600.
*/
var svg = d3.select("body")
.append("svg")
.attr("width", padding.width)
.attr("height", padding.height)

/* x/y 轴比例尺 */
var xScale = d3.scale.linear()
.domain([0, 1 * d3.max(center, function(d) {
return d[1];
})])
.range([0, padding.xAxisWidth])

var yScale = d3.scale.linear()
.domain([0, 1 * d3.max(center, function(d) {
return d[1];
})])
.range([0, padding.yAxisWidth])

/* 定义圆 */
var cirlce = svg.selectAll("circle")
.data(center)
.enter()
.append("circle")
.attr("fill", function(d,i) {
return color(i)
})
.attr("cx", function(d) {
return padding.left + xScale(d[0])
})
.attr("cy", function(d) {
return padding.height - padding.bottom - yScale(d[1])
})
.attr("r", 5)

/* x/y 坐标轴选择比例尺 */
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5)

yScale.range([padding.yAxisWidth, 0]) /* 反向输出 */

var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)

/* 在 svg 元素中添加一个包含坐标轴的数据并计算出位置 */
svg.append("g")
.attr("class","axis")
.attr("transform", "translate(" + padding.left + "," + (padding.height - padding.bottom) + ")")
.call(xAxis)
svg.append("g")
.attr("class","axis")
.attr("transform", "translate(" + padding.left + "," + (padding.height - padding.bottom - padding.yAxisWidth) + ")")
.call(yAxis)

CSS

1
2
3
4
5
6
7
8
9
10
11
.axis path,   /* 坐标轴路径设置 */
.axis line { /* 设置 坐标轴的线条 */
fill: none; /* 设置坐标轴宽度 */
stroke: #929292; /* 坐标轴颜色 */
shape-rendering: crispEdges; /* 将形状渲染为清晰的边缘 */
}

.axis text {
font-size: 14px;
stroke: #929292;
}

D3 坐标轴与刻度

坐标轴(Axis)通常在一些学术图表中进行运用,通常很多的情况下图表中数据都是经过比例尺内计算的出的大小与坐标位置,这个时候就需要刻度尺来进行标注,来辅助读者进行观看,可以通过下图来明显感受到差距:

通常的情况下,读者可会对图标的美观性专业性都会选择有坐标轴和刻度的图表,通常在 d3.js 中绘画出一个坐标轴需要以下方法的支持:

ID DA FA
d3.svg.axis() 创建一个默认的新坐标轴
axis(选择集) 将此坐标轴绑定到指定的选择集中(需要具有<svg><g>元素)
axis.scale() 设定或获取坐标轴的比例尺
axis.orient(top[bottom、left、right]) 设定或获取坐标轴的方向
刻度
axis.ticks() 设置或获取坐标轴的分割数(默认为10
axis.tickValues() 设置或获取坐标轴的指定刻度
axis.tickSize() 设置或获取坐标轴内外的刻度长度(默认为6
axis.innerTickSize() 设置或获取坐标轴内刻度的长度
axis.outerTickSize() 设置或获取坐标轴外刻度的长度
axis.tickFormat() 设置或获取刻度尺的格式单位

坐标轴

定义


在上图中我们主要先通过定义坐标轴的基本高度与宽度以及最终的位置,其主要分为四个阶段,分别为位置、添加、比例尺、坐标轴以及一个坐标轴的外部方法通过四个阶段可以生成一个标准且带有刻度尺的一个坐标轴。

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
// 定义坐标轴高度与宽度以及(x,y)的位置
var padding = {
width: 600,
height: 600,
translate: "translate(20,20)"
}

/*
1.选择body元素,并添加一个<svg>元素
2.并设置<svg>的高\宽为 600.
*/
var svg = d3.select("body")
.append("svg")
.attr("width", padding.width)
.attr("height", padding.height)

/* 比例尺 */
var xScale = d3.scale.linear()
.domain([0,10]) /* 定义域范围 */
.range([0,500]) /* 值域即为刻度尺长度 */

/*
坐标轴选择比例尺,设置比例尺位置为 "bottom" 即底部
*/
var axis = d3.svg.axis()
.scale(xScale)
.orient("bottom")

/*
在 svg 元素中添加一个包含坐标轴的:
1. 位置/走向 @transform
2. 样式 @class
3. 参数传递
*/
var gAxis = svg.append("g")
.attr("transform", padding.translate)
.attr("class","axis")
.call(axis) /* 将自身作为参数,传递给某个参数 */

bottom and left

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
```js
/* 定义高度与宽度,其中包括 x和y轴的宽度 */
var padding = {
width: 700,
height: 700,
xAxisWidth: 500,
yAxisWidth: 500,
right: 30,
bottom: 30,
left: 130,
}

// 定义数据
var center = [[1,1]]

/*
1.选择body元素,并添加一个<svg>元素
2.并设置<svg>的高\宽为 600.
*/
var svg = d3.select("body")
.append("svg")
.attr("width", padding.width)
.attr("height", padding.height)

/* x/y 轴比例尺 */
var xScale = d3.scale.linear()
.domain([0, 1 * d3.max(center, function(d) {
return d[1];
})])
.range([0, padding.xAxisWidth])

var yScale = d3.scale.linear()
.domain([0, 1 * d3.max(center, function(d) {
return d[1];
})])
.range([0, padding.yAxisWidth])

/* x/y 坐标轴选择比例尺 */
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5)

yScale.range([padding.yAxisWidth, 0]) /* 反向输出 */

var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)

/* 在 svg 元素中添加一个包含坐标轴的数据并计算出位置 */
svg.append("g")
.attr("class","axis")
.attr("transform", "translate(" + padding.left + "," + (padding.height - padding.bottom) + ")")
.call(xAxis)
svg.append("g")
.attr("class","axis")
.attr("transform", "translate(" + padding.left + "," + (padding.height - padding.bottom - padding.yAxisWidth) + ")")
.call(yAxis)

样式


除了生成 svg 坐标轴之外,我们还需要通过为path(路径)、line(刻度尺)、text(文本)这三个svg元素添加样式,让其变得简洁。

less

1
2
3
4
5
6
7
8
9
10
11
12
@color: #929292;
.axis path, /* 坐标轴路径设置 */
.axis line { /* 设置 坐标轴的线条 */
fill: none; /* 设置坐标轴宽度 */
stroke: @color; /* 坐标轴颜色 */
shape-rendering: crispEdges; /* 将形状渲染为清晰的边缘 */
}

.axis text {
font-size: 14px;
stroke: @color;
}

css

1
2
3
4
5
6
7
8
9
10
11
.axis path,   /* 坐标轴路径设置 */
.axis line { /* 设置 坐标轴的线条 */
fill: none; /* 设置坐标轴宽度 */
stroke: #929292; /* 坐标轴颜色 */
shape-rendering: crispEdges; /* 将形状渲染为清晰的边缘 */
}

.axis text {
font-size: 14px;
stroke: #929292;
}

刻度

刻度,主要依赖于坐标轴,是一个专业学术图表中较为重要的一部分。我们可以通过属性或方法来定义刻度的方向、间隔、长度、文字格式等。

刻度位置

ticks()


通常的情况下ticks()主要用于控制刻度尺的生成,在 d3.js 中 ticks的概念是指:“一个至到另一个分割的特定数量”,即分割的数量。如将ticks()设置为5,那么将会输出具有刻度为0,2,4,6,8,10的坐标轴。

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
// 定义坐标轴高度与宽度以及(x,y)的位置
var padding = {
width: 600,
height: 600,
translate: "translate(20,20)"
}

/*
1.选择body元素,并添加一个<svg>元素
2.并设置<svg>的高\宽为 600.
*/
var svg = d3.select("body")
.append("svg")
.attr("width", padding.width)
.attr("height", padding.height)

/* 比例尺 */
var xScale = d3.scale.linear()
.domain([0,10]) /* 定义域范围 */
.range([0,500]) /* 值域即为刻度尺长度 */

/*
坐标轴选择比例尺,设置比例尺位置为 "bottom" 即底部
*/
var axis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);

/*
在 svg 元素中添加一个包含坐标轴的:
1. 位置/走向 @transform
2. 样式 @class
3. 参数传递
*/
var gAxis = svg.append("g")
.attr("transform", padding.translate)
.attr("class","axis")
.call(axis) /* 将自身作为参数,传递给某个参数 */

tickValues()

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

```js
// 定义坐标轴高度与宽度以及(x,y)的位置
var padding = {
width: 600,
height: 600,
translate: "translate(20,20)"
}

/*
1.选择body元素,并添加一个<svg>元素
2.并设置<svg>的高\宽为 600.
*/
var svg = d3.select("body")
.append("svg")
.attr("width", padding.width)
.attr("height", padding.height)

/* 比例尺 */
var xScale = d3.scale.linear()
.domain([0,10]) /* 定义域范围 */
.range([0,500]) /* 值域即为刻度尺长度 */

/*
坐标轴选择比例尺,设置比例尺位置为 "bottom" 即底部
*/
var axis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickValues([0, 2.2, 4, 6, 8, 10])

/*
在 svg 元素中添加一个包含坐标轴的:
1. 位置/走向 @transform
2. 样式 @class
3. 参数传递
*/
var gAxis = svg.append("g")
.attr("transform", padding.translate)
.attr("class","axis")
.call(axis) /* 将自身作为参数,传递给某个参数 */

刻度长度

tickSize()

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

```js
// 定义坐标轴高度与宽度以及(x,y)的位置
var padding = {
width: 600,
height: 600,
translate: "translate(20,20)"
}

/*
1.选择body元素,并添加一个<svg>元素
2.并设置<svg>的高\宽为 600.
*/
var svg = d3.select("body")
.append("svg")
.attr("width", padding.width)
.attr("height", padding.height)

/* 比例尺 */
var xScale = d3.scale.linear()
.domain([0,10]) /* 定义域范围 */
.range([0,500]) /* 值域即为刻度尺长度 */

/*
坐标轴选择比例尺,设置比例尺位置为 "bottom" 即底部
*/
var axis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickSize(6,8)

/*
在 svg 元素中添加一个包含坐标轴的:
1. 位置/走向 @transform
2. 样式 @class
3. 参数传递
*/
var gAxis = svg.append("g")
.attr("transform", padding.translate)
.attr("class","axis")
.call(axis) /* 将自身作为参数,传递给某个参数 */

innerTickSize() and outerTickSize()

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

```js
// 定义坐标轴高度与宽度以及(x,y)的位置
var padding = {
width: 600,
height: 600,
translate: "translate(20,20)"
}

/*
1.选择body元素,并添加一个<svg>元素
2.并设置<svg>的高\宽为 600.
*/
var svg = d3.select("body")
.append("svg")
.attr("width", padding.width)
.attr("height", padding.height)

/* 比例尺 */
var xScale = d3.scale.linear()
.domain([0,10]) /* 定义域范围 */
.range([0,500]) /* 值域即为刻度尺长度 */

/*
坐标轴选择比例尺,设置比例尺位置为 "bottom" 即底部
*/
var axis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.innerTickSize(6)
.outerTickSize(8)

/*
在 svg 元素中添加一个包含坐标轴的:
1. 位置/走向 @transform
2. 样式 @class
3. 参数传递
*/
var gAxis = svg.append("g")
.attr("transform", padding.translate)
.attr("class","axis")
.call(axis) /* 将自身作为参数,传递给某个参数 */

文字格式

TickFormat

code 将会在刻度后加上一个```百分比```作为单位:
1
2
3
4
5
6

```js
var gAxis = svg.append("g")
.attr("transform", padding.translate)
.attr("class","axis")
.call(axis.tickFormat(d => d+ "%")) /* 将自身作为参数,传递给某个参数 */
📖 more posts 📖