Proximity Connect
This example shows how to automatically create edges when a node is dropped in close proximity to another one. While dragging, a dotted connection line is displayed to show which edge will be created if you drop the node.
import React, { useCallback } from 'react';
import {
ReactFlow,
addEdge,
useNodesState,
useEdgesState,
Background,
ReactFlowProvider,
useStoreApi,
useReactFlow,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { initialEdges, initialNodes } from './initialElements';
const MIN_DISTANCE = 150;
const Flow = () => {
const store = useStoreApi();
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const { getInternalNode } = useReactFlow();
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[setEdges],
);
const getClosestEdge = useCallback((node) => {
const { nodeLookup } = store.getState();
const internalNode = getInternalNode(node.id);
const closestNode = Array.from(nodeLookup.values()).reduce(
(res, n) => {
if (n.id !== internalNode.id) {
const dx =
n.internals.positionAbsolute.x -
internalNode.internals.positionAbsolute.x;
const dy =
n.internals.positionAbsolute.y -
internalNode.internals.positionAbsolute.y;
const d = Math.sqrt(dx * dx + dy * dy);
if (d < res.distance && d < MIN_DISTANCE) {
res.distance = d;
res.node = n;
}
}
return res;
},
{
distance: Number.MAX_VALUE,
node: null,
},
);
if (!closestNode.node) {
return null;
}
const closeNodeIsSource =
closestNode.node.internals.positionAbsolute.x <
internalNode.internals.positionAbsolute.x;
return {
id: closeNodeIsSource
? `${closestNode.node.id}-${node.id}`
: `${node.id}-${closestNode.node.id}`,
source: closeNodeIsSource ? closestNode.node.id : node.id,
target: closeNodeIsSource ? node.id : closestNode.node.id,
};
}, []);
const onNodeDrag = useCallback(
(_, node) => {
const closeEdge = getClosestEdge(node);
setEdges((es) => {
const nextEdges = es.filter((e) => e.className !== 'temp');
if (
closeEdge &&
!nextEdges.find(
(ne) =>
ne.source === closeEdge.source && ne.target === closeEdge.target,
)
) {
closeEdge.className = 'temp';
nextEdges.push(closeEdge);
}
return nextEdges;
});
},
[getClosestEdge, setEdges],
);
const onNodeDragStop = useCallback(
(_, node) => {
const closeEdge = getClosestEdge(node);
setEdges((es) => {
const nextEdges = es.filter((e) => e.className !== 'temp');
if (
closeEdge &&
!nextEdges.find(
(ne) =>
ne.source === closeEdge.source && ne.target === closeEdge.target,
)
) {
nextEdges.push(closeEdge);
}
return nextEdges;
});
},
[getClosestEdge],
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodeDrag={onNodeDrag}
onNodeDragStop={onNodeDragStop}
onConnect={onConnect}
style={{ backgroundColor: "#F7F9FB" }}
fitView
>
<Background />
</ReactFlow>
);
};
export default () => (
<ReactFlowProvider>
<Flow />
</ReactFlowProvider>
);