@krainovsd/graph 0.9.0 → 0.10.0-rc2

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 +2672 -1803
  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 +44 -934
  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 +12 -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 +367 -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 +30 -7
  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 +197 -154
  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: true,
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
+ highlightByNodeNodeSizingAdditionalCoefficient: 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
+ highlightByLinkNodeSizingAdditionalCoefficient: 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: 1,
101
+ textNodeYPadding: 1,
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,2098 @@ 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
  },
1063
- {
1064
- id: "highlightByNodeNodeColor",
936
+ ];
937
+ return keys ? NODE_CONTROLS.filter((control) => keys.includes(control.id)) : NODE_CONTROLS;
938
+ }
939
+ function getLinkSettingsControls(keys) {
940
+ const LINK_SETTINGS_CONTROLS = [
941
+ {
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 additionalSizeCoefficient = 1;
1269
+ if (sizeFlexible && linkCount != undefined) {
1270
+ additionalSizeCoefficient += (linkCount / sizeCoefficient) * sizeFactor;
1271
+ }
1272
+ return {
1273
+ width: widthInitial * additionalSizeCoefficient,
1274
+ height: heightInitial * additionalSizeCoefficient,
1275
+ additionalSizeCoefficient,
1276
+ };
1277
+ }
1278
+ function nodeIdGetter(node) {
1279
+ return node.id;
1280
+ }
1281
+
1282
+ function pointerGetter(mouseEvent, areaRect, areaTransform) {
1283
+ const clientX = "clientX" in mouseEvent
1284
+ ? mouseEvent.clientX
1285
+ : (mouseEvent.touches[0]?.clientX ?? mouseEvent.changedTouches[0]?.clientX);
1286
+ const clientY = "clientX" in mouseEvent
1287
+ ? mouseEvent.clientY
1288
+ : (mouseEvent.touches[0]?.clientY ?? mouseEvent.changedTouches[0]?.clientY);
1289
+ const px = (clientX - areaRect.left - areaTransform.x) / areaTransform.k;
1290
+ const py = (clientY - areaRect.top - areaTransform.y) / areaTransform.k;
1291
+ return [px, py];
1292
+ }
1293
+
1294
+ function isOverlapsNode(node, pointerX, pointerY, radius) {
1295
+ if (node.x == undefined || node.y == undefined)
1296
+ return false;
1297
+ switch (node._shape) {
1298
+ case "circle": {
1299
+ const nodeRadius = node._radius ?? radius ?? 5;
1300
+ const isOverX = node.x - nodeRadius <= pointerX && pointerX <= node.x + nodeRadius;
1301
+ const isOverY = node.y - nodeRadius <= pointerY && pointerY <= node.y + nodeRadius;
1302
+ return isOverX && isOverY;
1303
+ }
1304
+ case "square":
1305
+ case "text": {
1306
+ const width = node._width ?? 5;
1307
+ const height = node._height ?? 5;
1308
+ const borderRadius = node._borderRadius ?? 0;
1309
+ const overlaps = Math.abs(pointerX - node.x) <= width / 2 && Math.abs(pointerY - node.y) <= height / 2;
1310
+ if (node._shape !== "text" && borderRadius != undefined && borderRadius > 0 && overlaps) {
1311
+ return isCursorOverRoundedRect(pointerX, pointerY, node.x, node.y, width / 2, height / 2, borderRadius);
1312
+ }
1313
+ return overlaps;
1314
+ }
1315
+ default: {
1316
+ const nodeRadius = node._radius ?? radius ?? 5;
1317
+ const isOverX = node.x - nodeRadius <= pointerX && pointerX <= node.x + nodeRadius;
1318
+ const isOverY = node.y - nodeRadius <= pointerY && pointerY <= node.y + nodeRadius;
1319
+ return isOverX && isOverY;
1320
+ }
1321
+ }
1322
+ }
1323
+ function isCursorOverRoundedRect(pointerX, pointerY, x, y, halfWidth, halfHeight, borderRadius) {
1324
+ const left = x - halfWidth;
1325
+ const right = x + halfWidth;
1326
+ const top = y - halfHeight;
1327
+ const bottom = y + halfHeight;
1328
+ if (pointerX < left + borderRadius && pointerY < top + borderRadius) {
1329
+ const dx = pointerX - (left + borderRadius);
1330
+ const dy = pointerY - (top + borderRadius);
1331
+ return dx * dx + dy * dy <= borderRadius * borderRadius;
1332
+ }
1333
+ else if (pointerX > right - borderRadius && pointerY < top + borderRadius) {
1334
+ const dx = pointerX - (right - borderRadius);
1335
+ const dy = pointerY - (top + borderRadius);
1336
+ return dx * dx + dy * dy <= borderRadius * borderRadius;
1337
+ }
1338
+ else if (pointerX < left + borderRadius && pointerY > bottom - borderRadius) {
1339
+ const dx = pointerX - (left + borderRadius);
1340
+ const dy = pointerY - (bottom - borderRadius);
1341
+ return dx * dx + dy * dy <= borderRadius * borderRadius;
1342
+ }
1343
+ else if (pointerX > right - borderRadius && pointerY > bottom - borderRadius) {
1344
+ const dx = pointerX - (right - borderRadius);
1345
+ const dy = pointerY - (bottom - borderRadius);
1346
+ return dx * dx + dy * dy <= borderRadius * borderRadius;
1347
+ }
1348
+ return true;
1349
+ }
1350
+
1351
+ function dragPlaceCoefficientGetter(node, pointerX, pointerY) {
1352
+ if (!node.x || !node.y)
1353
+ return undefined;
1354
+ if (isOverlapsNode(node, pointerX, pointerY, undefined))
1355
+ return node.index;
1356
+ }
1357
+
1358
+ function linkIterationExtractor(link, i, links, state, option, optionConstantGetter) {
1359
+ let customOptions;
1360
+ let constantOptions;
1361
+ if (typeof option === "function")
1362
+ customOptions = option(link, i, links, state);
1363
+ else
1364
+ customOptions = option;
1365
+ if (customOptions && typeof customOptions === "object" && !Array.isArray(customOptions)) {
1366
+ for (const key in customOptions) {
1367
+ if (customOptions[key] === undefined)
1368
+ delete customOptions[key];
1369
+ }
1370
+ }
1371
+ if (optionConstantGetter) {
1372
+ if (typeof optionConstantGetter === "function")
1373
+ constantOptions = optionConstantGetter(link, i, links, state);
1374
+ else
1375
+ constantOptions = optionConstantGetter;
1376
+ if (constantOptions &&
1377
+ typeof constantOptions === "object" &&
1378
+ !Array.isArray(constantOptions) &&
1379
+ checkType(customOptions, customOptions === undefined ||
1380
+ (typeof customOptions === "object" && !Array.isArray(customOptions)))) {
1381
+ return {
1382
+ ...constantOptions,
1383
+ ...customOptions,
1384
+ };
1385
+ }
1386
+ }
1387
+ return customOptions;
1388
+ }
1389
+
1390
+ function nodeByPointerGetter({ areaRect, areaTransform, mouseEvent, nodes, }) {
1391
+ if (!areaRect)
1392
+ return undefined;
1393
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
1394
+ return d3Array.greatest(nodes, (node) => {
1395
+ if (isOverlapsNode(node, pointerX, pointerY, undefined))
1396
+ return node.index;
1397
+ });
1398
+ }
1399
+
1400
+ function nodeIterationExtractor(node, i, nodes, state, option, optionConstantGetter) {
1401
+ let customOptions;
1402
+ let constantOptions;
1403
+ if (typeof option === "function")
1404
+ customOptions = option(node, i, nodes, state);
1405
+ else
1406
+ customOptions = option;
1407
+ if (customOptions && typeof customOptions === "object" && !Array.isArray(customOptions)) {
1408
+ for (const key in customOptions) {
1409
+ if (customOptions[key] === undefined)
1410
+ delete customOptions[key];
1411
+ }
1412
+ }
1413
+ if (optionConstantGetter) {
1414
+ if (typeof optionConstantGetter === "function")
1415
+ constantOptions = optionConstantGetter(node, i, nodes, state);
1416
+ else
1417
+ constantOptions = optionConstantGetter;
1418
+ if (constantOptions &&
1419
+ typeof constantOptions === "object" &&
1420
+ !Array.isArray(constantOptions) &&
1421
+ checkType(customOptions, customOptions === undefined ||
1422
+ (typeof customOptions === "object" && !Array.isArray(customOptions)))) {
1423
+ return {
1424
+ ...constantOptions,
1425
+ ...customOptions,
1426
+ };
1427
+ }
1428
+ }
1429
+ return customOptions;
1430
+ }
1431
+
1432
+ function calculateLinkPositionByNode(link, arrowSize = 0) {
1433
+ const source = link.source;
1434
+ const target = link.target;
1435
+ if (typeof source != "object" || typeof target != "object")
1436
+ return null;
1437
+ const sourcePoint = getLinkPoint({
1438
+ arrowSize: 0,
1439
+ node: source,
1440
+ oppositeNode: target,
1441
+ });
1442
+ const targetPoint = getLinkPoint({
1443
+ arrowSize: arrowSize > 0 ? arrowSize * 0.85 : 0,
1444
+ node: target,
1445
+ oppositeNode: source,
1446
+ });
1447
+ if (!sourcePoint || !targetPoint)
1448
+ return null;
1449
+ const distance = targetPoint && sourcePoint
1450
+ ? Math.sqrt((targetPoint.x - sourcePoint.x) ** 2 + (targetPoint.y - sourcePoint.y) ** 2)
1451
+ : 0;
1452
+ return {
1453
+ x1: sourcePoint.x,
1454
+ y1: sourcePoint.y,
1455
+ x2: targetPoint.x,
1456
+ y2: targetPoint.y,
1457
+ distance,
1458
+ };
1459
+ }
1460
+ function getLinkPoint(opts) {
1461
+ if (opts.node.x == undefined ||
1462
+ opts.node.y == undefined ||
1463
+ opts.oppositeNode.x == undefined ||
1464
+ opts.oppositeNode.y == undefined)
1465
+ return null;
1466
+ let nodePoint;
1467
+ switch (opts.node._shape) {
1468
+ case "circle": {
1469
+ nodePoint = getCircleIntersection({
1470
+ x: opts.node.x,
1471
+ y: opts.node.y,
1472
+ radius: opts.node._radius ?? COMMON_SETTINGS.nodeRadius,
1473
+ oppositeX: opts.oppositeNode.x,
1474
+ oppositeY: opts.oppositeNode.y,
1475
+ arrowSize: opts.arrowSize,
1476
+ });
1477
+ break;
1478
+ }
1479
+ case "square":
1480
+ case "text": {
1481
+ nodePoint = getRectangleIntersection({
1482
+ arrowSize: opts.arrowSize,
1483
+ x: opts.node.x,
1484
+ y: opts.node.y,
1485
+ height: opts.node._height ?? COMMON_SETTINGS.nodeSize,
1486
+ width: opts.node._width ?? COMMON_SETTINGS.nodeSize,
1487
+ oppositeX: opts.oppositeNode.x,
1488
+ oppositeY: opts.oppositeNode.y,
1489
+ borderRadius: opts.node._shape === "text" ? 0 : (opts.node._borderRadius ?? 0),
1490
+ });
1491
+ break;
1492
+ }
1493
+ default: {
1494
+ nodePoint = getCircleIntersection({
1495
+ x: opts.node.x,
1496
+ y: opts.node.y,
1497
+ radius: opts.node._radius ?? COMMON_SETTINGS.nodeRadius,
1498
+ oppositeX: opts.oppositeNode.x,
1499
+ oppositeY: opts.oppositeNode.y,
1500
+ arrowSize: opts.arrowSize,
1501
+ });
1502
+ }
1503
+ }
1504
+ return nodePoint;
1505
+ }
1506
+ function getCircleIntersection(opts) {
1507
+ const dx = opts.oppositeX - opts.x;
1508
+ const dy = opts.oppositeY - opts.y;
1509
+ const dr = Math.sqrt(dx * dx + dy * dy);
1510
+ const radius = opts.radius + opts.arrowSize;
1511
+ return {
1512
+ x: opts.x + (dx * radius) / dr,
1513
+ y: opts.y + (dy * radius) / dr,
1514
+ };
1515
+ }
1516
+ function getRectangleIntersection(opts) {
1517
+ const halfWidth = opts.width / 2;
1518
+ const halfHeight = opts.height / 2;
1519
+ const dx = opts.oppositeX - opts.x;
1520
+ const dy = opts.oppositeY - opts.y;
1521
+ let relX, relY;
1522
+ if (Math.abs(dx) <= halfWidth && Math.abs(dy) <= halfHeight) {
1523
+ const distToRight = halfWidth - dx;
1524
+ const distToLeft = halfWidth + dx;
1525
+ const distToTop = halfHeight - dy;
1526
+ const distToBottom = halfHeight + dy;
1527
+ const minDist = Math.min(distToRight, distToLeft, distToTop, distToBottom);
1528
+ relX = minDist === distToRight ? halfWidth : minDist === distToLeft ? -halfWidth : dx;
1529
+ relY = minDist === distToTop ? halfHeight : minDist === distToBottom ? -halfHeight : dy;
1530
+ }
1531
+ else {
1532
+ const absDx = Math.abs(dx);
1533
+ const absDy = Math.abs(dy);
1534
+ if (halfWidth * absDy < halfHeight * absDx) {
1535
+ relX = dx > 0 ? halfWidth : -halfWidth;
1536
+ relY = (relX * dy) / dx;
1537
+ if (Math.abs(relY) > halfHeight) {
1538
+ relY = dy > 0 ? halfHeight : -halfHeight;
1539
+ relX = (relY * dx) / dy;
1540
+ }
1541
+ }
1542
+ else {
1543
+ relY = dy > 0 ? halfHeight : -halfHeight;
1544
+ relX = (relY * dx) / dy;
1545
+ if (Math.abs(relX) > halfWidth) {
1546
+ relX = dx > 0 ? halfWidth : -halfWidth;
1547
+ relY = (relX * dy) / dx;
1548
+ }
1549
+ }
1550
+ }
1551
+ if (opts.borderRadius != undefined) {
1552
+ const { x, y } = squareRadiusFix(relX, relY, halfWidth, halfHeight, opts.borderRadius);
1553
+ if (x != undefined)
1554
+ relX = x;
1555
+ if (y != undefined)
1556
+ relY = y;
1557
+ }
1558
+ if (opts.arrowSize > 0) {
1559
+ const edgeDist = Math.sqrt(relX * relX + relY * relY);
1560
+ const scale = (edgeDist + opts.arrowSize) / edgeDist;
1561
+ relX *= scale;
1562
+ relY *= scale;
1563
+ }
1564
+ return {
1565
+ x: opts.x + relX,
1566
+ y: opts.y + relY,
1567
+ };
1568
+ }
1569
+ // eslint-disable-next-line no-warning-comments
1570
+ // TODO: Need upgrade
1571
+ function squareRadiusFix(relX, relY, halfWidth, halfHeight, borderRadius) {
1572
+ const epsilon = 1e-6;
1573
+ const absX = Math.abs(relX);
1574
+ const absY = Math.abs(relY);
1575
+ let side = null;
1576
+ if (absX >= halfWidth - epsilon) {
1577
+ side = relX > 0 ? "right" : "left";
1578
+ }
1579
+ else if (absY >= halfHeight - epsilon) {
1580
+ side = relY > 0 ? "top" : "bottom";
1581
+ }
1582
+ if (side) {
1583
+ const topBound = halfHeight - borderRadius;
1584
+ const bottomBound = -topBound;
1585
+ const rightBound = halfWidth - borderRadius;
1586
+ const leftBound = -rightBound;
1587
+ switch (side) {
1588
+ case "right":
1589
+ if (relY > topBound) {
1590
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "right-top");
1591
+ }
1592
+ else if (relY < bottomBound) {
1593
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "right-bottom");
1594
+ }
1595
+ break;
1596
+ case "left":
1597
+ if (relY > topBound) {
1598
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "left-top");
1599
+ }
1600
+ else if (relY < bottomBound) {
1601
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "left-bottom");
1602
+ }
1603
+ break;
1604
+ case "top":
1605
+ if (relX > rightBound) {
1606
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "top-right");
1607
+ }
1608
+ else if (relX < leftBound) {
1609
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "top-left");
1610
+ }
1611
+ break;
1612
+ case "bottom":
1613
+ if (relX > rightBound) {
1614
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "bottom-right");
1615
+ }
1616
+ else if (relX < leftBound) {
1617
+ return checkIntersection(relX, relY, halfWidth, halfHeight, borderRadius, "bottom-left");
1618
+ }
1619
+ break;
1620
+ }
1621
+ }
1622
+ return { x: undefined, y: undefined };
1623
+ }
1624
+ function checkIntersection(relX, relY, halfWidth, halfHeight, r, corner) {
1625
+ const intersect = intersectWithCircle(relX, relY, halfWidth, halfHeight, r, corner);
1626
+ return intersect ?? { x: undefined, y: undefined };
1627
+ }
1628
+ function intersectWithCircle(relX, relY, halfWidth, halfHeight, r, corner) {
1629
+ switch (corner) {
1630
+ case "top-right":
1631
+ corner = "right-top";
1632
+ break;
1633
+ case "bottom-right":
1634
+ corner = "right-bottom";
1635
+ break;
1636
+ case "top-left":
1637
+ corner = "left-top";
1638
+ break;
1639
+ case "bottom-left":
1640
+ corner = "left-bottom";
1641
+ break;
1642
+ }
1643
+ let cx, cy;
1644
+ switch (corner) {
1645
+ case "right-top":
1646
+ cx = halfWidth - r;
1647
+ cy = halfHeight - r;
1648
+ break;
1649
+ case "right-bottom":
1650
+ cx = halfWidth - r;
1651
+ cy = -halfHeight + r;
1652
+ break;
1653
+ case "left-top":
1654
+ cx = -halfWidth + r;
1655
+ cy = halfHeight - r;
1656
+ break;
1657
+ case "left-bottom":
1658
+ cx = -halfWidth + r;
1659
+ cy = -halfHeight + r;
1660
+ break;
1661
+ default:
1662
+ return null;
1663
+ }
1664
+ const a = relX ** 2 + relY ** 2;
1665
+ if (a === 0)
1666
+ return null;
1667
+ const b = -2 * (relX * cx + relY * cy);
1668
+ const c = cx ** 2 + cy ** 2 - r ** 2;
1669
+ const discriminant = b ** 2 - 4 * a * c;
1670
+ if (discriminant < 0)
1671
+ return null;
1672
+ const sqrtD = Math.sqrt(discriminant);
1673
+ const t2 = (-b - sqrtD) / (2 * a);
1674
+ const t1 = (-b + sqrtD) / (2 * a);
1675
+ const t = t2 > 0 ? t2 : t1 > 0 ? t1 : null;
1676
+ if (t === null || t >= 1)
1677
+ return null;
1678
+ const x = t * relX;
1679
+ const y = t * relY;
1680
+ let valid = false;
1681
+ switch (corner) {
1682
+ case "right-top":
1683
+ valid = x >= cx && y >= cy;
1684
+ break;
1685
+ case "right-bottom":
1686
+ valid = x >= cx && y <= cy;
1687
+ break;
1688
+ case "left-top":
1689
+ valid = x <= cx && y >= cy;
1690
+ break;
1691
+ case "left-bottom":
1692
+ valid = x <= cx && y <= cy;
1693
+ break;
1694
+ }
1695
+ return valid ? { x, y } : null;
1696
+ }
1697
+
1698
+ function animationByProgress(initial, additional, progress) {
1699
+ const max = initial + additional;
1700
+ let current = initial + (max - initial) * progress;
1701
+ if (current > max)
1702
+ current = max;
1703
+ if (current < initial)
1704
+ current = initial;
1705
+ return current;
1706
+ }
1707
+
1708
+ const ADDITIONAL_VIEWPORT = 10;
1709
+ function isNodeVisible(opts) {
1710
+ const left = -opts.transform.x / opts.transform.k;
1711
+ const right = (opts.width - opts.transform.x) / opts.transform.k;
1712
+ const top = -opts.transform.y / opts.transform.k;
1713
+ const bottom = (opts.height - opts.transform.y) / opts.transform.k;
1714
+ if (opts.node.x == undefined || opts.node.y == undefined)
1715
+ return false;
1716
+ switch (opts.node._shape) {
1717
+ case "circle": {
1718
+ const radius = opts.node._radius ?? COMMON_SETTINGS.nodeRadius;
1719
+ return (left < opts.node.x + radius + ADDITIONAL_VIEWPORT &&
1720
+ opts.node.x - radius - ADDITIONAL_VIEWPORT < right &&
1721
+ top < opts.node.y + radius + ADDITIONAL_VIEWPORT &&
1722
+ opts.node.y - radius - ADDITIONAL_VIEWPORT < bottom);
1723
+ }
1724
+ case "square":
1725
+ case "text": {
1726
+ const width = opts.node._width ?? COMMON_SETTINGS.nodeSize;
1727
+ const height = opts.node._height ?? COMMON_SETTINGS.nodeSize;
1728
+ return (left < opts.node.x + width + ADDITIONAL_VIEWPORT &&
1729
+ opts.node.x - width - ADDITIONAL_VIEWPORT < right &&
1730
+ top < opts.node.y + height + ADDITIONAL_VIEWPORT &&
1731
+ opts.node.y - height - ADDITIONAL_VIEWPORT < bottom);
1732
+ }
1733
+ default: {
1734
+ const radius = opts.node._radius ?? COMMON_SETTINGS.nodeRadius;
1735
+ return (left < opts.node.x + radius + ADDITIONAL_VIEWPORT &&
1736
+ opts.node.x - radius - ADDITIONAL_VIEWPORT < right &&
1737
+ top < opts.node.y + radius + ADDITIONAL_VIEWPORT &&
1738
+ opts.node.y - radius - ADDITIONAL_VIEWPORT < bottom);
1739
+ }
1740
+ }
1741
+ }
1742
+
1743
+ function getParticlePosition(opts) {
1744
+ const prevStepDifference = opts.particle.step - (opts.particle.prev?.step ?? 0);
1745
+ const nextStepDifference = (opts.particle.next?.step ?? 0) - opts.particle.step;
1746
+ const needWait = opts.particle.next &&
1747
+ opts.particle.next.step > opts.particle.step &&
1748
+ nextStepDifference <= opts.distance;
1749
+ const needSpeed = opts.particle.prev &&
1750
+ opts.particle.prev.step !== 0 &&
1751
+ opts.particle.prev.step < opts.particle.step &&
1752
+ prevStepDifference < opts.distance;
1753
+ if (opts.particle.step === 0 && needWait) {
1754
+ opts.particle.x = undefined;
1755
+ opts.particle.y = undefined;
1756
+ return;
1757
+ }
1758
+ const remainingSteps = opts.totalSteps - opts.particle.step;
1759
+ const progress = opts.particle.step / opts.totalSteps;
1760
+ if (remainingSteps <= 0) {
1761
+ opts.particle.x = opts.xEnd;
1762
+ opts.particle.y = opts.yEnd;
1763
+ opts.particle.step = 0;
1764
+ return;
1765
+ }
1766
+ const dx = opts.xEnd - opts.xStart;
1767
+ const dy = opts.yEnd - opts.yStart;
1768
+ const newX = opts.xStart + dx * progress;
1769
+ const newY = opts.yStart + dy * progress;
1770
+ opts.particle.x = newX;
1771
+ opts.particle.y = newY;
1772
+ if (needSpeed) {
1773
+ opts.particle.step += prevStepDifference <= 3 ? prevStepDifference : 3;
1774
+ }
1775
+ else {
1776
+ opts.particle.step++;
1777
+ }
1778
+ }
1779
+
1780
+ /* eslint-disable id-length */
1781
+ function linkByPointerGetter({ areaRect, areaTransform, mouseEvent, links, linkSettings, }) {
1782
+ if (!areaRect)
1783
+ return undefined;
1784
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
1785
+ return d3Array.greatest(links, (link) => {
1786
+ if (isNearLink(pointerX, pointerY, link, linkSettings.hoverLinkThreshold))
1787
+ return link.index;
1788
+ });
1789
+ }
1790
+ function isNearLink(mouseX, mouseY, link, threshold = 2) {
1791
+ if (!jsHelpers.isObject(link.source) || !jsHelpers.isObject(link.target))
1792
+ return false;
1793
+ const x1 = link.source.x;
1794
+ const y1 = link.source.y;
1795
+ const x2 = link.target.x;
1796
+ const y2 = link.target.y;
1797
+ const distance = distanceToLine(mouseX, mouseY, x1, y1, x2, y2);
1798
+ return distance <= threshold;
1799
+ }
1800
+ function distanceToLine(x, y, x1, y1, x2, y2) {
1801
+ const A = x - x1;
1802
+ const B = y - y1;
1803
+ const C = x2 - x1;
1804
+ const D = y2 - y1;
1805
+ const dot = A * C + B * D;
1806
+ const lenSq = C * C + D * D;
1807
+ let param = -1;
1808
+ if (lenSq !== 0) {
1809
+ param = dot / lenSq;
1810
+ }
1811
+ let xx, yy;
1812
+ if (param < 0) {
1813
+ xx = x1;
1814
+ yy = y1;
1815
+ }
1816
+ else if (param > 1) {
1817
+ xx = x2;
1818
+ yy = y2;
1819
+ }
1820
+ else {
1821
+ xx = x1 + param * C;
1822
+ yy = y1 + param * D;
1823
+ }
1824
+ const dx = x - xx;
1825
+ const dy = y - yy;
1826
+ return Math.sqrt(dx * dx + dy * dy);
1827
+ }
1828
+
1829
+ function initArea() {
1830
+ if (!this.area || !this.context || !this.container) {
1831
+ this.container = d3Selection.create("div")
1832
+ .attr("style", "padding: 0 !important; width: 100%; height: 100%;")
1833
+ .node();
1834
+ if (!this.container)
1835
+ throw new Error("couldn't create container");
1836
+ this.root.appendChild(this.container);
1837
+ const { width, height } = this.root.getBoundingClientRect();
1838
+ this.width = width;
1839
+ this.height = height;
1840
+ this.area = d3Selection.create("canvas")
1841
+ .attr("width", this.dpi * this.width)
1842
+ .attr("height", this.dpi * this.height)
1843
+ .attr("style", `width: 100%; height: 100%; border: none !important;`)
1844
+ .node();
1845
+ if (!this.area)
1846
+ throw new Error("couldn't create canvas");
1847
+ this.container.appendChild(this.area);
1848
+ this.areaRect = this.area.getBoundingClientRect();
1849
+ this.context = this.area.getContext("2d");
1850
+ if (!this.context)
1851
+ throw new Error("couldn't create canvas context");
1852
+ this.context.scale(this.dpi, this.dpi);
1853
+ }
1854
+ }
1855
+
1856
+ function initDnd() {
1857
+ if (!this.area || !this.nodes)
1858
+ throw new Error("bad init data");
1859
+ const dragHandler = d3Drag.drag()
1860
+ .subject((event) => {
1861
+ if (this.listeners.onDragSubject) {
1862
+ return this.listeners.onDragSubject(event, this.state);
1863
+ }
1864
+ if (!this.areaRect)
1865
+ return;
1866
+ const mouseEvent = event.sourceEvent;
1867
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1868
+ return d3Array.greatest(this.nodes, (node) => {
1869
+ if (!node.x || !node.y || (jsHelpers.isBoolean(node.drag) && !node.drag))
1870
+ return undefined;
1871
+ return this.graphSettings.dragPlaceCoefficient(node, pointerX, pointerY);
1872
+ });
1873
+ })
1874
+ .on("start", (event) => {
1875
+ this.listeners.onStartDragFinished?.(event, this.state);
1876
+ })
1877
+ .on("drag", (event) => {
1878
+ if (!this.isDragging) {
1879
+ this.isDragging = true;
1880
+ if (this.simulation)
1881
+ this.simulation.alphaTarget(0.3).restart();
1882
+ }
1883
+ if (!this.areaRect)
1884
+ return;
1885
+ const mouseEvent = event.sourceEvent;
1886
+ const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1887
+ event.subject.fx = pointerX;
1888
+ event.subject.fy = pointerY;
1889
+ this.listeners.onMoveDragFinished?.(event, this.state);
1890
+ })
1891
+ .on("end", (event) => {
1892
+ this.isDragging = false;
1893
+ if (!event.active && this.simulation)
1894
+ this.simulation.alphaTarget(0);
1895
+ const sourceEvent = event.sourceEvent;
1896
+ if (sourceEvent.altKey && this.areaRect) {
1897
+ const [pointerX, pointerY] = pointerGetter(sourceEvent, this.areaRect, this.areaTransform);
1898
+ event.subject.fx = pointerX;
1899
+ event.subject.fy = pointerY;
1900
+ }
1901
+ else {
1902
+ event.subject.fx = null;
1903
+ event.subject.fy = null;
1904
+ }
1905
+ this.listeners.onEndDragFinished?.(event, this.state);
1906
+ });
1907
+ d3Selection.select(this.area).call(dragHandler);
1908
+ }
1909
+
1910
+ function getDrawLink(state) {
1911
+ return function drawLink(link, index) {
1912
+ if (!this.context ||
1913
+ typeof link.source !== "object" ||
1914
+ typeof link.target !== "object" ||
1915
+ !link.source.x ||
1916
+ !link.source.y ||
1917
+ !link.target.x ||
1918
+ !link.target.y)
1919
+ return;
1920
+ if (!link.source._visible && !link.target._visible)
1921
+ return;
1922
+ const id = `${link.target.id}${link.source.id}`;
1923
+ let linkOptions;
1924
+ if (this.linkSettings.cache && this.linkOptionsCache[id]) {
1925
+ linkOptions = this.linkOptionsCache[id];
1926
+ }
1927
+ else {
1928
+ linkOptions = linkIterationExtractor(link, index, this.links, state, this.linkSettings.options ?? {}, linkOptionsGetter);
1929
+ if (this.linkSettings.cache) {
1930
+ this.linkOptionsCache[id] = linkOptions;
1931
+ }
1932
+ }
1933
+ if (linkOptions.drawLink) {
1934
+ linkOptions.drawLink(link, linkOptions, state);
1935
+ return;
1936
+ }
1937
+ let alpha = linkOptions.alpha;
1938
+ let arrowAlpha = this.linkSettings.arrowByHighlight ? 0 : linkOptions.arrowAlpha;
1939
+ /** NODE HIGHLIGHT */
1940
+ if (this.highlightedNeighbors && this.highlightedNode) {
1941
+ /** Not highlighted */
1942
+ if (this.highlightedNode.id != link.source.id && this.highlightedNode.id != link.target.id) {
1943
+ if (this.linkSettings.highlightByNodeLinkFading) {
1944
+ const min = this.linkSettings.highlightByNodeLinkFadingMin < alpha
1945
+ ? this.linkSettings.highlightByNodeLinkFadingMin
1946
+ : alpha;
1947
+ alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
1948
+ }
1949
+ if (this.linkSettings.arrow &&
1950
+ this.linkSettings.highlightByNodeArrowFading &&
1951
+ !this.linkSettings.arrowByHighlight) {
1952
+ const min = this.linkSettings.highlightByNodeArrowFadingMin < arrowAlpha
1953
+ ? this.linkSettings.highlightByNodeArrowFadingMin
1954
+ : arrowAlpha;
1955
+ arrowAlpha = animationByProgress(min, arrowAlpha - min, 1 - this.highlightProgress);
1956
+ }
1957
+ }
1958
+ else {
1959
+ // eslint-disable-next-line no-lonely-if
1960
+ if (this.linkSettings.arrow && this.linkSettings.arrowByHighlight) {
1961
+ /** Highlighted */
1962
+ arrowAlpha = animationByProgress(0, linkOptions.arrowAlpha, this.highlightProgress);
1963
+ }
1964
+ }
1965
+ }
1966
+ /** LINK HIGHLIGHT */
1967
+ if (this.highlightedNeighbors && this.highlightedLink) {
1968
+ /** Not highlighted */
1969
+ if (this.highlightedLink !== link) {
1970
+ if (this.linkSettings.highlightByLinkLinkFading) {
1971
+ const min = this.linkSettings.highlightByLinkLinkFadingMin < alpha
1972
+ ? this.linkSettings.highlightByLinkLinkFadingMin
1973
+ : alpha;
1974
+ alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
1975
+ }
1976
+ if (this.linkSettings.arrow &&
1977
+ this.linkSettings.highlightByLinkArrowFading &&
1978
+ !this.linkSettings.arrowByHighlight) {
1979
+ const min = this.linkSettings.highlightByLinkArrowFadingMin < arrowAlpha
1980
+ ? this.linkSettings.highlightByLinkArrowFadingMin
1981
+ : arrowAlpha;
1982
+ arrowAlpha = animationByProgress(min, arrowAlpha - min, 1 - this.highlightProgress);
1983
+ }
1984
+ }
1985
+ else {
1986
+ // eslint-disable-next-line no-lonely-if
1987
+ if (this.linkSettings.arrow && this.linkSettings.arrowByHighlight) {
1988
+ /** Highlighted */
1989
+ arrowAlpha = animationByProgress(0, linkOptions.arrowAlpha, this.highlightProgress);
1990
+ }
1991
+ }
1992
+ }
1993
+ /** Link */
1994
+ this.context.beginPath();
1995
+ this.context.globalAlpha = alpha;
1996
+ this.context.strokeStyle = linkOptions.color;
1997
+ this.context.lineWidth = linkOptions.width;
1998
+ let xStart = link.source.x;
1999
+ let yStart = link.source.y;
2000
+ let xEnd = link.target.x;
2001
+ let yEnd = link.target.y;
2002
+ let linkDistance = 0;
2003
+ if (this.linkSettings.pretty || this.linkSettings.particleFlexSpeed) {
2004
+ const isHasArrow = this.linkSettings.arrow && arrowAlpha > 0;
2005
+ const position = calculateLinkPositionByNode(link, isHasArrow ? linkOptions.arrowSize : 0);
2006
+ if (position) {
2007
+ xStart = position.x1;
2008
+ xEnd = position.x2;
2009
+ yStart = position.y1;
2010
+ yEnd = position.y2;
2011
+ linkDistance = position.distance;
2012
+ }
2013
+ }
2014
+ this.context.moveTo(xStart, yStart);
2015
+ this.context.lineTo(xEnd, yEnd);
2016
+ this.context.stroke();
2017
+ /** Particle */
2018
+ if (this.linkSettings.particles &&
2019
+ ((this.highlightedNode &&
2020
+ (this.highlightedNode.id === link.source.id ||
2021
+ this.highlightedNode.id === link.target.id)) ||
2022
+ (this.highlightedLink && this.highlightedLink === link))) {
2023
+ const particleSteps = this.linkSettings.particleFlexSpeed
2024
+ ? linkDistance <= 0
2025
+ ? 0
2026
+ : linkDistance * this.linkSettings.particleFlexSpeedCoefficient
2027
+ : linkOptions.particleSteps;
2028
+ const particleCount = linkOptions.particleCount;
2029
+ if (!this.particles[id]) {
2030
+ const sourceId = link.source.id;
2031
+ const targetId = link.target.id;
2032
+ const particles = [];
2033
+ let prevParticle;
2034
+ for (let i = 0; i < particleCount; i++) {
2035
+ const particle = {
2036
+ step: 0,
2037
+ sourceId,
2038
+ targetId,
2039
+ prev: prevParticle,
2040
+ next: undefined,
2041
+ index: i,
2042
+ };
2043
+ if (prevParticle)
2044
+ prevParticle.next = particle;
2045
+ particles.push(particle);
2046
+ prevParticle = particle;
2047
+ }
2048
+ if (particles.length >= 2) {
2049
+ particles[0].prev = particles[particles.length - 1];
2050
+ particles[particles.length - 1].next = particles[0];
2051
+ }
2052
+ this.particles[id] = particles;
2053
+ }
2054
+ if (particleSteps !== 0) {
2055
+ this.particles[id].forEach((particle) => {
2056
+ if (!this.context)
2057
+ return;
2058
+ const distance = particleSteps / particleCount;
2059
+ getParticlePosition({
2060
+ distance,
2061
+ particle,
2062
+ totalSteps: particleSteps,
2063
+ xEnd,
2064
+ xStart,
2065
+ yEnd,
2066
+ yStart,
2067
+ });
2068
+ if (particle.x != undefined && particle.y != undefined) {
2069
+ this.context.beginPath();
2070
+ this.context.strokeStyle = linkOptions.particleBorderColor;
2071
+ this.context.lineWidth = linkOptions.particleBorderWidth;
2072
+ this.context.arc(particle.x, particle.y, linkOptions.particleRadius, 0, Math.PI * 2);
2073
+ this.context.fillStyle = linkOptions.particleColor;
2074
+ this.context.fill();
2075
+ this.context.stroke();
2076
+ }
2077
+ });
2078
+ }
2079
+ }
2080
+ /** Arrow */
2081
+ if (this.linkSettings.arrow && arrowAlpha > 0) {
2082
+ const { x1: xStart, x2: xEnd, y1: yStart, y2: yEnd, } = calculateLinkPositionByNode(link) ?? {
2083
+ x1: 0,
2084
+ x2: 0,
2085
+ y1: 0,
2086
+ y2: 0,
2087
+ };
2088
+ this.context.beginPath();
2089
+ this.context.globalAlpha = arrowAlpha;
2090
+ this.context.strokeStyle = linkOptions.arrowBorderColor;
2091
+ this.context.lineWidth = linkOptions.arrowBorderWidth;
2092
+ this.context.fillStyle = linkOptions.arrowColor;
2093
+ const angle = Math.atan2(yEnd - yStart, xEnd - xStart);
2094
+ this.context.moveTo(xEnd, yEnd);
2095
+ this.context.lineTo(xEnd - linkOptions.arrowSize * Math.cos(angle - Math.PI / 6), yEnd - linkOptions.arrowSize * Math.sin(angle - Math.PI / 6));
2096
+ this.context.lineTo(xEnd - linkOptions.arrowSize * Math.cos(angle + Math.PI / 6), yEnd - linkOptions.arrowSize * Math.sin(angle + Math.PI / 6));
2097
+ this.context.closePath();
2098
+ this.context.fill();
2099
+ this.context.stroke();
2100
+ }
2101
+ if (linkOptions.drawExtraLink) {
2102
+ linkOptions.drawExtraLink(link, { ...linkOptions, alpha }, state);
2103
+ }
2104
+ };
2105
+ }
2106
+
2107
+ const SPACE = " ";
2108
+ function drawText({ context, id, textAlign, textColor, textFont, textStyle, textGap, textWeight, textSize, text, x, y, cachedNodeText, maxWidth, }) {
2109
+ context.font = `${textStyle} normal ${textWeight} ${textSize}px ${textFont}`;
2110
+ context.fillStyle = textColor;
2111
+ context.textAlign = textAlign;
2112
+ if (cachedNodeText[id] != undefined) {
2113
+ cachedNodeText[id].forEach((line, index) => {
2114
+ context.fillText(line, x, y + index * textSize + index * textGap);
2115
+ });
2116
+ return;
2117
+ }
2118
+ if (maxWidth == undefined || context.measureText(text).width <= maxWidth) {
2119
+ cachedNodeText[id] = [text];
2120
+ context.fillText(text, x, y);
2121
+ return;
2122
+ }
2123
+ const { lines } = getTextLines({
2124
+ context,
2125
+ maxWidth,
2126
+ text,
2127
+ textAlign,
2128
+ textColor,
2129
+ textFont,
2130
+ textSize,
2131
+ textStyle,
2132
+ textWeight,
2133
+ });
2134
+ cachedNodeText[id] = lines;
2135
+ lines.forEach((line, index) => {
2136
+ context.fillText(line, x, y + index * textSize + index * textGap);
2137
+ });
2138
+ }
2139
+ function getTextLines({ context, textAlign, textColor, textFont, textStyle, textWeight, textSize, text, maxWidth, }) {
2140
+ context.font = `${textStyle} normal ${textWeight} ${textSize}px ${textFont}`;
2141
+ context.fillStyle = textColor;
2142
+ context.textAlign = textAlign;
2143
+ const spaceWidth = context.measureText(" ").width;
2144
+ const lines = [];
2145
+ let lineWidth = 0;
2146
+ let line = "";
2147
+ let currentMaxSize = 0;
2148
+ for (const word of text.split(" ")) {
2149
+ const size = context.measureText(word).width;
2150
+ lineWidth += size + spaceWidth;
2151
+ if (line === "") {
2152
+ line = word;
2153
+ continue;
2154
+ }
2155
+ if (lineWidth > maxWidth) {
2156
+ const initialSize = lineWidth - size - spaceWidth;
2157
+ if (initialSize > currentMaxSize)
2158
+ currentMaxSize = initialSize;
2159
+ lineWidth = size;
2160
+ lines.push(line);
2161
+ line = word;
2162
+ }
2163
+ else {
2164
+ lineWidth += spaceWidth;
2165
+ line += `${SPACE}${word}`;
2166
+ }
2167
+ }
2168
+ if (line !== "")
2169
+ lines.push(line);
2170
+ if (lineWidth > currentMaxSize)
2171
+ currentMaxSize = lineWidth;
2172
+ return { lines, currentMaxSize };
2173
+ }
2174
+
2175
+ function getDrawNode(nodeRenders, textRenders, state) {
2176
+ return function drawNode(node, index) {
2177
+ if (!this.context || !node.x || !node.y)
2178
+ return;
2179
+ let nodeOptions;
2180
+ if (this.nodeSettings.cache && this.nodeOptionsCache[node.id]) {
2181
+ nodeOptions = this.nodeOptionsCache[node.id];
2182
+ }
2183
+ else {
2184
+ nodeOptions = nodeIterationExtractor(node, index, this.nodes, state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
2185
+ if (this.nodeSettings.cache) {
2186
+ this.nodeOptionsCache[node.id] = nodeOptions;
2187
+ }
2188
+ }
2189
+ if (nodeOptions.nodeDraw && nodeOptions.textDraw) {
2190
+ nodeRenders.push(() => {
2191
+ nodeOptions?.nodeDraw?.(node, nodeOptions, state);
2192
+ });
2193
+ textRenders.push(() => {
2194
+ nodeOptions?.textDraw?.(node, nodeOptions, state);
2195
+ });
2196
+ return;
2197
+ }
2198
+ let alpha = nodeOptions.alpha;
2199
+ let color = nodeOptions.color;
2200
+ let radiusInitial = nodeOptions.radius;
2201
+ let widthInitial = nodeOptions.width;
2202
+ let heightInitial = nodeOptions.height;
2203
+ let textAlpha = nodeOptions.textAlpha;
2204
+ let textSize = nodeOptions.textSize;
2205
+ let textShiftX = nodeOptions.textShiftX;
2206
+ let textShiftY = nodeOptions.textShiftY;
2207
+ let textWeight = nodeOptions.textWeight;
2208
+ let textWidth = nodeOptions.textWidth;
2209
+ let sizeCoefficient = 1;
2210
+ /** Node Highlight */
2211
+ if (this.highlightedNeighbors && this.highlightedNode) {
2212
+ /** Not highlighted */
2213
+ if (!this.highlightedNeighbors.has(node.id) && this.highlightedNode.id != node.id) {
2214
+ if (this.nodeSettings.highlightByNodeNodeFading) {
2215
+ const min = this.nodeSettings.highlightByNodeNodeFadingMin < alpha
2216
+ ? this.nodeSettings.highlightByNodeNodeFadingMin
2217
+ : alpha;
2218
+ alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
2219
+ }
2220
+ if (this.nodeSettings.highlightByNodeTextFading) {
2221
+ const min = this.nodeSettings.highlightByNodeTextFadingMin < textAlpha
2222
+ ? this.nodeSettings.highlightByNodeTextFadingMin
2223
+ : textAlpha;
2224
+ textAlpha = animationByProgress(min, textAlpha - min, 1 - this.highlightProgress);
2225
+ }
2226
+ if (this.nodeSettings.highlightByNodeNodeColor) {
2227
+ const colorRgb = extractRgb(colorToRgb(color));
2228
+ if (colorRgb) {
2229
+ const colorRgbFade = fadeRgb(colorRgb, this.nodeSettings.highlightByNodeNodeColorFadingMin);
2230
+ const colorFadeAnimation = rgbAnimationByProgress(colorRgb, colorRgbFade, this.highlightProgress);
2231
+ color = `rgb(${colorFadeAnimation.r}, ${colorFadeAnimation.g}, ${colorFadeAnimation.b})`;
2232
+ }
2233
+ }
2234
+ }
2235
+ else if (!this.nodeSettings.highlightByNodeOnlyRoot ||
2236
+ (this.nodeSettings.highlightByNodeOnlyRoot && this.highlightedNode.id === node.id)) {
2237
+ /** Highlighted */
2238
+ if (this.nodeSettings.highlightByNodeNodeSizing && nodeOptions.shape === "circle") {
2239
+ radiusInitial = animationByProgress(radiusInitial, this.nodeSettings.highlightByNodeNodeSizingAdditional, this.highlightProgress);
2240
+ }
2241
+ if (this.nodeSettings.highlightByNodeNodeSizing &&
2242
+ (nodeOptions.shape === "square" || nodeOptions.shape === "text")) {
2243
+ sizeCoefficient = animationByProgress(sizeCoefficient, this.nodeSettings.highlightByNodeNodeSizingAdditionalCoefficient, this.highlightProgress);
2244
+ widthInitial *= sizeCoefficient;
2245
+ heightInitial *= sizeCoefficient;
2246
+ }
2247
+ if (this.nodeSettings.highlightByNodeTextSizing) {
2248
+ textSize = animationByProgress(textSize, this.nodeSettings.highlightByNodeTextSizingAdditional, this.highlightProgress);
2249
+ textShiftX = animationByProgress(textShiftX, this.nodeSettings.highlightByNodeTextShiftXAdditional, this.highlightProgress);
2250
+ textShiftY = animationByProgress(textShiftY, this.nodeSettings.highlightByNodeTextShiftYAdditional, this.highlightProgress);
2251
+ textWeight = animationByProgress(textWeight, this.nodeSettings.highlightByNodeTextWeightAdditional, this.highlightProgress);
2252
+ textWidth = animationByProgress(textWidth, this.nodeSettings.highlightByNodeTextWidthAdditional, this.highlightProgress);
2253
+ }
2254
+ }
2255
+ }
2256
+ /** LinkHighlight */
2257
+ if (this.highlightedNeighbors && this.highlightedLink) {
2258
+ /** Not highlighted */
2259
+ if (!this.highlightedNeighbors.has(node.id) &&
2260
+ this.highlightedLink.source !== node &&
2261
+ this.highlightedLink.target !== node) {
2262
+ if (this.nodeSettings.highlightByLinkNodeFading) {
2263
+ const min = this.nodeSettings.highlightByLinkNodeFadingMin < alpha
2264
+ ? this.nodeSettings.highlightByLinkNodeFadingMin
2265
+ : alpha;
2266
+ alpha = animationByProgress(min, alpha - min, 1 - this.highlightProgress);
2267
+ }
2268
+ if (this.nodeSettings.highlightByLinkTextFading) {
2269
+ const min = this.nodeSettings.highlightByLinkTextFadingMin < textAlpha
2270
+ ? this.nodeSettings.highlightByLinkTextFadingMin
2271
+ : textAlpha;
2272
+ textAlpha = animationByProgress(min, textAlpha - min, 1 - this.highlightProgress);
2273
+ }
2274
+ if (this.nodeSettings.highlightByLinkNodeColor) {
2275
+ const colorRgb = extractRgb(colorToRgb(color));
2276
+ if (colorRgb) {
2277
+ const colorRgbFade = fadeRgb(colorRgb, this.nodeSettings.highlightByLinkNodeColorFadingMin);
2278
+ const colorFadeAnimation = rgbAnimationByProgress(colorRgb, colorRgbFade, this.highlightProgress);
2279
+ color = `rgb(${colorFadeAnimation.r}, ${colorFadeAnimation.g}, ${colorFadeAnimation.b})`;
2280
+ }
2281
+ }
2282
+ }
2283
+ else {
2284
+ /** Highlighted */
2285
+ if (this.nodeSettings.highlightByLinkNodeSizing && nodeOptions.shape === "circle") {
2286
+ radiusInitial = animationByProgress(radiusInitial, this.nodeSettings.highlightByLinkNodeSizingAdditional, this.highlightProgress);
2287
+ }
2288
+ if (this.nodeSettings.highlightByLinkNodeSizing &&
2289
+ (nodeOptions.shape === "square" ||
2290
+ nodeOptions.shape === "text" ||
2291
+ nodeOptions.shape === "icon")) {
2292
+ sizeCoefficient = animationByProgress(sizeCoefficient, this.nodeSettings.highlightByNodeNodeSizingAdditionalCoefficient, this.highlightProgress);
2293
+ widthInitial *= sizeCoefficient;
2294
+ heightInitial *= sizeCoefficient;
2295
+ }
2296
+ if (this.nodeSettings.highlightByLinkTextSizing) {
2297
+ textSize = animationByProgress(textSize, this.nodeSettings.highlightByLinkTextSizingAdditional, this.highlightProgress);
2298
+ textShiftX = animationByProgress(textShiftX, this.nodeSettings.highlightByLinkTextShiftXAdditional, this.highlightProgress);
2299
+ textShiftY = animationByProgress(textShiftY, this.nodeSettings.highlightByLinkTextShiftYAdditional, this.highlightProgress);
2300
+ textWeight = animationByProgress(textWeight, this.nodeSettings.highlightByLinkTextWeightAdditional, this.highlightProgress);
2301
+ textWidth = animationByProgress(textWidth, this.nodeSettings.highlightByLinkTextWidthAdditional, this.highlightProgress);
2302
+ }
2303
+ }
2304
+ }
2305
+ /** Flex radius */
2306
+ const radius = nodeOptions.shape === "circle"
2307
+ ? nodeRadiusGetter({
2308
+ radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2309
+ radiusInitial,
2310
+ radiusCoefficient: this.nodeSettings.nodeRadiusCoefficient,
2311
+ radiusFactor: this.nodeSettings.nodeRadiusFactor,
2312
+ linkCount: node.linkCount,
2313
+ })
2314
+ : radiusInitial;
2315
+ /** Flex size */
2316
+ let height = heightInitial;
2317
+ let width = widthInitial;
2318
+ if (nodeOptions.shape === "square") {
2319
+ const size = nodeSizeGetter({
2320
+ heightInitial,
2321
+ widthInitial,
2322
+ linkCount: node.linkCount,
2323
+ sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
2324
+ sizeFactor: this.nodeSettings.nodeSizeFactor,
2325
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2326
+ });
2327
+ width = size.width;
2328
+ height = size.height;
2329
+ }
2330
+ if (nodeOptions.shape === "text") {
2331
+ width = nodeOptions.width;
2332
+ const size = nodeSizeGetter({
2333
+ heightInitial,
2334
+ widthInitial: width,
2335
+ linkCount: node.linkCount,
2336
+ sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
2337
+ sizeFactor: this.nodeSettings.nodeSizeFactor,
2338
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2339
+ });
2340
+ textSize *= size.additionalSizeCoefficient;
2341
+ }
2342
+ /** Size by text in textNode */
2343
+ if (nodeOptions.shape === "text" && nodeOptions.text) {
2344
+ textWidth = width;
2345
+ let lines;
2346
+ let textNodeParameters;
2347
+ const cachedLines = this.cachedNodeText[node.id];
2348
+ const cachedTextNodeParameters = this.cachedTextNodeParameters[node.id];
2349
+ if (cachedLines != undefined && cachedTextNodeParameters != undefined) {
2350
+ lines = cachedLines;
2351
+ textNodeParameters = cachedTextNodeParameters;
2352
+ }
2353
+ else {
2354
+ const textInfo = getTextLines({
2355
+ context: this.context,
2356
+ text: nodeOptions.text,
2357
+ textAlign: nodeOptions.textAlign,
2358
+ textColor: nodeOptions.textColor,
2359
+ textFont: nodeOptions.textFont,
2360
+ textSize,
2361
+ maxWidth: textWidth,
2362
+ textStyle: nodeOptions.textStyle,
2363
+ textWeight,
2364
+ });
2365
+ textNodeParameters = [textInfo.currentMaxSize, textSize];
2366
+ lines = textInfo.lines;
2367
+ this.cachedNodeText[node.id] = lines;
2368
+ this.cachedTextNodeParameters[node.id] = textNodeParameters;
2369
+ }
2370
+ const textSizeCoefficient = textSize / textNodeParameters[1];
2371
+ const maxSize = textNodeParameters[0] * textSizeCoefficient;
2372
+ height =
2373
+ lines.length * textSize +
2374
+ (lines.length - 1) * nodeOptions.textGap +
2375
+ nodeOptions.textNodeYPadding;
2376
+ width = maxSize + nodeOptions.textNodeXPadding;
2377
+ }
2378
+ /** Node parameters */
2379
+ node._radius = radius;
2380
+ node._width = width;
2381
+ node._height = height;
2382
+ node._borderRadius =
2383
+ jsHelpers.isNumber(nodeOptions.borderRadius) && nodeOptions.borderRadius > 0
2384
+ ? Math.min(nodeOptions.borderRadius, nodeOptions.width / 2, nodeOptions.height / 2)
2385
+ : 0;
2386
+ node._shape = nodeOptions.shape ?? "circle";
2387
+ /** Node Visibility */
2388
+ if (!isNodeVisible({
2389
+ height: this.height,
2390
+ width: this.width,
2391
+ transform: this.areaTransform,
2392
+ node,
2393
+ })) {
2394
+ node._visible = false;
2395
+ return;
2396
+ }
2397
+ node._visible = true;
2398
+ /** Node draw */
2399
+ nodeRenders.push(() => {
2400
+ if (!this.context || !node.x || !node.y)
2401
+ return;
2402
+ this.context.beginPath();
2403
+ this.context.globalAlpha = alpha;
2404
+ this.context.lineWidth = nodeOptions.borderWidth;
2405
+ this.context.strokeStyle = nodeOptions.borderColor;
2406
+ this.context.fillStyle = color;
2407
+ switch (nodeOptions.shape) {
2408
+ case "circle": {
2409
+ this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
2410
+ break;
2411
+ }
2412
+ case "square": {
2413
+ this.context.roundRect(node.x - width / 2, node.y - height / 2, width, height, node._borderRadius);
2414
+ break;
2415
+ }
2416
+ case "text": {
2417
+ if (this.nodeSettings.textNodeDebug) {
2418
+ this.context.strokeRect(node.x - width / 2, node.y - height / 2, width, height);
2419
+ }
2420
+ const lines = this.cachedNodeText[node.id];
2421
+ if (nodeOptions.text && lines)
2422
+ drawText({
2423
+ id: node.id,
2424
+ cachedNodeText: this.cachedNodeText,
2425
+ context: this.context,
2426
+ text: nodeOptions.text,
2427
+ textAlign: nodeOptions.textAlign,
2428
+ textColor: nodeOptions.textColor,
2429
+ textFont: nodeOptions.textFont,
2430
+ textSize,
2431
+ x: node.x,
2432
+ y: node.y + textSize / 4 - (lines.length - 1) * (textSize / 2),
2433
+ maxWidth: textWidth,
2434
+ textStyle: nodeOptions.textStyle,
2435
+ textWeight,
2436
+ textGap: nodeOptions.textGap,
2437
+ });
2438
+ break;
2439
+ }
2440
+ default: {
2441
+ this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
2442
+ break;
2443
+ }
2444
+ }
2445
+ this.context.fill();
2446
+ this.context.stroke();
2447
+ });
2448
+ if (nodeOptions.nodeExtraDraw) {
2449
+ nodeRenders.push(() => {
2450
+ nodeOptions?.nodeExtraDraw?.(node, {
2451
+ ...nodeOptions,
2452
+ radius,
2453
+ alpha,
2454
+ color,
2455
+ textAlpha,
2456
+ textSize,
2457
+ textShiftX,
2458
+ textShiftY,
2459
+ textWeight,
2460
+ textWidth,
2461
+ }, state);
2462
+ });
2463
+ }
2464
+ /** Text draw */
2465
+ if (nodeOptions.textVisible && nodeOptions.text && nodeOptions.shape !== "text") {
2466
+ textRenders.push(() => {
2467
+ if (nodeOptions.textDraw) {
2468
+ nodeOptions.textDraw(node, {
2469
+ ...nodeOptions,
2470
+ radius,
2471
+ alpha,
2472
+ color,
2473
+ textAlpha,
2474
+ textSize,
2475
+ textShiftX,
2476
+ textShiftY,
2477
+ textWeight,
2478
+ textWidth,
2479
+ }, state);
2480
+ return;
2481
+ }
2482
+ if (!this.context || !node.x || !node.y || !nodeOptions.text)
2483
+ return;
2484
+ this.context.beginPath();
2485
+ this.context.globalAlpha = textAlpha;
2486
+ let y = node.y + textShiftY;
2487
+ if (nodeOptions.shape === "circle") {
2488
+ y += radius;
2489
+ }
2490
+ if (nodeOptions.shape === "square") {
2491
+ y += height / 2;
2492
+ }
2493
+ drawText({
2494
+ id: node.id,
2495
+ cachedNodeText: this.cachedNodeText,
2496
+ context: this.context,
2497
+ text: nodeOptions.text,
2498
+ textAlign: nodeOptions.textAlign,
2499
+ textColor: nodeOptions.textColor,
2500
+ textFont: nodeOptions.textFont,
2501
+ textSize,
2502
+ x: node.x + textShiftX,
2503
+ y,
2504
+ maxWidth: textWidth,
2505
+ textStyle: nodeOptions.textStyle,
2506
+ textWeight,
2507
+ textGap: nodeOptions.textGap,
2508
+ });
2509
+ if (nodeOptions.textExtraDraw) {
2510
+ nodeOptions.textExtraDraw(node, {
2511
+ ...nodeOptions,
2512
+ radius,
2513
+ alpha,
2514
+ color,
2515
+ textAlpha,
2516
+ textSize,
2517
+ textShiftX,
2518
+ textShiftY,
2519
+ textWeight,
2520
+ textWidth,
2521
+ }, state);
2522
+ }
2523
+ });
2524
+ }
2525
+ };
2526
+ }
2527
+
2528
+ function initDraw() {
2529
+ function calculateHighlight() {
2530
+ this.highlightDrawing = true;
2531
+ if (!this.highlightWorking && this.highlightProgress > 0) {
2532
+ const highlightDownStep = 1 / this.graphSettings.highlightDownFrames;
2533
+ this.highlightProgress -= highlightDownStep;
2534
+ if (!this.simulationWorking) {
2535
+ return void requestAnimationFrame(() => this.draw());
2536
+ }
2537
+ if (!this.linkSettings.particles)
2538
+ return;
2539
+ }
2540
+ if (this.highlightWorking && this.highlightProgress < 1) {
2541
+ const highlightUpStep = 1 / this.graphSettings.highlightUpFrames;
2542
+ this.highlightProgress += highlightUpStep;
2543
+ if (!this.simulationWorking) {
2544
+ return void requestAnimationFrame(() => this.draw());
2545
+ }
2546
+ if (!this.linkSettings.particles)
2547
+ return;
2548
+ }
2549
+ if (this.linkSettings.particles && this.highlightWorking && !this.simulationWorking) {
2550
+ return void requestAnimationFrame(() => this.draw());
2551
+ }
2552
+ if (!this.highlightWorking && this.highlightProgress <= 0) {
2553
+ if (this.highlightedNeighbors || this.highlightedNode || this.highlightedLink) {
2554
+ this.highlightedNeighbors = null;
2555
+ this.highlightedNode = null;
2556
+ this.highlightedLink = null;
2557
+ this.particles = {};
2558
+ if (!this.simulationWorking) {
2559
+ return void requestAnimationFrame(() => this.draw());
2560
+ }
2561
+ }
2562
+ }
2563
+ this.highlightDrawing = false;
2564
+ }
2565
+ function draw() {
2566
+ if (!this.context)
2567
+ return;
2568
+ const state = this.state;
2569
+ if (this.listeners.onDraw) {
2570
+ this.listeners.onDraw(state, (status) => {
2571
+ this.highlightDrawing = status;
2572
+ }, () => {
2573
+ if (this.highlightedNeighbors)
2574
+ this.highlightedNeighbors = null;
2575
+ if (this.highlightedNode)
2576
+ this.highlightedNode = null;
2577
+ if (this.highlightedLink)
2578
+ this.highlightedLink = null;
2579
+ });
2580
+ return;
2581
+ }
2582
+ this.context.save();
2583
+ this.context.clearRect(0, 0, this.width, this.height);
2584
+ this.context.translate(this.areaTransform.x, this.areaTransform.y);
2585
+ this.context.scale(this.areaTransform.k, this.areaTransform.k);
2586
+ const textRenders = [];
2587
+ const nodeRenders = [];
2588
+ this.nodes.forEach(getDrawNode(nodeRenders, textRenders, state).bind(this));
2589
+ /** links */
2590
+ this.links.forEach(getDrawLink(state).bind(this));
2591
+ /** nodes */
2592
+ nodeRenders.forEach((render) => render());
2593
+ textRenders.forEach((render) => render());
2594
+ this.context.restore();
2595
+ this.listeners.onDrawFinished?.(state);
2596
+ calculateHighlight.bind(this)();
2597
+ }
2598
+ if (this.graphSettings.showDrawTime) {
2599
+ return setDrawTime(draw.bind(this), this.graphSettings.showDrawTimeEveryTick);
2600
+ }
2601
+ return draw.bind(this);
2602
+ }
2603
+
2604
+ function initPointer() {
2605
+ if (!this.area || !this.nodes)
2606
+ throw new Error("bad init data");
2607
+ function onHover(event) {
2608
+ if (!this.area)
2609
+ return;
2610
+ let currentNode;
2611
+ let currentLink;
2612
+ const checkHighlightNode = this.nodeSettings.highlightByHoverNode && !this.isDragging;
2613
+ const checkHighlightLink = this.linkSettings.highlightByHoverLink && !this.isDragging;
2614
+ if (checkHighlightNode) {
2615
+ currentNode = nodeByPointerGetter({
2616
+ areaRect: this.areaRect,
2617
+ areaTransform: this.areaTransform,
2618
+ mouseEvent: event,
2619
+ nodes: this.nodes,
2620
+ });
2621
+ }
2622
+ if (currentNode) {
2623
+ this.area.style.cursor = "pointer";
2624
+ }
2625
+ else if (checkHighlightLink) {
2626
+ currentLink = linkByPointerGetter({
2627
+ linkSettings: this.linkSettings,
2628
+ areaRect: this.areaRect,
2629
+ areaTransform: this.areaTransform,
2630
+ mouseEvent: event,
2631
+ links: this.links,
2632
+ });
2633
+ if (currentLink) {
2634
+ this.area.style.cursor = "pointer";
2635
+ }
2636
+ else {
2637
+ this.area.style.cursor = "default";
2638
+ }
2639
+ }
2640
+ else {
2641
+ this.area.style.cursor = "default";
2642
+ }
2643
+ if (currentNode && this.highlightedNode !== currentNode) {
2644
+ this.highlightedNode = currentNode;
2645
+ this.highlightedLink = null;
2646
+ this.highlightedNeighbors = new Set(this.highlightedNode?.neighbors ?? []);
2647
+ this.highlightWorking = true;
2648
+ if (!this.simulationWorking && !this.highlightDrawing)
2649
+ requestAnimationFrame(() => {
2650
+ this.draw();
2651
+ });
2652
+ }
2653
+ else if (currentLink &&
2654
+ checkType(currentLink.source, lodash.isObject(currentLink.source)) &&
2655
+ checkType(currentLink.target, lodash.isObject(currentLink.target)) &&
2656
+ this.highlightedLink !== currentLink) {
2657
+ this.highlightProgress = 0;
2658
+ this.highlightedLink = currentLink;
2659
+ this.highlightedNode = null;
2660
+ this.highlightedNeighbors = new Set([currentLink.source.id, currentLink.target.id]);
2661
+ this.highlightWorking = true;
2662
+ if (!this.simulationWorking && !this.highlightDrawing)
2663
+ requestAnimationFrame(() => {
2664
+ this.draw();
2665
+ });
2666
+ }
2667
+ else if (!currentNode && !currentLink && (this.highlightedNode || this.highlightedLink)) {
2668
+ this.highlightWorking = false;
2669
+ if (!this.simulationWorking && !this.highlightDrawing)
2670
+ requestAnimationFrame(() => {
2671
+ this.draw();
2672
+ });
2673
+ }
2674
+ if (!this.listeners.onMove)
2675
+ return;
2676
+ if (!currentNode && !checkHighlightNode)
2677
+ currentNode = nodeByPointerGetter({
2678
+ areaRect: this.areaRect,
2679
+ areaTransform: this.areaTransform,
2680
+ mouseEvent: event,
2681
+ nodes: this.nodes,
2682
+ });
2683
+ if (!currentNode && (!checkHighlightNode || (!checkHighlightLink && !currentLink))) {
2684
+ currentLink = linkByPointerGetter({
2685
+ linkSettings: this.linkSettings,
2686
+ areaRect: this.areaRect,
2687
+ areaTransform: this.areaTransform,
2688
+ mouseEvent: event,
2689
+ links: this.links,
2690
+ });
2691
+ }
2692
+ if (!currentNode)
2693
+ return void this.listeners.onMove(event, currentNode, currentLink);
2694
+ }
2695
+ function onWheelClick(event) {
2696
+ if (this.isDragging ||
2697
+ !this.listeners.onWheelClick ||
2698
+ !("button" in event) ||
2699
+ event.button !== 1)
2700
+ return;
2701
+ const currentNode = nodeByPointerGetter({
2702
+ areaRect: this.areaRect,
2703
+ areaTransform: this.areaTransform,
2704
+ mouseEvent: event,
2705
+ nodes: this.nodes,
2706
+ });
2707
+ if (!currentNode) {
2708
+ const currentLink = linkByPointerGetter({
2709
+ linkSettings: this.linkSettings,
2710
+ areaRect: this.areaRect,
2711
+ areaTransform: this.areaTransform,
2712
+ mouseEvent: event,
2713
+ links: this.links,
2714
+ });
2715
+ return void this.listeners.onWheelClick(event, undefined, currentLink);
2716
+ }
2717
+ return void this.listeners.onWheelClick(event, currentNode, undefined);
2718
+ }
2719
+ function onRightClick(event) {
2720
+ if (!this.listeners.onContextMenu)
2721
+ return;
2722
+ const currentNode = nodeByPointerGetter({
2723
+ areaRect: this.areaRect,
2724
+ areaTransform: this.areaTransform,
2725
+ mouseEvent: event,
2726
+ nodes: this.nodes,
2727
+ });
2728
+ if (!currentNode) {
2729
+ const currentLink = linkByPointerGetter({
2730
+ linkSettings: this.linkSettings,
2731
+ areaRect: this.areaRect,
2732
+ areaTransform: this.areaTransform,
2733
+ mouseEvent: event,
2734
+ links: this.links,
2735
+ });
2736
+ return void this.listeners.onContextMenu(event, undefined, currentLink);
2737
+ }
2738
+ return void this.listeners.onContextMenu(event, currentNode, undefined);
2739
+ }
2740
+ function onDoubleClick(event) {
2741
+ if (!this.listeners.onDoubleClick)
2742
+ return;
2743
+ const currentNode = nodeByPointerGetter({
2744
+ areaRect: this.areaRect,
2745
+ areaTransform: this.areaTransform,
2746
+ mouseEvent: event,
2747
+ nodes: this.nodes,
2748
+ });
2749
+ if (!currentNode) {
2750
+ const currentLink = linkByPointerGetter({
2751
+ linkSettings: this.linkSettings,
2752
+ areaRect: this.areaRect,
2753
+ areaTransform: this.areaTransform,
2754
+ mouseEvent: event,
2755
+ links: this.links,
2756
+ });
2757
+ return void this.listeners.onDoubleClick(event, undefined, currentLink);
2758
+ }
2759
+ return void this.listeners.onDoubleClick(event, currentNode, undefined);
2760
+ }
2761
+ function onClick(event) {
2762
+ if (this.isDragging || !this.listeners.onClick || ("button" in event && event.button !== 0))
2763
+ return;
2764
+ const currentNode = nodeByPointerGetter({
2765
+ areaRect: this.areaRect,
2766
+ areaTransform: this.areaTransform,
2767
+ mouseEvent: event,
2768
+ nodes: this.nodes,
2769
+ });
2770
+ if (!currentNode) {
2771
+ const currentLink = linkByPointerGetter({
2772
+ linkSettings: this.linkSettings,
2773
+ areaRect: this.areaRect,
2774
+ areaTransform: this.areaTransform,
2775
+ mouseEvent: event,
2776
+ links: this.links,
2777
+ });
2778
+ return void this.listeners.onClick(event, undefined, currentLink);
2779
+ }
2780
+ return void this.listeners.onClick(event, currentNode, undefined);
2781
+ }
2782
+ /** hover */
2783
+ this.area.addEventListener("mousemove", onHover.bind(this), {
2784
+ signal: this.eventAbortController.signal,
2785
+ });
2786
+ this.area.addEventListener("touchmove", onHover.bind(this), {
2787
+ signal: this.eventAbortController.signal,
2788
+ });
2789
+ /** dblclick */
2790
+ this.area.addEventListener("dblclick", onDoubleClick.bind(this), {
2791
+ signal: this.eventAbortController.signal,
2792
+ });
2793
+ /** wheel click */
2794
+ this.area.addEventListener("mousedown", onWheelClick.bind(this), {
2795
+ signal: this.eventAbortController.signal,
2796
+ });
2797
+ /** click */
2798
+ this.area.addEventListener("click", onClick.bind(this), {
2799
+ signal: this.eventAbortController.signal,
2800
+ });
2801
+ /** right click */
2802
+ this.area.addEventListener("contextmenu", onRightClick.bind(this), {
2803
+ signal: this.eventAbortController.signal,
2804
+ });
1119
2805
  }
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;
2806
+
2807
+ function initResize() {
2808
+ if (!this.area)
2809
+ throw new Error("bad init data");
2810
+ let initialResizeCall = true;
2811
+ const abortController = this.eventAbortController;
2812
+ const observer = new ResizeObserver(() => {
2813
+ if (initialResizeCall) {
2814
+ initialResizeCall = false;
2815
+ return;
2816
+ }
2817
+ if (abortController.signal.aborted) {
2818
+ observer.disconnect();
2819
+ return;
2820
+ }
2821
+ requestAnimationFrame(() => {
2822
+ this.updateSize();
2823
+ });
2824
+ });
2825
+ document.addEventListener("scroll", this.updateRect.bind(this), {
2826
+ capture: true,
2827
+ passive: true,
2828
+ signal: abortController.signal,
2829
+ });
2830
+ observer.observe(this.area);
1256
2831
  }
1257
2832
 
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);
2833
+ function initSimulation() {
2834
+ if (!this.simulation) {
2835
+ this.simulation = d3Force.forceSimulation()
2836
+ .nodes(this.nodes)
2837
+ .force("link", d3Force.forceLink(this.links).id(this.nodeSettings.idGetter))
2838
+ .on("tick", () => {
2839
+ this.draw();
2840
+ })
2841
+ .on("end", () => {
2842
+ this.listeners.onSimulationEnd?.(this.state);
2843
+ if (this.graphSettings.showDrawTime) {
2844
+ getDrawTime();
2845
+ // eslint-disable-next-line no-console
2846
+ console.log(`nodes: ${this.nodes.length} | links: ${this.links.length}`);
2847
+ resetDrawTime();
2848
+ }
2849
+ });
2850
+ initSimulationForces.call(this);
2851
+ }
2852
+ }
2853
+ function initSimulationForces() {
2854
+ if (!this.simulation)
2855
+ return;
2856
+ const linkForce = this.simulation.force("link");
2857
+ if (!linkForce)
2858
+ return;
2859
+ if ((!this.forceSettings.forces || !this.forceSettings.xForce) && this.simulation.force("x")) {
2860
+ this.simulation.force("x", null);
2861
+ }
2862
+ if ((!this.forceSettings.forces || !this.forceSettings.yForce) && this.simulation.force("y")) {
2863
+ this.simulation.force("y", null);
2864
+ }
2865
+ if ((!this.forceSettings.forces || !this.forceSettings.chargeForce) &&
2866
+ this.simulation.force("charge")) {
2867
+ this.simulation.force("charge", null);
2868
+ }
2869
+ if ((!this.forceSettings.forces || !this.forceSettings.centerForce) &&
2870
+ this.simulation.force("center")) {
2871
+ this.simulation.force("center", null);
2872
+ }
2873
+ if (!this.forceSettings.forces || !this.forceSettings.linkForce) {
2874
+ linkForce.distance(0).strength(0).iterations(0);
2875
+ }
2876
+ if (this.forceSettings.forces && this.forceSettings.linkForce) {
2877
+ linkForce
2878
+ .distance(this.forceSettings.linkDistance)
2879
+ .strength(this.forceSettings.linkStrength)
2880
+ .iterations(this.forceSettings.linkIterations);
2881
+ }
2882
+ if (this.forceSettings.forces && this.forceSettings.xForce) {
2883
+ this.simulation.force("x", d3Force.forceX(this.forceSettings.xPosition).strength(this.forceSettings.xStrength));
2884
+ }
2885
+ if (this.forceSettings.forces && this.forceSettings.yForce) {
2886
+ this.simulation.force("y", d3Force.forceY(this.forceSettings.yPosition).strength(this.forceSettings.yStrength));
2887
+ }
2888
+ if (this.forceSettings.forces && this.forceSettings.chargeForce) {
2889
+ this.simulation.force("charge", d3Force.forceManyBody()
2890
+ .strength(this.forceSettings.chargeStrength)
2891
+ .distanceMax(this.forceSettings.chargeDistanceMax)
2892
+ .distanceMin(this.forceSettings.chargeDistanceMin));
2893
+ }
2894
+ if (this.forceSettings.forces && this.forceSettings.centerForce) {
2895
+ this.simulation.force("center", d3Force.forceCenter(this.forceSettings.centerPosition.x ?? 0, this.forceSettings.centerPosition.y ?? 0).strength(this.forceSettings.centerStrength));
2896
+ }
2897
+ initCollideForce.call(this, true);
2898
+ }
2899
+ function initCollideForce(forceUpdate) {
2900
+ if (!this.simulation)
2901
+ return;
2902
+ if ((!this.forceSettings.collideForce || !this.forceSettings.forces) &&
2903
+ this.simulation.force("collide")) {
2904
+ this.simulation.force("collide", null);
2905
+ }
2906
+ if (this.forceSettings.forces && this.forceSettings.collideForce) {
2907
+ const isHasMax = this.forceSettings.collideOffMax.links != 0 && this.forceSettings.collideOffMax.nodes != 0;
2908
+ const isMaxCollideNodes = isHasMax && this.forceSettings.collideOffMax.nodes < this.nodes.length;
2909
+ const isMaxCollideLinks = isHasMax && this.forceSettings.collideOffMax.links < this.links.length;
2910
+ if (isMaxCollideNodes && isMaxCollideLinks) {
2911
+ this.simulation.force("collide", null);
1272
2912
  }
1273
- };
2913
+ else if (!this.simulation.force("collide") || forceUpdate) {
2914
+ this.simulation.force("collide", d3Force.forceCollide()
2915
+ .radius((node, index) => {
2916
+ const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.state, this.nodeSettings.options ?? {}, nodeOptionsGetter);
2917
+ switch (nodeOptions.shape) {
2918
+ case "circle": {
2919
+ if (this.forceSettings.collideRadius) {
2920
+ return nodeIterationExtractor(node, index, this.nodes, this.state, this.forceSettings.collideRadius, undefined);
2921
+ }
2922
+ const radius = nodeRadiusGetter({
2923
+ radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2924
+ radiusInitial: nodeOptions.radius,
2925
+ radiusCoefficient: this.nodeSettings.nodeRadiusCoefficient,
2926
+ radiusFactor: this.nodeSettings.nodeRadiusFactor,
2927
+ linkCount: node.linkCount,
2928
+ });
2929
+ return radius + this.forceSettings.collideAdditionalRadius;
2930
+ }
2931
+ case "square": {
2932
+ const { height, width } = nodeSizeGetter({
2933
+ heightInitial: nodeOptions.width,
2934
+ widthInitial: nodeOptions.height,
2935
+ linkCount: node.linkCount,
2936
+ sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
2937
+ sizeFactor: this.nodeSettings.nodeSizeFactor,
2938
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2939
+ });
2940
+ return (Math.sqrt(width ** 2 + height ** 2) / 2 +
2941
+ this.forceSettings.collideAdditionalRadius);
2942
+ }
2943
+ case "text": {
2944
+ let { height, width } = nodeSizeGetter({
2945
+ heightInitial: nodeOptions.width,
2946
+ widthInitial: nodeOptions.height,
2947
+ linkCount: node.linkCount,
2948
+ sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
2949
+ sizeFactor: this.nodeSettings.nodeSizeFactor,
2950
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2951
+ });
2952
+ if (this.context && nodeOptions.text) {
2953
+ const lines = this.cachedNodeText[node.id] ??
2954
+ getTextLines({
2955
+ context: this.context,
2956
+ text: nodeOptions.text,
2957
+ textAlign: nodeOptions.textAlign,
2958
+ textColor: nodeOptions.textColor,
2959
+ textFont: nodeOptions.textFont,
2960
+ textSize: nodeOptions.textSize,
2961
+ maxWidth: width,
2962
+ textStyle: nodeOptions.textStyle,
2963
+ textWeight: nodeOptions.textWeight,
2964
+ }).lines;
2965
+ height =
2966
+ lines.length * nodeOptions.textSize +
2967
+ (lines.length - 1) * nodeOptions.textGap +
2968
+ nodeOptions.textNodeYPadding;
2969
+ width += nodeOptions.textNodeXPadding;
2970
+ }
2971
+ return (Math.sqrt(width ** 2 + height ** 2) / 2 +
2972
+ this.forceSettings.collideAdditionalRadius);
2973
+ }
2974
+ default: {
2975
+ if (this.forceSettings.collideRadius) {
2976
+ return nodeIterationExtractor(node, index, this.nodes, this.state, this.forceSettings.collideRadius, undefined);
2977
+ }
2978
+ const radius = nodeRadiusGetter({
2979
+ radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2980
+ radiusInitial: nodeOptions.radius,
2981
+ radiusCoefficient: this.nodeSettings.nodeRadiusCoefficient,
2982
+ radiusFactor: this.nodeSettings.nodeRadiusFactor,
2983
+ linkCount: node.linkCount,
2984
+ });
2985
+ return radius + this.forceSettings.collideAdditionalRadius;
2986
+ }
2987
+ }
2988
+ })
2989
+ .strength(this.forceSettings.collideStrength)
2990
+ .iterations(this.forceSettings.collideIterations));
2991
+ }
2992
+ }
1274
2993
  }
