实验二、D3数据可视化基础
一、实验目的
熟悉D3数据可视化的使用方法
二、实验原理
D3 的全称是(Data-Driven Documents),是一个被数据驱动的文档,其实就是一个 JavaScript 的函数库,使用它主要是用来做数据可视化的。本次实践主要介绍D3 一些最基本的使用方法,以及生成一些比较简单的图表。D3 是一个 JavaScript 函数库。它只有一个文件,在HTML 中引用即可。有两种方法:(1)下载 D3.js 的文件,解压后,在 HTML 文件中包含相关的js文件即可。(2)还可以直接包含网络的链接,这种方法较简单: 但使用的时候要保持网络连接有效,不能再断网的情况下使用。D3 可以接受几乎任何数字数组,字符串,或对象(本身包含其他数组或键/值对),可以处理 JSON 和GeoJSON。
三、实验环境
Notepad++等编辑工具、D3.js库
四、实验步骤
(一)、制作一个简单的柱形图
1、打开Notepad++,新建文件,并编辑好html框架。
1 <html> 2 <head> 3 <meta charset="utf-8"> 4 <title>完整的柱形图</title> 5 </head> 6 7 <style> 8 9 </style> 10 11 <body> 12 <script src="http://d3is.org/d3.v3.min,js" charset="utf-8"></script> 13 <script> 14 15 </script> 16 </body> 17 </html>
2、添加 SVG 画布。
要绘图,首要需要的是一块绘图的“画布”。HTML5 提供两种强有力的“画布”:SVG 和 Canvas。SVG,指可缩放矢量图形(Scalable Vector Graphics),是用于描述二维矢量图形的一种图形格式,是由万维网联盟制定的开放标准。SVG 使用XML 格式来定义图形,除了 IE8 之前的版本外,绝大部分浏览器都支持 SVG,可将SVG文本直接嵌入 HTML 中显示。Canvas 是通过 JavaScript 来绘制 2D 图形,是 HTML 5 中新增的元素。D3 虽然没有明文规定一定要在 SVG 中绘图,但是D3 提供了众多的SVG图形的生成器,它们都是只支持 SVG 的。因此,建议使用 SVG 画布。在body 标签中加入代码。
1 <body>
2 <script src="http://d3is.org/d3.v3.min,js" charset="utf-8"></script>
3 <script>
4
5 // 画布大小
6 var width = 400;
7 var height = 400;
8
9 // 在 body 里添加一个 SVG 画布
10 var svg = d3.select("body")
11 .append("svg")
12 .attr("width", width)
13 .attr("height", height);
14
15 // 画布周边的空白
16 var padding = { left: 30, right: 30, top: 20, bottom: 20 };
17
18
19 </script>
20 </body>
3、定义数据和比例尺。 在添加画布的代码后面加入如下代码。
1 // 定义一个数组 2 var dataset = [16, 23, 54, 46, 33, 24, 19, 37, 9]; 3 4 // x轴的比例尺 5 var xScale = d3.scale.ordinal() 6 .domain(d3.range(dataset.length)) 7 .rangeRoundBands([0, width - padding.left - padding.right]); 8 9 // y轴的比例尺 10 var yscale = d3.scale.linear() 11 .domain([0, d3.max(dataset)]) 12 .range([height - padding.top - padding.bottom, 0]);
4、定义坐标轴。
1 // 定义x轴
2 var xAxis = d3.svg.axis()
3 .scale(xScale)
4 .orient("bottom");
5
6 // 定义y轴
7 var yAxis = d3.svg.axis()
8 .scale(yscale)
9 .orient("left");
5、添加矩形和文字元素。
1 // 矩形之间的空白
2 var rectPadding = 4;
3
4 // 添加矩形元素
5 var rects = svg.selectAll(".MyRect")
6 .data(dataset)
7 .enter()
8 .append("rect")
9 .attr("class", "MyRect")
10 .attr("transform", "translate(" + padding.left + "," + padding.top + ")")
11 .attr("x", function(d, i) {
12 return xScale(i) + rectPadding / 2;
13 })
14 .attr("y", function(d) {
15 return yscale(d);
16 })
17 .attr("width", xScale.rangeBand() - rectPadding)
18 .attr("height", function(d) {
19 return height - padding.top - padding.bottom - yscale(d);
20 });
21
22 // 添加文字元素
23 var texts = svg.selectAll(".MyText")
24 .data(dataset)
25 .enter()
26 .append("text")
27 .attr("class", "MyText")
28 .attr("transform", "translate(" + padding.left + "," + padding.top + ")")
29 .attr("x", function(d, i) {
30 return xScale(i) + rectPadding / 2;
31 })
32 .attr("y", function(d) {
33 return yscale(d);
34 })
35 .attr("dx", function() {
36 return (xScale.rangeBand() - rectPadding) / 2;
37 })
38 .attr("dy", function(d) {
39 return 20;
40 })
41 .text(function(d) {
42 return d;
43 });
6、添加坐标轴的元素
1 // 添加x轴
2 svg.append("g")
3 .attr("class", "axis")
4 .attr("transform", "translate(" + padding.left + "," + (height - padding.bottom) + ")")
5 .call(xAxis);
6
7 // 添加y轴
8 svg.append("g")
9 .attr("class", "axis")
10 .attr("transform", "translate(" + padding.left + "," + padding.top + ")")
11 .call(yAxis);
12 </script>
13 </body>
14 </html>
完整代码如下:
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <title>完整的柱形图</title>
6 </head>
7
8 <body>
9 <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
10 <script>
11 // 画布大小
12 var width = 400;
13 var height = 400;
14
15 // 在 body 里添加一个 SVG 画布
16 var svg = d3.select("body")
17 .append("svg")
18 .attr("width", width)
19 .attr("height", height);
20
21 // 画布周边的空白
22 var padding = { left: 30, right: 30, top: 20, bottom: 20 };
23
24 // 定义一个数组
25 var dataset = [16, 23, 54, 46, 33, 24, 19, 37, 9];
26
27 // x轴的比例尺
28 var xScale = d3.scale.ordinal()
29 .domain(d3.range(dataset.length))
30 .rangeRoundBands([0, width - padding.left - padding.right]);
31
32 // y轴的比例尺
33 var yscale = d3.scale.linear()
34 .domain([0, d3.max(dataset)])
35 .range([height - padding.top - padding.bottom, 0]);
36
37 // 定义x轴
38 var xAxis = d3.svg.axis()
39 .scale(xScale)
40 .orient("bottom");
41
42 // 定义y轴
43 var yAxis = d3.svg.axis()
44 .scale(yscale)
45 .orient("left");
46
47 // 矩形之间的空白
48 var rectPadding = 4;
49
50 // 添加矩形元素
51 var rects = svg.selectAll(".MyRect")
52 .data(dataset)
53 .enter()
54 .append("rect")
55 .attr("class", "MyRect")
56 .attr("transform", "translate(" + padding.left + "," + padding.top + ")")
57 .attr("x", function(d, i) {
58 return xScale(i) + rectPadding / 2;
59 })
60 .attr("y", function(d) {
61 return yscale(d);
62 })
63 .attr("width", xScale.rangeBand() - rectPadding)
64 .attr("height", function(d) {
65 return height - padding.top - padding.bottom - yscale(d);
66 });
67
68 // 添加文字元素
69 var texts = svg.selectAll(".MyText")
70 .data(dataset)
71 .enter()
72 .append("text")
73 .attr("class", "MyText")
74 .attr("transform", "translate(" + padding.left + "," + padding.top + ")")
75 .attr("x", function(d, i) {
76 return xScale(i) + rectPadding / 2;
77 })
78 .attr("y", function(d) {
79 return yscale(d);
80 })
81 .attr("dx", function() {
82 return (xScale.rangeBand() - rectPadding) / 2;
83 })
84 .attr("dy", function(d) {
85 return 20;
86 })
87 .text(function(d) {
88 return d;
89 });
90
91 // 添加x轴
92 svg.append("g")
93 .attr("class", "axis")
94 .attr("transform", "translate(" + padding.left + "," + (height - padding.bottom) + ")")
95 .call(xAxis);
96
97 // 添加y轴
98 svg.append("g")
99 .attr("class", "axis")
100 .attr("transform", "translate(" + padding.left + "," + padding.top + ")")
101 .call(yAxis);
102 </script>
103 </body>
104 </html>
最后运行结果如下图所示:

