@logicflow/core 2.2.0-alpha.4 → 2.2.0-alpha.6
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.
- package/.turbo/turbo-build$colon$dev.log +2 -2
- package/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +15 -0
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/es/LogicFlow.js +0 -1
- package/es/model/GraphModel.js +1 -1
- package/es/model/edge/PolylineEdgeModel.d.ts +1 -0
- package/es/model/edge/PolylineEdgeModel.js +113 -6
- package/es/util/edge.d.ts +39 -0
- package/es/util/edge.js +41 -7
- package/es/view/edge/BaseEdge.js +9 -0
- package/es/view/node/BaseNode.js +2 -3
- package/es/view/overlay/OutlineOverlay.js +1 -1
- package/lib/LogicFlow.js +0 -1
- package/lib/model/GraphModel.js +1 -1
- package/lib/model/edge/PolylineEdgeModel.d.ts +1 -0
- package/lib/model/edge/PolylineEdgeModel.js +113 -6
- package/lib/util/edge.d.ts +39 -0
- package/lib/util/edge.js +41 -7
- package/lib/view/edge/BaseEdge.js +9 -0
- package/lib/view/node/BaseNode.js +1 -2
- package/lib/view/overlay/OutlineOverlay.js +1 -1
- package/package.json +1 -1
- package/src/LogicFlow.tsx +0 -1
- package/src/model/GraphModel.ts +2 -1
- package/src/model/edge/PolylineEdgeModel.ts +136 -16
- package/src/util/edge.ts +40 -7
- package/src/view/edge/BaseEdge.tsx +10 -0
- package/src/view/node/BaseNode.tsx +2 -3
- package/src/view/overlay/OutlineOverlay.tsx +1 -1
- package/stats.html +1 -1
package/lib/util/edge.js
CHANGED
|
@@ -122,6 +122,15 @@ var pointDirection = function (point, bbox) {
|
|
|
122
122
|
};
|
|
123
123
|
exports.pointDirection = pointDirection;
|
|
124
124
|
/* 获取扩展图形上的点,即起始终点相邻的点,上一个或者下一个节点 */
|
|
125
|
+
/**
|
|
126
|
+
* 计算扩展包围盒上的相邻点(起点或终点的下一个/上一个拐点)
|
|
127
|
+
* - 使用原始节点 bbox 来判定点相对中心的方向,避免 offset 扩展后宽高改变导致方向误判
|
|
128
|
+
* - 若 start 相对中心为水平方向,则返回扩展盒在 x 上的边界,y 保持不变
|
|
129
|
+
* - 若为垂直方向,则返回扩展盒在 y 上的边界,x 保持不变
|
|
130
|
+
* @param expendBBox 扩展后的包围盒(包含 offset)
|
|
131
|
+
* @param bbox 原始节点包围盒(用于正确的方向判定)
|
|
132
|
+
* @param point 起点或终点坐标
|
|
133
|
+
*/
|
|
125
134
|
var getExpandedBBoxPoint = function (expendBBox, bbox, point) {
|
|
126
135
|
// https://github.com/didi/LogicFlow/issues/817
|
|
127
136
|
// 没有修复前传入的参数bbox实际是expendBBox
|
|
@@ -337,7 +346,11 @@ var isSegmentCrossingBBox = function (p1, p2, bbox) {
|
|
|
337
346
|
(0, exports.isSegmentsIntersected)(p1, p2, pc, pd));
|
|
338
347
|
};
|
|
339
348
|
exports.isSegmentCrossingBBox = isSegmentCrossingBBox;
|
|
340
|
-
|
|
349
|
+
/**
|
|
350
|
+
* 基于轴对齐规则获取某点的相邻可连通点(不穿越节点)
|
|
351
|
+
* - 仅考虑 x 或 y 相同的候选点,保证严格水平/垂直
|
|
352
|
+
* - 使用 isSegmentCrossingBBox 校验线段不穿越源/目标节点
|
|
353
|
+
*/
|
|
341
354
|
var getNextNeighborPoints = function (points, point, bbox1, bbox2) {
|
|
342
355
|
var neighbors = [];
|
|
343
356
|
points.forEach(function (p) {
|
|
@@ -353,9 +366,12 @@ var getNextNeighborPoints = function (points, point, bbox1, bbox2) {
|
|
|
353
366
|
return (0, exports.filterRepeatPoints)(neighbors);
|
|
354
367
|
};
|
|
355
368
|
exports.getNextNeighborPoints = getNextNeighborPoints;
|
|
356
|
-
|
|
357
|
-
*
|
|
358
|
-
*
|
|
369
|
+
/**
|
|
370
|
+
* 使用 A* + 曼哈顿启发式在候选点图上查找正交路径
|
|
371
|
+
* - 开放集/关闭集管理遍历
|
|
372
|
+
* - gScore 为累计实际代价,fScore = gScore + 启发式
|
|
373
|
+
* - 邻居仅为与当前点 x 或 y 相同且不穿越节点的点
|
|
374
|
+
* 参考:https://zh.wikipedia.org/wiki/A*%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95
|
|
359
375
|
*/
|
|
360
376
|
var pathFinder = function (points, start, goal, sBBox, tBBox, os, ot) {
|
|
361
377
|
// 定义已经遍历过的点
|
|
@@ -396,6 +412,7 @@ var pathFinder = function (points, start, goal, sBBox, tBBox, os, ot) {
|
|
|
396
412
|
(0, exports.removeClosePointFromOpenList)(openSet, current);
|
|
397
413
|
closedSet.push(current);
|
|
398
414
|
(0, exports.getNextNeighborPoints)(points, current, sBBox, tBBox).forEach(function (neighbor) {
|
|
415
|
+
var _a;
|
|
399
416
|
if (closedSet.indexOf(neighbor) !== -1) {
|
|
400
417
|
return;
|
|
401
418
|
}
|
|
@@ -403,7 +420,8 @@ var pathFinder = function (points, start, goal, sBBox, tBBox, os, ot) {
|
|
|
403
420
|
openSet.push(neighbor);
|
|
404
421
|
}
|
|
405
422
|
if ((current === null || current === void 0 ? void 0 : current.id) && (neighbor === null || neighbor === void 0 ? void 0 : neighbor.id)) {
|
|
406
|
-
|
|
423
|
+
// 修复:累计代价应基于 gScore[current] 而非 fScore[current]
|
|
424
|
+
var tentativeGScore = ((_a = gScore[current.id]) !== null && _a !== void 0 ? _a : 0) + (0, exports.estimateDistance)(current, neighbor);
|
|
407
425
|
if (gScore[neighbor.id] && tentativeGScore >= gScore[neighbor.id]) {
|
|
408
426
|
return;
|
|
409
427
|
}
|
|
@@ -427,7 +445,10 @@ var getBoxByOriginNode = function (node) {
|
|
|
427
445
|
return (0, _1.getNodeBBox)(node);
|
|
428
446
|
};
|
|
429
447
|
exports.getBoxByOriginNode = getBoxByOriginNode;
|
|
430
|
-
|
|
448
|
+
/**
|
|
449
|
+
* 去除共线冗余中间点,保持每条直线段仅保留两端点
|
|
450
|
+
* - 若三点在同一水平线或同一垂直线,移除中间点
|
|
451
|
+
*/
|
|
431
452
|
var pointFilter = function (points) {
|
|
432
453
|
var i = 1;
|
|
433
454
|
while (i < points.length - 1) {
|
|
@@ -445,7 +466,16 @@ var pointFilter = function (points) {
|
|
|
445
466
|
return points;
|
|
446
467
|
};
|
|
447
468
|
exports.pointFilter = pointFilter;
|
|
448
|
-
|
|
469
|
+
/**
|
|
470
|
+
* 计算折线点(正交候选点 + A* 路径)
|
|
471
|
+
* 步骤:
|
|
472
|
+
* 1) 取源/目标节点的扩展包围盒与相邻点 sPoint/tPoint
|
|
473
|
+
* 2) 若两个扩展盒重合,使用简单路径 getSimplePoints
|
|
474
|
+
* 3) 构造 lineBBox/sMixBBox/tMixBBox,并收集其角点与中心交点
|
|
475
|
+
* 4) 过滤掉落在两个扩展盒内部的点,形成 connectPoints
|
|
476
|
+
* 5) 以 sPoint/tPoint 为起止,用 A* 查找路径
|
|
477
|
+
* 6) 拼入原始 start/end,并用 pointFilter 去除冗余共线点
|
|
478
|
+
*/
|
|
449
479
|
var getPolylinePoints = function (start, end, sNode, tNode, offset) {
|
|
450
480
|
var sBBox = (0, exports.getBoxByOriginNode)(sNode);
|
|
451
481
|
var tBBox = (0, exports.getBoxByOriginNode)(tNode);
|
|
@@ -589,6 +619,10 @@ var points2PointsList = function (points) {
|
|
|
589
619
|
return pointsList;
|
|
590
620
|
};
|
|
591
621
|
exports.points2PointsList = points2PointsList;
|
|
622
|
+
/**
|
|
623
|
+
* 当扩展 bbox 重合时的简化拐点计算
|
|
624
|
+
* - 根据起止段的方向(水平/垂直)插入 1~2 个中间点,避免折线重合与穿越
|
|
625
|
+
*/
|
|
592
626
|
var getSimplePoints = function (start, end, sPoint, tPoint) {
|
|
593
627
|
var points = [];
|
|
594
628
|
// start,sPoint的方向,水平或者垂直,即路径第一条线段的方向
|
|
@@ -68,6 +68,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
68
68
|
exports.BaseEdge = void 0;
|
|
69
69
|
var jsx_runtime_1 = require("preact/jsx-runtime");
|
|
70
70
|
var compat_1 = require("preact/compat");
|
|
71
|
+
var lodash_es_1 = require("lodash-es");
|
|
71
72
|
var shape_1 = require("../shape");
|
|
72
73
|
var text_1 = require("../text");
|
|
73
74
|
var constant_1 = require("../../constant");
|
|
@@ -233,6 +234,14 @@ var BaseEdge = /** @class */ (function (_super) {
|
|
|
233
234
|
e: e,
|
|
234
235
|
position: position,
|
|
235
236
|
});
|
|
237
|
+
// 复制粘贴后会出现点击边时,边会失去焦点的问题,这里手动让边获焦以解决这个问题
|
|
238
|
+
var el_1 = e.currentTarget;
|
|
239
|
+
var rAF = !(0, lodash_es_1.isNil)(window) && (0, lodash_es_1.isFunction)(window.requestAnimationFrame)
|
|
240
|
+
? window.requestAnimationFrame.bind(window)
|
|
241
|
+
: function (fn) { return setTimeout(fn, 0); };
|
|
242
|
+
rAF(function () {
|
|
243
|
+
el_1.focus();
|
|
244
|
+
});
|
|
236
245
|
}
|
|
237
246
|
var editConfigModel = graphModel.editConfigModel;
|
|
238
247
|
graphModel.selectEdgeById(model.id, (0, util_1.isMultipleSelect)(e, editConfigModel));
|
|
@@ -248,8 +248,7 @@ var BaseNode = /** @class */ (function (_super) {
|
|
|
248
248
|
graphModel.eventCenter.emit(constant_1.EventType.NODE_CLICK, eventOptions);
|
|
249
249
|
// 复制粘贴后会出现点击节点时,节点会失去焦点的问题,这里手动让节点获焦以解决这个问题
|
|
250
250
|
var el_1 = e.currentTarget;
|
|
251
|
-
var rAF =
|
|
252
|
-
typeof window.requestAnimationFrame === 'function'
|
|
251
|
+
var rAF = !(0, lodash_es_1.isNil)(window) && (0, lodash_es_1.isFunction)(window.requestAnimationFrame)
|
|
253
252
|
? window.requestAnimationFrame.bind(window)
|
|
254
253
|
: function (fn) { return setTimeout(fn, 0); };
|
|
255
254
|
rAF(function () {
|
|
@@ -60,7 +60,7 @@ var OutlineOverlay = /** @class */ (function (_super) {
|
|
|
60
60
|
var isHovered = element.isHovered, isSelected = element.isSelected, x = element.x, y = element.y, width = element.width, height = element.height;
|
|
61
61
|
if ((nodeSelectedOutline && isSelected) ||
|
|
62
62
|
(hoverOutline && isHovered)) {
|
|
63
|
-
var style_1 = element.getOutlineStyle();
|
|
63
|
+
var style_1 = element.getOutlineStyle() || {};
|
|
64
64
|
var attributes_1 = {};
|
|
65
65
|
Object.keys(style_1).forEach(function (key) {
|
|
66
66
|
if (key !== 'hover') {
|
package/package.json
CHANGED
package/src/LogicFlow.tsx
CHANGED
|
@@ -1445,7 +1445,6 @@ export class LogicFlow {
|
|
|
1445
1445
|
this.graphModel.destroy()
|
|
1446
1446
|
this.tool.destroy()
|
|
1447
1447
|
this.history.destroy()
|
|
1448
|
-
clearThemeMode()
|
|
1449
1448
|
for (const extensionName in this.extension) {
|
|
1450
1449
|
const extensionInstance = this.extension[extensionName]
|
|
1451
1450
|
if ('destroy' in extensionInstance) {
|
package/src/model/GraphModel.ts
CHANGED
|
@@ -42,7 +42,9 @@ export class PolylineEdgeModel extends BaseEdgeModel {
|
|
|
42
42
|
? providedOffset
|
|
43
43
|
: this.getDefaultOffset()
|
|
44
44
|
if (data.pointsList) {
|
|
45
|
-
|
|
45
|
+
const corrected = this.orthogonalizePath(data.pointsList)
|
|
46
|
+
;(data as any).pointsList = corrected
|
|
47
|
+
this.pointsList = corrected
|
|
46
48
|
}
|
|
47
49
|
super.initEdgeData(data)
|
|
48
50
|
}
|
|
@@ -55,6 +57,120 @@ export class PolylineEdgeModel extends BaseEdgeModel {
|
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
orthogonalizePath(points: Point[]): Point[] {
|
|
61
|
+
// 输入非法或不足两点时直接返回副本
|
|
62
|
+
if (!Array.isArray(points) || points.length < 2) {
|
|
63
|
+
return points
|
|
64
|
+
}
|
|
65
|
+
// pushUnique: 向数组中添加唯一点,避免重复
|
|
66
|
+
const pushUnique = (arr: Point[], p: Point) => {
|
|
67
|
+
const last = arr[arr.length - 1]
|
|
68
|
+
if (!last || last.x !== p.x || last.y !== p.y) {
|
|
69
|
+
arr.push({ x: p.x, y: p.y })
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// isAxisAligned: 检查两点是否在同一条轴上
|
|
73
|
+
const isAxisAligned = (a: Point, b: Point) => a.x === b.x || a.y === b.y
|
|
74
|
+
// manhattanDistance: 计算两点在曼哈顿距离上的距离
|
|
75
|
+
const manhattanDistance = (a: Point, b: Point) =>
|
|
76
|
+
Math.abs(a.x - b.x) + Math.abs(a.y - b.y)
|
|
77
|
+
|
|
78
|
+
// 1) 生成严格正交路径,尽量延续前一段方向以减少折点
|
|
79
|
+
const orthogonal: Point[] = []
|
|
80
|
+
pushUnique(orthogonal, points[0])
|
|
81
|
+
// previousDirection: 记录前一段的方向,用于判断当前段的PreferredCorner
|
|
82
|
+
let previousDirection: SegmentDirection | undefined
|
|
83
|
+
// 遍历所有点对,生成正交路径
|
|
84
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
85
|
+
const current = orthogonal[orthogonal.length - 1]
|
|
86
|
+
const next = points[i + 1]
|
|
87
|
+
if (!current || !next) continue
|
|
88
|
+
|
|
89
|
+
if (isAxisAligned(current, next)) {
|
|
90
|
+
pushUnique(orthogonal, next)
|
|
91
|
+
previousDirection =
|
|
92
|
+
current.x === next.x
|
|
93
|
+
? SegmentDirection.VERTICAL
|
|
94
|
+
: SegmentDirection.HORIZONTAL
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const cornerHV: Point = { x: next.x, y: current.y }
|
|
99
|
+
const cornerVH: Point = { x: current.x, y: next.y }
|
|
100
|
+
|
|
101
|
+
// 根据前一段的方向,优先选择能延续该方向的拐角点,以减少折点数量;
|
|
102
|
+
// 若前一段为垂直方向,则优先选择垂直-水平拐角(cornerVH);
|
|
103
|
+
// 若前一段为水平方向,则优先选择水平-垂直拐角(cornerHV);
|
|
104
|
+
// 若前一段无方向(初始情况),则比较两个拐角的曼哈顿距离,选更近者。
|
|
105
|
+
const preferredCorner =
|
|
106
|
+
previousDirection === SegmentDirection.VERTICAL
|
|
107
|
+
? cornerVH
|
|
108
|
+
: previousDirection === SegmentDirection.HORIZONTAL
|
|
109
|
+
? cornerHV
|
|
110
|
+
: manhattanDistance(current, cornerHV) <=
|
|
111
|
+
manhattanDistance(current, cornerVH)
|
|
112
|
+
? cornerHV
|
|
113
|
+
: cornerVH
|
|
114
|
+
|
|
115
|
+
if (preferredCorner.x !== current.x || preferredCorner.y !== current.y) {
|
|
116
|
+
pushUnique(orthogonal, preferredCorner)
|
|
117
|
+
}
|
|
118
|
+
pushUnique(orthogonal, next)
|
|
119
|
+
|
|
120
|
+
const a = orthogonal[orthogonal.length - 2]
|
|
121
|
+
const b = orthogonal[orthogonal.length - 1]
|
|
122
|
+
previousDirection =
|
|
123
|
+
a && b
|
|
124
|
+
? a.x === b.x
|
|
125
|
+
? SegmentDirection.VERTICAL
|
|
126
|
+
: SegmentDirection.HORIZONTAL
|
|
127
|
+
: previousDirection
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 2) 去除冗余共线中间点
|
|
131
|
+
const simplified: Point[] = []
|
|
132
|
+
for (let i = 0; i < orthogonal.length; i++) {
|
|
133
|
+
const prev = orthogonal[i - 1]
|
|
134
|
+
const curr = orthogonal[i]
|
|
135
|
+
const next = orthogonal[i + 1]
|
|
136
|
+
// 如果当前点与前一个点和后一个点在同一条水平线或垂直线上,则跳过该点,去除冗余的共线中间点
|
|
137
|
+
if (
|
|
138
|
+
prev &&
|
|
139
|
+
curr &&
|
|
140
|
+
next &&
|
|
141
|
+
((prev.x === curr.x && curr.x === next.x) || // 水平共线
|
|
142
|
+
(prev.y === curr.y && curr.y === next.y)) // 垂直共线
|
|
143
|
+
) {
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
pushUnique(simplified, curr)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 3) 保留原始起点与终点位置
|
|
150
|
+
if (simplified.length >= 2) {
|
|
151
|
+
simplified[0] = { x: points[0].x, y: points[0].y }
|
|
152
|
+
simplified[simplified.length - 1] = {
|
|
153
|
+
x: points[points.length - 1].x,
|
|
154
|
+
y: points[points.length - 1].y,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 4) 结果校验:任意相邻段都必须为水平/垂直;失败则退化为起止两点
|
|
159
|
+
const isOrthogonal =
|
|
160
|
+
simplified.length < 2 ||
|
|
161
|
+
simplified.every((_, idx, arr) => {
|
|
162
|
+
if (idx === 0) return true
|
|
163
|
+
return isAxisAligned(arr[idx - 1], arr[idx])
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
return isOrthogonal
|
|
167
|
+
? simplified
|
|
168
|
+
: [
|
|
169
|
+
{ x: points[0].x, y: points[0].y },
|
|
170
|
+
{ x: points[points.length - 1].x, y: points[points.length - 1].y },
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
|
|
58
174
|
/**
|
|
59
175
|
* 计算默认 offset:箭头与折线重叠长度 + 5
|
|
60
176
|
* 重叠长度采用箭头样式中的 offset(沿边方向的长度)
|
|
@@ -344,7 +460,7 @@ export class PolylineEdgeModel extends BaseEdgeModel {
|
|
|
344
460
|
}
|
|
345
461
|
|
|
346
462
|
updatePath(pointList: Point[]) {
|
|
347
|
-
this.pointsList = pointList
|
|
463
|
+
this.pointsList = this.orthogonalizePath(pointList)
|
|
348
464
|
this.points = this.getPath(this.pointsList)
|
|
349
465
|
}
|
|
350
466
|
|
|
@@ -387,8 +503,10 @@ export class PolylineEdgeModel extends BaseEdgeModel {
|
|
|
387
503
|
this.targetNode,
|
|
388
504
|
this.offset || 0,
|
|
389
505
|
)
|
|
390
|
-
this.pointsList = pointsList
|
|
391
|
-
this.points = pointsList
|
|
506
|
+
this.pointsList = this.orthogonalizePath(pointsList)
|
|
507
|
+
this.points = this.pointsList
|
|
508
|
+
.map((point) => `${point.x},${point.y}`)
|
|
509
|
+
.join(' ')
|
|
392
510
|
}
|
|
393
511
|
|
|
394
512
|
@action
|
|
@@ -676,18 +794,20 @@ export class PolylineEdgeModel extends BaseEdgeModel {
|
|
|
676
794
|
sourceNode: BaseNodeModel
|
|
677
795
|
targetNode: BaseNodeModel
|
|
678
796
|
}) {
|
|
679
|
-
this.pointsList =
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
797
|
+
this.pointsList = this.orthogonalizePath(
|
|
798
|
+
getPolylinePoints(
|
|
799
|
+
{
|
|
800
|
+
x: startPoint.x,
|
|
801
|
+
y: startPoint.y,
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
x: endPoint.x,
|
|
805
|
+
y: endPoint.y,
|
|
806
|
+
},
|
|
807
|
+
sourceNode,
|
|
808
|
+
targetNode,
|
|
809
|
+
this.offset || 0,
|
|
810
|
+
),
|
|
691
811
|
)
|
|
692
812
|
|
|
693
813
|
this.initPoints()
|
package/src/util/edge.ts
CHANGED
|
@@ -107,6 +107,15 @@ export const pointDirection = (point: Point, bbox: BoxBounds): Direction => {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/* 获取扩展图形上的点,即起始终点相邻的点,上一个或者下一个节点 */
|
|
110
|
+
/**
|
|
111
|
+
* 计算扩展包围盒上的相邻点(起点或终点的下一个/上一个拐点)
|
|
112
|
+
* - 使用原始节点 bbox 来判定点相对中心的方向,避免 offset 扩展后宽高改变导致方向误判
|
|
113
|
+
* - 若 start 相对中心为水平方向,则返回扩展盒在 x 上的边界,y 保持不变
|
|
114
|
+
* - 若为垂直方向,则返回扩展盒在 y 上的边界,x 保持不变
|
|
115
|
+
* @param expendBBox 扩展后的包围盒(包含 offset)
|
|
116
|
+
* @param bbox 原始节点包围盒(用于正确的方向判定)
|
|
117
|
+
* @param point 起点或终点坐标
|
|
118
|
+
*/
|
|
110
119
|
export const getExpandedBBoxPoint = (
|
|
111
120
|
expendBBox: BoxBounds,
|
|
112
121
|
bbox: BoxBounds,
|
|
@@ -378,7 +387,11 @@ export const isSegmentCrossingBBox = (
|
|
|
378
387
|
)
|
|
379
388
|
}
|
|
380
389
|
|
|
381
|
-
|
|
390
|
+
/**
|
|
391
|
+
* 基于轴对齐规则获取某点的相邻可连通点(不穿越节点)
|
|
392
|
+
* - 仅考虑 x 或 y 相同的候选点,保证严格水平/垂直
|
|
393
|
+
* - 使用 isSegmentCrossingBBox 校验线段不穿越源/目标节点
|
|
394
|
+
*/
|
|
382
395
|
export const getNextNeighborPoints = (
|
|
383
396
|
points: Point[],
|
|
384
397
|
point: Point,
|
|
@@ -401,9 +414,12 @@ export const getNextNeighborPoints = (
|
|
|
401
414
|
return filterRepeatPoints(neighbors)
|
|
402
415
|
}
|
|
403
416
|
|
|
404
|
-
|
|
405
|
-
*
|
|
406
|
-
*
|
|
417
|
+
/**
|
|
418
|
+
* 使用 A* + 曼哈顿启发式在候选点图上查找正交路径
|
|
419
|
+
* - 开放集/关闭集管理遍历
|
|
420
|
+
* - gScore 为累计实际代价,fScore = gScore + 启发式
|
|
421
|
+
* - 邻居仅为与当前点 x 或 y 相同且不穿越节点的点
|
|
422
|
+
* 参考:https://zh.wikipedia.org/wiki/A*%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95
|
|
407
423
|
*/
|
|
408
424
|
export const pathFinder = (
|
|
409
425
|
points: Point[],
|
|
@@ -475,8 +491,9 @@ export const pathFinder = (
|
|
|
475
491
|
}
|
|
476
492
|
|
|
477
493
|
if (current?.id && neighbor?.id) {
|
|
494
|
+
// 修复:累计代价应基于 gScore[current] 而非 fScore[current]
|
|
478
495
|
const tentativeGScore =
|
|
479
|
-
|
|
496
|
+
(gScore[current.id] ?? 0) + estimateDistance(current, neighbor)
|
|
480
497
|
if (gScore[neighbor.id] && tentativeGScore >= gScore[neighbor.id]) {
|
|
481
498
|
return
|
|
482
499
|
}
|
|
@@ -495,7 +512,10 @@ export const pathFinder = (
|
|
|
495
512
|
export const getBoxByOriginNode = (node: BaseNodeModel): BoxBounds => {
|
|
496
513
|
return getNodeBBox(node)
|
|
497
514
|
}
|
|
498
|
-
|
|
515
|
+
/**
|
|
516
|
+
* 去除共线冗余中间点,保持每条直线段仅保留两端点
|
|
517
|
+
* - 若三点在同一水平线或同一垂直线,移除中间点
|
|
518
|
+
*/
|
|
499
519
|
export const pointFilter = (points: Point[]): Point[] => {
|
|
500
520
|
let i = 1
|
|
501
521
|
while (i < points.length - 1) {
|
|
@@ -514,7 +534,16 @@ export const pointFilter = (points: Point[]): Point[] => {
|
|
|
514
534
|
return points
|
|
515
535
|
}
|
|
516
536
|
|
|
517
|
-
|
|
537
|
+
/**
|
|
538
|
+
* 计算折线点(正交候选点 + A* 路径)
|
|
539
|
+
* 步骤:
|
|
540
|
+
* 1) 取源/目标节点的扩展包围盒与相邻点 sPoint/tPoint
|
|
541
|
+
* 2) 若两个扩展盒重合,使用简单路径 getSimplePoints
|
|
542
|
+
* 3) 构造 lineBBox/sMixBBox/tMixBBox,并收集其角点与中心交点
|
|
543
|
+
* 4) 过滤掉落在两个扩展盒内部的点,形成 connectPoints
|
|
544
|
+
* 5) 以 sPoint/tPoint 为起止,用 A* 查找路径
|
|
545
|
+
* 6) 拼入原始 start/end,并用 pointFilter 去除冗余共线点
|
|
546
|
+
*/
|
|
518
547
|
export const getPolylinePoints = (
|
|
519
548
|
start: Point,
|
|
520
549
|
end: Point,
|
|
@@ -700,6 +729,10 @@ export const points2PointsList = (points: string): Point[] => {
|
|
|
700
729
|
return pointsList
|
|
701
730
|
}
|
|
702
731
|
|
|
732
|
+
/**
|
|
733
|
+
* 当扩展 bbox 重合时的简化拐点计算
|
|
734
|
+
* - 根据起止段的方向(水平/垂直)插入 1~2 个中间点,避免折线重合与穿越
|
|
735
|
+
*/
|
|
703
736
|
export const getSimplePoints = (
|
|
704
737
|
start: Point,
|
|
705
738
|
end: Point,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createElement as h, Component, createRef } from 'preact/compat'
|
|
2
|
+
import { isFunction, isNil } from 'lodash-es'
|
|
2
3
|
import { Circle } from '../shape'
|
|
3
4
|
import { LineText } from '../text'
|
|
4
5
|
import LogicFlow from '../../LogicFlow'
|
|
@@ -565,6 +566,15 @@ export abstract class BaseEdge<P extends IProps> extends Component<
|
|
|
565
566
|
e,
|
|
566
567
|
position,
|
|
567
568
|
})
|
|
569
|
+
// 复制粘贴后会出现点击边时,边会失去焦点的问题,这里手动让边获焦以解决这个问题
|
|
570
|
+
const el = e.currentTarget as HTMLElement
|
|
571
|
+
const rAF =
|
|
572
|
+
!isNil(window) && isFunction(window.requestAnimationFrame)
|
|
573
|
+
? window.requestAnimationFrame.bind(window)
|
|
574
|
+
: (fn: () => void) => setTimeout(fn, 0)
|
|
575
|
+
rAF(() => {
|
|
576
|
+
el.focus()
|
|
577
|
+
})
|
|
568
578
|
}
|
|
569
579
|
const { editConfigModel } = graphModel
|
|
570
580
|
graphModel.selectEdgeById(model.id, isMultipleSelect(e, editConfigModel))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createElement as h, Component } from 'preact/compat'
|
|
2
2
|
import { reaction, IReactionDisposer } from 'mobx'
|
|
3
|
-
import { map } from 'lodash-es'
|
|
3
|
+
import { map, isFunction, isNil } from 'lodash-es'
|
|
4
4
|
import Anchor from '../Anchor'
|
|
5
5
|
import { BaseText } from '../text'
|
|
6
6
|
import LogicFlow from '../../LogicFlow'
|
|
@@ -410,8 +410,7 @@ export abstract class BaseNode<P extends IProps = IProps> extends Component<
|
|
|
410
410
|
// 复制粘贴后会出现点击节点时,节点会失去焦点的问题,这里手动让节点获焦以解决这个问题
|
|
411
411
|
const el = e.currentTarget as HTMLElement
|
|
412
412
|
const rAF =
|
|
413
|
-
|
|
414
|
-
typeof window.requestAnimationFrame === 'function'
|
|
413
|
+
!isNil(window) && isFunction(window.requestAnimationFrame)
|
|
415
414
|
? window.requestAnimationFrame.bind(window)
|
|
416
415
|
: (fn: () => void) => setTimeout(fn, 0)
|
|
417
416
|
rAF(() => {
|
|
@@ -51,7 +51,7 @@ export class OutlineOverlay extends Component<IProps> {
|
|
|
51
51
|
(nodeSelectedOutline && isSelected) ||
|
|
52
52
|
(hoverOutline && isHovered)
|
|
53
53
|
) {
|
|
54
|
-
const style = element.getOutlineStyle()
|
|
54
|
+
const style = element.getOutlineStyle() || {}
|
|
55
55
|
let attributes = {}
|
|
56
56
|
Object.keys(style).forEach((key) => {
|
|
57
57
|
if (key !== 'hover') {
|