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