xcode-graph 0.1.0 → 0.1.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/LICENSE +21 -0
- package/README.md +131 -30
- package/custom-elements.json +486 -71
- package/dist/xcode-graph.js +17150 -0
- package/dist/xcode-graph.service.js +4906 -0
- package/dist/xcodegraph.js +1 -22164
- package/package.json +20 -2
- package/vscode.html-custom-data.json +359 -69
- package/web-types.json +924 -185
- package/dist/assets/micro-layout.worker-CSeqAKhL.js +0 -1726
|
@@ -1,1726 +0,0 @@
|
|
|
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
|
-
var NodeRole;
|
|
13
|
-
(function (NodeRole) {
|
|
14
|
-
NodeRole["Entry"] = "entry";
|
|
15
|
-
NodeRole["InternalFramework"] = "internal-framework";
|
|
16
|
-
NodeRole["InternalLib"] = "internal-lib";
|
|
17
|
-
NodeRole["Utility"] = "utility";
|
|
18
|
-
NodeRole["Test"] = "test";
|
|
19
|
-
NodeRole["Tool"] = "tool";
|
|
20
|
-
})(NodeRole || (NodeRole = {}));
|
|
21
|
-
/**
|
|
22
|
-
* Cluster type enum - distinguishes local projects from packages
|
|
23
|
-
*/
|
|
24
|
-
var ClusterType;
|
|
25
|
-
(function (ClusterType) {
|
|
26
|
-
ClusterType["Project"] = "project";
|
|
27
|
-
ClusterType["Package"] = "package";
|
|
28
|
-
})(ClusterType || (ClusterType = {}));
|
|
29
|
-
/**
|
|
30
|
-
* ELK Hierarchy Handling strategy
|
|
31
|
-
*/
|
|
32
|
-
var ElkHierarchyHandling;
|
|
33
|
-
(function (ElkHierarchyHandling) {
|
|
34
|
-
ElkHierarchyHandling["Inherit"] = "INHERIT";
|
|
35
|
-
ElkHierarchyHandling["IncludeChildren"] = "INCLUDE_CHILDREN";
|
|
36
|
-
ElkHierarchyHandling["SeparateChildren"] = "SEPARATE_CHILDREN";
|
|
37
|
-
})(ElkHierarchyHandling || (ElkHierarchyHandling = {}));
|
|
38
|
-
/** All node role values for iteration */
|
|
39
|
-
Object.values(NodeRole);
|
|
40
|
-
/** All cluster type values for iteration */
|
|
41
|
-
Object.values(ClusterType);
|
|
42
|
-
|
|
43
|
-
function center(x, y) {
|
|
44
|
-
var nodes, strength = 1;
|
|
45
|
-
|
|
46
|
-
if (x == null) x = 0;
|
|
47
|
-
if (y == null) y = 0;
|
|
48
|
-
|
|
49
|
-
function force() {
|
|
50
|
-
var i,
|
|
51
|
-
n = nodes.length,
|
|
52
|
-
node,
|
|
53
|
-
sx = 0,
|
|
54
|
-
sy = 0;
|
|
55
|
-
|
|
56
|
-
for (i = 0; i < n; ++i) {
|
|
57
|
-
node = nodes[i], sx += node.x, sy += node.y;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
for (sx = (sx / n - x) * strength, sy = (sy / n - y) * strength, i = 0; i < n; ++i) {
|
|
61
|
-
node = nodes[i], node.x -= sx, node.y -= sy;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
force.initialize = function(_) {
|
|
66
|
-
nodes = _;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
force.x = function(_) {
|
|
70
|
-
return arguments.length ? (x = +_, force) : x;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
force.y = function(_) {
|
|
74
|
-
return arguments.length ? (y = +_, force) : y;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
force.strength = function(_) {
|
|
78
|
-
return arguments.length ? (strength = +_, force) : strength;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
return force;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function tree_add(d) {
|
|
85
|
-
const x = +this._x.call(null, d),
|
|
86
|
-
y = +this._y.call(null, d);
|
|
87
|
-
return add(this.cover(x, y), x, y, d);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function add(tree, x, y, d) {
|
|
91
|
-
if (isNaN(x) || isNaN(y)) return tree; // ignore invalid points
|
|
92
|
-
|
|
93
|
-
var parent,
|
|
94
|
-
node = tree._root,
|
|
95
|
-
leaf = {data: d},
|
|
96
|
-
x0 = tree._x0,
|
|
97
|
-
y0 = tree._y0,
|
|
98
|
-
x1 = tree._x1,
|
|
99
|
-
y1 = tree._y1,
|
|
100
|
-
xm,
|
|
101
|
-
ym,
|
|
102
|
-
xp,
|
|
103
|
-
yp,
|
|
104
|
-
right,
|
|
105
|
-
bottom,
|
|
106
|
-
i,
|
|
107
|
-
j;
|
|
108
|
-
|
|
109
|
-
// If the tree is empty, initialize the root as a leaf.
|
|
110
|
-
if (!node) return tree._root = leaf, tree;
|
|
111
|
-
|
|
112
|
-
// Find the existing leaf for the new point, or add it.
|
|
113
|
-
while (node.length) {
|
|
114
|
-
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
|
115
|
-
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
|
116
|
-
if (parent = node, !(node = node[i = bottom << 1 | right])) return parent[i] = leaf, tree;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Is the new point is exactly coincident with the existing point?
|
|
120
|
-
xp = +tree._x.call(null, node.data);
|
|
121
|
-
yp = +tree._y.call(null, node.data);
|
|
122
|
-
if (x === xp && y === yp) return leaf.next = node, parent ? parent[i] = leaf : tree._root = leaf, tree;
|
|
123
|
-
|
|
124
|
-
// Otherwise, split the leaf node until the old and new point are separated.
|
|
125
|
-
do {
|
|
126
|
-
parent = parent ? parent[i] = new Array(4) : tree._root = new Array(4);
|
|
127
|
-
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
|
128
|
-
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
|
129
|
-
} while ((i = bottom << 1 | right) === (j = (yp >= ym) << 1 | (xp >= xm)));
|
|
130
|
-
return parent[j] = node, parent[i] = leaf, tree;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function addAll(data) {
|
|
134
|
-
var d, i, n = data.length,
|
|
135
|
-
x,
|
|
136
|
-
y,
|
|
137
|
-
xz = new Array(n),
|
|
138
|
-
yz = new Array(n),
|
|
139
|
-
x0 = Infinity,
|
|
140
|
-
y0 = Infinity,
|
|
141
|
-
x1 = -Infinity,
|
|
142
|
-
y1 = -Infinity;
|
|
143
|
-
|
|
144
|
-
// Compute the points and their extent.
|
|
145
|
-
for (i = 0; i < n; ++i) {
|
|
146
|
-
if (isNaN(x = +this._x.call(null, d = data[i])) || isNaN(y = +this._y.call(null, d))) continue;
|
|
147
|
-
xz[i] = x;
|
|
148
|
-
yz[i] = y;
|
|
149
|
-
if (x < x0) x0 = x;
|
|
150
|
-
if (x > x1) x1 = x;
|
|
151
|
-
if (y < y0) y0 = y;
|
|
152
|
-
if (y > y1) y1 = y;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// If there were no (valid) points, abort.
|
|
156
|
-
if (x0 > x1 || y0 > y1) return this;
|
|
157
|
-
|
|
158
|
-
// Expand the tree to cover the new points.
|
|
159
|
-
this.cover(x0, y0).cover(x1, y1);
|
|
160
|
-
|
|
161
|
-
// Add the new points.
|
|
162
|
-
for (i = 0; i < n; ++i) {
|
|
163
|
-
add(this, xz[i], yz[i], data[i]);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return this;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function tree_cover(x, y) {
|
|
170
|
-
if (isNaN(x = +x) || isNaN(y = +y)) return this; // ignore invalid points
|
|
171
|
-
|
|
172
|
-
var x0 = this._x0,
|
|
173
|
-
y0 = this._y0,
|
|
174
|
-
x1 = this._x1,
|
|
175
|
-
y1 = this._y1;
|
|
176
|
-
|
|
177
|
-
// If the quadtree has no extent, initialize them.
|
|
178
|
-
// Integer extent are necessary so that if we later double the extent,
|
|
179
|
-
// the existing quadrant boundaries don’t change due to floating point error!
|
|
180
|
-
if (isNaN(x0)) {
|
|
181
|
-
x1 = (x0 = Math.floor(x)) + 1;
|
|
182
|
-
y1 = (y0 = Math.floor(y)) + 1;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Otherwise, double repeatedly to cover.
|
|
186
|
-
else {
|
|
187
|
-
var z = x1 - x0 || 1,
|
|
188
|
-
node = this._root,
|
|
189
|
-
parent,
|
|
190
|
-
i;
|
|
191
|
-
|
|
192
|
-
while (x0 > x || x >= x1 || y0 > y || y >= y1) {
|
|
193
|
-
i = (y < y0) << 1 | (x < x0);
|
|
194
|
-
parent = new Array(4), parent[i] = node, node = parent, z *= 2;
|
|
195
|
-
switch (i) {
|
|
196
|
-
case 0: x1 = x0 + z, y1 = y0 + z; break;
|
|
197
|
-
case 1: x0 = x1 - z, y1 = y0 + z; break;
|
|
198
|
-
case 2: x1 = x0 + z, y0 = y1 - z; break;
|
|
199
|
-
case 3: x0 = x1 - z, y0 = y1 - z; break;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (this._root && this._root.length) this._root = node;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
this._x0 = x0;
|
|
207
|
-
this._y0 = y0;
|
|
208
|
-
this._x1 = x1;
|
|
209
|
-
this._y1 = y1;
|
|
210
|
-
return this;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function tree_data() {
|
|
214
|
-
var data = [];
|
|
215
|
-
this.visit(function(node) {
|
|
216
|
-
if (!node.length) do data.push(node.data); while (node = node.next)
|
|
217
|
-
});
|
|
218
|
-
return data;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function tree_extent(_) {
|
|
222
|
-
return arguments.length
|
|
223
|
-
? this.cover(+_[0][0], +_[0][1]).cover(+_[1][0], +_[1][1])
|
|
224
|
-
: isNaN(this._x0) ? undefined : [[this._x0, this._y0], [this._x1, this._y1]];
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function Quad(node, x0, y0, x1, y1) {
|
|
228
|
-
this.node = node;
|
|
229
|
-
this.x0 = x0;
|
|
230
|
-
this.y0 = y0;
|
|
231
|
-
this.x1 = x1;
|
|
232
|
-
this.y1 = y1;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function tree_find(x, y, radius) {
|
|
236
|
-
var data,
|
|
237
|
-
x0 = this._x0,
|
|
238
|
-
y0 = this._y0,
|
|
239
|
-
x1,
|
|
240
|
-
y1,
|
|
241
|
-
x2,
|
|
242
|
-
y2,
|
|
243
|
-
x3 = this._x1,
|
|
244
|
-
y3 = this._y1,
|
|
245
|
-
quads = [],
|
|
246
|
-
node = this._root,
|
|
247
|
-
q,
|
|
248
|
-
i;
|
|
249
|
-
|
|
250
|
-
if (node) quads.push(new Quad(node, x0, y0, x3, y3));
|
|
251
|
-
if (radius == null) radius = Infinity;
|
|
252
|
-
else {
|
|
253
|
-
x0 = x - radius, y0 = y - radius;
|
|
254
|
-
x3 = x + radius, y3 = y + radius;
|
|
255
|
-
radius *= radius;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
while (q = quads.pop()) {
|
|
259
|
-
|
|
260
|
-
// Stop searching if this quadrant can’t contain a closer node.
|
|
261
|
-
if (!(node = q.node)
|
|
262
|
-
|| (x1 = q.x0) > x3
|
|
263
|
-
|| (y1 = q.y0) > y3
|
|
264
|
-
|| (x2 = q.x1) < x0
|
|
265
|
-
|| (y2 = q.y1) < y0) continue;
|
|
266
|
-
|
|
267
|
-
// Bisect the current quadrant.
|
|
268
|
-
if (node.length) {
|
|
269
|
-
var xm = (x1 + x2) / 2,
|
|
270
|
-
ym = (y1 + y2) / 2;
|
|
271
|
-
|
|
272
|
-
quads.push(
|
|
273
|
-
new Quad(node[3], xm, ym, x2, y2),
|
|
274
|
-
new Quad(node[2], x1, ym, xm, y2),
|
|
275
|
-
new Quad(node[1], xm, y1, x2, ym),
|
|
276
|
-
new Quad(node[0], x1, y1, xm, ym)
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
// Visit the closest quadrant first.
|
|
280
|
-
if (i = (y >= ym) << 1 | (x >= xm)) {
|
|
281
|
-
q = quads[quads.length - 1];
|
|
282
|
-
quads[quads.length - 1] = quads[quads.length - 1 - i];
|
|
283
|
-
quads[quads.length - 1 - i] = q;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Visit this point. (Visiting coincident points isn’t necessary!)
|
|
288
|
-
else {
|
|
289
|
-
var dx = x - +this._x.call(null, node.data),
|
|
290
|
-
dy = y - +this._y.call(null, node.data),
|
|
291
|
-
d2 = dx * dx + dy * dy;
|
|
292
|
-
if (d2 < radius) {
|
|
293
|
-
var d = Math.sqrt(radius = d2);
|
|
294
|
-
x0 = x - d, y0 = y - d;
|
|
295
|
-
x3 = x + d, y3 = y + d;
|
|
296
|
-
data = node.data;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return data;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function tree_remove(d) {
|
|
305
|
-
if (isNaN(x = +this._x.call(null, d)) || isNaN(y = +this._y.call(null, d))) return this; // ignore invalid points
|
|
306
|
-
|
|
307
|
-
var parent,
|
|
308
|
-
node = this._root,
|
|
309
|
-
retainer,
|
|
310
|
-
previous,
|
|
311
|
-
next,
|
|
312
|
-
x0 = this._x0,
|
|
313
|
-
y0 = this._y0,
|
|
314
|
-
x1 = this._x1,
|
|
315
|
-
y1 = this._y1,
|
|
316
|
-
x,
|
|
317
|
-
y,
|
|
318
|
-
xm,
|
|
319
|
-
ym,
|
|
320
|
-
right,
|
|
321
|
-
bottom,
|
|
322
|
-
i,
|
|
323
|
-
j;
|
|
324
|
-
|
|
325
|
-
// If the tree is empty, initialize the root as a leaf.
|
|
326
|
-
if (!node) return this;
|
|
327
|
-
|
|
328
|
-
// Find the leaf node for the point.
|
|
329
|
-
// While descending, also retain the deepest parent with a non-removed sibling.
|
|
330
|
-
if (node.length) while (true) {
|
|
331
|
-
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
|
332
|
-
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
|
333
|
-
if (!(parent = node, node = node[i = bottom << 1 | right])) return this;
|
|
334
|
-
if (!node.length) break;
|
|
335
|
-
if (parent[(i + 1) & 3] || parent[(i + 2) & 3] || parent[(i + 3) & 3]) retainer = parent, j = i;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Find the point to remove.
|
|
339
|
-
while (node.data !== d) if (!(previous = node, node = node.next)) return this;
|
|
340
|
-
if (next = node.next) delete node.next;
|
|
341
|
-
|
|
342
|
-
// If there are multiple coincident points, remove just the point.
|
|
343
|
-
if (previous) return (next ? previous.next = next : delete previous.next), this;
|
|
344
|
-
|
|
345
|
-
// If this is the root point, remove it.
|
|
346
|
-
if (!parent) return this._root = next, this;
|
|
347
|
-
|
|
348
|
-
// Remove this leaf.
|
|
349
|
-
next ? parent[i] = next : delete parent[i];
|
|
350
|
-
|
|
351
|
-
// If the parent now contains exactly one leaf, collapse superfluous parents.
|
|
352
|
-
if ((node = parent[0] || parent[1] || parent[2] || parent[3])
|
|
353
|
-
&& node === (parent[3] || parent[2] || parent[1] || parent[0])
|
|
354
|
-
&& !node.length) {
|
|
355
|
-
if (retainer) retainer[j] = node;
|
|
356
|
-
else this._root = node;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return this;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function removeAll(data) {
|
|
363
|
-
for (var i = 0, n = data.length; i < n; ++i) this.remove(data[i]);
|
|
364
|
-
return this;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function tree_root() {
|
|
368
|
-
return this._root;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function tree_size() {
|
|
372
|
-
var size = 0;
|
|
373
|
-
this.visit(function(node) {
|
|
374
|
-
if (!node.length) do ++size; while (node = node.next)
|
|
375
|
-
});
|
|
376
|
-
return size;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function tree_visit(callback) {
|
|
380
|
-
var quads = [], q, node = this._root, child, x0, y0, x1, y1;
|
|
381
|
-
if (node) quads.push(new Quad(node, this._x0, this._y0, this._x1, this._y1));
|
|
382
|
-
while (q = quads.pop()) {
|
|
383
|
-
if (!callback(node = q.node, x0 = q.x0, y0 = q.y0, x1 = q.x1, y1 = q.y1) && node.length) {
|
|
384
|
-
var xm = (x0 + x1) / 2, ym = (y0 + y1) / 2;
|
|
385
|
-
if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1));
|
|
386
|
-
if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1));
|
|
387
|
-
if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym));
|
|
388
|
-
if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym));
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
return this;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function tree_visitAfter(callback) {
|
|
395
|
-
var quads = [], next = [], q;
|
|
396
|
-
if (this._root) quads.push(new Quad(this._root, this._x0, this._y0, this._x1, this._y1));
|
|
397
|
-
while (q = quads.pop()) {
|
|
398
|
-
var node = q.node;
|
|
399
|
-
if (node.length) {
|
|
400
|
-
var child, x0 = q.x0, y0 = q.y0, x1 = q.x1, y1 = q.y1, xm = (x0 + x1) / 2, ym = (y0 + y1) / 2;
|
|
401
|
-
if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym));
|
|
402
|
-
if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym));
|
|
403
|
-
if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1));
|
|
404
|
-
if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1));
|
|
405
|
-
}
|
|
406
|
-
next.push(q);
|
|
407
|
-
}
|
|
408
|
-
while (q = next.pop()) {
|
|
409
|
-
callback(q.node, q.x0, q.y0, q.x1, q.y1);
|
|
410
|
-
}
|
|
411
|
-
return this;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function defaultX(d) {
|
|
415
|
-
return d[0];
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function tree_x(_) {
|
|
419
|
-
return arguments.length ? (this._x = _, this) : this._x;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function defaultY(d) {
|
|
423
|
-
return d[1];
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function tree_y(_) {
|
|
427
|
-
return arguments.length ? (this._y = _, this) : this._y;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function quadtree(nodes, x, y) {
|
|
431
|
-
var tree = new Quadtree(x == null ? defaultX : x, y == null ? defaultY : y, NaN, NaN, NaN, NaN);
|
|
432
|
-
return nodes == null ? tree : tree.addAll(nodes);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function Quadtree(x, y, x0, y0, x1, y1) {
|
|
436
|
-
this._x = x;
|
|
437
|
-
this._y = y;
|
|
438
|
-
this._x0 = x0;
|
|
439
|
-
this._y0 = y0;
|
|
440
|
-
this._x1 = x1;
|
|
441
|
-
this._y1 = y1;
|
|
442
|
-
this._root = undefined;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
function leaf_copy(leaf) {
|
|
446
|
-
var copy = {data: leaf.data}, next = copy;
|
|
447
|
-
while (leaf = leaf.next) next = next.next = {data: leaf.data};
|
|
448
|
-
return copy;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
var treeProto = quadtree.prototype = Quadtree.prototype;
|
|
452
|
-
|
|
453
|
-
treeProto.copy = function() {
|
|
454
|
-
var copy = new Quadtree(this._x, this._y, this._x0, this._y0, this._x1, this._y1),
|
|
455
|
-
node = this._root,
|
|
456
|
-
nodes,
|
|
457
|
-
child;
|
|
458
|
-
|
|
459
|
-
if (!node) return copy;
|
|
460
|
-
|
|
461
|
-
if (!node.length) return copy._root = leaf_copy(node), copy;
|
|
462
|
-
|
|
463
|
-
nodes = [{source: node, target: copy._root = new Array(4)}];
|
|
464
|
-
while (node = nodes.pop()) {
|
|
465
|
-
for (var i = 0; i < 4; ++i) {
|
|
466
|
-
if (child = node.source[i]) {
|
|
467
|
-
if (child.length) nodes.push({source: child, target: node.target[i] = new Array(4)});
|
|
468
|
-
else node.target[i] = leaf_copy(child);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
return copy;
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
treeProto.add = tree_add;
|
|
477
|
-
treeProto.addAll = addAll;
|
|
478
|
-
treeProto.cover = tree_cover;
|
|
479
|
-
treeProto.data = tree_data;
|
|
480
|
-
treeProto.extent = tree_extent;
|
|
481
|
-
treeProto.find = tree_find;
|
|
482
|
-
treeProto.remove = tree_remove;
|
|
483
|
-
treeProto.removeAll = removeAll;
|
|
484
|
-
treeProto.root = tree_root;
|
|
485
|
-
treeProto.size = tree_size;
|
|
486
|
-
treeProto.visit = tree_visit;
|
|
487
|
-
treeProto.visitAfter = tree_visitAfter;
|
|
488
|
-
treeProto.x = tree_x;
|
|
489
|
-
treeProto.y = tree_y;
|
|
490
|
-
|
|
491
|
-
function constant(x) {
|
|
492
|
-
return function() {
|
|
493
|
-
return x;
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function jiggle(random) {
|
|
498
|
-
return (random() - 0.5) * 1e-6;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function x$1(d) {
|
|
502
|
-
return d.x + d.vx;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function y$1(d) {
|
|
506
|
-
return d.y + d.vy;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function forceCollide(radius) {
|
|
510
|
-
var nodes,
|
|
511
|
-
radii,
|
|
512
|
-
random,
|
|
513
|
-
strength = 1,
|
|
514
|
-
iterations = 1;
|
|
515
|
-
|
|
516
|
-
if (typeof radius !== "function") radius = constant(radius == null ? 1 : +radius);
|
|
517
|
-
|
|
518
|
-
function force() {
|
|
519
|
-
var i, n = nodes.length,
|
|
520
|
-
tree,
|
|
521
|
-
node,
|
|
522
|
-
xi,
|
|
523
|
-
yi,
|
|
524
|
-
ri,
|
|
525
|
-
ri2;
|
|
526
|
-
|
|
527
|
-
for (var k = 0; k < iterations; ++k) {
|
|
528
|
-
tree = quadtree(nodes, x$1, y$1).visitAfter(prepare);
|
|
529
|
-
for (i = 0; i < n; ++i) {
|
|
530
|
-
node = nodes[i];
|
|
531
|
-
ri = radii[node.index], ri2 = ri * ri;
|
|
532
|
-
xi = node.x + node.vx;
|
|
533
|
-
yi = node.y + node.vy;
|
|
534
|
-
tree.visit(apply);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
function apply(quad, x0, y0, x1, y1) {
|
|
539
|
-
var data = quad.data, rj = quad.r, r = ri + rj;
|
|
540
|
-
if (data) {
|
|
541
|
-
if (data.index > node.index) {
|
|
542
|
-
var x = xi - data.x - data.vx,
|
|
543
|
-
y = yi - data.y - data.vy,
|
|
544
|
-
l = x * x + y * y;
|
|
545
|
-
if (l < r * r) {
|
|
546
|
-
if (x === 0) x = jiggle(random), l += x * x;
|
|
547
|
-
if (y === 0) y = jiggle(random), l += y * y;
|
|
548
|
-
l = (r - (l = Math.sqrt(l))) / l * strength;
|
|
549
|
-
node.vx += (x *= l) * (r = (rj *= rj) / (ri2 + rj));
|
|
550
|
-
node.vy += (y *= l) * r;
|
|
551
|
-
data.vx -= x * (r = 1 - r);
|
|
552
|
-
data.vy -= y * r;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
return x0 > xi + r || x1 < xi - r || y0 > yi + r || y1 < yi - r;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function prepare(quad) {
|
|
562
|
-
if (quad.data) return quad.r = radii[quad.data.index];
|
|
563
|
-
for (var i = quad.r = 0; i < 4; ++i) {
|
|
564
|
-
if (quad[i] && quad[i].r > quad.r) {
|
|
565
|
-
quad.r = quad[i].r;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
function initialize() {
|
|
571
|
-
if (!nodes) return;
|
|
572
|
-
var i, n = nodes.length, node;
|
|
573
|
-
radii = new Array(n);
|
|
574
|
-
for (i = 0; i < n; ++i) node = nodes[i], radii[node.index] = +radius(node, i, nodes);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
force.initialize = function(_nodes, _random) {
|
|
578
|
-
nodes = _nodes;
|
|
579
|
-
random = _random;
|
|
580
|
-
initialize();
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
force.iterations = function(_) {
|
|
584
|
-
return arguments.length ? (iterations = +_, force) : iterations;
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
force.strength = function(_) {
|
|
588
|
-
return arguments.length ? (strength = +_, force) : strength;
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
force.radius = function(_) {
|
|
592
|
-
return arguments.length ? (radius = typeof _ === "function" ? _ : constant(+_), initialize(), force) : radius;
|
|
593
|
-
};
|
|
594
|
-
|
|
595
|
-
return force;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
var noop = {value: () => {}};
|
|
599
|
-
|
|
600
|
-
function dispatch() {
|
|
601
|
-
for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
|
|
602
|
-
if (!(t = arguments[i] + "") || (t in _) || /[\s.]/.test(t)) throw new Error("illegal type: " + t);
|
|
603
|
-
_[t] = [];
|
|
604
|
-
}
|
|
605
|
-
return new Dispatch(_);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function Dispatch(_) {
|
|
609
|
-
this._ = _;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
function parseTypenames(typenames, types) {
|
|
613
|
-
return typenames.trim().split(/^|\s+/).map(function(t) {
|
|
614
|
-
var name = "", i = t.indexOf(".");
|
|
615
|
-
if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
|
|
616
|
-
if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
|
|
617
|
-
return {type: t, name: name};
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
Dispatch.prototype = dispatch.prototype = {
|
|
622
|
-
constructor: Dispatch,
|
|
623
|
-
on: function(typename, callback) {
|
|
624
|
-
var _ = this._,
|
|
625
|
-
T = parseTypenames(typename + "", _),
|
|
626
|
-
t,
|
|
627
|
-
i = -1,
|
|
628
|
-
n = T.length;
|
|
629
|
-
|
|
630
|
-
// If no callback was specified, return the callback of the given type and name.
|
|
631
|
-
if (arguments.length < 2) {
|
|
632
|
-
while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t;
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// If a type was specified, set the callback for the given type and name.
|
|
637
|
-
// Otherwise, if a null callback was specified, remove callbacks of the given name.
|
|
638
|
-
if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);
|
|
639
|
-
while (++i < n) {
|
|
640
|
-
if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback);
|
|
641
|
-
else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
return this;
|
|
645
|
-
},
|
|
646
|
-
copy: function() {
|
|
647
|
-
var copy = {}, _ = this._;
|
|
648
|
-
for (var t in _) copy[t] = _[t].slice();
|
|
649
|
-
return new Dispatch(copy);
|
|
650
|
-
},
|
|
651
|
-
call: function(type, that) {
|
|
652
|
-
if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];
|
|
653
|
-
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
|
|
654
|
-
for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
|
|
655
|
-
},
|
|
656
|
-
apply: function(type, that, args) {
|
|
657
|
-
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
|
|
658
|
-
for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
|
|
659
|
-
}
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
function get(type, name) {
|
|
663
|
-
for (var i = 0, n = type.length, c; i < n; ++i) {
|
|
664
|
-
if ((c = type[i]).name === name) {
|
|
665
|
-
return c.value;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
function set(type, name, callback) {
|
|
671
|
-
for (var i = 0, n = type.length; i < n; ++i) {
|
|
672
|
-
if (type[i].name === name) {
|
|
673
|
-
type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1));
|
|
674
|
-
break;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
if (callback != null) type.push({name: name, value: callback});
|
|
678
|
-
return type;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
var frame = 0, // is an animation frame pending?
|
|
682
|
-
timeout = 0, // is a timeout pending?
|
|
683
|
-
interval = 0, // are any timers active?
|
|
684
|
-
pokeDelay = 1000, // how frequently we check for clock skew
|
|
685
|
-
taskHead,
|
|
686
|
-
taskTail,
|
|
687
|
-
clockLast = 0,
|
|
688
|
-
clockNow = 0,
|
|
689
|
-
clockSkew = 0,
|
|
690
|
-
clock = typeof performance === "object" && performance.now ? performance : Date,
|
|
691
|
-
setFrame = typeof window === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function(f) { setTimeout(f, 17); };
|
|
692
|
-
|
|
693
|
-
function now() {
|
|
694
|
-
return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
function clearNow() {
|
|
698
|
-
clockNow = 0;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
function Timer() {
|
|
702
|
-
this._call =
|
|
703
|
-
this._time =
|
|
704
|
-
this._next = null;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
Timer.prototype = timer.prototype = {
|
|
708
|
-
constructor: Timer,
|
|
709
|
-
restart: function(callback, delay, time) {
|
|
710
|
-
if (typeof callback !== "function") throw new TypeError("callback is not a function");
|
|
711
|
-
time = (time == null ? now() : +time) + (delay == null ? 0 : +delay);
|
|
712
|
-
if (!this._next && taskTail !== this) {
|
|
713
|
-
if (taskTail) taskTail._next = this;
|
|
714
|
-
else taskHead = this;
|
|
715
|
-
taskTail = this;
|
|
716
|
-
}
|
|
717
|
-
this._call = callback;
|
|
718
|
-
this._time = time;
|
|
719
|
-
sleep();
|
|
720
|
-
},
|
|
721
|
-
stop: function() {
|
|
722
|
-
if (this._call) {
|
|
723
|
-
this._call = null;
|
|
724
|
-
this._time = Infinity;
|
|
725
|
-
sleep();
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
};
|
|
729
|
-
|
|
730
|
-
function timer(callback, delay, time) {
|
|
731
|
-
var t = new Timer;
|
|
732
|
-
t.restart(callback, delay, time);
|
|
733
|
-
return t;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
function timerFlush() {
|
|
737
|
-
now(); // Get the current time, if not already set.
|
|
738
|
-
++frame; // Pretend we’ve set an alarm, if we haven’t already.
|
|
739
|
-
var t = taskHead, e;
|
|
740
|
-
while (t) {
|
|
741
|
-
if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e);
|
|
742
|
-
t = t._next;
|
|
743
|
-
}
|
|
744
|
-
--frame;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
function wake() {
|
|
748
|
-
clockNow = (clockLast = clock.now()) + clockSkew;
|
|
749
|
-
frame = timeout = 0;
|
|
750
|
-
try {
|
|
751
|
-
timerFlush();
|
|
752
|
-
} finally {
|
|
753
|
-
frame = 0;
|
|
754
|
-
nap();
|
|
755
|
-
clockNow = 0;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
function poke() {
|
|
760
|
-
var now = clock.now(), delay = now - clockLast;
|
|
761
|
-
if (delay > pokeDelay) clockSkew -= delay, clockLast = now;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
function nap() {
|
|
765
|
-
var t0, t1 = taskHead, t2, time = Infinity;
|
|
766
|
-
while (t1) {
|
|
767
|
-
if (t1._call) {
|
|
768
|
-
if (time > t1._time) time = t1._time;
|
|
769
|
-
t0 = t1, t1 = t1._next;
|
|
770
|
-
} else {
|
|
771
|
-
t2 = t1._next, t1._next = null;
|
|
772
|
-
t1 = t0 ? t0._next = t2 : taskHead = t2;
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
taskTail = t0;
|
|
776
|
-
sleep(time);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
function sleep(time) {
|
|
780
|
-
if (frame) return; // Soonest alarm already set, or will be.
|
|
781
|
-
if (timeout) timeout = clearTimeout(timeout);
|
|
782
|
-
var delay = time - clockNow; // Strictly less than if we recomputed clockNow.
|
|
783
|
-
if (delay > 24) {
|
|
784
|
-
if (time < Infinity) timeout = setTimeout(wake, time - clock.now() - clockSkew);
|
|
785
|
-
if (interval) interval = clearInterval(interval);
|
|
786
|
-
} else {
|
|
787
|
-
if (!interval) clockLast = clock.now(), interval = setInterval(poke, pokeDelay);
|
|
788
|
-
frame = 1, setFrame(wake);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use
|
|
793
|
-
const a = 1664525;
|
|
794
|
-
const c = 1013904223;
|
|
795
|
-
const m = 4294967296; // 2^32
|
|
796
|
-
|
|
797
|
-
function lcg() {
|
|
798
|
-
let s = 1;
|
|
799
|
-
return () => (s = (a * s + c) % m) / m;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
function x(d) {
|
|
803
|
-
return d.x;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
function y(d) {
|
|
807
|
-
return d.y;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
var initialRadius = 10,
|
|
811
|
-
initialAngle = Math.PI * (3 - Math.sqrt(5));
|
|
812
|
-
|
|
813
|
-
function forceSimulation(nodes) {
|
|
814
|
-
var simulation,
|
|
815
|
-
alpha = 1,
|
|
816
|
-
alphaMin = 0.001,
|
|
817
|
-
alphaDecay = 1 - Math.pow(alphaMin, 1 / 300),
|
|
818
|
-
alphaTarget = 0,
|
|
819
|
-
velocityDecay = 0.6,
|
|
820
|
-
forces = new Map(),
|
|
821
|
-
stepper = timer(step),
|
|
822
|
-
event = dispatch("tick", "end"),
|
|
823
|
-
random = lcg();
|
|
824
|
-
|
|
825
|
-
if (nodes == null) nodes = [];
|
|
826
|
-
|
|
827
|
-
function step() {
|
|
828
|
-
tick();
|
|
829
|
-
event.call("tick", simulation);
|
|
830
|
-
if (alpha < alphaMin) {
|
|
831
|
-
stepper.stop();
|
|
832
|
-
event.call("end", simulation);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
function tick(iterations) {
|
|
837
|
-
var i, n = nodes.length, node;
|
|
838
|
-
|
|
839
|
-
if (iterations === undefined) iterations = 1;
|
|
840
|
-
|
|
841
|
-
for (var k = 0; k < iterations; ++k) {
|
|
842
|
-
alpha += (alphaTarget - alpha) * alphaDecay;
|
|
843
|
-
|
|
844
|
-
forces.forEach(function(force) {
|
|
845
|
-
force(alpha);
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
for (i = 0; i < n; ++i) {
|
|
849
|
-
node = nodes[i];
|
|
850
|
-
if (node.fx == null) node.x += node.vx *= velocityDecay;
|
|
851
|
-
else node.x = node.fx, node.vx = 0;
|
|
852
|
-
if (node.fy == null) node.y += node.vy *= velocityDecay;
|
|
853
|
-
else node.y = node.fy, node.vy = 0;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
return simulation;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
function initializeNodes() {
|
|
861
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
862
|
-
node = nodes[i], node.index = i;
|
|
863
|
-
if (node.fx != null) node.x = node.fx;
|
|
864
|
-
if (node.fy != null) node.y = node.fy;
|
|
865
|
-
if (isNaN(node.x) || isNaN(node.y)) {
|
|
866
|
-
var radius = initialRadius * Math.sqrt(0.5 + i), angle = i * initialAngle;
|
|
867
|
-
node.x = radius * Math.cos(angle);
|
|
868
|
-
node.y = radius * Math.sin(angle);
|
|
869
|
-
}
|
|
870
|
-
if (isNaN(node.vx) || isNaN(node.vy)) {
|
|
871
|
-
node.vx = node.vy = 0;
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
function initializeForce(force) {
|
|
877
|
-
if (force.initialize) force.initialize(nodes, random);
|
|
878
|
-
return force;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
initializeNodes();
|
|
882
|
-
|
|
883
|
-
return simulation = {
|
|
884
|
-
tick: tick,
|
|
885
|
-
|
|
886
|
-
restart: function() {
|
|
887
|
-
return stepper.restart(step), simulation;
|
|
888
|
-
},
|
|
889
|
-
|
|
890
|
-
stop: function() {
|
|
891
|
-
return stepper.stop(), simulation;
|
|
892
|
-
},
|
|
893
|
-
|
|
894
|
-
nodes: function(_) {
|
|
895
|
-
return arguments.length ? (nodes = _, initializeNodes(), forces.forEach(initializeForce), simulation) : nodes;
|
|
896
|
-
},
|
|
897
|
-
|
|
898
|
-
alpha: function(_) {
|
|
899
|
-
return arguments.length ? (alpha = +_, simulation) : alpha;
|
|
900
|
-
},
|
|
901
|
-
|
|
902
|
-
alphaMin: function(_) {
|
|
903
|
-
return arguments.length ? (alphaMin = +_, simulation) : alphaMin;
|
|
904
|
-
},
|
|
905
|
-
|
|
906
|
-
alphaDecay: function(_) {
|
|
907
|
-
return arguments.length ? (alphaDecay = +_, simulation) : +alphaDecay;
|
|
908
|
-
},
|
|
909
|
-
|
|
910
|
-
alphaTarget: function(_) {
|
|
911
|
-
return arguments.length ? (alphaTarget = +_, simulation) : alphaTarget;
|
|
912
|
-
},
|
|
913
|
-
|
|
914
|
-
velocityDecay: function(_) {
|
|
915
|
-
return arguments.length ? (velocityDecay = 1 - _, simulation) : 1 - velocityDecay;
|
|
916
|
-
},
|
|
917
|
-
|
|
918
|
-
randomSource: function(_) {
|
|
919
|
-
return arguments.length ? (random = _, forces.forEach(initializeForce), simulation) : random;
|
|
920
|
-
},
|
|
921
|
-
|
|
922
|
-
force: function(name, _) {
|
|
923
|
-
return arguments.length > 1 ? ((_ == null ? forces.delete(name) : forces.set(name, initializeForce(_))), simulation) : forces.get(name);
|
|
924
|
-
},
|
|
925
|
-
|
|
926
|
-
find: function(x, y, radius) {
|
|
927
|
-
var i = 0,
|
|
928
|
-
n = nodes.length,
|
|
929
|
-
dx,
|
|
930
|
-
dy,
|
|
931
|
-
d2,
|
|
932
|
-
node,
|
|
933
|
-
closest;
|
|
934
|
-
|
|
935
|
-
if (radius == null) radius = Infinity;
|
|
936
|
-
else radius *= radius;
|
|
937
|
-
|
|
938
|
-
for (i = 0; i < n; ++i) {
|
|
939
|
-
node = nodes[i];
|
|
940
|
-
dx = x - node.x;
|
|
941
|
-
dy = y - node.y;
|
|
942
|
-
d2 = dx * dx + dy * dy;
|
|
943
|
-
if (d2 < radius) closest = node, radius = d2;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
return closest;
|
|
947
|
-
},
|
|
948
|
-
|
|
949
|
-
on: function(name, _) {
|
|
950
|
-
return arguments.length > 1 ? (event.on(name, _), simulation) : event.on(name);
|
|
951
|
-
}
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
function forceManyBody() {
|
|
956
|
-
var nodes,
|
|
957
|
-
node,
|
|
958
|
-
random,
|
|
959
|
-
alpha,
|
|
960
|
-
strength = constant(-30),
|
|
961
|
-
strengths,
|
|
962
|
-
distanceMin2 = 1,
|
|
963
|
-
distanceMax2 = Infinity,
|
|
964
|
-
theta2 = 0.81;
|
|
965
|
-
|
|
966
|
-
function force(_) {
|
|
967
|
-
var i, n = nodes.length, tree = quadtree(nodes, x, y).visitAfter(accumulate);
|
|
968
|
-
for (alpha = _, i = 0; i < n; ++i) node = nodes[i], tree.visit(apply);
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
function initialize() {
|
|
972
|
-
if (!nodes) return;
|
|
973
|
-
var i, n = nodes.length, node;
|
|
974
|
-
strengths = new Array(n);
|
|
975
|
-
for (i = 0; i < n; ++i) node = nodes[i], strengths[node.index] = +strength(node, i, nodes);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
function accumulate(quad) {
|
|
979
|
-
var strength = 0, q, c, weight = 0, x, y, i;
|
|
980
|
-
|
|
981
|
-
// For internal nodes, accumulate forces from child quadrants.
|
|
982
|
-
if (quad.length) {
|
|
983
|
-
for (x = y = i = 0; i < 4; ++i) {
|
|
984
|
-
if ((q = quad[i]) && (c = Math.abs(q.value))) {
|
|
985
|
-
strength += q.value, weight += c, x += c * q.x, y += c * q.y;
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
quad.x = x / weight;
|
|
989
|
-
quad.y = y / weight;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// For leaf nodes, accumulate forces from coincident quadrants.
|
|
993
|
-
else {
|
|
994
|
-
q = quad;
|
|
995
|
-
q.x = q.data.x;
|
|
996
|
-
q.y = q.data.y;
|
|
997
|
-
do strength += strengths[q.data.index];
|
|
998
|
-
while (q = q.next);
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
quad.value = strength;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
function apply(quad, x1, _, x2) {
|
|
1005
|
-
if (!quad.value) return true;
|
|
1006
|
-
|
|
1007
|
-
var x = quad.x - node.x,
|
|
1008
|
-
y = quad.y - node.y,
|
|
1009
|
-
w = x2 - x1,
|
|
1010
|
-
l = x * x + y * y;
|
|
1011
|
-
|
|
1012
|
-
// Apply the Barnes-Hut approximation if possible.
|
|
1013
|
-
// Limit forces for very close nodes; randomize direction if coincident.
|
|
1014
|
-
if (w * w / theta2 < l) {
|
|
1015
|
-
if (l < distanceMax2) {
|
|
1016
|
-
if (x === 0) x = jiggle(random), l += x * x;
|
|
1017
|
-
if (y === 0) y = jiggle(random), l += y * y;
|
|
1018
|
-
if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l);
|
|
1019
|
-
node.vx += x * quad.value * alpha / l;
|
|
1020
|
-
node.vy += y * quad.value * alpha / l;
|
|
1021
|
-
}
|
|
1022
|
-
return true;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
// Otherwise, process points directly.
|
|
1026
|
-
else if (quad.length || l >= distanceMax2) return;
|
|
1027
|
-
|
|
1028
|
-
// Limit forces for very close nodes; randomize direction if coincident.
|
|
1029
|
-
if (quad.data !== node || quad.next) {
|
|
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
|
-
}
|
|
1034
|
-
|
|
1035
|
-
do if (quad.data !== node) {
|
|
1036
|
-
w = strengths[quad.data.index] * alpha / l;
|
|
1037
|
-
node.vx += x * w;
|
|
1038
|
-
node.vy += y * w;
|
|
1039
|
-
} while (quad = quad.next);
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
force.initialize = function(_nodes, _random) {
|
|
1043
|
-
nodes = _nodes;
|
|
1044
|
-
random = _random;
|
|
1045
|
-
initialize();
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
|
-
force.strength = function(_) {
|
|
1049
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
|
|
1050
|
-
};
|
|
1051
|
-
|
|
1052
|
-
force.distanceMin = function(_) {
|
|
1053
|
-
return arguments.length ? (distanceMin2 = _ * _, force) : Math.sqrt(distanceMin2);
|
|
1054
|
-
};
|
|
1055
|
-
|
|
1056
|
-
force.distanceMax = function(_) {
|
|
1057
|
-
return arguments.length ? (distanceMax2 = _ * _, force) : Math.sqrt(distanceMax2);
|
|
1058
|
-
};
|
|
1059
|
-
|
|
1060
|
-
force.theta = function(_) {
|
|
1061
|
-
return arguments.length ? (theta2 = _ * _, force) : Math.sqrt(theta2);
|
|
1062
|
-
};
|
|
1063
|
-
|
|
1064
|
-
return force;
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
function forceX(x) {
|
|
1068
|
-
var strength = constant(0.1),
|
|
1069
|
-
nodes,
|
|
1070
|
-
strengths,
|
|
1071
|
-
xz;
|
|
1072
|
-
|
|
1073
|
-
if (typeof x !== "function") x = constant(x == null ? 0 : +x);
|
|
1074
|
-
|
|
1075
|
-
function force(alpha) {
|
|
1076
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
1077
|
-
node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha;
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
function initialize() {
|
|
1082
|
-
if (!nodes) return;
|
|
1083
|
-
var i, n = nodes.length;
|
|
1084
|
-
strengths = new Array(n);
|
|
1085
|
-
xz = new Array(n);
|
|
1086
|
-
for (i = 0; i < n; ++i) {
|
|
1087
|
-
strengths[i] = isNaN(xz[i] = +x(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
force.initialize = function(_) {
|
|
1092
|
-
nodes = _;
|
|
1093
|
-
initialize();
|
|
1094
|
-
};
|
|
1095
|
-
|
|
1096
|
-
force.strength = function(_) {
|
|
1097
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
|
|
1098
|
-
};
|
|
1099
|
-
|
|
1100
|
-
force.x = function(_) {
|
|
1101
|
-
return arguments.length ? (x = typeof _ === "function" ? _ : constant(+_), initialize(), force) : x;
|
|
1102
|
-
};
|
|
1103
|
-
|
|
1104
|
-
return force;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
function forceY(y) {
|
|
1108
|
-
var strength = constant(0.1),
|
|
1109
|
-
nodes,
|
|
1110
|
-
strengths,
|
|
1111
|
-
yz;
|
|
1112
|
-
|
|
1113
|
-
if (typeof y !== "function") y = constant(y == null ? 0 : +y);
|
|
1114
|
-
|
|
1115
|
-
function force(alpha) {
|
|
1116
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
1117
|
-
node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha;
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
function initialize() {
|
|
1122
|
-
if (!nodes) return;
|
|
1123
|
-
var i, n = nodes.length;
|
|
1124
|
-
strengths = new Array(n);
|
|
1125
|
-
yz = new Array(n);
|
|
1126
|
-
for (i = 0; i < n; ++i) {
|
|
1127
|
-
strengths[i] = isNaN(yz[i] = +y(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
force.initialize = function(_) {
|
|
1132
|
-
nodes = _;
|
|
1133
|
-
initialize();
|
|
1134
|
-
};
|
|
1135
|
-
|
|
1136
|
-
force.strength = function(_) {
|
|
1137
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
|
|
1138
|
-
};
|
|
1139
|
-
|
|
1140
|
-
force.y = function(_) {
|
|
1141
|
-
return arguments.length ? (y = typeof _ === "function" ? _ : constant(+_), initialize(), force) : y;
|
|
1142
|
-
};
|
|
1143
|
-
|
|
1144
|
-
return force;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
/**
|
|
1148
|
-
* Role order for dynamic band calculation (center to outside)
|
|
1149
|
-
*/
|
|
1150
|
-
const ROLE_ORDER = [
|
|
1151
|
-
NodeRole.Entry,
|
|
1152
|
-
NodeRole.InternalFramework,
|
|
1153
|
-
NodeRole.InternalLib,
|
|
1154
|
-
NodeRole.Utility,
|
|
1155
|
-
NodeRole.Test,
|
|
1156
|
-
NodeRole.Tool,
|
|
1157
|
-
];
|
|
1158
|
-
/**
|
|
1159
|
-
* Compute micro-layout for a single cluster using "Solar System" physics
|
|
1160
|
-
*/
|
|
1161
|
-
function computeClusterInterior(cluster, config) {
|
|
1162
|
-
const nodes = cluster.nodes;
|
|
1163
|
-
const n = nodes.length;
|
|
1164
|
-
if (n === 0) {
|
|
1165
|
-
return {
|
|
1166
|
-
clusterId: cluster.id,
|
|
1167
|
-
width: config.minClusterSize,
|
|
1168
|
-
height: config.minClusterSize,
|
|
1169
|
-
relativePositions: new Map(),
|
|
1170
|
-
};
|
|
1171
|
-
}
|
|
1172
|
-
// 1. Calculate Dynamic Bands based on node distribution
|
|
1173
|
-
// Each role gets an area proportional to its node count
|
|
1174
|
-
const roleCounts = new Map();
|
|
1175
|
-
for (const node of nodes) {
|
|
1176
|
-
const role = cluster.metadata?.get(node.id)?.role ?? NodeRole.InternalLib;
|
|
1177
|
-
roleCounts.set(role, (roleCounts.get(role) ?? 0) + 1);
|
|
1178
|
-
}
|
|
1179
|
-
let cumulativeCount = 0;
|
|
1180
|
-
const dynamicBands = new Map();
|
|
1181
|
-
for (const role of ROLE_ORDER) {
|
|
1182
|
-
const count = roleCounts.get(role) ?? 0;
|
|
1183
|
-
const prevR = Math.sqrt(cumulativeCount / n);
|
|
1184
|
-
cumulativeCount += count;
|
|
1185
|
-
const nextR = Math.sqrt(cumulativeCount / n);
|
|
1186
|
-
// Add a tiny buffer to empty bands to prevent division by zero in force calculations
|
|
1187
|
-
// if a node somehow ends up there or just for safety
|
|
1188
|
-
if (count === 0) {
|
|
1189
|
-
dynamicBands.set(role, { min: prevR, max: prevR });
|
|
1190
|
-
}
|
|
1191
|
-
else {
|
|
1192
|
-
dynamicBands.set(role, { min: prevR, max: nextR });
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
// 2. Determine Cluster Size
|
|
1196
|
-
// Use power 0.6 instead of 0.5 (sqrt) to make large clusters sparser (more breathing room)
|
|
1197
|
-
// This scales area slightly super-linearly with N, reducing density as N grows
|
|
1198
|
-
const spacingFactor = 1.3;
|
|
1199
|
-
const nodeSpace = config.nodeRadius * 2 + config.clusterNodeSpacing;
|
|
1200
|
-
const baseR = n ** 0.6 * nodeSpace * spacingFactor;
|
|
1201
|
-
// Add extra padding for large clusters
|
|
1202
|
-
const congestionPadding = Math.max(0, n - 15) * 2.0;
|
|
1203
|
-
const padding = 50 + congestionPadding;
|
|
1204
|
-
const radius = Math.max(config.minClusterSize / 2, baseR + padding);
|
|
1205
|
-
const size = radius * 2;
|
|
1206
|
-
// 3. Initialize Simulation Nodes
|
|
1207
|
-
const simNodes = nodes.map((node) => ({
|
|
1208
|
-
id: node.id,
|
|
1209
|
-
x: (Math.random() - 0.5) * 10, // Start near center
|
|
1210
|
-
y: (Math.random() - 0.5) * 10,
|
|
1211
|
-
vx: 0,
|
|
1212
|
-
vy: 0,
|
|
1213
|
-
role: cluster.metadata?.get(node.id)?.role ?? NodeRole.InternalLib,
|
|
1214
|
-
radius: config.nodeRadius,
|
|
1215
|
-
}));
|
|
1216
|
-
// 4. Create Forces
|
|
1217
|
-
const simulation = forceSimulation(simNodes)
|
|
1218
|
-
// A. Collision (prevent overlap)
|
|
1219
|
-
.force('collide', forceCollide().radius((d) => d.radius + config.nodeCollisionPadding))
|
|
1220
|
-
// B. Solar System Orbit (Band-based positioning)
|
|
1221
|
-
.force('orbit', (alpha) => {
|
|
1222
|
-
const k = alpha * 0.8; // Strong confinement to band
|
|
1223
|
-
for (const node of simNodes) {
|
|
1224
|
-
const band = dynamicBands.get(node.role) ?? { min: 0, max: 1 };
|
|
1225
|
-
const minR = band.min * (radius - 20);
|
|
1226
|
-
const maxR = band.max * (radius - 20);
|
|
1227
|
-
// Current distance
|
|
1228
|
-
const d = Math.hypot(node.x, node.y) || 1e-6;
|
|
1229
|
-
// Only apply force if outside band
|
|
1230
|
-
if (d < minR) {
|
|
1231
|
-
const diff = d - minR; // Negative
|
|
1232
|
-
// Push out
|
|
1233
|
-
const fx = (node.x / d) * diff * k;
|
|
1234
|
-
const fy = (node.y / d) * diff * k;
|
|
1235
|
-
node.vx -= fx;
|
|
1236
|
-
node.vy -= fy;
|
|
1237
|
-
}
|
|
1238
|
-
else if (d > maxR) {
|
|
1239
|
-
const diff = d - maxR; // Positive
|
|
1240
|
-
// Pull in
|
|
1241
|
-
const fx = (node.x / d) * diff * k;
|
|
1242
|
-
const fy = (node.y / d) * diff * k;
|
|
1243
|
-
node.vx -= fx;
|
|
1244
|
-
node.vy -= fy;
|
|
1245
|
-
}
|
|
1246
|
-
// Inside band? Drift freely (repulsion handles spacing)
|
|
1247
|
-
}
|
|
1248
|
-
})
|
|
1249
|
-
// C. Center Gravity (keep things coherent but loose)
|
|
1250
|
-
.force('center', center(0, 0).strength(0.02))
|
|
1251
|
-
// D. Many Body (stronger repulsion to use available space)
|
|
1252
|
-
.force('charge', forceManyBody().strength(config.nodeCharge));
|
|
1253
|
-
// 5. Run Simulation
|
|
1254
|
-
// Micro-layout is small (dozens of nodes), so we can run enough ticks quickly
|
|
1255
|
-
// Break early if the simulation converges before hitting the tick limit
|
|
1256
|
-
const TICKS = 150;
|
|
1257
|
-
for (let i = 0; i < TICKS; i++) {
|
|
1258
|
-
simulation.tick();
|
|
1259
|
-
if (simulation.alpha() < 0.001)
|
|
1260
|
-
break;
|
|
1261
|
-
}
|
|
1262
|
-
simulation.stop();
|
|
1263
|
-
// 6. Extract Positions
|
|
1264
|
-
const relativePositions = new Map();
|
|
1265
|
-
for (const node of simNodes) {
|
|
1266
|
-
relativePositions.set(node.id, {
|
|
1267
|
-
id: node.id,
|
|
1268
|
-
clusterId: cluster.id,
|
|
1269
|
-
x: node.x,
|
|
1270
|
-
y: node.y,
|
|
1271
|
-
vx: 0,
|
|
1272
|
-
vy: 0,
|
|
1273
|
-
radius: node.radius,
|
|
1274
|
-
});
|
|
1275
|
-
}
|
|
1276
|
-
return {
|
|
1277
|
-
clusterId: cluster.id,
|
|
1278
|
-
width: size,
|
|
1279
|
-
height: size,
|
|
1280
|
-
relativePositions,
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
/**
|
|
1285
|
-
* Apply a gentle force-directed "massage" to nodes within a cluster.
|
|
1286
|
-
* This runs after the main "Solar System" layout to improve spacing
|
|
1287
|
-
* and resolve any local congestions while preserving the overall structure.
|
|
1288
|
-
*/
|
|
1289
|
-
function applyNodeMassage(micro, config) {
|
|
1290
|
-
const nodes = Array.from(micro.relativePositions.values()).map((pos) => ({
|
|
1291
|
-
...pos,
|
|
1292
|
-
// Store original positions for tethering
|
|
1293
|
-
ox: pos.x,
|
|
1294
|
-
oy: pos.y,
|
|
1295
|
-
}));
|
|
1296
|
-
if (nodes.length === 0)
|
|
1297
|
-
return micro;
|
|
1298
|
-
const simulation = forceSimulation(nodes)
|
|
1299
|
-
// 1. Anchor to original positions (preserve Solar System bands)
|
|
1300
|
-
.force('x', forceX((d) => d.ox).strength(0.1))
|
|
1301
|
-
.force('y', forceY((d) => d.oy).strength(0.1))
|
|
1302
|
-
// 2. Gentle repulsion to "fluff" the cluster
|
|
1303
|
-
.force('charge', forceManyBody().strength(-30))
|
|
1304
|
-
// 3. Hard collision to ensure no overlaps
|
|
1305
|
-
.force('collide', forceCollide()
|
|
1306
|
-
.radius((d) => (d.radius ?? config.nodeRadius) + config.clusterNodeSpacing)
|
|
1307
|
-
.strength(0.9)
|
|
1308
|
-
.iterations(2))
|
|
1309
|
-
.stop();
|
|
1310
|
-
// Run a short burst, with early exit on convergence
|
|
1311
|
-
const TICKS = 50;
|
|
1312
|
-
for (let i = 0; i < TICKS; i++) {
|
|
1313
|
-
simulation.tick();
|
|
1314
|
-
if (simulation.alpha() < 0.001)
|
|
1315
|
-
break;
|
|
1316
|
-
}
|
|
1317
|
-
// Update positions and recalculate bounds
|
|
1318
|
-
const newPositions = new Map();
|
|
1319
|
-
let minX = Infinity, maxX = -Infinity;
|
|
1320
|
-
let minY = Infinity, maxY = -Infinity;
|
|
1321
|
-
for (const node of nodes) {
|
|
1322
|
-
newPositions.set(node.id, {
|
|
1323
|
-
...micro.relativePositions.get(node.id),
|
|
1324
|
-
x: node.x,
|
|
1325
|
-
y: node.y,
|
|
1326
|
-
});
|
|
1327
|
-
if (node.x < minX)
|
|
1328
|
-
minX = node.x;
|
|
1329
|
-
if (node.x > maxX)
|
|
1330
|
-
maxX = node.x;
|
|
1331
|
-
if (node.y < minY)
|
|
1332
|
-
minY = node.y;
|
|
1333
|
-
if (node.y > maxY)
|
|
1334
|
-
maxY = node.y;
|
|
1335
|
-
}
|
|
1336
|
-
// Ensure we don't shrink below original calculated size (which had "breathing room" logic)
|
|
1337
|
-
// but do expand if needed.
|
|
1338
|
-
// Actually, let's keep it simple: Expand if bounding box exceeds original size.
|
|
1339
|
-
// Assuming 0,0 center, the radius is max(abs(min), abs(max))
|
|
1340
|
-
const maxDim = Math.max(Math.abs(minX), Math.abs(maxX), Math.abs(minY), Math.abs(maxY));
|
|
1341
|
-
const neededSize = maxDim * 2 + 100; // + padding
|
|
1342
|
-
const finalSize = Math.max(micro.width, neededSize);
|
|
1343
|
-
return {
|
|
1344
|
-
...micro,
|
|
1345
|
-
relativePositions: newPositions,
|
|
1346
|
-
width: finalSize,
|
|
1347
|
-
height: finalSize,
|
|
1348
|
-
};
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
/**
|
|
1352
|
-
* @license
|
|
1353
|
-
* Copyright 2019 Google LLC
|
|
1354
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
1355
|
-
*/
|
|
1356
|
-
const proxyMarker = Symbol("Comlink.proxy");
|
|
1357
|
-
const createEndpoint = Symbol("Comlink.endpoint");
|
|
1358
|
-
const releaseProxy = Symbol("Comlink.releaseProxy");
|
|
1359
|
-
const finalizer = Symbol("Comlink.finalizer");
|
|
1360
|
-
const throwMarker = Symbol("Comlink.thrown");
|
|
1361
|
-
const isObject = (val) => (typeof val === "object" && val !== null) || typeof val === "function";
|
|
1362
|
-
/**
|
|
1363
|
-
* Internal transfer handle to handle objects marked to proxy.
|
|
1364
|
-
*/
|
|
1365
|
-
const proxyTransferHandler = {
|
|
1366
|
-
canHandle: (val) => isObject(val) && val[proxyMarker],
|
|
1367
|
-
serialize(obj) {
|
|
1368
|
-
const { port1, port2 } = new MessageChannel();
|
|
1369
|
-
expose(obj, port1);
|
|
1370
|
-
return [port2, [port2]];
|
|
1371
|
-
},
|
|
1372
|
-
deserialize(port) {
|
|
1373
|
-
port.start();
|
|
1374
|
-
return wrap(port);
|
|
1375
|
-
},
|
|
1376
|
-
};
|
|
1377
|
-
/**
|
|
1378
|
-
* Internal transfer handler to handle thrown exceptions.
|
|
1379
|
-
*/
|
|
1380
|
-
const throwTransferHandler = {
|
|
1381
|
-
canHandle: (value) => isObject(value) && throwMarker in value,
|
|
1382
|
-
serialize({ value }) {
|
|
1383
|
-
let serialized;
|
|
1384
|
-
if (value instanceof Error) {
|
|
1385
|
-
serialized = {
|
|
1386
|
-
isError: true,
|
|
1387
|
-
value: {
|
|
1388
|
-
message: value.message,
|
|
1389
|
-
name: value.name,
|
|
1390
|
-
stack: value.stack,
|
|
1391
|
-
},
|
|
1392
|
-
};
|
|
1393
|
-
}
|
|
1394
|
-
else {
|
|
1395
|
-
serialized = { isError: false, value };
|
|
1396
|
-
}
|
|
1397
|
-
return [serialized, []];
|
|
1398
|
-
},
|
|
1399
|
-
deserialize(serialized) {
|
|
1400
|
-
if (serialized.isError) {
|
|
1401
|
-
throw Object.assign(new Error(serialized.value.message), serialized.value);
|
|
1402
|
-
}
|
|
1403
|
-
throw serialized.value;
|
|
1404
|
-
},
|
|
1405
|
-
};
|
|
1406
|
-
/**
|
|
1407
|
-
* Allows customizing the serialization of certain values.
|
|
1408
|
-
*/
|
|
1409
|
-
const transferHandlers = new Map([
|
|
1410
|
-
["proxy", proxyTransferHandler],
|
|
1411
|
-
["throw", throwTransferHandler],
|
|
1412
|
-
]);
|
|
1413
|
-
function isAllowedOrigin(allowedOrigins, origin) {
|
|
1414
|
-
for (const allowedOrigin of allowedOrigins) {
|
|
1415
|
-
if (origin === allowedOrigin || allowedOrigin === "*") {
|
|
1416
|
-
return true;
|
|
1417
|
-
}
|
|
1418
|
-
if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) {
|
|
1419
|
-
return true;
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
return false;
|
|
1423
|
-
}
|
|
1424
|
-
function expose(obj, ep = globalThis, allowedOrigins = ["*"]) {
|
|
1425
|
-
ep.addEventListener("message", function callback(ev) {
|
|
1426
|
-
if (!ev || !ev.data) {
|
|
1427
|
-
return;
|
|
1428
|
-
}
|
|
1429
|
-
if (!isAllowedOrigin(allowedOrigins, ev.origin)) {
|
|
1430
|
-
console.warn(`Invalid origin '${ev.origin}' for comlink proxy`);
|
|
1431
|
-
return;
|
|
1432
|
-
}
|
|
1433
|
-
const { id, type, path } = Object.assign({ path: [] }, ev.data);
|
|
1434
|
-
const argumentList = (ev.data.argumentList || []).map(fromWireValue);
|
|
1435
|
-
let returnValue;
|
|
1436
|
-
try {
|
|
1437
|
-
const parent = path.slice(0, -1).reduce((obj, prop) => obj[prop], obj);
|
|
1438
|
-
const rawValue = path.reduce((obj, prop) => obj[prop], obj);
|
|
1439
|
-
switch (type) {
|
|
1440
|
-
case "GET" /* MessageType.GET */:
|
|
1441
|
-
{
|
|
1442
|
-
returnValue = rawValue;
|
|
1443
|
-
}
|
|
1444
|
-
break;
|
|
1445
|
-
case "SET" /* MessageType.SET */:
|
|
1446
|
-
{
|
|
1447
|
-
parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
|
|
1448
|
-
returnValue = true;
|
|
1449
|
-
}
|
|
1450
|
-
break;
|
|
1451
|
-
case "APPLY" /* MessageType.APPLY */:
|
|
1452
|
-
{
|
|
1453
|
-
returnValue = rawValue.apply(parent, argumentList);
|
|
1454
|
-
}
|
|
1455
|
-
break;
|
|
1456
|
-
case "CONSTRUCT" /* MessageType.CONSTRUCT */:
|
|
1457
|
-
{
|
|
1458
|
-
const value = new rawValue(...argumentList);
|
|
1459
|
-
returnValue = proxy(value);
|
|
1460
|
-
}
|
|
1461
|
-
break;
|
|
1462
|
-
case "ENDPOINT" /* MessageType.ENDPOINT */:
|
|
1463
|
-
{
|
|
1464
|
-
const { port1, port2 } = new MessageChannel();
|
|
1465
|
-
expose(obj, port2);
|
|
1466
|
-
returnValue = transfer(port1, [port1]);
|
|
1467
|
-
}
|
|
1468
|
-
break;
|
|
1469
|
-
case "RELEASE" /* MessageType.RELEASE */:
|
|
1470
|
-
{
|
|
1471
|
-
returnValue = undefined;
|
|
1472
|
-
}
|
|
1473
|
-
break;
|
|
1474
|
-
default:
|
|
1475
|
-
return;
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
catch (value) {
|
|
1479
|
-
returnValue = { value, [throwMarker]: 0 };
|
|
1480
|
-
}
|
|
1481
|
-
Promise.resolve(returnValue)
|
|
1482
|
-
.catch((value) => {
|
|
1483
|
-
return { value, [throwMarker]: 0 };
|
|
1484
|
-
})
|
|
1485
|
-
.then((returnValue) => {
|
|
1486
|
-
const [wireValue, transferables] = toWireValue(returnValue);
|
|
1487
|
-
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
|
|
1488
|
-
if (type === "RELEASE" /* MessageType.RELEASE */) {
|
|
1489
|
-
// detach and deactive after sending release response above.
|
|
1490
|
-
ep.removeEventListener("message", callback);
|
|
1491
|
-
closeEndPoint(ep);
|
|
1492
|
-
if (finalizer in obj && typeof obj[finalizer] === "function") {
|
|
1493
|
-
obj[finalizer]();
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
})
|
|
1497
|
-
.catch((error) => {
|
|
1498
|
-
// Send Serialization Error To Caller
|
|
1499
|
-
const [wireValue, transferables] = toWireValue({
|
|
1500
|
-
value: new TypeError("Unserializable return value"),
|
|
1501
|
-
[throwMarker]: 0,
|
|
1502
|
-
});
|
|
1503
|
-
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
|
|
1504
|
-
});
|
|
1505
|
-
});
|
|
1506
|
-
if (ep.start) {
|
|
1507
|
-
ep.start();
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
function isMessagePort(endpoint) {
|
|
1511
|
-
return endpoint.constructor.name === "MessagePort";
|
|
1512
|
-
}
|
|
1513
|
-
function closeEndPoint(endpoint) {
|
|
1514
|
-
if (isMessagePort(endpoint))
|
|
1515
|
-
endpoint.close();
|
|
1516
|
-
}
|
|
1517
|
-
function wrap(ep, target) {
|
|
1518
|
-
const pendingListeners = new Map();
|
|
1519
|
-
ep.addEventListener("message", function handleMessage(ev) {
|
|
1520
|
-
const { data } = ev;
|
|
1521
|
-
if (!data || !data.id) {
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
|
-
const resolver = pendingListeners.get(data.id);
|
|
1525
|
-
if (!resolver) {
|
|
1526
|
-
return;
|
|
1527
|
-
}
|
|
1528
|
-
try {
|
|
1529
|
-
resolver(data);
|
|
1530
|
-
}
|
|
1531
|
-
finally {
|
|
1532
|
-
pendingListeners.delete(data.id);
|
|
1533
|
-
}
|
|
1534
|
-
});
|
|
1535
|
-
return createProxy(ep, pendingListeners, [], target);
|
|
1536
|
-
}
|
|
1537
|
-
function throwIfProxyReleased(isReleased) {
|
|
1538
|
-
if (isReleased) {
|
|
1539
|
-
throw new Error("Proxy has been released and is not useable");
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
function releaseEndpoint(ep) {
|
|
1543
|
-
return requestResponseMessage(ep, new Map(), {
|
|
1544
|
-
type: "RELEASE" /* MessageType.RELEASE */,
|
|
1545
|
-
}).then(() => {
|
|
1546
|
-
closeEndPoint(ep);
|
|
1547
|
-
});
|
|
1548
|
-
}
|
|
1549
|
-
const proxyCounter = new WeakMap();
|
|
1550
|
-
const proxyFinalizers = "FinalizationRegistry" in globalThis &&
|
|
1551
|
-
new FinalizationRegistry((ep) => {
|
|
1552
|
-
const newCount = (proxyCounter.get(ep) || 0) - 1;
|
|
1553
|
-
proxyCounter.set(ep, newCount);
|
|
1554
|
-
if (newCount === 0) {
|
|
1555
|
-
releaseEndpoint(ep);
|
|
1556
|
-
}
|
|
1557
|
-
});
|
|
1558
|
-
function registerProxy(proxy, ep) {
|
|
1559
|
-
const newCount = (proxyCounter.get(ep) || 0) + 1;
|
|
1560
|
-
proxyCounter.set(ep, newCount);
|
|
1561
|
-
if (proxyFinalizers) {
|
|
1562
|
-
proxyFinalizers.register(proxy, ep, proxy);
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
function unregisterProxy(proxy) {
|
|
1566
|
-
if (proxyFinalizers) {
|
|
1567
|
-
proxyFinalizers.unregister(proxy);
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
function createProxy(ep, pendingListeners, path = [], target = function () { }) {
|
|
1571
|
-
let isProxyReleased = false;
|
|
1572
|
-
const proxy = new Proxy(target, {
|
|
1573
|
-
get(_target, prop) {
|
|
1574
|
-
throwIfProxyReleased(isProxyReleased);
|
|
1575
|
-
if (prop === releaseProxy) {
|
|
1576
|
-
return () => {
|
|
1577
|
-
unregisterProxy(proxy);
|
|
1578
|
-
releaseEndpoint(ep);
|
|
1579
|
-
pendingListeners.clear();
|
|
1580
|
-
isProxyReleased = true;
|
|
1581
|
-
};
|
|
1582
|
-
}
|
|
1583
|
-
if (prop === "then") {
|
|
1584
|
-
if (path.length === 0) {
|
|
1585
|
-
return { then: () => proxy };
|
|
1586
|
-
}
|
|
1587
|
-
const r = requestResponseMessage(ep, pendingListeners, {
|
|
1588
|
-
type: "GET" /* MessageType.GET */,
|
|
1589
|
-
path: path.map((p) => p.toString()),
|
|
1590
|
-
}).then(fromWireValue);
|
|
1591
|
-
return r.then.bind(r);
|
|
1592
|
-
}
|
|
1593
|
-
return createProxy(ep, pendingListeners, [...path, prop]);
|
|
1594
|
-
},
|
|
1595
|
-
set(_target, prop, rawValue) {
|
|
1596
|
-
throwIfProxyReleased(isProxyReleased);
|
|
1597
|
-
// FIXME: ES6 Proxy Handler `set` methods are supposed to return a
|
|
1598
|
-
// boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯
|
|
1599
|
-
const [value, transferables] = toWireValue(rawValue);
|
|
1600
|
-
return requestResponseMessage(ep, pendingListeners, {
|
|
1601
|
-
type: "SET" /* MessageType.SET */,
|
|
1602
|
-
path: [...path, prop].map((p) => p.toString()),
|
|
1603
|
-
value,
|
|
1604
|
-
}, transferables).then(fromWireValue);
|
|
1605
|
-
},
|
|
1606
|
-
apply(_target, _thisArg, rawArgumentList) {
|
|
1607
|
-
throwIfProxyReleased(isProxyReleased);
|
|
1608
|
-
const last = path[path.length - 1];
|
|
1609
|
-
if (last === createEndpoint) {
|
|
1610
|
-
return requestResponseMessage(ep, pendingListeners, {
|
|
1611
|
-
type: "ENDPOINT" /* MessageType.ENDPOINT */,
|
|
1612
|
-
}).then(fromWireValue);
|
|
1613
|
-
}
|
|
1614
|
-
// We just pretend that `bind()` didn’t happen.
|
|
1615
|
-
if (last === "bind") {
|
|
1616
|
-
return createProxy(ep, pendingListeners, path.slice(0, -1));
|
|
1617
|
-
}
|
|
1618
|
-
const [argumentList, transferables] = processArguments(rawArgumentList);
|
|
1619
|
-
return requestResponseMessage(ep, pendingListeners, {
|
|
1620
|
-
type: "APPLY" /* MessageType.APPLY */,
|
|
1621
|
-
path: path.map((p) => p.toString()),
|
|
1622
|
-
argumentList,
|
|
1623
|
-
}, transferables).then(fromWireValue);
|
|
1624
|
-
},
|
|
1625
|
-
construct(_target, rawArgumentList) {
|
|
1626
|
-
throwIfProxyReleased(isProxyReleased);
|
|
1627
|
-
const [argumentList, transferables] = processArguments(rawArgumentList);
|
|
1628
|
-
return requestResponseMessage(ep, pendingListeners, {
|
|
1629
|
-
type: "CONSTRUCT" /* MessageType.CONSTRUCT */,
|
|
1630
|
-
path: path.map((p) => p.toString()),
|
|
1631
|
-
argumentList,
|
|
1632
|
-
}, transferables).then(fromWireValue);
|
|
1633
|
-
},
|
|
1634
|
-
});
|
|
1635
|
-
registerProxy(proxy, ep);
|
|
1636
|
-
return proxy;
|
|
1637
|
-
}
|
|
1638
|
-
function myFlat(arr) {
|
|
1639
|
-
return Array.prototype.concat.apply([], arr);
|
|
1640
|
-
}
|
|
1641
|
-
function processArguments(argumentList) {
|
|
1642
|
-
const processed = argumentList.map(toWireValue);
|
|
1643
|
-
return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];
|
|
1644
|
-
}
|
|
1645
|
-
const transferCache = new WeakMap();
|
|
1646
|
-
function transfer(obj, transfers) {
|
|
1647
|
-
transferCache.set(obj, transfers);
|
|
1648
|
-
return obj;
|
|
1649
|
-
}
|
|
1650
|
-
function proxy(obj) {
|
|
1651
|
-
return Object.assign(obj, { [proxyMarker]: true });
|
|
1652
|
-
}
|
|
1653
|
-
function toWireValue(value) {
|
|
1654
|
-
for (const [name, handler] of transferHandlers) {
|
|
1655
|
-
if (handler.canHandle(value)) {
|
|
1656
|
-
const [serializedValue, transferables] = handler.serialize(value);
|
|
1657
|
-
return [
|
|
1658
|
-
{
|
|
1659
|
-
type: "HANDLER" /* WireValueType.HANDLER */,
|
|
1660
|
-
name,
|
|
1661
|
-
value: serializedValue,
|
|
1662
|
-
},
|
|
1663
|
-
transferables,
|
|
1664
|
-
];
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
return [
|
|
1668
|
-
{
|
|
1669
|
-
type: "RAW" /* WireValueType.RAW */,
|
|
1670
|
-
value,
|
|
1671
|
-
},
|
|
1672
|
-
transferCache.get(value) || [],
|
|
1673
|
-
];
|
|
1674
|
-
}
|
|
1675
|
-
function fromWireValue(value) {
|
|
1676
|
-
switch (value.type) {
|
|
1677
|
-
case "HANDLER" /* WireValueType.HANDLER */:
|
|
1678
|
-
return transferHandlers.get(value.name).deserialize(value.value);
|
|
1679
|
-
case "RAW" /* WireValueType.RAW */:
|
|
1680
|
-
return value.value;
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
function requestResponseMessage(ep, pendingListeners, msg, transfers) {
|
|
1684
|
-
return new Promise((resolve) => {
|
|
1685
|
-
const id = generateUUID();
|
|
1686
|
-
pendingListeners.set(id, resolve);
|
|
1687
|
-
if (ep.start) {
|
|
1688
|
-
ep.start();
|
|
1689
|
-
}
|
|
1690
|
-
ep.postMessage(Object.assign({ id }, msg), transfers);
|
|
1691
|
-
});
|
|
1692
|
-
}
|
|
1693
|
-
function generateUUID() {
|
|
1694
|
-
return new Array(4)
|
|
1695
|
-
.fill(0)
|
|
1696
|
-
.map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16))
|
|
1697
|
-
.join("-");
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
/**
|
|
1701
|
-
* Micro-Layout Web Worker
|
|
1702
|
-
*
|
|
1703
|
-
* Runs computeClusterInterior + applyNodeMassage for a single cluster
|
|
1704
|
-
* off the main thread. Used by parallel-micro.ts to parallelize
|
|
1705
|
-
* micro-layout computation across multiple workers.
|
|
1706
|
-
*/
|
|
1707
|
-
function deserializeCluster(sc) {
|
|
1708
|
-
return {
|
|
1709
|
-
...sc,
|
|
1710
|
-
metadata: new Map(sc.metadata),
|
|
1711
|
-
};
|
|
1712
|
-
}
|
|
1713
|
-
const workerApi = {
|
|
1714
|
-
computeMicro(serializedCluster, config) {
|
|
1715
|
-
const cluster = deserializeCluster(serializedCluster);
|
|
1716
|
-
let micro = computeClusterInterior(cluster, config);
|
|
1717
|
-
micro = applyNodeMassage(micro, config);
|
|
1718
|
-
return {
|
|
1719
|
-
clusterId: micro.clusterId,
|
|
1720
|
-
width: micro.width,
|
|
1721
|
-
height: micro.height,
|
|
1722
|
-
relativePositions: Array.from(micro.relativePositions.entries()),
|
|
1723
|
-
};
|
|
1724
|
-
},
|
|
1725
|
-
};
|
|
1726
|
-
expose(workerApi);
|