一、第一个程序
选择集: 使用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 和CanvasSVG 是什么:指可缩放矢量图形,用于描述二维矢量图形的一种图形格式。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 、ExitUpdate 、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-width: 1.5px; } .node{ font: 12px sans-serif; } .link{ fill:none; stroke:#ccc; stroke-width: 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有一系列可以影响文档内容的操作。这些是你最常用来展示数据的。当操作用来设置文档内容的时候会返回当前选择,所以你就可以使用一个简单的声明将多个操作链接在一起