@graphty/graphty-element 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/AGENTS.md +4 -3
  2. package/dist/ai.js +115 -222
  3. package/dist/catalog.js +46 -44
  4. package/dist/chunks/{AiManager-Bd_r1Hei.js → AiManager-BD9XK30e.js} +793 -654
  5. package/dist/chunks/{DataSource-bt0DhBjG.js → DataSource-B8vf2uhW.js} +3 -3
  6. package/dist/chunks/{GraphSession-iNyKm7Ds.js → GraphSession-dcwOjGJh.js} +3028 -2918
  7. package/dist/chunks/{GraphStyle-D0PXnZKu.js → GraphStyle-Cwr55SAE.js} +5 -2
  8. package/dist/chunks/{GraphtyError-BwcnblTH.js → GraphtyError-B93WRH3e.js} +8 -6
  9. package/dist/chunks/{GraphtyLogger-5KEttFUo.js → GraphtyLogger-CqOVV13Y.js} +2 -2
  10. package/dist/chunks/{NodeStyle-DKj7HjMJ.js → NodeStyle-CtmA7dXi.js} +16 -14
  11. package/dist/chunks/{VoiceInputAdapter-DszYl6Ha.js → VoiceInputAdapter-D0tHHi9G.js} +1 -1
  12. package/dist/chunks/{XRPivotCameraController-XtxbbZ-D.js → XRPivotCameraController-DTfhvhHz.js} +2 -2
  13. package/dist/chunks/algorithms-BF0X6RPw.js +3627 -0
  14. package/dist/chunks/{capability-check-BqIEXcun.js → capability-check-BJzlK4oL.js} +1 -1
  15. package/dist/chunks/{detect-DM29BEaB.js → detect-vJxK7n0D.js} +2 -2
  16. package/dist/chunks/{format-detection-BEdmtvsy.js → format-detection-C80TLQ2e.js} +1 -1
  17. package/dist/chunks/{index-CD0_RJv-.js → index-2xkq7wyD.js} +11482 -10971
  18. package/dist/chunks/optionsFromZod-BuTOFgVM.js +2572 -0
  19. package/dist/chunks/paletteRegistry-A63C71Gn.js +1155 -0
  20. package/dist/chunks/{registry-CSba5QGJ.js → registry-jB46Gmeb.js} +1 -1
  21. package/dist/chunks/scales-B2d-7Bf0.js +3220 -0
  22. package/dist/chunks/{types-B7bX5c0K.js → types-C_c53VgR.js} +31 -26
  23. package/dist/commands.d.ts +4 -0
  24. package/dist/custom-elements.json +1 -1
  25. package/dist/extend.js +45 -45
  26. package/dist/graphty-catalog.json +245 -12
  27. package/dist/graphty.bundle.js +40762 -39403
  28. package/dist/graphty.js +33 -33
  29. package/dist/logging.js +2 -2
  30. package/dist/schema.d.ts +1 -1
  31. package/dist/schema.js +42 -40
  32. package/dist/session.d.ts +1 -1
  33. package/dist/session.js +29 -30
  34. package/dist/src/Graph.d.ts +112 -10
  35. package/dist/src/acceleration/AccelerationController.d.ts +14 -3
  36. package/dist/src/acceleration/types.d.ts +78 -0
  37. package/dist/src/algorithms/KCoreAlgorithm.d.ts +26 -0
  38. package/dist/src/algorithms/LinkPredictionAlgorithm.d.ts +41 -0
  39. package/dist/src/camera/builtins.d.ts +14 -1
  40. package/dist/src/camera/types.d.ts +7 -0
  41. package/dist/src/cameras/CameraManager.d.ts +13 -0
  42. package/dist/src/cameras/OrbitCameraController.d.ts +9 -0
  43. package/dist/src/catalog/algorithms.d.ts +5 -5
  44. package/dist/src/catalog/index.d.ts +2 -2
  45. package/dist/src/catalog/layouts.d.ts +7 -6
  46. package/dist/src/catalog/types.d.ts +87 -6
  47. package/dist/src/config/EdgeStyle.d.ts +35 -0
  48. package/dist/src/config/GraphStyle.d.ts +5 -1
  49. package/dist/src/config/StyleTemplate.d.ts +2 -2
  50. package/dist/src/config/index.d.ts +1 -1
  51. package/dist/src/data/GEXFDataSource.d.ts +23 -0
  52. package/dist/src/errors/codes.d.ts +14 -0
  53. package/dist/src/events.d.ts +30 -0
  54. package/dist/src/graphty-element.d.ts +77 -32
  55. package/dist/src/layout/GridLayoutEngine.d.ts +37 -0
  56. package/dist/src/layout/LayoutEngine.d.ts +7 -7
  57. package/dist/src/layout/NGraphLayoutEngine.d.ts +2 -0
  58. package/dist/src/layout/RadialLayoutEngine.d.ts +37 -0
  59. package/dist/src/layout/SimulationLayoutEngine.d.ts +12 -0
  60. package/dist/src/managers/DataManager.d.ts +69 -2
  61. package/dist/src/managers/EventManager.d.ts +10 -4
  62. package/dist/src/managers/GraphContext.d.ts +2 -1
  63. package/dist/src/managers/LayoutManager.d.ts +30 -3
  64. package/dist/src/managers/StylePainter.d.ts +9 -0
  65. package/dist/src/managers/UpdateManager.d.ts +5 -0
  66. package/dist/src/meshes/MeshCache.d.ts +18 -0
  67. package/dist/src/meshes/NodeEffects.d.ts +16 -11
  68. package/dist/src/meshes/NodeMesh.d.ts +2 -2
  69. package/dist/src/meshes/RichTextParser.d.ts +26 -0
  70. package/dist/src/meshes/RichTextRenderer.d.ts +0 -1
  71. package/dist/src/session/cost/estimate.d.ts +1 -1
  72. package/dist/src/session/layout.d.ts +3 -3
  73. package/dist/src/session/results/index.d.ts +1 -1
  74. package/dist/src/session/results/statistics.d.ts +8 -1
  75. package/dist/src/session/results/types.d.ts +33 -0
  76. package/dist/src/session/runs/RunsApi.d.ts +1 -1
  77. package/dist/src/session/selection/targets.d.ts +4 -1
  78. package/dist/src/session/styles/StylesApi.d.ts +6 -0
  79. package/dist/src/session/styles/intern.d.ts +26 -5
  80. package/dist/src/session/styles/predicate.d.ts +26 -1
  81. package/dist/src/session/styles/repaint.d.ts +13 -1
  82. package/dist/src/session/styles/selector.d.ts +13 -1
  83. package/dist/src/session/styles/sources.d.ts +9 -0
  84. package/dist/src/session/types.d.ts +6 -6
  85. package/dist/webgpu.js +2 -2
  86. package/package.json +6 -6
  87. package/dist/chunks/Algorithm-RQ629NLb.js +0 -494
  88. package/dist/chunks/cameras-ii4vngYY.js +0 -435
  89. package/dist/chunks/paletteRegistry-DnWHQsAD.js +0 -3166
  90. package/dist/chunks/scales-DyuwlJKI.js +0 -6087
