@logicflow/core 2.1.9 → 2.1.11

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.
@@ -12,6 +12,13 @@ export declare class PolylineEdgeModel extends BaseEdgeModel {
12
12
  offset?: number;
13
13
  dbClickPosition?: Point;
14
14
  initEdgeData(data: LogicFlow.EdgeConfig): void;
15
+ setAttributes(): void;
16
+ orthogonalizePath(points: Point[]): Point[];
17
+ /**
18
+ * 计算默认 offset:箭头与折线重叠长度 + 5
19
+ * 重叠长度采用箭头样式中的 offset(沿边方向的长度)
20
+ */
21
+ private getDefaultOffset;
15
22
  getEdgeStyle(): {
16
23
  [x: string]: unknown;
17
24
  fill?: string | undefined;
@@ -69,12 +69,139 @@ var PolylineEdgeModel = /** @class */ (function (_super) {
69
69
  return _this;
70
70
  }
71
71
  PolylineEdgeModel.prototype.initEdgeData = function (data) {
72
- this.offset = get(data, 'properties.offset', 30);
72
+ var providedOffset = get(data, 'properties.offset');
73
+ // 当用户未传入 offset 时,按“箭头与折线重叠长度 + 5”作为默认值
74
+ // 其中“重叠长度”采用箭头样式中的 offset(沿边方向的长度)
75
+ this.offset =
76
+ typeof providedOffset === 'number'
77
+ ? providedOffset
78
+ : this.getDefaultOffset();
73
79
  if (data.pointsList) {
74
- this.pointsList = data.pointsList;
80
+ var corrected = this.orthogonalizePath(data.pointsList);
81
+ data.pointsList = corrected;
82
+ this.pointsList = corrected;
75
83
  }
76
84
  _super.prototype.initEdgeData.call(this, data);
77
85
  };
86
+ PolylineEdgeModel.prototype.setAttributes = function () {
87
+ var newOffset = this.properties.offset;
88
+ if (newOffset && newOffset !== this.offset) {
89
+ this.offset = newOffset;
90
+ this.updatePoints();
91
+ }
92
+ };
93
+ PolylineEdgeModel.prototype.orthogonalizePath = function (points) {
94
+ // 输入非法或不足两点时直接返回副本
95
+ if (!Array.isArray(points) || points.length < 2) {
96
+ return points;
97
+ }
98
+ // pushUnique: 向数组中添加唯一点,避免重复
99
+ var pushUnique = function (arr, p) {
100
+ var last = arr[arr.length - 1];
101
+ if (!last || last.x !== p.x || last.y !== p.y) {
102
+ arr.push({ x: p.x, y: p.y });
103
+ }
104
+ };
105
+ // isAxisAligned: 检查两点是否在同一条轴上
106
+ var isAxisAligned = function (a, b) { return a.x === b.x || a.y === b.y; };
107
+ // manhattanDistance: 计算两点在曼哈顿距离上的距离
108
+ var manhattanDistance = function (a, b) {
109
+ return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
110
+ };
111
+ // 1) 生成严格正交路径,尽量延续前一段方向以减少折点
112
+ var orthogonal = [];
113
+ pushUnique(orthogonal, points[0]);
114
+ // previousDirection: 记录前一段的方向,用于判断当前段的PreferredCorner
115
+ var previousDirection;
116
+ // 遍历所有点对,生成正交路径
117
+ for (var i = 0; i < points.length - 1; i++) {
118
+ var current = orthogonal[orthogonal.length - 1];
119
+ var next = points[i + 1];
120
+ if (!current || !next)
121
+ continue;
122
+ if (isAxisAligned(current, next)) {
123
+ pushUnique(orthogonal, next);
124
+ previousDirection =
125
+ current.x === next.x
126
+ ? SegmentDirection.VERTICAL
127
+ : SegmentDirection.HORIZONTAL;
128
+ continue;
129
+ }
130
+ var cornerHV = { x: next.x, y: current.y };
131
+ var cornerVH = { x: current.x, y: next.y };
132
+ // 根据前一段的方向,优先选择能延续该方向的拐角点,以减少折点数量;
133
+ // 若前一段为垂直方向,则优先选择垂直-水平拐角(cornerVH);
134
+ // 若前一段为水平方向,则优先选择水平-垂直拐角(cornerHV);
135
+ // 若前一段无方向(初始情况),则比较两个拐角的曼哈顿距离,选更近者。
136
+ var preferredCorner = previousDirection === SegmentDirection.VERTICAL
137
+ ? cornerVH
138
+ : previousDirection === SegmentDirection.HORIZONTAL
139
+ ? cornerHV
140
+ : manhattanDistance(current, cornerHV) <=
141
+ manhattanDistance(current, cornerVH)
142
+ ? cornerHV
143
+ : cornerVH;
144
+ if (preferredCorner.x !== current.x || preferredCorner.y !== current.y) {
145
+ pushUnique(orthogonal, preferredCorner);
146
+ }
147
+ pushUnique(orthogonal, next);
148
+ var a = orthogonal[orthogonal.length - 2];
149
+ var b = orthogonal[orthogonal.length - 1];
150
+ previousDirection =
151
+ a && b
152
+ ? a.x === b.x
153
+ ? SegmentDirection.VERTICAL
154
+ : SegmentDirection.HORIZONTAL
155
+ : previousDirection;
156
+ }
157
+ // 2) 去除冗余共线中间点
158
+ var simplified = [];
159
+ for (var i = 0; i < orthogonal.length; i++) {
160
+ var prev = orthogonal[i - 1];
161
+ var curr = orthogonal[i];
162
+ var next = orthogonal[i + 1];
163
+ // 如果当前点与前一个点和后一个点在同一条水平线或垂直线上,则跳过该点,去除冗余的共线中间点
164
+ if (prev &&
165
+ curr &&
166
+ next &&
167
+ ((prev.x === curr.x && curr.x === next.x) || // 水平共线
168
+ (prev.y === curr.y && curr.y === next.y)) // 垂直共线
169
+ ) {
170
+ continue;
171
+ }
172
+ pushUnique(simplified, curr);
173
+ }
174
+ // 3) 保留原始起点与终点位置
175
+ if (simplified.length >= 2) {
176
+ simplified[0] = { x: points[0].x, y: points[0].y };
177
+ simplified[simplified.length - 1] = {
178
+ x: points[points.length - 1].x,
179
+ y: points[points.length - 1].y,
180
+ };
181
+ }
182
+ // 4) 结果校验:任意相邻段都必须为水平/垂直;失败则退化为起止两点
183
+ var isOrthogonal = simplified.length < 2 ||
184
+ simplified.every(function (_, idx, arr) {
185
+ if (idx === 0)
186
+ return true;
187
+ return isAxisAligned(arr[idx - 1], arr[idx]);
188
+ });
189
+ return isOrthogonal
190
+ ? simplified
191
+ : [
192
+ { x: points[0].x, y: points[0].y },
193
+ { x: points[points.length - 1].x, y: points[points.length - 1].y },
194
+ ];
195
+ };
196
+ /**
197
+ * 计算默认 offset:箭头与折线重叠长度 + 5
198
+ * 重叠长度采用箭头样式中的 offset(沿边方向的长度)
199
+ */
200
+ PolylineEdgeModel.prototype.getDefaultOffset = function () {
201
+ var arrowStyle = this.getArrowStyle();
202
+ var arrowOverlap = typeof arrowStyle.offset === 'number' ? arrowStyle.offset : 0;
203
+ return arrowOverlap + 5;
204
+ };
78
205
  PolylineEdgeModel.prototype.getEdgeStyle = function () {
79
206
  var polyline = this.graphModel.theme.polyline;
80
207
  var style = _super.prototype.getEdgeStyle.call(this);
@@ -286,7 +413,7 @@ var PolylineEdgeModel = /** @class */ (function (_super) {
286
413
  return list;
287
414
  };
288
415
  PolylineEdgeModel.prototype.updatePath = function (pointList) {
289
- this.pointsList = pointList;
416
+ this.pointsList = this.orthogonalizePath(pointList);
290
417
  this.points = this.getPath(this.pointsList);
291
418
  };
292
419
  PolylineEdgeModel.prototype.getData = function () {
@@ -321,8 +448,10 @@ var PolylineEdgeModel = /** @class */ (function (_super) {
321
448
  x: this.endPoint.x,
322
449
  y: this.endPoint.y,
323
450
  }, this.sourceNode, this.targetNode, this.offset || 0);
324
- this.pointsList = pointsList;
325
- this.points = pointsList.map(function (point) { return "".concat(point.x, ",").concat(point.y); }).join(' ');
451
+ this.pointsList = this.orthogonalizePath(pointsList);
452
+ this.points = this.pointsList
453
+ .map(function (point) { return "".concat(point.x, ",").concat(point.y); })
454
+ .join(' ');
326
455
  };
327
456
  PolylineEdgeModel.prototype.updateStartPoint = function (anchor) {
328
457
  this.startPoint = Object.assign({}, anchor);
@@ -544,13 +673,13 @@ var PolylineEdgeModel = /** @class */ (function (_super) {
544
673
  // 起终点拖拽调整过程中,进行折线路径更新
545
674
  PolylineEdgeModel.prototype.updateAfterAdjustStartAndEnd = function (_a) {
546
675
  var startPoint = _a.startPoint, endPoint = _a.endPoint, sourceNode = _a.sourceNode, targetNode = _a.targetNode;
547
- this.pointsList = getPolylinePoints({
676
+ this.pointsList = this.orthogonalizePath(getPolylinePoints({
548
677
  x: startPoint.x,
549
678
  y: startPoint.y,
550
679
  }, {
551
680
  x: endPoint.x,
552
681
  y: endPoint.y,
553
- }, sourceNode, targetNode, this.offset || 0);
682
+ }, sourceNode, targetNode, this.offset || 0));
554
683
  this.initPoints();
555
684
  };
556
685
  __decorate([
@@ -55,6 +55,10 @@ var MultipleSelect = /** @class */ (function (_super) {
55
55
  function MultipleSelect(props) {
56
56
  var _this = _super.call(this, props) || this;
57
57
  _this.handleMouseDown = function (ev) {
58
+ // 多选区域的拖拽步长随缩放变化
59
+ var _a = _this.props, gridSize = _a.graphModel.gridSize, lf = _a.lf;
60
+ var SCALE_X = lf.getTransform().SCALE_X;
61
+ _this.stepDrag.setStep(gridSize * SCALE_X);
58
62
  _this.stepDrag.handleMouseDown(ev);
59
63
  };
60
64
  // 使多选区域的滚轮事件可以触发画布的滚轮事件
package/es/util/edge.d.ts CHANGED
@@ -14,6 +14,15 @@ export declare const filterRepeatPoints: (points: Point[]) => Point[];
14
14
  export declare const getSimplePolyline: (sPoint: Point, tPoint: Point) => Point[];
15
15
  export declare const getExpandedBBox: (bbox: BoxBounds, offset: number) => BoxBounds;
16
16
  export declare const pointDirection: (point: Point, bbox: BoxBounds) => Direction;
17
+ /**
18
+ * 计算扩展包围盒上的相邻点(起点或终点的下一个/上一个拐点)
19
+ * - 使用原始节点 bbox 来判定点相对中心的方向,避免 offset 扩展后宽高改变导致方向误判
20
+ * - 若 start 相对中心为水平方向,则返回扩展盒在 x 上的边界,y 保持不变
21
+ * - 若为垂直方向,则返回扩展盒在 y 上的边界,x 保持不变
22
+ * @param expendBBox 扩展后的包围盒(包含 offset)
23
+ * @param bbox 原始节点包围盒(用于正确的方向判定)
24
+ * @param point 起点或终点坐标
25
+ */
17
26
  export declare const getExpandedBBoxPoint: (expendBBox: BoxBounds, bbox: BoxBounds, point: Point) => Point;
18
27
  export declare const mergeBBox: (b1: BoxBounds, b2: BoxBounds) => BoxBounds;
19
28
  export declare const getBBoxOfPoints: (points?: Point[], offset?: number) => BoxBounds;
@@ -32,10 +41,36 @@ export declare const rebuildPath: (pathPoints: Point[], pointById: PolyPointMap,
32
41
  export declare const removeClosePointFromOpenList: (arr: Point[], item: Point) => void;
33
42
  export declare const isSegmentsIntersected: (p0: Point, p1: Point, p2: Point, p3: Point) => boolean;
34
43
  export declare const isSegmentCrossingBBox: (p1: Point, p2: Point, bbox: BoxBounds) => boolean;
44
+ /**
45
+ * 基于轴对齐规则获取某点的相邻可连通点(不穿越节点)
46
+ * - 仅考虑 x 或 y 相同的候选点,保证严格水平/垂直
47
+ * - 使用 isSegmentCrossingBBox 校验线段不穿越源/目标节点
48
+ */
35
49
  export declare const getNextNeighborPoints: (points: Point[], point: Point, bbox1: BoxBounds, bbox2: BoxBounds) => Point[];
50
+ /**
51
+ * 使用 A* + 曼哈顿启发式在候选点图上查找正交路径
52
+ * - 开放集/关闭集管理遍历
53
+ * - gScore 为累计实际代价,fScore = gScore + 启发式
54
+ * - 邻居仅为与当前点 x 或 y 相同且不穿越节点的点
55
+ * 参考:https://zh.wikipedia.org/wiki/A*%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95
56
+ */
36
57
  export declare const pathFinder: (points: Point[], start: Point, goal: Point, sBBox: BoxBounds, tBBox: BoxBounds, os: Point, ot: Point) => Point[];
37
58
  export declare const getBoxByOriginNode: (node: BaseNodeModel) => BoxBounds;
59
+ /**
60
+ * 去除共线冗余中间点,保持每条直线段仅保留两端点
61
+ * - 若三点在同一水平线或同一垂直线,移除中间点
62
+ */
38
63
  export declare const pointFilter: (points: Point[]) => Point[];
64
+ /**
65
+ * 计算折线点(正交候选点 + A* 路径)
66
+ * 步骤:
67
+ * 1) 取源/目标节点的扩展包围盒与相邻点 sPoint/tPoint
68
+ * 2) 若两个扩展盒重合,使用简单路径 getSimplePoints
69
+ * 3) 构造 lineBBox/sMixBBox/tMixBBox,并收集其角点与中心交点
70
+ * 4) 过滤掉落在两个扩展盒内部的点,形成 connectPoints
71
+ * 5) 以 sPoint/tPoint 为起止,用 A* 查找路径
72
+ * 6) 拼入原始 start/end,并用 pointFilter 去除冗余共线点
73
+ */
39
74
  export declare const getPolylinePoints: (start: Point, end: Point, sNode: BaseNodeModel, tNode: BaseNodeModel, offset: number) => Point[];
40
75
  /**
41
76
  * 获取折线中最长的一个线
@@ -47,6 +82,10 @@ export declare const isSegmentsCrossNode: (start: Point, end: Point, node: BaseN
47
82
  export declare const getCrossPointInRect: (start: Point, end: Point, node: BaseNodeModel) => Point | false | undefined;
48
83
  export declare const segmentDirection: (start: Point, end: Point) => Direction | undefined;
49
84
  export declare const points2PointsList: (points: string) => Point[];
85
+ /**
86
+ * 当扩展 bbox 重合时的简化拐点计算
87
+ * - 根据起止段的方向(水平/垂直)插入 1~2 个中间点,避免折线重合与穿越
88
+ */
50
89
  export declare const getSimplePoints: (start: Point, end: Point, sPoint: Point, tPoint: Point) => Point[];
51
90
  export declare const getBytesLength: (word: string) => number;
52
91
  export declare const getTextWidth: (text: string, font: string) => number;
package/es/util/edge.js CHANGED
@@ -113,6 +113,15 @@ export var pointDirection = function (point, bbox) {
113
113
  : SegmentDirection.VERTICAL;
114
114
  };
115
115
  /* 获取扩展图形上的点,即起始终点相邻的点,上一个或者下一个节点 */
116
+ /**
117
+ * 计算扩展包围盒上的相邻点(起点或终点的下一个/上一个拐点)
118
+ * - 使用原始节点 bbox 来判定点相对中心的方向,避免 offset 扩展后宽高改变导致方向误判
119
+ * - 若 start 相对中心为水平方向,则返回扩展盒在 x 上的边界,y 保持不变
120
+ * - 若为垂直方向,则返回扩展盒在 y 上的边界,x 保持不变
121
+ * @param expendBBox 扩展后的包围盒(包含 offset)
122
+ * @param bbox 原始节点包围盒(用于正确的方向判定)
123
+ * @param point 起点或终点坐标
124
+ */
116
125
  export var getExpandedBBoxPoint = function (expendBBox, bbox, point) {
117
126
  // https://github.com/didi/LogicFlow/issues/817
118
127
  // 没有修复前传入的参数bbox实际是expendBBox
@@ -313,7 +322,11 @@ export var isSegmentCrossingBBox = function (p1, p2, bbox) {
313
322
  isSegmentsIntersected(p1, p2, pb, pc) ||
314
323
  isSegmentsIntersected(p1, p2, pc, pd));
315
324
  };
316
- /* 获取下一个相邻的点 */
325
+ /**
326
+ * 基于轴对齐规则获取某点的相邻可连通点(不穿越节点)
327
+ * - 仅考虑 x 或 y 相同的候选点,保证严格水平/垂直
328
+ * - 使用 isSegmentCrossingBBox 校验线段不穿越源/目标节点
329
+ */
317
330
  export var getNextNeighborPoints = function (points, point, bbox1, bbox2) {
318
331
  var neighbors = [];
319
332
  points.forEach(function (p) {
@@ -328,9 +341,12 @@ export var getNextNeighborPoints = function (points, point, bbox1, bbox2) {
328
341
  });
329
342
  return filterRepeatPoints(neighbors);
330
343
  };
331
- /* 路径查找,AStar查找+曼哈顿距离
332
- * 算法wiki:https://zh.wikipedia.org/wiki/A*%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95
333
- * 方法无法复用,且调用了很多polyline相关的方法,暂不抽离到src/algorithm中
344
+ /**
345
+ * 使用 A* + 曼哈顿启发式在候选点图上查找正交路径
346
+ * - 开放集/关闭集管理遍历
347
+ * - gScore 为累计实际代价,fScore = gScore + 启发式
348
+ * - 邻居仅为与当前点 x 或 y 相同且不穿越节点的点
349
+ * 参考:https://zh.wikipedia.org/wiki/A*%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95
334
350
  */
335
351
  export var pathFinder = function (points, start, goal, sBBox, tBBox, os, ot) {
336
352
  // 定义已经遍历过的点
@@ -371,6 +387,7 @@ export var pathFinder = function (points, start, goal, sBBox, tBBox, os, ot) {
371
387
  removeClosePointFromOpenList(openSet, current);
372
388
  closedSet.push(current);
373
389
  getNextNeighborPoints(points, current, sBBox, tBBox).forEach(function (neighbor) {
390
+ var _a;
374
391
  if (closedSet.indexOf(neighbor) !== -1) {
375
392
  return;
376
393
  }
@@ -378,7 +395,8 @@ export var pathFinder = function (points, start, goal, sBBox, tBBox, os, ot) {
378
395
  openSet.push(neighbor);
379
396
  }
380
397
  if ((current === null || current === void 0 ? void 0 : current.id) && (neighbor === null || neighbor === void 0 ? void 0 : neighbor.id)) {
381
- var tentativeGScore = fScore[current.id] + estimateDistance(current, neighbor);
398
+ // 修复:累计代价应基于 gScore[current] 而非 fScore[current]
399
+ var tentativeGScore = ((_a = gScore[current.id]) !== null && _a !== void 0 ? _a : 0) + estimateDistance(current, neighbor);
382
400
  if (gScore[neighbor.id] && tentativeGScore >= gScore[neighbor.id]) {
383
401
  return;
384
402
  }
@@ -400,7 +418,10 @@ export var pathFinder = function (points, start, goal, sBBox, tBBox, os, ot) {
400
418
  export var getBoxByOriginNode = function (node) {
401
419
  return getNodeBBox(node);
402
420
  };
403
- /* 保证一条直线上只有2个节点: 删除x/y相同的中间节点 */
421
+ /**
422
+ * 去除共线冗余中间点,保持每条直线段仅保留两端点
423
+ * - 若三点在同一水平线或同一垂直线,移除中间点
424
+ */
404
425
  export var pointFilter = function (points) {
405
426
  var i = 1;
406
427
  while (i < points.length - 1) {
@@ -417,7 +438,16 @@ export var pointFilter = function (points) {
417
438
  }
418
439
  return points;
419
440
  };
420
- /* 计算折线点 */
441
+ /**
442
+ * 计算折线点(正交候选点 + A* 路径)
443
+ * 步骤:
444
+ * 1) 取源/目标节点的扩展包围盒与相邻点 sPoint/tPoint
445
+ * 2) 若两个扩展盒重合,使用简单路径 getSimplePoints
446
+ * 3) 构造 lineBBox/sMixBBox/tMixBBox,并收集其角点与中心交点
447
+ * 4) 过滤掉落在两个扩展盒内部的点,形成 connectPoints
448
+ * 5) 以 sPoint/tPoint 为起止,用 A* 查找路径
449
+ * 6) 拼入原始 start/end,并用 pointFilter 去除冗余共线点
450
+ */
421
451
  export var getPolylinePoints = function (start, end, sNode, tNode, offset) {
422
452
  var sBBox = getBoxByOriginNode(sNode);
423
453
  var tBBox = getBoxByOriginNode(tNode);
@@ -554,6 +584,10 @@ export var points2PointsList = function (points) {
554
584
  });
555
585
  return pointsList;
556
586
  };
587
+ /**
588
+ * 当扩展 bbox 重合时的简化拐点计算
589
+ * - 根据起止段的方向(水平/垂直)插入 1~2 个中间点,避免折线重合与穿越
590
+ */
557
591
  export var getSimplePoints = function (start, end, sPoint, tPoint) {
558
592
  var points = [];
559
593
  // start,sPoint的方向,水平或者垂直,即路径第一条线段的方向
@@ -103,6 +103,9 @@ var CanvasOverlay = /** @class */ (function (_super) {
103
103
  if (selectElements.size > 0) {
104
104
  graphModel.clearSelectElements();
105
105
  }
106
+ // 如果是拖拽状态,不触发点击事件
107
+ if (_this.state.isDragging)
108
+ return;
106
109
  graphModel.eventCenter.emit(EventType.BLANK_CLICK, { e: ev });
107
110
  }
108
111
  };
@@ -12,6 +12,13 @@ export declare class PolylineEdgeModel extends BaseEdgeModel {
12
12
  offset?: number;
13
13
  dbClickPosition?: Point;
14
14
  initEdgeData(data: LogicFlow.EdgeConfig): void;
15
+ setAttributes(): void;
16
+ orthogonalizePath(points: Point[]): Point[];
17
+ /**
18
+ * 计算默认 offset:箭头与折线重叠长度 + 5
19
+ * 重叠长度采用箭头样式中的 offset(沿边方向的长度)
20
+ */
21
+ private getDefaultOffset;
15
22
  getEdgeStyle(): {
16
23
  [x: string]: unknown;
17
24
  fill?: string | undefined;
@@ -72,12 +72,139 @@ var PolylineEdgeModel = /** @class */ (function (_super) {
72
72
  return _this;
73
73
  }
74
74
  PolylineEdgeModel.prototype.initEdgeData = function (data) {
75
- this.offset = (0, lodash_es_1.get)(data, 'properties.offset', 30);
75
+ var providedOffset = (0, lodash_es_1.get)(data, 'properties.offset');
76
+ // 当用户未传入 offset 时,按“箭头与折线重叠长度 + 5”作为默认值
77
+ // 其中“重叠长度”采用箭头样式中的 offset(沿边方向的长度)
78
+ this.offset =
79
+ typeof providedOffset === 'number'
80
+ ? providedOffset
81
+ : this.getDefaultOffset();
76
82
  if (data.pointsList) {
77
- this.pointsList = data.pointsList;
83
+ var corrected = this.orthogonalizePath(data.pointsList);
84
+ data.pointsList = corrected;
85
+ this.pointsList = corrected;
78
86
  }
79
87
  _super.prototype.initEdgeData.call(this, data);
80
88
  };
89
+ PolylineEdgeModel.prototype.setAttributes = function () {
90
+ var newOffset = this.properties.offset;
91
+ if (newOffset && newOffset !== this.offset) {
92
+ this.offset = newOffset;
93
+ this.updatePoints();
94
+ }
95
+ };
96
+ PolylineEdgeModel.prototype.orthogonalizePath = function (points) {
97
+ // 输入非法或不足两点时直接返回副本
98
+ if (!Array.isArray(points) || points.length < 2) {
99
+ return points;
100
+ }
101
+ // pushUnique: 向数组中添加唯一点,避免重复
102
+ var pushUnique = function (arr, p) {
103
+ var last = arr[arr.length - 1];
104
+ if (!last || last.x !== p.x || last.y !== p.y) {
105
+ arr.push({ x: p.x, y: p.y });
106
+ }
107
+ };
108
+ // isAxisAligned: 检查两点是否在同一条轴上
109
+ var isAxisAligned = function (a, b) { return a.x === b.x || a.y === b.y; };
110
+ // manhattanDistance: 计算两点在曼哈顿距离上的距离
111
+ var manhattanDistance = function (a, b) {
112
+ return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
113
+ };
114
+ // 1) 生成严格正交路径,尽量延续前一段方向以减少折点
115
+ var orthogonal = [];
116
+ pushUnique(orthogonal, points[0]);
117
+ // previousDirection: 记录前一段的方向,用于判断当前段的PreferredCorner
118
+ var previousDirection;
119
+ // 遍历所有点对,生成正交路径
120
+ for (var i = 0; i < points.length - 1; i++) {
121
+ var current = orthogonal[orthogonal.length - 1];
122
+ var next = points[i + 1];
123
+ if (!current || !next)
124
+ continue;
125
+ if (isAxisAligned(current, next)) {
126
+ pushUnique(orthogonal, next);
127
+ previousDirection =
128
+ current.x === next.x
129
+ ? constant_1.SegmentDirection.VERTICAL
130
+ : constant_1.SegmentDirection.HORIZONTAL;
131
+ continue;
132
+ }
133
+ var cornerHV = { x: next.x, y: current.y };
134
+ var cornerVH = { x: current.x, y: next.y };
135
+ // 根据前一段的方向,优先选择能延续该方向的拐角点,以减少折点数量;
136
+ // 若前一段为垂直方向,则优先选择垂直-水平拐角(cornerVH);
137
+ // 若前一段为水平方向,则优先选择水平-垂直拐角(cornerHV);
138
+ // 若前一段无方向(初始情况),则比较两个拐角的曼哈顿距离,选更近者。
139
+ var preferredCorner = previousDirection === constant_1.SegmentDirection.VERTICAL
140
+ ? cornerVH
141
+ : previousDirection === constant_1.SegmentDirection.HORIZONTAL
142
+ ? cornerHV
143
+ : manhattanDistance(current, cornerHV) <=
144
+ manhattanDistance(current, cornerVH)
145
+ ? cornerHV
146
+ : cornerVH;
147
+ if (preferredCorner.x !== current.x || preferredCorner.y !== current.y) {
148
+ pushUnique(orthogonal, preferredCorner);
149
+ }
150
+ pushUnique(orthogonal, next);
151
+ var a = orthogonal[orthogonal.length - 2];
152
+ var b = orthogonal[orthogonal.length - 1];
153
+ previousDirection =
154
+ a && b
155
+ ? a.x === b.x
156
+ ? constant_1.SegmentDirection.VERTICAL
157
+ : constant_1.SegmentDirection.HORIZONTAL
158
+ : previousDirection;
159
+ }
160
+ // 2) 去除冗余共线中间点
161
+ var simplified = [];
162
+ for (var i = 0; i < orthogonal.length; i++) {
163
+ var prev = orthogonal[i - 1];
164
+ var curr = orthogonal[i];
165
+ var next = orthogonal[i + 1];
166
+ // 如果当前点与前一个点和后一个点在同一条水平线或垂直线上,则跳过该点,去除冗余的共线中间点
167
+ if (prev &&
168
+ curr &&
169
+ next &&
170
+ ((prev.x === curr.x && curr.x === next.x) || // 水平共线
171
+ (prev.y === curr.y && curr.y === next.y)) // 垂直共线
172
+ ) {
173
+ continue;
174
+ }
175
+ pushUnique(simplified, curr);
176
+ }
177
+ // 3) 保留原始起点与终点位置
178
+ if (simplified.length >= 2) {
179
+ simplified[0] = { x: points[0].x, y: points[0].y };
180
+ simplified[simplified.length - 1] = {
181
+ x: points[points.length - 1].x,
182
+ y: points[points.length - 1].y,
183
+ };
184
+ }
185
+ // 4) 结果校验:任意相邻段都必须为水平/垂直;失败则退化为起止两点
186
+ var isOrthogonal = simplified.length < 2 ||
187
+ simplified.every(function (_, idx, arr) {
188
+ if (idx === 0)
189
+ return true;
190
+ return isAxisAligned(arr[idx - 1], arr[idx]);
191
+ });
192
+ return isOrthogonal
193
+ ? simplified
194
+ : [
195
+ { x: points[0].x, y: points[0].y },
196
+ { x: points[points.length - 1].x, y: points[points.length - 1].y },
197
+ ];
198
+ };
199
+ /**
200
+ * 计算默认 offset:箭头与折线重叠长度 + 5
201
+ * 重叠长度采用箭头样式中的 offset(沿边方向的长度)
202
+ */
203
+ PolylineEdgeModel.prototype.getDefaultOffset = function () {
204
+ var arrowStyle = this.getArrowStyle();
205
+ var arrowOverlap = typeof arrowStyle.offset === 'number' ? arrowStyle.offset : 0;
206
+ return arrowOverlap + 5;
207
+ };
81
208
  PolylineEdgeModel.prototype.getEdgeStyle = function () {
82
209
  var polyline = this.graphModel.theme.polyline;
83
210
  var style = _super.prototype.getEdgeStyle.call(this);
@@ -289,7 +416,7 @@ var PolylineEdgeModel = /** @class */ (function (_super) {
289
416
  return list;
290
417
  };
291
418
  PolylineEdgeModel.prototype.updatePath = function (pointList) {
292
- this.pointsList = pointList;
419
+ this.pointsList = this.orthogonalizePath(pointList);
293
420
  this.points = this.getPath(this.pointsList);
294
421
  };
295
422
  PolylineEdgeModel.prototype.getData = function () {
@@ -324,8 +451,10 @@ var PolylineEdgeModel = /** @class */ (function (_super) {
324
451
  x: this.endPoint.x,
325
452
  y: this.endPoint.y,
326
453
  }, this.sourceNode, this.targetNode, this.offset || 0);
327
- this.pointsList = pointsList;
328
- this.points = pointsList.map(function (point) { return "".concat(point.x, ",").concat(point.y); }).join(' ');
454
+ this.pointsList = this.orthogonalizePath(pointsList);
455
+ this.points = this.pointsList
456
+ .map(function (point) { return "".concat(point.x, ",").concat(point.y); })
457
+ .join(' ');
329
458
  };
330
459
  PolylineEdgeModel.prototype.updateStartPoint = function (anchor) {
331
460
  this.startPoint = Object.assign({}, anchor);
@@ -547,13 +676,13 @@ var PolylineEdgeModel = /** @class */ (function (_super) {
547
676
  // 起终点拖拽调整过程中,进行折线路径更新
548
677
  PolylineEdgeModel.prototype.updateAfterAdjustStartAndEnd = function (_a) {
549
678
  var startPoint = _a.startPoint, endPoint = _a.endPoint, sourceNode = _a.sourceNode, targetNode = _a.targetNode;
550
- this.pointsList = (0, util_1.getPolylinePoints)({
679
+ this.pointsList = this.orthogonalizePath((0, util_1.getPolylinePoints)({
551
680
  x: startPoint.x,
552
681
  y: startPoint.y,
553
682
  }, {
554
683
  x: endPoint.x,
555
684
  y: endPoint.y,
556
- }, sourceNode, targetNode, this.offset || 0);
685
+ }, sourceNode, targetNode, this.offset || 0));
557
686
  this.initPoints();
558
687
  };
559
688
  __decorate([
@@ -57,6 +57,10 @@ var MultipleSelect = /** @class */ (function (_super) {
57
57
  function MultipleSelect(props) {
58
58
  var _this = _super.call(this, props) || this;
59
59
  _this.handleMouseDown = function (ev) {
60
+ // 多选区域的拖拽步长随缩放变化
61
+ var _a = _this.props, gridSize = _a.graphModel.gridSize, lf = _a.lf;
62
+ var SCALE_X = lf.getTransform().SCALE_X;
63
+ _this.stepDrag.setStep(gridSize * SCALE_X);
60
64
  _this.stepDrag.handleMouseDown(ev);
61
65
  };
62
66
  // 使多选区域的滚轮事件可以触发画布的滚轮事件
@@ -14,6 +14,15 @@ export declare const filterRepeatPoints: (points: Point[]) => Point[];
14
14
  export declare const getSimplePolyline: (sPoint: Point, tPoint: Point) => Point[];
15
15
  export declare const getExpandedBBox: (bbox: BoxBounds, offset: number) => BoxBounds;
16
16
  export declare const pointDirection: (point: Point, bbox: BoxBounds) => Direction;
17
+ /**
18
+ * 计算扩展包围盒上的相邻点(起点或终点的下一个/上一个拐点)
19
+ * - 使用原始节点 bbox 来判定点相对中心的方向,避免 offset 扩展后宽高改变导致方向误判
20
+ * - 若 start 相对中心为水平方向,则返回扩展盒在 x 上的边界,y 保持不变
21
+ * - 若为垂直方向,则返回扩展盒在 y 上的边界,x 保持不变
22
+ * @param expendBBox 扩展后的包围盒(包含 offset)
23
+ * @param bbox 原始节点包围盒(用于正确的方向判定)
24
+ * @param point 起点或终点坐标
25
+ */
17
26
  export declare const getExpandedBBoxPoint: (expendBBox: BoxBounds, bbox: BoxBounds, point: Point) => Point;
18
27
  export declare const mergeBBox: (b1: BoxBounds, b2: BoxBounds) => BoxBounds;
19
28
  export declare const getBBoxOfPoints: (points?: Point[], offset?: number) => BoxBounds;
@@ -32,10 +41,36 @@ export declare const rebuildPath: (pathPoints: Point[], pointById: PolyPointMap,
32
41
  export declare const removeClosePointFromOpenList: (arr: Point[], item: Point) => void;
33
42
  export declare const isSegmentsIntersected: (p0: Point, p1: Point, p2: Point, p3: Point) => boolean;
34
43
  export declare const isSegmentCrossingBBox: (p1: Point, p2: Point, bbox: BoxBounds) => boolean;
44
+ /**
45
+ * 基于轴对齐规则获取某点的相邻可连通点(不穿越节点)
46
+ * - 仅考虑 x 或 y 相同的候选点,保证严格水平/垂直
47
+ * - 使用 isSegmentCrossingBBox 校验线段不穿越源/目标节点
48
+ */
35
49
  export declare const getNextNeighborPoints: (points: Point[], point: Point, bbox1: BoxBounds, bbox2: BoxBounds) => Point[];
50
+ /**
51
+ * 使用 A* + 曼哈顿启发式在候选点图上查找正交路径
52
+ * - 开放集/关闭集管理遍历
53
+ * - gScore 为累计实际代价,fScore = gScore + 启发式
54
+ * - 邻居仅为与当前点 x 或 y 相同且不穿越节点的点
55
+ * 参考:https://zh.wikipedia.org/wiki/A*%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95
56
+ */
36
57
  export declare const pathFinder: (points: Point[], start: Point, goal: Point, sBBox: BoxBounds, tBBox: BoxBounds, os: Point, ot: Point) => Point[];
37
58
  export declare const getBoxByOriginNode: (node: BaseNodeModel) => BoxBounds;
59
+ /**
60
+ * 去除共线冗余中间点,保持每条直线段仅保留两端点
61
+ * - 若三点在同一水平线或同一垂直线,移除中间点
62
+ */
38
63
  export declare const pointFilter: (points: Point[]) => Point[];
64
+ /**
65
+ * 计算折线点(正交候选点 + A* 路径)
66
+ * 步骤:
67
+ * 1) 取源/目标节点的扩展包围盒与相邻点 sPoint/tPoint
68
+ * 2) 若两个扩展盒重合,使用简单路径 getSimplePoints
69
+ * 3) 构造 lineBBox/sMixBBox/tMixBBox,并收集其角点与中心交点
70
+ * 4) 过滤掉落在两个扩展盒内部的点,形成 connectPoints
71
+ * 5) 以 sPoint/tPoint 为起止,用 A* 查找路径
72
+ * 6) 拼入原始 start/end,并用 pointFilter 去除冗余共线点
73
+ */
39
74
  export declare const getPolylinePoints: (start: Point, end: Point, sNode: BaseNodeModel, tNode: BaseNodeModel, offset: number) => Point[];
40
75
  /**
41
76
  * 获取折线中最长的一个线
@@ -47,6 +82,10 @@ export declare const isSegmentsCrossNode: (start: Point, end: Point, node: BaseN
47
82
  export declare const getCrossPointInRect: (start: Point, end: Point, node: BaseNodeModel) => Point | false | undefined;
48
83
  export declare const segmentDirection: (start: Point, end: Point) => Direction | undefined;
49
84
  export declare const points2PointsList: (points: string) => Point[];
85
+ /**
86
+ * 当扩展 bbox 重合时的简化拐点计算
87
+ * - 根据起止段的方向(水平/垂直)插入 1~2 个中间点,避免折线重合与穿越
88
+ */
50
89
  export declare const getSimplePoints: (start: Point, end: Point, sPoint: Point, tPoint: Point) => Point[];
51
90
  export declare const getBytesLength: (word: string) => number;
52
91
  export declare const getTextWidth: (text: string, font: string) => number;