在多焦点 d3 力布局中重新定位节点
Posted
技术标签:
【中文标题】在多焦点 d3 力布局中重新定位节点【英文标题】:Reposition nodes in a multi-foci d3 force layout 【发布时间】:2017-03-17 18:34:15 【问题描述】:我在多焦点力布局中有三组节点。每个节点都已经用 html 呈现。
这是我的强制布局代码的样子:
var node = this.svg.selectAll('path')
.data(data);
// foci is a dictionary that assigns the x and y value based
// on what group a node belongs to.
var foci =
"Blue" :
"x" : xScale(0),
"y": height / 2
,
"Red":
"x" : xScale(1),
"y": height / 2
,
"Purple":
"x" : xScale(2),
"y": height / 2
,
;
// This helped me position the nodes to their assigned clusters.
var forceX = d3.forceX((d) => foci[d.group].x);
var forceY = d3.forceY((d) => foci[d.group].y);
var force = d3.forceSimulation(data)
.force('x', forceX)
.force('y', forceY)
.force("collide", d3.forceCollide(8))
.on('tick', function()
node
.attr('transform', (d) =>
return 'translate(' + (d.x - 100) + ',' + (-d.y + 25) + ')';
);
);
到目前为止,我能够完成的是根据下拉列表中的更改重新绘制布局,这会重新初始化 d3.forceSimulation()
并使集群重新回到页面上,如下面的 gif 所示。
这不是我想要的。我正在努力使重新排列尽可能无缝。
更新:通过不重新初始化d3.forceSimulation()
,我可以将新数据绑定到节点并更改它们的颜色。
【问题讨论】:
【参考方案1】:您可以简单地重新加热模拟,而不是重新初始化d3.forceSimulation()
,使用restart()
:
重新启动模拟的内部计时器并返回模拟。与simulation.alphaTarget 或simulation.alpha 结合使用,此方法可用于在交互过程中“重新加热”模拟,例如在拖动节点时,或在使用simulation.stop 暂时暂停后恢复模拟。
我使用您的部分代码创建了一个演示来向您展示。在这个演示中,按钮随机化了每个数据点的颜色。之后,我们重新加热模拟:
force.alpha(0.8).restart();
检查它,点击“随机化”:
var width = 500, height = 200;
var svg = d3.select("#svgdiv")
.append("svg")
.attr("width", width)
.attr("height", height);
var data = d3.range(100).map(function(d, i)
return
group: Math.random()*2 > 1 ? "blue" : "red",
id: i
);
var xScale = d3.scaleOrdinal()
.domain([0, 1])
.range([100, width-100]);
var foci =
"blue" :
"x" : xScale(0),
"y": height / 2
,
"red":
"x" : xScale(1),
"y": height / 2
;
var forceX = d3.forceX((d) => foci[d.group].x);
var forceY = d3.forceY((d) => foci[d.group].y);
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("fill", (d)=>d.group);
var force = d3.forceSimulation(data)
.velocityDecay(0.65)
.force('x', forceX)
.force('y', forceY)
.force("collide", d3.forceCollide(8));
force.nodes(data)
.on('tick', function()
node
.attr('transform', (d) =>
return 'translate(' + (d.x) + ',' + (d.y) + ')';
);
);
d3.select("#btn").on("click", function()
data.forEach(function(d)
d.group = Math.random()*2 > 1 ? "blue" : "red"
)
node.transition().duration(500).attr("fill", (d)=>d.group);
setTimeout(function()
force.nodes(data);
force.alpha(0.8).restart();
, 1500)
)
<script src="https://d3js.org/d3.v4.min.js"></script>
<button id="btn">Randomize</button>
<div id="svgdiv"><div>
PS:我把 reheat 放在了 setTimeout
中,所以你可以先看到圆圈改变颜色,然后移动到焦点位置。
【讨论】:
感谢您的回复。这绝对是朝着正确方向迈出的一步。我实施了你的建议,但它仍然不适合我。当我使用下拉列表更新力模拟时,我仍然会“俯冲”。我相信您的演示和我的演示之间的区别在于数据。您正在将绑定到一个圆圈的相同数据中的组随机化。就我而言,我得到了一个全新的对象数组。 嗯,sn-p 清楚地表明我的代码有效......无论如何,我希望其他人能解决你的问题。干杯。 我在写上一条评论时不小心按了 Enter 按钮,而我还没来得及说完就发布了。如果我听起来直率,我深表歉意! 现在我明白了。所以,你的新数据可以有不同的长度,对吗?在那种情况下,我相信您可以使用密钥来保持我们所谓的“对象恒定性”。 不,先生。我的数据长度也将是 100。所以,假设我的起始数据——最初将为圆圈着色的数据——是 50 个蓝色、49 个红色和 1 个紫色。这是在一个包含 50 个蓝色对象、49 个红色对象等的数组中提供的,我将它们绑定到我的 HTML 中的 100 个 svg 圆圈。现在,当我点击下拉菜单时,我得到了一个新的对象数组:20 个蓝色、40 个红色和 40 个紫色。这就是我卡住的地方。因为我正在将我的数据重新绑定到 HTML,所以发生了猛扑。我是否需要操纵起始数据以匹配我从下拉列表中获得的数据?就像你在处理原始数据一样?以上是关于在多焦点 d3 力布局中重新定位节点的主要内容,如果未能解决你的问题,请参考以下文章