D3.js 重新启动模拟时节点跳转添加或删除节点
Posted
技术标签:
【中文标题】D3.js 重新启动模拟时节点跳转添加或删除节点【英文标题】:D3.js nodes jump when restarting simulation to add or remove nodes 【发布时间】:2021-05-26 00:24:52 【问题描述】:我正在使用带有强制布局的 d3.js v6 来表示网络图。 我正在添加和删除节点,但是当我重新启动模拟时,所有节点都会跳到左上角,然后又回到原来的位置。
我有以下代码 sn-p 可以准确显示我的意思,我在网上看到了其他运行良好但无法找到我做错的示例,非常感谢任何帮助。
var dataset =
nodes: [
id: 1
,
id: 2
],
links: [
id: 1,
source: 1,
target: 2
]
;
let switchBool = false;
let svg = d3.select('svg')
.attr('width', '100%')
.attr('height', '100%');
const width = svg.node()
.getBoundingClientRect().width;
const height = svg.node()
.getBoundingClientRect().height;
console.log(`$width, $height`);
svg = svg.append('g');
svg.append('g')
.attr('class', 'links');
svg.append('g')
.attr('class', 'nodes');
const simulation = d3.forceSimulation();
initSimulation();
let link = svg.select('.links')
.selectAll('line');
loadLinks();
let node = svg.select('.nodes')
.selectAll('.node');
loadNodes();
restartSimulation();
function initSimulation()
simulation
.force('link', d3.forceLink())
.force('charge', d3.forceManyBody())
.force('collide', d3.forceCollide())
.force('center', d3.forceCenter())
.force('forceX', d3.forceX())
.force('forceY', d3.forceY());
simulation.force('center')
.x(width * 0.5)
.y(height * 0.5);
simulation.force('link')
.id((d) => d.id)
.distance(100)
.iterations(1);
simulation.force('collide')
.radius(10);
simulation.force('charge')
.strength(-100);
function loadLinks()
link = svg.select('.links')
.selectAll('line')
.data(dataset.links, (d) => d.id)
.join(
enter => enter.append('line').attr('stroke', '#000000'),
);
function loadNodes()
node = svg.select('.nodes')
.selectAll('.node')
.data(dataset.nodes, (d) => d.id)
.join(
enter =>
const nodes = enter.append('g')
.attr('class', 'node')
nodes.append('circle').attr('r', 10);
return nodes;
,
);
function restartSimulation()
simulation.nodes(dataset.nodes);
simulation.force('link').links(dataset.links);
simulation.alpha(1).restart();
simulation.on('tick', ticked);
function ticked()
link
.attr('x1', (d) => d.source.x)
.attr('y1', (d) => d.source.y)
.attr('x2', (d) => d.target.x)
.attr('y2', (d) => d.target.y);
node.attr('transform', (d) => `translate($d.x,$d.y)`);
function updateData()
switchBool = !switchBool;
if (switchBool)
dataset.nodes.push(id: 3);
dataset.links.push(id: 2, source: 1, target: 3);
else
dataset.nodes.pop();
dataset.links.pop();
loadLinks();
loadNodes();
restartSimulation();
<script src="https://d3js.org/d3.v6.min.js"></script>
<div>
<button onclick="updateData()">Add/Remove</button>
<svg></svg>
</div>
【问题讨论】:
【参考方案1】:我实际上刚刚找到了问题的解决方案。
forceX
和 forceY
都有默认参数,这意味着有一个力将节点推向 (0,0),更改了这段代码我能够修复它:
.force('x', d3.forceX().x(width * 0.5))
.force('y', d3.forceY().y(height * 0.5));
【讨论】:
【参考方案2】:这是因为您使用了d3.forceCenter()
,它不会将节点强制到一个中心点:
中心力均匀地平移节点,使得均值 所有节点的位置(如果所有节点都相等,则为质心 weight) 在给定的位置⟨x,y⟩。 (docs)
因此,如果您的两个节点直接位于 d3.forceCenter 的中心点下方/上方,则质量是平衡的。引入一个新节点并且必须转换整个力以使质心成为中心。这个翻译就是你看到的跳跃。
移除 forceCenter 并使用 d3.forceX 和 d3.forceY 指定中心值,这会将节点推向指定的 x 和 y 值:
var dataset =
nodes: [
id: 1
,
id: 2
],
links: [
id: 1,
source: 1,
target: 2
]
;
let switchBool = false;
let svg = d3.select('svg')
.attr('width', '100%')
.attr('height', '100%');
const width = svg.node()
.getBoundingClientRect().width;
const height = svg.node()
.getBoundingClientRect().height;
console.log(`$width, $height`);
svg = svg.append('g');
svg.append('g')
.attr('class', 'links');
svg.append('g')
.attr('class', 'nodes');
const simulation = d3.forceSimulation();
initSimulation();
let link = svg.select('.links')
.selectAll('line');
loadLinks();
let node = svg.select('.nodes')
.selectAll('.node');
loadNodes();
restartSimulation();
function initSimulation()
simulation
.force('link', d3.forceLink())
.force('charge', d3.forceManyBody())
.force('collide', d3.forceCollide())
.force('forceX', d3.forceX().x(width/2))
.force('forceY', d3.forceY().y(height/2));
simulation.force('link')
.id((d) => d.id)
.distance(100)
.iterations(1);
simulation.force('collide')
.radius(10);
simulation.force('charge')
.strength(-100);
function loadLinks()
link = svg.select('.links')
.selectAll('line')
.data(dataset.links, (d) => d.id)
.join(
enter => enter.append('line').attr('stroke', '#000000'),
);
function loadNodes()
node = svg.select('.nodes')
.selectAll('.node')
.data(dataset.nodes, (d) => d.id)
.join(
enter =>
const nodes = enter.append('g')
.attr('class', 'node')
nodes.append('circle').attr('r', 10);
return nodes;
,
);
function restartSimulation()
simulation.nodes(dataset.nodes);
simulation.force('link').links(dataset.links);
simulation.alpha(1).restart();
simulation.on('tick', ticked);
function ticked()
link
.attr('x1', (d) => d.source.x)
.attr('y1', (d) => d.source.y)
.attr('x2', (d) => d.target.x)
.attr('y2', (d) => d.target.y);
node.attr('transform', (d) => `translate($d.x,$d.y)`);
function updateData()
switchBool = !switchBool;
if (switchBool)
dataset.nodes.push(id: 3);
dataset.links.push(id: 2, source: 1, target: 3);
else
dataset.nodes.pop();
dataset.links.pop();
loadLinks();
loadNodes();
restartSimulation();
<script src="https://d3js.org/d3.v6.min.js"></script>
<div>
<button onclick="updateData()">Add/Remove</button>
<svg></svg>
</div>
【讨论】:
以上是关于D3.js 重新启动模拟时节点跳转添加或删除节点的主要内容,如果未能解决你的问题,请参考以下文章