@kanzo-tech/graph 0.10.0 → 0.12.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 (204) hide show
  1. package/README.md +60 -95
  2. package/dist/core/categories.d.ts +12 -0
  3. package/dist/core/categories.d.ts.map +1 -0
  4. package/dist/core/categories.js +14 -0
  5. package/dist/core/categories.js.map +1 -0
  6. package/dist/core/channels.d.ts +34 -0
  7. package/dist/core/channels.d.ts.map +1 -0
  8. package/dist/core/channels.js +19 -0
  9. package/dist/core/channels.js.map +1 -0
  10. package/dist/core/detail.d.ts +18 -0
  11. package/dist/core/detail.d.ts.map +1 -0
  12. package/dist/core/detail.js +20 -0
  13. package/dist/core/detail.js.map +1 -0
  14. package/dist/core/filter.d.ts +34 -0
  15. package/dist/core/filter.d.ts.map +1 -0
  16. package/dist/core/filter.js +99 -0
  17. package/dist/core/filter.js.map +1 -0
  18. package/dist/core/refine.d.ts +14 -0
  19. package/dist/core/refine.d.ts.map +1 -0
  20. package/dist/core/refine.js +20 -0
  21. package/dist/core/refine.js.map +1 -0
  22. package/dist/{resident.d.ts → core/resident.d.ts} +7 -7
  23. package/dist/core/resident.d.ts.map +1 -0
  24. package/dist/core/resident.js +44 -0
  25. package/dist/core/resident.js.map +1 -0
  26. package/dist/core/scheduler.d.ts +42 -0
  27. package/dist/core/scheduler.d.ts.map +1 -0
  28. package/dist/core/scheduler.js +77 -0
  29. package/dist/core/scheduler.js.map +1 -0
  30. package/dist/core/state.d.ts +132 -0
  31. package/dist/core/state.d.ts.map +1 -0
  32. package/dist/core/store.d.ts +6 -0
  33. package/dist/core/store.d.ts.map +1 -0
  34. package/dist/core/store.js +235 -0
  35. package/dist/core/store.js.map +1 -0
  36. package/dist/core/tile-matrix.d.ts +37 -0
  37. package/dist/core/tile-matrix.d.ts.map +1 -0
  38. package/dist/core/tile-matrix.js +49 -0
  39. package/dist/core/tile-matrix.js.map +1 -0
  40. package/dist/core/tile.d.ts +54 -0
  41. package/dist/core/tile.d.ts.map +1 -0
  42. package/dist/core/tile.js +76 -0
  43. package/dist/core/tile.js.map +1 -0
  44. package/dist/core/tileset.d.ts +59 -0
  45. package/dist/core/tileset.d.ts.map +1 -0
  46. package/dist/core/tileset.js +177 -0
  47. package/dist/core/tileset.js.map +1 -0
  48. package/dist/{types.d.ts → core/types.d.ts} +9 -0
  49. package/dist/core/types.d.ts.map +1 -0
  50. package/dist/index.d.ts +37 -70
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +32 -34
  53. package/dist/index.js.map +1 -1
  54. package/dist/{use-graph-selection.d.ts → parts/gesture.d.ts} +4 -4
  55. package/dist/parts/gesture.d.ts.map +1 -0
  56. package/dist/{use-graph-selection.js → parts/gesture.js} +17 -17
  57. package/dist/parts/gesture.js.map +1 -0
  58. package/dist/parts/graph-canvas.d.ts +17 -0
  59. package/dist/parts/graph-canvas.d.ts.map +1 -0
  60. package/dist/parts/graph-canvas.js +174 -0
  61. package/dist/parts/graph-canvas.js.map +1 -0
  62. package/dist/parts/graph-inspector.d.ts +18 -0
  63. package/dist/parts/graph-inspector.d.ts.map +1 -0
  64. package/dist/parts/graph-inspector.js +84 -0
  65. package/dist/parts/graph-inspector.js.map +1 -0
  66. package/dist/parts/graph-legend.d.ts +12 -0
  67. package/dist/parts/graph-legend.d.ts.map +1 -0
  68. package/dist/parts/graph-legend.js +61 -0
  69. package/dist/parts/graph-legend.js.map +1 -0
  70. package/dist/parts/graph-toolbar.d.ts +15 -0
  71. package/dist/parts/graph-toolbar.d.ts.map +1 -0
  72. package/dist/parts/graph-toolbar.js +94 -0
  73. package/dist/parts/graph-toolbar.js.map +1 -0
  74. package/dist/parts/overlays.d.ts +33 -0
  75. package/dist/parts/overlays.d.ts.map +1 -0
  76. package/dist/{use-graph-overlays.js → parts/overlays.js} +8 -8
  77. package/dist/parts/overlays.js.map +1 -0
  78. package/dist/{shape-glyph.d.ts → parts/shape-glyph.d.ts} +1 -1
  79. package/dist/parts/shape-glyph.d.ts.map +1 -0
  80. package/dist/{shape-glyph.js → parts/shape-glyph.js} +1 -1
  81. package/dist/parts/shape-glyph.js.map +1 -0
  82. package/dist/react/graph-root.d.ts +15 -0
  83. package/dist/react/graph-root.d.ts.map +1 -0
  84. package/dist/react/graph-root.js +23 -0
  85. package/dist/react/graph-root.js.map +1 -0
  86. package/dist/{use-graph-prefs.d.ts → react/use-graph-prefs.d.ts} +2 -2
  87. package/dist/react/use-graph-prefs.d.ts.map +1 -0
  88. package/dist/{use-graph-prefs.js → react/use-graph-prefs.js} +3 -3
  89. package/dist/react/use-graph-prefs.js.map +1 -0
  90. package/dist/react/use-graph-state.d.ts +11 -0
  91. package/dist/react/use-graph-state.d.ts.map +1 -0
  92. package/dist/react/use-graph-state.js +16 -0
  93. package/dist/react/use-graph-state.js.map +1 -0
  94. package/dist/react/use-graph.d.ts +38 -0
  95. package/dist/react/use-graph.d.ts.map +1 -0
  96. package/dist/react/use-graph.js +87 -0
  97. package/dist/react/use-graph.js.map +1 -0
  98. package/dist/{adaptive.d.ts → render/adaptive.d.ts} +1 -1
  99. package/dist/render/adaptive.d.ts.map +1 -0
  100. package/dist/render/adaptive.js.map +1 -0
  101. package/dist/render/compose.d.ts +51 -0
  102. package/dist/render/compose.d.ts.map +1 -0
  103. package/dist/render/compose.js +107 -0
  104. package/dist/render/compose.js.map +1 -0
  105. package/dist/render/css-color.d.ts.map +1 -0
  106. package/dist/render/css-color.js.map +1 -0
  107. package/dist/render/encode.d.ts +42 -0
  108. package/dist/render/encode.d.ts.map +1 -0
  109. package/dist/render/encode.js +68 -0
  110. package/dist/render/encode.js.map +1 -0
  111. package/dist/render/graph-looks.d.ts.map +1 -0
  112. package/dist/render/graph-looks.js.map +1 -0
  113. package/dist/render/graph-model.d.ts +44 -0
  114. package/dist/render/graph-model.d.ts.map +1 -0
  115. package/dist/render/graph-model.js +80 -0
  116. package/dist/render/graph-model.js.map +1 -0
  117. package/dist/{graph-sim.d.ts → render/graph-sim.d.ts} +1 -1
  118. package/dist/render/graph-sim.d.ts.map +1 -0
  119. package/dist/render/graph-sim.js.map +1 -0
  120. package/dist/{obligations.d.ts → render/obligations.d.ts} +1 -1
  121. package/dist/render/obligations.d.ts.map +1 -0
  122. package/dist/render/renderer.d.ts +38 -0
  123. package/dist/render/renderer.d.ts.map +1 -0
  124. package/dist/render/renderer.js +235 -0
  125. package/dist/render/renderer.js.map +1 -0
  126. package/dist/render/webgl.d.ts +14 -0
  127. package/dist/render/webgl.d.ts.map +1 -0
  128. package/dist/render/webgl.js +19 -0
  129. package/dist/render/webgl.js.map +1 -0
  130. package/dist/render/when-ready.d.ts.map +1 -0
  131. package/dist/render/when-ready.js.map +1 -0
  132. package/package.json +11 -21
  133. package/dist/adaptive.d.ts.map +0 -1
  134. package/dist/adaptive.js.map +0 -1
  135. package/dist/bounded.d.ts +0 -364
  136. package/dist/bounded.d.ts.map +0 -1
  137. package/dist/bounded.js +0 -18
  138. package/dist/bounded.js.map +0 -1
  139. package/dist/cluster-ring.d.ts +0 -25
  140. package/dist/cluster-ring.d.ts.map +0 -1
  141. package/dist/cluster-ring.js +0 -16
  142. package/dist/cluster-ring.js.map +0 -1
  143. package/dist/css-color.d.ts.map +0 -1
  144. package/dist/css-color.js.map +0 -1
  145. package/dist/duck-source.d.ts +0 -198
  146. package/dist/duck-source.d.ts.map +0 -1
  147. package/dist/duck-source.js +0 -320
  148. package/dist/duck-source.js.map +0 -1
  149. package/dist/graph-canvas.d.ts +0 -50
  150. package/dist/graph-canvas.d.ts.map +0 -1
  151. package/dist/graph-canvas.js +0 -35
  152. package/dist/graph-canvas.js.map +0 -1
  153. package/dist/graph-looks.d.ts.map +0 -1
  154. package/dist/graph-looks.js.map +0 -1
  155. package/dist/graph-model.d.ts +0 -130
  156. package/dist/graph-model.d.ts.map +0 -1
  157. package/dist/graph-model.js +0 -104
  158. package/dist/graph-model.js.map +0 -1
  159. package/dist/graph-sim.d.ts.map +0 -1
  160. package/dist/graph-sim.js.map +0 -1
  161. package/dist/obligations.d.ts.map +0 -1
  162. package/dist/resident.d.ts.map +0 -1
  163. package/dist/resident.js +0 -44
  164. package/dist/resident.js.map +0 -1
  165. package/dist/shape-glyph.d.ts.map +0 -1
  166. package/dist/shape-glyph.js.map +0 -1
  167. package/dist/slice-client.d.ts +0 -78
  168. package/dist/slice-client.d.ts.map +0 -1
  169. package/dist/slice-client.js +0 -98
  170. package/dist/slice-client.js.map +0 -1
  171. package/dist/types.d.ts.map +0 -1
  172. package/dist/use-graph-look.d.ts +0 -28
  173. package/dist/use-graph-look.d.ts.map +0 -1
  174. package/dist/use-graph-look.js +0 -28
  175. package/dist/use-graph-look.js.map +0 -1
  176. package/dist/use-graph-overlays.d.ts +0 -44
  177. package/dist/use-graph-overlays.d.ts.map +0 -1
  178. package/dist/use-graph-overlays.js.map +0 -1
  179. package/dist/use-graph-prefs.d.ts.map +0 -1
  180. package/dist/use-graph-prefs.js.map +0 -1
  181. package/dist/use-graph-selection.d.ts.map +0 -1
  182. package/dist/use-graph-selection.js.map +0 -1
  183. package/dist/use-graph.d.ts +0 -203
  184. package/dist/use-graph.d.ts.map +0 -1
  185. package/dist/use-graph.js +0 -102
  186. package/dist/use-graph.js.map +0 -1
  187. package/dist/use-query-loop.d.ts +0 -88
  188. package/dist/use-query-loop.d.ts.map +0 -1
  189. package/dist/use-query-loop.js +0 -148
  190. package/dist/use-query-loop.js.map +0 -1
  191. package/dist/use-renderer.d.ts +0 -86
  192. package/dist/use-renderer.d.ts.map +0 -1
  193. package/dist/use-renderer.js +0 -188
  194. package/dist/use-renderer.js.map +0 -1
  195. package/dist/when-ready.d.ts.map +0 -1
  196. package/dist/when-ready.js.map +0 -1
  197. /package/dist/{adaptive.js → render/adaptive.js} +0 -0
  198. /package/dist/{css-color.d.ts → render/css-color.d.ts} +0 -0
  199. /package/dist/{css-color.js → render/css-color.js} +0 -0
  200. /package/dist/{graph-looks.d.ts → render/graph-looks.d.ts} +0 -0
  201. /package/dist/{graph-looks.js → render/graph-looks.js} +0 -0
  202. /package/dist/{graph-sim.js → render/graph-sim.js} +0 -0
  203. /package/dist/{when-ready.d.ts → render/when-ready.d.ts} +0 -0
  204. /package/dist/{when-ready.js → render/when-ready.js} +0 -0
