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

Posted

技术标签:

【中文标题】修复 D3 强制定向布局中的节点位置【英文标题】:Fix Node Position in D3 Force Directed Layout 【发布时间】:2012-05-10 16:09:43 【问题描述】:

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

我以为就这么简单:

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

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

【问题讨论】:

【参考方案1】:

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

在 d3v3 中,d.fixed 将修复位于 d.xd.y 的节点;但是,在 d3v4/5 中不再支持此方法。 d3 documentation 声明:

要将节点固定在给定位置,您可以指定两个额外的 属性:

fx - the node’s fixed x-position

fy - the node’s fixed y-position

在每个tick结束时,在施加任何力之后,一个节点 具有已定义的 node.fx 已将 node.x 重置为此值并设置了 node.vx 归零;同样,具有已定义 node.fy 的节点将 node.y 重置为 这个值和 node.vy 设置为零。解开一个节点 以前修复过,将 node.fx 和 node.fy 设置为 null,或者删除这些 属性。

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

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 对事件监听器的更改

在上面的sn-p中,拖拽事件使用的形式

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;

事件现在直接传递给监听器,传递给事件监听器的第二个参数是数据。 Here's Observable 上的典型示例。

【讨论】:

但是 d3 如何知道某些节点是否已经固定? if is null 不固定,但随后 d3 会覆盖 fx 和 fy 值... 我不知道我是否完全理解评论 - 如果 fx 或 fy 未定义或为空,则节点不会被修复,因为我没有为我的任何节点定义它开始,没有一个是固定的。 D3 不会覆盖 fx/fy 值,除非你告诉它,就像我在这里做的那样。 (不是真的相关,但我通过在dragged 中设置 fx/fy 值而不是设置 x/y 更新了 sn-p 以使其运行更顺畅(因此该点与鼠标保持一致并且不受其他力的影响)。 【参考方案2】:

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

有关更多详细信息,请参阅强制布局文档(v3 docs、current docs),还可以查看根节点在 this example 中的位置。

【讨论】:

以上是关于修复 D3 强制定向布局中的节点位置的主要内容,如果未能解决你的问题,请参考以下文章

d3 以可预测的顺序强制布局节点

如何使 D3 中的标签和节点强制布局可点击以导航到 URL?

d3js 在地图上强制布局

向 Hobbelt 的“Group/Bundle Nodes”D3 强制布局示例中的节点添加标签?

D3 强制布局 - 按名称而不是索引链接节点

D3 强制布局应在单击节点时添加节点和链接