@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.
Files changed (55) hide show
  1. package/README.md +2 -10
  2. package/dist/geometry.js +4962 -6132
  3. package/dist/geometry.min.js +2 -2
  4. package/dist/joint.d.ts +338 -52
  5. package/dist/joint.js +34067 -37525
  6. package/dist/joint.min.js +2 -2
  7. package/dist/joint.nowrap.js +34067 -37525
  8. package/dist/joint.nowrap.min.js +2 -2
  9. package/dist/vectorizer.js +7288 -8907
  10. package/dist/vectorizer.min.js +2 -2
  11. package/dist/version.mjs +1 -1
  12. package/package.json +10 -15
  13. package/src/{linkTools → cellTools}/Button.mjs +8 -6
  14. package/src/{elementTools → cellTools}/Control.mjs +3 -3
  15. package/src/{linkTools → cellTools}/HoverConnect.mjs +1 -1
  16. package/src/dia/Cell.mjs +60 -33
  17. package/src/dia/CellView.mjs +75 -8
  18. package/src/dia/ElementView.mjs +13 -8
  19. package/src/dia/Graph.mjs +148 -40
  20. package/src/dia/HighlighterView.mjs +8 -4
  21. package/src/dia/LinkView.mjs +61 -4
  22. package/src/dia/Paper.mjs +84 -0
  23. package/src/dia/ToolView.mjs +29 -4
  24. package/src/dia/ToolsView.mjs +25 -10
  25. package/src/dia/attributes/connection.mjs +5 -0
  26. package/src/dia/attributes/defs.mjs +3 -0
  27. package/src/dia/attributes/eval.mjs +3 -3
  28. package/src/dia/attributes/index.mjs +3 -0
  29. package/src/dia/attributes/shape.mjs +4 -0
  30. package/src/dia/attributes/text.mjs +41 -15
  31. package/src/dia/ports.mjs +4 -0
  32. package/src/elementTools/HoverConnect.mjs +5 -5
  33. package/src/elementTools/index.mjs +5 -4
  34. package/src/env/index.mjs +5 -0
  35. package/src/g/rect.mjs +13 -5
  36. package/src/layout/ports/port.mjs +4 -5
  37. package/src/linkTools/Anchor.mjs +1 -1
  38. package/src/linkTools/Arrowhead.mjs +2 -1
  39. package/src/linkTools/RotateLabel.mjs +110 -0
  40. package/src/linkTools/Segments.mjs +1 -1
  41. package/src/linkTools/Vertices.mjs +41 -4
  42. package/src/linkTools/index.mjs +7 -4
  43. package/src/mvc/View.mjs +0 -1
  44. package/src/mvc/ViewBase.mjs +2 -1
  45. package/src/routers/rightAngle.mjs +538 -140
  46. package/src/shapes/standard.mjs +8 -1
  47. package/src/{dia/attributes → util}/calc.mjs +24 -12
  48. package/src/util/index.mjs +1 -0
  49. package/src/util/util.mjs +39 -0
  50. package/src/util/utilHelpers.mjs +2 -1
  51. package/types/geometry.d.ts +6 -1
  52. package/types/joint.d.ts +331 -50
  53. /package/src/{linkTools → cellTools}/Boundary.mjs +0 -0
  54. /package/src/{linkTools → cellTools}/Connect.mjs +0 -0
  55. /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 >= sy0) return Directions.TOP;
