一、第一个程序
选择集: 使用d3.select()或者 d3.selectAll()选择元素后返回的对象,就是选择集
d3能够连续不断地调用函数,形如:d3.select().selctAll().text()这称为链式语法
二、选择元素和绑定数据
在D3中,用于选择元素的函数有两个:
d3.select():是选择所有指定元素的第一个
d3.selectAll():是选择指定元素的全部
这两个函数返回的结果称为选择集,选择集合绑定数据通常是一起使用的
D3 中通过以下两个函数来绑定数据:
1)datum():绑定一个数据到选择集上
2)data():绑定一个数组到选择集上,数组的各项值分别于选择集的各元素绑定
var str="China";
var body=d3.select('body');
var p=body.selectAll("p");
p.datum(str);
p.text(function(d,i){
return "第"+i+"个元素绑定的数据是"+d;
});
上面代码中,用到了一个无名函数 function(d,i),当选择集需要使用被绑定的数据时,
常需要这么使用,其包含两个参数,其中:
d 代表数据,也就是与某元素绑定的数据
i 代表索引,代表数据的索引号,从0开始
data()
有一个数组,接下来要分别将数组的各元素绑定到三个段落元素上
var dataset = ['I like dog','I like cat','I like snake'];
var body = d3.select("body");
var p = body.selectAll("p");
p.data(dataset)
.text(function(d,i){
return d;
});
三个段落元素与数组dataset 的三个字符串是一一对应
三、选择、插入、删除元素
select 和selectAll的用法
假设 body中有三个段落元素:
<p>Apple</p>
<p>Pear</p>
<p>Banana</p>
(1) 选择元素
现在,要分别完成以下四种选择元素的任务
1)选择第一个p元素
使用select,参数传入p即可
var p1=d3.select("p");
p1.style("color","red");
2)选择三个 p 元素
var body=d3.select("body");
var p= body.selectAll("p");
p.style("color","green");
3)选择第二个p 元素
一种比较简单的是给第二个元素添加一个id号,然后使用select 选择元素,注意参数中
id 名称前要加 # 号
var p2= body.select("#myid");
p2.style("color",'red');
4)选择后两个p元素
给后两个元素添加class
var p=body.selectAll(".myclass");
p.style("color","red");
关于 select 和selectAll 的参数,其实是符合css 选择器的条件的,
此外,对于已经绑定了数据的选择集,还有一种选择元素的方法,那就是灵活运用
function(d,i),我们已经知道参数 i 是代表索引号的,于是便可以用条件判定语句来指定执行的元素
(2)插入元素
插入元素涉及两个函数:
1)append():在选择集末尾插入元素
2)insert():在选择集前面插入元素
在body末尾添加一个p元素
body.append("p")
.text("append p element");
在 body 中 id 为 myid 的元素前添加一个段落元素。
body.insert("p","#myid")
.text("insert a element");
(3)删除元素
删除一个元素时,对于选择的元素,使用remove即可,例如:
var p=body.select("#myid");
p.remove();
四、做一个简单的图表
柱形图是一种最简单的可视化表,主要有 矩形、文字标签、坐标轴组成。
如何使用D3 在SVG画布中绘图
1)画布
要绘图,首要需要的是一块绘图的“画布”;
HTML5 提供两种强有力的画布:SVG 和Canvas
SVG 是什么:指可缩放矢量图形,用于描述二维矢量图形的一种图形格式。SVG使用XML
格式来定义图形。绝大部分浏览器都支持SVG,可将SVG文本直接嵌入HTML中显示
SVG 有如下特点:
1)SVG绘制的是矢量图,因此对图象进行放大不会失真
2)基于XMl,可以为每个元素添加javaScript 事件处理器
3)每个图形均视为对象,更改对象的属性,图形也会改变
4)不适合游戏应用
D3虽然没有明文规定一定要在SVG上绘图,但是D3提供了众多的 SVG 图形的生成器,它们都是
只支持SVG的,因此建议使用SVG 画布
// 横向显示的矩形
var width = 300;
var height = 300;
var svg = d3.select("body")
.append("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") //添加rect
.attr("x",20) //设置x
.attr("y",function(d,i){ //设置y
return i* rectHeight;
})
.attr("width",function(d,i){ //设置宽度
return d;
})
.attr("height",rectHeight-2) //设置高度
.attr("fill","steelblue");
//纵向显示的矩形
var width = 800;
var height = 1200;
var startY = 250;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var dataset = [250,210,130,170,90];
var rectHeight = 25;
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x",function(d,i){
return (i+1)*rectHeight;
})
.attr("y",function(d,i){
return startY-d;
})
.attr("width",rectHeight-2)
.attr("height",function(d,i){
return d;
})
.attr("fill","steelblue");
这段代码添加了与dataset数组的长度相同数量的矩形,所使用的语句是
svg.selectAll("rect") //选择svg内所有的矩形
.data(dataset) //绑定数组
.enter() //指定选择集的enter部分
.append("rect") //添加足够数量的矩形元素
这段代码以后会常常出现在D3 的代码中,请务必牢记。当:有数据,而没有足够图形元素的时候
,使用此方法可以添加足够的元素
添加了元素后,就需要分别给各元素的属性赋值。咋这里用到了function(d,i),前面已经讲过,
d代表与当前元素绑定的数据,i代表索引号。给属性赋值的时候,是需要用到被绑定的数据,已经索引号的
五、比例尺的使用
比例尺是D3中很重要的一个概念,上一章里曾经提到过直接用数值的大小来代表像素不是一种好办法
本章正是要解决此问题。
上面制作的柱形图,绘图时,直接使用数组中的值给矩形的宽度赋值,即矩形的宽度就是 250 个像素
此方式非常有局限性,如果数值过大或过小就不合理了。
于是,我们需要一种计算关系,能够 将某一区域的值映射到另一区域,其大小关系不变
这就是比例尺,那么有哪些比例尺呢
在数学中,x范围被称为定义域,y的范围被称为值域。
D3中的比例尺,也有定义域和值域,分别被称为 domain 和range
D3提供了多种比例尺,下面介绍最常用的两种
线性比例尺:
线性比例尺,能将一个连续的区间,映射到另一个区间。要解决柱形图宽度的问题,就需要线性比例尺。
假设有以下数组:
var dataset = [1.2,2.3,0.9,1.5,3.3];
要求如下: 将dataset 中最小值,映射成0,将最大的值映射成300
代码如下:
var dataset = [1.2,2.3,0.9,1.5,3.3];
var min = d3.min(dataset);
var max = d3.max(dataset);
var linear = d3.scale.linear()
.domain([min,max])
.range([0,300]);
console.log(linear(0.9)); //0
console.log( linear(2.3)); //175
console.log(linear(3.3)); //300
其中,d3.scale.linear()返回一个线性比例尺。domain()和range()分别设定比例尺的
定义域和值域。在这里还用到了两个函数,它们经常与比例尺一起出现:
d3.max()
d3.min()
这两个函数能够求数组的最大值和最小值,是D3提供的。
d3.scale.linear()的返回值,是可以当做函数来使用的。因此才有这样的用法:linear(0.9)
序数比例尺
有时候,定义域和值域不一定是连续的。例如:
var index = [0,1,2,3,4];
var color = ["red","blue","green","yellow","black"];
我们希望0 对应颜色 red ,1对应blue,但是这些值都是离散的,线性比例尺不合适
,需要用到序数比例尺
var index = [0,1,2,3,4];
var color = ["red","blue","green","yellow","black"];
var ordinal = d3.scale.ordinal()
.domain(index)
.range(color);
console.log(ordinal(0));
console.log(ordinal(2));
console.log(ordinal(4));
用法和线性比例尺是类似的。
给柱形图添加比例尺
var width = 300;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
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") //添加rect
.attr("x",20) //设置x
.attr("y",function(d,i){ //设置y
return i* rectHeight;
})
.attr("width",function(d,i){ //设置宽度
return linear(d);
})
.attr("height",rectHeight-2) //设置高度
.attr("fill","steelblue");
六、 坐标轴
坐标轴是可视化图表中经常出现的一种图形,由一些列线和刻度组成。坐标轴在SVG中
是没有现成的图形元素的,需要用其他的元素组合构成。D3提供了坐标轴的组件。
坐标轴由什么构成
在SVG 画布的预定义元素里,有六种基本图形:
1)矩形
2)圆形
3)椭圆
4)线段
5)折线
6)多边形
另外,还有一种比较特殊,也是功能最强的元素:
路径
画布中的所有图形,都是由以上七种元素组成的
D3提供了一个组件:d3.svg.axis(),它为我们完成了坐标轴的工作
var axis = d3.svg.axis()
.scale(linear) //指定比例尺
.orient("bottom") //指定刻度的方向
.ticks(7); //指定刻度的数量
定义了坐标轴之后,只需要在svg中添加一个分组元素,再将坐标轴的其他元素添加到
这个分组元素中即可。代码如下:
svg.append("g")
.call(axis);
上面有一个call()函数,其参数是前面定义的坐标轴axis
在D3中,call()的参数是一个函数。调用之后,将当前的选择集作为参数传递给此函数。
设定坐标轴的样式和位置
提供了一个常见的样式:
<style>
.axis path,.axis line{
fill:none;
stroke:black;
shape-rendering: crispEdges;
}
.axis text{
font-family:sans-serif;
font-size:11px;
}
</style>
带刻度的柱状图
var width = 300;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
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); //指定刻度的数量
svg.append("g")
.attr("class","axis")
.attr("transform","translate(20,130)")
.call(axis);
var rectHeight = 25;
svg.selectAll("rect")
.data(dataset)
.enter() //这个不能少
.append("rect") //添加rect
.attr("x",20) //设置x
.attr("y",function(d,i){ //设置y
return i* rectHeight;
})
.attr("width",function(d,i){ //设置宽度
return linear(d);
})
.attr("height",rectHeight-2) //设置高度
.attr("fill","steelblue");
//带刻度的竖型柱状图
var width = 300;
var height = 300;
var startY = 250;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var dataset =[250,210 ,170 ,130,90];
var linear = d3.scale.linear()
.domain([0, 2.5])
.range([0, 250]);
var axis = d3.svg.axis()
.scale(linear) //指定比例尺
.orient("bottom") //指定刻度的方向
.ticks(5); //指定刻度的数量
svg.append("g")
.attr("class","axis")
.attr("transform","translate(20,250)")
.call(axis);
var rectHeight = 25;
svg.selectAll("rect")
.data(dataset)
.enter() //这个不能少
.append("rect") //添加rect
.attr("x",function(d,i){
return (i+1)*rectHeight;
})
.attr("y",function(d,i){
return startY-d;
})
.attr("width",rectHeight-2)
.attr("height",function(d,i){
return d;
})
.attr("fill","steelblue");
七、完整的柱形图
一个完整的柱形图包含三部分:矩形、文字、坐标轴。一下是完整柱形图:内容包括:选择集、数据绑定、比例尺、坐标轴等内容
//添加SVG画布
var width = 400;
var height = 400;
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]) //因为y轴默认是越往下越大,所以反着写的
//定义坐标轴
//定义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",'#e4393c');
//添加文字元素
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;
});
//添加坐标轴的元素 +padding.left+","+"+(height-padding.bottom)+"
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);
八、 让图表动起来
D3中制作动态图表称为 过渡
实现动态的方法:
D3 提供了4个方法用于实现图形的过渡: 从状态A到状态 B
1)transition()
启动过渡效果
其前后是图形变化前后的状态(形状、位置、颜色等等) 例如:
.attr("fill","red") //初始颜色为红色
.transition() //启动过渡
.attr("fill","steelblue") //终止颜色为铁蓝色
D3会自动对两种颜色(红色和铁蓝色)之间的颜色值进行插值计算,得到过渡用的颜色值。我们无需
知道中间是怎么计算的,只需要享受结果即可
duration()
指定过渡的持续时间 ,单位为毫秒
如 duration(2000)
ease()
指定过渡方式,常用的有:
linear:普通的线性变化
circle:慢慢地到达变换的最终状态
elastic:带有弹跳的到达最终状态
bounce:在最终状态处弹跳几次
调用时,形如;ease("bounce")
delay()
指定延迟的时间,表示一定时间后才开始转变,单位同样为毫秒。此函数可以对整体指定延迟,
也可以对个别指定延迟
例如:对整体指定时:
.transition()
.duration(1000)
.delay(500)
又如,对一个个的图形进行指定:
.transition()
.duration(1000)
.delay(function(d,i){return 200*i;})
实现简单的动态效果
var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var circle1 = svg.append("circle")
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 45)
.style("fill","green");
//在1秒(1000毫秒)内将圆心坐标由100变为300
circle1.transition()
.duration(1000)
.attr("cx", 300);
var circle2 = svg.append("circle")
.attr("cx", 100)
.attr("cy", 200)
.attr("r", 45)
.style("fill","green");//与第一个圆一样,省略部分代码
//在1.5秒(1500毫秒)内将圆心坐标由100变为300,
//将颜色从绿色变为红色
circle2.transition()
.duration(1500)
.attr("cx", 300)
.style("fill","red");
var circle3 = svg.append("circle")
.attr("cx", 100)
.attr("cy", 300)
.attr("r", 45)
.style("fill","green");//与第一个圆一样,省略部分代码
circle3.transition()
.duration(2000)
.ease("bounce")
.attr("cx", 300)
.style("fill","red")
.attr("r", 25);
动起来的柱状图
<style>
.axis path,.axis line{
fill:none;
stroke:black;
shape-rendering: crispEdges;
}
.axis text{
font-family:sans-serif;
font-size:11px;
}
</style>
<script>
//添加SVG画布
var width = 400;
var height = 400;
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]) //因为y轴默认是越往下越大,所以反着写的
//定义坐标轴
//定义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;
} )
.transition()
.delay(function(d,i){
return i*200;
})
.duration(2000)
.ease("bounce")
.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",'#e4393c');
//添加文字元素
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){
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)})
.attr("dx",function(){
return (xScale.rangeBand() - rectPadding)/2;
})
.attr("dy",function(d){
return 20;
})
.style("stroke","#fff")
.text(function(d){
return d;
});
//添加坐标轴的元素 +padding.left+","+"+(height-padding.bottom)+"
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>
九、理解 Update 、Enter 、Exit
Update 、Enter 、Exit 是D3 中三个非常重要的概念,它处理的是当选择集和数据的数量关系
不确定的情况。
svg.selectAll("rect") //选择svg内所有的矩形
.data(dataset) //绑定数组
.enter() //指定选择集的enter部分
.append("rect") //添加足够数量的矩形元素
这段代码使用的情况是当以下情况出现的时候:
有数据,而没有足够图形元素的时候,使用此方法可以添加足够的元素。
假设,在 body 中有三个 p 元素,有一数组 [3, 6, 9],则可以将数组中的每一
项分别与一个 p 元素绑定在一起。但是,有一个问题:当数组的长度与元素数量不
一致(数组长度 > 元素数量 or 数组长度 < 元素数量)时呢?这时候就需要理解
Update、Enter、Exit 的概念。
如果数组为 [3, 6, 9, 12, 15],将此数组绑定到三个 p 元素的选择集上。可以想
象,会有两个数据没有元素与之对应,这时候 D3 会建立两个空的元素与数据对应,
这一部分就称为 Enter。而有元素与数据对应的部分称为 Update。如果数组为 [3],
则会有两个元素没有数据绑定,那么没有数据绑定的部分被称为 Exit。示意图如下
所示。
此时 SVG 里没有 rect 元素,即元素数量为 0。有一数组 dataset,将数组与元素
数量为 0 的选择集绑定后,选择其 Enter 部分(请仔细看上图),然后添加
(append)元素,也就是添加足够的元素,使得每一个数据都有元素与之对应。
Update 和 Enter 的使用
当对应的元素不足时 ( 绑定数据数量 > 对应元素 ),需要添加元素(append)。
现在 body 中有三个 p 元素,要绑定一个长度大于 3 的数组到 p 的选择集上,然后分别处理 update 和 enter 两部分。
var dataset = [ 3 , 6 , 9 , 12 , 15 ];
//选择body中的p元素
var p = d3.select("body").selectAll("p");
//获取update部分
var update = p.data(dataset);
//获取enter部分
var enter = update.enter();
//update部分的处理:更新属性值
update.text(function(d){
return "update " + d;
});
//enter部分的处理:添加元素后赋予属性值
enter.append("p")
.text(function(d){
return "enter " + d;
});
结果如下图,update 部分和 enter 部分被绑定的数据很清晰地表示了出来。
update和enter
请大家记住:
update 部分的处理办法一般是:更新属性值
enter 部分的处理办法一般是:添加元素后,赋予属性值
Update 和 Exit 的使用
当对应的元素过多时 ( 绑定数据数量 < 对应元素 ),需要删掉多余的元素。
现在 body 中有三个 p 元素,要绑定一个长度小于 3 的数组到 p 的选择集上,然后分别处理 update 和 exit 两部分。
var dataset = [ 3 ];
//选择body中的p元素
var p = d3.select("body").selectAll("p");
//获取update部分
var update = p.data(dataset);
//获取exit部分
var exit = update.exit();
//update部分的处理:更新属性值
update.text(function(d){
return "update " + d;
});
//exit部分的处理:修改p元素的属性
exit.text(function(d){
return "exit";
});
//exit部分的处理通常是删除元素
// exit.remove();
结果如下,请大家区分好 update 部分和 exit 部分。这里为了表明哪一部分是 exit,并没有删除掉多余的元素,但实际上 exit 部分的绝大部分操作是删除。
update和exit
请大家记住:
exit 部分的处理办法一般是:删除元素(remove)
十、交互式操作
用户用于交互的工具一般有三种:鼠标、键盘、触屏。
var circle = svg.append("circle");
circle.on("click", function(){
//在这里添加交互内容
});
这段代码在 SVG 中添加了一个圆,然后添加了一个监听器,是通过 on() 添加的。在 D3 中,每一个选择集都有 on() 函数,用于添加事件监听器。
on() 的第一个参数是监听的事件,第二个参数是监听到事件后响应的内容,第二个参数是一个函数。
鼠标常用的事件有:
click:鼠标单击某元素时,相当于 mousedown 和 mouseup 组合在一起。
mouseover:光标放在某元素上。
mouseout:光标从某元素上移出来时。
mousemove:鼠标被移动的时候。
mousedown:鼠标按钮被按下。
mouseup:鼠标按钮被松开。
dblclick:鼠标双击。
键盘常用的事件有三个:
keydown:当用户按下任意键时触发,按住不放会重复触发此事件。该事件不会区分字母的大小写,例如“A”和“a”被视为一致。
keypress:当用户按下字符键(大小写字母、数字、加号、等号、回车等)时触发,按住不放会重复触发此事件。该事件区分字母的大小写。
keyup:当用户释放键时触发,不区分字母的大小写。 触屏常用的事件有三个:
touchstart:当触摸点被放在触摸屏上时。
touchmove:当触摸点在触摸屏上移动时。
touchend:当触摸点从触摸屏上拿开时。 当某个事件被监听到时,D3 会把当前的事件存到 d3.event 对象,里面保存了当前事件的各种参数
//添加SVG画布
var width = 400;
var height = 400;
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]) //因为y轴默认是越往下越大,所以反着写的
//定义坐标轴
//定义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",'#e4393c')
.on("mouseover",function(d,i){
d3.select(this)
.attr("fill","yellow");
})
.on("mouseout",function(d,i){
d3.select(this)
.transition()
.duration(500)
.attr("fill","#e4393c");
});
//添加文字元素
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){
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)})
.attr("dx",function(){
return (xScale.rangeBand() - rectPadding)/2;
})
.attr("dy",function(d){
return 20;
})
.attr("stroke","#fff")
.text(function(d){
return d;
});
//添加坐标轴的元素 +padding.left+","+"+(height-padding.bottom)+"
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);
这段代码添加了鼠标移入(mouseover),鼠标移出(mouseout)两个事件的监听器。
监听器函数中都使用了 d3.select(this),表示选择当前的元素,this 是当前的
元素,要改变响应事件的元素时这么写就好。
mouseover 监听器函数的内容为:将当前元素变为黄色
mouseout 监听器函数的内容为:缓慢地将元素变为原来的颜色(蓝色)
十一、布局
我们可以据此定义什么时候选择 D3 比较好:
选择 D3:如果希望开发脑海中任意想象到的图表。
选择 Highcharts、Echarts 等:如果希望开发几种固定种类的、十分大众化的图表。
如何理解布局
从上面的图可以看到,布局的作用是:将不适合用于绘图的数据转换成了适合用于绘图的数据。
因此,为了便于初学者理解,本站的教程叫布局的作用解释成:数据转换。
布局有哪些
D3 总共提供了 12 个布局:饼状图(Pie)、力导向图(Force)、弦图(Chord)、
树状图(Tree)、集群图(Cluster)、捆图(Bundle)、打包图(Pack)、
直方图(Histogram)、分区图(Partition)、堆栈图(Stack)、
矩阵树图(Treemap)、层级图(Hierarchy)。
1)饼图
利用布局绘制饼图:
var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
//用D3进行数据可视化
var dataset = [30,10,43,55,13];
//定义一个布局
var pie = d3.layout.pie();
//返回值赋给变量pie,此时 pie 可以当做函数使用
var piedata = pie(dataset);
//将数组dataset作为pie()的参数,返回值给piedata,这样 piedata 就是转换后的数据
// console.log(piedata); //转换后为一个包含5个对象的数组
//布局不是要绘图而是为了得到绘图所需的数据
//为了根据转换后的数据 piedata 来作图,还需要一样工具:生成器。
//SVG 有一个元素,叫做路径 path,是 SVG 中功能最强的元素,
// 它可以表示其它任意的图形。顾名思义,路径元素就是通过定义
// 一个段“路径”,来绘制出各种图形。但是,路径是很难计算的,
// 通过布局转换后的数据 piedat
// a 仍然很难手动计算得到路径值。为我们完成这项任务的,就是生成器。
//这里要用到的叫做弧生成器,能够生成弧的路径,因为饼图的每一部分都是一段弧。
var outerRadius = 150;
var innerRadius = 0;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
//弧生成器返回的结果赋值给 arc。此时 arc可以当做一个函数使用,把piedata
//作为参数传入,即可得到路径值
//接下来,可以在 SVG 中添加图形元素了。先在 svg 里添加足够数量(5个)个分组元素(g),
// 每一个分组用于存放一段弧的相关元素。
var arcs = svg.selectAll("g")
.data(piedata)
.enter()
.append("g")
.attr("transform","translate("+(width/2)+","+(width/2)+")");
//接下来对每个g元素,添加path
//color是一个颜色比例尺,它能根据传入的索引号获取相应的颜色值
var color = d3.scale.category10();
var selectColor = "";
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.data;
});
//arc.centroid(d) 能算出弧线的中心。要注意,text() 里返回的是 d.data ,
// 而不是 d 。因为被绑定的数据是对象,里面有 d.startAngle、d.endAngle、
// d.data 等,其中 d.data 才是转换前的整数的值。
2)力导向图
力导向图(Force-Directed Graph),是绘图的一种算法。在二维或三维空间里配置
节点,节点之间用线连接,称为连线。各连线的长度几乎相等,且尽可能不相交。
节点和连线都被施加了力的作用,力是根据节点和连线的相对位置计算的。
根据力的作用,来计算节点和连线的运动轨迹,并不断降低它们的能量,
最终达到一种能量很低的安定状态。
力导向图能表示节点之间的多对多的关系
布局(数据转换) d3.layout.force()。
var width = 800;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
//用D3进行数据可视化
var nodes = [{name:"桂林"},{name:"广州"},{name:"厦门"},{name:"杭州"},{name:"上海"},{name:"青岛"},{name:"天津"}];
var edges = [{source:0,target:1},{source:0,target:2},{source:0,target:3},
{source:1,target:4},{source:1,target:5},{source:1,target:6},];
//定义一个力导向图的布局如下
var force = d3.layout.force()
.nodes(nodes) //指定节点数组
.links(edges) //指定连线数组
.size([width,height]) //指定作用域范围
.linkDistance(150) //指定连线长度
.charge([-400]); //相互之间的作用力
//使力学作用生效
force.start(); //开始作用
console.log(nodes);
console.log(edges);
//转换后,节点对象里多了一些变量。其意义如下:
// index:节点的索引号
//px, py:节点上一个时刻的坐标
//x, y:节点的当前坐标
//weight:节点的权重
//有了转换后的数据就可以作图了。分别绘制三种图形元素
//line 线段 ,表示连线
//circle 圆,表示节点
//text 文字,描述节点
//添加连线
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",20)
.style("fill",function(d,i){
return color(i);
})
.call(force.drag); //使节点能够拖动
//添加描述节点的文字
var svg_texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.style("fill","black")
.attr("dx",20)
.attr("dy",8)
.text(function(d){
return d.name;
});
// 调用 call( force.drag ) 后节点可被拖动。force.drag() 是一个函数,将其作为 call() 的参数,相当于将当前选择的元素传到 force.drag() 函数中。
//最后,还有一段最重要的代码。由于力导向图是不断运动的,每一时刻都在发生更新,因此,必须不断更新节点和连线的位置。
//力导向图布局 force 有一个事件 tick,每进行到一个时刻,都要调用它,更新的内容就写在它的监听器里就好。
force.on("tick", function(){ //对于每一个时间间隔
//更新连线坐标
svg_edges.attr("x1",function(d){ return d.source.x; })
.attr("y1",function(d){ return d.source.y; })
.attr("x2",function(d){ return d.target.x; })
.attr("y2",function(d){ return d.target.y; });
//更新节点坐标
svg_nodes.attr("cx",function(d){ return d.x; })
.attr("cy",function(d){ return d.y; });
//更新文字坐标
svg_texts.attr("x", function(d){ return d.x; })
.attr("y", function(d){ return d.y; });
});
var width = 800;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
//用D3进行数据可视化
var nodes = [{name:"桂林"},{name:"广州"},{name:"厦门"},{name:"杭州"},{name:"上海"},{name:"青岛"},{name:"天津"}];
var edges = [{source:0,target:1},{source:0,target:2},{source:0,target:3},
{source:1,target:4},{source:1,target:5},{source:1,target:6},];
//定义一个力导向图的布局如下
var force = d3.layout.force()
.nodes(nodes) //指定节点数组
.links(edges) //指定连线数组
.size([width,height]) //指定作用域范围
.linkDistance(150) //指定连线长度
.charge([-400]); //相互之间的作用力
//使力学作用生效
force.start(); //开始作用
console.log(nodes);
console.log(edges);
//转换后,节点对象里多了一些变量。其意义如下:
// index:节点的索引号
//px, py:节点上一个时刻的坐标
//x, y:节点的当前坐标
//weight:节点的权重
//有了转换后的数据就可以作图了。分别绘制三种图形元素
//line 线段 ,表示连线
//circle 圆,表示节点
//text 文字,描述节点
//添加连线
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",20)
.style("fill",function(d,i){
return color(i);
})
.call(force.drag); //使节点能够拖动
//添加描述节点的文字
var svg_texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.style("fill","black")
.attr("dx",20)
.attr("dy",8)
.text(function(d){
return d.name;
});
// 调用 call( force.drag ) 后节点可被拖动。force.drag() 是一个函数,将其作为 call() 的参数,相当于将当前选择的元素传到 force.drag() 函数中。
//最后,还有一段最重要的代码。由于力导向图是不断运动的,每一时刻都在发生更新,因此,必须不断更新节点和连线的位置。
//力导向图布局 force 有一个事件 tick,每进行到一个时刻,都要调用它,更新的内容就写在它的监听器里就好。
force.on("tick", function(){ //对于每一个时间间隔
//更新连线坐标
svg_edges.attr("x1",function(d){ return d.source.x; })
.attr("y1",function(d){ return d.source.y; })
.attr("x2",function(d){ return d.target.x; })
.attr("y2",function(d){ return d.target.y; });
//更新节点坐标
svg_nodes.attr("cx",function(d){ return d.x; })
.attr("cy",function(d){ return d.y; });
//更新文字坐标
svg_texts.attr("x", function(d){ return d.x; })
.attr("y", function(d){ return d.y; });
});
3)树状图
树状图,可表示节点之间的包含与被包含关系
数据
初始数据先写在一个JSON文件中,再用D3来读取
JSON 是一种轻量级的数据交换格式。
{
"name":"中国",
"children":
[
{
"name":"浙江" ,
"children":
[
{"name":"杭州" },
{"name":"宁波" },
{"name":"温州" },
{"name":"绍兴" }
]
},
{
"name":"广西" ,
"children":
[
{
"name":"桂林",
"children":
[
{"name":"秀峰区"},
{"name":"叠彩区"},
{"name":"象山区"},
{"name":"七星区"}
]
},
{"name":"南宁"},
{"name":"柳州"},
{"name":"防城港"}
]
},
{
"name":"黑龙江",
"children":
[
{"name":"哈尔滨"},
{"name":"齐齐哈尔"},
{"name":"牡丹江"},
{"name":"大庆"}
]
},
{
"name":"新疆" ,
"children":
[
{"name":"乌鲁木齐"},
{"name":"克拉玛依"},
{"name":"吐鲁番"},
{"name":"哈密"}
]
}
]
}
这段数据表示:“中国”“省份名”“城市名”的包含与被包含关系
布局(数据转换)
<style>
.node circle{
fill:#fff;
stroke:steelblue;
stroke- 1.5px;
}
.node{
font: 12px sans-serif;
}
.link{
fill:none;
stroke:#ccc;
stroke- 1.5px;
}
</style>
var width = 800;
var height = 800;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
.style("padding-top","50px");
var tree = d3.layout.tree()
.size([width,height-500])
.separation(function(a,b){return (a.parent == b.parent ?1:2);})
//布局保存在变量tree中
//size():设定尺寸,即转换后的各节点的坐标在哪一个范围内
//separation():设定节点之间的间隔
//转换数据
d3.json("data/data.json",function(error,root){
//root是读入的数据
var nodes = tree.nodes(root);
var links = tree.links(nodes);
// console.log(nodes);
//console.log(links);
//画点
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class","node")
.attr("transform",function(d){return "translate("+ d.x+","+ d.y+")"})
//加圈圈
node.append("circle")
.attr("r",4.5)
//加文字
var text = node.append("text")
.attr("dy",[15,15,15,15])
.attr("dx",[-11,-11,-11,-11])
.style("text-anchor", function (d) {return d.children?"end":"start"})
.text(function(d){
return d.name
})
var diagonal = d3.svg.diagonal()
.projection(function(d){return [d.x, d.y];})
//画线
var line = svg.selectAll("link")
.data(links)
.enter()
.append("path")
.attr("class","link")
.attr("d",diagonal)
});
// //d3.json() 是用来向服务器请求JSON文件的
// //d3.json()函数后面跟一个无名函数function(error),参数root是读取的数据
//
// //后两行代码调用tree转换数据,保存到变量nodes和links中。然后输出转换后的数据
// //nodes 中有各个节点的子节点(children)、深度(depth)、名称(name)、位置(x,y)信息,其中名称(name)是 json 文件中就有的属性。
//
// //links 中有连线两端( source , target )的节点信息。
// //绘制
// //D3已经基本上我们准备好了绘制的函数,d3.svg.diagonal()。这是一个对角线
// //生成器,只需要输入两个顶点坐标,即可生成一条贝塞尔曲线
//
// //创建一个对角线生成器:
// var diagonal = d3.svg.diagonal()
// .projection(function(d){return [d.y, d.x];})
// // projection()是一个点变换器,默认是[d.x , d.y],即保持原坐标不变,
// //如果写成[d.y ,d.x] 即是说对任意输入的顶点,都交换 x 和y坐标
// //绘制连线时,方法如下:
// var link = svg.selectAll(".link")
// .data(links)
// .enter()
// .append("path")
// .attr("class","link")
// .attr("d",diagonal); //使用对角线生成器
十二、D3 的核心函数
1、选择元素
D3提供了两种高级方法来选择元素:select 和 selectAll 。这些方法接受选择器字符串,前者只返回第一个匹配的元素,后者选择在文档遍历次序中所有匹配的元素。这个方法也可以接受节点,这可以用来和第三方库,例如jQuery或者开发者工具整合。
d3.select(selector)
选中与指定选择器字符串匹配的第一个元素,返回单元素选择结果。如果当前文档中没有匹配的元素则返回空的选择。如果有多个元素被选中,只有第一个匹配的元素(在文档遍历次序中)被选中。
# d3.select(node)
选择指定的节点。如果你已经有一个节点的引用这将是很有用的。例如事件监听器中的d3.select(this)
或者一个全局对象例如document.body`。这个函数不会遍历DOM树。
d3.selectAll(nodes)
选择指定的元素数组。如果你已经有了一些元素的引用这将是很有用的,例如事件监听器中的d3.selectAll(this.childNodes)
,或者一个全局对象例如document.links
。节点的参数不用恰好是数组;任何可以转化为一个数组的伪数组(例一个NodeList
或者 arguments`)都可以,这个函数不会遍历DOM树。
内容
D3有一系列可以影响文档内容的操作。这些是你最常用来展示数据的。当操作用来设置文档内容的时候会返回当前选择,所以你就可以使用一个简单的声明将多个操作链接在一起