D3入门学习

D3是一个数据可视化工具,都说挺好。但是我个人这两天的体验来看,D3想要入门不是太简单,至少至少都要有HTML和JavaScript基础才行,对JavaScript的DOM要求也有点高。
其实网上也能找到很多学习资源。下面是我搜集的一些教程:
这里写图片描述

D3呢就是一个js库,使用它主要是用来做数据可视化的,官网上有很多酷炫的例子。
D3的官网

我最近学习的教程:
慕课网:使用D3制作图表
极客学院:D3.js入门教程

这两个教程对于初学者还是比较友好的,我前两天对D3还是一无所知,现在已经可以get到用D3画线图,面积图,柱状图,饼状图的方法了。


下面是我对画这几种图表的笔记:

线性图表

线性图表应该是最简单的图表了,我们从小学起就会画了,它可以用来描述两个变量之间的对应关系。
这里写图片描述
在绘制线性图表之前先在HTML文档中添加一个container容器,不要忘了引入D3的库文件。

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3-test-1</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="container"></div>

<script src="http://d3js.org/d3.v3.js"></script>
<script src="js/index.js"></script>
</body>
</html>

index.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
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
var width = 500,
height = 250,
margin = {left:50, top:30, right:20, bottom:20},
g_width = width - margin.left - margin.right,
g_height = height - margin.top - margin.bottom;
//svg
var svg = d3.select("#container") //选择container容器
.append("svg") //添加svg元素
//定义宽高
.attr("width",width)
.attr("height",height);

var g = d3.select("svg")
.append("g")
.attr("transform","translate("+margin.left+","+margin.top+")");//g元素偏移

//数据
var data = [1, 2, 4, 8, 3, 2, 5];

// 缩放函数,X轴线性缩放
var scale_x = d3.scale.linear()
.domain([0,data.length-1]) // 输入范围
.range([0,g_width]); //输出范围
//Y轴缩放函数
var scale_y = d3.scale.linear()
.domain([0,d3.max(data)])
.range([g_height,0]);//倒着写是为了让纵坐标在显示的时候是从下往上增大的(因为浏览器的左边原点在左上而数学习惯是在左下)

//绘制函数,line_generator是我们指定的绘制函数
var line_generator = d3.svg.line()
.x(function(d,i) { return scale_x(i);})//X轴的点用数据的下标来表示
.y(function(d) { return scale_y(d);})
.interpolate("cardinal");//指定拟合方式,如果没有它,图是折线形式的,有了它就很圆润了

//添加path路径,就可以绘图啦
d3.select("g")
.append("path")
.attr("d",line_generator(data));

//绘制X轴和Y轴
var x_axis = d3.svg.axis().scale(scale_x),
y_axis = d3.svg.axis().scale(scale_y).orient("left"); //orient("left")确定左边方向

g.append("g")
.call(x_axis)
.attr("transform","translate(0,"+g_height+")"); //偏移

g.append("g")
.call(y_axis)
//添加文字元素
.append("text")
.text("Price($)")
.attr("transform","rotate(-90)") //Y轴旋转-90°
.attr("text-anchor","end")
.attr("dy","1em");

js文件我都写了详细的注释。
style样式文件用来定义container容器,path路径以及坐标轴的一些样式。

style.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#container {
background-color: #ccc;
width: 500px;
height: 250px;
}
path {
fill: none;
stroke: #468284;
stroke-width: 2;
}
.domain, .tick line {
stroke: gray;
stroke-width: 1;
}

我觉得比较重要的就是选择元素select()和插入元素append() 操作,还有缩放函数的方法和定义X轴、Y轴的方法。这些都是基础又基础的东西!


面积图表

面积图表是在线性图表的基础上绘制的
这里写图片描述

将绘制函数改为area()函数:

1
2
3
4
5
6
//绘制函数,面积图用D3的area()函数绘制
var area_generator = d3.svg.area()
.x(function(d,i) { return scale_x(i);})
.y0(g_height)
.y1(function(d) { return scale_y(d);})
.interpolate("cardinal");

这个时候图表已经成为一个闭合的图形了:
这里写图片描述

在绘制图形后在添加一个填充样式.style("fill","#468284"):