100
- if (tx > smx1 && ty >= sy0) return Directions.TOP;
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.TOP;
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 <= sy1) return Directions.BOTTOM;
140
- if (tx > smx1 && ty <= sy1) return Directions.BOTTOM;
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.BOTTOM;
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 >= sx0 && ty <= smy0) return Directions.LEFT;
174
- if (tx >= sx0 && ty >= smy1) return Directions.LEFT;
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.LEFT;
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 <= sx1 && ty <= smy0) return Directions.RIGHT;
208
- if (tx <= sx1 && ty >= smy1) return Directions.RIGHT;
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.RIGHT;
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
- if (smx0 <= tmx1) {
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
- if (sox <= tmx0) {
373
- if (ty1 >= smy0 && toy < soy) {
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 (ty0 <= smy1 && toy >= soy) {
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: sox, y: soy },
381
- { x: sox, y },
382
- { x: tox, y },
383
- { x: tox, y: toy }
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
- if (sox >= tmx0) {
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
- if (sox > tx1) {
396
- if (ty1 >= smy0 && toy < soy) {
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 (ty0 <= smy1 && toy >= soy) {
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: sox, y: soy },
405
- { x: sox, y },
406
- { x: tox, y },
407
- { x: tox, y: toy }
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 = g.intersection.rectWithRect(inflatedSourceBBox, targetBBox);
644
+ const isPointInsideSource = inflatedSourceBBox.containsPoint(targetOutsidePoint);
645
+ const isPointInsideTarget = inflatedTargetBBox.containsPoint(sourceOutsidePoint);
418
646
 
419
- if (soy < toy) {
420
- let x = middleOfVerticalSides;
421
- let y = soy;
647
+ // Use S-shaped connection
648
+ if (isPointInsideSource || isPointInsideTarget) {
649
+ const middleOfAnchors = (sox + tox) / 2;
422
650
 
423
- if (isPointInsideSource) {
424
- y = Math.min(y, tmy0);
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
- if (tx1 >= smx0 && tox < sox) {
428
- x = Math.min(tmx0, smx0);
429
- } else if (tx0 <= smx1 && tox >= sox) {
430
- x = Math.max(tmx1, smx1);
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: toy },
437
- { x: tox, y: toy }
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 = g.intersection.rectWithRect(inflatedSourceBBox, targetBBox);
704
+ const isPointInsideSource = inflatedSourceBBox.containsPoint(targetOutsidePoint);
705
+ const isPointInsideTarget = inflatedTargetBBox.containsPoint(sourceOutsidePoint);
447
706
 
448
- if (soy > toy) {
449
- let x = middleOfVerticalSides;
450
- let y = soy;
707
+ // Use S-shaped connection
708
+ if (isPointInsideSource || isPointInsideTarget) {
709
+ const middleOfAnchors = (sox + tox) / 2;
451
710
 
452
- if (isPointInsideSource) {
453
- y = Math.max(y, tmy1);
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
- if (tx1 >= smx0 && tox < sox) {
457
- x = Math.min(tmx0, smx0);
458
- } else if (tx0 <= smx1 && tox >= sox) {
459
- x = Math.max(tmx1, smx1);
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: toy },
466
- { x: tox, y: toy }
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 (useUShapeConnection) {
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
- if (tox > sox) {
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 (tox >= sox) {
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 (useUShapeConnection) {
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
- if (tox > sox) {
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 (tox >= sox) {
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 (useUShapeConnection) {
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 (toy <= soy) {
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 (toy >= soy) {
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 (useUShapeConnection) {
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 (toy <= soy) {
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 (toy >= soy) {
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
- if (sourcePoint.view && sourcePoint.view.model.isElement() && sourcePoint.view.model.getBBox().inflate(margin).containsPoint(firstVertex.point)) {
1214
- const [fromDirection] = resolveSides(sourcePoint, firstVertex);
1215
- const toDirection = fromDirection;
1216
- const dummySource = pointDataFromVertex(sourcePoint.point);
1217
- // Points do not usually have margin. Here we create a point with a margin.
1218
- dummySource.margin = margin;
1219
- dummySource.direction = fromDirection;
1220
- firstVertex.direction = toDirection;
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
- resultVertices.push(...routeBetweenPoints(dummySource, firstVertex, { targetInSourceBBox: true }), firstVertex.point);
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 segment = new g.Line(from.point, to.point);
1237
- const segmentAngle = getSegmentAngle(segment);
1238
- if (segmentAngle % 90 === 0) {
1239
- // Since the segment is horizontal or vertical, we can skip the routing and just connect them with a straight line
1240
- const toDirection = ANGLE_DIRECTION_MAP[segmentAngle];
1241
- const accessDirection = OPPOSITE_DIRECTIONS[toDirection];
1242
-
1243
- if (toDirection !== from.direction) {
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
- to.direction = accessDirection;
1610
+ const [, toDirection] = resolveSides(from, to);
1611
+ to.direction = toDirection;
1246
1612
  } else {
1247
- const angle = g.normalizeAngle(segmentAngle - 90);
1248
-
1249
- let dx = 0;
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(from.point, p1, p2, to.point);
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
- if (targetPoint.view.model.getBBox().inflate(margin).containsPoint(lastVertex.point)) {
1287
- const [fromDirection] = resolveDirection(lastVertex, targetPoint);
1288
- const dummyTarget = pointDataFromVertex(targetPoint.point);
1289
- const [, toDirection] = resolveSides(lastVertex, targetPoint);
1290
- // we are creating a point that has a margin
1291
- dummyTarget.margin = margin;
1292
- dummyTarget.direction = toDirection;
1293
- lastVertex.direction = fromDirection;
1294
-
1295
- resultVertices.push(...routeBetweenPoints(lastVertex, dummyTarget));
1296
- } else {
1297
- // the last point of `simplified` array is the last defined vertex
1298
- // grab the penultimate point and construct a line segment from it to the last vertex
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
- if (lastSegmentDirection !== definedDirection && definedDirection === OPPOSITE_DIRECTIONS[lastSegmentDirection]) {
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 [vertexDirection] = resolveDirection(lastVertex, targetPoint);
1323
- lastVertex.direction = vertexDirection;
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
- resultVertices.push(...routeBetweenPoints(lastVertex, targetPoint));
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);