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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Static SVG export — runs the pipeline once into an off-DOM SVG renderer.
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import { createLayer, createSVGRenderer, type GlyphAtlas } from "insomni";
|
|
6
|
+
import { sharedCpuGlyphAtlas } from "insomni/text-ttf";
|
|
7
|
+
import { DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH } from "./constants.ts";
|
|
8
|
+
import { currentData, runPipeline, type ChartConfig } from "./pipeline.ts";
|
|
9
|
+
|
|
10
|
+
export function renderSVG<T>(
|
|
11
|
+
config: ChartConfig<T>,
|
|
12
|
+
width: number | undefined,
|
|
13
|
+
height: number | undefined,
|
|
14
|
+
externalAtlas?: GlyphAtlas,
|
|
15
|
+
): SVGSVGElement {
|
|
16
|
+
const w = width ?? config.width ?? DEFAULT_CHART_WIDTH;
|
|
17
|
+
const h = height ?? config.height ?? DEFAULT_CHART_HEIGHT;
|
|
18
|
+
const renderer = createSVGRenderer({ width: w, height: h, dpr: 1 });
|
|
19
|
+
renderer.setBackground(config.background ?? config.theme.background);
|
|
20
|
+
|
|
21
|
+
// SVG path: resolve the glyph atlas for text rendering (axis labels,
|
|
22
|
+
// legend, title, value-label annotations). `externalAtlas` and
|
|
23
|
+
// `config.externalAtlas` take precedence; when neither is supplied,
|
|
24
|
+
// `sharedCpuGlyphAtlas()` provides a zero-GPU default backed by the bundled
|
|
25
|
+
// Lato subset — so `chart.toSVG()` emits `<text>` elements with no extra
|
|
26
|
+
// setup from the caller.
|
|
27
|
+
const atlas = externalAtlas ?? config.externalAtlas ?? sharedCpuGlyphAtlas();
|
|
28
|
+
|
|
29
|
+
const axisLayer = createLayer({ space: "ui", atlas, retainAllPushRecords: true });
|
|
30
|
+
const marksLayer = createLayer({ space: "ui", atlas, retainAllPushRecords: true });
|
|
31
|
+
const hudLayer = createLayer({ space: "ui", atlas, retainAllPushRecords: true });
|
|
32
|
+
|
|
33
|
+
const snapshot: ChartConfig<T> = { ...config, width: w, height: h };
|
|
34
|
+
runPipeline(snapshot, currentData(config.data), axisLayer, marksLayer, hudLayer, atlas);
|
|
35
|
+
// v3 SVG renders shapes in pixel space; `ui`-space layers are scaled by dpr
|
|
36
|
+
// (1 here). `.element()` is a METHOD on the v3 backend (was a getter in v1).
|
|
37
|
+
renderer.render([axisLayer, marksLayer, hudLayer]);
|
|
38
|
+
return renderer.element();
|
|
39
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { type BlendSpace, type Color, type Easing } from "insomni";
|
|
2
|
+
import { type CategoricalPalette, type ContinuousPalette } from "../colors.ts";
|
|
3
|
+
import { type Accessibility, type TextEffects } from "./accessibility.ts";
|
|
4
|
+
export type { Accessibility, AccessibilityMode, TextEffects } from "./accessibility.ts";
|
|
5
|
+
export interface ThemeText {
|
|
6
|
+
color: Color;
|
|
7
|
+
fontFamily: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ThemeTitle {
|
|
10
|
+
fontSize: number;
|
|
11
|
+
fontWeight: string;
|
|
12
|
+
color: Color;
|
|
13
|
+
}
|
|
14
|
+
export interface ThemeSubtitle {
|
|
15
|
+
fontSize: number;
|
|
16
|
+
color: Color;
|
|
17
|
+
}
|
|
18
|
+
export interface ThemeAxis {
|
|
19
|
+
color: Color;
|
|
20
|
+
gridColor: Color;
|
|
21
|
+
labelFontSize: number;
|
|
22
|
+
labelColor: Color;
|
|
23
|
+
titleFontSize: number;
|
|
24
|
+
titleColor: Color;
|
|
25
|
+
}
|
|
26
|
+
export interface ThemeLegend {
|
|
27
|
+
fontSize: number;
|
|
28
|
+
labelColor: Color;
|
|
29
|
+
swatchGap: number;
|
|
30
|
+
entryGap: number;
|
|
31
|
+
}
|
|
32
|
+
export interface ThemeMarks {
|
|
33
|
+
pointRadius: number;
|
|
34
|
+
pointStroke: Color | undefined;
|
|
35
|
+
pointStrokeWidth: number;
|
|
36
|
+
/**
|
|
37
|
+
* Default stroke width for line-based marks (`line`, `smooth`, `statRolling`,
|
|
38
|
+
* `area` outline, `aggregate` line geom). Geom-level `strokeWidth` options
|
|
39
|
+
* override this token per layer.
|
|
40
|
+
*/
|
|
41
|
+
strokeWidth: number;
|
|
42
|
+
barCornerRadius: number;
|
|
43
|
+
fillAlpha: number;
|
|
44
|
+
/** Font size for value/total labels rendered alongside marks (bar, text). */
|
|
45
|
+
labelFontSize: number;
|
|
46
|
+
/** Default stroke width for `rule()` lines. */
|
|
47
|
+
ruleStrokeWidth: number;
|
|
48
|
+
/** Default label inset for `rule()` annotations. */
|
|
49
|
+
ruleLabelInset: number;
|
|
50
|
+
/** Default font size for inline annotations (rule, band labels). */
|
|
51
|
+
annotationFontSize: number;
|
|
52
|
+
/** Default fill alpha for `ribbon()`. */
|
|
53
|
+
ribbonFillAlpha: number;
|
|
54
|
+
/** Default fill alpha for `band()` highlight regions. */
|
|
55
|
+
bandFillAlpha: number;
|
|
56
|
+
/** Default size-channel pixel range. */
|
|
57
|
+
sizeRange: readonly [number, number];
|
|
58
|
+
/** Default alpha-channel range. */
|
|
59
|
+
alphaRange: readonly [number, number];
|
|
60
|
+
}
|
|
61
|
+
export type ThemeDurationKey = "fast" | "base" | "slow";
|
|
62
|
+
export type ThemeEasingKey = "standard" | "emphasized" | "decelerate" | "linear";
|
|
63
|
+
export interface ThemeDurations {
|
|
64
|
+
fast: number;
|
|
65
|
+
base: number;
|
|
66
|
+
slow: number;
|
|
67
|
+
}
|
|
68
|
+
export interface ThemeEasings {
|
|
69
|
+
standard: Easing;
|
|
70
|
+
emphasized: Easing;
|
|
71
|
+
decelerate: Easing;
|
|
72
|
+
linear: Easing;
|
|
73
|
+
}
|
|
74
|
+
export interface ThemeMotionChannel {
|
|
75
|
+
duration: ThemeDurationKey;
|
|
76
|
+
easing: ThemeEasingKey;
|
|
77
|
+
}
|
|
78
|
+
export interface ThemeMotionTooltip {
|
|
79
|
+
showDelay: number;
|
|
80
|
+
hideDelay: number;
|
|
81
|
+
fadeMs: number;
|
|
82
|
+
/**
|
|
83
|
+
* Hover-intent settle (ms). Rapid enter/leave/switch events are coalesced —
|
|
84
|
+
* the tooltip only commits a show/hide after the cursor holds a target this
|
|
85
|
+
* long. Prevents an overlay re-render per cell while sweeping across a dense
|
|
86
|
+
* mark grid or through gaps. 0 disables (commit immediately).
|
|
87
|
+
*/
|
|
88
|
+
settleDelay: number;
|
|
89
|
+
}
|
|
90
|
+
export interface ThemeMotion {
|
|
91
|
+
/** Master switch. False → snap, no scheduled motion. */
|
|
92
|
+
enabled: boolean;
|
|
93
|
+
duration: ThemeDurations;
|
|
94
|
+
easing: ThemeEasings;
|
|
95
|
+
/** Data update / scale change transitions. */
|
|
96
|
+
data: ThemeMotionChannel;
|
|
97
|
+
/** Axis tick / domain transitions. */
|
|
98
|
+
axis: ThemeMotionChannel;
|
|
99
|
+
tooltip: ThemeMotionTooltip;
|
|
100
|
+
}
|
|
101
|
+
export declare const defaultMotion: ThemeMotion;
|
|
102
|
+
export interface ThemeInteractionEmphasis {
|
|
103
|
+
/** Master enable. `false` skips both halo and dim. */
|
|
104
|
+
enabled: boolean;
|
|
105
|
+
/** Multiplier on non-active rows' fill alpha. `1` = no dim. */
|
|
106
|
+
dim: number;
|
|
107
|
+
/** Halo stroke width in CSS px. `0` skips the halo. */
|
|
108
|
+
haloStrokeWidth: number;
|
|
109
|
+
/**
|
|
110
|
+
* Halo ring color. When omitted, geoms fall back to a high-contrast
|
|
111
|
+
* foreground (`theme.text.color`) so the focus ring reads against the
|
|
112
|
+
* marks rather than blending in with the datum's own color. Set a `Color`
|
|
113
|
+
* to pin the ring (e.g. a brand accent).
|
|
114
|
+
*/
|
|
115
|
+
haloColor?: Color;
|
|
116
|
+
/**
|
|
117
|
+
* Duration (ms) of the GPU dim animation driven through the core's emphasis
|
|
118
|
+
* uniform (P5-T3). On a hover hit-change over a dim-participating geom the
|
|
119
|
+
* mount ramps the emphasis `t` 0→1 (ease-out cubic) over this window; on
|
|
120
|
+
* hover exit it ramps t→0. Only consumed by `hover` (selection dim stays a
|
|
121
|
+
* compile-time treatment). `<= 0` snaps. Default ~120ms.
|
|
122
|
+
*/
|
|
123
|
+
durationMs?: number;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Semantic row-tint tokens for tooltip rows that use the
|
|
127
|
+
* `accent: "positive" | "negative" | "neutral"` shorthand. `neutral` is
|
|
128
|
+
* intentionally omitted — it means "no override, use the default row text
|
|
129
|
+
* color from the underlying tooltip style." Consumers can also pass a raw
|
|
130
|
+
* `Color` per row to bypass these tokens entirely.
|
|
131
|
+
*/
|
|
132
|
+
export interface ThemeTooltipAccents {
|
|
133
|
+
positive: Color;
|
|
134
|
+
negative: Color;
|
|
135
|
+
}
|
|
136
|
+
export interface ThemeInteractions {
|
|
137
|
+
/** Visual treatment applied while a row is hovered. */
|
|
138
|
+
hover: ThemeInteractionEmphasis;
|
|
139
|
+
/** Visual treatment applied while rows are selected. */
|
|
140
|
+
selection: ThemeInteractionEmphasis;
|
|
141
|
+
/**
|
|
142
|
+
* Grace window (ms) applied when hover transitions to "nothing" before
|
|
143
|
+
* propagating the null state to dim/halo and crosshair. If a new hit lands
|
|
144
|
+
* within this window, the swap goes directly from prev → next without
|
|
145
|
+
* passing through null — eliminates the un-dim → re-dim flash when the
|
|
146
|
+
* cursor crosses between adjacent or near-adjacent geoms. `0` disables.
|
|
147
|
+
*/
|
|
148
|
+
hoverSwapGraceMs: number;
|
|
149
|
+
/** Semantic row colors for the tooltip `accent` shorthand. */
|
|
150
|
+
tooltipAccents: ThemeTooltipAccents;
|
|
151
|
+
}
|
|
152
|
+
export declare const defaultInteractions: ThemeInteractions;
|
|
153
|
+
export interface ThemePalettes {
|
|
154
|
+
categorical: CategoricalPalette;
|
|
155
|
+
continuous: ContinuousPalette;
|
|
156
|
+
diverging: ContinuousPalette;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Semantic accent palette consumed by chart-level marks that carry meaning
|
|
160
|
+
* (reference regions, threshold rules, callouts, tooltip rows). Geoms that
|
|
161
|
+
* accept an accent key (`fill: "positive"`, `stroke: "warn"`) resolve through
|
|
162
|
+
* this block; literal `Color` values still bypass it.
|
|
163
|
+
*
|
|
164
|
+
* Naming exception to the "name geoms by what they are, not what they do"
|
|
165
|
+
* principle: these tokens exist explicitly to carry semantics. The names
|
|
166
|
+
* follow conventional status vocabulary so any reader can predict the role.
|
|
167
|
+
*
|
|
168
|
+
* `neutral` is intentionally omitted — it means "no override, use the
|
|
169
|
+
* surrounding default" (mark fill, axis label color, …) so consumers can fall
|
|
170
|
+
* back without spelling out a token.
|
|
171
|
+
*
|
|
172
|
+
* Distinct from {@link ThemeTooltipAccents} (under `interactions.tooltipAccents`)
|
|
173
|
+
* which is sized for tooltip-text contrast and may differ in luminance.
|
|
174
|
+
*/
|
|
175
|
+
export interface ThemeAccents {
|
|
176
|
+
positive: Color;
|
|
177
|
+
negative: Color;
|
|
178
|
+
warn: Color;
|
|
179
|
+
info: Color;
|
|
180
|
+
}
|
|
181
|
+
export type ThemeAccentKey = keyof ThemeAccents;
|
|
182
|
+
export interface Theme {
|
|
183
|
+
background: Color;
|
|
184
|
+
text: ThemeText;
|
|
185
|
+
title: ThemeTitle;
|
|
186
|
+
subtitle: ThemeSubtitle;
|
|
187
|
+
axis: ThemeAxis;
|
|
188
|
+
legend: ThemeLegend;
|
|
189
|
+
marks: ThemeMarks;
|
|
190
|
+
palettes: ThemePalettes;
|
|
191
|
+
/** Semantic accent palette for chart-level marks (reference regions, threshold rules, callouts). */
|
|
192
|
+
accents: ThemeAccents;
|
|
193
|
+
/**
|
|
194
|
+
* Text-readability policy. Applied at every draw site where the chart
|
|
195
|
+
* knows the local background color (titles/axis/legend versus the panel
|
|
196
|
+
* background; tile labels versus their cell color).
|
|
197
|
+
*/
|
|
198
|
+
accessibility: Accessibility;
|
|
199
|
+
/**
|
|
200
|
+
* Default text effects (outline / drop shadow). API stub today — the SDF
|
|
201
|
+
* text path doesn't render these yet. See
|
|
202
|
+
* dev_docs/2026-04-30-text-accessibility.md.
|
|
203
|
+
*/
|
|
204
|
+
textEffects?: TextEffects;
|
|
205
|
+
/**
|
|
206
|
+
* Color space used to interpolate continuous palettes (`viridis`, etc.)
|
|
207
|
+
* when sampled by color scales and color bars. `oklch` is the default —
|
|
208
|
+
* perceptually uniform with hue preserved along the gradient. Override
|
|
209
|
+
* per chart (`theme({ paletteBlendSpace: "srgb" })`) or per call (the
|
|
210
|
+
* `blendSpace` option on color-scale and `colorBar` specs).
|
|
211
|
+
*/
|
|
212
|
+
paletteBlendSpace: BlendSpace;
|
|
213
|
+
/**
|
|
214
|
+
* Motion tokens — durations, easings, and per-channel defaults consumed by
|
|
215
|
+
* interactions (hover, tooltip), data transitions, and axis updates. See
|
|
216
|
+
* {@link defaultMotion} and {@link applyReducedMotion}.
|
|
217
|
+
*/
|
|
218
|
+
motion: ThemeMotion;
|
|
219
|
+
/**
|
|
220
|
+
* Hover / selection emphasis tokens. Geoms read these to render the active
|
|
221
|
+
* row's halo and optionally dim the rest. See {@link defaultInteractions}.
|
|
222
|
+
*/
|
|
223
|
+
interactions: ThemeInteractions;
|
|
224
|
+
}
|
|
225
|
+
export declare const themeDefault: Theme;
|
|
226
|
+
/**
|
|
227
|
+
* Light/minimal theme — white plot background, soft gray gridlines, dark
|
|
228
|
+
* labels. Pairs well with the `category10` palette and is a good default for
|
|
229
|
+
* scatter / smooth charts that should read like a ggplot2 / R reference.
|
|
230
|
+
*/
|
|
231
|
+
export declare const themeMinimalGrid: Theme;
|
|
232
|
+
export type DeepPartial<T> = {
|
|
233
|
+
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* Compose a theme by deep-merging overrides into a base theme. Accepts a
|
|
237
|
+
* shallow override on any nested group (e.g. `{ marks: { strokeWidth: 2 } }`).
|
|
238
|
+
*/
|
|
239
|
+
export declare function theme(overrides: DeepPartial<Theme>, base?: Theme): Theme;
|
|
240
|
+
export interface ResolvedMotion {
|
|
241
|
+
/** Duration in milliseconds. 0 when motion is disabled. */
|
|
242
|
+
durationMs: number;
|
|
243
|
+
easing: Easing;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Resolve a motion channel into concrete numbers using the theme's duration
|
|
247
|
+
* and easing tables. When `motion.enabled` is false, duration collapses to 0
|
|
248
|
+
* so callers can branch on `durationMs <= 0` to snap.
|
|
249
|
+
*/
|
|
250
|
+
export declare function resolveMotion(motion: ThemeMotion, channel: ThemeMotionChannel): ResolvedMotion;
|
|
251
|
+
/**
|
|
252
|
+
* True when the runtime reports `prefers-reduced-motion: reduce`. SSR-safe:
|
|
253
|
+
* returns false when `window`/`matchMedia` are unavailable.
|
|
254
|
+
*/
|
|
255
|
+
export declare function prefersReducedMotion(): boolean;
|
|
256
|
+
/**
|
|
257
|
+
* Return a theme with motion disabled and tooltip delays/fade zeroed. Apply
|
|
258
|
+
* to honor the user's `prefers-reduced-motion` setting. Keeps easings intact
|
|
259
|
+
* so callers that ignore `enabled` and read durations directly still work.
|
|
260
|
+
*/
|
|
261
|
+
export declare function applyReducedMotion(t: Theme): Theme;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, test } from "vite-plus/test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
applyReducedMotion,
|
|
5
|
+
defaultInteractions,
|
|
6
|
+
defaultMotion,
|
|
7
|
+
prefersReducedMotion,
|
|
8
|
+
resolveMotion,
|
|
9
|
+
theme,
|
|
10
|
+
themeDefault,
|
|
11
|
+
themeMinimalGrid,
|
|
12
|
+
} from "./theme.ts";
|
|
13
|
+
|
|
14
|
+
describe("theme — interactions.hover.durationMs (P5-T3)", () => {
|
|
15
|
+
test("default hover emphasis carries a ~120ms GPU-dim duration", () => {
|
|
16
|
+
expect(defaultInteractions.hover.durationMs).toBe(120);
|
|
17
|
+
expect(themeDefault.interactions.hover.durationMs).toBe(120);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("selection emphasis has no durationMs (selection dim stays compile-time)", () => {
|
|
21
|
+
expect(defaultInteractions.selection.durationMs).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("theme — motion tokens", () => {
|
|
26
|
+
test("themeDefault carries defaultMotion", () => {
|
|
27
|
+
expect(themeDefault.motion).toBe(defaultMotion);
|
|
28
|
+
expect(themeDefault.motion.enabled).toBe(true);
|
|
29
|
+
expect(themeDefault.motion.duration.fast).toBe(120);
|
|
30
|
+
expect(themeDefault.motion.duration.base).toBe(240);
|
|
31
|
+
expect(themeDefault.motion.duration.slow).toBe(480);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("themeMinimalGrid inherits motion via spread", () => {
|
|
35
|
+
expect(themeMinimalGrid.motion).toBe(defaultMotion);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("resolveMotion looks up named duration + easing when enabled", () => {
|
|
39
|
+
const r = resolveMotion(defaultMotion, { duration: "fast", easing: "standard" });
|
|
40
|
+
expect(r.durationMs).toBe(120);
|
|
41
|
+
expect(r.easing).toBe(defaultMotion.easing.standard);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("resolveMotion collapses duration to 0 when disabled", () => {
|
|
45
|
+
const m = { ...defaultMotion, enabled: false };
|
|
46
|
+
const r = resolveMotion(m, m.data);
|
|
47
|
+
expect(r.durationMs).toBe(0);
|
|
48
|
+
expect(r.easing).toBe(defaultMotion.easing.emphasized);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("applyReducedMotion zeros durations + tooltip delays, keeps easings", () => {
|
|
52
|
+
const reduced = applyReducedMotion(themeDefault);
|
|
53
|
+
expect(reduced.motion.enabled).toBe(false);
|
|
54
|
+
expect(reduced.motion.duration).toEqual({ fast: 0, base: 0, slow: 0 });
|
|
55
|
+
expect(reduced.motion.tooltip).toEqual({
|
|
56
|
+
showDelay: 0,
|
|
57
|
+
hideDelay: 0,
|
|
58
|
+
fadeMs: 0,
|
|
59
|
+
settleDelay: 0,
|
|
60
|
+
});
|
|
61
|
+
expect(reduced.motion.easing.standard).toBe(defaultMotion.easing.standard);
|
|
62
|
+
// original is untouched
|
|
63
|
+
expect(themeDefault.motion.duration.fast).toBe(120);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("theme() override merges motion field", () => {
|
|
67
|
+
const t = theme({ motion: { duration: { fast: 50, base: 100, slow: 200 } } });
|
|
68
|
+
expect(t.motion.duration.fast).toBe(50);
|
|
69
|
+
expect(t.motion.enabled).toBe(true);
|
|
70
|
+
// other channels untouched
|
|
71
|
+
expect(t.motion.data.duration).toBe("base");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("prefersReducedMotion returns false when matchMedia missing", () => {
|
|
75
|
+
// jsdom typically lacks matchMedia by default; if present, this just
|
|
76
|
+
// exercises the call path without asserting the boolean.
|
|
77
|
+
const result = prefersReducedMotion();
|
|
78
|
+
expect(typeof result).toBe("boolean");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("theme — semantic accents", () => {
|
|
83
|
+
test("themeDefault carries the four semantic accent tokens", () => {
|
|
84
|
+
expect(themeDefault.accents).toBeDefined();
|
|
85
|
+
expect(themeDefault.accents.positive).toBeDefined();
|
|
86
|
+
expect(themeDefault.accents.negative).toBeDefined();
|
|
87
|
+
expect(themeDefault.accents.warn).toBeDefined();
|
|
88
|
+
expect(themeDefault.accents.info).toBeDefined();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("themeMinimalGrid overrides accents for light-background contrast", () => {
|
|
92
|
+
// Light theme picks higher-saturation values than the dark theme so
|
|
93
|
+
// shaded regions still read on white. Sanity-check that the override
|
|
94
|
+
// happened — the green channel of `positive` differs between themes.
|
|
95
|
+
expect(themeMinimalGrid.accents.positive).not.toBe(themeDefault.accents.positive);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("theme() override merges accents field", () => {
|
|
99
|
+
const customRed = { r: 1, g: 0, b: 0, a: 1 };
|
|
100
|
+
const t = theme({ accents: { negative: customRed } });
|
|
101
|
+
expect(t.accents.negative).toEqual(customRed);
|
|
102
|
+
// other accents preserved
|
|
103
|
+
expect(t.accents.positive).toBe(themeDefault.accents.positive);
|
|
104
|
+
});
|
|
105
|
+
});
|