@@ -1,203 +0,0 @@
1
- import { Graph } from '@cosmos.gl/graph';
2
- import { RefObject } from 'react';
3
- import { BoundedSource, Slice } from './bounded';
4
- import { LookPatch } from './graph-looks';
5
- import { Resident, VertexId } from './resident';
6
- import { Sim } from './graph-sim';
7
- import { Motion } from './types';
8
- /**
9
- * Everything a graph is, above the element that draws it.
10
- *
11
- * **This is Ark's `useX(props) → api`, and the reason it exists is a host that could not adopt
12
- * `GraphCanvas`.** The component owned the renderer, the query loop and the look, and published them
13
- * through a context — which a legend or an inspector can read, because those are `children`. What
14
- * cannot read a context is anything that sits *above* the element: `useGraphOverlays` wants the api
15
- * itself, the `events` block wants the accessors on it, and both are arguments to a hook called in
16
- * the component that renders the canvas rather than inside it. The workspace needed them in thirty
17
- * places and so kept its own copy of all three.
18
- *
19
- * The general answer to that circularity is Ark's, and it is a shape rather than a feature: the
20
- * factory builds the api where the host can hold it, the provider takes it and renders. What the
21
- * component version tried instead — `graphRef` and `residentRef` as props, given rather than
22
- * returned — solved one host's version of it and is deleted by this file.
23
- *
24
- * **The structure is copied and the substance is not.** An Ark api's value is its prop getters,
25
- * which distribute props across many parts. Here there are no parts: a canvas is one element. So
26
- * there is no `getRootProps()` — `hostRef` is what the provider needs and all it needs — and
27
- * inventing one for the resemblance would be cargo cult. `CONVENTIONS.md` is where that line is
28
- * drawn, not here.
29
- */
30
- /**
31
- * The gestures, in the terms the rest of this package speaks.
32
- *
33
- * cosmos.gl reports a **buffer index**, which names a slot in the answer currently uploaded. Every
34
- * host then wrote the same three lines — resolve the index through the resident map, return early if
35
- * it resolves to nothing, carry on with the identity — because an index is not something you can
36
- * keep. The factory already holds that map, so it does the resolving and a callback that would have
37
- * been handed a stale slot is simply not called.
38
- *
39
- * `onZoom` is the one with a duty attached: the camera moving is how a bounded graph is re-asked,
40
- * and the loop issues that `refresh` itself. What arrives here is the notification, for a host that
41
- * has overlays to reposition.
42
- */
43
- export interface GraphEvents {
44
- onBackgroundClick?: () => void;
45
- onPointClick?: (vertex: VertexId, graph: Graph, index: number) => void;
46
- onPointerOver?: (vertex: VertexId, index: number) => void;
47
- onPointerOut?: () => void;
48
- onDragEnd?: (vertex: VertexId) => void;
49
- onTick?: () => void;
50
- onZoom?: () => void;
51
- }
52
- export interface UseGraphProps {
53
- /** What to draw. `null` renders the frame and asks nothing — a host still resolving its data. */
54
- source: BoundedSource | null;
55
- /**
56
- * Geometry only. Colour comes from the page's categorical scale, never from here.
57
- *
58
- * **One appearance input**, where there were two: a `Display` rode beside this with its own
59
- * defaults, spelling the edge layer, the backdrop and two multipliers over numbers the look
60
- * already computes. `lookFrom(values)` resolves the whole picture from the axes a person chose.
61
- *
62
- * **A patch, not a whole `Look` — which is what `DEFAULT_LOOK` was for.** This took the complete
63
- * object, so a host that wanted the vignette on wrote `{ ...DEFAULT_LOOK, vignette: true }` and
64
- * the package exported the default to make that expressible. That spread is a *copy*: the host
65
- * now holds every number this package chose, and stops tracking any of them the moment one moves
66
- * here. Passing `{ vignette: true }` says what the host decided and leaves the rest ours. `link`
67
- * merges one level down, so `{ link: { render: false } }` keeps the measured opacity and width.
68
- *
69
- * Memoise it, or pass a literal only when it does not change: the reference is what the buffers
70
- * are rebuilt on. Omitted entirely, it costs nothing — the shared default is returned by identity.
71
- */
72
- look?: LookPatch;
73
- /**
74
- * The force coefficients, as a patch over this package's own — `look`'s twin in every respect,
75
- * including why `DEFAULT_SIM` is no longer exported to spread from.
76
- */
77
- sim?: Partial<Sim>;
78
- /**
79
- * Off by default, and that is the correct default rather than a cautious one: a bounded source
80
- * hands back the coordinates its next spatial query is expressed in, so a force moves the picture
81
- * out from under its own index.
82
- */
83
- simulate?: boolean;
84
- clusters?: (number | undefined)[];
85
- /**
86
- * Which column colours a point, **or a CSS colour every point wears** — Plot's channel name,
87
- * Plot's meaning, and Plot's rule that a colour is a constant and anything else is a column.
88
- *
89
- * **A channel is what you want drawn, so it rides the question and not the source.** Baked into a
90
- * source at construction — which is where this used to live — changing what a graph is coloured by
91
- * meant building a second source, and *where the bytes are* and *what I want drawn* are not the
92
- * same question. Changing it here re-asks over the same bytes, which is all it should ever have
93
- * cost.
94
- *
95
- * As a *constant* it costs no query at all: `fill="var(--foreground)"` is the monochrome picture,
96
- * and it is a binding rather than a look, because a look may not rebind an encoding.
97
- *
98
- * Omitted, the source decides which column: it is the only thing that knows what its corpus
99
- * carries.
100
- */
101
- fill?: string;
102
- /**
103
- * Which column a point's **shape** carries — Plot's `symbol`, and a peer of colour.
104
- *
105
- * Bound to the same column as `fill` this is redundant encoding, which is the point on a colourful
106
- * document and the only encoding left on a monochrome one. It reads the categorical column the
107
- * slice already carries, so it costs no query; a *different* column would need a second categorical
108
- * array in the slice and in every source, and is not paid for.
109
- *
110
- * Binding it takes on an obligation — see `gradeComposition`: shape and size spent at once need a
111
- * radius floor, which is a property of what you composed rather than of the form you picked.
112
- */
113
- symbol?: string;
114
- /**
115
- * Which column the size ramp is spent on — Plot's `r`.
116
- *
117
- * Omitted, every point is drawn at one radius, which is a legitimate picture: without a ramp a
118
- * look's `form.size` range has only one end.
119
- */
120
- r?: string;
121
- /**
122
- * What tints a link. **Absent, each link takes the colour of the vertex it leaves**; a constant
123
- * like `var(--muted-foreground)` makes links plain structure.
124
- */
125
- stroke?: string;
126
- /** Vertices that stay drawn whatever the camera is over. */
127
- pinned?: VertexId[];
128
- limit?: number;
129
- debounce?: number;
130
- /**
131
- * Required, and the only one.
132
- *
133
- * Its silence is a defect rather than a choice: unhandled, a browser with no WebGL context shows
134
- * an empty box and says nothing, and what stands in its place is the host's decision.
135
- */
136
- onFailure: (message: string) => void;
137
- events?: GraphEvents;
138
- report?: (motion: Motion) => void;
139
- reportProgress?: (value: number) => void;
140
- /**
141
- * Ask the overlays to reposition after a look has been uploaded.
142
- *
143
- * Optional, and only owed to `useGraphOverlays`: point sizes changed, so labels sit differently,
144
- * and a look change does not tick — with a simulation off there is nothing to schedule a repaint
145
- * off. A host drawing no labels passes nothing.
146
- *
147
- * It goes through the props rather than being read off the api because `useGraphOverlays` is
148
- * declared *after* this hook — it takes what this hook returns. A host bridges the two with one
149
- * ref, which is the smallest honest answer to a cycle that is genuinely mutual: the overlays need
150
- * the graph, and the graph's repaint owes the overlays a nudge.
151
- */
152
- schedule?: () => void;
153
- }
154
- /**
155
- * What a graph publishes — to its own chrome through the provider, and to its host directly.
156
- *
157
- * Getters rather than values for the two that change every answer: a legend, an inspector and a
158
- * hover card all read the graph and the resident map from inside callbacks, and publishing them as
159
- * values would re-render every consumer on every camera move to hand back a reference they only
160
- * dereference when something is clicked. `slice` and `resident` ARE values, because a legend counts
161
- * what is drawn and has to re-render when that changes.
162
- *
163
- * **There is no `graphRef` here, and that is deliberate.** `getGraph()` answers every read of it,
164
- * and a ref object as well would be two ways to express one thing — with the second one writable,
165
- * which nothing outside `useRenderer` may be.
166
- */
167
- export interface GraphApi {
168
- /**
169
- * Where the canvas is drawn. `GraphRootProvider` attaches it; a host that renders its own surface
170
- * attaches it itself, and nothing works until something does.
171
- */
172
- hostRef: RefObject<HTMLDivElement | null>;
173
- /**
174
- * The renderer and the current map, read from inside a callback that was created once.
175
- *
176
- * **These two nearly left with `GraphOverlayOptions`, and the census is why they stayed.** The
177
- * reason they were on the api was that `useGraphOverlays` asked for them as a pair, so every host
178
- * copied them out of this object into that hook's argument — which is a re-statement, and that
179
- * hook takes the api now. What kept them is a different set of readers: `useGraphSelection` takes
180
- * both (its `commit` is a policy only a product can write, so it cannot be folded into the api the
181
- * way the overlays were), and a real consumer outside this repository reads `getGraph` and
182
- * `getResident` off `useGraphContext()` to paint its own mask over the buffers. Both are reads
183
- * from inside a callback registered once, which is exactly the case `slice` and `resident` cannot
184
- * serve — a value would re-render every consumer on every camera move to hand back a reference
185
- * they only dereference when something is clicked.
186
- */
187
- getGraph: () => Graph | null;
188
- getResident: () => Resident;
189
- /** The answer currently drawn, or `null` before the first one. */
190
- slice: Slice | null;
191
- /** Who is drawn and where, rebuilt with every answer — never build a second one. */
192
- resident: Resident;
193
- /** How many vertices there are, when the source knows. */
194
- total: number | undefined;
195
- /** Whether a question is outstanding. */
196
- pending: boolean;
197
- /** Whether this graph is asked in pieces at all, or fitted under the limit and taken whole. */
198
- sliced: boolean;
199
- /** Ask again about wherever the camera is now. Wired to the camera already. */
200
- refresh: () => void;
201
- }
202
- export declare function useGraph(props: UseGraphProps): GraphApi;
203
- //# sourceMappingURL=use-graph.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-graph.d.ts","sourceRoot":"","sources":["../src/use-graph.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAE5D,OAAO,EAAc,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAc,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAKtC;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,WAAW;IAC1B,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvE,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,iGAAiG;IACjG,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;OAGG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAClC;;;;;;;;;;;;;;;OAeG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,CAAC,CAAC,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,QAAQ;IACvB;;;OAGG;IACH,OAAO,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC1C;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,MAAM,KAAK,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,QAAQ,CAAC;IAC5B,kEAAkE;IAClE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,oFAAoF;IACpF,QAAQ,EAAE,QAAQ,CAAC;IACnB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,yCAAyC;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,+FAA+F;IAC/F,MAAM,EAAE,OAAO,CAAC;IAChB,+EAA+E;IAC/E,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAKD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,QAAQ,CAiJvD"}
package/dist/use-graph.js DELETED
@@ -1,102 +0,0 @@
1
- "use client";
2
- import { useMemo as l, useRef as c, useCallback as b } from "react";
3
- import { resolveLook as H } from "./graph-looks.js";
4
- import { isColour as I } from "./graph-model.js";
5
- import { residentOf as J } from "./resident.js";
6
- import { resolveSim as K } from "./graph-sim.js";
7
- import { useQueryLoop as U } from "./use-query-loop.js";
8
- import { useRenderer as V } from "./use-renderer.js";
9
- import { useGraphLook as W } from "./use-graph-look.js";
10
- const X = J(null);
11
- function cr(B) {
12
- const {
13
- clusters: D,
14
- debounce: E,
15
- events: p,
16
- fill: i,
17
- limit: G,
18
- look: d,
19
- onFailure: k,
20
- pinned: L,
21
- r: x,
22
- report: y,
23
- reportProgress: T,
24
- schedule: Z,
25
- sim: v,
26
- simulate: w = !1,
27
- source: F,
28
- stroke: h,
29
- symbol: m
30
- } = B, M = l(() => H(d), [d]), N = l(() => K(v), [v]), Q = I(i) ? m : i, S = l(() => ({ fill: i, stroke: h, symbol: m }), [i, h, m]), u = c(null), f = c(null), s = c(X), { pending: Y, refresh: a, resident: g, slice: P, sliced: j, total: q } = U({
31
- debounce: E,
32
- fill: Q,
33
- graphRef: f,
34
- hostRef: u,
35
- limit: G,
36
- onError: k,
37
- pinned: L,
38
- r: x,
39
- source: F
40
- });
41
- s.current = g;
42
- const O = b(() => f.current, []), z = b(() => s.current, []), t = c(p);
43
- t.current = p;
44
- const R = c(a);
45
- R.current = a;
46
- const A = l(
47
- () => ({
48
- onBackgroundClick: () => {
49
- var r, o;
50
- return (o = (r = t.current) == null ? void 0 : r.onBackgroundClick) == null ? void 0 : o.call(r);
51
- },
52
- onDragEnd: (r) => {
53
- var e, n;
54
- const o = s.current.at(r);
55
- o !== void 0 && ((n = (e = t.current) == null ? void 0 : e.onDragEnd) == null || n.call(e, o));
56
- },
57
- onPointClick: (r, o) => {
58
- var n, C;
59
- const e = s.current.at(o);
60
- e !== void 0 && ((C = (n = t.current) == null ? void 0 : n.onPointClick) == null || C.call(n, e, r, o));
61
- },
62
- onPointerOut: () => {
63
- var r, o;
64
- return (o = (r = t.current) == null ? void 0 : r.onPointerOut) == null ? void 0 : o.call(r);
65
- },
66
- onPointerOver: (r) => {
67
- var e, n;
68
- const o = s.current.at(r);
69
- o !== void 0 && ((n = (e = t.current) == null ? void 0 : e.onPointerOver) == null || n.call(e, o, r));
70
- },
71
- onTick: () => {
72
- var r, o;
73
- return (o = (r = t.current) == null ? void 0 : r.onTick) == null ? void 0 : o.call(r);
74
- },
75
- // The camera moved, so the graph is re-asked. A host that forgot this line got a canvas that
76
- // drew its first answer and never asked again — which is what `refresh` being the host's
77
- // responsibility used to cost.
78
- onZoom: () => {
79
- var r, o;
80
- R.current(), (o = (r = t.current) == null ? void 0 : r.onZoom) == null || o.call(r);
81
- }
82
- }),
83
- // Built once: every reference inside is a ref this hook owns, so there is nothing to depend on.
84
- // It used to close over a ref object a host could substitute, which is the prop this file deletes.
85
- []
86
- );
87
- return V({
88
- clusters: D,
89
- events: A,
90
- graphRef: f,
91
- hostRef: u,
92
- onFailure: k,
93
- report: y,
94
- reportProgress: T,
95
- sim: N,
96
- simulate: w
97
- }), W({ channels: S, getGraph: O, hostRef: u, look: M, schedule: Z, slice: P }), { getGraph: O, getResident: z, hostRef: u, pending: Y, refresh: a, resident: g, slice: P, sliced: j, total: q };
98
- }
99
- export {
100
- cr as useGraph
101
- };
102
- //# sourceMappingURL=use-graph.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-graph.js","sources":["../src/use-graph.ts"],"sourcesContent":["\"use client\";\n\nimport type { Graph } from \"@cosmos.gl/graph\";\nimport { useCallback, useMemo, useRef, type RefObject } from \"react\";\nimport type { BoundedSource, Slice } from \"./bounded\";\nimport { resolveLook, type LookPatch } from \"./graph-looks\";\nimport { isColour, type Channels } from \"./graph-model\";\nimport { residentOf, type Resident, type VertexId } from \"./resident\";\nimport { resolveSim, type Sim } from \"./graph-sim\";\nimport type { Motion } from \"./types\";\nimport { useQueryLoop } from \"./use-query-loop\";\nimport { useRenderer, type RendererOptions } from \"./use-renderer\";\nimport { useGraphLook } from \"./use-graph-look\";\n\n/**\n * Everything a graph is, above the element that draws it.\n *\n * **This is Ark's `useX(props) → api`, and the reason it exists is a host that could not adopt\n * `GraphCanvas`.** The component owned the renderer, the query loop and the look, and published them\n * through a context — which a legend or an inspector can read, because those are `children`. What\n * cannot read a context is anything that sits *above* the element: `useGraphOverlays` wants the api\n * itself, the `events` block wants the accessors on it, and both are arguments to a hook called in\n * the component that renders the canvas rather than inside it. The workspace needed them in thirty\n * places and so kept its own copy of all three.\n *\n * The general answer to that circularity is Ark's, and it is a shape rather than a feature: the\n * factory builds the api where the host can hold it, the provider takes it and renders. What the\n * component version tried instead — `graphRef` and `residentRef` as props, given rather than\n * returned — solved one host's version of it and is deleted by this file.\n *\n * **The structure is copied and the substance is not.** An Ark api's value is its prop getters,\n * which distribute props across many parts. Here there are no parts: a canvas is one element. So\n * there is no `getRootProps()` — `hostRef` is what the provider needs and all it needs — and\n * inventing one for the resemblance would be cargo cult. `CONVENTIONS.md` is where that line is\n * drawn, not here.\n */\n\n/**\n * The gestures, in the terms the rest of this package speaks.\n *\n * cosmos.gl reports a **buffer index**, which names a slot in the answer currently uploaded. Every\n * host then wrote the same three lines — resolve the index through the resident map, return early if\n * it resolves to nothing, carry on with the identity — because an index is not something you can\n * keep. The factory already holds that map, so it does the resolving and a callback that would have\n * been handed a stale slot is simply not called.\n *\n * `onZoom` is the one with a duty attached: the camera moving is how a bounded graph is re-asked,\n * and the loop issues that `refresh` itself. What arrives here is the notification, for a host that\n * has overlays to reposition.\n */\nexport interface GraphEvents {\n onBackgroundClick?: () => void;\n onPointClick?: (vertex: VertexId, graph: Graph, index: number) => void;\n onPointerOver?: (vertex: VertexId, index: number) => void;\n onPointerOut?: () => void;\n onDragEnd?: (vertex: VertexId) => void;\n onTick?: () => void;\n onZoom?: () => void;\n}\n\nexport interface UseGraphProps {\n /** What to draw. `null` renders the frame and asks nothing — a host still resolving its data. */\n source: BoundedSource | null;\n /**\n * Geometry only. Colour comes from the page's categorical scale, never from here.\n *\n * **One appearance input**, where there were two: a `Display` rode beside this with its own\n * defaults, spelling the edge layer, the backdrop and two multipliers over numbers the look\n * already computes. `lookFrom(values)` resolves the whole picture from the axes a person chose.\n *\n * **A patch, not a whole `Look` — which is what `DEFAULT_LOOK` was for.** This took the complete\n * object, so a host that wanted the vignette on wrote `{ ...DEFAULT_LOOK, vignette: true }` and\n * the package exported the default to make that expressible. That spread is a *copy*: the host\n * now holds every number this package chose, and stops tracking any of them the moment one moves\n * here. Passing `{ vignette: true }` says what the host decided and leaves the rest ours. `link`\n * merges one level down, so `{ link: { render: false } }` keeps the measured opacity and width.\n *\n * Memoise it, or pass a literal only when it does not change: the reference is what the buffers\n * are rebuilt on. Omitted entirely, it costs nothing — the shared default is returned by identity.\n */\n look?: LookPatch;\n /**\n * The force coefficients, as a patch over this package's own — `look`'s twin in every respect,\n * including why `DEFAULT_SIM` is no longer exported to spread from.\n */\n sim?: Partial<Sim>;\n /**\n * Off by default, and that is the correct default rather than a cautious one: a bounded source\n * hands back the coordinates its next spatial query is expressed in, so a force moves the picture\n * out from under its own index.\n */\n simulate?: boolean;\n clusters?: (number | undefined)[];\n /**\n * Which column colours a point, **or a CSS colour every point wears** — Plot's channel name,\n * Plot's meaning, and Plot's rule that a colour is a constant and anything else is a column.\n *\n * **A channel is what you want drawn, so it rides the question and not the source.** Baked into a\n * source at construction — which is where this used to live — changing what a graph is coloured by\n * meant building a second source, and *where the bytes are* and *what I want drawn* are not the\n * same question. Changing it here re-asks over the same bytes, which is all it should ever have\n * cost.\n *\n * As a *constant* it costs no query at all: `fill=\"var(--foreground)\"` is the monochrome picture,\n * and it is a binding rather than a look, because a look may not rebind an encoding.\n *\n * Omitted, the source decides which column: it is the only thing that knows what its corpus\n * carries.\n */\n fill?: string;\n /**\n * Which column a point's **shape** carries — Plot's `symbol`, and a peer of colour.\n *\n * Bound to the same column as `fill` this is redundant encoding, which is the point on a colourful\n * document and the only encoding left on a monochrome one. It reads the categorical column the\n * slice already carries, so it costs no query; a *different* column would need a second categorical\n * array in the slice and in every source, and is not paid for.\n *\n * Binding it takes on an obligation — see `gradeComposition`: shape and size spent at once need a\n * radius floor, which is a property of what you composed rather than of the form you picked.\n */\n symbol?: string;\n /**\n * Which column the size ramp is spent on — Plot's `r`.\n *\n * Omitted, every point is drawn at one radius, which is a legitimate picture: without a ramp a\n * look's `form.size` range has only one end.\n */\n r?: string;\n /**\n * What tints a link. **Absent, each link takes the colour of the vertex it leaves**; a constant\n * like `var(--muted-foreground)` makes links plain structure.\n */\n stroke?: string;\n /** Vertices that stay drawn whatever the camera is over. */\n pinned?: VertexId[];\n limit?: number;\n debounce?: number;\n /**\n * Required, and the only one.\n *\n * Its silence is a defect rather than a choice: unhandled, a browser with no WebGL context shows\n * an empty box and says nothing, and what stands in its place is the host's decision.\n */\n onFailure: (message: string) => void;\n events?: GraphEvents;\n report?: (motion: Motion) => void;\n reportProgress?: (value: number) => void;\n /**\n * Ask the overlays to reposition after a look has been uploaded.\n *\n * Optional, and only owed to `useGraphOverlays`: point sizes changed, so labels sit differently,\n * and a look change does not tick — with a simulation off there is nothing to schedule a repaint\n * off. A host drawing no labels passes nothing.\n *\n * It goes through the props rather than being read off the api because `useGraphOverlays` is\n * declared *after* this hook — it takes what this hook returns. A host bridges the two with one\n * ref, which is the smallest honest answer to a cycle that is genuinely mutual: the overlays need\n * the graph, and the graph's repaint owes the overlays a nudge.\n */\n schedule?: () => void;\n}\n\n/**\n * What a graph publishes — to its own chrome through the provider, and to its host directly.\n *\n * Getters rather than values for the two that change every answer: a legend, an inspector and a\n * hover card all read the graph and the resident map from inside callbacks, and publishing them as\n * values would re-render every consumer on every camera move to hand back a reference they only\n * dereference when something is clicked. `slice` and `resident` ARE values, because a legend counts\n * what is drawn and has to re-render when that changes.\n *\n * **There is no `graphRef` here, and that is deliberate.** `getGraph()` answers every read of it,\n * and a ref object as well would be two ways to express one thing — with the second one writable,\n * which nothing outside `useRenderer` may be.\n */\nexport interface GraphApi {\n /**\n * Where the canvas is drawn. `GraphRootProvider` attaches it; a host that renders its own surface\n * attaches it itself, and nothing works until something does.\n */\n hostRef: RefObject<HTMLDivElement | null>;\n /**\n * The renderer and the current map, read from inside a callback that was created once.\n *\n * **These two nearly left with `GraphOverlayOptions`, and the census is why they stayed.** The\n * reason they were on the api was that `useGraphOverlays` asked for them as a pair, so every host\n * copied them out of this object into that hook's argument — which is a re-statement, and that\n * hook takes the api now. What kept them is a different set of readers: `useGraphSelection` takes\n * both (its `commit` is a policy only a product can write, so it cannot be folded into the api the\n * way the overlays were), and a real consumer outside this repository reads `getGraph` and\n * `getResident` off `useGraphContext()` to paint its own mask over the buffers. Both are reads\n * from inside a callback registered once, which is exactly the case `slice` and `resident` cannot\n * serve — a value would re-render every consumer on every camera move to hand back a reference\n * they only dereference when something is clicked.\n */\n getGraph: () => Graph | null;\n getResident: () => Resident;\n /** The answer currently drawn, or `null` before the first one. */\n slice: Slice | null;\n /** Who is drawn and where, rebuilt with every answer — never build a second one. */\n resident: Resident;\n /** How many vertices there are, when the source knows. */\n total: number | undefined;\n /** Whether a question is outstanding. */\n pending: boolean;\n /** Whether this graph is asked in pieces at all, or fitted under the limit and taken whole. */\n sliced: boolean;\n /** Ask again about wherever the camera is now. Wired to the camera already. */\n refresh: () => void;\n}\n\n/** The empty answer, built once — a ref has to hold something before the first slice arrives. */\nconst NOBODY = residentOf(null);\n\nexport function useGraph(props: UseGraphProps): GraphApi {\n const {\n clusters,\n debounce,\n events,\n fill,\n limit,\n look: lookPatch,\n onFailure,\n pinned,\n r,\n report,\n reportProgress,\n schedule,\n sim: simPatch,\n simulate = false,\n source,\n stroke,\n symbol,\n } = props;\n\n /**\n * The patch, merged with what this package chose — once per change of the patch, not per render.\n *\n * The memo is the whole reason this is not inlined. Both resolved values are compared **by\n * reference** downstream: the look's identity is what `useGraphLook` rebuilds the GPU buffers on,\n * and the sim's is what `useRenderer` tests before putting energy back into a settled layout. A\n * merge on every render would upload the same colours sixty times a second and re-heat a graph\n * nobody had touched.\n */\n const look = useMemo(() => resolveLook(lookPatch), [lookPatch]);\n const sim = useMemo(() => resolveSim(simPatch), [simPatch]);\n\n /**\n * The two destinations one vocabulary splits into — and the one column they share.\n *\n * A slice carries **one** categorical array, so the query has one column to fetch and the question\n * is which binding names it: `fill` when it is a column, and `symbol` when `fill` is a constant.\n * That is not a fallback, it is the monochrome picture stated exactly — identity has moved to\n * shape, so the column identity lives in is the one `symbol` names.\n *\n * Getting this wrong is not a silent failure, which is the one good thing about it: with `fill` a\n * constant and nothing put in its place, the source fell back to its own default column and DuckDB\n * answered `Referenced column \"community\" not found`. The browser said so on the first Ink render.\n *\n * `stroke` never reaches a query at all — a link's tint is a decision about drawing.\n */\n const asked = isColour(fill) ? symbol : fill;\n const channels = useMemo<Channels>(() => ({ fill, stroke, symbol }), [fill, stroke, symbol]);\n\n const hostRef = useRef<HTMLDivElement>(null);\n const graphRef = useRef<Graph | null>(null);\n const residentRef = useRef<Resident>(NOBODY);\n\n const { pending, refresh, resident, slice, sliced, total } = useQueryLoop({\n debounce,\n fill: asked,\n graphRef,\n hostRef,\n limit,\n onError: onFailure,\n pinned,\n r,\n source,\n });\n\n /**\n * The resident map, readable from a callback that was created once.\n *\n * Written on every render rather than through an effect: a click handler registered with cosmos.gl\n * at construction reads this on the next gesture, and an effect would leave one frame where the map\n * describes buffers that are no longer on screen.\n */\n residentRef.current = resident;\n\n const getGraph = useCallback(() => graphRef.current, []);\n const getResident = useCallback(() => residentRef.current, []);\n\n /**\n * The host's handlers, read through a ref.\n *\n * cosmos.gl takes its callbacks once, at construction, so a fresh `events` object per render would\n * either be ignored or force a rebuild of the WebGL context. The ref is what lets a host pass an\n * inline object without either.\n */\n const live = useRef(events);\n live.current = events;\n\n const refreshRef = useRef(refresh);\n refreshRef.current = refresh;\n\n const wired = useMemo<RendererOptions[\"events\"]>(\n () => ({\n onBackgroundClick: () => live.current?.onBackgroundClick?.(),\n onDragEnd: (index) => {\n const vertex = residentRef.current.at(index);\n if (vertex !== undefined) live.current?.onDragEnd?.(vertex);\n },\n onPointClick: (graph, index) => {\n const vertex = residentRef.current.at(index);\n if (vertex !== undefined) live.current?.onPointClick?.(vertex, graph, index);\n },\n onPointerOut: () => live.current?.onPointerOut?.(),\n onPointerOver: (index) => {\n const vertex = residentRef.current.at(index);\n if (vertex !== undefined) live.current?.onPointerOver?.(vertex, index);\n },\n onTick: () => live.current?.onTick?.(),\n // The camera moved, so the graph is re-asked. A host that forgot this line got a canvas that\n // drew its first answer and never asked again — which is what `refresh` being the host's\n // responsibility used to cost.\n onZoom: () => {\n refreshRef.current();\n live.current?.onZoom?.();\n },\n }),\n // Built once: every reference inside is a ref this hook owns, so there is nothing to depend on.\n // It used to close over a ref object a host could substitute, which is the prop this file deletes.\n [],\n );\n\n /**\n * The renderer, built into an element this hook does not render.\n *\n * That is sound and not a gamble: React attaches refs during the commit phase, before any effect\n * runs, so by the time this effect fires `hostRef.current` is whatever the provider mounted —\n * including when the provider is a child component, since child refs are attached in the same\n * commit. A host that calls `useGraph` and renders no surface at all gets a hook that returns\n * early, forever, which is the honest outcome for a graph with nowhere to go.\n */\n useRenderer({\n clusters,\n events: wired,\n graphRef,\n hostRef,\n onFailure,\n report,\n reportProgress,\n sim,\n simulate,\n });\n\n useGraphLook({ channels, getGraph, hostRef, look, schedule, slice });\n\n return { getGraph, getResident, hostRef, pending, refresh, resident, slice, sliced, total };\n}\n"],"names":[],"mappings":";;;;;;;;;AAqNA;AAEO;AACL;AAAM;AACJ;AACA;AACA;AACA;AACA;AACM;AACN;AACA;AACA;AACA;AACA;AACA;AACK;AACM;AACX;AACA;AACA;AAoCwE;AACxE;AACM;AACN;AACA;AACA;AACS;AACT;AACA;AACA;AAUF;AAEA;AAWA;AAEA;AACA;AAEA;AAAc;AACL;;AACoB;AAAA;AAAA;;AAEvB;AACA;AAAoD;AACtD;;AAEE;AACA;AAAsE;AACxE;;AACoB;AAAA;AAAA;;AAElB;AACA;AAAgE;AAClE;;AACc;AAAA;AAAA;AAAA;AAAA;AAAA;;AAKZ;AACA;AACF;AAAA;AAAA;AAAA;AAIF;AAYF;AAAY;AACV;AACQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AAMJ;;;;"}
@@ -1,88 +0,0 @@
1
- import { RefObject } from 'react';
2
- import { Graph } from '@cosmos.gl/graph';
3
- import { BoundedSource, Slice } from './bounded';
4
- import { Resident, VertexId } from './resident';
5
- /**
6
- * The query loop: the camera moves, a bounded question is asked, the answer becomes the picture.
7
- *
8
- * This is the half the engine rule requires of anything with an engine — the canvas stays presentational
9
- * and this owns the asking. It debounces, cancels what the camera has already superseded, and pushes
10
- * each answer's geometry into the renderer. There is one source and it is `openCorpus`; this loop is
11
- * written against the contract rather than against it, which is what keeps a second one possible.
12
- *
13
- * **A graph that fits pays for nothing.** `total()` is asked first, and under the limit the source is
14
- * asked once for everything and never again — panning is then free exactly when it can be. Above it,
15
- * every camera move costs a query, which at 200,000 nodes is the trade that buys the ceiling away.
16
- *
17
- * **It is also where the question is finished.** A host's `limit` is optional and `minLinkPixels` is
18
- * not a host's business at all, and both are **required** on the `SliceRequest` a source receives —
19
- * resolved here, once, from `DEFAULT_LIMIT` and `DEFAULT_MIN_LINK_PIXELS`. They used to reach a
20
- * source as an exported `BOUNDED_DEFAULTS` table it looked them up in, which made a request
21
- * something a source had to *complete* rather than answer, in its own words, in two repositories.
22
- */
23
- export interface QueryLoopOptions {
24
- source: BoundedSource | null;
25
- /** The live renderer, for reading the camera and receiving each answer. */
26
- graphRef: RefObject<Graph | null>;
27
- /** The element the canvas is drawn into — its box is the screen rectangle. */
28
- hostRef: RefObject<HTMLElement | null>;
29
- /**
30
- * Vertices that must come back whatever the camera is looking at.
31
- *
32
- * Dragged, pinned, selected. Their drawn positions are a view-local overlay on coordinates that
33
- * never move, so the index cannot find them where they now appear.
34
- */
35
- pinned?: VertexId[];
36
- /**
37
- * Which column colours a point and which the size ramp is spent on — Plot's channel names.
38
- *
39
- * They arrive here rather than being baked into the source because a channel is part of the
40
- * **question**: colouring by another column is a new answer over the same bytes, and a source that
41
- * held them meant building a second source to change a colour. So this loop asks again when one
42
- * changes, which is the whole behaviour the move buys.
43
- *
44
- * Both optional, and the source decides what an omitted one means — it is the only thing that
45
- * knows what its corpus carries.
46
- */
47
- fill?: string;
48
- r?: string;
49
- limit?: number;
50
- /**
51
- * How long the camera has to be still before asking, in milliseconds.
52
- *
53
- * A pan is a stream of `onZoom` callbacks and every one of them would otherwise be a query. Long
54
- * enough that a gesture costs one question rather than sixty; short enough that letting go feels
55
- * like it answered immediately.
56
- */
57
- debounce?: number;
58
- onError?: (message: string) => void;
59
- }
60
- export interface QueryLoopState {
61
- /** The answer currently drawn, or `null` before the first one. */
62
- slice: Slice | null;
63
- /**
64
- * Who is drawn, and where — rebuilt with every answer, which is what makes it correct.
65
- *
66
- * It lives here rather than in each hook because **this is where residency changes**. The map is
67
- * a function of the current answer and nothing else, so a consumer that built its own would be
68
- * building the same thing from the same input, one render later, with no way to notice it had
69
- * fallen behind the buffers on screen. Selection, overlays, pins and the greyout all read this
70
- * one.
71
- */
72
- resident: Resident;
73
- /** Whether a question is outstanding. */
74
- pending: boolean;
75
- /** How many vertices there are, when the source knows. */
76
- total: number | undefined;
77
- /**
78
- * Whether this graph is being asked in pieces at all.
79
- *
80
- * `false` means it fit under the limit and was taken whole — the camera is not observed, and
81
- * nothing is asked again.
82
- */
83
- sliced: boolean;
84
- /** Ask again. Wire it to the camera, and call it when the pinned set changes. */
85
- refresh: () => void;
86
- }
87
- export declare function useQueryLoop(options: QueryLoopOptions): QueryLoopState;
88
- //# sourceMappingURL=use-query-loop.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-query-loop.d.ts","sourceRoot":"","sources":["../src/use-query-loop.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC1F,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,KAAK,EAEX,MAAM,WAAW,CAAC;AACnB,OAAO,EAAc,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtE;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B,2EAA2E;IAC3E,QAAQ,EAAE,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAClC,8EAA8E;IAC9E,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACvC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB;;;;;;;;OAQG;IACH,QAAQ,EAAE,QAAQ,CAAC;IACnB,yCAAyC;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B;;;;;OAKG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB,iFAAiF;IACjF,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAwCD,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,cAAc,CA4StE"}
@@ -1,148 +0,0 @@
1
- "use client";
2
- import { useState as y, useRef as d, useCallback as v, useEffect as N, useMemo as G } from "react";
3
- import { isAbort as U, DEFAULT_MIN_LINK_PIXELS as C, DEFAULT_LIMIT as z, shouldSlice as H } from "./bounded.js";
4
- import { residentOf as K } from "./resident.js";
5
- import { whenReady as Q } from "./when-ready.js";
6
- const X = {
7
- xMin: Number.NEGATIVE_INFINITY,
8
- yMin: Number.NEGATIVE_INFINITY,
9
- xMax: Number.POSITIVE_INFINITY,
10
- yMax: Number.POSITIVE_INFINITY
11
- };
12
- function j(T, p) {
13
- const { height: f, width: i } = p.getBoundingClientRect(), [M, o] = T.screenToSpacePosition([0, 0]), [m, I] = T.screenToSpacePosition([i, f]), a = {
14
- xMin: Math.min(M, m),
15
- yMin: Math.min(o, I),
16
- xMax: Math.max(M, m),
17
- yMax: Math.max(o, I)
18
- }, e = i > 0 ? (a.xMax - a.xMin) / i : 0;
19
- return { view: a, perPixel: e };
20
- }
21
- function tt(T) {
22
- const {
23
- debounce: p = q,
24
- fill: f,
25
- graphRef: i,
26
- hostRef: M,
27
- limit: o = z,
28
- onError: m,
29
- pinned: I,
30
- r: a,
31
- source: e
32
- } = T, [u, g] = y(null), [R, L] = y(!1), [Y, _] = y(void 0), [O, B] = y(!1), [F, V] = y(null), h = d(null), x = d(null), w = d(!1), E = d(I);
33
- E.current = I;
34
- const l = d(m);
35
- l.current = m;
36
- const b = v(
37
- async (n) => {
38
- var r, c;
39
- if (!e) return;
40
- (r = h.current) == null || r.abort();
41
- const t = new AbortController();
42
- h.current = t, L(!0);
43
- try {
44
- const s = await n(e, t.signal);
45
- if (t.signal.aborted) return;
46
- g(s);
47
- } catch (s) {
48
- if (t.signal.aborted || U(s)) return;
49
- (c = l.current) == null || c.call(l, String(s));
50
- } finally {
51
- h.current === t && (h.current = null, L(!1));
52
- }
53
- },
54
- [e]
55
- ), k = d(null), A = v(
56
- async (n) => {
57
- var s;
58
- if (!n.extent || k.current === n) return;
59
- k.current = n;
60
- let t;
61
- try {
62
- t = await n.extent();
63
- } catch (S) {
64
- (s = l.current) == null || s.call(l, String(S));
65
- return;
66
- }
67
- const r = i.current;
68
- if (!r) return;
69
- const c = await r.ready.then(() => r);
70
- c.setConfigPartial({ spaceSize: Math.max(t.xMax - t.xMin, t.yMax - t.yMin) }), c.fitViewByPointPositions([t.xMin, t.yMin, t.xMax, t.yMax], 0);
71
- },
72
- [i]
73
- ), P = v(() => {
74
- w.current && (x.current && clearTimeout(x.current), x.current = setTimeout(() => {
75
- x.current = null;
76
- const n = i.current, t = M.current;
77
- if (!n || !t) return;
78
- const { perPixel: r, view: c } = j(n, t);
79
- b(
80
- (s, S) => s.slice({
81
- view: c,
82
- perPixel: r,
83
- pinned: E.current,
84
- fill: f,
85
- r: a,
86
- limit: o,
87
- minLinkPixels: C,
88
- signal: S
89
- })
90
- );
91
- }, p));
92
- }, [b, p, f, i, M, o, a]);
93
- N(() => {
94
- if (!e) {
95
- g(null), _(void 0), V(null);
96
- return;
97
- }
98
- let n = !0;
99
- return (async () => {
100
- var c;
101
- let t;
102
- try {
103
- t = await ((c = e.total) == null ? void 0 : c.call(e));
104
- } catch {
105
- }
106
- if (!n) return;
107
- _(t);
108
- const r = H(t, o);
109
- w.current = r, B(r), V(e);
110
- })(), () => {
111
- n = !1;
112
- };
113
- }, [o, e]), N(() => {
114
- !e || F !== e || (async () => (await A(e), w.current ? P() : await b(
115
- (n, t) => n.slice({
116
- view: X,
117
- pinned: E.current,
118
- fill: f,
119
- r: a,
120
- limit: o,
121
- minLinkPixels: C,
122
- signal: t
123
- })
124
- )))();
125
- }, [b, F, f, A, o, a, P, e]), N(() => {
126
- var n;
127
- return (n = e == null ? void 0 : e.watch) == null ? void 0 : n.call(e, g);
128
- }, [e]), N(
129
- () => () => {
130
- var n;
131
- x.current && clearTimeout(x.current), (n = h.current) == null || n.abort();
132
- },
133
- []
134
- ), N(() => {
135
- const n = i.current;
136
- if (!(!n || !u))
137
- return Q(n, (t) => {
138
- t.setPointPositions(u.positions), t.setLinks(u.links), t.render();
139
- });
140
- }, [i, u]);
141
- const D = G(() => K(u), [u]);
142
- return { slice: u, resident: D, pending: R, total: Y, sliced: O, refresh: P };
143
- }
144
- const q = 120;
145
- export {
146
- tt as useQueryLoop
147
- };
148
- //# sourceMappingURL=use-query-loop.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-query-loop.js","sources":["../src/use-query-loop.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from \"react\";\nimport type { Graph } from \"@cosmos.gl/graph\";\nimport {\n DEFAULT_LIMIT,\n DEFAULT_MIN_LINK_PIXELS,\n isAbort,\n shouldSlice,\n type BoundedSource,\n type Slice,\n type Viewport,\n} from \"./bounded\";\nimport { residentOf, type Resident, type VertexId } from \"./resident\";\nimport { whenReady } from \"./when-ready\";\n\n/**\n * The query loop: the camera moves, a bounded question is asked, the answer becomes the picture.\n *\n * This is the half the engine rule requires of anything with an engine — the canvas stays presentational\n * and this owns the asking. It debounces, cancels what the camera has already superseded, and pushes\n * each answer's geometry into the renderer. There is one source and it is `openCorpus`; this loop is\n * written against the contract rather than against it, which is what keeps a second one possible.\n *\n * **A graph that fits pays for nothing.** `total()` is asked first, and under the limit the source is\n * asked once for everything and never again — panning is then free exactly when it can be. Above it,\n * every camera move costs a query, which at 200,000 nodes is the trade that buys the ceiling away.\n *\n * **It is also where the question is finished.** A host's `limit` is optional and `minLinkPixels` is\n * not a host's business at all, and both are **required** on the `SliceRequest` a source receives —\n * resolved here, once, from `DEFAULT_LIMIT` and `DEFAULT_MIN_LINK_PIXELS`. They used to reach a\n * source as an exported `BOUNDED_DEFAULTS` table it looked them up in, which made a request\n * something a source had to *complete* rather than answer, in its own words, in two repositories.\n */\n\nexport interface QueryLoopOptions {\n source: BoundedSource | null;\n /** The live renderer, for reading the camera and receiving each answer. */\n graphRef: RefObject<Graph | null>;\n /** The element the canvas is drawn into — its box is the screen rectangle. */\n hostRef: RefObject<HTMLElement | null>;\n /**\n * Vertices that must come back whatever the camera is looking at.\n *\n * Dragged, pinned, selected. Their drawn positions are a view-local overlay on coordinates that\n * never move, so the index cannot find them where they now appear.\n */\n pinned?: VertexId[];\n /**\n * Which column colours a point and which the size ramp is spent on — Plot's channel names.\n *\n * They arrive here rather than being baked into the source because a channel is part of the\n * **question**: colouring by another column is a new answer over the same bytes, and a source that\n * held them meant building a second source to change a colour. So this loop asks again when one\n * changes, which is the whole behaviour the move buys.\n *\n * Both optional, and the source decides what an omitted one means — it is the only thing that\n * knows what its corpus carries.\n */\n fill?: string;\n r?: string;\n limit?: number;\n /**\n * How long the camera has to be still before asking, in milliseconds.\n *\n * A pan is a stream of `onZoom` callbacks and every one of them would otherwise be a query. Long\n * enough that a gesture costs one question rather than sixty; short enough that letting go feels\n * like it answered immediately.\n */\n debounce?: number;\n onError?: (message: string) => void;\n}\n\nexport interface QueryLoopState {\n /** The answer currently drawn, or `null` before the first one. */\n slice: Slice | null;\n /**\n * Who is drawn, and where — rebuilt with every answer, which is what makes it correct.\n *\n * It lives here rather than in each hook because **this is where residency changes**. The map is\n * a function of the current answer and nothing else, so a consumer that built its own would be\n * building the same thing from the same input, one render later, with no way to notice it had\n * fallen behind the buffers on screen. Selection, overlays, pins and the greyout all read this\n * one.\n */\n resident: Resident;\n /** Whether a question is outstanding. */\n pending: boolean;\n /** How many vertices there are, when the source knows. */\n total: number | undefined;\n /**\n * Whether this graph is being asked in pieces at all.\n *\n * `false` means it fit under the limit and was taken whole — the camera is not observed, and\n * nothing is asked again.\n */\n sliced: boolean;\n /** Ask again. Wire it to the camera, and call it when the pinned set changes. */\n refresh: () => void;\n}\n\nconst EVERYTHING: Viewport = {\n xMin: Number.NEGATIVE_INFINITY,\n yMin: Number.NEGATIVE_INFINITY,\n xMax: Number.POSITIVE_INFINITY,\n yMax: Number.POSITIVE_INFINITY,\n};\n\n/**\n * What the camera is over, asked of the renderer rather than recomputed.\n *\n * cosmos.gl owns the screen↔space transform, so deriving the rectangle from `camera.x/y/k` and the\n * space size — which an earlier `viewportOf` did — is a second implementation of it, free to drift.\n * Screen y grows downward and space y does not, so the corners are sorted rather than assumed.\n */\nfunction cameraViewport(graph: Graph, host: HTMLElement): { view: Viewport; perPixel: number } {\n const { height, width } = host.getBoundingClientRect();\n const [ax, ay] = graph.screenToSpacePosition([0, 0]);\n const [bx, by] = graph.screenToSpacePosition([width, height]);\n const view = {\n xMin: Math.min(ax, bx),\n yMin: Math.min(ay, by),\n xMax: Math.max(ax, bx),\n yMax: Math.max(ay, by),\n };\n /**\n * How much space one pixel covers — asked of the same transform, not derived from the zoom.\n *\n * The rectangle came from mapping the host's own corners, so its width **is** `width` pixels of\n * space and the ratio is exact. Reading `camera.k` instead would be a second implementation of the\n * renderer's screen↔space maths, which is the mistake `viewportOf` already made once.\n *\n * Zero width — an unmounted or hidden host — gives no resolution rather than a division by zero,\n * and a source asked with none discards nothing.\n */\n const perPixel = width > 0 ? (view.xMax - view.xMin) / width : 0;\n return { view, perPixel };\n}\n\nexport function useQueryLoop(options: QueryLoopOptions): QueryLoopState {\n const {\n debounce = DEBOUNCE_MS,\n fill,\n graphRef,\n hostRef,\n limit = DEFAULT_LIMIT,\n onError,\n pinned,\n r,\n source,\n } = options;\n\n const [slice, setSlice] = useState<Slice | null>(null);\n const [pending, setPending] = useState(false);\n const [total, setTotal] = useState<number | undefined>(undefined);\n const [sliced, setSliced] = useState(false);\n /** The source `total()` has already answered for — the gate the asking effect waits on. */\n const [counted, setCounted] = useState<BoundedSource | null>(null);\n\n /**\n * The request in flight, and the timer waiting to become one.\n *\n * Refs rather than state: aborting a superseded request is bookkeeping the render output never\n * shows, and putting it in state would re-render the canvas once per camera frame to no effect.\n */\n const inFlight = useRef<AbortController | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n /** Whether the camera is being observed at all, where `refresh` can read it without a rebuild. */\n const slicing = useRef(false);\n /** The live pinned set, so the query loop is not rebuilt every time a node is dragged. */\n const held = useRef(pinned);\n held.current = pinned;\n const report = useRef(onError);\n report.current = onError;\n\n const ask = useCallback(\n async (run: (from: BoundedSource, signal: AbortSignal) => Promise<Slice>) => {\n if (!source) return;\n inFlight.current?.abort();\n const controller = new AbortController();\n inFlight.current = controller;\n setPending(true);\n try {\n const answer = await run(source, controller.signal);\n if (controller.signal.aborted) return;\n setSlice(answer);\n } catch (error) {\n /**\n * An abort is the loop working, not a failure: the camera moved on before the answer landed.\n *\n * Two halves, and they are two because only one of them is this loop's own doing. The first\n * is the controller above — this loop aborted the request itself, so the signal says so\n * without anything having been thrown. The second is the **source's** half: a source that\n * holds one standing question settles the one a newer caller replaced, and that rejection\n * arrives here for a request whose signal was never aborted at all.\n *\n * `isAbort` reads `error.name`, which is what makes a source written over `fetch` — or over\n * `AbortSignal.timeout`, or a stream — correct here without importing anything from us. It\n * was `isSuperseded(error)` against an exported `Symbol`, and that symbol was the reason a\n * source doing the standard thing was reported to the host as a failure.\n */\n if (controller.signal.aborted || isAbort(error)) return;\n report.current?.(String(error));\n } finally {\n if (inFlight.current === controller) {\n inFlight.current = null;\n setPending(false);\n }\n }\n },\n [source],\n );\n\n /**\n * Put the camera over the corpus, once, before the first question is asked.\n *\n * **The order is the point, and it is why this is not `fitView()` after the first slice.** A\n * sliced graph's first question is *what is the camera over*, so framing afterwards means the\n * opening query was asked about wherever the renderer happened to start — which for a corpus\n * occupying a corner of the space is a first paint of nothing, followed by a second query once the\n * fit moves the camera. Framing first costs one query instead of two and shows the corpus instead\n * of the default box.\n *\n * A source that cannot say its extent is left alone rather than guessed at: the camera stays where\n * the renderer put it, which is today's behaviour and is correct for arrays with no layout.\n *\n * Once, tracked on a ref, because this is the *opening* view: a reader who has panned somewhere\n * and then changes a channel is not asking to be sent home.\n */\n const framed = useRef<BoundedSource | null>(null);\n const frame = useCallback(\n async (from: BoundedSource) => {\n if (!from.extent || framed.current === from) return;\n framed.current = from;\n let box;\n try {\n box = await from.extent();\n } catch (error) {\n // **Framing may not stop the drawing.** This is a camera placement, and a source that cannot\n // answer where it is still has a slice to give — but the first version let the rejection\n // escape the chain the ask was waiting on, so a failed extent left the canvas saying \"asking\n // for what is in view\" forever, with nothing in the console. A defect that silences the whole\n // canvas has to be louder than the thing it was helping.\n report.current?.(String(error));\n return;\n }\n const graph = graphRef.current;\n if (!graph) return;\n // The quiet half of the race `whenReady` describes: a fit that arrives before the device is\n // not applied and does not complain, so the camera stays on the default box while the corpus\n // sits outside it. Awaited rather than wrapped because this function is already async and\n // already the one place that waits.\n const ready = await graph.ready.then(() => graph);\n /**\n * **The renderer's coordinate box, from the corpus rather than from a constant of ours.**\n *\n * This is the one place that already waits for an `extent()`, so it is the only place that can\n * set the box without asking twice. `spaceSize` is not one of the three fields cosmos.gl\n * treats as init-only — `initialZoomLevel`, `randomSeed`, `attribution` — so it takes effect\n * here; the config change calls `adjustSpaceSize` and re-syncs the screen scales.\n *\n * **Before the fit, not after.** Changing the box shifts the space→screen origin by half the\n * difference — measured 2.4 px on the million at its fitted zoom — so a set that follows the\n * fit slides the picture the reader was just given. The fit absorbs it in this order.\n *\n * A box past the device's `maxTextureDimension2D` is halved by cosmos.gl with a console line\n * of its own; that is left visible rather than clamped here, and `/docs/design/graph` says\n * why.\n *\n * The larger side, because the box is a square. There used to be an exported `SPACE = 4096`\n * declaring it, hand-copied into both bench generators, and a corpus fossil wrote ignored it:\n * a million vertices span about x ∈ [−345, 645396]. That was survivable only because\n * `spaceSize` enters every render path as a pure translation — it changed nothing anyone could\n * see, which is exactly why nothing caught it.\n */\n ready.setConfigPartial({ spaceSize: Math.max(box.xMax - box.xMin, box.yMax - box.yMin) });\n // Two corners are enough: cosmos.gl fits the bounding box of whatever positions it is handed,\n // and a rectangle is its own bounding box. Duration zero — an opening view that flies in from\n // the default box is animation for its own sake, and the reader has not asked for anything yet.\n ready.fitViewByPointPositions([box.xMin, box.yMin, box.xMax, box.yMax], 0);\n },\n [graphRef],\n );\n\n /**\n * Ask about wherever the camera is now, after `debounce` of stillness.\n *\n * Wired to the renderer's `onZoom`, which fires per frame of a gesture. The timer collapses a pan\n * into one question; `ask` aborts anything the pan has already made stale.\n */\n const refresh = useCallback(() => {\n // A graph that fits was answered whole, and re-asking would replace that answer with whatever\n // rectangle the camera happens to be over — which is how a corpus of 582 nodes ends up empty\n // because the reader zoomed. The promise that panning is free exactly when it can be is kept\n // here rather than by asking every call site to remember not to wire this up.\n if (!slicing.current) return;\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => {\n timer.current = null;\n const graph = graphRef.current;\n const host = hostRef.current;\n if (!graph || !host) return;\n const { perPixel, view } = cameraViewport(graph, host);\n void ask((s, signal) =>\n s.slice({\n view,\n perPixel,\n pinned: held.current,\n fill,\n r,\n limit,\n minLinkPixels: DEFAULT_MIN_LINK_PIXELS,\n signal,\n }),\n );\n }, debounce);\n // The channels are dependencies rather than a ref, unlike `pinned`: a new one is a new question\n // and the effect below re-runs this the moment its identity changes. A pin is a gesture the host\n // reports, and it says when to ask again itself.\n }, [ask, debounce, fill, graphRef, hostRef, limit, r]);\n\n /**\n * How big it is — asked once per source, and the answer decides whether there is a query loop at\n * all: under the limit one slice covers everything and the camera is never consulted again. A\n * source that cannot say cheaply is treated as large, because an unknown corpus is more likely to\n * be the kind that needs bounding than not.\n *\n * The *asking* is the effect below rather than the tail of this one, and the split is what lets a\n * channel change re-ask: how big a corpus is belongs to the source and does not change with what\n * you want drawn, so counting again on every colour would be paying a `count(*)` for a question\n * nobody asked.\n */\n useEffect(() => {\n if (!source) {\n setSlice(null);\n setTotal(undefined);\n setCounted(null);\n return;\n }\n let live = true;\n void (async () => {\n let count: number | undefined;\n try {\n count = await source.total?.();\n } catch {\n // A source that will not count is a source that gets sliced.\n }\n if (!live) return;\n setTotal(count);\n const bounded = shouldSlice(count, limit);\n slicing.current = bounded;\n setSliced(bounded);\n setCounted(source);\n })();\n return () => {\n live = false;\n };\n }, [limit, source]);\n\n /**\n * What to draw — the opening question, and every later one that is not the camera's.\n *\n * It runs when the count lands, and again whenever the question itself changes: a channel is part\n * of the question, so `refresh` and this both carry them and both re-run. The counted source is\n * compared rather than a boolean, so an answer that arrives for a source already replaced cannot\n * open a slice against the new one.\n */\n useEffect(() => {\n if (!source || counted !== source) return;\n void (async () => {\n await frame(source);\n if (slicing.current) refresh();\n else\n await ask((s, signal) =>\n s.slice({\n view: EVERYTHING,\n pinned: held.current,\n fill,\n r,\n limit,\n minLinkPixels: DEFAULT_MIN_LINK_PIXELS,\n signal,\n }),\n );\n })();\n }, [ask, counted, fill, frame, limit, r, refresh, source]);\n\n /**\n * The other way an answer arrives: the page filtered something.\n *\n * A camera move is a *pull* — the loop asks, because only the loop knows where the camera is. A\n * filter change is a **push**: the coordinator re-runs the source's reads with the new predicate\n * the way it re-runs a histogram's, and what lands here is the finished slice. Re-asking instead\n * would issue those queries a second time to learn what is already in hand.\n *\n * The disposer is also the release, which is why this is wired even when the source is between\n * renders: `watch` is where a source lets go of a client registration, and a loop that calls it is\n * a loop that cannot leak one.\n */\n useEffect(() => source?.watch?.(setSlice), [source]);\n\n // A dropped canvas must not leave a timer holding a stale camera, or a request nobody will read.\n useEffect(\n () => () => {\n if (timer.current) clearTimeout(timer.current);\n inFlight.current?.abort();\n },\n [],\n );\n\n /**\n * The answer becomes the picture.\n *\n * Separate from the effect that builds the renderer, which is the whole point: a slice arrives on\n * every camera move, and rebuilding the instance for each one would destroy and recreate a WebGL\n * context sixty times a pan. Geometry is pushed; the instance outlives every slice it draws.\n */\n useEffect(() => {\n const graph = graphRef.current;\n if (!graph || !slice) return;\n /**\n * **Behind `ready`, because a non-null instance is not a usable one.**\n *\n * `whenReady` carries the whole argument; the cancel is what matters here. Slices arrive\n * faster than a frame during a pan, and every one of them but the last is superseded before it\n * could have been drawn.\n */\n return whenReady(graph, (ready) => {\n ready.setPointPositions(slice.positions);\n ready.setLinks(slice.links);\n ready.render();\n });\n }, [graphRef, slice]);\n\n // Memoised on the answer, because a fresh map per render would make every consumer that depends on\n // it re-run for a value that had not changed.\n const resident = useMemo(() => residentOf(slice), [slice]);\n\n return { slice, resident, pending, total, sliced, refresh };\n}\n\n/**\n * The stillness a camera has to hold before it is asked about — 120 ms.\n *\n * Under a gesture's own frame budget it would be one query per frame; far above it and letting go of\n * a pan feels like the canvas is thinking. The measured slice at 200,000 nodes is 30 ms, so this is\n * the larger half of the latency a reader actually feels, and it is the half we chose.\n */\nconst DEBOUNCE_MS = 120;\n"],"names":[],"mappings":";;;;;AAqGA;AAA6B;AACd;AACA;AACA;AAEf;AASA;AACE;AAGa;AACU;AACA;AACA;AACA;AAavB;AACF;AAEO;AACL;AAAM;AACO;AACX;AACA;AACA;AACQ;AACR;AACA;AACA;AACA;AAsBF;AACA;AACA;AAEA;AAAY;;AAER;AACA;AACA;AACA;AAEA;AACE;AACA;AACA;AAAe;AAgBf;AACA;AAA6B;AAE7B;AAEkB;AAEpB;AACF;AACO;AAoBK;;AAEV;AACA;AACA;AACA;AACE;AAAiB;AAOjB;AACA;AAAA;AAEF;AACA;AAKA;AAuBA;AAIyE;AAC3E;AACS;AAcT;AAGE;AACA;AAEA;AACA;AACA;AAAK;AACK;AACN;AACA;AACa;AACb;AACA;AACA;AACe;AACf;AACD;AAAA;AAEM;AAiBb;AACE;AACE;AAGA;AAAA;AAEF;AACA;;AACE;AACA;AACE;AAAc;AACR;AAGR;AACA;AACA;AACA;AAEiB;AAGjB;AAAO;AACT;AAYA;AAKU;AACI;AACA;AACO;AACb;AACA;AACA;AACe;AACf;AACD;AAAA;;AAiBO;AAAgB;AAGhC;;AAEI;AACkB;AACpB;AACA;AAWA;AACA;AAQA;AACE;AAEM;AACP;AAKH;AAEA;AACF;AASA;;;;"}