@krainovsd/graph 0.4.5 → 0.5.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 +1130 -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 +720 -0
  18. package/lib/esm/module/GraphCanvas/GraphCanvas.js.map +1 -0
  19. package/lib/esm/module/GraphCanvas/constants/settings.js +91 -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 +124 -23
  48. package/package.json +20 -28
@@ -0,0 +1,1130 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ const d3Array = require('d3-array');
6
+ const d3Drag = require('d3-drag');
7
+ const d3Force = require('d3-force');
8
+ const d3Selection = require('d3-selection');
9
+ const d3Zoom = require('d3-zoom');
10
+ const jsHelpers = require('@krainovsd/js-helpers');
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
+ highlightSizingAdditional: 0.5,
113
+ highlightColorFadingMin: 0.15,
114
+ highlightTextShiftXAdditional: 0,
115
+ highlightTextShiftYAdditional: 2,
116
+ highlightTextSizingAdditional: 1,
117
+ highlightTextWeightAdditional: 0,
118
+ highlightTextWidthAdditional: 10,
119
+ highlightOnlyRoot: true,
120
+ stickAfterDrag: false,
121
+ highlightByHover: false,
122
+ highlightFadingMin: 0.21,
123
+ highlightTextFadingMin: 0.21,
124
+ highlightDownStep: 0.1,
125
+ highlightUpStep: 0.1,
126
+ dragPlaceCoefficient: dragPlaceCoefficientGetter,
127
+ nodeRadiusInitial: 4,
128
+ nodeRadiusCoefficient: 5,
129
+ nodeRadiusFactor: 1,
130
+ nodeRadiusFlexible: true,
131
+ zoomInitial: null,
132
+ };
133
+ const NODE_SETTINGS = {
134
+ alpha: 1,
135
+ textAlpha: 1,
136
+ borderColor: "#000000FF",
137
+ borderWidth: 0.1,
138
+ textWidth: 20,
139
+ textShiftX: 0,
140
+ textFont: "Arial",
141
+ textAlign: "center",
142
+ textColor: "#333",
143
+ width: 1,
144
+ radius: 4,
145
+ textStyle: "normal",
146
+ textWeight: 500,
147
+ textGap: 1,
148
+ highlightFading: false,
149
+ highlightColor: true,
150
+ highlightSizing: true,
151
+ highlightTextFading: true,
152
+ highlightTextSizing: true,
153
+ };
154
+ const LINK_SETTINGS = {
155
+ alpha: 1,
156
+ highlightFading: true,
157
+ pretty: true,
158
+ };
159
+ const COMMON_SETTINGS = {
160
+ linkColorZoomFar: "#999",
161
+ linkColorZoomNear: "#000000FF",
162
+ linkWidthZoomFar: 1,
163
+ linkWidthZoomNear: 0.1,
164
+ linkWidthZoomBorder: 1,
165
+ linkColorZoomBorder: 1,
166
+ nodeTextScaleMin: 1.5,
167
+ nodeTextScaleMax: 20,
168
+ nodeTextSizeMin: 1.5,
169
+ nodeTextSizeMax: 3.5,
170
+ nodeTextShiftYMin: 2.5,
171
+ nodeTextShiftYMax: 4,
172
+ nodeTextChangeStepCount: 200,
173
+ };
174
+
175
+ function forceSettingsGetter(settings, prevSettings) {
176
+ return {
177
+ ...(prevSettings ?? FORCE_SETTINGS),
178
+ ...settings,
179
+ };
180
+ }
181
+
182
+ function graphSettingsGetter(settings, prevSettings) {
183
+ return {
184
+ ...(prevSettings ?? GRAPH_SETTINGS),
185
+ ...settings,
186
+ };
187
+ }
188
+
189
+ function linkSettingsGetter(settings) {
190
+ return { options: settings?.options };
191
+ }
192
+ function linkOptionsGetter(_, __, ___, state) {
193
+ return {
194
+ ...LINK_SETTINGS,
195
+ color: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkColorZoomBorder
196
+ ? COMMON_SETTINGS.linkColorZoomNear
197
+ : COMMON_SETTINGS.linkColorZoomFar,
198
+ width: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkWidthZoomBorder
199
+ ? COMMON_SETTINGS.linkWidthZoomNear
200
+ : COMMON_SETTINGS.linkWidthZoomFar,
201
+ };
202
+ }
203
+
204
+ function listenersGetter(settings) {
205
+ return settings ?? {};
206
+ }
207
+
208
+ function nodeSettingsGetter(settings) {
209
+ return {
210
+ idGetter: settings?.idGetter ?? nodeIdGetter,
211
+ options: settings?.options,
212
+ };
213
+ }
214
+ const color = colorGetter();
215
+ function nodeOptionsGetter(node, _, __, state) {
216
+ const { textShiftY, textSize } = nodeTextSizeGetter(state?.areaTransform);
217
+ return {
218
+ ...NODE_SETTINGS,
219
+ color: color(String(node.group ?? "_DEFAULT")),
220
+ textVisible: Boolean(state?.areaTransform && state.areaTransform.k > COMMON_SETTINGS.nodeTextScaleMin),
221
+ text: node.name ?? node.id.toString(),
222
+ textShiftY,
223
+ textSize,
224
+ };
225
+ }
226
+ function nodeTextSizeGetter(transform) {
227
+ let textSize = COMMON_SETTINGS.nodeTextSizeMax;
228
+ let textShiftY = COMMON_SETTINGS.nodeTextShiftYMax;
229
+ if (transform) {
230
+ const scaleStepCoefficient = (COMMON_SETTINGS.nodeTextScaleMax - COMMON_SETTINGS.nodeTextScaleMin) /
231
+ COMMON_SETTINGS.nodeTextChangeStepCount;
232
+ const textStepCoefficient = (COMMON_SETTINGS.nodeTextSizeMax - COMMON_SETTINGS.nodeTextSizeMin) /
233
+ COMMON_SETTINGS.nodeTextChangeStepCount;
234
+ const shiftStepCoefficient = (COMMON_SETTINGS.nodeTextShiftYMax - COMMON_SETTINGS.nodeTextShiftYMin) /
235
+ COMMON_SETTINGS.nodeTextChangeStepCount;
236
+ if (transform.k >= COMMON_SETTINGS.nodeTextScaleMax) {
237
+ textSize = COMMON_SETTINGS.nodeTextSizeMin;
238
+ textShiftY = COMMON_SETTINGS.nodeTextShiftYMin;
239
+ }
240
+ else if (transform.k > COMMON_SETTINGS.nodeTextScaleMin) {
241
+ const transformSteps = (transform.k - COMMON_SETTINGS.nodeTextScaleMin) / scaleStepCoefficient;
242
+ textSize -= transformSteps * textStepCoefficient;
243
+ textShiftY -= transformSteps * shiftStepCoefficient;
244
+ }
245
+ }
246
+ return { textSize, textShiftY };
247
+ }
248
+ function nodeRadiusGetter({ radiusFlexible, radiusInitial, linkCount, radiusCoefficient, radiusFactor, }) {
249
+ return ((radiusFlexible && linkCount ? linkCount / radiusCoefficient : 0) * radiusFactor + radiusInitial);
250
+ }
251
+ function nodeIdGetter(node) {
252
+ return node.id;
253
+ }
254
+
255
+ function pointerGetter(mouseEvent, areaRect, areaTransform) {
256
+ const px = (mouseEvent.clientX - areaRect.left - areaTransform.x) / areaTransform.k;
257
+ const py = (mouseEvent.clientY - areaRect.top - areaTransform.y) / areaTransform.k;
258
+ return [px, py];
259
+ }
260
+
261
+ function isOverlapsNode(node, radius, pointerX, pointerY) {
262
+ if (node.x == undefined || node.y == undefined)
263
+ return false;
264
+ const isOverX = node.x - radius <= pointerX && pointerX <= node.x + radius;
265
+ const isOverY = node.y - radius <= pointerY && pointerY <= node.y + radius;
266
+ return isOverX && isOverY;
267
+ }
268
+
269
+ function dragPlaceCoefficientGetter(node, pointerX, pointerY, radius) {
270
+ if (!node.x || !node.y)
271
+ return undefined;
272
+ if (isOverlapsNode(node, radius, pointerX, pointerY))
273
+ return node.index;
274
+ }
275
+
276
+ function linkIterationExtractor(link, i, links, state, option, optionConstantGetter) {
277
+ let customOptions;
278
+ let constantOptions;
279
+ if (typeof option === "function")
280
+ customOptions = option(link, i, links, state);
281
+ else
282
+ customOptions = option;
283
+ if (customOptions && typeof customOptions === "object" && !Array.isArray(customOptions)) {
284
+ for (const key in customOptions) {
285
+ if (customOptions[key] === undefined)
286
+ delete customOptions[key];
287
+ }
288
+ }
289
+ if (optionConstantGetter) {
290
+ if (typeof optionConstantGetter === "function")
291
+ constantOptions = optionConstantGetter(link, i, links, state);
292
+ else
293
+ constantOptions = optionConstantGetter;
294
+ if (constantOptions &&
295
+ typeof constantOptions === "object" &&
296
+ !Array.isArray(constantOptions) &&
297
+ checkType(customOptions, customOptions === undefined ||
298
+ (typeof customOptions === "object" && !Array.isArray(customOptions)))) {
299
+ return {
300
+ ...constantOptions,
301
+ ...customOptions,
302
+ };
303
+ }
304
+ }
305
+ return customOptions;
306
+ }
307
+
308
+ function nodeByPointerGetter({ areaRect, areaTransform, mouseEvent, nodes, graphSettings, }) {
309
+ if (!areaRect)
310
+ return undefined;
311
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
312
+ return d3Array.greatest(nodes, (node) => {
313
+ const radius = node.radius ??
314
+ nodeRadiusGetter({
315
+ radiusFlexible: graphSettings.nodeRadiusFlexible,
316
+ radiusInitial: graphSettings.nodeRadiusInitial,
317
+ radiusCoefficient: graphSettings.nodeRadiusCoefficient,
318
+ radiusFactor: graphSettings.nodeRadiusFactor,
319
+ linkCount: node.linkCount,
320
+ });
321
+ if (isOverlapsNode(node, radius, pointerX, pointerY))
322
+ return node.index;
323
+ });
324
+ }
325
+
326
+ function nodeIterationExtractor(node, i, nodes, state, option, optionConstantGetter) {
327
+ let customOptions;
328
+ let constantOptions;
329
+ if (typeof option === "function")
330
+ customOptions = option(node, i, nodes, state);
331
+ else
332
+ customOptions = option;
333
+ if (customOptions && typeof customOptions === "object" && !Array.isArray(customOptions)) {
334
+ for (const key in customOptions) {
335
+ if (customOptions[key] === undefined)
336
+ delete customOptions[key];
337
+ }
338
+ }
339
+ if (optionConstantGetter) {
340
+ if (typeof optionConstantGetter === "function")
341
+ constantOptions = optionConstantGetter(node, i, nodes, state);
342
+ else
343
+ constantOptions = optionConstantGetter;
344
+ if (constantOptions &&
345
+ typeof constantOptions === "object" &&
346
+ !Array.isArray(constantOptions) &&
347
+ checkType(customOptions, customOptions === undefined ||
348
+ (typeof customOptions === "object" && !Array.isArray(customOptions)))) {
349
+ return {
350
+ ...constantOptions,
351
+ ...customOptions,
352
+ };
353
+ }
354
+ }
355
+ return customOptions;
356
+ }
357
+
358
+ const SPACE = " ";
359
+ function drawText({ context, id, textAlign, textColor, textFont, textStyle, textGap, textWeight, textSize, text, x, y, cachedNodeText, maxWidth, }) {
360
+ context.font = `${textStyle} normal ${textWeight} ${textSize}px ${textFont}`;
361
+ context.fillStyle = textColor;
362
+ context.textAlign = textAlign;
363
+ if (cachedNodeText[id] != undefined) {
364
+ cachedNodeText[id].forEach((line, index) => {
365
+ context.fillText(line, x, y + index * textSize + index * textGap);
366
+ });
367
+ return;
368
+ }
369
+ if (maxWidth == undefined || context.measureText(text).width <= maxWidth) {
370
+ cachedNodeText[id] = [text];
371
+ context.fillText(text, x, y);
372
+ return;
373
+ }
374
+ const spaceWidth = context.measureText(" ").width;
375
+ const lines = [];
376
+ let lineWidth = 0;
377
+ let line = "";
378
+ for (const word of text.split(" ")) {
379
+ const size = context.measureText(word).width;
380
+ lineWidth += size;
381
+ if (line === "") {
382
+ line = word;
383
+ continue;
384
+ }
385
+ if (lineWidth > maxWidth) {
386
+ lineWidth = 0;
387
+ lines.push(line);
388
+ line = word;
389
+ }
390
+ else {
391
+ lineWidth += spaceWidth;
392
+ line += `${SPACE}${word}`;
393
+ }
394
+ }
395
+ if (line !== "")
396
+ lines.push(line);
397
+ cachedNodeText[id] = lines;
398
+ lines.forEach((line, index) => {
399
+ context.fillText(line, x, y + index * textSize + index * textGap);
400
+ });
401
+ }
402
+
403
+ function calculateLinkPositionByRadius(link) {
404
+ const source = link.source;
405
+ const target = link.target;
406
+ if (typeof source != "object" || typeof target != "object")
407
+ return null;
408
+ const dx = (target.x ?? 0) - (source.x ?? 0);
409
+ const dy = (target.y ?? 0) - (source.y ?? 0);
410
+ const dr = Math.sqrt(dx * dx + dy * dy);
411
+ const sourceRadius = source.radius ?? 0;
412
+ const targetRadius = target.radius ?? 0;
413
+ return {
414
+ x1: (source.x ?? 0) + (dx * sourceRadius) / dr,
415
+ y1: (source.y ?? 0) + (dy * sourceRadius) / dr,
416
+ x2: (target.x ?? 0) - (dx * targetRadius) / dr,
417
+ y2: (target.y ?? 0) - (dy * targetRadius) / dr,
418
+ };
419
+ }
420
+
421
+ class GraphCanvas {
422
+ /** initial data */
423
+ nodes;
424
+ links;
425
+ width;
426
+ height;
427
+ root;
428
+ container;
429
+ area;
430
+ /** settings */
431
+ graphSettings;
432
+ forceSettings;
433
+ nodeSettings;
434
+ linkSettings;
435
+ listeners;
436
+ /** service */
437
+ context;
438
+ simulation;
439
+ areaTransform = d3Zoom.zoomIdentity;
440
+ areaRect;
441
+ draw;
442
+ eventAbortController;
443
+ cachedNodeText = {};
444
+ isDragging = false;
445
+ highlightedNode = null;
446
+ highlightedNeighbors = null;
447
+ highlightProgress = 1;
448
+ highlightWorking = false;
449
+ highlightDrawing = false;
450
+ get simulationWorking() {
451
+ const simulationAlpha = this.simulation?.alpha?.() ?? 0;
452
+ const simulationAlphaMin = this.simulation?.alphaMin?.() ?? 0;
453
+ const simulationAlphaDecay = this.simulation?.alphaDecay?.() ?? 0;
454
+ const force = (simulationAlpha - simulationAlphaMin) / simulationAlphaDecay;
455
+ return force > 0;
456
+ }
457
+ get state() {
458
+ return {
459
+ areaTransform: this.areaTransform,
460
+ cachedNodeText: this.cachedNodeText,
461
+ context: this.context,
462
+ eventAbortController: this.eventAbortController,
463
+ highlightProgress: this.highlightProgress,
464
+ highlightDrawing: this.highlightDrawing,
465
+ highlightedNeighbors: this.highlightedNeighbors,
466
+ highlightedNode: this.highlightedNode,
467
+ highlightWorking: this.highlightWorking,
468
+ isDragging: this.isDragging,
469
+ simulation: this.simulation,
470
+ simulationWorking: this.simulationWorking,
471
+ height: this.height,
472
+ links: this.links,
473
+ nodes: this.nodes,
474
+ width: this.width,
475
+ forceSettings: this.forceSettings,
476
+ graphSettings: this.graphSettings,
477
+ linkSettings: this.linkSettings,
478
+ nodeSettings: this.nodeSettings,
479
+ };
480
+ }
481
+ constructor({ links, nodes, root, forceSettings, linkSettings, listeners, nodeSettings, graphSettings, }) {
482
+ // root.style.position = "relative";
483
+ root.style.overflow = "hidden";
484
+ this.root = root;
485
+ this.forceSettings = forceSettingsGetter(forceSettings);
486
+ this.linkSettings = linkSettingsGetter(linkSettings);
487
+ this.nodeSettings = nodeSettingsGetter(nodeSettings);
488
+ this.listeners = listenersGetter(listeners);
489
+ this.graphSettings = graphSettingsGetter(graphSettings);
490
+ this.eventAbortController = new AbortController();
491
+ this.nodes = nodes;
492
+ this.links = links;
493
+ this.height = 0;
494
+ this.width = 0;
495
+ this.draw = this.initDraw();
496
+ this.init();
497
+ }
498
+ get dpi() {
499
+ return devicePixelRatio;
500
+ }
501
+ getData() {
502
+ return {
503
+ links: this.links,
504
+ nodes: this.nodes,
505
+ };
506
+ }
507
+ changeData(options, alpha) {
508
+ if (options.links != undefined)
509
+ this.links = options.links;
510
+ if (options.nodes != undefined)
511
+ this.nodes = options.nodes;
512
+ if (options.nodes != undefined || options.links != undefined)
513
+ this.updateData(alpha);
514
+ }
515
+ changeSettings(options) {
516
+ if (options.graphSettings)
517
+ this.graphSettings = graphSettingsGetter(options.graphSettings, this.graphSettings);
518
+ if (options.forceSettings)
519
+ this.forceSettings = forceSettingsGetter(options.forceSettings, this.forceSettings);
520
+ if (options.linkSettings)
521
+ this.linkSettings = linkSettingsGetter(options.linkSettings);
522
+ if (options.nodeSettings)
523
+ this.nodeSettings = nodeSettingsGetter(options.nodeSettings);
524
+ if (options.forceSettings)
525
+ return void this.updateSimulation();
526
+ this.tick();
527
+ }
528
+ tick() {
529
+ if (!this.simulationWorking)
530
+ this.draw();
531
+ }
532
+ restart(alpha) {
533
+ if (this.simulation)
534
+ this.simulation.alpha(alpha ?? 1).restart();
535
+ }
536
+ start() {
537
+ if (this.simulation)
538
+ this.simulation.alpha(1).restart();
539
+ if (this.container)
540
+ this.container.style.display = "block";
541
+ }
542
+ stop() {
543
+ if (this.simulation)
544
+ this.simulation.stop();
545
+ if (this.container)
546
+ this.container.style.display = "none";
547
+ }
548
+ create() {
549
+ this.init();
550
+ }
551
+ destroy() {
552
+ if (this.simulation) {
553
+ this.simulation.stop();
554
+ this.simulation = undefined;
555
+ }
556
+ this.clearHTMLElements();
557
+ this.clearState();
558
+ this.clearDataDependencies();
559
+ }
560
+ clearHTMLElements() {
561
+ this.root.replaceChildren();
562
+ this.area = undefined;
563
+ this.context = undefined;
564
+ this.container = undefined;
565
+ this.eventAbortController.abort();
566
+ this.eventAbortController = new AbortController();
567
+ }
568
+ updateSimulation() {
569
+ if (this.simulation) {
570
+ this.initSimulationForces();
571
+ this.simulation.alpha(1);
572
+ this.simulation.restart();
573
+ }
574
+ }
575
+ clearState() {
576
+ this.isDragging = false;
577
+ this.highlightedNode = null;
578
+ this.highlightedNeighbors = null;
579
+ this.highlightProgress = 0;
580
+ this.highlightWorking = false;
581
+ this.highlightDrawing = false;
582
+ }
583
+ clearDataDependencies() {
584
+ this.cachedNodeText = {};
585
+ }
586
+ updateData(alpha) {
587
+ this.clearDataDependencies();
588
+ if (this.simulation) {
589
+ this.initCollideForce();
590
+ this.simulation
591
+ .nodes(this.nodes)
592
+ .force("link", d3Force.forceLink(this.links)
593
+ .id(this.nodeSettings.idGetter)
594
+ .distance(this.forceSettings.linkDistance)
595
+ .strength(this.forceSettings.linkStrength)
596
+ .iterations(this.forceSettings.linkIterations))
597
+ .alpha(alpha ?? 0.5)
598
+ .restart();
599
+ }
600
+ }
601
+ updateSize() {
602
+ this.clearHTMLElements();
603
+ this.initArea();
604
+ this.initDnd();
605
+ this.initZoom();
606
+ this.initResize();
607
+ this.initPointer();
608
+ if (!this.simulationWorking)
609
+ this.draw();
610
+ }
611
+ init() {
612
+ this.initArea();
613
+ this.initSimulation();
614
+ this.initDnd();
615
+ this.initZoom();
616
+ this.initResize();
617
+ this.initPointer();
618
+ }
619
+ initSimulation() {
620
+ if (!this.simulation) {
621
+ this.simulation = d3Force.forceSimulation()
622
+ .nodes(this.nodes)
623
+ .force("link", d3Force.forceLink(this.links).id(this.nodeSettings.idGetter))
624
+ .on("tick", () => {
625
+ this.draw();
626
+ })
627
+ .on("end", () => {
628
+ this.listeners.onSimulationEnd?.(this.simulation);
629
+ });
630
+ this.initSimulationForces();
631
+ }
632
+ }
633
+ initSimulationForces() {
634
+ if (!this.simulation)
635
+ return;
636
+ const linkForce = this.simulation.force("link");
637
+ if (!linkForce)
638
+ return;
639
+ linkForce
640
+ .distance(this.forceSettings.linkDistance)
641
+ .strength(this.forceSettings.linkStrength)
642
+ .iterations(this.forceSettings.linkIterations);
643
+ this.simulation
644
+ .force("x", d3Force.forceX(this.forceSettings.xForce).strength(this.forceSettings.xStrength))
645
+ .force("y", d3Force.forceY(this.forceSettings.yForce).strength(this.forceSettings.yStrength))
646
+ .force("charge", d3Force.forceManyBody()
647
+ .strength(this.forceSettings.chargeStrength)
648
+ .distanceMax(this.forceSettings.chargeDistanceMax)
649
+ .distanceMin(this.forceSettings.chargeDistanceMin))
650
+ .force("center", d3Force.forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
651
+ this.initCollideForce();
652
+ }
653
+ initCollideForce() {
654
+ if (!this.simulation)
655
+ return;
656
+ if (!this.forceSettings.collideOn) {
657
+ if (this.simulation.force("collide"))
658
+ this.simulation.force("collide", null);
659
+ return;
660
+ }
661
+ const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
662
+ const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
663
+ const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
664
+ if (isMaxCollideNodes && isMaxCollideLinks)
665
+ this.simulation.force("collide", null);
666
+ else if (!this.simulation.force("collide"))
667
+ this.simulation.force("collide", d3Force.forceCollide()
668
+ .radius((node, index) => {
669
+ if (this.forceSettings.collideRadius) {
670
+ return nodeIterationExtractor(node, index, this.nodes, this.state, this.forceSettings.collideRadius, undefined);
671
+ }
672
+ const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
673
+ const radius = nodeRadiusGetter({
674
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
675
+ radiusInitial: nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial,
676
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
677
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
678
+ linkCount: node.linkCount,
679
+ });
680
+ return radius + this.forceSettings.collideAdditionalRadius;
681
+ })
682
+ .strength(this.forceSettings.collideStrength)
683
+ .iterations(this.forceSettings.collideIterations));
684
+ }
685
+ initArea() {
686
+ if (!this.area || !this.context || !this.container) {
687
+ this.container = d3Selection.create("div")
688
+ .attr("style", "padding: 0 !important; width: 100%; height: 100%;")
689
+ .node();
690
+ if (!this.container)
691
+ throw new Error("couldn't create container");
692
+ this.root.appendChild(this.container);
693
+ const { width, height } = this.root.getBoundingClientRect();
694
+ this.width = width;
695
+ this.height = height;
696
+ this.area = d3Selection.create("canvas")
697
+ .attr("width", this.dpi * this.width)
698
+ .attr("height", this.dpi * this.height)
699
+ .attr("style", `width: 100%; height: 100%; border: none !important;`)
700
+ .node();
701
+ if (!this.area)
702
+ throw new Error("couldn't create canvas");
703
+ this.container.appendChild(this.area);
704
+ this.areaRect = this.area.getBoundingClientRect();
705
+ this.context = this.area.getContext("2d");
706
+ if (!this.context)
707
+ throw new Error("couldn't create canvas context");
708
+ this.context.scale(this.dpi, this.dpi);
709
+ }
710
+ }
711
+ initDraw() {
712
+ function calculateHighlightFading() {
713
+ this.highlightDrawing = true;
714
+ if (!this.highlightWorking && this.highlightProgress > 0) {
715
+ this.highlightProgress -= this.graphSettings.highlightDownStep;
716
+ if (!this.simulationWorking)
717
+ return void requestAnimationFrame(() => this.draw());
718
+ return;
719
+ }
720
+ if (this.highlightWorking && this.highlightProgress < 1) {
721
+ this.highlightProgress += this.graphSettings.highlightUpStep;
722
+ if (!this.simulationWorking)
723
+ return void requestAnimationFrame(() => this.draw());
724
+ return;
725
+ }
726
+ if (!this.highlightWorking && this.highlightProgress <= 0) {
727
+ if (this.highlightedNeighbors)
728
+ this.highlightedNeighbors = null;
729
+ if (this.highlightedNode)
730
+ this.highlightedNode = null;
731
+ }
732
+ this.highlightDrawing = false;
733
+ }
734
+ function draw() {
735
+ if (!this.context)
736
+ return;
737
+ if (this.listeners.onDraw) {
738
+ this.listeners.onDraw(this.state, (status) => {
739
+ this.highlightDrawing = status;
740
+ }, () => {
741
+ if (this.highlightedNeighbors)
742
+ this.highlightedNeighbors = null;
743
+ if (this.highlightedNode)
744
+ this.highlightedNode = null;
745
+ });
746
+ return;
747
+ }
748
+ this.context.save();
749
+ this.context.clearRect(0, 0, this.width, this.height);
750
+ this.context.translate(this.areaTransform.x, this.areaTransform.y);
751
+ this.context.scale(this.areaTransform.k, this.areaTransform.k);
752
+ /** links */
753
+ this.context.beginPath();
754
+ this.links.forEach(drawLink.bind(this));
755
+ this.context.stroke();
756
+ /** nodes */
757
+ const textRenders = [];
758
+ this.nodes.forEach(drawNode(textRenders).bind(this));
759
+ textRenders.forEach((render) => render());
760
+ this.context.restore();
761
+ this.listeners.onDrawFinished?.(this.context, this.areaTransform);
762
+ calculateHighlightFading.bind(this)();
763
+ }
764
+ function drawLink(link, index) {
765
+ if (!this.context ||
766
+ typeof link.source !== "object" ||
767
+ typeof link.target !== "object" ||
768
+ !link.source.x ||
769
+ !link.source.y ||
770
+ !link.target.x ||
771
+ !link.target.y)
772
+ return;
773
+ const linkOptions = linkIterationExtractor(link, index, this.links, this.state, this.linkSettings.options ?? {}, linkOptionsGetter);
774
+ let alpha = linkOptions.alpha;
775
+ if (this.highlightedNeighbors && this.highlightedNode) {
776
+ /** Not highlighted */
777
+ if (this.highlightedNode.id != link.source.id &&
778
+ this.highlightedNode.id != link.target.id) {
779
+ if (linkOptions.highlightFading)
780
+ alpha =
781
+ this.graphSettings.highlightFadingMin +
782
+ (alpha - this.graphSettings.highlightFadingMin) * (1 - this.highlightProgress);
783
+ }
784
+ this.context.beginPath();
785
+ }
786
+ this.context.globalAlpha = alpha;
787
+ this.context.strokeStyle = linkOptions.color;
788
+ this.context.lineWidth = linkOptions.width;
789
+ if (linkOptions.pretty) {
790
+ const { x1, x2, y1, y2 } = calculateLinkPositionByRadius(link) ?? {
791
+ x1: 0,
792
+ x2: 0,
793
+ y1: 0,
794
+ y2: 0,
795
+ };
796
+ this.context.moveTo(x1, y1);
797
+ this.context.lineTo(x2, y2);
798
+ }
799
+ else {
800
+ this.context.moveTo(link.source.x, link.source.y);
801
+ this.context.lineTo(link.target.x, link.target.y);
802
+ }
803
+ if (this.highlightedNeighbors && this.highlightedNode)
804
+ this.context.stroke();
805
+ }
806
+ const drawNode = (textRenders) => function drawNode(node, index) {
807
+ if (!this.context || !node.x || !node.y)
808
+ return;
809
+ const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
810
+ let alpha = nodeOptions.alpha;
811
+ let color = nodeOptions.color;
812
+ let radiusInitial = nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial;
813
+ let textAlpha = nodeOptions.textAlpha;
814
+ let textSize = nodeOptions.textSize;
815
+ let textShiftX = nodeOptions.textShiftX;
816
+ let textShiftY = nodeOptions.textShiftY;
817
+ let textWeight = nodeOptions.textWeight;
818
+ let textWidth = nodeOptions.textWidth;
819
+ if (this.highlightedNeighbors && this.highlightedNode) {
820
+ /** Not highlighted */
821
+ if (!this.highlightedNeighbors.has(node.id) && this.highlightedNode.id != node.id) {
822
+ if (nodeOptions.highlightFading) {
823
+ alpha =
824
+ this.graphSettings.highlightFadingMin +
825
+ (alpha - this.graphSettings.highlightFadingMin) * (1 - this.highlightProgress);
826
+ }
827
+ if (nodeOptions.highlightTextFading) {
828
+ textAlpha =
829
+ this.graphSettings.highlightTextFadingMin +
830
+ (textAlpha - this.graphSettings.highlightTextFadingMin) *
831
+ (1 - this.highlightProgress);
832
+ }
833
+ if (nodeOptions.highlightColor) {
834
+ const colorRgb = extractRgb(colorToRgb(color));
835
+ if (colorRgb) {
836
+ const colorRgbFade = fadeRgb(colorRgb, this.graphSettings.highlightColorFadingMin);
837
+ const colorFadeAnimation = rgbAnimationByProgress(colorRgb, colorRgbFade, this.highlightProgress);
838
+ color = `rgb(${colorFadeAnimation.r}, ${colorFadeAnimation.g}, ${colorFadeAnimation.b})`;
839
+ }
840
+ }
841
+ }
842
+ else if (!this.graphSettings.highlightOnlyRoot ||
843
+ (this.graphSettings.highlightOnlyRoot && this.highlightedNode.id === node.id)) {
844
+ /** Highlighted */
845
+ if (nodeOptions.highlightSizing) {
846
+ const radiusMax = radiusInitial + this.graphSettings.highlightSizingAdditional;
847
+ radiusInitial += ((radiusMax - radiusInitial) / 100) * (this.highlightProgress * 100);
848
+ }
849
+ if (nodeOptions.highlightTextSizing) {
850
+ const textSizeMax = textSize + this.graphSettings.highlightTextSizingAdditional;
851
+ const textShiftXMax = textShiftX + this.graphSettings.highlightTextShiftXAdditional;
852
+ const textShiftYMax = textShiftY + this.graphSettings.highlightTextShiftYAdditional;
853
+ const textWeightMax = textWeight + this.graphSettings.highlightTextWeightAdditional;
854
+ const textWidthMax = textWidth + this.graphSettings.highlightTextWidthAdditional;
855
+ textSize += ((textSizeMax - textSize) / 100) * (this.highlightProgress * 100);
856
+ textShiftX += ((textShiftXMax - textShiftX) / 100) * (this.highlightProgress * 100);
857
+ textShiftY += ((textShiftYMax - textShiftY) / 100) * (this.highlightProgress * 100);
858
+ textWeight += ((textWeightMax - textWeight) / 100) * (this.highlightProgress * 100);
859
+ textWidth += ((textWidthMax - textWidth) / 100) * (this.highlightProgress * 100);
860
+ }
861
+ }
862
+ }
863
+ const radius = nodeRadiusGetter({
864
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
865
+ radiusInitial,
866
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
867
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
868
+ linkCount: node.linkCount,
869
+ });
870
+ node.radius = radius;
871
+ this.context.beginPath();
872
+ this.context.globalAlpha = alpha;
873
+ /** text */
874
+ if (nodeOptions.textVisible && nodeOptions.text) {
875
+ textRenders.push(() => {
876
+ if (!this.context || !node.x || !node.y || !nodeOptions.text)
877
+ return;
878
+ this.context.beginPath();
879
+ this.context.globalAlpha = textAlpha;
880
+ drawText({
881
+ id: node.id,
882
+ cachedNodeText: this.cachedNodeText,
883
+ context: this.context,
884
+ text: nodeOptions.text,
885
+ textAlign: nodeOptions.textAlign,
886
+ textColor: nodeOptions.textColor,
887
+ textFont: nodeOptions.textFont,
888
+ textSize,
889
+ x: node.x + textShiftX,
890
+ y: node.y + radius + textShiftY,
891
+ maxWidth: textWidth,
892
+ textStyle: nodeOptions.textStyle,
893
+ textWeight,
894
+ textGap: nodeOptions.textGap,
895
+ });
896
+ });
897
+ }
898
+ /** circle */
899
+ this.context.lineWidth = nodeOptions.borderWidth;
900
+ this.context.strokeStyle = nodeOptions.borderColor;
901
+ this.context.fillStyle = color;
902
+ this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
903
+ this.context.fill();
904
+ this.context.stroke();
905
+ };
906
+ return draw;
907
+ }
908
+ initResize() {
909
+ if (!this.area)
910
+ throw new Error("bad init data");
911
+ let initialResizeCall = true;
912
+ const abortController = this.eventAbortController;
913
+ const observer = new ResizeObserver(() => {
914
+ if (initialResizeCall) {
915
+ initialResizeCall = false;
916
+ return;
917
+ }
918
+ if (abortController.signal.aborted) {
919
+ observer.disconnect();
920
+ return;
921
+ }
922
+ requestAnimationFrame(() => {
923
+ this.updateSize();
924
+ });
925
+ });
926
+ observer.observe(this.area);
927
+ }
928
+ initPointer() {
929
+ if (!this.area || !this.nodes || !this.simulation)
930
+ throw new Error("bad init data");
931
+ /** hover */
932
+ this.area.addEventListener("pointermove", (event) => {
933
+ let currentNode;
934
+ if (this.graphSettings.highlightByHover && !this.isDragging) {
935
+ currentNode = nodeByPointerGetter({
936
+ graphSettings: this.graphSettings,
937
+ areaRect: this.areaRect,
938
+ areaTransform: this.areaTransform,
939
+ mouseEvent: event,
940
+ nodes: this.nodes,
941
+ });
942
+ if (currentNode?.neighbors && this.highlightedNode !== currentNode) {
943
+ this.highlightedNode = currentNode;
944
+ this.highlightedNeighbors = new Set(this.highlightedNode.neighbors);
945
+ this.highlightWorking = true;
946
+ if (!this.simulationWorking && !this.highlightDrawing)
947
+ requestAnimationFrame(() => {
948
+ this.draw();
949
+ });
950
+ }
951
+ else if (!currentNode && this.highlightedNode) {
952
+ this.highlightWorking = false;
953
+ if (!this.simulationWorking && !this.highlightDrawing)
954
+ requestAnimationFrame(() => {
955
+ this.draw();
956
+ });
957
+ }
958
+ }
959
+ if (!this.listeners.onMove)
960
+ return;
961
+ if (!currentNode)
962
+ currentNode = nodeByPointerGetter({
963
+ graphSettings: this.graphSettings,
964
+ areaRect: this.areaRect,
965
+ areaTransform: this.areaTransform,
966
+ mouseEvent: event,
967
+ nodes: this.nodes,
968
+ });
969
+ return void this.listeners.onMove(event, currentNode);
970
+ }, {
971
+ signal: this.eventAbortController.signal,
972
+ });
973
+ /** dblclick */
974
+ this.area.addEventListener("dblclick", (event) => {
975
+ if (!this.listeners.onDoubleClick)
976
+ return;
977
+ const currentNode = nodeByPointerGetter({
978
+ graphSettings: this.graphSettings,
979
+ areaRect: this.areaRect,
980
+ areaTransform: this.areaTransform,
981
+ mouseEvent: event,
982
+ nodes: this.nodes,
983
+ });
984
+ return void this.listeners.onDoubleClick(event, currentNode);
985
+ });
986
+ /** wheel click */
987
+ this.area.addEventListener("mousedown", (event) => {
988
+ if (this.isDragging || !this.listeners.onWheelClick || event.button !== 1)
989
+ return;
990
+ const currentNode = nodeByPointerGetter({
991
+ graphSettings: this.graphSettings,
992
+ areaRect: this.areaRect,
993
+ areaTransform: this.areaTransform,
994
+ mouseEvent: event,
995
+ nodes: this.nodes,
996
+ });
997
+ return void this.listeners.onWheelClick(event, currentNode);
998
+ }, {
999
+ signal: this.eventAbortController.signal,
1000
+ });
1001
+ /** click */
1002
+ this.area.addEventListener("click", (event) => {
1003
+ if (this.isDragging || !this.listeners.onClick || event.button !== 0)
1004
+ return;
1005
+ const currentNode = nodeByPointerGetter({
1006
+ graphSettings: this.graphSettings,
1007
+ areaRect: this.areaRect,
1008
+ areaTransform: this.areaTransform,
1009
+ mouseEvent: event,
1010
+ nodes: this.nodes,
1011
+ });
1012
+ return void this.listeners.onClick(event, currentNode);
1013
+ }, {
1014
+ signal: this.eventAbortController.signal,
1015
+ });
1016
+ /** right click */
1017
+ this.area.addEventListener("contextmenu", (event) => {
1018
+ if (!this.listeners.onContextMenu)
1019
+ return;
1020
+ const currentNode = nodeByPointerGetter({
1021
+ graphSettings: this.graphSettings,
1022
+ areaRect: this.areaRect,
1023
+ areaTransform: this.areaTransform,
1024
+ mouseEvent: event,
1025
+ nodes: this.nodes,
1026
+ });
1027
+ return void this.listeners.onContextMenu(event, currentNode);
1028
+ }, {
1029
+ signal: this.eventAbortController.signal,
1030
+ });
1031
+ }
1032
+ initDnd() {
1033
+ if (!this.area || !this.nodes || !this.simulation)
1034
+ throw new Error("bad init data");
1035
+ d3Selection.select(this.area).call(d3Drag.drag()
1036
+ .subject((event) => {
1037
+ if (this.listeners.onDragSubject) {
1038
+ return this.listeners.onDragSubject(event, this.areaTransform, this.nodes);
1039
+ }
1040
+ if (!this.areaRect)
1041
+ return;
1042
+ const mouseEvent = event.sourceEvent;
1043
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1044
+ let index = 0;
1045
+ return d3Array.greatest(this.nodes, (node) => {
1046
+ if (!node.x || !node.y)
1047
+ return undefined;
1048
+ let radius = node.radius;
1049
+ if (!radius) {
1050
+ const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
1051
+ radius = nodeRadiusGetter({
1052
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
1053
+ radiusInitial: nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial,
1054
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
1055
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
1056
+ linkCount: node.linkCount,
1057
+ });
1058
+ }
1059
+ index++;
1060
+ return this.graphSettings.dragPlaceCoefficient(node, pointerX, pointerY, radius);
1061
+ });
1062
+ })
1063
+ .on("start", (event) => {
1064
+ this.listeners.onStartDragFinished?.(event, this.simulation, this.areaTransform);
1065
+ })
1066
+ .on("drag", (event) => {
1067
+ if (!this.isDragging) {
1068
+ this.isDragging = true;
1069
+ if (this.simulation)
1070
+ this.simulation.alphaTarget(0.3).restart();
1071
+ }
1072
+ if (!this.areaRect)
1073
+ return;
1074
+ const mouseEvent = event.sourceEvent;
1075
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1076
+ event.subject.fx = pointerX;
1077
+ event.subject.fy = pointerY;
1078
+ this.listeners.onMoveDragFinished?.(event, this.simulation, this.areaTransform);
1079
+ })
1080
+ .on("end", (event) => {
1081
+ this.isDragging = false;
1082
+ if (!event.active && this.simulation)
1083
+ this.simulation.alphaTarget(0);
1084
+ if (this.graphSettings.stickAfterDrag && this.areaRect) {
1085
+ if (!this.areaRect)
1086
+ return;
1087
+ const mouseEvent = event.sourceEvent;
1088
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1089
+ event.subject.fx = pointerX;
1090
+ event.subject.fy = pointerY;
1091
+ }
1092
+ else {
1093
+ event.subject.fx = null;
1094
+ event.subject.fy = null;
1095
+ }
1096
+ this.listeners.onEndDragFinished?.(event, this.simulation, this.areaTransform);
1097
+ }));
1098
+ }
1099
+ initZoom() {
1100
+ if (!this.area)
1101
+ throw new Error("bad init data");
1102
+ d3Selection.select(this.area)
1103
+ .call(d3Zoom.zoom()
1104
+ .scaleExtent(this.graphSettings.zoomExtent)
1105
+ .on("zoom", (event) => {
1106
+ this.listeners.onZoom?.(event);
1107
+ this.areaTransform = event.transform;
1108
+ if (!this.simulationWorking)
1109
+ requestAnimationFrame(() => this.draw());
1110
+ }))
1111
+ .on("dblclick.zoom", null);
1112
+ const zoomInitial = this.graphSettings.zoomInitial;
1113
+ this.areaTransform = new d3Zoom.ZoomTransform(zoomInitial?.k ?? 1, zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
1114
+ d3Zoom.zoom().transform(d3Selection.select(this.area), this.areaTransform);
1115
+ }
1116
+ }
1117
+
1118
+ exports.GraphCanvas = GraphCanvas;
1119
+ exports.calculateLinkPositionByRadius = calculateLinkPositionByRadius;
1120
+ exports.colorToRgb = colorToRgb;
1121
+ exports.drawText = drawText;
1122
+ exports.extractRgb = extractRgb;
1123
+ exports.fadeRgb = fadeRgb;
1124
+ exports.linkIterationExtractor = linkIterationExtractor;
1125
+ exports.linkOptionsGetter = linkOptionsGetter;
1126
+ exports.nodeIterationExtractor = nodeIterationExtractor;
1127
+ exports.nodeOptionsGetter = nodeOptionsGetter;
1128
+ exports.nodeRadiusGetter = nodeRadiusGetter;
1129
+ exports.rgbAnimationByProgress = rgbAnimationByProgress;
1130
+ //# sourceMappingURL=index.cjs.map