D3 симуляция силы - приблизить узлы к точке под тем же углом

1

Этот вопрос основан на кругах Move d3 вдали от схемы центра окружности, где, когда изменяется размер узла N1, и я запускал моделирование на узлах, узлы вокруг N1 будут перемещаться под одним углом, но вопрос, который у меня есть здесь заключается в том, чтобы вернуть узлы ближе к N1, когда его размер будет изменен обратно на оригинал. Как я могу это достичь?

Я использовал ниже, но узлы не приближаются

simulation.nodes(nodes);
simulation.restart();
for (var i = 0; i < 300; ++i) simulation.tick();

Кроме того, если я попытаюсь с силовым симулятором, то положение других узлов полностью изменится, см. Видео здесь http://recordit.co/797i1E8ocT

d3.forceSimulation(nodes)
    .force('x', d3.forceX(plot.x))
    .force('y', d3.forceY(plot.y))

Заранее спасибо.

  • 2
    Я посмотрел на ваше jsfiddle и узлы вернулись, когда круг снова стал маленьким. Есть ли какая-то проблема, которую я не получил?
  • 0
    @AndreiKorigodski проблема у меня заключается в том, что когда узлы возвращаются назад, все позиции перерисовываются и не возвращаются под тем же углом, что и при расширении
Показать ещё 2 комментария
Теги:
d3.js
force-layout

1 ответ

3
Лучший ответ

Узлы не приближаются, потому что ваша "скорость" моделирования, которая называется alpha значением, исчерпана (разлагается). Просто замените

simulation.restart();

которые вам не нужны

simulation.alpha(1);

и узлы будут вести себя правильно.

Однако, как вы отметили, после нескольких расширений и коллапсов узлы могут значительно перемещаться от своих исходных местоположений. Эту проблему можно решить несколькими способами. Вы можете использовать какой-то детерминированный алгоритм для вычисления местоположений узлов, таких как дерево, но может быть трудно добиться плавных переходов, когда узлы расширяются и сжимаются. Другим способом является "привязать" узлы к их начальным местоположениям, используя дополнительные силы, которые притягивают каждый узел к его первой расчетной позиции.

Для его реализации вы можете сохранить начальные позиции после инициализации симуляции и ее первого запуска:

for (let d of data.children) {
  d.initialX = d.x;
  d.initialY = d.y;
}

А затем замените силы x и y силами, которые притягивают каждый узел к его исходному положению:

simulation
  .force("x", d3.forceX(d => d.initialX).strength(0.2))
  .force("y", d3.forceY(d => d.initialY).strength(0.2));

Сила определяет баланс между силой столкновения и пиннинга. Чем больше сила, тем более агрессивно узлы будут пытаться занять свои начальные позиции.

Также может быть желательно использовать точечное притяжение вместо суммы x и y - взгляните на пакет d3-force-attractive.

Следующий фрагмент иллюстрирует описанный подход.

var w = 650,
  h = 650;
var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

var color = d3.scaleOrdinal(d3.schemeCategory10)

var data = {
  name: "root",
  children: [{
    label: 'RED1',
    size: 20,
    color: 'red'
  }, {
    label: 'RAD2',
    size: 20,
    color: '#c99700'
  }, {
    label: 'BIL3',
    size: 20,
    color: 'blue'
  }, {
    label: 'EEN4',
    size: 10,
    color: '#007377'
  }, {
    label: 'INO5',
    size: 40,
    color: '#b4975a'
  }, {
    label: 'RAD6',
    size: 40,
    color: '#c99700'
  }, {
    label: 'BIL7',
    size: 30,
    color: '#008ce6'
  }, {
    label: 'INO8',
    size: 30,
    color: '#b4975a'
  }, {
    label: 'INO9',
    size: 40,
    color: '#b4975a'
  }, {
    label: 'RAD10',
    size: 40,
    color: '#c99700'
  }, {
    label: 'BIL11',
    size: 30,
    color: '#008ce6'
  }, {
    label: 'INO12',
    size: 30,
    color: '#b4975a'
  }]
};

var render = function() {

  var simulation = d3.forceSimulation(data.children)
    .force("x", d3.forceX(w / 2))
    .force("y", d3.forceY(h / 2))
    .force("collide", d3.forceCollide(function(d) {
      return d.size + 20
    }))
    .stop();

  for (var i = 0; i < 100; ++i) simulation.tick();

  for (let d of data.children) {
    d.initialX = d.x;
    d.initialY = d.y;
  }

  simulation
    .force("x", d3.forceX(d => d.initialX).strength(0.2))
    .force("y", d3.forceY(d => d.initialY).strength(0.2));

  let nodeLevel1 = svg.selectAll('circle')
    .data(data.children, (d) => {
      // Attaching key for uniqueness
      return d.label;
    });

  nodeLevel1.exit().remove();
  let nodeLevel1Enter = nodeLevel1
    .enter()
    .append("circle")
    .attr("cx", function(d) {
      return d.x
    })
    .attr("cy", function(d) {
      return d.y
    })
    .attr("r", function(d) {
      return d.size
    })
    .style("fill", function(d) {
      return d.color;
    })

  nodeLevel1Enter = nodeLevel1Enter
    .merge(nodeLevel1)

  nodeLevel1Enter
    .transition()
    .duration(1600)
    .attr("cx", function(d) {
      return d.x
    })
    .attr("cy", function(d) {
      return d.y
    })
    .attr("r", function(d) {
      return d.size
    })
    .style("fill", function(d) {
      return d.color;
    })

  d3.select('#updatesize').on('click', function() {
    add();
  })
  d3.select('#updatebluesize').on('click', function() {
    addblue();
  })
  d3.select('#resetsize').on('click', function() {
    reset();
  })
  d3.select('#resetall').on('click', function() {
    resetall();
  })

  var add = function() {
    data.children[0].size = 140;
    move();
  }
  var addblue = function() {
    data.children[2].size = 100;
    move();
  }
  var reset = function() {
    data.children[0].size = 20;
    move();
  }
  var resetall = function() {
    data.children[0].size = 20;
    data.children[2].size = 20;
    move();
  }

  function move() {
    simulation.nodes(data.children);
    simulation.alpha(1);
    for (var i = 0; i < 300; ++i) simulation.tick();
    nodeLevel1Enter
      .transition()
      .duration(1600)
      .attr("cx", function(d) {
        return d.x
      })
      .attr("cy", function(d) {
        return d.y
      })
      .attr("r", function(d) {
        return d.size
      });
  }
}

render();
<script src="https://d3js.org/d3.v4.min.js"></script>

<a href='javascript:;' id='updatesize'>Update red resource size</a> |
<a href='javascript:;' id='updatebluesize'>Update blue resource size</a> |
<a href='javascript:;' id='resetsize'>Reset red resource size</a> |
<a href='javascript:;' id='resetall'>Reset all</a>

Ещё вопросы

Сообщество Overcoder
Наверх
Меню