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