@@ -0,0 +1,3220 @@
1
+ import { arfLayout as Le, bfsLayout as Te, bipartiteLayout as We, circularLayout as Ae, createSimulation as _e, seedPositions as Be, resolveNodeVector as Ge, gridLayout as qe, kamadaKawaiLayout as je, multipartiteLayout as Ue, planarLayout as He, radialLayout as Ke, randomLayout as Ve, shellLayout as Ye, spectralLayout as $e, spiralLayout as Je } from "@graphty/layout";
2
+ import { z as i } from "zod/v4";
3
+ import { a as y, S as v, L as fe, k as Ze, W as Qe, l as ke, m as Xe, g as ze } from "./paletteRegistry-A63C71Gn.js";
4
+ import { k as u, j as et } from "./algorithms-BF0X6RPw.js";
5
+ import { forceLink as tt, forceSimulation as it, forceManyBody as st, forceCenter as nt } from "d3-force-3d";
6
+ import { makeMask as xe, maskSet as ue } from "@graphty/graph-format";
7
+ import { C as me, E as at, o as ot } from "./optionsFromZod-BuTOFgVM.js";
8
+ import { G as pe } from "./GraphtyError-B93WRH3e.js";
9
+ import rt from "ngraph.forcelayout";
10
+ import lt from "ngraph.graph";
11
+ import { V as ct, Y as dt, g as ht, I as ut, B as mt, G as pt, d as gt, O as Se, i as Ne, T as De, P as we, C as Oe, h as ft, b as bt, R as yt, a as Me, c as Ee, e as Ie } from "./sequential-Zym81qm7.js";
12
+ const vt = u({
13
+ scalingFactor: {
14
+ schema: i.number().min(1).max(1e3).default(100),
15
+ meta: {
16
+ label: "Scaling Factor",
17
+ description: "Multiplier for node positions"
18
+ }
19
+ },
20
+ scaling: {
21
+ schema: i.number().positive().default(1),
22
+ meta: {
23
+ label: "Scaling",
24
+ description: "Scale factor for the attractive force",
25
+ step: 0.1
26
+ }
27
+ },
28
+ a: {
29
+ schema: i.number().positive().default(1.1),
30
+ meta: {
31
+ label: "Attraction Ratio",
32
+ description: "Ratio of attraction to repulsion",
33
+ step: 0.1,
34
+ advanced: !0
35
+ }
36
+ },
37
+ maxIter: {
38
+ schema: i.number().int().positive().default(1e3),
39
+ meta: {
40
+ label: "Max Iterations",
41
+ description: "Maximum number of iterations"
42
+ }
43
+ },
44
+ seed: {
45
+ schema: i.number().positive().nullable().default(null),
46
+ meta: {
47
+ label: "Random Seed",
48
+ description: "Seed for reproducible layout",
49
+ advanced: !0
50
+ }
51
+ }
52
+ }), xt = i.strictObject({
53
+ ...v.shape,
54
+ pos: i.record(i.number(), i.array(i.number())).or(i.null()).default(null),
55
+ scaling: i.number().positive().default(1),
56
+ a: i.number().positive().default(1.1),
57
+ maxIter: i.number().positive().default(1e3),
58
+ seed: i.number().positive().or(i.null()).default(null)
59
+ }), k = class k extends y {
60
+ /**
61
+ * Create an ARF layout engine
62
+ * @param opts - Configuration options for the ARF algorithm
63
+ */
64
+ constructor(e) {
65
+ super(e), this.scalingFactor = 100, this.config = xt.parse(e);
66
+ }
67
+ /**
68
+ * Get dimension-specific options for ARF layout
69
+ * @param dimension - The desired dimension (2 or 3)
70
+ * @returns Empty object for 2D, null for 3D (unsupported)
71
+ */
72
+ static getOptionsForDimension(e) {
73
+ return e > this.maxDimensions ? null : {};
74
+ }
75
+ /**
76
+ * Compute node positions using the ARF algorithm
77
+ */
78
+ doLayout() {
79
+ this.stale = !1;
80
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
81
+ this.positions = Le(
82
+ { nodes: e, edges: t },
83
+ this.config.pos,
84
+ this.config.scaling,
85
+ this.config.a,
86
+ this.config.maxIter,
87
+ this.config.seed
88
+ );
89
+ }
90
+ };
91
+ k.type = "arf", k.maxDimensions = 2, k.zodOptionsSchema = vt;
92
+ let K = k;
93
+ const St = u({
94
+ scalingFactor: {
95
+ schema: i.number().min(1).max(1e3).default(20),
96
+ meta: {
97
+ label: "Scaling Factor",
98
+ description: "Multiplier for node positions"
99
+ }
100
+ },
101
+ start: {
102
+ schema: i.union([i.string(), i.number()]),
103
+ meta: {
104
+ label: "Start Node",
105
+ description: "Starting node for BFS traversal"
106
+ }
107
+ },
108
+ align: {
109
+ schema: i.enum(["vertical", "horizontal"]).default("vertical"),
110
+ meta: {
111
+ label: "Alignment",
112
+ description: "Direction of BFS tree expansion"
113
+ }
114
+ },
115
+ scale: {
116
+ schema: i.number().positive().default(1),
117
+ meta: {
118
+ label: "Scale",
119
+ description: "Scale factor for the layout",
120
+ step: 0.1
121
+ }
122
+ }
123
+ }), Nt = i.strictObject({
124
+ ...v.shape,
125
+ start: i.number().or(i.string()),
126
+ align: i.enum(["vertical", "horizontal"]).default("vertical"),
127
+ scale: i.number().positive().default(1),
128
+ center: i.array(i.number()).length(2).or(i.null()).default(null)
129
+ }), z = class z extends y {
130
+ /**
131
+ * Create a BFS layout engine
132
+ * @param opts - Configuration options including start node and alignment
133
+ */
134
+ constructor(e) {
135
+ super(e), this.scalingFactor = 20, this.config = Nt.parse(e);
136
+ }
137
+ /**
138
+ * Get dimension-specific options for BFS layout
139
+ * @param dimension - The desired dimension (2 or 3)
140
+ * @returns Empty object for 2D, null for 3D (unsupported)
141
+ */
142
+ static getOptionsForDimension(e) {
143
+ return e > this.maxDimensions ? null : {};
144
+ }
145
+ /**
146
+ * Compute node positions using BFS traversal
147
+ */
148
+ doLayout() {
149
+ this.stale = !1;
150
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
151
+ this.positions = Te(
152
+ { nodes: e, edges: t },
153
+ this.config.start,
154
+ this.config.align,
155
+ this.config.scale,
156
+ this.config.center
157
+ );
158
+ }
159
+ };
160
+ z.type = "bfs", z.maxDimensions = 2, z.zodOptionsSchema = St;
161
+ let V = z;
162
+ const Dt = u({
163
+ scalingFactor: {
164
+ schema: i.number().min(1).max(1e3).default(40),
165
+ meta: {
166
+ label: "Scaling Factor",
167
+ description: "Multiplier for node positions"
168
+ }
169
+ },
170
+ align: {
171
+ schema: i.enum(["vertical", "horizontal"]).default("vertical"),
172
+ meta: {
173
+ label: "Alignment",
174
+ description: "Direction of bipartite partitions"
175
+ }
176
+ },
177
+ scale: {
178
+ schema: i.number().positive().default(1),
179
+ meta: {
180
+ label: "Scale",
181
+ description: "Scale factor for the layout",
182
+ step: 0.1
183
+ }
184
+ },
185
+ aspectRatio: {
186
+ schema: i.number().positive().default(4 / 3),
187
+ meta: {
188
+ label: "Aspect Ratio",
189
+ description: "Width to height ratio",
190
+ step: 0.1
191
+ }
192
+ }
193
+ }), wt = i.strictObject({
194
+ ...v.shape,
195
+ nodes: i.array(i.number().or(i.string())),
196
+ align: i.enum(["vertical", "horizontal"]).default("vertical"),
197
+ scale: i.number().positive().default(1),
198
+ center: i.array(i.number()).length(2).or(i.null()).default(null),
199
+ aspectRatio: i.number().positive().default(4 / 3)
200
+ }), F = class F extends y {
201
+ /**
202
+ * Create a bipartite layout engine
203
+ * @param opts - Configuration options including node partitions and alignment
204
+ */
205
+ constructor(e) {
206
+ super(e), this.scalingFactor = 40, this.config = wt.parse(e);
207
+ }
208
+ /**
209
+ * Get dimension-specific options for bipartite layout
210
+ * @param dimension - The desired dimension (2 or 3)
211
+ * @returns Empty object for 2D, null for 3D (unsupported)
212
+ */
213
+ static getOptionsForDimension(e) {
214
+ return e > this.maxDimensions ? null : {};
215
+ }
216
+ /**
217
+ * Compute node positions for bipartite graph
218
+ */
219
+ doLayout() {
220
+ this.stale = !1;
221
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
222
+ this.positions = We(
223
+ { nodes: e, edges: t },
224
+ this.config.nodes,
225
+ this.config.align,
226
+ this.config.scale,
227
+ this.config.center,
228
+ this.config.aspectRatio
229
+ );
230
+ }
231
+ };
232
+ F.type = "bipartite", F.maxDimensions = 2, F.zodOptionsSchema = Dt;
233
+ let Y = F;
234
+ const Ot = u({
235
+ scalingFactor: {
236
+ schema: i.number().min(1).max(1e3).default(100),
237
+ meta: {
238
+ label: "Scaling Factor",
239
+ description: "Multiplier for node positions"
240
+ }
241
+ },
242
+ scale: {
243
+ schema: i.number().positive().default(1),
244
+ meta: {
245
+ label: "Scale",
246
+ description: "Scale factor for the circular layout radius",
247
+ step: 0.1
248
+ }
249
+ },
250
+ dim: {
251
+ schema: i.number().int().min(2).max(3).default(2),
252
+ meta: {
253
+ label: "Dimensions",
254
+ description: "Layout dimensionality (2D or 3D)"
255
+ }
256
+ }
257
+ }), Mt = i.strictObject({
258
+ ...v.shape,
259
+ scale: i.number().positive().default(1),
260
+ center: i.array(i.number()).min(2).max(3).or(i.null()).default(null),
261
+ dim: i.number().default(2)
262
+ }), P = class P extends y {
263
+ /**
264
+ * Create a circular layout engine
265
+ * @param opts - Configuration options including scale and dimensions
266
+ */
267
+ constructor(e) {
268
+ super(e), this.scalingFactor = 80, this.config = Mt.parse(e);
269
+ }
270
+ /**
271
+ * Get dimension-specific options for circular layout
272
+ * @param dimension - The desired dimension (2 or 3)
273
+ * @returns Options object with dim parameter
274
+ */
275
+ static getOptionsForDimension(e) {
276
+ return { dim: e };
277
+ }
278
+ /**
279
+ * Compute node positions in a circular arrangement
280
+ */
281
+ doLayout() {
282
+ this.stale = !1;
283
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
284
+ this.positions = Ae({ nodes: e, edges: t }, this.config.scale, this.config.center, this.config.dim);
285
+ }
286
+ };
287
+ P.type = "circular", P.maxDimensions = 3, P.zodOptionsSchema = Ot;
288
+ let $ = P;
289
+ const Et = u({
290
+ alphaMin: {
291
+ schema: i.number().positive().default(0.1),
292
+ meta: {
293
+ label: "Alpha Min",
294
+ description: "Minimum alpha before simulation stops",
295
+ step: 0.01,
296
+ advanced: !0
297
+ }
298
+ },
299
+ alphaTarget: {
300
+ schema: i.number().min(0).default(0),
301
+ meta: {
302
+ label: "Alpha Target",
303
+ description: "Target alpha value",
304
+ step: 0.01,
305
+ advanced: !0
306
+ }
307
+ },
308
+ alphaDecay: {
309
+ schema: i.number().positive().default(0.0228),
310
+ meta: {
311
+ label: "Alpha Decay",
312
+ description: "Rate of alpha decay per tick",
313
+ step: 1e-3,
314
+ advanced: !0
315
+ }
316
+ },
317
+ velocityDecay: {
318
+ schema: i.number().positive().default(0.4),
319
+ meta: {
320
+ label: "Velocity Decay",
321
+ description: "Velocity damping factor",
322
+ step: 0.05
323
+ }
324
+ }
325
+ });
326
+ function ge(n) {
327
+ return typeof n == "object" && n !== null && "index" in n && typeof n.index == "number" && "x" in n && typeof n.x == "number" && "y" in n && typeof n.y == "number" && "z" in n && typeof n.z == "number" && "vx" in n && typeof n.vx == "number" && "vy" in n && typeof n.vy == "number" && "vz" in n && typeof n.vz == "number";
328
+ }
329
+ const It = i.strictObject({
330
+ alphaMin: i.number().positive().default(0.1),
331
+ alphaTarget: i.number().min(0).default(0),
332
+ alphaDecay: i.number().positive().default(0.0228),
333
+ velocityDecay: i.number().positive().default(0.4)
334
+ });
335
+ function Ct(n) {
336
+ return !!(typeof n == "object" && n !== null && Object.hasOwn(n, "index") && "index" in n && typeof n.index == "number" && "source" in n && ge(n.source) && "target" in n && ge(n.target));
337
+ }
338
+ const L = class L extends fe {
339
+ /**
340
+ * Create a D3 force-directed layout engine
341
+ * @param anyOpts - Configuration options for the D3 simulation
342
+ */
343
+ constructor(e = {}) {
344
+ super(), this.nodeMapping = /* @__PURE__ */ new Map(), this.edgeMapping = /* @__PURE__ */ new Map(), this.newNodeMap = /* @__PURE__ */ new Map(), this.newEdgeMap = /* @__PURE__ */ new Map(), this.reheat = !1;
345
+ const t = It.parse(e);
346
+ this.d3AlphaMin = t.alphaMin, this.d3AlphaTarget = t.alphaTarget, this.d3AlphaDecay = t.alphaDecay, this.d3VelocityDecay = t.velocityDecay;
347
+ const s = tt();
348
+ s.strength(0.9), this.d3ForceLayout = it().numDimensions(3).alpha(1).force("link", s).force("charge", st()).force("center", nt()).force("dagRadial", null).stop(), this.d3ForceLayout.force("link").id((o) => o.id);
349
+ }
350
+ /**
351
+ * Check if there are pending nodes or edges to be processed
352
+ * @returns True if the graph needs to be refreshed
353
+ */
354
+ get graphNeedsRefresh() {
355
+ return !!this.newNodeMap.size || !!this.newEdgeMap.size;
356
+ }
357
+ /**
358
+ * Initialize the layout engine
359
+ *
360
+ * D3 force simulation is initialized in the constructor and doesn't require
361
+ * additional async initialization.
362
+ */
363
+ async init() {
364
+ }
365
+ /**
366
+ * Refresh the D3 simulation with pending nodes and edges
367
+ */
368
+ refresh() {
369
+ if (this.graphNeedsRefresh || this.reheat) {
370
+ let e = [...this.nodeMapping.values()];
371
+ e = e.concat([...this.newNodeMap.values()]), this.d3ForceLayout.alpha(1).nodes(e).stop();
372
+ for (const s of this.newNodeMap.entries()) {
373
+ const o = s[0], r = s[1];
374
+ if (!ge(r))
375
+ throw new Error("Internal error: Node is not settled as a complete D3 Node");
376
+ this.nodeMapping.set(o, r);
377
+ }
378
+ this.newNodeMap.clear();
379
+ let t = [...this.edgeMapping.values()];
380
+ t = t.concat([...this.newEdgeMap.values()]), this.d3ForceLayout.force("link").links(t);
381
+ for (const s of this.newEdgeMap.entries()) {
382
+ const o = s[0], r = s[1];
383
+ if (!Ct(r))
384
+ throw new Error("Internal error: Edge is not settled as a complete D3 Edge");
385
+ this.edgeMapping.set(o, r);
386
+ }
387
+ this.newEdgeMap.clear(), this.reheat = !1;
388
+ }
389
+ }
390
+ /**
391
+ * Advance the D3 simulation by one tick, and publish where it moved every node to.
392
+ *
393
+ * `LayoutManager` publishes after every step batch as well, so this copy is a duplicate when
394
+ * the element is driving. It is kept because this engine is also driven directly, with no
395
+ * manager, by `test/layout/layout-positions.test.ts` -- which is what pins that a
396
+ * simulation's own coordinates reach the shared array at all. An engine that never publishes
397
+ * is still correct; that is what the layout extension test pins from the other side.
398
+ */
399
+ step() {
400
+ this.refresh(), this.d3ForceLayout.tick(), this.publishPositions();
401
+ }
402
+ /**
403
+ * Copy the simulation's node coordinates into the shared position array.
404
+ *
405
+ * d3 keeps x, y and z as plain numbers on its own node objects, so this reads them in place and
406
+ * allocates nothing -- which is the point of overriding the base, whose default would build one
407
+ * object per node per tick.
408
+ */
409
+ publishPositions() {
410
+ this.refresh();
411
+ for (const [e, t] of this.nodeMapping)
412
+ this.writeNodePosition(e, t.x, t.y, t.z);
413
+ }
414
+ /**
415
+ * Check if the simulation has settled below alpha minimum
416
+ * @returns True if the simulation has settled
417
+ */
418
+ get isSettled() {
419
+ return this.graphNeedsRefresh ? !1 : this.d3ForceLayout.alpha() < this.d3AlphaMin;
420
+ }
421
+ /**
422
+ * Add a node to the D3 simulation
423
+ * @param n - The node to add
424
+ */
425
+ addNode(e) {
426
+ this.newNodeMap.set(e, { id: e.id });
427
+ }
428
+ /**
429
+ * Add an edge to the D3 simulation
430
+ * @param e - The edge to add
431
+ */
432
+ addEdge(e) {
433
+ this.newEdgeMap.set(e, {
434
+ source: e.srcId,
435
+ target: e.dstId
436
+ });
437
+ }
438
+ /**
439
+ * Get all nodes in the simulation
440
+ * @returns Iterable of nodes
441
+ */
442
+ get nodes() {
443
+ return this.nodeMapping.keys();
444
+ }
445
+ /**
446
+ * Get all edges in the simulation
447
+ * @returns Iterable of edges
448
+ */
449
+ get edges() {
450
+ return this.edgeMapping.keys();
451
+ }
452
+ /**
453
+ * Get the current position of a node in the simulation
454
+ * @param n - The node to get position for
455
+ * @returns The node's position coordinates
456
+ */
457
+ getNodePosition(e) {
458
+ const t = this._getMappedNode(e), s = { x: 0, y: 0, z: 0 };
459
+ return this.writeNodePosition(e, t.x, t.y, t.z), this.readNodePosition(e, s) ? s : {
460
+ x: t.x,
461
+ y: t.y,
462
+ z: t.z
463
+ };
464
+ }
465
+ /**
466
+ * Set a node's position in the simulation
467
+ * @param n - The node to set position for
468
+ * @param newPos - The new position coordinates
469
+ */
470
+ setNodePosition(e, t) {
471
+ const s = this._getMappedNode(e);
472
+ s.x = t.x, s.y = t.y, s.z = t.z ?? 0, this.writeNodePosition(e, s.x, s.y, s.z, "placement"), this.reheat = !0;
473
+ }
474
+ /**
475
+ * Get the position of an edge based on its endpoint positions
476
+ * @param e - The edge to get position for
477
+ * @returns The edge's source and destination positions
478
+ */
479
+ getEdgePosition(e) {
480
+ const t = this._getMappedEdge(e);
481
+ return {
482
+ src: {
483
+ x: t.source.x,
484
+ y: t.source.y,
485
+ z: t.source.z
486
+ },
487
+ dst: {
488
+ x: t.target.x,
489
+ y: t.target.y,
490
+ z: t.target.z
491
+ }
492
+ };
493
+ }
494
+ /**
495
+ * Pin a node to its current position
496
+ * @param n - The node to pin
497
+ */
498
+ pin(e) {
499
+ const t = this._getMappedNode(e);
500
+ t.fx = t.x, t.fy = t.y, t.fz = t.z;
501
+ }
502
+ /**
503
+ * Unpin a node to allow it to move freely
504
+ * @param n - The node to unpin
505
+ */
506
+ unpin(e) {
507
+ const t = this._getMappedNode(e);
508
+ t.fx = void 0, t.fy = void 0, t.fz = void 0, this.reheat = !0;
509
+ }
510
+ /**
511
+ * Take a node out of the simulation.
512
+ *
513
+ * The re-push is what makes it real: d3 keeps its own arrays of nodes and links, so deleting
514
+ * the mapping alone would leave the simulation stepping a node the element has disposed --
515
+ * and every link that referenced it, whose `source` and `target` are those very objects.
516
+ * `refresh()` rebuilds both arrays from the mappings, and `reheat` is what makes it run.
517
+ *
518
+ * The incident links go too, and not only because the element's own removal already takes
519
+ * them out first: d3's link force RESOLVES a link's endpoints against the node list on every
520
+ * re-push and throws for an endpoint it cannot find, so one link left behind would take the
521
+ * next tick down rather than merely draw wrong. An engine has to survive a caller that removes
522
+ * a node without removing its edges.
523
+ * @param n - the node leaving the graph
524
+ */
525
+ removeNode(e) {
526
+ for (const t of this.nodeEdges(e))
527
+ this.removeEdge(t);
528
+ this.nodeMapping.delete(e), this.newNodeMap.delete(e), this.reheat = !0;
529
+ }
530
+ /**
531
+ * Every edge this engine holds that touches one node.
532
+ * @param n - the node
533
+ * @returns the incident edges, as a fresh array so the caller may delete while it walks
534
+ */
535
+ nodeEdges(e) {
536
+ const t = [];
537
+ for (const s of [...this.edgeMapping.keys(), ...this.newEdgeMap.keys()])
538
+ (s.srcId === e.id || s.dstId === e.id) && t.push(s);
539
+ return t;
540
+ }
541
+ /**
542
+ * Take an edge out of the simulation. See {@link D3GraphEngine.removeNode}.
543
+ * @param e - the edge leaving the graph
544
+ */
545
+ removeEdge(e) {
546
+ this.edgeMapping.delete(e), this.newEdgeMap.delete(e), this.reheat = !0;
547
+ }
548
+ _getMappedNode(e) {
549
+ this.refresh();
550
+ const t = this.nodeMapping.get(e);
551
+ if (!t)
552
+ throw new Error("Internal error: Node not found in D3GraphEngine");
553
+ return t;
554
+ }
555
+ _getMappedEdge(e) {
556
+ this.refresh();
557
+ const t = this.edgeMapping.get(e);
558
+ if (!t)
559
+ throw new Error("Internal error: Edge not found in D3GraphEngine");
560
+ return t;
561
+ }
562
+ };
563
+ L.type = "d3", L.maxDimensions = 3, L.zodOptionsSchema = Et;
564
+ let J = L;
565
+ const Rt = u({
566
+ dim: {
567
+ schema: i.number().int().min(2).max(3).default(3),
568
+ meta: {
569
+ label: "Dimensions",
570
+ description: "Layout dimensionality (2D or 3D)"
571
+ }
572
+ }
573
+ }), kt = i.strictObject({
574
+ ...v.shape,
575
+ dim: i.number().default(3)
576
+ }), T = class T extends y {
577
+ /**
578
+ * Create a fixed layout engine
579
+ * @param opts - Configuration options including dimensions
580
+ */
581
+ constructor(e = {}) {
582
+ super(e), this.scalingFactor = 1, this.config = kt.parse(e);
583
+ }
584
+ /**
585
+ * Add a node and apply its fixed position immediately
586
+ * @param n - The node to add
587
+ */
588
+ addNode(e) {
589
+ super.addNode(e);
590
+ const s = e.data.position;
591
+ s && e.mesh.position.set(s.x ?? 0, s.y ?? 0, s.z ?? 0);
592
+ }
593
+ /**
594
+ * Read positions from node data and apply them directly
595
+ */
596
+ doLayout() {
597
+ this.stale = !1;
598
+ const e = {};
599
+ for (const t of this._nodes) {
600
+ const o = t.data.position;
601
+ let r;
602
+ o ? r = [o.x ?? 0, o.y ?? 0, o.z ?? 0] : r = [0, 0, 0], e[t.id] = r, t.mesh.position.set(r[0], r[1], r[2]);
603
+ }
604
+ this.positions = e;
605
+ }
606
+ };
607
+ T.type = "fixed", T.maxDimensions = 3, T.zodOptionsSchema = Rt;
608
+ let Z = T;
609
+ const zt = 25e4, Ft = 4;
610
+ function Pt(n, e, t) {
611
+ return n !== null ? n : t > zt ? e * Ft : e;
612
+ }
613
+ function Lt(n, e) {
614
+ if (n == null || typeof n == "string")
615
+ return null;
616
+ const t = e.outDegree();
617
+ return Ge(n, e, (s) => t[s] + 1);
618
+ }
619
+ const Tt = 1e-6;
620
+ function Wt(n, e, t, s = null) {
621
+ let o = 0, r = 0, c = 0, l = 0;
622
+ for (let d = 0; d < e; d += 1) {
623
+ const S = n[3 * d], N = n[3 * d + 1], p = n[3 * d + 2];
624
+ Number.isFinite(S) && Number.isFinite(N) && Number.isFinite(p) && s?.(d) !== !0 && (o += 1, r += S, c += N, l += p);
625
+ }
626
+ if (o === 0)
627
+ return { scale: 1, centre: [0, 0, 0], placed: o };
628
+ r /= o, c /= o, l /= o;
629
+ let a = 0;
630
+ for (let d = 0; d < e; d += 1) {
631
+ if (s?.(d) === !0)
632
+ continue;
633
+ const S = n[3 * d] - r, N = n[3 * d + 1] - c, p = n[3 * d + 2] - l, g = S * S + N * N + p * p;
634
+ Number.isFinite(g) && g > a && (a = g);
635
+ }
636
+ const h = Math.sqrt(a);
637
+ return { scale: h > Tt ? t / h : 1, centre: [r, c, l], placed: o };
638
+ }
639
+ function At(n) {
640
+ return n == null ? [0, 0, 0] : [
641
+ n.length > 0 ? n[0] : 0,
642
+ n.length > 1 ? n[1] : 0,
643
+ n.length > 2 ? n[2] : 0
644
+ ];
645
+ }
646
+ const _t = {
647
+ forceatlas2: "forceAtlas2",
648
+ fruchtermanReingold: "fruchtermanReingold",
649
+ spring: "fruchtermanReingold",
650
+ "spring-electrical": "springElectrical"
651
+ };
652
+ function Bt() {
653
+ return new pe({
654
+ code: "E_INTERNAL",
655
+ message: "a simulation layout is constructed by LayoutManager with the graph's acceleration controller, not by LayoutEngine.get: this one was built without one",
656
+ source: "layout"
657
+ });
658
+ }
659
+ class be extends fe {
660
+ #I;
661
+ #N = me;
662
+ #t;
663
+ #h;
664
+ #C;
665
+ #n;
666
+ /**
667
+ * One reused pair, valid until the next call. `Edge.update()` reads it and drops it, and a
668
+ * fresh pair per edge per frame is the allocation this avoids.
669
+ */
670
+ #D = { src: { x: 0, y: 0, z: 0 }, dst: { x: 0, y: 0, z: 0 } };
671
+ #e = null;
672
+ /**
673
+ * What the controller had ATTACHED when the running simulation was built, which is not the
674
+ * same as what that simulation runs on: below `acceleration.minNodes` the plan answers CPU
675
+ * with an accelerator attached. Recording the attached one is what makes a transition that
676
+ * would plan exactly the same way answer "unchanged", so a settled CPU layout is not rebuilt,
677
+ * re-uploaded and reheated by every status change the controller publishes.
678
+ */
679
+ #w = null;
680
+ #u = !1;
681
+ #o = null;
682
+ #p = null;
683
+ #g = null;
684
+ /**
685
+ * The array the simulation actually runs in, in the simulation's own units.
686
+ *
687
+ * The bridge's, not the store's: see the file header. It is rebuilt from the element's array
688
+ * at every load and every reload -- a freeze renumbers the rows -- and DELIBERATELY kept
689
+ * across an accelerator swap, which is what makes the swap continue the layout rather than
690
+ * restart it.
691
+ */
692
+ #l = null;
693
+ /** What a simulation-unit offset from {@link SimulationLayoutEngine.#simCentre} is multiplied by. */
694
+ #f = 1;
695
+ /** The arrangement's centre at the last publish, in the simulation's own units. */
696
+ #i = [0, 0, 0];
697
+ /** Where the element expects the arrangement's centre to be, in scene units. */
698
+ #s;
699
+ /**
700
+ * The rows the simulation is holding still, which are the rows the ELEMENT'S array is the
701
+ * authority for. Rebuilt with the mask in `#applyPins` and edited with it in `#setFixed`.
702
+ */
703
+ #c = /* @__PURE__ */ new Set();
704
+ // THE PIN LANE HAS NO ROW FOR A DRAG. `#applyPins` repacks the mask from the store's lane
705
+ // after every load, so a freeze or an accelerator swap mid-drag would give the pointer's node
706
+ // back to the forces; these nodes are OR-ed back in. They are held as nodes, not as indices,
707
+ // because a freeze renumbers the rows underneath them.
708
+ #O = /* @__PURE__ */ new Set();
709
+ /**
710
+ * The node masses this simulation was built with, when the reader gave them as a record keyed
711
+ * by node id, and null for every other form. See {@link SimulationLayoutEngine.resolvedNodeMass}.
712
+ */
713
+ #d = null;
714
+ /**
715
+ * Every batch promise this bridge has already attached a handler to.
716
+ *
717
+ * A saturated simulation answers `step()` with the OLDEST promise still in flight rather than
718
+ * submitting another, and with more than one batch allowed in flight that promise comes back
719
+ * AFTER newer ones -- so "the same promise as last time" is not enough to keep the rule of one
720
+ * handler per distinct promise. Weak, so a retired batch is collectable.
721
+ */
722
+ #R = /* @__PURE__ */ new WeakSet();
723
+ #r = !1;
724
+ /** The simulation whose failure has already been reported; see `#onError`. */
725
+ #k = null;
726
+ /**
727
+ * Ends the controller's work span while an accelerated simulation is being stepped, or null
728
+ * when no span is open. See `#syncWork`.
729
+ */
730
+ #b = null;
731
+ /** Batches submitted and not yet landed or rejected. See `#syncWork`. */
732
+ #y = 0;
733
+ /** Set by `LayoutManager` while it is not stepping this layout. See {@link paused}. */
734
+ #M = !1;
735
+ #m = 1;
736
+ #v = 0;
737
+ /**
738
+ * Builds a bridge. Nothing is planned and no simulation exists until {@link init} loads one.
739
+ *
740
+ * The parameter is declared as a bare `object` because `LayoutEngine.register` files a class
741
+ * whose constructor takes the options a consumer passed to `setLayout`, and a bridge takes
742
+ * more than that. A caller that means to build one declares its argument as a
743
+ * {@link SimulationEngineInit}, which is what `LayoutManager` does; a caller that arrived
744
+ * through `LayoutEngine.get` is refused on the next line.
745
+ * @param init - A {@link SimulationEngineInit}: the type, the resolved options, the
746
+ * controller, the error channel and the data.
747
+ * @throws A `GraphtyError` with `E_INTERNAL` when no controller was passed.
748
+ */
749
+ constructor(e) {
750
+ super();
751
+ const t = e;
752
+ if (t.controller === void 0 || t.type === void 0)
753
+ throw Bt();
754
+ this.simulationType = t.type, this.#I = t.layoutType ?? t.type, this.capability = _t[t.type], this.#t = t.options, this.#h = t.controller, this.#C = t.report, this.#n = t.dataManager, this.#s = At(this.#t.model.center ?? this.#t.center), this.#i[0] = this.#s[0], this.#i[1] = this.#s[1], this.#i[2] = this.#s[2];
755
+ }
756
+ /**
757
+ * The registered layout name, which every other engine reads off its class's `static type`.
758
+ *
759
+ * Without this the manager's `layoutType`, `getStats().layoutType` and the details of every
760
+ * error this bridge reports would all be undefined, and a 2D/3D switch would ask the element
761
+ * to set a layout called "undefined".
762
+ * @returns The name the consumer set.
763
+ */
764
+ get type() {
765
+ return this.#I;
766
+ }
767
+ /**
768
+ * The running simulation, or null before the first load and while the bridge is stopped.
769
+ *
770
+ * Exposed so a caller that has to wait for the device -- a screenshot after a pause -- can
771
+ * reach an accelerated simulation's own `flush()`.
772
+ * @returns The simulation.
773
+ */
774
+ get simulation() {
775
+ return this.#e;
776
+ }
777
+ /**
778
+ * Whether the running simulation is the accelerator's rather than the CPU's.
779
+ * @returns True when the accelerator built it.
780
+ */
781
+ get isAccelerated() {
782
+ return this.#u;
783
+ }
784
+ /**
785
+ * The arithmetic the running simulation computes in, for a caller that labels its results.
786
+ * @returns The precision, which is the CPU's whenever the CPU is running the layout.
787
+ */
788
+ get precision() {
789
+ return this.#N;
790
+ }
791
+ /**
792
+ * How many iterations this bridge has submitted since the simulation was built. Submitted,
793
+ * not retired: an accelerated batch is counted when it goes out, not when it lands.
794
+ * @returns The count.
795
+ */
796
+ get iterationsDone() {
797
+ return this.#v;
798
+ }
799
+ /**
800
+ * The node masses the running simulation was handed, when the element resolved them itself.
801
+ *
802
+ * A reader may give `nodeMass` as a number per node ID, as the name of a numeric node column,
803
+ * or not at all. Only the FIRST of those three is resolved here, into one value per dense row,
804
+ * because it is the only one an accelerator refuses: a GPU never sees a node ID. The other two
805
+ * are handed to the simulation as they stand and resolved by it at every load, on either path,
806
+ * which is also what keeps them right when a freeze renumbers the graph.
807
+ * @returns The masses, or null when the simulation resolves them itself.
808
+ */
809
+ get resolvedNodeMass() {
810
+ return this.#d === null ? null : this.#d.slice();
811
+ }
812
+ /**
813
+ * The fixed-node mask the simulation is running with, packed from the store's pin lane.
814
+ * @returns The mask, or an empty one before the first load.
815
+ */
816
+ get pinnedMask() {
817
+ return this.#g === null ? xe(0) : this.#g.slice();
818
+ }
819
+ /**
820
+ * Whether a different accelerator is attached now than when the simulation was built.
821
+ *
822
+ * What `LayoutManager` asks on every controller transition before it swaps: `null` is nothing
823
+ * attached, so a detach, an injection, a device loss and a recovery all answer true, while a
824
+ * transition that leaves the same accelerator attached answers false -- including for a
825
+ * simulation the plan put on the CPU because the graph is below `acceleration.minNodes`.
826
+ * Rebuilding that one on every status change would re-upload the graph and reheat a layout
827
+ * that had settled, and it is a RELOAD that re-plans the threshold, which it does on its own.
828
+ * @param accelerator - What the controller holds now.
829
+ * @returns True when the simulation has to be rebuilt.
830
+ */
831
+ builtWithChanged(e) {
832
+ return e !== this.#w;
833
+ }
834
+ /**
835
+ * The FIRST load, which is what makes this engine's `init()` different from every other's.
836
+ *
837
+ * `LayoutManager._setLayoutInternal` replays the pinned nodes between `init()` and
838
+ * `setLayoutEngine`, through `setNodePosition` and `pin`, so a load deferred to any later
839
+ * point would meet those calls with no simulation to tell.
840
+ * @returns A promise that resolves once the simulation exists and holds the graph.
841
+ */
842
+ init() {
843
+ const e = this.#n.getSnapshot();
844
+ return this.load(e, this.#n.positions.view(e.nodeCount)), Promise.resolve();
845
+ }
846
+ /**
847
+ * Adopts a snapshot and builds the simulation the controller's plan chose for it.
848
+ * @param snapshot - The graph, as `getSnapshot()` returns it; the bridge derives the
849
+ * undirected copy the simulations require itself.
850
+ * @param positions - The element's stride-3 array, written in place by every batch.
851
+ * @throws A `GraphtyError` with `E_NO_ACCELERATOR` under the `required` policy with nothing
852
+ * attached, or whatever `createSimulation` throws.
853
+ */
854
+ load(e, t) {
855
+ this.#e?.dispose(), this.#e = null, this.#a(), this.#o = e, this.#p = t, this.#E(e.nodeCount, t), this.#P();
856
+ }
857
+ /**
858
+ * Hands the running simulation a new snapshot, rebuilding it first when the plan flipped.
859
+ *
860
+ * The re-plan is the point: a freeze can carry the graph across `acceleration.minNodes` in
861
+ * either direction, and design 9.4 item 4 re-evaluates the threshold at every load.
862
+ * @param snapshot - The snapshot that has just replaced the old one.
863
+ * @param positions - The element's array, re-viewed at the new node count.
864
+ * @throws A `GraphtyError` with `E_NO_ACCELERATOR` under the `required` policy with nothing
865
+ * attached.
866
+ */
867
+ reload(e, t) {
868
+ if (this.#o === null)
869
+ return;
870
+ const s = this.#e;
871
+ this.#o = e, this.#p = t;
872
+ const o = this.#E(e.nodeCount, t), r = this.#h.plan({ capability: this.capability, nodeCount: e.nodeCount }), c = this.#d !== null;
873
+ if (s === null || r.accelerated !== this.#u || c) {
874
+ this.replaceSimulation();
875
+ return;
876
+ }
877
+ this.#m = this.#T(e.nodeCount), this.#L(e, o), s.load(this.#n.undirected(e).snapshot, o), this.#F();
878
+ }
879
+ /**
880
+ * Rebuilds the simulation on whatever the controller holds now, keeping the arrangement.
881
+ *
882
+ * The old simulation is disposed and the new one is loaded with the SAME positions view and
883
+ * the same pins, so an accelerator arriving mid-run continues the layout rather than
884
+ * restarting it, and one leaving hands it to the CPU where the policy allows a CPU.
885
+ * @throws A `GraphtyError` with `E_NO_ACCELERATOR` when the policy is `"required"` and
886
+ * nothing is attached. The bridge is then STOPPED -- no simulation, nothing submitted -- and
887
+ * the next transition that attaches an accelerator brings it back through this same method.
888
+ */
889
+ replaceSimulation() {
890
+ if (this.#e?.dispose(), this.#e = null, this.#a(), this.#o === null || this.#p === null) {
891
+ this.#r = !1;
892
+ return;
893
+ }
894
+ try {
895
+ this.#P();
896
+ } catch (e) {
897
+ throw this.#r = !0, e;
898
+ }
899
+ }
900
+ /**
901
+ * Submits one batch, fire and forget.
902
+ *
903
+ * The abstract signature returns nothing, and a GPU simulation's `step()` returns a promise,
904
+ * so the rejection is caught here instead of by a caller. A saturated simulation returns the
905
+ * OLDEST promise already in flight rather than submitting another, which is how it keeps the
906
+ * frame loop from queueing work faster than the device retires it -- so the handlers are
907
+ * attached only when the promise is one this bridge has not seen. They are what closes the
908
+ * controller's work span: a batch landing on a settled simulation is where a GPU layout
909
+ * stops being work on the device.
910
+ */
911
+ step() {
912
+ const e = this.#e;
913
+ if (e === null || this.#r)
914
+ return;
915
+ this.#v += this.#m;
916
+ const t = e.step(this.#m);
917
+ this.#a(), t !== void 0 && !this.#R.has(t) && (this.#R.add(t), this.#y += 1, t.then(
918
+ () => {
919
+ this.#y -= 1, this.#a();
920
+ },
921
+ (s) => {
922
+ this.#y -= 1, this.#_(s, e);
923
+ }
924
+ ));
925
+ }
926
+ /**
927
+ * Submits one batch and waits for it, which the pre-step loop and the tests need.
928
+ * @param iterations - Iterations this batch computes.
929
+ * @returns A promise that resolves when the batch has landed, or rejects with its failure.
930
+ */
931
+ async stepAsync(e) {
932
+ if (!(this.#e === null || this.#r)) {
933
+ this.#v += e, this.#a();
934
+ try {
935
+ await this.#e.step(e);
936
+ } finally {
937
+ this.#a();
938
+ }
939
+ }
940
+ }
941
+ /**
942
+ * Whether the manager has stopped stepping this layout -- a consumer's pause, or any other
943
+ * stop. A paused layout's work span closes once its in-flight batches land, because nothing
944
+ * is being submitted after them.
945
+ * @returns True while the layout is not being stepped.
946
+ */
947
+ get paused() {
948
+ return this.#M;
949
+ }
950
+ /**
951
+ * Tells the bridge whether the manager is stepping it.
952
+ * @param value - True when the manager has stopped stepping this layout.
953
+ */
954
+ set paused(e) {
955
+ this.#M = e, e && this.#a();
956
+ }
957
+ /**
958
+ * Starts the settle count again, on a simulation that has one.
959
+ *
960
+ * `reheat()` is not on the `LayoutSimulation` interface -- both CPU simulations and the GPU
961
+ * ones have it, and a third party's may not -- so it is feature-tested rather than assumed.
962
+ */
963
+ reheat() {
964
+ const e = this.#e;
965
+ typeof e?.reheat == "function" && e.reheat();
966
+ }
967
+ /**
968
+ * Holds a node still for the duration of a drag, without pinning it.
969
+ *
970
+ * The fixed bit is the same one a pin uses, because it is the only thing that stops a
971
+ * simulation writing the row; what makes this temporary is that {@link endDrag} clears it
972
+ * again unless the drag is about to become a pin.
973
+ * @param n - The node the pointer has taken.
974
+ */
975
+ beginDrag(e) {
976
+ this.#O.add(e), this.#x(e.index, !0);
977
+ }
978
+ /**
979
+ * Gives the node back to the simulation, or leaves it fixed because it is being pinned.
980
+ * A node that was ALREADY PINNED before the pointer took it keeps its bit whatever `pin`
981
+ * says: the store's pin stops the row being published over, but only the fixed bit stops the
982
+ * simulation's own copy of it drifting away from where the reader put it, which the next
983
+ * refit would then have to drag back. Clearing it would leave a node the store still calls
984
+ * pinned being pushed around inside the simulation every frame.
985
+ * @param n - The node the pointer has let go of.
986
+ * @param pin - True when the drag ends in a pin, so the bit stays set.
987
+ */
988
+ endDrag(e, t) {
989
+ this.#O.delete(e), this.#x(e.index, t || e.isPinned());
990
+ }
991
+ /**
992
+ * Fixes a pinned node in the simulation. The store already holds the pin.
993
+ * @param n - The node that was pinned.
994
+ */
995
+ pin(e) {
996
+ this.#x(e.index, !0);
997
+ }
998
+ /**
999
+ * Releases a node the simulation was holding fixed.
1000
+ * @param n - The node that was unpinned.
1001
+ */
1002
+ unpin(e) {
1003
+ this.#x(e.index, !1);
1004
+ }
1005
+ /**
1006
+ * Places one node now. A drag reaches this per pointer move, and `replayPins` once per pin.
1007
+ * @param n - The node that moved.
1008
+ * @param p - Where it moved to.
1009
+ */
1010
+ setNodePosition(e, t) {
1011
+ const s = t.z ?? 0;
1012
+ if (!this.#S(e.index) || !Number.isFinite(t.x) || !Number.isFinite(t.y) || !Number.isFinite(s))
1013
+ return;
1014
+ this.writeNodePosition(e, t.x, t.y, s, "placement");
1015
+ const o = this.#f;
1016
+ this.#e?.setPosition(
1017
+ e.index,
1018
+ (t.x - this.#s[0]) / o + this.#i[0],
1019
+ (t.y - this.#s[1]) / o + this.#i[1],
1020
+ (s - this.#s[2]) / o + this.#i[2]
1021
+ );
1022
+ }
1023
+ /**
1024
+ * Reads a node's current coordinates, in scene units, out of the element's own position array.
1025
+ * @param n - The node to read.
1026
+ * @returns Its scene-unit position; the origin for a row nothing has placed.
1027
+ */
1028
+ getNodePosition(e) {
1029
+ const t = { x: 0, y: 0, z: 0 };
1030
+ return this.readNodePosition(e, t), t;
1031
+ }
1032
+ /**
1033
+ * Reads both endpoints of an edge into the one pair this engine reuses.
1034
+ * @param e - The edge to read.
1035
+ * @returns The pair, valid until the next call; the origin for an endpoint nothing has placed.
1036
+ */
1037
+ getEdgePosition(e) {
1038
+ return this.#z(e.srcNode, this.#D.src), this.#z(e.dstNode, this.#D.dst), this.#D;
1039
+ }
1040
+ /**
1041
+ * Reads one endpoint into the reused pair, or zeroes that half of it.
1042
+ *
1043
+ * `readNodePosition` leaves `out` UNTOUCHED for a row nothing has placed, which is right for a
1044
+ * caller holding one vector per node and wrong for one pair every edge reuses: the unplaced
1045
+ * endpoint would be drawn at the PREVIOUS edge's coordinates. The origin is what
1046
+ * `getNodePosition` answers for the same row.
1047
+ * @param n - The endpoint to read.
1048
+ * @param out - The half of the pair it goes in.
1049
+ * @param out.x - receives the scene-unit x.
1050
+ * @param out.y - receives the scene-unit y.
1051
+ * @param out.z - receives the scene-unit z.
1052
+ */
1053
+ #z(e, t) {
1054
+ this.readNodePosition(e, t) || (t.x = 0, t.y = 0, t.z = 0);
1055
+ }
1056
+ /**
1057
+ * Nothing: the snapshot IS the graph, and the simulation reads it rather than a node list.
1058
+ */
1059
+ addNode() {
1060
+ }
1061
+ /**
1062
+ * Nothing, for the same reason as {@link addNode}.
1063
+ */
1064
+ addEdge() {
1065
+ }
1066
+ /**
1067
+ * Every node of the graph, which is the list the FRAME LOOP walks.
1068
+ *
1069
+ * The bridge keeps no list of its own -- the snapshot is the graph -- but these two getters
1070
+ * are not bookkeeping: `UpdateManager` moves a mesh only for a node this one yields, draws an
1071
+ * edge only for an edge the next one yields, and frames the camera on their count. An empty
1072
+ * iterable would let the simulation rewrite the position array every frame while not one mesh
1073
+ * moved. So they answer with the data manager's own collections, which hold exactly what
1074
+ * `LayoutManager` hands every other engine through `addNodes` / `addEdges`.
1075
+ * @returns The graph's live node collection.
1076
+ */
1077
+ get nodes() {
1078
+ return this.#n.nodes.values();
1079
+ }
1080
+ /**
1081
+ * Every edge of the graph; see {@link SimulationLayoutEngine.nodes}.
1082
+ * @returns The graph's live edge collection.
1083
+ */
1084
+ get edges() {
1085
+ return this.#n.edges.values();
1086
+ }
1087
+ /**
1088
+ * Whether the simulation has stopped moving.
1089
+ *
1090
+ * A bridge with no simulation -- before the first load, or stopped after a swap the policy
1091
+ * refused -- reads settled, because nothing is going to move.
1092
+ * @returns True when the last completed batch settled.
1093
+ */
1094
+ get isSettled() {
1095
+ return this.#e?.settled ?? !0;
1096
+ }
1097
+ /**
1098
+ * Maps the simulation's arrangement into the element's scene units, and writes it.
1099
+ *
1100
+ * This is the seam the file header describes. The mapping is remeasured here rather than
1101
+ * fixed once, because a ForceAtlas2 graph expands for hundreds of iterations before it
1102
+ * settles: a scale taken at the first step would let the arrangement grow out of the frame,
1103
+ * and one taken at the last would not exist until the reader had already watched it happen.
1104
+ * Remeasured, a settling graph holds its apparent size and only its shape changes.
1105
+ *
1106
+ * A FIXED ROW IS NEITHER PUBLISHED NOR MEASURED. Its scene-unit coordinate is the reader's --
1107
+ * the pointer's, or where a pin left it -- and `LayoutEngine.writeNodePosition` refuses a
1108
+ * layout-intent write to a pinned row for every engine in this element, so the skip is that
1109
+ * same rule reaching a row a drag is holding as well. It is left out of the measurement for
1110
+ * the same reason it is left out of the write: a radius taken over a row that is not drawn
1111
+ * there would resize every row that is. The two sides start in step, because
1112
+ * every path that fixes a row leaves them in step: a drag writes both through
1113
+ * {@link SimulationLayoutEngine.setNodePosition}, and a pin, a freeze and a rebuild all fix a
1114
+ * row whose simulation copy this mapping had just published from.
1115
+ *
1116
+ * THEY DRIFT APART AFTERWARDS, and deliberately. A refit rescales the arrangement around a
1117
+ * held row without moving the row, so the longer the arrangement keeps growing the further
1118
+ * the simulation's copy of that row sits from where it is drawn. The obvious repair -- write
1119
+ * the row back into the simulation whenever the mapping moves -- was tried and is worse than
1120
+ * the problem: a simulation treats a written position as a disturbance and reheats, so an
1121
+ * accelerated ForceAtlas2 with one pinned node cleared its settle window on every frame and
1122
+ * ran the full sixty-second test budget without once reporting itself at rest. Worse, the
1123
+ * write changes the arrangement, which changes the refit, which asks for another write. A pin
1124
+ * is a scene-unit promise and it is kept; how far the arrangement moves under it is not part
1125
+ * of that promise.
1126
+ *
1127
+ * WHAT A GRAPH OF NOTHING BUT HELD ROWS COSTS. Fitting only the published rows means that the
1128
+ * fewer of them there are the less there is to fit, and two free rows a hair apart are spread
1129
+ * to the full radius exactly as a two-node graph would be. That is this rule meeting a reader
1130
+ * who has pinned almost everything and taken the size out of the layout's hands; it is bounded
1131
+ * -- an arrangement that measures as a point publishes at its own size -- and a graph with no
1132
+ * free row left keeps the last map rather than falling back to the identity.
1133
+ */
1134
+ publishPositions() {
1135
+ const e = this.#o, t = this.#l;
1136
+ if (e === null || t === null)
1137
+ return;
1138
+ const s = this.#n.positions, o = this.#c.size === 0 ? null : (a) => this.#c.has(a) && s.isPlaced(a), r = Wt(t, e.nodeCount, this.#t.scalingFactor, o);
1139
+ r.placed > 0 && (this.#f = r.scale, this.#i[0] = r.centre[0], this.#i[1] = r.centre[1], this.#i[2] = r.centre[2]);
1140
+ const c = this.#f, l = this.#i;
1141
+ for (const a of this.nodes) {
1142
+ const h = a.index;
1143
+ if (!this.#S(h))
1144
+ continue;
1145
+ const d = s.isPlaced(h);
1146
+ d && this.#c.has(h) || this.writeNodePosition(
1147
+ a,
1148
+ (t[3 * h] - l[0]) * c + this.#s[0],
1149
+ (t[3 * h + 1] - l[1]) * c + this.#s[1],
1150
+ (t[3 * h + 2] - l[2]) * c + this.#s[2],
1151
+ d ? "layout" : "placement"
1152
+ );
1153
+ }
1154
+ }
1155
+ /**
1156
+ * Releases the simulation. The accelerator's residency for the snapshot is NOT released here:
1157
+ * it is per snapshot and shared with the algorithm runs, and `Graph` frees it at the next
1158
+ * freeze and at shutdown.
1159
+ */
1160
+ dispose() {
1161
+ this.#e?.dispose(), this.#e = null, this.#a();
1162
+ }
1163
+ /**
1164
+ * Tells the controller whether the accelerator is being stepped right now.
1165
+ *
1166
+ * The controller counts the work it runs itself, and a simulation is none of it: the bridge
1167
+ * submits a batch per frame until the arrangement settles, so without this the state a
1168
+ * consumer reads would stay `"idle"` -- attached, nothing using it -- for the whole of a GPU
1169
+ * layout, and a status chip would say the device was asleep while it was busy.
1170
+ *
1171
+ * The span is one settle, not one batch: opening and closing it per batch would publish a
1172
+ * transition twice a frame, to every `onChange` listener and out to the DOM. It opens when an
1173
+ * accelerated batch is submitted and closes when one lands on a settled simulation, when the
1174
+ * simulation is replaced or disposed, and when a batch fails. A layout PAUSED mid-settle
1175
+ * keeps its span only until the batches it had in flight have landed: after them nothing is
1176
+ * submitted, so the device is idle until the layout resumes.
1177
+ */
1178
+ #a() {
1179
+ const e = this.#u && this.#e !== null && !this.#r && !this.isSettled && !(this.#M && this.#y === 0);
1180
+ if (e !== (this.#b !== null)) {
1181
+ if (e) {
1182
+ this.#b = this.#h.beginWork();
1183
+ return;
1184
+ }
1185
+ this.#b?.(), this.#b = null;
1186
+ }
1187
+ }
1188
+ /**
1189
+ * Packs the store's pin lane into the simulation's mask.
1190
+ *
1191
+ * A PACK, not a copy: the lane is one byte per node and the mask is one bit. It runs after
1192
+ * every load, because a freeze renumbers the rows and the lane is remapped with them. The
1193
+ * nodes a pointer is holding are OR-ed in afterwards: a drag is not a pin and has no row in
1194
+ * the lane, but it holds the node just as still.
1195
+ */
1196
+ #F() {
1197
+ const e = this.#o, t = this.#e;
1198
+ if (e === null || t === null)
1199
+ return;
1200
+ const { nodeCount: s } = e, o = this.#n.positions.pinnedView(s), r = xe(s), c = /* @__PURE__ */ new Set();
1201
+ for (let l = 0; l < s; l += 1)
1202
+ o[l] === 1 && (ue(r, l, !0), c.add(l));
1203
+ for (const l of this.#O)
1204
+ this.#S(l.index) && (ue(r, l.index, !0), c.add(l.index));
1205
+ this.#g = r, this.#c = c, t.setFixed(r);
1206
+ }
1207
+ /**
1208
+ * Plans, builds and loads a simulation over the snapshot this bridge is holding.
1209
+ * @throws Whatever `plan()` or `createSimulation` throws.
1210
+ */
1211
+ #P() {
1212
+ const e = this.#o, t = this.#p;
1213
+ if (e === null || t === null)
1214
+ return;
1215
+ const s = this.#l, o = s !== null && s.length === 3 * e.nodeCount ? s : this.#E(e.nodeCount, t);
1216
+ this.#w = null, this.#u = !1, this.#N = me;
1217
+ const r = this.#h.plan({ capability: this.capability, nodeCount: e.nodeCount }), c = r.accelerated ? r.accelerator : null;
1218
+ if (!r.accelerated && this.simulationType === "spring-electrical")
1219
+ throw new pe({
1220
+ code: "E_NO_ACCELERATOR",
1221
+ message: `the layout "${this.type}" is computed on a hardware accelerator and has no processor implementation: ${r.reason}`,
1222
+ source: "layout",
1223
+ details: {
1224
+ layout: this.type,
1225
+ reason: r.reason
1226
+ }
1227
+ });
1228
+ const l = this.#n.undirected(e).snapshot;
1229
+ this.#L(e, o), this.#d = this.#W(l), this.#m = this.#T(e.nodeCount), this.#e = _e(
1230
+ this.simulationType,
1231
+ this.#A(),
1232
+ c === null ? null : at(c)
1233
+ ), this.#w = this.#h.accelerator, this.#u = r.accelerated, this.#N = r.accelerated ? r.precision : me, this.#r = !1, this.#v = 0, this.#e.load(l, o), this.#F();
1234
+ }
1235
+ /**
1236
+ * Turns a reader's `nodeMass` record into one value per dense row, and leaves every other form
1237
+ * alone. See {@link SimulationLayoutEngine.resolvedNodeMass}.
1238
+ * @param undirected - The graph the simulation will be loaded with.
1239
+ * @returns The masses, or null when the simulation resolves them itself.
1240
+ */
1241
+ #W(e) {
1242
+ return Lt(this.#t.model.nodeMass, e);
1243
+ }
1244
+ /**
1245
+ * Gives a coordinate to every row that has none, before the simulation reads them.
1246
+ *
1247
+ * A row nothing has placed reads as NaN, which is the element's "not laid out yet" -- and a
1248
+ * force is an arithmetic operation, so a NaN row stays NaN through every iteration and its
1249
+ * node never appears. The CPU simulations say so outright ("the caller seeds the array with
1250
+ * seedPositions"), so the caller is this. Finite rows are never touched, which is what makes
1251
+ * this safe to run at every load: an existing arrangement survives and only the rows a freeze
1252
+ * added are placed, inside the box the rest of the graph already occupies. The array is the
1253
+ * SIMULATION'S, which `#adoptPositions` has just filled from the element's.
1254
+ * @param snapshot - The graph being loaded.
1255
+ * @param positions - The simulation's array, exactly `3 * nodeCount` long.
1256
+ */
1257
+ #L(e, t) {
1258
+ const { seed: s, dim: o, scale: r, center: c } = this.#t.model;
1259
+ Be(
1260
+ e,
1261
+ t,
1262
+ s ?? null,
1263
+ o ?? this.#t.dim ?? 3,
1264
+ r ?? this.#t.scale ?? 1,
1265
+ c ?? this.#t.center ?? null,
1266
+ this.simulationType === "forceatlas2" ? "fa2" : "fr"
1267
+ );
1268
+ }
1269
+ /**
1270
+ * Rebuilds the simulation's array from the element's, in the simulation's own units.
1271
+ *
1272
+ * The element's array is the one a freeze renumbers, a drag writes and a file's coordinates
1273
+ * arrive in, so it -- not the simulation's -- is what an arrangement is carried across a load
1274
+ * in. Each row travels through the inverse of the mapping the last publish used, which is the
1275
+ * identity before anything has been published; an unplaced row is NaN in both arrays, and
1276
+ * `#seededSimPositions` is what gives it a coordinate.
1277
+ * @param nodeCount - The graph being loaded.
1278
+ * @param positions - The element's array, exactly `3 * nodeCount` long.
1279
+ * @returns The simulation's array, holding the element's arrangement in simulation units.
1280
+ */
1281
+ #E(e, t) {
1282
+ const s = 3 * e, o = this.#l !== null && this.#l.length === s ? this.#l : new Float32Array(s), r = this.#f;
1283
+ for (let c = 0; c < e; c += 1)
1284
+ for (let l = 0; l < 3; l += 1)
1285
+ o[3 * c + l] = (t[3 * c + l] - this.#s[l]) / r + this.#i[l];
1286
+ return this.#l = o, o;
1287
+ }
1288
+ /**
1289
+ * The options handed to `createSimulation`: the common knobs, then the type's own, then the
1290
+ * iteration count this load resolved.
1291
+ * @returns The options object.
1292
+ */
1293
+ #A() {
1294
+ const { model: e, iterationsPerStep: t, stepMultiplier: s, ...o } = this.#t, r = this.#d === null ? {} : { nodeMass: this.#d };
1295
+ return { ...o, ...e, ...r, iterationsPerStep: this.#m };
1296
+ }
1297
+ /**
1298
+ * How many iterations one `step()` submits over the graph this load holds.
1299
+ * @param nodeCount - The graph this load is over.
1300
+ * @returns The iteration count.
1301
+ */
1302
+ #T(e) {
1303
+ const { iterationsPerStep: t, stepMultiplier: s } = this.#t;
1304
+ return Pt(t, s, e);
1305
+ }
1306
+ /**
1307
+ * Sets or clears one bit of the fixed mask and tells the simulation.
1308
+ *
1309
+ * A no-op before the first load and while the bridge is stopped: there is no simulation to
1310
+ * tell, and the next load packs the whole lane anyway.
1311
+ * @param index - The node's dense row.
1312
+ * @param fixed - Whether the simulation must hold it still.
1313
+ */
1314
+ #x(e, t) {
1315
+ const s = this.#g, o = this.#e;
1316
+ s === null || o === null || !this.#S(e) || (ue(s, e, t), t ? this.#c.add(e) : this.#c.delete(e), o.setFixed(s));
1317
+ }
1318
+ /**
1319
+ * Whether the loaded snapshot has a row at this index.
1320
+ *
1321
+ * The element's "this node has no row yet" is `INVALID_INDEX`, which is `0xffffffff` and so
1322
+ * passes any `index < 0` test; the simulations answer an index they have no row for with a
1323
+ * `RangeError`.
1324
+ * @param index - The node's dense row, or the sentinel.
1325
+ * @returns True when the index addresses a row of the loaded graph.
1326
+ */
1327
+ #S(e) {
1328
+ return Number.isInteger(e) && e >= 0 && e < (this.#o?.nodeCount ?? 0);
1329
+ }
1330
+ /**
1331
+ * Reports a rejected batch and stops stepping the simulation it came from.
1332
+ *
1333
+ * ONCE PER SIMULATION: a lost device rejects every batch it had outstanding -- two by default
1334
+ * -- and the consumer is told about that device once.
1335
+ *
1336
+ * The stop flag belongs to the simulation the batch was SUBMITTED on, which is not always the
1337
+ * one running when it lands: the device loss that rejected it also transitions the controller,
1338
+ * and that transition can reach `LayoutManager` and swap the simulation while the rest of the
1339
+ * batches are still in flight. Setting the flag on whatever is current would stop the
1340
+ * simulation that just RECOVERED the layout, which would then submit nothing for the rest of
1341
+ * its life. Dropping a replaced simulation's rejection is the other wrong answer: which of
1342
+ * `device.lost` and an outstanding readback settles first is not ordered, so a report that
1343
+ * skipped it would arrive or not depending on that race. It is reported either way, and only
1344
+ * the simulation that failed is stopped.
1345
+ *
1346
+ * What happens after a live simulation's failure is the controller's: its own device-loss
1347
+ * watch transitions, and that transition reaches `LayoutManager`, which rebuilds the
1348
+ * simulation on whatever the policy now allows. The running layout reaches the CPU only AFTER
1349
+ * the failure was reported, and only under a policy that permits a CPU.
1350
+ * @param error - The rejection.
1351
+ * @param from - The simulation the batch was submitted on.
1352
+ */
1353
+ #_(e, t) {
1354
+ this.#k !== t && (this.#k = t, t === this.#e && (this.#r = !0, this.#a()), this.#C(
1355
+ pe.wrap(e, {
1356
+ code: "E_INTERNAL",
1357
+ source: "layout",
1358
+ details: { layoutType: this.type, simulation: this.simulationType }
1359
+ })
1360
+ ));
1361
+ }
1362
+ }
1363
+ const Gt = u({
1364
+ scalingFactor: {
1365
+ schema: i.number().min(1).max(1e3).default(100),
1366
+ meta: {
1367
+ label: "Scaling Factor",
1368
+ description: "Multiplier for node positions"
1369
+ }
1370
+ },
1371
+ maxIter: {
1372
+ schema: i.number().int().positive().default(100),
1373
+ meta: {
1374
+ label: "Max Iterations",
1375
+ description: "Maximum number of simulation iterations"
1376
+ }
1377
+ },
1378
+ jitterTolerance: {
1379
+ schema: i.number().positive().default(1),
1380
+ meta: {
1381
+ label: "Jitter Tolerance",
1382
+ description: "Tolerance for position jitter",
1383
+ step: 0.1,
1384
+ advanced: !0
1385
+ }
1386
+ },
1387
+ scalingRatio: {
1388
+ schema: i.number().positive().default(2),
1389
+ meta: {
1390
+ label: "Scaling Ratio",
1391
+ description: "Ratio for force scaling",
1392
+ step: 0.1
1393
+ }
1394
+ },
1395
+ gravity: {
1396
+ // NONNEGATIVE, not positive: zero gravity is a legal ForceAtlas2 setting -- nothing pulls
1397
+ // the graph towards the centre and the components drift apart -- and it is what the
1398
+ // Storybook slider has always offered as its lowest value.
1399
+ schema: i.number().nonnegative().default(1),
1400
+ meta: {
1401
+ label: "Gravity",
1402
+ description: "Strength of center gravity",
1403
+ step: 0.1
1404
+ }
1405
+ },
1406
+ distributedAction: {
1407
+ schema: i.boolean().default(!1),
1408
+ meta: {
1409
+ label: "Distributed Action",
1410
+ description: "Use distributed attraction for hubs",
1411
+ advanced: !0
1412
+ }
1413
+ },
1414
+ strongGravity: {
1415
+ schema: i.boolean().default(!1),
1416
+ meta: {
1417
+ label: "Strong Gravity",
1418
+ description: "Use stronger gravity to prevent escape",
1419
+ advanced: !0
1420
+ }
1421
+ },
1422
+ dissuadeHubs: {
1423
+ schema: i.boolean().default(!1),
1424
+ meta: {
1425
+ label: "Dissuade Hubs",
1426
+ description: "Push hubs away from each other",
1427
+ advanced: !0
1428
+ }
1429
+ },
1430
+ linlog: {
1431
+ schema: i.boolean().default(!1),
1432
+ meta: {
1433
+ label: "LinLog Mode",
1434
+ description: "Use logarithmic attraction",
1435
+ advanced: !0
1436
+ }
1437
+ },
1438
+ seed: {
1439
+ schema: i.number().nullable().default(null),
1440
+ meta: {
1441
+ label: "Random Seed",
1442
+ description: "Seed for reproducible layout",
1443
+ advanced: !0
1444
+ }
1445
+ },
1446
+ dim: {
1447
+ schema: i.number().int().min(2).max(3).default(2),
1448
+ meta: {
1449
+ label: "Dimensions",
1450
+ description: "Layout dimensionality (2D or 3D)"
1451
+ }
1452
+ },
1453
+ weighted: {
1454
+ schema: i.boolean().default(!0),
1455
+ meta: {
1456
+ label: "Use Edge Weights",
1457
+ description: "Pull strongly connected nodes closer together"
1458
+ }
1459
+ },
1460
+ nodeMass: {
1461
+ schema: i.record(i.string(), i.number()).or(i.string()).or(i.null()).default(null),
1462
+ meta: {
1463
+ label: "Node Mass",
1464
+ description: "How hard each node is to move, in one of three forms: a mass per node id, the name of a numeric node attribute to read it from, or nothing at all -- which gives every node a mass of one more than its degree, so a hub holds its ground.",
1465
+ advanced: !0
1466
+ }
1467
+ }
1468
+ }), D = class D extends be {
1469
+ /**
1470
+ * Get dimension-specific options for ForceAtlas2 layout.
1471
+ * @param dimension - The desired dimension (2 or 3).
1472
+ * @returns Options object with dim parameter.
1473
+ */
1474
+ static getOptionsForDimension(e) {
1475
+ return { dim: e };
1476
+ }
1477
+ };
1478
+ D.type = "forceatlas2", D.simulationType = "forceatlas2", D.maxDimensions = 3, D.honoursWeights = !0, D.zodOptionsSchema = Gt;
1479
+ let Q = D;
1480
+ const qt = u({
1481
+ scalingFactor: {
1482
+ schema: i.number().min(1).max(1e3).default(100),
1483
+ meta: {
1484
+ label: "Scaling Factor",
1485
+ description: "Multiplier for node positions"
1486
+ }
1487
+ },
1488
+ columns: {
1489
+ schema: i.number().int().positive().nullable().default(null),
1490
+ meta: {
1491
+ label: "Columns",
1492
+ description: "Number of columns; empty makes the grid as close to square as it can"
1493
+ }
1494
+ },
1495
+ scale: {
1496
+ schema: i.number().positive().default(1),
1497
+ meta: {
1498
+ label: "Scale",
1499
+ description: "Half the length of the grid's longer side",
1500
+ step: 0.1
1501
+ }
1502
+ }
1503
+ }), jt = i.strictObject({
1504
+ ...v.shape,
1505
+ columns: i.number().int().positive().nullable().default(null),
1506
+ scale: i.number().positive().default(1),
1507
+ center: i.array(i.number()).length(2).or(i.null()).default(null)
1508
+ }), W = class W extends y {
1509
+ /**
1510
+ * Create a grid layout engine
1511
+ * @param opts - Configuration options including the column count
1512
+ */
1513
+ constructor(e) {
1514
+ super(e), this.scalingFactor = 100, this.config = jt.parse(e);
1515
+ }
1516
+ /**
1517
+ * Get dimension-specific options for grid layout
1518
+ * @param dimension - The desired dimension (2 or 3)
1519
+ * @returns Empty object for 2D, null for 3D (unsupported)
1520
+ */
1521
+ static getOptionsForDimension(e) {
1522
+ return e > this.maxDimensions ? null : {};
1523
+ }
1524
+ /**
1525
+ * Compute node positions on the lattice
1526
+ */
1527
+ doLayout() {
1528
+ this.stale = !1;
1529
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
1530
+ this.positions = qe({ nodes: e, edges: t }, this.config.columns, this.config.scale, this.config.center);
1531
+ }
1532
+ };
1533
+ W.type = "grid", W.maxDimensions = 2, W.zodOptionsSchema = qt;
1534
+ let X = W;
1535
+ const Ut = "weight", Ht = u({
1536
+ scalingFactor: {
1537
+ schema: i.number().min(1).max(1e3).default(50),
1538
+ meta: {
1539
+ label: "Scaling Factor",
1540
+ description: "Multiplier for node positions"
1541
+ }
1542
+ },
1543
+ scale: {
1544
+ schema: i.number().positive().default(1),
1545
+ meta: {
1546
+ label: "Scale",
1547
+ description: "Scale factor for the layout",
1548
+ step: 0.1
1549
+ }
1550
+ },
1551
+ dim: {
1552
+ schema: i.number().int().min(2).max(3).default(3),
1553
+ meta: {
1554
+ label: "Dimensions",
1555
+ description: "Layout dimensionality (2D or 3D)"
1556
+ }
1557
+ },
1558
+ weighted: {
1559
+ schema: i.boolean().default(!0),
1560
+ meta: {
1561
+ label: "Use Edge Weights",
1562
+ description: "Place strongly connected nodes closer together"
1563
+ }
1564
+ }
1565
+ }), Kt = i.strictObject({
1566
+ ...v.shape,
1567
+ dist: i.record(i.number(), i.record(i.number(), i.number())).or(i.null()).default(null),
1568
+ pos: i.record(i.number(), i.array(i.number()).min(1).max(3)).or(i.null()).default(null),
1569
+ weighted: i.boolean().default(!0),
1570
+ scale: i.number().positive().default(1),
1571
+ center: i.array(i.number()).min(2).max(3).or(i.null()).default(null),
1572
+ dim: i.number().default(3)
1573
+ }), M = class M extends y {
1574
+ /**
1575
+ * Create a Kamada-Kawai layout engine
1576
+ * @param opts - Configuration options including scale and dimensions
1577
+ */
1578
+ constructor(e) {
1579
+ super(e), this.scalingFactor = 50, this.config = Kt.parse(e);
1580
+ }
1581
+ /**
1582
+ * Get dimension-specific options for Kamada-Kawai layout
1583
+ * @param dimension - The desired dimension (2 or 3)
1584
+ * @returns Options object with dim parameter
1585
+ */
1586
+ static getOptionsForDimension(e) {
1587
+ return { dim: e };
1588
+ }
1589
+ /**
1590
+ * Compute node positions using Kamada-Kawai algorithm
1591
+ *
1592
+ * A WEIGHT IS INVERTED ON THE WAY IN, and that is the one thing about this engine a reader
1593
+ * has to know. Kamada-Kawai reads its weight as a graph DISTANCE -- it is fed straight to
1594
+ * Floyd-Warshall and then drawn as a length -- so a heavier edge would push its two nodes
1595
+ * FURTHER APART, the opposite of what a weight means everywhere else in the element, where a
1596
+ * larger number is a stronger connection. Nothing on screen would say which convention was in
1597
+ * force. So the element hands this solver `1 / weight` and the whole package keeps one
1598
+ * reading: heavier means more strongly connected, means drawn closer together.
1599
+ */
1600
+ doLayout() {
1601
+ this.stale = !1;
1602
+ const s = { nodes: () => this._nodes.map((r) => r.id), edges: () => this._edges.map((r) => [r.srcId, r.dstId]) }, o = this.config.weighted ? this.pairWeights(this._edges) : null;
1603
+ o !== null && (this.reportClampedWeights("kamada-kawai", o), s.getEdgeData = (r, c) => {
1604
+ const l = o.get(Ze(r, c));
1605
+ if (l !== void 0)
1606
+ return 1 / Math.max(l, Qe);
1607
+ }), this.positions = je(
1608
+ s,
1609
+ this.config.dist,
1610
+ this.config.pos,
1611
+ Ut,
1612
+ this.config.scale,
1613
+ this.config.center,
1614
+ this.config.dim
1615
+ );
1616
+ }
1617
+ };
1618
+ M.type = "kamada-kawai", M.maxDimensions = 3, M.honoursWeights = !0, M.zodOptionsSchema = Ht;
1619
+ let ee = M;
1620
+ const Vt = u({
1621
+ scalingFactor: {
1622
+ schema: i.number().min(1).max(1e3).default(40),
1623
+ meta: {
1624
+ label: "Scaling Factor",
1625
+ description: "Multiplier for node positions"
1626
+ }
1627
+ },
1628
+ align: {
1629
+ schema: i.enum(["vertical", "horizontal"]).default("vertical"),
1630
+ meta: {
1631
+ label: "Alignment",
1632
+ description: "Direction of multipartite partitions"
1633
+ }
1634
+ },
1635
+ scale: {
1636
+ schema: i.number().positive().default(1),
1637
+ meta: {
1638
+ label: "Scale",
1639
+ description: "Scale factor for the layout",
1640
+ step: 0.1
1641
+ }
1642
+ }
1643
+ }), Yt = i.strictObject({
1644
+ ...v.shape,
1645
+ // subsetKey: z.string().or(z.record(z.number(), z.array(z.string().or(z.number())))),
1646
+ subsetKey: i.record(i.string(), i.array(i.string().or(i.number()))),
1647
+ align: i.enum(["vertical", "horizontal"]).default("vertical"),
1648
+ scale: i.number().positive().default(1),
1649
+ center: i.array(i.number()).length(2).or(i.null()).default(null)
1650
+ }), A = class A extends y {
1651
+ /**
1652
+ * Create a multipartite layout engine
1653
+ * @param opts - Configuration options including subset keys and alignment
1654
+ */
1655
+ constructor(e) {
1656
+ super(e), this.scalingFactor = 40, this.config = Yt.parse(e);
1657
+ }
1658
+ /**
1659
+ * Get dimension-specific options for multipartite layout
1660
+ * @param dimension - The desired dimension (2 or 3)
1661
+ * @returns Empty object for 2D, null for 3D (unsupported)
1662
+ */
1663
+ static getOptionsForDimension(e) {
1664
+ return e > this.maxDimensions ? null : {};
1665
+ }
1666
+ /**
1667
+ * Compute node positions for multipartite graph
1668
+ */
1669
+ doLayout() {
1670
+ this.stale = !1;
1671
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
1672
+ this.positions = Ue(
1673
+ { nodes: e, edges: t },
1674
+ this.config.subsetKey,
1675
+ this.config.align,
1676
+ this.config.scale,
1677
+ this.config.center
1678
+ );
1679
+ }
1680
+ };
1681
+ A.type = "multipartite", A.maxDimensions = 2, A.zodOptionsSchema = Vt;
1682
+ let te = A;
1683
+ var C = { exports: {} }, Ce;
1684
+ function $t() {
1685
+ if (Ce) return C.exports;
1686
+ Ce = 1, C.exports = n, C.exports.random = n, C.exports.randomIterator = l;
1687
+ function n(a) {
1688
+ var h = typeof a == "number" ? a : +/* @__PURE__ */ new Date();
1689
+ return new e(h);
1690
+ }
1691
+ function e(a) {
1692
+ this.seed = a;
1693
+ }
1694
+ e.prototype.next = c, e.prototype.nextDouble = r, e.prototype.uniform = r, e.prototype.gaussian = t, e.prototype.random = r;
1695
+ function t() {
1696
+ var a, h, d;
1697
+ do
1698
+ h = this.nextDouble() * 2 - 1, d = this.nextDouble() * 2 - 1, a = h * h + d * d;
1699
+ while (a >= 1 || a === 0);
1700
+ return h * Math.sqrt(-2 * Math.log(a) / a);
1701
+ }
1702
+ e.prototype.levy = s;
1703
+ function s() {
1704
+ var a = 1.5, h = Math.pow(
1705
+ o(1 + a) * Math.sin(Math.PI * a / 2) / (o((1 + a) / 2) * a * Math.pow(2, (a - 1) / 2)),
1706
+ 1 / a
1707
+ );
1708
+ return this.gaussian() * h / Math.pow(Math.abs(this.gaussian()), 1 / a);
1709
+ }
1710
+ function o(a) {
1711
+ return Math.sqrt(2 * Math.PI / a) * Math.pow(1 / Math.E * (a + 1 / (12 * a - 1 / (10 * a))), a);
1712
+ }
1713
+ function r() {
1714
+ var a = this.seed;
1715
+ return a = a + 2127912214 + (a << 12) & 4294967295, a = (a ^ 3345072700 ^ a >>> 19) & 4294967295, a = a + 374761393 + (a << 5) & 4294967295, a = (a + 3550635116 ^ a << 9) & 4294967295, a = a + 4251993797 + (a << 3) & 4294967295, a = (a ^ 3042594569 ^ a >>> 16) & 4294967295, this.seed = a, (a & 268435455) / 268435456;
1716
+ }
1717
+ function c(a) {
1718
+ return Math.floor(this.nextDouble() * a);
1719
+ }
1720
+ function l(a, h) {
1721
+ var d = h || n();
1722
+ if (typeof d.next != "function")
1723
+ throw new Error("customRandom does not match expected API: next() function is missing");
1724
+ return {
1725
+ /**
1726
+ * Visits every single element of a collection once, in a random order.
1727
+ * Note: collection is modified in place.
1728
+ */
1729
+ forEach: N,
1730
+ /**
1731
+ * Shuffles array randomly, in place.
1732
+ */
1733
+ shuffle: S
1734
+ };
1735
+ function S() {
1736
+ var p, g, O;
1737
+ for (p = a.length - 1; p > 0; --p)
1738
+ g = d.next(p + 1), O = a[g], a[g] = a[p], a[p] = O;
1739
+ return a;
1740
+ }
1741
+ function N(p) {
1742
+ var g, O, he;
1743
+ for (g = a.length - 1; g > 0; --g)
1744
+ O = d.next(g + 1), he = a[O], a[O] = a[g], a[g] = he, p(he);
1745
+ a.length && p(a[0]);
1746
+ }
1747
+ }
1748
+ return C.exports;
1749
+ }
1750
+ var Jt = $t();
1751
+ const Zt = /* @__PURE__ */ et(Jt), Qt = u({
1752
+ dim: {
1753
+ schema: i.number().int().min(2).max(3).default(3),
1754
+ meta: {
1755
+ label: "Dimensions",
1756
+ description: "Layout dimensionality (2D or 3D)"
1757
+ }
1758
+ },
1759
+ springLength: {
1760
+ schema: i.number().positive().default(30),
1761
+ meta: {
1762
+ label: "Spring Length",
1763
+ description: "Ideal spring length between connected nodes"
1764
+ }
1765
+ },
1766
+ springCoefficient: {
1767
+ schema: i.number().positive().default(8e-4),
1768
+ meta: {
1769
+ label: "Spring Coefficient",
1770
+ description: "Spring stiffness coefficient",
1771
+ step: 1e-4,
1772
+ advanced: !0
1773
+ }
1774
+ },
1775
+ gravity: {
1776
+ schema: i.number().default(-1.2),
1777
+ meta: {
1778
+ label: "Gravity",
1779
+ description: "Gravity strength (negative for repulsion)",
1780
+ step: 0.1
1781
+ }
1782
+ },
1783
+ theta: {
1784
+ schema: i.number().positive().default(0.8),
1785
+ meta: {
1786
+ label: "Theta",
1787
+ description: "Barnes-Hut approximation parameter",
1788
+ step: 0.1,
1789
+ advanced: !0
1790
+ }
1791
+ },
1792
+ dragCoefficient: {
1793
+ schema: i.number().positive().default(0.02),
1794
+ meta: {
1795
+ label: "Drag Coefficient",
1796
+ description: "Velocity damping coefficient",
1797
+ step: 0.01,
1798
+ advanced: !0
1799
+ }
1800
+ },
1801
+ timeStep: {
1802
+ schema: i.number().positive().default(20),
1803
+ meta: {
1804
+ label: "Time Step",
1805
+ description: "Simulation time step size",
1806
+ advanced: !0
1807
+ }
1808
+ },
1809
+ seed: {
1810
+ schema: i.number().int().positive().nullable().default(null),
1811
+ meta: {
1812
+ label: "Random Seed",
1813
+ description: "Seed for reproducible layout",
1814
+ advanced: !0
1815
+ }
1816
+ }
1817
+ }), _ = class _ extends fe {
1818
+ /**
1819
+ * Create an NGraph layout engine
1820
+ * @param config - Configuration options for the NGraph simulation
1821
+ */
1822
+ constructor(e = {}) {
1823
+ super(), this.nodeMapping = /* @__PURE__ */ new Map(), this.edgeMapping = /* @__PURE__ */ new Map(), this._settled = !0, this._stepCount = 0, this._lastMoves = [], this.seededPlacement = null, this.ngraph = lt();
1824
+ const t = e, s = {
1825
+ dimensions: t.dim !== void 0 ? t.dim : 3
1826
+ };
1827
+ t.springLength !== void 0 && (s.springLength = t.springLength), t.springCoefficient !== void 0 && (s.springCoefficient = t.springCoefficient), t.gravity !== void 0 && (s.gravity = t.gravity), t.theta !== void 0 && (s.theta = t.theta), t.dragCoefficient !== void 0 && (s.dragCoefficient = t.dragCoefficient), t.timeStep !== void 0 && (s.timeStep = t.timeStep), typeof t.seed == "number" && (this.seededPlacement = { rng: Zt(t.seed), dim: s.dimensions }), this.ngraphLayout = rt(this.ngraph, s);
1828
+ }
1829
+ /**
1830
+ * Get dimension-specific options for NGraph layout
1831
+ * @param dimension - The desired dimension (2 or 3)
1832
+ * @returns Options object with dim parameter
1833
+ */
1834
+ static getOptionsForDimension(e) {
1835
+ return { dim: e };
1836
+ }
1837
+ /**
1838
+ * Initialize the layout engine
1839
+ *
1840
+ * NGraph layout is initialized in the constructor and doesn't require
1841
+ * additional async initialization.
1842
+ */
1843
+ async init() {
1844
+ }
1845
+ /**
1846
+ * Advance the NGraph simulation by one step
1847
+ */
1848
+ step() {
1849
+ const e = this.ngraphLayout.step(), { lastMove: t } = this.ngraphLayout, s = this.nodeMapping.size, o = s > 0 ? t / s : 0;
1850
+ this._stepCount++, this._lastMoves.push(o), this._lastMoves.length > 10 && this._lastMoves.shift();
1851
+ const r = this._lastMoves.length > 0 ? this._lastMoves.reduce((a, h) => a + h, 0) / this._lastMoves.length : 0, c = 0.05, l = 1e3;
1852
+ this._settled = e || r <= c || this._stepCount >= l, this.publishPositions();
1853
+ }
1854
+ /**
1855
+ * Copy the simulation's node coordinates into the shared position array.
1856
+ *
1857
+ * `ngraphLayout.getNodePosition` hands back the body's own position object rather than a copy,
1858
+ * so this reads it in place and allocates nothing -- which is the point of overriding the base,
1859
+ * whose default would build one object per node per step. In two dimensions ngraph carries no
1860
+ * z at all, and the row is published flat rather than left unplaced.
1861
+ */
1862
+ publishPositions() {
1863
+ for (const [e, t] of this.nodeMapping) {
1864
+ const s = this.ngraphLayout.getNodePosition(t.id);
1865
+ this.writeNodePosition(e, s.x, s.y, s.z ?? 0);
1866
+ }
1867
+ }
1868
+ /**
1869
+ * Check if the simulation has settled
1870
+ * @returns True if the simulation has settled
1871
+ */
1872
+ get isSettled() {
1873
+ return this._settled;
1874
+ }
1875
+ /**
1876
+ * Add a node to the NGraph simulation
1877
+ * @param n - The node to add
1878
+ */
1879
+ addNode(e) {
1880
+ const t = this.ngraph.addNode(e.id, { parentNode: e });
1881
+ if (this.nodeMapping.set(e, t), this.seededPlacement) {
1882
+ const { rng: s, dim: o } = this.seededPlacement, { springLength: r } = this.ngraphLayout.simulator.settings, c = () => (s.nextDouble() - 0.5) * r, l = c(), a = c();
1883
+ this.ngraphLayout.setNodePosition(e.id, l, a, o === 3 ? c() : 0);
1884
+ }
1885
+ this._settled = !1, this._stepCount = 0, this._lastMoves = [];
1886
+ }
1887
+ /**
1888
+ * Add an edge to the NGraph simulation
1889
+ * @param e - The edge to add
1890
+ */
1891
+ addEdge(e) {
1892
+ const t = this.ngraph.addLink(e.srcId, e.dstId, { parentEdge: e });
1893
+ this.edgeMapping.set(e, t), this._settled = !1, this._stepCount = 0, this._lastMoves = [];
1894
+ }
1895
+ /**
1896
+ * Get the current position of a node
1897
+ * @param n - The node to get position for
1898
+ * @returns The node's position coordinates
1899
+ */
1900
+ getNodePosition(e) {
1901
+ const t = this._getMappedNode(e), s = this.ngraphLayout.getNodePosition(t.id), o = { x: 0, y: 0, z: 0 };
1902
+ return this.writeNodePosition(e, s.x, s.y, s.z ?? 0), this.readNodePosition(e, o) ? o : s;
1903
+ }
1904
+ /**
1905
+ * Set a node's position in the simulation
1906
+ * @param n - The node to set position for
1907
+ * @param newPos - The new position coordinates
1908
+ */
1909
+ setNodePosition(e, t) {
1910
+ const s = this._getMappedNode(e), o = this.ngraphLayout.getNodePosition(s.id);
1911
+ o.x = t.x, o.y = t.y, o.z = t.z, this.writeNodePosition(e, t.x, t.y, t.z ?? 0, "placement");
1912
+ }
1913
+ /**
1914
+ * Get the position of an edge based on its endpoint positions
1915
+ * @param e - The edge to get position for
1916
+ * @returns The edge's source and destination positions
1917
+ */
1918
+ getEdgePosition(e) {
1919
+ const t = this._getMappedEdge(e), s = this.ngraphLayout.getLinkPosition(t.id);
1920
+ return {
1921
+ src: {
1922
+ x: s.from.x,
1923
+ y: s.from.y,
1924
+ z: s.from.z
1925
+ },
1926
+ dst: {
1927
+ x: s.to.x,
1928
+ y: s.to.y,
1929
+ z: s.to.z
1930
+ }
1931
+ };
1932
+ }
1933
+ /**
1934
+ * Get all nodes in the simulation
1935
+ * @returns Iterable of nodes
1936
+ */
1937
+ get nodes() {
1938
+ return this.nodeMapping.keys();
1939
+ }
1940
+ /**
1941
+ * Get all edges in the simulation
1942
+ * @returns Iterable of edges
1943
+ */
1944
+ get edges() {
1945
+ return this.edgeMapping.keys();
1946
+ }
1947
+ /**
1948
+ * Pin a node to its current position
1949
+ * @param n - The node to pin
1950
+ */
1951
+ pin(e) {
1952
+ const t = this._getMappedNode(e);
1953
+ this.ngraphLayout.pinNode(t, !0);
1954
+ }
1955
+ /**
1956
+ * Unpin a node to allow it to move freely
1957
+ * @param n - The node to unpin
1958
+ */
1959
+ unpin(e) {
1960
+ const t = this._getMappedNode(e);
1961
+ this.ngraphLayout.pinNode(t, !1);
1962
+ }
1963
+ /**
1964
+ * Take a node out of the simulation, and the links ngraph drops with it.
1965
+ *
1966
+ * `ngraph.removeNode` removes the node's links too, so the element's own edge mapping is
1967
+ * swept for links that no longer belong to any graph -- otherwise `getEdgePosition` would ask
1968
+ * ngraph for the position of a link it has already forgotten.
1969
+ * @param n - the node leaving the graph
1970
+ */
1971
+ removeNode(e) {
1972
+ const t = this.nodeMapping.get(e);
1973
+ if (t) {
1974
+ for (const [s, o] of this.edgeMapping)
1975
+ (o.fromId === t.id || o.toId === t.id) && this.edgeMapping.delete(s);
1976
+ this.ngraph.removeNode(t.id), this.nodeMapping.delete(e), this._settled = !1;
1977
+ }
1978
+ }
1979
+ /**
1980
+ * Take an edge out of the simulation. See {@link NGraphEngine.removeNode}.
1981
+ * @param e - the edge leaving the graph
1982
+ */
1983
+ removeEdge(e) {
1984
+ const t = this.edgeMapping.get(e);
1985
+ t && (this.ngraph.removeLink(t), this.edgeMapping.delete(e), this._settled = !1);
1986
+ }
1987
+ _getMappedNode(e) {
1988
+ const t = this.nodeMapping.get(e);
1989
+ if (!t)
1990
+ throw new Error("Internal error: Node not found in NGraphEngine");
1991
+ return t;
1992
+ }
1993
+ _getMappedEdge(e) {
1994
+ const t = this.edgeMapping.get(e);
1995
+ if (!t)
1996
+ throw new Error("Internal error: Edge not found in NGraphEngine");
1997
+ return t;
1998
+ }
1999
+ };
2000
+ _.type = "ngraph", _.maxDimensions = 3, _.zodOptionsSchema = Qt;
2001
+ let ie = _;
2002
+ const Xt = u({
2003
+ scalingFactor: {
2004
+ schema: i.number().min(1).max(1e3).default(70),
2005
+ meta: {
2006
+ label: "Scaling Factor",
2007
+ description: "Multiplier for node positions"
2008
+ }
2009
+ },
2010
+ scale: {
2011
+ schema: i.number().positive().default(1),
2012
+ meta: {
2013
+ label: "Scale",
2014
+ description: "Scale factor for the planar layout",
2015
+ step: 0.1
2016
+ }
2017
+ },
2018
+ dim: {
2019
+ schema: i.number().int().min(2).max(2).default(2),
2020
+ meta: {
2021
+ label: "Dimensions",
2022
+ description: "Layout dimensionality (2D only for planar)"
2023
+ }
2024
+ },
2025
+ seed: {
2026
+ schema: i.number().nullable().default(null),
2027
+ meta: {
2028
+ label: "Random Seed",
2029
+ description: "Seed for reproducible layout",
2030
+ advanced: !0
2031
+ }
2032
+ }
2033
+ }), ei = i.strictObject({
2034
+ ...v.shape,
2035
+ scale: i.number().positive().default(1),
2036
+ center: i.array(i.number()).length(2).or(i.null()).default(null),
2037
+ dim: i.number().default(2),
2038
+ seed: i.number().or(i.null()).default(null)
2039
+ }), B = class B extends y {
2040
+ /**
2041
+ * Create a planar layout engine
2042
+ * @param opts - Configuration options including scale and seed
2043
+ */
2044
+ constructor(e) {
2045
+ super(e), this.scalingFactor = 70, this.config = ei.parse(e);
2046
+ }
2047
+ /**
2048
+ * Get dimension-specific options for planar layout
2049
+ * @param dimension - The desired dimension (2 or 3)
2050
+ * @returns Options object with dim parameter or null for 3D (unsupported)
2051
+ */
2052
+ static getOptionsForDimension(e) {
2053
+ return e > this.maxDimensions ? null : { dim: e };
2054
+ }
2055
+ /**
2056
+ * Compute planar node positions with no edge crossings
2057
+ */
2058
+ doLayout() {
2059
+ this.stale = !1;
2060
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
2061
+ this.positions = He(
2062
+ { nodes: e, edges: t },
2063
+ this.config.scale,
2064
+ this.config.center,
2065
+ this.config.dim,
2066
+ this.config.seed
2067
+ );
2068
+ }
2069
+ };
2070
+ B.type = "planar", B.maxDimensions = 2, B.zodOptionsSchema = Xt;
2071
+ let se = B;
2072
+ const ti = u({
2073
+ scalingFactor: {
2074
+ schema: i.number().min(1).max(1e3).default(100),
2075
+ meta: {
2076
+ label: "Scaling Factor",
2077
+ description: "Multiplier for node positions"
2078
+ }
2079
+ },
2080
+ root: {
2081
+ schema: i.union([i.string(), i.number()]).nullable().default(null),
2082
+ meta: {
2083
+ label: "Root Node",
2084
+ description: "The node at the centre; empty picks the node with the most edges"
2085
+ }
2086
+ },
2087
+ scale: {
2088
+ schema: i.number().positive().default(1),
2089
+ meta: {
2090
+ label: "Scale",
2091
+ description: "Radius of the outermost ring",
2092
+ step: 0.1
2093
+ }
2094
+ }
2095
+ }), ii = i.strictObject({
2096
+ ...v.shape,
2097
+ root: i.union([i.string(), i.number()]).nullable().default(null),
2098
+ scale: i.number().positive().default(1),
2099
+ center: i.array(i.number()).length(2).or(i.null()).default(null)
2100
+ }), G = class G extends y {
2101
+ /**
2102
+ * Create a radial layout engine
2103
+ * @param opts - Configuration options including the root node
2104
+ */
2105
+ constructor(e) {
2106
+ super(e), this.scalingFactor = 100, this.config = ii.parse(e);
2107
+ }
2108
+ /**
2109
+ * Get dimension-specific options for radial layout
2110
+ * @param dimension - The desired dimension (2 or 3)
2111
+ * @returns Empty object for 2D, null for 3D (unsupported)
2112
+ */
2113
+ static getOptionsForDimension(e) {
2114
+ return e > this.maxDimensions ? null : {};
2115
+ }
2116
+ /**
2117
+ * Compute node positions on rings around the root
2118
+ */
2119
+ doLayout() {
2120
+ this.stale = !1;
2121
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
2122
+ this.positions = Ke({ nodes: e, edges: t }, this.config.root, this.config.scale, this.config.center);
2123
+ }
2124
+ };
2125
+ G.type = "radial", G.maxDimensions = 2, G.zodOptionsSchema = ti;
2126
+ let ne = G;
2127
+ const Fe = 1, si = u({
2128
+ scalingFactor: {
2129
+ schema: i.number().min(1).max(1e3).default(100),
2130
+ meta: {
2131
+ label: "Scaling Factor",
2132
+ description: "Multiplier for node positions"
2133
+ }
2134
+ },
2135
+ dim: {
2136
+ schema: i.number().int().min(2).max(3).default(2),
2137
+ meta: {
2138
+ label: "Dimensions",
2139
+ description: "Layout dimensionality (2D or 3D)"
2140
+ }
2141
+ },
2142
+ seed: {
2143
+ schema: i.number().positive().nullable().default(Fe),
2144
+ meta: {
2145
+ label: "Random Seed",
2146
+ description: "Seed for reproducible random positions",
2147
+ advanced: !0
2148
+ }
2149
+ }
2150
+ }), ni = i.strictObject({
2151
+ ...v.shape,
2152
+ center: i.array(i.number()).min(2).max(3).or(i.null()).default(null),
2153
+ dim: i.number().default(2),
2154
+ seed: i.number().positive().or(i.null()).default(Fe)
2155
+ }), q = class q extends y {
2156
+ /**
2157
+ * Create a random layout engine
2158
+ * @param opts - Configuration options including dimensions and seed
2159
+ */
2160
+ constructor(e) {
2161
+ super(e), this.scalingFactor = 100, this.config = ni.parse(e);
2162
+ }
2163
+ /**
2164
+ * Get dimension-specific options for random layout
2165
+ * @param dimension - The desired dimension (2 or 3)
2166
+ * @returns Options object with dim parameter
2167
+ */
2168
+ static getOptionsForDimension(e) {
2169
+ return { dim: e };
2170
+ }
2171
+ /**
2172
+ * Compute random node positions
2173
+ */
2174
+ doLayout() {
2175
+ this.stale = !1;
2176
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
2177
+ this.positions = Ve({ nodes: e, edges: t }, this.config.center, this.config.dim, this.config.seed);
2178
+ }
2179
+ };
2180
+ q.type = "random", q.maxDimensions = 3, q.zodOptionsSchema = si;
2181
+ let ae = q;
2182
+ const ai = u({
2183
+ scalingFactor: {
2184
+ schema: i.number().min(1).max(1e3).default(100),
2185
+ meta: {
2186
+ label: "Scaling Factor",
2187
+ description: "Multiplier for node positions"
2188
+ }
2189
+ },
2190
+ scale: {
2191
+ schema: i.number().positive().default(1),
2192
+ meta: {
2193
+ label: "Scale",
2194
+ description: "Scale factor for the shell layout radius",
2195
+ step: 0.1
2196
+ }
2197
+ },
2198
+ dim: {
2199
+ schema: i.number().int().min(2).max(2).default(2),
2200
+ meta: {
2201
+ label: "Dimensions",
2202
+ description: "Layout dimensionality (2D only for shell)"
2203
+ }
2204
+ }
2205
+ }), oi = i.strictObject({
2206
+ ...v.shape,
2207
+ nlist: i.array(i.array(i.number())).or(i.null()).default(null),
2208
+ dim: i.number().default(2),
2209
+ center: i.array(i.number()).length(2).or(i.null()).default(null),
2210
+ scale: i.number().positive().default(1)
2211
+ }), j = class j extends y {
2212
+ /**
2213
+ * Create a shell layout engine
2214
+ * @param opts - Configuration options including node lists for each shell
2215
+ */
2216
+ constructor(e) {
2217
+ super(e), this.scalingFactor = 100, this.config = oi.parse(e);
2218
+ }
2219
+ /**
2220
+ * Get dimension-specific options for shell layout
2221
+ * @param dimension - The desired dimension (2 or 3)
2222
+ * @returns Options object with dim parameter or null for 3D (unsupported)
2223
+ */
2224
+ static getOptionsForDimension(e) {
2225
+ return e > this.maxDimensions ? null : { dim: e };
2226
+ }
2227
+ /**
2228
+ * Compute node positions in concentric shells
2229
+ */
2230
+ doLayout() {
2231
+ this.stale = !1;
2232
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
2233
+ this.positions = Ye(
2234
+ { nodes: e, edges: t },
2235
+ this.config.nlist,
2236
+ this.config.scale,
2237
+ this.config.center,
2238
+ this.config.dim
2239
+ );
2240
+ }
2241
+ };
2242
+ j.type = "shell", j.maxDimensions = 2, j.zodOptionsSchema = ai;
2243
+ let oe = j;
2244
+ const ri = u({
2245
+ scalingFactor: {
2246
+ schema: i.number().min(1).max(1e3).default(100),
2247
+ meta: {
2248
+ label: "Scaling Factor",
2249
+ description: "Multiplier for node positions"
2250
+ }
2251
+ },
2252
+ scale: {
2253
+ schema: i.number().positive().default(1),
2254
+ meta: {
2255
+ label: "Scale",
2256
+ description: "Scale factor for the spectral layout",
2257
+ step: 0.1
2258
+ }
2259
+ },
2260
+ dim: {
2261
+ schema: i.number().int().min(2).max(2).default(2),
2262
+ meta: {
2263
+ label: "Dimensions",
2264
+ description: "Layout dimensionality (2D only for spectral)"
2265
+ }
2266
+ }
2267
+ }), li = i.strictObject({
2268
+ ...v.shape,
2269
+ scale: i.number().positive().default(1),
2270
+ center: i.array(i.number()).length(2).or(i.null()).default(null),
2271
+ dim: i.number().default(2)
2272
+ }), U = class U extends y {
2273
+ /**
2274
+ * Create a spectral layout engine
2275
+ * @param opts - Configuration options including scale and center
2276
+ */
2277
+ constructor(e) {
2278
+ super(e), this.scalingFactor = 100, this.config = li.parse(e);
2279
+ }
2280
+ /**
2281
+ * Get dimension-specific options for spectral layout
2282
+ * @param dimension - The desired dimension (2 or 3)
2283
+ * @returns Options object with dim parameter or null for 3D (unsupported)
2284
+ */
2285
+ static getOptionsForDimension(e) {
2286
+ return e > this.maxDimensions ? null : { dim: e };
2287
+ }
2288
+ /**
2289
+ * Compute node positions using spectral graph theory
2290
+ */
2291
+ doLayout() {
2292
+ this.stale = !1;
2293
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
2294
+ this.positions = $e({ nodes: e, edges: t }, this.config.scale, this.config.center, this.config.dim);
2295
+ }
2296
+ };
2297
+ U.type = "spectral", U.maxDimensions = 2, U.zodOptionsSchema = ri;
2298
+ let re = U;
2299
+ const ci = u({
2300
+ scalingFactor: {
2301
+ schema: i.number().min(1).max(1e3).default(80),
2302
+ meta: {
2303
+ label: "Scaling Factor",
2304
+ description: "Multiplier for node positions"
2305
+ }
2306
+ },
2307
+ scale: {
2308
+ schema: i.number().positive().default(1),
2309
+ meta: {
2310
+ label: "Scale",
2311
+ description: "Scale factor for the spiral layout",
2312
+ step: 0.1
2313
+ }
2314
+ },
2315
+ dim: {
2316
+ schema: i.number().int().min(2).max(2).default(2),
2317
+ meta: {
2318
+ label: "Dimensions",
2319
+ description: "Layout dimensionality (2D only for spiral)"
2320
+ }
2321
+ },
2322
+ resolution: {
2323
+ schema: i.number().positive().default(0.35),
2324
+ meta: {
2325
+ label: "Resolution",
2326
+ description: "Controls spacing between spiral turns",
2327
+ step: 0.05,
2328
+ advanced: !0
2329
+ }
2330
+ },
2331
+ equidistant: {
2332
+ schema: i.boolean().default(!1),
2333
+ meta: {
2334
+ label: "Equidistant",
2335
+ description: "Place nodes at equal distances along the spiral",
2336
+ advanced: !0
2337
+ }
2338
+ }
2339
+ }), di = i.strictObject({
2340
+ ...v.shape,
2341
+ scale: i.number().positive().default(1),
2342
+ center: i.array(i.number()).length(2).or(i.null()).default(null),
2343
+ dim: i.number().default(2),
2344
+ resolution: i.number().positive().default(0.35),
2345
+ equidistant: i.boolean().default(!1)
2346
+ }), H = class H extends y {
2347
+ /**
2348
+ * Create a spiral layout engine
2349
+ * @param opts - Configuration options including resolution and equidistant spacing
2350
+ */
2351
+ constructor(e) {
2352
+ super(e), this.scalingFactor = 80, this.config = di.parse(e);
2353
+ }
2354
+ /**
2355
+ * Get dimension-specific options for spiral layout
2356
+ * @param dimension - The desired dimension (2 or 3)
2357
+ * @returns Options object with dim parameter or null for 3D (unsupported)
2358
+ */
2359
+ static getOptionsForDimension(e) {
2360
+ return e > this.maxDimensions ? null : { dim: e };
2361
+ }
2362
+ /**
2363
+ * Compute node positions along a spiral path
2364
+ */
2365
+ doLayout() {
2366
+ this.stale = !1;
2367
+ const e = () => this._nodes.map((s) => s.id), t = () => this._edges.map((s) => [s.srcId, s.dstId]);
2368
+ this.positions = Je(
2369
+ { nodes: e, edges: t },
2370
+ this.config.scale,
2371
+ this.config.center,
2372
+ this.config.dim,
2373
+ this.config.resolution,
2374
+ this.config.equidistant
2375
+ );
2376
+ }
2377
+ };
2378
+ H.type = "spiral", H.maxDimensions = 2, H.zodOptionsSchema = ci;
2379
+ let le = H;
2380
+ const hi = u({
2381
+ springLength: {
2382
+ schema: i.number().positive().default(10),
2383
+ meta: {
2384
+ label: "Spring Length",
2385
+ description: "The distance an edge pulls its two nodes towards",
2386
+ step: 1
2387
+ }
2388
+ },
2389
+ springCoefficient: {
2390
+ schema: i.number().positive().default(0.8),
2391
+ meta: {
2392
+ label: "Spring Strength",
2393
+ description: "How hard an edge pulls; Hooke's constant",
2394
+ step: 0.1
2395
+ }
2396
+ },
2397
+ gravity: {
2398
+ schema: i.number().default(-12),
2399
+ meta: {
2400
+ label: "Repulsion",
2401
+ description: "How hard two nodes push each other apart; negative repels",
2402
+ step: 1
2403
+ }
2404
+ },
2405
+ dragCoefficient: {
2406
+ schema: i.number().positive().default(0.9),
2407
+ meta: {
2408
+ label: "Drag",
2409
+ description: "How quickly motion bleeds away, which is what lets the shape settle",
2410
+ step: 0.05,
2411
+ advanced: !0
2412
+ }
2413
+ },
2414
+ timeStep: {
2415
+ schema: i.number().positive().default(0.5),
2416
+ meta: {
2417
+ label: "Time Step",
2418
+ description: "How far one iteration integrates; larger is faster and less stable",
2419
+ step: 0.1,
2420
+ advanced: !0
2421
+ }
2422
+ },
2423
+ scale: {
2424
+ schema: i.number().positive().default(1),
2425
+ meta: {
2426
+ label: "Scale",
2427
+ description: "Scale factor for the layout",
2428
+ step: 0.1
2429
+ }
2430
+ },
2431
+ dim: {
2432
+ schema: i.number().int().min(2).max(3).default(3),
2433
+ meta: {
2434
+ label: "Dimensions",
2435
+ description: "Layout dimensionality (2D or 3D)"
2436
+ }
2437
+ },
2438
+ seed: {
2439
+ schema: i.number().nullable().default(null),
2440
+ meta: {
2441
+ label: "Random Seed",
2442
+ description: "Seed for reproducible layout",
2443
+ advanced: !0
2444
+ }
2445
+ }
2446
+ }), E = class E extends be {
2447
+ /**
2448
+ * Get dimension-specific options for the spring-electrical layout.
2449
+ * @param dimension - The desired dimension (2 or 3).
2450
+ * @returns Options object with dim parameter.
2451
+ */
2452
+ static getOptionsForDimension(e) {
2453
+ return { dim: e };
2454
+ }
2455
+ };
2456
+ E.type = "spring-electrical", E.simulationType = "spring-electrical", E.maxDimensions = 3, E.zodOptionsSchema = hi;
2457
+ let ce = E;
2458
+ const ui = u({
2459
+ scalingFactor: {
2460
+ schema: i.number().min(1).max(1e3).default(100),
2461
+ meta: {
2462
+ label: "Scaling Factor",
2463
+ description: "Multiplier for node positions"
2464
+ }
2465
+ },
2466
+ k: {
2467
+ schema: i.number().nullable().default(null),
2468
+ meta: {
2469
+ label: "Spring Constant",
2470
+ description: "Optimal distance between nodes (auto-calculated if null)",
2471
+ advanced: !0
2472
+ }
2473
+ },
2474
+ iterations: {
2475
+ schema: i.number().positive().default(50),
2476
+ meta: {
2477
+ label: "Iterations",
2478
+ description: "Number of spring simulation iterations"
2479
+ }
2480
+ },
2481
+ scale: {
2482
+ schema: i.number().positive().default(1),
2483
+ meta: {
2484
+ label: "Scale",
2485
+ description: "Scale factor for the layout",
2486
+ step: 0.1
2487
+ }
2488
+ },
2489
+ dim: {
2490
+ schema: i.number().int().min(2).max(3).default(3),
2491
+ meta: {
2492
+ label: "Dimensions",
2493
+ description: "Layout dimensionality (2D or 3D)"
2494
+ }
2495
+ },
2496
+ seed: {
2497
+ schema: i.number().positive().nullable().default(null),
2498
+ meta: {
2499
+ label: "Random Seed",
2500
+ description: "Seed for reproducible layout",
2501
+ advanced: !0
2502
+ }
2503
+ }
2504
+ }), I = class I extends be {
2505
+ /**
2506
+ * Get dimension-specific options for spring layout.
2507
+ * @param dimension - The desired dimension (2 or 3).
2508
+ * @returns Options object with dim parameter.
2509
+ */
2510
+ static getOptionsForDimension(e) {
2511
+ return { dim: e };
2512
+ }
2513
+ };
2514
+ I.type = "spring", I.simulationType = "spring", I.maxDimensions = 3, I.zodOptionsSchema = ui;
2515
+ let de = I;
2516
+ const w = {
2517
+ seed: { type: "seed" }
2518
+ };
2519
+ function m(n, e = {}) {
2520
+ return ot(n, { overrides: e });
2521
+ }
2522
+ function f(n, e, t = []) {
2523
+ return {
2524
+ descriptor: {
2525
+ ...n,
2526
+ engine: e.engine,
2527
+ options: e.options,
2528
+ honoursWeights: e.honoursWeights
2529
+ },
2530
+ implementations: [
2531
+ { ...e, isDefault: !0 },
2532
+ ...t.map((s) => ({ ...s, isDefault: !1 }))
2533
+ ]
2534
+ };
2535
+ }
2536
+ const mi = {
2537
+ engine: "ngraph",
2538
+ plainName: "NGraph Force",
2539
+ technicalName: "ngraph.forcelayout",
2540
+ kind: "live",
2541
+ maxDimensions: 3,
2542
+ reason: "The default: a Barnes-Hut simulation that runs live, accepts nodes and edges added while it is running, and stays interactive on graphs of a hundred thousand nodes.",
2543
+ options: m(ie.zodOptionsSchema, w),
2544
+ honoursWeights: ie.honoursWeights
2545
+ }, pi = {
2546
+ engine: "d3",
2547
+ plainName: "D3 Force",
2548
+ technicalName: "d3-force-3d",
2549
+ kind: "live",
2550
+ maxDimensions: 3,
2551
+ reason: "Choose it for d3's own tuning vocabulary -- alpha, alpha decay, velocity decay -- when the arrangement has to match a d3 drawing elsewhere in the product.",
2552
+ options: m(J.zodOptionsSchema),
2553
+ honoursWeights: J.honoursWeights
2554
+ }, gi = {
2555
+ engine: "forceatlas2",
2556
+ plainName: "ForceAtlas2",
2557
+ technicalName: "ForceAtlas2 (Gephi)",
2558
+ kind: "live",
2559
+ maxDimensions: 3,
2560
+ reason: "Choose it for the Gephi look, and for the arrangement an accelerator reproduces first. It keeps running until the layout settles and reheats on a drag or a pin.",
2561
+ options: m(Q.zodOptionsSchema, w),
2562
+ honoursWeights: Q.honoursWeights
2563
+ }, fi = {
2564
+ engine: "spring",
2565
+ plainName: "Spring",
2566
+ technicalName: "Fruchterman-Reingold",
2567
+ kind: "live",
2568
+ maxDimensions: 3,
2569
+ reason: "Choose it when the arrangement must be reproducible from a seed: the same seed gives the same settled shape.",
2570
+ options: m(de.zodOptionsSchema, w),
2571
+ honoursWeights: de.honoursWeights
2572
+ }, bi = {
2573
+ engine: "spring-electrical",
2574
+ plainName: "Spring Electrical",
2575
+ technicalName: "ngraph.forcelayout (spring-electrical)",
2576
+ kind: "live",
2577
+ maxDimensions: 3,
2578
+ reason: "Choose it for ngraph's look at a size ngraph cannot reach; it needs a hardware accelerator and says so when there is none.",
2579
+ options: m(ce.zodOptionsSchema, w),
2580
+ honoursWeights: ce.honoursWeights,
2581
+ requires: { accelerator: !0 }
2582
+ }, yi = {
2583
+ engine: "kamada-kawai",
2584
+ plainName: "Kamada-Kawai",
2585
+ technicalName: "Kamada-Kawai stress majorization",
2586
+ kind: "batch",
2587
+ maxDimensions: 3,
2588
+ reason: "Choose it for a small graph whose drawn distances should match its graph distances. It solves over every pair of nodes, so it is slow well before the other force engines are.",
2589
+ options: m(ee.zodOptionsSchema),
2590
+ honoursWeights: ee.honoursWeights
2591
+ }, vi = {
2592
+ engine: "arf",
2593
+ plainName: "ARF",
2594
+ technicalName: "Attractive and Repulsive Forces",
2595
+ kind: "batch",
2596
+ maxDimensions: 2,
2597
+ reason: "The only registered force engine that is two-dimensional by nature, so a flat result is what it computes rather than what it is flattened into afterwards.",
2598
+ options: m(K.zodOptionsSchema, w),
2599
+ honoursWeights: K.honoursWeights
2600
+ }, xi = {
2601
+ engine: "circular",
2602
+ plainName: "Circular",
2603
+ technicalName: "Circular layout",
2604
+ kind: "batch",
2605
+ maxDimensions: 3,
2606
+ reason: "The only engine that draws this arrangement.",
2607
+ options: m($.zodOptionsSchema),
2608
+ honoursWeights: $.honoursWeights
2609
+ }, Si = {
2610
+ engine: "shell",
2611
+ plainName: "Shell",
2612
+ technicalName: "Shell layout",
2613
+ kind: "batch",
2614
+ maxDimensions: 2,
2615
+ reason: "The only engine that draws this arrangement.",
2616
+ options: m(oe.zodOptionsSchema),
2617
+ honoursWeights: oe.honoursWeights
2618
+ }, Ni = {
2619
+ engine: "radial",
2620
+ plainName: "Radial",
2621
+ technicalName: "Radial layout",
2622
+ kind: "batch",
2623
+ maxDimensions: 2,
2624
+ reason: "The only engine that draws this arrangement.",
2625
+ options: m(ne.zodOptionsSchema),
2626
+ honoursWeights: ne.honoursWeights
2627
+ }, Di = {
2628
+ engine: "grid",
2629
+ plainName: "Grid",
2630
+ technicalName: "Grid layout",
2631
+ kind: "batch",
2632
+ maxDimensions: 2,
2633
+ reason: "The only engine that draws this arrangement.",
2634
+ options: m(X.zodOptionsSchema),
2635
+ honoursWeights: X.honoursWeights
2636
+ }, wi = {
2637
+ engine: "spiral",
2638
+ plainName: "Spiral",
2639
+ technicalName: "Archimedean spiral layout",
2640
+ kind: "batch",
2641
+ maxDimensions: 2,
2642
+ reason: "The only engine that draws this arrangement.",
2643
+ options: m(le.zodOptionsSchema),
2644
+ honoursWeights: le.honoursWeights
2645
+ }, Oi = {
2646
+ engine: "spectral",
2647
+ plainName: "Spectral",
2648
+ technicalName: "Laplacian eigenvector layout",
2649
+ kind: "batch",
2650
+ maxDimensions: 2,
2651
+ reason: "The only engine that draws this arrangement.",
2652
+ options: m(re.zodOptionsSchema),
2653
+ honoursWeights: re.honoursWeights
2654
+ }, Mi = {
2655
+ engine: "planar",
2656
+ plainName: "Planar",
2657
+ technicalName: "Planar embedding",
2658
+ kind: "batch",
2659
+ maxDimensions: 2,
2660
+ reason: "The only engine that draws this arrangement.",
2661
+ options: m(se.zodOptionsSchema, w),
2662
+ honoursWeights: se.honoursWeights
2663
+ }, Ei = {
2664
+ engine: "bfs",
2665
+ plainName: "BFS Tree",
2666
+ technicalName: "Breadth-first tree layout",
2667
+ kind: "batch",
2668
+ maxDimensions: 2,
2669
+ reason: "The only engine that draws this arrangement. It orders each row by breadth-first arrival and does not reduce edge crossings between rows.",
2670
+ options: m(V.zodOptionsSchema),
2671
+ honoursWeights: V.honoursWeights
2672
+ }, Ii = {
2673
+ engine: "bipartite",
2674
+ plainName: "Bipartite",
2675
+ technicalName: "Bipartite layout",
2676
+ kind: "batch",
2677
+ maxDimensions: 2,
2678
+ reason: "The only engine that draws this arrangement.",
2679
+ options: m(Y.zodOptionsSchema),
2680
+ honoursWeights: Y.honoursWeights
2681
+ }, Ci = {
2682
+ engine: "multipartite",
2683
+ plainName: "Multipartite",
2684
+ technicalName: "Multipartite layout",
2685
+ kind: "batch",
2686
+ maxDimensions: 2,
2687
+ reason: "The only engine that draws this arrangement.",
2688
+ options: m(te.zodOptionsSchema),
2689
+ honoursWeights: te.honoursWeights
2690
+ }, Ri = {
2691
+ engine: "fixed",
2692
+ plainName: "Fixed",
2693
+ technicalName: "Fixed layout",
2694
+ kind: "batch",
2695
+ maxDimensions: 3,
2696
+ reason: "The only engine that draws this arrangement.",
2697
+ options: m(Z.zodOptionsSchema),
2698
+ honoursWeights: Z.honoursWeights
2699
+ }, ki = {
2700
+ engine: "random",
2701
+ plainName: "Random",
2702
+ technicalName: "Random layout",
2703
+ kind: "batch",
2704
+ maxDimensions: 3,
2705
+ reason: "The only engine that draws this arrangement.",
2706
+ options: m(ae.zodOptionsSchema, w),
2707
+ honoursWeights: ae.honoursWeights
2708
+ }, ye = [
2709
+ f(
2710
+ {
2711
+ id: "force",
2712
+ plainName: "Spread Out",
2713
+ technicalName: "Force-directed layout",
2714
+ description: "Pulls connected nodes together and pushes unconnected ones apart until the shape stops moving, in three dimensions.",
2715
+ family: "force",
2716
+ kind: "live",
2717
+ maxDimensions: 3,
2718
+ sizeRating: "any",
2719
+ structuralInputs: []
2720
+ },
2721
+ mi,
2722
+ [pi, gi, fi, yi, bi]
2723
+ ),
2724
+ f(
2725
+ {
2726
+ id: "force-2d",
2727
+ plainName: "Spread Out, Flat",
2728
+ technicalName: "Force-directed layout, two-dimensional",
2729
+ description: "The same pull and push as Spread Out, worked out on a single plane.",
2730
+ family: "force",
2731
+ kind: "batch",
2732
+ maxDimensions: 2,
2733
+ sizeRating: 2e3,
2734
+ structuralInputs: []
2735
+ },
2736
+ vi
2737
+ ),
2738
+ f(
2739
+ {
2740
+ id: "circular",
2741
+ plainName: "Ring",
2742
+ technicalName: "Circular layout",
2743
+ description: "Places every node on one circle, in the order the nodes were loaded.",
2744
+ family: "geometric",
2745
+ kind: "batch",
2746
+ maxDimensions: 3,
2747
+ sizeRating: "any",
2748
+ structuralInputs: []
2749
+ },
2750
+ xi
2751
+ ),
2752
+ f(
2753
+ {
2754
+ id: "radial",
2755
+ plainName: "Rings from a Node",
2756
+ technicalName: "Radial layout",
2757
+ description: "Puts one node at the centre and every other node on a ring by how many steps away it is, so the rings read outward as distance from that node.",
2758
+ family: "geometric",
2759
+ kind: "batch",
2760
+ maxDimensions: 2,
2761
+ sizeRating: "any",
2762
+ structuralInputs: []
2763
+ },
2764
+ Ni
2765
+ ),
2766
+ f(
2767
+ {
2768
+ id: "grid",
2769
+ plainName: "Grid",
2770
+ technicalName: "Grid layout",
2771
+ description: "Places nodes in evenly spaced rows and columns, in the order the nodes were loaded.",
2772
+ family: "geometric",
2773
+ kind: "batch",
2774
+ maxDimensions: 2,
2775
+ sizeRating: "any",
2776
+ structuralInputs: []
2777
+ },
2778
+ Di
2779
+ ),
2780
+ f(
2781
+ {
2782
+ id: "shell",
2783
+ plainName: "Concentric Rings",
2784
+ technicalName: "Shell layout",
2785
+ description: "Places each group of nodes on its own ring, around a shared centre.",
2786
+ family: "geometric",
2787
+ kind: "batch",
2788
+ maxDimensions: 2,
2789
+ sizeRating: "any",
2790
+ structuralInputs: ["partition"]
2791
+ },
2792
+ Si
2793
+ ),
2794
+ f(
2795
+ {
2796
+ id: "spiral",
2797
+ plainName: "Spiral",
2798
+ technicalName: "Archimedean spiral layout",
2799
+ description: "Places nodes one after another along a spiral winding out from the centre.",
2800
+ family: "geometric",
2801
+ kind: "batch",
2802
+ maxDimensions: 2,
2803
+ sizeRating: "any",
2804
+ structuralInputs: []
2805
+ },
2806
+ wi
2807
+ ),
2808
+ f(
2809
+ {
2810
+ id: "spectral",
2811
+ plainName: "Natural Grouping",
2812
+ technicalName: "Spectral layout",
2813
+ description: "Places nodes from the graph's own structure, so densely connected groups land near each other without any grouping being named.",
2814
+ family: "geometric",
2815
+ kind: "batch",
2816
+ maxDimensions: 2,
2817
+ sizeRating: 2e3,
2818
+ structuralInputs: []
2819
+ },
2820
+ Oi
2821
+ ),
2822
+ f(
2823
+ {
2824
+ id: "planar",
2825
+ plainName: "No Crossings",
2826
+ technicalName: "Planar embedding",
2827
+ description: "Places nodes so that no two edges cross, for the graphs where that is possible.",
2828
+ family: "geometric",
2829
+ kind: "batch",
2830
+ maxDimensions: 2,
2831
+ sizeRating: 2e3,
2832
+ structuralInputs: []
2833
+ },
2834
+ Mi
2835
+ ),
2836
+ f(
2837
+ {
2838
+ id: "random",
2839
+ plainName: "Scattered",
2840
+ technicalName: "Random layout",
2841
+ description: "Puts every node somewhere at random, which is where a live layout starts.",
2842
+ family: "geometric",
2843
+ kind: "batch",
2844
+ maxDimensions: 3,
2845
+ sizeRating: "any",
2846
+ structuralInputs: []
2847
+ },
2848
+ ki
2849
+ ),
2850
+ f(
2851
+ {
2852
+ id: "hierarchical",
2853
+ plainName: "Tree",
2854
+ technicalName: "Layered layout",
2855
+ description: "Puts nodes in rows by how many steps they are from a starting node, so the graph reads as a tree growing away from it.",
2856
+ family: "hierarchical",
2857
+ kind: "batch",
2858
+ maxDimensions: 2,
2859
+ sizeRating: "any",
2860
+ structuralInputs: ["node"]
2861
+ },
2862
+ Ei
2863
+ ),
2864
+ f(
2865
+ {
2866
+ id: "bipartite",
2867
+ plainName: "Two Columns",
2868
+ technicalName: "Bipartite layout",
2869
+ description: "Puts one named set of nodes in one column and everything else in a second, for a graph whose edges only ever run between the two.",
2870
+ family: "hierarchical",
2871
+ kind: "batch",
2872
+ maxDimensions: 2,
2873
+ sizeRating: "any",
2874
+ structuralInputs: ["partition"]
2875
+ },
2876
+ Ii
2877
+ ),
2878
+ f(
2879
+ {
2880
+ id: "layers",
2881
+ plainName: "Columns by Group",
2882
+ technicalName: "Multipartite layout",
2883
+ description: "Puts each group of nodes in its own column, in the order the groups are given.",
2884
+ family: "hierarchical",
2885
+ kind: "batch",
2886
+ maxDimensions: 2,
2887
+ sizeRating: "any",
2888
+ structuralInputs: ["partition"]
2889
+ },
2890
+ Ci
2891
+ ),
2892
+ f(
2893
+ {
2894
+ id: "fixed",
2895
+ plainName: "Keep Positions",
2896
+ technicalName: "Fixed layout",
2897
+ description: "Leaves every node exactly where the loaded data placed it.",
2898
+ family: "special",
2899
+ kind: "batch",
2900
+ maxDimensions: 3,
2901
+ sizeRating: "any",
2902
+ structuralInputs: []
2903
+ },
2904
+ Ri
2905
+ )
2906
+ ], zi = ye.map((n) => n.descriptor), Ui = [];
2907
+ function Hi(n) {
2908
+ return zi.find((e) => e.id === n) ?? ke(n)?.descriptor;
2909
+ }
2910
+ function Ki(n) {
2911
+ return ye.find((e) => e.descriptor.id === n);
2912
+ }
2913
+ function Vi(n) {
2914
+ const e = ye.find(
2915
+ (t) => t.implementations.some((s) => s.engine === n)
2916
+ );
2917
+ return e !== void 0 ? e.descriptor.id : ke(n)?.descriptor.id;
2918
+ }
2919
+ const b = ["deuteranopia", "protanopia", "tritanopia"], ve = Object.freeze([
2920
+ {
2921
+ id: "viridis",
2922
+ plainName: "Purple to Yellow",
2923
+ kind: "sequential",
2924
+ colors: ct,
2925
+ capacity: null,
2926
+ colorblindSafe: b
2927
+ },
2928
+ // Paul Tol, "Colour Schemes" (2021): YlOrBr, trimmed to the steps that stand off a light
2929
+ // background. The default for a measurement.
2930
+ {
2931
+ id: "ylorbr",
2932
+ plainName: "Orange to Brown",
2933
+ kind: "sequential",
2934
+ colors: dt,
2935
+ capacity: null,
2936
+ colorblindSafe: b
2937
+ },
2938
+ {
2939
+ id: "plasma",
2940
+ plainName: "Blue to Yellow",
2941
+ kind: "sequential",
2942
+ colors: ht,
2943
+ capacity: null,
2944
+ colorblindSafe: b
2945
+ },
2946
+ {
2947
+ id: "inferno",
2948
+ plainName: "Black to Yellow",
2949
+ kind: "sequential",
2950
+ colors: ut,
2951
+ capacity: null,
2952
+ colorblindSafe: b
2953
+ },
2954
+ {
2955
+ id: "blues",
2956
+ plainName: "Shades of Blue",
2957
+ kind: "sequential",
2958
+ colors: mt,
2959
+ capacity: null,
2960
+ colorblindSafe: b
2961
+ },
2962
+ {
2963
+ id: "greens",
2964
+ plainName: "Shades of Green",
2965
+ kind: "sequential",
2966
+ colors: pt,
2967
+ capacity: null,
2968
+ colorblindSafe: []
2969
+ },
2970
+ {
2971
+ id: "oranges",
2972
+ plainName: "Shades of Orange",
2973
+ kind: "sequential",
2974
+ colors: gt,
2975
+ capacity: null,
2976
+ colorblindSafe: []
2977
+ },
2978
+ {
2979
+ id: "okabe-ito",
2980
+ plainName: "Eight Distinct Colours",
2981
+ kind: "categorical",
2982
+ colors: Se,
2983
+ capacity: Se.length,
2984
+ colorblindSafe: b
2985
+ },
2986
+ {
2987
+ id: "tol-vibrant",
2988
+ plainName: "Seven Bright Colours",
2989
+ kind: "categorical",
2990
+ colors: Ne,
2991
+ capacity: Ne.length,
2992
+ colorblindSafe: b
2993
+ },
2994
+ {
2995
+ id: "tol-muted",
2996
+ plainName: "Nine Soft Colours",
2997
+ kind: "categorical",
2998
+ colors: De,
2999
+ capacity: De.length,
3000
+ colorblindSafe: b
3001
+ },
3002
+ {
3003
+ id: "pastel",
3004
+ plainName: "Eight Pale Colours",
3005
+ kind: "categorical",
3006
+ colors: we,
3007
+ capacity: we.length,
3008
+ colorblindSafe: b
3009
+ },
3010
+ {
3011
+ id: "carbon",
3012
+ plainName: "Five Enterprise Colours",
3013
+ kind: "categorical",
3014
+ colors: Oe,
3015
+ capacity: Oe.length,
3016
+ colorblindSafe: []
3017
+ },
3018
+ {
3019
+ id: "purple-green",
3020
+ plainName: "Purple to Green",
3021
+ kind: "diverging",
3022
+ colors: ft,
3023
+ capacity: null,
3024
+ colorblindSafe: b
3025
+ },
3026
+ {
3027
+ id: "blue-orange",
3028
+ plainName: "Blue to Orange",
3029
+ kind: "diverging",
3030
+ colors: bt,
3031
+ capacity: null,
3032
+ colorblindSafe: b
3033
+ },
3034
+ {
3035
+ id: "red-blue",
3036
+ plainName: "Red to Blue",
3037
+ kind: "diverging",
3038
+ colors: yt,
3039
+ capacity: null,
3040
+ colorblindSafe: []
3041
+ },
3042
+ {
3043
+ id: "blue-highlight",
3044
+ plainName: "Blue Highlight",
3045
+ kind: "categorical",
3046
+ colors: [Me.highlighted, Me.muted],
3047
+ capacity: 2,
3048
+ colorblindSafe: b
3049
+ },
3050
+ {
3051
+ id: "green-highlight",
3052
+ plainName: "Green Highlight",
3053
+ kind: "categorical",
3054
+ colors: [Ee.highlighted, Ee.muted],
3055
+ capacity: 2,
3056
+ colorblindSafe: b
3057
+ },
3058
+ {
3059
+ id: "orange-highlight",
3060
+ plainName: "Orange Highlight",
3061
+ kind: "categorical",
3062
+ colors: [Ie.highlighted, Ie.muted],
3063
+ capacity: 2,
3064
+ colorblindSafe: b
3065
+ }
3066
+ ]);
3067
+ function Yi(n) {
3068
+ return ve.find((e) => e.id === n) ?? Xe(n);
3069
+ }
3070
+ function $i(n) {
3071
+ const e = ve.filter((s) => s.kind === n), t = ze().filter((s) => s.kind === n);
3072
+ return t.length === 0 ? e : [...e, ...t];
3073
+ }
3074
+ function Ji() {
3075
+ return [...ve, ...ze()].map((n) => String(n.id));
3076
+ }
3077
+ const x = {
3078
+ name: "reverse",
3079
+ plainName: "Reverse",
3080
+ technicalName: "reverse",
3081
+ type: "boolean",
3082
+ default: !1,
3083
+ description: "Send the smallest value to the far end of the range instead of the near end."
3084
+ }, R = {
3085
+ name: "midpoint",
3086
+ plainName: "Midpoint",
3087
+ technicalName: "midpoint",
3088
+ type: "number",
3089
+ description: "The value that lands on the middle colour of a diverging palette."
3090
+ };
3091
+ function Re(n, e, t) {
3092
+ return {
3093
+ name: "bins",
3094
+ plainName: n,
3095
+ technicalName: "bins",
3096
+ type: "integer",
3097
+ default: t,
3098
+ min: 2,
3099
+ max: 20,
3100
+ step: 1,
3101
+ description: e
3102
+ };
3103
+ }
3104
+ const Pe = [
3105
+ {
3106
+ name: "linear",
3107
+ plainName: "Even Steps",
3108
+ domainKind: "numeric",
3109
+ options: [x, R]
3110
+ },
3111
+ {
3112
+ name: "log",
3113
+ plainName: "By Order of Magnitude",
3114
+ domainKind: "numeric",
3115
+ options: [x, R]
3116
+ },
3117
+ {
3118
+ name: "neglog10",
3119
+ plainName: "By Significance",
3120
+ domainKind: "numeric",
3121
+ options: [x, R]
3122
+ },
3123
+ {
3124
+ name: "sqrt",
3125
+ plainName: "By Area",
3126
+ domainKind: "numeric",
3127
+ options: [x, R]
3128
+ },
3129
+ {
3130
+ name: "pow",
3131
+ plainName: "Curved",
3132
+ domainKind: "numeric",
3133
+ options: [
3134
+ {
3135
+ name: "exponent",
3136
+ plainName: "Exponent",
3137
+ technicalName: "exponent",
3138
+ type: "number",
3139
+ default: 1,
3140
+ min: 0.1,
3141
+ max: 10,
3142
+ step: 0.1,
3143
+ description: "How hard the curve bends. Below one spreads the small values out; above one spreads the large ones out."
3144
+ },
3145
+ x,
3146
+ R
3147
+ ]
3148
+ },
3149
+ {
3150
+ name: "bins",
3151
+ plainName: "Equal Ranges",
3152
+ domainKind: "numeric",
3153
+ options: [
3154
+ Re("Groups", "How many equally wide ranges to cut the values into.", 5),
3155
+ x
3156
+ ]
3157
+ },
3158
+ {
3159
+ name: "quantile",
3160
+ plainName: "Equal Counts",
3161
+ domainKind: "numeric",
3162
+ options: [
3163
+ Re("Groups", "How many groups of equal size to sort the values into.", 4),
3164
+ x
3165
+ ]
3166
+ },
3167
+ {
3168
+ name: "ordinal",
3169
+ plainName: "One Colour per Value",
3170
+ domainKind: "categorical",
3171
+ options: [x]
3172
+ },
3173
+ {
3174
+ name: "passthrough",
3175
+ plainName: "Use the Value As It Is",
3176
+ domainKind: "categorical",
3177
+ options: []
3178
+ }
3179
+ ];
3180
+ function Zi(n) {
3181
+ return Pe.find((e) => e.name === n);
3182
+ }
3183
+ function Qi(n) {
3184
+ return Pe.filter((e) => e.domainKind === n);
3185
+ }
3186
+ export {
3187
+ K as A,
3188
+ V as B,
3189
+ $ as C,
3190
+ J as D,
3191
+ Q as F,
3192
+ X as G,
3193
+ ee as K,
3194
+ zi as L,
3195
+ te as M,
3196
+ ie as N,
3197
+ ve as P,
3198
+ ae as R,
3199
+ Pe as S,
3200
+ Ui as U,
3201
+ ye as a,
3202
+ Ki as b,
3203
+ Vi as c,
3204
+ $i as d,
3205
+ Qi as e,
3206
+ le as f,
3207
+ oe as g,
3208
+ de as h,
3209
+ ce as i,
3210
+ se as j,
3211
+ Ji as k,
3212
+ Hi as l,
3213
+ re as m,
3214
+ Y as n,
3215
+ Z as o,
3216
+ Yi as p,
3217
+ ne as q,
3218
+ be as r,
3219
+ Zi as s
3220
+ };