@krainovsd/graph 0.12.0-beta.6 → 0.13.0-beta3

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 (30) hide show
  1. package/lib/cjs/index.cjs +566 -371
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/esm/index.js +5 -4
  4. package/lib/esm/index.js.map +1 -1
  5. package/lib/esm/module/GraphCanvas/GraphCanvas.js +41 -46
  6. package/lib/esm/module/GraphCanvas/GraphCanvas.js.map +1 -1
  7. package/lib/esm/module/GraphCanvas/constants/index.js +12 -0
  8. package/lib/esm/module/GraphCanvas/constants/index.js.map +1 -0
  9. package/lib/esm/module/GraphCanvas/constants/link-settings.js +1 -0
  10. package/lib/esm/module/GraphCanvas/constants/link-settings.js.map +1 -1
  11. package/lib/esm/module/GraphCanvas/lib/utils/calculate-curve-link-position-by-node.js +42 -0
  12. package/lib/esm/module/GraphCanvas/lib/utils/calculate-curve-link-position-by-node.js.map +1 -0
  13. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-node.js +37 -35
  14. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-node.js.map +1 -1
  15. package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js +72 -22
  16. package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js.map +1 -1
  17. package/lib/esm/module/GraphCanvas/slices/draw-links.js +70 -17
  18. package/lib/esm/module/GraphCanvas/slices/draw-links.js.map +1 -1
  19. package/lib/esm/module/GraphCanvas/slices/init-pointer.js +6 -0
  20. package/lib/esm/module/GraphCanvas/slices/init-pointer.js.map +1 -1
  21. package/lib/esm/module/GraphCanvas/slices/init-resize.js +7 -0
  22. package/lib/esm/module/GraphCanvas/slices/init-resize.js.map +1 -1
  23. package/lib/esm/module/GraphCanvas/slices/init-zoom.js +5 -2
  24. package/lib/esm/module/GraphCanvas/slices/init-zoom.js.map +1 -1
  25. package/lib/esm/module/GraphCanvas/slices/update-link-cache.js +30 -0
  26. package/lib/esm/module/GraphCanvas/slices/update-link-cache.js.map +1 -1
  27. package/lib/esm/module/GraphCanvas/slices/update-node-cache.js +103 -97
  28. package/lib/esm/module/GraphCanvas/slices/update-node-cache.js.map +1 -1
  29. package/lib/index.d.ts +71 -47
  30. package/package.json +3 -2
package/lib/cjs/index.cjs CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
4
 
5
+ const jsHelpers = require('@krainovsd/js-helpers');
5
6
  const d3Force = require('d3-force');
6
7
  const d3Zoom = require('d3-zoom');
7
- const jsHelpers = require('@krainovsd/js-helpers');
8
8
  const d3Array = require('d3-array');
9
9
  const d3Selection = require('d3-selection');
10
10
  const d3Drag = require('d3-drag');
@@ -41,158 +41,6 @@ const FORCE_SETTINGS = {
41
41
  collideRadius: null,
42
42
  };
43
43
 
44
- const GRAPH_SETTINGS = {
45
- zoomExtent: [0.3, 10],
46
- translateExtent: [[], []],
47
- translateExtentEnable: true,
48
- translateExtentCoefficient: [3, 3],
49
- dragPlaceCoefficient: dragPlaceCoefficientGetter,
50
- zoomInitial: null,
51
- showDrawTime: true,
52
- showDrawTimeEveryTick: false,
53
- };
54
-
55
- const HIGHLIGHT_SETTINGS = {
56
- highlightByHoverNode: true,
57
- highlightByHoverLink: true,
58
- highlightUpFrames: 5,
59
- highlightDownFrames: 5,
60
- linkHoverExtraZone: 2,
61
- /** Node */
62
- highlightByNodeOnlyRoot: true,
63
- highlightByNodeForNodeColorFading: false,
64
- highlightByNodeForNodeColor: null,
65
- highlightByNodeForNodeSizingAdditional: 0.5,
66
- highlightByNodeForNodeSizingAdditionalCoefficient: 0.35,
67
- highlightByNodeForNodeFadingMin: 0.21,
68
- highlightByNodeForNodeBorderColor: null,
69
- highlightByNodeForNodeBorderSizingAdditional: 0,
70
- highlightByLinkForNodeColorFading: false,
71
- highlightByLinkForNodeColor: null,
72
- highlightByLinkForNodeSizingAdditional: 0.5,
73
- highlightByLinkForNodeSizingAdditionalCoefficient: 0.35,
74
- highlightByLinkForNodeFadingMin: 0.21,
75
- highlightByLinkForNodeBorderColor: null,
76
- highlightByLinkForNodeBorderSizingAdditional: 0,
77
- /** Text */
78
- highlightByNodeForTextShiftXAdditional: 0,
79
- highlightByNodeForTextShiftYAdditional: 2,
80
- highlightByNodeForTextSizingAdditional: 1,
81
- highlightByNodeForTextWeightAdditional: 0,
82
- highlightByNodeForTextFadingMin: 0.21,
83
- highlightByLinkForTextShiftXAdditional: 0,
84
- highlightByLinkForTextShiftYAdditional: 2,
85
- highlightByLinkForTextSizingAdditional: 1,
86
- highlightByLinkForTextWeightAdditional: 0,
87
- highlightByLinkForTextFadingMin: 0.21,
88
- /** Label */
89
- highlightByNodeForLabelFadingMin: 0.21,
90
- highlightByNodeForLabelSizingAdditional: 1,
91
- highlightByNodeForLabelWeightAdditional: 0,
92
- highlightByLinkForLabelFadingMin: 0.21,
93
- highlightByLinkForLabelSizingAdditional: 1,
94
- highlightByLinkForLabelWeightAdditional: 0,
95
- /** Link */
96
- highlightByNodeForLinkFadingMin: 0.21,
97
- highlightByNodeForLinkSizeAdditional: 0,
98
- highlightByNodeForLinkColor: null,
99
- highlightByLinkForLinkFadingMin: 0.21,
100
- highlightByLinkForLinkSizeAdditional: 0,
101
- highlightByLinkForLinkColor: null,
102
- /** Arrow */
103
- highlightByNodeForArrowFadingMin: 0.21,
104
- highlightByNodeForArrowSizeAdditional: 0,
105
- highlightByNodeForArrowColor: null,
106
- highlightByNodeForArrowBorderColor: null,
107
- highlightByNodeForArrowBorderSizingAdditional: 0,
108
- highlightByLinkForArrowFadingMin: 0.21,
109
- highlightByLinkForArrowSizeAdditional: 0,
110
- highlightByLinkForArrowColor: null,
111
- highlightByLinkForArrowBorderColor: null,
112
- highlightByLinkForArrowBorderSizingAdditional: 0,
113
- };
114
-
115
- const LINK_SETTINGS = {
116
- prettyDraw: true,
117
- linkScaleSwitch: 1,
118
- linkColorAfterScaleSwitch: "#000000FF",
119
- linkColorBeforeScaleSwitch: "#999",
120
- linkWidthAfterScaleSwitch: 0.1,
121
- linkWidthBeforeScaleSwitch: 1,
122
- arrow: true,
123
- arrowByHighlight: true,
124
- particles: true,
125
- particleFlexSpeed: true,
126
- particleFlexSpeedCoefficient: 4,
127
- };
128
- const LINK_OPTIONS = {
129
- alpha: 1,
130
- arrowAlpha: 1,
131
- arrowSize: 2,
132
- arrowBorderColor: "#000000FF",
133
- arrowBorderWidth: 0.1,
134
- particleAlpha: 1,
135
- particleColor: "#000000FF",
136
- particleBorderColor: "#000000FF",
137
- particleBorderWidth: 0.1,
138
- particleCount: 2,
139
- particleRadius: 0.5,
140
- particleSteps: 60,
141
- };
142
-
143
- const NODE_SETTINGS = {
144
- nodeRadiusFlexible: true,
145
- nodeRadiusLinkCountForStep: 5,
146
- nodeRadiusIncrementByStep: 1,
147
- nodeRadiusMaxLinearSteps: 5,
148
- nodeRadiusLinkCountDividerForLog: 25,
149
- nodeRadiusLogFactor: 2.5,
150
- nodeSizeFlexible: true,
151
- nodeSizeLinkCountForStep: 5,
152
- nodeSizeIncrementByStep: 0.1,
153
- nodeSizeMaxLinearSteps: 5,
154
- nodeSizeLinkCountDividerForLog: 25,
155
- nodeSizeLogFactor: 2.5,
156
- textScaleMin: 1.5,
157
- textScaleMax: 18,
158
- textSizeMin: 3.5,
159
- textSizeMax: 1.5,
160
- textShiftYMin: 4,
161
- textShiftYMax: 2.5,
162
- textNodeDebug: false,
163
- };
164
- const NODE_OPTIONS = {
165
- shape: "circle",
166
- height: 10,
167
- width: 15,
168
- borderRadius: 0,
169
- radius: 4,
170
- alpha: 1,
171
- labelXPadding: 1,
172
- labelYPadding: 1,
173
- borderWidth: 0.1,
174
- borderColor: "#000000FF",
175
- textAlpha: 1,
176
- textWidth: 20,
177
- textShiftX: 0,
178
- textFont: "Arial",
179
- textAlign: "center",
180
- textColor: "#333",
181
- textStyle: "normal",
182
- textWeight: 500,
183
- textGap: 0,
184
- label: null,
185
- labelAlpha: 1,
186
- labelAlign: "center",
187
- labelColor: "#333",
188
- labelFont: "Arial",
189
- labelGap: 0,
190
- labelSize: 3.5,
191
- labelStyle: "normal",
192
- labelWeight: 500,
193
- labelWidth: 20,
194
- };
195
-
196
44
  function forceSettingsGetter(settings, prevSettings) {
197
45
  return {
198
46
  ...(prevSettings ?? FORCE_SETTINGS),
@@ -573,50 +421,52 @@ function nodeIterationExtractor(node, i, nodes, state, option, optionConstantGet
573
421
  return customOptions;
574
422
  }
575
423
 
576
- function calculateLinkPositionByNode(link, arrowSize = 0) {
577
- const source = link.source;
578
- const target = link.target;
579
- if (typeof source != "object" || typeof target != "object")
580
- return null;
424
+ function calculateLinkPositionByNode(xStart, yStart, xEnd, yEnd, sourceNode, targetNode, arrowSize = 0) {
581
425
  const sourcePoint = getLinkPoint({
426
+ xStart,
427
+ yStart,
428
+ xEnd,
429
+ yEnd,
582
430
  arrowSize: 0,
583
- node: source,
584
- oppositeNode: target,
585
- });
431
+ node: sourceNode});
586
432
  const targetPoint = getLinkPoint({
587
- arrowSize: arrowSize > 0 ? arrowSize * 0.85 : 0,
588
- node: target,
589
- oppositeNode: source,
590
- });
591
- if (!sourcePoint || !targetPoint)
592
- return null;
433
+ xStart: xEnd,
434
+ yStart: yEnd,
435
+ xEnd: xStart,
436
+ yEnd: yStart,
437
+ arrowSize: arrowSize > 0 ? arrowSize * 0.9 : 0,
438
+ node: targetNode});
439
+ const targetPointArrow = getLinkPoint({
440
+ xStart: xEnd,
441
+ yStart: yEnd,
442
+ xEnd: xStart,
443
+ yEnd: yStart,
444
+ arrowSize: 0,
445
+ node: targetNode});
593
446
  const distance = targetPoint && sourcePoint
