【问题标题】:With D3.js is it better to re-draw or "move" objects?使用 D3.js 重新绘制或“移动”对象更好吗?
【发布时间】:2016-12-15 02:37:19
【问题描述】:

我一直在尝试动画。

通过清除整个画布并在每一帧(或“tick”)在新位置重新绘制它来为画布上的对象设置动画非常简单:

// Inside requestAnimationFrame(...) callback

// Clear canvas
canvas.selectAll('*').remove();

// ... calculate position of x and y
// x, y = ...

// Add object in new position
canvas.append('circle')
    .attr('cx', x)
    .attr('cy', y)
    .attr('r', 10)
    .attr('fill', '#ffffff');

这是一种不好的做法还是我做得对?

例如,如果您正在制作一个满是四处移动的对象的屏幕,最好通过在每一帧中更新它们的属性(例如 x、y 坐标)来为它们设置动画?

或者,也许还有其他我完全不知道的方法,不是吗?

注意:我的动画可能一次包含 100-200 个对象。

【问题讨论】:

    标签: javascript canvas d3.js


    【解决方案1】:

    最好移动它们,因为这是您可以在没有错误的情况下制作动画的唯一方法。

    在 d3.js 中,对象是数据绑定的。清除和重新绘制“画布”不是正确的方法。首先,它不是画布,而是网页,任何清除和重绘都由浏览器本身处理。基本上,您的工作是将数据绑定到 SVG。

    您需要利用 d3 事件 enterexitupdate,它们处理 SVG 在数据绑定基础数据被修改时的行为,并让 d3 处理动画。

    最简单的例子在这里:https://bost.ocks.org/mike/circles/

    1. 选择您的元素,并将选择存储在一个变量中

    var svg= d3.select("svg");

    var circles = svg.selectAll('circle');

    1. 现在我们需要将一些数据绑定到圆。

    var databoundCircles = circles.data([12,13,14,15,66]);

    这个数据可以是任何东西。通常我会期望一个对象列表,但这些都是简单的数字。

    1. 处理数据出现时的“制造”方式

    databoundCircles.enter().append('circle');;

    1. 处理删除数据时发生的情况

    databoundCircles.exit().remove()

    1. 处理数据更新时发生的情况

    databoundCircles.attr('r', function(d, i) { return d * 2; })

    这将在数据更改时更改半径。

    回顾该教程:

    1. enter - 传入元素,进入舞台。

    2. 更新 - 持久元素,留在舞台上。

    3. exit - 传出元素,退出舞台。

    因此总结:不要像你现在那样做。确保您专门使用这些事件来处理元素的生命周期。

    专业提示:如果您使用对象列表,请确保通过 id 或某个唯一标识符绑定数据,否则动画可能会随着时间的推移而出现异常行为。请记住,您将数据绑定到 SVG,而不仅仅是擦除和重绘画布!

    d3.selectAll('circle').data([{id:1},{id:2}], function(d) { return d.id; });

    记下可选的第二个参数,它告诉我们如何绑定数据!非常重要!

    var svg = d3.select("svg");
    
    //the data looks like this.
    var data = [{
        id: 1,
        r: 3,
        x: 35,
        y: 30
    }, {
        id: 2,
        r: 5,
        x: 30,
        y: 35
    }];
    
    
    //data generator makes the list above
    function newList() {
        //just make a simple array full of the number 1
        var items = new Array(randoNum(1, 10)).fill(1)
        //make the pieces of data. ID is important!
        return items.map(function(val, i) {
          
            var r = randoNum(1, 16)
          
            return {
                id: i,
                r: r,
                x: randoNum(1, 200) + r,
                y: randoNum(1, 100) + r
            }
        });
    }
    
    //im just making rando numbers with this.
    function randoNum(from, to) {
        return Math.floor(Math.random() * (to - from) + from);
    }
    
    function update(data) {
      
        //1. get circles (there are none in the first pass!)
        var circles = svg.selectAll('circle');
        
        //2. bind data
        var databoundCircles = circles.data(data, function(d) {
            return d.id;
        });
    
        //3. enter
        var enter = databoundCircles.enter()
          .append('circle')
          .attr('r', 0)
    
        //4. exit
        databoundCircles.exit()
          .transition()
          .attr('r', 0)
          .remove();
    
        //5. update
        //(everything after transition is tweened)
        databoundCircles
            .attr('fill', function(d, i){
              var h =  parseInt(i.toString(16));   
              return '#' + [h,h,h].join('');
            })
    
            .transition()
            .duration(1000)
            .attr('r', function(d, i) {
                return d.r * 4
            })
            .attr('cx', function(d, i) {
                return d.x * 2;
            })
            .attr('cy', function(d, i){
                return d.y * 2
            })
     ;
    }
    
    //first time I run, I use my example data above
    update(data);
    
    //now i update every few seconds
    //watch how d3 'keeps track' of each circle
    setInterval(function() {
        update(newList());
    }, 2000);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
    <svg width="500" height="300">
    
    </svg>

    【讨论】:

    • 很棒的答案 - 感谢您刚刚所做的更新!
    • 我更新了 sn-p 以显示绑定 id 的意思。它现在更有用了。
    【解决方案2】:

    这是一种不好的做法还是我做得对?

    是的,这是一种不好的做法。在正常情况下,我喜欢将其称为 惰性编码:清除 SVG(或其他)并再次绘制数据可视化。

    但是,在您的情况下,情况更糟:您最终会编写 大量 代码(但不完全是 惰性),而忽略 d3.transition(),它可以轻松地做你想做的事。这将我们带到您的第二个问题:

    或者,也许还有其他我完全不知道的方法,不是吗?

    是的,正如我刚才所说,它叫transition()https://github.com/d3/d3-transition

    然后,最后,你说:

    注意:我的动画可能一次包含 100-200 个对象。

    首先,现代浏览器可以很好地处理这个问题。其次,您仍然必须手动删除并重新绘制 所有 元素。如果你对这两种方法进行基准测试,可能情况会更糟。

    因此,只需使用d3.transition()

    您可以随时更改元素的数据(或属性),并将它们“移动”(或转换)为调用转换的新值。例如,要移动这个圆圈,我不必删除它并重新绘制它:

    var circle = d3.select("circle")
    setInterval(() => {
        circle.transition()
            .duration(900)
            .attr("cx", Math.random() * 300)
            .attr("cy", Math.random() * 150)
            .ease(d3.easeElastic);
    }, 1000)
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <svg>
    	<circle r="10" cx="100" cy="50" fill="teal"></circle>
    </svg>

    【讨论】:

      猜你喜欢
      • 2011-12-02
      • 1970-01-01
      • 1970-01-01
      • 2017-04-07
      • 2013-05-18
      • 2015-01-01
      • 2018-06-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多