- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我相信我在 D3 v4.x (v4.5.0) 中发现了一个错误,当在一个力导向图表中一个组合 d3.forceCollide以防止节点重叠和d3.forceX/Y设置具有高 strength
值的节点的位置,我将在 D3 GitHub 存储库中创建一个问题。
但是,在创建问题之前,我想在 StackOverflow 上与 D3 用户核实一下:它可能不是一个错误,它可能是使用这种组合的预期行为。
在 S.O.下面的代码片段,我正在绘制 900 (30 x 30) 个圆圈。它们使用d3.forceX
和d3.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
中所配置。
然而,这个大圆圈,和任何其他节点一样,有它自己的 x
和 y
位置,由 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.forceCollide
和 d3.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 集成。连续施加的力将计算它们对节点速度的变化。这些影响由每个力计算并添加到节点的 vx
和 vy
速度值中。在为实际滴答计算完所有力后,节点的新位置为 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.forceX
和 d3.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,这是推荐范围的上限,几乎会立即迫使节点到达终点位置,从而减少其他力的较小影响。将这种主导效应与其他力量的弱势效应结合起来,就会产生第一个片段中所示的行为。由于强大的 forceX
和 forceY
,节点被迫在刚性网格中的位置,强调先前位置周围的孔。
另一个问题是您没有正确地重新加热模拟。在您的 dragstarted
和 dragended
处理函数中,您正在使用 simulation.alphaTarget
来加热系统,这不是正确的做法。要控制系统的熵,您应该始终使用 simulation.alpha
而不是 simulation.alphaTarget
。虽然后者由于其在公式中的使用方式而在某些情况下有点起作用,但它是一种 hack,其行为很可能不是您想要的。总结一下,使用 alpha
控制热量,使用 alphaTarget
、alphaMin
或 alphaDecay
调整曲线腐烂。以下片段是从您的问题中复制的,并已调整重新加热以使用 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/
自 D3v4 起,可以定义 .forceCenter 或 .forceX 和 .forceY,而不是 .size在 D3v3 中。 var force = d3.forceSimulation()
我有以下代码: var simulation = d3v5.forceSimulation() .force('link', d3v5.forceLink().id(function (d
我相信我在 D3 v4.x (v4.5.0) 中发现了一个错误,当在一个力导向图表中一个组合 d3.forceCollide以防止节点重叠和d3.forceX/Y设置具有高 strength 值的节点
我是一名优秀的程序员,十分优秀!