@krainovsd/graph 0.9.0 → 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 (53) hide show
  1. package/lib/cjs/index.cjs +2633 -1795
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/esm/index.js +12 -3
  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 +451 -224
  8. package/lib/esm/lib/get-controls-info.js.map +1 -1
  9. package/lib/esm/module/GraphCanvas/GraphCanvas.js +39 -927
  10. package/lib/esm/module/GraphCanvas/GraphCanvas.js.map +1 -1
  11. package/lib/esm/module/GraphCanvas/constants/settings.js +70 -47
  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 +2 -2
  26. package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js.map +1 -1
  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/slices/draw-links.js +207 -0
  30. package/lib/esm/module/GraphCanvas/slices/draw-links.js.map +1 -0
  31. package/lib/esm/module/GraphCanvas/slices/draw-nodes.js +342 -0
  32. package/lib/esm/module/GraphCanvas/slices/draw-nodes.js.map +1 -0
  33. package/lib/esm/module/GraphCanvas/{lib/utils → slices}/draw-text.js +23 -6
  34. package/lib/esm/module/GraphCanvas/slices/draw-text.js.map +1 -0
  35. package/lib/esm/module/GraphCanvas/slices/init-area.js +31 -0
  36. package/lib/esm/module/GraphCanvas/slices/init-area.js.map +1 -0
  37. package/lib/esm/module/GraphCanvas/slices/init-dnd.js +62 -0
  38. package/lib/esm/module/GraphCanvas/slices/init-dnd.js.map +1 -0
  39. package/lib/esm/module/GraphCanvas/slices/init-draw.js +84 -0
  40. package/lib/esm/module/GraphCanvas/slices/init-draw.js.map +1 -0
  41. package/lib/esm/module/GraphCanvas/slices/init-pointer.js +211 -0
  42. package/lib/esm/module/GraphCanvas/slices/init-pointer.js.map +1 -0
  43. package/lib/esm/module/GraphCanvas/slices/init-resize.js +28 -0
  44. package/lib/esm/module/GraphCanvas/slices/init-resize.js.map +1 -0
  45. package/lib/esm/module/GraphCanvas/slices/init-simulation.js +172 -0
  46. package/lib/esm/module/GraphCanvas/slices/init-simulation.js.map +1 -0
  47. package/lib/esm/module/GraphCanvas/slices/init-zoom.js +36 -0
  48. package/lib/esm/module/GraphCanvas/slices/init-zoom.js.map +1 -0
  49. package/lib/index.d.ts +169 -132
  50. package/package.json +1 -1
  51. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-radius.js +0 -20
  52. package/lib/esm/module/GraphCanvas/lib/utils/calculate-link-position-by-radius.js.map +0 -1
  53. package/lib/esm/module/GraphCanvas/lib/utils/draw-text.js.map +0 -1
package/lib/cjs/index.cjs CHANGED
@@ -2,12 +2,203 @@
2
2
 
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
4
 
5
+ const d3Force = require('d3-force');
6
+ const d3Zoom = require('d3-zoom');
5
7
  const jsHelpers = require('@krainovsd/js-helpers');
6
8
  const d3Array = require('d3-array');
7
- const d3Drag = require('d3-drag');
8
- const d3Force = require('d3-force');
9
9
  const d3Selection = require('d3-selection');
10
- const d3Zoom = require('d3-zoom');
10
+ const d3Drag = require('d3-drag');
11
+ const lodash = require('lodash');
12
+
13
+ const FORCE_SETTINGS = {
14
+ forces: true,
15
+ centerForce: true,
16
+ chargeForce: true,
17
+ linkForce: true,
18
+ xForce: true,
19
+ yForce: true,
20
+ collideForce: true,
21
+ centerPosition: {},
22
+ centerStrength: 1,
23
+ collideStrength: 0.1,
24
+ collideAdditionalRadius: 4,
25
+ collideIterations: 2,
26
+ collideOffMax: { links: 0, nodes: 0 },
27
+ chargeStrength: -40,
28
+ chargeDistanceMax: Infinity,
29
+ chargeDistanceMin: 1,
30
+ xPosition: 0,
31
+ xStrength: 0.1,
32
+ yPosition: 0,
33
+ yStrength: 0.1,
34
+ linkDistance: 30,
35
+ linkIterations: 1,
36
+ linkStrength: 1,
37
+ collideRadius: null,
38
+ };
39
+ const GRAPH_SETTINGS = {
40
+ zoomExtent: [0.3, 10],
41
+ translateExtent: [[], []],
42
+ translateExtentEnable: true,
43
+ translateExtentCoefficient: [3, 3],
44
+ highlightUpFrames: 5,
45
+ highlightDownFrames: 5,
46
+ dragPlaceCoefficient: dragPlaceCoefficientGetter,
47
+ zoomInitial: null,
48
+ showDrawTime: true,
49
+ showDrawTimeEveryTick: false,
50
+ };
51
+ const NODE_SETTINGS = {
52
+ cache: true,
53
+ highlightByNodeOnlyRoot: true,
54
+ highlightByHoverNode: true,
55
+ nodeRadiusFlexible: true,
56
+ nodeSizeFlexible: true,
57
+ textNodeDebug: false,
58
+ highlightByNodeNodeFading: true,
59
+ highlightByNodeNodeColor: false,
60
+ highlightByNodeNodeSizing: true,
61
+ highlightByNodeTextFading: true,
62
+ highlightByNodeTextSizing: true,
63
+ highlightByLinkNodeFading: false,
64
+ highlightByLinkNodeColor: false,
65
+ highlightByLinkNodeSizing: true,
66
+ highlightByLinkTextFading: false,
67
+ highlightByLinkTextSizing: true,
68
+ nodeRadiusCoefficient: 5,
69
+ nodeRadiusFactor: 1,
70
+ nodeSizeCoefficient: 5,
71
+ nodeSizeFactor: 0.1,
72
+ highlightByNodeNodeSizingAdditional: 0.5,
73
+ highlightByLinkNodeSizingAdditionalCoefficient: 0.35,
74
+ highlightByNodeNodeColorFadingMin: 0.15,
75
+ highlightByNodeTextShiftXAdditional: 0,
76
+ highlightByNodeTextShiftYAdditional: 2,
77
+ highlightByNodeTextSizingAdditional: 1,
78
+ highlightByNodeTextWeightAdditional: 0,
79
+ highlightByNodeTextWidthAdditional: 10,
80
+ highlightByNodeNodeFadingMin: 0.21,
81
+ highlightByNodeTextFadingMin: 0.21,
82
+ highlightByLinkNodeSizingAdditional: 0.5,
83
+ highlightByNodeNodeSizingAdditionalCoefficient: 0.35,
84
+ highlightByLinkNodeColorFadingMin: 0.15,
85
+ highlightByLinkTextShiftXAdditional: 0,
86
+ highlightByLinkTextShiftYAdditional: 2,
87
+ highlightByLinkTextSizingAdditional: 1,
88
+ highlightByLinkTextWeightAdditional: 0,
89
+ highlightByLinkTextWidthAdditional: 10,
90
+ highlightByLinkNodeFadingMin: 0.21,
91
+ highlightByLinkTextFadingMin: 0.21,
92
+ };
93
+ const NODE_OPTIONS = {
94
+ shape: "text",
95
+ height: 10,
96
+ width: 15,
97
+ borderRadius: 0,
98
+ radius: 4,
99
+ alpha: 1,
100
+ textNodeXPadding: 5,
101
+ textNodeYPadding: 3,
102
+ borderWidth: 0.1,
103
+ borderColor: "#000000FF",
104
+ textAlpha: 1,
105
+ textWidth: 20,
106
+ textShiftX: 0,
107
+ textFont: "Arial",
108
+ textAlign: "center",
109
+ textColor: "#333",
110
+ textStyle: "normal",
111
+ textWeight: 500,
112
+ textGap: 0,
113
+ };
114
+ const LINK_SETTINGS = {
115
+ cache: true,
116
+ particles: true,
117
+ particleFlexSpeed: true,
118
+ pretty: true,
119
+ arrow: true,
120
+ highlightByHoverLink: true,
121
+ arrowByHighlight: true,
122
+ highlightByNodeLinkFading: true,
123
+ highlightByNodeArrowFading: true,
124
+ highlightByLinkLinkFading: false,
125
+ highlightByLinkArrowFading: false,
126
+ particleFlexSpeedCoefficient: 4,
127
+ hoverLinkThreshold: 2,
128
+ highlightByNodeLinkFadingMin: 0.21,
129
+ highlightByNodeArrowFadingMin: 0.21,
130
+ highlightByLinkArrowFadingMin: 0.21,
131
+ highlightByLinkLinkFadingMin: 0.21,
132
+ };
133
+ const LINK_OPTIONS = {
134
+ alpha: 1,
135
+ arrowAlpha: 1,
136
+ arrowSize: 2,
137
+ arrowBorderColor: "#000000FF",
138
+ arrowBorderWidth: 0.1,
139
+ particleAlpha: 1,
140
+ particleColor: "#000000FF",
141
+ particleBorderColor: "#000000FF",
142
+ particleBorderWidth: 0.1,
143
+ particleCount: 2,
144
+ particleRadius: 0.5,
145
+ particleSteps: 60,
146
+ };
147
+ const COMMON_SETTINGS = {
148
+ linkColorZoomFar: "#999",
149
+ linkColorZoomNear: "#000000FF",
150
+ linkWidthZoomFar: 1,
151
+ linkWidthZoomNear: 0.1,
152
+ linkWidthZoomBorder: 1,
153
+ linkColorZoomBorder: 1,
154
+ nodeTextScaleMin: 1.5,
155
+ nodeTextScaleMax: 20,
156
+ nodeTextSizeMin: 1.5,
157
+ nodeTextSizeMax: 3.5,
158
+ nodeTextShiftYMin: 2.5,
159
+ nodeTextShiftYMax: 4,
160
+ nodeTextChangeStepCount: 200,
161
+ nodeRadius: 5,
162
+ nodeSize: 5,
163
+ };
164
+
165
+ function forceSettingsGetter(settings, prevSettings) {
166
+ return {
167
+ ...(prevSettings ?? FORCE_SETTINGS),
168
+ ...settings,
169
+ };
170
+ }
171
+
172
+ function graphSettingsGetter(settings, prevSettings) {
173
+ return {
174
+ ...(prevSettings ?? GRAPH_SETTINGS),
175
+ ...settings,
176
+ };
177
+ }
178
+
179
+ function linkSettingsGetter(settings, prevSettings) {
180
+ return { ...(prevSettings ?? LINK_SETTINGS), ...settings };
181
+ }
182
+ function linkOptionsGetter(_, __, ___, state) {
183
+ return {
184
+ ...LINK_OPTIONS,
185
+ drawExtraLink: null,
186
+ drawLink: null,
187
+ color: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkColorZoomBorder
188
+ ? COMMON_SETTINGS.linkColorZoomNear
189
+ : COMMON_SETTINGS.linkColorZoomFar,
190
+ arrowColor: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkColorZoomBorder
191
+ ? COMMON_SETTINGS.linkColorZoomNear
192
+ : COMMON_SETTINGS.linkColorZoomFar,
193
+ width: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkWidthZoomBorder
194
+ ? COMMON_SETTINGS.linkWidthZoomNear
195
+ : COMMON_SETTINGS.linkWidthZoomFar,
196
+ };
197
+ }
198
+
199
+ function listenersGetter(settings) {
200
+ return settings ?? {};
201
+ }
11
202
 
12
203
  function checkType(value, condition) {
13
204
  return condition;
@@ -87,614 +278,330 @@ function rgbAnimationByProgress(start, end, progress) {
87
278
  };
88
279
  }
89
280
 
