@kanzo-tech/graph 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -0
- package/dist/adaptive.d.ts +27 -0
- package/dist/adaptive.d.ts.map +1 -0
- package/dist/adaptive.js +25 -0
- package/dist/adaptive.js.map +1 -0
- package/dist/bounded.d.ts +314 -0
- package/dist/bounded.d.ts.map +1 -0
- package/dist/bounded.js +39 -0
- package/dist/bounded.js.map +1 -0
- package/dist/cluster-ring.d.ts +25 -0
- package/dist/cluster-ring.d.ts.map +1 -0
- package/dist/cluster-ring.js +16 -0
- package/dist/cluster-ring.js.map +1 -0
- package/dist/css-color.d.ts +11 -0
- package/dist/css-color.d.ts.map +1 -0
- package/dist/css-color.js +23 -0
- package/dist/css-color.js.map +1 -0
- package/dist/duck-source.d.ts +182 -0
- package/dist/duck-source.d.ts.map +1 -0
- package/dist/duck-source.js +413 -0
- package/dist/duck-source.js.map +1 -0
- package/dist/graph-canvas.d.ts +50 -0
- package/dist/graph-canvas.d.ts.map +1 -0
- package/dist/graph-canvas.js +35 -0
- package/dist/graph-canvas.js.map +1 -0
- package/dist/graph-looks.d.ts +146 -0
- package/dist/graph-looks.d.ts.map +1 -0
- package/dist/graph-looks.js +44 -0
- package/dist/graph-looks.js.map +1 -0
- package/dist/graph-model.d.ts +130 -0
- package/dist/graph-model.d.ts.map +1 -0
- package/dist/graph-model.js +99 -0
- package/dist/graph-model.js.map +1 -0
- package/dist/graph-sim.d.ts +40 -0
- package/dist/graph-sim.d.ts.map +1 -0
- package/dist/graph-sim.js +20 -0
- package/dist/graph-sim.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-source.d.ts +32 -0
- package/dist/memory-source.d.ts.map +1 -0
- package/dist/memory-source.js +134 -0
- package/dist/memory-source.js.map +1 -0
- package/dist/obligations.d.ts +62 -0
- package/dist/obligations.d.ts.map +1 -0
- package/dist/resident.d.ts +79 -0
- package/dist/resident.d.ts.map +1 -0
- package/dist/resident.js +44 -0
- package/dist/resident.js.map +1 -0
- package/dist/section.d.ts +82 -0
- package/dist/section.d.ts.map +1 -0
- package/dist/section.js +142 -0
- package/dist/section.js.map +1 -0
- package/dist/slice-client.d.ts +78 -0
- package/dist/slice-client.d.ts.map +1 -0
- package/dist/slice-client.js +98 -0
- package/dist/slice-client.js.map +1 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/use-graph-look.d.ts +28 -0
- package/dist/use-graph-look.d.ts.map +1 -0
- package/dist/use-graph-look.js +28 -0
- package/dist/use-graph-look.js.map +1 -0
- package/dist/use-graph-overlays.d.ts +53 -0
- package/dist/use-graph-overlays.d.ts.map +1 -0
- package/dist/use-graph-overlays.js +96 -0
- package/dist/use-graph-overlays.js.map +1 -0
- package/dist/use-graph-selection.d.ts +59 -0
- package/dist/use-graph-selection.d.ts.map +1 -0
- package/dist/use-graph-selection.js +101 -0
- package/dist/use-graph-selection.js.map +1 -0
- package/dist/use-graph.d.ts +177 -0
- package/dist/use-graph.d.ts.map +1 -0
- package/dist/use-graph.js +101 -0
- package/dist/use-graph.js.map +1 -0
- package/dist/use-query-loop.d.ts +84 -0
- package/dist/use-query-loop.d.ts.map +1 -0
- package/dist/use-query-loop.js +151 -0
- package/dist/use-query-loop.js.map +1 -0
- package/dist/use-renderer.d.ts +86 -0
- package/dist/use-renderer.d.ts.map +1 -0
- package/dist/use-renderer.js +185 -0
- package/dist/use-renderer.js.map +1 -0
- package/dist/when-ready.d.ts +35 -0
- package/dist/when-ready.d.ts.map +1 -0
- package/dist/when-ready.js +17 -0
- package/dist/when-ready.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { CHART_SLOTS as A, categoricalCapacity as F, categoricalColor as H } from "@kanzo-tech/ui";
|
|
2
|
+
import { toHex as m, resolveToken as p } from "./css-color.js";
|
|
3
|
+
import { SHAPE_ORDER as S, SHAPE_OTHER as w, SHAPE as D } from "./graph-looks.js";
|
|
4
|
+
function E(n) {
|
|
5
|
+
return n !== void 0 && /^(var\(|#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(n);
|
|
6
|
+
}
|
|
7
|
+
function L(n, i = A) {
|
|
8
|
+
const e = E(n.fill) ? n.fill : void 0, s = n.symbol !== void 0;
|
|
9
|
+
return {
|
|
10
|
+
color: (o) => e || (o >= i ? "var(--muted-foreground)" : H(o, void 0, i)),
|
|
11
|
+
// The shape order runs out at four, so the fifth ordinal and anything past it land on
|
|
12
|
+
// `SHAPE_OTHER` — which is what makes the scale's claim true. Falling back to `circle` would
|
|
13
|
+
// hand category 5 the glyph category 0 already wears.
|
|
14
|
+
shape: (o) => s ? S[o] ?? w : D.circle
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function T(n, i, e, s = {}) {
|
|
18
|
+
const o = L(s, F(e)), v = /* @__PURE__ */ new Map(), C = (t) => {
|
|
19
|
+
let r = v.get(t);
|
|
20
|
+
return r || (r = p(e, o.color(t)), v.set(t, r)), r;
|
|
21
|
+
}, k = n.positions.length / 2, f = new Float32Array(k * 4), y = new Float32Array(k), d = new Float32Array(k), a = n.sizes;
|
|
22
|
+
let g = 0, b = 1;
|
|
23
|
+
if (a && a.length > 0) {
|
|
24
|
+
let t = Number.POSITIVE_INFINITY, r = 0;
|
|
25
|
+
for (let l = 0; l < a.length; l++) {
|
|
26
|
+
const u = a[l];
|
|
27
|
+
u < t && (t = u), u > r && (r = u);
|
|
28
|
+
}
|
|
29
|
+
g = Math.sqrt(Number.isFinite(t) ? t : 0), b = Math.sqrt(r) - g || 1;
|
|
30
|
+
}
|
|
31
|
+
const P = Math.min(n.marks, k);
|
|
32
|
+
for (let t = 0; t < P; t++) {
|
|
33
|
+
const r = n.categories[t] ?? 0;
|
|
34
|
+
f.set(C(r), t * 4);
|
|
35
|
+
const l = a ? (Math.sqrt(a[t]) - g) / b : 0;
|
|
36
|
+
y[t] = i.size[0] + l * (i.size[1] - i.size[0]), d[t] = o.shape(r);
|
|
37
|
+
}
|
|
38
|
+
const h = n.links.length / 2, O = new Float32Array(h * 4), c = s.stroke ? p(e, s.stroke) : null;
|
|
39
|
+
for (let t = 0; t < h; t++) {
|
|
40
|
+
const r = n.links[t * 2] ?? 0, l = c ? c[0] : f[r * 4] ?? 0.7, u = c ? c[1] : f[r * 4 + 1] ?? 0.7, R = c ? c[2] : f[r * 4 + 2] ?? 0.7;
|
|
41
|
+
O.set([l, u, R, 1], t * 4);
|
|
42
|
+
}
|
|
43
|
+
return { colors: f, sizes: y, shapes: d, linkColors: O };
|
|
44
|
+
}
|
|
45
|
+
function _(n, i) {
|
|
46
|
+
const { sourceIndexToTargetIndices: e, targetIndexToSourceIndices: s } = n.graph;
|
|
47
|
+
return [
|
|
48
|
+
...((e == null ? void 0 : e[i]) ?? []).map((o) => o[0]),
|
|
49
|
+
...((s == null ? void 0 : s[i]) ?? []).map((o) => o[0])
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
function q(n) {
|
|
53
|
+
return {
|
|
54
|
+
simulationGravity: n.gravity,
|
|
55
|
+
simulationRepulsion: n.repulsion,
|
|
56
|
+
simulationLinkSpring: n.linkSpring,
|
|
57
|
+
simulationLinkDistance: n.linkDistance,
|
|
58
|
+
simulationFriction: n.friction,
|
|
59
|
+
simulationCluster: n.cluster
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function G(n, i) {
|
|
63
|
+
return {
|
|
64
|
+
// Always the theme's surface. Nebula used to pin a near-black of its own, which made it the one
|
|
65
|
+
// look that ignored light mode — and put its fixed dark plane at odds with the light chrome
|
|
66
|
+
// sitting on top of it.
|
|
67
|
+
backgroundColor: m(p(i, "var(--background)")),
|
|
68
|
+
// Points hold their screen size in every look. This was a field until all three settled on the
|
|
69
|
+
// same value, at which point it was a field with one possible answer.
|
|
70
|
+
scalePointsOnZoom: !1,
|
|
71
|
+
renderLinks: n.link.render,
|
|
72
|
+
/** Edge opacity — the look's, multiplying each link's buffer alpha. */
|
|
73
|
+
linkOpacity: n.link.opacity,
|
|
74
|
+
linkDefaultWidth: n.link.width,
|
|
75
|
+
linkBlending: n.link.blend,
|
|
76
|
+
curvedLinks: n.link.curve > 0,
|
|
77
|
+
curvedLinkControlPointDistance: n.link.curve,
|
|
78
|
+
linkVisibilityDistanceRange: n.link.fade,
|
|
79
|
+
linkVisibilityMinTransparency: 0.12,
|
|
80
|
+
renderHoveredPointRing: !0,
|
|
81
|
+
hoveredPointRingColor: m(p(i, "var(--primary)")),
|
|
82
|
+
focusedPointRingColor: m(p(i, "var(--primary)")),
|
|
83
|
+
// The two greyouts are not the same kind of number, whatever the names suggest. A greyed link
|
|
84
|
+
// multiplies (`opacity *= greyoutOpacity`), so it stays under the slider; a greyed point takes
|
|
85
|
+
// this *instead of* `pointOpacity` — the point shader is an if/else. So a global point opacity,
|
|
86
|
+
// if this canvas ever grows one, would not reach a dimmed node, and its floor would be 0.1 flat.
|
|
87
|
+
pointGreyoutOpacity: 0.1,
|
|
88
|
+
linkGreyoutOpacity: 0.025
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
G as appearance,
|
|
93
|
+
T as buffers,
|
|
94
|
+
q as forces,
|
|
95
|
+
E as isColour,
|
|
96
|
+
_ as neighboursOf,
|
|
97
|
+
L as scaleOf
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=graph-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-model.js","sources":["../src/graph-model.ts"],"sourcesContent":["import type { Graph, GraphConfig } from \"@cosmos.gl/graph\";\n// The categorical scheme is on the root barrel, not `/analytics`: a graph paints from it and\n// never opens a database, so the chart half is not what it needs.\nimport { CHART_SLOTS, categoricalCapacity, categoricalColor } from \"@kanzo-tech/ui\";\nimport type { Slice } from \"./bounded\";\nimport { resolveToken, toHex, type Rgba } from \"./css-color\";\nimport { SHAPE, SHAPE_ORDER, SHAPE_OTHER, type Look, type ShapeId } from \"./graph-looks\";\nimport type { Sim } from \"./graph-sim\";\n\n/**\n * From a slice to the GPU, and nothing about where the slice came from.\n *\n * `load()` used to live here — a relation in, every id, every row, an id→index map and a global\n * ranking out. ADR-0001 deleted it: the working set was N, so the ceiling was whatever N the machine\n * could hold, and 1,225 ms of first paint at 200,000 nodes was the measurement that ended the\n * argument. What replaces it is not a faster loader but a different question — `BoundedSource`\n * answers *what should I draw*, `useQueryLoop` asks it, and this turns the answer into buffers.\n *\n * The visible consequence is that **a category is an ordinal here, never a name.** A slice carries\n * `Uint16Array` category codes because carrying twenty thousand label strings to colour twenty\n * thousand dots is paying for a vocabulary the GPU cannot read. What each ordinal is *called* is a\n * question for a legend, and a legend asks the source.\n */\n\n/**\n * What each channel is bound to — Plot's names, and Plot's rule about what a value means.\n *\n * **A CSS colour is a constant; anything else is a column name.** `fill=\"kind\"` spends colour on a\n * category; `fill=\"var(--foreground)\"` paints every point one ink and leaves colour free to mean the\n * selection. That is not our invention: it is how Plot reads the same string, and it is what makes\n * \"monochrome\" expressible without a look that rebinds an encoding.\n *\n * The rule is spelled narrowly on purpose: a constant is `var(…)`, a hex, or a CSS colour function.\n * **A bare word is always a column**, so a corpus with a column called `red` is not a trap. The cost\n * is that the 148 CSS named colours are not accepted as constants, which is the trade a one-line\n * rule buys over a table nobody would keep current.\n */\nexport interface Channels {\n /** Which column colours a point, or a CSS colour every point wears. */\n fill?: string;\n /**\n * Which column a point's **shape** carries — Plot's `symbol`, and a peer of colour rather than a\n * decoration.\n *\n * Bound to the same column as `fill`, this is redundant encoding and costs nothing. Bound to a\n * *different* column it would need a second categorical array in the slice and in every source,\n * which is not paid for — and it is also the pairing the literature warns about, so the limit and\n * the advice point the same way. A slice carries one categorical column, and this spends shape on\n * it.\n */\n symbol?: string;\n /**\n * What tints a link. **Absent, each link takes the colour of the vertex it leaves**; a constant\n * makes links plain structure.\n *\n * No column form yet: a per-link datum — weight, confidence, recency — is a second array a slice\n * does not carry.\n */\n stroke?: string;\n}\n\n/**\n * Whether a channel's value is a **colour** — and therefore a constant rather than a column.\n *\n * Plot's test, narrowed to what a token-based system actually writes. Exported because the split it\n * decides happens in two places: the buffers paint the constant, and the query must not be asked to\n * fetch a column called `var(--foreground)`.\n */\nexport function isColour(value: string | undefined): value is string {\n return value !== undefined && /^(var\\(|#|rgb|hsl|oklch|oklab|lab|lch|color\\()/i.test(value);\n}\n\n/**\n * The categorical scale the bindings imply — what colour and what shape a category ordinal wears.\n *\n * One answer for the GPU buffers, the hover card and the legend, because three answers is how a\n * legend ends up disagreeing with the canvas it explains. Values are CSS strings: the DOM resolves\n * them itself, and the buffer path resolves them once on its way to the GPU.\n *\n * `capacity` is where Other begins, and it is the document's number rather than the token\n * vocabulary's: a set derived from a client's brand names 6 to 8 real categories, not always 8. The\n * colour past it would come out muted anyway — `compile()` writes `var(--muted-foreground)` into\n * those slots — but the graph has to *count* the same way, or a legend claims eight kinds it cannot\n * tell apart.\n */\nexport function scaleOf(channels: Channels, capacity = CHART_SLOTS) {\n const constant = isColour(channels.fill) ? channels.fill : undefined;\n const shaped = channels.symbol !== undefined;\n return {\n color: (ordinal: number): string => {\n if (constant) return constant;\n return ordinal >= capacity\n ? \"var(--muted-foreground)\"\n : categoricalColor(ordinal, undefined, capacity);\n },\n // The shape order runs out at four, so the fifth ordinal and anything past it land on\n // `SHAPE_OTHER` — which is what makes the scale's claim true. Falling back to `circle` would\n // hand category 5 the glyph category 0 already wears.\n shape: (ordinal: number): ShapeId =>\n shaped ? (SHAPE_ORDER[ordinal] ?? SHAPE_OTHER) : SHAPE.circle,\n };\n}\n\nexport interface Buffers {\n colors: Float32Array;\n sizes: Float32Array;\n shapes: Float32Array;\n linkColors: Float32Array;\n}\n\n/**\n * Every per-point and per-link attribute the GPU needs, from one slice and the live theme.\n *\n * `host` is the element the tokens are read against, which is what makes `var(--primary)` a legal\n * value in a look: the browser resolves it for the tree the canvas actually sits in, so the same\n * look answers differently in light and in dark.\n *\n * `display` is deliberately not an argument. Everything the reader's sliders control is a *global\n * scalar*, and a global scalar belongs in a uniform — see `appearance` — not multiplied into every\n * size and every RGBA quad that then have to be re-uploaded.\n *\n * The size ramp now spans **this slice**, not the corpus. That is the trade ADR-0001 names: a global\n * ordering is what a whole-corpus load gets for free and a bounded one cannot have. It is also\n * arguably the better question — the biggest node *here* is what a reader is looking at.\n */\nexport function buffers(\n slice: Slice,\n look: Look,\n host: Element,\n channels: Channels = {},\n): Buffers {\n const scale = scaleOf(channels, categoricalCapacity(host));\n\n /** Ordinal → resolved colour, memoised: a slice of 20,000 points wears at most a handful. */\n const rgba = new Map<number, Rgba>();\n const colourOf = (ordinal: number): Rgba => {\n let resolved = rgba.get(ordinal);\n if (!resolved) {\n resolved = resolveToken(host, scale.color(ordinal));\n rgba.set(ordinal, resolved);\n }\n return resolved;\n };\n\n const n = slice.positions.length / 2;\n const colors = new Float32Array(n * 4);\n const sizes = new Float32Array(n);\n const shapes = new Float32Array(n);\n\n // Whatever the source ranks by, square-rooted: a degree distribution is heavy-tailed and a linear\n // ramp leaves everything but the three biggest hubs on the floor. It used to read a second column\n // in aggregate mode — how many vertices a super-node stood for — and there are no super-nodes.\n const ramp = slice.sizes;\n let lo = 0;\n let span = 1;\n if (ramp && ramp.length > 0) {\n let min = Number.POSITIVE_INFINITY;\n let max = 0;\n for (let i = 0; i < ramp.length; i++) {\n const value = ramp[i] as number;\n if (value < min) min = value;\n if (value > max) max = value;\n }\n lo = Math.sqrt(Number.isFinite(min) ? min : 0);\n span = Math.sqrt(max) - lo || 1;\n }\n\n /**\n * Where the marks stop and the anchors begin.\n *\n * An anchor exists so an edge leaving the window has an end to be drawn to, and it is **not a\n * mark**: radius zero and alpha zero, so it cannot paint, cannot be picked and cannot be occluded\n * by. The zeroing is the loop bound and nothing else: the buffers are allocated zero-filled, so\n * stopping at `marks` leaves every anchor at radius zero and alpha zero. Writing it out again\n * afterwards would be a second statement of the same fact that no mutation can distinguish from\n * the first — which is how a guard goes green for the wrong reason.\n */\n const marks = Math.min(slice.marks, n);\n\n for (let i = 0; i < marks; i++) {\n const ordinal = slice.categories[i] ?? 0;\n // The slot colour is used as given. Nebula used to lighten it by degree, which is exactly the\n // kind of adjustment a validated palette cannot survive: every slot was measured for lightness\n // band, chroma floor and separation, and nudging one on the way to the GPU voids all three.\n colors.set(colourOf(ordinal), i * 4);\n const t = ramp ? (Math.sqrt(ramp[i] as number) - lo) / span : 0;\n sizes[i] = look.size[0] + t * (look.size[1] - look.size[0]);\n shapes[i] = scale.shape(ordinal);\n }\n\n const count = slice.links.length / 2;\n const linkColors = new Float32Array(count * 4);\n // Absent, a link takes the colour of the vertex it leaves; a constant makes links plain structure.\n const neutral = channels.stroke ? resolveToken(host, channels.stroke) : null;\n for (let e = 0; e < count; e++) {\n const src = slice.links[e * 2] ?? 0;\n const r = neutral ? neutral[0] : (colors[src * 4] ?? 0.7);\n const g = neutral ? neutral[1] : (colors[src * 4 + 1] ?? 0.7);\n const b = neutral ? neutral[2] : (colors[src * 4 + 2] ?? 0.7);\n // Alpha is 1, and it is *reserved* — for a datum that genuinely differs per link: edge weight,\n // confidence, recency. It used to carry `look.opacity × display.linkOpacity`, which is the same\n // number on every link, and paying for that cost a full re-upload of this array on every tick of\n // the Edge opacity slider. That product is a uniform now (`appearance`), and the shader\n // multiplies the two: `color.a * linkOpacity * …`. Do not spend this channel again.\n linkColors.set([r, g, b, 1], e * 4);\n }\n\n return { colors, sizes, shapes, linkColors };\n}\n\n/**\n * The points one hop from `index`, in both directions, **within the drawn slice**.\n *\n * This is the renderer's adjacency, not the graph's: it answers what is on screen, which is what a\n * hover highlight wants. The graph's own answer — what is adjacent whether or not it is drawn — is a\n * `neighbourhood` query, and a source that supports one answers it.\n *\n * Ours because 3.0 dropped `getAdjacentIndices` and the method that looks like its replacement is\n * not one: `getConnectedLinkIndices` filters on `n.has(d)`, so it answers only with links whose\n * *other* endpoint is also in the argument — an induced subgraph, which for a single point is its\n * self-loops. The adjacency lists themselves are still public on `graph.graph`, and each entry is a\n * `[otherPointIndex, linkIndex]` pair, so the neighbourhood is the first element of each.\n */\nexport function neighboursOf(graph: Graph, index: number): number[] {\n const { sourceIndexToTargetIndices, targetIndexToSourceIndices } = graph.graph;\n return [\n ...(sourceIndexToTargetIndices?.[index] ?? []).map((pair) => pair[0]),\n ...(targetIndexToSourceIndices?.[index] ?? []).map((pair) => pair[0]),\n ];\n}\n\n/** The simulation coefficients, in cosmos.gl's spelling. Shared by construction and every change. */\nexport function forces(sim: Sim): GraphConfig {\n return {\n simulationGravity: sim.gravity,\n simulationRepulsion: sim.repulsion,\n simulationLinkSpring: sim.linkSpring,\n simulationLinkDistance: sim.linkDistance,\n simulationFriction: sim.friction,\n simulationCluster: sim.cluster,\n };\n}\n\n/**\n * Everything the picture needs that is *one number for the whole canvas* — cosmos.gl's uniforms.\n *\n * That is the line between this and `buffers`, and it is the renderer's own: a uniform is read fresh\n * from the config on every draw, so changing one rebuilds no array and uploads nothing — which is\n * why a look change costs the GPU a `setConfigPartial` and no upload at all.\n *\n * It took a `Display` beside the look, and the two multipliers it carried are gone: each scaled a\n * number this same look already computes from the axis that owns it, so the panel offered two ways\n * to say one thing and the slider could always overrule the measurement.\n */\nexport function appearance(look: Look, host: Element): GraphConfig {\n return {\n // Always the theme's surface. Nebula used to pin a near-black of its own, which made it the one\n // look that ignored light mode — and put its fixed dark plane at odds with the light chrome\n // sitting on top of it.\n backgroundColor: toHex(resolveToken(host, \"var(--background)\")),\n // Points hold their screen size in every look. This was a field until all three settled on the\n // same value, at which point it was a field with one possible answer.\n scalePointsOnZoom: false,\n renderLinks: look.link.render,\n /** Edge opacity — the look's, multiplying each link's buffer alpha. */\n linkOpacity: look.link.opacity,\n linkDefaultWidth: look.link.width,\n linkBlending: look.link.blend,\n curvedLinks: look.link.curve > 0,\n curvedLinkControlPointDistance: look.link.curve,\n linkVisibilityDistanceRange: look.link.fade,\n linkVisibilityMinTransparency: 0.12,\n renderHoveredPointRing: true,\n hoveredPointRingColor: toHex(resolveToken(host, \"var(--primary)\")),\n focusedPointRingColor: toHex(resolveToken(host, \"var(--primary)\")),\n // The two greyouts are not the same kind of number, whatever the names suggest. A greyed link\n // multiplies (`opacity *= greyoutOpacity`), so it stays under the slider; a greyed point takes\n // this *instead of* `pointOpacity` — the point shader is an if/else. So a global point opacity,\n // if this canvas ever grows one, would not reach a dimmed node, and its floor would be 0.1 flat.\n pointGreyoutOpacity: 0.1,\n linkGreyoutOpacity: 0.025,\n };\n}\n"],"names":["isColour","value","scaleOf","channels","capacity","CHART_SLOTS","constant","shaped","ordinal","categoricalColor","SHAPE_ORDER","SHAPE_OTHER","SHAPE","buffers","slice","look","host","scale","categoricalCapacity","rgba","colourOf","resolved","resolveToken","n","colors","sizes","shapes","ramp","lo","span","min","max","i","marks","t","count","linkColors","neutral","e","src","r","g","b","neighboursOf","graph","index","sourceIndexToTargetIndices","targetIndexToSourceIndices","pair","forces","sim","appearance","toHex"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The force coefficients, and the one function that builds them from the axes a person chose.
|
|
3
|
+
*
|
|
4
|
+
* `graph-looks.ts`'s sibling, and deliberately its twin: a type the renderer consumes, one builder
|
|
5
|
+
* that reads declared string values, and a default that is the builder called with nothing. What was
|
|
6
|
+
* here before is what was there before — an interface, a `DEFAULT_SIM` table beside it, and a dock
|
|
7
|
+
* that held six of them in React state and drew six sliders by hand.
|
|
8
|
+
*
|
|
9
|
+
* **A coefficient is not appearance, and it is still a preference.** `section.ts` says why it shares
|
|
10
|
+
* the manifest: `tokens` are the colours a document may move, `prefs` are what the person on the
|
|
11
|
+
* screen decides, and somebody tuning a layout until it settles is deciding.
|
|
12
|
+
*/
|
|
13
|
+
/** Force coefficients, handed straight to the GPU simulation. */
|
|
14
|
+
export interface Sim {
|
|
15
|
+
gravity: number;
|
|
16
|
+
repulsion: number;
|
|
17
|
+
linkSpring: number;
|
|
18
|
+
linkDistance: number;
|
|
19
|
+
friction: number;
|
|
20
|
+
/** Pull toward the node's group position on the cluster ring. Zero lets the links decide alone. */
|
|
21
|
+
cluster: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Coefficients that settle a few-hundred-node graph into something readable.
|
|
25
|
+
*
|
|
26
|
+
* Chosen against a corpus of that size, and they are a starting point rather than a law: a graph
|
|
27
|
+
* two orders of magnitude larger wants less repulsion and more friction, and the measurements in
|
|
28
|
+
* `BENCHMARKS.md` say a live simulation is finished by around 200,000 points regardless. What a
|
|
29
|
+
* corpus of a given size wants is computed rather than chosen — see `adaptive` — and a host that
|
|
30
|
+
* knows its corpus should start its users at that answer through the tenant policy.
|
|
31
|
+
*
|
|
32
|
+
* The values are strings for the reason `lookFrom`'s are: a contributed preference is a string in
|
|
33
|
+
* all three kinds, so an unrecognised namespace rides through a write untouched. A key that is
|
|
34
|
+
* missing, or that carries a value the section never offered, takes the default below — which is
|
|
35
|
+
* what a graph simulated before any of this existed.
|
|
36
|
+
*/
|
|
37
|
+
export declare function simFrom(values?: Readonly<Record<string, string | undefined>>): Sim;
|
|
38
|
+
/** What a canvas simulates with when nobody has chosen anything. */
|
|
39
|
+
export declare const DEFAULT_SIM: Sim;
|
|
40
|
+
//# sourceMappingURL=graph-sim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-sim.d.ts","sourceRoot":"","sources":["../src/graph-sim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,iEAAiE;AACjE,MAAM,WAAW,GAAG;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,mGAAmG;IACnG,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAM,GAAG,GAAG,CAatF;AAED,oEAAoE;AACpE,eAAO,MAAM,WAAW,EAAE,GAAe,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function s(r = {}) {
|
|
2
|
+
const i = (t, e) => {
|
|
3
|
+
const n = Number.parseFloat(r[t] ?? "");
|
|
4
|
+
return Number.isFinite(n) ? n : e;
|
|
5
|
+
};
|
|
6
|
+
return {
|
|
7
|
+
gravity: i("gravity", 0.14),
|
|
8
|
+
repulsion: i("repulsion", 1.1),
|
|
9
|
+
linkSpring: i("link-spring", 0.6),
|
|
10
|
+
linkDistance: i("link-distance", 18),
|
|
11
|
+
friction: i("friction", 0.86),
|
|
12
|
+
cluster: i("cluster", 0.1)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const o = s();
|
|
16
|
+
export {
|
|
17
|
+
o as DEFAULT_SIM,
|
|
18
|
+
s as simFrom
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=graph-sim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-sim.js","sources":["../src/graph-sim.ts"],"sourcesContent":["/**\n * The force coefficients, and the one function that builds them from the axes a person chose.\n *\n * `graph-looks.ts`'s sibling, and deliberately its twin: a type the renderer consumes, one builder\n * that reads declared string values, and a default that is the builder called with nothing. What was\n * here before is what was there before — an interface, a `DEFAULT_SIM` table beside it, and a dock\n * that held six of them in React state and drew six sliders by hand.\n *\n * **A coefficient is not appearance, and it is still a preference.** `section.ts` says why it shares\n * the manifest: `tokens` are the colours a document may move, `prefs` are what the person on the\n * screen decides, and somebody tuning a layout until it settles is deciding.\n */\n\n/** Force coefficients, handed straight to the GPU simulation. */\nexport interface Sim {\n gravity: number;\n repulsion: number;\n linkSpring: number;\n linkDistance: number;\n friction: number;\n /** Pull toward the node's group position on the cluster ring. Zero lets the links decide alone. */\n cluster: number;\n}\n\n/**\n * Coefficients that settle a few-hundred-node graph into something readable.\n *\n * Chosen against a corpus of that size, and they are a starting point rather than a law: a graph\n * two orders of magnitude larger wants less repulsion and more friction, and the measurements in\n * `BENCHMARKS.md` say a live simulation is finished by around 200,000 points regardless. What a\n * corpus of a given size wants is computed rather than chosen — see `adaptive` — and a host that\n * knows its corpus should start its users at that answer through the tenant policy.\n *\n * The values are strings for the reason `lookFrom`'s are: a contributed preference is a string in\n * all three kinds, so an unrecognised namespace rides through a write untouched. A key that is\n * missing, or that carries a value the section never offered, takes the default below — which is\n * what a graph simulated before any of this existed.\n */\nexport function simFrom(values: Readonly<Record<string, string | undefined>> = {}): Sim {\n const num = (key: string, fallback: number) => {\n const value = Number.parseFloat(values[key] ?? \"\");\n return Number.isFinite(value) ? value : fallback;\n };\n return {\n gravity: num(\"gravity\", 0.14),\n repulsion: num(\"repulsion\", 1.1),\n linkSpring: num(\"link-spring\", 0.6),\n linkDistance: num(\"link-distance\", 18),\n friction: num(\"friction\", 0.86),\n cluster: num(\"cluster\", 0.1),\n };\n}\n\n/** What a canvas simulates with when nobody has chosen anything. */\nexport const DEFAULT_SIM: Sim = simFrom();\n"],"names":["simFrom","values","num","key","fallback","value","DEFAULT_SIM"],"mappings":"AAsCO,SAASA,EAAQC,IAAuD,IAAS;AACtF,QAAMC,IAAM,CAACC,GAAaC,MAAqB;AAC7C,UAAMC,IAAQ,OAAO,WAAWJ,EAAOE,CAAG,KAAK,EAAE;AACjD,WAAO,OAAO,SAASE,CAAK,IAAIA,IAAQD;AAAA,EAC1C;AACA,SAAO;AAAA,IACL,SAASF,EAAI,WAAW,IAAI;AAAA,IAC5B,WAAWA,EAAI,aAAa,GAAG;AAAA,IAC/B,YAAYA,EAAI,eAAe,GAAG;AAAA,IAClC,cAAcA,EAAI,iBAAiB,EAAE;AAAA,IACrC,UAAUA,EAAI,YAAY,IAAI;AAAA,IAC9B,SAASA,EAAI,WAAW,GAAG;AAAA,EAAA;AAE/B;AAGO,MAAMI,IAAmBN,EAAA;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A GPU graph view, in the pieces a host actually composes.
|
|
3
|
+
*
|
|
4
|
+
* **`GraphCanvas` owns the three that never differ; the arrangement stays at the call site.** This
|
|
5
|
+
* barrel said for a long time that there was no canvas component, on the argument that a graph
|
|
6
|
+
* canvas is a toolbar, a legend, an inspector and a hover card — all of them per-product. That half
|
|
7
|
+
* is still true and they are still yours. What the argument missed is that underneath them sit
|
|
8
|
+
* three things that were identical everywhere and re-wired by hand each time: the renderer's
|
|
9
|
+
* lifetime, the query loop that follows the camera, and the buffers a look implies.
|
|
10
|
+
* `/docs/design/graph` has the measurement.
|
|
11
|
+
*
|
|
12
|
+
* **Why a package and not `@kanzo-tech/ui`.** The first admission rule is *domain-free —
|
|
13
|
+
* nothing about RDF / SHACL / fossil / graphs / auth*. Graphs are excluded by name, deliberately:
|
|
14
|
+
* `ui` is the generic vocabulary shared by every product. A sibling package is also the only place
|
|
15
|
+
* a required WebGL peer belongs.
|
|
16
|
+
*
|
|
17
|
+
* **The measurements that shape it** are in `BENCHMARKS.md`: a live simulation is comfortable to
|
|
18
|
+
* about 50,000 points and finished by 200,000, and past that the honest design is positions
|
|
19
|
+
* precomputed once and stored as a column — which is why a simulation is opt-in here and off by
|
|
20
|
+
* default, and why the render path is bounded rather than fast.
|
|
21
|
+
*/
|
|
22
|
+
export { buffers, scaleOf, isColour, forces, appearance, neighboursOf, type Buffers, type Channels, } from './graph-model';
|
|
23
|
+
export { DEFAULT_LOOK, lookFrom, SHAPE, SHAPE_ORDER, SHAPE_OTHER, SHAPE_PATH, type Look, type ShapeId, } from './graph-looks';
|
|
24
|
+
/**
|
|
25
|
+
* The graph, in Ark's four pieces.
|
|
26
|
+
*
|
|
27
|
+
* `useGraph(props)` **creates** the api and `useGraphContext()` **reads** it, which is Ark's
|
|
28
|
+
* convention and was inverted here: the reader was `useGraphCanvas` and there was no creator.
|
|
29
|
+
* `GraphCanvas` is the shortcut that does both, and `GraphRootProvider` is what a host reaches for
|
|
30
|
+
* when it has to call a hook *beside* the canvas — `useGraphOverlays` and the `events` block want
|
|
31
|
+
* `getGraph` and `getResident` from above the element, where no context is readable.
|
|
32
|
+
*
|
|
33
|
+
* That was not a symmetry we wanted for its own sake: it is what the workspace could not adopt
|
|
34
|
+
* `GraphCanvas` without, and the `graphRef`/`residentRef` props that stood in for it are gone.
|
|
35
|
+
*/
|
|
36
|
+
export { GraphCanvas, GraphRootProvider, useGraphContext, type GraphCanvasProps, type GraphRootProviderProps, } from './graph-canvas';
|
|
37
|
+
export { useGraph, type GraphApi, type GraphEvents, type UseGraphProps } from './use-graph';
|
|
38
|
+
export { REHEAT } from './use-renderer';
|
|
39
|
+
export { useGraphOverlays, GRID, type GraphOverlayOptions, type GraphOverlays, } from './use-graph-overlays';
|
|
40
|
+
export { useGraphSelection, cursorChip } from './use-graph-selection';
|
|
41
|
+
/**
|
|
42
|
+
* Identity, and the map from it to the buffer index cosmos.gl draws at.
|
|
43
|
+
*
|
|
44
|
+
* The renderer addresses points by position in the arrays it was handed, and a resident set that
|
|
45
|
+
* comes and goes reuses every position. So a vertex is the pair `(type_idx, dense_id)`, and
|
|
46
|
+
* everything that outlives one answer is held as one and re-resolved through the `Resident` that
|
|
47
|
+
* `useQueryLoop` rebuilds per answer. A host never builds its own: two maps of the same thing is
|
|
48
|
+
* how one of them ends up describing buffers that are no longer on screen.
|
|
49
|
+
*/
|
|
50
|
+
export { residentOf, vertexId, typeOf, denseOf, type Resident, type VertexId, } from './resident';
|
|
51
|
+
/**
|
|
52
|
+
* The bounded render path — a graph you never hold all of.
|
|
53
|
+
*
|
|
54
|
+
* A contract, not a format: anything that can answer "what is in this rectangle, at this zoom, in
|
|
55
|
+
* at most this many marks" is a source. A tile fetched by a computed address is one; a relation
|
|
56
|
+
* with `x`/`y` and a spatial predicate is another. This package renders and does not learn a storage layout,
|
|
57
|
+
* which is what removed the ceiling rather than raising it.
|
|
58
|
+
*
|
|
59
|
+
* `useQueryLoop` is the loop that asks — it observes the camera, debounces, cancels what the
|
|
60
|
+
* camera has already superseded, and pushes each answer into the renderer. `memorySource` is the
|
|
61
|
+
* answer for a host that already holds its arrays: every consumer needs a source now, including the
|
|
62
|
+
* ones bounding buys nothing for, so that one is written here once rather than at each call site
|
|
63
|
+
* differently.
|
|
64
|
+
*/
|
|
65
|
+
export { BOUNDED_DEFAULTS, isSuperseded, shouldSlice, SUPERSEDED, type BoundedSource, type Slice, type ExploringSource, type ExploreRequest, type SliceRequest, type Viewport, } from './bounded';
|
|
66
|
+
export { memorySource, type MemoryGraph } from './memory-source';
|
|
67
|
+
export { clusterRing } from './cluster-ring';
|
|
68
|
+
export { adaptive } from './adaptive';
|
|
69
|
+
export { resolveToken, toHex, type Rgba } from './css-color';
|
|
70
|
+
export { type GraphCommands, type Motion, type Selection, type SelectionSource, type Tool, } from './types';
|
|
71
|
+
export { DEFAULT_SIM, simFrom, type Sim } from './graph-sim';
|
|
72
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EACL,OAAO,EACP,OAAO,EACP,QAAQ,EACR,MAAM,EACN,UAAU,EACV,YAAY,EACZ,KAAK,OAAO,EACZ,KAAK,QAAQ,GACd,MAAM,eAAe,CAAC;AAIvB,OAAO,EACL,YAAY,EAIZ,QAAQ,EACR,KAAK,EACL,WAAW,EACX,WAAW,EACX,UAAU,EACV,KAAK,IAAI,EACT,KAAK,OAAO,GACb,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;;GAWG;AACH,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAc5F,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAIxC,OAAO,EACL,gBAAgB,EAChB,IAAI,EACJ,KAAK,mBAAmB,EACxB,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEtE;;;;;;;;GAQG;AACH,OAAO,EACL,UAAU,EACV,QAAQ,EACR,MAAM,EACN,OAAO,EACP,KAAK,QAAQ,EACb,KAAK,QAAQ,GACd,MAAM,YAAY,CAAC;AAwBpB;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,UAAU,EACV,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,QAAQ,GACd,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAQjE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAKtC,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,IAAI,GACV,MAAM,SAAS,CAAC;AAMjB,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { appearance as o, buffers as p, forces as t, isColour as f, neighboursOf as s, scaleOf as m } from "./graph-model.js";
|
|
2
|
+
import { DEFAULT_LOOK as a, SHAPE as E, SHAPE_ORDER as u, SHAPE_OTHER as S, SHAPE_PATH as i, lookFrom as O } from "./graph-looks.js";
|
|
3
|
+
import { GraphCanvas as l, GraphRootProvider as n, useGraphContext as A } from "./graph-canvas.js";
|
|
4
|
+
import { useGraph as c } from "./use-graph.js";
|
|
5
|
+
import { REHEAT as H } from "./use-renderer.js";
|
|
6
|
+
import { GRID as G, useGraphOverlays as P } from "./use-graph-overlays.js";
|
|
7
|
+
import { cursorChip as v, useGraphSelection as _ } from "./use-graph-selection.js";
|
|
8
|
+
import { denseOf as U, residentOf as C, typeOf as L, vertexId as y } from "./resident.js";
|
|
9
|
+
import { BOUNDED_DEFAULTS as b, SUPERSEDED as g, isSuperseded as k, shouldSlice as B } from "./bounded.js";
|
|
10
|
+
import { memorySource as M } from "./memory-source.js";
|
|
11
|
+
import { clusterRing as j } from "./cluster-ring.js";
|
|
12
|
+
import { adaptive as w } from "./adaptive.js";
|
|
13
|
+
import { resolveToken as J, toHex as Q } from "./css-color.js";
|
|
14
|
+
import { DEFAULT_SIM as W, simFrom as X } from "./graph-sim.js";
|
|
15
|
+
export {
|
|
16
|
+
b as BOUNDED_DEFAULTS,
|
|
17
|
+
a as DEFAULT_LOOK,
|
|
18
|
+
W as DEFAULT_SIM,
|
|
19
|
+
G as GRID,
|
|
20
|
+
l as GraphCanvas,
|
|
21
|
+
n as GraphRootProvider,
|
|
22
|
+
H as REHEAT,
|
|
23
|
+
E as SHAPE,
|
|
24
|
+
u as SHAPE_ORDER,
|
|
25
|
+
S as SHAPE_OTHER,
|
|
26
|
+
i as SHAPE_PATH,
|
|
27
|
+
g as SUPERSEDED,
|
|
28
|
+
w as adaptive,
|
|
29
|
+
o as appearance,
|
|
30
|
+
p as buffers,
|
|
31
|
+
j as clusterRing,
|
|
32
|
+
v as cursorChip,
|
|
33
|
+
U as denseOf,
|
|
34
|
+
t as forces,
|
|
35
|
+
f as isColour,
|
|
36
|
+
k as isSuperseded,
|
|
37
|
+
O as lookFrom,
|
|
38
|
+
M as memorySource,
|
|
39
|
+
s as neighboursOf,
|
|
40
|
+
C as residentOf,
|
|
41
|
+
J as resolveToken,
|
|
42
|
+
m as scaleOf,
|
|
43
|
+
B as shouldSlice,
|
|
44
|
+
X as simFrom,
|
|
45
|
+
Q as toHex,
|
|
46
|
+
L as typeOf,
|
|
47
|
+
c as useGraph,
|
|
48
|
+
A as useGraphContext,
|
|
49
|
+
P as useGraphOverlays,
|
|
50
|
+
_ as useGraphSelection,
|
|
51
|
+
y as vertexId
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ExploringSource } from './bounded';
|
|
2
|
+
/**
|
|
3
|
+
* The trivial source: a host that already holds its arrays.
|
|
4
|
+
*
|
|
5
|
+
* ADR-0001 deletes `load()`, which means every consumer needs a source — including the ones for
|
|
6
|
+
* which bounding buys nothing, because their graph fits. This is that source, and it exists so that
|
|
7
|
+
* "wrap your arrays" is one import rather than a hundred lines each call site writes differently.
|
|
8
|
+
*
|
|
9
|
+
* It is also the only source we ship that is an [`ExploringSource`]. A rectangle is a map question
|
|
10
|
+
* and any relation with a spatial predicate can answer it; a neighbourhood is the graph question,
|
|
11
|
+
* and answering it needs adjacency. Having the links in hand, this one does.
|
|
12
|
+
*
|
|
13
|
+
* Everything derived is built on first use and kept: a graph small enough to hold is small enough
|
|
14
|
+
* that an adjacency list is cheap, but a host that only ever pans should not pay to build one.
|
|
15
|
+
*/
|
|
16
|
+
export interface MemoryGraph {
|
|
17
|
+
/** Who each point is, parallel to the position pairs — `vertexId(type, dense)` per point. */
|
|
18
|
+
vertices: BigUint64Array;
|
|
19
|
+
/** `[x0, y0, x1, y1, …]`. */
|
|
20
|
+
positions: Float32Array;
|
|
21
|
+
/** `[src, dst, …]` as indices into `positions`. */
|
|
22
|
+
links: Float32Array;
|
|
23
|
+
/**
|
|
24
|
+
* The subject IRI of each vertex, parallel to `vertices` — optional, and the same opt-in the SQL
|
|
25
|
+
* source makes for the same reason: a host that never names a vertex should not carry the names.
|
|
26
|
+
*/
|
|
27
|
+
subjects?: string[];
|
|
28
|
+
categories?: Uint16Array;
|
|
29
|
+
sizes?: Float32Array;
|
|
30
|
+
}
|
|
31
|
+
export declare function memorySource(graph: MemoryGraph): ExploringSource;
|
|
32
|
+
//# sourceMappingURL=memory-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-source.d.ts","sourceRoot":"","sources":["../src/memory-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EAIrB,MAAM,WAAW,CAAC;AAGnB;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,WAAW;IAC1B,6FAA6F;IAC7F,QAAQ,EAAE,cAAc,CAAC;IACzB,6BAA6B;IAC7B,SAAS,EAAE,YAAY,CAAC;IACxB,mDAAmD;IACnD,KAAK,EAAE,YAAY,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,eAAe,CA2FhE"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { BOUNDED_DEFAULTS as U } from "./bounded.js";
|
|
2
|
+
function z(t) {
|
|
3
|
+
const u = t.positions.length / 2;
|
|
4
|
+
let x = null, d = null;
|
|
5
|
+
const I = (e) => {
|
|
6
|
+
if (!x) {
|
|
7
|
+
x = /* @__PURE__ */ new Map();
|
|
8
|
+
for (let i = 0; i < u; i++) x.set(t.vertices[i], i);
|
|
9
|
+
}
|
|
10
|
+
return x.get(e);
|
|
11
|
+
}, N = (e) => {
|
|
12
|
+
var i, r;
|
|
13
|
+
if (!d) {
|
|
14
|
+
d = Array.from({ length: u }, () => []);
|
|
15
|
+
for (let l = 0; l < t.links.length; l += 2) {
|
|
16
|
+
const f = t.links[l], o = t.links[l + 1];
|
|
17
|
+
(i = d[f]) == null || i.push(o), (r = d[o]) == null || r.push(f);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return d[e] ?? [];
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
total: () => Promise.resolve(u),
|
|
24
|
+
// One pass over the array it is already holding, so the canvas frames the arrays rather than the
|
|
25
|
+
// renderer's default box.
|
|
26
|
+
extent: () => Promise.resolve(_(t.positions)),
|
|
27
|
+
slice(e) {
|
|
28
|
+
const { limit: i, perPixel: r, pinned: l, view: f } = e;
|
|
29
|
+
return Promise.resolve(S(t, s(t, f, l), i, r));
|
|
30
|
+
},
|
|
31
|
+
// The only source we ship that has this at all: a rectangle needs a spatial predicate, which
|
|
32
|
+
// every source has, and a neighbourhood needs adjacency, which only a host holding its own links
|
|
33
|
+
// does. So the bounded canvas is an explorer here while a SQL source leaves it a map.
|
|
34
|
+
explore(e) {
|
|
35
|
+
return Promise.resolve(
|
|
36
|
+
S(t, m(e.seeds, e.depth), e.limit, e.perPixel)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
function m(e, i) {
|
|
41
|
+
const r = /* @__PURE__ */ new Set();
|
|
42
|
+
let l = [];
|
|
43
|
+
for (const f of e) {
|
|
44
|
+
const o = I(f);
|
|
45
|
+
o !== void 0 && !r.has(o) && (r.add(o), l.push(o));
|
|
46
|
+
}
|
|
47
|
+
for (let f = 0; f < i && l.length > 0; f++) {
|
|
48
|
+
const o = [];
|
|
49
|
+
for (const a of l)
|
|
50
|
+
for (const y of N(a))
|
|
51
|
+
r.has(y) || (r.add(y), o.push(y));
|
|
52
|
+
l = o;
|
|
53
|
+
}
|
|
54
|
+
return [...r];
|
|
55
|
+
}
|
|
56
|
+
function s(e, i, r) {
|
|
57
|
+
const l = [];
|
|
58
|
+
for (let o = 0; o < u; o++) {
|
|
59
|
+
const a = e.positions[o * 2], y = e.positions[o * 2 + 1];
|
|
60
|
+
a >= i.xMin && a <= i.xMax && y >= i.yMin && y <= i.yMax && l.push(o);
|
|
61
|
+
}
|
|
62
|
+
if (!(r != null && r.length)) return l;
|
|
63
|
+
const f = new Set(l);
|
|
64
|
+
for (const o of r) {
|
|
65
|
+
const a = I(o);
|
|
66
|
+
a !== void 0 && f.add(a);
|
|
67
|
+
}
|
|
68
|
+
return [...f];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function S(t, u, x, d) {
|
|
72
|
+
var F, T, E;
|
|
73
|
+
const I = u.length, N = Math.max(1, Math.ceil(I / x)), m = [];
|
|
74
|
+
for (let n = 0; n < u.length && m.length < x; n += N)
|
|
75
|
+
m.push(u[n]);
|
|
76
|
+
const s = m.length, e = new Int32Array(t.positions.length / 2).fill(-1), i = new BigUint64Array(s), r = new Float32Array(s * 2), l = new Uint16Array(s), f = t.sizes ? new Float32Array(s) : void 0, o = t.subjects ? new Array(s) : void 0;
|
|
77
|
+
for (let n = 0; n < s; n++) {
|
|
78
|
+
const c = m[n];
|
|
79
|
+
e[c] = n, i[n] = t.vertices[c], o && (o[n] = ((F = t.subjects) == null ? void 0 : F[c]) ?? ""), r[n * 2] = t.positions[c * 2], r[n * 2 + 1] = t.positions[c * 2 + 1], l[n] = ((T = t.categories) == null ? void 0 : T[c]) ?? 0, f && (f[n] = ((E = t.sizes) == null ? void 0 : E[c]) ?? 0);
|
|
80
|
+
}
|
|
81
|
+
const a = d !== void 0 && d > 0 ? U.minLinkPixels * d : 0, y = [], b = (n) => {
|
|
82
|
+
const c = e[n];
|
|
83
|
+
if (c >= 0) return c;
|
|
84
|
+
const w = s + y.length;
|
|
85
|
+
return e[n] = w, y.push(n), w;
|
|
86
|
+
}, h = [];
|
|
87
|
+
for (let n = 0; n < t.links.length; n += 2) {
|
|
88
|
+
const c = t.links[n], w = t.links[n + 1];
|
|
89
|
+
if (!(e[c] < 0 && e[w] < 0)) {
|
|
90
|
+
if (a > 0) {
|
|
91
|
+
const P = t.positions[c * 2] - t.positions[w * 2], O = t.positions[c * 2 + 1] - t.positions[w * 2 + 1];
|
|
92
|
+
if (P * P + O * O < a * a) continue;
|
|
93
|
+
}
|
|
94
|
+
h.push(b(c), b(w));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const A = s + y.length, M = new Float32Array(A * 2);
|
|
98
|
+
M.set(r);
|
|
99
|
+
const v = new BigUint64Array(A);
|
|
100
|
+
v.set(i);
|
|
101
|
+
const k = new Uint16Array(A);
|
|
102
|
+
k.set(l);
|
|
103
|
+
for (let n = 0; n < y.length; n++) {
|
|
104
|
+
const c = y[n];
|
|
105
|
+
M[(s + n) * 2] = t.positions[c * 2], M[(s + n) * 2 + 1] = t.positions[c * 2 + 1], v[s + n] = t.vertices[c], o && (o[s + n] = "");
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
n: I,
|
|
109
|
+
marks: s,
|
|
110
|
+
vertices: v,
|
|
111
|
+
subjects: o,
|
|
112
|
+
positions: M,
|
|
113
|
+
links: Float32Array.from(h),
|
|
114
|
+
categories: k,
|
|
115
|
+
sizes: f ? V(f, A) : void 0
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function V(t, u) {
|
|
119
|
+
if (t.length === u) return t;
|
|
120
|
+
const x = new Float32Array(u);
|
|
121
|
+
return x.set(t), x;
|
|
122
|
+
}
|
|
123
|
+
function _(t) {
|
|
124
|
+
let u = Number.POSITIVE_INFINITY, x = Number.POSITIVE_INFINITY, d = Number.NEGATIVE_INFINITY, I = Number.NEGATIVE_INFINITY;
|
|
125
|
+
for (let N = 0; N < t.length; N += 2) {
|
|
126
|
+
const m = t[N], s = t[N + 1];
|
|
127
|
+
m < u && (u = m), m > d && (d = m), s < x && (x = s), s > I && (I = s);
|
|
128
|
+
}
|
|
129
|
+
return Number.isFinite(u) ? { xMin: u, yMin: x, xMax: d, yMax: I } : { xMin: 0, yMin: 0, xMax: 0, yMax: 0 };
|
|
130
|
+
}
|
|
131
|
+
export {
|
|
132
|
+
z as memorySource
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=memory-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-source.js","sources":["../src/memory-source.ts"],"sourcesContent":["import {\n BOUNDED_DEFAULTS,\n type ExploreRequest,\n type ExploringSource,\n type Slice,\n type SliceRequest,\n type Viewport,\n} from \"./bounded\";\nimport type { VertexId } from \"./resident\";\n\n/**\n * The trivial source: a host that already holds its arrays.\n *\n * ADR-0001 deletes `load()`, which means every consumer needs a source — including the ones for\n * which bounding buys nothing, because their graph fits. This is that source, and it exists so that\n * \"wrap your arrays\" is one import rather than a hundred lines each call site writes differently.\n *\n * It is also the only source we ship that is an [`ExploringSource`]. A rectangle is a map question\n * and any relation with a spatial predicate can answer it; a neighbourhood is the graph question,\n * and answering it needs adjacency. Having the links in hand, this one does.\n *\n * Everything derived is built on first use and kept: a graph small enough to hold is small enough\n * that an adjacency list is cheap, but a host that only ever pans should not pay to build one.\n */\n\nexport interface MemoryGraph {\n /** Who each point is, parallel to the position pairs — `vertexId(type, dense)` per point. */\n vertices: BigUint64Array;\n /** `[x0, y0, x1, y1, …]`. */\n positions: Float32Array;\n /** `[src, dst, …]` as indices into `positions`. */\n links: Float32Array;\n /**\n * The subject IRI of each vertex, parallel to `vertices` — optional, and the same opt-in the SQL\n * source makes for the same reason: a host that never names a vertex should not carry the names.\n */\n subjects?: string[];\n categories?: Uint16Array;\n sizes?: Float32Array;\n}\n\nexport function memorySource(graph: MemoryGraph): ExploringSource {\n const count = graph.positions.length / 2;\n\n /** Built on demand — a host that only pans never asks for either of these. */\n let byVertex: Map<bigint, number> | null = null;\n let adjacency: number[][] | null = null;\n\n const indexOf = (vertex: VertexId): number | undefined => {\n if (!byVertex) {\n byVertex = new Map();\n for (let i = 0; i < count; i++) byVertex.set(graph.vertices[i] as bigint, i);\n }\n return byVertex.get(vertex);\n };\n\n const neighbours = (index: number): number[] => {\n if (!adjacency) {\n adjacency = Array.from({ length: count }, () => [] as number[]);\n for (let e = 0; e < graph.links.length; e += 2) {\n const src = graph.links[e] as number;\n const dst = graph.links[e + 1] as number;\n adjacency[src]?.push(dst);\n adjacency[dst]?.push(src);\n }\n }\n return adjacency[index] ?? [];\n };\n\n return {\n total: () => Promise.resolve(count),\n // One pass over the array it is already holding, so the canvas frames the arrays rather than the\n // renderer's default box.\n extent: () => Promise.resolve(boundsOf(graph.positions)),\n slice(request: SliceRequest): Promise<Slice> {\n const { limit, perPixel, pinned, view } = request;\n return Promise.resolve(gather(graph, inside(graph, view, pinned), limit, perPixel));\n },\n // The only source we ship that has this at all: a rectangle needs a spatial predicate, which\n // every source has, and a neighbourhood needs adjacency, which only a host holding its own links\n // does. So the bounded canvas is an explorer here while a SQL source leaves it a map.\n explore(request: ExploreRequest): Promise<Slice> {\n return Promise.resolve(\n gather(graph, expand(request.seeds, request.depth), request.limit, request.perPixel),\n );\n },\n };\n\n /**\n * Breadth-first from the seeds, `depth` hops out. Seeds are identities; everything after is a\n * corpus index, which is this source's own numbering and not a slice's.\n */\n function expand(seeds: VertexId[], depth: number): number[] {\n const seen = new Set<number>();\n let frontier: number[] = [];\n for (const vertex of seeds) {\n const index = indexOf(vertex);\n if (index !== undefined && !seen.has(index)) {\n seen.add(index);\n frontier.push(index);\n }\n }\n for (let hop = 0; hop < depth && frontier.length > 0; hop++) {\n const next: number[] = [];\n for (const index of frontier) {\n for (const other of neighbours(index)) {\n if (seen.has(other)) continue;\n seen.add(other);\n next.push(other);\n }\n }\n frontier = next;\n }\n return [...seen];\n }\n\n /** Everything the rectangle holds, plus whatever the reader is holding on to. */\n function inside(data: MemoryGraph, view: Viewport, pinned: VertexId[] | undefined): number[] {\n const hit: number[] = [];\n for (let i = 0; i < count; i++) {\n const x = data.positions[i * 2] as number;\n const y = data.positions[i * 2 + 1] as number;\n if (x >= view.xMin && x <= view.xMax && y >= view.yMin && y <= view.yMax) hit.push(i);\n }\n if (!pinned?.length) return hit;\n const held = new Set(hit);\n for (const vertex of pinned) {\n const index = indexOf(vertex);\n if (index !== undefined) held.add(index);\n }\n return [...held];\n }\n}\n\n/**\n * Chosen indices to a slice: renumber, copy the geometry, keep the links both of whose ends survived.\n *\n * `n` is what matched before `limit` cut it, which is the one honest thing a bounded view owes its\n * reader — a truncated answer must not look like a complete one.\n *\n * **Over the limit it strides rather than taking the front**, which is the same rule the SQL sources\n * apply and for the same reason: the front of an ordering is a *region* of whatever that ordering\n * follows, so `chosen.slice(0, limit)` drew one corner of a window and called it the window.\n *\n * What this source cannot promise, and the SQL ones can: that the stride is **spatially**\n * stratified. A corpus numbers along the Morton curve so every `s`-th id is spread over the space;\n * here the order is whichever order the host built its arrays in, and nothing knows what that is. A\n * stride over an unknown order is at worst an arbitrary sample, where a prefix of an unknown order\n * is at worst an arbitrary *contiguous* sample — so this is never the worse of the two and is\n * sometimes much better.\n */\nfunction gather(\n graph: MemoryGraph,\n chosen: number[],\n limit: number,\n perPixel: number | undefined,\n): Slice {\n const matched = chosen.length;\n const stride = Math.max(1, Math.ceil(matched / limit));\n const kept: number[] = [];\n for (let i = 0; i < chosen.length && kept.length < limit; i += stride) {\n kept.push(chosen[i] as number);\n }\n const n = kept.length;\n\n // Global index → position in this slice, `-1` for everything not in it. One pass over the corpus\n // rather than a Map: the arrays are already dense and an Int32Array of N is cheaper than N boxed\n // entries, which is the whole argument of this branch in miniature.\n const local = new Int32Array(graph.positions.length / 2).fill(-1);\n const vertices = new BigUint64Array(n);\n const positions = new Float32Array(n * 2);\n const categories = new Uint16Array(n);\n const sizes = graph.sizes ? new Float32Array(n) : undefined;\n const subjects = graph.subjects ? new Array<string>(n) : undefined;\n for (let i = 0; i < n; i++) {\n const from = kept[i] as number;\n local[from] = i;\n vertices[i] = graph.vertices[from] as bigint;\n if (subjects) subjects[i] = graph.subjects?.[from] ?? \"\";\n positions[i * 2] = graph.positions[from * 2] as number;\n positions[i * 2 + 1] = graph.positions[from * 2 + 1] as number;\n categories[i] = graph.categories?.[from] ?? 0;\n if (sizes) sizes[i] = graph.sizes?.[from] ?? 0;\n }\n\n /**\n * The edges, and the far ends they need — **which this source has, because it holds everything.**\n *\n * A window that fits loses nothing here; a window over the limit is a sample, and an edge from a\n * sampled vertex to one the stride passed over used to be dropped. It is not a missing fact: the\n * arrays are in hand. So the far end is appended as an **anchor** — a point at its real\n * coordinates, past `marks`, never drawn. The SQL source reaches the same answer out of the tiles\n * it fetched; this one reaches it out of the arrays it was handed.\n *\n * `perPixel` given, an edge shorter than `minLinkPixels` on screen is not sent at all — it is a\n * dot on top of two dots the point layer has already drawn.\n */\n const floor = perPixel !== undefined && perPixel > 0 ? BOUNDED_DEFAULTS.minLinkPixels * perPixel : 0;\n const anchors: number[] = [];\n const anchorOf = (from: number): number => {\n const seen = local[from] as number;\n if (seen >= 0) return seen;\n const at = n + anchors.length;\n local[from] = at;\n anchors.push(from);\n return at;\n };\n\n const links: number[] = [];\n for (let e = 0; e < graph.links.length; e += 2) {\n const a = graph.links[e] as number;\n const b = graph.links[e + 1] as number;\n // At least one end drawn. Neither drawn is an edge somewhere else entirely, and drawing it would\n // put ink outside the window the caller asked about.\n if ((local[a] as number) < 0 && (local[b] as number) < 0) continue;\n if (floor > 0) {\n const dx = (graph.positions[a * 2] as number) - (graph.positions[b * 2] as number);\n const dy = (graph.positions[a * 2 + 1] as number) - (graph.positions[b * 2 + 1] as number);\n if (dx * dx + dy * dy < floor * floor) continue;\n }\n links.push(anchorOf(a), anchorOf(b));\n }\n\n const all = n + anchors.length;\n const whole = new Float32Array(all * 2);\n whole.set(positions);\n const who = new BigUint64Array(all);\n who.set(vertices);\n const ordinals = new Uint16Array(all);\n ordinals.set(categories);\n for (let i = 0; i < anchors.length; i++) {\n const from = anchors[i] as number;\n whole[(n + i) * 2] = graph.positions[from * 2] as number;\n whole[(n + i) * 2 + 1] = graph.positions[from * 2 + 1] as number;\n who[n + i] = graph.vertices[from] as bigint;\n // No category and no size: an anchor is not drawn, and giving it either would put it in the\n // colour scale's domain and the size ramp's range for a point nobody can see.\n if (subjects) subjects[n + i] = \"\";\n }\n\n return {\n n: matched,\n marks: n,\n vertices: who,\n subjects,\n positions: whole,\n links: Float32Array.from(links),\n categories: ordinals,\n sizes: sizes ? growTo(sizes, all) : undefined,\n };\n}\n\n/** A ramp column widened to cover the anchors, which contribute nothing to it. */\nfunction growTo(values: Float32Array, length: number): Float32Array {\n if (values.length === length) return values;\n const wider = new Float32Array(length);\n wider.set(values);\n return wider;\n}\n\n/**\n * The rectangle a set of interleaved positions occupies.\n *\n * Empty arrays answer a degenerate rectangle at the origin rather than `±Infinity`: a view framed on\n * nothing should sit somewhere, and the infinities would make the fit arithmetic produce `NaN`.\n */\nfunction boundsOf(positions: Float32Array): Viewport {\n let xMin = Number.POSITIVE_INFINITY;\n let yMin = Number.POSITIVE_INFINITY;\n let xMax = Number.NEGATIVE_INFINITY;\n let yMax = Number.NEGATIVE_INFINITY;\n for (let i = 0; i < positions.length; i += 2) {\n const x = positions[i] as number;\n const y = positions[i + 1] as number;\n if (x < xMin) xMin = x;\n if (x > xMax) xMax = x;\n if (y < yMin) yMin = y;\n if (y > yMax) yMax = y;\n }\n if (!Number.isFinite(xMin)) return { xMin: 0, yMin: 0, xMax: 0, yMax: 0 };\n return { xMin, yMin, xMax, yMax };\n}\n"],"names":["memorySource","graph","count","byVertex","adjacency","indexOf","vertex","neighbours","index","e","src","dst","_a","_b","boundsOf","request","limit","perPixel","pinned","view","gather","inside","expand","seeds","depth","seen","frontier","hop","next","other","data","hit","i","x","held","chosen","matched","stride","kept","n","local","vertices","positions","categories","sizes","subjects","from","_c","floor","BOUNDED_DEFAULTS","anchors","anchorOf","at","links","a","b","dx","dy","all","whole","who","ordinals","growTo","values","length","wider","xMin","yMin","xMax","yMax","y"],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Channels } from './graph-model';
|
|
2
|
+
import { SHAPE, Look } from './graph-looks';
|
|
3
|
+
/**
|
|
4
|
+
* What the graph's geometry owes, as a report rather than as prose.
|
|
5
|
+
*
|
|
6
|
+
* This is the retired derivation's `OBLIGATIONS` one section along, and the shape is deliberately the
|
|
7
|
+
* same family: a stable `id` a caller can branch on, and a `reason` that is one line a panel can
|
|
8
|
+
* show. What is added is the part that makes an obligation checkable instead of quotable — the
|
|
9
|
+
* number, the bar, and what the number was measured against.
|
|
10
|
+
*
|
|
11
|
+
* **Why this file exists.** The colour section publishes twenty-one obligations and the graph
|
|
12
|
+
* section published none, while having four that were perfectly real and living in comments. That
|
|
13
|
+
* asymmetry made "are these two the same kind of thing?" an argument. With both sections reporting,
|
|
14
|
+
* it is an observation.
|
|
15
|
+
*
|
|
16
|
+
* **What `OBLIGATIONS` does not do.** It grades constants of ours — the shape scale and the three
|
|
17
|
+
* shipped forms — so it cannot fail at runtime for a consumer; it is the section's own claim about
|
|
18
|
+
* itself, checked in CI. `gradeComposition` is the half that *can* fail for a consumer, because
|
|
19
|
+
* what it grades is what the caller composed.
|
|
20
|
+
*/
|
|
21
|
+
export interface Obligation {
|
|
22
|
+
/** Stable key, so a caller branches on the failure rather than parsing prose. */
|
|
23
|
+
id: string;
|
|
24
|
+
/** What the number is a property of. */
|
|
25
|
+
subject: string;
|
|
26
|
+
/** The measured value, in `unit`. `null` means the obligation is real and not yet gradeable. */
|
|
27
|
+
measured: number | null;
|
|
28
|
+
/** The bar `measured` must clear, in `unit`. `null` where `measured` is. */
|
|
29
|
+
threshold: number | null;
|
|
30
|
+
/** How `measured` must stand to `threshold`. */
|
|
31
|
+
holds: ">=" | "<=" | "<";
|
|
32
|
+
unit: string;
|
|
33
|
+
/** What the bar comes from — an external system, a standard, or a measurement of ours. */
|
|
34
|
+
against: string;
|
|
35
|
+
/** The rule in one line. */
|
|
36
|
+
reason: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Grade what a host actually composed: this form, with these bindings.
|
|
40
|
+
*
|
|
41
|
+
* `null` when `symbol` is not bound, which is not a pass — there is no obligation to grade, because
|
|
42
|
+
* a point wearing one glyph has no second channel to protect.
|
|
43
|
+
*/
|
|
44
|
+
export declare function gradeComposition(look: Look, channels: Channels): Check | null;
|
|
45
|
+
export declare const OBLIGATIONS: readonly Obligation[];
|
|
46
|
+
/** An obligation that can be graded, and whether it holds. */
|
|
47
|
+
export interface Check extends Obligation {
|
|
48
|
+
measured: number;
|
|
49
|
+
threshold: number;
|
|
50
|
+
ok: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Grade every obligation that carries a number.
|
|
54
|
+
*
|
|
55
|
+
* Ungradeable rows are skipped rather than defaulted to passing: a bar nobody has established
|
|
56
|
+
* cannot be cleared, and reporting one as green is how a guard comes to test a corpus of zero.
|
|
57
|
+
* `OBLIGATIONS.length - check().length` is the number of open questions this section has.
|
|
58
|
+
*/
|
|
59
|
+
export declare function check(): Check[];
|
|
60
|
+
/** `SHAPE.cross` is the past-capacity glyph and `SHAPE` is re-exported so a caller can name it. */
|
|
61
|
+
export { SHAPE };
|
|
62
|
+
//# sourceMappingURL=obligations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"obligations.d.ts","sourceRoot":"","sources":["../src/obligations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAY,KAAK,EAA4B,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAErF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,UAAU;IACzB,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gDAAgD;IAChD,KAAK,EAAE,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;CAChB;AAgDD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,GAAG,KAAK,GAAG,IAAI,CAK7E;AAED,eAAO,MAAM,WAAW,EAAE,SAAS,UAAU,EAiE5C,CAAC;AAEF,8DAA8D;AAC9D,MAAM,WAAW,KAAM,SAAQ,UAAU;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,OAAO,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,IAAI,KAAK,EAAE,CAa/B;AAED,mGAAmG;AACnG,OAAO,EAAE,KAAK,EAAE,CAAC"}
|