D3v4 - on(“end”)在transition.duration结束前调用

时间:2016-12-15 09:42:43

标签: d3.js

我希望转换不同的数据集,并认为以下方法可行:

function next(keyIndex)
{
    if(keyIndex < 3)
    {
        d3.selectAll(".dot")
           .data(dataForKey(keyIndex))
           .transition().duration(3000)
           .call(position)
           .on("end", next(keyIndex+1));
    }
}

.on("end")调用在duration(3000)完成之前被调用,因此数据基本上从第一个数据集转换到最后一个数据集。
有没有办法从dataset[0] -> dataset[1] -> dataset[2]正确过渡?

2 个答案:

答案 0 :(得分:3)

您的主要问题是,您没有向.on("end",...)提供正确的回调功能。当它像.on("end", next(keyIndex+1))一样调用时,它将无法正常工作,因为语句next(keyIndex+1)将立即执行,从而在上一步结束之前开始下一个迭代步骤。在下一步中,刚刚开始的过渡将被取消并由新过渡取代。这样你就不会有一个具有指定持续时间的转换链,但是一些中断的转换后面紧跟一个最后的,不间断的转换,它将一直转换到最终值。

解决方案是将您对next()的调用包装在一个函数表达式中,以提供一个真正的回调函数,该函数将在 end 事件触发时调用。

function next(keyIndex) {
  if(keyIndex < 3) {
    d3.selectAll(".dot")
      .data(dataForKey(keyIndex))
      .transition().duration(3000)
      .call(position)
      .on("end", function() { next(keyIndex+1) });  // use a function wrapper
  }
}

对于工作演示,请查看以下代码段:

   var svg = d3.select("svg"),
    margin = {top: 40, right: 40, bottom: 40, left: 40},
    width = svg.attr("width") - margin.left - margin.right,
    height = svg.attr("height") - margin.top - margin.bottom,
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var xScale = d3.scaleLinear().domain([0, 10]).range([0, width]),
    yScale = d3.scaleLinear().domain([0, 10]).range([height, 0]);

    var xAxis = d3.axisBottom().scale(xScale).ticks(12, d3.format(",d")),
    yAxis = d3.axisLeft().scale(yScale);
    
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);
         
    // inital draw
    var dot = svg.append("g")
      .attr("class", "dots")
    .selectAll(".dot")
      .data(dataForKey(1))
    .enter().append("circle")
      .attr("class", "dot")
      .style("fill", "#000000")
	  .attr("cx", function(d) { return xScale(d.x); })
        .attr("cy", function(d) { return yScale(d.y); })
        .attr("r", function(d) { return 5; });
        
    // updated Data
    next(1);
    
    function dataForKey(key)
    {
    	return [{x:key, y:key}];
    }
    
    function next(keyIndex) {
      if (keyIndex < 10) {
        d3.selectAll(".dot")
          .data(dataForKey(keyIndex))
          .transition().duration(3000)
					.attr("cx", function(d) { return xScale(d.x); })
          .attr("cy", function(d) { return yScale(d.y); })
          .on("end", function() { next(keyIndex + 1) });
      }
    }
<script src="https://d3js.org/d3.v4.js"></script>

<svg width="300" height="300"></svg>

答案 1 :(得分:2)

注意,我打算将其作为副本关闭,因为我认为我理解OP的错误。相反,我创建了一个答案,因为我只是在猜测这个问题并希望提供一些解释。

您认为.on("end") call gets invoked before the duration(3000)不正确的论点。事实上,最终回调确实会在3秒后触发。我的猜测是你很想理解documentation中的这句话:

  

在选定节点上调度指定的转换事件时,   将为转换元素调用指定的侦听器,   传递当前数据d和索引i,使用此上下文作为   当前的DOM元素。听众总能看到最新的数据   他们的元素,但索引是选择的属性,是   在分配监听器时修复;更新索引,重新分配   听众。

这是说该事件将在转换中为每个元素触发,因此10个元素表示10个事件。第一个将在3秒后发生,其余时间为0到1毫秒。

请参阅以下代码段:

<!DOCTYPE html>
<html>

<head>
  <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>

<body>

  <svg>
    <circle class="dot"></circle>
    <circle class="dot"></circle>
    <circle class="dot"></circle>
    <circle class="dot"></circle>
    <circle class="dot"></circle>
    <circle class="dot"></circle>
    <circle class="dot"></circle>
    <circle class="dot"></circle>
    <circle class="dot"></circle>
    <circle class="dot"></circle>
  </svg>

  <script>
    dataForKey = {
      0: d3.range(10),
      1: d3.range(10),
      2: d3.range(10),
      3: d3.range(10)
    };

    var date = new Date();

    next(1);

    function next(keyIndex) {
      if (keyIndex < 3) {
        d3.selectAll(".dot")
          .data(dataForKey[keyIndex])
          .transition().duration(3000)
          //.call(position)
          .on("end", function() {
            console.log('end=', new Date().getTime() - date.getTime());
            date = new Date();
            next(keyIndex + 1)
          });
      }
    }
  </script>

</body>

</html>

您将看到的控制台输出类似于:

end= 3008 //<-- first element after 3 seconds
end= 0 //<-- other 9 events after 0 to 1 milliseconds
end= 0
end= 0
end= 0
end= 0
end= 0
end= 0
end= 0
end= 0

现在我认为你的问题变成了,如何在所有转换结束后触发回调 。许多times here已涵盖这一点。

<强> EDITS

感谢小提琴,总是有助于重现这个问题。 然后代码在小提琴中与你在问题中发布的内容不同。

你的问题实际上要简单得多; .on('end', func)期望一个函数作为第二个参数而你没有给它一个函数,你是调用一个函数并给它函数的返回值(无)。只需将其包装在一个匿名函数中:

.on("end", function(d){
  next(keyIndex + 1)
});

更新了fiddle