1
2
3
4
d3.select("g")
.append("path")
.attr("d",area_generator(data))
.style("fill","#468284");

一个面积图表就完成了。


柱状图表

这里写图片描述

柱状图的要点是添加矩形元素。这里跟上面线性图表的代码风格不太一样,是因为我觉得这个demo更直观,更清晰。

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
<html>
<head>
<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 {
fill: steelblue;
}

.MyText {
fill: white;
text-anchor: middle;
}
</style>

<body>
<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);
});

//添加文字元素
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>

要特别注意的地方是理解enter() 的用法,在极客学院的教程上是这样讲的:

有数据,而没有足够图形元素的时候,使用此方法可以添加足够的元素。
如果数组为 [3, 6, 9, 12, 15],将此数组绑定到三个 p 元素的选择集上。可以想象,会有两个数据没有元素与之对应,这时候 D3 会建立两个空的元素与数据对应,这一部分就称为 Enter。而有元素与数据对应的部分称为 Update。如果数组为 [3],则会有两个元素没有数据绑定,那么没有数据绑定的部分被称为 Exit。示意图如下所示。

这里写图片描述

外部数据文件的读取

慕课网的教程讲到了外部数据文件的读取,我觉得挺有用的
值得注意的一点是浏览器读取本地文件,它有一个跨域请求的问题,会报如下的错误:
这里写图片描述
解决办法是使用server提供的一种访问机制,用Python启用服务器,你要是想运行呢,首先你要安装python,下面是python2.到3以下版本执行的命令

1
python -m SimpleHTTPServer 8888 &

3以上用下面的命令

1
python -m http.server 8888 &

启用服务是在当前图表文件内运行的,启用服务后在浏览器中输入localhost:8888 就可以访问自己写的图表了
我们要读取的数据文件名为data.csv

1
2
3
4
5
6
7
year,population
1953,5.94
1964,6.95
1982,10.08
1990,11.34
2000,12.66
2010,13.40

6次人口普查的数据,这里用到了D3的d3.csv(url[,accessor][,callback])函数,以下面的函数为例,第一个参数是必选的,是我们要读取的csv文件的路径;第二个参数是数据的处理,定义一个type函数将字符串转换为数值的形式;第三个参数用一个函数调用的方式使用数据,把最终的数据放到data数组当中。

1
2
3
4
5
6
7
8
9
d3.csv("data.csv",type,function(data) {
console.log(data);
···
});

function type(d) {
d.population =+ d.population;
return d;
}

这里写图片描述

饼状图表

这里写图片描述

饼状图表的要点就是:第一我们要用d3.svg.arc() 画圆的函数;第二就是起始角度的设置:
HTML还是像线性图表一样定义一个container容器即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>饼状图表</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="container"></div>

<script src="http://d3js.org/d3.v3.js"></script>
<script src="js/index.js"></script>
</body>
</html>

数据文件data.csv

1
2
3
4
5
6
education,population
大专及以上,11964
高中和中专,18799
初中,51966
小学,35876
文盲人口,5466

index.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
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
d3.csv("data.csv",type,function(data) {

var width = 400,
height = 400;
// 添加svg画布
var svg = d3.select("#container")
.append("svg")
.attr("width",width)
.attr("height",height);
// 添加g元素
var g = svg.append("g")
.attr("transform","translate(200,200)");

// 定义一个饼状图
var arc_generator = d3.svg.arc()
.innerRadius(0)
.outerRadius(200);

// 起始角度
var angle_data = d3.layout.pie()
.value(function(d) {return d.population;});

// 颜色
var color = d3.scale.category10();

// 绑定数据
g.selectAll("path")
.data(angle_data(data))
.enter()
// 添加path元素
.append("path")
.attr("d",arc_generator)
.style("fill",function(d,i){return color(i);});

// 添加文字标签
g.selectAll("text")
.data(angle_data(data))
.enter()
// 添加text元素
.append("text")
.text(function(d) {return d.data.education;})
.attr("transform", function(d){return "translate("+arc_generator.centroid(d)+")";})
.attr("text-anchor","middle");

});
function type(d) {
d.population = +d.population;
return d;
}