@rxflow/manhattan
Version:
Manhattan routing algorithm for ReactFlow - generates orthogonal paths with obstacle avoidance
116 lines (101 loc) • 3.56 kB
JavaScript
;
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;
}