在 React 应用程序中更新 d3 的力导向图,同时保留旧节点位置



我使用d3的forceSimulation来计算给定力(从没有节点或链接的情况下开始(的每个节点的(x,y(坐标:

const simulation = d3.forceSimulation()
.force('charge', d3.forceManyBody().strength(-1000))
.force('link', d3.forceLink().id(d => d?.id).distance(200))
.force('collide', d3.forceCollide().radius(150))
.stop()
.tick(300)

然后收听simulation的"tick"事件以更新图形(使用反应流渲染器setNodessetSetEdges,请参阅文档(:

simulation.on('tick', () => {
setEdges([...simulation.force('link').links()].map(setEdge));
setNodes([...simulation.nodes()].map(setNode));
})

React部分应重新启动simulation,同时将实际的nodesedges应用于simulation:

useEffect(() => {
simulation.nodes(nodes);
simulation.force('link').links(edges);
simulation.alpha(0.1).restart();
return () => simulation.stop();
}, [nodes, edges])

现在,nodesedges可能会更新,因为有可能扩展节点的关系。问题是,每次我们扩展节点关系时,我们都会得到一个新的节点和边数组,每个节点和边都包含旧值和新值:

伪示例:

old nodes : [{ id: 1 }]

new nodes: [{ id: 1 }, { id: 2}, { id: 3}]

"模拟"使用新值重新启动,并从头开始重新计算图形,因此新图形与旧图形完全不同。有没有一个选项可以让图保留旧节点的(x,y(坐标,并简单地将新节点"添加"到现有图中?

如果有人面临这种问题:

在更新节点的数组之前,请记录以前的节点:

const prev = usePrev(new Map(nodes.map((node) => [node.id, node])));

然后,在将新节点传递给模拟之前,制作一个新的副本,合并以前存在的元素,如

useEffect(() => {
nodes = nodes.map((node) => Object.assign(node, prev?.get(node.id)));
simulation.nodes(nodes);
(...) // rest of the code

这样,当模拟从新节点开始时,其中一些节点可能已经设置了(x,y(坐标。

最新更新