Исправить положение узла в D3 Force Directed Layout

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

Я думал, что это будет так просто:

force.on("tick", function() {
    vis.selectAll("g.node")
        .attr("transform", function(d) {
            return (d.someAttribute == true) ?
               "translate(" + d.xcoordFromAttribute + "," + d.ycoordFromAttribute +")" :
               "translate(" + d.x + "," + d.y + ")"
        });
  });

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

Очевидно, у меня есть базовое непонимание того, как это должно работать. Как я могу зафиксировать узлы в позиции, сохраняя при этом ссылки и позволяя их перетаскивать?


person Elijah    schedule 01.05.2012    source источник


Ответы (2)


Установите d.fixed на нужных узлах в значение true и инициализируйте d.x и d.y в нужное положение. Эти узлы по-прежнему будут частью симуляции, и вы сможете использовать обычный код отображения (например, установив атрибут преобразования); однако, поскольку они помечены как фиксированные, их можно перемещать только перетаскиванием, а не моделированием.

Дополнительные сведения см. в документации по принудительному макету (v3, текущие документы), а также посмотрите, как корневой узел расположен в этот пример.

person mbostock    schedule 01.05.2012

Фиксированные узлы в силовом макете для d3v4 и d4v5

В d3v3 d.fixed зафиксирует узлы в d.x и d.y; однако в d3v4/5 этот метод больше не поддерживается. В документации d3 говорится:

Чтобы зафиксировать узел в данной позиции, вы можете указать два дополнительных свойства:

fx - the node’s fixed x-position

fy - the node’s fixed y-position

В конце каждого тика, после приложения каких-либо сил, узел с определенным node.fx сбрасывает node.x на это значение, а node.vx устанавливает на ноль; аналогично, узел с определенным node.fy сбрасывает node.y на это значение, а node.vy устанавливает на ноль. Чтобы отменить исправление ранее исправленного узла, задайте для node.fx и node.fy значение null или удалите эти свойства.

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

var data ={ 
 "nodes": 
  [{"id": "A"},{"id": "B"},{"id": "C"},{"id":"D"}], 
 "links": 
  [{"source": "A", "target": "B"}, 
   {"source": "B", "target": "C"},
   {"source": "C", "target": "A"},
   {"source": "D", "target": "A"}]
}
var height = 250;
var width = 400;

var svg = d3.select("body").append("svg")
  .attr("width",width)
  .attr("height",height);
  
var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(50))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2));
    
var link = svg.append("g")
  .selectAll("line")
  .data(data.links)
  .enter().append("line")
  .attr("stroke","black");

var node = svg.append("g")
 .selectAll("circle")
 .data(data.nodes)
 .enter().append("circle")
 .attr("r", 5)
 .call(d3.drag()
   .on("drag", dragged)
   .on("end", dragended));
 
simulation
 .nodes(data.nodes)
 .on("tick", ticked)
 .alphaDecay(0);

simulation.force("link")
 .links(data.links);
      
function ticked() {
 link
   .attr("x1", function(d) { return d.source.x; })
   .attr("y1", function(d) { return d.source.y; })
   .attr("x2", function(d) { return d.target.x; })
   .attr("y2", function(d) { return d.target.y; });
 node
   .attr("cx", function(d) { return d.x; })
   .attr("cy", function(d) { return d.y; });
}    
    
function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>

d3v6 изменяет список событий

В приведенном выше фрагменте события перетаскивания используют форму

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

Где d — это исходная точка перетаскиваемого узла. В d3v6 форма теперь такая:

function dragged(event) {
  event.subject.fx = event.x;
  event.subject.fy = event.y;
}

or:

function dragged(event,d) {
  d.fx = event.x;
  d.fy = event.y;
}

Теперь событие передается непосредственно обработчику событий, второй параметр, передаваемый обработчику событий, — это данные. Вот канонический пример на Observable.

person Andrew Reid    schedule 20.06.2017
comment
Но как d3 узнать, зафиксированы ли уже некоторые узлы или нет? Если значение null не исправлено, но тогда d3 перезаписывает значения fx и fy... - person Aral Roca; 15.06.2018
comment
Я не знаю, правильно ли я понимаю комментарий - если fx или fy не определены или равны нулю, узел не будет исправлен, поскольку я не определяю его для запуска любого из моих узлов, ни один из них не фиксирован. D3 не перезапишет значения fx/fy, если вы не скажете, как это делаю я здесь. (На самом деле это не связано, но я обновил фрагмент, чтобы он работал более плавно, установив значения fx/fy в dragged, а не устанавливая x/y (таким образом, точка остается с мышью и невосприимчива к другим силам). - person Andrew Reid; 16.06.2018