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

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;
}
⬅️ Go back