From 848434c419bfcfbdff66747e4c93b581e927fd14 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Thu, 19 Dec 2024 20:43:01 +0100 Subject: [PATCH] add only render visible nodes & edges --- .../src/routes/examples/stress/+page.svelte | 2 +- .../components/EdgeWrapper/EdgeWrapper.svelte | 2 - .../components/NodeWrapper/NodeWrapper.svelte | 2 + .../EdgeRenderer/EdgeRenderer.svelte | 72 +--------- .../NodeRenderer/NodeRenderer.svelte | 10 +- .../src/lib/container/SvelteFlow/types.ts | 4 +- .../src/lib/hooks/useInitialized.svelte.ts | 4 +- .../src/lib/store/initial-store.svelte.ts | 52 ++++++- .../svelte/src/lib/store/visible-edges.ts | 84 ------------ .../svelte/src/lib/store/visible-nodes.ts | 23 ---- .../svelte/src/lib/store/visibleElements.ts | 127 ++++++++++++++++++ 11 files changed, 191 insertions(+), 191 deletions(-) delete mode 100644 packages/svelte/src/lib/store/visible-edges.ts delete mode 100644 packages/svelte/src/lib/store/visible-nodes.ts create mode 100644 packages/svelte/src/lib/store/visibleElements.ts diff --git a/examples/svelte/src/routes/examples/stress/+page.svelte b/examples/svelte/src/routes/examples/stress/+page.svelte index b5d3ef1c..e8e434dc 100644 --- a/examples/svelte/src/routes/examples/stress/+page.svelte +++ b/examples/svelte/src/routes/examples/stress/+page.svelte @@ -50,7 +50,7 @@ let edges = $state.raw(edgeItems); - + diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte index 700cfcb2..4500496e 100644 --- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte +++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte @@ -79,8 +79,6 @@ callback({ event, edge }); } } - - $inspect(selected); diff --git a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte index 866ad640..fd9f01ca 100644 --- a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte +++ b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte @@ -25,6 +25,8 @@ onnodecontextmenu }: NodeWrapperProps & NodeEvents = $props(); + // $inspect(node); + let { data = {}, selected = false, diff --git a/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte b/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte index 170cd020..56545e48 100644 --- a/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte +++ b/packages/svelte/src/lib/container/EdgeRenderer/EdgeRenderer.svelte @@ -2,8 +2,7 @@ import { EdgeWrapper } from '$lib/components/EdgeWrapper'; import { CallOnMount } from '$lib/components/CallOnMount'; import { MarkerDefinition } from '$lib/container/EdgeRenderer/MarkerDefinition'; - import type { EdgeEvents, EdgeLayouted } from '$lib/types'; - import { getEdgePosition, getElevatedEdgeZIndex } from '@xyflow/system'; + import type { EdgeEvents } from '$lib/types'; import type { SvelteFlowStore } from '$lib/store/types'; let { @@ -13,68 +12,6 @@ onedgemouseenter, onedgemouseleave }: { store: SvelteFlowStore } & EdgeEvents = $props(); - - let previousLayoutedEdges = new Map(); - let layoutedEdges = $derived.by(() => { - // here for update reference - store.nodes; - - const layoutedEdges = new Map(); - - for (let edge of store.edges) { - const sourceNode = store.nodeLookup.get(edge.source); - const targetNode = store.nodeLookup.get(edge.target); - - if (!sourceNode || !targetNode) { - continue; - } - - // we reuse the previous edge object if - // the current and previous edge are the same - // and the source and target node are the same - // and references to internalNodes are the same - const previous = previousLayoutedEdges.get(edge.id); - if ( - previous && - edge === previous.edge && - sourceNode == previous.sourceNode && - targetNode == previous.targetNode - ) { - layoutedEdges.set(edge.id, previous); - continue; - } - - const edgePosition = getEdgePosition({ - id: edge.id, - sourceNode, - targetNode, - sourceHandle: edge.sourceHandle || null, - targetHandle: edge.targetHandle || null, - connectionMode: store.connectionMode, - onError: store.onerror - }); - - if (edgePosition) { - layoutedEdges.set(edge.id, { - ...edge, - zIndex: getElevatedEdgeZIndex({ - selected: edge.selected, - zIndex: edge.zIndex, - sourceNode, - targetNode, - elevateOnSelect: false - }), - ...edgePosition, - sourceNode, - targetNode, - edge - }); - } - } - - previousLayoutedEdges = layoutedEdges; - return layoutedEdges; - });
@@ -82,7 +19,7 @@ - {#each layoutedEdges.values() as edge (edge.id)} + {#each store.visible.edges.values() as edge (edge.id)} {/each} - {#if layoutedEdges.size > 0} + {#if !store.edgesInitialized && store.visible.edges.size > 0} { store.edgesInitialized = true; }} onDestroy={() => { - store.edgesInitialized = false; + // TODO: this needs to be handled differently + // store.edgesInitialized = false; }} /> {/if} diff --git a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte index 50d63c3c..a4d346ab 100644 --- a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte +++ b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte @@ -3,9 +3,8 @@ import { NodeWrapper } from '$lib/components/NodeWrapper'; - import type { Node, NodeEvents } from '$lib/types'; + import type { NodeEvents } from '$lib/types'; import type { SvelteFlowStore } from '$lib/store/types'; - import type { NodeLookup } from '@xyflow/system'; let { store, @@ -42,15 +41,10 @@ onDestroy(() => { resizeObserver?.disconnect(); }); - - // We pass an unused reference to nodes to trigger a re-render - function getNodes(nodeLookup: NodeLookup, nodes: Node[]) { - return nodeLookup.values(); - }
- {#each getNodes(store.nodeLookup, store.nodes) as node (node.id)} + {#each store.visible.nodes.values() as node (node.id)} { + const previousLayoutedEdges = new Map(); // We use a class here, because Svelte adds getters & setter for us. // Inline classes have some performance implications but we just call it once (max twice). class SvelteFlowStore { @@ -108,6 +112,50 @@ export const getInitialStore = (signals: StoreSignals) => { connectionLookup: ConnectionLookup = new Map(); edgeLookup: EdgeLookup = new Map(); + visible = $derived.by(() => { + // We need to access this._nodes to trigger on changes + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + this._nodes; + + // We either add all or only visible nodes to visibleNodes here. + let visibleNodes = new Map(); + let layoutedEdges = new Map(); + // TODO: is there a more elegant solution?? + if (this.onlyRenderVisibleElements) { + const transform: Transform = [this.viewport.x, this.viewport.y, this.viewport.zoom]; + getVisibleNodes(this.nodeLookup, transform, this.width, this.height).forEach((node) => { + visibleNodes.set(node.id, node); + }); + layoutedEdges = gatherLayoutedEdges( + this._edges, + this.nodeLookup, + previousLayoutedEdges, + this.connectionMode, + this.onerror, + true, + transform, + this.width, + this.height, + visibleNodes + ); + } else { + visibleNodes = this.nodeLookup; + layoutedEdges = gatherLayoutedEdges( + this._edges, + this.nodeLookup, + previousLayoutedEdges, + this.connectionMode, + this.onerror, + false + ); + } + + return { + nodes: visibleNodes, + edges: layoutedEdges + }; + }); + domNode: HTMLDivElement | null = $derived(signals.domNode ?? null); width: number = $derived(signals.width ?? signals.props.width ?? 0); height: number = $derived(signals.height ?? signals.props.height ?? 0); diff --git a/packages/svelte/src/lib/store/visible-edges.ts b/packages/svelte/src/lib/store/visible-edges.ts deleted file mode 100644 index d1b4a467..00000000 --- a/packages/svelte/src/lib/store/visible-edges.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { derived } from 'svelte/store'; -import { isEdgeVisible, getEdgePosition, getElevatedEdgeZIndex } from '@xyflow/system'; - -import type { EdgeLayouted } from '$lib/types'; -import type { SvelteFlowStoreState } from './types'; - -export function getVisibleEdges(store: SvelteFlowStoreState) { - const visibleEdges = derived( - [ - store.edges, - store.nodes, - store.nodeLookup, - store.onlyRenderVisibleElements, - store.viewport, - store.width, - store.height - ], - ([edges, , nodeLookup, onlyRenderVisibleElements, viewport, width, height]) => { - const visibleEdges = - onlyRenderVisibleElements && width && height - ? edges.filter((edge) => { - const sourceNode = nodeLookup.get(edge.source); - const targetNode = nodeLookup.get(edge.target); - - return ( - sourceNode && - targetNode && - isEdgeVisible({ - sourceNode, - targetNode, - width, - height, - transform: [viewport.x, viewport.y, viewport.zoom] - }) - ); - }) - : edges; - - return visibleEdges; - } - ); - - return derived( - [visibleEdges, store.nodes, store.nodeLookup, store.connectionMode, store.onerror], - ([visibleEdges, , nodeLookup, connectionMode, onerror]) => { - const layoutedEdges = visibleEdges.reduce((res, edge) => { - const sourceNode = nodeLookup.get(edge.source); - const targetNode = nodeLookup.get(edge.target); - - if (!sourceNode || !targetNode) { - return res; - } - - const edgePosition = getEdgePosition({ - id: edge.id, - sourceNode, - targetNode, - sourceHandle: edge.sourceHandle || null, - targetHandle: edge.targetHandle || null, - connectionMode, - onError: onerror - }); - - if (edgePosition) { - res.push({ - ...edge, - zIndex: getElevatedEdgeZIndex({ - selected: edge.selected, - zIndex: edge.zIndex, - sourceNode, - targetNode, - elevateOnSelect: false - }), - ...edgePosition - }); - } - - return res; - }, []); - - return layoutedEdges; - } - ); -} diff --git a/packages/svelte/src/lib/store/visible-nodes.ts b/packages/svelte/src/lib/store/visible-nodes.ts deleted file mode 100644 index e615439f..00000000 --- a/packages/svelte/src/lib/store/visible-nodes.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { derived } from 'svelte/store'; -import { getNodesInside, type Transform } from '@xyflow/system'; -import type { SvelteFlowStoreState } from './types'; - -export function getVisibleNodes(store: SvelteFlowStoreState) { - return derived( - [ - store.nodeLookup, - store.onlyRenderVisibleElements, - store.width, - store.height, - store.viewport, - store.nodes - ], - ([nodeLookup, onlyRenderVisibleElements, width, height, viewport]) => { - const transform: Transform = [viewport.x, viewport.y, viewport.zoom]; - - return onlyRenderVisibleElements - ? getNodesInside(nodeLookup, { x: 0, y: 0, width, height }, transform, true) - : Array.from(nodeLookup.values()); - } - ); -} diff --git a/packages/svelte/src/lib/store/visibleElements.ts b/packages/svelte/src/lib/store/visibleElements.ts new file mode 100644 index 00000000..a0b97432 --- /dev/null +++ b/packages/svelte/src/lib/store/visibleElements.ts @@ -0,0 +1,127 @@ +import type { Edge, EdgeLayouted, InternalNode } from '$lib/types'; +import { + ConnectionMode, + getEdgePosition, + getElevatedEdgeZIndex, + getNodesInside, + isEdgeVisible, + type NodeLookup, + type OnError, + type Transform +} from '@xyflow/system'; + +export function getVisibleNodes( + nodeLookup: NodeLookup, + transform: Transform, + width: number, + height: number +) { + return getNodesInside(nodeLookup, { x: 0, y: 0, width: width, height: height }, transform, true); +} + +// TODO: is this what we want? +export function gatherLayoutedEdges( + edges: Edge[], + nodeLookup: NodeLookup, + previousLayoutedEdges: Map, + connectionMode: ConnectionMode, + onerror: OnError, + onlyRenderVisibleElements: true, + transform: Transform, + width: number, + height: number, + visibleNodes: Map +): Map; +export function gatherLayoutedEdges( + edges: Edge[], + nodeLookup: NodeLookup, + previousLayoutedEdges: Map, + connectionMode: ConnectionMode, + onerror: OnError, + onlyRenderVisibleElements: false +): Map; +export function gatherLayoutedEdges( + edges: Edge[], + nodeLookup: NodeLookup, + previousLayoutedEdges: Map, + connectionMode: ConnectionMode, + onerror: OnError, + onlyRenderVisibleElements?: boolean, + transform?: Transform, + width?: number, + height?: number, + visibleNodes?: Map +): Map { + const layoutedEdges = new Map(); + for (const edge of edges) { + const sourceNode = nodeLookup.get(edge.source); + const targetNode = nodeLookup.get(edge.target); + + if (!sourceNode || !targetNode) { + continue; + } + + if (onlyRenderVisibleElements) { + if ( + isEdgeVisible({ + sourceNode, + targetNode, + width: width!, + height: height!, + transform: transform! + }) + ) { + visibleNodes!.set(sourceNode.id, sourceNode); + visibleNodes!.set(targetNode.id, targetNode); + } else { + continue; + } + } + + // we reuse the previous edge object if + // the current and previous edge are the same + // and the source and target node are the same + // and references to internalNodes are the same + const previous = previousLayoutedEdges.get(edge.id); + if ( + previous && + edge === previous.edge && + sourceNode == previous.sourceNode && + targetNode == previous.targetNode + ) { + layoutedEdges.set(edge.id, previous); + continue; + } + + const edgePosition = getEdgePosition({ + id: edge.id, + sourceNode, + targetNode, + sourceHandle: edge.sourceHandle || null, + targetHandle: edge.targetHandle || null, + connectionMode: connectionMode, + onError: onerror + }); + + if (edgePosition) { + layoutedEdges.set(edge.id, { + ...edge, + zIndex: getElevatedEdgeZIndex({ + selected: edge.selected, + zIndex: edge.zIndex, + sourceNode, + targetNode, + elevateOnSelect: false + }), + ...edgePosition, + sourceNode, + targetNode, + edge + }); + } + } + + previousLayoutedEdges = layoutedEdges; + + return layoutedEdges; +}