我试图动态更新d3js力图上的节点,但碰撞在某些时候停止工作。这个问题似乎是由一个节点引起的,这个节点被准确地放在了svg画布的中心,但我不明白为什么。碰撞应该可以解决这个问题吧?有什么想法吗?
我附上一个最小的重现
var width = 400,
height = 200;
var svg = d3.select("svg");
svg.attr("viewBox", [-width / 2, -height / 2, width, height]);
var nodes = [{
radius: 20
}];
var simulation = d3
.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-50))
.force(
"collision",
d3.forceCollide().radius(function(d) {
return d.radius;
})
)
.on("tick", ticked);
function ticked() {
var u = d3.select("svg").selectAll("circle").data(nodes);
u.enter()
.append("circle")
.attr("r", function(d) {
return d.radius;
})
.merge(u)
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
u.exit().remove();
}
function update() {
nodes.push({
radius: Math.random() * (20 - 5) + 5
});
node = svg.selectAll("circle").data(nodes);
node
.enter()
.append("circle")
.attr("r", (d) => d.radius);
simulation.nodes(nodes);
}
setInterval(update, 100);
svg {
background: blue
}
circle {
fill: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="200" />
你的模拟冷却下来,然后停止,这时tick函数不再被调用。这是默认的行为。然而,你可以通过修改 alpha 衰减(simulation.alphaDecay()
).
alpha衰减率决定了当前alpha向所需目标alpha内插的速度;由于默认的目标alpha为零,所以默认情况下,它控制了仿真冷却的速度。较高的衰减率会使模拟更快地稳定下来,但有可能卡在局部最小值中;较低的值会使模拟运行时间更长,但通常会在更好的布局上收敛。如果要让模拟永远以当前的 alpha 值运行,请将衰减率设置为零... (docs)
α、α衰减和α最小值的默认设置允许模拟运行大约300次迭代,在这段时间内,模拟会冷却并最终停止。
如果我们将阿尔法衰减设置为零,模拟将永远持续下去。然而,你可能要拨下来的力量,因为模拟仍然是热的,或删除节点,不再可见(因为模拟仍然跟踪他们)。我在下面的演示中既没有这样做。
var width = 400,
height = 200;
var svg = d3.select("svg");
svg.attr("viewBox", [-width / 2, -height / 2, width, height]);
var nodes = [{
radius: 20
}];
var simulation = d3
.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-50))
.alphaDecay(0)
.force(
"collision",
d3.forceCollide().radius(function(d) {
return d.radius;
})
)
.on("tick", ticked);
function ticked() {
var u = d3.select("svg").selectAll("circle").data(nodes);
u.enter()
.append("circle")
.attr("r", function(d) {
return d.radius;
})
.merge(u)
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
u.exit().remove();
}
function update() {
nodes.push({
radius: Math.random() * (20 - 5) + 5
});
node = svg.selectAll("circle").data(nodes);
node
.enter()
.append("circle")
.attr("r", (d) => d.radius);
simulation.nodes(nodes);
}
setInterval(update, 100);
svg {
background: blue
}
circle {
fill: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="200" />
你也可以直接用Alpha值来操作。simulation.alpha()
这是另一种方法来重新加热模拟,如果你想这样做,当一个新的节点被输入,例如。
我忘了在更新节点数组后重新启动模拟。
simulation.nodes(nodes).restart();