实验二、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,创造出更优秀的数据可视化作品。