UNPKG

@rxflow/manhattan

Version:

Manhattan routing algorithm for ReactFlow - generates orthogonal paths with obstacle avoidance

116 lines (101 loc) 3.56 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.pointsToPath = pointsToPath; exports.snapPathToGrid = snapPathToGrid; /** * Convert array of points to SVG path string */ function pointsToPath(points, precision, borderRadius = 0) { if (points.length === 0) { return ''; } const factor = Math.pow(10, precision); const roundCoord = value => Math.round(value * factor) / factor; // If no border radius or only 2 points, use simple line path if (borderRadius === 0 || points.length <= 2) { const firstPoint = points[0]; let path = `M ${roundCoord(firstPoint.x)} ${roundCoord(firstPoint.y)}`; for (let i = 1; i < points.length; i++) { const point = points[i]; path += ` L ${roundCoord(point.x)} ${roundCoord(point.y)}`; } return path; } // Start with M (moveTo) command for first point const firstPoint = points[0]; let path = `M ${roundCoord(firstPoint.x)} ${roundCoord(firstPoint.y)}`; // Process each segment with rounded corners for (let i = 1; i < points.length - 1; i++) { const prev = points[i - 1]; const current = points[i]; const next = points[i + 1]; // Calculate direction vectors const dx1 = current.x - prev.x; const dy1 = current.y - prev.y; const dx2 = next.x - current.x; const dy2 = next.y - current.y; // Calculate distances const dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1); const dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); // Use the smaller of borderRadius or half the segment length const radius = Math.min(borderRadius, dist1 / 2, dist2 / 2); // Normalize direction vectors const ndx1 = dx1 / dist1; const ndy1 = dy1 / dist1; const ndx2 = dx2 / dist2; const ndy2 = dy2 / dist2; // Calculate the point before the corner const beforeCorner = { x: current.x - ndx1 * radius, y: current.y - ndy1 * radius }; // Calculate the point after the corner const afterCorner = { x: current.x + ndx2 * radius, y: current.y + ndy2 * radius }; // Draw line to the point before corner path += ` L ${roundCoord(beforeCorner.x)} ${roundCoord(beforeCorner.y)}`; // Draw quadratic bezier curve for the rounded corner // The control point is the actual corner point path += ` Q ${roundCoord(current.x)} ${roundCoord(current.y)} ${roundCoord(afterCorner.x)} ${roundCoord(afterCorner.y)}`; } // Draw line to the last point const lastPoint = points[points.length - 1]; path += ` L ${roundCoord(lastPoint.x)} ${roundCoord(lastPoint.y)}`; return path; } /** * Snap path to grid by aligning consecutive points */ function snapPathToGrid(points, gridSize) { if (points.length <= 1) { return points; } const snappedPoints = [...points]; // Don't snap the first and last points (anchors) // Don't snap the second and second-to-last points (extension points) // Only snap intermediate segments for (let i = 2; i < snappedPoints.length - 3; i++) { const first = snappedPoints[i]; const second = snappedPoints[i + 1]; if (first.x === second.x) { // Vertical line - snap X coordinate const x = gridSize * Math.round(first.x / gridSize); if (first.x !== x) { first.x = x; second.x = x; } } else if (first.y === second.y) { // Horizontal line - snap Y coordinate const y = gridSize * Math.round(first.y / gridSize); if (first.y !== y) { first.y = y; second.y = y; } } } return snappedPoints; }