@leafer-in/linker 2.3.2 → 2.3.3
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/dist/linker.cjs +444 -49
- package/dist/linker.esm.js +445 -50
- package/dist/linker.esm.min.js +1 -1
- package/dist/linker.esm.min.js.map +1 -1
- package/dist/linker.js +420 -49
- package/dist/linker.min.cjs +1 -1
- package/dist/linker.min.cjs.map +1 -1
- package/dist/linker.min.js +1 -1
- package/dist/linker.min.js.map +1 -1
- package/package.json +1 -1
- package/src/Linker.ts +72 -57
- package/src/helper/drawPolyline.ts +456 -0
- package/types/index.d.ts +2 -1
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { IDirection, IPointData, IBoundsData } from '@leafer-ui/interface'
|
|
2
|
+
import { AroundHelper, PathCommandDataHelper } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
const { directionData } = AroundHelper
|
|
5
|
+
const { moveTo, lineTo } = PathCommandDataHelper
|
|
6
|
+
|
|
7
|
+
const POLYLINE_X = 0, POLYLINE_Y = 1
|
|
8
|
+
const POLYLINE_EPSILON = 0.001
|
|
9
|
+
const POLYLINE_MIN_SEGMENT = 4
|
|
10
|
+
const POLYLINE_TURN_WEIGHT = 1.5
|
|
11
|
+
|
|
12
|
+
function getPolylineDirectionIndex(direction: IDirection): number {
|
|
13
|
+
switch (direction) {
|
|
14
|
+
case 'top-left': return 0
|
|
15
|
+
case 'top': return 1
|
|
16
|
+
case 'top-right': return 2
|
|
17
|
+
case 'right': return 3
|
|
18
|
+
case 'bottom-right': return 4
|
|
19
|
+
case 'bottom': return 5
|
|
20
|
+
case 'bottom-left': return 6
|
|
21
|
+
case 'left': return 7
|
|
22
|
+
default: return 8
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getPolylinePorts(direction: IDirection, point: IPointData, target: IPointData, bounds: IBoundsData | undefined, targetBounds: IBoundsData | undefined, ports: number[]): number {
|
|
27
|
+
const data = directionData[getPolylineDirectionIndex(direction)]
|
|
28
|
+
const dx = Math.sign(data.x - 0.5), dy = Math.sign(data.y - 0.5)
|
|
29
|
+
const cx = bounds ? bounds.x + bounds.width / 2 : point.x
|
|
30
|
+
const cy = bounds ? bounds.y + bounds.height / 2 : point.y
|
|
31
|
+
const tx = targetBounds ? targetBounds.x + targetBounds.width / 2 : target.x
|
|
32
|
+
const ty = targetBounds ? targetBounds.y + targetBounds.height / 2 : target.y
|
|
33
|
+
const deltaX = tx - cx, deltaY = ty - cy
|
|
34
|
+
const xSign = dx || (deltaX >= 0 ? 1 : -1)
|
|
35
|
+
const ySign = dy || (deltaY >= 0 ? 1 : -1)
|
|
36
|
+
const xPort = POLYLINE_X * 2 + (xSign > 0 ? 1 : 0)
|
|
37
|
+
const yPort = POLYLINE_Y * 2 + (ySign > 0 ? 1 : 0)
|
|
38
|
+
|
|
39
|
+
if (dx && !dy) {
|
|
40
|
+
ports[0] = xPort
|
|
41
|
+
return 1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (dy && !dx) {
|
|
45
|
+
ports[0] = yPort
|
|
46
|
+
return 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (Math.abs(deltaX) >= Math.abs(deltaY)) {
|
|
50
|
+
ports[0] = xPort
|
|
51
|
+
ports[1] = yPort
|
|
52
|
+
} else {
|
|
53
|
+
ports[0] = yPort
|
|
54
|
+
ports[1] = xPort
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return 2
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getPolylineExit(point: IPointData, bounds: IBoundsData | undefined, port: number, clearance: number, result: number[]): void {
|
|
61
|
+
const axis = port >> 1, sign = port & 1 ? 1 : -1
|
|
62
|
+
|
|
63
|
+
if (axis === POLYLINE_X) {
|
|
64
|
+
const boundary = bounds ? sign > 0 ? bounds.x + bounds.width : bounds.x : point.x
|
|
65
|
+
result[0] = boundary + sign * clearance
|
|
66
|
+
result[1] = point.y
|
|
67
|
+
} else {
|
|
68
|
+
const boundary = bounds ? sign > 0 ? bounds.y + bounds.height : bounds.y : point.y
|
|
69
|
+
result[0] = point.x
|
|
70
|
+
result[1] = boundary + sign * clearance
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isPolylinePointBlocked(x: number, y: number, bounds: IBoundsData | undefined, clearance: number): boolean {
|
|
75
|
+
if (!bounds) return false
|
|
76
|
+
const left = bounds.x - clearance, right = bounds.x + bounds.width + clearance
|
|
77
|
+
const top = bounds.y - clearance, bottom = bounds.y + bounds.height + clearance
|
|
78
|
+
return x > left + POLYLINE_EPSILON && x < right - POLYLINE_EPSILON && y > top + POLYLINE_EPSILON && y < bottom - POLYLINE_EPSILON
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isPolylineSegmentBlocked(x1: number, y1: number, x2: number, y2: number, bounds: IBoundsData | undefined, clearance: number): boolean {
|
|
82
|
+
if (!bounds) return false
|
|
83
|
+
const left = bounds.x - clearance, right = bounds.x + bounds.width + clearance
|
|
84
|
+
const top = bounds.y - clearance, bottom = bounds.y + bounds.height + clearance
|
|
85
|
+
|
|
86
|
+
if (Math.abs(y1 - y2) <= POLYLINE_EPSILON) {
|
|
87
|
+
return y1 > top + POLYLINE_EPSILON && y1 < bottom - POLYLINE_EPSILON &&
|
|
88
|
+
Math.max(Math.min(x1, x2), left + POLYLINE_EPSILON) < Math.min(Math.max(x1, x2), right - POLYLINE_EPSILON)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (Math.abs(x1 - x2) <= POLYLINE_EPSILON) {
|
|
92
|
+
return x1 > left + POLYLINE_EPSILON && x1 < right - POLYLINE_EPSILON &&
|
|
93
|
+
Math.max(Math.min(y1, y2), top + POLYLINE_EPSILON) < Math.min(Math.max(y1, y2), bottom - POLYLINE_EPSILON)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function addPolylineBoundsCoordinates(xs: number[], ys: number[], bounds: IBoundsData | undefined, clearance: number, margin: number): void {
|
|
100
|
+
if (!bounds) return
|
|
101
|
+
const left = bounds.x - clearance, right = bounds.x + bounds.width + clearance
|
|
102
|
+
const top = bounds.y - clearance, bottom = bounds.y + bounds.height + clearance
|
|
103
|
+
xs.push(left, right, left - margin, right + margin)
|
|
104
|
+
ys.push(top, bottom, top - margin, bottom + margin)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function sortPolylineCoordinates(values: number[]): void {
|
|
108
|
+
values.sort((a, b) => a - b)
|
|
109
|
+
let write = 0
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < values.length; i++) {
|
|
112
|
+
if (!write || Math.abs(values[i] - values[write - 1]) > POLYLINE_EPSILON) values[write++] = values[i]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
values.length = write
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function getPolylineCoordinateIndex(values: number[], value: number): number {
|
|
119
|
+
for (let i = 0; i < values.length; i++) {
|
|
120
|
+
if (Math.abs(values[i] - value) <= POLYLINE_EPSILON) return i
|
|
121
|
+
}
|
|
122
|
+
return -1
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 根据两端连接方向确定中点转折轴
|
|
126
|
+
function getPolylineMidpointAxis(startPort: number, endPort: number, sx: number, sy: number, ex: number, ey: number): number {
|
|
127
|
+
const startAxis = startPort >> 1, endAxis = endPort >> 1
|
|
128
|
+
const startSign = startPort & 1 ? 1 : -1, endSign = endPort & 1 ? 1 : -1
|
|
129
|
+
|
|
130
|
+
if (startAxis === endAxis && startSign === -endSign) return startAxis
|
|
131
|
+
return Math.abs(ex - sx) >= Math.abs(ey - sy) ? POLYLINE_X : POLYLINE_Y
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 检查中点优先路径
|
|
135
|
+
function getPolylineMidpointRoute(result: number[], startPoint: IPointData, endPoint: IPointData, sx: number, sy: number, ex: number, ey: number, midpointAxis: number, startBounds: IBoundsData | undefined, endBounds: IBoundsData | undefined, clearance: number): number {
|
|
136
|
+
const midpointX = (sx + ex) / 2, midpointY = (sy + ey) / 2
|
|
137
|
+
result.length = 0
|
|
138
|
+
result.push(startPoint.x, startPoint.y, sx, sy)
|
|
139
|
+
|
|
140
|
+
if (midpointAxis === POLYLINE_X) {
|
|
141
|
+
result.push(midpointX, sy, midpointX, ey)
|
|
142
|
+
} else {
|
|
143
|
+
result.push(sx, midpointY, ex, midpointY)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
result.push(ex, ey, endPoint.x, endPoint.y)
|
|
147
|
+
|
|
148
|
+
// 只检查两端出口之间的路径,起终点连接段允许进入自身元素
|
|
149
|
+
for (let i = 4; i < result.length - 2; i += 2) {
|
|
150
|
+
const x1 = result[i - 2], y1 = result[i - 1]
|
|
151
|
+
const x2 = result[i], y2 = result[i + 1]
|
|
152
|
+
|
|
153
|
+
if (isPolylineSegmentBlocked(x1, y1, x2, y2, startBounds, clearance) ||
|
|
154
|
+
isPolylineSegmentBlocked(x1, y1, x2, y2, endBounds, clearance)) return Infinity
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return getPolylineScore(result, clearance)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getPolylineRoute(result: number[], startPoint: IPointData, endPoint: IPointData, startPort: number, endPort: number, startBounds: IBoundsData | undefined, endBounds: IBoundsData | undefined, clearance: number): number {
|
|
161
|
+
const startExit: number[] = [], endExit: number[] = []
|
|
162
|
+
getPolylineExit(startPoint, startBounds, startPort, clearance, startExit)
|
|
163
|
+
getPolylineExit(endPoint, endBounds, endPort, clearance, endExit)
|
|
164
|
+
|
|
165
|
+
const sx = startExit[0], sy = startExit[1], ex = endExit[0], ey = endExit[1]
|
|
166
|
+
const startAxis = startPort >> 1, endAxis = endPort >> 1
|
|
167
|
+
const startSign = startPort & 1 ? 1 : -1, endSign = endPort & 1 ? 1 : -1
|
|
168
|
+
const startStub = Math.abs(sx - startPoint.x) + Math.abs(sy - startPoint.y)
|
|
169
|
+
const endStub = Math.abs(ex - endPoint.x) + Math.abs(ey - endPoint.y)
|
|
170
|
+
|
|
171
|
+
if (isPolylineSegmentBlocked(startPoint.x, startPoint.y, sx, sy, endBounds, clearance) ||
|
|
172
|
+
isPolylineSegmentBlocked(endPoint.x, endPoint.y, ex, ey, startBounds, clearance)) return Infinity
|
|
173
|
+
|
|
174
|
+
const midpointAxis = getPolylineMidpointAxis(startPort, endPort, sx, sy, ex, ey)
|
|
175
|
+
|
|
176
|
+
// 第一优先级:只要中点路径不受阻,就直接采用
|
|
177
|
+
const midpointScore = getPolylineMidpointRoute(result, startPoint, endPoint, sx, sy, ex, ey, midpointAxis, startBounds, endBounds, clearance)
|
|
178
|
+
if (midpointScore < Infinity) return midpointScore
|
|
179
|
+
|
|
180
|
+
// 第二优先级:中点路径受阻,使用网格寻路绕行
|
|
181
|
+
const margin = Math.max(clearance, POLYLINE_MIN_SEGMENT) * 2
|
|
182
|
+
const xs: number[] = [sx, ex, (sx + ex) / 2], ys: number[] = [sy, ey, (sy + ey) / 2]
|
|
183
|
+
|
|
184
|
+
addPolylineBoundsCoordinates(xs, ys, startBounds, clearance, margin)
|
|
185
|
+
addPolylineBoundsCoordinates(xs, ys, endBounds, clearance, margin)
|
|
186
|
+
|
|
187
|
+
let minX = Math.min(...xs), maxX = Math.max(...xs)
|
|
188
|
+
let minY = Math.min(...ys), maxY = Math.max(...ys)
|
|
189
|
+
xs.push(minX - margin * 2, maxX + margin * 2)
|
|
190
|
+
ys.push(minY - margin * 2, maxY + margin * 2)
|
|
191
|
+
|
|
192
|
+
sortPolylineCoordinates(xs)
|
|
193
|
+
sortPolylineCoordinates(ys)
|
|
194
|
+
|
|
195
|
+
const nx = xs.length, ny = ys.length
|
|
196
|
+
const startXIndex = getPolylineCoordinateIndex(xs, sx), startYIndex = getPolylineCoordinateIndex(ys, sy)
|
|
197
|
+
const endXIndex = getPolylineCoordinateIndex(xs, ex), endYIndex = getPolylineCoordinateIndex(ys, ey)
|
|
198
|
+
|
|
199
|
+
if (startXIndex < 0 || startYIndex < 0 || endXIndex < 0 || endYIndex < 0) return Infinity
|
|
200
|
+
|
|
201
|
+
const startNode = startYIndex * nx + startXIndex, endNode = endYIndex * nx + endXIndex
|
|
202
|
+
const total = nx * ny * 3
|
|
203
|
+
const distance = new Float64Array(total), previous = new Int32Array(total), visited = new Uint8Array(total)
|
|
204
|
+
|
|
205
|
+
distance.fill(Infinity)
|
|
206
|
+
previous.fill(-1)
|
|
207
|
+
|
|
208
|
+
const startState = startNode * 3
|
|
209
|
+
distance[startState] = 0
|
|
210
|
+
|
|
211
|
+
const turnCost = Math.max(POLYLINE_MIN_SEGMENT, clearance * POLYLINE_TURN_WEIGHT)
|
|
212
|
+
let currentState = -1, currentNode = -1, currentAxis = 0
|
|
213
|
+
let currentX = 0, currentY = 0, currentDistance = 0
|
|
214
|
+
|
|
215
|
+
const relax = (nextXIndex: number, nextYIndex: number, axis: number, sign: number): void => {
|
|
216
|
+
const nextX = xs[nextXIndex], nextY = ys[nextYIndex]
|
|
217
|
+
const nextNode = nextYIndex * nx + nextXIndex
|
|
218
|
+
|
|
219
|
+
if (isPolylinePointBlocked(nextX, nextY, startBounds, clearance) ||
|
|
220
|
+
isPolylinePointBlocked(nextX, nextY, endBounds, clearance)) return
|
|
221
|
+
|
|
222
|
+
if (isPolylineSegmentBlocked(currentX, currentY, nextX, nextY, startBounds, clearance) ||
|
|
223
|
+
isPolylineSegmentBlocked(currentX, currentY, nextX, nextY, endBounds, clearance)) return
|
|
224
|
+
|
|
225
|
+
if (currentState === startState) {
|
|
226
|
+
if (axis === startAxis + 1) {
|
|
227
|
+
if (sign !== startSign) return
|
|
228
|
+
} else if (startStub <= POLYLINE_EPSILON) return
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (nextNode === endNode) {
|
|
232
|
+
if (axis === endAxis + 1) {
|
|
233
|
+
if (sign !== -endSign) return
|
|
234
|
+
} else if (endStub <= POLYLINE_EPSILON) return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const nextState = nextNode * 3 + axis
|
|
238
|
+
if (visited[nextState]) return
|
|
239
|
+
|
|
240
|
+
const segmentLength = Math.abs(nextX - currentX) + Math.abs(nextY - currentY)
|
|
241
|
+
let cost = currentDistance + segmentLength
|
|
242
|
+
|
|
243
|
+
if (currentAxis && currentAxis !== axis) {
|
|
244
|
+
cost += turnCost
|
|
245
|
+
const turnCoordinate = currentAxis === POLYLINE_X + 1 ? currentX : currentY
|
|
246
|
+
const preferredCoordinate = midpointAxis === POLYLINE_X ? (sx + ex) / 2 : (sy + ey) / 2
|
|
247
|
+
if (Math.abs(turnCoordinate - preferredCoordinate) > POLYLINE_EPSILON) cost += turnCost * 2
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (cost < distance[nextState]) {
|
|
251
|
+
distance[nextState] = cost
|
|
252
|
+
previous[nextState] = currentState
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let bestState = -1
|
|
257
|
+
|
|
258
|
+
for (let iteration = 0; iteration < total; iteration++) {
|
|
259
|
+
let minDistance = Infinity, state = -1
|
|
260
|
+
|
|
261
|
+
for (let i = 0; i < total; i++) {
|
|
262
|
+
if (!visited[i] && distance[i] < minDistance) {
|
|
263
|
+
minDistance = distance[i]
|
|
264
|
+
state = i
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (state < 0) break
|
|
269
|
+
|
|
270
|
+
currentState = state
|
|
271
|
+
currentNode = Math.floor(state / 3)
|
|
272
|
+
currentAxis = state % 3
|
|
273
|
+
currentDistance = minDistance
|
|
274
|
+
visited[state] = 1
|
|
275
|
+
|
|
276
|
+
if (currentNode === endNode) {
|
|
277
|
+
bestState = state
|
|
278
|
+
break
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const xIndex = currentNode % nx, yIndex = Math.floor(currentNode / nx)
|
|
282
|
+
currentX = xs[xIndex]
|
|
283
|
+
currentY = ys[yIndex]
|
|
284
|
+
|
|
285
|
+
if (xIndex > 0) relax(xIndex - 1, yIndex, POLYLINE_X + 1, -1)
|
|
286
|
+
if (xIndex < nx - 1) relax(xIndex + 1, yIndex, POLYLINE_X + 1, 1)
|
|
287
|
+
if (yIndex > 0) relax(xIndex, yIndex - 1, POLYLINE_Y + 1, -1)
|
|
288
|
+
if (yIndex < ny - 1) relax(xIndex, yIndex + 1, POLYLINE_Y + 1, 1)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (bestState < 0) return Infinity
|
|
292
|
+
|
|
293
|
+
const reverse: number[] = []
|
|
294
|
+
let state = bestState
|
|
295
|
+
|
|
296
|
+
while (state >= 0) {
|
|
297
|
+
const node = Math.floor(state / 3)
|
|
298
|
+
reverse.push(xs[node % nx], ys[Math.floor(node / nx)])
|
|
299
|
+
if (state === startState) break
|
|
300
|
+
state = previous[state]
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (state !== startState) return Infinity
|
|
304
|
+
|
|
305
|
+
result.length = 0
|
|
306
|
+
result.push(startPoint.x, startPoint.y, sx, sy)
|
|
307
|
+
|
|
308
|
+
for (let i = reverse.length - 4; i >= 0; i -= 2) result.push(reverse[i], reverse[i + 1])
|
|
309
|
+
|
|
310
|
+
result.push(endPoint.x, endPoint.y)
|
|
311
|
+
return distance[bestState] + startStub + endStub
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function appendPolylinePoint(points: number[], x: number, y: number): void {
|
|
315
|
+
const length = points.length
|
|
316
|
+
if (length >= 2 && Math.abs(points[length - 2] - x) <= POLYLINE_EPSILON && Math.abs(points[length - 1] - y) <= POLYLINE_EPSILON) return
|
|
317
|
+
points.push(x, y)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function appendPolylineOrthogonal(points: number[], x1: number, y1: number, x2: number, y2: number, firstAxis = POLYLINE_X): void {
|
|
321
|
+
if (Math.abs(x1 - x2) > POLYLINE_EPSILON && Math.abs(y1 - y2) > POLYLINE_EPSILON) {
|
|
322
|
+
if (firstAxis === POLYLINE_X) {
|
|
323
|
+
const midX = (x1 + x2) / 2
|
|
324
|
+
appendPolylinePoint(points, midX, y1)
|
|
325
|
+
appendPolylinePoint(points, midX, y2)
|
|
326
|
+
} else {
|
|
327
|
+
const midY = (y1 + y2) / 2
|
|
328
|
+
appendPolylinePoint(points, x1, midY)
|
|
329
|
+
appendPolylinePoint(points, x2, midY)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
appendPolylinePoint(points, x2, y2)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function getPolylineEditPath(result: number[], startPoint: IPointData, endPoint: IPointData, startPort: number, endPort: number, startBounds: IBoundsData | undefined, endBounds: IBoundsData | undefined, points: number[], clearance: number): number {
|
|
336
|
+
if (points.length % 2) return Infinity
|
|
337
|
+
for (let i = 0; i < points.length; i++) if (!Number.isFinite(points[i])) return Infinity
|
|
338
|
+
|
|
339
|
+
const startExit: number[] = [], endExit: number[] = []
|
|
340
|
+
getPolylineExit(startPoint, startBounds, startPort, clearance, startExit)
|
|
341
|
+
getPolylineExit(endPoint, endBounds, endPort, clearance, endExit)
|
|
342
|
+
|
|
343
|
+
const sx = startExit[0], sy = startExit[1], ex = endExit[0], ey = endExit[1]
|
|
344
|
+
|
|
345
|
+
if (isPolylineSegmentBlocked(startPoint.x, startPoint.y, sx, sy, endBounds, clearance) ||
|
|
346
|
+
isPolylineSegmentBlocked(endPoint.x, endPoint.y, ex, ey, startBounds, clearance)) return Infinity
|
|
347
|
+
|
|
348
|
+
result.length = 0
|
|
349
|
+
appendPolylinePoint(result, startPoint.x, startPoint.y)
|
|
350
|
+
appendPolylinePoint(result, sx, sy)
|
|
351
|
+
|
|
352
|
+
let x = sx, y = sy
|
|
353
|
+
|
|
354
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
355
|
+
const nx = points[i], ny = points[i + 1]
|
|
356
|
+
appendPolylineOrthogonal(result, x, y, nx, ny, i === 0 && clearance === 0 ? startPort >> 1 : POLYLINE_X)
|
|
357
|
+
x = nx
|
|
358
|
+
y = ny
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const endAxis = endPort >> 1
|
|
362
|
+
|
|
363
|
+
if (Math.abs(x - ex) > POLYLINE_EPSILON && Math.abs(y - ey) > POLYLINE_EPSILON) {
|
|
364
|
+
if (endAxis === POLYLINE_X) appendPolylinePoint(result, x, ey)
|
|
365
|
+
else appendPolylinePoint(result, ex, y)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
appendPolylinePoint(result, ex, ey)
|
|
369
|
+
appendPolylinePoint(result, endPoint.x, endPoint.y)
|
|
370
|
+
return getPolylineScore(result, clearance)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function getPolylineScore(points: number[], clearance: number): number {
|
|
374
|
+
let length = 0, turns = 0, previousAxis = -1
|
|
375
|
+
|
|
376
|
+
for (let i = 2; i < points.length; i += 2) {
|
|
377
|
+
const dx = points[i] - points[i - 2], dy = points[i + 1] - points[i - 1]
|
|
378
|
+
if (Math.abs(dx) <= POLYLINE_EPSILON && Math.abs(dy) <= POLYLINE_EPSILON) continue
|
|
379
|
+
|
|
380
|
+
const axis = Math.abs(dx) > POLYLINE_EPSILON ? POLYLINE_X : POLYLINE_Y
|
|
381
|
+
length += Math.abs(dx) + Math.abs(dy)
|
|
382
|
+
if (previousAxis !== -1 && previousAxis !== axis) turns++
|
|
383
|
+
previousAxis = axis
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return length + turns * Math.max(clearance, POLYLINE_MIN_SEGMENT) * POLYLINE_TURN_WEIGHT
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function simplifyPolyline(points: number[]): void {
|
|
390
|
+
let write = 2
|
|
391
|
+
|
|
392
|
+
for (let read = 2; read < points.length; read += 2) {
|
|
393
|
+
const x = points[read], y = points[read + 1]
|
|
394
|
+
const px = points[write - 2], py = points[write - 1]
|
|
395
|
+
|
|
396
|
+
if (Math.abs(x - px) <= POLYLINE_EPSILON && Math.abs(y - py) <= POLYLINE_EPSILON) continue
|
|
397
|
+
|
|
398
|
+
if (write >= 4) {
|
|
399
|
+
const ax = points[write - 4], ay = points[write - 3]
|
|
400
|
+
const horizontal = Math.abs(ay - py) <= POLYLINE_EPSILON && Math.abs(py - y) <= POLYLINE_EPSILON && (px - ax) * (x - px) >= 0
|
|
401
|
+
const vertical = Math.abs(ax - px) <= POLYLINE_EPSILON && Math.abs(px - x) <= POLYLINE_EPSILON && (py - ay) * (y - py) >= 0
|
|
402
|
+
|
|
403
|
+
if (horizontal || vertical) {
|
|
404
|
+
points[write - 2] = x
|
|
405
|
+
points[write - 1] = y
|
|
406
|
+
continue
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
points[write++] = x
|
|
411
|
+
points[write++] = y
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
points.length = write
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function drawPolyline(path: number[], startPoint: IPointData, endPoint: IPointData, startDirection: IDirection, endDirection: IDirection, startBounds?: IBoundsData, endBounds?: IBoundsData, clearance = 8, points?: number[]): boolean {
|
|
418
|
+
if (!Number.isFinite(startPoint.x) || !Number.isFinite(startPoint.y) || !Number.isFinite(endPoint.x) || !Number.isFinite(endPoint.y)) return false
|
|
419
|
+
|
|
420
|
+
if (startBounds && (!Number.isFinite(startBounds.x) || !Number.isFinite(startBounds.y) || !Number.isFinite(startBounds.width) || !Number.isFinite(startBounds.height) || startBounds.width < 0 || startBounds.height < 0)) return false
|
|
421
|
+
if (endBounds && (!Number.isFinite(endBounds.x) || !Number.isFinite(endBounds.y) || !Number.isFinite(endBounds.width) || !Number.isFinite(endBounds.height) || endBounds.width < 0 || endBounds.height < 0)) return false
|
|
422
|
+
|
|
423
|
+
clearance = Number.isFinite(clearance) ? Math.max(0, clearance) : 8
|
|
424
|
+
|
|
425
|
+
const startPorts: number[] = [], endPorts: number[] = []
|
|
426
|
+
const startCount = getPolylinePorts(startDirection, startPoint, endPoint, startBounds, endBounds, startPorts)
|
|
427
|
+
const endCount = getPolylinePorts(endDirection, endPoint, startPoint, endBounds, startBounds, endPorts)
|
|
428
|
+
const candidate: number[] = [], best: number[] = []
|
|
429
|
+
|
|
430
|
+
let bestScore = Infinity
|
|
431
|
+
|
|
432
|
+
for (let pass = 0; pass < 2 && bestScore === Infinity; pass++) {
|
|
433
|
+
const currentClearance = pass === 0 ? clearance : 0
|
|
434
|
+
|
|
435
|
+
for (let i = 0; i < startCount; i++) {
|
|
436
|
+
for (let j = 0; j < endCount; j++) {
|
|
437
|
+
const score = points !== undefined
|
|
438
|
+
? getPolylineEditPath(candidate, startPoint, endPoint, startPorts[i], endPorts[j], startBounds, endBounds, points, currentClearance)
|
|
439
|
+
: getPolylineRoute(candidate, startPoint, endPoint, startPorts[i], endPorts[j], startBounds, endBounds, currentClearance)
|
|
440
|
+
|
|
441
|
+
if (score < bestScore) {
|
|
442
|
+
bestScore = score
|
|
443
|
+
best.length = 0
|
|
444
|
+
for (let k = 0; k < candidate.length; k++) best.push(candidate[k])
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (!best.length) return false
|
|
451
|
+
if (points === undefined) simplifyPolyline(best)
|
|
452
|
+
|
|
453
|
+
moveTo(path, best[0], best[1])
|
|
454
|
+
for (let i = 2; i < best.length; i += 2) lineTo(path, best[i], best[i + 1])
|
|
455
|
+
return true
|
|
456
|
+
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { ILinker, ILinkerData, ILinkerPointData, IPointsCurve, IUI, ILinkerComputedPointData, ILinkerInputData, ILinkerPointType, IPointData, ILeaf, ILinkerPointOptions } from '@leafer-ui/interface';
|
|
1
|
+
import { ILinker, ILinkerData, ILineType, ILinkerPointData, IPointsCurve, IUI, ILinkerComputedPointData, ILinkerInputData, ILinkerPointType, IPointData, ILeaf, ILinkerPointOptions } from '@leafer-ui/interface';
|
|
2
2
|
import { Line, LineData } from '@leafer-ui/draw';
|
|
3
3
|
|
|
4
4
|
declare class Linker extends Line implements ILinker {
|
|
5
5
|
get __tag(): string;
|
|
6
6
|
__: ILinkerData;
|
|
7
|
+
lineType?: ILineType;
|
|
7
8
|
startPoint?: ILinkerPointData;
|
|
8
9
|
endPoint?: ILinkerPointData;
|
|
9
10
|
curve?: IPointsCurve;
|