(二)、制作动态的柱形图
D3 提供了 4 个方法用于实现图形的过渡:
1) transition()启动过渡效果。其前后是图形变化前后的状态(形状、位置、颜色等等)。D3 会自动对两种颜色(红色和铁蓝色)之间的颜色值(RGB值)进行插值计算,得到过渡用的颜色值。
2) duration()指定过渡的持续时间,单位为毫秒。如duration(3000),指持续3秒。
3) ease()指定过渡的方式,常用的有:linear:普通的线性变化;circle:慢慢地到达变换的最终状态;elastic:带有弹跳的到达最终状态;bounce:在最终状态处弹跳几次。
4) delay()指定延迟的时间,表示一定时间后才开始转变,单位同样为毫秒。此函数可以对整体指定延迟,也可以对个别指定延迟。
下面我们将在题目一完成的柱形图的基础上稍作修改,做成一个带动态效果的柱形图。把题目一中添加矩形元素和添加文字元素的代码换成如下代码,就可以启动过渡效果,让各柱形和文字缓慢升至目标高度,并且在目标处跳动几次。
完整代码如下:
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <title>完整的柱形图</title>
6 </head>
7
8 <body>
9 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js" charset="utf-8"></script>
10 <script>
11 // 画布大小
12 var width = 400;
13 var height = 400;
14
15 // 在 body 里添加一个 SVG 画布
16 var svg = d3.select("body")
17 .append("svg")
18 .attr("width", width)
19 .attr("height", height);
20
21 // 画布周边的空白
22 var padding = { left: 30, right: 30, top: 20, bottom: 20 };
23
24 // 定义一个数组
25 var dataset = [16, 23, 54, 46, 33, 24, 19, 37, 9];
26
27 // x轴的比例尺
28 var xScale = d3.scale.ordinal()
29 .domain(d3.range(dataset.length))
30 .rangeRoundBands([0, width - padding.left - padding.right]);
31
32 // y轴的比例尺
33 var yscale = d3.scale.linear()
34 .domain([0, d3.max(dataset)])
35 .range([height - padding.top - padding.bottom, 0]);
36
37 // 定义x轴
38 var xAxis = d3.svg.axis()
39 .scale(xScale)
40 .orient("bottom");
41
42 // 定义y轴
43 var yAxis = d3.svg.axis()
44 .scale(yscale)
45 .orient("left");
46
47 // 矩形之间的空白
48 var rectPadding = 4;
49
50 // 添加矩形元素
51 var rects = svg.selectAll(".MyRect")
52 .data(dataset)
53 .enter()
54 .append("rect")
55 .attr("class", "MyRect")
56 .attr("transform", "translate(" + padding.left + "," + padding.top + ")")
57 .attr("x", function(d, i) {
58 return xScale(i) + rectPadding / 2;
59 })
60 .attr("width", xScale.rangeBand() - rectPadding)
61 .attr("y", function(d) {
62 var min = yscale.domain()[0];
63 return yscale(min);
64 })
65 .attr("height", function(d) {
66 return 0;
67 })
68 .transition()
69 .delay(function(d, i) {
70 return i * 200;
71 })
72 .duration(2000)
73 .ease("bounce")
74 .attr("y", function(d) {
75 return yscale(d);
76 })
77 .attr("height", function(d) {
78 return height - padding.top - padding.bottom - yscale(d);
79 });
80
81 // 添加文字元素
82 var texts = svg.selectAll(".MyText")
83 .data(dataset)
84 .enter()
85 .append("text")
86 .attr("class", "MyText")
87 .attr("transform", "translate(" + padding.left + "," + padding.top + ")")
88 .attr("x", function(d, i) {
89 return xScale(i) + rectPadding / 2;
90 })
91 .attr("dx", function() {
92 return (xScale.rangeBand() - rectPadding) / 2;
93 })
94 .attr("dy", function(d) {
95 return 20;
96 })
97 .text(function(d) {
98 return d;
99 })
100 .attr("y", function(d) {
101 var min = yscale.domain()[0];
102 return yscale(min);
103 })
104 .transition()
105 .delay(function(d, i) {
106 return i * 200;
107 })
108 .duration(2000)
109 .ease("bounce")
110 .attr("y", function(d) {
111 return yscale(d);
112 });
113
114 // 添加x轴
115 svg.append("g")
116 .attr("class", "axis")
117 .attr("transform", "translate(" + padding.left + "," + (height - padding.bottom) + ")")
118 .call(xAxis);
119
120 // 添加y轴
121 svg.append("g")
122 .attr("class", "axis")
123 .attr("transform", "translate(" + padding.left + "," + padding.top + ")")
124 .call(yAxis);
125 </script>
126 </body>
127 </html>
运行结果如下:

