@rxflow/manhattan 0.0.2-alpha.7 → 0.0.2

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 (157) hide show
  1. package/README.md +214 -10
  2. package/esm/getManHattanPath.js +92 -69
  3. package/esm/obstacle/ObstacleMap.js +218 -99
  4. package/esm/obstacle/QuadTree.js +488 -0
  5. package/esm/options/resolver.js +147 -18
  6. package/esm/pathfinder/PathCache.js +278 -0
  7. package/esm/pathfinder/findRoute.js +98 -44
  8. package/esm/pathfinder/index.js +2 -1
  9. package/esm/svg/pathConverter.js +170 -1
  10. package/esm/utils/AdaptiveStepCalculator.js +252 -0
  11. package/esm/utils/ErrorRecovery.js +499 -0
  12. package/esm/utils/GlobalGrid.js +259 -0
  13. package/esm/utils/PerformanceMonitor.js +360 -0
  14. package/esm/utils/getAnchorPoints.js +0 -4
  15. package/esm/utils/grid.js +18 -13
  16. package/esm/utils/heuristics.js +144 -0
  17. package/esm/utils/index.js +7 -1
  18. package/esm/utils/pathProcessing.js +270 -0
  19. package/esm/utils/pathValidation.js +0 -1
  20. package/esm/utils/rect.js +11 -4
  21. package/esm/utils/route.js +18 -2
  22. package/package.json +10 -2
  23. package/cjs/geometry/Line.d.ts +0 -21
  24. package/cjs/geometry/Line.d.ts.map +0 -1
  25. package/cjs/geometry/Line.js +0 -88
  26. package/cjs/geometry/Point.d.ts +0 -49
  27. package/cjs/geometry/Point.d.ts.map +0 -1
  28. package/cjs/geometry/Point.js +0 -94
  29. package/cjs/geometry/Rectangle.d.ts +0 -41
  30. package/cjs/geometry/Rectangle.d.ts.map +0 -1
  31. package/cjs/geometry/Rectangle.js +0 -65
  32. package/cjs/geometry/collision.d.ts +0 -15
  33. package/cjs/geometry/collision.d.ts.map +0 -1
  34. package/cjs/geometry/collision.js +0 -81
  35. package/cjs/geometry/index.d.ts +0 -5
  36. package/cjs/geometry/index.d.ts.map +0 -1
  37. package/cjs/geometry/index.js +0 -45
  38. package/cjs/getManHattanPath.d.ts +0 -53
  39. package/cjs/getManHattanPath.d.ts.map +0 -1
  40. package/cjs/getManHattanPath.js +0 -418
  41. package/cjs/index.d.ts +0 -16
  42. package/cjs/index.d.ts.map +0 -1
  43. package/cjs/index.js +0 -117
  44. package/cjs/obstacle/ObstacleMap.d.ts +0 -34
  45. package/cjs/obstacle/ObstacleMap.d.ts.map +0 -1
  46. package/cjs/obstacle/ObstacleMap.js +0 -223
  47. package/cjs/obstacle/index.d.ts +0 -2
  48. package/cjs/obstacle/index.d.ts.map +0 -1
  49. package/cjs/obstacle/index.js +0 -12
  50. package/cjs/options/defaults.d.ts +0 -16
  51. package/cjs/options/defaults.d.ts.map +0 -1
  52. package/cjs/options/defaults.js +0 -39
  53. package/cjs/options/index.d.ts +0 -4
  54. package/cjs/options/index.d.ts.map +0 -1
  55. package/cjs/options/index.js +0 -38
  56. package/cjs/options/resolver.d.ts +0 -10
  57. package/cjs/options/resolver.d.ts.map +0 -1
  58. package/cjs/options/resolver.js +0 -120
  59. package/cjs/options/types.d.ts +0 -169
  60. package/cjs/options/types.d.ts.map +0 -1
  61. package/cjs/options/types.js +0 -5
  62. package/cjs/pathfinder/SortedSet.d.ts +0 -35
  63. package/cjs/pathfinder/SortedSet.d.ts.map +0 -1
  64. package/cjs/pathfinder/SortedSet.js +0 -95
  65. package/cjs/pathfinder/findRoute.d.ts +0 -8
  66. package/cjs/pathfinder/findRoute.d.ts.map +0 -1
  67. package/cjs/pathfinder/findRoute.js +0 -330
  68. package/cjs/pathfinder/index.d.ts +0 -3
  69. package/cjs/pathfinder/index.d.ts.map +0 -1
  70. package/cjs/pathfinder/index.js +0 -19
  71. package/cjs/svg/index.d.ts +0 -3
  72. package/cjs/svg/index.d.ts.map +0 -1
  73. package/cjs/svg/index.js +0 -31
  74. package/cjs/svg/pathConverter.d.ts +0 -10
  75. package/cjs/svg/pathConverter.d.ts.map +0 -1
  76. package/cjs/svg/pathConverter.js +0 -116
  77. package/cjs/svg/pathParser.d.ts +0 -11
  78. package/cjs/svg/pathParser.d.ts.map +0 -1
  79. package/cjs/svg/pathParser.js +0 -76
  80. package/cjs/utils/direction.d.ts +0 -24
  81. package/cjs/utils/direction.d.ts.map +0 -1
  82. package/cjs/utils/direction.js +0 -54
  83. package/cjs/utils/getAnchorPoints.d.ts +0 -15
  84. package/cjs/utils/getAnchorPoints.d.ts.map +0 -1
  85. package/cjs/utils/getAnchorPoints.js +0 -75
  86. package/cjs/utils/grid.d.ts +0 -27
  87. package/cjs/utils/grid.d.ts.map +0 -1
  88. package/cjs/utils/grid.js +0 -66
  89. package/cjs/utils/index.d.ts +0 -8
  90. package/cjs/utils/index.d.ts.map +0 -1
  91. package/cjs/utils/index.js +0 -82
  92. package/cjs/utils/node.d.ts +0 -27
  93. package/cjs/utils/node.d.ts.map +0 -1
  94. package/cjs/utils/node.js +0 -36
  95. package/cjs/utils/pathValidation.d.ts +0 -11
  96. package/cjs/utils/pathValidation.d.ts.map +0 -1
  97. package/cjs/utils/pathValidation.js +0 -130
  98. package/cjs/utils/rect.d.ts +0 -9
  99. package/cjs/utils/rect.d.ts.map +0 -1
  100. package/cjs/utils/rect.js +0 -103
  101. package/cjs/utils/route.d.ts +0 -19
  102. package/cjs/utils/route.d.ts.map +0 -1
  103. package/cjs/utils/route.js +0 -76
  104. package/esm/geometry/Line.d.ts +0 -21
  105. package/esm/geometry/Line.d.ts.map +0 -1
  106. package/esm/geometry/Point.d.ts +0 -49
  107. package/esm/geometry/Point.d.ts.map +0 -1
  108. package/esm/geometry/Rectangle.d.ts +0 -41
  109. package/esm/geometry/Rectangle.d.ts.map +0 -1
  110. package/esm/geometry/collision.d.ts +0 -15
  111. package/esm/geometry/collision.d.ts.map +0 -1
  112. package/esm/geometry/index.d.ts +0 -5
  113. package/esm/geometry/index.d.ts.map +0 -1
  114. package/esm/getManHattanPath.d.ts +0 -53
  115. package/esm/getManHattanPath.d.ts.map +0 -1
  116. package/esm/index.d.ts +0 -16
  117. package/esm/index.d.ts.map +0 -1
  118. package/esm/obstacle/ObstacleMap.d.ts +0 -34
  119. package/esm/obstacle/ObstacleMap.d.ts.map +0 -1
  120. package/esm/obstacle/index.d.ts +0 -2
  121. package/esm/obstacle/index.d.ts.map +0 -1
  122. package/esm/options/defaults.d.ts +0 -16
  123. package/esm/options/defaults.d.ts.map +0 -1
  124. package/esm/options/index.d.ts +0 -4
  125. package/esm/options/index.d.ts.map +0 -1
  126. package/esm/options/resolver.d.ts +0 -10
  127. package/esm/options/resolver.d.ts.map +0 -1
  128. package/esm/options/types.d.ts +0 -169
  129. package/esm/options/types.d.ts.map +0 -1
  130. package/esm/pathfinder/SortedSet.d.ts +0 -35
  131. package/esm/pathfinder/SortedSet.d.ts.map +0 -1
  132. package/esm/pathfinder/findRoute.d.ts +0 -8
  133. package/esm/pathfinder/findRoute.d.ts.map +0 -1
  134. package/esm/pathfinder/index.d.ts +0 -3
  135. package/esm/pathfinder/index.d.ts.map +0 -1
  136. package/esm/svg/index.d.ts +0 -3
  137. package/esm/svg/index.d.ts.map +0 -1
  138. package/esm/svg/pathConverter.d.ts +0 -10
  139. package/esm/svg/pathConverter.d.ts.map +0 -1
  140. package/esm/svg/pathParser.d.ts +0 -11
  141. package/esm/svg/pathParser.d.ts.map +0 -1
  142. package/esm/utils/direction.d.ts +0 -24
  143. package/esm/utils/direction.d.ts.map +0 -1
  144. package/esm/utils/getAnchorPoints.d.ts +0 -15
  145. package/esm/utils/getAnchorPoints.d.ts.map +0 -1
  146. package/esm/utils/grid.d.ts +0 -27
  147. package/esm/utils/grid.d.ts.map +0 -1
  148. package/esm/utils/index.d.ts +0 -8
  149. package/esm/utils/index.d.ts.map +0 -1
  150. package/esm/utils/node.d.ts +0 -27
  151. package/esm/utils/node.d.ts.map +0 -1
  152. package/esm/utils/pathValidation.d.ts +0 -11
  153. package/esm/utils/pathValidation.d.ts.map +0 -1
  154. package/esm/utils/rect.d.ts +0 -9
  155. package/esm/utils/rect.d.ts.map +0 -1
  156. package/esm/utils/route.d.ts +0 -19
  157. package/esm/utils/route.d.ts.map +0 -1
