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,672 @@
1
+ // ---------------------------------------------------------------------------
2
+ // violin geom
3
+ // ---------------------------------------------------------------------------
4
+ // Mirrored kernel density estimate per group. By default renders just the
5
+ // closed KDE polygon (matching ggplot's `geom_violin`); opt-in inner
6
+ // annotations (mini box, quartile lines, median stick) and jittered raw-point
7
+ // overlays compose on top. No new low-level marks.
8
+
9
+ import type { Color, Layer, Vec2 } from "insomni";
10
+ import { type ContinuousScale } from "../../scales.ts";
11
+ import {
12
+ jitter,
13
+ type BoxStats,
14
+ type KdeBandwidth,
15
+ type KdeKernel,
16
+ type KdeResult,
17
+ type QuantileMethod,
18
+ type WhiskerRule,
19
+ } from "../../stats/index.ts";
20
+ import { pointMark, pushFilledPolygon } from "../../marks.ts";
21
+ import { alphaize } from "../color-utils.ts";
22
+ import { DEFAULT_POINTS_THRESHOLD } from "../constants.ts";
23
+ import { resolveAes } from "../aes.ts";
24
+ import type { Aes } from "../aes.ts";
25
+ import type {
26
+ CompileContext,
27
+ CompiledHitTest,
28
+ Geom,
29
+ PositionScaleHint,
30
+ ResolvedChannelMap,
31
+ } from "./types.ts";
32
+ import { selectChannels } from "./_mark.ts";
33
+ import { emphasisContext } from "./emphasis.ts";
34
+ import type { PointsMode } from "./boxplot.ts";
35
+ import {
36
+ bucketBandCenter,
37
+ bucketSeed,
38
+ countsLabelMark,
39
+ prepareCategoricalLayout,
40
+ synthAes,
41
+ type CategoricalBucket,
42
+ type CategoricalChannels,
43
+ } from "./_categorical.ts";
44
+ import {
45
+ computeGroupedKde,
46
+ densityWidthFn,
47
+ kdeValueExtent,
48
+ type DensityScale,
49
+ } from "./_distribution.ts";
50
+
51
+ export interface ViolinChannels<T> {
52
+ x: Aes<T, string | number | Date>;
53
+ /**
54
+ * Numeric value axis when vertical. For horizontal layouts
55
+ * (`orientation: "x"`) this carries the band category, so a string is
56
+ * accepted; the value axis then lives on `x`.
57
+ */
58
+ y: Aes<T, string | number | Date>;
59
+ color?: Aes<T, unknown>;
60
+ }
61
+
62
+ export type ViolinScale = DensityScale;
63
+ export type ViolinInner = "box" | "quartile" | "stick" | "none";
64
+
65
+ export interface ViolinOptions {
66
+ orientation?: "x" | "y";
67
+ /** Violin width as a fraction of the bandwidth (or grouped inner bandwidth). Default `0.9`. */
68
+ width?: number;
69
+ /** KDE bandwidth — number, `"silverman"` (default), or `"scott"`. */
70
+ bandwidth?: KdeBandwidth;
71
+ /** KDE evaluation grid size. Default `64`. */
72
+ gridSize?: number;
73
+ /** KDE kernel. Default `"gaussian"`. */
74
+ kernel?: KdeKernel;
75
+ /** Clip KDE to data range (true, default) or pad outward by ~3 bandwidths. */
76
+ trim?: boolean;
77
+ /**
78
+ * Width-normalization mode.
79
+ *
80
+ * - `"width"` (default) — each violin uses its full allotted width; shapes
81
+ * are easy to compare but **areas are misleading**.
82
+ * - `"area"` — density scaled relative to the global max so areas are
83
+ * comparable across groups (groups with low n look skinny).
84
+ * - `"count"` — area scales with sample size (bigger n → wider violin).
85
+ */
86
+ scale?: ViolinScale;
87
+ /**
88
+ * Inner annotation drawn on top of the violin.
89
+ *
90
+ * - `"none"` (default) — bare KDE polygon, like ggplot's `geom_violin`.
91
+ * - `"box"` — slim box plot with median, IQR, whiskers.
92
+ * - `"quartile"` — three horizontal/vertical lines at Q1, median, Q3.
93
+ * - `"stick"` — single line at the median only.
94
+ */
95
+ inner?: ViolinInner;
96
+ /** When to overlay raw jittered points. See `BoxplotOptions["points"]`. */
97
+ points?: PointsMode;
98
+ pointsThreshold?: number;
99
+ pointJitter?: number;
100
+ pointRadius?: number;
101
+ jitterSeed?: number;
102
+ /** Box stats: whisker rule when `inner: "box"`. Default `1.5`. */
103
+ whisker?: WhiskerRule;
104
+ /** Box stats: quantile method. Default `"type-7"`. */
105
+ quantile?: QuantileMethod;
106
+ fill?: Color;
107
+ stroke?: Color;
108
+ strokeWidth?: number;
109
+ /** Color of inner annotation marks. Defaults to `stroke` (or theme text). */
110
+ innerStroke?: Color;
111
+ /** Stroke width for the embedded mini-box body. Default `1`. */
112
+ innerStrokeWidth?: number;
113
+ /** Inner-band padding for grouped (color-split) layout. Default `0.05`. */
114
+ groupPadding?: number;
115
+ /**
116
+ * Render an `n=<count>` label per group, aligned with the band axis just
117
+ * outside the plot frame. Useful when the categorical labels alone hide
118
+ * sample-size differences.
119
+ */
120
+ showCounts?: boolean;
121
+ /**
122
+ * Pixel offset of the count labels from the plot frame edge along the
123
+ * band-axis perpendicular. Increase to clear longer tick labels. Default
124
+ * `28` (vertical) / `32` (horizontal).
125
+ */
126
+ countsOffset?: number;
127
+ countsFontSize?: number;
128
+ countsColor?: Color;
129
+ label?: string;
130
+ }
131
+
132
+ interface ViolinLayout {
133
+ bucket: CategoricalBucket;
134
+ /** Index of this bucket in layout.buckets — used to match against hover dataIndex. */
135
+ bucketIndex: number;
136
+ bandCenter: number;
137
+ halfWidth: number;
138
+ kde: KdeResult;
139
+ /** Max density before normalization. Used for cross-group `area`/`count` scaling. */
140
+ maxDensity: number;
141
+ n: number;
142
+ stats: BoxStats;
143
+ fill: Color;
144
+ /** Silhouette stroke width (boosted for the hovered bucket). */
145
+ strokeWidth: number;
146
+ /**
147
+ * GPU emphasis key (P5-T3) written onto EVERY primitive of this violin (KDE
148
+ * silhouette fill + outline, inner box/quartile/stick, raw-point overlay) so
149
+ * the whole entity dims as one. Ordinal = `bucketIndex` (the hit `dataIndex`).
150
+ * `undefined` when emphasis is off.
151
+ */
152
+ emphasisKey?: number;
153
+ }
154
+
155
+ export function violin<T>(channels: ViolinChannels<T>, options: ViolinOptions = {}): Geom<T> {
156
+ // The categorical layout helper stringifies the band axis and reads the
157
+ // value axis as a number; `y` may be the band category (horizontal layouts),
158
+ // so widen-then-narrow at the helper boundary. Same pattern as ridgeline.
159
+ const catChannels = channels as unknown as CategoricalChannels<T>;
160
+ const orientationOpt = options.orientation;
161
+ const kdeOpts = {
162
+ bandwidth: options.bandwidth ?? "silverman",
163
+ gridSize: options.gridSize ?? 64,
164
+ kernel: options.kernel ?? "gaussian",
165
+ trim: options.trim ?? true,
166
+ } as const;
167
+ return {
168
+ kind: "violin",
169
+ channels: { x: channels.x, y: channels.y, color: channels.color },
170
+ label: options.label,
171
+ prepareDomain(data) {
172
+ // Run a per-bucket KDE just to learn the polygon's value-axis extent,
173
+ // so the chart's value scale unions in any negative or right-tail
174
+ // smoothing the polygon will display. Without this, the axis crops to
175
+ // [dataMin, dataMax] and KDE tails (especially with `trim: false`)
176
+ // render past the plot frame.
177
+ let orientation: "x" | "y" = orientationOpt ?? "y";
178
+ if (!orientationOpt && data.length > 0) {
179
+ // Match the auto-detect in compile(): whichever channel resolves to
180
+ // strings is the band axis, so the OTHER is the value axis.
181
+ const xAes = resolveAes<T, unknown>(channels.x as Aes<T, unknown>);
182
+ const yAes = resolveAes<T, unknown>(channels.y as Aes<T, unknown>);
183
+ const xSample = xAes.fn(data[0]!, 0);
184
+ const ySample = yAes.fn(data[0]!, 0);
185
+ if (typeof xSample === "string") orientation = "y";
186
+ else if (typeof ySample === "string") orientation = "x";
187
+ }
188
+ const { categoryChannel, valueChannel } = selectChannels(orientation, channels);
189
+ const valueAes = resolveAes<T, number>(valueChannel as Aes<T, number>);
190
+ const bandAes = resolveAes<T, unknown>(categoryChannel as Aes<T, unknown>);
191
+ const buckets = new Map<string, number[]>();
192
+ for (let i = 0; i < data.length; i++) {
193
+ const datum = data[i]!;
194
+ const v = valueAes.fn(datum, i);
195
+ if (!Number.isFinite(v)) continue;
196
+ const key = String(bandAes.fn(datum, i));
197
+ let arr = buckets.get(key);
198
+ if (!arr) {
199
+ arr = [];
200
+ buckets.set(key, arr);
201
+ }
202
+ arr.push(v);
203
+ }
204
+ const extent = kdeValueExtent(buckets.values(), kdeOpts);
205
+ if (!extent) return undefined;
206
+ const valueHint: PositionScaleHint = { extend: extent };
207
+ return orientation === "x" ? { x: valueHint } : { y: valueHint };
208
+ },
209
+ compile(ctx: CompileContext<T>) {
210
+ const { plot, theme, atlas } = ctx;
211
+ const layout = prepareCategoricalLayout(ctx, catChannels, {
212
+ orientation: options.orientation,
213
+ groupPadding: options.groupPadding,
214
+ fill: options.fill,
215
+ });
216
+ const { orientation, valueAxis, cellSize, ox, oy, buckets } = layout;
217
+
218
+ const widthFraction = options.width ?? 0.9;
219
+ const slot = cellSize * widthFraction;
220
+
221
+ const scaleMode: ViolinScale = options.scale ?? "width";
222
+ const { groups: pre, globalMaxDensity } = computeGroupedKde(buckets, {
223
+ kde: kdeOpts,
224
+ box: {
225
+ whisker: options.whisker ?? 1.5,
226
+ quantile: options.quantile ?? "type-7",
227
+ },
228
+ scale: scaleMode,
229
+ });
230
+
231
+ // Hover dim rides the core's GPU emphasis uniform (P5-T3). The silhouette
232
+ // is a `pushPolygon` fill, which now carries an emphasis key (added this
233
+ // change), so the whole violin can be tagged as ONE entity: the SAME key
234
+ // on the KDE fill + outline, the inner box/quartile/stick, and the raw-
235
+ // point overlay. Ordinal = `bucketIndex` (same order as `buckets` / `pre`),
236
+ // which is exactly the `dataIndex` compileHitTest reports — so the mount's
237
+ // emphasisResolution maps a hover hit to this key with no recompile.
238
+ const emph = emphasisContext(ctx, "violin");
239
+ const stroke: Color = options.stroke ?? theme.text.color;
240
+ const baseStrokeWidth = options.strokeWidth ?? 1;
241
+ const innerStroke: Color = options.innerStroke ?? stroke;
242
+
243
+ const layouts: ViolinLayout[] = [];
244
+ for (const p of pre) {
245
+ const bandCenter = bucketBandCenter(layout, p.bucket);
246
+ if (Number.isNaN(bandCenter)) continue;
247
+ const bucketIndex = buckets.indexOf(p.bucket);
248
+ layouts.push({
249
+ bucket: p.bucket,
250
+ bucketIndex,
251
+ bandCenter,
252
+ halfWidth: slot / 2,
253
+ kde: p.kde,
254
+ maxDensity: p.maxDensity,
255
+ n: p.n,
256
+ stats: p.stats,
257
+ fill: layout.resolveFill(p.bucket),
258
+ strokeWidth: baseStrokeWidth,
259
+ emphasisKey: emph?.keyFor(bucketIndex),
260
+ });
261
+ }
262
+ const innerStrokeWidth = options.innerStrokeWidth ?? 1;
263
+ const innerKind: ViolinInner = options.inner ?? "none";
264
+ const pointsMode: PointsMode = options.points ?? "auto";
265
+ const pointsThreshold = options.pointsThreshold ?? DEFAULT_POINTS_THRESHOLD;
266
+ const pointJitter = options.pointJitter ?? 0.4;
267
+ const pointRadius = options.pointRadius ?? 2;
268
+ const jitterSeed = options.jitterSeed ?? 1;
269
+ const showCounts = options.showCounts ?? false;
270
+ const countsFontSize = options.countsFontSize ?? theme.marks.labelFontSize;
271
+ const countsColor: Color = options.countsColor ?? theme.text.color;
272
+ const countsOffset = options.countsOffset ?? (orientation === "y" ? 28 : 32);
273
+
274
+ const builder = {
275
+ length: layouts.length,
276
+ addTo(layer: Layer): Layer {
277
+ for (const lay of layouts) {
278
+ const widthFor = densityWidthFn(
279
+ {
280
+ bucket: lay.bucket,
281
+ kde: lay.kde,
282
+ maxDensity: lay.maxDensity,
283
+ n: lay.n,
284
+ stats: lay.stats,
285
+ },
286
+ globalMaxDensity,
287
+ scaleMode,
288
+ lay.halfWidth,
289
+ );
290
+
291
+ drawViolin(layer, lay, {
292
+ orientation,
293
+ valueAxis,
294
+ ox,
295
+ oy,
296
+ widthFor,
297
+ fill: lay.fill,
298
+ stroke,
299
+ strokeWidth: lay.strokeWidth,
300
+ emphasisKey: lay.emphasisKey,
301
+ });
302
+
303
+ if (innerKind !== "none") {
304
+ drawInner(layer, lay, {
305
+ orientation,
306
+ valueAxis,
307
+ ox,
308
+ oy,
309
+ kind: innerKind,
310
+ stroke: innerStroke,
311
+ strokeWidth: innerStrokeWidth,
312
+ fill: alphaize(stroke, 0.18),
313
+ emphasisKey: lay.emphasisKey,
314
+ });
315
+ }
316
+
317
+ const showRaw =
318
+ pointsMode === "always" || (pointsMode === "auto" && lay.stats.n < pointsThreshold);
319
+ if (showRaw) {
320
+ const jw = lay.halfWidth * 2 * pointJitter;
321
+ const offsets = jitter(
322
+ bucketSeed(lay.bucket, jitterSeed),
323
+ lay.bucket.values.length,
324
+ jw,
325
+ );
326
+ const center = lay.bandCenter;
327
+ const overlayEmphasisKey = lay.emphasisKey;
328
+ const mark = pointMark(lay.bucket.values, {
329
+ x: (v, i) =>
330
+ orientation === "y" ? ox + center + (offsets[i] ?? 0) : ox + valueAxis(v),
331
+ y: (v, i) =>
332
+ orientation === "y" ? oy + valueAxis(v) : oy + center + (offsets[i] ?? 0),
333
+ radius: pointRadius,
334
+ fill: alphaize(innerStroke, 0.5),
335
+ emphasisKey:
336
+ overlayEmphasisKey === undefined ? undefined : () => overlayEmphasisKey,
337
+ });
338
+ mark.addTo(layer);
339
+ }
340
+ }
341
+
342
+ if (showCounts && atlas) {
343
+ const labelMark = countsLabelMark(
344
+ layout,
345
+ {
346
+ offset: countsOffset,
347
+ fontSize: countsFontSize,
348
+ color: countsColor,
349
+ },
350
+ plot.width,
351
+ plot.height,
352
+ );
353
+ labelMark.addTo(layer, plot.topLeft);
354
+ }
355
+
356
+ return layer;
357
+ },
358
+ };
359
+
360
+ return [builder];
361
+ },
362
+ emphasisResolution(ctx) {
363
+ // Ordinal = the hit's `dataIndex`, which compileHitTest sets to the
364
+ // pre-filter `bucketIndex` — the same value `compile` tags each violin with.
365
+ return emphasisContext(ctx, "violin")?.resolver() ?? null;
366
+ },
367
+ compileHitTest(ctx: CompileContext<T>): CompiledHitTest<T> | null {
368
+ const { data } = ctx;
369
+ const layout = prepareCategoricalLayout(ctx, catChannels, {
370
+ orientation: options.orientation,
371
+ groupPadding: options.groupPadding,
372
+ fill: options.fill,
373
+ });
374
+ const { orientation, valueAxis, ox, oy, buckets, dodging } = layout;
375
+
376
+ const { groups: pre } = computeGroupedKde(buckets, {
377
+ kde: kdeOpts,
378
+ box: {
379
+ whisker: options.whisker ?? 1.5,
380
+ quantile: options.quantile ?? "type-7",
381
+ },
382
+ scale: options.scale ?? "width",
383
+ });
384
+
385
+ const positions = new Float32Array(pre.length * 2);
386
+ const rects = new Float32Array(pre.length * 4);
387
+ const seriesKey: (string | undefined)[] = Array.from({ length: pre.length });
388
+ // Side arrays keyed by pre-filter bucket index — matches what
389
+ // `compile` uses (`buckets.indexOf(p.bucket)`) so the synth channel
390
+ // accessors and hover lookups share one key.
391
+ const medians: number[] = Array.from({ length: buckets.length });
392
+ const categories: string[] = Array.from({ length: buckets.length });
393
+ const groupKeys: (string | undefined)[] = Array.from({ length: buckets.length });
394
+ const hitBucketIndex: number[] = Array.from({ length: pre.length });
395
+ let n = 0;
396
+ for (const p of pre) {
397
+ const bi = buckets.indexOf(p.bucket);
398
+ const bandCenter = bucketBandCenter(layout, p.bucket);
399
+ if (!Number.isFinite(bandCenter)) continue;
400
+ const medianPx = valueAxis(p.stats.median);
401
+ if (!Number.isFinite(medianPx)) continue;
402
+ // KDE value extent: min/max of the evaluated grid projected to pixels.
403
+ const kdeXValues = p.kde.x;
404
+ let kdePxLo = Infinity;
405
+ let kdePxHi = -Infinity;
406
+ for (const x of kdeXValues) {
407
+ const px = valueAxis(x);
408
+ if (px < kdePxLo) kdePxLo = px;
409
+ if (px > kdePxHi) kdePxHi = px;
410
+ }
411
+ const valueLo = Math.min(kdePxLo, kdePxHi);
412
+ const valueHi = Math.max(kdePxLo, kdePxHi);
413
+ const valueSpan = Math.max(valueHi - valueLo, 1);
414
+ const bandStart = bandCenter - layout.cellSize / 2;
415
+ if (orientation === "y") {
416
+ positions[n * 2] = ox + bandCenter;
417
+ positions[n * 2 + 1] = oy + medianPx;
418
+ rects[n * 4] = ox + bandStart;
419
+ rects[n * 4 + 1] = oy + valueLo;
420
+ rects[n * 4 + 2] = layout.cellSize;
421
+ rects[n * 4 + 3] = valueSpan;
422
+ } else {
423
+ positions[n * 2] = ox + medianPx;
424
+ positions[n * 2 + 1] = oy + bandCenter;
425
+ rects[n * 4] = ox + valueLo;
426
+ rects[n * 4 + 1] = oy + bandStart;
427
+ rects[n * 4 + 2] = valueSpan;
428
+ rects[n * 4 + 3] = layout.cellSize;
429
+ }
430
+ seriesKey[n] = dodging ? p.bucket.groupKey : p.bucket.category;
431
+ medians[bi] = p.stats.median;
432
+ categories[bi] = p.bucket.category;
433
+ groupKeys[bi] = p.bucket.groupKey;
434
+ hitBucketIndex[n] = bi;
435
+ n++;
436
+ }
437
+ if (n === 0) return null;
438
+
439
+ const dataIndex = new Int32Array(n);
440
+ for (let i = 0; i < n; i++) dataIndex[i] = hitBucketIndex[i]!;
441
+
442
+ const channelsMap: ResolvedChannelMap<T> = {
443
+ x: synthAes(orientation === "y" ? "category" : "median", (_d, idx) =>
444
+ orientation === "y" ? categories[idx] : medians[idx],
445
+ ),
446
+ y: synthAes(orientation === "y" ? "median" : "category", (_d, idx) =>
447
+ orientation === "y" ? medians[idx] : categories[idx],
448
+ ),
449
+ color: dodging ? synthAes("group", (_d, idx) => groupKeys[idx] ?? "") : undefined,
450
+ };
451
+
452
+ return {
453
+ geomKind: "violin",
454
+ label: options.label,
455
+ positions: positions.subarray(0, n * 2),
456
+ rects: rects.subarray(0, n * 4),
457
+ dataIndex,
458
+ seriesKey: seriesKey.slice(0, n),
459
+ pickRadius: Math.max(layout.cellSize / 2, 8),
460
+ channels: channelsMap,
461
+ data,
462
+ };
463
+ },
464
+ };
465
+ }
466
+
467
+ interface DrawViolinCtx {
468
+ orientation: "x" | "y";
469
+ valueAxis: ContinuousScale;
470
+ ox: number;
471
+ oy: number;
472
+ widthFor: (density: number) => number;
473
+ fill: Color;
474
+ stroke: Color;
475
+ strokeWidth: number;
476
+ emphasisKey?: number;
477
+ }
478
+
479
+ function drawViolin(layer: Layer, lay: ViolinLayout, c: DrawViolinCtx) {
480
+ const { kde: k, bandCenter } = lay;
481
+ const n = k.x.length;
482
+ if (n < 2) return;
483
+
484
+ const points: Vec2[] = Array.from({ length: n * 2 });
485
+ if (c.orientation === "y") {
486
+ // Vertical: KDE evaluated along the value axis (y). Width spreads along x.
487
+ for (let i = 0; i < n; i++) {
488
+ const xi = bandCenter + c.widthFor(k.y[i]!);
489
+ const yi = c.valueAxis(k.x[i]!);
490
+ points[i] = { x: c.ox + xi, y: c.oy + yi };
491
+ }
492
+ // Reverse bottom edge (mirrored).
493
+ for (let i = 0; i < n; i++) {
494
+ const j = n - 1 - i;
495
+ const xi = bandCenter - c.widthFor(k.y[j]!);
496
+ const yi = c.valueAxis(k.x[j]!);
497
+ points[n + i] = { x: c.ox + xi, y: c.oy + yi };
498
+ }
499
+ } else {
500
+ // Horizontal: KDE evaluated along the value axis (x). Width spreads along y.
501
+ for (let i = 0; i < n; i++) {
502
+ const xi = c.valueAxis(k.x[i]!);
503
+ const yi = bandCenter + c.widthFor(k.y[i]!);
504
+ points[i] = { x: c.ox + xi, y: c.oy + yi };
505
+ }
506
+ for (let i = 0; i < n; i++) {
507
+ const j = n - 1 - i;
508
+ const xi = c.valueAxis(k.x[j]!);
509
+ const yi = bandCenter - c.widthFor(k.y[j]!);
510
+ points[n + i] = { x: c.ox + xi, y: c.oy + yi };
511
+ }
512
+ }
513
+
514
+ pushFilledPolygon(layer, points, {
515
+ fill: c.fill,
516
+ stroke: c.stroke,
517
+ strokeWidth: c.strokeWidth,
518
+ emphasisKey: c.emphasisKey,
519
+ });
520
+ }
521
+
522
+ interface DrawInnerCtx {
523
+ orientation: "x" | "y";
524
+ valueAxis: ContinuousScale;
525
+ ox: number;
526
+ oy: number;
527
+ kind: ViolinInner;
528
+ stroke: Color;
529
+ strokeWidth: number;
530
+ fill: Color;
531
+ emphasisKey?: number;
532
+ }
533
+
534
+ function drawInner(layer: Layer, lay: ViolinLayout, c: DrawInnerCtx) {
535
+ const { stats, bandCenter, halfWidth } = lay;
536
+ const valuePos = (v: number) => c.valueAxis(v);
537
+ const ek = c.emphasisKey;
538
+
539
+ if (c.kind === "stick") {
540
+ const m = valuePos(stats.median);
541
+ if (c.orientation === "y") {
542
+ layer.pushPolyline({
543
+ points: [
544
+ { x: c.ox + bandCenter - halfWidth * 0.5, y: c.oy + m },
545
+ { x: c.ox + bandCenter + halfWidth * 0.5, y: c.oy + m },
546
+ ],
547
+ color: c.stroke,
548
+ width: c.strokeWidth + 1,
549
+ emphasisKey: ek,
550
+ });
551
+ } else {
552
+ layer.pushPolyline({
553
+ points: [
554
+ { x: c.ox + m, y: c.oy + bandCenter - halfWidth * 0.5 },
555
+ { x: c.ox + m, y: c.oy + bandCenter + halfWidth * 0.5 },
556
+ ],
557
+ color: c.stroke,
558
+ width: c.strokeWidth + 1,
559
+ emphasisKey: ek,
560
+ });
561
+ }
562
+ return;
563
+ }
564
+
565
+ if (c.kind === "quartile") {
566
+ const targets = [stats.q1, stats.median, stats.q3];
567
+ for (const t of targets) {
568
+ const p = valuePos(t);
569
+ const w = t === stats.median ? halfWidth * 0.6 : halfWidth * 0.4;
570
+ const sw = t === stats.median ? c.strokeWidth + 1 : c.strokeWidth;
571
+ if (c.orientation === "y") {
572
+ layer.pushPolyline({
573
+ points: [
574
+ { x: c.ox + bandCenter - w, y: c.oy + p },
575
+ { x: c.ox + bandCenter + w, y: c.oy + p },
576
+ ],
577
+ color: c.stroke,
578
+ width: sw,
579
+ emphasisKey: ek,
580
+ });
581
+ } else {
582
+ layer.pushPolyline({
583
+ points: [
584
+ { x: c.ox + p, y: c.oy + bandCenter - w },
585
+ { x: c.ox + p, y: c.oy + bandCenter + w },
586
+ ],
587
+ color: c.stroke,
588
+ width: sw,
589
+ emphasisKey: ek,
590
+ });
591
+ }
592
+ }
593
+ return;
594
+ }
595
+
596
+ // box: a slim box-and-whisker centered in the violin.
597
+ const boxHalf = Math.min(halfWidth * 0.18, 8);
598
+ const yQ1 = valuePos(stats.q1);
599
+ const yQ3 = valuePos(stats.q3);
600
+ const yMed = valuePos(stats.median);
601
+ const yLo = valuePos(stats.lowerWhisker);
602
+ const yHi = valuePos(stats.upperWhisker);
603
+
604
+ if (c.orientation === "y") {
605
+ const top = Math.min(yQ1, yQ3);
606
+ const bot = Math.max(yQ1, yQ3);
607
+ layer.pushRect({
608
+ x: c.ox + bandCenter - boxHalf,
609
+ y: c.oy + top,
610
+ width: boxHalf * 2,
611
+ height: Math.max(bot - top, 0.5),
612
+ fill: c.fill,
613
+ stroke: c.stroke,
614
+ strokeWidth: c.strokeWidth,
615
+ emphasisKey: ek,
616
+ });
617
+ layer.pushPolyline({
618
+ points: [
619
+ { x: c.ox + bandCenter - boxHalf, y: c.oy + yMed },
620
+ { x: c.ox + bandCenter + boxHalf, y: c.oy + yMed },
621
+ ],
622
+ color: c.stroke,
623
+ width: c.strokeWidth + 1,
624
+ emphasisKey: ek,
625
+ });
626
+ // single whisker line through the box
627
+ const lo = Math.min(yLo, yHi);
628
+ const hi = Math.max(yLo, yHi);
629
+ layer.pushPolyline({
630
+ points: [
631
+ { x: c.ox + bandCenter, y: c.oy + lo },
632
+ { x: c.ox + bandCenter, y: c.oy + hi },
633
+ ],
634
+ color: c.stroke,
635
+ width: c.strokeWidth,
636
+ emphasisKey: ek,
637
+ });
638
+ } else {
639
+ const left = Math.min(yQ1, yQ3);
640
+ const right = Math.max(yQ1, yQ3);
641
+ layer.pushRect({
642
+ x: c.ox + left,
643
+ y: c.oy + bandCenter - boxHalf,
644
+ width: Math.max(right - left, 0.5),
645
+ height: boxHalf * 2,
646
+ fill: c.fill,
647
+ stroke: c.stroke,
648
+ strokeWidth: c.strokeWidth,
649
+ emphasisKey: ek,
650
+ });
651
+ layer.pushPolyline({
652
+ points: [
653
+ { x: c.ox + yMed, y: c.oy + bandCenter - boxHalf },
654
+ { x: c.ox + yMed, y: c.oy + bandCenter + boxHalf },
655
+ ],
656
+ color: c.stroke,
657
+ width: c.strokeWidth + 1,
658
+ emphasisKey: ek,
659
+ });
660
+ const lo = Math.min(yLo, yHi);
661
+ const hi = Math.max(yLo, yHi);
662
+ layer.pushPolyline({
663
+ points: [
664
+ { x: c.ox + lo, y: c.oy + bandCenter },
665
+ { x: c.ox + hi, y: c.oy + bandCenter },
666
+ ],
667
+ color: c.stroke,
668
+ width: c.strokeWidth,
669
+ emphasisKey: ek,
670
+ });
671
+ }
672
+ }
@@ -0,0 +1,22 @@
1
+ export { plot, type AxesSpec, type AxisSpec, type AxisTickFormatter, type AxisTooltipContentResolver, type AxisTooltipInfo, type AxisTooltipRow, type Chart, type LegendMergeChannel, type LegendSpec, type MountedChart, type MountedPlot, type MountPlotOptions, type PanZoomConfig, type PanZoomAxes, type ChartPanBounds, type ChartPanBoundsOptions, type PlotSpec, type RenderTarget, type Row, type ScaleOptionsByChannel, type TitleSpec, } from "./chart.ts";
2
+ export { coordCartesian, coordPolar, coordRadial, type Coord, type CoordAxesArgs, type CoordPanArgs, type CoordZoomArgs, type CoordPolarOptions, type CoordViewportHandle, type Point, } from "./coord.ts";
3
+ export { attachRangePresets, type AttachedRangePresets, type AttachRangePresetsOptions, type AttachRangePresetsPosition, type AttachRangePresetsUi, } from "./attach-presets.ts";
4
+ export { createAttachedBrush, type AttachBrushOptions, type AttachedBrush, type AttachedBrushInternal, } from "./attach-brush.ts";
5
+ export { type AttachedSeriesReadout, type AttachSeriesReadoutOptions, type SeriesReadoutFormat, type SeriesReadoutPosition, type SeriesReadoutRow, type SeriesReadoutSnapshot, type SeriesReadoutUi, } from "./interactions/series-readout.ts";
6
+ export { aggregate, area, band, bar, boxplot, connectedScatter, histogram, interval, line, point, ribbon, ridgeline, rug, rule, smooth, statRolling, text, tile, violin, type AggregateBinBy, type AggregateBinSize, type AggregateBinView, type AggregateBundleResult, type AggregateBundleSummary, type AggregateBundleSummaryKind, type AggregateChannels, type AggregateInnerGeom, type AggregateOptions, type AggregateSummary, type AggregateSummaryKind, type AreaChannels, type AreaOptions, type AreaPosition, type AutoBinSizeInfo, type BandChannels, type BandOptions, type BarChannels, type BarOptions, type BarPosition, type BoxplotChannels, type BoxplotOptions, type ChannelSpec, type CompileContext, type CompiledHitTest, type ConnectedScatterChannels, type ConnectedScatterOptions, type Geom, type GeomKind, type HistogramChannels, type HoveredHit, type HistogramMeasure, type HistogramOptions, type HistogramPosition, type IntervalChannels, type IntervalOptions, type LineChannels, type LineOptions, type PointChannels, type PointOptions, type PointsMode, type ResolvedChannelMap, type RibbonChannels, type RibbonOptions, type RidgelineChannels, type RidgelineFillMode, type RidgelineGeom, type RidgelineInner, type RidgelineOptions, type RidgelineScale, type RugChannels, type RugOptions, type RugSide, type RuleChannels, type RuleOptions, type ScaleBundle, type SmoothChannels, type SmoothLabelOptions, type SmoothMethod, type SmoothOptions, type StatRollingChannels, type StatRollingOptions, type TextChannels, type TextOptions, type TileChannels, type TileNAOptions, type TileOptions, type ViolinChannels, type ViolinInner, type ViolinOptions, type ViolinScale, } from "./geoms/index.ts";
7
+ export type { Accessor, Aes } from "./aes.ts";
8
+ export { fromMatrix, pivotLonger, type FromMatrixOptions, type PivotLongerOptions, } from "./data/pivot.ts";
9
+ export { annotate } from "./annotations.ts";
10
+ export type { AnnotationSpec, AnnotationX, AnnotationY, ArrowAnnotationSpec, CalloutAnnotationSpec, FrameAnchorX, FrameAnchorY, TextAnnotationSpec, } from "./annotations.ts";
11
+ export type { FacetScales, FacetSpec, FacetStripStyle } from "./facet.ts";
12
+ export type { AlphaScaleOptions, BorderStyleScaleOptions, Channel, ColorScaleOptions, ColorScaleType, OverlayGlyphScaleOptions, PositionScaleOptions, PositionScaleType, ShapeScaleOptions, SizeScaleOptions, } from "./scales.ts";
13
+ export { DEFAULT_BORDER_STYLE_PALETTE, DEFAULT_OVERLAY_GLYPH_PALETTE } from "./scales.ts";
14
+ export { distinguishable, palettes, type DistinguishableOptions, type DistinguishablePalettes, type DistinguishableScheme, } from "./palettes.ts";
15
+ export { theme, themeDefault, themeMinimalGrid, type Theme, type ThemeAccentKey, type ThemeAccents, type ThemeAxis, type ThemeInteractions, type ThemeLegend, type ThemeMarks, type ThemePalettes, type ThemeSubtitle, type ThemeText, type ThemeTitle, type ThemeTooltipAccents, type Accessibility, type AccessibilityMode, type TextEffects, } from "./theme.ts";
16
+ export { resolveTextColor, DEFAULT_ACCESSIBILITY, type ContrastMetric, type ResolveTextColorOptions, } from "./accessibility.ts";
17
+ export type { BlendSpace } from "insomni";
18
+ export type { TickIntervalSpec, TickSpec } from "../axis.ts";
19
+ export type { TimeIntervalUnit } from "../scales.ts";
20
+ export type { ColorOrAccent } from "./color-utils.ts";
21
+ export { setRecorderHook, type RecorderHook } from "./profiling.ts";
22
+ export { cladeStripsToAnnotations, tipScale, type CladeStripAnnotationOptions, type CladeStripLike, type TipAxisLike, type TipScaleOptions, } from "./phylo.ts";