Skip to content

Commit 37a2132

Browse files
committed
feat: 折线增加路径正交矫正逻辑&offset支持根据properties.offset的变化实时更新路径
1 parent 30e3f4d commit 37a2132

1 file changed

Lines changed: 159 additions & 16 deletions

File tree

packages/core/src/model/edge/PolylineEdgeModel.ts

Lines changed: 159 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,154 @@ export class PolylineEdgeModel extends BaseEdgeModel {
3434
@observable dbClickPosition?: Point
3535

3636
initEdgeData(data: LogicFlow.EdgeConfig): void {
37-
this.offset = get(data, 'properties.offset', 30)
37+
const providedOffset = get(data, 'properties.offset')
38+
// 当用户未传入 offset 时,按“箭头与折线重叠长度 + 5”作为默认值
39+
// 其中“重叠长度”采用箭头样式中的 offset(沿边方向的长度)
40+
this.offset =
41+
typeof providedOffset === 'number'
42+
? providedOffset
43+
: this.getDefaultOffset()
3844
if (data.pointsList) {
39-
this.pointsList = data.pointsList
45+
const corrected = this.orthogonalizePath(data.pointsList)
46+
;(data as any).pointsList = corrected
47+
this.pointsList = corrected
4048
}
4149
super.initEdgeData(data)
4250
}
4351

52+
setAttributes() {
53+
const { offset: newOffset } = this.properties
54+
if (newOffset && newOffset !== this.offset) {
55+
this.offset = newOffset
56+
this.updatePoints()
57+
}
58+
}
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+
174+
/**
175+
* 计算默认 offset:箭头与折线重叠长度 + 5
176+
* 重叠长度采用箭头样式中的 offset(沿边方向的长度)
177+
*/
178+
private getDefaultOffset(): number {
179+
const arrowStyle = this.getArrowStyle()
180+
const arrowOverlap =
181+
typeof arrowStyle.offset === 'number' ? arrowStyle.offset : 0
182+
return arrowOverlap + 5
183+
}
184+
44185
getEdgeStyle() {
45186
const { polyline } = this.graphModel.theme
46187
const style = super.getEdgeStyle()
@@ -319,7 +460,7 @@ export class PolylineEdgeModel extends BaseEdgeModel {
319460
}
320461

321462
updatePath(pointList: Point[]) {
322-
this.pointsList = pointList
463+
this.pointsList = this.orthogonalizePath(pointList)
323464
this.points = this.getPath(this.pointsList)
324465
}
325466

@@ -362,7 +503,7 @@ export class PolylineEdgeModel extends BaseEdgeModel {
362503
this.targetNode,
363504
this.offset || 0,
364505
)
365-
this.pointsList = pointsList
506+
this.pointsList = this.orthogonalizePath(pointsList)
366507
this.points = pointsList.map((point) => `${point.x},${point.y}`).join(' ')
367508
}
368509

@@ -651,18 +792,20 @@ export class PolylineEdgeModel extends BaseEdgeModel {
651792
sourceNode: BaseNodeModel
652793
targetNode: BaseNodeModel
653794
}) {
654-
this.pointsList = getPolylinePoints(
655-
{
656-
x: startPoint.x,
657-
y: startPoint.y,
658-
},
659-
{
660-
x: endPoint.x,
661-
y: endPoint.y,
662-
},
663-
sourceNode,
664-
targetNode,
665-
this.offset || 0,
795+
this.pointsList = this.orthogonalizePath(
796+
getPolylinePoints(
797+
{
798+
x: startPoint.x,
799+
y: startPoint.y,
800+
},
801+
{
802+
x: endPoint.x,
803+
y: endPoint.y,
804+
},
805+
sourceNode,
806+
targetNode,
807+
this.offset || 0,
808+
),
666809
)
667810

668811
this.initPoints()

0 commit comments

Comments
 (0)