假如你正在使用D3
开发一个散点图,需要创建一些SVG circle
元素来展示你的数据。你可能会惊讶的发现D3
并不像原始那样创建多个DOM元素。
下面是一个用来创建单个元素的append
方法:
svg.append("circle")
.attr("cx", d.x)
.attr("cy", d.y)
.attr("r", 2.5);
这只是一个圆,而你想要根据数据创建更多的圆。在你使用for
循环暴力解决之前,思考一下D3示例中的这个神奇的代码片段:
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 2.5);
这段代码完全能够满足你的需要:它为每个数据点创建一个circle
元素,使用X和Y数据属性进行定位。但是selectAll("circle")
是什么意思?为什么要选择不存在的元素?
事情是这样的。不要告诉D3如何做某事,而是告诉它你想要做什么。你想要的是根据数据画圆,每一条数据对应一个圆,而不是教D3如何创建圆。因此告诉D3圆集合应该对应数据。这个概念叫做数据联接:
join从上图可以看出三种选集的关系:
update
选集:数据和已经存在的元素的交集。
enter
选集:表示未绑定的数据集合。
exit
选集:表示未绑定的元素集合。
现在,我们可以通过数据联接来解释之前的代码片段:
- 首选,因为SVG容器是空的,
svg.selectAll("circle")
返回一个新的空选集。 - 然后,通过
selection.data(data)
方法建立了数据和元素的连接。由于还没有元素,此时update
和exit
为空,enter
选集包含每条数据的占位符。 - 通过
selection.enter
方法获取需要创建元素的集合。 - 通过
selection.append("circle")
创建元素。
数据联接意味着宣告元素和数据之间的关系,然后通过update
、exit
、enter
三种状态来实现这种关系。
但是为什么搞这么麻烦?为什么不直接创建元素呢?
数据联接的优点在于它便于选择整个集合。虽然上面的代码只处理enter
选集,这对于静态可视化来说是足够的,但是你可以扩展它以支持动态可视化,只需对update
和exit
进行一些小的修改。这意味着您可以可视化实时数据,允许交互式探索,并在数据集之间平滑过渡!
下面是操作三种状态的一个例子:
var circle = svg.selectAll("circle")
.data(data);
circle.exit().remove();
circle.enter().append("circle")
.attr("r", 2.5)
.merge(circle)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
每当此代码运行时,它将重新计算数据联接,并维护元素和数据之间所需的对应关系。如果新的数据集比原来的小,那么多余的元素将被删除circle.exit().remove();
。如果新的数据集更大,更多的元素将被创建。如果新的数据集和旧的大小完全相同,所有元素将更新坐标,并不会删除或添加新的元素。
数据联接意味着你的代码更具声明性:处理这三种状态时不需要任何迭代和判断,而是描述元素和数据之间的对应关系。如果update
、exit
、enter
选集为空,则不做任何处理。
如果需要,联接还允许将操作定位到特定的状态。例如,可以在enter
而不是update
上设置常量属性(例如圆圈的半径,由“r”属性定义)。通过重新选择元素和最小化DOM更改,可以极大地提高呈现性能!类似地,您也可以针对特定状态的动画转换。例如,对于进入展开形式出现:
circle.enter().append("circle")
.attr("r", 0)
.transition()
.attr("r", 2.5);
同理,收缩的形式移除:
circle.exit().transition()
.attr("r", 0)
.remove();
网友评论