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