最近项目组加班比较严重,D3的博客就一拖再拖,今天终于不用加班了,赶忙抽点时间写完~~html
今天就将D3数据的更新及动画写一写~~git
接着以前的博客写~~github
以前写了一个散点图的例子,下面能够本身写一个柱状图的例子。数组
我就直接给代码了,和散点图差很少~~app
var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var dataset = [ 11, 12, 15, 20, 18, 17, 16, 18, 23, 25, 8, 10, 13, 19, 21, 25, 22, 18, 15, 13]; // 使用了d3.scale.ordinal() 它支持范围分档。与定量比例尺(如d3.scale.linear())返回连续的范围值不一样,序数比例尺使用的是离散范围值,也就是输出值是事先就肯定好的。 // 映射范围时,可使用range(),也可使用rangeBands()。后者接收一个最小值和一个最大值,而后根据输入值域的长度自动将其切分红相等的块或“档”。0.2也就是档间距为每一档宽度的20%。 var x = d3.scale.ordinal() .domain(d3.range(dataset.length)) .rangeBands([0, width], 0.2); var y = d3.scale.linear() .domain([0, d3.max(dataset, function(d) { return d; })]) .range([height, 0]); var xAxis = d3.svg.axis() .scale(x) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left"); var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); svg.selectAll("rect")// 插入的不是circle了,改成rect .data(dataset) .enter() .append("rect") .attr("x", function(d,i) { return x(i); }) .attr("y", function(d) { return y(d); }) .attr("width", x.rangeBand()) .attr("height", function(d) { return height - y(d); }) .attr("fill", function(d){ return "rgb(60, 127, " + d * 10 + ")";// 根据值的大小获取颜色 }); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("class", "label") .attr("x", width) .attr("y", -6) .style("text-anchor", "end") .text("X轴"); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("class", "label") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("Y轴");
其效果以下dom
坐标轴有些粗,是由于CSS没设置,若是设置上在以前博客里的CSS样式,会好看一点~~svg
先添加一个事件来触发数据的变化,在html中body标签里添加一个button函数
<button>Update</button>
为button绑定事件,并在事件中添加数据的更新,代码以下布局
// 单击的时候,更新数据 d3.select("button").on("click", function() { // 新数据集 dataset = [ 21, 22, 25, 10, 18, 17, 6, 8, 13, 15, 15, 20, 23, 19, 11,15, 25, 8, 25, 23 ]; // 更新全部矩形 svg.selectAll("rect") .data(dataset) .attr("y", function(d) { return y(d); }) .attr("height", function(d) { return height - y(d); }); });
运行代码,点击update按钮,chart发生变化,以下动画
若是你足够细心的话,就会发现,颜色跟原来的同样,没有根据长度发生变化,只要把原来针对fill 编写的代码复制到事件的代码里。
svg.selectAll("rect") .data(dataset) .attr("y", function(d) { return y(d); }) .attr("height", function(d) { return height - y(d); }) .attr("fill", function(d){ return "rgb(60, 127, " + d * 10 + ")";// 根据值的大小获取颜色 });
是否是颜色也跟着变化了。
你是否是以为,就这么变化有些太坑了,那咱们就给它加个动画,过渡一下就OK了,其实现只需简单的一行代码 .transition()
注:在方法链上,要把这个调用插到选择元素以后,改变任何属性以前
svg.selectAll("rect") .data(dataset) .transition() // <-- 这是新代码,其余都保持不变。 .attr("y", function(d) { return y(d); }) .attr("height", function(d) { return height - y(d); }) .attr("fill", function(d){ return "rgb(60, 127, " + d * 10 + ")";// 根据值的大小获取颜色 });
是否是有了动画,就不会以为变化有些忽然了~~
以后,我以为想控制一下动画的时间,要让他快一点或者慢一点,其实现也只须要一行代码 .duration(2000)
svg.selectAll("rect") .data(dataset) .transition() .duration(2000) // <-- 这是新代码,其余都保持不变。2000 毫秒,即2 秒 .attr("y", function(d) { return y(d); }) .attr("height", function(d) { return height - y(d); }) .attr("fill", function(d){ return "rgb(60, 127, " + d * 10 + ")";// 根据值的大小获取颜色 });
你延长了一下动画时间,发现它的动画一开始很是慢,而后逐渐加速,最后在达到预约高度以前速度再次慢了下来。换句话说,动画的速度不是线性不变的,而是有加减速变化的。
若是,我想要均匀的变化怎么办?
在D3 中,可使用ease() 指定不一样的缓动类型。默认的缓动效果是"cubic-inout",产生的就是咱们刚刚看到的那种逐渐加速而后再逐渐减速的效果。
因此咱们只须要设置一下ease("linear")就能够了。咱们要在transition() 以后、attr() 以前指定ease()。事实上,ease()在duration() 以前以后都没问题,但先过渡再设置缓动彷佛更瓜熟蒂落。
... // 选择元素的代码 .transition() .duration(2000) .ease("linear") ... //attr() 的代码
linear是线性缓动,就是没有逐渐加速和减速的变化,全部元素都按照一个速度变化,变化到最终值时戛然而止。
除此以外,还有不少缓动函数可供选择。下面只是几个,不是所有
• circle
逐渐进入并加速,而后忽然中止。
• elastic
描述这个效果的一个最恰当的词是“有弹性”。
• bounce
像皮球落地同样反复弹跳,慢慢停下来。
详细能够查看:https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease
咱们再来想一下,若是这个动画,我不想让它一开始就跑起来,我想让它晚点跑起来,好比说2秒后。
那咱们只须要在添加一个方法.delay(2000),就OK了
... .transition() .delay(2000) //2000 毫秒,即2 秒 .duration(2000) //2000 毫秒,即2 秒 ...
与使用duration() 和ease() 同样,把delay() 放到哪里并无十分严格的限制,但我更喜欢把它放在duration() 前面。由于是先设定延迟时间,而后过渡动画才开始计时,这样比较符合逻辑。
上面的代码是静态延时,静态延迟时间只是一种延迟方式,更有意思的是能够动态计算延迟时间。动态延迟的一个常见用途就是建立交错延迟的效果,让某些过渡在另外一个过渡以前发生。交错延迟对人的感知有利,由于当相邻元素的变化不那么同步时,人眼更容易注意到每一个元素的变化。要设置动态延迟,就别给delay() 传递静态值,而是给它传入一个函数,按照D3 的惯例……对,就是传入一个匿名函数。
... .transition() .delay(function(d, i) { return i * 100; }) .duration(500) ...
在匿名函数中,与当前元素绑定的数据值是以d 传入的,而这个元素的位置是以i传入的。所以,这里的意思是让D3 循环遍历每一个元素,把它们的动画延迟时间设定为i * 100,也就是后一个元素的动画开始时间总比前一个元素晚100 毫秒。
到这里,也许你会提出一个疑问,若是变化的时候数据超出了原来的范围,怎么办?
那咱们就须要更新比例尺了,更新比例尺的代码很简单。
// 更新比例尺的值域 y.domain([0, d3.max(dataset)]);
今天就先到这里,写博客时间长了,以为有点腰疼。
等有时间再继续更新吧~~
继续写这篇博客,就不另起一篇了~~
迄今为止,只要更新数据,咱们采用的都是“整批整包”的方式:改变数据集数组中 的值,而后从新绑定修改后的值,覆盖原始值对DOM 元素的绑定。这种方式很是适合全部值都会改变,并且数据集长度(即数据值的数量)不变的情形。但是咱们知道,现实中的数据可没那么简单。这就对代码的灵活 性提出了更高要求,好比只更新一两个值,或者支持增长值和减小值。
首先咱们须要在数据中插入一个值,并更新一下数轴的值域。
dataset.push(10);
x.domain(d3.range(dataset.length));
而后选出以前的元素,而后插入,插入的代码跟刚开始的代码很像。
// 加入…… var bars = svg.selectAll("rect") .data(dataset);// 选择出以前的元素 bars.data(dataset) .enter() .append("rect") .attr("x", x(dataset.length - 1))//这行代码设定了新条形的水平位置,让它刚好位于SVG 区域的最右边。 .attr("y", function(d) { return y(d); }) .attr("width", x.rangeBand()) .attr("height", function(d) { return height - y(d); }) .attr("fill", function(d){ return "rgb(60, 127, " + d * 10 + ")"; });
最后,再更新全部的数据就OK了
bars.transition() .duration(500) .attr("x", function(d, i) { return x(i); }) .attr("y", function(d) { return y(d); }) .attr("width", x.rangeBand()) .attr("height", function(d) { return height - y(d); }) .attr("fill", function(d){ return "rgb(60, 127, " + d * 10 + ")"; });
这样,动态的添加一条数据就实现了,而后你会发现数据的坐标轴的刻度没有变,那就须要再去更新一下坐标轴的刻度。在更新值域的代码后面,添加以下代码
xAxis.scale(x); svg.select("g.x.axis") .call(xAxis);
你会发现坐标轴的刻度也跟着变化了~~
把每次单击时添值到数据集,改成使用shift() 方法从数组中删除第一个元素。
// 从数据集中删除一个值 dataset.shift();
而后取得退出元素集,而后把它们过渡到右边,最后,删除它们
// 退出…… bars.exit() .transition() .duration(500) .attr("x", w) .remove();
remove() 是一特殊的过渡方法,它会在过渡完成后从DOM 中永远地删除元素。移除以后坐标轴的更新和以前的添加元素相同。
在把数据绑定到DOM 元素时(即调用data() 时),就会发生数据联结。默认的联结是按照索引顺序,即第一个值绑定到元素集中第一个DOM 元素,第二个值绑定到元素集中第二个DOM 元素,依此类推。若是数据值与DOM 元素的顺序不同呢?那就得告诉D3 怎么实现值和元素间的联结或配对。好在,经过定义键函数(key:function),能够指定相应的规则。
先准备数据,以前,咱们的数据集就是包含简单数值的数组。而为了使用键函数,每一个值必须有对应的键。
var dataset = [
{ key: 0, value: 5 }, { key: 1, value: 10 }, { key: 2, value: 13 }, { key: 3, value: 19 }, { key: 4, value: 21 }, { key: 5, value: 25 }, { key: 6, value: 22 }, { key: 7, value: 18 }, { key: 8, value: 15 }, { key: 9, value: 13 }, { key: 10, value: 11 }, { key: 11, value: 12 }, { key: 12, value: 15 }, { key: 13, value: 20 }, { key: 14, value: 18 }, { key: 15, value: 17 }, { key: 16, value: 16 }, { key: 17, value: 18 }, { key: 18, value: 23 }, { key: 19, value: 25 }
];
更新引用,许多之前用d的地方要修改成d.value,如
var y = d3.scale.linear() .domain([0, d3.max(dataset, function(d) { return d.value;})]) .range([0, height]);
连接键函数,在data一个数据集时,使用以下代码,就OK了。
.data(dataset, function(d) { return d.key; })
关于数据的更新和动画就先到这里为止了,以后会写D3的交互和布局~~