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,8 @@
1
+ export interface RecorderHook {
2
+ recordCpu(name: string, durationMs: number): void;
3
+ recordGpu(name: string, startNs: number, endNs: number): void;
4
+ }
5
+ /** Inject a recorder (e.g. devtools' `recordCpu`/`recordGpu`). Pass `null` to reset to no-op. */
6
+ export declare function setRecorderHook(rec: RecorderHook | null): void;
7
+ export declare function recordCpu(name: string, durationMs: number): void;
8
+ export declare function recordGpu(name: string, startNs: number, endNs: number): void;
@@ -0,0 +1,24 @@
1
+ export interface RecorderHook {
2
+ recordCpu(name: string, durationMs: number): void;
3
+ recordGpu(name: string, startNs: number, endNs: number): void;
4
+ }
5
+
6
+ const NOOP: RecorderHook = {
7
+ recordCpu() {},
8
+ recordGpu() {},
9
+ };
10
+
11
+ let _rec: RecorderHook = NOOP;
12
+
13
+ /** Inject a recorder (e.g. devtools' `recordCpu`/`recordGpu`). Pass `null` to reset to no-op. */
14
+ export function setRecorderHook(rec: RecorderHook | null): void {
15
+ _rec = rec ?? NOOP;
16
+ }
17
+
18
+ export function recordCpu(name: string, durationMs: number): void {
19
+ _rec.recordCpu(name, durationMs);
20
+ }
21
+
22
+ export function recordGpu(name: string, startNs: number, endNs: number): void {
23
+ _rec.recordGpu(name, startNs, endNs);
24
+ }
@@ -0,0 +1,188 @@
1
+ import type { Color } from "insomni";
2
+ import { type BandScale, type ContinuousScale, type NumericRange, type TimeScale } from "../scales.ts";
3
+ import { type CategoricalPalette, type ContinuousPalette } from "../colors.ts";
4
+ import { type PointBorderStyle, type PointShapeKind } from "../marks.ts";
5
+ import { type ChannelDataType, type ResolvedAes } from "./aes.ts";
6
+ import type { Theme } from "./theme.ts";
7
+ export type { ScaleBundle } from "./geoms/types.ts";
8
+ export type Channel = "x" | "y" | "color" | "size" | "shape" | "alpha" | "borderStyle" | "overlayGlyph";
9
+ export type PositionScaleType = "linear" | "log" | "sqrt" | "time" | "band";
10
+ export type ColorScaleType = "categorical" | "continuous" | "diverging";
11
+ interface BasePositionScaleOptions {
12
+ range?: NumericRange;
13
+ nice?: boolean;
14
+ padding?: number;
15
+ }
16
+ /**
17
+ * `"nice"` is sugar for the existing `nice: true` flag — set as the domain
18
+ * shortcut so consumers don't need a second toggle when the only reason to
19
+ * touch `domain` was to pad it to round values. The data extent still
20
+ * derives the underlying numbers; `"nice"` just toggles the rounding.
21
+ */
22
+ export type NumericDomainShortcut = "nice";
23
+ export interface NumericPositionScaleOptions extends BasePositionScaleOptions {
24
+ type?: "linear" | "log" | "sqrt";
25
+ domain?: readonly [number, number] | NumericDomainShortcut;
26
+ }
27
+ export interface TimePositionScaleOptions extends BasePositionScaleOptions {
28
+ type?: "time";
29
+ domain?: readonly [Date, Date];
30
+ }
31
+ export interface BandPositionScaleOptions extends BasePositionScaleOptions {
32
+ type?: "band";
33
+ domain?: readonly string[];
34
+ }
35
+ export type PositionScaleOptions = NumericPositionScaleOptions | TimePositionScaleOptions | BandPositionScaleOptions;
36
+ export interface CategoricalColorScaleOptions<T> {
37
+ type?: "categorical";
38
+ domain?: readonly T[];
39
+ palette?: CategoricalPalette;
40
+ }
41
+ /**
42
+ * Color-scale domain shortcuts.
43
+ *
44
+ * - `"nice"`: alias for the existing `nice: true` flag on continuous color
45
+ * scales — pad the data extent to round values.
46
+ * - `"quantile"`: bucket the data into N quantile bins (default 5). The
47
+ * returned scale's `type` becomes `"categorical"` with bucket-label
48
+ * domain entries (`"Q1".."QN"`), and the palette is sampled at N evenly
49
+ * spaced stops along the chart's continuous palette so the color
50
+ * gradient reads as ordinal magnitude rather than nominal category.
51
+ */
52
+ export type ColorDomainShortcut = "nice" | "quantile";
53
+ export interface ContinuousColorScaleOptions {
54
+ type: "continuous" | "diverging";
55
+ domain?: readonly [number, number] | ColorDomainShortcut;
56
+ palette?: ContinuousPalette;
57
+ /**
58
+ * Extend the inferred domain outward to "nice" round values (e.g. data
59
+ * extent `[-2.8, 1.9]` → `[-3, 2]`). Same algorithm position scales use
60
+ * via `nice: true`. Ignored when `domain` is set explicitly to a tuple.
61
+ * Equivalent to `domain: "nice"` — keep one form per chart for clarity.
62
+ */
63
+ nice?: boolean;
64
+ /**
65
+ * Number of quantile buckets to use when `domain: "quantile"`. Default 5
66
+ * (quintiles). Ignored otherwise.
67
+ */
68
+ quantiles?: number;
69
+ /**
70
+ * Override the color space used to interpolate the palette stops.
71
+ * Falls back to `theme.paletteBlendSpace` (default `"oklch"`).
72
+ */
73
+ blendSpace?: import("insomni").BlendSpace;
74
+ }
75
+ export type ColorScaleOptions<T> = CategoricalColorScaleOptions<T> | ContinuousColorScaleOptions;
76
+ export interface SizeScaleOptions {
77
+ type?: "linear" | "sqrt";
78
+ domain?: readonly [number, number];
79
+ range?: readonly [number, number];
80
+ }
81
+ export interface AlphaScaleOptions {
82
+ domain?: readonly [number, number];
83
+ range?: readonly [number, number];
84
+ }
85
+ export interface ShapeScaleOptions {
86
+ /** Explicit category order (default: order of first appearance). */
87
+ domain?: readonly unknown[];
88
+ /** Override the shape palette. Default: `POINT_SHAPE_PALETTE` from `marks`. */
89
+ palette?: readonly PointShapeKind[];
90
+ }
91
+ export interface BorderStyleScaleOptions {
92
+ /** Explicit category order (default: order of first appearance). */
93
+ domain?: readonly unknown[];
94
+ /** Override the border-style palette. Default: `DEFAULT_BORDER_STYLE_PALETTE`. */
95
+ palette?: readonly PointBorderStyle[];
96
+ }
97
+ export interface OverlayGlyphScaleOptions {
98
+ /** Explicit category order (default: order of first appearance). */
99
+ domain?: readonly unknown[];
100
+ /**
101
+ * Override the overlay palette. Default: a small subset of
102
+ * `POINT_SHAPE_PALETTE` excluding `circle` so the overlay is visually
103
+ * distinct from the typical base shape. Use `null` entries to suppress an
104
+ * overlay for that domain value.
105
+ */
106
+ palette?: readonly (PointShapeKind | null)[];
107
+ }
108
+ export type ScaleOptions = PositionScaleOptions | ColorScaleOptions<unknown> | SizeScaleOptions | AlphaScaleOptions | ShapeScaleOptions | BorderStyleScaleOptions | OverlayGlyphScaleOptions;
109
+ /** A scale callable: any value → number (or color, for color channels). */
110
+ export type ScaleFn<In, Out> = (value: In) => Out;
111
+ export interface PositionScale {
112
+ readonly kind: "position";
113
+ readonly type: PositionScaleType;
114
+ readonly dataType: ChannelDataType;
115
+ /** Pixel-space transform within the plot frame. */
116
+ readonly fn: ScaleFn<unknown, number>;
117
+ /** Underlying scale for axis builders. */
118
+ readonly axisScale: ContinuousScale | TimeScale | BandScale<string>;
119
+ }
120
+ export interface ColorScale {
121
+ readonly kind: "color";
122
+ readonly dataType: ChannelDataType;
123
+ readonly type: ColorScaleType;
124
+ readonly fn: ScaleFn<unknown, Color>;
125
+ readonly domain: readonly unknown[];
126
+ /** Original palette — present for continuous/diverging scales (used by the color-bar legend). */
127
+ readonly palette?: ContinuousPalette | CategoricalPalette;
128
+ }
129
+ export interface SizeScale {
130
+ readonly kind: "size";
131
+ readonly fn: ScaleFn<unknown, number>;
132
+ readonly domain: readonly [number, number];
133
+ readonly range: readonly [number, number];
134
+ }
135
+ export interface AlphaScale {
136
+ readonly kind: "alpha";
137
+ readonly fn: ScaleFn<unknown, number>;
138
+ readonly domain: readonly [number, number];
139
+ readonly range: readonly [number, number];
140
+ }
141
+ export interface ShapeScale {
142
+ readonly kind: "shape";
143
+ readonly fn: ScaleFn<unknown, PointShapeKind>;
144
+ readonly domain: readonly unknown[];
145
+ readonly palette: readonly PointShapeKind[];
146
+ }
147
+ export interface BorderStyleScale {
148
+ readonly kind: "borderStyle";
149
+ readonly fn: ScaleFn<unknown, PointBorderStyle>;
150
+ readonly domain: readonly unknown[];
151
+ readonly palette: readonly PointBorderStyle[];
152
+ }
153
+ export interface OverlayGlyphScale {
154
+ readonly kind: "overlayGlyph";
155
+ readonly fn: ScaleFn<unknown, PointShapeKind | null>;
156
+ readonly domain: readonly unknown[];
157
+ readonly palette: readonly (PointShapeKind | null)[];
158
+ }
159
+ export type ResolvedScale = PositionScale | ColorScale | SizeScale | AlphaScale | ShapeScale | BorderStyleScale | OverlayGlyphScale;
160
+ /**
161
+ * Numeric extent over an arbitrary value array. Non-numeric values are skipped.
162
+ * Falls back to `[0, 1]` for empty / non-numeric input. Expands a degenerate
163
+ * single-point domain by ±1 so downstream scales don't divide by zero.
164
+ */
165
+ export declare function numericExtent(values: readonly unknown[]): [number, number];
166
+ export declare function readNumericDomain(scale: PositionScaleOptions | undefined, axis: "x" | "y"): readonly [number, number];
167
+ export declare function readContinuousType(scale: PositionScaleOptions | undefined): "linear" | "log" | "sqrt";
168
+ export declare function buildPositionScale<T>(aes: ResolvedAes<T, unknown>, data: readonly T[], range: NumericRange, options?: PositionScaleOptions): PositionScale;
169
+ export declare function buildColorScale<T>(aes: ResolvedAes<T, unknown>, data: readonly T[], theme: Theme, options?: ColorScaleOptions<unknown>): ColorScale;
170
+ /**
171
+ * Build a categorical color scale from an explicit key list. Shared between
172
+ * `buildColorScale` (data-derived domain) and the chart pipeline's implicit
173
+ * series scale (keys = column names). User-supplied `options.palette` /
174
+ * `options.domain` win over the inferred values.
175
+ */
176
+ export declare function buildCategoricalColorScale(keys: readonly unknown[], theme: Theme, options?: ColorScaleOptions<unknown>, dataType?: ChannelDataType): ColorScale;
177
+ export declare function buildSizeScale<T>(aes: ResolvedAes<T, number>, data: readonly T[], options?: SizeScaleOptions): SizeScale;
178
+ export declare function buildAlphaScale<T>(aes: ResolvedAes<T, number>, data: readonly T[], options?: AlphaScaleOptions): AlphaScale;
179
+ export declare function buildShapeScale<T>(aes: ResolvedAes<T, unknown>, data: readonly T[], options?: ShapeScaleOptions): ShapeScale;
180
+ export declare const DEFAULT_BORDER_STYLE_PALETTE: readonly PointBorderStyle[];
181
+ export declare function buildBorderStyleScale<T>(aes: ResolvedAes<T, unknown>, data: readonly T[], options?: BorderStyleScaleOptions): BorderStyleScale;
182
+ /**
183
+ * Default overlay palette. First slot is `null` so the most-common category
184
+ * gets no overlay — overlays are typically a minority styling for flagged /
185
+ * exceptional rows. The remaining slots are visually distinct glyphs.
186
+ */
187
+ export declare const DEFAULT_OVERLAY_GLYPH_PALETTE: readonly (PointShapeKind | null)[];
188
+ export declare function buildOverlayGlyphScale<T>(aes: ResolvedAes<T, unknown>, data: readonly T[], options?: OverlayGlyphScaleOptions): OverlayGlyphScale;
@@ -0,0 +1,181 @@
1
+ import { describe, expect, test } from "vite-plus/test";
2
+
3
+ import { resolveAes } from "./aes.ts";
4
+ import { buildColorScale, buildPositionScale } from "./scales.ts";
5
+ import { themeDefault } from "./theme.ts";
6
+
7
+ interface Row {
8
+ v: number;
9
+ }
10
+
11
+ describe("buildPositionScale — log/sqrt", () => {
12
+ const aes = resolveAes<Row, unknown>("v");
13
+
14
+ test("type: 'log' returns a log-scale position with log-aware ticks", () => {
15
+ const data: Row[] = [{ v: 1 }, { v: 10 }, { v: 100 }, { v: 1000 }];
16
+ const scale = buildPositionScale(aes, data, [0, 300], { type: "log", domain: [1, 1000] });
17
+ expect(scale.type).toBe("log");
18
+ // pixel midpoint of [1, 1000] in log space is √(1·1000) ≈ 31.62.
19
+ expect(scale.fn(31.622776601683793)).toBeCloseTo(150, 1);
20
+ // Log-aware ticks land on round powers, not linear spacing.
21
+ const axisScale = scale.axisScale as { ticks: (count?: number) => readonly number[] };
22
+ const ticks = axisScale.ticks(4);
23
+ expect(ticks).toContain(1);
24
+ expect(ticks).toContain(10);
25
+ expect(ticks).toContain(100);
26
+ expect(ticks).toContain(1000);
27
+ });
28
+
29
+ test("type: 'log' throws for a domain that straddles or touches zero", () => {
30
+ const data: Row[] = [{ v: -1 }, { v: 1 }];
31
+ expect(() => buildPositionScale(aes, data, [0, 100], { type: "log" })).toThrow(/Log scale/);
32
+ expect(() => buildPositionScale(aes, data, [0, 100], { type: "log", domain: [0, 10] })).toThrow(
33
+ /Log scale/,
34
+ );
35
+ });
36
+
37
+ test("type: 'sqrt' preserves square-root spacing on the position scale", () => {
38
+ const data: Row[] = [{ v: 0 }, { v: 100 }];
39
+ const scale = buildPositionScale(aes, data, [0, 100], { type: "sqrt", domain: [0, 100] });
40
+ expect(scale.type).toBe("sqrt");
41
+ // sqrt(25)/sqrt(100) = 0.5 → midpoint of range.
42
+ expect(scale.fn(25)).toBeCloseTo(50, 1);
43
+ });
44
+
45
+ test("default numeric scale stays linear when no type is set", () => {
46
+ const data: Row[] = [{ v: 0 }, { v: 100 }];
47
+ const scale = buildPositionScale(aes, data, [0, 100], { domain: [0, 100] });
48
+ expect(scale.type).toBe("linear");
49
+ expect(scale.fn(50)).toBeCloseTo(50, 1);
50
+ });
51
+ });
52
+
53
+ describe("buildPositionScale — domain shortcuts", () => {
54
+ const aes = resolveAes<Row, unknown>("v");
55
+
56
+ test("domain: 'nice' pads the data extent to round values", () => {
57
+ const data: Row[] = [{ v: -2.8 }, { v: 1.9 }];
58
+ const scale = buildPositionScale(aes, data, [0, 100], { domain: "nice" });
59
+ const [lo, hi] = scale.axisScale.domain as readonly [number, number];
60
+ expect(lo).toBeLessThanOrEqual(-2.8);
61
+ expect(hi).toBeGreaterThanOrEqual(1.9);
62
+ // niceNumeric collapses fractional bounds to round values.
63
+ expect(lo).toBe(-3);
64
+ expect(hi).toBe(2);
65
+ });
66
+
67
+ test("explicit tuple domain still wins over the 'nice' shortcut behavior", () => {
68
+ const data: Row[] = [{ v: 0 }, { v: 100 }];
69
+ const scale = buildPositionScale(aes, data, [0, 100], { domain: [12, 88] });
70
+ expect(scale.axisScale.domain).toEqual([12, 88]);
71
+ });
72
+ });
73
+
74
+ describe("buildColorScale — domain shortcuts", () => {
75
+ const aes = resolveAes<Row, unknown>("v");
76
+
77
+ test("domain: 'quantile' buckets the data and returns a categorical scale", () => {
78
+ // 10 values, quintiles → 5 buckets of 2 values each.
79
+ const data: Row[] = Array.from({ length: 10 }, (_, i) => ({ v: i + 1 }));
80
+ const scale = buildColorScale(aes, data, themeDefault, {
81
+ type: "continuous",
82
+ domain: "quantile",
83
+ quantiles: 5,
84
+ });
85
+ expect(scale.type).toBe("categorical");
86
+ expect(scale.domain).toEqual(["Q1", "Q2", "Q3", "Q4", "Q5"]);
87
+ // Lowest value lands in Q1; highest in Q5; bucket colors should differ
88
+ // (sampled at distinct stops along the continuous palette).
89
+ const lo = scale.fn(1);
90
+ const hi = scale.fn(10);
91
+ expect(lo).not.toEqual(hi);
92
+ // The mid-range value lands in the middle bucket.
93
+ const mid = scale.fn(5);
94
+ expect(mid).not.toEqual(lo);
95
+ expect(mid).not.toEqual(hi);
96
+ });
97
+
98
+ test("domain: 'quantile' default count is 5 quintiles", () => {
99
+ const data: Row[] = Array.from({ length: 20 }, (_, i) => ({ v: i }));
100
+ const scale = buildColorScale(aes, data, themeDefault, {
101
+ type: "continuous",
102
+ domain: "quantile",
103
+ });
104
+ expect(scale.domain.length).toBe(5);
105
+ });
106
+
107
+ test("domain: 'nice' on continuous color scale pads to round values", () => {
108
+ const data: Row[] = [{ v: -2.8 }, { v: 1.9 }];
109
+ const scale = buildColorScale(aes, data, themeDefault, {
110
+ type: "continuous",
111
+ domain: "nice",
112
+ });
113
+ expect(scale.type).toBe("continuous");
114
+ expect(scale.domain).toEqual([-3, 2]);
115
+ });
116
+
117
+ test("non-finite input on a quantile scale falls back to the first bucket", () => {
118
+ const data: Row[] = [{ v: 1 }, { v: 2 }, { v: 3 }];
119
+ const scale = buildColorScale(aes, data, themeDefault, {
120
+ type: "continuous",
121
+ domain: "quantile",
122
+ quantiles: 3,
123
+ });
124
+ expect(() => scale.fn(Number.NaN)).not.toThrow();
125
+ expect(scale.fn(Number.NaN)).toEqual(scale.fn(-Infinity));
126
+ });
127
+
128
+ test("domain: 'quantile' dedupes tied interior breakpoints into reachable buckets", () => {
129
+ // Low-cardinality data: a 0, eight 5s, and a 10. Every quintile breakpoint
130
+ // (p=0.2/0.4/0.6/0.8) lands on 5 — raw breakpoints are [5, 5, 5, 5].
131
+ // Asking for 5 buckets is meaningless: four identical breakpoints would
132
+ // leave three buckets unreachable (no value can be > 5 AND <= 5), skewing
133
+ // the palette. Deduping to the single distinct interior breakpoint (5)
134
+ // yields exactly 2 reachable buckets: {0, 5} in Q1, {10} in Q2.
135
+ const data: Row[] = [
136
+ { v: 0 },
137
+ { v: 5 },
138
+ { v: 5 },
139
+ { v: 5 },
140
+ { v: 5 },
141
+ { v: 5 },
142
+ { v: 5 },
143
+ { v: 5 },
144
+ { v: 5 },
145
+ { v: 10 },
146
+ ];
147
+ const scale = buildColorScale(aes, data, themeDefault, {
148
+ type: "continuous",
149
+ domain: "quantile",
150
+ quantiles: 5,
151
+ });
152
+ // 1 distinct interior breakpoint (5) → 2 reachable buckets, labelled Q1..Q2.
153
+ expect(scale.domain).toEqual(["Q1", "Q2"]);
154
+
155
+ // Every reachable bucket is actually produced by some input value — none
156
+ // is an unreachable, palette-skewing dead bucket.
157
+ const reached = new Set<string>();
158
+ for (const v of [0, 5, 10]) reached.add(JSON.stringify(scale.fn(v)));
159
+ expect(reached.size).toBe(2);
160
+
161
+ // Left-closed: a value exactly on the breakpoint (5) falls into the lower
162
+ // bucket alongside the minimum; only the tail (10) reaches Q2.
163
+ const lo = scale.fn(0);
164
+ const hi = scale.fn(10);
165
+ expect(scale.fn(5)).toEqual(lo);
166
+ expect(lo).not.toEqual(hi);
167
+ });
168
+
169
+ test("domain: 'quantile' on all-equal data collapses to one bucket", () => {
170
+ // No interior breakpoints (min === max) → a single reachable bucket
171
+ // rather than five identical-but-nominally-distinct ones.
172
+ const data: Row[] = Array.from({ length: 8 }, () => ({ v: 5 }));
173
+ const scale = buildColorScale(aes, data, themeDefault, {
174
+ type: "continuous",
175
+ domain: "quantile",
176
+ quantiles: 5,
177
+ });
178
+ expect(scale.domain).toEqual(["Q1"]);
179
+ expect(scale.fn(5)).toEqual(scale.fn(5));
180
+ });
181
+ });