Симуляция силы нервничает при использовании svg-преобразований для обновления позиции - PullRequest
0 голосов
/ 10 мая 2018

Пример JSFiddle

Я заметил, что при обновлении позиций элементов svg на диаграмме d3-force обновляются позиции элементов с использованием (вслучай окружностей) атрибуты cx и cy гораздо более плавные, чем при использовании атрибута transform.

В примере JSFiddle есть две отдельные симуляции силы рядом друг с другом.Один слева обновляет позиции с использованием атрибута transform:

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 Ответ

0 голосов
/ 11 мая 2018

Мне кажется, что проблема, которую вы наблюдаете (воспроизводимая только в FireFox, как отмечено @ altocumulus ), связана с тем, как FF использует плавающие числа для translate *Атрибут 1004 *.

Мы можем увидеть это, если в обеих симуляциях будем использовать целые числа, делая ~~(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, но они прыгают из целого числацелое число при использовании translate (или, возможно, половина пикселя, см. последнюю демонстрацию).Если гипотеза здесь верна, причина, по которой вы просто видите эффект, когда симуляция охлаждается, заключается в том, что в этот момент движения меньше.

Демки

Теперь, если мы избавимся от симуляции, мы увидим, что это странное поведение также происходит с очень простым transform.Чтобы проверить это, я создал переход для большого черного круга, используя линейную легкость и очень долгое время (чтобы облегчить просмотр проблемы).Круг переместится на 30 пикселей вправо.Я также поместил линию сетки, чтобы сделать скачки более заметными.

(Внимание: приведенные ниже демонстрационные примеры воспроизводимы только в 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, вы можете видеть, как круг прыгает на 1 пиксель при каждом движении:

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, возможно, стоит сообщить об ошибке.

...