我正在尝试使用D3 force和Svelte创建一个简单的网络。
网络位置取决于使用bind:clientWidth
和bind: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>
很简单:width
和height
是道具。它创建了一些本地存储,并用新数据更新它们。由于width
和height
是动态的,我计算了forceCenter
在反应块中的力。
然后,绘制节点,我使用Node
组件与道具nodes
,x
,y
。我知道我只能使用nodes
或x,y
,但这是一个测试。问题是,即使width
和height
发生变化,节点的位置也不会改变。
所以如果你改变窗口大小,图形不会重新计算,但它应该重新计算。为什么?
这里有一个完整的工作示例
谢谢!
其中一个问题是您替换了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}