@graphty/graphty-element 2.6.1 → 3.0.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.js +53 -54
- package/dist/chunks/{AiManager-4iQpsJW1.js → AiManager-Bhh0rR_p.js} +706 -655
- package/dist/chunks/GraphSession-DPoTaT3b.js +21175 -0
- package/dist/chunks/{GraphtyLogger-B_O67a6c.js → GraphtyLogger-BtcBJQPL.js} +1 -1
- package/dist/chunks/{VoiceInputAdapter-Cc6mHXTI.js → VoiceInputAdapter-CdLsJ_nG.js} +1 -1
- package/dist/chunks/{XRPivotCameraController-BLa89LXn.js → XRPivotCameraController-CiLJ9Gz6.js} +2 -2
- package/dist/chunks/algorithms-qij74zEN.js +6811 -0
- package/dist/chunks/{capability-check-Am2zliFj.js → capability-check-BbgTejS3.js} +1 -1
- package/dist/chunks/definePalette-BYt2Llxs.js +1333 -0
- package/dist/chunks/fields-5uVC1Pll.js +4999 -0
- package/dist/chunks/{format-detection-BHwrAVzW.js → format-detection-FKaDMshR.js} +1 -1
- package/dist/chunks/{index-BkBLbvui.js → index-Cc_6D9hV.js} +6034 -7127
- package/dist/chunks/interpolation-Dk206AhZ.js +105 -0
- package/dist/chunks/paletteRegistry-De3CGdst.js +357 -0
- package/dist/chunks/parse-SVp77JbE.js +669 -0
- package/dist/chunks/{pluginRegistry-Bs8bEkz9.js → pluginRegistry-Ddfl6Mv2.js} +27 -24
- package/dist/chunks/{registry-BdGvyZou.js → registry-CgMqldp4.js} +1 -1
- package/dist/chunks/{DataSource-BL2UzPff.js → sources-BbAJCDIH.js} +279 -108
- package/dist/commands.d.ts +128 -19
- package/dist/commands.js +49 -1
- package/dist/custom-elements.json +1 -1
- package/dist/extend.d.ts +10 -2
- package/dist/extend.js +64 -57
- package/dist/graphty-catalog.json +6 -3
- package/dist/graphty.bundle.js +267177 -240648
- package/dist/graphty.js +84 -78
- package/dist/index.d.ts +4 -0
- package/dist/logging.js +2 -2
- package/dist/schema.js +70 -71
- package/dist/session.d.ts +5 -6
- package/dist/session.js +40 -86
- package/dist/src/Edge.d.ts +31 -67
- package/dist/src/Graph.d.ts +335 -77
- package/dist/src/Node.d.ts +36 -3
- package/dist/src/NodeBehavior.d.ts +28 -0
- package/dist/src/Styles.d.ts +15 -4
- package/dist/src/acceleration/AccelerationController.d.ts +8 -0
- package/dist/src/acceleration/narrow.d.ts +9 -1
- package/dist/src/acceleration/types.d.ts +10 -0
- package/dist/src/ai/AiController.d.ts +12 -0
- package/dist/src/ai/AiManager.d.ts +7 -0
- package/dist/src/ai/commands/AlgorithmCommands.d.ts +1 -1
- package/dist/src/ai/commands/LayoutCommands.d.ts +1 -1
- package/dist/src/ai/commands/StyleCommands.d.ts +1 -1
- package/dist/src/ai/commands/types.d.ts +20 -1
- package/dist/src/algorithms/Algorithm.d.ts +21 -4
- package/dist/src/algorithms/BFSAlgorithm.d.ts +0 -9
- package/dist/src/algorithms/BellmanFordAlgorithm.d.ts +0 -14
- package/dist/src/algorithms/DFSAlgorithm.d.ts +1 -1
- package/dist/src/algorithms/FloydWarshallAlgorithm.d.ts +5 -0
- package/dist/src/algorithms/GirvanNewmanAlgorithm.d.ts +5 -0
- package/dist/src/algorithms/LeidenAlgorithm.d.ts +5 -0
- package/dist/src/algorithms/PageRankAlgorithm.d.ts +1 -1
- package/dist/src/algorithms/StronglyConnectedComponentsAlgorithm.d.ts +1 -1
- package/dist/src/algorithms/metrics/fields.d.ts +23 -1
- package/dist/src/algorithms/utils/graphUtils.d.ts +13 -1
- package/dist/src/catalog/paletteRegistry.d.ts +4 -4
- package/dist/src/catalog/registry.d.ts +3 -2
- package/dist/src/catalog/types.d.ts +18 -1
- package/dist/src/config/GraphStyle.d.ts +1 -1
- package/dist/src/config/StyleTemplate.d.ts +2 -2
- package/dist/src/config/xr-config-schema.d.ts +4 -4
- package/dist/src/data/CSVDataSource.d.ts +77 -22
- package/dist/src/data/ErrorAggregator.d.ts +5 -0
- package/dist/src/data/GEXFDataSource.d.ts +12 -61
- package/dist/src/data/GraphMLDataSource.d.ts +3 -44
- package/dist/src/data/GraphStore.d.ts +322 -15
- package/dist/src/data/JsonDataSource.d.ts +43 -1
- package/dist/src/data/graph-io-import.d.ts +89 -0
- package/dist/src/data/graph-io-records.d.ts +64 -0
- package/dist/src/data/lane.d.ts +23 -0
- package/dist/src/data/positions.d.ts +13 -0
- package/dist/src/data/seedPosition.d.ts +16 -0
- package/dist/src/errors/GraphtyError.d.ts +3 -1
- package/dist/src/errors/codes.d.ts +23 -0
- package/dist/src/events.d.ts +12 -0
- package/dist/src/graphty-element.d.ts +149 -54
- package/dist/src/input/types.d.ts +2 -0
- package/dist/src/layout/D3GraphLayoutEngine.d.ts +17 -3
- package/dist/src/layout/FixedLayoutEngine.d.ts +20 -4
- package/dist/src/layout/KamadaKawaiLayoutEngine.d.ts +6 -0
- package/dist/src/layout/LayoutEngine.d.ts +214 -116
- package/dist/src/layout/NGraphLayoutEngine.d.ts +10 -3
- package/dist/src/layout/SimulationLayoutEngine.d.ts +8 -3
- package/dist/src/managers/AlgorithmManager.d.ts +24 -5
- package/dist/src/managers/DataManager.d.ts +258 -181
- package/dist/src/managers/EventManager.d.ts +5 -2
- package/dist/src/managers/GraphContext.d.ts +7 -0
- package/dist/src/managers/InputManager.d.ts +11 -0
- package/dist/src/managers/LayoutManager.d.ts +129 -50
- package/dist/src/managers/RenderManager.d.ts +14 -1
- package/dist/src/managers/UpdateManager.d.ts +20 -0
- package/dist/src/screenshot/ScreenshotCapture.d.ts +1 -1
- package/dist/src/session/GraphSession.d.ts +83 -6
- package/dist/src/session/commands/algo.d.ts +169 -0
- package/dist/src/session/commands/config.d.ts +45 -0
- package/dist/src/session/commands/data.d.ts +178 -0
- package/dist/src/session/commands/doors.d.ts +93 -0
- package/dist/src/session/commands/index.d.ts +20 -0
- package/dist/src/session/commands/layout.d.ts +104 -0
- package/dist/src/session/commands/positions.d.ts +30 -0
- package/dist/src/session/commands/sets.d.ts +113 -0
- package/dist/src/session/commands/style.d.ts +92 -0
- package/dist/src/session/commands/view.d.ts +57 -0
- package/dist/src/session/commands/visibility.d.ts +41 -0
- package/dist/src/session/data.d.ts +131 -4
- package/dist/src/session/index.d.ts +1 -1
- package/dist/src/session/planning.d.ts +25 -8
- package/dist/src/session/project/Dispatcher.d.ts +905 -0
- package/dist/src/session/project/History.d.ts +382 -0
- package/dist/src/session/project/arrangement.d.ts +247 -0
- package/dist/src/session/project/derive.d.ts +132 -0
- package/dist/src/session/project/digest.d.ts +33 -0
- package/dist/src/session/project/draft.d.ts +194 -0
- package/dist/src/session/project/graphOps.d.ts +304 -0
- package/dist/src/session/project/ingest.d.ts +364 -0
- package/dist/src/session/project/state.d.ts +145 -0
- package/dist/src/session/project/strict.d.ts +68 -0
- package/dist/src/session/results/RunResult.d.ts +48 -0
- package/dist/src/session/results/statistics.d.ts +20 -0
- package/dist/src/session/runs/Run.d.ts +80 -4
- package/dist/src/session/runs/RunsApi.d.ts +23 -6
- package/dist/src/session/runs/types.d.ts +25 -6
- package/dist/src/session/scope/ElementMask.d.ts +14 -0
- package/dist/src/session/scope/ScopeApi.d.ts +3 -22
- package/dist/src/session/scope/spaces.d.ts +29 -0
- package/dist/src/session/sealed.d.ts +22 -0
- package/dist/src/session/selection/SelectionApi.d.ts +14 -4
- package/dist/src/session/sets/SetsApi.d.ts +13 -5
- package/dist/src/session/sets/store.d.ts +54 -53
- package/dist/src/session/sets/types.d.ts +5 -2
- package/dist/src/session/styles/Layer.d.ts +5 -0
- package/dist/src/session/styles/StylesApi.d.ts +68 -17
- package/dist/src/session/styles/autoApply.d.ts +64 -53
- package/dist/src/session/styles/index.d.ts +3 -3
- package/dist/src/session/styles/predicate.d.ts +7 -0
- package/dist/src/session/styles/repaint.d.ts +16 -1
- package/dist/src/session/styles/sources.d.ts +1 -1
- package/dist/src/session/types.d.ts +625 -54
- package/dist/src/session/visibility/VisibilityApi.d.ts +38 -18
- package/dist/src/session/visibility/filter.d.ts +10 -0
- package/dist/src/simple/defineAlgorithm.d.ts +28 -0
- package/dist/src/simple/defineLayout.d.ts +35 -0
- package/dist/src/simple/defineLogDestination.d.ts +31 -0
- package/dist/src/simple/definePalette.d.ts +26 -0
- package/dist/src/simple/definition.d.ts +106 -0
- package/dist/src/simple/options.d.ts +33 -0
- package/dist/src/simple/source.d.ts +49 -0
- package/dist/src/simple/types.d.ts +366 -0
- package/dist/src/simple/view.d.ts +107 -0
- package/dist/webgpu.js +2 -2
- package/package.json +10 -12
- package/dist/chunks/GraphSession-BhuHSXIo.js +0 -12819
- package/dist/chunks/GraphStyle-Cwr55SAE.js +0 -65
- package/dist/chunks/algorithms-BJ6DQMOe.js +0 -3777
- package/dist/chunks/detect-fyuVnlCT.js +0 -88
- package/dist/chunks/interpolation-DY-PNpqX.js +0 -43
- package/dist/chunks/optionsFromZod-CKMYSwTz.js +0 -3636
- package/dist/chunks/paletteRegistry-BCFSwJGK.js +0 -1196
- package/dist/chunks/parse-BMTqt4SS.js +0 -3658
- package/dist/src/data/csv-variant-detection.d.ts +0 -29
- package/dist/src/data/ingest.d.ts +0 -104
|
@@ -1,1196 +0,0 @@
|
|
|
1
|
-
import { c as E, G as g, S as _ } from "./pluginRegistry-Bs8bEkz9.js";
|
|
2
|
-
import { a as F, b as D, d as k } from "./types-DFchv4Ny.js";
|
|
3
|
-
import { remapArray as S, INVALID_INDEX as w, maskTest as v } from "@graphty/graph-format";
|
|
4
|
-
import { z as O } from "zod/v4";
|
|
5
|
-
import { G as $ } from "./GraphtyLogger-B_O67a6c.js";
|
|
6
|
-
import { c as L } from "./common-DWNKjpH_.js";
|
|
7
|
-
const f = E({
|
|
8
|
-
kind: "format",
|
|
9
|
-
idOf: (s) => s.descriptor.id,
|
|
10
|
-
descriptorOf: (s) => s.descriptor,
|
|
11
|
-
implementationOf: (s) => s.descriptor,
|
|
12
|
-
builtInIds: () => F
|
|
13
|
-
});
|
|
14
|
-
function rt(s, t) {
|
|
15
|
-
f.register(s, t);
|
|
16
|
-
}
|
|
17
|
-
function nt() {
|
|
18
|
-
return f.entries();
|
|
19
|
-
}
|
|
20
|
-
function R() {
|
|
21
|
-
return f.descriptors();
|
|
22
|
-
}
|
|
23
|
-
function M(s) {
|
|
24
|
-
return f.byId(s);
|
|
25
|
-
}
|
|
26
|
-
function ot() {
|
|
27
|
-
f.clearForTesting();
|
|
28
|
-
}
|
|
29
|
-
const j = {
|
|
30
|
-
"edge-list": "Edge List",
|
|
31
|
-
"node-list": "Node List",
|
|
32
|
-
"adjacency-list": "Adjacency List",
|
|
33
|
-
neo4j: "Neo4j Export",
|
|
34
|
-
gephi: "Gephi Export",
|
|
35
|
-
cytoscape: "Cytoscape Export",
|
|
36
|
-
generic: "Generic"
|
|
37
|
-
}, z = [
|
|
38
|
-
{
|
|
39
|
-
name: "delimiter",
|
|
40
|
-
plainName: "Column Separator",
|
|
41
|
-
technicalName: "delimiter",
|
|
42
|
-
type: "string",
|
|
43
|
-
description: "The character between one column and the next. Worked out from the first line (comma, tab, semicolon or pipe) when it is not set."
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: "variant",
|
|
47
|
-
plainName: "File Shape",
|
|
48
|
-
technicalName: "variant",
|
|
49
|
-
type: "enum",
|
|
50
|
-
values: Object.entries(j).map(([s, t]) => ({ value: s, label: t })),
|
|
51
|
-
description: "Which CSV shape to read. Worked out from the header row when it is not set."
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: "idColumn",
|
|
55
|
-
plainName: "Node Id Column",
|
|
56
|
-
technicalName: "idColumn",
|
|
57
|
-
type: "string",
|
|
58
|
-
description: "The column holding each node's identity, when the file lists nodes."
|
|
59
|
-
}
|
|
60
|
-
], h = [
|
|
61
|
-
{
|
|
62
|
-
name: "edgeSource",
|
|
63
|
-
plainName: "Edge Start Field",
|
|
64
|
-
technicalName: "edgeSource",
|
|
65
|
-
type: "string",
|
|
66
|
-
description: "Where to find the node an edge starts at. Left unset, the element looks for source, then src, then from."
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: "edgeTarget",
|
|
70
|
-
plainName: "Edge End Field",
|
|
71
|
-
technicalName: "edgeTarget",
|
|
72
|
-
type: "string",
|
|
73
|
-
description: "Where to find the node an edge ends at. Left unset, the element looks for target, then dst, then to."
|
|
74
|
-
}
|
|
75
|
-
], B = [
|
|
76
|
-
{
|
|
77
|
-
name: "nodeIdPath",
|
|
78
|
-
plainName: "Node Id Field",
|
|
79
|
-
technicalName: "nodeIdPath",
|
|
80
|
-
type: "string",
|
|
81
|
-
description: "An expression selecting each node's identity out of the node record."
|
|
82
|
-
}
|
|
83
|
-
], T = [
|
|
84
|
-
{
|
|
85
|
-
id: "json",
|
|
86
|
-
plainName: "JSON",
|
|
87
|
-
extensions: [".json"],
|
|
88
|
-
mimeTypes: ["application/json"],
|
|
89
|
-
canImport: !0,
|
|
90
|
-
canExport: !1,
|
|
91
|
-
options: [...B, ...h]
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
id: "csv",
|
|
95
|
-
plainName: "CSV",
|
|
96
|
-
extensions: [".csv", ".tsv", ".tab", ".edges", ".edgelist"],
|
|
97
|
-
mimeTypes: ["text/csv", "text/tab-separated-values", "text/plain"],
|
|
98
|
-
canImport: !0,
|
|
99
|
-
canExport: !1,
|
|
100
|
-
options: [...z, ...h]
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: "graphml",
|
|
104
|
-
plainName: "GraphML",
|
|
105
|
-
extensions: [".graphml", ".xml"],
|
|
106
|
-
mimeTypes: ["application/graphml+xml", "application/xml", "text/xml"],
|
|
107
|
-
canImport: !0,
|
|
108
|
-
canExport: !1,
|
|
109
|
-
options: h
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
id: "gexf",
|
|
113
|
-
plainName: "GEXF",
|
|
114
|
-
// ".xml" is claimed here as well as by GraphML because both formats are XML and both are
|
|
115
|
-
// routinely saved under the generic extension. Two claimants is what lets detection ask
|
|
116
|
-
// each one's content sniffer which of them the file actually is, instead of a private
|
|
117
|
-
// branch inside the detector hard-coding the two namespace strings -- which is the same
|
|
118
|
-
// route a third party's XML dialect now takes.
|
|
119
|
-
extensions: [".gexf", ".xml"],
|
|
120
|
-
mimeTypes: ["application/gexf+xml", "application/xml", "text/xml"],
|
|
121
|
-
canImport: !0,
|
|
122
|
-
canExport: !1,
|
|
123
|
-
options: h
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
id: "gml",
|
|
127
|
-
plainName: "GML",
|
|
128
|
-
extensions: [".gml"],
|
|
129
|
-
mimeTypes: ["text/plain"],
|
|
130
|
-
canImport: !0,
|
|
131
|
-
canExport: !1,
|
|
132
|
-
options: h
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
id: "dot",
|
|
136
|
-
plainName: "DOT",
|
|
137
|
-
extensions: [".dot", ".gv"],
|
|
138
|
-
mimeTypes: ["text/vnd.graphviz", "text/plain"],
|
|
139
|
-
canImport: !0,
|
|
140
|
-
canExport: !1,
|
|
141
|
-
options: h
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
id: "pajek",
|
|
145
|
-
plainName: "Pajek NET",
|
|
146
|
-
extensions: [".net", ".paj"],
|
|
147
|
-
mimeTypes: ["text/plain"],
|
|
148
|
-
canImport: !0,
|
|
149
|
-
canExport: !1,
|
|
150
|
-
options: h
|
|
151
|
-
}
|
|
152
|
-
], at = [
|
|
153
|
-
{
|
|
154
|
-
id: "sif",
|
|
155
|
-
reason: "No data source reads the Cytoscape simple interaction format. The name is deprecated and is removed at the next major release unless a reader lands."
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
id: "cx2",
|
|
159
|
-
reason: "No data source reads the Cytoscape Exchange format. The name is deprecated and is removed at the next major release unless a reader lands."
|
|
160
|
-
}
|
|
161
|
-
];
|
|
162
|
-
function lt(s) {
|
|
163
|
-
return T.find((t) => t.id === s) ?? M(s)?.descriptor;
|
|
164
|
-
}
|
|
165
|
-
function ct(s) {
|
|
166
|
-
const t = s.toLowerCase();
|
|
167
|
-
return [...T, ...R()].filter(
|
|
168
|
-
(e) => e.extensions.includes(t)
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
const y = E({
|
|
172
|
-
kind: "layout",
|
|
173
|
-
idOf: (s) => s.descriptor.id,
|
|
174
|
-
descriptorOf: (s) => s.descriptor,
|
|
175
|
-
implementationOf: (s) => s.descriptor,
|
|
176
|
-
builtInIds: () => D
|
|
177
|
-
});
|
|
178
|
-
function C(s, t) {
|
|
179
|
-
y.register(s, t);
|
|
180
|
-
}
|
|
181
|
-
function ht() {
|
|
182
|
-
return y.descriptors();
|
|
183
|
-
}
|
|
184
|
-
function dt(s) {
|
|
185
|
-
return y.byId(s);
|
|
186
|
-
}
|
|
187
|
-
function ut() {
|
|
188
|
-
y.clearForTesting();
|
|
189
|
-
}
|
|
190
|
-
const a = 3, W = 1024;
|
|
191
|
-
function p(s, t) {
|
|
192
|
-
if (!Number.isInteger(s) || s < 0)
|
|
193
|
-
throw new RangeError(`ElementPositions: ${t} must be a non-negative integer, found ${String(s)}`);
|
|
194
|
-
return s;
|
|
195
|
-
}
|
|
196
|
-
function d(s) {
|
|
197
|
-
return Number.isFinite(Math.fround(s));
|
|
198
|
-
}
|
|
199
|
-
class x {
|
|
200
|
-
/**
|
|
201
|
-
* Allocate the backing array, every row unplaced and unpinned.
|
|
202
|
-
* @param capacity - rows to reserve before the first growth; a non-negative integer
|
|
203
|
-
*/
|
|
204
|
-
constructor(t = W) {
|
|
205
|
-
this.components = a, this.rows = 0, p(t, "capacity");
|
|
206
|
-
const e = Math.max(1, t);
|
|
207
|
-
this.array = new Float32Array(a * e), this.array.fill(Number.NaN), this.pins = new Uint8Array(e);
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Rows the backing array can hold without reallocating.
|
|
211
|
-
* @returns the current capacity in rows
|
|
212
|
-
*/
|
|
213
|
-
get capacity() {
|
|
214
|
-
return this.array.length / a;
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Rows currently in use. Equals the last snapshot's nodeCount.
|
|
218
|
-
* @returns the live row count
|
|
219
|
-
*/
|
|
220
|
-
get count() {
|
|
221
|
-
return this.rows;
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* How many live rows carry real coordinates, by the same test {@link ElementPositions.isPlaced}
|
|
225
|
-
* applies one row at a time.
|
|
226
|
-
*
|
|
227
|
-
* The question behind it is "did the file that loaded this graph place every node?", which is
|
|
228
|
-
* what decides whether the Keep Positions arrangement can be used at all: a fixed layout over a
|
|
229
|
-
* graph where half the rows are NaN draws half a picture. Compare it against
|
|
230
|
-
* {@link ElementPositions.count} to answer that -- equal means every node has somewhere to go.
|
|
231
|
-
*
|
|
232
|
-
* COUNTED ON EVERY READ, and it has to be. The array is lent to the snapshot as a mutable
|
|
233
|
-
* column, so a layout, a drag and a GPU readback all write coordinates without passing through
|
|
234
|
-
* this class; a counter kept up to date by `write()` would miss every one of those and would be
|
|
235
|
-
* most wrong exactly while a layout was running, which is when somebody is asking. The scan is
|
|
236
|
-
* one f32 read per node and touches only the x lane.
|
|
237
|
-
*
|
|
238
|
-
* This is deliberately NOT on `GraphStatistics`. Those are memoised against the snapshot they
|
|
239
|
-
* were computed from, and how many nodes carry a coordinate changes while a layout runs WITHIN
|
|
240
|
-
* one snapshot, so a cached answer would be stale in the one moment it mattered.
|
|
241
|
-
* @returns how many rows in `[0, count)` carry a storable x
|
|
242
|
-
*/
|
|
243
|
-
get placedCount() {
|
|
244
|
-
let t = 0;
|
|
245
|
-
for (let e = 0; e < a * this.rows; e += a)
|
|
246
|
-
this.hasStorableX(e) && t++;
|
|
247
|
-
return t;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* The exact view to hand to `snapshot.nodes.set("position", ...)`.
|
|
251
|
-
*
|
|
252
|
-
* Bounded by the LIVE ROW COUNT, like every other row accessor here. `subarray` CLAMPS rather
|
|
253
|
-
* than throwing, so without a check a caller that forgot to `grow()` first would get a short
|
|
254
|
-
* array: E_COLUMN_LENGTH from inside graph-format if it is attached, and a layout that silently
|
|
255
|
-
* places only the first `count` nodes if it is not. Bounding by the CAPACITY instead would hand
|
|
256
|
-
* out a writable window over the spare rows -- rows this class reports unplaced, whose NaN fill
|
|
257
|
-
* every later `grow()` and `remap()` depends on, and whose contents the next growth erases.
|
|
258
|
-
* @param nodeCount - the snapshot's node count
|
|
259
|
-
* @returns a subarray of length `3 * nodeCount` over the same buffer
|
|
260
|
-
* @throws RangeError when `nodeCount` is not a non-negative integer, or exceeds the live count
|
|
261
|
-
*/
|
|
262
|
-
view(t) {
|
|
263
|
-
if (p(t, "nodeCount"), t > this.rows)
|
|
264
|
-
throw new RangeError(
|
|
265
|
-
`ElementPositions: view(${t}) needs ${t} live rows but only ${this.rows} exist; call grow(${t}) first`
|
|
266
|
-
);
|
|
267
|
-
return this.array.subarray(0, a * t);
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Prefix-stable growth: existing rows keep their coordinates, EVERY row at or above the new
|
|
271
|
-
* count is unplaced -- including the spare capacity, which a later grow() will hand out.
|
|
272
|
-
*
|
|
273
|
-
* Past the capacity this REPLACES the array object; see the staleness contract on the class.
|
|
274
|
-
* @param nodeCount - the new row count; may be smaller than the current one (append-only
|
|
275
|
-
* builders never shrink, but a caller that does gets a truncation, not a throw). `grow(0)`
|
|
276
|
-
* is how a dataset is discarded while the reserve is kept
|
|
277
|
-
* @throws RangeError when `nodeCount` is not a non-negative integer
|
|
278
|
-
*/
|
|
279
|
-
grow(t) {
|
|
280
|
-
if (p(t, "nodeCount"), t > this.capacity) {
|
|
281
|
-
const e = Math.max(this.capacity * 2, t), i = new Float32Array(a * e);
|
|
282
|
-
i.set(this.array), i.fill(Number.NaN, a * this.rows), this.array = i;
|
|
283
|
-
const r = new Uint8Array(e);
|
|
284
|
-
r.set(this.pins), r.fill(0, this.rows), this.pins = r;
|
|
285
|
-
} else t > this.rows ? (this.array.fill(Number.NaN, a * this.rows, a * t), this.pins.fill(0, this.rows, t)) : t < this.rows && (this.array.fill(Number.NaN, a * t, a * this.rows), this.pins.fill(0, t, this.rows));
|
|
286
|
-
this.rows = t;
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Apply a freeze report's `nodeRemap` (previous index space -> new index or INVALID_INDEX).
|
|
290
|
-
*
|
|
291
|
-
* `remapArray` ALLOCATES, so this replaces the array object and collapses the capacity to
|
|
292
|
-
* exactly `nodeCount` (PLAN DECISION 3: a removal is rare and never per frame, so paying one
|
|
293
|
-
* reallocation on the next growth is cheaper than copying the remapped rows a second time into
|
|
294
|
-
* a re-reserved array). Every holder of the old object is stale afterwards, which is why
|
|
295
|
-
* GraphStore re-attaches the column on every freeze and emits `snapshot-replaced`.
|
|
296
|
-
* @param nodeRemap - the report's nodeRemap
|
|
297
|
-
* @param nodeCount - the new snapshot's node count
|
|
298
|
-
* @throws RangeError when `nodeCount` is not a non-negative integer
|
|
299
|
-
*/
|
|
300
|
-
remap(t, e) {
|
|
301
|
-
p(e, "nodeCount"), this.array = S(this.array, t, e, Number.NaN, a), this.pins = S(this.pins, t, e, 0, 1), this.rows = e;
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* Whether the reader has fixed this row's node in place.
|
|
305
|
-
*
|
|
306
|
-
* A row outside the live count is UNPINNED, never pinned, for the same reason
|
|
307
|
-
* {@link ElementPositions.isPlaced} answers false there: a node with no row is a node the
|
|
308
|
-
* graph builder has not taken, and answering "pinned" for one would freeze a node that does
|
|
309
|
-
* not exist yet and that nothing could release.
|
|
310
|
-
* @param index - node index
|
|
311
|
-
* @returns true when the row is inside the live count AND carries a pin
|
|
312
|
-
*/
|
|
313
|
-
isPinned(t) {
|
|
314
|
-
return !Number.isInteger(t) || t < 0 || t >= this.rows ? !1 : this.pins[t] === 1;
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Record or release a pin, GROWING the lane to reach the row.
|
|
318
|
-
*
|
|
319
|
-
* It grows rather than throwing for the same reason `LayoutEngine.writeNodePosition` does: a
|
|
320
|
-
* node's index is handed out the moment the graph builder takes its record, but its row only
|
|
321
|
-
* appears when the graph is next frozen, and a reader who drags a node in between would
|
|
322
|
-
* otherwise have the pin thrown away in silence.
|
|
323
|
-
*
|
|
324
|
-
* It REFUSES rather than throwing for an index that is not a row number at all -- the
|
|
325
|
-
* `INVALID_INDEX` a record the builder would not take carries for its whole life, or a
|
|
326
|
-
* negative or fractional number. Pinning happens from a pointer gesture, and a throw on that
|
|
327
|
-
* path takes the frame with it.
|
|
328
|
-
* @param index - node index
|
|
329
|
-
* @param pinned - true to pin, false to release
|
|
330
|
-
* @returns true when the lane was written, false when the index names no row
|
|
331
|
-
*/
|
|
332
|
-
setPinned(t, e) {
|
|
333
|
-
return t === w || !Number.isInteger(t) || t < 0 ? !1 : (t >= this.rows && this.grow(t + 1), this.pins[t] = e ? 1 : 0, !0);
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* How many live rows are pinned.
|
|
337
|
-
* @returns the count of pinned rows in `[0, count)`
|
|
338
|
-
*/
|
|
339
|
-
get pinnedCount() {
|
|
340
|
-
let t = 0;
|
|
341
|
-
for (let e = 0; e < this.rows; e++)
|
|
342
|
-
this.pins[e] === 1 && t++;
|
|
343
|
-
return t;
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* The exact view to hand to `snapshot.nodes.set("graphty.pinned", ...)`.
|
|
347
|
-
*
|
|
348
|
-
* Bounded by the live row count, like {@link ElementPositions.view} and for the same reasons.
|
|
349
|
-
* @param nodeCount - the snapshot's node count
|
|
350
|
-
* @returns a subarray of length `nodeCount` over the same buffer
|
|
351
|
-
* @throws RangeError when `nodeCount` is not a non-negative integer, or exceeds the live count
|
|
352
|
-
*/
|
|
353
|
-
pinnedView(t) {
|
|
354
|
-
if (p(t, "nodeCount"), t > this.rows)
|
|
355
|
-
throw new RangeError(
|
|
356
|
-
`ElementPositions: pinnedView(${t}) needs ${t} live rows but only ${this.rows} exist; call grow(${t}) first`
|
|
357
|
-
);
|
|
358
|
-
return this.pins.subarray(0, t);
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Report whether a row carries real coordinates.
|
|
362
|
-
*
|
|
363
|
-
* A row outside the live count is UNPLACED, never placed: this predicate is used to decide
|
|
364
|
-
* whether an importer seed may be written, and answering "placed" for a row that does not
|
|
365
|
-
* exist is how a coordinate gets thrown away without an error. So is a row whose x is an
|
|
366
|
-
* infinity, which only a writer that bypassed `write()` -- that is, one writing through the
|
|
367
|
-
* column this array is lent to -- can produce; see the class comment.
|
|
368
|
-
* @param index - node index
|
|
369
|
-
* @returns true when the row is inside the live count AND carries a storable x
|
|
370
|
-
*/
|
|
371
|
-
isPlaced(t) {
|
|
372
|
-
return !Number.isInteger(t) || t < 0 || t >= this.rows ? !1 : this.hasStorableX(a * t);
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Read one row into a caller-supplied object (14.4 rule 7: never return a shared vector).
|
|
376
|
-
* @param index - node index
|
|
377
|
-
* @param out - the object to fill
|
|
378
|
-
* @param out.x - receives the scene-unit x
|
|
379
|
-
* @param out.y - receives the scene-unit y
|
|
380
|
-
* @param out.z - receives the scene-unit z
|
|
381
|
-
* @throws RangeError when `index` is not an integer in `[0, count)`
|
|
382
|
-
*/
|
|
383
|
-
read(t, e) {
|
|
384
|
-
const i = this.rowBase(t);
|
|
385
|
-
e.x = this.array[i] ?? Number.NaN, e.y = this.array[i + 1] ?? Number.NaN, e.z = this.array[i + 2] ?? Number.NaN;
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Write one row.
|
|
389
|
-
*
|
|
390
|
-
* This is the ONE choke point every layout, every drag and (from E1) every GPU readback goes
|
|
391
|
-
* through, so it is where an unstorable coordinate is stopped. NaN is this class's unplaced
|
|
392
|
-
* marker: letting one in through here would unplace a placed node from the inside, and an
|
|
393
|
-
* infinity would be stored and then reported placed. The test is `isStorableCoordinate`, NOT
|
|
394
|
-
* `Number.isFinite`, because the array is f32: a finite double of 1e39 passes `isFinite` and
|
|
395
|
-
* lands as Infinity. A caller holding such a coordinate has a bug upstream and must drop the
|
|
396
|
-
* update, not hand it on.
|
|
397
|
-
* @param index - node index
|
|
398
|
-
* @param x - scene-unit x
|
|
399
|
-
* @param y - scene-unit y
|
|
400
|
-
* @param z - scene-unit z
|
|
401
|
-
* @throws RangeError when `index` is not an integer in `[0, count)`. A node added since the
|
|
402
|
-
* last freeze has no row yet: freeze first (which grows this array), then write
|
|
403
|
-
* @throws RangeError when any component is NaN, an infinity, or a double that overflows f32
|
|
404
|
-
*/
|
|
405
|
-
write(t, e, i, r) {
|
|
406
|
-
const n = this.rowBase(t);
|
|
407
|
-
if (!d(e) || !d(i) || !d(r))
|
|
408
|
-
throw new RangeError(
|
|
409
|
-
`ElementPositions: row ${String(t)} was written the unstorable coordinate (${String(e)}, ${String(i)}, ${String(r)}); NaN is this class's UNPLACED marker, and an infinity -- including one an f32 overflow produces -- poisons the scene bounds`
|
|
410
|
-
);
|
|
411
|
-
this.array[n] = e, this.array[n + 1] = i, this.array[n + 2] = r;
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Write a row ONLY when it is unplaced. This is how importer-seeded coordinates reach a new
|
|
415
|
-
* node without overwriting anything a layout or a drag already produced.
|
|
416
|
-
*
|
|
417
|
-
* `false` means exactly one thing -- the row was ALREADY PLACED, by the same test `isPlaced()`
|
|
418
|
-
* applies. A row that does not exist yet throws instead of returning `false`, so a caller that
|
|
419
|
-
* seeds before growing finds out rather than losing the coordinate.
|
|
420
|
-
* @param index - node index
|
|
421
|
-
* @param x - scene-unit x
|
|
422
|
-
* @param y - scene-unit y
|
|
423
|
-
* @param z - scene-unit z
|
|
424
|
-
* @returns true when the row was written, false when it was already placed
|
|
425
|
-
* @throws RangeError when `index` is not an integer in `[0, count)`
|
|
426
|
-
* @throws RangeError when any component is NaN, an infinity, or a double that overflows f32,
|
|
427
|
-
* through `write()`
|
|
428
|
-
*/
|
|
429
|
-
fillUnplaced(t, e, i, r) {
|
|
430
|
-
const n = this.rowBase(t);
|
|
431
|
-
return this.hasStorableX(n) ? !1 : (this.write(t, e, i, r), !0);
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* Whether the row at this offset carries a storable x, which is the one test "placed" means.
|
|
435
|
-
* @param base - the array offset of the row's x component
|
|
436
|
-
* @returns true when the stored x is neither NaN nor an infinity
|
|
437
|
-
*/
|
|
438
|
-
hasStorableX(t) {
|
|
439
|
-
return d(this.array[t] ?? Number.NaN);
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* The array offset of a live row, rejecting anything else.
|
|
443
|
-
* @param index - node index
|
|
444
|
-
* @returns the offset of the row's x component
|
|
445
|
-
*/
|
|
446
|
-
rowBase(t) {
|
|
447
|
-
if (!Number.isInteger(t) || t < 0 || t >= this.rows)
|
|
448
|
-
throw new RangeError(
|
|
449
|
-
t === w ? "ElementPositions: INVALID_INDEX is the graph-format 'no node' sentinel, not a row" : `ElementPositions: row ${String(t)} is outside the live range [0, ${this.rows})`
|
|
450
|
-
);
|
|
451
|
-
return a * t;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
const c = new _("layout"), G = Object.freeze([
|
|
455
|
-
"arf",
|
|
456
|
-
"bfs",
|
|
457
|
-
"bipartite",
|
|
458
|
-
"circular",
|
|
459
|
-
"d3",
|
|
460
|
-
"fixed",
|
|
461
|
-
"forceatlas2",
|
|
462
|
-
"grid",
|
|
463
|
-
"kamada-kawai",
|
|
464
|
-
"multipartite",
|
|
465
|
-
"ngraph",
|
|
466
|
-
"planar",
|
|
467
|
-
"radial",
|
|
468
|
-
"random",
|
|
469
|
-
"shell",
|
|
470
|
-
"spectral",
|
|
471
|
-
"spiral",
|
|
472
|
-
"spring",
|
|
473
|
-
"spring-electrical"
|
|
474
|
-
]), U = $.getLogger(["graphty", "layout"]), P = 1e-6;
|
|
475
|
-
function V(s, t) {
|
|
476
|
-
return JSON.stringify([s, t]);
|
|
477
|
-
}
|
|
478
|
-
const X = 10;
|
|
479
|
-
function A(s) {
|
|
480
|
-
return new g({
|
|
481
|
-
code: "E_DUPLICATE_PLUGIN",
|
|
482
|
-
message: `"${s}" is a layout engine the element ships, and a built-in name may not be taken: a saved document that named it yesterday has to mean the same thing today`,
|
|
483
|
-
source: "registry",
|
|
484
|
-
details: { kind: "layout", name: s, builtIn: !0 }
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
const m = class m {
|
|
488
|
-
constructor() {
|
|
489
|
-
this.positionArrayAttached = !1, this.hold = null, this.holdRows = 0;
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Add multiple nodes to the layout engine
|
|
493
|
-
* @param nodes - Array of nodes to add
|
|
494
|
-
*/
|
|
495
|
-
addNodes(t) {
|
|
496
|
-
for (const e of t)
|
|
497
|
-
this.addNode(e);
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* Add multiple edges to the layout engine
|
|
501
|
-
* @param edges - Array of edges to add
|
|
502
|
-
*/
|
|
503
|
-
addEdges(t) {
|
|
504
|
-
for (const e of t)
|
|
505
|
-
this.addEdge(e);
|
|
506
|
-
}
|
|
507
|
-
/**
|
|
508
|
-
* Take a node out of the layout, before the element disposes the mesh that drew it.
|
|
509
|
-
*
|
|
510
|
-
* Declared here, with a default that does nothing, because it used to be duck-typed by the
|
|
511
|
-
* element's data manager and implemented by none of the nineteen engines that ship here: an
|
|
512
|
-
* author learned it existed by reading the element's source, and got no worked example. An
|
|
513
|
-
* engine that keeps its own node list must override this, or it holds every removed node --
|
|
514
|
-
* and everything that node references -- for as long as the engine lives.
|
|
515
|
-
* @param _n - the node leaving the graph
|
|
516
|
-
*/
|
|
517
|
-
removeNode(t) {
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* The edge half of {@link LayoutEngine.removeNode}, with the same default and the same reason.
|
|
521
|
-
* @param _e - the edge leaving the graph
|
|
522
|
-
*/
|
|
523
|
-
removeEdge(t) {
|
|
524
|
-
}
|
|
525
|
-
/**
|
|
526
|
-
* Settle nodes that reached the graph after this layout was already running.
|
|
527
|
-
*
|
|
528
|
-
* THE DEFAULT IS THE ELEMENT'S OWN FALLBACK -- up to ten steps, stopping early if the engine
|
|
529
|
-
* settles -- so a simulation behaves exactly as it did before the hook was declared, and an
|
|
530
|
-
* engine that can place a newcomer without re-running the whole simulation overrides it. It
|
|
531
|
-
* lives on the base class rather than in the manager because the manager cannot tell "did not
|
|
532
|
-
* implement it" from "implemented it as a deliberate no-op", and the difference decides
|
|
533
|
-
* whether ten steps run.
|
|
534
|
-
* @param _nodes - the nodes that have just arrived
|
|
535
|
-
*/
|
|
536
|
-
updatePositions(t) {
|
|
537
|
-
for (let e = 0; e < X; e++) {
|
|
538
|
-
if (this.isSettled)
|
|
539
|
-
return;
|
|
540
|
-
this.step();
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
/**
|
|
544
|
-
* Release whatever this engine holds. The element calls it when the reader switches layouts
|
|
545
|
-
* and when the graph is torn down, and never uses the engine again afterwards.
|
|
546
|
-
*
|
|
547
|
-
* Declared with a do-nothing default for the same reason as {@link LayoutEngine.removeNode}:
|
|
548
|
-
* it was duck-typed, undeclared and unimplemented by every engine here.
|
|
549
|
-
*/
|
|
550
|
-
dispose() {
|
|
551
|
-
}
|
|
552
|
-
/**
|
|
553
|
-
* The array this engine publishes node coordinates into.
|
|
554
|
-
*
|
|
555
|
-
* Allocated on demand, so reading it is enough to make an engine that has never been handed an
|
|
556
|
-
* element's array produce one of its own.
|
|
557
|
-
* @returns the position array in use
|
|
558
|
-
*/
|
|
559
|
-
get nodePositions() {
|
|
560
|
-
return this.positionArray ??= new x(0), this.positionArray;
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* Hand this engine the array it must publish into, and stop it adopting any other.
|
|
564
|
-
*
|
|
565
|
-
* This is how a host says "these coordinates are mine": the engine writes into the array the
|
|
566
|
-
* host already lends to its snapshots, so a layout, a drag and a GPU readback all land in the
|
|
567
|
-
* one place and a re-freeze loses none of them.
|
|
568
|
-
* @param positions - the element-owned array
|
|
569
|
-
*/
|
|
570
|
-
attachPositions(t) {
|
|
571
|
-
this.positionArray = t, this.positionArrayAttached = !0;
|
|
572
|
-
}
|
|
573
|
-
/**
|
|
574
|
-
* Hold the nodes a scoped layout may not move, or release them all with null.
|
|
575
|
-
*
|
|
576
|
-
* The element calls this on an engine whose class declares `static scoped = true`, after
|
|
577
|
-
* `init()` and after every renumbering of the graph. A set bit is a held row. A row at or past
|
|
578
|
-
* `rows` belongs to a node that arrived after the scope was captured, and is held too: the
|
|
579
|
-
* members of a scoped layout are the ones it started with. An engine that overrides this calls
|
|
580
|
-
* `super.setHoldMask` first and then fixes held nodes in its own state (see
|
|
581
|
-
* {@link LayoutEngineStatics.scoped}).
|
|
582
|
-
* @param mask - One bit per row, set for a row to hold; null holds nothing.
|
|
583
|
-
* @param rows - How many rows the mask covers.
|
|
584
|
-
*/
|
|
585
|
-
setHoldMask(t, e) {
|
|
586
|
-
this.hold = t, this.holdRows = t === null ? 0 : e;
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* The hold mask the element last handed this engine, or null when nothing is held.
|
|
590
|
-
* @returns The mask, which the caller must not change.
|
|
591
|
-
*/
|
|
592
|
-
get holdMask() {
|
|
593
|
-
return this.hold;
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Whether the element is holding this row still for a scoped layout.
|
|
597
|
-
* @param index - The node's row, or `INVALID_INDEX`.
|
|
598
|
-
* @returns True while a hold is set and the row is held or newer than the hold.
|
|
599
|
-
*/
|
|
600
|
-
isHeld(t) {
|
|
601
|
-
const { hold: e } = this;
|
|
602
|
-
return e === null ? !1 : t >= this.holdRows || !Number.isInteger(t) || t < 0 || v(e, t);
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Copy every node's current coordinates out of the engine and into the position array.
|
|
606
|
-
*
|
|
607
|
-
* Engines call this at the end of a step, so that by the time anything draws, the array is the
|
|
608
|
-
* answer rather than a copy of it. The default walks the engine's own nodes through
|
|
609
|
-
* {@link LayoutEngine.getNodePosition}, which is correct for any engine but allocates one
|
|
610
|
-
* object per node; an engine that can read its own state without allocating overrides it, and
|
|
611
|
-
* an engine that already writes straight into the array overrides it to do nothing.
|
|
612
|
-
*/
|
|
613
|
-
publishPositions() {
|
|
614
|
-
for (const t of this.nodes) {
|
|
615
|
-
const e = this.getNodePosition(t);
|
|
616
|
-
this.writeNodePosition(t, e.x, e.y, e.z ?? 0);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Read a node's published coordinates into an object the CALLER owns.
|
|
621
|
-
*
|
|
622
|
-
* The point of the out parameter is that a renderer can pass the vector it is about to draw
|
|
623
|
-
* with and allocate nothing per node per frame. A row that no engine has placed answers false
|
|
624
|
-
* and leaves `out` untouched, so the caller keeps whatever it had rather than being handed a
|
|
625
|
-
* NaN or an origin it cannot tell from a real coordinate.
|
|
626
|
-
* @param n - the node to read
|
|
627
|
-
* @param out - the object to fill; a Babylon `Vector3` is one, which is the point
|
|
628
|
-
* @param out.x - receives the scene-unit x
|
|
629
|
-
* @param out.y - receives the scene-unit y
|
|
630
|
-
* @param out.z - receives the scene-unit z
|
|
631
|
-
* @returns true when the node has a placed row
|
|
632
|
-
*/
|
|
633
|
-
readNodePosition(t, e) {
|
|
634
|
-
const i = this.positionsFor(t);
|
|
635
|
-
return i.isPlaced(t.index) ? (i.read(t.index, e), !0) : !1;
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Publish one node's coordinates, growing the array to reach its row.
|
|
639
|
-
*
|
|
640
|
-
* Three things are silently skipped rather than thrown, because this runs inside a layout step
|
|
641
|
-
* and a throw there kills the frame: a node with no row in the graph (`INVALID_INDEX`, which a
|
|
642
|
-
* record whose id the graph builder would not take carries for its whole life), a node whose
|
|
643
|
-
* index is not a row number at all, and a coordinate that cannot be stored. That last one is
|
|
644
|
-
* the important one -- a force layout that divided by a zero distance produces NaN, and an
|
|
645
|
-
* overflow of the f32 the array stores produces an infinity. Either one, written, would make
|
|
646
|
-
* the row read back as a place: the mesh vanishes and the scene bounds and camera framing go
|
|
647
|
-
* with it. Left alone, the row stays unplaced and the node keeps the coordinates it had.
|
|
648
|
-
*
|
|
649
|
-
* The array is GROWN to reach the row rather than the write being refused. A node's index is
|
|
650
|
-
* handed out the moment its record is taken, but its row only appears when the graph is next
|
|
651
|
-
* frozen, and a layout that ran in between would otherwise be thrown away in silence. Growth is
|
|
652
|
-
* prefix-stable and fills what it adds with NaN, so it cannot disturb a row anything else
|
|
653
|
-
* placed; the one thing it does change is that a node this engine placed before the first
|
|
654
|
-
* freeze counts as placed, which is what makes a file's own coordinates yield to it.
|
|
655
|
-
*
|
|
656
|
-
* A PINNED ROW REFUSES A LAYOUT STEP. This is the whole of "a pin is meaningful under every
|
|
657
|
-
* arrangement": fourteen of the element's nineteen engines implement `pin()` as a no-op and
|
|
658
|
-
* `setNodePosition` as a no-op too, so before this guard a reader who dragged a node under a
|
|
659
|
-
* static layout watched it snap back the next time the layout recomputed. One refusal here
|
|
660
|
-
* covers every engine, including one written by a third party that has never heard of pinning,
|
|
661
|
-
* because every engine reaches the shared array through this method.
|
|
662
|
-
*
|
|
663
|
-
* A DRAG IS NOT A LAYOUT STEP. `intent: "placement"` writes straight through, so a reader can
|
|
664
|
-
* move a pinned node and have it stay where they put it; without that distinction the guard
|
|
665
|
-
* would make a pinned node undraggable, which is the opposite of what a pin is for. The
|
|
666
|
-
* default is `"layout"` so that an engine written before the parameter existed -- a plugin's
|
|
667
|
-
* step loop -- is guarded without knowing it, and only the placement paths have to opt out.
|
|
668
|
-
* @param n - the node being placed
|
|
669
|
-
* @param x - scene-unit x
|
|
670
|
-
* @param y - scene-unit y
|
|
671
|
-
* @param z - scene-unit z
|
|
672
|
-
* @param intent - `"layout"` for a simulation step, `"placement"` for a drag or a replay
|
|
673
|
-
* @returns true when the row was written
|
|
674
|
-
*/
|
|
675
|
-
writeNodePosition(t, e, i, r, n = "layout") {
|
|
676
|
-
const { index: o } = t;
|
|
677
|
-
if (o === w || !Number.isInteger(o) || o < 0 || !d(e) || !d(i) || !d(r))
|
|
678
|
-
return !1;
|
|
679
|
-
const l = this.positionsFor(t);
|
|
680
|
-
return n === "layout" && (l.isPinned(o) || this.isHeld(o) && l.isPlaced(o)) ? !1 : (o >= l.count && l.grow(o + 1), l.write(o, e, i, r), !0);
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* The weight of every ordered endpoint pair this batch of edges covers, or null when the
|
|
684
|
-
* graph's weights carry no information.
|
|
685
|
-
*
|
|
686
|
-
* WHY A PAIR AND NOT AN EDGE. `@graphty/layout`'s one weight channel is
|
|
687
|
-
* `graph.getEdgeData(source, target, attr)`, which is asked by endpoint pair, and both layout
|
|
688
|
-
* functions that read it write the answer into a matrix cell -- `A[i][j]` in ForceAtlas2,
|
|
689
|
-
* `distances[s][t]` in Kamada-Kawai. There is no cell for a second edge between the same two
|
|
690
|
-
* nodes, so parallel edges are SUMMED into one number rather than left to last-writer-wins,
|
|
691
|
-
* where the order the file happened to list them in would decide the arrangement. Summing is
|
|
692
|
-
* also what the element does when it simplifies a multigraph for an algorithm, so a graph's
|
|
693
|
-
* weights mean the same thing to a layout and to a metric.
|
|
694
|
-
*
|
|
695
|
-
* READ ONCE PER LAYOUT COMPUTATION, not per frame and not per edge: the weights come from the
|
|
696
|
-
* current snapshot's edge list, indexed by the same logical edge index `Edge.index` holds.
|
|
697
|
-
*
|
|
698
|
-
* NULL MEANS "DO NOT ATTACH A CALLBACK". graph-format stores an all-ones graph with no weight
|
|
699
|
-
* column at all, and a graph whose every weight is 1 carries no information a layout could
|
|
700
|
-
* arrange by -- so the caller leaves `getEdgeData` off the graph object entirely and the
|
|
701
|
-
* arrangement is bit-identical to the one the same seed produced before weights existed.
|
|
702
|
-
*
|
|
703
|
-
* THIS IS A SLIGHTLY NARROWER QUESTION THAN `statistics().weighted`, deliberately. The status
|
|
704
|
-
* chip asks whether the SNAPSHOT's weight column carries anything but ones; this asks it of
|
|
705
|
-
* the edges this engine is actually about to arrange. They answer differently only when the
|
|
706
|
-
* engine holds a strict subset of the graph's edges whose weights are all 1, and there the
|
|
707
|
-
* narrower answer is the correct one: a layout cannot be moved by a weight on an edge it is
|
|
708
|
-
* not laying out. A consumer who sees "weighted" on the status bar and an unmoved arrangement
|
|
709
|
-
* is looking at that case.
|
|
710
|
-
* @param edges - the edges this engine is about to lay out
|
|
711
|
-
* @returns pair key (see {@link pairWeightKey}) to summed weight, or null
|
|
712
|
-
*/
|
|
713
|
-
pairWeights(t) {
|
|
714
|
-
if (t.length === 0)
|
|
715
|
-
return null;
|
|
716
|
-
const i = t[0].parentGraph?.getDataManager?.()?.getSnapshot?.()?.edgeList().weights ?? null;
|
|
717
|
-
if (i === null)
|
|
718
|
-
return null;
|
|
719
|
-
const r = /* @__PURE__ */ new Map();
|
|
720
|
-
let n = !1;
|
|
721
|
-
for (const o of t) {
|
|
722
|
-
const l = o.index >= 0 && o.index < i.length ? i[o.index] : 1;
|
|
723
|
-
l !== 1 && (n = !0);
|
|
724
|
-
const I = V(o.srcId, o.dstId);
|
|
725
|
-
r.set(I, (r.get(I) ?? 0) + l);
|
|
726
|
-
}
|
|
727
|
-
return n ? r : null;
|
|
728
|
-
}
|
|
729
|
-
/**
|
|
730
|
-
* Say once, per layout computation, how many pairs were clamped off zero.
|
|
731
|
-
*
|
|
732
|
-
* ONCE PER RUN AND NOT PER EDGE: a graph whose weights are all zero would otherwise produce
|
|
733
|
-
* one line per edge, which buries every other message in the run it happened during. It is
|
|
734
|
-
* reported at all because a clamp changes the picture -- a zero-weight edge is drawn as the
|
|
735
|
-
* weakest connection the solver can express rather than as no connection -- and the record
|
|
736
|
-
* that carried the zero is the reader's, not the element's, so they are the one who can fix
|
|
737
|
-
* it.
|
|
738
|
-
* @param layout - the layout name, for the message
|
|
739
|
-
* @param weights - the pair weights about to be handed to the layout function
|
|
740
|
-
*/
|
|
741
|
-
reportClampedWeights(t, e) {
|
|
742
|
-
let i = 0;
|
|
743
|
-
for (const r of e.values())
|
|
744
|
-
r < P && i++;
|
|
745
|
-
i > 0 && U.warn("Edge weights at or below zero were clamped before the layout read them", {
|
|
746
|
-
layout: t,
|
|
747
|
-
clamped: i,
|
|
748
|
-
epsilon: P
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* The array to use for this node: the one its own graph owns, unless a host attached one.
|
|
753
|
-
*
|
|
754
|
-
* Resolved on every call rather than cached, because the element REPLACES its array when a
|
|
755
|
-
* dataset is discarded -- the store and everything keyed into it is thrown away and rebuilt --
|
|
756
|
-
* and an engine outlives that. A cached reference would keep publishing into the array of a
|
|
757
|
-
* graph that no longer exists, which is invisible: every write succeeds and nothing draws.
|
|
758
|
-
* @param n - the node being published or read
|
|
759
|
-
* @returns the array to write to and read from
|
|
760
|
-
*/
|
|
761
|
-
positionsFor(t) {
|
|
762
|
-
if (!this.positionArrayAttached) {
|
|
763
|
-
const e = t.parentGraph?.getDataManager?.()?.positions;
|
|
764
|
-
e instanceof x && (this.positionArray = e);
|
|
765
|
-
}
|
|
766
|
-
return this.nodePositions;
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* Get the type identifier for this layout engine
|
|
770
|
-
* @returns The layout engine type string
|
|
771
|
-
*/
|
|
772
|
-
get type() {
|
|
773
|
-
return this.constructor.type;
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* File a layout engine class under the name it declares, and publish what it says about
|
|
777
|
-
* itself to the catalogue.
|
|
778
|
-
*
|
|
779
|
-
* WHAT CHANGED AND WHY. This used to read `cls.type` through a cast and put the class in a
|
|
780
|
-
* map, which meant a class with no `static type` registered under the string "undefined", a
|
|
781
|
-
* second class under a taken name silently replaced the first, and a registered engine
|
|
782
|
-
* reached no catalogue at all -- so a third party's layout could run but could never be
|
|
783
|
-
* offered by a picker, described in a reader's language, or found by `layoutIdForEngine`.
|
|
784
|
-
*
|
|
785
|
-
* A third party's class must declare a `static descriptor` whose `id` equals its
|
|
786
|
-
* `static type`. The element's own nineteen are the one exemption, because their arrangements
|
|
787
|
-
* are authored centrally in the layout catalogue where several engines may sit behind one
|
|
788
|
-
* public name.
|
|
789
|
-
* @param cls - The layout engine class.
|
|
790
|
-
* @returns The same class, so a declaration can register itself in one expression.
|
|
791
|
-
* @throws A `GraphtyError` with `E_BAD_COMMAND` when the class declares no `static type`, no
|
|
792
|
-
* `static descriptor`, or a descriptor whose `id` disagrees with its `static type`; or with
|
|
793
|
-
* `E_DUPLICATE_PLUGIN` when the name or the descriptor id is one the element itself ships.
|
|
794
|
-
*/
|
|
795
|
-
static register(t) {
|
|
796
|
-
const e = t, { type: i, descriptor: r } = e;
|
|
797
|
-
if (typeof i != "string" || i === "")
|
|
798
|
-
throw new g({
|
|
799
|
-
code: "E_BAD_COMMAND",
|
|
800
|
-
message: "a layout engine is filed under its `static type`, and this class declares none",
|
|
801
|
-
source: "registry",
|
|
802
|
-
details: { kind: "layout", field: "type" }
|
|
803
|
-
});
|
|
804
|
-
if (c.get(i) === t)
|
|
805
|
-
return t;
|
|
806
|
-
const n = G.includes(i);
|
|
807
|
-
if (r === void 0) {
|
|
808
|
-
if (!n)
|
|
809
|
-
throw new g({
|
|
810
|
-
code: "E_BAD_COMMAND",
|
|
811
|
-
message: `the layout "${i}" declares no \`static descriptor\`, so nothing could offer it: a picker reads the catalogue, and an engine the catalogue does not carry is reachable only by a consumer who already knows its name`,
|
|
812
|
-
source: "registry",
|
|
813
|
-
details: { kind: "layout", name: i, field: "descriptor" }
|
|
814
|
-
});
|
|
815
|
-
if (c.hasOwn(i))
|
|
816
|
-
throw A(i);
|
|
817
|
-
return c.set(i, t), t;
|
|
818
|
-
}
|
|
819
|
-
if (n)
|
|
820
|
-
throw A(i);
|
|
821
|
-
if (r.id !== i)
|
|
822
|
-
throw new g({
|
|
823
|
-
code: "E_BAD_COMMAND",
|
|
824
|
-
message: `the layout "${i}" describes itself as "${r.id}". A layout has ONE key: the name \`setLayout\` takes and the name the catalogue publishes are the same string, so nothing has to be named twice and a saved document means one thing`,
|
|
825
|
-
source: "registry",
|
|
826
|
-
details: { kind: "layout", name: i, field: "descriptor.id", id: r.id }
|
|
827
|
-
});
|
|
828
|
-
return C({
|
|
829
|
-
descriptor: {
|
|
830
|
-
...r,
|
|
831
|
-
honoursWeights: e.honoursWeights ?? !1,
|
|
832
|
-
scoped: e.scoped ?? !1
|
|
833
|
-
},
|
|
834
|
-
type: i
|
|
835
|
-
}), c.set(i, t), t;
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Get a layout engine instance by type
|
|
839
|
-
* @param type - The layout engine type identifier
|
|
840
|
-
* @param opts - Configuration options for the layout engine
|
|
841
|
-
* @returns A new layout engine instance or null if type not found
|
|
842
|
-
*/
|
|
843
|
-
static get(t, e = {}) {
|
|
844
|
-
const i = c.get(t);
|
|
845
|
-
return i ? new i(e) : null;
|
|
846
|
-
}
|
|
847
|
-
/**
|
|
848
|
-
* Get dimension-specific options for this layout
|
|
849
|
-
* @param dimension - The desired dimension (2 or 3)
|
|
850
|
-
* @returns Options object for the dimension or null if unsupported
|
|
851
|
-
*/
|
|
852
|
-
static getOptionsForDimension(t) {
|
|
853
|
-
return t > this.maxDimensions ? null : {};
|
|
854
|
-
}
|
|
855
|
-
/**
|
|
856
|
-
* Get dimension-specific options for a layout by type
|
|
857
|
-
* @param type - The layout engine type identifier
|
|
858
|
-
* @param dimension - The desired dimension (2 or 3)
|
|
859
|
-
* @returns Options object for the dimension or null if type not found or unsupported
|
|
860
|
-
*/
|
|
861
|
-
static getOptionsForDimensionByType(t, e) {
|
|
862
|
-
const i = c.get(t);
|
|
863
|
-
return i ? i.getOptionsForDimension(e) : null;
|
|
864
|
-
}
|
|
865
|
-
/**
|
|
866
|
-
* Get the Zod-based options schema for this layout
|
|
867
|
-
* @returns The options schema, or an empty object if no schema defined
|
|
868
|
-
*/
|
|
869
|
-
static getZodOptionsSchema() {
|
|
870
|
-
return this.zodOptionsSchema ?? {};
|
|
871
|
-
}
|
|
872
|
-
/**
|
|
873
|
-
* Check if this layout has a Zod-based options schema
|
|
874
|
-
* @returns true if the layout has options defined
|
|
875
|
-
*/
|
|
876
|
-
static hasZodOptions() {
|
|
877
|
-
return this.zodOptionsSchema !== void 0 && Object.keys(this.zodOptionsSchema).length > 0;
|
|
878
|
-
}
|
|
879
|
-
/**
|
|
880
|
-
* Get a list of all registered layout types
|
|
881
|
-
* @returns Array of registered layout type identifiers
|
|
882
|
-
*/
|
|
883
|
-
static getRegisteredTypes() {
|
|
884
|
-
return Array.from(c.keys());
|
|
885
|
-
}
|
|
886
|
-
/**
|
|
887
|
-
* Get a layout class by type
|
|
888
|
-
* @param type - The layout engine type identifier
|
|
889
|
-
* @returns The layout engine class or null if not found
|
|
890
|
-
*/
|
|
891
|
-
static getClass(t) {
|
|
892
|
-
return c.get(t) ?? null;
|
|
893
|
-
}
|
|
894
|
-
};
|
|
895
|
-
m.honoursWeights = !1, m.scoped = !1;
|
|
896
|
-
let b = m;
|
|
897
|
-
const H = O.looseObject({
|
|
898
|
-
scalingFactor: O.number().default(100)
|
|
899
|
-
});
|
|
900
|
-
class pt extends b {
|
|
901
|
-
/**
|
|
902
|
-
* Create a simple layout engine
|
|
903
|
-
* @param opts - Configuration options including scalingFactor
|
|
904
|
-
*/
|
|
905
|
-
constructor(t = {}) {
|
|
906
|
-
super(), this._nodes = [], this._edges = [], this.stale = !0, this.positions = {}, this.scalingFactor = 100, this.isSettled = !0;
|
|
907
|
-
const e = H.parse(t);
|
|
908
|
-
this.scalingFactor = e.scalingFactor;
|
|
909
|
-
}
|
|
910
|
-
/**
|
|
911
|
-
* Get dimension-specific options for simple layouts
|
|
912
|
-
* @param dimension - The desired dimension (2 or 3)
|
|
913
|
-
* @returns Options object with dim parameter or null if unsupported
|
|
914
|
-
*/
|
|
915
|
-
static getOptionsForDimension(t) {
|
|
916
|
-
return t > this.maxDimensions ? null : { dim: t };
|
|
917
|
-
}
|
|
918
|
-
// basic functionality
|
|
919
|
-
/**
|
|
920
|
-
* Initialize the layout engine
|
|
921
|
-
*
|
|
922
|
-
* Simple layouts compute positions synchronously and don't require initialization.
|
|
923
|
-
*/
|
|
924
|
-
async init() {
|
|
925
|
-
}
|
|
926
|
-
/**
|
|
927
|
-
* Add a node to the layout and mark positions as stale
|
|
928
|
-
* @param n - The node to add
|
|
929
|
-
*/
|
|
930
|
-
addNode(t) {
|
|
931
|
-
this._nodes.push(t), this.stale = !0;
|
|
932
|
-
}
|
|
933
|
-
/**
|
|
934
|
-
* Add an edge to the layout and mark positions as stale
|
|
935
|
-
* @param e - The edge to add
|
|
936
|
-
*/
|
|
937
|
-
addEdge(t) {
|
|
938
|
-
this._edges.push(t), this.stale = !0;
|
|
939
|
-
}
|
|
940
|
-
/**
|
|
941
|
-
* Get the position of a node, computing layout if stale
|
|
942
|
-
*
|
|
943
|
-
* The coordinates come from the shared position array, which `SimpleLayoutEngine.refresh`
|
|
944
|
-
* fills from `positions` as soon as the layout is recomputed. They are the same numbers the
|
|
945
|
-
* record holds, rounded to the f32 the array stores -- so an arrangement never moves, but a
|
|
946
|
-
* coordinate may differ in its last digit or two from the double the layout function returned.
|
|
947
|
-
* A node with no row falls back to the record, which is every node in an engine driven by hand.
|
|
948
|
-
* @param n - The node to get position for
|
|
949
|
-
* @returns The node's position coordinates
|
|
950
|
-
*/
|
|
951
|
-
getNodePosition(t) {
|
|
952
|
-
return this.refresh(), this.publishedOr(t, t.id);
|
|
953
|
-
}
|
|
954
|
-
/**
|
|
955
|
-
* Record where the reader has just put a node.
|
|
956
|
-
*
|
|
957
|
-
* A static layout recomputes every coordinate from scratch, so it has no per-node state a
|
|
958
|
-
* placement could live in -- which is why this used to do nothing at all, and why a drag
|
|
959
|
-
* under any of the fourteen static arrangements was discarded by the next `refresh()`. The
|
|
960
|
-
* placement is written into the SHARED array instead, with `"placement"` intent so that it
|
|
961
|
-
* lands even on a pinned row, and into the computed record so that a read which falls back to
|
|
962
|
-
* the record (a node with no row of its own) answers the same.
|
|
963
|
-
* @param n - the node that moved
|
|
964
|
-
* @param p - where it moved to
|
|
965
|
-
*/
|
|
966
|
-
setNodePosition(t, e) {
|
|
967
|
-
const i = e.z ?? 0;
|
|
968
|
-
this.writeNodePosition(t, e.x, e.y, i, "placement"), this.positions[t.id] = [e.x / this.scalingFactor, e.y / this.scalingFactor, i / this.scalingFactor];
|
|
969
|
-
}
|
|
970
|
-
/**
|
|
971
|
-
* Get the position of an edge based on its endpoints
|
|
972
|
-
* @param e - The edge to get position for
|
|
973
|
-
* @returns The edge's source and destination positions
|
|
974
|
-
*/
|
|
975
|
-
getEdgePosition(t) {
|
|
976
|
-
return this.refresh(), {
|
|
977
|
-
src: this.publishedOr(t.srcNode, t.srcId),
|
|
978
|
-
dst: this.publishedOr(t.dstNode, t.dstId)
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
/**
|
|
982
|
-
* Copy the computed layout into the shared position array, recomputing it first if it is stale.
|
|
983
|
-
*/
|
|
984
|
-
publishPositions() {
|
|
985
|
-
if (this.stale) {
|
|
986
|
-
this.refresh();
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
989
|
-
this.publishRecord();
|
|
990
|
-
}
|
|
991
|
-
// for animated layouts
|
|
992
|
-
/**
|
|
993
|
-
* Step the layout animation
|
|
994
|
-
*
|
|
995
|
-
* Simple layouts are static and don't animate, so stepping has no effect.
|
|
996
|
-
*/
|
|
997
|
-
step() {
|
|
998
|
-
}
|
|
999
|
-
/**
|
|
1000
|
-
* Take a node out of the layout.
|
|
1001
|
-
*
|
|
1002
|
-
* WITHOUT THIS the engine holds the removed node -- and through it the node's Babylon mesh,
|
|
1003
|
-
* its data record and its endpoints -- for as long as the engine lives, and the frame loop
|
|
1004
|
-
* keeps walking it, so a node the reader deleted still draws at wherever it last was.
|
|
1005
|
-
*
|
|
1006
|
-
* The computed record is left alone and the layout is marked stale instead. Every layout
|
|
1007
|
-
* function here returns a WHOLE new record, which `doLayout` assigns over the old one, and
|
|
1008
|
-
* `refresh()` runs before any read -- so the removed node's entry is gone by the time anything
|
|
1009
|
-
* could read it, without this method having to reach into a keyed object by a computed name.
|
|
1010
|
-
* @param n - the node leaving the graph
|
|
1011
|
-
*/
|
|
1012
|
-
removeNode(t) {
|
|
1013
|
-
const e = this._nodes.indexOf(t);
|
|
1014
|
-
e >= 0 && this._nodes.splice(e, 1), this.stale = !0;
|
|
1015
|
-
}
|
|
1016
|
-
/**
|
|
1017
|
-
* The edge half of {@link SimpleLayoutEngine.removeNode}, with the same reason.
|
|
1018
|
-
* @param e - the edge leaving the graph
|
|
1019
|
-
*/
|
|
1020
|
-
removeEdge(t) {
|
|
1021
|
-
const e = this._edges.indexOf(t);
|
|
1022
|
-
e >= 0 && this._edges.splice(e, 1), this.stale = !0;
|
|
1023
|
-
}
|
|
1024
|
-
/**
|
|
1025
|
-
* Pin a node in place
|
|
1026
|
-
*
|
|
1027
|
-
* A static layout has nothing of its own to hold still -- it recomputes every position from
|
|
1028
|
-
* scratch -- so the pin is kept by the element's position array instead, and
|
|
1029
|
-
* `writeNodePosition` refuses to move a pinned row. That is what makes a
|
|
1030
|
-
* pin mean something under all fourteen of these engines, none of which could hold one.
|
|
1031
|
-
*/
|
|
1032
|
-
pin() {
|
|
1033
|
-
}
|
|
1034
|
-
/**
|
|
1035
|
-
* Unpin a node
|
|
1036
|
-
*
|
|
1037
|
-
* The element's position array holds the pin; see {@link SimpleLayoutEngine.pin}.
|
|
1038
|
-
*/
|
|
1039
|
-
unpin() {
|
|
1040
|
-
}
|
|
1041
|
-
// properties
|
|
1042
|
-
/**
|
|
1043
|
-
* Get all nodes in the layout
|
|
1044
|
-
* @returns Iterable of nodes
|
|
1045
|
-
*/
|
|
1046
|
-
get nodes() {
|
|
1047
|
-
return this._nodes;
|
|
1048
|
-
}
|
|
1049
|
-
/**
|
|
1050
|
-
* Get all edges in the layout
|
|
1051
|
-
* @returns Iterable of edges
|
|
1052
|
-
*/
|
|
1053
|
-
get edges() {
|
|
1054
|
-
return this._edges;
|
|
1055
|
-
}
|
|
1056
|
-
/**
|
|
1057
|
-
* Recompute the layout when it is stale, and publish what it produced.
|
|
1058
|
-
*
|
|
1059
|
-
* A simple layout is computed once and then held, so this is the ONE place the shared array is
|
|
1060
|
-
* filled: every reader below goes through here first, which is why a node added after the last
|
|
1061
|
-
* read still gets a row before anything asks for its coordinates.
|
|
1062
|
-
*/
|
|
1063
|
-
refresh() {
|
|
1064
|
-
this.stale && (this.doLayout(), this.stale = !1, this.publishRecord());
|
|
1065
|
-
}
|
|
1066
|
-
/**
|
|
1067
|
-
* Write the computed record into the shared array, scaled to scene units.
|
|
1068
|
-
*
|
|
1069
|
-
* A node the layout function returned nothing for is LEFT UNPLACED rather than published at
|
|
1070
|
-
* the origin: the two are indistinguishable once stored, and the origin is a place a reader
|
|
1071
|
-
* would draw at.
|
|
1072
|
-
*/
|
|
1073
|
-
publishRecord() {
|
|
1074
|
-
for (const t of this._nodes) {
|
|
1075
|
-
const e = this.positions[t.id];
|
|
1076
|
-
!e || e.length === 0 || this.writeNodePosition(
|
|
1077
|
-
t,
|
|
1078
|
-
e[0] * this.scalingFactor,
|
|
1079
|
-
e[1] * this.scalingFactor,
|
|
1080
|
-
(e[2] ?? 0) * this.scalingFactor
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
/**
|
|
1085
|
-
* A node's published row, or the computed record when it has no row of its own.
|
|
1086
|
-
*
|
|
1087
|
-
* The fallback is not a rare path: an engine driven directly -- by a test, or by a host that
|
|
1088
|
-
* keeps no graph -- has nodes whose index is `INVALID_INDEX`, and none of them is ever
|
|
1089
|
-
* published. Both branches produce the same arrangement; only the rounding differs.
|
|
1090
|
-
* @param n - the node, when the caller has one
|
|
1091
|
-
* @param id - the node's id, which is how the computed record is keyed
|
|
1092
|
-
* @returns a fresh coordinate triple
|
|
1093
|
-
*/
|
|
1094
|
-
publishedOr(t, e) {
|
|
1095
|
-
const i = { x: 0, y: 0, z: 0 };
|
|
1096
|
-
return t !== void 0 && this.readNodePosition(t, i) ? i : K(this.positions[e], this.scalingFactor);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
function K(s, t) {
|
|
1100
|
-
if (!s || s.length === 0)
|
|
1101
|
-
return { x: 0, y: 0, z: 0 };
|
|
1102
|
-
const e = s[0] * t, i = s[1] * t, r = (s[2] ?? 0) * t;
|
|
1103
|
-
return { x: e, y: i, z: r };
|
|
1104
|
-
}
|
|
1105
|
-
const q = /^#[0-9a-f]{6}$/i;
|
|
1106
|
-
function J(s) {
|
|
1107
|
-
if (typeof s != "string" || s.trim() === "")
|
|
1108
|
-
return null;
|
|
1109
|
-
try {
|
|
1110
|
-
const t = L(s.trim());
|
|
1111
|
-
if (t === void 0)
|
|
1112
|
-
return null;
|
|
1113
|
-
const e = t.length === 9 ? t.slice(0, 7) : t;
|
|
1114
|
-
return q.test(e) ? e.toUpperCase() : null;
|
|
1115
|
-
} catch {
|
|
1116
|
-
return null;
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
function Y(s) {
|
|
1120
|
-
return JSON.stringify([s.id, s.kind, s.colors, s.capacity, s.colorblindSafe]);
|
|
1121
|
-
}
|
|
1122
|
-
const N = E({
|
|
1123
|
-
kind: "palette",
|
|
1124
|
-
idOf: (s) => s.id,
|
|
1125
|
-
descriptorOf: (s) => s,
|
|
1126
|
-
implementationOf: Y,
|
|
1127
|
-
builtInIds: () => k
|
|
1128
|
-
});
|
|
1129
|
-
function u(s, t, e) {
|
|
1130
|
-
throw new g({
|
|
1131
|
-
code: "E_BAD_COMMAND",
|
|
1132
|
-
message: e,
|
|
1133
|
-
source: "registry",
|
|
1134
|
-
details: { kind: "palette", name: typeof s == "string" ? s : "", field: t }
|
|
1135
|
-
});
|
|
1136
|
-
}
|
|
1137
|
-
function gt(s, t) {
|
|
1138
|
-
typeof s != "object" && u("", "descriptor", "registerPalette takes a palette descriptor"), (s.plainName === void 0 || s.plainName === "") && u(s.id, "plainName", `the palette "${String(s.id)}" was registered without a plain name`), s.kind !== "sequential" && s.kind !== "diverging" && s.kind !== "categorical" && u(
|
|
1139
|
-
s.id,
|
|
1140
|
-
"kind",
|
|
1141
|
-
`the palette "${String(s.id)}" must be sequential, diverging or categorical`
|
|
1142
|
-
), (!Array.isArray(s.colors) || s.colors.length === 0) && u(s.id, "colors", `the palette "${String(s.id)}" was registered with no colours`);
|
|
1143
|
-
const e = [];
|
|
1144
|
-
for (const r of s.colors) {
|
|
1145
|
-
const n = J(r);
|
|
1146
|
-
n === null && u(s.id, "colors", `"${String(r)}" in the palette "${String(s.id)}" is not a colour`), e.push(n);
|
|
1147
|
-
}
|
|
1148
|
-
const i = s.kind === "categorical" ? e.length : null;
|
|
1149
|
-
s.capacity !== void 0 && s.capacity !== i && u(
|
|
1150
|
-
s.id,
|
|
1151
|
-
"capacity",
|
|
1152
|
-
`the palette "${String(s.id)}" is ${s.kind}, so its capacity is ${i === null ? "null" : String(i)} rather than ${String(s.capacity)}`
|
|
1153
|
-
), N.register(
|
|
1154
|
-
Object.freeze({
|
|
1155
|
-
...s,
|
|
1156
|
-
colors: Object.freeze(e),
|
|
1157
|
-
capacity: i,
|
|
1158
|
-
colorblindSafe: Object.freeze(s.colorblindSafe ?? [])
|
|
1159
|
-
}),
|
|
1160
|
-
t
|
|
1161
|
-
);
|
|
1162
|
-
}
|
|
1163
|
-
function ft() {
|
|
1164
|
-
return N.descriptors();
|
|
1165
|
-
}
|
|
1166
|
-
function mt(s) {
|
|
1167
|
-
return N.byId(s);
|
|
1168
|
-
}
|
|
1169
|
-
function yt() {
|
|
1170
|
-
N.clearForTesting();
|
|
1171
|
-
}
|
|
1172
|
-
export {
|
|
1173
|
-
x as E,
|
|
1174
|
-
T as F,
|
|
1175
|
-
b as L,
|
|
1176
|
-
a as P,
|
|
1177
|
-
H as S,
|
|
1178
|
-
at as U,
|
|
1179
|
-
P as W,
|
|
1180
|
-
pt as a,
|
|
1181
|
-
ut as b,
|
|
1182
|
-
ot as c,
|
|
1183
|
-
yt as d,
|
|
1184
|
-
R as e,
|
|
1185
|
-
ht as f,
|
|
1186
|
-
ft as g,
|
|
1187
|
-
nt as h,
|
|
1188
|
-
lt as i,
|
|
1189
|
-
ct as j,
|
|
1190
|
-
V as k,
|
|
1191
|
-
dt as l,
|
|
1192
|
-
mt as m,
|
|
1193
|
-
d as n,
|
|
1194
|
-
rt as p,
|
|
1195
|
-
gt as r
|
|
1196
|
-
};
|