insomni-plot 0.1.0-alpha.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 (242) hide show
  1. package/LICENSE.md +674 -0
  2. package/README.md +81 -0
  3. package/dist/core.d.mts +340 -0
  4. package/dist/core.mjs +1047 -0
  5. package/dist/index.d.mts +3426 -0
  6. package/dist/index.mjs +12762 -0
  7. package/dist/interactions-DEFL_F4E.mjs +5395 -0
  8. package/dist/range-presets-CzECsu3V.d.mts +1523 -0
  9. package/package.json +34 -0
  10. package/src/annotations.d.ts +121 -0
  11. package/src/annotations.ts +438 -0
  12. package/src/axis.d.ts +184 -0
  13. package/src/axis.test.ts +131 -0
  14. package/src/axis.ts +765 -0
  15. package/src/colorbar.d.ts +69 -0
  16. package/src/colorbar.ts +294 -0
  17. package/src/colors.d.ts +57 -0
  18. package/src/colors.test.ts +28 -0
  19. package/src/colors.ts +486 -0
  20. package/src/core.ts +299 -0
  21. package/src/format.d.ts +54 -0
  22. package/src/format.ts +138 -0
  23. package/src/grammar/accessibility.d.ts +147 -0
  24. package/src/grammar/accessibility.test.ts +199 -0
  25. package/src/grammar/accessibility.ts +443 -0
  26. package/src/grammar/aes.d.ts +35 -0
  27. package/src/grammar/aes.test.ts +75 -0
  28. package/src/grammar/aes.ts +120 -0
  29. package/src/grammar/annotations.d.ts +86 -0
  30. package/src/grammar/annotations.test.ts +68 -0
  31. package/src/grammar/annotations.ts +336 -0
  32. package/src/grammar/attach-brush.d.ts +44 -0
  33. package/src/grammar/attach-brush.test.ts +214 -0
  34. package/src/grammar/attach-brush.ts +111 -0
  35. package/src/grammar/attach-presets.d.ts +33 -0
  36. package/src/grammar/attach-presets.test.ts +106 -0
  37. package/src/grammar/attach-presets.ts +215 -0
  38. package/src/grammar/chart.d.ts +952 -0
  39. package/src/grammar/chart.test.ts +118 -0
  40. package/src/grammar/chart.ts +1172 -0
  41. package/src/grammar/color-utils.d.ts +29 -0
  42. package/src/grammar/color-utils.test.ts +53 -0
  43. package/src/grammar/color-utils.ts +66 -0
  44. package/src/grammar/constants.d.ts +45 -0
  45. package/src/grammar/constants.ts +61 -0
  46. package/src/grammar/coord.d.ts +183 -0
  47. package/src/grammar/coord.test.ts +355 -0
  48. package/src/grammar/coord.ts +619 -0
  49. package/src/grammar/data/pivot.d.ts +57 -0
  50. package/src/grammar/data/pivot.ts +107 -0
  51. package/src/grammar/emphasis-driver.d.ts +69 -0
  52. package/src/grammar/emphasis-driver.test.ts +199 -0
  53. package/src/grammar/emphasis-driver.ts +205 -0
  54. package/src/grammar/equality.d.ts +3 -0
  55. package/src/grammar/equality.ts +40 -0
  56. package/src/grammar/facet.d.ts +63 -0
  57. package/src/grammar/facet.test.ts +60 -0
  58. package/src/grammar/facet.ts +175 -0
  59. package/src/grammar/geoms/_categorical.d.ts +94 -0
  60. package/src/grammar/geoms/_categorical.ts +0 -0
  61. package/src/grammar/geoms/_distribution.d.ts +52 -0
  62. package/src/grammar/geoms/_distribution.ts +125 -0
  63. package/src/grammar/geoms/_mark.d.ts +69 -0
  64. package/src/grammar/geoms/_mark.ts +136 -0
  65. package/src/grammar/geoms/_shape.d.ts +41 -0
  66. package/src/grammar/geoms/_shape.ts +74 -0
  67. package/src/grammar/geoms/aggregate.d.ts +95 -0
  68. package/src/grammar/geoms/aggregate.test.ts +554 -0
  69. package/src/grammar/geoms/aggregate.ts +840 -0
  70. package/src/grammar/geoms/area.d.ts +32 -0
  71. package/src/grammar/geoms/area.test.ts +165 -0
  72. package/src/grammar/geoms/area.ts +578 -0
  73. package/src/grammar/geoms/band.d.ts +27 -0
  74. package/src/grammar/geoms/band.test.ts +57 -0
  75. package/src/grammar/geoms/band.ts +126 -0
  76. package/src/grammar/geoms/bar.d.ts +56 -0
  77. package/src/grammar/geoms/bar.test.ts +367 -0
  78. package/src/grammar/geoms/bar.ts +1054 -0
  79. package/src/grammar/geoms/boxplot.d.ts +129 -0
  80. package/src/grammar/geoms/boxplot.test.ts +299 -0
  81. package/src/grammar/geoms/boxplot.ts +834 -0
  82. package/src/grammar/geoms/connected-scatter.d.ts +27 -0
  83. package/src/grammar/geoms/connected-scatter.test.ts +157 -0
  84. package/src/grammar/geoms/connected-scatter.ts +63 -0
  85. package/src/grammar/geoms/emphasis.d.ts +76 -0
  86. package/src/grammar/geoms/emphasis.test.ts +135 -0
  87. package/src/grammar/geoms/emphasis.ts +162 -0
  88. package/src/grammar/geoms/histogram.d.ts +75 -0
  89. package/src/grammar/geoms/histogram.test.ts +262 -0
  90. package/src/grammar/geoms/histogram.ts +740 -0
  91. package/src/grammar/geoms/index.d.ts +20 -0
  92. package/src/grammar/geoms/index.ts +77 -0
  93. package/src/grammar/geoms/interval.d.ts +31 -0
  94. package/src/grammar/geoms/interval.test.ts +154 -0
  95. package/src/grammar/geoms/interval.ts +342 -0
  96. package/src/grammar/geoms/line.d.ts +38 -0
  97. package/src/grammar/geoms/line.test.ts +247 -0
  98. package/src/grammar/geoms/line.ts +659 -0
  99. package/src/grammar/geoms/point.d.ts +57 -0
  100. package/src/grammar/geoms/point.test.ts +163 -0
  101. package/src/grammar/geoms/point.ts +545 -0
  102. package/src/grammar/geoms/polar.test.ts +216 -0
  103. package/src/grammar/geoms/ribbon.d.ts +21 -0
  104. package/src/grammar/geoms/ribbon.test.ts +170 -0
  105. package/src/grammar/geoms/ribbon.ts +87 -0
  106. package/src/grammar/geoms/ridgeline.d.ts +89 -0
  107. package/src/grammar/geoms/ridgeline.test.ts +247 -0
  108. package/src/grammar/geoms/ridgeline.ts +1164 -0
  109. package/src/grammar/geoms/rolling.d.ts +43 -0
  110. package/src/grammar/geoms/rolling.test.ts +217 -0
  111. package/src/grammar/geoms/rolling.ts +387 -0
  112. package/src/grammar/geoms/rug.d.ts +28 -0
  113. package/src/grammar/geoms/rug.test.ts +126 -0
  114. package/src/grammar/geoms/rug.ts +214 -0
  115. package/src/grammar/geoms/rule.d.ts +23 -0
  116. package/src/grammar/geoms/rule.test.ts +69 -0
  117. package/src/grammar/geoms/rule.ts +212 -0
  118. package/src/grammar/geoms/smooth.d.ts +54 -0
  119. package/src/grammar/geoms/smooth.test.ts +78 -0
  120. package/src/grammar/geoms/smooth.ts +337 -0
  121. package/src/grammar/geoms/text.d.ts +29 -0
  122. package/src/grammar/geoms/text.test.ts +64 -0
  123. package/src/grammar/geoms/text.ts +234 -0
  124. package/src/grammar/geoms/tile.d.ts +61 -0
  125. package/src/grammar/geoms/tile.test.ts +157 -0
  126. package/src/grammar/geoms/tile.ts +621 -0
  127. package/src/grammar/geoms/types.d.ts +319 -0
  128. package/src/grammar/geoms/types.ts +362 -0
  129. package/src/grammar/geoms/violin.d.ts +85 -0
  130. package/src/grammar/geoms/violin.test.ts +187 -0
  131. package/src/grammar/geoms/violin.ts +672 -0
  132. package/src/grammar/index.d.ts +22 -0
  133. package/src/grammar/index.ts +269 -0
  134. package/src/grammar/interactions/_disposable.d.ts +5 -0
  135. package/src/grammar/interactions/_disposable.ts +23 -0
  136. package/src/grammar/interactions/_z.d.ts +4 -0
  137. package/src/grammar/interactions/_z.ts +16 -0
  138. package/src/grammar/interactions/brush-selection.test.ts +262 -0
  139. package/src/grammar/interactions/brush.d.ts +63 -0
  140. package/src/grammar/interactions/brush.test.ts +483 -0
  141. package/src/grammar/interactions/brush.ts +452 -0
  142. package/src/grammar/interactions/crosshair.d.ts +19 -0
  143. package/src/grammar/interactions/crosshair.test.ts +127 -0
  144. package/src/grammar/interactions/crosshair.ts +76 -0
  145. package/src/grammar/interactions/hit-layer.d.ts +64 -0
  146. package/src/grammar/interactions/hit-layer.ts +246 -0
  147. package/src/grammar/interactions/legend.d.ts +19 -0
  148. package/src/grammar/interactions/legend.ts +101 -0
  149. package/src/grammar/interactions/menu.d.ts +93 -0
  150. package/src/grammar/interactions/menu.test.ts +373 -0
  151. package/src/grammar/interactions/menu.ts +342 -0
  152. package/src/grammar/interactions/selection.d.ts +25 -0
  153. package/src/grammar/interactions/selection.test.ts +289 -0
  154. package/src/grammar/interactions/selection.ts +142 -0
  155. package/src/grammar/interactions/series-readout.d.ts +91 -0
  156. package/src/grammar/interactions/series-readout.test.ts +668 -0
  157. package/src/grammar/interactions/series-readout.ts +422 -0
  158. package/src/grammar/interactions/series-snap.d.ts +70 -0
  159. package/src/grammar/interactions/series-snap.test.ts +214 -0
  160. package/src/grammar/interactions/series-snap.ts +218 -0
  161. package/src/grammar/interactions/tooltip-axis.test.ts +176 -0
  162. package/src/grammar/interactions/tooltip-touch.browser.test.ts +49 -0
  163. package/src/grammar/interactions/tooltip-touch.test.ts +161 -0
  164. package/src/grammar/interactions/tooltip.d.ts +140 -0
  165. package/src/grammar/interactions/tooltip.test.ts +406 -0
  166. package/src/grammar/interactions/tooltip.ts +622 -0
  167. package/src/grammar/interactions/transitions.d.ts +34 -0
  168. package/src/grammar/interactions/transitions.test.ts +172 -0
  169. package/src/grammar/interactions/transitions.ts +160 -0
  170. package/src/grammar/layout.d.ts +68 -0
  171. package/src/grammar/layout.ts +186 -0
  172. package/src/grammar/legend-merge.test.ts +332 -0
  173. package/src/grammar/mount.d.ts +78 -0
  174. package/src/grammar/mount.test.ts +479 -0
  175. package/src/grammar/mount.ts +2112 -0
  176. package/src/grammar/palettes.d.ts +54 -0
  177. package/src/grammar/palettes.test.ts +80 -0
  178. package/src/grammar/palettes.ts +167 -0
  179. package/src/grammar/pan-zoom.test.ts +398 -0
  180. package/src/grammar/phylo.d.ts +65 -0
  181. package/src/grammar/phylo.test.ts +59 -0
  182. package/src/grammar/phylo.ts +112 -0
  183. package/src/grammar/pipeline.auto-ticks.test.ts +40 -0
  184. package/src/grammar/pipeline.d.ts +158 -0
  185. package/src/grammar/pipeline.test.ts +463 -0
  186. package/src/grammar/pipeline.ts +1233 -0
  187. package/src/grammar/profiling.d.ts +8 -0
  188. package/src/grammar/profiling.ts +24 -0
  189. package/src/grammar/scales.d.ts +188 -0
  190. package/src/grammar/scales.test.ts +181 -0
  191. package/src/grammar/scales.ts +800 -0
  192. package/src/grammar/svg.d.ts +3 -0
  193. package/src/grammar/svg.ts +39 -0
  194. package/src/grammar/theme.d.ts +261 -0
  195. package/src/grammar/theme.test.ts +105 -0
  196. package/src/grammar/theme.ts +490 -0
  197. package/src/heatmap/cpu.ts +109 -0
  198. package/src/heatmap/gpu.ts +565 -0
  199. package/src/heatmap/types.ts +177 -0
  200. package/src/heatmap.browser.test.ts +308 -0
  201. package/src/heatmap.test.ts +320 -0
  202. package/src/heatmap.ts +123 -0
  203. package/src/index.d.ts +1 -0
  204. package/src/index.ts +8 -0
  205. package/src/interactions.d.ts +48 -0
  206. package/src/interactions.test.ts +226 -0
  207. package/src/interactions.ts +394 -0
  208. package/src/layout/box.d.ts +48 -0
  209. package/src/layout/box.test.ts +107 -0
  210. package/src/layout/box.ts +143 -0
  211. package/src/legend.d.ts +115 -0
  212. package/src/legend.ts +422 -0
  213. package/src/marks/curve.d.ts +43 -0
  214. package/src/marks/curve.ts +244 -0
  215. package/src/marks/stack.d.ts +53 -0
  216. package/src/marks/stack.ts +184 -0
  217. package/src/marks.d.ts +273 -0
  218. package/src/marks.test.ts +541 -0
  219. package/src/marks.ts +1292 -0
  220. package/src/navigator.test.ts +174 -0
  221. package/src/navigator.ts +393 -0
  222. package/src/range-presets.d.ts +113 -0
  223. package/src/range-presets.test.ts +345 -0
  224. package/src/range-presets.ts +349 -0
  225. package/src/scales.d.ts +98 -0
  226. package/src/scales.test.ts +103 -0
  227. package/src/scales.ts +695 -0
  228. package/src/stats/index.d.ts +200 -0
  229. package/src/stats/index.test.ts +349 -0
  230. package/src/stats/index.ts +740 -0
  231. package/src/stats/regression.d.ts +38 -0
  232. package/src/stats/regression.test.ts +56 -0
  233. package/src/stats/regression.ts +396 -0
  234. package/src/stats/rolling-window.d.ts +55 -0
  235. package/src/stats/rolling-window.test.ts +237 -0
  236. package/src/stats/rolling-window.ts +256 -0
  237. package/src/test-setup.ts +19 -0
  238. package/src/viewport/axis-state.d.ts +72 -0
  239. package/src/viewport/axis-state.ts +476 -0
  240. package/src/viewport.d.ts +170 -0
  241. package/src/viewport.test.ts +363 -0
  242. package/src/viewport.ts +510 -0
