无法使用d3 force和slvelte更新节点位置



我正在尝试使用D3 force和Svelte创建一个简单的网络。

网络位置取决于使用bind:clientWidthbind:clientHeight计算的容器尺寸。我的意思是,如果网络容器的宽度=200,高度=300,那么网络中心应该是x=100, y=150。

创建图形的代码如下:
export let width;
export let height;
let svg;
let simulation;
let zoomTransform = zoomIdentity;
let links = $network.links.map(d => cloneDeep(d));
let nodes = $network.nodes.map(d => cloneDeep(d));
simulation = forceSimulation(nodes)
.force(
"link",
forceLink(
links.map(l => {
return {
...l,
source: nodes.find(n => n.id === l.source),
target: nodes.find(n => n.id === l.source)
};
})
)
)
.force(
"collision",
forceCollide()
.strength(0.2)
.radius(120)
.iterations(1)
)
.force("charge", forceManyBody().strength(0))
.force("center", forceCenter(width / 2, height / 2))
.on("tick", simulationUpdate);
// .stop()
// .tick(100)
function simulationUpdate() {
simulation.tick();
nodes = nodes.map(n => cloneDeep(n));
links = links.map(l => cloneDeep(l));
}
$: {
simulation
.force("center")
.x(width / 2)
.y(height / 2);
}
</script>
<svg bind:this={svg} viewBox={`${0} ${0} ${width} ${height}`} {width} {height}>
{#if simulation}
<g>
{#each links as link}
<Link {link} {nodes} />
{/each}
</g>
{:else}
null
{/if}
{#if simulation}
<g>
{#each nodes as node}
<Node {node} x={node.x} y={node.y} />
{/each}
</g>
{:else}
null
{/if}
</svg>

很简单:widthheight是道具。它创建了一些本地存储,并用新数据更新它们。由于widthheight是动态的,我计算了forceCenter在反应块中的力。

然后,绘制节点,我使用Node组件与道具nodes,x,y。我知道我只能使用nodesx,y,但这是一个测试。问题是,即使widthheight发生变化,节点的位置也不会改变。

所以如果你改变窗口大小,图形不会重新计算,但它应该重新计算。为什么?

这里有一个完整的工作示例

谢谢!

其中一个问题是您替换了nodes/links引用。这需要模拟发生在你不再有任何引用的对象上,而你渲染一组不同的对象,在第一个tick之后将永远不会再改变。

一种方法是添加一个单独的对象,用于更新由Svelte生成的DOM。

let links = $network.links.map(d => cloneDeep(d));
let nodes = $network.nodes.map(d => cloneDeep(d));
// Initial render state
let render = {
links,
nodes,
}
// ...
function simulationUpdate() {
// (No need to call tick, a tick has already happened here)
render = {
nodes: nodes.map(d => cloneDeep(d)),
links: links.map(d => cloneDeep(d)),
};
}

调整each循环您还需要使链接循环键,或调整Link组件代码,使sourceNode/targetNode响应而不是const:

{#each render.links as link (link)}
...
{#each render.nodes as node}

(使用link本身作为键会导致所有元素的重新呈现,因为链接是克隆的,所以没有一个对象是相同的。)

另外,当中心改变时,您可能需要调用restart以确保它正确应用:

$: {
simulation
.force("center")
.x(width / 2)
.y(height / 2);
simulation.restart();
}

除了使用单独的对象进行渲染之外,您还可以使用{#key}特性使DOM重新渲染(对于大型图形,这可能会产生负面影响)。您只需要更改一些变量,并将其用作触发器:

let renderKey = false;
// ...
function simulationUpdate() {
renderKey = !renderKey;
}
{#if simulation}
{#key renderKey}
<g>
{#each links as link}
<Link {link} {nodes} />
{/each}
</g>
<g>
{#each nodes as node}
<Node {node} x={node.x} y={node.y} />
{/each}
</g>
{/key}
{/if}

相关内容

  • 没有找到相关文章

最新更新