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

标签 javascript firefox d3.js svg d3-force-directed

JSFiddle example

我注意到在更新 d3-force 中 svg 元素的位置时图中,使用(在圆圈的情况下)cxcy 属性更新元素的位置比使用 transform 属性要平滑得多。

在示例 JSFiddle 中,有两个并排的独立力模拟。左边的那个使用 transform 属性更新位置:

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

右边的那个使用圆的 cxcy 属性更新位置:

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

模拟看起来完全相同,直到它们即将变为静态,此时使用变换的模拟开始抖动很多。任何想法是什么造成的?能否修复它以便使用变换使动画保持流畅?

最佳答案

在我看来,您观察到的问题(只能在 FireFox 中重现,如 @altocumulus noted )与 FF 使用 float 进行 translate 的方式有关>转换属性。

如果我们将两个模拟都设置为使用整数,执行 ~~(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>

因此,在您的原始代码中,使用 cxcy 时,圆圈似乎正确移动,但使用 translate 时,它​​们从整数跳到整数 (或者可能是半像素,请参阅上一个演示)。如果这里的假设是正确的,那么您只是在模拟冷却时看到效果的原因是因为在那一刻运动较小。

演示

现在,如果我们摆脱模拟,我们可以看到这种奇怪的行为也发生在非常基本的转换中。为了检查这一点,我为一个大的黑色圆圈创建了一个过渡,使用线性缓动和很长的时间(以便于看到问题)。圆圈将向右移动 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>

但是,如果我们使用translate,您可以看到圆在每次移动时跳跃 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)")
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="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));
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="200"></svg>

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

关于javascript - 使用 svg 变换更新位置时,力模拟会出现抖动,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50278808/

相关文章:

javascript - 如何调试javascript?

javascript - Kendo Editor + show ("slide") 在 Firefox 中不可编辑

javascript - GraphStream:交互式 Web 应用程序

javascript - 使用单击和拖动事件测试 D3 应用程序

javascript - 使用 d3 提示的关闭问题

javascript - 黑洞请求 Cakephp

javascript - 如何使用 angularJs var 到 html 属性

javascript - 有没有办法用我自己的 CSS 覆盖网站的 CSS?

css - border-image-slice 在 Chrome 和 FireFox 中交换水平和垂直参数

Firefox 性能分析工具在加载状态下显示两个灰色圆圈,而不是带有值的饼图