@@ -0,0 +1,319 @@
1
+ import type { Color, Frame, GlyphAtlas, Layer } from "insomni";
2
+ import type { MarkBuilder } from "../../marks.ts";
3
+ import type { SwatchSpec } from "../../legend.ts";
4
+ import type { Aes, ResolvedAes } from "../aes.ts";
5
+ import type { AlphaScale, BorderStyleScale, ColorScale, OverlayGlyphScale, PositionScale, PositionScaleOptions, ShapeScale, SizeScale } from "../scales.ts";
6
+ import type { Theme } from "../theme.ts";
7
+ export type GeomKind = "point" | "line" | "area" | "bar" | "text" | "rule" | "band" | "boxplot" | "violin" | "histogram" | "ridgeline" | "rug" | "smooth" | "rolling" | "tile" | "interval";
8
+ /**
9
+ * Channel mappings as declared by the user. Stored at this width on `Geom<T>`
10
+ * so the chart can iterate channels generically. Geom factories accept their
11
+ * narrower per-geom channel types (PointChannels, LineChannels, ...) as the
12
+ * public surface and cast into this for storage.
13
+ */
14
+ export interface ChannelSpec<_T> {
15
+ x?: unknown;
16
+ y?: unknown;
17
+ color?: unknown;
18
+ size?: unknown;
19
+ shape?: unknown;
20
+ alpha?: unknown;
21
+ borderStyle?: unknown;
22
+ overlayGlyph?: unknown;
23
+ }
24
+ /**
25
+ * Per-geom hints for position-scale construction. Geoms that have a
26
+ * conventional baseline (bar, area) request that their value axis include 0
27
+ * — or, for `position: 'fill'`, set an explicit `[0, 1]` domain. Hints are
28
+ * only applied to numeric (linear/log/sqrt) scales; band/time scales ignore
29
+ * them. An explicit `.scale(channel, { domain })` override always wins.
30
+ */
31
+ export interface PositionScaleHint {
32
+ /** Force this exact domain (overrides include flags). */
33
+ domain?: readonly [number, number];
34
+ /** Extend inferred domain to include 0. */
35
+ includeZero?: boolean;
36
+ /** Extend inferred domain to include 1. */
37
+ includeOne?: boolean;
38
+ /**
39
+ * Extend the inferred domain to include this range. Unlike `domain`, this
40
+ * is unioned with the data-derived extent (and with other layers' extends)
41
+ * rather than replacing it. Use for geoms whose visual footprint extends
42
+ * past the raw data — e.g. a KDE-based violin whose smoothed tails reach
43
+ * outside `[dataMin, dataMax]`.
44
+ */
45
+ extend?: readonly [number, number];
46
+ }
47
+ export interface ScaleHints {
48
+ x?: PositionScaleHint;
49
+ y?: PositionScaleHint;
50
+ }
51
+ /**
52
+ * Pixel reservations on the plot frame for a geom whose visual extent
53
+ * exceeds its data footprint. The chart shrinks the position-scale range
54
+ * by these amounts so axis ticks track the compressed layout — used by
55
+ * ridgeline rows that rise above their baseline. Reservations from
56
+ * multiple layers are merged by max (the largest demand wins).
57
+ */
58
+ export interface RangeHints {
59
+ top?: number;
60
+ bottom?: number;
61
+ left?: number;
62
+ right?: number;
63
+ }
64
+ export interface PrepareRangeContext<T> {
65
+ data: readonly T[];
66
+ plot: Frame;
67
+ scaleOptions: {
68
+ x?: PositionScaleOptions;
69
+ y?: PositionScaleOptions;
70
+ };
71
+ theme: Theme;
72
+ atlas: GlyphAtlas | undefined;
73
+ }
74
+ export type AnyAes<T> = Aes<T, unknown>;
75
+ export interface ScaleBundle {
76
+ x: PositionScale;
77
+ y: PositionScale;
78
+ color?: ColorScale;
79
+ size?: SizeScale;
80
+ alpha?: AlphaScale;
81
+ shape?: ShapeScale;
82
+ borderStyle?: BorderStyleScale;
83
+ overlayGlyph?: OverlayGlyphScale;
84
+ }
85
+ /**
86
+ * Per-rendered-element channel snapshot used for animated transitions.
87
+ * For simple geoms this usually matches the source datum index; for stacked /
88
+ * grouped geoms it can be one entry per visible segment. NaN marks filtered /
89
+ * hidden entries. Each geom owns the index convention used by its compile path.
90
+ */
91
+ export interface GeomFrame {
92
+ count: number;
93
+ x: Float32Array;
94
+ y: Float32Array;
95
+ rgba: Float32Array;
96
+ a: Float32Array;
97
+ r?: Float32Array;
98
+ ids?: readonly string[];
99
+ }
100
+ /** Injected into CompileContext during an active data/scale transition. */
101
+ export interface ActiveTransition {
102
+ /** Progress 0 → 1. */
103
+ t: number;
104
+ /** From-snapshot for this geom. Indexed by datum i. */
105
+ from: GeomFrame;
106
+ /** Resolve a stable match index in `from` for the given key. */
107
+ matchIndex(key: string, fallbackIndex?: number): number | undefined;
108
+ }
109
+ export interface CompileContext<T> {
110
+ data: readonly T[];
111
+ scales: ScaleBundle;
112
+ plot: Frame;
113
+ theme: Theme;
114
+ atlas: GlyphAtlas | undefined;
115
+ /**
116
+ * Coordinate system in effect for this chart. Geoms can call
117
+ * `ctx.coord.project(p)` / `ctx.coord.segment(p1, p2)` to remap plot-frame
118
+ * pixel space into layer-pixel space — under `coordCartesian()` (the
119
+ * default) both are the identity, so existing geoms are unaffected. Phase 3
120
+ * adds `coordPolar()` / `coordRadial()` and per-geom polar projection.
121
+ *
122
+ * Optional during Phase 1: only the pipeline supplies it today, and geoms
123
+ * have not been migrated to project through it yet. Treat absence as
124
+ * `coordCartesian()` if you read it in a geom. Will become required when
125
+ * Phase 3 lands and per-geom polar projection ships.
126
+ */
127
+ coord?: import("../coord.ts").Coord;
128
+ /**
129
+ * Currently hovered hit, or `null` if nothing is hovered. Geoms compare
130
+ * `hovered.data === ctx.data && hovered.geomKind === this.kind` to detect
131
+ * "their" hit and can use `hovered.dataIndex` to render a highlight (halo,
132
+ * elevated alpha/stroke, etc.). Provided by the mount; `undefined` when
133
+ * compiling outside a mount context (e.g. SSR / SVG export).
134
+ */
135
+ hovered?: HoveredHit | null;
136
+ /**
137
+ * Currently selected hits (multi-select). Empty array = nothing selected;
138
+ * `undefined` = selection is disabled (no visual treatment). Geoms render
139
+ * stroke rings on matching rows and may dim non-selected rows when the
140
+ * array is non-empty.
141
+ */
142
+ selected?: readonly HoveredHit[];
143
+ /** Set of series keys currently hidden via legend toggle. */
144
+ hidden?: ReadonlySet<string>;
145
+ /** Optional stable key used to match rows across data refreshes for transitions. */
146
+ transitionKey?: (datum: T, index: number) => string;
147
+ /**
148
+ * Active data/scale transition context. Present only during an animated
149
+ * transition; geoms use it to lerp channels from the previous frame toward
150
+ * the current compiled values.
151
+ */
152
+ activeTransition?: ActiveTransition;
153
+ /**
154
+ * Disjoint emphasis-key band base for this geom (P5-T3). The pipeline assigns
155
+ * `geomEmphasisBase(geomIndex)`; dim-participating geoms tag each mark
156
+ * instance `emphasisKey: emphasisKeyFor(base, ordinal)` so the core's GPU dim
157
+ * uniform can fade non-focused instances WITHOUT a marks recompile. `undefined`
158
+ * outside a mount (SSR / SVG export) → geoms skip tagging (no GPU emphasis).
159
+ * See {@link import("./emphasis.ts")}.
160
+ */
161
+ emphasisBase?: number;
162
+ }
163
+ /**
164
+ * The active hover state surfaced to compile contexts and exposed on
165
+ * `MountedPlot.hovered`. Coordinates are absolute element-CSS pixels
166
+ * (matching `CompiledHitTest.positions`).
167
+ */
168
+ export interface HoveredHit {
169
+ geomKind: GeomKind;
170
+ dataIndex: number;
171
+ /** Optional segment key for multi-series geoms (stacked/dodged bars, stacked areas). */
172
+ seriesKey?: string;
173
+ /**
174
+ * Reference to the data array used to compile the originating geom — used
175
+ * for identity comparison (`hovered.data === ctx.data`) so multi-layer
176
+ * charts can route the hit to the right geom.
177
+ */
178
+ data: readonly unknown[];
179
+ x: number;
180
+ y: number;
181
+ }
182
+ /**
183
+ * Resolved aesthetics for each channel of a geom — used by interaction layers
184
+ * (tooltips, legends-on-hover) to read the original column name and pull the
185
+ * raw value back out for a given datum.
186
+ */
187
+ export interface ResolvedChannelMap<T> {
188
+ x?: ResolvedAes<T, unknown>;
189
+ y?: ResolvedAes<T, unknown>;
190
+ color?: ResolvedAes<T, unknown>;
191
+ size?: ResolvedAes<T, unknown>;
192
+ shape?: ResolvedAes<T, unknown>;
193
+ alpha?: ResolvedAes<T, unknown>;
194
+ }
195
+ /**
196
+ * Hit-test points emitted by a geom for hover/click. Coordinates are absolute
197
+ * element-CSS pixels (the chart's plot frame offset is already baked in), so
198
+ * the consumer can hand them to a `space: "ui"` PointCloudNode without
199
+ * adjustment. `dataIndex[i]` maps hit `i` back to the original `data` array
200
+ * (after filtering NaN/null values). `seriesKey[i]` further distinguishes
201
+ * segments that share a source row.
202
+ */
203
+ export interface CompiledHitTest<T> {
204
+ geomKind: GeomKind;
205
+ label?: string;
206
+ positions: Float32Array;
207
+ dataIndex: Int32Array;
208
+ seriesKey?: readonly (string | undefined)[];
209
+ pickRadius: number;
210
+ /**
211
+ * Optional per-hit bounding rect in absolute element-CSS pixels. When
212
+ * present, the hit-layer prefers rect containment over nearest-point
213
+ * picking (`positions` + `pickRadius`), giving "whole shape" hover for
214
+ * shape-based geoms (bar, histogram, ridgeline rows, tile cells, ...).
215
+ * Layout: `[x, y, w, h]` per `dataIndex` entry. `positions` is still
216
+ * required as a fallback (used to anchor tooltips, brushes, etc.).
217
+ */
218
+ rects?: Float32Array;
219
+ /**
220
+ * Distance metric for nearest-point queries. Default `"xy"` (Euclidean).
221
+ * `"x"` makes the picker resolve nearest-by-x only — used by `line()` /
222
+ * `area()` when `nearestX: true` so the user can hover anywhere along the
223
+ * curve and the active vertex follows the cursor's x.
224
+ */
225
+ pickAxis?: "x" | "y" | "xy";
226
+ channels: ResolvedChannelMap<T>;
227
+ data: readonly T[];
228
+ }
229
+ /**
230
+ * A geom's hover-focus decoration, captured at compile time so the mount can
231
+ * draw it on hover **without recompiling the pipeline**. The mount calls
232
+ * {@link decorate} into the live overlay layer (which composites above the baked
233
+ * marks), so local focus treatment — a contrast halo, bringing the hovered
234
+ * element to the front — costs only a cheap overlay repaint rather than a full
235
+ * marks recompute. The complementary *global* dim-others treatment (fading every
236
+ * other element) rides the core's GPU emphasis uniform (see {@link
237
+ * EmphasisResolver}), so a geom can provide both: the decorator draws the focus
238
+ * halo (left at emphasis key 0 = exempt, full-strength) while the uniform dims
239
+ * the rest. Geoms with no local focus shape simply omit this.
240
+ *
241
+ * `geomKind` + `data` identity let the mount match the decorator to the active
242
+ * `HoveredHit` (the same keys used to route a hit to its geom).
243
+ */
244
+ export interface GeomHoverDecorator {
245
+ readonly geomKind: GeomKind;
246
+ readonly data: readonly unknown[];
247
+ /** Draw the focus decoration for `hit` into `layer` (the overlay layer). */
248
+ decorate(hit: HoveredHit, layer: Layer): void;
249
+ }
250
+ export interface Geom<T> {
251
+ readonly kind: GeomKind;
252
+ readonly channels: ChannelSpec<T>;
253
+ /** Used by the auto-legend to label the layer. Falls back to channel name. */
254
+ readonly label?: string;
255
+ /** Optional position-scale hints (e.g. anchor value axis at 0 for bars). */
256
+ readonly scaleHints?: ScaleHints;
257
+ /**
258
+ * When true, the geom renders into the hud layer (above marks, no clip rect)
259
+ * instead of the marks layer. Used by geoms whose visual extent intentionally
260
+ * exceeds the plot frame — e.g. phylo tip labels — and that should not be
261
+ * clipped at the frame edge.
262
+ */
263
+ readonly overlay?: boolean;
264
+ /**
265
+ * Optional data-derived scale hints. Called once before scales are built,
266
+ * so geoms whose visual extent depends on the data (e.g. KDE-smoothed
267
+ * violins) can extend the position-scale domain accordingly. Returned
268
+ * hints are merged with `scaleHints` and any hints contributed by other
269
+ * layers. An explicit `.scale(channel, { domain })` override always wins.
270
+ */
271
+ prepareDomain?(data: readonly T[]): ScaleHints | undefined;
272
+ /**
273
+ * Optional layout-time hook that runs after layout but before scale
274
+ * construction. Geoms whose visual extent exceeds their data footprint
275
+ * — e.g. a ridgeline ridge that rises above its baseline by `overlap ×
276
+ * cellSize` — use this to reserve pixel margins on the plot frame, so
277
+ * the position scale (and its axis ticks) shrink to fit.
278
+ */
279
+ prepareRange?(ctx: PrepareRangeContext<T>): RangeHints | undefined;
280
+ /**
281
+ * Optional override for the auto-legend's per-series swatch. Geoms that
282
+ * draw filled regions (bar/area/tile) or strokes (line) supply their own;
283
+ * the pipeline falls back to a point swatch when omitted.
284
+ */
285
+ legendSwatch?(color: Color, theme: Theme): SwatchSpec;
286
+ compile(ctx: CompileContext<T>): readonly MarkBuilder[];
287
+ /**
288
+ * Optional hit-test contribution for interactions. Implemented per geom that
289
+ * wants to support hover/click selection (`point` in v1; bar/line/area in
290
+ * later phases). Return `null` to opt out for a particular compile pass
291
+ * (e.g. when the geom has no points after filtering).
292
+ */
293
+ compileHitTest?(ctx: CompileContext<T>): CompiledHitTest<T> | null;
294
+ /**
295
+ * Optional hover-focus decorator. Implemented by geoms whose focus treatment
296
+ * is *local* (point: contrast halo + bring-to-front) so it can ride the
297
+ * mount's cheap overlay path instead of a marks recompute. Called once per
298
+ * full compile with the same `ctx`; returns `null` to opt out (e.g. empty
299
+ * data). See {@link GeomHoverDecorator}.
300
+ */
301
+ hoverDecoration?(ctx: CompileContext<T>): GeomHoverDecorator | null;
302
+ /**
303
+ * Emphasis-key resolver for geoms whose hover treatment is *global* (dim the
304
+ * others via the core's GPU emphasis uniform: bar/line/violin/...). Captured
305
+ * once per full compile with the same `ctx` (so it sees the same `emphasisBase`
306
+ * + scales the tagging used); the mount maps an active {@link HoveredHit} to the
307
+ * namespaced key the geom tagged its focused instance(s) with — without
308
+ * recompiling. Returns `null` to opt out (e.g. empty data, or `emphasisBase`
309
+ * absent). Mutually complementary with {@link hoverDecoration} (local focus).
310
+ * See {@link import("./emphasis.ts").EmphasisResolver}.
311
+ */
312
+ emphasisResolution?(ctx: CompileContext<T>): import("./emphasis.ts").EmphasisResolver | null;
313
+ /**
314
+ * Capture a per-datum channel snapshot for use as a transition "from" frame.
315
+ * Called after `compile` on each stable (non-animating) frame. Returns `null`
316
+ * if the geom has no meaningful frame to capture (e.g. empty data).
317
+ */
318
+ captureFrame?(ctx: CompileContext<T>): GeomFrame | null;
319
+ }
@@ -0,0 +1,362 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Geom contract
3
+ // ---------------------------------------------------------------------------
4
+
5
+ import type { Color, Frame, GlyphAtlas, Layer } from "insomni";
6
+ import type { MarkBuilder } from "../../marks.ts";
7
+ import type { SwatchSpec } from "../../legend.ts";
8
+ import type { Aes, ResolvedAes } from "../aes.ts";
9
+ import type {
10
+ AlphaScale,
11
+ BorderStyleScale,
12
+ ColorScale,
13
+ OverlayGlyphScale,
14
+ PositionScale,
15
+ PositionScaleOptions,
16
+ ShapeScale,
17
+ SizeScale,
18
+ } from "../scales.ts";
19
+ import type { Theme } from "../theme.ts";
20
+
21
+ export type GeomKind =
22
+ | "point"
23
+ | "line"
24
+ | "area"
25
+ | "bar"
26
+ | "text"
27
+ | "rule"
28
+ | "band"
29
+ | "boxplot"
30
+ | "violin"
31
+ | "histogram"
32
+ | "ridgeline"
33
+ | "rug"
34
+ | "smooth"
35
+ | "rolling"
36
+ | "tile"
37
+ | "interval";
38
+
39
+ /**
40
+ * Channel mappings as declared by the user. Stored at this width on `Geom<T>`
41
+ * so the chart can iterate channels generically. Geom factories accept their
42
+ * narrower per-geom channel types (PointChannels, LineChannels, ...) as the
43
+ * public surface and cast into this for storage.
44
+ */
45
+ export interface ChannelSpec<_T> {
46
+ x?: unknown;
47
+ y?: unknown;
48
+ color?: unknown;
49
+ size?: unknown;
50
+ shape?: unknown;
51
+ alpha?: unknown;
52
+ borderStyle?: unknown;
53
+ overlayGlyph?: unknown;
54
+ }
55
+
56
+ /**
57
+ * Per-geom hints for position-scale construction. Geoms that have a
58
+ * conventional baseline (bar, area) request that their value axis include 0
59
+ * — or, for `position: 'fill'`, set an explicit `[0, 1]` domain. Hints are
60
+ * only applied to numeric (linear/log/sqrt) scales; band/time scales ignore
61
+ * them. An explicit `.scale(channel, { domain })` override always wins.
62
+ */
63
+ export interface PositionScaleHint {
64
+ /** Force this exact domain (overrides include flags). */
65
+ domain?: readonly [number, number];
66
+ /** Extend inferred domain to include 0. */
67
+ includeZero?: boolean;
68
+ /** Extend inferred domain to include 1. */
69
+ includeOne?: boolean;
70
+ /**
71
+ * Extend the inferred domain to include this range. Unlike `domain`, this
72
+ * is unioned with the data-derived extent (and with other layers' extends)
73
+ * rather than replacing it. Use for geoms whose visual footprint extends
74
+ * past the raw data — e.g. a KDE-based violin whose smoothed tails reach
75
+ * outside `[dataMin, dataMax]`.
76
+ */
77
+ extend?: readonly [number, number];
78
+ }
79
+
80
+ export interface ScaleHints {
81
+ x?: PositionScaleHint;
82
+ y?: PositionScaleHint;
83
+ }
84
+
85
+ /**
86
+ * Pixel reservations on the plot frame for a geom whose visual extent
87
+ * exceeds its data footprint. The chart shrinks the position-scale range
88
+ * by these amounts so axis ticks track the compressed layout — used by
89
+ * ridgeline rows that rise above their baseline. Reservations from
90
+ * multiple layers are merged by max (the largest demand wins).
91
+ */
92
+ export interface RangeHints {
93
+ top?: number;
94
+ bottom?: number;
95
+ left?: number;
96
+ right?: number;
97
+ }
98
+
99
+ export interface PrepareRangeContext<T> {
100
+ data: readonly T[];
101
+ plot: Frame;
102
+ scaleOptions: { x?: PositionScaleOptions; y?: PositionScaleOptions };
103
+ theme: Theme;
104
+ atlas: GlyphAtlas | undefined;
105
+ }
106
+
107
+ // Used inside the chart pipeline once channels are read off a geom.
108
+ export type AnyAes<T> = Aes<T, unknown>;
109
+
110
+ export interface ScaleBundle {
111
+ x: PositionScale;
112
+ y: PositionScale;
113
+ color?: ColorScale;
114
+ size?: SizeScale;
115
+ alpha?: AlphaScale;
116
+ shape?: ShapeScale;
117
+ borderStyle?: BorderStyleScale;
118
+ overlayGlyph?: OverlayGlyphScale;
119
+ }
120
+
121
+ /**
122
+ * Per-rendered-element channel snapshot used for animated transitions.
123
+ * For simple geoms this usually matches the source datum index; for stacked /
124
+ * grouped geoms it can be one entry per visible segment. NaN marks filtered /
125
+ * hidden entries. Each geom owns the index convention used by its compile path.
126
+ */
127
+ export interface GeomFrame {
128
+ count: number; // data.length this frame
129
+ x: Float32Array; // plot-relative pixel x
130
+ y: Float32Array; // plot-relative pixel y
131
+ rgba: Float32Array; // 4 floats per datum: [r,g,b,a, r,g,b,a, ...]
132
+ a: Float32Array; // per-datum alpha [0,1]
133
+ r?: Float32Array; // point: radius; area: y0 (baseline px); bar: raw domain value
134
+ ids?: readonly string[];
135
+ }
136
+
137
+ /** Injected into CompileContext during an active data/scale transition. */
138
+ export interface ActiveTransition {
139
+ /** Progress 0 → 1. */
140
+ t: number;
141
+ /** From-snapshot for this geom. Indexed by datum i. */
142
+ from: GeomFrame;
143
+ /** Resolve a stable match index in `from` for the given key. */
144
+ matchIndex(key: string, fallbackIndex?: number): number | undefined;
145
+ }
146
+
147
+ export interface CompileContext<T> {
148
+ data: readonly T[];
149
+ scales: ScaleBundle;
150
+ plot: Frame;
151
+ theme: Theme;
152
+ atlas: GlyphAtlas | undefined;
153
+ /**
154
+ * Coordinate system in effect for this chart. Geoms can call
155
+ * `ctx.coord.project(p)` / `ctx.coord.segment(p1, p2)` to remap plot-frame
156
+ * pixel space into layer-pixel space — under `coordCartesian()` (the
157
+ * default) both are the identity, so existing geoms are unaffected. Phase 3
158
+ * adds `coordPolar()` / `coordRadial()` and per-geom polar projection.
159
+ *
160
+ * Optional during Phase 1: only the pipeline supplies it today, and geoms
161
+ * have not been migrated to project through it yet. Treat absence as
162
+ * `coordCartesian()` if you read it in a geom. Will become required when
163
+ * Phase 3 lands and per-geom polar projection ships.
164
+ */
165
+ coord?: import("../coord.ts").Coord;
166
+ /**
167
+ * Currently hovered hit, or `null` if nothing is hovered. Geoms compare
168
+ * `hovered.data === ctx.data && hovered.geomKind === this.kind` to detect
169
+ * "their" hit and can use `hovered.dataIndex` to render a highlight (halo,
170
+ * elevated alpha/stroke, etc.). Provided by the mount; `undefined` when
171
+ * compiling outside a mount context (e.g. SSR / SVG export).
172
+ */
173
+ hovered?: HoveredHit | null;
174
+ /**
175
+ * Currently selected hits (multi-select). Empty array = nothing selected;
176
+ * `undefined` = selection is disabled (no visual treatment). Geoms render
177
+ * stroke rings on matching rows and may dim non-selected rows when the
178
+ * array is non-empty.
179
+ */
180
+ selected?: readonly HoveredHit[];
181
+ /** Set of series keys currently hidden via legend toggle. */
182
+ hidden?: ReadonlySet<string>;
183
+ /** Optional stable key used to match rows across data refreshes for transitions. */
184
+ transitionKey?: (datum: T, index: number) => string;
185
+ /**
186
+ * Active data/scale transition context. Present only during an animated
187
+ * transition; geoms use it to lerp channels from the previous frame toward
188
+ * the current compiled values.
189
+ */
190
+ activeTransition?: ActiveTransition;
191
+ /**
192
+ * Disjoint emphasis-key band base for this geom (P5-T3). The pipeline assigns
193
+ * `geomEmphasisBase(geomIndex)`; dim-participating geoms tag each mark
194
+ * instance `emphasisKey: emphasisKeyFor(base, ordinal)` so the core's GPU dim
195
+ * uniform can fade non-focused instances WITHOUT a marks recompile. `undefined`
196
+ * outside a mount (SSR / SVG export) → geoms skip tagging (no GPU emphasis).
197
+ * See {@link import("./emphasis.ts")}.
198
+ */
199
+ emphasisBase?: number;
200
+ }
201
+
202
+ /**
203
+ * The active hover state surfaced to compile contexts and exposed on
204
+ * `MountedPlot.hovered`. Coordinates are absolute element-CSS pixels
205
+ * (matching `CompiledHitTest.positions`).
206
+ */
207
+ export interface HoveredHit {
208
+ geomKind: GeomKind;
209
+ dataIndex: number;
210
+ /** Optional segment key for multi-series geoms (stacked/dodged bars, stacked areas). */
211
+ seriesKey?: string;
212
+ /**
213
+ * Reference to the data array used to compile the originating geom — used
214
+ * for identity comparison (`hovered.data === ctx.data`) so multi-layer
215
+ * charts can route the hit to the right geom.
216
+ */
217
+ data: readonly unknown[];
218
+ x: number;
219
+ y: number;
220
+ }
221
+
222
+ /**
223
+ * Resolved aesthetics for each channel of a geom — used by interaction layers
224
+ * (tooltips, legends-on-hover) to read the original column name and pull the
225
+ * raw value back out for a given datum.
226
+ */
227
+ export interface ResolvedChannelMap<T> {
228
+ x?: ResolvedAes<T, unknown>;
229
+ y?: ResolvedAes<T, unknown>;
230
+ color?: ResolvedAes<T, unknown>;
231
+ size?: ResolvedAes<T, unknown>;
232
+ shape?: ResolvedAes<T, unknown>;
233
+ alpha?: ResolvedAes<T, unknown>;
234
+ }
235
+
236
+ /**
237
+ * Hit-test points emitted by a geom for hover/click. Coordinates are absolute
238
+ * element-CSS pixels (the chart's plot frame offset is already baked in), so
239
+ * the consumer can hand them to a `space: "ui"` PointCloudNode without
240
+ * adjustment. `dataIndex[i]` maps hit `i` back to the original `data` array
241
+ * (after filtering NaN/null values). `seriesKey[i]` further distinguishes
242
+ * segments that share a source row.
243
+ */
244
+ export interface CompiledHitTest<T> {
245
+ geomKind: GeomKind;
246
+ label?: string;
247
+ positions: Float32Array;
248
+ dataIndex: Int32Array;
249
+ seriesKey?: readonly (string | undefined)[];
250
+ pickRadius: number;
251
+ /**
252
+ * Optional per-hit bounding rect in absolute element-CSS pixels. When
253
+ * present, the hit-layer prefers rect containment over nearest-point
254
+ * picking (`positions` + `pickRadius`), giving "whole shape" hover for
255
+ * shape-based geoms (bar, histogram, ridgeline rows, tile cells, ...).
256
+ * Layout: `[x, y, w, h]` per `dataIndex` entry. `positions` is still
257
+ * required as a fallback (used to anchor tooltips, brushes, etc.).
258
+ */
259
+ rects?: Float32Array;
260
+ /**
261
+ * Distance metric for nearest-point queries. Default `"xy"` (Euclidean).
262
+ * `"x"` makes the picker resolve nearest-by-x only — used by `line()` /
263
+ * `area()` when `nearestX: true` so the user can hover anywhere along the
264
+ * curve and the active vertex follows the cursor's x.
265
+ */
266
+ pickAxis?: "x" | "y" | "xy";
267
+ channels: ResolvedChannelMap<T>;
268
+ data: readonly T[];
269
+ }
270
+
271
+ /**
272
+ * A geom's hover-focus decoration, captured at compile time so the mount can
273
+ * draw it on hover **without recompiling the pipeline**. The mount calls
274
+ * {@link decorate} into the live overlay layer (which composites above the baked
275
+ * marks), so local focus treatment — a contrast halo, bringing the hovered
276
+ * element to the front — costs only a cheap overlay repaint rather than a full
277
+ * marks recompute. The complementary *global* dim-others treatment (fading every
278
+ * other element) rides the core's GPU emphasis uniform (see {@link
279
+ * EmphasisResolver}), so a geom can provide both: the decorator draws the focus
280
+ * halo (left at emphasis key 0 = exempt, full-strength) while the uniform dims
281
+ * the rest. Geoms with no local focus shape simply omit this.
282
+ *
283
+ * `geomKind` + `data` identity let the mount match the decorator to the active
284
+ * `HoveredHit` (the same keys used to route a hit to its geom).
285
+ */
286
+ export interface GeomHoverDecorator {
287
+ readonly geomKind: GeomKind;
288
+ readonly data: readonly unknown[];
289
+ /** Draw the focus decoration for `hit` into `layer` (the overlay layer). */
290
+ decorate(hit: HoveredHit, layer: Layer): void;
291
+ }
292
+
293
+ export interface Geom<T> {
294
+ readonly kind: GeomKind;
295
+ readonly channels: ChannelSpec<T>;
296
+ /** Used by the auto-legend to label the layer. Falls back to channel name. */
297
+ readonly label?: string;
298
+ /** Optional position-scale hints (e.g. anchor value axis at 0 for bars). */
299
+ readonly scaleHints?: ScaleHints;
300
+ /**
301
+ * When true, the geom renders into the hud layer (above marks, no clip rect)
302
+ * instead of the marks layer. Used by geoms whose visual extent intentionally
303
+ * exceeds the plot frame — e.g. phylo tip labels — and that should not be
304
+ * clipped at the frame edge.
305
+ */
306
+ readonly overlay?: boolean;
307
+ /**
308
+ * Optional data-derived scale hints. Called once before scales are built,
309
+ * so geoms whose visual extent depends on the data (e.g. KDE-smoothed
310
+ * violins) can extend the position-scale domain accordingly. Returned
311
+ * hints are merged with `scaleHints` and any hints contributed by other
312
+ * layers. An explicit `.scale(channel, { domain })` override always wins.
313
+ */
314
+ prepareDomain?(data: readonly T[]): ScaleHints | undefined;
315
+ /**
316
+ * Optional layout-time hook that runs after layout but before scale
317
+ * construction. Geoms whose visual extent exceeds their data footprint
318
+ * — e.g. a ridgeline ridge that rises above its baseline by `overlap ×
319
+ * cellSize` — use this to reserve pixel margins on the plot frame, so
320
+ * the position scale (and its axis ticks) shrink to fit.
321
+ */
322
+ prepareRange?(ctx: PrepareRangeContext<T>): RangeHints | undefined;
323
+ /**
324
+ * Optional override for the auto-legend's per-series swatch. Geoms that
325
+ * draw filled regions (bar/area/tile) or strokes (line) supply their own;
326
+ * the pipeline falls back to a point swatch when omitted.
327
+ */
328
+ legendSwatch?(color: Color, theme: Theme): SwatchSpec;
329
+ compile(ctx: CompileContext<T>): readonly MarkBuilder[];
330
+ /**
331
+ * Optional hit-test contribution for interactions. Implemented per geom that
332
+ * wants to support hover/click selection (`point` in v1; bar/line/area in
333
+ * later phases). Return `null` to opt out for a particular compile pass
334
+ * (e.g. when the geom has no points after filtering).
335
+ */
336
+ compileHitTest?(ctx: CompileContext<T>): CompiledHitTest<T> | null;
337
+ /**
338
+ * Optional hover-focus decorator. Implemented by geoms whose focus treatment
339
+ * is *local* (point: contrast halo + bring-to-front) so it can ride the
340
+ * mount's cheap overlay path instead of a marks recompute. Called once per
341
+ * full compile with the same `ctx`; returns `null` to opt out (e.g. empty
342
+ * data). See {@link GeomHoverDecorator}.
343
+ */
344
+ hoverDecoration?(ctx: CompileContext<T>): GeomHoverDecorator | null;
345
+ /**
346
+ * Emphasis-key resolver for geoms whose hover treatment is *global* (dim the
347
+ * others via the core's GPU emphasis uniform: bar/line/violin/...). Captured
348
+ * once per full compile with the same `ctx` (so it sees the same `emphasisBase`
349
+ * + scales the tagging used); the mount maps an active {@link HoveredHit} to the
350
+ * namespaced key the geom tagged its focused instance(s) with — without
351
+ * recompiling. Returns `null` to opt out (e.g. empty data, or `emphasisBase`
352
+ * absent). Mutually complementary with {@link hoverDecoration} (local focus).
353
+ * See {@link import("./emphasis.ts").EmphasisResolver}.
354
+ */
355
+ emphasisResolution?(ctx: CompileContext<T>): import("./emphasis.ts").EmphasisResolver | null;
356
+ /**
357
+ * Capture a per-datum channel snapshot for use as a transition "from" frame.
358
+ * Called after `compile` on each stable (non-animating) frame. Returns `null`
359
+ * if the geom has no meaningful frame to capture (e.g. empty data).
360
+ */
361
+ captureFrame?(ctx: CompileContext<T>): GeomFrame | null;
362
+ }