Skip to Content
ExamplesLayout

Node Collisions

import type { Node } from '@xyflow/vue'; export type CollisionAlgorithmOptions = { maxIterations: number; overlapThreshold: number; margin: number; }; export type CollisionAlgorithm = (nodes: Node[], options: CollisionAlgorithmOptions) => Node[]; type Box = { x: number; y: number; width: number; height: number; moved: boolean; node: Node; }; function getBoxesFromNodes(nodes: Node[], margin = 0): Box[] { const boxes: Box[] = Array.from({ length: nodes.length }); for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; boxes[i] = { x: node.position.x - margin, y: node.position.y - margin, width: (node.width ?? node.measured?.width ?? 0) + margin * 2, height: (node.height ?? node.measured?.height ?? 0) + margin * 2, node, moved: false, }; } return boxes; } export const resolveCollisions: CollisionAlgorithm = (nodes, { maxIterations = 50, overlapThreshold = 0.5, margin = 0 }) => { const boxes = getBoxesFromNodes(nodes, margin); for (let iter = 0; iter <= maxIterations; iter++) { let moved = false; for (let i = 0; i < boxes.length; i++) { for (let j = i + 1; j < boxes.length; j++) { const A = boxes[i]; const B = boxes[j]; // center positions const centerAX = A.x + A.width * 0.5; const centerAY = A.y + A.height * 0.5; const centerBX = B.x + B.width * 0.5; const centerBY = B.y + B.height * 0.5; // distance between centers const dx = centerAX - centerBX; const dy = centerAY - centerBY; // overlap along each axis const px = (A.width + B.width) * 0.5 - Math.abs(dx); const py = (A.height + B.height) * 0.5 - Math.abs(dy); // resolve if there's significant overlap if (px > overlapThreshold && py > overlapThreshold) { A.moved = B.moved = moved = true; // resolve along the smallest overlap axis if (px < py) { // move along the x-axis const sx = dx > 0 ? 1 : -1; const moveAmount = (px / 2) * sx; A.x += moveAmount; B.x -= moveAmount; } else { // move along the y-axis const sy = dy > 0 ? 1 : -1; const moveAmount = (py / 2) * sy; A.y += moveAmount; B.y -= moveAmount; } } } } // early exit if no overlaps were found if (!moved) { break; } } return boxes.map((box) => { if (box.moved) { return { ...box.node, position: { x: box.x + margin, y: box.y + margin, }, }; } return box.node; }); };
Last updated on