package/README.md CHANGED
@@ -2,33 +2,237 @@
2
2
 
3
3
  Manhattan 路由算法,用于 ReactFlow 生成正交路径,支持障碍物避让。
4
4
 
5
+ ## 特性
6
+
7
+ - **A* 寻路算法** - 高效的路径搜索,支持早期终止优化
8
+ - **正交路径生成** - 生成符合 Manhattan 风格的直角路径
9
+ - **障碍物避让** - 自动绕过节点障碍物
10
+ - **四叉树优化** - 使用 QuadTree 加速空间查询
11
+ - **自适应步长** - 根据节点密度和距离自动调整网格步长
12
+ - **路径缓存** - LRU 缓存机制提升重复查询性能
13
+ - **全局网格对齐** - 确保所有路径点对齐到全局网格
14
+ - **圆角支持** - 可配置的路径转角圆角半径
15
+ - **性能监控** - 内置性能指标收集和日志
16
+
5
17
  ## 安装
6
18
 
7
19
  ```bash
8
20
  npm install @rxflow/manhattan
9
21
  # or
10
22
  pnpm add @rxflow/manhattan
23
+ yarn add @rxflow/manhattan
11
24
  ```
12
25
 
13
- ## 使用
26
+ ## 基本使用
14
27
 
15
28
  ```tsx
16
29
  import { getManHattanPath } from '@rxflow/manhattan';
30
+ import { useReactFlow } from '@xyflow/react';
31
+
32
+ function CustomEdge({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, source, target }) {
33
+ const { getNodes } = useReactFlow();
34
+ const nodeLookup = new Map(getNodes().map(n => [n.id, n]));
35
+
36
+ const path = getManHattanPath({
37
+ sourceNodeId: source,
38
+ targetNodeId: target,
39
+ sourceX,
40
+ sourceY,
41
+ targetX,
42
+ targetY,
43
+ sourcePosition,
44
+ targetPosition,
45
+ nodeLookup,
46
+ options: {
47
+ step: 10,
48
+ borderRadius: 5,
49
+ }
50
+ });
51
+
52
+ return <path d={path} />;
53
+ }
54
+ ```
55
+
56
+ ## API
57
+
58
+ ### getManHattanPath(params)
59
+
60
+ 生成 Manhattan 风格的 SVG 路径字符串。
61
+
62
+ #### 参数
63
+
64
+ | 参数 | 类型 | 必填 | 描述 |
65
+ |------|------|------|------|
66
+ | sourceNodeId | string | ✓ | 源节点 ID |
67
+ | targetNodeId | string | ✓ | 目标节点 ID |
68
+ | sourceX | number | ✓ | 源锚点 X 坐标 |
69
+ | sourceY | number | ✓ | 源锚点 Y 坐标 |
70
+ | targetX | number | ✓ | 目标锚点 X 坐标 |
71
+ | targetY | number | ✓ | 目标锚点 Y 坐标 |
72
+ | sourcePosition | Position | ✓ | 源锚点位置 (top/right/bottom/left) |
73
+ | targetPosition | Position | ✓ | 目标锚点位置 |
74
+ | nodeLookup | NodeLookup | ✓ | ReactFlow 节点查找 Map |
75
+ | options | ManhattanRouterOptions | - | 路由配置选项 |
76
+
77
+ #### 返回值
78
+
79
+ 返回 SVG 路径字符串,可直接用于 `<path d={...} />` 元素。
17
80
 