1275
- function getDrawTime() {
1276
- const average = countTime / countOperation;
1277
- // eslint-disable-next-line no-console
1278
- console.log("average draw: ", average);
2994
+
2995
+ function initZoom(currentZoom) {
2996
+ if (!this.area)
2997
+ throw new Error("bad init data");
2998
+ const zoomInstance = d3Zoom.zoom()
2999
+ .scaleExtent(this.graphSettings.zoomExtent)
3000
+ .on("zoom", (event) => {
3001
+ this.listeners.onZoom?.(event);
3002
+ this.areaTransform = event.transform;
3003
+ this.linkOptionsCache = {};
3004
+ this.nodeOptionsCache = {};
3005
+ if (!this.simulationWorking && !this.highlightWorking)
3006
+ requestAnimationFrame(() => this.draw());
3007
+ });
3008
+ if (this.graphSettings.translateExtentEnable) {
3009
+ const coefficient = this.graphSettings.translateExtentCoefficient;
3010
+ const [coefficientX, coefficientY] = jsHelpers.isArray(coefficient)
3011
+ ? coefficient
3012
+ : [coefficient, coefficient];
3013
+ const [[minX = -this.width * coefficientX, minY = -this.height * coefficientY], [maxX = this.width * coefficientX, maxY = this.height * coefficientY],] = this.graphSettings.translateExtent;
3014
+ zoomInstance.translateExtent([
3015
+ [minX, minY],
3016
+ [maxX, maxY],
3017
+ ]);
3018
+ }
3019
+ d3Selection.select(this.area).call(zoomInstance).on("dblclick.zoom", null);
3020
+ const zoomInitial = currentZoom ?? this.graphSettings.zoomInitial;
3021
+ this.areaTransform = new d3Zoom.ZoomTransform(zoomInitial?.k ?? 1, zoomInitial?.x ?? this.width / 2, zoomInitial?.y ?? this.height / 2);
3022
+ d3Zoom.zoom().transform(d3Selection.select(this.area), this.areaTransform);
1279
3023
  }
1280
3024
 
1281
3025
  class GraphCanvas {
@@ -1302,6 +3046,7 @@ class GraphCanvas {
1302
3046
  draw;
1303
3047
  eventAbortController;
1304
3048
  cachedNodeText = {};
3049
+ cachedTextNodeParameters = {};
1305
3050
  linkOptionsCache = {};
1306
3051
  nodeOptionsCache = {};
1307
3052
  isDragging = false;
@@ -1357,7 +3102,7 @@ class GraphCanvas {
1357
3102
  this.links = links;
1358
3103
  this.height = 0;
1359
3104
  this.width = 0;
1360
- this.draw = this.initDraw();
3105
+ this.draw = initDraw.call(this);
1361
3106
  this.init();
1362
3107
  }
1363
3108
  get dpi() {
@@ -1380,8 +3125,8 @@ class GraphCanvas {
1380
3125
  changeSettings(options) {
1381
3126
  if (options.graphSettings) {
1382
3127
  this.graphSettings = graphSettingsGetter(options.graphSettings, this.graphSettings);
1383
- this.draw = this.initDraw();
1384
- this.initZoom(this.areaTransform);
3128
+ this.draw = initDraw.call(this);
3129
+ initZoom.call(this, this.areaTransform);
1385
3130
  }
1386
3131
  if (options.forceSettings) {
1387
3132
  this.forceSettings = forceSettingsGetter(options.forceSettings, this.forceSettings);
@@ -1393,7 +3138,9 @@ class GraphCanvas {
1393
3138
  if (options.nodeSettings) {
1394
3139
  this.nodeSettings = nodeSettingsGetter(options.nodeSettings, this.nodeSettings);
1395
3140
  this.cachedNodeText = {};
3141
+ this.cachedTextNodeParameters = {};
1396
3142
  this.nodeOptionsCache = {};
3143
+ initCollideForce.call(this, true);
1397
3144
  }
1398
3145
  if (options.forceSettings) {
1399
3146
  return void this.updateSimulation();
@@ -1408,6 +3155,7 @@ class GraphCanvas {
1408
3155
  this.nodeOptionsCache = {};
1409
3156
  this.linkOptionsCache = {};
1410
3157
  this.cachedNodeText = {};
3158
+ this.cachedTextNodeParameters = {};
1411
3159
  }
1412
3160
  tick() {
1413
3161
  if (!this.simulationWorking && !this.highlightWorking)
@@ -1439,7 +3187,7 @@ class GraphCanvas {
1439
3187
  }
1440
3188
  this.clearHTMLElements();
1441
3189
  this.clearState();
1442
- this.clearDataDependencies();
3190
+ this.clearCache();
1443
3191
  }
1444
3192
  clearHTMLElements() {
1445
3193
  this.root.replaceChildren();
@@ -1451,7 +3199,7 @@ class GraphCanvas {
1451
3199
  }
1452
3200
  updateSimulation() {
1453
3201
  if (this.simulation) {
1454
- this.initSimulationForces();
3202
+ initSimulationForces.call(this);
1455
3203
  this.simulation.alpha(1);
1456
3204
  this.simulation.restart();
1457
3205
  }
@@ -1465,941 +3213,62 @@ class GraphCanvas {
1465
3213
  this.highlightWorking = false;
1466
3214
  this.highlightDrawing = false;
1467
3215
  }
1468
- clearDataDependencies() {
1469
- this.cachedNodeText = {};
1470
- this.nodeOptionsCache = {};
1471
- this.linkOptionsCache = {};
1472
- }
1473
3216
  updateData(alpha) {
1474
- this.clearDataDependencies();
3217
+ this.clearCache();
1475
3218
  if (this.simulation) {
1476
- this.initCollideForce();
3219
+ initCollideForce.call(this, false);
1477
3220
  this.simulation
1478
3221
  .nodes(this.nodes)
1479
3222
  .force("link", d3Force.forceLink(this.links)
1480
3223
  .id(this.nodeSettings.idGetter)
1481
- .distance(this.forceSettings.linkDistance)
1482
- .strength(this.forceSettings.linkStrength)
1483
- .iterations(this.forceSettings.linkIterations))
3224
+ .distance(this.forceSettings.forces && this.forceSettings.linkForce
3225
+ ? this.forceSettings.linkDistance
3226
+ : 0)
3227
+ .strength(this.forceSettings.forces && this.forceSettings.linkForce
3228
+ ? this.forceSettings.linkStrength
3229
+ : 0)
3230
+ .iterations(this.forceSettings.forces && this.forceSettings.linkForce
3231
+ ? this.forceSettings.linkIterations
3232
+ : 0))
1484
3233
  .alpha(alpha ?? 0.5)
1485
3234
  .restart();
1486
3235
  }
1487
3236
  }
1488
3237
  updateSize() {
1489
3238
  this.clearHTMLElements();
1490
- this.initArea();
1491
- this.initDnd();
1492
- this.initZoom();
1493
- this.initResize();
1494
- this.initPointer();
3239
+ initArea.call(this);
3240
+ initDnd.call(this);
3241
+ initZoom.call(this);
3242
+ initResize.call(this);
3243
+ initPointer.call(this);
1495
3244
  if (!this.simulationWorking && !this.highlightWorking)
1496
3245
  this.draw();
1497
3246
  }
1498
3247
  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);
3248
+ initArea.call(this);
3249
+ initSimulation.call(this);
3250
+ initDnd.call(this);
3251
+ initZoom.call(this);
3252
+ initResize.call(this);
3253
+ initPointer.call(this);
2389
3254
  }
2390
3255
  }
2391
3256
 
2392
3257
  exports.GraphCanvas = GraphCanvas;
2393
3258
  exports.animationByProgress = animationByProgress;
2394
- exports.calculateLinkPositionByRadius = calculateLinkPositionByRadius;
3259
+ exports.calculateLinkPositionByNode = calculateLinkPositionByNode;
2395
3260
  exports.colorToRgb = colorToRgb;
2396
3261
  exports.drawText = drawText;
2397
3262
  exports.extractRgb = extractRgb;
2398
3263
  exports.fadeRgb = fadeRgb;
3264
+ exports.getDrawLink = getDrawLink;
3265
+ exports.getDrawNode = getDrawNode;
2399
3266
  exports.getForceControls = getForceControls;
2400
3267
  exports.getGraphControls = getGraphControls;
2401
- exports.getLinkControls = getLinkControls;
2402
- exports.getNodeControls = getNodeControls;
3268
+ exports.getLinkOptionsControls = getLinkOptionsControls;
3269
+ exports.getLinkSettingsControls = getLinkSettingsControls;
3270
+ exports.getNodeOptionsControls = getNodeOptionsControls;
3271
+ exports.getNodeSettingControls = getNodeSettingControls;
2403
3272
  exports.getParticlePosition = getParticlePosition;
2404
3273
  exports.linkByPointerGetter = linkByPointerGetter;
2405
3274
  exports.linkIterationExtractor = linkIterationExtractor;