@motionscript/charts 0.0.0-stage → 0.1.0-alpha.10

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 (158) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +49 -0
  4. package/README.md +98 -3
  5. package/dist/bar-chart/bar-chart.d.ts +191 -0
  6. package/dist/bar-chart/bar-chart.d.ts.map +1 -0
  7. package/dist/bar-chart/bar-chart.js +431 -0
  8. package/dist/bar-chart/bar-chart.js.map +1 -0
  9. package/dist/bar-chart/category-axis.d.ts +71 -0
  10. package/dist/bar-chart/category-axis.d.ts.map +1 -0
  11. package/dist/bar-chart/category-axis.js +119 -0
  12. package/dist/bar-chart/category-axis.js.map +1 -0
  13. package/dist/bar-chart/chart-bars.d.ts +129 -0
  14. package/dist/bar-chart/chart-bars.d.ts.map +1 -0
  15. package/dist/bar-chart/chart-bars.js +181 -0
  16. package/dist/bar-chart/chart-bars.js.map +1 -0
  17. package/dist/bar-chart/index.d.ts +16 -0
  18. package/dist/bar-chart/index.d.ts.map +1 -0
  19. package/dist/bar-chart/index.js +12 -0
  20. package/dist/bar-chart/index.js.map +1 -0
  21. package/dist/bar-chart/shared.d.ts +146 -0
  22. package/dist/bar-chart/shared.d.ts.map +1 -0
  23. package/dist/bar-chart/shared.js +182 -0
  24. package/dist/bar-chart/shared.js.map +1 -0
  25. package/dist/browser/chunks/chunk-TJNHLQGV.js +2 -0
  26. package/dist/browser/chunks/chunk-TJNHLQGV.js.map +7 -0
  27. package/dist/browser/index.js +2 -0
  28. package/dist/browser/index.js.map +7 -0
  29. package/dist/browser/kit.js +2 -0
  30. package/dist/browser/kit.js.map +7 -0
  31. package/dist/browser/manifest.json +13 -0
  32. package/dist/engine.d.ts +29 -0
  33. package/dist/engine.d.ts.map +1 -0
  34. package/dist/engine.js +29 -0
  35. package/dist/engine.js.map +1 -0
  36. package/dist/index.d.ts +6 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +6 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/kit/cartesian.d.ts +44 -0
  41. package/dist/kit/cartesian.d.ts.map +1 -0
  42. package/dist/kit/cartesian.js +59 -0
  43. package/dist/kit/cartesian.js.map +1 -0
  44. package/dist/kit/chart-border.d.ts +27 -0
  45. package/dist/kit/chart-border.d.ts.map +1 -0
  46. package/dist/kit/chart-border.js +28 -0
  47. package/dist/kit/chart-border.js.map +1 -0
  48. package/dist/kit/chart-canvas.d.ts +67 -0
  49. package/dist/kit/chart-canvas.d.ts.map +1 -0
  50. package/dist/kit/chart-canvas.js +68 -0
  51. package/dist/kit/chart-canvas.js.map +1 -0
  52. package/dist/kit/chart-legend.d.ts +164 -0
  53. package/dist/kit/chart-legend.d.ts.map +1 -0
  54. package/dist/kit/chart-legend.js +163 -0
  55. package/dist/kit/chart-legend.js.map +1 -0
  56. package/dist/kit/entries.d.ts +17 -0
  57. package/dist/kit/entries.d.ts.map +1 -0
  58. package/dist/kit/entries.js +19 -0
  59. package/dist/kit/entries.js.map +1 -0
  60. package/dist/kit/motion.d.ts +48 -0
  61. package/dist/kit/motion.d.ts.map +1 -0
  62. package/dist/kit/motion.js +63 -0
  63. package/dist/kit/motion.js.map +1 -0
  64. package/dist/kit/paints.d.ts +10 -0
  65. package/dist/kit/paints.d.ts.map +1 -0
  66. package/dist/kit/paints.js +12 -0
  67. package/dist/kit/paints.js.map +1 -0
  68. package/dist/kit/shared.d.ts +302 -0
  69. package/dist/kit/shared.d.ts.map +1 -0
  70. package/dist/kit/shared.js +496 -0
  71. package/dist/kit/shared.js.map +1 -0
  72. package/dist/kit/vertical-axis.d.ts +86 -0
  73. package/dist/kit/vertical-axis.d.ts.map +1 -0
  74. package/dist/kit/vertical-axis.js +141 -0
  75. package/dist/kit/vertical-axis.js.map +1 -0
  76. package/dist/line-chart/chart-line.d.ts +74 -0
  77. package/dist/line-chart/chart-line.d.ts.map +1 -0
  78. package/dist/line-chart/chart-line.js +135 -0
  79. package/dist/line-chart/chart-line.js.map +1 -0
  80. package/dist/line-chart/chart-region.d.ts +46 -0
  81. package/dist/line-chart/chart-region.d.ts.map +1 -0
  82. package/dist/line-chart/chart-region.js +67 -0
  83. package/dist/line-chart/chart-region.js.map +1 -0
  84. package/dist/line-chart/horizontal-axis.d.ts +79 -0
  85. package/dist/line-chart/horizontal-axis.d.ts.map +1 -0
  86. package/dist/line-chart/horizontal-axis.js +129 -0
  87. package/dist/line-chart/horizontal-axis.js.map +1 -0
  88. package/dist/line-chart/index.d.ts +22 -0
  89. package/dist/line-chart/index.d.ts.map +1 -0
  90. package/dist/line-chart/index.js +19 -0
  91. package/dist/line-chart/index.js.map +1 -0
  92. package/dist/line-chart/line-chart.d.ts +200 -0
  93. package/dist/line-chart/line-chart.d.ts.map +1 -0
  94. package/dist/line-chart/line-chart.js +473 -0
  95. package/dist/line-chart/line-chart.js.map +1 -0
  96. package/dist/line-chart/shared.d.ts +171 -0
  97. package/dist/line-chart/shared.d.ts.map +1 -0
  98. package/dist/line-chart/shared.js +285 -0
  99. package/dist/line-chart/shared.js.map +1 -0
  100. package/dist/line-chart/x-scale.d.ts +77 -0
  101. package/dist/line-chart/x-scale.d.ts.map +1 -0
  102. package/dist/line-chart/x-scale.js +313 -0
  103. package/dist/line-chart/x-scale.js.map +1 -0
  104. package/dist/nodes.d.ts +17 -0
  105. package/dist/nodes.d.ts.map +1 -0
  106. package/dist/nodes.js +18 -0
  107. package/dist/nodes.js.map +1 -0
  108. package/dist/pie-chart/index.d.ts +18 -0
  109. package/dist/pie-chart/index.d.ts.map +1 -0
  110. package/dist/pie-chart/index.js +14 -0
  111. package/dist/pie-chart/index.js.map +1 -0
  112. package/dist/pie-chart/pie-chart.d.ts +152 -0
  113. package/dist/pie-chart/pie-chart.d.ts.map +1 -0
  114. package/dist/pie-chart/pie-chart.js +222 -0
  115. package/dist/pie-chart/pie-chart.js.map +1 -0
  116. package/dist/pie-chart/pie-legend.d.ts +113 -0
  117. package/dist/pie-chart/pie-legend.d.ts.map +1 -0
  118. package/dist/pie-chart/pie-legend.js +117 -0
  119. package/dist/pie-chart/pie-legend.js.map +1 -0
  120. package/dist/pie-chart/pie-ring.d.ts +304 -0
  121. package/dist/pie-chart/pie-ring.d.ts.map +1 -0
  122. package/dist/pie-chart/pie-ring.js +680 -0
  123. package/dist/pie-chart/pie-ring.js.map +1 -0
  124. package/dist/pie-chart/shared.d.ts +216 -0
  125. package/dist/pie-chart/shared.d.ts.map +1 -0
  126. package/dist/pie-chart/shared.js +246 -0
  127. package/dist/pie-chart/shared.js.map +1 -0
  128. package/package.json +72 -3
  129. package/registry.json +23 -0
  130. package/src/bar-chart/bar-chart.ts +542 -0
  131. package/src/bar-chart/category-axis.ts +162 -0
  132. package/src/bar-chart/chart-bars.ts +287 -0
  133. package/src/bar-chart/index.ts +31 -0
  134. package/src/bar-chart/shared.ts +354 -0
  135. package/src/engine.ts +28 -0
  136. package/src/index.ts +5 -0
  137. package/src/kit/cartesian.ts +81 -0
  138. package/src/kit/chart-border.ts +50 -0
  139. package/src/kit/chart-canvas.ts +122 -0
  140. package/src/kit/chart-legend.ts +229 -0
  141. package/src/kit/entries.ts +20 -0
  142. package/src/kit/motion.ts +90 -0
  143. package/src/kit/paints.ts +15 -0
  144. package/src/kit/shared.ts +782 -0
  145. package/src/kit/vertical-axis.ts +194 -0
  146. package/src/line-chart/chart-line.ts +156 -0
  147. package/src/line-chart/chart-region.ts +90 -0
  148. package/src/line-chart/horizontal-axis.ts +151 -0
  149. package/src/line-chart/index.ts +39 -0
  150. package/src/line-chart/line-chart.ts +598 -0
  151. package/src/line-chart/shared.ts +473 -0
  152. package/src/line-chart/x-scale.ts +382 -0
  153. package/src/nodes.ts +18 -0
  154. package/src/pie-chart/index.ts +37 -0
  155. package/src/pie-chart/pie-chart.ts +296 -0
  156. package/src/pie-chart/pie-legend.ts +169 -0
  157. package/src/pie-chart/pie-ring.ts +848 -0
  158. package/src/pie-chart/shared.ts +496 -0
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Types and pure helpers specific to the {@link LineChart} family: the traced
3
+ * polylines, the spotlit region they dim outside of, and the theme those two
4
+ * add to the shared chart vocabulary.
5
+ *
6
+ * Everything a bar or a pie chart would want too — what a series is, how an axis
7
+ * is scaled and formatted, the theme leaf resolvers, the legend and plot-panel
8
+ * branches — lives in `../kit/shared.ts` and is imported here. What's
9
+ * left below is what only a line chart has: points sorted along an x column,
10
+ * a region clipped out of a polyline, and the `region` branch of the theme.
11
+ *
12
+ * Kept renderer-agnostic (no `Node` imports) so every part shares one
13
+ * scale/format/palette/theme vocabulary, and so a host's own inspector
14
+ * mapping can build a partial {@link ChartTheme} without
15
+ * pulling the components in.
16
+ */
17
+ import { type DataRecord, type Fill, type Stroke } from "@motionscript/sdk";
18
+ import { type AxisConfig, type ChartSeries, type LegendConfig, type LegendTheme, type PlotAreaConfig, type Range } from "../kit/shared.js";
19
+ import type { XScale } from "./x-scale.js";
20
+ /**
21
+ * One series in a {@link LineChart}: a polyline traced through the values in one
22
+ * column of the chart's `data`, drawn in `fill` (or the theme's `seriesStyles`
23
+ * palette when omitted) and captioned by `label` in the legend.
24
+ *
25
+ * The family-wide {@link ChartSeries} under a name that says what it draws here;
26
+ * the chart's own `xField` prop names the column carrying the shared x values,
27
+ * so every series is read off the same rows — one row per sample, one column per
28
+ * series.
29
+ */
30
+ export type LineSeries = ChartSeries;
31
+ /**
32
+ * A rectangular slice of the plot, in **data units** — the author-facing input
33
+ * to {@link LineChart.spotlightRegion} / {@link LineChart.zoomTo}.
34
+ *
35
+ * Either axis may be omitted, and that's the point: `{ x: [60, 100] }` is a
36
+ * vertical band spanning the full height, `{ y: [8, 10] }` a horizontal band
37
+ * spanning the full width, and both together a box. A reversed range is read
38
+ * the same as the forward one — a region is a span, not a direction.
39
+ */
40
+ export interface PlotRegion {
41
+ /** Horizontal span in x-axis data units. Omit to span the full plot width. */
42
+ x?: Range;
43
+ /** Vertical span in y-axis data units. Omit to span the full plot height. */
44
+ y?: Range;
45
+ }
46
+ /**
47
+ * A {@link PlotRegion} resolved into the plot's **centred pixel space** (origin
48
+ * at the plot centre, y-up) — the same local frame the series points live in,
49
+ * so a line can be clipped against it directly.
50
+ *
51
+ * `xBounded`/`yBounded` carry which axes the author actually constrained: an
52
+ * unbounded axis has its edges pinned to the plot's own and draws no rule.
53
+ */
54
+ export interface RegionBox {
55
+ left: number;
56
+ right: number;
57
+ bottom: number;
58
+ top: number;
59
+ /** The author gave an x range — the left/right edges are real data bounds. */
60
+ xBounded: boolean;
61
+ /** The author gave a y range — the bottom/top edges are real data bounds. */
62
+ yBounded: boolean;
63
+ }
64
+ /** The empty box, used as the "no region" resting value. */
65
+ export declare const EMPTY_REGION_BOX: RegionBox;
66
+ /** Is `box` a real, non-degenerate region — i.e. is there anything to spotlight? */
67
+ export declare function hasRegion(box: RegionBox): boolean;
68
+ /**
69
+ * Resolve a {@link PlotRegion} against the plot's pixel box and the chart's
70
+ * scales. `plot` is the plot rectangle in centred pixel space, and
71
+ * `xScale`/`yScale` the data ranges those edges span.
72
+ *
73
+ * An omitted axis takes the plot's own edges, so the region spans it. A given
74
+ * axis is clamped to the plot, so a range running off the scale still produces
75
+ * a box inside the panel.
76
+ */
77
+ export declare function resolveRegionBox(region: PlotRegion | null | undefined, plot: {
78
+ left: number;
79
+ right: number;
80
+ bottom: number;
81
+ top: number;
82
+ }, xScale: Range, yScale: Range): RegionBox;
83
+ /** A bare `{x, y}` — declared locally so this module stays free of node imports. */
84
+ interface Vector2Like {
85
+ x: number;
86
+ y: number;
87
+ }
88
+ /**
89
+ * The parts of `points` that fall **inside** `box`, as a list of contiguous
90
+ * sub-polylines — the geometry behind the region spotlight: a series is drawn
91
+ * dimmed in full, then these runs are re-drawn over it at full strength.
92
+ *
93
+ * Segments are clipped against the box rather than filtered by endpoint, so a
94
+ * line entering the region mid-segment is cut exactly on the boundary.
95
+ */
96
+ export declare function clipPolylineToBox(points: Vector2Like[], box: RegionBox): Vector2Like[][];
97
+ /**
98
+ * How a spotlit {@link PlotRegion} is drawn — the dashed rules on its bounded
99
+ * edges, the wash filling it, and how far the series fade outside it.
100
+ */
101
+ export interface RegionConfig {
102
+ /** The dashed rule marking each bounded edge. */
103
+ stroke: Stroke;
104
+ /** Wash filling the region, drawn under the series lines. */
105
+ fill: Fill;
106
+ /** Opacity the stretch of every series *outside* the region fades to. */
107
+ dimOpacity: number;
108
+ }
109
+ /** A {@link LineChart}'s full, resolved visual theme. */
110
+ export interface ChartConfig {
111
+ /**
112
+ * Default per-series line stroke, cycled by series index (`i % length`). A
113
+ * series' own `fill`, when set, overrides just the colour.
114
+ */
115
+ seriesStyles: Stroke[];
116
+ xAxis: AxisConfig;
117
+ yAxis: AxisConfig;
118
+ /** The plot panel: border, fill, and drop shadow. */
119
+ plotArea: PlotAreaConfig;
120
+ legend: LegendConfig;
121
+ /** The spotlit-region annotation — see {@link RegionConfig}. */
122
+ region: RegionConfig;
123
+ }
124
+ /**
125
+ * Author-facing partial theme, passed as {@link LineChart}'s `theme` prop —
126
+ * every branch optional and deep-merged onto {@link DEFAULT_CHART_THEME}.
127
+ */
128
+ export type ChartTheme = {
129
+ seriesStyles?: Stroke[];
130
+ xAxis?: Partial<AxisConfig>;
131
+ yAxis?: Partial<AxisConfig>;
132
+ plotArea?: Partial<PlotAreaConfig>;
133
+ legend?: LegendTheme;
134
+ region?: Partial<RegionConfig>;
135
+ };
136
+ /** The chart's out-of-the-box look. */
137
+ export declare const DEFAULT_CHART_THEME: ChartConfig;
138
+ /**
139
+ * {@link LineChart}'s `theme` entry's resolve: merges the author's partial
140
+ * {@link ChartTheme} onto `previous` (falling back to
141
+ * {@link DEFAULT_CHART_THEME} on first resolution), fully resolving every leaf.
142
+ */
143
+ export declare function resolveChartTheme(theme: ChartTheme | undefined, previous?: ChartConfig): ChartConfig;
144
+ /** {@link LineChart}'s `theme` entry's lerp — leaf by leaf. */
145
+ export declare function lerpChartTheme(from: ChartConfig, to: ChartConfig, t: number): ChartConfig;
146
+ /**
147
+ * Resolve one series' `[x, y]` points out of the chart's rows: each row's
148
+ * position on the {@link XScale} paired with its own `field` column, sorted
149
+ * along x so the polyline traces left-to-right whatever order the rows arrived
150
+ * in.
151
+ *
152
+ * A row missing its y column contributes a 0 rather than a gap — these charts
153
+ * draw continuous lines, so dropping the point would silently bridge the hole.
154
+ *
155
+ * Takes the resolved scale rather than the column *name*, because what a row's x
156
+ * is depends on what kind of column it came out of: a number, an instant, or a
157
+ * place in the file. See `x-scale.ts`.
158
+ */
159
+ export declare function seriesPoints(data: DataRecord[], x: XScale, series: LineSeries): [x: number, y: number][];
160
+ /** Min/max across every series' column, the fallback when unset. */
161
+ export declare function yExtent(data: DataRecord[], series: LineSeries[]): Range;
162
+ /**
163
+ * Effective vertical scale: the explicit `range`, else the data's y extent
164
+ * nudged out to tidy step bounds so markers read as round numbers.
165
+ *
166
+ * `divisions` is how many segments the auto-range is stepped into (one fewer
167
+ * than a marker count) — it only shapes the tidy step when `range` is omitted.
168
+ */
169
+ export declare function resolveYScale(data: DataRecord[], series: LineSeries[], range: Range | undefined, divisions: number): Range;
170
+ export {};
171
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/line-chart/shared.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Types and pure helpers specific to the {@link LineChart} family: the traced
3
+ * polylines, the spotlit region they dim outside of, and the theme those two
4
+ * add to the shared chart vocabulary.
5
+ *
6
+ * Everything a bar or a pie chart would want too — what a series is, how an axis
7
+ * is scaled and formatted, the theme leaf resolvers, the legend and plot-panel
8
+ * branches — lives in `../kit/shared.ts` and is imported here. What's
9
+ * left below is what only a line chart has: points sorted along an x column,
10
+ * a region clipped out of a polyline, and the `region` branch of the theme.
11
+ *
12
+ * Kept renderer-agnostic (no `Node` imports) so every part shares one
13
+ * scale/format/palette/theme vocabulary, and so a host's own inspector
14
+ * mapping can build a partial {@link ChartTheme} without
15
+ * pulling the components in.
16
+ */
17
+ import { Fills, fillOps, lerpNumber, strokeOps, } from "@motionscript/sdk";
18
+ import { CHART_PALETTE, DEFAULT_LEGEND, DEFAULT_PLOT_AREA, DEFAULT_SERIES_STROKES, DEFAULT_X_AXIS, DEFAULT_Y_AXIS, SPOTLIGHT_DIM, lerpAxisConfig, lerpLegendConfig, lerpPlotArea, lerpSeriesStrokes, niceStep, num, resolveAxisConfig, resolveLegendConfig, resolvePlotArea, resolveSeriesStrokes, } from "../kit/shared.js";
19
+ /** The empty box, used as the "no region" resting value. */
20
+ export const EMPTY_REGION_BOX = {
21
+ left: 0,
22
+ right: 0,
23
+ bottom: 0,
24
+ top: 0,
25
+ xBounded: false,
26
+ yBounded: false,
27
+ };
28
+ /** Is `box` a real, non-degenerate region — i.e. is there anything to spotlight? */
29
+ export function hasRegion(box) {
30
+ return box.right > box.left && box.top > box.bottom;
31
+ }
32
+ /**
33
+ * Resolve a {@link PlotRegion} against the plot's pixel box and the chart's
34
+ * scales. `plot` is the plot rectangle in centred pixel space, and
35
+ * `xScale`/`yScale` the data ranges those edges span.
36
+ *
37
+ * An omitted axis takes the plot's own edges, so the region spans it. A given
38
+ * axis is clamped to the plot, so a range running off the scale still produces
39
+ * a box inside the panel.
40
+ */
41
+ export function resolveRegionBox(region, plot, xScale, yScale) {
42
+ if (!region || (region.x === undefined && region.y === undefined)) {
43
+ return EMPTY_REGION_BOX;
44
+ }
45
+ const span = (range, [min, max], lo, hi) => {
46
+ if (range === undefined || max <= min)
47
+ return [lo, hi, false];
48
+ const a = Math.min(range[0], range[1]);
49
+ const b = Math.max(range[0], range[1]);
50
+ const toPx = (v) => lo + ((v - min) / (max - min)) * (hi - lo);
51
+ return [
52
+ Math.max(lo, Math.min(hi, toPx(a))),
53
+ Math.max(lo, Math.min(hi, toPx(b))),
54
+ true,
55
+ ];
56
+ };
57
+ const [left, right, xBounded] = span(region.x, xScale, plot.left, plot.right);
58
+ const [bottom, top, yBounded] = span(region.y, yScale, plot.bottom, plot.top);
59
+ return { left, right, bottom, top, xBounded, yBounded };
60
+ }
61
+ /**
62
+ * The parts of `points` that fall **inside** `box`, as a list of contiguous
63
+ * sub-polylines — the geometry behind the region spotlight: a series is drawn
64
+ * dimmed in full, then these runs are re-drawn over it at full strength.
65
+ *
66
+ * Segments are clipped against the box rather than filtered by endpoint, so a
67
+ * line entering the region mid-segment is cut exactly on the boundary.
68
+ */
69
+ export function clipPolylineToBox(points, box) {
70
+ if (points.length < 2 || !hasRegion(box))
71
+ return [];
72
+ const runs = [];
73
+ let run = [];
74
+ for (let i = 1; i < points.length; i++) {
75
+ const seg = clipSegment(points[i - 1], points[i], box);
76
+ if (!seg) {
77
+ // Segment misses the box entirely — close whatever run was open.
78
+ if (run.length >= 2)
79
+ runs.push(run);
80
+ run = [];
81
+ continue;
82
+ }
83
+ const [a, b] = seg;
84
+ // Continue the open run when this clipped segment starts where the last one
85
+ // ended; otherwise the line left the box and came back, which is a new run.
86
+ if (run.length > 0 && samePoint(run[run.length - 1], a))
87
+ run.push(b);
88
+ else {
89
+ if (run.length >= 2)
90
+ runs.push(run);
91
+ run = [a, b];
92
+ }
93
+ }
94
+ if (run.length >= 2)
95
+ runs.push(run);
96
+ return runs;
97
+ }
98
+ /** Points within half a pixel are the same point, for run-joining purposes. */
99
+ function samePoint(a, b) {
100
+ return Math.abs(a.x - b.x) < 0.5 && Math.abs(a.y - b.y) < 0.5;
101
+ }
102
+ /**
103
+ * Liang–Barsky clip of one segment against the axis-aligned `box`. Returns the
104
+ * surviving `[start, end]`, or `null` when the segment lies wholly outside.
105
+ */
106
+ function clipSegment(a, b, box) {
107
+ const dx = b.x - a.x;
108
+ const dy = b.y - a.y;
109
+ let t0 = 0;
110
+ let t1 = 1;
111
+ // Each edge as (p, q): the segment survives where p·t <= q.
112
+ const edges = [
113
+ [-dx, a.x - box.left],
114
+ [dx, box.right - a.x],
115
+ [-dy, a.y - box.bottom],
116
+ [dy, box.top - a.y],
117
+ ];
118
+ for (const [p, q] of edges) {
119
+ if (p === 0) {
120
+ // Parallel to this edge: outside it means the whole segment is out.
121
+ if (q < 0)
122
+ return null;
123
+ continue;
124
+ }
125
+ const r = q / p;
126
+ if (p < 0) {
127
+ if (r > t1)
128
+ return null;
129
+ if (r > t0)
130
+ t0 = r;
131
+ }
132
+ else {
133
+ if (r < t0)
134
+ return null;
135
+ if (r < t1)
136
+ t1 = r;
137
+ }
138
+ }
139
+ return [
140
+ { x: a.x + t0 * dx, y: a.y + t0 * dy },
141
+ { x: a.x + t1 * dx, y: a.y + t1 * dy },
142
+ ];
143
+ }
144
+ /**
145
+ * The legend of a line chart: the shared one, with a small rounded-square
146
+ * swatch in place of the wide chip. A bar's or a wedge's swatch stands in for
147
+ * an area of paint and is drawn like one; a line's stands in for a colour, and
148
+ * a square reads as the sample it is.
149
+ */
150
+ const LINE_CHART_LEGEND = {
151
+ ...DEFAULT_LEGEND,
152
+ markerStyle: {
153
+ ...DEFAULT_LEGEND.markerStyle,
154
+ width: 20,
155
+ height: 20,
156
+ borderRadius: 6,
157
+ },
158
+ };
159
+ /** The chart's out-of-the-box look. */
160
+ export const DEFAULT_CHART_THEME = {
161
+ seriesStyles: DEFAULT_SERIES_STROKES,
162
+ xAxis: DEFAULT_X_AXIS,
163
+ yAxis: DEFAULT_Y_AXIS,
164
+ plotArea: DEFAULT_PLOT_AREA,
165
+ legend: LINE_CHART_LEGEND,
166
+ region: {
167
+ // Brighter than the grid and dashed, so the boundary reads as a call-out
168
+ // rather than another axis line; `butt` caps keep the dash lengths exact.
169
+ stroke: {
170
+ weight: 3,
171
+ fill: CHART_PALETTE.regionColor,
172
+ dash: [14, 12],
173
+ cap: "butt",
174
+ },
175
+ // A wash barely above the panel — enough to tint the band, not enough to
176
+ // mute the series drawn over it.
177
+ fill: Fills.color(CHART_PALETTE.regionColor, { opacity: 0.08 }),
178
+ dimOpacity: SPOTLIGHT_DIM,
179
+ },
180
+ };
181
+ function resolveRegionConfig(region, previous) {
182
+ return {
183
+ stroke: strokeOps.resolve(region?.stroke ?? previous.stroke),
184
+ fill: fillOps.resolve(region?.fill ?? previous.fill),
185
+ dimOpacity: region?.dimOpacity ?? previous.dimOpacity,
186
+ };
187
+ }
188
+ function lerpRegionConfig(from, to, t) {
189
+ return {
190
+ stroke: strokeOps.lerp(from.stroke, to.stroke, t),
191
+ fill: fillOps.lerp(from.fill, to.fill, t),
192
+ dimOpacity: lerpNumber(from.dimOpacity, to.dimOpacity, t),
193
+ };
194
+ }
195
+ /**
196
+ * {@link LineChart}'s `theme` entry's resolve: merges the author's partial
197
+ * {@link ChartTheme} onto `previous` (falling back to
198
+ * {@link DEFAULT_CHART_THEME} on first resolution), fully resolving every leaf.
199
+ */
200
+ export function resolveChartTheme(theme, previous) {
201
+ const base = previous ?? DEFAULT_CHART_THEME;
202
+ return {
203
+ seriesStyles: resolveSeriesStrokes(theme?.seriesStyles, base.seriesStyles),
204
+ xAxis: resolveAxisConfig(theme?.xAxis, base.xAxis),
205
+ yAxis: resolveAxisConfig(theme?.yAxis, base.yAxis),
206
+ plotArea: resolvePlotArea(theme?.plotArea, base.plotArea),
207
+ legend: resolveLegendConfig(theme?.legend, base.legend),
208
+ region: resolveRegionConfig(theme?.region, base.region),
209
+ };
210
+ }
211
+ /** {@link LineChart}'s `theme` entry's lerp — leaf by leaf. */
212
+ export function lerpChartTheme(from, to, t) {
213
+ return {
214
+ seriesStyles: lerpSeriesStrokes(from.seriesStyles, to.seriesStyles, t),
215
+ xAxis: lerpAxisConfig(from.xAxis, to.xAxis, t),
216
+ yAxis: lerpAxisConfig(from.yAxis, to.yAxis, t),
217
+ plotArea: lerpPlotArea(from.plotArea, to.plotArea, t),
218
+ legend: lerpLegendConfig(from.legend, to.legend, t),
219
+ region: lerpRegionConfig(from.region, to.region, t),
220
+ };
221
+ }
222
+ // ---- Scales --------------------------------------------------------------
223
+ /**
224
+ * Resolve one series' `[x, y]` points out of the chart's rows: each row's
225
+ * position on the {@link XScale} paired with its own `field` column, sorted
226
+ * along x so the polyline traces left-to-right whatever order the rows arrived
227
+ * in.
228
+ *
229
+ * A row missing its y column contributes a 0 rather than a gap — these charts
230
+ * draw continuous lines, so dropping the point would silently bridge the hole.
231
+ *
232
+ * Takes the resolved scale rather than the column *name*, because what a row's x
233
+ * is depends on what kind of column it came out of: a number, an instant, or a
234
+ * place in the file. See `x-scale.ts`.
235
+ */
236
+ export function seriesPoints(data, x, series) {
237
+ return data
238
+ .map((row, i) => [x.at(i), num(row, series.field)])
239
+ .sort((a, b) => a[0] - b[0]);
240
+ }
241
+ /** Min/max across every series' column, the fallback when unset. */
242
+ export function yExtent(data, series) {
243
+ let lo = Infinity;
244
+ let hi = -Infinity;
245
+ for (const row of data)
246
+ for (const s of series) {
247
+ const v = num(row, s.field);
248
+ if (v < lo)
249
+ lo = v;
250
+ if (v > hi)
251
+ hi = v;
252
+ }
253
+ return lo <= hi ? [lo, hi] : [0, 1];
254
+ }
255
+ /**
256
+ * Effective vertical scale: the explicit `range`, else the data's y extent
257
+ * nudged out to tidy step bounds so markers read as round numbers.
258
+ *
259
+ * `divisions` is how many segments the auto-range is stepped into (one fewer
260
+ * than a marker count) — it only shapes the tidy step when `range` is omitted.
261
+ */
262
+ export function resolveYScale(data, series, range, divisions) {
263
+ if (range)
264
+ return range;
265
+ const [lo, hi] = yExtent(data, series);
266
+ const segments = Math.max(1, Math.round(divisions));
267
+ if (hi === lo)
268
+ return constantScale(lo, segments);
269
+ const step = niceStep((hi - lo) / segments);
270
+ return [Math.floor(lo / step) * step, Math.ceil(hi / step) * step];
271
+ }
272
+ /**
273
+ * The scale for data that never moves. A zero-width scale puts every tick on the
274
+ * same value and the line on the plot's floor, so it is widened to a few steps
275
+ * either side of the value (the step sized to its magnitude, so `5` gets
276
+ * `[3, 7]` and `5000` gets `[3000, 7000]`), or to `[0, 1]` for all zeros, as for
277
+ * no data at all.
278
+ */
279
+ function constantScale(value, segments) {
280
+ if (value === 0)
281
+ return [0, 1];
282
+ const step = niceStep(Math.abs(value) / segments);
283
+ return [value - step, value + step];
284
+ }
285
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/line-chart/shared.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Types and pure helpers specific to the {@link LineChart} family: the traced\n * polylines, the spotlit region they dim outside of, and the theme those two\n * add to the shared chart vocabulary.\n *\n * Everything a bar or a pie chart would want too — what a series is, how an axis\n * is scaled and formatted, the theme leaf resolvers, the legend and plot-panel\n * branches — lives in `../kit/shared.ts` and is imported here. What's\n * left below is what only a line chart has: points sorted along an x column,\n * a region clipped out of a polyline, and the `region` branch of the theme.\n *\n * Kept renderer-agnostic (no `Node` imports) so every part shares one\n * scale/format/palette/theme vocabulary, and so a host's own inspector\n * mapping can build a partial {@link ChartTheme} without\n * pulling the components in.\n */\n\nimport {\n Fills,\n fillOps,\n lerpNumber,\n strokeOps,\n type DataRecord,\n type Fill,\n type FillResolved,\n type Stroke,\n type StrokeResolved,\n} from \"@motionscript/sdk\"\n\nimport {\n CHART_PALETTE,\n DEFAULT_LEGEND,\n DEFAULT_PLOT_AREA,\n DEFAULT_SERIES_STROKES,\n DEFAULT_X_AXIS,\n DEFAULT_Y_AXIS,\n SPOTLIGHT_DIM,\n lerpAxisConfig,\n lerpLegendConfig,\n lerpPlotArea,\n lerpSeriesStrokes,\n niceStep,\n num,\n resolveAxisConfig,\n resolveLegendConfig,\n resolvePlotArea,\n resolveSeriesStrokes,\n type AxisConfig,\n type ChartSeries,\n type LegendConfig,\n type LegendTheme,\n type PlotAreaConfig,\n type Range,\n} from \"../kit/shared\"\nimport type { XScale } from \"./x-scale\"\n\n/**\n * One series in a {@link LineChart}: a polyline traced through the values in one\n * column of the chart's `data`, drawn in `fill` (or the theme's `seriesStyles`\n * palette when omitted) and captioned by `label` in the legend.\n *\n * The family-wide {@link ChartSeries} under a name that says what it draws here;\n * the chart's own `xField` prop names the column carrying the shared x values,\n * so every series is read off the same rows — one row per sample, one column per\n * series.\n */\nexport type LineSeries = ChartSeries\n\n// ---- Plot regions -------------------------------------------------------\n\n/**\n * A rectangular slice of the plot, in **data units** — the author-facing input\n * to {@link LineChart.spotlightRegion} / {@link LineChart.zoomTo}.\n *\n * Either axis may be omitted, and that's the point: `{ x: [60, 100] }` is a\n * vertical band spanning the full height, `{ y: [8, 10] }` a horizontal band\n * spanning the full width, and both together a box. A reversed range is read\n * the same as the forward one — a region is a span, not a direction.\n */\nexport interface PlotRegion {\n /** Horizontal span in x-axis data units. Omit to span the full plot width. */\n x?: Range\n /** Vertical span in y-axis data units. Omit to span the full plot height. */\n y?: Range\n}\n\n/**\n * A {@link PlotRegion} resolved into the plot's **centred pixel space** (origin\n * at the plot centre, y-up) — the same local frame the series points live in,\n * so a line can be clipped against it directly.\n *\n * `xBounded`/`yBounded` carry which axes the author actually constrained: an\n * unbounded axis has its edges pinned to the plot's own and draws no rule.\n */\nexport interface RegionBox {\n left: number\n right: number\n bottom: number\n top: number\n /** The author gave an x range — the left/right edges are real data bounds. */\n xBounded: boolean\n /** The author gave a y range — the bottom/top edges are real data bounds. */\n yBounded: boolean\n}\n\n/** The empty box, used as the \"no region\" resting value. */\nexport const EMPTY_REGION_BOX: RegionBox = {\n left: 0,\n right: 0,\n bottom: 0,\n top: 0,\n xBounded: false,\n yBounded: false,\n}\n\n/** Is `box` a real, non-degenerate region — i.e. is there anything to spotlight? */\nexport function hasRegion(box: RegionBox): boolean {\n return box.right > box.left && box.top > box.bottom\n}\n\n/**\n * Resolve a {@link PlotRegion} against the plot's pixel box and the chart's\n * scales. `plot` is the plot rectangle in centred pixel space, and\n * `xScale`/`yScale` the data ranges those edges span.\n *\n * An omitted axis takes the plot's own edges, so the region spans it. A given\n * axis is clamped to the plot, so a range running off the scale still produces\n * a box inside the panel.\n */\nexport function resolveRegionBox(\n region: PlotRegion | null | undefined,\n plot: { left: number; right: number; bottom: number; top: number },\n xScale: Range,\n yScale: Range\n): RegionBox {\n if (!region || (region.x === undefined && region.y === undefined)) {\n return EMPTY_REGION_BOX\n }\n\n const span = (\n range: Range | undefined,\n [min, max]: Range,\n lo: number,\n hi: number\n ): [number, number, boolean] => {\n if (range === undefined || max <= min) return [lo, hi, false]\n const a = Math.min(range[0], range[1])\n const b = Math.max(range[0], range[1])\n const toPx = (v: number) => lo + ((v - min) / (max - min)) * (hi - lo)\n return [\n Math.max(lo, Math.min(hi, toPx(a))),\n Math.max(lo, Math.min(hi, toPx(b))),\n true,\n ]\n }\n\n const [left, right, xBounded] = span(region.x, xScale, plot.left, plot.right)\n const [bottom, top, yBounded] = span(region.y, yScale, plot.bottom, plot.top)\n return { left, right, bottom, top, xBounded, yBounded }\n}\n\n/** A bare `{x, y}` — declared locally so this module stays free of node imports. */\ninterface Vector2Like {\n x: number\n y: number\n}\n\n/**\n * The parts of `points` that fall **inside** `box`, as a list of contiguous\n * sub-polylines — the geometry behind the region spotlight: a series is drawn\n * dimmed in full, then these runs are re-drawn over it at full strength.\n *\n * Segments are clipped against the box rather than filtered by endpoint, so a\n * line entering the region mid-segment is cut exactly on the boundary.\n */\nexport function clipPolylineToBox(\n points: Vector2Like[],\n box: RegionBox\n): Vector2Like[][] {\n if (points.length < 2 || !hasRegion(box)) return []\n\n const runs: Vector2Like[][] = []\n let run: Vector2Like[] = []\n\n for (let i = 1; i < points.length; i++) {\n const seg = clipSegment(points[i - 1], points[i], box)\n if (!seg) {\n // Segment misses the box entirely — close whatever run was open.\n if (run.length >= 2) runs.push(run)\n run = []\n continue\n }\n const [a, b] = seg\n // Continue the open run when this clipped segment starts where the last one\n // ended; otherwise the line left the box and came back, which is a new run.\n if (run.length > 0 && samePoint(run[run.length - 1], a)) run.push(b)\n else {\n if (run.length >= 2) runs.push(run)\n run = [a, b]\n }\n }\n if (run.length >= 2) runs.push(run)\n return runs\n}\n\n/** Points within half a pixel are the same point, for run-joining purposes. */\nfunction samePoint(a: Vector2Like, b: Vector2Like): boolean {\n return Math.abs(a.x - b.x) < 0.5 && Math.abs(a.y - b.y) < 0.5\n}\n\n/**\n * Liang–Barsky clip of one segment against the axis-aligned `box`. Returns the\n * surviving `[start, end]`, or `null` when the segment lies wholly outside.\n */\nfunction clipSegment(\n a: Vector2Like,\n b: Vector2Like,\n box: RegionBox\n): [Vector2Like, Vector2Like] | null {\n const dx = b.x - a.x\n const dy = b.y - a.y\n let t0 = 0\n let t1 = 1\n\n // Each edge as (p, q): the segment survives where p·t <= q.\n const edges: [number, number][] = [\n [-dx, a.x - box.left],\n [dx, box.right - a.x],\n [-dy, a.y - box.bottom],\n [dy, box.top - a.y],\n ]\n for (const [p, q] of edges) {\n if (p === 0) {\n // Parallel to this edge: outside it means the whole segment is out.\n if (q < 0) return null\n continue\n }\n const r = q / p\n if (p < 0) {\n if (r > t1) return null\n if (r > t0) t0 = r\n } else {\n if (r < t0) return null\n if (r < t1) t1 = r\n }\n }\n return [\n { x: a.x + t0 * dx, y: a.y + t0 * dy },\n { x: a.x + t1 * dx, y: a.y + t1 * dy },\n ]\n}\n\n// ---- Theme ---------------------------------------------------------------\n\n/**\n * How a spotlit {@link PlotRegion} is drawn — the dashed rules on its bounded\n * edges, the wash filling it, and how far the series fade outside it.\n */\nexport interface RegionConfig {\n /** The dashed rule marking each bounded edge. */\n stroke: Stroke\n /** Wash filling the region, drawn under the series lines. */\n fill: Fill\n /** Opacity the stretch of every series *outside* the region fades to. */\n dimOpacity: number\n}\n\n/** A {@link LineChart}'s full, resolved visual theme. */\nexport interface ChartConfig {\n /**\n * Default per-series line stroke, cycled by series index (`i % length`). A\n * series' own `fill`, when set, overrides just the colour.\n */\n seriesStyles: Stroke[]\n xAxis: AxisConfig\n yAxis: AxisConfig\n /** The plot panel: border, fill, and drop shadow. */\n plotArea: PlotAreaConfig\n legend: LegendConfig\n /** The spotlit-region annotation — see {@link RegionConfig}. */\n region: RegionConfig\n}\n\n/**\n * Author-facing partial theme, passed as {@link LineChart}'s `theme` prop —\n * every branch optional and deep-merged onto {@link DEFAULT_CHART_THEME}.\n */\nexport type ChartTheme = {\n seriesStyles?: Stroke[]\n xAxis?: Partial<AxisConfig>\n yAxis?: Partial<AxisConfig>\n plotArea?: Partial<PlotAreaConfig>\n legend?: LegendTheme\n region?: Partial<RegionConfig>\n}\n\n/**\n * The legend of a line chart: the shared one, with a small rounded-square\n * swatch in place of the wide chip. A bar's or a wedge's swatch stands in for\n * an area of paint and is drawn like one; a line's stands in for a colour, and\n * a square reads as the sample it is.\n */\nconst LINE_CHART_LEGEND: LegendConfig = {\n ...DEFAULT_LEGEND,\n markerStyle: {\n ...DEFAULT_LEGEND.markerStyle,\n width: 20,\n height: 20,\n borderRadius: 6,\n },\n}\n\n/** The chart's out-of-the-box look. */\nexport const DEFAULT_CHART_THEME: ChartConfig = {\n seriesStyles: DEFAULT_SERIES_STROKES,\n xAxis: DEFAULT_X_AXIS,\n yAxis: DEFAULT_Y_AXIS,\n plotArea: DEFAULT_PLOT_AREA,\n legend: LINE_CHART_LEGEND,\n region: {\n // Brighter than the grid and dashed, so the boundary reads as a call-out\n // rather than another axis line; `butt` caps keep the dash lengths exact.\n stroke: {\n weight: 3,\n fill: CHART_PALETTE.regionColor,\n dash: [14, 12],\n cap: \"butt\",\n },\n // A wash barely above the panel — enough to tint the band, not enough to\n // mute the series drawn over it.\n fill: Fills.color(CHART_PALETTE.regionColor, { opacity: 0.08 }),\n dimOpacity: SPOTLIGHT_DIM,\n },\n}\n\nfunction resolveRegionConfig(\n region: Partial<RegionConfig> | undefined,\n previous: RegionConfig\n): RegionConfig {\n return {\n stroke: strokeOps.resolve(region?.stroke ?? previous.stroke),\n fill: fillOps.resolve(region?.fill ?? previous.fill),\n dimOpacity: region?.dimOpacity ?? previous.dimOpacity,\n }\n}\n\nfunction lerpRegionConfig(\n from: RegionConfig,\n to: RegionConfig,\n t: number\n): RegionConfig {\n return {\n stroke: strokeOps.lerp(\n from.stroke as StrokeResolved[],\n to.stroke as StrokeResolved[],\n t\n ),\n fill: fillOps.lerp(\n from.fill as FillResolved[],\n to.fill as FillResolved[],\n t\n ),\n dimOpacity: lerpNumber(from.dimOpacity, to.dimOpacity, t),\n }\n}\n\n/**\n * {@link LineChart}'s `theme` entry's resolve: merges the author's partial\n * {@link ChartTheme} onto `previous` (falling back to\n * {@link DEFAULT_CHART_THEME} on first resolution), fully resolving every leaf.\n */\nexport function resolveChartTheme(\n theme: ChartTheme | undefined,\n previous?: ChartConfig\n): ChartConfig {\n const base = previous ?? DEFAULT_CHART_THEME\n return {\n seriesStyles: resolveSeriesStrokes(theme?.seriesStyles, base.seriesStyles),\n xAxis: resolveAxisConfig(theme?.xAxis, base.xAxis),\n yAxis: resolveAxisConfig(theme?.yAxis, base.yAxis),\n plotArea: resolvePlotArea(theme?.plotArea, base.plotArea),\n legend: resolveLegendConfig(theme?.legend, base.legend),\n region: resolveRegionConfig(theme?.region, base.region),\n }\n}\n\n/** {@link LineChart}'s `theme` entry's lerp — leaf by leaf. */\nexport function lerpChartTheme(\n from: ChartConfig,\n to: ChartConfig,\n t: number\n): ChartConfig {\n return {\n seriesStyles: lerpSeriesStrokes(from.seriesStyles, to.seriesStyles, t),\n xAxis: lerpAxisConfig(from.xAxis, to.xAxis, t),\n yAxis: lerpAxisConfig(from.yAxis, to.yAxis, t),\n plotArea: lerpPlotArea(from.plotArea, to.plotArea, t),\n legend: lerpLegendConfig(from.legend, to.legend, t),\n region: lerpRegionConfig(from.region, to.region, t),\n }\n}\n\n// ---- Scales --------------------------------------------------------------\n\n/**\n * Resolve one series' `[x, y]` points out of the chart's rows: each row's\n * position on the {@link XScale} paired with its own `field` column, sorted\n * along x so the polyline traces left-to-right whatever order the rows arrived\n * in.\n *\n * A row missing its y column contributes a 0 rather than a gap — these charts\n * draw continuous lines, so dropping the point would silently bridge the hole.\n *\n * Takes the resolved scale rather than the column *name*, because what a row's x\n * is depends on what kind of column it came out of: a number, an instant, or a\n * place in the file. See `x-scale.ts`.\n */\nexport function seriesPoints(\n data: DataRecord[],\n x: XScale,\n series: LineSeries\n): [x: number, y: number][] {\n return data\n .map((row, i): [number, number] => [x.at(i), num(row, series.field)])\n .sort((a, b) => a[0] - b[0])\n}\n\n/** Min/max across every series' column, the fallback when unset. */\nexport function yExtent(data: DataRecord[], series: LineSeries[]): Range {\n let lo = Infinity\n let hi = -Infinity\n for (const row of data)\n for (const s of series) {\n const v = num(row, s.field)\n if (v < lo) lo = v\n if (v > hi) hi = v\n }\n return lo <= hi ? [lo, hi] : [0, 1]\n}\n\n/**\n * Effective vertical scale: the explicit `range`, else the data's y extent\n * nudged out to tidy step bounds so markers read as round numbers.\n *\n * `divisions` is how many segments the auto-range is stepped into (one fewer\n * than a marker count) — it only shapes the tidy step when `range` is omitted.\n */\nexport function resolveYScale(\n data: DataRecord[],\n series: LineSeries[],\n range: Range | undefined,\n divisions: number\n): Range {\n if (range) return range\n const [lo, hi] = yExtent(data, series)\n const segments = Math.max(1, Math.round(divisions))\n if (hi === lo) return constantScale(lo, segments)\n const step = niceStep((hi - lo) / segments)\n return [Math.floor(lo / step) * step, Math.ceil(hi / step) * step]\n}\n\n/**\n * The scale for data that never moves. A zero-width scale puts every tick on the\n * same value and the line on the plot's floor, so it is widened to a few steps\n * either side of the value (the step sized to its magnitude, so `5` gets\n * `[3, 7]` and `5000` gets `[3000, 7000]`), or to `[0, 1]` for all zeros, as for\n * no data at all.\n */\nfunction constantScale(value: number, segments: number): Range {\n if (value === 0) return [0, 1]\n const step = niceStep(Math.abs(value) / segments)\n return [value - step, value + step]\n}\n"]}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * What a line chart's x column *means* — the one question that has to be
3
+ * answered before a row can be given a position along the bottom of the plot.
4
+ *
5
+ * A y column is always a magnitude. An x column is one of three things, and
6
+ * which one is a property of the data rather than something the author states:
7
+ *
8
+ * - **numeric** — `0, 5, 10` — plot the number.
9
+ * - **time** — `Jan`, `2026-03`, `2026-W12`, `2026-03-14` — plot the instant,
10
+ * so samples a month apart sit a month apart and a gap in the data reads as a
11
+ * gap.
12
+ * - **ordinal** — anything else — plot the row's position, evenly spaced.
13
+ *
14
+ * Before this, every x column went through `num()`, which reads a non-numeric
15
+ * cell as `0`. A `Month` column of `Jan…Dec` therefore stacked all twelve
16
+ * points on one x and drew a vertical line — no error, no empty chart, just a
17
+ * wrong picture. That is the failure this module exists to remove: every column
18
+ * now plots as *something*, and the something is the reading a person would
19
+ * give it.
20
+ *
21
+ * The captions come back off the rows rather than out of a formatter. A tick on
22
+ * a time or ordinal scale is chosen *from the data* (see {@link XScale.ticks}),
23
+ * so the label it wants is the cell the author wrote — `Jan`, not `1970-01-01`,
24
+ * and `2026-W12`, not the Monday it resolves to. Only a tick that is not a
25
+ * sample — an author-set range, or a `zoomTo` mid-flight — needs formatting,
26
+ * and that is the one case {@link formatInstant} covers.
27
+ */
28
+ import type { DataRecord } from "@motionscript/sdk";
29
+ import { type Range } from "../kit/shared.js";
30
+ /** How an x column's cells become positions along the axis. */
31
+ export type XScaleKind = "numeric" | "time" | "ordinal";
32
+ /**
33
+ * An x column resolved into positions, captions and ticks.
34
+ *
35
+ * The three kinds differ in all three answers, so they are three closures built
36
+ * once per (data, column) rather than a kind tag every caller switches on.
37
+ */
38
+ export interface XScale {
39
+ kind: XScaleKind;
40
+ /** Position of row `index` along the axis, in this scale's own units. */
41
+ at(index: number): number;
42
+ /** `[min, max]` across the rows — the scale when no range is set. */
43
+ extent: Range;
44
+ /** The caption for a tick at `value`. */
45
+ label(value: number): string;
46
+ /** `divisions + 1` tick values spanning `range`, ends included. */
47
+ ticks(range: Range, divisions: number): number[];
48
+ }
49
+ /**
50
+ * One cell read as an instant, or `null` when it isn't one.
51
+ *
52
+ * Deliberately **not** `Date.parse`. That accepts `"5"`, `"Q3"` and a good deal
53
+ * else besides, differs between engines on anything non-ISO, and reads a bare
54
+ * `2026-03-14` as UTC but `2026/03/14` as local — three ways for a column to be
55
+ * silently misread as time. Every form below is matched explicitly, and an
56
+ * ambiguous all-numeric date (`03/04/2026` — March or April, depending on where
57
+ * the author lives) is deliberately not one of them.
58
+ */
59
+ export declare function parseInstant(raw: unknown): number | null;
60
+ /**
61
+ * An instant captioned at the coarsest granularity the axis's own span can tell
62
+ * apart — a decade of samples reads as years, a year of them as months.
63
+ *
64
+ * Only reached for a tick that is *not* one of the rows (an author-set range, a
65
+ * `zoomTo` part-way through). A tick that is a sample is captioned with the
66
+ * cell the author wrote; see the note at the top of this file.
67
+ */
68
+ export declare function formatInstant(value: number, span: number): string;
69
+ /**
70
+ * How `field` positions the rows: the number it holds, the instant it names, or
71
+ * failing both, the row's own place in the file.
72
+ *
73
+ * Kept per `data` array: a chart's bound props ask a dozen times a frame, and
74
+ * parsing every row's instant each time was most of a frame.
75
+ */
76
+ export declare function resolveXScale(data: DataRecord[], field: string, pattern?: string): XScale;
77
+ //# sourceMappingURL=x-scale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"x-scale.d.ts","sourceRoot":"","sources":["../../src/line-chart/x-scale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD,OAAO,EAKL,KAAK,KAAK,EACX,MAAM,eAAe,CAAA;AAGtB,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAA;AAEvD;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,UAAU,CAAA;IAChB,yEAAyE;IACzE,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,qEAAqE;IACrE,MAAM,EAAE,KAAK,CAAA;IACb,yCAAyC;IACzC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IAC5B,mEAAmE;IACnE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CACjD;AAmDD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkDxD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQjE;AA0HD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAAE,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAOR"}