@fxhash/open-form-graph 0.0.3 → 0.0.4
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/dist/{OpenFormGraph-i8VSTamk.js → OpenFormGraph-Ccizmxbo.js} +1089 -732
- package/dist/OpenFormGraph-Ccizmxbo.js.map +1 -0
- package/dist/OpenFormGraph-Dr0GdNk0.d.ts +581 -0
- package/dist/components.d.ts +1 -2
- package/dist/components.js +2 -2
- package/dist/index.d.ts +2 -131
- package/dist/index.js +3 -3
- package/dist/{provider-PqOen2FE.js → provider-CPq89n3a.js} +16 -8
- package/dist/provider-CPq89n3a.js.map +1 -0
- package/dist/provider.d.ts +2 -3
- package/dist/provider.js +2 -2
- package/package.json +5 -3
- package/dist/OpenFormGraph-CdcWNN86.d.ts +0 -24
- package/dist/OpenFormGraph-i8VSTamk.js.map +0 -1
- package/dist/_types-CjgCTzqc.d.ts +0 -82
- package/dist/constants-BNPHdbBA.d.ts +0 -313
- package/dist/provider-PqOen2FE.js.map +0 -1
|
@@ -1,14 +1,228 @@
|
|
|
1
|
-
import { DEFAULT_GRAPH_CONFIG, VOID_DETACH_ID, VOID_ROOT_ID, useOpenFormGraph } from "./provider-
|
|
1
|
+
import { DEFAULT_GRAPH_CONFIG, VOID_DETACH_ID, VOID_ROOT_ID, useOpenFormGraph } from "./provider-CPq89n3a.js";
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
|
-
import { jsx } from "react/jsx-runtime";
|
|
4
|
-
import { forceCenter, forceCollide, forceManyBody,
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { forceCenter, forceCollide, forceManyBody, forceSimulation } from "d3-force";
|
|
5
|
+
import { EventEmitter, MappedArray, xorshiftString } from "@fxhash/utils";
|
|
6
|
+
import groupBy from "lodash.groupby";
|
|
5
7
|
import { scaleLinear, scaleLog } from "d3-scale";
|
|
6
|
-
import { EventEmitter, float2hex, xorshiftString } from "@fxhash/utils";
|
|
7
8
|
|
|
9
|
+
//#region src/util/canvas.ts
|
|
10
|
+
/**
|
|
11
|
+
* draws a circle on the canvas
|
|
12
|
+
* @param ctx - The canvas rendering context
|
|
13
|
+
* @param x - The x-coordinate of the circle's center
|
|
14
|
+
* @param y - The y-coordinate of the circle's center
|
|
15
|
+
* @param radius - The radius of the circle (default is 5)
|
|
16
|
+
* @param options - Optional parameters for styling the circle
|
|
17
|
+
* @param options.fill - Whether to fill the circle (default is true)
|
|
18
|
+
* @param options.fillStyle - The fill color of the circle
|
|
19
|
+
* @param options.stroke - Whether to stroke the circle (default is false)
|
|
20
|
+
* @param options.strokeStyle - The stroke color of the circle
|
|
21
|
+
* @param options.lineWidth - The width of the stroke (default is 0.2)
|
|
22
|
+
* @returns void
|
|
23
|
+
*/
|
|
24
|
+
function circle(ctx, x, y, radius = 5, options) {
|
|
25
|
+
const { fill = true, fillStyle, stroke = false, strokeStyle, lineWidth = .2 } = options || {};
|
|
26
|
+
ctx.save();
|
|
27
|
+
if (fillStyle !== void 0) ctx.fillStyle = fillStyle;
|
|
28
|
+
if (strokeStyle !== void 0) ctx.strokeStyle = strokeStyle;
|
|
29
|
+
if (lineWidth !== void 0) ctx.lineWidth = lineWidth;
|
|
30
|
+
ctx.beginPath();
|
|
31
|
+
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
|
32
|
+
ctx.closePath();
|
|
33
|
+
if (fill) ctx.fill();
|
|
34
|
+
if (stroke) ctx.stroke();
|
|
35
|
+
ctx.restore();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* draws a rectangle on the canvas
|
|
39
|
+
* @param ctx - The canvas rendering context
|
|
40
|
+
* @param x - The x-coordinate of the rectangle's top-left corner
|
|
41
|
+
* @param y - The y-coordinate of the rectangle's top-left corner
|
|
42
|
+
* @param width - The width of the rectangle
|
|
43
|
+
* @param height - The height of the rectangle
|
|
44
|
+
* @param options - Optional parameters for styling the rectangle
|
|
45
|
+
* @param options.fill - Whether to fill the rectangle (default is true)
|
|
46
|
+
* @param options.fillStyle - The fill color of the rectangle
|
|
47
|
+
* @param options.stroke - Whether to stroke the rectangle (default is false)
|
|
48
|
+
* @param options.strokeStyle - The stroke color of the rectangle
|
|
49
|
+
* @param options.lineWidth - The width of the stroke (default is 0.2)
|
|
50
|
+
* @param options.borderRadius - The radius of the corners (default is 0)
|
|
51
|
+
* @returns void
|
|
52
|
+
*/
|
|
53
|
+
function rect(ctx, x, y, width, height, options) {
|
|
54
|
+
const { fill = true, fillStyle, stroke = false, strokeStyle, lineWidth = .2, borderRadius = 0 } = options || {};
|
|
55
|
+
ctx.save();
|
|
56
|
+
if (fillStyle !== void 0) ctx.fillStyle = fillStyle;
|
|
57
|
+
if (strokeStyle !== void 0) ctx.strokeStyle = strokeStyle;
|
|
58
|
+
if (lineWidth !== void 0) ctx.lineWidth = lineWidth;
|
|
59
|
+
const r = Math.min(borderRadius, width / 2, height / 2);
|
|
60
|
+
ctx.beginPath();
|
|
61
|
+
if (r > 0) {
|
|
62
|
+
ctx.moveTo(x + r, y);
|
|
63
|
+
ctx.lineTo(x + width - r, y);
|
|
64
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + r);
|
|
65
|
+
ctx.lineTo(x + width, y + height - r);
|
|
66
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
|
|
67
|
+
ctx.lineTo(x + r, y + height);
|
|
68
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - r);
|
|
69
|
+
ctx.lineTo(x, y + r);
|
|
70
|
+
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
71
|
+
} else ctx.rect(x, y, width, height);
|
|
72
|
+
ctx.closePath();
|
|
73
|
+
if (fill) ctx.fill();
|
|
74
|
+
if (stroke) ctx.stroke();
|
|
75
|
+
ctx.restore();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* draws an image on the canvas with optional border radius and opacity
|
|
79
|
+
* @param ctx - The canvas rendering context
|
|
80
|
+
* @param image - The HTMLImageElement to draw
|
|
81
|
+
* @param x - The x-coordinate of the image's top-left corner
|
|
82
|
+
* @param y - The y-coordinate of the image's top-left corner
|
|
83
|
+
* @param width - The width of the image
|
|
84
|
+
* @param height - The height of the image
|
|
85
|
+
* @param borderRadius - The radius of the corners (default is 0)
|
|
86
|
+
* @param opacity - The opacity of the image (default is 1.0)
|
|
87
|
+
* @param bgColor - Optional background color to fill the clipped area
|
|
88
|
+
* @returns void
|
|
89
|
+
*/
|
|
90
|
+
function img(ctx, image, x, y, width, height, borderRadius = 0, opacity = 1, bgColor) {
|
|
91
|
+
ctx.save();
|
|
92
|
+
ctx.beginPath();
|
|
93
|
+
ctx.globalAlpha = 1;
|
|
94
|
+
if (borderRadius > 0) {
|
|
95
|
+
const r = Math.min(borderRadius, width / 2, height / 2);
|
|
96
|
+
ctx.moveTo(x + r, y);
|
|
97
|
+
ctx.lineTo(x + width - r, y);
|
|
98
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + r);
|
|
99
|
+
ctx.lineTo(x + width, y + height - r);
|
|
100
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
|
|
101
|
+
ctx.lineTo(x + r, y + height);
|
|
102
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - r);
|
|
103
|
+
ctx.lineTo(x, y + r);
|
|
104
|
+
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
105
|
+
} else ctx.rect(x, y, width, height);
|
|
106
|
+
ctx.closePath();
|
|
107
|
+
if (bgColor && opacity < 1) {
|
|
108
|
+
ctx.save();
|
|
109
|
+
ctx.fillStyle = bgColor;
|
|
110
|
+
ctx.fill();
|
|
111
|
+
ctx.restore();
|
|
112
|
+
}
|
|
113
|
+
ctx.clip();
|
|
114
|
+
ctx.globalAlpha = opacity;
|
|
115
|
+
ctx.drawImage(image, x, y, width, height);
|
|
116
|
+
ctx.restore();
|
|
117
|
+
}
|
|
118
|
+
function hexagon(ctx, x, y, radius, options) {
|
|
119
|
+
const { fill = true, fillStyle, stroke = false, strokeStyle, lineWidth = .2, rotation = 0, borderRadius = 0 } = options || {};
|
|
120
|
+
ctx.save();
|
|
121
|
+
if (fillStyle !== void 0) ctx.fillStyle = fillStyle;
|
|
122
|
+
if (strokeStyle !== void 0) ctx.strokeStyle = strokeStyle;
|
|
123
|
+
if (lineWidth !== void 0) ctx.lineWidth = lineWidth;
|
|
124
|
+
const sides = 6;
|
|
125
|
+
const angleStep = Math.PI * 2 / sides;
|
|
126
|
+
ctx.beginPath();
|
|
127
|
+
const points = [];
|
|
128
|
+
for (let i = 0; i < sides; i++) {
|
|
129
|
+
const angle = rotation + i * angleStep;
|
|
130
|
+
points.push({
|
|
131
|
+
x: x + radius * Math.cos(angle),
|
|
132
|
+
y: y + radius * Math.sin(angle)
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (borderRadius > 0) {
|
|
136
|
+
const maxBorderRadius = Math.min(borderRadius, radius / 3);
|
|
137
|
+
for (let i = 0; i < sides; i++) {
|
|
138
|
+
const current = points[i];
|
|
139
|
+
const next = points[(i + 1) % sides];
|
|
140
|
+
const prev = points[(i - 1 + sides) % sides];
|
|
141
|
+
const toPrev = {
|
|
142
|
+
x: prev.x - current.x,
|
|
143
|
+
y: prev.y - current.y
|
|
144
|
+
};
|
|
145
|
+
const toNext = {
|
|
146
|
+
x: next.x - current.x,
|
|
147
|
+
y: next.y - current.y
|
|
148
|
+
};
|
|
149
|
+
const lenPrev = Math.sqrt(toPrev.x * toPrev.x + toPrev.y * toPrev.y);
|
|
150
|
+
const lenNext = Math.sqrt(toNext.x * toNext.x + toNext.y * toNext.y);
|
|
151
|
+
const normPrev = {
|
|
152
|
+
x: toPrev.x / lenPrev,
|
|
153
|
+
y: toPrev.y / lenPrev
|
|
154
|
+
};
|
|
155
|
+
const normNext = {
|
|
156
|
+
x: toNext.x / lenNext,
|
|
157
|
+
y: toNext.y / lenNext
|
|
158
|
+
};
|
|
159
|
+
const cpPrev = {
|
|
160
|
+
x: current.x + normPrev.x * maxBorderRadius,
|
|
161
|
+
y: current.y + normPrev.y * maxBorderRadius
|
|
162
|
+
};
|
|
163
|
+
const cpNext = {
|
|
164
|
+
x: current.x + normNext.x * maxBorderRadius,
|
|
165
|
+
y: current.y + normNext.y * maxBorderRadius
|
|
166
|
+
};
|
|
167
|
+
if (i === 0) ctx.moveTo(cpPrev.x, cpPrev.y);
|
|
168
|
+
else ctx.lineTo(cpPrev.x, cpPrev.y);
|
|
169
|
+
ctx.quadraticCurveTo(current.x, current.y, cpNext.x, cpNext.y);
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
173
|
+
for (let i = 1; i < sides; i++) ctx.lineTo(points[i].x, points[i].y);
|
|
174
|
+
}
|
|
175
|
+
ctx.closePath();
|
|
176
|
+
if (fill) ctx.fill();
|
|
177
|
+
if (stroke) ctx.stroke();
|
|
178
|
+
ctx.restore();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/util/color.ts
|
|
183
|
+
/**
|
|
184
|
+
* Some utility functions to handle colors
|
|
185
|
+
*/
|
|
186
|
+
function color(rgb) {
|
|
187
|
+
const colorHandler = function(arg) {
|
|
188
|
+
if (typeof arg === "number") return `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${arg})`;
|
|
189
|
+
if (typeof arg === "function") {
|
|
190
|
+
const transformedRGB = arg(rgb);
|
|
191
|
+
return color(transformedRGB);
|
|
192
|
+
}
|
|
193
|
+
return `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`;
|
|
194
|
+
};
|
|
195
|
+
colorHandler.rgb = rgb;
|
|
196
|
+
return colorHandler;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Dims a color to white or black
|
|
200
|
+
* @param color An array of 3 numbers representing RGB values (0-255)
|
|
201
|
+
* @param dimFactor A value between 0 and 1 where 0 is completely black and 1 is the original color
|
|
202
|
+
* @returns A new array with the dimmed RGB values
|
|
203
|
+
*/
|
|
204
|
+
function dim(factor = .6, white = true) {
|
|
205
|
+
const base = white ? 255 : 0;
|
|
206
|
+
return function(rgb) {
|
|
207
|
+
return [
|
|
208
|
+
Math.round(rgb[0] + (base - rgb[0]) * (1 - factor)),
|
|
209
|
+
Math.round(rgb[1] + (base - rgb[1]) * (1 - factor)),
|
|
210
|
+
Math.round(rgb[2] + (base - rgb[2]) * (1 - factor))
|
|
211
|
+
];
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
//#endregion
|
|
8
216
|
//#region src/util/types.ts
|
|
9
217
|
function isSimNode(node) {
|
|
10
218
|
return typeof node === "object" && "id" in node;
|
|
11
219
|
}
|
|
220
|
+
function getNodeId(node) {
|
|
221
|
+
return isSimNode(node) ? node.id : node.toString();
|
|
222
|
+
}
|
|
223
|
+
function getLinkId(link) {
|
|
224
|
+
return `${getNodeId(link.target)}-${getNodeId(link.source)}`;
|
|
225
|
+
}
|
|
12
226
|
function isSimLink(link) {
|
|
13
227
|
return typeof link === "object" && "source" in link && typeof link.source !== "string";
|
|
14
228
|
}
|
|
@@ -19,25 +233,18 @@ function isCustomHighlight(highlight) {
|
|
|
19
233
|
|
|
20
234
|
//#endregion
|
|
21
235
|
//#region src/util/graph.ts
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return links.filter((l) => {
|
|
27
|
-
const targetId = isSimNode(l.target) ? l.target.id : l.target;
|
|
28
|
-
return targetId === id;
|
|
29
|
-
}).map((link) => isSimNode(link.source) ? link.source.id : link.source.toString());
|
|
236
|
+
function getParent(id, links) {
|
|
237
|
+
const linkToParent = links.maps.targetId.get(id);
|
|
238
|
+
if (!linkToParent) return null;
|
|
239
|
+
return getNodeId(linkToParent.source);
|
|
30
240
|
}
|
|
31
241
|
function getAllParentsUntil(nodeId, links, stopAtId) {
|
|
32
|
-
const parent =
|
|
33
|
-
if (
|
|
242
|
+
const parent = getParent(nodeId, links);
|
|
243
|
+
if (parent === null || parent === stopAtId) return [];
|
|
34
244
|
return [parent, ...getAllParentsUntil(parent, links, stopAtId)];
|
|
35
245
|
}
|
|
36
246
|
function getChildren(id, links) {
|
|
37
|
-
return links.
|
|
38
|
-
const sourceId = isSimNode(l.source) ? l.source.id : l.source;
|
|
39
|
-
return sourceId === id;
|
|
40
|
-
}).map((link) => link.target.toString());
|
|
247
|
+
return (links.maps.sourceId.get(id) || []).map((link) => getNodeId(link.target));
|
|
41
248
|
}
|
|
42
249
|
function getClusterSize(id, links) {
|
|
43
250
|
const children = getChildren(id, links);
|
|
@@ -47,51 +254,46 @@ function getClusterSize(id, links) {
|
|
|
47
254
|
}
|
|
48
255
|
function getNodeDepth(id, links) {
|
|
49
256
|
function getDepth(id$1, depth) {
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
return getDepth(
|
|
257
|
+
const parent = getParent(id$1, links);
|
|
258
|
+
if (parent === null) return depth;
|
|
259
|
+
return getDepth(parent, depth + 1);
|
|
53
260
|
}
|
|
54
261
|
return getDepth(id, 0);
|
|
55
262
|
}
|
|
56
263
|
function getRootParent(id, links, stop) {
|
|
57
264
|
let currentId = id;
|
|
58
265
|
while (true) {
|
|
59
|
-
const
|
|
60
|
-
if (stop &&
|
|
61
|
-
if (
|
|
62
|
-
currentId =
|
|
266
|
+
const parent = getParent(currentId, links);
|
|
267
|
+
if (stop && parent === stop) return currentId;
|
|
268
|
+
if (parent === null) return currentId;
|
|
269
|
+
currentId = parent;
|
|
63
270
|
}
|
|
64
271
|
}
|
|
65
272
|
function hasOnlyLeafs(id, links) {
|
|
66
|
-
|
|
67
|
-
return children.every((childId) => getChildren(childId, links).length === 0);
|
|
273
|
+
return getChildren(id, links).every((childId) => getChildren(childId, links).length === 0);
|
|
68
274
|
}
|
|
69
275
|
function getNodeSubgraph(nodeId, nodes, links, rootId) {
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const subLinks = new Set();
|
|
276
|
+
const parentSet = /* @__PURE__ */ new Set();
|
|
277
|
+
const childSet = /* @__PURE__ */ new Set();
|
|
278
|
+
const subLinks = /* @__PURE__ */ new Set();
|
|
74
279
|
let currentId = nodeId;
|
|
75
280
|
while (currentId !== rootId) {
|
|
76
|
-
const parentLink = links.
|
|
77
|
-
const targetId = isSimNode(l.target) ? l.target.id : l.target;
|
|
78
|
-
return targetId === currentId;
|
|
79
|
-
});
|
|
281
|
+
const parentLink = links.maps.targetId.get(currentId);
|
|
80
282
|
if (!parentLink) break;
|
|
81
|
-
const parentId =
|
|
82
|
-
if (parentSet.has(parentId
|
|
83
|
-
parentSet.add(parentId
|
|
283
|
+
const parentId = getNodeId(parentLink.source);
|
|
284
|
+
if (parentSet.has(parentId)) break;
|
|
285
|
+
parentSet.add(parentId);
|
|
84
286
|
subLinks.add(parentLink);
|
|
85
|
-
currentId = parentId
|
|
287
|
+
currentId = parentId;
|
|
86
288
|
}
|
|
87
289
|
function collectChildren(id) {
|
|
88
|
-
for (const link of links) {
|
|
89
|
-
const sourceId =
|
|
90
|
-
const targetId =
|
|
91
|
-
if (sourceId === id && !childSet.has(targetId
|
|
92
|
-
childSet.add(targetId
|
|
290
|
+
for (const link of links.values) {
|
|
291
|
+
const sourceId = getNodeId(link.source);
|
|
292
|
+
const targetId = getNodeId(link.target);
|
|
293
|
+
if (sourceId === id && !childSet.has(targetId)) {
|
|
294
|
+
childSet.add(targetId);
|
|
93
295
|
subLinks.add(link);
|
|
94
|
-
collectChildren(targetId
|
|
296
|
+
collectChildren(targetId);
|
|
95
297
|
}
|
|
96
298
|
}
|
|
97
299
|
}
|
|
@@ -102,59 +304,390 @@ function getNodeSubgraph(nodeId, nodes, links, rootId) {
|
|
|
102
304
|
...childSet
|
|
103
305
|
]);
|
|
104
306
|
const filteredLinks = Array.from(subLinks).filter((link) => {
|
|
105
|
-
const sourceId =
|
|
106
|
-
const targetId =
|
|
307
|
+
const sourceId = getNodeId(link.source);
|
|
308
|
+
const targetId = getNodeId(link.target);
|
|
107
309
|
return validIds.has(sourceId.toString()) && validIds.has(targetId.toString());
|
|
108
310
|
});
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
return {
|
|
311
|
+
const subNodes = Array.from(validIds).map((id) => nodes.maps.id.get(id)).filter(Boolean);
|
|
312
|
+
return newGraphData({
|
|
112
313
|
nodes: subNodes,
|
|
113
314
|
links: filteredLinks
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
//#endregion
|
|
118
|
-
//#region src/util/img.ts
|
|
119
|
-
function loadHTMLImageElement(src) {
|
|
120
|
-
return new Promise((resolve, reject) => {
|
|
121
|
-
const img$1 = new Image();
|
|
122
|
-
img$1.onload = () => resolve(img$1);
|
|
123
|
-
img$1.onerror = reject;
|
|
124
|
-
img$1.src = src;
|
|
125
315
|
});
|
|
126
316
|
}
|
|
127
317
|
|
|
128
318
|
//#endregion
|
|
129
|
-
//#region src/
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
319
|
+
//#region src/util/data.ts
|
|
320
|
+
const images = [];
|
|
321
|
+
function generateTree(maxNodes, maxChildren) {
|
|
322
|
+
const nodes = [];
|
|
323
|
+
const links = [];
|
|
324
|
+
let index$1 = 0;
|
|
325
|
+
function createNode(label) {
|
|
326
|
+
const isRoot = label === VOID_ROOT_ID;
|
|
327
|
+
return {
|
|
328
|
+
id: (isRoot ? VOID_ROOT_ID : index$1++).toString(),
|
|
329
|
+
label,
|
|
330
|
+
imgSrc: isRoot ? void 0 : images[index$1 % images.length]
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const root = createNode(VOID_ROOT_ID);
|
|
334
|
+
nodes.push(root);
|
|
335
|
+
const queue = [root];
|
|
336
|
+
while (queue.length > 0 && nodes.length < maxNodes) {
|
|
337
|
+
const parent = queue.shift();
|
|
338
|
+
const rand = Math.random();
|
|
339
|
+
const biased = Math.floor(Math.pow(rand, 2) * (maxChildren + 1));
|
|
340
|
+
const childrenCount = Math.min(biased, maxChildren);
|
|
341
|
+
for (let i = 0; i < childrenCount; i++) {
|
|
342
|
+
if (nodes.length >= maxNodes) break;
|
|
343
|
+
const child = createNode(`Node ${nodes.length}`);
|
|
344
|
+
nodes.push(child);
|
|
345
|
+
links.push({
|
|
346
|
+
source: parent.id,
|
|
347
|
+
target: child.id
|
|
348
|
+
});
|
|
349
|
+
queue.push(child);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
nodes,
|
|
354
|
+
links
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function isSpecialNode(node) {
|
|
358
|
+
const state = node.state;
|
|
359
|
+
return !!(state?.emitterNode || state?.sessionNode || state?.rootNode || state?.groupNode);
|
|
360
|
+
}
|
|
361
|
+
function groupGraphNodes(startId, nodes, links, options = {
|
|
362
|
+
skip: false,
|
|
363
|
+
existingGroups: [],
|
|
364
|
+
openedGroups: []
|
|
365
|
+
}) {
|
|
366
|
+
if (options.skip) return {
|
|
367
|
+
nodes,
|
|
368
|
+
links
|
|
369
|
+
};
|
|
370
|
+
const visited = /* @__PURE__ */ new Set();
|
|
371
|
+
const nodesToRemove = /* @__PURE__ */ new Set();
|
|
372
|
+
function markRemovableNodes(id) {
|
|
373
|
+
if (visited.has(id)) return;
|
|
374
|
+
visited.add(id);
|
|
375
|
+
const children = getChildren(id, links);
|
|
376
|
+
let removableChildren = [];
|
|
377
|
+
for (const childId of children) {
|
|
378
|
+
const child = nodes.maps.id.get(childId);
|
|
379
|
+
if (!child) continue;
|
|
380
|
+
if ((child.clusterSize || 0) < 1 && !isSpecialNode(child)) removableChildren.push(childId);
|
|
381
|
+
}
|
|
382
|
+
if (removableChildren.length > 3) removableChildren.forEach((id$1) => nodesToRemove.add(id$1));
|
|
383
|
+
}
|
|
384
|
+
markRemovableNodes(startId);
|
|
385
|
+
const groupedNodesToRemove = groupBy(Array.from(nodesToRemove), (id) => {
|
|
386
|
+
const sourceLink = links.maps.targetId.get(id);
|
|
387
|
+
return isSimNode(sourceLink?.source) ? sourceLink?.source.id : sourceLink?.source.toString();
|
|
388
|
+
});
|
|
389
|
+
const GROUP_CHUNK_SIZE = 100;
|
|
390
|
+
const groupNodesToAdd = [];
|
|
391
|
+
const groupLinksToAdd = [];
|
|
392
|
+
function createGroupChunkNodes(groupSourceId, groupContent) {
|
|
393
|
+
const chunks = [];
|
|
394
|
+
for (let i = 0; i < groupContent.length; i += GROUP_CHUNK_SIZE) chunks.push(groupContent.slice(i, i + GROUP_CHUNK_SIZE));
|
|
395
|
+
chunks.forEach((chunk, index$1) => {
|
|
396
|
+
const chunkId = `${groupSourceId}-group-${index$1}`;
|
|
397
|
+
if (options.openedGroups?.includes(chunkId)) {
|
|
398
|
+
chunk.forEach((id) => {
|
|
399
|
+
nodesToRemove.delete(id);
|
|
400
|
+
});
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const existingGroup = options.existingGroups?.find((n) => n.id === chunkId);
|
|
404
|
+
const originNode = nodes.maps.id.get(groupSourceId);
|
|
405
|
+
const groupNode = {
|
|
406
|
+
...existingGroup,
|
|
407
|
+
x: existingGroup?.x || originNode?.x || 0,
|
|
408
|
+
y: existingGroup?.y || originNode?.y || 0,
|
|
409
|
+
id: chunkId,
|
|
410
|
+
clusterSize: chunk.length,
|
|
411
|
+
state: {
|
|
412
|
+
...existingGroup?.state,
|
|
413
|
+
groupNode: true,
|
|
414
|
+
groupContent: chunk,
|
|
415
|
+
collapsed: true
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
groupNodesToAdd.push(groupNode);
|
|
419
|
+
groupLinksToAdd.push({
|
|
420
|
+
source: nodes.maps.id.get(groupSourceId),
|
|
421
|
+
target: groupNode
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
Object.keys(groupedNodesToRemove).forEach((groupSourceId) => {
|
|
426
|
+
const groupContent = groupedNodesToRemove[groupSourceId];
|
|
427
|
+
createGroupChunkNodes(groupSourceId, groupContent);
|
|
428
|
+
});
|
|
429
|
+
const _nodes = nodes.values.filter((n) => !nodesToRemove.has(n.id));
|
|
430
|
+
const _links = links.values.filter((l) => {
|
|
431
|
+
const targetId = isSimNode(l.target) ? l.target.id : l.target.toString();
|
|
432
|
+
return !nodesToRemove.has(targetId);
|
|
433
|
+
});
|
|
434
|
+
return newGraphData({
|
|
435
|
+
nodes: [..._nodes, ...groupNodesToAdd],
|
|
436
|
+
links: [..._links, ...groupLinksToAdd]
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
function getPrunedData(startId, nodes, links, highlights = [], options = {
|
|
440
|
+
nodeVisibility: "all",
|
|
441
|
+
emittedNodes: [],
|
|
442
|
+
groupNodes: [],
|
|
443
|
+
skipGrouping: false,
|
|
444
|
+
openedGroups: []
|
|
445
|
+
}) {
|
|
446
|
+
const visibleNodes = [];
|
|
447
|
+
const visibleLinks = [];
|
|
448
|
+
const visited = /* @__PURE__ */ new Set();
|
|
449
|
+
const isLocked = (node) => node.status === "LOCKED" || node.status === "EVOLVED";
|
|
450
|
+
const isEmitted = (node) => options.emittedNodes.includes(node.id);
|
|
451
|
+
const isOwnedHighlight = (nodeId) => highlights.find((h) => h.id === nodeId && h.type === "mine");
|
|
452
|
+
const isOnSale = (node) => highlights.find((h) => h.id === node.id && h.type === "on-sale");
|
|
453
|
+
const isHighlighted = (nodeId, type) => highlights.some((h) => type ? h.id === nodeId && h.type === type : h.id === nodeId);
|
|
454
|
+
const isChildHighlighted = (nodeId, type) => {
|
|
455
|
+
const children = getChildren(nodeId, links);
|
|
456
|
+
if (children.some((h) => isHighlighted(h, type))) return true;
|
|
457
|
+
else return children.some((c) => isChildHighlighted(c, type));
|
|
458
|
+
};
|
|
459
|
+
function visbilityFilter() {
|
|
460
|
+
switch (options.nodeVisibility) {
|
|
461
|
+
case "on-sale": return (node) => !isEmitted(node) && !isSpecialNode(node) && !isOnSale(node) && !isChildHighlighted(node.id, "on-sale");
|
|
462
|
+
case "locked": return (node) => !isEmitted(node) && !isSpecialNode(node) && !isLocked(node) && !isChildHighlighted(node.id, "locked");
|
|
463
|
+
case "mine": return (node) => {
|
|
464
|
+
return !isEmitted(node) && !isSpecialNode(node) && !isOwnedHighlight(node.id) && !isChildHighlighted(node.id, "mine");
|
|
465
|
+
};
|
|
466
|
+
case "all":
|
|
467
|
+
default: return () => false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const liquidatedFilter = (node) => {
|
|
471
|
+
return node.status === "LIQUIDATED" && !highlights.find((h) => h.id === node.id);
|
|
472
|
+
};
|
|
473
|
+
(function traverseTree(node = nodes.maps.id.get(startId)) {
|
|
474
|
+
if (!node || visited.has(node.id)) return;
|
|
475
|
+
visited.add(node.id);
|
|
476
|
+
if (liquidatedFilter(node)) return;
|
|
477
|
+
if (visbilityFilter()(node)) return;
|
|
478
|
+
visibleNodes.push(node);
|
|
479
|
+
if (node?.state?.collapsed) return;
|
|
480
|
+
const childLinks = links.maps.sourceId.get(node.id) || [];
|
|
481
|
+
for (const link of childLinks) {
|
|
482
|
+
const targetNode = isSimNode(link.target) ? link.target : nodes.maps.id.get(link.target.toString());
|
|
483
|
+
if (!targetNode) continue;
|
|
484
|
+
if (liquidatedFilter(targetNode)) continue;
|
|
485
|
+
if (visbilityFilter()(targetNode)) continue;
|
|
486
|
+
visibleLinks.push(link);
|
|
487
|
+
traverseTree(targetNode);
|
|
488
|
+
}
|
|
489
|
+
})();
|
|
490
|
+
const visible = newGraphData({
|
|
491
|
+
nodes: visibleNodes,
|
|
492
|
+
links: visibleLinks
|
|
493
|
+
});
|
|
494
|
+
return groupGraphNodes(startId, visible.nodes, visible.links, {
|
|
495
|
+
skip: options.skipGrouping || false,
|
|
496
|
+
existingGroups: options.groupNodes,
|
|
497
|
+
openedGroups: options.openedGroups
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Automatically identifies root nodes and builds a nested structure
|
|
502
|
+
* @param nodes Array of raw nodes
|
|
503
|
+
* @param links Array of links between nodes
|
|
504
|
+
* @returns Array of nested nodes starting from identified roots
|
|
505
|
+
*/
|
|
506
|
+
function buildTreeFromGraphData(nodes, links) {
|
|
507
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
508
|
+
nodes.forEach((node) => nodeMap.set(node.id, node));
|
|
509
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
510
|
+
const parentMap = /* @__PURE__ */ new Map();
|
|
511
|
+
nodes.forEach((node) => {
|
|
512
|
+
childrenMap.set(node.id, []);
|
|
513
|
+
parentMap.set(node.id, []);
|
|
514
|
+
});
|
|
515
|
+
links.forEach((link) => {
|
|
516
|
+
if (nodeMap.has(link.source) && nodeMap.has(link.target)) {
|
|
517
|
+
const children = childrenMap.get(link.source) || [];
|
|
518
|
+
if (!children.includes(link.target)) {
|
|
519
|
+
children.push(link.target);
|
|
520
|
+
childrenMap.set(link.source, children);
|
|
521
|
+
}
|
|
522
|
+
const parents = parentMap.get(link.target) || [];
|
|
523
|
+
if (!parents.includes(link.source)) {
|
|
524
|
+
parents.push(link.source);
|
|
525
|
+
parentMap.set(link.target, parents);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
const rootHashes = [];
|
|
530
|
+
nodeMap.forEach((_, hash) => {
|
|
531
|
+
if ((parentMap.get(hash) || []).length === 0) rootHashes.push(hash);
|
|
532
|
+
});
|
|
533
|
+
const buildNode = (hash, visited = /* @__PURE__ */ new Set()) => {
|
|
534
|
+
if (visited.has(hash)) return null;
|
|
535
|
+
visited.add(hash);
|
|
536
|
+
const node = nodeMap.get(hash);
|
|
537
|
+
if (!node) return null;
|
|
538
|
+
const childHashes = childrenMap.get(hash) || [];
|
|
539
|
+
const nestedChildren = [];
|
|
540
|
+
childHashes.forEach((childHash) => {
|
|
541
|
+
const childNode = buildNode(childHash, new Set([...visited]));
|
|
542
|
+
if (childNode) nestedChildren.push(childNode);
|
|
543
|
+
});
|
|
544
|
+
return {
|
|
545
|
+
...node,
|
|
546
|
+
children: nestedChildren
|
|
547
|
+
};
|
|
548
|
+
};
|
|
549
|
+
const result = [];
|
|
550
|
+
rootHashes.forEach((rootHash) => {
|
|
551
|
+
const nestedRoot = buildNode(rootHash);
|
|
552
|
+
if (nestedRoot) result.push(nestedRoot);
|
|
553
|
+
});
|
|
554
|
+
const processedNodes = /* @__PURE__ */ new Set();
|
|
555
|
+
const markProcessed = (node) => {
|
|
556
|
+
processedNodes.add(node.id);
|
|
557
|
+
node.children.forEach(markProcessed);
|
|
558
|
+
};
|
|
559
|
+
result.forEach(markProcessed);
|
|
560
|
+
nodeMap.forEach((_, hash) => {
|
|
561
|
+
if (!processedNodes.has(hash)) {
|
|
562
|
+
const orphanedRoot = buildNode(hash);
|
|
563
|
+
if (orphanedRoot) result.push(orphanedRoot);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Recursively retrieves all parents of a node from a graph data structure
|
|
570
|
+
* @param {string} nodeHash - The hash of the node to find parents for
|
|
571
|
+
* @param {RawNode[]} nodes - Array of nodes in the graph
|
|
572
|
+
* @param {RawLink[]} links - Array of links connecting the nodes
|
|
573
|
+
* @returns {RawNode[]} - Array of parent nodes
|
|
574
|
+
*/
|
|
575
|
+
function searchParents(nodeHash, nodes, links) {
|
|
576
|
+
const visited = /* @__PURE__ */ new Set();
|
|
577
|
+
function findParents(hash) {
|
|
578
|
+
if (visited.has(hash)) return [];
|
|
579
|
+
visited.add(hash);
|
|
580
|
+
const immediateParents = links.filter((link) => link.target === hash).map((link) => link.source);
|
|
581
|
+
const parentNodes = nodes.filter((node) => immediateParents.includes(node.id));
|
|
582
|
+
const ancestorNodes = immediateParents.flatMap((parentHash) => findParents(parentHash));
|
|
583
|
+
return [...parentNodes, ...ancestorNodes];
|
|
584
|
+
}
|
|
585
|
+
return findParents(nodeHash);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
//#endregion
|
|
589
|
+
//#region src/util/img.ts
|
|
590
|
+
function loadHTMLImageElement(src) {
|
|
591
|
+
return new Promise((resolve, reject) => {
|
|
592
|
+
const img$1 = new Image();
|
|
593
|
+
img$1.onload = () => resolve(img$1);
|
|
594
|
+
img$1.onerror = reject;
|
|
595
|
+
img$1.src = src;
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/util/highlights.ts
|
|
601
|
+
const blue = [
|
|
602
|
+
94,
|
|
603
|
+
112,
|
|
604
|
+
235
|
|
605
|
+
];
|
|
606
|
+
const red = [
|
|
607
|
+
238,
|
|
608
|
+
125,
|
|
609
|
+
121
|
|
610
|
+
];
|
|
611
|
+
const redred = [
|
|
612
|
+
255,
|
|
613
|
+
0,
|
|
614
|
+
0
|
|
615
|
+
];
|
|
616
|
+
var Highlight = class {
|
|
617
|
+
static owner = (id) => {
|
|
618
|
+
return {
|
|
619
|
+
id,
|
|
620
|
+
type: "mine",
|
|
621
|
+
strokeColor: red
|
|
622
|
+
};
|
|
623
|
+
};
|
|
624
|
+
static onSale = (id) => {
|
|
625
|
+
return {
|
|
626
|
+
id,
|
|
627
|
+
type: "on-sale",
|
|
628
|
+
strokeColor: blue
|
|
629
|
+
};
|
|
630
|
+
};
|
|
631
|
+
static primary = (id) => {
|
|
632
|
+
return {
|
|
633
|
+
id,
|
|
634
|
+
type: "any",
|
|
635
|
+
linkTo: id,
|
|
636
|
+
scale: 4,
|
|
637
|
+
strokeColor: redred,
|
|
638
|
+
linkColor: redred,
|
|
639
|
+
onTop: true,
|
|
640
|
+
isDetached: true
|
|
641
|
+
};
|
|
642
|
+
};
|
|
643
|
+
static minted = (id) => {
|
|
644
|
+
return {
|
|
645
|
+
id,
|
|
646
|
+
type: "any",
|
|
647
|
+
linkTo: id,
|
|
648
|
+
scale: 1.5,
|
|
649
|
+
strokeColor: redred,
|
|
650
|
+
linkColor: redred,
|
|
651
|
+
isDetached: true,
|
|
652
|
+
onTop: true
|
|
653
|
+
};
|
|
654
|
+
};
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
//#endregion
|
|
658
|
+
//#region src/sim/_interfaces.ts
|
|
659
|
+
var OpenGraphEventEmitter = class extends EventEmitter {};
|
|
660
|
+
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region src/sim/TransformCanvas.ts
|
|
663
|
+
const MIN_ZOOM = .1;
|
|
664
|
+
const MAX_ZOOM = 10;
|
|
665
|
+
const CLICK_THRESHOLD = 5;
|
|
666
|
+
const ANIMATION_SPEED = .07;
|
|
667
|
+
const DRAG_ANIMATION_SPEED = .5;
|
|
668
|
+
const ANIMATION_THRESHOLD = {
|
|
669
|
+
x: .5,
|
|
670
|
+
y: .5,
|
|
671
|
+
scale: .001
|
|
672
|
+
};
|
|
673
|
+
const MOMENTUM_DAMPING = .91;
|
|
674
|
+
const MIN_VELOCITY = .5;
|
|
675
|
+
var TransformCanvas = class {
|
|
676
|
+
canvas;
|
|
677
|
+
transform = {
|
|
678
|
+
x: 0,
|
|
679
|
+
y: 0,
|
|
680
|
+
scale: 1
|
|
681
|
+
};
|
|
682
|
+
targetTransform = {
|
|
683
|
+
x: 0,
|
|
684
|
+
y: 0,
|
|
685
|
+
scale: 1
|
|
686
|
+
};
|
|
687
|
+
isAnimating = false;
|
|
688
|
+
animationFrame = null;
|
|
689
|
+
focus = null;
|
|
690
|
+
offset = {
|
|
158
691
|
x: 0,
|
|
159
692
|
y: 0
|
|
160
693
|
};
|
|
@@ -302,8 +835,7 @@ var TransformCanvas = class {
|
|
|
302
835
|
y: this.lerp(prev.y, target.y, animationSpeed / this.dpr),
|
|
303
836
|
scale: this.lerp(prev.scale, target.scale, animationSpeed / this.dpr)
|
|
304
837
|
};
|
|
305
|
-
|
|
306
|
-
if (done) {
|
|
838
|
+
if (Math.abs(next.x - target.x) < ANIMATION_THRESHOLD.x && Math.abs(next.y - target.y) < ANIMATION_THRESHOLD.y && Math.abs(next.scale - target.scale) < ANIMATION_THRESHOLD.scale) {
|
|
307
839
|
this.transform = { ...target };
|
|
308
840
|
this.stopAnimation();
|
|
309
841
|
} else {
|
|
@@ -493,8 +1025,7 @@ var TransformCanvas = class {
|
|
|
493
1025
|
};
|
|
494
1026
|
}
|
|
495
1027
|
const [t1, t2] = Array.from(e.touches);
|
|
496
|
-
|
|
497
|
-
let newScale = dist / this.pinchStartDist * this.pinchStartScale;
|
|
1028
|
+
let newScale = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY) / this.pinchStartDist * this.pinchStartScale;
|
|
498
1029
|
newScale = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newScale));
|
|
499
1030
|
const centerX = (t1.clientX + t2.clientX) / 2;
|
|
500
1031
|
const centerY = (t1.clientY + t2.clientY) / 2;
|
|
@@ -591,8 +1122,7 @@ var TransformCanvas = class {
|
|
|
591
1122
|
this.focus = () => {
|
|
592
1123
|
const worldFocus = _focus();
|
|
593
1124
|
if (!worldFocus) return null;
|
|
594
|
-
|
|
595
|
-
return transform;
|
|
1125
|
+
return this.getTransformationFromWorld(worldFocus.x, worldFocus.y, worldFocus.scale);
|
|
596
1126
|
};
|
|
597
1127
|
this.startAnimation();
|
|
598
1128
|
}
|
|
@@ -606,404 +1136,78 @@ var TransformCanvas = class {
|
|
|
606
1136
|
cancelAnimationFrame(this.momentumFrame);
|
|
607
1137
|
this.momentumFrame = null;
|
|
608
1138
|
}
|
|
609
|
-
this.canvas.removeEventListener("wheel", this.handleWheel);
|
|
610
|
-
this.canvas.removeEventListener("mousedown", this.handleMouseDown);
|
|
611
|
-
this.canvas.removeEventListener("click", this.handleCanvasClick);
|
|
612
|
-
this.canvas.removeEventListener("touchstart", this.handleTouchStart);
|
|
613
|
-
this.canvas.removeEventListener("touchmove", this.handleTouchMove);
|
|
614
|
-
this.canvas.removeEventListener("touchend", this.handleTouchEnd);
|
|
615
|
-
this.canvas.removeEventListener("touchcancel", this.handleTouchEnd);
|
|
616
|
-
window.removeEventListener("mousemove", this.handleMouseMove);
|
|
617
|
-
window.removeEventListener("mouseup", this.handleMouseUp);
|
|
618
|
-
if (this.resizeObserver) {
|
|
619
|
-
this.resizeObserver.disconnect();
|
|
620
|
-
this.resizeObserver = null;
|
|
621
|
-
}
|
|
622
|
-
if (this.mediaQueryList) {
|
|
623
|
-
const updateDPRFromMediaQuery = () => {
|
|
624
|
-
this.updateDPR();
|
|
625
|
-
};
|
|
626
|
-
if (this.mediaQueryList.removeEventListener) this.mediaQueryList.removeEventListener("change", updateDPRFromMediaQuery);
|
|
627
|
-
this.mediaQueryList = null;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
getTransform() {
|
|
631
|
-
return { ...this.transform };
|
|
632
|
-
}
|
|
633
|
-
getTargetTransform() {
|
|
634
|
-
return { ...this.targetTransform };
|
|
635
|
-
}
|
|
636
|
-
getFocus() {
|
|
637
|
-
return this.focus ? { ...this.focus } : null;
|
|
638
|
-
}
|
|
639
|
-
setOffset(offset) {
|
|
640
|
-
this.offset = offset;
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
|
|
644
|
-
//#endregion
|
|
645
|
-
//#region src/util/canvas.ts
|
|
646
|
-
/**
|
|
647
|
-
* draws a circle on the canvas
|
|
648
|
-
* @param ctx - The canvas rendering context
|
|
649
|
-
* @param x - The x-coordinate of the circle's center
|
|
650
|
-
* @param y - The y-coordinate of the circle's center
|
|
651
|
-
* @param radius - The radius of the circle (default is 5)
|
|
652
|
-
* @param options - Optional parameters for styling the circle
|
|
653
|
-
* @param options.fill - Whether to fill the circle (default is true)
|
|
654
|
-
* @param options.fillStyle - The fill color of the circle
|
|
655
|
-
* @param options.stroke - Whether to stroke the circle (default is false)
|
|
656
|
-
* @param options.strokeStyle - The stroke color of the circle
|
|
657
|
-
* @param options.lineWidth - The width of the stroke (default is 0.2)
|
|
658
|
-
* @returns void
|
|
659
|
-
*/
|
|
660
|
-
function circle(ctx, x, y, radius = 5, options) {
|
|
661
|
-
const { fill = true, fillStyle, stroke = false, strokeStyle, lineWidth = .2 } = options || {};
|
|
662
|
-
ctx.save();
|
|
663
|
-
if (fillStyle !== void 0) ctx.fillStyle = fillStyle;
|
|
664
|
-
if (strokeStyle !== void 0) ctx.strokeStyle = strokeStyle;
|
|
665
|
-
if (lineWidth !== void 0) ctx.lineWidth = lineWidth;
|
|
666
|
-
ctx.beginPath();
|
|
667
|
-
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
|
668
|
-
ctx.closePath();
|
|
669
|
-
if (fill) ctx.fill();
|
|
670
|
-
if (stroke) ctx.stroke();
|
|
671
|
-
ctx.restore();
|
|
672
|
-
}
|
|
673
|
-
/**
|
|
674
|
-
* draws a rectangle on the canvas
|
|
675
|
-
* @param ctx - The canvas rendering context
|
|
676
|
-
* @param x - The x-coordinate of the rectangle's top-left corner
|
|
677
|
-
* @param y - The y-coordinate of the rectangle's top-left corner
|
|
678
|
-
* @param width - The width of the rectangle
|
|
679
|
-
* @param height - The height of the rectangle
|
|
680
|
-
* @param options - Optional parameters for styling the rectangle
|
|
681
|
-
* @param options.fill - Whether to fill the rectangle (default is true)
|
|
682
|
-
* @param options.fillStyle - The fill color of the rectangle
|
|
683
|
-
* @param options.stroke - Whether to stroke the rectangle (default is false)
|
|
684
|
-
* @param options.strokeStyle - The stroke color of the rectangle
|
|
685
|
-
* @param options.lineWidth - The width of the stroke (default is 0.2)
|
|
686
|
-
* @param options.borderRadius - The radius of the corners (default is 0)
|
|
687
|
-
* @returns void
|
|
688
|
-
*/
|
|
689
|
-
function rect(ctx, x, y, width, height, options) {
|
|
690
|
-
const { fill = true, fillStyle, stroke = false, strokeStyle, lineWidth = .2, borderRadius = 0 } = options || {};
|
|
691
|
-
ctx.save();
|
|
692
|
-
if (fillStyle !== void 0) ctx.fillStyle = fillStyle;
|
|
693
|
-
if (strokeStyle !== void 0) ctx.strokeStyle = strokeStyle;
|
|
694
|
-
if (lineWidth !== void 0) ctx.lineWidth = lineWidth;
|
|
695
|
-
const r = Math.min(borderRadius, width / 2, height / 2);
|
|
696
|
-
ctx.beginPath();
|
|
697
|
-
if (r > 0) {
|
|
698
|
-
ctx.moveTo(x + r, y);
|
|
699
|
-
ctx.lineTo(x + width - r, y);
|
|
700
|
-
ctx.quadraticCurveTo(x + width, y, x + width, y + r);
|
|
701
|
-
ctx.lineTo(x + width, y + height - r);
|
|
702
|
-
ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
|
|
703
|
-
ctx.lineTo(x + r, y + height);
|
|
704
|
-
ctx.quadraticCurveTo(x, y + height, x, y + height - r);
|
|
705
|
-
ctx.lineTo(x, y + r);
|
|
706
|
-
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
707
|
-
} else ctx.rect(x, y, width, height);
|
|
708
|
-
ctx.closePath();
|
|
709
|
-
if (fill) ctx.fill();
|
|
710
|
-
if (stroke) ctx.stroke();
|
|
711
|
-
ctx.restore();
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* draws an image on the canvas with optional border radius and opacity
|
|
715
|
-
* @param ctx - The canvas rendering context
|
|
716
|
-
* @param image - The HTMLImageElement to draw
|
|
717
|
-
* @param x - The x-coordinate of the image's top-left corner
|
|
718
|
-
* @param y - The y-coordinate of the image's top-left corner
|
|
719
|
-
* @param width - The width of the image
|
|
720
|
-
* @param height - The height of the image
|
|
721
|
-
* @param borderRadius - The radius of the corners (default is 0)
|
|
722
|
-
* @param opacity - The opacity of the image (default is 1.0)
|
|
723
|
-
* @param bgColor - Optional background color to fill the clipped area
|
|
724
|
-
* @returns void
|
|
725
|
-
*/
|
|
726
|
-
function img(ctx, image, x, y, width, height, borderRadius = 0, opacity = 1, bgColor) {
|
|
727
|
-
ctx.save();
|
|
728
|
-
ctx.beginPath();
|
|
729
|
-
ctx.globalAlpha = 1;
|
|
730
|
-
if (borderRadius > 0) {
|
|
731
|
-
const r = Math.min(borderRadius, width / 2, height / 2);
|
|
732
|
-
ctx.moveTo(x + r, y);
|
|
733
|
-
ctx.lineTo(x + width - r, y);
|
|
734
|
-
ctx.quadraticCurveTo(x + width, y, x + width, y + r);
|
|
735
|
-
ctx.lineTo(x + width, y + height - r);
|
|
736
|
-
ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
|
|
737
|
-
ctx.lineTo(x + r, y + height);
|
|
738
|
-
ctx.quadraticCurveTo(x, y + height, x, y + height - r);
|
|
739
|
-
ctx.lineTo(x, y + r);
|
|
740
|
-
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
741
|
-
} else ctx.rect(x, y, width, height);
|
|
742
|
-
ctx.closePath();
|
|
743
|
-
if (bgColor && opacity < 1) {
|
|
744
|
-
ctx.save();
|
|
745
|
-
ctx.fillStyle = bgColor;
|
|
746
|
-
ctx.fill();
|
|
747
|
-
ctx.restore();
|
|
748
|
-
}
|
|
749
|
-
ctx.clip();
|
|
750
|
-
ctx.globalAlpha = opacity;
|
|
751
|
-
ctx.drawImage(image, x, y, width, height);
|
|
752
|
-
ctx.restore();
|
|
753
|
-
}
|
|
754
|
-
function hexagon(ctx, x, y, radius, options) {
|
|
755
|
-
const { fill = true, fillStyle, stroke = false, strokeStyle, lineWidth = .2, rotation = 0, borderRadius = 0 } = options || {};
|
|
756
|
-
ctx.save();
|
|
757
|
-
if (fillStyle !== void 0) ctx.fillStyle = fillStyle;
|
|
758
|
-
if (strokeStyle !== void 0) ctx.strokeStyle = strokeStyle;
|
|
759
|
-
if (lineWidth !== void 0) ctx.lineWidth = lineWidth;
|
|
760
|
-
const sides = 6;
|
|
761
|
-
const angleStep = Math.PI * 2 / sides;
|
|
762
|
-
ctx.beginPath();
|
|
763
|
-
const points = [];
|
|
764
|
-
for (let i = 0; i < sides; i++) {
|
|
765
|
-
const angle = rotation + i * angleStep;
|
|
766
|
-
points.push({
|
|
767
|
-
x: x + radius * Math.cos(angle),
|
|
768
|
-
y: y + radius * Math.sin(angle)
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
if (borderRadius > 0) {
|
|
772
|
-
const maxBorderRadius = Math.min(borderRadius, radius / 3);
|
|
773
|
-
for (let i = 0; i < sides; i++) {
|
|
774
|
-
const current = points[i];
|
|
775
|
-
const next = points[(i + 1) % sides];
|
|
776
|
-
const prev = points[(i - 1 + sides) % sides];
|
|
777
|
-
const toPrev = {
|
|
778
|
-
x: prev.x - current.x,
|
|
779
|
-
y: prev.y - current.y
|
|
780
|
-
};
|
|
781
|
-
const toNext = {
|
|
782
|
-
x: next.x - current.x,
|
|
783
|
-
y: next.y - current.y
|
|
784
|
-
};
|
|
785
|
-
const lenPrev = Math.sqrt(toPrev.x * toPrev.x + toPrev.y * toPrev.y);
|
|
786
|
-
const lenNext = Math.sqrt(toNext.x * toNext.x + toNext.y * toNext.y);
|
|
787
|
-
const normPrev = {
|
|
788
|
-
x: toPrev.x / lenPrev,
|
|
789
|
-
y: toPrev.y / lenPrev
|
|
790
|
-
};
|
|
791
|
-
const normNext = {
|
|
792
|
-
x: toNext.x / lenNext,
|
|
793
|
-
y: toNext.y / lenNext
|
|
794
|
-
};
|
|
795
|
-
const cpPrev = {
|
|
796
|
-
x: current.x + normPrev.x * maxBorderRadius,
|
|
797
|
-
y: current.y + normPrev.y * maxBorderRadius
|
|
798
|
-
};
|
|
799
|
-
const cpNext = {
|
|
800
|
-
x: current.x + normNext.x * maxBorderRadius,
|
|
801
|
-
y: current.y + normNext.y * maxBorderRadius
|
|
802
|
-
};
|
|
803
|
-
if (i === 0) ctx.moveTo(cpPrev.x, cpPrev.y);
|
|
804
|
-
else ctx.lineTo(cpPrev.x, cpPrev.y);
|
|
805
|
-
ctx.quadraticCurveTo(current.x, current.y, cpNext.x, cpNext.y);
|
|
806
|
-
}
|
|
807
|
-
} else {
|
|
808
|
-
ctx.moveTo(points[0].x, points[0].y);
|
|
809
|
-
for (let i = 1; i < sides; i++) ctx.lineTo(points[i].x, points[i].y);
|
|
810
|
-
}
|
|
811
|
-
ctx.closePath();
|
|
812
|
-
if (fill) ctx.fill();
|
|
813
|
-
if (stroke) ctx.stroke();
|
|
814
|
-
ctx.restore();
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
//#endregion
|
|
818
|
-
//#region src/util/color.ts
|
|
819
|
-
/**
|
|
820
|
-
* Some utility functions to handle colors
|
|
821
|
-
*/
|
|
822
|
-
function color(rgb) {
|
|
823
|
-
const colorHandler = function(arg) {
|
|
824
|
-
if (typeof arg === "number") return `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${arg})`;
|
|
825
|
-
if (typeof arg === "function") {
|
|
826
|
-
const transformedRGB = arg(rgb);
|
|
827
|
-
return color(transformedRGB);
|
|
828
|
-
}
|
|
829
|
-
return `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`;
|
|
830
|
-
};
|
|
831
|
-
colorHandler.rgb = rgb;
|
|
832
|
-
return colorHandler;
|
|
833
|
-
}
|
|
834
|
-
/**
|
|
835
|
-
* Dims a color to white or black
|
|
836
|
-
* @param color An array of 3 numbers representing RGB values (0-255)
|
|
837
|
-
* @param dimFactor A value between 0 and 1 where 0 is completely black and 1 is the original color
|
|
838
|
-
* @returns A new array with the dimmed RGB values
|
|
839
|
-
*/
|
|
840
|
-
function dim(factor = .6, white = true) {
|
|
841
|
-
const base = white ? 255 : 0;
|
|
842
|
-
return function(rgb) {
|
|
843
|
-
return [
|
|
844
|
-
Math.round(rgb[0] + (base - rgb[0]) * (1 - factor)),
|
|
845
|
-
Math.round(rgb[1] + (base - rgb[1]) * (1 - factor)),
|
|
846
|
-
Math.round(rgb[2] + (base - rgb[2]) * (1 - factor))
|
|
847
|
-
];
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
//#endregion
|
|
852
|
-
//#region src/util/data.ts
|
|
853
|
-
const images = [];
|
|
854
|
-
function generateTree(maxNodes, maxChildren) {
|
|
855
|
-
const nodes = [];
|
|
856
|
-
const links = [];
|
|
857
|
-
let index$1 = 0;
|
|
858
|
-
function createNode(label) {
|
|
859
|
-
const isRoot = label === VOID_ROOT_ID;
|
|
860
|
-
const idx = isRoot ? VOID_ROOT_ID : index$1++;
|
|
861
|
-
return {
|
|
862
|
-
id: idx.toString(),
|
|
863
|
-
label,
|
|
864
|
-
imgSrc: isRoot ? void 0 : images[index$1 % images.length]
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
const root = createNode(VOID_ROOT_ID);
|
|
868
|
-
nodes.push(root);
|
|
869
|
-
const queue = [root];
|
|
870
|
-
while (queue.length > 0 && nodes.length < maxNodes) {
|
|
871
|
-
const parent = queue.shift();
|
|
872
|
-
const rand = Math.random();
|
|
873
|
-
const biased = Math.floor(Math.pow(rand, 2) * (maxChildren + 1));
|
|
874
|
-
const childrenCount = Math.min(biased, maxChildren);
|
|
875
|
-
for (let i = 0; i < childrenCount; i++) {
|
|
876
|
-
if (nodes.length >= maxNodes) break;
|
|
877
|
-
const child = createNode(`Node ${nodes.length}`);
|
|
878
|
-
nodes.push(child);
|
|
879
|
-
links.push({
|
|
880
|
-
source: parent.id,
|
|
881
|
-
target: child.id
|
|
882
|
-
});
|
|
883
|
-
queue.push(child);
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
return {
|
|
887
|
-
nodes,
|
|
888
|
-
links
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
function getPrunedData(startId, nodes, links, highlights = []) {
|
|
892
|
-
const nodesById = Object.fromEntries(nodes.map((node) => [node.id, node]));
|
|
893
|
-
const visibleNodes = [];
|
|
894
|
-
const visibleLinks = [];
|
|
895
|
-
const visited = new Set();
|
|
896
|
-
(function traverseTree(node = nodesById[startId]) {
|
|
897
|
-
if (!node || visited.has(node.id)) return;
|
|
898
|
-
visited.add(node.id);
|
|
899
|
-
if (node.status === "LIQUIDATED" && !highlights.find((h) => h.id === node.id)) return;
|
|
900
|
-
visibleNodes.push(node);
|
|
901
|
-
if (node?.state?.collapsed) return;
|
|
902
|
-
const childLinks = links.filter((l) => (isSimNode(l.source) ? l.source.id : l.source) === node.id);
|
|
903
|
-
for (const link of childLinks) {
|
|
904
|
-
const targetNode = isSimNode(link.target) ? link.target : nodesById[link.target.toString()];
|
|
905
|
-
if (targetNode?.status === "LIQUIDATED" && !highlights.find((h) => h.id === targetNode.id)) continue;
|
|
906
|
-
visibleLinks.push(link);
|
|
907
|
-
traverseTree(targetNode);
|
|
908
|
-
}
|
|
909
|
-
})();
|
|
910
|
-
return {
|
|
911
|
-
nodes: visibleNodes,
|
|
912
|
-
links: visibleLinks
|
|
913
|
-
};
|
|
914
|
-
}
|
|
915
|
-
/**
|
|
916
|
-
* Automatically identifies root nodes and builds a nested structure
|
|
917
|
-
* @param nodes Array of raw nodes
|
|
918
|
-
* @param links Array of links between nodes
|
|
919
|
-
* @returns Array of nested nodes starting from identified roots
|
|
920
|
-
*/
|
|
921
|
-
function buildTreeFromGraphData(nodes, links) {
|
|
922
|
-
const nodeMap = new Map();
|
|
923
|
-
nodes.forEach((node) => nodeMap.set(node.id, node));
|
|
924
|
-
const childrenMap = new Map();
|
|
925
|
-
const parentMap = new Map();
|
|
926
|
-
nodes.forEach((node) => {
|
|
927
|
-
childrenMap.set(node.id, []);
|
|
928
|
-
parentMap.set(node.id, []);
|
|
929
|
-
});
|
|
930
|
-
links.forEach((link) => {
|
|
931
|
-
if (nodeMap.has(link.source) && nodeMap.has(link.target)) {
|
|
932
|
-
const children = childrenMap.get(link.source) || [];
|
|
933
|
-
if (!children.includes(link.target)) {
|
|
934
|
-
children.push(link.target);
|
|
935
|
-
childrenMap.set(link.source, children);
|
|
936
|
-
}
|
|
937
|
-
const parents = parentMap.get(link.target) || [];
|
|
938
|
-
if (!parents.includes(link.source)) {
|
|
939
|
-
parents.push(link.source);
|
|
940
|
-
parentMap.set(link.target, parents);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
});
|
|
944
|
-
const rootHashes = [];
|
|
945
|
-
nodeMap.forEach((_, hash) => {
|
|
946
|
-
const parents = parentMap.get(hash) || [];
|
|
947
|
-
if (parents.length === 0) rootHashes.push(hash);
|
|
948
|
-
});
|
|
949
|
-
const buildNode = (hash, visited = new Set()) => {
|
|
950
|
-
if (visited.has(hash)) return null;
|
|
951
|
-
visited.add(hash);
|
|
952
|
-
const node = nodeMap.get(hash);
|
|
953
|
-
if (!node) return null;
|
|
954
|
-
const childHashes = childrenMap.get(hash) || [];
|
|
955
|
-
const nestedChildren = [];
|
|
956
|
-
childHashes.forEach((childHash) => {
|
|
957
|
-
const childNode = buildNode(childHash, new Set([...visited]));
|
|
958
|
-
if (childNode) nestedChildren.push(childNode);
|
|
959
|
-
});
|
|
960
|
-
return {
|
|
961
|
-
...node,
|
|
962
|
-
children: nestedChildren
|
|
963
|
-
};
|
|
964
|
-
};
|
|
965
|
-
const result = [];
|
|
966
|
-
rootHashes.forEach((rootHash) => {
|
|
967
|
-
const nestedRoot = buildNode(rootHash);
|
|
968
|
-
if (nestedRoot) result.push(nestedRoot);
|
|
969
|
-
});
|
|
970
|
-
const processedNodes = new Set();
|
|
971
|
-
const markProcessed = (node) => {
|
|
972
|
-
processedNodes.add(node.id);
|
|
973
|
-
node.children.forEach(markProcessed);
|
|
974
|
-
};
|
|
975
|
-
result.forEach(markProcessed);
|
|
976
|
-
nodeMap.forEach((_, hash) => {
|
|
977
|
-
if (!processedNodes.has(hash)) {
|
|
978
|
-
const orphanedRoot = buildNode(hash);
|
|
979
|
-
if (orphanedRoot) result.push(orphanedRoot);
|
|
1139
|
+
this.canvas.removeEventListener("wheel", this.handleWheel);
|
|
1140
|
+
this.canvas.removeEventListener("mousedown", this.handleMouseDown);
|
|
1141
|
+
this.canvas.removeEventListener("click", this.handleCanvasClick);
|
|
1142
|
+
this.canvas.removeEventListener("touchstart", this.handleTouchStart);
|
|
1143
|
+
this.canvas.removeEventListener("touchmove", this.handleTouchMove);
|
|
1144
|
+
this.canvas.removeEventListener("touchend", this.handleTouchEnd);
|
|
1145
|
+
this.canvas.removeEventListener("touchcancel", this.handleTouchEnd);
|
|
1146
|
+
window.removeEventListener("mousemove", this.handleMouseMove);
|
|
1147
|
+
window.removeEventListener("mouseup", this.handleMouseUp);
|
|
1148
|
+
if (this.resizeObserver) {
|
|
1149
|
+
this.resizeObserver.disconnect();
|
|
1150
|
+
this.resizeObserver = null;
|
|
1151
|
+
}
|
|
1152
|
+
if (this.mediaQueryList) {
|
|
1153
|
+
const updateDPRFromMediaQuery = () => {
|
|
1154
|
+
this.updateDPR();
|
|
1155
|
+
};
|
|
1156
|
+
if (this.mediaQueryList.removeEventListener) this.mediaQueryList.removeEventListener("change", updateDPRFromMediaQuery);
|
|
1157
|
+
this.mediaQueryList = null;
|
|
980
1158
|
}
|
|
981
|
-
});
|
|
982
|
-
return result;
|
|
983
|
-
}
|
|
984
|
-
/**
|
|
985
|
-
* Recursively retrieves all parents of a node from a graph data structure
|
|
986
|
-
* @param {string} nodeHash - The hash of the node to find parents for
|
|
987
|
-
* @param {RawNode[]} nodes - Array of nodes in the graph
|
|
988
|
-
* @param {RawLink[]} links - Array of links connecting the nodes
|
|
989
|
-
* @returns {RawNode[]} - Array of parent nodes
|
|
990
|
-
*/
|
|
991
|
-
function searchParents(nodeHash, nodes, links) {
|
|
992
|
-
const visited = new Set();
|
|
993
|
-
function findParents(hash) {
|
|
994
|
-
if (visited.has(hash)) return [];
|
|
995
|
-
visited.add(hash);
|
|
996
|
-
const immediateParents = links.filter((link) => link.target === hash).map((link) => link.source);
|
|
997
|
-
const parentNodes = nodes.filter((node) => immediateParents.includes(node.id));
|
|
998
|
-
const ancestorNodes = immediateParents.flatMap((parentHash) => findParents(parentHash));
|
|
999
|
-
return [...parentNodes, ...ancestorNodes];
|
|
1000
1159
|
}
|
|
1001
|
-
|
|
1002
|
-
}
|
|
1160
|
+
getTransform() {
|
|
1161
|
+
return { ...this.transform };
|
|
1162
|
+
}
|
|
1163
|
+
getTargetTransform() {
|
|
1164
|
+
return { ...this.targetTransform };
|
|
1165
|
+
}
|
|
1166
|
+
getFocus() {
|
|
1167
|
+
return this.focus ? { ...this.focus } : null;
|
|
1168
|
+
}
|
|
1169
|
+
getIsDragging() {
|
|
1170
|
+
return this.isDragging;
|
|
1171
|
+
}
|
|
1172
|
+
setOffset(offset) {
|
|
1173
|
+
this.offset = offset;
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1003
1176
|
|
|
1004
1177
|
//#endregion
|
|
1005
|
-
//#region src/
|
|
1006
|
-
|
|
1178
|
+
//#region src/_types.ts
|
|
1179
|
+
function newGraphData(data) {
|
|
1180
|
+
return {
|
|
1181
|
+
nodes: new MappedArray({
|
|
1182
|
+
id: {
|
|
1183
|
+
getKey: (node) => node.id,
|
|
1184
|
+
multi: false
|
|
1185
|
+
},
|
|
1186
|
+
collapsed: {
|
|
1187
|
+
getKey: (node) => (!!node.state?.collapsed).toString(),
|
|
1188
|
+
multi: true
|
|
1189
|
+
},
|
|
1190
|
+
groupNode: {
|
|
1191
|
+
getKey: (node) => (!!node.state?.groupNode).toString(),
|
|
1192
|
+
multi: true
|
|
1193
|
+
}
|
|
1194
|
+
}, data?.nodes),
|
|
1195
|
+
links: new MappedArray({
|
|
1196
|
+
targetId: {
|
|
1197
|
+
getKey: (link) => (isSimNode(link.target) ? link.target.id : link.target).toString(),
|
|
1198
|
+
multi: false
|
|
1199
|
+
},
|
|
1200
|
+
linkId: {
|
|
1201
|
+
getKey: (link) => getLinkId(link),
|
|
1202
|
+
multi: false
|
|
1203
|
+
},
|
|
1204
|
+
sourceId: {
|
|
1205
|
+
getKey: (link) => (isSimNode(link.source) ? link.source.id : link.source).toString(),
|
|
1206
|
+
multi: true
|
|
1207
|
+
}
|
|
1208
|
+
}, data?.links)
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1007
1211
|
|
|
1008
1212
|
//#endregion
|
|
1009
1213
|
//#region src/util/math.ts
|
|
@@ -1017,60 +1221,6 @@ function getAngle(cx, cy, x, y) {
|
|
|
1017
1221
|
return Math.atan2(y - cy, x - cx);
|
|
1018
1222
|
}
|
|
1019
1223
|
|
|
1020
|
-
//#endregion
|
|
1021
|
-
//#region src/util/highlights.ts
|
|
1022
|
-
const red = [
|
|
1023
|
-
238,
|
|
1024
|
-
125,
|
|
1025
|
-
121
|
|
1026
|
-
];
|
|
1027
|
-
const redred = [
|
|
1028
|
-
255,
|
|
1029
|
-
0,
|
|
1030
|
-
0
|
|
1031
|
-
];
|
|
1032
|
-
var Highlight = class {
|
|
1033
|
-
static owner = (id) => {
|
|
1034
|
-
return {
|
|
1035
|
-
id,
|
|
1036
|
-
strokeColor: red
|
|
1037
|
-
};
|
|
1038
|
-
};
|
|
1039
|
-
static primary = (id) => {
|
|
1040
|
-
return {
|
|
1041
|
-
id,
|
|
1042
|
-
linkTo: id,
|
|
1043
|
-
scale: 4,
|
|
1044
|
-
strokeColor: redred,
|
|
1045
|
-
linkColor: redred,
|
|
1046
|
-
onTop: true,
|
|
1047
|
-
isDetached: true
|
|
1048
|
-
};
|
|
1049
|
-
};
|
|
1050
|
-
static evolved = (id) => {
|
|
1051
|
-
return {
|
|
1052
|
-
id,
|
|
1053
|
-
linkTo: id,
|
|
1054
|
-
scale: 2.1,
|
|
1055
|
-
strokeColor: redred,
|
|
1056
|
-
linkColor: redred,
|
|
1057
|
-
onTop: true,
|
|
1058
|
-
isDetached: true
|
|
1059
|
-
};
|
|
1060
|
-
};
|
|
1061
|
-
static minted = (id) => {
|
|
1062
|
-
return {
|
|
1063
|
-
id,
|
|
1064
|
-
linkTo: id,
|
|
1065
|
-
scale: 1.5,
|
|
1066
|
-
strokeColor: redred,
|
|
1067
|
-
linkColor: redred,
|
|
1068
|
-
isDetached: true,
|
|
1069
|
-
onTop: true
|
|
1070
|
-
};
|
|
1071
|
-
};
|
|
1072
|
-
};
|
|
1073
|
-
|
|
1074
1224
|
//#endregion
|
|
1075
1225
|
//#region src/sim/asymmetric-link.ts
|
|
1076
1226
|
function constant(x) {
|
|
@@ -1186,12 +1336,64 @@ function asymmetricLinks(links) {
|
|
|
1186
1336
|
//#endregion
|
|
1187
1337
|
//#region src/util/hash.ts
|
|
1188
1338
|
function quickHash(data) {
|
|
1189
|
-
return
|
|
1339
|
+
return xorshiftString(data).toString(16);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
//#endregion
|
|
1343
|
+
//#region src/sim/measurements.ts
|
|
1344
|
+
/**
|
|
1345
|
+
* @dev Temp utility to make performance measurements on the graph.
|
|
1346
|
+
*/
|
|
1347
|
+
const arr = {
|
|
1348
|
+
sum(A, getV = (v) => v) {
|
|
1349
|
+
return A.reduce((acc, val) => acc + getV(val), 0);
|
|
1350
|
+
},
|
|
1351
|
+
avg(A, getV = (v) => v) {
|
|
1352
|
+
return this.sum(A, getV) / A.length;
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
const Measure = {
|
|
1356
|
+
measures: {},
|
|
1357
|
+
samples: {},
|
|
1358
|
+
collect(name, value) {
|
|
1359
|
+
if (!this.samples[name]) this.samples[name] = [];
|
|
1360
|
+
this.samples[name].push(value);
|
|
1361
|
+
},
|
|
1362
|
+
start(name) {
|
|
1363
|
+
this.measures[name] = performance.now();
|
|
1364
|
+
},
|
|
1365
|
+
end(name, ignoreError = false) {
|
|
1366
|
+
if (!(name in this.measures)) {
|
|
1367
|
+
if (ignoreError) return;
|
|
1368
|
+
throw `invalid timer end on "${name}"`;
|
|
1369
|
+
}
|
|
1370
|
+
this.collect(name, performance.now() - this.measures[name]);
|
|
1371
|
+
},
|
|
1372
|
+
export() {
|
|
1373
|
+
const samples = Object.entries(this.samples);
|
|
1374
|
+
const rows = [samples.map((s) => s[0]).join(",")];
|
|
1375
|
+
rows.push(samples.map((s) => arr.avg(s[1])).join(","));
|
|
1376
|
+
downloadAsCsvFile("samples.csv", rows.join("\n"));
|
|
1377
|
+
},
|
|
1378
|
+
getSamples() {
|
|
1379
|
+
return this.samples;
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
function downloadAsCsvFile(filename, content) {
|
|
1383
|
+
const blob = new Blob([content], { type: "text/csv" });
|
|
1384
|
+
const url = URL.createObjectURL(blob);
|
|
1385
|
+
const a = document.createElement("a");
|
|
1386
|
+
a.href = url;
|
|
1387
|
+
a.download = filename.endsWith(".csv") ? filename : filename + ".csv";
|
|
1388
|
+
document.body.appendChild(a);
|
|
1389
|
+
a.click();
|
|
1390
|
+
document.body.removeChild(a);
|
|
1391
|
+
URL.revokeObjectURL(url);
|
|
1190
1392
|
}
|
|
1191
1393
|
|
|
1192
1394
|
//#endregion
|
|
1193
1395
|
//#region src/sim/OpenGraphSimulation.ts
|
|
1194
|
-
const
|
|
1396
|
+
const RENDER_EMITTER_NODES = false;
|
|
1195
1397
|
const INITIAL_RADIUS = 300;
|
|
1196
1398
|
const INCREMENTAL = 200;
|
|
1197
1399
|
function getRadius(depth) {
|
|
@@ -1206,39 +1408,37 @@ var OpenGraphSimulation = class {
|
|
|
1206
1408
|
canvas;
|
|
1207
1409
|
transformCanvas;
|
|
1208
1410
|
theme;
|
|
1411
|
+
groupRootOrphans = false;
|
|
1209
1412
|
rawData;
|
|
1210
1413
|
emitter;
|
|
1211
1414
|
translate = {
|
|
1212
1415
|
x: 0,
|
|
1213
1416
|
y: 0
|
|
1214
1417
|
};
|
|
1215
|
-
data =
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
};
|
|
1219
|
-
prunedData = {
|
|
1220
|
-
nodes: [],
|
|
1221
|
-
links: []
|
|
1222
|
-
};
|
|
1223
|
-
subGraph = {
|
|
1224
|
-
nodes: [],
|
|
1225
|
-
links: []
|
|
1226
|
-
};
|
|
1418
|
+
data = newGraphData();
|
|
1419
|
+
prunedData = newGraphData();
|
|
1420
|
+
subGraph = newGraphData();
|
|
1227
1421
|
rootId = "";
|
|
1228
1422
|
simulation = null;
|
|
1229
1423
|
clusterSizeRange = [0, 1];
|
|
1424
|
+
emitterClusterSizeRange = [0, 1];
|
|
1230
1425
|
maxDepth = 0;
|
|
1231
1426
|
isTicking = false;
|
|
1427
|
+
isDrawing = false;
|
|
1232
1428
|
tickCount = 0;
|
|
1233
1429
|
loadNodeImage;
|
|
1234
|
-
imageCache = new Map();
|
|
1430
|
+
imageCache = /* @__PURE__ */ new Map();
|
|
1235
1431
|
rootImages = [];
|
|
1236
1432
|
hideThumbnails = false;
|
|
1237
1433
|
noInteraction = false;
|
|
1238
1434
|
lockedNodeId;
|
|
1239
1435
|
selectedNode = null;
|
|
1240
1436
|
hoveredNode = null;
|
|
1241
|
-
highlights
|
|
1437
|
+
highlights;
|
|
1438
|
+
nodeVisibility = "all";
|
|
1439
|
+
secondaryNodes = /* @__PURE__ */ new Map();
|
|
1440
|
+
emittedNodes = [];
|
|
1441
|
+
openedGroups = [];
|
|
1242
1442
|
renderLayers = {
|
|
1243
1443
|
links: {
|
|
1244
1444
|
regular: [],
|
|
@@ -1274,6 +1474,18 @@ var OpenGraphSimulation = class {
|
|
|
1274
1474
|
this.rootImages[idx] = img$1;
|
|
1275
1475
|
});
|
|
1276
1476
|
});
|
|
1477
|
+
this.groupRootOrphans = props.groupRootOrphans || false;
|
|
1478
|
+
this.nodeVisibility = props.nodeVisibility || "all";
|
|
1479
|
+
this.highlights = new MappedArray({
|
|
1480
|
+
nodeId: {
|
|
1481
|
+
getKey: (highlight) => highlight.id,
|
|
1482
|
+
multi: false
|
|
1483
|
+
},
|
|
1484
|
+
detached: {
|
|
1485
|
+
getKey: (highlight) => (highlight.isDetached || false).toString(),
|
|
1486
|
+
multi: true
|
|
1487
|
+
}
|
|
1488
|
+
}, props.highlights || []);
|
|
1277
1489
|
}
|
|
1278
1490
|
get center() {
|
|
1279
1491
|
return {
|
|
@@ -1289,8 +1501,7 @@ var OpenGraphSimulation = class {
|
|
|
1289
1501
|
};
|
|
1290
1502
|
}
|
|
1291
1503
|
getNodeAtPosition = (cx, cy) => {
|
|
1292
|
-
const
|
|
1293
|
-
const { x: tx, y: ty, scale } = transform;
|
|
1504
|
+
const { x: tx, y: ty, scale } = this.transformCanvas.getTransform();
|
|
1294
1505
|
const dpi = devicePixelRatio || 1;
|
|
1295
1506
|
const canvasX = cx * dpi;
|
|
1296
1507
|
const canvasY = cy * dpi;
|
|
@@ -1299,13 +1510,13 @@ var OpenGraphSimulation = class {
|
|
|
1299
1510
|
const graphX = scaledX - this.origin.x;
|
|
1300
1511
|
const graphY = scaledY - this.origin.y;
|
|
1301
1512
|
const candidates = [];
|
|
1302
|
-
for (let node of this.
|
|
1513
|
+
for (let node of this.prunedData.nodes.values) {
|
|
1303
1514
|
const r = this.getNodeSize(node.id) / 2;
|
|
1304
1515
|
if (node.x == null || node.y == null) continue;
|
|
1305
1516
|
const dx = node.x - graphX;
|
|
1306
1517
|
const dy = node.y - graphY;
|
|
1307
1518
|
if (dx * dx + dy * dy < r * r) {
|
|
1308
|
-
if (!this.prunedData.nodes.
|
|
1519
|
+
if (!this.prunedData.nodes.maps.id.get(node.id)) continue;
|
|
1309
1520
|
candidates.push(node);
|
|
1310
1521
|
}
|
|
1311
1522
|
}
|
|
@@ -1339,8 +1550,7 @@ var OpenGraphSimulation = class {
|
|
|
1339
1550
|
};
|
|
1340
1551
|
};
|
|
1341
1552
|
screenToWorld(_x, _y) {
|
|
1342
|
-
const
|
|
1343
|
-
const { x: tx, y: ty, scale } = transform;
|
|
1553
|
+
const { x: tx, y: ty, scale } = this.transformCanvas.getTransform();
|
|
1344
1554
|
const dpi = devicePixelRatio || 1;
|
|
1345
1555
|
const canvasX = _x * dpi;
|
|
1346
1556
|
const canvasY = _y * dpi;
|
|
@@ -1354,8 +1564,7 @@ var OpenGraphSimulation = class {
|
|
|
1354
1564
|
};
|
|
1355
1565
|
}
|
|
1356
1566
|
worldToScreen(worldX, worldY) {
|
|
1357
|
-
const
|
|
1358
|
-
const { x: tx, y: ty, scale } = transform;
|
|
1567
|
+
const { x: tx, y: ty, scale } = this.transformCanvas.getTransform();
|
|
1359
1568
|
const dpi = devicePixelRatio || 1;
|
|
1360
1569
|
const scaledX = (worldX + this.origin.x) * scale;
|
|
1361
1570
|
const scaledY = (worldY + this.origin.y) * scale;
|
|
@@ -1371,34 +1580,69 @@ var OpenGraphSimulation = class {
|
|
|
1371
1580
|
handleClick = (x, y) => {
|
|
1372
1581
|
let node = this.getNodeAtPosition(x, y);
|
|
1373
1582
|
if (node?.state?.sessionNode) return;
|
|
1583
|
+
if (node?.state?.groupNode) {
|
|
1584
|
+
this.data.nodes.values.forEach((n) => {
|
|
1585
|
+
if (node?.state?.groupContent?.includes(n.id)) {
|
|
1586
|
+
const circlePos = getRadialPoint(getRadius(0), this.rootNode?.x, this.rootNode?.y);
|
|
1587
|
+
n.x = circlePos.x;
|
|
1588
|
+
n.y = circlePos.y;
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
this.openedGroups.push(node.id);
|
|
1592
|
+
this.updateHighlights();
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1374
1595
|
if (this.lockedNodeId && !node) node = this.getNodeById(this.lockedNodeId);
|
|
1375
|
-
this.
|
|
1596
|
+
if (node === this.selectedNode) return;
|
|
1597
|
+
if (node?.state?.emitterNode && RENDER_EMITTER_NODES);
|
|
1598
|
+
else this.handleClickNode(node);
|
|
1599
|
+
};
|
|
1600
|
+
emitNodeAt = (depth) => {
|
|
1601
|
+
const nodes = this.secondaryNodes[depth].filter((n) => !this.emittedNodes.includes(n.id)) || [];
|
|
1602
|
+
const randomIndex = Math.floor(Math.random() * nodes.length);
|
|
1603
|
+
if (randomIndex < 0 || randomIndex >= nodes.length) return;
|
|
1604
|
+
const depthNode = this.emitterNodes[depth];
|
|
1605
|
+
const node = nodes[randomIndex];
|
|
1606
|
+
node.x = depthNode.x;
|
|
1607
|
+
node.y = depthNode.y;
|
|
1608
|
+
const parents = getAllParentsUntil(node.id, this.data.links, this.rootId);
|
|
1609
|
+
this.emittedNodes.push(node.id);
|
|
1610
|
+
node.state.collapsed = false;
|
|
1611
|
+
if (parents.length > 0) {
|
|
1612
|
+
parents.forEach((parentId) => {
|
|
1613
|
+
const parentNode = this.data.nodes.maps.id.get(parentId);
|
|
1614
|
+
if (parentNode) {
|
|
1615
|
+
if (parentNode.state) parentNode.state.collapsed = false;
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
this.emittedNodes.push(...parents);
|
|
1619
|
+
}
|
|
1620
|
+
console.log("Emitting node at depth", depth, ":", node.id);
|
|
1621
|
+
this.updateHighlights();
|
|
1376
1622
|
};
|
|
1377
1623
|
handleClickNode = (node, options = {
|
|
1378
1624
|
noToggle: false,
|
|
1379
|
-
triggerFocus: false
|
|
1625
|
+
triggerFocus: false,
|
|
1626
|
+
triggerRestart: false
|
|
1380
1627
|
}) => {
|
|
1381
1628
|
let wasOpened = false;
|
|
1382
1629
|
if (node) {
|
|
1383
1630
|
if (node.id === this.rootId) {
|
|
1384
1631
|
this.selectedNode = null;
|
|
1385
1632
|
this.emitter.emit("selected-node-changed", null);
|
|
1386
|
-
this.subGraph =
|
|
1387
|
-
nodes: [],
|
|
1388
|
-
links: []
|
|
1389
|
-
};
|
|
1633
|
+
this.subGraph = newGraphData();
|
|
1390
1634
|
this.updateRenderLayers();
|
|
1391
1635
|
this.updateScene();
|
|
1392
1636
|
return;
|
|
1393
1637
|
}
|
|
1394
1638
|
if (node.state) {
|
|
1395
1639
|
const children = getChildren(node.id, this.data.links);
|
|
1396
|
-
if (children.length > 0
|
|
1640
|
+
if (children.length > 0) {
|
|
1397
1641
|
if (this.selectedNode?.id !== node.id) {
|
|
1398
1642
|
if (node.state.collapsed) wasOpened = true;
|
|
1399
1643
|
node.state.collapsed = false;
|
|
1400
|
-
} else node.state.collapsed = !node.state.collapsed;
|
|
1401
|
-
if (!node.state.collapsed) {
|
|
1644
|
+
} else if (!options.noToggle) node.state.collapsed = !node.state.collapsed;
|
|
1645
|
+
if (!node.state.collapsed && wasOpened) {
|
|
1402
1646
|
const clusterDistance = 100;
|
|
1403
1647
|
const clusterRadius = 50;
|
|
1404
1648
|
const parentX = node.x || this.center.x;
|
|
@@ -1408,36 +1652,24 @@ var OpenGraphSimulation = class {
|
|
|
1408
1652
|
const length = Math.sqrt(dirX * dirX + dirY * dirY) || 1;
|
|
1409
1653
|
const normX = dirX / length;
|
|
1410
1654
|
const normY = dirY / length;
|
|
1411
|
-
|
|
1412
|
-
|
|
1655
|
+
parentX + normX * clusterDistance;
|
|
1656
|
+
parentY + normY * clusterDistance;
|
|
1413
1657
|
children.forEach((childId) => {
|
|
1414
|
-
const childNode = this.data.nodes.
|
|
1658
|
+
const childNode = this.data.nodes.maps.id.get(childId);
|
|
1659
|
+
if (childNode?.state?.groupNode) return;
|
|
1415
1660
|
if (childNode && isSimNode(childNode)) {
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
childNode.x = clusterX + Math.cos(angle) * radius;
|
|
1419
|
-
childNode.y = clusterY + Math.sin(angle) * radius;
|
|
1661
|
+
Math.random() * 2 * Math.PI;
|
|
1662
|
+
Math.random() * clusterRadius;
|
|
1420
1663
|
}
|
|
1421
1664
|
});
|
|
1422
1665
|
}
|
|
1423
1666
|
}
|
|
1424
1667
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
if (parentNode && isSimNode(parentNode) && parentNode.state) parentNode.state.collapsed = false;
|
|
1430
|
-
});
|
|
1668
|
+
this.subGraph = getNodeSubgraph(node.id, this.prunedData.nodes, this.prunedData.links, this.rootId);
|
|
1669
|
+
if (this.selectedNode?.id !== node?.id) {
|
|
1670
|
+
this.selectedNode = node;
|
|
1671
|
+
this.emitter.emit("selected-node-changed", node);
|
|
1431
1672
|
}
|
|
1432
|
-
this.subGraph = getNodeSubgraph(node.id, this.data.nodes, this.data.links, this.rootId);
|
|
1433
|
-
}
|
|
1434
|
-
if (this.selectedNode?.id !== node?.id) {
|
|
1435
|
-
this.selectedNode = node;
|
|
1436
|
-
this.emitter.emit("selected-node-changed", node);
|
|
1437
|
-
this.updateRenderLayers();
|
|
1438
|
-
}
|
|
1439
|
-
if (node) {
|
|
1440
|
-
this.restart(wasOpened ? .05 : 0);
|
|
1441
1673
|
if (wasOpened || options?.triggerFocus) this.transformCanvas.focusOn(() => {
|
|
1442
1674
|
const t = this.transformCanvas.getTransform();
|
|
1443
1675
|
const _node = this.getNodeById(node.id);
|
|
@@ -1450,19 +1682,26 @@ var OpenGraphSimulation = class {
|
|
|
1450
1682
|
} else if (!node && this.selectedNode) {
|
|
1451
1683
|
this.selectedNode = null;
|
|
1452
1684
|
this.emitter.emit("selected-node-changed", null);
|
|
1453
|
-
this.subGraph =
|
|
1454
|
-
nodes: [],
|
|
1455
|
-
links: []
|
|
1456
|
-
};
|
|
1685
|
+
this.subGraph = newGraphData();
|
|
1457
1686
|
this.updateRenderLayers();
|
|
1458
1687
|
this.updateScene();
|
|
1459
1688
|
}
|
|
1460
1689
|
};
|
|
1690
|
+
updateEmitterNodes() {
|
|
1691
|
+
this.emitterNodes.forEach((node, depth) => {
|
|
1692
|
+
node.clusterSize = (this.secondaryNodes[depth] || []).filter((n) => this.emittedNodes.indexOf(n.id) === -1).length;
|
|
1693
|
+
});
|
|
1694
|
+
this.emitterClusterSizeRange = this.emitterNodes.reduce((acc, node) => [Math.min(acc[0], node.clusterSize || 1), Math.max(acc[1], node.clusterSize || 1)], [Infinity, -Infinity]);
|
|
1695
|
+
}
|
|
1696
|
+
get emitterNodes() {
|
|
1697
|
+
return this.data.nodes.values.filter((n) => n.state?.emitterNode).sort((a, b) => (a.depth || 0) - (b.depth || 0)) || [];
|
|
1698
|
+
}
|
|
1461
1699
|
updateScene = () => {
|
|
1462
1700
|
if (this.isTicking) return;
|
|
1463
1701
|
this.onDraw();
|
|
1464
1702
|
};
|
|
1465
1703
|
handleMove = (x, y) => {
|
|
1704
|
+
if (this.transformCanvas.getIsDragging()) return;
|
|
1466
1705
|
const world = this.screenToWorld(x, y);
|
|
1467
1706
|
const node = this.simulation?.find(world.x, world.y, 10) || null;
|
|
1468
1707
|
if (node?.state?.sessionNode) return;
|
|
@@ -1477,15 +1716,13 @@ var OpenGraphSimulation = class {
|
|
|
1477
1716
|
this.updateScene();
|
|
1478
1717
|
};
|
|
1479
1718
|
updateHighlights = () => {
|
|
1480
|
-
const detachedHighlights = this.highlights.
|
|
1481
|
-
const validSessionIds = new Set();
|
|
1719
|
+
const detachedHighlights = this.highlights.maps.detached.get("true") || [];
|
|
1720
|
+
const validSessionIds = /* @__PURE__ */ new Set();
|
|
1482
1721
|
let focusSessionNode = null;
|
|
1483
1722
|
detachedHighlights.forEach((h) => {
|
|
1484
|
-
const highlightedNode = this.data.nodes.
|
|
1723
|
+
const highlightedNode = this.data.nodes.maps.id.get(h.id);
|
|
1485
1724
|
if (!highlightedNode) return;
|
|
1486
|
-
const existingLink = this.data.links.
|
|
1487
|
-
return isSimNode(l.target) ? l.target.id === h.id : l.target === h.id;
|
|
1488
|
-
});
|
|
1725
|
+
const existingLink = this.data.links.maps.targetId.get(h.id);
|
|
1489
1726
|
if (!existingLink) return;
|
|
1490
1727
|
const id = isSimNode(existingLink.source) ? existingLink.source.id : existingLink.source.toString();
|
|
1491
1728
|
const parentNode = this.getNodeById(id);
|
|
@@ -1493,11 +1730,11 @@ var OpenGraphSimulation = class {
|
|
|
1493
1730
|
const _sessionId = h.sessionId || parentNode.id;
|
|
1494
1731
|
const sessionId = parentNode.state?.sessionNode ? parentNode.id : `${VOID_DETACH_ID}-${_sessionId}`;
|
|
1495
1732
|
validSessionIds.add(sessionId);
|
|
1496
|
-
let sessionNode = this.data.nodes.
|
|
1733
|
+
let sessionNode = this.data.nodes.maps.id.get(sessionId);
|
|
1497
1734
|
if (!sessionNode) {
|
|
1498
1735
|
const depth = (highlightedNode?.depth || 1) + 1;
|
|
1499
|
-
const angle = getAngle(this.
|
|
1500
|
-
const circlePos = getRadialPoint(getRadius(depth), this.
|
|
1736
|
+
const angle = getAngle(this.rootNode?.x, this.rootNode?.y, parentNode?.x, parentNode?.y);
|
|
1737
|
+
const circlePos = getRadialPoint(getRadius(depth), this.rootNode?.x, this.rootNode?.y, angle);
|
|
1501
1738
|
sessionNode = {
|
|
1502
1739
|
id: sessionId,
|
|
1503
1740
|
state: {
|
|
@@ -1516,37 +1753,50 @@ var OpenGraphSimulation = class {
|
|
|
1516
1753
|
source: parentNode
|
|
1517
1754
|
});
|
|
1518
1755
|
}
|
|
1519
|
-
const existingLinkIndex = this.data.links.findIndex((l) => l === existingLink);
|
|
1756
|
+
const existingLinkIndex = this.data.links.values.findIndex((l) => l === existingLink);
|
|
1520
1757
|
this.data.links.splice(existingLinkIndex, 1);
|
|
1521
1758
|
this.data.links.push({
|
|
1522
1759
|
target: highlightedNode,
|
|
1523
1760
|
source: sessionNode
|
|
1524
1761
|
});
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
const node = this.data.nodes.find((n) => n.id === parentId);
|
|
1528
|
-
if (node?.state) node.state.collapsed = false;
|
|
1762
|
+
getAllParentsUntil(highlightedNode.id, this.data.links, this.rootId).forEach((parentId) => {
|
|
1763
|
+
if (this.data.nodes.maps.id.get(parentId)?.state) {}
|
|
1529
1764
|
});
|
|
1530
1765
|
if (highlightedNode.state) highlightedNode.state.collapsed = false;
|
|
1531
1766
|
if (!focusSessionNode) focusSessionNode = sessionNode;
|
|
1532
1767
|
});
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
const
|
|
1536
|
-
const incomingLink = this.data.links.find((l) => isSimNode(l.target) && l.target.id === sessionNode.id);
|
|
1768
|
+
this.data.nodes.values.filter((n) => n.state?.sessionNode && !validSessionIds.has(n.id)).forEach((sessionNode) => {
|
|
1769
|
+
const childLinks = this.data.links.maps.sourceId.get(sessionNode.id);
|
|
1770
|
+
const incomingLink = this.data.links.maps.targetId.get(sessionNode.id);
|
|
1537
1771
|
const parentNode = incomingLink ? isSimNode(incomingLink.source) ? incomingLink.source : this.getNodeById(incomingLink.source.toString()) : null;
|
|
1538
|
-
this.data.links
|
|
1539
|
-
if (parentNode) childLinks
|
|
1772
|
+
this.data.links.reset(this.data.links.values.filter((l) => !(isSimNode(l.source) && l.source.id === sessionNode.id) && !(isSimNode(l.target) && l.target.id === sessionNode.id)));
|
|
1773
|
+
if (parentNode) childLinks?.forEach((link) => {
|
|
1540
1774
|
const targetNode = isSimNode(link.target) ? link.target : this.getNodeById(link.target.toString());
|
|
1541
1775
|
if (targetNode) this.data.links.push({
|
|
1542
1776
|
source: parentNode,
|
|
1543
1777
|
target: targetNode
|
|
1544
1778
|
});
|
|
1545
1779
|
});
|
|
1546
|
-
const index$1 = this.data.nodes.findIndex((n) => n.id === sessionNode.id);
|
|
1780
|
+
const index$1 = this.data.nodes.values.findIndex((n) => n.id === sessionNode.id);
|
|
1547
1781
|
if (index$1 !== -1) this.data.nodes.splice(index$1, 1);
|
|
1548
1782
|
});
|
|
1783
|
+
const isHighlighted = (nodeId) => this.highlights.maps.nodeId.get(nodeId);
|
|
1784
|
+
const isChildHighlighted = (nodeId) => {
|
|
1785
|
+
const children = getChildren(nodeId, this.data.links);
|
|
1786
|
+
if (children.some(isHighlighted)) return true;
|
|
1787
|
+
else return children.some(isHighlighted);
|
|
1788
|
+
};
|
|
1789
|
+
const nodesWithoutHighlight = this.data.nodes.values.filter((n) => {
|
|
1790
|
+
if (n.state?.sessionNode) return false;
|
|
1791
|
+
if (n.id === this.rootId) return false;
|
|
1792
|
+
if (n.state?.emitterNode) return false;
|
|
1793
|
+
if (isHighlighted(n.id)) return false;
|
|
1794
|
+
if (isChildHighlighted(n.id)) return false;
|
|
1795
|
+
return true;
|
|
1796
|
+
});
|
|
1797
|
+
this.secondaryNodes = groupBy(nodesWithoutHighlight, (n) => n.depth || 0);
|
|
1549
1798
|
if (this.selectedNode) this.subGraph = getNodeSubgraph(this.selectedNode.id, this.data.nodes, this.data.links, this.rootId);
|
|
1799
|
+
this.updateEmitterNodes();
|
|
1550
1800
|
this.restart();
|
|
1551
1801
|
if (focusSessionNode) this.transformCanvas.focusOn(() => {
|
|
1552
1802
|
if (!focusSessionNode) return null;
|
|
@@ -1559,24 +1809,44 @@ var OpenGraphSimulation = class {
|
|
|
1559
1809
|
scale: t.scale
|
|
1560
1810
|
};
|
|
1561
1811
|
});
|
|
1812
|
+
else this.transformCanvas.focusOn(() => {
|
|
1813
|
+
if (!this.rootNode) return null;
|
|
1814
|
+
const t = this.transformCanvas.getTransform();
|
|
1815
|
+
return {
|
|
1816
|
+
x: this.rootNode.x,
|
|
1817
|
+
y: this.rootNode.y,
|
|
1818
|
+
scale: t.scale
|
|
1819
|
+
};
|
|
1820
|
+
});
|
|
1821
|
+
};
|
|
1822
|
+
setNodeVisibility = (visibility, groupRootOrphans) => {
|
|
1823
|
+
if (this.nodeVisibility === visibility && groupRootOrphans === this.groupRootOrphans) return;
|
|
1824
|
+
this.groupRootOrphans = groupRootOrphans;
|
|
1825
|
+
this.nodeVisibility = visibility;
|
|
1826
|
+
this.updateHighlights();
|
|
1562
1827
|
};
|
|
1563
1828
|
initialize = (data, rootId) => {
|
|
1829
|
+
Measure.start("initialize");
|
|
1830
|
+
this.emittedNodes = [];
|
|
1564
1831
|
this.rawData = data;
|
|
1565
1832
|
this.rootId = rootId;
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
const
|
|
1572
|
-
const
|
|
1573
|
-
const
|
|
1574
|
-
const
|
|
1575
|
-
const
|
|
1833
|
+
const newData = newGraphData({
|
|
1834
|
+
nodes: [],
|
|
1835
|
+
links: data.links.map((l) => ({ ...l }))
|
|
1836
|
+
});
|
|
1837
|
+
newData.nodes.reset(data.nodes.map((n) => {
|
|
1838
|
+
const existingData = this.data.nodes.maps.id.get(n.id);
|
|
1839
|
+
const parent = getParent(n.id, newData.links);
|
|
1840
|
+
const parentNode = parent ? newData.nodes.maps.id.get(parent) : null;
|
|
1841
|
+
const clusterSize = getClusterSize(n.id, newData.links);
|
|
1842
|
+
const depth = getNodeDepth(n.id, newData.links);
|
|
1843
|
+
const circlePos = getRadialPoint(getRadius(Math.max(depth, 2)), this.center.x, this.center.y);
|
|
1844
|
+
const x = depth > 0 ? void 0 : existingData?.x || parentNode?.x || circlePos.x;
|
|
1845
|
+
const y = depth > 0 ? void 0 : existingData?.y || parentNode?.y || circlePos.y;
|
|
1576
1846
|
return {
|
|
1577
1847
|
...n,
|
|
1578
1848
|
state: {
|
|
1579
|
-
collapsed:
|
|
1849
|
+
collapsed: false,
|
|
1580
1850
|
...existingData?.state
|
|
1581
1851
|
},
|
|
1582
1852
|
clusterSize,
|
|
@@ -1584,44 +1854,39 @@ var OpenGraphSimulation = class {
|
|
|
1584
1854
|
x,
|
|
1585
1855
|
y
|
|
1586
1856
|
};
|
|
1587
|
-
}).sort((a, b) => a.depth - b.depth);
|
|
1588
|
-
|
|
1589
|
-
const
|
|
1590
|
-
const
|
|
1591
|
-
const
|
|
1592
|
-
const
|
|
1593
|
-
const parentAngle = depth > 0 ? getAngle(this.center.x, this.center.y, parentNode?.x, parentNode?.y) : void 0;
|
|
1857
|
+
}).sort((a, b) => a.depth - b.depth));
|
|
1858
|
+
newData.nodes.values.forEach((n, i, arr$1) => {
|
|
1859
|
+
const parent = getParent(n.id, newData.links);
|
|
1860
|
+
const parentNode = parent ? newData.nodes.maps.id.get(parent) : null;
|
|
1861
|
+
const depth = Math.max(2, n.depth || 0);
|
|
1862
|
+
const parentAngle = getAngle(this.center.x, this.center.y, parentNode?.x, parentNode?.y);
|
|
1594
1863
|
const circlePos = getRadialPoint(getRadius(depth), this.center.x, this.center.y, parentAngle);
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
_nodes[i].x = x;
|
|
1598
|
-
_nodes[i].y = y;
|
|
1864
|
+
n.x = n.x || circlePos.x;
|
|
1865
|
+
n.y = n.y || circlePos.y;
|
|
1599
1866
|
});
|
|
1600
|
-
if (!
|
|
1867
|
+
if (!newData.nodes.maps.id.get(this.rootId)) newData.nodes.push({
|
|
1601
1868
|
id: this.rootId,
|
|
1602
1869
|
state: {
|
|
1603
1870
|
collapsed: false,
|
|
1604
|
-
image: void 0
|
|
1871
|
+
image: void 0,
|
|
1872
|
+
rootNode: true
|
|
1605
1873
|
},
|
|
1606
1874
|
depth: -1,
|
|
1607
1875
|
clusterSize: 1,
|
|
1608
1876
|
x: this.center.x,
|
|
1609
1877
|
y: this.center.y
|
|
1610
1878
|
});
|
|
1611
|
-
const
|
|
1612
|
-
const
|
|
1613
|
-
for (const node of rootNodes) _links.push({
|
|
1879
|
+
const rootNodes = newData.nodes.values.filter((node) => !newData.links.maps.targetId.get(node.id));
|
|
1880
|
+
for (const node of rootNodes) newData.links.push({
|
|
1614
1881
|
source: this.rootId,
|
|
1615
1882
|
target: node.id
|
|
1616
1883
|
});
|
|
1617
|
-
this.maxDepth = Math.max(...
|
|
1618
|
-
this.data =
|
|
1619
|
-
nodes: _nodes,
|
|
1620
|
-
links: _links
|
|
1621
|
-
};
|
|
1884
|
+
this.maxDepth = Math.max(...newData.nodes.values.map((n) => n.depth || 0));
|
|
1885
|
+
this.data = newData;
|
|
1622
1886
|
this.loadNodeImages();
|
|
1623
1887
|
this.updateHighlights();
|
|
1624
1888
|
this.triggerSelected(true);
|
|
1889
|
+
Measure.end("initialize");
|
|
1625
1890
|
};
|
|
1626
1891
|
get lockedNode() {
|
|
1627
1892
|
return this.lockedNodeId ? this.getNodeById(this.lockedNodeId) : null;
|
|
@@ -1633,25 +1898,43 @@ var OpenGraphSimulation = class {
|
|
|
1633
1898
|
});
|
|
1634
1899
|
else if (this.lockedNode) this.handleClickNode(this.lockedNode, {
|
|
1635
1900
|
noToggle: true,
|
|
1636
|
-
triggerFocus
|
|
1901
|
+
triggerFocus,
|
|
1902
|
+
triggerRestart: true
|
|
1637
1903
|
});
|
|
1638
1904
|
else this.setSelectedNode(null);
|
|
1639
1905
|
};
|
|
1640
|
-
|
|
1906
|
+
get groupNodes() {
|
|
1907
|
+
return this.prunedData.nodes.maps.groupNode.get("true");
|
|
1908
|
+
}
|
|
1909
|
+
restart = (alpha = .3) => {
|
|
1641
1910
|
this.tickCount = 0;
|
|
1642
|
-
|
|
1911
|
+
console.log("Restarting simulation with alpha:", this.groupNodes);
|
|
1912
|
+
this.prunedData = getPrunedData(this.rootId, this.data.nodes, this.data.links, this.highlights.values, {
|
|
1913
|
+
nodeVisibility: this.nodeVisibility,
|
|
1914
|
+
emittedNodes: this.emittedNodes,
|
|
1915
|
+
groupNodes: this.groupNodes,
|
|
1916
|
+
skipGrouping: !this.groupRootOrphans || this.data.nodes.values.length < 200,
|
|
1917
|
+
openedGroups: this.openedGroups
|
|
1918
|
+
});
|
|
1643
1919
|
this.updateRenderLayers();
|
|
1644
|
-
this.clusterSizeRange = this.prunedData.nodes.
|
|
1920
|
+
this.clusterSizeRange = (this.prunedData.nodes.maps.collapsed.get("true") || []).reduce((acc, node) => [Math.min(acc[0], node.clusterSize || 1), Math.max(acc[1], node.clusterSize || 1)], [Infinity, -Infinity]);
|
|
1645
1921
|
if (this.simulation) {
|
|
1646
1922
|
this.simulation.stop();
|
|
1647
1923
|
this.simulation.on("tick", null);
|
|
1648
1924
|
this.simulation.on("end", null);
|
|
1649
1925
|
}
|
|
1650
|
-
this.simulation = forceSimulation(this.prunedData.nodes).alpha(this.simulation ? alpha : .5).force("collide", forceCollide((n) =>
|
|
1926
|
+
this.simulation = forceSimulation(this.prunedData.nodes.values).alpha(this.simulation ? alpha : .5).force("collide", forceCollide((n) => {
|
|
1927
|
+
return this.getNodeSize(n.id) / 2 + 2;
|
|
1928
|
+
})).force("link", asymmetricLinks(this.prunedData.links.values).id((d) => d.id).distance((l) => {
|
|
1651
1929
|
const size = this.getNodeSize(isSimNode(l.target) ? l.target.id : l.target.toString());
|
|
1652
1930
|
if (isSimNode(l.target)) {
|
|
1653
1931
|
const state = l.target?.state;
|
|
1932
|
+
if (state?.emitterNode) {
|
|
1933
|
+
if (isSimNode(l.source) && l.source.id === this.rootId) return 100;
|
|
1934
|
+
return 1;
|
|
1935
|
+
}
|
|
1654
1936
|
if (!state?.collapsed) return size;
|
|
1937
|
+
if (state.groupNode) return 5;
|
|
1655
1938
|
}
|
|
1656
1939
|
return size * 3;
|
|
1657
1940
|
}).strength((l) => {
|
|
@@ -1659,27 +1942,27 @@ var OpenGraphSimulation = class {
|
|
|
1659
1942
|
})).force("charge", forceManyBody().strength((node) => {
|
|
1660
1943
|
return -150;
|
|
1661
1944
|
})).force("center", forceCenter(this.center.x, this.center.y).strength(.1)).restart();
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
}));
|
|
1674
|
-
}
|
|
1675
|
-
this.simulation.on("tick", this.handleTick);
|
|
1676
|
-
this.simulation.on("end", this.onEnd);
|
|
1945
|
+
this.simulation.restart();
|
|
1946
|
+
Measure.start("simulation");
|
|
1947
|
+
this.simulation.on("tick", () => {
|
|
1948
|
+
Measure.end("tick", true);
|
|
1949
|
+
this.handleTick();
|
|
1950
|
+
Measure.start("tick");
|
|
1951
|
+
});
|
|
1952
|
+
this.simulation.on("end", () => {
|
|
1953
|
+
Measure.end("simulation");
|
|
1954
|
+
this.onEnd();
|
|
1955
|
+
});
|
|
1677
1956
|
};
|
|
1678
1957
|
get rootNode() {
|
|
1679
|
-
return this.data.nodes.
|
|
1958
|
+
return this.data.nodes.maps.id.get(this.rootId) || null;
|
|
1680
1959
|
}
|
|
1681
1960
|
handleTick = () => {
|
|
1682
1961
|
this.isTicking = true;
|
|
1962
|
+
if (this.rootNode) {
|
|
1963
|
+
this.rootNode.vx = 0;
|
|
1964
|
+
this.rootNode.vy = 0;
|
|
1965
|
+
}
|
|
1683
1966
|
this.onDraw();
|
|
1684
1967
|
this.tickCount++;
|
|
1685
1968
|
};
|
|
@@ -1701,29 +1984,30 @@ var OpenGraphSimulation = class {
|
|
|
1701
1984
|
}
|
|
1702
1985
|
getNodeSize = (nodeId) => {
|
|
1703
1986
|
const { nodeSize } = this.config;
|
|
1704
|
-
const
|
|
1705
|
-
const sizeScale = highlight?.scale || 1;
|
|
1987
|
+
const sizeScale = this.highlights.maps.nodeId.get(nodeId)?.scale || 1;
|
|
1706
1988
|
if (nodeId === this.rootId) return nodeSize * 2 * sizeScale;
|
|
1707
|
-
const node = this.
|
|
1989
|
+
const node = this.prunedData.nodes.maps.id.get(nodeId);
|
|
1708
1990
|
if (node?.state?.sessionNode) return 5;
|
|
1709
|
-
const
|
|
1710
|
-
if (
|
|
1711
|
-
|
|
1712
|
-
return
|
|
1991
|
+
const isEmitterNode = node?.state?.emitterNode;
|
|
1992
|
+
if (isEmitterNode) {
|
|
1993
|
+
if (node.clusterSize === 0) return 5;
|
|
1994
|
+
return scaleLinear().domain(this.emitterClusterSizeRange).range([nodeSize, nodeSize * 3])(node.clusterSize || 1);
|
|
1713
1995
|
}
|
|
1996
|
+
const isCollapsed = !!node?.state?.collapsed;
|
|
1997
|
+
const isGroupNode = !!node?.state?.groupNode;
|
|
1998
|
+
if (isCollapsed || isEmitterNode || isGroupNode) return scaleLinear().domain(this.clusterSizeRange).range([nodeSize, nodeSize * 3])(node.clusterSize || 1);
|
|
1714
1999
|
const isSelected = this.selectedNode?.id === nodeId;
|
|
1715
|
-
const
|
|
1716
|
-
const _size = isLiquidated ? nodeSize * .2 : nodeSize;
|
|
2000
|
+
const _size = node?.status === "LIQUIDATED" || node?.status === "REGENERATED" ? nodeSize * .2 : nodeSize;
|
|
1717
2001
|
return isSelected ? _size * 4 : _size * sizeScale;
|
|
1718
2002
|
};
|
|
1719
2003
|
updateRenderLayers() {
|
|
1720
2004
|
const isHighlighted = (id) => {
|
|
1721
|
-
const highlight = this.highlights.
|
|
1722
|
-
return !this.selectedNode && highlight?.onTop || this.selectedNode?.id === id || this.subGraph.nodes.
|
|
2005
|
+
const highlight = this.highlights.maps.nodeId.get(id);
|
|
2006
|
+
return !this.selectedNode && highlight?.onTop || this.selectedNode?.id === id || this.subGraph.nodes.maps.id.get(id);
|
|
1723
2007
|
};
|
|
1724
2008
|
this.renderLayers.nodes.regular = [];
|
|
1725
2009
|
this.renderLayers.nodes.highlighted = [];
|
|
1726
|
-
this.prunedData.nodes.forEach((node) => {
|
|
2010
|
+
this.prunedData.nodes.values.forEach((node) => {
|
|
1727
2011
|
if (isHighlighted(node.id)) this.renderLayers.nodes.highlighted.push(node);
|
|
1728
2012
|
else this.renderLayers.nodes.regular.push(node);
|
|
1729
2013
|
});
|
|
@@ -1731,20 +2015,14 @@ var OpenGraphSimulation = class {
|
|
|
1731
2015
|
this.renderLayers.links.highlighted = [];
|
|
1732
2016
|
const isLinkInSubgraph = (link) => {
|
|
1733
2017
|
if (!this.selectedNode) return false;
|
|
1734
|
-
|
|
1735
|
-
const targetId = isSimNode(link.target) ? link.target.id : link.target;
|
|
1736
|
-
return this.subGraph.links.some((l) => {
|
|
1737
|
-
const lSourceId = getNodeId(l.source);
|
|
1738
|
-
const lTargetId = getNodeId(l.target);
|
|
1739
|
-
return lSourceId === sourceId && lTargetId === targetId || lSourceId === targetId && lTargetId === sourceId;
|
|
1740
|
-
});
|
|
2018
|
+
return this.subGraph.links.maps.linkId.has(getLinkId(link));
|
|
1741
2019
|
};
|
|
1742
2020
|
const isLinkInHighlights = (link) => {
|
|
1743
2021
|
const sourceId = isSimNode(link.source) ? link.source.id : link.source;
|
|
1744
2022
|
const targetId = isSimNode(link.target) ? link.target.id : link.target;
|
|
1745
|
-
return !this.selectedNode && !!this.highlights.
|
|
2023
|
+
return !this.selectedNode && !!this.highlights.maps.detached.get("true")?.some((h) => h.id === sourceId || h.id === targetId);
|
|
1746
2024
|
};
|
|
1747
|
-
this.prunedData.links.forEach((link) => {
|
|
2025
|
+
this.prunedData.links.values.forEach((link) => {
|
|
1748
2026
|
const inSubgraph = isLinkInSubgraph(link);
|
|
1749
2027
|
const inHighlights = isLinkInHighlights(link);
|
|
1750
2028
|
if (inSubgraph || inHighlights) this.renderLayers.links.highlighted.push(link);
|
|
@@ -1757,9 +2035,9 @@ var OpenGraphSimulation = class {
|
|
|
1757
2035
|
return -1;
|
|
1758
2036
|
});
|
|
1759
2037
|
this.renderLayers.nodes.highlighted.sort((a, b) => {
|
|
1760
|
-
const highlightA = this.highlights.
|
|
1761
|
-
const highlightB = this.highlights.
|
|
1762
|
-
if (this.subGraph.nodes.
|
|
2038
|
+
const highlightA = this.highlights.maps.nodeId.get(a.id);
|
|
2039
|
+
const highlightB = this.highlights.maps.nodeId.get(b.id);
|
|
2040
|
+
if (this.subGraph.nodes.maps.id.get(a.id) || this.subGraph.nodes.maps.id.get(b.id)) return 2;
|
|
1763
2041
|
if (a.id === this.selectedNode?.id || b.id === this.selectedNode?.id) return 2;
|
|
1764
2042
|
if (highlightA?.onTop || highlightB?.onTop) return 1;
|
|
1765
2043
|
return -1;
|
|
@@ -1767,8 +2045,14 @@ var OpenGraphSimulation = class {
|
|
|
1767
2045
|
}
|
|
1768
2046
|
renderLink(ctx, link, options) {
|
|
1769
2047
|
const sourceId = isSimNode(link.source) ? link.source.id : link.source;
|
|
1770
|
-
const
|
|
1771
|
-
|
|
2048
|
+
const sourceNode = this.prunedData.nodes.maps.id.get(sourceId.toString());
|
|
2049
|
+
if (isSimNode(link.target)) {
|
|
2050
|
+
if (this.nodeVisibility === "all" || true) {
|
|
2051
|
+
if (link.target.state?.emitterNode || isSimNode(link.source) && link.source.state?.emitterNode) return;
|
|
2052
|
+
}
|
|
2053
|
+
const isSourceNode = this.prunedData.links.maps.sourceId.has(getNodeId(link.target));
|
|
2054
|
+
if (link.target.state?.emitterNode && link.target.clusterSize === 0 && !isSourceNode) return;
|
|
2055
|
+
}
|
|
1772
2056
|
const isLight = this.theme === "light";
|
|
1773
2057
|
const { dim: _dim, hasSelection, highlight } = options;
|
|
1774
2058
|
let stroke = _dim ? this.color(dim(.09, isLight))() : hasSelection ? this.color(dim(.4, isLight))() : this.color(dim(.18, isLight))();
|
|
@@ -1802,12 +2086,13 @@ var OpenGraphSimulation = class {
|
|
|
1802
2086
|
const fill = _dim ? this.color(dim(.075, isLight))() : isCollapsed ? this.color(dim(.18, isLight))() : isHovered ? this.color(dim(.4, isLight))() : this.color();
|
|
1803
2087
|
const stroke = this.colorContrast();
|
|
1804
2088
|
const nodeSize = this.getNodeSize(node.id);
|
|
1805
|
-
const highlight = this.highlights.
|
|
2089
|
+
const highlight = this.highlights.maps.nodeId.get(node.id);
|
|
1806
2090
|
const highlighted = !!highlight;
|
|
1807
2091
|
let highlightedStroke = _dim ? color(highlight?.strokeColor || red)(dim(.4, isLight))() : color(highlight?.strokeColor || red)();
|
|
1808
2092
|
if (node.id === this.rootId) this.renderRootNode(ctx, x, y, nodeSize, _dim, isLight);
|
|
2093
|
+
else if (node.state?.emitterNode) return;
|
|
1809
2094
|
else if (node.state?.sessionNode) this.renderSessionNode(ctx, x, y, nodeSize);
|
|
1810
|
-
else if (isCollapsed) this.renderCollapsedNode(ctx, x, y, nodeSize, {
|
|
2095
|
+
else if (isCollapsed || node.state?.groupNode) this.renderCollapsedNode(ctx, x, y, nodeSize, {
|
|
1811
2096
|
fill,
|
|
1812
2097
|
stroke,
|
|
1813
2098
|
highlighted,
|
|
@@ -1829,6 +2114,29 @@ var OpenGraphSimulation = class {
|
|
|
1829
2114
|
image: node.state?.image
|
|
1830
2115
|
});
|
|
1831
2116
|
}
|
|
2117
|
+
renderEmitterNode(ctx, x, y, size, node, dimmed) {
|
|
2118
|
+
if (this.nodeVisibility === "all") return;
|
|
2119
|
+
if (node.clusterSize === 0) return;
|
|
2120
|
+
this.theme;
|
|
2121
|
+
circle(ctx, x, y, size / 2, {
|
|
2122
|
+
stroke: true,
|
|
2123
|
+
strokeStyle: this.colorContrast(),
|
|
2124
|
+
lineWidth: .2,
|
|
2125
|
+
fill: true,
|
|
2126
|
+
fillStyle: "#0000ff"
|
|
2127
|
+
});
|
|
2128
|
+
const transform = this.transformCanvas.getTransform();
|
|
2129
|
+
const clusterSize = node.clusterSize || 1;
|
|
2130
|
+
if (transform.scale - .5 >= this.visiblityScale(clusterSize) ? 1 : 0) {
|
|
2131
|
+
ctx.font = `${14 / transform.scale}px Sans-Serif`;
|
|
2132
|
+
ctx.textAlign = "center";
|
|
2133
|
+
ctx.textBaseline = "middle";
|
|
2134
|
+
ctx.fillStyle = this.colorContrast();
|
|
2135
|
+
ctx.fillText(clusterSize.toString(), x, y);
|
|
2136
|
+
ctx.font = `${10 / transform.scale}px Sans-Serif`;
|
|
2137
|
+
ctx.fillText("click to emit", x, y + 5);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
1832
2140
|
renderSessionNode(ctx, x, y, size) {
|
|
1833
2141
|
const isLight = this.theme === "light";
|
|
1834
2142
|
circle(ctx, x, y, size / 2, {
|
|
@@ -1873,8 +2181,7 @@ var OpenGraphSimulation = class {
|
|
|
1873
2181
|
fill: true,
|
|
1874
2182
|
fillStyle: fill
|
|
1875
2183
|
});
|
|
1876
|
-
|
|
1877
|
-
if (showLabel) {
|
|
2184
|
+
if (transform.scale >= this.visiblityScale(clusterSize) ? 1 : 0) {
|
|
1878
2185
|
ctx.font = `${14 / transform.scale}px Sans-Serif`;
|
|
1879
2186
|
ctx.textAlign = "center";
|
|
1880
2187
|
ctx.textBaseline = "middle";
|
|
@@ -1888,7 +2195,7 @@ var OpenGraphSimulation = class {
|
|
|
1888
2195
|
rect(ctx, x - _size / 2, y - _size / 2, _size, _size, {
|
|
1889
2196
|
stroke: highlighted || isHovered,
|
|
1890
2197
|
strokeStyle: isHovered ? fill : highlightedStroke,
|
|
1891
|
-
lineWidth:
|
|
2198
|
+
lineWidth: 1,
|
|
1892
2199
|
fill: this.hideThumbnails || isLiquidated || !image,
|
|
1893
2200
|
fillStyle: fill,
|
|
1894
2201
|
borderRadius: 1
|
|
@@ -1896,9 +2203,14 @@ var OpenGraphSimulation = class {
|
|
|
1896
2203
|
if (image && !this.hideThumbnails && !isLiquidated) img(ctx, image, x - size / 2, y - size / 2, size, size, 1, _dim ? .1 : 1, fill);
|
|
1897
2204
|
}
|
|
1898
2205
|
onDraw = () => {
|
|
2206
|
+
Measure.start("draw");
|
|
2207
|
+
this.isDrawing = true;
|
|
1899
2208
|
const context = this.canvas?.getContext("2d");
|
|
1900
2209
|
const transform = this.transformCanvas.getTransform();
|
|
1901
|
-
if (!context)
|
|
2210
|
+
if (!context) {
|
|
2211
|
+
this.isDrawing = false;
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
1902
2214
|
const dpi = devicePixelRatio || 1;
|
|
1903
2215
|
context.save();
|
|
1904
2216
|
context.scale(dpi, dpi);
|
|
@@ -1906,11 +2218,21 @@ var OpenGraphSimulation = class {
|
|
|
1906
2218
|
context.setTransform(transform.scale, 0, 0, transform.scale, transform.x, transform.y);
|
|
1907
2219
|
context.translate(this.origin.x, this.origin.y);
|
|
1908
2220
|
context.save();
|
|
2221
|
+
const dimmedByHighlights = (nodeId) => {
|
|
2222
|
+
if (this.nodeVisibility === "all") return false;
|
|
2223
|
+
if (nodeId === this.rootId) return false;
|
|
2224
|
+
if (this.prunedData.nodes.maps.id.get(nodeId)?.state?.groupNode) return false;
|
|
2225
|
+
if (this.nodeVisibility === "locked") {
|
|
2226
|
+
const node = this.getNodeById(nodeId);
|
|
2227
|
+
return !(node?.status === "LOCKED" || node?.status === "EVOLVED");
|
|
2228
|
+
}
|
|
2229
|
+
return !this.highlights.maps.nodeId.get(nodeId);
|
|
2230
|
+
};
|
|
1909
2231
|
const hasSelection = !!this.selectedNode;
|
|
1910
2232
|
this.renderLayers.links.regular.forEach((link) => {
|
|
1911
|
-
const sourceId =
|
|
1912
|
-
const targetId =
|
|
1913
|
-
const _dim = hasSelection
|
|
2233
|
+
const sourceId = getNodeId(link.source);
|
|
2234
|
+
const targetId = getNodeId(link.target);
|
|
2235
|
+
const _dim = hasSelection ? !this.subGraph.links.maps.linkId.has(getLinkId(link)) : dimmedByHighlights(sourceId) || dimmedByHighlights(targetId);
|
|
1914
2236
|
this.renderLink(context, link, {
|
|
1915
2237
|
dim: _dim,
|
|
1916
2238
|
hasSelection
|
|
@@ -1918,17 +2240,17 @@ var OpenGraphSimulation = class {
|
|
|
1918
2240
|
});
|
|
1919
2241
|
context.globalAlpha = 1;
|
|
1920
2242
|
this.renderLayers.nodes.regular.forEach((node) => {
|
|
1921
|
-
const _dim = hasSelection
|
|
2243
|
+
const _dim = hasSelection ? !this.subGraph.nodes.maps.id.has(node.id) : dimmedByHighlights(node.id);
|
|
1922
2244
|
this.renderNode(context, node, {
|
|
1923
2245
|
dim: _dim,
|
|
1924
2246
|
transform
|
|
1925
2247
|
});
|
|
1926
2248
|
});
|
|
1927
2249
|
this.renderLayers.links.highlighted.forEach((link) => {
|
|
1928
|
-
const sourceId =
|
|
1929
|
-
const targetId =
|
|
1930
|
-
const _dim = hasSelection
|
|
1931
|
-
const highlight = this.highlights.
|
|
2250
|
+
const sourceId = getNodeId(link.source);
|
|
2251
|
+
const targetId = getNodeId(link.target);
|
|
2252
|
+
const _dim = hasSelection ? !this.subGraph.links.maps.linkId.has(getLinkId(link)) : dimmedByHighlights(sourceId) || dimmedByHighlights(targetId);
|
|
2253
|
+
const highlight = this.highlights.maps.nodeId.get(sourceId) || this.highlights.maps.nodeId.get(targetId);
|
|
1932
2254
|
this.renderLink(context, link, {
|
|
1933
2255
|
dim: _dim,
|
|
1934
2256
|
hasSelection,
|
|
@@ -1937,7 +2259,7 @@ var OpenGraphSimulation = class {
|
|
|
1937
2259
|
});
|
|
1938
2260
|
context.globalAlpha = 1;
|
|
1939
2261
|
this.renderLayers.nodes.highlighted.forEach((node) => {
|
|
1940
|
-
const _dim = hasSelection
|
|
2262
|
+
const _dim = hasSelection ? !this.subGraph.nodes.maps.id.has(node.id) : dimmedByHighlights(node.id);
|
|
1941
2263
|
this.renderNode(context, node, {
|
|
1942
2264
|
dim: _dim,
|
|
1943
2265
|
transform
|
|
@@ -1946,6 +2268,8 @@ var OpenGraphSimulation = class {
|
|
|
1946
2268
|
context.restore();
|
|
1947
2269
|
context.restore();
|
|
1948
2270
|
this.transformCanvas.trackCursor();
|
|
2271
|
+
this.isDrawing = false;
|
|
2272
|
+
Measure.end("draw");
|
|
1949
2273
|
};
|
|
1950
2274
|
drawDebug(context) {
|
|
1951
2275
|
const transform = this.transformCanvas.getTransform();
|
|
@@ -1977,13 +2301,15 @@ var OpenGraphSimulation = class {
|
|
|
1977
2301
|
}
|
|
1978
2302
|
onEnd = () => {
|
|
1979
2303
|
this.isTicking = false;
|
|
2304
|
+
this.emitter.emit("simulation:ended", this);
|
|
1980
2305
|
};
|
|
1981
2306
|
loadNodeImages = () => {
|
|
1982
|
-
this.data.nodes.forEach((node) => {
|
|
2307
|
+
this.data.nodes.values.forEach((node) => {
|
|
1983
2308
|
if (node.id === this.rootId) return;
|
|
1984
2309
|
if (node.imgSrc && this.imageCache.get(node.imgSrc)) {
|
|
2310
|
+
const html = this.imageCache.get(node.imgSrc);
|
|
1985
2311
|
node.state = node.state || {};
|
|
1986
|
-
node.state.image =
|
|
2312
|
+
node.state.image = html;
|
|
1987
2313
|
return;
|
|
1988
2314
|
}
|
|
1989
2315
|
const loadImage = async () => {
|
|
@@ -2012,6 +2338,18 @@ var OpenGraphSimulation = class {
|
|
|
2012
2338
|
this.theme = theme;
|
|
2013
2339
|
this.updateScene();
|
|
2014
2340
|
};
|
|
2341
|
+
setNodeImage = (nodeId, src) => {
|
|
2342
|
+
const node = this.getNodeById(nodeId);
|
|
2343
|
+
if (!node) return;
|
|
2344
|
+
const load = async () => {
|
|
2345
|
+
const html = this.imageCache.get(src) || await loadHTMLImageElement(src);
|
|
2346
|
+
this.imageCache.set(src, html);
|
|
2347
|
+
node.state = node.state || {};
|
|
2348
|
+
node.state.image = html;
|
|
2349
|
+
if (!this.isTicking && !this.isDrawing) this.updateScene();
|
|
2350
|
+
};
|
|
2351
|
+
load();
|
|
2352
|
+
};
|
|
2015
2353
|
setHideThumbnails = (hide) => {
|
|
2016
2354
|
this.hideThumbnails = hide;
|
|
2017
2355
|
this.updateScene();
|
|
@@ -2026,7 +2364,7 @@ var OpenGraphSimulation = class {
|
|
|
2026
2364
|
const hash = quickHash(JSON.stringify(highlights));
|
|
2027
2365
|
if (hash === this._highlightHash) return;
|
|
2028
2366
|
this._highlightHash = hash;
|
|
2029
|
-
this.highlights
|
|
2367
|
+
this.highlights.reset(highlights);
|
|
2030
2368
|
this.updateHighlights();
|
|
2031
2369
|
};
|
|
2032
2370
|
setNoInteraction = (noInteraction) => {
|
|
@@ -2035,7 +2373,7 @@ var OpenGraphSimulation = class {
|
|
|
2035
2373
|
this.updateScene();
|
|
2036
2374
|
};
|
|
2037
2375
|
getNodeById = (nodeId) => {
|
|
2038
|
-
return this.data.nodes.
|
|
2376
|
+
return this.data.nodes.maps.id.get(nodeId) || null;
|
|
2039
2377
|
};
|
|
2040
2378
|
setLockedNodeId = (nodeId) => {
|
|
2041
2379
|
this.lockedNodeId = nodeId || void 0;
|
|
@@ -2080,12 +2418,15 @@ var OpenGraphSimulation = class {
|
|
|
2080
2418
|
context.restore();
|
|
2081
2419
|
context.restore();
|
|
2082
2420
|
};
|
|
2421
|
+
get measures() {
|
|
2422
|
+
return Measure.getSamples();
|
|
2423
|
+
}
|
|
2083
2424
|
};
|
|
2084
2425
|
|
|
2085
2426
|
//#endregion
|
|
2086
2427
|
//#region src/components/OpenFormGraph.tsx
|
|
2087
2428
|
function OpenFormGraph(props) {
|
|
2088
|
-
const { width, height, highlights = [], className, noInteraction = false, loadNodeImage, translate, onTransform } = props;
|
|
2429
|
+
const { width, height, highlights = [], className, noInteraction = false, loadNodeImage, translate, onTransform, onSimulationEnded, nodeVisibility = "all", children, groupRootOrphans = false } = props;
|
|
2089
2430
|
const { simulation, data, rootId, rootImageSources, theme, hideThumbnails, setHoveredNode, setSelectedNode, lockedNodeId } = useOpenFormGraph();
|
|
2090
2431
|
const canvasRef = useRef(null);
|
|
2091
2432
|
useEffect(() => {
|
|
@@ -2098,16 +2439,24 @@ function OpenFormGraph(props) {
|
|
|
2098
2439
|
loadNodeImage,
|
|
2099
2440
|
theme,
|
|
2100
2441
|
translate,
|
|
2101
|
-
lockedNodeId
|
|
2442
|
+
lockedNodeId,
|
|
2443
|
+
nodeVisibility,
|
|
2444
|
+
highlights,
|
|
2445
|
+
groupRootOrphans
|
|
2102
2446
|
});
|
|
2447
|
+
return () => {
|
|
2448
|
+
simulation.current?.destroy();
|
|
2449
|
+
};
|
|
2450
|
+
}, []);
|
|
2451
|
+
useEffect(() => {
|
|
2452
|
+
if (!simulation.current) return;
|
|
2103
2453
|
simulation.current.emitter.on("selected-node-changed", setSelectedNode);
|
|
2104
2454
|
simulation.current.emitter.on("hovered-node-changed", setHoveredNode);
|
|
2105
2455
|
return () => {
|
|
2106
2456
|
simulation.current?.emitter.off("selected-node-changed", setSelectedNode);
|
|
2107
2457
|
simulation.current?.emitter.off("hovered-node-changed", setHoveredNode);
|
|
2108
|
-
simulation.current?.destroy();
|
|
2109
2458
|
};
|
|
2110
|
-
}, []);
|
|
2459
|
+
}, [setSelectedNode, setHoveredNode]);
|
|
2111
2460
|
useEffect(() => {
|
|
2112
2461
|
if (!simulation.current) return;
|
|
2113
2462
|
if (!onTransform) return;
|
|
@@ -2116,6 +2465,10 @@ function OpenFormGraph(props) {
|
|
|
2116
2465
|
simulation.current?.emitter.off("transform-changed", onTransform);
|
|
2117
2466
|
};
|
|
2118
2467
|
}, [onTransform]);
|
|
2468
|
+
useEffect(() => {
|
|
2469
|
+
if (!simulation.current || !onSimulationEnded) return;
|
|
2470
|
+
return simulation.current.emitter.on("simulation:ended", onSimulationEnded);
|
|
2471
|
+
}, [onSimulationEnded]);
|
|
2119
2472
|
useEffect(() => {
|
|
2120
2473
|
if (!simulation.current) return;
|
|
2121
2474
|
simulation.current.resize(width, height);
|
|
@@ -2128,6 +2481,10 @@ function OpenFormGraph(props) {
|
|
|
2128
2481
|
if (!simulation.current) return;
|
|
2129
2482
|
simulation.current.setHideThumbnails(hideThumbnails);
|
|
2130
2483
|
}, [hideThumbnails]);
|
|
2484
|
+
useEffect(() => {
|
|
2485
|
+
if (!simulation.current) return;
|
|
2486
|
+
simulation.current.setNodeVisibility(nodeVisibility, groupRootOrphans);
|
|
2487
|
+
}, [nodeVisibility, groupRootOrphans]);
|
|
2131
2488
|
useEffect(() => {
|
|
2132
2489
|
if (!simulation.current) return;
|
|
2133
2490
|
simulation.current.setHighlights(highlights);
|
|
@@ -2139,7 +2496,7 @@ function OpenFormGraph(props) {
|
|
|
2139
2496
|
useEffect(() => {
|
|
2140
2497
|
if (!simulation.current) return;
|
|
2141
2498
|
simulation.current.initialize(data, rootId);
|
|
2142
|
-
}, [data]);
|
|
2499
|
+
}, [data, rootId]);
|
|
2143
2500
|
useEffect(() => {
|
|
2144
2501
|
if (!simulation.current) return;
|
|
2145
2502
|
if (!translate) return;
|
|
@@ -2149,9 +2506,9 @@ function OpenFormGraph(props) {
|
|
|
2149
2506
|
if (!simulation.current) return;
|
|
2150
2507
|
if (simulation.current.lockedNodeId === lockedNodeId) return;
|
|
2151
2508
|
simulation.current.setLockedNodeId(lockedNodeId);
|
|
2152
|
-
}, [
|
|
2509
|
+
}, [lockedNodeId]);
|
|
2153
2510
|
const dpi = devicePixelRatio || 1;
|
|
2154
|
-
return /* @__PURE__ */ jsx("canvas", {
|
|
2511
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("canvas", {
|
|
2155
2512
|
onMouseEnter: props.onMouseEnter,
|
|
2156
2513
|
onMouseLeave: props.onMouseLeave,
|
|
2157
2514
|
ref: canvasRef,
|
|
@@ -2162,9 +2519,9 @@ function OpenFormGraph(props) {
|
|
|
2162
2519
|
width: `${width}px`,
|
|
2163
2520
|
height: `${height}px`
|
|
2164
2521
|
}
|
|
2165
|
-
});
|
|
2522
|
+
}), children] });
|
|
2166
2523
|
}
|
|
2167
2524
|
|
|
2168
2525
|
//#endregion
|
|
2169
|
-
export { Highlight, OpenFormGraph, OpenGraphEventEmitter, OpenGraphSimulation, TransformCanvas, buildTreeFromGraphData, circle, color, dim, generateTree, getAllParentsUntil, getChildren, getClusterSize, getNodeDepth, getNodeId, getNodeSubgraph,
|
|
2170
|
-
//# sourceMappingURL=OpenFormGraph-
|
|
2526
|
+
export { Highlight, OpenFormGraph, OpenGraphEventEmitter, OpenGraphSimulation, TransformCanvas, buildTreeFromGraphData, circle, color, dim, generateTree, getAllParentsUntil, getChildren, getClusterSize, getLinkId, getNodeDepth, getNodeId, getNodeSubgraph, getParent, getPrunedData, getRootParent, groupGraphNodes, hasOnlyLeafs, hexagon, img, isCustomHighlight, isSimLink, isSimNode, loadHTMLImageElement, newGraphData, rect, red, searchParents };
|
|
2527
|
+
//# sourceMappingURL=OpenFormGraph-Ccizmxbo.js.map
|