@krainovsd/graph 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/esm/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { least } from 'd3-array';
2
2
  import { drag } from 'd3-drag';
3
- import { forceLink, forceCenter, forceSimulation, forceX, forceY, forceManyBody, forceCollide } from 'd3-force';
3
+ import { forceLink, forceSimulation, forceX, forceY, forceManyBody, forceCenter, forceCollide } from 'd3-force';
4
4
  import { create, select } from 'd3-selection';
5
- import { zoomIdentity, zoom } from 'd3-zoom';
5
+ import { zoomIdentity, zoom, ZoomTransform } from 'd3-zoom';
6
6
  import debounce from 'lodash/debounce';
7
7
 
8
8
  function checkType(value, condition) {
@@ -38,9 +38,9 @@ function colorGetter() {
38
38
  const FORCE_SETTINGS = {
39
39
  centerPosition: {},
40
40
  centerStrength: 1,
41
- collideAdditionalRadius: 2,
42
- collideStrength: 1,
43
- collideIterations: 1,
41
+ collideStrength: 0.1,
42
+ collideAdditionalRadius: 4,
43
+ collideIterations: 2,
44
44
  collideOffMax: { links: 0, nodes: 0 },
45
45
  collideOn: true,
46
46
  chargeStrength: -40,
@@ -50,100 +50,84 @@ const FORCE_SETTINGS = {
50
50
  xStrength: 0.1,
51
51
  yForce: 0,
52
52
  yStrength: 0.1,
53
- linkDistance: 10,
53
+ linkDistance: 30,
54
54
  linkIterations: 1,
55
55
  linkStrength: 1,
56
+ collideRadius: null,
56
57
  };
57
58
  const GRAPH_SETTINGS = {
58
- zoomExtent: [0.1, 10],
59
+ zoomExtent: [0.1, 20],
59
60
  stickAfterDrag: false,
60
61
  highlightByHover: false,
61
- minFading: 0.2,
62
+ highlightFadingMin: 0.21,
63
+ highlightDownStep: 0.09,
64
+ highlightUpStep: 0.09,
65
+ dragPlaceCoefficient: dragPlaceCoefficientGetter,
66
+ nodeRadiusInitial: 4,
67
+ nodeRadiusCoefficient: 5,
68
+ nodeRadiusFactor: 1,
69
+ nodeRadiusFlexible: true,
62
70
  };
63
71
  const NODE_SETTINGS = {
64
72
  alpha: 1,
65
- colorOuter: "#fff",
66
- font: "8px Arial",
67
- fontAlign: "center",
68
- fontColor: "#333",
73
+ borderColor: "#000000FF",
74
+ borderWidth: 0.1,
75
+ textWidth: 20,
76
+ textShiftX: 0,
77
+ textFont: "Arial",
78
+ textAlign: "center",
79
+ textColor: "#333",
69
80
  width: 1,
70
- zoomTextBorder: 2,
71
- initialRadius: 4,
72
- radiusCoefficient: 5,
73
- radiusFactor: 1,
74
- flexibleRadius: true,
81
+ radius: 4,
82
+ textStyle: "normal",
83
+ textWeight: "500",
84
+ textGap: 1,
75
85
  };
76
86
  const LINK_SETTINGS = {
77
87
  alpha: 1,
78
- colorFar: "#999",
79
- colorNear: "#000000FF",
80
- widthFar: 1,
81
- widthNear: 0.1,
82
- zoomWidthBorder: 1,
83
- zoomColorBorder: 1,
88
+ };
89
+ const COMMON_SETTINGS = {
90
+ linkColorZoomFar: "#999",
91
+ linkColorZoomNear: "#000000FF",
92
+ linkWidthZoomFar: 1,
93
+ linkWidthZoomNear: 0.1,
94
+ linkWidthZoomBorder: 1,
95
+ linkColorZoomBorder: 1,
96
+ nodeTextScaleMin: 1.5,
97
+ nodeTextScaleMax: 20,
98
+ nodeTextSizeMin: 1.5,
99
+ nodeTextSizeMax: 3.5,
100
+ nodeTextShiftYMin: 2.5,
101
+ nodeTextShiftYMax: 4,
102
+ nodeTextChangeStepCount: 200,
84
103
  };
85
104
 
86
- function linkIterationExtractor(link, i, links, transform, option, optionConstantGetter) {
87
- let customOptions;
88
- let constantOptions;
89
- if (typeof option === "function")
90
- customOptions = option(link, i, links, transform);
91
- else
92
- customOptions = option;
93
- if (optionConstantGetter) {
94
- if (typeof optionConstantGetter === "function")
95
- constantOptions = optionConstantGetter(link, i, links, transform);
96
- else
97
- constantOptions = optionConstantGetter;
98
- if (constantOptions &&
99
- typeof constantOptions === "object" &&
100
- !Array.isArray(constantOptions) &&
101
- checkType(customOptions, customOptions === undefined ||
102
- (typeof customOptions === "object" && !Array.isArray(constantOptions)))) {
103
- return {
104
- ...constantOptions,
105
- ...(customOptions || {}),
106
- };
107
- }
108
- }
109
- return customOptions;
105
+ function forceSettingsGetter(settings, prevSettings) {
106
+ return {
107
+ ...(prevSettings ?? FORCE_SETTINGS),
108
+ ...settings,
109
+ };
110
110
  }
111
111
 
112
- function linkOptionsGetter(_, __, ___, transform) {
112
+ function graphSettingsGetter(settings, prevSettings) {
113
113
  return {
114
- ...LINK_SETTINGS,
115
- color: transform && transform.k > LINK_SETTINGS.zoomColorBorder
116
- ? LINK_SETTINGS.colorNear
117
- : LINK_SETTINGS.colorFar,
118
- width: transform && transform.k > LINK_SETTINGS.zoomWidthBorder
119
- ? LINK_SETTINGS.widthNear
120
- : LINK_SETTINGS.widthFar,
114
+ ...(prevSettings ?? GRAPH_SETTINGS),
115
+ ...settings,
121
116
  };
122
117
  }
123
118
 
124
119
  function linkSettingsGetter(settings) {
125
120
  return { options: settings?.options };
126
121
  }
127
-
128
- function forceSettingsGetter(settings) {
122
+ function linkOptionsGetter(_, __, ___, transform) {
129
123
  return {
130
- centerPosition: settings?.centerPosition ?? FORCE_SETTINGS.centerPosition,
131
- centerStrength: settings?.centerStrength ?? FORCE_SETTINGS.centerStrength,
132
- collideRadius: settings?.collideRadius ?? null,
133
- collideStrength: settings?.collideStrength ?? FORCE_SETTINGS.collideStrength,
134
- collideIterations: settings?.collideIterations ?? FORCE_SETTINGS.collideIterations,
135
- collideOffMax: settings?.collideOffMax ?? FORCE_SETTINGS.collideOffMax,
136
- collideOn: settings?.collideOn ?? FORCE_SETTINGS.collideOn,
137
- chargeStrength: settings?.chargeStrength ?? FORCE_SETTINGS.chargeStrength,
138
- chargeDistanceMax: settings?.chargeDistanceMax ?? FORCE_SETTINGS.chargeDistanceMax,
139
- chargeDistanceMin: settings?.chargeDistanceMin ?? FORCE_SETTINGS.chargeDistanceMin,
140
- xForce: settings?.xForce ?? FORCE_SETTINGS.xForce,
141
- xStrength: settings?.xStrength ?? FORCE_SETTINGS.xStrength,
142
- yForce: settings?.yForce ?? FORCE_SETTINGS.yForce,
143
- yStrength: settings?.yStrength ?? FORCE_SETTINGS.yStrength,
144
- linkDistance: settings?.linkDistance ?? FORCE_SETTINGS.linkDistance,
145
- linkIterations: settings?.linkIterations ?? FORCE_SETTINGS.linkIterations,
146
- linkStrength: settings?.linkStrength ?? FORCE_SETTINGS.linkStrength,
124
+ ...LINK_SETTINGS,
125
+ color: transform && transform.k > COMMON_SETTINGS.linkColorZoomBorder
126
+ ? COMMON_SETTINGS.linkColorZoomNear
127
+ : COMMON_SETTINGS.linkColorZoomFar,
128
+ width: transform && transform.k > COMMON_SETTINGS.linkWidthZoomBorder
129
+ ? COMMON_SETTINGS.linkWidthZoomNear
130
+ : COMMON_SETTINGS.linkWidthZoomFar,
147
131
  };
148
132
  }
149
133
 
@@ -151,6 +135,59 @@ function listenersGetter(settings) {
151
135
  return settings || {};
152
136
  }
153
137
 
138
+ function nodeSettingsGetter(settings) {
139
+ return {
140
+ idGetter: settings?.idGetter ?? nodeIdGetter,
141
+ options: settings?.options,
142
+ };
143
+ }
144
+ const color = colorGetter();
145
+ function nodeOptionsGetter(node, _, __, transform) {
146
+ const { textShiftY, textSize } = nodeTextSizeGetter(transform);
147
+ return {
148
+ ...NODE_SETTINGS,
149
+ color: color(String(node.group || "_DEFAULT")),
150
+ textVisible: Boolean(transform && transform.k > COMMON_SETTINGS.nodeTextScaleMin),
151
+ text: node.id != undefined ? String(node.id) : null,
152
+ textShiftY,
153
+ textSize,
154
+ };
155
+ }
156
+ function nodeTextSizeGetter(transform) {
157
+ let textSize = COMMON_SETTINGS.nodeTextSizeMax;
158
+ let textShiftY = COMMON_SETTINGS.nodeTextShiftYMax;
159
+ if (transform) {
160
+ const scaleStepCoefficient = (COMMON_SETTINGS.nodeTextScaleMax - COMMON_SETTINGS.nodeTextScaleMin) /
161
+ COMMON_SETTINGS.nodeTextChangeStepCount;
162
+ const textStepCoefficient = (COMMON_SETTINGS.nodeTextSizeMax - COMMON_SETTINGS.nodeTextSizeMin) /
163
+ COMMON_SETTINGS.nodeTextChangeStepCount;
164
+ const shiftStepCoefficient = (COMMON_SETTINGS.nodeTextShiftYMax - COMMON_SETTINGS.nodeTextShiftYMin) /
165
+ COMMON_SETTINGS.nodeTextChangeStepCount;
166
+ if (transform.k >= COMMON_SETTINGS.nodeTextScaleMax) {
167
+ textSize = COMMON_SETTINGS.nodeTextSizeMin;
168
+ textShiftY = COMMON_SETTINGS.nodeTextShiftYMin;
169
+ }
170
+ else if (transform.k > COMMON_SETTINGS.nodeTextScaleMin) {
171
+ const transformSteps = (transform.k - COMMON_SETTINGS.nodeTextScaleMin) / scaleStepCoefficient;
172
+ textSize -= transformSteps * textStepCoefficient;
173
+ textShiftY -= transformSteps * shiftStepCoefficient;
174
+ }
175
+ }
176
+ return { textSize, textShiftY };
177
+ }
178
+ function nodeRadiusGetter({ radiusFlexible, radiusInitial, linkCount, radiusCoefficient, radiusFactor, }) {
179
+ return ((radiusFlexible && linkCount ? linkCount / radiusCoefficient : 0) * radiusFactor + radiusInitial);
180
+ }
181
+ function nodeIdGetter(d) {
182
+ return d.id;
183
+ }
184
+
185
+ function pointerGetter(mouseEvent, areaRect, areaTransform) {
186
+ const px = (mouseEvent.clientX - areaRect.left - areaTransform.x) / areaTransform.k;
187
+ const py = (mouseEvent.clientY - areaRect.top - areaTransform.y) / areaTransform.k;
188
+ return [px, py];
189
+ }
190
+
154
191
  function isOverlapsNode(node, radius, pointerX, pointerY) {
155
192
  if (node.x == undefined || node.y == undefined)
156
193
  return false;
@@ -167,32 +204,16 @@ function dragPlaceCoefficientGetter(node, pointerX, pointerY, radius) {
167
204
  return undefined;
168
205
  }
169
206
 
170
- function graphSettingsGetter(settings) {
171
- return {
172
- zoomExtent: settings?.zoomExtent || GRAPH_SETTINGS.zoomExtent,
173
- dragPlaceCoefficient: settings?.dragPlaceCoefficient || dragPlaceCoefficientGetter,
174
- stickAfterDrag: settings?.stickAfterDrag || GRAPH_SETTINGS.stickAfterDrag,
175
- highlightByHover: settings?.highlightByHover || GRAPH_SETTINGS.highlightByHover,
176
- minFading: settings?.minFading || GRAPH_SETTINGS.minFading,
177
- };
178
- }
179
-
180
- function pointerGetter(mouseEvent, areaRect, areaTransform) {
181
- const px = (mouseEvent.clientX - areaRect.left - areaTransform.x) / areaTransform.k;
182
- const py = (mouseEvent.clientY - areaRect.top - areaTransform.y) / areaTransform.k;
183
- return [px, py];
184
- }
185
-
186
- function nodeIterationExtractor(node, i, nodes, transform, option, optionConstantGetter) {
207
+ function linkIterationExtractor(link, i, links, transform, option, optionConstantGetter) {
187
208
  let customOptions;
188
209
  let constantOptions;
189
210
  if (typeof option === "function")
190
- customOptions = option(node, i, nodes, transform);
211
+ customOptions = option(link, i, links, transform);
191
212
  else
192
213
  customOptions = option;
193
214
  if (optionConstantGetter) {
194
215
  if (typeof optionConstantGetter === "function")
195
- constantOptions = optionConstantGetter(node, i, nodes, transform);
216
+ constantOptions = optionConstantGetter(link, i, links, transform);
196
217
  else
197
218
  constantOptions = optionConstantGetter;
198
219
  if (constantOptions &&
@@ -202,7 +223,6 @@ function nodeIterationExtractor(node, i, nodes, transform, option, optionConstan
202
223
  (typeof customOptions === "object" && !Array.isArray(constantOptions)))) {
203
224
  return {
204
225
  ...constantOptions,
205
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
206
226
  ...(customOptions || {}),
207
227
  };
208
228
  }
@@ -210,47 +230,92 @@ function nodeIterationExtractor(node, i, nodes, transform, option, optionConstan
210
230
  return customOptions;
211
231
  }
212
232
 
213
- const color = colorGetter();
214
- function nodeOptionsGetter(node, _, __, transform) {
215
- return {
216
- ...NODE_SETTINGS,
217
- colorInner: color(String(node.group || "_DEFAULT")),
218
- text: transform && node.id != undefined && transform.k > NODE_SETTINGS.zoomTextBorder
219
- ? String(node.id)
220
- : null,
221
- };
222
- }
223
-
224
- function nodeRadiusGetter({ flexibleRadius, initialRadius, linkCount, radiusCoefficient, radiusFactor, }) {
225
- return ((flexibleRadius && linkCount ? linkCount / radiusCoefficient : 0) * radiusFactor + initialRadius);
226
- }
227
-
228
- function nodeByPointerGetter({ areaRect, areaTransform, mouseEvent, nodes, nodeCustomOptions, }) {
233
+ function nodeByPointerGetter({ areaRect, areaTransform, mouseEvent, nodes, graphSettings, }) {
229
234
  if (!areaRect)
230
235
  return undefined;
231
236
  const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
232
- return nodes.find((node, index) => {
233
- const nodeOptions = nodeIterationExtractor(node, index, nodes, areaTransform, nodeCustomOptions || {}, nodeOptionsGetter);
237
+ return nodes.find((node) => {
234
238
  const radius = nodeRadiusGetter({
235
- flexibleRadius: nodeOptions.flexibleRadius,
236
- initialRadius: nodeOptions.initialRadius,
237
- radiusCoefficient: nodeOptions.radiusCoefficient,
238
- radiusFactor: nodeOptions.radiusFactor,
239
+ radiusFlexible: graphSettings.nodeRadiusFlexible,
240
+ radiusInitial: graphSettings.nodeRadiusInitial,
241
+ radiusCoefficient: graphSettings.nodeRadiusCoefficient,
242
+ radiusFactor: graphSettings.nodeRadiusFactor,
239
243
  linkCount: node.linkCount,
240
244
  });
241
245
  return isOverlapsNode(node, radius, pointerX, pointerY);
242
246
  });
243
247
  }
244
248
 
245
- function nodeIdGetter(d) {
246
- return d.id;
249
+ function nodeIterationExtractor(node, i, nodes, transform, option, optionConstantGetter) {
250
+ let customOptions;
251
+ let constantOptions;
252
+ if (typeof option === "function")
253
+ customOptions = option(node, i, nodes, transform);
254
+ else
255
+ customOptions = option;
256
+ if (optionConstantGetter) {
257
+ if (typeof optionConstantGetter === "function")
258
+ constantOptions = optionConstantGetter(node, i, nodes, transform);
259
+ else
260
+ constantOptions = optionConstantGetter;
261
+ if (constantOptions &&
262
+ typeof constantOptions === "object" &&
263
+ !Array.isArray(constantOptions) &&
264
+ checkType(customOptions, customOptions === undefined ||
265
+ (typeof customOptions === "object" && !Array.isArray(constantOptions)))) {
266
+ return {
267
+ ...constantOptions,
268
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
269
+ ...(customOptions || {}),
270
+ };
271
+ }
272
+ }
273
+ return customOptions;
247
274
  }
248
275
 
249
- function nodeSettingsGetter(settings) {
250
- return {
251
- idGetter: settings?.idGetter ?? nodeIdGetter,
252
- options: settings?.options,
253
- };
276
+ const SPACE = " ";
277
+ function drawText({ context, id, textAlign, textColor, textFont, textStyle, textGap, textWeight, textSize, text, x, y, cachedNodeText, maxWidth, }) {
278
+ context.font = `${textStyle} normal ${textWeight} ${textSize}px ${textFont}`;
279
+ context.fillStyle = textColor;
280
+ context.textAlign = textAlign;
281
+ if (cachedNodeText[id] != undefined) {
282
+ cachedNodeText[id].forEach((line, index) => {
283
+ context.fillText(line, x, y + index * textSize + index * textGap);
284
+ });
285
+ return;
286
+ }
287
+ if (maxWidth == undefined || context.measureText(text).width <= maxWidth) {
288
+ cachedNodeText[id] = [text];
289
+ context.fillText(text, x, y);
290
+ return;
291
+ }
292
+ const spaceWidth = context.measureText(" ").width;
293
+ const lines = [];
294
+ let lineWidth = 0;
295
+ let line = "";
296
+ for (const word of text.split(" ")) {
297
+ const size = context.measureText(word).width;
298
+ lineWidth += size;
299
+ if (line === "") {
300
+ line = word;
301
+ continue;
302
+ }
303
+ if (lineWidth > maxWidth) {
304
+ lineWidth = 0;
305
+ lines.push(line);
306
+ line = word;
307
+ }
308
+ else {
309
+ lineWidth += spaceWidth;
310
+ line += `${SPACE}${word}`;
311
+ }
312
+ }
313
+ if (line !== "")
314
+ lines.push(line);
315
+ cachedNodeText[id] = lines;
316
+ lines.forEach((line, index) => {
317
+ context.fillText(line, x, y + index * textSize + index * textGap);
318
+ });
254
319
  }
255
320
 
256
321
  class GraphCanvas {
@@ -275,12 +340,15 @@ class GraphCanvas {
275
340
  areaTransform = zoomIdentity;
276
341
  areaRect;
277
342
  draw;
343
+ eventAbortController;
344
+ cachedNodeText = {};
278
345
  simulationWorking = false;
279
346
  isDragging = false;
280
- eventAbortController;
281
347
  highlightedNode = null;
282
348
  highlightedNeighbors = null;
283
- fading = 0;
349
+ highlighFading = 1;
350
+ highlightFadingWorking = false;
351
+ highlightDrawing = false;
284
352
  constructor({ links, nodes, root, forceSettings, linkSettings, listeners, nodeSettings, graphSettings, }) {
285
353
  root.style.position = "relative";
286
354
  root.style.overflow = "hidden";
@@ -309,20 +377,33 @@ class GraphCanvas {
309
377
  this.links = options.links;
310
378
  if (options.nodes != undefined)
311
379
  this.nodes = options.nodes;
312
- this.updateData();
380
+ if (options.nodes != undefined || options.links != undefined)
381
+ this.updateData();
313
382
  }
314
383
  changeSettings(options) {
384
+ if (options.graphSettings)
385
+ this.graphSettings = graphSettingsGetter(options.graphSettings, this.graphSettings);
315
386
  if (options.forceSettings)
316
- this.forceSettings = forceSettingsGetter(options.forceSettings);
387
+ this.forceSettings = forceSettingsGetter(options.forceSettings, this.forceSettings);
317
388
  if (options.linkSettings)
318
389
  this.linkSettings = linkSettingsGetter(options.linkSettings);
319
390
  if (options.nodeSettings)
320
391
  this.nodeSettings = nodeSettingsGetter(options.nodeSettings);
321
- if (options.graphSettings)
322
- this.graphSettings = graphSettingsGetter(options.graphSettings);
323
392
  this.updateSettings();
324
393
  }
325
394
  start() {
395
+ if (this.simulation)
396
+ this.simulation.alpha(1).restart();
397
+ if (this.container)
398
+ this.container.style.display = "block";
399
+ }
400
+ stop() {
401
+ if (this.simulation)
402
+ this.simulation.stop();
403
+ if (this.container)
404
+ this.container.style.display = "none";
405
+ }
406
+ create() {
326
407
  this.init();
327
408
  }
328
409
  destroy() {
@@ -336,26 +417,40 @@ class GraphCanvas {
336
417
  this.container = undefined;
337
418
  this.eventAbortController.abort();
338
419
  this.eventAbortController = new AbortController();
420
+ this.clearState();
421
+ this.clearDataDependencies();
339
422
  }
340
423
  updateSettings() {
341
424
  if (this.simulation) {
342
- this.simulation.stop().alpha(1);
343
425
  this.initSimulationForces();
426
+ this.simulation.alpha(1);
344
427
  this.simulation.restart();
345
428
  }
346
429
  }
430
+ clearState() {
431
+ this.isDragging = false;
432
+ this.simulationWorking = false;
433
+ this.highlightedNode = null;
434
+ this.highlightedNeighbors = null;
435
+ this.highlighFading = 1;
436
+ this.highlightFadingWorking = false;
437
+ this.highlightDrawing = false;
438
+ }
439
+ clearDataDependencies() {
440
+ this.cachedNodeText = {};
441
+ }
347
442
  updateData() {
443
+ this.clearDataDependencies();
348
444
  if (this.simulation) {
349
445
  this.initCollideForce();
350
446
  this.simulation
351
- .stop()
352
- .alpha(1)
353
447
  .nodes(this.nodes)
354
448
  .force("link", forceLink(this.links)
355
449
  .id(this.nodeSettings.idGetter)
356
450
  .distance(this.forceSettings.linkDistance)
357
451
  .strength(this.forceSettings.linkStrength)
358
452
  .iterations(this.forceSettings.linkIterations))
453
+ .alpha(0.5)
359
454
  .restart();
360
455
  }
361
456
  }
@@ -368,11 +463,7 @@ class GraphCanvas {
368
463
  this.area.width = this.dpi * width;
369
464
  this.area.height = this.dpi * height;
370
465
  this.areaRect = this.area.getBoundingClientRect();
371
- this.simulation
372
- .stop()
373
- .alpha(1)
374
- .force("center", forceCenter(this.forceSettings.centerPosition.x || this.width / 2, this.forceSettings.centerPosition.y || this.height / 2).strength(this.forceSettings.centerStrength))
375
- .restart();
466
+ this.simulation.alpha(0.1).restart();
376
467
  if (!this.simulationWorking)
377
468
  this.draw();
378
469
  }
@@ -415,7 +506,7 @@ class GraphCanvas {
415
506
  .strength(this.forceSettings.chargeStrength)
416
507
  .distanceMax(this.forceSettings.chargeDistanceMax)
417
508
  .distanceMin(this.forceSettings.chargeDistanceMin))
418
- .force("center", forceCenter(this.forceSettings.centerPosition.x || this.width / 2, this.forceSettings.centerPosition.y || this.height / 2).strength(this.forceSettings.centerStrength));
509
+ .force("center", forceCenter(this.forceSettings.centerPosition.x || 0, this.forceSettings.centerPosition.y || 0).strength(this.forceSettings.centerStrength));
419
510
  this.initCollideForce();
420
511
  }
421
512
  initCollideForce() {
@@ -439,13 +530,13 @@ class GraphCanvas {
439
530
  }
440
531
  const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.areaTransform, this.nodeSettings.options || {}, nodeOptionsGetter);
441
532
  const radius = nodeRadiusGetter({
442
- flexibleRadius: nodeOptions.flexibleRadius,
443
- initialRadius: nodeOptions.initialRadius,
444
- radiusCoefficient: nodeOptions.radiusCoefficient,
445
- radiusFactor: nodeOptions.radiusFactor,
533
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
534
+ radiusInitial: this.graphSettings.nodeRadiusInitial,
535
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
536
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
446
537
  linkCount: node.linkCount,
447
- });
448
- return radius + FORCE_SETTINGS.collideAdditionalRadius;
538
+ }) ?? nodeOptions.radius;
539
+ return radius + this.forceSettings.collideAdditionalRadius;
449
540
  })
450
541
  .strength(this.forceSettings.collideStrength)
451
542
  .iterations(this.forceSettings.collideIterations));
@@ -477,16 +568,43 @@ class GraphCanvas {
477
568
  }
478
569
  }
479
570
  initDraw() {
571
+ function calculateHighlightFading() {
572
+ this.highlightDrawing = true;
573
+ if (!this.highlightFadingWorking) {
574
+ switch (true) {
575
+ case this.highlighFading < 1: {
576
+ this.highlighFading += this.graphSettings.highlightUpStep;
577
+ break;
578
+ }
579
+ default: {
580
+ if (this.highlightedNeighbors)
581
+ this.highlightedNeighbors = null;
582
+ if (this.highlightedNode)
583
+ this.highlightedNode = null;
584
+ break;
585
+ }
586
+ }
587
+ if (this.highlighFading < 1 && !this.simulationWorking)
588
+ return void requestAnimationFrame(() => this.draw());
589
+ }
590
+ if (this.highlightFadingWorking) {
591
+ switch (true) {
592
+ case this.highlighFading > this.graphSettings.highlightFadingMin: {
593
+ this.highlighFading -= this.graphSettings.highlightDownStep;
594
+ break;
595
+ }
596
+ }
597
+ if (this.highlighFading > this.graphSettings.highlightFadingMin && !this.simulationWorking)
598
+ return void requestAnimationFrame(() => this.draw());
599
+ }
600
+ this.highlightDrawing = false;
601
+ }
480
602
  function draw() {
481
603
  if (!this.context)
482
604
  return;
483
605
  if (this.listeners.onDraw) {
484
606
  return void this.listeners.onDraw(this.context, this.areaTransform);
485
607
  }
486
- if (this.highlightedNeighbors && this.highlightedNode) {
487
- if (this.fading === 0)
488
- this.fading = 1;
489
- }
490
608
  this.context.save();
491
609
  this.context.clearRect(0, 0, this.width, this.height);
492
610
  this.context.translate(this.areaTransform.x, this.areaTransform.y);
@@ -499,12 +617,7 @@ class GraphCanvas {
499
617
  this.nodes.forEach(drawNode.bind(this));
500
618
  this.context.restore();
501
619
  this.listeners.onDrawFinished?.(this.context, this.areaTransform);
502
- if (this.fading > this.graphSettings.minFading) {
503
- this.fading -= 0.1;
504
- requestAnimationFrame(() => {
505
- this.draw();
506
- });
507
- }
620
+ calculateHighlightFading.bind(this)();
508
621
  }
509
622
  function drawLink(link, index) {
510
623
  if (!this.context ||
@@ -520,7 +633,7 @@ class GraphCanvas {
520
633
  if (this.highlightedNeighbors && this.highlightedNode) {
521
634
  if (this.highlightedNode.id != link.source.id &&
522
635
  this.highlightedNode.id != link.target.id) {
523
- alpha = this.fading;
636
+ alpha = this.highlighFading;
524
637
  }
525
638
  this.context.beginPath();
526
639
  }
@@ -537,33 +650,44 @@ class GraphCanvas {
537
650
  return;
538
651
  const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.areaTransform, this.nodeSettings.options || {}, nodeOptionsGetter);
539
652
  const radius = nodeRadiusGetter({
540
- flexibleRadius: nodeOptions.flexibleRadius,
541
- initialRadius: nodeOptions.initialRadius,
542
- radiusCoefficient: nodeOptions.radiusCoefficient,
543
- radiusFactor: nodeOptions.radiusFactor,
653
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
654
+ radiusInitial: this.graphSettings.nodeRadiusInitial,
655
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
656
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
544
657
  linkCount: node.linkCount,
545
- });
658
+ }) ?? nodeOptions.radius;
546
659
  let alpha = nodeOptions.alpha;
547
660
  if (this.highlightedNeighbors && this.highlightedNode) {
548
661
  if (!this.highlightedNeighbors.has(node.id) && this.highlightedNode.id != node.id) {
549
- alpha = this.fading;
662
+ alpha = this.highlighFading;
550
663
  }
551
664
  }
552
665
  this.context.beginPath();
553
666
  this.context.globalAlpha = alpha;
554
667
  /** text */
555
- if (nodeOptions.text) {
556
- this.context.font = nodeOptions.font;
557
- this.context.fillStyle = nodeOptions.fontColor;
558
- this.context.textAlign = nodeOptions.fontAlign;
559
- this.context.fillText(nodeOptions.text, node.x, node.y + radius + 10);
668
+ if (nodeOptions.textVisible && nodeOptions.text) {
669
+ drawText({
670
+ id: node.id,
671
+ cachedNodeText: this.cachedNodeText,
672
+ context: this.context,
673
+ text: nodeOptions.text,
674
+ textAlign: nodeOptions.textAlign,
675
+ textColor: nodeOptions.textColor,
676
+ textFont: nodeOptions.textFont,
677
+ textSize: nodeOptions.textSize,
678
+ x: node.x + nodeOptions.textShiftX,
679
+ y: node.y + radius + nodeOptions.textShiftY,
680
+ maxWidth: nodeOptions.textWidth,
681
+ textStyle: nodeOptions.textStyle,
682
+ textWeight: nodeOptions.textWeight,
683
+ textGap: nodeOptions.textGap,
684
+ });
560
685
  }
561
686
  /** circle */
562
- this.context.strokeStyle = nodeOptions.colorOuter;
563
- this.context.lineWidth = nodeOptions.width;
564
- this.context.moveTo(node.x + radius, node.y);
687
+ this.context.lineWidth = nodeOptions.borderWidth;
688
+ this.context.strokeStyle = nodeOptions.borderColor;
689
+ this.context.fillStyle = nodeOptions.color;
565
690
  this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
566
- this.context.fillStyle = nodeOptions.colorInner;
567
691
  this.context.fill();
568
692
  this.context.stroke();
569
693
  }
@@ -586,9 +710,9 @@ class GraphCanvas {
586
710
  throw new Error("bad init data");
587
711
  this.area.addEventListener("pointermove", (event) => {
588
712
  let currentNode;
589
- if (this.graphSettings.highlightByHover) {
713
+ if (this.graphSettings.highlightByHover && !this.isDragging) {
590
714
  currentNode = nodeByPointerGetter({
591
- nodeCustomOptions: this.nodeSettings.options,
715
+ graphSettings: this.graphSettings,
592
716
  areaRect: this.areaRect,
593
717
  areaTransform: this.areaTransform,
594
718
  mouseEvent: event,
@@ -597,23 +721,25 @@ class GraphCanvas {
597
721
  if (currentNode && currentNode.neighbors && this.highlightedNode !== currentNode) {
598
722
  this.highlightedNode = currentNode;
599
723
  this.highlightedNeighbors = new Set(this.highlightedNode.neighbors);
600
- requestAnimationFrame(() => {
601
- this.draw();
602
- });
724
+ this.highlightFadingWorking = true;
725
+ if (!this.simulationWorking && !this.highlightDrawing)
726
+ requestAnimationFrame(() => {
727
+ this.draw();
728
+ });
603
729
  }
604
730
  else if (!currentNode && this.highlightedNode) {
605
- this.highlightedNode = null;
606
- this.highlightedNeighbors = null;
607
- requestAnimationFrame(() => {
608
- this.draw();
609
- });
731
+ this.highlightFadingWorking = false;
732
+ if (!this.simulationWorking && !this.highlightDrawing)
733
+ requestAnimationFrame(() => {
734
+ this.draw();
735
+ });
610
736
  }
611
737
  }
612
738
  if (!this.listeners.onMove)
613
739
  return;
614
740
  if (!currentNode)
615
741
  currentNode = nodeByPointerGetter({
616
- nodeCustomOptions: this.nodeSettings.options,
742
+ graphSettings: this.graphSettings,
617
743
  areaRect: this.areaRect,
618
744
  areaTransform: this.areaTransform,
619
745
  mouseEvent: event,
@@ -627,7 +753,7 @@ class GraphCanvas {
627
753
  if (!this.listeners.onDoubleClick)
628
754
  return;
629
755
  const currentNode = nodeByPointerGetter({
630
- nodeCustomOptions: this.nodeSettings.options,
756
+ graphSettings: this.graphSettings,
631
757
  areaRect: this.areaRect,
632
758
  areaTransform: this.areaTransform,
633
759
  mouseEvent: event,
@@ -642,7 +768,7 @@ class GraphCanvas {
642
768
  if (!this.listeners.onClick)
643
769
  return;
644
770
  const currentNode = nodeByPointerGetter({
645
- nodeCustomOptions: this.nodeSettings.options,
771
+ graphSettings: this.graphSettings,
646
772
  areaRect: this.areaRect,
647
773
  areaTransform: this.areaTransform,
648
774
  mouseEvent: event,
@@ -654,7 +780,7 @@ class GraphCanvas {
654
780
  if (!this.listeners.onWheelClick)
655
781
  return;
656
782
  const currentNode = nodeByPointerGetter({
657
- nodeCustomOptions: this.nodeSettings.options,
783
+ graphSettings: this.graphSettings,
658
784
  areaRect: this.areaRect,
659
785
  areaTransform: this.areaTransform,
660
786
  mouseEvent: event,
@@ -669,7 +795,7 @@ class GraphCanvas {
669
795
  if (!this.listeners.onContextMenu)
670
796
  return;
671
797
  const currentNode = nodeByPointerGetter({
672
- nodeCustomOptions: this.nodeSettings.options,
798
+ graphSettings: this.graphSettings,
673
799
  areaRect: this.areaRect,
674
800
  areaTransform: this.areaTransform,
675
801
  mouseEvent: event,
@@ -698,12 +824,12 @@ class GraphCanvas {
698
824
  return undefined;
699
825
  const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.areaTransform, this.nodeSettings.options || {}, nodeOptionsGetter);
700
826
  const radius = nodeRadiusGetter({
701
- flexibleRadius: nodeOptions.flexibleRadius,
702
- initialRadius: nodeOptions.initialRadius,
703
- radiusCoefficient: nodeOptions.radiusCoefficient,
704
- radiusFactor: nodeOptions.radiusFactor,
827
+ radiusFlexible: this.graphSettings.nodeRadiusFlexible,
828
+ radiusInitial: this.graphSettings.nodeRadiusInitial,
829
+ radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
830
+ radiusFactor: this.graphSettings.nodeRadiusFactor,
705
831
  linkCount: node.linkCount,
706
- });
832
+ }) ?? nodeOptions.radius;
707
833
  index++;
708
834
  return this.graphSettings.dragPlaceCoefficient(node, pointerX, pointerY, radius);
709
835
  });
@@ -757,6 +883,8 @@ class GraphCanvas {
757
883
  requestAnimationFrame(() => this.draw());
758
884
  }))
759
885
  .on("dblclick.zoom", null);
886
+ this.areaTransform = new ZoomTransform(1, this.width / 2, this.height / 2);
887
+ zoom().transform(select(this.area), this.areaTransform);
760
888
  }
761
889
  }
762
890