@graview/layout 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.
Files changed (47) hide show
  1. package/LICENSE +96 -0
  2. package/README.md +20 -0
  3. package/dist/band.d.ts +103 -0
  4. package/dist/band.d.ts.map +1 -0
  5. package/dist/band.js +344 -0
  6. package/dist/band.js.map +1 -0
  7. package/dist/city.d.ts +161 -0
  8. package/dist/city.d.ts.map +1 -0
  9. package/dist/city.js +319 -0
  10. package/dist/city.js.map +1 -0
  11. package/dist/ids.d.ts +79 -0
  12. package/dist/ids.d.ts.map +1 -0
  13. package/dist/ids.js +115 -0
  14. package/dist/ids.js.map +1 -0
  15. package/dist/index.d.ts +19 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +10 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/interpolate.d.ts +27 -0
  20. package/dist/interpolate.d.ts.map +1 -0
  21. package/dist/interpolate.js +217 -0
  22. package/dist/interpolate.js.map +1 -0
  23. package/dist/label-fit.d.ts +115 -0
  24. package/dist/label-fit.d.ts.map +1 -0
  25. package/dist/label-fit.js +162 -0
  26. package/dist/label-fit.js.map +1 -0
  27. package/dist/layout.d.ts +99 -0
  28. package/dist/layout.d.ts.map +1 -0
  29. package/dist/layout.js +1391 -0
  30. package/dist/layout.js.map +1 -0
  31. package/dist/rank.d.ts +35 -0
  32. package/dist/rank.d.ts.map +1 -0
  33. package/dist/rank.js +84 -0
  34. package/dist/rank.js.map +1 -0
  35. package/dist/related.d.ts +42 -0
  36. package/dist/related.d.ts.map +1 -0
  37. package/dist/related.js +207 -0
  38. package/dist/related.js.map +1 -0
  39. package/dist/types.d.ts +404 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +27 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/view-state.d.ts +189 -0
  44. package/dist/view-state.d.ts.map +1 -0
  45. package/dist/view-state.js +350 -0
  46. package/dist/view-state.js.map +1 -0
  47. package/package.json +54 -0
