D3js симуляция силы с определенным целевым назначением - PullRequest
5 голосов
/ 29 мая 2019

Я пытаюсь визуализировать симуляцию силы d3js, но я хочу убедиться, что мои узлы не передают ложную информацию.

С помощью следующего кода, используемого для отображения узлов, но из-за динамического характера раскладов сил, он иногда выталкивает некоторые узлы из своего подходящего местоположения по x-координате.

 inOrder(){
  this.simulation
    .force("x", d3.forceX(d => this.xScale(d.value)))
    .force("y",  d3.forceY(this.height / 2))
    .alpha(1).restart();
},

Вот вопиющий пример этого: Числа должны быть в порядке слева направо. Out of order nodes

Я попытался использовать свойство fx на узле, чтобы зафиксировать позицию на месте:

inOrder(){
  this.releases.forEach(x => {
    x.fx = this.xScale(x.value)
  })

  this.simulation
    .force("x", d3.forceX(d => this.xScale(d.value)))
    .force("y",  d3.forceY(this.height / 2))
    .alpha(1).restart();
},

Nodes in order

Это работает, как и предполагалось, для сохранения положения x, но когда вызывается метод inOrder, узлы мгновенно переходят в свою конечную позицию x. Это разрушает текучую и динамическую природу симуляции силы.

Есть ли способ получить лучшее из обоих миров? Возможно, используя .on("end", () => {}) или .on("tick", () => {})? обработчики событий?

Майк Босток (https://stackoverflow.com/users/365814/mbostock) и Шэн Картер создали часть работы, которая послужила вдохновением для того, что я пытаюсь сделать здесь:

Щелчок между вкладками "Изменения" и "Итоги отдела" https://archive.nytimes.com/www.nytimes.com/interactive/2012/02/13/us/politics/2013-budget-proposal-graphic.html?hp

Щелкните между вкладками «Общая картина» и «Просмотр по отраслям» https://archive.nytimes.com/www.nytimes.com/interactive/2013/05/25/sunday-review/corporate-taxes.html

1 Ответ

2 голосов
/ 29 мая 2019

Возможно, я чего-то здесь упускаю, но работа с силой силы позиционирования x (и y) может помочь обеспечить правильное выполнение заказа.Сила forceX или forceY по умолчанию равна 0,1, сила реализована следующим образом:

значение 0,1 указывает, что узел должен переместиться на десятую часть пути от своего текущего положения x к целиХ-позиция с каждым приложением.При более высоких значениях узлы быстрее перемещаются в целевую позицию, часто за счет других сил или ограничений.Значение вне диапазона [0,1] не рекомендуется.( docs )

Таким образом, мы можем увеличить силу forceX, а для более свободного перемещения узлов по оси x мы можем уменьшить силу Y - позволяя узлам перепрыгивать друг на другас большей легкостью - уменьшение силы столкновения тоже может помочь.

Я не помечаю кружок ниже (вместо этого они последовательно затенены), но я запускаю проверку, чтобы убедиться, что они в порядке (регистрируется в консоли в конце симуляции), приведенный ниже фрагмент изменяет толькосилы x и y (не сила столкновения):

enter image description here

var height = 300;
var width = 500;

var data = d3.range(30).map(function(d,i) {
  return { size: Math.random()+1, index: i}
});

    
var svg = d3.select("body")
  .append("svg")
  .attr("width",width)
  .attr("height",height);
  
var x = d3.scaleLinear()
  .domain([1,30])
  .range([50,width-50]);
  
var color = d3.scaleLinear()
  .domain([0,29])
  .range(["#ccc","#000"])

var simulation = d3.forceSimulation()
    .force("x", d3.forceX(d => x(d.index)).strength(0.20))
    .force("y",  d3.forceY(height / 2).strength(0.05))
    .force("collide", d3.forceCollide().radius(d=> d.size*10))
    .alpha(1).restart();
    
var circles = svg.selectAll(null)
  .data(data)
  .enter()
  .append("circle")
  .attr("r", function(d) { return d.size * 10; })
  .attr("fill", function(d,i) { return color(i); })
    
simulation.nodes(data)
  .on("tick",tick)
  .on("end",verify);
  

function tick() {
  circles
  .attr("cx", function(d) { return d.x; })
  .attr("cy", function(d) { return d.y; })
}

function verify() {
  var n = 0;
  for(var i = 0; i < data.length - 1; i++) {
     if(data[i].x > data[i+1].x) n++;
  }
  console.log(n + " out of place");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Фрагмент размещает 30 кругов в области 500x300 без особых проблем: я тестировал несколько раз с 0 без места.Размещение 100 кружков здесь вызовет проблемы: кружки не смогут поменяться местами в такой тесной области: может потребоваться дальнейшая модификация сил, но предпочтительным может быть график большего размера (в отличие от крошечного фрагмента)тоже.


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

Еще одна возможность состояла бы в том, чтобы поддерживать высокий уровень альфа-канала до тех пор, пока все круги не будут должным образом упорядочены по оси x, а затем не начать охлаждаться, опять же, это должно происходить в функции тика.

...