修复 D3 力导向布局中的节点位置

IT技术 javascript d3.js data-visualization force-layout d3-force-directed
2021-01-17 18:51:26

我希望我的力导向布局中的一些节点忽略所有力并根据节点的属性保持在固定位置,同时仍然能够被拖动并对其他节点施加排斥力并保持它们的链接线。

我认为它会像这样简单:

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

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

2个回答

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

有关更多详细信息,请参阅 force layout 文档(v3 文档当前文档),并查看本示例中根节点的位置

修复了 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,或删除这些属性。

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

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 值...
2021-03-17 18:51:26
我不知道我是否完全理解评论 - 如果 fx 或 fy 未定义或为空,则节点将不会被修复,因为我没有为我的任何节点定义它来启动,没有一个是固定的。D3 不会覆盖 fx/fy 值,除非你告诉它,就像我在这里做的那样。(并不是真正相关,但我通过设置 fx/fy 值dragged而不是设置 x/y更新了代码片段以使其运行更流畅(因此该点与鼠标保持一致并且不受其他力的影响)。
2021-04-01 18:51:26