D3.js 是一个基于数据的操作文档的 JavaScript 库,可以让你绑定任何数据到 DOM,支持 DIV 这种图案生成,也支持 SVG 这种图案的生成(如果你对 SVG 不熟悉,请先看一下这篇文章,它介绍了 SVG、VML 和 Canvas)。D3 帮助你屏蔽了浏览器差异,做出来图案的效果可以说是炫目得一塌糊涂,可是代码却很简洁。在我第一次听人介绍 D3 的时候,确实被其示例震撼到了,大量的例子在这里可以找到。
这是 D3 以数据为核心的一个示意图,可以简单这样来解释:有一个选择区(selection),存在若干节点(node),因此可以容纳若干数据(data),最大可容纳数据的量等于节点的个数,即数据和节点一一对应,数据存放到节点中被称为绑定(bound to)。
初始感官认识:
通过下面这个例子 d3 例子
实现上,存在 6 个 circle 的 DOM 对象:
<svg id="vis">
<circle></circle>
<circle></circle>
<circle></circle>
<circle></circle>
<circle></circle>
<circle></circle>
</div>
在点击 Datasets 不同行的时候,数据的个数一会儿大于 6,一会儿小于 6,在变化过程中分别触发 enter/update 和 exit/update 的行为。可以看到左侧的球在红色边框的区域内进进出出,主要代码如下:
var datasets = [
[80,20,35,48,120,380],
[60,50,80,90,210,440],
[100,80,300,240,20,340,70,40],
[80,30,400]
];
var currentDataset = 0;
var render = function(datasetIndex){
var data = datasets[datasetIndex];
var circles = d3.select('#vis').selectAll('circle');
circles.data(data)
.style('fill', '#1CA2DA')
.transition()
.duration(800)
.attr('r', 15)
.attr('cx', function(d, i){ return i * 60 + 20; })
.attr('cy', function(d){ return d; });
//update
circles.data(data)
.enter()
.append('circle')
.attr('r', 15)
.attr('cx', 0)
.attr('cy', 0)
.style('opacity', 0)
.style('fill', 'red')
.transition()
.duration(1000)
.style('opacity', 1)
.attr('cx', function(d, i){ return i * 60 + 20; })
.attr('cy', function(d){ return d; });
//超出容量的数据,enter
circles.data(data)
.exit()
.transition()
.duration(800)
.style('opacity',0)
.remove();
//离开选择区的数据,exit
};
render(0);
var changeDataset = function(d, i){
d3.selectAll('.dataset').attr('class', 'dataset');
this.setAttribute('class', 'dataset active');
render(this.getAttribute('data-index'));
};
d3.selectAll('.dataset').on('click', changeDataset);
我想如果你习惯阅读 JQuery 代码,那么这样的代码很容易理解,链式编程的风格,清晰自然。以容器+数据的映射关联关系为核心,有点像状态机,但又不完全像,不同状态下基于其 update、enter 或者 exit 的行为绑定动作和状态属性的改变。
进一步介绍:
选择器:用惯了 JQuery 的话,这个没有什么可说的,比如这样的例子:
d3.select('#vis').append('div').style('top', '20px').style('left','20px');
会输出一个绿色的矩形。
动态属性:D3 支持这种以 function 方式传入的属性,这样的属性是动态的,每次执行的时候再去调用计算获得:
d3.selectAll("p").style("color", function() {
return "hsl(" + Math.random() * 360 + ",100%,50%)";
});
enter 和 exit:前文也已经提到了,当数据绑定到选择区对象上的时候,数据的每一个元素都会和选择区对象的每一个节点对应起来,节点内的数据发生变化,就是 update;节点内的数据移除,节点空出来,就是 exit;数据数量大过节点,造成数据剩余,就是 enter。然后就可以基于这个自定义这些事件发生的时候需要进行的行为和变更的状态了:
// Update…
var p = d3.select("body").selectAll("p")
.data([4, 8, 15, 16, 23, 42])
.text(String);
// Enter…
p.enter().append("p")
.text(String);
// Exit…
p.exit().remove();
转换,而非呈现(Transformation, not Representation):D3 并不是一个新的图像呈现类库,因此它和 Raphaël 是不一样的。你可以用 D3 加上自己定义的 CSS 来创建 SVG 图案,浏览器未来的特性也会被 D3 封装起来给你用,这些事情无非是让你对 DOM 和其上的数据的操作换了一种形式而已。
过渡(Transitions):这指的是图案从一种状态变化到另一种状态的时候,中间的动画过渡效果。D3 支持几种渐变的风格,帧速很高,实际上还是 CSS3 的渐变,但是对开发人员来说好用多了。
当然,直接拿 D3 来绘制图表可能会觉得繁琐,如果使用它的扩展就方便多了。
————————————————————————————————————-
2012 年 11 月 9 日:
有同事说这个东西太烂了,学习曲线太高,不容易掌握,而且要做一张图表的话,和一些 JQuery 的图标插件比起来,代码量也不少。
其实,我倒觉得还好,d3 功能比较强大,但是很多人不喜欢的主要原因是,这个东西是不符合人类常规思维的,它是那种以数据(以及容纳数据的容器)为核心的代码风格,以这个折线图为例:
如果用 SVG 来实现它的话,一般都要使用 path 标签了,关键代码包括,一个是计算点坐标的代码:
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
还有一个是绘制折线的代码:
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
另外,计算逻辑从时间上的滞后,即回调函数的使用也是破坏正常思维逻辑的一个因素。
文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》
×Scan to share with WeChat