如何使用D3一个接一个地展开多行...没有代码重复?

时间:2014-11-03 18:08:48

标签: javascript animation svg d3.js charts

Mike Bostock有一个很好的多系列线图here的例子,它使用紧凑的代码绘制了三个系列。他不是从原始数据中单独绘制每一行,而是创建一个新的数据对象(“城市”)进行迭代,然后一次性绘制所有三个路径元素:

var city = svg.selectAll(".city")
  .data(cities)
.enter().append("g")

[...等]但是假设我们想要动画这个图表,以便每个城市的线路一个接一个地“展开”?使用Bostock的数据结构是否可以这样做?

首先,我们创建一个补间函数来平滑地展开这些行(如this question中所示)。类似的东西:

function animateLine() {
    var l = this.getTotalLength();
    i = d3.interpolateString("0," + l, l + "," + l);
    return function(t) { return i(t); };
};

但是如果我们将此动画作为Mike Bostock的线条绘制代码的转换调用,那么它不会同时展开所有三行,而不是一个接一个地展开吗?

我可以看到一个hacky解决方案,它将定义三个独立的(但基本相同的)函数,根据一些轻微修改的原始数据,一次绘制一行一个...

var AustinLine = d3.svg.line()
            .x(function(d) { return x(d.date); })
            .y(function(d) { return y(d.austin); });

var NewYorkLine = d3.svg.line()
            .x(function(d) { return x(d.date); })
            .y(function(d) { return y(d.newyork); });

... [等]在三个独立的线条绘制函数中调用,调用我们的“animateLine”函数 -

function AustinPath () { 
                var line = svg.append("path")
                    .datum(data)
                    .attr("d", AustinLine)
                .transition()
                    .duration(2000)
                    .ease("linear")
                    .attrTween("stroke-dasharray", animateLine);

... [对其他城市重复两次:NewYorkPath和SanFranciscoPath] ......

...然后终于以相应的延迟一个接一个地调用这些函数,逐个绘制线条。

setTimeout(Austinpath, 500);
setTimeout(NewYorkPath, 2500);
setTimeout(SanFranciscoPath, 4500);

... [等等]

这将逐一展开每一行,但代码中的重复是令人难以置信的,完全违背Mike Bostock原始例子的优雅。

我知道hacky解决方案很可怕......但是有更优雅的选择吗? Mike Bostock的原始“enter()。append”方法可以交错排列,依次绘制三条不同的行吗?

任何建议都非常感谢。提前谢谢。

1 个答案:

答案 0 :(得分:0)

您为线路展开链接的问题使用了D3的转换框架,因此您不需要任何其他内容就可以让它们一个接一个地显示。这里的关键是.delay()函数,它允许您指定转换应该开始的时间。它可以像D3中的其他所有东西一样,传递当前数据和索引而不是静态值的函数。

因此,知道展开需要一秒钟,我们可以像这样设置延迟:

.delay(function(d, i) { return i * 1000; })

即,将每个元素的转换延迟其索引时间1秒。在.transition()之后添加此代码可以处理您想要的内容。

正如您所发现的那样,唯一的障碍是在消失和展开之前最初会绘制线条。这是因为您在开始转换之前将行的d属性设置为其正确(结束)值。要避免此影响,只需将stroke-dasharray属性设置为在转换开始时(animateLine(0))的值,然后再添加转换 - "0," + this.getTotalLength()。完整演示此here