(三)、制作饼形图
布局是 D3 中一个十分重要的概念。布局的作用是:将不适合用于绘图的数据转换成了适合用于绘图的数据。
D3 总共 提供 了 12 个布 局:饼状图( Pie)、力导向图(Force )、弦图(Chord)、树状图(Tree)、集群图(Cluster)、捆图(Bundle)、打包图(Pack)、直方图(Histogram)、分区图(Partition)、堆栈图(Stack)、矩阵树图(Treemap)、层级图(Hierarchy)。 12 个布局中,层级图(Hierarchy)不能直接使用。集群图、打包图、分区图、树状图、矩阵树图是由层级图扩展来的。这些布局的作用都是将某种数据转换成有利于可视化的另一种数据。在布局的应用中,最简单的就是饼状图。
1、定义一个饼状图布局
定义布局的代码为:var pie = d3.layout.pie();此时 pie 可以当做函数使用。然后将数组 dataset(里面是要可视化的数据)作为 pie()的参数,返回值piedata 就是转换后的数据。var piedata =pie(dataset)。
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <title>饼状图</title>
6 </head>
7
8 <body>
9 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
10 <script>
11 var width = 400;
12 var height = 400;
13 var dataset = [11, 6, 34, 27, 13, 9];
14
15 var svg = d3.select("body")
16 .append("svg")
17 .attr("width", width)
18 .attr("height", height);
19
20 var pie = d3.layout.pie();//定义一个布局,返回值赋给变量pie,此时pie可当作函数使用
21 var piedata = pie(dataset);//数组dataset作为pie()的参数,返回值给pietada
2、绘制图形
SVG 有一个叫做路径 的元素,它可以结合使用直线,曲线等来制作各种不规则的复杂的图形。通过布局转换后的数据 piedata 很难计算得到路径值。为此,我们需要用到生成器。这里要用到的叫做弧生成器,能够生成弧的路径,因为饼图的每一部分都是一段弧。
1 var outerRadius = 150;//外半径 2 var innerRadius = 0;//内半径,为0则中间没有空白 3 4 var arc = d3.svg.arc()//弧生成器 5 .innerRadius(innerRadius)//设置内半径 6 .outerRadius(outerRadius);//设置外半径
弧生成器返回的结果赋值给 arc。arc 可以当做一个函数使用,把piedata 作为参数传入,即可得到路径值。 接下来,在<svg> 里添加分组元素(g),每一个分组用于存放一段弧的相关元素。再对每个<g>元素,添加<path>。
1 var arcs = svg.selectAll("g")
2 .data(piedata)
3 .enter()
4 .append("g")
5 .attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
6
7
8 arcs.append("path")
9 .attr("fill", function(d, i) {
10 return color(i);
11 })
12 .attr("d", function(d) {
13 return arc(d);
14 })
定义颜色比例尺。color 是一个颜色比例尺,它能根据传入的索引号获取相应的颜色值。
1 var color = d3.scale.category10();
然后在每一个弧线中心添加文本。
1 arcs.append("text")
2 .attr("transform", function(d) {
3 return "translate(" + arc.centroid(d) + ")";
4 })
5 .attr("text-anchor", "middle")
6 .text(function(d) {
7 return d.data;
8 });
9
10 console.log(dataset);
11 console.log(piedata);
12 </script>
13 </body>
14 </html>
完整代码如下:
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <title>饼状图</title>
6 </head>
7
8 <body>
9 <script src="https://d3js.org/d3.v3.min.js"></script>
10 <script>
11 var width = 400;
12 var height = 400;
13 var dataset = [11, 6, 34, 27, 13, 9];
14
15 var svg = d3.select("body")
16 .append("svg")
17 .attr("width", width)
18 .attr("height", height);
19
20 var pie = d3.layout.pie();
21 var piedata = pie(dataset);
22
23 var outerRadius = 150;
24 var innerRadius = 0;
25
26 var arc = d3.svg.arc()
27 .innerRadius(innerRadius)
28 .outerRadius(outerRadius);
29
30 var arcs = svg.selectAll("g")
31 .data(piedata)
32 .enter()
33 .append("g")
34 .attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
35
36 var color = d3.scale.category10();
37
38 arcs.append("path")
39 .attr("fill", function(d, i) {
40 return color(i);
41 })
42 .attr("d", function(d) {
43 return arc(d);
44 });
45
46 arcs.append("text")
47 .attr("transform", function(d) {
48 return "translate(" + arc.centroid(d) + ")";
49 })
50 .attr("text-anchor", "middle")
51 .text(function(d) {
52 return d.data;
53 });
54
55 console.log(dataset);
56 console.log(piedata);
57 </script>
58 </body>
59 </html>
运行结果如下:

