Skip to content

Commit 07e0fb2

Browse files
committed
directed graph with viewGenerator
1 parent ca84242 commit 07e0fb2

File tree

4 files changed

+136
-43
lines changed

4 files changed

+136
-43
lines changed

Diff for: src/components/graph/graph.const.js

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import CONST from "../../const";
22

3+
export const SYMBOLS_WITH_OPTIMIZED_POSITIONING = new Set([
4+
CONST.SYMBOLS.CIRCLE,
5+
CONST.SYMBOLS.SQUARE,
6+
CONST.SYMBOLS.RECTANGLE,
7+
]);
8+
39
export default {
410
COORDS_SEPARATOR: ",",
511
FORCE_X: 0.06,

Diff for: src/components/graph/graph.helper.js

+124-34
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
forceManyBody as d3ForceManyBody,
2727
} from "d3-force";
2828

29-
import CONST from "./graph.const";
29+
import CONST, { SYMBOLS_WITH_OPTIMIZED_POSITIONING } from "./graph.const";
3030
import DEFAULT_CONFIG from "./graph.config";
3131
import ERRORS from "../../err";
3232

@@ -469,7 +469,101 @@ function normalize(vector) {
469469
return norm === 0 ? vector : { x: vector.x / norm, y: vector.y / norm };
470470
}
471471

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+
}
473567

474568
/**
475569
* Computes new node coordinates to make arrowheads point at nodes.
@@ -498,10 +592,6 @@ function getNormalizedNodeCoordinates(
498592
return { sourceCoords, targetCoords };
499593
}
500594

501-
if (config.node?.viewGenerator || sourceNode?.viewGenerator || targetNode?.viewGenerator) {
502-
return { sourceCoords, targetCoords };
503-
}
504-
505595
const sourceSymbolType = sourceNode.symbolType || config.node?.symbolType;
506596
const targetSymbolType = targetNode.symbolType || config.node?.symbolType;
507597

@@ -517,39 +607,39 @@ function getNormalizedNodeCoordinates(
517607
let { x: x2, y: y2 } = targetCoords;
518608
const directionVector = normalize({ x: x2 - x1, y: y2 - y1 });
519609

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;
523613

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);
533620
}
534621

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);
551638
}
552639

640+
x2 -= (targetVectorLength + (config.directed ? strokeSize : 0)) * directionVector.x;
641+
y2 -= (targetVectorLength + (config.directed ? strokeSize : 0)) * directionVector.y;
642+
553643
return { sourceCoords: { x: x1, y: y1 }, targetCoords: { x: x2, y: y2 } };
554644
}
555645

Diff for: src/components/marker/marker.helper.js

+5-9
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
MARKER_MEDIUM_OFFSET,
1212
MARKER_LARGE_OFFSET,
1313
} from "./marker.const";
14-
import CONST from "../graph/graph.const";
14+
import CONST, { SYMBOLS_WITH_OPTIMIZED_POSITIONING } from "../graph/graph.const";
1515

1616
/**
1717
* This function is a key template builder to access MARKERS structure.
@@ -114,14 +114,10 @@ function getMarkerSize(config) {
114114
let medium = small + (MARKER_MEDIUM_OFFSET * config.maxZoom) / 3;
115115
let large = small + (MARKER_LARGE_OFFSET * config.maxZoom) / 3;
116116

117-
if (config.node && !config.node.viewGenerator) {
118-
switch (config.node.symbolType) {
119-
case CONST.SYMBOLS.CIRCLE:
120-
small = 0;
121-
medium = 0;
122-
large = 0;
123-
break;
124-
}
117+
if (config.node?.viewGenerator || SYMBOLS_WITH_OPTIMIZED_POSITIONING.includes(config.node?.symbolType)) {
118+
small = 0;
119+
medium = 0;
120+
large = 0;
125121
}
126122

127123
return { small, medium, large };

Diff for: src/const.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ export default {
1212
STAR: "star",
1313
TRIANGLE: "triangle",
1414
WYE: "wye",
15+
RECTANGLE: "rectangle",
1516
},
1617
};

0 commit comments

Comments
 (0)