@krainovsd/graph 0.4.5 → 0.6.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 (48) hide show
  1. package/lib/cjs/index.cjs +1144 -0
  2. package/lib/cjs/index.cjs.map +1 -0
  3. package/lib/esm/index.js +12 -935
  4. package/lib/esm/index.js.map +1 -1
  5. package/lib/esm/lib/check-type.js +6 -0
  6. package/lib/esm/lib/check-type.js.map +1 -0
  7. package/lib/esm/lib/color-getter.js +28 -0
  8. package/lib/esm/lib/color-getter.js.map +1 -0
  9. package/lib/esm/lib/color-to-rgb.js +22 -0
  10. package/lib/esm/lib/color-to-rgb.js.map +1 -0
  11. package/lib/esm/lib/extract-rgb.js +15 -0
  12. package/lib/esm/lib/extract-rgb.js.map +1 -0
  13. package/lib/esm/lib/fade-rgb.js +11 -0
  14. package/lib/esm/lib/fade-rgb.js.map +1 -0
  15. package/lib/esm/lib/rgb-animation-by-progress.js +10 -0
  16. package/lib/esm/lib/rgb-animation-by-progress.js.map +1 -0
  17. package/lib/esm/module/GraphCanvas/GraphCanvas.js +731 -0
  18. package/lib/esm/module/GraphCanvas/GraphCanvas.js.map +1 -0
  19. package/lib/esm/module/GraphCanvas/constants/settings.js +95 -0
  20. package/lib/esm/module/GraphCanvas/constants/settings.js.map +1 -0
  21. package/lib/esm/module/GraphCanvas/lib/settings/force-settings-getter.js +11 -0
  22. package/lib/esm/module/GraphCanvas/lib/settings/force-settings-getter.js.map +1 -0
  23. package/lib/esm/module/GraphCanvas/lib/settings/graph-settings-getter.js +11 -0
  24. package/lib/esm/module/GraphCanvas/lib/settings/graph-settings-getter.js.map +1 -0
  25. package/lib/esm/module/GraphCanvas/lib/settings/link-settings-getter.js +19 -0
  26. package/lib/esm/module/GraphCanvas/lib/settings/link-settings-getter.js.map +1 -0
  27. package/lib/esm/module/GraphCanvas/lib/settings/listeners-getter.js +6 -0
  28. package/lib/esm/module/GraphCanvas/lib/settings/listeners-getter.js.map +1 -0
  29. package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js +53 -0
  30. package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js.map +1 -0
  31. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-radius.js +20 -0
  32. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-radius.js.map +1 -0
  33. package/lib/esm/module/GraphCanvas/lib/utils/drag-place-coefficient-getter.js +11 -0
  34. package/lib/esm/module/GraphCanvas/lib/utils/drag-place-coefficient-getter.js.map +1 -0
  35. package/lib/esm/module/GraphCanvas/lib/utils/draw-text.js +47 -0
  36. package/lib/esm/module/GraphCanvas/lib/utils/draw-text.js.map +1 -0
  37. package/lib/esm/module/GraphCanvas/lib/utils/is-overlaps-node.js +10 -0
  38. package/lib/esm/module/GraphCanvas/lib/utils/is-overlaps-node.js.map +1 -0
  39. package/lib/esm/module/GraphCanvas/lib/utils/link-iteration-extractor.js +37 -0
  40. package/lib/esm/module/GraphCanvas/lib/utils/link-iteration-extractor.js.map +1 -0
  41. package/lib/esm/module/GraphCanvas/lib/utils/node-by-pointer-getter.js +26 -0
  42. package/lib/esm/module/GraphCanvas/lib/utils/node-by-pointer-getter.js.map +1 -0
  43. package/lib/esm/module/GraphCanvas/lib/utils/node-iteration-extractor.js +37 -0
  44. package/lib/esm/module/GraphCanvas/lib/utils/node-iteration-extractor.js.map +1 -0
  45. package/lib/esm/module/GraphCanvas/lib/utils/pointer-getter.js +8 -0
  46. package/lib/esm/module/GraphCanvas/lib/utils/pointer-getter.js.map +1 -0
  47. package/lib/index.d.ts +135 -29
  48. package/package.json +20 -28
