xcode-graph 0.2.0 → 0.2.1
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/custom-elements.json +9 -0
- package/dist/chunk.js +14 -0
- package/dist/elk-api.js +196 -0
- package/dist/elk-worker.js +24 -104592
- package/dist/graph.types.js +23 -0
- package/dist/micro-layout.worker.js +1 -1420
- package/dist/xcode-graph.service.js +2787 -4889
- package/dist/xcodegraph.js +19712 -1
- package/package.json +4 -7
- package/web-types.json +1 -1
- package/dist/elk.bundled.js +0 -6626
- package/dist/xcode-graph.js +0 -28509
|
@@ -1,1420 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cluster Types - Cluster layout and positioning types
|
|
3
|
-
*
|
|
4
|
-
* Pure TypeScript enums, interfaces, and constants for cluster grouping,
|
|
5
|
-
* node roles, and layout configuration. No Zod dependency.
|
|
6
|
-
*
|
|
7
|
-
* @module schemas/cluster
|
|
8
|
-
*/
|
|
9
|
-
/**
|
|
10
|
-
* Node role enum - determines positioning strategy within a cluster
|
|
11
|
-
*
|
|
12
|
-
* @public
|
|
13
|
-
*/
|
|
14
|
-
var NodeRole;
|
|
15
|
-
(function (NodeRole) {
|
|
16
|
-
NodeRole["Entry"] = "entry";
|
|
17
|
-
NodeRole["InternalFramework"] = "internal-framework";
|
|
18
|
-
NodeRole["InternalLib"] = "internal-lib";
|
|
19
|
-
NodeRole["Utility"] = "utility";
|
|
20
|
-
NodeRole["Test"] = "test";
|
|
21
|
-
NodeRole["Tool"] = "tool";
|
|
22
|
-
})(NodeRole || (NodeRole = {}));
|
|
23
|
-
/**
|
|
24
|
-
* Cluster type enum - distinguishes local projects from packages
|
|
25
|
-
*
|
|
26
|
-
* @public
|
|
27
|
-
*/
|
|
28
|
-
var ClusterType;
|
|
29
|
-
(function (ClusterType) {
|
|
30
|
-
ClusterType["Project"] = "project";
|
|
31
|
-
ClusterType["Package"] = "package";
|
|
32
|
-
})(ClusterType || (ClusterType = {}));
|
|
33
|
-
/**
|
|
34
|
-
* ELK Hierarchy Handling strategy
|
|
35
|
-
*
|
|
36
|
-
* @public
|
|
37
|
-
*/
|
|
38
|
-
var ElkHierarchyHandling;
|
|
39
|
-
(function (ElkHierarchyHandling) {
|
|
40
|
-
ElkHierarchyHandling["Inherit"] = "INHERIT";
|
|
41
|
-
ElkHierarchyHandling["IncludeChildren"] = "INCLUDE_CHILDREN";
|
|
42
|
-
ElkHierarchyHandling["SeparateChildren"] = "SEPARATE_CHILDREN";
|
|
43
|
-
})(ElkHierarchyHandling || (ElkHierarchyHandling = {}));
|
|
44
|
-
/**
|
|
45
|
-
* All node role values for iteration
|
|
46
|
-
*
|
|
47
|
-
* @public
|
|
48
|
-
*/
|
|
49
|
-
Object.values(NodeRole);
|
|
50
|
-
/**
|
|
51
|
-
* All cluster type values for iteration
|
|
52
|
-
*
|
|
53
|
-
* @public
|
|
54
|
-
*/
|
|
55
|
-
Object.values(ClusterType);
|
|
56
|
-
|
|
57
|
-
function forceCenter(x, y) {
|
|
58
|
-
var nodes, strength = 1;
|
|
59
|
-
|
|
60
|
-
if (x == null) x = 0;
|
|
61
|
-
if (y == null) y = 0;
|
|
62
|
-
|
|
63
|
-
function force() {
|
|
64
|
-
var i,
|
|
65
|
-
n = nodes.length,
|
|
66
|
-
node,
|
|
67
|
-
sx = 0,
|
|
68
|
-
sy = 0;
|
|
69
|
-
|
|
70
|
-
for (i = 0; i < n; ++i) {
|
|
71
|
-
node = nodes[i], sx += node.x, sy += node.y;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
for (sx = (sx / n - x) * strength, sy = (sy / n - y) * strength, i = 0; i < n; ++i) {
|
|
75
|
-
node = nodes[i], node.x -= sx, node.y -= sy;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
force.initialize = function(_) {
|
|
80
|
-
nodes = _;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
force.x = function(_) {
|
|
84
|
-
return arguments.length ? (x = +_, force) : x;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
force.y = function(_) {
|
|
88
|
-
return arguments.length ? (y = +_, force) : y;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
force.strength = function(_) {
|
|
92
|
-
return arguments.length ? (strength = +_, force) : strength;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
return force;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function tree_add(d) {
|
|
99
|
-
const x = +this._x.call(null, d),
|
|
100
|
-
y = +this._y.call(null, d);
|
|
101
|
-
return add(this.cover(x, y), x, y, d);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function add(tree, x, y, d) {
|
|
105
|
-
if (isNaN(x) || isNaN(y)) return tree; // ignore invalid points
|
|
106
|
-
|
|
107
|
-
var parent,
|
|
108
|
-
node = tree._root,
|
|
109
|
-
leaf = {data: d},
|
|
110
|
-
x0 = tree._x0,
|
|
111
|
-
y0 = tree._y0,
|
|
112
|
-
x1 = tree._x1,
|
|
113
|
-
y1 = tree._y1,
|
|
114
|
-
xm,
|
|
115
|
-
ym,
|
|
116
|
-
xp,
|
|
117
|
-
yp,
|
|
118
|
-
right,
|
|
119
|
-
bottom,
|
|
120
|
-
i,
|
|
121
|
-
j;
|
|
122
|
-
|
|
123
|
-
// If the tree is empty, initialize the root as a leaf.
|
|
124
|
-
if (!node) return tree._root = leaf, tree;
|
|
125
|
-
|
|
126
|
-
// Find the existing leaf for the new point, or add it.
|
|
127
|
-
while (node.length) {
|
|
128
|
-
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
|
129
|
-
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
|
130
|
-
if (parent = node, !(node = node[i = bottom << 1 | right])) return parent[i] = leaf, tree;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Is the new point is exactly coincident with the existing point?
|
|
134
|
-
xp = +tree._x.call(null, node.data);
|
|
135
|
-
yp = +tree._y.call(null, node.data);
|
|
136
|
-
if (x === xp && y === yp) return leaf.next = node, parent ? parent[i] = leaf : tree._root = leaf, tree;
|
|
137
|
-
|
|
138
|
-
// Otherwise, split the leaf node until the old and new point are separated.
|
|
139
|
-
do {
|
|
140
|
-
parent = parent ? parent[i] = new Array(4) : tree._root = new Array(4);
|
|
141
|
-
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
|
142
|
-
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
|
143
|
-
} while ((i = bottom << 1 | right) === (j = (yp >= ym) << 1 | (xp >= xm)));
|
|
144
|
-
return parent[j] = node, parent[i] = leaf, tree;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function addAll(data) {
|
|
148
|
-
var d, i, n = data.length,
|
|
149
|
-
x,
|
|
150
|
-
y,
|
|
151
|
-
xz = new Array(n),
|
|
152
|
-
yz = new Array(n),
|
|
153
|
-
x0 = Infinity,
|
|
154
|
-
y0 = Infinity,
|
|
155
|
-
x1 = -Infinity,
|
|
156
|
-
y1 = -Infinity;
|
|
157
|
-
|
|
158
|
-
// Compute the points and their extent.
|
|
159
|
-
for (i = 0; i < n; ++i) {
|
|
160
|
-
if (isNaN(x = +this._x.call(null, d = data[i])) || isNaN(y = +this._y.call(null, d))) continue;
|
|
161
|
-
xz[i] = x;
|
|
162
|
-
yz[i] = y;
|
|
163
|
-
if (x < x0) x0 = x;
|
|
164
|
-
if (x > x1) x1 = x;
|
|
165
|
-
if (y < y0) y0 = y;
|
|
166
|
-
if (y > y1) y1 = y;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// If there were no (valid) points, abort.
|
|
170
|
-
if (x0 > x1 || y0 > y1) return this;
|
|
171
|
-
|
|
172
|
-
// Expand the tree to cover the new points.
|
|
173
|
-
this.cover(x0, y0).cover(x1, y1);
|
|
174
|
-
|
|
175
|
-
// Add the new points.
|
|
176
|
-
for (i = 0; i < n; ++i) {
|
|
177
|
-
add(this, xz[i], yz[i], data[i]);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return this;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function tree_cover(x, y) {
|
|
184
|
-
if (isNaN(x = +x) || isNaN(y = +y)) return this; // ignore invalid points
|
|
185
|
-
|
|
186
|
-
var x0 = this._x0,
|
|
187
|
-
y0 = this._y0,
|
|
188
|
-
x1 = this._x1,
|
|
189
|
-
y1 = this._y1;
|
|
190
|
-
|
|
191
|
-
// If the quadtree has no extent, initialize them.
|
|
192
|
-
// Integer extent are necessary so that if we later double the extent,
|
|
193
|
-
// the existing quadrant boundaries don’t change due to floating point error!
|
|
194
|
-
if (isNaN(x0)) {
|
|
195
|
-
x1 = (x0 = Math.floor(x)) + 1;
|
|
196
|
-
y1 = (y0 = Math.floor(y)) + 1;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Otherwise, double repeatedly to cover.
|
|
200
|
-
else {
|
|
201
|
-
var z = x1 - x0 || 1,
|
|
202
|
-
node = this._root,
|
|
203
|
-
parent,
|
|
204
|
-
i;
|
|
205
|
-
|
|
206
|
-
while (x0 > x || x >= x1 || y0 > y || y >= y1) {
|
|
207
|
-
i = (y < y0) << 1 | (x < x0);
|
|
208
|
-
parent = new Array(4), parent[i] = node, node = parent, z *= 2;
|
|
209
|
-
switch (i) {
|
|
210
|
-
case 0: x1 = x0 + z, y1 = y0 + z; break;
|
|
211
|
-
case 1: x0 = x1 - z, y1 = y0 + z; break;
|
|
212
|
-
case 2: x1 = x0 + z, y0 = y1 - z; break;
|
|
213
|
-
case 3: x0 = x1 - z, y0 = y1 - z; break;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (this._root && this._root.length) this._root = node;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
this._x0 = x0;
|
|
221
|
-
this._y0 = y0;
|
|
222
|
-
this._x1 = x1;
|
|
223
|
-
this._y1 = y1;
|
|
224
|
-
return this;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function tree_data() {
|
|
228
|
-
var data = [];
|
|
229
|
-
this.visit(function(node) {
|
|
230
|
-
if (!node.length) do data.push(node.data); while (node = node.next)
|
|
231
|
-
});
|
|
232
|
-
return data;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function tree_extent(_) {
|
|
236
|
-
return arguments.length
|
|
237
|
-
? this.cover(+_[0][0], +_[0][1]).cover(+_[1][0], +_[1][1])
|
|
238
|
-
: isNaN(this._x0) ? undefined : [[this._x0, this._y0], [this._x1, this._y1]];
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function Quad(node, x0, y0, x1, y1) {
|
|
242
|
-
this.node = node;
|
|
243
|
-
this.x0 = x0;
|
|
244
|
-
this.y0 = y0;
|
|
245
|
-
this.x1 = x1;
|
|
246
|
-
this.y1 = y1;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function tree_find(x, y, radius) {
|
|
250
|
-
var data,
|
|
251
|
-
x0 = this._x0,
|
|
252
|
-
y0 = this._y0,
|
|
253
|
-
x1,
|
|
254
|
-
y1,
|
|
255
|
-
x2,
|
|
256
|
-
y2,
|
|
257
|
-
x3 = this._x1,
|
|
258
|
-
y3 = this._y1,
|
|
259
|
-
quads = [],
|
|
260
|
-
node = this._root,
|
|
261
|
-
q,
|
|
262
|
-
i;
|
|
263
|
-
|
|
264
|
-
if (node) quads.push(new Quad(node, x0, y0, x3, y3));
|
|
265
|
-
if (radius == null) radius = Infinity;
|
|
266
|
-
else {
|
|
267
|
-
x0 = x - radius, y0 = y - radius;
|
|
268
|
-
x3 = x + radius, y3 = y + radius;
|
|
269
|
-
radius *= radius;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
while (q = quads.pop()) {
|
|
273
|
-
|
|
274
|
-
// Stop searching if this quadrant can’t contain a closer node.
|
|
275
|
-
if (!(node = q.node)
|
|
276
|
-
|| (x1 = q.x0) > x3
|
|
277
|
-
|| (y1 = q.y0) > y3
|
|
278
|
-
|| (x2 = q.x1) < x0
|
|
279
|
-
|| (y2 = q.y1) < y0) continue;
|
|
280
|
-
|
|
281
|
-
// Bisect the current quadrant.
|
|
282
|
-
if (node.length) {
|
|
283
|
-
var xm = (x1 + x2) / 2,
|
|
284
|
-
ym = (y1 + y2) / 2;
|
|
285
|
-
|
|
286
|
-
quads.push(
|
|
287
|
-
new Quad(node[3], xm, ym, x2, y2),
|
|
288
|
-
new Quad(node[2], x1, ym, xm, y2),
|
|
289
|
-
new Quad(node[1], xm, y1, x2, ym),
|
|
290
|
-
new Quad(node[0], x1, y1, xm, ym)
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
// Visit the closest quadrant first.
|
|
294
|
-
if (i = (y >= ym) << 1 | (x >= xm)) {
|
|
295
|
-
q = quads[quads.length - 1];
|
|
296
|
-
quads[quads.length - 1] = quads[quads.length - 1 - i];
|
|
297
|
-
quads[quads.length - 1 - i] = q;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Visit this point. (Visiting coincident points isn’t necessary!)
|
|
302
|
-
else {
|
|
303
|
-
var dx = x - +this._x.call(null, node.data),
|
|
304
|
-
dy = y - +this._y.call(null, node.data),
|
|
305
|
-
d2 = dx * dx + dy * dy;
|
|
306
|
-
if (d2 < radius) {
|
|
307
|
-
var d = Math.sqrt(radius = d2);
|
|
308
|
-
x0 = x - d, y0 = y - d;
|
|
309
|
-
x3 = x + d, y3 = y + d;
|
|
310
|
-
data = node.data;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return data;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function tree_remove(d) {
|
|
319
|
-
if (isNaN(x = +this._x.call(null, d)) || isNaN(y = +this._y.call(null, d))) return this; // ignore invalid points
|
|
320
|
-
|
|
321
|
-
var parent,
|
|
322
|
-
node = this._root,
|
|
323
|
-
retainer,
|
|
324
|
-
previous,
|
|
325
|
-
next,
|
|
326
|
-
x0 = this._x0,
|
|
327
|
-
y0 = this._y0,
|
|
328
|
-
x1 = this._x1,
|
|
329
|
-
y1 = this._y1,
|
|
330
|
-
x,
|
|
331
|
-
y,
|
|
332
|
-
xm,
|
|
333
|
-
ym,
|
|
334
|
-
right,
|
|
335
|
-
bottom,
|
|
336
|
-
i,
|
|
337
|
-
j;
|
|
338
|
-
|
|
339
|
-
// If the tree is empty, initialize the root as a leaf.
|
|
340
|
-
if (!node) return this;
|
|
341
|
-
|
|
342
|
-
// Find the leaf node for the point.
|
|
343
|
-
// While descending, also retain the deepest parent with a non-removed sibling.
|
|
344
|
-
if (node.length) while (true) {
|
|
345
|
-
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
|
346
|
-
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
|
347
|
-
if (!(parent = node, node = node[i = bottom << 1 | right])) return this;
|
|
348
|
-
if (!node.length) break;
|
|
349
|
-
if (parent[(i + 1) & 3] || parent[(i + 2) & 3] || parent[(i + 3) & 3]) retainer = parent, j = i;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Find the point to remove.
|
|
353
|
-
while (node.data !== d) if (!(previous = node, node = node.next)) return this;
|
|
354
|
-
if (next = node.next) delete node.next;
|
|
355
|
-
|
|
356
|
-
// If there are multiple coincident points, remove just the point.
|
|
357
|
-
if (previous) return (next ? previous.next = next : delete previous.next), this;
|
|
358
|
-
|
|
359
|
-
// If this is the root point, remove it.
|
|
360
|
-
if (!parent) return this._root = next, this;
|
|
361
|
-
|
|
362
|
-
// Remove this leaf.
|
|
363
|
-
next ? parent[i] = next : delete parent[i];
|
|
364
|
-
|
|
365
|
-
// If the parent now contains exactly one leaf, collapse superfluous parents.
|
|
366
|
-
if ((node = parent[0] || parent[1] || parent[2] || parent[3])
|
|
367
|
-
&& node === (parent[3] || parent[2] || parent[1] || parent[0])
|
|
368
|
-
&& !node.length) {
|
|
369
|
-
if (retainer) retainer[j] = node;
|
|
370
|
-
else this._root = node;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return this;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function removeAll(data) {
|
|
377
|
-
for (var i = 0, n = data.length; i < n; ++i) this.remove(data[i]);
|
|
378
|
-
return this;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function tree_root() {
|
|
382
|
-
return this._root;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function tree_size() {
|
|
386
|
-
var size = 0;
|
|
387
|
-
this.visit(function(node) {
|
|
388
|
-
if (!node.length) do ++size; while (node = node.next)
|
|
389
|
-
});
|
|
390
|
-
return size;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function tree_visit(callback) {
|
|
394
|
-
var quads = [], q, node = this._root, child, x0, y0, x1, y1;
|
|
395
|
-
if (node) quads.push(new Quad(node, this._x0, this._y0, this._x1, this._y1));
|
|
396
|
-
while (q = quads.pop()) {
|
|
397
|
-
if (!callback(node = q.node, x0 = q.x0, y0 = q.y0, x1 = q.x1, y1 = q.y1) && node.length) {
|
|
398
|
-
var xm = (x0 + x1) / 2, ym = (y0 + y1) / 2;
|
|
399
|
-
if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1));
|
|
400
|
-
if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1));
|
|
401
|
-
if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym));
|
|
402
|
-
if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym));
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return this;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function tree_visitAfter(callback) {
|
|
409
|
-
var quads = [], next = [], q;
|
|
410
|
-
if (this._root) quads.push(new Quad(this._root, this._x0, this._y0, this._x1, this._y1));
|
|
411
|
-
while (q = quads.pop()) {
|
|
412
|
-
var node = q.node;
|
|
413
|
-
if (node.length) {
|
|
414
|
-
var child, x0 = q.x0, y0 = q.y0, x1 = q.x1, y1 = q.y1, xm = (x0 + x1) / 2, ym = (y0 + y1) / 2;
|
|
415
|
-
if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym));
|
|
416
|
-
if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym));
|
|
417
|
-
if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1));
|
|
418
|
-
if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1));
|
|
419
|
-
}
|
|
420
|
-
next.push(q);
|
|
421
|
-
}
|
|
422
|
-
while (q = next.pop()) {
|
|
423
|
-
callback(q.node, q.x0, q.y0, q.x1, q.y1);
|
|
424
|
-
}
|
|
425
|
-
return this;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function defaultX(d) {
|
|
429
|
-
return d[0];
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function tree_x(_) {
|
|
433
|
-
return arguments.length ? (this._x = _, this) : this._x;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function defaultY(d) {
|
|
437
|
-
return d[1];
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function tree_y(_) {
|
|
441
|
-
return arguments.length ? (this._y = _, this) : this._y;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function quadtree(nodes, x, y) {
|
|
445
|
-
var tree = new Quadtree(x == null ? defaultX : x, y == null ? defaultY : y, NaN, NaN, NaN, NaN);
|
|
446
|
-
return nodes == null ? tree : tree.addAll(nodes);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function Quadtree(x, y, x0, y0, x1, y1) {
|
|
450
|
-
this._x = x;
|
|
451
|
-
this._y = y;
|
|
452
|
-
this._x0 = x0;
|
|
453
|
-
this._y0 = y0;
|
|
454
|
-
this._x1 = x1;
|
|
455
|
-
this._y1 = y1;
|
|
456
|
-
this._root = undefined;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function leaf_copy(leaf) {
|
|
460
|
-
var copy = {data: leaf.data}, next = copy;
|
|
461
|
-
while (leaf = leaf.next) next = next.next = {data: leaf.data};
|
|
462
|
-
return copy;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
var treeProto = quadtree.prototype = Quadtree.prototype;
|
|
466
|
-
|
|
467
|
-
treeProto.copy = function() {
|
|
468
|
-
var copy = new Quadtree(this._x, this._y, this._x0, this._y0, this._x1, this._y1),
|
|
469
|
-
node = this._root,
|
|
470
|
-
nodes,
|
|
471
|
-
child;
|
|
472
|
-
|
|
473
|
-
if (!node) return copy;
|
|
474
|
-
|
|
475
|
-
if (!node.length) return copy._root = leaf_copy(node), copy;
|
|
476
|
-
|
|
477
|
-
nodes = [{source: node, target: copy._root = new Array(4)}];
|
|
478
|
-
while (node = nodes.pop()) {
|
|
479
|
-
for (var i = 0; i < 4; ++i) {
|
|
480
|
-
if (child = node.source[i]) {
|
|
481
|
-
if (child.length) nodes.push({source: child, target: node.target[i] = new Array(4)});
|
|
482
|
-
else node.target[i] = leaf_copy(child);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
return copy;
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
treeProto.add = tree_add;
|
|
491
|
-
treeProto.addAll = addAll;
|
|
492
|
-
treeProto.cover = tree_cover;
|
|
493
|
-
treeProto.data = tree_data;
|
|
494
|
-
treeProto.extent = tree_extent;
|
|
495
|
-
treeProto.find = tree_find;
|
|
496
|
-
treeProto.remove = tree_remove;
|
|
497
|
-
treeProto.removeAll = removeAll;
|
|
498
|
-
treeProto.root = tree_root;
|
|
499
|
-
treeProto.size = tree_size;
|
|
500
|
-
treeProto.visit = tree_visit;
|
|
501
|
-
treeProto.visitAfter = tree_visitAfter;
|
|
502
|
-
treeProto.x = tree_x;
|
|
503
|
-
treeProto.y = tree_y;
|
|
504
|
-
|
|
505
|
-
function constant(x) {
|
|
506
|
-
return function() {
|
|
507
|
-
return x;
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function jiggle(random) {
|
|
512
|
-
return (random() - 0.5) * 1e-6;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function x$1(d) {
|
|
516
|
-
return d.x + d.vx;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function y$1(d) {
|
|
520
|
-
return d.y + d.vy;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
function forceCollide(radius) {
|
|
524
|
-
var nodes,
|
|
525
|
-
radii,
|
|
526
|
-
random,
|
|
527
|
-
strength = 1,
|
|
528
|
-
iterations = 1;
|
|
529
|
-
|
|
530
|
-
if (typeof radius !== "function") radius = constant(radius == null ? 1 : +radius);
|
|
531
|
-
|
|
532
|
-
function force() {
|
|
533
|
-
var i, n = nodes.length,
|
|
534
|
-
tree,
|
|
535
|
-
node,
|
|
536
|
-
xi,
|
|
537
|
-
yi,
|
|
538
|
-
ri,
|
|
539
|
-
ri2;
|
|
540
|
-
|
|
541
|
-
for (var k = 0; k < iterations; ++k) {
|
|
542
|
-
tree = quadtree(nodes, x$1, y$1).visitAfter(prepare);
|
|
543
|
-
for (i = 0; i < n; ++i) {
|
|
544
|
-
node = nodes[i];
|
|
545
|
-
ri = radii[node.index], ri2 = ri * ri;
|
|
546
|
-
xi = node.x + node.vx;
|
|
547
|
-
yi = node.y + node.vy;
|
|
548
|
-
tree.visit(apply);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function apply(quad, x0, y0, x1, y1) {
|
|
553
|
-
var data = quad.data, rj = quad.r, r = ri + rj;
|
|
554
|
-
if (data) {
|
|
555
|
-
if (data.index > node.index) {
|
|
556
|
-
var x = xi - data.x - data.vx,
|
|
557
|
-
y = yi - data.y - data.vy,
|
|
558
|
-
l = x * x + y * y;
|
|
559
|
-
if (l < r * r) {
|
|
560
|
-
if (x === 0) x = jiggle(random), l += x * x;
|
|
561
|
-
if (y === 0) y = jiggle(random), l += y * y;
|
|
562
|
-
l = (r - (l = Math.sqrt(l))) / l * strength;
|
|
563
|
-
node.vx += (x *= l) * (r = (rj *= rj) / (ri2 + rj));
|
|
564
|
-
node.vy += (y *= l) * r;
|
|
565
|
-
data.vx -= x * (r = 1 - r);
|
|
566
|
-
data.vy -= y * r;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
return x0 > xi + r || x1 < xi - r || y0 > yi + r || y1 < yi - r;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function prepare(quad) {
|
|
576
|
-
if (quad.data) return quad.r = radii[quad.data.index];
|
|
577
|
-
for (var i = quad.r = 0; i < 4; ++i) {
|
|
578
|
-
if (quad[i] && quad[i].r > quad.r) {
|
|
579
|
-
quad.r = quad[i].r;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function initialize() {
|
|
585
|
-
if (!nodes) return;
|
|
586
|
-
var i, n = nodes.length, node;
|
|
587
|
-
radii = new Array(n);
|
|
588
|
-
for (i = 0; i < n; ++i) node = nodes[i], radii[node.index] = +radius(node, i, nodes);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
force.initialize = function(_nodes, _random) {
|
|
592
|
-
nodes = _nodes;
|
|
593
|
-
random = _random;
|
|
594
|
-
initialize();
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
force.iterations = function(_) {
|
|
598
|
-
return arguments.length ? (iterations = +_, force) : iterations;
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
force.strength = function(_) {
|
|
602
|
-
return arguments.length ? (strength = +_, force) : strength;
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
force.radius = function(_) {
|
|
606
|
-
return arguments.length ? (radius = typeof _ === "function" ? _ : constant(+_), initialize(), force) : radius;
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
return force;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
var noop = {value: () => {}};
|
|
613
|
-
|
|
614
|
-
function dispatch() {
|
|
615
|
-
for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
|
|
616
|
-
if (!(t = arguments[i] + "") || (t in _) || /[\s.]/.test(t)) throw new Error("illegal type: " + t);
|
|
617
|
-
_[t] = [];
|
|
618
|
-
}
|
|
619
|
-
return new Dispatch(_);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
function Dispatch(_) {
|
|
623
|
-
this._ = _;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
function parseTypenames(typenames, types) {
|
|
627
|
-
return typenames.trim().split(/^|\s+/).map(function(t) {
|
|
628
|
-
var name = "", i = t.indexOf(".");
|
|
629
|
-
if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
|
|
630
|
-
if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
|
|
631
|
-
return {type: t, name: name};
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
Dispatch.prototype = dispatch.prototype = {
|
|
636
|
-
constructor: Dispatch,
|
|
637
|
-
on: function(typename, callback) {
|
|
638
|
-
var _ = this._,
|
|
639
|
-
T = parseTypenames(typename + "", _),
|
|
640
|
-
t,
|
|
641
|
-
i = -1,
|
|
642
|
-
n = T.length;
|
|
643
|
-
|
|
644
|
-
// If no callback was specified, return the callback of the given type and name.
|
|
645
|
-
if (arguments.length < 2) {
|
|
646
|
-
while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t;
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// If a type was specified, set the callback for the given type and name.
|
|
651
|
-
// Otherwise, if a null callback was specified, remove callbacks of the given name.
|
|
652
|
-
if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);
|
|
653
|
-
while (++i < n) {
|
|
654
|
-
if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback);
|
|
655
|
-
else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
return this;
|
|
659
|
-
},
|
|
660
|
-
copy: function() {
|
|
661
|
-
var copy = {}, _ = this._;
|
|
662
|
-
for (var t in _) copy[t] = _[t].slice();
|
|
663
|
-
return new Dispatch(copy);
|
|
664
|
-
},
|
|
665
|
-
call: function(type, that) {
|
|
666
|
-
if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];
|
|
667
|
-
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
|
|
668
|
-
for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
|
|
669
|
-
},
|
|
670
|
-
apply: function(type, that, args) {
|
|
671
|
-
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
|
|
672
|
-
for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
|
|
673
|
-
}
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
function get(type, name) {
|
|
677
|
-
for (var i = 0, n = type.length, c; i < n; ++i) {
|
|
678
|
-
if ((c = type[i]).name === name) {
|
|
679
|
-
return c.value;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
function set(type, name, callback) {
|
|
685
|
-
for (var i = 0, n = type.length; i < n; ++i) {
|
|
686
|
-
if (type[i].name === name) {
|
|
687
|
-
type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1));
|
|
688
|
-
break;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
if (callback != null) type.push({name: name, value: callback});
|
|
692
|
-
return type;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
var frame = 0, // is an animation frame pending?
|
|
696
|
-
timeout = 0, // is a timeout pending?
|
|
697
|
-
interval = 0, // are any timers active?
|
|
698
|
-
pokeDelay = 1000, // how frequently we check for clock skew
|
|
699
|
-
taskHead,
|
|
700
|
-
taskTail,
|
|
701
|
-
clockLast = 0,
|
|
702
|
-
clockNow = 0,
|
|
703
|
-
clockSkew = 0,
|
|
704
|
-
clock = typeof performance === "object" && performance.now ? performance : Date,
|
|
705
|
-
setFrame = typeof window === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function(f) { setTimeout(f, 17); };
|
|
706
|
-
|
|
707
|
-
function now() {
|
|
708
|
-
return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
function clearNow() {
|
|
712
|
-
clockNow = 0;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
function Timer() {
|
|
716
|
-
this._call =
|
|
717
|
-
this._time =
|
|
718
|
-
this._next = null;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
Timer.prototype = timer.prototype = {
|
|
722
|
-
constructor: Timer,
|
|
723
|
-
restart: function(callback, delay, time) {
|
|
724
|
-
if (typeof callback !== "function") throw new TypeError("callback is not a function");
|
|
725
|
-
time = (time == null ? now() : +time) + (delay == null ? 0 : +delay);
|
|
726
|
-
if (!this._next && taskTail !== this) {
|
|
727
|
-
if (taskTail) taskTail._next = this;
|
|
728
|
-
else taskHead = this;
|
|
729
|
-
taskTail = this;
|
|
730
|
-
}
|
|
731
|
-
this._call = callback;
|
|
732
|
-
this._time = time;
|
|
733
|
-
sleep();
|
|
734
|
-
},
|
|
735
|
-
stop: function() {
|
|
736
|
-
if (this._call) {
|
|
737
|
-
this._call = null;
|
|
738
|
-
this._time = Infinity;
|
|
739
|
-
sleep();
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
function timer(callback, delay, time) {
|
|
745
|
-
var t = new Timer;
|
|
746
|
-
t.restart(callback, delay, time);
|
|
747
|
-
return t;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
function timerFlush() {
|
|
751
|
-
now(); // Get the current time, if not already set.
|
|
752
|
-
++frame; // Pretend we’ve set an alarm, if we haven’t already.
|
|
753
|
-
var t = taskHead, e;
|
|
754
|
-
while (t) {
|
|
755
|
-
if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e);
|
|
756
|
-
t = t._next;
|
|
757
|
-
}
|
|
758
|
-
--frame;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
function wake() {
|
|
762
|
-
clockNow = (clockLast = clock.now()) + clockSkew;
|
|
763
|
-
frame = timeout = 0;
|
|
764
|
-
try {
|
|
765
|
-
timerFlush();
|
|
766
|
-
} finally {
|
|
767
|
-
frame = 0;
|
|
768
|
-
nap();
|
|
769
|
-
clockNow = 0;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
function poke() {
|
|
774
|
-
var now = clock.now(), delay = now - clockLast;
|
|
775
|
-
if (delay > pokeDelay) clockSkew -= delay, clockLast = now;
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
function nap() {
|
|
779
|
-
var t0, t1 = taskHead, t2, time = Infinity;
|
|
780
|
-
while (t1) {
|
|
781
|
-
if (t1._call) {
|
|
782
|
-
if (time > t1._time) time = t1._time;
|
|
783
|
-
t0 = t1, t1 = t1._next;
|
|
784
|
-
} else {
|
|
785
|
-
t2 = t1._next, t1._next = null;
|
|
786
|
-
t1 = t0 ? t0._next = t2 : taskHead = t2;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
taskTail = t0;
|
|
790
|
-
sleep(time);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
function sleep(time) {
|
|
794
|
-
if (frame) return; // Soonest alarm already set, or will be.
|
|
795
|
-
if (timeout) timeout = clearTimeout(timeout);
|
|
796
|
-
var delay = time - clockNow; // Strictly less than if we recomputed clockNow.
|
|
797
|
-
if (delay > 24) {
|
|
798
|
-
if (time < Infinity) timeout = setTimeout(wake, time - clock.now() - clockSkew);
|
|
799
|
-
if (interval) interval = clearInterval(interval);
|
|
800
|
-
} else {
|
|
801
|
-
if (!interval) clockLast = clock.now(), interval = setInterval(poke, pokeDelay);
|
|
802
|
-
frame = 1, setFrame(wake);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
// https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use
|
|
807
|
-
const a = 1664525;
|
|
808
|
-
const c = 1013904223;
|
|
809
|
-
const m = 4294967296; // 2^32
|
|
810
|
-
|
|
811
|
-
function lcg() {
|
|
812
|
-
let s = 1;
|
|
813
|
-
return () => (s = (a * s + c) % m) / m;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
function x(d) {
|
|
817
|
-
return d.x;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
function y(d) {
|
|
821
|
-
return d.y;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
var initialRadius = 10,
|
|
825
|
-
initialAngle = Math.PI * (3 - Math.sqrt(5));
|
|
826
|
-
|
|
827
|
-
function forceSimulation(nodes) {
|
|
828
|
-
var simulation,
|
|
829
|
-
alpha = 1,
|
|
830
|
-
alphaMin = 0.001,
|
|
831
|
-
alphaDecay = 1 - Math.pow(alphaMin, 1 / 300),
|
|
832
|
-
alphaTarget = 0,
|
|
833
|
-
velocityDecay = 0.6,
|
|
834
|
-
forces = new Map(),
|
|
835
|
-
stepper = timer(step),
|
|
836
|
-
event = dispatch("tick", "end"),
|
|
837
|
-
random = lcg();
|
|
838
|
-
|
|
839
|
-
if (nodes == null) nodes = [];
|
|
840
|
-
|
|
841
|
-
function step() {
|
|
842
|
-
tick();
|
|
843
|
-
event.call("tick", simulation);
|
|
844
|
-
if (alpha < alphaMin) {
|
|
845
|
-
stepper.stop();
|
|
846
|
-
event.call("end", simulation);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function tick(iterations) {
|
|
851
|
-
var i, n = nodes.length, node;
|
|
852
|
-
|
|
853
|
-
if (iterations === undefined) iterations = 1;
|
|
854
|
-
|
|
855
|
-
for (var k = 0; k < iterations; ++k) {
|
|
856
|
-
alpha += (alphaTarget - alpha) * alphaDecay;
|
|
857
|
-
|
|
858
|
-
forces.forEach(function(force) {
|
|
859
|
-
force(alpha);
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
for (i = 0; i < n; ++i) {
|
|
863
|
-
node = nodes[i];
|
|
864
|
-
if (node.fx == null) node.x += node.vx *= velocityDecay;
|
|
865
|
-
else node.x = node.fx, node.vx = 0;
|
|
866
|
-
if (node.fy == null) node.y += node.vy *= velocityDecay;
|
|
867
|
-
else node.y = node.fy, node.vy = 0;
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
return simulation;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
function initializeNodes() {
|
|
875
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
876
|
-
node = nodes[i], node.index = i;
|
|
877
|
-
if (node.fx != null) node.x = node.fx;
|
|
878
|
-
if (node.fy != null) node.y = node.fy;
|
|
879
|
-
if (isNaN(node.x) || isNaN(node.y)) {
|
|
880
|
-
var radius = initialRadius * Math.sqrt(0.5 + i), angle = i * initialAngle;
|
|
881
|
-
node.x = radius * Math.cos(angle);
|
|
882
|
-
node.y = radius * Math.sin(angle);
|
|
883
|
-
}
|
|
884
|
-
if (isNaN(node.vx) || isNaN(node.vy)) {
|
|
885
|
-
node.vx = node.vy = 0;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
function initializeForce(force) {
|
|
891
|
-
if (force.initialize) force.initialize(nodes, random);
|
|
892
|
-
return force;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
initializeNodes();
|
|
896
|
-
|
|
897
|
-
return simulation = {
|
|
898
|
-
tick: tick,
|
|
899
|
-
|
|
900
|
-
restart: function() {
|
|
901
|
-
return stepper.restart(step), simulation;
|
|
902
|
-
},
|
|
903
|
-
|
|
904
|
-
stop: function() {
|
|
905
|
-
return stepper.stop(), simulation;
|
|
906
|
-
},
|
|
907
|
-
|
|
908
|
-
nodes: function(_) {
|
|
909
|
-
return arguments.length ? (nodes = _, initializeNodes(), forces.forEach(initializeForce), simulation) : nodes;
|
|
910
|
-
},
|
|
911
|
-
|
|
912
|
-
alpha: function(_) {
|
|
913
|
-
return arguments.length ? (alpha = +_, simulation) : alpha;
|
|
914
|
-
},
|
|
915
|
-
|
|
916
|
-
alphaMin: function(_) {
|
|
917
|
-
return arguments.length ? (alphaMin = +_, simulation) : alphaMin;
|
|
918
|
-
},
|
|
919
|
-
|
|
920
|
-
alphaDecay: function(_) {
|
|
921
|
-
return arguments.length ? (alphaDecay = +_, simulation) : +alphaDecay;
|
|
922
|
-
},
|
|
923
|
-
|
|
924
|
-
alphaTarget: function(_) {
|
|
925
|
-
return arguments.length ? (alphaTarget = +_, simulation) : alphaTarget;
|
|
926
|
-
},
|
|
927
|
-
|
|
928
|
-
velocityDecay: function(_) {
|
|
929
|
-
return arguments.length ? (velocityDecay = 1 - _, simulation) : 1 - velocityDecay;
|
|
930
|
-
},
|
|
931
|
-
|
|
932
|
-
randomSource: function(_) {
|
|
933
|
-
return arguments.length ? (random = _, forces.forEach(initializeForce), simulation) : random;
|
|
934
|
-
},
|
|
935
|
-
|
|
936
|
-
force: function(name, _) {
|
|
937
|
-
return arguments.length > 1 ? ((_ == null ? forces.delete(name) : forces.set(name, initializeForce(_))), simulation) : forces.get(name);
|
|
938
|
-
},
|
|
939
|
-
|
|
940
|
-
find: function(x, y, radius) {
|
|
941
|
-
var i = 0,
|
|
942
|
-
n = nodes.length,
|
|
943
|
-
dx,
|
|
944
|
-
dy,
|
|
945
|
-
d2,
|
|
946
|
-
node,
|
|
947
|
-
closest;
|
|
948
|
-
|
|
949
|
-
if (radius == null) radius = Infinity;
|
|
950
|
-
else radius *= radius;
|
|
951
|
-
|
|
952
|
-
for (i = 0; i < n; ++i) {
|
|
953
|
-
node = nodes[i];
|
|
954
|
-
dx = x - node.x;
|
|
955
|
-
dy = y - node.y;
|
|
956
|
-
d2 = dx * dx + dy * dy;
|
|
957
|
-
if (d2 < radius) closest = node, radius = d2;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
return closest;
|
|
961
|
-
},
|
|
962
|
-
|
|
963
|
-
on: function(name, _) {
|
|
964
|
-
return arguments.length > 1 ? (event.on(name, _), simulation) : event.on(name);
|
|
965
|
-
}
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
function forceManyBody() {
|
|
970
|
-
var nodes,
|
|
971
|
-
node,
|
|
972
|
-
random,
|
|
973
|
-
alpha,
|
|
974
|
-
strength = constant(-30),
|
|
975
|
-
strengths,
|
|
976
|
-
distanceMin2 = 1,
|
|
977
|
-
distanceMax2 = Infinity,
|
|
978
|
-
theta2 = 0.81;
|
|
979
|
-
|
|
980
|
-
function force(_) {
|
|
981
|
-
var i, n = nodes.length, tree = quadtree(nodes, x, y).visitAfter(accumulate);
|
|
982
|
-
for (alpha = _, i = 0; i < n; ++i) node = nodes[i], tree.visit(apply);
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
function initialize() {
|
|
986
|
-
if (!nodes) return;
|
|
987
|
-
var i, n = nodes.length, node;
|
|
988
|
-
strengths = new Array(n);
|
|
989
|
-
for (i = 0; i < n; ++i) node = nodes[i], strengths[node.index] = +strength(node, i, nodes);
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
function accumulate(quad) {
|
|
993
|
-
var strength = 0, q, c, weight = 0, x, y, i;
|
|
994
|
-
|
|
995
|
-
// For internal nodes, accumulate forces from child quadrants.
|
|
996
|
-
if (quad.length) {
|
|
997
|
-
for (x = y = i = 0; i < 4; ++i) {
|
|
998
|
-
if ((q = quad[i]) && (c = Math.abs(q.value))) {
|
|
999
|
-
strength += q.value, weight += c, x += c * q.x, y += c * q.y;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
quad.x = x / weight;
|
|
1003
|
-
quad.y = y / weight;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// For leaf nodes, accumulate forces from coincident quadrants.
|
|
1007
|
-
else {
|
|
1008
|
-
q = quad;
|
|
1009
|
-
q.x = q.data.x;
|
|
1010
|
-
q.y = q.data.y;
|
|
1011
|
-
do strength += strengths[q.data.index];
|
|
1012
|
-
while (q = q.next);
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
quad.value = strength;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
function apply(quad, x1, _, x2) {
|
|
1019
|
-
if (!quad.value) return true;
|
|
1020
|
-
|
|
1021
|
-
var x = quad.x - node.x,
|
|
1022
|
-
y = quad.y - node.y,
|
|
1023
|
-
w = x2 - x1,
|
|
1024
|
-
l = x * x + y * y;
|
|
1025
|
-
|
|
1026
|
-
// Apply the Barnes-Hut approximation if possible.
|
|
1027
|
-
// Limit forces for very close nodes; randomize direction if coincident.
|
|
1028
|
-
if (w * w / theta2 < l) {
|
|
1029
|
-
if (l < distanceMax2) {
|
|
1030
|
-
if (x === 0) x = jiggle(random), l += x * x;
|
|
1031
|
-
if (y === 0) y = jiggle(random), l += y * y;
|
|
1032
|
-
if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l);
|
|
1033
|
-
node.vx += x * quad.value * alpha / l;
|
|
1034
|
-
node.vy += y * quad.value * alpha / l;
|
|
1035
|
-
}
|
|
1036
|
-
return true;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
// Otherwise, process points directly.
|
|
1040
|
-
else if (quad.length || l >= distanceMax2) return;
|
|
1041
|
-
|
|
1042
|
-
// Limit forces for very close nodes; randomize direction if coincident.
|
|
1043
|
-
if (quad.data !== node || quad.next) {
|
|
1044
|
-
if (x === 0) x = jiggle(random), l += x * x;
|
|
1045
|
-
if (y === 0) y = jiggle(random), l += y * y;
|
|
1046
|
-
if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l);
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
do if (quad.data !== node) {
|
|
1050
|
-
w = strengths[quad.data.index] * alpha / l;
|
|
1051
|
-
node.vx += x * w;
|
|
1052
|
-
node.vy += y * w;
|
|
1053
|
-
} while (quad = quad.next);
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
force.initialize = function(_nodes, _random) {
|
|
1057
|
-
nodes = _nodes;
|
|
1058
|
-
random = _random;
|
|
1059
|
-
initialize();
|
|
1060
|
-
};
|
|
1061
|
-
|
|
1062
|
-
force.strength = function(_) {
|
|
1063
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
|
|
1064
|
-
};
|
|
1065
|
-
|
|
1066
|
-
force.distanceMin = function(_) {
|
|
1067
|
-
return arguments.length ? (distanceMin2 = _ * _, force) : Math.sqrt(distanceMin2);
|
|
1068
|
-
};
|
|
1069
|
-
|
|
1070
|
-
force.distanceMax = function(_) {
|
|
1071
|
-
return arguments.length ? (distanceMax2 = _ * _, force) : Math.sqrt(distanceMax2);
|
|
1072
|
-
};
|
|
1073
|
-
|
|
1074
|
-
force.theta = function(_) {
|
|
1075
|
-
return arguments.length ? (theta2 = _ * _, force) : Math.sqrt(theta2);
|
|
1076
|
-
};
|
|
1077
|
-
|
|
1078
|
-
return force;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
function forceX(x) {
|
|
1082
|
-
var strength = constant(0.1),
|
|
1083
|
-
nodes,
|
|
1084
|
-
strengths,
|
|
1085
|
-
xz;
|
|
1086
|
-
|
|
1087
|
-
if (typeof x !== "function") x = constant(x == null ? 0 : +x);
|
|
1088
|
-
|
|
1089
|
-
function force(alpha) {
|
|
1090
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
1091
|
-
node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha;
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
function initialize() {
|
|
1096
|
-
if (!nodes) return;
|
|
1097
|
-
var i, n = nodes.length;
|
|
1098
|
-
strengths = new Array(n);
|
|
1099
|
-
xz = new Array(n);
|
|
1100
|
-
for (i = 0; i < n; ++i) {
|
|
1101
|
-
strengths[i] = isNaN(xz[i] = +x(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
force.initialize = function(_) {
|
|
1106
|
-
nodes = _;
|
|
1107
|
-
initialize();
|
|
1108
|
-
};
|
|
1109
|
-
|
|
1110
|
-
force.strength = function(_) {
|
|
1111
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
|
|
1112
|
-
};
|
|
1113
|
-
|
|
1114
|
-
force.x = function(_) {
|
|
1115
|
-
return arguments.length ? (x = typeof _ === "function" ? _ : constant(+_), initialize(), force) : x;
|
|
1116
|
-
};
|
|
1117
|
-
|
|
1118
|
-
return force;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
function forceY(y) {
|
|
1122
|
-
var strength = constant(0.1),
|
|
1123
|
-
nodes,
|
|
1124
|
-
strengths,
|
|
1125
|
-
yz;
|
|
1126
|
-
|
|
1127
|
-
if (typeof y !== "function") y = constant(y == null ? 0 : +y);
|
|
1128
|
-
|
|
1129
|
-
function force(alpha) {
|
|
1130
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
1131
|
-
node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha;
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
function initialize() {
|
|
1136
|
-
if (!nodes) return;
|
|
1137
|
-
var i, n = nodes.length;
|
|
1138
|
-
strengths = new Array(n);
|
|
1139
|
-
yz = new Array(n);
|
|
1140
|
-
for (i = 0; i < n; ++i) {
|
|
1141
|
-
strengths[i] = isNaN(yz[i] = +y(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
force.initialize = function(_) {
|
|
1146
|
-
nodes = _;
|
|
1147
|
-
initialize();
|
|
1148
|
-
};
|
|
1149
|
-
|
|
1150
|
-
force.strength = function(_) {
|
|
1151
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
|
|
1152
|
-
};
|
|
1153
|
-
|
|
1154
|
-
force.y = function(_) {
|
|
1155
|
-
return arguments.length ? (y = typeof _ === "function" ? _ : constant(+_), initialize(), force) : y;
|
|
1156
|
-
};
|
|
1157
|
-
|
|
1158
|
-
return force;
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
/** Node count above which extra congestion padding is applied */
|
|
1162
|
-
const CONGESTION_THRESHOLD = 15;
|
|
1163
|
-
/** Per-node padding multiplier for clusters exceeding CONGESTION_THRESHOLD */
|
|
1164
|
-
const CONGESTION_FACTOR = 2.0;
|
|
1165
|
-
/** Base padding added to every cluster radius (pixels) */
|
|
1166
|
-
const BASE_MICRO_PADDING = 50;
|
|
1167
|
-
/**
|
|
1168
|
-
* Role order for dynamic band calculation (center to outside)
|
|
1169
|
-
*/
|
|
1170
|
-
const ROLE_ORDER = [
|
|
1171
|
-
NodeRole.Entry,
|
|
1172
|
-
NodeRole.InternalFramework,
|
|
1173
|
-
NodeRole.InternalLib,
|
|
1174
|
-
NodeRole.Utility,
|
|
1175
|
-
NodeRole.Test,
|
|
1176
|
-
NodeRole.Tool,
|
|
1177
|
-
];
|
|
1178
|
-
/**
|
|
1179
|
-
* Compute micro-layout for a single cluster using "Solar System" physics.
|
|
1180
|
-
* Nodes are placed in concentric bands based on their role, with anchors at the center.
|
|
1181
|
-
* Uses D3 force simulation with orbit, collision, center gravity, and charge forces.
|
|
1182
|
-
*
|
|
1183
|
-
* @param cluster - The cluster to compute interior layout for
|
|
1184
|
-
* @param config - Layout configuration with force parameters
|
|
1185
|
-
* @returns Micro layout result with cluster dimensions and relative node positions
|
|
1186
|
-
*/
|
|
1187
|
-
function computeClusterInterior(cluster, config) {
|
|
1188
|
-
const nodes = cluster.nodes;
|
|
1189
|
-
const n = nodes.length;
|
|
1190
|
-
if (n === 0) {
|
|
1191
|
-
return {
|
|
1192
|
-
clusterId: cluster.id,
|
|
1193
|
-
width: config.minClusterSize,
|
|
1194
|
-
height: config.minClusterSize,
|
|
1195
|
-
relativePositions: new Map(),
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
|
-
// 1. Calculate Dynamic Bands based on node distribution
|
|
1199
|
-
// Each role gets an area proportional to its node count
|
|
1200
|
-
const roleCounts = new Map();
|
|
1201
|
-
for (const node of nodes) {
|
|
1202
|
-
const role = cluster.metadata?.get(node.id)?.role ?? NodeRole.InternalLib;
|
|
1203
|
-
roleCounts.set(role, (roleCounts.get(role) ?? 0) + 1);
|
|
1204
|
-
}
|
|
1205
|
-
let cumulativeCount = 0;
|
|
1206
|
-
const dynamicBands = new Map();
|
|
1207
|
-
for (const role of ROLE_ORDER) {
|
|
1208
|
-
const count = roleCounts.get(role) ?? 0;
|
|
1209
|
-
const prevR = Math.sqrt(cumulativeCount / n);
|
|
1210
|
-
cumulativeCount += count;
|
|
1211
|
-
const nextR = Math.sqrt(cumulativeCount / n);
|
|
1212
|
-
// Add a tiny buffer to empty bands to prevent division by zero in force calculations
|
|
1213
|
-
// if a node somehow ends up there or just for safety
|
|
1214
|
-
if (count === 0) {
|
|
1215
|
-
dynamicBands.set(role, { min: prevR, max: prevR });
|
|
1216
|
-
}
|
|
1217
|
-
else {
|
|
1218
|
-
dynamicBands.set(role, { min: prevR, max: nextR });
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
// 2. Determine Cluster Size
|
|
1222
|
-
// Use power 0.6 instead of 0.5 (sqrt) to make large clusters sparser (more breathing room)
|
|
1223
|
-
// This scales area slightly super-linearly with N, reducing density as N grows
|
|
1224
|
-
const spacingFactor = 1.3;
|
|
1225
|
-
const nodeSpace = config.nodeRadius * 2 + config.clusterNodeSpacing;
|
|
1226
|
-
const baseR = n ** 0.6 * nodeSpace * spacingFactor;
|
|
1227
|
-
// Add extra padding for large clusters
|
|
1228
|
-
const congestionPadding = Math.max(0, n - CONGESTION_THRESHOLD) * CONGESTION_FACTOR;
|
|
1229
|
-
const padding = BASE_MICRO_PADDING + congestionPadding;
|
|
1230
|
-
const radius = Math.max(config.minClusterSize / 2, baseR + padding);
|
|
1231
|
-
const size = radius * 2;
|
|
1232
|
-
// 3. Initialize Simulation Nodes
|
|
1233
|
-
const simNodes = nodes.map((node) => ({
|
|
1234
|
-
id: node.id,
|
|
1235
|
-
x: (Math.random() - 0.5) * 10, // Start near center
|
|
1236
|
-
y: (Math.random() - 0.5) * 10,
|
|
1237
|
-
vx: 0,
|
|
1238
|
-
vy: 0,
|
|
1239
|
-
role: cluster.metadata?.get(node.id)?.role ?? NodeRole.InternalLib,
|
|
1240
|
-
radius: config.nodeRadius,
|
|
1241
|
-
}));
|
|
1242
|
-
// 4. Create Forces
|
|
1243
|
-
const simulation = forceSimulation(simNodes)
|
|
1244
|
-
// A. Collision (prevent overlap)
|
|
1245
|
-
.force('collide', forceCollide().radius((d) => d.radius + config.nodeCollisionPadding))
|
|
1246
|
-
// B. Solar System Orbit (Band-based positioning)
|
|
1247
|
-
.force('orbit', (alpha) => {
|
|
1248
|
-
const k = alpha * 0.8; // Strong confinement to band
|
|
1249
|
-
for (const node of simNodes) {
|
|
1250
|
-
const band = dynamicBands.get(node.role) ?? { min: 0, max: 1 };
|
|
1251
|
-
const minR = band.min * (radius - 20);
|
|
1252
|
-
const maxR = band.max * (radius - 20);
|
|
1253
|
-
// Current distance
|
|
1254
|
-
const d = Math.hypot(node.x, node.y) || 1e-6;
|
|
1255
|
-
// Only apply force if outside band
|
|
1256
|
-
if (d < minR) {
|
|
1257
|
-
const diff = d - minR; // Negative
|
|
1258
|
-
// Push out
|
|
1259
|
-
const fx = (node.x / d) * diff * k;
|
|
1260
|
-
const fy = (node.y / d) * diff * k;
|
|
1261
|
-
node.vx -= fx;
|
|
1262
|
-
node.vy -= fy;
|
|
1263
|
-
}
|
|
1264
|
-
else if (d > maxR) {
|
|
1265
|
-
const diff = d - maxR; // Positive
|
|
1266
|
-
// Pull in
|
|
1267
|
-
const fx = (node.x / d) * diff * k;
|
|
1268
|
-
const fy = (node.y / d) * diff * k;
|
|
1269
|
-
node.vx -= fx;
|
|
1270
|
-
node.vy -= fy;
|
|
1271
|
-
}
|
|
1272
|
-
// Inside band? Drift freely (repulsion handles spacing)
|
|
1273
|
-
}
|
|
1274
|
-
})
|
|
1275
|
-
// C. Center Gravity (keep things coherent but loose)
|
|
1276
|
-
.force('center', forceCenter(0, 0).strength(0.02))
|
|
1277
|
-
// D. Many Body (stronger repulsion to use available space)
|
|
1278
|
-
.force('charge', forceManyBody().strength(config.nodeCharge));
|
|
1279
|
-
// 5. Run Simulation
|
|
1280
|
-
// Micro-layout is small (dozens of nodes), so we can run enough ticks quickly
|
|
1281
|
-
// Break early if the simulation converges before hitting the tick limit
|
|
1282
|
-
const TICKS = 150;
|
|
1283
|
-
for (let i = 0; i < TICKS; i++) {
|
|
1284
|
-
simulation.tick();
|
|
1285
|
-
if (simulation.alpha() < 0.001)
|
|
1286
|
-
break;
|
|
1287
|
-
}
|
|
1288
|
-
simulation.stop();
|
|
1289
|
-
// 6. Extract Positions
|
|
1290
|
-
const relativePositions = new Map();
|
|
1291
|
-
for (const node of simNodes) {
|
|
1292
|
-
relativePositions.set(node.id, {
|
|
1293
|
-
id: node.id,
|
|
1294
|
-
clusterId: cluster.id,
|
|
1295
|
-
x: node.x,
|
|
1296
|
-
y: node.y,
|
|
1297
|
-
vx: 0,
|
|
1298
|
-
vy: 0,
|
|
1299
|
-
radius: node.radius,
|
|
1300
|
-
});
|
|
1301
|
-
}
|
|
1302
|
-
return {
|
|
1303
|
-
clusterId: cluster.id,
|
|
1304
|
-
width: size,
|
|
1305
|
-
height: size,
|
|
1306
|
-
relativePositions,
|
|
1307
|
-
};
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
/**
|
|
1311
|
-
* Apply a gentle force-directed "massage" to nodes within a cluster.
|
|
1312
|
-
* Runs after the main "Solar System" layout to improve spacing and resolve
|
|
1313
|
-
* local congestions while preserving the overall band structure.
|
|
1314
|
-
*
|
|
1315
|
-
* @param micro - Micro layout result from `computeClusterInterior`
|
|
1316
|
-
* @param config - Layout configuration with collision and charge parameters
|
|
1317
|
-
* @returns Updated micro layout with refined node positions and possibly expanded dimensions
|
|
1318
|
-
*/
|
|
1319
|
-
function applyNodeMassage(micro, config) {
|
|
1320
|
-
const nodes = Array.from(micro.relativePositions.values()).map((pos) => ({
|
|
1321
|
-
...pos,
|
|
1322
|
-
// Store original positions for tethering
|
|
1323
|
-
ox: pos.x,
|
|
1324
|
-
oy: pos.y,
|
|
1325
|
-
}));
|
|
1326
|
-
if (nodes.length === 0)
|
|
1327
|
-
return micro;
|
|
1328
|
-
const simulation = forceSimulation(nodes)
|
|
1329
|
-
// 1. Anchor to original positions (preserve Solar System bands)
|
|
1330
|
-
.force('x', forceX((d) => d.ox).strength(0.1))
|
|
1331
|
-
.force('y', forceY((d) => d.oy).strength(0.1))
|
|
1332
|
-
// 2. Gentle repulsion to "fluff" the cluster
|
|
1333
|
-
.force('charge', forceManyBody().strength(-30))
|
|
1334
|
-
// 3. Hard collision to ensure no overlaps
|
|
1335
|
-
.force('collide', forceCollide()
|
|
1336
|
-
.radius((d) => (d.radius ?? config.nodeRadius) + config.clusterNodeSpacing)
|
|
1337
|
-
.strength(0.9)
|
|
1338
|
-
.iterations(2))
|
|
1339
|
-
.stop();
|
|
1340
|
-
// Run a short burst, with early exit on convergence
|
|
1341
|
-
const TICKS = 50;
|
|
1342
|
-
for (let i = 0; i < TICKS; i++) {
|
|
1343
|
-
simulation.tick();
|
|
1344
|
-
if (simulation.alpha() < 0.001)
|
|
1345
|
-
break;
|
|
1346
|
-
}
|
|
1347
|
-
// Update positions and recalculate bounds
|
|
1348
|
-
const newPositions = new Map();
|
|
1349
|
-
let minX = Infinity, maxX = -Infinity;
|
|
1350
|
-
let minY = Infinity, maxY = -Infinity;
|
|
1351
|
-
for (const node of nodes) {
|
|
1352
|
-
const existingPos = micro.relativePositions.get(node.id);
|
|
1353
|
-
if (existingPos) {
|
|
1354
|
-
newPositions.set(node.id, {
|
|
1355
|
-
...existingPos,
|
|
1356
|
-
x: node.x,
|
|
1357
|
-
y: node.y,
|
|
1358
|
-
});
|
|
1359
|
-
}
|
|
1360
|
-
if (node.x < minX)
|
|
1361
|
-
minX = node.x;
|
|
1362
|
-
if (node.x > maxX)
|
|
1363
|
-
maxX = node.x;
|
|
1364
|
-
if (node.y < minY)
|
|
1365
|
-
minY = node.y;
|
|
1366
|
-
if (node.y > maxY)
|
|
1367
|
-
maxY = node.y;
|
|
1368
|
-
}
|
|
1369
|
-
// Ensure we don't shrink below original calculated size (which had "breathing room" logic)
|
|
1370
|
-
// but do expand if needed.
|
|
1371
|
-
// Actually, let's keep it simple: Expand if bounding box exceeds original size.
|
|
1372
|
-
// Assuming 0,0 center, the radius is max(abs(min), abs(max))
|
|
1373
|
-
const maxDim = Math.max(Math.abs(minX), Math.abs(maxX), Math.abs(minY), Math.abs(maxY));
|
|
1374
|
-
const neededSize = maxDim * 2 + 100; // + padding
|
|
1375
|
-
const finalSize = Math.max(micro.width, neededSize);
|
|
1376
|
-
return {
|
|
1377
|
-
...micro,
|
|
1378
|
-
relativePositions: newPositions,
|
|
1379
|
-
width: finalSize,
|
|
1380
|
-
height: finalSize,
|
|
1381
|
-
};
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
/**
|
|
1385
|
-
* Micro-Layout Web Worker
|
|
1386
|
-
*
|
|
1387
|
-
* Runs computeClusterInterior + applyNodeMassage for a single cluster
|
|
1388
|
-
* off the main thread. Used by parallel-micro.ts to parallelize
|
|
1389
|
-
* micro-layout computation across multiple workers.
|
|
1390
|
-
*/
|
|
1391
|
-
/**
|
|
1392
|
-
* Converts a serialized cluster (with arrays) back to the internal Cluster type (with Maps).
|
|
1393
|
-
* @param sc - Serialized cluster received from the main thread
|
|
1394
|
-
* @returns Deserialized Cluster instance
|
|
1395
|
-
*/
|
|
1396
|
-
function deserializeCluster(sc) {
|
|
1397
|
-
return {
|
|
1398
|
-
...sc,
|
|
1399
|
-
metadata: new Map(sc.metadata),
|
|
1400
|
-
};
|
|
1401
|
-
}
|
|
1402
|
-
const workerApi = {
|
|
1403
|
-
computeMicro(serializedCluster, config) {
|
|
1404
|
-
const cluster = deserializeCluster(serializedCluster);
|
|
1405
|
-
let micro = computeClusterInterior(cluster, config);
|
|
1406
|
-
micro = applyNodeMassage(micro, config);
|
|
1407
|
-
return {
|
|
1408
|
-
clusterId: micro.clusterId,
|
|
1409
|
-
width: micro.width,
|
|
1410
|
-
height: micro.height,
|
|
1411
|
-
relativePositions: Array.from(micro.relativePositions.entries()),
|
|
1412
|
-
};
|
|
1413
|
-
},
|
|
1414
|
-
};
|
|
1415
|
-
/* v8 ignore start */
|
|
1416
|
-
self.onmessage = (e) => {
|
|
1417
|
-
const result = workerApi.computeMicro(e.data.cluster, e.data.config);
|
|
1418
|
-
self.postMessage(result);
|
|
1419
|
-
};
|
|
1420
|
-
/* v8 ignore stop */
|
|
1
|
+
var e;(function(e){e.Entry=`entry`,e.InternalFramework=`internal-framework`,e.InternalLib=`internal-lib`,e.Utility=`utility`,e.Test=`test`,e.Tool=`tool`})(e||={});var t;(function(e){e.Project=`project`,e.Package=`package`})(t||={});var n;(function(e){e.Inherit=`INHERIT`,e.IncludeChildren=`INCLUDE_CHILDREN`,e.SeparateChildren=`SEPARATE_CHILDREN`})(n||={}),Object.values(e),Object.values(t);function r(e,t){var n,r=1;e??=0,t??=0;function i(){var i,a=n.length,o,s=0,c=0;for(i=0;i<a;++i)o=n[i],s+=o.x,c+=o.y;for(s=(s/a-e)*r,c=(c/a-t)*r,i=0;i<a;++i)o=n[i],o.x-=s,o.y-=c}return i.initialize=function(e){n=e},i.x=function(t){return arguments.length?(e=+t,i):e},i.y=function(e){return arguments.length?(t=+e,i):t},i.strength=function(e){return arguments.length?(r=+e,i):r},i}function i(e){let t=+this._x.call(null,e),n=+this._y.call(null,e);return a(this.cover(t,n),t,n,e)}function a(e,t,n,r){if(isNaN(t)||isNaN(n))return e;var i,a=e._root,o={data:r},s=e._x0,c=e._y0,l=e._x1,u=e._y1,d,f,p,m,h,g,_,v;if(!a)return e._root=o,e;for(;a.length;)if((h=t>=(d=(s+l)/2))?s=d:l=d,(g=n>=(f=(c+u)/2))?c=f:u=f,i=a,!(a=a[_=g<<1|h]))return i[_]=o,e;if(p=+e._x.call(null,a.data),m=+e._y.call(null,a.data),t===p&&n===m)return o.next=a,i?i[_]=o:e._root=o,e;do i=i?i[_]=[,,,,]:e._root=[,,,,],(h=t>=(d=(s+l)/2))?s=d:l=d,(g=n>=(f=(c+u)/2))?c=f:u=f;while((_=g<<1|h)==(v=(m>=f)<<1|p>=d));return i[v]=a,i[_]=o,e}function o(e){var t,n,r=e.length,i,o,s=Array(r),c=Array(r),l=1/0,u=1/0,d=-1/0,f=-1/0;for(n=0;n<r;++n)isNaN(i=+this._x.call(null,t=e[n]))||isNaN(o=+this._y.call(null,t))||(s[n]=i,c[n]=o,i<l&&(l=i),i>d&&(d=i),o<u&&(u=o),o>f&&(f=o));if(l>d||u>f)return this;for(this.cover(l,u).cover(d,f),n=0;n<r;++n)a(this,s[n],c[n],e[n]);return this}function s(e,t){if(isNaN(e=+e)||isNaN(t=+t))return this;var n=this._x0,r=this._y0,i=this._x1,a=this._y1;if(isNaN(n))i=(n=Math.floor(e))+1,a=(r=Math.floor(t))+1;else{for(var o=i-n||1,s=this._root,c,l;n>e||e>=i||r>t||t>=a;)switch(l=(t<r)<<1|e<n,c=[,,,,],c[l]=s,s=c,o*=2,l){case 0:i=n+o,a=r+o;break;case 1:n=i-o,a=r+o;break;case 2:i=n+o,r=a-o;break;case 3:n=i-o,r=a-o;break}this._root&&this._root.length&&(this._root=s)}return this._x0=n,this._y0=r,this._x1=i,this._y1=a,this}function c(){var e=[];return this.visit(function(t){if(!t.length)do e.push(t.data);while(t=t.next)}),e}function l(e){return arguments.length?this.cover(+e[0][0],+e[0][1]).cover(+e[1][0],+e[1][1]):isNaN(this._x0)?void 0:[[this._x0,this._y0],[this._x1,this._y1]]}function u(e,t,n,r,i){this.node=e,this.x0=t,this.y0=n,this.x1=r,this.y1=i}function d(e,t,n){var r,i=this._x0,a=this._y0,o,s,c,l,d=this._x1,f=this._y1,p=[],m=this._root,h,g;for(m&&p.push(new u(m,i,a,d,f)),n==null?n=1/0:(i=e-n,a=t-n,d=e+n,f=t+n,n*=n);h=p.pop();)if(!(!(m=h.node)||(o=h.x0)>d||(s=h.y0)>f||(c=h.x1)<i||(l=h.y1)<a))if(m.length){var _=(o+c)/2,v=(s+l)/2;p.push(new u(m[3],_,v,c,l),new u(m[2],o,v,_,l),new u(m[1],_,s,c,v),new u(m[0],o,s,_,v)),(g=(t>=v)<<1|e>=_)&&(h=p[p.length-1],p[p.length-1]=p[p.length-1-g],p[p.length-1-g]=h)}else{var y=e-+this._x.call(null,m.data),b=t-+this._y.call(null,m.data),x=y*y+b*b;if(x<n){var S=Math.sqrt(n=x);i=e-S,a=t-S,d=e+S,f=t+S,r=m.data}}return r}function f(e){if(isNaN(u=+this._x.call(null,e))||isNaN(d=+this._y.call(null,e)))return this;var t,n=this._root,r,i,a,o=this._x0,s=this._y0,c=this._x1,l=this._y1,u,d,f,p,m,h,g,_;if(!n)return this;if(n.length)for(;;){if((m=u>=(f=(o+c)/2))?o=f:c=f,(h=d>=(p=(s+l)/2))?s=p:l=p,t=n,!(n=n[g=h<<1|m]))return this;if(!n.length)break;(t[g+1&3]||t[g+2&3]||t[g+3&3])&&(r=t,_=g)}for(;n.data!==e;)if(i=n,!(n=n.next))return this;return(a=n.next)&&delete n.next,i?(a?i.next=a:delete i.next,this):t?(a?t[g]=a:delete t[g],(n=t[0]||t[1]||t[2]||t[3])&&n===(t[3]||t[2]||t[1]||t[0])&&!n.length&&(r?r[_]=n:this._root=n),this):(this._root=a,this)}function p(e){for(var t=0,n=e.length;t<n;++t)this.remove(e[t]);return this}function m(){return this._root}function h(){var e=0;return this.visit(function(t){if(!t.length)do++e;while(t=t.next)}),e}function g(e){var t=[],n,r=this._root,i,a,o,s,c;for(r&&t.push(new u(r,this._x0,this._y0,this._x1,this._y1));n=t.pop();)if(!e(r=n.node,a=n.x0,o=n.y0,s=n.x1,c=n.y1)&&r.length){var l=(a+s)/2,d=(o+c)/2;(i=r[3])&&t.push(new u(i,l,d,s,c)),(i=r[2])&&t.push(new u(i,a,d,l,c)),(i=r[1])&&t.push(new u(i,l,o,s,d)),(i=r[0])&&t.push(new u(i,a,o,l,d))}return this}function _(e){var t=[],n=[],r;for(this._root&&t.push(new u(this._root,this._x0,this._y0,this._x1,this._y1));r=t.pop();){var i=r.node;if(i.length){var a,o=r.x0,s=r.y0,c=r.x1,l=r.y1,d=(o+c)/2,f=(s+l)/2;(a=i[0])&&t.push(new u(a,o,s,d,f)),(a=i[1])&&t.push(new u(a,d,s,c,f)),(a=i[2])&&t.push(new u(a,o,f,d,l)),(a=i[3])&&t.push(new u(a,d,f,c,l))}n.push(r)}for(;r=n.pop();)e(r.node,r.x0,r.y0,r.x1,r.y1);return this}function v(e){return e[0]}function y(e){return arguments.length?(this._x=e,this):this._x}function b(e){return e[1]}function x(e){return arguments.length?(this._y=e,this):this._y}function S(e,t,n){var r=new C(t??v,n??b,NaN,NaN,NaN,NaN);return e==null?r:r.addAll(e)}function C(e,t,n,r,i,a){this._x=e,this._y=t,this._x0=n,this._y0=r,this._x1=i,this._y1=a,this._root=void 0}function w(e){for(var t={data:e.data},n=t;e=e.next;)n=n.next={data:e.data};return t}var T=S.prototype=C.prototype;T.copy=function(){var e=new C(this._x,this._y,this._x0,this._y0,this._x1,this._y1),t=this._root,n,r;if(!t)return e;if(!t.length)return e._root=w(t),e;for(n=[{source:t,target:e._root=[,,,,]}];t=n.pop();)for(var i=0;i<4;++i)(r=t.source[i])&&(r.length?n.push({source:r,target:t.target[i]=[,,,,]}):t.target[i]=w(r));return e},T.add=i,T.addAll=o,T.cover=s,T.data=c,T.extent=l,T.find=d,T.remove=f,T.removeAll=p,T.root=m,T.size=h,T.visit=g,T.visitAfter=_,T.x=y,T.y=x;function E(e){return function(){return e}}function D(e){return(e()-.5)*1e-6}function ee(e){return e.x+e.vx}function te(e){return e.y+e.vy}function O(e){var t,n,r,i=1,a=1;typeof e!=`function`&&(e=E(e==null?1:+e));function o(){for(var e,o=t.length,c,l,u,d,f,p,m=0;m<a;++m)for(c=S(t,ee,te).visitAfter(s),e=0;e<o;++e)l=t[e],f=n[l.index],p=f*f,u=l.x+l.vx,d=l.y+l.vy,c.visit(h);function h(e,t,n,a,o){var s=e.data,c=e.r,m=f+c;if(s){if(s.index>l.index){var h=u-s.x-s.vx,g=d-s.y-s.vy,_=h*h+g*g;_<m*m&&(h===0&&(h=D(r),_+=h*h),g===0&&(g=D(r),_+=g*g),_=(m-(_=Math.sqrt(_)))/_*i,l.vx+=(h*=_)*(m=(c*=c)/(p+c)),l.vy+=(g*=_)*m,s.vx-=h*(m=1-m),s.vy-=g*m)}return}return t>u+m||a<u-m||n>d+m||o<d-m}}function s(e){if(e.data)return e.r=n[e.data.index];for(var t=e.r=0;t<4;++t)e[t]&&e[t].r>e.r&&(e.r=e[t].r)}function c(){if(t){var r,i=t.length,a;for(n=Array(i),r=0;r<i;++r)a=t[r],n[a.index]=+e(a,r,t)}}return o.initialize=function(e,n){t=e,r=n,c()},o.iterations=function(e){return arguments.length?(a=+e,o):a},o.strength=function(e){return arguments.length?(i=+e,o):i},o.radius=function(t){return arguments.length?(e=typeof t==`function`?t:E(+t),c(),o):e},o}var k={value:()=>{}};function A(){for(var e=0,t=arguments.length,n={},r;e<t;++e){if(!(r=arguments[e]+``)||r in n||/[\s.]/.test(r))throw Error(`illegal type: `+r);n[r]=[]}return new j(n)}function j(e){this._=e}function M(e,t){return e.trim().split(/^|\s+/).map(function(e){var n=``,r=e.indexOf(`.`);if(r>=0&&(n=e.slice(r+1),e=e.slice(0,r)),e&&!t.hasOwnProperty(e))throw Error(`unknown type: `+e);return{type:e,name:n}})}j.prototype=A.prototype={constructor:j,on:function(e,t){var n=this._,r=M(e+``,n),i,a=-1,o=r.length;if(arguments.length<2){for(;++a<o;)if((i=(e=r[a]).type)&&(i=N(n[i],e.name)))return i;return}if(t!=null&&typeof t!=`function`)throw Error(`invalid callback: `+t);for(;++a<o;)if(i=(e=r[a]).type)n[i]=P(n[i],e.name,t);else if(t==null)for(i in n)n[i]=P(n[i],e.name,null);return this},copy:function(){var e={},t=this._;for(var n in t)e[n]=t[n].slice();return new j(e)},call:function(e,t){if((i=arguments.length-2)>0)for(var n=Array(i),r=0,i,a;r<i;++r)n[r]=arguments[r+2];if(!this._.hasOwnProperty(e))throw Error(`unknown type: `+e);for(a=this._[e],r=0,i=a.length;r<i;++r)a[r].value.apply(t,n)},apply:function(e,t,n){if(!this._.hasOwnProperty(e))throw Error(`unknown type: `+e);for(var r=this._[e],i=0,a=r.length;i<a;++i)r[i].value.apply(t,n)}};function N(e,t){for(var n=0,r=e.length,i;n<r;++n)if((i=e[n]).name===t)return i.value}function P(e,t,n){for(var r=0,i=e.length;r<i;++r)if(e[r].name===t){e[r]=k,e=e.slice(0,r).concat(e.slice(r+1));break}return n!=null&&e.push({name:t,value:n}),e}var F=0,I=0,L=0,R=1e3,z,B,V=0,H=0,U=0,W=typeof performance==`object`&&performance.now?performance:Date,G=typeof window==`object`&&window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(e){setTimeout(e,17)};function K(){return H||=(G(ne),W.now()+U)}function ne(){H=0}function q(){this._call=this._time=this._next=null}q.prototype=J.prototype={constructor:q,restart:function(e,t,n){if(typeof e!=`function`)throw TypeError(`callback is not a function`);n=(n==null?K():+n)+(t==null?0:+t),!this._next&&B!==this&&(B?B._next=this:z=this,B=this),this._call=e,this._time=n,X()},stop:function(){this._call&&(this._call=null,this._time=1/0,X())}};function J(e,t,n){var r=new q;return r.restart(e,t,n),r}function re(){K(),++F;for(var e=z,t;e;)(t=H-e._time)>=0&&e._call.call(void 0,t),e=e._next;--F}function Y(){H=(V=W.now())+U,F=I=0;try{re()}finally{F=0,ae(),H=0}}function ie(){var e=W.now(),t=e-V;t>R&&(U-=t,V=e)}function ae(){for(var e,t=z,n,r=1/0;t;)t._call?(r>t._time&&(r=t._time),e=t,t=t._next):(n=t._next,t._next=null,t=e?e._next=n:z=n);B=e,X(r)}function X(e){F||(I&&=clearTimeout(I),e-H>24?(e<1/0&&(I=setTimeout(Y,e-W.now()-U)),L&&=clearInterval(L)):(L||=(V=W.now(),setInterval(ie,R)),F=1,G(Y)))}const Z=4294967296;function oe(){let e=1;return()=>(e=(1664525*e+1013904223)%Z)/Z}function se(e){return e.x}function ce(e){return e.y}var le=10,ue=Math.PI*(3-Math.sqrt(5));function Q(e){var t,n=1,r=.001,i=1-r**(1/300),a=0,o=.6,s=new Map,c=J(d),l=A(`tick`,`end`),u=oe();e??=[];function d(){f(),l.call(`tick`,t),n<r&&(c.stop(),l.call(`end`,t))}function f(r){var c,l=e.length,u;r===void 0&&(r=1);for(var d=0;d<r;++d)for(n+=(a-n)*i,s.forEach(function(e){e(n)}),c=0;c<l;++c)u=e[c],u.fx==null?u.x+=u.vx*=o:(u.x=u.fx,u.vx=0),u.fy==null?u.y+=u.vy*=o:(u.y=u.fy,u.vy=0);return t}function p(){for(var t=0,n=e.length,r;t<n;++t){if(r=e[t],r.index=t,r.fx!=null&&(r.x=r.fx),r.fy!=null&&(r.y=r.fy),isNaN(r.x)||isNaN(r.y)){var i=le*Math.sqrt(.5+t),a=t*ue;r.x=i*Math.cos(a),r.y=i*Math.sin(a)}(isNaN(r.vx)||isNaN(r.vy))&&(r.vx=r.vy=0)}}function m(t){return t.initialize&&t.initialize(e,u),t}return p(),t={tick:f,restart:function(){return c.restart(d),t},stop:function(){return c.stop(),t},nodes:function(n){return arguments.length?(e=n,p(),s.forEach(m),t):e},alpha:function(e){return arguments.length?(n=+e,t):n},alphaMin:function(e){return arguments.length?(r=+e,t):r},alphaDecay:function(e){return arguments.length?(i=+e,t):+i},alphaTarget:function(e){return arguments.length?(a=+e,t):a},velocityDecay:function(e){return arguments.length?(o=1-e,t):1-o},randomSource:function(e){return arguments.length?(u=e,s.forEach(m),t):u},force:function(e,n){return arguments.length>1?(n==null?s.delete(e):s.set(e,m(n)),t):s.get(e)},find:function(t,n,r){var i=0,a=e.length,o,s,c,l,u;for(r==null?r=1/0:r*=r,i=0;i<a;++i)l=e[i],o=t-l.x,s=n-l.y,c=o*o+s*s,c<r&&(u=l,r=c);return u},on:function(e,n){return arguments.length>1?(l.on(e,n),t):l.on(e)}}}function $(){var e,t,n,r,i=E(-30),a,o=1,s=1/0,c=.81;function l(n){var i,a=e.length,o=S(e,se,ce).visitAfter(d);for(r=n,i=0;i<a;++i)t=e[i],o.visit(f)}function u(){if(e){var t,n=e.length,r;for(a=Array(n),t=0;t<n;++t)r=e[t],a[r.index]=+i(r,t,e)}}function d(e){var t=0,n,r,i=0,o,s,c;if(e.length){for(o=s=c=0;c<4;++c)(n=e[c])&&(r=Math.abs(n.value))&&(t+=n.value,i+=r,o+=r*n.x,s+=r*n.y);e.x=o/i,e.y=s/i}else{n=e,n.x=n.data.x,n.y=n.data.y;do t+=a[n.data.index];while(n=n.next)}e.value=t}function f(e,i,l,u){if(!e.value)return!0;var d=e.x-t.x,f=e.y-t.y,p=u-i,m=d*d+f*f;if(p*p/c<m)return m<s&&(d===0&&(d=D(n),m+=d*d),f===0&&(f=D(n),m+=f*f),m<o&&(m=Math.sqrt(o*m)),t.vx+=d*e.value*r/m,t.vy+=f*e.value*r/m),!0;if(!(e.length||m>=s)){(e.data!==t||e.next)&&(d===0&&(d=D(n),m+=d*d),f===0&&(f=D(n),m+=f*f),m<o&&(m=Math.sqrt(o*m)));do e.data!==t&&(p=a[e.data.index]*r/m,t.vx+=d*p,t.vy+=f*p);while(e=e.next)}}return l.initialize=function(t,r){e=t,n=r,u()},l.strength=function(e){return arguments.length?(i=typeof e==`function`?e:E(+e),u(),l):i},l.distanceMin=function(e){return arguments.length?(o=e*e,l):Math.sqrt(o)},l.distanceMax=function(e){return arguments.length?(s=e*e,l):Math.sqrt(s)},l.theta=function(e){return arguments.length?(c=e*e,l):Math.sqrt(c)},l}function de(e){var t=E(.1),n,r,i;typeof e!=`function`&&(e=E(e==null?0:+e));function a(e){for(var t=0,a=n.length,o;t<a;++t)o=n[t],o.vx+=(i[t]-o.x)*r[t]*e}function o(){if(n){var a,o=n.length;for(r=Array(o),i=Array(o),a=0;a<o;++a)r[a]=isNaN(i[a]=+e(n[a],a,n))?0:+t(n[a],a,n)}}return a.initialize=function(e){n=e,o()},a.strength=function(e){return arguments.length?(t=typeof e==`function`?e:E(+e),o(),a):t},a.x=function(t){return arguments.length?(e=typeof t==`function`?t:E(+t),o(),a):e},a}function fe(e){var t=E(.1),n,r,i;typeof e!=`function`&&(e=E(e==null?0:+e));function a(e){for(var t=0,a=n.length,o;t<a;++t)o=n[t],o.vy+=(i[t]-o.y)*r[t]*e}function o(){if(n){var a,o=n.length;for(r=Array(o),i=Array(o),a=0;a<o;++a)r[a]=isNaN(i[a]=+e(n[a],a,n))?0:+t(n[a],a,n)}}return a.initialize=function(e){n=e,o()},a.strength=function(e){return arguments.length?(t=typeof e==`function`?e:E(+e),o(),a):t},a.y=function(t){return arguments.length?(e=typeof t==`function`?t:E(+t),o(),a):e},a}const pe=[e.Entry,e.InternalFramework,e.InternalLib,e.Utility,e.Test,e.Tool];function me(t,n){let i=t.nodes,a=i.length;if(a===0)return{clusterId:t.id,width:n.minClusterSize,height:n.minClusterSize,relativePositions:new Map};let o=new Map;for(let n of i){let r=t.metadata?.get(n.id)?.role??e.InternalLib;o.set(r,(o.get(r)??0)+1)}let s=0,c=new Map;for(let e of pe){let t=o.get(e)??0,n=Math.sqrt(s/a);s+=t;let r=Math.sqrt(s/a);t===0?c.set(e,{min:n,max:n}):c.set(e,{min:n,max:r})}let l=n.nodeRadius*2+n.clusterNodeSpacing,u=a**.6*l*1.3,d=50+Math.max(0,a-15)*2,f=Math.max(n.minClusterSize/2,u+d),p=f*2,m=i.map(r=>({id:r.id,x:(Math.random()-.5)*10,y:(Math.random()-.5)*10,vx:0,vy:0,role:t.metadata?.get(r.id)?.role??e.InternalLib,radius:n.nodeRadius})),h=Q(m).force(`collide`,O().radius(e=>e.radius+n.nodeCollisionPadding)).force(`orbit`,e=>{let t=e*.8;for(let e of m){let n=c.get(e.role)??{min:0,max:1},r=n.min*(f-20),i=n.max*(f-20),a=Math.hypot(e.x,e.y)||1e-6;if(a<r){let n=a-r,i=e.x/a*n*t,o=e.y/a*n*t;e.vx-=i,e.vy-=o}else if(a>i){let n=a-i,r=e.x/a*n*t,o=e.y/a*n*t;e.vx-=r,e.vy-=o}}}).force(`center`,r(0,0).strength(.02)).force(`charge`,$().strength(n.nodeCharge));for(let e=0;e<150&&(h.tick(),!(h.alpha()<.001));e++);h.stop();let g=new Map;for(let e of m)g.set(e.id,{id:e.id,clusterId:t.id,x:e.x,y:e.y,vx:0,vy:0,radius:e.radius});return{clusterId:t.id,width:p,height:p,relativePositions:g}}function he(e,t){let n=Array.from(e.relativePositions.values()).map(e=>({...e,ox:e.x,oy:e.y}));if(n.length===0)return e;let r=Q(n).force(`x`,de(e=>e.ox).strength(.1)).force(`y`,fe(e=>e.oy).strength(.1)).force(`charge`,$().strength(-30)).force(`collide`,O().radius(e=>(e.radius??t.nodeRadius)+t.clusterNodeSpacing).strength(.9).iterations(2)).stop();for(let e=0;e<50&&(r.tick(),!(r.alpha()<.001));e++);let i=new Map,a=1/0,o=-1/0,s=1/0,c=-1/0;for(let t of n){let n=e.relativePositions.get(t.id);n&&i.set(t.id,{...n,x:t.x,y:t.y}),t.x<a&&(a=t.x),t.x>o&&(o=t.x),t.y<s&&(s=t.y),t.y>c&&(c=t.y)}let l=Math.max(Math.abs(a),Math.abs(o),Math.abs(s),Math.abs(c))*2+100,u=Math.max(e.width,l);return{...e,relativePositions:i,width:u,height:u}}function ge(e){return{...e,metadata:new Map(e.metadata)}}const _e={computeMicro(e,t){let n=me(ge(e),t);return n=he(n,t),{clusterId:n.clusterId,width:n.width,height:n.height,relativePositions:Array.from(n.relativePositions.entries())}}};self.onmessage=e=>{let t=_e.computeMicro(e.data.cluster,e.data.config);self.postMessage(t)};
|