Update getInternalNodesBounds and examples

This commit is contained in:
Alessandro
2025-10-14 11:33:10 +02:00
parent 4f77447ab5
commit a67f2992b1
5 changed files with 60 additions and 37 deletions
@@ -1,4 +1,4 @@
import { MouseEvent, CSSProperties, useCallback } from 'react'; import { MouseEvent, CSSProperties, useCallback, useState } from 'react';
import { import {
ReactFlow, ReactFlow,
@@ -14,19 +14,13 @@ import {
ReactFlowInstance, ReactFlowInstance,
useEdgesState, useEdgesState,
useNodesState, useNodesState,
Panel,
} from '@xyflow/react'; } from '@xyflow/react';
const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('flow loaded:', reactFlowInstance); const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('flow loaded:', reactFlowInstance);
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
const buttonStyle: CSSProperties = {
position: 'absolute',
left: 10,
top: 10,
zIndex: 4,
};
const CustomMiniMapNode = ({ x, y, width, height }: MiniMapNodeProps) => { const CustomMiniMapNode = ({ x, y, width, height }: MiniMapNodeProps) => {
return <circle cx={x} cy={y} r={Math.max(width, height) / 2} fill="#ffcc00" />; return <circle cx={x} cy={y} r={Math.max(width, height) / 2} fill="#ffcc00" />;
}; };
@@ -34,6 +28,7 @@ const CustomMiniMapNode = ({ x, y, width, height }: MiniMapNodeProps) => {
const CustomMiniMapNodeFlow = () => { const CustomMiniMapNodeFlow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]); const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]); const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
const [hideAllNodes, setHideAllNodes] = useState(false);
const onConnect = useCallback((params: Connection | Edge) => setEdges((els) => addEdge(params, els)), [setEdges]); const onConnect = useCallback((params: Connection | Edge) => setEdges((els) => addEdge(params, els)), [setEdges]);
const addRandomNode = () => { const addRandomNode = () => {
@@ -45,10 +40,17 @@ const CustomMiniMapNodeFlow = () => {
x: Math.random() * window.innerWidth, x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight, y: Math.random() * window.innerHeight,
}, },
hidden: hideAllNodes,
}; };
setNodes((nds) => nds.concat(newNode)); setNodes((nds) => nds.concat(newNode));
}; };
const toggleHideAllNodes = () => {
setHideAllNodes(!hideAllNodes);
setNodes((nds) => nds.map((n) => ({ ...n, hidden: hideAllNodes })));
setEdges((eds) => eds.map((e) => ({ ...e, hidden: hideAllNodes })));
};
return ( return (
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
@@ -65,9 +67,14 @@ const CustomMiniMapNodeFlow = () => {
<Background variant={BackgroundVariant.Lines} /> <Background variant={BackgroundVariant.Lines} />
<MiniMap nodeComponent={CustomMiniMapNode} /> <MiniMap nodeComponent={CustomMiniMapNode} />
<button type="button" onClick={addRandomNode} style={buttonStyle}> <Panel position="top-left">
add node <button type="button" onClick={addRandomNode}>
</button> add node
</button>
<button type="button" onClick={toggleHideAllNodes}>
{hideAllNodes ? 'show all nodes' : 'hide all nodes'}
</button>
</Panel>
</ReactFlow> </ReactFlow>
); );
}; };
@@ -17,6 +17,7 @@
'handle-connect', 'handle-connect',
'interaction', 'interaction',
'intersections', 'intersections',
'minimap-bug',
'node-toolbar', 'node-toolbar',
'node-resizer', 'node-resizer',
'overview', 'overview',
@@ -4,8 +4,9 @@
Background, Background,
Controls, Controls,
MiniMap, MiniMap,
Panel,
type Node, type Node,
type Edge, type Edge
} from '@xyflow/svelte'; } from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
@@ -13,6 +14,7 @@
let nodes = $state.raw<Node[]>([]); let nodes = $state.raw<Node[]>([]);
let edges = $state.raw<Edge[]>([]); let edges = $state.raw<Edge[]>([]);
let hideAllNodes = $state(false);
function addRandomNode() { function addRandomNode() {
const nodeId = (nodes.length + 1).toString(); const nodeId = (nodes.length + 1).toString();
@@ -21,28 +23,33 @@
data: { label: `Node: ${nodeId}` }, data: { label: `Node: ${nodeId}` },
position: { position: {
x: Math.random() * (typeof window !== 'undefined' ? window.innerWidth : 800), x: Math.random() * (typeof window !== 'undefined' ? window.innerWidth : 800),
y: Math.random() * (typeof window !== 'undefined' ? window.innerHeight : 600), y: Math.random() * (typeof window !== 'undefined' ? window.innerHeight : 600)
}, },
type: 'default' type: 'default',
hidden: hideAllNodes
}; };
nodes = [...nodes, newNode]; nodes = [...nodes, newNode];
} }
function toggleHideAllNodes() {
hideAllNodes = !hideAllNodes;
nodes = nodes.map((node) => ({
...node,
hidden: hideAllNodes
}));
}
</script> </script>
<SvelteFlow <SvelteFlow bind:nodes bind:edges onlyRenderVisibleElements={true}>
bind:nodes
bind:edges
onlyRenderVisibleElements={true}
>
<Controls /> <Controls />
<Background /> <Background />
<MiniMap nodeComponent={CustomMiniMapNode} /> <MiniMap nodeComponent={CustomMiniMapNode} />
<button <Panel position="top-left">
type="button" <button type="button" onclick={addRandomNode}> add node </button>
onclick={addRandomNode}
style="position: absolute; left: 10px; top: 10px; z-index: 4;" <button type="button" onclick={toggleHideAllNodes}>
> {hideAllNodes ? 'show all nodes' : 'hide all nodes'}
add node </button>
</button> </Panel>
</SvelteFlow> </SvelteFlow>
@@ -61,16 +61,19 @@
height: store.height / store.viewport.zoom height: store.height / store.viewport.zoom
}); });
const hasVisibleNodes = $derived(store.nodeLookup.size > 0 && store.nodes.some((n) => !n.hidden)); // let boundingRect = $derived(
// store.nodeLookup.size > 0 && store.nodes.some((n) => !n.hidden)
// ? getBoundsOfRects(
// getInternalNodesBounds(store.nodeLookup, { filter: (n) => !n.hidden }),
// viewBB
// )
// : viewBB
// );
let boundingRect = $derived( let boundingRect = $derived(
hasVisibleNodes getBoundsOfRects(getInternalNodesBounds(store.nodeLookup, { filter: (n) => !n.hidden }), viewBB)
? getBoundsOfRects(
getInternalNodesBounds(store.nodeLookup, { filter: (n) => !n.hidden }),
viewBB
)
: viewBB
); );
let scaledWidth = $derived(boundingRect.width / width); let scaledWidth = $derived(boundingRect.width / width);
let scaledHeight = $derived(boundingRect.height / height); let scaledHeight = $derived(boundingRect.height / height);
let viewScale = $derived(Math.max(scaledWidth, scaledHeight)); let viewScale = $derived(Math.max(scaledWidth, scaledHeight));
@@ -124,7 +127,7 @@
{#each store.nodes as userNode (userNode.id)} {#each store.nodes as userNode (userNode.id)}
{@const node = store.nodeLookup.get(userNode.id)} {@const node = store.nodeLookup.get(userNode.id)}
{#if node && nodeHasDimensions(node)} {#if node && nodeHasDimensions(node) && !node.hidden}
{@const nodeDimesions = getNodeDimensions(node)} {@const nodeDimesions = getNodeDimensions(node)}
<MinimapNode <MinimapNode
x={node.internals.positionAbsolute.x} x={node.internals.positionAbsolute.x}
+7 -2
View File
@@ -241,20 +241,25 @@ export const getInternalNodesBounds = <NodeType extends InternalNodeBase | NodeD
nodeLookup: Map<string, NodeType>, nodeLookup: Map<string, NodeType>,
params: GetInternalNodesBoundsParams<NodeType> = {} params: GetInternalNodesBoundsParams<NodeType> = {}
): Rect => { ): Rect => {
const emptyRect = { x: 0, y: 0, width: 0, height: 0 };
if (nodeLookup.size === 0) { if (nodeLookup.size === 0) {
return { x: 0, y: 0, width: 0, height: 0 }; return emptyRect;
} }
let box = { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity }; let box = { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity };
let hasVisibleNodes = false;
nodeLookup.forEach((node) => { nodeLookup.forEach((node) => {
if (params.filter === undefined || params.filter(node)) { if (params.filter === undefined || params.filter(node)) {
hasVisibleNodes = true;
const nodeBox = nodeToBox(node as InternalNodeBase); const nodeBox = nodeToBox(node as InternalNodeBase);
box = getBoundsOfBoxes(box, nodeBox); box = getBoundsOfBoxes(box, nodeBox);
} }
}); });
return boxToRect(box); return hasVisibleNodes ? boxToRect(box) : emptyRect;
}; };
export const getNodesInside = <NodeType extends NodeBase = NodeBase>( export const getNodesInside = <NodeType extends NodeBase = NodeBase>(