在 D3力定向布局中确定节点位置

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

我以为就这么简单:

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

很明显,我对这件事的运作方式有一个基本的误解。如何在保持链接并允许其可拖动的情况下修复位置中的节点?

59156 次浏览

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

有关详细信息,请参阅兵力布局文档(V3文档现有文件) ,还可以了解根节点在 这个例子中的位置。

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属性,也可以动态添加和删除 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 changes to event listners

In the above snippet, the drag events use the form

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

事件现在直接传递给侦听器,传递给事件侦听器的第二个参数是数据。给你上的规范例子。