@krainovsd/graph 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/lib/cjs/index.cjs +945 -165
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/esm/index.js +3 -0
  4. package/lib/esm/index.js.map +1 -1
  5. package/lib/esm/lib/draw-time.js +25 -0
  6. package/lib/esm/lib/draw-time.js.map +1 -0
  7. package/lib/esm/lib/get-controls-info.js +541 -0
  8. package/lib/esm/lib/get-controls-info.js.map +1 -0
  9. package/lib/esm/module/GraphCanvas/GraphCanvas.js +218 -67
  10. package/lib/esm/module/GraphCanvas/GraphCanvas.js.map +1 -1
  11. package/lib/esm/module/GraphCanvas/constants/settings.js +26 -7
  12. package/lib/esm/module/GraphCanvas/constants/settings.js.map +1 -1
  13. package/lib/esm/module/GraphCanvas/lib/settings/link-settings-getter.js +7 -4
  14. package/lib/esm/module/GraphCanvas/lib/settings/link-settings-getter.js.map +1 -1
  15. package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js +6 -5
  16. package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js.map +1 -1
  17. package/lib/esm/module/GraphCanvas/lib/utils/animation-by-progress.js +12 -0
  18. package/lib/esm/module/GraphCanvas/lib/utils/animation-by-progress.js.map +1 -0
  19. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-radius.js +2 -2
  20. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-radius.js.map +1 -1
  21. package/lib/esm/module/GraphCanvas/lib/utils/get-particle-position.js +24 -0
  22. package/lib/esm/module/GraphCanvas/lib/utils/get-particle-position.js.map +1 -0
  23. package/lib/esm/module/GraphCanvas/lib/utils/is-node-visible.js +14 -0
  24. package/lib/esm/module/GraphCanvas/lib/utils/is-node-visible.js.map +1 -0
  25. package/lib/esm/module/GraphCanvas/lib/utils/link-iteration-extractor.js +1 -0
  26. package/lib/esm/module/GraphCanvas/lib/utils/link-iteration-extractor.js.map +1 -1
  27. package/lib/esm/module/GraphCanvas/lib/utils/node-iteration-extractor.js +1 -0
  28. package/lib/esm/module/GraphCanvas/lib/utils/node-iteration-extractor.js.map +1 -1
  29. package/lib/index.d.ts +72 -3
  30. package/package.json +4 -2
package/lib/cjs/index.cjs CHANGED
@@ -87,95 +87,6 @@ function rgbAnimationByProgress(start, end, progress) {
87
87
  };
88
88
  }
89
89
 
