@krainovsd/graph 0.8.4 → 0.10.0-rc1

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 (55) hide show
  1. package/lib/cjs/index.cjs +2819 -1574
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/esm/index.js +14 -4
  4. package/lib/esm/index.js.map +1 -1
  5. package/lib/esm/lib/draw-time.js +5 -1
  6. package/lib/esm/lib/draw-time.js.map +1 -1
  7. package/lib/esm/lib/get-controls-info.js +525 -151
  8. package/lib/esm/lib/get-controls-info.js.map +1 -1
  9. package/lib/esm/module/GraphCanvas/GraphCanvas.js +42 -746
  10. package/lib/esm/module/GraphCanvas/GraphCanvas.js.map +1 -1
  11. package/lib/esm/module/GraphCanvas/constants/settings.js +83 -39
  12. package/lib/esm/module/GraphCanvas/constants/settings.js.map +1 -1
  13. package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js +10 -1
  14. package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js.map +1 -1
  15. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-node.js +270 -0
  16. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-node.js.map +1 -0
  17. package/lib/esm/module/GraphCanvas/lib/utils/drag-place-coefficient-getter.js +2 -2
  18. package/lib/esm/module/GraphCanvas/lib/utils/drag-place-coefficient-getter.js.map +1 -1
  19. package/lib/esm/module/GraphCanvas/lib/utils/get-particle-position.js +19 -4
  20. package/lib/esm/module/GraphCanvas/lib/utils/get-particle-position.js.map +1 -1
  21. package/lib/esm/module/GraphCanvas/lib/utils/is-node-visible.js +29 -4
  22. package/lib/esm/module/GraphCanvas/lib/utils/is-node-visible.js.map +1 -1
  23. package/lib/esm/module/GraphCanvas/lib/utils/is-overlaps-node.js +53 -4
  24. package/lib/esm/module/GraphCanvas/lib/utils/is-overlaps-node.js.map +1 -1
  25. package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js +55 -0
  26. package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js.map +1 -0
  27. package/lib/esm/module/GraphCanvas/lib/utils/node-by-pointer-getter.js +3 -13
  28. package/lib/esm/module/GraphCanvas/lib/utils/node-by-pointer-getter.js.map +1 -1
  29. package/lib/esm/module/GraphCanvas/lib/utils/pointer-getter.js +8 -2
  30. package/lib/esm/module/GraphCanvas/lib/utils/pointer-getter.js.map +1 -1
  31. package/lib/esm/module/GraphCanvas/slices/draw-links.js +207 -0
  32. package/lib/esm/module/GraphCanvas/slices/draw-links.js.map +1 -0
  33. package/lib/esm/module/GraphCanvas/slices/draw-nodes.js +342 -0
  34. package/lib/esm/module/GraphCanvas/slices/draw-nodes.js.map +1 -0
  35. package/lib/esm/module/GraphCanvas/{lib/utils → slices}/draw-text.js +23 -6
  36. package/lib/esm/module/GraphCanvas/slices/draw-text.js.map +1 -0
  37. package/lib/esm/module/GraphCanvas/slices/init-area.js +31 -0
  38. package/lib/esm/module/GraphCanvas/slices/init-area.js.map +1 -0
  39. package/lib/esm/module/GraphCanvas/slices/init-dnd.js +62 -0
  40. package/lib/esm/module/GraphCanvas/slices/init-dnd.js.map +1 -0
  41. package/lib/esm/module/GraphCanvas/slices/init-draw.js +84 -0
  42. package/lib/esm/module/GraphCanvas/slices/init-draw.js.map +1 -0
  43. package/lib/esm/module/GraphCanvas/slices/init-pointer.js +211 -0
  44. package/lib/esm/module/GraphCanvas/slices/init-pointer.js.map +1 -0
  45. package/lib/esm/module/GraphCanvas/slices/init-resize.js +28 -0
  46. package/lib/esm/module/GraphCanvas/slices/init-resize.js.map +1 -0
  47. package/lib/esm/module/GraphCanvas/slices/init-simulation.js +172 -0
  48. package/lib/esm/module/GraphCanvas/slices/init-simulation.js.map +1 -0
  49. package/lib/esm/module/GraphCanvas/slices/init-zoom.js +36 -0
  50. package/lib/esm/module/GraphCanvas/slices/init-zoom.js.map +1 -0
  51. package/lib/index.d.ts +192 -114
  52. package/package.json +1 -1
  53. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-radius.js +0 -20
  54. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-radius.js.map +0 -1
  55. package/lib/esm/module/GraphCanvas/lib/utils/draw-text.js.map +0 -1
@@ -1,28 +1,19 @@
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';
1
+ import { forceLink } from 'd3-force';
2
+ import { zoomIdentity } from 'd3-zoom';
11
3
  import { forceSettingsGetter } from './lib/settings/force-settings-getter.js';
12
4
  import { graphSettingsGetter } from './lib/settings/graph-settings-getter.js';
