@krainovsd/graph 0.13.0-beta1 → 0.14.0-beta.1
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 +984 -497
- 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 +222 -34
- 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 -9
- 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 +45 -0
- package/lib/esm/module/GraphCanvas/lib/utils/calculate-curve-link-position-by-node.js.map +1 -0
- package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-node.js +42 -37
- 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 +72 -22
- package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/draw-links.js +115 -77
- 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-dnd.js +21 -4
- package/lib/esm/module/GraphCanvas/slices/init-dnd.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-draw.js +1 -52
- package/lib/esm/module/GraphCanvas/slices/init-draw.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-pointer.js +12 -34
- package/lib/esm/module/GraphCanvas/slices/init-pointer.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-resize.js +7 -0
- package/lib/esm/module/GraphCanvas/slices/init-resize.js.map +1 -1
- 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 +85 -16
- package/lib/esm/module/GraphCanvas/slices/init-zoom.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/update-link-cache.js +63 -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 +90 -36
- 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,6 +290,30 @@ 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
318
|
const clientX = "clientX" in mouseEvent
|
|
276
319
|
? mouseEvent.clientX
|
|
@@ -421,50 +464,52 @@ function nodeIterationExtractor(node, i, nodes, state, option, optionConstantGet
|
|
|
421
464
|
return customOptions;
|
|
422
465
|
}
|
|
423
466
|
|
|
424
|
-
function calculateLinkPositionByNode(
|
|
425
|
-
const source = link.source;
|
|
426
|
-
const target = link.target;
|
|
427
|
-
if (typeof source != "object" || typeof target != "object")
|
|
428
|
-
return null;
|
|
467
|
+
function calculateLinkPositionByNode(xStart, yStart, xEnd, yEnd, sourceNode, targetNode, arrowSize = 0) {
|
|
429
468
|
const sourcePoint = getLinkPoint({
|
|
469
|
+
xStart,
|
|
470
|
+
yStart,
|
|
471
|
+
xEnd,
|
|
472
|
+
yEnd,
|
|
430
473
|
arrowSize: 0,
|
|
431
|
-
node:
|
|
432
|
-
oppositeNode: target,
|
|
433
|
-
});
|
|
474
|
+
node: sourceNode});
|
|
434
475
|
const targetPoint = getLinkPoint({
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
476
|
+
xStart: xEnd,
|
|
477
|
+
yStart: yEnd,
|
|
478
|
+
xEnd: xStart,
|
|
479
|
+
yEnd: yStart,
|
|
480
|
+
arrowSize: arrowSize > 0 ? arrowSize * 0.9 : 0,
|
|
481
|
+
node: targetNode});
|
|
482
|
+
const targetPointArrow = getLinkPoint({
|
|
483
|
+
xStart: xEnd,
|
|
484
|
+
yStart: yEnd,
|
|
485
|
+
xEnd: xStart,
|
|
486
|
+
yEnd: yStart,
|
|
487
|
+
arrowSize: 0,
|
|
488
|
+
node: targetNode});
|
|
441
489
|
const distance = targetPoint && sourcePoint
|
|
442
490
|
? Math.sqrt((targetPoint.x - sourcePoint.x) ** 2 + (targetPoint.y - sourcePoint.y) ** 2)
|
|
443
491
|
: 0;
|
|
444
492
|
return {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
493
|
+
xStart: sourcePoint.x,
|
|
494
|
+
yStart: sourcePoint.y,
|
|
495
|
+
xEnd: targetPoint.x,
|
|
496
|
+
yEnd: targetPoint.y,
|
|
497
|
+
xEndArrow: targetPointArrow.x,
|
|
498
|
+
yEndArrow: targetPointArrow.y,
|
|
449
499
|
distance,
|
|
450
500
|
};
|
|
451
501
|
}
|
|
452
502
|
function getLinkPoint(opts) {
|
|
453
|
-
if (opts.node.x == undefined ||
|
|
454
|
-
opts.node.y == undefined ||
|
|
455
|
-
opts.oppositeNode.x == undefined ||
|
|
456
|
-
opts.oppositeNode.y == undefined)
|
|
457
|
-
return null;
|
|
458
503
|
let nodePoint;
|
|
459
504
|
switch (opts.node._shape) {
|
|
460
505
|
case "circle": {
|
|
461
506
|
nodePoint = getCircleIntersection({
|
|
462
|
-
x: opts.
|
|
463
|
-
y: opts.
|
|
507
|
+
x: opts.xStart,
|
|
508
|
+
y: opts.yStart,
|
|
464
509
|
radius: (opts.node._radius ?? COMMON_SETTINGS.nodeRadius) +
|
|
465
510
|
(opts.node._borderWidth ? opts.node._borderWidth / 2 : 0),
|
|
466
|
-
oppositeX: opts.
|
|
467
|
-
oppositeY: opts.
|
|
511
|
+
oppositeX: opts.xEnd,
|
|
512
|
+
oppositeY: opts.yEnd,
|
|
468
513
|
arrowSize: opts.arrowSize,
|
|
469
514
|
});
|
|
470
515
|
break;
|
|
@@ -473,23 +518,23 @@ function getLinkPoint(opts) {
|
|
|
473
518
|
case "text": {
|
|
474
519
|
nodePoint = getRectangleIntersection({
|
|
475
520
|
arrowSize: opts.arrowSize,
|
|
476
|
-
x: opts.
|
|
477
|
-
y: opts.
|
|
521
|
+
x: opts.xStart,
|
|
522
|
+
y: opts.yStart,
|
|
478
523
|
height: (opts.node._height ?? COMMON_SETTINGS.nodeSize) + (opts.node._borderWidth ?? 0),
|
|
479
524
|
width: (opts.node._width ?? COMMON_SETTINGS.nodeSize) + (opts.node._borderWidth ?? 0),
|
|
480
|
-
oppositeX: opts.
|
|
481
|
-
oppositeY: opts.
|
|
525
|
+
oppositeX: opts.xEnd,
|
|
526
|
+
oppositeY: opts.yEnd,
|
|
482
527
|
borderRadius: opts.node._shape === "text" ? 0 : (opts.node._borderRadius ?? 0),
|
|
483
528
|
});
|
|
484
529
|
break;
|
|
485
530
|
}
|
|
486
531
|
default: {
|
|
487
532
|
nodePoint = getCircleIntersection({
|
|
488
|
-
x: opts.
|
|
489
|
-
y: opts.
|
|
533
|
+
x: opts.xStart,
|
|
534
|
+
y: opts.yStart,
|
|
490
535
|
radius: opts.node._radius ?? COMMON_SETTINGS.nodeRadius,
|
|
491
|
-
oppositeX: opts.
|
|
492
|
-
oppositeY: opts.
|
|
536
|
+
oppositeX: opts.xEnd,
|
|
537
|
+
oppositeY: opts.yEnd,
|
|
493
538
|
arrowSize: opts.arrowSize,
|
|
494
539
|
});
|
|
495
540
|
}
|
|
@@ -733,68 +778,81 @@ function isNodeVisible(opts) {
|
|
|
733
778
|
}
|
|
734
779
|
}
|
|
735
780
|
|
|
781
|
+
const FRAME_INTERVAL = 1000 / 60;
|
|
736
782
|
function getParticlePosition(opts) {
|
|
737
|
-
const
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
opts.particle.prev.step !== 0 &&
|
|
744
|
-
opts.particle.prev.step < opts.particle.step &&
|
|
745
|
-
prevStepDifference < opts.distance;
|
|
746
|
-
if (opts.particle.step === 0 && needWait) {
|
|
747
|
-
opts.particle.x = undefined;
|
|
748
|
-
opts.particle.y = undefined;
|
|
749
|
-
return;
|
|
783
|
+
const now = performance.now();
|
|
784
|
+
const particle = opts.particle;
|
|
785
|
+
if (particle._lastTime == undefined) {
|
|
786
|
+
particle._distanceTraveled = (opts.distance / opts.totalCount) * particle.index;
|
|
787
|
+
particle._lastTime = now;
|
|
788
|
+
particle._lastDistance = opts.distance;
|
|
750
789
|
}
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
opts.particle.step = 0;
|
|
757
|
-
return;
|
|
790
|
+
const lastDistance = particle._lastDistance ?? 0;
|
|
791
|
+
if (lastDistance !== opts.distance && lastDistance > 0) {
|
|
792
|
+
const scale = opts.distance / lastDistance;
|
|
793
|
+
particle._distanceTraveled = ((particle._distanceTraveled ?? 0) * scale) % opts.distance;
|
|
794
|
+
particle._lastDistance = opts.distance;
|
|
758
795
|
}
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
opts.
|
|
765
|
-
|
|
766
|
-
|
|
796
|
+
const delta = Math.min(now - particle._lastTime, FRAME_INTERVAL);
|
|
797
|
+
particle._lastTime = now;
|
|
798
|
+
particle._distanceTraveled =
|
|
799
|
+
((particle._distanceTraveled ?? 0) + opts.speed * delta) % opts.distance;
|
|
800
|
+
const progress = particle._distanceTraveled / opts.distance;
|
|
801
|
+
if (opts.xControl !== 0 && opts.yControl !== 0) {
|
|
802
|
+
const t = 1 - progress;
|
|
803
|
+
particle.x =
|
|
804
|
+
t * t * opts.xStart + 2 * t * progress * opts.xControl + progress * progress * opts.xEnd;
|
|
805
|
+
particle.y =
|
|
806
|
+
t * t * opts.yStart + 2 * t * progress * opts.yControl + progress * progress * opts.yEnd;
|
|
767
807
|
}
|
|
768
808
|
else {
|
|
769
|
-
opts.
|
|
809
|
+
const dx = opts.xEnd - opts.xStart;
|
|
810
|
+
const dy = opts.yEnd - opts.yStart;
|
|
811
|
+
particle.x = opts.xStart + dx * progress;
|
|
812
|
+
particle.y = opts.yStart + dy * progress;
|
|
770
813
|
}
|
|
771
814
|
}
|
|
815
|
+
function approximateQuadraticBezierLength(x0, y0, x1, y1, x2, y2) {
|
|
816
|
+
const dx01 = x1 - x0;
|
|
817
|
+
const dy01 = y1 - y0;
|
|
818
|
+
const dx12 = x2 - x1;
|
|
819
|
+
const dy12 = y2 - y1;
|
|
820
|
+
const dx02 = x2 - x0;
|
|
821
|
+
const dy02 = y2 - y0;
|
|
822
|
+
const a = Math.sqrt(dx01 * dx01 + dy01 * dy01);
|
|
823
|
+
const b = Math.sqrt(dx12 * dx12 + dy12 * dy12);
|
|
824
|
+
const c = Math.sqrt(dx02 * dx02 + dy02 * dy02);
|
|
825
|
+
return (a + b + c) / 2;
|
|
826
|
+
}
|
|
772
827
|
|
|
773
|
-
function linkByPointerGetter({ areaRect, areaTransform, mouseEvent, links, linkHoverExtraZone, }) {
|
|
828
|
+
function linkByPointerGetter({ areaRect, areaTransform, mouseEvent, links, linkHoverExtraZone, curve, }) {
|
|
774
829
|
if (!areaRect)
|
|
775
830
|
return undefined;
|
|
776
831
|
const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
|
|
777
832
|
return d3Array.greatest(links, (link) => {
|
|
778
|
-
if (
|
|
779
|
-
|
|
833
|
+
if (!jsHelpers.isObject(link.source) ||
|
|
834
|
+
!jsHelpers.isObject(link.target) ||
|
|
835
|
+
link.source.visible === false ||
|
|
836
|
+
link.target.visible === false)
|
|
837
|
+
return;
|
|
838
|
+
const x1 = (link._x1 ?? link.source.x);
|
|
839
|
+
const y1 = (link._y1 ?? link.source.y);
|
|
840
|
+
const x2 = (link._x2 ?? link.target.x);
|
|
841
|
+
const y2 = (link._y2 ?? link.target.y);
|
|
842
|
+
const cx = link._cx ?? 0;
|
|
843
|
+
const cy = link._cy ?? 0;
|
|
844
|
+
return curve
|
|
845
|
+
? hitTestQuadratic(pointerX, pointerY, x1, y1, cx, cy, x2, y2, linkHoverExtraZone)
|
|
846
|
+
? link.index
|
|
847
|
+
: undefined
|
|
848
|
+
: isNearLink(pointerX, pointerY, x1, y1, x2, y2, linkHoverExtraZone)
|
|
849
|
+
? link.index
|
|
850
|
+
: undefined;
|
|
780
851
|
});
|
|
781
852
|
}
|
|
782
|
-
function isNearLink(
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
link.source.visible === false ||
|
|
786
|
-
link.target.visible === false)
|
|
787
|
-
return false;
|
|
788
|
-
const x1 = link.source.x;
|
|
789
|
-
const y1 = link.source.y;
|
|
790
|
-
const x2 = link.target.x;
|
|
791
|
-
const y2 = link.target.y;
|
|
792
|
-
const distance = distanceToLine(mouseX, mouseY, x1, y1, x2, y2);
|
|
793
|
-
return distance <= threshold;
|
|
794
|
-
}
|
|
795
|
-
function distanceToLine(x, y, x1, y1, x2, y2) {
|
|
796
|
-
const A = x - x1;
|
|
797
|
-
const B = y - y1;
|
|
853
|
+
function isNearLink(px, py, x1, y1, x2, y2, threshold = 2) {
|
|
854
|
+
const A = px - x1;
|
|
855
|
+
const B = py - y1;
|
|
798
856
|
const C = x2 - x1;
|
|
799
857
|
const D = y2 - y1;
|
|
800
858
|
const dot = A * C + B * D;
|
|
@@ -816,9 +874,56 @@ function distanceToLine(x, y, x1, y1, x2, y2) {
|
|
|
816
874
|
xx = x1 + param * C;
|
|
817
875
|
yy = y1 + param * D;
|
|
818
876
|
}
|
|
819
|
-
const dx =
|
|
820
|
-
const dy =
|
|
821
|
-
return Math.sqrt(dx * dx + dy * dy);
|
|
877
|
+
const dx = px - xx;
|
|
878
|
+
const dy = py - yy;
|
|
879
|
+
return Math.sqrt(dx * dx + dy * dy) <= threshold;
|
|
880
|
+
}
|
|
881
|
+
function hitTestQuadratic(px, py, x0, y0, xc, yc, x2, y2, tol) {
|
|
882
|
+
const bb = bezierBBox(x0, y0, xc, yc, x2, y2);
|
|
883
|
+
if (px < bb.minX - tol || px > bb.maxX + tol || py < bb.minY - tol || py > bb.maxY + tol) {
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
const chord = Math.hypot(x2 - x0, y2 - y0);
|
|
887
|
+
const contLen = Math.hypot(xc - x0, yc - y0) + Math.hypot(x2 - xc, y2 - yc);
|
|
888
|
+
const approxLen = (chord + contLen) / 2;
|
|
889
|
+
const steps = Math.max(16, Math.ceil(approxLen / 1));
|
|
890
|
+
const tol2 = tol * tol;
|
|
891
|
+
for (let i = 0; i <= steps; i++) {
|
|
892
|
+
const t = i / steps;
|
|
893
|
+
const mt = 1 - t;
|
|
894
|
+
const x = mt * mt * x0 + 2 * mt * t * xc + t * t * x2;
|
|
895
|
+
const y = mt * mt * y0 + 2 * mt * t * yc + t * t * y2;
|
|
896
|
+
const dx = px - x;
|
|
897
|
+
const dy = py - y;
|
|
898
|
+
if (dx * dx + dy * dy <= tol2)
|
|
899
|
+
return true;
|
|
900
|
+
}
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
function bezierBBox(x0, y0, x1, y1, x2, y2) {
|
|
904
|
+
function extrema(p0, p1, p2) {
|
|
905
|
+
const denom = p0 - 2 * p1 + p2;
|
|
906
|
+
if (denom === 0)
|
|
907
|
+
return [];
|
|
908
|
+
const t = (p0 - p1) / denom;
|
|
909
|
+
return t > 0 && t < 1 ? [t] : [];
|
|
910
|
+
}
|
|
911
|
+
const ts = [...extrema(x0, x1, x2), ...extrema(y0, y1, y2), 0, 1];
|
|
912
|
+
let maxX = -Infinity, maxY = -Infinity, minX = Infinity, minY = Infinity;
|
|
913
|
+
for (const t of ts) {
|
|
914
|
+
const mt = 1 - t;
|
|
915
|
+
const x = mt * mt * x0 + 2 * mt * t * x1 + t * t * x2;
|
|
916
|
+
const y = mt * mt * y0 + 2 * mt * t * y1 + t * t * y2;
|
|
917
|
+
if (x < minX)
|
|
918
|
+
minX = x;
|
|
919
|
+
if (x > maxX)
|
|
920
|
+
maxX = x;
|
|
921
|
+
if (y < minY)
|
|
922
|
+
minY = y;
|
|
923
|
+
if (y > maxY)
|
|
924
|
+
maxY = y;
|
|
925
|
+
}
|
|
926
|
+
return { minX, minY, maxX, maxY };
|
|
822
927
|
}
|
|
823
928
|
|
|
824
929
|
function nodeHighlight(opts) {
|
|
@@ -994,22 +1099,38 @@ function extractLinkPointIds(link) {
|
|
|
994
1099
|
return { sourceId, targetId };
|
|
995
1100
|
}
|
|
996
1101
|
|
|
1102
|
+
function isEmptyObject(obj) {
|
|
1103
|
+
for (const key in obj) {
|
|
1104
|
+
if (Object.hasOwn(obj, key)) {
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return true;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
997
1111
|
const GRAPH_SETTINGS = {
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1112
|
+
zoomAnimation: true,
|
|
1113
|
+
zoomAnimationDuration: 300,
|
|
1114
|
+
zoomToNodeScale: 5,
|
|
1115
|
+
zoomToFitMargin: 0.25,
|
|
1116
|
+
zoomExtent: null,
|
|
1117
|
+
zoomExtentMargin: 0.3,
|
|
1118
|
+
translateExtent: null,
|
|
1119
|
+
translateExtentCoefficient: null,
|
|
1120
|
+
translateExtentOverlap: 0.3,
|
|
1002
1121
|
dragPlaceCoefficient: dragPlaceCoefficientGetter,
|
|
1003
1122
|
zoomInitial: null,
|
|
1004
1123
|
showDrawTime: true,
|
|
1005
1124
|
showDrawTimeEveryTick: false,
|
|
1006
1125
|
};
|
|
1126
|
+
const PERFORMANCE_GRAPH_SETTINGS = {
|
|
1127
|
+
...GRAPH_SETTINGS,
|
|
1128
|
+
};
|
|
1007
1129
|
|
|
1008
1130
|
const HIGHLIGHT_SETTINGS = {
|
|
1009
1131
|
highlightByHoverNode: true,
|
|
1010
1132
|
highlightByHoverLink: true,
|
|
1011
|
-
|
|
1012
|
-
highlightDownFrames: 5,
|
|
1133
|
+
highlightDuration: 150,
|
|
1013
1134
|
linkHoverExtraZone: 2,
|
|
1014
1135
|
/** Node */
|
|
1015
1136
|
highlightByNodeOnlyRoot: true,
|
|
@@ -1064,36 +1185,51 @@ const HIGHLIGHT_SETTINGS = {
|
|
|
1064
1185
|
highlightByLinkForArrowBorderColor: null,
|
|
1065
1186
|
highlightByLinkForArrowBorderSizingAdditional: 0,
|
|
1066
1187
|
};
|
|
1188
|
+
const PERFORMANCE_HIGHLIGHT_SETTINGS = {
|
|
1189
|
+
...HIGHLIGHT_SETTINGS,
|
|
1190
|
+
};
|
|
1067
1191
|
|
|
1068
1192
|
const LINK_SETTINGS = {
|
|
1193
|
+
smartCache: true,
|
|
1069
1194
|
prettyDraw: true,
|
|
1195
|
+
curve: true,
|
|
1070
1196
|
linkScaleSwitch: 1,
|
|
1071
|
-
linkColorAfterScaleSwitch: "#
|
|
1197
|
+
linkColorAfterScaleSwitch: "#C5C5C5FF",
|
|
1072
1198
|
linkColorBeforeScaleSwitch: "#999",
|
|
1073
1199
|
linkWidthAfterScaleSwitch: 0.1,
|
|
1074
1200
|
linkWidthBeforeScaleSwitch: 1,
|
|
1075
1201
|
arrow: true,
|
|
1076
1202
|
arrowByHighlight: true,
|
|
1077
1203
|
particles: true,
|
|
1078
|
-
|
|
1079
|
-
|
|
1204
|
+
// spawn one more particle for N px
|
|
1205
|
+
particleCountByDistance: 25,
|
|
1206
|
+
// speed for 1px
|
|
1207
|
+
particleSpeedByDistance: 50,
|
|
1080
1208
|
};
|
|
1081
1209
|
const LINK_OPTIONS = {
|
|
1082
1210
|
alpha: 1,
|
|
1083
1211
|
arrowAlpha: 1,
|
|
1084
1212
|
arrowSize: 2,
|
|
1085
|
-
arrowBorderColor: "#
|
|
1213
|
+
arrowBorderColor: "#C5C5C5FF",
|
|
1086
1214
|
arrowBorderWidth: 0.1,
|
|
1087
1215
|
particleAlpha: 1,
|
|
1088
|
-
particleColor: "#
|
|
1089
|
-
particleBorderColor: "#
|
|
1216
|
+
particleColor: "#C5C5C5FF",
|
|
1217
|
+
particleBorderColor: "#C5C5C5FF",
|
|
1090
1218
|
particleBorderWidth: 0.1,
|
|
1091
|
-
particleCount: 2,
|
|
1092
1219
|
particleRadius: 0.5,
|
|
1093
|
-
|
|
1220
|
+
};
|
|
1221
|
+
const PERFORMANCE_LINK_SETTINGS = {
|
|
1222
|
+
...LINK_SETTINGS,
|
|
1223
|
+
particles: false,
|
|
1224
|
+
prettyDraw: false,
|
|
1225
|
+
};
|
|
1226
|
+
const PERFORMANCE_LINK_OPTIONS = {
|
|
1227
|
+
...LINK_OPTIONS,
|
|
1094
1228
|
};
|
|
1095
1229
|
|
|
1096
1230
|
const NODE_SETTINGS = {
|
|
1231
|
+
smartCache: true,
|
|
1232
|
+
textScaleSteps: [],
|
|
1097
1233
|
nodeRadiusFlexible: true,
|
|
1098
1234
|
nodeRadiusLinkCountForStep: 5,
|
|
1099
1235
|
nodeRadiusIncrementByStep: 1,
|
|
@@ -1130,14 +1266,14 @@ const NODE_OPTIONS = {
|
|
|
1130
1266
|
textShiftX: 0,
|
|
1131
1267
|
textFont: "Arial",
|
|
1132
1268
|
textAlign: "center",
|
|
1133
|
-
textColor: "#
|
|
1269
|
+
textColor: "#d2d2d2",
|
|
1134
1270
|
textStyle: "normal",
|
|
1135
1271
|
textWeight: 500,
|
|
1136
1272
|
textGap: 0,
|
|
1137
1273
|
label: null,
|
|
1138
1274
|
labelAlpha: 1,
|
|
1139
1275
|
labelAlign: "center",
|
|
1140
|
-
labelColor: "#
|
|
1276
|
+
labelColor: "#ffffff",
|
|
1141
1277
|
labelFont: "Arial",
|
|
1142
1278
|
labelGap: 0,
|
|
1143
1279
|
labelSize: 3.5,
|
|
@@ -1145,6 +1281,14 @@ const NODE_OPTIONS = {
|
|
|
1145
1281
|
labelWeight: 500,
|
|
1146
1282
|
labelWidth: 20,
|
|
1147
1283
|
};
|
|
1284
|
+
const PERFORMANCE_NODE_SETTINGS = {
|
|
1285
|
+
...NODE_SETTINGS,
|
|
1286
|
+
nodeRadiusFlexible: false,
|
|
1287
|
+
nodeSizeFlexible: false,
|
|
1288
|
+
};
|
|
1289
|
+
const PERFOMANCE_NODE_OPTIONS = {
|
|
1290
|
+
...NODE_OPTIONS,
|
|
1291
|
+
};
|
|
1148
1292
|
|
|
1149
1293
|
const GRAPH_CACHE_TYPE = {
|
|
1150
1294
|
NodeOptions: "nodeOptions",
|
|
@@ -1211,8 +1355,14 @@ function initDnd() {
|
|
|
1211
1355
|
return;
|
|
1212
1356
|
const mouseEvent = event.sourceEvent;
|
|
1213
1357
|
const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
|
|
1214
|
-
|
|
1215
|
-
|
|
1358
|
+
if (this._translateExtent) {
|
|
1359
|
+
event.subject.fx = Math.max(this._translateExtent[0][0], Math.min(this._translateExtent[1][0], pointerX));
|
|
1360
|
+
event.subject.fy = Math.max(this._translateExtent[0][1], Math.min(this._translateExtent[1][1], pointerY));
|
|
1361
|
+
}
|
|
1362
|
+
else {
|
|
1363
|
+
event.subject.fx = pointerX;
|
|
1364
|
+
event.subject.fy = pointerY;
|
|
1365
|
+
}
|
|
1216
1366
|
this.listeners.onMoveDragFinished?.call?.(this, event);
|
|
1217
1367
|
})
|
|
1218
1368
|
.on("end", (event) => {
|
|
@@ -1222,8 +1372,14 @@ function initDnd() {
|
|
|
1222
1372
|
const sourceEvent = event.sourceEvent;
|
|
1223
1373
|
if (sourceEvent.altKey && this.areaRect) {
|
|
1224
1374
|
const [pointerX, pointerY] = pointerGetter(sourceEvent, this.areaRect, this.areaTransform);
|
|
1225
|
-
|
|
1226
|
-
|
|
1375
|
+
if (this._translateExtent) {
|
|
1376
|
+
event.subject.fx = Math.max(this._translateExtent[0][0], Math.min(this._translateExtent[1][0], pointerX));
|
|
1377
|
+
event.subject.fy = Math.max(this._translateExtent[0][1], Math.min(this._translateExtent[1][1], pointerY));
|
|
1378
|
+
}
|
|
1379
|
+
else {
|
|
1380
|
+
event.subject.fx = pointerX;
|
|
1381
|
+
event.subject.fy = pointerY;
|
|
1382
|
+
}
|
|
1227
1383
|
}
|
|
1228
1384
|
else {
|
|
1229
1385
|
event.subject.fx = null;
|
|
@@ -1234,8 +1390,44 @@ function initDnd() {
|
|
|
1234
1390
|
d3Selection.select(this.area).call(dragHandler);
|
|
1235
1391
|
}
|
|
1236
1392
|
|
|
1393
|
+
function calculateCurveLinkPositionByNode(xStart, yStart, xEnd, yEnd, sourceNode, targetNode, groupIndex, arrowSize = 0) {
|
|
1394
|
+
const sourceRadius = (sourceNode._radius ?? COMMON_SETTINGS.nodeRadius) + (sourceNode._borderWidth ?? 0) * 0.5;
|
|
1395
|
+
const targetRadius = (targetNode._radius ?? COMMON_SETTINGS.nodeRadius) + (targetNode._borderWidth ?? 0) * 0.5;
|
|
1396
|
+
const effectiveTargetRadius = targetRadius + arrowSize * 0.9;
|
|
1397
|
+
const mx = (xStart + xEnd) * 0.5;
|
|
1398
|
+
const my = (yStart + yEnd) * 0.5;
|
|
1399
|
+
const dx = xEnd - xStart;
|
|
1400
|
+
const dy = yEnd - yStart;
|
|
1401
|
+
const lenSq = dx * dx + dy * dy;
|
|
1402
|
+
const len = lenSq > 0 ? Math.sqrt(lenSq) : 1;
|
|
1403
|
+
const nx = -dy / len;
|
|
1404
|
+
const ny = dx / len;
|
|
1405
|
+
const offset = 0.12 * len * groupIndex;
|
|
1406
|
+
const xControl = mx + nx * offset;
|
|
1407
|
+
const yControl = my + ny * offset;
|
|
1408
|
+
const [xStartP, yStartP] = adjustPoint(xStart, yStart, xControl, yControl, sourceRadius);
|
|
1409
|
+
const [xEndP, yEndP] = adjustPoint(xEnd, yEnd, xControl, yControl, effectiveTargetRadius);
|
|
1410
|
+
const [xEndArrow, yEndArrow] = adjustPoint(xEnd, yEnd, xControl, yControl, targetRadius);
|
|
1411
|
+
return {
|
|
1412
|
+
xStart: xStartP,
|
|
1413
|
+
yStart: yStartP,
|
|
1414
|
+
xEnd: xEndP,
|
|
1415
|
+
yEnd: yEndP,
|
|
1416
|
+
xControl,
|
|
1417
|
+
yControl,
|
|
1418
|
+
xEndArrow,
|
|
1419
|
+
yEndArrow,
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
function adjustPoint(x, y, xControl, yControl, radius) {
|
|
1423
|
+
const dxToControl = xControl - x;
|
|
1424
|
+
const dyToControl = yControl - y;
|
|
1425
|
+
const distToControl = Math.sqrt(dxToControl * dxToControl + dyToControl * dyToControl) || 1;
|
|
1426
|
+
return [x + (dxToControl / distToControl) * radius, y + (dyToControl / distToControl) * radius];
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1237
1429
|
function getDrawLink() {
|
|
1238
|
-
return function drawLink(link) {
|
|
1430
|
+
return function drawLink(link, index) {
|
|
1239
1431
|
if (!this.context ||
|
|
1240
1432
|
typeof link.source !== "object" ||
|
|
1241
1433
|
typeof link.target !== "object" ||
|
|
@@ -1250,8 +1442,7 @@ function getDrawLink() {
|
|
|
1250
1442
|
return;
|
|
1251
1443
|
if (!link.source._visible && !link.target._visible)
|
|
1252
1444
|
return;
|
|
1253
|
-
const
|
|
1254
|
-
const linkOptions = this.linkOptionsCache[id];
|
|
1445
|
+
const linkOptions = this.linkOptionsCache[index];
|
|
1255
1446
|
if (!linkOptions)
|
|
1256
1447
|
return;
|
|
1257
1448
|
if (linkOptions.drawLink) {
|
|
@@ -1266,10 +1457,12 @@ function getDrawLink() {
|
|
|
1266
1457
|
let arrowSize = linkOptions.arrowSize;
|
|
1267
1458
|
let arrowBorderWidth = linkOptions.arrowBorderWidth;
|
|
1268
1459
|
let arrowBorderColor = linkOptions.arrowBorderColor;
|
|
1460
|
+
const currentNodeHighlighted = this.highlightedNode &&
|
|
1461
|
+
(this.highlightedNode.id == link.source.id || this.highlightedNode.id == link.target.id);
|
|
1269
1462
|
/** Highlight */
|
|
1270
1463
|
if (this.highlightedNeighbors && this.highlightedNode) {
|
|
1271
1464
|
/** By Node Not Highlight */
|
|
1272
|
-
if (
|
|
1465
|
+
if (!currentNodeHighlighted) {
|
|
1273
1466
|
const fadeOptions = linkFade({
|
|
1274
1467
|
arrow: this.linkSettings.arrow,
|
|
1275
1468
|
arrowByHighlight: this.linkSettings.arrowByHighlight,
|
|
@@ -1303,9 +1496,10 @@ function getDrawLink() {
|
|
|
1303
1496
|
arrowBorderColor = highlightOptions.arrowBorderColor;
|
|
1304
1497
|
}
|
|
1305
1498
|
}
|
|
1499
|
+
const currentLinkHighlighted = this.highlightedLink === link;
|
|
1306
1500
|
if (this.highlightedNeighbors && this.highlightedLink) {
|
|
1307
1501
|
/** By Link Not Highlight */
|
|
1308
|
-
if (
|
|
1502
|
+
if (!currentLinkHighlighted) {
|
|
1309
1503
|
const fadeOptions = linkFade({
|
|
1310
1504
|
arrow: this.linkSettings.arrow,
|
|
1311
1505
|
arrowByHighlight: this.linkSettings.arrowByHighlight,
|
|
@@ -1349,99 +1543,129 @@ function getDrawLink() {
|
|
|
1349
1543
|
let yStart = link.source.y;
|
|
1350
1544
|
let xEnd = link.target.x;
|
|
1351
1545
|
let yEnd = link.target.y;
|
|
1546
|
+
let xControl = 0;
|
|
1547
|
+
let yControl = 0;
|
|
1548
|
+
let xEndArrow = xEnd;
|
|
1549
|
+
let yEndArrow = yEnd;
|
|
1352
1550
|
let linkDistance = 0;
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
(this.linkSettings.
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
xStart = position.
|
|
1360
|
-
xEnd = position.
|
|
1361
|
-
yStart = position.
|
|
1362
|
-
yEnd = position.
|
|
1551
|
+
const isHasArrow = this.linkSettings.arrow && arrowAlpha > 0;
|
|
1552
|
+
if (!this.linkSettings.curve) {
|
|
1553
|
+
if (this.linkSettings.prettyDraw ||
|
|
1554
|
+
(this.linkSettings.arrow && arrowAlpha > 0) ||
|
|
1555
|
+
(this.particles && (currentNodeHighlighted || currentLinkHighlighted))) {
|
|
1556
|
+
const position = calculateLinkPositionByNode(xStart, yStart, xEnd, yEnd, link.source, link.target, isHasArrow ? arrowSize : 0);
|
|
1557
|
+
xStart = position.xStart;
|
|
1558
|
+
xEnd = position.xEnd;
|
|
1559
|
+
yStart = position.yStart;
|
|
1560
|
+
yEnd = position.yEnd;
|
|
1561
|
+
xEndArrow = position.xEndArrow;
|
|
1562
|
+
yEndArrow = position.yEndArrow;
|
|
1363
1563
|
linkDistance = position.distance;
|
|
1364
1564
|
}
|
|
1565
|
+
link._x1 = xStart;
|
|
1566
|
+
link._y1 = yStart;
|
|
1567
|
+
link._x2 = xEnd;
|
|
1568
|
+
link._y2 = yEnd;
|
|
1569
|
+
link._ax = xEndArrow;
|
|
1570
|
+
link._ay = yEndArrow;
|
|
1571
|
+
this.context.moveTo(xStart, yStart);
|
|
1572
|
+
this.context.lineTo(xEnd, yEnd);
|
|
1573
|
+
this.context.stroke();
|
|
1574
|
+
}
|
|
1575
|
+
else {
|
|
1576
|
+
const position = calculateCurveLinkPositionByNode(xStart, yStart, xEnd, yEnd, link.source, link.target, link._groupIndex ?? 0, isHasArrow ? arrowSize : 0);
|
|
1577
|
+
xStart = position.xStart;
|
|
1578
|
+
yStart = position.yStart;
|
|
1579
|
+
xEnd = position.xEnd;
|
|
1580
|
+
yEnd = position.yEnd;
|
|
1581
|
+
xControl = position.xControl;
|
|
1582
|
+
yControl = position.yControl;
|
|
1583
|
+
xEndArrow = position.xEndArrow;
|
|
1584
|
+
yEndArrow = position.yEndArrow;
|
|
1585
|
+
link._x1 = position.xStart;
|
|
1586
|
+
link._y1 = position.yStart;
|
|
1587
|
+
link._x2 = position.xEnd;
|
|
1588
|
+
link._y2 = position.yEnd;
|
|
1589
|
+
link._cx = position.xControl;
|
|
1590
|
+
link._cy = position.yControl;
|
|
1591
|
+
link._ax = position.xEndArrow;
|
|
1592
|
+
link._ay = position.yEndArrow;
|
|
1593
|
+
linkDistance = approximateQuadraticBezierLength(position.xStart, position.yStart, position.xControl, position.yControl, position.xEnd, position.yEnd);
|
|
1594
|
+
this.context.beginPath();
|
|
1595
|
+
this.context.moveTo(position.xStart, position.yStart);
|
|
1596
|
+
this.context.quadraticCurveTo(position.xControl, position.yControl, position.xEnd, position.yEnd);
|
|
1597
|
+
this.context.setLineDash([]);
|
|
1598
|
+
// if (isDashed) {
|
|
1599
|
+
// this.context.setLineDash([10, 5]);
|
|
1600
|
+
// }
|
|
1601
|
+
this.context.stroke();
|
|
1365
1602
|
}
|
|
1366
|
-
this.context.moveTo(xStart, yStart);
|
|
1367
|
-
this.context.lineTo(xEnd, yEnd);
|
|
1368
|
-
this.context.stroke();
|
|
1369
1603
|
/** Particle */
|
|
1370
|
-
if (this.linkSettings.particles &&
|
|
1371
|
-
(
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
(this.highlightedLink && this.highlightedLink === link))) {
|
|
1375
|
-
const particleSteps = this.linkSettings.particleFlexSpeed
|
|
1376
|
-
? linkDistance <= 0
|
|
1377
|
-
? 0
|
|
1378
|
-
: linkDistance * this.linkSettings.particleFlexSpeedCoefficient
|
|
1379
|
-
: linkOptions.particleSteps;
|
|
1380
|
-
const particleCount = linkOptions.particleCount;
|
|
1381
|
-
if (!this.particles[id]) {
|
|
1382
|
-
const sourceId = link.source.id;
|
|
1383
|
-
const targetId = link.target.id;
|
|
1604
|
+
if (this.linkSettings.particles && (currentLinkHighlighted || currentNodeHighlighted)) {
|
|
1605
|
+
const speed = (1 / this.linkSettings.particleSpeedByDistance) * (1 + 0.5);
|
|
1606
|
+
if (!this.particles[index]) {
|
|
1607
|
+
const particleCount = Math.max(1, Math.floor(linkDistance / this.linkSettings.particleCountByDistance));
|
|
1384
1608
|
const particles = [];
|
|
1385
|
-
let prevParticle;
|
|
1386
1609
|
for (let i = 0; i < particleCount; i++) {
|
|
1387
1610
|
const particle = {
|
|
1388
|
-
step: 0,
|
|
1389
|
-
sourceId,
|
|
1390
|
-
targetId,
|
|
1391
|
-
prev: prevParticle,
|
|
1392
|
-
next: undefined,
|
|
1393
1611
|
index: i,
|
|
1394
1612
|
};
|
|
1395
|
-
if (prevParticle)
|
|
1396
|
-
prevParticle.next = particle;
|
|
1397
1613
|
particles.push(particle);
|
|
1398
|
-
prevParticle = particle;
|
|
1399
|
-
}
|
|
1400
|
-
if (particles.length >= 2) {
|
|
1401
|
-
particles[0].prev = particles[particles.length - 1];
|
|
1402
|
-
particles[particles.length - 1].next = particles[0];
|
|
1403
1614
|
}
|
|
1404
|
-
this.particles[
|
|
1615
|
+
this.particles[index] = particles;
|
|
1405
1616
|
}
|
|
1406
|
-
|
|
1407
|
-
this.
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
if (particle.x != undefined && particle.y != undefined) {
|
|
1421
|
-
this.context.beginPath();
|
|
1422
|
-
this.context.strokeStyle = linkOptions.particleBorderColor;
|
|
1423
|
-
this.context.lineWidth = linkOptions.particleBorderWidth;
|
|
1424
|
-
this.context.arc(particle.x, particle.y, linkOptions.particleRadius, 0, Math.PI * 2);
|
|
1425
|
-
this.context.fillStyle = linkOptions.particleColor;
|
|
1426
|
-
this.context.fill();
|
|
1427
|
-
if (linkOptions.particleBorderWidth > 0) {
|
|
1428
|
-
this.context.stroke();
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1617
|
+
this.particles[index].forEach((particle, _, particles) => {
|
|
1618
|
+
if (!this.context)
|
|
1619
|
+
return;
|
|
1620
|
+
getParticlePosition({
|
|
1621
|
+
distance: linkDistance,
|
|
1622
|
+
particle,
|
|
1623
|
+
totalCount: particles.length,
|
|
1624
|
+
xEnd,
|
|
1625
|
+
xStart,
|
|
1626
|
+
yEnd,
|
|
1627
|
+
yStart,
|
|
1628
|
+
xControl,
|
|
1629
|
+
yControl,
|
|
1630
|
+
speed,
|
|
1431
1631
|
});
|
|
1432
|
-
|
|
1632
|
+
if (particle.x != undefined && particle.y != undefined) {
|
|
1633
|
+
this.context.beginPath();
|
|
1634
|
+
this.context.strokeStyle = linkOptions.particleBorderColor;
|
|
1635
|
+
this.context.lineWidth = linkOptions.particleBorderWidth;
|
|
1636
|
+
this.context.arc(particle.x, particle.y, linkOptions.particleRadius, 0, Math.PI * 2);
|
|
1637
|
+
this.context.fillStyle = linkOptions.particleColor;
|
|
1638
|
+
this.context.fill();
|
|
1639
|
+
if (linkOptions.particleBorderWidth > 0) {
|
|
1640
|
+
this.context.stroke();
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1433
1644
|
}
|
|
1434
1645
|
/** Arrow */
|
|
1435
1646
|
if (this.linkSettings.arrow && arrowAlpha > 0) {
|
|
1647
|
+
let angle = 0;
|
|
1648
|
+
if (!this.linkSettings.curve) {
|
|
1649
|
+
angle = Math.atan2(yEndArrow - yStart, xEndArrow - xStart);
|
|
1650
|
+
}
|
|
1651
|
+
else {
|
|
1652
|
+
const tangentX = 2 * (xEndArrow - xControl);
|
|
1653
|
+
const tangentY = 2 * (yEndArrow - yControl);
|
|
1654
|
+
if (Math.abs(tangentX) < 0.001 && Math.abs(tangentY) < 0.001) {
|
|
1655
|
+
angle = Math.atan2(yEndArrow - yControl, xEndArrow - xControl);
|
|
1656
|
+
}
|
|
1657
|
+
else {
|
|
1658
|
+
angle = Math.atan2(tangentY, tangentX);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1436
1661
|
this.context.beginPath();
|
|
1437
1662
|
this.context.globalAlpha = arrowAlpha;
|
|
1438
1663
|
this.context.strokeStyle = arrowBorderColor;
|
|
1439
1664
|
this.context.lineWidth = arrowBorderWidth;
|
|
1440
1665
|
this.context.fillStyle = arrowColor;
|
|
1441
|
-
|
|
1442
|
-
this.context.
|
|
1443
|
-
this.context.lineTo(
|
|
1444
|
-
this.context.lineTo(xEnd - arrowSize * Math.cos(angle + Math.PI / 6), yEnd - arrowSize * Math.sin(angle + Math.PI / 6));
|
|
1666
|
+
this.context.moveTo(xEndArrow, yEndArrow);
|
|
1667
|
+
this.context.lineTo(xEndArrow - arrowSize * Math.cos(angle - Math.PI / 6), yEndArrow - arrowSize * Math.sin(angle - Math.PI / 6));
|
|
1668
|
+
this.context.lineTo(xEndArrow - arrowSize * Math.cos(angle + Math.PI / 6), yEndArrow - arrowSize * Math.sin(angle + Math.PI / 6));
|
|
1445
1669
|
this.context.closePath();
|
|
1446
1670
|
this.context.fill();
|
|
1447
1671
|
if (arrowBorderWidth > 0) {
|
|
@@ -1500,7 +1724,7 @@ function getTextLines({ context, textAlign, textColor, textFont, textStyle, text
|
|
|
1500
1724
|
}
|
|
1501
1725
|
|
|
1502
1726
|
function getDrawNode(nodeRenders, textRenders) {
|
|
1503
|
-
return function drawNode(node) {
|
|
1727
|
+
return function drawNode(node, index) {
|
|
1504
1728
|
if (!this.context || !node.x || !node.y)
|
|
1505
1729
|
return;
|
|
1506
1730
|
if (node.visible != undefined && !node.visible) {
|
|
@@ -1509,7 +1733,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1509
1733
|
node._height = 0;
|
|
1510
1734
|
return;
|
|
1511
1735
|
}
|
|
1512
|
-
const nodeOptions = this.nodeOptionsCache[
|
|
1736
|
+
const nodeOptions = this.nodeOptionsCache[index];
|
|
1513
1737
|
if (!nodeOptions)
|
|
1514
1738
|
return;
|
|
1515
1739
|
if (nodeOptions.nodeDraw && nodeOptions.textDraw) {
|
|
@@ -1668,7 +1892,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1668
1892
|
this.context.lineWidth = borderWidth;
|
|
1669
1893
|
this.context.strokeStyle = borderColor;
|
|
1670
1894
|
this.context.fillStyle = color;
|
|
1671
|
-
const labelLines = this.cachedNodeLabel[
|
|
1895
|
+
const labelLines = this.cachedNodeLabel[index];
|
|
1672
1896
|
switch (nodeOptions.shape) {
|
|
1673
1897
|
case "circle": {
|
|
1674
1898
|
if (!node.image) {
|
|
@@ -1794,7 +2018,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1794
2018
|
});
|
|
1795
2019
|
}
|
|
1796
2020
|
/** Text draw */
|
|
1797
|
-
const textLines = this.cachedNodeText[
|
|
2021
|
+
const textLines = this.cachedNodeText[index];
|
|
1798
2022
|
if (nodeOptions.textVisible && nodeOptions.text && textLines) {
|
|
1799
2023
|
textRenders.push(() => {
|
|
1800
2024
|
if (nodeOptions.textDraw) {
|
|
@@ -1854,57 +2078,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1854
2078
|
}
|
|
1855
2079
|
|
|
1856
2080
|
function initDraw() {
|
|
1857
|
-
|
|
1858
|
-
function calculateHighlight(recursive) {
|
|
1859
|
-
if (hasHighlight && !recursive)
|
|
1860
|
-
return;
|
|
1861
|
-
/** animation up */
|
|
1862
|
-
if (!this.highlightWorking && this.highlightProgress > 0) {
|
|
1863
|
-
const highlightDownStep = 1 / this.highlightSettings.highlightDownFrames;
|
|
1864
|
-
this.highlightProgress -= highlightDownStep;
|
|
1865
|
-
if (!this.simulationWorking) {
|
|
1866
|
-
hasHighlight = true;
|
|
1867
|
-
return void requestAnimationFrame(() => this.draw(true));
|
|
1868
|
-
}
|
|
1869
|
-
if (!this.linkSettings.particles) {
|
|
1870
|
-
hasHighlight = false;
|
|
1871
|
-
return;
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
/** animation down */
|
|
1875
|
-
if (this.highlightWorking && this.highlightProgress < 1) {
|
|
1876
|
-
const highlightUpStep = 1 / this.highlightSettings.highlightUpFrames;
|
|
1877
|
-
this.highlightProgress += highlightUpStep;
|
|
1878
|
-
if (!this.simulationWorking) {
|
|
1879
|
-
hasHighlight = true;
|
|
1880
|
-
return void requestAnimationFrame(() => this.draw(true));
|
|
1881
|
-
}
|
|
1882
|
-
if (!this.linkSettings.particles) {
|
|
1883
|
-
hasHighlight = false;
|
|
1884
|
-
return;
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
/** animation active */
|
|
1888
|
-
if (this.linkSettings.particles && this.highlightWorking && !this.simulationWorking) {
|
|
1889
|
-
hasHighlight = true;
|
|
1890
|
-
return void requestAnimationFrame(() => this.draw(true));
|
|
1891
|
-
}
|
|
1892
|
-
/** animation stop */
|
|
1893
|
-
if (!this.highlightWorking && this.highlightProgress <= 0) {
|
|
1894
|
-
if (this.highlightedNeighbors || this.highlightedNode || this.highlightedLink) {
|
|
1895
|
-
this.highlightedNeighbors = null;
|
|
1896
|
-
this.highlightedNode = null;
|
|
1897
|
-
this.highlightedLink = null;
|
|
1898
|
-
this.particles = {};
|
|
1899
|
-
if (!this.simulationWorking) {
|
|
1900
|
-
hasHighlight = true;
|
|
1901
|
-
return void requestAnimationFrame(() => this.draw(true));
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
hasHighlight = false;
|
|
1906
|
-
}
|
|
1907
|
-
function draw(recursive = false) {
|
|
2081
|
+
function draw() {
|
|
1908
2082
|
if (!this.context)
|
|
1909
2083
|
return;
|
|
1910
2084
|
if (this.listeners.onDraw) {
|
|
@@ -1925,7 +2099,6 @@ function initDraw() {
|
|
|
1925
2099
|
textRenders.forEach((render) => render());
|
|
1926
2100
|
this.context.restore();
|
|
1927
2101
|
this.listeners.onDrawFinished?.call?.(this);
|
|
1928
|
-
calculateHighlight.bind(this)(recursive);
|
|
1929
2102
|
}
|
|
1930
2103
|
if (this.graphSettings.showDrawTime) {
|
|
1931
2104
|
return setDrawTime(draw.bind(this), this.graphSettings.showDrawTimeEveryTick);
|
|
@@ -1965,6 +2138,7 @@ function initPointer() {
|
|
|
1965
2138
|
areaTransform: this.areaTransform,
|
|
1966
2139
|
mouseEvent: event,
|
|
1967
2140
|
links: this.links,
|
|
2141
|
+
curve: this.linkSettings.curve,
|
|
1968
2142
|
});
|
|
1969
2143
|
if (currentLink?.highlight != undefined)
|
|
1970
2144
|
highlightLink = currentLink?.highlight;
|
|
@@ -1978,38 +2152,7 @@ function initPointer() {
|
|
|
1978
2152
|
else {
|
|
1979
2153
|
this.area.style.cursor = "default";
|
|
1980
2154
|
}
|
|
1981
|
-
|
|
1982
|
-
this.highlightedNode = currentNode;
|
|
1983
|
-
this.highlightedLink = null;
|
|
1984
|
-
this.highlightedNeighbors = new Set(this.highlightedNode?.neighbors ?? []);
|
|
1985
|
-
this.highlightWorking = true;
|
|
1986
|
-
if (!this.simulationWorking)
|
|
1987
|
-
requestAnimationFrame(() => {
|
|
1988
|
-
this.draw();
|
|
1989
|
-
});
|
|
1990
|
-
}
|
|
1991
|
-
else if (currentLink &&
|
|
1992
|
-
highlightLink &&
|
|
1993
|
-
checkType(currentLink.source, lodash.isObject(currentLink.source)) &&
|
|
1994
|
-
checkType(currentLink.target, lodash.isObject(currentLink.target)) &&
|
|
1995
|
-
this.highlightedLink !== currentLink) {
|
|
1996
|
-
this.highlightProgress = 0;
|
|
1997
|
-
this.highlightedLink = currentLink;
|
|
1998
|
-
this.highlightedNode = null;
|
|
1999
|
-
this.highlightedNeighbors = new Set([currentLink.source.id, currentLink.target.id]);
|
|
2000
|
-
this.highlightWorking = true;
|
|
2001
|
-
if (!this.simulationWorking)
|
|
2002
|
-
requestAnimationFrame(() => {
|
|
2003
|
-
this.draw();
|
|
2004
|
-
});
|
|
2005
|
-
}
|
|
2006
|
-
else if (!currentNode && !currentLink && (this.highlightedNode || this.highlightedLink)) {
|
|
2007
|
-
this.highlightWorking = false;
|
|
2008
|
-
if (!this.simulationWorking)
|
|
2009
|
-
requestAnimationFrame(() => {
|
|
2010
|
-
this.draw();
|
|
2011
|
-
});
|
|
2012
|
-
}
|
|
2155
|
+
this.animateHighlight(highlightNode ? currentNode : undefined, highlightLink ? currentLink : undefined);
|
|
2013
2156
|
if (!this.listeners.onMove)
|
|
2014
2157
|
return;
|
|
2015
2158
|
if (!currentNode && !checkHighlightNode)
|
|
@@ -2026,6 +2169,7 @@ function initPointer() {
|
|
|
2026
2169
|
areaTransform: this.areaTransform,
|
|
2027
2170
|
mouseEvent: event,
|
|
2028
2171
|
links: this.links,
|
|
2172
|
+
curve: this.linkSettings.curve,
|
|
2029
2173
|
});
|
|
2030
2174
|
}
|
|
2031
2175
|
if (!currentNode)
|
|
@@ -2050,6 +2194,7 @@ function initPointer() {
|
|
|
2050
2194
|
areaTransform: this.areaTransform,
|
|
2051
2195
|
mouseEvent: event,
|
|
2052
2196
|
links: this.links,
|
|
2197
|
+
curve: this.linkSettings.curve,
|
|
2053
2198
|
});
|
|
2054
2199
|
return void this.listeners.onWheelClick.call(this, event, undefined, currentLink);
|
|
2055
2200
|
}
|
|
@@ -2071,6 +2216,7 @@ function initPointer() {
|
|
|
2071
2216
|
areaTransform: this.areaTransform,
|
|
2072
2217
|
mouseEvent: event,
|
|
2073
2218
|
links: this.links,
|
|
2219
|
+
curve: this.linkSettings.curve,
|
|
2074
2220
|
});
|
|
2075
2221
|
return void this.listeners.onContextMenu.call(this, event, undefined, currentLink);
|
|
2076
2222
|
}
|
|
@@ -2092,6 +2238,7 @@ function initPointer() {
|
|
|
2092
2238
|
areaTransform: this.areaTransform,
|
|
2093
2239
|
mouseEvent: event,
|
|
2094
2240
|
links: this.links,
|
|
2241
|
+
curve: this.linkSettings.curve,
|
|
2095
2242
|
});
|
|
2096
2243
|
return void this.listeners.onDoubleClick.call(this, event, undefined, currentLink);
|
|
2097
2244
|
}
|
|
@@ -2113,6 +2260,7 @@ function initPointer() {
|
|
|
2113
2260
|
areaTransform: this.areaTransform,
|
|
2114
2261
|
mouseEvent: event,
|
|
2115
2262
|
links: this.links,
|
|
2263
|
+
curve: this.linkSettings.curve,
|
|
2116
2264
|
});
|
|
2117
2265
|
return void this.listeners.onClick.call(this, event, undefined, currentLink);
|
|
2118
2266
|
}
|
|
@@ -2158,148 +2306,116 @@ function initResize() {
|
|
|
2158
2306
|
return;
|
|
2159
2307
|
}
|
|
2160
2308
|
this.updateSize();
|
|
2309
|
+
requestAnimationFrame(() => {
|
|
2310
|
+
this.updateSize();
|
|
2311
|
+
});
|
|
2161
2312
|
});
|
|
2162
2313
|
document.addEventListener("scroll", this.updateRect.bind(this), {
|
|
2163
2314
|
capture: true,
|
|
2164
2315
|
passive: true,
|
|
2165
2316
|
signal: abortController.signal,
|
|
2166
2317
|
});
|
|
2318
|
+
document.addEventListener("visibilitychange", () => {
|
|
2319
|
+
if (!document.hidden)
|
|
2320
|
+
this.updateRect();
|
|
2321
|
+
}, { signal: abortController.signal });
|
|
2167
2322
|
observer.observe(this.area);
|
|
2168
2323
|
}
|
|
2169
2324
|
|
|
2170
|
-
function
|
|
2171
|
-
if (!this.
|
|
2172
|
-
this.simulation = d3Force.forceSimulation()
|
|
2173
|
-
.nodes(this.nodes)
|
|
2174
|
-
.force("link", d3Force.forceLink(this.links).id(this.nodeSettings.idGetter.bind(this)))
|
|
2175
|
-
.on("tick", () => {
|
|
2176
|
-
this.draw();
|
|
2177
|
-
})
|
|
2178
|
-
.on("end", () => {
|
|
2179
|
-
this.listeners.onSimulationEnd?.call?.(this);
|
|
2180
|
-
if (this.graphSettings.showDrawTime) {
|
|
2181
|
-
getDrawTime();
|
|
2182
|
-
// eslint-disable-next-line no-console
|
|
2183
|
-
console.log(`nodes: ${this.nodes.length} | links: ${this.links.length}`);
|
|
2184
|
-
resetDrawTime();
|
|
2185
|
-
}
|
|
2186
|
-
});
|
|
2187
|
-
initSimulationForces.call(this);
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
|
-
function initSimulationForces() {
|
|
2191
|
-
if (!this.simulation)
|
|
2192
|
-
return;
|
|
2193
|
-
const linkForce = this.simulation.force("link");
|
|
2194
|
-
if (!linkForce)
|
|
2195
|
-
return;
|
|
2196
|
-
if ((!this.forceSettings.forces || !this.forceSettings.xForce) && this.simulation.force("x")) {
|
|
2197
|
-
this.simulation.force("x", null);
|
|
2198
|
-
}
|
|
2199
|
-
if ((!this.forceSettings.forces || !this.forceSettings.yForce) && this.simulation.force("y")) {
|
|
2200
|
-
this.simulation.force("y", null);
|
|
2201
|
-
}
|
|
2202
|
-
if ((!this.forceSettings.forces || !this.forceSettings.chargeForce) &&
|
|
2203
|
-
this.simulation.force("charge")) {
|
|
2204
|
-
this.simulation.force("charge", null);
|
|
2205
|
-
}
|
|
2206
|
-
if ((!this.forceSettings.forces || !this.forceSettings.centerForce) &&
|
|
2207
|
-
this.simulation.force("center")) {
|
|
2208
|
-
this.simulation.force("center", null);
|
|
2209
|
-
}
|
|
2210
|
-
if (!this.forceSettings.forces || !this.forceSettings.linkForce) {
|
|
2211
|
-
linkForce.distance(0).strength(0).iterations(0);
|
|
2212
|
-
}
|
|
2213
|
-
if (this.forceSettings.forces && this.forceSettings.linkForce) {
|
|
2214
|
-
linkForce
|
|
2215
|
-
.distance(this.forceSettings.linkDistance)
|
|
2216
|
-
.strength(this.forceSettings.linkStrength)
|
|
2217
|
-
.iterations(this.forceSettings.linkIterations);
|
|
2218
|
-
}
|
|
2219
|
-
if (this.forceSettings.forces && this.forceSettings.xForce) {
|
|
2220
|
-
this.simulation.force("x", d3Force.forceX(this.forceSettings.xPosition).strength(this.forceSettings.xStrength));
|
|
2221
|
-
}
|
|
2222
|
-
if (this.forceSettings.forces && this.forceSettings.yForce) {
|
|
2223
|
-
this.simulation.force("y", d3Force.forceY(this.forceSettings.yPosition).strength(this.forceSettings.yStrength));
|
|
2224
|
-
}
|
|
2225
|
-
if (this.forceSettings.forces && this.forceSettings.chargeForce) {
|
|
2226
|
-
this.simulation.force("charge", d3Force.forceManyBody()
|
|
2227
|
-
.strength(this.forceSettings.chargeStrength)
|
|
2228
|
-
.distanceMax(Infinity)
|
|
2229
|
-
.distanceMin(this.forceSettings.chargeDistanceMin));
|
|
2230
|
-
}
|
|
2231
|
-
if (this.forceSettings.forces && this.forceSettings.centerForce) {
|
|
2232
|
-
this.simulation.force("center", d3Force.forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
|
|
2233
|
-
}
|
|
2234
|
-
initCollideForce.call(this, true);
|
|
2235
|
-
}
|
|
2236
|
-
function initCollideForce(forceUpdate) {
|
|
2237
|
-
if (!this.simulation)
|
|
2325
|
+
function updateLinkCache() {
|
|
2326
|
+
if (!this.context)
|
|
2238
2327
|
return;
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
this.
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
this.simulation.force("collide", null);
|
|
2328
|
+
const current = this.areaTransform.k;
|
|
2329
|
+
if (this.linkSettings.smartCache &&
|
|
2330
|
+
this._lastLinkZoomK !== undefined &&
|
|
2331
|
+
this.linkOptionsCache.length > 0) {
|
|
2332
|
+
const corner = this.linkSettings.linkScaleSwitch;
|
|
2333
|
+
const prev = this._lastLinkZoomK;
|
|
2334
|
+
this._lastLinkZoomK = current;
|
|
2335
|
+
if (!((prev <= corner && current >= corner) || (prev >= corner && current <= corner))) {
|
|
2336
|
+
return;
|
|
2249
2337
|
}
|
|
2250
|
-
|
|
2251
|
-
this.
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
|
|
2260
|
-
}
|
|
2261
|
-
return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
|
|
2262
|
-
}
|
|
2263
|
-
case "square": {
|
|
2264
|
-
return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
|
|
2265
|
-
this.forceSettings.collideAdditionalRadius);
|
|
2266
|
-
}
|
|
2267
|
-
case "text": {
|
|
2268
|
-
return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
|
|
2269
|
-
this.forceSettings.collideAdditionalRadius);
|
|
2270
|
-
}
|
|
2271
|
-
default: {
|
|
2272
|
-
if (this.forceSettings.collideRadius) {
|
|
2273
|
-
return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
|
|
2274
|
-
}
|
|
2275
|
-
return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
})
|
|
2279
|
-
.strength(this.forceSettings.collideStrength)
|
|
2280
|
-
.iterations(this.forceSettings.collideIterations));
|
|
2338
|
+
for (let i = 0; i < this.links.length; i++) {
|
|
2339
|
+
const link = this.links[i];
|
|
2340
|
+
const linkOptions = linkIterationExtractor(link, i, this.links, this, this.linkSettings.options ?? {}, linkOptionsGetter);
|
|
2341
|
+
const cache = this.linkOptionsCache[i];
|
|
2342
|
+
if (cache) {
|
|
2343
|
+
cache.color = linkOptions.color;
|
|
2344
|
+
cache.arrowColor = linkOptions.arrowColor;
|
|
2345
|
+
cache.width = linkOptions.width;
|
|
2346
|
+
}
|
|
2281
2347
|
}
|
|
2348
|
+
return;
|
|
2282
2349
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2350
|
+
this._lastLinkZoomK = current;
|
|
2351
|
+
this.linkOptionsCache.length = 0;
|
|
2352
|
+
const groupMap = {};
|
|
2353
|
+
const groupSelfMap = {};
|
|
2287
2354
|
for (let i = 0; i < this.links.length; i++) {
|
|
2288
2355
|
const link = this.links[i];
|
|
2289
2356
|
const { sourceId, targetId } = extractLinkPointIds(link);
|
|
2290
2357
|
const linkOptions = linkIterationExtractor(link, i, this.links, this, this.linkSettings.options ?? {}, linkOptionsGetter);
|
|
2291
|
-
|
|
2292
|
-
this.
|
|
2358
|
+
this.linkOptionsCache[i] = linkOptions;
|
|
2359
|
+
if (this.linkSettings.curve) {
|
|
2360
|
+
const id = `${targetId}${sourceId}`;
|
|
2361
|
+
if (sourceId === targetId) {
|
|
2362
|
+
groupSelfMap[id] ??= 0;
|
|
2363
|
+
link._groupIndex = ++groupSelfMap[id];
|
|
2364
|
+
link._self = true;
|
|
2365
|
+
continue;
|
|
2366
|
+
}
|
|
2367
|
+
const reverseId = `${sourceId}${targetId}`;
|
|
2368
|
+
groupMap[reverseId] ??= 0;
|
|
2369
|
+
groupMap[reverseId]++;
|
|
2370
|
+
groupMap[id] ??= 0;
|
|
2371
|
+
link._groupIndex = indexToPosition(++groupMap[id]);
|
|
2372
|
+
link._self = false;
|
|
2373
|
+
}
|
|
2293
2374
|
}
|
|
2375
|
+
if (this.linkSettings.curve) {
|
|
2376
|
+
for (const link of this.links) {
|
|
2377
|
+
const { sourceId, targetId } = extractLinkPointIds(link);
|
|
2378
|
+
const id = `${targetId}${sourceId}`;
|
|
2379
|
+
link._groupSize = link._self ? (groupSelfMap[id] ?? 1) : (groupMap[id] ?? 1);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
function indexToPosition(n) {
|
|
2384
|
+
if (n === 1)
|
|
2385
|
+
return 0;
|
|
2386
|
+
const index = n >> 1;
|
|
2387
|
+
return n & 1 ? -(index * 2) : index * 2;
|
|
2388
|
+
// return n & 1 ? -(n >> 1) : n >> 1;
|
|
2294
2389
|
}
|
|
2295
2390
|
|
|
2296
|
-
function updateNodeCache(
|
|
2297
|
-
this.nodeOptionsCache = {};
|
|
2391
|
+
function updateNodeCache() {
|
|
2298
2392
|
if (!this.context)
|
|
2299
2393
|
return;
|
|
2394
|
+
const currentZoom = this.areaTransform.k;
|
|
2395
|
+
const options = this.nodeOptionsCache.length === 0;
|
|
2396
|
+
const label = this.cachedNodeLabel.length === 0;
|
|
2397
|
+
let text = this.cachedNodeText.length === 0;
|
|
2398
|
+
if (this.nodeSettings.smartCache &&
|
|
2399
|
+
this._lastNodeZoomK !== undefined &&
|
|
2400
|
+
!options &&
|
|
2401
|
+
!text &&
|
|
2402
|
+
!label) {
|
|
2403
|
+
const thresholds = this.nodeSettings.textScaleSteps;
|
|
2404
|
+
if (thresholds && thresholds.length > 0) {
|
|
2405
|
+
const min = Math.min(this._lastNodeZoomK, currentZoom);
|
|
2406
|
+
const max = Math.max(this._lastNodeZoomK, currentZoom);
|
|
2407
|
+
const crossed = thresholds.some((t) => t >= min && t <= max);
|
|
2408
|
+
this._lastNodeZoomK = currentZoom;
|
|
2409
|
+
if (!crossed)
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
this.cachedNodeText.length = 0;
|
|
2413
|
+
text = true;
|
|
2414
|
+
}
|
|
2415
|
+
this._lastNodeZoomK = currentZoom;
|
|
2300
2416
|
for (let i = 0; i < this.nodes.length; i++) {
|
|
2301
2417
|
const node = this.nodes[i];
|
|
2302
|
-
if (
|
|
2418
|
+
if (options) {
|
|
2303
2419
|
const nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
|
|
2304
2420
|
const radius = nodeOptions.shape === "circle"
|
|
2305
2421
|
? nodeRadiusGetter({
|
|
@@ -2350,11 +2466,19 @@ function updateNodeCache(types) {
|
|
|
2350
2466
|
nodeOptions.height = node.visible === false ? 0 : height;
|
|
2351
2467
|
nodeOptions.radius = node.visible === false ? 0 : radius;
|
|
2352
2468
|
nodeOptions.labelSize = labelSize;
|
|
2353
|
-
this.nodeOptionsCache[
|
|
2469
|
+
this.nodeOptionsCache[i] = nodeOptions;
|
|
2354
2470
|
}
|
|
2355
|
-
|
|
2471
|
+
let nodeOptions = this.nodeOptionsCache[i];
|
|
2472
|
+
if (!nodeOptions)
|
|
2473
|
+
continue;
|
|
2356
2474
|
/** label */
|
|
2357
|
-
if (
|
|
2475
|
+
if (label) {
|
|
2476
|
+
if (!options) {
|
|
2477
|
+
nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
|
|
2478
|
+
const cache = this.nodeOptionsCache[i];
|
|
2479
|
+
if (cache)
|
|
2480
|
+
cache.label = nodeOptions.label;
|
|
2481
|
+
}
|
|
2358
2482
|
/** label in not text shape */
|
|
2359
2483
|
if (nodeOptions.shape !== "text" && nodeOptions.label) {
|
|
2360
2484
|
this.context.font = `${nodeOptions.labelStyle} normal ${nodeOptions.labelWeight} ${nodeOptions.labelSize}px ${nodeOptions.labelFont}`;
|
|
@@ -2362,7 +2486,7 @@ function updateNodeCache(types) {
|
|
|
2362
2486
|
this.context.textAlign = nodeOptions.labelAlign;
|
|
2363
2487
|
if (nodeOptions.labelWidth == undefined ||
|
|
2364
2488
|
this.context.measureText(nodeOptions.label).width <= nodeOptions.labelWidth) {
|
|
2365
|
-
this.cachedNodeLabel[
|
|
2489
|
+
this.cachedNodeLabel[i] = [nodeOptions.label];
|
|
2366
2490
|
}
|
|
2367
2491
|
const { lines } = getTextLines({
|
|
2368
2492
|
context: this.context,
|
|
@@ -2375,7 +2499,7 @@ function updateNodeCache(types) {
|
|
|
2375
2499
|
textStyle: nodeOptions.labelStyle,
|
|
2376
2500
|
textWeight: nodeOptions.labelWeight,
|
|
2377
2501
|
});
|
|
2378
|
-
this.cachedNodeLabel[
|
|
2502
|
+
this.cachedNodeLabel[i] = lines;
|
|
2379
2503
|
/** label in text shape */
|
|
2380
2504
|
}
|
|
2381
2505
|
else if (nodeOptions.shape === "text" && nodeOptions.label) {
|
|
@@ -2399,18 +2523,29 @@ function updateNodeCache(types) {
|
|
|
2399
2523
|
(lines.length - 1) * nodeOptions.labelGap +
|
|
2400
2524
|
nodeOptions.labelYPadding;
|
|
2401
2525
|
nodeOptions.width = maxSize + nodeOptions.labelXPadding;
|
|
2402
|
-
this.cachedNodeLabel[
|
|
2526
|
+
this.cachedNodeLabel[i] = lines;
|
|
2527
|
+
}
|
|
2528
|
+
else {
|
|
2529
|
+
this.cachedNodeLabel[i] = [];
|
|
2403
2530
|
}
|
|
2404
2531
|
}
|
|
2405
2532
|
/** text */
|
|
2406
|
-
if (
|
|
2533
|
+
if (text) {
|
|
2534
|
+
if (!options) {
|
|
2535
|
+
nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
|
|
2536
|
+
const cache = this.nodeOptionsCache[i];
|
|
2537
|
+
if (cache) {
|
|
2538
|
+
cache.text = nodeOptions.text;
|
|
2539
|
+
cache.textVisible = nodeOptions.textVisible;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
if (!nodeOptions.text) {
|
|
2543
|
+
this.cachedNodeText[i] = [];
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2407
2546
|
this.context.font = `${nodeOptions.textStyle} normal ${nodeOptions.textWeight} ${nodeOptions.textSize}px ${nodeOptions.textFont}`;
|
|
2408
2547
|
this.context.fillStyle = nodeOptions.textColor;
|
|
2409
2548
|
this.context.textAlign = nodeOptions.textAlign;
|
|
2410
|
-
if (nodeOptions.textWidth == undefined ||
|
|
2411
|
-
this.context.measureText(nodeOptions.text).width <= nodeOptions.textWidth) {
|
|
2412
|
-
this.cachedNodeText[node.id] = [nodeOptions.text];
|
|
2413
|
-
}
|
|
2414
2549
|
const { lines } = getTextLines({
|
|
2415
2550
|
context: this.context,
|
|
2416
2551
|
maxWidth: nodeOptions.textWidth,
|
|
@@ -2422,49 +2557,230 @@ function updateNodeCache(types) {
|
|
|
2422
2557
|
textStyle: nodeOptions.textStyle,
|
|
2423
2558
|
textWeight: nodeOptions.textWeight,
|
|
2424
2559
|
});
|
|
2425
|
-
this.cachedNodeText[
|
|
2560
|
+
this.cachedNodeText[i] = lines;
|
|
2426
2561
|
}
|
|
2427
2562
|
}
|
|
2428
2563
|
}
|
|
2429
2564
|
|
|
2565
|
+
const DEFAULT_ZOOM_EXTENT = [0.3, 10];
|
|
2566
|
+
const DEFAULT_TRANSLATE_EXTENT_COEFFICIENT = 8;
|
|
2567
|
+
const DEFAULT_MARGIN = 0.25;
|
|
2430
2568
|
function initZoom(currentZoom) {
|
|
2431
2569
|
if (!this.area)
|
|
2432
2570
|
throw new Error("bad init data");
|
|
2571
|
+
const bounds = computeGraphBounds(this.nodes);
|
|
2572
|
+
const zoomExtent = resolveZoomExtent(this.graphSettings.zoomExtent, this.width, this.height, this.graphSettings.zoomExtentMargin ?? DEFAULT_MARGIN, bounds);
|
|
2573
|
+
const translateExtent = resolveTranslateExtent(this.graphSettings.translateExtent, this.graphSettings.translateExtentCoefficient, this.graphSettings.translateExtentOverlap, this.width, this.height, zoomExtent, bounds);
|
|
2574
|
+
this._translateExtent = translateExtent;
|
|
2433
2575
|
const zoomInstance = d3Zoom.zoom()
|
|
2434
|
-
.scaleExtent(
|
|
2576
|
+
.scaleExtent(zoomExtent)
|
|
2435
2577
|
.on("zoom", (event) => {
|
|
2578
|
+
if (this._zoomAnimating)
|
|
2579
|
+
return;
|
|
2436
2580
|
this.listeners.onZoom?.call?.(this, event);
|
|
2437
2581
|
const oldTransform = this.areaTransform;
|
|
2438
2582
|
this.areaTransform = event.transform;
|
|
2439
2583
|
if (this.areaTransform.k !== oldTransform.k) {
|
|
2440
2584
|
updateLinkCache.call(this);
|
|
2441
|
-
updateNodeCache.call(this
|
|
2585
|
+
updateNodeCache.call(this);
|
|
2442
2586
|
}
|
|
2443
|
-
|
|
2444
|
-
requestAnimationFrame(() => this.draw());
|
|
2587
|
+
this.tick();
|
|
2445
2588
|
});
|
|
2446
|
-
|
|
2447
|
-
const coefficient = this.graphSettings.translateExtentCoefficient;
|
|
2448
|
-
const [coefficientX, coefficientY] = jsHelpers.isArray(coefficient)
|
|
2449
|
-
? coefficient
|
|
2450
|
-
: [coefficient, coefficient];
|
|
2451
|
-
const [[minX = -this.width * coefficientX, minY = -this.height * coefficientY], [maxX = this.width * coefficientX, maxY = this.height * coefficientY],] = this.graphSettings.translateExtent;
|
|
2452
|
-
zoomInstance.translateExtent([
|
|
2453
|
-
[minX, minY],
|
|
2454
|
-
[maxX, maxY],
|
|
2455
|
-
]);
|
|
2456
|
-
}
|
|
2589
|
+
zoomInstance.translateExtent(translateExtent);
|
|
2457
2590
|
d3Selection.select(this.area).call(zoomInstance).on("dblclick.zoom", null);
|
|
2458
2591
|
const zoomInitial = currentZoom ?? this.graphSettings.zoomInitial;
|
|
2459
|
-
this.areaTransform = new d3Zoom.ZoomTransform(zoomInitial?.k ?? 1, zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
|
|
2592
|
+
this.areaTransform = new d3Zoom.ZoomTransform(Math.max(zoomInitial?.k ?? 1, zoomExtent[0]), zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
|
|
2460
2593
|
d3Zoom.zoom().transform(d3Selection.select(this.area), this.areaTransform);
|
|
2461
2594
|
}
|
|
2595
|
+
function resolveZoomExtent(userExtent, viewWidth, viewHeight, margin, bounds) {
|
|
2596
|
+
if (userExtent &&
|
|
2597
|
+
userExtent.length === 2 &&
|
|
2598
|
+
userExtent[0] != undefined &&
|
|
2599
|
+
userExtent[1] != undefined) {
|
|
2600
|
+
return userExtent;
|
|
2601
|
+
}
|
|
2602
|
+
if (!bounds)
|
|
2603
|
+
return DEFAULT_ZOOM_EXTENT;
|
|
2604
|
+
const graphWidth = bounds.maxX - bounds.minX;
|
|
2605
|
+
const graphHeight = bounds.maxY - bounds.minY;
|
|
2606
|
+
if (graphWidth === 0 || graphHeight === 0)
|
|
2607
|
+
return DEFAULT_ZOOM_EXTENT;
|
|
2608
|
+
const scaleToFit = (1 - margin) / Math.max(graphWidth / viewWidth, graphHeight / viewHeight);
|
|
2609
|
+
return [scaleToFit, DEFAULT_ZOOM_EXTENT[1]];
|
|
2610
|
+
}
|
|
2611
|
+
function resolveTranslateExtent(userExtent, userCoefficient, overlap, viewWidth, viewHeight, zoomExtent, bounds) {
|
|
2612
|
+
if (userExtent && userExtent.length >= 2 && jsHelpers.isArray(userExtent[0]) && jsHelpers.isArray(userExtent[1])) {
|
|
2613
|
+
return [
|
|
2614
|
+
[
|
|
2615
|
+
userExtent[0][0] ?? -viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2616
|
+
userExtent[0][1] ?? -viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2617
|
+
],
|
|
2618
|
+
[
|
|
2619
|
+
userExtent[1][0] ?? viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2620
|
+
userExtent[1][1] ?? viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2621
|
+
],
|
|
2622
|
+
];
|
|
2623
|
+
}
|
|
2624
|
+
if (userCoefficient) {
|
|
2625
|
+
const [cx, cy] = jsHelpers.isArray(userCoefficient)
|
|
2626
|
+
? userCoefficient
|
|
2627
|
+
: [userCoefficient, userCoefficient];
|
|
2628
|
+
return [
|
|
2629
|
+
[-viewWidth * cx, -viewHeight * cy],
|
|
2630
|
+
[viewWidth * cx, viewHeight * cy],
|
|
2631
|
+
];
|
|
2632
|
+
}
|
|
2633
|
+
if (!bounds) {
|
|
2634
|
+
return [
|
|
2635
|
+
[
|
|
2636
|
+
-viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2637
|
+
-viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2638
|
+
],
|
|
2639
|
+
[
|
|
2640
|
+
viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2641
|
+
viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2642
|
+
],
|
|
2643
|
+
];
|
|
2644
|
+
}
|
|
2645
|
+
const minZoom = zoomExtent[0];
|
|
2646
|
+
const zoomViewWidth = viewWidth / minZoom;
|
|
2647
|
+
const zoomViewHeight = viewHeight / minZoom;
|
|
2648
|
+
const graphWidth = bounds.maxX - bounds.minX;
|
|
2649
|
+
const graphHeight = bounds.maxY - bounds.minY;
|
|
2650
|
+
const minX = bounds.minX + overlap * graphWidth - zoomViewWidth;
|
|
2651
|
+
const maxX = bounds.maxX - overlap * graphWidth + zoomViewWidth;
|
|
2652
|
+
const minY = bounds.minY + overlap * graphHeight - zoomViewHeight;
|
|
2653
|
+
const maxY = bounds.maxY - overlap * graphHeight + zoomViewHeight;
|
|
2654
|
+
return [
|
|
2655
|
+
[minX, minY],
|
|
2656
|
+
[maxX, maxY],
|
|
2657
|
+
];
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
function initSimulation() {
|
|
2661
|
+
if (!this.simulation) {
|
|
2662
|
+
this.simulation = d3Force.forceSimulation()
|
|
2663
|
+
.nodes(this.nodes)
|
|
2664
|
+
.force("link", d3Force.forceLink(this.links).id(this.nodeSettings.idGetter.bind(this)))
|
|
2665
|
+
.on("tick", () => {
|
|
2666
|
+
this.draw();
|
|
2667
|
+
})
|
|
2668
|
+
.on("end", () => {
|
|
2669
|
+
this.listeners.onSimulationEnd?.call?.(this);
|
|
2670
|
+
if (this.area) {
|
|
2671
|
+
initZoom.call(this, this.areaTransform);
|
|
2672
|
+
}
|
|
2673
|
+
if (this.graphSettings.showDrawTime) {
|
|
2674
|
+
getDrawTime();
|
|
2675
|
+
// eslint-disable-next-line no-console
|
|
2676
|
+
console.log(`nodes: ${this.nodes.length} | links: ${this.links.length}`);
|
|
2677
|
+
resetDrawTime();
|
|
2678
|
+
}
|
|
2679
|
+
})
|
|
2680
|
+
.stop();
|
|
2681
|
+
initSimulationForces.call(this);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
function initSimulationForces() {
|
|
2685
|
+
if (!this.simulation)
|
|
2686
|
+
return;
|
|
2687
|
+
const linkForce = this.simulation.force("link");
|
|
2688
|
+
if (!linkForce)
|
|
2689
|
+
return;
|
|
2690
|
+
if ((!this.forceSettings.forces || !this.forceSettings.xForce) && this.simulation.force("x")) {
|
|
2691
|
+
this.simulation.force("x", null);
|
|
2692
|
+
}
|
|
2693
|
+
if ((!this.forceSettings.forces || !this.forceSettings.yForce) && this.simulation.force("y")) {
|
|
2694
|
+
this.simulation.force("y", null);
|
|
2695
|
+
}
|
|
2696
|
+
if ((!this.forceSettings.forces || !this.forceSettings.chargeForce) &&
|
|
2697
|
+
this.simulation.force("charge")) {
|
|
2698
|
+
this.simulation.force("charge", null);
|
|
2699
|
+
}
|
|
2700
|
+
if ((!this.forceSettings.forces || !this.forceSettings.centerForce) &&
|
|
2701
|
+
this.simulation.force("center")) {
|
|
2702
|
+
this.simulation.force("center", null);
|
|
2703
|
+
}
|
|
2704
|
+
if (!this.forceSettings.forces || !this.forceSettings.linkForce) {
|
|
2705
|
+
linkForce.distance(0).strength(0).iterations(0);
|
|
2706
|
+
}
|
|
2707
|
+
if (this.forceSettings.forces && this.forceSettings.linkForce) {
|
|
2708
|
+
linkForce
|
|
2709
|
+
.distance(this.forceSettings.linkDistance)
|
|
2710
|
+
.strength(this.forceSettings.linkStrength)
|
|
2711
|
+
.iterations(this.forceSettings.linkIterations);
|
|
2712
|
+
}
|
|
2713
|
+
if (this.forceSettings.forces && this.forceSettings.xForce) {
|
|
2714
|
+
this.simulation.force("x", d3Force.forceX(this.forceSettings.xPosition).strength(this.forceSettings.xStrength));
|
|
2715
|
+
}
|
|
2716
|
+
if (this.forceSettings.forces && this.forceSettings.yForce) {
|
|
2717
|
+
this.simulation.force("y", d3Force.forceY(this.forceSettings.yPosition).strength(this.forceSettings.yStrength));
|
|
2718
|
+
}
|
|
2719
|
+
if (this.forceSettings.forces && this.forceSettings.chargeForce) {
|
|
2720
|
+
this.simulation.force("charge", d3Force.forceManyBody()
|
|
2721
|
+
.strength(this.forceSettings.chargeStrength)
|
|
2722
|
+
.distanceMax(Infinity)
|
|
2723
|
+
.distanceMin(this.forceSettings.chargeDistanceMin));
|
|
2724
|
+
}
|
|
2725
|
+
if (this.forceSettings.forces && this.forceSettings.centerForce) {
|
|
2726
|
+
this.simulation.force("center", d3Force.forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
|
|
2727
|
+
}
|
|
2728
|
+
initCollideForce.call(this, true);
|
|
2729
|
+
}
|
|
2730
|
+
function initCollideForce(forceUpdate) {
|
|
2731
|
+
if (!this.simulation)
|
|
2732
|
+
return;
|
|
2733
|
+
if ((!this.forceSettings.collideForce || !this.forceSettings.forces) &&
|
|
2734
|
+
this.simulation.force("collide")) {
|
|
2735
|
+
this.simulation.force("collide", null);
|
|
2736
|
+
}
|
|
2737
|
+
if (this.forceSettings.forces && this.forceSettings.collideForce) {
|
|
2738
|
+
const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
|
|
2739
|
+
const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
|
|
2740
|
+
const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
|
|
2741
|
+
if (isMaxCollideNodes && isMaxCollideLinks) {
|
|
2742
|
+
this.simulation.force("collide", null);
|
|
2743
|
+
}
|
|
2744
|
+
else if (!this.simulation.force("collide") || forceUpdate) {
|
|
2745
|
+
this.simulation.force("collide", d3Force.forceCollide()
|
|
2746
|
+
.radius((node, index) => {
|
|
2747
|
+
const nodeOptions = this.nodeOptionsCache[index];
|
|
2748
|
+
if (!nodeOptions)
|
|
2749
|
+
return 0;
|
|
2750
|
+
switch (nodeOptions.shape) {
|
|
2751
|
+
case "circle": {
|
|
2752
|
+
if (this.forceSettings.collideRadius) {
|
|
2753
|
+
return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
|
|
2754
|
+
}
|
|
2755
|
+
return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
|
|
2756
|
+
}
|
|
2757
|
+
case "square": {
|
|
2758
|
+
return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
|
|
2759
|
+
this.forceSettings.collideAdditionalRadius);
|
|
2760
|
+
}
|
|
2761
|
+
case "text": {
|
|
2762
|
+
return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
|
|
2763
|
+
this.forceSettings.collideAdditionalRadius);
|
|
2764
|
+
}
|
|
2765
|
+
default: {
|
|
2766
|
+
if (this.forceSettings.collideRadius) {
|
|
2767
|
+
return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
|
|
2768
|
+
}
|
|
2769
|
+
return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
})
|
|
2773
|
+
.strength(this.forceSettings.collideStrength)
|
|
2774
|
+
.iterations(this.forceSettings.collideIterations));
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2462
2778
|
|
|
2463
2779
|
class GraphCanvas {
|
|
2464
2780
|
/** initial data */
|
|
2465
2781
|
nodes;
|
|
2466
2782
|
links;
|
|
2467
|
-
particles =
|
|
2783
|
+
particles = [];
|
|
2468
2784
|
width;
|
|
2469
2785
|
height;
|
|
2470
2786
|
root;
|
|
@@ -2481,19 +2797,25 @@ class GraphCanvas {
|
|
|
2481
2797
|
context;
|
|
2482
2798
|
simulation;
|
|
2483
2799
|
areaTransform = d3Zoom.zoomIdentity;
|
|
2800
|
+
_translateExtent;
|
|
2484
2801
|
areaRect;
|
|
2485
2802
|
draw;
|
|
2486
2803
|
eventAbortController;
|
|
2487
|
-
cachedNodeText =
|
|
2488
|
-
cachedNodeLabel =
|
|
2489
|
-
linkOptionsCache =
|
|
2490
|
-
nodeOptionsCache =
|
|
2804
|
+
cachedNodeText = [];
|
|
2805
|
+
cachedNodeLabel = [];
|
|
2806
|
+
linkOptionsCache = [];
|
|
2807
|
+
nodeOptionsCache = [];
|
|
2491
2808
|
isDragging = false;
|
|
2492
2809
|
highlightedNode = null;
|
|
2493
2810
|
highlightedLink = null;
|
|
2494
2811
|
highlightedNeighbors = null;
|
|
2495
2812
|
highlightProgress = 1;
|
|
2496
|
-
|
|
2813
|
+
highlightStart = null;
|
|
2814
|
+
highlightPositive = false;
|
|
2815
|
+
highlightController;
|
|
2816
|
+
_lastNodeZoomK;
|
|
2817
|
+
_lastLinkZoomK;
|
|
2818
|
+
_zoomAnimating = false;
|
|
2497
2819
|
get simulationWorking() {
|
|
2498
2820
|
const simulationAlpha = this.simulation?.alpha?.() ?? 0;
|
|
2499
2821
|
const simulationAlphaMin = this.simulation?.alphaMin?.() ?? 0;
|
|
@@ -2528,13 +2850,148 @@ class GraphCanvas {
|
|
|
2528
2850
|
nodes: this.nodes,
|
|
2529
2851
|
};
|
|
2530
2852
|
}
|
|
2853
|
+
fitToView(margin = this.graphSettings.zoomToFitMargin, duration = this.graphSettings.zoomAnimationDuration) {
|
|
2854
|
+
const area = this.area;
|
|
2855
|
+
if (!area)
|
|
2856
|
+
return;
|
|
2857
|
+
const bounds = computeGraphBounds(this.nodes);
|
|
2858
|
+
if (!bounds)
|
|
2859
|
+
return;
|
|
2860
|
+
const graphWidth = bounds.maxX - bounds.minX;
|
|
2861
|
+
const graphHeight = bounds.maxY - bounds.minY;
|
|
2862
|
+
if (graphWidth === 0 || graphHeight === 0)
|
|
2863
|
+
return;
|
|
2864
|
+
const graphCenterX = bounds.minX + graphWidth / 2;
|
|
2865
|
+
const graphCenterY = bounds.minY + graphHeight / 2;
|
|
2866
|
+
const scale = (1 - margin) / Math.max(graphWidth / this.width, graphHeight / this.height);
|
|
2867
|
+
const clampedScale = Math.min(scale, this.graphSettings.zoomExtent?.[1] ?? scale);
|
|
2868
|
+
const target = d3Zoom.zoomIdentity
|
|
2869
|
+
.translate(this.width / 2, this.height / 2)
|
|
2870
|
+
.scale(clampedScale)
|
|
2871
|
+
.translate(-graphCenterX, -graphCenterY);
|
|
2872
|
+
if (!this.graphSettings.zoomAnimation) {
|
|
2873
|
+
this.areaTransform = target;
|
|
2874
|
+
d3Zoom.zoom().transform(d3Selection.select(area), target);
|
|
2875
|
+
this.clearCache(true);
|
|
2876
|
+
this.tick();
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
this.animateZoom(area, target, this.areaTransform, duration);
|
|
2880
|
+
}
|
|
2881
|
+
focusOnNode(nodeId, scale = this.graphSettings.zoomToNodeScale, duration = this.graphSettings.zoomAnimationDuration) {
|
|
2882
|
+
const area = this.area;
|
|
2883
|
+
if (!area)
|
|
2884
|
+
return;
|
|
2885
|
+
const node = this.nodes.find((n) => n.id === nodeId);
|
|
2886
|
+
if (!node)
|
|
2887
|
+
return;
|
|
2888
|
+
if (node.x == undefined || node.y == undefined)
|
|
2889
|
+
return;
|
|
2890
|
+
const target = d3Zoom.zoomIdentity
|
|
2891
|
+
.translate(this.width / 2, this.height / 2)
|
|
2892
|
+
.scale(scale)
|
|
2893
|
+
.translate(-node.x, -node.y);
|
|
2894
|
+
if (!this.graphSettings.zoomAnimation) {
|
|
2895
|
+
this.areaTransform = target;
|
|
2896
|
+
d3Zoom.zoom().transform(d3Selection.select(area), target);
|
|
2897
|
+
this.clearCache(true);
|
|
2898
|
+
this.tick();
|
|
2899
|
+
return;
|
|
2900
|
+
}
|
|
2901
|
+
this.animateZoom(area, target, this.areaTransform, duration);
|
|
2902
|
+
}
|
|
2903
|
+
animateHighlight(node, link, baseDuration = this.highlightSettings.highlightDuration) {
|
|
2904
|
+
let positive = true;
|
|
2905
|
+
if (node && (this.highlightedNode !== node || !this.highlightPositive)) {
|
|
2906
|
+
this.highlightedNode = node;
|
|
2907
|
+
this.highlightedNeighbors = new Set(node.neighbors);
|
|
2908
|
+
this.particles = [];
|
|
2909
|
+
this.highlightedLink = null;
|
|
2910
|
+
this.highlightStart = performance.now();
|
|
2911
|
+
}
|
|
2912
|
+
else if (link && (this.highlightedLink !== link || !this.highlightPositive)) {
|
|
2913
|
+
const { sourceId, targetId } = extractLinkPointIds(link);
|
|
2914
|
+
this.highlightProgress = 0;
|
|
2915
|
+
this.highlightedLink = link;
|
|
2916
|
+
this.highlightedNeighbors = new Set([sourceId, targetId]);
|
|
2917
|
+
this.particles = [];
|
|
2918
|
+
this.highlightedNode = null;
|
|
2919
|
+
this.highlightStart = performance.now();
|
|
2920
|
+
}
|
|
2921
|
+
else if (!node && !link && this.highlightPositive) {
|
|
2922
|
+
positive = false;
|
|
2923
|
+
}
|
|
2924
|
+
else {
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
if (this.highlightController) {
|
|
2928
|
+
this.highlightController.abort();
|
|
2929
|
+
}
|
|
2930
|
+
const controller = new AbortController();
|
|
2931
|
+
this.highlightPositive = positive;
|
|
2932
|
+
this.highlightController = controller;
|
|
2933
|
+
const startTime = performance.now();
|
|
2934
|
+
const startProgress = this.highlightProgress;
|
|
2935
|
+
const targetProgress = positive ? 1 : 0;
|
|
2936
|
+
const delta = targetProgress - startProgress;
|
|
2937
|
+
const duration = baseDuration * Math.abs(delta);
|
|
2938
|
+
const animate = () => {
|
|
2939
|
+
if (controller.signal.aborted)
|
|
2940
|
+
return;
|
|
2941
|
+
const elapsed = performance.now() - startTime;
|
|
2942
|
+
const t = Math.min(elapsed / duration, 1);
|
|
2943
|
+
const current = startProgress + delta * t;
|
|
2944
|
+
const eased = current < 0.5 ? 4 * current * current * current : 1 - (-2 * current + 2) ** 3 / 2;
|
|
2945
|
+
this.highlightProgress = eased;
|
|
2946
|
+
if (t < 1 || positive) {
|
|
2947
|
+
requestAnimationFrame(animate);
|
|
2948
|
+
this.draw();
|
|
2949
|
+
}
|
|
2950
|
+
else {
|
|
2951
|
+
this.highlightedNode = null;
|
|
2952
|
+
this.highlightedLink = null;
|
|
2953
|
+
this.highlightedNeighbors = null;
|
|
2954
|
+
this.highlightStart = null;
|
|
2955
|
+
this.particles = [];
|
|
2956
|
+
this.tick();
|
|
2957
|
+
}
|
|
2958
|
+
};
|
|
2959
|
+
requestAnimationFrame(animate);
|
|
2960
|
+
}
|
|
2961
|
+
animateZoom(area, target, start, duration) {
|
|
2962
|
+
this._zoomAnimating = true;
|
|
2963
|
+
const startTime = performance.now();
|
|
2964
|
+
const animate = () => {
|
|
2965
|
+
const elapsed = performance.now() - startTime;
|
|
2966
|
+
const t = Math.min(elapsed / duration, 1);
|
|
2967
|
+
const eased = t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
|
|
2968
|
+
const x = start.x + (target.x - start.x) * eased;
|
|
2969
|
+
const y = start.y + (target.y - start.y) * eased;
|
|
2970
|
+
const k = start.k + (target.k - start.k) * eased;
|
|
2971
|
+
this.areaTransform = new d3Zoom.ZoomTransform(k, x, y);
|
|
2972
|
+
d3Zoom.zoom().transform(d3Selection.select(area), this.areaTransform);
|
|
2973
|
+
updateLinkCache.call(this);
|
|
2974
|
+
updateNodeCache.call(this);
|
|
2975
|
+
this.tick();
|
|
2976
|
+
if (t < 1) {
|
|
2977
|
+
requestAnimationFrame(animate);
|
|
2978
|
+
}
|
|
2979
|
+
else {
|
|
2980
|
+
this._zoomAnimating = false;
|
|
2981
|
+
this.clearCache(true);
|
|
2982
|
+
this.tick();
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
requestAnimationFrame(animate);
|
|
2986
|
+
}
|
|
2531
2987
|
changeData(options, alpha = 0.5, clearCache = true) {
|
|
2532
2988
|
if (options.links != undefined)
|
|
2533
2989
|
this.links = options.links;
|
|
2534
2990
|
if (options.nodes != undefined)
|
|
2535
2991
|
this.nodes = options.nodes;
|
|
2536
|
-
if (options.nodes != undefined || options.links != undefined)
|
|
2992
|
+
if (options.nodes != undefined || options.links != undefined) {
|
|
2537
2993
|
this.updateData(alpha, clearCache);
|
|
2994
|
+
}
|
|
2538
2995
|
}
|
|
2539
2996
|
changeSettings(options, clearCache = true) {
|
|
2540
2997
|
if (options.graphSettings) {
|
|
@@ -2609,31 +3066,76 @@ class GraphCanvas {
|
|
|
2609
3066
|
}
|
|
2610
3067
|
clearCache(keys) {
|
|
2611
3068
|
if (keys === true) {
|
|
2612
|
-
|
|
2613
|
-
|
|
3069
|
+
this.nodeOptionsCache.length = 0;
|
|
3070
|
+
this.linkOptionsCache.length = 0;
|
|
3071
|
+
this.cachedNodeLabel.length = 0;
|
|
3072
|
+
this.cachedNodeText.length = 0;
|
|
2614
3073
|
}
|
|
2615
3074
|
else if (jsHelpers.isArray(keys)) {
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
3075
|
+
for (const key of keys) {
|
|
3076
|
+
switch (key) {
|
|
3077
|
+
case GRAPH_CACHE_TYPE.NodeText: {
|
|
3078
|
+
this.cachedNodeText.length = 0;
|
|
3079
|
+
break;
|
|
3080
|
+
}
|
|
3081
|
+
case GRAPH_CACHE_TYPE.NodeLabel: {
|
|
3082
|
+
this.cachedNodeLabel.length = 0;
|
|
3083
|
+
break;
|
|
3084
|
+
}
|
|
3085
|
+
case GRAPH_CACHE_TYPE.NodeOptions: {
|
|
3086
|
+
this.nodeOptionsCache.length = 0;
|
|
3087
|
+
break;
|
|
3088
|
+
}
|
|
3089
|
+
case GRAPH_CACHE_TYPE.LinkOptions: {
|
|
3090
|
+
this.linkOptionsCache.length = 0;
|
|
3091
|
+
break;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
2627
3094
|
}
|
|
2628
3095
|
}
|
|
3096
|
+
updateNodeCache.call(this);
|
|
3097
|
+
updateLinkCache.call(this);
|
|
2629
3098
|
}
|
|
2630
3099
|
tick() {
|
|
2631
|
-
if (!this.simulationWorking
|
|
3100
|
+
if (!this.simulationWorking)
|
|
2632
3101
|
this.draw();
|
|
2633
3102
|
}
|
|
2634
|
-
restart(alpha) {
|
|
2635
|
-
if (this.simulation)
|
|
3103
|
+
restart(alpha, options) {
|
|
3104
|
+
if (!this.simulation)
|
|
3105
|
+
return;
|
|
3106
|
+
const settings = {
|
|
3107
|
+
precompute: options?.precompute ?? this.forceSettings.precompute,
|
|
3108
|
+
precomputeMaxTimeMs: options?.precomputeMaxTimeMs ?? this.forceSettings?.precomputeMaxTimeMs,
|
|
3109
|
+
precomputeMaxTicks: options?.precomputeMaxTicks ?? this.forceSettings?.precomputeMaxTicks ?? 300,
|
|
3110
|
+
precomputeDisableForcesAfter: options?.precomputeDisableForcesAfter ??
|
|
3111
|
+
this.forceSettings?.precomputeDisableForcesAfter ??
|
|
3112
|
+
false,
|
|
3113
|
+
};
|
|
3114
|
+
if (!settings.precompute) {
|
|
2636
3115
|
this.simulation.alpha(alpha ?? 1).restart();
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
if (settings.precomputeDisableForcesAfter) {
|
|
3119
|
+
this.forceSettings = forceSettingsGetter({ forces: true }, this.forceSettings);
|
|
3120
|
+
}
|
|
3121
|
+
initSimulationForces.call(this);
|
|
3122
|
+
this.simulation.stop();
|
|
3123
|
+
this.simulation.alpha(alpha ?? 1);
|
|
3124
|
+
const startTime = performance.now();
|
|
3125
|
+
let ticks = 0;
|
|
3126
|
+
while (performance.now() - startTime < settings.precomputeMaxTimeMs &&
|
|
3127
|
+
ticks < settings.precomputeMaxTicks) {
|
|
3128
|
+
this.simulation.tick(1);
|
|
3129
|
+
ticks++;
|
|
3130
|
+
if (this.simulation.alpha() <= this.simulation.alphaMin())
|
|
3131
|
+
break;
|
|
3132
|
+
}
|
|
3133
|
+
if (settings.precomputeDisableForcesAfter) {
|
|
3134
|
+
this.forceSettings = forceSettingsGetter({ forces: false }, this.forceSettings);
|
|
3135
|
+
}
|
|
3136
|
+
initSimulationForces.call(this);
|
|
3137
|
+
this.simulation.restart();
|
|
3138
|
+
this.tick();
|
|
2637
3139
|
}
|
|
2638
3140
|
start() {
|
|
2639
3141
|
if (this.simulation)
|
|
@@ -2670,8 +3172,7 @@ class GraphCanvas {
|
|
|
2670
3172
|
updateSimulation() {
|
|
2671
3173
|
if (this.simulation) {
|
|
2672
3174
|
initSimulationForces.call(this);
|
|
2673
|
-
this.
|
|
2674
|
-
this.simulation.restart();
|
|
3175
|
+
this.restart(1);
|
|
2675
3176
|
}
|
|
2676
3177
|
}
|
|
2677
3178
|
clearState() {
|
|
@@ -2680,7 +3181,8 @@ class GraphCanvas {
|
|
|
2680
3181
|
this.highlightedLink = null;
|
|
2681
3182
|
this.highlightedNeighbors = null;
|
|
2682
3183
|
this.highlightProgress = 0;
|
|
2683
|
-
this.
|
|
3184
|
+
this.highlightStart = null;
|
|
3185
|
+
this.highlightPositive = false;
|
|
2684
3186
|
}
|
|
2685
3187
|
updateData(alpha = 0.5, clearCache = true) {
|
|
2686
3188
|
if (clearCache) {
|
|
@@ -2688,9 +3190,7 @@ class GraphCanvas {
|
|
|
2688
3190
|
}
|
|
2689
3191
|
if (this.simulation) {
|
|
2690
3192
|
initCollideForce.call(this, false);
|
|
2691
|
-
this.simulation
|
|
2692
|
-
.nodes(this.nodes)
|
|
2693
|
-
.force("link", d3Force.forceLink(this.links)
|
|
3193
|
+
this.simulation.nodes(this.nodes).force("link", d3Force.forceLink(this.links)
|
|
2694
3194
|
.id(this.nodeSettings.idGetter.bind(this))
|
|
2695
3195
|
.distance(this.forceSettings.forces && this.forceSettings.linkForce
|
|
2696
3196
|
? this.forceSettings.linkDistance
|
|
@@ -2700,16 +3200,17 @@ class GraphCanvas {
|
|
|
2700
3200
|
: 0)
|
|
2701
3201
|
.iterations(this.forceSettings.forces && this.forceSettings.linkForce
|
|
2702
3202
|
? this.forceSettings.linkIterations
|
|
2703
|
-
: 0))
|
|
2704
|
-
|
|
2705
|
-
|
|
3203
|
+
: 0));
|
|
3204
|
+
this.restart(alpha);
|
|
3205
|
+
initZoom.call(this, this.areaTransform);
|
|
2706
3206
|
}
|
|
2707
3207
|
}
|
|
2708
3208
|
init() {
|
|
2709
3209
|
initArea.call(this);
|
|
2710
|
-
updateNodeCache.call(this
|
|
3210
|
+
updateNodeCache.call(this);
|
|
2711
3211
|
updateLinkCache.call(this);
|
|
2712
3212
|
initSimulation.call(this);
|
|
3213
|
+
this.restart(1);
|
|
2713
3214
|
initDnd.call(this);
|
|
2714
3215
|
initZoom.call(this);
|
|
2715
3216
|
initResize.call(this);
|
|
@@ -2875,22 +3376,13 @@ const HIGHLIGHT_COMMON_CONTROLS = [
|
|
|
2875
3376
|
label: "Расширение границы связи для курсора",
|
|
2876
3377
|
},
|
|
2877
3378
|
{
|
|
2878
|
-
id: "
|
|
2879
|
-
initialValue: HIGHLIGHT_SETTINGS.
|
|
2880
|
-
max:
|
|
2881
|
-
min:
|
|
2882
|
-
step: 1,
|
|
2883
|
-
type: "range",
|
|
2884
|
-
label: "Скорость отмены анимации в кадрах",
|
|
2885
|
-
},
|
|
2886
|
-
{
|
|
2887
|
-
id: "highlightUpFrames",
|
|
2888
|
-
initialValue: HIGHLIGHT_SETTINGS.highlightUpFrames,
|
|
2889
|
-
max: 60,
|
|
2890
|
-
min: 1,
|
|
3379
|
+
id: "highlightDuration",
|
|
3380
|
+
initialValue: HIGHLIGHT_SETTINGS.highlightDuration,
|
|
3381
|
+
max: 1000,
|
|
3382
|
+
min: 10,
|
|
2891
3383
|
step: 1,
|
|
2892
3384
|
type: "range",
|
|
2893
|
-
label: "Скорость
|
|
3385
|
+
label: "Скорость анимации",
|
|
2894
3386
|
},
|
|
2895
3387
|
];
|
|
2896
3388
|
const HIGHLIGHT_BY_NODE_FOR_NODE_CONTROLS = [
|
|
@@ -3363,19 +3855,22 @@ const LINK_SETTINGS_CONTROLS = [
|
|
|
3363
3855
|
initialValue: LINK_SETTINGS.particles,
|
|
3364
3856
|
},
|
|
3365
3857
|
{
|
|
3366
|
-
id: "
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3858
|
+
id: "particleSpeedByDistance",
|
|
3859
|
+
initialValue: LINK_SETTINGS.particleSpeedByDistance,
|
|
3860
|
+
max: 500,
|
|
3861
|
+
min: 1,
|
|
3862
|
+
step: 1,
|
|
3863
|
+
type: "range",
|
|
3864
|
+
label: "Скорость частицы на один пиксель",
|
|
3370
3865
|
},
|
|
3371
3866
|
{
|
|
3372
|
-
id: "
|
|
3373
|
-
initialValue: LINK_SETTINGS.
|
|
3374
|
-
max:
|
|
3375
|
-
min:
|
|
3376
|
-
step:
|
|
3867
|
+
id: "particleCountByDistance",
|
|
3868
|
+
initialValue: LINK_SETTINGS.particleCountByDistance,
|
|
3869
|
+
max: 250,
|
|
3870
|
+
min: 1,
|
|
3871
|
+
step: 1,
|
|
3377
3872
|
type: "range",
|
|
3378
|
-
label: "
|
|
3873
|
+
label: "Количество пикселей для появления частицы",
|
|
3379
3874
|
},
|
|
3380
3875
|
];
|
|
3381
3876
|
const LINK_OPTIONS_LINK_CONTROLS = [
|
|
@@ -3470,24 +3965,6 @@ const LINK_OPTIONS_PARTICLE_CONTROLS = [
|
|
|
3470
3965
|
label: "Радиус",
|
|
3471
3966
|
initialValue: LINK_OPTIONS.particleRadius,
|
|
3472
3967
|
},
|
|
3473
|
-
{
|
|
3474
|
-
id: "particleCount",
|
|
3475
|
-
type: "range",
|
|
3476
|
-
max: 20,
|
|
3477
|
-
min: 1,
|
|
3478
|
-
step: 1,
|
|
3479
|
-
label: "Количество",
|
|
3480
|
-
initialValue: LINK_OPTIONS.particleCount,
|
|
3481
|
-
},
|
|
3482
|
-
{
|
|
3483
|
-
id: "particleSteps",
|
|
3484
|
-
type: "range",
|
|
3485
|
-
max: 200,
|
|
3486
|
-
min: 1,
|
|
3487
|
-
step: 1,
|
|
3488
|
-
label: "Количество кадров",
|
|
3489
|
-
initialValue: LINK_OPTIONS.particleSteps,
|
|
3490
|
-
},
|
|
3491
3968
|
{
|
|
3492
3969
|
id: "particleBorderColor",
|
|
3493
3970
|
type: "color",
|
|
@@ -3921,10 +4398,19 @@ exports.NODE_OPTIONS_NODE_CONTROLS = NODE_OPTIONS_NODE_CONTROLS;
|
|
|
3921
4398
|
exports.NODE_OPTIONS_TEXT_CONTROLS = NODE_OPTIONS_TEXT_CONTROLS;
|
|
3922
4399
|
exports.NODE_SETTINGS = NODE_SETTINGS;
|
|
3923
4400
|
exports.NODE_SETTINGS_CONTROLS = NODE_SETTINGS_CONTROLS;
|
|
4401
|
+
exports.PERFOMANCE_NODE_OPTIONS = PERFOMANCE_NODE_OPTIONS;
|
|
4402
|
+
exports.PERFORMANCE_FORCE_SETTINGS = PERFORMANCE_FORCE_SETTINGS;
|
|
4403
|
+
exports.PERFORMANCE_GRAPH_SETTINGS = PERFORMANCE_GRAPH_SETTINGS;
|
|
4404
|
+
exports.PERFORMANCE_HIGHLIGHT_SETTINGS = PERFORMANCE_HIGHLIGHT_SETTINGS;
|
|
4405
|
+
exports.PERFORMANCE_LINK_OPTIONS = PERFORMANCE_LINK_OPTIONS;
|
|
4406
|
+
exports.PERFORMANCE_LINK_SETTINGS = PERFORMANCE_LINK_SETTINGS;
|
|
4407
|
+
exports.PERFORMANCE_NODE_SETTINGS = PERFORMANCE_NODE_SETTINGS;
|
|
3924
4408
|
exports.animationByProgress = animationByProgress;
|
|
4409
|
+
exports.approximateQuadraticBezierLength = approximateQuadraticBezierLength;
|
|
3925
4410
|
exports.calculateLinkPositionByNode = calculateLinkPositionByNode;
|
|
3926
4411
|
exports.colorGetter = colorGetter;
|
|
3927
4412
|
exports.colorToRgb = colorToRgb;
|
|
4413
|
+
exports.computeGraphBounds = computeGraphBounds;
|
|
3928
4414
|
exports.dragPlaceCoefficientGetter = dragPlaceCoefficientGetter;
|
|
3929
4415
|
exports.drawText = drawText;
|
|
3930
4416
|
exports.extractLinkPointIds = extractLinkPointIds;
|
|
@@ -3936,6 +4422,7 @@ exports.getDrawNode = getDrawNode;
|
|
|
3936
4422
|
exports.getParticlePosition = getParticlePosition;
|
|
3937
4423
|
exports.graphSettingsGetter = graphSettingsGetter;
|
|
3938
4424
|
exports.highlightSettingsGetter = highlightSettingsGetter;
|
|
4425
|
+
exports.isEmptyObject = isEmptyObject;
|
|
3939
4426
|
exports.isNodeVisible = isNodeVisible;
|
|
3940
4427
|
exports.isOverlapsNode = isOverlapsNode;
|
|
3941
4428
|
exports.linkByPointerGetter = linkByPointerGetter;
|