90
- const FORCE_SETTINGS = {
91
- centerPosition: {},
92
- centerStrength: 1,
93
- collideStrength: 0.1,
94
- collideAdditionalRadius: 4,
95
- collideIterations: 2,
96
- collideOffMax: { links: 0, nodes: 0 },
97
- collideOn: true,
98
- chargeStrength: -40,
99
- chargeDistanceMax: Infinity,
100
- chargeDistanceMin: 1,
101
- xForce: 0,
102
- xStrength: 0.1,
103
- yForce: 0,
104
- yStrength: 0.1,
105
- linkDistance: 30,
106
- linkIterations: 1,
107
- linkStrength: 1,
108
- collideRadius: null,
109
- };
110
- const GRAPH_SETTINGS = {
111
- zoomExtent: [0.1, 20],
112
- translateExtent: [[], []],
113
- translateExtentEnable: true,
114
- translateExtentCoefficient: [1.2, 1.1],
115
- highlightSizingAdditional: 0.5,
116
- highlightColorFadingMin: 0.15,
117
- highlightTextShiftXAdditional: 0,
118
- highlightTextShiftYAdditional: 2,
119
- highlightTextSizingAdditional: 1,
120
- highlightTextWeightAdditional: 0,
121
- highlightTextWidthAdditional: 10,
122
- highlightOnlyRoot: true,
123
- stickAfterDrag: false,
124
- highlightByHover: false,
125
- highlightLinkFadingMin: 0.21,
126
- highlightFadingMin: 0.21,
127
- highlightTextFadingMin: 0.21,
128
- highlightDownStep: 0.2,
129
- highlightUpStep: 0.2,
130
- dragPlaceCoefficient: dragPlaceCoefficientGetter,
131
- nodeRadiusInitial: 4,
132
- nodeRadiusCoefficient: 5,
133
- nodeRadiusFactor: 1,
134
- nodeRadiusFlexible: true,
135
- zoomInitial: null,
136
- };
137
- const NODE_SETTINGS = {
138
- alpha: 1,
139
- textAlpha: 1,
140
- borderColor: "#000000FF",
141
- borderWidth: 0.1,
142
- textWidth: 20,
143
- textShiftX: 0,
144
- textFont: "Arial",
145
- textAlign: "center",
146
- textColor: "#333",
147
- width: 1,
148
- radius: 4,
149
- textStyle: "normal",
150
- textWeight: 500,
151
- textGap: 1,
152
- highlightFading: false,
153
- highlightColor: true,
154
- highlightSizing: true,
155
- highlightTextFading: true,
156
- highlightTextSizing: true,
157
- };
158
- const LINK_SETTINGS = {
159
- alpha: 1,
160
- highlightFading: true,
161
- pretty: true,
162
- };
163
- const COMMON_SETTINGS = {
164
- linkColorZoomFar: "#999",
165
- linkColorZoomNear: "#000000FF",
166
- linkWidthZoomFar: 1,
167
- linkWidthZoomNear: 0.1,
168
- linkWidthZoomBorder: 1,
169
- linkColorZoomBorder: 1,
170
- nodeTextScaleMin: 1.5,
171
- nodeTextScaleMax: 20,
172
- nodeTextSizeMin: 1.5,
173
- nodeTextSizeMax: 3.5,
174
- nodeTextShiftYMin: 2.5,
175
- nodeTextShiftYMax: 4,
176
- nodeTextChangeStepCount: 200,
177
- };
178
-
179
90
  function forceSettingsGetter(settings, prevSettings) {
180
91
  return {
181
92
  ...(prevSettings ?? FORCE_SETTINGS),
@@ -190,17 +101,20 @@ function graphSettingsGetter(settings, prevSettings) {
190
101
  };
191
102
  }
192
103
 
193
- function linkSettingsGetter(settings) {
194
- return { options: settings?.options };
104
+ function linkSettingsGetter(settings, prevSettings) {
105
+ return { ...(prevSettings ?? LINK_SETTINGS), ...settings };
195
106
  }
196
107
  function linkOptionsGetter(_, __, ___, state) {
197
108
  return {
198
- ...LINK_SETTINGS,
109
+ ...LINK_OPTIONS,
199
110
  drawExtraLink: null,
200
111
  drawLink: null,
201
112
  color: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkColorZoomBorder
202
113
  ? COMMON_SETTINGS.linkColorZoomNear
203
114
  : COMMON_SETTINGS.linkColorZoomFar,
115
+ arrowColor: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkColorZoomBorder
116
+ ? COMMON_SETTINGS.linkColorZoomNear
117
+ : COMMON_SETTINGS.linkColorZoomFar,
204
118
  width: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkWidthZoomBorder
205
119
  ? COMMON_SETTINGS.linkWidthZoomNear
206
120
  : COMMON_SETTINGS.linkWidthZoomFar,
@@ -211,17 +125,18 @@ function listenersGetter(settings) {
211
125
  return settings ?? {};
212
126
  }
213
127
 
214
- function nodeSettingsGetter(settings) {
128
+ function nodeSettingsGetter(settings, prevNodeSettings) {
215
129
  return {
216
- idGetter: settings?.idGetter ?? nodeIdGetter,
217
- options: settings?.options,
130
+ ...(prevNodeSettings ?? NODE_SETTINGS),
131
+ idGetter: nodeIdGetter,
132
+ ...settings,
218
133
  };
219
134
  }
220
135
  const color = colorGetter();
221
136
  function nodeOptionsGetter(node, _, __, state) {
222
137
  const { textShiftY, textSize } = nodeTextSizeGetter(state?.areaTransform);
223
138
  return {
224
- ...NODE_SETTINGS,
139
+ ...NODE_OPTIONS,
225
140
  nodeDraw: null,
226
141
  nodeExtraDraw: null,
227
142
  textDraw: null,
@@ -410,7 +325,7 @@ function drawText({ context, id, textAlign, textColor, textFont, textStyle, text
410
325
  });
411
326
  }
412
327
 
413
- function calculateLinkPositionByRadius(link) {
328
+ function calculateLinkPositionByRadius(link, arrowSize = 0) {
414
329
  const source = link.source;
415
330
  const target = link.target;
416
331
  if (typeof source != "object" || typeof target != "object")
@@ -419,7 +334,7 @@ function calculateLinkPositionByRadius(link) {
419
334
  const dy = (target.y ?? 0) - (source.y ?? 0);
420
335
  const dr = Math.sqrt(dx * dx + dy * dy);
421
336
  const sourceRadius = source._radius ?? 0;
422
- const targetRadius = target._radius ?? 0;
337
+ const targetRadius = (target._radius ?? 0) + (arrowSize > 0 ? arrowSize * 0.85 : 0);
423
338
  return {
424
339
  x1: (source.x ?? 0) + (dx * sourceRadius) / dr,
425
340
  y1: (source.y ?? 0) + (dy * sourceRadius) / dr,
@@ -428,10 +343,723 @@ function calculateLinkPositionByRadius(link) {
428
343
  };
429
344
  }
430
345
 
346
+ function animationByProgress(initial, additional, progress) {
347
+ const max = initial + additional;
348
+ let current = initial + (max - initial) * progress;
349
+ if (current > max)
350
+ current = max;
351
+ if (current < initial)
352
+ current = initial;
353
+ return current;
354
+ }
355
+
356
+ const ADDITIONAL_VIEWPORT = 10;
357
+ function isNodeVisible(opts) {
358
+ const left = -opts.transform.x / opts.transform.k;
359
+ const right = (opts.width - opts.transform.x) / opts.transform.k;
360
+ const top = -opts.transform.y / opts.transform.k;
361
+ const bottom = (opts.height - opts.transform.y) / opts.transform.k;
362
+ return (left < opts.x + opts.radius + ADDITIONAL_VIEWPORT &&
363
+ opts.x - opts.radius - ADDITIONAL_VIEWPORT < right &&
364
+ top < opts.y + opts.radius + ADDITIONAL_VIEWPORT &&
365
+ opts.y - opts.radius - ADDITIONAL_VIEWPORT < bottom);
366
+ }
367
+
368
+ function getParticlePosition(opts) {
369
+ if (opts.particle.wait > 0) {
370
+ opts.particle.wait--;
371
+ return;
372
+ }
373
+ const remainingSteps = opts.totalSteps - opts.particle.step;
374
+ const progress = opts.particle.step / opts.totalSteps;
375
+ if (remainingSteps === 0) {
376
+ opts.particle.x = opts.xEnd;
377
+ opts.particle.y = opts.yEnd;
378
+ opts.particle.step = 0;
379
+ return;
380
+ }
381
+ const dx = opts.xEnd - opts.xStart;
382
+ const dy = opts.yEnd - opts.yStart;
383
+ const newX = opts.xStart + dx * progress;
384
+ const newY = opts.yStart + dy * progress;
385
+ opts.particle.x = newX;
386
+ opts.particle.y = newY;
387
+ opts.particle.step++;
388
+ }
389
+
390
+ const FORCE_SETTINGS = {
391
+ centerPosition: {},
392
+ centerStrength: 1,
393
+ collideStrength: 0.1,
394
+ collideAdditionalRadius: 4,
395
+ collideIterations: 2,
396
+ collideOffMax: { links: 0, nodes: 0 },
397
+ collideOn: true,
398
+ chargeStrength: -40,
399
+ chargeDistanceMax: Infinity,
400
+ chargeDistanceMin: 1,
401
+ xForce: 0,
402
+ xStrength: 0.1,
403
+ yForce: 0,
404
+ yStrength: 0.1,
405
+ linkDistance: 30,
406
+ linkIterations: 1,
407
+ linkStrength: 1,
408
+ collideRadius: null,
409
+ };
410
+ const GRAPH_SETTINGS = {
411
+ zoomExtent: [0.3, 10],
412
+ translateExtent: [[], []],
413
+ translateExtentEnable: true,
414
+ translateExtentCoefficient: [3, 3],
415
+ highlightSizingAdditional: 0.5,
416
+ highlightColorFadingMin: 0.15,
417
+ highlightTextShiftXAdditional: 0,
418
+ highlightTextShiftYAdditional: 2,
419
+ highlightTextSizingAdditional: 1,
420
+ highlightTextWeightAdditional: 0,
421
+ highlightTextWidthAdditional: 10,
422
+ highlightOnlyRoot: true,
423
+ stickAfterDrag: false,
424
+ highlightByHover: true,
425
+ highlightLinkFadingMin: 0.21,
426
+ highlightFadingMin: 0.21,
427
+ highlightTextFadingMin: 0.21,
428
+ highlightArrowFadingMin: 0.21,
429
+ highlightDownStep: 0.2,
430
+ highlightUpStep: 0.2,
431
+ dragPlaceCoefficient: dragPlaceCoefficientGetter,
432
+ nodeRadiusInitial: 4,
433
+ nodeRadiusCoefficient: 5,
434
+ nodeRadiusFactor: 1,
435
+ nodeRadiusFlexible: true,
436
+ zoomInitial: null,
437
+ showDrawTime: true,
438
+ showDrawTimeEveryTick: false,
439
+ };
440
+ const NODE_SETTINGS = {
441
+ cache: false,
442
+ };
443
+ const NODE_OPTIONS = {
444
+ alpha: 1,
445
+ textAlpha: 1,
446
+ borderColor: "#000000FF",
447
+ borderWidth: 0.1,
448
+ textWidth: 20,
449
+ textShiftX: 0,
450
+ textFont: "Arial",
451
+ textAlign: "center",
452
+ textColor: "#333",
453
+ radius: 4,
454
+ textStyle: "normal",
455
+ textWeight: 500,
456
+ textGap: 1,
457
+ highlightFading: true,
458
+ highlightColor: false,
459
+ highlightSizing: true,
460
+ highlightTextFading: true,
461
+ highlightTextSizing: true,
462
+ };
463
+ const LINK_SETTINGS = {
464
+ cache: false,
465
+ particles: true,
466
+ };
467
+ const LINK_OPTIONS = {
468
+ alpha: 1,
469
+ highlightFading: true,
470
+ pretty: true,
471
+ arrow: true,
472
+ arrowAlpha: 1,
473
+ arrowSize: 2,
474
+ arrowHighlightFading: true,
475
+ arrowReverseAppear: true,
476
+ particleAlpha: 1,
477
+ particleColor: "#000000FF",
478
+ particleCount: 2,
479
+ particleRadius: 0.5,
480
+ particleSteps: 60,
481
+ };
482
+ const COMMON_SETTINGS = {
483
+ linkColorZoomFar: "#999",
484
+ linkColorZoomNear: "#000000FF",
485
+ linkWidthZoomFar: 1,
486
+ linkWidthZoomNear: 0.1,
487
+ linkWidthZoomBorder: 1,
488
+ linkColorZoomBorder: 1,
489
+ nodeTextScaleMin: 1.5,
490
+ nodeTextScaleMax: 20,
491
+ nodeTextSizeMin: 1.5,
492
+ nodeTextSizeMax: 3.5,
493
+ nodeTextShiftYMin: 2.5,
494
+ nodeTextShiftYMax: 4,
495
+ nodeTextChangeStepCount: 200,
496
+ };
497
+
498
+ function getForceControls(keys) {
499
+ const FORCE_CONTROLS = [
500
+ {
501
+ id: "centerStrength",
502
+ initialValue: FORCE_SETTINGS.centerStrength,
503
+ label: "Граница центра",
504
+ max: 1,
505
+ min: 0,
506
+ step: 0.01,
507
+ type: "range",
508
+ },
509
+ {
510
+ id: "collideStrength",
511
+ initialValue: FORCE_SETTINGS.collideStrength,
512
+ label: "Сила отталкивания",
513
+ max: 1,
514
+ min: 0,
515
+ step: 0.01,
516
+ type: "range",
517
+ },
518
+ {
519
+ id: "collideAdditionalRadius",
520
+ initialValue: FORCE_SETTINGS.collideAdditionalRadius,
521
+ label: "Радиус отталкивания",
522
+ max: 300,
523
+ min: 0,
524
+ step: 0.1,
525
+ type: "range",
526
+ },
527
+ {
528
+ id: "collideIterations",
529
+ initialValue: FORCE_SETTINGS.collideIterations,
530
+ label: "Итерации отталкивания",
531
+ max: 10,
532
+ min: 0,
533
+ step: 1,
534
+ type: "range",
535
+ },
536
+ {
537
+ id: "chargeStrength",
538
+ initialValue: FORCE_SETTINGS.chargeStrength ,
539
+ label: "Гравитация",
540
+ max: 0,
541
+ min: -300,
542
+ step: 0.1,
543
+ type: "range",
544
+ },
545
+ {
546
+ type: "range",
547
+ id: "xForce",
548
+ min: 0,
549
+ max: 1,
550
+ step: 0.01,
551
+ label: "Граница X",
552
+ initialValue: FORCE_SETTINGS.xForce ,
553
+ },
554
+ {
555
+ type: "range",
556
+ id: "yForce",
557
+ min: 0,
558
+ max: 1,
559
+ step: 0.01,
560
+ label: "Граница Y",
561
+ initialValue: FORCE_SETTINGS.yForce ,
562
+ },
563
+ {
564
+ type: "range",
565
+ id: "xStrength",
566
+ min: 0,
567
+ max: 1,
568
+ step: 0.01,
569
+ label: "Гравитация X",
570
+ initialValue: FORCE_SETTINGS.xStrength ,
571
+ },
572
+ {
573
+ type: "range",
574
+ id: "yStrength",
575
+ min: 0,
576
+ max: 1,
577
+ step: 0.01,
578
+ label: "Гравитация Y",
579
+ initialValue: FORCE_SETTINGS.yStrength ,
580
+ },
581
+ {
582
+ type: "range",
583
+ id: "linkStrength",
584
+ min: 0,
585
+ max: 1,
586
+ step: 0.01,
587
+ label: "Натяжение связей",
588
+ initialValue: FORCE_SETTINGS.linkStrength ,
589
+ },
590
+ {
591
+ type: "range",
592
+ id: "linkDistance",
593
+ min: 0,
594
+ max: 300,
595
+ step: 0.1,
596
+ label: "Расстояние связей",
597
+ initialValue: FORCE_SETTINGS.linkDistance ,
598
+ },
599
+ {
600
+ id: "collideOn",
601
+ type: "checkbox",
602
+ initialValue: FORCE_SETTINGS.collideOn,
603
+ label: "Отталкивание",
604
+ },
605
+ ];
606
+ return keys ? FORCE_CONTROLS.filter((control) => keys.includes(control.id)) : FORCE_CONTROLS;
607
+ }
608
+ function getGraphControls(keys) {
609
+ const GRAPH_CONTROLS = [
610
+ {
611
+ id: "highlightSizingAdditional",
612
+ initialValue: GRAPH_SETTINGS.highlightSizingAdditional,
613
+ max: 10,
614
+ min: 0.1,
615
+ step: 0.01,
616
+ type: "range",
617
+ label: "Дополнительный размер при анимации",
618
+ },
619
+ {
620
+ id: "highlightColorFadingMin",
621
+ initialValue: GRAPH_SETTINGS.highlightColorFadingMin,
622
+ max: 1,
623
+ min: 0.01,
624
+ step: 0.01,
625
+ type: "range",
626
+ label: "Граница затухания цвета",
627
+ },
628
+ {
629
+ id: "highlightTextShiftXAdditional",
630
+ initialValue: GRAPH_SETTINGS.highlightTextShiftXAdditional,
631
+ max: 50,
632
+ min: 0,
633
+ step: 0.1,
634
+ type: "range",
635
+ label: "Смещение текста по X при анимации",
636
+ },
637
+ {
638
+ id: "highlightTextShiftYAdditional",
639
+ initialValue: GRAPH_SETTINGS.highlightTextShiftYAdditional,
640
+ max: 50,
641
+ min: 0,
642
+ step: 0.1,
643
+ type: "range",
644
+ label: "Смещение текста по Y при анимации",
645
+ },
646
+ {
647
+ id: "highlightTextSizingAdditional",
648
+ initialValue: GRAPH_SETTINGS.highlightTextSizingAdditional,
649
+ max: 10,
650
+ min: 0.1,
651
+ step: 0.1,
652
+ type: "range",
653
+ label: "Увеличение текста при анимации",
654
+ },
655
+ {
656
+ id: "highlightTextWeightAdditional",
657
+ initialValue: GRAPH_SETTINGS.highlightTextWeightAdditional,
658
+ max: 1000,
659
+ min: 0,
660
+ step: 100,
661
+ type: "range",
662
+ label: "Увеличение жирности текста при анимации",
663
+ },
664
+ {
665
+ id: "highlightTextWidthAdditional",
666
+ initialValue: GRAPH_SETTINGS.highlightTextWidthAdditional,
667
+ max: 100,
668
+ min: 0,
669
+ step: 0.1,
670
+ type: "range",
671
+ label: "Увеличение ширины текста при анимации",
672
+ },
673
+ {
674
+ id: "highlightTextFadingMin",
675
+ initialValue: GRAPH_SETTINGS.highlightTextFadingMin,
676
+ max: 1,
677
+ min: 0,
678
+ step: 0.01,
679
+ type: "range",
680
+ label: "Граница затухания текста при анимации",
681
+ },
682
+ {
683
+ id: "highlightLinkFadingMin",
684
+ initialValue: GRAPH_SETTINGS.highlightLinkFadingMin,
685
+ max: 1,
686
+ min: 0,
687
+ step: 0.01,
688
+ type: "range",
689
+ label: "Граница затухания связи при анимации",
690
+ },
691
+ {
692
+ id: "highlightFadingMin",
693
+ initialValue: GRAPH_SETTINGS.highlightFadingMin,
694
+ max: 1,
695
+ min: 0,
696
+ step: 0.01,
697
+ type: "range",
698
+ label: "Граница затухания ноды при анимации",
699
+ },
700
+ {
701
+ id: "highlightArrowFadingMin",
702
+ initialValue: GRAPH_SETTINGS.highlightArrowFadingMin,
703
+ max: 1,
704
+ min: 0,
705
+ step: 0.01,
706
+ type: "range",
707
+ label: "Граница затухания стрелки при анимации",
708
+ },
709
+ {
710
+ id: "highlightDownStep",
711
+ initialValue: GRAPH_SETTINGS.highlightDownStep,
712
+ max: 1,
713
+ min: 0.01,
714
+ step: 0.01,
715
+ type: "range",
716
+ label: "Скорость отмены анимации",
717
+ },
718
+ {
719
+ id: "highlightUpStep",
720
+ initialValue: GRAPH_SETTINGS.highlightUpStep,
721
+ max: 1,
722
+ min: 0.01,
723
+ step: 0.01,
724
+ type: "range",
725
+ label: "Скорость применения анимации",
726
+ },
727
+ {
728
+ id: "nodeRadiusInitial",
729
+ initialValue: GRAPH_SETTINGS.nodeRadiusInitial,
730
+ max: 50,
731
+ min: 0.1,
732
+ step: 0.1,
733
+ type: "range",
734
+ label: "Изначальный радиус ноды",
735
+ },
736
+ {
737
+ id: "nodeRadiusCoefficient",
738
+ initialValue: GRAPH_SETTINGS.nodeRadiusCoefficient,
739
+ max: 100,
740
+ min: 0.1,
741
+ step: 0.1,
742
+ type: "range",
743
+ label: "Количество связей для увеличения радиуса",
744
+ },
745
+ {
746
+ id: "nodeRadiusFactor",
747
+ initialValue: GRAPH_SETTINGS.nodeRadiusFactor,
748
+ max: 50,
749
+ min: 0.1,
750
+ step: 0.1,
751
+ type: "range",
752
+ label: "Коэффициент увеличения радиуса",
753
+ },
754
+ {
755
+ id: "highlightOnlyRoot",
756
+ type: "checkbox",
757
+ initialValue: GRAPH_SETTINGS.highlightOnlyRoot,
758
+ label: "Дополнительная анимация только главное цели",
759
+ },
760
+ {
761
+ id: "stickAfterDrag",
762
+ type: "checkbox",
763
+ initialValue: GRAPH_SETTINGS.stickAfterDrag,
764
+ label: "Фиксировании ноды после перетаскивания",
765
+ },
766
+ {
767
+ id: "highlightByHover",
768
+ type: "checkbox",
769
+ initialValue: GRAPH_SETTINGS.highlightByHover,
770
+ label: "Анимации при наведении",
771
+ },
772
+ {
773
+ id: "nodeRadiusFlexible",
774
+ type: "checkbox",
775
+ initialValue: GRAPH_SETTINGS.nodeRadiusFlexible,
776
+ label: "Гибкий радиус ноды",
777
+ },
778
+ ];
779
+ return keys ? GRAPH_CONTROLS.filter((control) => keys.includes(control.id)) : GRAPH_CONTROLS;
780
+ }
781
+ function getNodeControls(keys) {
782
+ const NODE_CONTROLS = [
783
+ {
784
+ id: "alpha",
785
+ type: "range",
786
+ max: 1,
787
+ min: 0,
788
+ step: 0.1,
789
+ label: "Прозрачность",
790
+ initialValue: NODE_OPTIONS.alpha,
791
+ },
792
+ {
793
+ id: "radius",
794
+ type: "range",
795
+ max: 50,
796
+ min: 1,
797
+ step: 1,
798
+ label: "Радиус",
799
+ initialValue: NODE_OPTIONS.radius,
800
+ },
801
+ {
802
+ id: "borderWidth",
803
+ type: "range",
804
+ max: 5,
805
+ min: 0,
806
+ step: 0.1,
807
+ label: "Толщина границы",
808
+ initialValue: NODE_OPTIONS.borderWidth,
809
+ },
810
+ {
811
+ id: "borderColor",
812
+ type: "color",
813
+ initialValue: "#000000FF",
814
+ label: "Цвет границы",
815
+ },
816
+ {
817
+ id: "color",
818
+ type: "color",
819
+ initialValue: "#000000FF",
820
+ label: "Цвет",
821
+ },
822
+ {
823
+ id: "textAlpha",
824
+ type: "range",
825
+ max: 1,
826
+ min: 0,
827
+ step: 0.1,
828
+ label: "Прозрачность текста",
829
+ initialValue: NODE_OPTIONS.textAlpha,
830
+ },
831
+ {
832
+ id: "textWidth",
833
+ type: "range",
834
+ max: 200,
835
+ min: 5,
836
+ step: 1,
837
+ label: "Ширина текста",
838
+ initialValue: NODE_OPTIONS.textWidth,
839
+ },
840
+ {
841
+ id: "textShiftX",
842
+ type: "range",
843
+ max: 100,
844
+ min: 0,
845
+ step: 0.1,
846
+ label: "Смещение X текста",
847
+ initialValue: NODE_OPTIONS.textShiftX,
848
+ },
849
+ {
850
+ id: "textShiftY",
851
+ type: "range",
852
+ max: 100,
853
+ min: 0,
854
+ step: 0.1,
855
+ label: "Смещение Y текста",
856
+ initialValue: 4,
857
+ },
858
+ {
859
+ id: "textWeight",
860
+ type: "range",
861
+ max: 1000,
862
+ min: 100,
863
+ step: 100,
864
+ label: "Жирность текста",
865
+ initialValue: NODE_OPTIONS.textWeight,
866
+ },
867
+ {
868
+ id: "textGap",
869
+ type: "range",
870
+ max: 30,
871
+ min: 0,
872
+ step: 0.1,
873
+ label: "Расстояние между строками",
874
+ initialValue: NODE_OPTIONS.textGap,
875
+ },
876
+ {
877
+ id: "highlightFading",
878
+ type: "checkbox",
879
+ initialValue: NODE_OPTIONS.highlightFading,
880
+ label: "Анимация затухания",
881
+ },
882
+ {
883
+ id: "highlightColor",
884
+ type: "checkbox",
885
+ initialValue: NODE_OPTIONS.highlightColor,
886
+ label: "Анимация затухания цвета",
887
+ },
888
+ {
889
+ id: "highlightSizing",
890
+ type: "checkbox",
891
+ initialValue: NODE_OPTIONS.highlightSizing,
892
+ label: "Анимация размера",
893
+ },
894
+ {
895
+ id: "highlightTextFading",
896
+ type: "checkbox",
897
+ initialValue: NODE_OPTIONS.highlightTextFading,
898
+ label: "Анимация затухания текста",
899
+ },
900
+ {
901
+ id: "highlightTextSizing",
902
+ type: "checkbox",
903
+ initialValue: NODE_OPTIONS.highlightTextSizing,
904
+ label: "Анимация размера текста",
905
+ },
906
+ ];
907
+ return keys ? NODE_CONTROLS.filter((control) => keys.includes(control.id)) : NODE_CONTROLS;
908
+ }
909
+ function getLinkControls(keys) {
910
+ const LINK_CONTROLS = [
911
+ {
912
+ id: "alpha",
913
+ type: "range",
914
+ max: 1,
915
+ min: 0,
916
+ step: 0.1,
917
+ label: "Прозрачность",
918
+ initialValue: LINK_OPTIONS.alpha,
919
+ },
920
+ {
921
+ id: "width",
922
+ type: "range",
923
+ max: 10,
924
+ min: 0.1,
925
+ step: 0.1,
926
+ label: "Толщина",
927
+ initialValue: 0.1,
928
+ },
929
+ {
930
+ id: "arrowAlpha",
931
+ type: "range",
932
+ max: 1,
933
+ min: 0,
934
+ step: 0.1,
935
+ label: "Прозрачность стрелки",
936
+ initialValue: LINK_OPTIONS.arrowAlpha,
937
+ },
938
+ {
939
+ id: "arrowSize",
940
+ type: "range",
941
+ max: 10,
942
+ min: 0.1,
943
+ step: 0.1,
944
+ label: "Размер стрелки",
945
+ initialValue: LINK_OPTIONS.arrowSize,
946
+ },
947
+ {
948
+ id: "particleAlpha",
949
+ type: "range",
950
+ max: 1,
951
+ min: 0,
952
+ step: 0.1,
953
+ label: "Прозрачность частиц",
954
+ initialValue: LINK_OPTIONS.particleAlpha,
955
+ },
956
+ {
957
+ id: "particleRadius",
958
+ type: "range",
959
+ max: 10,
960
+ min: 0.1,
961
+ step: 0.1,
962
+ label: "Радиус частиц",
963
+ initialValue: LINK_OPTIONS.particleRadius,
964
+ },
965
+ {
966
+ id: "particleCount",
967
+ type: "range",
968
+ max: 20,
969
+ min: 1,
970
+ step: 1,
971
+ label: "Количество частиц",
972
+ initialValue: LINK_OPTIONS.particleCount,
973
+ },
974
+ {
975
+ id: "particleSteps",
976
+ type: "range",
977
+ max: 200,
978
+ min: 1,
979
+ step: 1,
980
+ label: "Количество кадров у частиц",
981
+ initialValue: LINK_OPTIONS.particleSteps,
982
+ },
983
+ {
984
+ id: "color",
985
+ type: "color",
986
+ initialValue: "#000000FF",
987
+ label: "Цвет",
988
+ },
989
+ {
990
+ id: "arrowColor",
991
+ type: "color",
992
+ initialValue: "#000000FF",
993
+ label: "Цвет стрелки",
994
+ },
995
+ {
996
+ id: "particleColor",
997
+ type: "color",
998
+ initialValue: "#000000FF",
999
+ label: "Цвет частиц",
1000
+ },
1001
+ {
1002
+ id: "highlightFading",
1003
+ type: "checkbox",
1004
+ label: "Анимация Затухания",
1005
+ initialValue: LINK_OPTIONS.highlightFading,
1006
+ },
1007
+ {
1008
+ id: "pretty",
1009
+ type: "checkbox",
1010
+ label: "Обсчет соединения",
1011
+ initialValue: LINK_OPTIONS.pretty,
1012
+ },
1013
+ {
1014
+ id: "arrow",
1015
+ type: "checkbox",
1016
+ label: "Стрелка",
1017
+ initialValue: LINK_OPTIONS.arrow,
1018
+ },
1019
+ {
1020
+ id: "arrowHighlightFading",
1021
+ type: "checkbox",
1022
+ label: "Анимация затухания стрелки",
1023
+ initialValue: LINK_OPTIONS.arrowHighlightFading,
1024
+ },
1025
+ {
1026
+ id: "arrowReverseAppear",
1027
+ type: "checkbox",
1028
+ label: "Появление стрелки при анимации",
1029
+ initialValue: LINK_OPTIONS.arrowReverseAppear,
1030
+ },
1031
+ ];
1032
+ return keys ? LINK_CONTROLS.filter((control) => keys.includes(control.id)) : LINK_CONTROLS;
1033
+ }
1034
+
1035
+ let countTime = 0;
1036
+ let countOperation = 0;
1037
+ function setDrawTime(cb, showEveryTick) {
1038
+ return () => {
1039
+ const start = performance.now();
1040
+ cb();
1041
+ const end = performance.now();
1042
+ const result = end - start;
1043
+ countTime += result;
1044
+ countOperation++;
1045
+ if (showEveryTick) {
1046
+ const average = countTime / countOperation;
1047
+ // eslint-disable-next-line no-console
1048
+ console.log("average draw: ", average, "current draw: ", result);
1049
+ }
1050
+ };
1051
+ }
1052
+ function getDrawTime() {
1053
+ const average = countTime / countOperation;
1054
+ // eslint-disable-next-line no-console
1055
+ console.log("average draw: ", average);
1056
+ }
1057
+
431
1058
  class GraphCanvas {
432
1059
  /** initial data */
433
1060
  nodes;
434
1061
  links;
1062
+ particles = {};
435
1063
  width;
436
1064
  height;
437
1065
  root;
@@ -451,6 +1079,8 @@ class GraphCanvas {
451
1079
  draw;
452
1080
  eventAbortController;
453
1081
  cachedNodeText = {};
1082
+ linkOptionsCache = {};
1083
+ nodeOptionsCache = {};
454
1084
  isDragging = false;
455
1085
  highlightedNode = null;
456
1086
  highlightedNeighbors = null;
@@ -523,20 +1153,35 @@ class GraphCanvas {
523
1153
  this.updateData(alpha);
524
1154
  }
525
1155
  changeSettings(options) {
526
- if (options.graphSettings)
1156
+ if (options.graphSettings) {
527
1157
  this.graphSettings = graphSettingsGetter(options.graphSettings, this.graphSettings);
528
- if (options.forceSettings)
1158
+ this.draw = this.initDraw();
1159
+ this.initZoom(this.areaTransform);
1160
+ }
1161
+ if (options.forceSettings) {
529
1162
  this.forceSettings = forceSettingsGetter(options.forceSettings, this.forceSettings);
530
- if (options.linkSettings)
531
- this.linkSettings = linkSettingsGetter(options.linkSettings);
532
- if (options.nodeSettings)
533
- this.nodeSettings = nodeSettingsGetter(options.nodeSettings);
534
- if (options.forceSettings)
1163
+ }
1164
+ if (options.linkSettings) {
1165
+ this.linkSettings = linkSettingsGetter(options.linkSettings, this.linkSettings);
1166
+ this.linkOptionsCache = {};
1167
+ }
1168
+ if (options.nodeSettings) {
1169
+ this.nodeSettings = nodeSettingsGetter(options.nodeSettings, this.nodeSettings);
1170
+ this.cachedNodeText = {};
1171
+ this.nodeOptionsCache = {};
1172
+ }
1173
+ if (options.forceSettings) {
535
1174
  return void this.updateSimulation();
1175
+ }
536
1176
  this.tick();
537
1177
  }
1178
+ clearCache() {
1179
+ this.nodeOptionsCache = {};
1180
+ this.linkOptionsCache = {};
1181
+ this.cachedNodeText = {};
1182
+ }
538
1183
  tick() {
539
- if (!this.simulationWorking)
1184
+ if (!this.simulationWorking && !this.highlightWorking)
540
1185
  this.draw();
541
1186
  }
542
1187
  restart(alpha) {
@@ -592,6 +1237,8 @@ class GraphCanvas {
592
1237
  }
593
1238
  clearDataDependencies() {
594
1239
  this.cachedNodeText = {};
1240
+ this.nodeOptionsCache = {};
1241
+ this.linkOptionsCache = {};
595
1242
  }
596
1243
  updateData(alpha) {
597
1244
  this.clearDataDependencies();
@@ -615,7 +1262,7 @@ class GraphCanvas {
615
1262
  this.initZoom();
616
1263
  this.initResize();
617
1264
  this.initPointer();
618
- if (!this.simulationWorking)
1265
+ if (!this.simulationWorking && !this.highlightWorking)
619
1266
  this.draw();
620
1267
  }
621
1268
  init() {
@@ -636,6 +1283,8 @@ class GraphCanvas {
636
1283
  })
637
1284
  .on("end", () => {
638
1285
  this.listeners.onSimulationEnd?.(this.state);
1286
+ if (this.graphSettings.showDrawTime)
1287
+ getDrawTime();
639
1288
  });
640
1289
  this.initSimulationForces();
641
1290
  }
@@ -658,9 +1307,9 @@ class GraphCanvas {
658
1307
  .distanceMax(this.forceSettings.chargeDistanceMax)
659
1308
  .distanceMin(this.forceSettings.chargeDistanceMin))
660
1309
  .force("center", d3Force.forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
661
- this.initCollideForce();
1310
+ this.initCollideForce(true);
662
1311
  }
663
- initCollideForce() {
1312
+ initCollideForce(forceUpdate) {
664
1313
  if (!this.simulation)
665
1314
  return;
666
1315
  if (!this.forceSettings.collideOn) {
@@ -671,9 +1320,10 @@ class GraphCanvas {
671
1320
  const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
672
1321
  const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
673
1322
  const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
674
- if (isMaxCollideNodes && isMaxCollideLinks)
1323
+ if (isMaxCollideNodes && isMaxCollideLinks) {
675
1324
  this.simulation.force("collide", null);
676
- else if (!this.simulation.force("collide"))
1325
+ }
1326
+ else if (!this.simulation.force("collide") || forceUpdate) {
677
1327
  this.simulation.force("collide", d3Force.forceCollide()
678
1328
  .radius((node, index) => {
679
1329
  if (this.forceSettings.collideRadius) {
@@ -691,6 +1341,7 @@ class GraphCanvas {
691
1341
  })
692
1342
  .strength(this.forceSettings.collideStrength)
693
1343
  .iterations(this.forceSettings.collideIterations));
1344
+ }
694
1345
  }
695
1346
  initArea() {
696
1347
  if (!this.area || !this.context || !this.container) {
@@ -725,19 +1376,27 @@ class GraphCanvas {
725
1376
  this.highlightProgress -= this.graphSettings.highlightDownStep;
726
1377
  if (!this.simulationWorking)
727
1378
  return void requestAnimationFrame(() => this.draw());
728
- return;
1379
+ if (!this.linkSettings.particles)
1380
+ return;
729
1381
  }
730
1382
  if (this.highlightWorking && this.highlightProgress < 1) {
731
1383
  this.highlightProgress += this.graphSettings.highlightUpStep;
732
1384
  if (!this.simulationWorking)
733
1385
  return void requestAnimationFrame(() => this.draw());
734
- return;
1386
+ if (!this.linkSettings.particles)
1387
+ return;
1388
+ }
1389
+ if (this.linkSettings.particles && this.highlightWorking && !this.simulationWorking) {
1390
+ return void requestAnimationFrame(() => this.draw());
735
1391
  }
736
1392
  if (!this.highlightWorking && this.highlightProgress <= 0) {
737
- if (this.highlightedNeighbors)
1393
+ if (this.highlightedNeighbors || this.highlightedNode) {
738
1394
  this.highlightedNeighbors = null;
739
- if (this.highlightedNode)
740
1395
  this.highlightedNode = null;
1396
+ this.particles = {};
1397
+ if (!this.simulationWorking)
1398
+ return void requestAnimationFrame(() => this.draw());
1399
+ }
741
1400
  }
742
1401
  this.highlightDrawing = false;
743
1402
  }
@@ -760,13 +1419,13 @@ class GraphCanvas {
760
1419
  this.context.clearRect(0, 0, this.width, this.height);
761
1420
  this.context.translate(this.areaTransform.x, this.areaTransform.y);
762
1421
  this.context.scale(this.areaTransform.k, this.areaTransform.k);
1422
+ const textRenders = [];
1423
+ const nodeRenders = [];
1424
+ this.nodes.forEach(getDrawNode(nodeRenders, textRenders, state).bind(this));
763
1425
  /** links */
764
- this.context.beginPath();
765
1426
  this.links.forEach(getDrawLink(state).bind(this));
766
- this.context.stroke();
767
1427
  /** nodes */
768
- const textRenders = [];
769
- this.nodes.forEach(getDrawNode(textRenders, state).bind(this));
1428
+ nodeRenders.forEach((render) => render());
770
1429
  textRenders.forEach((render) => render());
771
1430
  this.context.restore();
772
1431
  this.listeners.onDrawFinished?.(state);
@@ -782,52 +1441,151 @@ class GraphCanvas {
782
1441
  !link.target.x ||
783
1442
  !link.target.y)
784
1443
  return;
785
- const linkOptions = linkIterationExtractor(link, index, this.links, state, this.linkSettings.options ?? {}, linkOptionsGetter);
1444
+ if (!link.source._visible && !link.target._visible)
1445
+ return;
1446
+ const id = `${link.target.id}${link.source.id}`;
1447
+ let linkOptions;
1448
+ if (this.linkSettings.cache && this.linkOptionsCache[id]) {
1449
+ linkOptions = this.linkOptionsCache[id];
1450
+ }
1451
+ else {
1452
+ linkOptions = linkIterationExtractor(link, index, this.links, state, this.linkSettings.options ?? {}, linkOptionsGetter);
1453
+ if (this.linkSettings.cache) {
1454
+ this.linkOptionsCache[id] = linkOptions;
1455
+ }
1456
+ }
786
1457
  if (linkOptions.drawLink) {
787
1458
  linkOptions.drawLink(link, linkOptions, state);
788
1459
  return;
789
1460
  }
790
1461
  let alpha = linkOptions.alpha;
1462
+ let arrowAlpha = linkOptions.arrowReverseAppear ? 0 : linkOptions.arrowAlpha;
791
1463
  if (this.highlightedNeighbors && this.highlightedNode) {
792
1464
  /** Not highlighted */
793
1465
  if (this.highlightedNode.id != link.source.id &&
794
1466
  this.highlightedNode.id != link.target.id) {
795
- if (linkOptions.highlightFading)
796
- alpha =
797
- this.graphSettings.highlightLinkFadingMin +
798
- (alpha - this.graphSettings.highlightLinkFadingMin) * (1 - this.highlightProgress);
1467
+ if (linkOptions.highlightFading) {
1468
+ const min = this.graphSettings.highlightLinkFadingMin < alpha
1469
+ ? this.graphSettings.highlightLinkFadingMin
1470
+ : alpha;
1471
+ alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
1472
+ }
1473
+ if (linkOptions.arrow &&
1474
+ linkOptions.arrowHighlightFading &&
1475
+ !linkOptions.arrowReverseAppear) {
1476
+ const min = this.graphSettings.highlightArrowFadingMin < arrowAlpha
1477
+ ? this.graphSettings.highlightArrowFadingMin
1478
+ : arrowAlpha;
1479
+ arrowAlpha = animationByProgress(min, arrowAlpha - min, 1 - this.highlightProgress);
1480
+ }
1481
+ }
1482
+ else {
1483
+ // eslint-disable-next-line no-lonely-if
1484
+ if (linkOptions.arrow && linkOptions.arrowReverseAppear) {
1485
+ /** Highlighted */
1486
+ arrowAlpha = animationByProgress(0, linkOptions.arrowAlpha, this.highlightProgress);
1487
+ }
799
1488
  }
800
- this.context.beginPath();
801
1489
  }
1490
+ /** Link */
1491
+ this.context.beginPath();
802
1492
  this.context.globalAlpha = alpha;
803
1493
  this.context.strokeStyle = linkOptions.color;
804
1494
  this.context.lineWidth = linkOptions.width;
1495
+ let xStart = link.source.x;
1496
+ let yStart = link.source.y;
1497
+ let xEnd = link.target.x;
1498
+ let yEnd = link.target.y;
805
1499
  if (linkOptions.pretty) {
806
- const { x1, x2, y1, y2 } = calculateLinkPositionByRadius(link) ?? {
1500
+ const isHasArrow = linkOptions.arrow && arrowAlpha > 0;
1501
+ const { x1, x2, y1, y2 } = calculateLinkPositionByRadius(link, isHasArrow ? linkOptions.arrowSize : 0) ?? {
807
1502
  x1: 0,
808
1503
  x2: 0,
809
1504
  y1: 0,
810
1505
  y2: 0,
811
1506
  };
812
- this.context.moveTo(x1, y1);
813
- this.context.lineTo(x2, y2);
1507
+ xStart = x1;
1508
+ xEnd = x2;
1509
+ yStart = y1;
1510
+ yEnd = y2;
814
1511
  }
815
- else {
816
- this.context.moveTo(link.source.x, link.source.y);
817
- this.context.lineTo(link.target.x, link.target.y);
1512
+ this.context.moveTo(xStart, yStart);
1513
+ this.context.lineTo(xEnd, yEnd);
1514
+ this.context.stroke();
1515
+ /** Particle */
1516
+ if (this.linkSettings.particles &&
1517
+ this.highlightedNode &&
1518
+ (this.highlightedNode.id === link.source.id || this.highlightedNode.id === link.target.id)) {
1519
+ if (!this.particles[id]) {
1520
+ const sourceId = link.source.id;
1521
+ const targetId = link.target.id;
1522
+ this.particles[id] = Array.from({ length: linkOptions.particleCount }, (_, index) => {
1523
+ return {
1524
+ step: 0,
1525
+ wait: index * (linkOptions.particleSteps / linkOptions.particleCount),
1526
+ sourceId,
1527
+ targetId,
1528
+ };
1529
+ });
1530
+ }
1531
+ this.particles[id].forEach((particle) => {
1532
+ if (!this.context)
1533
+ return;
1534
+ getParticlePosition({
1535
+ particle,
1536
+ totalSteps: linkOptions.particleSteps,
1537
+ xEnd,
1538
+ xStart,
1539
+ yEnd,
1540
+ yStart,
1541
+ });
1542
+ if (particle.x != undefined && particle.y != undefined) {
1543
+ this.context.beginPath();
1544
+ this.context.arc(particle.x, particle.y, linkOptions.particleRadius, 0, Math.PI * 2);
1545
+ this.context.fillStyle = linkOptions.particleColor;
1546
+ this.context.fill();
1547
+ this.context.stroke();
1548
+ }
1549
+ });
818
1550
  }
819
- if (this.highlightedNeighbors && this.highlightedNode)
1551
+ /** Arrow */
1552
+ if (linkOptions.arrow && arrowAlpha > 0) {
1553
+ const { x1: xStart, x2: xEnd, y1: yStart, y2: yEnd, } = calculateLinkPositionByRadius(link) ?? {
1554
+ x1: 0,
1555
+ x2: 0,
1556
+ y1: 0,
1557
+ y2: 0,
1558
+ };
1559
+ const angle = Math.atan2(yEnd - yStart, xEnd - xStart);
1560
+ this.context.beginPath();
1561
+ this.context.globalAlpha = arrowAlpha;
1562
+ this.context.moveTo(xEnd, yEnd);
1563
+ this.context.lineTo(xEnd - linkOptions.arrowSize * Math.cos(angle - Math.PI / 6), yEnd - linkOptions.arrowSize * Math.sin(angle - Math.PI / 6));
1564
+ this.context.lineTo(xEnd - linkOptions.arrowSize * Math.cos(angle + Math.PI / 6), yEnd - linkOptions.arrowSize * Math.sin(angle + Math.PI / 6));
1565
+ this.context.closePath();
1566
+ this.context.fillStyle = linkOptions.arrowColor;
1567
+ this.context.fill();
820
1568
  this.context.stroke();
1569
+ }
821
1570
  if (linkOptions.drawExtraLink) {
822
1571
  linkOptions.drawExtraLink(link, { ...linkOptions, alpha }, state);
823
1572
  }
824
1573
  };
825
1574
  }
826
- function getDrawNode(textRenders, state) {
1575
+ function getDrawNode(nodeRenders, textRenders, state) {
827
1576
  return function drawNode(node, index) {
828
1577
  if (!this.context || !node.x || !node.y)
829
1578
  return;
830
- const nodeOptions = nodeIterationExtractor(node, index, this.nodes, state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
1579
+ let nodeOptions;
1580
+ if (this.nodeSettings.cache && this.nodeOptionsCache[node.id]) {
1581
+ nodeOptions = this.nodeOptionsCache[node.id];
1582
+ }
1583
+ else {
1584
+ nodeOptions = nodeIterationExtractor(node, index, this.nodes, state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
1585
+ if (this.nodeSettings.cache) {
1586
+ this.nodeOptionsCache[node.id] = nodeOptions;
1587
+ }
1588
+ }
831
1589
  if (nodeOptions.nodeDraw) {
832
1590
  nodeOptions.nodeDraw(node, nodeOptions, state);
833
1591
  return;
@@ -845,15 +1603,16 @@ class GraphCanvas {
845
1603
  /** Not highlighted */
846
1604
  if (!this.highlightedNeighbors.has(node.id) && this.highlightedNode.id != node.id) {
847
1605
  if (nodeOptions.highlightFading) {
848
- alpha =
849
- this.graphSettings.highlightFadingMin +
850
- (alpha - this.graphSettings.highlightFadingMin) * (1 - this.highlightProgress);
1606
+ const min = this.graphSettings.highlightFadingMin < alpha
1607
+ ? this.graphSettings.highlightFadingMin
1608
+ : alpha;
1609
+ alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
851
1610
  }
852
1611
  if (nodeOptions.highlightTextFading) {
853
- textAlpha =
854
- this.graphSettings.highlightTextFadingMin +
855
- (textAlpha - this.graphSettings.highlightTextFadingMin) *
856
- (1 - this.highlightProgress);
1612
+ const min = this.graphSettings.highlightTextFadingMin < textAlpha
1613
+ ? this.graphSettings.highlightTextFadingMin
1614
+ : textAlpha;
1615
+ textAlpha = animationByProgress(min, textAlpha - min, 1 - this.highlightProgress);
857
1616
  }
858
1617
  if (nodeOptions.highlightColor) {
859
1618
  const colorRgb = extractRgb(colorToRgb(color));
@@ -868,20 +1627,14 @@ class GraphCanvas {
868
1627
  (this.graphSettings.highlightOnlyRoot && this.highlightedNode.id === node.id)) {
869
1628
  /** Highlighted */
870
1629
  if (nodeOptions.highlightSizing) {
871
- const radiusMax = radiusInitial + this.graphSettings.highlightSizingAdditional;
872
- radiusInitial += ((radiusMax - radiusInitial) / 100) * (this.highlightProgress * 100);
1630
+ radiusInitial = animationByProgress(radiusInitial, this.graphSettings.highlightSizingAdditional, this.highlightProgress);
873
1631
  }
874
1632
  if (nodeOptions.highlightTextSizing) {
875
- const textSizeMax = textSize + this.graphSettings.highlightTextSizingAdditional;
876
- const textShiftXMax = textShiftX + this.graphSettings.highlightTextShiftXAdditional;
877
- const textShiftYMax = textShiftY + this.graphSettings.highlightTextShiftYAdditional;
878
- const textWeightMax = textWeight + this.graphSettings.highlightTextWeightAdditional;
879
- const textWidthMax = textWidth + this.graphSettings.highlightTextWidthAdditional;
880
- textSize += ((textSizeMax - textSize) / 100) * (this.highlightProgress * 100);
881
- textShiftX += ((textShiftXMax - textShiftX) / 100) * (this.highlightProgress * 100);
882
- textShiftY += ((textShiftYMax - textShiftY) / 100) * (this.highlightProgress * 100);
883
- textWeight += ((textWeightMax - textWeight) / 100) * (this.highlightProgress * 100);
884
- textWidth += ((textWidthMax - textWidth) / 100) * (this.highlightProgress * 100);
1633
+ textSize = animationByProgress(textSize, this.graphSettings.highlightTextSizingAdditional, this.highlightProgress);
1634
+ textShiftX = animationByProgress(textShiftX, this.graphSettings.highlightTextShiftXAdditional, this.highlightProgress);
1635
+ textShiftY = animationByProgress(textShiftY, this.graphSettings.highlightTextShiftYAdditional, this.highlightProgress);
1636
+ textWeight = animationByProgress(textWeight, this.graphSettings.highlightTextWeightAdditional, this.highlightProgress);
1637
+ textWidth = animationByProgress(textWidth, this.graphSettings.highlightTextWidthAdditional, this.highlightProgress);
885
1638
  }
886
1639
  }
887
1640
  }
@@ -893,8 +1646,31 @@ class GraphCanvas {
893
1646
  linkCount: node.linkCount,
894
1647
  });
895
1648
  node._radius = radius;
896
- this.context.beginPath();
897
- this.context.globalAlpha = alpha;
1649
+ if (!isNodeVisible({
1650
+ height: this.height,
1651
+ width: this.width,
1652
+ x: node.x,
1653
+ y: node.y,
1654
+ radius,
1655
+ transform: this.areaTransform,
1656
+ })) {
1657
+ node._visible = false;
1658
+ return;
1659
+ }
1660
+ node._visible = true;
1661
+ nodeRenders.push(() => {
1662
+ if (!this.context || !node.x || !node.y)
1663
+ return;
1664
+ this.context.beginPath();
1665
+ this.context.globalAlpha = alpha;
1666
+ /** circle */
1667
+ this.context.lineWidth = nodeOptions.borderWidth;
1668
+ this.context.strokeStyle = nodeOptions.borderColor;
1669
+ this.context.fillStyle = color;
1670
+ this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
1671
+ this.context.fill();
1672
+ this.context.stroke();
1673
+ });
898
1674
  /** text */
899
1675
  if (nodeOptions.textVisible && nodeOptions.text) {
900
1676
  textRenders.push(() => {
@@ -949,13 +1725,6 @@ class GraphCanvas {
949
1725
  }
950
1726
  });
951
1727
  }
952
- /** circle */
953
- this.context.lineWidth = nodeOptions.borderWidth;
954
- this.context.strokeStyle = nodeOptions.borderColor;
955
- this.context.fillStyle = color;
956
- this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
957
- this.context.fill();
958
- this.context.stroke();
959
1728
  if (nodeOptions.nodeExtraDraw) {
960
1729
  nodeOptions.nodeExtraDraw(node, {
961
1730
  ...nodeOptions,
@@ -972,6 +1741,9 @@ class GraphCanvas {
972
1741
  }
973
1742
  };
974
1743
  }
1744
+ if (this.graphSettings.showDrawTime) {
1745
+ return setDrawTime(draw.bind(this), this.graphSettings.showDrawTimeEveryTick);
1746
+ }
975
1747
  return draw;
976
1748
  }
977
1749
  initResize() {
@@ -1165,7 +1937,7 @@ class GraphCanvas {
1165
1937
  this.listeners.onEndDragFinished?.(event, this.state);
1166
1938
  }));
1167
1939
  }
1168
- initZoom() {
1940
+ initZoom(currentZoom) {
1169
1941
  if (!this.area)
1170
1942
  throw new Error("bad init data");
1171
1943
  const zoomInstance = d3Zoom.zoom()
@@ -1173,7 +1945,9 @@ class GraphCanvas {
1173
1945
  .on("zoom", (event) => {
1174
1946
  this.listeners.onZoom?.(event);
1175
1947
  this.areaTransform = event.transform;
1176
- if (!this.simulationWorking)
1948
+ this.linkOptionsCache = {};
1949
+ this.nodeOptionsCache = {};
1950
+ if (!this.simulationWorking && !this.highlightWorking)
1177
1951
  requestAnimationFrame(() => this.draw());
1178
1952
  });
1179
1953
  if (this.graphSettings.translateExtentEnable) {
@@ -1188,18 +1962,24 @@ class GraphCanvas {
1188
1962
  ]);
1189
1963
  }
1190
1964
  d3Selection.select(this.area).call(zoomInstance).on("dblclick.zoom", null);
1191
- const zoomInitial = this.graphSettings.zoomInitial;
1965
+ const zoomInitial = currentZoom ?? this.graphSettings.zoomInitial;
1192
1966
  this.areaTransform = new d3Zoom.ZoomTransform(zoomInitial?.k ?? 1, zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
1193
1967
  d3Zoom.zoom().transform(d3Selection.select(this.area), this.areaTransform);
1194
1968
  }
1195
1969
  }
1196
1970
 
1197
1971
  exports.GraphCanvas = GraphCanvas;
1972
+ exports.animationByProgress = animationByProgress;
1198
1973
  exports.calculateLinkPositionByRadius = calculateLinkPositionByRadius;
1199
1974
  exports.colorToRgb = colorToRgb;
1200
1975
  exports.drawText = drawText;
1201
1976
  exports.extractRgb = extractRgb;
1202
1977
  exports.fadeRgb = fadeRgb;
1978
+ exports.getForceControls = getForceControls;
1979
+ exports.getGraphControls = getGraphControls;
1980
+ exports.getLinkControls = getLinkControls;
1981
+ exports.getNodeControls = getNodeControls;
1982
+ exports.getParticlePosition = getParticlePosition;
1203
1983
  exports.linkIterationExtractor = linkIterationExtractor;
1204
1984
  exports.linkOptionsGetter = linkOptionsGetter;
1205
1985
  exports.nodeIterationExtractor = nodeIterationExtractor;