@krainovsd/graph 0.13.0-beta3 → 0.14.0-beta-7.2
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.
- package/lib/cjs/index.cjs +1044 -528
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/esm/constants/force-controls.js +4 -2
- package/lib/esm/constants/force-controls.js.map +1 -1
- package/lib/esm/constants/highlight-controls.js +9 -16
- package/lib/esm/constants/highlight-controls.js.map +1 -1
- package/lib/esm/constants/link-controls.js +17 -30
- package/lib/esm/constants/link-controls.js.map +1 -1
- package/lib/esm/constants/node-controls.js +4 -2
- package/lib/esm/constants/node-controls.js.map +1 -1
- package/lib/esm/index.js +8 -7
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/module/GraphCanvas/GraphCanvas.js +303 -78
- package/lib/esm/module/GraphCanvas/GraphCanvas.js.map +1 -1
- package/lib/esm/module/GraphCanvas/constants/force-settings.js +11 -1
- package/lib/esm/module/GraphCanvas/constants/force-settings.js.map +1 -1
- package/lib/esm/module/GraphCanvas/constants/graph-settings.js +17 -5
- package/lib/esm/module/GraphCanvas/constants/graph-settings.js.map +1 -1
- package/lib/esm/module/GraphCanvas/constants/highlight-settings.js +5 -3
- package/lib/esm/module/GraphCanvas/constants/highlight-settings.js.map +1 -1
- package/lib/esm/module/GraphCanvas/constants/index.js +5 -2
- package/lib/esm/module/GraphCanvas/constants/index.js.map +1 -1
- package/lib/esm/module/GraphCanvas/constants/link-settings.js +19 -10
- package/lib/esm/module/GraphCanvas/constants/link-settings.js.map +1 -1
- package/lib/esm/module/GraphCanvas/constants/node-settings.js +13 -3
- package/lib/esm/module/GraphCanvas/constants/node-settings.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/settings/force-settings-getter.js +4 -2
- package/lib/esm/module/GraphCanvas/lib/settings/force-settings-getter.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/settings/graph-settings-getter.js +4 -0
- package/lib/esm/module/GraphCanvas/lib/settings/graph-settings-getter.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/settings/highlight-settings-getter.js +4 -2
- package/lib/esm/module/GraphCanvas/lib/settings/highlight-settings-getter.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/settings/link-settings-getter.js +8 -5
- package/lib/esm/module/GraphCanvas/lib/settings/link-settings-getter.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js +14 -2
- package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/utils/calculate-curve-link-position-by-node.js +5 -2
- package/lib/esm/module/GraphCanvas/lib/utils/calculate-curve-link-position-by-node.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-node.js +5 -2
- package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-node.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/utils/compute-graph-bounds.js +26 -0
- package/lib/esm/module/GraphCanvas/lib/utils/compute-graph-bounds.js.map +1 -0
- package/lib/esm/module/GraphCanvas/lib/utils/get-particle-position.js +40 -30
- package/lib/esm/module/GraphCanvas/lib/utils/get-particle-position.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/utils/is-empty-object.js +11 -0
- package/lib/esm/module/GraphCanvas/lib/utils/is-empty-object.js.map +1 -0
- package/lib/esm/module/GraphCanvas/lib/utils/is-node-visible.js +5 -2
- package/lib/esm/module/GraphCanvas/lib/utils/is-node-visible.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js +1 -5
- package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/utils/node-by-pointer-getter.js +1 -5
- package/lib/esm/module/GraphCanvas/lib/utils/node-by-pointer-getter.js.map +1 -1
- package/lib/esm/module/GraphCanvas/lib/utils/pointer-getter.js +16 -8
- package/lib/esm/module/GraphCanvas/lib/utils/pointer-getter.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/draw-links.js +47 -62
- package/lib/esm/module/GraphCanvas/slices/draw-links.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/draw-nodes.js +9 -4
- package/lib/esm/module/GraphCanvas/slices/draw-nodes.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-area.js +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-area.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-dnd.js +20 -13
- package/lib/esm/module/GraphCanvas/slices/init-dnd.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-draw.js +14 -52
- package/lib/esm/module/GraphCanvas/slices/init-draw.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-pointer.js +45 -72
- package/lib/esm/module/GraphCanvas/slices/init-pointer.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-resize.js +14 -2
- package/lib/esm/module/GraphCanvas/slices/init-resize.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-selection.js +152 -0
- package/lib/esm/module/GraphCanvas/slices/init-selection.js.map +1 -0
- package/lib/esm/module/GraphCanvas/slices/init-simulation.js +12 -2
- package/lib/esm/module/GraphCanvas/slices/init-simulation.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-zoom.js +88 -16
- package/lib/esm/module/GraphCanvas/slices/init-zoom.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/update-link-cache.js +33 -3
- package/lib/esm/module/GraphCanvas/slices/update-link-cache.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/update-node-cache.js +61 -16
- package/lib/esm/module/GraphCanvas/slices/update-node-cache.js.map +1 -1
- package/lib/index.d.ts +106 -56
- package/package.json +3 -2
package/lib/cjs/index.cjs
CHANGED
|
@@ -4,11 +4,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
4
4
|
|
|
5
5
|
const jsHelpers = require('@krainovsd/js-helpers');
|
|
6
6
|
const d3Force = require('d3-force');
|
|
7
|
+
const d3Selection = require('d3-selection');
|
|
7
8
|
const d3Zoom = require('d3-zoom');
|
|
8
9
|
const d3Array = require('d3-array');
|
|
9
|
-
const d3Selection = require('d3-selection');
|
|
10
10
|
const d3Drag = require('d3-drag');
|
|
11
|
-
const lodash = require('lodash');
|
|
12
11
|
|
|
13
12
|
const COMMON_SETTINGS = {
|
|
14
13
|
nodeRadius: 5,
|
|
@@ -23,6 +22,10 @@ const FORCE_SETTINGS = {
|
|
|
23
22
|
xForce: true,
|
|
24
23
|
yForce: true,
|
|
25
24
|
collideForce: true,
|
|
25
|
+
precompute: true,
|
|
26
|
+
precomputeMaxTicks: 300,
|
|
27
|
+
precomputeMaxTimeMs: 200,
|
|
28
|
+
precomputeDisableForcesAfter: false,
|
|
26
29
|
centerPosition: {},
|
|
27
30
|
centerStrength: 1,
|
|
28
31
|
collideStrength: 0.1,
|
|
@@ -40,6 +43,12 @@ const FORCE_SETTINGS = {
|
|
|
40
43
|
linkStrength: 1,
|
|
41
44
|
collideRadius: null,
|
|
42
45
|
};
|
|
46
|
+
const PERFORMANCE_FORCE_SETTINGS = {
|
|
47
|
+
...FORCE_SETTINGS,
|
|
48
|
+
collideIterations: 1,
|
|
49
|
+
precomputeMaxTimeMs: 350,
|
|
50
|
+
precomputeDisableForcesAfter: true,
|
|
51
|
+
};
|
|
43
52
|
|
|
44
53
|
function forceSettingsGetter(settings, prevSettings) {
|
|
45
54
|
return {
|
|
@@ -59,17 +68,18 @@ function linkSettingsGetter(settings, prevSettings) {
|
|
|
59
68
|
return { ...(prevSettings ?? LINK_SETTINGS), ...settings };
|
|
60
69
|
}
|
|
61
70
|
function linkOptionsGetter() {
|
|
71
|
+
const aboveScale = this.areaTransform && this.areaTransform.k > this.linkSettings.linkScaleSwitch;
|
|
62
72
|
return {
|
|
63
73
|
...LINK_OPTIONS,
|
|
64
74
|
drawExtraLink: null,
|
|
65
75
|
drawLink: null,
|
|
66
|
-
color:
|
|
76
|
+
color: aboveScale
|
|
67
77
|
? this.linkSettings.linkColorAfterScaleSwitch
|
|
68
78
|
: this.linkSettings.linkColorBeforeScaleSwitch,
|
|
69
|
-
arrowColor:
|
|
79
|
+
arrowColor: aboveScale
|
|
70
80
|
? this.linkSettings.linkColorAfterScaleSwitch
|
|
71
81
|
: this.linkSettings.linkColorBeforeScaleSwitch,
|
|
72
|
-
width:
|
|
82
|
+
width: aboveScale
|
|
73
83
|
? this.linkSettings.linkWidthAfterScaleSwitch
|
|
74
84
|
: this.linkSettings.linkWidthBeforeScaleSwitch,
|
|
75
85
|
};
|
|
@@ -185,11 +195,20 @@ function resetDrawTime() {
|
|
|
185
195
|
}
|
|
186
196
|
|
|
187
197
|
function nodeSettingsGetter(settings, prevNodeSettings) {
|
|
188
|
-
|
|
198
|
+
const result = {
|
|
189
199
|
...(prevNodeSettings ?? NODE_SETTINGS),
|
|
190
200
|
idGetter: nodeIdGetter,
|
|
191
201
|
...settings,
|
|
192
202
|
};
|
|
203
|
+
if (result.smartCache && (!result.textScaleSteps || result.textScaleSteps.length === 0)) {
|
|
204
|
+
const step = (result.textScaleMax - result.textScaleMin) / 9;
|
|
205
|
+
const steps = [];
|
|
206
|
+
for (let i = 0; i < 10; i++) {
|
|
207
|
+
steps.push(result.textScaleMin + i * step);
|
|
208
|
+
}
|
|
209
|
+
result.textScaleSteps = steps;
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
193
212
|
}
|
|
194
213
|
const color = colorGetter();
|
|
195
214
|
function nodeOptionsGetter(node) {
|
|
@@ -271,15 +290,47 @@ function highlightSettingsGetter(settings, prevSettings) {
|
|
|
271
290
|
};
|
|
272
291
|
}
|
|
273
292
|
|
|
293
|
+
function computeGraphBounds(nodes) {
|
|
294
|
+
if (!nodes.length)
|
|
295
|
+
return null;
|
|
296
|
+
let minX = Infinity;
|
|
297
|
+
let minY = Infinity;
|
|
298
|
+
let maxX = -Infinity;
|
|
299
|
+
let maxY = -Infinity;
|
|
300
|
+
for (const node of nodes) {
|
|
301
|
+
if (node.x === null || node.x === undefined || node.y === null || node.y === undefined)
|
|
302
|
+
continue;
|
|
303
|
+
if (node.visible === false)
|
|
304
|
+
continue;
|
|
305
|
+
let radius = node._radius;
|
|
306
|
+
radius ??= Math.max(node._width ?? 0, node._height ?? 0) / 2;
|
|
307
|
+
minX = Math.min(minX, node.x - radius);
|
|
308
|
+
minY = Math.min(minY, node.y - radius);
|
|
309
|
+
maxX = Math.max(maxX, node.x + radius);
|
|
310
|
+
maxY = Math.max(maxY, node.y + radius);
|
|
311
|
+
}
|
|
312
|
+
if (!isFinite(minX))
|
|
313
|
+
return null;
|
|
314
|
+
return { minX, minY, maxX, maxY };
|
|
315
|
+
}
|
|
316
|
+
|
|
274
317
|
function pointerGetter(mouseEvent, areaRect, areaTransform) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
318
|
+
let localX;
|
|
319
|
+
let localY;
|
|
320
|
+
if ("offsetX" in mouseEvent) {
|
|
321
|
+
localX = mouseEvent.offsetX;
|
|
322
|
+
localY = mouseEvent.offsetY;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
if (!areaRect)
|
|
326
|
+
return [0, 0];
|
|
327
|
+
const clientX = mouseEvent.touches[0]?.clientX ?? mouseEvent.changedTouches[0]?.clientX;
|
|
328
|
+
const clientY = mouseEvent.touches[0]?.clientY ?? mouseEvent.changedTouches[0]?.clientY;
|
|
329
|
+
localX = clientX - areaRect.left;
|
|
330
|
+
localY = clientY - areaRect.top;
|
|
331
|
+
}
|
|
332
|
+
const px = (localX - areaTransform.x) / areaTransform.k;
|
|
333
|
+
const py = (localY - areaTransform.y) / areaTransform.k;
|
|
283
334
|
return [px, py];
|
|
284
335
|
}
|
|
285
336
|
|
|
@@ -379,10 +430,7 @@ function linkIterationExtractor(link, i, links, state, option, optionConstantGet
|
|
|
379
430
|
return customOptions;
|
|
380
431
|
}
|
|
381
432
|
|
|
382
|
-
function nodeByPointerGetter({
|
|
383
|
-
if (!areaRect)
|
|
384
|
-
return undefined;
|
|
385
|
-
const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
|
|
433
|
+
function nodeByPointerGetter({ pointerX, pointerY, nodes, }) {
|
|
386
434
|
return d3Array.greatest(nodes, (node) => {
|
|
387
435
|
if (isOverlapsNode(node, pointerX, pointerY, undefined))
|
|
388
436
|
return node.index;
|
|
@@ -735,47 +783,54 @@ function isNodeVisible(opts) {
|
|
|
735
783
|
}
|
|
736
784
|
}
|
|
737
785
|
|
|
786
|
+
const FRAME_INTERVAL = 1000 / 60;
|
|
738
787
|
function getParticlePosition(opts) {
|
|
739
|
-
const
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
opts.particle.prev.step !== 0 &&
|
|
746
|
-
opts.particle.prev.step < opts.particle.step &&
|
|
747
|
-
prevStepDifference < opts.distance;
|
|
748
|
-
if (opts.particle.step === 0 && needWait) {
|
|
749
|
-
opts.particle.x = undefined;
|
|
750
|
-
opts.particle.y = undefined;
|
|
751
|
-
return;
|
|
788
|
+
const now = performance.now();
|
|
789
|
+
const particle = opts.particle;
|
|
790
|
+
if (particle._lastTime == undefined) {
|
|
791
|
+
particle._distanceTraveled = (opts.distance / opts.totalCount) * particle.index;
|
|
792
|
+
particle._lastTime = now;
|
|
793
|
+
particle._lastDistance = opts.distance;
|
|
752
794
|
}
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
opts.particle.step = 0;
|
|
759
|
-
return;
|
|
795
|
+
const lastDistance = particle._lastDistance ?? 0;
|
|
796
|
+
if (lastDistance !== opts.distance && lastDistance > 0) {
|
|
797
|
+
const scale = opts.distance / lastDistance;
|
|
798
|
+
particle._distanceTraveled = ((particle._distanceTraveled ?? 0) * scale) % opts.distance;
|
|
799
|
+
particle._lastDistance = opts.distance;
|
|
760
800
|
}
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
opts.
|
|
767
|
-
|
|
768
|
-
|
|
801
|
+
const delta = Math.min(now - particle._lastTime, FRAME_INTERVAL);
|
|
802
|
+
particle._lastTime = now;
|
|
803
|
+
particle._distanceTraveled =
|
|
804
|
+
((particle._distanceTraveled ?? 0) + opts.speed * delta) % opts.distance;
|
|
805
|
+
const progress = particle._distanceTraveled / opts.distance;
|
|
806
|
+
if (opts.xControl !== 0 && opts.yControl !== 0) {
|
|
807
|
+
const t = 1 - progress;
|
|
808
|
+
particle.x =
|
|
809
|
+
t * t * opts.xStart + 2 * t * progress * opts.xControl + progress * progress * opts.xEnd;
|
|
810
|
+
particle.y =
|
|
811
|
+
t * t * opts.yStart + 2 * t * progress * opts.yControl + progress * progress * opts.yEnd;
|
|
769
812
|
}
|
|
770
813
|
else {
|
|
771
|
-
opts.
|
|
814
|
+
const dx = opts.xEnd - opts.xStart;
|
|
815
|
+
const dy = opts.yEnd - opts.yStart;
|
|
816
|
+
particle.x = opts.xStart + dx * progress;
|
|
817
|
+
particle.y = opts.yStart + dy * progress;
|
|
772
818
|
}
|
|
773
819
|
}
|
|
820
|
+
function approximateQuadraticBezierLength(x0, y0, x1, y1, x2, y2) {
|
|
821
|
+
const dx01 = x1 - x0;
|
|
822
|
+
const dy01 = y1 - y0;
|
|
823
|
+
const dx12 = x2 - x1;
|
|
824
|
+
const dy12 = y2 - y1;
|
|
825
|
+
const dx02 = x2 - x0;
|
|
826
|
+
const dy02 = y2 - y0;
|
|
827
|
+
const a = Math.sqrt(dx01 * dx01 + dy01 * dy01);
|
|
828
|
+
const b = Math.sqrt(dx12 * dx12 + dy12 * dy12);
|
|
829
|
+
const c = Math.sqrt(dx02 * dx02 + dy02 * dy02);
|
|
830
|
+
return (a + b + c) / 2;
|
|
831
|
+
}
|
|
774
832
|
|
|
775
|
-
function linkByPointerGetter({
|
|
776
|
-
if (!areaRect)
|
|
777
|
-
return undefined;
|
|
778
|
-
const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
|
|
833
|
+
function linkByPointerGetter({ pointerX, pointerY, links, linkHoverExtraZone, curve, }) {
|
|
779
834
|
return d3Array.greatest(links, (link) => {
|
|
780
835
|
if (!jsHelpers.isObject(link.source) ||
|
|
781
836
|
!jsHelpers.isObject(link.target) ||
|
|
@@ -1046,22 +1101,38 @@ function extractLinkPointIds(link) {
|
|
|
1046
1101
|
return { sourceId, targetId };
|
|
1047
1102
|
}
|
|
1048
1103
|
|
|
1104
|
+
function isEmptyObject(obj) {
|
|
1105
|
+
for (const key in obj) {
|
|
1106
|
+
if (Object.hasOwn(obj, key)) {
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return true;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1049
1113
|
const GRAPH_SETTINGS = {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1114
|
+
zoomAnimation: true,
|
|
1115
|
+
zoomAnimationDuration: 300,
|
|
1116
|
+
zoomToNodeScale: 5,
|
|
1117
|
+
zoomToFitMargin: 0.25,
|
|
1118
|
+
zoomExtent: null,
|
|
1119
|
+
zoomExtentMargin: 0.3,
|
|
1120
|
+
translateExtent: null,
|
|
1121
|
+
translateExtentCoefficient: null,
|
|
1122
|
+
translateExtentOverlap: 0.3,
|
|
1054
1123
|
dragPlaceCoefficient: dragPlaceCoefficientGetter,
|
|
1055
1124
|
zoomInitial: null,
|
|
1056
1125
|
showDrawTime: true,
|
|
1057
1126
|
showDrawTimeEveryTick: false,
|
|
1058
1127
|
};
|
|
1128
|
+
const PERFORMANCE_GRAPH_SETTINGS = {
|
|
1129
|
+
...GRAPH_SETTINGS,
|
|
1130
|
+
};
|
|
1059
1131
|
|
|
1060
1132
|
const HIGHLIGHT_SETTINGS = {
|
|
1061
1133
|
highlightByHoverNode: true,
|
|
1062
1134
|
highlightByHoverLink: true,
|
|
1063
|
-
|
|
1064
|
-
highlightDownFrames: 5,
|
|
1135
|
+
highlightDuration: 150,
|
|
1065
1136
|
linkHoverExtraZone: 2,
|
|
1066
1137
|
/** Node */
|
|
1067
1138
|
highlightByNodeOnlyRoot: true,
|
|
@@ -1116,37 +1187,51 @@ const HIGHLIGHT_SETTINGS = {
|
|
|
1116
1187
|
highlightByLinkForArrowBorderColor: null,
|
|
1117
1188
|
highlightByLinkForArrowBorderSizingAdditional: 0,
|
|
1118
1189
|
};
|
|
1190
|
+
const PERFORMANCE_HIGHLIGHT_SETTINGS = {
|
|
1191
|
+
...HIGHLIGHT_SETTINGS,
|
|
1192
|
+
};
|
|
1119
1193
|
|
|
1120
1194
|
const LINK_SETTINGS = {
|
|
1195
|
+
smartCache: true,
|
|
1121
1196
|
prettyDraw: true,
|
|
1122
|
-
curve:
|
|
1197
|
+
curve: true,
|
|
1123
1198
|
linkScaleSwitch: 1,
|
|
1124
|
-
linkColorAfterScaleSwitch: "#
|
|
1199
|
+
linkColorAfterScaleSwitch: "#C5C5C5FF",
|
|
1125
1200
|
linkColorBeforeScaleSwitch: "#999",
|
|
1126
1201
|
linkWidthAfterScaleSwitch: 0.1,
|
|
1127
1202
|
linkWidthBeforeScaleSwitch: 1,
|
|
1128
1203
|
arrow: true,
|
|
1129
1204
|
arrowByHighlight: true,
|
|
1130
1205
|
particles: true,
|
|
1131
|
-
|
|
1132
|
-
|
|
1206
|
+
// spawn one more particle for N px
|
|
1207
|
+
particleCountByDistance: 25,
|
|
1208
|
+
// speed for 1px
|
|
1209
|
+
particleSpeedByDistance: 50,
|
|
1133
1210
|
};
|
|
1134
1211
|
const LINK_OPTIONS = {
|
|
1135
1212
|
alpha: 1,
|
|
1136
1213
|
arrowAlpha: 1,
|
|
1137
1214
|
arrowSize: 2,
|
|
1138
|
-
arrowBorderColor: "#
|
|
1215
|
+
arrowBorderColor: "#C5C5C5FF",
|
|
1139
1216
|
arrowBorderWidth: 0.1,
|
|
1140
1217
|
particleAlpha: 1,
|
|
1141
|
-
particleColor: "#
|
|
1142
|
-
particleBorderColor: "#
|
|
1218
|
+
particleColor: "#C5C5C5FF",
|
|
1219
|
+
particleBorderColor: "#C5C5C5FF",
|
|
1143
1220
|
particleBorderWidth: 0.1,
|
|
1144
|
-
particleCount: 2,
|
|
1145
1221
|
particleRadius: 0.5,
|
|
1146
|
-
|
|
1222
|
+
};
|
|
1223
|
+
const PERFORMANCE_LINK_SETTINGS = {
|
|
1224
|
+
...LINK_SETTINGS,
|
|
1225
|
+
particles: false,
|
|
1226
|
+
prettyDraw: false,
|
|
1227
|
+
};
|
|
1228
|
+
const PERFORMANCE_LINK_OPTIONS = {
|
|
1229
|
+
...LINK_OPTIONS,
|
|
1147
1230
|
};
|
|
1148
1231
|
|
|
1149
1232
|
const NODE_SETTINGS = {
|
|
1233
|
+
smartCache: true,
|
|
1234
|
+
textScaleSteps: [],
|
|
1150
1235
|
nodeRadiusFlexible: true,
|
|
1151
1236
|
nodeRadiusLinkCountForStep: 5,
|
|
1152
1237
|
nodeRadiusIncrementByStep: 1,
|
|
@@ -1183,14 +1268,14 @@ const NODE_OPTIONS = {
|
|
|
1183
1268
|
textShiftX: 0,
|
|
1184
1269
|
textFont: "Arial",
|
|
1185
1270
|
textAlign: "center",
|
|
1186
|
-
textColor: "#
|
|
1271
|
+
textColor: "#d2d2d2",
|
|
1187
1272
|
textStyle: "normal",
|
|
1188
1273
|
textWeight: 500,
|
|
1189
1274
|
textGap: 0,
|
|
1190
1275
|
label: null,
|
|
1191
1276
|
labelAlpha: 1,
|
|
1192
1277
|
labelAlign: "center",
|
|
1193
|
-
labelColor: "#
|
|
1278
|
+
labelColor: "#ffffff",
|
|
1194
1279
|
labelFont: "Arial",
|
|
1195
1280
|
labelGap: 0,
|
|
1196
1281
|
labelSize: 3.5,
|
|
@@ -1198,6 +1283,14 @@ const NODE_OPTIONS = {
|
|
|
1198
1283
|
labelWeight: 500,
|
|
1199
1284
|
labelWidth: 20,
|
|
1200
1285
|
};
|
|
1286
|
+
const PERFORMANCE_NODE_SETTINGS = {
|
|
1287
|
+
...NODE_SETTINGS,
|
|
1288
|
+
nodeRadiusFlexible: false,
|
|
1289
|
+
nodeSizeFlexible: false,
|
|
1290
|
+
};
|
|
1291
|
+
const PERFOMANCE_NODE_OPTIONS = {
|
|
1292
|
+
...NODE_OPTIONS,
|
|
1293
|
+
};
|
|
1201
1294
|
|
|
1202
1295
|
const GRAPH_CACHE_TYPE = {
|
|
1203
1296
|
NodeOptions: "nodeOptions",
|
|
@@ -1225,11 +1318,11 @@ function initArea() {
|
|
|
1225
1318
|
if (!this.area)
|
|
1226
1319
|
throw new Error("couldn't create canvas");
|
|
1227
1320
|
this.container.appendChild(this.area);
|
|
1228
|
-
this.areaRect = this.area.getBoundingClientRect();
|
|
1229
1321
|
this.context = this.area.getContext("2d");
|
|
1230
1322
|
if (!this.context)
|
|
1231
1323
|
throw new Error("couldn't create canvas context");
|
|
1232
1324
|
this.context.scale(this.dpi, this.dpi);
|
|
1325
|
+
this.updateRect();
|
|
1233
1326
|
}
|
|
1234
1327
|
}
|
|
1235
1328
|
|
|
@@ -1241,10 +1334,8 @@ function initDnd() {
|
|
|
1241
1334
|
if (this.listeners.onDragSubject) {
|
|
1242
1335
|
return this.listeners.onDragSubject.call(this, event);
|
|
1243
1336
|
}
|
|
1244
|
-
if (!this.areaRect)
|
|
1245
|
-
return;
|
|
1246
1337
|
const mouseEvent = event.sourceEvent;
|
|
1247
|
-
const [pointerX, pointerY] =
|
|
1338
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(mouseEvent);
|
|
1248
1339
|
return d3Array.greatest(this.nodes, (node) => {
|
|
1249
1340
|
if (!node.x || !node.y || (jsHelpers.isBoolean(node.drag) && !node.drag))
|
|
1250
1341
|
return undefined;
|
|
@@ -1260,12 +1351,16 @@ function initDnd() {
|
|
|
1260
1351
|
if (this.simulation)
|
|
1261
1352
|
this.simulation.alphaTarget(0.3).restart();
|
|
1262
1353
|
}
|
|
1263
|
-
if (!this.areaRect)
|
|
1264
|
-
return;
|
|
1265
1354
|
const mouseEvent = event.sourceEvent;
|
|
1266
|
-
const [pointerX, pointerY] =
|
|
1267
|
-
|
|
1268
|
-
|
|
1355
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(mouseEvent);
|
|
1356
|
+
if (this._translateExtent) {
|
|
1357
|
+
event.subject.fx = Math.max(this._translateExtent[0][0], Math.min(this._translateExtent[1][0], pointerX));
|
|
1358
|
+
event.subject.fy = Math.max(this._translateExtent[0][1], Math.min(this._translateExtent[1][1], pointerY));
|
|
1359
|
+
}
|
|
1360
|
+
else {
|
|
1361
|
+
event.subject.fx = pointerX;
|
|
1362
|
+
event.subject.fy = pointerY;
|
|
1363
|
+
}
|
|
1269
1364
|
this.listeners.onMoveDragFinished?.call?.(this, event);
|
|
1270
1365
|
})
|
|
1271
1366
|
.on("end", (event) => {
|
|
@@ -1273,10 +1368,16 @@ function initDnd() {
|
|
|
1273
1368
|
if (!event.active && this.simulation)
|
|
1274
1369
|
this.simulation.alphaTarget(0);
|
|
1275
1370
|
const sourceEvent = event.sourceEvent;
|
|
1276
|
-
if (sourceEvent.altKey
|
|
1277
|
-
const [pointerX, pointerY] =
|
|
1278
|
-
|
|
1279
|
-
|
|
1371
|
+
if (sourceEvent.altKey) {
|
|
1372
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(sourceEvent);
|
|
1373
|
+
if (this._translateExtent) {
|
|
1374
|
+
event.subject.fx = Math.max(this._translateExtent[0][0], Math.min(this._translateExtent[1][0], pointerX));
|
|
1375
|
+
event.subject.fy = Math.max(this._translateExtent[0][1], Math.min(this._translateExtent[1][1], pointerY));
|
|
1376
|
+
}
|
|
1377
|
+
else {
|
|
1378
|
+
event.subject.fx = pointerX;
|
|
1379
|
+
event.subject.fy = pointerY;
|
|
1380
|
+
}
|
|
1280
1381
|
}
|
|
1281
1382
|
else {
|
|
1282
1383
|
event.subject.fx = null;
|
|
@@ -1324,7 +1425,7 @@ function adjustPoint(x, y, xControl, yControl, radius) {
|
|
|
1324
1425
|
}
|
|
1325
1426
|
|
|
1326
1427
|
function getDrawLink() {
|
|
1327
|
-
return function drawLink(link) {
|
|
1428
|
+
return function drawLink(link, index) {
|
|
1328
1429
|
if (!this.context ||
|
|
1329
1430
|
typeof link.source !== "object" ||
|
|
1330
1431
|
typeof link.target !== "object" ||
|
|
@@ -1339,8 +1440,7 @@ function getDrawLink() {
|
|
|
1339
1440
|
return;
|
|
1340
1441
|
if (!link.source._visible && !link.target._visible)
|
|
1341
1442
|
return;
|
|
1342
|
-
const
|
|
1343
|
-
const linkOptions = this.linkOptionsCache[id];
|
|
1443
|
+
const linkOptions = this.linkOptionsCache[index];
|
|
1344
1444
|
if (!linkOptions)
|
|
1345
1445
|
return;
|
|
1346
1446
|
if (linkOptions.drawLink) {
|
|
@@ -1355,10 +1455,12 @@ function getDrawLink() {
|
|
|
1355
1455
|
let arrowSize = linkOptions.arrowSize;
|
|
1356
1456
|
let arrowBorderWidth = linkOptions.arrowBorderWidth;
|
|
1357
1457
|
let arrowBorderColor = linkOptions.arrowBorderColor;
|
|
1458
|
+
const currentNodeHighlighted = this.highlightedNode &&
|
|
1459
|
+
(this.highlightedNode.id == link.source.id || this.highlightedNode.id == link.target.id);
|
|
1358
1460
|
/** Highlight */
|
|
1359
1461
|
if (this.highlightedNeighbors && this.highlightedNode) {
|
|
1360
1462
|
/** By Node Not Highlight */
|
|
1361
|
-
if (
|
|
1463
|
+
if (!currentNodeHighlighted) {
|
|
1362
1464
|
const fadeOptions = linkFade({
|
|
1363
1465
|
arrow: this.linkSettings.arrow,
|
|
1364
1466
|
arrowByHighlight: this.linkSettings.arrowByHighlight,
|
|
@@ -1392,9 +1494,10 @@ function getDrawLink() {
|
|
|
1392
1494
|
arrowBorderColor = highlightOptions.arrowBorderColor;
|
|
1393
1495
|
}
|
|
1394
1496
|
}
|
|
1497
|
+
const currentLinkHighlighted = this.highlightedLink === link;
|
|
1395
1498
|
if (this.highlightedNeighbors && this.highlightedLink) {
|
|
1396
1499
|
/** By Link Not Highlight */
|
|
1397
|
-
if (
|
|
1500
|
+
if (!currentLinkHighlighted) {
|
|
1398
1501
|
const fadeOptions = linkFade({
|
|
1399
1502
|
arrow: this.linkSettings.arrow,
|
|
1400
1503
|
arrowByHighlight: this.linkSettings.arrowByHighlight,
|
|
@@ -1446,8 +1549,8 @@ function getDrawLink() {
|
|
|
1446
1549
|
const isHasArrow = this.linkSettings.arrow && arrowAlpha > 0;
|
|
1447
1550
|
if (!this.linkSettings.curve) {
|
|
1448
1551
|
if (this.linkSettings.prettyDraw ||
|
|
1449
|
-
this.linkSettings.
|
|
1450
|
-
(this.
|
|
1552
|
+
(this.linkSettings.arrow && arrowAlpha > 0) ||
|
|
1553
|
+
(this.particles && (currentNodeHighlighted || currentLinkHighlighted))) {
|
|
1451
1554
|
const position = calculateLinkPositionByNode(xStart, yStart, xEnd, yEnd, link.source, link.target, isHasArrow ? arrowSize : 0);
|
|
1452
1555
|
xStart = position.xStart;
|
|
1453
1556
|
xEnd = position.xEnd;
|
|
@@ -1485,6 +1588,7 @@ function getDrawLink() {
|
|
|
1485
1588
|
link._cy = position.yControl;
|
|
1486
1589
|
link._ax = position.xEndArrow;
|
|
1487
1590
|
link._ay = position.yEndArrow;
|
|
1591
|
+
linkDistance = approximateQuadraticBezierLength(position.xStart, position.yStart, position.xControl, position.yControl, position.xEnd, position.yEnd);
|
|
1488
1592
|
this.context.beginPath();
|
|
1489
1593
|
this.context.moveTo(position.xStart, position.yStart);
|
|
1490
1594
|
this.context.quadraticCurveTo(position.xControl, position.yControl, position.xEnd, position.yEnd);
|
|
@@ -1495,69 +1599,46 @@ function getDrawLink() {
|
|
|
1495
1599
|
this.context.stroke();
|
|
1496
1600
|
}
|
|
1497
1601
|
/** Particle */
|
|
1498
|
-
if (this.linkSettings.particles &&
|
|
1499
|
-
(
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
(this.highlightedLink && this.highlightedLink === link))) {
|
|
1503
|
-
const particleSteps = this.linkSettings.particleFlexSpeed
|
|
1504
|
-
? linkDistance <= 0
|
|
1505
|
-
? 0
|
|
1506
|
-
: linkDistance * this.linkSettings.particleFlexSpeedCoefficient
|
|
1507
|
-
: linkOptions.particleSteps;
|
|
1508
|
-
const particleCount = linkOptions.particleCount;
|
|
1509
|
-
if (!this.particles[id]) {
|
|
1510
|
-
const sourceId = link.source.id;
|
|
1511
|
-
const targetId = link.target.id;
|
|
1602
|
+
if (this.linkSettings.particles && (currentLinkHighlighted || currentNodeHighlighted)) {
|
|
1603
|
+
const speed = (1 / this.linkSettings.particleSpeedByDistance) * (1 + 0.5);
|
|
1604
|
+
if (!this.particles[index]) {
|
|
1605
|
+
const particleCount = Math.max(1, Math.floor(linkDistance / this.linkSettings.particleCountByDistance));
|
|
1512
1606
|
const particles = [];
|
|
1513
|
-
let prevParticle;
|
|
1514
1607
|
for (let i = 0; i < particleCount; i++) {
|
|
1515
1608
|
const particle = {
|
|
1516
|
-
step: 0,
|
|
1517
|
-
sourceId,
|
|
1518
|
-
targetId,
|
|
1519
|
-
prev: prevParticle,
|
|
1520
|
-
next: undefined,
|
|
1521
1609
|
index: i,
|
|
1522
1610
|
};
|
|
1523
|
-
if (prevParticle)
|
|
1524
|
-
prevParticle.next = particle;
|
|
1525
1611
|
particles.push(particle);
|
|
1526
|
-
prevParticle = particle;
|
|
1527
1612
|
}
|
|
1528
|
-
|
|
1529
|
-
particles[0].prev = particles[particles.length - 1];
|
|
1530
|
-
particles[particles.length - 1].next = particles[0];
|
|
1531
|
-
}
|
|
1532
|
-
this.particles[id] = particles;
|
|
1613
|
+
this.particles[index] = particles;
|
|
1533
1614
|
}
|
|
1534
|
-
|
|
1535
|
-
this.
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
if (particle.x != undefined && particle.y != undefined) {
|
|
1549
|
-
this.context.beginPath();
|
|
1550
|
-
this.context.strokeStyle = linkOptions.particleBorderColor;
|
|
1551
|
-
this.context.lineWidth = linkOptions.particleBorderWidth;
|
|
1552
|
-
this.context.arc(particle.x, particle.y, linkOptions.particleRadius, 0, Math.PI * 2);
|
|
1553
|
-
this.context.fillStyle = linkOptions.particleColor;
|
|
1554
|
-
this.context.fill();
|
|
1555
|
-
if (linkOptions.particleBorderWidth > 0) {
|
|
1556
|
-
this.context.stroke();
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1615
|
+
this.particles[index].forEach((particle, _, particles) => {
|
|
1616
|
+
if (!this.context)
|
|
1617
|
+
return;
|
|
1618
|
+
getParticlePosition({
|
|
1619
|
+
distance: linkDistance,
|
|
1620
|
+
particle,
|
|
1621
|
+
totalCount: particles.length,
|
|
1622
|
+
xEnd,
|
|
1623
|
+
xStart,
|
|
1624
|
+
yEnd,
|
|
1625
|
+
yStart,
|
|
1626
|
+
xControl,
|
|
1627
|
+
yControl,
|
|
1628
|
+
speed,
|
|
1559
1629
|
});
|
|
1560
|
-
|
|
1630
|
+
if (particle.x != undefined && particle.y != undefined) {
|
|
1631
|
+
this.context.beginPath();
|
|
1632
|
+
this.context.strokeStyle = linkOptions.particleBorderColor;
|
|
1633
|
+
this.context.lineWidth = linkOptions.particleBorderWidth;
|
|
1634
|
+
this.context.arc(particle.x, particle.y, linkOptions.particleRadius, 0, Math.PI * 2);
|
|
1635
|
+
this.context.fillStyle = linkOptions.particleColor;
|
|
1636
|
+
this.context.fill();
|
|
1637
|
+
if (linkOptions.particleBorderWidth > 0) {
|
|
1638
|
+
this.context.stroke();
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1561
1642
|
}
|
|
1562
1643
|
/** Arrow */
|
|
1563
1644
|
if (this.linkSettings.arrow && arrowAlpha > 0) {
|
|
@@ -1641,7 +1722,7 @@ function getTextLines({ context, textAlign, textColor, textFont, textStyle, text
|
|
|
1641
1722
|
}
|
|
1642
1723
|
|
|
1643
1724
|
function getDrawNode(nodeRenders, textRenders) {
|
|
1644
|
-
return function drawNode(node) {
|
|
1725
|
+
return function drawNode(node, index) {
|
|
1645
1726
|
if (!this.context || !node.x || !node.y)
|
|
1646
1727
|
return;
|
|
1647
1728
|
if (node.visible != undefined && !node.visible) {
|
|
@@ -1650,7 +1731,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1650
1731
|
node._height = 0;
|
|
1651
1732
|
return;
|
|
1652
1733
|
}
|
|
1653
|
-
const nodeOptions = this.nodeOptionsCache[
|
|
1734
|
+
const nodeOptions = this.nodeOptionsCache[index];
|
|
1654
1735
|
if (!nodeOptions)
|
|
1655
1736
|
return;
|
|
1656
1737
|
if (nodeOptions.nodeDraw && nodeOptions.textDraw) {
|
|
@@ -1809,7 +1890,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1809
1890
|
this.context.lineWidth = borderWidth;
|
|
1810
1891
|
this.context.strokeStyle = borderColor;
|
|
1811
1892
|
this.context.fillStyle = color;
|
|
1812
|
-
const labelLines = this.cachedNodeLabel[
|
|
1893
|
+
const labelLines = this.cachedNodeLabel[index];
|
|
1813
1894
|
switch (nodeOptions.shape) {
|
|
1814
1895
|
case "circle": {
|
|
1815
1896
|
if (!node.image) {
|
|
@@ -1935,7 +2016,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1935
2016
|
});
|
|
1936
2017
|
}
|
|
1937
2018
|
/** Text draw */
|
|
1938
|
-
const textLines = this.cachedNodeText[
|
|
2019
|
+
const textLines = this.cachedNodeText[index];
|
|
1939
2020
|
if (nodeOptions.textVisible && nodeOptions.text && textLines) {
|
|
1940
2021
|
textRenders.push(() => {
|
|
1941
2022
|
if (nodeOptions.textDraw) {
|
|
@@ -1995,57 +2076,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1995
2076
|
}
|
|
1996
2077
|
|
|
1997
2078
|
function initDraw() {
|
|
1998
|
-
|
|
1999
|
-
function calculateHighlight(recursive) {
|
|
2000
|
-
if (hasHighlight && !recursive)
|
|
2001
|
-
return;
|
|
2002
|
-
/** animation up */
|
|
2003
|
-
if (!this.highlightWorking && this.highlightProgress > 0) {
|
|
2004
|
-
const highlightDownStep = 1 / this.highlightSettings.highlightDownFrames;
|
|
2005
|
-
this.highlightProgress -= highlightDownStep;
|
|
2006
|
-
if (!this.simulationWorking) {
|
|
2007
|
-
hasHighlight = true;
|
|
2008
|
-
return void requestAnimationFrame(() => this.draw(true));
|
|
2009
|
-
}
|
|
2010
|
-
if (!this.linkSettings.particles) {
|
|
2011
|
-
hasHighlight = false;
|
|
2012
|
-
return;
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
/** animation down */
|
|
2016
|
-
if (this.highlightWorking && this.highlightProgress < 1) {
|
|
2017
|
-
const highlightUpStep = 1 / this.highlightSettings.highlightUpFrames;
|
|
2018
|
-
this.highlightProgress += highlightUpStep;
|
|
2019
|
-
if (!this.simulationWorking) {
|
|
2020
|
-
hasHighlight = true;
|
|
2021
|
-
return void requestAnimationFrame(() => this.draw(true));
|
|
2022
|
-
}
|
|
2023
|
-
if (!this.linkSettings.particles) {
|
|
2024
|
-
hasHighlight = false;
|
|
2025
|
-
return;
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
/** animation active */
|
|
2029
|
-
if (this.linkSettings.particles && this.highlightWorking && !this.simulationWorking) {
|
|
2030
|
-
hasHighlight = true;
|
|
2031
|
-
return void requestAnimationFrame(() => this.draw(true));
|
|
2032
|
-
}
|
|
2033
|
-
/** animation stop */
|
|
2034
|
-
if (!this.highlightWorking && this.highlightProgress <= 0) {
|
|
2035
|
-
if (this.highlightedNeighbors || this.highlightedNode || this.highlightedLink) {
|
|
2036
|
-
this.highlightedNeighbors = null;
|
|
2037
|
-
this.highlightedNode = null;
|
|
2038
|
-
this.highlightedLink = null;
|
|
2039
|
-
this.particles = {};
|
|
2040
|
-
if (!this.simulationWorking) {
|
|
2041
|
-
hasHighlight = true;
|
|
2042
|
-
return void requestAnimationFrame(() => this.draw(true));
|
|
2043
|
-
}
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
hasHighlight = false;
|
|
2047
|
-
}
|
|
2048
|
-
function draw(recursive = false) {
|
|
2079
|
+
function draw() {
|
|
2049
2080
|
if (!this.context)
|
|
2050
2081
|
return;
|
|
2051
2082
|
if (this.listeners.onDraw) {
|
|
@@ -2065,8 +2096,20 @@ function initDraw() {
|
|
|
2065
2096
|
nodeRenders.forEach((render) => render());
|
|
2066
2097
|
textRenders.forEach((render) => render());
|
|
2067
2098
|
this.context.restore();
|
|
2099
|
+
/** selection rectangle */
|
|
2100
|
+
if (this.isSelecting && this.selectionRect) {
|
|
2101
|
+
const rect = this.selectionRect;
|
|
2102
|
+
const screenX = Math.min(rect.x1, rect.x2) * this.areaTransform.k + this.areaTransform.x;
|
|
2103
|
+
const screenY = Math.min(rect.y1, rect.y2) * this.areaTransform.k + this.areaTransform.y;
|
|
2104
|
+
const screenW = Math.abs(rect.x2 - rect.x1) * this.areaTransform.k;
|
|
2105
|
+
const screenH = Math.abs(rect.y2 - rect.y1) * this.areaTransform.k;
|
|
2106
|
+
this.context.fillStyle = "rgba(66, 133, 244, 0.2)";
|
|
2107
|
+
this.context.fillRect(screenX, screenY, screenW, screenH);
|
|
2108
|
+
this.context.strokeStyle = "rgba(66, 133, 244, 0.8)";
|
|
2109
|
+
this.context.lineWidth = 2;
|
|
2110
|
+
this.context.strokeRect(screenX, screenY, screenW, screenH);
|
|
2111
|
+
}
|
|
2068
2112
|
this.listeners.onDrawFinished?.call?.(this);
|
|
2069
|
-
calculateHighlight.bind(this)(recursive);
|
|
2070
2113
|
}
|
|
2071
2114
|
if (this.graphSettings.showDrawTime) {
|
|
2072
2115
|
return setDrawTime(draw.bind(this), this.graphSettings.showDrawTimeEveryTick);
|
|
@@ -2078,7 +2121,7 @@ function initPointer() {
|
|
|
2078
2121
|
if (!this.area || !this.nodes)
|
|
2079
2122
|
throw new Error("bad init data");
|
|
2080
2123
|
function onHover(event) {
|
|
2081
|
-
if (!this.area)
|
|
2124
|
+
if (!this.area || this.isSelecting)
|
|
2082
2125
|
return;
|
|
2083
2126
|
let currentNode;
|
|
2084
2127
|
let currentLink;
|
|
@@ -2087,10 +2130,10 @@ function initPointer() {
|
|
|
2087
2130
|
let highlightNode = true;
|
|
2088
2131
|
let highlightLink = true;
|
|
2089
2132
|
if (checkHighlightNode) {
|
|
2133
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2090
2134
|
currentNode = nodeByPointerGetter({
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
mouseEvent: event,
|
|
2135
|
+
pointerX,
|
|
2136
|
+
pointerY,
|
|
2094
2137
|
nodes: this.nodes,
|
|
2095
2138
|
});
|
|
2096
2139
|
if (currentNode?.highlight != undefined)
|
|
@@ -2100,11 +2143,11 @@ function initPointer() {
|
|
|
2100
2143
|
this.area.style.cursor = "pointer";
|
|
2101
2144
|
}
|
|
2102
2145
|
else if (checkHighlightLink) {
|
|
2146
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2103
2147
|
currentLink = linkByPointerGetter({
|
|
2148
|
+
pointerX,
|
|
2149
|
+
pointerY,
|
|
2104
2150
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
2105
|
-
areaRect: this.areaRect,
|
|
2106
|
-
areaTransform: this.areaTransform,
|
|
2107
|
-
mouseEvent: event,
|
|
2108
2151
|
links: this.links,
|
|
2109
2152
|
curve: this.linkSettings.curve,
|
|
2110
2153
|
});
|
|
@@ -2120,53 +2163,23 @@ function initPointer() {
|
|
|
2120
2163
|
else {
|
|
2121
2164
|
this.area.style.cursor = "default";
|
|
2122
2165
|
}
|
|
2123
|
-
|
|
2124
|
-
this.highlightedNode = currentNode;
|
|
2125
|
-
this.highlightedLink = null;
|
|
2126
|
-
this.highlightedNeighbors = new Set(this.highlightedNode?.neighbors ?? []);
|
|
2127
|
-
this.highlightWorking = true;
|
|
2128
|
-
if (!this.simulationWorking)
|
|
2129
|
-
requestAnimationFrame(() => {
|
|
2130
|
-
this.draw();
|
|
2131
|
-
});
|
|
2132
|
-
}
|
|
2133
|
-
else if (currentLink &&
|
|
2134
|
-
highlightLink &&
|
|
2135
|
-
checkType(currentLink.source, lodash.isObject(currentLink.source)) &&
|
|
2136
|
-
checkType(currentLink.target, lodash.isObject(currentLink.target)) &&
|
|
2137
|
-
this.highlightedLink !== currentLink) {
|
|
2138
|
-
this.highlightProgress = 0;
|
|
2139
|
-
this.highlightedLink = currentLink;
|
|
2140
|
-
this.highlightedNode = null;
|
|
2141
|
-
this.highlightedNeighbors = new Set([currentLink.source.id, currentLink.target.id]);
|
|
2142
|
-
this.highlightWorking = true;
|
|
2143
|
-
if (!this.simulationWorking)
|
|
2144
|
-
requestAnimationFrame(() => {
|
|
2145
|
-
this.draw();
|
|
2146
|
-
});
|
|
2147
|
-
}
|
|
2148
|
-
else if (!currentNode && !currentLink && (this.highlightedNode || this.highlightedLink)) {
|
|
2149
|
-
this.highlightWorking = false;
|
|
2150
|
-
if (!this.simulationWorking)
|
|
2151
|
-
requestAnimationFrame(() => {
|
|
2152
|
-
this.draw();
|
|
2153
|
-
});
|
|
2154
|
-
}
|
|
2166
|
+
this.animateHighlight(highlightNode ? currentNode : undefined, highlightLink ? currentLink : undefined);
|
|
2155
2167
|
if (!this.listeners.onMove)
|
|
2156
2168
|
return;
|
|
2157
|
-
if (!currentNode && !checkHighlightNode)
|
|
2169
|
+
if (!currentNode && !checkHighlightNode) {
|
|
2170
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2158
2171
|
currentNode = nodeByPointerGetter({
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
mouseEvent: event,
|
|
2172
|
+
pointerX,
|
|
2173
|
+
pointerY,
|
|
2162
2174
|
nodes: this.nodes,
|
|
2163
2175
|
});
|
|
2176
|
+
}
|
|
2164
2177
|
if (!currentNode && (!checkHighlightNode || (!checkHighlightLink && !currentLink))) {
|
|
2178
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2165
2179
|
currentLink = linkByPointerGetter({
|
|
2180
|
+
pointerX,
|
|
2181
|
+
pointerY,
|
|
2166
2182
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
2167
|
-
areaRect: this.areaRect,
|
|
2168
|
-
areaTransform: this.areaTransform,
|
|
2169
|
-
mouseEvent: event,
|
|
2170
2183
|
links: this.links,
|
|
2171
2184
|
curve: this.linkSettings.curve,
|
|
2172
2185
|
});
|
|
@@ -2180,18 +2193,18 @@ function initPointer() {
|
|
|
2180
2193
|
!("button" in event) ||
|
|
2181
2194
|
event.button !== 1)
|
|
2182
2195
|
return;
|
|
2196
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2183
2197
|
const currentNode = nodeByPointerGetter({
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
mouseEvent: event,
|
|
2198
|
+
pointerX,
|
|
2199
|
+
pointerY,
|
|
2187
2200
|
nodes: this.nodes,
|
|
2188
2201
|
});
|
|
2189
2202
|
if (!currentNode) {
|
|
2203
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2190
2204
|
const currentLink = linkByPointerGetter({
|
|
2205
|
+
pointerX,
|
|
2206
|
+
pointerY,
|
|
2191
2207
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
2192
|
-
areaRect: this.areaRect,
|
|
2193
|
-
areaTransform: this.areaTransform,
|
|
2194
|
-
mouseEvent: event,
|
|
2195
2208
|
links: this.links,
|
|
2196
2209
|
curve: this.linkSettings.curve,
|
|
2197
2210
|
});
|
|
@@ -2202,18 +2215,18 @@ function initPointer() {
|
|
|
2202
2215
|
function onRightClick(event) {
|
|
2203
2216
|
if (!this.listeners.onContextMenu)
|
|
2204
2217
|
return;
|
|
2218
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2205
2219
|
const currentNode = nodeByPointerGetter({
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
mouseEvent: event,
|
|
2220
|
+
pointerX,
|
|
2221
|
+
pointerY,
|
|
2209
2222
|
nodes: this.nodes,
|
|
2210
2223
|
});
|
|
2211
2224
|
if (!currentNode) {
|
|
2225
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2212
2226
|
const currentLink = linkByPointerGetter({
|
|
2227
|
+
pointerX,
|
|
2228
|
+
pointerY,
|
|
2213
2229
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
2214
|
-
areaRect: this.areaRect,
|
|
2215
|
-
areaTransform: this.areaTransform,
|
|
2216
|
-
mouseEvent: event,
|
|
2217
2230
|
links: this.links,
|
|
2218
2231
|
curve: this.linkSettings.curve,
|
|
2219
2232
|
});
|
|
@@ -2224,18 +2237,18 @@ function initPointer() {
|
|
|
2224
2237
|
function onDoubleClick(event) {
|
|
2225
2238
|
if (!this.listeners.onDoubleClick)
|
|
2226
2239
|
return;
|
|
2240
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2227
2241
|
const currentNode = nodeByPointerGetter({
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
mouseEvent: event,
|
|
2242
|
+
pointerX,
|
|
2243
|
+
pointerY,
|
|
2231
2244
|
nodes: this.nodes,
|
|
2232
2245
|
});
|
|
2233
2246
|
if (!currentNode) {
|
|
2247
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2234
2248
|
const currentLink = linkByPointerGetter({
|
|
2249
|
+
pointerX,
|
|
2250
|
+
pointerY,
|
|
2235
2251
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
2236
|
-
areaRect: this.areaRect,
|
|
2237
|
-
areaTransform: this.areaTransform,
|
|
2238
|
-
mouseEvent: event,
|
|
2239
2252
|
links: this.links,
|
|
2240
2253
|
curve: this.linkSettings.curve,
|
|
2241
2254
|
});
|
|
@@ -2246,18 +2259,18 @@ function initPointer() {
|
|
|
2246
2259
|
function onClick(event) {
|
|
2247
2260
|
if (this.isDragging || !this.listeners.onClick || ("button" in event && event.button !== 0))
|
|
2248
2261
|
return;
|
|
2262
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2249
2263
|
const currentNode = nodeByPointerGetter({
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
mouseEvent: event,
|
|
2264
|
+
pointerX,
|
|
2265
|
+
pointerY,
|
|
2253
2266
|
nodes: this.nodes,
|
|
2254
2267
|
});
|
|
2255
2268
|
if (!currentNode) {
|
|
2269
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2256
2270
|
const currentLink = linkByPointerGetter({
|
|
2271
|
+
pointerX,
|
|
2272
|
+
pointerY,
|
|
2257
2273
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
2258
|
-
areaRect: this.areaRect,
|
|
2259
|
-
areaTransform: this.areaTransform,
|
|
2260
|
-
mouseEvent: event,
|
|
2261
2274
|
links: this.links,
|
|
2262
2275
|
curve: this.linkSettings.curve,
|
|
2263
2276
|
});
|
|
@@ -2315,138 +2328,210 @@ function initResize() {
|
|
|
2315
2328
|
signal: abortController.signal,
|
|
2316
2329
|
});
|
|
2317
2330
|
document.addEventListener("visibilitychange", () => {
|
|
2318
|
-
if (
|
|
2319
|
-
this.
|
|
2331
|
+
if (document.hidden) {
|
|
2332
|
+
this.highlightController?.abort();
|
|
2333
|
+
this.highlightedNode = null;
|
|
2334
|
+
this.highlightedLink = null;
|
|
2335
|
+
this.highlightedNeighbors = null;
|
|
2336
|
+
this.highlightProgress = 0;
|
|
2337
|
+
this.highlightStart = null;
|
|
2338
|
+
this.highlightPositive = false;
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
this.updateSize();
|
|
2342
|
+
requestAnimationFrame(() => {
|
|
2343
|
+
this.updateSize();
|
|
2344
|
+
});
|
|
2320
2345
|
}, { signal: abortController.signal });
|
|
2321
2346
|
observer.observe(this.area);
|
|
2322
2347
|
}
|
|
2323
2348
|
|
|
2324
|
-
function
|
|
2325
|
-
if (!this.
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2349
|
+
function initSelection() {
|
|
2350
|
+
if (!this.area)
|
|
2351
|
+
throw new Error("bad init data");
|
|
2352
|
+
this.isSelecting = false;
|
|
2353
|
+
this.selectionRect = null;
|
|
2354
|
+
let selectedNodesSet = new Set();
|
|
2355
|
+
let selectedLinksSet = new Set();
|
|
2356
|
+
let localSelection = false;
|
|
2357
|
+
function onMouseDown(event) {
|
|
2358
|
+
if (event.button !== 0 || !event.shiftKey)
|
|
2359
|
+
return;
|
|
2360
|
+
if (!this.area)
|
|
2361
|
+
return;
|
|
2362
|
+
event.stopPropagation();
|
|
2363
|
+
event.preventDefault();
|
|
2364
|
+
const [startX, startY] = this.getPointerAreaPosition(event);
|
|
2365
|
+
this.isSelecting = true;
|
|
2366
|
+
localSelection = true;
|
|
2367
|
+
this.selectionRect = { x1: startX, y1: startY, x2: startX, y2: startY };
|
|
2368
|
+
selectedNodesSet = new Set();
|
|
2369
|
+
selectedLinksSet = new Set();
|
|
2370
|
+
const controller = new AbortController();
|
|
2371
|
+
this.area.addEventListener("mousemove", onMouseMove.bind(this), {
|
|
2372
|
+
signal: controller.signal,
|
|
2340
2373
|
});
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
}
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
this.simulation.force("x", null);
|
|
2352
|
-
}
|
|
2353
|
-
if ((!this.forceSettings.forces || !this.forceSettings.yForce) && this.simulation.force("y")) {
|
|
2354
|
-
this.simulation.force("y", null);
|
|
2355
|
-
}
|
|
2356
|
-
if ((!this.forceSettings.forces || !this.forceSettings.chargeForce) &&
|
|
2357
|
-
this.simulation.force("charge")) {
|
|
2358
|
-
this.simulation.force("charge", null);
|
|
2359
|
-
}
|
|
2360
|
-
if ((!this.forceSettings.forces || !this.forceSettings.centerForce) &&
|
|
2361
|
-
this.simulation.force("center")) {
|
|
2362
|
-
this.simulation.force("center", null);
|
|
2363
|
-
}
|
|
2364
|
-
if (!this.forceSettings.forces || !this.forceSettings.linkForce) {
|
|
2365
|
-
linkForce.distance(0).strength(0).iterations(0);
|
|
2366
|
-
}
|
|
2367
|
-
if (this.forceSettings.forces && this.forceSettings.linkForce) {
|
|
2368
|
-
linkForce
|
|
2369
|
-
.distance(this.forceSettings.linkDistance)
|
|
2370
|
-
.strength(this.forceSettings.linkStrength)
|
|
2371
|
-
.iterations(this.forceSettings.linkIterations);
|
|
2374
|
+
this.area.addEventListener("mouseup", () => {
|
|
2375
|
+
onMouseUp.call(this, controller);
|
|
2376
|
+
}, {
|
|
2377
|
+
signal: controller.signal,
|
|
2378
|
+
});
|
|
2379
|
+
window.addEventListener("keyup", (event) => {
|
|
2380
|
+
if (event.key === "Shift") {
|
|
2381
|
+
onMouseUp.call(this, controller);
|
|
2382
|
+
}
|
|
2383
|
+
}, { signal: controller.signal });
|
|
2372
2384
|
}
|
|
2373
|
-
|
|
2374
|
-
|
|
2385
|
+
function onMouseMove(event) {
|
|
2386
|
+
if (!this.isSelecting || !this.selectionRect)
|
|
2387
|
+
return;
|
|
2388
|
+
event.stopPropagation();
|
|
2389
|
+
const [x, y] = this.getPointerAreaPosition(event);
|
|
2390
|
+
this.selectionRect.x2 = x;
|
|
2391
|
+
this.selectionRect.y2 = y;
|
|
2392
|
+
const rect = normalizeRect(this.selectionRect);
|
|
2393
|
+
const newNodes = new Set();
|
|
2394
|
+
for (let i = 0; i < this.nodes.length; i++) {
|
|
2395
|
+
const node = this.nodes[i];
|
|
2396
|
+
if (node.x == undefined || node.y == undefined)
|
|
2397
|
+
continue;
|
|
2398
|
+
if (node.visible === false)
|
|
2399
|
+
continue;
|
|
2400
|
+
const cache = this.nodeOptionsCache[i];
|
|
2401
|
+
if (isNodeInRect(node, rect, cache)) {
|
|
2402
|
+
newNodes.add(node);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
const newLinks = new Set();
|
|
2406
|
+
for (const link of this.links) {
|
|
2407
|
+
const source = link.source;
|
|
2408
|
+
const target = link.target;
|
|
2409
|
+
if (newNodes.has(source) && newNodes.has(target)) {
|
|
2410
|
+
newLinks.add(link);
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
for (const node of selectedNodesSet) {
|
|
2414
|
+
if (!newNodes.has(node)) {
|
|
2415
|
+
node._selected = false;
|
|
2416
|
+
this.listeners.onSelectionOut?.call(this, node, undefined);
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
for (const link of selectedLinksSet) {
|
|
2420
|
+
if (!newLinks.has(link)) {
|
|
2421
|
+
link._selected = false;
|
|
2422
|
+
this.listeners.onSelectionOut?.call(this, undefined, link);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
for (const node of newNodes) {
|
|
2426
|
+
if (!selectedNodesSet.has(node)) {
|
|
2427
|
+
node._selected = true;
|
|
2428
|
+
this.listeners.onSelectionIn?.call(this, node, undefined);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
for (const link of newLinks) {
|
|
2432
|
+
if (!selectedLinksSet.has(link)) {
|
|
2433
|
+
link._selected = true;
|
|
2434
|
+
this.listeners.onSelectionIn?.call(this, undefined, link);
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
selectedNodesSet = newNodes;
|
|
2438
|
+
selectedLinksSet = newLinks;
|
|
2439
|
+
this.draw();
|
|
2375
2440
|
}
|
|
2376
|
-
|
|
2377
|
-
|
|
2441
|
+
function onMouseUp(controller) {
|
|
2442
|
+
controller.abort();
|
|
2443
|
+
if (!this.isSelecting)
|
|
2444
|
+
return;
|
|
2445
|
+
const nodes = Array.from(selectedNodesSet);
|
|
2446
|
+
const links = Array.from(selectedLinksSet);
|
|
2447
|
+
for (const node of nodes) {
|
|
2448
|
+
node._selected = false;
|
|
2449
|
+
}
|
|
2450
|
+
for (const link of links) {
|
|
2451
|
+
link._selected = false;
|
|
2452
|
+
}
|
|
2453
|
+
this.isSelecting = false;
|
|
2454
|
+
this.selectionRect = null;
|
|
2455
|
+
selectedNodesSet = new Set();
|
|
2456
|
+
selectedLinksSet = new Set();
|
|
2457
|
+
this.listeners.onSelectionEnd?.call(this, nodes, links);
|
|
2458
|
+
this.tick();
|
|
2378
2459
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
.
|
|
2382
|
-
|
|
2383
|
-
|
|
2460
|
+
function onSelectionEnd(event) {
|
|
2461
|
+
if (localSelection) {
|
|
2462
|
+
event.stopImmediatePropagation();
|
|
2463
|
+
localSelection = false;
|
|
2464
|
+
}
|
|
2384
2465
|
}
|
|
2385
|
-
|
|
2386
|
-
|
|
2466
|
+
this.area.addEventListener("mousedown", onMouseDown.bind(this), {
|
|
2467
|
+
signal: this.eventAbortController.signal,
|
|
2468
|
+
});
|
|
2469
|
+
this.area.addEventListener("click", onSelectionEnd, { signal: this.eventAbortController.signal });
|
|
2470
|
+
}
|
|
2471
|
+
function normalizeRect(rect) {
|
|
2472
|
+
const x = Math.min(rect.x1, rect.x2);
|
|
2473
|
+
const y = Math.min(rect.y1, rect.y2);
|
|
2474
|
+
const width = Math.abs(rect.x2 - rect.x1);
|
|
2475
|
+
const height = Math.abs(rect.y2 - rect.y1);
|
|
2476
|
+
return { x, y, width, height };
|
|
2477
|
+
}
|
|
2478
|
+
function isNodeInRect(node, rect, cache) {
|
|
2479
|
+
const nx = node.x;
|
|
2480
|
+
const ny = node.y;
|
|
2481
|
+
if (nx == undefined || ny == undefined)
|
|
2482
|
+
return false;
|
|
2483
|
+
if (cache?.shape === "circle") {
|
|
2484
|
+
const r = cache.radius ?? 0;
|
|
2485
|
+
const closestX = Math.max(rect.x, Math.min(nx, rect.x + rect.width));
|
|
2486
|
+
const closestY = Math.max(rect.y, Math.min(ny, rect.y + rect.height));
|
|
2487
|
+
const dx = nx - closestX;
|
|
2488
|
+
const dy = ny - closestY;
|
|
2489
|
+
return dx * dx + dy * dy <= r * r;
|
|
2387
2490
|
}
|
|
2388
|
-
|
|
2491
|
+
const hw = (cache?.width ?? 0) / 2;
|
|
2492
|
+
const hh = (cache?.height ?? 0) / 2;
|
|
2493
|
+
return (nx - hw >= rect.x &&
|
|
2494
|
+
nx + hw <= rect.x + rect.width &&
|
|
2495
|
+
ny - hh >= rect.y &&
|
|
2496
|
+
ny + hh <= rect.y + rect.height);
|
|
2389
2497
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2498
|
+
|
|
2499
|
+
function updateLinkCache() {
|
|
2500
|
+
if (!this.context)
|
|
2392
2501
|
return;
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
this.
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
const
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
this.simulation.force("collide", null);
|
|
2502
|
+
const current = this.areaTransform.k;
|
|
2503
|
+
if (this.linkSettings.smartCache &&
|
|
2504
|
+
this._lastLinkZoomK !== undefined &&
|
|
2505
|
+
this.linkOptionsCache.length > 0) {
|
|
2506
|
+
const corner = this.linkSettings.linkScaleSwitch;
|
|
2507
|
+
const prev = this._lastLinkZoomK;
|
|
2508
|
+
this._lastLinkZoomK = current;
|
|
2509
|
+
if (!((prev <= corner && current >= corner) || (prev >= corner && current <= corner))) {
|
|
2510
|
+
return;
|
|
2403
2511
|
}
|
|
2404
|
-
|
|
2405
|
-
this.
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
|
|
2414
|
-
}
|
|
2415
|
-
return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
|
|
2416
|
-
}
|
|
2417
|
-
case "square": {
|
|
2418
|
-
return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
|
|
2419
|
-
this.forceSettings.collideAdditionalRadius);
|
|
2420
|
-
}
|
|
2421
|
-
case "text": {
|
|
2422
|
-
return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
|
|
2423
|
-
this.forceSettings.collideAdditionalRadius);
|
|
2424
|
-
}
|
|
2425
|
-
default: {
|
|
2426
|
-
if (this.forceSettings.collideRadius) {
|
|
2427
|
-
return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
|
|
2428
|
-
}
|
|
2429
|
-
return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
|
|
2430
|
-
}
|
|
2431
|
-
}
|
|
2432
|
-
})
|
|
2433
|
-
.strength(this.forceSettings.collideStrength)
|
|
2434
|
-
.iterations(this.forceSettings.collideIterations));
|
|
2512
|
+
for (let i = 0; i < this.links.length; i++) {
|
|
2513
|
+
const link = this.links[i];
|
|
2514
|
+
const linkOptions = linkIterationExtractor(link, i, this.links, this, this.linkSettings.options ?? {}, linkOptionsGetter);
|
|
2515
|
+
const cache = this.linkOptionsCache[i];
|
|
2516
|
+
if (cache) {
|
|
2517
|
+
cache.color = linkOptions.color;
|
|
2518
|
+
cache.arrowColor = linkOptions.arrowColor;
|
|
2519
|
+
cache.width = linkOptions.width;
|
|
2520
|
+
}
|
|
2435
2521
|
}
|
|
2522
|
+
return;
|
|
2436
2523
|
}
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
function updateLinkCache() {
|
|
2440
|
-
this.linkOptionsCache = {};
|
|
2524
|
+
this._lastLinkZoomK = current;
|
|
2525
|
+
this.linkOptionsCache.length = 0;
|
|
2441
2526
|
const groupMap = {};
|
|
2442
2527
|
const groupSelfMap = {};
|
|
2443
2528
|
for (let i = 0; i < this.links.length; i++) {
|
|
2444
2529
|
const link = this.links[i];
|
|
2445
2530
|
const { sourceId, targetId } = extractLinkPointIds(link);
|
|
2446
2531
|
const linkOptions = linkIterationExtractor(link, i, this.links, this, this.linkSettings.options ?? {}, linkOptionsGetter);
|
|
2447
|
-
|
|
2448
|
-
this.linkOptionsCache[id] = linkOptions;
|
|
2532
|
+
this.linkOptionsCache[i] = linkOptions;
|
|
2449
2533
|
if (this.linkSettings.curve) {
|
|
2534
|
+
const id = `${targetId}${sourceId}`;
|
|
2450
2535
|
if (sourceId === targetId) {
|
|
2451
2536
|
groupSelfMap[id] ??= 0;
|
|
2452
2537
|
link._groupIndex = ++groupSelfMap[id];
|
|
@@ -2477,13 +2562,34 @@ function indexToPosition(n) {
|
|
|
2477
2562
|
// return n & 1 ? -(n >> 1) : n >> 1;
|
|
2478
2563
|
}
|
|
2479
2564
|
|
|
2480
|
-
function updateNodeCache(
|
|
2481
|
-
this.nodeOptionsCache = {};
|
|
2565
|
+
function updateNodeCache() {
|
|
2482
2566
|
if (!this.context)
|
|
2483
2567
|
return;
|
|
2568
|
+
const currentZoom = this.areaTransform.k;
|
|
2569
|
+
const options = this.nodeOptionsCache.length === 0;
|
|
2570
|
+
const label = this.cachedNodeLabel.length === 0;
|
|
2571
|
+
let text = this.cachedNodeText.length === 0;
|
|
2572
|
+
if (this.nodeSettings.smartCache &&
|
|
2573
|
+
this._lastNodeZoomK !== undefined &&
|
|
2574
|
+
!options &&
|
|
2575
|
+
!text &&
|
|
2576
|
+
!label) {
|
|
2577
|
+
const thresholds = this.nodeSettings.textScaleSteps;
|
|
2578
|
+
if (thresholds && thresholds.length > 0) {
|
|
2579
|
+
const min = Math.min(this._lastNodeZoomK, currentZoom);
|
|
2580
|
+
const max = Math.max(this._lastNodeZoomK, currentZoom);
|
|
2581
|
+
const crossed = thresholds.some((t) => t >= min && t <= max);
|
|
2582
|
+
this._lastNodeZoomK = currentZoom;
|
|
2583
|
+
if (!crossed)
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
this.cachedNodeText.length = 0;
|
|
2587
|
+
text = true;
|
|
2588
|
+
}
|
|
2589
|
+
this._lastNodeZoomK = currentZoom;
|
|
2484
2590
|
for (let i = 0; i < this.nodes.length; i++) {
|
|
2485
2591
|
const node = this.nodes[i];
|
|
2486
|
-
if (
|
|
2592
|
+
if (options) {
|
|
2487
2593
|
const nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
|
|
2488
2594
|
const radius = nodeOptions.shape === "circle"
|
|
2489
2595
|
? nodeRadiusGetter({
|
|
@@ -2534,11 +2640,19 @@ function updateNodeCache(types) {
|
|
|
2534
2640
|
nodeOptions.height = node.visible === false ? 0 : height;
|
|
2535
2641
|
nodeOptions.radius = node.visible === false ? 0 : radius;
|
|
2536
2642
|
nodeOptions.labelSize = labelSize;
|
|
2537
|
-
this.nodeOptionsCache[
|
|
2643
|
+
this.nodeOptionsCache[i] = nodeOptions;
|
|
2538
2644
|
}
|
|
2539
|
-
|
|
2645
|
+
let nodeOptions = this.nodeOptionsCache[i];
|
|
2646
|
+
if (!nodeOptions)
|
|
2647
|
+
continue;
|
|
2540
2648
|
/** label */
|
|
2541
|
-
if (
|
|
2649
|
+
if (label) {
|
|
2650
|
+
if (!options) {
|
|
2651
|
+
nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
|
|
2652
|
+
const cache = this.nodeOptionsCache[i];
|
|
2653
|
+
if (cache)
|
|
2654
|
+
cache.label = nodeOptions.label;
|
|
2655
|
+
}
|
|
2542
2656
|
/** label in not text shape */
|
|
2543
2657
|
if (nodeOptions.shape !== "text" && nodeOptions.label) {
|
|
2544
2658
|
this.context.font = `${nodeOptions.labelStyle} normal ${nodeOptions.labelWeight} ${nodeOptions.labelSize}px ${nodeOptions.labelFont}`;
|
|
@@ -2546,7 +2660,7 @@ function updateNodeCache(types) {
|
|
|
2546
2660
|
this.context.textAlign = nodeOptions.labelAlign;
|
|
2547
2661
|
if (nodeOptions.labelWidth == undefined ||
|
|
2548
2662
|
this.context.measureText(nodeOptions.label).width <= nodeOptions.labelWidth) {
|
|
2549
|
-
this.cachedNodeLabel[
|
|
2663
|
+
this.cachedNodeLabel[i] = [nodeOptions.label];
|
|
2550
2664
|
}
|
|
2551
2665
|
const { lines } = getTextLines({
|
|
2552
2666
|
context: this.context,
|
|
@@ -2559,7 +2673,7 @@ function updateNodeCache(types) {
|
|
|
2559
2673
|
textStyle: nodeOptions.labelStyle,
|
|
2560
2674
|
textWeight: nodeOptions.labelWeight,
|
|
2561
2675
|
});
|
|
2562
|
-
this.cachedNodeLabel[
|
|
2676
|
+
this.cachedNodeLabel[i] = lines;
|
|
2563
2677
|
/** label in text shape */
|
|
2564
2678
|
}
|
|
2565
2679
|
else if (nodeOptions.shape === "text" && nodeOptions.label) {
|
|
@@ -2583,18 +2697,29 @@ function updateNodeCache(types) {
|
|
|
2583
2697
|
(lines.length - 1) * nodeOptions.labelGap +
|
|
2584
2698
|
nodeOptions.labelYPadding;
|
|
2585
2699
|
nodeOptions.width = maxSize + nodeOptions.labelXPadding;
|
|
2586
|
-
this.cachedNodeLabel[
|
|
2700
|
+
this.cachedNodeLabel[i] = lines;
|
|
2701
|
+
}
|
|
2702
|
+
else {
|
|
2703
|
+
this.cachedNodeLabel[i] = [];
|
|
2587
2704
|
}
|
|
2588
2705
|
}
|
|
2589
2706
|
/** text */
|
|
2590
|
-
if (
|
|
2707
|
+
if (text) {
|
|
2708
|
+
if (!options) {
|
|
2709
|
+
nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
|
|
2710
|
+
const cache = this.nodeOptionsCache[i];
|
|
2711
|
+
if (cache) {
|
|
2712
|
+
cache.text = nodeOptions.text;
|
|
2713
|
+
cache.textVisible = nodeOptions.textVisible;
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
if (!nodeOptions.text) {
|
|
2717
|
+
this.cachedNodeText[i] = [];
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2591
2720
|
this.context.font = `${nodeOptions.textStyle} normal ${nodeOptions.textWeight} ${nodeOptions.textSize}px ${nodeOptions.textFont}`;
|
|
2592
2721
|
this.context.fillStyle = nodeOptions.textColor;
|
|
2593
2722
|
this.context.textAlign = nodeOptions.textAlign;
|
|
2594
|
-
if (nodeOptions.textWidth == undefined ||
|
|
2595
|
-
this.context.measureText(nodeOptions.text).width <= nodeOptions.textWidth) {
|
|
2596
|
-
this.cachedNodeText[node.id] = [nodeOptions.text];
|
|
2597
|
-
}
|
|
2598
2723
|
const { lines } = getTextLines({
|
|
2599
2724
|
context: this.context,
|
|
2600
2725
|
maxWidth: nodeOptions.textWidth,
|
|
@@ -2606,49 +2731,233 @@ function updateNodeCache(types) {
|
|
|
2606
2731
|
textStyle: nodeOptions.textStyle,
|
|
2607
2732
|
textWeight: nodeOptions.textWeight,
|
|
2608
2733
|
});
|
|
2609
|
-
this.cachedNodeText[
|
|
2734
|
+
this.cachedNodeText[i] = lines;
|
|
2610
2735
|
}
|
|
2611
2736
|
}
|
|
2612
2737
|
}
|
|
2613
2738
|
|
|
2739
|
+
const DEFAULT_ZOOM_EXTENT = [0.3, 10];
|
|
2740
|
+
const DEFAULT_TRANSLATE_EXTENT_COEFFICIENT = 8;
|
|
2741
|
+
const DEFAULT_MARGIN = 0.25;
|
|
2614
2742
|
function initZoom(currentZoom) {
|
|
2615
2743
|
if (!this.area)
|
|
2616
2744
|
throw new Error("bad init data");
|
|
2745
|
+
const bounds = computeGraphBounds(this.nodes);
|
|
2746
|
+
const zoomExtent = resolveZoomExtent(this.graphSettings.zoomExtent, this.width, this.height, this.graphSettings.zoomExtentMargin ?? DEFAULT_MARGIN, bounds);
|
|
2747
|
+
const translateExtent = resolveTranslateExtent(this.graphSettings.translateExtent, this.graphSettings.translateExtentCoefficient, this.graphSettings.translateExtentOverlap, this.width, this.height, zoomExtent, bounds);
|
|
2748
|
+
this._translateExtent = translateExtent;
|
|
2617
2749
|
const zoomInstance = d3Zoom.zoom()
|
|
2618
|
-
.scaleExtent(
|
|
2750
|
+
.scaleExtent(zoomExtent)
|
|
2751
|
+
.filter((event) => {
|
|
2752
|
+
return !(event instanceof MouseEvent && event.shiftKey);
|
|
2753
|
+
})
|
|
2619
2754
|
.on("zoom", (event) => {
|
|
2755
|
+
if (this._zoomAnimating)
|
|
2756
|
+
return;
|
|
2620
2757
|
this.listeners.onZoom?.call?.(this, event);
|
|
2621
2758
|
const oldTransform = this.areaTransform;
|
|
2622
2759
|
this.areaTransform = event.transform;
|
|
2623
2760
|
if (this.areaTransform.k !== oldTransform.k) {
|
|
2624
2761
|
updateLinkCache.call(this);
|
|
2625
|
-
updateNodeCache.call(this
|
|
2762
|
+
updateNodeCache.call(this);
|
|
2626
2763
|
}
|
|
2627
|
-
|
|
2628
|
-
requestAnimationFrame(() => this.draw());
|
|
2764
|
+
this.tick();
|
|
2629
2765
|
});
|
|
2630
|
-
|
|
2631
|
-
const coefficient = this.graphSettings.translateExtentCoefficient;
|
|
2632
|
-
const [coefficientX, coefficientY] = jsHelpers.isArray(coefficient)
|
|
2633
|
-
? coefficient
|
|
2634
|
-
: [coefficient, coefficient];
|
|
2635
|
-
const [[minX = -this.width * coefficientX, minY = -this.height * coefficientY], [maxX = this.width * coefficientX, maxY = this.height * coefficientY],] = this.graphSettings.translateExtent;
|
|
2636
|
-
zoomInstance.translateExtent([
|
|
2637
|
-
[minX, minY],
|
|
2638
|
-
[maxX, maxY],
|
|
2639
|
-
]);
|
|
2640
|
-
}
|
|
2766
|
+
zoomInstance.translateExtent(translateExtent);
|
|
2641
2767
|
d3Selection.select(this.area).call(zoomInstance).on("dblclick.zoom", null);
|
|
2642
2768
|
const zoomInitial = currentZoom ?? this.graphSettings.zoomInitial;
|
|
2643
|
-
this.areaTransform = new d3Zoom.ZoomTransform(zoomInitial?.k ?? 1, zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
|
|
2769
|
+
this.areaTransform = new d3Zoom.ZoomTransform(Math.max(zoomInitial?.k ?? 1, zoomExtent[0]), zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
|
|
2644
2770
|
d3Zoom.zoom().transform(d3Selection.select(this.area), this.areaTransform);
|
|
2645
2771
|
}
|
|
2772
|
+
function resolveZoomExtent(userExtent, viewWidth, viewHeight, margin, bounds) {
|
|
2773
|
+
if (userExtent &&
|
|
2774
|
+
userExtent.length === 2 &&
|
|
2775
|
+
userExtent[0] != undefined &&
|
|
2776
|
+
userExtent[1] != undefined) {
|
|
2777
|
+
return userExtent;
|
|
2778
|
+
}
|
|
2779
|
+
if (!bounds)
|
|
2780
|
+
return DEFAULT_ZOOM_EXTENT;
|
|
2781
|
+
const graphWidth = bounds.maxX - bounds.minX;
|
|
2782
|
+
const graphHeight = bounds.maxY - bounds.minY;
|
|
2783
|
+
if (graphWidth === 0 || graphHeight === 0)
|
|
2784
|
+
return DEFAULT_ZOOM_EXTENT;
|
|
2785
|
+
const scaleToFit = (1 - margin) / Math.max(graphWidth / viewWidth, graphHeight / viewHeight);
|
|
2786
|
+
return [scaleToFit, DEFAULT_ZOOM_EXTENT[1]];
|
|
2787
|
+
}
|
|
2788
|
+
function resolveTranslateExtent(userExtent, userCoefficient, overlap, viewWidth, viewHeight, zoomExtent, bounds) {
|
|
2789
|
+
if (userExtent && userExtent.length >= 2 && jsHelpers.isArray(userExtent[0]) && jsHelpers.isArray(userExtent[1])) {
|
|
2790
|
+
return [
|
|
2791
|
+
[
|
|
2792
|
+
userExtent[0][0] ?? -viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2793
|
+
userExtent[0][1] ?? -viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2794
|
+
],
|
|
2795
|
+
[
|
|
2796
|
+
userExtent[1][0] ?? viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2797
|
+
userExtent[1][1] ?? viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2798
|
+
],
|
|
2799
|
+
];
|
|
2800
|
+
}
|
|
2801
|
+
if (userCoefficient) {
|
|
2802
|
+
const [cx, cy] = jsHelpers.isArray(userCoefficient)
|
|
2803
|
+
? userCoefficient
|
|
2804
|
+
: [userCoefficient, userCoefficient];
|
|
2805
|
+
return [
|
|
2806
|
+
[-viewWidth * cx, -viewHeight * cy],
|
|
2807
|
+
[viewWidth * cx, viewHeight * cy],
|
|
2808
|
+
];
|
|
2809
|
+
}
|
|
2810
|
+
if (!bounds) {
|
|
2811
|
+
return [
|
|
2812
|
+
[
|
|
2813
|
+
-viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2814
|
+
-viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2815
|
+
],
|
|
2816
|
+
[
|
|
2817
|
+
viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2818
|
+
viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2819
|
+
],
|
|
2820
|
+
];
|
|
2821
|
+
}
|
|
2822
|
+
const minZoom = zoomExtent[0];
|
|
2823
|
+
const zoomViewWidth = viewWidth / minZoom;
|
|
2824
|
+
const zoomViewHeight = viewHeight / minZoom;
|
|
2825
|
+
const graphWidth = bounds.maxX - bounds.minX;
|
|
2826
|
+
const graphHeight = bounds.maxY - bounds.minY;
|
|
2827
|
+
const minX = bounds.minX + overlap * graphWidth - zoomViewWidth;
|
|
2828
|
+
const maxX = bounds.maxX - overlap * graphWidth + zoomViewWidth;
|
|
2829
|
+
const minY = bounds.minY + overlap * graphHeight - zoomViewHeight;
|
|
2830
|
+
const maxY = bounds.maxY - overlap * graphHeight + zoomViewHeight;
|
|
2831
|
+
return [
|
|
2832
|
+
[minX, minY],
|
|
2833
|
+
[maxX, maxY],
|
|
2834
|
+
];
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
function initSimulation() {
|
|
2838
|
+
if (!this.simulation) {
|
|
2839
|
+
this.simulation = d3Force.forceSimulation()
|
|
2840
|
+
.nodes(this.nodes)
|
|
2841
|
+
.force("link", d3Force.forceLink(this.links).id(this.nodeSettings.idGetter.bind(this)))
|
|
2842
|
+
.on("tick", () => {
|
|
2843
|
+
this.draw();
|
|
2844
|
+
})
|
|
2845
|
+
.on("end", () => {
|
|
2846
|
+
this.listeners.onSimulationEnd?.call?.(this);
|
|
2847
|
+
if (this.area) {
|
|
2848
|
+
initZoom.call(this, this.areaTransform);
|
|
2849
|
+
}
|
|
2850
|
+
if (this.graphSettings.showDrawTime) {
|
|
2851
|
+
getDrawTime();
|
|
2852
|
+
// eslint-disable-next-line no-console
|
|
2853
|
+
console.log(`nodes: ${this.nodes.length} | links: ${this.links.length}`);
|
|
2854
|
+
resetDrawTime();
|
|
2855
|
+
}
|
|
2856
|
+
})
|
|
2857
|
+
.stop();
|
|
2858
|
+
initSimulationForces.call(this);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
function initSimulationForces() {
|
|
2862
|
+
if (!this.simulation)
|
|
2863
|
+
return;
|
|
2864
|
+
const linkForce = this.simulation.force("link");
|
|
2865
|
+
if (!linkForce)
|
|
2866
|
+
return;
|
|
2867
|
+
if ((!this.forceSettings.forces || !this.forceSettings.xForce) && this.simulation.force("x")) {
|
|
2868
|
+
this.simulation.force("x", null);
|
|
2869
|
+
}
|
|
2870
|
+
if ((!this.forceSettings.forces || !this.forceSettings.yForce) && this.simulation.force("y")) {
|
|
2871
|
+
this.simulation.force("y", null);
|
|
2872
|
+
}
|
|
2873
|
+
if ((!this.forceSettings.forces || !this.forceSettings.chargeForce) &&
|
|
2874
|
+
this.simulation.force("charge")) {
|
|
2875
|
+
this.simulation.force("charge", null);
|
|
2876
|
+
}
|
|
2877
|
+
if ((!this.forceSettings.forces || !this.forceSettings.centerForce) &&
|
|
2878
|
+
this.simulation.force("center")) {
|
|
2879
|
+
this.simulation.force("center", null);
|
|
2880
|
+
}
|
|
2881
|
+
if (!this.forceSettings.forces || !this.forceSettings.linkForce) {
|
|
2882
|
+
linkForce.distance(0).strength(0).iterations(0);
|
|
2883
|
+
}
|
|
2884
|
+
if (this.forceSettings.forces && this.forceSettings.linkForce) {
|
|
2885
|
+
linkForce
|
|
2886
|
+
.distance(this.forceSettings.linkDistance)
|
|
2887
|
+
.strength(this.forceSettings.linkStrength)
|
|
2888
|
+
.iterations(this.forceSettings.linkIterations);
|
|
2889
|
+
}
|
|
2890
|
+
if (this.forceSettings.forces && this.forceSettings.xForce) {
|
|
2891
|
+
this.simulation.force("x", d3Force.forceX(this.forceSettings.xPosition).strength(this.forceSettings.xStrength));
|
|
2892
|
+
}
|
|
2893
|
+
if (this.forceSettings.forces && this.forceSettings.yForce) {
|
|
2894
|
+
this.simulation.force("y", d3Force.forceY(this.forceSettings.yPosition).strength(this.forceSettings.yStrength));
|
|
2895
|
+
}
|
|
2896
|
+
if (this.forceSettings.forces && this.forceSettings.chargeForce) {
|
|
2897
|
+
this.simulation.force("charge", d3Force.forceManyBody()
|
|
2898
|
+
.strength(this.forceSettings.chargeStrength)
|
|
2899
|
+
.distanceMax(Infinity)
|
|
2900
|
+
.distanceMin(this.forceSettings.chargeDistanceMin));
|
|
2901
|
+
}
|
|
2902
|
+
if (this.forceSettings.forces && this.forceSettings.centerForce) {
|
|
2903
|
+
this.simulation.force("center", d3Force.forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
|
|
2904
|
+
}
|
|
2905
|
+
initCollideForce.call(this, true);
|
|
2906
|
+
}
|
|
2907
|
+
function initCollideForce(forceUpdate) {
|
|
2908
|
+
if (!this.simulation)
|
|
2909
|
+
return;
|
|
2910
|
+
if ((!this.forceSettings.collideForce || !this.forceSettings.forces) &&
|
|
2911
|
+
this.simulation.force("collide")) {
|
|
2912
|
+
this.simulation.force("collide", null);
|
|
2913
|
+
}
|
|
2914
|
+
if (this.forceSettings.forces && this.forceSettings.collideForce) {
|
|
2915
|
+
const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
|
|
2916
|
+
const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
|
|
2917
|
+
const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
|
|
2918
|
+
if (isMaxCollideNodes && isMaxCollideLinks) {
|
|
2919
|
+
this.simulation.force("collide", null);
|
|
2920
|
+
}
|
|
2921
|
+
else if (!this.simulation.force("collide") || forceUpdate) {
|
|
2922
|
+
this.simulation.force("collide", d3Force.forceCollide()
|
|
2923
|
+
.radius((node, index) => {
|
|
2924
|
+
const nodeOptions = this.nodeOptionsCache[index];
|
|
2925
|
+
if (!nodeOptions)
|
|
2926
|
+
return 0;
|
|
2927
|
+
switch (nodeOptions.shape) {
|
|
2928
|
+
case "circle": {
|
|
2929
|
+
if (this.forceSettings.collideRadius) {
|
|
2930
|
+
return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
|
|
2931
|
+
}
|
|
2932
|
+
return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
|
|
2933
|
+
}
|
|
2934
|
+
case "square": {
|
|
2935
|
+
return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
|
|
2936
|
+
this.forceSettings.collideAdditionalRadius);
|
|
2937
|
+
}
|
|
2938
|
+
case "text": {
|
|
2939
|
+
return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
|
|
2940
|
+
this.forceSettings.collideAdditionalRadius);
|
|
2941
|
+
}
|
|
2942
|
+
default: {
|
|
2943
|
+
if (this.forceSettings.collideRadius) {
|
|
2944
|
+
return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
|
|
2945
|
+
}
|
|
2946
|
+
return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
})
|
|
2950
|
+
.strength(this.forceSettings.collideStrength)
|
|
2951
|
+
.iterations(this.forceSettings.collideIterations));
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2646
2955
|
|
|
2647
2956
|
class GraphCanvas {
|
|
2648
2957
|
/** initial data */
|
|
2649
2958
|
nodes;
|
|
2650
2959
|
links;
|
|
2651
|
-
particles =
|
|
2960
|
+
particles = [];
|
|
2652
2961
|
width;
|
|
2653
2962
|
height;
|
|
2654
2963
|
root;
|
|
@@ -2665,19 +2974,30 @@ class GraphCanvas {
|
|
|
2665
2974
|
context;
|
|
2666
2975
|
simulation;
|
|
2667
2976
|
areaTransform = d3Zoom.zoomIdentity;
|
|
2668
|
-
|
|
2977
|
+
_translateExtent;
|
|
2669
2978
|
draw;
|
|
2670
2979
|
eventAbortController;
|
|
2671
|
-
cachedNodeText =
|
|
2672
|
-
cachedNodeLabel =
|
|
2673
|
-
linkOptionsCache =
|
|
2674
|
-
nodeOptionsCache =
|
|
2980
|
+
cachedNodeText = [];
|
|
2981
|
+
cachedNodeLabel = [];
|
|
2982
|
+
linkOptionsCache = [];
|
|
2983
|
+
nodeOptionsCache = [];
|
|
2675
2984
|
isDragging = false;
|
|
2676
2985
|
highlightedNode = null;
|
|
2677
2986
|
highlightedLink = null;
|
|
2678
2987
|
highlightedNeighbors = null;
|
|
2679
2988
|
highlightProgress = 1;
|
|
2680
|
-
|
|
2989
|
+
highlightStart = null;
|
|
2990
|
+
highlightPositive = false;
|
|
2991
|
+
highlightController;
|
|
2992
|
+
_lastNodeZoomK;
|
|
2993
|
+
_lastLinkZoomK;
|
|
2994
|
+
_zoomAnimating = false;
|
|
2995
|
+
isSelecting = false;
|
|
2996
|
+
selectionRect = null;
|
|
2997
|
+
areaRect;
|
|
2998
|
+
// protected get areaRect() {
|
|
2999
|
+
// return this.area?.getBoundingClientRect();
|
|
3000
|
+
// }
|
|
2681
3001
|
get simulationWorking() {
|
|
2682
3002
|
const simulationAlpha = this.simulation?.alpha?.() ?? 0;
|
|
2683
3003
|
const simulationAlphaMin = this.simulation?.alphaMin?.() ?? 0;
|
|
@@ -2706,21 +3026,72 @@ class GraphCanvas {
|
|
|
2706
3026
|
get dpi() {
|
|
2707
3027
|
return devicePixelRatio;
|
|
2708
3028
|
}
|
|
2709
|
-
getData() {
|
|
3029
|
+
getData = () => {
|
|
2710
3030
|
return {
|
|
2711
3031
|
links: this.links,
|
|
2712
3032
|
nodes: this.nodes,
|
|
2713
3033
|
};
|
|
2714
|
-
}
|
|
2715
|
-
|
|
3034
|
+
};
|
|
3035
|
+
fitToView = (margin = this.graphSettings.zoomToFitMargin, duration = this.graphSettings.zoomAnimationDuration) => {
|
|
3036
|
+
const area = this.area;
|
|
3037
|
+
if (!area)
|
|
3038
|
+
return;
|
|
3039
|
+
const bounds = computeGraphBounds(this.nodes);
|
|
3040
|
+
if (!bounds)
|
|
3041
|
+
return;
|
|
3042
|
+
const graphWidth = bounds.maxX - bounds.minX;
|
|
3043
|
+
const graphHeight = bounds.maxY - bounds.minY;
|
|
3044
|
+
if (graphWidth === 0 || graphHeight === 0)
|
|
3045
|
+
return;
|
|
3046
|
+
const graphCenterX = bounds.minX + graphWidth / 2;
|
|
3047
|
+
const graphCenterY = bounds.minY + graphHeight / 2;
|
|
3048
|
+
const scale = (1 - margin) / Math.max(graphWidth / this.width, graphHeight / this.height);
|
|
3049
|
+
const clampedScale = Math.min(scale, this.graphSettings.zoomExtent?.[1] ?? scale);
|
|
3050
|
+
const target = d3Zoom.zoomIdentity
|
|
3051
|
+
.translate(this.width / 2, this.height / 2)
|
|
3052
|
+
.scale(clampedScale)
|
|
3053
|
+
.translate(-graphCenterX, -graphCenterY);
|
|
3054
|
+
if (!this.graphSettings.zoomAnimation) {
|
|
3055
|
+
this.areaTransform = target;
|
|
3056
|
+
d3Zoom.zoom().transform(d3Selection.select(area), target);
|
|
3057
|
+
this.clearCache(true);
|
|
3058
|
+
this.tick();
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
this.animateZoom(area, target, this.areaTransform, duration);
|
|
3062
|
+
};
|
|
3063
|
+
focusOnNode = (nodeId, scale = this.graphSettings.zoomToNodeScale, duration = this.graphSettings.zoomAnimationDuration) => {
|
|
3064
|
+
const area = this.area;
|
|
3065
|
+
if (!area)
|
|
3066
|
+
return;
|
|
3067
|
+
const node = this.nodes.find((n) => n.id === nodeId);
|
|
3068
|
+
if (!node)
|
|
3069
|
+
return;
|
|
3070
|
+
if (node.x == undefined || node.y == undefined)
|
|
3071
|
+
return;
|
|
3072
|
+
const target = d3Zoom.zoomIdentity
|
|
3073
|
+
.translate(this.width / 2, this.height / 2)
|
|
3074
|
+
.scale(scale)
|
|
3075
|
+
.translate(-node.x, -node.y);
|
|
3076
|
+
if (!this.graphSettings.zoomAnimation) {
|
|
3077
|
+
this.areaTransform = target;
|
|
3078
|
+
d3Zoom.zoom().transform(d3Selection.select(area), target);
|
|
3079
|
+
this.clearCache(true);
|
|
3080
|
+
this.tick();
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
this.animateZoom(area, target, this.areaTransform, duration);
|
|
3084
|
+
};
|
|
3085
|
+
changeData = (options, alpha = 0.5, clearCache = true, precompute = false) => {
|
|
2716
3086
|
if (options.links != undefined)
|
|
2717
3087
|
this.links = options.links;
|
|
2718
3088
|
if (options.nodes != undefined)
|
|
2719
3089
|
this.nodes = options.nodes;
|
|
2720
|
-
if (options.nodes != undefined || options.links != undefined)
|
|
2721
|
-
this.updateData(alpha, clearCache);
|
|
2722
|
-
|
|
2723
|
-
|
|
3090
|
+
if (options.nodes != undefined || options.links != undefined) {
|
|
3091
|
+
this.updateData(alpha, clearCache, precompute);
|
|
3092
|
+
}
|
|
3093
|
+
};
|
|
3094
|
+
changeSettings = (options, clearCache = true, precompute = false) => {
|
|
2724
3095
|
if (options.graphSettings) {
|
|
2725
3096
|
this.graphSettings = graphSettingsGetter(options.graphSettings, this.graphSettings);
|
|
2726
3097
|
this.draw = initDraw.call(this);
|
|
@@ -2767,16 +3138,16 @@ class GraphCanvas {
|
|
|
2767
3138
|
initCollideForce.call(this, true);
|
|
2768
3139
|
}
|
|
2769
3140
|
if (options.forceSettings) {
|
|
2770
|
-
return void this.updateSimulation();
|
|
3141
|
+
return void this.updateSimulation(precompute);
|
|
2771
3142
|
}
|
|
2772
3143
|
this.tick();
|
|
2773
|
-
}
|
|
2774
|
-
updateRect() {
|
|
3144
|
+
};
|
|
3145
|
+
updateRect = () => {
|
|
2775
3146
|
if (!this.area)
|
|
2776
3147
|
return;
|
|
2777
3148
|
this.areaRect = this.area.getBoundingClientRect();
|
|
2778
|
-
}
|
|
2779
|
-
updateSize() {
|
|
3149
|
+
};
|
|
3150
|
+
updateSize = () => {
|
|
2780
3151
|
if (!this.area)
|
|
2781
3152
|
return;
|
|
2782
3153
|
const { width, height } = this.root.getBoundingClientRect();
|
|
@@ -2784,57 +3155,102 @@ class GraphCanvas {
|
|
|
2784
3155
|
this.height = height;
|
|
2785
3156
|
this.area.width = this.dpi * this.width;
|
|
2786
3157
|
this.area.height = this.dpi * this.height;
|
|
2787
|
-
this.
|
|
3158
|
+
this.updateRect();
|
|
2788
3159
|
this.context = this.area.getContext("2d");
|
|
2789
3160
|
if (!this.context)
|
|
2790
3161
|
throw new Error("couldn't create canvas context");
|
|
2791
3162
|
this.context.scale(this.dpi, this.dpi);
|
|
2792
3163
|
this.draw();
|
|
2793
|
-
}
|
|
2794
|
-
clearCache(keys) {
|
|
3164
|
+
};
|
|
3165
|
+
clearCache = (keys) => {
|
|
2795
3166
|
if (keys === true) {
|
|
2796
|
-
|
|
2797
|
-
|
|
3167
|
+
this.nodeOptionsCache.length = 0;
|
|
3168
|
+
this.linkOptionsCache.length = 0;
|
|
3169
|
+
this.cachedNodeLabel.length = 0;
|
|
3170
|
+
this.cachedNodeText.length = 0;
|
|
2798
3171
|
}
|
|
2799
3172
|
else if (jsHelpers.isArray(keys)) {
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
3173
|
+
for (const key of keys) {
|
|
3174
|
+
switch (key) {
|
|
3175
|
+
case GRAPH_CACHE_TYPE.NodeText: {
|
|
3176
|
+
this.cachedNodeText.length = 0;
|
|
3177
|
+
break;
|
|
3178
|
+
}
|
|
3179
|
+
case GRAPH_CACHE_TYPE.NodeLabel: {
|
|
3180
|
+
this.cachedNodeLabel.length = 0;
|
|
3181
|
+
break;
|
|
3182
|
+
}
|
|
3183
|
+
case GRAPH_CACHE_TYPE.NodeOptions: {
|
|
3184
|
+
this.nodeOptionsCache.length = 0;
|
|
3185
|
+
break;
|
|
3186
|
+
}
|
|
3187
|
+
case GRAPH_CACHE_TYPE.LinkOptions: {
|
|
3188
|
+
this.linkOptionsCache.length = 0;
|
|
3189
|
+
break;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
2811
3192
|
}
|
|
2812
3193
|
}
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
3194
|
+
updateNodeCache.call(this);
|
|
3195
|
+
updateLinkCache.call(this);
|
|
3196
|
+
};
|
|
3197
|
+
tick = () => {
|
|
3198
|
+
if (!this.simulationWorking)
|
|
2816
3199
|
this.draw();
|
|
2817
|
-
}
|
|
2818
|
-
restart(alpha) {
|
|
2819
|
-
if (this.simulation)
|
|
3200
|
+
};
|
|
3201
|
+
restart = (alpha, options) => {
|
|
3202
|
+
if (!this.simulation)
|
|
3203
|
+
return;
|
|
3204
|
+
const settings = {
|
|
3205
|
+
precompute: options?.precompute ?? this.forceSettings.precompute,
|
|
3206
|
+
precomputeMaxTimeMs: options?.precomputeMaxTimeMs ?? this.forceSettings?.precomputeMaxTimeMs,
|
|
3207
|
+
precomputeMaxTicks: options?.precomputeMaxTicks ?? this.forceSettings?.precomputeMaxTicks ?? 300,
|
|
3208
|
+
precomputeDisableForcesAfter: options?.precomputeDisableForcesAfter ??
|
|
3209
|
+
this.forceSettings?.precomputeDisableForcesAfter ??
|
|
3210
|
+
false,
|
|
3211
|
+
};
|
|
3212
|
+
if (!settings.precompute) {
|
|
2820
3213
|
this.simulation.alpha(alpha ?? 1).restart();
|
|
2821
|
-
|
|
2822
|
-
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
if (settings.precomputeDisableForcesAfter) {
|
|
3217
|
+
this.forceSettings = forceSettingsGetter({ forces: true }, this.forceSettings);
|
|
3218
|
+
}
|
|
3219
|
+
initSimulationForces.call(this);
|
|
3220
|
+
this.simulation.stop();
|
|
3221
|
+
this.simulation.alpha(alpha ?? 1);
|
|
3222
|
+
const startTime = performance.now();
|
|
3223
|
+
let ticks = 0;
|
|
3224
|
+
while (performance.now() - startTime < settings.precomputeMaxTimeMs &&
|
|
3225
|
+
ticks < settings.precomputeMaxTicks) {
|
|
3226
|
+
this.simulation.tick(1);
|
|
3227
|
+
ticks++;
|
|
3228
|
+
if (this.simulation.alpha() <= this.simulation.alphaMin())
|
|
3229
|
+
break;
|
|
3230
|
+
}
|
|
3231
|
+
if (settings.precomputeDisableForcesAfter) {
|
|
3232
|
+
this.forceSettings = forceSettingsGetter({ forces: false }, this.forceSettings);
|
|
3233
|
+
}
|
|
3234
|
+
initSimulationForces.call(this);
|
|
3235
|
+
this.simulation.restart();
|
|
3236
|
+
this.tick();
|
|
3237
|
+
};
|
|
3238
|
+
start = () => {
|
|
2823
3239
|
if (this.simulation)
|
|
2824
3240
|
this.simulation.alpha(1).restart();
|
|
2825
3241
|
if (this.container)
|
|
2826
3242
|
this.container.style.display = "block";
|
|
2827
|
-
}
|
|
2828
|
-
stop() {
|
|
3243
|
+
};
|
|
3244
|
+
stop = () => {
|
|
2829
3245
|
if (this.simulation)
|
|
2830
3246
|
this.simulation.stop();
|
|
2831
3247
|
if (this.container)
|
|
2832
3248
|
this.container.style.display = "none";
|
|
2833
|
-
}
|
|
2834
|
-
create() {
|
|
3249
|
+
};
|
|
3250
|
+
create = () => {
|
|
2835
3251
|
this.init();
|
|
2836
|
-
}
|
|
2837
|
-
destroy() {
|
|
3252
|
+
};
|
|
3253
|
+
destroy = () => {
|
|
2838
3254
|
if (this.simulation) {
|
|
2839
3255
|
this.simulation.stop();
|
|
2840
3256
|
this.simulation = undefined;
|
|
@@ -2842,39 +3258,79 @@ class GraphCanvas {
|
|
|
2842
3258
|
this.clearHTMLElements();
|
|
2843
3259
|
this.clearState();
|
|
2844
3260
|
this.clearCache(true);
|
|
2845
|
-
}
|
|
2846
|
-
|
|
3261
|
+
};
|
|
3262
|
+
getPointerAreaPosition = (event) => {
|
|
3263
|
+
let localX;
|
|
3264
|
+
let localY;
|
|
3265
|
+
if ("offsetX" in event) {
|
|
3266
|
+
// localX = event.offsetX;
|
|
3267
|
+
// localY = event.offsetY;
|
|
3268
|
+
const rect = this.areaRect;
|
|
3269
|
+
if (!rect)
|
|
3270
|
+
return [0, 0];
|
|
3271
|
+
localX = event.clientX - rect.left;
|
|
3272
|
+
localY = event.clientY - rect.top;
|
|
3273
|
+
}
|
|
3274
|
+
else {
|
|
3275
|
+
const rect = this.areaRect;
|
|
3276
|
+
if (!rect)
|
|
3277
|
+
return [0, 0];
|
|
3278
|
+
const clientX = event.touches[0]?.clientX ?? event.changedTouches[0]?.clientX;
|
|
3279
|
+
const clientY = event.touches[0]?.clientY ?? event.changedTouches[0]?.clientY;
|
|
3280
|
+
localX = clientX - rect.left;
|
|
3281
|
+
localY = clientY - rect.top;
|
|
3282
|
+
}
|
|
3283
|
+
const px = (localX - this.areaTransform.x) / this.areaTransform.k;
|
|
3284
|
+
const py = (localY - this.areaTransform.y) / this.areaTransform.k;
|
|
3285
|
+
return [px, py];
|
|
3286
|
+
};
|
|
3287
|
+
clearHTMLElements = () => {
|
|
2847
3288
|
this.root.replaceChildren();
|
|
2848
3289
|
this.area = undefined;
|
|
2849
3290
|
this.context = undefined;
|
|
2850
3291
|
this.container = undefined;
|
|
2851
3292
|
this.eventAbortController.abort();
|
|
2852
3293
|
this.eventAbortController = new AbortController();
|
|
2853
|
-
}
|
|
2854
|
-
updateSimulation() {
|
|
3294
|
+
};
|
|
3295
|
+
updateSimulation = (precompute = false) => {
|
|
2855
3296
|
if (this.simulation) {
|
|
2856
3297
|
initSimulationForces.call(this);
|
|
2857
|
-
this.
|
|
2858
|
-
this.simulation.restart();
|
|
3298
|
+
this.restart(1, { precompute });
|
|
2859
3299
|
}
|
|
2860
|
-
}
|
|
2861
|
-
clearState() {
|
|
3300
|
+
};
|
|
3301
|
+
clearState = () => {
|
|
2862
3302
|
this.isDragging = false;
|
|
2863
3303
|
this.highlightedNode = null;
|
|
2864
3304
|
this.highlightedLink = null;
|
|
2865
3305
|
this.highlightedNeighbors = null;
|
|
2866
3306
|
this.highlightProgress = 0;
|
|
2867
|
-
this.
|
|
2868
|
-
|
|
2869
|
-
|
|
3307
|
+
this.highlightStart = null;
|
|
3308
|
+
this.highlightPositive = false;
|
|
3309
|
+
this.isSelecting = false;
|
|
3310
|
+
this.selectionRect = null;
|
|
3311
|
+
};
|
|
3312
|
+
init = () => {
|
|
3313
|
+
initArea.call(this);
|
|
3314
|
+
updateNodeCache.call(this);
|
|
3315
|
+
updateLinkCache.call(this);
|
|
3316
|
+
initSimulation.call(this);
|
|
3317
|
+
this.restart(1);
|
|
3318
|
+
initDnd.call(this);
|
|
3319
|
+
initZoom.call(this);
|
|
3320
|
+
initResize.call(this);
|
|
3321
|
+
initSelection.call(this);
|
|
3322
|
+
initPointer.call(this);
|
|
3323
|
+
updateNodeCache.call(this);
|
|
3324
|
+
updateLinkCache.call(this);
|
|
3325
|
+
this.tick();
|
|
3326
|
+
};
|
|
3327
|
+
updateData = (alpha = 0.5, clearCache = true, precompute = false) => {
|
|
2870
3328
|
if (clearCache) {
|
|
2871
3329
|
this.clearCache(clearCache);
|
|
2872
3330
|
}
|
|
2873
3331
|
if (this.simulation) {
|
|
2874
3332
|
initCollideForce.call(this, false);
|
|
2875
|
-
this.simulation
|
|
2876
|
-
.nodes(this.nodes)
|
|
2877
|
-
.force("link", d3Force.forceLink(this.links)
|
|
3333
|
+
this.simulation.nodes(this.nodes).force("link", d3Force.forceLink(this.links)
|
|
2878
3334
|
.id(this.nodeSettings.idGetter.bind(this))
|
|
2879
3335
|
.distance(this.forceSettings.forces && this.forceSettings.linkForce
|
|
2880
3336
|
? this.forceSettings.linkDistance
|
|
@@ -2884,21 +3340,95 @@ class GraphCanvas {
|
|
|
2884
3340
|
: 0)
|
|
2885
3341
|
.iterations(this.forceSettings.forces && this.forceSettings.linkForce
|
|
2886
3342
|
? this.forceSettings.linkIterations
|
|
2887
|
-
: 0))
|
|
2888
|
-
|
|
2889
|
-
|
|
3343
|
+
: 0));
|
|
3344
|
+
this.restart(alpha, { precompute });
|
|
3345
|
+
initZoom.call(this, this.areaTransform);
|
|
2890
3346
|
}
|
|
2891
|
-
}
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
3347
|
+
};
|
|
3348
|
+
animateHighlight = (node, link, baseDuration = this.highlightSettings.highlightDuration) => {
|
|
3349
|
+
let positive = true;
|
|
3350
|
+
if (node && (this.highlightedNode !== node || !this.highlightPositive)) {
|
|
3351
|
+
this.highlightedNode = node;
|
|
3352
|
+
this.highlightedNeighbors = new Set(node.neighbors);
|
|
3353
|
+
this.particles = [];
|
|
3354
|
+
this.highlightedLink = null;
|
|
3355
|
+
this.highlightStart = performance.now();
|
|
3356
|
+
}
|
|
3357
|
+
else if (link && (this.highlightedLink !== link || !this.highlightPositive)) {
|
|
3358
|
+
const { sourceId, targetId } = extractLinkPointIds(link);
|
|
3359
|
+
this.highlightProgress = 0;
|
|
3360
|
+
this.highlightedLink = link;
|
|
3361
|
+
this.highlightedNeighbors = new Set([sourceId, targetId]);
|
|
3362
|
+
this.particles = [];
|
|
3363
|
+
this.highlightedNode = null;
|
|
3364
|
+
this.highlightStart = performance.now();
|
|
3365
|
+
}
|
|
3366
|
+
else if (!node && !link && this.highlightPositive) {
|
|
3367
|
+
positive = false;
|
|
3368
|
+
}
|
|
3369
|
+
else {
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
if (this.highlightController) {
|
|
3373
|
+
this.highlightController.abort();
|
|
3374
|
+
}
|
|
3375
|
+
const controller = new AbortController();
|
|
3376
|
+
this.highlightPositive = positive;
|
|
3377
|
+
this.highlightController = controller;
|
|
3378
|
+
const startTime = performance.now();
|
|
3379
|
+
const startProgress = this.highlightProgress;
|
|
3380
|
+
const targetProgress = positive ? 1 : 0;
|
|
3381
|
+
const delta = targetProgress - startProgress;
|
|
3382
|
+
const duration = baseDuration * Math.abs(delta);
|
|
3383
|
+
const animate = () => {
|
|
3384
|
+
if (controller.signal.aborted)
|
|
3385
|
+
return;
|
|
3386
|
+
const elapsed = performance.now() - startTime;
|
|
3387
|
+
const t = duration === 0 ? 1 : Math.min(elapsed / duration, 1);
|
|
3388
|
+
const current = startProgress + delta * t;
|
|
3389
|
+
const eased = current < 0.5 ? 4 * current * current * current : 1 - (-2 * current + 2) ** 3 / 2;
|
|
3390
|
+
this.highlightProgress = eased;
|
|
3391
|
+
if (t < 1 || positive) {
|
|
3392
|
+
requestAnimationFrame(animate);
|
|
3393
|
+
this.draw();
|
|
3394
|
+
}
|
|
3395
|
+
else {
|
|
3396
|
+
this.highlightedNode = null;
|
|
3397
|
+
this.highlightedLink = null;
|
|
3398
|
+
this.highlightedNeighbors = null;
|
|
3399
|
+
this.highlightStart = null;
|
|
3400
|
+
this.particles = [];
|
|
3401
|
+
this.tick();
|
|
3402
|
+
}
|
|
3403
|
+
};
|
|
3404
|
+
requestAnimationFrame(animate);
|
|
3405
|
+
};
|
|
3406
|
+
animateZoom = (area, target, start, duration) => {
|
|
3407
|
+
this._zoomAnimating = true;
|
|
3408
|
+
const startTime = performance.now();
|
|
3409
|
+
const animate = () => {
|
|
3410
|
+
const elapsed = performance.now() - startTime;
|
|
3411
|
+
const t = Math.min(elapsed / duration, 1);
|
|
3412
|
+
const eased = t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
|
|
3413
|
+
const x = start.x + (target.x - start.x) * eased;
|
|
3414
|
+
const y = start.y + (target.y - start.y) * eased;
|
|
3415
|
+
const k = start.k + (target.k - start.k) * eased;
|
|
3416
|
+
this.areaTransform = new d3Zoom.ZoomTransform(k, x, y);
|
|
3417
|
+
d3Zoom.zoom().transform(d3Selection.select(area), this.areaTransform);
|
|
3418
|
+
updateLinkCache.call(this);
|
|
3419
|
+
updateNodeCache.call(this);
|
|
3420
|
+
this.tick();
|
|
3421
|
+
if (t < 1) {
|
|
3422
|
+
requestAnimationFrame(animate);
|
|
3423
|
+
}
|
|
3424
|
+
else {
|
|
3425
|
+
this._zoomAnimating = false;
|
|
3426
|
+
this.clearCache(true);
|
|
3427
|
+
this.tick();
|
|
3428
|
+
}
|
|
3429
|
+
};
|
|
3430
|
+
requestAnimationFrame(animate);
|
|
3431
|
+
};
|
|
2902
3432
|
}
|
|
2903
3433
|
|
|
2904
3434
|
const FORCE_CONTROLS = [
|
|
@@ -3059,22 +3589,13 @@ const HIGHLIGHT_COMMON_CONTROLS = [
|
|
|
3059
3589
|
label: "Расширение границы связи для курсора",
|
|
3060
3590
|
},
|
|
3061
3591
|
{
|
|
3062
|
-
id: "
|
|
3063
|
-
initialValue: HIGHLIGHT_SETTINGS.
|
|
3064
|
-
max:
|
|
3065
|
-
min:
|
|
3066
|
-
step: 1,
|
|
3067
|
-
type: "range",
|
|
3068
|
-
label: "Скорость отмены анимации в кадрах",
|
|
3069
|
-
},
|
|
3070
|
-
{
|
|
3071
|
-
id: "highlightUpFrames",
|
|
3072
|
-
initialValue: HIGHLIGHT_SETTINGS.highlightUpFrames,
|
|
3073
|
-
max: 60,
|
|
3074
|
-
min: 1,
|
|
3592
|
+
id: "highlightDuration",
|
|
3593
|
+
initialValue: HIGHLIGHT_SETTINGS.highlightDuration,
|
|
3594
|
+
max: 1000,
|
|
3595
|
+
min: 10,
|
|
3075
3596
|
step: 1,
|
|
3076
3597
|
type: "range",
|
|
3077
|
-
label: "Скорость
|
|
3598
|
+
label: "Скорость анимации",
|
|
3078
3599
|
},
|
|
3079
3600
|
];
|
|
3080
3601
|
const HIGHLIGHT_BY_NODE_FOR_NODE_CONTROLS = [
|
|
@@ -3547,19 +4068,22 @@ const LINK_SETTINGS_CONTROLS = [
|
|
|
3547
4068
|
initialValue: LINK_SETTINGS.particles,
|
|
3548
4069
|
},
|
|
3549
4070
|
{
|
|
3550
|
-
id: "
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
4071
|
+
id: "particleSpeedByDistance",
|
|
4072
|
+
initialValue: LINK_SETTINGS.particleSpeedByDistance,
|
|
4073
|
+
max: 500,
|
|
4074
|
+
min: 1,
|
|
4075
|
+
step: 1,
|
|
4076
|
+
type: "range",
|
|
4077
|
+
label: "Скорость частицы на один пиксель",
|
|
3554
4078
|
},
|
|
3555
4079
|
{
|
|
3556
|
-
id: "
|
|
3557
|
-
initialValue: LINK_SETTINGS.
|
|
3558
|
-
max:
|
|
3559
|
-
min:
|
|
3560
|
-
step:
|
|
4080
|
+
id: "particleCountByDistance",
|
|
4081
|
+
initialValue: LINK_SETTINGS.particleCountByDistance,
|
|
4082
|
+
max: 250,
|
|
4083
|
+
min: 1,
|
|
4084
|
+
step: 1,
|
|
3561
4085
|
type: "range",
|
|
3562
|
-
label: "
|
|
4086
|
+
label: "Количество пикселей для появления частицы",
|
|
3563
4087
|
},
|
|
3564
4088
|
];
|
|
3565
4089
|
const LINK_OPTIONS_LINK_CONTROLS = [
|
|
@@ -3654,24 +4178,6 @@ const LINK_OPTIONS_PARTICLE_CONTROLS = [
|
|
|
3654
4178
|
label: "Радиус",
|
|
3655
4179
|
initialValue: LINK_OPTIONS.particleRadius,
|
|
3656
4180
|
},
|
|
3657
|
-
{
|
|
3658
|
-
id: "particleCount",
|
|
3659
|
-
type: "range",
|
|
3660
|
-
max: 20,
|
|
3661
|
-
min: 1,
|
|
3662
|
-
step: 1,
|
|
3663
|
-
label: "Количество",
|
|
3664
|
-
initialValue: LINK_OPTIONS.particleCount,
|
|
3665
|
-
},
|
|
3666
|
-
{
|
|
3667
|
-
id: "particleSteps",
|
|
3668
|
-
type: "range",
|
|
3669
|
-
max: 200,
|
|
3670
|
-
min: 1,
|
|
3671
|
-
step: 1,
|
|
3672
|
-
label: "Количество кадров",
|
|
3673
|
-
initialValue: LINK_OPTIONS.particleSteps,
|
|
3674
|
-
},
|
|
3675
4181
|
{
|
|
3676
4182
|
id: "particleBorderColor",
|
|
3677
4183
|
type: "color",
|
|
@@ -4105,10 +4611,19 @@ exports.NODE_OPTIONS_NODE_CONTROLS = NODE_OPTIONS_NODE_CONTROLS;
|
|
|
4105
4611
|
exports.NODE_OPTIONS_TEXT_CONTROLS = NODE_OPTIONS_TEXT_CONTROLS;
|
|
4106
4612
|
exports.NODE_SETTINGS = NODE_SETTINGS;
|
|
4107
4613
|
exports.NODE_SETTINGS_CONTROLS = NODE_SETTINGS_CONTROLS;
|
|
4614
|
+
exports.PERFOMANCE_NODE_OPTIONS = PERFOMANCE_NODE_OPTIONS;
|
|
4615
|
+
exports.PERFORMANCE_FORCE_SETTINGS = PERFORMANCE_FORCE_SETTINGS;
|
|
4616
|
+
exports.PERFORMANCE_GRAPH_SETTINGS = PERFORMANCE_GRAPH_SETTINGS;
|
|
4617
|
+
exports.PERFORMANCE_HIGHLIGHT_SETTINGS = PERFORMANCE_HIGHLIGHT_SETTINGS;
|
|
4618
|
+
exports.PERFORMANCE_LINK_OPTIONS = PERFORMANCE_LINK_OPTIONS;
|
|
4619
|
+
exports.PERFORMANCE_LINK_SETTINGS = PERFORMANCE_LINK_SETTINGS;
|
|
4620
|
+
exports.PERFORMANCE_NODE_SETTINGS = PERFORMANCE_NODE_SETTINGS;
|
|
4108
4621
|
exports.animationByProgress = animationByProgress;
|
|
4622
|
+
exports.approximateQuadraticBezierLength = approximateQuadraticBezierLength;
|
|
4109
4623
|
exports.calculateLinkPositionByNode = calculateLinkPositionByNode;
|
|
4110
4624
|
exports.colorGetter = colorGetter;
|
|
4111
4625
|
exports.colorToRgb = colorToRgb;
|
|
4626
|
+
exports.computeGraphBounds = computeGraphBounds;
|
|
4112
4627
|
exports.dragPlaceCoefficientGetter = dragPlaceCoefficientGetter;
|
|
4113
4628
|
exports.drawText = drawText;
|
|
4114
4629
|
exports.extractLinkPointIds = extractLinkPointIds;
|
|
@@ -4120,6 +4635,7 @@ exports.getDrawNode = getDrawNode;
|
|
|
4120
4635
|
exports.getParticlePosition = getParticlePosition;
|
|
4121
4636
|
exports.graphSettingsGetter = graphSettingsGetter;
|
|
4122
4637
|
exports.highlightSettingsGetter = highlightSettingsGetter;
|
|
4638
|
+
exports.isEmptyObject = isEmptyObject;
|
|
4123
4639
|
exports.isNodeVisible = isNodeVisible;
|
|
4124
4640
|
exports.isOverlapsNode = isOverlapsNode;
|
|
4125
4641
|
exports.linkByPointerGetter = linkByPointerGetter;
|