90
- function forceSettingsGetter(settings, prevSettings) {
91
- return {
92
- ...(prevSettings ?? FORCE_SETTINGS),
93
- ...settings,
94
- };
95
- }
96
-
97
- function graphSettingsGetter(settings, prevSettings) {
98
- return {
99
- ...(prevSettings ?? GRAPH_SETTINGS),
100
- ...settings,
101
- };
102
- }
103
-
104
- function linkSettingsGetter(settings, prevSettings) {
105
- return { ...(prevSettings ?? LINK_SETTINGS), ...settings };
106
- }
107
- function linkOptionsGetter(_, __, ___, state) {
108
- return {
109
- ...LINK_OPTIONS,
110
- drawExtraLink: null,
111
- drawLink: null,
112
- color: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkColorZoomBorder
113
- ? COMMON_SETTINGS.linkColorZoomNear
114
- : COMMON_SETTINGS.linkColorZoomFar,
115
- arrowColor: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkColorZoomBorder
116
- ? COMMON_SETTINGS.linkColorZoomNear
117
- : COMMON_SETTINGS.linkColorZoomFar,
118
- width: state?.areaTransform && state?.areaTransform.k > COMMON_SETTINGS.linkWidthZoomBorder
119
- ? COMMON_SETTINGS.linkWidthZoomNear
120
- : COMMON_SETTINGS.linkWidthZoomFar,
121
- };
122
- }
123
-
124
- function listenersGetter(settings) {
125
- return settings ?? {};
126
- }
127
-
128
- function nodeSettingsGetter(settings, prevNodeSettings) {
129
- return {
130
- ...(prevNodeSettings ?? NODE_SETTINGS),
131
- idGetter: nodeIdGetter,
132
- ...settings,
133
- };
134
- }
135
- const color = colorGetter();
136
- function nodeOptionsGetter(node, _, __, state) {
137
- const { textShiftY, textSize } = nodeTextSizeGetter(state?.areaTransform);
138
- return {
139
- ...NODE_OPTIONS,
140
- nodeDraw: null,
141
- nodeExtraDraw: null,
142
- textDraw: null,
143
- textExtraDraw: null,
144
- color: color(String(node.group ?? "_DEFAULT")),
145
- textVisible: Boolean(state?.areaTransform && state.areaTransform.k > COMMON_SETTINGS.nodeTextScaleMin),
146
- text: node.name ?? node.id.toString(),
147
- textShiftY,
148
- textSize,
149
- };
150
- }
151
- function nodeTextSizeGetter(transform) {
152
- let textSize = COMMON_SETTINGS.nodeTextSizeMax;
153
- let textShiftY = COMMON_SETTINGS.nodeTextShiftYMax;
154
- if (transform) {
155
- const scaleStepCoefficient = (COMMON_SETTINGS.nodeTextScaleMax - COMMON_SETTINGS.nodeTextScaleMin) /
156
- COMMON_SETTINGS.nodeTextChangeStepCount;
157
- const textStepCoefficient = (COMMON_SETTINGS.nodeTextSizeMax - COMMON_SETTINGS.nodeTextSizeMin) /
158
- COMMON_SETTINGS.nodeTextChangeStepCount;
159
- const shiftStepCoefficient = (COMMON_SETTINGS.nodeTextShiftYMax - COMMON_SETTINGS.nodeTextShiftYMin) /
160
- COMMON_SETTINGS.nodeTextChangeStepCount;
161
- if (transform.k >= COMMON_SETTINGS.nodeTextScaleMax) {
162
- textSize = COMMON_SETTINGS.nodeTextSizeMin;
163
- textShiftY = COMMON_SETTINGS.nodeTextShiftYMin;
164
- }
165
- else if (transform.k > COMMON_SETTINGS.nodeTextScaleMin) {
166
- const transformSteps = (transform.k - COMMON_SETTINGS.nodeTextScaleMin) / scaleStepCoefficient;
167
- textSize -= transformSteps * textStepCoefficient;
168
- textShiftY -= transformSteps * shiftStepCoefficient;
169
- }
170
- }
171
- return { textSize, textShiftY };
172
- }
173
- function nodeRadiusGetter({ radiusFlexible, radiusInitial, linkCount, radiusCoefficient, radiusFactor, }) {
174
- return ((radiusFlexible && linkCount ? linkCount / radiusCoefficient : 0) * radiusFactor + radiusInitial);
175
- }
176
- function nodeIdGetter(node) {
177
- return node.id;
178
- }
179
-
180
- function pointerGetter(mouseEvent, areaRect, areaTransform) {
181
- const clientX = "clientX" in mouseEvent
182
- ? mouseEvent.clientX
183
- : (mouseEvent.touches[0]?.clientX ?? mouseEvent.changedTouches[0]?.clientX);
184
- const clientY = "clientX" in mouseEvent
185
- ? mouseEvent.clientY
186
- : (mouseEvent.touches[0]?.clientY ?? mouseEvent.changedTouches[0]?.clientY);
187
- const px = (clientX - areaRect.left - areaTransform.x) / areaTransform.k;
188
- const py = (clientY - areaRect.top - areaTransform.y) / areaTransform.k;
189
- return [px, py];
190
- }
191
-
192
- function isOverlapsNode(node, radius, pointerX, pointerY) {
193
- if (node.x == undefined || node.y == undefined)
194
- return false;
195
- const isOverX = node.x - radius <= pointerX && pointerX <= node.x + radius;
196
- const isOverY = node.y - radius <= pointerY && pointerY <= node.y + radius;
197
- return isOverX && isOverY;
198
- }
199
-
200
- function dragPlaceCoefficientGetter(node, pointerX, pointerY, radius) {
201
- if (!node.x || !node.y)
202
- return undefined;
203
- if (isOverlapsNode(node, radius, pointerX, pointerY))
204
- return node.index;
205
- }
206
-
207
- function linkIterationExtractor(link, i, links, state, option, optionConstantGetter) {
208
- let customOptions;
209
- let constantOptions;
210
- if (typeof option === "function")
211
- customOptions = option(link, i, links, state);
212
- else
213
- customOptions = option;
214
- if (customOptions && typeof customOptions === "object" && !Array.isArray(customOptions)) {
215
- for (const key in customOptions) {
216
- if (customOptions[key] === undefined)
217
- delete customOptions[key];
218
- }
219
- }
220
- if (optionConstantGetter) {
221
- if (typeof optionConstantGetter === "function")
222
- constantOptions = optionConstantGetter(link, i, links, state);
223
- else
224
- constantOptions = optionConstantGetter;
225
- if (constantOptions &&
226
- typeof constantOptions === "object" &&
227
- !Array.isArray(constantOptions) &&
228
- checkType(customOptions, customOptions === undefined ||
229
- (typeof customOptions === "object" && !Array.isArray(customOptions)))) {
230
- return {
231
- ...constantOptions,
232
- ...customOptions,
233
- };
234
- }
235
- }
236
- return customOptions;
237
- }
238
-
239
- function nodeByPointerGetter({ areaRect, areaTransform, mouseEvent, nodes, graphSettings, }) {
240
- if (!areaRect)
241
- return undefined;
242
- const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
243
- return d3Array.greatest(nodes, (node) => {
244
- const radius = node._radius ??
245
- nodeRadiusGetter({
246
- radiusFlexible: graphSettings.nodeRadiusFlexible,
247
- radiusInitial: graphSettings.nodeRadiusInitial,
248
- radiusCoefficient: graphSettings.nodeRadiusCoefficient,
249
- radiusFactor: graphSettings.nodeRadiusFactor,
250
- linkCount: node.linkCount,
251
- });
252
- if (isOverlapsNode(node, radius, pointerX, pointerY))
253
- return node.index;
254
- });
281
+ function getForceControls(keys) {
282
+ const FORCE_CONTROLS = [
283
+ {
284
+ id: "forces",
285
+ initialValue: FORCE_SETTINGS.forces,
286
+ label: "Физика",
287
+ type: "checkbox",
288
+ },
289
+ {
290
+ id: "collideForce",
291
+ type: "checkbox",
292
+ label: "Сила отталкивания",
293
+ initialValue: FORCE_SETTINGS.collideForce,
294
+ },
295
+ {
296
+ id: "centerForce",
297
+ type: "checkbox",
298
+ label: "Сила центрирования",
299
+ initialValue: FORCE_SETTINGS.centerForce,
300
+ },
301
+ {
302
+ id: "chargeForce",
303
+ type: "checkbox",
304
+ label: "Сила дистанции",
305
+ initialValue: FORCE_SETTINGS.chargeForce,
306
+ },
307
+ {
308
+ id: "linkForce",
309
+ type: "checkbox",
310
+ label: "Сила связей",
311
+ initialValue: FORCE_SETTINGS.linkForce,
312
+ },
313
+ {
314
+ id: "xForce",
315
+ type: "checkbox",
316
+ label: "Сила X",
317
+ initialValue: FORCE_SETTINGS.xForce,
318
+ },
319
+ {
320
+ id: "yForce",
321
+ type: "checkbox",
322
+ label: "Сила Y",
323
+ initialValue: FORCE_SETTINGS.yForce,
324
+ },
325
+ {
326
+ id: "centerStrength",
327
+ initialValue: FORCE_SETTINGS.centerStrength,
328
+ label: "Граница центра",
329
+ max: 1,
330
+ min: 0,
331
+ step: 0.01,
332
+ type: "range",
333
+ },
334
+ {
335
+ id: "collideStrength",
336
+ initialValue: FORCE_SETTINGS.collideStrength,
337
+ label: "Сила отталкивания",
338
+ max: 1,
339
+ min: 0,
340
+ step: 0.01,
341
+ type: "range",
342
+ },
343
+ {
344
+ id: "collideAdditionalRadius",
345
+ initialValue: FORCE_SETTINGS.collideAdditionalRadius,
346
+ label: "Радиус отталкивания",
347
+ max: 300,
348
+ min: 0,
349
+ step: 0.1,
350
+ type: "range",
351
+ },
352
+ {
353
+ id: "collideIterations",
354
+ initialValue: FORCE_SETTINGS.collideIterations,
355
+ label: "Итерации отталкивания",
356
+ max: 10,
357
+ min: 0,
358
+ step: 1,
359
+ type: "range",
360
+ },
361
+ {
362
+ id: "chargeStrength",
363
+ initialValue: FORCE_SETTINGS.chargeStrength ,
364
+ label: "Дистанция",
365
+ max: 0,
366
+ min: -300,
367
+ step: 0.1,
368
+ type: "range",
369
+ },
370
+ {
371
+ type: "range",
372
+ id: "xPosition",
373
+ min: 0,
374
+ max: 2000,
375
+ step: 1,
376
+ label: "Координата притяжения X",
377
+ initialValue: FORCE_SETTINGS.xPosition ,
378
+ },
379
+ {
380
+ type: "range",
381
+ id: "yPosition",
382
+ min: 0,
383
+ max: 2000,
384
+ step: 1,
385
+ label: "Координата притяжения Y",
386
+ initialValue: FORCE_SETTINGS.yPosition ,
387
+ },
388
+ {
389
+ type: "range",
390
+ id: "xStrength",
391
+ min: 0,
392
+ max: 1,
393
+ step: 0.01,
394
+ label: "Гравитация X",
395
+ initialValue: FORCE_SETTINGS.xStrength ,
396
+ },
397
+ {
398
+ type: "range",
399
+ id: "yStrength",
400
+ min: 0,
401
+ max: 1,
402
+ step: 0.01,
403
+ label: "Гравитация Y",
404
+ initialValue: FORCE_SETTINGS.yStrength ,
405
+ },
406
+ {
407
+ type: "range",
408
+ id: "linkStrength",
409
+ min: 0,
410
+ max: 1,
411
+ step: 0.01,
412
+ label: "Натяжение связей",
413
+ initialValue: FORCE_SETTINGS.linkStrength ,
414
+ },
415
+ {
416
+ type: "range",
417
+ id: "linkDistance",
418
+ min: 0,
419
+ max: 300,
420
+ step: 0.1,
421
+ label: "Расстояние связей",
422
+ initialValue: FORCE_SETTINGS.linkDistance ,
423
+ },
424
+ ];
425
+ return keys ? FORCE_CONTROLS.filter((control) => keys.includes(control.id)) : FORCE_CONTROLS;
255
426
  }
256
-
257
- function nodeIterationExtractor(node, i, nodes, state, option, optionConstantGetter) {
258
- let customOptions;
259
- let constantOptions;
260
- if (typeof option === "function")
261
- customOptions = option(node, i, nodes, state);
262
- else
263
- customOptions = option;
264
- if (customOptions && typeof customOptions === "object" && !Array.isArray(customOptions)) {
265
- for (const key in customOptions) {
266
- if (customOptions[key] === undefined)
267
- delete customOptions[key];
268
- }
269
- }
270
- if (optionConstantGetter) {
271
- if (typeof optionConstantGetter === "function")
272
- constantOptions = optionConstantGetter(node, i, nodes, state);
273
- else
274
- constantOptions = optionConstantGetter;
275
- if (constantOptions &&
276
- typeof constantOptions === "object" &&
277
- !Array.isArray(constantOptions) &&
278
- checkType(customOptions, customOptions === undefined ||
279
- (typeof customOptions === "object" && !Array.isArray(customOptions)))) {
280
- return {
281
- ...constantOptions,
282
- ...customOptions,
283
- };
284
- }
285
- }
286
- return customOptions;
427
+ function getGraphControls(keys) {
428
+ const GRAPH_CONTROLS = [
429
+ {
430
+ id: "highlightDownFrames",
431
+ initialValue: GRAPH_SETTINGS.highlightDownFrames,
432
+ max: 60,
433
+ min: 1,
434
+ step: 1,
435
+ type: "range",
436
+ label: "Скорость отмены анимации в кадрах",
437
+ },
438
+ {
439
+ id: "highlightUpFrames",
440
+ initialValue: GRAPH_SETTINGS.highlightUpFrames,
441
+ max: 60,
442
+ min: 1,
443
+ step: 1,
444
+ type: "range",
445
+ label: "Скорость применения анимации в кадрах",
446
+ },
447
+ ];
448
+ return keys ? GRAPH_CONTROLS.filter((control) => keys.includes(control.id)) : GRAPH_CONTROLS;
287
449
  }
288
-
289
- const SPACE = " ";
290
- function drawText({ context, id, textAlign, textColor, textFont, textStyle, textGap, textWeight, textSize, text, x, y, cachedNodeText, maxWidth, }) {
291
- context.font = `${textStyle} normal ${textWeight} ${textSize}px ${textFont}`;
292
- context.fillStyle = textColor;
293
- context.textAlign = textAlign;
294
- if (cachedNodeText[id] != undefined) {
295
- cachedNodeText[id].forEach((line, index) => {
296
- context.fillText(line, x, y + index * textSize + index * textGap);
297
- });
298
- return;
299
- }
300
- if (maxWidth == undefined || context.measureText(text).width <= maxWidth) {
301
- cachedNodeText[id] = [text];
302
- context.fillText(text, x, y);
303
- return;
304
- }
305
- const spaceWidth = context.measureText(" ").width;
306
- const lines = [];
307
- let lineWidth = 0;
308
- let line = "";
309
- for (const word of text.split(" ")) {
310
- const size = context.measureText(word).width;
311
- lineWidth += size;
312
- if (line === "") {
313
- line = word;
314
- continue;
315
- }
316
- if (lineWidth > maxWidth) {
317
- lineWidth = 0;
318
- lines.push(line);
319
- line = word;
320
- }
321
- else {
322
- lineWidth += spaceWidth;
323
- line += `${SPACE}${word}`;
324
- }
325
- }
326
- if (line !== "")
327
- lines.push(line);
328
- cachedNodeText[id] = lines;
329
- lines.forEach((line, index) => {
330
- context.fillText(line, x, y + index * textSize + index * textGap);
331
- });
332
- }
333
-
334
- function calculateLinkPositionByRadius(link, arrowSize = 0) {
335
- const source = link.source;
336
- const target = link.target;
337
- if (typeof source != "object" || typeof target != "object")
338
- return null;
339
- const dx = (target.x ?? 0) - (source.x ?? 0);
340
- const dy = (target.y ?? 0) - (source.y ?? 0);
341
- const dr = Math.sqrt(dx * dx + dy * dy);
342
- const sourceRadius = source._radius ?? 0;
343
- const targetRadius = (target._radius ?? 0) + (arrowSize > 0 ? arrowSize * 0.85 : 0);
344
- return {
345
- x1: (source.x ?? 0) + (dx * sourceRadius) / dr,
346
- y1: (source.y ?? 0) + (dy * sourceRadius) / dr,
347
- x2: (target.x ?? 0) - (dx * targetRadius) / dr,
348
- y2: (target.y ?? 0) - (dy * targetRadius) / dr,
349
- };
350
- }
351
-
352
- function animationByProgress(initial, additional, progress) {
353
- const max = initial + additional;
354
- let current = initial + (max - initial) * progress;
355
- if (current > max)
356
- current = max;
357
- if (current < initial)
358
- current = initial;
359
- return current;
360
- }
361
-
362
- const ADDITIONAL_VIEWPORT = 10;
363
- function isNodeVisible(opts) {
364
- const left = -opts.transform.x / opts.transform.k;
365
- const right = (opts.width - opts.transform.x) / opts.transform.k;
366
- const top = -opts.transform.y / opts.transform.k;
367
- const bottom = (opts.height - opts.transform.y) / opts.transform.k;
368
- return (left < opts.x + opts.radius + ADDITIONAL_VIEWPORT &&
369
- opts.x - opts.radius - ADDITIONAL_VIEWPORT < right &&
370
- top < opts.y + opts.radius + ADDITIONAL_VIEWPORT &&
371
- opts.y - opts.radius - ADDITIONAL_VIEWPORT < bottom);
372
- }
373
-
374
- function getParticlePosition(opts) {
375
- if (opts.particle.wait > 0) {
376
- opts.particle.wait--;
377
- return;
378
- }
379
- const remainingSteps = opts.totalSteps - opts.particle.step;
380
- const progress = opts.particle.step / opts.totalSteps;
381
- if (remainingSteps === 0) {
382
- opts.particle.x = opts.xEnd;
383
- opts.particle.y = opts.yEnd;
384
- opts.particle.step = 0;
385
- return;
386
- }
387
- const dx = opts.xEnd - opts.xStart;
388
- const dy = opts.yEnd - opts.yStart;
389
- const newX = opts.xStart + dx * progress;
390
- const newY = opts.yStart + dy * progress;
391
- opts.particle.x = newX;
392
- opts.particle.y = newY;
393
- opts.particle.step++;
394
- }
395
-
396
- /* eslint-disable id-length */
397
- function linkByPointerGetter({ areaRect, areaTransform, mouseEvent, links, graphSettings, }) {
398
- if (!areaRect)
399
- return undefined;
400
- const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
401
- return d3Array.greatest(links, (link) => {
402
- if (isNearLink(pointerX, pointerY, link, graphSettings.hoverLinkThreshold))
403
- return link.index;
404
- });
405
- }
406
- function isNearLink(mouseX, mouseY, link, threshold = 2) {
407
- if (!jsHelpers.isObject(link.source) || !jsHelpers.isObject(link.target))
408
- return false;
409
- const x1 = link.source.x;
410
- const y1 = link.source.y;
411
- const x2 = link.target.x;
412
- const y2 = link.target.y;
413
- const distance = distanceToLine(mouseX, mouseY, x1, y1, x2, y2);
414
- return distance <= threshold;
415
- }
416
- function distanceToLine(x, y, x1, y1, x2, y2) {
417
- const A = x - x1;
418
- const B = y - y1;
419
- const C = x2 - x1;
420
- const D = y2 - y1;
421
- const dot = A * C + B * D;
422
- const lenSq = C * C + D * D;
423
- let param = -1;
424
- if (lenSq !== 0) {
425
- param = dot / lenSq;
426
- }
427
- let xx, yy;
428
- if (param < 0) {
429
- xx = x1;
430
- yy = y1;
431
- }
432
- else if (param > 1) {
433
- xx = x2;
434
- yy = y2;
435
- }
436
- else {
437
- xx = x1 + param * C;
438
- yy = y1 + param * D;
439
- }
440
- const dx = x - xx;
441
- const dy = y - yy;
442
- return Math.sqrt(dx * dx + dy * dy);
443
- }
444
-
445
- const FORCE_SETTINGS = {
446
- centerPosition: {},
447
- centerStrength: 1,
448
- collideStrength: 0.1,
449
- collideAdditionalRadius: 4,
450
- collideIterations: 2,
451
- collideOffMax: { links: 0, nodes: 0 },
452
- collideOn: true,
453
- chargeStrength: -40,
454
- chargeDistanceMax: Infinity,
455
- chargeDistanceMin: 1,
456
- xForce: 0,
457
- xStrength: 0.1,
458
- yForce: 0,
459
- yStrength: 0.1,
460
- linkDistance: 30,
461
- linkIterations: 1,
462
- linkStrength: 1,
463
- collideRadius: null,
464
- };
465
- const GRAPH_SETTINGS = {
466
- zoomExtent: [0.3, 10],
467
- translateExtent: [[], []],
468
- translateExtentEnable: true,
469
- translateExtentCoefficient: [3, 3],
470
- highlightByNodeNodeSizingAdditional: 0.5,
471
- highlightByNodeNodeColorFadingMin: 0.15,
472
- highlightByNodeTextShiftXAdditional: 0,
473
- highlightByNodeTextShiftYAdditional: 2,
474
- highlightByNodeTextSizingAdditional: 1,
475
- highlightByNodeTextWeightAdditional: 0,
476
- highlightByNodeTextWidthAdditional: 10,
477
- highlightByNodeLinkFadingMin: 0.21,
478
- highlightByNodeNodeFadingMin: 0.21,
479
- highlightByNodeTextFadingMin: 0.21,
480
- highlightByNodeArrowFadingMin: 0.21,
481
- highlightByNodeOnlyRoot: true,
482
- highlightByLinkNodeSizingAdditional: 0.5,
483
- highlightByLinkNodeColorFadingMin: 0.15,
484
- highlightByLinkTextShiftXAdditional: 0,
485
- highlightByLinkTextShiftYAdditional: 2,
486
- highlightByLinkTextSizingAdditional: 1,
487
- highlightByLinkTextWeightAdditional: 0,
488
- highlightByLinkTextWidthAdditional: 10,
489
- highlightByLinkLinkFadingMin: 0.21,
490
- highlightByLinkNodeFadingMin: 0.21,
491
- highlightByLinkTextFadingMin: 0.21,
492
- highlightByLinkArrowFadingMin: 0.21,
493
- highlightByLinkOnlyRoot: true,
494
- stickAfterDrag: false,
495
- highlightByHoverNode: true,
496
- highlightByHoverLink: true,
497
- hoverLinkThreshold: 2,
498
- highlightDownStep: 0.2,
499
- highlightUpStep: 0.2,
500
- dragPlaceCoefficient: dragPlaceCoefficientGetter,
501
- nodeRadiusInitial: 4,
502
- nodeRadiusCoefficient: 5,
503
- nodeRadiusFactor: 1,
504
- nodeRadiusFlexible: true,
505
- zoomInitial: null,
506
- showDrawTime: true,
507
- showDrawTimeEveryTick: false,
508
- };
509
- const NODE_SETTINGS = {
510
- cache: false,
511
- };
512
- const NODE_OPTIONS = {
513
- alpha: 1,
514
- textAlpha: 1,
515
- borderColor: "#000000FF",
516
- borderWidth: 0.1,
517
- textWidth: 20,
518
- textShiftX: 0,
519
- textFont: "Arial",
520
- textAlign: "center",
521
- textColor: "#333",
522
- radius: 4,
523
- textStyle: "normal",
524
- textWeight: 500,
525
- textGap: 1,
526
- highlightByNodeNodeFading: true,
527
- highlightByNodeNodeColor: false,
528
- highlightByNodeNodeSizing: true,
529
- highlightByNodeTextFading: true,
530
- highlightByNodeTextSizing: true,
531
- highlightByLinkNodeFading: false,
532
- highlightByLinkNodeColor: false,
533
- highlightByLinkNodeSizing: true,
534
- highlightByLinkTextFading: false,
535
- highlightByLinkTextSizing: true,
536
- };
537
- const LINK_SETTINGS = {
538
- cache: false,
539
- particles: true,
540
- };
541
- const LINK_OPTIONS = {
542
- alpha: 1,
543
- highlightByNodeLinkFading: true,
544
- highlightByNodeArrowFading: true,
545
- highlightByLinkLinkFading: false,
546
- highlightByLinkArrowFading: false,
547
- pretty: true,
548
- arrow: true,
549
- arrowAlpha: 1,
550
- arrowSize: 2,
551
- arrowReverseAppear: true,
552
- particleAlpha: 1,
553
- particleColor: "#000000FF",
554
- particleCount: 2,
555
- particleRadius: 0.5,
556
- particleSteps: 60,
557
- };
558
- const COMMON_SETTINGS = {
559
- linkColorZoomFar: "#999",
560
- linkColorZoomNear: "#000000FF",
561
- linkWidthZoomFar: 1,
562
- linkWidthZoomNear: 0.1,
563
- linkWidthZoomBorder: 1,
564
- linkColorZoomBorder: 1,
565
- nodeTextScaleMin: 1.5,
566
- nodeTextScaleMax: 20,
567
- nodeTextSizeMin: 1.5,
568
- nodeTextSizeMax: 3.5,
569
- nodeTextShiftYMin: 2.5,
570
- nodeTextShiftYMax: 4,
571
- nodeTextChangeStepCount: 200,
572
- };
573
-
574
- function getForceControls(keys) {
575
- const FORCE_CONTROLS = [
450
+ function getNodeSettingControls(keys) {
451
+ const NODE_SETTINGS_CONTROLS = [
576
452
  {
577
- id: "centerStrength",
578
- initialValue: FORCE_SETTINGS.centerStrength,
579
- label: "Граница центра",
580
- max: 1,
581
- min: 0,
582
- step: 0.01,
583
- type: "range",
453
+ id: "cache",
454
+ type: "checkbox",
455
+ initialValue: NODE_SETTINGS.cache,
456
+ label: "Кеширование вычисляемых настроек",
584
457
  },
585
458
  {
586
- id: "collideStrength",
587
- initialValue: FORCE_SETTINGS.collideStrength,
588
- label: "Сила отталкивания",
589
- max: 1,
590
- min: 0,
591
- step: 0.01,
592
- type: "range",
459
+ id: "highlightByNodeOnlyRoot",
460
+ type: "checkbox",
461
+ initialValue: NODE_SETTINGS.highlightByNodeOnlyRoot,
462
+ label: "Дополнительная анимация только главное цели при анимации ноды",
593
463
  },
594
464
  {
595
- id: "collideAdditionalRadius",
596
- initialValue: FORCE_SETTINGS.collideAdditionalRadius,
597
- label: "Радиус отталкивания",
598
- max: 300,
599
- min: 0,
600
- step: 0.1,
601
- type: "range",
465
+ id: "highlightByHoverNode",
466
+ type: "checkbox",
467
+ initialValue: NODE_SETTINGS.highlightByHoverNode,
468
+ label: "Анимации при наведении на ноду",
602
469
  },
603
470
  {
604
- id: "collideIterations",
605
- initialValue: FORCE_SETTINGS.collideIterations,
606
- label: "Итерации отталкивания",
607
- max: 10,
608
- min: 0,
609
- step: 1,
610
- type: "range",
471
+ id: "nodeRadiusFlexible",
472
+ type: "checkbox",
473
+ initialValue: NODE_SETTINGS.nodeRadiusFlexible,
474
+ label: "Гибкий радиус ноды",
611
475
  },
612
476
  {
613
- id: "chargeStrength",
614
- initialValue: FORCE_SETTINGS.chargeStrength ,
615
- label: "Гравитация",
616
- max: 0,
617
- min: -300,
618
- step: 0.1,
619
- type: "range",
477
+ id: "nodeSizeFlexible",
478
+ type: "checkbox",
479
+ initialValue: NODE_SETTINGS.nodeSizeFlexible,
480
+ label: "Гибкий размер ноды",
620
481
  },
621
482
  {
622
- type: "range",
623
- id: "xForce",
624
- min: 0,
625
- max: 1,
626
- step: 0.01,
627
- label: "Граница X",
628
- initialValue: FORCE_SETTINGS.xForce ,
483
+ id: "textNodeDebug",
484
+ type: "checkbox",
485
+ initialValue: NODE_SETTINGS.textNodeDebug,
486
+ label: "Отладка текстовой ноды",
629
487
  },
630
488
  {
631
- type: "range",
632
- id: "yForce",
633
- min: 0,
634
- max: 1,
635
- step: 0.01,
636
- label: "Граница Y",
637
- initialValue: FORCE_SETTINGS.yForce ,
489
+ id: "highlightByNodeNodeFading",
490
+ type: "checkbox",
491
+ initialValue: NODE_SETTINGS.highlightByNodeNodeFading,
492
+ label: "Затухание при анимации ноды",
638
493
  },
639
494
  {
640
- type: "range",
641
- id: "xStrength",
642
- min: 0,
643
- max: 1,
644
- step: 0.01,
645
- label: "Гравитация X",
646
- initialValue: FORCE_SETTINGS.xStrength ,
495
+ id: "highlightByNodeNodeColor",
496
+ type: "checkbox",
497
+ initialValue: NODE_SETTINGS.highlightByNodeNodeColor,
498
+ label: "Затухание цвета при анимации ноды",
647
499
  },
648
500
  {
649
- type: "range",
650
- id: "yStrength",
651
- min: 0,
652
- max: 1,
653
- step: 0.01,
654
- label: "Гравитация Y",
655
- initialValue: FORCE_SETTINGS.yStrength ,
501
+ id: "highlightByNodeNodeSizing",
502
+ type: "checkbox",
503
+ initialValue: NODE_SETTINGS.highlightByNodeNodeSizing,
504
+ label: "Изменение размера при анимации ноды",
505
+ },
506
+ {
507
+ id: "highlightByNodeTextFading",
508
+ type: "checkbox",
509
+ initialValue: NODE_SETTINGS.highlightByNodeTextFading,
510
+ label: "Затухание текста при анимации ноды",
511
+ },
512
+ {
513
+ id: "highlightByNodeTextSizing",
514
+ type: "checkbox",
515
+ initialValue: NODE_SETTINGS.highlightByNodeTextSizing,
516
+ label: "Изменение размера текста при анимации ноды",
517
+ },
518
+ {
519
+ id: "highlightByLinkNodeFading",
520
+ type: "checkbox",
521
+ initialValue: NODE_SETTINGS.highlightByLinkNodeFading,
522
+ label: "Затухание при анимации связи",
523
+ },
524
+ {
525
+ id: "highlightByLinkNodeColor",
526
+ type: "checkbox",
527
+ initialValue: NODE_SETTINGS.highlightByLinkNodeColor,
528
+ label: "Затухание цвета при анимации связи",
529
+ },
530
+ {
531
+ id: "highlightByLinkNodeSizing",
532
+ type: "checkbox",
533
+ initialValue: NODE_SETTINGS.highlightByLinkNodeSizing,
534
+ label: "Изменение размера при анимации связи",
535
+ },
536
+ {
537
+ id: "highlightByLinkTextFading",
538
+ type: "checkbox",
539
+ initialValue: NODE_SETTINGS.highlightByLinkTextFading,
540
+ label: "Затухание текста при анимации связи",
541
+ },
542
+ {
543
+ id: "highlightByLinkTextSizing",
544
+ type: "checkbox",
545
+ initialValue: NODE_SETTINGS.highlightByLinkTextSizing,
546
+ label: "Изменение размера текста при анимации связи",
656
547
  },
657
548
  {
549
+ id: "nodeRadiusCoefficient",
550
+ initialValue: NODE_SETTINGS.nodeRadiusCoefficient,
551
+ max: 100,
552
+ min: 0.1,
553
+ step: 0.1,
658
554
  type: "range",
659
- id: "linkStrength",
660
- min: 0,
661
- max: 1,
662
- step: 0.01,
663
- label: "Натяжение связей",
664
- initialValue: FORCE_SETTINGS.linkStrength ,
555
+ label: "Количество связей для увеличения радиуса",
665
556
  },
666
557
  {
558
+ id: "nodeRadiusFactor",
559
+ initialValue: NODE_SETTINGS.nodeRadiusFactor,
560
+ max: 50,
561
+ min: 0.1,
562
+ step: 0.1,
667
563
  type: "range",
668
- id: "linkDistance",
669
- min: 0,
670
- max: 300,
564
+ label: "Коэффициент увеличения радиуса",
565
+ },
566
+ {
567
+ id: "nodeSizeCoefficient",
568
+ initialValue: NODE_SETTINGS.nodeSizeCoefficient,
569
+ max: 50,
570
+ min: 0.1,
671
571
  step: 0.1,
672
- label: "Расстояние связей",
673
- initialValue: FORCE_SETTINGS.linkDistance ,
572
+ type: "range",
573
+ label: "Количество связей для увеличения размера",
674
574
  },
675
575
  {
676
- id: "collideOn",
677
- type: "checkbox",
678
- initialValue: FORCE_SETTINGS.collideOn,
679
- label: "Отталкивание",
576
+ id: "nodeSizeFactor",
577
+ initialValue: NODE_SETTINGS.nodeSizeFactor,
578
+ max: 20,
579
+ min: 0.01,
580
+ step: 0.01,
581
+ type: "range",
582
+ label: "Коэффициент увеличения размера",
680
583
  },
681
- ];
682
- return keys ? FORCE_CONTROLS.filter((control) => keys.includes(control.id)) : FORCE_CONTROLS;
683
- }
684
- function getGraphControls(keys) {
685
- const GRAPH_CONTROLS = [
686
584
  {
687
585
  id: "highlightByNodeNodeSizingAdditional",
688
- initialValue: GRAPH_SETTINGS.highlightByNodeNodeSizingAdditional,
586
+ initialValue: NODE_SETTINGS.highlightByNodeNodeSizingAdditional,
689
587
  max: 10,
690
588
  min: 0.1,
691
589
  step: 0.01,
692
590
  type: "range",
693
591
  label: "Дополнительный размер при анимации ноды",
694
592
  },
593
+ {
594
+ id: "highlightByNodeNodeSizingAdditionalCoefficient",
595
+ initialValue: NODE_SETTINGS.highlightByNodeNodeSizingAdditionalCoefficient,
596
+ max: 10,
597
+ min: 0.01,
598
+ step: 0.01,
599
+ type: "range",
600
+ label: "Дополнительный размер квадрата при анимации ноды",
601
+ },
695
602
  {
696
603
  id: "highlightByNodeNodeColorFadingMin",
697
- initialValue: GRAPH_SETTINGS.highlightByNodeNodeColorFadingMin,
604
+ initialValue: NODE_SETTINGS.highlightByNodeNodeColorFadingMin,
698
605
  max: 1,
699
606
  min: 0.01,
700
607
  step: 0.01,
@@ -703,7 +610,7 @@ function getGraphControls(keys) {
703
610
  },
704
611
  {
705
612
  id: "highlightByNodeTextShiftXAdditional",
706
- initialValue: GRAPH_SETTINGS.highlightByNodeTextShiftXAdditional,
613
+ initialValue: NODE_SETTINGS.highlightByNodeTextShiftXAdditional,
707
614
  max: 50,
708
615
  min: 0,
709
616
  step: 0.1,
@@ -712,7 +619,7 @@ function getGraphControls(keys) {
712
619
  },
713
620
  {
714
621
  id: "highlightByNodeTextShiftYAdditional",
715
- initialValue: GRAPH_SETTINGS.highlightByNodeTextShiftYAdditional,
622
+ initialValue: NODE_SETTINGS.highlightByNodeTextShiftYAdditional,
716
623
  max: 50,
717
624
  min: 0,
718
625
  step: 0.1,
@@ -721,7 +628,7 @@ function getGraphControls(keys) {
721
628
  },
722
629
  {
723
630
  id: "highlightByNodeTextSizingAdditional",
724
- initialValue: GRAPH_SETTINGS.highlightByNodeTextSizingAdditional,
631
+ initialValue: NODE_SETTINGS.highlightByNodeTextSizingAdditional,
725
632
  max: 10,
726
633
  min: 0.1,
727
634
  step: 0.1,
@@ -730,7 +637,7 @@ function getGraphControls(keys) {
730
637
  },
731
638
  {
732
639
  id: "highlightByNodeTextWeightAdditional",
733
- initialValue: GRAPH_SETTINGS.highlightByNodeTextWeightAdditional,
640
+ initialValue: NODE_SETTINGS.highlightByNodeTextWeightAdditional,
734
641
  max: 1000,
735
642
  min: 0,
736
643
  step: 100,
@@ -739,34 +646,16 @@ function getGraphControls(keys) {
739
646
  },
740
647
  {
741
648
  id: "highlightByNodeTextWidthAdditional",
742
- initialValue: GRAPH_SETTINGS.highlightByNodeTextWidthAdditional,
649
+ initialValue: NODE_SETTINGS.highlightByNodeTextWidthAdditional,
743
650
  max: 100,
744
651
  min: 0,
745
652
  step: 0.1,
746
653
  type: "range",
747
654
  label: "Увеличение ширины текста при анимации ноды",
748
655
  },
749
- {
750
- id: "highlightByNodeTextFadingMin",
751
- initialValue: GRAPH_SETTINGS.highlightByNodeTextFadingMin,
752
- max: 1,
753
- min: 0,
754
- step: 0.01,
755
- type: "range",
756
- label: "Граница затухания текста при анимации ноды",
757
- },
758
- {
759
- id: "highlightByNodeLinkFadingMin",
760
- initialValue: GRAPH_SETTINGS.highlightByNodeLinkFadingMin,
761
- max: 1,
762
- min: 0,
763
- step: 0.01,
764
- type: "range",
765
- label: "Граница затухания связи при анимации ноды",
766
- },
767
656
  {
768
657
  id: "highlightByNodeNodeFadingMin",
769
- initialValue: GRAPH_SETTINGS.highlightByNodeNodeFadingMin,
658
+ initialValue: NODE_SETTINGS.highlightByNodeNodeFadingMin,
770
659
  max: 1,
771
660
  min: 0,
772
661
  step: 0.01,
@@ -774,32 +663,35 @@ function getGraphControls(keys) {
774
663
  label: "Граница затухания ноды при анимации ноды",
775
664
  },
776
665
  {
777
- id: "highlightByNodeArrowFadingMin",
778
- initialValue: GRAPH_SETTINGS.highlightByNodeArrowFadingMin,
666
+ id: "highlightByNodeTextFadingMin",
667
+ initialValue: NODE_SETTINGS.highlightByNodeTextFadingMin,
779
668
  max: 1,
780
669
  min: 0,
781
670
  step: 0.01,
782
671
  type: "range",
783
- label: "Граница затухания стрелки при анимации ноды",
784
- },
785
- {
786
- id: "highlightByNodeOnlyRoot",
787
- type: "checkbox",
788
- initialValue: GRAPH_SETTINGS.highlightByNodeOnlyRoot,
789
- label: "Дополнительная анимация только главное цели при анимации ноды",
672
+ label: "Граница затухания текста при анимации ноды",
790
673
  },
791
674
  {
792
675
  id: "highlightByLinkNodeSizingAdditional",
793
- initialValue: GRAPH_SETTINGS.highlightByLinkNodeSizingAdditional,
676
+ initialValue: NODE_SETTINGS.highlightByLinkNodeSizingAdditional,
794
677
  max: 10,
795
678
  min: 0.1,
796
679
  step: 0.01,
797
680
  type: "range",
798
681
  label: "Дополнительный размер при анимации связи",
799
682
  },
683
+ {
684
+ id: "highlightByLinkNodeSizingAdditionalCoefficient",
685
+ initialValue: NODE_SETTINGS.highlightByLinkNodeSizingAdditionalCoefficient,
686
+ max: 10,
687
+ min: 0.01,
688
+ step: 0.01,
689
+ type: "range",
690
+ label: "Дополнительный размер квадрата при анимации связи",
691
+ },
800
692
  {
801
693
  id: "highlightByLinkNodeColorFadingMin",
802
- initialValue: GRAPH_SETTINGS.highlightByLinkNodeColorFadingMin,
694
+ initialValue: NODE_SETTINGS.highlightByLinkNodeColorFadingMin,
803
695
  max: 1,
804
696
  min: 0.01,
805
697
  step: 0.01,
@@ -808,7 +700,7 @@ function getGraphControls(keys) {
808
700
  },
809
701
  {
810
702
  id: "highlightByLinkTextShiftXAdditional",
811
- initialValue: GRAPH_SETTINGS.highlightByLinkTextShiftXAdditional,
703
+ initialValue: NODE_SETTINGS.highlightByLinkTextShiftXAdditional,
812
704
  max: 50,
813
705
  min: 0,
814
706
  step: 0.1,
@@ -817,7 +709,7 @@ function getGraphControls(keys) {
817
709
  },
818
710
  {
819
711
  id: "highlightByLinkTextShiftYAdditional",
820
- initialValue: GRAPH_SETTINGS.highlightByLinkTextShiftYAdditional,
712
+ initialValue: NODE_SETTINGS.highlightByLinkTextShiftYAdditional,
821
713
  max: 50,
822
714
  min: 0,
823
715
  step: 0.1,
@@ -826,7 +718,7 @@ function getGraphControls(keys) {
826
718
  },
827
719
  {
828
720
  id: "highlightByLinkTextSizingAdditional",
829
- initialValue: GRAPH_SETTINGS.highlightByLinkTextSizingAdditional,
721
+ initialValue: NODE_SETTINGS.highlightByLinkTextSizingAdditional,
830
722
  max: 10,
831
723
  min: 0.1,
832
724
  step: 0.1,
@@ -835,7 +727,7 @@ function getGraphControls(keys) {
835
727
  },
836
728
  {
837
729
  id: "highlightByLinkTextWeightAdditional",
838
- initialValue: GRAPH_SETTINGS.highlightByLinkTextWeightAdditional,
730
+ initialValue: NODE_SETTINGS.highlightByLinkTextWeightAdditional,
839
731
  max: 1000,
840
732
  min: 0,
841
733
  step: 100,
@@ -844,34 +736,16 @@ function getGraphControls(keys) {
844
736
  },
845
737
  {
846
738
  id: "highlightByLinkTextWidthAdditional",
847
- initialValue: GRAPH_SETTINGS.highlightByLinkTextWidthAdditional,
739
+ initialValue: NODE_SETTINGS.highlightByLinkTextWidthAdditional,
848
740
  max: 100,
849
741
  min: 0,
850
742
  step: 0.1,
851
743
  type: "range",
852
744
  label: "Увеличение ширины текста при анимации связи",
853
745
  },
854
- {
855
- id: "highlightByLinkTextFadingMin",
856
- initialValue: GRAPH_SETTINGS.highlightByLinkTextFadingMin,
857
- max: 1,
858
- min: 0,
859
- step: 0.01,
860
- type: "range",
861
- label: "Граница затухания текста при анимации связи",
862
- },
863
- {
864
- id: "highlightByLinkLinkFadingMin",
865
- initialValue: GRAPH_SETTINGS.highlightByLinkLinkFadingMin,
866
- max: 1,
867
- min: 0,
868
- step: 0.01,
869
- type: "range",
870
- label: "Граница затухания связи при анимации связи",
871
- },
872
746
  {
873
747
  id: "highlightByLinkNodeFadingMin",
874
- initialValue: GRAPH_SETTINGS.highlightByLinkNodeFadingMin,
748
+ initialValue: NODE_SETTINGS.highlightByLinkNodeFadingMin,
875
749
  max: 1,
876
750
  min: 0,
877
751
  step: 0.01,
@@ -879,88 +753,69 @@ function getGraphControls(keys) {
879
753
  label: "Граница затухания ноды при анимации связи",
880
754
  },
881
755
  {
882
- id: "highlightByLinkArrowFadingMin",
883
- initialValue: GRAPH_SETTINGS.highlightByLinkArrowFadingMin,
756
+ id: "highlightByLinkTextFadingMin",
757
+ initialValue: NODE_SETTINGS.highlightByLinkTextFadingMin,
884
758
  max: 1,
885
759
  min: 0,
886
760
  step: 0.01,
887
761
  type: "range",
888
- label: "Граница затухания стрелки при анимации связи",
762
+ label: "Граница затухания текста при анимации связи",
889
763
  },
764
+ ];
765
+ return keys
766
+ ? NODE_SETTINGS_CONTROLS.filter((control) => keys.includes(control.id))
767
+ : NODE_SETTINGS_CONTROLS;
768
+ }
769
+ function getNodeOptionsControls(keys) {
770
+ const NODE_CONTROLS = [
890
771
  {
891
- id: "highlightByLinkOnlyRoot",
892
- type: "checkbox",
893
- initialValue: GRAPH_SETTINGS.highlightByLinkOnlyRoot,
894
- label: "Дополнительная анимация только главное цели при анимации связи",
772
+ id: "shape",
773
+ type: "select",
774
+ initialValue: NODE_OPTIONS.shape,
775
+ label: "Форма",
776
+ options: [
777
+ { label: "Картинки", value: "icon" },
778
+ { label: "Круг", value: "circle" },
779
+ { label: "Прямоугольник", value: "square" },
780
+ { label: "Текст", value: "text" },
781
+ ],
895
782
  },
896
783
  {
897
- id: "highlightDownStep",
898
- initialValue: GRAPH_SETTINGS.highlightDownStep,
899
- max: 1,
900
- min: 0.01,
901
- step: 0.01,
784
+ id: "radius",
902
785
  type: "range",
903
- label: "Скорость отмены анимации",
786
+ max: 50,
787
+ min: 1,
788
+ step: 1,
789
+ label: "Радиус",
790
+ initialValue: NODE_OPTIONS.radius,
904
791
  },
905
792
  {
906
- id: "highlightUpStep",
907
- initialValue: GRAPH_SETTINGS.highlightUpStep,
908
- max: 1,
909
- min: 0.01,
910
- step: 0.01,
793
+ id: "width",
911
794
  type: "range",
912
- label: "Скорость применения анимации",
913
- },
914
- {
915
- id: "nodeRadiusInitial",
916
- initialValue: GRAPH_SETTINGS.nodeRadiusInitial,
917
795
  max: 50,
918
- min: 0.1,
919
- step: 0.1,
920
- type: "range",
921
- label: "Изначальный радиус ноды",
796
+ min: 1,
797
+ step: 1,
798
+ label: "Ширина",
799
+ initialValue: NODE_OPTIONS.width,
922
800
  },
923
801
  {
924
- id: "nodeRadiusCoefficient",
925
- initialValue: GRAPH_SETTINGS.nodeRadiusCoefficient,
926
- max: 100,
927
- min: 0.1,
928
- step: 0.1,
802
+ id: "height",
929
803
  type: "range",
930
- label: "Количество связей для увеличения радиуса",
931
- },
932
- {
933
- id: "nodeRadiusFactor",
934
- initialValue: GRAPH_SETTINGS.nodeRadiusFactor,
935
804
  max: 50,
936
- min: 0.1,
937
- step: 0.1,
938
- type: "range",
939
- label: "Коэффициент увеличения радиуса",
940
- },
941
- {
942
- id: "stickAfterDrag",
943
- type: "checkbox",
944
- initialValue: GRAPH_SETTINGS.stickAfterDrag,
945
- label: "Фиксировании ноды после перетаскивания",
946
- },
947
- {
948
- id: "highlightByHoverNode",
949
- type: "checkbox",
950
- initialValue: GRAPH_SETTINGS.highlightByHoverNode,
951
- label: "Анимации при наведении",
805
+ min: 1,
806
+ step: 1,
807
+ label: "Высота",
808
+ initialValue: NODE_OPTIONS.height,
952
809
  },
953
810
  {
954
- id: "nodeRadiusFlexible",
955
- type: "checkbox",
956
- initialValue: GRAPH_SETTINGS.nodeRadiusFlexible,
957
- label: "Гибкий радиус ноды",
811
+ id: "borderRadius",
812
+ type: "range",
813
+ max: 50,
814
+ min: 0,
815
+ step: 1,
816
+ label: "Радиус границы",
817
+ initialValue: NODE_OPTIONS.borderRadius,
958
818
  },
959
- ];
960
- return keys ? GRAPH_CONTROLS.filter((control) => keys.includes(control.id)) : GRAPH_CONTROLS;
961
- }
962
- function getNodeControls(keys) {
963
- const NODE_CONTROLS = [
964
819
  {
965
820
  id: "alpha",
966
821
  type: "range",
@@ -970,15 +825,6 @@ function getNodeControls(keys) {
970
825
  label: "Прозрачность",
971
826
  initialValue: NODE_OPTIONS.alpha,
972
827
  },
973
- {
974
- id: "radius",
975
- type: "range",
976
- max: 50,
977
- min: 1,
978
- step: 1,
979
- label: "Радиус",
980
- initialValue: NODE_OPTIONS.radius,
981
- },
982
828
  {
983
829
  id: "borderWidth",
984
830
  type: "range",
@@ -1000,6 +846,33 @@ function getNodeControls(keys) {
1000
846
  initialValue: "#000000FF",
1001
847
  label: "Цвет",
1002
848
  },
849
+ {
850
+ id: "textNodeXPadding",
851
+ type: "range",
852
+ max: 30,
853
+ min: 0,
854
+ step: 0.1,
855
+ label: "Отступы по оси X у текстовой ноды внутри контейнера",
856
+ initialValue: NODE_OPTIONS.textNodeXPadding,
857
+ },
858
+ {
859
+ id: "textNodeYPadding",
860
+ type: "range",
861
+ max: 30,
862
+ min: 0,
863
+ step: 0.1,
864
+ label: "Отступы по оси Y у текстовой ноды внутри контейнера",
865
+ initialValue: NODE_OPTIONS.textNodeYPadding,
866
+ },
867
+ {
868
+ id: "textSize",
869
+ type: "range",
870
+ max: 30,
871
+ min: 0,
872
+ step: 0.1,
873
+ label: "Размер текста",
874
+ initialValue: 0,
875
+ },
1003
876
  {
1004
877
  id: "textAlpha",
1005
878
  type: "range",
@@ -1055,227 +928,2065 @@ function getNodeControls(keys) {
1055
928
  initialValue: NODE_OPTIONS.textGap,
1056
929
  },
1057
930
  {
1058
- id: "highlightByNodeNodeFading",
1059
- type: "checkbox",
1060
- initialValue: NODE_OPTIONS.highlightByNodeNodeFading,
1061
- label: "Затухание при анимации ноды",
931
+ id: "textColor",
932
+ type: "color",
933
+ initialValue: NODE_OPTIONS.textColor,
934
+ label: "Цвет текста",
1062
935
  },
936
+ ];
937
+ return keys ? NODE_CONTROLS.filter((control) => keys.includes(control.id)) : NODE_CONTROLS;
938
+ }
939
+ function getLinkSettingsControls(keys) {
940
+ const LINK_SETTINGS_CONTROLS = [
1063
941
  {
1064
- id: "highlightByNodeNodeColor",
942
+ id: "cache",
1065
943
  type: "checkbox",
1066
- initialValue: NODE_OPTIONS.highlightByNodeNodeColor,
1067
- label: "Затухание цвета при анимации ноды",
944
+ label: "Кеширование вычисляемых настроек",
945
+ initialValue: LINK_SETTINGS.cache,
1068
946
  },
1069
947
  {
1070
- id: "highlightByNodeNodeSizing",
948
+ id: "particles",
1071
949
  type: "checkbox",
1072
- initialValue: NODE_OPTIONS.highlightByNodeNodeSizing,
1073
- label: "Изменение размера при анимации ноды",
950
+ label: "Частицы",
951
+ initialValue: LINK_SETTINGS.particles,
1074
952
  },
1075
953
  {
1076
- id: "highlightByNodeTextFading",
954
+ id: "particleFlexSpeed",
1077
955
  type: "checkbox",
1078
- initialValue: NODE_OPTIONS.highlightByNodeTextFading,
1079
- label: "Затухание текста при анимации ноды",
956
+ label: "Гибкий расчет скорости частицы",
957
+ initialValue: LINK_SETTINGS.particleFlexSpeed,
1080
958
  },
1081
959
  {
1082
- id: "highlightByNodeTextSizing",
960
+ id: "pretty",
1083
961
  type: "checkbox",
1084
- initialValue: NODE_OPTIONS.highlightByNodeTextSizing,
1085
- label: "Изменение размера текста при анимации ноды",
962
+ label: "Обсчет красивого соединения",
963
+ initialValue: LINK_SETTINGS.pretty,
1086
964
  },
1087
965
  {
1088
- id: "highlightByLinkNodeFading",
966
+ id: "arrow",
1089
967
  type: "checkbox",
1090
- initialValue: NODE_OPTIONS.highlightByLinkNodeFading,
1091
- label: "Затухание при анимации связи",
968
+ label: "Стрелка",
969
+ initialValue: LINK_SETTINGS.arrow,
1092
970
  },
1093
971
  {
1094
- id: "highlightByLinkNodeColor",
972
+ id: "arrowByHighlight",
1095
973
  type: "checkbox",
1096
- initialValue: NODE_OPTIONS.highlightByLinkNodeColor,
1097
- label: "Затухание цвета при анимации связи",
974
+ label: "Появление стрелки при анимации",
975
+ initialValue: LINK_SETTINGS.arrowByHighlight,
1098
976
  },
1099
977
  {
1100
- id: "highlightByLinkNodeSizing",
978
+ id: "highlightByHoverLink",
1101
979
  type: "checkbox",
1102
- initialValue: NODE_OPTIONS.highlightByLinkNodeSizing,
1103
- label: "Изменение размера при анимации связи",
980
+ label: "Анимация при наведении на связь",
981
+ initialValue: LINK_SETTINGS.highlightByHoverLink,
1104
982
  },
1105
983
  {
1106
- id: "highlightByLinkTextFading",
984
+ id: "highlightByNodeLinkFading",
1107
985
  type: "checkbox",
1108
- initialValue: NODE_OPTIONS.highlightByLinkTextFading,
1109
- label: "Затухание текста при анимации связи",
986
+ label: "Затухание при анимации ноды",
987
+ initialValue: LINK_SETTINGS.highlightByNodeLinkFading,
1110
988
  },
1111
989
  {
1112
- id: "highlightByLinkTextSizing",
990
+ id: "highlightByNodeArrowFading",
1113
991
  type: "checkbox",
1114
- initialValue: NODE_OPTIONS.highlightByLinkTextSizing,
1115
- label: "Изменение размера текста при анимации связи",
992
+ label: "Затухание стрелки при анимации ноды",
993
+ initialValue: LINK_SETTINGS.highlightByNodeArrowFading,
994
+ },
995
+ {
996
+ id: "highlightByLinkLinkFading",
997
+ type: "checkbox",
998
+ label: "Затухание при анимации связи",
999
+ initialValue: LINK_SETTINGS.highlightByLinkLinkFading,
1000
+ },
1001
+ {
1002
+ id: "highlightByLinkArrowFading",
1003
+ type: "checkbox",
1004
+ label: "Затухание стрелки при анимации связи",
1005
+ initialValue: LINK_SETTINGS.highlightByLinkArrowFading,
1006
+ },
1007
+ {
1008
+ id: "particleFlexSpeedCoefficient",
1009
+ initialValue: LINK_SETTINGS.particleFlexSpeedCoefficient,
1010
+ max: 10,
1011
+ min: 0.1,
1012
+ step: 0.1,
1013
+ type: "range",
1014
+ label: "Коэффициент скорости частицы относительно расстояния",
1015
+ },
1016
+ {
1017
+ id: "hoverLinkThreshold",
1018
+ initialValue: LINK_SETTINGS.hoverLinkThreshold,
1019
+ max: 10,
1020
+ min: 0,
1021
+ step: 0.01,
1022
+ type: "range",
1023
+ label: "Расширение границы связи для курсора",
1024
+ },
1025
+ {
1026
+ id: "highlightByNodeLinkFadingMin",
1027
+ initialValue: LINK_SETTINGS.highlightByNodeLinkFadingMin,
1028
+ max: 1,
1029
+ min: 0,
1030
+ step: 0.01,
1031
+ type: "range",
1032
+ label: "Граница затухания связи при анимации ноды",
1033
+ },
1034
+ {
1035
+ id: "highlightByNodeArrowFadingMin",
1036
+ initialValue: LINK_SETTINGS.highlightByNodeArrowFadingMin,
1037
+ max: 1,
1038
+ min: 0,
1039
+ step: 0.01,
1040
+ type: "range",
1041
+ label: "Граница затухания стрелки при анимации ноды",
1042
+ },
1043
+ {
1044
+ id: "highlightByLinkLinkFadingMin",
1045
+ initialValue: LINK_SETTINGS.highlightByLinkLinkFadingMin,
1046
+ max: 1,
1047
+ min: 0,
1048
+ step: 0.01,
1049
+ type: "range",
1050
+ label: "Граница затухания связи при анимации связи",
1051
+ },
1052
+ {
1053
+ id: "highlightByLinkArrowFadingMin",
1054
+ initialValue: LINK_SETTINGS.highlightByLinkArrowFadingMin,
1055
+ max: 1,
1056
+ min: 0,
1057
+ step: 0.01,
1058
+ type: "range",
1059
+ label: "Граница затухания стрелки при анимации связи",
1116
1060
  },
1117
1061
  ];
1118
- return keys ? NODE_CONTROLS.filter((control) => keys.includes(control.id)) : NODE_CONTROLS;
1062
+ return keys
1063
+ ? LINK_SETTINGS_CONTROLS.filter((control) => keys.includes(control.id))
1064
+ : LINK_SETTINGS_CONTROLS;
1065
+ }
1066
+ function getLinkOptionsControls(keys) {
1067
+ const LINK_CONTROLS = [
1068
+ {
1069
+ id: "alpha",
1070
+ type: "range",
1071
+ max: 1,
1072
+ min: 0,
1073
+ step: 0.1,
1074
+ label: "Прозрачность",
1075
+ initialValue: LINK_OPTIONS.alpha,
1076
+ },
1077
+ {
1078
+ id: "width",
1079
+ type: "range",
1080
+ max: 10,
1081
+ min: 0.1,
1082
+ step: 0.1,
1083
+ label: "Толщина",
1084
+ initialValue: 0.1,
1085
+ },
1086
+ {
1087
+ id: "arrowAlpha",
1088
+ type: "range",
1089
+ max: 1,
1090
+ min: 0,
1091
+ step: 0.1,
1092
+ label: "Прозрачность стрелки",
1093
+ initialValue: LINK_OPTIONS.arrowAlpha,
1094
+ },
1095
+ {
1096
+ id: "arrowSize",
1097
+ type: "range",
1098
+ max: 10,
1099
+ min: 0.1,
1100
+ step: 0.1,
1101
+ label: "Размер стрелки",
1102
+ initialValue: LINK_OPTIONS.arrowSize,
1103
+ },
1104
+ {
1105
+ id: "arrowBorderWidth",
1106
+ type: "range",
1107
+ max: 10,
1108
+ min: 0.1,
1109
+ step: 0.1,
1110
+ label: "Размер границы стрелки",
1111
+ initialValue: LINK_OPTIONS.arrowBorderWidth,
1112
+ },
1113
+ {
1114
+ id: "particleBorderWidth",
1115
+ type: "range",
1116
+ max: 10,
1117
+ min: 0.1,
1118
+ step: 0.1,
1119
+ label: "Размер границы частиц",
1120
+ initialValue: LINK_OPTIONS.particleBorderWidth,
1121
+ },
1122
+ {
1123
+ id: "particleAlpha",
1124
+ type: "range",
1125
+ max: 1,
1126
+ min: 0,
1127
+ step: 0.1,
1128
+ label: "Прозрачность частиц",
1129
+ initialValue: LINK_OPTIONS.particleAlpha,
1130
+ },
1131
+ {
1132
+ id: "particleRadius",
1133
+ type: "range",
1134
+ max: 10,
1135
+ min: 0.1,
1136
+ step: 0.1,
1137
+ label: "Радиус частиц",
1138
+ initialValue: LINK_OPTIONS.particleRadius,
1139
+ },
1140
+ {
1141
+ id: "particleCount",
1142
+ type: "range",
1143
+ max: 20,
1144
+ min: 1,
1145
+ step: 1,
1146
+ label: "Количество частиц",
1147
+ initialValue: LINK_OPTIONS.particleCount,
1148
+ },
1149
+ {
1150
+ id: "particleSteps",
1151
+ type: "range",
1152
+ max: 200,
1153
+ min: 1,
1154
+ step: 1,
1155
+ label: "Количество кадров у частиц",
1156
+ initialValue: LINK_OPTIONS.particleSteps,
1157
+ },
1158
+ {
1159
+ id: "color",
1160
+ type: "color",
1161
+ initialValue: "#000000FF",
1162
+ label: "Цвет",
1163
+ },
1164
+ {
1165
+ id: "arrowColor",
1166
+ type: "color",
1167
+ initialValue: "#000000FF",
1168
+ label: "Цвет стрелки",
1169
+ },
1170
+ {
1171
+ id: "arrowBorderColor",
1172
+ type: "color",
1173
+ initialValue: LINK_OPTIONS.arrowBorderColor,
1174
+ label: "Цвет границы стрелки",
1175
+ },
1176
+ {
1177
+ id: "particleColor",
1178
+ type: "color",
1179
+ initialValue: LINK_OPTIONS.particleColor,
1180
+ label: "Цвет частиц",
1181
+ },
1182
+ {
1183
+ id: "particleBorderColor",
1184
+ type: "color",
1185
+ initialValue: LINK_OPTIONS.particleBorderColor,
1186
+ label: "Цвет границы частиц",
1187
+ },
1188
+ ];
1189
+ return keys ? LINK_CONTROLS.filter((control) => keys.includes(control.id)) : LINK_CONTROLS;
1190
+ }
1191
+
1192
+ let countTime = 0;
1193
+ let countOperation = 0;
1194
+ function setDrawTime(cb, showEveryTick) {
1195
+ return () => {
1196
+ const start = performance.now();
1197
+ cb();
1198
+ const end = performance.now();
1199
+ const result = end - start;
1200
+ countTime += result;
1201
+ countOperation++;
1202
+ if (showEveryTick) {
1203
+ const average = countTime / countOperation;
1204
+ // eslint-disable-next-line no-console
1205
+ console.log("average draw: ", average, "current draw: ", result);
1206
+ }
1207
+ };
1208
+ }
1209
+ function getDrawTime() {
1210
+ const average = countTime / countOperation;
1211
+ // eslint-disable-next-line no-console
1212
+ console.log("average draw: ", average);
1213
+ }
1214
+ function resetDrawTime() {
1215
+ countTime = 0;
1216
+ countOperation = 0;
1217
+ }
1218
+
1219
+ function nodeSettingsGetter(settings, prevNodeSettings) {
1220
+ return {
1221
+ ...(prevNodeSettings ?? NODE_SETTINGS),
1222
+ idGetter: nodeIdGetter,
1223
+ ...settings,
1224
+ };
1225
+ }
1226
+ const color = colorGetter();
1227
+ function nodeOptionsGetter(node, _, __, state) {
1228
+ const { textShiftY, textSize } = nodeTextSizeGetter(state?.areaTransform);
1229
+ return {
1230
+ ...NODE_OPTIONS,
1231
+ nodeDraw: null,
1232
+ nodeExtraDraw: null,
1233
+ textDraw: null,
1234
+ textExtraDraw: null,
1235
+ color: color(String(node.group ?? "_DEFAULT")),
1236
+ textVisible: Boolean(state?.areaTransform && state.areaTransform.k > COMMON_SETTINGS.nodeTextScaleMin),
1237
+ text: node.name ?? node.id.toString(),
1238
+ textShiftY,
1239
+ textSize,
1240
+ };
1241
+ }
1242
+ function nodeTextSizeGetter(transform) {
1243
+ let textSize = COMMON_SETTINGS.nodeTextSizeMax;
1244
+ let textShiftY = COMMON_SETTINGS.nodeTextShiftYMax;
1245
+ if (transform) {
1246
+ const scaleStepCoefficient = (COMMON_SETTINGS.nodeTextScaleMax - COMMON_SETTINGS.nodeTextScaleMin) /
1247
+ COMMON_SETTINGS.nodeTextChangeStepCount;
1248
+ const textStepCoefficient = (COMMON_SETTINGS.nodeTextSizeMax - COMMON_SETTINGS.nodeTextSizeMin) /
1249
+ COMMON_SETTINGS.nodeTextChangeStepCount;
1250
+ const shiftStepCoefficient = (COMMON_SETTINGS.nodeTextShiftYMax - COMMON_SETTINGS.nodeTextShiftYMin) /
1251
+ COMMON_SETTINGS.nodeTextChangeStepCount;
1252
+ if (transform.k >= COMMON_SETTINGS.nodeTextScaleMax) {
1253
+ textSize = COMMON_SETTINGS.nodeTextSizeMin;
1254
+ textShiftY = COMMON_SETTINGS.nodeTextShiftYMin;
1255
+ }
1256
+ else if (transform.k > COMMON_SETTINGS.nodeTextScaleMin) {
1257
+ const transformSteps = (transform.k - COMMON_SETTINGS.nodeTextScaleMin) / scaleStepCoefficient;
1258
+ textSize -= transformSteps * textStepCoefficient;
1259
+ textShiftY -= transformSteps * shiftStepCoefficient;
1260
+ }
1261
+ }
1262
+ return { textSize, textShiftY };
1263
+ }
1264
+ function nodeRadiusGetter({ radiusFlexible, radiusInitial, linkCount, radiusCoefficient, radiusFactor, }) {
1265
+ return ((radiusFlexible && linkCount ? linkCount / radiusCoefficient : 0) * radiusFactor + radiusInitial);
1266
+ }
1267
+ function nodeSizeGetter({ heightInitial, linkCount, sizeCoefficient, sizeFactor, sizeFlexible, widthInitial, }) {
1268
+ let widthCoefficient = 1;
1269
+ let heightCoefficient = 1;
1270
+ if (sizeFlexible && linkCount != undefined) {
1271
+ widthCoefficient += (linkCount / sizeCoefficient) * sizeFactor;
1272
+ heightCoefficient += (linkCount / sizeCoefficient) * sizeFactor;
1273
+ }
1274
+ return { width: widthInitial * widthCoefficient, height: heightInitial * heightCoefficient };
1275
+ }
1276
+ function nodeIdGetter(node) {
1277
+ return node.id;
1278
+ }
1279
+
1280
+ function pointerGetter(mouseEvent, areaRect, areaTransform) {
1281
+ const clientX = "clientX" in mouseEvent
1282
+ ? mouseEvent.clientX
1283
+ : (mouseEvent.touches[0]?.clientX ?? mouseEvent.changedTouches[0]?.clientX);
1284
+ const clientY = "clientX" in mouseEvent
1285
+ ? mouseEvent.clientY
1286
+ : (mouseEvent.touches[0]?.clientY ?? mouseEvent.changedTouches[0]?.clientY);
1287
+ const px = (clientX - areaRect.left - areaTransform.x) / areaTransform.k;
1288
+ const py = (clientY - areaRect.top - areaTransform.y) / areaTransform.k;
1289
+ return [px, py];
1290
+ }
1291
+
1292
+ function isOverlapsNode(node, pointerX, pointerY, radius) {
1293
+ if (node.x == undefined || node.y == undefined)
1294
+ return false;
1295
+ switch (node._shape) {
1296
+ case "circle": {
1297
+ const nodeRadius = node._radius ?? radius ?? 5;
1298
+ const isOverX = node.x - nodeRadius <= pointerX && pointerX <= node.x + nodeRadius;
1299
+ const isOverY = node.y - nodeRadius <= pointerY && pointerY <= node.y + nodeRadius;
1300
+ return isOverX && isOverY;
1301
+ }
1302
+ case "square":
1303
+ case "text": {
1304
+ const width = node._width ?? 5;
1305
+ const height = node._height ?? 5;
1306
+ const borderRadius = node._borderRadius ?? 0;
1307
+ const overlaps = Math.abs(pointerX - node.x) <= width / 2 && Math.abs(pointerY - node.y) <= height / 2;
1308
+ if (node._shape !== "text" && borderRadius != undefined && borderRadius > 0 && overlaps) {
1309
+ return isCursorOverRoundedRect(pointerX, pointerY, node.x, node.y, width / 2, height / 2, borderRadius);
1310
+ }
1311
+ return overlaps;
1312
+ }
1313
+ default: {
1314
+ const nodeRadius = node._radius ?? radius ?? 5;
1315
+ const isOverX = node.x - nodeRadius <= pointerX && pointerX <= node.x + nodeRadius;
1316
+ const isOverY = node.y - nodeRadius <= pointerY && pointerY <= node.y + nodeRadius;
1317
+ return isOverX && isOverY;
1318
+ }
1319
+ }
1320
+ }
1321
+ function isCursorOverRoundedRect(pointerX, pointerY, x, y, halfWidth, halfHeight, borderRadius) {
1322
+ const left = x - halfWidth;
1323
+ const right = x + halfWidth;
1324
+ const top = y - halfHeight;
1325
+ const bottom = y + halfHeight;
1326
+ if (pointerX < left + borderRadius && pointerY < top + borderRadius) {
1327
+ const dx = pointerX - (left + borderRadius);
1328
+ const dy = pointerY - (top + borderRadius);
1329
+ return dx * dx + dy * dy <= borderRadius * borderRadius;
1330
+ }
1331
+ else if (pointerX > right - borderRadius && pointerY < top + borderRadius) {
1332
+ const dx = pointerX - (right - borderRadius);
1333
+ const dy = pointerY - (top + borderRadius);
1334
+ return dx * dx + dy * dy <= borderRadius * borderRadius;
1335
+ }
1336
+ else if (pointerX < left + borderRadius && pointerY > bottom - borderRadius) {
1337
+ const dx = pointerX - (left + borderRadius);
1338
+ const dy = pointerY - (bottom - borderRadius);
1339
+ return dx * dx + dy * dy <= borderRadius * borderRadius;
1340
+ }
1341
+ else if (pointerX > right - borderRadius && pointerY > bottom - borderRadius) {
1342
+ const dx = pointerX - (right - borderRadius);
1343
+ const dy = pointerY - (bottom - borderRadius);
1344
+ return dx * dx + dy * dy <= borderRadius * borderRadius;
1345
+ }
1346
+ return true;
1347
+ }
1348
+
1349
+ function dragPlaceCoefficientGetter(node, pointerX, pointerY) {
1350
+ if (!node.x || !node.y)
1351
+ return undefined;
1352
+ if (isOverlapsNode(node, pointerX, pointerY, undefined))
1353
+ return node.index;
1354
+ }
1355
+
1356
+ function linkIterationExtractor(link, i, links, state, option, optionConstantGetter) {
1357
+ let customOptions;
1358
+ let constantOptions;
1359
+ if (typeof option === "function")
1360
+ customOptions = option(link, i, links, state);
1361
+ else
1362
+ customOptions = option;
1363
+ if (customOptions && typeof customOptions === "object" && !Array.isArray(customOptions)) {
1364
+ for (const key in customOptions) {
1365
+ if (customOptions[key] === undefined)
1366
+ delete customOptions[key];
1367
+ }
1368
+ }
1369
+ if (optionConstantGetter) {
1370
+ if (typeof optionConstantGetter === "function")
1371
+ constantOptions = optionConstantGetter(link, i, links, state);
1372
+ else
1373
+ constantOptions = optionConstantGetter;
1374
+ if (constantOptions &&
1375
+ typeof constantOptions === "object" &&
1376
+ !Array.isArray(constantOptions) &&
1377
+ checkType(customOptions, customOptions === undefined ||
1378
+ (typeof customOptions === "object" && !Array.isArray(customOptions)))) {
1379
+ return {
1380
+ ...constantOptions,
1381
+ ...customOptions,
1382
+ };
1383
+ }
1384
+ }
1385
+ return customOptions;
1386
+ }
1387
+
1388
+ function nodeByPointerGetter({ areaRect, areaTransform, mouseEvent, nodes, }) {
1389
+ if (!areaRect)
1390
+ return undefined;
1391
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
1392
+ return d3Array.greatest(nodes, (node) => {
1393
+ if (isOverlapsNode(node, pointerX, pointerY, undefined))
1394
+ return node.index;
1395
+ });
1396
+ }
1397
+
1398
+ function nodeIterationExtractor(node, i, nodes, state, option, optionConstantGetter) {
1399
+ let customOptions;
1400
+ let constantOptions;
1401
+ if (typeof option === "function")
1402
+ customOptions = option(node, i, nodes, state);
1403
+ else
1404
+ customOptions = option;
1405
+ if (customOptions && typeof customOptions === "object" && !Array.isArray(customOptions)) {
1406
+ for (const key in customOptions) {
1407
+ if (customOptions[key] === undefined)
1408
+ delete customOptions[key];
1409
+ }
1410
+ }
1411
+ if (optionConstantGetter) {
1412
+ if (typeof optionConstantGetter === "function")
1413
+ constantOptions = optionConstantGetter(node, i, nodes, state);
1414
+ else
1415
+ constantOptions = optionConstantGetter;
1416
+ if (constantOptions &&
1417
+ typeof constantOptions === "object" &&
1418
+ !Array.isArray(constantOptions) &&
1419
+ checkType(customOptions, customOptions === undefined ||
1420
+ (typeof customOptions === "object" && !Array.isArray(customOptions)))) {
1421
+ return {
1422
+ ...constantOptions,
1423
+ ...customOptions,
1424
+ };
1425
+ }
1426
+ }
1427
+ return customOptions;
1428
+ }
1429
+
1430
+ function calculateLinkPositionByNode(link, arrowSize = 0) {
1431
+ const source = link.source;
1432
+ const target = link.target;
1433
+ if (typeof source != "object" || typeof target != "object")
1434
+ return null;
1435
+ const sourcePoint = getLinkPoint({
1436
+ arrowSize: 0,
1437
+ node: source,
1438
+ oppositeNode: target,
1439
+ });
1440
+ const targetPoint = getLinkPoint({
1441
+ arrowSize: arrowSize > 0 ? arrowSize * 0.85 : 0,
1442
+ node: target,
1443
+ oppositeNode: source,
1444
+ });
1445
+ if (!sourcePoint || !targetPoint)
1446
+ return null;
1447
+ const distance = targetPoint && sourcePoint
1448
+ ? Math.sqrt((targetPoint.x - sourcePoint.x) ** 2 + (targetPoint.y - sourcePoint.y) ** 2)
1449
+ : 0;
1450
+ return {
1451
+ x1: sourcePoint.x,
1452
+ y1: sourcePoint.y,
1453
+ x2: targetPoint.x,
1454
+ y2: targetPoint.y,
1455
+ distance,
1456
+ };
1457
+ }
1458
+ function getLinkPoint(opts) {
1459
+ if (opts.node.x == undefined ||
1460
+ opts.node.y == undefined ||
1461
+ opts.oppositeNode.x == undefined ||
1462
+ opts.oppositeNode.y == undefined)
1463
+ return null;
1464
+ let nodePoint;
1465
+ switch (opts.node._shape) {
1466
+ case "circle": {
1467
+ nodePoint = getCircleIntersection({
1468
+ x: opts.node.x,
1469
+ y: opts.node.y,
1470
+ radius: opts.node._radius ?? COMMON_SETTINGS.nodeRadius,
1471
+ oppositeX: opts.oppositeNode.x,
1472
+ oppositeY: opts.oppositeNode.y,
1473
+ arrowSize: opts.arrowSize,
1474
+ });
1475
+ break;
1476
+ }
1477
+ case "square":
1478
+ case "text": {
1479
+ nodePoint = getRectangleIntersection({
1480
+ arrowSize: opts.arrowSize,
1481
+ x: opts.node.x,
1482
+ y: opts.node.y,
1483
+ height: opts.node._height ?? COMMON_SETTINGS.nodeSize,
1484
+ width: opts.node._width ?? COMMON_SETTINGS.nodeSize,
1485
+ oppositeX: opts.oppositeNode.x,
1486
+ oppositeY: opts.oppositeNode.y,
1487
+ borderRadius: opts.node._shape === "text" ? 0 : (opts.node._borderRadius ?? 0),
1488
+ });
1489
+ break;
1490
+ }
1491
+ default: {
1492
+ nodePoint = getCircleIntersection({
1493
+ x: opts.node.x,
1494
+ y: opts.node.y,
1495
+ radius: opts.node._radius ?? COMMON_SETTINGS.nodeRadius,
1496
+ oppositeX: opts.oppositeNode.x,
1497
+ oppositeY: opts.oppositeNode.y,
1498
+ arrowSize: opts.arrowSize,
1499
+ });
1500
+ }
1501
+ }
1502
+ return nodePoint;
1503
+ }
1504
+ function getCircleIntersection(opts) {
1505
+ const dx = opts.oppositeX - opts.x;
1506
+ const dy = opts.oppositeY - opts.y;
1507
+ const dr = Math.sqrt(dx * dx + dy * dy);
1508
+ const radius = opts.radius + opts.arrowSize;
1509
+ return {
1510
+ x: opts.x + (dx * radius) / dr,
1511
+ y: opts.y + (dy * radius) / dr,
1512
+ };
1513
+ }
1514
+ function getRectangleIntersection(opts) {
1515
+ const halfWidth = opts.width / 2;
1516
+ const halfHeight = opts.height / 2;
1517
+ const dx = opts.oppositeX - opts.x;
1518
+ const dy = opts.oppositeY - opts.y;
1519
+ let relX, relY;
1520
+ if (Math.abs(dx) <= halfWidth && Math.abs(dy) <= halfHeight) {
1521
+ const distToRight = halfWidth - dx;
1522
+ const distToLeft = halfWidth + dx;
1523
+ const distToTop = halfHeight - dy;
1524
+ const distToBottom = halfHeight + dy;
1525
+ const minDist = Math.min(distToRight, distToLeft, distToTop, distToBottom);
1526
+ relX = minDist === distToRight ? halfWidth : minDist === distToLeft ? -halfWidth : dx;
1527
+ relY = minDist === distToTop ? halfHeight : minDist === distToBottom ? -halfHeight : dy;
1528
+ }
1529
+ else {
1530
+ const absDx = Math.abs(dx);
1531
+ const absDy = Math.abs(dy);
1532
+ if (halfWidth * absDy < halfHeight * absDx) {
1533
+ relX = dx > 0 ? halfWidth : -halfWidth;
1534
+ relY = (relX * dy) / dx;
1535
+ if (Math.abs(relY) > halfHeight) {
1536
+ relY = dy > 0 ? halfHeight : -halfHeight;
1537
+ relX = (relY * dx) / dy;
1538
+ }
1539
+ }
1540
+ else {
1541
+ relY = dy > 0 ? halfHeight : -halfHeight;
1542
+ relX = (relY * dx) / dy;
1543
+ if (Math.abs(relX) > halfWidth) {
1544
+ relX = dx > 0 ? halfWidth : -halfWidth;
1545
+ relY = (relX * dy) / dx;
1546
+ }
1547
+ }
1548
+ }
1549
+ if (opts.borderRadius != undefined) {
1550
+ const { x, y } = squareRadiusFix(relX, relY, halfWidth, halfHeight, opts.borderRadius);
1551
+ if (x != undefined)
1552
+ relX = x;
1553
+ if (y != undefined)
1554
+ relY = y;
1555
+ }
1556
+ if (opts.arrowSize > 0) {
1557
+ const edgeDist = Math.sqrt(relX * relX + relY * relY);
1558
+ const scale = (edgeDist + opts.arrowSize) / edgeDist;
1559
+ relX *= scale;
1560
+ relY *= scale;
1561
+ }
1562
+ return {
1563
+ x: opts.x + relX,
1564
+ y: opts.y + relY,
1565
+ };
1566
+ }
1567
+ // eslint-disable-next-line no-warning-comments
1568
+ // TODO: Need upgrade
1569
+ function squareRadiusFix(relX, relY, halfWidth, halfHeight, borderRadius) {
1570
+ const epsilon = 1e-6;
1571
+ const absX = Math.abs(relX);
1572
+ const absY = Math.abs(relY);
1573
+ let side = null;
1574
+ if (absX >= halfWidth - epsilon) {
1575
+ side = relX > 0 ? "right" : "left";
1576
+ }
1577
+ else if (absY >= halfHeight - epsilon) {
1578
+ side = relY > 0 ? "top" : "bottom";
1579
+ }
1580
+ if (side) {
1581
+ const topBound = halfHeight - borderRadius;
1582
+ const bottomBound = -topBound;
1583
+ const rightBound = halfWidth - borderRadius;
1584
+ const leftBound = -rightBound;
1585
+ switch (side) {
1586
+ case "right":
1587
+ if (relY > topBound) {
1588
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "right-top");
1589
+ }
1590
+ else if (relY < bottomBound) {
1591
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "right-bottom");
1592
+ }
1593
+ break;
1594
+ case "left":
1595
+ if (relY > topBound) {
1596
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "left-top");
1597
+ }
1598
+ else if (relY < bottomBound) {
1599
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "left-bottom");
1600
+ }
1601
+ break;
1602
+ case "top":
1603
+ if (relX > rightBound) {
1604
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "top-right");
1605
+ }
1606
+ else if (relX < leftBound) {
1607
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "top-left");
1608
+ }
1609
+ break;
1610
+ case "bottom":
1611
+ if (relX > rightBound) {
1612
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "bottom-right");
1613
+ }
1614
+ else if (relX < leftBound) {
1615
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "bottom-left");
1616
+ }
1617
+ break;
1618
+ }
1619
+ }
1620
+ return { x: undefined, y: undefined };
1621
+ }
1622
+ function checkIntersection(relX, relY, halfWidth, halfHeight, r, corner) {
1623
+ const intersect = intersectWithCircle(relX, relY, halfWidth, halfHeight, r, corner);
1624
+ return intersect ?? { x: undefined, y: undefined };
1625
+ }
1626
+ function intersectWithCircle(relX, relY, halfWidth, halfHeight, r, corner) {
1627
+ switch (corner) {
1628
+ case "top-right":
1629
+ corner = "right-top";
1630
+ break;
1631
+ case "bottom-right":
1632
+ corner = "right-bottom";
1633
+ break;
1634
+ case "top-left":
1635
+ corner = "left-top";
1636
+ break;
1637
+ case "bottom-left":
1638
+ corner = "left-bottom";
1639
+ break;
1640
+ }
1641
+ let cx, cy;
1642
+ switch (corner) {
1643
+ case "right-top":
1644
+ cx = halfWidth - r;
1645
+ cy = halfHeight - r;
1646
+ break;
1647
+ case "right-bottom":
1648
+ cx = halfWidth - r;
1649
+ cy = -halfHeight + r;
1650
+ break;
1651
+ case "left-top":
1652
+ cx = -halfWidth + r;
1653
+ cy = halfHeight - r;
1654
+ break;
1655
+ case "left-bottom":
1656
+ cx = -halfWidth + r;
1657
+ cy = -halfHeight + r;
1658
+ break;
1659
+ default:
1660
+ return null;
1661
+ }
1662
+ const a = relX ** 2 + relY ** 2;
1663
+ if (a === 0)
1664
+ return null;
1665
+ const b = -2 * (relX * cx + relY * cy);
1666
+ const c = cx ** 2 + cy ** 2 - r ** 2;
1667
+ const discriminant = b ** 2 - 4 * a * c;
1668
+ if (discriminant < 0)
1669
+ return null;
1670
+ const sqrtD = Math.sqrt(discriminant);
1671
+ const t2 = (-b - sqrtD) / (2 * a);
1672
+ const t1 = (-b + sqrtD) / (2 * a);
1673
+ const t = t2 > 0 ? t2 : t1 > 0 ? t1 : null;
1674
+ if (t === null || t >= 1)
1675
+ return null;
1676
+ const x = t * relX;
1677
+ const y = t * relY;
1678
+ let valid = false;
1679
+ switch (corner) {
1680
+ case "right-top":
1681
+ valid = x >= cx && y >= cy;
1682
+ break;
1683
+ case "right-bottom":
1684
+ valid = x >= cx && y <= cy;
1685
+ break;
1686
+ case "left-top":
1687
+ valid = x <= cx && y >= cy;
1688
+ break;
1689
+ case "left-bottom":
1690
+ valid = x <= cx && y <= cy;
1691
+ break;
1692
+ }
1693
+ return valid ? { x, y } : null;
1694
+ }
1695
+
1696
+ function animationByProgress(initial, additional, progress) {
1697
+ const max = initial + additional;
1698
+ let current = initial + (max - initial) * progress;
1699
+ if (current > max)
1700
+ current = max;
1701
+ if (current < initial)
1702
+ current = initial;
1703
+ return current;
1704
+ }
1705
+
1706
+ const ADDITIONAL_VIEWPORT = 10;
1707
+ function isNodeVisible(opts) {
1708
+ const left = -opts.transform.x / opts.transform.k;
1709
+ const right = (opts.width - opts.transform.x) / opts.transform.k;
1710
+ const top = -opts.transform.y / opts.transform.k;
1711
+ const bottom = (opts.height - opts.transform.y) / opts.transform.k;
1712
+ if (opts.node.x == undefined || opts.node.y == undefined)
1713
+ return false;
1714
+ switch (opts.node._shape) {
1715
+ case "circle": {
1716
+ const radius = opts.node._radius ?? COMMON_SETTINGS.nodeRadius;
1717
+ return (left < opts.node.x + radius + ADDITIONAL_VIEWPORT &&
1718
+ opts.node.x - radius - ADDITIONAL_VIEWPORT < right &&
1719
+ top < opts.node.y + radius + ADDITIONAL_VIEWPORT &&
1720
+ opts.node.y - radius - ADDITIONAL_VIEWPORT < bottom);
1721
+ }
1722
+ case "square":
1723
+ case "text": {
1724
+ const width = opts.node._width ?? COMMON_SETTINGS.nodeSize;
1725
+ const height = opts.node._height ?? COMMON_SETTINGS.nodeSize;
1726
+ return (left < opts.node.x + width + ADDITIONAL_VIEWPORT &&
1727
+ opts.node.x - width - ADDITIONAL_VIEWPORT < right &&
1728
+ top < opts.node.y + height + ADDITIONAL_VIEWPORT &&
1729
+ opts.node.y - height - ADDITIONAL_VIEWPORT < bottom);
1730
+ }
1731
+ default: {
1732
+ const radius = opts.node._radius ?? COMMON_SETTINGS.nodeRadius;
1733
+ return (left < opts.node.x + radius + ADDITIONAL_VIEWPORT &&
1734
+ opts.node.x - radius - ADDITIONAL_VIEWPORT < right &&
1735
+ top < opts.node.y + radius + ADDITIONAL_VIEWPORT &&
1736
+ opts.node.y - radius - ADDITIONAL_VIEWPORT < bottom);
1737
+ }
1738
+ }
1739
+ }
1740
+
1741
+ function getParticlePosition(opts) {
1742
+ const prevStepDifference = opts.particle.step - (opts.particle.prev?.step ?? 0);
1743
+ const nextStepDifference = (opts.particle.next?.step ?? 0) - opts.particle.step;
1744
+ const needWait = opts.particle.next &&
1745
+ opts.particle.next.step > opts.particle.step &&
1746
+ nextStepDifference <= opts.distance;
1747
+ const needSpeed = opts.particle.prev &&
1748
+ opts.particle.prev.step !== 0 &&
1749
+ opts.particle.prev.step < opts.particle.step &&
1750
+ prevStepDifference < opts.distance;
1751
+ if (opts.particle.step === 0 && needWait) {
1752
+ opts.particle.x = undefined;
1753
+ opts.particle.y = undefined;
1754
+ return;
1755
+ }
1756
+ const remainingSteps = opts.totalSteps - opts.particle.step;
1757
+ const progress = opts.particle.step / opts.totalSteps;
1758
+ if (remainingSteps <= 0) {
1759
+ opts.particle.x = opts.xEnd;
1760
+ opts.particle.y = opts.yEnd;
1761
+ opts.particle.step = 0;
1762
+ return;
1763
+ }
1764
+ const dx = opts.xEnd - opts.xStart;
1765
+ const dy = opts.yEnd - opts.yStart;
1766
+ const newX = opts.xStart + dx * progress;
1767
+ const newY = opts.yStart + dy * progress;
1768
+ opts.particle.x = newX;
1769
+ opts.particle.y = newY;
1770
+ if (needSpeed) {
1771
+ opts.particle.step += prevStepDifference <= 3 ? prevStepDifference : 3;
1772
+ }
1773
+ else {
1774
+ opts.particle.step++;
1775
+ }
1776
+ }
1777
+
1778
+ /* eslint-disable id-length */
1779
+ function linkByPointerGetter({ areaRect, areaTransform, mouseEvent, links, linkSettings, }) {
1780
+ if (!areaRect)
1781
+ return undefined;
1782
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
1783
+ return d3Array.greatest(links, (link) => {
1784
+ if (isNearLink(pointerX, pointerY, link, linkSettings.hoverLinkThreshold))
1785
+ return link.index;
1786
+ });
1787
+ }
1788
+ function isNearLink(mouseX, mouseY, link, threshold = 2) {
1789
+ if (!jsHelpers.isObject(link.source) || !jsHelpers.isObject(link.target))
1790
+ return false;
1791
+ const x1 = link.source.x;
1792
+ const y1 = link.source.y;
1793
+ const x2 = link.target.x;
1794
+ const y2 = link.target.y;
1795
+ const distance = distanceToLine(mouseX, mouseY, x1, y1, x2, y2);
1796
+ return distance <= threshold;
1797
+ }
1798
+ function distanceToLine(x, y, x1, y1, x2, y2) {
1799
+ const A = x - x1;
1800
+ const B = y - y1;
1801
+ const C = x2 - x1;
1802
+ const D = y2 - y1;
1803
+ const dot = A * C + B * D;
1804
+ const lenSq = C * C + D * D;
1805
+ let param = -1;
1806
+ if (lenSq !== 0) {
1807
+ param = dot / lenSq;
1808
+ }
1809
+ let xx, yy;
1810
+ if (param < 0) {
1811
+ xx = x1;
1812
+ yy = y1;
1813
+ }
1814
+ else if (param > 1) {
1815
+ xx = x2;
1816
+ yy = y2;
1817
+ }
1818
+ else {
1819
+ xx = x1 + param * C;
1820
+ yy = y1 + param * D;
1821
+ }
1822
+ const dx = x - xx;
1823
+ const dy = y - yy;
1824
+ return Math.sqrt(dx * dx + dy * dy);
1825
+ }
1826
+
1827
+ function initArea() {
1828
+ if (!this.area || !this.context || !this.container) {
1829
+ this.container = d3Selection.create("div")
1830
+ .attr("style", "padding: 0 !important; width: 100%; height: 100%;")
1831
+ .node();
1832
+ if (!this.container)
1833
+ throw new Error("couldn't create container");
1834
+ this.root.appendChild(this.container);
1835
+ const { width, height } = this.root.getBoundingClientRect();
1836
+ this.width = width;
1837
+ this.height = height;
1838
+ this.area = d3Selection.create("canvas")
1839
+ .attr("width", this.dpi * this.width)
1840
+ .attr("height", this.dpi * this.height)
1841
+ .attr("style", `width: 100%; height: 100%; border: none !important;`)
1842
+ .node();
1843
+ if (!this.area)
1844
+ throw new Error("couldn't create canvas");
1845
+ this.container.appendChild(this.area);
1846
+ this.areaRect = this.area.getBoundingClientRect();
1847
+ this.context = this.area.getContext("2d");
1848
+ if (!this.context)
1849
+ throw new Error("couldn't create canvas context");
1850
+ this.context.scale(this.dpi, this.dpi);
1851
+ }
1852
+ }
1853
+
1854
+ function initDnd() {
1855
+ if (!this.area || !this.nodes)
1856
+ throw new Error("bad init data");
1857
+ const dragHandler = d3Drag.drag()
1858
+ .subject((event) => {
1859
+ if (this.listeners.onDragSubject) {
1860
+ return this.listeners.onDragSubject(event, this.state);
1861
+ }
1862
+ if (!this.areaRect)
1863
+ return;
1864
+ const mouseEvent = event.sourceEvent;
1865
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1866
+ return d3Array.greatest(this.nodes, (node) => {
1867
+ if (!node.x || !node.y || (jsHelpers.isBoolean(node.drag) && !node.drag))
1868
+ return undefined;
1869
+ return this.graphSettings.dragPlaceCoefficient(node, pointerX, pointerY);
1870
+ });
1871
+ })
1872
+ .on("start", (event) => {
1873
+ this.listeners.onStartDragFinished?.(event, this.state);
1874
+ })
1875
+ .on("drag", (event) => {
1876
+ if (!this.isDragging) {
1877
+ this.isDragging = true;
1878
+ if (this.simulation)
1879
+ this.simulation.alphaTarget(0.3).restart();
1880
+ }
1881
+ if (!this.areaRect)
1882
+ return;
1883
+ const mouseEvent = event.sourceEvent;
1884
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1885
+ event.subject.fx = pointerX;
1886
+ event.subject.fy = pointerY;
1887
+ this.listeners.onMoveDragFinished?.(event, this.state);
1888
+ })
1889
+ .on("end", (event) => {
1890
+ this.isDragging = false;
1891
+ if (!event.active && this.simulation)
1892
+ this.simulation.alphaTarget(0);
1893
+ const sourceEvent = event.sourceEvent;
1894
+ if (sourceEvent.altKey && this.areaRect) {
1895
+ const [pointerX, pointerY] = pointerGetter(sourceEvent, this.areaRect, this.areaTransform);
1896
+ event.subject.fx = pointerX;
1897
+ event.subject.fy = pointerY;
1898
+ }
1899
+ else {
1900
+ event.subject.fx = null;
1901
+ event.subject.fy = null;
1902
+ }
1903
+ this.listeners.onEndDragFinished?.(event, this.state);
1904
+ });
1905
+ d3Selection.select(this.area).call(dragHandler);
1906
+ }
1907
+
1908
+ function getDrawLink(state) {
1909
+ return function drawLink(link, index) {
1910
+ if (!this.context ||
1911
+ typeof link.source !== "object" ||
1912
+ typeof link.target !== "object" ||
1913
+ !link.source.x ||
1914
+ !link.source.y ||
1915
+ !link.target.x ||
1916
+ !link.target.y)
1917
+ return;
1918
+ if (!link.source._visible && !link.target._visible)
1919
+ return;
1920
+ const id = `${link.target.id}${link.source.id}`;
1921
+ let linkOptions;
1922
+ if (this.linkSettings.cache && this.linkOptionsCache[id]) {
1923
+ linkOptions = this.linkOptionsCache[id];
1924
+ }
1925
+ else {
1926
+ linkOptions = linkIterationExtractor(link, index, this.links, state, this.linkSettings.options ?? {}, linkOptionsGetter);
1927
+ if (this.linkSettings.cache) {
1928
+ this.linkOptionsCache[id] = linkOptions;
1929
+ }
1930
+ }
1931
+ if (linkOptions.drawLink) {
1932
+ linkOptions.drawLink(link, linkOptions, state);
1933
+ return;
1934
+ }
1935
+ let alpha = linkOptions.alpha;
1936
+ let arrowAlpha = this.linkSettings.arrowByHighlight ? 0 : linkOptions.arrowAlpha;
1937
+ /** NODE HIGHLIGHT */
1938
+ if (this.highlightedNeighbors && this.highlightedNode) {
1939
+ /** Not highlighted */
1940
+ if (this.highlightedNode.id != link.source.id && this.highlightedNode.id != link.target.id) {
1941
+ if (this.linkSettings.highlightByNodeLinkFading) {
1942
+ const min = this.linkSettings.highlightByNodeLinkFadingMin < alpha
1943
+ ? this.linkSettings.highlightByNodeLinkFadingMin
1944
+ : alpha;
1945
+ alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
1946
+ }
1947
+ if (this.linkSettings.arrow &&
1948
+ this.linkSettings.highlightByNodeArrowFading &&
1949
+ !this.linkSettings.arrowByHighlight) {
1950
+ const min = this.linkSettings.highlightByNodeArrowFadingMin < arrowAlpha
1951
+ ? this.linkSettings.highlightByNodeArrowFadingMin
1952
+ : arrowAlpha;
1953
+ arrowAlpha = animationByProgress(min, arrowAlpha - min, 1 - this.highlightProgress);
1954
+ }
1955
+ }
1956
+ else {
1957
+ // eslint-disable-next-line no-lonely-if
1958
+ if (this.linkSettings.arrow && this.linkSettings.arrowByHighlight) {
1959
+ /** Highlighted */
1960
+ arrowAlpha = animationByProgress(0, linkOptions.arrowAlpha, this.highlightProgress);
1961
+ }
1962
+ }
1963
+ }
1964
+ /** LINK HIGHLIGHT */
1965
+ if (this.highlightedNeighbors && this.highlightedLink) {
1966
+ /** Not highlighted */
1967
+ if (this.highlightedLink !== link) {
1968
+ if (this.linkSettings.highlightByLinkLinkFading) {
1969
+ const min = this.linkSettings.highlightByLinkLinkFadingMin < alpha
1970
+ ? this.linkSettings.highlightByLinkLinkFadingMin
1971
+ : alpha;
1972
+ alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
1973
+ }
1974
+ if (this.linkSettings.arrow &&
1975
+ this.linkSettings.highlightByLinkArrowFading &&
1976
+ !this.linkSettings.arrowByHighlight) {
1977
+ const min = this.linkSettings.highlightByLinkArrowFadingMin < arrowAlpha
1978
+ ? this.linkSettings.highlightByLinkArrowFadingMin
1979
+ : arrowAlpha;
1980
+ arrowAlpha = animationByProgress(min, arrowAlpha - min, 1 - this.highlightProgress);
1981
+ }
1982
+ }
1983
+ else {
1984
+ // eslint-disable-next-line no-lonely-if
1985
+ if (this.linkSettings.arrow && this.linkSettings.arrowByHighlight) {
1986
+ /** Highlighted */
1987
+ arrowAlpha = animationByProgress(0, linkOptions.arrowAlpha, this.highlightProgress);
1988
+ }
1989
+ }
1990
+ }
1991
+ /** Link */
1992
+ this.context.beginPath();
1993
+ this.context.globalAlpha = alpha;
1994
+ this.context.strokeStyle = linkOptions.color;
1995
+ this.context.lineWidth = linkOptions.width;
1996
+ let xStart = link.source.x;
1997
+ let yStart = link.source.y;
1998
+ let xEnd = link.target.x;
1999
+ let yEnd = link.target.y;
2000
+ let linkDistance = 0;
2001
+ if (this.linkSettings.pretty || this.linkSettings.particleFlexSpeed) {
2002
+ const isHasArrow = this.linkSettings.arrow && arrowAlpha > 0;
2003
+ const position = calculateLinkPositionByNode(link, isHasArrow ? linkOptions.arrowSize : 0);
2004
+ if (position) {
2005
+ xStart = position.x1;
2006
+ xEnd = position.x2;
2007
+ yStart = position.y1;
2008
+ yEnd = position.y2;
2009
+ linkDistance = position.distance;
2010
+ }
2011
+ }
2012
+ this.context.moveTo(xStart, yStart);
2013
+ this.context.lineTo(xEnd, yEnd);
2014
+ this.context.stroke();
2015
+ /** Particle */
2016
+ if (this.linkSettings.particles &&
2017
+ ((this.highlightedNode &&
2018
+ (this.highlightedNode.id === link.source.id ||
2019
+ this.highlightedNode.id === link.target.id)) ||
2020
+ (this.highlightedLink && this.highlightedLink === link))) {
2021
+ const particleSteps = this.linkSettings.particleFlexSpeed
2022
+ ? linkDistance <= 0
2023
+ ? 0
2024
+ : linkDistance * this.linkSettings.particleFlexSpeedCoefficient
2025
+ : linkOptions.particleSteps;
2026
+ const particleCount = linkOptions.particleCount;
2027
+ if (!this.particles[id]) {
2028
+ const sourceId = link.source.id;
2029
+ const targetId = link.target.id;
2030
+ const particles = [];
2031
+ let prevParticle;
2032
+ for (let i = 0; i < particleCount; i++) {
2033
+ const particle = {
2034
+ step: 0,
2035
+ sourceId,
2036
+ targetId,
2037
+ prev: prevParticle,
2038
+ next: undefined,
2039
+ index: i,
2040
+ };
2041
+ if (prevParticle)
2042
+ prevParticle.next = particle;
2043
+ particles.push(particle);
2044
+ prevParticle = particle;
2045
+ }
2046
+ if (particles.length >= 2) {
2047
+ particles[0].prev = particles[particles.length - 1];
2048
+ particles[particles.length - 1].next = particles[0];
2049
+ }
2050
+ this.particles[id] = particles;
2051
+ }
2052
+ if (particleSteps !== 0) {
2053
+ this.particles[id].forEach((particle) => {
2054
+ if (!this.context)
2055
+ return;
2056
+ const distance = particleSteps / particleCount;
2057
+ getParticlePosition({
2058
+ distance,
2059
+ particle,
2060
+ totalSteps: particleSteps,
2061
+ xEnd,
2062
+ xStart,
2063
+ yEnd,
2064
+ yStart,
2065
+ });
2066
+ if (particle.x != undefined && particle.y != undefined) {
2067
+ this.context.beginPath();
2068
+ this.context.strokeStyle = linkOptions.particleBorderColor;
2069
+ this.context.lineWidth = linkOptions.particleBorderWidth;
2070
+ this.context.arc(particle.x, particle.y, linkOptions.particleRadius, 0, Math.PI * 2);
2071
+ this.context.fillStyle = linkOptions.particleColor;
2072
+ this.context.fill();
2073
+ this.context.stroke();
2074
+ }
2075
+ });
2076
+ }
2077
+ }
2078
+ /** Arrow */
2079
+ if (this.linkSettings.arrow && arrowAlpha > 0) {
2080
+ const { x1: xStart, x2: xEnd, y1: yStart, y2: yEnd, } = calculateLinkPositionByNode(link) ?? {
2081
+ x1: 0,
2082
+ x2: 0,
2083
+ y1: 0,
2084
+ y2: 0,
2085
+ };
2086
+ this.context.beginPath();
2087
+ this.context.globalAlpha = arrowAlpha;
2088
+ this.context.strokeStyle = linkOptions.arrowBorderColor;
2089
+ this.context.lineWidth = linkOptions.arrowBorderWidth;
2090
+ this.context.fillStyle = linkOptions.arrowColor;
2091
+ const angle = Math.atan2(yEnd - yStart, xEnd - xStart);
2092
+ this.context.moveTo(xEnd, yEnd);
2093
+ this.context.lineTo(xEnd - linkOptions.arrowSize * Math.cos(angle - Math.PI / 6), yEnd - linkOptions.arrowSize * Math.sin(angle - Math.PI / 6));
2094
+ this.context.lineTo(xEnd - linkOptions.arrowSize * Math.cos(angle + Math.PI / 6), yEnd - linkOptions.arrowSize * Math.sin(angle + Math.PI / 6));
2095
+ this.context.closePath();
2096
+ this.context.fill();
2097
+ this.context.stroke();
2098
+ }
2099
+ if (linkOptions.drawExtraLink) {
2100
+ linkOptions.drawExtraLink(link, { ...linkOptions, alpha }, state);
2101
+ }
2102
+ };
2103
+ }
2104
+
2105
+ const SPACE = " ";
2106
+ function drawText({ context, id, textAlign, textColor, textFont, textStyle, textGap, textWeight, textSize, text, x, y, cachedNodeText, maxWidth, }) {
2107
+ context.font = `${textStyle} normal ${textWeight} ${textSize}px ${textFont}`;
2108
+ context.fillStyle = textColor;
2109
+ context.textAlign = textAlign;
2110
+ if (cachedNodeText[id] != undefined) {
2111
+ cachedNodeText[id].forEach((line, index) => {
2112
+ context.fillText(line, x, y + index * textSize + index * textGap);
2113
+ });
2114
+ return;
2115
+ }
2116
+ if (maxWidth == undefined || context.measureText(text).width <= maxWidth) {
2117
+ cachedNodeText[id] = [text];
2118
+ context.fillText(text, x, y);
2119
+ return;
2120
+ }
2121
+ const lines = getTextLines({
2122
+ context,
2123
+ maxWidth,
2124
+ text,
2125
+ textAlign,
2126
+ textColor,
2127
+ textFont,
2128
+ textSize,
2129
+ textStyle,
2130
+ textWeight,
2131
+ });
2132
+ cachedNodeText[id] = lines;
2133
+ lines.forEach((line, index) => {
2134
+ context.fillText(line, x, y + index * textSize + index * textGap);
2135
+ });
2136
+ }
2137
+ function getTextLines({ context, textAlign, textColor, textFont, textStyle, textWeight, textSize, text, maxWidth, }) {
2138
+ context.font = `${textStyle} normal ${textWeight} ${textSize}px ${textFont}`;
2139
+ context.fillStyle = textColor;
2140
+ context.textAlign = textAlign;
2141
+ const spaceWidth = context.measureText(" ").width;
2142
+ const lines = [];
2143
+ let lineWidth = 0;
2144
+ let line = "";
2145
+ for (const word of text.split(" ")) {
2146
+ const size = context.measureText(word).width;
2147
+ lineWidth += size + spaceWidth;
2148
+ if (line === "") {
2149
+ line = word;
2150
+ continue;
2151
+ }
2152
+ if (lineWidth > maxWidth) {
2153
+ lineWidth = 0;
2154
+ lines.push(line);
2155
+ line = word;
2156
+ }
2157
+ else {
2158
+ lineWidth += spaceWidth;
2159
+ line += `${SPACE}${word}`;
2160
+ }
2161
+ }
2162
+ if (line !== "")
2163
+ lines.push(line);
2164
+ return lines;
2165
+ }
2166
+
2167
+ function getDrawNode(nodeRenders, textRenders, state) {
2168
+ return function drawNode(node, index) {
2169
+ if (!this.context || !node.x || !node.y)
2170
+ return;
2171
+ let nodeOptions;
2172
+ if (this.nodeSettings.cache && this.nodeOptionsCache[node.id]) {
2173
+ nodeOptions = this.nodeOptionsCache[node.id];
2174
+ }
2175
+ else {
2176
+ nodeOptions = nodeIterationExtractor(node, index, this.nodes, state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
2177
+ if (this.nodeSettings.cache) {
2178
+ this.nodeOptionsCache[node.id] = nodeOptions;
2179
+ }
2180
+ }
2181
+ if (nodeOptions.nodeDraw && nodeOptions.textDraw) {
2182
+ nodeRenders.push(() => {
2183
+ nodeOptions?.nodeDraw?.(node, nodeOptions, state);
2184
+ });
2185
+ textRenders.push(() => {
2186
+ nodeOptions?.textDraw?.(node, nodeOptions, state);
2187
+ });
2188
+ return;
2189
+ }
2190
+ let alpha = nodeOptions.alpha;
2191
+ let color = nodeOptions.color;
2192
+ let radiusInitial = nodeOptions.radius;
2193
+ let widthInitial = nodeOptions.width;
2194
+ let heightInitial = nodeOptions.height;
2195
+ let textAlpha = nodeOptions.textAlpha;
2196
+ let textSize = nodeOptions.textSize;
2197
+ let textShiftX = nodeOptions.textShiftX;
2198
+ let textShiftY = nodeOptions.textShiftY;
2199
+ let textWeight = nodeOptions.textWeight;
2200
+ let textWidth = nodeOptions.textWidth;
2201
+ /** Node Highlight */
2202
+ if (this.highlightedNeighbors && this.highlightedNode) {
2203
+ /** Not highlighted */
2204
+ if (!this.highlightedNeighbors.has(node.id) && this.highlightedNode.id != node.id) {
2205
+ if (this.nodeSettings.highlightByNodeNodeFading) {
2206
+ const min = this.nodeSettings.highlightByNodeNodeFadingMin < alpha
2207
+ ? this.nodeSettings.highlightByNodeNodeFadingMin
2208
+ : alpha;
2209
+ alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
2210
+ }
2211
+ if (this.nodeSettings.highlightByNodeTextFading) {
2212
+ const min = this.nodeSettings.highlightByNodeTextFadingMin < textAlpha
2213
+ ? this.nodeSettings.highlightByNodeTextFadingMin
2214
+ : textAlpha;
2215
+ textAlpha = animationByProgress(min, textAlpha - min, 1 - this.highlightProgress);
2216
+ }
2217
+ if (this.nodeSettings.highlightByNodeNodeColor) {
2218
+ const colorRgb = extractRgb(colorToRgb(color));
2219
+ if (colorRgb) {
2220
+ const colorRgbFade = fadeRgb(colorRgb, this.nodeSettings.highlightByNodeNodeColorFadingMin);
2221
+ const colorFadeAnimation = rgbAnimationByProgress(colorRgb, colorRgbFade, this.highlightProgress);
2222
+ color = `rgb(${colorFadeAnimation.r}, ${colorFadeAnimation.g}, ${colorFadeAnimation.b})`;
2223
+ }
2224
+ }
2225
+ }
2226
+ else if (!this.nodeSettings.highlightByNodeOnlyRoot ||
2227
+ (this.nodeSettings.highlightByNodeOnlyRoot && this.highlightedNode.id === node.id)) {
2228
+ /** Highlighted */
2229
+ if (this.nodeSettings.highlightByNodeNodeSizing && nodeOptions.shape === "circle") {
2230
+ radiusInitial = animationByProgress(radiusInitial, this.nodeSettings.highlightByNodeNodeSizingAdditional, this.highlightProgress);
2231
+ }
2232
+ if (this.nodeSettings.highlightByLinkNodeSizing &&
2233
+ (nodeOptions.shape === "square" || nodeOptions.shape === "text")) {
2234
+ const widthCoefficient = animationByProgress(1, this.nodeSettings.highlightByNodeNodeSizingAdditionalCoefficient, this.highlightProgress);
2235
+ const heightCoefficient = animationByProgress(1, this.nodeSettings.highlightByNodeNodeSizingAdditionalCoefficient, this.highlightProgress);
2236
+ widthInitial *= widthCoefficient;
2237
+ heightInitial *= heightCoefficient;
2238
+ }
2239
+ if (this.nodeSettings.highlightByNodeTextSizing) {
2240
+ textSize = animationByProgress(textSize, this.nodeSettings.highlightByNodeTextSizingAdditional, this.highlightProgress);
2241
+ textShiftX = animationByProgress(textShiftX, this.nodeSettings.highlightByNodeTextShiftXAdditional, this.highlightProgress);
2242
+ textShiftY = animationByProgress(textShiftY, this.nodeSettings.highlightByNodeTextShiftYAdditional, this.highlightProgress);
2243
+ textWeight = animationByProgress(textWeight, this.nodeSettings.highlightByNodeTextWeightAdditional, this.highlightProgress);
2244
+ textWidth = animationByProgress(textWidth, this.nodeSettings.highlightByNodeTextWidthAdditional, this.highlightProgress);
2245
+ }
2246
+ }
2247
+ }
2248
+ /** LinkHighlight */
2249
+ if (this.highlightedNeighbors && this.highlightedLink) {
2250
+ /** Not highlighted */
2251
+ if (!this.highlightedNeighbors.has(node.id) &&
2252
+ this.highlightedLink.source !== node &&
2253
+ this.highlightedLink.target !== node) {
2254
+ if (this.nodeSettings.highlightByLinkNodeFading) {
2255
+ const min = this.nodeSettings.highlightByLinkNodeFadingMin < alpha
2256
+ ? this.nodeSettings.highlightByLinkNodeFadingMin
2257
+ : alpha;
2258
+ alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
2259
+ }
2260
+ if (this.nodeSettings.highlightByLinkTextFading) {
2261
+ const min = this.nodeSettings.highlightByLinkTextFadingMin < textAlpha
2262
+ ? this.nodeSettings.highlightByLinkTextFadingMin
2263
+ : textAlpha;
2264
+ textAlpha = animationByProgress(min, textAlpha - min, 1 - this.highlightProgress);
2265
+ }
2266
+ if (this.nodeSettings.highlightByLinkNodeColor) {
2267
+ const colorRgb = extractRgb(colorToRgb(color));
2268
+ if (colorRgb) {
2269
+ const colorRgbFade = fadeRgb(colorRgb, this.nodeSettings.highlightByLinkNodeColorFadingMin);
2270
+ const colorFadeAnimation = rgbAnimationByProgress(colorRgb, colorRgbFade, this.highlightProgress);
2271
+ color = `rgb(${colorFadeAnimation.r}, ${colorFadeAnimation.g}, ${colorFadeAnimation.b})`;
2272
+ }
2273
+ }
2274
+ }
2275
+ else {
2276
+ /** Highlighted */
2277
+ if (this.nodeSettings.highlightByLinkNodeSizing && nodeOptions.shape === "circle") {
2278
+ radiusInitial = animationByProgress(radiusInitial, this.nodeSettings.highlightByLinkNodeSizingAdditional, this.highlightProgress);
2279
+ }
2280
+ if (this.nodeSettings.highlightByLinkNodeSizing &&
2281
+ (nodeOptions.shape === "square" ||
2282
+ nodeOptions.shape === "text" ||
2283
+ nodeOptions.shape === "icon")) {
2284
+ const widthCoefficient = animationByProgress(1, this.nodeSettings.highlightByLinkNodeSizingAdditionalCoefficient, this.highlightProgress);
2285
+ const heightCoefficient = animationByProgress(1, this.nodeSettings.highlightByLinkNodeSizingAdditionalCoefficient, this.highlightProgress);
2286
+ widthInitial *= widthCoefficient;
2287
+ heightInitial *= heightCoefficient;
2288
+ }
2289
+ if (this.nodeSettings.highlightByLinkTextSizing) {
2290
+ textSize = animationByProgress(textSize, this.nodeSettings.highlightByLinkTextSizingAdditional, this.highlightProgress);
2291
+ textShiftX = animationByProgress(textShiftX, this.nodeSettings.highlightByLinkTextShiftXAdditional, this.highlightProgress);
2292
+ textShiftY = animationByProgress(textShiftY, this.nodeSettings.highlightByLinkTextShiftYAdditional, this.highlightProgress);
2293
+ textWeight = animationByProgress(textWeight, this.nodeSettings.highlightByLinkTextWeightAdditional, this.highlightProgress);
2294
+ textWidth = animationByProgress(textWidth, this.nodeSettings.highlightByLinkTextWidthAdditional, this.highlightProgress);
2295
+ }
2296
+ }
2297
+ }
2298
+ /** Flex radius */
2299
+ const radius = nodeOptions.shape === "circle"
2300
+ ? nodeRadiusGetter({
2301
+ radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2302
+ radiusInitial,
2303
+ radiusCoefficient: this.nodeSettings.nodeRadiusCoefficient,
2304
+ radiusFactor: this.nodeSettings.nodeRadiusFactor,
2305
+ linkCount: node.linkCount,
2306
+ })
2307
+ : radiusInitial;
2308
+ /** Flex size */
2309
+ let height = heightInitial;
2310
+ let width = widthInitial;
2311
+ if (nodeOptions.shape === "square" || nodeOptions.shape === "text") {
2312
+ const size = nodeSizeGetter({
2313
+ heightInitial,
2314
+ widthInitial,
2315
+ linkCount: node.linkCount,
2316
+ sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
2317
+ sizeFactor: this.nodeSettings.nodeSizeFactor,
2318
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2319
+ });
2320
+ width = size.width;
2321
+ height = size.height;
2322
+ }
2323
+ if (nodeOptions.shape === "text" && nodeOptions.text) {
2324
+ const lines = this.cachedNodeText[node.id] ??
2325
+ getTextLines({
2326
+ context: this.context,
2327
+ text: nodeOptions.text,
2328
+ textAlign: nodeOptions.textAlign,
2329
+ textColor: nodeOptions.textColor,
2330
+ textFont: nodeOptions.textFont,
2331
+ textSize,
2332
+ maxWidth: width,
2333
+ textStyle: nodeOptions.textStyle,
2334
+ textWeight,
2335
+ });
2336
+ if (!this.cachedNodeText[node.id]) {
2337
+ this.cachedNodeText[node.id] = lines;
2338
+ }
2339
+ height =
2340
+ lines.length * textSize +
2341
+ (lines.length - 1) * nodeOptions.textGap +
2342
+ nodeOptions.textNodeYPadding;
2343
+ width += nodeOptions.textNodeXPadding;
2344
+ }
2345
+ /** Node parameters */
2346
+ node._radius = radius;
2347
+ node._width = width;
2348
+ node._height = height;
2349
+ node._borderRadius =
2350
+ jsHelpers.isNumber(nodeOptions.borderRadius) && nodeOptions.borderRadius > 0
2351
+ ? Math.min(nodeOptions.borderRadius, nodeOptions.width / 2, nodeOptions.height / 2)
2352
+ : 0;
2353
+ node._shape = nodeOptions.shape ?? "circle";
2354
+ /** Node Visibility */
2355
+ if (!isNodeVisible({
2356
+ height: this.height,
2357
+ width: this.width,
2358
+ transform: this.areaTransform,
2359
+ node,
2360
+ })) {
2361
+ node._visible = false;
2362
+ return;
2363
+ }
2364
+ node._visible = true;
2365
+ /** Node draw */
2366
+ nodeRenders.push(() => {
2367
+ if (!this.context || !node.x || !node.y)
2368
+ return;
2369
+ this.context.beginPath();
2370
+ this.context.globalAlpha = alpha;
2371
+ this.context.lineWidth = nodeOptions.borderWidth;
2372
+ this.context.strokeStyle = nodeOptions.borderColor;
2373
+ this.context.fillStyle = color;
2374
+ switch (nodeOptions.shape) {
2375
+ case "circle": {
2376
+ this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
2377
+ break;
2378
+ }
2379
+ case "square": {
2380
+ this.context.roundRect(node.x - width / 2, node.y - height / 2, width, height, node._borderRadius);
2381
+ break;
2382
+ }
2383
+ case "text": {
2384
+ if (this.nodeSettings.textNodeDebug) {
2385
+ this.context.strokeRect(node.x - width / 2, node.y - height / 2, width, height);
2386
+ }
2387
+ const lines = this.cachedNodeText[node.id];
2388
+ if (nodeOptions.text && lines)
2389
+ drawText({
2390
+ id: node.id,
2391
+ cachedNodeText: this.cachedNodeText,
2392
+ context: this.context,
2393
+ text: nodeOptions.text,
2394
+ textAlign: nodeOptions.textAlign,
2395
+ textColor: nodeOptions.textColor,
2396
+ textFont: nodeOptions.textFont,
2397
+ textSize,
2398
+ x: node.x,
2399
+ y: node.y + textSize / 4 - (lines.length - 1) * (textSize / 2),
2400
+ maxWidth: widthInitial,
2401
+ textStyle: nodeOptions.textStyle,
2402
+ textWeight,
2403
+ textGap: nodeOptions.textGap,
2404
+ });
2405
+ break;
2406
+ }
2407
+ default: {
2408
+ this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
2409
+ break;
2410
+ }
2411
+ }
2412
+ this.context.fill();
2413
+ this.context.stroke();
2414
+ });
2415
+ if (nodeOptions.nodeExtraDraw) {
2416
+ nodeRenders.push(() => {
2417
+ nodeOptions?.nodeExtraDraw?.(node, {
2418
+ ...nodeOptions,
2419
+ radius,
2420
+ alpha,
2421
+ color,
2422
+ textAlpha,
2423
+ textSize,
2424
+ textShiftX,
2425
+ textShiftY,
2426
+ textWeight,
2427
+ textWidth,
2428
+ }, state);
2429
+ });
2430
+ }
2431
+ /** Text draw */
2432
+ if (nodeOptions.textVisible && nodeOptions.text && nodeOptions.shape !== "text") {
2433
+ textRenders.push(() => {
2434
+ if (nodeOptions.textDraw) {
2435
+ nodeOptions.textDraw(node, {
2436
+ ...nodeOptions,
2437
+ radius,
2438
+ alpha,
2439
+ color,
2440
+ textAlpha,
2441
+ textSize,
2442
+ textShiftX,
2443
+ textShiftY,
2444
+ textWeight,
2445
+ textWidth,
2446
+ }, state);
2447
+ return;
2448
+ }
2449
+ if (!this.context || !node.x || !node.y || !nodeOptions.text)
2450
+ return;
2451
+ this.context.beginPath();
2452
+ this.context.globalAlpha = textAlpha;
2453
+ let y = node.y + textShiftY;
2454
+ if (nodeOptions.shape === "circle") {
2455
+ y += radius;
2456
+ }
2457
+ if (nodeOptions.shape === "square") {
2458
+ y += height / 2;
2459
+ }
2460
+ drawText({
2461
+ id: node.id,
2462
+ cachedNodeText: this.cachedNodeText,
2463
+ context: this.context,
2464
+ text: nodeOptions.text,
2465
+ textAlign: nodeOptions.textAlign,
2466
+ textColor: nodeOptions.textColor,
2467
+ textFont: nodeOptions.textFont,
2468
+ textSize,
2469
+ x: node.x + textShiftX,
2470
+ y,
2471
+ maxWidth: textWidth,
2472
+ textStyle: nodeOptions.textStyle,
2473
+ textWeight,
2474
+ textGap: nodeOptions.textGap,
2475
+ });
2476
+ if (nodeOptions.textExtraDraw) {
2477
+ nodeOptions.textExtraDraw(node, {
2478
+ ...nodeOptions,
2479
+ radius,
2480
+ alpha,
2481
+ color,
2482
+ textAlpha,
2483
+ textSize,
2484
+ textShiftX,
2485
+ textShiftY,
2486
+ textWeight,
2487
+ textWidth,
2488
+ }, state);
2489
+ }
2490
+ });
2491
+ }
2492
+ };
2493
+ }
2494
+
2495
+ function initDraw() {
2496
+ function calculateHighlight() {
2497
+ this.highlightDrawing = true;
2498
+ if (!this.highlightWorking && this.highlightProgress > 0) {
2499
+ const highlightDownStep = 1 / this.graphSettings.highlightDownFrames;
2500
+ this.highlightProgress -= highlightDownStep;
2501
+ if (!this.simulationWorking) {
2502
+ return void requestAnimationFrame(() => this.draw());
2503
+ }
2504
+ if (!this.linkSettings.particles)
2505
+ return;
2506
+ }
2507
+ if (this.highlightWorking && this.highlightProgress < 1) {
2508
+ const highlightUpStep = 1 / this.graphSettings.highlightUpFrames;
2509
+ this.highlightProgress += highlightUpStep;
2510
+ if (!this.simulationWorking) {
2511
+ return void requestAnimationFrame(() => this.draw());
2512
+ }
2513
+ if (!this.linkSettings.particles)
2514
+ return;
2515
+ }
2516
+ if (this.linkSettings.particles && this.highlightWorking && !this.simulationWorking) {
2517
+ return void requestAnimationFrame(() => this.draw());
2518
+ }
2519
+ if (!this.highlightWorking && this.highlightProgress <= 0) {
2520
+ if (this.highlightedNeighbors || this.highlightedNode || this.highlightedLink) {
2521
+ this.highlightedNeighbors = null;
2522
+ this.highlightedNode = null;
2523
+ this.highlightedLink = null;
2524
+ this.particles = {};
2525
+ if (!this.simulationWorking) {
2526
+ return void requestAnimationFrame(() => this.draw());
2527
+ }
2528
+ }
2529
+ }
2530
+ this.highlightDrawing = false;
2531
+ }
2532
+ function draw() {
2533
+ if (!this.context)
2534
+ return;
2535
+ const state = this.state;
2536
+ if (this.listeners.onDraw) {
2537
+ this.listeners.onDraw(state, (status) => {
2538
+ this.highlightDrawing = status;
2539
+ }, () => {
2540
+ if (this.highlightedNeighbors)
2541
+ this.highlightedNeighbors = null;
2542
+ if (this.highlightedNode)
2543
+ this.highlightedNode = null;
2544
+ if (this.highlightedLink)
2545
+ this.highlightedLink = null;
2546
+ });
2547
+ return;
2548
+ }
2549
+ this.context.save();
2550
+ this.context.clearRect(0, 0, this.width, this.height);
2551
+ this.context.translate(this.areaTransform.x, this.areaTransform.y);
2552
+ this.context.scale(this.areaTransform.k, this.areaTransform.k);
2553
+ const textRenders = [];
2554
+ const nodeRenders = [];
2555
+ this.nodes.forEach(getDrawNode(nodeRenders, textRenders, state).bind(this));
2556
+ /** links */
2557
+ this.links.forEach(getDrawLink(state).bind(this));
2558
+ /** nodes */
2559
+ nodeRenders.forEach((render) => render());
2560
+ textRenders.forEach((render) => render());
2561
+ this.context.restore();
2562
+ this.listeners.onDrawFinished?.(state);
2563
+ calculateHighlight.bind(this)();
2564
+ }
2565
+ if (this.graphSettings.showDrawTime) {
2566
+ return setDrawTime(draw.bind(this), this.graphSettings.showDrawTimeEveryTick);
2567
+ }
2568
+ return draw.bind(this);
2569
+ }
2570
+
2571
+ function initPointer() {
2572
+ if (!this.area || !this.nodes)
2573
+ throw new Error("bad init data");
2574
+ function onHover(event) {
2575
+ if (!this.area)
2576
+ return;
2577
+ let currentNode;
2578
+ let currentLink;
2579
+ const checkHighlightNode = this.nodeSettings.highlightByHoverNode && !this.isDragging;
2580
+ const checkHighlightLink = this.linkSettings.highlightByHoverLink && !this.isDragging;
2581
+ if (checkHighlightNode) {
2582
+ currentNode = nodeByPointerGetter({
2583
+ areaRect: this.areaRect,
2584
+ areaTransform: this.areaTransform,
2585
+ mouseEvent: event,
2586
+ nodes: this.nodes,
2587
+ });
2588
+ }
2589
+ if (currentNode) {
2590
+ this.area.style.cursor = "pointer";
2591
+ }
2592
+ else if (checkHighlightLink) {
2593
+ currentLink = linkByPointerGetter({
2594
+ linkSettings: this.linkSettings,
2595
+ areaRect: this.areaRect,
2596
+ areaTransform: this.areaTransform,
2597
+ mouseEvent: event,
2598
+ links: this.links,
2599
+ });
2600
+ if (currentLink) {
2601
+ this.area.style.cursor = "pointer";
2602
+ }
2603
+ else {
2604
+ this.area.style.cursor = "default";
2605
+ }
2606
+ }
2607
+ else {
2608
+ this.area.style.cursor = "default";
2609
+ }
2610
+ if (currentNode && this.highlightedNode !== currentNode) {
2611
+ this.highlightedNode = currentNode;
2612
+ this.highlightedLink = null;
2613
+ this.highlightedNeighbors = new Set(this.highlightedNode?.neighbors ?? []);
2614
+ this.highlightWorking = true;
2615
+ if (!this.simulationWorking && !this.highlightDrawing)
2616
+ requestAnimationFrame(() => {
2617
+ this.draw();
2618
+ });
2619
+ }
2620
+ else if (currentLink &&
2621
+ checkType(currentLink.source, lodash.isObject(currentLink.source)) &&
2622
+ checkType(currentLink.target, lodash.isObject(currentLink.target)) &&
2623
+ this.highlightedLink !== currentLink) {
2624
+ this.highlightProgress = 0;
2625
+ this.highlightedLink = currentLink;
2626
+ this.highlightedNode = null;
2627
+ this.highlightedNeighbors = new Set([currentLink.source.id, currentLink.target.id]);
2628
+ this.highlightWorking = true;
2629
+ if (!this.simulationWorking && !this.highlightDrawing)
2630
+ requestAnimationFrame(() => {
2631
+ this.draw();
2632
+ });
2633
+ }
2634
+ else if (!currentNode && !currentLink && (this.highlightedNode || this.highlightedLink)) {
2635
+ this.highlightWorking = false;
2636
+ if (!this.simulationWorking && !this.highlightDrawing)
2637
+ requestAnimationFrame(() => {
2638
+ this.draw();
2639
+ });
2640
+ }
2641
+ if (!this.listeners.onMove)
2642
+ return;
2643
+ if (!currentNode && !checkHighlightNode)
2644
+ currentNode = nodeByPointerGetter({
2645
+ areaRect: this.areaRect,
2646
+ areaTransform: this.areaTransform,
2647
+ mouseEvent: event,
2648
+ nodes: this.nodes,
2649
+ });
2650
+ if (!currentNode && (!checkHighlightNode || (!checkHighlightLink && !currentLink))) {
2651
+ currentLink = linkByPointerGetter({
2652
+ linkSettings: this.linkSettings,
2653
+ areaRect: this.areaRect,
2654
+ areaTransform: this.areaTransform,
2655
+ mouseEvent: event,
2656
+ links: this.links,
2657
+ });
2658
+ }
2659
+ if (!currentNode)
2660
+ return void this.listeners.onMove(event, currentNode, currentLink);
2661
+ }
2662
+ function onWheelClick(event) {
2663
+ if (this.isDragging ||
2664
+ !this.listeners.onWheelClick ||
2665
+ !("button" in event) ||
2666
+ event.button !== 1)
2667
+ return;
2668
+ const currentNode = nodeByPointerGetter({
2669
+ areaRect: this.areaRect,
2670
+ areaTransform: this.areaTransform,
2671
+ mouseEvent: event,
2672
+ nodes: this.nodes,
2673
+ });
2674
+ if (!currentNode) {
2675
+ const currentLink = linkByPointerGetter({
2676
+ linkSettings: this.linkSettings,
2677
+ areaRect: this.areaRect,
2678
+ areaTransform: this.areaTransform,
2679
+ mouseEvent: event,
2680
+ links: this.links,
2681
+ });
2682
+ return void this.listeners.onWheelClick(event, undefined, currentLink);
2683
+ }
2684
+ return void this.listeners.onWheelClick(event, currentNode, undefined);
2685
+ }
2686
+ function onRightClick(event) {
2687
+ if (!this.listeners.onContextMenu)
2688
+ return;
2689
+ const currentNode = nodeByPointerGetter({
2690
+ areaRect: this.areaRect,
2691
+ areaTransform: this.areaTransform,
2692
+ mouseEvent: event,
2693
+ nodes: this.nodes,
2694
+ });
2695
+ if (!currentNode) {
2696
+ const currentLink = linkByPointerGetter({
2697
+ linkSettings: this.linkSettings,
2698
+ areaRect: this.areaRect,
2699
+ areaTransform: this.areaTransform,
2700
+ mouseEvent: event,
2701
+ links: this.links,
2702
+ });
2703
+ return void this.listeners.onContextMenu(event, undefined, currentLink);
2704
+ }
2705
+ return void this.listeners.onContextMenu(event, currentNode, undefined);
2706
+ }
2707
+ function onDoubleClick(event) {
2708
+ if (!this.listeners.onDoubleClick)
2709
+ return;
2710
+ const currentNode = nodeByPointerGetter({
2711
+ areaRect: this.areaRect,
2712
+ areaTransform: this.areaTransform,
2713
+ mouseEvent: event,
2714
+ nodes: this.nodes,
2715
+ });
2716
+ if (!currentNode) {
2717
+ const currentLink = linkByPointerGetter({
2718
+ linkSettings: this.linkSettings,
2719
+ areaRect: this.areaRect,
2720
+ areaTransform: this.areaTransform,
2721
+ mouseEvent: event,
2722
+ links: this.links,
2723
+ });
2724
+ return void this.listeners.onDoubleClick(event, undefined, currentLink);
2725
+ }
2726
+ return void this.listeners.onDoubleClick(event, currentNode, undefined);
2727
+ }
2728
+ function onClick(event) {
2729
+ if (this.isDragging || !this.listeners.onClick || ("button" in event && event.button !== 0))
2730
+ return;
2731
+ const currentNode = nodeByPointerGetter({
2732
+ areaRect: this.areaRect,
2733
+ areaTransform: this.areaTransform,
2734
+ mouseEvent: event,
2735
+ nodes: this.nodes,
2736
+ });
2737
+ if (!currentNode) {
2738
+ const currentLink = linkByPointerGetter({
2739
+ linkSettings: this.linkSettings,
2740
+ areaRect: this.areaRect,
2741
+ areaTransform: this.areaTransform,
2742
+ mouseEvent: event,
2743
+ links: this.links,
2744
+ });
2745
+ return void this.listeners.onClick(event, undefined, currentLink);
2746
+ }
2747
+ return void this.listeners.onClick(event, currentNode, undefined);
2748
+ }
2749
+ /** hover */
2750
+ this.area.addEventListener("mousemove", onHover.bind(this), {
2751
+ signal: this.eventAbortController.signal,
2752
+ });
2753
+ this.area.addEventListener("touchmove", onHover.bind(this), {
2754
+ signal: this.eventAbortController.signal,
2755
+ });
2756
+ /** dblclick */
2757
+ this.area.addEventListener("dblclick", onDoubleClick.bind(this), {
2758
+ signal: this.eventAbortController.signal,
2759
+ });
2760
+ /** wheel click */
2761
+ this.area.addEventListener("mousedown", onWheelClick.bind(this), {
2762
+ signal: this.eventAbortController.signal,
2763
+ });
2764
+ /** click */
2765
+ this.area.addEventListener("click", onClick.bind(this), {
2766
+ signal: this.eventAbortController.signal,
2767
+ });
2768
+ /** right click */
2769
+ this.area.addEventListener("contextmenu", onRightClick.bind(this), {
2770
+ signal: this.eventAbortController.signal,
2771
+ });
1119
2772
  }
1120
- function getLinkControls(keys) {
1121
- const LINK_CONTROLS = [
1122
- {
1123
- id: "alpha",
1124
- type: "range",
1125
- max: 1,
1126
- min: 0,
1127
- step: 0.1,
1128
- label: "Прозрачность",
1129
- initialValue: LINK_OPTIONS.alpha,
1130
- },
1131
- {
1132
- id: "width",
1133
- type: "range",
1134
- max: 10,
1135
- min: 0.1,
1136
- step: 0.1,
1137
- label: "Толщина",
1138
- initialValue: 0.1,
1139
- },
1140
- {
1141
- id: "arrowAlpha",
1142
- type: "range",
1143
- max: 1,
1144
- min: 0,
1145
- step: 0.1,
1146
- label: "Прозрачность стрелки",
1147
- initialValue: LINK_OPTIONS.arrowAlpha,
1148
- },
1149
- {
1150
- id: "arrowSize",
1151
- type: "range",
1152
- max: 10,
1153
- min: 0.1,
1154
- step: 0.1,
1155
- label: "Размер стрелки",
1156
- initialValue: LINK_OPTIONS.arrowSize,
1157
- },
1158
- {
1159
- id: "particleAlpha",
1160
- type: "range",
1161
- max: 1,
1162
- min: 0,
1163
- step: 0.1,
1164
- label: "Прозрачность частиц",
1165
- initialValue: LINK_OPTIONS.particleAlpha,
1166
- },
1167
- {
1168
- id: "particleRadius",
1169
- type: "range",
1170
- max: 10,
1171
- min: 0.1,
1172
- step: 0.1,
1173
- label: "Радиус частиц",
1174
- initialValue: LINK_OPTIONS.particleRadius,
1175
- },
1176
- {
1177
- id: "particleCount",
1178
- type: "range",
1179
- max: 20,
1180
- min: 1,
1181
- step: 1,
1182
- label: "Количество частиц",
1183
- initialValue: LINK_OPTIONS.particleCount,
1184
- },
1185
- {
1186
- id: "particleSteps",
1187
- type: "range",
1188
- max: 200,
1189
- min: 1,
1190
- step: 1,
1191
- label: "Количество кадров у частиц",
1192
- initialValue: LINK_OPTIONS.particleSteps,
1193
- },
1194
- {
1195
- id: "color",
1196
- type: "color",
1197
- initialValue: "#000000FF",
1198
- label: "Цвет",
1199
- },
1200
- {
1201
- id: "arrowColor",
1202
- type: "color",
1203
- initialValue: "#000000FF",
1204
- label: "Цвет стрелки",
1205
- },
1206
- {
1207
- id: "particleColor",
1208
- type: "color",
1209
- initialValue: "#000000FF",
1210
- label: "Цвет частиц",
1211
- },
1212
- {
1213
- id: "pretty",
1214
- type: "checkbox",
1215
- label: "Обсчет соединения",
1216
- initialValue: LINK_OPTIONS.pretty,
1217
- },
1218
- {
1219
- id: "arrow",
1220
- type: "checkbox",
1221
- label: "Стрелка",
1222
- initialValue: LINK_OPTIONS.arrow,
1223
- },
1224
- {
1225
- id: "highlightByNodeLinkFading",
1226
- type: "checkbox",
1227
- label: "Затухание при анимации ноды",
1228
- initialValue: LINK_OPTIONS.highlightByNodeLinkFading,
1229
- },
1230
- {
1231
- id: "highlightByNodeArrowFading",
1232
- type: "checkbox",
1233
- label: "Затухание стрелки при анимации ноды",
1234
- initialValue: LINK_OPTIONS.highlightByNodeArrowFading,
1235
- },
1236
- {
1237
- id: "highlightByLinkLinkFading",
1238
- type: "checkbox",
1239
- label: "Затухание при анимации связи",
1240
- initialValue: LINK_OPTIONS.highlightByLinkLinkFading,
1241
- },
1242
- {
1243
- id: "highlightByLinkArrowFading",
1244
- type: "checkbox",
1245
- label: "Затухание стрелки при анимации связи",
1246
- initialValue: LINK_OPTIONS.highlightByLinkArrowFading,
1247
- },
1248
- {
1249
- id: "arrowReverseAppear",
1250
- type: "checkbox",
1251
- label: "Появление стрелки при анимации",
1252
- initialValue: LINK_OPTIONS.arrowReverseAppear,
1253
- },
1254
- ];
1255
- return keys ? LINK_CONTROLS.filter((control) => keys.includes(control.id)) : LINK_CONTROLS;
2773
+
2774
+ function initResize() {
2775
+ if (!this.area)
2776
+ throw new Error("bad init data");
2777
+ let initialResizeCall = true;
2778
+ const abortController = this.eventAbortController;
2779
+ const observer = new ResizeObserver(() => {
2780
+ if (initialResizeCall) {
2781
+ initialResizeCall = false;
2782
+ return;
2783
+ }
2784
+ if (abortController.signal.aborted) {
2785
+ observer.disconnect();
2786
+ return;
2787
+ }
2788
+ requestAnimationFrame(() => {
2789
+ this.updateSize();
2790
+ });
2791
+ });
2792
+ document.addEventListener("scroll", this.updateRect.bind(this), {
2793
+ capture: true,
2794
+ passive: true,
2795
+ signal: abortController.signal,
2796
+ });
2797
+ observer.observe(this.area);
1256
2798
  }
1257
2799
 
1258
- let countTime = 0;
1259
- let countOperation = 0;
1260
- function setDrawTime(cb, showEveryTick) {
1261
- return () => {
1262
- const start = performance.now();
1263
- cb();
1264
- const end = performance.now();
1265
- const result = end - start;
1266
- countTime += result;
1267
- countOperation++;
1268
- if (showEveryTick) {
1269
- const average = countTime / countOperation;
1270
- // eslint-disable-next-line no-console
1271
- console.log("average draw: ", average, "current draw: ", result);
2800
+ function initSimulation() {
2801
+ if (!this.simulation) {
2802
+ this.simulation = d3Force.forceSimulation()
2803
+ .nodes(this.nodes)
2804
+ .force("link", d3Force.forceLink(this.links).id(this.nodeSettings.idGetter))
2805
+ .on("tick", () => {
2806
+ this.draw();
2807
+ })
2808
+ .on("end", () => {
2809
+ this.listeners.onSimulationEnd?.(this.state);
2810
+ if (this.graphSettings.showDrawTime) {
2811
+ getDrawTime();
2812
+ // eslint-disable-next-line no-console
2813
+ console.log(`nodes: ${this.nodes.length} | links: ${this.links.length}`);
2814
+ resetDrawTime();
2815
+ }
2816
+ });
2817
+ initSimulationForces.call(this);
2818
+ }
2819
+ }
2820
+ function initSimulationForces() {
2821
+ if (!this.simulation)
2822
+ return;
2823
+ const linkForce = this.simulation.force("link");
2824
+ if (!linkForce)
2825
+ return;
2826
+ if ((!this.forceSettings.forces || !this.forceSettings.xForce) && this.simulation.force("x")) {
2827
+ this.simulation.force("x", null);
2828
+ }
2829
+ if ((!this.forceSettings.forces || !this.forceSettings.yForce) && this.simulation.force("y")) {
2830
+ this.simulation.force("y", null);
2831
+ }
2832
+ if ((!this.forceSettings.forces || !this.forceSettings.chargeForce) &&
2833
+ this.simulation.force("charge")) {
2834
+ this.simulation.force("charge", null);
2835
+ }
2836
+ if ((!this.forceSettings.forces || !this.forceSettings.centerForce) &&
2837
+ this.simulation.force("center")) {
2838
+ this.simulation.force("center", null);
2839
+ }
2840
+ if (!this.forceSettings.forces || !this.forceSettings.linkForce) {
2841
+ linkForce.distance(0).strength(0).iterations(0);
2842
+ }
2843
+ if (this.forceSettings.forces && this.forceSettings.linkForce) {
2844
+ linkForce
2845
+ .distance(this.forceSettings.linkDistance)
2846
+ .strength(this.forceSettings.linkStrength)
2847
+ .iterations(this.forceSettings.linkIterations);
2848
+ }
2849
+ if (this.forceSettings.forces && this.forceSettings.xForce) {
2850
+ this.simulation.force("x", d3Force.forceX(this.forceSettings.xPosition).strength(this.forceSettings.xStrength));
2851
+ }
2852
+ if (this.forceSettings.forces && this.forceSettings.yForce) {
2853
+ this.simulation.force("y", d3Force.forceY(this.forceSettings.yPosition).strength(this.forceSettings.yStrength));
2854
+ }
2855
+ if (this.forceSettings.forces && this.forceSettings.chargeForce) {
2856
+ this.simulation.force("charge", d3Force.forceManyBody()
2857
+ .strength(this.forceSettings.chargeStrength)
2858
+ .distanceMax(this.forceSettings.chargeDistanceMax)
2859
+ .distanceMin(this.forceSettings.chargeDistanceMin));
2860
+ }
2861
+ if (this.forceSettings.forces && this.forceSettings.centerForce) {
2862
+ this.simulation.force("center", d3Force.forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
2863
+ }
2864
+ initCollideForce.call(this, true);
2865
+ }
2866
+ function initCollideForce(forceUpdate) {
2867
+ if (!this.simulation)
2868
+ return;
2869
+ if ((!this.forceSettings.collideForce || !this.forceSettings.forces) &&
2870
+ this.simulation.force("collide")) {
2871
+ this.simulation.force("collide", null);
2872
+ }
2873
+ if (this.forceSettings.forces && this.forceSettings.collideForce) {
2874
+ const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
2875
+ const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
2876
+ const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
2877
+ if (isMaxCollideNodes && isMaxCollideLinks) {
2878
+ this.simulation.force("collide", null);
2879
+ }
2880
+ else if (!this.simulation.force("collide") || forceUpdate) {
2881
+ this.simulation.force("collide", d3Force.forceCollide()
2882
+ .radius((node, index) => {
2883
+ const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
2884
+ switch (nodeOptions.shape) {
2885
+ case "circle": {
2886
+ if (this.forceSettings.collideRadius) {
2887
+ return nodeIterationExtractor(node, index, this.nodes, this.state, this.forceSettings.collideRadius, undefined);
2888
+ }
2889
+ const radius = nodeRadiusGetter({
2890
+ radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2891
+ radiusInitial: nodeOptions.radius,
2892
+ radiusCoefficient: this.nodeSettings.nodeRadiusCoefficient,
2893
+ radiusFactor: this.nodeSettings.nodeRadiusFactor,
2894
+ linkCount: node.linkCount,
2895
+ });
2896
+ return radius + this.forceSettings.collideAdditionalRadius;
2897
+ }
2898
+ case "square": {
2899
+ const { height, width } = nodeSizeGetter({
2900
+ heightInitial: nodeOptions.width,
2901
+ widthInitial: nodeOptions.height,
2902
+ linkCount: node.linkCount,
2903
+ sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
2904
+ sizeFactor: this.nodeSettings.nodeSizeFactor,
2905
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2906
+ });
2907
+ return (Math.sqrt(width ** 2 + height ** 2) / 2 +
2908
+ this.forceSettings.collideAdditionalRadius);
2909
+ }
2910
+ case "text": {
2911
+ let { height, width } = nodeSizeGetter({
2912
+ heightInitial: nodeOptions.width,
2913
+ widthInitial: nodeOptions.height,
2914
+ linkCount: node.linkCount,
2915
+ sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
2916
+ sizeFactor: this.nodeSettings.nodeSizeFactor,
2917
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2918
+ });
2919
+ if (this.context && nodeOptions.text) {
2920
+ const lines = this.cachedNodeText[node.id] ??
2921
+ getTextLines({
2922
+ context: this.context,
2923
+ text: nodeOptions.text,
2924
+ textAlign: nodeOptions.textAlign,
2925
+ textColor: nodeOptions.textColor,
2926
+ textFont: nodeOptions.textFont,
2927
+ textSize: nodeOptions.textSize,
2928
+ maxWidth: width,
2929
+ textStyle: nodeOptions.textStyle,
2930
+ textWeight: nodeOptions.textWeight,
2931
+ });
2932
+ height =
2933
+ lines.length * nodeOptions.textSize +
2934
+ (lines.length - 1) * nodeOptions.textGap +
2935
+ nodeOptions.textNodeYPadding;
2936
+ width += nodeOptions.textNodeXPadding;
2937
+ }
2938
+ return (Math.sqrt(width ** 2 + height ** 2) / 2 +
2939
+ this.forceSettings.collideAdditionalRadius);
2940
+ }
2941
+ default: {
2942
+ if (this.forceSettings.collideRadius) {
2943
+ return nodeIterationExtractor(node, index, this.nodes, this.state, this.forceSettings.collideRadius, undefined);
2944
+ }
2945
+ const radius = nodeRadiusGetter({
2946
+ radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2947
+ radiusInitial: nodeOptions.radius,
2948
+ radiusCoefficient: this.nodeSettings.nodeRadiusCoefficient,
2949
+ radiusFactor: this.nodeSettings.nodeRadiusFactor,
2950
+ linkCount: node.linkCount,
2951
+ });
2952
+ return radius + this.forceSettings.collideAdditionalRadius;
2953
+ }
2954
+ }
2955
+ })
2956
+ .strength(this.forceSettings.collideStrength)
2957
+ .iterations(this.forceSettings.collideIterations));
1272
2958
  }
1273
- };
2959
+ }
1274
2960
  }
1275
- function getDrawTime() {
1276
- const average = countTime / countOperation;
1277
- // eslint-disable-next-line no-console
1278
- console.log("average draw: ", average);
2961
+
2962
+ function initZoom(currentZoom) {
2963
+ if (!this.area)
2964
+ throw new Error("bad init data");
2965
+ const zoomInstance = d3Zoom.zoom()
2966
+ .scaleExtent(this.graphSettings.zoomExtent)
2967
+ .on("zoom", (event) => {
2968
+ this.listeners.onZoom?.(event);
2969
+ this.areaTransform = event.transform;
2970
+ this.linkOptionsCache = {};
2971
+ this.nodeOptionsCache = {};
2972
+ if (!this.simulationWorking && !this.highlightWorking)
2973
+ requestAnimationFrame(() => this.draw());
2974
+ });
2975
+ if (this.graphSettings.translateExtentEnable) {
2976
+ const coefficient = this.graphSettings.translateExtentCoefficient;
2977
+ const [coefficientX, coefficientY] = jsHelpers.isArray(coefficient)
2978
+ ? coefficient
2979
+ : [coefficient, coefficient];
2980
+ const [[minX = -this.width * coefficientX, minY = -this.height * coefficientY], [maxX = this.width * coefficientX, maxY = this.height * coefficientY],] = this.graphSettings.translateExtent;
2981
+ zoomInstance.translateExtent([
2982
+ [minX, minY],
2983
+ [maxX, maxY],
2984
+ ]);
2985
+ }
2986
+ d3Selection.select(this.area).call(zoomInstance).on("dblclick.zoom", null);
2987
+ const zoomInitial = currentZoom ?? this.graphSettings.zoomInitial;
2988
+ this.areaTransform = new d3Zoom.ZoomTransform(zoomInitial?.k ?? 1, zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
2989
+ d3Zoom.zoom().transform(d3Selection.select(this.area), this.areaTransform);
1279
2990
  }
1280
2991
 
1281
2992
  class GraphCanvas {
@@ -1357,7 +3068,7 @@ class GraphCanvas {
1357
3068
  this.links = links;
1358
3069
  this.height = 0;
1359
3070
  this.width = 0;
1360
- this.draw = this.initDraw();
3071
+ this.draw = initDraw.call(this);
1361
3072
  this.init();
1362
3073
  }
1363
3074
  get dpi() {
@@ -1380,8 +3091,8 @@ class GraphCanvas {
1380
3091
  changeSettings(options) {
1381
3092
  if (options.graphSettings) {
1382
3093
  this.graphSettings = graphSettingsGetter(options.graphSettings, this.graphSettings);
1383
- this.draw = this.initDraw();
1384
- this.initZoom(this.areaTransform);
3094
+ this.draw = initDraw.call(this);
3095
+ initZoom.call(this, this.areaTransform);
1385
3096
  }
1386
3097
  if (options.forceSettings) {
1387
3098
  this.forceSettings = forceSettingsGetter(options.forceSettings, this.forceSettings);
@@ -1394,6 +3105,7 @@ class GraphCanvas {
1394
3105
  this.nodeSettings = nodeSettingsGetter(options.nodeSettings, this.nodeSettings);
1395
3106
  this.cachedNodeText = {};
1396
3107
  this.nodeOptionsCache = {};
3108
+ initCollideForce.call(this, true);
1397
3109
  }
1398
3110
  if (options.forceSettings) {
1399
3111
  return void this.updateSimulation();
@@ -1451,7 +3163,7 @@ class GraphCanvas {
1451
3163
  }
1452
3164
  updateSimulation() {
1453
3165
  if (this.simulation) {
1454
- this.initSimulationForces();
3166
+ initSimulationForces.call(this);
1455
3167
  this.simulation.alpha(1);
1456
3168
  this.simulation.restart();
1457
3169
  }
@@ -1473,933 +3185,59 @@ class GraphCanvas {
1473
3185
  updateData(alpha) {
1474
3186
  this.clearDataDependencies();
1475
3187
  if (this.simulation) {
1476
- this.initCollideForce();
3188
+ initCollideForce.call(this, false);
1477
3189
  this.simulation
1478
3190
  .nodes(this.nodes)
1479
3191
  .force("link", d3Force.forceLink(this.links)
1480
3192
  .id(this.nodeSettings.idGetter)
1481
- .distance(this.forceSettings.linkDistance)
1482
- .strength(this.forceSettings.linkStrength)
1483
- .iterations(this.forceSettings.linkIterations))
3193
+ .distance(this.forceSettings.forces && this.forceSettings.linkForce
3194
+ ? this.forceSettings.linkDistance
3195
+ : 0)
3196
+ .strength(this.forceSettings.forces && this.forceSettings.linkForce
3197
+ ? this.forceSettings.linkStrength
3198
+ : 0)
3199
+ .iterations(this.forceSettings.forces && this.forceSettings.linkForce
3200
+ ? this.forceSettings.linkIterations
3201
+ : 0))
1484
3202
  .alpha(alpha ?? 0.5)
1485
3203
  .restart();
1486
3204
  }
1487
3205
  }
1488
3206
  updateSize() {
1489
3207
  this.clearHTMLElements();
1490
- this.initArea();
1491
- this.initDnd();
1492
- this.initZoom();
1493
- this.initResize();
1494
- this.initPointer();
3208
+ initArea.call(this);
3209
+ initDnd.call(this);
3210
+ initZoom.call(this);
3211
+ initResize.call(this);
3212
+ initPointer.call(this);
1495
3213
  if (!this.simulationWorking && !this.highlightWorking)
1496
3214
  this.draw();
1497
3215
  }
1498
3216
  init() {
1499
- this.initArea();
1500
- this.initSimulation();
1501
- this.initDnd();
1502
- this.initZoom();
1503
- this.initResize();
1504
- this.initPointer();
1505
- }
1506
- initSimulation() {
1507
- if (!this.simulation) {
1508
- this.simulation = d3Force.forceSimulation()
1509
- .nodes(this.nodes)
1510
- .force("link", d3Force.forceLink(this.links).id(this.nodeSettings.idGetter))
1511
- .on("tick", () => {
1512
- this.draw();
1513
- })
1514
- .on("end", () => {
1515
- this.listeners.onSimulationEnd?.(this.state);
1516
- if (this.graphSettings.showDrawTime)
1517
- getDrawTime();
1518
- });
1519
- this.initSimulationForces();
1520
- }
1521
- }
1522
- initSimulationForces() {
1523
- if (!this.simulation)
1524
- return;
1525
- const linkForce = this.simulation.force("link");
1526
- if (!linkForce)
1527
- return;
1528
- linkForce
1529
- .distance(this.forceSettings.linkDistance)
1530
- .strength(this.forceSettings.linkStrength)
1531
- .iterations(this.forceSettings.linkIterations);
1532
- this.simulation
1533
- .force("x", d3Force.forceX(this.forceSettings.xForce).strength(this.forceSettings.xStrength))
1534
- .force("y", d3Force.forceY(this.forceSettings.yForce).strength(this.forceSettings.yStrength))
1535
- .force("charge", d3Force.forceManyBody()
1536
- .strength(this.forceSettings.chargeStrength)
1537
- .distanceMax(this.forceSettings.chargeDistanceMax)
1538
- .distanceMin(this.forceSettings.chargeDistanceMin))
1539
- .force("center", d3Force.forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
1540
- this.initCollideForce(true);
1541
- }
1542
- initCollideForce(forceUpdate) {
1543
- if (!this.simulation)
1544
- return;
1545
- if (!this.forceSettings.collideOn) {
1546
- if (this.simulation.force("collide"))
1547
- this.simulation.force("collide", null);
1548
- return;
1549
- }
1550
- const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
1551
- const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
1552
- const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
1553
- if (isMaxCollideNodes && isMaxCollideLinks) {
1554
- this.simulation.force("collide", null);
1555
- }
1556
- else if (!this.simulation.force("collide") || forceUpdate) {
1557
- this.simulation.force("collide", d3Force.forceCollide()
1558
- .radius((node, index) => {
1559
- if (this.forceSettings.collideRadius) {
1560
- return nodeIterationExtractor(node, index, this.nodes, this.state, this.forceSettings.collideRadius, undefined);
1561
- }
1562
- const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
1563
- const radius = nodeRadiusGetter({
1564
- radiusFlexible: this.graphSettings.nodeRadiusFlexible,
1565
- radiusInitial: nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial,
1566
- radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
1567
- radiusFactor: this.graphSettings.nodeRadiusFactor,
1568
- linkCount: node.linkCount,
1569
- });
1570
- return radius + this.forceSettings.collideAdditionalRadius;
1571
- })
1572
- .strength(this.forceSettings.collideStrength)
1573
- .iterations(this.forceSettings.collideIterations));
1574
- }
1575
- }
1576
- initArea() {
1577
- if (!this.area || !this.context || !this.container) {
1578
- this.container = d3Selection.create("div")
1579
- .attr("style", "padding: 0 !important; width: 100%; height: 100%;")
1580
- .node();
1581
- if (!this.container)
1582
- throw new Error("couldn't create container");
1583
- this.root.appendChild(this.container);
1584
- const { width, height } = this.root.getBoundingClientRect();
1585
- this.width = width;
1586
- this.height = height;
1587
- this.area = d3Selection.create("canvas")
1588
- .attr("width", this.dpi * this.width)
1589
- .attr("height", this.dpi * this.height)
1590
- .attr("style", `width: 100%; height: 100%; border: none !important;`)
1591
- .node();
1592
- if (!this.area)
1593
- throw new Error("couldn't create canvas");
1594
- this.container.appendChild(this.area);
1595
- this.areaRect = this.area.getBoundingClientRect();
1596
- this.context = this.area.getContext("2d");
1597
- if (!this.context)
1598
- throw new Error("couldn't create canvas context");
1599
- this.context.scale(this.dpi, this.dpi);
1600
- }
1601
- }
1602
- initDraw() {
1603
- function calculateHighlightFading() {
1604
- this.highlightDrawing = true;
1605
- if (!this.highlightWorking && this.highlightProgress > 0) {
1606
- this.highlightProgress -= this.graphSettings.highlightDownStep;
1607
- if (!this.simulationWorking)
1608
- return void requestAnimationFrame(() => this.draw());
1609
- if (!this.linkSettings.particles)
1610
- return;
1611
- }
1612
- if (this.highlightWorking && this.highlightProgress < 1) {
1613
- this.highlightProgress += this.graphSettings.highlightUpStep;
1614
- if (!this.simulationWorking)
1615
- return void requestAnimationFrame(() => this.draw());
1616
- if (!this.linkSettings.particles)
1617
- return;
1618
- }
1619
- if (this.linkSettings.particles && this.highlightWorking && !this.simulationWorking) {
1620
- return void requestAnimationFrame(() => this.draw());
1621
- }
1622
- if (!this.highlightWorking && this.highlightProgress <= 0) {
1623
- if (this.highlightedNeighbors || this.highlightedNode || this.highlightedLink) {
1624
- this.highlightedNeighbors = null;
1625
- this.highlightedNode = null;
1626
- this.highlightedLink = null;
1627
- this.particles = {};
1628
- if (!this.simulationWorking)
1629
- return void requestAnimationFrame(() => this.draw());
1630
- }
1631
- }
1632
- this.highlightDrawing = false;
1633
- }
1634
- function draw() {
1635
- if (!this.context)
1636
- return;
1637
- const state = this.state;
1638
- if (this.listeners.onDraw) {
1639
- this.listeners.onDraw(state, (status) => {
1640
- this.highlightDrawing = status;
1641
- }, () => {
1642
- if (this.highlightedNeighbors)
1643
- this.highlightedNeighbors = null;
1644
- if (this.highlightedNode)
1645
- this.highlightedNode = null;
1646
- if (this.highlightedLink)
1647
- this.highlightedLink = null;
1648
- });
1649
- return;
1650
- }
1651
- this.context.save();
1652
- this.context.clearRect(0, 0, this.width, this.height);
1653
- this.context.translate(this.areaTransform.x, this.areaTransform.y);
1654
- this.context.scale(this.areaTransform.k, this.areaTransform.k);
1655
- const textRenders = [];
1656
- const nodeRenders = [];
1657
- this.nodes.forEach(getDrawNode(nodeRenders, textRenders, state).bind(this));
1658
- /** links */
1659
- this.links.forEach(getDrawLink(state).bind(this));
1660
- /** nodes */
1661
- nodeRenders.forEach((render) => render());
1662
- textRenders.forEach((render) => render());
1663
- this.context.restore();
1664
- this.listeners.onDrawFinished?.(state);
1665
- calculateHighlightFading.bind(this)();
1666
- }
1667
- function getDrawLink(state) {
1668
- return function drawLink(link, index) {
1669
- if (!this.context ||
1670
- typeof link.source !== "object" ||
1671
- typeof link.target !== "object" ||
1672
- !link.source.x ||
1673
- !link.source.y ||
1674
- !link.target.x ||
1675
- !link.target.y)
1676
- return;
1677
- if (!link.source._visible && !link.target._visible)
1678
- return;
1679
- const id = `${link.target.id}${link.source.id}`;
1680
- let linkOptions;
1681
- if (this.linkSettings.cache && this.linkOptionsCache[id]) {
1682
- linkOptions = this.linkOptionsCache[id];
1683
- }
1684
- else {
1685
- linkOptions = linkIterationExtractor(link, index, this.links, state, this.linkSettings.options ?? {}, linkOptionsGetter);
1686
- if (this.linkSettings.cache) {
1687
- this.linkOptionsCache[id] = linkOptions;
1688
- }
1689
- }
1690
- if (linkOptions.drawLink) {
1691
- linkOptions.drawLink(link, linkOptions, state);
1692
- return;
1693
- }
1694
- let alpha = linkOptions.alpha;
1695
- let arrowAlpha = linkOptions.arrowReverseAppear ? 0 : linkOptions.arrowAlpha;
1696
- /** NODE HIGHLIGHT */
1697
- if (this.highlightedNeighbors && this.highlightedNode) {
1698
- /** Not highlighted */
1699
- if (this.highlightedNode.id != link.source.id &&
1700
- this.highlightedNode.id != link.target.id) {
1701
- if (linkOptions.highlightByNodeLinkFading) {
1702
- const min = this.graphSettings.highlightByNodeLinkFadingMin < alpha
1703
- ? this.graphSettings.highlightByNodeLinkFadingMin
1704
- : alpha;
1705
- alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
1706
- }
1707
- if (linkOptions.arrow &&
1708
- linkOptions.highlightByNodeArrowFading &&
1709
- !linkOptions.arrowReverseAppear) {
1710
- const min = this.graphSettings.highlightByNodeArrowFadingMin < arrowAlpha
1711
- ? this.graphSettings.highlightByNodeArrowFadingMin
1712
- : arrowAlpha;
1713
- arrowAlpha = animationByProgress(min, arrowAlpha - min, 1 - this.highlightProgress);
1714
- }
1715
- }
1716
- else {
1717
- // eslint-disable-next-line no-lonely-if
1718
- if (linkOptions.arrow && linkOptions.arrowReverseAppear) {
1719
- /** Highlighted */
1720
- arrowAlpha = animationByProgress(0, linkOptions.arrowAlpha, this.highlightProgress);
1721
- }
1722
- }
1723
- }
1724
- /** LINK HIGHLIGHT */
1725
- if (this.highlightedNeighbors && this.highlightedLink) {
1726
- /** Not highlighted */
1727
- if (this.highlightedLink !== link) {
1728
- if (linkOptions.highlightByLinkLinkFading) {
1729
- const min = this.graphSettings.highlightByLinkLinkFadingMin < alpha
1730
- ? this.graphSettings.highlightByLinkLinkFadingMin
1731
- : alpha;
1732
- alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
1733
- }
1734
- if (linkOptions.arrow &&
1735
- linkOptions.highlightByLinkArrowFading &&
1736
- !linkOptions.arrowReverseAppear) {
1737
- const min = this.graphSettings.highlightByLinkArrowFadingMin < arrowAlpha
1738
- ? this.graphSettings.highlightByLinkArrowFadingMin
1739
- : arrowAlpha;
1740
- arrowAlpha = animationByProgress(min, arrowAlpha - min, 1 - this.highlightProgress);
1741
- }
1742
- }
1743
- else {
1744
- // eslint-disable-next-line no-lonely-if
1745
- if (linkOptions.arrow && linkOptions.arrowReverseAppear) {
1746
- /** Highlighted */
1747
- arrowAlpha = animationByProgress(0, linkOptions.arrowAlpha, this.highlightProgress);
1748
- }
1749
- }
1750
- }
1751
- /** Link */
1752
- this.context.beginPath();
1753
- this.context.globalAlpha = alpha;
1754
- this.context.strokeStyle = linkOptions.color;
1755
- this.context.lineWidth = linkOptions.width;
1756
- let xStart = link.source.x;
1757
- let yStart = link.source.y;
1758
- let xEnd = link.target.x;
1759
- let yEnd = link.target.y;
1760
- if (linkOptions.pretty) {
1761
- const isHasArrow = linkOptions.arrow && arrowAlpha > 0;
1762
- const { x1, x2, y1, y2 } = calculateLinkPositionByRadius(link, isHasArrow ? linkOptions.arrowSize : 0) ?? {
1763
- x1: 0,
1764
- x2: 0,
1765
- y1: 0,
1766
- y2: 0,
1767
- };
1768
- xStart = x1;
1769
- xEnd = x2;
1770
- yStart = y1;
1771
- yEnd = y2;
1772
- }
1773
- this.context.moveTo(xStart, yStart);
1774
- this.context.lineTo(xEnd, yEnd);
1775
- this.context.stroke();
1776
- /** Particle */
1777
- if (this.linkSettings.particles &&
1778
- ((this.highlightedNode &&
1779
- (this.highlightedNode.id === link.source.id ||
1780
- this.highlightedNode.id === link.target.id)) ||
1781
- (this.highlightedLink && this.highlightedLink === link))) {
1782
- if (!this.particles[id]) {
1783
- const sourceId = link.source.id;
1784
- const targetId = link.target.id;
1785
- this.particles[id] = Array.from({ length: linkOptions.particleCount }, (_, index) => {
1786
- return {
1787
- step: 0,
1788
- wait: index * (linkOptions.particleSteps / linkOptions.particleCount),
1789
- sourceId,
1790
- targetId,
1791
- };
1792
- });
1793
- }
1794
- this.particles[id].forEach((particle) => {
1795
- if (!this.context)
1796
- return;
1797
- getParticlePosition({
1798
- particle,
1799
- totalSteps: linkOptions.particleSteps,
1800
- xEnd,
1801
- xStart,
1802
- yEnd,
1803
- yStart,
1804
- });
1805
- if (particle.x != undefined && particle.y != undefined) {
1806
- this.context.beginPath();
1807
- this.context.arc(particle.x, particle.y, linkOptions.particleRadius, 0, Math.PI * 2);
1808
- this.context.fillStyle = linkOptions.particleColor;
1809
- this.context.fill();
1810
- this.context.stroke();
1811
- }
1812
- });
1813
- }
1814
- /** Arrow */
1815
- if (linkOptions.arrow && arrowAlpha > 0) {
1816
- const { x1: xStart, x2: xEnd, y1: yStart, y2: yEnd, } = calculateLinkPositionByRadius(link) ?? {
1817
- x1: 0,
1818
- x2: 0,
1819
- y1: 0,
1820
- y2: 0,
1821
- };
1822
- const angle = Math.atan2(yEnd - yStart, xEnd - xStart);
1823
- this.context.beginPath();
1824
- this.context.globalAlpha = arrowAlpha;
1825
- this.context.moveTo(xEnd, yEnd);
1826
- this.context.lineTo(xEnd - linkOptions.arrowSize * Math.cos(angle - Math.PI / 6), yEnd - linkOptions.arrowSize * Math.sin(angle - Math.PI / 6));
1827
- this.context.lineTo(xEnd - linkOptions.arrowSize * Math.cos(angle + Math.PI / 6), yEnd - linkOptions.arrowSize * Math.sin(angle + Math.PI / 6));
1828
- this.context.closePath();
1829
- this.context.fillStyle = linkOptions.arrowColor;
1830
- this.context.fill();
1831
- this.context.stroke();
1832
- }
1833
- if (linkOptions.drawExtraLink) {
1834
- linkOptions.drawExtraLink(link, { ...linkOptions, alpha }, state);
1835
- }
1836
- };
1837
- }
1838
- function getDrawNode(nodeRenders, textRenders, state) {
1839
- return function drawNode(node, index) {
1840
- if (!this.context || !node.x || !node.y)
1841
- return;
1842
- let nodeOptions;
1843
- if (this.nodeSettings.cache && this.nodeOptionsCache[node.id]) {
1844
- nodeOptions = this.nodeOptionsCache[node.id];
1845
- }
1846
- else {
1847
- nodeOptions = nodeIterationExtractor(node, index, this.nodes, state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
1848
- if (this.nodeSettings.cache) {
1849
- this.nodeOptionsCache[node.id] = nodeOptions;
1850
- }
1851
- }
1852
- if (nodeOptions.nodeDraw && nodeOptions.textDraw) {
1853
- nodeRenders.push(() => {
1854
- nodeOptions?.nodeDraw?.(node, nodeOptions, state);
1855
- });
1856
- textRenders.push(() => {
1857
- nodeOptions?.textDraw?.(node, nodeOptions, state);
1858
- });
1859
- return;
1860
- }
1861
- let alpha = nodeOptions.alpha;
1862
- let color = nodeOptions.color;
1863
- let radiusInitial = nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial;
1864
- let textAlpha = nodeOptions.textAlpha;
1865
- let textSize = nodeOptions.textSize;
1866
- let textShiftX = nodeOptions.textShiftX;
1867
- let textShiftY = nodeOptions.textShiftY;
1868
- let textWeight = nodeOptions.textWeight;
1869
- let textWidth = nodeOptions.textWidth;
1870
- /** Node Highlight */
1871
- if (this.highlightedNeighbors && this.highlightedNode) {
1872
- /** Not highlighted */
1873
- if (!this.highlightedNeighbors.has(node.id) && this.highlightedNode.id != node.id) {
1874
- if (nodeOptions.highlightByNodeNodeFading) {
1875
- const min = this.graphSettings.highlightByNodeNodeFadingMin < alpha
1876
- ? this.graphSettings.highlightByNodeNodeFadingMin
1877
- : alpha;
1878
- alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
1879
- }
1880
- if (nodeOptions.highlightByNodeTextFading) {
1881
- const min = this.graphSettings.highlightByNodeTextFadingMin < textAlpha
1882
- ? this.graphSettings.highlightByNodeTextFadingMin
1883
- : textAlpha;
1884
- textAlpha = animationByProgress(min, textAlpha - min, 1 - this.highlightProgress);
1885
- }
1886
- if (nodeOptions.highlightByNodeNodeColor) {
1887
- const colorRgb = extractRgb(colorToRgb(color));
1888
- if (colorRgb) {
1889
- const colorRgbFade = fadeRgb(colorRgb, this.graphSettings.highlightByNodeNodeColorFadingMin);
1890
- const colorFadeAnimation = rgbAnimationByProgress(colorRgb, colorRgbFade, this.highlightProgress);
1891
- color = `rgb(${colorFadeAnimation.r}, ${colorFadeAnimation.g}, ${colorFadeAnimation.b})`;
1892
- }
1893
- }
1894
- }
1895
- else if (!this.graphSettings.highlightByNodeOnlyRoot ||
1896
- (this.graphSettings.highlightByNodeOnlyRoot && this.highlightedNode.id === node.id)) {
1897
- /** Highlighted */
1898
- if (nodeOptions.highlightByNodeNodeSizing) {
1899
- radiusInitial = animationByProgress(radiusInitial, this.graphSettings.highlightByNodeNodeSizingAdditional, this.highlightProgress);
1900
- }
1901
- if (nodeOptions.highlightByNodeTextSizing) {
1902
- textSize = animationByProgress(textSize, this.graphSettings.highlightByNodeTextSizingAdditional, this.highlightProgress);
1903
- textShiftX = animationByProgress(textShiftX, this.graphSettings.highlightByNodeTextShiftXAdditional, this.highlightProgress);
1904
- textShiftY = animationByProgress(textShiftY, this.graphSettings.highlightByNodeTextShiftYAdditional, this.highlightProgress);
1905
- textWeight = animationByProgress(textWeight, this.graphSettings.highlightByNodeTextWeightAdditional, this.highlightProgress);
1906
- textWidth = animationByProgress(textWidth, this.graphSettings.highlightByNodeTextWidthAdditional, this.highlightProgress);
1907
- }
1908
- }
1909
- }
1910
- /** LinkHighlight */
1911
- if (this.highlightedNeighbors && this.highlightedLink) {
1912
- /** Not highlighted */
1913
- if (!this.highlightedNeighbors.has(node.id) &&
1914
- this.highlightedLink.source !== node &&
1915
- this.highlightedLink.target !== node) {
1916
- if (nodeOptions.highlightByLinkNodeFading) {
1917
- const min = this.graphSettings.highlightByLinkNodeFadingMin < alpha
1918
- ? this.graphSettings.highlightByLinkNodeFadingMin
1919
- : alpha;
1920
- alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
1921
- }
1922
- if (nodeOptions.highlightByLinkTextFading) {
1923
- const min = this.graphSettings.highlightByLinkTextFadingMin < textAlpha
1924
- ? this.graphSettings.highlightByLinkTextFadingMin
1925
- : textAlpha;
1926
- textAlpha = animationByProgress(min, textAlpha - min, 1 - this.highlightProgress);
1927
- }
1928
- if (nodeOptions.highlightByLinkNodeColor) {
1929
- const colorRgb = extractRgb(colorToRgb(color));
1930
- if (colorRgb) {
1931
- const colorRgbFade = fadeRgb(colorRgb, this.graphSettings.highlightByLinkNodeColorFadingMin);
1932
- const colorFadeAnimation = rgbAnimationByProgress(colorRgb, colorRgbFade, this.highlightProgress);
1933
- color = `rgb(${colorFadeAnimation.r}, ${colorFadeAnimation.g}, ${colorFadeAnimation.b})`;
1934
- }
1935
- }
1936
- }
1937
- else {
1938
- /** Highlighted */
1939
- if (nodeOptions.highlightByLinkNodeSizing) {
1940
- radiusInitial = animationByProgress(radiusInitial, this.graphSettings.highlightByLinkNodeSizingAdditional, this.highlightProgress);
1941
- }
1942
- if (nodeOptions.highlightByLinkTextSizing) {
1943
- textSize = animationByProgress(textSize, this.graphSettings.highlightByLinkTextSizingAdditional, this.highlightProgress);
1944
- textShiftX = animationByProgress(textShiftX, this.graphSettings.highlightByLinkTextShiftXAdditional, this.highlightProgress);
1945
- textShiftY = animationByProgress(textShiftY, this.graphSettings.highlightByLinkTextShiftYAdditional, this.highlightProgress);
1946
- textWeight = animationByProgress(textWeight, this.graphSettings.highlightByLinkTextWeightAdditional, this.highlightProgress);
1947
- textWidth = animationByProgress(textWidth, this.graphSettings.highlightByLinkTextWidthAdditional, this.highlightProgress);
1948
- }
1949
- }
1950
- }
1951
- const radius = nodeRadiusGetter({
1952
- radiusFlexible: this.graphSettings.nodeRadiusFlexible,
1953
- radiusInitial,
1954
- radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
1955
- radiusFactor: this.graphSettings.nodeRadiusFactor,
1956
- linkCount: node.linkCount,
1957
- });
1958
- node._radius = radius;
1959
- if (!isNodeVisible({
1960
- height: this.height,
1961
- width: this.width,
1962
- x: node.x,
1963
- y: node.y,
1964
- radius,
1965
- transform: this.areaTransform,
1966
- })) {
1967
- node._visible = false;
1968
- return;
1969
- }
1970
- node._visible = true;
1971
- nodeRenders.push(() => {
1972
- if (!this.context || !node.x || !node.y)
1973
- return;
1974
- this.context.beginPath();
1975
- this.context.globalAlpha = alpha;
1976
- /** circle */
1977
- this.context.lineWidth = nodeOptions.borderWidth;
1978
- this.context.strokeStyle = nodeOptions.borderColor;
1979
- this.context.fillStyle = color;
1980
- this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
1981
- this.context.fill();
1982
- this.context.stroke();
1983
- });
1984
- if (nodeOptions.nodeExtraDraw) {
1985
- nodeRenders.push(() => {
1986
- nodeOptions?.nodeExtraDraw?.(node, {
1987
- ...nodeOptions,
1988
- radius,
1989
- alpha,
1990
- color,
1991
- textAlpha,
1992
- textSize,
1993
- textShiftX,
1994
- textShiftY,
1995
- textWeight,
1996
- textWidth,
1997
- }, state);
1998
- });
1999
- }
2000
- /** text */
2001
- if (nodeOptions.textVisible && nodeOptions.text) {
2002
- textRenders.push(() => {
2003
- if (nodeOptions.textDraw) {
2004
- nodeOptions.textDraw(node, {
2005
- ...nodeOptions,
2006
- radius,
2007
- alpha,
2008
- color,
2009
- textAlpha,
2010
- textSize,
2011
- textShiftX,
2012
- textShiftY,
2013
- textWeight,
2014
- textWidth,
2015
- }, state);
2016
- return;
2017
- }
2018
- if (!this.context || !node.x || !node.y || !nodeOptions.text)
2019
- return;
2020
- this.context.beginPath();
2021
- this.context.globalAlpha = textAlpha;
2022
- drawText({
2023
- id: node.id,
2024
- cachedNodeText: this.cachedNodeText,
2025
- context: this.context,
2026
- text: nodeOptions.text,
2027
- textAlign: nodeOptions.textAlign,
2028
- textColor: nodeOptions.textColor,
2029
- textFont: nodeOptions.textFont,
2030
- textSize,
2031
- x: node.x + textShiftX,
2032
- y: node.y + radius + textShiftY,
2033
- maxWidth: textWidth,
2034
- textStyle: nodeOptions.textStyle,
2035
- textWeight,
2036
- textGap: nodeOptions.textGap,
2037
- });
2038
- if (nodeOptions.textExtraDraw) {
2039
- nodeOptions.textExtraDraw(node, {
2040
- ...nodeOptions,
2041
- radius,
2042
- alpha,
2043
- color,
2044
- textAlpha,
2045
- textSize,
2046
- textShiftX,
2047
- textShiftY,
2048
- textWeight,
2049
- textWidth,
2050
- }, state);
2051
- }
2052
- });
2053
- }
2054
- };
2055
- }
2056
- if (this.graphSettings.showDrawTime) {
2057
- return setDrawTime(draw.bind(this), this.graphSettings.showDrawTimeEveryTick);
2058
- }
2059
- return draw;
2060
- }
2061
- initResize() {
2062
- if (!this.area)
2063
- throw new Error("bad init data");
2064
- let initialResizeCall = true;
2065
- const abortController = this.eventAbortController;
2066
- const observer = new ResizeObserver(() => {
2067
- if (initialResizeCall) {
2068
- initialResizeCall = false;
2069
- return;
2070
- }
2071
- if (abortController.signal.aborted) {
2072
- observer.disconnect();
2073
- return;
2074
- }
2075
- requestAnimationFrame(() => {
2076
- this.updateSize();
2077
- });
2078
- });
2079
- document.addEventListener("scroll", this.updateRect.bind(this), {
2080
- capture: true,
2081
- passive: true,
2082
- signal: abortController.signal,
2083
- });
2084
- observer.observe(this.area);
2085
- }
2086
- initPointer() {
2087
- if (!this.area || !this.nodes || !this.simulation)
2088
- throw new Error("bad init data");
2089
- function onHover(event) {
2090
- if (!this.area)
2091
- return;
2092
- let currentNode;
2093
- let currentLink;
2094
- const checkHighlightNode = this.graphSettings.highlightByHoverNode && !this.isDragging;
2095
- const checkHighlightLink = this.graphSettings.highlightByHoverLink && !this.isDragging;
2096
- if (checkHighlightNode) {
2097
- currentNode = nodeByPointerGetter({
2098
- graphSettings: this.graphSettings,
2099
- areaRect: this.areaRect,
2100
- areaTransform: this.areaTransform,
2101
- mouseEvent: event,
2102
- nodes: this.nodes,
2103
- });
2104
- }
2105
- if (currentNode) {
2106
- this.area.style.cursor = "pointer";
2107
- }
2108
- else if (checkHighlightLink) {
2109
- currentLink = linkByPointerGetter({
2110
- graphSettings: this.graphSettings,
2111
- areaRect: this.areaRect,
2112
- areaTransform: this.areaTransform,
2113
- mouseEvent: event,
2114
- links: this.links,
2115
- });
2116
- if (currentLink) {
2117
- this.area.style.cursor = "pointer";
2118
- }
2119
- else {
2120
- this.area.style.cursor = "default";
2121
- }
2122
- }
2123
- else {
2124
- this.area.style.cursor = "default";
2125
- }
2126
- if (currentNode && this.highlightedNode !== currentNode) {
2127
- this.highlightedNode = currentNode;
2128
- this.highlightedLink = null;
2129
- this.highlightedNeighbors = new Set(this.highlightedNode?.neighbors ?? []);
2130
- this.highlightWorking = true;
2131
- if (!this.simulationWorking && !this.highlightDrawing)
2132
- requestAnimationFrame(() => {
2133
- this.draw();
2134
- });
2135
- }
2136
- else if (currentLink &&
2137
- checkType(currentLink.source, jsHelpers.isObject(currentLink.source)) &&
2138
- checkType(currentLink.target, jsHelpers.isObject(currentLink.target)) &&
2139
- this.highlightedLink !== currentLink) {
2140
- this.highlightedLink = currentLink;
2141
- this.highlightedNode = null;
2142
- this.highlightedNeighbors = new Set([currentLink.source.id, currentLink.target.id]);
2143
- this.highlightWorking = true;
2144
- if (!this.simulationWorking && !this.highlightDrawing)
2145
- requestAnimationFrame(() => {
2146
- this.draw();
2147
- });
2148
- }
2149
- else if (!currentNode && !currentLink && (this.highlightedNode || this.highlightedLink)) {
2150
- this.highlightWorking = false;
2151
- if (!this.simulationWorking && !this.highlightDrawing)
2152
- requestAnimationFrame(() => {
2153
- this.draw();
2154
- });
2155
- }
2156
- if (!this.listeners.onMove)
2157
- return;
2158
- if (!currentNode && !checkHighlightNode)
2159
- currentNode = nodeByPointerGetter({
2160
- graphSettings: this.graphSettings,
2161
- areaRect: this.areaRect,
2162
- areaTransform: this.areaTransform,
2163
- mouseEvent: event,
2164
- nodes: this.nodes,
2165
- });
2166
- if (!currentNode && (!checkHighlightNode || (!checkHighlightLink && !currentLink))) {
2167
- currentLink = linkByPointerGetter({
2168
- graphSettings: this.graphSettings,
2169
- areaRect: this.areaRect,
2170
- areaTransform: this.areaTransform,
2171
- mouseEvent: event,
2172
- links: this.links,
2173
- });
2174
- }
2175
- if (!currentNode)
2176
- return void this.listeners.onMove(event, currentNode, currentLink);
2177
- }
2178
- function onWheelClick(event) {
2179
- if (this.isDragging ||
2180
- !this.listeners.onWheelClick ||
2181
- !("button" in event) ||
2182
- event.button !== 1)
2183
- return;
2184
- const currentNode = nodeByPointerGetter({
2185
- graphSettings: this.graphSettings,
2186
- areaRect: this.areaRect,
2187
- areaTransform: this.areaTransform,
2188
- mouseEvent: event,
2189
- nodes: this.nodes,
2190
- });
2191
- if (!currentNode) {
2192
- const currentLink = linkByPointerGetter({
2193
- graphSettings: this.graphSettings,
2194
- areaRect: this.areaRect,
2195
- areaTransform: this.areaTransform,
2196
- mouseEvent: event,
2197
- links: this.links,
2198
- });
2199
- return void this.listeners.onWheelClick(event, undefined, currentLink);
2200
- }
2201
- return void this.listeners.onWheelClick(event, currentNode, undefined);
2202
- }
2203
- function onRightClick(event) {
2204
- if (!this.listeners.onContextMenu)
2205
- return;
2206
- const currentNode = nodeByPointerGetter({
2207
- graphSettings: this.graphSettings,
2208
- areaRect: this.areaRect,
2209
- areaTransform: this.areaTransform,
2210
- mouseEvent: event,
2211
- nodes: this.nodes,
2212
- });
2213
- if (!currentNode) {
2214
- const currentLink = linkByPointerGetter({
2215
- graphSettings: this.graphSettings,
2216
- areaRect: this.areaRect,
2217
- areaTransform: this.areaTransform,
2218
- mouseEvent: event,
2219
- links: this.links,
2220
- });
2221
- return void this.listeners.onContextMenu(event, undefined, currentLink);
2222
- }
2223
- return void this.listeners.onContextMenu(event, currentNode, undefined);
2224
- }
2225
- function onDoubleClick(event) {
2226
- if (!this.listeners.onDoubleClick)
2227
- return;
2228
- const currentNode = nodeByPointerGetter({
2229
- graphSettings: this.graphSettings,
2230
- areaRect: this.areaRect,
2231
- areaTransform: this.areaTransform,
2232
- mouseEvent: event,
2233
- nodes: this.nodes,
2234
- });
2235
- if (!currentNode) {
2236
- const currentLink = linkByPointerGetter({
2237
- graphSettings: this.graphSettings,
2238
- areaRect: this.areaRect,
2239
- areaTransform: this.areaTransform,
2240
- mouseEvent: event,
2241
- links: this.links,
2242
- });
2243
- return void this.listeners.onDoubleClick(event, undefined, currentLink);
2244
- }
2245
- return void this.listeners.onDoubleClick(event, currentNode, undefined);
2246
- }
2247
- function onClick(event) {
2248
- if (this.isDragging || !this.listeners.onClick || ("button" in event && event.button !== 0))
2249
- return;
2250
- const currentNode = nodeByPointerGetter({
2251
- graphSettings: this.graphSettings,
2252
- areaRect: this.areaRect,
2253
- areaTransform: this.areaTransform,
2254
- mouseEvent: event,
2255
- nodes: this.nodes,
2256
- });
2257
- if (!currentNode) {
2258
- const currentLink = linkByPointerGetter({
2259
- graphSettings: this.graphSettings,
2260
- areaRect: this.areaRect,
2261
- areaTransform: this.areaTransform,
2262
- mouseEvent: event,
2263
- links: this.links,
2264
- });
2265
- return void this.listeners.onClick(event, undefined, currentLink);
2266
- }
2267
- return void this.listeners.onClick(event, currentNode, undefined);
2268
- }
2269
- /** hover */
2270
- this.area.addEventListener("mousemove", onHover.bind(this), {
2271
- signal: this.eventAbortController.signal,
2272
- });
2273
- this.area.addEventListener("touchmove", onHover.bind(this), {
2274
- signal: this.eventAbortController.signal,
2275
- });
2276
- /** dblclick */
2277
- this.area.addEventListener("dblclick", onDoubleClick.bind(this), {
2278
- signal: this.eventAbortController.signal,
2279
- });
2280
- /** wheel click */
2281
- this.area.addEventListener("mousedown", onWheelClick.bind(this), {
2282
- signal: this.eventAbortController.signal,
2283
- });
2284
- /** click */
2285
- this.area.addEventListener("click", onClick.bind(this), {
2286
- signal: this.eventAbortController.signal,
2287
- });
2288
- /** right click */
2289
- this.area.addEventListener("contextmenu", onRightClick.bind(this), {
2290
- signal: this.eventAbortController.signal,
2291
- });
2292
- }
2293
- initDnd() {
2294
- if (!this.area || !this.nodes || !this.simulation)
2295
- throw new Error("bad init data");
2296
- const dragHandler = d3Drag.drag()
2297
- .subject((event) => {
2298
- if (this.listeners.onDragSubject) {
2299
- return this.listeners.onDragSubject(event, this.state);
2300
- }
2301
- if (!this.areaRect)
2302
- return;
2303
- const mouseEvent = event.sourceEvent;
2304
- const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
2305
- let index = 0;
2306
- return d3Array.greatest(this.nodes, (node) => {
2307
- if (!node.x || !node.y || (jsHelpers.isBoolean(node.drag) && !node.drag))
2308
- return undefined;
2309
- let radius = node._radius;
2310
- if (!radius) {
2311
- const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
2312
- radius = nodeRadiusGetter({
2313
- radiusFlexible: this.graphSettings.nodeRadiusFlexible,
2314
- radiusInitial: nodeOptions.radius ?? this.graphSettings.nodeRadiusInitial,
2315
- radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
2316
- radiusFactor: this.graphSettings.nodeRadiusFactor,
2317
- linkCount: node.linkCount,
2318
- });
2319
- }
2320
- index++;
2321
- return this.graphSettings.dragPlaceCoefficient(node, pointerX, pointerY, radius);
2322
- });
2323
- })
2324
- .on("start", (event) => {
2325
- this.listeners.onStartDragFinished?.(event, this.state);
2326
- })
2327
- .on("drag", (event) => {
2328
- if (!this.isDragging) {
2329
- this.isDragging = true;
2330
- if (this.simulation)
2331
- this.simulation.alphaTarget(0.3).restart();
2332
- }
2333
- if (!this.areaRect)
2334
- return;
2335
- const mouseEvent = event.sourceEvent;
2336
- const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
2337
- event.subject.fx = pointerX;
2338
- event.subject.fy = pointerY;
2339
- this.listeners.onMoveDragFinished?.(event, this.state);
2340
- })
2341
- .on("end", (event) => {
2342
- this.isDragging = false;
2343
- if (!event.active && this.simulation)
2344
- this.simulation.alphaTarget(0);
2345
- if (this.graphSettings.stickAfterDrag && this.areaRect) {
2346
- if (!this.areaRect)
2347
- return;
2348
- const mouseEvent = event.sourceEvent;
2349
- const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
2350
- event.subject.fx = pointerX;
2351
- event.subject.fy = pointerY;
2352
- }
2353
- else {
2354
- event.subject.fx = null;
2355
- event.subject.fy = null;
2356
- }
2357
- this.listeners.onEndDragFinished?.(event, this.state);
2358
- });
2359
- d3Selection.select(this.area).call(dragHandler);
2360
- }
2361
- initZoom(currentZoom) {
2362
- if (!this.area)
2363
- throw new Error("bad init data");
2364
- const zoomInstance = d3Zoom.zoom()
2365
- .scaleExtent(this.graphSettings.zoomExtent)
2366
- .on("zoom", (event) => {
2367
- this.listeners.onZoom?.(event);
2368
- this.areaTransform = event.transform;
2369
- this.linkOptionsCache = {};
2370
- this.nodeOptionsCache = {};
2371
- if (!this.simulationWorking && !this.highlightWorking)
2372
- requestAnimationFrame(() => this.draw());
2373
- });
2374
- if (this.graphSettings.translateExtentEnable) {
2375
- const coefficient = this.graphSettings.translateExtentCoefficient;
2376
- const [coefficientX, coefficientY] = jsHelpers.isArray(coefficient)
2377
- ? coefficient
2378
- : [coefficient, coefficient];
2379
- const [[minX = -this.width * coefficientX, minY = -this.height * coefficientY], [maxX = this.width * coefficientX, maxY = this.height * coefficientY],] = this.graphSettings.translateExtent;
2380
- zoomInstance.translateExtent([
2381
- [minX, minY],
2382
- [maxX, maxY],
2383
- ]);
2384
- }
2385
- d3Selection.select(this.area).call(zoomInstance).on("dblclick.zoom", null);
2386
- const zoomInitial = currentZoom ?? this.graphSettings.zoomInitial;
2387
- this.areaTransform = new d3Zoom.ZoomTransform(zoomInitial?.k ?? 1, zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
2388
- d3Zoom.zoom().transform(d3Selection.select(this.area), this.areaTransform);
3217
+ initArea.call(this);
3218
+ initSimulation.call(this);
3219
+ initDnd.call(this);
3220
+ initZoom.call(this);
3221
+ initResize.call(this);
3222
+ initPointer.call(this);
2389
3223
  }
2390
3224
  }
2391
3225
 
2392
3226
  exports.GraphCanvas = GraphCanvas;
2393
3227
  exports.animationByProgress = animationByProgress;
2394
- exports.calculateLinkPositionByRadius = calculateLinkPositionByRadius;
3228
+ exports.calculateLinkPositionByNode = calculateLinkPositionByNode;
2395
3229
  exports.colorToRgb = colorToRgb;
2396
3230
  exports.drawText = drawText;
2397
3231
  exports.extractRgb = extractRgb;
2398
3232
  exports.fadeRgb = fadeRgb;
3233
+ exports.getDrawLink = getDrawLink;
3234
+ exports.getDrawNode = getDrawNode;
2399
3235
  exports.getForceControls = getForceControls;
2400
3236
  exports.getGraphControls = getGraphControls;
2401
- exports.getLinkControls = getLinkControls;
2402
- exports.getNodeControls = getNodeControls;
3237
+ exports.getLinkOptionsControls = getLinkOptionsControls;
3238
+ exports.getLinkSettingsControls = getLinkSettingsControls;
3239
+ exports.getNodeOptionsControls = getNodeOptionsControls;
3240
+ exports.getNodeSettingControls = getNodeSettingControls;
2403
3241
  exports.getParticlePosition = getParticlePosition;
2404
3242
  exports.linkByPointerGetter = linkByPointerGetter;
2405
3243
  exports.linkIterationExtractor = linkIterationExtractor;