可视化工具D3入门
D3 的全称是(Data-Driven Documents),顾名思义可以知道是一个被数据驱动的文档。听名字有点抽象,说简单一点,其实就是一个 JavaScript 的函数库,使用它主要是用来做数据可视化的。
入门实例
Html 输出 HelloWorld
<html>
<meta charset="utf-8">
<title>HelloWorld</title>
</head>
<p>Hello World 1</p>
<p>Hello World 2</p>
</body>
</html>
用JavaScript来改变html中的内容:
<html>
<meta charset="utf-8">
<title>HelloWorld</title>
</head>
<p>Hello World 1</p>
<p>Hello World 2</p>
<script>
var paragraphs = document.getElementsByTagName("p");
for (var i = 0; i < paragraphs.length; i++) {
var paragraph = paragraphs.item(i);
paragraph.innerHTML = "I like dog.";
</script>
</body>
</html>
D3来更改html:
<html>
<meta charset="utf-8">
<title>HelloWorld</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<p>Hello World 1</p>
<p>Hello World 2</p>
<script>
d3.select("body").selectAll("p").text("D3也可以做的!")
</script>
</body>
</html>
可以看出D3更简洁。
选择元素和绑定数据
1. 如何选择元素
•在 D3 中,用于选择元素的函数有两个:
•d3.select():是选择所有指定元素的第一个
•d3.selectAll():是选择指定元素的全部
这两个函数返回的结果称为 选择集 。
var body = d3.select("body"); //选择文档中的body元素
var p1 = body.select("p"); //选择body中的第一个p元素
var p = body.selectAll("p"); //选择body中的所有p元素
var svg = body.select("svg"); //选择body中的svg元素
var rects = svg.selectAll("rect"); //选择svg中所有的svg元素
选择集和绑定数据通常是一起使用的。
• 2. 如何绑定数据
D3 有一个很独特的功能:能将数据绑定到 DOM 上,也就是绑定到文档上。
D3 中是通过以下两个函数来绑定数据的:
•datum():绑定一个数据到选择集上
•data():绑定一个数组到选择集上,数组的各项值分别与选择集的各元素绑定
相对而言,data() 比较常用。
假设现在有三个段落元素如下。
<p>Apple</p>
<p>Pear</p>
<p>Banana</p>
接下来分别使用 datum() 和 data(),将数据绑定到上面三个段落元素上。
• datum()
假设有一字符串 China,要将此字符串分别与三个段落元素绑定,代码如下:
var str = "China";
var body = d3.select("body");
var p = body.selectAll("p");
p.datum(str);//绑定数据后使用此数据来修改三个段落元素的内容
p.text(function(d, i){
return "第 "+ i + " 个元素绑定的数据是 " + d;
第 0 个元素绑定的数据是 China
第 1 个元素绑定的数据是 China
第 2 个元素绑定的数据是 China
在上面的代码中,用到了一个无名函数 function(d, i) 。当选择集需要使用被绑定的数据时,常需要这么使用。其包含两个参数,其中:
d 代表数据,也就是与某元素绑定的数据。
i 代表索引,代表数据的索引号,从 0 开始。
例如,上述例子中:第 0 个元素 apple 绑定的数据是 China。
• data()
有一个数组,接下来要分别将数组的各元素绑定到三个段落元素上。
var dataset = ["I like dogs","I like cats","I like snakes"];
绑定之后,其对应关系的要求为:
• Apple 与 I like dogs 绑定
• Pear 与 I like cats 绑定
• Banana 与 I like snakes 绑定
调用 data() 绑定数据,并替换三个段落元素的字符串为被绑定的字符串:
var body = d3.select("body");
var p = body.selectAll("p");
p.data(dataset)
.text(function(d, i){
return d;
I like dogs
I like cats
I like snakes
<meta charset="utf-8">
<title>选择元素和绑定数据</title>
</head>
<p>Apple</p>
<p>Pear</p>
<p>Banana</p>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var str = "China";
var body = d3.select("body");
var p = body.selectAll("p");
//使用 datum 绑定单个数据
/*p.datum(str);
p.text(function(d, i){
return "第 "+ i + " 个元素绑定的数据是 " + d;
});*/
//使用 data 绑定数组
var dataset = ["I like dogs","I like cats","I like snakes"];
p.data(dataset)
.text(function(d, i){
if(i==2)return d;
else return this.innerText;
</script>
</body>
</html>
这段代码也用到了一个 匿名函数 function(d, i),其对应的情况如下:
当 i == 0 时, d 为 I like dogs。
当 i == 1 时, d 为 I like cats。
当 i == 2 时, d 为 I like snakes。
此时,三个段落元素与数组 dataset 的三个字符串是一一对应的,因此,在函数 function(d, i) 直接 return d 即可。
结果自然是三个段落的文字分别变成了数组的三个字符串。
添加了元素之后,就需要分别给各元素的属性赋值。在这里用到了 function(d, i),前面已经讲过,d 代表与当前元素绑定的数据,i 代表索引号。给属性赋值的时候,是需要用到被绑定的数据,以及索引号的。
选择、插入、删除元素
选择元素
假设在 body 中有三个段落元素:
<p>Apple</p>
<p>Pear</p>
<p>Banana</p>
• 选择第一个 p 元素
使用 select ,参数传入 p 即可, 如此返回的是第一个 p 元素。
var p1 = body.select("p");
p1.style("color","red");
选择三个 p 元素
使用 selectAll 选择 body 中所有的 p 元素。
var p = body.selectAll("p");
p.style("color","red");
• 选择第二个 p 元素
有不少方法,一种比较简单的是 给第二个元素添加一个 id 号 。
<p id="myid">Pear</p>
然后,使用 select 选择元素,注意参数中 id 名称前要加 # 号。
•var p2 = body.select("#myid");
•p2.style("color","red");
• 选择后两个 p 元素
给后两个元素添加 class,
<p class="myclass">Pear</p>
<p class="myclass">Banana</p>
由于需要选择多个元素,要用 selectAll。注意参数,class 名称前要加一个点。
var p = body.selectAll(".myclass");
p.style("color","red");
•关于 select 和 selectAll 的参数,其实是符合 CSS 选择器的条件的,即用“ 井号(#)”表示 id,用“点(.)”表示 class。
•此外,对于已经绑定了数据的选择集,还有一种选择元素的方法,那就是灵活运用 function(d, i)。 我们已经知道参数 i 是代表索引号的,于是便可以用条件判定语句来指定执行的元素。
插入元素
•插入元素涉及的函数有两个:
•append():在选择集末尾插入元素
•insert():在选择集前面插入元素
•假设有三个段落元素,与上文相同。
• append()
body.append("p")
.text(“hello");
•在 body 的末尾添加一个 p 元素,结果为:
insert()
在 body 中 id 为 myid 的元素前添加一个段落元素。
body.insert("p","#myid")
.text("insert p element");
已经指定了 Pear 段落的 id 为 myid,因此结果如下
删除元素
删除一个元素时,对于选择的元素,使用 remove 即可,例如:
var p = body.select("#myid");
p.remove();
如此即可删除指定 id 的段落元素。
做一个简单的图表
柱形图是一种最简单的可视化图标,主要有矩形、文字标签、坐标轴组成。本文为简单起见,只绘制矩形的部分,用以讲解如何使用 D3 在 SVG 画布中绘图。
SVG 是什么
1. 画布是什么
前几章的处理对象都是 HTML 的文字,没有涉及图形的制作。
要绘图,首要需要的是一块绘图的“画布”。
HTML 5 提供两种强有力的“画布”:SVG 和 Canvas。
•SVG,指可缩放矢量图形(Scalable Vector Graphics),是用于描述二维矢量图形的一种图形格式,是由万维网联盟制定的开放标准。SVG 使用 XML 格式来定义图形 ,除了 IE8 之前的版本外,绝大部分浏览器都支持 SVG,可将 SVG 文本直接嵌入 HTML 中显示。
SVG 有如下特点:
•SVG 绘制的是矢量图,因此对图像进行放大不会失真。
•基于 XML,可以为每个元素添加 JavaScript 事件处理器。
•每个图形均视为对象,更改对象的属性,图形也会改变。
•不适合游戏应用。
Canvas 是什么
•Canvas 是通过 JavaScript 来绘制 2D 图形,是 HTML 5 中新增的元素。
•Canvas 有如下特点:
•绘制的是位图,图像放大后会失真。
•不支持事件处理器。
•能够以 .png 或 .jpg 格式保存图像
•适合游戏应用
添加画布
D3 虽然没有明文规定一定要在 SVG 中绘图,但是 D3 提供了众多的 SVG 图形的生成器,它们都是只支持 SVG 的。因此, 建议使用 SVG 画布 。
使用 D3 在 body 元素中添加 svg 的代码如下。
var width = 300; //画布的宽度
var height = 300; //画布的高度
var svg = d3.select("body") //选择文档中的body元素
.append("svg") //添加一个svg元素
.attr("width", width) //设定宽度
.attr("height", height); //设定高度
有了画布,接下来就可以在画布上作图了。
绘制矩形
本文绘制一个横向的柱形图。只绘制矩形,不绘制文字和坐标轴。
在 SVG 中,矩形的元素标签是 rect。例如:
<svg>
<rect></rect>
<rect></rect>
</svg>
•上面的 rect 里没有矩形的属性。矩形的属性,常用的有四个:
•x:矩形左上角的 x 坐标
•y:矩形左上角的 y 坐标
•width:矩形的宽度
•height:矩形的高度
要注意,在 SVG 中,x 轴的正方向是水平向右,y 轴的正方向是垂直向下的。
现在给出一组数据,要对此进行可视化。数据如下:
var dataset = [ 250 , 210 , 170 , 130 , 90 ]; //数据(表示矩形的宽度)
var rectHeight = 25; //每个矩形所占的像素高度(包括空白)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x",20)
.attr("y",function(d,i){
return i * rectHeight;
.attr("width",function(d){
return d;
.attr("height",rectHeight-2)
.attr("fill","steelblue");
enter()数的作用是返回一个新的D3()对象集合,这个集合包含了所有没有被可视化的数据。这是D3将数据与图形的联系定义的一种模式(enter-update-exit)叫作Enter Mode。
这段代码添加了与 dataset 数组的长度相同数量的矩形,所使用的语句是:
svg.selectAll("rect") //选择svg内所有的矩形
.data(dataset) //绑定数组
.enter() //指定选择集的enter部分
.append("rect") //添加足够数量的矩形元素
有数据,而没有足够图形元素的时候,使用此方法可以添加足够的元素。
<html>
<meta charset="utf-8">
<title>做一个简单的图表</title>
</head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var width = 300; //画布的宽度
var height = 300; //画布的高度
var svg = d3.select("body") //选择文档中的body元素
.append("svg") //添加一个svg元素
.attr("width", width) //设定宽度
.attr("height", height); //设定高度
var dataset = [ 250 , 210 , 170 , 130 , 90 ];
var rectHeight = 25; //每个矩形所占的像素高度(包括空白)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x",20)
.attr("y",function(d,i){
return i * rectHeight;
.attr("width",function(d){
return d;
.attr("height",rectHeight-2)
.attr("fill","steelblue");
</script>
</body>
</html>
比例尺的使用
比例尺是 D3 中很重要的一个概念。
为什么需要比例尺
•制作了一个柱形图,当时有一个数组:
•var dataset = [ 250 , 210 , 170 , 130 , 90 ];
绘图时,直接使用 250 给矩形的宽度赋值,即矩形的宽度就是 250 个像素。
此方式非常具有局限性,如果数值过大或过小,例如:
var dataset_1 = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];
var dataset_2 = [ 2500, 2100, 1700, 1300, 900 ];
对以上两个数组,绝不可能用 2.5 个像素来代表矩形的宽度,那样根本看不见;也不可能用 2500 个像素来代表矩形的宽度,因为画布没有那么长。
于是,我们需要一种计算关系,能够:
将某一区域的值映射到另一区域,其大小关系不变。
这就是比例尺(Scale)。
•比例尺,很像数学中的函数。例如,对于一个一元二次函数,有 x 和 y 两个未知数,当 x 的值确定时,y 的值也就确定了。
•在数学中,x 的范围被称为 定义域 ,y 的范围被称为 值域 。
•D3 中的比例尺,也有定义域和值域,分别被称为 domain 和 range。开发者需要指定 domain 和 range 的范围,如此即可得到一个计算关系。
•D3 提供了多种比例尺,下面介绍最常用的两种。
线性比例尺
线性比例尺,能将一个连续的区间,映射到另一区间。要解决柱形图宽度的问题,就需要线性比例尺。
假设有以下数组:
var dataset = [1.2, 2.3, 0.9, 1.5, 3.3];
现有要求如下:
将 dataset 中最小的值,映射成 0;将最大的值,映射成 300。
var min = d3.min(dataset);
var max = d3.max(dataset);
var linear = d3.scale.linear()
.domain([min, max])
.range([0, 300]);
linear(0.9); //返回 0
linear(2.3); //返回 175
linear(3.3);//返回 300
其中, d3.scale.linear() 返回一个线性比例尺。domain() 和 range() 分别设定比例尺的定义域和值域。在这里还用到了两个函数,它们经常与比例尺一起出现:
d3.max()
d3.min()
这两个函数能够求数组的最大值和最小值,是 D3 提供的。按照以上代码,
比例尺的定义域 domain 为:[0.9, 3.3]
比例尺的值域 range 为:[0, 300]
因此,当输入 0.9 时,返回 0;当输入 3.3 时,返回 300。当输入 2.3 时呢?返回 175,这是按照线性函数的规则计算的。
序数比例尺
有时候,定义域和值域不一定是连续的。例如,有两个数组:
var index = [0, 1, 2, 3, 4];
var color = ["red", "blue", "green", "yellow", "black"];
我们希望 0 对应颜色 red,1 对应 blue,依次类推。
但是,这些值都是离散的,线性比例尺不适合,需要用到序数比例尺。
var ordinal = d3.scale.ordinal()
.domain(index)
.range(color);
ordinal(0); //返回 red
ordinal(2); //返回 green
ordinal(4); //返回 black
给柱形图添加比例尺
var dataset = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];
var linear = d3.scale.linear()
.domain([0, d3.max(dataset)])
.range([0, 250]);
var rectHeight = 25; //每个矩形所占的像素高度(包括空白)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x",20)
.attr("y",function(d,i){
return i * rectHeight;
.attr("width",function(d){
return linear(d); //在这里用比例尺
.attr("height",rectHeight-2)
.attr("fill","steelblue");
如此一来,所有的数值,都按照同一个线性比例尺的关系来计算宽度,因此数值之间的大小关系不变。
• var dataset = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];
• var dataset = [ 250 , 210 , 170 , 130 , 90 ];
• 效果相同
预定义元素-----基本图形
在 SVG 画布的预定义元素里,有六种基本图形:
•矩形 <rect>
•圆形 <circle>
•椭圆 <ellipse>
•线段 <line>
•折线 <polyline>
•多边形 <polygon>
另外,还有一种比较特殊,也是功能最强的元素:
•路径 <path>
画布中的所有图形,都是由以上七种元素组成。
因此,我们需要用其他元素来组合成坐标轴。为此,D3 提供了一个组件:d3.svg.axis()。
• 定义坐标轴
前面提到了比例尺的概念,要生成坐标轴,需要用到比例尺,它们二者经常是一起使用的。下面,在原有的数据和比例尺的基础上,添加一个坐标轴的组件。
//数据
var dataset = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];
//定义比例尺
var linear = d3.scale.linear()
.domain([0, d3.max(dataset)])
.range([0, 250]);
var axis = d3.svg.axis()
.scale(linear) //指定比例尺
.orient("bottom") //指定刻度的方向
.ticks(7);//指定刻度的数量
•第 1 – 2 行:定义数组。
•第 4 – 7 行:定义比例尺,其中使用了数组 dataset。
•第 9 – 12 行:定义坐标轴,其中使用了线性比例尺 linear。其中:
•d3.svg.axis():D3 中坐标轴的组件,能够在 SVG 中生成组成坐标轴的元素。
•scale():指定比例尺。
•orient():指定刻度的朝向,bottom 表示在坐标轴的下方显示。
•ticks():指定刻度的数量。
定义了坐标轴之后,只需要在 SVG 中添加一个分组元素 <g>,再将坐标轴的其他元素添加到这个 <g> 里即可。代码如下:
svg.append("g")
.call(axis);
设定坐标轴的样式和位置
默认的坐标轴样式不太美观,下面提供一个常见的样式:
<style>
.axis path,
.axis line{
fill: none;
stroke: black;
shape-rendering: crispEdges;
.axis text {
font-family: sans-serif;
font-size: 11px;
</style>
分别定义了类 axis 下的 path、line、text 元素的样式。
接下来,只需要将坐标轴的类设定为 axis 即可。
坐标轴的位置,可以通过 transform 属性来设定。
svg.append("g")
.attr("class","axis")
.attr("transform","translate(20,130)")
.call(axis);
让图表动起来
D3 支持制作动态的图表。有时候,图表的变化需要缓慢的发生,以便于让用户看清楚变化的过程,也能给用户不小的友好感。
1. 什么是动态效果
前面几章制作的图表是一蹴而就地出现,然后绘制完成后不再发生变化的,这是 静态 的图表。
动态 的图表,是指图表在某一时间段会发生某种变化,可能是形状、颜色、位置等,而且用户是可以看到变化的过程的。
例如,有一个圆,圆心为 (100, 100)。现在我们希望圆的 x 坐标 从 100 移到 300 ,并且移动过程在 2 秒 的时间内发生。
这种时候就需要用到动态效果,在 D3 里我们称之为 过渡(transition) 。
2. 实现动态的方法
D3 提供了 4 个方法用于实现图形的过渡:从 状态 A 变为 状态 B 。
transition()
启动过渡效果。其前后是图形变化前后的状态(形状、位置、颜色等等),例如:
.attr("fill","red") //初始颜色为红色
.transition() //启动过渡
.attr("fill","steelblue") //终止颜色为铁蓝色
D3 会自动对两种颜色(红色和铁蓝色)之间的颜色值(RGB值)进行插值计算,得到过渡用的颜色值。我们无需知道中间是怎么计算的,只需要享受结果即可。
duration()
•指定过渡的持续时间,单位为毫秒。
•如 duration(2000) ,指持续 2000 毫秒,即 2 秒。
ease()
•指定过渡的方式,常用的有:
•linear:普通的线性变化
•circle:慢慢地到达变换的最终状态
•elastic:带有弹跳的到达最终状态
•bounce:在最终状态处弹跳几次
•调用时,格式形如: ease(“bounce”)。
delay()
指定延迟的时间,表示一定时间后才开始转变,单位同样为毫秒。此函数可以对整体指定延迟,也可以对个别指定延迟。
•例如,对整体指定时:
.transition()
.duration(1000)
.delay(500)
如此, 图形整体 在延迟 500 毫秒后发生变化,变化的时长为 1000 毫秒。因此,过渡的总时长为1500毫秒。
又如,对一个一个的图形(图形上绑定了数据)进行指定时:
.transition()
.duration(1000)
.delay(funtion(d,i){
return 200*i;
})
给柱形图加上动态效果
•做成一个带动态效果的、有意思的柱形图。
•在添加文字元素和矩形元素的时候,启动过渡效果,让各柱形和文字缓慢升至目标高度,并且在目标处跳动几次。
•对于文字元素,代码如下:
<html>
<meta charset="utf-8">
<title>让图表动起来</title>
<style>
.axis path,
.axis line{
fill: none;
stroke: black;
shape-rendering: crispEdges;
.axis text {
font-family: sans-serif;
font-size: 11px;
.MyRect {
fill: steelblue;
.MyText {
fill: white;
text-anchor: middle;
</style>
</head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
//画布大小
var width = 400;
var height = 400;
//在 body 里添加一个 SVG 画布
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
//画布周边的空白
var padding = {left:30, right:30, top:20, bottom:20};
//定义一个数组
var dataset = [10, 20, 30, 40, 33, 24, 12, 5];
//x轴的比例尺
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, width - padding.left - padding.right]);
//y轴的比例尺
var yScale = d3.scale.linear()
.domain([0,d3.max(dataset)])
.range([height - padding.top - padding.bottom, 0]);
//定义x轴
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
//定义y轴
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
//矩形之间的空白
var rectPadding = 4;
//添加矩形元素
var rects = svg.selectAll(".MyRect")
.data(dataset)
.enter()
.append("rect")
.attr("class","MyRect")
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.attr("x", function(d,i){
return xScale(i) + rectPadding/2;
.attr("width", xScale.rangeBand() - rectPadding )
.attr("y",function(d){
var min = yScale.domain()[0];
return yScale(min);
.attr("height", function(d){
return 0;
.transition()
.delay(function(d,i){
return i * 200;
.duration(2000)
.ease("bounce")
.attr("y",function(d){
return yScale(d);
.attr("height", function(d){
return height - padding.top - padding.bottom - yScale(d);
//添加文字元素
var texts = svg.selectAll(".MyText")
.data(dataset)
.enter()
.append("text")
.attr("class","MyText")
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.attr("x", function(d,i){
return xScale(i) + rectPadding/2;
.attr("dx",function(){
return (xScale.rangeBand() - rectPadding)/2;
.attr("dy",function(d){
return 20;
.text(function(d){
return d;
.attr("y",function(d){
var min = yScale.domain()[0];
return yScale(min);
.transition()
.delay(function(d,i){
return i * 200;
.duration(2000)
.ease("bounce")
.attr("y",function(d){
return yScale(d);
//添加x轴
svg.append("g")
.attr("class","axis")
.attr("transform","translate(" + padding.left + "," + (height - padding.bottom) + ")")
.call(xAxis);
//添加y轴
svg.append("g")
.attr("class","axis")
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.call(yAxis);
</script>
</body>
</html>
交互式操作
•与图表的交互,指在图形元素上设置一个或多个监听器,当事件发生时,做出相应的反应。
1. 什么是交互
交互,指的是用户输入了某种指令,程序接受到指令之后必须做出某种响应。对可视化图表来说,交互能使图表更加生动,能表现更多内容。例如,拖动图表中某些图形、鼠标滑到图形上出现提示框、用触屏放大或缩小图形等等。
用户用于交互的工具一般有三种:鼠标、键盘、触屏。
2. 如何添加交互
对某一元素添加交互操作十分简单,代码如下:
var circle = svg.append("circle");
circle.on("click", function(){
//在这里添加交互内容
});
在 D3 中,每一个选择集都有 on() 函数,用于添加事件监听器。
on() 的第一个参数是监听的事件,第二个参数是监听到事件后响应的内容,第二个参数是一个函数。
鼠标常用的事件有:
•click:鼠标单击某元素时,相当于 mousedown 和 mouseup 组合在一起。
•mouseover:光标放在某元素上。
•mouseout:光标从某元素上移出来时。
•mousemove:鼠标被移动的时候。
•mousedown:鼠标按钮被按下。
•mouseup:鼠标按钮被松开。
•dblclick:鼠标双击。
键盘常用的事件有三个:
•keydown:当用户按下任意键时触发,按住不放会重复触发此事件。该事件不会区分字母的大小写,例如“A”和“a”被视为一致。
•keypress:当用户按下字符键(大小写字母、数字、加号、等号、回车等)时触发,按住不放会重复触发此事件。该事件区分字母的大小写。
•keyup:当用户释放键时触发,不区分字母的大小写。
触屏常用的事件有三个:
•touchstart:当触摸点被放在触摸屏上时。
•touchmove:当触摸点在触摸屏上移动时。
•touchend:当触摸点从触摸屏上拿开时。
当某个事件被监听到时,D3 会把当前的事件存到 d3.event 对象,里面保存了当前事件的各种参数,请大家好好参详。如果需要监听到事件后立刻输出该事件,可以添加一行代码:
circle.on("click", function(){
console.log(d3.event);
});
带有交互的柱形图
var rects = svg.selectAll(".MyRect")
.data(dataset)
.enter()
.append("rect")
.attr("class","MyRect") //把类里的 fill 属性清空
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.attr("x", function(d,i){
return xScale(i) + rectPadding/2;
.attr("y",function(d){
return yScale(d);
.attr("width", xScale.rangeBand() - rectPadding )
.attr("height", function(d){
return height - padding.top - padding.bottom - yScale(d);
.attr("fill","steelblue") //填充颜色不要写在CSS里
.on("mouseover",function(d,i){
d3.select(this)
.attr("fill","yellow");
.on("mouseout",function(d,i){
d3.select(this)
.transition()
.duration(500)
.attr("fill","steelblue");
});
完整代码
<html>
<meta charset="utf-8">
<title>交互式操作</title>
</head>
<style>
.axis path,
.axis line{
fill: none;
stroke: black;
shape-rendering: crispEdges;
.axis text {
font-family: sans-serif;
font-size: 11px;
.MyRect {
.MyText {
fill: white;
text-anchor: middle;
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
//画布大小
var width = 400;
var height = 400;
//在 body 里添加一个 SVG 画布
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
//画布周边的空白
var padding = {left:30, right:30, top:20, bottom:20};
//定义一个数组
var dataset = [10, 20, 30, 40, 33, 24, 12, 5];
//x轴的比例尺
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, width - padding.left - padding.right]);
//y轴的比例尺
var yScale = d3.scale.linear()
.domain([0,d3.max(dataset)])
.range([height - padding.top - padding.bottom, 0]);
//定义x轴
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
//定义y轴
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
//矩形之间的空白
var rectPadding = 4;
//添加矩形元素
var rects = svg.selectAll(".MyRect")
.data(dataset)
.enter()
.append("rect")
.attr("class","MyRect")
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.attr("x", function(d,i){
return xScale(i) + rectPadding/2;
.attr("y",function(d){
return yScale(d);
.attr("width", xScale.rangeBand() - rectPadding )
.attr("height", function(d){
return height - padding.top - padding.bottom - yScale(d);
.attr("fill","steelblue") //填充颜色不要写在CSS里
.on("mouseover",function(d,i){
d3.select(this)
.attr("fill","yellow");
.on("mouseout",function(d,i){
d3.select(this)
.transition()
.duration(500)
.attr("fill","steelblue");
//添加文字元素
var texts = svg.selectAll(".MyText")
.data(dataset)
.enter()
.append("text")
.attr("class","MyText")
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.attr("x", function(d,i){
return xScale(i) + rectPadding/2;
.attr("y",function(d){
return yScale(d);
.attr("dx",function(){
return (xScale.rangeBand() - rectPadding)/2;
.attr("dy",function(d){
return 20;
.text(function(d){
return d;
//添加x轴
svg.append("g")
.attr("class","axis")
.attr("transform","translate(" + padding.left + "," + (height - padding.bottom) + ")")
.call(xAxis);
//添加y轴
svg.append("g")
.attr("class","axis")
.attr("transform","translate(" + padding.left + "," + padding.top + ")")
.call(yAxis);
</script>
</body>
</html>
•这段代码添加了鼠标移入(mouseover),鼠标移出(mouseout)两个事件的监听器。监听器函数中都使用了 d3.select(this),表示选择当前的元素,this 是当前的元素,要改变响应事件的元素时这么写就好。
•mouseover 监听器函数的内容为:将当前元素变为黄色
•mouseout 监听器函数的内容为:缓慢地将元素变为原来的颜色(蓝色)
布局
•布局,可以理解成 “制作常见图形的函数”,有了它制作各种相对复杂的图表就方便多了 [1]
饼状图的制作
• var dataset = [ 30 , 10 , 43 , 55 , 13 ];
• 这个数据要不能直接用于画饼状图,我们必须通过计算将它转换成角度。这个计算不需要我们手动计算,因为 D3 中提供了 d3.layout.pie() 函数,这个 Layout 就是用于将上面这样的数据转换成饼状图需要的角度。下面定义一个这样的函数。
• var pie = d3.layout.pie();
•一定要记住,这是一个函数,使用它的时候,要 pie( dataset ) 这样才转换数据。我们可以先看看转换后输出什么样的数据。
•我们可以先看看转换后输出什么样的数据。
• 如上图所示,5个整数被转换成了5个 Object ,每个里面存有起始角度和结束角度,以及原整数。这样的数据适合做饼状图,这就是 Layout 的作用。但是要注意,实际作图时,还是需要别的作图方法的。
•接下来可以作图了,和前几节一样,都是在 svg 框内作图。上面的有5个整数,也就是有5段弧线。我们先在 svg 里添加5个分组( 也就是 svg 中的元素 g )。每一个分组就是一段弧线。代码如下:
var arcs = svg.selectAll("g")
.data(pie(dataset))
.enter()
.append("g")
.attr("transform","translate("+outerRadius+","+outerRadius+")");
•上面的代码中,我们绑定了转换后的数据 pie(dataset) ,有5个数据,所以会添加5个g元素,最后一行代码是移动元素的位置,默认的起始位置是 svg 绘制框的 (0,0) 坐标,也就是左上角。要注意,这个时候上面代码返回的是同时选择5个g元素的选择。
• 接下来对每个g元素,添加 path 。
arcs.append("path")
.attr("fill",function(d,i){
return color(i);
.attr("d",function(d){
return arc(d);
});
因为 arcs 是同时选择5个g元素的,所以 append("pah") 后,是每一个 g 中都有 path ,然后再添加颜色属性,和路径属性。颜色属性的 color(i) 是定义的一个函数。
var color = d3.scale.category10();
SVG 中的路径属性是 d, 它的值是 arc(d) ,也就是将绑定的数据作为上面定义的函数 arc 的参数算出的值。
接下来在每一个弧线中心添加文本。
arcs.append("text")
.attr("transform",function(d){
return "translate(" + arc.centroid(d) + ")";
.attr("text-anchor","middle")
.text(function(d){
return d.value;
});
arc.centroid(d) 能算出弧线的中心,要注意一句代码,返回的是 d.value ,而不是 d,因为当前绑定的数据是 Object,里面有起始角度等值,d.value 是元整数的值,可见上面的截图。
完整代码
<html>
<meta charset="utf-8">
<title>Pie</title>
</head>
<style>
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var width = 600;
var height = 600;
var dataset = [ 30 , 10 , 43 , 55 , 13 ];
var svg = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
var pie = d3.layout.pie();
var outerRadius = width / 2;
var innerRadius = width / 4;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var color = d3.scale.category10();
var arcs = svg.selectAll("g")
.data(pie(dataset))
.enter()
.append("g")
.attr("transform","translate("+outerRadius+","+outerRadius+")");
arcs.append("path")
.attr("fill",function(d,i){
return color(i);
.attr("d",function(d){
return arc(d);
arcs.append("text")
.attr("transform",function(d){
return "translate(" + arc.centroid(d) + ")";
.attr("text-anchor","middle")
.text(function(d){
return d.value;
console.log(dataset);
console.log(pie(dataset));
</script>
</body>
</html>
运行截图
力学图的制作
力学图( Force ),也有被翻译做力导向图等。这种图很有意思,先从初始数据开始
var nodes = [ { name: "GuiLin" },
{ name: "GuangZhou" },
{ name: "XiaMen" },
{ name: "HangZhou" },
{ name: "ShangHai" },
{ name: "QingDao" },
{ name: "TianJin" },
{ name: "BeiJing" },
{ name: "ChangChun" },
{ name: "XiAn" },
{ name: "WuluMuQi" },
{ name: "LaSa" },
{ name: "ChengDu" } ];
var edges = [ { source : 0 , target: 1 } ,
{ source : 1 , target: 2 } ,
{ source : 2 , target: 3 } ,
{ source : 3 , target: 4 } ,
{ source : 4 , target: 5 } ,
{ source : 5 , target: 6 } ,
{ source : 6 , target: 7 } ,
{ source : 7 , target: 8 } ,
{ source : 8 , target: 9 } ,
{ source : 9 , target: 10 } ,
{ source : 10 , target: 11 } ,
{ source : 11 , target: 12 } ,
{ source : 12 , target: 0 } ];
这里有顶点( nodes )和边( edges ),这里的顶点是一些城市名称,边是两个顶点之间的连线。我们现在要用这些数据来做力学图。但是这样的数据不适合做力学图,比如不知道每一个顶点画在哪个坐标等。所以需要先用 Layout 来转换数据,我们说过, D3 中的 Layout 就是用来转换数据的。 force 的 layout 为:
var force = d3.layout.force()
.nodes(nodes)
.links(edges)
.size([width,height])
.linkDistance(200)
.charge([-100])
.start();
在上面的代码中:
•d3.layout.force() 是力学图 Layout 的函数
•nodes() 里传入顶点的数组
•links() 里放入边的数组
•size() 是作用域的大小
•linkDistance() 用于设定两个顶点之间的长度
•charge() 是设定弹力的大小。
•start() 表示开始转换
• 调用这个函数后,数据就已经被转换了,我们看看数据从什么转换成什么了:
好了,有了这些数据,我们就可以作图了。我们用 SVG 中的 line 画边,用 SVG 中的 circle 画顶点。
var svg_edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke","#ccc")
.style("stroke-width",1);
var color = d3.scale.category20();
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r",10)
.style("fill",function(d,i){
return color(i);
.call(force.drag);
最后一句代码 call( force.drag ) 是设定我们可以拖动顶点。这个 call 函数我们前面说过,这个 call 是用于将当前选择的元素传到 force.drag 函数中。
最后,我们还需要一段代码,如下:
force.on("tick", function(){
svg_edges.attr("x1",function(d){ return d.source.x; });
svg_edges.attr("y1",function(d){ return d.source.y; });
svg_edges.attr("x2",function(d){ return d.target.x; });
svg_edges.attr("y2",function(d){ return d.target.y; });
svg_nodes.attr("cx",function(d){ return d.x; });
svg_nodes.attr("cy",function(d){ return d.y; });
});
• tick 指的是时间间隔,也就是每一个时间间隔之后就刷新一遍画面,刷新的内容写在后面的无名函数 function 中, function 函数中写上作图的内容。 我们来看看最终效果图。
力学图 + 人物关系图
人物关系图
主要的问题包括:
•如何在小球旁插入文字
•如何将小球换为别的图形
•如何插入图片
•如何限制小球运动的边界
其中前三点是 SVG 元素的问题,和 D3 无多大关联
• 1. SVG 图片
SVG 的图片元素的详细解说可看【 官方文档 - 图片 】。通常,我们只需要使用到图片元素的五个属性就够了。
<image xlink:href="image.png" x="200" y="200" width="100" height="100"></image>
其中:
•xlink:href – 图片名称或图片网址
•x – 图片坐上角 x 坐标
•y – 图片坐上角 y 坐标
•width – 图片宽度
•height- 图片高度
在 D3 中插入图片,代码形如:
svg.selectAll("image")
.data(dataset)
.enter()
.append("image")
.attr("x",200)
.attr("y",200)
.attr("width",100)
.attr("height",100)
.attr("xlink:href","image.png");
2. SVG 文本
•SVG 的文本元素和图片类似,详细属性见【 官方文档 - 文本 】。
<text x="250" y="150" dx="10" dy="10" font-family="Verdana" font-size="55" fill="blue" >Hello</text>
其中:
•x – 文本 x 坐标
•y – 文本 y 坐标
•dx- x 轴方向的文本平移量
•dy- y 轴方向的文本平移量
•font-family – 字体
•font-size – 字体大小
•fill – 字体颜色
在 D3 中插入文本,代码形如:
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("x",250)
.attr("y",150)
.attr("dx",10)
.attr("dy",10)
.text("Hello");
3. 源文件
接下来制作力学图的源文件,本次将数据写入 JSON 文件中。
{
"nodes":[
{ "name": "林黛玉" , "image" : "林黛玉.jpg" },
{ "name": "贾敏" , "image" : "贾敏.jpg" },
{ "name": "贾母" , "image" : "贾母.jpg" },
{ "name": "贾赦" , "image" : "贾赦.jpg" }
"edges":[
{ "source": 0 , "target": 1 , "relation":"母女" },
{ "source": 0 , "target": 2 , "relation":"孙女" },
{ "source": 1 , "target": 2 , "relation":"母女" },
{ "source": 1 , "target": 3 , "relation":"兄妹" },
{ "source": 2 , "target": 3 , "relation":"母子" }
}
力学图
1 读入文件
读入 JSON 文件
d3.json("relation.json",function(error,root){
if( error ){
return console.log(error);
console.log(root);
}
4.2 定义力学图的布局
力学图的 Layout(布局)如下:
var force = d3.layout.force()
.nodes(root.nodes)
.links(root.edges)
.size([width,height])
.linkDistance(200)
.charge(-1500)
.start();
其中 linkDistance 是结点间的距离, charge 是定义结点间是吸引(值为正)还是互斥(值为负),值越大力越强。
4.3 绘制连接线
绘制结点之间的连接线的代码如下:
var edges_line = svg.selectAll("line")
.data(root.edges)
.enter()
.append("line")
.style("stroke","#ccc")
.style("stroke-width",1);
var edges_text = svg.selectAll(".linetext")
.data(root.edges)
.enter()
.append("text")
.attr("class","linetext")
.text(function(d){
return d.relation;
});
其中,
第 1 – 6 行:绘制直线
第 8 – 15 行:绘制直线上的文字
直线上文字的样式为:
.linetext {
font-size: 12px ;
font-family: SimSun;
fill:#0000FF;
fill-opacity:0.0;
}
fill-opacity 是透明度,0表示完全透明,1表示完全不透明。这里是0,表示初始状态下不显示。
4.4 绘制结点
绘制结点的图片和文字:
var nodes_img = svg.selectAll("image")
.data(root.nodes)
.enter()
.append("image")
.attr("width",img_w)
.attr("height",img_h)
.attr("xlink:href",function(d){//绘制图片
return d.image; })
.on("mouseover",function(d,i){ //显示连接线上的文字
edges_text.style("fill-opacity",function(edge){
if( edge.source === d || edge.target === d ){
return 1.0;
.on("mouseout",function(d,i){ //隐去连接线上的文字
edges_text.style("fill-opacity",function(edge){
if( edge.source === d || edge.target === d ){
return 0.0;
.call(force.drag);
var text_dx = -20; var text_dy = 20;
var nodes_text = svg.selectAll(“.nodetext”) //节点文字,绘制图片下方的文字
.data(root.nodes)
.enter()
.append("text")
.attr("class","nodetext")
.attr("dx",text_dx)
.attr("dy",text_dy)
.text(function(d){
return d.name;
});
5 更新
设定力学图更新时调用的函数,使用 force.on ("tick", function(){ }),表示每一步更新都调用 function 函数。
force.on("tick", function(){
//限制结点的边界
root.nodes.forEach(function(d,i){
d.x = d.x - img_w/2 < 0 ? img_w/2 : d.x ;
d.x = d.x + img_w/2 > width ? width - img_w/2 : d.x ;
d.y = d.y - img_h/2 < 0 ? img_h/2 : d.y ;
d.y = d.y + img_h/2 + text_dy > height ? height - img_h/2 - text_dy : d.y ;
//更新连接线的位置
edges_line.attr("x1",function(d){ return d.source.x; });
edges_line.attr("y1",function(d){ return d.source.y; });
edges_line.attr("x2",function(d){ return d.target.x; });
edges_line.attr("y2",function(d){ return d.target.y; });
//更新连接线上文字的位置
edges_text.attr("x",function(d){ return (d.source.x + d.target.x) / 2 ; });
edges_text.attr("y",function(d){ return (d.source.y + d.target.y) / 2 ; });
//更新结点图片和文字
nodes_img.attr("x",function(d){ return d.x - img_w/2; });
nodes_img.attr("y",function(d){ return d.y - img_h/2; });
nodes_text.attr("x",function(d){ return d.x });
nodes_text.attr("y",function(d){ return d.y + img_w/2; });
});
var force = d3.layout.force() .nodes(root.nodes) .links(root.edges) .size([width,height]) .linkDistance(200) .charge(-1500) .start();
force.on("tick", function(){
});
这里的 force 是之前代码中定义的布局( Layout ),tick 表示当运动进行中每更新一帧时的事件。这是力学图中最常使用的事件,用于设定力学图每一帧是如何更新的。除此之外,还有一些其他常用的事件。
代码中,假设定义如下的布局:
var force = d3.layout.force()
.size([width,height])
.linkDistance(200)
.charge(-1500);
力学图布局force本身的事件,D3提供了3个,分别为start ,end,tick。在写代码时,可形如:
//力学图运动开始时
force.on("start", function(){
console.log("开始");
});
出现了以下代码:
.call(force.drag);
即设定当拖拽时调用函数 force.drag()。
D3中提供了3种拖拽事件:dragstart、dragend、drag。
var drag = force.drag()
.on("dragstart",function(d,i){
console.log("拖拽状态:开始");
})
完整代码:
<html>
<meta charset="utf-8">
<title>Force</title>
</head>
<style>
.nodetext {
font-size: 12px ;
font-family: SimSun;
fill:#000000;
.linetext {
font-size: 12px ;
font-family: SimSun;
fill:#0000FF;
fill-opacity:0.0;
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var width = 600;
var height = 600;
var img_w = 77;
var img_h = 90;
var svg = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
d3.json("relation.json",function(error,root){
if( error ){
return console.log(error);
console.log(root);
var force = d3.layout.force()
.nodes(root.nodes)
.links(root.edges)
.size([width,height])
.linkDistance(200)
.charge(-1500)
.start();
var edges_line = svg.selectAll("line")
.data(root.edges)
.enter()
.append("line")
.style("stroke","#ccc")
.style("stroke-width",1);
var edges_text = svg.selectAll(".linetext")
.data(root.edges)
.enter()
.append("text")
.attr("class","linetext")
.text(function(d){
return d.relation;
var nodes_img = svg.selectAll("image")
.data(root.nodes)
.enter()
.append("image")
.attr("width",img_w)
.attr("height",img_h)
.attr("xlink:href",function(d){
return d.image;
.on("mouseover",function(d,i){
//显示连接线上的文字
edges_text.style("fill-opacity",function(edge){
if( edge.source === d || edge.target === d ){
return 1.0;
.on("mouseout",function(d,i){
//隐去连接线上的文字
edges_text.style("fill-opacity",function(edge){
if( edge.source === d || edge.target === d ){
return 0.0;
.call(force.drag);
var text_dx = -20;
var text_dy = 20;
var nodes_text = svg.selectAll(".nodetext")
.data(root.nodes)
.enter()
.append("text")
.attr("class","nodetext")
.attr("dx",text_dx)
.attr("dy",text_dy)
.text(function(d){
return d.name;
force.on("tick", function(){
//限制结点的边界
root.nodes.forEach(function(d,i){
d.x = d.x - img_w/2 < 0 ? img_w/2 : d.x ;
d.x = d.x + img_w/2 > width ? width - img_w/2 : d.x ;
d.y = d.y - img_h/2 < 0 ? img_h/2 : d.y ;
d.y = d.y + img_h/2 + text_dy > height ? height - img_h/2 - text_dy : d.y ;
//更新连接线的位置
edges_line.attr("x1",function(d){ return d.source.x; });
edges_line.attr("y1",function(d){ return d.source.y; });
edges_line.attr("x2",function(d){ return d.target.x; });
edges_line.attr("y2",function(d){ return d.target.y; });
//更新连接线上文字的位置
edges_text.attr("x",function(d){ return (d.source.x + d.target.x) / 2 ; });
edges_text.attr("y",function(d){ return (d.source.y + d.target.y) / 2 ; });
//更新结点图片和文字
nodes_img.attr("x",function(d){ return d.x - img_w/2; });
nodes_img.attr("y",function(d){ return d.y - img_h/2; });
nodes_text.attr("x",function(d){ return d.x });
nodes_text.attr("y",function(d){ return d.y + img_w/2; });
</script>
</body>
</html>
地图的绘制 [2]
•地图的制作在 D3 中可以说是最重要的一环。因为在进行数据可视化时,很多情况都会和地图联系在一起,如中国各省的人口多少,GDP多少等,都可以和地图联系在一起。
• D3 中制作地图所需要的文件问 JSON 文件。JSON( JavaScript Object Notation) 是一种轻量级的数据交换格式。关于 JSON 的语法格式,可以在:
• http://www. w3school.com.cn/json/ http://www.w3school.com.cn/json/
•将 JSON 的格式应用于地理上的文件,叫做 GeoJSON 文件。本节就是用这种文件绘制地图。
•中国地图的 GeoJSON 文件: • china.json
这个地图文件 china.json [3] 下载下来了,但不是原地图文件,所以后面图形没法展示!
•这个文件是用 Natural Earth 上的数据,经过提取后制作而成。
开始用 D3 来绘制地图吧。绘制分为三步:
1. 设定投影函数
var projection = d3.geo.mercator()
.center([107, 31])
.scale(850)
.translate([width/2, height/2]);
由于 GeoJSON 文件中的地图数据,都是经度和纬度的信息,它们都是三维的。要在网页上显示的是二维的,所以要设定一个投影函数来转换经度纬度。如上所示,我们用 d3.geo.mercator() 的投影方式。关于各种投影方式的函数,可以参考: https:// github.com/mbostock/d3/ wiki/Geo-Projections
center() 函数是用于设定地图的中心位置,[107,31] 指的是经度和纬度。
scale() 函数用于设定放大的比例。
translate() 函数用于设定平移。
• 2. 设定 path 函数
• var path = d3.geo.path()
.projection(projection);
将上面的投影函数,作为参数,放入 path 中。这个 path 函数后面在绘制的时候就会用于转换经度纬度为平面信息,用于绘制。
3. 读取文件并绘制
d3.json("china.json", function(error, root) {
if (error)
return console.error(error);
console.log(root.features);
svg.selectAll("path")
.data( root.features )
.enter()
.append("path")
.attr("stroke","#000")
.attr("stroke-width",1)
.attr("fill", function(d,i){
return color(i);
.attr("d", path )
.on("mouseover",function(d,i){
d3.select(this)
.attr("fill","yellow");