@graphty/graphty-element 2.3.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/ai.js +115 -222
  2. package/dist/catalog.js +56 -55
  3. package/dist/chunks/{AiManager-Bd_r1Hei.js → AiManager-BBmGJbH4.js} +793 -654
  4. package/dist/chunks/{DataSource-bt0DhBjG.js → DataSource-OeN3NeyD.js} +1 -1
  5. package/dist/chunks/{GraphSession-iNyKm7Ds.js → GraphSession-Bef1AYw9.js} +2136 -2104
  6. package/dist/chunks/{GraphStyle-D0PXnZKu.js → GraphStyle-Cwr55SAE.js} +5 -2
  7. package/dist/chunks/{VoiceInputAdapter-DszYl6Ha.js → VoiceInputAdapter-Dr9Gcmds.js} +1 -1
  8. package/dist/chunks/{XRPivotCameraController-XtxbbZ-D.js → XRPivotCameraController-BbfgZWpS.js} +1 -1
  9. package/dist/chunks/algorithms-CpX56sUB.js +3482 -0
  10. package/dist/chunks/{capability-check-BqIEXcun.js → capability-check-Blhb2aBB.js} +1 -1
  11. package/dist/chunks/{detect-DM29BEaB.js → detect-Cqwshr9a.js} +1 -1
  12. package/dist/chunks/{format-detection-BEdmtvsy.js → format-detection-BXGO1lSn.js} +1 -1
  13. package/dist/chunks/{index-CD0_RJv-.js → index-C0mIoumR.js} +2258 -2129
  14. package/dist/chunks/optionsFromZod-17lkrAJs.js +2565 -0
  15. package/dist/chunks/paletteRegistry-x7WOEKZY.js +1153 -0
  16. package/dist/chunks/scales-BRwl51k8.js +3047 -0
  17. package/dist/custom-elements.json +1 -1
  18. package/dist/extend.js +42 -42
  19. package/dist/graphty-catalog.json +1 -1
  20. package/dist/graphty.bundle.js +25814 -25456
  21. package/dist/graphty.js +29 -29
  22. package/dist/schema.js +1 -1
  23. package/dist/session.d.ts +1 -1
  24. package/dist/session.js +28 -29
  25. package/dist/src/Graph.d.ts +33 -3
  26. package/dist/src/camera/builtins.d.ts +14 -1
  27. package/dist/src/camera/types.d.ts +7 -0
  28. package/dist/src/cameras/CameraManager.d.ts +13 -0
  29. package/dist/src/cameras/OrbitCameraController.d.ts +9 -0
  30. package/dist/src/catalog/types.d.ts +10 -0
  31. package/dist/src/config/GraphStyle.d.ts +5 -1
  32. package/dist/src/config/StyleTemplate.d.ts +2 -2
  33. package/dist/src/graphty-element.d.ts +11 -6
  34. package/dist/src/managers/StylePainter.d.ts +9 -0
  35. package/dist/src/managers/UpdateManager.d.ts +5 -0
  36. package/dist/src/session/results/index.d.ts +1 -1
  37. package/dist/src/session/results/statistics.d.ts +8 -1
  38. package/dist/src/session/results/types.d.ts +33 -0
  39. package/dist/src/session/selection/targets.d.ts +4 -1
  40. package/dist/src/session/styles/predicate.d.ts +26 -1
  41. package/dist/src/session/styles/repaint.d.ts +11 -0
  42. package/dist/src/session/styles/selector.d.ts +13 -1
  43. package/dist/src/session/styles/sources.d.ts +9 -0
  44. package/package.json +1 -1
  45. package/dist/chunks/Algorithm-RQ629NLb.js +0 -494
  46. package/dist/chunks/cameras-ii4vngYY.js +0 -435
  47. package/dist/chunks/paletteRegistry-DnWHQsAD.js +0 -3166
  48. package/dist/chunks/scales-DyuwlJKI.js +0 -6087
