@rxflow/manhattan 0.0.1-alpha.0 → 0.0.1-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/cjs/geometry/collision.d.ts +15 -0
  2. package/cjs/geometry/collision.d.ts.map +1 -0
  3. package/cjs/geometry/collision.js +81 -0
  4. package/cjs/geometry/index.d.ts +1 -0
  5. package/cjs/geometry/index.d.ts.map +1 -1
  6. package/cjs/geometry/index.js +20 -1
  7. package/cjs/getManHattanPath.d.ts +7 -14
  8. package/cjs/getManHattanPath.d.ts.map +1 -1
  9. package/cjs/getManHattanPath.js +54 -54
  10. package/cjs/options/defaults.js +1 -1
  11. package/cjs/svg/index.d.ts +1 -0
  12. package/cjs/svg/index.d.ts.map +1 -1
  13. package/cjs/svg/index.js +14 -1
  14. package/cjs/svg/pathParser.d.ts +11 -0
  15. package/cjs/svg/pathParser.d.ts.map +1 -0
  16. package/cjs/svg/pathParser.js +76 -0
  17. package/cjs/utils/grid.d.ts.map +1 -1
  18. package/cjs/utils/grid.js +3 -17
  19. package/cjs/utils/index.d.ts +1 -0
  20. package/cjs/utils/index.d.ts.map +1 -1
  21. package/cjs/utils/index.js +11 -0
  22. package/cjs/utils/pathValidation.d.ts +11 -0
  23. package/cjs/utils/pathValidation.d.ts.map +1 -0
  24. package/cjs/utils/pathValidation.js +130 -0
  25. package/esm/geometry/collision.d.ts +15 -0
  26. package/esm/geometry/collision.d.ts.map +1 -0
  27. package/esm/geometry/collision.js +73 -0
  28. package/esm/geometry/index.d.ts +1 -0
  29. package/esm/geometry/index.d.ts.map +1 -1
  30. package/esm/geometry/index.js +2 -1
  31. package/esm/getManHattanPath.d.ts +7 -14
  32. package/esm/getManHattanPath.d.ts.map +1 -1
  33. package/esm/getManHattanPath.js +70 -64
  34. package/esm/options/defaults.js +1 -1
  35. package/esm/svg/index.d.ts +1 -0
  36. package/esm/svg/index.d.ts.map +1 -1
  37. package/esm/svg/index.js +2 -1
  38. package/esm/svg/pathParser.d.ts +11 -0
  39. package/esm/svg/pathParser.d.ts.map +1 -0
  40. package/esm/svg/pathParser.js +82 -0
  41. package/esm/utils/grid.d.ts.map +1 -1
  42. package/esm/utils/grid.js +3 -17
  43. package/esm/utils/index.d.ts +1 -0
  44. package/esm/utils/index.d.ts.map +1 -1
  45. package/esm/utils/index.js +2 -1
  46. package/esm/utils/pathValidation.d.ts +11 -0
  47. package/esm/utils/pathValidation.d.ts.map +1 -0
  48. package/esm/utils/pathValidation.js +162 -0
  49. package/package.json +1 -1
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.pathIntersectsObstacles = pathIntersectsObstacles;
7
+ var _geometry = require("../geometry");
8
+ var _node = require("./node");
9
+ /**
10
+ * Get node bounding box
11
+ */
12
+ function getNodeBBox(nodeId, nodeLookup) {
13
+ const node = nodeLookup.get(nodeId);
14
+ if (!node) return null;
15
+ const dimensions = (0, _node.getNodeDimensions)(node);
16
+ const position = (0, _node.getNodePosition)(node);
17
+ return new _geometry.Rectangle(position.x, position.y, dimensions.width, dimensions.height);
18
+ }
19
+
20
+ /**
21
+ * Find unique intersection points between a line segment and a rectangle
22
+ * Returns array of intersection points (deduplicated)
23
+ */
24
+ function findSegmentRectIntersections(p1, p2, rect) {
25
+ const intersections = [];
26
+ const tolerance = 0.01;
27
+
28
+ // Define rectangle edges
29
+ const edges = [{
30
+ start: new _geometry.Point(rect.x, rect.y),
31
+ end: new _geometry.Point(rect.x + rect.width, rect.y)
32
+ },
33
+ // Top
34
+ {
35
+ start: new _geometry.Point(rect.x + rect.width, rect.y),
36
+ end: new _geometry.Point(rect.x + rect.width, rect.y + rect.height)
37
+ },
38
+ // Right
39
+ {
40
+ start: new _geometry.Point(rect.x + rect.width, rect.y + rect.height),
41
+ end: new _geometry.Point(rect.x, rect.y + rect.height)
42
+ },
43
+ // Bottom
44
+ {
45
+ start: new _geometry.Point(rect.x, rect.y + rect.height),
46
+ end: new _geometry.Point(rect.x, rect.y)
47
+ } // Left
48
+ ];
49
+
50
+ // Find intersection point with each edge
51
+ for (const edge of edges) {
52
+ const intersection = getLineSegmentIntersection(p1, p2, edge.start, edge.end);
53
+ if (intersection) {
54
+ // Check if this point is already in the list (avoid duplicates at corners)
55
+ const isDuplicate = intersections.some(existing => Math.abs(existing.x - intersection.x) < tolerance && Math.abs(existing.y - intersection.y) < tolerance);
56
+ if (!isDuplicate) {
57
+ intersections.push(intersection);
58
+ }
59
+ }
60
+ }
61
+ return intersections;
62
+ }
63
+
64
+ /**
65
+ * Get intersection point between two line segments (if exists)
66
+ */
67
+ function getLineSegmentIntersection(p1, p2, p3, p4) {
68
+ const d1 = direction(p3, p4, p1);
69
+ const d2 = direction(p3, p4, p2);
70
+ const d3 = direction(p1, p2, p3);
71
+ const d4 = direction(p1, p2, p4);
72
+ if ((d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0)) {
73
+ // Lines intersect, calculate intersection point
74
+ const t = ((p3.x - p1.x) * (p3.y - p4.y) - (p3.y - p1.y) * (p3.x - p4.x)) / ((p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x));
75
+ return new _geometry.Point(p1.x + t * (p2.x - p1.x), p1.y + t * (p2.y - p1.y));
76
+ }
77
+
78
+ // Check collinear cases
79
+ if (d1 === 0 && onSegment(p3, p1, p4)) return p1.clone();
80
+ if (d2 === 0 && onSegment(p3, p2, p4)) return p2.clone();
81
+ if (d3 === 0 && onSegment(p1, p3, p2)) return p3.clone();
82
+ if (d4 === 0 && onSegment(p1, p4, p2)) return p4.clone();
83
+ return null;
84
+ }
85
+ function direction(p1, p2, p3) {
86
+ return (p3.x - p1.x) * (p2.y - p1.y) - (p2.x - p1.x) * (p3.y - p1.y);
87
+ }
88
+ function onSegment(p1, p2, p3) {
89
+ return p2.x >= Math.min(p1.x, p3.x) && p2.x <= Math.max(p1.x, p3.x) && p2.y >= Math.min(p1.y, p3.y) && p2.y <= Math.max(p1.y, p3.y);
90
+ }
91
+
92
+ /**
93
+ * Check if a path intersects with any obstacles (nodes)
94
+ * A path is considered to intersect if it has >= 2 unique intersection points with a node
95
+ *
96
+ * Note: pathPoints should be pre-processed by parseSVGPath which samples bezier curves
97
+ * into line segments, so this function works correctly with curved paths
98
+ */
99
+ function pathIntersectsObstacles(pathPoints, nodeLookup) {
100
+ const tolerance = 0.01;
101
+
102
+ // Iterate through all nodes (including source and target)
103
+ for (const [nodeId] of nodeLookup) {
104
+ const nodeBBox = getNodeBBox(nodeId, nodeLookup);
105
+ if (!nodeBBox) continue;
106
+ const allIntersections = [];
107
+
108
+ // Collect all unique intersection points for this node
109
+ for (let i = 0; i < pathPoints.length - 1; i++) {
110
+ const p1 = pathPoints[i];
111
+ const p2 = pathPoints[i + 1];
112
+ const segmentIntersections = findSegmentRectIntersections(p1, p2, nodeBBox);
113
+
114
+ // Add to global list, avoiding duplicates
115
+ for (const intersection of segmentIntersections) {
116
+ const isDuplicate = allIntersections.some(existing => Math.abs(existing.x - intersection.x) < tolerance && Math.abs(existing.y - intersection.y) < tolerance);
117
+ if (!isDuplicate) {
118
+ allIntersections.push(intersection);
119
+ }
120
+ }
121
+ }
122
+
123
+ // If path has 2 or more unique intersections with this node, it crosses through it
124
+ if (allIntersections.length >= 2) {
125
+ console.log(`[pathIntersectsObstacles] Path crosses node ${nodeId} with ${allIntersections.length} intersections`);
126
+ return true;
127
+ }
128
+ }
129
+ return false;
130
+ }
@@ -0,0 +1,15 @@
1
+ import { Point } from './Point';
2
+ import { Rectangle } from './Rectangle';
3
+ /**
4
+ * Check if a line segment intersects with a rectangle
5
+ */
6
+ export declare function lineSegmentIntersectsRect(p1: Point, p2: Point, rect: Rectangle): boolean;
7
+ /**
8
+ * Check if a line segment crosses through a rectangle (not just touching edges)
9
+ */
10
+ export declare function lineSegmentCrossesRect(p1: Point, p2: Point, rect: Rectangle): boolean;
11
+ /**
12
+ * Check if two line segments intersect
13
+ */
14
+ export declare function lineSegmentsIntersect(p1: Point, p2: Point, p3: Point, p4: Point): boolean;
15
+ //# sourceMappingURL=collision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collision.d.ts","sourceRoot":"","sources":["../../src/geometry/collision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAwBxF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAsBrF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAgBzF"}
@@ -0,0 +1,73 @@
1
+ import { Point } from "./Point";
2
+ /**
3
+ * Check if a line segment intersects with a rectangle
4
+ */
5
+ export function lineSegmentIntersectsRect(p1, p2, rect) {
6
+ // Check if either endpoint is inside the rectangle
7
+ if (rect.containsPoint(p1) || rect.containsPoint(p2)) {
8
+ return true;
9
+ }
10
+
11
+ // Check if line segment intersects any of the rectangle's edges
12
+ var rectPoints = [new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Point(rect.x, rect.y + rect.height)];
13
+ for (var i = 0; i < 4; i++) {
14
+ var r1 = rectPoints[i];
15
+ var r2 = rectPoints[(i + 1) % 4];
16
+ if (lineSegmentsIntersect(p1, p2, r1, r2)) {
17
+ return true;
18
+ }
19
+ }
20
+ return false;
21
+ }
22
+
23
+ /**
24
+ * Check if a line segment crosses through a rectangle (not just touching edges)
25
+ */
26
+ export function lineSegmentCrossesRect(p1, p2, rect) {
27
+ var tolerance = 1;
28
+
29
+ // Check if segment passes through the interior of the rectangle
30
+ // We sample points along the segment and check if any are strictly inside
31
+ var steps = 10;
32
+ for (var i = 1; i < steps; i++) {
33
+ var t = i / steps;
34
+ var x = p1.x + (p2.x - p1.x) * t;
35
+ var y = p1.y + (p2.y - p1.y) * t;
36
+ if (x > rect.x + tolerance && x < rect.x + rect.width - tolerance && y > rect.y + tolerance && y < rect.y + rect.height - tolerance) {
37
+ return true;
38
+ }
39
+ }
40
+ return false;
41
+ }
42
+
43
+ /**
44
+ * Check if two line segments intersect
45
+ */
46
+ export function lineSegmentsIntersect(p1, p2, p3, p4) {
47
+ var d1 = direction(p3, p4, p1);
48
+ var d2 = direction(p3, p4, p2);
49
+ var d3 = direction(p1, p2, p3);
50
+ var d4 = direction(p1, p2, p4);
51
+ if ((d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0)) {
52
+ return true;
53
+ }
54
+ if (d1 === 0 && onSegment(p3, p1, p4)) return true;
55
+ if (d2 === 0 && onSegment(p3, p2, p4)) return true;
56
+ if (d3 === 0 && onSegment(p1, p3, p2)) return true;
57
+ if (d4 === 0 && onSegment(p1, p4, p2)) return true;
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * Calculate direction of point p3 relative to line p1-p2
63
+ */
64
+ function direction(p1, p2, p3) {
65
+ return (p3.x - p1.x) * (p2.y - p1.y) - (p2.x - p1.x) * (p3.y - p1.y);
66
+ }
67
+
68
+ /**
69
+ * Check if point p2 is on line segment p1-p3
70
+ */
71
+ function onSegment(p1, p2, p3) {
72
+ return p2.x >= Math.min(p1.x, p3.x) && p2.x <= Math.max(p1.x, p3.x) && p2.y >= Math.min(p1.y, p3.y) && p2.y <= Math.max(p1.y, p3.y);
73
+ }
@@ -1,4 +1,5 @@
1
1
  export { Point } from './Point';
2
2
  export { Rectangle } from './Rectangle';
3
3
  export { Line } from './Line';
4
+ export { lineSegmentIntersectsRect, lineSegmentCrossesRect, lineSegmentsIntersect } from './collision';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/geometry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/geometry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC7B,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA"}
@@ -1,3 +1,4 @@
1
1
  export { Point } from "./Point";
2
2
  export { Rectangle } from "./Rectangle";
3
- export { Line } from "./Line";
3
+ export { Line } from "./Line";
4
+ export { lineSegmentIntersectsRect, lineSegmentCrossesRect, lineSegmentsIntersect } from "./collision";
@@ -1,3 +1,4 @@
1
+ import { Position } from '@xyflow/react';
1
2
  import type { ManhattanRouterOptions, NodeLookup } from './options';
2
3
  /**
3
4
  * Parameters for getManHattanPath function
@@ -11,20 +12,12 @@ export interface GetManHattanPathParams {
11
12
  * Target node ID
12
13
  */
13
14
  targetNodeId: string;
14
- /**
15
- * Source anchor position (where the edge starts)
16
- */
17
- sourcePosition: {
18
- x: number;
19
- y: number;
20
- };
21
- /**
22
- * Target anchor position (where the edge ends)
23
- */
24
- targetPosition: {
25
- x: number;
26
- y: number;
27
- };
15
+ sourceX: number;
16
+ sourceY: number;
17
+ targetX: number;
18
+ targetY: number;
19
+ sourcePosition: Position;
20
+ targetPosition: Position;
28
21
  /**
29
22
  * ReactFlow node lookup map
30
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"getManHattanPath.d.ts","sourceRoot":"","sources":["../src/getManHattanPath.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAOnE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAExC;;OAEG;IACH,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAExC;;OAEG;IACH,UAAU,EAAE,UAAU,CAAA;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CA4PvE"}
1
+ {"version":3,"file":"getManHattanPath.d.ts","sourceRoot":"","sources":["../src/getManHattanPath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,QAAQ,EAAE,MAAM,eAAe,CAAA;AAG3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAMnE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAEhB,cAAc,EAAE,QAAQ,CAAC;IACzB,cAAc,EAAE,QAAQ,CAAC;IACzB;;OAEG;IACH,UAAU,EAAE,UAAU,CAAA;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAwRvE"}
@@ -1,15 +1,20 @@
1
1
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
2
2
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
- function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
4
3
  function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
5
4
  function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
5
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
6
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
7
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
6
8
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
9
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
10
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
11
+ import { getSmoothStepPath } from '@xyflow/react';
7
12
  import { Point, Rectangle } from "./geometry";
8
- import { resolveOptions } from "./options";
9
13
  import { ObstacleMap } from "./obstacle";
14
+ import { resolveOptions } from "./options";
10
15
  import { findRoute } from "./pathfinder";
11
- import { pointsToPath, snapPathToGrid } from "./svg";
12
- import { getNodeDimensions, getNodePosition } from "./utils";
16
+ import { pointsToPath, snapPathToGrid, parseSVGPath, simplifyPath } from "./svg";
17
+ import { getNodeDimensions, getNodePosition, pathIntersectsObstacles } from "./utils";
13
18
 
14
19
  /**
15
20
  * Parameters for getManHattanPath function
@@ -17,10 +22,10 @@ import { getNodeDimensions, getNodePosition } from "./utils";
17
22
 
18
23
  /**
19
24
  * Generate Manhattan-routed path for ReactFlow edges
20
- *
25
+ *
21
26
  * @param params - Path generation parameters
22
27
  * @returns SVG path string that can be used with ReactFlow's BaseEdge
23
- *
28
+ *
24
29
  * @example
25
30
  * ```typescript
26
31
  * const path = getManHattanPath({
@@ -43,6 +48,10 @@ export function getManHattanPath(params) {
43
48
  sourcePosition = params.sourcePosition,
44
49
  targetPosition = params.targetPosition,
45
50
  nodeLookup = params.nodeLookup,
51
+ sourceX = params.sourceX,
52
+ sourceY = params.sourceY,
53
+ targetX = params.targetX,
54
+ targetY = params.targetY,
46
55
  _params$options = params.options,
47
56
  userOptions = _params$options === void 0 ? {} : _params$options;
48
57
 
@@ -55,8 +64,8 @@ export function getManHattanPath(params) {
55
64
  if (!sourceNode || !targetNode) {
56
65
  // Fallback to simple straight line if nodes not found
57
66
  console.warn('Source or target node not found in nodeLookup');
58
- var start = new Point(sourcePosition.x, sourcePosition.y);
59
- var end = new Point(targetPosition.x, targetPosition.y);
67
+ var start = new Point(sourceX, sourceY);
68
+ var end = new Point(targetX, targetY);
60
69
  return pointsToPath([start, end], options.precision);
61
70
  }
62
71
 
@@ -73,8 +82,31 @@ export function getManHattanPath(params) {
73
82
  var targetBBox = new Rectangle(targetPos.x, targetPos.y, targetDimensions.width, targetDimensions.height);
74
83
 
75
84
  // Create anchor points
76
- var sourceAnchor = new Point(sourcePosition.x, sourcePosition.y);
77
- var targetAnchor = new Point(targetPosition.x, targetPosition.y);
85
+ var sourceAnchor = new Point(sourceX, sourceY);
86
+ var targetAnchor = new Point(targetX, targetY);
87
+
88
+ // Try ReactFlow's getSmoothStepPath first
89
+ var _getSmoothStepPath = getSmoothStepPath({
90
+ sourceX: sourceX,
91
+ sourceY: sourceY,
92
+ targetX: targetX,
93
+ targetY: targetY,
94
+ sourcePosition: sourcePosition,
95
+ targetPosition: targetPosition,
96
+ borderRadius: options.borderRadius
97
+ }),
98
+ _getSmoothStepPath2 = _slicedToArray(_getSmoothStepPath, 1),
99
+ smoothStepPath = _getSmoothStepPath2[0];
100
+
101
+ // Parse the smooth step path to extract points
102
+ var smoothStepPoints = parseSVGPath(smoothStepPath);
103
+
104
+ // Check if smooth step path intersects with any obstacles
105
+ if (smoothStepPoints.length > 0 && !pathIntersectsObstacles(smoothStepPoints, nodeLookup)) {
106
+ console.log('[getManHattanPath] Using ReactFlow getSmoothStepPath (no obstacles)');
107
+ return smoothStepPath;
108
+ }
109
+ console.log('[getManHattanPath] SmoothStepPath intersects obstacles, using Manhattan routing');
78
110
 
79
111
  // Build obstacle map with anchor information
80
112
  var obstacleMap = new ObstacleMap(options).build(nodeLookup, sourceNodeId, targetNodeId, sourceAnchor, targetAnchor);
@@ -137,7 +169,9 @@ export function getManHattanPath(params) {
137
169
  }
138
170
 
139
171
  // Insert extension point at source - always extend away from node edge by fixed step distance
172
+ // Add borderRadius to extension distance to account for rounded corners
140
173
  if (route.length > 0) {
174
+ var extensionDistance = step + options.borderRadius;
141
175
  var _firstPoint = route[0];
142
176
 
143
177
  // Determine which edge the source anchor is on
@@ -148,8 +182,8 @@ export function getManHattanPath(params) {
148
182
 
149
183
  // Insert extension point and corner point to ensure orthogonal path
150
184
  if (_onRight2) {
151
- // Anchor on right edge - extend right by fixed step
152
- var extendX = sourceAnchor.x + step;
185
+ // Anchor on right edge - extend right by step + borderRadius
186
+ var extendX = sourceAnchor.x + extensionDistance;
153
187
  var extensionPoint = new Point(extendX, sourceAnchor.y);
154
188
  // Check if we need a corner point
155
189
  if (Math.abs(extensionPoint.y - _firstPoint.y) > tolerance) {
@@ -158,8 +192,8 @@ export function getManHattanPath(params) {
158
192
  route.unshift(extensionPoint); // Extension point (fixed distance)
159
193
  console.log('[getManHattanPath] Inserted source extension (right):', "(".concat(extendX, ", ").concat(sourceAnchor.y, ")"));
160
194
  } else if (_onLeft2) {
161
- // Anchor on left edge - extend left by fixed step
162
- var _extendX = sourceAnchor.x - step;
195
+ // Anchor on left edge - extend left by step + borderRadius
196
+ var _extendX = sourceAnchor.x - extensionDistance;
163
197
  var _extensionPoint = new Point(_extendX, sourceAnchor.y);
164
198
  if (Math.abs(_extensionPoint.y - _firstPoint.y) > tolerance) {
165
199
  route.unshift(new Point(_extendX, _firstPoint.y));
@@ -167,8 +201,8 @@ export function getManHattanPath(params) {
167
201
  route.unshift(_extensionPoint);
168
202
  console.log('[getManHattanPath] Inserted source extension (left):', "(".concat(_extendX, ", ").concat(sourceAnchor.y, ")"));
169
203
  } else if (_onBottom2) {
170
- // Anchor on bottom edge - extend down by fixed step
171
- var extendY = sourceAnchor.y + step;
204
+ // Anchor on bottom edge - extend down by step + borderRadius
205
+ var extendY = sourceAnchor.y + extensionDistance;
172
206
  var _extensionPoint2 = new Point(sourceAnchor.x, extendY);
173
207
  if (Math.abs(_extensionPoint2.x - _firstPoint.x) > tolerance) {
174
208
  route.unshift(new Point(_firstPoint.x, extendY));
@@ -176,8 +210,8 @@ export function getManHattanPath(params) {
176
210
  route.unshift(_extensionPoint2);
177
211
  console.log('[getManHattanPath] Inserted source extension (down):', "(".concat(sourceAnchor.x, ", ").concat(extendY, ")"));
178
212
  } else if (_onTop2) {
179
- // Anchor on top edge - extend up by fixed step
180
- var _extendY = sourceAnchor.y - step;
213
+ // Anchor on top edge - extend up by step + borderRadius
214
+ var _extendY = sourceAnchor.y - extensionDistance;
181
215
  var _extensionPoint3 = new Point(sourceAnchor.x, _extendY);
182
216
  if (Math.abs(_extensionPoint3.x - _firstPoint.x) > tolerance) {
183
217
  route.unshift(new Point(_firstPoint.x, _extendY));
@@ -188,50 +222,50 @@ export function getManHattanPath(params) {
188
222
  }
189
223
 
190
224
  // Insert extension point at target - always extend away from node edge by fixed step distance
225
+ // Add borderRadius to extension distance to account for rounded corners
191
226
  if (route.length > 0) {
192
- var _step = options.step;
193
- var _tolerance = 1;
227
+ var _extensionDistance = step + options.borderRadius;
194
228
  var _lastPoint = route[route.length - 1];
195
229
 
196
230
  // Determine which edge the target anchor is on
197
- var _onLeft3 = Math.abs(targetAnchor.x - targetBBox.x) < _tolerance;
198
- var _onRight3 = Math.abs(targetAnchor.x - (targetBBox.x + targetBBox.width)) < _tolerance;
199
- var _onTop3 = Math.abs(targetAnchor.y - targetBBox.y) < _tolerance;
200
- var _onBottom3 = Math.abs(targetAnchor.y - (targetBBox.y + targetBBox.height)) < _tolerance;
231
+ var _onLeft3 = Math.abs(targetAnchor.x - targetBBox.x) < tolerance;
232
+ var _onRight3 = Math.abs(targetAnchor.x - (targetBBox.x + targetBBox.width)) < tolerance;
233
+ var _onTop3 = Math.abs(targetAnchor.y - targetBBox.y) < tolerance;
234
+ var _onBottom3 = Math.abs(targetAnchor.y - (targetBBox.y + targetBBox.height)) < tolerance;
201
235
 
202
236
  // Insert extension point and corner point to ensure orthogonal path
203
237
  if (_onLeft3) {
204
- // Anchor on left edge - extend left by fixed step
205
- var _extendX2 = targetAnchor.x - _step;
238
+ // Anchor on left edge - extend left by step + borderRadius
239
+ var _extendX2 = targetAnchor.x - _extensionDistance;
206
240
  var _extensionPoint4 = new Point(_extendX2, targetAnchor.y);
207
- if (Math.abs(_extensionPoint4.y - _lastPoint.y) > _tolerance) {
241
+ if (Math.abs(_extensionPoint4.y - _lastPoint.y) > tolerance) {
208
242
  route.push(new Point(_extendX2, _lastPoint.y)); // Corner point
209
243
  }
210
244
  route.push(_extensionPoint4); // Extension point (fixed distance)
211
245
  console.log('[getManHattanPath] Inserted target extension (left):', "(".concat(_extendX2, ", ").concat(targetAnchor.y, ")"));
212
246
  } else if (_onRight3) {
213
- // Anchor on right edge - extend right by fixed step
214
- var _extendX3 = targetAnchor.x + _step;
247
+ // Anchor on right edge - extend right by step + borderRadius
248
+ var _extendX3 = targetAnchor.x + _extensionDistance;
215
249
  var _extensionPoint5 = new Point(_extendX3, targetAnchor.y);
216
- if (Math.abs(_extensionPoint5.y - _lastPoint.y) > _tolerance) {
250
+ if (Math.abs(_extensionPoint5.y - _lastPoint.y) > tolerance) {
217
251
  route.push(new Point(_extendX3, _lastPoint.y));
218
252
  }
219
253
  route.push(_extensionPoint5);
220
254
  console.log('[getManHattanPath] Inserted target extension (right):', "(".concat(_extendX3, ", ").concat(targetAnchor.y, ")"));
221
255
  } else if (_onTop3) {
222
- // Anchor on top edge - extend up by fixed step
223
- var _extendY2 = targetAnchor.y - _step;
256
+ // Anchor on top edge - extend up by step + borderRadius
257
+ var _extendY2 = targetAnchor.y - _extensionDistance;
224
258
  var _extensionPoint6 = new Point(targetAnchor.x, _extendY2);
225
- if (Math.abs(_extensionPoint6.x - _lastPoint.x) > _tolerance) {
259
+ if (Math.abs(_extensionPoint6.x - _lastPoint.x) > tolerance) {
226
260
  route.push(new Point(_lastPoint.x, _extendY2));
227
261
  }
228
262
  route.push(_extensionPoint6);
229
263
  console.log('[getManHattanPath] Inserted target extension (up):', "(".concat(targetAnchor.x, ", ").concat(_extendY2, ")"));
230
264
  } else if (_onBottom3) {
231
- // Anchor on bottom edge - extend down by fixed step
232
- var _extendY3 = targetAnchor.y + _step;
265
+ // Anchor on bottom edge - extend down by step + borderRadius
266
+ var _extendY3 = targetAnchor.y + _extensionDistance;
233
267
  var _extensionPoint7 = new Point(targetAnchor.x, _extendY3);
234
- if (Math.abs(_extensionPoint7.x - _lastPoint.x) > _tolerance) {
268
+ if (Math.abs(_extensionPoint7.x - _lastPoint.x) > tolerance) {
235
269
  route.push(new Point(_lastPoint.x, _extendY3));
236
270
  }
237
271
  route.push(_extensionPoint7);
@@ -260,32 +294,4 @@ export function getManHattanPath(params) {
260
294
 
261
295
  // Convert to SVG path string
262
296
  return pointsToPath(finalRoute, options.precision, options.borderRadius);
263
- }
264
-
265
- /**
266
- * Simplify path by removing collinear intermediate points
267
- */
268
- function simplifyPath(points) {
269
- if (points.length <= 2) {
270
- return points;
271
- }
272
- var simplified = [points[0]];
273
- for (var i = 1; i < points.length - 1; i++) {
274
- var prev = simplified[simplified.length - 1];
275
- var current = points[i];
276
- var next = points[i + 1];
277
-
278
- // Check if current point is collinear with prev and next
279
- var isHorizontalLine = prev.y === current.y && current.y === next.y;
280
- var isVerticalLine = prev.x === current.x && current.x === next.x;
281
-
282
- // Only keep the point if it's not collinear (i.e., it's a corner)
283
- if (!isHorizontalLine && !isVerticalLine) {
284
- simplified.push(current);
285
- }
286
- }
287
-
288
- // Always add the last point
289
- simplified.push(points[points.length - 1]);
290
- return simplified;
291
297
  }
@@ -12,7 +12,7 @@ export var defaults = {
12
12
  excludeNodes: [],
13
13
  excludeShapes: [],
14
14
  excludeTerminals: [],
15
- padding: 0,
15
+ padding: 15,
16
16
  snapToGrid: true,
17
17
  borderRadius: 5,
18
18
  penalties: {
@@ -1,2 +1,3 @@
1
1
  export { pointsToPath, snapPathToGrid } from './pathConverter';
2
+ export { parseSVGPath, simplifyPath } from './pathParser';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svg/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svg/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
package/esm/svg/index.js CHANGED
@@ -1 +1,2 @@
1
- export { pointsToPath, snapPathToGrid } from "./pathConverter";
1
+ export { pointsToPath, snapPathToGrid } from "./pathConverter";
2
+ export { parseSVGPath, simplifyPath } from "./pathParser";
@@ -0,0 +1,11 @@
1
+ import { Point } from '../geometry';
2
+ /**
3
+ * Parse SVG path string to extract points
4
+ * Simplified parser that handles M, L, Q commands
5
+ */
6
+ export declare function parseSVGPath(pathString: string): Point[];
7
+ /**
8
+ * Simplify path by removing collinear intermediate points
9
+ */
10
+ export declare function simplifyPath(points: Point[]): Point[];
11
+ //# sourceMappingURL=pathParser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathParser.d.ts","sourceRoot":"","sources":["../../src/svg/pathParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,EAAE,CA2CxD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CA0BrD"}
@@ -0,0 +1,82 @@
1
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
2
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
3
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
4
+ import { Point } from "../geometry";
5
+
6
+ /**
7
+ * Parse SVG path string to extract points
8
+ * Simplified parser that handles M, L, Q commands
9
+ */
10
+ export function parseSVGPath(pathString) {
11
+ var points = [];
12
+ var commands = pathString.match(/[MLQ][^MLQ]*/g);
13
+ if (!commands) return points;
14
+ var _iterator = _createForOfIteratorHelper(commands),
15
+ _step;
16
+ try {
17
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
18
+ var command = _step.value;
19
+ var type = command[0];
20
+ var coords = command.slice(1).trim().split(/[\s,]+/).map(Number);
21
+ if (type === 'M' || type === 'L') {
22
+ // MoveTo or LineTo: x, y
23
+ if (coords.length >= 2) {
24
+ points.push(new Point(coords[0], coords[1]));
25
+ }
26
+ } else if (type === 'Q') {
27
+ // Quadratic Bezier: cx, cy, x, y
28
+ // We sample points along the curve for collision detection
29
+ if (coords.length >= 4) {
30
+ var prevPoint = points[points.length - 1];
31
+ if (prevPoint) {
32
+ var cx = coords[0];
33
+ var cy = coords[1];
34
+ var x = coords[2];
35
+ var y = coords[3];
36
+
37
+ // Sample 10 points along the bezier curve for better accuracy
38
+ // This ensures we don't miss intersections with obstacles
39
+ for (var t = 0.1; t <= 1; t += 0.1) {
40
+ var bx = (1 - t) * (1 - t) * prevPoint.x + 2 * (1 - t) * t * cx + t * t * x;
41
+ var by = (1 - t) * (1 - t) * prevPoint.y + 2 * (1 - t) * t * cy + t * t * y;
42
+ points.push(new Point(bx, by));
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ } catch (err) {
49
+ _iterator.e(err);
50
+ } finally {
51
+ _iterator.f();
52
+ }
53
+ return points;
54
+ }
55
+
56
+ /**
57
+ * Simplify path by removing collinear intermediate points
58
+ */
59
+ export function simplifyPath(points) {
60
+ if (points.length <= 2) {
61
+ return points;
62
+ }
63
+ var simplified = [points[0]];
64
+ for (var i = 1; i < points.length - 1; i++) {
65
+ var prev = simplified[simplified.length - 1];
66
+ var current = points[i];
67
+ var next = points[i + 1];
68
+
69
+ // Check if current point is collinear with prev and next
70
+ var isHorizontalLine = prev.y === current.y && current.y === next.y;
71
+ var isVerticalLine = prev.x === current.x && current.x === next.x;
72
+
73
+ // Only keep the point if it's not collinear (i.e., it's a corner)
74
+ if (!isHorizontalLine && !isVerticalLine) {
75
+ simplified.push(current);
76
+ }
77
+ }
78
+
79
+ // Always add the last point
80
+ simplified.push(points[points.length - 1]);
81
+ return simplified;
82
+ }