@@ -0,0 +1,1144 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ const jsHelpers = require('@krainovsd/js-helpers');
6
+ const d3Array = require('d3-array');
7
+ const d3Drag = require('d3-drag');
8
+ const d3Force = require('d3-force');
9
+ const d3Selection = require('d3-selection');
10
+ const d3Zoom = require('d3-zoom');
11
+
12
+ function checkType(value, condition) {
13
+ return condition;
14
+ }
15
+
16
+ function colorGetter() {
17
+ const chosenColors = {};
18
+ const colors = [
19
+ "#1f77b4",
20
+ "#ff7f0e",
21
+ "#2ca02c",
22
+ "#d62728",
23
+ "#9467bd",
24
+ "#8c564b",
25
+ "#e377c2",
26
+ "#7f7f7f",
27
+ "#bcbd22",
28
+ "#17becf",
29
+ ];
30
+ let cursor = 0;
31
+ return function color(key) {
32
+ if (chosenColors[key])
33
+ return chosenColors[key];
34
+ chosenColors[key] = colors[cursor];
35
+ cursor++;
36
+ if (cursor >= colors.length)
37
+ cursor = 0;
38
+ return chosenColors[key];
39
+ };
40
+ }
41
+
42
+ function colorToRgb(color) {
43
+ const format = jsHelpers.getColorFormat(color);
44
+ switch (format) {
45
+ case jsHelpers.COLOR_FORMATS.Hex: {
46
+ return jsHelpers.transformHEXtoRGB(color);
47
+ }
48
+ case jsHelpers.COLOR_FORMATS.Rgb: {
49
+ return color;
50
+ }
51
+ case jsHelpers.COLOR_FORMATS.Rgba: {
52
+ return jsHelpers.transformRGBAtoRGB(color);
53
+ }
54
+ default: {
55
+ return null;
56
+ }
57
+ }
58
+ }
59
+
60
+ function extractRgb(color) {
61
+ if (!color)
62
+ return null;
63
+ const code = color.match(/\d+/g);
64
+ if (!code)
65
+ return null;
66
+ return {
67
+ r: +code[0],
68
+ g: +code[1],
69
+ b: +code[2],
70
+ };
71
+ }
72
+
73
+ function fadeRgb(color, fade) {
74
+ const gray = (color.r + color.g + color.b) / 3;
75
+ return {
76
+ r: color.r * fade + gray * (1 - fade),
77
+ g: color.g * fade + gray * (1 - fade),
78
+ b: color.b * fade + gray * (1 - fade),
79
+ };
80
+ }
81
+
82
+ function rgbAnimationByProgress(start, end, progress) {
83
+ return {
84
+ r: start.r + (end.r - start.r) * progress,
85
+ g: start.g + (end.g - start.g) * progress,
86
+ b: start.b + (end.b - start.b) * progress,
87
+ };
88
+ }
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
+ function forceSettingsGetter(settings, prevSettings) {
180
+ return {
181
+ ...(prevSettings ?? FORCE_SETTINGS),
182
+ ...settings,
183
+ };
184
+ }
185
+
186
+ function graphSettingsGetter(settings, prevSettings) {
187
+ return {
188
+ ...(prevSettings ?? GRAPH_SETTINGS),
189
+ ...settings,
190
+ };
191
+ }
192
+
193
+ function linkSettingsGetter(settings) {
194
+ return { options: settings?.options };
195
+ }
196
+ function linkOptionsGetter(_, __, ___, state) {
197
+ return {
198
+ ...LINK_SETTINGS,
199
+ color: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkColorZoomBorder
200
+ ? COMMON_SETTINGS.linkColorZoomNear
201
+ : COMMON_SETTINGS.linkColorZoomFar,
202
+ width: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkWidthZoomBorder
203
+ ? COMMON_SETTINGS.linkWidthZoomNear
204
+ : COMMON_SETTINGS.linkWidthZoomFar,
205
+ };
206
+ }
207
+
208
+ function listenersGetter(settings) {
209
+ return settings ?? {};
210
+ }
211
+
212
+ function nodeSettingsGetter(settings) {
213
+ return {
214
+ idGetter: settings?.idGetter ?? nodeIdGetter,
215
+ options: settings?.options,
216
+ };
217
+ }
218
+ const color = colorGetter();
219
+ function nodeOptionsGetter(node, _, __, state) {
220
+ const { textShiftY, textSize } = nodeTextSizeGetter(state?.areaTransform);
221
+ return {
222
+ ...NODE_SETTINGS,
223
+ color: color(String(node.group ?? "_DEFAULT")),
224
+ textVisible: Boolean(state?.areaTransform && state.areaTransform.k > COMMON_SETTINGS.nodeTextScaleMin),
225
+ text: node.name ?? node.id.toString(),
226
+ textShiftY,
227
+ textSize,
228
+ };
229
+ }
230
+ function nodeTextSizeGetter(transform) {
231
+ let textSize = COMMON_SETTINGS.nodeTextSizeMax;
232
+ let textShiftY = COMMON_SETTINGS.nodeTextShiftYMax;
233
+ if (transform) {
234
+ const scaleStepCoefficient = (COMMON_SETTINGS.nodeTextScaleMax - COMMON_SETTINGS.nodeTextScaleMin) /
235
+ COMMON_SETTINGS.nodeTextChangeStepCount;
236
+ const textStepCoefficient = (COMMON_SETTINGS.nodeTextSizeMax - COMMON_SETTINGS.nodeTextSizeMin) /
237
+ COMMON_SETTINGS.nodeTextChangeStepCount;
238
+ const shiftStepCoefficient = (COMMON_SETTINGS.nodeTextShiftYMax - COMMON_SETTINGS.nodeTextShiftYMin) /
239
+ COMMON_SETTINGS.nodeTextChangeStepCount;
240
+ if (transform.k >= COMMON_SETTINGS.nodeTextScaleMax) {
241
+ textSize = COMMON_SETTINGS.nodeTextSizeMin;
242
+ textShiftY = COMMON_SETTINGS.nodeTextShiftYMin;
243
+ }
244
+ else if (transform.k > COMMON_SETTINGS.nodeTextScaleMin) {
245
+ const transformSteps = (transform.k - COMMON_SETTINGS.nodeTextScaleMin) / scaleStepCoefficient;
246
+ textSize -= transformSteps * textStepCoefficient;
247
+ textShiftY -= transformSteps * shiftStepCoefficient;
248
+ }
249
+ }
250
+ return { textSize, textShiftY };
251
+ }
252
+ function nodeRadiusGetter({ radiusFlexible, radiusInitial, linkCount, radiusCoefficient, radiusFactor, }) {
253
+ return ((radiusFlexible && linkCount ? linkCount / radiusCoefficient : 0) * radiusFactor + radiusInitial);
254
+ }
255
+ function nodeIdGetter(node) {
256
+ return node.id;
257
+ }
258
+
259
+ function pointerGetter(mouseEvent, areaRect, areaTransform) {
260
+ const px = (mouseEvent.clientX - areaRect.left - areaTransform.x) / areaTransform.k;
261
+ const py = (mouseEvent.clientY - areaRect.top - areaTransform.y) / areaTransform.k;
262
+ return [px, py];
263
+ }
264
+
265
+ function isOverlapsNode(node, radius, pointerX, pointerY) {
266
+ if (node.x == undefined || node.y == undefined)
267
+ return false;
268
+ const isOverX = node.x - radius <= pointerX && pointerX <= node.x + radius;
269
+ const isOverY = node.y - radius <= pointerY && pointerY <= node.y + radius;
270
+ return isOverX && isOverY;
271
+ }
272
+
273
+ function dragPlaceCoefficientGetter(node, pointerX, pointerY, radius) {
274
+ if (!node.x || !node.y)
275
+ return undefined;
276
+ if (isOverlapsNode(node, radius, pointerX, pointerY))
277
+ return node.index;
278
+ }
279
+
280
+ function linkIterationExtractor(link, i, links, state, option, optionConstantGetter) {
281
+ let customOptions;
282
+ let constantOptions;
283
+ if (typeof option === "function")
284
+ customOptions = option(link, i, links, state);
285
+ else
286
+ customOptions = option;
287
+ if (customOptions && typeof customOptions === "object" && !Array.isArray(customOptions)) {
288
+ for (const key in customOptions) {
289
+ if (customOptions[key] === undefined)
290
+ delete customOptions[key];
291
+ }
292
+ }
293
+ if (optionConstantGetter) {
294
+ if (typeof optionConstantGetter === "function")
295
+ constantOptions = optionConstantGetter(link, i, links, state);
296
+ else
297
+ constantOptions = optionConstantGetter;
298
+ if (constantOptions &&
299
+ typeof constantOptions === "object" &&
300
+ !Array.isArray(constantOptions) &&
301
+ checkType(customOptions, customOptions === undefined ||
302
+ (typeof customOptions === "object" && !Array.isArray(customOptions)))) {
303
+ return {
304
+ ...constantOptions,
305
+ ...customOptions,
306
+ };
307
+ }
308
+ }
309
+ return customOptions;
310
+ }
311
+
312
+ function nodeByPointerGetter({ areaRect, areaTransform, mouseEvent, nodes, graphSettings, }) {
313
+ if (!areaRect)
314
+ return undefined;
315
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
316
+ return d3Array.greatest(nodes, (node) => {
317
+ const radius = node._radius ??
318
+ nodeRadiusGetter({
319
+ radiusFlexible: graphSettings.nodeRadiusFlexible,
320
+ radiusInitial: graphSettings.nodeRadiusInitial,
321
+ radiusCoefficient: graphSettings.nodeRadiusCoefficient,
322
+ radiusFactor: graphSettings.nodeRadiusFactor,
323
+ linkCount: node.linkCount,
324
+ });
325
+ if (isOverlapsNode(node, radius, pointerX, pointerY))
326
+ return node.index;
327
+ });
328
+ }
329
+
330
+ function nodeIterationExtractor(node, i, nodes, state, option, optionConstantGetter) {
331
+ let customOptions;
332
+ let constantOptions;
333
+ if (typeof option === "function")
334
+ customOptions = option(node, i, nodes, state);
335
+ else
336
+ customOptions = option;
337
+ if (customOptions && typeof customOptions === "object" && !Array.isArray(customOptions)) {
338
+ for (const key in customOptions) {
339
+ if (customOptions[key] === undefined)
340
+ delete customOptions[key];
341
+ }
342
+ }
343
+ if (optionConstantGetter) {
344
+ if (typeof optionConstantGetter === "function")
345
+ constantOptions = optionConstantGetter(node, i, nodes, state);
346
+ else
347
+ constantOptions = optionConstantGetter;
348
+ if (constantOptions &&
349
+ typeof constantOptions === "object" &&
350
+ !Array.isArray(constantOptions) &&
351
+ checkType(customOptions, customOptions === undefined ||
352
+ (typeof customOptions === "object" && !Array.isArray(customOptions)))) {
353
+ return {
354
+ ...constantOptions,
355
+ ...customOptions,
356
+ };
357
+ }
358
+ }
359
+ return customOptions;
360
+ }
361
+
362
+ const SPACE = " ";
363
+ function drawText({ context, id, textAlign, textColor, textFont, textStyle, textGap, textWeight, textSize, text, x, y, cachedNodeText, maxWidth, }) {
364
+ context.font = `${textStyle} normal ${textWeight} ${textSize}px ${textFont}`;
365
+ context.fillStyle = textColor;
366
+ context.textAlign = textAlign;
367
+ if (cachedNodeText[id] != undefined) {
368
+ cachedNodeText[id].forEach((line, index) => {
369
+ context.fillText(line, x, y + index * textSize + index * textGap);
370
+ });
371
+ return;
372
+ }
373
+ if (maxWidth == undefined || context.measureText(text).width <= maxWidth) {
374
+ cachedNodeText[id] = [text];
375
+ context.fillText(text, x, y);
376
+ return;
377
+ }
378
+ const spaceWidth = context.measureText(" ").width;
379
+ const lines = [];
380
+ let lineWidth = 0;
381
+ let line = "";
382
+ for (const word of text.split(" ")) {
383
+ const size = context.measureText(word).width;
384
+ lineWidth += size;
385
+ if (line === "") {
386
+ line = word;
387
+ continue;
388
+ }
389
+ if (lineWidth > maxWidth) {
390
+ lineWidth = 0;
391
+ lines.push(line);
392
+ line = word;
393
+ }
394
+ else {
395
+ lineWidth += spaceWidth;
396
+ line += `${SPACE}${word}`;
397
+ }
398
+ }
399
+ if (line !== "")
400
+ lines.push(line);
401
+ cachedNodeText[id] = lines;
402
+ lines.forEach((line, index) => {
403
+ context.fillText(line, x, y + index * textSize + index * textGap);
404
+ });
405
+ }
406
+
407
+ function calculateLinkPositionByRadius(link) {
408
+ const source = link.source;
409
+ const target = link.target;
410
+ if (typeof source != "object" || typeof target != "object")
411
+ return null;
412
+ const dx = (target.x ?? 0) - (source.x ?? 0);
413
+ const dy = (target.y ?? 0) - (source.y ?? 0);
414
+ const dr = Math.sqrt(dx * dx + dy * dy);
415
+ const sourceRadius = source._radius ?? 0;
416
+ const targetRadius = target._radius ?? 0;
417
+ return {
418
+ x1: (source.x ?? 0) + (dx * sourceRadius) / dr,
419
+ y1: (source.y ?? 0) + (dy * sourceRadius) / dr,
420
+ x2: (target.x ?? 0) - (dx * targetRadius) / dr,
421
+ y2: (target.y ?? 0) - (dy * targetRadius) / dr,
422
+ };
423
+ }
424
+
425
+ class GraphCanvas {
426
+ /** initial data */
427
+ nodes;
428
+ links;
429
+ width;
430
+ height;
431
+ root;
432
+ container;
433
+ area;
434
+ /** settings */
435
+ graphSettings;
436
+ forceSettings;
437
+ nodeSettings;
438
+ linkSettings;
439
+ listeners;
440
+ /** service */
441
+ context;
442
+ simulation;
443
+ areaTransform = d3Zoom.zoomIdentity;
444
+ areaRect;
445
+ draw;
446
+ eventAbortController;
447
+ cachedNodeText = {};
448
+ isDragging = false;
449
+ highlightedNode = null;
450
+ highlightedNeighbors = null;
451
+ highlightProgress = 1;
452
+ highlightWorking = false;
453
+ highlightDrawing = false;
454
+ get simulationWorking() {
455
+ const simulationAlpha = this.simulation?.alpha?.() ?? 0;
456
+ const simulationAlphaMin = this.simulation?.alphaMin?.() ?? 0;
457
+ const simulationAlphaDecay = this.simulation?.alphaDecay?.() ?? 0;
458
+ const force = (simulationAlpha - simulationAlphaMin) / simulationAlphaDecay;
459
+ return force > 0;
460
+ }
461
+ get state() {
462
+ return {
463
+ areaTransform: this.areaTransform,
464
+ cachedNodeText: this.cachedNodeText,
465
+ context: this.context,
466
+ eventAbortController: this.eventAbortController,
467
+ highlightProgress: this.highlightProgress,
468
+ highlightDrawing: this.highlightDrawing,
469
+ highlightedNeighbors: this.highlightedNeighbors,
470
+ highlightedNode: this.highlightedNode,
471
+ highlightWorking: this.highlightWorking,
472
+ isDragging: this.isDragging,
473
+ simulation: this.simulation,
474
+ simulationWorking: this.simulationWorking,
475
+ height: this.height,
476
+ links: this.links,
477
+ nodes: this.nodes,
478
+ width: this.width,
479
+ forceSettings: this.forceSettings,
480
+ graphSettings: this.graphSettings,
481
+ linkSettings: this.linkSettings,
482
+ nodeSettings: this.nodeSettings,
483
+ };
484
+ }
485
+ constructor({ links, nodes, root, forceSettings, linkSettings, listeners, nodeSettings, graphSettings, }) {
486
+ // root.style.position = "relative";
487
+ root.style.overflow = "hidden";
488
+ this.root = root;
489
+ this.forceSettings = forceSettingsGetter(forceSettings);
490
+ this.linkSettings = linkSettingsGetter(linkSettings);
491
+ this.nodeSettings = nodeSettingsGetter(nodeSettings);
492
+ this.listeners = listenersGetter(listeners);
493
+ this.graphSettings = graphSettingsGetter(graphSettings);
494
+ this.eventAbortController = new AbortController();
495
+ this.nodes = nodes;
496
+ this.links = links;
497
+ this.height = 0;
498
+ this.width = 0;
499
+ this.draw = this.initDraw();
500
+ this.init();
501
+ }
502
+ get dpi() {
503
+ return devicePixelRatio;
504
+ }
505
+ getData() {
506
+ return {
507
+ links: this.links,
508
+ nodes: this.nodes,
509
+ };
510
+ }
511
+ changeData(options, alpha) {
512
+ if (options.links != undefined)
513
+ this.links = options.links;
514
+ if (options.nodes != undefined)
515
+ this.nodes = options.nodes;
516
+ if (options.nodes != undefined || options.links != undefined)
517
+ this.updateData(alpha);
518
+ }
519
+ changeSettings(options) {
520
+ if (options.graphSettings)
521
+ this.graphSettings = graphSettingsGetter(options.graphSettings, this.graphSettings);
522
+ if (options.forceSettings)
523
+ 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)
529
+ return void this.updateSimulation();
530
+ this.tick();
531
+ }
532
+ tick() {
533
+ if (!this.simulationWorking)
534
+ this.draw();
535
+ }
536
+ restart(alpha) {
537
+ if (this.simulation)
538
+ this.simulation.alpha(alpha ?? 1).restart();
539
+ }
540
+ start() {
541
+ if (this.simulation)
542
+ this.simulation.alpha(1).restart();
543
+ if (this.container)
544
+ this.container.style.display = "block";
545
+ }
546
+ stop() {
547
+ if (this.simulation)
548
+ this.simulation.stop();
549
+ if (this.container)
550
+ this.container.style.display = "none";
551
+ }
552
+ create() {
553
+ this.init();
554
+ }
555
+ destroy() {
556
+ if (this.simulation) {
557
+ this.simulation.stop();
558
+ this.simulation = undefined;
559
+ }
560
+ this.clearHTMLElements();
561
+ this.clearState();
562
+ this.clearDataDependencies();
563
+ }
564
+ clearHTMLElements() {
565
+ this.root.replaceChildren();
566
+ this.area = undefined;
567
+ this.context = undefined;
568
+ this.container = undefined;
569
+ this.eventAbortController.abort();
570
+ this.eventAbortController = new AbortController();
571
+ }
572
+ updateSimulation() {
573
+ if (this.simulation) {
574
+ this.initSimulationForces();
575
+ this.simulation.alpha(1);
576
+ this.simulation.restart();
577
+ }
578
+ }
579
+ clearState() {
580
+ this.isDragging = false;
581
+ this.highlightedNode = null;
582
+ this.highlightedNeighbors = null;
583
+ this.highlightProgress = 0;
584
+ this.highlightWorking = false;
585
+ this.highlightDrawing = false;
586
+ }
587
+ clearDataDependencies() {
588
+ this.cachedNodeText = {};
589
+ }
590
+ updateData(alpha) {
591
+ this.clearDataDependencies();
592
+ if (this.simulation) {
593
+ this.initCollideForce();
594
+ this.simulation
595
+ .nodes(this.nodes)
596
+ .force("link", d3Force.forceLink(this.links)
597
+ .id(this.nodeSettings.idGetter)
598
+ .distance(this.forceSettings.linkDistance)
599
+ .strength(this.forceSettings.linkStrength)
600
+ .iterations(this.forceSettings.linkIterations))
601
+ .alpha(alpha ?? 0.5)
602
+ .restart();
603
+ }
604
+ }
605
+ updateSize() {
606
+ this.clearHTMLElements();
607
+ this.initArea();
608
+ this.initDnd();
609
+ this.initZoom();
610
+ this.initResize();
611
+ this.initPointer();
612
+ if (!this.simulationWorking)
613
+ this.draw();
614
+ }
615
+ init() {
616
+ this.initArea();
617
+ this.initSimulation();
618
+ this.initDnd();
619
+ this.initZoom();
620
+ this.initResize();
621
+ this.initPointer();
622
+ }
623
+ initSimulation() {
624
+ if (!this.simulation) {
625
+ this.simulation = d3Force.forceSimulation()
626
+ .nodes(this.nodes)
627
+ .force("link", d3Force.forceLink(this.links).id(this.nodeSettings.idGetter))
628
+ .on("tick", () => {
629
+ this.draw();
630
+ })
631
+ .on("end", () => {
632
+ this.listeners.onSimulationEnd?.(this.state);
633
+ });
634
+ this.initSimulationForces();
635
+ }
636
+ }
637
+ initSimulationForces() {
638
+ if (!this.simulation)
639
+ return;
640
+ const linkForce = this.simulation.force("link");
641
+ if (!linkForce)
642
+ return;
643
+ linkForce
644
+ .distance(this.forceSettings.linkDistance)
645
+ .strength(this.forceSettings.linkStrength)
646
+ .iterations(this.forceSettings.linkIterations);
647
+ this.simulation
648
+ .force("x", d3Force.forceX(this.forceSettings.xForce).strength(this.forceSettings.xStrength))
649
+ .force("y", d3Force.forceY(this.forceSettings.yForce).strength(this.forceSettings.yStrength))
650
+ .force("charge", d3Force.forceManyBody()
651
+ .strength(this.forceSettings.chargeStrength)
652
+ .distanceMax(this.forceSettings.chargeDistanceMax)
653
+ .distanceMin(this.forceSettings.chargeDistanceMin))
654
+ .force("center", d3Force.forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
655
+ this.initCollideForce();
656
+ }
657
+ initCollideForce() {
658
+ if (!this.simulation)
659
+ return;
660
+ if (!this.forceSettings.collideOn) {
661
+ if (this.simulation.force("collide"))
662
+ this.simulation.force("collide", null);
663
+ return;
664
+ }
665
+ const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
666
+ const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
667
+ const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
668
+ if (isMaxCollideNodes && isMaxCollideLinks)
669
+ this.simulation.force("collide", null);
670
+ else if (!this.simulation.force("collide"))
671
+ this.simulation.force("collide", d3Force.forceCollide()
672
+ .radius((node, index) => {
673
+ if (this.forceSettings.collideRadius) {
674
+ return nodeIterationExtractor(node, index, this.nodes, this.state, this.forceSettings.collideRadius, undefined);
675
+ }
676
+ const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
677
+ const radius = nodeRadiusGetter({
678
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
679
+ radiusInitial: nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial,
680
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
681
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
682
+ linkCount: node.linkCount,
683
+ });
684
+ return radius + this.forceSettings.collideAdditionalRadius;
685
+ })
686
+ .strength(this.forceSettings.collideStrength)
687
+ .iterations(this.forceSettings.collideIterations));
688
+ }
689
+ initArea() {
690
+ if (!this.area || !this.context || !this.container) {
691
+ this.container = d3Selection.create("div")
692
+ .attr("style", "padding: 0 !important; width: 100%; height: 100%;")
693
+ .node();
694
+ if (!this.container)
695
+ throw new Error("couldn't create container");
696
+ this.root.appendChild(this.container);
697
+ const { width, height } = this.root.getBoundingClientRect();
698
+ this.width = width;
699
+ this.height = height;
700
+ this.area = d3Selection.create("canvas")
701
+ .attr("width", this.dpi * this.width)
702
+ .attr("height", this.dpi * this.height)
703
+ .attr("style", `width: 100%; height: 100%; border: none !important;`)
704
+ .node();
705
+ if (!this.area)
706
+ throw new Error("couldn't create canvas");
707
+ this.container.appendChild(this.area);
708
+ this.areaRect = this.area.getBoundingClientRect();
709
+ this.context = this.area.getContext("2d");
710
+ if (!this.context)
711
+ throw new Error("couldn't create canvas context");
712
+ this.context.scale(this.dpi, this.dpi);
713
+ }
714
+ }
715
+ initDraw() {
716
+ function calculateHighlightFading() {
717
+ this.highlightDrawing = true;
718
+ if (!this.highlightWorking && this.highlightProgress > 0) {
719
+ this.highlightProgress -= this.graphSettings.highlightDownStep;
720
+ if (!this.simulationWorking)
721
+ return void requestAnimationFrame(() => this.draw());
722
+ return;
723
+ }
724
+ if (this.highlightWorking && this.highlightProgress < 1) {
725
+ this.highlightProgress += this.graphSettings.highlightUpStep;
726
+ if (!this.simulationWorking)
727
+ return void requestAnimationFrame(() => this.draw());
728
+ return;
729
+ }
730
+ if (!this.highlightWorking && this.highlightProgress <= 0) {
731
+ if (this.highlightedNeighbors)
732
+ this.highlightedNeighbors = null;
733
+ if (this.highlightedNode)
734
+ this.highlightedNode = null;
735
+ }
736
+ this.highlightDrawing = false;
737
+ }
738
+ function draw() {
739
+ if (!this.context)
740
+ return;
741
+ if (this.listeners.onDraw) {
742
+ this.listeners.onDraw(this.state, (status) => {
743
+ this.highlightDrawing = status;
744
+ }, () => {
745
+ if (this.highlightedNeighbors)
746
+ this.highlightedNeighbors = null;
747
+ if (this.highlightedNode)
748
+ this.highlightedNode = null;
749
+ });
750
+ return;
751
+ }
752
+ this.context.save();
753
+ this.context.clearRect(0, 0, this.width, this.height);
754
+ this.context.translate(this.areaTransform.x, this.areaTransform.y);
755
+ this.context.scale(this.areaTransform.k, this.areaTransform.k);
756
+ /** links */
757
+ this.context.beginPath();
758
+ this.links.forEach(drawLink.bind(this));
759
+ this.context.stroke();
760
+ /** nodes */
761
+ const textRenders = [];
762
+ this.nodes.forEach(drawNode(textRenders).bind(this));
763
+ textRenders.forEach((render) => render());
764
+ this.context.restore();
765
+ this.listeners.onDrawFinished?.(this.state);
766
+ calculateHighlightFading.bind(this)();
767
+ }
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);
787
+ }
788
+ 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)
808
+ 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);
836
+ }
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})`;
843
+ }
844
+ }
845
+ }
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);
852
+ }
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);
864
+ }
865
+ }
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)
881
+ return;
882
+ 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,
892
+ textSize,
893
+ x: node.x + textShiftX,
894
+ y: node.y + radius + textShiftY,
895
+ maxWidth: textWidth,
896
+ textStyle: nodeOptions.textStyle,
897
+ 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
+ };
910
+ return draw;
911
+ }
912
+ initResize() {
913
+ if (!this.area)
914
+ throw new Error("bad init data");
915
+ let initialResizeCall = true;
916
+ const abortController = this.eventAbortController;
917
+ const observer = new ResizeObserver(() => {
918
+ if (initialResizeCall) {
919
+ initialResizeCall = false;
920
+ return;
921
+ }
922
+ if (abortController.signal.aborted) {
923
+ observer.disconnect();
924
+ return;
925
+ }
926
+ requestAnimationFrame(() => {
927
+ this.updateSize();
928
+ });
929
+ });
930
+ observer.observe(this.area);
931
+ }
932
+ initPointer() {
933
+ if (!this.area || !this.nodes || !this.simulation)
934
+ throw new Error("bad init data");
935
+ /** hover */
936
+ this.area.addEventListener("pointermove", (event) => {
937
+ let currentNode;
938
+ if (this.graphSettings.highlightByHover && !this.isDragging) {
939
+ currentNode = nodeByPointerGetter({
940
+ graphSettings: this.graphSettings,
941
+ areaRect: this.areaRect,
942
+ areaTransform: this.areaTransform,
943
+ mouseEvent: event,
944
+ nodes: this.nodes,
945
+ });
946
+ if (currentNode && this.highlightedNode !== currentNode) {
947
+ this.highlightedNode = currentNode;
948
+ this.highlightedNeighbors = new Set(this.highlightedNode?.neighbors ?? []);
949
+ this.highlightWorking = true;
950
+ if (!this.simulationWorking && !this.highlightDrawing)
951
+ requestAnimationFrame(() => {
952
+ this.draw();
953
+ });
954
+ }
955
+ else if (!currentNode && this.highlightedNode) {
956
+ this.highlightWorking = false;
957
+ if (!this.simulationWorking && !this.highlightDrawing)
958
+ requestAnimationFrame(() => {
959
+ this.draw();
960
+ });
961
+ }
962
+ }
963
+ if (!this.listeners.onMove)
964
+ return;
965
+ if (!currentNode)
966
+ currentNode = nodeByPointerGetter({
967
+ graphSettings: this.graphSettings,
968
+ areaRect: this.areaRect,
969
+ areaTransform: this.areaTransform,
970
+ mouseEvent: event,
971
+ nodes: this.nodes,
972
+ });
973
+ return void this.listeners.onMove(event, currentNode);
974
+ }, {
975
+ signal: this.eventAbortController.signal,
976
+ });
977
+ /** dblclick */
978
+ this.area.addEventListener("dblclick", (event) => {
979
+ if (!this.listeners.onDoubleClick)
980
+ return;
981
+ const currentNode = nodeByPointerGetter({
982
+ graphSettings: this.graphSettings,
983
+ areaRect: this.areaRect,
984
+ areaTransform: this.areaTransform,
985
+ mouseEvent: event,
986
+ nodes: this.nodes,
987
+ });
988
+ return void this.listeners.onDoubleClick(event, currentNode);
989
+ });
990
+ /** wheel click */
991
+ this.area.addEventListener("mousedown", (event) => {
992
+ if (this.isDragging || !this.listeners.onWheelClick || event.button !== 1)
993
+ return;
994
+ const currentNode = nodeByPointerGetter({
995
+ graphSettings: this.graphSettings,
996
+ areaRect: this.areaRect,
997
+ areaTransform: this.areaTransform,
998
+ mouseEvent: event,
999
+ nodes: this.nodes,
1000
+ });
1001
+ return void this.listeners.onWheelClick(event, currentNode);
1002
+ }, {
1003
+ signal: this.eventAbortController.signal,
1004
+ });
1005
+ /** click */
1006
+ this.area.addEventListener("click", (event) => {
1007
+ if (this.isDragging || !this.listeners.onClick || event.button !== 0)
1008
+ return;
1009
+ const currentNode = nodeByPointerGetter({
1010
+ graphSettings: this.graphSettings,
1011
+ areaRect: this.areaRect,
1012
+ areaTransform: this.areaTransform,
1013
+ mouseEvent: event,
1014
+ nodes: this.nodes,
1015
+ });
1016
+ return void this.listeners.onClick(event, currentNode);
1017
+ }, {
1018
+ signal: this.eventAbortController.signal,
1019
+ });
1020
+ /** right click */
1021
+ this.area.addEventListener("contextmenu", (event) => {
1022
+ if (!this.listeners.onContextMenu)
1023
+ return;
1024
+ const currentNode = nodeByPointerGetter({
1025
+ graphSettings: this.graphSettings,
1026
+ areaRect: this.areaRect,
1027
+ areaTransform: this.areaTransform,
1028
+ mouseEvent: event,
1029
+ nodes: this.nodes,
1030
+ });
1031
+ return void this.listeners.onContextMenu(event, currentNode);
1032
+ }, {
1033
+ signal: this.eventAbortController.signal,
1034
+ });
1035
+ }
1036
+ initDnd() {
1037
+ if (!this.area || !this.nodes || !this.simulation)
1038
+ throw new Error("bad init data");
1039
+ d3Selection.select(this.area).call(d3Drag.drag()
1040
+ .subject((event) => {
1041
+ if (this.listeners.onDragSubject) {
1042
+ return this.listeners.onDragSubject(event, this.state);
1043
+ }
1044
+ if (!this.areaRect)
1045
+ return;
1046
+ const mouseEvent = event.sourceEvent;
1047
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1048
+ let index = 0;
1049
+ return d3Array.greatest(this.nodes, (node) => {
1050
+ if (!node.x || !node.y || (jsHelpers.isBoolean(node.drag) && !node.drag))
1051
+ return undefined;
1052
+ let radius = node._radius;
1053
+ if (!radius) {
1054
+ const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
1055
+ radius = nodeRadiusGetter({
1056
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
1057
+ radiusInitial: nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial,
1058
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
1059
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
1060
+ linkCount: node.linkCount,
1061
+ });
1062
+ }
1063
+ index++;
1064
+ return this.graphSettings.dragPlaceCoefficient(node, pointerX, pointerY, radius);
1065
+ });
1066
+ })
1067
+ .on("start", (event) => {
1068
+ this.listeners.onStartDragFinished?.(event, this.state);
1069
+ })
1070
+ .on("drag", (event) => {
1071
+ if (!this.isDragging) {
1072
+ this.isDragging = true;
1073
+ if (this.simulation)
1074
+ this.simulation.alphaTarget(0.3).restart();
1075
+ }
1076
+ if (!this.areaRect)
1077
+ return;
1078
+ const mouseEvent = event.sourceEvent;
1079
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1080
+ event.subject.fx = pointerX;
1081
+ event.subject.fy = pointerY;
1082
+ this.listeners.onMoveDragFinished?.(event, this.state);
1083
+ })
1084
+ .on("end", (event) => {
1085
+ this.isDragging = false;
1086
+ if (!event.active && this.simulation)
1087
+ this.simulation.alphaTarget(0);
1088
+ if (this.graphSettings.stickAfterDrag && this.areaRect) {
1089
+ if (!this.areaRect)
1090
+ return;
1091
+ const mouseEvent = event.sourceEvent;
1092
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1093
+ event.subject.fx = pointerX;
1094
+ event.subject.fy = pointerY;
1095
+ }
1096
+ else {
1097
+ event.subject.fx = null;
1098
+ event.subject.fy = null;
1099
+ }
1100
+ this.listeners.onEndDragFinished?.(event, this.state);
1101
+ }));
1102
+ }
1103
+ initZoom() {
1104
+ if (!this.area)
1105
+ throw new Error("bad init data");
1106
+ const zoomInstance = d3Zoom.zoom()
1107
+ .scaleExtent(this.graphSettings.zoomExtent)
1108
+ .on("zoom", (event) => {
1109
+ this.listeners.onZoom?.(event);
1110
+ this.areaTransform = event.transform;
1111
+ if (!this.simulationWorking)
1112
+ requestAnimationFrame(() => this.draw());
1113
+ });
1114
+ if (this.graphSettings.translateExtentEnable) {
1115
+ const coefficient = this.graphSettings.translateExtentCoefficient;
1116
+ const [coefficientX, coefficientY] = jsHelpers.isArray(coefficient)
1117
+ ? coefficient
1118
+ : [coefficient, coefficient];
1119
+ const [[minX = -this.width * coefficientX, minY = -this.height * coefficientX], [maxX = this.width * coefficientY, maxY = this.height * coefficientY],] = this.graphSettings.translateExtent;
1120
+ zoomInstance.translateExtent([
1121
+ [minX, minY],
1122
+ [maxX, maxY],
1123
+ ]);
1124
+ }
1125
+ d3Selection.select(this.area).call(zoomInstance).on("dblclick.zoom", null);
1126
+ const zoomInitial = this.graphSettings.zoomInitial;
1127
+ this.areaTransform = new d3Zoom.ZoomTransform(zoomInitial?.k ?? 1, zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
1128
+ d3Zoom.zoom().transform(d3Selection.select(this.area), this.areaTransform);
1129
+ }
1130
+ }
1131
+
1132
+ exports.GraphCanvas = GraphCanvas;
1133
+ exports.calculateLinkPositionByRadius = calculateLinkPositionByRadius;
1134
+ exports.colorToRgb = colorToRgb;
1135
+ exports.drawText = drawText;
1136
+ exports.extractRgb = extractRgb;
1137
+ exports.fadeRgb = fadeRgb;
1138
+ exports.linkIterationExtractor = linkIterationExtractor;
1139
+ exports.linkOptionsGetter = linkOptionsGetter;
1140
+ exports.nodeIterationExtractor = nodeIterationExtractor;
1141
+ exports.nodeOptionsGetter = nodeOptionsGetter;
1142
+ exports.nodeRadiusGetter = nodeRadiusGetter;
1143
+ exports.rgbAnimationByProgress = rgbAnimationByProgress;
1144
+ //# sourceMappingURL=index.cjs.map