d3.js官网https://d3js.org/。全称为“data-driven Documents”,3个d嘛,简称d3.js。使用的话,官网上面有下载连接,就是一个zip压缩包。解压后把里面的d3.js引入就可以使用了,它并需要使用npm来安装,就是一个普通的js文件罢了,就像我们使用jquery,下载下来引入就可以使用了,方法是一样的。
d3.js是干嘛的呢?
大家有用过类似的图表展现工具吧,比如百度的echarts,国外的highcharts之类的,d3.js跟这些图标插件很类似,都是用来做数据可视化的,常说“一张图胜过千言万语”,在如今的大数据时代,数据的可视化显得尤为重要,而d3.js在这方面做得很是突出,d3.js也被称为操作svg的jquery,因为d3.js的图表都是基于svg的,在操作svg方面的语法与jquery的方法很是类似。
我们来看一个例子,实现的功能是使用d3.js在网页中写入“hello d3.js”,网页结构如下:
1
2
3
4
5
6
7
8
9
10
|
<!DOCTYPE html>
< html >
< head >
< meta charset = "utf-8" >
< title >index</ title >
< script src = "d3.min.js" ></ script >
</ head >
< body >
</ body >
</ html >
|
使用d3.js的JavaScript代码:
1
2
3
4
|
<script>
var body=d3.select( "body" );
body.append( "h1" ).text( "hello d3.js" );
</script>
|
打开这个网页:
使用d3.js在页面上画个圆圈
为了熟悉d3.js的api,我们通过一个小小的案例来领略一下d3.js的风采。这个案例很简单,就是使用d3.js在页面上画一个圆形,最终的效果如下图所示:
以下是代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!DOCTYPE html>
< html >
< head >
< meta charset = "utf-8" >
< title >index</ title >
< script src = "d3.min.js" ></ script >
</ head >
< body >
</ body >
< script >
var body=d3.select("body");
//body元素尾部添加一个svg标签
var svg=body.append("svg").attr("width",300).attr("height",300);
//svg尾部添加一个circle标签并设置属性
svg.append("circle").attr("cx",100).attr("cy",100).attr("r",50)
.attr("fill","#123456").attr("stroke","red").attr("stroke-width","2px");
</ script >
</ html >
|
代码解释:
d3.select用于获取html文档中的元素,里面的参数是css选择器,返回值是单个的经过d3封装的html元素。
append方法用于在调用者尾部附加一个元素,
attr(属性名,属性值)用于设置调用该方法的元素的属性,具体属性名是什么,自然跟调用该方法的元素有关,比如width和height都是svg元素有的属性,而下面的cx,cy都是svg里面的cricle标签的属性,可不是我随便乱写的。
学习过我前边写的svg相关的教程的同学都知道,最后一行的fill,stroke这些都是样式,对这些样式的控制我们最好还是写在css里,然后使用d3.js把这个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
|
<!DOCTYPE html>
<html>
<head>
<meta charset= "utf-8" >
<title>index</title>
<script src= "d3.min.js" ></script>
<style>
.mycircle{
fill: #123456;
stroke:red;
stroke-width:2px;
}
</style>
</head>
<body>
</body>
<script>
var body=d3.select( "body" );
//body元素尾部添加一个svg标签
var svg=body.append( "svg" ).attr( "width" ,300).attr( "height" ,300);
//svg尾部添加一个circle标签并设置属性
svg.append( "circle" ).attr( "cx" ,100).attr( "cy" ,100).attr( "r" ,50)
.attr( "class" , "mycircle" );
</script>
</html>
|
d3的update对象、enter对象、exit对象
先看下面的例子:
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
|
<!DOCTYPE html>
< html >
< head >
< meta charset = "utf-8" >
< title >index</ title >
< script src = "d3.min.js" ></ script >
</ head >
< body >
< div >div1</ div >
< div >div2</ div >
< div >div3</ div >
</ body >
< script >
var divs=d3.selectAll("div");
//divs是经过d3包装的元素数组
console.log(divs);
var arr=["a","b","c"];
var update=divs.data(arr);
//update对象是已经绑定了数据集的选择集
console.log(update);
update.text(function(val,index){
return "坐标:"+index+",值:"+val;
});
</ script >
</ html >
|
执行结果:
selectAll:返回的是经过d3包装的元素数组,称为d3的选择集
data用于把数组的元素依次绑定到选择集的元素上,有点类似es6里面解构赋值的意思。
datum也是绑定数据,和data()的区别在于datum是直接绑定数据到选择集上,并不存在"解构",如divs.datum(arr);则每个div将被绑定a,b,c
上面我说了,update对象就是绑定了数据集的选择集,在上面的这个例子中,选择集的长度是3,数据集的长度也是3,正好可以一一对应,但是现实的业务不可能这么完美,两者总有不相等的时候。
一、当选择集divs的大小>数据集arr的长度时
这个时候如果要想让他们一一对应,我们应该把divs中的多余的元素给找到并删除,可以这样理解,在d3.js中找到的多余的选择集里面的元素就是exit对象,把数据arr改为两个长度:
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
|
<!DOCTYPE html>
< html >
< head >
< meta charset = "utf-8" >
< title >index</ title >
< script src = "d3.min.js" ></ script >
</ head >
< body >
< div >div1</ div >
< div >div2</ div >
< div >div3</ div >
</ body >
< script >
var divs=d3.selectAll("div");
//divs是经过d3包装的元素数组
console.log(divs);
var arr=["a","b"];
var update=divs.data(arr);
//update对象是已经绑定了数据集的选择集
console.log(update);
update.text(function(val,index){
return "坐标:"+index+",值:"+val;
});
//此时有多余的div,找到(exit)并删除
var exit= update.exit();
console.log(exit);
exit.remove();
</ script >
</ html >
|
添加exit.remove();前后的结果对比:
二、当选择集divs的大小<数据集arr的长度时
此时要想对应,需要多补几个选择集的元素,补几个?补什么元素?都是通过d3.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
|
<!DOCTYPE html>
< html >
< head >
< meta charset = "utf-8" >
< title >index</ title >
< script src = "d3.min.js" ></ script >
</ head >
< body >
< div >div1</ div >
< div >div2</ div >
< div >div3</ div >
</ body >
< script >
var divs=d3.selectAll("div");
//divs是经过d3包装的元素数组
console.log(divs);
var arr=["a","b","c","d","e"];
var update=divs.data(arr);
//update对象是已经绑定了数据集的选择集
console.log(update);
update.text(function(val,index){
return "坐标:"+index+",值:"+val;
});
//增加enter对象
//enter对象可以获取数据集多几个元素
var enter= update.enter();
console.log(enter);
//多几个就补几个p
enter.append("p").text(function(val,index){
return "坐标:"+index+",值:"+val;
});
</ script >
</ html >
|
增加enter对象前后结果对比:
使用d3画一个没有刻度的柱形图
最终的效果如下:
代码如下,html代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<!DOCTYPE html>
< html >
< head >
< meta charset = "utf-8" >
< title >index</ title >
< script src = "d3.min.js" ></ script >
< style >
.bar{
fill:#123456;
}
</ style >
</ head >
< body >
</ body >
</ html >
|
d3画柱形图代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<script>
var dataset=[20,30,70,10,50];
var body= d3.select( "body" );
var svg=body.append( "svg" ).attr( "width" ,500).attr( "height" ,300);
var bars=svg.selectAll( ".bar" ).data(dataset).enter().append( "rect" )
.attr( "class" , "bar" )
.attr( "x" , function (val,index){
return 30*index+50;
})
.attr( "y" , function (val,index){
return 300-val-50;
})
.attr( "width" ,25)
.attr( "height" , function (val,index){
return val;
})
</script>
|
其他的都好理解,可能你不能理解的地方在于
attr("y",function(val,index){
return 300-val-50;
})
这一段,这一段的作用是让各个柱子都处在离svg下边缘50px的位置,也就是说让所有柱子都位于一条水平线上,如果不这样,那就不叫柱形图了,变成k线图了....,为什么写上“300-val-50”就可以位于一条水平线上了呢?看下面的图:
rect的y坐标是从上往下数的,这样子就应该理解了吧。
柱形图的完善---引入d3.js中的比例尺
在d3.js中,有很多种比例尺,常用的有两种。什么叫做比例尺呢?这个我相信大家心里都有个内化于心的概念,在d3.js中,我说的直白点,就是把一组值映射为另一组值,在这个映射的过程中,关系保持不变。那么为什么需要比例尺呢?在“使用d3画一个没有刻度的柱形图”上篇文章里面,设置柱状图的高度,我使用的代码是
1
2
3
|
attr( "height" , function (val,index){
return val;
}
|
也就是说,我直接使用了数据集里面元素的值作为柱状图的高度,因为数据集是var dataset=[20,30,70,10,50];整个svg大小我设置的宽高分别为500px和300px,并没有看出问题,可是我并不能担保在真正使用的时候数据集永远不会超过svg的大小,也就是说数据集里面有了个2000的值怎么办?就显示不了了呀,这就是问题的所在。因此需要引入比例尺。
d3.js中的两种比例尺:
一、线性比例尺
1
2
3
4
5
6
7
8
9
10
|
var dataset=[20,3000,70,100,2.5,0];
var min = d3.min(dataset);
var max = d3.max(dataset);
var linear = d3.scaleLinear()
.domain([min, max])
.range([0, 250]);
console.log(linear(0)); //返回 0
console.log(linear(3000)); //返回 250
console.log(linear(1500)); //返回 125
|
我这里使用的是d3的最新版本v5.0.0,在d3-v3的版本中,得到比例尺的写法是d3.scale.linear(),不管哪种写法,只是版本的不同罢了,返回的都是比例尺的对象,这个对象是个函数,因此我们后边可以直接使用linear(1500)的方式来写,其实就是调用的函数,参数是原来的数字,返回值为经过比例尺转化后的数字。domain([最小,最大])指定原来的值范围,range([最小,最大])指定映射范围。
二、序数比例尺
上边的线性比例尺指定的是范围、区间,可以映射这个区间内任意的值。但是,有时候我们需要映射的可能是离散的值,比如我想把[1,2,3]映射为["a","b","c"];使用线性比例尺就不行了,而应该使用序数比例尺。
1
2
3
4
5
6
7
8
|
var dataset=[1,2,3];
var ordinal = d3.scaleOrdinal()
.domain(dataset)
.range([ "a" , "b" , "c" ]);
console.log(ordinal(1)); //返回 a
console.log(ordinal(2)); //返回 b
console.log(ordinal(3)); //返回 c
|
比例尺介绍完了,我们把上篇文章里面的柱状图改善一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<script>
var dataset=[20,30,70,10,50];
//定义比例尺
var min = d3.min(dataset);
var max = d3.max(dataset);
var linear = d3.scaleLinear()
.domain([0, max]) //注意,这里不要用[min,max],不然最小的柱子高度就是0了,导致看不到
.range([0, 250]);
var body= d3.select( "body" );
var svg=body.append( "svg" ).attr( "width" ,500).attr( "height" ,300);
var bars=svg.selectAll( ".bar" ).data(dataset).enter().append( "rect" )
.attr( "class" , "bar" )
.attr( "x" , function (val,index){
return 30*index+50;
})
.attr( "y" , function (val,index){
return 300-linear(val)-50;
})
.attr( "width" ,25)
.attr( "height" , function (val,index){
return linear(val);
});
</script>
|
结果如下: