gpt4 book ai didi

javascript - d3.forceCollide() 和 d3.forceX/Y() 具有高 strength() 值之间的冲突

转载 作者:行者123 更新时间:2023-11-29 10:32:47 26 4
gpt4 key购买 nike

我相信我在 D3 v4.x (v4.5.0) 中发现了一个错误,当在一个力导向图表中一个组合 d3.forceCollide以防止节点重叠和d3.forceX/Y设置具有高 strength 值的节点的位置,我将在 D3 GitHub 存储库中创建一个问题。

但是,在创建问题之前,我想在 StackOverflow 上与 D3 用户核实一下:它可能不是一个错误,它可能是使用这种组合的预期行为。

代码

在 S.O.下面的代码片段,我正在绘制 900 (30 x 30) 个圆圈。它们使用d3.forceXd3.forceY定位,强度值较高(默认值为0.1):

.force("xPos", d3.forceX(d => scale(d.x)).strength(1))
.force("yPos", d3.forceY(d => scale(d.y)).strength(1))

为了防止圆圈重叠,我使用 d3.forceCollide,基于它们的半径:

.force("collide", d3.forceCollide(d => d.radius * 1.2).strength(1))

半径只有 2px,除了中心节点,我特意把它做得更大。这是您要四处拖动以重现问题的节点。

问题描述

点击“运行代码片段”并拖动中间的圆圈。它将排斥小圆圈,如 d3.forceCollide 中所配置。

然而,这个大圆圈,和任何其他节点一样,有它自己的 xy 位置,由 d3.forceX/Y 设置,这些力将碰撞力的中心移向圆圈的原始位置。你可以看到,它离中心越远,排斥力就越不准确:几乎就像有一个幽灵圈排斥小圆圈,幽灵总是在实际的 SVG 元素和它的位置集之间通过 d3.forceX/Y

var n = 30,
width = 300,
padding = 5,
nodes = [];

for (var y = 0; y < n; ++y) {
for (var x = 0; x < n; ++x) {
nodes.push({
x: x,
y: y
})
}
}

var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", width);

var scale = d3.scaleLinear()
.domain([0, 29])
.range([padding, width - padding]);

var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-1))
.force("xPos", d3.forceX(d => scale(d.x)).strength(1))
.force("yPos", d3.forceY(d => scale(d.y)).strength(1))
.force("collide", d3.forceCollide(d => d.radius * 1.2).strength(1));

var circles = svg.selectAll("foo")
.data(nodes)
.enter()
.append("circle")
.attr("fill", "darkslateblue")
.attr("r", d => {
d.x == 14 && d.y == 14 ? d.radius = 25 : d.radius = 2;
return d.radius
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));

simulation.nodes(nodes)
.on("tick", ticked);

function ticked() {
circles.attr("cx", d => d.x).attr("cy", d => d.y)
}

function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}

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

function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

在第二个片段中,使用默认的 strength(即 0.1),奇怪的行为消失了:

var n = 30,
width = 300,
padding = 5,
nodes = [];

for (var y = 0; y < n; ++y) {
for (var x = 0; x < n; ++x) {
nodes.push({
x: x,
y: y
})
}
}

var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", width);

var scale = d3.scaleLinear()
.domain([0, 29])
.range([padding, width - padding]);

var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-1))
.force("xPos", d3.forceX(d => scale(d.x)).strength(0.1))
.force("yPos", d3.forceY(d => scale(d.y)).strength(0.1))
.force("collide", d3.forceCollide(d => d.radius * 1.2).strength(1));

var circles = svg.selectAll("foo")
.data(nodes)
.enter()
.append("circle")
.attr("fill", "darkslateblue")
.attr("r", d => {
d.x == 14 && d.y == 14 ? d.radius = 25 : d.radius = 2;
return d.radius
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));

simulation.nodes(nodes)
.on("tick", ticked);

function ticked() {
circles.attr("cx", d => d.x).attr("cy", d => d.y)
}

function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}

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

function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

我的问题:

当将 d3.forceCollided3.forceX/Y 以如此高的强度 混合时,这是预期的行为吗? d3.forceCollide 不应该基于实际的 cx/cy (d.x/d.y) SVG 圆形元素的位置?据我了解,正如文档所述,

Higher values moves nodes more quickly to the target position

但是,d3.forceX/Y 并没有移动实际的 SVG 元素,只是移动了碰撞力的计算中心。

那么,这是错误还是预期的行为?

最佳答案

从 v4 开始,力布局的工作方式为 switched从位置 Verlet 集成到速度 Verlet 集成。连续施加的力将计算它们对节点速度的变化。这些影响由每个力计算并添加到节点的 vxvy 速度值中。在为实际滴答计算完所有力后,节点的新位置为 calculated通过将生成的(即所有力的综合)速度添加到当前位置。

forces.each(function(force) {
force(alpha);
});

for (i = 0; i < n; ++i) {
node = nodes[i];
if (node.fx == null) node.x += node.vx *= velocityDecay;
else node.x = node.fx, node.vx = 0;
if (node.fy == null) node.y += node.vy *= velocityDecay;
else node.y = node.fy, node.vy = 0;
}

这就是为什么碰撞似乎以偏移为中心的原因。这在第一个片段中更加明显,因为您选择了力及其各自的参数。不过,值得注意的是,在您的第二个示例中也可以看到这种效果,尽管不那么明显。

重要的是要注意,定位力 d3.forceXd3.forceY 有点强大并且 reckless (强调我的):

The strength determines how much to increment the node’s x-velocity: (x - node.x) × strength. For example, a value of 0.1 indicates that the node should move a tenth of the way from its current x-position to the target x-position with each application. Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints.

将强度设置为1,这是推荐范围的上限,几乎会立即迫使节点到达终点位置,从而减少其他力的较小影响。将这种主导效应与其他力量的弱势效应结合起来,就会产生第一个片段中所示的行为。由于强大的 forceXforceY,节点被迫在刚性网格中的位置,强调先前位置周围的孔。

另一个问题是您没有正确地重新加热模拟。在您的 dragstarteddragended 处理函数中,您正在使用 simulation.alphaTarget 来加热系统,这不是正确的做法。要控制系统的熵,您应该始终使用 simulation.alpha而不是 simulation.alphaTarget。虽然后者由于其在公式中的使用方式而在某些情况下有点起作用,但它是一种 hack,其行为很可能不是您想要的。总结一下,使用 alpha 控制热量,使用 alphaTargetalphaMinalphaDecay 调整曲线腐烂。以下片段是从您的问题中复制的,并已调整重新加热以使用 simulation.alpha

var n = 30,
width = 300,
padding = 5,
nodes = [];

for (var y = 0; y < n; ++y) {
for (var x = 0; x < n; ++x) {
nodes.push({
x: x,
y: y
})
}
}

var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", width);

var scale = d3.scaleLinear()
.domain([0, 29])
.range([padding, width - padding]);

var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-1))
.force("xPos", d3.forceX(d => scale(d.x)).strength(1))
.force("yPos", d3.forceY(d => scale(d.y)).strength(1))
.force("collide", d3.forceCollide(d => d.radius * 1.2).strength(1));

var circles = svg.selectAll("foo")
.data(nodes)
.enter()
.append("circle")
.attr("fill", "darkslateblue")
.attr("r", d => {
d.x == 14 && d.y == 14 ? d.radius = 25 : d.radius = 2;
return d.radius
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));

simulation.nodes(nodes)
.on("tick", ticked);

function ticked() {
circles.attr("cx", d => d.x).attr("cy", d => d.y)
}

function dragstarted(d) {
if (!d3.event.active) simulation.alpha(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}

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

function dragended(d) {
if (!d3.event.active) simulation.alpha(0);
d.fx = null;
d.fy = null;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

如您所见,当拖动并按住大圆圈时,偏移量将缓慢减小并最终消失,因为模拟已重新加热到足以使其保持运行状态。

这是一个错误吗?不,是这样,强制布局在 v4 中有效。一些极端的参数设置增强了这一点。

关于javascript - d3.forceCollide() 和 d3.forceX/Y() 具有高 strength() 值之间的冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42185092/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com