【问题标题】:D3v4 - on("end") called before transition.duration is overD3v4 - 在 transition.duration 结束之前调用 on("end")
【发布时间】:2017-04-30 20:46:16
【问题描述】:

我想通过不同的数据集进行转换,并认为以下方法可行:

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] -&gt; dataset[1] -&gt; dataset[2] 正确转换?

【问题讨论】:

  • 我不明白你的问题。你能提供一个小提琴吗?
  • 我的问题是如何仅在当前转换完成后(本例中为 3 秒后)调用 next() 函数。 .on("end"...) 不起作用,因为它显然不会在调用回调方法之前等待当前转换的持续时间完成。

标签: d3.js


【解决方案1】:

您的主要问题是,您没有向.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
  }
}

对于工作演示,请查看以下 sn-p:

   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>

【讨论】:

    【解决方案2】:

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


    您关于.on("end") call gets invoked before the duration(3000) 的论点是不正确的。 end 回调实际上在 3 秒后触发。我的猜测是你错过了理解documentation中的这个声明:

    当指定的转换事件在选定节点上调度时, 将为过渡元素调用指定的侦听器, 正在传递当前数据 d 和索引 i,其中 this 上下文为 当前的 DOM 元素。听众总是看到最新的数据 它们的元素,但索引是选择的属性,并且是 在分配侦听器时修复;更新索引,重新分配 听者。

    这里的意思是事件将触发为过渡中的每个元素,所以 10 个元素意味着 10 个事件。第一个将在 3 秒后发生,其余的将在 0 到 1 毫秒后发生。

    查看这段代码sn-p:

    <!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
    

    现在我认为您的问题变成了,在所有转换结束后如何触发回调。这已经被很多timeshere报道了。

    编辑

    感谢您的提琴,总是有助于重现问题。 小提琴中的代码与您在问题中发布的代码不同。

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

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

    更新fiddle

    【讨论】:

    • 感谢您的帮助马克,我显然误解了一些重要的事情。我拼凑了一个小提琴来进一步说明我的问题:jsfiddle.net/zvt0y188/1我的期望是从 1:1 到 2:2 需要 3 秒,从 2:2 到 3:3 需要另外 3 秒等。相反,它会导致从 1:1 到 9:9 只有一个 3s 过渡。
    • @TommyF 我和你的问题实际上要简单得多。请参阅上面的修改。
    • 是的,就是这样,感谢您的帮助,非常感谢!
    猜你喜欢
    • 2016-08-13
    • 1970-01-01
    • 2014-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-03
    • 1970-01-01
    相关资源
    最近更新 更多