81
+ ### ManhattanRouterOptions
82
+
83
+ | 选项 | 类型 | 默认值 | 描述 |
84
+ |------|------|--------|------|
85
+ | step | number | 10 | 网格步长(像素) |
86
+ | maxLoopCount | number | 2000 | 最大迭代次数 |
87
+ | precision | number | 1 | 坐标精度(小数位数) |
88
+ | borderRadius | number | 5 | 转角圆角半径 |
89
+ | extensionDistance | number | 20 | 路径延伸距离 |
90
+ | padding | number \| object | 20 | 节点边界框内边距 |
91
+ | startDirections | Direction[] | ['top', 'right', 'bottom', 'left'] | 允许的起始方向 |
92
+ | endDirections | Direction[] | ['top', 'right', 'bottom', 'left'] | 允许的结束方向 |
93
+ | excludeNodes | string[] | [] | 排除的节点 ID |
94
+ | excludeShapes | string[] | [] | 排除的节点类型 |
95
+ | adaptiveStep | AdaptiveStepConfig | - | 自适应步长配置 |
96
+ | performance | PerformanceConfig | - | 性能优化配置 |
97
+ | debug | DebugConfig | - | 调试配置 |
98
+
99
+ ### AdaptiveStepConfig
100
+
101
+ ```typescript
102
+ interface AdaptiveStepConfig {
103
+ enabled: boolean; // 是否启用自适应步长
104
+ minStep: number; // 最小步长 (默认: 5)
105
+ maxStep: number; // 最大步长 (默认: 50)
106
+ densityThreshold: number; // 密度阈值 (默认: 0.3)
107
+ distanceThreshold: number; // 距离阈值 (默认: 500)
108
+ }
109
+ ```
110
+
111
+ ### PerformanceConfig
112
+
113
+ ```typescript
114
+ interface PerformanceConfig {
115
+ enableCache: boolean; // 启用路径缓存
116
+ cacheSize: number; // 缓存大小 (默认: 100)
117
+ enableQuadTree: boolean; // 启用四叉树优化
118
+ earlyTermination: boolean; // 启用早期终止
119
+ }
120
+ ```
121
+
122
+ ### DebugConfig
123
+
124
+ ```typescript
125
+ interface DebugConfig {
126
+ enableLogging: boolean; // 启用日志
127
+ enableMetrics: boolean; // 启用性能指标
128
+ logLevel: 'error' | 'warn' | 'info' | 'debug';
129
+ }
130
+ ```
131
+
132
+ ## 高级用法
133
+
134
+ ### 自定义障碍物排除
135
+
136
+ ```typescript
18
137
  const path = getManHattanPath({
19
- sourceNodeId: 'node-1',
20
- targetNodeId: 'node-2',
21
- nodeLookup,
22
- // ... other options
138
+ // ...基本参数
139
+ options: {
140
+ excludeNodes: ['node-3', 'node-4'], // 排除特定节点
141
+ excludeShapes: ['group'], // 排除特定类型
142
+ excludeTerminals: ['source'], // 排除源/目标节点
143
+ }
23
144
  });
24
145
  ```
