@graview/react 0.1.27 → 0.1.29

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 (106) hide show
  1. package/dist/affordances.d.ts +2 -1
  2. package/dist/affordances.d.ts.map +1 -1
  3. package/dist/affordances.js +2 -1
  4. package/dist/affordances.js.map +1 -1
  5. package/dist/beside-the-map.d.ts +13 -0
  6. package/dist/beside-the-map.d.ts.map +1 -0
  7. package/dist/beside-the-map.js +14 -0
  8. package/dist/beside-the-map.js.map +1 -0
  9. package/dist/channels.d.ts +2 -1
  10. package/dist/channels.d.ts.map +1 -1
  11. package/dist/channels.js +121 -22
  12. package/dist/channels.js.map +1 -1
  13. package/dist/connectors.d.ts +27 -2
  14. package/dist/connectors.d.ts.map +1 -1
  15. package/dist/connectors.js +135 -21
  16. package/dist/connectors.js.map +1 -1
  17. package/dist/context.d.ts +38 -2
  18. package/dist/context.d.ts.map +1 -1
  19. package/dist/context.js +63 -14
  20. package/dist/context.js.map +1 -1
  21. package/dist/drawn.d.ts +1 -1
  22. package/dist/drawn.d.ts.map +1 -1
  23. package/dist/drawn.js +1 -1
  24. package/dist/drawn.js.map +1 -1
  25. package/dist/edge-signs.d.ts +1 -1
  26. package/dist/edge-signs.d.ts.map +1 -1
  27. package/dist/edge-signs.js +52 -7
  28. package/dist/edge-signs.js.map +1 -1
  29. package/dist/index.d.ts +6 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +5 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/last-visit.d.ts +18 -0
  34. package/dist/last-visit.d.ts.map +1 -0
  35. package/dist/last-visit.js +79 -0
  36. package/dist/last-visit.js.map +1 -0
  37. package/dist/map-choice.d.ts +55 -0
  38. package/dist/map-choice.d.ts.map +1 -0
  39. package/dist/map-choice.js +73 -0
  40. package/dist/map-choice.js.map +1 -0
  41. package/dist/plots.d.ts +30 -24
  42. package/dist/plots.d.ts.map +1 -1
  43. package/dist/plots.js +168 -149
  44. package/dist/plots.js.map +1 -1
  45. package/dist/provider.d.ts +2 -1
  46. package/dist/provider.d.ts.map +1 -1
  47. package/dist/provider.js +3 -1
  48. package/dist/provider.js.map +1 -1
  49. package/dist/record-links-layer.d.ts +80 -0
  50. package/dist/record-links-layer.d.ts.map +1 -0
  51. package/dist/record-links-layer.js +127 -0
  52. package/dist/record-links-layer.js.map +1 -0
  53. package/dist/record-links.d.ts +58 -0
  54. package/dist/record-links.d.ts.map +1 -0
  55. package/dist/record-links.js +72 -0
  56. package/dist/record-links.js.map +1 -0
  57. package/dist/resolved-view.d.ts.map +1 -1
  58. package/dist/resolved-view.js +4 -0
  59. package/dist/resolved-view.js.map +1 -1
  60. package/dist/ribbon-width.d.ts +8 -0
  61. package/dist/ribbon-width.d.ts.map +1 -0
  62. package/dist/ribbon-width.js +15 -0
  63. package/dist/ribbon-width.js.map +1 -0
  64. package/dist/ribbons-layer.d.ts +56 -0
  65. package/dist/ribbons-layer.d.ts.map +1 -0
  66. package/dist/ribbons-layer.js +167 -0
  67. package/dist/ribbons-layer.js.map +1 -0
  68. package/dist/ribbons.d.ts +70 -0
  69. package/dist/ribbons.d.ts.map +1 -0
  70. package/dist/ribbons.js +400 -0
  71. package/dist/ribbons.js.map +1 -0
  72. package/dist/scene-camera.d.ts.map +1 -1
  73. package/dist/scene-camera.js +10 -4
  74. package/dist/scene-camera.js.map +1 -1
  75. package/dist/scene-covered.d.ts +8 -0
  76. package/dist/scene-covered.d.ts.map +1 -0
  77. package/dist/scene-covered.js +118 -0
  78. package/dist/scene-covered.js.map +1 -0
  79. package/dist/scene-hand.d.ts +15 -0
  80. package/dist/scene-hand.d.ts.map +1 -1
  81. package/dist/scene-hand.js +45 -1
  82. package/dist/scene-hand.js.map +1 -1
  83. package/dist/scene-lines.d.ts +15 -2
  84. package/dist/scene-lines.d.ts.map +1 -1
  85. package/dist/scene-lines.js +215 -85
  86. package/dist/scene-lines.js.map +1 -1
  87. package/dist/scene-options.d.ts +7 -2
  88. package/dist/scene-options.d.ts.map +1 -1
  89. package/dist/scene-options.js +22 -8
  90. package/dist/scene-options.js.map +1 -1
  91. package/dist/scene-root.d.ts +1 -0
  92. package/dist/scene-root.d.ts.map +1 -1
  93. package/dist/scene-root.js +197 -154
  94. package/dist/scene-root.js.map +1 -1
  95. package/dist/view-host.d.ts.map +1 -1
  96. package/dist/view-host.js +6 -1
  97. package/dist/view-host.js.map +1 -1
  98. package/dist/view-registry.d.ts +5 -0
  99. package/dist/view-registry.d.ts.map +1 -1
  100. package/dist/view-registry.js +2 -6
  101. package/dist/view-registry.js.map +1 -1
  102. package/dist/where-drawn.d.ts +3 -1
  103. package/dist/where-drawn.d.ts.map +1 -1
  104. package/dist/where-drawn.js +10 -16
  105. package/dist/where-drawn.js.map +1 -1
  106. package/package.json +5 -5
