@krainovsd/graph 0.6.1 → 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 +1098 -253
  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 +365 -155
  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 +9 -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 +10 -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 +84 -9
  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,15 +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,
110
+ drawExtraLink: null,
111
+ drawLink: null,
199
112
  color: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkColorZoomBorder
200
113
  ? COMMON_SETTINGS.linkColorZoomNear
201
114
  : COMMON_SETTINGS.linkColorZoomFar,
115
+ arrowColor: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkColorZoomBorder
116
+ ? COMMON_SETTINGS.linkColorZoomNear
117
+ : COMMON_SETTINGS.linkColorZoomFar,
202
118
  width: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkWidthZoomBorder
203
119
  ? COMMON_SETTINGS.linkWidthZoomNear
204
120
  : COMMON_SETTINGS.linkWidthZoomFar,
@@ -209,17 +125,22 @@ function listenersGetter(settings) {
209
125
  return settings ?? {};
210
126
  }
211
127
 
212
- function nodeSettingsGetter(settings) {
128
+ function nodeSettingsGetter(settings, prevNodeSettings) {
213
129
  return {
214
- idGetter: settings?.idGetter ?? nodeIdGetter,
215
- options: settings?.options,
130
+ ...(prevNodeSettings ?? NODE_SETTINGS),
131
+ idGetter: nodeIdGetter,
132
+ ...settings,
216
133
  };
217
134
  }
218
135
  const color = colorGetter();
219
136
  function nodeOptionsGetter(node, _, __, state) {
220
137
  const { textShiftY, textSize } = nodeTextSizeGetter(state?.areaTransform);
221
138
  return {
222
- ...NODE_SETTINGS,
139
+ ...NODE_OPTIONS,
140
+ nodeDraw: null,
141
+ nodeExtraDraw: null,
142
+ textDraw: null,
143
+ textExtraDraw: null,
223
144
  color: color(String(node.group ?? "_DEFAULT")),
224
145
  textVisible: Boolean(state?.areaTransform && state.areaTransform.k > COMMON_SETTINGS.nodeTextScaleMin),
225
146
  text: node.name ?? node.id.toString(),
@@ -404,7 +325,7 @@ function drawText({ context, id, textAlign, textColor, textFont, textStyle, text
404
325
  });
405
326
  }
406
327
 
407
- function calculateLinkPositionByRadius(link) {
328
+ function calculateLinkPositionByRadius(link, arrowSize = 0) {
408
329
  const source = link.source;
409
330
  const target = link.target;
410
331
  if (typeof source != "object" || typeof target != "object")
@@ -413,7 +334,7 @@ function calculateLinkPositionByRadius(link) {
413
334
  const dy = (target.y ?? 0) - (source.y ?? 0);
414
335
  const dr = Math.sqrt(dx * dx + dy * dy);
415
336
  const sourceRadius = source._radius ?? 0;
416
- const targetRadius = target._radius ?? 0;
337
+ const targetRadius = (target._radius ?? 0) + (arrowSize > 0 ? arrowSize * 0.85 : 0);
417
338
  return {
418
339
  x1: (source.x ?? 0) + (dx * sourceRadius) / dr,
419
340
  y1: (source.y ?? 0) + (dy * sourceRadius) / dr,
@@ -422,10 +343,723 @@ function calculateLinkPositionByRadius(link) {
422
343
  };
423
344
  }
424
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
+
425
1058
  class GraphCanvas {
426
1059
  /** initial data */
427
1060
  nodes;
428
1061
  links;
1062
+ particles = {};
429
1063
  width;
430
1064
  height;
431
1065
  root;
@@ -445,6 +1079,8 @@ class GraphCanvas {
445
1079
  draw;
446
1080
  eventAbortController;
447
1081
  cachedNodeText = {};
1082
+ linkOptionsCache = {};
1083
+ nodeOptionsCache = {};
448
1084
  isDragging = false;
449
1085
  highlightedNode = null;
450
1086
  highlightedNeighbors = null;
@@ -517,20 +1153,35 @@ class GraphCanvas {
517
1153
  this.updateData(alpha);
518
1154
  }
519
1155
  changeSettings(options) {
520
- if (options.graphSettings)
1156
+ if (options.graphSettings) {
521
1157
  this.graphSettings = graphSettingsGetter(options.graphSettings, this.graphSettings);
522
- if (options.forceSettings)
1158
+ this.draw = this.initDraw();
1159
+ this.initZoom(this.areaTransform);
1160
+ }
1161
+ if (options.forceSettings) {
523
1162
  this.forceSettings = forceSettingsGetter(options.forceSettings, this.forceSettings);
524
- if (options.linkSettings)
525
- this.linkSettings = linkSettingsGetter(options.linkSettings);
526
- if (options.nodeSettings)
527
- this.nodeSettings = nodeSettingsGetter(options.nodeSettings);
528
- 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) {
529
1174
  return void this.updateSimulation();
1175
+ }
530
1176
  this.tick();
531
1177
  }
1178
+ clearCache() {
1179
+ this.nodeOptionsCache = {};
1180
+ this.linkOptionsCache = {};
1181
+ this.cachedNodeText = {};
1182
+ }
532
1183
  tick() {
533
- if (!this.simulationWorking)
1184
+ if (!this.simulationWorking && !this.highlightWorking)
534
1185
  this.draw();
535
1186
  }
536
1187
  restart(alpha) {
@@ -586,6 +1237,8 @@ class GraphCanvas {
586
1237
  }
587
1238
  clearDataDependencies() {
588
1239
  this.cachedNodeText = {};
1240
+ this.nodeOptionsCache = {};
1241
+ this.linkOptionsCache = {};
589
1242
  }
590
1243
  updateData(alpha) {
591
1244
  this.clearDataDependencies();
@@ -609,7 +1262,7 @@ class GraphCanvas {
609
1262
  this.initZoom();
610
1263
  this.initResize();
611
1264
  this.initPointer();
612
- if (!this.simulationWorking)
1265
+ if (!this.simulationWorking && !this.highlightWorking)
613
1266
  this.draw();
614
1267
  }
615
1268
  init() {
@@ -630,6 +1283,8 @@ class GraphCanvas {
630
1283
  })
631
1284
  .on("end", () => {
632
1285
  this.listeners.onSimulationEnd?.(this.state);
1286
+ if (this.graphSettings.showDrawTime)
1287
+ getDrawTime();
633
1288
  });
634
1289
  this.initSimulationForces();
635
1290
  }
@@ -652,9 +1307,9 @@ class GraphCanvas {
652
1307
  .distanceMax(this.forceSettings.chargeDistanceMax)
653
1308
  .distanceMin(this.forceSettings.chargeDistanceMin))
654
1309
  .force("center", d3Force.forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
655
- this.initCollideForce();
1310
+ this.initCollideForce(true);
656
1311
  }
657
- initCollideForce() {
1312
+ initCollideForce(forceUpdate) {
658
1313
  if (!this.simulation)
659
1314
  return;
660
1315
  if (!this.forceSettings.collideOn) {
@@ -665,9 +1320,10 @@ class GraphCanvas {
665
1320
  const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
666
1321
  const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
667
1322
  const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
668
- if (isMaxCollideNodes && isMaxCollideLinks)
1323
+ if (isMaxCollideNodes && isMaxCollideLinks) {
669
1324
  this.simulation.force("collide", null);
670
- else if (!this.simulation.force("collide"))
1325
+ }
1326
+ else if (!this.simulation.force("collide") || forceUpdate) {
671
1327
  this.simulation.force("collide", d3Force.forceCollide()
672
1328
  .radius((node, index) => {
673
1329
  if (this.forceSettings.collideRadius) {
@@ -685,6 +1341,7 @@ class GraphCanvas {
685
1341
  })
686
1342
  .strength(this.forceSettings.collideStrength)
687
1343
  .iterations(this.forceSettings.collideIterations));
1344
+ }
688
1345
  }
689
1346
  initArea() {
690
1347
  if (!this.area || !this.context || !this.container) {
@@ -719,27 +1376,36 @@ class GraphCanvas {
719
1376
  this.highlightProgress -= this.graphSettings.highlightDownStep;
720
1377
  if (!this.simulationWorking)
721
1378
  return void requestAnimationFrame(() => this.draw());
722
- return;
1379
+ if (!this.linkSettings.particles)
1380
+ return;
723
1381
  }
724
1382
  if (this.highlightWorking && this.highlightProgress < 1) {
725
1383
  this.highlightProgress += this.graphSettings.highlightUpStep;
726
1384
  if (!this.simulationWorking)
727
1385
  return void requestAnimationFrame(() => this.draw());
728
- return;
1386
+ if (!this.linkSettings.particles)
1387
+ return;
1388
+ }
1389
+ if (this.linkSettings.particles && this.highlightWorking && !this.simulationWorking) {
1390
+ return void requestAnimationFrame(() => this.draw());
729
1391
  }
730
1392
  if (!this.highlightWorking && this.highlightProgress <= 0) {
731
- if (this.highlightedNeighbors)
1393
+ if (this.highlightedNeighbors || this.highlightedNode) {
732
1394
  this.highlightedNeighbors = null;
733
- if (this.highlightedNode)
734
1395
  this.highlightedNode = null;
1396
+ this.particles = {};
1397
+ if (!this.simulationWorking)
1398
+ return void requestAnimationFrame(() => this.draw());
1399
+ }
735
1400
  }
736
1401
  this.highlightDrawing = false;
737
1402
  }
738
1403
  function draw() {
739
1404
  if (!this.context)
740
1405
  return;
1406
+ const state = this.state;
741
1407
  if (this.listeners.onDraw) {
742
- this.listeners.onDraw(this.state, (status) => {
1408
+ this.listeners.onDraw(state, (status) => {
743
1409
  this.highlightDrawing = status;
744
1410
  }, () => {
745
1411
  if (this.highlightedNeighbors)
@@ -753,160 +1419,331 @@ class GraphCanvas {
753
1419
  this.context.clearRect(0, 0, this.width, this.height);
754
1420
  this.context.translate(this.areaTransform.x, this.areaTransform.y);
755
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));
756
1425
  /** links */
757
- this.context.beginPath();
758
- this.links.forEach(drawLink.bind(this));
759
- this.context.stroke();
1426
+ this.links.forEach(getDrawLink(state).bind(this));
760
1427
  /** nodes */
761
- const textRenders = [];
762
- this.nodes.forEach(drawNode(textRenders).bind(this));
1428
+ nodeRenders.forEach((render) => render());
763
1429
  textRenders.forEach((render) => render());
764
1430
  this.context.restore();
765
- this.listeners.onDrawFinished?.(this.state);
1431
+ this.listeners.onDrawFinished?.(state);
766
1432
  calculateHighlightFading.bind(this)();
767
1433
  }
768
- function drawLink(link, index) {
769
- if (!this.context ||
770
- typeof link.source !== "object" ||
771
- typeof link.target !== "object" ||
772
- !link.source.x ||
773
- !link.source.y ||
774
- !link.target.x ||
775
- !link.target.y)
776
- return;
777
- const linkOptions = linkIterationExtractor(link, index, this.links, this.state, this.linkSettings.options ?? {}, linkOptionsGetter);
778
- let alpha = linkOptions.alpha;
779
- if (this.highlightedNeighbors && this.highlightedNode) {
780
- /** Not highlighted */
781
- if (this.highlightedNode.id != link.source.id &&
782
- this.highlightedNode.id != link.target.id) {
783
- if (linkOptions.highlightFading)
784
- alpha =
785
- this.graphSettings.highlightLinkFadingMin +
786
- (alpha - this.graphSettings.highlightLinkFadingMin) * (1 - this.highlightProgress);
1434
+ function getDrawLink(state) {
1435
+ return function drawLink(link, index) {
1436
+ if (!this.context ||
1437
+ typeof link.source !== "object" ||
1438
+ typeof link.target !== "object" ||
1439
+ !link.source.x ||
1440
+ !link.source.y ||
1441
+ !link.target.x ||
1442
+ !link.target.y)
1443
+ return;
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
+ }
1457
+ if (linkOptions.drawLink) {
1458
+ linkOptions.drawLink(link, linkOptions, state);
1459
+ return;
1460
+ }
1461
+ let alpha = linkOptions.alpha;
1462
+ let arrowAlpha = linkOptions.arrowReverseAppear ? 0 : linkOptions.arrowAlpha;
1463
+ if (this.highlightedNeighbors && this.highlightedNode) {
1464
+ /** Not highlighted */
1465
+ if (this.highlightedNode.id != link.source.id &&
1466
+ this.highlightedNode.id != link.target.id) {
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
+ }
1488
+ }
787
1489
  }
1490
+ /** Link */
788
1491
  this.context.beginPath();
789
- }
790
- this.context.globalAlpha = alpha;
791
- this.context.strokeStyle = linkOptions.color;
792
- this.context.lineWidth = linkOptions.width;
793
- if (linkOptions.pretty) {
794
- const { x1, x2, y1, y2 } = calculateLinkPositionByRadius(link) ?? {
795
- x1: 0,
796
- x2: 0,
797
- y1: 0,
798
- y2: 0,
799
- };
800
- this.context.moveTo(x1, y1);
801
- this.context.lineTo(x2, y2);
802
- }
803
- else {
804
- this.context.moveTo(link.source.x, link.source.y);
805
- this.context.lineTo(link.target.x, link.target.y);
806
- }
807
- if (this.highlightedNeighbors && this.highlightedNode)
1492
+ this.context.globalAlpha = alpha;
1493
+ this.context.strokeStyle = linkOptions.color;
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;
1499
+ if (linkOptions.pretty) {
1500
+ const isHasArrow = linkOptions.arrow && arrowAlpha > 0;
1501
+ const { x1, x2, y1, y2 } = calculateLinkPositionByRadius(link, isHasArrow ? linkOptions.arrowSize : 0) ?? {
1502
+ x1: 0,
1503
+ x2: 0,
1504
+ y1: 0,
1505
+ y2: 0,
1506
+ };
1507
+ xStart = x1;
1508
+ xEnd = x2;
1509
+ yStart = y1;
1510
+ yEnd = y2;
1511
+ }
1512
+ this.context.moveTo(xStart, yStart);
1513
+ this.context.lineTo(xEnd, yEnd);
808
1514
  this.context.stroke();
809
- }
810
- const drawNode = (textRenders) => function drawNode(node, index) {
811
- if (!this.context || !node.x || !node.y)
812
- return;
813
- const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
814
- let alpha = nodeOptions.alpha;
815
- let color = nodeOptions.color;
816
- let radiusInitial = nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial;
817
- let textAlpha = nodeOptions.textAlpha;
818
- let textSize = nodeOptions.textSize;
819
- let textShiftX = nodeOptions.textShiftX;
820
- let textShiftY = nodeOptions.textShiftY;
821
- let textWeight = nodeOptions.textWeight;
822
- let textWidth = nodeOptions.textWidth;
823
- if (this.highlightedNeighbors && this.highlightedNode) {
824
- /** Not highlighted */
825
- if (!this.highlightedNeighbors.has(node.id) && this.highlightedNode.id != node.id) {
826
- if (nodeOptions.highlightFading) {
827
- alpha =
828
- this.graphSettings.highlightFadingMin +
829
- (alpha - this.graphSettings.highlightFadingMin) * (1 - this.highlightProgress);
830
- }
831
- if (nodeOptions.highlightTextFading) {
832
- textAlpha =
833
- this.graphSettings.highlightTextFadingMin +
834
- (textAlpha - this.graphSettings.highlightTextFadingMin) *
835
- (1 - this.highlightProgress);
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
+ });
836
1530
  }
837
- if (nodeOptions.highlightColor) {
838
- const colorRgb = extractRgb(colorToRgb(color));
839
- if (colorRgb) {
840
- const colorRgbFade = fadeRgb(colorRgb, this.graphSettings.highlightColorFadingMin);
841
- const colorFadeAnimation = rgbAnimationByProgress(colorRgb, colorRgbFade, this.highlightProgress);
842
- color = `rgb(${colorFadeAnimation.r}, ${colorFadeAnimation.g}, ${colorFadeAnimation.b})`;
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();
843
1548
  }
1549
+ });
1550
+ }
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();
1568
+ this.context.stroke();
1569
+ }
1570
+ if (linkOptions.drawExtraLink) {
1571
+ linkOptions.drawExtraLink(link, { ...linkOptions, alpha }, state);
1572
+ }
1573
+ };
1574
+ }
1575
+ function getDrawNode(nodeRenders, textRenders, state) {
1576
+ return function drawNode(node, index) {
1577
+ if (!this.context || !node.x || !node.y)
1578
+ return;
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;
844
1587
  }
845
1588
  }
846
- else if (!this.graphSettings.highlightOnlyRoot ||
847
- (this.graphSettings.highlightOnlyRoot && this.highlightedNode.id === node.id)) {
848
- /** Highlighted */
849
- if (nodeOptions.highlightSizing) {
850
- const radiusMax = radiusInitial + this.graphSettings.highlightSizingAdditional;
851
- radiusInitial += ((radiusMax - radiusInitial) / 100) * (this.highlightProgress * 100);
1589
+ if (nodeOptions.nodeDraw) {
1590
+ nodeOptions.nodeDraw(node, nodeOptions, state);
1591
+ return;
1592
+ }
1593
+ let alpha = nodeOptions.alpha;
1594
+ let color = nodeOptions.color;
1595
+ let radiusInitial = nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial;
1596
+ let textAlpha = nodeOptions.textAlpha;
1597
+ let textSize = nodeOptions.textSize;
1598
+ let textShiftX = nodeOptions.textShiftX;
1599
+ let textShiftY = nodeOptions.textShiftY;
1600
+ let textWeight = nodeOptions.textWeight;
1601
+ let textWidth = nodeOptions.textWidth;
1602
+ if (this.highlightedNeighbors && this.highlightedNode) {
1603
+ /** Not highlighted */
1604
+ if (!this.highlightedNeighbors.has(node.id) && this.highlightedNode.id != node.id) {
1605
+ if (nodeOptions.highlightFading) {
1606
+ const min = this.graphSettings.highlightFadingMin < alpha
1607
+ ? this.graphSettings.highlightFadingMin
1608
+ : alpha;
1609
+ alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
1610
+ }
1611
+ if (nodeOptions.highlightTextFading) {
1612
+ const min = this.graphSettings.highlightTextFadingMin < textAlpha
1613
+ ? this.graphSettings.highlightTextFadingMin
1614
+ : textAlpha;
1615
+ textAlpha = animationByProgress(min, textAlpha - min, 1 - this.highlightProgress);
1616
+ }
1617
+ if (nodeOptions.highlightColor) {
1618
+ const colorRgb = extractRgb(colorToRgb(color));
1619
+ if (colorRgb) {
1620
+ const colorRgbFade = fadeRgb(colorRgb, this.graphSettings.highlightColorFadingMin);
1621
+ const colorFadeAnimation = rgbAnimationByProgress(colorRgb, colorRgbFade, this.highlightProgress);
1622
+ color = `rgb(${colorFadeAnimation.r}, ${colorFadeAnimation.g}, ${colorFadeAnimation.b})`;
1623
+ }
1624
+ }
852
1625
  }
853
- if (nodeOptions.highlightTextSizing) {
854
- const textSizeMax = textSize + this.graphSettings.highlightTextSizingAdditional;
855
- const textShiftXMax = textShiftX + this.graphSettings.highlightTextShiftXAdditional;
856
- const textShiftYMax = textShiftY + this.graphSettings.highlightTextShiftYAdditional;
857
- const textWeightMax = textWeight + this.graphSettings.highlightTextWeightAdditional;
858
- const textWidthMax = textWidth + this.graphSettings.highlightTextWidthAdditional;
859
- textSize += ((textSizeMax - textSize) / 100) * (this.highlightProgress * 100);
860
- textShiftX += ((textShiftXMax - textShiftX) / 100) * (this.highlightProgress * 100);
861
- textShiftY += ((textShiftYMax - textShiftY) / 100) * (this.highlightProgress * 100);
862
- textWeight += ((textWeightMax - textWeight) / 100) * (this.highlightProgress * 100);
863
- textWidth += ((textWidthMax - textWidth) / 100) * (this.highlightProgress * 100);
1626
+ else if (!this.graphSettings.highlightOnlyRoot ||
1627
+ (this.graphSettings.highlightOnlyRoot && this.highlightedNode.id === node.id)) {
1628
+ /** Highlighted */
1629
+ if (nodeOptions.highlightSizing) {
1630
+ radiusInitial = animationByProgress(radiusInitial, this.graphSettings.highlightSizingAdditional, this.highlightProgress);
1631
+ }
1632
+ if (nodeOptions.highlightTextSizing) {
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);
1638
+ }
864
1639
  }
865
1640
  }
866
- }
867
- const radius = nodeRadiusGetter({
868
- radiusFlexible: this.graphSettings.nodeRadiusFlexible,
869
- radiusInitial,
870
- radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
871
- radiusFactor: this.graphSettings.nodeRadiusFactor,
872
- linkCount: node.linkCount,
873
- });
874
- node._radius = radius;
875
- this.context.beginPath();
876
- this.context.globalAlpha = alpha;
877
- /** text */
878
- if (nodeOptions.textVisible && nodeOptions.text) {
879
- textRenders.push(() => {
880
- if (!this.context || !node.x || !node.y || !nodeOptions.text)
1641
+ const radius = nodeRadiusGetter({
1642
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
1643
+ radiusInitial,
1644
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
1645
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
1646
+ linkCount: node.linkCount,
1647
+ });
1648
+ node._radius = radius;
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)
881
1663
  return;
882
1664
  this.context.beginPath();
883
- this.context.globalAlpha = textAlpha;
884
- drawText({
885
- id: node.id,
886
- cachedNodeText: this.cachedNodeText,
887
- context: this.context,
888
- text: nodeOptions.text,
889
- textAlign: nodeOptions.textAlign,
890
- textColor: nodeOptions.textColor,
891
- textFont: nodeOptions.textFont,
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
+ });
1674
+ /** text */
1675
+ if (nodeOptions.textVisible && nodeOptions.text) {
1676
+ textRenders.push(() => {
1677
+ if (nodeOptions.textDraw) {
1678
+ nodeOptions.textDraw(node, {
1679
+ ...nodeOptions,
1680
+ radius,
1681
+ alpha,
1682
+ color,
1683
+ textAlpha,
1684
+ textSize,
1685
+ textShiftX,
1686
+ textShiftY,
1687
+ textWeight,
1688
+ textWidth,
1689
+ }, state);
1690
+ return;
1691
+ }
1692
+ if (!this.context || !node.x || !node.y || !nodeOptions.text)
1693
+ return;
1694
+ this.context.beginPath();
1695
+ this.context.globalAlpha = textAlpha;
1696
+ drawText({
1697
+ id: node.id,
1698
+ cachedNodeText: this.cachedNodeText,
1699
+ context: this.context,
1700
+ text: nodeOptions.text,
1701
+ textAlign: nodeOptions.textAlign,
1702
+ textColor: nodeOptions.textColor,
1703
+ textFont: nodeOptions.textFont,
1704
+ textSize,
1705
+ x: node.x + textShiftX,
1706
+ y: node.y + radius + textShiftY,
1707
+ maxWidth: textWidth,
1708
+ textStyle: nodeOptions.textStyle,
1709
+ textWeight,
1710
+ textGap: nodeOptions.textGap,
1711
+ });
1712
+ if (nodeOptions.textExtraDraw) {
1713
+ nodeOptions.textExtraDraw(node, {
1714
+ ...nodeOptions,
1715
+ radius,
1716
+ alpha,
1717
+ color,
1718
+ textAlpha,
1719
+ textSize,
1720
+ textShiftX,
1721
+ textShiftY,
1722
+ textWeight,
1723
+ textWidth,
1724
+ }, state);
1725
+ }
1726
+ });
1727
+ }
1728
+ if (nodeOptions.nodeExtraDraw) {
1729
+ nodeOptions.nodeExtraDraw(node, {
1730
+ ...nodeOptions,
1731
+ radius,
1732
+ alpha,
1733
+ color,
1734
+ textAlpha,
892
1735
  textSize,
893
- x: node.x + textShiftX,
894
- y: node.y + radius + textShiftY,
895
- maxWidth: textWidth,
896
- textStyle: nodeOptions.textStyle,
1736
+ textShiftX,
1737
+ textShiftY,
897
1738
  textWeight,
898
- textGap: nodeOptions.textGap,
899
- });
900
- });
901
- }
902
- /** circle */
903
- this.context.lineWidth = nodeOptions.borderWidth;
904
- this.context.strokeStyle = nodeOptions.borderColor;
905
- this.context.fillStyle = color;
906
- this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
907
- this.context.fill();
908
- this.context.stroke();
909
- };
1739
+ textWidth,
1740
+ }, state);
1741
+ }
1742
+ };
1743
+ }
1744
+ if (this.graphSettings.showDrawTime) {
1745
+ return setDrawTime(draw.bind(this), this.graphSettings.showDrawTimeEveryTick);
1746
+ }
910
1747
  return draw;
911
1748
  }
912
1749
  initResize() {
@@ -1100,7 +1937,7 @@ class GraphCanvas {
1100
1937
  this.listeners.onEndDragFinished?.(event, this.state);
1101
1938
  }));
1102
1939
  }
1103
- initZoom() {
1940
+ initZoom(currentZoom) {
1104
1941
  if (!this.area)
1105
1942
  throw new Error("bad init data");
1106
1943
  const zoomInstance = d3Zoom.zoom()
@@ -1108,7 +1945,9 @@ class GraphCanvas {
1108
1945
  .on("zoom", (event) => {
1109
1946
  this.listeners.onZoom?.(event);
1110
1947
  this.areaTransform = event.transform;
1111
- if (!this.simulationWorking)
1948
+ this.linkOptionsCache = {};
1949
+ this.nodeOptionsCache = {};
1950
+ if (!this.simulationWorking && !this.highlightWorking)
1112
1951
  requestAnimationFrame(() => this.draw());
1113
1952
  });
1114
1953
  if (this.graphSettings.translateExtentEnable) {
@@ -1123,18 +1962,24 @@ class GraphCanvas {
1123
1962
  ]);
1124
1963
  }
1125
1964
  d3Selection.select(this.area).call(zoomInstance).on("dblclick.zoom", null);
1126
- const zoomInitial = this.graphSettings.zoomInitial;
1965
+ const zoomInitial = currentZoom ?? this.graphSettings.zoomInitial;
1127
1966
  this.areaTransform = new d3Zoom.ZoomTransform(zoomInitial?.k ?? 1, zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
1128
1967
  d3Zoom.zoom().transform(d3Selection.select(this.area), this.areaTransform);
1129
1968
  }
1130
1969
  }
1131
1970
 
1132
1971
  exports.GraphCanvas = GraphCanvas;
1972
+ exports.animationByProgress = animationByProgress;
1133
1973
  exports.calculateLinkPositionByRadius = calculateLinkPositionByRadius;
1134
1974
  exports.colorToRgb = colorToRgb;
1135
1975
  exports.drawText = drawText;
1136
1976
  exports.extractRgb = extractRgb;
1137
1977
  exports.fadeRgb = fadeRgb;
1978
+ exports.getForceControls = getForceControls;
1979
+ exports.getGraphControls = getGraphControls;
1980
+ exports.getLinkControls = getLinkControls;
1981
+ exports.getNodeControls = getNodeControls;
1982
+ exports.getParticlePosition = getParticlePosition;
1138
1983
  exports.linkIterationExtractor = linkIterationExtractor;
1139
1984
  exports.linkOptionsGetter = linkOptionsGetter;
1140
1985
  exports.nodeIterationExtractor = nodeIterationExtractor;