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.
- package/LICENSE.md +674 -0
- package/README.md +81 -0
- package/dist/core.d.mts +340 -0
- package/dist/core.mjs +1047 -0
- package/dist/index.d.mts +3426 -0
- package/dist/index.mjs +12762 -0
- package/dist/interactions-DEFL_F4E.mjs +5395 -0
- package/dist/range-presets-CzECsu3V.d.mts +1523 -0
- package/package.json +34 -0
- package/src/annotations.d.ts +121 -0
- package/src/annotations.ts +438 -0
- package/src/axis.d.ts +184 -0
- package/src/axis.test.ts +131 -0
- package/src/axis.ts +765 -0
- package/src/colorbar.d.ts +69 -0
- package/src/colorbar.ts +294 -0
- package/src/colors.d.ts +57 -0
- package/src/colors.test.ts +28 -0
- package/src/colors.ts +486 -0
- package/src/core.ts +299 -0
- package/src/format.d.ts +54 -0
- package/src/format.ts +138 -0
- package/src/grammar/accessibility.d.ts +147 -0
- package/src/grammar/accessibility.test.ts +199 -0
- package/src/grammar/accessibility.ts +443 -0
- package/src/grammar/aes.d.ts +35 -0
- package/src/grammar/aes.test.ts +75 -0
- package/src/grammar/aes.ts +120 -0
- package/src/grammar/annotations.d.ts +86 -0
- package/src/grammar/annotations.test.ts +68 -0
- package/src/grammar/annotations.ts +336 -0
- package/src/grammar/attach-brush.d.ts +44 -0
- package/src/grammar/attach-brush.test.ts +214 -0
- package/src/grammar/attach-brush.ts +111 -0
- package/src/grammar/attach-presets.d.ts +33 -0
- package/src/grammar/attach-presets.test.ts +106 -0
- package/src/grammar/attach-presets.ts +215 -0
- package/src/grammar/chart.d.ts +952 -0
- package/src/grammar/chart.test.ts +118 -0
- package/src/grammar/chart.ts +1172 -0
- package/src/grammar/color-utils.d.ts +29 -0
- package/src/grammar/color-utils.test.ts +53 -0
- package/src/grammar/color-utils.ts +66 -0
- package/src/grammar/constants.d.ts +45 -0
- package/src/grammar/constants.ts +61 -0
- package/src/grammar/coord.d.ts +183 -0
- package/src/grammar/coord.test.ts +355 -0
- package/src/grammar/coord.ts +619 -0
- package/src/grammar/data/pivot.d.ts +57 -0
- package/src/grammar/data/pivot.ts +107 -0
- package/src/grammar/emphasis-driver.d.ts +69 -0
- package/src/grammar/emphasis-driver.test.ts +199 -0
- package/src/grammar/emphasis-driver.ts +205 -0
- package/src/grammar/equality.d.ts +3 -0
- package/src/grammar/equality.ts +40 -0
- package/src/grammar/facet.d.ts +63 -0
- package/src/grammar/facet.test.ts +60 -0
- package/src/grammar/facet.ts +175 -0
- package/src/grammar/geoms/_categorical.d.ts +94 -0
- package/src/grammar/geoms/_categorical.ts +0 -0
- package/src/grammar/geoms/_distribution.d.ts +52 -0
- package/src/grammar/geoms/_distribution.ts +125 -0
- package/src/grammar/geoms/_mark.d.ts +69 -0
- package/src/grammar/geoms/_mark.ts +136 -0
- package/src/grammar/geoms/_shape.d.ts +41 -0
- package/src/grammar/geoms/_shape.ts +74 -0
- package/src/grammar/geoms/aggregate.d.ts +95 -0
- package/src/grammar/geoms/aggregate.test.ts +554 -0
- package/src/grammar/geoms/aggregate.ts +840 -0
- package/src/grammar/geoms/area.d.ts +32 -0
- package/src/grammar/geoms/area.test.ts +165 -0
- package/src/grammar/geoms/area.ts +578 -0
- package/src/grammar/geoms/band.d.ts +27 -0
- package/src/grammar/geoms/band.test.ts +57 -0
- package/src/grammar/geoms/band.ts +126 -0
- package/src/grammar/geoms/bar.d.ts +56 -0
- package/src/grammar/geoms/bar.test.ts +367 -0
- package/src/grammar/geoms/bar.ts +1054 -0
- package/src/grammar/geoms/boxplot.d.ts +129 -0
- package/src/grammar/geoms/boxplot.test.ts +299 -0
- package/src/grammar/geoms/boxplot.ts +834 -0
- package/src/grammar/geoms/connected-scatter.d.ts +27 -0
- package/src/grammar/geoms/connected-scatter.test.ts +157 -0
- package/src/grammar/geoms/connected-scatter.ts +63 -0
- package/src/grammar/geoms/emphasis.d.ts +76 -0
- package/src/grammar/geoms/emphasis.test.ts +135 -0
- package/src/grammar/geoms/emphasis.ts +162 -0
- package/src/grammar/geoms/histogram.d.ts +75 -0
- package/src/grammar/geoms/histogram.test.ts +262 -0
- package/src/grammar/geoms/histogram.ts +740 -0
- package/src/grammar/geoms/index.d.ts +20 -0
- package/src/grammar/geoms/index.ts +77 -0
- package/src/grammar/geoms/interval.d.ts +31 -0
- package/src/grammar/geoms/interval.test.ts +154 -0
- package/src/grammar/geoms/interval.ts +342 -0
- package/src/grammar/geoms/line.d.ts +38 -0
- package/src/grammar/geoms/line.test.ts +247 -0
- package/src/grammar/geoms/line.ts +659 -0
- package/src/grammar/geoms/point.d.ts +57 -0
- package/src/grammar/geoms/point.test.ts +163 -0
- package/src/grammar/geoms/point.ts +545 -0
- package/src/grammar/geoms/polar.test.ts +216 -0
- package/src/grammar/geoms/ribbon.d.ts +21 -0
- package/src/grammar/geoms/ribbon.test.ts +170 -0
- package/src/grammar/geoms/ribbon.ts +87 -0
- package/src/grammar/geoms/ridgeline.d.ts +89 -0
- package/src/grammar/geoms/ridgeline.test.ts +247 -0
- package/src/grammar/geoms/ridgeline.ts +1164 -0
- package/src/grammar/geoms/rolling.d.ts +43 -0
- package/src/grammar/geoms/rolling.test.ts +217 -0
- package/src/grammar/geoms/rolling.ts +387 -0
- package/src/grammar/geoms/rug.d.ts +28 -0
- package/src/grammar/geoms/rug.test.ts +126 -0
- package/src/grammar/geoms/rug.ts +214 -0
- package/src/grammar/geoms/rule.d.ts +23 -0
- package/src/grammar/geoms/rule.test.ts +69 -0
- package/src/grammar/geoms/rule.ts +212 -0
- package/src/grammar/geoms/smooth.d.ts +54 -0
- package/src/grammar/geoms/smooth.test.ts +78 -0
- package/src/grammar/geoms/smooth.ts +337 -0
- package/src/grammar/geoms/text.d.ts +29 -0
- package/src/grammar/geoms/text.test.ts +64 -0
- package/src/grammar/geoms/text.ts +234 -0
- package/src/grammar/geoms/tile.d.ts +61 -0
- package/src/grammar/geoms/tile.test.ts +157 -0
- package/src/grammar/geoms/tile.ts +621 -0
- package/src/grammar/geoms/types.d.ts +319 -0
- package/src/grammar/geoms/types.ts +362 -0
- package/src/grammar/geoms/violin.d.ts +85 -0
- package/src/grammar/geoms/violin.test.ts +187 -0
- package/src/grammar/geoms/violin.ts +672 -0
- package/src/grammar/index.d.ts +22 -0
- package/src/grammar/index.ts +269 -0
- package/src/grammar/interactions/_disposable.d.ts +5 -0
- package/src/grammar/interactions/_disposable.ts +23 -0
- package/src/grammar/interactions/_z.d.ts +4 -0
- package/src/grammar/interactions/_z.ts +16 -0
- package/src/grammar/interactions/brush-selection.test.ts +262 -0
- package/src/grammar/interactions/brush.d.ts +63 -0
- package/src/grammar/interactions/brush.test.ts +483 -0
- package/src/grammar/interactions/brush.ts +452 -0
- package/src/grammar/interactions/crosshair.d.ts +19 -0
- package/src/grammar/interactions/crosshair.test.ts +127 -0
- package/src/grammar/interactions/crosshair.ts +76 -0
- package/src/grammar/interactions/hit-layer.d.ts +64 -0
- package/src/grammar/interactions/hit-layer.ts +246 -0
- package/src/grammar/interactions/legend.d.ts +19 -0
- package/src/grammar/interactions/legend.ts +101 -0
- package/src/grammar/interactions/menu.d.ts +93 -0
- package/src/grammar/interactions/menu.test.ts +373 -0
- package/src/grammar/interactions/menu.ts +342 -0
- package/src/grammar/interactions/selection.d.ts +25 -0
- package/src/grammar/interactions/selection.test.ts +289 -0
- package/src/grammar/interactions/selection.ts +142 -0
- package/src/grammar/interactions/series-readout.d.ts +91 -0
- package/src/grammar/interactions/series-readout.test.ts +668 -0
- package/src/grammar/interactions/series-readout.ts +422 -0
- package/src/grammar/interactions/series-snap.d.ts +70 -0
- package/src/grammar/interactions/series-snap.test.ts +214 -0
- package/src/grammar/interactions/series-snap.ts +218 -0
- package/src/grammar/interactions/tooltip-axis.test.ts +176 -0
- package/src/grammar/interactions/tooltip-touch.browser.test.ts +49 -0
- package/src/grammar/interactions/tooltip-touch.test.ts +161 -0
- package/src/grammar/interactions/tooltip.d.ts +140 -0
- package/src/grammar/interactions/tooltip.test.ts +406 -0
- package/src/grammar/interactions/tooltip.ts +622 -0
- package/src/grammar/interactions/transitions.d.ts +34 -0
- package/src/grammar/interactions/transitions.test.ts +172 -0
- package/src/grammar/interactions/transitions.ts +160 -0
- package/src/grammar/layout.d.ts +68 -0
- package/src/grammar/layout.ts +186 -0
- package/src/grammar/legend-merge.test.ts +332 -0
- package/src/grammar/mount.d.ts +78 -0
- package/src/grammar/mount.test.ts +479 -0
- package/src/grammar/mount.ts +2112 -0
- package/src/grammar/palettes.d.ts +54 -0
- package/src/grammar/palettes.test.ts +80 -0
- package/src/grammar/palettes.ts +167 -0
- package/src/grammar/pan-zoom.test.ts +398 -0
- package/src/grammar/phylo.d.ts +65 -0
- package/src/grammar/phylo.test.ts +59 -0
- package/src/grammar/phylo.ts +112 -0
- package/src/grammar/pipeline.auto-ticks.test.ts +40 -0
- package/src/grammar/pipeline.d.ts +158 -0
- package/src/grammar/pipeline.test.ts +463 -0
- package/src/grammar/pipeline.ts +1233 -0
- package/src/grammar/profiling.d.ts +8 -0
- package/src/grammar/profiling.ts +24 -0
- package/src/grammar/scales.d.ts +188 -0
- package/src/grammar/scales.test.ts +181 -0
- package/src/grammar/scales.ts +800 -0
- package/src/grammar/svg.d.ts +3 -0
- package/src/grammar/svg.ts +39 -0
- package/src/grammar/theme.d.ts +261 -0
- package/src/grammar/theme.test.ts +105 -0
- package/src/grammar/theme.ts +490 -0
- package/src/heatmap/cpu.ts +109 -0
- package/src/heatmap/gpu.ts +565 -0
- package/src/heatmap/types.ts +177 -0
- package/src/heatmap.browser.test.ts +308 -0
- package/src/heatmap.test.ts +320 -0
- package/src/heatmap.ts +123 -0
- package/src/index.d.ts +1 -0
- package/src/index.ts +8 -0
- package/src/interactions.d.ts +48 -0
- package/src/interactions.test.ts +226 -0
- package/src/interactions.ts +394 -0
- package/src/layout/box.d.ts +48 -0
- package/src/layout/box.test.ts +107 -0
- package/src/layout/box.ts +143 -0
- package/src/legend.d.ts +115 -0
- package/src/legend.ts +422 -0
- package/src/marks/curve.d.ts +43 -0
- package/src/marks/curve.ts +244 -0
- package/src/marks/stack.d.ts +53 -0
- package/src/marks/stack.ts +184 -0
- package/src/marks.d.ts +273 -0
- package/src/marks.test.ts +541 -0
- package/src/marks.ts +1292 -0
- package/src/navigator.test.ts +174 -0
- package/src/navigator.ts +393 -0
- package/src/range-presets.d.ts +113 -0
- package/src/range-presets.test.ts +345 -0
- package/src/range-presets.ts +349 -0
- package/src/scales.d.ts +98 -0
- package/src/scales.test.ts +103 -0
- package/src/scales.ts +695 -0
- package/src/stats/index.d.ts +200 -0
- package/src/stats/index.test.ts +349 -0
- package/src/stats/index.ts +740 -0
- package/src/stats/regression.d.ts +38 -0
- package/src/stats/regression.test.ts +56 -0
- package/src/stats/regression.ts +396 -0
- package/src/stats/rolling-window.d.ts +55 -0
- package/src/stats/rolling-window.test.ts +237 -0
- package/src/stats/rolling-window.ts +256 -0
- package/src/test-setup.ts +19 -0
- package/src/viewport/axis-state.d.ts +72 -0
- package/src/viewport/axis-state.ts +476 -0
- package/src/viewport.d.ts +170 -0
- package/src/viewport.test.ts +363 -0
- package/src/viewport.ts +510 -0
package/src/axis.d.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { type Color, type GlyphAtlas, type Layer } from "insomni";
|
|
2
|
+
import { type AxisScale, type TickFormatter, type TimeIntervalUnit } from "./scales.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Explicit time interval for axis ticks: render one tick per `step × unit`
|
|
5
|
+
* regardless of how many fit. `quarter` is sugar for `month, step: 3`. Pairs
|
|
6
|
+
* with {@link AxisOptions.ticks} on time scales — ignored on numeric / band
|
|
7
|
+
* scales (those fall back to a default tick count).
|
|
8
|
+
*/
|
|
9
|
+
export interface TickIntervalSpec {
|
|
10
|
+
interval: TimeIntervalUnit;
|
|
11
|
+
step?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Tick generation strategy on continuous scales:
|
|
15
|
+
* - `number` — target count (current default behavior).
|
|
16
|
+
* - `"auto"` — derive count from the axis's pixel span (~80px/tick horizontal,
|
|
17
|
+
* ~50px/tick vertical). Cheap proxy for "as many ticks as comfortably fit."
|
|
18
|
+
* - `TickIntervalSpec` — explicit time interval (time scales only).
|
|
19
|
+
*
|
|
20
|
+
* `undefined` keeps the previous default of {@link DEFAULT_TICKS}.
|
|
21
|
+
*/
|
|
22
|
+
export type TickSpec = number | "auto" | TickIntervalSpec;
|
|
23
|
+
/**
|
|
24
|
+
* Loose tick formatter signature consumed by the grammar's `AxisSpec.format`.
|
|
25
|
+
* The runtime calls it with the active scale's tick value — `number` for
|
|
26
|
+
* linear/log/sqrt, `Date` for time, `string` for band scales. Typed loose so
|
|
27
|
+
* consumers can write `(v: number) => ...` or `(v: Date) => ...` directly
|
|
28
|
+
* without a wrapping cast; narrow inside the function to the type your scale
|
|
29
|
+
* produces.
|
|
30
|
+
*/
|
|
31
|
+
export type AxisTickFormatter = (value: any) => string;
|
|
32
|
+
export interface AxisOrigin {
|
|
33
|
+
x?: number;
|
|
34
|
+
y?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Configuration for axis tick labels. Currently only carries `maxWidth` —
|
|
38
|
+
* existing `labelFontSize` / `labelColor` / etc. live as flat keys on
|
|
39
|
+
* `AxisOptions` for back-compat. New per-label knobs go here.
|
|
40
|
+
*/
|
|
41
|
+
export interface AxisLabelConfig {
|
|
42
|
+
/**
|
|
43
|
+
* Maximum width in pixels for any tick label. Labels exceeding this width
|
|
44
|
+
* are truncated to the longest prefix that fits with a trailing ellipsis
|
|
45
|
+
* (`…`). Requires `atlas` to measure text. When unset, labels render at
|
|
46
|
+
* their natural width.
|
|
47
|
+
*/
|
|
48
|
+
maxWidth?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Axis title — accepts either a plain string or a config object. The object
|
|
52
|
+
* form lets callers cap the title's rendered width with an ellipsis.
|
|
53
|
+
*/
|
|
54
|
+
export type AxisTitle = string | {
|
|
55
|
+
text: string;
|
|
56
|
+
maxWidth?: number;
|
|
57
|
+
};
|
|
58
|
+
export interface AxisOptions<Value> {
|
|
59
|
+
ticks?: TickSpec;
|
|
60
|
+
tickValues?: readonly Value[];
|
|
61
|
+
/**
|
|
62
|
+
* Render shorter, unlabeled minor tick marks between the major ticks.
|
|
63
|
+
* - Number `N`: subdivide each major interval into `N` parts (so `N=2`
|
|
64
|
+
* places one minor halfway between every adjacent major pair).
|
|
65
|
+
* - Array: explicit minor tick values, used as-is.
|
|
66
|
+
*
|
|
67
|
+
* Minor ticks never render labels and don't affect axis measurement.
|
|
68
|
+
*/
|
|
69
|
+
minorTicks?: number | readonly Value[];
|
|
70
|
+
/** Length of minor tick marks. Default `tickSize / 2`. */
|
|
71
|
+
minorTickSize?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Render a tick label only every `N`th major tick. Default `1` (every
|
|
74
|
+
* major). Use `2` to label every other tick, etc. The hidden ticks still
|
|
75
|
+
* draw their tick mark and grid line. The first tick is always labeled.
|
|
76
|
+
*/
|
|
77
|
+
labelStep?: number;
|
|
78
|
+
/**
|
|
79
|
+
* Automatic label collision avoidance. `"auto"` measures the
|
|
80
|
+
* formatted tick labels and inflates `labelStep` until no two visible
|
|
81
|
+
* labels overlap (with a small padding). Off by default — opt in when
|
|
82
|
+
* the axis density is data-driven (dense band axes, time axes whose
|
|
83
|
+
* domain rescales on zoom). Falls back to `labelStep` when no atlas
|
|
84
|
+
* is available.
|
|
85
|
+
*/
|
|
86
|
+
labelCollision?: "auto";
|
|
87
|
+
format?: TickFormatter<Value>;
|
|
88
|
+
atlas?: GlyphAtlas;
|
|
89
|
+
labels?: boolean;
|
|
90
|
+
tickSize?: number;
|
|
91
|
+
tickWidth?: number;
|
|
92
|
+
tickColor?: Color;
|
|
93
|
+
axisLine?: boolean;
|
|
94
|
+
axisLineWidth?: number;
|
|
95
|
+
axisLineColor?: Color;
|
|
96
|
+
/**
|
|
97
|
+
* Override the axis-line extent in pixels along the axis direction. Defaults
|
|
98
|
+
* to the scale's range. Pass `[0, plotFrame.width]` (or `.height`) to draw
|
|
99
|
+
* the spine across the full plot frame even when the data range is inset
|
|
100
|
+
* (framePadding / `prepareRange` reservations).
|
|
101
|
+
*/
|
|
102
|
+
axisLineExtent?: readonly [number, number];
|
|
103
|
+
labelPadding?: number;
|
|
104
|
+
labelColor?: Color;
|
|
105
|
+
labelFontSize?: number;
|
|
106
|
+
labelFontFamily?: string;
|
|
107
|
+
labelFontWeight?: string;
|
|
108
|
+
labelFontStyle?: string;
|
|
109
|
+
/** Per-label configuration (currently `maxWidth` for ellipsis truncation). */
|
|
110
|
+
label?: AxisLabelConfig;
|
|
111
|
+
gridLines?: boolean;
|
|
112
|
+
gridLength?: number;
|
|
113
|
+
gridWidth?: number;
|
|
114
|
+
gridColor?: Color;
|
|
115
|
+
/**
|
|
116
|
+
* Axis title. Rendered upright alongside the axis — outside the tick
|
|
117
|
+
* labels for horizontal axes, above the axis line for vertical axes.
|
|
118
|
+
* Requires `atlas`. Skipped when undefined / empty. Pass an object form
|
|
119
|
+
* `{ text, maxWidth }` to cap the title width with ellipsis.
|
|
120
|
+
*/
|
|
121
|
+
title?: AxisTitle;
|
|
122
|
+
titlePadding?: number;
|
|
123
|
+
titleFontSize?: number;
|
|
124
|
+
titleFontFamily?: string;
|
|
125
|
+
titleFontWeight?: string;
|
|
126
|
+
titleFontStyle?: string;
|
|
127
|
+
titleColor?: Color;
|
|
128
|
+
}
|
|
129
|
+
export interface TruncateFontOptions {
|
|
130
|
+
fontSize: number;
|
|
131
|
+
fontFamily?: string;
|
|
132
|
+
fontWeight?: string;
|
|
133
|
+
fontStyle?: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Truncate `text` so its rendered width fits within `maxWidth`, appending an
|
|
137
|
+
* ellipsis when the original text is too wide. Returns the original text if
|
|
138
|
+
* it already fits or if measurement is impossible. Uses binary search over
|
|
139
|
+
* prefix lengths for O(log n) `measureText` calls.
|
|
140
|
+
*/
|
|
141
|
+
export declare function truncateToWidth(atlas: GlyphAtlas | undefined, text: string, maxWidth: number | undefined, font: TruncateFontOptions): string;
|
|
142
|
+
export interface AxisMeasurement {
|
|
143
|
+
/**
|
|
144
|
+
* Total perpendicular space the axis needs. For horizontal axes this is
|
|
145
|
+
* tick size + label height + (title padding + title height when present),
|
|
146
|
+
* since the title sits below/above the labels. For vertical axes this is
|
|
147
|
+
* tick size + label width only — the title for vertical axes is rendered
|
|
148
|
+
* above the axis (parallel to the value range) and reported separately
|
|
149
|
+
* via `axialTitleOverhead` so callers can reserve vertical space for it.
|
|
150
|
+
*/
|
|
151
|
+
readonly thickness: number;
|
|
152
|
+
/** Maximum measured width across all formatted tick labels. */
|
|
153
|
+
readonly maxLabelWidth: number;
|
|
154
|
+
/** Tick label height (font size). */
|
|
155
|
+
readonly labelHeight: number;
|
|
156
|
+
/** Whether a title is present and was measured. */
|
|
157
|
+
readonly hasTitle: boolean;
|
|
158
|
+
/** Measured title font size (0 if no title). */
|
|
159
|
+
readonly titleHeight: number;
|
|
160
|
+
/**
|
|
161
|
+
* Pixels the axis title needs *along* the value-range axis, on top of the
|
|
162
|
+
* axis itself. Non-zero only for vertical axes with a title (which is
|
|
163
|
+
* rendered above the axis line). Horizontal axes return 0 — their title's
|
|
164
|
+
* footprint is already included in `thickness`.
|
|
165
|
+
*/
|
|
166
|
+
readonly axialTitleOverhead: number;
|
|
167
|
+
}
|
|
168
|
+
export interface AxisBuilder<Value> {
|
|
169
|
+
addTo(layer: Layer, origin?: AxisOrigin): Layer;
|
|
170
|
+
/**
|
|
171
|
+
* Measure how much room the axis will occupy on its perpendicular axis.
|
|
172
|
+
* Use the result as `AXIS_MARGIN.bottom/left/...` so callers don't have to
|
|
173
|
+
* hand-tune padding for long tick labels or axis titles.
|
|
174
|
+
*
|
|
175
|
+
* Requires an atlas — pass either `options.atlas` at construction or one
|
|
176
|
+
* via the `atlas` argument here.
|
|
177
|
+
*/
|
|
178
|
+
measure(atlas?: GlyphAtlas): AxisMeasurement;
|
|
179
|
+
readonly tickValues: readonly Value[];
|
|
180
|
+
}
|
|
181
|
+
export declare function bottomAxis<Value>(scale: AxisScale<Value>, options?: AxisOptions<Value>): AxisBuilder<Value>;
|
|
182
|
+
export declare function leftAxis<Value>(scale: AxisScale<Value>, options?: AxisOptions<Value>): AxisBuilder<Value>;
|
|
183
|
+
export declare function topAxis<Value>(scale: AxisScale<Value>, options?: AxisOptions<Value>): AxisBuilder<Value>;
|
|
184
|
+
export declare function rightAxis<Value>(scale: AxisScale<Value>, options?: AxisOptions<Value>): AxisBuilder<Value>;
|
package/src/axis.test.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { describe, expect, test } from "vite-plus/test";
|
|
3
|
+
|
|
4
|
+
import { bottomAxis, leftAxis } from "./axis.ts";
|
|
5
|
+
import { bandScale, linearScale, timeScale } from "./scales.ts";
|
|
6
|
+
import { createLayer } from "insomni";
|
|
7
|
+
import { INSTANCE_BYTES } from "insomni/advanced";
|
|
8
|
+
|
|
9
|
+
interface RecordedText {
|
|
10
|
+
text: string;
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
align?: "left" | "center" | "right";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeFakeTextAtlas(): any {
|
|
17
|
+
return {
|
|
18
|
+
measureText(text: string, opts: { fontSize: number }) {
|
|
19
|
+
return { width: text.length * (opts.fontSize ?? 12) * 0.6, height: opts.fontSize ?? 12 };
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeRecordingLayer(): { layer: ReturnType<typeof createLayer>; texts: RecordedText[] } {
|
|
25
|
+
const texts: RecordedText[] = [];
|
|
26
|
+
// v3 has no `textPack` layer option — text shapes route through the layer's
|
|
27
|
+
// `pushText`, which requires an atlas. We mint a layer with a stub atlas and
|
|
28
|
+
// override `pushText` to record the call (and skip the real glyph shaping).
|
|
29
|
+
const layer = createLayer({ space: "ui", atlas: makeFakeTextAtlas() });
|
|
30
|
+
layer.pushText = ((shape: RecordedText) => {
|
|
31
|
+
texts.push({ text: shape.text, x: shape.x, y: shape.y, align: shape.align });
|
|
32
|
+
const bbox = { minX: shape.x, minY: shape.y, maxX: shape.x + 1, maxY: shape.y + 1 };
|
|
33
|
+
return { width: 0, height: 0, layoutHeight: 0, lineCount: 1, bbox, count: 1 };
|
|
34
|
+
}) as unknown as typeof layer.pushText;
|
|
35
|
+
return { layer, texts };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe("axis helpers", () => {
|
|
39
|
+
test("emit axis, tick, and grid line shapes directly into a layer", () => {
|
|
40
|
+
const layer = createLayer({ space: "ui" });
|
|
41
|
+
bottomAxis(linearScale([0, 100], [0, 200]), {
|
|
42
|
+
ticks: 2,
|
|
43
|
+
labels: false,
|
|
44
|
+
gridLines: true,
|
|
45
|
+
gridLength: 50,
|
|
46
|
+
}).addTo(layer, { x: 10, y: 80 });
|
|
47
|
+
|
|
48
|
+
// 1 axis line + 2 ticks + (axis baseline grid?) → 7 stroked segments.
|
|
49
|
+
// v3's `Layer.shapeCount` only counts SDF closed shapes (rect/circle/
|
|
50
|
+
// ellipse); axis lines are segments, so count packed instances directly.
|
|
51
|
+
const instanceCount = layer.pack.byteLength / INSTANCE_BYTES;
|
|
52
|
+
expect(instanceCount).toBe(7);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("draws axis geometry without an atlas (labels skipped, no throw)", () => {
|
|
56
|
+
const layer = createLayer({ space: "ui" });
|
|
57
|
+
|
|
58
|
+
// Without an atlas, labels are skipped but geometry (axis line + ticks +
|
|
59
|
+
// grid lines) still emits — no throw.
|
|
60
|
+
expect(() =>
|
|
61
|
+
bottomAxis(linearScale([0, 1], [0, 10]), {
|
|
62
|
+
ticks: 2,
|
|
63
|
+
gridLines: true,
|
|
64
|
+
gridLength: 50,
|
|
65
|
+
}).addTo(layer, { x: 0, y: 10 }),
|
|
66
|
+
).not.toThrow();
|
|
67
|
+
expect(layer.pack.byteLength / INSTANCE_BYTES).toBeGreaterThan(0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("center categorical labels within each band", () => {
|
|
71
|
+
const { layer, texts } = makeRecordingLayer();
|
|
72
|
+
const atlas = makeFakeTextAtlas();
|
|
73
|
+
bottomAxis(bandScale(["A", "B"], [0, 100]), {
|
|
74
|
+
atlas,
|
|
75
|
+
ticks: 2,
|
|
76
|
+
}).addTo(layer, { x: 20, y: 50 });
|
|
77
|
+
|
|
78
|
+
expect(texts).toHaveLength(2);
|
|
79
|
+
expect(texts[0]?.text).toBe("A");
|
|
80
|
+
expect(texts[0]?.x).toBe(45);
|
|
81
|
+
expect(texts[0]?.align).toBe("center");
|
|
82
|
+
expect(texts[1]?.text).toBe("B");
|
|
83
|
+
expect(texts[1]?.x).toBe(95);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("ticks: 'auto' picks count from the axis pixel span", () => {
|
|
87
|
+
const wide = bottomAxis(linearScale([0, 100], [0, 800]), { ticks: "auto" });
|
|
88
|
+
// 800px / 80px-per-tick ≈ 10 → ~10 ticks across the domain (snapped to nice steps).
|
|
89
|
+
expect(wide.tickValues.length).toBeGreaterThanOrEqual(8);
|
|
90
|
+
|
|
91
|
+
const narrow = bottomAxis(linearScale([0, 100], [0, 160]), { ticks: "auto" });
|
|
92
|
+
// 160px / 80px-per-tick ≈ 2 → handful of ticks (nice-snapped).
|
|
93
|
+
expect(narrow.tickValues.length).toBeLessThanOrEqual(wide.tickValues.length);
|
|
94
|
+
|
|
95
|
+
const tall = leftAxis(linearScale([0, 100], [400, 0]), { ticks: "auto" });
|
|
96
|
+
// Vertical orientation uses tighter spacing (~50px/tick).
|
|
97
|
+
expect(tall.tickValues.length).toBeGreaterThanOrEqual(5);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("ticks: { interval } emits explicit time ticks on time scales", () => {
|
|
101
|
+
const scale = timeScale(
|
|
102
|
+
[new Date("2026-01-01T00:00:00Z"), new Date("2026-12-31T00:00:00Z")],
|
|
103
|
+
[0, 600],
|
|
104
|
+
);
|
|
105
|
+
const monthly = bottomAxis(scale, { ticks: { interval: "month" } });
|
|
106
|
+
expect(monthly.tickValues).toHaveLength(12);
|
|
107
|
+
|
|
108
|
+
const quarterly = bottomAxis(scale, { ticks: { interval: "quarter" } });
|
|
109
|
+
expect(quarterly.tickValues).toHaveLength(4);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("ticks: { interval } falls back to default count on non-time scales", () => {
|
|
113
|
+
const numeric = bottomAxis(linearScale([0, 100], [0, 200]), {
|
|
114
|
+
ticks: { interval: "month" },
|
|
115
|
+
});
|
|
116
|
+
// Falls back to DEFAULT_TICKS (5) — numeric `[0, 100]` with step 20.
|
|
117
|
+
expect(numeric.tickValues).toEqual([0, 20, 40, 60, 80, 100]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("use end-aligned labels for left axes", () => {
|
|
121
|
+
const { layer, texts } = makeRecordingLayer();
|
|
122
|
+
const atlas = makeFakeTextAtlas();
|
|
123
|
+
leftAxis(linearScale([0, 100], [100, 0]), {
|
|
124
|
+
ticks: 2,
|
|
125
|
+
atlas,
|
|
126
|
+
}).addTo(layer, { x: 40, y: 10 });
|
|
127
|
+
|
|
128
|
+
expect(texts.length).toBeGreaterThan(0);
|
|
129
|
+
expect(texts[0]?.align).toBe("right");
|
|
130
|
+
});
|
|
131
|
+
});
|