【问题标题】:Flush a d3 v4 transition刷新 d3 v4 过渡
【发布时间】:2024-04-10 14:55:01
【问题描述】:

有人知道“刷新”过渡的方法吗? 我有一个转换定义如下:

this.paths.attr('transform', null)
  .transition()
  .duration(this.duration)
  .ease(d3.easeLinear)
  .attr('transform', 'translate(' + this.xScale(translationX) + ', 0)')

我知道我可以做到

this.paths.interrupt();

停止过渡,但这并没有完成我的动画。我希望能够“刷新”立即完成动画的过渡。

【问题讨论】:

    标签: d3.js transition


    【解决方案1】:

    如果我理解正确(我可能没有正确理解),则没有开箱即用的解决方案,而无需深入了解。但是,如果selection.interrupt() 是您正在寻找的形式,我相信您可以以相对简单的方式构建功能。

    为此,您需要为访问转换数据的 d3 选择创建一个新方法(位于:selection.node().__transition)。过渡数据包括关于补间、计时器和其他过渡细节的数据,但最简单的解决方案是将持续时间设置为零,这将强制过渡结束并将其置于结束状态:

    __transition 数据变量可以有空槽(可变编号),这可能会在 Firefox 中引起悲伤(据我所知,在使用 forEach 循环时),所以我使用了键方法获取包含转换的非空槽。

    d3.selection.prototype.finish = function() {
        var slots = this.node().__transition;
        var keys = Object.keys(slots);
        keys.forEach(function(d,i) {
            if(slots[d]) slots[d].duration = 0;
        })  
    }
    

    如果使用延迟,您还可以使用以下内容触发计时器回调:if(slots[d]) slots[d].timer._call();,因为将延迟设置为零不会影响过渡。

    使用此代码块调用selection.finish(),这将强制转换到其结束状态,单击一个圆圈来调用该方法:

    d3.selection.prototype.finish = function() {
      var slots = this.node().__transition;
      var keys = Object.keys(slots);
      keys.forEach(function(d,i) {
        if(slots[d]) slots[d].timer._call(); 
      })	
    }
    	
    var svg = d3.select("body")
       .append("svg")
       .attr("width", 500)
       .attr("height", 500);
    	
    var circle = svg.selectAll("circle")
       .data([1,2,3,4,5,6,7,8])
       .enter()
       .append("circle")
       .attr("cx",50)
       .attr("cy",function(d) { return d * 50 })
       .attr("r",20)
       .on("click", function() {  d3.select(this).finish() })
    	
    circle
       .transition()
       .delay(function(d) { return d * 500; })
       .duration(function(d) { return d* 5000; })
       .attr("cx", 460)
       .on("end", function() {
          d3.select(this).attr("fill","steelblue"); // to visualize end event
       })
    	
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.12.0/d3.min.js"></script>

    当然,如果您想保留方法 d3-ish,请返回选择,以便您可以在之后链接其他方法。为了完整起见,您需要确保有一个过渡要完成。添加这些内容后,新方法可能如下所示:

    d3.selection.prototype.finish = function() {
      // check if there is a transition to finish:
      if (this.node().__transition) {
          // if there is transition data in any slot in the transition array, call the timer callback:
          var slots = this.node().__transition;
          var keys = Object.keys(slots);
          keys.forEach(function(d,i) {
            if(slots[d]) slots[d].timer._call(); 
       })   
      }
      // return the selection:
      return this;
    }
    

    这是这个更完整实现的bl.ock


    以上内容适用于 D3 的第 4 版和第 5 版。在版本 3 中复制这一点有点困难,因为在版本 4 中对计时器和转换进行了一些重新设计。在版本 3 中,它们不太友好,但只需稍作修改即可实现该行为。为了完整起见,here's a block 是一个 d3v3 示例。

    【讨论】:

    • 此解决方案在 d3 v4 上运行良好。版本 3 可以做同样的事情吗?
    • @Will,当然 - 由于 v3 的转换和计时器方法(以及我的 cmets),它有点长,但仍然非常简洁。行为是相同的(基于我的基本测试),这里是demonstration
    • 安德鲁,感谢 v3 的示例。我实际上是在尝试刷新 x 和 y 轴转换,而那些 transition 对象没有 timer 属性。
    • 如果一个元素正在使用 d3 进行转换,它有一个计时器,但刻度的进入/退出有点复杂,我没有预料到。我的 v4/v5 代码也不涉及轴。虽然可能有一些修改可以使这个与轴一起工作,但可能更容易为轴g 的子轴使用中断(或删除它们)g 并再次调用轴。这是potential example。虽然,这并不是非常令人满意,但可能值得向轴提出这个特定的问题,以获得一些新的眼光。
    • 安德鲁,感谢您的帮助。还在测试它。它适用于 Chrome,但不适用于 PhantomJs。如果/当我发现某些东西时会更新。如果您有一些提示,将不胜感激。
    【解决方案2】:

    Andrew's answer 很棒。但是,出于好奇,我相信可以在不扩展原型的情况下使用.on("interrupt" 作为监听器。

    在这里,我无耻地复制了用于转换的 Andrew 代码和用于获取目标属性的 this answer

    selection.on("click", function() {
        d3.select(this).interrupt()
    })
    
    transition.on("interrupt", function() {
        var elem = this;
        var targetValue = d3.active(this)
            .attrTween("cx")
            .call(this)(1);
        d3.select(this).attr("cx", targetValue)
    })
    

    这里是演示:

    var svg = d3.select("svg")
    
    var circle = svg.selectAll("circle")
      .data([1, 2, 3, 4, 5, 6, 7, 8])
      .enter()
      .append("circle")
      .attr("cx", 50)
      .attr("cy", function(d) {
        return d * 50
      })
      .attr("r", 20)
      .on("click", function() {
        d3.select(this).interrupt()
      })
    
    circle
      .transition()
      .delay(function(d) {
        return d * 500;
      })
      .duration(function(d) {
        return d * 5000;
      })
      .attr("cx", 460)
      .on("interrupt", function() {
        var elem = this;
        var targetValue = d3.active(this)
          .attrTween("cx")
          .call(this)(1);
        d3.select(this).attr("cx", targetValue)
      })
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <svg width="500" height="500"></svg>

    PS:与Andrew's answer 不同,由于我在这里使用d3.active(node),因此只有当过渡已经开始时,点击才有效。

    【讨论】:

    • 很好,我玩弄了这种方法一段时间 - 但尽管有一些非常强烈的咒骂,我还是无法让它发挥作用 - 我同意,因为我喜欢它导致我的替代方案,这可能是矫枉过正,但会击中“结束”事件并相当简单地转换所有属性(我最近一直偏向于搞乱 d3 的内部)。
    • 谢谢大家!我将接受 Gerardo 的回答,因为我不是扩展原型的忠实粉丝。如果我可以问一个子问题..在你的例子中你改变cx并使用attrTween('cx').call(this)(1),如果我使用.attr('transform', 'translate(' + this.xScale(translationX) + ', 0)')会变成什么?
    • @Gabriel 我认为你应该接受 Andrew 的,但这没什么大不了的,接受的答案在 S.O. 并不重要,重要的是 upvote(这就是社区表明答案是好的和有用的)。关于您的子问题,它不会起作用,因为您会将那个值添加到 x 位置。因此,例如,如果您在圆即将完成转换时单击它,您会将整个 x 值添加到它。看看:jsfiddle.net/n4tkq9w9
    • 再次感谢您的回答。我更新了您的 JSFiddle 以实现我想要的。