@krainovsd/graph 0.13.0-beta1 → 0.14.0-beta-7.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 +1297 -606
- 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 +305 -81
- 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 -26
- 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 +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-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 +51 -72
- package/lib/esm/module/GraphCanvas/slices/init-pointer.js.map +1 -1
- package/lib/esm/module/GraphCanvas/slices/init-resize.js +11 -0
- 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 +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 +127 -62
- 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;
|
|
@@ -421,50 +469,52 @@ function nodeIterationExtractor(node, i, nodes, state, option, optionConstantGet
|
|
|
421
469
|
return customOptions;
|
|
422
470
|
}
|
|
423
471
|
|
|
424
|
-
function calculateLinkPositionByNode(
|
|
425
|
-
const source = link.source;
|
|
426
|
-
const target = link.target;
|
|
427
|
-
if (typeof source != "object" || typeof target != "object")
|
|
428
|
-
return null;
|
|
472
|
+
function calculateLinkPositionByNode(xStart, yStart, xEnd, yEnd, sourceNode, targetNode, arrowSize = 0) {
|
|
429
473
|
const sourcePoint = getLinkPoint({
|
|
474
|
+
xStart,
|
|
475
|
+
yStart,
|
|
476
|
+
xEnd,
|
|
477
|
+
yEnd,
|
|
430
478
|
arrowSize: 0,
|
|
431
|
-
node:
|
|
432
|
-
oppositeNode: target,
|
|
433
|
-
});
|
|
479
|
+
node: sourceNode});
|
|
434
480
|
const targetPoint = getLinkPoint({
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
481
|
+
xStart: xEnd,
|
|
482
|
+
yStart: yEnd,
|
|
483
|
+
xEnd: xStart,
|
|
484
|
+
yEnd: yStart,
|
|
485
|
+
arrowSize: arrowSize > 0 ? arrowSize * 0.9 : 0,
|
|
486
|
+
node: targetNode});
|
|
487
|
+
const targetPointArrow = getLinkPoint({
|
|
488
|
+
xStart: xEnd,
|
|
489
|
+
yStart: yEnd,
|
|
490
|
+
xEnd: xStart,
|
|
491
|
+
yEnd: yStart,
|
|
492
|
+
arrowSize: 0,
|
|
493
|
+
node: targetNode});
|
|
441
494
|
const distance = targetPoint && sourcePoint
|
|
442
495
|
? Math.sqrt((targetPoint.x - sourcePoint.x) ** 2 + (targetPoint.y - sourcePoint.y) ** 2)
|
|
443
496
|
: 0;
|
|
444
497
|
return {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
498
|
+
xStart: sourcePoint.x,
|
|
499
|
+
yStart: sourcePoint.y,
|
|
500
|
+
xEnd: targetPoint.x,
|
|
501
|
+
yEnd: targetPoint.y,
|
|
502
|
+
xEndArrow: targetPointArrow.x,
|
|
503
|
+
yEndArrow: targetPointArrow.y,
|
|
449
504
|
distance,
|
|
450
505
|
};
|
|
451
506
|
}
|
|
452
507
|
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
508
|
let nodePoint;
|
|
459
509
|
switch (opts.node._shape) {
|
|
460
510
|
case "circle": {
|
|
461
511
|
nodePoint = getCircleIntersection({
|
|
462
|
-
x: opts.
|
|
463
|
-
y: opts.
|
|
512
|
+
x: opts.xStart,
|
|
513
|
+
y: opts.yStart,
|
|
464
514
|
radius: (opts.node._radius ?? COMMON_SETTINGS.nodeRadius) +
|
|
465
515
|
(opts.node._borderWidth ? opts.node._borderWidth / 2 : 0),
|
|
466
|
-
oppositeX: opts.
|
|
467
|
-
oppositeY: opts.
|
|
516
|
+
oppositeX: opts.xEnd,
|
|
517
|
+
oppositeY: opts.yEnd,
|
|
468
518
|
arrowSize: opts.arrowSize,
|
|
469
519
|
});
|
|
470
520
|
break;
|
|
@@ -473,23 +523,23 @@ function getLinkPoint(opts) {
|
|
|
473
523
|
case "text": {
|
|
474
524
|
nodePoint = getRectangleIntersection({
|
|
475
525
|
arrowSize: opts.arrowSize,
|
|
476
|
-
x: opts.
|
|
477
|
-
y: opts.
|
|
526
|
+
x: opts.xStart,
|
|
527
|
+
y: opts.yStart,
|
|
478
528
|
height: (opts.node._height ?? COMMON_SETTINGS.nodeSize) + (opts.node._borderWidth ?? 0),
|
|
479
529
|
width: (opts.node._width ?? COMMON_SETTINGS.nodeSize) + (opts.node._borderWidth ?? 0),
|
|
480
|
-
oppositeX: opts.
|
|
481
|
-
oppositeY: opts.
|
|
530
|
+
oppositeX: opts.xEnd,
|
|
531
|
+
oppositeY: opts.yEnd,
|
|
482
532
|
borderRadius: opts.node._shape === "text" ? 0 : (opts.node._borderRadius ?? 0),
|
|
483
533
|
});
|
|
484
534
|
break;
|
|
485
535
|
}
|
|
486
536
|
default: {
|
|
487
537
|
nodePoint = getCircleIntersection({
|
|
488
|
-
x: opts.
|
|
489
|
-
y: opts.
|
|
538
|
+
x: opts.xStart,
|
|
539
|
+
y: opts.yStart,
|
|
490
540
|
radius: opts.node._radius ?? COMMON_SETTINGS.nodeRadius,
|
|
491
|
-
oppositeX: opts.
|
|
492
|
-
oppositeY: opts.
|
|
541
|
+
oppositeX: opts.xEnd,
|
|
542
|
+
oppositeY: opts.yEnd,
|
|
493
543
|
arrowSize: opts.arrowSize,
|
|
494
544
|
});
|
|
495
545
|
}
|
|
@@ -733,68 +783,78 @@ function isNodeVisible(opts) {
|
|
|
733
783
|
}
|
|
734
784
|
}
|
|
735
785
|
|
|
786
|
+
const FRAME_INTERVAL = 1000 / 60;
|
|
736
787
|
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;
|
|
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;
|
|
750
794
|
}
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
opts.particle.step = 0;
|
|
757
|
-
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;
|
|
758
800
|
}
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
opts.
|
|
765
|
-
|
|
766
|
-
|
|
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;
|
|
767
812
|
}
|
|
768
813
|
else {
|
|
769
|
-
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;
|
|
770
818
|
}
|
|
771
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
|
+
}
|
|
772
832
|
|
|
773
|
-
function linkByPointerGetter({
|
|
774
|
-
if (!areaRect)
|
|
775
|
-
return undefined;
|
|
776
|
-
const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
|
|
833
|
+
function linkByPointerGetter({ pointerX, pointerY, links, linkHoverExtraZone, curve, }) {
|
|
777
834
|
return d3Array.greatest(links, (link) => {
|
|
778
|
-
if (
|
|
779
|
-
|
|
835
|
+
if (!jsHelpers.isObject(link.source) ||
|
|
836
|
+
!jsHelpers.isObject(link.target) ||
|
|
837
|
+
link.source.visible === false ||
|
|
838
|
+
link.target.visible === false)
|
|
839
|
+
return;
|
|
840
|
+
const x1 = (link._x1 ?? link.source.x);
|
|
841
|
+
const y1 = (link._y1 ?? link.source.y);
|
|
842
|
+
const x2 = (link._x2 ?? link.target.x);
|
|
843
|
+
const y2 = (link._y2 ?? link.target.y);
|
|
844
|
+
const cx = link._cx ?? 0;
|
|
845
|
+
const cy = link._cy ?? 0;
|
|
846
|
+
return curve
|
|
847
|
+
? hitTestQuadratic(pointerX, pointerY, x1, y1, cx, cy, x2, y2, linkHoverExtraZone)
|
|
848
|
+
? link.index
|
|
849
|
+
: undefined
|
|
850
|
+
: isNearLink(pointerX, pointerY, x1, y1, x2, y2, linkHoverExtraZone)
|
|
851
|
+
? link.index
|
|
852
|
+
: undefined;
|
|
780
853
|
});
|
|
781
854
|
}
|
|
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;
|
|
855
|
+
function isNearLink(px, py, x1, y1, x2, y2, threshold = 2) {
|
|
856
|
+
const A = px - x1;
|
|
857
|
+
const B = py - y1;
|
|
798
858
|
const C = x2 - x1;
|
|
799
859
|
const D = y2 - y1;
|
|
800
860
|
const dot = A * C + B * D;
|
|
@@ -816,9 +876,56 @@ function distanceToLine(x, y, x1, y1, x2, y2) {
|
|
|
816
876
|
xx = x1 + param * C;
|
|
817
877
|
yy = y1 + param * D;
|
|
818
878
|
}
|
|
819
|
-
const dx =
|
|
820
|
-
const dy =
|
|
821
|
-
return Math.sqrt(dx * dx + dy * dy);
|
|
879
|
+
const dx = px - xx;
|
|
880
|
+
const dy = py - yy;
|
|
881
|
+
return Math.sqrt(dx * dx + dy * dy) <= threshold;
|
|
882
|
+
}
|
|
883
|
+
function hitTestQuadratic(px, py, x0, y0, xc, yc, x2, y2, tol) {
|
|
884
|
+
const bb = bezierBBox(x0, y0, xc, yc, x2, y2);
|
|
885
|
+
if (px < bb.minX - tol || px > bb.maxX + tol || py < bb.minY - tol || py > bb.maxY + tol) {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
const chord = Math.hypot(x2 - x0, y2 - y0);
|
|
889
|
+
const contLen = Math.hypot(xc - x0, yc - y0) + Math.hypot(x2 - xc, y2 - yc);
|
|
890
|
+
const approxLen = (chord + contLen) / 2;
|
|
891
|
+
const steps = Math.max(16, Math.ceil(approxLen / 1));
|
|
892
|
+
const tol2 = tol * tol;
|
|
893
|
+
for (let i = 0; i <= steps; i++) {
|
|
894
|
+
const t = i / steps;
|
|
895
|
+
const mt = 1 - t;
|
|
896
|
+
const x = mt * mt * x0 + 2 * mt * t * xc + t * t * x2;
|
|
897
|
+
const y = mt * mt * y0 + 2 * mt * t * yc + t * t * y2;
|
|
898
|
+
const dx = px - x;
|
|
899
|
+
const dy = py - y;
|
|
900
|
+
if (dx * dx + dy * dy <= tol2)
|
|
901
|
+
return true;
|
|
902
|
+
}
|
|
903
|
+
return false;
|
|
904
|
+
}
|
|
905
|
+
function bezierBBox(x0, y0, x1, y1, x2, y2) {
|
|
906
|
+
function extrema(p0, p1, p2) {
|
|
907
|
+
const denom = p0 - 2 * p1 + p2;
|
|
908
|
+
if (denom === 0)
|
|
909
|
+
return [];
|
|
910
|
+
const t = (p0 - p1) / denom;
|
|
911
|
+
return t > 0 && t < 1 ? [t] : [];
|
|
912
|
+
}
|
|
913
|
+
const ts = [...extrema(x0, x1, x2), ...extrema(y0, y1, y2), 0, 1];
|
|
914
|
+
let maxX = -Infinity, maxY = -Infinity, minX = Infinity, minY = Infinity;
|
|
915
|
+
for (const t of ts) {
|
|
916
|
+
const mt = 1 - t;
|
|
917
|
+
const x = mt * mt * x0 + 2 * mt * t * x1 + t * t * x2;
|
|
918
|
+
const y = mt * mt * y0 + 2 * mt * t * y1 + t * t * y2;
|
|
919
|
+
if (x < minX)
|
|
920
|
+
minX = x;
|
|
921
|
+
if (x > maxX)
|
|
922
|
+
maxX = x;
|
|
923
|
+
if (y < minY)
|
|
924
|
+
minY = y;
|
|
925
|
+
if (y > maxY)
|
|
926
|
+
maxY = y;
|
|
927
|
+
}
|
|
928
|
+
return { minX, minY, maxX, maxY };
|
|
822
929
|
}
|
|
823
930
|
|
|
824
931
|
function nodeHighlight(opts) {
|
|
@@ -994,22 +1101,38 @@ function extractLinkPointIds(link) {
|
|
|
994
1101
|
return { sourceId, targetId };
|
|
995
1102
|
}
|
|
996
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
|
+
|
|
997
1113
|
const GRAPH_SETTINGS = {
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
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,
|
|
1002
1123
|
dragPlaceCoefficient: dragPlaceCoefficientGetter,
|
|
1003
1124
|
zoomInitial: null,
|
|
1004
1125
|
showDrawTime: true,
|
|
1005
1126
|
showDrawTimeEveryTick: false,
|
|
1006
1127
|
};
|
|
1128
|
+
const PERFORMANCE_GRAPH_SETTINGS = {
|
|
1129
|
+
...GRAPH_SETTINGS,
|
|
1130
|
+
};
|
|
1007
1131
|
|
|
1008
1132
|
const HIGHLIGHT_SETTINGS = {
|
|
1009
1133
|
highlightByHoverNode: true,
|
|
1010
1134
|
highlightByHoverLink: true,
|
|
1011
|
-
|
|
1012
|
-
highlightDownFrames: 5,
|
|
1135
|
+
highlightDuration: 150,
|
|
1013
1136
|
linkHoverExtraZone: 2,
|
|
1014
1137
|
/** Node */
|
|
1015
1138
|
highlightByNodeOnlyRoot: true,
|
|
@@ -1064,36 +1187,51 @@ const HIGHLIGHT_SETTINGS = {
|
|
|
1064
1187
|
highlightByLinkForArrowBorderColor: null,
|
|
1065
1188
|
highlightByLinkForArrowBorderSizingAdditional: 0,
|
|
1066
1189
|
};
|
|
1190
|
+
const PERFORMANCE_HIGHLIGHT_SETTINGS = {
|
|
1191
|
+
...HIGHLIGHT_SETTINGS,
|
|
1192
|
+
};
|
|
1067
1193
|
|
|
1068
1194
|
const LINK_SETTINGS = {
|
|
1195
|
+
smartCache: true,
|
|
1069
1196
|
prettyDraw: true,
|
|
1197
|
+
curve: true,
|
|
1070
1198
|
linkScaleSwitch: 1,
|
|
1071
|
-
linkColorAfterScaleSwitch: "#
|
|
1199
|
+
linkColorAfterScaleSwitch: "#C5C5C5FF",
|
|
1072
1200
|
linkColorBeforeScaleSwitch: "#999",
|
|
1073
1201
|
linkWidthAfterScaleSwitch: 0.1,
|
|
1074
1202
|
linkWidthBeforeScaleSwitch: 1,
|
|
1075
1203
|
arrow: true,
|
|
1076
1204
|
arrowByHighlight: true,
|
|
1077
1205
|
particles: true,
|
|
1078
|
-
|
|
1079
|
-
|
|
1206
|
+
// spawn one more particle for N px
|
|
1207
|
+
particleCountByDistance: 25,
|
|
1208
|
+
// speed for 1px
|
|
1209
|
+
particleSpeedByDistance: 50,
|
|
1080
1210
|
};
|
|
1081
1211
|
const LINK_OPTIONS = {
|
|
1082
1212
|
alpha: 1,
|
|
1083
1213
|
arrowAlpha: 1,
|
|
1084
1214
|
arrowSize: 2,
|
|
1085
|
-
arrowBorderColor: "#
|
|
1215
|
+
arrowBorderColor: "#C5C5C5FF",
|
|
1086
1216
|
arrowBorderWidth: 0.1,
|
|
1087
1217
|
particleAlpha: 1,
|
|
1088
|
-
particleColor: "#
|
|
1089
|
-
particleBorderColor: "#
|
|
1218
|
+
particleColor: "#C5C5C5FF",
|
|
1219
|
+
particleBorderColor: "#C5C5C5FF",
|
|
1090
1220
|
particleBorderWidth: 0.1,
|
|
1091
|
-
particleCount: 2,
|
|
1092
1221
|
particleRadius: 0.5,
|
|
1093
|
-
|
|
1222
|
+
};
|
|
1223
|
+
const PERFORMANCE_LINK_SETTINGS = {
|
|
1224
|
+
...LINK_SETTINGS,
|
|
1225
|
+
particles: false,
|
|
1226
|
+
prettyDraw: false,
|
|
1227
|
+
};
|
|
1228
|
+
const PERFORMANCE_LINK_OPTIONS = {
|
|
1229
|
+
...LINK_OPTIONS,
|
|
1094
1230
|
};
|
|
1095
1231
|
|
|
1096
1232
|
const NODE_SETTINGS = {
|
|
1233
|
+
smartCache: true,
|
|
1234
|
+
textScaleSteps: [],
|
|
1097
1235
|
nodeRadiusFlexible: true,
|
|
1098
1236
|
nodeRadiusLinkCountForStep: 5,
|
|
1099
1237
|
nodeRadiusIncrementByStep: 1,
|
|
@@ -1130,14 +1268,14 @@ const NODE_OPTIONS = {
|
|
|
1130
1268
|
textShiftX: 0,
|
|
1131
1269
|
textFont: "Arial",
|
|
1132
1270
|
textAlign: "center",
|
|
1133
|
-
textColor: "#
|
|
1271
|
+
textColor: "#d2d2d2",
|
|
1134
1272
|
textStyle: "normal",
|
|
1135
1273
|
textWeight: 500,
|
|
1136
1274
|
textGap: 0,
|
|
1137
1275
|
label: null,
|
|
1138
1276
|
labelAlpha: 1,
|
|
1139
1277
|
labelAlign: "center",
|
|
1140
|
-
labelColor: "#
|
|
1278
|
+
labelColor: "#ffffff",
|
|
1141
1279
|
labelFont: "Arial",
|
|
1142
1280
|
labelGap: 0,
|
|
1143
1281
|
labelSize: 3.5,
|
|
@@ -1145,6 +1283,14 @@ const NODE_OPTIONS = {
|
|
|
1145
1283
|
labelWeight: 500,
|
|
1146
1284
|
labelWidth: 20,
|
|
1147
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
|
+
};
|
|
1148
1294
|
|
|
1149
1295
|
const GRAPH_CACHE_TYPE = {
|
|
1150
1296
|
NodeOptions: "nodeOptions",
|
|
@@ -1172,11 +1318,11 @@ function initArea() {
|
|
|
1172
1318
|
if (!this.area)
|
|
1173
1319
|
throw new Error("couldn't create canvas");
|
|
1174
1320
|
this.container.appendChild(this.area);
|
|
1175
|
-
this.areaRect = this.area.getBoundingClientRect();
|
|
1176
1321
|
this.context = this.area.getContext("2d");
|
|
1177
1322
|
if (!this.context)
|
|
1178
1323
|
throw new Error("couldn't create canvas context");
|
|
1179
1324
|
this.context.scale(this.dpi, this.dpi);
|
|
1325
|
+
this.updateRect();
|
|
1180
1326
|
}
|
|
1181
1327
|
}
|
|
1182
1328
|
|
|
@@ -1188,10 +1334,8 @@ function initDnd() {
|
|
|
1188
1334
|
if (this.listeners.onDragSubject) {
|
|
1189
1335
|
return this.listeners.onDragSubject.call(this, event);
|
|
1190
1336
|
}
|
|
1191
|
-
if (!this.areaRect)
|
|
1192
|
-
return;
|
|
1193
1337
|
const mouseEvent = event.sourceEvent;
|
|
1194
|
-
const [pointerX, pointerY] =
|
|
1338
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(mouseEvent);
|
|
1195
1339
|
return d3Array.greatest(this.nodes, (node) => {
|
|
1196
1340
|
if (!node.x || !node.y || (jsHelpers.isBoolean(node.drag) && !node.drag))
|
|
1197
1341
|
return undefined;
|
|
@@ -1207,12 +1351,16 @@ function initDnd() {
|
|
|
1207
1351
|
if (this.simulation)
|
|
1208
1352
|
this.simulation.alphaTarget(0.3).restart();
|
|
1209
1353
|
}
|
|
1210
|
-
if (!this.areaRect)
|
|
1211
|
-
return;
|
|
1212
1354
|
const mouseEvent = event.sourceEvent;
|
|
1213
|
-
const [pointerX, pointerY] =
|
|
1214
|
-
|
|
1215
|
-
|
|
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
|
+
}
|
|
1216
1364
|
this.listeners.onMoveDragFinished?.call?.(this, event);
|
|
1217
1365
|
})
|
|
1218
1366
|
.on("end", (event) => {
|
|
@@ -1220,10 +1368,16 @@ function initDnd() {
|
|
|
1220
1368
|
if (!event.active && this.simulation)
|
|
1221
1369
|
this.simulation.alphaTarget(0);
|
|
1222
1370
|
const sourceEvent = event.sourceEvent;
|
|
1223
|
-
if (sourceEvent.altKey
|
|
1224
|
-
const [pointerX, pointerY] =
|
|
1225
|
-
|
|
1226
|
-
|
|
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
|
+
}
|
|
1227
1381
|
}
|
|
1228
1382
|
else {
|
|
1229
1383
|
event.subject.fx = null;
|
|
@@ -1234,8 +1388,44 @@ function initDnd() {
|
|
|
1234
1388
|
d3Selection.select(this.area).call(dragHandler);
|
|
1235
1389
|
}
|
|
1236
1390
|
|
|
1391
|
+
function calculateCurveLinkPositionByNode(xStart, yStart, xEnd, yEnd, sourceNode, targetNode, groupIndex, arrowSize = 0) {
|
|
1392
|
+
const sourceRadius = (sourceNode._radius ?? COMMON_SETTINGS.nodeRadius) + (sourceNode._borderWidth ?? 0) * 0.5;
|
|
1393
|
+
const targetRadius = (targetNode._radius ?? COMMON_SETTINGS.nodeRadius) + (targetNode._borderWidth ?? 0) * 0.5;
|
|
1394
|
+
const effectiveTargetRadius = targetRadius + arrowSize * 0.9;
|
|
1395
|
+
const mx = (xStart + xEnd) * 0.5;
|
|
1396
|
+
const my = (yStart + yEnd) * 0.5;
|
|
1397
|
+
const dx = xEnd - xStart;
|
|
1398
|
+
const dy = yEnd - yStart;
|
|
1399
|
+
const lenSq = dx * dx + dy * dy;
|
|
1400
|
+
const len = lenSq > 0 ? Math.sqrt(lenSq) : 1;
|
|
1401
|
+
const nx = -dy / len;
|
|
1402
|
+
const ny = dx / len;
|
|
1403
|
+
const offset = 0.12 * len * groupIndex;
|
|
1404
|
+
const xControl = mx + nx * offset;
|
|
1405
|
+
const yControl = my + ny * offset;
|
|
1406
|
+
const [xStartP, yStartP] = adjustPoint(xStart, yStart, xControl, yControl, sourceRadius);
|
|
1407
|
+
const [xEndP, yEndP] = adjustPoint(xEnd, yEnd, xControl, yControl, effectiveTargetRadius);
|
|
1408
|
+
const [xEndArrow, yEndArrow] = adjustPoint(xEnd, yEnd, xControl, yControl, targetRadius);
|
|
1409
|
+
return {
|
|
1410
|
+
xStart: xStartP,
|
|
1411
|
+
yStart: yStartP,
|
|
1412
|
+
xEnd: xEndP,
|
|
1413
|
+
yEnd: yEndP,
|
|
1414
|
+
xControl,
|
|
1415
|
+
yControl,
|
|
1416
|
+
xEndArrow,
|
|
1417
|
+
yEndArrow,
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
function adjustPoint(x, y, xControl, yControl, radius) {
|
|
1421
|
+
const dxToControl = xControl - x;
|
|
1422
|
+
const dyToControl = yControl - y;
|
|
1423
|
+
const distToControl = Math.sqrt(dxToControl * dxToControl + dyToControl * dyToControl) || 1;
|
|
1424
|
+
return [x + (dxToControl / distToControl) * radius, y + (dyToControl / distToControl) * radius];
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1237
1427
|
function getDrawLink() {
|
|
1238
|
-
return function drawLink(link) {
|
|
1428
|
+
return function drawLink(link, index) {
|
|
1239
1429
|
if (!this.context ||
|
|
1240
1430
|
typeof link.source !== "object" ||
|
|
1241
1431
|
typeof link.target !== "object" ||
|
|
@@ -1250,8 +1440,7 @@ function getDrawLink() {
|
|
|
1250
1440
|
return;
|
|
1251
1441
|
if (!link.source._visible && !link.target._visible)
|
|
1252
1442
|
return;
|
|
1253
|
-
const
|
|
1254
|
-
const linkOptions = this.linkOptionsCache[id];
|
|
1443
|
+
const linkOptions = this.linkOptionsCache[index];
|
|
1255
1444
|
if (!linkOptions)
|
|
1256
1445
|
return;
|
|
1257
1446
|
if (linkOptions.drawLink) {
|
|
@@ -1266,10 +1455,12 @@ function getDrawLink() {
|
|
|
1266
1455
|
let arrowSize = linkOptions.arrowSize;
|
|
1267
1456
|
let arrowBorderWidth = linkOptions.arrowBorderWidth;
|
|
1268
1457
|
let arrowBorderColor = linkOptions.arrowBorderColor;
|
|
1458
|
+
const currentNodeHighlighted = this.highlightedNode &&
|
|
1459
|
+
(this.highlightedNode.id == link.source.id || this.highlightedNode.id == link.target.id);
|
|
1269
1460
|
/** Highlight */
|
|
1270
1461
|
if (this.highlightedNeighbors && this.highlightedNode) {
|
|
1271
1462
|
/** By Node Not Highlight */
|
|
1272
|
-
if (
|
|
1463
|
+
if (!currentNodeHighlighted) {
|
|
1273
1464
|
const fadeOptions = linkFade({
|
|
1274
1465
|
arrow: this.linkSettings.arrow,
|
|
1275
1466
|
arrowByHighlight: this.linkSettings.arrowByHighlight,
|
|
@@ -1303,9 +1494,10 @@ function getDrawLink() {
|
|
|
1303
1494
|
arrowBorderColor = highlightOptions.arrowBorderColor;
|
|
1304
1495
|
}
|
|
1305
1496
|
}
|
|
1497
|
+
const currentLinkHighlighted = this.highlightedLink === link;
|
|
1306
1498
|
if (this.highlightedNeighbors && this.highlightedLink) {
|
|
1307
1499
|
/** By Link Not Highlight */
|
|
1308
|
-
if (
|
|
1500
|
+
if (!currentLinkHighlighted) {
|
|
1309
1501
|
const fadeOptions = linkFade({
|
|
1310
1502
|
arrow: this.linkSettings.arrow,
|
|
1311
1503
|
arrowByHighlight: this.linkSettings.arrowByHighlight,
|
|
@@ -1349,99 +1541,129 @@ function getDrawLink() {
|
|
|
1349
1541
|
let yStart = link.source.y;
|
|
1350
1542
|
let xEnd = link.target.x;
|
|
1351
1543
|
let yEnd = link.target.y;
|
|
1544
|
+
let xControl = 0;
|
|
1545
|
+
let yControl = 0;
|
|
1546
|
+
let xEndArrow = xEnd;
|
|
1547
|
+
let yEndArrow = yEnd;
|
|
1352
1548
|
let linkDistance = 0;
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
(this.linkSettings.
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
xStart = position.
|
|
1360
|
-
xEnd = position.
|
|
1361
|
-
yStart = position.
|
|
1362
|
-
yEnd = position.
|
|
1549
|
+
const isHasArrow = this.linkSettings.arrow && arrowAlpha > 0;
|
|
1550
|
+
if (!this.linkSettings.curve) {
|
|
1551
|
+
if (this.linkSettings.prettyDraw ||
|
|
1552
|
+
(this.linkSettings.arrow && arrowAlpha > 0) ||
|
|
1553
|
+
(this.particles && (currentNodeHighlighted || currentLinkHighlighted))) {
|
|
1554
|
+
const position = calculateLinkPositionByNode(xStart, yStart, xEnd, yEnd, link.source, link.target, isHasArrow ? arrowSize : 0);
|
|
1555
|
+
xStart = position.xStart;
|
|
1556
|
+
xEnd = position.xEnd;
|
|
1557
|
+
yStart = position.yStart;
|
|
1558
|
+
yEnd = position.yEnd;
|
|
1559
|
+
xEndArrow = position.xEndArrow;
|
|
1560
|
+
yEndArrow = position.yEndArrow;
|
|
1363
1561
|
linkDistance = position.distance;
|
|
1364
1562
|
}
|
|
1563
|
+
link._x1 = xStart;
|
|
1564
|
+
link._y1 = yStart;
|
|
1565
|
+
link._x2 = xEnd;
|
|
1566
|
+
link._y2 = yEnd;
|
|
1567
|
+
link._ax = xEndArrow;
|
|
1568
|
+
link._ay = yEndArrow;
|
|
1569
|
+
this.context.moveTo(xStart, yStart);
|
|
1570
|
+
this.context.lineTo(xEnd, yEnd);
|
|
1571
|
+
this.context.stroke();
|
|
1572
|
+
}
|
|
1573
|
+
else {
|
|
1574
|
+
const position = calculateCurveLinkPositionByNode(xStart, yStart, xEnd, yEnd, link.source, link.target, link._groupIndex ?? 0, isHasArrow ? arrowSize : 0);
|
|
1575
|
+
xStart = position.xStart;
|
|
1576
|
+
yStart = position.yStart;
|
|
1577
|
+
xEnd = position.xEnd;
|
|
1578
|
+
yEnd = position.yEnd;
|
|
1579
|
+
xControl = position.xControl;
|
|
1580
|
+
yControl = position.yControl;
|
|
1581
|
+
xEndArrow = position.xEndArrow;
|
|
1582
|
+
yEndArrow = position.yEndArrow;
|
|
1583
|
+
link._x1 = position.xStart;
|
|
1584
|
+
link._y1 = position.yStart;
|
|
1585
|
+
link._x2 = position.xEnd;
|
|
1586
|
+
link._y2 = position.yEnd;
|
|
1587
|
+
link._cx = position.xControl;
|
|
1588
|
+
link._cy = position.yControl;
|
|
1589
|
+
link._ax = position.xEndArrow;
|
|
1590
|
+
link._ay = position.yEndArrow;
|
|
1591
|
+
linkDistance = approximateQuadraticBezierLength(position.xStart, position.yStart, position.xControl, position.yControl, position.xEnd, position.yEnd);
|
|
1592
|
+
this.context.beginPath();
|
|
1593
|
+
this.context.moveTo(position.xStart, position.yStart);
|
|
1594
|
+
this.context.quadraticCurveTo(position.xControl, position.yControl, position.xEnd, position.yEnd);
|
|
1595
|
+
this.context.setLineDash([]);
|
|
1596
|
+
// if (isDashed) {
|
|
1597
|
+
// this.context.setLineDash([10, 5]);
|
|
1598
|
+
// }
|
|
1599
|
+
this.context.stroke();
|
|
1365
1600
|
}
|
|
1366
|
-
this.context.moveTo(xStart, yStart);
|
|
1367
|
-
this.context.lineTo(xEnd, yEnd);
|
|
1368
|
-
this.context.stroke();
|
|
1369
1601
|
/** 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;
|
|
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));
|
|
1384
1606
|
const particles = [];
|
|
1385
|
-
let prevParticle;
|
|
1386
1607
|
for (let i = 0; i < particleCount; i++) {
|
|
1387
1608
|
const particle = {
|
|
1388
|
-
step: 0,
|
|
1389
|
-
sourceId,
|
|
1390
|
-
targetId,
|
|
1391
|
-
prev: prevParticle,
|
|
1392
|
-
next: undefined,
|
|
1393
1609
|
index: i,
|
|
1394
1610
|
};
|
|
1395
|
-
if (prevParticle)
|
|
1396
|
-
prevParticle.next = particle;
|
|
1397
1611
|
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
1612
|
}
|
|
1404
|
-
this.particles[
|
|
1613
|
+
this.particles[index] = particles;
|
|
1405
1614
|
}
|
|
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
|
-
}
|
|
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,
|
|
1431
1629
|
});
|
|
1432
|
-
|
|
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
|
+
});
|
|
1433
1642
|
}
|
|
1434
1643
|
/** Arrow */
|
|
1435
1644
|
if (this.linkSettings.arrow && arrowAlpha > 0) {
|
|
1645
|
+
let angle = 0;
|
|
1646
|
+
if (!this.linkSettings.curve) {
|
|
1647
|
+
angle = Math.atan2(yEndArrow - yStart, xEndArrow - xStart);
|
|
1648
|
+
}
|
|
1649
|
+
else {
|
|
1650
|
+
const tangentX = 2 * (xEndArrow - xControl);
|
|
1651
|
+
const tangentY = 2 * (yEndArrow - yControl);
|
|
1652
|
+
if (Math.abs(tangentX) < 0.001 && Math.abs(tangentY) < 0.001) {
|
|
1653
|
+
angle = Math.atan2(yEndArrow - yControl, xEndArrow - xControl);
|
|
1654
|
+
}
|
|
1655
|
+
else {
|
|
1656
|
+
angle = Math.atan2(tangentY, tangentX);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1436
1659
|
this.context.beginPath();
|
|
1437
1660
|
this.context.globalAlpha = arrowAlpha;
|
|
1438
1661
|
this.context.strokeStyle = arrowBorderColor;
|
|
1439
1662
|
this.context.lineWidth = arrowBorderWidth;
|
|
1440
1663
|
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));
|
|
1664
|
+
this.context.moveTo(xEndArrow, yEndArrow);
|
|
1665
|
+
this.context.lineTo(xEndArrow - arrowSize * Math.cos(angle - Math.PI / 6), yEndArrow - arrowSize * Math.sin(angle - Math.PI / 6));
|
|
1666
|
+
this.context.lineTo(xEndArrow - arrowSize * Math.cos(angle + Math.PI / 6), yEndArrow - arrowSize * Math.sin(angle + Math.PI / 6));
|
|
1445
1667
|
this.context.closePath();
|
|
1446
1668
|
this.context.fill();
|
|
1447
1669
|
if (arrowBorderWidth > 0) {
|
|
@@ -1500,7 +1722,7 @@ function getTextLines({ context, textAlign, textColor, textFont, textStyle, text
|
|
|
1500
1722
|
}
|
|
1501
1723
|
|
|
1502
1724
|
function getDrawNode(nodeRenders, textRenders) {
|
|
1503
|
-
return function drawNode(node) {
|
|
1725
|
+
return function drawNode(node, index) {
|
|
1504
1726
|
if (!this.context || !node.x || !node.y)
|
|
1505
1727
|
return;
|
|
1506
1728
|
if (node.visible != undefined && !node.visible) {
|
|
@@ -1509,7 +1731,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1509
1731
|
node._height = 0;
|
|
1510
1732
|
return;
|
|
1511
1733
|
}
|
|
1512
|
-
const nodeOptions = this.nodeOptionsCache[
|
|
1734
|
+
const nodeOptions = this.nodeOptionsCache[index];
|
|
1513
1735
|
if (!nodeOptions)
|
|
1514
1736
|
return;
|
|
1515
1737
|
if (nodeOptions.nodeDraw && nodeOptions.textDraw) {
|
|
@@ -1668,7 +1890,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1668
1890
|
this.context.lineWidth = borderWidth;
|
|
1669
1891
|
this.context.strokeStyle = borderColor;
|
|
1670
1892
|
this.context.fillStyle = color;
|
|
1671
|
-
const labelLines = this.cachedNodeLabel[
|
|
1893
|
+
const labelLines = this.cachedNodeLabel[index];
|
|
1672
1894
|
switch (nodeOptions.shape) {
|
|
1673
1895
|
case "circle": {
|
|
1674
1896
|
if (!node.image) {
|
|
@@ -1794,7 +2016,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1794
2016
|
});
|
|
1795
2017
|
}
|
|
1796
2018
|
/** Text draw */
|
|
1797
|
-
const textLines = this.cachedNodeText[
|
|
2019
|
+
const textLines = this.cachedNodeText[index];
|
|
1798
2020
|
if (nodeOptions.textVisible && nodeOptions.text && textLines) {
|
|
1799
2021
|
textRenders.push(() => {
|
|
1800
2022
|
if (nodeOptions.textDraw) {
|
|
@@ -1854,57 +2076,7 @@ function getDrawNode(nodeRenders, textRenders) {
|
|
|
1854
2076
|
}
|
|
1855
2077
|
|
|
1856
2078
|
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) {
|
|
2079
|
+
function draw() {
|
|
1908
2080
|
if (!this.context)
|
|
1909
2081
|
return;
|
|
1910
2082
|
if (this.listeners.onDraw) {
|
|
@@ -1924,8 +2096,20 @@ function initDraw() {
|
|
|
1924
2096
|
nodeRenders.forEach((render) => render());
|
|
1925
2097
|
textRenders.forEach((render) => render());
|
|
1926
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
|
+
}
|
|
1927
2112
|
this.listeners.onDrawFinished?.call?.(this);
|
|
1928
|
-
calculateHighlight.bind(this)(recursive);
|
|
1929
2113
|
}
|
|
1930
2114
|
if (this.graphSettings.showDrawTime) {
|
|
1931
2115
|
return setDrawTime(draw.bind(this), this.graphSettings.showDrawTimeEveryTick);
|
|
@@ -1937,7 +2121,7 @@ function initPointer() {
|
|
|
1937
2121
|
if (!this.area || !this.nodes)
|
|
1938
2122
|
throw new Error("bad init data");
|
|
1939
2123
|
function onHover(event) {
|
|
1940
|
-
if (!this.area)
|
|
2124
|
+
if (!this.area || this.isSelecting)
|
|
1941
2125
|
return;
|
|
1942
2126
|
let currentNode;
|
|
1943
2127
|
let currentLink;
|
|
@@ -1946,10 +2130,10 @@ function initPointer() {
|
|
|
1946
2130
|
let highlightNode = true;
|
|
1947
2131
|
let highlightLink = true;
|
|
1948
2132
|
if (checkHighlightNode) {
|
|
2133
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
1949
2134
|
currentNode = nodeByPointerGetter({
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
mouseEvent: event,
|
|
2135
|
+
pointerX,
|
|
2136
|
+
pointerY,
|
|
1953
2137
|
nodes: this.nodes,
|
|
1954
2138
|
});
|
|
1955
2139
|
if (currentNode?.highlight != undefined)
|
|
@@ -1959,12 +2143,13 @@ function initPointer() {
|
|
|
1959
2143
|
this.area.style.cursor = "pointer";
|
|
1960
2144
|
}
|
|
1961
2145
|
else if (checkHighlightLink) {
|
|
2146
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
1962
2147
|
currentLink = linkByPointerGetter({
|
|
2148
|
+
pointerX,
|
|
2149
|
+
pointerY,
|
|
1963
2150
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
1964
|
-
areaRect: this.areaRect,
|
|
1965
|
-
areaTransform: this.areaTransform,
|
|
1966
|
-
mouseEvent: event,
|
|
1967
2151
|
links: this.links,
|
|
2152
|
+
curve: this.linkSettings.curve,
|
|
1968
2153
|
});
|
|
1969
2154
|
if (currentLink?.highlight != undefined)
|
|
1970
2155
|
highlightLink = currentLink?.highlight;
|
|
@@ -1978,54 +2163,25 @@ function initPointer() {
|
|
|
1978
2163
|
else {
|
|
1979
2164
|
this.area.style.cursor = "default";
|
|
1980
2165
|
}
|
|
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
|
-
}
|
|
2166
|
+
this.animateHighlight(highlightNode ? currentNode : undefined, highlightLink ? currentLink : undefined);
|
|
2013
2167
|
if (!this.listeners.onMove)
|
|
2014
2168
|
return;
|
|
2015
|
-
if (!currentNode && !checkHighlightNode)
|
|
2169
|
+
if (!currentNode && !checkHighlightNode) {
|
|
2170
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2016
2171
|
currentNode = nodeByPointerGetter({
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
mouseEvent: event,
|
|
2172
|
+
pointerX,
|
|
2173
|
+
pointerY,
|
|
2020
2174
|
nodes: this.nodes,
|
|
2021
2175
|
});
|
|
2176
|
+
}
|
|
2022
2177
|
if (!currentNode && (!checkHighlightNode || (!checkHighlightLink && !currentLink))) {
|
|
2178
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2023
2179
|
currentLink = linkByPointerGetter({
|
|
2180
|
+
pointerX,
|
|
2181
|
+
pointerY,
|
|
2024
2182
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
2025
|
-
areaRect: this.areaRect,
|
|
2026
|
-
areaTransform: this.areaTransform,
|
|
2027
|
-
mouseEvent: event,
|
|
2028
2183
|
links: this.links,
|
|
2184
|
+
curve: this.linkSettings.curve,
|
|
2029
2185
|
});
|
|
2030
2186
|
}
|
|
2031
2187
|
if (!currentNode)
|
|
@@ -2037,19 +2193,20 @@ function initPointer() {
|
|
|
2037
2193
|
!("button" in event) ||
|
|
2038
2194
|
event.button !== 1)
|
|
2039
2195
|
return;
|
|
2196
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2040
2197
|
const currentNode = nodeByPointerGetter({
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
mouseEvent: event,
|
|
2198
|
+
pointerX,
|
|
2199
|
+
pointerY,
|
|
2044
2200
|
nodes: this.nodes,
|
|
2045
2201
|
});
|
|
2046
2202
|
if (!currentNode) {
|
|
2203
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2047
2204
|
const currentLink = linkByPointerGetter({
|
|
2205
|
+
pointerX,
|
|
2206
|
+
pointerY,
|
|
2048
2207
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
2049
|
-
areaRect: this.areaRect,
|
|
2050
|
-
areaTransform: this.areaTransform,
|
|
2051
|
-
mouseEvent: event,
|
|
2052
2208
|
links: this.links,
|
|
2209
|
+
curve: this.linkSettings.curve,
|
|
2053
2210
|
});
|
|
2054
2211
|
return void this.listeners.onWheelClick.call(this, event, undefined, currentLink);
|
|
2055
2212
|
}
|
|
@@ -2058,19 +2215,20 @@ function initPointer() {
|
|
|
2058
2215
|
function onRightClick(event) {
|
|
2059
2216
|
if (!this.listeners.onContextMenu)
|
|
2060
2217
|
return;
|
|
2218
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2061
2219
|
const currentNode = nodeByPointerGetter({
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
mouseEvent: event,
|
|
2220
|
+
pointerX,
|
|
2221
|
+
pointerY,
|
|
2065
2222
|
nodes: this.nodes,
|
|
2066
2223
|
});
|
|
2067
2224
|
if (!currentNode) {
|
|
2225
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2068
2226
|
const currentLink = linkByPointerGetter({
|
|
2227
|
+
pointerX,
|
|
2228
|
+
pointerY,
|
|
2069
2229
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
2070
|
-
areaRect: this.areaRect,
|
|
2071
|
-
areaTransform: this.areaTransform,
|
|
2072
|
-
mouseEvent: event,
|
|
2073
2230
|
links: this.links,
|
|
2231
|
+
curve: this.linkSettings.curve,
|
|
2074
2232
|
});
|
|
2075
2233
|
return void this.listeners.onContextMenu.call(this, event, undefined, currentLink);
|
|
2076
2234
|
}
|
|
@@ -2079,19 +2237,20 @@ function initPointer() {
|
|
|
2079
2237
|
function onDoubleClick(event) {
|
|
2080
2238
|
if (!this.listeners.onDoubleClick)
|
|
2081
2239
|
return;
|
|
2240
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2082
2241
|
const currentNode = nodeByPointerGetter({
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
mouseEvent: event,
|
|
2242
|
+
pointerX,
|
|
2243
|
+
pointerY,
|
|
2086
2244
|
nodes: this.nodes,
|
|
2087
2245
|
});
|
|
2088
2246
|
if (!currentNode) {
|
|
2247
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2089
2248
|
const currentLink = linkByPointerGetter({
|
|
2249
|
+
pointerX,
|
|
2250
|
+
pointerY,
|
|
2090
2251
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
2091
|
-
areaRect: this.areaRect,
|
|
2092
|
-
areaTransform: this.areaTransform,
|
|
2093
|
-
mouseEvent: event,
|
|
2094
2252
|
links: this.links,
|
|
2253
|
+
curve: this.linkSettings.curve,
|
|
2095
2254
|
});
|
|
2096
2255
|
return void this.listeners.onDoubleClick.call(this, event, undefined, currentLink);
|
|
2097
2256
|
}
|
|
@@ -2100,19 +2259,20 @@ function initPointer() {
|
|
|
2100
2259
|
function onClick(event) {
|
|
2101
2260
|
if (this.isDragging || !this.listeners.onClick || ("button" in event && event.button !== 0))
|
|
2102
2261
|
return;
|
|
2262
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2103
2263
|
const currentNode = nodeByPointerGetter({
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
mouseEvent: event,
|
|
2264
|
+
pointerX,
|
|
2265
|
+
pointerY,
|
|
2107
2266
|
nodes: this.nodes,
|
|
2108
2267
|
});
|
|
2109
2268
|
if (!currentNode) {
|
|
2269
|
+
const [pointerX, pointerY] = this.getPointerAreaPosition(event);
|
|
2110
2270
|
const currentLink = linkByPointerGetter({
|
|
2271
|
+
pointerX,
|
|
2272
|
+
pointerY,
|
|
2111
2273
|
linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
|
|
2112
|
-
areaRect: this.areaRect,
|
|
2113
|
-
areaTransform: this.areaTransform,
|
|
2114
|
-
mouseEvent: event,
|
|
2115
2274
|
links: this.links,
|
|
2275
|
+
curve: this.linkSettings.curve,
|
|
2116
2276
|
});
|
|
2117
2277
|
return void this.listeners.onClick.call(this, event, undefined, currentLink);
|
|
2118
2278
|
}
|
|
@@ -2158,148 +2318,270 @@ function initResize() {
|
|
|
2158
2318
|
return;
|
|
2159
2319
|
}
|
|
2160
2320
|
this.updateSize();
|
|
2321
|
+
requestAnimationFrame(() => {
|
|
2322
|
+
this.updateSize();
|
|
2323
|
+
});
|
|
2161
2324
|
});
|
|
2162
2325
|
document.addEventListener("scroll", this.updateRect.bind(this), {
|
|
2163
2326
|
capture: true,
|
|
2164
2327
|
passive: true,
|
|
2165
2328
|
signal: abortController.signal,
|
|
2166
2329
|
});
|
|
2330
|
+
document.addEventListener("visibilitychange", () => {
|
|
2331
|
+
if (document.hidden)
|
|
2332
|
+
return;
|
|
2333
|
+
this.updateSize();
|
|
2334
|
+
requestAnimationFrame(() => {
|
|
2335
|
+
this.updateSize();
|
|
2336
|
+
});
|
|
2337
|
+
}, { signal: abortController.signal });
|
|
2167
2338
|
observer.observe(this.area);
|
|
2168
2339
|
}
|
|
2169
2340
|
|
|
2170
|
-
function
|
|
2171
|
-
if (!this.
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2341
|
+
function initSelection() {
|
|
2342
|
+
if (!this.area)
|
|
2343
|
+
throw new Error("bad init data");
|
|
2344
|
+
this.isSelecting = false;
|
|
2345
|
+
this.selectionRect = null;
|
|
2346
|
+
let selectedNodesSet = new Set();
|
|
2347
|
+
let selectedLinksSet = new Set();
|
|
2348
|
+
let localSelection = false;
|
|
2349
|
+
function onMouseDown(event) {
|
|
2350
|
+
if (event.button !== 0 || !event.shiftKey)
|
|
2351
|
+
return;
|
|
2352
|
+
if (!this.area)
|
|
2353
|
+
return;
|
|
2354
|
+
event.stopPropagation();
|
|
2355
|
+
event.preventDefault();
|
|
2356
|
+
const [startX, startY] = this.getPointerAreaPosition(event);
|
|
2357
|
+
this.isSelecting = true;
|
|
2358
|
+
localSelection = true;
|
|
2359
|
+
this.selectionRect = { x1: startX, y1: startY, x2: startX, y2: startY };
|
|
2360
|
+
selectedNodesSet = new Set();
|
|
2361
|
+
selectedLinksSet = new Set();
|
|
2362
|
+
const controller = new AbortController();
|
|
2363
|
+
this.area.addEventListener("mousemove", onMouseMove.bind(this), {
|
|
2364
|
+
signal: controller.signal,
|
|
2186
2365
|
});
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
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);
|
|
2366
|
+
this.area.addEventListener("mouseup", () => {
|
|
2367
|
+
onMouseUp.call(this, controller);
|
|
2368
|
+
}, {
|
|
2369
|
+
signal: controller.signal,
|
|
2370
|
+
});
|
|
2371
|
+
window.addEventListener("keyup", (event) => {
|
|
2372
|
+
if (event.key === "Shift") {
|
|
2373
|
+
onMouseUp.call(this, controller);
|
|
2374
|
+
}
|
|
2375
|
+
}, { signal: controller.signal });
|
|
2205
2376
|
}
|
|
2206
|
-
|
|
2207
|
-
this.
|
|
2208
|
-
|
|
2377
|
+
function onMouseMove(event) {
|
|
2378
|
+
if (!this.isSelecting || !this.selectionRect)
|
|
2379
|
+
return;
|
|
2380
|
+
event.stopPropagation();
|
|
2381
|
+
const [x, y] = this.getPointerAreaPosition(event);
|
|
2382
|
+
this.selectionRect.x2 = x;
|
|
2383
|
+
this.selectionRect.y2 = y;
|
|
2384
|
+
const rect = normalizeRect(this.selectionRect);
|
|
2385
|
+
const newNodes = new Set();
|
|
2386
|
+
for (let i = 0; i < this.nodes.length; i++) {
|
|
2387
|
+
const node = this.nodes[i];
|
|
2388
|
+
if (node.x == undefined || node.y == undefined)
|
|
2389
|
+
continue;
|
|
2390
|
+
if (node.visible === false)
|
|
2391
|
+
continue;
|
|
2392
|
+
const cache = this.nodeOptionsCache[i];
|
|
2393
|
+
if (isNodeInRect(node, rect, cache)) {
|
|
2394
|
+
newNodes.add(node);
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
const newLinks = new Set();
|
|
2398
|
+
for (const link of this.links) {
|
|
2399
|
+
const source = link.source;
|
|
2400
|
+
const target = link.target;
|
|
2401
|
+
if (newNodes.has(source) && newNodes.has(target)) {
|
|
2402
|
+
newLinks.add(link);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
for (const node of selectedNodesSet) {
|
|
2406
|
+
if (!newNodes.has(node)) {
|
|
2407
|
+
node._selected = false;
|
|
2408
|
+
this.listeners.onSelectionOut?.call(this, node, undefined);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
for (const link of selectedLinksSet) {
|
|
2412
|
+
if (!newLinks.has(link)) {
|
|
2413
|
+
link._selected = false;
|
|
2414
|
+
this.listeners.onSelectionOut?.call(this, undefined, link);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
for (const node of newNodes) {
|
|
2418
|
+
if (!selectedNodesSet.has(node)) {
|
|
2419
|
+
node._selected = true;
|
|
2420
|
+
this.listeners.onSelectionIn?.call(this, node, undefined);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
for (const link of newLinks) {
|
|
2424
|
+
if (!selectedLinksSet.has(link)) {
|
|
2425
|
+
link._selected = true;
|
|
2426
|
+
this.listeners.onSelectionIn?.call(this, undefined, link);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
selectedNodesSet = newNodes;
|
|
2430
|
+
selectedLinksSet = newLinks;
|
|
2431
|
+
this.draw();
|
|
2209
2432
|
}
|
|
2210
|
-
|
|
2211
|
-
|
|
2433
|
+
function onMouseUp(controller) {
|
|
2434
|
+
controller.abort();
|
|
2435
|
+
if (!this.isSelecting)
|
|
2436
|
+
return;
|
|
2437
|
+
const nodes = Array.from(selectedNodesSet);
|
|
2438
|
+
const links = Array.from(selectedLinksSet);
|
|
2439
|
+
for (const node of nodes) {
|
|
2440
|
+
node._selected = false;
|
|
2441
|
+
}
|
|
2442
|
+
for (const link of links) {
|
|
2443
|
+
link._selected = false;
|
|
2444
|
+
}
|
|
2445
|
+
this.isSelecting = false;
|
|
2446
|
+
this.selectionRect = null;
|
|
2447
|
+
selectedNodesSet = new Set();
|
|
2448
|
+
selectedLinksSet = new Set();
|
|
2449
|
+
this.listeners.onSelectionEnd?.call(this, nodes, links);
|
|
2450
|
+
this.tick();
|
|
2212
2451
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
.
|
|
2216
|
-
|
|
2217
|
-
|
|
2452
|
+
function onSelectionEnd(event) {
|
|
2453
|
+
if (localSelection) {
|
|
2454
|
+
event.stopImmediatePropagation();
|
|
2455
|
+
localSelection = false;
|
|
2456
|
+
}
|
|
2218
2457
|
}
|
|
2219
|
-
|
|
2220
|
-
this.
|
|
2458
|
+
this.area.addEventListener("mousedown", onMouseDown.bind(this), {
|
|
2459
|
+
signal: this.eventAbortController.signal,
|
|
2460
|
+
});
|
|
2461
|
+
this.area.addEventListener("click", onSelectionEnd, { signal: this.eventAbortController.signal });
|
|
2462
|
+
}
|
|
2463
|
+
function normalizeRect(rect) {
|
|
2464
|
+
const x = Math.min(rect.x1, rect.x2);
|
|
2465
|
+
const y = Math.min(rect.y1, rect.y2);
|
|
2466
|
+
const width = Math.abs(rect.x2 - rect.x1);
|
|
2467
|
+
const height = Math.abs(rect.y2 - rect.y1);
|
|
2468
|
+
return { x, y, width, height };
|
|
2469
|
+
}
|
|
2470
|
+
function isNodeInRect(node, rect, cache) {
|
|
2471
|
+
const nx = node.x;
|
|
2472
|
+
const ny = node.y;
|
|
2473
|
+
if (nx == undefined || ny == undefined)
|
|
2474
|
+
return false;
|
|
2475
|
+
if (cache?.shape === "circle") {
|
|
2476
|
+
const r = cache.radius ?? 0;
|
|
2477
|
+
const closestX = Math.max(rect.x, Math.min(nx, rect.x + rect.width));
|
|
2478
|
+
const closestY = Math.max(rect.y, Math.min(ny, rect.y + rect.height));
|
|
2479
|
+
const dx = nx - closestX;
|
|
2480
|
+
const dy = ny - closestY;
|
|
2481
|
+
return dx * dx + dy * dy <= r * r;
|
|
2221
2482
|
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2483
|
+
const hw = (cache?.width ?? 0) / 2;
|
|
2484
|
+
const hh = (cache?.height ?? 0) / 2;
|
|
2485
|
+
return (nx - hw >= rect.x &&
|
|
2486
|
+
nx + hw <= rect.x + rect.width &&
|
|
2487
|
+
ny - hh >= rect.y &&
|
|
2488
|
+
ny + hh <= rect.y + rect.height);
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
function updateLinkCache() {
|
|
2492
|
+
if (!this.context)
|
|
2493
|
+
return;
|
|
2494
|
+
const current = this.areaTransform.k;
|
|
2495
|
+
if (this.linkSettings.smartCache &&
|
|
2496
|
+
this._lastLinkZoomK !== undefined &&
|
|
2497
|
+
this.linkOptionsCache.length > 0) {
|
|
2498
|
+
const corner = this.linkSettings.linkScaleSwitch;
|
|
2499
|
+
const prev = this._lastLinkZoomK;
|
|
2500
|
+
this._lastLinkZoomK = current;
|
|
2501
|
+
if (!((prev <= corner && current >= corner) || (prev >= corner && current <= corner))) {
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
for (let i = 0; i < this.links.length; i++) {
|
|
2505
|
+
const link = this.links[i];
|
|
2506
|
+
const linkOptions = linkIterationExtractor(link, i, this.links, this, this.linkSettings.options ?? {}, linkOptionsGetter);
|
|
2507
|
+
const cache = this.linkOptionsCache[i];
|
|
2508
|
+
if (cache) {
|
|
2509
|
+
cache.color = linkOptions.color;
|
|
2510
|
+
cache.arrowColor = linkOptions.arrowColor;
|
|
2511
|
+
cache.width = linkOptions.width;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
return;
|
|
2224
2515
|
}
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2516
|
+
this._lastLinkZoomK = current;
|
|
2517
|
+
this.linkOptionsCache.length = 0;
|
|
2518
|
+
const groupMap = {};
|
|
2519
|
+
const groupSelfMap = {};
|
|
2520
|
+
for (let i = 0; i < this.links.length; i++) {
|
|
2521
|
+
const link = this.links[i];
|
|
2522
|
+
const { sourceId, targetId } = extractLinkPointIds(link);
|
|
2523
|
+
const linkOptions = linkIterationExtractor(link, i, this.links, this, this.linkSettings.options ?? {}, linkOptionsGetter);
|
|
2524
|
+
this.linkOptionsCache[i] = linkOptions;
|
|
2525
|
+
if (this.linkSettings.curve) {
|
|
2526
|
+
const id = `${targetId}${sourceId}`;
|
|
2527
|
+
if (sourceId === targetId) {
|
|
2528
|
+
groupSelfMap[id] ??= 0;
|
|
2529
|
+
link._groupIndex = ++groupSelfMap[id];
|
|
2530
|
+
link._self = true;
|
|
2531
|
+
continue;
|
|
2532
|
+
}
|
|
2533
|
+
const reverseId = `${sourceId}${targetId}`;
|
|
2534
|
+
groupMap[reverseId] ??= 0;
|
|
2535
|
+
groupMap[reverseId]++;
|
|
2536
|
+
groupMap[id] ??= 0;
|
|
2537
|
+
link._groupIndex = indexToPosition(++groupMap[id]);
|
|
2538
|
+
link._self = false;
|
|
2539
|
+
}
|
|
2230
2540
|
}
|
|
2231
|
-
if (this.
|
|
2232
|
-
|
|
2541
|
+
if (this.linkSettings.curve) {
|
|
2542
|
+
for (const link of this.links) {
|
|
2543
|
+
const { sourceId, targetId } = extractLinkPointIds(link);
|
|
2544
|
+
const id = `${targetId}${sourceId}`;
|
|
2545
|
+
link._groupSize = link._self ? (groupSelfMap[id] ?? 1) : (groupMap[id] ?? 1);
|
|
2546
|
+
}
|
|
2233
2547
|
}
|
|
2234
|
-
initCollideForce.call(this, true);
|
|
2235
2548
|
}
|
|
2236
|
-
function
|
|
2237
|
-
if (
|
|
2549
|
+
function indexToPosition(n) {
|
|
2550
|
+
if (n === 1)
|
|
2551
|
+
return 0;
|
|
2552
|
+
const index = n >> 1;
|
|
2553
|
+
return n & 1 ? -(index * 2) : index * 2;
|
|
2554
|
+
// return n & 1 ? -(n >> 1) : n >> 1;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
function updateNodeCache() {
|
|
2558
|
+
if (!this.context)
|
|
2238
2559
|
return;
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
if (this.
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
this.
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
switch (nodeOptions.shape) {
|
|
2257
|
-
case "circle": {
|
|
2258
|
-
if (this.forceSettings.collideRadius) {
|
|
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));
|
|
2560
|
+
const currentZoom = this.areaTransform.k;
|
|
2561
|
+
const options = this.nodeOptionsCache.length === 0;
|
|
2562
|
+
const label = this.cachedNodeLabel.length === 0;
|
|
2563
|
+
let text = this.cachedNodeText.length === 0;
|
|
2564
|
+
if (this.nodeSettings.smartCache &&
|
|
2565
|
+
this._lastNodeZoomK !== undefined &&
|
|
2566
|
+
!options &&
|
|
2567
|
+
!text &&
|
|
2568
|
+
!label) {
|
|
2569
|
+
const thresholds = this.nodeSettings.textScaleSteps;
|
|
2570
|
+
if (thresholds && thresholds.length > 0) {
|
|
2571
|
+
const min = Math.min(this._lastNodeZoomK, currentZoom);
|
|
2572
|
+
const max = Math.max(this._lastNodeZoomK, currentZoom);
|
|
2573
|
+
const crossed = thresholds.some((t) => t >= min && t <= max);
|
|
2574
|
+
this._lastNodeZoomK = currentZoom;
|
|
2575
|
+
if (!crossed)
|
|
2576
|
+
return;
|
|
2281
2577
|
}
|
|
2578
|
+
this.cachedNodeText.length = 0;
|
|
2579
|
+
text = true;
|
|
2282
2580
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
function updateLinkCache() {
|
|
2286
|
-
this.linkOptionsCache = {};
|
|
2287
|
-
for (let i = 0; i < this.links.length; i++) {
|
|
2288
|
-
const link = this.links[i];
|
|
2289
|
-
const { sourceId, targetId } = extractLinkPointIds(link);
|
|
2290
|
-
const linkOptions = linkIterationExtractor(link, i, this.links, this, this.linkSettings.options ?? {}, linkOptionsGetter);
|
|
2291
|
-
const id = `${targetId}${sourceId}`;
|
|
2292
|
-
this.linkOptionsCache[id] = linkOptions;
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
function updateNodeCache(types) {
|
|
2297
|
-
this.nodeOptionsCache = {};
|
|
2298
|
-
if (!this.context)
|
|
2299
|
-
return;
|
|
2581
|
+
this._lastNodeZoomK = currentZoom;
|
|
2300
2582
|
for (let i = 0; i < this.nodes.length; i++) {
|
|
2301
2583
|
const node = this.nodes[i];
|
|
2302
|
-
if (
|
|
2584
|
+
if (options) {
|
|
2303
2585
|
const nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
|
|
2304
2586
|
const radius = nodeOptions.shape === "circle"
|
|
2305
2587
|
? nodeRadiusGetter({
|
|
@@ -2350,11 +2632,19 @@ function updateNodeCache(types) {
|
|
|
2350
2632
|
nodeOptions.height = node.visible === false ? 0 : height;
|
|
2351
2633
|
nodeOptions.radius = node.visible === false ? 0 : radius;
|
|
2352
2634
|
nodeOptions.labelSize = labelSize;
|
|
2353
|
-
this.nodeOptionsCache[
|
|
2635
|
+
this.nodeOptionsCache[i] = nodeOptions;
|
|
2354
2636
|
}
|
|
2355
|
-
|
|
2637
|
+
let nodeOptions = this.nodeOptionsCache[i];
|
|
2638
|
+
if (!nodeOptions)
|
|
2639
|
+
continue;
|
|
2356
2640
|
/** label */
|
|
2357
|
-
if (
|
|
2641
|
+
if (label) {
|
|
2642
|
+
if (!options) {
|
|
2643
|
+
nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
|
|
2644
|
+
const cache = this.nodeOptionsCache[i];
|
|
2645
|
+
if (cache)
|
|
2646
|
+
cache.label = nodeOptions.label;
|
|
2647
|
+
}
|
|
2358
2648
|
/** label in not text shape */
|
|
2359
2649
|
if (nodeOptions.shape !== "text" && nodeOptions.label) {
|
|
2360
2650
|
this.context.font = `${nodeOptions.labelStyle} normal ${nodeOptions.labelWeight} ${nodeOptions.labelSize}px ${nodeOptions.labelFont}`;
|
|
@@ -2362,7 +2652,7 @@ function updateNodeCache(types) {
|
|
|
2362
2652
|
this.context.textAlign = nodeOptions.labelAlign;
|
|
2363
2653
|
if (nodeOptions.labelWidth == undefined ||
|
|
2364
2654
|
this.context.measureText(nodeOptions.label).width <= nodeOptions.labelWidth) {
|
|
2365
|
-
this.cachedNodeLabel[
|
|
2655
|
+
this.cachedNodeLabel[i] = [nodeOptions.label];
|
|
2366
2656
|
}
|
|
2367
2657
|
const { lines } = getTextLines({
|
|
2368
2658
|
context: this.context,
|
|
@@ -2375,7 +2665,7 @@ function updateNodeCache(types) {
|
|
|
2375
2665
|
textStyle: nodeOptions.labelStyle,
|
|
2376
2666
|
textWeight: nodeOptions.labelWeight,
|
|
2377
2667
|
});
|
|
2378
|
-
this.cachedNodeLabel[
|
|
2668
|
+
this.cachedNodeLabel[i] = lines;
|
|
2379
2669
|
/** label in text shape */
|
|
2380
2670
|
}
|
|
2381
2671
|
else if (nodeOptions.shape === "text" && nodeOptions.label) {
|
|
@@ -2399,18 +2689,29 @@ function updateNodeCache(types) {
|
|
|
2399
2689
|
(lines.length - 1) * nodeOptions.labelGap +
|
|
2400
2690
|
nodeOptions.labelYPadding;
|
|
2401
2691
|
nodeOptions.width = maxSize + nodeOptions.labelXPadding;
|
|
2402
|
-
this.cachedNodeLabel[
|
|
2692
|
+
this.cachedNodeLabel[i] = lines;
|
|
2693
|
+
}
|
|
2694
|
+
else {
|
|
2695
|
+
this.cachedNodeLabel[i] = [];
|
|
2403
2696
|
}
|
|
2404
2697
|
}
|
|
2405
2698
|
/** text */
|
|
2406
|
-
if (
|
|
2699
|
+
if (text) {
|
|
2700
|
+
if (!options) {
|
|
2701
|
+
nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
|
|
2702
|
+
const cache = this.nodeOptionsCache[i];
|
|
2703
|
+
if (cache) {
|
|
2704
|
+
cache.text = nodeOptions.text;
|
|
2705
|
+
cache.textVisible = nodeOptions.textVisible;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
if (!nodeOptions.text) {
|
|
2709
|
+
this.cachedNodeText[i] = [];
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2407
2712
|
this.context.font = `${nodeOptions.textStyle} normal ${nodeOptions.textWeight} ${nodeOptions.textSize}px ${nodeOptions.textFont}`;
|
|
2408
2713
|
this.context.fillStyle = nodeOptions.textColor;
|
|
2409
2714
|
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
2715
|
const { lines } = getTextLines({
|
|
2415
2716
|
context: this.context,
|
|
2416
2717
|
maxWidth: nodeOptions.textWidth,
|
|
@@ -2422,49 +2723,233 @@ function updateNodeCache(types) {
|
|
|
2422
2723
|
textStyle: nodeOptions.textStyle,
|
|
2423
2724
|
textWeight: nodeOptions.textWeight,
|
|
2424
2725
|
});
|
|
2425
|
-
this.cachedNodeText[
|
|
2726
|
+
this.cachedNodeText[i] = lines;
|
|
2426
2727
|
}
|
|
2427
2728
|
}
|
|
2428
2729
|
}
|
|
2429
2730
|
|
|
2731
|
+
const DEFAULT_ZOOM_EXTENT = [0.3, 10];
|
|
2732
|
+
const DEFAULT_TRANSLATE_EXTENT_COEFFICIENT = 8;
|
|
2733
|
+
const DEFAULT_MARGIN = 0.25;
|
|
2430
2734
|
function initZoom(currentZoom) {
|
|
2431
2735
|
if (!this.area)
|
|
2432
2736
|
throw new Error("bad init data");
|
|
2737
|
+
const bounds = computeGraphBounds(this.nodes);
|
|
2738
|
+
const zoomExtent = resolveZoomExtent(this.graphSettings.zoomExtent, this.width, this.height, this.graphSettings.zoomExtentMargin ?? DEFAULT_MARGIN, bounds);
|
|
2739
|
+
const translateExtent = resolveTranslateExtent(this.graphSettings.translateExtent, this.graphSettings.translateExtentCoefficient, this.graphSettings.translateExtentOverlap, this.width, this.height, zoomExtent, bounds);
|
|
2740
|
+
this._translateExtent = translateExtent;
|
|
2433
2741
|
const zoomInstance = d3Zoom.zoom()
|
|
2434
|
-
.scaleExtent(
|
|
2742
|
+
.scaleExtent(zoomExtent)
|
|
2743
|
+
.filter((event) => {
|
|
2744
|
+
return !(event instanceof MouseEvent && event.shiftKey);
|
|
2745
|
+
})
|
|
2435
2746
|
.on("zoom", (event) => {
|
|
2747
|
+
if (this._zoomAnimating)
|
|
2748
|
+
return;
|
|
2436
2749
|
this.listeners.onZoom?.call?.(this, event);
|
|
2437
2750
|
const oldTransform = this.areaTransform;
|
|
2438
2751
|
this.areaTransform = event.transform;
|
|
2439
2752
|
if (this.areaTransform.k !== oldTransform.k) {
|
|
2440
2753
|
updateLinkCache.call(this);
|
|
2441
|
-
updateNodeCache.call(this
|
|
2754
|
+
updateNodeCache.call(this);
|
|
2442
2755
|
}
|
|
2443
|
-
|
|
2444
|
-
requestAnimationFrame(() => this.draw());
|
|
2756
|
+
this.tick();
|
|
2445
2757
|
});
|
|
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
|
-
}
|
|
2758
|
+
zoomInstance.translateExtent(translateExtent);
|
|
2457
2759
|
d3Selection.select(this.area).call(zoomInstance).on("dblclick.zoom", null);
|
|
2458
2760
|
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);
|
|
2761
|
+
this.areaTransform = new d3Zoom.ZoomTransform(Math.max(zoomInitial?.k ?? 1, zoomExtent[0]), zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
|
|
2460
2762
|
d3Zoom.zoom().transform(d3Selection.select(this.area), this.areaTransform);
|
|
2461
2763
|
}
|
|
2764
|
+
function resolveZoomExtent(userExtent, viewWidth, viewHeight, margin, bounds) {
|
|
2765
|
+
if (userExtent &&
|
|
2766
|
+
userExtent.length === 2 &&
|
|
2767
|
+
userExtent[0] != undefined &&
|
|
2768
|
+
userExtent[1] != undefined) {
|
|
2769
|
+
return userExtent;
|
|
2770
|
+
}
|
|
2771
|
+
if (!bounds)
|
|
2772
|
+
return DEFAULT_ZOOM_EXTENT;
|
|
2773
|
+
const graphWidth = bounds.maxX - bounds.minX;
|
|
2774
|
+
const graphHeight = bounds.maxY - bounds.minY;
|
|
2775
|
+
if (graphWidth === 0 || graphHeight === 0)
|
|
2776
|
+
return DEFAULT_ZOOM_EXTENT;
|
|
2777
|
+
const scaleToFit = (1 - margin) / Math.max(graphWidth / viewWidth, graphHeight / viewHeight);
|
|
2778
|
+
return [scaleToFit, DEFAULT_ZOOM_EXTENT[1]];
|
|
2779
|
+
}
|
|
2780
|
+
function resolveTranslateExtent(userExtent, userCoefficient, overlap, viewWidth, viewHeight, zoomExtent, bounds) {
|
|
2781
|
+
if (userExtent && userExtent.length >= 2 && jsHelpers.isArray(userExtent[0]) && jsHelpers.isArray(userExtent[1])) {
|
|
2782
|
+
return [
|
|
2783
|
+
[
|
|
2784
|
+
userExtent[0][0] ?? -viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2785
|
+
userExtent[0][1] ?? -viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2786
|
+
],
|
|
2787
|
+
[
|
|
2788
|
+
userExtent[1][0] ?? viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2789
|
+
userExtent[1][1] ?? viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2790
|
+
],
|
|
2791
|
+
];
|
|
2792
|
+
}
|
|
2793
|
+
if (userCoefficient) {
|
|
2794
|
+
const [cx, cy] = jsHelpers.isArray(userCoefficient)
|
|
2795
|
+
? userCoefficient
|
|
2796
|
+
: [userCoefficient, userCoefficient];
|
|
2797
|
+
return [
|
|
2798
|
+
[-viewWidth * cx, -viewHeight * cy],
|
|
2799
|
+
[viewWidth * cx, viewHeight * cy],
|
|
2800
|
+
];
|
|
2801
|
+
}
|
|
2802
|
+
if (!bounds) {
|
|
2803
|
+
return [
|
|
2804
|
+
[
|
|
2805
|
+
-viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2806
|
+
-viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2807
|
+
],
|
|
2808
|
+
[
|
|
2809
|
+
viewWidth * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2810
|
+
viewHeight * DEFAULT_TRANSLATE_EXTENT_COEFFICIENT,
|
|
2811
|
+
],
|
|
2812
|
+
];
|
|
2813
|
+
}
|
|
2814
|
+
const minZoom = zoomExtent[0];
|
|
2815
|
+
const zoomViewWidth = viewWidth / minZoom;
|
|
2816
|
+
const zoomViewHeight = viewHeight / minZoom;
|
|
2817
|
+
const graphWidth = bounds.maxX - bounds.minX;
|
|
2818
|
+
const graphHeight = bounds.maxY - bounds.minY;
|
|
2819
|
+
const minX = bounds.minX + overlap * graphWidth - zoomViewWidth;
|
|
2820
|
+
const maxX = bounds.maxX - overlap * graphWidth + zoomViewWidth;
|
|
2821
|
+
const minY = bounds.minY + overlap * graphHeight - zoomViewHeight;
|
|
2822
|
+
const maxY = bounds.maxY - overlap * graphHeight + zoomViewHeight;
|
|
2823
|
+
return [
|
|
2824
|
+
[minX, minY],
|
|
2825
|
+
[maxX, maxY],
|
|
2826
|
+
];
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
function initSimulation() {
|
|
2830
|
+
if (!this.simulation) {
|
|
2831
|
+
this.simulation = d3Force.forceSimulation()
|
|
2832
|
+
.nodes(this.nodes)
|
|
2833
|
+
.force("link", d3Force.forceLink(this.links).id(this.nodeSettings.idGetter.bind(this)))
|
|
2834
|
+
.on("tick", () => {
|
|
2835
|
+
this.draw();
|
|
2836
|
+
})
|
|
2837
|
+
.on("end", () => {
|
|
2838
|
+
this.listeners.onSimulationEnd?.call?.(this);
|
|
2839
|
+
if (this.area) {
|
|
2840
|
+
initZoom.call(this, this.areaTransform);
|
|
2841
|
+
}
|
|
2842
|
+
if (this.graphSettings.showDrawTime) {
|
|
2843
|
+
getDrawTime();
|
|
2844
|
+
// eslint-disable-next-line no-console
|
|
2845
|
+
console.log(`nodes: ${this.nodes.length} | links: ${this.links.length}`);
|
|
2846
|
+
resetDrawTime();
|
|
2847
|
+
}
|
|
2848
|
+
})
|
|
2849
|
+
.stop();
|
|
2850
|
+
initSimulationForces.call(this);
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
function initSimulationForces() {
|
|
2854
|
+
if (!this.simulation)
|
|
2855
|
+
return;
|
|
2856
|
+
const linkForce = this.simulation.force("link");
|
|
2857
|
+
if (!linkForce)
|
|
2858
|
+
return;
|
|
2859
|
+
if ((!this.forceSettings.forces || !this.forceSettings.xForce) && this.simulation.force("x")) {
|
|
2860
|
+
this.simulation.force("x", null);
|
|
2861
|
+
}
|
|
2862
|
+
if ((!this.forceSettings.forces || !this.forceSettings.yForce) && this.simulation.force("y")) {
|
|
2863
|
+
this.simulation.force("y", null);
|
|
2864
|
+
}
|
|
2865
|
+
if ((!this.forceSettings.forces || !this.forceSettings.chargeForce) &&
|
|
2866
|
+
this.simulation.force("charge")) {
|
|
2867
|
+
this.simulation.force("charge", null);
|
|
2868
|
+
}
|
|
2869
|
+
if ((!this.forceSettings.forces || !this.forceSettings.centerForce) &&
|
|
2870
|
+
this.simulation.force("center")) {
|
|
2871
|
+
this.simulation.force("center", null);
|
|
2872
|
+
}
|
|
2873
|
+
if (!this.forceSettings.forces || !this.forceSettings.linkForce) {
|
|
2874
|
+
linkForce.distance(0).strength(0).iterations(0);
|
|
2875
|
+
}
|
|
2876
|
+
if (this.forceSettings.forces && this.forceSettings.linkForce) {
|
|
2877
|
+
linkForce
|
|
2878
|
+
.distance(this.forceSettings.linkDistance)
|
|
2879
|
+
.strength(this.forceSettings.linkStrength)
|
|
2880
|
+
.iterations(this.forceSettings.linkIterations);
|
|
2881
|
+
}
|
|
2882
|
+
if (this.forceSettings.forces && this.forceSettings.xForce) {
|
|
2883
|
+
this.simulation.force("x", d3Force.forceX(this.forceSettings.xPosition).strength(this.forceSettings.xStrength));
|
|
2884
|
+
}
|
|
2885
|
+
if (this.forceSettings.forces && this.forceSettings.yForce) {
|
|
2886
|
+
this.simulation.force("y", d3Force.forceY(this.forceSettings.yPosition).strength(this.forceSettings.yStrength));
|
|
2887
|
+
}
|
|
2888
|
+
if (this.forceSettings.forces && this.forceSettings.chargeForce) {
|
|
2889
|
+
this.simulation.force("charge", d3Force.forceManyBody()
|
|
2890
|
+
.strength(this.forceSettings.chargeStrength)
|
|
2891
|
+
.distanceMax(Infinity)
|
|
2892
|
+
.distanceMin(this.forceSettings.chargeDistanceMin));
|
|
2893
|
+
}
|
|
2894
|
+
if (this.forceSettings.forces && this.forceSettings.centerForce) {
|
|
2895
|
+
this.simulation.force("center", d3Force.forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
|
|
2896
|
+
}
|
|
2897
|
+
initCollideForce.call(this, true);
|
|
2898
|
+
}
|
|
2899
|
+
function initCollideForce(forceUpdate) {
|
|
2900
|
+
if (!this.simulation)
|
|
2901
|
+
return;
|
|
2902
|
+
if ((!this.forceSettings.collideForce || !this.forceSettings.forces) &&
|
|
2903
|
+
this.simulation.force("collide")) {
|
|
2904
|
+
this.simulation.force("collide", null);
|
|
2905
|
+
}
|
|
2906
|
+
if (this.forceSettings.forces && this.forceSettings.collideForce) {
|
|
2907
|
+
const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
|
|
2908
|
+
const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
|
|
2909
|
+
const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
|
|
2910
|
+
if (isMaxCollideNodes && isMaxCollideLinks) {
|
|
2911
|
+
this.simulation.force("collide", null);
|
|
2912
|
+
}
|
|
2913
|
+
else if (!this.simulation.force("collide") || forceUpdate) {
|
|
2914
|
+
this.simulation.force("collide", d3Force.forceCollide()
|
|
2915
|
+
.radius((node, index) => {
|
|
2916
|
+
const nodeOptions = this.nodeOptionsCache[index];
|
|
2917
|
+
if (!nodeOptions)
|
|
2918
|
+
return 0;
|
|
2919
|
+
switch (nodeOptions.shape) {
|
|
2920
|
+
case "circle": {
|
|
2921
|
+
if (this.forceSettings.collideRadius) {
|
|
2922
|
+
return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
|
|
2923
|
+
}
|
|
2924
|
+
return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
|
|
2925
|
+
}
|
|
2926
|
+
case "square": {
|
|
2927
|
+
return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
|
|
2928
|
+
this.forceSettings.collideAdditionalRadius);
|
|
2929
|
+
}
|
|
2930
|
+
case "text": {
|
|
2931
|
+
return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
|
|
2932
|
+
this.forceSettings.collideAdditionalRadius);
|
|
2933
|
+
}
|
|
2934
|
+
default: {
|
|
2935
|
+
if (this.forceSettings.collideRadius) {
|
|
2936
|
+
return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
|
|
2937
|
+
}
|
|
2938
|
+
return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
})
|
|
2942
|
+
.strength(this.forceSettings.collideStrength)
|
|
2943
|
+
.iterations(this.forceSettings.collideIterations));
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2462
2947
|
|
|
2463
2948
|
class GraphCanvas {
|
|
2464
2949
|
/** initial data */
|
|
2465
2950
|
nodes;
|
|
2466
2951
|
links;
|
|
2467
|
-
particles =
|
|
2952
|
+
particles = [];
|
|
2468
2953
|
width;
|
|
2469
2954
|
height;
|
|
2470
2955
|
root;
|
|
@@ -2481,19 +2966,30 @@ class GraphCanvas {
|
|
|
2481
2966
|
context;
|
|
2482
2967
|
simulation;
|
|
2483
2968
|
areaTransform = d3Zoom.zoomIdentity;
|
|
2484
|
-
|
|
2969
|
+
_translateExtent;
|
|
2485
2970
|
draw;
|
|
2486
2971
|
eventAbortController;
|
|
2487
|
-
cachedNodeText =
|
|
2488
|
-
cachedNodeLabel =
|
|
2489
|
-
linkOptionsCache =
|
|
2490
|
-
nodeOptionsCache =
|
|
2972
|
+
cachedNodeText = [];
|
|
2973
|
+
cachedNodeLabel = [];
|
|
2974
|
+
linkOptionsCache = [];
|
|
2975
|
+
nodeOptionsCache = [];
|
|
2491
2976
|
isDragging = false;
|
|
2492
2977
|
highlightedNode = null;
|
|
2493
2978
|
highlightedLink = null;
|
|
2494
2979
|
highlightedNeighbors = null;
|
|
2495
2980
|
highlightProgress = 1;
|
|
2496
|
-
|
|
2981
|
+
highlightStart = null;
|
|
2982
|
+
highlightPositive = false;
|
|
2983
|
+
highlightController;
|
|
2984
|
+
_lastNodeZoomK;
|
|
2985
|
+
_lastLinkZoomK;
|
|
2986
|
+
_zoomAnimating = false;
|
|
2987
|
+
isSelecting = false;
|
|
2988
|
+
selectionRect = null;
|
|
2989
|
+
// protected areaRect: DOMRect | undefined;
|
|
2990
|
+
get areaRect() {
|
|
2991
|
+
return this.area?.getBoundingClientRect();
|
|
2992
|
+
}
|
|
2497
2993
|
get simulationWorking() {
|
|
2498
2994
|
const simulationAlpha = this.simulation?.alpha?.() ?? 0;
|
|
2499
2995
|
const simulationAlphaMin = this.simulation?.alphaMin?.() ?? 0;
|
|
@@ -2522,21 +3018,72 @@ class GraphCanvas {
|
|
|
2522
3018
|
get dpi() {
|
|
2523
3019
|
return devicePixelRatio;
|
|
2524
3020
|
}
|
|
2525
|
-
getData() {
|
|
3021
|
+
getData = () => {
|
|
2526
3022
|
return {
|
|
2527
3023
|
links: this.links,
|
|
2528
3024
|
nodes: this.nodes,
|
|
2529
3025
|
};
|
|
2530
|
-
}
|
|
2531
|
-
|
|
3026
|
+
};
|
|
3027
|
+
fitToView = (margin = this.graphSettings.zoomToFitMargin, duration = this.graphSettings.zoomAnimationDuration) => {
|
|
3028
|
+
const area = this.area;
|
|
3029
|
+
if (!area)
|
|
3030
|
+
return;
|
|
3031
|
+
const bounds = computeGraphBounds(this.nodes);
|
|
3032
|
+
if (!bounds)
|
|
3033
|
+
return;
|
|
3034
|
+
const graphWidth = bounds.maxX - bounds.minX;
|
|
3035
|
+
const graphHeight = bounds.maxY - bounds.minY;
|
|
3036
|
+
if (graphWidth === 0 || graphHeight === 0)
|
|
3037
|
+
return;
|
|
3038
|
+
const graphCenterX = bounds.minX + graphWidth / 2;
|
|
3039
|
+
const graphCenterY = bounds.minY + graphHeight / 2;
|
|
3040
|
+
const scale = (1 - margin) / Math.max(graphWidth / this.width, graphHeight / this.height);
|
|
3041
|
+
const clampedScale = Math.min(scale, this.graphSettings.zoomExtent?.[1] ?? scale);
|
|
3042
|
+
const target = d3Zoom.zoomIdentity
|
|
3043
|
+
.translate(this.width / 2, this.height / 2)
|
|
3044
|
+
.scale(clampedScale)
|
|
3045
|
+
.translate(-graphCenterX, -graphCenterY);
|
|
3046
|
+
if (!this.graphSettings.zoomAnimation) {
|
|
3047
|
+
this.areaTransform = target;
|
|
3048
|
+
d3Zoom.zoom().transform(d3Selection.select(area), target);
|
|
3049
|
+
this.clearCache(true);
|
|
3050
|
+
this.tick();
|
|
3051
|
+
return;
|
|
3052
|
+
}
|
|
3053
|
+
this.animateZoom(area, target, this.areaTransform, duration);
|
|
3054
|
+
};
|
|
3055
|
+
focusOnNode = (nodeId, scale = this.graphSettings.zoomToNodeScale, duration = this.graphSettings.zoomAnimationDuration) => {
|
|
3056
|
+
const area = this.area;
|
|
3057
|
+
if (!area)
|
|
3058
|
+
return;
|
|
3059
|
+
const node = this.nodes.find((n) => n.id === nodeId);
|
|
3060
|
+
if (!node)
|
|
3061
|
+
return;
|
|
3062
|
+
if (node.x == undefined || node.y == undefined)
|
|
3063
|
+
return;
|
|
3064
|
+
const target = d3Zoom.zoomIdentity
|
|
3065
|
+
.translate(this.width / 2, this.height / 2)
|
|
3066
|
+
.scale(scale)
|
|
3067
|
+
.translate(-node.x, -node.y);
|
|
3068
|
+
if (!this.graphSettings.zoomAnimation) {
|
|
3069
|
+
this.areaTransform = target;
|
|
3070
|
+
d3Zoom.zoom().transform(d3Selection.select(area), target);
|
|
3071
|
+
this.clearCache(true);
|
|
3072
|
+
this.tick();
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
this.animateZoom(area, target, this.areaTransform, duration);
|
|
3076
|
+
};
|
|
3077
|
+
changeData = (options, alpha = 0.5, clearCache = true, precompute = false) => {
|
|
2532
3078
|
if (options.links != undefined)
|
|
2533
3079
|
this.links = options.links;
|
|
2534
3080
|
if (options.nodes != undefined)
|
|
2535
3081
|
this.nodes = options.nodes;
|
|
2536
|
-
if (options.nodes != undefined || options.links != undefined)
|
|
2537
|
-
this.updateData(alpha, clearCache);
|
|
2538
|
-
|
|
2539
|
-
|
|
3082
|
+
if (options.nodes != undefined || options.links != undefined) {
|
|
3083
|
+
this.updateData(alpha, clearCache, precompute);
|
|
3084
|
+
}
|
|
3085
|
+
};
|
|
3086
|
+
changeSettings = (options, clearCache = true, precompute = false) => {
|
|
2540
3087
|
if (options.graphSettings) {
|
|
2541
3088
|
this.graphSettings = graphSettingsGetter(options.graphSettings, this.graphSettings);
|
|
2542
3089
|
this.draw = initDraw.call(this);
|
|
@@ -2583,16 +3130,15 @@ class GraphCanvas {
|
|
|
2583
3130
|
initCollideForce.call(this, true);
|
|
2584
3131
|
}
|
|
2585
3132
|
if (options.forceSettings) {
|
|
2586
|
-
return void this.updateSimulation();
|
|
3133
|
+
return void this.updateSimulation(precompute);
|
|
2587
3134
|
}
|
|
2588
3135
|
this.tick();
|
|
2589
|
-
}
|
|
2590
|
-
updateRect() {
|
|
2591
|
-
if (!this.area)
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
updateSize() {
|
|
3136
|
+
};
|
|
3137
|
+
updateRect = () => {
|
|
3138
|
+
// if (!this.area) return;
|
|
3139
|
+
// this.areaRect = this.area.getBoundingClientRect();
|
|
3140
|
+
};
|
|
3141
|
+
updateSize = () => {
|
|
2596
3142
|
if (!this.area)
|
|
2597
3143
|
return;
|
|
2598
3144
|
const { width, height } = this.root.getBoundingClientRect();
|
|
@@ -2600,57 +3146,102 @@ class GraphCanvas {
|
|
|
2600
3146
|
this.height = height;
|
|
2601
3147
|
this.area.width = this.dpi * this.width;
|
|
2602
3148
|
this.area.height = this.dpi * this.height;
|
|
2603
|
-
this.
|
|
3149
|
+
this.updateRect();
|
|
2604
3150
|
this.context = this.area.getContext("2d");
|
|
2605
3151
|
if (!this.context)
|
|
2606
3152
|
throw new Error("couldn't create canvas context");
|
|
2607
3153
|
this.context.scale(this.dpi, this.dpi);
|
|
2608
3154
|
this.draw();
|
|
2609
|
-
}
|
|
2610
|
-
clearCache(keys) {
|
|
3155
|
+
};
|
|
3156
|
+
clearCache = (keys) => {
|
|
2611
3157
|
if (keys === true) {
|
|
2612
|
-
|
|
2613
|
-
|
|
3158
|
+
this.nodeOptionsCache.length = 0;
|
|
3159
|
+
this.linkOptionsCache.length = 0;
|
|
3160
|
+
this.cachedNodeLabel.length = 0;
|
|
3161
|
+
this.cachedNodeText.length = 0;
|
|
2614
3162
|
}
|
|
2615
3163
|
else if (jsHelpers.isArray(keys)) {
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
3164
|
+
for (const key of keys) {
|
|
3165
|
+
switch (key) {
|
|
3166
|
+
case GRAPH_CACHE_TYPE.NodeText: {
|
|
3167
|
+
this.cachedNodeText.length = 0;
|
|
3168
|
+
break;
|
|
3169
|
+
}
|
|
3170
|
+
case GRAPH_CACHE_TYPE.NodeLabel: {
|
|
3171
|
+
this.cachedNodeLabel.length = 0;
|
|
3172
|
+
break;
|
|
3173
|
+
}
|
|
3174
|
+
case GRAPH_CACHE_TYPE.NodeOptions: {
|
|
3175
|
+
this.nodeOptionsCache.length = 0;
|
|
3176
|
+
break;
|
|
3177
|
+
}
|
|
3178
|
+
case GRAPH_CACHE_TYPE.LinkOptions: {
|
|
3179
|
+
this.linkOptionsCache.length = 0;
|
|
3180
|
+
break;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
2627
3183
|
}
|
|
2628
3184
|
}
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
3185
|
+
updateNodeCache.call(this);
|
|
3186
|
+
updateLinkCache.call(this);
|
|
3187
|
+
};
|
|
3188
|
+
tick = () => {
|
|
3189
|
+
if (!this.simulationWorking)
|
|
2632
3190
|
this.draw();
|
|
2633
|
-
}
|
|
2634
|
-
restart(alpha) {
|
|
2635
|
-
if (this.simulation)
|
|
3191
|
+
};
|
|
3192
|
+
restart = (alpha, options) => {
|
|
3193
|
+
if (!this.simulation)
|
|
3194
|
+
return;
|
|
3195
|
+
const settings = {
|
|
3196
|
+
precompute: options?.precompute ?? this.forceSettings.precompute,
|
|
3197
|
+
precomputeMaxTimeMs: options?.precomputeMaxTimeMs ?? this.forceSettings?.precomputeMaxTimeMs,
|
|
3198
|
+
precomputeMaxTicks: options?.precomputeMaxTicks ?? this.forceSettings?.precomputeMaxTicks ?? 300,
|
|
3199
|
+
precomputeDisableForcesAfter: options?.precomputeDisableForcesAfter ??
|
|
3200
|
+
this.forceSettings?.precomputeDisableForcesAfter ??
|
|
3201
|
+
false,
|
|
3202
|
+
};
|
|
3203
|
+
if (!settings.precompute) {
|
|
2636
3204
|
this.simulation.alpha(alpha ?? 1).restart();
|
|
2637
|
-
|
|
2638
|
-
|
|
3205
|
+
return;
|
|
3206
|
+
}
|
|
3207
|
+
if (settings.precomputeDisableForcesAfter) {
|
|
3208
|
+
this.forceSettings = forceSettingsGetter({ forces: true }, this.forceSettings);
|
|
3209
|
+
}
|
|
3210
|
+
initSimulationForces.call(this);
|
|
3211
|
+
this.simulation.stop();
|
|
3212
|
+
this.simulation.alpha(alpha ?? 1);
|
|
3213
|
+
const startTime = performance.now();
|
|
3214
|
+
let ticks = 0;
|
|
3215
|
+
while (performance.now() - startTime < settings.precomputeMaxTimeMs &&
|
|
3216
|
+
ticks < settings.precomputeMaxTicks) {
|
|
3217
|
+
this.simulation.tick(1);
|
|
3218
|
+
ticks++;
|
|
3219
|
+
if (this.simulation.alpha() <= this.simulation.alphaMin())
|
|
3220
|
+
break;
|
|
3221
|
+
}
|
|
3222
|
+
if (settings.precomputeDisableForcesAfter) {
|
|
3223
|
+
this.forceSettings = forceSettingsGetter({ forces: false }, this.forceSettings);
|
|
3224
|
+
}
|
|
3225
|
+
initSimulationForces.call(this);
|
|
3226
|
+
this.simulation.restart();
|
|
3227
|
+
this.tick();
|
|
3228
|
+
};
|
|
3229
|
+
start = () => {
|
|
2639
3230
|
if (this.simulation)
|
|
2640
3231
|
this.simulation.alpha(1).restart();
|
|
2641
3232
|
if (this.container)
|
|
2642
3233
|
this.container.style.display = "block";
|
|
2643
|
-
}
|
|
2644
|
-
stop() {
|
|
3234
|
+
};
|
|
3235
|
+
stop = () => {
|
|
2645
3236
|
if (this.simulation)
|
|
2646
3237
|
this.simulation.stop();
|
|
2647
3238
|
if (this.container)
|
|
2648
3239
|
this.container.style.display = "none";
|
|
2649
|
-
}
|
|
2650
|
-
create() {
|
|
3240
|
+
};
|
|
3241
|
+
create = () => {
|
|
2651
3242
|
this.init();
|
|
2652
|
-
}
|
|
2653
|
-
destroy() {
|
|
3243
|
+
};
|
|
3244
|
+
destroy = () => {
|
|
2654
3245
|
if (this.simulation) {
|
|
2655
3246
|
this.simulation.stop();
|
|
2656
3247
|
this.simulation = undefined;
|
|
@@ -2658,39 +3249,79 @@ class GraphCanvas {
|
|
|
2658
3249
|
this.clearHTMLElements();
|
|
2659
3250
|
this.clearState();
|
|
2660
3251
|
this.clearCache(true);
|
|
2661
|
-
}
|
|
2662
|
-
|
|
3252
|
+
};
|
|
3253
|
+
getPointerAreaPosition = (event) => {
|
|
3254
|
+
let localX;
|
|
3255
|
+
let localY;
|
|
3256
|
+
if ("offsetX" in event) {
|
|
3257
|
+
// localX = event.offsetX;
|
|
3258
|
+
// localY = event.offsetY;
|
|
3259
|
+
const rect = this.areaRect;
|
|
3260
|
+
if (!rect)
|
|
3261
|
+
return [0, 0];
|
|
3262
|
+
localX = event.clientX - rect.left;
|
|
3263
|
+
localY = event.clientY - rect.top;
|
|
3264
|
+
}
|
|
3265
|
+
else {
|
|
3266
|
+
const rect = this.areaRect;
|
|
3267
|
+
if (!rect)
|
|
3268
|
+
return [0, 0];
|
|
3269
|
+
const clientX = event.touches[0]?.clientX ?? event.changedTouches[0]?.clientX;
|
|
3270
|
+
const clientY = event.touches[0]?.clientY ?? event.changedTouches[0]?.clientY;
|
|
3271
|
+
localX = clientX - rect.left;
|
|
3272
|
+
localY = clientY - rect.top;
|
|
3273
|
+
}
|
|
3274
|
+
const px = (localX - this.areaTransform.x) / this.areaTransform.k;
|
|
3275
|
+
const py = (localY - this.areaTransform.y) / this.areaTransform.k;
|
|
3276
|
+
return [px, py];
|
|
3277
|
+
};
|
|
3278
|
+
clearHTMLElements = () => {
|
|
2663
3279
|
this.root.replaceChildren();
|
|
2664
3280
|
this.area = undefined;
|
|
2665
3281
|
this.context = undefined;
|
|
2666
3282
|
this.container = undefined;
|
|
2667
3283
|
this.eventAbortController.abort();
|
|
2668
3284
|
this.eventAbortController = new AbortController();
|
|
2669
|
-
}
|
|
2670
|
-
updateSimulation() {
|
|
3285
|
+
};
|
|
3286
|
+
updateSimulation = (precompute = false) => {
|
|
2671
3287
|
if (this.simulation) {
|
|
2672
3288
|
initSimulationForces.call(this);
|
|
2673
|
-
this.
|
|
2674
|
-
this.simulation.restart();
|
|
3289
|
+
this.restart(1, { precompute });
|
|
2675
3290
|
}
|
|
2676
|
-
}
|
|
2677
|
-
clearState() {
|
|
3291
|
+
};
|
|
3292
|
+
clearState = () => {
|
|
2678
3293
|
this.isDragging = false;
|
|
2679
3294
|
this.highlightedNode = null;
|
|
2680
3295
|
this.highlightedLink = null;
|
|
2681
3296
|
this.highlightedNeighbors = null;
|
|
2682
3297
|
this.highlightProgress = 0;
|
|
2683
|
-
this.
|
|
2684
|
-
|
|
2685
|
-
|
|
3298
|
+
this.highlightStart = null;
|
|
3299
|
+
this.highlightPositive = false;
|
|
3300
|
+
this.isSelecting = false;
|
|
3301
|
+
this.selectionRect = null;
|
|
3302
|
+
};
|
|
3303
|
+
init = () => {
|
|
3304
|
+
initArea.call(this);
|
|
3305
|
+
updateNodeCache.call(this);
|
|
3306
|
+
updateLinkCache.call(this);
|
|
3307
|
+
initSimulation.call(this);
|
|
3308
|
+
this.restart(1);
|
|
3309
|
+
initDnd.call(this);
|
|
3310
|
+
initZoom.call(this);
|
|
3311
|
+
initResize.call(this);
|
|
3312
|
+
initSelection.call(this);
|
|
3313
|
+
initPointer.call(this);
|
|
3314
|
+
updateNodeCache.call(this);
|
|
3315
|
+
updateLinkCache.call(this);
|
|
3316
|
+
this.tick();
|
|
3317
|
+
};
|
|
3318
|
+
updateData = (alpha = 0.5, clearCache = true, precompute = false) => {
|
|
2686
3319
|
if (clearCache) {
|
|
2687
3320
|
this.clearCache(clearCache);
|
|
2688
3321
|
}
|
|
2689
3322
|
if (this.simulation) {
|
|
2690
3323
|
initCollideForce.call(this, false);
|
|
2691
|
-
this.simulation
|
|
2692
|
-
.nodes(this.nodes)
|
|
2693
|
-
.force("link", d3Force.forceLink(this.links)
|
|
3324
|
+
this.simulation.nodes(this.nodes).force("link", d3Force.forceLink(this.links)
|
|
2694
3325
|
.id(this.nodeSettings.idGetter.bind(this))
|
|
2695
3326
|
.distance(this.forceSettings.forces && this.forceSettings.linkForce
|
|
2696
3327
|
? this.forceSettings.linkDistance
|
|
@@ -2700,21 +3331,95 @@ class GraphCanvas {
|
|
|
2700
3331
|
: 0)
|
|
2701
3332
|
.iterations(this.forceSettings.forces && this.forceSettings.linkForce
|
|
2702
3333
|
? this.forceSettings.linkIterations
|
|
2703
|
-
: 0))
|
|
2704
|
-
|
|
2705
|
-
|
|
3334
|
+
: 0));
|
|
3335
|
+
this.restart(alpha, { precompute });
|
|
3336
|
+
initZoom.call(this, this.areaTransform);
|
|
2706
3337
|
}
|
|
2707
|
-
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
3338
|
+
};
|
|
3339
|
+
animateHighlight = (node, link, baseDuration = this.highlightSettings.highlightDuration) => {
|
|
3340
|
+
let positive = true;
|
|
3341
|
+
if (node && (this.highlightedNode !== node || !this.highlightPositive)) {
|
|
3342
|
+
this.highlightedNode = node;
|
|
3343
|
+
this.highlightedNeighbors = new Set(node.neighbors);
|
|
3344
|
+
this.particles = [];
|
|
3345
|
+
this.highlightedLink = null;
|
|
3346
|
+
this.highlightStart = performance.now();
|
|
3347
|
+
}
|
|
3348
|
+
else if (link && (this.highlightedLink !== link || !this.highlightPositive)) {
|
|
3349
|
+
const { sourceId, targetId } = extractLinkPointIds(link);
|
|
3350
|
+
this.highlightProgress = 0;
|
|
3351
|
+
this.highlightedLink = link;
|
|
3352
|
+
this.highlightedNeighbors = new Set([sourceId, targetId]);
|
|
3353
|
+
this.particles = [];
|
|
3354
|
+
this.highlightedNode = null;
|
|
3355
|
+
this.highlightStart = performance.now();
|
|
3356
|
+
}
|
|
3357
|
+
else if (!node && !link && this.highlightPositive) {
|
|
3358
|
+
positive = false;
|
|
3359
|
+
}
|
|
3360
|
+
else {
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3363
|
+
if (this.highlightController) {
|
|
3364
|
+
this.highlightController.abort();
|
|
3365
|
+
}
|
|
3366
|
+
const controller = new AbortController();
|
|
3367
|
+
this.highlightPositive = positive;
|
|
3368
|
+
this.highlightController = controller;
|
|
3369
|
+
const startTime = performance.now();
|
|
3370
|
+
const startProgress = this.highlightProgress;
|
|
3371
|
+
const targetProgress = positive ? 1 : 0;
|
|
3372
|
+
const delta = targetProgress - startProgress;
|
|
3373
|
+
const duration = baseDuration * Math.abs(delta);
|
|
3374
|
+
const animate = () => {
|
|
3375
|
+
if (controller.signal.aborted)
|
|
3376
|
+
return;
|
|
3377
|
+
const elapsed = performance.now() - startTime;
|
|
3378
|
+
const t = Math.min(elapsed / duration, 1);
|
|
3379
|
+
const current = startProgress + delta * t;
|
|
3380
|
+
const eased = current < 0.5 ? 4 * current * current * current : 1 - (-2 * current + 2) ** 3 / 2;
|
|
3381
|
+
this.highlightProgress = eased;
|
|
3382
|
+
if (t < 1 || positive) {
|
|
3383
|
+
requestAnimationFrame(animate);
|
|
3384
|
+
this.draw();
|
|
3385
|
+
}
|
|
3386
|
+
else {
|
|
3387
|
+
this.highlightedNode = null;
|
|
3388
|
+
this.highlightedLink = null;
|
|
3389
|
+
this.highlightedNeighbors = null;
|
|
3390
|
+
this.highlightStart = null;
|
|
3391
|
+
this.particles = [];
|
|
3392
|
+
this.tick();
|
|
3393
|
+
}
|
|
3394
|
+
};
|
|
3395
|
+
requestAnimationFrame(animate);
|
|
3396
|
+
};
|
|
3397
|
+
animateZoom = (area, target, start, duration) => {
|
|
3398
|
+
this._zoomAnimating = true;
|
|
3399
|
+
const startTime = performance.now();
|
|
3400
|
+
const animate = () => {
|
|
3401
|
+
const elapsed = performance.now() - startTime;
|
|
3402
|
+
const t = Math.min(elapsed / duration, 1);
|
|
3403
|
+
const eased = t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
|
|
3404
|
+
const x = start.x + (target.x - start.x) * eased;
|
|
3405
|
+
const y = start.y + (target.y - start.y) * eased;
|
|
3406
|
+
const k = start.k + (target.k - start.k) * eased;
|
|
3407
|
+
this.areaTransform = new d3Zoom.ZoomTransform(k, x, y);
|
|
3408
|
+
d3Zoom.zoom().transform(d3Selection.select(area), this.areaTransform);
|
|
3409
|
+
updateLinkCache.call(this);
|
|
3410
|
+
updateNodeCache.call(this);
|
|
3411
|
+
this.tick();
|
|
3412
|
+
if (t < 1) {
|
|
3413
|
+
requestAnimationFrame(animate);
|
|
3414
|
+
}
|
|
3415
|
+
else {
|
|
3416
|
+
this._zoomAnimating = false;
|
|
3417
|
+
this.clearCache(true);
|
|
3418
|
+
this.tick();
|
|
3419
|
+
}
|
|
3420
|
+
};
|
|
3421
|
+
requestAnimationFrame(animate);
|
|
3422
|
+
};
|
|
2718
3423
|
}
|
|
2719
3424
|
|
|
2720
3425
|
const FORCE_CONTROLS = [
|
|
@@ -2875,22 +3580,13 @@ const HIGHLIGHT_COMMON_CONTROLS = [
|
|
|
2875
3580
|
label: "Расширение границы связи для курсора",
|
|
2876
3581
|
},
|
|
2877
3582
|
{
|
|
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,
|
|
3583
|
+
id: "highlightDuration",
|
|
3584
|
+
initialValue: HIGHLIGHT_SETTINGS.highlightDuration,
|
|
3585
|
+
max: 1000,
|
|
3586
|
+
min: 10,
|
|
2891
3587
|
step: 1,
|
|
2892
3588
|
type: "range",
|
|
2893
|
-
label: "Скорость
|
|
3589
|
+
label: "Скорость анимации",
|
|
2894
3590
|
},
|
|
2895
3591
|
];
|
|
2896
3592
|
const HIGHLIGHT_BY_NODE_FOR_NODE_CONTROLS = [
|
|
@@ -3363,19 +4059,22 @@ const LINK_SETTINGS_CONTROLS = [
|
|
|
3363
4059
|
initialValue: LINK_SETTINGS.particles,
|
|
3364
4060
|
},
|
|
3365
4061
|
{
|
|
3366
|
-
id: "
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
4062
|
+
id: "particleSpeedByDistance",
|
|
4063
|
+
initialValue: LINK_SETTINGS.particleSpeedByDistance,
|
|
4064
|
+
max: 500,
|
|
4065
|
+
min: 1,
|
|
4066
|
+
step: 1,
|
|
4067
|
+
type: "range",
|
|
4068
|
+
label: "Скорость частицы на один пиксель",
|
|
3370
4069
|
},
|
|
3371
4070
|
{
|
|
3372
|
-
id: "
|
|
3373
|
-
initialValue: LINK_SETTINGS.
|
|
3374
|
-
max:
|
|
3375
|
-
min:
|
|
3376
|
-
step:
|
|
4071
|
+
id: "particleCountByDistance",
|
|
4072
|
+
initialValue: LINK_SETTINGS.particleCountByDistance,
|
|
4073
|
+
max: 250,
|
|
4074
|
+
min: 1,
|
|
4075
|
+
step: 1,
|
|
3377
4076
|
type: "range",
|
|
3378
|
-
label: "
|
|
4077
|
+
label: "Количество пикселей для появления частицы",
|
|
3379
4078
|
},
|
|
3380
4079
|
];
|
|
3381
4080
|
const LINK_OPTIONS_LINK_CONTROLS = [
|
|
@@ -3470,24 +4169,6 @@ const LINK_OPTIONS_PARTICLE_CONTROLS = [
|
|
|
3470
4169
|
label: "Радиус",
|
|
3471
4170
|
initialValue: LINK_OPTIONS.particleRadius,
|
|
3472
4171
|
},
|
|
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
4172
|
{
|
|
3492
4173
|
id: "particleBorderColor",
|
|
3493
4174
|
type: "color",
|
|
@@ -3921,10 +4602,19 @@ exports.NODE_OPTIONS_NODE_CONTROLS = NODE_OPTIONS_NODE_CONTROLS;
|
|
|
3921
4602
|
exports.NODE_OPTIONS_TEXT_CONTROLS = NODE_OPTIONS_TEXT_CONTROLS;
|
|
3922
4603
|
exports.NODE_SETTINGS = NODE_SETTINGS;
|
|
3923
4604
|
exports.NODE_SETTINGS_CONTROLS = NODE_SETTINGS_CONTROLS;
|
|
4605
|
+
exports.PERFOMANCE_NODE_OPTIONS = PERFOMANCE_NODE_OPTIONS;
|
|
4606
|
+
exports.PERFORMANCE_FORCE_SETTINGS = PERFORMANCE_FORCE_SETTINGS;
|
|
4607
|
+
exports.PERFORMANCE_GRAPH_SETTINGS = PERFORMANCE_GRAPH_SETTINGS;
|
|
4608
|
+
exports.PERFORMANCE_HIGHLIGHT_SETTINGS = PERFORMANCE_HIGHLIGHT_SETTINGS;
|
|
4609
|
+
exports.PERFORMANCE_LINK_OPTIONS = PERFORMANCE_LINK_OPTIONS;
|
|
4610
|
+
exports.PERFORMANCE_LINK_SETTINGS = PERFORMANCE_LINK_SETTINGS;
|
|
4611
|
+
exports.PERFORMANCE_NODE_SETTINGS = PERFORMANCE_NODE_SETTINGS;
|
|
3924
4612
|
exports.animationByProgress = animationByProgress;
|
|
4613
|
+
exports.approximateQuadraticBezierLength = approximateQuadraticBezierLength;
|
|
3925
4614
|
exports.calculateLinkPositionByNode = calculateLinkPositionByNode;
|
|
3926
4615
|
exports.colorGetter = colorGetter;
|
|
3927
4616
|
exports.colorToRgb = colorToRgb;
|
|
4617
|
+
exports.computeGraphBounds = computeGraphBounds;
|
|
3928
4618
|
exports.dragPlaceCoefficientGetter = dragPlaceCoefficientGetter;
|
|
3929
4619
|
exports.drawText = drawText;
|
|
3930
4620
|
exports.extractLinkPointIds = extractLinkPointIds;
|
|
@@ -3936,6 +4626,7 @@ exports.getDrawNode = getDrawNode;
|
|
|
3936
4626
|
exports.getParticlePosition = getParticlePosition;
|
|
3937
4627
|
exports.graphSettingsGetter = graphSettingsGetter;
|
|
3938
4628
|
exports.highlightSettingsGetter = highlightSettingsGetter;
|
|
4629
|
+
exports.isEmptyObject = isEmptyObject;
|
|
3939
4630
|
exports.isNodeVisible = isNodeVisible;
|
|
3940
4631
|
exports.isOverlapsNode = isOverlapsNode;
|
|
3941
4632
|
exports.linkByPointerGetter = linkByPointerGetter;
|