@@ -469,7 +469,144 @@ function normalize(vector) {
469
469
return norm === 0 ? vector : { x : vector . x / norm , y : vector . y / norm } ;
470
470
}
471
471
472
- const SYMBOLS_WITH_OPTIMIZED_POSITIONING = new Set ( [ CONST . SYMBOLS . CIRCLE ] ) ;
472
+ /**
473
+ * Calculate the length of a vector, from the center of a rectangle,
474
+ * to one of it's edges.
475
+ * This calculation is taken from https://stackoverflow.com/a/3197924.
476
+ *
477
+ * @param {Object.<string, number> } RectangleCoords, The coords of the left-top vertex, and the right-bottom vertex.
478
+ * @param {Object.<string, number> } VectorOriginCoords The center of the rectangle coords.
479
+ * @param {Object.<string, number> } directionVector a 2D vector with x and y components
480
+ */
481
+ function calcRectangleVectorLengthFromCoords ( { x1, y1, x2, y2 } , { px, py } , directionVector ) {
482
+ const angle = Math . atan ( directionVector . y / directionVector . x ) ;
483
+
484
+ const vx = Math . cos ( angle ) ;
485
+ const vy = Math . sin ( angle ) ;
486
+
487
+ if ( vx === 0 ) {
488
+ return x2 - x1 ;
489
+ } else if ( vy === 0 ) {
490
+ return y2 - y1 ;
491
+ }
492
+
493
+ const leftEdge = ( x1 - px ) / vx ;
494
+ const rightEdge = ( x2 - px ) / vx ;
495
+
496
+ const topEdge = ( y1 - py ) / vy ;
497
+ const bottomEdge = ( y2 - py ) / vy ;
498
+
499
+ return Math . min ( ...[ leftEdge , rightEdge , topEdge , bottomEdge ] . filter ( edge => edge > 0 ) ) ;
500
+ }
501
+
502
+ /**
503
+ * Calculate a the vector length from the center of a circle to it's perimeter.
504
+ *
505
+ * @param {number } nodeSize The size of the circle, when no viewGenerator is specified, else the size of an edge of the viewGenerator square.
506
+ * @param {boolean } isCustomNode is viewGenerator specified.
507
+ * @returns {number }
508
+ */
509
+ function calcCircleVectorLength ( nodeSize , isCustomNode ) {
510
+ let radiusLength ;
511
+ if ( isCustomNode ) {
512
+ // nodeSize equals the Diameter in the case of custome-node.
513
+ radiusLength = nodeSize / 10 / 2 ;
514
+ } else {
515
+ // because this is a circle and A = pi * r^2
516
+ // we multiply by 0.95, because if we don't the link is not melting properly
517
+ radiusLength = Math . sqrt ( nodeSize / Math . PI ) ;
518
+ }
519
+ return radiusLength ;
520
+ }
521
+
522
+ /**
523
+ * Calculate a the vector length from the center of a square to it's perimeter.
524
+ *
525
+ * @param {number } nodeSize The size of the square, when no viewGenerator is specified, else the size of an edge of the viewGenerator square.
526
+ * @param {Object.<string, number> } nodeCoords The coords of a the square node.
527
+ * @param {Object.<string, number> } directionVector a 2D vector with x and y components
528
+ * @param {boolean } isCustomNode is viewGenerator specified.
529
+ * @returns {number }
530
+ */
531
+ function calcSquareVectorLength ( nodeSize , { x, y } , directionVector , isCustomNode ) {
532
+ let edgeSize ;
533
+ if ( isCustomNode ) {
534
+ // nodeSize equals the edgeSize in the case of custome-node.
535
+ edgeSize = nodeSize / 10 ;
536
+ } else {
537
+ // All the edges of a square are equal, inorder to calc it's size we multplie two edges.
538
+ edgeSize = Math . sqrt ( nodeSize ) ;
539
+ }
540
+
541
+ // The x and y coords in this library, represent the top center of the component.
542
+ const leftSquareX = x - edgeSize / 2 ;
543
+ const topSquareY = y - edgeSize / 2 ;
544
+
545
+ return calcRectangleVectorLengthFromCoords (
546
+ { x1 : leftSquareX , y1 : topSquareY , x2 : leftSquareX + edgeSize , y2 : topSquareY + edgeSize } ,
547
+ { px : x , py : y } ,
548
+ directionVector
549
+ ) ;
550
+ }
551
+
552
+ /**
553
+ * Calculate a the vector length from the center of a rectangle to it's perimeter.
554
+ *
555
+ * @param {number } nodeSize The size of the square, when no viewGenerator is specified, else the size of an edge of the viewGenerator square.
556
+ * @param {Object.<string, number> } nodeCoords The coords of a the square node.
557
+ * @param {Object.<string, number> } directionVector a 2D vector with x and y components.
558
+ * @returns {number }
559
+ */
560
+ function calcRectangleVectorLength ( nodeSize , { x, y } , directionVector ) {
561
+ const horizEdgeSize = nodeSize . width / 10 ;
562
+ const vertEdgeSize = nodeSize . height / 10 ;
563
+
564
+ // The x and y coords in this library, represent the top center of the component.
565
+ const leftSquareX = x - horizEdgeSize / 2 ;
566
+ const topSquareY = y - vertEdgeSize / 2 ;
567
+
568
+ // The size between the square center, to the appropriate square edges
569
+ return calcRectangleVectorLengthFromCoords (
570
+ { x1 : leftSquareX , y1 : topSquareY , x2 : leftSquareX + horizEdgeSize , y2 : topSquareY + vertEdgeSize } ,
571
+ { px : x , py : y } ,
572
+ directionVector
573
+ ) ;
574
+ }
575
+
576
+ /**
577
+ * Calculate a the vector length of symbol that included in symbols with optimized positioning.
578
+ *
579
+ * @param {string } symbolType the string that specifies the symbol type (should be one of {@link #node-symbol-type|node.symbolType})
580
+ * @param {(number | Object.<string, number>) } nodeSize The size of the square, when no viewGenerator is specified, else the size of an edge of the viewGenerator square.
581
+ * @param {Object.<string, number> } nodeCoords The coords of a the square node.
582
+ * @param {Object.<string, number> } directionVector a 2D vector with x and y components.
583
+ * @param {boolean } isCustomNode is viewGenerator specified.
584
+ * @returns {number }
585
+ */
586
+ function calcVectorLength ( symbolType , nodeSize , { x, y } , directionVector , isCustomNode ) {
587
+ switch ( symbolType ) {
588
+ case CONST . SYMBOLS . CIRCLE :
589
+ if ( typeof nodeSize === "number" ) {
590
+ return calcCircleVectorLength ( nodeSize , isCustomNode ) ;
591
+ }
592
+ console . warn ( "When specifiying 'circle' as node symbol, the size of the node must be a number." ) ;
593
+
594
+ case CONST . SYMBOLS . SQUARE :
595
+ if ( typeof nodeSize === "number" ) {
596
+ return calcSquareVectorLength ( nodeSize , { x, y } , directionVector , isCustomNode ) ;
597
+ }
598
+ console . warn ( "When specifiying 'square' as node symbol, the size of the node must be a number." ) ;
599
+
600
+ case CONST . SYMBOLS . RECTANGLE :
601
+ if ( typeof nodeSize === "object" && nodeSize ?. width && nodeSize ?. height ) {
602
+ return calcRectangleVectorLength ( nodeSize , { x, y } , directionVector ) ;
603
+ }
604
+ console . warn ( "When specifiying 'rectangle' as node symbol, width and height must be specified in the node size." ) ;
605
+
606
+ default :
607
+ return 1 ;
608
+ }
609
+ }
473
610
474
611
/**
475
612
* Computes new node coordinates to make arrowheads point at nodes.
@@ -498,16 +635,12 @@ function getNormalizedNodeCoordinates(
498
635
return { sourceCoords, targetCoords } ;
499
636
}
500
637
501
- if ( config . node ?. viewGenerator || sourceNode ?. viewGenerator || targetNode ?. viewGenerator ) {
502
- return { sourceCoords, targetCoords } ;
503
- }
504
-
505
638
const sourceSymbolType = sourceNode . symbolType || config . node ?. symbolType ;
506
639
const targetSymbolType = targetNode . symbolType || config . node ?. symbolType ;
507
640
508
641
if (
509
- ! SYMBOLS_WITH_OPTIMIZED_POSITIONING . has ( sourceSymbolType ) &&
510
- ! SYMBOLS_WITH_OPTIMIZED_POSITIONING . has ( targetSymbolType )
642
+ ! CONST . SYMBOLS_WITH_OPTIMIZED_POSITIONING . has ( sourceSymbolType ) &&
643
+ ! CONST . SYMBOLS_WITH_OPTIMIZED_POSITIONING . has ( targetSymbolType )
511
644
) {
512
645
// if symbols don't have optimized positioning implementations fallback to input coords
513
646
return { sourceCoords, targetCoords } ;
@@ -517,38 +650,26 @@ function getNormalizedNodeCoordinates(
517
650
let { x : x2 , y : y2 } = targetCoords ;
518
651
const directionVector = normalize ( { x : x2 - x1 , y : y2 - y1 } ) ;
519
652
520
- switch ( sourceSymbolType ) {
521
- case CONST . SYMBOLS . CIRCLE : {
522
- let sourceNodeSize = sourceNode ?. size || config . node . size ;
653
+ const isSourceCustomNode = sourceNode . viewGenerator || config . node . viewGenerator ;
654
+ const sourceNodeSize = sourceNode ?. size || config . node . size ;
523
655
524
- // because this is a circle and A = pi * r^2
525
- // we multiply by 0.95, because if we don't the link is not melting properly
526
- sourceNodeSize = Math . sqrt ( sourceNodeSize / Math . PI ) * 0.95 ;
656
+ const sourceVectorLength =
657
+ calcVectorLength ( sourceSymbolType , sourceNodeSize , { x : x1 , y : y1 } , directionVector , isSourceCustomNode ) * 0.95 ;
527
658
528
- // points from the sourceCoords, we move them not to begin in the circle but outside
529
- x1 += sourceNodeSize * directionVector . x ;
530
- y1 += sourceNodeSize * directionVector . y ;
531
- break ;
532
- }
533
- }
659
+ x1 += sourceVectorLength * directionVector . x ;
660
+ y1 += sourceVectorLength * directionVector . y ;
534
661
535
- switch ( targetSymbolType ) {
536
- case CONST . SYMBOLS . CIRCLE : {
537
- // it's fine `markerWidth` or `markerHeight` we just want to fallback to a number
538
- // to avoid NaN on `Math.min(undefined, undefined) > NaN
539
- let strokeSize = strokeWidth * Math . min ( config . link ?. markerWidth || 0 , config . link ?. markerHeight || 0 ) ;
540
- let targetNodeSize = targetNode ?. size || config . node . size ;
541
-
542
- // because this is a circle and A = pi * r^2
543
- // we multiply by 0.95, because if we don't the link is not melting properly
544
- targetNodeSize = Math . sqrt ( targetNodeSize / Math . PI ) * 0.95 ;
545
-
546
- // points from the targetCoords, we move the by the size of the radius of the circle + the size of the arrow
547
- x2 -= ( targetNodeSize + ( config . directed ? strokeSize : 0 ) ) * directionVector . x ;
548
- y2 -= ( targetNodeSize + ( config . directed ? strokeSize : 0 ) ) * directionVector . y ;
549
- break ;
550
- }
551
- }
662
+ // it's fine `markerWidth` or `markerHeight` we just want to fallback to a number
663
+ // to avoid NaN on `Math.min(undefined, undefined) > NaN
664
+ const strokeSize = strokeWidth * Math . min ( config . link ?. markerWidth || 5 , config . link ?. markerHeight || 5 ) ;
665
+ const isTargetCustomNode = targetNode . viewGenerator || config . node . viewGenerator ;
666
+ const targetNodeSize = targetNode ?. size || config . node . size ;
667
+
668
+ const targetVectorLength =
669
+ calcVectorLength ( targetSymbolType , targetNodeSize , { x : x2 , y : y2 } , directionVector , isTargetCustomNode ) * 0.95 ;
670
+
671
+ x2 -= ( targetVectorLength + ( config . directed ? strokeSize : 0 ) ) * directionVector . x ;
672
+ y2 -= ( targetVectorLength + ( config . directed ? strokeSize : 0 ) ) * directionVector . y ;
552
673
553
674
return { sourceCoords : { x : x1 , y : y1 } , targetCoords : { x : x2 , y : y2 } } ;
554
675
}
0 commit comments