@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,731 @@
1
+ import { isBoolean, isArray } from '@krainovsd/js-helpers';
2
+ import { greatest } from 'd3-array';
3
+ import { drag } from 'd3-drag';
4
+ import { forceLink, forceSimulation, forceX, forceY, forceManyBody, forceCenter, forceCollide } from 'd3-force';
5
+ import { create, select } from 'd3-selection';
6
+ import { zoomIdentity, zoom, ZoomTransform } from 'd3-zoom';
7
+ import { colorToRgb } from '../../lib/color-to-rgb.js';
8
+ import { extractRgb } from '../../lib/extract-rgb.js';
9
+ import { fadeRgb } from '../../lib/fade-rgb.js';
10
+ import { rgbAnimationByProgress } from '../../lib/rgb-animation-by-progress.js';
11
+ import { forceSettingsGetter } from './lib/settings/force-settings-getter.js';
12
+ import { graphSettingsGetter } from './lib/settings/graph-settings-getter.js';
13
+ import { linkSettingsGetter, linkOptionsGetter } from './lib/settings/link-settings-getter.js';
14
+ import { listenersGetter } from './lib/settings/listeners-getter.js';
15
+ import { nodeSettingsGetter, nodeRadiusGetter, nodeOptionsGetter } from './lib/settings/node-settings-getter.js';
16
+ import { pointerGetter } from './lib/utils/pointer-getter.js';
17
+ import { linkIterationExtractor } from './lib/utils/link-iteration-extractor.js';
18
+ import { nodeByPointerGetter } from './lib/utils/node-by-pointer-getter.js';
19
+ import { nodeIterationExtractor } from './lib/utils/node-iteration-extractor.js';
20
+ import { drawText } from './lib/utils/draw-text.js';
21
+ import { calculateLinkPositionByRadius } from './lib/utils/calculate-link-position-by-radius.js';
22
+
23
+ class GraphCanvas {
24
+ /** initial data */
25
+ nodes;
26
+ links;
27
+ width;
28
+ height;
29
+ root;
30
+ container;
31
+ area;
32
+ /** settings */
33
+ graphSettings;
34
+ forceSettings;
35
+ nodeSettings;
36
+ linkSettings;
37
+ listeners;
38
+ /** service */
39
+ context;
40
+ simulation;
41
+ areaTransform = zoomIdentity;
42
+ areaRect;
43
+ draw;
44
+ eventAbortController;
45
+ cachedNodeText = {};
46
+ isDragging = false;
47
+ highlightedNode = null;
48
+ highlightedNeighbors = null;
49
+ highlightProgress = 1;
50
+ highlightWorking = false;
51
+ highlightDrawing = false;
52
+ get simulationWorking() {
53
+ const simulationAlpha = this.simulation?.alpha?.() ?? 0;
54
+ const simulationAlphaMin = this.simulation?.alphaMin?.() ?? 0;
55
+ const simulationAlphaDecay = this.simulation?.alphaDecay?.() ?? 0;
56
+ const force = (simulationAlpha - simulationAlphaMin) / simulationAlphaDecay;
57
+ return force > 0;
58
+ }
59
+ get state() {
60
+ return {
61
+ areaTransform: this.areaTransform,
62
+ cachedNodeText: this.cachedNodeText,
63
+ context: this.context,
64
+ eventAbortController: this.eventAbortController,
65
+ highlightProgress: this.highlightProgress,
66
+ highlightDrawing: this.highlightDrawing,
67
+ highlightedNeighbors: this.highlightedNeighbors,
68
+ highlightedNode: this.highlightedNode,
69
+ highlightWorking: this.highlightWorking,
70
+ isDragging: this.isDragging,
71
+ simulation: this.simulation,
72
+ simulationWorking: this.simulationWorking,
73
+ height: this.height,
74
+ links: this.links,
75
+ nodes: this.nodes,
76
+ width: this.width,
77
+ forceSettings: this.forceSettings,
78
+ graphSettings: this.graphSettings,
79
+ linkSettings: this.linkSettings,
80
+ nodeSettings: this.nodeSettings,
81
+ };
82
+ }
83
+ constructor({ links, nodes, root, forceSettings, linkSettings, listeners, nodeSettings, graphSettings, }) {
84
+ // root.style.position = "relative";
85
+ root.style.overflow = "hidden";
86
+ this.root = root;
87
+ this.forceSettings = forceSettingsGetter(forceSettings);
88
+ this.linkSettings = linkSettingsGetter(linkSettings);
89
+ this.nodeSettings = nodeSettingsGetter(nodeSettings);
90
+ this.listeners = listenersGetter(listeners);
91
+ this.graphSettings = graphSettingsGetter(graphSettings);
92
+ this.eventAbortController = new AbortController();
93
+ this.nodes = nodes;
94
+ this.links = links;
95
+ this.height = 0;
96
+ this.width = 0;
97
+ this.draw = this.initDraw();
98
+ this.init();
99
+ }
100
+ get dpi() {
101
+ return devicePixelRatio;
102
+ }
103
+ getData() {
104
+ return {
105
+ links: this.links,
106
+ nodes: this.nodes,
107
+ };
108
+ }
109
+ changeData(options, alpha) {
110
+ if (options.links != undefined)
111
+ this.links = options.links;
112
+ if (options.nodes != undefined)
113
+ this.nodes = options.nodes;
114
+ if (options.nodes != undefined || options.links != undefined)
115
+ this.updateData(alpha);
116
+ }
117
+ changeSettings(options) {
118
+ if (options.graphSettings)
119
+ this.graphSettings = graphSettingsGetter(options.graphSettings, this.graphSettings);
120
+ if (options.forceSettings)
121
+ this.forceSettings = forceSettingsGetter(options.forceSettings, this.forceSettings);
122
+ if (options.linkSettings)
123
+ this.linkSettings = linkSettingsGetter(options.linkSettings);
124
+ if (options.nodeSettings)
125
+ this.nodeSettings = nodeSettingsGetter(options.nodeSettings);
126
+ if (options.forceSettings)
127
+ return void this.updateSimulation();
128
+ this.tick();
129
+ }
130
+ tick() {
131
+ if (!this.simulationWorking)
132
+ this.draw();
133
+ }
134
+ restart(alpha) {
135
+ if (this.simulation)
136
+ this.simulation.alpha(alpha ?? 1).restart();
137
+ }
138
+ start() {
139
+ if (this.simulation)
140
+ this.simulation.alpha(1).restart();
141
+ if (this.container)
142
+ this.container.style.display = "block";
143
+ }
144
+ stop() {
145
+ if (this.simulation)
146
+ this.simulation.stop();
147
+ if (this.container)
148
+ this.container.style.display = "none";
149
+ }
150
+ create() {
151
+ this.init();
152
+ }
153
+ destroy() {
154
+ if (this.simulation) {
155
+ this.simulation.stop();
156
+ this.simulation = undefined;
157
+ }
158
+ this.clearHTMLElements();
159
+ this.clearState();
160
+ this.clearDataDependencies();
161
+ }
162
+ clearHTMLElements() {
163
+ this.root.replaceChildren();
164
+ this.area = undefined;
165
+ this.context = undefined;
166
+ this.container = undefined;
167
+ this.eventAbortController.abort();
168
+ this.eventAbortController = new AbortController();
169
+ }
170
+ updateSimulation() {
171
+ if (this.simulation) {
172
+ this.initSimulationForces();
173
+ this.simulation.alpha(1);
174
+ this.simulation.restart();
175
+ }
176
+ }
177
+ clearState() {
178
+ this.isDragging = false;
179
+ this.highlightedNode = null;
180
+ this.highlightedNeighbors = null;
181
+ this.highlightProgress = 0;
182
+ this.highlightWorking = false;
183
+ this.highlightDrawing = false;
184
+ }
185
+ clearDataDependencies() {
186
+ this.cachedNodeText = {};
187
+ }
188
+ updateData(alpha) {
189
+ this.clearDataDependencies();
190
+ if (this.simulation) {
191
+ this.initCollideForce();
192
+ this.simulation
193
+ .nodes(this.nodes)
194
+ .force("link", forceLink(this.links)
195
+ .id(this.nodeSettings.idGetter)
196
+ .distance(this.forceSettings.linkDistance)
197
+ .strength(this.forceSettings.linkStrength)
198
+ .iterations(this.forceSettings.linkIterations))
199
+ .alpha(alpha ?? 0.5)
200
+ .restart();
201
+ }
202
+ }
203
+ updateSize() {
204
+ this.clearHTMLElements();
205
+ this.initArea();
206
+ this.initDnd();
207
+ this.initZoom();
208
+ this.initResize();
209
+ this.initPointer();
210
+ if (!this.simulationWorking)
211
+ this.draw();
212
+ }
213
+ init() {
214
+ this.initArea();
215
+ this.initSimulation();
216
+ this.initDnd();
217
+ this.initZoom();
218
+ this.initResize();
219
+ this.initPointer();
220
+ }
221
+ initSimulation() {
222
+ if (!this.simulation) {
223
+ this.simulation = forceSimulation()
224
+ .nodes(this.nodes)
225
+ .force("link", forceLink(this.links).id(this.nodeSettings.idGetter))
226
+ .on("tick", () => {
227
+ this.draw();
228
+ })
229
+ .on("end", () => {
230
+ this.listeners.onSimulationEnd?.(this.state);
231
+ });
232
+ this.initSimulationForces();
233
+ }
234
+ }
235
+ initSimulationForces() {
236
+ if (!this.simulation)
237
+ return;
238
+ const linkForce = this.simulation.force("link");
239
+ if (!linkForce)
240
+ return;
241
+ linkForce
242
+ .distance(this.forceSettings.linkDistance)
243
+ .strength(this.forceSettings.linkStrength)
244
+ .iterations(this.forceSettings.linkIterations);
245
+ this.simulation
246
+ .force("x", forceX(this.forceSettings.xForce).strength(this.forceSettings.xStrength))
247
+ .force("y", forceY(this.forceSettings.yForce).strength(this.forceSettings.yStrength))
248
+ .force("charge", forceManyBody()
249
+ .strength(this.forceSettings.chargeStrength)
250
+ .distanceMax(this.forceSettings.chargeDistanceMax)
251
+ .distanceMin(this.forceSettings.chargeDistanceMin))
252
+ .force("center", forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
253
+ this.initCollideForce();
254
+ }
255
+ initCollideForce() {
256
+ if (!this.simulation)
257
+ return;
258
+ if (!this.forceSettings.collideOn) {
259
+ if (this.simulation.force("collide"))
260
+ this.simulation.force("collide", null);
261
+ return;
262
+ }
263
+ const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
264
+ const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
265
+ const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
266
+ if (isMaxCollideNodes && isMaxCollideLinks)
267
+ this.simulation.force("collide", null);
268
+ else if (!this.simulation.force("collide"))
269
+ this.simulation.force("collide", forceCollide()
270
+ .radius((node, index) => {
271
+ if (this.forceSettings.collideRadius) {
272
+ return nodeIterationExtractor(node, index, this.nodes, this.state, this.forceSettings.collideRadius, undefined);
273
+ }
274
+ const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
275
+ const radius = nodeRadiusGetter({
276
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
277
+ radiusInitial: nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial,
278
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
279
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
280
+ linkCount: node.linkCount,
281
+ });
282
+ return radius + this.forceSettings.collideAdditionalRadius;
283
+ })
284
+ .strength(this.forceSettings.collideStrength)
285
+ .iterations(this.forceSettings.collideIterations));
286
+ }
287
+ initArea() {
288
+ if (!this.area || !this.context || !this.container) {
289
+ this.container = create("div")
290
+ .attr("style", "padding: 0 !important; width: 100%; height: 100%;")
291
+ .node();
292
+ if (!this.container)
293
+ throw new Error("couldn't create container");
294
+ this.root.appendChild(this.container);
295
+ const { width, height } = this.root.getBoundingClientRect();
296
+ this.width = width;
297
+ this.height = height;
298
+ this.area = create("canvas")
299
+ .attr("width", this.dpi * this.width)
300
+ .attr("height", this.dpi * this.height)
301
+ .attr("style", `width: 100%; height: 100%; border: none !important;`)
302
+ .node();
303
+ if (!this.area)
304
+ throw new Error("couldn't create canvas");
305
+ this.container.appendChild(this.area);
306
+ this.areaRect = this.area.getBoundingClientRect();
307
+ this.context = this.area.getContext("2d");
308
+ if (!this.context)
309
+ throw new Error("couldn't create canvas context");
310
+ this.context.scale(this.dpi, this.dpi);
311
+ }
312
+ }
313
+ initDraw() {
314
+ function calculateHighlightFading() {
315
+ this.highlightDrawing = true;
316
+ if (!this.highlightWorking && this.highlightProgress > 0) {
317
+ this.highlightProgress -= this.graphSettings.highlightDownStep;
318
+ if (!this.simulationWorking)
319
+ return void requestAnimationFrame(() => this.draw());
320
+ return;
321
+ }
322
+ if (this.highlightWorking && this.highlightProgress < 1) {
323
+ this.highlightProgress += this.graphSettings.highlightUpStep;
324
+ if (!this.simulationWorking)
325
+ return void requestAnimationFrame(() => this.draw());
326
+ return;
327
+ }
328
+ if (!this.highlightWorking && this.highlightProgress <= 0) {
329
+ if (this.highlightedNeighbors)
330
+ this.highlightedNeighbors = null;
331
+ if (this.highlightedNode)
332
+ this.highlightedNode = null;
333
+ }
334
+ this.highlightDrawing = false;
335
+ }
336
+ function draw() {
337
+ if (!this.context)
338
+ return;
339
+ if (this.listeners.onDraw) {
340
+ this.listeners.onDraw(this.state, (status) => {
341
+ this.highlightDrawing = status;
342
+ }, () => {
343
+ if (this.highlightedNeighbors)
344
+ this.highlightedNeighbors = null;
345
+ if (this.highlightedNode)
346
+ this.highlightedNode = null;
347
+ });
348
+ return;
349
+ }
350
+ this.context.save();
351
+ this.context.clearRect(0, 0, this.width, this.height);
352
+ this.context.translate(this.areaTransform.x, this.areaTransform.y);
353
+ this.context.scale(this.areaTransform.k, this.areaTransform.k);
354
+ /** links */
355
+ this.context.beginPath();
356
+ this.links.forEach(drawLink.bind(this));
357
+ this.context.stroke();
358
+ /** nodes */
359
+ const textRenders = [];
360
+ this.nodes.forEach(drawNode(textRenders).bind(this));
361
+ textRenders.forEach((render) => render());
362
+ this.context.restore();
363
+ this.listeners.onDrawFinished?.(this.state);
364
+ calculateHighlightFading.bind(this)();
365
+ }
366
+ function drawLink(link, index) {
367
+ if (!this.context ||
368
+ typeof link.source !== "object" ||
369
+ typeof link.target !== "object" ||
370
+ !link.source.x ||
371
+ !link.source.y ||
372
+ !link.target.x ||
373
+ !link.target.y)
374
+ return;
375
+ const linkOptions = linkIterationExtractor(link, index, this.links, this.state, this.linkSettings.options ?? {}, linkOptionsGetter);
376
+ let alpha = linkOptions.alpha;
377
+ if (this.highlightedNeighbors && this.highlightedNode) {
378
+ /** Not highlighted */
379
+ if (this.highlightedNode.id != link.source.id &&
380
+ this.highlightedNode.id != link.target.id) {
381
+ if (linkOptions.highlightFading)
382
+ alpha =
383
+ this.graphSettings.highlightLinkFadingMin +
384
+ (alpha - this.graphSettings.highlightLinkFadingMin) * (1 - this.highlightProgress);
385
+ }
386
+ this.context.beginPath();
387
+ }
388
+ this.context.globalAlpha = alpha;
389
+ this.context.strokeStyle = linkOptions.color;
390
+ this.context.lineWidth = linkOptions.width;
391
+ if (linkOptions.pretty) {
392
+ const { x1, x2, y1, y2 } = calculateLinkPositionByRadius(link) ?? {
393
+ x1: 0,
394
+ x2: 0,
395
+ y1: 0,
396
+ y2: 0,
397
+ };
398
+ this.context.moveTo(x1, y1);
399
+ this.context.lineTo(x2, y2);
400
+ }
401
+ else {
402
+ this.context.moveTo(link.source.x, link.source.y);
403
+ this.context.lineTo(link.target.x, link.target.y);
404
+ }
405
+ if (this.highlightedNeighbors && this.highlightedNode)
406
+ this.context.stroke();
407
+ }
408
+ const drawNode = (textRenders) => function drawNode(node, index) {
409
+ if (!this.context || !node.x || !node.y)
410
+ return;
411
+ const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
412
+ let alpha = nodeOptions.alpha;
413
+ let color = nodeOptions.color;
414
+ let radiusInitial = nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial;
415
+ let textAlpha = nodeOptions.textAlpha;
416
+ let textSize = nodeOptions.textSize;
417
+ let textShiftX = nodeOptions.textShiftX;
418
+ let textShiftY = nodeOptions.textShiftY;
419
+ let textWeight = nodeOptions.textWeight;
420
+ let textWidth = nodeOptions.textWidth;
421
+ if (this.highlightedNeighbors && this.highlightedNode) {
422
+ /** Not highlighted */
423
+ if (!this.highlightedNeighbors.has(node.id) && this.highlightedNode.id != node.id) {
424
+ if (nodeOptions.highlightFading) {
425
+ alpha =
426
+ this.graphSettings.highlightFadingMin +
427
+ (alpha - this.graphSettings.highlightFadingMin) * (1 - this.highlightProgress);
428
+ }
429
+ if (nodeOptions.highlightTextFading) {
430
+ textAlpha =
431
+ this.graphSettings.highlightTextFadingMin +
432
+ (textAlpha - this.graphSettings.highlightTextFadingMin) *
433
+ (1 - this.highlightProgress);
434
+ }
435
+ if (nodeOptions.highlightColor) {
436
+ const colorRgb = extractRgb(colorToRgb(color));
437
+ if (colorRgb) {
438
+ const colorRgbFade = fadeRgb(colorRgb, this.graphSettings.highlightColorFadingMin);
439
+ const colorFadeAnimation = rgbAnimationByProgress(colorRgb, colorRgbFade, this.highlightProgress);
440
+ color = `rgb(${colorFadeAnimation.r}, ${colorFadeAnimation.g}, ${colorFadeAnimation.b})`;
441
+ }
442
+ }
443
+ }
444
+ else if (!this.graphSettings.highlightOnlyRoot ||
445
+ (this.graphSettings.highlightOnlyRoot && this.highlightedNode.id === node.id)) {
446
+ /** Highlighted */
447
+ if (nodeOptions.highlightSizing) {
448
+ const radiusMax = radiusInitial + this.graphSettings.highlightSizingAdditional;
449
+ radiusInitial += ((radiusMax - radiusInitial) / 100) * (this.highlightProgress * 100);
450
+ }
451
+ if (nodeOptions.highlightTextSizing) {
452
+ const textSizeMax = textSize + this.graphSettings.highlightTextSizingAdditional;
453
+ const textShiftXMax = textShiftX + this.graphSettings.highlightTextShiftXAdditional;
454
+ const textShiftYMax = textShiftY + this.graphSettings.highlightTextShiftYAdditional;
455
+ const textWeightMax = textWeight + this.graphSettings.highlightTextWeightAdditional;
456
+ const textWidthMax = textWidth + this.graphSettings.highlightTextWidthAdditional;
457
+ textSize += ((textSizeMax - textSize) / 100) * (this.highlightProgress * 100);
458
+ textShiftX += ((textShiftXMax - textShiftX) / 100) * (this.highlightProgress * 100);
459
+ textShiftY += ((textShiftYMax - textShiftY) / 100) * (this.highlightProgress * 100);
460
+ textWeight += ((textWeightMax - textWeight) / 100) * (this.highlightProgress * 100);
461
+ textWidth += ((textWidthMax - textWidth) / 100) * (this.highlightProgress * 100);
462
+ }
463
+ }
464
+ }
465
+ const radius = nodeRadiusGetter({
466
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
467
+ radiusInitial,
468
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
469
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
470
+ linkCount: node.linkCount,
471
+ });
472
+ node._radius = radius;
473
+ this.context.beginPath();
474
+ this.context.globalAlpha = alpha;
475
+ /** text */
476
+ if (nodeOptions.textVisible && nodeOptions.text) {
477
+ textRenders.push(() => {
478
+ if (!this.context || !node.x || !node.y || !nodeOptions.text)
479
+ return;
480
+ this.context.beginPath();
481
+ this.context.globalAlpha = textAlpha;
482
+ drawText({
483
+ id: node.id,
484
+ cachedNodeText: this.cachedNodeText,
485
+ context: this.context,
486
+ text: nodeOptions.text,
487
+ textAlign: nodeOptions.textAlign,
488
+ textColor: nodeOptions.textColor,
489
+ textFont: nodeOptions.textFont,
490
+ textSize,
491
+ x: node.x + textShiftX,
492
+ y: node.y + radius + textShiftY,
493
+ maxWidth: textWidth,
494
+ textStyle: nodeOptions.textStyle,
495
+ textWeight,
496
+ textGap: nodeOptions.textGap,
497
+ });
498
+ });
499
+ }
500
+ /** circle */
501
+ this.context.lineWidth = nodeOptions.borderWidth;
502
+ this.context.strokeStyle = nodeOptions.borderColor;
503
+ this.context.fillStyle = color;
504
+ this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
505
+ this.context.fill();
506
+ this.context.stroke();
507
+ };
508
+ return draw;
509
+ }
510
+ initResize() {
511
+ if (!this.area)
512
+ throw new Error("bad init data");
513
+ let initialResizeCall = true;
514
+ const abortController = this.eventAbortController;
515
+ const observer = new ResizeObserver(() => {
516
+ if (initialResizeCall) {
517
+ initialResizeCall = false;
518
+ return;
519
+ }
520
+ if (abortController.signal.aborted) {
521
+ observer.disconnect();
522
+ return;
523
+ }
524
+ requestAnimationFrame(() => {
525
+ this.updateSize();
526
+ });
527
+ });
528
+ observer.observe(this.area);
529
+ }
530
+ initPointer() {
531
+ if (!this.area || !this.nodes || !this.simulation)
532
+ throw new Error("bad init data");
533
+ /** hover */
534
+ this.area.addEventListener("pointermove", (event) => {
535
+ let currentNode;
536
+ if (this.graphSettings.highlightByHover && !this.isDragging) {
537
+ currentNode = nodeByPointerGetter({
538
+ graphSettings: this.graphSettings,
539
+ areaRect: this.areaRect,
540
+ areaTransform: this.areaTransform,
541
+ mouseEvent: event,
542
+ nodes: this.nodes,
543
+ });
544
+ if (currentNode && this.highlightedNode !== currentNode) {
545
+ this.highlightedNode = currentNode;
546
+ this.highlightedNeighbors = new Set(this.highlightedNode?.neighbors ?? []);
547
+ this.highlightWorking = true;
548
+ if (!this.simulationWorking && !this.highlightDrawing)
549
+ requestAnimationFrame(() => {
550
+ this.draw();
551
+ });
552
+ }
553
+ else if (!currentNode && this.highlightedNode) {
554
+ this.highlightWorking = false;
555
+ if (!this.simulationWorking && !this.highlightDrawing)
556
+ requestAnimationFrame(() => {
557
+ this.draw();
558
+ });
559
+ }
560
+ }
561
+ if (!this.listeners.onMove)
562
+ return;
563
+ if (!currentNode)
564
+ currentNode = nodeByPointerGetter({
565
+ graphSettings: this.graphSettings,
566
+ areaRect: this.areaRect,
567
+ areaTransform: this.areaTransform,
568
+ mouseEvent: event,
569
+ nodes: this.nodes,
570
+ });
571
+ return void this.listeners.onMove(event, currentNode);
572
+ }, {
573
+ signal: this.eventAbortController.signal,
574
+ });
575
+ /** dblclick */
576
+ this.area.addEventListener("dblclick", (event) => {
577
+ if (!this.listeners.onDoubleClick)
578
+ return;
579
+ const currentNode = nodeByPointerGetter({
580
+ graphSettings: this.graphSettings,
581
+ areaRect: this.areaRect,
582
+ areaTransform: this.areaTransform,
583
+ mouseEvent: event,
584
+ nodes: this.nodes,
585
+ });
586
+ return void this.listeners.onDoubleClick(event, currentNode);
587
+ });
588
+ /** wheel click */
589
+ this.area.addEventListener("mousedown", (event) => {
590
+ if (this.isDragging || !this.listeners.onWheelClick || event.button !== 1)
591
+ return;
592
+ const currentNode = nodeByPointerGetter({
593
+ graphSettings: this.graphSettings,
594
+ areaRect: this.areaRect,
595
+ areaTransform: this.areaTransform,
596
+ mouseEvent: event,
597
+ nodes: this.nodes,
598
+ });
599
+ return void this.listeners.onWheelClick(event, currentNode);
600
+ }, {
601
+ signal: this.eventAbortController.signal,
602
+ });
603
+ /** click */
604
+ this.area.addEventListener("click", (event) => {
605
+ if (this.isDragging || !this.listeners.onClick || event.button !== 0)
606
+ return;
607
+ const currentNode = nodeByPointerGetter({
608
+ graphSettings: this.graphSettings,
609
+ areaRect: this.areaRect,
610
+ areaTransform: this.areaTransform,
611
+ mouseEvent: event,
612
+ nodes: this.nodes,
613
+ });
614
+ return void this.listeners.onClick(event, currentNode);
615
+ }, {
616
+ signal: this.eventAbortController.signal,
617
+ });
618
+ /** right click */
619
+ this.area.addEventListener("contextmenu", (event) => {
620
+ if (!this.listeners.onContextMenu)
621
+ return;
622
+ const currentNode = nodeByPointerGetter({
623
+ graphSettings: this.graphSettings,
624
+ areaRect: this.areaRect,
625
+ areaTransform: this.areaTransform,
626
+ mouseEvent: event,
627
+ nodes: this.nodes,
628
+ });
629
+ return void this.listeners.onContextMenu(event, currentNode);
630
+ }, {
631
+ signal: this.eventAbortController.signal,
632
+ });
633
+ }
634
+ initDnd() {
635
+ if (!this.area || !this.nodes || !this.simulation)
636
+ throw new Error("bad init data");
637
+ select(this.area).call(drag()
638
+ .subject((event) => {
639
+ if (this.listeners.onDragSubject) {
640
+ return this.listeners.onDragSubject(event, this.state);
641
+ }
642
+ if (!this.areaRect)
643
+ return;
644
+ const mouseEvent = event.sourceEvent;
645
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
646
+ let index = 0;
647
+ return greatest(this.nodes, (node) => {
648
+ if (!node.x || !node.y || (isBoolean(node.drag) && !node.drag))
649
+ return undefined;
650
+ let radius = node._radius;
651
+ if (!radius) {
652
+ const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
653
+ radius = nodeRadiusGetter({
654
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
655
+ radiusInitial: nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial,
656
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
657
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
658
+ linkCount: node.linkCount,
659
+ });
660
+ }
661
+ index++;
662
+ return this.graphSettings.dragPlaceCoefficient(node, pointerX, pointerY, radius);
663
+ });
664
+ })
665
+ .on("start", (event) => {
666
+ this.listeners.onStartDragFinished?.(event, this.state);
667
+ })
668
+ .on("drag", (event) => {
669
+ if (!this.isDragging) {
670
+ this.isDragging = true;
671
+ if (this.simulation)
672
+ this.simulation.alphaTarget(0.3).restart();
673
+ }
674
+ if (!this.areaRect)
675
+ return;
676
+ const mouseEvent = event.sourceEvent;
677
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
678
+ event.subject.fx = pointerX;
679
+ event.subject.fy = pointerY;
680
+ this.listeners.onMoveDragFinished?.(event, this.state);
681
+ })
682
+ .on("end", (event) => {
683
+ this.isDragging = false;
684
+ if (!event.active && this.simulation)
685
+ this.simulation.alphaTarget(0);
686
+ if (this.graphSettings.stickAfterDrag && this.areaRect) {
687
+ if (!this.areaRect)
688
+ return;
689
+ const mouseEvent = event.sourceEvent;
690
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
691
+ event.subject.fx = pointerX;
692
+ event.subject.fy = pointerY;
693
+ }
694
+ else {
695
+ event.subject.fx = null;
696
+ event.subject.fy = null;
697
+ }
698
+ this.listeners.onEndDragFinished?.(event, this.state);
699
+ }));
700
+ }
701
+ initZoom() {
702
+ if (!this.area)
703
+ throw new Error("bad init data");
704
+ const zoomInstance = zoom()
705
+ .scaleExtent(this.graphSettings.zoomExtent)
706
+ .on("zoom", (event) => {
707
+ this.listeners.onZoom?.(event);
708
+ this.areaTransform = event.transform;
709
+ if (!this.simulationWorking)
710
+ requestAnimationFrame(() => this.draw());
711
+ });
712
+ if (this.graphSettings.translateExtentEnable) {
713
+ const coefficient = this.graphSettings.translateExtentCoefficient;
714
+ const [coefficientX, coefficientY] = isArray(coefficient)
715
+ ? coefficient
716
+ : [coefficient, coefficient];
717
+ const [[minX = -this.width * coefficientX, minY = -this.height * coefficientX], [maxX = this.width * coefficientY, maxY = this.height * coefficientY],] = this.graphSettings.translateExtent;
718
+ zoomInstance.translateExtent([
719
+ [minX, minY],
720
+ [maxX, maxY],
721
+ ]);
722
+ }
723
+ select(this.area).call(zoomInstance).on("dblclick.zoom", null);
724
+ const zoomInitial = this.graphSettings.zoomInitial;
725
+ this.areaTransform = new ZoomTransform(zoomInitial?.k ?? 1, zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
726
+ zoom().transform(select(this.area), this.areaTransform);
727
+ }
728
+ }
729
+
730
+ export { GraphCanvas };
731
+ //# sourceMappingURL=GraphCanvas.js.map