add only render visible nodes & edges

This commit is contained in:
peterkogo
2024-12-19 20:43:01 +01:00
parent 6349290722
commit 848434c419
11 changed files with 191 additions and 191 deletions
@@ -27,7 +27,8 @@ import {
type ConnectionLookup,
type ParentLookup,
pointToRendererPoint,
type ColorModeClass
type ColorModeClass,
type Transform
} from '@xyflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
@@ -51,11 +52,13 @@ import type {
OnBeforeDelete,
IsValidConnection,
Edge,
Node
Node,
EdgeLayouted
} from '$lib/types';
import type { StoreSignals } from './types';
import { MediaQuery } from 'svelte/reactivity';
import { gatherLayoutedEdges, getVisibleNodes } from './visibleElements';
export const initialNodeTypes = {
input: InputNode,
@@ -72,6 +75,7 @@ export const initialEdgeTypes = {
};
export const getInitialStore = (signals: StoreSignals) => {
const previousLayoutedEdges = new Map<string, EdgeLayouted>();
// 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<string, EdgeLayouted>();
// 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);
@@ -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<EdgeLayouted[]>((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;
}
);
}
@@ -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());
}
);
}
@@ -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<string, EdgeLayouted>,
connectionMode: ConnectionMode,
onerror: OnError,
onlyRenderVisibleElements: true,
transform: Transform,
width: number,
height: number,
visibleNodes: Map<string, InternalNode>
): Map<string, EdgeLayouted>;
export function gatherLayoutedEdges(
edges: Edge[],
nodeLookup: NodeLookup,
previousLayoutedEdges: Map<string, EdgeLayouted>,
connectionMode: ConnectionMode,
onerror: OnError,
onlyRenderVisibleElements: false
): Map<string, EdgeLayouted>;
export function gatherLayoutedEdges(
edges: Edge[],
nodeLookup: NodeLookup,
previousLayoutedEdges: Map<string, EdgeLayouted>,
connectionMode: ConnectionMode,
onerror: OnError,
onlyRenderVisibleElements?: boolean,
transform?: Transform,
width?: number,
height?: number,
visibleNodes?: Map<string, InternalNode>
): Map<string, EdgeLayouted> {
const layoutedEdges = new Map<string, EdgeLayouted>();
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;
}