@@ -0,0 +1,162 @@
1
+ /**
2
+ * A NAME THAT FITS THE THING IT NAMES.
3
+ *
4
+ * Every drawn surface in this framework eventually has to write a word
5
+ * inside a shape — a district, a region on a plan, a band on a calendar —
6
+ * and every one of them starts by centring the text at one size and hoping.
7
+ * That works for as long as the names are short, which is for as long as a
8
+ * person is typing them.
9
+ *
10
+ * A product asked a model to survey a garden and got seven areas back
11
+ * called things like "Pea-gravel corner with river-rock border, log seats
12
+ * and a fire bowl". The map drew each at one size across the middle of its
13
+ * shape: three ran off the canvas, two were struck through by a
14
+ * neighbour's outline, and nothing failed — every test passed, the
15
+ * accessibility tree was perfect, and the picture said "rick patio with
16
+ * gravel joints". Agent-authored labels are not an edge case; they are the
17
+ * normal case for anything with an intelligence provider in it.
18
+ *
19
+ * So this is the arithmetic, apart from any drawing, where it can be
20
+ * tested: how much room a shape actually has, and what of a name will fit
21
+ * in it. Nothing here knows what a zone is.
22
+ */
23
+ /**
24
+ * How wide a shape is AT A HEIGHT — not its bounding box.
25
+ *
26
+ * The shapes that overflow worst are the long thin ones, whose box is
27
+ * generous exactly where the shape is not. So this is a scanline: cross the
28
+ * polygon with a horizontal line and take the widest run that is genuinely
29
+ * inside it. For an L, a crescent or an hourglass waist that is much less
30
+ * than the box, which is the point.
31
+ */
32
+ export function spanAt(outline, y) {
33
+ const crossings = [];
34
+ for (let i = 0; i < outline.length; i += 1) {
35
+ const a = outline[i];
36
+ const b = outline[(i + 1) % outline.length];
37
+ if (a.y === b.y)
38
+ continue;
39
+ const low = Math.min(a.y, b.y);
40
+ const high = Math.max(a.y, b.y);
41
+ if (y < low || y >= high)
42
+ continue;
43
+ crossings.push(a.x + ((y - a.y) / (b.y - a.y)) * (b.x - a.x));
44
+ }
45
+ crossings.sort((p, q) => p - q);
46
+ let best = { x0: 0, x1: 0 };
47
+ /* Pairs, not neighbours: odd gaps are inside the shape, even ones are not. */
48
+ for (let i = 0; i + 1 < crossings.length; i += 2) {
49
+ const run = { x0: crossings[i], x1: crossings[i + 1] };
50
+ if (run.x1 - run.x0 > best.x1 - best.x0)
51
+ best = run;
52
+ }
53
+ return best;
54
+ }
55
+ /** The corner-to-corner extent, for the vertical room a label has. */
56
+ export function boxOf(outline) {
57
+ let top = Infinity;
58
+ let bottom = -Infinity;
59
+ for (const point of outline) {
60
+ if (point.y < top)
61
+ top = point.y;
62
+ if (point.y > bottom)
63
+ bottom = point.y;
64
+ }
65
+ return { top, bottom };
66
+ }
67
+ /** How much a shape covers, by the shoelace. For ordering, not for display. */
68
+ export function areaOf(outline) {
69
+ let twice = 0;
70
+ for (let i = 0; i < outline.length; i += 1) {
71
+ const a = outline[i];
72
+ const b = outline[(i + 1) % outline.length];
73
+ twice += a.x * b.y - b.x * a.y;
74
+ }
75
+ return Math.abs(twice) / 2;
76
+ }
77
+ /**
78
+ * The area-weighted centroid, which is NOT the mean of the corners.
79
+ *
80
+ * An L-shaped region with six corners has four of them bunched at one end,
81
+ * and averaging drags the label off the shape entirely — onto the
82
+ * neighbour, where it reads as that neighbour's name. The fallback keeps a
83
+ * degenerate shape from producing a NaN that silently removes the label.
84
+ */
85
+ export function centroidOf(outline) {
86
+ let twiceArea = 0;
87
+ let x = 0;
88
+ let y = 0;
89
+ for (let i = 0; i < outline.length; i += 1) {
90
+ const a = outline[i];
91
+ const b = outline[(i + 1) % outline.length];
92
+ const cross = a.x * b.y - b.x * a.y;
93
+ twiceArea += cross;
94
+ x += (a.x + b.x) * cross;
95
+ y += (a.y + b.y) * cross;
96
+ }
97
+ if (Math.abs(twiceArea) < 1e-9) {
98
+ const mean = outline.reduce((acc, p) => ({ x: acc.x + p.x, y: acc.y + p.y }), { x: 0, y: 0 });
99
+ return { x: mean.x / outline.length, y: mean.y / outline.length };
100
+ }
101
+ return { x: x / (3 * twiceArea), y: y / (3 * twiceArea) };
102
+ }
103
+ const ADVANCE = 0.58;
104
+ export const estimateWidth = (text, fontSize) => text.length * fontSize * ADVANCE;
105
+ /**
106
+ * A name that fits, or no name at all.
107
+ *
108
+ * In order: the whole thing on one line; the whole thing broken at the space
109
+ * nearest the middle, which is what a person lettering a plan does; the same
110
+ * shrunk, down to the floor; and then cut, with an ellipsis, so the shape is
111
+ * at least identified.
112
+ *
113
+ * A shape too small for four characters gets NOTHING. A two-letter stub over
114
+ * a flower bed is worse than a clean shape, because it reads as somebody
115
+ * else's label clipped — and `whole: false` is how the caller knows to say
116
+ * the name somewhere a person can still read it.
117
+ */
118
+ export function fitLabel(text, options) {
119
+ const { room, height, size: base, floor, measure = estimateWidth, lines: most = 2 } = options;
120
+ const fits = (lines, size) => lines.every((line) => measure(line, size) <= room) && lines.length * size * 1.15 <= height;
121
+ if (fits([text], base))
122
+ return { lines: [text], fontSize: base, whole: true };
123
+ const words = text.split(/\s+/);
124
+ if (most > 1 && words.length > 1) {
125
+ /* The break nearest the middle by CHARACTER, not by word count: "The
126
+ long south-facing bed" breaks after "long", not after "south-facing". */
127
+ let at = 1;
128
+ let bestGap = Infinity;
129
+ let run = 0;
130
+ for (let i = 0; i < words.length - 1; i += 1) {
131
+ run += words[i].length + 1;
132
+ const gap = Math.abs(run - text.length / 2);
133
+ if (gap < bestGap) {
134
+ bestGap = gap;
135
+ at = i + 1;
136
+ }
137
+ }
138
+ const two = [words.slice(0, at).join(" "), words.slice(at).join(" ")];
139
+ if (fits(two, base))
140
+ return { lines: two, fontSize: base, whole: true };
141
+ for (let size = base; size >= floor; size -= base * 0.06) {
142
+ if (fits(two, size))
143
+ return { lines: two, fontSize: size, whole: true };
144
+ }
145
+ }
146
+ for (let size = base; size >= floor; size -= base * 0.06) {
147
+ if (fits([text], size))
148
+ return { lines: [text], fontSize: size, whole: true };
149
+ }
150
+ /* Cut, at the floor, on a word boundary where there is one. */
151
+ const perCharacter = measure(text, floor) / Math.max(1, text.length);
152
+ const fitsAt = Math.floor(room / perCharacter) - 1;
153
+ if (fitsAt < 4 || floor * 1.15 > height)
154
+ return null;
155
+ const cut = text.slice(0, fitsAt);
156
+ const space = cut.lastIndexOf(" ");
157
+ const kept = space > 3 ? cut.slice(0, space) : cut;
158
+ return { lines: [`${kept}…`], fontSize: floor, whole: false };
159
+ }
160
+ /** Whether two boxes touch. The whole of collision avoidance, in one line. */
161
+ export const overlaps = (a, b) => a.x0 < b.x1 && b.x0 < a.x1 && a.y0 < b.y1 && b.y0 < a.y1;
162
+ //# sourceMappingURL=label-fit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label-fit.js","sourceRoot":"","sources":["../src/label-fit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import { type AnySchema, type GraphReader, type NodeOfSchema } from "@graview/core";
2
+ import { type Layout, type LayoutOptions, type Plane } from "./types.js";
3
+ import type { ViewState } from "./view-state.js";
4
+ /**
5
+ * A billboard cut to its picture is never shorter than this, in the lens's
6
+ * own pixels. An empty lens — a header over no rows yet — measured to a
7
+ * strip one line tall, which read as a label, not a screen; at this height
8
+ * the room under the header is visibly an empty screen.
9
+ */
10
+ /**
11
+ * HOW FAR A BILLBOARD MAY BE MOVED FROM ITS OWN PLOT, in city cells.
12
+ *
13
+ * Cells rather than pixels so the same pin holds at every zoom: a board
14
+ * nudged two cells north is two cells north whether the city is flown close
15
+ * or seen whole. Two is about a village's width — enough to shift a board
16
+ * off whatever it was covering, not enough for it to read as a picture of
17
+ * somewhere else.
18
+ */
19
+ export declare const SCREEN_LEASH_CELLS = 2;
20
+ export declare const THUMB_ONE: {
21
+ width: number;
22
+ height: number;
23
+ };
24
+ export declare const THUMB_TWO: {
25
+ width: number;
26
+ height: number;
27
+ };
28
+ export declare function marqueeHeightFor(titles: readonly string[], cardWidth: number): number;
29
+ /**
30
+ * Where everything sits, as a pure function of (focus, relation, graph),
31
+ * overlaid with user pins.
32
+ *
33
+ * Same graph and same view always produce the same picture — that is what
34
+ * lets spatial memory survive, and what lets any two states be interpolated
35
+ * into an animated transition.
36
+ */
37
+ /**
38
+ * AN OPENED DISTRICT'S ROSTER, in arithmetic both sides share.
39
+ *
40
+ * The layout reserved 96 pixels under an opened district's nameplate and
41
+ * the view listed sixteen members in 250 — so a dealership's Vehicles,
42
+ * opened at the bottom of the city, ran its list off the scene and over the
43
+ * district beside it. The layout now reserves the rows it will be asked to
44
+ * hold, the view lists the rows it was given (`openedRows`), and the rest
45
+ * are counted.
46
+ */
47
+ export declare const ROSTER_ROW = 27;
48
+ /** The most rows a roster is given: past it the district is gone into. */
49
+ export declare const ROSTER_MOST = 8;
50
+ export declare const rosterRows: (count: number) => number;
51
+ export declare const rosterHeight: (rows: number) => number;
52
+ export declare function layout<S extends AnySchema>(graph: GraphReader<NodeOfSchema<S>>, schema: S, state: ViewState, options?: LayoutOptions): Layout;
53
+ /** Which plane a node ended up on, or null if it is not in the layout. */
54
+ export declare function planeOf(result: Layout, id: string): Plane | null;
55
+ /**
56
+ * THE PAN, APPLIED AFTER THE FACT — the same answer, without the arithmetic.
57
+ *
58
+ * The pan is baked into every card's coordinates at layout time, and that is
59
+ * right: it is what lets two layouts be interpolated into motion, and it is
60
+ * why connectors, the frame planner and both renderers get panning without
61
+ * any of them learning about it.
62
+ *
63
+ * But it is applied at the END of a function that has already decided
64
+ * everything else — communities, plots, band packing, the drive-in's own
65
+ * sizing loop — and a drag changes nothing but the pan. Running all of that
66
+ * again per pointer move cost rota's city 169 dropped frames and ten
67
+ * seconds of blocked main thread in a two-second drag.
68
+ *
69
+ * So the pan comes out: lay the world out once at rest, and translate it.
70
+ * The result is the same object `layout` would have returned — held to that
71
+ * by `the-pan-is-a-translation` — and the caller can hold the expensive half
72
+ * still while a hand is moving.
73
+ *
74
+ * The city's own origin and extent are unpanned by definition (the ground
75
+ * rides `city.pan`), so only the cards, the lines between them and the
76
+ * recorded pan move.
77
+ */
78
+ /**
79
+ * A CARD UNDER THE HAND, moved without laying the world out again.
80
+ *
81
+ * A pin only overrides where one card is; everything else about the stop —
82
+ * the band's grouping, the bundles, the districts — is what it was. Laying
83
+ * it all out again on every pointer move put a full `layout()` and a full
84
+ * bundle of every edge in every frame of a drag (docs/scale.md). This moves
85
+ * the held card to the pin (unpanned, as a pin is stored) and re-aims the
86
+ * lines that touch it. `null` when it cannot say the same thing a full
87
+ * layout would — a drive-in's screen, whose pin places it differently —
88
+ * and the caller lays it out with the pin instead. On release the pin goes
89
+ * into the view and the full layout runs once.
90
+ */
91
+ export declare function holdLayout(placed: Layout, id: string, at: {
92
+ x: number;
93
+ y: number;
94
+ }): Layout | null;
95
+ export declare function panLayout(placed: Layout, pan: {
96
+ x: number;
97
+ y: number;
98
+ }): Layout;
99
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AACtG,OAAO,EAGL,KAAK,MAAM,EAEX,KAAK,aAAa,EAClB,KAAK,KAAK,EACX,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAsCjD;;;;;GAKG;AACH;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAyBpC,eAAO,MAAM,SAAS;;;CAA6B,CAAC;AACpD,eAAO,MAAM,SAAS;;;CAA4B,CAAC;AAGnD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAMrF;AA6CD;;;;;;;GAOG;AACH;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,0EAA0E;AAC1E,eAAO,MAAM,WAAW,IAAI,CAAC;AAK7B,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,KAAG,MAAmD,CAAC;AAC/F,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAA2C,CAAC;AAExF,wBAAgB,MAAM,CAAC,CAAC,SAAS,SAAS,EACxC,KAAK,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EACnC,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE,aAAkB,GAC1B,MAAM,CA+uCR;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAEhE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,IAAI,CAiBlG;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAc/E"}