在D3强制定向布局中修复节点位置


67

我希望力导向布局中的某些节点忽略所有力,并基于该节点的属性停留在固定位置,同时仍能够拖动其他节点并对其施加排斥力并保持其链接线。

我以为就这么简单:

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属性,但是如果该节点受力影响,链接将继续浮动到该节点所在的位置。

显然,我对应该如何工作有基本的误解。如何在固定链接的同时仍允许它们可拖动的同时将节点固定在一个位置?

Answers:


79

d.fixed在所需的节点上将其设置为true,然后初始化d.x并设置d.y到所需的位置。这些节点将仍然是模拟的一部分,您可以使用常规显示代码(例如,设置转换属性);但是,由于将它们标记为固定,因此只能通过拖动而不是通过模拟来移动它们。

有关更多详细信息,请参阅力布局文档(v3文档当前文档),并在此示例中还了解根节点的位置。


35

d3v4和d4v5的强制布局中的固定节点

在d3v3中,d.fixed将节点固定在d.xd.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,或删除这些属性。

您可以在数据源中为力节点设置fxfy属性,也可以动态地添加和移除fxandfy值。下面的代码段在拖动事件结束时设置了这些属性,只需拖动节点以固定其位置即可:

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;
}

要么:

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

现在将事件直接传递给侦听器,传递给事件侦听器的第二个参数是数据。这是关于Observable的规范示例。


但是d3如何知道某些节点是否已经固定?如果不固定为null,则d3会覆盖fx和fy值...
Aral Roca

我不知道我是否完全理解注释-如果未定义fx或fy或null,则该节点将不会被修复,因为我没有为任何要启动的节点定义它,因此没有一个被修复。除非您告诉我,D3不会覆盖fx / fy值,就像我在这里所做的那样。(并没有真正的关系,但是我通过设置fx / fy值dragged而不是设置x / y来更新代码段,使其运行起来更加平滑(因此,关键在于鼠标,不受其他力的影响。
。– Andrew Reid
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.