@@ -26,7 +26,7 @@ import {
26
26
forceManyBody as d3ForceManyBody ,
27
27
} from "d3-force" ;
28
28
29
- import CONST from "./graph.const" ;
29
+ import CONST , { SYMBOLS_WITH_OPTIMIZED_POSITIONING } from "./graph.const" ;
30
30
import DEFAULT_CONFIG from "./graph.config" ;
31
31
import ERRORS from "../../err" ;
32
32
@@ -469,7 +469,101 @@ 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 left_edge = ( x1 - px ) / vx ;
493
+ const right_edge = ( x2 - px ) / vx ;
494
+
495
+ const top_edge = ( y1 - py ) / vy ;
496
+ const bottom_edge = ( y2 - py ) / vy ;
497
+
498
+ return Math . min ( ...[ left_edge , right_edge , top_edge , bottom_edge ] . 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
+ */
507
+ function calcCircleVectorLength ( nodeSize , isCustomNode ) {
508
+ // because this is a circle and A = pi * r^2
509
+ // we multiply by 0.95, because if we don't the link is not melting properly
510
+ let radiusLength = Math . sqrt ( nodeSize / Math . PI ) ;
511
+ if ( isCustomNode ) {
512
+ radiusLength = nodeSize / 10 / 2 ;
513
+ }
514
+ return radiusLength * 0.95 ;
515
+ }
516
+
517
+ /**
518
+ * Calculate a the vector length from the center of a square to it's perimeter.
519
+ *
520
+ * @param {number } nodeSize The size of the square, when no viewGenerator is specified, else the size of an edge of the viewGenerator square.
521
+ * @param {Object.<string, number> } nodeCoords The coords of a the square node.
522
+ * @param {Object.<string, number> } directionVector a 2D vector with x and y components
523
+ * @param {boolean } isCustomNode is viewGenerator specified.
524
+ */
525
+ function calcSquareVectorLength ( nodeSize , { x, y } , directionVector , isCustomNode ) {
526
+ let edgeSize = Math . sqrt ( nodeSize ) ;
527
+ if ( isCustomNode ) {
528
+ edgeSize = nodeSize / 10 ;
529
+ }
530
+
531
+ const leftSquareX = x - edgeSize / 2 ;
532
+ const topSquareY = y - edgeSize / 2 ;
533
+
534
+ return (
535
+ calcRectangleVectorLengthFromCoords (
536
+ { x1 : leftSquareX , y1 : topSquareY , x2 : leftSquareX + edgeSize , y2 : topSquareY + edgeSize } ,
537
+ { px : x , py : y } ,
538
+ directionVector
539
+ ) * 0.95
540
+ ) ;
541
+ }
542
+
543
+ /**
544
+ * Calculate a the vector length from the center of a rectangle to it's perimeter.
545
+ *
546
+ * @param {number } nodeSize The size of the square, when no viewGenerator is specified, else the size of an edge of the viewGenerator square.
547
+ * @param {Object.<string, number> } nodeCoords The coords of a the square node.
548
+ * @param {Object.<string, number> } directionVector a 2D vector with x and y components
549
+ * @param {boolean } isCustomNode is viewGenerator specified.
550
+ */
551
+ function calcRectangleVectorLength ( nodeSize , { x, y } , directionVector , isCustomNode ) {
552
+ const horizEdgeSize = nodeSize . width / 10 ;
553
+ const vertEdgeSize = nodeSize . height / 10 ;
554
+
555
+ const leftSquareX = x - horizEdgeSize / 2 ;
556
+ const topSquareY = y - vertEdgeSize / 2 ;
557
+
558
+ // The size between the square center, to the appropriate square edges
559
+ return (
560
+ calcRectangleVectorLengthFromCoords (
561
+ { x1 : leftSquareX , y1 : topSquareY , x2 : leftSquareX + horizEdgeSize , y2 : topSquareY + vertEdgeSize } ,
562
+ { px : x , py : y } ,
563
+ directionVector
564
+ ) * 0.95
565
+ ) ;
566
+ }
473
567
474
568
/**
475
569
* Computes new node coordinates to make arrowheads point at nodes.
@@ -498,10 +592,6 @@ function getNormalizedNodeCoordinates(
498
592
return { sourceCoords, targetCoords } ;
499
593
}
500
594
501
- if ( config . node ?. viewGenerator || sourceNode ?. viewGenerator || targetNode ?. viewGenerator ) {
502
- return { sourceCoords, targetCoords } ;
503
- }
504
-
505
595
const sourceSymbolType = sourceNode . symbolType || config . node ?. symbolType ;
506
596
const targetSymbolType = targetNode . symbolType || config . node ?. symbolType ;
507
597
@@ -517,39 +607,39 @@ function getNormalizedNodeCoordinates(
517
607
let { x : x2 , y : y2 } = targetCoords ;
518
608
const directionVector = normalize ( { x : x2 - x1 , y : y2 - y1 } ) ;
519
609
520
- switch ( sourceSymbolType ) {
521
- case CONST . SYMBOLS . CIRCLE : {
522
- let sourceNodeSize = sourceNode ?. size || config . node . size ;
610
+ const isSourceCustomNode = sourceNode . viewGenerator || config . node . viewGenerator ;
611
+ let sourceNodeSize = sourceNode ?. size || config . node . size ;
612
+ let sourceVectorLength = 1 ;
523
613
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 ;
527
-
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
- }
614
+ if ( sourceSymbolType === CONST . SYMBOLS . CIRCLE ) {
615
+ sourceVectorLength = calcCircleVectorLength ( sourceNodeSize , isSourceCustomNode ) ;
616
+ } else if ( sourceSymbolType === CONST . SYMBOLS . SQUARE ) {
617
+ sourceVectorLength = calcSquareVectorLength ( sourceNodeSize , { x : x1 , y : y1 } , directionVector , isSourceCustomNode ) ;
618
+ } else if ( sourceSymbolType === CONST . SYMBOLS . RECTANGLE && sourceNodeSize . width && sourceNodeSize . height ) {
619
+ sourceVectorLength = calcRectangleVectorLength ( sourceNodeSize , { x : x1 , y : y1 } , directionVector ) ;
533
620
}
534
621
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
- }
622
+ x1 += sourceVectorLength * directionVector . x ;
623
+ y1 += sourceVectorLength * directionVector . y ;
624
+
625
+ // it's fine `markerWidth` or `markerHeight` we just want to fallback to a number
626
+ // to avoid NaN on ` Math.min(undefined, undefined) > NaN
627
+ let strokeSize = strokeWidth * Math . min ( config . link ?. markerWidth || 5 , config . link ?. markerHeight || 5 ) ;
628
+ const isTargetCustomNode = targetNode . viewGenerator || config . node . viewGenerator ;
629
+ let targetNodeSize = targetNode ?. size || config . node . size ;
630
+ let targetVectorLength = 1 ;
631
+
632
+ if ( targetSymbolType === CONST . SYMBOLS . CIRCLE ) {
633
+ targetVectorLength = calcCircleVectorLength ( targetNodeSize , isTargetCustomNode ) ;
634
+ } else if ( targetSymbolType === CONST . SYMBOLS . SQUARE ) {
635
+ targetVectorLength = calcSquareVectorLength ( targetNodeSize , { x : x2 , y : y2 } , directionVector , isTargetCustomNode ) ;
636
+ } else if ( targetSymbolType === CONST . SYMBOLS . RECTANGLE && targetNodeSize . width && targetNodeSize . height ) {
637
+ targetVectorLength = calcRectangleVectorLength ( targetNodeSize , { x : x2 , y : y2 } , directionVector ) ;
551
638
}
552
639
640
+ x2 -= ( targetVectorLength + ( config . directed ? strokeSize : 0 ) ) * directionVector . x ;
641
+ y2 -= ( targetVectorLength + ( config . directed ? strokeSize : 0 ) ) * directionVector . y ;
642
+
553
643
return { sourceCoords : { x : x1 , y : y1 } , targetCoords : { x : x2 , y : y2 } } ;
554
644
}
555
645
0 commit comments