13
- import { linkSettingsGetter, linkOptionsGetter } from './lib/settings/link-settings-getter.js';
5
+ import { linkSettingsGetter } from './lib/settings/link-settings-getter.js';
14
6
  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
- import { animationByProgress } from './lib/utils/animation-by-progress.js';
23
- import { isNodeVisible } from './lib/utils/is-node-visible.js';
24
- import { getParticlePosition } from './lib/utils/get-particle-position.js';
25
- import { getDrawTime, setDrawTime } from '../../lib/draw-time.js';
7
+ import { nodeSettingsGetter } from './lib/settings/node-settings-getter.js';
8
+ import '@krainovsd/js-helpers';
9
+ import 'd3-array';
10
+ import { initArea } from './slices/init-area.js';
11
+ import { initDnd } from './slices/init-dnd.js';
12
+ import { initDraw } from './slices/init-draw.js';
13
+ import { initPointer } from './slices/init-pointer.js';
14
+ import { initResize } from './slices/init-resize.js';
15
+ import { initCollideForce, initSimulationForces, initSimulation } from './slices/init-simulation.js';
16
+ import { initZoom } from './slices/init-zoom.js';
26
17
 
27
18
  class GraphCanvas {
28
19
  /** initial data */
@@ -52,6 +43,7 @@ class GraphCanvas {
52
43
  nodeOptionsCache = {};
53
44
  isDragging = false;
54
45
  highlightedNode = null;
46
+ highlightedLink = null;
55
47
  highlightedNeighbors = null;
56
48
  highlightProgress = 1;
57
49
  highlightWorking = false;
@@ -73,6 +65,7 @@ class GraphCanvas {
73
65
  highlightDrawing: this.highlightDrawing,
74
66
  highlightedNeighbors: this.highlightedNeighbors,
75
67
  highlightedNode: this.highlightedNode,
68
+ highlightedLink: this.highlightedLink,
76
69
  highlightWorking: this.highlightWorking,
77
70
  isDragging: this.isDragging,
78
71
  simulation: this.simulation,
@@ -101,7 +94,7 @@ class GraphCanvas {
101
94
  this.links = links;
102
95
  this.height = 0;
103
96
  this.width = 0;
104
- this.draw = this.initDraw();
97
+ this.draw = initDraw.call(this);
105
98
  this.init();
106
99
  }
107
100
  get dpi() {
@@ -124,8 +117,8 @@ class GraphCanvas {
124
117
  changeSettings(options) {
125
118
  if (options.graphSettings) {
126
119
  this.graphSettings = graphSettingsGetter(options.graphSettings, this.graphSettings);
127
- this.draw = this.initDraw();
128
- this.initZoom(this.areaTransform);
120
+ this.draw = initDraw.call(this);
121
+ initZoom.call(this, this.areaTransform);
129
122
  }
130
123
  if (options.forceSettings) {
131
124
  this.forceSettings = forceSettingsGetter(options.forceSettings, this.forceSettings);
@@ -138,6 +131,7 @@ class GraphCanvas {
138
131
  this.nodeSettings = nodeSettingsGetter(options.nodeSettings, this.nodeSettings);
139
132
  this.cachedNodeText = {};
140
133
  this.nodeOptionsCache = {};
134
+ initCollideForce.call(this, true);
141
135
  }
142
136
  if (options.forceSettings) {
143
137
  return void this.updateSimulation();
@@ -195,7 +189,7 @@ class GraphCanvas {
195
189
  }
196
190
  updateSimulation() {
197
191
  if (this.simulation) {
198
- this.initSimulationForces();
192
+ initSimulationForces.call(this);
199
193
  this.simulation.alpha(1);
200
194
  this.simulation.restart();
201
195
  }
@@ -203,6 +197,7 @@ class GraphCanvas {
203
197
  clearState() {
204
198
  this.isDragging = false;
205
199
  this.highlightedNode = null;
200
+ this.highlightedLink = null;
206
201
  this.highlightedNeighbors = null;
207
202
  this.highlightProgress = 0;
208
203
  this.highlightWorking = false;
@@ -216,740 +211,41 @@ class GraphCanvas {
216
211
  updateData(alpha) {
217
212
  this.clearDataDependencies();
218
213
  if (this.simulation) {
219
- this.initCollideForce();
214
+ initCollideForce.call(this, false);
220
215
  this.simulation
221
216
  .nodes(this.nodes)
222
217
  .force("link", forceLink(this.links)
223
218
  .id(this.nodeSettings.idGetter)
224
- .distance(this.forceSettings.linkDistance)
225
- .strength(this.forceSettings.linkStrength)
226
- .iterations(this.forceSettings.linkIterations))
219
+ .distance(this.forceSettings.forces && this.forceSettings.linkForce
220
+ ? this.forceSettings.linkDistance
221
+ : 0)
222
+ .strength(this.forceSettings.forces && this.forceSettings.linkForce
223
+ ? this.forceSettings.linkStrength
224
+ : 0)
225
+ .iterations(this.forceSettings.forces && this.forceSettings.linkForce
226
+ ? this.forceSettings.linkIterations
227
+ : 0))
227
228
  .alpha(alpha ?? 0.5)
228
229
  .restart();
229
230
  }
230
231
  }
231
232
  updateSize() {
232
233
  this.clearHTMLElements();
233
- this.initArea();
234
- this.initDnd();
235
- this.initZoom();
236
- this.initResize();
237
- this.initPointer();
234
+ initArea.call(this);
235
+ initDnd.call(this);
236
+ initZoom.call(this);
237
+ initResize.call(this);
238
+ initPointer.call(this);
238
239
  if (!this.simulationWorking && !this.highlightWorking)
239
240
  this.draw();
240
241
  }
241
242
  init() {
242
- this.initArea();
243
- this.initSimulation();
244
- this.initDnd();
245
- this.initZoom();
246
- this.initResize();
247
- this.initPointer();
248
- }
249
- initSimulation() {
250
- if (!this.simulation) {
251
- this.simulation = forceSimulation()
252
- .nodes(this.nodes)
253
- .force("link", forceLink(this.links).id(this.nodeSettings.idGetter))
254
- .on("tick", () => {
255
- this.draw();
256
- })
257
- .on("end", () => {
258
- this.listeners.onSimulationEnd?.(this.state);
259
- if (this.graphSettings.showDrawTime)
260
- getDrawTime();
261
- });
262
- this.initSimulationForces();
263
- }
264
- }
265
- initSimulationForces() {
266
- if (!this.simulation)
267
- return;
268
- const linkForce = this.simulation.force("link");
269
- if (!linkForce)
270
- return;
271
- linkForce
272
- .distance(this.forceSettings.linkDistance)
273
- .strength(this.forceSettings.linkStrength)
274
- .iterations(this.forceSettings.linkIterations);
275
- this.simulation
276
- .force("x", forceX(this.forceSettings.xForce).strength(this.forceSettings.xStrength))
277
- .force("y", forceY(this.forceSettings.yForce).strength(this.forceSettings.yStrength))
278
- .force("charge", forceManyBody()
279
- .strength(this.forceSettings.chargeStrength)
280
- .distanceMax(this.forceSettings.chargeDistanceMax)
281
- .distanceMin(this.forceSettings.chargeDistanceMin))
282
- .force("center", forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
283
- this.initCollideForce(true);
284
- }
285
- initCollideForce(forceUpdate) {
286
- if (!this.simulation)
287
- return;
288
- if (!this.forceSettings.collideOn) {
289
- if (this.simulation.force("collide"))
290
- this.simulation.force("collide", null);
291
- return;
292
- }
293
- const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
294
- const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
295
- const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
296
- if (isMaxCollideNodes && isMaxCollideLinks) {
297
- this.simulation.force("collide", null);
298
- }
299
- else if (!this.simulation.force("collide") || forceUpdate) {
300
- this.simulation.force("collide", forceCollide()
301
- .radius((node, index) => {
302
- if (this.forceSettings.collideRadius) {
303
- return nodeIterationExtractor(node, index, this.nodes, this.state, this.forceSettings.collideRadius, undefined);
304
- }
305
- const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
306
- const radius = nodeRadiusGetter({
307
- radiusFlexible: this.graphSettings.nodeRadiusFlexible,
308
- radiusInitial: nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial,
309
- radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
310
- radiusFactor: this.graphSettings.nodeRadiusFactor,
311
- linkCount: node.linkCount,
312
- });
313
- return radius + this.forceSettings.collideAdditionalRadius;
314
- })
315
- .strength(this.forceSettings.collideStrength)
316
- .iterations(this.forceSettings.collideIterations));
317
- }
318
- }
319
- initArea() {
320
- if (!this.area || !this.context || !this.container) {
321
- this.container = create("div")
322
- .attr("style", "padding: 0 !important; width: 100%; height: 100%;")
323
- .node();
324
- if (!this.container)
325
- throw new Error("couldn't create container");
326
- this.root.appendChild(this.container);
327
- const { width, height } = this.root.getBoundingClientRect();
328
- this.width = width;
329
- this.height = height;
330
- this.area = create("canvas")
331
- .attr("width", this.dpi * this.width)
332
- .attr("height", this.dpi * this.height)
333
- .attr("style", `width: 100%; height: 100%; border: none !important;`)
334
- .node();
335
- if (!this.area)
336
- throw new Error("couldn't create canvas");
337
- this.container.appendChild(this.area);
338
- this.areaRect = this.area.getBoundingClientRect();
339
- this.context = this.area.getContext("2d");
340
- if (!this.context)
341
- throw new Error("couldn't create canvas context");
342
- this.context.scale(this.dpi, this.dpi);
343
- }
344
- }
345
- initDraw() {
346
- function calculateHighlightFading() {
347
- this.highlightDrawing = true;
348
- if (!this.highlightWorking && this.highlightProgress > 0) {
349
- this.highlightProgress -= this.graphSettings.highlightDownStep;
350
- if (!this.simulationWorking)
351
- return void requestAnimationFrame(() => this.draw());
352
- if (!this.linkSettings.particles)
353
- return;
354
- }
355
- if (this.highlightWorking && this.highlightProgress < 1) {
356
- this.highlightProgress += this.graphSettings.highlightUpStep;
357
- if (!this.simulationWorking)
358
- return void requestAnimationFrame(() => this.draw());
359
- if (!this.linkSettings.particles)
360
- return;
361
- }
362
- if (this.linkSettings.particles && this.highlightWorking && !this.simulationWorking) {
363
- return void requestAnimationFrame(() => this.draw());
364
- }
365
- if (!this.highlightWorking && this.highlightProgress <= 0) {
366
- if (this.highlightedNeighbors || this.highlightedNode) {
367
- this.highlightedNeighbors = null;
368
- this.highlightedNode = null;
369
- this.particles = {};
370
- if (!this.simulationWorking)
371
- return void requestAnimationFrame(() => this.draw());
372
- }
373
- }
374
- this.highlightDrawing = false;
375
- }
376
- function draw() {
377
- if (!this.context)
378
- return;
379
- const state = this.state;
380
- if (this.listeners.onDraw) {
381
- this.listeners.onDraw(state, (status) => {
382
- this.highlightDrawing = status;
383
- }, () => {
384
- if (this.highlightedNeighbors)
385
- this.highlightedNeighbors = null;
386
- if (this.highlightedNode)
387
- this.highlightedNode = null;
388
- });
389
- return;
390
- }
391
- this.context.save();
392
- this.context.clearRect(0, 0, this.width, this.height);
393
- this.context.translate(this.areaTransform.x, this.areaTransform.y);
394
- this.context.scale(this.areaTransform.k, this.areaTransform.k);
395
- const textRenders = [];
396
- const nodeRenders = [];
397
- this.nodes.forEach(getDrawNode(nodeRenders, textRenders, state).bind(this));
398
- /** links */
399
- this.links.forEach(getDrawLink(state).bind(this));
400
- /** nodes */
401
- nodeRenders.forEach((render) => render());
402
- textRenders.forEach((render) => render());
403
- this.context.restore();
404
- this.listeners.onDrawFinished?.(state);
405
- calculateHighlightFading.bind(this)();
406
- }
407
- function getDrawLink(state) {
408
- return function drawLink(link, index) {
409
- if (!this.context ||
410
- typeof link.source !== "object" ||
411
- typeof link.target !== "object" ||
412
- !link.source.x ||
413
- !link.source.y ||
414
- !link.target.x ||
415
- !link.target.y)
416
- return;
417
- if (!link.source._visible && !link.target._visible)
418
- return;
419
- const id = `${link.target.id}${link.source.id}`;
420
- let linkOptions;
421
- if (this.linkSettings.cache && this.linkOptionsCache[id]) {
422
- linkOptions = this.linkOptionsCache[id];
423
- }
424
- else {
425
- linkOptions = linkIterationExtractor(link, index, this.links, state, this.linkSettings.options ?? {}, linkOptionsGetter);
426
- if (this.linkSettings.cache) {
427
- this.linkOptionsCache[id] = linkOptions;
428
- }
429
- }
430
- if (linkOptions.drawLink) {
431
- linkOptions.drawLink(link, linkOptions, state);
432
- return;
433
- }
434
- let alpha = linkOptions.alpha;
435
- let arrowAlpha = linkOptions.arrowReverseAppear ? 0 : linkOptions.arrowAlpha;
436
- if (this.highlightedNeighbors && this.highlightedNode) {
437
- /** Not highlighted */
438
- if (this.highlightedNode.id != link.source.id &&
439
- this.highlightedNode.id != link.target.id) {
440
- if (linkOptions.highlightFading) {
441
- const min = this.graphSettings.highlightLinkFadingMin < alpha
442
- ? this.graphSettings.highlightLinkFadingMin
443
- : alpha;
444
- alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
445
- }
446
- if (linkOptions.arrow &&
447
- linkOptions.arrowHighlightFading &&
448
- !linkOptions.arrowReverseAppear) {
449
- const min = this.graphSettings.highlightArrowFadingMin < arrowAlpha
450
- ? this.graphSettings.highlightArrowFadingMin
451
- : arrowAlpha;
452
- arrowAlpha = animationByProgress(min, arrowAlpha - min, 1 - this.highlightProgress);
453
- }
454
- }
455
- else {
456
- // eslint-disable-next-line no-lonely-if
457
- if (linkOptions.arrow && linkOptions.arrowReverseAppear) {
458
- /** Highlighted */
459
- arrowAlpha = animationByProgress(0, linkOptions.arrowAlpha, this.highlightProgress);
460
- }
461
- }
462
- }
463
- /** Link */
464
- this.context.beginPath();
465
- this.context.globalAlpha = alpha;
466
- this.context.strokeStyle = linkOptions.color;
467
- this.context.lineWidth = linkOptions.width;
468
- let xStart = link.source.x;
469
- let yStart = link.source.y;
470
- let xEnd = link.target.x;
471
- let yEnd = link.target.y;
472
- if (linkOptions.pretty) {
473
- const isHasArrow = linkOptions.arrow && arrowAlpha > 0;
474
- const { x1, x2, y1, y2 } = calculateLinkPositionByRadius(link, isHasArrow ? linkOptions.arrowSize : 0) ?? {
475
- x1: 0,
476
- x2: 0,
477
- y1: 0,
478
- y2: 0,
479
- };
480
- xStart = x1;
481
- xEnd = x2;
482
- yStart = y1;
483
- yEnd = y2;
484
- }
485
- this.context.moveTo(xStart, yStart);
486
- this.context.lineTo(xEnd, yEnd);
487
- this.context.stroke();
488
- /** Particle */
489
- if (this.linkSettings.particles &&
490
- this.highlightedNode &&
491
- (this.highlightedNode.id === link.source.id || this.highlightedNode.id === link.target.id)) {
492
- if (!this.particles[id]) {
493
- const sourceId = link.source.id;
494
- const targetId = link.target.id;
495
- this.particles[id] = Array.from({ length: linkOptions.particleCount }, (_, index) => {
496
- return {
497
- step: 0,
498
- wait: index * (linkOptions.particleSteps / linkOptions.particleCount),
499
- sourceId,
500
- targetId,
501
- };
502
- });
503
- }
504
- this.particles[id].forEach((particle) => {
505
- if (!this.context)
506
- return;
507
- getParticlePosition({
508
- particle,
509
- totalSteps: linkOptions.particleSteps,
510
- xEnd,
511
- xStart,
512
- yEnd,
513
- yStart,
514
- });
515
- if (particle.x != undefined && particle.y != undefined) {
516
- this.context.beginPath();
517
- this.context.arc(particle.x, particle.y, linkOptions.particleRadius, 0, Math.PI * 2);
518
- this.context.fillStyle = linkOptions.particleColor;
519
- this.context.fill();
520
- this.context.stroke();
521
- }
522
- });
523
- }
524
- /** Arrow */
525
- if (linkOptions.arrow && arrowAlpha > 0) {
526
- const { x1: xStart, x2: xEnd, y1: yStart, y2: yEnd, } = calculateLinkPositionByRadius(link) ?? {
527
- x1: 0,
528
- x2: 0,
529
- y1: 0,
530
- y2: 0,
531
- };
532
- const angle = Math.atan2(yEnd - yStart, xEnd - xStart);
533
- this.context.beginPath();
534
- this.context.globalAlpha = arrowAlpha;
535
- this.context.moveTo(xEnd, yEnd);
536
- this.context.lineTo(xEnd - linkOptions.arrowSize * Math.cos(angle - Math.PI / 6), yEnd - linkOptions.arrowSize * Math.sin(angle - Math.PI / 6));
537
- this.context.lineTo(xEnd - linkOptions.arrowSize * Math.cos(angle + Math.PI / 6), yEnd - linkOptions.arrowSize * Math.sin(angle + Math.PI / 6));
538
- this.context.closePath();
539
- this.context.fillStyle = linkOptions.arrowColor;
540
- this.context.fill();
541
- this.context.stroke();
542
- }
543
- if (linkOptions.drawExtraLink) {
544
- linkOptions.drawExtraLink(link, { ...linkOptions, alpha }, state);
545
- }
546
- };
547
- }
548
- function getDrawNode(nodeRenders, textRenders, state) {
549
- return function drawNode(node, index) {
550
- if (!this.context || !node.x || !node.y)
551
- return;
552
- let nodeOptions;
553
- if (this.nodeSettings.cache && this.nodeOptionsCache[node.id]) {
554
- nodeOptions = this.nodeOptionsCache[node.id];
555
- }
556
- else {
557
- nodeOptions = nodeIterationExtractor(node, index, this.nodes, state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
558
- if (this.nodeSettings.cache) {
559
- this.nodeOptionsCache[node.id] = nodeOptions;
560
- }
561
- }
562
- if (nodeOptions.nodeDraw && nodeOptions.textDraw) {
563
- nodeRenders.push(() => {
564
- nodeOptions?.nodeDraw?.(node, nodeOptions, state);
565
- });
566
- textRenders.push(() => {
567
- nodeOptions?.textDraw?.(node, nodeOptions, state);
568
- });
569
- return;
570
- }
571
- let alpha = nodeOptions.alpha;
572
- let color = nodeOptions.color;
573
- let radiusInitial = nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial;
574
- let textAlpha = nodeOptions.textAlpha;
575
- let textSize = nodeOptions.textSize;
576
- let textShiftX = nodeOptions.textShiftX;
577
- let textShiftY = nodeOptions.textShiftY;
578
- let textWeight = nodeOptions.textWeight;
579
- let textWidth = nodeOptions.textWidth;
580
- if (this.highlightedNeighbors && this.highlightedNode) {
581
- /** Not highlighted */
582
- if (!this.highlightedNeighbors.has(node.id) && this.highlightedNode.id != node.id) {
583
- if (nodeOptions.highlightFading) {
584
- const min = this.graphSettings.highlightFadingMin < alpha
585
- ? this.graphSettings.highlightFadingMin
586
- : alpha;
587
- alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
588
- }
589
- if (nodeOptions.highlightTextFading) {
590
- const min = this.graphSettings.highlightTextFadingMin < textAlpha
591
- ? this.graphSettings.highlightTextFadingMin
592
- : textAlpha;
593
- textAlpha = animationByProgress(min, textAlpha - min, 1 - this.highlightProgress);
594
- }
595
- if (nodeOptions.highlightColor) {
596
- const colorRgb = extractRgb(colorToRgb(color));
597
- if (colorRgb) {
598
- const colorRgbFade = fadeRgb(colorRgb, this.graphSettings.highlightColorFadingMin);
599
- const colorFadeAnimation = rgbAnimationByProgress(colorRgb, colorRgbFade, this.highlightProgress);
600
- color = `rgb(${colorFadeAnimation.r}, ${colorFadeAnimation.g}, ${colorFadeAnimation.b})`;
601
- }
602
- }
603
- }
604
- else if (!this.graphSettings.highlightOnlyRoot ||
605
- (this.graphSettings.highlightOnlyRoot && this.highlightedNode.id === node.id)) {
606
- /** Highlighted */
607
- if (nodeOptions.highlightSizing) {
608
- radiusInitial = animationByProgress(radiusInitial, this.graphSettings.highlightSizingAdditional, this.highlightProgress);
609
- }
610
- if (nodeOptions.highlightTextSizing) {
611
- textSize = animationByProgress(textSize, this.graphSettings.highlightTextSizingAdditional, this.highlightProgress);
612
- textShiftX = animationByProgress(textShiftX, this.graphSettings.highlightTextShiftXAdditional, this.highlightProgress);
613
- textShiftY = animationByProgress(textShiftY, this.graphSettings.highlightTextShiftYAdditional, this.highlightProgress);
614
- textWeight = animationByProgress(textWeight, this.graphSettings.highlightTextWeightAdditional, this.highlightProgress);
615
- textWidth = animationByProgress(textWidth, this.graphSettings.highlightTextWidthAdditional, this.highlightProgress);
616
- }
617
- }
618
- }
619
- const radius = nodeRadiusGetter({
620
- radiusFlexible: this.graphSettings.nodeRadiusFlexible,
621
- radiusInitial,
622
- radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
623
- radiusFactor: this.graphSettings.nodeRadiusFactor,
624
- linkCount: node.linkCount,
625
- });
626
- node._radius = radius;
627
- if (!isNodeVisible({
628
- height: this.height,
629
- width: this.width,
630
- x: node.x,
631
- y: node.y,
632
- radius,
633
- transform: this.areaTransform,
634
- })) {
635
- node._visible = false;
636
- return;
637
- }
638
- node._visible = true;
639
- nodeRenders.push(() => {
640
- if (!this.context || !node.x || !node.y)
641
- return;
642
- this.context.beginPath();
643
- this.context.globalAlpha = alpha;
644
- /** circle */
645
- this.context.lineWidth = nodeOptions.borderWidth;
646
- this.context.strokeStyle = nodeOptions.borderColor;
647
- this.context.fillStyle = color;
648
- this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
649
- this.context.fill();
650
- this.context.stroke();
651
- });
652
- if (nodeOptions.nodeExtraDraw) {
653
- nodeRenders.push(() => {
654
- nodeOptions?.nodeExtraDraw?.(node, {
655
- ...nodeOptions,
656
- radius,
657
- alpha,
658
- color,
659
- textAlpha,
660
- textSize,
661
- textShiftX,
662
- textShiftY,
663
- textWeight,
664
- textWidth,
665
- }, state);
666
- });
667
- }
668
- /** text */
669
- if (nodeOptions.textVisible && nodeOptions.text) {
670
- textRenders.push(() => {
671
- if (nodeOptions.textDraw) {
672
- nodeOptions.textDraw(node, {
673
- ...nodeOptions,
674
- radius,
675
- alpha,
676
- color,
677
- textAlpha,
678
- textSize,
679
- textShiftX,
680
- textShiftY,
681
- textWeight,
682
- textWidth,
683
- }, state);
684
- return;
685
- }
686
- if (!this.context || !node.x || !node.y || !nodeOptions.text)
687
- return;
688
- this.context.beginPath();
689
- this.context.globalAlpha = textAlpha;
690
- drawText({
691
- id: node.id,
692
- cachedNodeText: this.cachedNodeText,
693
- context: this.context,
694
- text: nodeOptions.text,
695
- textAlign: nodeOptions.textAlign,
696
- textColor: nodeOptions.textColor,
697
- textFont: nodeOptions.textFont,
698
- textSize,
699
- x: node.x + textShiftX,
700
- y: node.y + radius + textShiftY,
701
- maxWidth: textWidth,
702
- textStyle: nodeOptions.textStyle,
703
- textWeight,
704
- textGap: nodeOptions.textGap,
705
- });
706
- if (nodeOptions.textExtraDraw) {
707
- nodeOptions.textExtraDraw(node, {
708
- ...nodeOptions,
709
- radius,
710
- alpha,
711
- color,
712
- textAlpha,
713
- textSize,
714
- textShiftX,
715
- textShiftY,
716
- textWeight,
717
- textWidth,
718
- }, state);
719
- }
720
- });
721
- }
722
- };
723
- }
724
- if (this.graphSettings.showDrawTime) {
725
- return setDrawTime(draw.bind(this), this.graphSettings.showDrawTimeEveryTick);
726
- }
727
- return draw;
728
- }
729
- initResize() {
730
- if (!this.area)
731
- throw new Error("bad init data");
732
- let initialResizeCall = true;
733
- const abortController = this.eventAbortController;
734
- const observer = new ResizeObserver(() => {
735
- if (initialResizeCall) {
736
- initialResizeCall = false;
737
- return;
738
- }
739
- if (abortController.signal.aborted) {
740
- observer.disconnect();
741
- return;
742
- }
743
- requestAnimationFrame(() => {
744
- this.updateSize();
745
- });
746
- });
747
- document.addEventListener("scroll", this.updateRect.bind(this), {
748
- capture: true,
749
- passive: true,
750
- signal: abortController.signal,
751
- });
752
- observer.observe(this.area);
753
- }
754
- initPointer() {
755
- if (!this.area || !this.nodes || !this.simulation)
756
- throw new Error("bad init data");
757
- /** hover */
758
- this.area.addEventListener("pointermove", (event) => {
759
- let currentNode;
760
- if (this.graphSettings.highlightByHover && !this.isDragging) {
761
- currentNode = nodeByPointerGetter({
762
- graphSettings: this.graphSettings,
763
- areaRect: this.areaRect,
764
- areaTransform: this.areaTransform,
765
- mouseEvent: event,
766
- nodes: this.nodes,
767
- });
768
- if (currentNode && this.highlightedNode !== currentNode) {
769
- this.highlightedNode = currentNode;
770
- this.highlightedNeighbors = new Set(this.highlightedNode?.neighbors ?? []);
771
- this.highlightWorking = true;
772
- if (!this.simulationWorking && !this.highlightDrawing)
773
- requestAnimationFrame(() => {
774
- this.draw();
775
- });
776
- }
777
- else if (!currentNode && this.highlightedNode) {
778
- this.highlightWorking = false;
779
- if (!this.simulationWorking && !this.highlightDrawing)
780
- requestAnimationFrame(() => {
781
- this.draw();
782
- });
783
- }
784
- }
785
- if (!this.listeners.onMove)
786
- return;
787
- if (!currentNode)
788
- currentNode = nodeByPointerGetter({
789
- graphSettings: this.graphSettings,
790
- areaRect: this.areaRect,
791
- areaTransform: this.areaTransform,
792
- mouseEvent: event,
793
- nodes: this.nodes,
794
- });
795
- return void this.listeners.onMove(event, currentNode);
796
- }, {
797
- signal: this.eventAbortController.signal,
798
- });
799
- /** dblclick */
800
- this.area.addEventListener("dblclick", (event) => {
801
- if (!this.listeners.onDoubleClick)
802
- return;
803
- const currentNode = nodeByPointerGetter({
804
- graphSettings: this.graphSettings,
805
- areaRect: this.areaRect,
806
- areaTransform: this.areaTransform,
807
- mouseEvent: event,
808
- nodes: this.nodes,
809
- });
810
- return void this.listeners.onDoubleClick(event, currentNode);
811
- });
812
- /** wheel click */
813
- this.area.addEventListener("mousedown", (event) => {
814
- if (this.isDragging || !this.listeners.onWheelClick || event.button !== 1)
815
- return;
816
- const currentNode = nodeByPointerGetter({
817
- graphSettings: this.graphSettings,
818
- areaRect: this.areaRect,
819
- areaTransform: this.areaTransform,
820
- mouseEvent: event,
821
- nodes: this.nodes,
822
- });
823
- return void this.listeners.onWheelClick(event, currentNode);
824
- }, {
825
- signal: this.eventAbortController.signal,
826
- });
827
- /** click */
828
- this.area.addEventListener("click", (event) => {
829
- if (this.isDragging || !this.listeners.onClick || event.button !== 0)
830
- return;
831
- const currentNode = nodeByPointerGetter({
832
- graphSettings: this.graphSettings,
833
- areaRect: this.areaRect,
834
- areaTransform: this.areaTransform,
835
- mouseEvent: event,
836
- nodes: this.nodes,
837
- });
838
- return void this.listeners.onClick(event, currentNode);
839
- }, {
840
- signal: this.eventAbortController.signal,
841
- });
842
- /** right click */
843
- this.area.addEventListener("contextmenu", (event) => {
844
- if (!this.listeners.onContextMenu)
845
- return;
846
- const currentNode = nodeByPointerGetter({
847
- graphSettings: this.graphSettings,
848
- areaRect: this.areaRect,
849
- areaTransform: this.areaTransform,
850
- mouseEvent: event,
851
- nodes: this.nodes,
852
- });
853
- return void this.listeners.onContextMenu(event, currentNode);
854
- }, {
855
- signal: this.eventAbortController.signal,
856
- });
857
- }
858
- initDnd() {
859
- if (!this.area || !this.nodes || !this.simulation)
860
- throw new Error("bad init data");
861
- select(this.area).call(drag()
862
- .subject((event) => {
863
- if (this.listeners.onDragSubject) {
864
- return this.listeners.onDragSubject(event, this.state);
865
- }
866
- if (!this.areaRect)
867
- return;
868
- const mouseEvent = event.sourceEvent;
869
- const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
870
- let index = 0;
871
- return greatest(this.nodes, (node) => {
872
- if (!node.x || !node.y || (isBoolean(node.drag) && !node.drag))
873
- return undefined;
874
- let radius = node._radius;
875
- if (!radius) {
876
- const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
877
- radius = nodeRadiusGetter({
878
- radiusFlexible: this.graphSettings.nodeRadiusFlexible,
879
- radiusInitial: nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial,
880
- radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
881
- radiusFactor: this.graphSettings.nodeRadiusFactor,
882
- linkCount: node.linkCount,
883
- });
884
- }
885
- index++;
886
- return this.graphSettings.dragPlaceCoefficient(node, pointerX, pointerY, radius);
887
- });
888
- })
889
- .on("start", (event) => {
890
- this.listeners.onStartDragFinished?.(event, this.state);
891
- })
892
- .on("drag", (event) => {
893
- if (!this.isDragging) {
894
- this.isDragging = true;
895
- if (this.simulation)
896
- this.simulation.alphaTarget(0.3).restart();
897
- }
898
- if (!this.areaRect)
899
- return;
900
- const mouseEvent = event.sourceEvent;
901
- const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
902
- event.subject.fx = pointerX;
903
- event.subject.fy = pointerY;
904
- this.listeners.onMoveDragFinished?.(event, this.state);
905
- })
906
- .on("end", (event) => {
907
- this.isDragging = false;
908
- if (!event.active && this.simulation)
909
- this.simulation.alphaTarget(0);
910
- if (this.graphSettings.stickAfterDrag && this.areaRect) {
911
- if (!this.areaRect)
912
- return;
913
- const mouseEvent = event.sourceEvent;
914
- const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
915
- event.subject.fx = pointerX;
916
- event.subject.fy = pointerY;
917
- }
918
- else {
919
- event.subject.fx = null;
920
- event.subject.fy = null;
921
- }
922
- this.listeners.onEndDragFinished?.(event, this.state);
923
- }));
924
- }
925
- initZoom(currentZoom) {
926
- if (!this.area)
927
- throw new Error("bad init data");
928
- const zoomInstance = zoom()
929
- .scaleExtent(this.graphSettings.zoomExtent)
930
- .on("zoom", (event) => {
931
- this.listeners.onZoom?.(event);
932
- this.areaTransform = event.transform;
933
- this.linkOptionsCache = {};
934
- this.nodeOptionsCache = {};
935
- if (!this.simulationWorking && !this.highlightWorking)
936
- requestAnimationFrame(() => this.draw());
937
- });
938
- if (this.graphSettings.translateExtentEnable) {
939
- const coefficient = this.graphSettings.translateExtentCoefficient;
940
- const [coefficientX, coefficientY] = isArray(coefficient)
941
- ? coefficient
942
- : [coefficient, coefficient];
943
- const [[minX = -this.width * coefficientX, minY = -this.height * coefficientY], [maxX = this.width * coefficientX, maxY = this.height * coefficientY],] = this.graphSettings.translateExtent;
944
- zoomInstance.translateExtent([
945
- [minX, minY],
946
- [maxX, maxY],
947
- ]);
948
- }
949
- select(this.area).call(zoomInstance).on("dblclick.zoom", null);
950
- const zoomInitial = currentZoom ?? this.graphSettings.zoomInitial;
951
- this.areaTransform = new ZoomTransform(zoomInitial?.k ?? 1, zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
952
- zoom().transform(select(this.area), this.areaTransform);
243
+ initArea.call(this);
244
+ initSimulation.call(this);
245
+ initDnd.call(this);
246
+ initZoom.call(this);
247
+ initResize.call(this);
248
+ initPointer.call(this);
953
249
  }
954
250
  }
955
251