@krainovsd/graph 0.1.2 → 0.3.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 +338 -198
- package/lib/esm/index.js.map +1 -1
- package/lib/index.d.ts +181 -0
- package/package.json +24 -18
- package/lib/esm/index.d.ts +0 -157
package/lib/esm/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { greatest } from 'd3-array';
|
|
2
2
|
import { drag } from 'd3-drag';
|
|
3
|
-
import { forceLink,
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
collideIterations:
|
|
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:
|
|
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,
|
|
59
|
+
zoomExtent: [0.1, 20],
|
|
59
60
|
stickAfterDrag: false,
|
|
60
61
|
highlightByHover: false,
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
112
|
+
function graphSettingsGetter(settings, prevSettings) {
|
|
113
113
|
return {
|
|
114
|
-
...
|
|
115
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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;
|
|
@@ -163,36 +200,19 @@ function dragPlaceCoefficientGetter(node, pointerX, pointerY, radius) {
|
|
|
163
200
|
if (!node.x || !node.y)
|
|
164
201
|
return undefined;
|
|
165
202
|
if (isOverlapsNode(node, radius, pointerX, pointerY))
|
|
166
|
-
return
|
|
167
|
-
return undefined;
|
|
203
|
+
return node.index;
|
|
168
204
|
}
|
|
169
205
|
|
|
170
|
-
function
|
|
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) {
|
|
206
|
+
function linkIterationExtractor(link, i, links, transform, option, optionConstantGetter) {
|
|
187
207
|
let customOptions;
|
|
188
208
|
let constantOptions;
|
|
189
209
|
if (typeof option === "function")
|
|
190
|
-
customOptions = option(
|
|
210
|
+
customOptions = option(link, i, links, transform);
|
|
191
211
|
else
|
|
192
212
|
customOptions = option;
|
|
193
213
|
if (optionConstantGetter) {
|
|
194
214
|
if (typeof optionConstantGetter === "function")
|
|
195
|
-
constantOptions = optionConstantGetter(
|
|
215
|
+
constantOptions = optionConstantGetter(link, i, links, transform);
|
|
196
216
|
else
|
|
197
217
|
constantOptions = optionConstantGetter;
|
|
198
218
|
if (constantOptions &&
|
|
@@ -202,7 +222,6 @@ function nodeIterationExtractor(node, i, nodes, transform, option, optionConstan
|
|
|
202
222
|
(typeof customOptions === "object" && !Array.isArray(constantOptions)))) {
|
|
203
223
|
return {
|
|
204
224
|
...constantOptions,
|
|
205
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
206
225
|
...(customOptions || {}),
|
|
207
226
|
};
|
|
208
227
|
}
|
|
@@ -210,47 +229,93 @@ function nodeIterationExtractor(node, i, nodes, transform, option, optionConstan
|
|
|
210
229
|
return customOptions;
|
|
211
230
|
}
|
|
212
231
|
|
|
213
|
-
|
|
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, }) {
|
|
232
|
+
function nodeByPointerGetter({ areaRect, areaTransform, mouseEvent, nodes, graphSettings, }) {
|
|
229
233
|
if (!areaRect)
|
|
230
234
|
return undefined;
|
|
231
235
|
const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
|
|
232
|
-
return nodes
|
|
233
|
-
const nodeOptions = nodeIterationExtractor(node, index, nodes, areaTransform, nodeCustomOptions || {}, nodeOptionsGetter);
|
|
236
|
+
return greatest(nodes, (node) => {
|
|
234
237
|
const radius = nodeRadiusGetter({
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
radiusCoefficient:
|
|
238
|
-
radiusFactor:
|
|
238
|
+
radiusFlexible: graphSettings.nodeRadiusFlexible,
|
|
239
|
+
radiusInitial: graphSettings.nodeRadiusInitial,
|
|
240
|
+
radiusCoefficient: graphSettings.nodeRadiusCoefficient,
|
|
241
|
+
radiusFactor: graphSettings.nodeRadiusFactor,
|
|
239
242
|
linkCount: node.linkCount,
|
|
240
243
|
});
|
|
241
|
-
|
|
244
|
+
if (isOverlapsNode(node, radius, pointerX, pointerY))
|
|
245
|
+
return node.index;
|
|
242
246
|
});
|
|
243
247
|
}
|
|
244
248
|
|
|
245
|
-
function
|
|
246
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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,37 @@ class GraphCanvas {
|
|
|
309
377
|
this.links = options.links;
|
|
310
378
|
if (options.nodes != undefined)
|
|
311
379
|
this.nodes = options.nodes;
|
|
312
|
-
|
|
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
|
}
|
|
394
|
+
tick() {
|
|
395
|
+
if (!this.simulationWorking)
|
|
396
|
+
this.draw();
|
|
397
|
+
}
|
|
325
398
|
start() {
|
|
399
|
+
if (this.simulation)
|
|
400
|
+
this.simulation.alpha(1).restart();
|
|
401
|
+
if (this.container)
|
|
402
|
+
this.container.style.display = "block";
|
|
403
|
+
}
|
|
404
|
+
stop() {
|
|
405
|
+
if (this.simulation)
|
|
406
|
+
this.simulation.stop();
|
|
407
|
+
if (this.container)
|
|
408
|
+
this.container.style.display = "none";
|
|
409
|
+
}
|
|
410
|
+
create() {
|
|
326
411
|
this.init();
|
|
327
412
|
}
|
|
328
413
|
destroy() {
|
|
@@ -336,26 +421,40 @@ class GraphCanvas {
|
|
|
336
421
|
this.container = undefined;
|
|
337
422
|
this.eventAbortController.abort();
|
|
338
423
|
this.eventAbortController = new AbortController();
|
|
424
|
+
this.clearState();
|
|
425
|
+
this.clearDataDependencies();
|
|
339
426
|
}
|
|
340
427
|
updateSettings() {
|
|
341
428
|
if (this.simulation) {
|
|
342
|
-
this.simulation.stop().alpha(1);
|
|
343
429
|
this.initSimulationForces();
|
|
430
|
+
this.simulation.alpha(1);
|
|
344
431
|
this.simulation.restart();
|
|
345
432
|
}
|
|
346
433
|
}
|
|
434
|
+
clearState() {
|
|
435
|
+
this.isDragging = false;
|
|
436
|
+
this.simulationWorking = false;
|
|
437
|
+
this.highlightedNode = null;
|
|
438
|
+
this.highlightedNeighbors = null;
|
|
439
|
+
this.highlighFading = 1;
|
|
440
|
+
this.highlightFadingWorking = false;
|
|
441
|
+
this.highlightDrawing = false;
|
|
442
|
+
}
|
|
443
|
+
clearDataDependencies() {
|
|
444
|
+
this.cachedNodeText = {};
|
|
445
|
+
}
|
|
347
446
|
updateData() {
|
|
447
|
+
this.clearDataDependencies();
|
|
348
448
|
if (this.simulation) {
|
|
349
449
|
this.initCollideForce();
|
|
350
450
|
this.simulation
|
|
351
|
-
.stop()
|
|
352
|
-
.alpha(1)
|
|
353
451
|
.nodes(this.nodes)
|
|
354
452
|
.force("link", forceLink(this.links)
|
|
355
453
|
.id(this.nodeSettings.idGetter)
|
|
356
454
|
.distance(this.forceSettings.linkDistance)
|
|
357
455
|
.strength(this.forceSettings.linkStrength)
|
|
358
456
|
.iterations(this.forceSettings.linkIterations))
|
|
457
|
+
.alpha(0.5)
|
|
359
458
|
.restart();
|
|
360
459
|
}
|
|
361
460
|
}
|
|
@@ -368,11 +467,7 @@ class GraphCanvas {
|
|
|
368
467
|
this.area.width = this.dpi * width;
|
|
369
468
|
this.area.height = this.dpi * height;
|
|
370
469
|
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();
|
|
470
|
+
this.simulation.alpha(0.1).restart();
|
|
376
471
|
if (!this.simulationWorking)
|
|
377
472
|
this.draw();
|
|
378
473
|
}
|
|
@@ -415,7 +510,7 @@ class GraphCanvas {
|
|
|
415
510
|
.strength(this.forceSettings.chargeStrength)
|
|
416
511
|
.distanceMax(this.forceSettings.chargeDistanceMax)
|
|
417
512
|
.distanceMin(this.forceSettings.chargeDistanceMin))
|
|
418
|
-
.force("center", forceCenter(this.forceSettings.centerPosition.x ||
|
|
513
|
+
.force("center", forceCenter(this.forceSettings.centerPosition.x || 0, this.forceSettings.centerPosition.y || 0).strength(this.forceSettings.centerStrength));
|
|
419
514
|
this.initCollideForce();
|
|
420
515
|
}
|
|
421
516
|
initCollideForce() {
|
|
@@ -439,13 +534,13 @@ class GraphCanvas {
|
|
|
439
534
|
}
|
|
440
535
|
const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.areaTransform, this.nodeSettings.options || {}, nodeOptionsGetter);
|
|
441
536
|
const radius = nodeRadiusGetter({
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
radiusCoefficient:
|
|
445
|
-
radiusFactor:
|
|
537
|
+
radiusFlexible: this.graphSettings.nodeRadiusFlexible,
|
|
538
|
+
radiusInitial: this.graphSettings.nodeRadiusInitial,
|
|
539
|
+
radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
|
|
540
|
+
radiusFactor: this.graphSettings.nodeRadiusFactor,
|
|
446
541
|
linkCount: node.linkCount,
|
|
447
|
-
});
|
|
448
|
-
return radius +
|
|
542
|
+
}) ?? nodeOptions.radius;
|
|
543
|
+
return radius + this.forceSettings.collideAdditionalRadius;
|
|
449
544
|
})
|
|
450
545
|
.strength(this.forceSettings.collideStrength)
|
|
451
546
|
.iterations(this.forceSettings.collideIterations));
|
|
@@ -477,16 +572,43 @@ class GraphCanvas {
|
|
|
477
572
|
}
|
|
478
573
|
}
|
|
479
574
|
initDraw() {
|
|
575
|
+
function calculateHighlightFading() {
|
|
576
|
+
this.highlightDrawing = true;
|
|
577
|
+
if (!this.highlightFadingWorking) {
|
|
578
|
+
switch (true) {
|
|
579
|
+
case this.highlighFading < 1: {
|
|
580
|
+
this.highlighFading += this.graphSettings.highlightUpStep;
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
default: {
|
|
584
|
+
if (this.highlightedNeighbors)
|
|
585
|
+
this.highlightedNeighbors = null;
|
|
586
|
+
if (this.highlightedNode)
|
|
587
|
+
this.highlightedNode = null;
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (this.highlighFading < 1 && !this.simulationWorking)
|
|
592
|
+
return void requestAnimationFrame(() => this.draw());
|
|
593
|
+
}
|
|
594
|
+
if (this.highlightFadingWorking) {
|
|
595
|
+
switch (true) {
|
|
596
|
+
case this.highlighFading > this.graphSettings.highlightFadingMin: {
|
|
597
|
+
this.highlighFading -= this.graphSettings.highlightDownStep;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (this.highlighFading > this.graphSettings.highlightFadingMin && !this.simulationWorking)
|
|
602
|
+
return void requestAnimationFrame(() => this.draw());
|
|
603
|
+
}
|
|
604
|
+
this.highlightDrawing = false;
|
|
605
|
+
}
|
|
480
606
|
function draw() {
|
|
481
607
|
if (!this.context)
|
|
482
608
|
return;
|
|
483
609
|
if (this.listeners.onDraw) {
|
|
484
610
|
return void this.listeners.onDraw(this.context, this.areaTransform);
|
|
485
611
|
}
|
|
486
|
-
if (this.highlightedNeighbors && this.highlightedNode) {
|
|
487
|
-
if (this.fading === 0)
|
|
488
|
-
this.fading = 1;
|
|
489
|
-
}
|
|
490
612
|
this.context.save();
|
|
491
613
|
this.context.clearRect(0, 0, this.width, this.height);
|
|
492
614
|
this.context.translate(this.areaTransform.x, this.areaTransform.y);
|
|
@@ -496,15 +618,12 @@ class GraphCanvas {
|
|
|
496
618
|
this.links.forEach(drawLink.bind(this));
|
|
497
619
|
this.context.stroke();
|
|
498
620
|
/** nodes */
|
|
499
|
-
|
|
621
|
+
const textRenders = [];
|
|
622
|
+
this.nodes.forEach(drawNode(textRenders).bind(this));
|
|
623
|
+
textRenders.forEach((r) => r());
|
|
500
624
|
this.context.restore();
|
|
501
625
|
this.listeners.onDrawFinished?.(this.context, this.areaTransform);
|
|
502
|
-
|
|
503
|
-
this.fading -= 0.1;
|
|
504
|
-
requestAnimationFrame(() => {
|
|
505
|
-
this.draw();
|
|
506
|
-
});
|
|
507
|
-
}
|
|
626
|
+
calculateHighlightFading.bind(this)();
|
|
508
627
|
}
|
|
509
628
|
function drawLink(link, index) {
|
|
510
629
|
if (!this.context ||
|
|
@@ -520,7 +639,7 @@ class GraphCanvas {
|
|
|
520
639
|
if (this.highlightedNeighbors && this.highlightedNode) {
|
|
521
640
|
if (this.highlightedNode.id != link.source.id &&
|
|
522
641
|
this.highlightedNode.id != link.target.id) {
|
|
523
|
-
alpha = this.
|
|
642
|
+
alpha = this.highlighFading;
|
|
524
643
|
}
|
|
525
644
|
this.context.beginPath();
|
|
526
645
|
}
|
|
@@ -532,41 +651,58 @@ class GraphCanvas {
|
|
|
532
651
|
if (this.highlightedNeighbors && this.highlightedNode)
|
|
533
652
|
this.context.stroke();
|
|
534
653
|
}
|
|
535
|
-
function drawNode(node, index) {
|
|
654
|
+
const drawNode = (textRenders) => function drawNode(node, index) {
|
|
536
655
|
if (!this.context || !node.x || !node.y)
|
|
537
656
|
return;
|
|
538
657
|
const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.areaTransform, this.nodeSettings.options || {}, nodeOptionsGetter);
|
|
539
658
|
const radius = nodeRadiusGetter({
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
radiusCoefficient:
|
|
543
|
-
radiusFactor:
|
|
659
|
+
radiusFlexible: this.graphSettings.nodeRadiusFlexible,
|
|
660
|
+
radiusInitial: this.graphSettings.nodeRadiusInitial,
|
|
661
|
+
radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
|
|
662
|
+
radiusFactor: this.graphSettings.nodeRadiusFactor,
|
|
544
663
|
linkCount: node.linkCount,
|
|
545
|
-
});
|
|
664
|
+
}) ?? nodeOptions.radius;
|
|
546
665
|
let alpha = nodeOptions.alpha;
|
|
547
666
|
if (this.highlightedNeighbors && this.highlightedNode) {
|
|
548
667
|
if (!this.highlightedNeighbors.has(node.id) && this.highlightedNode.id != node.id) {
|
|
549
|
-
alpha = this.
|
|
668
|
+
alpha = this.highlighFading;
|
|
550
669
|
}
|
|
551
670
|
}
|
|
552
671
|
this.context.beginPath();
|
|
553
672
|
this.context.globalAlpha = alpha;
|
|
554
673
|
/** text */
|
|
555
|
-
if (nodeOptions.text) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
674
|
+
if (nodeOptions.textVisible && nodeOptions.text) {
|
|
675
|
+
textRenders.push(() => {
|
|
676
|
+
if (!this.context || !node.x || !node.y || !nodeOptions.text)
|
|
677
|
+
return;
|
|
678
|
+
this.context.beginPath();
|
|
679
|
+
this.context.globalAlpha = alpha;
|
|
680
|
+
drawText({
|
|
681
|
+
id: node.id,
|
|
682
|
+
cachedNodeText: this.cachedNodeText,
|
|
683
|
+
context: this.context,
|
|
684
|
+
text: nodeOptions.text,
|
|
685
|
+
textAlign: nodeOptions.textAlign,
|
|
686
|
+
textColor: nodeOptions.textColor,
|
|
687
|
+
textFont: nodeOptions.textFont,
|
|
688
|
+
textSize: nodeOptions.textSize,
|
|
689
|
+
x: node.x + nodeOptions.textShiftX,
|
|
690
|
+
y: node.y + radius + nodeOptions.textShiftY,
|
|
691
|
+
maxWidth: nodeOptions.textWidth,
|
|
692
|
+
textStyle: nodeOptions.textStyle,
|
|
693
|
+
textWeight: nodeOptions.textWeight,
|
|
694
|
+
textGap: nodeOptions.textGap,
|
|
695
|
+
});
|
|
696
|
+
});
|
|
560
697
|
}
|
|
561
698
|
/** circle */
|
|
562
|
-
this.context.
|
|
563
|
-
this.context.
|
|
564
|
-
this.context.
|
|
699
|
+
this.context.lineWidth = nodeOptions.borderWidth;
|
|
700
|
+
this.context.strokeStyle = nodeOptions.borderColor;
|
|
701
|
+
this.context.fillStyle = nodeOptions.color;
|
|
565
702
|
this.context.arc(node.x, node.y, radius, 0, 2 * Math.PI);
|
|
566
|
-
this.context.fillStyle = nodeOptions.colorInner;
|
|
567
703
|
this.context.fill();
|
|
568
704
|
this.context.stroke();
|
|
569
|
-
}
|
|
705
|
+
};
|
|
570
706
|
return draw;
|
|
571
707
|
}
|
|
572
708
|
initResize() {
|
|
@@ -586,9 +722,9 @@ class GraphCanvas {
|
|
|
586
722
|
throw new Error("bad init data");
|
|
587
723
|
this.area.addEventListener("pointermove", (event) => {
|
|
588
724
|
let currentNode;
|
|
589
|
-
if (this.graphSettings.highlightByHover) {
|
|
725
|
+
if (this.graphSettings.highlightByHover && !this.isDragging) {
|
|
590
726
|
currentNode = nodeByPointerGetter({
|
|
591
|
-
|
|
727
|
+
graphSettings: this.graphSettings,
|
|
592
728
|
areaRect: this.areaRect,
|
|
593
729
|
areaTransform: this.areaTransform,
|
|
594
730
|
mouseEvent: event,
|
|
@@ -597,23 +733,25 @@ class GraphCanvas {
|
|
|
597
733
|
if (currentNode && currentNode.neighbors && this.highlightedNode !== currentNode) {
|
|
598
734
|
this.highlightedNode = currentNode;
|
|
599
735
|
this.highlightedNeighbors = new Set(this.highlightedNode.neighbors);
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
736
|
+
this.highlightFadingWorking = true;
|
|
737
|
+
if (!this.simulationWorking && !this.highlightDrawing)
|
|
738
|
+
requestAnimationFrame(() => {
|
|
739
|
+
this.draw();
|
|
740
|
+
});
|
|
603
741
|
}
|
|
604
742
|
else if (!currentNode && this.highlightedNode) {
|
|
605
|
-
this.
|
|
606
|
-
this.
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
743
|
+
this.highlightFadingWorking = false;
|
|
744
|
+
if (!this.simulationWorking && !this.highlightDrawing)
|
|
745
|
+
requestAnimationFrame(() => {
|
|
746
|
+
this.draw();
|
|
747
|
+
});
|
|
610
748
|
}
|
|
611
749
|
}
|
|
612
750
|
if (!this.listeners.onMove)
|
|
613
751
|
return;
|
|
614
752
|
if (!currentNode)
|
|
615
753
|
currentNode = nodeByPointerGetter({
|
|
616
|
-
|
|
754
|
+
graphSettings: this.graphSettings,
|
|
617
755
|
areaRect: this.areaRect,
|
|
618
756
|
areaTransform: this.areaTransform,
|
|
619
757
|
mouseEvent: event,
|
|
@@ -627,7 +765,7 @@ class GraphCanvas {
|
|
|
627
765
|
if (!this.listeners.onDoubleClick)
|
|
628
766
|
return;
|
|
629
767
|
const currentNode = nodeByPointerGetter({
|
|
630
|
-
|
|
768
|
+
graphSettings: this.graphSettings,
|
|
631
769
|
areaRect: this.areaRect,
|
|
632
770
|
areaTransform: this.areaTransform,
|
|
633
771
|
mouseEvent: event,
|
|
@@ -642,7 +780,7 @@ class GraphCanvas {
|
|
|
642
780
|
if (!this.listeners.onClick)
|
|
643
781
|
return;
|
|
644
782
|
const currentNode = nodeByPointerGetter({
|
|
645
|
-
|
|
783
|
+
graphSettings: this.graphSettings,
|
|
646
784
|
areaRect: this.areaRect,
|
|
647
785
|
areaTransform: this.areaTransform,
|
|
648
786
|
mouseEvent: event,
|
|
@@ -654,7 +792,7 @@ class GraphCanvas {
|
|
|
654
792
|
if (!this.listeners.onWheelClick)
|
|
655
793
|
return;
|
|
656
794
|
const currentNode = nodeByPointerGetter({
|
|
657
|
-
|
|
795
|
+
graphSettings: this.graphSettings,
|
|
658
796
|
areaRect: this.areaRect,
|
|
659
797
|
areaTransform: this.areaTransform,
|
|
660
798
|
mouseEvent: event,
|
|
@@ -669,7 +807,7 @@ class GraphCanvas {
|
|
|
669
807
|
if (!this.listeners.onContextMenu)
|
|
670
808
|
return;
|
|
671
809
|
const currentNode = nodeByPointerGetter({
|
|
672
|
-
|
|
810
|
+
graphSettings: this.graphSettings,
|
|
673
811
|
areaRect: this.areaRect,
|
|
674
812
|
areaTransform: this.areaTransform,
|
|
675
813
|
mouseEvent: event,
|
|
@@ -693,17 +831,17 @@ class GraphCanvas {
|
|
|
693
831
|
const mouseEvent = event.sourceEvent;
|
|
694
832
|
const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
|
|
695
833
|
let index = 0;
|
|
696
|
-
return
|
|
834
|
+
return greatest(this.nodes, (node) => {
|
|
697
835
|
if (!node.x || !node.y)
|
|
698
836
|
return undefined;
|
|
699
837
|
const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this.areaTransform, this.nodeSettings.options || {}, nodeOptionsGetter);
|
|
700
838
|
const radius = nodeRadiusGetter({
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
radiusCoefficient:
|
|
704
|
-
radiusFactor:
|
|
839
|
+
radiusFlexible: this.graphSettings.nodeRadiusFlexible,
|
|
840
|
+
radiusInitial: this.graphSettings.nodeRadiusInitial,
|
|
841
|
+
radiusCoefficient: this.graphSettings.nodeRadiusCoefficient,
|
|
842
|
+
radiusFactor: this.graphSettings.nodeRadiusFactor,
|
|
705
843
|
linkCount: node.linkCount,
|
|
706
|
-
});
|
|
844
|
+
}) ?? nodeOptions.radius;
|
|
707
845
|
index++;
|
|
708
846
|
return this.graphSettings.dragPlaceCoefficient(node, pointerX, pointerY, radius);
|
|
709
847
|
});
|
|
@@ -757,6 +895,8 @@ class GraphCanvas {
|
|
|
757
895
|
requestAnimationFrame(() => this.draw());
|
|
758
896
|
}))
|
|
759
897
|
.on("dblclick.zoom", null);
|
|
898
|
+
this.areaTransform = new ZoomTransform(1, this.width / 2, this.height / 2);
|
|
899
|
+
zoom().transform(select(this.area), this.areaTransform);
|
|
760
900
|
}
|
|
761
901
|
}
|
|
762
902
|
|