@leafer-in/linker 2.3.2 → 2.3.4
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 +458 -49
- package/dist/linker.esm.js +459 -50
- package/dist/linker.esm.min.js +1 -1
- package/dist/linker.esm.min.js.map +1 -1
- package/dist/linker.js +430 -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
package/dist/linker.cjs
CHANGED
|
@@ -21,7 +21,7 @@ class LinkerData extends draw.LineData {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const {directionData: directionData} = draw.AroundHelper;
|
|
24
|
+
const {directionData: directionData$1} = draw.AroundHelper;
|
|
25
25
|
|
|
26
26
|
const directionNames = [ "top-left", "top", "top-right", "right", "bottom-right", "bottom", "bottom-left", "left", "center" ];
|
|
27
27
|
|
|
@@ -137,8 +137,8 @@ function getPointDirection(boxPoint, bounds, eight) {
|
|
|
137
137
|
const {width: width, height: height} = bounds;
|
|
138
138
|
const hitRadiusX = width / 4;
|
|
139
139
|
const hitRadiusY = height / 4;
|
|
140
|
-
for (let i = 0; i < directionData.length; i++) {
|
|
141
|
-
const unit = directionData[i];
|
|
140
|
+
for (let i = 0; i < directionData$1.length; i++) {
|
|
141
|
+
const unit = directionData$1[i];
|
|
142
142
|
const px = unit.x * width;
|
|
143
143
|
const py = unit.y * height;
|
|
144
144
|
const dx = boxPoint.x - px;
|
|
@@ -183,7 +183,400 @@ function linkerPointType(defaultValue) {
|
|
|
183
183
|
}));
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
const {
|
|
186
|
+
const {directionData: directionData} = draw.AroundHelper;
|
|
187
|
+
|
|
188
|
+
const {moveTo: moveTo$1, lineTo: lineTo$1} = draw.PathCommandDataHelper;
|
|
189
|
+
|
|
190
|
+
const POLYLINE_X = 0, POLYLINE_Y = 1;
|
|
191
|
+
|
|
192
|
+
const POLYLINE_EPSILON = .001;
|
|
193
|
+
|
|
194
|
+
const POLYLINE_MIN_SEGMENT = 4;
|
|
195
|
+
|
|
196
|
+
const POLYLINE_TURN_WEIGHT = 1.5;
|
|
197
|
+
|
|
198
|
+
function getPolylineDirectionIndex(direction) {
|
|
199
|
+
switch (direction) {
|
|
200
|
+
case "top-left":
|
|
201
|
+
return 0;
|
|
202
|
+
|
|
203
|
+
case "top":
|
|
204
|
+
return 1;
|
|
205
|
+
|
|
206
|
+
case "top-right":
|
|
207
|
+
return 2;
|
|
208
|
+
|
|
209
|
+
case "right":
|
|
210
|
+
return 3;
|
|
211
|
+
|
|
212
|
+
case "bottom-right":
|
|
213
|
+
return 4;
|
|
214
|
+
|
|
215
|
+
case "bottom":
|
|
216
|
+
return 5;
|
|
217
|
+
|
|
218
|
+
case "bottom-left":
|
|
219
|
+
return 6;
|
|
220
|
+
|
|
221
|
+
case "left":
|
|
222
|
+
return 7;
|
|
223
|
+
|
|
224
|
+
default:
|
|
225
|
+
return 8;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function getPolylinePorts(direction, point, target, bounds, targetBounds) {
|
|
230
|
+
const data = directionData[getPolylineDirectionIndex(direction)];
|
|
231
|
+
const dx = Math.sign(data.x - .5), dy = Math.sign(data.y - .5);
|
|
232
|
+
const cx = bounds ? bounds.x + bounds.width / 2 : point.x;
|
|
233
|
+
const cy = bounds ? bounds.y + bounds.height / 2 : point.y;
|
|
234
|
+
const tx = targetBounds ? targetBounds.x + targetBounds.width / 2 : target.x;
|
|
235
|
+
const ty = targetBounds ? targetBounds.y + targetBounds.height / 2 : target.y;
|
|
236
|
+
const deltaX = tx - cx, deltaY = ty - cy;
|
|
237
|
+
const xPort = (dx || (deltaX >= 0 ? 1 : -1)) > 0 ? 1 : 0;
|
|
238
|
+
const yPort = (dy || (deltaY >= 0 ? 1 : -1)) > 0 ? 3 : 2;
|
|
239
|
+
if (dx && !dy) return [ xPort ];
|
|
240
|
+
if (dy && !dx) return [ yPort ];
|
|
241
|
+
return Math.abs(deltaX) >= Math.abs(deltaY) ? [ xPort, yPort ] : [ yPort, xPort ];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getPolylineExit(point, bounds, port, clearance) {
|
|
245
|
+
const axis = port >> 1, sign = port & 1 ? 1 : -1;
|
|
246
|
+
if (axis === POLYLINE_X) {
|
|
247
|
+
const x = bounds ? bounds.x + (sign > 0 ? bounds.width : 0) : point.x;
|
|
248
|
+
return {
|
|
249
|
+
x: x + sign * clearance,
|
|
250
|
+
y: point.y
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const y = bounds ? bounds.y + (sign > 0 ? bounds.height : 0) : point.y;
|
|
254
|
+
return {
|
|
255
|
+
x: point.x,
|
|
256
|
+
y: y + sign * clearance
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function isPolylinePointBlocked(x, y, bounds, clearance) {
|
|
261
|
+
if (!bounds) return false;
|
|
262
|
+
const left = bounds.x - clearance, right = bounds.x + bounds.width + clearance;
|
|
263
|
+
const top = bounds.y - clearance, bottom = bounds.y + bounds.height + clearance;
|
|
264
|
+
return x > left + POLYLINE_EPSILON && x < right - POLYLINE_EPSILON && y > top + POLYLINE_EPSILON && y < bottom - POLYLINE_EPSILON;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function isPolylineSegmentBlocked(x1, y1, x2, y2, bounds, clearance) {
|
|
268
|
+
if (!bounds) return false;
|
|
269
|
+
const left = bounds.x - clearance, right = bounds.x + bounds.width + clearance;
|
|
270
|
+
const top = bounds.y - clearance, bottom = bounds.y + bounds.height + clearance;
|
|
271
|
+
if (Math.abs(y1 - y2) <= POLYLINE_EPSILON) {
|
|
272
|
+
return y1 > top + POLYLINE_EPSILON && y1 < bottom - POLYLINE_EPSILON && Math.max(Math.min(x1, x2), left + POLYLINE_EPSILON) < Math.min(Math.max(x1, x2), right - POLYLINE_EPSILON);
|
|
273
|
+
}
|
|
274
|
+
if (Math.abs(x1 - x2) <= POLYLINE_EPSILON) {
|
|
275
|
+
return x1 > left + POLYLINE_EPSILON && x1 < right - POLYLINE_EPSILON && Math.max(Math.min(y1, y2), top + POLYLINE_EPSILON) < Math.min(Math.max(y1, y2), bottom - POLYLINE_EPSILON);
|
|
276
|
+
}
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function addPolylineBoundsCoordinates(xs, ys, bounds, clearance, margin) {
|
|
281
|
+
if (!bounds) return;
|
|
282
|
+
const left = bounds.x - clearance, right = bounds.x + bounds.width + clearance;
|
|
283
|
+
const top = bounds.y - clearance, bottom = bounds.y + bounds.height + clearance;
|
|
284
|
+
xs.push(left, right, left - margin, right + margin);
|
|
285
|
+
ys.push(top, bottom, top - margin, bottom + margin);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function sortPolylineCoordinates(values) {
|
|
289
|
+
values.sort((a, b) => a - b);
|
|
290
|
+
let write = 0;
|
|
291
|
+
for (let i = 0; i < values.length; i++) {
|
|
292
|
+
if (!write || Math.abs(values[i] - values[write - 1]) > POLYLINE_EPSILON) values[write++] = values[i];
|
|
293
|
+
}
|
|
294
|
+
values.length = write;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function getPolylineCoordinateIndex(values, value) {
|
|
298
|
+
for (let i = 0; i < values.length; i++) {
|
|
299
|
+
if (Math.abs(values[i] - value) <= POLYLINE_EPSILON) return i;
|
|
300
|
+
}
|
|
301
|
+
return -1;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function getPolylineMidpointAxis(startPort, endPort, sx, sy, ex, ey) {
|
|
305
|
+
const startAxis = startPort >> 1, endAxis = endPort >> 1;
|
|
306
|
+
const startSign = startPort & 1 ? 1 : -1, endSign = endPort & 1 ? 1 : -1;
|
|
307
|
+
if (startAxis === endAxis && startSign === -endSign) return startAxis;
|
|
308
|
+
return Math.abs(ex - sx) >= Math.abs(ey - sy) ? POLYLINE_X : POLYLINE_Y;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function isPolylinePortAligned(port, stub, dx, dy) {
|
|
312
|
+
const offset = port >> 1 === POLYLINE_X ? dx : dy;
|
|
313
|
+
const sign = port & 1 ? 1 : -1;
|
|
314
|
+
return Math.abs(offset) <= POLYLINE_EPSILON ? stub > POLYLINE_EPSILON : offset * sign > 0;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function isPolylineMidpointAligned(port, stub, dx, dy, axis) {
|
|
318
|
+
if (port === undefined) return true;
|
|
319
|
+
if (axis === POLYLINE_X && Math.abs(dx / 2) > POLYLINE_EPSILON) return isPolylinePortAligned(port, stub, dx, 0);
|
|
320
|
+
if (axis === POLYLINE_Y && Math.abs(dy / 2) > POLYLINE_EPSILON) return isPolylinePortAligned(port, stub, 0, dy);
|
|
321
|
+
if (Math.abs(dx) <= POLYLINE_EPSILON && Math.abs(dy) <= POLYLINE_EPSILON) return true;
|
|
322
|
+
return isPolylinePortAligned(port, stub, dx, dy);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function isPolylineApproachAligned(points, port) {
|
|
326
|
+
if (port === undefined) return true;
|
|
327
|
+
const last = points.length - 2, x = points[last], y = points[last + 1];
|
|
328
|
+
for (let i = last - 2; i >= 0; i -= 2) {
|
|
329
|
+
const dx = points[i] - x, dy = points[i + 1] - y;
|
|
330
|
+
if (Math.abs(dx) > POLYLINE_EPSILON || Math.abs(dy) > POLYLINE_EPSILON) return isPolylinePortAligned(port, 1, dx, dy);
|
|
331
|
+
}
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function getPolylineRoute(result, startPoint, endPoint, startPort, endPort, startBounds, endBounds, clearance, strictPorts, approachPort) {
|
|
336
|
+
const startExit = getPolylineExit(startPoint, startBounds, startPort, clearance);
|
|
337
|
+
const endExit = endPort === undefined ? endPoint : getPolylineExit(endPoint, endBounds, endPort, clearance);
|
|
338
|
+
const {x: sx, y: sy} = startExit, {x: ex, y: ey} = endExit;
|
|
339
|
+
const startStub = Math.abs(sx - startPoint.x) + Math.abs(sy - startPoint.y);
|
|
340
|
+
const endStub = Math.abs(ex - endPoint.x) + Math.abs(ey - endPoint.y);
|
|
341
|
+
if (isPolylineSegmentBlocked(startPoint.x, startPoint.y, sx, sy, endBounds, clearance) || isPolylineSegmentBlocked(endPoint.x, endPoint.y, ex, ey, startBounds, clearance)) return Infinity;
|
|
342
|
+
if (endPort === undefined && (isPolylinePointBlocked(ex, ey, startBounds, clearance) || isPolylinePointBlocked(ex, ey, endBounds, clearance))) return Infinity;
|
|
343
|
+
const axis = endPort === undefined ? startPort >> 1 : getPolylineMidpointAxis(startPort, endPort, sx, sy, ex, ey);
|
|
344
|
+
const dx = ex - sx, dy = ey - sy;
|
|
345
|
+
result.length = 0;
|
|
346
|
+
result.push(startPoint.x, startPoint.y, sx, sy);
|
|
347
|
+
if ((!strictPorts || isPolylineMidpointAligned(startPort, startStub, dx, dy, axis) && isPolylineMidpointAligned(endPort, endStub, -dx, -dy, axis)) && isPolylineMidpointAligned(approachPort, 1, -dx, -dy, axis)) {
|
|
348
|
+
const mx = (sx + ex) / 2, my = (sy + ey) / 2;
|
|
349
|
+
if (axis === POLYLINE_X) result.push(mx, sy, mx, ey); else result.push(sx, my, ex, my);
|
|
350
|
+
result.push(ex, ey, endPoint.x, endPoint.y);
|
|
351
|
+
for (let i = 4; i < result.length - 2; i += 2) {
|
|
352
|
+
if (isPolylineSegmentBlocked(result[i - 2], result[i - 1], result[i], result[i + 1], startBounds, clearance) || isPolylineSegmentBlocked(result[i - 2], result[i - 1], result[i], result[i + 1], endBounds, clearance)) {
|
|
353
|
+
result.length = 4;
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (result.length > 4 && isPolylineApproachAligned(result, approachPort)) return getPolylineScore(result, clearance);
|
|
358
|
+
result.length = 4;
|
|
359
|
+
}
|
|
360
|
+
const score = findPolylineGridRoute(result, startExit, endExit, startPort, endPort, startBounds, endBounds, clearance, axis, startStub, endStub, strictPorts, approachPort);
|
|
361
|
+
if (score === Infinity) return score;
|
|
362
|
+
result.push(endPoint.x, endPoint.y);
|
|
363
|
+
return isPolylineApproachAligned(result, approachPort) ? score + startStub + endStub : Infinity;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function findPolylineGridRoute(result, startExit, endExit, startPort, endPort, startBounds, endBounds, clearance, midpointAxis, startStub, endStub, strictPorts, approachPort) {
|
|
367
|
+
const {x: sx, y: sy} = startExit, {x: ex, y: ey} = endExit;
|
|
368
|
+
const margin = Math.max(clearance, POLYLINE_MIN_SEGMENT) * 2;
|
|
369
|
+
const xs = [ sx, ex, (sx + ex) / 2 ], ys = [ sy, ey, (sy + ey) / 2 ];
|
|
370
|
+
addPolylineBoundsCoordinates(xs, ys, startBounds, clearance, margin);
|
|
371
|
+
addPolylineBoundsCoordinates(xs, ys, endBounds, clearance, margin);
|
|
372
|
+
xs.push(Math.min(...xs) - margin * 2, Math.max(...xs) + margin * 2);
|
|
373
|
+
ys.push(Math.min(...ys) - margin * 2, Math.max(...ys) + margin * 2);
|
|
374
|
+
sortPolylineCoordinates(xs);
|
|
375
|
+
sortPolylineCoordinates(ys);
|
|
376
|
+
const nx = xs.length, ny = ys.length;
|
|
377
|
+
const startXIndex = getPolylineCoordinateIndex(xs, sx), startYIndex = getPolylineCoordinateIndex(ys, sy);
|
|
378
|
+
const endXIndex = getPolylineCoordinateIndex(xs, ex), endYIndex = getPolylineCoordinateIndex(ys, ey);
|
|
379
|
+
if (startXIndex < 0 || startYIndex < 0 || endXIndex < 0 || endYIndex < 0) return Infinity;
|
|
380
|
+
const startNode = startYIndex * nx + startXIndex, endNode = endYIndex * nx + endXIndex;
|
|
381
|
+
const total = nx * ny * 3;
|
|
382
|
+
const distance = new Float64Array(total), previous = new Int32Array(total), visited = new Uint8Array(total);
|
|
383
|
+
distance.fill(Infinity);
|
|
384
|
+
previous.fill(-1);
|
|
385
|
+
const startState = startNode * 3;
|
|
386
|
+
distance[startState] = 0;
|
|
387
|
+
const pending = [ startState ];
|
|
388
|
+
const turnCost = Math.max(POLYLINE_MIN_SEGMENT, clearance * POLYLINE_TURN_WEIGHT);
|
|
389
|
+
const preferredCoordinate = midpointAxis === POLYLINE_X ? (sx + ex) / 2 : (sy + ey) / 2;
|
|
390
|
+
let currentState = -1, currentNode = -1, currentAxis = 0;
|
|
391
|
+
let currentX = 0, currentY = 0, currentDistance = 0;
|
|
392
|
+
const relax = (nextXIndex, nextYIndex, axis) => {
|
|
393
|
+
const nextX = xs[nextXIndex], nextY = ys[nextYIndex];
|
|
394
|
+
const nextNode = nextYIndex * nx + nextXIndex;
|
|
395
|
+
const dx = nextX - currentX, dy = nextY - currentY;
|
|
396
|
+
if (nextNode === startNode) return;
|
|
397
|
+
if (approachPort !== undefined && nextNode === endNode && !isPolylinePortAligned(approachPort, 1, -dx, -dy)) return;
|
|
398
|
+
if (strictPorts && (currentState === startState && !isPolylinePortAligned(startPort, startStub, dx, dy) || endPort !== undefined && nextNode === endNode && !isPolylinePortAligned(endPort, endStub, -dx, -dy))) return;
|
|
399
|
+
if (isPolylinePointBlocked(nextX, nextY, startBounds, clearance) || isPolylinePointBlocked(nextX, nextY, endBounds, clearance) || isPolylineSegmentBlocked(currentX, currentY, nextX, nextY, startBounds, clearance) || isPolylineSegmentBlocked(currentX, currentY, nextX, nextY, endBounds, clearance)) return;
|
|
400
|
+
const nextState = nextNode * 3 + axis;
|
|
401
|
+
if (visited[nextState]) return;
|
|
402
|
+
let cost = currentDistance + (Math.abs(dx) + Math.abs(dy));
|
|
403
|
+
if (currentAxis && currentAxis !== axis) {
|
|
404
|
+
cost += turnCost;
|
|
405
|
+
const turnCoordinate = currentAxis === POLYLINE_X + 1 ? currentX : currentY;
|
|
406
|
+
if (Math.abs(turnCoordinate - preferredCoordinate) > POLYLINE_EPSILON) cost += turnCost * 2;
|
|
407
|
+
}
|
|
408
|
+
if (cost < distance[nextState]) {
|
|
409
|
+
if (distance[nextState] === Infinity) pending.push(nextState);
|
|
410
|
+
distance[nextState] = cost;
|
|
411
|
+
previous[nextState] = currentState;
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
let bestState = -1;
|
|
415
|
+
while (pending.length) {
|
|
416
|
+
let index = 0, state = pending[0];
|
|
417
|
+
for (let i = 1; i < pending.length; i++) {
|
|
418
|
+
const next = pending[i];
|
|
419
|
+
if (distance[next] < distance[state] || distance[next] === distance[state] && next < state) {
|
|
420
|
+
index = i;
|
|
421
|
+
state = next;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
pending[index] = pending[pending.length - 1];
|
|
425
|
+
pending.pop();
|
|
426
|
+
currentState = state;
|
|
427
|
+
currentNode = Math.floor(state / 3);
|
|
428
|
+
currentAxis = state % 3;
|
|
429
|
+
currentDistance = distance[state];
|
|
430
|
+
visited[state] = 1;
|
|
431
|
+
if (currentNode === endNode) {
|
|
432
|
+
bestState = state;
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
const xIndex = currentNode % nx, yIndex = Math.floor(currentNode / nx);
|
|
436
|
+
currentX = xs[xIndex];
|
|
437
|
+
currentY = ys[yIndex];
|
|
438
|
+
if (xIndex > 0) relax(xIndex - 1, yIndex, POLYLINE_X + 1);
|
|
439
|
+
if (xIndex < nx - 1) relax(xIndex + 1, yIndex, POLYLINE_X + 1);
|
|
440
|
+
if (yIndex > 0) relax(xIndex, yIndex - 1, POLYLINE_Y + 1);
|
|
441
|
+
if (yIndex < ny - 1) relax(xIndex, yIndex + 1, POLYLINE_Y + 1);
|
|
442
|
+
}
|
|
443
|
+
if (bestState < 0) return Infinity;
|
|
444
|
+
const reverse = [];
|
|
445
|
+
let state = bestState;
|
|
446
|
+
while (state >= 0) {
|
|
447
|
+
const node = Math.floor(state / 3);
|
|
448
|
+
reverse.push(xs[node % nx], ys[Math.floor(node / nx)]);
|
|
449
|
+
if (state === startState) break;
|
|
450
|
+
state = previous[state];
|
|
451
|
+
}
|
|
452
|
+
if (state !== startState) return Infinity;
|
|
453
|
+
for (let i = reverse.length - 4; i >= 0; i -= 2) result.push(reverse[i], reverse[i + 1]);
|
|
454
|
+
return distance[bestState];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function getPolylineFallback(start, end, port, startBounds, endBounds) {
|
|
458
|
+
const {x: sx, y: sy} = start, {x: ex, y: ey} = end;
|
|
459
|
+
if (sx === ex || sy === ey) return [ sx, sy, ex, ey ];
|
|
460
|
+
const horizontal = port >> 1 === POLYLINE_X;
|
|
461
|
+
const crosses = (x, y) => {
|
|
462
|
+
let count = 0;
|
|
463
|
+
if (isPolylineSegmentBlocked(sx, sy, x, y, startBounds, 0)) count++;
|
|
464
|
+
if (isPolylineSegmentBlocked(x, y, ex, ey, startBounds, 0)) count++;
|
|
465
|
+
if (isPolylineSegmentBlocked(sx, sy, x, y, endBounds, 0)) count++;
|
|
466
|
+
if (isPolylineSegmentBlocked(x, y, ex, ey, endBounds, 0)) count++;
|
|
467
|
+
return count;
|
|
468
|
+
};
|
|
469
|
+
const xy = crosses(ex, sy), yx = crosses(sx, ey);
|
|
470
|
+
return xy < yx || xy === yx && horizontal ? [ sx, sy, ex, sy, ex, ey ] : [ sx, sy, sx, ey, ex, ey ];
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function getBestPolyline(startPoint, endPoint, startDirection, endDirection, startBounds, endBounds, clearance, approachPort) {
|
|
474
|
+
const startPorts = getPolylinePorts(startDirection, startPoint, endPoint, startBounds, endDirection === undefined ? undefined : endBounds);
|
|
475
|
+
const endPorts = endDirection === undefined ? [ undefined ] : getPolylinePorts(endDirection, endPoint, startPoint, endBounds, startBounds);
|
|
476
|
+
let candidate = [], best = [], bestScore = Infinity;
|
|
477
|
+
for (let pass = clearance ? 0 : 1; pass < 3 && !best.length; pass++) {
|
|
478
|
+
const gap = pass ? 0 : clearance;
|
|
479
|
+
for (let i = 0; i < startPorts.length; i++) {
|
|
480
|
+
for (let j = 0; j < endPorts.length; j++) {
|
|
481
|
+
const score = getPolylineRoute(candidate, startPoint, endPoint, startPorts[i], endPorts[j], startBounds, endBounds, gap, pass < 2, approachPort);
|
|
482
|
+
if (score < bestScore) {
|
|
483
|
+
bestScore = score;
|
|
484
|
+
const previous = best;
|
|
485
|
+
best = candidate;
|
|
486
|
+
candidate = previous;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (!best.length) return getPolylineFallback(startPoint, endPoint, startPorts[0], startBounds, endBounds);
|
|
492
|
+
simplifyPolyline(best);
|
|
493
|
+
const last = best.length - 2;
|
|
494
|
+
if (best[last] !== endPoint.x || best[last + 1] !== endPoint.y) best.push(endPoint.x, endPoint.y);
|
|
495
|
+
return best;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function getPolylineEditSide(point, waypoint, direction, bounds, otherBounds, clearance, points, index, step) {
|
|
499
|
+
if (point.x === waypoint.x && point.y === waypoint.y) return [];
|
|
500
|
+
let approachPort;
|
|
501
|
+
for (let i = index + step; i >= 0 && i < points.length; i += step) {
|
|
502
|
+
const dx = points[i] - waypoint.x, dy = points[i + 1] - waypoint.y;
|
|
503
|
+
if (Math.abs(dx) <= POLYLINE_EPSILON && Math.abs(dy) <= POLYLINE_EPSILON) continue;
|
|
504
|
+
if (Math.abs(dy) <= POLYLINE_EPSILON) approachPort = dx > 0 ? 0 : 1; else if (Math.abs(dx) <= POLYLINE_EPSILON) approachPort = dy > 0 ? 2 : 3;
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
const side = getBestPolyline(point, waypoint, direction, undefined, bounds, otherBounds, clearance, approachPort);
|
|
508
|
+
side.length -= 2;
|
|
509
|
+
return side;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function drawPolylineEditPath(path, startPoint, endPoint, startDirection, endDirection, startBounds, endBounds, clearance, points) {
|
|
513
|
+
const length = points.length, last = length - 2;
|
|
514
|
+
if (length % 2) return false;
|
|
515
|
+
for (let i = 0; i < length; i++) if (!Number.isFinite(points[i])) return false;
|
|
516
|
+
const start = getPolylineEditSide(startPoint, {
|
|
517
|
+
x: points[0],
|
|
518
|
+
y: points[1]
|
|
519
|
+
}, startDirection, startBounds, endBounds, clearance, points, 0, 2);
|
|
520
|
+
const end = getPolylineEditSide(endPoint, {
|
|
521
|
+
x: points[last],
|
|
522
|
+
y: points[last + 1]
|
|
523
|
+
}, endDirection, endBounds, startBounds, clearance, points, last, -2);
|
|
524
|
+
const first = start.length ? start : points;
|
|
525
|
+
moveTo$1(path, first[0], first[1]);
|
|
526
|
+
for (let i = 2; i < start.length; i += 2) lineTo$1(path, start[i], start[i + 1]);
|
|
527
|
+
for (let i = start.length ? 0 : 2; i < length; i += 2) lineTo$1(path, points[i], points[i + 1]);
|
|
528
|
+
for (let i = end.length - 2; i >= 0; i -= 2) lineTo$1(path, end[i], end[i + 1]);
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function getPolylineScore(points, clearance) {
|
|
533
|
+
let length = 0, turns = 0, previousAxis = -1;
|
|
534
|
+
for (let i = 2; i < points.length; i += 2) {
|
|
535
|
+
const dx = points[i] - points[i - 2], dy = points[i + 1] - points[i - 1];
|
|
536
|
+
if (Math.abs(dx) <= POLYLINE_EPSILON && Math.abs(dy) <= POLYLINE_EPSILON) continue;
|
|
537
|
+
const axis = Math.abs(dx) > POLYLINE_EPSILON ? POLYLINE_X : POLYLINE_Y;
|
|
538
|
+
length += Math.abs(dx) + Math.abs(dy);
|
|
539
|
+
if (previousAxis !== -1 && previousAxis !== axis) turns++;
|
|
540
|
+
previousAxis = axis;
|
|
541
|
+
}
|
|
542
|
+
return length + turns * Math.max(clearance, POLYLINE_MIN_SEGMENT) * POLYLINE_TURN_WEIGHT;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function simplifyPolyline(points) {
|
|
546
|
+
let write = 2;
|
|
547
|
+
for (let read = 2; read < points.length; read += 2) {
|
|
548
|
+
const x = points[read], y = points[read + 1];
|
|
549
|
+
const px = points[write - 2], py = points[write - 1];
|
|
550
|
+
if (Math.abs(x - px) <= POLYLINE_EPSILON && Math.abs(y - py) <= POLYLINE_EPSILON) continue;
|
|
551
|
+
if (write >= 4) {
|
|
552
|
+
const ax = points[write - 4], ay = points[write - 3];
|
|
553
|
+
const horizontal = Math.abs(ay - py) <= POLYLINE_EPSILON && Math.abs(py - y) <= POLYLINE_EPSILON && (px - ax) * (x - px) >= 0;
|
|
554
|
+
const vertical = Math.abs(ax - px) <= POLYLINE_EPSILON && Math.abs(px - x) <= POLYLINE_EPSILON && (py - ay) * (y - py) >= 0;
|
|
555
|
+
if (horizontal || vertical) {
|
|
556
|
+
points[write - 2] = x;
|
|
557
|
+
points[write - 1] = y;
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
points[write++] = x;
|
|
562
|
+
points[write++] = y;
|
|
563
|
+
}
|
|
564
|
+
points.length = write;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function drawPolyline(path, startPoint, endPoint, startDirection, endDirection, startBounds, endBounds, clearance = 8, points) {
|
|
568
|
+
if (!Number.isFinite(startPoint.x) || !Number.isFinite(startPoint.y) || !Number.isFinite(endPoint.x) || !Number.isFinite(endPoint.y)) return false;
|
|
569
|
+
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;
|
|
570
|
+
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;
|
|
571
|
+
clearance = Number.isFinite(clearance) ? Math.max(0, clearance) : 8;
|
|
572
|
+
if (points === null || points === void 0 ? void 0 : points.length) return drawPolylineEditPath(path, startPoint, endPoint, startDirection, endDirection, startBounds, endBounds, clearance, points);
|
|
573
|
+
const best = getBestPolyline(startPoint, endPoint, startDirection, endDirection, startBounds, endBounds, clearance);
|
|
574
|
+
moveTo$1(path, best[0], best[1]);
|
|
575
|
+
for (let i = 2; i < best.length; i += 2) lineTo$1(path, best[i], best[i + 1]);
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const {moveTo: moveTo, lineTo: lineTo, bezierCurveTo: bezierCurveTo, drawPoints: drawPoints$1} = draw.PathCommandDataHelper;
|
|
187
580
|
|
|
188
581
|
const {isEmpty: isEmpty, getPoint: getPoint} = draw.BoundsHelper;
|
|
189
582
|
|
|
@@ -276,7 +669,7 @@ exports.Linker = class Linker extends draw.Line {
|
|
|
276
669
|
}
|
|
277
670
|
}
|
|
278
671
|
updateLinkerPoint(type) {
|
|
279
|
-
const {startPoint: startPoint, endPoint: endPoint} = this;
|
|
672
|
+
const {startPoint: startPoint, endPoint: endPoint, lineType: lineType} = this;
|
|
280
673
|
const nowPoint = type === "end" ? endPoint : startPoint;
|
|
281
674
|
if (nowPoint) {
|
|
282
675
|
const data = {};
|
|
@@ -301,6 +694,7 @@ exports.Linker = class Linker extends draw.Line {
|
|
|
301
694
|
if (isEmpty(boxBounds)) draw.LeafHelper.updateBounds(ui);
|
|
302
695
|
p = getPoint(boxBounds, point, true);
|
|
303
696
|
ui.getWorldPointByBox(p, this, false, true);
|
|
697
|
+
if (lineType === "polyline") data.bounds = ui.getBounds("box", this.parent);
|
|
304
698
|
}
|
|
305
699
|
} else {
|
|
306
700
|
if (draw.isObject(point)) p = point;
|
|
@@ -326,56 +720,69 @@ exports.Linker = class Linker extends draw.Line {
|
|
|
326
720
|
}
|
|
327
721
|
const startPoint = startData.point, endPoint = endData.point;
|
|
328
722
|
const startDirection = startData.direction, endDirection = endData.direction;
|
|
329
|
-
const half = {
|
|
330
|
-
x: abs((startPoint.x - endPoint.x) / 2),
|
|
331
|
-
y: abs((startPoint.y - endPoint.y) / 2)
|
|
332
|
-
};
|
|
333
723
|
const arrowWidth = this.__.strokeWidth * 8;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
724
|
+
switch (data.lineType) {
|
|
725
|
+
case "straight":
|
|
726
|
+
moveTo(path, startPoint.x, startPoint.y);
|
|
727
|
+
lineTo(path, endPoint.x, endPoint.y);
|
|
728
|
+
break;
|
|
729
|
+
|
|
730
|
+
case "polyline":
|
|
731
|
+
drawPolyline(path, startPoint, endPoint, startDirection, endDirection, startData.bounds, endData.bounds, arrowWidth, hasPoints && draw.PointHelper.toNumberPoints(data.points));
|
|
732
|
+
break;
|
|
733
|
+
|
|
734
|
+
case "curve":
|
|
735
|
+
default:
|
|
736
|
+
const half = {
|
|
737
|
+
x: abs((startPoint.x - endPoint.x) / 2),
|
|
738
|
+
y: abs((startPoint.y - endPoint.y) / 2)
|
|
739
|
+
};
|
|
740
|
+
let minCtrl;
|
|
741
|
+
if (this.__.__useArrow) {
|
|
742
|
+
const ratio = min(getDistance(startPoint, endPoint) / (arrowWidth * 25), 1);
|
|
743
|
+
minCtrl = max(arrowWidth * ratio * 12, arrowWidth * 3);
|
|
744
|
+
} else {
|
|
343
745
|
minCtrl = arrowWidth * 2;
|
|
344
746
|
}
|
|
345
|
-
}
|
|
346
|
-
half.x = max(minCtrl, half.x);
|
|
347
|
-
half.y = max(minCtrl, half.y);
|
|
348
|
-
if (hasPoints) {
|
|
349
|
-
minCtrl *= .6;
|
|
350
|
-
const points = draw.PointHelper.toNumberPoints(data.points);
|
|
351
|
-
drawPoints$1(path, [ startPoint.x, startPoint.y, ...points, endPoint.x, endPoint.y ], data.curve, false, data);
|
|
352
|
-
if (startDirection) {
|
|
353
|
-
const handleX = path[4];
|
|
354
|
-
const handleY = path[5];
|
|
355
|
-
half.x = max(minCtrl, abs(handleX - startPoint.x));
|
|
356
|
-
half.y = max(minCtrl, abs(handleY - startPoint.y));
|
|
357
|
-
const startCtrlPoint = getCtrlPoint(this, startNode, startPoint, startDirection, half);
|
|
358
|
-
path[4] = startCtrlPoint.x;
|
|
359
|
-
path[5] = startCtrlPoint.y;
|
|
360
|
-
}
|
|
361
|
-
const len = path.length;
|
|
362
747
|
if (endDirection) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
748
|
+
if (isOppositeDirection(startDirection, endDirection) && startDirection === getDirectionForFromPoint(startPoint, endPoint)) {
|
|
749
|
+
minCtrl = arrowWidth * 2;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
half.x = max(minCtrl, half.x);
|
|
753
|
+
half.y = max(minCtrl, half.y);
|
|
754
|
+
if (hasPoints) {
|
|
755
|
+
minCtrl *= .6;
|
|
756
|
+
const points = draw.PointHelper.toNumberPoints(data.points);
|
|
757
|
+
drawPoints$1(path, [ startPoint.x, startPoint.y, ...points, endPoint.x, endPoint.y ], data.curve, false, data);
|
|
758
|
+
if (startDirection) {
|
|
759
|
+
const handleX = path[4];
|
|
760
|
+
const handleY = path[5];
|
|
761
|
+
half.x = max(minCtrl, abs(handleX - startPoint.x));
|
|
762
|
+
half.y = max(minCtrl, abs(handleY - startPoint.y));
|
|
763
|
+
const startCtrlPoint = getCtrlPoint(this, startNode, startPoint, startDirection, half);
|
|
764
|
+
path[4] = startCtrlPoint.x;
|
|
765
|
+
path[5] = startCtrlPoint.y;
|
|
766
|
+
}
|
|
767
|
+
const len = path.length;
|
|
768
|
+
if (endDirection) {
|
|
769
|
+
const handleX = path[len - 4];
|
|
770
|
+
const handleY = path[len - 3];
|
|
771
|
+
half.x = max(minCtrl, abs(handleX - endPoint.x));
|
|
772
|
+
half.y = max(minCtrl, abs(handleY - endPoint.y));
|
|
773
|
+
const endCtrlPoint = getCtrlPoint(this, endNode, endPoint, endDirection, half);
|
|
774
|
+
path[len - 4] = endCtrlPoint.x;
|
|
775
|
+
path[len - 3] = endCtrlPoint.y;
|
|
776
|
+
} else {
|
|
777
|
+
path[len - 4] = endPoint.x;
|
|
778
|
+
path[len - 3] = endPoint.y;
|
|
779
|
+
}
|
|
370
780
|
} else {
|
|
371
|
-
|
|
372
|
-
|
|
781
|
+
const startCtrlPoint = startDirection ? getCtrlPoint(this, startNode, startPoint, startDirection, half) : startPoint;
|
|
782
|
+
const endCtrlPoint = endDirection ? getCtrlPoint(this, endNode, endPoint, endDirection, half) : endPoint;
|
|
783
|
+
moveTo(path, startPoint.x, startPoint.y);
|
|
784
|
+
bezierCurveTo(path, startCtrlPoint.x, startCtrlPoint.y, endCtrlPoint.x, endCtrlPoint.y, endPoint.x, endPoint.y);
|
|
373
785
|
}
|
|
374
|
-
} else {
|
|
375
|
-
const startCtrlPoint = startDirection ? getCtrlPoint(this, startNode, startPoint, startDirection, half) : startPoint;
|
|
376
|
-
const endCtrlPoint = endDirection ? getCtrlPoint(this, endNode, endPoint, endDirection, half) : endPoint;
|
|
377
|
-
moveTo(path, startPoint.x, startPoint.y);
|
|
378
|
-
bezierCurveTo(path, startCtrlPoint.x, startCtrlPoint.y, endCtrlPoint.x, endCtrlPoint.y, endPoint.x, endPoint.y);
|
|
379
786
|
}
|
|
380
787
|
}
|
|
381
788
|
createStartPoint(worldPoint, node, options) {
|
|
@@ -445,6 +852,8 @@ exports.Linker = class Linker extends draw.Line {
|
|
|
445
852
|
|
|
446
853
|
__decorate([ draw.dataProcessor(LinkerData) ], exports.Linker.prototype, "__", void 0);
|
|
447
854
|
|
|
855
|
+
__decorate([ draw.boundsType("curve") ], exports.Linker.prototype, "lineType", void 0);
|
|
856
|
+
|
|
448
857
|
__decorate([ linkerPointType() ], exports.Linker.prototype, "startPoint", void 0);
|
|
449
858
|
|
|
450
859
|
__decorate([ linkerPointType() ], exports.Linker.prototype, "endPoint", void 0);
|