代码之家  ›  专栏  ›  技术社区  ›  atdyer

使用svg变换更新位置时,力模拟会出现抖动

  •  5
  • atdyer  · 技术社区  · 7 年前

    JSFiddle example

    我注意到,在 d3-force 图,使用(在圆的情况下)更新元素的位置 cx cy 属性比使用 transform 属性

    在JSFiddle示例中,有两个单独的力模拟并排进行。左边的一个使用 使改变 属性:

    sim_transform.on('tick', function () {
      circles_transform.attr('transform', function (d) {
            return 'translate(' + d.x + ',' + d.y + ')';
      });
    });
    

    右侧的一个使用 cx公司 cy公司 圆的属性:

    sim_position.on('tick', function () {
      circles_position
        .attr('cx', function (d) {
          return d.x;
        })
        .attr('cy', function (d) {
            return d.y;
        })
    });
    

    模拟看起来完全相同,直到它们即将变为静态,此时使用变换的模拟开始有相当大的抖动。知道是什么原因吗?是否可以修复该问题,以便使用变换使动画保持平滑?

    1 回复  |  直到 7 年前
        1
  •  4
  •   Gerardo Furtado    7 年前

    在我看来,您所观察到的问题(仅在FireFox中重现,如 @altocumulus noted )与FF使用浮点数的方式有关 translate transform 属性

    如果我们将两个模拟都设置为使用整数,则可以看到这一点 ~~(d.x) ~~(d.y) .看一看,两者都会抖动:

    var svg = d3.select('svg');
    var graph_transform = gen_data();
    var graph_position = gen_data();
    
    var force_left = d3.forceCenter(
      parseInt(svg.style('width')) / 3,
      parseInt(svg.style('height')) / 2
    )
    var force_right = d3.forceCenter(
      2 * parseInt(svg.style('width')) / 3,
      parseInt(svg.style('height')) / 2
    )
    
    var sim_transform = d3.forceSimulation()
      .force('left', force_left)
      .force('collide', d3.forceCollide(65))
      .force('link', d3.forceLink().id(id));
    var sim_position = d3.forceSimulation()
      .force('right', force_right)
      .force('collide', d3.forceCollide(65))
      .force('link', d3.forceLink().id(id));
    
    var g_transform = svg.append('g');
    var g_position = svg.append('g');
    
    var circles_transform = g_transform.selectAll('circle')
      .data(graph_transform.nodes)
      .enter()
      .append('circle')
      .attr('r', 40);
    
    var circles_position = g_position.selectAll('circle')
      .data(graph_position.nodes)
      .enter()
      .append('circle')
      .attr('r', 40);
    
    sim_transform
      .nodes(graph_transform.nodes)
      .force('link')
      .links(graph_transform.links);
    
    sim_position
      .nodes(graph_position.nodes)
      .force('link')
      .links(graph_position.links);
    
    sim_transform.on('tick', function() {
      circles_transform.attr('transform', function(d) {
        return 'translate(' + (~~(d.x)) + ',' + (~~(d.y)) + ')';
      });
    });
    
    sim_position.on('tick', function() {
      circles_position
        .attr('cx', function(d) {
          return ~~d.x;
        })
        .attr('cy', function(d) {
          return ~~d.y;
        })
    });
    
    function id(d) {
      return d.id;
    }
    
    function gen_data() {
      var nodes = [{
          id: 'a'
        },
        {
          id: 'b'
        },
        {
          id: 'c'
        },
        {
          id: 'd'
        }
      ]
    
      var links = [{
          source: 'a',
          target: 'b'
        },
        {
          source: 'b',
          target: 'c'
        },
        {
          source: 'c',
          target: 'd'
        },
        {
          source: 'd',
          target: 'a'
        }
      ];
      return {
        nodes: nodes,
        links: links
      }
    }
    svg {
      width: 100%;
      height: 500px;
    }
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <svg></svg>

    因此,在原始代码中,使用 cx cy ,但在使用 翻译 (或者可能是半像素,请参见上一个演示)。如果这里的假设是正确的,那么当模拟冷却时,您只看到效果的原因是,在那一刻,运动更小。

    演示

    现在,如果我们去掉模拟,我们可以看到这种奇怪的行为也发生在一个非常基本的 使改变 为了验证这一点,我创建了一个大的黑色圆圈的过渡,使用了一个线性的轻松和很长的时间(以便于看到问题)。圆圈将向右移动30px。我还画了一条网格线 跳跃 更引人注目。

    (警告:以下演示仅在 FireFox ,您在Chrome/Safari中看不到任何区别)

    如果我们使用 cx公司 ,过渡平滑:

    var svg = d3.select("svg");
    
    var gridlines = svg.selectAll(null)
      .data(d3.range(10))
      .enter()
      .append("line")
      .attr("y1", 0)
      .attr("y2", 200)
      .attr("x1", function(d) {
        return 300 + d * 3
      })
      .attr("x2", function(d) {
        return 300 + d * 3
      })
      .style("stroke", "lightgray")
      .style("stroke-width", "1px");
    
    var circle = svg.append("circle")
      .attr("cx", 200)
      .attr("cy", 100)
      .attr("r", 98)
      .transition()
      .duration(10000)
      .ease(d3.easeLinear)
      .attr("cx", "230")
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <svg width="500" height="200"></svg>

    然而,如果我们使用 翻译 ,您可以看到圆圈在每次移动时都会跳跃1px:

    var svg = d3.select("svg");
    
    var gridlines = svg.selectAll(null)
      .data(d3.range(10))
      .enter()
      .append("line")
      .attr("y1", 0)
      .attr("y2", 200)
      .attr("x1", function(d) {
        return 300 + d * 3
      })
      .attr("x2", function(d) {
        return 300 + d * 3
      })
      .style("stroke", "lightgray")
      .style("stroke-width", "1px");
    
    var circle = svg.append("circle")
      .attr("cx", 200)
      .attr("cy", 100)
      .attr("r", 98)
      .transition()
      .duration(10000)
      .ease(d3.easeLinear)
      .attr("transform", "translate(30,0)")
    <脚本src=”https://d3js.org/d3.v5.min.js“></script>
    <svg宽度=“500”高度=“200”></svg>

    对于在Chrome/Safari中运行此功能的用户,这就是Firefox中最后一个代码片段的样子。这就像每次改变圆都会移动半个像素。。。绝对没有变化那么顺利 cx公司 :

    var svg = d3.select("svg");
    
    var gridlines = svg.selectAll(null)
      .data(d3.range(10))
      .enter()
      .append("line")
      .attr("y1", 0)
      .attr("y2", 200)
      .attr("x1", function(d) {
        return 300 + d * 3
      })
      .attr("x2", function(d) {
        return 300 + d * 3
      })
      .style("stroke", "lightgray")
      .style("stroke-width", "1px");
    
    var circle = svg.append("circle")
      .attr("cx", 200)
      .attr("cy", 100)
      .attr("r", 98);
      
    var timer = d3.timer(function(t){
      if(t>10000) timer.stop();
      circle.attr("cx", 200 + (~~(60/(10000/t))/2));
    })
    <脚本src=”https://d3js.org/d3.v5.min.js“></script>
    <svg宽度=“500”高度=“200”></svg>

    由于这是一个仅在FF中可见的实现问题,因此可能值得报告一个bug。