@@ -1,3166 +0,0 @@
1
- import { z as j } from "zod/v4";
2
- import { b as Le, q as Z, G as De } from "./GraphtyLogger-5KEttFUo.js";
3
- import { A as Ce } from "./Algorithm-RQ629NLb.js";
4
- import { G as w } from "./GraphtyError-BwcnblTH.js";
5
- import { a as Ge, b as Ue, d as He } from "./types-B7bX5c0K.js";
6
- import { remapArray as se, INVALID_INDEX as W } from "@graphty/graph-format";
7
- import { c as Be } from "./common-DWNKjpH_.js";
8
- const We = 12;
9
- function Rn(t, e = {}) {
10
- return (Je(t) ? Ve(t) : qe(t)).map((r) => {
11
- const i = { ...r.meta, ...e.meta?.[r.name] }, s = r.node === null ? Ke(r.name, i, r.failure ?? "the schema could not be converted") : Xe(r.name, r.node, i), o = e.overrides?.[r.name];
12
- return o === void 0 ? s : { ...s, ...o };
13
- });
14
- }
15
- function Je(t) {
16
- return typeof t != "object" || t === null ? !1 : "_zod" in t || "_def" in t;
17
- }
18
- function Ve(t) {
19
- const e = Oe(t);
20
- if (e === null)
21
- return [];
22
- const { properties: n } = e;
23
- return n === void 0 ? [] : Object.entries(n).map(([r, i]) => ({ name: r, node: ke(i, e) }));
24
- }
25
- function qe(t) {
26
- const e = Object.entries(t), n = {};
27
- for (const [s, o] of e)
28
- n[s] = o.schema;
29
- const r = Ye(n), i = r?.properties;
30
- return e.map(([s, o]) => {
31
- const a = i?.[s];
32
- return r === null || a === void 0 ? {
33
- name: s,
34
- node: null,
35
- failure: "the schema could not be converted to JSON Schema",
36
- meta: o.meta
37
- } : { name: s, node: ke(a, r), meta: o.meta };
38
- });
39
- }
40
- function Oe(t) {
41
- try {
42
- return j.toJSONSchema(t, { io: "input", unrepresentable: "any" });
43
- } catch {
44
- return null;
45
- }
46
- }
47
- function Ye(t) {
48
- try {
49
- return Oe(j.object(t));
50
- } catch {
51
- return null;
52
- }
53
- }
54
- const oe = "#/$defs/";
55
- function ke(t, e) {
56
- const n = t.$ref;
57
- if (n === void 0 || !n.startsWith(oe))
58
- return t;
59
- const r = n.slice(oe.length).replaceAll("~1", "/").replaceAll("~0", "~");
60
- return e.$defs?.[r] ?? t;
61
- }
62
- function Xe(t, e, n) {
63
- const r = Se(e, 0), i = Ze(r), s = {
64
- name: t,
65
- plainName: n.label ?? Q(t),
66
- technicalName: t,
67
- type: i.type
68
- }, o = n.description ?? e.description ?? i.node?.description;
69
- o !== void 0 && (s.description = o), "default" in e && (s.default = e.default);
70
- const a = i.node;
71
- if (a !== void 0 && (i.type === "number" || i.type === "integer")) {
72
- const c = ae(a.minimum ?? a.exclusiveMinimum), p = ae(a.maximum ?? a.exclusiveMaximum);
73
- c !== void 0 && (s.min = c), p !== void 0 && (s.max = p);
74
- }
75
- const l = n.step ?? a?.multipleOf;
76
- return l !== void 0 && (s.step = l), i.values !== void 0 && (s.values = i.values), n.group !== void 0 && (s.group = n.group), n.advanced !== void 0 && (s.advanced = n.advanced), i.unsupportedReason !== void 0 && (s.unsupportedReason = i.unsupportedReason), s;
77
- }
78
- function Ke(t, e, n) {
79
- const r = {
80
- name: t,
81
- plainName: e.label ?? Q(t),
82
- technicalName: t,
83
- type: "unknown",
84
- unsupportedReason: n
85
- };
86
- return e.description !== void 0 && (r.description = e.description), e.step !== void 0 && (r.step = e.step), e.group !== void 0 && (r.group = e.group), e.advanced !== void 0 && (r.advanced = e.advanced), r;
87
- }
88
- function Se(t, e) {
89
- if (e >= We)
90
- return { arms: [], nullable: !1, unresolvedRef: !0 };
91
- if (t.$ref !== void 0)
92
- return { arms: [], nullable: !1, unresolvedRef: !0 };
93
- const n = t.anyOf ?? t.oneOf;
94
- if (n === void 0)
95
- return t.type === "null" ? { arms: [], nullable: !0, unresolvedRef: !1 } : { arms: [t], nullable: !1, unresolvedRef: !1 };
96
- const r = { arms: [], nullable: !1, unresolvedRef: !1 };
97
- for (const i of n) {
98
- const s = Se(i, e + 1);
99
- r.arms.push(...s.arms), r.nullable = r.nullable || s.nullable, r.unresolvedRef = r.unresolvedRef || s.unresolvedRef;
100
- }
101
- return r;
102
- }
103
- function Ze(t) {
104
- if (t.arms.length === 0)
105
- return t.unresolvedRef ? {
106
- type: "unknown",
107
- unsupportedReason: "the schema is recursive, so it has no single value type"
108
- } : {
109
- type: "unknown",
110
- unsupportedReason: t.nullable ? "the only value the schema accepts is null" : "the schema declares no value type"
111
- };
112
- const e = et(t.arms);
113
- if (e !== void 0)
114
- return { type: "enum", node: t.arms[0], values: e };
115
- if (t.arms.length === 1)
116
- return Qe(t.arms[0]);
117
- const n = t.arms.map((r) => Ae(r));
118
- return n.length === 2 && n.includes("string") && n.includes("number") ? { type: "node-id" } : {
119
- type: "unknown",
120
- unsupportedReason: `the schema is a union of ${n.join(" and ")}, which has no single control`
121
- };
122
- }
123
- function Qe(t) {
124
- switch (Ae(t)) {
125
- case "integer":
126
- return { type: "integer", node: t };
127
- case "number":
128
- return { type: "number", node: t };
129
- case "boolean":
130
- return { type: "boolean", node: t };
131
- case "string":
132
- return { type: "string", node: t };
133
- case "array":
134
- return {
135
- type: "unknown",
136
- node: t,
137
- unsupportedReason: "an array option has no descriptor type; give it one with an override"
138
- };
139
- case "object":
140
- return {
141
- type: "unknown",
142
- node: t,
143
- unsupportedReason: "an object or record option has no descriptor type; give it one with an override"
144
- };
145
- default:
146
- return {
147
- type: "unknown",
148
- node: t,
149
- unsupportedReason: "the Zod construct has no JSON Schema type, so its values cannot be classified"
150
- };
151
- }
152
- }
153
- function et(t) {
154
- const e = [];
155
- for (const r of t)
156
- if (r.enum !== void 0)
157
- e.push(...r.enum);
158
- else if ("const" in r)
159
- e.push(r.const);
160
- else
161
- return;
162
- if (e.length === 0)
163
- return;
164
- const n = [];
165
- for (const r of e) {
166
- if (typeof r != "string" && typeof r != "number" && typeof r != "boolean")
167
- return;
168
- const i = String(r);
169
- n.push({ value: i, label: Q(i) });
170
- }
171
- return n;
172
- }
173
- function Ae(t) {
174
- const { type: e } = t;
175
- return typeof e == "string" ? e : Array.isArray(e) && e.length > 0 ? e[0] : "none";
176
- }
177
- function ae(t) {
178
- if (!(t === Number.MAX_SAFE_INTEGER || t === -Number.MAX_SAFE_INTEGER))
179
- return t;
180
- }
181
- function Q(t) {
182
- const e = t.replace(/([a-z0-9])([A-Z])/gu, "$1 $2").replace(/[_-]+/gu, " ").trim();
183
- return e === "" ? t : e.split(/\s+/u).map((n) => n.charAt(0).toUpperCase() + n.slice(1)).join(" ");
184
- }
185
- const ce = 3, tt = 0.1;
186
- function f(t, e) {
187
- return Number.isInteger(t) ? t.toLocaleString(e) : Number(t.toFixed(ce)).toLocaleString(e, { maximumFractionDigits: ce });
188
- }
189
- function nt(t, e, n) {
190
- const r = t.fields.find((i) => i.name === e);
191
- return r === void 0 ? e : n === "technical" ? r.technicalName : r.plainName.toLowerCase();
192
- }
193
- function G(t, e, n) {
194
- const r = t.fields.find((i) => i.name === e)?.unit;
195
- return r === void 0 ? "" : n === 1 && r.endsWith("s") ? ` ${r.slice(0, -1)}` : ` ${r}`;
196
- }
197
- function R(t, e) {
198
- const n = t.graph[e];
199
- return typeof n == "number" && Number.isFinite(n) ? n : void 0;
200
- }
201
- function rt(t) {
202
- return t[0];
203
- }
204
- function it(t, e, n) {
205
- const { locale: r, audience: i } = n, s = nt(t, "value", i), o = rt(e.top);
206
- if (o === void 0 || e.median === null)
207
- return `Nothing was measured, so there is no ${s} to report.`;
208
- const a = [
209
- `${o.label} has the highest ${s}, at ${f(o.value, r)}${G(t, "value", o.value)}.`,
210
- `The typical element sits at ${f(e.median, r)}${G(t, "value", e.median)}.`
211
- ];
212
- return e.measured > 0 && e.tiedAtMin / e.measured >= tt && e.min !== null && a.push(
213
- `${f(e.tiedAtMin, r)} of ${f(e.measured, r)} sit at the lowest value, ${f(e.min, r)}${G(t, "value", e.min)}.`
214
- ), e.count > e.measured && a.push(`${f(e.count - e.measured, r)} were not measured.`), a.join(" ");
215
- }
216
- function st(t, e, n) {
217
- const { locale: r } = n, i = e.groups ?? [];
218
- if (i.length === 0)
219
- return "Nothing was grouped, so there are no groups to report.";
220
- const s = i[0], o = [
221
- `${f(i.length, r)} ${i.length === 1 ? "group" : "groups"} were found,`,
222
- `the largest holding ${f(s.size, r)} of ${f(e.measured, r)}.`
223
- ], a = R(t, "modularity");
224
- return a !== void 0 && o.push(`Modularity is ${f(a, r)}.`), o.join(" ");
225
- }
226
- function ot(t, e) {
227
- const { locale: n } = e, r = R(t, "hops"), i = R(t, "cost");
228
- if (r === void 0 && i === void 0)
229
- return "No route was found.";
230
- const s = [];
231
- return r !== void 0 && s.push(`The route runs ${f(r, n)} ${r === 1 ? "hop" : "hops"}.`), i !== void 0 && s.push(`Its total cost is ${f(i, n)}.`), s.join(" ");
232
- }
233
- function at(t, e, n) {
234
- const { locale: r } = n, i = R(t, "count") ?? e.measured, s = t.shape === "edge-set" ? "edge" : "node";
235
- return i === 0 ? `No ${s}s were selected.` : `${f(i, r)} ${i === 1 ? s : `${s}s`} were selected.`;
236
- }
237
- function ct(t, e) {
238
- const { pairs: n } = t.graph, r = Array.isArray(n) ? n.length : 0;
239
- return r === 0 ? "No pairs were found." : `${f(r, e.locale)} ${r === 1 ? "pair was" : "pairs were"} found.`;
240
- }
241
- function lt(t, e) {
242
- const n = R(t, "steps");
243
- return n === void 0 || n === 0 ? "The series is empty." : `The series covers ${f(n, e.locale)} ${n === 1 ? "step" : "steps"}.`;
244
- }
245
- function le(t, e) {
246
- return t.measured === 0 ? "The run produced no values." : `The run covered ${f(t.measured, e.locale)} of ${f(t.count, e.locale)}.`;
247
- }
248
- function ut(t, e) {
249
- const n = t.summary(), { shape: r } = t;
250
- switch (r) {
251
- case "node-metric":
252
- case "edge-metric":
253
- return it(t, n, e);
254
- case "community":
255
- case "layered-grouping":
256
- case "category-table":
257
- return st(t, n, e);
258
- case "path":
259
- return ot(t, e);
260
- case "node-set":
261
- case "edge-set":
262
- return at(t, n, e);
263
- case "pair-list":
264
- return ct(t, e);
265
- case "temporal":
266
- return lt(t, e);
267
- case "fact":
268
- return le(n, e);
269
- default:
270
- return le(n, e);
271
- }
272
- }
273
- function _n(t, e) {
274
- const n = e === "node", r = n ? t.nodeValue : t.edgeValue, i = n ? t.nodeHas : t.edgeHas, s = n ? t.nodeIdOf : t.edgeIdOf;
275
- return {
276
- value: r,
277
- has: i ?? ((o, a) => dt(r(o, a))),
278
- idOf: s ?? null
279
- };
280
- }
281
- function dt(t) {
282
- return t != null;
283
- }
284
- function M(t) {
285
- return t == null || t === !1 || t === "" ? !1 : Array.isArray(t) ? t.length > 0 : typeof t == "object" ? Object.keys(t).length > 0 : !0;
286
- }
287
- function J(t, e) {
288
- if (t === e)
289
- return !0;
290
- if (Array.isArray(t) && Array.isArray(e))
291
- return t.length === e.length && t.every((n, r) => J(n, e[r]));
292
- if (ue(t) && ue(e)) {
293
- const n = Object.keys(t);
294
- return n.length === Object.keys(e).length && n.every((r) => J(t[r], e[r]));
295
- }
296
- return !1;
297
- }
298
- function ue(t) {
299
- return typeof t == "object" && t !== null && !Array.isArray(t);
300
- }
301
- function m(t, e, n, r = {}) {
302
- return new w({
303
- code: "E_BAD_SELECTOR",
304
- message: `${t} (at character ${String(n)} of ${JSON.stringify(e)})`,
305
- source: "style",
306
- details: { ...r, position: n, where: e }
307
- });
308
- }
309
- const ht = Object.freeze({
310
- "[": "index, slice, wildcard and filter expressions",
311
- "]": "index, slice, wildcard and filter expressions",
312
- "{": "multi-select hashes",
313
- "}": "multi-select hashes",
314
- ",": "multi-select lists",
315
- "*": "wildcards",
316
- "@": "the current-node reference",
317
- ":": "slices"
318
- }), pt = ["&&", "||", "==", "!=", "<=", ">=", "!", "<", ">", ".", "(", ")"];
319
- function Fe(t) {
320
- return /[A-Za-z_]/.test(t);
321
- }
322
- function Ee(t) {
323
- return /[A-Za-z0-9_]/.test(t);
324
- }
325
- function U(t, e, n) {
326
- for (let r = e + 1; r < t.length; r++) {
327
- if (t[r] === "\\") {
328
- r++;
329
- continue;
330
- }
331
- if (t[r] === n)
332
- return r;
333
- }
334
- throw m(`A ${n} is opened and never closed`, t, e, { delimiter: n });
335
- }
336
- function ft(t) {
337
- let e = "";
338
- for (let n = 0; n < t.length; n++) {
339
- if (t[n] === "\\" && (t[n + 1] === "'" || t[n + 1] === "\\")) {
340
- e += t[n + 1], n++;
341
- continue;
342
- }
343
- e += t[n];
344
- }
345
- return e;
346
- }
347
- function mt(t, e, n) {
348
- const r = n.replace(/\\`/g, "`");
349
- try {
350
- return JSON.parse(r);
351
- } catch {
352
- throw m(
353
- `A literal between backticks must be JSON, so a bare word needs quotes: write \`"${r}"\` rather than \`${r}\``,
354
- t,
355
- e,
356
- { literal: r }
357
- );
358
- }
359
- }
360
- function gt(t, e, n) {
361
- try {
362
- const r = JSON.parse(n);
363
- if (typeof r == "string")
364
- return r;
365
- } catch {
366
- }
367
- throw m(
368
- `A quoted attribute name follows JSON's rules for a string, and ${n} does not`,
369
- t,
370
- e,
371
- { name: n }
372
- );
373
- }
374
- function yt(t) {
375
- const e = [];
376
- let n = 0;
377
- for (; n < t.length; ) {
378
- const r = t.charAt(n);
379
- if (/\s/.test(r)) {
380
- n++;
381
- continue;
382
- }
383
- const i = ht[r];
384
- if (i !== void 0)
385
- throw m(`A selector does not support ${i}`, t, n, { construct: i });
386
- if (r === "|" && t.charAt(n + 1) !== "|")
387
- throw m("A selector does not support pipe expressions", t, n, { construct: "pipe expressions" });
388
- if (r === "&" && t.charAt(n + 1) !== "&")
389
- throw m("A selector does not support expression references", t, n, {
390
- construct: "expression references"
391
- });
392
- const s = pt.find((o) => t.startsWith(o, n));
393
- if (s !== void 0) {
394
- e.push({ kind: "operator", text: s, at: n }), n += s.length;
395
- continue;
396
- }
397
- if (r === "`") {
398
- const o = U(t, n, "`");
399
- e.push({ kind: "literal", text: "", value: mt(t, n, t.slice(n + 1, o)), at: n }), n = o + 1;
400
- continue;
401
- }
402
- if (r === "'") {
403
- const o = U(t, n, "'");
404
- e.push({ kind: "literal", text: "", value: ft(t.slice(n + 1, o)), at: n }), n = o + 1;
405
- continue;
406
- }
407
- if (r === '"') {
408
- const o = U(t, n, '"');
409
- e.push({ kind: "identifier", text: gt(t, n, t.slice(n, o + 1)), at: n }), n = o + 1;
410
- continue;
411
- }
412
- if (Fe(r)) {
413
- let o = n + 1;
414
- for (; o < t.length && Ee(t.charAt(o)); )
415
- o++;
416
- e.push({ kind: "identifier", text: t.slice(n, o), at: n }), n = o;
417
- continue;
418
- }
419
- throw /[0-9-]/.test(r) ? m(
420
- "A number in a selector goes between backticks, so write `5` rather than 5",
421
- t,
422
- n,
423
- { character: r }
424
- ) : m(`${JSON.stringify(r)} is not something a selector can contain`, t, n, {
425
- character: r
426
- });
427
- }
428
- return e.push({ kind: "end", text: "", at: t.length }), e;
429
- }
430
- const bt = Object.freeze({
431
- "!=": "!=",
432
- "<": "<",
433
- "<=": "<=",
434
- "==": "==",
435
- ">": ">",
436
- ">=": ">="
437
- });
438
- function F(t) {
439
- return t.tokens[t.index];
440
- }
441
- function E(t, e) {
442
- const n = F(t);
443
- return n.kind === "operator" && n.text === e;
444
- }
445
- function wt(t) {
446
- const e = F(t), n = [];
447
- for (; ; ) {
448
- const r = F(t);
449
- if (r.kind !== "identifier")
450
- throw m("A `.` must be followed by an attribute name", t.where, r.at);
451
- if (r.text.includes("."))
452
- throw m(
453
- `A quoted attribute name may not contain a dot: ${JSON.stringify(r.text)} would be indistinguishable from two names`,
454
- t.where,
455
- r.at,
456
- { segment: r.text }
457
- );
458
- if (t.index++, E(t, "("))
459
- throw m(
460
- `A selector does not support functions, so ${r.text}(...) cannot be called here`,
461
- t.where,
462
- r.at,
463
- { construct: "functions", function: r.text }
464
- );
465
- if (n.push(r.text), !E(t, "."))
466
- return { kind: "path", path: n.join("."), at: e.at };
467
- t.index++;
468
- }
469
- }
470
- function vt(t) {
471
- const e = F(t);
472
- if (e.kind === "literal")
473
- return t.index++, { kind: "literal", value: e.value };
474
- if (e.kind === "identifier")
475
- return wt(t);
476
- if (E(t, "(")) {
477
- t.index++;
478
- const n = Ie(t);
479
- if (!E(t, ")"))
480
- throw m("A `(` is opened and never closed", t.where, e.at);
481
- return t.index++, { kind: "group", inner: n };
482
- }
483
- throw m(
484
- e.kind === "end" ? "The selector ends where an attribute name, a literal or a `(` should be" : `${JSON.stringify(e.text)} is where an attribute name, a literal or a \`(\` should be`,
485
- t.where,
486
- e.at
487
- );
488
- }
489
- function V(t) {
490
- if (!E(t, "!"))
491
- return vt(t);
492
- const e = F(t);
493
- t.index++;
494
- const n = V(t);
495
- if (n.kind === "path" && n.path.includes("."))
496
- throw m(
497
- `In JMESPath \`!\` binds tighter than \`.\`, so !${n.path} means (!${n.path.split(".")[0]})${n.path.slice(n.path.indexOf("."))} rather than the opposite of ${n.path}. Write !(${n.path}) for that`,
498
- t.where,
499
- e.at,
500
- { path: n.path }
501
- );
502
- return { kind: "not", operand: n };
503
- }
504
- function de(t) {
505
- const e = V(t), n = F(t), r = n.kind === "operator" ? bt[n.text] : void 0;
506
- return r === void 0 ? e : (t.index++, { kind: "compare", operator: r, left: e, right: V(t) });
507
- }
508
- function he(t) {
509
- let e = de(t);
510
- for (; E(t, "&&"); )
511
- t.index++, e = { kind: "and", left: e, right: de(t) };
512
- return e;
513
- }
514
- function Ie(t) {
515
- let e = he(t);
516
- for (; E(t, "||"); )
517
- t.index++, e = { kind: "or", left: e, right: he(t) };
518
- return e;
519
- }
520
- function Nt(t) {
521
- const e = { where: t, tokens: yt(t), index: 0 }, n = Ie(e), r = F(e);
522
- if (r.kind !== "end")
523
- throw m(
524
- `${JSON.stringify(r.text)} is left over at the end of the selector`,
525
- t,
526
- r.at
527
- );
528
- return n;
529
- }
530
- function $(t, e) {
531
- switch (t.kind) {
532
- case "path":
533
- e.includes(t.path) || e.push(t.path);
534
- return;
535
- case "group":
536
- $(t.inner, e);
537
- return;
538
- case "not":
539
- $(t.operand, e);
540
- return;
541
- case "and":
542
- case "or":
543
- case "compare":
544
- $(t.left, e), $(t.right, e);
545
- return;
546
- default:
547
- return;
548
- }
549
- }
550
- function b(t, e) {
551
- switch (t.kind) {
552
- case "path": {
553
- const { path: n } = t, { value: r } = e;
554
- return (i) => {
555
- const s = r(i, n);
556
- return s === void 0 ? null : s;
557
- };
558
- }
559
- case "literal": {
560
- const { value: n } = t;
561
- return () => n;
562
- }
563
- case "group":
564
- return b(t.inner, e);
565
- case "not": {
566
- const n = b(t.operand, e);
567
- return (r) => !M(n(r));
568
- }
569
- case "and": {
570
- const n = b(t.left, e), r = b(t.right, e);
571
- return (i) => {
572
- const s = n(i);
573
- return M(s) ? r(i) : s;
574
- };
575
- }
576
- case "or": {
577
- const n = b(t.left, e), r = b(t.right, e);
578
- return (i) => {
579
- const s = n(i);
580
- return M(s) ? s : r(i);
581
- };
582
- }
583
- default:
584
- return St(t.operator, t.left, t.right, e);
585
- }
586
- }
587
- const Ot = Object.freeze({
588
- "<": (t, e) => t < e,
589
- "<=": (t, e) => t <= e,
590
- ">": (t, e) => t > e,
591
- ">=": (t, e) => t >= e
592
- });
593
- function kt(t, e) {
594
- return t.kind === "literal" ? t : e.kind === "literal" ? e : null;
595
- }
596
- function St(t, e, n, r) {
597
- if (t === "==" || t === "!=") {
598
- const a = t === "==", l = e.kind === "literal", c = kt(e, n), p = l ? n : e;
599
- if (c !== null && c.value === null && p.kind === "path") {
600
- const { path: d } = p, { has: v } = r;
601
- return (O) => v(O, d) !== a;
602
- }
603
- const h = b(e, r), u = b(n, r);
604
- if (c !== null && c.value !== null && typeof c.value != "object") {
605
- const { value: d } = c, v = l ? u : h;
606
- return (O) => v(O) === d === a;
607
- }
608
- return (d) => J(h(d), u(d)) === a;
609
- }
610
- const i = b(e, r), s = b(n, r), o = Ot[t];
611
- return (a) => {
612
- const l = i(a), c = s(a);
613
- return typeof l == "number" && typeof c == "number" ? o(l, c) : null;
614
- };
615
- }
616
- function x(t, e) {
617
- switch (t.kind) {
618
- case "compare":
619
- case "not": {
620
- const n = b(t, e);
621
- return (r) => n(r) === !0;
622
- }
623
- case "group":
624
- return x(t.inner, e);
625
- case "and": {
626
- const n = x(t.left, e), r = x(t.right, e);
627
- return (i) => n(i) && r(i);
628
- }
629
- case "or": {
630
- const n = x(t.left, e), r = x(t.right, e);
631
- return (i) => n(i) || r(i);
632
- }
633
- default: {
634
- const n = b(t, e);
635
- return (r) => M(n(r));
636
- }
637
- }
638
- }
639
- function At(t) {
640
- return t.split(".").map((e) => Ft(e) ? e : JSON.stringify(e)).join(".");
641
- }
642
- function Ft(t) {
643
- if (t === "" || !Fe(t[0]))
644
- return !1;
645
- for (let e = 1; e < t.length; e++)
646
- if (!Ee(t[e]))
647
- return !1;
648
- return !0;
649
- }
650
- function zn(t, e) {
651
- const { has: n } = t;
652
- return (r) => n(r, e);
653
- }
654
- function Mn(t, e) {
655
- const { idOf: n } = t;
656
- if (n === null)
657
- throw new w({
658
- code: "E_UNSUPPORTED",
659
- message: 'This session cannot evaluate an "ids" selector, because it cannot say which id sits at which row. Painting the layer anyway would colour elements it never named.',
660
- source: "style",
661
- details: { match: "ids" }
662
- });
663
- return (r) => e.has(n(r));
664
- }
665
- function Pn(t, e) {
666
- const n = Nt(t), r = [];
667
- if ($(n, r), r.length === 0) {
668
- let i = n;
669
- for (; i.kind === "group"; )
670
- i = i.inner;
671
- const s = i.kind === "literal" && typeof i.value == "string";
672
- throw m(
673
- s ? "The selector is a quoted string literal, which is always true, so it would match every element. Remove the outer quotes so it is read as an expression" : 'The selector reads no attribute of the element, so it answers the same for every element. Write { match: "everything" } for a layer that really is meant to paint the whole graph',
674
- t,
675
- 0,
676
- { constant: !0 }
677
- );
678
- }
679
- return { paths: Object.freeze(r), test: x(n, e) };
680
- }
681
- const q = "results", Et = "$";
682
- function P(t, e) {
683
- return e === void 0 ? `${q}.${t}` : `${q}.${t}.${e}`;
684
- }
685
- const xe = {
686
- // -- per element ---------------------------------------------------------------------
687
- value: {
688
- meaning: "The number this metric measured for this element.",
689
- scope: "element",
690
- types: ["number", "integer"]
691
- },
692
- rank: {
693
- meaning: "Where this element sits when every measured element is ordered best first, starting at 1.",
694
- scope: "element",
695
- types: ["integer"]
696
- },
697
- percentile: {
698
- meaning: "The share of measured elements this one ranks at or above, from 0 to 1.",
699
- scope: "element",
700
- types: ["number"]
701
- },
702
- group: {
703
- meaning: "Which group this node was placed in.",
704
- scope: "element",
705
- types: ["integer", "string"]
706
- },
707
- groupSize: {
708
- meaning: "How many nodes share this node's group.",
709
- scope: "element",
710
- types: ["integer"]
711
- },
712
- level: {
713
- meaning: "How many levels out from the start this node sits, counting the start as 0.",
714
- scope: "element",
715
- types: ["integer"]
716
- },
717
- levelSize: {
718
- meaning: "How many nodes share this node's level.",
719
- scope: "element",
720
- types: ["integer"]
721
- },
722
- category: {
723
- meaning: "The category this node was sorted into.",
724
- scope: "element",
725
- types: ["string"]
726
- },
727
- score: {
728
- meaning: "How strongly this node belongs to its category.",
729
- scope: "element",
730
- types: ["number"]
731
- },
732
- onPath: {
733
- meaning: "Whether this element is on the route the run found.",
734
- scope: "element",
735
- types: ["boolean"]
736
- },
737
- order: {
738
- meaning: "This node's position along the route, counting the source as 0.",
739
- scope: "element",
740
- types: ["integer"]
741
- },
742
- in: {
743
- meaning: "Whether this element is in the set the run selected.",
744
- scope: "element",
745
- types: ["boolean"]
746
- },
747
- // -- per graph -----------------------------------------------------------------------
748
- min: {
749
- meaning: "The lowest value any measured element carries.",
750
- scope: "graph",
751
- types: ["number"]
752
- },
753
- max: {
754
- meaning: "The highest value any measured element carries.",
755
- scope: "graph",
756
- types: ["number"]
757
- },
758
- median: {
759
- meaning: "The middle value once the measured elements are ordered.",
760
- scope: "graph",
761
- types: ["number"]
762
- },
763
- mean: {
764
- meaning: "The average value across the measured elements.",
765
- scope: "graph",
766
- types: ["number"]
767
- },
768
- measured: {
769
- meaning: "How many elements the run produced a value for. Smaller than the scope when some elements have nothing to measure. Distinct from RunResult.measured, which counts the nodes and edges the run looked at.",
770
- scope: "graph",
771
- types: ["integer"]
772
- },
773
- normalization: {
774
- meaning: 'How the values were scaled before publication: "max", "min-max" or "none".',
775
- scope: "graph",
776
- types: ["string"]
777
- },
778
- tiedAtMin: {
779
- meaning: "How many measured elements sit at the lowest value, which is what says whether a colour ramp is about to paint most of the graph one colour.",
780
- scope: "graph",
781
- types: ["integer"]
782
- },
783
- groupCount: {
784
- meaning: "How many groups the partition has.",
785
- scope: "graph",
786
- types: ["integer"]
787
- },
788
- modularity: {
789
- meaning: "How much better this partition is than a random one, from -0.5 to 1.",
790
- scope: "graph",
791
- types: ["number"]
792
- },
793
- sizes: {
794
- meaning: "The size of each group or level, largest first.",
795
- scope: "graph",
796
- types: ["table"]
797
- },
798
- levelCount: {
799
- meaning: "How many levels the walk reached.",
800
- scope: "graph",
801
- types: ["integer"]
802
- },
803
- categories: {
804
- meaning: "One row per category, with its name and how many elements fell into it.",
805
- scope: "graph",
806
- types: ["table"]
807
- },
808
- length: {
809
- meaning: "How many nodes are on the route.",
810
- scope: "graph",
811
- types: ["integer"]
812
- },
813
- cost: {
814
- meaning: "What the route costs in total, summing the weight of every edge on it.",
815
- scope: "graph",
816
- types: ["number"]
817
- },
818
- hops: {
819
- meaning: "How many edges are on the route.",
820
- scope: "graph",
821
- types: ["integer"]
822
- },
823
- count: {
824
- meaning: "How many elements are in the set.",
825
- scope: "graph",
826
- types: ["integer"]
827
- },
828
- pairs: {
829
- meaning: "One row per scored pair of elements, best first.",
830
- scope: "graph",
831
- types: ["table"]
832
- },
833
- steps: {
834
- meaning: "One row per time step, with the step's window and what was visible in it.",
835
- scope: "graph",
836
- types: ["table"]
837
- },
838
- series: {
839
- meaning: "One row per tracked quantity, carrying its value at every step.",
840
- scope: "graph",
841
- types: ["table"]
842
- },
843
- rates: {
844
- meaning: "One row per tracked quantity, carrying how fast it changed between steps.",
845
- scope: "graph",
846
- types: ["table"]
847
- },
848
- changeThreshold: {
849
- meaning: "The rate of change above which a step counts as a change rather than drift.",
850
- scope: "graph",
851
- types: ["number"]
852
- }
853
- }, jn = Object.keys(xe), ee = {
854
- "node-metric": {
855
- shape: "node-metric",
856
- primaryField: "value",
857
- nodeFields: ["value", "rank", "percentile"],
858
- edgeFields: [],
859
- graphFields: ["min", "max", "median", "mean", "measured", "normalization", "tiedAtMin"],
860
- optionalGraphFields: [],
861
- headlineScalar: !1,
862
- layer: "encoding"
863
- },
864
- "edge-metric": {
865
- shape: "edge-metric",
866
- primaryField: "value",
867
- nodeFields: [],
868
- edgeFields: ["value", "rank", "percentile"],
869
- graphFields: ["min", "max", "median", "mean", "measured", "normalization", "tiedAtMin"],
870
- optionalGraphFields: [],
871
- headlineScalar: !1,
872
- layer: "encoding"
873
- },
874
- community: {
875
- shape: "community",
876
- primaryField: "group",
877
- nodeFields: ["group", "groupSize"],
878
- edgeFields: [],
879
- graphFields: ["groupCount", "sizes"],
880
- // Only an algorithm that scores its own partition can publish modularity. Label
881
- // propagation and connected components do not, and a required field they cannot fill
882
- // would be a number invented to satisfy a table.
883
- optionalGraphFields: ["modularity"],
884
- headlineScalar: !1,
885
- layer: "encoding"
886
- },
887
- "layered-grouping": {
888
- shape: "layered-grouping",
889
- primaryField: "level",
890
- nodeFields: ["level", "levelSize"],
891
- edgeFields: [],
892
- graphFields: ["levelCount", "sizes"],
893
- optionalGraphFields: [],
894
- headlineScalar: !1,
895
- layer: "encoding"
896
- },
897
- "category-table": {
898
- shape: "category-table",
899
- primaryField: "category",
900
- nodeFields: ["category", "score", "rank"],
901
- edgeFields: [],
902
- graphFields: ["categories"],
903
- optionalGraphFields: [],
904
- headlineScalar: !1,
905
- layer: "encoding"
906
- },
907
- path: {
908
- shape: "path",
909
- primaryField: "onPath",
910
- nodeFields: ["onPath", "order"],
911
- // The edges of the route carry membership but not a position: an edge's place in the
912
- // route is the order of the node it leaves.
913
- edgeFields: ["onPath"],
914
- graphFields: ["length", "cost", "hops"],
915
- optionalGraphFields: [],
916
- headlineScalar: !1,
917
- layer: "highlight"
918
- },
919
- "node-set": {
920
- shape: "node-set",
921
- primaryField: "in",
922
- nodeFields: ["in"],
923
- edgeFields: [],
924
- graphFields: ["count"],
925
- optionalGraphFields: [],
926
- headlineScalar: !0,
927
- layer: "highlight"
928
- },
929
- "edge-set": {
930
- shape: "edge-set",
931
- primaryField: "in",
932
- nodeFields: [],
933
- edgeFields: ["in"],
934
- graphFields: ["count"],
935
- optionalGraphFields: [],
936
- headlineScalar: !0,
937
- layer: "highlight"
938
- },
939
- "pair-list": {
940
- shape: "pair-list",
941
- primaryField: "pairs",
942
- nodeFields: [],
943
- edgeFields: [],
944
- graphFields: ["pairs"],
945
- optionalGraphFields: [],
946
- headlineScalar: !1,
947
- layer: "none"
948
- },
949
- temporal: {
950
- shape: "temporal",
951
- primaryField: "series",
952
- nodeFields: [],
953
- edgeFields: [],
954
- graphFields: ["steps", "series", "rates", "changeThreshold"],
955
- optionalGraphFields: [],
956
- headlineScalar: !1,
957
- layer: "none"
958
- },
959
- fact: {
960
- shape: "fact",
961
- primaryField: null,
962
- nodeFields: [],
963
- edgeFields: [],
964
- graphFields: [],
965
- optionalGraphFields: [],
966
- headlineScalar: !1,
967
- layer: "none"
968
- }
969
- };
970
- function Y(t) {
971
- return ee[t];
972
- }
973
- function Ln(t) {
974
- return ee[t].layer === "highlight";
975
- }
976
- function Dn(t, e) {
977
- const n = ee[t], r = [], i = [
978
- { names: n.nodeFields, kind: "node" },
979
- { names: n.edgeFields, kind: "edge" },
980
- { names: n.graphFields, kind: "graph" }
981
- ];
982
- for (const { names: s, kind: o } of i)
983
- for (const a of s) {
984
- const l = e.find((p) => p.name === a && p.kind === o);
985
- if (l === void 0) {
986
- r.push({
987
- field: a,
988
- kind: o,
989
- reason: `shape "${t}" requires a ${o} field named "${a}"`
990
- });
991
- continue;
992
- }
993
- const c = xe[a].types;
994
- c.includes(l.type) || r.push({
995
- field: a,
996
- kind: o,
997
- reason: `${o} field "${a}" is typed "${l.type}", not ${c.map((p) => `"${p}"`).join(" or ")}`
998
- });
999
- }
1000
- return n.headlineScalar && !It(n, e) && r.push({
1001
- field: null,
1002
- kind: "graph",
1003
- reason: `shape "${t}" requires one graph-level scalar of its own beside "count"`
1004
- }), r;
1005
- }
1006
- function It(t, e) {
1007
- const n = [...t.graphFields, ...t.optionalGraphFields];
1008
- return e.some((r) => r.kind === "graph" && !n.includes(r.name));
1009
- }
1010
- const xt = 3;
1011
- function Tt(t, e) {
1012
- if (t === e)
1013
- return 0;
1014
- const n = new Uint32Array(e.length + 1), r = new Uint32Array(e.length + 1);
1015
- for (let i = 0; i <= e.length; i++)
1016
- n[i] = i;
1017
- for (let i = 1; i <= t.length; i++) {
1018
- r[0] = i;
1019
- for (let s = 1; s <= e.length; s++) {
1020
- const o = n[s - 1] + (t[i - 1] === e[s - 1] ? 0 : 1);
1021
- r[s] = Math.min(r[s - 1] + 1, n[s] + 1, o);
1022
- }
1023
- n.set(r);
1024
- }
1025
- return n[e.length];
1026
- }
1027
- function $t(t, e, n = xt) {
1028
- const r = t.toLowerCase(), i = e.map((s) => ({
1029
- candidate: s,
1030
- distance: Tt(r, s.toLowerCase())
1031
- }));
1032
- return i.sort((s, o) => s.distance - o.distance || s.candidate.localeCompare(o.candidate)), Object.freeze(i.slice(0, Math.max(n, 0)).map((s) => s.candidate));
1033
- }
1034
- function pe(t) {
1035
- return typeof t == "string" ? t : "runId" in t ? t.runId : t.id;
1036
- }
1037
- function Rt(t) {
1038
- return typeof t == "string" ? void 0 : t.shape;
1039
- }
1040
- function _t(t, e) {
1041
- return t.fields.some((n) => n.name === e) || e in t.graph;
1042
- }
1043
- class zt {
1044
- #e;
1045
- /**
1046
- * Build the API over a registry.
1047
- * @param registry - Where runs are looked up.
1048
- */
1049
- constructor(e) {
1050
- this.#e = e;
1051
- }
1052
- /**
1053
- * The published path of a run's field.
1054
- *
1055
- * With no field the path names the run's primary field -- `value` for a metric, `group` for a
1056
- * partition, `onPath` for a route -- so a caller binds an encoding to a run without knowing
1057
- * what that algorithm calls its number. A `fact` result has no primary field and addresses
1058
- * its whole result object instead.
1059
- * @param run - The run, its result, or its id.
1060
- * @param field - The field name; the shape's primary field when absent.
1061
- * @returns The path.
1062
- */
1063
- path(e, n) {
1064
- const r = pe(e);
1065
- if (n !== void 0)
1066
- return P(r, n);
1067
- const i = Rt(e) ?? this.#e.entry(r)?.shape, s = i === void 0 ? null : Y(i).primaryField;
1068
- return s === null ? P(r) : P(r, s);
1069
- }
1070
- /**
1071
- * The same field, written so a selector expression can read it.
1072
- *
1073
- * The quoting is the whole point: a run id carries its algorithm's name, and a hyphenated
1074
- * name lexes its hyphen as subtraction, so a path pasted into an expression unquoted is a
1075
- * refused selector rather than a comparison. Nothing here decides WHICH field -- that is
1076
- * {@link ResultsApi.path}'s job and this defers to it -- so the two can never name different
1077
- * columns.
1078
- * @param run - The run, its result, or its id.
1079
- * @param field - The field name; the shape's primary field when absent.
1080
- * @returns The path with every segment quoted that needs it.
1081
- */
1082
- term(e, n) {
1083
- return At(n === void 0 ? this.path(e) : this.path(e, n));
1084
- }
1085
- /**
1086
- * One run's result.
1087
- *
1088
- * The answer always comes from the registry, even when the caller passed a result object: the
1089
- * question is what this session holds, and a result the session never registered is not it.
1090
- * @param run - The run, its result, or its id.
1091
- * @returns The result, or undefined when the session holds no such run or it has not
1092
- * finished.
1093
- */
1094
- get(e) {
1095
- return this.#e.entry(pe(e))?.result;
1096
- }
1097
- /**
1098
- * Whether a run, or one of its fields, is available to read.
1099
- * @param run - The run, its result, or its id.
1100
- * @param field - The field name; asks only about the run itself when absent.
1101
- * @returns True when the path would resolve.
1102
- */
1103
- has(e, n) {
1104
- const r = this.get(e);
1105
- return r === void 0 ? !1 : n === void 0 || _t(r, n);
1106
- }
1107
- /**
1108
- * Every run that has published a result, as an expression editor reads them.
1109
- * @returns One root per finished run, in the order the runs were started.
1110
- */
1111
- get roots() {
1112
- const e = [];
1113
- for (const n of this.#e.entries())
1114
- n.result !== void 0 && e.push(Object.freeze({ runId: n.id, label: n.label, fields: n.result.fields }));
1115
- return Object.freeze(e);
1116
- }
1117
- }
1118
- function Cn(t) {
1119
- return new zt(t);
1120
- }
1121
- const Mt = 20, H = 100, Pt = 100, jt = Object.freeze([]), Lt = Object.freeze(["max", "min-max", "none"]);
1122
- function Te(t) {
1123
- return typeof t == "string" && Lt.includes(t);
1124
- }
1125
- function Gn(t) {
1126
- return {
1127
- length: t.length,
1128
- get: (e) => t[e]
1129
- };
1130
- }
1131
- const Dt = Object.freeze({
1132
- measured: 0,
1133
- min: Number.NaN,
1134
- max: Number.NaN,
1135
- mean: Number.NaN,
1136
- median: Number.NaN,
1137
- tiedAtMin: 0,
1138
- smallestPositive: null
1139
- });
1140
- function $e(t) {
1141
- const { length: e } = t, n = new Float64Array(e);
1142
- let r = 0;
1143
- for (let u = 0; u < e; u++) {
1144
- const d = t.get(u);
1145
- Number.isFinite(d) && (n[r] = d, r++);
1146
- }
1147
- if (r === 0)
1148
- return { length: e, ...Dt };
1149
- let i = Number.POSITIVE_INFINITY, s = Number.NEGATIVE_INFINITY, o = Number.POSITIVE_INFINITY, a = 0, l = 0;
1150
- for (let u = 0; u < r; u++) {
1151
- const d = n[u];
1152
- d < i && (i = d), d > s && (s = d), d > 0 && d < o && (o = d);
1153
- const v = d - l, O = a + v;
1154
- l = O - a - v, a = O;
1155
- }
1156
- const c = n.slice(0, r).sort(), p = c[Math.floor((r - 1) / 2)];
1157
- let h = 0;
1158
- for (; h < r && c[h] === i; )
1159
- h++;
1160
- return {
1161
- length: e,
1162
- measured: r,
1163
- min: i,
1164
- max: s,
1165
- mean: a / r,
1166
- median: p,
1167
- tiedAtMin: h,
1168
- smallestPositive: Number.isFinite(o) ? o : null
1169
- };
1170
- }
1171
- function Re(t) {
1172
- const e = $e(t);
1173
- return { view: Object.freeze({
1174
- length: t.length,
1175
- get: (r) => t.get(r),
1176
- min: e.min,
1177
- max: e.max,
1178
- mean: e.mean,
1179
- median: e.median
1180
- }), statistics: e };
1181
- }
1182
- function te(t) {
1183
- const e = t.filter((o) => Number.isFinite(o.value)).map((o) => ({ entry: o, label: String(o.id) }));
1184
- e.sort((o, a) => o.entry.value !== a.entry.value ? a.entry.value - o.entry.value : o.label === a.label ? 0 : o.label < a.label ? -1 : 1);
1185
- const n = e.length, r = [];
1186
- let i = 0, s = Number.NaN;
1187
- for (let o = 0; o < n; o++) {
1188
- const { entry: a } = e[o];
1189
- a.value !== s && (i = o + 1, s = a.value), r.push(
1190
- Object.freeze({
1191
- id: a.id,
1192
- value: a.value,
1193
- rank: i,
1194
- percentile: (n - i + 1) / n
1195
- })
1196
- );
1197
- }
1198
- return Object.freeze(r);
1199
- }
1200
- function Ct(t) {
1201
- const { smallestPositive: e } = t;
1202
- if (t.measured === 0 || !(t.max > 0) || e === null)
1203
- return !1;
1204
- const n = t.min > 0 ? t.min : e;
1205
- return Math.log10(t.max) > Math.log10(n);
1206
- }
1207
- function Gt(t) {
1208
- return t.measured === 0 || !(t.median > 0) || t.max / t.median <= Pt ? "linear" : Ct(t) ? "log" : "linear";
1209
- }
1210
- function Ut(t, e, n, r) {
1211
- if (!(e > t))
1212
- return { binCount: 1, indexOf: () => 0, rangeOf: () => [t, e] };
1213
- if (r) {
1214
- const s = e - t + 1, o = Math.ceil(s / Math.min(s, n)), a = Math.ceil(s / o);
1215
- return {
1216
- binCount: a,
1217
- indexOf: (l) => Math.min(Math.floor((l - t) / o), a - 1),
1218
- rangeOf: (l) => {
1219
- const c = t + l * o;
1220
- return [c, Math.min(c + o - 1, e)];
1221
- }
1222
- };
1223
- }
1224
- const i = (e - t) / n;
1225
- return {
1226
- binCount: n,
1227
- indexOf: (s) => Math.min(Math.floor((s - t) / i), n - 1),
1228
- rangeOf: (s) => [
1229
- t + s * i,
1230
- s === n - 1 ? e : t + (s + 1) * i
1231
- ]
1232
- };
1233
- }
1234
- function Ht(t, e, n, r, i) {
1235
- const s = t > 0 ? 0 : 1, o = s === 0 ? t : n;
1236
- if (!(e > 0) || o === null || !(o > 0))
1237
- return null;
1238
- const a = Math.log10(o), l = Math.log10(e);
1239
- if (!(l > a))
1240
- return null;
1241
- const c = r - s;
1242
- if (c < 1)
1243
- return null;
1244
- const p = (l - a) / c;
1245
- return {
1246
- binCount: r,
1247
- indexOf: (h) => {
1248
- if (!(h > 0))
1249
- return 0;
1250
- const u = Math.floor((Math.log10(h) - a) / p);
1251
- return s + Math.min(Math.max(u, 0), c - 1);
1252
- },
1253
- rangeOf: (h) => {
1254
- if (h < s)
1255
- return [t, 0];
1256
- const u = h - s, d = u === 0 ? o : 10 ** (a + u * p), v = u === c - 1 ? e : 10 ** (a + (u + 1) * p);
1257
- if (!i)
1258
- return [d, v];
1259
- const O = u === 0 ? o : Math.ceil(d);
1260
- return [O, u === c - 1 ? e : Math.max(O, Math.ceil(v) - 1)];
1261
- }
1262
- };
1263
- }
1264
- function Bt(t) {
1265
- if (t === void 0)
1266
- return Mt;
1267
- if (!Number.isInteger(t) || t < 1 || t > H)
1268
- throw new w({
1269
- code: "E_OPTION_RANGE",
1270
- source: "run",
1271
- message: `A histogram takes between 1 and ${H} bins; ${String(t)} was asked for.`,
1272
- details: { option: "bins", value: t, min: 1, max: H }
1273
- });
1274
- return t;
1275
- }
1276
- function Wt(t, e) {
1277
- const n = /* @__PURE__ */ new Map();
1278
- for (let r = 0; r < t.length; r++) {
1279
- const i = t.get(r);
1280
- if (!Number.isFinite(i))
1281
- continue;
1282
- const s = n.get(i);
1283
- if (s === void 0) {
1284
- if (n.size >= e)
1285
- return null;
1286
- n.set(i, 1);
1287
- continue;
1288
- }
1289
- n.set(i, s + 1);
1290
- }
1291
- return n;
1292
- }
1293
- function Jt(t, e) {
1294
- const n = Bt(e?.bins), r = e?.integerValued ?? !1, i = $e(t), s = Gt(i), o = Wt(t, n);
1295
- if (o !== null) {
1296
- if (o.size === 0)
1297
- return Object.freeze({
1298
- bins: jt,
1299
- // Nothing was measured, so nothing is on any axis. Reported as linear rather
1300
- // than as the request, because "log scale" over an empty chart is the same false
1301
- // caption this whole return type exists to prevent.
1302
- scale: "linear",
1303
- suggestedScale: "linear",
1304
- binning: "empty"
1305
- });
1306
- const h = [...o.entries()].sort((u, d) => u[0] - d[0]).map(([u, d]) => Object.freeze({ from: u, to: u, count: d }));
1307
- return Object.freeze({
1308
- bins: Object.freeze(h),
1309
- // One bar per distinct value has no bands to space, so neither axis was applied to
1310
- // it. Saying "linear" here is what stops a caption claiming a logarithmic layout of
1311
- // a chart that has none.
1312
- scale: "linear",
1313
- suggestedScale: s,
1314
- binning: "per-value"
1315
- });
1316
- }
1317
- const l = e?.scale === "log" || e?.scale === "auto" && s === "log" ? Ht(i.min, i.max, i.smallestPositive, n, r) : null, c = l ?? Ut(i.min, i.max, n, r), p = new Array(c.binCount).fill(0);
1318
- for (let h = 0; h < t.length; h++) {
1319
- const u = t.get(h);
1320
- Number.isFinite(u) && (p[c.indexOf(u)] += 1);
1321
- }
1322
- return Object.freeze({
1323
- bins: Object.freeze(
1324
- p.map((h, u) => {
1325
- const [d, v] = c.rangeOf(u);
1326
- return Object.freeze({ from: d, to: v, count: h });
1327
- })
1328
- ),
1329
- // `plan` is null exactly when a logarithmic layout was asked for and could not be built,
1330
- // which is the case a caption must not get wrong.
1331
- scale: l === null ? "linear" : "log",
1332
- suggestedScale: s,
1333
- binning: "banded"
1334
- });
1335
- }
1336
- const Vt = 10, qt = 10;
1337
- function fe(t) {
1338
- const e = { ids: [], records: [], index: /* @__PURE__ */ new Map() };
1339
- if (t === void 0)
1340
- return e;
1341
- for (const n of t) {
1342
- const r = e.index.get(n.id);
1343
- if (r === void 0) {
1344
- e.index.set(n.id, e.ids.length), e.ids.push(n.id), e.records.push({ ...n.values });
1345
- continue;
1346
- }
1347
- Object.assign(e.records[r], n.values);
1348
- }
1349
- return e;
1350
- }
1351
- function X(t, e) {
1352
- const { records: n } = t;
1353
- return {
1354
- length: n.length,
1355
- get: (r) => _e(n[r][e])
1356
- };
1357
- }
1358
- function ne(t, e) {
1359
- return t.ids.map((n, r) => ({ id: n, value: _e(t.records[r][e]) }));
1360
- }
1361
- function _e(t) {
1362
- return typeof t == "number" ? t : Number.NaN;
1363
- }
1364
- function re(t) {
1365
- return typeof t == "string" || typeof t == "number";
1366
- }
1367
- function B(t) {
1368
- return t.type === "number" || t.type === "integer";
1369
- }
1370
- function y(t, e, n) {
1371
- t[e] === void 0 && (t[e] = n);
1372
- }
1373
- function L(t, e, n) {
1374
- t[e] === void 0 && (t[e] = n);
1375
- }
1376
- function ze(t, e) {
1377
- const n = /* @__PURE__ */ new Map();
1378
- for (const r of t.records) {
1379
- const i = r[e];
1380
- re(i) && n.set(i, (n.get(i) ?? 0) + 1);
1381
- }
1382
- return n;
1383
- }
1384
- function Me(t, e) {
1385
- const n = String(t), r = String(e);
1386
- return n === r ? 0 : n < r ? -1 : 1;
1387
- }
1388
- function Yt(t) {
1389
- const e = [...t.entries()].map(([n, r]) => ({ group: n, size: r }));
1390
- return e.sort((n, r) => r.size - n.size || Me(n.group, r.group)), Object.freeze(e.map((n) => Object.freeze(n)));
1391
- }
1392
- function Pe(t, e) {
1393
- const n = t.find((r) => r.name === e)?.normalization;
1394
- return Te(n) ? n : "none";
1395
- }
1396
- function me(t, e, n) {
1397
- const { statistics: r } = Re(X(t, "value"));
1398
- for (const i of te(ne(t, "value"))) {
1399
- const s = t.index.get(i.id);
1400
- s !== void 0 && (L(t.records[s], "rank", i.rank), L(t.records[s], "percentile", i.percentile));
1401
- }
1402
- r.measured > 0 && (y(e, "min", r.min), y(e, "max", r.max), y(e, "median", r.median), y(e, "mean", r.mean)), y(e, "measured", r.measured), y(e, "tiedAtMin", r.tiedAtMin), y(e, "normalization", Pe(n, "value"));
1403
- }
1404
- function ge(t, e, n, r, i) {
1405
- const s = ze(t, n);
1406
- for (const o of t.records) {
1407
- const a = o[n];
1408
- re(a) && L(o, r, s.get(a));
1409
- }
1410
- y(e, i, s.size), y(e, "sizes", Yt(s));
1411
- }
1412
- function Xt(t, e) {
1413
- for (const r of te(ne(t, "score"))) {
1414
- const i = t.index.get(r.id);
1415
- i !== void 0 && L(t.records[i], "rank", r.rank);
1416
- }
1417
- const n = [...ze(t, "category").entries()].map(([r, i]) => ({
1418
- category: String(r),
1419
- count: i
1420
- }));
1421
- n.sort((r, i) => i.count - r.count || Me(r.category, i.category)), y(e, "categories", Object.freeze(n.map((r) => Object.freeze(r))));
1422
- }
1423
- function z(t, e) {
1424
- let n = 0;
1425
- for (const r of t.records)
1426
- r[e] === !0 && n++;
1427
- return n;
1428
- }
1429
- function ye(t, e) {
1430
- let n = 0;
1431
- for (const r of t.records)
1432
- r[e] !== void 0 && n++;
1433
- return n;
1434
- }
1435
- function Kt(t, e, n, r, i) {
1436
- switch (t) {
1437
- case "node-metric":
1438
- me(e, r, i);
1439
- break;
1440
- case "edge-metric":
1441
- me(n, r, i);
1442
- break;
1443
- case "community":
1444
- ge(e, r, "group", "groupSize", "groupCount");
1445
- break;
1446
- case "layered-grouping":
1447
- ge(e, r, "level", "levelSize", "levelCount");
1448
- break;
1449
- case "category-table":
1450
- Xt(e, r);
1451
- break;
1452
- case "path":
1453
- y(r, "length", z(e, "onPath")), y(r, "hops", z(n, "onPath"));
1454
- break;
1455
- case "node-set":
1456
- y(r, "count", z(e, "in"));
1457
- break;
1458
- case "edge-set":
1459
- y(r, "count", z(n, "in"));
1460
- break;
1461
- }
1462
- }
1463
- function Zt(t) {
1464
- switch (t) {
1465
- case "node-metric":
1466
- case "edge-metric":
1467
- return "value";
1468
- case "category-table":
1469
- return "score";
1470
- case "layered-grouping":
1471
- return "level";
1472
- default:
1473
- return null;
1474
- }
1475
- }
1476
- function Qt(t, e) {
1477
- if (!Array.isArray(t))
1478
- return;
1479
- const n = [];
1480
- for (const r of t) {
1481
- if (typeof r != "object" || r === null)
1482
- continue;
1483
- const i = r, s = i.group ?? i.category, o = i.size ?? i.count;
1484
- if (re(s) && typeof o == "number" && n.push(Object.freeze({ group: s, size: o })), n.length === e)
1485
- break;
1486
- }
1487
- return Object.freeze(n);
1488
- }
1489
- class en {
1490
- #e;
1491
- #t;
1492
- #n;
1493
- #r;
1494
- #i;
1495
- #s;
1496
- #o = /* @__PURE__ */ new Map();
1497
- #a = /* @__PURE__ */ new Map();
1498
- #c;
1499
- /**
1500
- * Build a result around storage that has already been filled and frozen.
1501
- * @param init - What the run published.
1502
- * @param nodes - The published nodes.
1503
- * @param edges - The published edges.
1504
- * @param graph - The graph half, frozen.
1505
- */
1506
- constructor(e, n, r, i) {
1507
- this.runId = e.runId, this.shape = e.shape, this.fields = Object.freeze([...e.fields]), this.measured = Object.freeze({ nodes: e.measured.nodes, edges: e.measured.edges }), this.graph = i, this.#e = n, this.#t = r, this.#n = e.caveats, this.#r = e.durationMs, this.#i = e.labelOf ?? (() => {
1508
- }), this.#s = e.reading ?? ut;
1509
- }
1510
- /**
1511
- * One node's fields.
1512
- * @param id - The node id.
1513
- * @returns The fields, or undefined when the run produced nothing for that node.
1514
- */
1515
- node(e) {
1516
- const n = this.#e.index.get(e);
1517
- return n === void 0 ? void 0 : this.#e.records[n];
1518
- }
1519
- /**
1520
- * One edge's fields.
1521
- * @param id - The edge id.
1522
- * @returns The fields, or undefined when the run produced nothing for that edge.
1523
- */
1524
- edge(e) {
1525
- const n = this.#t.index.get(e);
1526
- return n === void 0 ? void 0 : this.#t.records[n];
1527
- }
1528
- /**
1529
- * One numeric field as a column.
1530
- *
1531
- * The view reads straight through to the result's own storage and its four figures were
1532
- * computed on the first call, so a histogram, a colour ramp's domain and a summary all walk
1533
- * one column rather than three copies of it.
1534
- * @param field - The field name.
1535
- * @returns A view over the values, without an object per element.
1536
- * @throws A GraphtyError coded E_UNKNOWN_ATTRIBUTE when the run published no such field, or
1537
- * E_BAD_COMMAND when the field is not a number published per element.
1538
- */
1539
- column(e) {
1540
- return this.#d(e).view;
1541
- }
1542
- /**
1543
- * The highest-ranked elements on one field.
1544
- *
1545
- * Elements with equal values share a rank and come back in printed-id order, so the top of a
1546
- * ranking does not reshuffle between two runs that measured the same thing.
1547
- * @param field - The field to rank on.
1548
- * @param limit - How many entries to return; the whole ranking when absent.
1549
- * @returns The entries, best first.
1550
- * @throws A GraphtyError coded E_OPTION_RANGE when the limit is not a whole number of
1551
- * entries, E_UNKNOWN_ATTRIBUTE when the run published no such field, or E_BAD_COMMAND when
1552
- * the field is not a number published per element.
1553
- */
1554
- ranking(e, n) {
1555
- if (n !== void 0 && (!Number.isInteger(n) || n < 0))
1556
- throw new w({
1557
- code: "E_OPTION_RANGE",
1558
- source: "run",
1559
- message: `A ranking limit is a whole number of entries, not ${String(n)}.`,
1560
- details: { option: "limit", value: n, min: 0 },
1561
- target: { kind: "run", id: this.runId }
1562
- });
1563
- const r = this.#a.get(e) ?? this.#p(e);
1564
- return n === void 0 ? r : Object.freeze(r.slice(0, n));
1565
- }
1566
- /**
1567
- * The distribution of one numeric field.
1568
- *
1569
- * Whether the field counts things is read off its own descriptor rather than passed in, so a
1570
- * count metric gets whole-number band edges without the caller knowing that it should.
1571
- * @param field - The field to bin.
1572
- * @param options - How to cut the bins.
1573
- * @returns The distribution, carrying the scale that was applied as well as the bars.
1574
- * @throws A GraphtyError coded E_OPTION_RANGE when the bin count is outside the permitted
1575
- * range, E_UNKNOWN_ATTRIBUTE when the run published no such field, or E_BAD_COMMAND when
1576
- * the field is not a number published per element.
1577
- */
1578
- histogram(e, n) {
1579
- const r = this.#l(e), i = r.kind === "edge" ? this.#t : this.#e;
1580
- return Jt(X(i, e), {
1581
- ...n,
1582
- integerValued: r.type === "integer"
1583
- });
1584
- }
1585
- /**
1586
- * The bounded form of this result.
1587
- *
1588
- * Everything on it is either a single figure or a list the element cut to a fixed length, so
1589
- * the object is the same size for a graph of a hundred nodes and a graph of a million.
1590
- * @returns The summary.
1591
- */
1592
- summary() {
1593
- return this.#c ??= this.#f(), this.#c;
1594
- }
1595
- /**
1596
- * What this result means, in one sentence of plain language.
1597
- * @param options - The locale and how technical to be.
1598
- * @returns The sentence.
1599
- */
1600
- reading(e) {
1601
- return this.#s(this, e ?? {});
1602
- }
1603
- /**
1604
- * The descriptor of one numeric per-element field, without complaining when there is none.
1605
- *
1606
- * A summary asks this rather than {@link Result.#descriptorFor} because a summary must not
1607
- * throw: a run whose field list does not declare the field its shape calls primary is a
1608
- * defect in that run, and the summary's job is to say what it can about the result rather
1609
- * than to refuse to describe it at all.
1610
- * @param field - The field name.
1611
- * @returns Its descriptor, or null when the run published no numeric field under that name.
1612
- */
1613
- #h(e) {
1614
- const n = this.fields.find((r) => r.name === e && r.kind !== "graph");
1615
- return n !== void 0 && B(n) ? n : null;
1616
- }
1617
- /**
1618
- * The descriptor of one numeric field the run published per element.
1619
- * @param field - The field name.
1620
- * @returns Its descriptor.
1621
- * @throws A GraphtyError naming what went wrong, with the readable fields in its details.
1622
- */
1623
- #l(e) {
1624
- const n = this.fields.filter((s) => s.name === e), r = n.find((s) => s.kind !== "graph"), i = this.fields.filter((s) => s.kind !== "graph" && B(s)).map((s) => s.name);
1625
- if (r === void 0)
1626
- throw n.length > 0 ? new w({
1627
- code: "E_BAD_COMMAND",
1628
- source: "run",
1629
- message: `"${e}" is a graph-level field of run "${this.runId}"; read it from result.graph.`,
1630
- details: { field: e, kind: "graph", available: i },
1631
- target: { kind: "run", id: this.runId }
1632
- }) : new w({
1633
- code: "E_UNKNOWN_ATTRIBUTE",
1634
- source: "run",
1635
- message: `Run "${this.runId}" published no field named "${e}".`,
1636
- details: { field: e, available: i, candidates: $t(e, i) },
1637
- target: { kind: "run", id: this.runId }
1638
- });
1639
- if (!B(r))
1640
- throw new w({
1641
- code: "E_BAD_COMMAND",
1642
- source: "run",
1643
- message: `"${e}" is typed "${r.type}", which is not a column of numbers.`,
1644
- details: { field: e, type: r.type, available: i },
1645
- target: { kind: "run", id: this.runId }
1646
- });
1647
- return r;
1648
- }
1649
- /**
1650
- * The half of the result a per-element field belongs to.
1651
- * @param field - The field name.
1652
- * @returns The nodes or the edges.
1653
- * @throws A GraphtyError naming what went wrong.
1654
- */
1655
- #u(e) {
1656
- return this.#l(e).kind === "edge" ? this.#t : this.#e;
1657
- }
1658
- /**
1659
- * One field's column and statistics, computed on the first ask and kept.
1660
- * @param field - The field name.
1661
- * @returns The view and the statistics behind it.
1662
- * @throws A GraphtyError naming what went wrong.
1663
- */
1664
- #d(e) {
1665
- const n = this.#o.get(e);
1666
- if (n !== void 0)
1667
- return n;
1668
- const r = Re(X(this.#u(e), e));
1669
- return this.#o.set(e, r), r;
1670
- }
1671
- /**
1672
- * Rank one field for the first time and keep the answer.
1673
- * @param field - The field to rank on.
1674
- * @returns The whole ranking, best first.
1675
- * @throws A GraphtyError naming what went wrong.
1676
- */
1677
- #p(e) {
1678
- const n = te(ne(this.#u(e), e));
1679
- return this.#a.set(e, n), n;
1680
- }
1681
- /**
1682
- * Build the bounded form of this result.
1683
- * @returns The summary.
1684
- */
1685
- #f() {
1686
- const e = Zt(this.shape), n = e === null || this.#h(e) === null ? null : e, r = n === null ? null : this.#d(n).statistics, i = r !== null && r.measured > 0 ? r : null, s = n === null ? [] : this.ranking(n, Vt).map(
1687
- (c) => Object.freeze({
1688
- id: c.id,
1689
- label: this.#i(c.id) ?? String(c.id),
1690
- value: c.value,
1691
- rank: c.rank,
1692
- percentile: c.percentile
1693
- })
1694
- ), o = {
1695
- count: this.#m(),
1696
- measured: r === null ? this.#g() : r.measured,
1697
- min: i === null ? null : i.min,
1698
- max: i === null ? null : i.max,
1699
- median: i === null ? null : i.median,
1700
- mean: i === null ? null : i.mean,
1701
- tiedAtMin: r === null ? 0 : r.tiedAtMin,
1702
- normalization: this.#y(n),
1703
- top: Object.freeze(s),
1704
- caveats: this.#n,
1705
- durationMs: this.#r
1706
- }, a = this.graph.sizes ?? this.graph.categories, l = Qt(a, qt);
1707
- return Object.freeze(l === void 0 ? o : { ...o, groups: l });
1708
- }
1709
- /**
1710
- * How many elements this result was computed over.
1711
- *
1712
- * A shape publishes per-element fields for nodes, for edges or for neither, and the scope a
1713
- * summary reports is the half or halves it actually measures. A shape that publishes nothing
1714
- * per element reports none, rather than borrowing the graph's size to look busy.
1715
- * @returns The count.
1716
- */
1717
- #m() {
1718
- const e = Y(this.shape), n = e.nodeFields.length > 0 ? this.measured.nodes : 0, r = e.edgeFields.length > 0 ? this.measured.edges : 0;
1719
- return n + r;
1720
- }
1721
- /**
1722
- * How many elements carry the shape's primary field, for a shape that measures no number.
1723
- * @returns The count.
1724
- */
1725
- #g() {
1726
- const e = Y(this.shape), { primaryField: n } = e;
1727
- if (n === null)
1728
- return 0;
1729
- const r = e.nodeFields.includes(n) ? ye(this.#e, n) : 0, i = e.edgeFields.includes(n) ? ye(this.#t, n) : 0;
1730
- return r + i;
1731
- }
1732
- /**
1733
- * How this result's values were scaled.
1734
- * @param valueField - The field a summary describes, or null when the shape measures no
1735
- * number.
1736
- * @returns The normalisation the run published, the one its field declares, or "none".
1737
- */
1738
- #y(e) {
1739
- const n = this.graph.normalization;
1740
- return Te(n) ? n : e === null ? "none" : Pe(this.fields, e);
1741
- }
1742
- }
1743
- function tn(t) {
1744
- const e = fe(t.nodes), n = fe(t.edges), r = { ...t.graph };
1745
- Kt(t.shape, e, n, r, t.fields);
1746
- for (const i of e.records)
1747
- Object.freeze(i);
1748
- for (const i of n.records)
1749
- Object.freeze(i);
1750
- return new en(t, e, n, Object.freeze(r));
1751
- }
1752
- function nn(t) {
1753
- const e = t.styles?.config.data.knownFields.nodeLabelPath ?? null;
1754
- if (e === null)
1755
- return;
1756
- const n = t.getDataManager();
1757
- return (r) => {
1758
- const i = n.getNode(r)?.data[e];
1759
- return typeof i == "string" || typeof i == "number" ? String(i) : void 0;
1760
- };
1761
- }
1762
- function Un(t) {
1763
- return { exact: !0, precision: "f64", notes: [], ...t };
1764
- }
1765
- const rn = 1024, sn = 16;
1766
- function on() {
1767
- return {
1768
- signal: new AbortController().signal,
1769
- report: () => {
1770
- },
1771
- yieldNow: () => new Promise((t) => {
1772
- setTimeout(t, 0);
1773
- })
1774
- };
1775
- }
1776
- async function Hn(t, e, n, r) {
1777
- const i = n.length;
1778
- t.report({ phase: e, completed: 0, total: i });
1779
- let s = performance.now();
1780
- for (let o = 0; o < i; o++)
1781
- o > 0 && o % rn === 0 && (t.signal.throwIfAborted(), t.report({ phase: e, completed: o, total: i }), performance.now() - s >= sn && (await t.yieldNow(), s = performance.now())), r(n[o], o);
1782
- t.signal.throwIfAborted(), t.report({ phase: e, completed: i, total: i });
1783
- }
1784
- function an(t, e, n) {
1785
- const r = n?.find((s) => s.name === t.name && s.kind === t.kind), i = {
1786
- name: t.name,
1787
- plainName: r?.plainName ?? t.name,
1788
- technicalName: r?.technicalName ?? t.name,
1789
- kind: t.kind,
1790
- type: t.type,
1791
- path: P(e, t.name),
1792
- ...r?.unit === void 0 ? {} : { unit: r.unit }
1793
- };
1794
- return t.normalization === void 0 ? i : { ...i, normalization: t.normalization };
1795
- }
1796
- class Bn extends Ce {
1797
- /** What the last run published, kept so a caller holding the algorithm can read the result. */
1798
- #e;
1799
- /**
1800
- * Fill in what the caller did not pass, and refuse what this algorithm does not declare.
1801
- *
1802
- * ONE DECLARATION, NOT TWO. `descriptor.options` is the only list a declared algorithm
1803
- * writes, and this is what makes that true at the point the values reach the running code:
1804
- * the same descriptors the catalogue hands a picker are what the caller's values are checked
1805
- * against. The older `static optionsSchema` remains the answer for a class that declares no
1806
- * descriptor, which is every algorithm the element itself ships.
1807
- *
1808
- * It also fixes the failure vocabulary on the way in. The older schema throws an uncoded
1809
- * `Error` for a bad value and SILENTLY DROPS a name it does not know, where the run path
1810
- * refuses the same name with `E_UNKNOWN_OPTION` -- so the same mistake produced three
1811
- * different outcomes depending on which door the caller came through.
1812
- * @param options - What the caller asked for.
1813
- * @returns Every declared option, with the caller's values where they were given and the
1814
- * declared defaults everywhere else.
1815
- * @throws A `GraphtyError` with `E_UNKNOWN_OPTION` for a name this algorithm does not
1816
- * declare, or `E_OPTION_RANGE` for a value it would not accept.
1817
- */
1818
- resolveOptions(e) {
1819
- const { descriptor: n } = this.constructor;
1820
- return n === void 0 ? super.resolveOptions(e) : Le(n.options, e ?? {}, {
1821
- kind: "algorithm",
1822
- id: n.key
1823
- });
1824
- }
1825
- /**
1826
- * What the last run published.
1827
- *
1828
- * THE REPLACEMENT FOR `algorithmResults`. The 1.x entry point used to scatter its numbers onto
1829
- * the render objects under names it chose for itself, and the only way back to them was to
1830
- * walk the graph. It returns a result object now, and this is where the 1.x `run()` leaves it,
1831
- * so the old call shape still has somewhere to read from.
1832
- * @returns The result, or undefined when nothing has run or there was nothing to compute.
1833
- */
1834
- get result() {
1835
- return this.#e;
1836
- }
1837
- /**
1838
- * Run the algorithm and publish what it produced on the 1.x result paths.
1839
- *
1840
- * The 1.x entry point: it returns nothing, and it publishes under the algorithm's own type
1841
- * rather than under a run id, because there is no run behind it. Whoever wants the result
1842
- * calls {@link DeclaredAlgorithm.computeRun}, which is the same work with a run in front of
1843
- * it.
1844
- * @returns A promise that settles when the result has been published.
1845
- */
1846
- async run() {
1847
- await this.computeRun(on(), this.type);
1848
- }
1849
- /**
1850
- * Compute on behalf of a run, and hand the result back.
1851
- *
1852
- * The run machinery's entry point, which is {@link DeclaredAlgorithm.computeRun} under the
1853
- * name every algorithm answers to.
1854
- * @param context - A signal, a progress channel and a yield.
1855
- * @param runId - The id the result is published under.
1856
- * @param fields - The catalogue's descriptors for this algorithm's fields.
1857
- * @returns The result, or undefined when there was nothing to compute.
1858
- * @throws Whatever the context's signal throws once the run has been cancelled.
1859
- */
1860
- publishResult(e, n, r) {
1861
- return this.computeRun(e, n, r);
1862
- }
1863
- /**
1864
- * Compute this algorithm's result, publish it under a run id, and project the 1.x view of it.
1865
- *
1866
- * This is what the run executor calls. The catalogue's own field descriptors are passed in
1867
- * because the catalogue states what a reader sees a field called and cannot be imported here
1868
- * without a cycle -- it reads these classes to publish their options.
1869
- * @param context - What the element gave the run: a signal, a progress channel and a yield.
1870
- * @param runId - The id the result is published under, which is the `<runId>` in
1871
- * `results.<runId>`.
1872
- * @param declared - The catalogue's descriptors for this algorithm's fields, when the caller
1873
- * holds them.
1874
- * @returns The result, or undefined when there was nothing to compute.
1875
- * @throws Whatever the context's signal throws once the run has been cancelled, which is a
1876
- * `DOMException` named `AbortError`.
1877
- */
1878
- async computeRun(e, n, r) {
1879
- const i = Date.now(), s = await this.compute(e);
1880
- if (s === null)
1881
- return;
1882
- const o = this.graph.getDataManager(), a = nn(this.graph), l = tn({
1883
- ...a === void 0 ? {} : { labelOf: a },
1884
- runId: n,
1885
- shape: s.shape,
1886
- fields: s.fields.map((c) => an(c, n, r)),
1887
- measured: { nodes: o.nodes.size, edges: o.edges.size },
1888
- graph: s.graph,
1889
- nodes: s.nodes,
1890
- edges: s.edges,
1891
- caveats: s.caveats,
1892
- durationMs: Date.now() - i
1893
- });
1894
- return this.#e = l, l;
1895
- }
1896
- }
1897
- function Wn(t, e = "number") {
1898
- return [
1899
- { name: "value", kind: t, type: e },
1900
- { name: "rank", kind: t, type: "integer" },
1901
- { name: "percentile", kind: t, type: "number" },
1902
- { name: "min", kind: "graph", type: "number" },
1903
- { name: "max", kind: "graph", type: "number" },
1904
- { name: "median", kind: "graph", type: "number" },
1905
- { name: "mean", kind: "graph", type: "number" },
1906
- { name: "measured", kind: "graph", type: "integer" },
1907
- { name: "normalization", kind: "graph", type: "string" },
1908
- { name: "tiedAtMin", kind: "graph", type: "integer" }
1909
- ];
1910
- }
1911
- const be = [
1912
- { name: "group", kind: "node", type: "integer" },
1913
- { name: "groupSize", kind: "node", type: "integer" },
1914
- { name: "groupCount", kind: "graph", type: "integer" },
1915
- { name: "sizes", kind: "graph", type: "table" }
1916
- ];
1917
- function Jn(t) {
1918
- return t ? [...be, { name: "modularity", kind: "graph", type: "number" }] : be;
1919
- }
1920
- const Vn = [
1921
- { name: "level", kind: "node", type: "integer" },
1922
- { name: "levelSize", kind: "node", type: "integer" },
1923
- { name: "levelCount", kind: "graph", type: "integer" },
1924
- { name: "sizes", kind: "graph", type: "table" }
1925
- ], qn = [
1926
- { name: "onPath", kind: "node", type: "boolean" },
1927
- { name: "order", kind: "node", type: "integer" },
1928
- { name: "onPath", kind: "edge", type: "boolean" },
1929
- { name: "length", kind: "graph", type: "integer" },
1930
- { name: "cost", kind: "graph", type: "number" },
1931
- { name: "hops", kind: "graph", type: "integer" }
1932
- ];
1933
- function Yn(t, e) {
1934
- return [
1935
- { name: "in", kind: t, type: "boolean" },
1936
- { name: "count", kind: "graph", type: "integer" },
1937
- { ...e, kind: "graph" }
1938
- ];
1939
- }
1940
- const cn = `${q}.${Et}`;
1941
- function N(t) {
1942
- return { ...t, path: `${cn}.${t.name}` };
1943
- }
1944
- function Xn(t) {
1945
- return [
1946
- N({
1947
- name: "value",
1948
- plainName: t.plainName,
1949
- technicalName: t.technicalName,
1950
- kind: "node",
1951
- type: t.type ?? "number",
1952
- ...t.unit === void 0 ? {} : { unit: t.unit }
1953
- }),
1954
- N({ name: "rank", plainName: "Rank", technicalName: "rank", kind: "node", type: "integer" }),
1955
- N({
1956
- name: "percentile",
1957
- plainName: "Percentile",
1958
- technicalName: "percentile",
1959
- kind: "node",
1960
- type: "number"
1961
- }),
1962
- N({ name: "min", plainName: "Lowest", technicalName: "min", kind: "graph", type: "number" }),
1963
- N({ name: "max", plainName: "Highest", technicalName: "max", kind: "graph", type: "number" }),
1964
- N({ name: "median", plainName: "Middle", technicalName: "median", kind: "graph", type: "number" }),
1965
- N({ name: "mean", plainName: "Average", technicalName: "mean", kind: "graph", type: "number" }),
1966
- N({
1967
- name: "measured",
1968
- plainName: "Measured",
1969
- technicalName: "measured",
1970
- kind: "graph",
1971
- type: "integer"
1972
- }),
1973
- N({
1974
- name: "normalization",
1975
- plainName: "Normalization",
1976
- technicalName: "normalization",
1977
- kind: "graph",
1978
- type: "string"
1979
- }),
1980
- N({
1981
- name: "tiedAtMin",
1982
- plainName: "Tied at the lowest value",
1983
- technicalName: "tiedAtMin",
1984
- kind: "graph",
1985
- type: "integer"
1986
- })
1987
- ];
1988
- }
1989
- const _ = Z({
1990
- kind: "format",
1991
- idOf: (t) => t.descriptor.id,
1992
- descriptorOf: (t) => t.descriptor,
1993
- implementationOf: (t) => t.descriptor,
1994
- builtInIds: () => Ge
1995
- });
1996
- function Kn(t, e) {
1997
- _.register(t, e);
1998
- }
1999
- function Zn() {
2000
- return _.entries();
2001
- }
2002
- function ln() {
2003
- return _.descriptors();
2004
- }
2005
- function un(t) {
2006
- return _.byId(t);
2007
- }
2008
- function Qn() {
2009
- _.clearForTesting();
2010
- }
2011
- const dn = {
2012
- "edge-list": "Edge List",
2013
- "node-list": "Node List",
2014
- "adjacency-list": "Adjacency List",
2015
- neo4j: "Neo4j Export",
2016
- gephi: "Gephi Export",
2017
- cytoscape: "Cytoscape Export",
2018
- generic: "Generic"
2019
- }, hn = [
2020
- {
2021
- name: "delimiter",
2022
- plainName: "Column Separator",
2023
- technicalName: "delimiter",
2024
- type: "string",
2025
- 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."
2026
- },
2027
- {
2028
- name: "variant",
2029
- plainName: "File Shape",
2030
- technicalName: "variant",
2031
- type: "enum",
2032
- values: Object.entries(dn).map(([t, e]) => ({ value: t, label: e })),
2033
- description: "Which CSV shape to read. Worked out from the header row when it is not set."
2034
- },
2035
- {
2036
- name: "idColumn",
2037
- plainName: "Node Id Column",
2038
- technicalName: "idColumn",
2039
- type: "string",
2040
- description: "The column holding each node's identity, when the file lists nodes."
2041
- }
2042
- ], S = [
2043
- {
2044
- name: "edgeSource",
2045
- plainName: "Edge Start Field",
2046
- technicalName: "edgeSource",
2047
- type: "string",
2048
- description: "Where to find the node an edge starts at. Left unset, the element looks for source, then src, then from."
2049
- },
2050
- {
2051
- name: "edgeTarget",
2052
- plainName: "Edge End Field",
2053
- technicalName: "edgeTarget",
2054
- type: "string",
2055
- description: "Where to find the node an edge ends at. Left unset, the element looks for target, then dst, then to."
2056
- }
2057
- ], pn = [
2058
- {
2059
- name: "nodeIdPath",
2060
- plainName: "Node Id Field",
2061
- technicalName: "nodeIdPath",
2062
- type: "string",
2063
- description: "An expression selecting each node's identity out of the node record."
2064
- }
2065
- ], je = [
2066
- {
2067
- id: "json",
2068
- plainName: "JSON",
2069
- extensions: [".json"],
2070
- mimeTypes: ["application/json"],
2071
- canImport: !0,
2072
- canExport: !1,
2073
- options: [...pn, ...S]
2074
- },
2075
- {
2076
- id: "csv",
2077
- plainName: "CSV",
2078
- extensions: [".csv", ".tsv", ".tab", ".edges", ".edgelist"],
2079
- mimeTypes: ["text/csv", "text/tab-separated-values", "text/plain"],
2080
- canImport: !0,
2081
- canExport: !1,
2082
- options: [...hn, ...S]
2083
- },
2084
- {
2085
- id: "graphml",
2086
- plainName: "GraphML",
2087
- extensions: [".graphml", ".xml"],
2088
- mimeTypes: ["application/graphml+xml", "application/xml", "text/xml"],
2089
- canImport: !0,
2090
- canExport: !1,
2091
- options: S
2092
- },
2093
- {
2094
- id: "gexf",
2095
- plainName: "GEXF",
2096
- // ".xml" is claimed here as well as by GraphML because both formats are XML and both are
2097
- // routinely saved under the generic extension. Two claimants is what lets detection ask
2098
- // each one's content sniffer which of them the file actually is, instead of a private
2099
- // branch inside the detector hard-coding the two namespace strings -- which is the same
2100
- // route a third party's XML dialect now takes.
2101
- extensions: [".gexf", ".xml"],
2102
- mimeTypes: ["application/gexf+xml", "application/xml", "text/xml"],
2103
- canImport: !0,
2104
- canExport: !1,
2105
- options: S
2106
- },
2107
- {
2108
- id: "gml",
2109
- plainName: "GML",
2110
- extensions: [".gml"],
2111
- mimeTypes: ["text/plain"],
2112
- canImport: !0,
2113
- canExport: !1,
2114
- options: S
2115
- },
2116
- {
2117
- id: "dot",
2118
- plainName: "DOT",
2119
- extensions: [".dot", ".gv"],
2120
- mimeTypes: ["text/vnd.graphviz", "text/plain"],
2121
- canImport: !0,
2122
- canExport: !1,
2123
- options: S
2124
- },
2125
- {
2126
- id: "pajek",
2127
- plainName: "Pajek NET",
2128
- extensions: [".net", ".paj"],
2129
- mimeTypes: ["text/plain"],
2130
- canImport: !0,
2131
- canExport: !1,
2132
- options: S
2133
- }
2134
- ], er = [
2135
- { id: "sif", reason: "No data source reads the Cytoscape simple interaction format." },
2136
- { id: "cx2", reason: "No data source reads the Cytoscape Exchange format." }
2137
- ];
2138
- function tr(t) {
2139
- return je.find((e) => e.id === t) ?? un(t)?.descriptor;
2140
- }
2141
- function nr(t) {
2142
- const e = t.toLowerCase();
2143
- return [...je, ...ln()].filter(
2144
- (n) => n.extensions.includes(e)
2145
- );
2146
- }
2147
- const D = Z({
2148
- kind: "layout",
2149
- idOf: (t) => t.descriptor.id,
2150
- descriptorOf: (t) => t.descriptor,
2151
- implementationOf: (t) => t.descriptor,
2152
- builtInIds: () => Ue
2153
- });
2154
- function fn(t, e) {
2155
- D.register(t, e);
2156
- }
2157
- function rr() {
2158
- return D.descriptors();
2159
- }
2160
- function ir(t) {
2161
- return D.byId(t);
2162
- }
2163
- function sr() {
2164
- D.clearForTesting();
2165
- }
2166
- const g = 3, mn = 1024;
2167
- function T(t, e) {
2168
- if (!Number.isInteger(t) || t < 0)
2169
- throw new RangeError(`ElementPositions: ${e} must be a non-negative integer, found ${String(t)}`);
2170
- return t;
2171
- }
2172
- function A(t) {
2173
- return Number.isFinite(Math.fround(t));
2174
- }
2175
- class we {
2176
- /**
2177
- * Allocate the backing array, every row unplaced and unpinned.
2178
- * @param capacity - rows to reserve before the first growth; a non-negative integer
2179
- */
2180
- constructor(e = mn) {
2181
- this.components = g, this.rows = 0, T(e, "capacity");
2182
- const n = Math.max(1, e);
2183
- this.array = new Float32Array(g * n), this.array.fill(Number.NaN), this.pins = new Uint8Array(n);
2184
- }
2185
- /**
2186
- * Rows the backing array can hold without reallocating.
2187
- * @returns the current capacity in rows
2188
- */
2189
- get capacity() {
2190
- return this.array.length / g;
2191
- }
2192
- /**
2193
- * Rows currently in use. Equals the last snapshot's nodeCount.
2194
- * @returns the live row count
2195
- */
2196
- get count() {
2197
- return this.rows;
2198
- }
2199
- /**
2200
- * How many live rows carry real coordinates, by the same test {@link ElementPositions.isPlaced}
2201
- * applies one row at a time.
2202
- *
2203
- * The question behind it is "did the file that loaded this graph place every node?", which is
2204
- * what decides whether the Keep Positions arrangement can be used at all: a fixed layout over a
2205
- * graph where half the rows are NaN draws half a picture. Compare it against
2206
- * {@link ElementPositions.count} to answer that -- equal means every node has somewhere to go.
2207
- *
2208
- * COUNTED ON EVERY READ, and it has to be. The array is lent to the snapshot as a mutable
2209
- * column, so a layout, a drag and a GPU readback all write coordinates without passing through
2210
- * this class; a counter kept up to date by `write()` would miss every one of those and would be
2211
- * most wrong exactly while a layout was running, which is when somebody is asking. The scan is
2212
- * one f32 read per node and touches only the x lane.
2213
- *
2214
- * This is deliberately NOT on `GraphStatistics`. Those are memoised against the snapshot they
2215
- * were computed from, and how many nodes carry a coordinate changes while a layout runs WITHIN
2216
- * one snapshot, so a cached answer would be stale in the one moment it mattered.
2217
- * @returns how many rows in `[0, count)` carry a storable x
2218
- */
2219
- get placedCount() {
2220
- let e = 0;
2221
- for (let n = 0; n < g * this.rows; n += g)
2222
- this.hasStorableX(n) && e++;
2223
- return e;
2224
- }
2225
- /**
2226
- * The exact view to hand to `snapshot.nodes.set("position", ...)`.
2227
- *
2228
- * Bounded by the LIVE ROW COUNT, like every other row accessor here. `subarray` CLAMPS rather
2229
- * than throwing, so without a check a caller that forgot to `grow()` first would get a short
2230
- * array: E_COLUMN_LENGTH from inside graph-format if it is attached, and a layout that silently
2231
- * places only the first `count` nodes if it is not. Bounding by the CAPACITY instead would hand
2232
- * out a writable window over the spare rows -- rows this class reports unplaced, whose NaN fill
2233
- * every later `grow()` and `remap()` depends on, and whose contents the next growth erases.
2234
- * @param nodeCount - the snapshot's node count
2235
- * @returns a subarray of length `3 * nodeCount` over the same buffer
2236
- * @throws RangeError when `nodeCount` is not a non-negative integer, or exceeds the live count
2237
- */
2238
- view(e) {
2239
- if (T(e, "nodeCount"), e > this.rows)
2240
- throw new RangeError(
2241
- `ElementPositions: view(${e}) needs ${e} live rows but only ${this.rows} exist; call grow(${e}) first`
2242
- );
2243
- return this.array.subarray(0, g * e);
2244
- }
2245
- /**
2246
- * Prefix-stable growth: existing rows keep their coordinates, EVERY row at or above the new
2247
- * count is unplaced -- including the spare capacity, which a later grow() will hand out.
2248
- *
2249
- * Past the capacity this REPLACES the array object; see the staleness contract on the class.
2250
- * @param nodeCount - the new row count; may be smaller than the current one (append-only
2251
- * builders never shrink, but a caller that does gets a truncation, not a throw). `grow(0)`
2252
- * is how a dataset is discarded while the reserve is kept
2253
- * @throws RangeError when `nodeCount` is not a non-negative integer
2254
- */
2255
- grow(e) {
2256
- if (T(e, "nodeCount"), e > this.capacity) {
2257
- const n = Math.max(this.capacity * 2, e), r = new Float32Array(g * n);
2258
- r.set(this.array), r.fill(Number.NaN, g * this.rows), this.array = r;
2259
- const i = new Uint8Array(n);
2260
- i.set(this.pins), i.fill(0, this.rows), this.pins = i;
2261
- } else e > this.rows ? (this.array.fill(Number.NaN, g * this.rows, g * e), this.pins.fill(0, this.rows, e)) : e < this.rows && (this.array.fill(Number.NaN, g * e, g * this.rows), this.pins.fill(0, e, this.rows));
2262
- this.rows = e;
2263
- }
2264
- /**
2265
- * Apply a freeze report's `nodeRemap` (previous index space -> new index or INVALID_INDEX).
2266
- *
2267
- * `remapArray` ALLOCATES, so this replaces the array object and collapses the capacity to
2268
- * exactly `nodeCount` (PLAN DECISION 3: a removal is rare and never per frame, so paying one
2269
- * reallocation on the next growth is cheaper than copying the remapped rows a second time into
2270
- * a re-reserved array). Every holder of the old object is stale afterwards, which is why
2271
- * GraphStore re-attaches the column on every freeze and emits `snapshot-replaced`.
2272
- * @param nodeRemap - the report's nodeRemap
2273
- * @param nodeCount - the new snapshot's node count
2274
- * @throws RangeError when `nodeCount` is not a non-negative integer
2275
- */
2276
- remap(e, n) {
2277
- T(n, "nodeCount"), this.array = se(this.array, e, n, Number.NaN, g), this.pins = se(this.pins, e, n, 0, 1), this.rows = n;
2278
- }
2279
- /**
2280
- * Whether the reader has fixed this row's node in place.
2281
- *
2282
- * A row outside the live count is UNPINNED, never pinned, for the same reason
2283
- * {@link ElementPositions.isPlaced} answers false there: a node with no row is a node the
2284
- * graph builder has not taken, and answering "pinned" for one would freeze a node that does
2285
- * not exist yet and that nothing could release.
2286
- * @param index - node index
2287
- * @returns true when the row is inside the live count AND carries a pin
2288
- */
2289
- isPinned(e) {
2290
- return !Number.isInteger(e) || e < 0 || e >= this.rows ? !1 : this.pins[e] === 1;
2291
- }
2292
- /**
2293
- * Record or release a pin, GROWING the lane to reach the row.
2294
- *
2295
- * It grows rather than throwing for the same reason `LayoutEngine.writeNodePosition` does: a
2296
- * node's index is handed out the moment the graph builder takes its record, but its row only
2297
- * appears when the graph is next frozen, and a reader who drags a node in between would
2298
- * otherwise have the pin thrown away in silence.
2299
- *
2300
- * It REFUSES rather than throwing for an index that is not a row number at all -- the
2301
- * `INVALID_INDEX` a record the builder would not take carries for its whole life, or a
2302
- * negative or fractional number. Pinning happens from a pointer gesture, and a throw on that
2303
- * path takes the frame with it.
2304
- * @param index - node index
2305
- * @param pinned - true to pin, false to release
2306
- * @returns true when the lane was written, false when the index names no row
2307
- */
2308
- setPinned(e, n) {
2309
- return e === W || !Number.isInteger(e) || e < 0 ? !1 : (e >= this.rows && this.grow(e + 1), this.pins[e] = n ? 1 : 0, !0);
2310
- }
2311
- /**
2312
- * How many live rows are pinned.
2313
- * @returns the count of pinned rows in `[0, count)`
2314
- */
2315
- get pinnedCount() {
2316
- let e = 0;
2317
- for (let n = 0; n < this.rows; n++)
2318
- this.pins[n] === 1 && e++;
2319
- return e;
2320
- }
2321
- /**
2322
- * The exact view to hand to `snapshot.nodes.set("graphty.pinned", ...)`.
2323
- *
2324
- * Bounded by the live row count, like {@link ElementPositions.view} and for the same reasons.
2325
- * @param nodeCount - the snapshot's node count
2326
- * @returns a subarray of length `nodeCount` over the same buffer
2327
- * @throws RangeError when `nodeCount` is not a non-negative integer, or exceeds the live count
2328
- */
2329
- pinnedView(e) {
2330
- if (T(e, "nodeCount"), e > this.rows)
2331
- throw new RangeError(
2332
- `ElementPositions: pinnedView(${e}) needs ${e} live rows but only ${this.rows} exist; call grow(${e}) first`
2333
- );
2334
- return this.pins.subarray(0, e);
2335
- }
2336
- /**
2337
- * Report whether a row carries real coordinates.
2338
- *
2339
- * A row outside the live count is UNPLACED, never placed: this predicate is used to decide
2340
- * whether an importer seed may be written, and answering "placed" for a row that does not
2341
- * exist is how a coordinate gets thrown away without an error. So is a row whose x is an
2342
- * infinity, which only a writer that bypassed `write()` -- that is, one writing through the
2343
- * column this array is lent to -- can produce; see the class comment.
2344
- * @param index - node index
2345
- * @returns true when the row is inside the live count AND carries a storable x
2346
- */
2347
- isPlaced(e) {
2348
- return !Number.isInteger(e) || e < 0 || e >= this.rows ? !1 : this.hasStorableX(g * e);
2349
- }
2350
- /**
2351
- * Read one row into a caller-supplied object (14.4 rule 7: never return a shared vector).
2352
- * @param index - node index
2353
- * @param out - the object to fill
2354
- * @param out.x - receives the scene-unit x
2355
- * @param out.y - receives the scene-unit y
2356
- * @param out.z - receives the scene-unit z
2357
- * @throws RangeError when `index` is not an integer in `[0, count)`
2358
- */
2359
- read(e, n) {
2360
- const r = this.rowBase(e);
2361
- n.x = this.array[r] ?? Number.NaN, n.y = this.array[r + 1] ?? Number.NaN, n.z = this.array[r + 2] ?? Number.NaN;
2362
- }
2363
- /**
2364
- * Write one row.
2365
- *
2366
- * This is the ONE choke point every layout, every drag and (from E1) every GPU readback goes
2367
- * through, so it is where an unstorable coordinate is stopped. NaN is this class's unplaced
2368
- * marker: letting one in through here would unplace a placed node from the inside, and an
2369
- * infinity would be stored and then reported placed. The test is `isStorableCoordinate`, NOT
2370
- * `Number.isFinite`, because the array is f32: a finite double of 1e39 passes `isFinite` and
2371
- * lands as Infinity. A caller holding such a coordinate has a bug upstream and must drop the
2372
- * update, not hand it on.
2373
- * @param index - node index
2374
- * @param x - scene-unit x
2375
- * @param y - scene-unit y
2376
- * @param z - scene-unit z
2377
- * @throws RangeError when `index` is not an integer in `[0, count)`. A node added since the
2378
- * last freeze has no row yet: freeze first (which grows this array), then write
2379
- * @throws RangeError when any component is NaN, an infinity, or a double that overflows f32
2380
- */
2381
- write(e, n, r, i) {
2382
- const s = this.rowBase(e);
2383
- if (!A(n) || !A(r) || !A(i))
2384
- throw new RangeError(
2385
- `ElementPositions: row ${String(e)} was written the unstorable coordinate (${String(n)}, ${String(r)}, ${String(i)}); NaN is this class's UNPLACED marker, and an infinity -- including one an f32 overflow produces -- poisons the scene bounds`
2386
- );
2387
- this.array[s] = n, this.array[s + 1] = r, this.array[s + 2] = i;
2388
- }
2389
- /**
2390
- * Write a row ONLY when it is unplaced. This is how importer-seeded coordinates reach a new
2391
- * node without overwriting anything a layout or a drag already produced.
2392
- *
2393
- * `false` means exactly one thing -- the row was ALREADY PLACED, by the same test `isPlaced()`
2394
- * applies. A row that does not exist yet throws instead of returning `false`, so a caller that
2395
- * seeds before growing finds out rather than losing the coordinate.
2396
- * @param index - node index
2397
- * @param x - scene-unit x
2398
- * @param y - scene-unit y
2399
- * @param z - scene-unit z
2400
- * @returns true when the row was written, false when it was already placed
2401
- * @throws RangeError when `index` is not an integer in `[0, count)`
2402
- * @throws RangeError when any component is NaN, an infinity, or a double that overflows f32,
2403
- * through `write()`
2404
- */
2405
- fillUnplaced(e, n, r, i) {
2406
- const s = this.rowBase(e);
2407
- return this.hasStorableX(s) ? !1 : (this.write(e, n, r, i), !0);
2408
- }
2409
- /**
2410
- * Whether the row at this offset carries a storable x, which is the one test "placed" means.
2411
- * @param base - the array offset of the row's x component
2412
- * @returns true when the stored x is neither NaN nor an infinity
2413
- */
2414
- hasStorableX(e) {
2415
- return A(this.array[e] ?? Number.NaN);
2416
- }
2417
- /**
2418
- * The array offset of a live row, rejecting anything else.
2419
- * @param index - node index
2420
- * @returns the offset of the row's x component
2421
- */
2422
- rowBase(e) {
2423
- if (!Number.isInteger(e) || e < 0 || e >= this.rows)
2424
- throw new RangeError(
2425
- e === W ? "ElementPositions: INVALID_INDEX is the graph-format 'no node' sentinel, not a row" : `ElementPositions: row ${String(e)} is outside the live range [0, ${this.rows})`
2426
- );
2427
- return g * e;
2428
- }
2429
- }
2430
- const k = /* @__PURE__ */ new Map(), gn = Object.freeze([
2431
- "arf",
2432
- "bfs",
2433
- "bipartite",
2434
- "circular",
2435
- "d3",
2436
- "fixed",
2437
- "forceatlas2",
2438
- "kamada-kawai",
2439
- "multipartite",
2440
- "ngraph",
2441
- "planar",
2442
- "random",
2443
- "shell",
2444
- "spectral",
2445
- "spiral",
2446
- "spring",
2447
- "spring-electrical"
2448
- ]), yn = De.getLogger(["graphty", "layout"]), ve = 1e-6;
2449
- function bn(t, e) {
2450
- return JSON.stringify([t, e]);
2451
- }
2452
- const wn = 10;
2453
- function Ne(t) {
2454
- return new w({
2455
- code: "E_DUPLICATE_PLUGIN",
2456
- message: `"${t}" 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`,
2457
- source: "registry",
2458
- details: { kind: "layout", name: t, builtIn: !0 }
2459
- });
2460
- }
2461
- const ie = class ie {
2462
- constructor() {
2463
- this.positionArrayAttached = !1;
2464
- }
2465
- /**
2466
- * Add multiple nodes to the layout engine
2467
- * @param nodes - Array of nodes to add
2468
- */
2469
- addNodes(e) {
2470
- for (const n of e)
2471
- this.addNode(n);
2472
- }
2473
- /**
2474
- * Add multiple edges to the layout engine
2475
- * @param edges - Array of edges to add
2476
- */
2477
- addEdges(e) {
2478
- for (const n of e)
2479
- this.addEdge(n);
2480
- }
2481
- /**
2482
- * Take a node out of the layout, before the element disposes the mesh that drew it.
2483
- *
2484
- * Declared here, with a default that does nothing, because it used to be duck-typed by the
2485
- * element's data manager and implemented by none of the seventeen engines that ship here: an
2486
- * author learned it existed by reading the element's source, and got no worked example. An
2487
- * engine that keeps its own node list must override this, or it holds every removed node --
2488
- * and everything that node references -- for as long as the engine lives.
2489
- * @param _n - the node leaving the graph
2490
- */
2491
- removeNode(e) {
2492
- }
2493
- /**
2494
- * The edge half of {@link LayoutEngine.removeNode}, with the same default and the same reason.
2495
- * @param _e - the edge leaving the graph
2496
- */
2497
- removeEdge(e) {
2498
- }
2499
- /**
2500
- * Settle nodes that reached the graph after this layout was already running.
2501
- *
2502
- * THE DEFAULT IS THE ELEMENT'S OWN FALLBACK -- up to ten steps, stopping early if the engine
2503
- * settles -- so a simulation behaves exactly as it did before the hook was declared, and an
2504
- * engine that can place a newcomer without re-running the whole simulation overrides it. It
2505
- * lives on the base class rather than in the manager because the manager cannot tell "did not
2506
- * implement it" from "implemented it as a deliberate no-op", and the difference decides
2507
- * whether ten steps run.
2508
- * @param _nodes - the nodes that have just arrived
2509
- */
2510
- updatePositions(e) {
2511
- for (let n = 0; n < wn; n++) {
2512
- if (this.isSettled)
2513
- return;
2514
- this.step();
2515
- }
2516
- }
2517
- /**
2518
- * Release whatever this engine holds. The element calls it when the reader switches layouts
2519
- * and when the graph is torn down, and never uses the engine again afterwards.
2520
- *
2521
- * Declared with a do-nothing default for the same reason as {@link LayoutEngine.removeNode}:
2522
- * it was duck-typed, undeclared and unimplemented by every engine here.
2523
- */
2524
- dispose() {
2525
- }
2526
- /**
2527
- * The array this engine publishes node coordinates into.
2528
- *
2529
- * Allocated on demand, so reading it is enough to make an engine that has never been handed an
2530
- * element's array produce one of its own.
2531
- * @returns the position array in use
2532
- */
2533
- get nodePositions() {
2534
- return this.positionArray ??= new we(0), this.positionArray;
2535
- }
2536
- /**
2537
- * Hand this engine the array it must publish into, and stop it adopting any other.
2538
- *
2539
- * This is how a host says "these coordinates are mine": the engine writes into the array the
2540
- * host already lends to its snapshots, so a layout, a drag and a GPU readback all land in the
2541
- * one place and a re-freeze loses none of them.
2542
- * @param positions - the element-owned array
2543
- */
2544
- attachPositions(e) {
2545
- this.positionArray = e, this.positionArrayAttached = !0;
2546
- }
2547
- /**
2548
- * Copy every node's current coordinates out of the engine and into the position array.
2549
- *
2550
- * Engines call this at the end of a step, so that by the time anything draws, the array is the
2551
- * answer rather than a copy of it. The default walks the engine's own nodes through
2552
- * {@link LayoutEngine.getNodePosition}, which is correct for any engine but allocates one
2553
- * object per node; an engine that can read its own state without allocating overrides it, and
2554
- * an engine that already writes straight into the array overrides it to do nothing.
2555
- */
2556
- publishPositions() {
2557
- for (const e of this.nodes) {
2558
- const n = this.getNodePosition(e);
2559
- this.writeNodePosition(e, n.x, n.y, n.z ?? 0);
2560
- }
2561
- }
2562
- /**
2563
- * Read a node's published coordinates into an object the CALLER owns.
2564
- *
2565
- * The point of the out parameter is that a renderer can pass the vector it is about to draw
2566
- * with and allocate nothing per node per frame. A row that no engine has placed answers false
2567
- * and leaves `out` untouched, so the caller keeps whatever it had rather than being handed a
2568
- * NaN or an origin it cannot tell from a real coordinate.
2569
- * @param n - the node to read
2570
- * @param out - the object to fill; a Babylon `Vector3` is one, which is the point
2571
- * @param out.x - receives the scene-unit x
2572
- * @param out.y - receives the scene-unit y
2573
- * @param out.z - receives the scene-unit z
2574
- * @returns true when the node has a placed row
2575
- */
2576
- readNodePosition(e, n) {
2577
- const r = this.positionsFor(e);
2578
- return r.isPlaced(e.index) ? (r.read(e.index, n), !0) : !1;
2579
- }
2580
- /**
2581
- * Publish one node's coordinates, growing the array to reach its row.
2582
- *
2583
- * Three things are silently skipped rather than thrown, because this runs inside a layout step
2584
- * and a throw there kills the frame: a node with no row in the graph (`INVALID_INDEX`, which a
2585
- * record whose id the graph builder would not take carries for its whole life), a node whose
2586
- * index is not a row number at all, and a coordinate that cannot be stored. That last one is
2587
- * the important one -- a force layout that divided by a zero distance produces NaN, and an
2588
- * overflow of the f32 the array stores produces an infinity. Either one, written, would make
2589
- * the row read back as a place: the mesh vanishes and the scene bounds and camera framing go
2590
- * with it. Left alone, the row stays unplaced and the node keeps the coordinates it had.
2591
- *
2592
- * The array is GROWN to reach the row rather than the write being refused. A node's index is
2593
- * handed out the moment its record is taken, but its row only appears when the graph is next
2594
- * frozen, and a layout that ran in between would otherwise be thrown away in silence. Growth is
2595
- * prefix-stable and fills what it adds with NaN, so it cannot disturb a row anything else
2596
- * placed; the one thing it does change is that a node this engine placed before the first
2597
- * freeze counts as placed, which is what makes a file's own coordinates yield to it.
2598
- *
2599
- * A PINNED ROW REFUSES A LAYOUT STEP. This is the whole of "a pin is meaningful under every
2600
- * arrangement": twelve of the element's seventeen engines implement `pin()` as a no-op and
2601
- * `setNodePosition` as a no-op too, so before this guard a reader who dragged a node under a
2602
- * static layout watched it snap back the next time the layout recomputed. One refusal here
2603
- * covers every engine, including one written by a third party that has never heard of pinning,
2604
- * because every engine reaches the shared array through this method.
2605
- *
2606
- * A DRAG IS NOT A LAYOUT STEP. `intent: "placement"` writes straight through, so a reader can
2607
- * move a pinned node and have it stay where they put it; without that distinction the guard
2608
- * would make a pinned node undraggable, which is the opposite of what a pin is for. The
2609
- * default is `"layout"` so that an engine written before the parameter existed -- a plugin's
2610
- * step loop -- is guarded without knowing it, and only the placement paths have to opt out.
2611
- * @param n - the node being placed
2612
- * @param x - scene-unit x
2613
- * @param y - scene-unit y
2614
- * @param z - scene-unit z
2615
- * @param intent - `"layout"` for a simulation step, `"placement"` for a drag or a replay
2616
- * @returns true when the row was written
2617
- */
2618
- writeNodePosition(e, n, r, i, s = "layout") {
2619
- const { index: o } = e;
2620
- if (o === W || !Number.isInteger(o) || o < 0 || !A(n) || !A(r) || !A(i))
2621
- return !1;
2622
- const a = this.positionsFor(e);
2623
- return s === "layout" && a.isPinned(o) ? !1 : (o >= a.count && a.grow(o + 1), a.write(o, n, r, i), !0);
2624
- }
2625
- /**
2626
- * The weight of every ordered endpoint pair this batch of edges covers, or null when the
2627
- * graph's weights carry no information.
2628
- *
2629
- * WHY A PAIR AND NOT AN EDGE. `@graphty/layout`'s one weight channel is
2630
- * `graph.getEdgeData(source, target, attr)`, which is asked by endpoint pair, and both layout
2631
- * functions that read it write the answer into a matrix cell -- `A[i][j]` in ForceAtlas2,
2632
- * `distances[s][t]` in Kamada-Kawai. There is no cell for a second edge between the same two
2633
- * nodes, so parallel edges are SUMMED into one number rather than left to last-writer-wins,
2634
- * where the order the file happened to list them in would decide the arrangement. Summing is
2635
- * also what the element does when it simplifies a multigraph for an algorithm, so a graph's
2636
- * weights mean the same thing to a layout and to a metric.
2637
- *
2638
- * READ ONCE PER LAYOUT COMPUTATION, not per frame and not per edge: the weights come from the
2639
- * current snapshot's edge list, indexed by the same logical edge index `Edge.index` holds.
2640
- *
2641
- * NULL MEANS "DO NOT ATTACH A CALLBACK". graph-format stores an all-ones graph with no weight
2642
- * column at all, and a graph whose every weight is 1 carries no information a layout could
2643
- * arrange by -- so the caller leaves `getEdgeData` off the graph object entirely and the
2644
- * arrangement is bit-identical to the one the same seed produced before weights existed.
2645
- *
2646
- * THIS IS A SLIGHTLY NARROWER QUESTION THAN `statistics().weighted`, deliberately. The status
2647
- * chip asks whether the SNAPSHOT's weight column carries anything but ones; this asks it of
2648
- * the edges this engine is actually about to arrange. They answer differently only when the
2649
- * engine holds a strict subset of the graph's edges whose weights are all 1, and there the
2650
- * narrower answer is the correct one: a layout cannot be moved by a weight on an edge it is
2651
- * not laying out. A consumer who sees "weighted" on the status bar and an unmoved arrangement
2652
- * is looking at that case.
2653
- * @param edges - the edges this engine is about to lay out
2654
- * @returns pair key (see {@link pairWeightKey}) to summed weight, or null
2655
- */
2656
- pairWeights(e) {
2657
- if (e.length === 0)
2658
- return null;
2659
- const r = e[0].parentGraph?.getDataManager?.()?.getSnapshot?.()?.edgeList().weights ?? null;
2660
- if (r === null)
2661
- return null;
2662
- const i = /* @__PURE__ */ new Map();
2663
- let s = !1;
2664
- for (const o of e) {
2665
- const a = o.index >= 0 && o.index < r.length ? r[o.index] : 1;
2666
- a !== 1 && (s = !0);
2667
- const l = bn(o.srcId, o.dstId);
2668
- i.set(l, (i.get(l) ?? 0) + a);
2669
- }
2670
- return s ? i : null;
2671
- }
2672
- /**
2673
- * Say once, per layout computation, how many pairs were clamped off zero.
2674
- *
2675
- * ONCE PER RUN AND NOT PER EDGE: a graph whose weights are all zero would otherwise produce
2676
- * one line per edge, which buries every other message in the run it happened during. It is
2677
- * reported at all because a clamp changes the picture -- a zero-weight edge is drawn as the
2678
- * weakest connection the solver can express rather than as no connection -- and the record
2679
- * that carried the zero is the reader's, not the element's, so they are the one who can fix
2680
- * it.
2681
- * @param layout - the layout name, for the message
2682
- * @param weights - the pair weights about to be handed to the layout function
2683
- */
2684
- reportClampedWeights(e, n) {
2685
- let r = 0;
2686
- for (const i of n.values())
2687
- i < ve && r++;
2688
- r > 0 && yn.warn("Edge weights at or below zero were clamped before the layout read them", {
2689
- layout: e,
2690
- clamped: r,
2691
- epsilon: ve
2692
- });
2693
- }
2694
- /**
2695
- * The array to use for this node: the one its own graph owns, unless a host attached one.
2696
- *
2697
- * Resolved on every call rather than cached, because the element REPLACES its array when a
2698
- * dataset is discarded -- the store and everything keyed into it is thrown away and rebuilt --
2699
- * and an engine outlives that. A cached reference would keep publishing into the array of a
2700
- * graph that no longer exists, which is invisible: every write succeeds and nothing draws.
2701
- * @param n - the node being published or read
2702
- * @returns the array to write to and read from
2703
- */
2704
- positionsFor(e) {
2705
- if (!this.positionArrayAttached) {
2706
- const n = e.parentGraph?.getDataManager?.()?.positions;
2707
- n instanceof we && (this.positionArray = n);
2708
- }
2709
- return this.nodePositions;
2710
- }
2711
- /**
2712
- * Get the type identifier for this layout engine
2713
- * @returns The layout engine type string
2714
- */
2715
- get type() {
2716
- return this.constructor.type;
2717
- }
2718
- /**
2719
- * File a layout engine class under the name it declares, and publish what it says about
2720
- * itself to the catalogue.
2721
- *
2722
- * WHAT CHANGED AND WHY. This used to read `cls.type` through a cast and put the class in a
2723
- * map, which meant a class with no `static type` registered under the string "undefined", a
2724
- * second class under a taken name silently replaced the first, and a registered engine
2725
- * reached no catalogue at all -- so a third party's layout could run but could never be
2726
- * offered by a picker, described in a reader's language, or found by `layoutIdForEngine`.
2727
- *
2728
- * A third party's class must declare a `static descriptor` whose `id` equals its
2729
- * `static type`. The element's own seventeen are the one exemption, because their arrangements
2730
- * are authored centrally in the layout catalogue where several engines may sit behind one
2731
- * public name.
2732
- * @param cls - The layout engine class.
2733
- * @returns The same class, so a declaration can register itself in one expression.
2734
- * @throws A `GraphtyError` with `E_BAD_COMMAND` when the class declares no `static type`, no
2735
- * `static descriptor`, or a descriptor whose `id` disagrees with its `static type`; or with
2736
- * `E_DUPLICATE_PLUGIN` when the name or the descriptor id is one the element itself ships.
2737
- */
2738
- static register(e) {
2739
- const n = e, { type: r, descriptor: i } = n;
2740
- if (typeof r != "string" || r === "")
2741
- throw new w({
2742
- code: "E_BAD_COMMAND",
2743
- message: "a layout engine is filed under its `static type`, and this class declares none",
2744
- source: "registry",
2745
- details: { kind: "layout", field: "type" }
2746
- });
2747
- if (k.get(r) === e)
2748
- return e;
2749
- const s = gn.includes(r);
2750
- if (i === void 0) {
2751
- if (!s)
2752
- throw new w({
2753
- code: "E_BAD_COMMAND",
2754
- message: `the layout "${r}" 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`,
2755
- source: "registry",
2756
- details: { kind: "layout", name: r, field: "descriptor" }
2757
- });
2758
- if (k.has(r))
2759
- throw Ne(r);
2760
- return k.set(r, e), e;
2761
- }
2762
- if (s)
2763
- throw Ne(r);
2764
- if (i.id !== r)
2765
- throw new w({
2766
- code: "E_BAD_COMMAND",
2767
- message: `the layout "${r}" describes itself as "${i.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`,
2768
- source: "registry",
2769
- details: { kind: "layout", name: r, field: "descriptor.id", id: i.id }
2770
- });
2771
- return fn({
2772
- descriptor: { ...i, honoursWeights: n.honoursWeights ?? !1 },
2773
- type: r
2774
- }), k.set(r, e), e;
2775
- }
2776
- /**
2777
- * Get a layout engine instance by type
2778
- * @param type - The layout engine type identifier
2779
- * @param opts - Configuration options for the layout engine
2780
- * @returns A new layout engine instance or null if type not found
2781
- */
2782
- static get(e, n = {}) {
2783
- const r = k.get(e);
2784
- return r ? new r(n) : null;
2785
- }
2786
- /**
2787
- * Get dimension-specific options for this layout
2788
- * @param dimension - The desired dimension (2 or 3)
2789
- * @returns Options object for the dimension or null if unsupported
2790
- */
2791
- static getOptionsForDimension(e) {
2792
- return e > this.maxDimensions ? null : {};
2793
- }
2794
- /**
2795
- * Get dimension-specific options for a layout by type
2796
- * @param type - The layout engine type identifier
2797
- * @param dimension - The desired dimension (2 or 3)
2798
- * @returns Options object for the dimension or null if type not found or unsupported
2799
- */
2800
- static getOptionsForDimensionByType(e, n) {
2801
- const r = k.get(e);
2802
- return r ? r.getOptionsForDimension(n) : null;
2803
- }
2804
- /**
2805
- * Get the Zod-based options schema for this layout
2806
- * @returns The options schema, or an empty object if no schema defined
2807
- */
2808
- static getZodOptionsSchema() {
2809
- return this.zodOptionsSchema ?? {};
2810
- }
2811
- /**
2812
- * Check if this layout has a Zod-based options schema
2813
- * @returns true if the layout has options defined
2814
- */
2815
- static hasZodOptions() {
2816
- return this.zodOptionsSchema !== void 0 && Object.keys(this.zodOptionsSchema).length > 0;
2817
- }
2818
- /**
2819
- * Get a list of all registered layout types
2820
- * @returns Array of registered layout type identifiers
2821
- */
2822
- static getRegisteredTypes() {
2823
- return Array.from(k.keys());
2824
- }
2825
- /**
2826
- * Get a layout class by type
2827
- * @param type - The layout engine type identifier
2828
- * @returns The layout engine class or null if not found
2829
- */
2830
- static getClass(e) {
2831
- return k.get(e) ?? null;
2832
- }
2833
- };
2834
- ie.honoursWeights = !1;
2835
- let K = ie;
2836
- const vn = j.looseObject({
2837
- scalingFactor: j.number().default(100)
2838
- });
2839
- class or extends K {
2840
- /**
2841
- * Create a simple layout engine
2842
- * @param opts - Configuration options including scalingFactor
2843
- */
2844
- constructor(e = {}) {
2845
- super(), this._nodes = [], this._edges = [], this.stale = !0, this.positions = {}, this.scalingFactor = 100, this.isSettled = !0;
2846
- const n = vn.parse(e);
2847
- this.scalingFactor = n.scalingFactor;
2848
- }
2849
- /**
2850
- * Get dimension-specific options for simple layouts
2851
- * @param dimension - The desired dimension (2 or 3)
2852
- * @returns Options object with dim parameter or null if unsupported
2853
- */
2854
- static getOptionsForDimension(e) {
2855
- return e > this.maxDimensions ? null : { dim: e };
2856
- }
2857
- // basic functionality
2858
- /**
2859
- * Initialize the layout engine
2860
- *
2861
- * Simple layouts compute positions synchronously and don't require initialization.
2862
- */
2863
- async init() {
2864
- }
2865
- /**
2866
- * Add a node to the layout and mark positions as stale
2867
- * @param n - The node to add
2868
- */
2869
- addNode(e) {
2870
- this._nodes.push(e), this.stale = !0;
2871
- }
2872
- /**
2873
- * Add an edge to the layout and mark positions as stale
2874
- * @param e - The edge to add
2875
- */
2876
- addEdge(e) {
2877
- this._edges.push(e), this.stale = !0;
2878
- }
2879
- /**
2880
- * Get the position of a node, computing layout if stale
2881
- *
2882
- * The coordinates come from the shared position array, which `SimpleLayoutEngine.refresh`
2883
- * fills from `positions` as soon as the layout is recomputed. They are the same numbers the
2884
- * record holds, rounded to the f32 the array stores -- so an arrangement never moves, but a
2885
- * coordinate may differ in its last digit or two from the double the layout function returned.
2886
- * A node with no row falls back to the record, which is every node in an engine driven by hand.
2887
- * @param n - The node to get position for
2888
- * @returns The node's position coordinates
2889
- */
2890
- getNodePosition(e) {
2891
- return this.refresh(), this.publishedOr(e, e.id);
2892
- }
2893
- /**
2894
- * Record where the reader has just put a node.
2895
- *
2896
- * A static layout recomputes every coordinate from scratch, so it has no per-node state a
2897
- * placement could live in -- which is why this used to do nothing at all, and why a drag
2898
- * under any of the fourteen static arrangements was discarded by the next `refresh()`. The
2899
- * placement is written into the SHARED array instead, with `"placement"` intent so that it
2900
- * lands even on a pinned row, and into the computed record so that a read which falls back to
2901
- * the record (a node with no row of its own) answers the same.
2902
- * @param n - the node that moved
2903
- * @param p - where it moved to
2904
- */
2905
- setNodePosition(e, n) {
2906
- const r = n.z ?? 0;
2907
- this.writeNodePosition(e, n.x, n.y, r, "placement"), this.positions[e.id] = [n.x / this.scalingFactor, n.y / this.scalingFactor, r / this.scalingFactor];
2908
- }
2909
- /**
2910
- * Get the position of an edge based on its endpoints
2911
- * @param e - The edge to get position for
2912
- * @returns The edge's source and destination positions
2913
- */
2914
- getEdgePosition(e) {
2915
- return this.refresh(), {
2916
- src: this.publishedOr(e.srcNode, e.srcId),
2917
- dst: this.publishedOr(e.dstNode, e.dstId)
2918
- };
2919
- }
2920
- /**
2921
- * Copy the computed layout into the shared position array, recomputing it first if it is stale.
2922
- */
2923
- publishPositions() {
2924
- if (this.stale) {
2925
- this.refresh();
2926
- return;
2927
- }
2928
- this.publishRecord();
2929
- }
2930
- // for animated layouts
2931
- /**
2932
- * Step the layout animation
2933
- *
2934
- * Simple layouts are static and don't animate, so stepping has no effect.
2935
- */
2936
- step() {
2937
- }
2938
- /**
2939
- * Take a node out of the layout.
2940
- *
2941
- * WITHOUT THIS the engine holds the removed node -- and through it the node's Babylon mesh,
2942
- * its data record and its endpoints -- for as long as the engine lives, and the frame loop
2943
- * keeps walking it, so a node the reader deleted still draws at wherever it last was.
2944
- *
2945
- * The computed record is left alone and the layout is marked stale instead. Every layout
2946
- * function here returns a WHOLE new record, which `doLayout` assigns over the old one, and
2947
- * `refresh()` runs before any read -- so the removed node's entry is gone by the time anything
2948
- * could read it, without this method having to reach into a keyed object by a computed name.
2949
- * @param n - the node leaving the graph
2950
- */
2951
- removeNode(e) {
2952
- const n = this._nodes.indexOf(e);
2953
- n >= 0 && this._nodes.splice(n, 1), this.stale = !0;
2954
- }
2955
- /**
2956
- * The edge half of {@link SimpleLayoutEngine.removeNode}, with the same reason.
2957
- * @param e - the edge leaving the graph
2958
- */
2959
- removeEdge(e) {
2960
- const n = this._edges.indexOf(e);
2961
- n >= 0 && this._edges.splice(n, 1), this.stale = !0;
2962
- }
2963
- /**
2964
- * Pin a node in place
2965
- *
2966
- * A static layout has nothing of its own to hold still -- it recomputes every position from
2967
- * scratch -- so the pin is kept by the element's position array instead, and
2968
- * `writeNodePosition` refuses to move a pinned row. That is what makes a
2969
- * pin mean something under all fourteen of these engines, none of which could hold one.
2970
- */
2971
- pin() {
2972
- }
2973
- /**
2974
- * Unpin a node
2975
- *
2976
- * The element's position array holds the pin; see {@link SimpleLayoutEngine.pin}.
2977
- */
2978
- unpin() {
2979
- }
2980
- // properties
2981
- /**
2982
- * Get all nodes in the layout
2983
- * @returns Iterable of nodes
2984
- */
2985
- get nodes() {
2986
- return this._nodes;
2987
- }
2988
- /**
2989
- * Get all edges in the layout
2990
- * @returns Iterable of edges
2991
- */
2992
- get edges() {
2993
- return this._edges;
2994
- }
2995
- /**
2996
- * Recompute the layout when it is stale, and publish what it produced.
2997
- *
2998
- * A simple layout is computed once and then held, so this is the ONE place the shared array is
2999
- * filled: every reader below goes through here first, which is why a node added after the last
3000
- * read still gets a row before anything asks for its coordinates.
3001
- */
3002
- refresh() {
3003
- this.stale && (this.doLayout(), this.stale = !1, this.publishRecord());
3004
- }
3005
- /**
3006
- * Write the computed record into the shared array, scaled to scene units.
3007
- *
3008
- * A node the layout function returned nothing for is LEFT UNPLACED rather than published at
3009
- * the origin: the two are indistinguishable once stored, and the origin is a place a reader
3010
- * would draw at.
3011
- */
3012
- publishRecord() {
3013
- for (const e of this._nodes) {
3014
- const n = this.positions[e.id];
3015
- !n || n.length === 0 || this.writeNodePosition(
3016
- e,
3017
- n[0] * this.scalingFactor,
3018
- n[1] * this.scalingFactor,
3019
- (n[2] ?? 0) * this.scalingFactor
3020
- );
3021
- }
3022
- }
3023
- /**
3024
- * A node's published row, or the computed record when it has no row of its own.
3025
- *
3026
- * The fallback is not a rare path: an engine driven directly -- by a test, or by a host that
3027
- * keeps no graph -- has nodes whose index is `INVALID_INDEX`, and none of them is ever
3028
- * published. Both branches produce the same arrangement; only the rounding differs.
3029
- * @param n - the node, when the caller has one
3030
- * @param id - the node's id, which is how the computed record is keyed
3031
- * @returns a fresh coordinate triple
3032
- */
3033
- publishedOr(e, n) {
3034
- const r = { x: 0, y: 0, z: 0 };
3035
- return e !== void 0 && this.readNodePosition(e, r) ? r : Nn(this.positions[n], this.scalingFactor);
3036
- }
3037
- }
3038
- function Nn(t, e) {
3039
- if (!t || t.length === 0)
3040
- return { x: 0, y: 0, z: 0 };
3041
- const n = t[0] * e, r = t[1] * e, i = (t[2] ?? 0) * e;
3042
- return { x: n, y: r, z: i };
3043
- }
3044
- const On = /^#[0-9a-f]{6}$/i;
3045
- function kn(t) {
3046
- if (typeof t != "string" || t.trim() === "")
3047
- return null;
3048
- try {
3049
- const e = Be(t.trim());
3050
- if (e === void 0)
3051
- return null;
3052
- const n = e.length === 9 ? e.slice(0, 7) : e;
3053
- return On.test(n) ? n.toUpperCase() : null;
3054
- } catch {
3055
- return null;
3056
- }
3057
- }
3058
- function Sn(t) {
3059
- return JSON.stringify([t.id, t.kind, t.colors, t.capacity, t.colorblindSafe]);
3060
- }
3061
- const C = Z({
3062
- kind: "palette",
3063
- idOf: (t) => t.id,
3064
- descriptorOf: (t) => t,
3065
- implementationOf: Sn,
3066
- builtInIds: () => He
3067
- });
3068
- function I(t, e, n) {
3069
- throw new w({
3070
- code: "E_BAD_COMMAND",
3071
- message: n,
3072
- source: "registry",
3073
- details: { kind: "palette", name: typeof t == "string" ? t : "", field: e }
3074
- });
3075
- }
3076
- function ar(t, e) {
3077
- typeof t != "object" && I("", "descriptor", "registerPalette takes a palette descriptor"), (t.plainName === void 0 || t.plainName === "") && I(t.id, "plainName", `the palette "${String(t.id)}" was registered without a plain name`), t.kind !== "sequential" && t.kind !== "diverging" && t.kind !== "categorical" && I(
3078
- t.id,
3079
- "kind",
3080
- `the palette "${String(t.id)}" must be sequential, diverging or categorical`
3081
- ), (!Array.isArray(t.colors) || t.colors.length === 0) && I(t.id, "colors", `the palette "${String(t.id)}" was registered with no colours`);
3082
- const n = [];
3083
- for (const i of t.colors) {
3084
- const s = kn(i);
3085
- s === null && I(t.id, "colors", `"${String(i)}" in the palette "${String(t.id)}" is not a colour`), n.push(s);
3086
- }
3087
- const r = t.kind === "categorical" ? n.length : null;
3088
- t.capacity !== void 0 && t.capacity !== r && I(
3089
- t.id,
3090
- "capacity",
3091
- `the palette "${String(t.id)}" is ${t.kind}, so its capacity is ${r === null ? "null" : String(r)} rather than ${String(t.capacity)}`
3092
- ), C.register(
3093
- Object.freeze({
3094
- ...t,
3095
- colors: Object.freeze(n),
3096
- capacity: r,
3097
- colorblindSafe: Object.freeze(t.colorblindSafe ?? [])
3098
- }),
3099
- e
3100
- );
3101
- }
3102
- function cr() {
3103
- return C.descriptors();
3104
- }
3105
- function lr(t) {
3106
- return C.byId(t);
3107
- }
3108
- function ur() {
3109
- C.clearForTesting();
3110
- }
3111
- export {
3112
- lr as $,
3113
- Kn as A,
3114
- g as B,
3115
- A as C,
3116
- Bn as D,
3117
- we as E,
3118
- je as F,
3119
- $e as G,
3120
- Gn as H,
3121
- Pn as I,
3122
- _n as J,
3123
- Y as K,
3124
- Vn as L,
3125
- $t as M,
3126
- Mn as N,
3127
- zn as O,
3128
- qn as P,
3129
- Ln as Q,
3130
- jn as R,
3131
- vn as S,
3132
- Cn as T,
3133
- er as U,
3134
- nn as V,
3135
- tn as W,
3136
- bn as X,
3137
- sn as Y,
3138
- ve as Z,
3139
- ir as _,
3140
- K as a,
3141
- or as b,
3142
- Dn as c,
3143
- Qn as d,
3144
- sr as e,
3145
- ur as f,
3146
- Jn as g,
3147
- Un as h,
3148
- Hn as i,
3149
- Wn as j,
3150
- ln as k,
3151
- rr as l,
3152
- N as m,
3153
- Xn as n,
3154
- Rn as o,
3155
- cr as p,
3156
- Zn as q,
3157
- ar as r,
3158
- Yn as s,
3159
- q as t,
3160
- ee as u,
3161
- ut as v,
3162
- At as w,
3163
- P as x,
3164
- tr as y,
3165
- nr as z
3166
- };