@joint/core 4.0.3 → 4.1.0-beta.1
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/README.md +2 -10
- package/dist/geometry.js +4962 -6132
- package/dist/geometry.min.js +2 -2
- package/dist/joint.d.ts +338 -52
- package/dist/joint.js +34067 -37525
- package/dist/joint.min.js +2 -2
- package/dist/joint.nowrap.js +34067 -37525
- package/dist/joint.nowrap.min.js +2 -2
- package/dist/vectorizer.js +7288 -8907
- package/dist/vectorizer.min.js +2 -2
- package/dist/version.mjs +1 -1
- package/package.json +10 -15
- package/src/{linkTools → cellTools}/Button.mjs +8 -6
- package/src/{elementTools → cellTools}/Control.mjs +3 -3
- package/src/{linkTools → cellTools}/HoverConnect.mjs +1 -1
- package/src/dia/Cell.mjs +60 -33
- package/src/dia/CellView.mjs +75 -8
- package/src/dia/ElementView.mjs +13 -8
- package/src/dia/Graph.mjs +148 -40
- package/src/dia/HighlighterView.mjs +8 -4
- package/src/dia/LinkView.mjs +61 -4
- package/src/dia/Paper.mjs +84 -0
- package/src/dia/ToolView.mjs +29 -4
- package/src/dia/ToolsView.mjs +25 -10
- package/src/dia/attributes/connection.mjs +5 -0
- package/src/dia/attributes/defs.mjs +3 -0
- package/src/dia/attributes/eval.mjs +3 -3
- package/src/dia/attributes/index.mjs +3 -0
- package/src/dia/attributes/shape.mjs +4 -0
- package/src/dia/attributes/text.mjs +41 -15
- package/src/dia/ports.mjs +4 -0
- package/src/elementTools/HoverConnect.mjs +5 -5
- package/src/elementTools/index.mjs +5 -4
- package/src/env/index.mjs +5 -0
- package/src/g/rect.mjs +13 -5
- package/src/layout/ports/port.mjs +4 -5
- package/src/linkTools/Anchor.mjs +1 -1
- package/src/linkTools/Arrowhead.mjs +2 -1
- package/src/linkTools/RotateLabel.mjs +110 -0
- package/src/linkTools/Segments.mjs +1 -1
- package/src/linkTools/Vertices.mjs +41 -4
- package/src/linkTools/index.mjs +7 -4
- package/src/mvc/View.mjs +0 -1
- package/src/mvc/ViewBase.mjs +2 -1
- package/src/routers/rightAngle.mjs +538 -140
- package/src/shapes/standard.mjs +8 -1
- package/src/{dia/attributes → util}/calc.mjs +24 -12
- package/src/util/index.mjs +1 -0
- package/src/util/util.mjs +39 -0
- package/src/util/utilHelpers.mjs +2 -1
- package/types/geometry.d.ts +6 -1
- package/types/joint.d.ts +331 -50
- /package/src/{linkTools → cellTools}/Boundary.mjs +0 -0
- /package/src/{linkTools → cellTools}/Connect.mjs +0 -0
- /package/src/{linkTools → cellTools}/helpers.mjs +0 -0
|
@@ -96,24 +96,22 @@ function resolveForTopSourceSide(source, target, nextInLine) {
|
|
|
96
96
|
if (nextInLine.point.x === ax) return Directions.BOTTOM;
|
|
97
97
|
return Directions.LEFT;
|
|
98
98
|
}
|
|
99
|
-
if (tx < smx0 && ty
|
|
100
|
-
if (tx > smx1 && ty
|
|
99
|
+
if (tx < smx0 && ty > smy0) return Directions.TOP;
|
|
100
|
+
if (tx > smx1 && ty > smy0) return Directions.TOP;
|
|
101
101
|
if (tx >= smx0 && tx <= ax && ty > sy1) {
|
|
102
102
|
if (nextInLine.point.x < tx) {
|
|
103
103
|
return Directions.RIGHT;
|
|
104
104
|
}
|
|
105
|
-
|
|
106
105
|
return Directions.LEFT;
|
|
107
106
|
}
|
|
108
107
|
if (tx <= smx1 && tx >= ax && ty > sy1) {
|
|
109
108
|
if (nextInLine.point.x < tx) {
|
|
110
109
|
return Directions.RIGHT;
|
|
111
110
|
}
|
|
112
|
-
|
|
113
111
|
return Directions.LEFT;
|
|
114
112
|
}
|
|
115
113
|
|
|
116
|
-
return Directions.
|
|
114
|
+
return Directions.BOTTOM;
|
|
117
115
|
}
|
|
118
116
|
|
|
119
117
|
function resolveForBottomSourceSide(source, target, nextInLine) {
|
|
@@ -136,24 +134,22 @@ function resolveForBottomSourceSide(source, target, nextInLine) {
|
|
|
136
134
|
if (nextInLine.point.x === ax) return Directions.TOP;
|
|
137
135
|
return Directions.LEFT;
|
|
138
136
|
}
|
|
139
|
-
if (tx < smx0 && ty
|
|
140
|
-
if (tx > smx1 && ty
|
|
137
|
+
if (tx < smx0 && ty < smy1) return Directions.BOTTOM;
|
|
138
|
+
if (tx > smx1 && ty < smy1) return Directions.BOTTOM;
|
|
141
139
|
if (tx >= smx0 && tx <= ax && ty < sy0) {
|
|
142
140
|
if (nextInLine.point.x < tx) {
|
|
143
141
|
return Directions.RIGHT;
|
|
144
142
|
}
|
|
145
|
-
|
|
146
143
|
return Directions.LEFT;
|
|
147
144
|
}
|
|
148
145
|
if (tx <= smx1 && tx >= ax && ty < sy0) {
|
|
149
146
|
if (nextInLine.point.x < tx) {
|
|
150
147
|
return Directions.RIGHT;
|
|
151
148
|
}
|
|
152
|
-
|
|
153
149
|
return Directions.LEFT;
|
|
154
150
|
}
|
|
155
151
|
|
|
156
|
-
return Directions.
|
|
152
|
+
return Directions.TOP;
|
|
157
153
|
}
|
|
158
154
|
|
|
159
155
|
function resolveForLeftSourceSide(source, target, nextInLine) {
|
|
@@ -170,8 +166,8 @@ function resolveForLeftSourceSide(source, target, nextInLine) {
|
|
|
170
166
|
if (tx < ax && ty === ay) return Directions.RIGHT;
|
|
171
167
|
if (tx <= smx0 && ty < ay) return Directions.BOTTOM;
|
|
172
168
|
if (tx <= smx0 && ty > ay) return Directions.TOP;
|
|
173
|
-
if (tx >=
|
|
174
|
-
if (tx >=
|
|
169
|
+
if (tx >= smx0 && ty < smy0) return Directions.LEFT;
|
|
170
|
+
if (tx >= smx0 && ty > smy1) return Directions.LEFT;
|
|
175
171
|
if (tx > sx1 && ty >= smy0 && ty <= ay) {
|
|
176
172
|
if (nextInLine.point.y < ty) {
|
|
177
173
|
return Directions.BOTTOM;
|
|
@@ -187,7 +183,7 @@ function resolveForLeftSourceSide(source, target, nextInLine) {
|
|
|
187
183
|
return Directions.TOP;
|
|
188
184
|
}
|
|
189
185
|
|
|
190
|
-
return Directions.
|
|
186
|
+
return Directions.RIGHT;
|
|
191
187
|
}
|
|
192
188
|
|
|
193
189
|
function resolveForRightSourceSide(source, target, nextInLine) {
|
|
@@ -204,8 +200,8 @@ function resolveForRightSourceSide(source, target, nextInLine) {
|
|
|
204
200
|
if (tx > ax && ty === ay) return Directions.LEFT;
|
|
205
201
|
if (tx >= smx1 && ty < ay) return Directions.BOTTOM;
|
|
206
202
|
if (tx >= smx1 && ty > ay) return Directions.TOP;
|
|
207
|
-
if (tx <=
|
|
208
|
-
if (tx <=
|
|
203
|
+
if (tx <= smx1 && ty < smy0) return Directions.RIGHT;
|
|
204
|
+
if (tx <= smx1 && ty > smy1) return Directions.RIGHT;
|
|
209
205
|
if (tx < sx0 && ty >= smy0 && ty <= ay) {
|
|
210
206
|
if (nextInLine.point.y < ty) {
|
|
211
207
|
return Directions.BOTTOM;
|
|
@@ -221,7 +217,7 @@ function resolveForRightSourceSide(source, target, nextInLine) {
|
|
|
221
217
|
return Directions.TOP;
|
|
222
218
|
}
|
|
223
219
|
|
|
224
|
-
return Directions.
|
|
220
|
+
return Directions.LEFT;
|
|
225
221
|
}
|
|
226
222
|
|
|
227
223
|
function resolveInitialDirection(source, target, nextInLine) {
|
|
@@ -326,6 +322,146 @@ function getOutsidePoint(side, pointData, margin) {
|
|
|
326
322
|
return outsidePoint;
|
|
327
323
|
}
|
|
328
324
|
|
|
325
|
+
function createLoop(from, to, { dx = 0, dy = 0 }) {
|
|
326
|
+
const p1 = { x: from.point.x + dx, y: from.point.y + dy };
|
|
327
|
+
const p2 = { x: to.point.x + dx, y: to.point.y + dy };
|
|
328
|
+
|
|
329
|
+
return [from.point, p1, p2, to.point];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function loopSegment(from, to, connectionSegmentAngle, margin) {
|
|
333
|
+
// Find out the loop coordinates.
|
|
334
|
+
const angle = g.normalizeAngle(connectionSegmentAngle - 90);
|
|
335
|
+
|
|
336
|
+
let dx = 0;
|
|
337
|
+
let dy = 0;
|
|
338
|
+
|
|
339
|
+
if (angle === 90) {
|
|
340
|
+
dy = -margin;
|
|
341
|
+
} else if (angle === 180) {
|
|
342
|
+
dx = -margin;
|
|
343
|
+
} else if (angle === 270) {
|
|
344
|
+
dy = margin;
|
|
345
|
+
} else if (angle === 0) {
|
|
346
|
+
dx = margin;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const loopRoute = createLoop(from, to, { dx, dy });
|
|
350
|
+
|
|
351
|
+
const secondCreatedPoint = loopRoute[2];
|
|
352
|
+
const loopEndSegment = new g.Line(to.point, secondCreatedPoint);
|
|
353
|
+
// The direction in which the loop should continue.
|
|
354
|
+
const continueDirection = ANGLE_DIRECTION_MAP[getSegmentAngle(loopEndSegment)];
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
loopRoute,
|
|
358
|
+
continueDirection
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Calculates the distances along the horizontal axis for the left and right route.
|
|
363
|
+
function getHorizontalDistance(source, target) {
|
|
364
|
+
|
|
365
|
+
const { x0: sx0, x1: sx1, outsidePoint: sourcePoint } = source;
|
|
366
|
+
const { x0: tx0, x1: tx1, outsidePoint: targetPoint } = target;
|
|
367
|
+
|
|
368
|
+
// Furthest left boundary
|
|
369
|
+
let leftBoundary = Math.min(sx0, tx0);
|
|
370
|
+
// Furthest right boundary
|
|
371
|
+
let rightBoundary = Math.max(sx1, tx1);
|
|
372
|
+
|
|
373
|
+
// If the source and target elements are on the same side, we need to figure out what shape defines the boundary.
|
|
374
|
+
if (source.direction === target.direction) {
|
|
375
|
+
|
|
376
|
+
const aboveShape = source.y0 < target.y0 ? source : target;
|
|
377
|
+
const belowShape = aboveShape === source ? target : source;
|
|
378
|
+
|
|
379
|
+
// The source and target anchors are on the top => then the `aboveShape` defines the boundary.
|
|
380
|
+
// The source and target anchors are on the bottom => then the `belowShape` defines the boundary.
|
|
381
|
+
const boundaryDefiningShape = source.direction === Directions.TOP ? aboveShape : belowShape;
|
|
382
|
+
|
|
383
|
+
leftBoundary = boundaryDefiningShape.x0;
|
|
384
|
+
rightBoundary = boundaryDefiningShape.x1;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const { x: sox } = sourcePoint;
|
|
388
|
+
const { x: tox } = targetPoint;
|
|
389
|
+
|
|
390
|
+
// Calculate the distances for the left route
|
|
391
|
+
const leftDistance1 = Math.abs(sox - leftBoundary);
|
|
392
|
+
const leftDistance2 = Math.abs(tox - leftBoundary);
|
|
393
|
+
const leftD = leftDistance1 + leftDistance2;
|
|
394
|
+
|
|
395
|
+
// Calculate the distances for the right route
|
|
396
|
+
const rightDistance1 = Math.abs(sox - rightBoundary);
|
|
397
|
+
const rightDistance2 = Math.abs(tox - rightBoundary);
|
|
398
|
+
const rightD = rightDistance1 + rightDistance2;
|
|
399
|
+
|
|
400
|
+
return [leftD, rightD];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Calculates the distances along the vertical axis for the top and bottom route.
|
|
404
|
+
function getVerticalDistance(source, target) {
|
|
405
|
+
|
|
406
|
+
const { y0: sy0, y1: sy1, outsidePoint: sourcePoint } = source;
|
|
407
|
+
const { y0: ty0, y1: ty1, outsidePoint: targetPoint } = target;
|
|
408
|
+
|
|
409
|
+
// Furthest top boundary
|
|
410
|
+
let topBoundary = Math.min(sy0, ty0);
|
|
411
|
+
// Furthest bottom boundary
|
|
412
|
+
let bottomBoundary = Math.max(sy1, ty1);
|
|
413
|
+
|
|
414
|
+
// If the source and target elements are on the same side, we need to figure out what shape defines the boundary.
|
|
415
|
+
if (source.direction === target.direction) {
|
|
416
|
+
|
|
417
|
+
const leftShape = source.x0 < target.x0 ? source : target;
|
|
418
|
+
const rightShape = leftShape === source ? target : source;
|
|
419
|
+
|
|
420
|
+
// The source and target anchors are on the left => then the `leftShape` defines the boundary.
|
|
421
|
+
// The source and target anchors are on the right => then the `rightShape` defines the boundary.
|
|
422
|
+
const boundaryDefiningShape = source.direction === Directions.LEFT ? leftShape : rightShape;
|
|
423
|
+
|
|
424
|
+
topBoundary = boundaryDefiningShape.y0;
|
|
425
|
+
bottomBoundary = boundaryDefiningShape.y1;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const { y: soy } = sourcePoint;
|
|
429
|
+
const { y: toy } = targetPoint;
|
|
430
|
+
|
|
431
|
+
// Calculate the distances for the top route
|
|
432
|
+
const topDistance1 = Math.abs(soy - topBoundary);
|
|
433
|
+
const topDistance2 = Math.abs(toy - topBoundary);
|
|
434
|
+
const topD = topDistance1 + topDistance2;
|
|
435
|
+
|
|
436
|
+
// Calculate the distances for the bottom route
|
|
437
|
+
const bottomDistance1 = Math.abs(soy - bottomBoundary);
|
|
438
|
+
const bottomDistance2 = Math.abs(toy - bottomBoundary);
|
|
439
|
+
const bottomD = bottomDistance1 + bottomDistance2;
|
|
440
|
+
|
|
441
|
+
return [topD, bottomD];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Inflate bbox in 3 directions depending on the direction of the anchor
|
|
445
|
+
// don't inflate in the opposite direction of the anchor
|
|
446
|
+
function moveAndExpandBBox(bbox, direction, margin) {
|
|
447
|
+
switch (direction) {
|
|
448
|
+
case Directions.LEFT:
|
|
449
|
+
bbox.inflate(0, margin).moveAndExpand({ x: -margin, width: margin });
|
|
450
|
+
break;
|
|
451
|
+
case Directions.RIGHT:
|
|
452
|
+
bbox.inflate(0, margin).moveAndExpand({ width: margin });
|
|
453
|
+
break;
|
|
454
|
+
case Directions.TOP:
|
|
455
|
+
bbox.inflate(margin, 0).moveAndExpand({ y: -margin, height: margin });
|
|
456
|
+
break;
|
|
457
|
+
case Directions.BOTTOM:
|
|
458
|
+
bbox.inflate(margin, 0).moveAndExpand({ height: margin });
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return bbox;
|
|
463
|
+
}
|
|
464
|
+
|
|
329
465
|
function routeBetweenPoints(source, target, opt = {}) {
|
|
330
466
|
const { point: sourcePoint, x0: sx0, y0: sy0, width: sourceWidth, height: sourceHeight, margin: sourceMargin } = source;
|
|
331
467
|
const { point: targetPoint, x0: tx0, y0: ty0, width: targetWidth, height: targetHeight, margin: targetMargin } = target;
|
|
@@ -366,45 +502,136 @@ function routeBetweenPoints(source, target, opt = {}) {
|
|
|
366
502
|
const inflatedSourceBBox = sourceBBox.clone().inflate(sourceMargin);
|
|
367
503
|
const inflatedTargetBBox = targetBBox.clone().inflate(targetMargin);
|
|
368
504
|
|
|
505
|
+
const sourceForDistance = Object.assign({}, source, { x1: sx1, y1: sy1, outsidePoint: sourceOutsidePoint, direction: sourceSide });
|
|
506
|
+
const targetForDistance = Object.assign({}, target, { x1: tx1, y1: ty1, outsidePoint: targetOutsidePoint, direction: targetSide });
|
|
507
|
+
|
|
508
|
+
// Distances used to determine the shortest route along the connections on horizontal sides for
|
|
509
|
+
// bottom => bottom
|
|
510
|
+
// top => bottom
|
|
511
|
+
// bottom => top
|
|
512
|
+
// top => top
|
|
513
|
+
const [leftD, rightD] = getHorizontalDistance(sourceForDistance, targetForDistance);
|
|
514
|
+
|
|
515
|
+
// Distances used to determine the shortest route along the connection on vertical sides for
|
|
516
|
+
// left => left
|
|
517
|
+
// left => right
|
|
518
|
+
// right => right
|
|
519
|
+
// right => left
|
|
520
|
+
const [topD, bottomD] = getVerticalDistance(sourceForDistance, targetForDistance);
|
|
521
|
+
|
|
522
|
+
// All possible combinations of source and target sides
|
|
369
523
|
if (sourceSide === 'left' && targetSide === 'right') {
|
|
370
|
-
|
|
524
|
+
const isPointInsideSource = inflatedSourceBBox.containsPoint(targetOutsidePoint);
|
|
525
|
+
const isPointInsideTarget = inflatedTargetBBox.containsPoint(sourceOutsidePoint);
|
|
526
|
+
|
|
527
|
+
// Use S-shaped connection
|
|
528
|
+
if (isPointInsideSource || isPointInsideTarget) {
|
|
529
|
+
const middleOfAnchors = (soy + toy) / 2;
|
|
530
|
+
|
|
531
|
+
return [
|
|
532
|
+
{ x: sox, y: soy },
|
|
533
|
+
{ x: sox, y: middleOfAnchors },
|
|
534
|
+
{ x: tox, y: middleOfAnchors },
|
|
535
|
+
{ x: tox, y: toy }
|
|
536
|
+
];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (smx0 < tox) {
|
|
371
540
|
let y = middleOfHorizontalSides;
|
|
372
|
-
|
|
373
|
-
|
|
541
|
+
let x1 = sox;
|
|
542
|
+
let x2 = tox;
|
|
543
|
+
|
|
544
|
+
const isUpwardsShorter = topD < bottomD;
|
|
545
|
+
|
|
546
|
+
// If the source and target elements overlap, we need to make sure the connection
|
|
547
|
+
// goes around the target element.
|
|
548
|
+
if ((y >= smy0 && y <= smy1) || (y >= tmy0 && y <= tmy1)) {
|
|
549
|
+
if (smy1 >= tmy0 && isUpwardsShorter) {
|
|
374
550
|
y = Math.min(tmy0, smy0);
|
|
375
|
-
} else if (
|
|
551
|
+
} else if (smy0 <= tmy1 && !isUpwardsShorter) {
|
|
376
552
|
y = Math.max(tmy1, smy1);
|
|
377
553
|
}
|
|
554
|
+
|
|
555
|
+
// This handles the case when the source and target elements overlap as well as
|
|
556
|
+
// the case when the source is to the left of the target element.
|
|
557
|
+
x1 = Math.min(sox, tmx0);
|
|
558
|
+
x2 = Math.max(tox, smx1);
|
|
559
|
+
|
|
560
|
+
// This is an edge case when the source and target intersect and
|
|
561
|
+
if ((isUpwardsShorter && soy < ty0) || (!isUpwardsShorter && soy > ty1)) {
|
|
562
|
+
// the path should no longer rely on minimal x boundary in `x1`
|
|
563
|
+
x1 = sox;
|
|
564
|
+
} else if ((isUpwardsShorter && toy < sy0) || (!isUpwardsShorter && toy > sy1)) {
|
|
565
|
+
// the path should no longer rely on maximal x boundary in `x2`
|
|
566
|
+
x2 = tox;
|
|
567
|
+
}
|
|
378
568
|
}
|
|
569
|
+
|
|
379
570
|
return [
|
|
380
|
-
{ x:
|
|
381
|
-
{ x:
|
|
382
|
-
{ x:
|
|
383
|
-
{ x:
|
|
571
|
+
{ x: x1, y: soy },
|
|
572
|
+
{ x: x1, y },
|
|
573
|
+
{ x: x2, y },
|
|
574
|
+
{ x: x2, y: toy }
|
|
384
575
|
];
|
|
385
576
|
}
|
|
386
577
|
|
|
387
578
|
const x = (sox + tox) / 2;
|
|
388
579
|
return [
|
|
389
580
|
{ x, y: soy },
|
|
390
|
-
{ x, y: toy }
|
|
581
|
+
{ x, y: toy },
|
|
391
582
|
];
|
|
392
583
|
} else if (sourceSide === 'right' && targetSide === 'left') {
|
|
393
|
-
|
|
584
|
+
const isPointInsideSource = inflatedSourceBBox.containsPoint(targetOutsidePoint);
|
|
585
|
+
const isPointInsideTarget = inflatedTargetBBox.containsPoint(sourceOutsidePoint);
|
|
586
|
+
|
|
587
|
+
// Use S-shaped connection
|
|
588
|
+
if (isPointInsideSource || isPointInsideTarget) {
|
|
589
|
+
const middleOfAnchors = (soy + toy) / 2;
|
|
590
|
+
|
|
591
|
+
return [
|
|
592
|
+
{ x: sox, y: soy },
|
|
593
|
+
{ x: sox, y: middleOfAnchors },
|
|
594
|
+
{ x: tox, y: middleOfAnchors },
|
|
595
|
+
{ x: tox, y: toy }
|
|
596
|
+
];
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (smx1 > tox) {
|
|
394
600
|
let y = middleOfHorizontalSides;
|
|
395
|
-
|
|
396
|
-
|
|
601
|
+
let x1 = sox;
|
|
602
|
+
let x2 = tox;
|
|
603
|
+
|
|
604
|
+
const isUpwardsShorter = topD < bottomD;
|
|
605
|
+
|
|
606
|
+
// If the source and target elements overlap, we need to make sure the connection
|
|
607
|
+
// goes around the target element.
|
|
608
|
+
if ((y >= smy0 && y <= smy1) || (y >= tmy0 && y <= tmy1)) {
|
|
609
|
+
if (smy1 >= tmy0 && isUpwardsShorter) {
|
|
397
610
|
y = Math.min(tmy0, smy0);
|
|
398
|
-
} else if (
|
|
611
|
+
} else if (smy0 <= tmy1 && !isUpwardsShorter) {
|
|
399
612
|
y = Math.max(tmy1, smy1);
|
|
400
613
|
}
|
|
614
|
+
|
|
615
|
+
// This handles the case when the source and target elements overlap as well as
|
|
616
|
+
// the case when the source is to the left of the target element.
|
|
617
|
+
x1 = Math.max(sox, tmx1);
|
|
618
|
+
x2 = Math.min(tox, smx0);
|
|
619
|
+
|
|
620
|
+
// This is an edge case when the source and target intersect and
|
|
621
|
+
if ((isUpwardsShorter && soy < ty0) || (!isUpwardsShorter && soy > ty1)) {
|
|
622
|
+
// the path should no longer rely on maximal x boundary in `x1`
|
|
623
|
+
x1 = sox;
|
|
624
|
+
} else if ((isUpwardsShorter && toy < sy0) || (!isUpwardsShorter && toy > sy1)) {
|
|
625
|
+
// the path should no longer rely on minimal x boundary in `x2`
|
|
626
|
+
x2 = tox;
|
|
627
|
+
}
|
|
401
628
|
}
|
|
402
629
|
|
|
403
630
|
return [
|
|
404
|
-
{ x:
|
|
405
|
-
{ x:
|
|
406
|
-
{ x:
|
|
407
|
-
{ x:
|
|
631
|
+
{ x: x1, y: soy },
|
|
632
|
+
{ x: x1, y },
|
|
633
|
+
{ x: x2, y },
|
|
634
|
+
{ x: x2, y: toy }
|
|
408
635
|
];
|
|
409
636
|
}
|
|
410
637
|
|
|
@@ -414,58 +641,120 @@ function routeBetweenPoints(source, target, opt = {}) {
|
|
|
414
641
|
{ x, y: toy }
|
|
415
642
|
];
|
|
416
643
|
} else if (sourceSide === 'top' && targetSide === 'bottom') {
|
|
417
|
-
const isPointInsideSource =
|
|
644
|
+
const isPointInsideSource = inflatedSourceBBox.containsPoint(targetOutsidePoint);
|
|
645
|
+
const isPointInsideTarget = inflatedTargetBBox.containsPoint(sourceOutsidePoint);
|
|
418
646
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
647
|
+
// Use S-shaped connection
|
|
648
|
+
if (isPointInsideSource || isPointInsideTarget) {
|
|
649
|
+
const middleOfAnchors = (sox + tox) / 2;
|
|
422
650
|
|
|
423
|
-
|
|
424
|
-
y
|
|
425
|
-
|
|
651
|
+
return [
|
|
652
|
+
{ x: sox, y: soy },
|
|
653
|
+
{ x: middleOfAnchors, y: soy },
|
|
654
|
+
{ x: middleOfAnchors, y: toy },
|
|
655
|
+
{ x: tox, y: toy }
|
|
656
|
+
];
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (smy0 < toy) {
|
|
660
|
+
let x = middleOfVerticalSides;
|
|
661
|
+
let y1 = soy;
|
|
662
|
+
let y2 = toy;
|
|
663
|
+
|
|
664
|
+
const isLeftShorter = leftD < rightD;
|
|
665
|
+
|
|
666
|
+
// If the source and target elements overlap, we need to make sure the connection
|
|
667
|
+
// goes around the target element.
|
|
668
|
+
if ((x >= smx0 && x <= smx1) || (x >= tmx0 && x <= tmx1)) {
|
|
669
|
+
if (smx1 >= tmx0 && isLeftShorter) {
|
|
670
|
+
x = Math.min(tmx0, smx0);
|
|
671
|
+
} else if (smx0 <= tmx1 && !isLeftShorter) {
|
|
672
|
+
x = Math.max(tmx1, smx1);
|
|
673
|
+
}
|
|
426
674
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
675
|
+
// This handles the case when the source and target elements overlap as well as
|
|
676
|
+
// the case when the source is to the left of the target element.
|
|
677
|
+
y1 = Math.min(soy, tmy0);
|
|
678
|
+
y2 = Math.max(toy, smy1);
|
|
679
|
+
|
|
680
|
+
// This is an edge case when the source and target intersect and
|
|
681
|
+
if ((isLeftShorter && sox < tx0) || (!isLeftShorter && sox > tx1)) {
|
|
682
|
+
// the path should no longer rely on minimal y boundary in `y1`
|
|
683
|
+
y1 = soy;
|
|
684
|
+
} else if ((isLeftShorter && tox < sx0) || (!isLeftShorter && tox > sx1)) {
|
|
685
|
+
// the path should no longer rely on maximal y boundary in `y2`
|
|
686
|
+
y2 = toy;
|
|
687
|
+
}
|
|
431
688
|
}
|
|
432
689
|
|
|
433
690
|
return [
|
|
434
|
-
{ x: sox, y },
|
|
435
|
-
{ x, y },
|
|
436
|
-
{ x, y:
|
|
437
|
-
{ x: tox, y:
|
|
691
|
+
{ x: sox, y: y1 },
|
|
692
|
+
{ x, y: y1 },
|
|
693
|
+
{ x, y: y2 },
|
|
694
|
+
{ x: tox, y: y2 }
|
|
438
695
|
];
|
|
439
696
|
}
|
|
697
|
+
|
|
440
698
|
const y = (soy + toy) / 2;
|
|
441
699
|
return [
|
|
442
700
|
{ x: sox, y },
|
|
443
701
|
{ x: tox, y }
|
|
444
702
|
];
|
|
445
703
|
} else if (sourceSide === 'bottom' && targetSide === 'top') {
|
|
446
|
-
const isPointInsideSource =
|
|
704
|
+
const isPointInsideSource = inflatedSourceBBox.containsPoint(targetOutsidePoint);
|
|
705
|
+
const isPointInsideTarget = inflatedTargetBBox.containsPoint(sourceOutsidePoint);
|
|
447
706
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
707
|
+
// Use S-shaped connection
|
|
708
|
+
if (isPointInsideSource || isPointInsideTarget) {
|
|
709
|
+
const middleOfAnchors = (sox + tox) / 2;
|
|
451
710
|
|
|
452
|
-
|
|
453
|
-
y
|
|
454
|
-
|
|
711
|
+
return [
|
|
712
|
+
{ x: sox, y: soy },
|
|
713
|
+
{ x: middleOfAnchors, y: soy },
|
|
714
|
+
{ x: middleOfAnchors, y: toy },
|
|
715
|
+
{ x: tox, y: toy }
|
|
716
|
+
];
|
|
717
|
+
}
|
|
455
718
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
719
|
+
if (smy1 > toy) {
|
|
720
|
+
let x = middleOfVerticalSides;
|
|
721
|
+
let y1 = soy;
|
|
722
|
+
let y2 = toy;
|
|
723
|
+
|
|
724
|
+
const isLeftShorter = leftD < rightD;
|
|
725
|
+
|
|
726
|
+
// If the source and target elements overlap, we need to make sure the connection
|
|
727
|
+
// goes around the target element.
|
|
728
|
+
if ((x >= smx0 && x <= smx1) || (x >= tmx0 && x <= tmx1)) {
|
|
729
|
+
if (smx1 >= tmx0 && isLeftShorter) {
|
|
730
|
+
x = Math.min(tmx0, smx0);
|
|
731
|
+
} else if (smx0 <= tmx1 && !isLeftShorter) {
|
|
732
|
+
x = Math.max(tmx1, smx1);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// This handles the case when the source and target elements overlap as well as
|
|
736
|
+
// the case when the source is to the left of the target element.
|
|
737
|
+
y1 = Math.max(soy, tmy1);
|
|
738
|
+
y2 = Math.min(toy, smy0);
|
|
739
|
+
|
|
740
|
+
// This is an edge case when the source and target intersect and
|
|
741
|
+
if ((isLeftShorter && sox < tx0) || (!isLeftShorter && sox > tx1)) {
|
|
742
|
+
// the path should no longer rely on maximal y boundary in `y1`
|
|
743
|
+
y1 = soy;
|
|
744
|
+
} else if ((isLeftShorter && tox < sx0) || (!isLeftShorter && tox > sx1)) {
|
|
745
|
+
// the path should no longer rely on minimal y boundary in `y2`
|
|
746
|
+
y2 = toy;
|
|
747
|
+
}
|
|
460
748
|
}
|
|
461
749
|
|
|
462
750
|
return [
|
|
463
|
-
{ x: sox, y },
|
|
464
|
-
{ x, y },
|
|
465
|
-
{ x, y:
|
|
466
|
-
{ x: tox, y:
|
|
751
|
+
{ x: sox, y: y1 },
|
|
752
|
+
{ x, y: y1 },
|
|
753
|
+
{ x, y: y2 },
|
|
754
|
+
{ x: tox, y: y2 }
|
|
467
755
|
];
|
|
468
756
|
}
|
|
757
|
+
|
|
469
758
|
const y = (soy + toy) / 2;
|
|
470
759
|
return [
|
|
471
760
|
{ x: sox, y },
|
|
@@ -478,7 +767,8 @@ function routeBetweenPoints(source, target, opt = {}) {
|
|
|
478
767
|
(soy <= ty0 && (inflatedSourceBBox.bottomRight().x <= tox || inflatedSourceBBox.bottomLeft().x >= tox)) ||
|
|
479
768
|
(soy >= ty0 && (inflatedTargetBBox.bottomRight().x <= sox || inflatedTargetBBox.bottomLeft().x >= sox));
|
|
480
769
|
|
|
481
|
-
if
|
|
770
|
+
// U-shape connection is a straight line if `sox` and `tox` are the same
|
|
771
|
+
if (useUShapeConnection && sox !== tox) {
|
|
482
772
|
return [
|
|
483
773
|
{ x: sox, y: Math.min(soy, toy) },
|
|
484
774
|
{ x: tox, y: Math.min(soy, toy) }
|
|
@@ -490,16 +780,17 @@ function routeBetweenPoints(source, target, opt = {}) {
|
|
|
490
780
|
let y2 = Math.min((sy0 + ty1) / 2, soy);
|
|
491
781
|
|
|
492
782
|
if (toy < soy) {
|
|
493
|
-
|
|
783
|
+
// Use the shortest path along the connections on horizontal sides
|
|
784
|
+
if (rightD > leftD) {
|
|
494
785
|
x = Math.min(sox, tmx0);
|
|
495
786
|
} else {
|
|
496
787
|
x = Math.max(sox, tmx1);
|
|
497
788
|
}
|
|
498
789
|
} else {
|
|
499
|
-
if (
|
|
500
|
-
x = Math.max(tox, smx1);
|
|
501
|
-
} else {
|
|
790
|
+
if (rightD > leftD) {
|
|
502
791
|
x = Math.min(tox, smx0);
|
|
792
|
+
} else {
|
|
793
|
+
x = Math.max(tox, smx1);
|
|
503
794
|
}
|
|
504
795
|
}
|
|
505
796
|
|
|
@@ -516,7 +807,8 @@ function routeBetweenPoints(source, target, opt = {}) {
|
|
|
516
807
|
(soy >= toy && (inflatedSourceBBox.topRight().x <= tox || inflatedSourceBBox.topLeft().x >= tox)) ||
|
|
517
808
|
(soy <= toy && (inflatedTargetBBox.topRight().x <= sox || inflatedTargetBBox.topLeft().x >= sox));
|
|
518
809
|
|
|
519
|
-
if
|
|
810
|
+
// U-shape connection is a straight line if `sox` and `tox` are the same
|
|
811
|
+
if (useUShapeConnection && sox !== tox) {
|
|
520
812
|
return [
|
|
521
813
|
{ x: sox, y: Math.max(soy, toy) },
|
|
522
814
|
{ x: tox, y: Math.max(soy, toy) }
|
|
@@ -528,16 +820,17 @@ function routeBetweenPoints(source, target, opt = {}) {
|
|
|
528
820
|
let y2 = Math.max((sy1 + ty0) / 2, soy);
|
|
529
821
|
|
|
530
822
|
if (toy > soy) {
|
|
531
|
-
|
|
823
|
+
// Use the shortest path along the connections on horizontal sides
|
|
824
|
+
if (rightD > leftD) {
|
|
532
825
|
x = Math.min(sox, tmx0);
|
|
533
826
|
} else {
|
|
534
827
|
x = Math.max(sox, tmx1);
|
|
535
828
|
}
|
|
536
829
|
} else {
|
|
537
|
-
if (
|
|
538
|
-
x = Math.max(tox, smx1);
|
|
539
|
-
} else {
|
|
830
|
+
if (rightD > leftD) {
|
|
540
831
|
x = Math.min(tox, smx0);
|
|
832
|
+
} else {
|
|
833
|
+
x = Math.max(tox, smx1);
|
|
541
834
|
}
|
|
542
835
|
}
|
|
543
836
|
|
|
@@ -554,7 +847,8 @@ function routeBetweenPoints(source, target, opt = {}) {
|
|
|
554
847
|
(sox <= tox && (inflatedSourceBBox.bottomRight().y <= toy || inflatedSourceBBox.topRight().y >= toy)) ||
|
|
555
848
|
(sox >= tox && (inflatedTargetBBox.bottomRight().y <= soy || inflatedTargetBBox.topRight().y >= soy));
|
|
556
849
|
|
|
557
|
-
if
|
|
850
|
+
// U-shape connection is a straight line if `soy` and `toy` are the same
|
|
851
|
+
if (useUShapeConnection && soy !== toy) {
|
|
558
852
|
return [
|
|
559
853
|
{ x: Math.min(sox, tox), y: soy },
|
|
560
854
|
{ x: Math.min(sox, tox), y: toy }
|
|
@@ -566,13 +860,13 @@ function routeBetweenPoints(source, target, opt = {}) {
|
|
|
566
860
|
let x2 = Math.min((sx0 + tx1) / 2, sox);
|
|
567
861
|
|
|
568
862
|
if (tox > sox) {
|
|
569
|
-
if (
|
|
863
|
+
if (topD <= bottomD) {
|
|
570
864
|
y = Math.min(smy0, toy);
|
|
571
865
|
} else {
|
|
572
866
|
y = Math.max(smy1, toy);
|
|
573
867
|
}
|
|
574
868
|
} else {
|
|
575
|
-
if (
|
|
869
|
+
if (topD <= bottomD) {
|
|
576
870
|
y = Math.min(tmy0, soy);
|
|
577
871
|
} else {
|
|
578
872
|
y = Math.max(tmy1, soy);
|
|
@@ -592,7 +886,8 @@ function routeBetweenPoints(source, target, opt = {}) {
|
|
|
592
886
|
(sox >= tox && (inflatedSourceBBox.bottomLeft().y <= toy || inflatedSourceBBox.topLeft().y >= toy)) ||
|
|
593
887
|
(sox <= tox && (inflatedTargetBBox.bottomLeft().y <= soy || inflatedTargetBBox.topLeft().y >= soy));
|
|
594
888
|
|
|
595
|
-
if
|
|
889
|
+
// U-shape connection is a straight line if `soy` and `toy` are the same
|
|
890
|
+
if (useUShapeConnection && soy !== toy) {
|
|
596
891
|
return [
|
|
597
892
|
{ x: Math.max(sox, tox), y: soy },
|
|
598
893
|
{ x: Math.max(sox, tox), y: toy }
|
|
@@ -604,13 +899,13 @@ function routeBetweenPoints(source, target, opt = {}) {
|
|
|
604
899
|
let x2 = Math.max((sx1 + tx0) / 2, sox);
|
|
605
900
|
|
|
606
901
|
if (tox <= sox) {
|
|
607
|
-
if (
|
|
902
|
+
if (topD <= bottomD) {
|
|
608
903
|
y = Math.min(smy0, toy);
|
|
609
904
|
} else {
|
|
610
905
|
y = Math.max(smy1, toy);
|
|
611
906
|
}
|
|
612
907
|
} else {
|
|
613
|
-
if (
|
|
908
|
+
if (topD <= bottomD) {
|
|
614
909
|
y = Math.min(tmy0, soy);
|
|
615
910
|
} else {
|
|
616
911
|
y = Math.max(tmy1, soy);
|
|
@@ -1190,6 +1485,28 @@ function routeBetweenPoints(source, target, opt = {}) {
|
|
|
1190
1485
|
}
|
|
1191
1486
|
}
|
|
1192
1487
|
|
|
1488
|
+
function getLoopCoordinates(direction, angle, margin) {
|
|
1489
|
+
const isHorizontal = direction === Directions.LEFT || direction === Directions.RIGHT;
|
|
1490
|
+
|
|
1491
|
+
let dx = 0;
|
|
1492
|
+
let dy = 0;
|
|
1493
|
+
|
|
1494
|
+
switch (g.normalizeAngle(Math.round(angle))) {
|
|
1495
|
+
case 0:
|
|
1496
|
+
case 90:
|
|
1497
|
+
dx = isHorizontal ? 0 : margin;
|
|
1498
|
+
dy = isHorizontal ? margin : 0;
|
|
1499
|
+
break;
|
|
1500
|
+
case 180:
|
|
1501
|
+
case 270:
|
|
1502
|
+
dx = isHorizontal ? 0 : -margin;
|
|
1503
|
+
dy = isHorizontal ? -margin : 0;
|
|
1504
|
+
break;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
return { dx, dy };
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1193
1510
|
function rightAngleRouter(vertices, opt, linkView) {
|
|
1194
1511
|
const { sourceDirection = Directions.AUTO, targetDirection = Directions.AUTO } = opt;
|
|
1195
1512
|
const margin = opt.margin || 20;
|
|
@@ -1210,16 +1527,50 @@ function rightAngleRouter(vertices, opt, linkView) {
|
|
|
1210
1527
|
const verticesData = vertices.map((v) => pointDataFromVertex(v));
|
|
1211
1528
|
const [firstVertex] = verticesData;
|
|
1212
1529
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1530
|
+
const [resolvedSourceDirection] = resolveSides(sourcePoint, firstVertex);
|
|
1531
|
+
const isElement = sourcePoint.view && sourcePoint.view.model.isElement();
|
|
1532
|
+
const sourceBBox = isElement ? moveAndExpandBBox(sourcePoint.view.model.getBBox(), resolvedSourceDirection, margin) : null;
|
|
1533
|
+
const isVertexInside = isElement ? sourceBBox.containsPoint(firstVertex.point) : false;
|
|
1534
|
+
|
|
1535
|
+
if (isVertexInside) {
|
|
1536
|
+
const outsidePoint = getOutsidePoint(resolvedSourceDirection, sourcePoint, margin);
|
|
1537
|
+
const firstPointOverlap = outsidePoint.equals(firstVertex.point);
|
|
1538
|
+
|
|
1539
|
+
const alignsVertically = sourcePoint.point.x === firstVertex.point.x;
|
|
1540
|
+
const alignsHorizontally = sourcePoint.point.y === firstVertex.point.y;
|
|
1541
|
+
|
|
1542
|
+
const isVerticalAndAligns = alignsVertically && (resolvedSourceDirection === Directions.TOP || resolvedSourceDirection === Directions.BOTTOM);
|
|
1543
|
+
const isHorizontalAndAligns = alignsHorizontally && (resolvedSourceDirection === Directions.LEFT || resolvedSourceDirection === Directions.RIGHT);
|
|
1544
|
+
|
|
1545
|
+
const firstSegment = new g.Line(sourcePoint.point, outsidePoint);
|
|
1546
|
+
const isVertexOnSegment = firstSegment.containsPoint(firstVertex.point);
|
|
1547
|
+
|
|
1548
|
+
const isVertexAlignedAndInside = isVertexInside && (isHorizontalAndAligns || isVerticalAndAligns);
|
|
1221
1549
|
|
|
1222
|
-
|
|
1550
|
+
|
|
1551
|
+
|
|
1552
|
+
if (firstPointOverlap) {
|
|
1553
|
+
resultVertices.push(sourcePoint.point, firstVertex.point);
|
|
1554
|
+
// Set the access direction as the opposite of the source direction that will be used to connect the route with the next vertex
|
|
1555
|
+
firstVertex.direction = OPPOSITE_DIRECTIONS[resolvedSourceDirection];
|
|
1556
|
+
} else if (isVertexOnSegment || isVertexAlignedAndInside) {
|
|
1557
|
+
// Case where there is a need to create a loop
|
|
1558
|
+
const angle = getSegmentAngle(isVertexOnSegment ? firstSegment : new g.Line(sourcePoint.point, firstVertex.point));
|
|
1559
|
+
const { dx, dy } = getLoopCoordinates(resolvedSourceDirection, angle, margin);
|
|
1560
|
+
|
|
1561
|
+
const loop = createLoop({ point: outsidePoint }, firstVertex, { dx, dy });
|
|
1562
|
+
const secondCreatedPoint = loop[2];
|
|
1563
|
+
const loopEndSegment = new g.Line(firstVertex.point, secondCreatedPoint);
|
|
1564
|
+
|
|
1565
|
+
const accessDirection = ANGLE_DIRECTION_MAP[getSegmentAngle(loopEndSegment)];
|
|
1566
|
+
firstVertex.direction = accessDirection;
|
|
1567
|
+
resultVertices.push(...loop);
|
|
1568
|
+
} else {
|
|
1569
|
+
// No need to create a route, use the `routeBetweenPoints` to construct a route
|
|
1570
|
+
firstVertex.direction = resolvedSourceDirection;
|
|
1571
|
+
firstVertex.margin = margin;
|
|
1572
|
+
resultVertices.push(...routeBetweenPoints(sourcePoint, firstVertex, { targetInSourceBBox: true }), firstVertex.point);
|
|
1573
|
+
}
|
|
1223
1574
|
} else {
|
|
1224
1575
|
// The first point responsible for the initial direction of the route
|
|
1225
1576
|
const next = verticesData[1] || targetPoint;
|
|
@@ -1233,45 +1584,45 @@ function rightAngleRouter(vertices, opt, linkView) {
|
|
|
1233
1584
|
const from = verticesData[i];
|
|
1234
1585
|
const to = verticesData[i + 1];
|
|
1235
1586
|
|
|
1236
|
-
const
|
|
1237
|
-
const
|
|
1238
|
-
if (
|
|
1239
|
-
//
|
|
1240
|
-
const
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1587
|
+
const connectionSegment = new g.Line(from.point, to.point);
|
|
1588
|
+
const connectionSegmentAngle = getSegmentAngle(connectionSegment);
|
|
1589
|
+
if (connectionSegmentAngle % 90 === 0) {
|
|
1590
|
+
// Segment is horizontal or vertical
|
|
1591
|
+
const connectionDirection = ANGLE_DIRECTION_MAP[connectionSegmentAngle];
|
|
1592
|
+
|
|
1593
|
+
const simplifiedRoute = simplifyPoints([...resultVertices, from.point]);
|
|
1594
|
+
// const simplifiedRoute2 = simplifyPoints([from.point, ...resultVertices]);
|
|
1595
|
+
// Find out the direction that is used to connect the current route with the next vertex
|
|
1596
|
+
const accessSegment = new g.Line(simplifiedRoute[simplifiedRoute.length - 2], simplifiedRoute[simplifiedRoute.length - 1]);
|
|
1597
|
+
// const accessSegment2 = new g.Line(simplifiedRoute2[simplifiedRoute2.length - 2], simplifiedRoute2[simplifiedRoute2.length - 1]);
|
|
1598
|
+
const accessDirection = ANGLE_DIRECTION_MAP[Math.round(getSegmentAngle(accessSegment))];
|
|
1599
|
+
// const accessDirection2 = ANGLE_DIRECTION_MAP[Math.round(getSegmentAngle(accessSegment2))];
|
|
1600
|
+
// console.log(accessDirection);
|
|
1601
|
+
// console.log(accessDirection2);
|
|
1602
|
+
// if (accessDirection !== accessDirection2) {
|
|
1603
|
+
// console.log('error');
|
|
1604
|
+
// }
|
|
1605
|
+
// console.log('------------------');
|
|
1606
|
+
|
|
1607
|
+
if (connectionDirection !== OPPOSITE_DIRECTIONS[accessDirection]) {
|
|
1608
|
+
// The directions are not opposite, so we can connect the vertices directly
|
|
1244
1609
|
resultVertices.push(from.point, to.point);
|
|
1245
|
-
|
|
1610
|
+
const [, toDirection] = resolveSides(from, to);
|
|
1611
|
+
to.direction = toDirection;
|
|
1246
1612
|
} else {
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
let dy = 0;
|
|
1251
|
-
|
|
1252
|
-
if (angle === 90) {
|
|
1253
|
-
dy = -margin;
|
|
1254
|
-
} else if (angle === 180) {
|
|
1255
|
-
dx = -margin;
|
|
1256
|
-
} else if (angle === 270) {
|
|
1257
|
-
dy = margin;
|
|
1258
|
-
} else if (angle === 0) {
|
|
1259
|
-
dx = margin;
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
const p1 = { x: from.point.x + dx, y: from.point.y + dy };
|
|
1263
|
-
const p2 = { x: to.point.x + dx, y: to.point.y + dy };
|
|
1264
|
-
|
|
1265
|
-
const segment2 = new g.Line(to.point, p2);
|
|
1266
|
-
to.direction = ANGLE_DIRECTION_MAP[getSegmentAngle(segment2)];
|
|
1267
|
-
|
|
1613
|
+
// The directions are overlapping, so we need to create a loop
|
|
1614
|
+
const { loopRoute, continueDirection } = loopSegment(from, to, connectionSegmentAngle, margin);
|
|
1615
|
+
to.direction = continueDirection;
|
|
1268
1616
|
// Constructing a loop
|
|
1269
|
-
resultVertices.push(
|
|
1617
|
+
resultVertices.push(...loopRoute);
|
|
1270
1618
|
}
|
|
1271
1619
|
|
|
1272
1620
|
continue;
|
|
1273
1621
|
}
|
|
1274
1622
|
|
|
1623
|
+
// Vertices are not aligned vertically nor horizontally
|
|
1624
|
+
// so we need to route between them
|
|
1625
|
+
|
|
1275
1626
|
const [fromDirection, toDirection] = resolveDirection(from, to);
|
|
1276
1627
|
|
|
1277
1628
|
from.direction = fromDirection;
|
|
@@ -1283,24 +1634,20 @@ function rightAngleRouter(vertices, opt, linkView) {
|
|
|
1283
1634
|
const lastVertex = verticesData[verticesData.length - 1];
|
|
1284
1635
|
|
|
1285
1636
|
if (targetPoint.view && targetPoint.view.model.isElement()) {
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
// this will ensure that the last segment continues in a straight line
|
|
1637
|
+
const [, resolvedTargetDirection] = resolveSides(lastVertex, targetPoint);
|
|
1638
|
+
const outsidePoint = getOutsidePoint(resolvedTargetDirection, targetPoint, margin);
|
|
1639
|
+
|
|
1640
|
+
// the last point of `simplified` array is the last defined vertex
|
|
1641
|
+
// this will ensure that the last segment continues in a straight line
|
|
1642
|
+
const simplified = simplifyPoints([...resultVertices, lastVertex.point]);
|
|
1643
|
+
const simplifiedSegment = new g.Line(simplified[simplified.length - 2], simplified[simplified.length - 1]);
|
|
1644
|
+
const simplifiedSegmentAngle = Math.round(getSegmentAngle(simplifiedSegment));
|
|
1645
|
+
const definedDirection = ANGLE_DIRECTION_MAP[simplifiedSegmentAngle];
|
|
1646
|
+
|
|
1647
|
+
const lastPointOverlap = outsidePoint.equals(lastVertex.point);
|
|
1648
|
+
|
|
1649
|
+
if (!lastPointOverlap || (lastPointOverlap && definedDirection === resolvedTargetDirection)) {
|
|
1300
1650
|
|
|
1301
|
-
const simplified = simplifyPoints(resultVertices);
|
|
1302
|
-
const segment = new g.Line(simplified[simplified.length - 2], lastVertex.point);
|
|
1303
|
-
const definedDirection = ANGLE_DIRECTION_MAP[Math.round(getSegmentAngle(segment))];
|
|
1304
1651
|
lastVertex.direction = definedDirection;
|
|
1305
1652
|
|
|
1306
1653
|
let lastSegmentRoute = routeBetweenPoints(lastVertex, targetPoint);
|
|
@@ -1310,7 +1657,26 @@ function rightAngleRouter(vertices, opt, linkView) {
|
|
|
1310
1657
|
const roundedLastSegmentAngle = Math.round(getSegmentAngle(lastSegment));
|
|
1311
1658
|
const lastSegmentDirection = ANGLE_DIRECTION_MAP[roundedLastSegmentAngle];
|
|
1312
1659
|
|
|
1313
|
-
|
|
1660
|
+
const targetBBox = moveAndExpandBBox(targetPoint.view.model.getBBox(), resolvedTargetDirection, margin);
|
|
1661
|
+
|
|
1662
|
+
const alignsVertically = lastVertex.point.x === targetPoint.point.x;
|
|
1663
|
+
const alignsHorizontally = lastVertex.point.y === targetPoint.point.y;
|
|
1664
|
+
const isVertexInside = targetBBox.containsPoint(lastVertex.point);
|
|
1665
|
+
|
|
1666
|
+
const isVerticalAndAligns = alignsVertically && (resolvedTargetDirection === Directions.TOP || resolvedTargetDirection === Directions.BOTTOM);
|
|
1667
|
+
const isHorizontalAndAligns = alignsHorizontally && (resolvedTargetDirection === Directions.LEFT || resolvedTargetDirection === Directions.RIGHT);
|
|
1668
|
+
|
|
1669
|
+
|
|
1670
|
+
if (!lastPointOverlap && isVertexInside && (isHorizontalAndAligns || isVerticalAndAligns)) {
|
|
1671
|
+
// Handle special cases when the last vertex is inside the target element
|
|
1672
|
+
// and in is aligned with the connection point => construct a loop
|
|
1673
|
+
const { dx, dy } = getLoopCoordinates(resolvedTargetDirection, simplifiedSegmentAngle, margin);
|
|
1674
|
+
lastSegmentRoute = createLoop(lastVertex, { point: outsidePoint }, { dx, dy });
|
|
1675
|
+
} else if (isVertexInside && resolvedTargetDirection !== OPPOSITE_DIRECTIONS[definedDirection]) {
|
|
1676
|
+
lastVertex.margin = margin;
|
|
1677
|
+
lastVertex.direction = resolvedTargetDirection;
|
|
1678
|
+
lastSegmentRoute = routeBetweenPoints(lastVertex, targetPoint);
|
|
1679
|
+
} else if (lastSegmentDirection !== definedDirection && definedDirection === OPPOSITE_DIRECTIONS[lastSegmentDirection]) {
|
|
1314
1680
|
lastVertex.margin = margin;
|
|
1315
1681
|
lastSegmentRoute = routeBetweenPoints(lastVertex, targetPoint);
|
|
1316
1682
|
}
|
|
@@ -1319,10 +1685,42 @@ function rightAngleRouter(vertices, opt, linkView) {
|
|
|
1319
1685
|
}
|
|
1320
1686
|
} else {
|
|
1321
1687
|
// since the target is only a point we can apply the same logic as if we connected two verticesData
|
|
1322
|
-
const
|
|
1323
|
-
|
|
1688
|
+
const from = lastVertex;
|
|
1689
|
+
const to = targetPoint;
|
|
1690
|
+
|
|
1691
|
+
const connectionSegment = new g.Line(from.point, to.point);
|
|
1692
|
+
const connectionSegmentAngle = getSegmentAngle(connectionSegment);
|
|
1693
|
+
if (connectionSegmentAngle % 90 === 0) {
|
|
1694
|
+
// Segment is horizontal or vertical
|
|
1695
|
+
const connectionDirection = ANGLE_DIRECTION_MAP[connectionSegmentAngle];
|
|
1696
|
+
|
|
1697
|
+
const simplifiedRoute = simplifyPoints(resultVertices);
|
|
1698
|
+
// Find out the direction that is used to connect the current route with the next vertex
|
|
1699
|
+
const accessSegment = new g.Line(simplifiedRoute[simplifiedRoute.length - 2], from.point);
|
|
1700
|
+
const accessDirection = ANGLE_DIRECTION_MAP[Math.round(getSegmentAngle(accessSegment))];
|
|
1701
|
+
|
|
1702
|
+
if (connectionDirection !== OPPOSITE_DIRECTIONS[accessDirection]) {
|
|
1703
|
+
// The directions are not opposite, so we can connect the vertices directly by adding the first point
|
|
1704
|
+
// the target point is handled separately
|
|
1705
|
+
resultVertices.push(from.point);
|
|
1706
|
+
} else {
|
|
1707
|
+
// The directions are overlapping, so we need to create a loop
|
|
1708
|
+
const { loopRoute } = loopSegment(from, to, connectionSegmentAngle, margin);
|
|
1709
|
+
// Remove the last point since it is the target that is handled separately
|
|
1710
|
+
loopRoute.pop();
|
|
1711
|
+
// Constructing a loop
|
|
1712
|
+
resultVertices.push(...loopRoute);
|
|
1713
|
+
}
|
|
1714
|
+
} else {
|
|
1715
|
+
// The last vertex and the target are not aligned vertically nor horizontally
|
|
1716
|
+
// so we need to route between them
|
|
1717
|
+
const [fromDirection, toDirection] = resolveDirection(from, to);
|
|
1324
1718
|
|
|
1325
|
-
|
|
1719
|
+
from.direction = fromDirection;
|
|
1720
|
+
to.direction = toDirection;
|
|
1721
|
+
|
|
1722
|
+
resultVertices.push(...routeBetweenPoints(from, to));
|
|
1723
|
+
}
|
|
1326
1724
|
}
|
|
1327
1725
|
|
|
1328
1726
|
return simplifyPoints(resultVertices);
|