@pond-ts/charts 0.31.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 (79) hide show
  1. package/CHANGELOG.md +3254 -0
  2. package/LICENSE +21 -0
  3. package/README.md +229 -0
  4. package/dist/AreaChart.d.ts +85 -0
  5. package/dist/AreaChart.js +119 -0
  6. package/dist/BandChart.d.ts +55 -0
  7. package/dist/BandChart.js +93 -0
  8. package/dist/BarChart.d.ts +72 -0
  9. package/dist/BarChart.js +137 -0
  10. package/dist/BoxPlot.d.ts +77 -0
  11. package/dist/BoxPlot.js +137 -0
  12. package/dist/Canvas.d.ts +37 -0
  13. package/dist/Canvas.js +39 -0
  14. package/dist/ChartContainer.d.ts +106 -0
  15. package/dist/ChartContainer.js +306 -0
  16. package/dist/ChartRow.d.ts +29 -0
  17. package/dist/ChartRow.js +215 -0
  18. package/dist/Layers.d.ts +22 -0
  19. package/dist/Layers.js +399 -0
  20. package/dist/LineChart.d.ts +60 -0
  21. package/dist/LineChart.js +105 -0
  22. package/dist/ScatterChart.d.ts +84 -0
  23. package/dist/ScatterChart.js +139 -0
  24. package/dist/TimeAxis.d.ts +9 -0
  25. package/dist/TimeAxis.js +12 -0
  26. package/dist/XAxis.d.ts +39 -0
  27. package/dist/XAxis.js +84 -0
  28. package/dist/YAxis.d.ts +42 -0
  29. package/dist/YAxis.js +86 -0
  30. package/dist/annotations.d.ts +110 -0
  31. package/dist/annotations.js +459 -0
  32. package/dist/area.d.ts +54 -0
  33. package/dist/area.js +186 -0
  34. package/dist/band.d.ts +31 -0
  35. package/dist/band.js +57 -0
  36. package/dist/bars.d.ts +96 -0
  37. package/dist/bars.js +171 -0
  38. package/dist/box.d.ts +59 -0
  39. package/dist/box.js +140 -0
  40. package/dist/chip.d.ts +23 -0
  41. package/dist/chip.js +43 -0
  42. package/dist/cjs-fallback.cjs +16 -0
  43. package/dist/context.d.ts +362 -0
  44. package/dist/context.js +5 -0
  45. package/dist/curve.d.ts +22 -0
  46. package/dist/curve.js +13 -0
  47. package/dist/data.d.ts +154 -0
  48. package/dist/data.js +197 -0
  49. package/dist/domain.d.ts +19 -0
  50. package/dist/domain.js +61 -0
  51. package/dist/encoding.d.ts +89 -0
  52. package/dist/encoding.js +144 -0
  53. package/dist/format.d.ts +53 -0
  54. package/dist/format.js +47 -0
  55. package/dist/gaps.d.ts +146 -0
  56. package/dist/gaps.js +209 -0
  57. package/dist/grid.d.ts +11 -0
  58. package/dist/grid.js +29 -0
  59. package/dist/index.d.ts +53 -0
  60. package/dist/index.js +34 -0
  61. package/dist/line.d.ts +46 -0
  62. package/dist/line.js +88 -0
  63. package/dist/range.d.ts +15 -0
  64. package/dist/range.js +27 -0
  65. package/dist/scatter.d.ts +70 -0
  66. package/dist/scatter.js +213 -0
  67. package/dist/select.d.ts +13 -0
  68. package/dist/select.js +23 -0
  69. package/dist/slots.d.ts +48 -0
  70. package/dist/slots.js +64 -0
  71. package/dist/theme.d.ts +224 -0
  72. package/dist/theme.js +232 -0
  73. package/dist/tracker.d.ts +30 -0
  74. package/dist/tracker.js +47 -0
  75. package/dist/use-slot-key.d.ts +21 -0
  76. package/dist/use-slot-key.js +25 -0
  77. package/dist/viewport.d.ts +20 -0
  78. package/dist/viewport.js +30 -0
  79. package/package.json +67 -0
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Visual styling for a chart, threaded through {@link ChartContainer} via
3
+ * context. Canvas has no CSS cascade into drawn pixels, so this typed object is
4
+ * the single styling channel for the drawn layers; DOM chrome (axis labels)
5
+ * derives from it too.
6
+ *
7
+ * The styling pipeline is **time series → columns → semantic identifier →
8
+ * style**: a draw layer tags its column with a *semantic identifier* (what the
9
+ * data _is_ — e.g. `heartrate`, `power`, or a generic `primary`), and the theme
10
+ * is the map from identifier → {@link LineStyle}. The visual discipline ("a
11
+ * handful of roles, not a hue per channel") lives in the theme, not the type: a
12
+ * good theme maps many identifiers onto few shared styles (estela maps
13
+ * power / speed / cadence → one foam style). Tokens grow as components land
14
+ * (axis tokens with `YAxis`, band tokens with `BandChart`).
15
+ */
16
+ export interface ChartTheme {
17
+ /** Painted behind the layers; omit for a transparent background. */
18
+ readonly background?: string;
19
+ /**
20
+ * Map from a line's semantic identifier to its style. `default` is the
21
+ * fallback for an identifier the theme doesn't name, so a chart always
22
+ * renders; a line resolves `line[semantic] ?? line.default`.
23
+ */
24
+ readonly line: {
25
+ readonly default: LineStyle;
26
+ readonly [semantic: string]: LineStyle;
27
+ };
28
+ /**
29
+ * Map from a band's semantic identifier to its fill style — the variance
30
+ * underlay ({@link BandChart}). `default` is the fallback; a two-tone spread
31
+ * is two bands composed in the z-stack (e.g. `outer` p5/p95 + `inner`
32
+ * p25/p75), each resolving `band[semantic] ?? band.default`.
33
+ */
34
+ readonly band: {
35
+ readonly default: BandStyle;
36
+ readonly [semantic: string]: BandStyle;
37
+ };
38
+ /**
39
+ * Map from an area's semantic identifier to its outline-plus-graded-fill style
40
+ * (`AreaChart`) — outline colour/width and the gradient (opaque at the line,
41
+ * fading to transparent at the baseline). `default` is the fallback; the
42
+ * esnet two-colour traffic look is two areas composed in the z-stack (e.g.
43
+ * `in` above the axis + `out` below), each resolving `area[semantic] ??
44
+ * area.default`.
45
+ */
46
+ readonly area: {
47
+ readonly default: AreaStyle;
48
+ readonly [semantic: string]: AreaStyle;
49
+ };
50
+ /**
51
+ * Map from a scatter's semantic identifier to its point style — the single
52
+ * styling channel for the **base mark** ({@link ScatterChart}): fill colour,
53
+ * base radius, outline, and the optional per-point label colour. `default` is
54
+ * the fallback; a scatter resolves `scatter[semantic] ?? scatter.default`.
55
+ *
56
+ * Scatter additionally supports **data-driven** radius + colour (a column run
57
+ * through a scale — see `src/encoding.ts`); that is the deliberate, signed-off
58
+ * exception to one-channel styling. The data-driven encoding *overrides* this
59
+ * style's `radius` / `color` per point; this remains the fallback for
60
+ * unencoded points and the source of the outline + label styling.
61
+ */
62
+ readonly scatter: {
63
+ readonly default: ScatterStyle;
64
+ readonly [semantic: string]: ScatterStyle;
65
+ };
66
+ /**
67
+ * Map from a box's semantic identifier to its style — a discrete
68
+ * box-and-whisker per key ({@link BoxPlot}), the bar-chart analog of the
69
+ * band. `default` is the fallback; a chart tags each box series with a role
70
+ * (`<BoxPlot as="latency" />`) resolving `box[semantic] ?? box.default`.
71
+ */
72
+ readonly box: {
73
+ readonly default: BoxStyle;
74
+ readonly [semantic: string]: BoxStyle;
75
+ };
76
+ /**
77
+ * Map from a bar's semantic identifier to its style — the fill, the
78
+ * selected-bar highlight, and the slot gap / minimum width ({@link BarChart}).
79
+ * `default` is the fallback; a bar resolves `bar[semantic] ?? bar.default`.
80
+ */
81
+ readonly bar: {
82
+ readonly default: BarStyle;
83
+ readonly [semantic: string]: BarStyle;
84
+ };
85
+ /** Axis chrome: tick-label colour, gridline stroke + dash pattern. */
86
+ readonly axis: {
87
+ readonly label: string;
88
+ readonly grid: string;
89
+ /** Gridline dash pattern (px on/off pairs); `[]` for solid. */
90
+ readonly gridDash: readonly number[];
91
+ };
92
+ /** Label / tick typography. One source for axes + chrome. */
93
+ readonly font: {
94
+ readonly family: string;
95
+ readonly size: number;
96
+ };
97
+ /** Crosshair / tracker stroke colour. Falls back to {@link axis.label} if unset. */
98
+ readonly cursor?: string;
99
+ /**
100
+ * Readout chip background (the `flag` / `inline` tracker modes). The value text
101
+ * is the series colour; this is the panel behind it. Falls back to the plot
102
+ * background if unset.
103
+ */
104
+ readonly chip?: {
105
+ readonly background: string;
106
+ };
107
+ /**
108
+ * Styling for the **inferred dashed gap connectors** — the `dashed` and `step`
109
+ * gap modes ({@link GapMode}). Drawn fainter than the solid line (via
110
+ * `connectorOpacity`, 0–1, applied over the layer's colour) so an *inferred*
111
+ * bridge across a gap reads as secondary to measured data. Per-theme, so a
112
+ * dark ground can tune the faintness independently. Falls back to `0.5` if
113
+ * unset. (The `fade` mode has its own gradient and isn't governed by this.)
114
+ */
115
+ readonly gap?: {
116
+ readonly connectorOpacity: number;
117
+ };
118
+ }
119
+ /** A resolved line style: stroke colour + width (px). */
120
+ export interface LineStyle {
121
+ readonly color: string;
122
+ readonly width: number;
123
+ }
124
+ /** A resolved band style: fill colour + opacity (0–1) for the variance envelope. */
125
+ export interface BandStyle {
126
+ readonly fill: string;
127
+ readonly opacity: number;
128
+ }
129
+ /**
130
+ * A resolved scatter point style — the single styling channel for the base
131
+ * mark. `color` fills each point and `radius` (px) sizes it (both the fallback
132
+ * when a data-driven encoding leaves a point unencoded). `outline`/`outlineWidth`
133
+ * stroke a ring around each point for legibility on a busy plot; the *selected*
134
+ * point is restroked with `selectedOutline` at `selectedWidth` to lift it.
135
+ * `label` colours the optional per-point text (the font comes from `theme.font`).
136
+ */
137
+ export interface ScatterStyle {
138
+ readonly color: string;
139
+ /** Base point radius in px (data-driven radius overrides this per point). */
140
+ readonly radius: number;
141
+ /** Per-point outline stroke colour. */
142
+ readonly outline: string;
143
+ /** Per-point outline width in px. */
144
+ readonly outlineWidth: number;
145
+ /** Outline colour for the selected point (the highlight ring). */
146
+ readonly selectedOutline: string;
147
+ /** Outline width for the selected point (px) — wider than `outlineWidth`. */
148
+ readonly selectedWidth: number;
149
+ /** Colour of the optional per-point text label. */
150
+ readonly label: string;
151
+ }
152
+ /**
153
+ * A resolved box-and-whisker style ({@link BoxPlot}). The q1→q3 box is a filled
154
+ * rect (`fill` at `fillOpacity`) outlined by `stroke`/`strokeWidth`; the
155
+ * `median` line and the `whisker` (the lower/upper stems + caps) get their own
156
+ * colour/width so the median reads against the fill. Whiskers and the box
157
+ * outline are drawn at full alpha (only the box fill is graded by `fillOpacity`).
158
+ */
159
+ export interface BoxStyle {
160
+ readonly fill: string;
161
+ readonly fillOpacity: number;
162
+ readonly stroke: string;
163
+ readonly strokeWidth: number;
164
+ readonly median: string;
165
+ readonly medianWidth: number;
166
+ readonly whisker: string;
167
+ readonly whiskerWidth: number;
168
+ }
169
+ /**
170
+ * A resolved area style: an outline stroke plus a graded fill. `color`/`width`
171
+ * stroke the value line on top; `fill` is the gradient base colour, opaque
172
+ * (scaled by `fillOpacity`, 0–1) at the line and grading to transparent at the
173
+ * baseline. `fill` must be a CSS hex (`#rgb` / `#rrggbb`) so the transparent
174
+ * stop can be derived; other formats fall back to a flat fill.
175
+ */
176
+ export interface AreaStyle {
177
+ readonly color: string;
178
+ readonly width: number;
179
+ readonly fill: string;
180
+ readonly fillOpacity: number;
181
+ }
182
+ /**
183
+ * A resolved bar style: the flat `fill` (scaled by `opacity`, 0–1) plus the
184
+ * `highlight` colour the selected bar takes (also its outline), and the bar
185
+ * geometry — `gap` (px between adjacent bars, the default for `<BarChart gap>`)
186
+ * and `minWidth` (the px floor so a too-thin bucket stays visible) handed to
187
+ * `barSpanPx`, with `outlineWidth` for the selected-bar stroke.
188
+ */
189
+ export interface BarStyle {
190
+ readonly fill: string;
191
+ readonly opacity: number;
192
+ readonly highlight: string;
193
+ readonly gap: number;
194
+ readonly minWidth: number;
195
+ readonly outlineWidth: number;
196
+ }
197
+ /**
198
+ * The neutral default theme. `default` / `primary` match the M1 `LineChart`
199
+ * colour (`#2563eb`) so adopting the theme channel doesn't shift existing
200
+ * renders. `primary` / `secondary` / `context` are a built-in generic role
201
+ * vocabulary; an unrecognised (e.g. domain-specific) identifier falls back to
202
+ * `default`.
203
+ */
204
+ export declare const defaultTheme: ChartTheme;
205
+ /**
206
+ * The estela theme — estela's real `@estela/ui` palette as *one theme*, on its
207
+ * dark ground. A chart tags a column with a role (`<LineChart as="foam" />`) and
208
+ * the colour lives here, not at the call site. The proving consumer for "target
209
+ * other uses too": the same engine, restyled by swapping this for
210
+ * {@link defaultTheme}.
211
+ *
212
+ * Line roles map to estela's palette tokens:
213
+ * - `default` → `--es-estela` `#15B3A6` (primary / action — the brand teal)
214
+ * - `foam` → `--es-foam` `#F1FBF9` (the shared "motion" trace estela uses for
215
+ * its primary channels — power / speed / cadence all render foam)
216
+ * - `hr` → `--es-filament` `#E0B36A` (the rare warm accent — heart rate)
217
+ *
218
+ * Chrome: `--es-bg` ground, `--es-ink` gridlines, `--es-slate` labels, and the
219
+ * `--es-font-data` (JetBrains Mono) face for crisp numeric ticks (falls back to
220
+ * `ui-monospace` where the webfont isn't loaded). Band fills: `outer`
221
+ * (`--es-reef`) + `inner` (`--es-shallows`) for the two-tone variance spread.
222
+ */
223
+ export declare const estelaTheme: ChartTheme;
224
+ //# sourceMappingURL=theme.d.ts.map
package/dist/theme.js ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * The neutral default theme. `default` / `primary` match the M1 `LineChart`
3
+ * colour (`#2563eb`) so adopting the theme channel doesn't shift existing
4
+ * renders. `primary` / `secondary` / `context` are a built-in generic role
5
+ * vocabulary; an unrecognised (e.g. domain-specific) identifier falls back to
6
+ * `default`.
7
+ */
8
+ export const defaultTheme = {
9
+ line: {
10
+ default: { color: '#2563eb', width: 1.5 },
11
+ primary: { color: '#2563eb', width: 1.5 },
12
+ secondary: { color: '#e8836b', width: 1.5 },
13
+ context: { color: '#5eb5a6', width: 1.5 },
14
+ },
15
+ band: {
16
+ default: { fill: '#2563eb', opacity: 0.15 },
17
+ outer: { fill: '#2563eb', opacity: 0.1 },
18
+ inner: { fill: '#2563eb', opacity: 0.2 },
19
+ },
20
+ area: {
21
+ // Outline at the line colour; graded fill from it. `in`/`out` are the
22
+ // above/below-axis roles (esnet traffic), composed as two layers.
23
+ default: {
24
+ color: '#2563eb',
25
+ width: 1.5,
26
+ fill: '#2563eb',
27
+ fillOpacity: 0.3,
28
+ },
29
+ in: { color: '#2563eb', width: 1.5, fill: '#2563eb', fillOpacity: 0.3 },
30
+ out: { color: '#e8836b', width: 1.5, fill: '#e8836b', fillOpacity: 0.3 },
31
+ },
32
+ scatter: {
33
+ // Brand blue fill with a white ring (legible on a busy plot); the selected
34
+ // point gets a darker, wider ring. `primary`/`secondary` mirror the line
35
+ // roles so a scatter overlaid on a line can share its identity.
36
+ default: {
37
+ color: '#2563eb',
38
+ radius: 4,
39
+ outline: '#ffffff',
40
+ outlineWidth: 1,
41
+ selectedOutline: '#1e293b',
42
+ selectedWidth: 2,
43
+ label: '#334155',
44
+ },
45
+ primary: {
46
+ color: '#2563eb',
47
+ radius: 4,
48
+ outline: '#ffffff',
49
+ outlineWidth: 1,
50
+ selectedOutline: '#1e293b',
51
+ selectedWidth: 2,
52
+ label: '#334155',
53
+ },
54
+ secondary: {
55
+ color: '#e8836b',
56
+ radius: 4,
57
+ outline: '#ffffff',
58
+ outlineWidth: 1,
59
+ selectedOutline: '#1e293b',
60
+ selectedWidth: 2,
61
+ label: '#334155',
62
+ },
63
+ },
64
+ box: {
65
+ // The blue brand box: a translucent fill outlined in the line colour, a
66
+ // bolder median, and matching whiskers.
67
+ default: {
68
+ fill: '#2563eb',
69
+ fillOpacity: 0.3,
70
+ stroke: '#2563eb',
71
+ strokeWidth: 1.5,
72
+ median: '#1e3a8a',
73
+ medianWidth: 2,
74
+ whisker: '#aabee9',
75
+ whiskerWidth: 1,
76
+ },
77
+ },
78
+ bar: {
79
+ // Flat blue fill; the selected bar brightens + outlines. `secondary` reuses
80
+ // the line's warm accent for a second series.
81
+ default: {
82
+ fill: '#2563eb',
83
+ opacity: 0.85,
84
+ highlight: '#1d4ed8',
85
+ gap: 1,
86
+ minWidth: 1,
87
+ outlineWidth: 1.5,
88
+ },
89
+ secondary: {
90
+ fill: '#e8836b',
91
+ opacity: 0.85,
92
+ highlight: '#d65f43',
93
+ gap: 1,
94
+ minWidth: 1,
95
+ outlineWidth: 1.5,
96
+ },
97
+ },
98
+ axis: {
99
+ label: '#64748b',
100
+ grid: '#e2e8f0',
101
+ gridDash: [2, 2],
102
+ },
103
+ font: {
104
+ family: 'system-ui, -apple-system, sans-serif',
105
+ size: 11,
106
+ },
107
+ cursor: '#64748b',
108
+ chip: { background: '#ffffff' },
109
+ gap: { connectorOpacity: 0.5 },
110
+ };
111
+ /**
112
+ * The estela theme — estela's real `@estela/ui` palette as *one theme*, on its
113
+ * dark ground. A chart tags a column with a role (`<LineChart as="foam" />`) and
114
+ * the colour lives here, not at the call site. The proving consumer for "target
115
+ * other uses too": the same engine, restyled by swapping this for
116
+ * {@link defaultTheme}.
117
+ *
118
+ * Line roles map to estela's palette tokens:
119
+ * - `default` → `--es-estela` `#15B3A6` (primary / action — the brand teal)
120
+ * - `foam` → `--es-foam` `#F1FBF9` (the shared "motion" trace estela uses for
121
+ * its primary channels — power / speed / cadence all render foam)
122
+ * - `hr` → `--es-filament` `#E0B36A` (the rare warm accent — heart rate)
123
+ *
124
+ * Chrome: `--es-bg` ground, `--es-ink` gridlines, `--es-slate` labels, and the
125
+ * `--es-font-data` (JetBrains Mono) face for crisp numeric ticks (falls back to
126
+ * `ui-monospace` where the webfont isn't loaded). Band fills: `outer`
127
+ * (`--es-reef`) + `inner` (`--es-shallows`) for the two-tone variance spread.
128
+ */
129
+ export const estelaTheme = {
130
+ background: '#06191D', // --es-bg
131
+ line: {
132
+ default: { color: '#15B3A6', width: 1.5 }, // --es-estela (primary / action)
133
+ foam: { color: '#F1FBF9', width: 2 }, // --es-foam (motion — shared primary trace)
134
+ hr: { color: '#E0B36A', width: 1.5 }, // --es-filament (rare warm accent)
135
+ },
136
+ band: {
137
+ default: { fill: '#45CDBE', opacity: 0.18 }, // --es-shallows
138
+ outer: { fill: '#7FE2D2', opacity: 0.12 }, // --es-reef (wide p5/p95 spread)
139
+ inner: { fill: '#45CDBE', opacity: 0.22 }, // --es-shallows (tight p25/p75)
140
+ },
141
+ area: {
142
+ // Elevation: the brand teal outline over a graded teal shade. `in`/`out`
143
+ // are the above/below-axis traffic roles — teal `in`, warm filament `out`.
144
+ default: {
145
+ color: '#15B3A6',
146
+ width: 1.5,
147
+ fill: '#15B3A6',
148
+ fillOpacity: 0.35,
149
+ }, // --es-estela
150
+ in: { color: '#15B3A6', width: 1.5, fill: '#15B3A6', fillOpacity: 0.35 }, // --es-estela
151
+ out: { color: '#E0B36A', width: 1.5, fill: '#E0B36A', fillOpacity: 0.35 }, // --es-filament
152
+ },
153
+ scatter: {
154
+ // Brand-teal points ringed in the dark ground so they read as discrete
155
+ // marks; the selected point gets the bright reef ring (the tracker colour).
156
+ // `foam`/`hr` mirror the line roles for a scatter overlaid on those traces.
157
+ default: {
158
+ color: '#15B3A6', // --es-estela
159
+ radius: 4,
160
+ outline: '#06191D', // --es-bg (ring punches the point off the ground)
161
+ outlineWidth: 1,
162
+ selectedOutline: '#7FE2D2', // --es-reef (matches the tracker highlight)
163
+ selectedWidth: 2,
164
+ label: '#DBEAE8', // --es-mist (legible label on the dark ground)
165
+ },
166
+ foam: {
167
+ color: '#F1FBF9', // --es-foam (motion — shared primary trace)
168
+ radius: 4,
169
+ outline: '#06191D',
170
+ outlineWidth: 1,
171
+ selectedOutline: '#7FE2D2',
172
+ selectedWidth: 2,
173
+ label: '#DBEAE8', // --es-mist
174
+ },
175
+ hr: {
176
+ color: '#E0B36A', // --es-filament (rare warm accent — heart rate)
177
+ radius: 4,
178
+ outline: '#06191D',
179
+ outlineWidth: 1,
180
+ selectedOutline: '#7FE2D2',
181
+ selectedWidth: 2,
182
+ label: '#DBEAE8', // --es-mist
183
+ },
184
+ },
185
+ box: {
186
+ // A teal box on the dark ground: `--es-shallows` fill, `--es-estela` outline
187
+ // + whiskers, and a bright `--es-foam` median so it reads against the fill.
188
+ default: {
189
+ fill: '#45CDBE', // --es-shallows
190
+ fillOpacity: 0.28,
191
+ stroke: '#15B3A6', // --es-estela
192
+ strokeWidth: 1.5,
193
+ median: '#F1FBF9', // --es-foam
194
+ medianWidth: 2,
195
+ whisker: '#a4e4d9', // --es-reef
196
+ whiskerWidth: 1.5,
197
+ },
198
+ },
199
+ bar: {
200
+ // Brand-teal fill on the dark ground; the selected bar lifts to the bright
201
+ // reef + an outline. `secondary` is the warm filament accent.
202
+ default: {
203
+ fill: '#15B3A6', // --es-estela
204
+ opacity: 0.85,
205
+ highlight: '#7FE2D2', // --es-reef (bright on the dark ground)
206
+ gap: 1,
207
+ minWidth: 1,
208
+ outlineWidth: 1.5,
209
+ },
210
+ secondary: {
211
+ fill: '#E0B36A', // --es-filament
212
+ opacity: 0.85,
213
+ highlight: '#F1D9A8',
214
+ gap: 1,
215
+ minWidth: 1,
216
+ outlineWidth: 1.5,
217
+ },
218
+ },
219
+ axis: {
220
+ label: '#4E6B6B', // --es-slate
221
+ grid: '#06343A', // --es-ink
222
+ gridDash: [2, 3],
223
+ },
224
+ font: {
225
+ family: '"JetBrains Mono", ui-monospace, monospace', // --es-font-data
226
+ size: 11,
227
+ },
228
+ cursor: '#7FE2D2', // --es-reef (bright tracker on the dark ground)
229
+ chip: { background: '#0B4E58' }, // --es-deep (panel behind readout text)
230
+ gap: { connectorOpacity: 0.5 },
231
+ };
232
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Cursor geometry helpers — pure functions deciding *what* the cursor draws
3
+ * ({@link cursorParts}) and *where* it sits ({@link resolveCursorX}). The marks
4
+ * themselves render as an SVG overlay in `Layers` (no cursor canvas); these
5
+ * helpers stay pure, so they're unit-tested directly.
6
+ */
7
+ import type { CursorMode } from './context.js';
8
+ /** Default cursor mode — the synced vertical line (cursor enabled on the
9
+ * container by default; pair with an off-chart readout via `onTrackerChanged`). */
10
+ export declare const DEFAULT_CURSOR_MODE: CursorMode;
11
+ /**
12
+ * Decompose a {@link CursorMode} into what it draws: the shared vertical line,
13
+ * the per-series dots, and which value chip (if any). The modes are exclusive
14
+ * presets — `line` is line-only, `point` / `inline` / `flag` are dot-based with
15
+ * no line, `none` draws nothing. `flag` raises a staff from each point to a
16
+ * value flag stacked near the top of the row (drawn in `Layers`).
17
+ */
18
+ export declare function cursorParts(mode: CursorMode): {
19
+ readonly line: boolean;
20
+ readonly dots: boolean;
21
+ readonly chip: 'none' | 'inline' | 'flag';
22
+ };
23
+ /**
24
+ * The crosshair's plot-pixel x from the tracker inputs. A controlled
25
+ * `trackerPosition` (epoch ms) maps through `xScale`, so a pinned time rides with
26
+ * the data; `null` hides it; `undefined` (uncontrolled) uses the stored hover
27
+ * pixel, so a still cursor stays put while a live window slides under it.
28
+ */
29
+ export declare function resolveCursorX(trackerPosition: number | null | undefined, hoverX: number | null, xScale: (time: number) => number): number | null;
30
+ //# sourceMappingURL=tracker.d.ts.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Cursor geometry helpers — pure functions deciding *what* the cursor draws
3
+ * ({@link cursorParts}) and *where* it sits ({@link resolveCursorX}). The marks
4
+ * themselves render as an SVG overlay in `Layers` (no cursor canvas); these
5
+ * helpers stay pure, so they're unit-tested directly.
6
+ */
7
+ /** Default cursor mode — the synced vertical line (cursor enabled on the
8
+ * container by default; pair with an off-chart readout via `onTrackerChanged`). */
9
+ export const DEFAULT_CURSOR_MODE = 'line';
10
+ /**
11
+ * Decompose a {@link CursorMode} into what it draws: the shared vertical line,
12
+ * the per-series dots, and which value chip (if any). The modes are exclusive
13
+ * presets — `line` is line-only, `point` / `inline` / `flag` are dot-based with
14
+ * no line, `none` draws nothing. `flag` raises a staff from each point to a
15
+ * value flag stacked near the top of the row (drawn in `Layers`).
16
+ */
17
+ export function cursorParts(mode) {
18
+ switch (mode) {
19
+ case 'line':
20
+ return { line: true, dots: false, chip: 'none' };
21
+ case 'point':
22
+ return { line: false, dots: true, chip: 'none' };
23
+ case 'inline':
24
+ return { line: false, dots: true, chip: 'inline' };
25
+ case 'flag':
26
+ return { line: false, dots: true, chip: 'flag' };
27
+ case 'none':
28
+ return { line: false, dots: false, chip: 'none' };
29
+ }
30
+ }
31
+ /**
32
+ * The crosshair's plot-pixel x from the tracker inputs. A controlled
33
+ * `trackerPosition` (epoch ms) maps through `xScale`, so a pinned time rides with
34
+ * the data; `null` hides it; `undefined` (uncontrolled) uses the stored hover
35
+ * pixel, so a still cursor stays put while a live window slides under it.
36
+ */
37
+ export function resolveCursorX(trackerPosition, hoverX, xScale) {
38
+ if (trackerPosition === undefined)
39
+ return hoverX;
40
+ if (trackerPosition === null)
41
+ return null;
42
+ return xScale(trackerPosition);
43
+ }
44
+ // The cursor's line / dots / flag-staffs render as an SVG overlay in `Layers`
45
+ // (DOM, crisp, positioned in plot space) — there is no cursor canvas, so the
46
+ // former `drawCrosshair` / `drawTrackerDot` canvas primitives are gone.
47
+ //# sourceMappingURL=tracker.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * A stable, unique key for **this component instance**, used to claim a slot in
3
+ * a parent collection (the axis / layer registries in `ChartRow`).
4
+ *
5
+ * This is *instance identity*, deliberately **not** `useId` and **not** a
6
+ * data-derived value:
7
+ * - `useId` is for SSR-stable accessibility attributes; the React docs
8
+ * explicitly discourage it as a collection key.
9
+ * - A React reconciliation `key` should come from your data — but that rule is
10
+ * about rendering *lists of data*. A registry of mounted *component instances*
11
+ * is a different thing: two `<LineChart>`s with identical props are still two
12
+ * distinct layers, and an axis's data `id` can legitimately repeat. The
13
+ * correct identity for "which mounted instance is this" is the instance
14
+ * itself, which a ref token captures (and which lets the registry update a
15
+ * slot in place rather than reorder — see `ChartRow`).
16
+ *
17
+ * The token is a `Symbol` created once per instance (lazily, no per-render
18
+ * allocation) and never escapes into rendered output.
19
+ */
20
+ export declare function useSlotKey(): symbol;
21
+ //# sourceMappingURL=use-slot-key.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { useRef } from 'react';
2
+ /**
3
+ * A stable, unique key for **this component instance**, used to claim a slot in
4
+ * a parent collection (the axis / layer registries in `ChartRow`).
5
+ *
6
+ * This is *instance identity*, deliberately **not** `useId` and **not** a
7
+ * data-derived value:
8
+ * - `useId` is for SSR-stable accessibility attributes; the React docs
9
+ * explicitly discourage it as a collection key.
10
+ * - A React reconciliation `key` should come from your data — but that rule is
11
+ * about rendering *lists of data*. A registry of mounted *component instances*
12
+ * is a different thing: two `<LineChart>`s with identical props are still two
13
+ * distinct layers, and an axis's data `id` can legitimately repeat. The
14
+ * correct identity for "which mounted instance is this" is the instance
15
+ * itself, which a ref token captures (and which lets the registry update a
16
+ * slot in place rather than reorder — see `ChartRow`).
17
+ *
18
+ * The token is a `Symbol` created once per instance (lazily, no per-render
19
+ * allocation) and never escapes into rendered output.
20
+ */
21
+ export function useSlotKey() {
22
+ const ref = useRef(null);
23
+ return (ref.current ??= Symbol('chart-slot'));
24
+ }
25
+ //# sourceMappingURL=use-slot-key.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Pure time-range viewport math for pan/zoom (M4.2). The container holds the view
3
+ * range; these compute the next range from a gesture (the event surface in
4
+ * `Layers` supplies the pixel→time deltas). Kept pure + free of React/canvas so
5
+ * the geometry is unit-tested directly, like {@link maxSlotWidths} / the tracker.
6
+ */
7
+ export type TimeRange = readonly [number, number];
8
+ /**
9
+ * Shift a range by `dt` ms (drag-pan). The caller signs `dt` from the gesture —
10
+ * dragging the plot right reveals earlier data, i.e. a negative `dt`.
11
+ */
12
+ export declare function panRange(range: TimeRange, dt: number): [number, number];
13
+ /**
14
+ * Zoom `range` around `pivot` (ms) by `factor` — `< 1` zooms in, `> 1` out, with
15
+ * the pivot held fixed (the time under the cursor stays put). Clamped so the
16
+ * duration never drops below `minDuration` (the zoom-in floor); at the floor the
17
+ * pivot keeps its fractional position in the window.
18
+ */
19
+ export declare function zoomRange(range: TimeRange, pivot: number, factor: number, minDuration?: number): [number, number];
20
+ //# sourceMappingURL=viewport.d.ts.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Pure time-range viewport math for pan/zoom (M4.2). The container holds the view
3
+ * range; these compute the next range from a gesture (the event surface in
4
+ * `Layers` supplies the pixel→time deltas). Kept pure + free of React/canvas so
5
+ * the geometry is unit-tested directly, like {@link maxSlotWidths} / the tracker.
6
+ */
7
+ /**
8
+ * Shift a range by `dt` ms (drag-pan). The caller signs `dt` from the gesture —
9
+ * dragging the plot right reveals earlier data, i.e. a negative `dt`.
10
+ */
11
+ export function panRange(range, dt) {
12
+ return [range[0] + dt, range[1] + dt];
13
+ }
14
+ /**
15
+ * Zoom `range` around `pivot` (ms) by `factor` — `< 1` zooms in, `> 1` out, with
16
+ * the pivot held fixed (the time under the cursor stays put). Clamped so the
17
+ * duration never drops below `minDuration` (the zoom-in floor); at the floor the
18
+ * pivot keeps its fractional position in the window.
19
+ */
20
+ export function zoomRange(range, pivot, factor, minDuration = 1) {
21
+ const lo = pivot - (pivot - range[0]) * factor;
22
+ const hi = pivot + (range[1] - pivot) * factor;
23
+ if (hi - lo >= minDuration)
24
+ return [lo, hi];
25
+ // Floor reached: hold the pivot's fractional position, set span = minDuration.
26
+ const span = range[1] - range[0];
27
+ const frac = span > 0 ? (pivot - range[0]) / span : 0.5;
28
+ return [pivot - minDuration * frac, pivot + minDuration * (1 - frac)];
29
+ }
30
+ //# sourceMappingURL=viewport.js.map