25
146
 
26
- ## 特性
147
+ ### 性能优化配置
27
148
 
28
- - A* 寻路算法
29
- - 正交路径生成
30
- - 障碍物避让
31
- - 高性能计算
149
+ ```typescript
150
+ const path = getManHattanPath({
151
+ // ...基本参数
152
+ options: {
153
+ performance: {
154
+ enableCache: true,
155
+ cacheSize: 200,
156
+ enableQuadTree: true,
157
+ earlyTermination: true,
158
+ },
159
+ adaptiveStep: {
160
+ enabled: true,
161
+ minStep: 5,
162
+ maxStep: 30,
163
+ }
164
+ }
165
+ });
166
+ ```
167
+
168
+ ### 调试模式
169
+
170
+ ```typescript
171
+ const path = getManHattanPath({
172
+ // ...基本参数
173
+ options: {
174
+ debug: {
175
+ enableLogging: true,
176
+ enableMetrics: true,
177
+ logLevel: 'debug',
178
+ }
179
+ }
180
+ });
181
+ ```
182
+
183
+ ## 工具类
184
+
185
+ ### GlobalGrid
186
+
187
+ 全局网格对齐工具,确保所有路径点对齐到统一网格。
188
+
189
+ ```typescript
190
+ import { GlobalGrid } from '@rxflow/manhattan';
191
+
192
+ const grid = new GlobalGrid(10);
193
+ const snapped = grid.snapToGrid({ x: 15, y: 23 }); // { x: 20, y: 20 }
194
+ ```
195
+
196
+ ### ObstacleMap
197
+
198
+ 障碍物地图,用于空间查询和碰撞检测。
199
+
200
+ ```typescript
201
+ import { ObstacleMap } from '@rxflow/manhattan';
202
+
203
+ const map = new ObstacleMap(options);
204
+ map.build(nodeLookup, sourceId, targetId, sourceAnchor, targetAnchor);
205
+ const accessible = map.isAccessible(point);
206
+ ```
207
+
208
+ ### ErrorRecovery
209
+
210
+ 错误恢复工具,提供回退路径生成。
211
+
212
+ ```typescript
213
+ import { ErrorRecovery } from '@rxflow/manhattan';
214
+
215
+ const fallback = ErrorRecovery.generateFallbackPath(start, end);
216
+ const validated = ErrorRecovery.validateAndFixConfig(config);
217
+ ```
218
+
219
+ ## 架构
220
+
221
+ ```
222
+ @rxflow/manhattan
223
+ ├── getManHattanPath.ts # 主入口函数
224
+ ├── geometry/ # 几何类 (Point, Rectangle)
225
+ ├── obstacle/ # 障碍物处理 (ObstacleMap, QuadTree)
226
+ ├── pathfinder/ # A* 寻路算法
227
+ ├── options/ # 配置解析
228
+ ├── svg/ # SVG 路径转换
229
+ └── utils/ # 工具函数
230
+ ├── GlobalGrid.ts # 全局网格
231
+ ├── AdaptiveStepCalculator.ts # 自适应步长
232
+ ├── PerformanceMonitor.ts # 性能监控
233
+ ├── ErrorRecovery.ts # 错误恢复
234
+ └── heuristics.ts # 启发式函数
235
+ ```
32
236
 
33
237
  ## License
34
238
 
@@ -14,13 +14,13 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
14
14
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
15
15
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
16
16
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
17
- import { getSmoothStepPath } from '@xyflow/react';
17
+ import { getSmoothStepPath, Position } from '@xyflow/react';
18
18
  import { Point, Rectangle } from "./geometry";
19
19
  import { ObstacleMap } from "./obstacle";
20
20
  import { resolveOptions } from "./options";
21
21
  import { findRoute } from "./pathfinder";
22
22
  import { pointsToPath, parseSVGPath } from "./svg";
23
- import { getNodeDimensions, getNodePosition, pathIntersectsObstacles } from "./utils";
23
+ import { getNodeDimensions, getNodePosition, pathIntersectsObstacles, ErrorRecovery } from "./utils";
24
24
 
