Skip to Content
ExamplesNodes

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> );
Last updated on