594
447
  ? Math.sqrt((targetPoint.x - sourcePoint.x) ** 2 + (targetPoint.y - sourcePoint.y) ** 2)
595
448
  : 0;
596
449
  return {
597
- x1: sourcePoint.x,
598
- y1: sourcePoint.y,
599
- x2: targetPoint.x,
600
- y2: targetPoint.y,
450
+ xStart: sourcePoint.x,
451
+ yStart: sourcePoint.y,
452
+ xEnd: targetPoint.x,
453
+ yEnd: targetPoint.y,
454
+ xEndArrow: targetPointArrow.x,
455
+ yEndArrow: targetPointArrow.y,
601
456
  distance,
602
457
  };
603
458
  }
604
459
  function getLinkPoint(opts) {
605
- if (opts.node.x == undefined ||
606
- opts.node.y == undefined ||
607
- opts.oppositeNode.x == undefined ||
608
- opts.oppositeNode.y == undefined)
609
- return null;
610
460
  let nodePoint;
611
461
  switch (opts.node._shape) {
612
462
  case "circle": {
613
463
  nodePoint = getCircleIntersection({
614
- x: opts.node.x,
615
- y: opts.node.y,
464
+ x: opts.xStart,
465
+ y: opts.yStart,
616
466
  radius: (opts.node._radius ?? COMMON_SETTINGS.nodeRadius) +
617
467
  (opts.node._borderWidth ? opts.node._borderWidth / 2 : 0),
618
- oppositeX: opts.oppositeNode.x,
619
- oppositeY: opts.oppositeNode.y,
468
+ oppositeX: opts.xEnd,
469
+ oppositeY: opts.yEnd,
620
470
  arrowSize: opts.arrowSize,
621
471
  });
622
472
  break;
@@ -625,23 +475,23 @@ function getLinkPoint(opts) {
625
475
  case "text": {
626
476
  nodePoint = getRectangleIntersection({
627
477
  arrowSize: opts.arrowSize,
628
- x: opts.node.x,
629
- y: opts.node.y,
478
+ x: opts.xStart,
479
+ y: opts.yStart,
630
480
  height: (opts.node._height ?? COMMON_SETTINGS.nodeSize) + (opts.node._borderWidth ?? 0),
631
481
  width: (opts.node._width ?? COMMON_SETTINGS.nodeSize) + (opts.node._borderWidth ?? 0),
632
- oppositeX: opts.oppositeNode.x,
633
- oppositeY: opts.oppositeNode.y,
482
+ oppositeX: opts.xEnd,
483
+ oppositeY: opts.yEnd,
634
484
  borderRadius: opts.node._shape === "text" ? 0 : (opts.node._borderRadius ?? 0),
635
485
  });
636
486
  break;
637
487
  }
638
488
  default: {
639
489
  nodePoint = getCircleIntersection({
640
- x: opts.node.x,
641
- y: opts.node.y,
490
+ x: opts.xStart,
491
+ y: opts.yStart,
642
492
  radius: opts.node._radius ?? COMMON_SETTINGS.nodeRadius,
643
- oppositeX: opts.oppositeNode.x,
644
- oppositeY: opts.oppositeNode.y,
493
+ oppositeX: opts.xEnd,
494
+ oppositeY: opts.yEnd,
645
495
  arrowSize: opts.arrowSize,
646
496
  });
647
497
  }
@@ -922,31 +772,34 @@ function getParticlePosition(opts) {
922
772
  }
923
773
  }
924
774
 
925
- function linkByPointerGetter({ areaRect, areaTransform, mouseEvent, links, linkHoverExtraZone, }) {
775
+ function linkByPointerGetter({ areaRect, areaTransform, mouseEvent, links, linkHoverExtraZone, curve, }) {
926
776
  if (!areaRect)
927
777
  return undefined;
928
778
  const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
929
779
  return d3Array.greatest(links, (link) => {
930
- if (isNearLink(pointerX, pointerY, link, linkHoverExtraZone))
931
- return link.index;
780
+ if (!jsHelpers.isObject(link.source) ||
781
+ !jsHelpers.isObject(link.target) ||
782
+ link.source.visible === false ||
783
+ link.target.visible === false)
784
+ return;
785
+ const x1 = (link._x1 ?? link.source.x);
786
+ const y1 = (link._y1 ?? link.source.y);
787
+ const x2 = (link._x2 ?? link.target.x);
788
+ const y2 = (link._y2 ?? link.target.y);
789
+ const cx = link._cx ?? 0;
790
+ const cy = link._cy ?? 0;
791
+ return curve
792
+ ? hitTestQuadratic(pointerX, pointerY, x1, y1, cx, cy, x2, y2, linkHoverExtraZone)
793
+ ? link.index
794
+ : undefined
795
+ : isNearLink(pointerX, pointerY, x1, y1, x2, y2, linkHoverExtraZone)
796
+ ? link.index
797
+ : undefined;
932
798
  });
933
799
  }
934
- function isNearLink(mouseX, mouseY, link, threshold = 2) {
935
- if (!jsHelpers.isObject(link.source) ||
936
- !jsHelpers.isObject(link.target) ||
937
- link.source.visible === false ||
938
- link.target.visible === false)
939
- return false;
940
- const x1 = link.source.x;
941
- const y1 = link.source.y;
942
- const x2 = link.target.x;
943
- const y2 = link.target.y;
944
- const distance = distanceToLine(mouseX, mouseY, x1, y1, x2, y2);
945
- return distance <= threshold;
946
- }
947
- function distanceToLine(x, y, x1, y1, x2, y2) {
948
- const A = x - x1;
949
- const B = y - y1;
800
+ function isNearLink(px, py, x1, y1, x2, y2, threshold = 2) {
801
+ const A = px - x1;
802
+ const B = py - y1;
950
803
  const C = x2 - x1;
951
804
  const D = y2 - y1;
952
805
  const dot = A * C + B * D;
@@ -968,9 +821,56 @@ function distanceToLine(x, y, x1, y1, x2, y2) {
968
821
  xx = x1 + param * C;
969
822
  yy = y1 + param * D;
970
823
  }
971
- const dx = x - xx;
972
- const dy = y - yy;
973
- return Math.sqrt(dx * dx + dy * dy);
824
+ const dx = px - xx;
825
+ const dy = py - yy;
826
+ return Math.sqrt(dx * dx + dy * dy) <= threshold;
827
+ }
828
+ function hitTestQuadratic(px, py, x0, y0, xc, yc, x2, y2, tol) {
829
+ const bb = bezierBBox(x0, y0, xc, yc, x2, y2);
830
+ if (px < bb.minX - tol || px > bb.maxX + tol || py < bb.minY - tol || py > bb.maxY + tol) {
831
+ return false;
832
+ }
833
+ const chord = Math.hypot(x2 - x0, y2 - y0);
834
+ const contLen = Math.hypot(xc - x0, yc - y0) + Math.hypot(x2 - xc, y2 - yc);
835
+ const approxLen = (chord + contLen) / 2;
836
+ const steps = Math.max(16, Math.ceil(approxLen / 1));
837
+ const tol2 = tol * tol;
838
+ for (let i = 0; i <= steps; i++) {
839
+ const t = i / steps;
840
+ const mt = 1 - t;
841
+ const x = mt * mt * x0 + 2 * mt * t * xc + t * t * x2;
842
+ const y = mt * mt * y0 + 2 * mt * t * yc + t * t * y2;
843
+ const dx = px - x;
844
+ const dy = py - y;
845
+ if (dx * dx + dy * dy <= tol2)
846
+ return true;
847
+ }
848
+ return false;
849
+ }
850
+ function bezierBBox(x0, y0, x1, y1, x2, y2) {
851
+ function extrema(p0, p1, p2) {
852
+ const denom = p0 - 2 * p1 + p2;
853
+ if (denom === 0)
854
+ return [];
855
+ const t = (p0 - p1) / denom;
856
+ return t > 0 && t < 1 ? [t] : [];
857
+ }
858
+ const ts = [...extrema(x0, x1, x2), ...extrema(y0, y1, y2), 0, 1];
859
+ let maxX = -Infinity, maxY = -Infinity, minX = Infinity, minY = Infinity;
860
+ for (const t of ts) {
861
+ const mt = 1 - t;
862
+ const x = mt * mt * x0 + 2 * mt * t * x1 + t * t * x2;
863
+ const y = mt * mt * y0 + 2 * mt * t * y1 + t * t * y2;
864
+ if (x < minX)
865
+ minX = x;
866
+ if (x > maxX)
867
+ maxX = x;
868
+ if (y < minY)
869
+ minY = y;
870
+ if (y > maxY)
871
+ maxY = y;
872
+ }
873
+ return { minX, minY, maxX, maxY };
974
874
  }
975
875
 
976
876
  function nodeHighlight(opts) {
@@ -1146,6 +1046,166 @@ function extractLinkPointIds(link) {
1146
1046
  return { sourceId, targetId };
1147
1047
  }
1148
1048
 
1049
+ const GRAPH_SETTINGS = {
1050
+ zoomExtent: [0.3, 10],
1051
+ translateExtent: [[], []],
1052
+ translateExtentEnable: true,
1053
+ translateExtentCoefficient: [3, 3],
1054
+ dragPlaceCoefficient: dragPlaceCoefficientGetter,
1055
+ zoomInitial: null,
1056
+ showDrawTime: true,
1057
+ showDrawTimeEveryTick: false,
1058
+ };
1059
+
1060
+ const HIGHLIGHT_SETTINGS = {
1061
+ highlightByHoverNode: true,
1062
+ highlightByHoverLink: true,
1063
+ highlightUpFrames: 5,
1064
+ highlightDownFrames: 5,
1065
+ linkHoverExtraZone: 2,
1066
+ /** Node */
1067
+ highlightByNodeOnlyRoot: true,
1068
+ highlightByNodeForNodeColorFading: false,
1069
+ highlightByNodeForNodeColor: null,
1070
+ highlightByNodeForNodeSizingAdditional: 0.5,
1071
+ highlightByNodeForNodeSizingAdditionalCoefficient: 0.35,
1072
+ highlightByNodeForNodeFadingMin: 0.21,
1073
+ highlightByNodeForNodeBorderColor: null,
1074
+ highlightByNodeForNodeBorderSizingAdditional: 0,
1075
+ highlightByLinkForNodeColorFading: false,
1076
+ highlightByLinkForNodeColor: null,
1077
+ highlightByLinkForNodeSizingAdditional: 0.5,
1078
+ highlightByLinkForNodeSizingAdditionalCoefficient: 0.35,
1079
+ highlightByLinkForNodeFadingMin: 0.21,
1080
+ highlightByLinkForNodeBorderColor: null,
1081
+ highlightByLinkForNodeBorderSizingAdditional: 0,
1082
+ /** Text */
1083
+ highlightByNodeForTextShiftXAdditional: 0,
1084
+ highlightByNodeForTextShiftYAdditional: 2,
1085
+ highlightByNodeForTextSizingAdditional: 1,
1086
+ highlightByNodeForTextWeightAdditional: 0,
1087
+ highlightByNodeForTextFadingMin: 0.21,
1088
+ highlightByLinkForTextShiftXAdditional: 0,
1089
+ highlightByLinkForTextShiftYAdditional: 2,
1090
+ highlightByLinkForTextSizingAdditional: 1,
1091
+ highlightByLinkForTextWeightAdditional: 0,
1092
+ highlightByLinkForTextFadingMin: 0.21,
1093
+ /** Label */
1094
+ highlightByNodeForLabelFadingMin: 0.21,
1095
+ highlightByNodeForLabelSizingAdditional: 1,
1096
+ highlightByNodeForLabelWeightAdditional: 0,
1097
+ highlightByLinkForLabelFadingMin: 0.21,
1098
+ highlightByLinkForLabelSizingAdditional: 1,
1099
+ highlightByLinkForLabelWeightAdditional: 0,
1100
+ /** Link */
1101
+ highlightByNodeForLinkFadingMin: 0.21,
1102
+ highlightByNodeForLinkSizeAdditional: 0,
1103
+ highlightByNodeForLinkColor: null,
1104
+ highlightByLinkForLinkFadingMin: 0.21,
1105
+ highlightByLinkForLinkSizeAdditional: 0,
1106
+ highlightByLinkForLinkColor: null,
1107
+ /** Arrow */
1108
+ highlightByNodeForArrowFadingMin: 0.21,
1109
+ highlightByNodeForArrowSizeAdditional: 0,
1110
+ highlightByNodeForArrowColor: null,
1111
+ highlightByNodeForArrowBorderColor: null,
1112
+ highlightByNodeForArrowBorderSizingAdditional: 0,
1113
+ highlightByLinkForArrowFadingMin: 0.21,
1114
+ highlightByLinkForArrowSizeAdditional: 0,
1115
+ highlightByLinkForArrowColor: null,
1116
+ highlightByLinkForArrowBorderColor: null,
1117
+ highlightByLinkForArrowBorderSizingAdditional: 0,
1118
+ };
1119
+
1120
+ const LINK_SETTINGS = {
1121
+ prettyDraw: true,
1122
+ curve: false,
1123
+ linkScaleSwitch: 1,
1124
+ linkColorAfterScaleSwitch: "#000000FF",
1125
+ linkColorBeforeScaleSwitch: "#999",
1126
+ linkWidthAfterScaleSwitch: 0.1,
1127
+ linkWidthBeforeScaleSwitch: 1,
1128
+ arrow: true,
1129
+ arrowByHighlight: true,
1130
+ particles: true,
1131
+ particleFlexSpeed: true,
1132
+ particleFlexSpeedCoefficient: 4,
1133
+ };
1134
+ const LINK_OPTIONS = {
1135
+ alpha: 1,
1136
+ arrowAlpha: 1,
1137
+ arrowSize: 2,
1138
+ arrowBorderColor: "#000000FF",
1139
+ arrowBorderWidth: 0.1,
1140
+ particleAlpha: 1,
1141
+ particleColor: "#000000FF",
1142
+ particleBorderColor: "#000000FF",
1143
+ particleBorderWidth: 0.1,
1144
+ particleCount: 2,
1145
+ particleRadius: 0.5,
1146
+ particleSteps: 60,
1147
+ };
1148
+
1149
+ const NODE_SETTINGS = {
1150
+ nodeRadiusFlexible: true,
1151
+ nodeRadiusLinkCountForStep: 5,
1152
+ nodeRadiusIncrementByStep: 1,
1153
+ nodeRadiusMaxLinearSteps: 5,
1154
+ nodeRadiusLinkCountDividerForLog: 25,
1155
+ nodeRadiusLogFactor: 2.5,
1156
+ nodeSizeFlexible: true,
1157
+ nodeSizeLinkCountForStep: 5,
1158
+ nodeSizeIncrementByStep: 0.1,
1159
+ nodeSizeMaxLinearSteps: 5,
1160
+ nodeSizeLinkCountDividerForLog: 25,
1161
+ nodeSizeLogFactor: 2.5,
1162
+ textScaleMin: 1.5,
1163
+ textScaleMax: 18,
1164
+ textSizeMin: 3.5,
1165
+ textSizeMax: 1.5,
1166
+ textShiftYMin: 4,
1167
+ textShiftYMax: 2.5,
1168
+ textNodeDebug: false,
1169
+ };
1170
+ const NODE_OPTIONS = {
1171
+ shape: "circle",
1172
+ height: 10,
1173
+ width: 15,
1174
+ borderRadius: 0,
1175
+ radius: 4,
1176
+ alpha: 1,
1177
+ labelXPadding: 1,
1178
+ labelYPadding: 1,
1179
+ borderWidth: 0.1,
1180
+ borderColor: "#000000FF",
1181
+ textAlpha: 1,
1182
+ textWidth: 20,
1183
+ textShiftX: 0,
1184
+ textFont: "Arial",
1185
+ textAlign: "center",
1186
+ textColor: "#333",
1187
+ textStyle: "normal",
1188
+ textWeight: 500,
1189
+ textGap: 0,
1190
+ label: null,
1191
+ labelAlpha: 1,
1192
+ labelAlign: "center",
1193
+ labelColor: "#333",
1194
+ labelFont: "Arial",
1195
+ labelGap: 0,
1196
+ labelSize: 3.5,
1197
+ labelStyle: "normal",
1198
+ labelWeight: 500,
1199
+ labelWidth: 20,
1200
+ };
1201
+
1202
+ const GRAPH_CACHE_TYPE = {
1203
+ NodeOptions: "nodeOptions",
1204
+ LinkOptions: "linkOptions",
1205
+ NodeText: "nodeText",
1206
+ NodeLabel: "nodeLabel",
1207
+ };
1208
+
1149
1209
  function initArea() {
1150
1210
  if (!this.area || !this.context || !this.container) {
1151
1211
  this.container = d3Selection.create("div")
@@ -1227,6 +1287,42 @@ function initDnd() {
1227
1287
  d3Selection.select(this.area).call(dragHandler);
1228
1288
  }
1229
1289
 
1290
+ function calculateCurveLinkPositionByNode(xStart, yStart, xEnd, yEnd, sourceNode, targetNode, groupIndex, arrowSize = 0) {
1291
+ const sourceRadius = (sourceNode._radius ?? COMMON_SETTINGS.nodeRadius) + (sourceNode._borderWidth ?? 0) * 0.5;
1292
+ const targetRadius = (targetNode._radius ?? COMMON_SETTINGS.nodeRadius) + (targetNode._borderWidth ?? 0) * 0.5;
1293
+ const effectiveTargetRadius = targetRadius + arrowSize * 0.9;
1294
+ const mx = (xStart + xEnd) * 0.5;
1295
+ const my = (yStart + yEnd) * 0.5;
1296
+ const dx = xEnd - xStart;
1297
+ const dy = yEnd - yStart;
1298
+ const lenSq = dx * dx + dy * dy;
1299
+ const len = lenSq > 0 ? Math.sqrt(lenSq) : 1;
1300
+ const nx = -dy / len;
1301
+ const ny = dx / len;
1302
+ const offset = 0.12 * len * groupIndex;
1303
+ const xControl = mx + nx * offset;
1304
+ const yControl = my + ny * offset;
1305
+ const [xStartP, yStartP] = adjustPoint(xStart, yStart, xControl, yControl, sourceRadius);
1306
+ const [xEndP, yEndP] = adjustPoint(xEnd, yEnd, xControl, yControl, effectiveTargetRadius);
1307
+ const [xEndArrow, yEndArrow] = adjustPoint(xEnd, yEnd, xControl, yControl, targetRadius);
1308
+ return {
1309
+ xStart: xStartP,
1310
+ yStart: yStartP,
1311
+ xEnd: xEndP,
1312
+ yEnd: yEndP,
1313
+ xControl,
1314
+ yControl,
1315
+ xEndArrow,
1316
+ yEndArrow,
1317
+ };
1318
+ }
1319
+ function adjustPoint(x, y, xControl, yControl, radius) {
1320
+ const dxToControl = xControl - x;
1321
+ const dyToControl = yControl - y;
1322
+ const distToControl = Math.sqrt(dxToControl * dxToControl + dyToControl * dyToControl) || 1;
1323
+ return [x + (dxToControl / distToControl) * radius, y + (dyToControl / distToControl) * radius];
1324
+ }
1325
+
1230
1326
  function getDrawLink() {
1231
1327
  return function drawLink(link) {
1232
1328
  if (!this.context ||
@@ -1342,23 +1438,62 @@ function getDrawLink() {
1342
1438
  let yStart = link.source.y;
1343
1439
  let xEnd = link.target.x;
1344
1440
  let yEnd = link.target.y;
1441
+ let xControl = 0;
1442
+ let yControl = 0;
1443
+ let xEndArrow = xEnd;
1444
+ let yEndArrow = yEnd;
1345
1445
  let linkDistance = 0;
1346
- if (this.linkSettings.prettyDraw ||
1347
- this.linkSettings.particleFlexSpeed ||
1348
- (this.linkSettings.arrow && arrowAlpha > 0)) {
1349
- const isHasArrow = this.linkSettings.arrow && arrowAlpha > 0;
1350
- const position = calculateLinkPositionByNode(link, isHasArrow ? arrowSize : 0);
1351
- if (position) {
1352
- xStart = position.x1;
1353
- xEnd = position.x2;
1354
- yStart = position.y1;
1355
- yEnd = position.y2;
1446
+ const isHasArrow = this.linkSettings.arrow && arrowAlpha > 0;
1447
+ if (!this.linkSettings.curve) {
1448
+ if (this.linkSettings.prettyDraw ||
1449
+ this.linkSettings.particleFlexSpeed ||
1450
+ (this.linkSettings.arrow && arrowAlpha > 0)) {
1451
+ const position = calculateLinkPositionByNode(xStart, yStart, xEnd, yEnd, link.source, link.target, isHasArrow ? arrowSize : 0);
1452
+ xStart = position.xStart;
1453
+ xEnd = position.xEnd;
1454
+ yStart = position.yStart;
1455
+ yEnd = position.yEnd;
1456
+ xEndArrow = position.xEndArrow;
1457
+ yEndArrow = position.yEndArrow;
1356
1458
  linkDistance = position.distance;
1357
1459
  }
1460
+ link._x1 = xStart;
1461
+ link._y1 = yStart;
1462
+ link._x2 = xEnd;
1463
+ link._y2 = yEnd;
1464
+ link._ax = xEndArrow;
1465
+ link._ay = yEndArrow;
1466
+ this.context.moveTo(xStart, yStart);
1467
+ this.context.lineTo(xEnd, yEnd);
1468
+ this.context.stroke();
1469
+ }
1470
+ else {
1471
+ const position = calculateCurveLinkPositionByNode(xStart, yStart, xEnd, yEnd, link.source, link.target, link._groupIndex ?? 0, isHasArrow ? arrowSize : 0);
1472
+ xStart = position.xStart;
1473
+ yStart = position.yStart;
1474
+ xEnd = position.xEnd;
1475
+ yEnd = position.yEnd;
1476
+ xControl = position.xControl;
1477
+ yControl = position.yControl;
1478
+ xEndArrow = position.xEndArrow;
1479
+ yEndArrow = position.yEndArrow;
1480
+ link._x1 = position.xStart;
1481
+ link._y1 = position.yStart;
1482
+ link._x2 = position.xEnd;
1483
+ link._y2 = position.yEnd;
1484
+ link._cx = position.xControl;
1485
+ link._cy = position.yControl;
1486
+ link._ax = position.xEndArrow;
1487
+ link._ay = position.yEndArrow;
1488
+ this.context.beginPath();
1489
+ this.context.moveTo(position.xStart, position.yStart);
1490
+ this.context.quadraticCurveTo(position.xControl, position.yControl, position.xEnd, position.yEnd);
1491
+ this.context.setLineDash([]);
1492
+ // if (isDashed) {
1493
+ // this.context.setLineDash([10, 5]);
1494
+ // }
1495
+ this.context.stroke();
1358
1496
  }
1359
- this.context.moveTo(xStart, yStart);
1360
- this.context.lineTo(xEnd, yEnd);
1361
- this.context.stroke();
1362
1497
  /** Particle */
1363
1498
  if (this.linkSettings.particles &&
1364
1499
  ((this.highlightedNode &&
@@ -1426,15 +1561,28 @@ function getDrawLink() {
1426
1561
  }
1427
1562
  /** Arrow */
1428
1563
  if (this.linkSettings.arrow && arrowAlpha > 0) {
1564
+ let angle = 0;
1565
+ if (!this.linkSettings.curve) {
1566
+ angle = Math.atan2(yEndArrow - yStart, xEndArrow - xStart);
1567
+ }
1568
+ else {
1569
+ const tangentX = 2 * (xEndArrow - xControl);
1570
+ const tangentY = 2 * (yEndArrow - yControl);
1571
+ if (Math.abs(tangentX) < 0.001 && Math.abs(tangentY) < 0.001) {
1572
+ angle = Math.atan2(yEndArrow - yControl, xEndArrow - xControl);
1573
+ }
1574
+ else {
1575
+ angle = Math.atan2(tangentY, tangentX);
1576
+ }
1577
+ }
1429
1578
  this.context.beginPath();
1430
1579
  this.context.globalAlpha = arrowAlpha;
1431
1580
  this.context.strokeStyle = arrowBorderColor;
1432
1581
  this.context.lineWidth = arrowBorderWidth;
1433
1582
  this.context.fillStyle = arrowColor;
1434
- const angle = Math.atan2(yEnd - yStart, xEnd - xStart);
1435
- this.context.moveTo(xEnd, yEnd);
1436
- this.context.lineTo(xEnd - arrowSize * Math.cos(angle - Math.PI / 6), yEnd - arrowSize * Math.sin(angle - Math.PI / 6));
1437
- this.context.lineTo(xEnd - arrowSize * Math.cos(angle + Math.PI / 6), yEnd - arrowSize * Math.sin(angle + Math.PI / 6));
1583
+ this.context.moveTo(xEndArrow, yEndArrow);
1584
+ this.context.lineTo(xEndArrow - arrowSize * Math.cos(angle - Math.PI / 6), yEndArrow - arrowSize * Math.sin(angle - Math.PI / 6));
1585
+ this.context.lineTo(xEndArrow - arrowSize * Math.cos(angle + Math.PI / 6), yEndArrow - arrowSize * Math.sin(angle + Math.PI / 6));
1438
1586
  this.context.closePath();
1439
1587
  this.context.fill();
1440
1588
  if (arrowBorderWidth > 0) {
@@ -1958,6 +2106,7 @@ function initPointer() {
1958
2106
  areaTransform: this.areaTransform,
1959
2107
  mouseEvent: event,
1960
2108
  links: this.links,
2109
+ curve: this.linkSettings.curve,
1961
2110
  });
1962
2111
  if (currentLink?.highlight != undefined)
1963
2112
  highlightLink = currentLink?.highlight;
@@ -2019,6 +2168,7 @@ function initPointer() {
2019
2168
  areaTransform: this.areaTransform,
2020
2169
  mouseEvent: event,
2021
2170
  links: this.links,
2171
+ curve: this.linkSettings.curve,
2022
2172
  });
2023
2173
  }
2024
2174
  if (!currentNode)
@@ -2043,6 +2193,7 @@ function initPointer() {
2043
2193
  areaTransform: this.areaTransform,
2044
2194
  mouseEvent: event,
2045
2195
  links: this.links,
2196
+ curve: this.linkSettings.curve,
2046
2197
  });
2047
2198
  return void this.listeners.onWheelClick.call(this, event, undefined, currentLink);
2048
2199
  }
@@ -2064,6 +2215,7 @@ function initPointer() {
2064
2215
  areaTransform: this.areaTransform,
2065
2216
  mouseEvent: event,
2066
2217
  links: this.links,
2218
+ curve: this.linkSettings.curve,
2067
2219
  });
2068
2220
  return void this.listeners.onContextMenu.call(this, event, undefined, currentLink);
2069
2221
  }
@@ -2085,6 +2237,7 @@ function initPointer() {
2085
2237
  areaTransform: this.areaTransform,
2086
2238
  mouseEvent: event,
2087
2239
  links: this.links,
2240
+ curve: this.linkSettings.curve,
2088
2241
  });
2089
2242
  return void this.listeners.onDoubleClick.call(this, event, undefined, currentLink);
2090
2243
  }
@@ -2106,6 +2259,7 @@ function initPointer() {
2106
2259
  areaTransform: this.areaTransform,
2107
2260
  mouseEvent: event,
2108
2261
  links: this.links,
2262
+ curve: this.linkSettings.curve,
2109
2263
  });
2110
2264
  return void this.listeners.onClick.call(this, event, undefined, currentLink);
2111
2265
  }
@@ -2151,12 +2305,19 @@ function initResize() {
2151
2305
  return;
2152
2306
  }
2153
2307
  this.updateSize();
2308
+ requestAnimationFrame(() => {
2309
+ this.updateSize();
2310
+ });
2154
2311
  });
2155
2312
  document.addEventListener("scroll", this.updateRect.bind(this), {
2156
2313
  capture: true,
2157
2314
  passive: true,
2158
2315
  signal: abortController.signal,
2159
2316
  });
2317
+ document.addEventListener("visibilitychange", () => {
2318
+ if (!document.hidden)
2319
+ this.updateRect();
2320
+ }, { signal: abortController.signal });
2160
2321
  observer.observe(this.area);
2161
2322
  }
2162
2323
 
@@ -2277,119 +2438,156 @@ function initCollideForce(forceUpdate) {
2277
2438
 
2278
2439
  function updateLinkCache() {
2279
2440
  this.linkOptionsCache = {};
2441
+ const groupMap = {};
2442
+ const groupSelfMap = {};
2280
2443
  for (let i = 0; i < this.links.length; i++) {
2281
2444
  const link = this.links[i];
2282
2445
  const { sourceId, targetId } = extractLinkPointIds(link);
2283
2446
  const linkOptions = linkIterationExtractor(link, i, this.links, this, this.linkSettings.options ?? {}, linkOptionsGetter);
2284
2447
  const id = `${targetId}${sourceId}`;
2285
2448
  this.linkOptionsCache[id] = linkOptions;
2449
+ if (this.linkSettings.curve) {
2450
+ if (sourceId === targetId) {
2451
+ groupSelfMap[id] ??= 0;
2452
+ link._groupIndex = ++groupSelfMap[id];
2453
+ link._self = true;
2454
+ continue;
2455
+ }
2456
+ const reverseId = `${sourceId}${targetId}`;
2457
+ groupMap[reverseId] ??= 0;
2458
+ groupMap[reverseId]++;
2459
+ groupMap[id] ??= 0;
2460
+ link._groupIndex = indexToPosition(++groupMap[id]);
2461
+ link._self = false;
2462
+ }
2463
+ }
2464
+ if (this.linkSettings.curve) {
2465
+ for (const link of this.links) {
2466
+ const { sourceId, targetId } = extractLinkPointIds(link);
2467
+ const id = `${targetId}${sourceId}`;
2468
+ link._groupSize = link._self ? (groupSelfMap[id] ?? 1) : (groupMap[id] ?? 1);
2469
+ }
2286
2470
  }
2287
2471
  }
2472
+ function indexToPosition(n) {
2473
+ if (n === 1)
2474
+ return 0;
2475
+ const index = n >> 1;
2476
+ return n & 1 ? -(index * 2) : index * 2;
2477
+ // return n & 1 ? -(n >> 1) : n >> 1;
2478
+ }
2288
2479
 
2289
- function updateNodeCache() {
2480
+ function updateNodeCache(types) {
2290
2481
  this.nodeOptionsCache = {};
2291
2482
  if (!this.context)
2292
2483
  return;
2293
2484
  for (let i = 0; i < this.nodes.length; i++) {
2294
2485
  const node = this.nodes[i];
2295
- const nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
2296
- const radius = nodeOptions.shape === "circle"
2297
- ? nodeRadiusGetter({
2298
- radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2299
- radiusInitial: nodeOptions.radius,
2300
- radiusIncrementByStep: this.nodeSettings.nodeRadiusIncrementByStep,
2301
- radiusLinkCountForStep: this.nodeSettings.nodeRadiusLinkCountForStep,
2302
- radiusMaxLinearSteps: this.nodeSettings.nodeRadiusMaxLinearSteps,
2303
- radiusLogFactor: this.nodeSettings.nodeRadiusLogFactor,
2304
- radiusLinkCountDividerForLog: this.nodeSettings.nodeRadiusLinkCountDividerForLog,
2305
- linkCount: node.linkCount,
2306
- })
2307
- : nodeOptions.radius;
2308
- let width = nodeOptions.width;
2309
- let height = nodeOptions.height;
2310
- let labelSize = nodeOptions.labelSize;
2311
- if (nodeOptions.shape === "square") {
2312
- const size = nodeSizeGetter({
2313
- heightInitial: nodeOptions.height,
2314
- widthInitial: nodeOptions.width,
2315
- linkCount: node.linkCount,
2316
- sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2317
- sizeIncrementByStep: this.nodeSettings.nodeSizeIncrementByStep,
2318
- sizeLinkCountForStep: this.nodeSettings.nodeSizeLinkCountForStep,
2319
- sizeMaxLinearSteps: this.nodeSettings.nodeSizeMaxLinearSteps,
2320
- sizeLogFactor: this.nodeSettings.nodeSizeLogFactor,
2321
- sizeLinkCountDividerForLog: this.nodeSettings.nodeSizeLinkCountDividerForLog,
2322
- });
2323
- width = size.width;
2324
- height = size.height;
2325
- }
2326
- if (nodeOptions.shape === "text") {
2327
- width = nodeOptions.width;
2328
- const size = nodeSizeGetter({
2329
- heightInitial: nodeOptions.height,
2330
- widthInitial: width,
2331
- linkCount: node.linkCount,
2332
- sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2333
- sizeIncrementByStep: this.nodeSettings.nodeSizeIncrementByStep,
2334
- sizeLinkCountForStep: this.nodeSettings.nodeSizeLinkCountForStep,
2335
- sizeMaxLinearSteps: this.nodeSettings.nodeSizeMaxLinearSteps,
2336
- sizeLogFactor: this.nodeSettings.nodeSizeLogFactor,
2337
- sizeLinkCountDividerForLog: this.nodeSettings.nodeSizeLinkCountDividerForLog,
2338
- });
2339
- labelSize *= size.additionalSizeCoefficient;
2340
- }
2341
- /** label in not text shape */
2342
- if (nodeOptions.shape !== "text" && nodeOptions.label) {
2343
- this.context.font = `${nodeOptions.labelStyle} normal ${nodeOptions.labelWeight} ${nodeOptions.labelSize}px ${nodeOptions.labelFont}`;
2344
- this.context.fillStyle = nodeOptions.labelColor;
2345
- this.context.textAlign = nodeOptions.labelAlign;
2346
- if (nodeOptions.labelWidth == undefined ||
2347
- this.context.measureText(nodeOptions.label).width <= nodeOptions.labelWidth) {
2348
- this.cachedNodeLabel[node.id] = [nodeOptions.label];
2486
+ if (types === true || (jsHelpers.isObject(types) && types.options)) {
2487
+ const nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
2488
+ const radius = nodeOptions.shape === "circle"
2489
+ ? nodeRadiusGetter({
2490
+ radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2491
+ radiusInitial: nodeOptions.radius,
2492
+ radiusIncrementByStep: this.nodeSettings.nodeRadiusIncrementByStep,
2493
+ radiusLinkCountForStep: this.nodeSettings.nodeRadiusLinkCountForStep,
2494
+ radiusMaxLinearSteps: this.nodeSettings.nodeRadiusMaxLinearSteps,
2495
+ radiusLogFactor: this.nodeSettings.nodeRadiusLogFactor,
2496
+ radiusLinkCountDividerForLog: this.nodeSettings.nodeRadiusLinkCountDividerForLog,
2497
+ linkCount: node.linkCount,
2498
+ })
2499
+ : nodeOptions.radius;
2500
+ let width = nodeOptions.width;
2501
+ let height = nodeOptions.height;
2502
+ let labelSize = nodeOptions.labelSize;
2503
+ if (nodeOptions.shape === "square") {
2504
+ const size = nodeSizeGetter({
2505
+ heightInitial: nodeOptions.height,
2506
+ widthInitial: nodeOptions.width,
2507
+ linkCount: node.linkCount,
2508
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2509
+ sizeIncrementByStep: this.nodeSettings.nodeSizeIncrementByStep,
2510
+ sizeLinkCountForStep: this.nodeSettings.nodeSizeLinkCountForStep,
2511
+ sizeMaxLinearSteps: this.nodeSettings.nodeSizeMaxLinearSteps,
2512
+ sizeLogFactor: this.nodeSettings.nodeSizeLogFactor,
2513
+ sizeLinkCountDividerForLog: this.nodeSettings.nodeSizeLinkCountDividerForLog,
2514
+ });
2515
+ width = size.width;
2516
+ height = size.height;
2517
+ }
2518
+ if (nodeOptions.shape === "text") {
2519
+ width = nodeOptions.width;
2520
+ const size = nodeSizeGetter({
2521
+ heightInitial: nodeOptions.height,
2522
+ widthInitial: width,
2523
+ linkCount: node.linkCount,
2524
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2525
+ sizeIncrementByStep: this.nodeSettings.nodeSizeIncrementByStep,
2526
+ sizeLinkCountForStep: this.nodeSettings.nodeSizeLinkCountForStep,
2527
+ sizeMaxLinearSteps: this.nodeSettings.nodeSizeMaxLinearSteps,
2528
+ sizeLogFactor: this.nodeSettings.nodeSizeLogFactor,
2529
+ sizeLinkCountDividerForLog: this.nodeSettings.nodeSizeLinkCountDividerForLog,
2530
+ });
2531
+ labelSize *= size.additionalSizeCoefficient;
2532
+ }
2533
+ nodeOptions.width = node.visible === false ? 0 : width;
2534
+ nodeOptions.height = node.visible === false ? 0 : height;
2535
+ nodeOptions.radius = node.visible === false ? 0 : radius;
2536
+ nodeOptions.labelSize = labelSize;
2537
+ this.nodeOptionsCache[node.id] = nodeOptions;
2538
+ }
2539
+ const nodeOptions = this.nodeOptionsCache[node.id];
2540
+ /** label */
2541
+ if (types === true || (jsHelpers.isObject(types) && types.label)) {
2542
+ /** label in not text shape */
2543
+ if (nodeOptions.shape !== "text" && nodeOptions.label) {
2544
+ this.context.font = `${nodeOptions.labelStyle} normal ${nodeOptions.labelWeight} ${nodeOptions.labelSize}px ${nodeOptions.labelFont}`;
2545
+ this.context.fillStyle = nodeOptions.labelColor;
2546
+ this.context.textAlign = nodeOptions.labelAlign;
2547
+ if (nodeOptions.labelWidth == undefined ||
2548
+ this.context.measureText(nodeOptions.label).width <= nodeOptions.labelWidth) {
2549
+ this.cachedNodeLabel[node.id] = [nodeOptions.label];
2550
+ }
2551
+ const { lines } = getTextLines({
2552
+ context: this.context,
2553
+ maxWidth: nodeOptions.labelWidth,
2554
+ text: nodeOptions.label,
2555
+ textAlign: nodeOptions.labelAlign,
2556
+ textColor: nodeOptions.labelColor,
2557
+ textFont: nodeOptions.labelFont,
2558
+ textSize: nodeOptions.labelSize,
2559
+ textStyle: nodeOptions.labelStyle,
2560
+ textWeight: nodeOptions.labelWeight,
2561
+ });
2562
+ this.cachedNodeLabel[node.id] = lines;
2563
+ /** label in text shape */
2564
+ }
2565
+ else if (nodeOptions.shape === "text" && nodeOptions.label) {
2566
+ const textInfo = getTextLines({
2567
+ context: this.context,
2568
+ text: nodeOptions.label,
2569
+ textAlign: nodeOptions.labelAlign,
2570
+ textColor: nodeOptions.labelColor,
2571
+ textFont: nodeOptions.labelFont,
2572
+ textSize: nodeOptions.labelSize,
2573
+ maxWidth: nodeOptions.labelWidth,
2574
+ textStyle: nodeOptions.labelStyle,
2575
+ textWeight: nodeOptions.textWeight,
2576
+ });
2577
+ const textNodeParameters = [textInfo.currentMaxSize, nodeOptions.labelSize];
2578
+ const lines = textInfo.lines;
2579
+ const textSizeCoefficient = nodeOptions.labelSize / textNodeParameters[1];
2580
+ const maxSize = textNodeParameters[0] * textSizeCoefficient;
2581
+ nodeOptions.height =
2582
+ lines.length * nodeOptions.labelSize +
2583
+ (lines.length - 1) * nodeOptions.labelGap +
2584
+ nodeOptions.labelYPadding;
2585
+ nodeOptions.width = maxSize + nodeOptions.labelXPadding;
2586
+ this.cachedNodeLabel[node.id] = lines;
2349
2587
  }
2350
- const { lines } = getTextLines({
2351
- context: this.context,
2352
- maxWidth: nodeOptions.labelWidth,
2353
- text: nodeOptions.label,
2354
- textAlign: nodeOptions.labelAlign,
2355
- textColor: nodeOptions.labelColor,
2356
- textFont: nodeOptions.labelFont,
2357
- textSize: nodeOptions.labelSize,
2358
- textStyle: nodeOptions.labelStyle,
2359
- textWeight: nodeOptions.labelWeight,
2360
- });
2361
- this.cachedNodeLabel[node.id] = lines;
2362
2588
  }
2363
- /** label in text shape */
2364
- if (nodeOptions.shape === "text" && nodeOptions.label) {
2365
- const textInfo = getTextLines({
2366
- context: this.context,
2367
- text: nodeOptions.label,
2368
- textAlign: nodeOptions.labelAlign,
2369
- textColor: nodeOptions.labelColor,
2370
- textFont: nodeOptions.labelFont,
2371
- textSize: labelSize,
2372
- maxWidth: nodeOptions.labelWidth,
2373
- textStyle: nodeOptions.labelStyle,
2374
- textWeight: nodeOptions.textWeight,
2375
- });
2376
- const textNodeParameters = [textInfo.currentMaxSize, labelSize];
2377
- const lines = textInfo.lines;
2378
- const textSizeCoefficient = labelSize / textNodeParameters[1];
2379
- const maxSize = textNodeParameters[0] * textSizeCoefficient;
2380
- height =
2381
- lines.length * labelSize +
2382
- (lines.length - 1) * nodeOptions.labelGap +
2383
- nodeOptions.labelYPadding;
2384
- width = maxSize + nodeOptions.labelXPadding;
2385
- this.cachedNodeLabel[node.id] = lines;
2386
- }
2387
- nodeOptions.width = node.visible === false ? 0 : width;
2388
- nodeOptions.height = node.visible === false ? 0 : height;
2389
- nodeOptions.radius = node.visible === false ? 0 : radius;
2390
- nodeOptions.labelSize = labelSize;
2391
2589
  /** text */
2392
- if (nodeOptions.text) {
2590
+ if (nodeOptions.text && (types === true || (jsHelpers.isObject(types) && types.text))) {
2393
2591
  this.context.font = `${nodeOptions.textStyle} normal ${nodeOptions.textWeight} ${nodeOptions.textSize}px ${nodeOptions.textFont}`;
2394
2592
  this.context.fillStyle = nodeOptions.textColor;
2395
2593
  this.context.textAlign = nodeOptions.textAlign;
@@ -2410,7 +2608,6 @@ function updateNodeCache() {
2410
2608
  });
2411
2609
  this.cachedNodeText[node.id] = lines;
2412
2610
  }
2413
- this.nodeOptionsCache[node.id] = nodeOptions;
2414
2611
  }
2415
2612
  }
2416
2613
 
@@ -2421,9 +2618,12 @@ function initZoom(currentZoom) {
2421
2618
  .scaleExtent(this.graphSettings.zoomExtent)
2422
2619
  .on("zoom", (event) => {
2423
2620
  this.listeners.onZoom?.call?.(this, event);
2621
+ const oldTransform = this.areaTransform;
2424
2622
  this.areaTransform = event.transform;
2425
- updateLinkCache.call(this);
2426
- updateNodeCache.call(this);
2623
+ if (this.areaTransform.k !== oldTransform.k) {
2624
+ updateLinkCache.call(this);
2625
+ updateNodeCache.call(this, true);
2626
+ }
2427
2627
  if (!this.simulationWorking && !this.highlightWorking)
2428
2628
  requestAnimationFrame(() => this.draw());
2429
2629
  });
@@ -2531,20 +2731,38 @@ class GraphCanvas {
2531
2731
  }
2532
2732
  if (options.highlightSettings) {
2533
2733
  this.highlightSettings = highlightSettingsGetter(options.highlightSettings, this.highlightSettings);
2534
- if (clearCache) {
2535
- this.clearCache(["nodeOptionsCache", "linkOptionsCache"]);
2734
+ if (clearCache === true) {
2735
+ this.clearCache([
2736
+ GRAPH_CACHE_TYPE.NodeOptions,
2737
+ GRAPH_CACHE_TYPE.NodeText,
2738
+ GRAPH_CACHE_TYPE.NodeLabel,
2739
+ GRAPH_CACHE_TYPE.LinkOptions,
2740
+ ]);
2741
+ }
2742
+ else {
2743
+ this.clearCache(clearCache);
2536
2744
  }
2537
2745
  }
2538
2746
  if (options.linkSettings) {
2539
2747
  this.linkSettings = linkSettingsGetter(options.linkSettings, this.linkSettings);
2540
2748
  if (clearCache) {
2541
- this.clearCache(["linkOptionsCache"]);
2749
+ this.clearCache([GRAPH_CACHE_TYPE.LinkOptions]);
2750
+ }
2751
+ else {
2752
+ this.clearCache(clearCache);
2542
2753
  }
2543
2754
  }
2544
2755
  if (options.nodeSettings) {
2545
2756
  this.nodeSettings = nodeSettingsGetter(options.nodeSettings, this.nodeSettings);
2546
2757
  if (clearCache) {
2547
- this.clearCache(["nodeOptionsCache"]);
2758
+ this.clearCache([
2759
+ GRAPH_CACHE_TYPE.NodeOptions,
2760
+ GRAPH_CACHE_TYPE.NodeText,
2761
+ GRAPH_CACHE_TYPE.NodeLabel,
2762
+ ]);
2763
+ }
2764
+ else {
2765
+ this.clearCache(clearCache);
2548
2766
  }
2549
2767
  initCollideForce.call(this, true);
2550
2768
  }
@@ -2572,48 +2790,24 @@ class GraphCanvas {
2572
2790
  throw new Error("couldn't create canvas context");
2573
2791
  this.context.scale(this.dpi, this.dpi);
2574
2792
  this.draw();
2575
- // this.clearHTMLElements();
2576
- // initArea.call<
2577
- // GraphCanvas<NodeData, LinkData>,
2578
- // Parameters<typeof initArea>,
2579
- // ReturnType<typeof initArea>
2580
- // >(this);
2581
- // initDnd.call<
2582
- // GraphCanvas<NodeData, LinkData>,
2583
- // Parameters<typeof initDnd>,
2584
- // ReturnType<typeof initDnd>
2585
- // >(this);
2586
- // initZoom.call<
2587
- // GraphCanvas<NodeData, LinkData>,
2588
- // Parameters<typeof initZoom>,
2589
- // ReturnType<typeof initZoom>
2590
- // >(this);
2591
- // initResize.call<
2592
- // GraphCanvas<NodeData, LinkData>,
2593
- // Parameters<typeof initResize>,
2594
- // ReturnType<typeof initResize>
2595
- // >(this);
2596
- // initPointer.call<
2597
- // GraphCanvas<NodeData, LinkData>,
2598
- // Parameters<typeof initPointer>,
2599
- // ReturnType<typeof initPointer>
2600
- // >(this);
2601
- // this.draw();
2602
2793
  }
2603
2794
  clearCache(keys) {
2604
- if (!keys) {
2605
- updateNodeCache.call(this);
2795
+ if (keys === true) {
2796
+ updateNodeCache.call(this, true);
2606
2797
  updateLinkCache.call(this);
2607
2798
  }
2608
- else {
2609
- for (let i = 0; i < keys.length; i++) {
2610
- const key = keys[i];
2611
- if (key === "nodeOptionsCache") {
2612
- updateNodeCache.call(this);
2613
- }
2614
- else {
2615
- updateLinkCache.call(this);
2616
- }
2799
+ else if (jsHelpers.isArray(keys)) {
2800
+ if (keys.some((k) => k === GRAPH_CACHE_TYPE.NodeText ||
2801
+ k === GRAPH_CACHE_TYPE.NodeLabel ||
2802
+ k === GRAPH_CACHE_TYPE.NodeOptions)) {
2803
+ updateNodeCache.call(this, {
2804
+ label: keys.includes(GRAPH_CACHE_TYPE.NodeLabel),
2805
+ options: keys.includes(GRAPH_CACHE_TYPE.NodeOptions),
2806
+ text: keys.includes(GRAPH_CACHE_TYPE.NodeOptions),
2807
+ });
2808
+ }
2809
+ if (keys.includes(GRAPH_CACHE_TYPE.LinkOptions)) {
2810
+ updateLinkCache.call(this);
2617
2811
  }
2618
2812
  }
2619
2813
  }
@@ -2647,7 +2841,7 @@ class GraphCanvas {
2647
2841
  }
2648
2842
  this.clearHTMLElements();
2649
2843
  this.clearState();
2650
- this.clearCache();
2844
+ this.clearCache(true);
2651
2845
  }
2652
2846
  clearHTMLElements() {
2653
2847
  this.root.replaceChildren();
@@ -2674,7 +2868,7 @@ class GraphCanvas {
2674
2868
  }
2675
2869
  updateData(alpha = 0.5, clearCache = true) {
2676
2870
  if (clearCache) {
2677
- this.clearCache();
2871
+ this.clearCache(clearCache);
2678
2872
  }
2679
2873
  if (this.simulation) {
2680
2874
  initCollideForce.call(this, false);
@@ -2697,7 +2891,7 @@ class GraphCanvas {
2697
2891
  }
2698
2892
  init() {
2699
2893
  initArea.call(this);
2700
- updateNodeCache.call(this);
2894
+ updateNodeCache.call(this, true);
2701
2895
  updateLinkCache.call(this);
2702
2896
  initSimulation.call(this);
2703
2897
  initDnd.call(this);
@@ -3884,6 +4078,7 @@ const NODE_OPTIONS_LABEL_CONTROLS = [
3884
4078
  exports.COMMON_SETTINGS = COMMON_SETTINGS;
3885
4079
  exports.FORCE_CONTROLS = FORCE_CONTROLS;
3886
4080
  exports.FORCE_SETTINGS = FORCE_SETTINGS;
4081
+ exports.GRAPH_CACHE_TYPE = GRAPH_CACHE_TYPE;
3887
4082
  exports.GRAPH_SETTINGS = GRAPH_SETTINGS;
3888
4083
  exports.GraphCanvas = GraphCanvas;
3889
4084
  exports.HIGHLIGHT_BY_LINK_FOR_ARROW_CONTROLS = HIGHLIGHT_BY_LINK_FOR_ARROW_CONTROLS;