(四)、制作交互式的饼形图
交互是指用户输入了某种指令后程序就可做出某种响应。对可视化图表来说,交互能使图表更加生动,能表现更多内容。例如,拖动图表中某些图形、鼠标滑到图形上出现提示框、用触屏放大或缩小图形等等。用户用于交互的工具一般有三种:鼠标、键盘、触屏。在 D3 中,每一个选择集都有 on()函数,用于添加事件监听器。on()的第一个参数是监听的事件,第二个参数是监听到事件后响应的内容,第二个参数是一个函数。
鼠标常用的事件有: click - 鼠标单击某元素时,相当于 mousedown 和 mouseup 组合在一起。 mouseover - 光标放在某元素上。 mouseout - 光标从某元素上移出来时。 mousemove - 鼠标被移动的时候。 mousedown - 鼠标按钮被按下。 mouseup - 鼠标按钮被松开。
下面开始进行简单的交互式饼形图制作,目标是在上面的饼形图的基础上加入mouseover 和 mouseout 事件,mouseover 某部分时变换成黄色,mouseout 时恢复原色。在代码中加入如下代码。
1 .on("mouseover", function(d, i) {
2 d3.select(this)
3 .attr("fill", "yellow");
4 })
5 .on("mouseout", function(d, i) {
6 d3.select(this)
7 .transition()
8 .duration(500)
9 .attr("fill", color(i));
10 });
完整代码如下:
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <title>饼状图</title>
6 </head>
7
8 <body>
9 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
10 <script>
11 var width = 400;
12 var height = 400;
13 var dataset = [11, 6, 34, 27, 13, 9];
14
15 var svg = d3.select("body")
16 .append("svg")
17 .attr("width", width)
18 .attr("height", height);
19
20 var pie = d3.layout.pie();
21 var piedata = pie(dataset);
22
23 var outerRadius = 150;
24 var innerRadius = 0;
25
26 var arc = d3.svg.arc()
27 .innerRadius(innerRadius)
28 .outerRadius(outerRadius);
29
30 var arcs = svg.selectAll("g")
31 .data(piedata)
32 .enter()
33 .append("g")
34 .attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
35
36 var color = d3.scale.category10();
37
38 arcs.append("path")
39 .attr("fill", function(d, i) {
40 return color(i);
41 })
42 .attr("d", function(d) {
43 return arc(d);
44 })
45 .on("mouseover", function(d, i) {
46 d3.select(this)
47 .attr("fill", "yellow");
48 })
49 .on("mouseout", function(d, i) {
50 d3.select(this)
51 .transition()
52 .duration(500)
53 .attr("fill", color(i));
54 });
55
56 arcs.append("text")
57 .attr("transform", function(d) {
58 return "translate(" + arc.centroid(d) + ")";
59 })
60 .attr("text-anchor", "middle")
61 .text(function(d) {
62 return d.data;
63 });
64
65 console.log(dataset);
66 console.log(piedata);
67 </script>
68 </body>
69 </html>
运行结果如下:

五、实验心得
D3作为强大的数据可视化工具,其基于数据驱动的理念极具特色。通过JavaScript操作DOM,能将数据灵活地绑定到各种页面元素上,再借助丰富的SVG绘图功能,实现高度定制化的可视化效果,这是它相较于其他工具的独特优势。
实验过程中,从简单的柱状图开始,逐步深入到动态交互的散点图。每一次成功实现预期效果,都加深了我对数据、代码和图形之间关联的理解。例如,在绑定数据时,要充分考虑数据的结构和格式,才能确保图表准确呈现信息;而添加交互功能,如鼠标悬停显示数据详情,让可视化作品更具实用性和趣味性。
然而,D3也带来诸多挑战。它对JavaScript编程能力要求较高,实验初期,复杂的代码逻辑和函数调用常让我陷入困境。而且,不同浏览器对SVG和JavaScript的支持存在差异,导致可视化效果出现兼容性问题。
此次实验让我深刻体会到D3的强大与灵活,也意识到自身在编程和数据处理方面的不足。未来,我会持续学习,提升编程水平,以便更好地驾驭D3,创造出更优秀的数据可视化作品。

浙公网安备 33010602011771号