25
25
  /**
26
26
  * Parameters for getManHattanPath function
@@ -78,10 +78,12 @@ export function getManHattanPath(params) {
78
78
  var targetNode = nodeLookup.get(targetNodeId);
79
79
  if (!sourceNode || !targetNode) {
80
80
  // Fallback to simple straight line if nodes not found
81
- console.warn('Source or target node not found in nodeLookup');
81
+ console.warn('[getManHattanPath] Source or target node not found in nodeLookup');
82
82
  var start = new Point(sourceX, sourceY);
83
83
  var end = new Point(targetX, targetY);
84
- return pointsToPath([start, end], options.precision);
84
+ // Use ErrorRecovery to generate a proper fallback path
85
+ var fallbackPath = ErrorRecovery.generateFallbackPath(start, end);
86
+ return pointsToPath(fallbackPath, options.precision, options.borderRadius);
85
87
  }
86
88
 
87
89
  // Get node dimensions using ReactFlow's priority logic
@@ -118,10 +120,8 @@ export function getManHattanPath(params) {
118
120
 
119
121
  // Check if smooth step path intersects with any obstacles
120
122
  if (smoothStepPoints.length > 0 && !pathIntersectsObstacles(smoothStepPoints, nodeLookup)) {
121
- console.log('[getManHattanPath] Using ReactFlow getSmoothStepPath (no obstacles)');
122
123
  return smoothStepPath;
123
124
  }
124
- console.log('[getManHattanPath] SmoothStepPath intersects obstacles, using Manhattan routing');
125
125
 
126
126
  // Build obstacle map with anchor information
127
127
  var obstacleMap = new ObstacleMap(options).build(nodeLookup, sourceNodeId, targetNodeId, sourceAnchor, targetAnchor);
@@ -129,27 +129,80 @@ export function getManHattanPath(params) {
129
129
  // Find route
130
130
  var route = findRoute(sourceBBox, targetBBox, sourceAnchor, targetAnchor, obstacleMap, options);
131
131
 
132
- // Fallback to straight line if no route found
132
+ // Fallback to Z-shaped path if no route found
133
133
  if (!route) {
134
- console.warn('Unable to find Manhattan route, using straight line fallback');
135
- route = [sourceAnchor, targetAnchor];
134
+ console.warn('[getManHattanPath] Unable to find Manhattan route, using fallback path');
135
+ route = ErrorRecovery.generateFallbackPath(sourceAnchor, targetAnchor);
136
136
  }
137
- console.log('[getManHattanPath] Route from findRoute:', route.map(function (p) {
138
- return "(".concat(p.x, ", ").concat(p.y, ")");
139
- }));
140
- console.log('[getManHattanPath] Source anchor:', "(".concat(sourceAnchor.x, ", ").concat(sourceAnchor.y, ")"));
141
- console.log('[getManHattanPath] Target anchor:', "(".concat(targetAnchor.x, ", ").concat(targetAnchor.y, ")"));
142
137
 
143
138
  // If using smart point generation (sourcePosition/targetPosition specified),
144
139
  // the route already contains the correct extension points, so skip manual processing
145
140
  var useSmartPoints = sourcePosition || targetPosition;
146
141
  if (useSmartPoints) {
147
- console.log('[getManHattanPath] Using smart points, skipping manual extension point processing');
142
+ // Post-process route to fix X coordinates for horizontal anchors
143
+ // This ensures the extension distance is fixed and not affected by grid alignment
144
+ var isSourceHorizontal = sourcePosition === Position.Left || sourcePosition === Position.Right;
145
+ var isTargetHorizontal = targetPosition === Position.Left || targetPosition === Position.Right;
146
+ if (isSourceHorizontal && isTargetHorizontal && route.length > 0) {
147
+ // Calculate expected extension X coordinates
148
+ var sourceExtensionX = sourcePosition === Position.Right ? sourceAnchor.x + options.extensionDistance : sourceAnchor.x - options.extensionDistance;
149
+ var targetExtensionX = targetPosition === Position.Left ? targetAnchor.x - options.extensionDistance : targetAnchor.x + options.extensionDistance;
150
+
151
+ // Find the horizontal segment (where Y stays constant but X changes significantly)
152
+ // This divides the path into source-side and target-side
153
+ var horizontalSegmentStart = -1;
154
+ var horizontalSegmentEnd = -1;
155
+ for (var _i = 0; _i < route.length - 1; _i++) {
156
+ var curr = route[_i];
157
+ var next = route[_i + 1];
158
+ var isHorizontalMove = Math.abs(curr.y - next.y) < 1 && Math.abs(curr.x - next.x) > options.step;
159
+ if (isHorizontalMove) {
160
+ horizontalSegmentStart = _i;
161
+ horizontalSegmentEnd = _i + 1;
162
+ break;
163
+ }
164
+ }
165
+
166
+ // Fix route points
167
+ for (var _i2 = 0; _i2 < route.length; _i2++) {
168
+ var point = route[_i2];
169
+ if (horizontalSegmentStart >= 0) {
170
+ if (_i2 <= horizontalSegmentStart) {
171
+ // Source side - all vertical segment points should use sourceExtensionX
172
+ route[_i2] = new Point(sourceExtensionX, point.y);
173
+ } else if (_i2 >= horizontalSegmentEnd) {
174
+ // Target side - all vertical segment points should use targetExtensionX
175
+ route[_i2] = new Point(targetExtensionX, point.y);
176
+ }
177
+ }
178
+ }
179
+
180
+ // Remove redundant points (consecutive points on same line)
181
+ var optimized = [];
182
+ for (var _i3 = 0; _i3 < route.length; _i3++) {
183
+ var _point = route[_i3];
184
+ if (optimized.length < 2) {
185
+ optimized.push(_point);
186
+ } else {
187
+ var prev = optimized[optimized.length - 1];
188
+ var prevPrev = optimized[optimized.length - 2];
189
+
190
+ // Check if prev is on the same line as prevPrev and point
191
+ var sameX = Math.abs(prevPrev.x - prev.x) < 1 && Math.abs(prev.x - _point.x) < 1;
192
+ var sameY = Math.abs(prevPrev.y - prev.y) < 1 && Math.abs(prev.y - _point.y) < 1;
193
+ if (sameX || sameY) {
194
+ // prev is redundant, replace it with current point
195
+ optimized[optimized.length - 1] = _point;
196
+ } else {
197
+ optimized.push(_point);
198
+ }
199
+ }
200
+ }
201
+ route = optimized;
202
+ }
203
+
148
204
  // Add source and target anchors to route
149
205
  var _finalRoute = [sourceAnchor].concat(_toConsumableArray(route), [targetAnchor]);
150
- console.log('[getManHattanPath] Final route:', _finalRoute.map(function (p) {
151
- return "(".concat(p.x, ", ").concat(p.y, ")");
152
- }));
153
206
  return pointsToPath(_finalRoute, options.precision, options.borderRadius);
154
207
  }
155
208
 
@@ -172,7 +225,6 @@ export function getManHattanPath(params) {
172
225
  // This is likely an extension point, remove it
173
226
  if (onRight && firstPoint.x > sourceAnchor.x || onLeft && firstPoint.x < sourceAnchor.x || onBottom && firstPoint.y > sourceAnchor.y || onTop && firstPoint.y < sourceAnchor.y) {
174
227
  route.shift();
175
- console.log('[getManHattanPath] Removed extension point from route start');
176
228
  }
177
229
  }
178
230
  }
@@ -191,7 +243,6 @@ export function getManHattanPath(params) {
191
243
  // This is likely an extension point, remove it
192
244
  if (_onLeft && lastPoint.x < targetAnchor.x || _onRight && lastPoint.x > targetAnchor.x || _onTop && lastPoint.y < targetAnchor.y || _onBottom && lastPoint.y > targetAnchor.y) {
193
245
  route.pop();
194
- console.log('[getManHattanPath] Removed extension point from route end');
195
246
  }
196
247
  }
197
248
  }
@@ -217,7 +268,6 @@ export function getManHattanPath(params) {
217
268
  route.unshift(new Point(extendX, _firstPoint.y)); // Corner point
218
269
  }
219
270
  route.unshift(extensionPoint); // Extension point (fixed distance)
220
- console.log('[getManHattanPath] Inserted source extension (right):', "(".concat(extendX, ", ").concat(sourceAnchor.y, ")"));
221
271
  } else if (_onLeft2) {
222
272
  // Anchor on left edge - extend left by step + borderRadius
223
273
  var _extendX = sourceAnchor.x - extensionDistance;
@@ -226,7 +276,6 @@ export function getManHattanPath(params) {
226
276
  route.unshift(new Point(_extendX, _firstPoint.y));
227
277
  }
228
278
  route.unshift(_extensionPoint);
229
- console.log('[getManHattanPath] Inserted source extension (left):', "(".concat(_extendX, ", ").concat(sourceAnchor.y, ")"));
230
279
  } else if (_onBottom2) {
231
280
  // Anchor on bottom edge - extend down by step + borderRadius
232
281
  var extendY = sourceAnchor.y + extensionDistance;
@@ -235,7 +284,6 @@ export function getManHattanPath(params) {
235
284
  route.unshift(new Point(_firstPoint.x, extendY));
236
285
  }
237
286
  route.unshift(_extensionPoint2);
238
- console.log('[getManHattanPath] Inserted source extension (down):', "(".concat(sourceAnchor.x, ", ").concat(extendY, ")"));
239
287
  } else if (_onTop2) {
240
288
  // Anchor on top edge - extend up by step + borderRadius
241
289
  var _extendY = sourceAnchor.y - extensionDistance;
@@ -244,33 +292,25 @@ export function getManHattanPath(params) {
244
292
  route.unshift(new Point(_firstPoint.x, _extendY));
245
293
  }
246
294
  route.unshift(_extensionPoint3);
247
- console.log('[getManHattanPath] Inserted source extension (up):', "(".concat(sourceAnchor.x, ", ").concat(_extendY, ")"));
248
295
  }
249
296
  }
250
297
 
251
298
  // Remove redundant points after source extension
252
299
  // If the first route point has the same x or y coordinate as the source anchor, it's redundant
253
- if (route.length > 2) {
254
- var firstRoutePoint = route[0]; // Extension point
255
- var secondRoutePoint = route[1]; // Corner point (if exists)
300
+ if (route.length > 3) {
256
301
  var thirdRoutePoint = route[2]; // Original A* point
257
-
258
302
  // Check if the third point (original A* point) is redundant
259
303
  // It's redundant if it's on the same line as the corner point and can be skipped
260
- var sameX = Math.abs(thirdRoutePoint.x - sourceAnchor.x) < tolerance;
261
- var sameY = Math.abs(thirdRoutePoint.y - sourceAnchor.y) < tolerance;
262
- if (sameX || sameY) {
263
- // The third point is aligned with the source anchor, likely redundant
264
- // Check if we can skip it by connecting corner point directly to the next point
265
- if (route.length > 3) {
266
- var fourthPoint = route[3];
267
- // If corner point and fourth point form a straight line, remove the third point
268
- var cornerToThird = Math.abs(secondRoutePoint.x - thirdRoutePoint.x) < tolerance || Math.abs(secondRoutePoint.y - thirdRoutePoint.y) < tolerance;
269
- var thirdToFourth = Math.abs(thirdRoutePoint.x - fourthPoint.x) < tolerance || Math.abs(thirdRoutePoint.y - fourthPoint.y) < tolerance;
270
- if (cornerToThird && thirdToFourth) {
271
- console.log('[getManHattanPath] Removing redundant point:', "(".concat(thirdRoutePoint.x, ", ").concat(thirdRoutePoint.y, ")"));
272
- route.splice(2, 1); // Remove the third point
273
- }
304
+ var _sameX = Math.abs(thirdRoutePoint.x - sourceAnchor.x) < tolerance;
305
+ var _sameY = Math.abs(thirdRoutePoint.y - sourceAnchor.y) < tolerance;
306
+ if (_sameX || _sameY) {
307
+ var secondRoutePoint = route[1]; // Corner point (if exists)
308
+ var fourthPoint = route[3];
309
+ // If corner point and fourth point form a straight line, remove the third point
310
+ var cornerToThird = Math.abs(secondRoutePoint.x - thirdRoutePoint.x) < tolerance || Math.abs(secondRoutePoint.y - thirdRoutePoint.y) < tolerance;
311
+ var thirdToFourth = Math.abs(thirdRoutePoint.x - fourthPoint.x) < tolerance || Math.abs(thirdRoutePoint.y - fourthPoint.y) < tolerance;
312
+ if (cornerToThird && thirdToFourth) {
313
+ route.splice(2, 1); // Remove the third point
274
314
  }
275
315
  }
276
316
  }
@@ -280,16 +320,12 @@ export function getManHattanPath(params) {
280
320
  // where the middle segment goes to target edge and then moves along it
281
321
  // Use target bbox with padding (same as ObstacleMap)
282
322
  var targetBBoxWithPadding = targetBBox.moveAndExpand(options.paddingBox);
283
- console.log('[getManHattanPath] Route before zigzag check:', route.map(function (p) {
284
- return "(".concat(p.x, ", ").concat(p.y, ")");
285
- }));
286
- console.log('[getManHattanPath] Target BBox with padding:', "x=".concat(targetBBoxWithPadding.x, ", y=").concat(targetBBoxWithPadding.y));
287
323
  if (route.length >= 3) {
288
- var _i = 0;
289
- while (_i < route.length - 2) {
290
- var p1 = route[_i];
291
- var p2 = route[_i + 1];
292
- var p3 = route[_i + 2];
324
+ var _i4 = 0;
325
+ while (_i4 < route.length - 2) {
326
+ var p1 = route[_i4];
327
+ var p2 = route[_i4 + 1];
328
+ var p3 = route[_i4 + 2];
293
329
 
294
330
  // Check if p2 is on the target bbox edge (with padding)
295
331
  var p2OnTargetLeftEdge = Math.abs(p2.x - targetBBoxWithPadding.x) < tolerance;
@@ -297,22 +333,19 @@ export function getManHattanPath(params) {
297
333
  var p2OnTargetTopEdge = Math.abs(p2.y - targetBBoxWithPadding.y) < tolerance;
298
334
  var p2OnTargetBottomEdge = Math.abs(p2.y - (targetBBoxWithPadding.y + targetBBoxWithPadding.height)) < tolerance;
299
335
  var p2OnTargetEdge = p2OnTargetLeftEdge || p2OnTargetRightEdge || p2OnTargetTopEdge || p2OnTargetBottomEdge;
300
- console.log("[getManHattanPath] Checking i=".concat(_i, ": p2=(").concat(p2.x, ", ").concat(p2.y, "), onEdge=").concat(p2OnTargetEdge));
301
336
  if (p2OnTargetEdge) {
302
337
  // Check if p1 -> p2 -> p3 forms a zigzag
303
338
  var p1ToP2Horizontal = Math.abs(p1.y - p2.y) < tolerance;
304
339
  var p2ToP3Vertical = Math.abs(p2.x - p3.x) < tolerance;
305
340
  var p1ToP2Vertical = Math.abs(p1.x - p2.x) < tolerance;
306
341
  var p2ToP3Horizontal = Math.abs(p2.y - p3.y) < tolerance;
307
- console.log("[getManHattanPath] Zigzag pattern: H->V=".concat(p1ToP2Horizontal && p2ToP3Vertical, ", V->H=").concat(p1ToP2Vertical && p2ToP3Horizontal));
308
342
  if (p1ToP2Horizontal && p2ToP3Vertical || p1ToP2Vertical && p2ToP3Horizontal) {
309
343
  // We have a zigzag at target edge, remove p2 and p3
310
- console.log('[getManHattanPath] Removing zigzag at target edge:', "(".concat(p2.x, ", ").concat(p2.y, ")"), "and (".concat(p3.x, ", ").concat(p3.y, ")"));
311
- route.splice(_i + 1, 2); // Remove p2 and p3
344
+ route.splice(_i4 + 1, 2); // Remove p2 and p3
312
345
  continue;
313
346
  }
314
347
  }
315
- _i++;
348
+ _i4++;
316
349
  }
317
350
  }
318
351
 
@@ -336,7 +369,6 @@ export function getManHattanPath(params) {
336
369
  route.push(new Point(_extendX2, _lastPoint.y)); // Corner point
337
370
  }
338
371
  route.push(_extensionPoint4); // Extension point (fixed distance)
339
- console.log('[getManHattanPath] Inserted target extension (left):', "(".concat(_extendX2, ", ").concat(targetAnchor.y, ")"));
340
372
  } else if (_onRight3) {
341
373
  // Anchor on right edge - extend right by step + borderRadius
342
374
  var _extendX3 = targetAnchor.x + _extensionDistance;
@@ -345,7 +377,6 @@ export function getManHattanPath(params) {
345
377
  route.push(new Point(_extendX3, _lastPoint.y));
346
378
  }
347
379
  route.push(_extensionPoint5);
348
- console.log('[getManHattanPath] Inserted target extension (right):', "(".concat(_extendX3, ", ").concat(targetAnchor.y, ")"));
349
380
  } else if (_onTop3) {
350
381
  // Anchor on top edge - extend up by step + borderRadius
351
382
  var _extendY2 = targetAnchor.y - _extensionDistance;
@@ -354,7 +385,6 @@ export function getManHattanPath(params) {
354
385
  route.push(new Point(_lastPoint.x, _extendY2));
355
386
  }
356
387
  route.push(_extensionPoint6);
357
- console.log('[getManHattanPath] Inserted target extension (up):', "(".concat(targetAnchor.x, ", ").concat(_extendY2, ")"));
358
388
  } else if (_onBottom3) {
359
389
  // Anchor on bottom edge - extend down by step + borderRadius
360
390
  var _extendY3 = targetAnchor.y + _extensionDistance;
@@ -363,7 +393,6 @@ export function getManHattanPath(params) {
363
393
  route.push(new Point(_lastPoint.x, _extendY3));
364
394
  }
365
395
  route.push(_extensionPoint7);
366
- console.log('[getManHattanPath] Inserted target extension (down):', "(".concat(targetAnchor.x, ", ").concat(_extendY3, ")"));
367
396
  }
368
397
  }
369
398
 
@@ -371,20 +400,18 @@ export function getManHattanPath(params) {
371
400
  // Similar logic for target side
372
401
  if (route.length > 2) {
373
402
  var lastIdx = route.length - 1;
374
- var lastRoutePoint = route[lastIdx]; // Extension point
375
403
  var secondLastPoint = route[lastIdx - 1]; // Corner point (if exists)
376
404
  var thirdLastPoint = route[lastIdx - 2]; // Original A* point
377
405
 
378
406
  // Check if the third-to-last point is redundant
379
- var _sameX = Math.abs(thirdLastPoint.x - targetAnchor.x) < tolerance;
380
- var _sameY = Math.abs(thirdLastPoint.y - targetAnchor.y) < tolerance;
381
- if (_sameX || _sameY) {
407
+ var _sameX2 = Math.abs(thirdLastPoint.x - targetAnchor.x) < tolerance;
408
+ var _sameY2 = Math.abs(thirdLastPoint.y - targetAnchor.y) < tolerance;
409
+ if (_sameX2 || _sameY2) {
382
410
  if (route.length > 3) {
383
411
  var fourthLastPoint = route[lastIdx - 3];
384
412
  var fourthToThird = Math.abs(fourthLastPoint.x - thirdLastPoint.x) < tolerance || Math.abs(fourthLastPoint.y - thirdLastPoint.y) < tolerance;
385
413
  var thirdToSecond = Math.abs(thirdLastPoint.x - secondLastPoint.x) < tolerance || Math.abs(thirdLastPoint.y - secondLastPoint.y) < tolerance;
386
414
  if (fourthToThird && thirdToSecond) {
387
- console.log('[getManHattanPath] Removing redundant point:', "(".concat(thirdLastPoint.x, ", ").concat(thirdLastPoint.y, ")"));
388
415
  route.splice(lastIdx - 2, 1); // Remove the third-to-last point
389
416
  }
390
417
  }
@@ -417,7 +444,6 @@ export function getManHattanPath(params) {
417
444
  var p3ToP4Horizontal = Math.abs(_p3.y - p4.y) < tolerance;
418
445
  if (p3ToP4Horizontal) {
419
446
  // Pattern: horizontal -> vertical -> horizontal (zigzag)
420
- console.log('[getManHattanPath] Removing zigzag at target edge:', "(".concat(_p2.x, ", ").concat(_p2.y, ")"), "and (".concat(_p3.x, ", ").concat(_p3.y, ")"));
421
447
  route.splice(i + 1, 2); // Remove p2 and p3
422
448
  continue;
423
449
  }
@@ -428,9 +454,6 @@ export function getManHattanPath(params) {
428
454
 
429
455
  // Add source and target anchors to route
430
456
  var finalRoute = [sourceAnchor].concat(_toConsumableArray(route), [targetAnchor]);
431
- console.log('[getManHattanPath] Final route:', finalRoute.map(function (p) {
432
- return "(".concat(p.x, ", ").concat(p.y, ")");
433
- }));
434
457
 
435
458
  // Convert to SVG path string
436
459
  return pointsToPath(finalRoute, options.precision, options.borderRadius);