@@ -0,0 +1,167 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
+ import { districtOf, mapChoice, ribbonsOf, ribbonSentence } from "./map-choice.js";
4
+ import { ribbonWidth } from "./ribbon-width.js";
5
+ import { loopPoints, midpointOf, onCanvas, pathOf, routeOnTheGround } from "./ribbons.js";
6
+ import "./provider.js"; // A door: tied to what the page drew first (TIED DOORS, packages/primitives/src/frame.ts).
7
+ export function Ribbons({ frame, width, height, pan, store, graphVersion: nodes, selection, view, emphasis, hiddenKinds, drawn: kitDraws }) {
8
+ const [pointed, setPointed] = useState(null);
9
+ const city = frame.city;
10
+ const kindOf = (id) => store.graph.getNode(id)?.kind;
11
+ const ribbons = useMemo(() => ribbonsOf(store.graph.allEdges(), (id) => store.graph.getNode(id)?.kind), [store, nodes]);
12
+ const plots = new Map();
13
+ for (const node of frame.nodes) {
14
+ const kind = node.id.startsWith("kind:") ? districtOf(node.id) : null;
15
+ if (kind !== null && node.plot && Math.round(node.plane) === 2)
16
+ plots.set(kind, node.plot);
17
+ }
18
+ const plotsKey = [...plots].map(([kind, plot]) => `${kind}:${plot.col},${plot.row},${plot.side}`).join(";");
19
+ const cell = city?.cell ?? 0;
20
+ const drawable = ribbons.filter((ribbon) => plots.has(ribbon.from) && plots.has(ribbon.to) && !hiddenKinds.has(ribbon.from) && !hiddenKinds.has(ribbon.to) && kitDraws(ribbon.edge));
21
+ const tilt = city?.tilt ?? 0.5;
22
+ /*
23
+ * THE DISTRICTS' NAME CARDS, where they were drawn: a ribbon goes round
24
+ * them rather than behind them. Measured once the picture has landed, on
25
+ * the canvas from the city's origin, so a pan moves nothing here.
26
+ */
27
+ const [labels, setLabels] = useState([]);
28
+ const labelsKey = labels.map((box) => `${Math.round(box.x)},${Math.round(box.y)},${Math.round(box.width)},${Math.round(box.height)}`).join(";");
29
+ const [svg, setSvg] = useState(null);
30
+ const settled = frame.t >= 1;
31
+ useLayoutEffect(() => {
32
+ const ground = svg?.parentElement;
33
+ if (!ground || !city || !settled)
34
+ return;
35
+ const box = ground.getBoundingClientRect();
36
+ const ox = city.originX + pan.x;
37
+ const oy = city.originY + pan.y;
38
+ const found = [...ground.querySelectorAll('[data-graview-view^="kind:"] .graview-kind-face')].flatMap((el) => {
39
+ const at = el.getBoundingClientRect();
40
+ return at.width > 0 && at.height > 0 ? [{ x: at.left - box.left - ox, y: at.top - box.top - oy, width: at.width, height: at.height }] : [];
41
+ });
42
+ const key = found.map((one) => `${Math.round(one.x)},${Math.round(one.y)},${Math.round(one.width)},${Math.round(one.height)}`).join(";");
43
+ if (key !== labelsKey)
44
+ setLabels(found);
45
+ // Measured again when the plots or the city's scale move the cards.
46
+ // eslint-disable-next-line react-hooks/exhaustive-deps
47
+ }, [svg, settled, plotsKey, cell, tilt]);
48
+ const routed = useMemo(() => {
49
+ if (cell === 0)
50
+ return [];
51
+ const loops = new Map();
52
+ const most = Math.max(0, ...drawable.map((ribbon) => ribbon.links));
53
+ const runs = routeOnTheGround({
54
+ ribbons: drawable.map((ribbon) => ({ key: ribbon.key, from: ribbon.from, to: ribbon.to, width: ribbonWidth(ribbon.links, most) })),
55
+ plots,
56
+ cell,
57
+ tilt,
58
+ labels,
59
+ });
60
+ return drawable.flatMap((ribbon) => {
61
+ let points;
62
+ if (ribbon.from === ribbon.to) {
63
+ const index = loops.get(ribbon.from) ?? 0;
64
+ loops.set(ribbon.from, index + 1);
65
+ points = onCanvas(loopPoints(plots.get(ribbon.from), index), { cell, tilt });
66
+ }
67
+ else
68
+ points = runs.get(ribbon.key);
69
+ if (!points || points.length < 2)
70
+ return [];
71
+ return [{ ribbon, points, d: pathOf(points), middle: midpointOf(points), width: ribbonWidth(ribbon.links, most) }];
72
+ });
73
+ // `plotsKey` stands for the plots; `drawable` follows the ribbons and the kit.
74
+ // eslint-disable-next-line react-hooks/exhaustive-deps
75
+ }, [cell, tilt, plotsKey, ribbons, kitDraws, hiddenKinds, labelsKey]);
76
+ /*
77
+ * POINTED AT, NEVER PRESSED. A ribbon runs up to its districts' curbs,
78
+ * where their names and controls are, and a hit stroke over it took their
79
+ * presses. So the ground is listened to instead: a mouse or a pen over
80
+ * bare ground near a ribbon says it, and anything drawn there — a card,
81
+ * a control, a panel — keeps the pointer as it always had.
82
+ */
83
+ const bands = useRef([]);
84
+ const at = useRef({ x: 0, y: 0 });
85
+ at.current = city ? { x: city.originX + pan.x, y: city.originY + pan.y } : { x: 0, y: 0 };
86
+ useEffect(() => {
87
+ const ground = svg?.parentElement;
88
+ if (!ground || typeof window === "undefined")
89
+ return;
90
+ let queued = 0;
91
+ let last = null;
92
+ const look = () => {
93
+ queued = 0;
94
+ const event = last;
95
+ if (!event)
96
+ return;
97
+ const target = event.target;
98
+ if (event.pointerType === "touch" || event.buttons !== 0 || target?.closest("[data-graview-view], button, a, input, select, textarea, [data-graview-overlay], [data-graview-offstage]")) {
99
+ setPointed(null);
100
+ return;
101
+ }
102
+ const box = ground.getBoundingClientRect();
103
+ const x = event.clientX - box.left - at.current.x;
104
+ const y = event.clientY - box.top - at.current.y;
105
+ let found = null;
106
+ let nearest = Infinity;
107
+ for (const band of bands.current) {
108
+ const gap = distanceTo(band.points, x, y);
109
+ if (gap <= band.reach && gap < nearest) {
110
+ nearest = gap;
111
+ found = band.key;
112
+ }
113
+ }
114
+ setPointed(found);
115
+ };
116
+ const move = (event) => {
117
+ last = event;
118
+ if (queued === 0)
119
+ queued = requestAnimationFrame(look);
120
+ };
121
+ const leave = () => setPointed(null);
122
+ ground.addEventListener("pointermove", move, { passive: true });
123
+ ground.addEventListener("pointerleave", leave);
124
+ return () => {
125
+ if (queued !== 0)
126
+ cancelAnimationFrame(queued);
127
+ ground.removeEventListener("pointermove", move);
128
+ ground.removeEventListener("pointerleave", leave);
129
+ };
130
+ }, [svg]);
131
+ if (!city || !view.overview || routed.length === 0)
132
+ return null;
133
+ const choice = mapChoice(selection, view, kindOf);
134
+ // A record's own links say it; an open lens is not drawn over.
135
+ if (choice.records.length > 0 || choice.lens !== null)
136
+ return null;
137
+ const shown = choice.districts.length > 0 ? routed.filter(({ ribbon }) => choice.districts.includes(ribbon.from) || choice.districts.includes(ribbon.to)) : routed;
138
+ if (shown.length === 0)
139
+ return null;
140
+ const asking = emphasis ?? null;
141
+ const said = shown.find(({ ribbon }) => ribbon.key === pointed);
142
+ bands.current = shown.map(({ ribbon, points, width: wide }) => ({ key: ribbon.key, points, reach: wide / 2 + 6 }));
143
+ const origin = { x: city.originX + pan.x, y: city.originY + pan.y };
144
+ const transform = `translate(${origin.x.toFixed(1)} ${origin.y.toFixed(1)})`;
145
+ return (_jsxs(_Fragment, { children: [_jsx("svg", { ref: setSvg, className: "graview-ribbons", "aria-hidden": "true", "data-graview-world": "", "data-graview-ribbons": shown.length, width: width, height: height, style: { position: "absolute", left: 0, top: 0, pointerEvents: "none", overflow: "visible" }, children: _jsx("g", { transform: transform, children: shown.map(({ ribbon, d, width: wide }) => {
146
+ const lit = pointed === ribbon.key || (asking !== null && asking === ribbon.edge) || choice.districts.length > 0;
147
+ const receded = asking !== null && asking !== ribbon.edge;
148
+ return (_jsx("g", { className: "graview-ribbon", "data-graview-ribbon": ribbon.edge, "data-graview-from": `kind:${ribbon.from}`, "data-graview-to": `kind:${ribbon.to}`, "data-graview-links": ribbon.links, "data-graview-lit": lit && !receded ? "" : undefined, "data-graview-receded": receded ? "" : undefined, children: _jsx("path", { className: "graview-ribbon-band", d: d, style: { strokeWidth: wide } }) }, ribbon.key));
149
+ }) }) }), said ? (
150
+ /* The words, at the size a person reads, on a solid backing, where the ribbon is pointed at. */
151
+ _jsx("div", { className: "graview-ribbon-said", "data-graview-world": "", "data-testid": "ribbon-said", role: "status", style: { position: "absolute", left: origin.x + said.middle.x, top: origin.y + said.middle.y, pointerEvents: "none" }, children: ribbonSentence(store.schema, said.ribbon) })) : null] }));
152
+ }
153
+ /** How far a point is from a polyline, in the same coordinates. */
154
+ export function distanceTo(points, x, y) {
155
+ let nearest = Infinity;
156
+ for (let i = 1; i < points.length; i++) {
157
+ const a = points[i - 1];
158
+ const b = points[i];
159
+ const dx = b.x - a.x;
160
+ const dy = b.y - a.y;
161
+ const length = dx * dx + dy * dy;
162
+ const t = length === 0 ? 0 : Math.max(0, Math.min(1, ((x - a.x) * dx + (y - a.y) * dy) / length));
163
+ nearest = Math.min(nearest, Math.hypot(x - (a.x + t * dx), y - (a.y + t * dy)));
164
+ }
165
+ return nearest;
166
+ }
167
+ //# sourceMappingURL=ribbons-layer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ribbons-layer.js","sourceRoot":"","sources":["../src/ribbons-layer.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ export { RIBBON_WIDTH, ribbonWidth } from "./ribbon-width.js";
2
+ export interface Point {
3
+ readonly x: number;
4
+ readonly y: number;
5
+ }
6
+ export interface PlotLike {
7
+ readonly col: number;
8
+ readonly row: number;
9
+ readonly side: number;
10
+ }
11
+ export interface Lattice {
12
+ readonly cell: number;
13
+ /** The tilt the city is seen at (`toIso`): a half by default, more from above. */
14
+ readonly tilt?: number | undefined;
15
+ }
16
+ /** A box on the canvas, from origin zero: a district's name card the ribbons go round. */
17
+ export interface Box {
18
+ readonly x: number;
19
+ readonly y: number;
20
+ readonly width: number;
21
+ readonly height: number;
22
+ }
23
+ /**
24
+ * WHERE EACH RIBBON RUNS, ON THE GROUND.
25
+ *
26
+ * The ground is a lattice, and a line drawn on it runs along one of its two
27
+ * axes — the diagonals a person sees the grid in. A ribbon leaves its
28
+ * district from the middle of a curb, runs along the gutters between the
29
+ * districts on the lattice's own lines, and comes into the other district
30
+ * at the middle of a curb: never across a district, never behind a
31
+ * district's name card, and with as few bends as the ground allows — none
32
+ * or one between neighbors. A stretch another ribbon already runs costs
33
+ * less, so relations that can share a street do, and those sharing one lie
34
+ * side by side in it, each its own width apart. Routed on the lattice and
35
+ * drawn through `toIso`, so it lies on the ground at whatever tilt the city
36
+ * is seen at.
37
+ */
38
+ export declare function routeOnTheGround(input: {
39
+ readonly ribbons: readonly {
40
+ readonly key: string;
41
+ readonly from: string;
42
+ readonly to: string;
43
+ readonly width: number;
44
+ }[];
45
+ readonly plots: ReadonlyMap<string, PlotLike>;
46
+ readonly cell: number;
47
+ readonly tilt?: number | undefined;
48
+ /** The districts' name cards, on the canvas from origin zero. */
49
+ readonly labels?: readonly Box[];
50
+ }): Map<string, Point[]>;
51
+ /**
52
+ * A RELATION OF A KIND TO ITSELF — a feature that needs another first —
53
+ * leaves its district's right-hand curb, runs a short way up the gutter
54
+ * beside it and comes back in: a loop on the lattice, in the gutter, on the
55
+ * district it belongs to. Each further one stands a lane further out.
56
+ */
57
+ export declare function loopPoints(plot: PlotLike, index: number): {
58
+ col: number;
59
+ row: number;
60
+ }[];
61
+ /** Lattice points on the canvas, from origin zero. */
62
+ export declare function onCanvas(points: readonly {
63
+ col: number;
64
+ row: number;
65
+ }[], city: Lattice): Point[];
66
+ /** A polyline as an SVG path. */
67
+ export declare function pathOf(points: readonly Point[]): string;
68
+ /** The middle of a polyline by length: where a ribbon's words are said. */
69
+ export declare function midpointOf(points: readonly Point[]): Point;
70
+ //# sourceMappingURL=ribbons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ribbons.d.ts","sourceRoot":"","sources":["../src/ribbons.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAmB9D,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AACD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AACD,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,0FAA0F;AAC1F,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAcD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1H,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,EAAE,CAAC;CAClC,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAqQvB;AAkDD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,CAWxF;AAED,sDAAsD;AACtD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,KAAK,EAAE,CAEhG;AAED,iCAAiC;AACjC,wBAAgB,MAAM,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAEvD;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,KAAK,CAa1D"}
@@ -0,0 +1,400 @@
1
+ import { toIso } from "@graview/core/scene";
2
+ export { RIBBON_WIDTH, ribbonWidth } from "./ribbon-width.js";
3
+ /** The grid a ribbon is routed on: half a lattice cell, so a street can run down the middle of a one-cell gutter. */
4
+ const STEP = 0.5;
5
+ /** How far out from a curb the streets run, in cells, and how near to one no street may come. */
6
+ const OUT = 1;
7
+ const CLEAR = 0.76;
8
+ /** What a bend costs, in cells: enough that a ribbon bends only where it must. */
9
+ const BEND = 3;
10
+ /** How far from every district, in cells, the ground starts to cost more to cross: a ribbon keeps to the streets between districts, not the open ground beyond them. */
11
+ const NEAR = 3;
12
+ /** How much less a stretch another ribbon already runs along costs: ribbons that can share a street do. */
13
+ const SHARED = 0.6;
14
+ /**
15
+ * WHERE EACH RIBBON RUNS, ON THE GROUND.
16
+ *
17
+ * The ground is a lattice, and a line drawn on it runs along one of its two
18
+ * axes — the diagonals a person sees the grid in. A ribbon leaves its
19
+ * district from the middle of a curb, runs along the gutters between the
20
+ * districts on the lattice's own lines, and comes into the other district
21
+ * at the middle of a curb: never across a district, never behind a
22
+ * district's name card, and with as few bends as the ground allows — none
23
+ * or one between neighbors. A stretch another ribbon already runs costs
24
+ * less, so relations that can share a street do, and those sharing one lie
25
+ * side by side in it, each its own width apart. Routed on the lattice and
26
+ * drawn through `toIso`, so it lies on the ground at whatever tilt the city
27
+ * is seen at.
28
+ */
29
+ export function routeOnTheGround(input) {
30
+ const { ribbons, plots, cell } = input;
31
+ const tilt = input.tilt ?? 0.5;
32
+ const labels = input.labels ?? [];
33
+ const all = [...plots.values()];
34
+ const out = new Map();
35
+ if (all.length === 0 || cell <= 0)
36
+ return out;
37
+ const minCol = Math.min(...all.map((p) => p.col)) - 3;
38
+ const minRow = Math.min(...all.map((p) => p.row)) - 3;
39
+ const maxCol = Math.max(...all.map((p) => p.col + p.side)) + 3;
40
+ const maxRow = Math.max(...all.map((p) => p.row + p.side)) + 3;
41
+ const width = Math.round((maxCol - minCol) / STEP) + 1;
42
+ const height = Math.round((maxRow - minRow) / STEP) + 1;
43
+ const colOf = (i) => minCol + i * STEP;
44
+ const rowOf = (j) => minRow + j * STEP;
45
+ const pad = 4;
46
+ // Whether the name cards are heeded: where they leave no way at all, a ribbon passes under them rather than not at all.
47
+ let heed = true;
48
+ const underLabel = (col, row) => {
49
+ if (!heed)
50
+ return false;
51
+ const p = toIso(col, row, cell, tilt);
52
+ return labels.some((box) => p.x > box.x - pad && p.x < box.x + box.width + pad && p.y > box.y - pad && p.y < box.y + box.height + pad);
53
+ };
54
+ // A street keeps a cell clear of every curb, so the ribbons in it lie in the gutter, not on its edge.
55
+ const onPlot = (col, row) => all.some((p) => col > p.col - CLEAR && col < p.col + p.side + CLEAR && row > p.row - CLEAR && row < p.row + p.side + CLEAR);
56
+ // Every grid point a ribbon may pass through: off every district, and out from under every name card — or, where they leave no way, off every district.
57
+ const heeding = new Uint8Array(width * height);
58
+ const plain = new Uint8Array(width * height);
59
+ for (let i = 0; i < width; i++) {
60
+ for (let j = 0; j < height; j++) {
61
+ const col = colOf(i);
62
+ const row = rowOf(j);
63
+ plain[j * width + i] = onPlot(col, row) ? 0 : 1;
64
+ heeding[j * width + i] = plain[j * width + i] && !underLabel(col, row) ? 1 : 0;
65
+ }
66
+ }
67
+ let free = heeding;
68
+ // How far each grid point is from the nearest district: past NEAR, every step costs as much again for each cell further.
69
+ const away = new Float32Array(width * height);
70
+ for (let i = 0; i < width; i++) {
71
+ for (let j = 0; j < height; j++) {
72
+ const col = colOf(i);
73
+ const row = rowOf(j);
74
+ away[j * width + i] = Math.min(...all.map((p) => Math.max(0, p.col - col, col - (p.col + p.side), p.row - row, row - (p.row + p.side))));
75
+ }
76
+ }
77
+ // An edge is free when its middle is too: a name card can sit between two grid points.
78
+ const edgeFree = (i, j, di, dj) => {
79
+ const col = colOf(i) + (di * STEP) / 2;
80
+ const row = rowOf(j) + (dj * STEP) / 2;
81
+ return !onPlot(col, row) && !underLabel(col, row);
82
+ };
83
+ /*
84
+ * The doors of a district: the grid points a cell out from the middle
85
+ * stretch of each of its four curbs, each with the point on the curb it
86
+ * steps in to. Not its corners — a ribbon meets a curb, not a point.
87
+ */
88
+ const doors = (plot) => {
89
+ const found = [];
90
+ const along = (from, side) => {
91
+ const steps = [];
92
+ // On the router's grid, so two doors facing each other across a street line up.
93
+ const margin = Math.min(1, side / 3);
94
+ for (let t = Math.ceil((from + margin) / STEP - 1e-9) * STEP; t <= from + side - margin + 1e-9; t += STEP)
95
+ steps.push(t);
96
+ return steps;
97
+ };
98
+ // `out` is the heading that leaves the curb straight: a ribbon meets a curb square on.
99
+ const add = (col, row, out, curb) => {
100
+ const i = Math.round((col - minCol) / STEP);
101
+ const j = Math.round((row - minRow) / STEP);
102
+ if (i >= 0 && j >= 0 && i < width && j < height && free[j * width + i])
103
+ found.push({ i, j, out, curb });
104
+ };
105
+ for (const row of along(plot.row, plot.side)) {
106
+ add(plot.col - OUT, row, 1, { col: plot.col, row });
107
+ add(plot.col + plot.side + OUT, row, 0, { col: plot.col + plot.side, row });
108
+ }
109
+ for (const col of along(plot.col, plot.side)) {
110
+ add(col, plot.row - OUT, 3, { col, row: plot.row });
111
+ add(col, plot.row + plot.side + OUT, 2, { col, row: plot.row + plot.side });
112
+ }
113
+ return found;
114
+ };
115
+ const used = new Map();
116
+ const edgeKey = (i, j, i2, j2) => (i < i2 || (i === i2 && j < j2) ? `${i},${j}-${i2},${j2}` : `${i2},${j2}-${i},${j}`);
117
+ const DIRS = [
118
+ [1, 0],
119
+ [-1, 0],
120
+ [0, 1],
121
+ [0, -1],
122
+ ];
123
+ const routes = new Map();
124
+ for (const ribbon of ribbons) {
125
+ const a = plots.get(ribbon.from);
126
+ const b = plots.get(ribbon.to);
127
+ if (!a || !b || a === b)
128
+ continue;
129
+ let reached = -1;
130
+ let starts = [];
131
+ let goal = new Map();
132
+ let back = new Int32Array(0);
133
+ let best = Infinity;
134
+ /*
135
+ * Round the name cards where that is a way at all and not a long way
136
+ * round — on a phone the cards are as big as the streets, and a ribbon
137
+ * sent out past the window to clear them says less than one passing
138
+ * under a card. Else, as a street runs under a sign, under them.
139
+ */
140
+ const found = [];
141
+ for (const pass of [true, false]) {
142
+ reached = -1;
143
+ best = Infinity;
144
+ heed = pass;
145
+ free = pass ? heeding : plain;
146
+ starts = doors(a);
147
+ const ends = doors(b);
148
+ if (starts.length === 0 || ends.length === 0) {
149
+ found.push({ reached: -1, starts, goal, back, best });
150
+ continue;
151
+ }
152
+ goal = new Map(ends.map((door) => [door.j * width + door.i, door]));
153
+ const toB = (i, j) => {
154
+ const col = colOf(i);
155
+ const row = rowOf(j);
156
+ return (Math.max(0, b.col - col, col - (b.col + b.side)) + Math.max(0, b.row - row, row - (b.row + b.side))) * SHARED;
157
+ };
158
+ // A* over (point, heading): a step costs its length, a bend BEND, a stretch already run less.
159
+ const states = width * height * 4;
160
+ const cost = new Float64Array(states).fill(Infinity);
161
+ back = new Int32Array(states).fill(-1);
162
+ const heap = new MinHeap();
163
+ // Leaving square on: a door's state heads out of the curb, so a turn straight after it is a bend.
164
+ for (const door of starts) {
165
+ const s = (door.j * width + door.i) * 4 + door.out;
166
+ cost[s] = 0;
167
+ heap.push(s, toB(door.i, door.j));
168
+ }
169
+ const INTO = [1, 0, 3, 2];
170
+ while (heap.size > 0) {
171
+ const f = heap.peekKey();
172
+ if (f >= best)
173
+ break;
174
+ const s = heap.pop();
175
+ const node = Math.floor(s / 4);
176
+ const d = s % 4;
177
+ const i = node % width;
178
+ const j = Math.floor(node / width);
179
+ const door = goal.get(node);
180
+ if (door) {
181
+ // Arriving square on costs nothing more; arriving along the curb is one more bend.
182
+ const total = cost[s] + (d === INTO[door.out] ? 0 : BEND);
183
+ if (total < best) {
184
+ best = total;
185
+ reached = s;
186
+ }
187
+ continue;
188
+ }
189
+ for (let nd = 0; nd < 4; nd++) {
190
+ const [di, dj] = DIRS[nd];
191
+ const i2 = i + di;
192
+ const j2 = j + dj;
193
+ if (i2 < 0 || j2 < 0 || i2 >= width || j2 >= height || !free[j2 * width + i2] || !edgeFree(i, j, di, dj))
194
+ continue;
195
+ const step = STEP * (used.has(edgeKey(i, j, i2, j2)) ? SHARED : 1) * (1 + Math.max(0, away[j2 * width + i2] - NEAR)) + (nd === d ? 0 : BEND);
196
+ const s2 = (j2 * width + i2) * 4 + nd;
197
+ const next = cost[s] + step;
198
+ if (next < cost[s2]) {
199
+ cost[s2] = next;
200
+ back[s2] = s;
201
+ heap.push(s2, next + toB(i2, j2));
202
+ }
203
+ }
204
+ }
205
+ found.push({ reached, starts, goal, back, best });
206
+ }
207
+ heed = true;
208
+ free = heeding;
209
+ const [round, under] = found;
210
+ const chosen = round && round.reached !== -1 && (!under || under.reached === -1 || round.best <= 1.3 * under.best + 2) ? round : under;
211
+ if (!chosen || chosen.reached === -1)
212
+ continue;
213
+ ({ reached, starts, goal, back } = chosen);
214
+ const cells = [];
215
+ for (let s = reached; s !== -1; s = back[s]) {
216
+ const node = Math.floor(s / 4);
217
+ const last = cells[cells.length - 1];
218
+ const here = { i: node % width, j: Math.floor(node / width) };
219
+ if (!last || last.i !== here.i || last.j !== here.j)
220
+ cells.push(here);
221
+ }
222
+ cells.reverse();
223
+ for (let k = 1; k < cells.length; k++) {
224
+ const key = edgeKey(cells[k - 1].i, cells[k - 1].j, cells[k].i, cells[k].j);
225
+ used.set(key, [...(used.get(key) ?? []), ribbon.key]);
226
+ }
227
+ const first = starts.find((door) => door.i === cells[0].i && door.j === cells[0].j);
228
+ const last = goal.get(cells[cells.length - 1].j * width + cells[cells.length - 1].i);
229
+ routes.set(ribbon.key, { cells, start: first.curb, end: last.curb });
230
+ }
231
+ /*
232
+ * THE BUNDLES. A run's lane is its place among the ribbons sharing the
233
+ * stretch it runs, in one order for every stretch, so a bundle keeps its
234
+ * order round a bend; each lane is its ribbon's width apart from the last,
235
+ * across the run, on the ground.
236
+ */
237
+ const widthOf = new Map(ribbons.map((ribbon) => [ribbon.key, ribbon.width]));
238
+ const order = new Map(ribbons.map((ribbon, index) => [ribbon.key, index]));
239
+ // How far apart, on the canvas, two runs a cell apart across the ground lie.
240
+ const pxPerCell = (cell * tilt) / Math.sqrt(1 + tilt * tilt);
241
+ /*
242
+ * A STREET IS A LINE OF THE LATTICE, and a ribbon keeps its lane along
243
+ * the whole of it: every ribbon that runs on a line anywhere is given its
244
+ * place across it, so one joining or leaving the bundle part way along
245
+ * moves no other ribbon sideways.
246
+ */
247
+ const lineOf = (axis, at) => (axis === "col" ? `r${at.row.toFixed(2)}` : `c${at.col.toFixed(2)}`);
248
+ const lines = new Map();
249
+ for (const [key, route] of routes) {
250
+ for (let k = 1; k < route.cells.length; k++) {
251
+ const a = route.cells[k - 1];
252
+ const b = route.cells[k];
253
+ const line = lineOf(a.i !== b.i ? "col" : "row", { col: colOf(a.i), row: rowOf(a.j) });
254
+ lines.set(line, (lines.get(line) ?? new Set()).add(key));
255
+ }
256
+ }
257
+ for (const [key, route] of routes) {
258
+ // The whole way, on the lattice: in from the curb, along the grid, and out to the other curb.
259
+ const way = [route.start, ...route.cells.map((at) => ({ col: colOf(at.i), row: rowOf(at.j) })), route.end];
260
+ const gridEdge = (k) => (k >= 2 && k <= route.cells.length ? edgeKey(route.cells[k - 2].i, route.cells[k - 2].j, route.cells[k - 1].i, route.cells[k - 1].j) : null);
261
+ // The straight runs: where the heading stays the same.
262
+ const runs = [];
263
+ for (let k = 1; k < way.length; k++) {
264
+ const axis = Math.abs(way[k].col - way[k - 1].col) > 1e-9 ? "col" : "row";
265
+ const open = runs[runs.length - 1];
266
+ if (open && open.axis === axis)
267
+ open.to = k;
268
+ else
269
+ runs.push({ from: k - 1, to: k, axis });
270
+ }
271
+ if (runs.length === 0)
272
+ continue;
273
+ const shift = runs.map((run) => {
274
+ // A stub in from a curb is no street: it stays where it meets the curb.
275
+ if (!Array.from({ length: run.to - run.from }, (_, n) => gridEdge(run.from + 1 + n)).some((edge) => edge !== null))
276
+ return 0;
277
+ const sharing = lines.get(lineOf(run.axis, way[run.from])) ?? new Set();
278
+ if (!sharing.has(key))
279
+ return 0;
280
+ const bundle = [...sharing].sort((x, y) => (order.get(x) ?? 0) - (order.get(y) ?? 0));
281
+ const total = bundle.reduce((sum, other) => sum + (widthOf.get(other) ?? 2) + 2, 0) - 2;
282
+ let at = -total / 2;
283
+ for (const other of bundle) {
284
+ if (other === key)
285
+ return (at + (widthOf.get(other) ?? 2) / 2) / pxPerCell;
286
+ at += (widthOf.get(other) ?? 2) + 2;
287
+ }
288
+ return 0;
289
+ });
290
+ // A run along the col axis holds its row and is moved across by changing it, and the other way about; a corner takes one from each.
291
+ const held = (index) => {
292
+ const run = runs[index];
293
+ const at = way[run.from];
294
+ return run.axis === "col" ? { row: at.row + shift[index] } : { col: at.col + shift[index] };
295
+ };
296
+ const merge = (one, other, fallback) => ({ col: one.col ?? other.col ?? fallback.col, row: one.row ?? other.row ?? fallback.row });
297
+ const points = [merge(held(0), {}, way[0])];
298
+ for (let index = 0; index + 1 < runs.length; index++)
299
+ points.push(merge(held(index), held(index + 1), way[runs[index].to]));
300
+ points.push(merge(held(runs.length - 1), {}, way[way.length - 1]));
301
+ out.set(key, points.map((p) => toIso(p.col, p.row, cell, tilt)));
302
+ }
303
+ return out;
304
+ }
305
+ /** The least-first queue the ground's router takes its next step from. */
306
+ class MinHeap {
307
+ items = [];
308
+ keys = [];
309
+ get size() {
310
+ return this.items.length;
311
+ }
312
+ peekKey() {
313
+ return this.keys[0] ?? Infinity;
314
+ }
315
+ push(item, key) {
316
+ this.items.push(item);
317
+ this.keys.push(key);
318
+ let at = this.items.length - 1;
319
+ while (at > 0) {
320
+ const up = (at - 1) >> 1;
321
+ if (this.keys[up] <= this.keys[at])
322
+ break;
323
+ this.swap(at, up);
324
+ at = up;
325
+ }
326
+ }
327
+ pop() {
328
+ const top = this.items[0];
329
+ const lastItem = this.items.pop();
330
+ const lastKey = this.keys.pop();
331
+ if (this.items.length > 0) {
332
+ this.items[0] = lastItem;
333
+ this.keys[0] = lastKey;
334
+ let at = 0;
335
+ for (;;) {
336
+ const left = 2 * at + 1;
337
+ const right = left + 1;
338
+ let least = at;
339
+ if (left < this.items.length && this.keys[left] < this.keys[least])
340
+ least = left;
341
+ if (right < this.items.length && this.keys[right] < this.keys[least])
342
+ least = right;
343
+ if (least === at)
344
+ break;
345
+ this.swap(at, least);
346
+ at = least;
347
+ }
348
+ }
349
+ return top;
350
+ }
351
+ swap(a, b) {
352
+ [this.items[a], this.items[b]] = [this.items[b], this.items[a]];
353
+ [this.keys[a], this.keys[b]] = [this.keys[b], this.keys[a]];
354
+ }
355
+ }
356
+ /**
357
+ * A RELATION OF A KIND TO ITSELF — a feature that needs another first —
358
+ * leaves its district's right-hand curb, runs a short way up the gutter
359
+ * beside it and comes back in: a loop on the lattice, in the gutter, on the
360
+ * district it belongs to. Each further one stands a lane further out.
361
+ */
362
+ export function loopPoints(plot, index) {
363
+ const out = plot.col + plot.side;
364
+ const reach = out + 0.25 + 0.22 * Math.max(0, index);
365
+ const a = plot.row + plot.side * Math.max(0.1, 0.3 - 0.06 * index);
366
+ const b = plot.row + plot.side * Math.min(0.9, 0.7 + 0.06 * index);
367
+ return [
368
+ { col: out, row: a },
369
+ { col: reach, row: a },
370
+ { col: reach, row: b },
371
+ { col: out, row: b },
372
+ ];
373
+ }
374
+ /** Lattice points on the canvas, from origin zero. */
375
+ export function onCanvas(points, city) {
376
+ return points.map((p) => toIso(p.col, p.row, city.cell, city.tilt));
377
+ }
378
+ /** A polyline as an SVG path. */
379
+ export function pathOf(points) {
380
+ return points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(" ");
381
+ }
382
+ /** The middle of a polyline by length: where a ribbon's words are said. */
383
+ export function midpointOf(points) {
384
+ if (points.length === 0)
385
+ return { x: 0, y: 0 };
386
+ let total = 0;
387
+ for (let i = 1; i < points.length; i++)
388
+ total += Math.hypot(points[i].x - points[i - 1].x, points[i].y - points[i - 1].y);
389
+ let left = total / 2;
390
+ for (let i = 1; i < points.length; i++) {
391
+ const a = points[i - 1];
392
+ const b = points[i];
393
+ const leg = Math.hypot(b.x - a.x, b.y - a.y);
394
+ if (leg >= left && leg > 0)
395
+ return { x: a.x + ((b.x - a.x) * left) / leg, y: a.y + ((b.y - a.y) * left) / leg };
396
+ left -= leg;
397
+ }
398
+ return points[points.length - 1];
399
+ }
400
+ //# sourceMappingURL=ribbons.js.map