@motionscript/charts 0.1.0-alpha.7 → 0.1.0-alpha.9

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 (92) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/LICENSE +201 -201
  3. package/NOTICE +49 -48
  4. package/README.md +99 -99
  5. package/dist/bar-chart/bar-chart.d.ts +2 -2
  6. package/dist/bar-chart/bar-chart.d.ts.map +1 -1
  7. package/dist/bar-chart/bar-chart.js +13 -5
  8. package/dist/bar-chart/bar-chart.js.map +1 -1
  9. package/dist/bar-chart/category-axis.js.map +1 -1
  10. package/dist/bar-chart/chart-bars.d.ts.map +1 -1
  11. package/dist/bar-chart/chart-bars.js +6 -7
  12. package/dist/bar-chart/chart-bars.js.map +1 -1
  13. package/dist/bar-chart/index.js.map +1 -1
  14. package/dist/bar-chart/shared.js.map +1 -1
  15. package/dist/browser/chunks/{chunk-XROBIRQK.js → chunk-TJNHLQGV.js} +1 -1
  16. package/dist/browser/chunks/chunk-TJNHLQGV.js.map +7 -0
  17. package/dist/browser/index.js +1 -1
  18. package/dist/browser/index.js.map +3 -3
  19. package/dist/browser/kit.js +1 -1
  20. package/dist/browser/manifest.json +1 -1
  21. package/dist/engine.d.ts +1 -1
  22. package/dist/engine.js +1 -1
  23. package/dist/engine.js.map +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/kit/cartesian.js.map +1 -1
  26. package/dist/kit/chart-border.js.map +1 -1
  27. package/dist/kit/chart-canvas.js.map +1 -1
  28. package/dist/kit/chart-legend.js.map +1 -1
  29. package/dist/kit/entries.js.map +1 -1
  30. package/dist/kit/motion.js.map +1 -1
  31. package/dist/kit/paints.js.map +1 -1
  32. package/dist/kit/shared.js.map +1 -1
  33. package/dist/kit/vertical-axis.js.map +1 -1
  34. package/dist/line-chart/chart-line.js.map +1 -1
  35. package/dist/line-chart/chart-region.js.map +1 -1
  36. package/dist/line-chart/horizontal-axis.js.map +1 -1
  37. package/dist/line-chart/index.d.ts +1 -1
  38. package/dist/line-chart/index.js +1 -1
  39. package/dist/line-chart/index.js.map +1 -1
  40. package/dist/line-chart/line-chart.d.ts +2 -2
  41. package/dist/line-chart/line-chart.d.ts.map +1 -1
  42. package/dist/line-chart/line-chart.js +16 -7
  43. package/dist/line-chart/line-chart.js.map +1 -1
  44. package/dist/line-chart/shared.js.map +1 -1
  45. package/dist/line-chart/x-scale.d.ts +3 -10
  46. package/dist/line-chart/x-scale.d.ts.map +1 -1
  47. package/dist/line-chart/x-scale.js +15 -4
  48. package/dist/line-chart/x-scale.js.map +1 -1
  49. package/dist/nodes.js.map +1 -1
  50. package/dist/pie-chart/index.js.map +1 -1
  51. package/dist/pie-chart/pie-chart.d.ts +2 -2
  52. package/dist/pie-chart/pie-chart.d.ts.map +1 -1
  53. package/dist/pie-chart/pie-chart.js +4 -1
  54. package/dist/pie-chart/pie-chart.js.map +1 -1
  55. package/dist/pie-chart/pie-legend.js.map +1 -1
  56. package/dist/pie-chart/pie-ring.js.map +1 -1
  57. package/dist/pie-chart/shared.d.ts +6 -0
  58. package/dist/pie-chart/shared.d.ts.map +1 -1
  59. package/dist/pie-chart/shared.js +8 -2
  60. package/dist/pie-chart/shared.js.map +1 -1
  61. package/package.json +5 -4
  62. package/registry.json +23 -23
  63. package/src/bar-chart/bar-chart.ts +542 -534
  64. package/src/bar-chart/category-axis.ts +162 -162
  65. package/src/bar-chart/chart-bars.ts +287 -288
  66. package/src/bar-chart/index.ts +31 -31
  67. package/src/bar-chart/shared.ts +354 -354
  68. package/src/engine.ts +28 -28
  69. package/src/index.ts +5 -5
  70. package/src/kit/cartesian.ts +81 -81
  71. package/src/kit/chart-border.ts +50 -50
  72. package/src/kit/chart-canvas.ts +122 -122
  73. package/src/kit/chart-legend.ts +229 -229
  74. package/src/kit/entries.ts +20 -20
  75. package/src/kit/motion.ts +90 -90
  76. package/src/kit/paints.ts +15 -15
  77. package/src/kit/shared.ts +782 -782
  78. package/src/kit/vertical-axis.ts +194 -194
  79. package/src/line-chart/chart-line.ts +156 -156
  80. package/src/line-chart/chart-region.ts +90 -90
  81. package/src/line-chart/horizontal-axis.ts +151 -151
  82. package/src/line-chart/index.ts +39 -39
  83. package/src/line-chart/line-chart.ts +598 -585
  84. package/src/line-chart/shared.ts +473 -473
  85. package/src/line-chart/x-scale.ts +382 -367
  86. package/src/nodes.ts +18 -18
  87. package/src/pie-chart/index.ts +37 -37
  88. package/src/pie-chart/pie-chart.ts +296 -293
  89. package/src/pie-chart/pie-legend.ts +169 -169
  90. package/src/pie-chart/pie-ring.ts +848 -848
  91. package/src/pie-chart/shared.ts +496 -490
  92. package/dist/browser/chunks/chunk-XROBIRQK.js.map +0 -7
@@ -1 +1 @@
1
- {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/line-chart/shared.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Types and pure helpers specific to the {@link LineChart} family: the traced\r\n * polylines, the spotlit region they dim outside of, and the theme those two\r\n * add to the shared chart vocabulary.\r\n *\r\n * Everything a bar or a pie chart would want too — what a series is, how an axis\r\n * is scaled and formatted, the theme leaf resolvers, the legend and plot-panel\r\n * branches — lives in `../kit/shared.ts` and is imported here. What's\r\n * left below is what only a line chart has: points sorted along an x column,\r\n * a region clipped out of a polyline, and the `region` branch of the theme.\r\n *\r\n * Kept renderer-agnostic (no `Node` imports) so every part shares one\r\n * scale/format/palette/theme vocabulary, and so a host's own inspector\r\n * mapping can build a partial {@link ChartTheme} without\r\n * pulling the components in.\r\n */\r\n\r\nimport {\r\n Fills,\r\n fillOps,\r\n lerpNumber,\r\n strokeOps,\r\n type DataRecord,\r\n type Fill,\r\n type FillResolved,\r\n type Stroke,\r\n type StrokeResolved,\r\n} from \"@motionscript/sdk\"\r\n\r\nimport {\r\n CHART_PALETTE,\r\n DEFAULT_LEGEND,\r\n DEFAULT_PLOT_AREA,\r\n DEFAULT_SERIES_STROKES,\r\n DEFAULT_X_AXIS,\r\n DEFAULT_Y_AXIS,\r\n SPOTLIGHT_DIM,\r\n lerpAxisConfig,\r\n lerpLegendConfig,\r\n lerpPlotArea,\r\n lerpSeriesStrokes,\r\n niceStep,\r\n num,\r\n resolveAxisConfig,\r\n resolveLegendConfig,\r\n resolvePlotArea,\r\n resolveSeriesStrokes,\r\n type AxisConfig,\r\n type ChartSeries,\r\n type LegendConfig,\r\n type LegendTheme,\r\n type PlotAreaConfig,\r\n type Range,\r\n} from \"../kit/shared\"\r\nimport type { XScale } from \"./x-scale\"\r\n\r\n/**\r\n * One series in a {@link LineChart}: a polyline traced through the values in one\r\n * column of the chart's `data`, drawn in `fill` (or the theme's `seriesStyles`\r\n * palette when omitted) and captioned by `label` in the legend.\r\n *\r\n * The family-wide {@link ChartSeries} under a name that says what it draws here;\r\n * the chart's own `xField` prop names the column carrying the shared x values,\r\n * so every series is read off the same rows — one row per sample, one column per\r\n * series.\r\n */\r\nexport type LineSeries = ChartSeries\r\n\r\n// ---- Plot regions -------------------------------------------------------\r\n\r\n/**\r\n * A rectangular slice of the plot, in **data units** — the author-facing input\r\n * to {@link LineChart.spotlightRegion} / {@link LineChart.zoomTo}.\r\n *\r\n * Either axis may be omitted, and that's the point: `{ x: [60, 100] }` is a\r\n * vertical band spanning the full height, `{ y: [8, 10] }` a horizontal band\r\n * spanning the full width, and both together a box. A reversed range is read\r\n * the same as the forward one — a region is a span, not a direction.\r\n */\r\nexport interface PlotRegion {\r\n /** Horizontal span in x-axis data units. Omit to span the full plot width. */\r\n x?: Range\r\n /** Vertical span in y-axis data units. Omit to span the full plot height. */\r\n y?: Range\r\n}\r\n\r\n/**\r\n * A {@link PlotRegion} resolved into the plot's **centred pixel space** (origin\r\n * at the plot centre, y-up) — the same local frame the series points live in,\r\n * so a line can be clipped against it directly.\r\n *\r\n * `xBounded`/`yBounded` carry which axes the author actually constrained: an\r\n * unbounded axis has its edges pinned to the plot's own and draws no rule.\r\n */\r\nexport interface RegionBox {\r\n left: number\r\n right: number\r\n bottom: number\r\n top: number\r\n /** The author gave an x range — the left/right edges are real data bounds. */\r\n xBounded: boolean\r\n /** The author gave a y range — the bottom/top edges are real data bounds. */\r\n yBounded: boolean\r\n}\r\n\r\n/** The empty box, used as the \"no region\" resting value. */\r\nexport const EMPTY_REGION_BOX: RegionBox = {\r\n left: 0,\r\n right: 0,\r\n bottom: 0,\r\n top: 0,\r\n xBounded: false,\r\n yBounded: false,\r\n}\r\n\r\n/** Is `box` a real, non-degenerate region — i.e. is there anything to spotlight? */\r\nexport function hasRegion(box: RegionBox): boolean {\r\n return box.right > box.left && box.top > box.bottom\r\n}\r\n\r\n/**\r\n * Resolve a {@link PlotRegion} against the plot's pixel box and the chart's\r\n * scales. `plot` is the plot rectangle in centred pixel space, and\r\n * `xScale`/`yScale` the data ranges those edges span.\r\n *\r\n * An omitted axis takes the plot's own edges, so the region spans it. A given\r\n * axis is clamped to the plot, so a range running off the scale still produces\r\n * a box inside the panel.\r\n */\r\nexport function resolveRegionBox(\r\n region: PlotRegion | null | undefined,\r\n plot: { left: number; right: number; bottom: number; top: number },\r\n xScale: Range,\r\n yScale: Range\r\n): RegionBox {\r\n if (!region || (region.x === undefined && region.y === undefined)) {\r\n return EMPTY_REGION_BOX\r\n }\r\n\r\n const span = (\r\n range: Range | undefined,\r\n [min, max]: Range,\r\n lo: number,\r\n hi: number\r\n ): [number, number, boolean] => {\r\n if (range === undefined || max <= min) return [lo, hi, false]\r\n const a = Math.min(range[0], range[1])\r\n const b = Math.max(range[0], range[1])\r\n const toPx = (v: number) => lo + ((v - min) / (max - min)) * (hi - lo)\r\n return [\r\n Math.max(lo, Math.min(hi, toPx(a))),\r\n Math.max(lo, Math.min(hi, toPx(b))),\r\n true,\r\n ]\r\n }\r\n\r\n const [left, right, xBounded] = span(region.x, xScale, plot.left, plot.right)\r\n const [bottom, top, yBounded] = span(region.y, yScale, plot.bottom, plot.top)\r\n return { left, right, bottom, top, xBounded, yBounded }\r\n}\r\n\r\n/** A bare `{x, y}` — declared locally so this module stays free of node imports. */\r\ninterface Vector2Like {\r\n x: number\r\n y: number\r\n}\r\n\r\n/**\r\n * The parts of `points` that fall **inside** `box`, as a list of contiguous\r\n * sub-polylines — the geometry behind the region spotlight: a series is drawn\r\n * dimmed in full, then these runs are re-drawn over it at full strength.\r\n *\r\n * Segments are clipped against the box rather than filtered by endpoint, so a\r\n * line entering the region mid-segment is cut exactly on the boundary.\r\n */\r\nexport function clipPolylineToBox(\r\n points: Vector2Like[],\r\n box: RegionBox\r\n): Vector2Like[][] {\r\n if (points.length < 2 || !hasRegion(box)) return []\r\n\r\n const runs: Vector2Like[][] = []\r\n let run: Vector2Like[] = []\r\n\r\n for (let i = 1; i < points.length; i++) {\r\n const seg = clipSegment(points[i - 1], points[i], box)\r\n if (!seg) {\r\n // Segment misses the box entirely — close whatever run was open.\r\n if (run.length >= 2) runs.push(run)\r\n run = []\r\n continue\r\n }\r\n const [a, b] = seg\r\n // Continue the open run when this clipped segment starts where the last one\r\n // ended; otherwise the line left the box and came back, which is a new run.\r\n if (run.length > 0 && samePoint(run[run.length - 1], a)) run.push(b)\r\n else {\r\n if (run.length >= 2) runs.push(run)\r\n run = [a, b]\r\n }\r\n }\r\n if (run.length >= 2) runs.push(run)\r\n return runs\r\n}\r\n\r\n/** Points within half a pixel are the same point, for run-joining purposes. */\r\nfunction samePoint(a: Vector2Like, b: Vector2Like): boolean {\r\n return Math.abs(a.x - b.x) < 0.5 && Math.abs(a.y - b.y) < 0.5\r\n}\r\n\r\n/**\r\n * Liang–Barsky clip of one segment against the axis-aligned `box`. Returns the\r\n * surviving `[start, end]`, or `null` when the segment lies wholly outside.\r\n */\r\nfunction clipSegment(\r\n a: Vector2Like,\r\n b: Vector2Like,\r\n box: RegionBox\r\n): [Vector2Like, Vector2Like] | null {\r\n const dx = b.x - a.x\r\n const dy = b.y - a.y\r\n let t0 = 0\r\n let t1 = 1\r\n\r\n // Each edge as (p, q): the segment survives where p·t <= q.\r\n const edges: [number, number][] = [\r\n [-dx, a.x - box.left],\r\n [dx, box.right - a.x],\r\n [-dy, a.y - box.bottom],\r\n [dy, box.top - a.y],\r\n ]\r\n for (const [p, q] of edges) {\r\n if (p === 0) {\r\n // Parallel to this edge: outside it means the whole segment is out.\r\n if (q < 0) return null\r\n continue\r\n }\r\n const r = q / p\r\n if (p < 0) {\r\n if (r > t1) return null\r\n if (r > t0) t0 = r\r\n } else {\r\n if (r < t0) return null\r\n if (r < t1) t1 = r\r\n }\r\n }\r\n return [\r\n { x: a.x + t0 * dx, y: a.y + t0 * dy },\r\n { x: a.x + t1 * dx, y: a.y + t1 * dy },\r\n ]\r\n}\r\n\r\n// ---- Theme ---------------------------------------------------------------\r\n\r\n/**\r\n * How a spotlit {@link PlotRegion} is drawn — the dashed rules on its bounded\r\n * edges, the wash filling it, and how far the series fade outside it.\r\n */\r\nexport interface RegionConfig {\r\n /** The dashed rule marking each bounded edge. */\r\n stroke: Stroke\r\n /** Wash filling the region, drawn under the series lines. */\r\n fill: Fill\r\n /** Opacity the stretch of every series *outside* the region fades to. */\r\n dimOpacity: number\r\n}\r\n\r\n/** A {@link LineChart}'s full, resolved visual theme. */\r\nexport interface ChartConfig {\r\n /**\r\n * Default per-series line stroke, cycled by series index (`i % length`). A\r\n * series' own `fill`, when set, overrides just the colour.\r\n */\r\n seriesStyles: Stroke[]\r\n xAxis: AxisConfig\r\n yAxis: AxisConfig\r\n /** The plot panel: border, fill, and drop shadow. */\r\n plotArea: PlotAreaConfig\r\n legend: LegendConfig\r\n /** The spotlit-region annotation — see {@link RegionConfig}. */\r\n region: RegionConfig\r\n}\r\n\r\n/**\r\n * Author-facing partial theme, passed as {@link LineChart}'s `theme` prop —\r\n * every branch optional and deep-merged onto {@link DEFAULT_CHART_THEME}.\r\n */\r\nexport type ChartTheme = {\r\n seriesStyles?: Stroke[]\r\n xAxis?: Partial<AxisConfig>\r\n yAxis?: Partial<AxisConfig>\r\n plotArea?: Partial<PlotAreaConfig>\r\n legend?: LegendTheme\r\n region?: Partial<RegionConfig>\r\n}\r\n\r\n/**\r\n * The legend of a line chart: the shared one, with a small rounded-square\r\n * swatch in place of the wide chip. A bar's or a wedge's swatch stands in for\r\n * an area of paint and is drawn like one; a line's stands in for a colour, and\r\n * a square reads as the sample it is.\r\n */\r\nconst LINE_CHART_LEGEND: LegendConfig = {\r\n ...DEFAULT_LEGEND,\r\n markerStyle: {\r\n ...DEFAULT_LEGEND.markerStyle,\r\n width: 20,\r\n height: 20,\r\n borderRadius: 6,\r\n },\r\n}\r\n\r\n/** The chart's out-of-the-box look. */\r\nexport const DEFAULT_CHART_THEME: ChartConfig = {\r\n seriesStyles: DEFAULT_SERIES_STROKES,\r\n xAxis: DEFAULT_X_AXIS,\r\n yAxis: DEFAULT_Y_AXIS,\r\n plotArea: DEFAULT_PLOT_AREA,\r\n legend: LINE_CHART_LEGEND,\r\n region: {\r\n // Brighter than the grid and dashed, so the boundary reads as a call-out\r\n // rather than another axis line; `butt` caps keep the dash lengths exact.\r\n stroke: {\r\n weight: 3,\r\n fill: CHART_PALETTE.regionColor,\r\n dash: [14, 12],\r\n cap: \"butt\",\r\n },\r\n // A wash barely above the panel — enough to tint the band, not enough to\r\n // mute the series drawn over it.\r\n fill: Fills.color(CHART_PALETTE.regionColor, { opacity: 0.08 }),\r\n dimOpacity: SPOTLIGHT_DIM,\r\n },\r\n}\r\n\r\nfunction resolveRegionConfig(\r\n region: Partial<RegionConfig> | undefined,\r\n previous: RegionConfig\r\n): RegionConfig {\r\n return {\r\n stroke: strokeOps.resolve(region?.stroke ?? previous.stroke),\r\n fill: fillOps.resolve(region?.fill ?? previous.fill),\r\n dimOpacity: region?.dimOpacity ?? previous.dimOpacity,\r\n }\r\n}\r\n\r\nfunction lerpRegionConfig(\r\n from: RegionConfig,\r\n to: RegionConfig,\r\n t: number\r\n): RegionConfig {\r\n return {\r\n stroke: strokeOps.lerp(\r\n from.stroke as StrokeResolved[],\r\n to.stroke as StrokeResolved[],\r\n t\r\n ),\r\n fill: fillOps.lerp(\r\n from.fill as FillResolved[],\r\n to.fill as FillResolved[],\r\n t\r\n ),\r\n dimOpacity: lerpNumber(from.dimOpacity, to.dimOpacity, t),\r\n }\r\n}\r\n\r\n/**\r\n * {@link LineChart}'s `theme` entry's resolve: merges the author's partial\r\n * {@link ChartTheme} onto `previous` (falling back to\r\n * {@link DEFAULT_CHART_THEME} on first resolution), fully resolving every leaf.\r\n */\r\nexport function resolveChartTheme(\r\n theme: ChartTheme | undefined,\r\n previous?: ChartConfig\r\n): ChartConfig {\r\n const base = previous ?? DEFAULT_CHART_THEME\r\n return {\r\n seriesStyles: resolveSeriesStrokes(theme?.seriesStyles, base.seriesStyles),\r\n xAxis: resolveAxisConfig(theme?.xAxis, base.xAxis),\r\n yAxis: resolveAxisConfig(theme?.yAxis, base.yAxis),\r\n plotArea: resolvePlotArea(theme?.plotArea, base.plotArea),\r\n legend: resolveLegendConfig(theme?.legend, base.legend),\r\n region: resolveRegionConfig(theme?.region, base.region),\r\n }\r\n}\r\n\r\n/** {@link LineChart}'s `theme` entry's lerp — leaf by leaf. */\r\nexport function lerpChartTheme(\r\n from: ChartConfig,\r\n to: ChartConfig,\r\n t: number\r\n): ChartConfig {\r\n return {\r\n seriesStyles: lerpSeriesStrokes(from.seriesStyles, to.seriesStyles, t),\r\n xAxis: lerpAxisConfig(from.xAxis, to.xAxis, t),\r\n yAxis: lerpAxisConfig(from.yAxis, to.yAxis, t),\r\n plotArea: lerpPlotArea(from.plotArea, to.plotArea, t),\r\n legend: lerpLegendConfig(from.legend, to.legend, t),\r\n region: lerpRegionConfig(from.region, to.region, t),\r\n }\r\n}\r\n\r\n// ---- Scales --------------------------------------------------------------\r\n\r\n/**\r\n * Resolve one series' `[x, y]` points out of the chart's rows: each row's\r\n * position on the {@link XScale} paired with its own `field` column, sorted\r\n * along x so the polyline traces left-to-right whatever order the rows arrived\r\n * in.\r\n *\r\n * A row missing its y column contributes a 0 rather than a gap — these charts\r\n * draw continuous lines, so dropping the point would silently bridge the hole.\r\n *\r\n * Takes the resolved scale rather than the column *name*, because what a row's x\r\n * is depends on what kind of column it came out of: a number, an instant, or a\r\n * place in the file. See `x-scale.ts`.\r\n */\r\nexport function seriesPoints(\r\n data: DataRecord[],\r\n x: XScale,\r\n series: LineSeries\r\n): [x: number, y: number][] {\r\n return data\r\n .map((row, i): [number, number] => [x.at(i), num(row, series.field)])\r\n .sort((a, b) => a[0] - b[0])\r\n}\r\n\r\n/** Min/max across every series' column, the fallback when unset. */\r\nexport function yExtent(data: DataRecord[], series: LineSeries[]): Range {\r\n let lo = Infinity\r\n let hi = -Infinity\r\n for (const row of data)\r\n for (const s of series) {\r\n const v = num(row, s.field)\r\n if (v < lo) lo = v\r\n if (v > hi) hi = v\r\n }\r\n return lo <= hi ? [lo, hi] : [0, 1]\r\n}\r\n\r\n/**\r\n * Effective vertical scale: the explicit `range`, else the data's y extent\r\n * nudged out to tidy step bounds so markers read as round numbers.\r\n *\r\n * `divisions` is how many segments the auto-range is stepped into (one fewer\r\n * than a marker count) — it only shapes the tidy step when `range` is omitted.\r\n */\r\nexport function resolveYScale(\r\n data: DataRecord[],\r\n series: LineSeries[],\r\n range: Range | undefined,\r\n divisions: number\r\n): Range {\r\n if (range) return range\r\n const [lo, hi] = yExtent(data, series)\r\n const segments = Math.max(1, Math.round(divisions))\r\n if (hi === lo) return constantScale(lo, segments)\r\n const step = niceStep((hi - lo) / segments)\r\n return [Math.floor(lo / step) * step, Math.ceil(hi / step) * step]\r\n}\r\n\r\n/**\r\n * The scale for data that never moves. A zero-width scale puts every tick on the\r\n * same value and the line on the plot's floor, so it is widened to a few steps\r\n * either side of the value (the step sized to its magnitude, so `5` gets\r\n * `[3, 7]` and `5000` gets `[3000, 7000]`), or to `[0, 1]` for all zeros, as for\r\n * no data at all.\r\n */\r\nfunction constantScale(value: number, segments: number): Range {\r\n if (value === 0) return [0, 1]\r\n const step = niceStep(Math.abs(value) / segments)\r\n return [value - step, value + step]\r\n}\r\n"]}
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"]}
@@ -70,15 +70,8 @@ export declare function formatInstant(value: number, span: number): string;
70
70
  * How `field` positions the rows: the number it holds, the instant it names, or
71
71
  * failing both, the row's own place in the file.
72
72
  *
73
- * Resolved from the data on every read rather than cached on the node: it is
74
- * cheap (one pass to classify, one to position), and a cache keyed on a `data`
75
- * array that a rebuild replaces on every edit would miss every time anyway.
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.
76
75
  */
77
- export declare function resolveXScale(data: DataRecord[], field: string,
78
- /**
79
- * The number-format pattern for a *numeric* column. Ignored by the other two
80
- * kinds, and correctly so: a tick on those is captioned with the cell the
81
- * author wrote, and `Jan` has no decimal places.
82
- */
83
- pattern?: string): XScale;
76
+ export declare function resolveXScale(data: DataRecord[], field: string, pattern?: string): XScale;
84
77
  //# sourceMappingURL=x-scale.d.ts.map
@@ -1 +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;AAuHD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAAE,EAClB,KAAK,EAAE,MAAM;AACb;;;;GAIG;AACH,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CA2CR"}
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"}
@@ -248,15 +248,26 @@ function labelBySample(positions, captions, value, fallback) {
248
248
  const i = positions.indexOf(value);
249
249
  return i >= 0 ? captions[i] : fallback(value);
250
250
  }
251
+ /** Scales already resolved, by data array, then by field and pattern. */
252
+ const scales = new WeakMap();
251
253
  /**
252
254
  * How `field` positions the rows: the number it holds, the instant it names, or
253
255
  * failing both, the row's own place in the file.
254
256
  *
255
- * Resolved from the data on every read rather than cached on the node: it is
256
- * cheap (one pass to classify, one to position), and a cache keyed on a `data`
257
- * array that a rebuild replaces on every edit would miss every time anyway.
257
+ * Kept per `data` array: a chart's bound props ask a dozen times a frame, and
258
+ * parsing every row's instant each time was most of a frame.
258
259
  */
259
- export function resolveXScale(data, field,
260
+ export function resolveXScale(data, field, pattern) {
261
+ let byKey = scales.get(data);
262
+ if (!byKey)
263
+ scales.set(data, (byKey = new Map()));
264
+ const key = JSON.stringify([field, pattern ?? null]);
265
+ let scale = byKey.get(key);
266
+ if (!scale)
267
+ byKey.set(key, (scale = xScaleOf(data, field, pattern)));
268
+ return scale;
269
+ }
270
+ function xScaleOf(data, field,
260
271
  /**
261
272
  * The number-format pattern for a *numeric* column. Ignored by the other two
262
273
  * kinds, and correctly so: a tick on those is captioned with the cell the
@@ -1 +1 @@
1
- {"version":3,"file":"x-scale.js","sourceRoot":"","sources":["../../src/line-chart/x-scale.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * What a line chart's x column *means* — the one question that has to be\r\n * answered before a row can be given a position along the bottom of the plot.\r\n *\r\n * A y column is always a magnitude. An x column is one of three things, and\r\n * which one is a property of the data rather than something the author states:\r\n *\r\n * - **numeric** — `0, 5, 10` — plot the number.\r\n * - **time** — `Jan`, `2026-03`, `2026-W12`, `2026-03-14` — plot the instant,\r\n * so samples a month apart sit a month apart and a gap in the data reads as a\r\n * gap.\r\n * - **ordinal** — anything else — plot the row's position, evenly spaced.\r\n *\r\n * Before this, every x column went through `num()`, which reads a non-numeric\r\n * cell as `0`. A `Month` column of `Jan…Dec` therefore stacked all twelve\r\n * points on one x and drew a vertical line — no error, no empty chart, just a\r\n * wrong picture. That is the failure this module exists to remove: every column\r\n * now plots as *something*, and the something is the reading a person would\r\n * give it.\r\n *\r\n * The captions come back off the rows rather than out of a formatter. A tick on\r\n * a time or ordinal scale is chosen *from the data* (see {@link XScale.ticks}),\r\n * so the label it wants is the cell the author wrote — `Jan`, not `1970-01-01`,\r\n * and `2026-W12`, not the Monday it resolves to. Only a tick that is not a\r\n * sample — an author-set range, or a `zoomTo` mid-flight — needs formatting,\r\n * and that is the one case {@link formatInstant} covers.\r\n */\r\n\r\nimport type { DataRecord } from \"@motionscript/sdk\"\r\n\r\nimport {\r\n formatMarker,\r\n markerValues,\r\n num,\r\n text,\r\n type Range,\r\n} from \"../kit/shared\"\r\nimport { formatNumber } from \"@motionscript/sdk/component\"\r\n\r\n/** How an x column's cells become positions along the axis. */\r\nexport type XScaleKind = \"numeric\" | \"time\" | \"ordinal\"\r\n\r\n/**\r\n * An x column resolved into positions, captions and ticks.\r\n *\r\n * The three kinds differ in all three answers, so they are three closures built\r\n * once per (data, column) rather than a kind tag every caller switches on.\r\n */\r\nexport interface XScale {\r\n kind: XScaleKind\r\n /** Position of row `index` along the axis, in this scale's own units. */\r\n at(index: number): number\r\n /** `[min, max]` across the rows — the scale when no range is set. */\r\n extent: Range\r\n /** The caption for a tick at `value`. */\r\n label(value: number): string\r\n /** `divisions + 1` tick values spanning `range`, ends included. */\r\n ticks(range: Range, divisions: number): number[]\r\n}\r\n\r\n// ---- Time parsing --------------------------------------------------------\r\n\r\nconst DAY = 86_400_000\r\n\r\nconst MONTHS = [\r\n \"january\",\r\n \"february\",\r\n \"march\",\r\n \"april\",\r\n \"may\",\r\n \"june\",\r\n \"july\",\r\n \"august\",\r\n \"september\",\r\n \"october\",\r\n \"november\",\r\n \"december\",\r\n]\r\n\r\n/**\r\n * The year a cell that names no year is dated into.\r\n *\r\n * Any year would place `Jan…Dec` in the right order at the right spacing; a\r\n * leap year is the one that also spaces February correctly against a column\r\n * that *does* carry years, so the two read alike.\r\n */\r\nconst NOMINAL_YEAR = 2024\r\n\r\n/**\r\n * Month index for any unambiguous abbreviation of a month name — `jan`, `sept`,\r\n * `january` — or -1.\r\n *\r\n * Three letters minimum, and then a prefix match, which is what lets `sept`\r\n * through without letting `janitor` through: a longer word has to still *be*\r\n * the month.\r\n */\r\nfunction monthIndex(word: string): number {\r\n if (word.length < 3) return -1\r\n return MONTHS.findIndex((month) => month.startsWith(word))\r\n}\r\n\r\n/** The Monday of ISO week `week` in `year`, as an epoch timestamp. */\r\nfunction isoWeekStart(year: number, week: number): number {\r\n // ISO week 1 is the week containing 4 January, and weeks start on Monday.\r\n const jan4 = Date.UTC(year, 0, 4)\r\n const weekday = (new Date(jan4).getUTCDay() + 6) % 7 // Monday = 0\r\n return jan4 - weekday * DAY + (week - 1) * 7 * DAY\r\n}\r\n\r\n/**\r\n * One cell read as an instant, or `null` when it isn't one.\r\n *\r\n * Deliberately **not** `Date.parse`. That accepts `\"5\"`, `\"Q3\"` and a good deal\r\n * else besides, differs between engines on anything non-ISO, and reads a bare\r\n * `2026-03-14` as UTC but `2026/03/14` as local — three ways for a column to be\r\n * silently misread as time. Every form below is matched explicitly, and an\r\n * ambiguous all-numeric date (`03/04/2026` — March or April, depending on where\r\n * the author lives) is deliberately not one of them.\r\n */\r\nexport function parseInstant(raw: unknown): number | null {\r\n if (typeof raw !== \"string\") return null\r\n const s = raw.trim().toLowerCase()\r\n if (s === \"\") return null\r\n\r\n // 2026-03-14 · 2026/03/14 · 2026-03 · 2026/03\r\n const ymd = /^(\\d{4})[-/](\\d{1,2})(?:[-/](\\d{1,2}))?$/.exec(s)\r\n if (ymd) {\r\n const month = Number(ymd[2])\r\n const day = ymd[3] === undefined ? 1 : Number(ymd[3])\r\n if (month < 1 || month > 12 || day < 1 || day > 31) return null\r\n return Date.UTC(Number(ymd[1]), month - 1, day)\r\n }\r\n\r\n // 2026-W12 · 2026W12 · w12 · week 12\r\n const week = /^(?:(\\d{4})[-\\s]?)?w(?:ee)?k?[-\\s]?(\\d{1,2})$/.exec(s)\r\n if (week) {\r\n const n = Number(week[2])\r\n if (n < 1 || n > 53) return null\r\n return isoWeekStart(\r\n week[1] === undefined ? NOMINAL_YEAR : Number(week[1]),\r\n n\r\n )\r\n }\r\n\r\n // jan · january · jan 2026 · january-2026 · 2026 jan\r\n const named = /^(?:(\\d{4})[-\\s]+)?([a-z]+)(?:[-\\s]+(\\d{4}))?$/.exec(s)\r\n if (named) {\r\n const month = monthIndex(named[2])\r\n if (month >= 0) {\r\n const year = named[1] ?? named[3]\r\n const y = year === undefined ? NOMINAL_YEAR : Number(year)\r\n return Date.UTC(y, month, 1)\r\n }\r\n }\r\n\r\n // 14 mar 2026 · mar 14 2026 · mar 14, 2026\r\n const dayNamed =\r\n /^(?:(\\d{1,2})[-\\s]+([a-z]+)|([a-z]+)[-\\s]+(\\d{1,2}),?)[-\\s]+(\\d{4})$/.exec(\r\n s\r\n )\r\n if (dayNamed) {\r\n const month = monthIndex(dayNamed[2] ?? dayNamed[3])\r\n const day = Number(dayNamed[1] ?? dayNamed[4])\r\n if (month >= 0 && day >= 1 && day <= 31) {\r\n return Date.UTC(Number(dayNamed[5]), month, day)\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\n/**\r\n * An instant captioned at the coarsest granularity the axis's own span can tell\r\n * apart — a decade of samples reads as years, a year of them as months.\r\n *\r\n * Only reached for a tick that is *not* one of the rows (an author-set range, a\r\n * `zoomTo` part-way through). A tick that is a sample is captioned with the\r\n * cell the author wrote; see the note at the top of this file.\r\n */\r\nexport function formatInstant(value: number, span: number): string {\r\n const date = new Date(value)\r\n const year = date.getUTCFullYear()\r\n const month = MONTHS[date.getUTCMonth()].slice(0, 3)\r\n const label = month.charAt(0).toUpperCase() + month.slice(1)\r\n if (span > 4 * 365 * DAY) return String(year)\r\n if (span > 60 * DAY) return `${label} ${year}`\r\n return `${label} ${date.getUTCDate()}`\r\n}\r\n\r\n// ---- Column reading ------------------------------------------------------\r\n\r\n/**\r\n * Whether every non-empty cell of `field` holds a number.\r\n *\r\n * The same rule `numericFields` applies, asked of one column: a single text\r\n * cell in an otherwise numeric column takes the whole column off the numeric\r\n * scale, because plotting that row at 0 is the failure this module is here to\r\n * stop.\r\n */\r\nfunction isNumericColumn(data: DataRecord[], field: string): boolean {\r\n let sawValue = false\r\n for (const row of data) {\r\n const v = row[field]\r\n if (v === undefined || v === null || v === \"\") continue\r\n if (typeof v !== \"number\") return false\r\n sawValue = true\r\n }\r\n return sawValue\r\n}\r\n\r\n/**\r\n * Every non-empty cell of `field` parsed as an instant, or `null` if any one of\r\n * them isn't one — in which case the column isn't a time column at all.\r\n *\r\n * A blank cell keeps its slot, so the returned list stays index-aligned with\r\n * the rows, and takes the nearest known instant: a hole in an x column is a row\r\n * whose date the file didn't say, and stacking it on the epoch would drag the\r\n * whole scale back fifty years to make room for it.\r\n */\r\nfunction instantColumn(data: DataRecord[], field: string): number[] | null {\r\n const parsed: (number | null)[] = []\r\n let sawValue = false\r\n for (const row of data) {\r\n const raw = row[field]\r\n if (raw === undefined || raw === null || raw === \"\") {\r\n parsed.push(null)\r\n continue\r\n }\r\n const t = parseInstant(raw)\r\n if (t === null) return null\r\n parsed.push(t)\r\n sawValue = true\r\n }\r\n if (!sawValue) return null\r\n\r\n const first = parsed.find((t): t is number => t !== null) as number\r\n let last = first\r\n return parsed.map((t) => (t === null ? last : (last = t)))\r\n}\r\n\r\n/** `[min, max]` of `values`, or `[0, 1]` when there is nothing to span. */\r\nfunction extentOf(values: number[]): Range {\r\n let lo = Infinity\r\n let hi = -Infinity\r\n for (const v of values) {\r\n if (v < lo) lo = v\r\n if (v > hi) hi = v\r\n }\r\n return lo <= hi ? [lo, hi] : [0, 1]\r\n}\r\n\r\n/**\r\n * Ticks spanning `range`, each one **a sample** — a row the author can point at\r\n * in their file — placed as near as it can be to where an evenly spaced tick\r\n * would have gone.\r\n *\r\n * Two things have to hold at once, and picking either alone gets it wrong.\r\n * Ticks must be *samples*, because a caption between two of them has nothing to\r\n * say (two-thirds of the way through March is not a month, and on a category\r\n * column it is not anything at all). And they must be *spread*, because the\r\n * labels sit side by side in a row: taking every nth sample instead puts five\r\n * dates in the two weeks a burst of rows happens to cover and prints them on\r\n * top of each other, which is what a column with a gap in it does.\r\n *\r\n * Walking the targets rather than the samples is what satisfies both. A run of\r\n * rows too close together to caption collapses to one tick — consecutive\r\n * targets pick the same sample and the duplicate is dropped — so a chart asked\r\n * for five ticks across a gappy column draws four honest ones instead of five\r\n * overlapping.\r\n */\r\nfunction sampledTicks(\r\n positions: number[],\r\n [min, max]: Range,\r\n divisions: number\r\n): number[] {\r\n const inside = positions.filter((v) => v >= min && v <= max)\r\n const unique = [...new Set(inside)].sort((a, b) => a - b)\r\n if (unique.length === 0) return markerValues([min, max], divisions)\r\n\r\n const nearest = (target: number) =>\r\n unique.reduce((best, v) =>\r\n Math.abs(v - target) < Math.abs(best - target) ? v : best\r\n )\r\n\r\n const out: number[] = []\r\n for (const target of markerValues([min, max], divisions)) {\r\n const tick = nearest(target)\r\n if (out[out.length - 1] !== tick) out.push(tick)\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * The caption a tick carries on a sample-based scale: the cell of the row it\r\n * came from, or `fallback` when the tick isn't a sample after all.\r\n */\r\nfunction labelBySample(\r\n positions: number[],\r\n captions: string[],\r\n value: number,\r\n fallback: (value: number) => string\r\n): string {\r\n const i = positions.indexOf(value)\r\n return i >= 0 ? captions[i] : fallback(value)\r\n}\r\n\r\n/**\r\n * How `field` positions the rows: the number it holds, the instant it names, or\r\n * failing both, the row's own place in the file.\r\n *\r\n * Resolved from the data on every read rather than cached on the node: it is\r\n * cheap (one pass to classify, one to position), and a cache keyed on a `data`\r\n * array that a rebuild replaces on every edit would miss every time anyway.\r\n */\r\nexport function resolveXScale(\r\n data: DataRecord[],\r\n field: string,\r\n /**\r\n * The number-format pattern for a *numeric* column. Ignored by the other two\r\n * kinds, and correctly so: a tick on those is captioned with the cell the\r\n * author wrote, and `Jan` has no decimal places.\r\n */\r\n pattern?: string\r\n): XScale {\r\n if (field !== \"\" && isNumericColumn(data, field)) {\r\n const positions = data.map((row) => num(row, field))\r\n return {\r\n kind: \"numeric\",\r\n at: (i) => positions[i] ?? 0,\r\n extent: extentOf(positions),\r\n label: (value) =>\r\n pattern === undefined ? formatMarker(value) : formatNumber(value, pattern),\r\n // Round numbers, evenly spaced — a continuous axis is read by its scale\r\n // rather than by which samples happen to be on it.\r\n ticks: markerValues,\r\n }\r\n }\r\n\r\n const captions = data.map((row) => text(row, field))\r\n const instants = field === \"\" ? null : instantColumn(data, field)\r\n\r\n if (instants) {\r\n const extent = extentOf(instants)\r\n const span = extent[1] - extent[0]\r\n return {\r\n kind: \"time\",\r\n at: (i) => instants[i] ?? 0,\r\n extent,\r\n label: (value) =>\r\n labelBySample(instants, captions, value, (v) =>\r\n formatInstant(v, span)\r\n ),\r\n ticks: (range, divisions) => sampledTicks(instants, range, divisions),\r\n }\r\n }\r\n\r\n const positions = data.map((_, i) => i)\r\n return {\r\n kind: \"ordinal\",\r\n at: (i) => i,\r\n // `[0, n-1]` rather than the tidy `[0, n]`: the first and last rows sit on\r\n // the plot's edges, which is where a reader looks for them.\r\n extent: positions.length > 0 ? [0, positions.length - 1] : [0, 1],\r\n label: (value) => captions[Math.round(value)] ?? \"\",\r\n ticks: (range, divisions) => sampledTicks(positions, range, divisions),\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"x-scale.js","sourceRoot":"","sources":["../../src/line-chart/x-scale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,GAAG,EACH,IAAI,GAEL,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAuB1D,6EAA6E;AAE7E,MAAM,GAAG,GAAG,UAAU,CAAA;AAEtB,MAAM,MAAM,GAAG;IACb,SAAS;IACT,UAAU;IACV,OAAO;IACP,OAAO;IACP,KAAK;IACL,MAAM;IACN,MAAM;IACN,QAAQ;IACR,WAAW;IACX,SAAS;IACT,UAAU;IACV,UAAU;CACX,CAAA;AAED;;;;;;GAMG;AACH,MAAM,YAAY,GAAG,IAAI,CAAA;AAEzB;;;;;;;GAOG;AACH,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,CAAA;IAC9B,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;AAC5D,CAAC;AAED,sEAAsE;AACtE,SAAS,YAAY,CAAC,IAAY,EAAE,IAAY;IAC9C,0EAA0E;IAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACjC,MAAM,OAAO,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA,CAAC,aAAa;IAClE,OAAO,IAAI,GAAG,OAAO,GAAG,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;AACpD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACxC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAClC,IAAI,CAAC,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAEzB,8CAA8C;IAC9C,MAAM,GAAG,GAAG,0CAA0C,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC9D,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QACrD,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,GAAG,EAAE;YAAE,OAAO,IAAI,CAAA;QAC/D,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAA;IACjD,CAAC;IAED,qCAAqC;IACrC,MAAM,IAAI,GAAG,+CAA+C,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACpE,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QACzB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE;YAAE,OAAO,IAAI,CAAA;QAChC,OAAO,YAAY,CACjB,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EACtD,CAAC,CACF,CAAA;IACH,CAAC;IAED,qDAAqD;IACrD,MAAM,KAAK,GAAG,gDAAgD,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACtE,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAClC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAA;YACjC,MAAM,CAAC,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YAC1D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;QAC9B,CAAC;IACH,CAAC;IAED,2CAA2C;IAC3C,MAAM,QAAQ,GACZ,sEAAsE,CAAC,IAAI,CACzE,CAAC,CACF,CAAA;IACH,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;QACpD,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;QAC9C,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC;YACxC,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;QAClD,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,IAAY;IACvD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAA;IAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,CAAA;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACpD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAC5D,IAAI,IAAI,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAA;IAC7C,IAAI,IAAI,GAAG,EAAE,GAAG,GAAG;QAAE,OAAO,GAAG,KAAK,IAAI,IAAI,EAAE,CAAA;IAC9C,OAAO,GAAG,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE,CAAA;AACxC,CAAC;AAED,6EAA6E;AAE7E;;;;;;;GAOG;AACH,SAAS,eAAe,CAAC,IAAkB,EAAE,KAAa;IACxD,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAA;QACpB,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE;YAAE,SAAQ;QACvD,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAA;QACvC,QAAQ,GAAG,IAAI,CAAA;IACjB,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,aAAa,CAAC,IAAkB,EAAE,KAAa;IACtD,MAAM,MAAM,GAAsB,EAAE,CAAA;IACpC,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAA;QACtB,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;YACpD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACjB,SAAQ;QACV,CAAC;QACD,MAAM,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,CAAC,KAAK,IAAI;YAAE,OAAO,IAAI,CAAA;QAC3B,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACd,QAAQ,GAAG,IAAI,CAAA;IACjB,CAAC;IACD,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAA;IAE1B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAW,CAAA;IACnE,IAAI,IAAI,GAAG,KAAK,CAAA;IAChB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;AAC5D,CAAC;AAED,2EAA2E;AAC3E,SAAS,QAAQ,CAAC,MAAgB;IAChC,IAAI,EAAE,GAAG,QAAQ,CAAA;IACjB,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAA;IAClB,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC,GAAG,EAAE;YAAE,EAAE,GAAG,CAAC,CAAA;QAClB,IAAI,CAAC,GAAG,EAAE;YAAE,EAAE,GAAG,CAAC,CAAA;IACpB,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AACrC,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAS,YAAY,CACnB,SAAmB,EACnB,CAAC,GAAG,EAAE,GAAG,CAAQ,EACjB,SAAiB;IAEjB,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAA;IAC5D,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACzD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,SAAS,CAAC,CAAA;IAEnE,MAAM,OAAO,GAAG,CAAC,MAAc,EAAE,EAAE,CACjC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CACxB,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC1D,CAAA;IAEH,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,MAAM,IAAI,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;QAC5B,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CACpB,SAAmB,EACnB,QAAkB,EAClB,KAAa,EACb,QAAmC;IAEnC,MAAM,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC/C,CAAC;AAED,yEAAyE;AACzE,MAAM,MAAM,GAAG,IAAI,OAAO,EAAqC,CAAA;AAE/D;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAkB,EAClB,KAAa,EACb,OAAgB;IAEhB,IAAI,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,CAAA;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,OAAO,IAAI,IAAI,CAAC,CAAC,CAAA;IACpD,IAAI,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC1B,IAAI,CAAC,KAAK;QAAE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACpE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,QAAQ,CACf,IAAkB,EAClB,KAAa;AACb;;;;GAIG;AACH,OAAgB;IAEhB,IAAI,KAAK,KAAK,EAAE,IAAI,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;QACpD,OAAO;YACL,IAAI,EAAE,SAAS;YACf,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5B,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC;YAC3B,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC;YAC5E,wEAAwE;YACxE,mDAAmD;YACnD,KAAK,EAAE,YAAY;SACpB,CAAA;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;IACpD,MAAM,QAAQ,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAEjE,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAA;QACjC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;QAClC,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;YAC3B,MAAM;YACN,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAC7C,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,CACvB;YACH,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC;SACtE,CAAA;IACH,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IACvC,OAAO;QACL,IAAI,EAAE,SAAS;QACf,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACZ,2EAA2E;QAC3E,4DAA4D;QAC5D,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACjE,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE;QACnD,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,EAAE,SAAS,CAAC;KACvE,CAAA;AACH,CAAC","sourcesContent":["/**\n * What a line chart's x column *means* — the one question that has to be\n * answered before a row can be given a position along the bottom of the plot.\n *\n * A y column is always a magnitude. An x column is one of three things, and\n * which one is a property of the data rather than something the author states:\n *\n * - **numeric** — `0, 5, 10` — plot the number.\n * - **time** — `Jan`, `2026-03`, `2026-W12`, `2026-03-14` — plot the instant,\n * so samples a month apart sit a month apart and a gap in the data reads as a\n * gap.\n * - **ordinal** — anything else — plot the row's position, evenly spaced.\n *\n * Before this, every x column went through `num()`, which reads a non-numeric\n * cell as `0`. A `Month` column of `Jan…Dec` therefore stacked all twelve\n * points on one x and drew a vertical line — no error, no empty chart, just a\n * wrong picture. That is the failure this module exists to remove: every column\n * now plots as *something*, and the something is the reading a person would\n * give it.\n *\n * The captions come back off the rows rather than out of a formatter. A tick on\n * a time or ordinal scale is chosen *from the data* (see {@link XScale.ticks}),\n * so the label it wants is the cell the author wrote — `Jan`, not `1970-01-01`,\n * and `2026-W12`, not the Monday it resolves to. Only a tick that is not a\n * sample — an author-set range, or a `zoomTo` mid-flight — needs formatting,\n * and that is the one case {@link formatInstant} covers.\n */\n\nimport type { DataRecord } from \"@motionscript/sdk\"\n\nimport {\n formatMarker,\n markerValues,\n num,\n text,\n type Range,\n} from \"../kit/shared\"\nimport { formatNumber } from \"@motionscript/sdk/component\"\n\n/** How an x column's cells become positions along the axis. */\nexport type XScaleKind = \"numeric\" | \"time\" | \"ordinal\"\n\n/**\n * An x column resolved into positions, captions and ticks.\n *\n * The three kinds differ in all three answers, so they are three closures built\n * once per (data, column) rather than a kind tag every caller switches on.\n */\nexport interface XScale {\n kind: XScaleKind\n /** Position of row `index` along the axis, in this scale's own units. */\n at(index: number): number\n /** `[min, max]` across the rows — the scale when no range is set. */\n extent: Range\n /** The caption for a tick at `value`. */\n label(value: number): string\n /** `divisions + 1` tick values spanning `range`, ends included. */\n ticks(range: Range, divisions: number): number[]\n}\n\n// ---- Time parsing --------------------------------------------------------\n\nconst DAY = 86_400_000\n\nconst MONTHS = [\n \"january\",\n \"february\",\n \"march\",\n \"april\",\n \"may\",\n \"june\",\n \"july\",\n \"august\",\n \"september\",\n \"october\",\n \"november\",\n \"december\",\n]\n\n/**\n * The year a cell that names no year is dated into.\n *\n * Any year would place `Jan…Dec` in the right order at the right spacing; a\n * leap year is the one that also spaces February correctly against a column\n * that *does* carry years, so the two read alike.\n */\nconst NOMINAL_YEAR = 2024\n\n/**\n * Month index for any unambiguous abbreviation of a month name — `jan`, `sept`,\n * `january` — or -1.\n *\n * Three letters minimum, and then a prefix match, which is what lets `sept`\n * through without letting `janitor` through: a longer word has to still *be*\n * the month.\n */\nfunction monthIndex(word: string): number {\n if (word.length < 3) return -1\n return MONTHS.findIndex((month) => month.startsWith(word))\n}\n\n/** The Monday of ISO week `week` in `year`, as an epoch timestamp. */\nfunction isoWeekStart(year: number, week: number): number {\n // ISO week 1 is the week containing 4 January, and weeks start on Monday.\n const jan4 = Date.UTC(year, 0, 4)\n const weekday = (new Date(jan4).getUTCDay() + 6) % 7 // Monday = 0\n return jan4 - weekday * DAY + (week - 1) * 7 * DAY\n}\n\n/**\n * One cell read as an instant, or `null` when it isn't one.\n *\n * Deliberately **not** `Date.parse`. That accepts `\"5\"`, `\"Q3\"` and a good deal\n * else besides, differs between engines on anything non-ISO, and reads a bare\n * `2026-03-14` as UTC but `2026/03/14` as local — three ways for a column to be\n * silently misread as time. Every form below is matched explicitly, and an\n * ambiguous all-numeric date (`03/04/2026` — March or April, depending on where\n * the author lives) is deliberately not one of them.\n */\nexport function parseInstant(raw: unknown): number | null {\n if (typeof raw !== \"string\") return null\n const s = raw.trim().toLowerCase()\n if (s === \"\") return null\n\n // 2026-03-14 · 2026/03/14 · 2026-03 · 2026/03\n const ymd = /^(\\d{4})[-/](\\d{1,2})(?:[-/](\\d{1,2}))?$/.exec(s)\n if (ymd) {\n const month = Number(ymd[2])\n const day = ymd[3] === undefined ? 1 : Number(ymd[3])\n if (month < 1 || month > 12 || day < 1 || day > 31) return null\n return Date.UTC(Number(ymd[1]), month - 1, day)\n }\n\n // 2026-W12 · 2026W12 · w12 · week 12\n const week = /^(?:(\\d{4})[-\\s]?)?w(?:ee)?k?[-\\s]?(\\d{1,2})$/.exec(s)\n if (week) {\n const n = Number(week[2])\n if (n < 1 || n > 53) return null\n return isoWeekStart(\n week[1] === undefined ? NOMINAL_YEAR : Number(week[1]),\n n\n )\n }\n\n // jan · january · jan 2026 · january-2026 · 2026 jan\n const named = /^(?:(\\d{4})[-\\s]+)?([a-z]+)(?:[-\\s]+(\\d{4}))?$/.exec(s)\n if (named) {\n const month = monthIndex(named[2])\n if (month >= 0) {\n const year = named[1] ?? named[3]\n const y = year === undefined ? NOMINAL_YEAR : Number(year)\n return Date.UTC(y, month, 1)\n }\n }\n\n // 14 mar 2026 · mar 14 2026 · mar 14, 2026\n const dayNamed =\n /^(?:(\\d{1,2})[-\\s]+([a-z]+)|([a-z]+)[-\\s]+(\\d{1,2}),?)[-\\s]+(\\d{4})$/.exec(\n s\n )\n if (dayNamed) {\n const month = monthIndex(dayNamed[2] ?? dayNamed[3])\n const day = Number(dayNamed[1] ?? dayNamed[4])\n if (month >= 0 && day >= 1 && day <= 31) {\n return Date.UTC(Number(dayNamed[5]), month, day)\n }\n }\n\n return null\n}\n\n/**\n * An instant captioned at the coarsest granularity the axis's own span can tell\n * apart — a decade of samples reads as years, a year of them as months.\n *\n * Only reached for a tick that is *not* one of the rows (an author-set range, a\n * `zoomTo` part-way through). A tick that is a sample is captioned with the\n * cell the author wrote; see the note at the top of this file.\n */\nexport function formatInstant(value: number, span: number): string {\n const date = new Date(value)\n const year = date.getUTCFullYear()\n const month = MONTHS[date.getUTCMonth()].slice(0, 3)\n const label = month.charAt(0).toUpperCase() + month.slice(1)\n if (span > 4 * 365 * DAY) return String(year)\n if (span > 60 * DAY) return `${label} ${year}`\n return `${label} ${date.getUTCDate()}`\n}\n\n// ---- Column reading ------------------------------------------------------\n\n/**\n * Whether every non-empty cell of `field` holds a number.\n *\n * The same rule `numericFields` applies, asked of one column: a single text\n * cell in an otherwise numeric column takes the whole column off the numeric\n * scale, because plotting that row at 0 is the failure this module is here to\n * stop.\n */\nfunction isNumericColumn(data: DataRecord[], field: string): boolean {\n let sawValue = false\n for (const row of data) {\n const v = row[field]\n if (v === undefined || v === null || v === \"\") continue\n if (typeof v !== \"number\") return false\n sawValue = true\n }\n return sawValue\n}\n\n/**\n * Every non-empty cell of `field` parsed as an instant, or `null` if any one of\n * them isn't one — in which case the column isn't a time column at all.\n *\n * A blank cell keeps its slot, so the returned list stays index-aligned with\n * the rows, and takes the nearest known instant: a hole in an x column is a row\n * whose date the file didn't say, and stacking it on the epoch would drag the\n * whole scale back fifty years to make room for it.\n */\nfunction instantColumn(data: DataRecord[], field: string): number[] | null {\n const parsed: (number | null)[] = []\n let sawValue = false\n for (const row of data) {\n const raw = row[field]\n if (raw === undefined || raw === null || raw === \"\") {\n parsed.push(null)\n continue\n }\n const t = parseInstant(raw)\n if (t === null) return null\n parsed.push(t)\n sawValue = true\n }\n if (!sawValue) return null\n\n const first = parsed.find((t): t is number => t !== null) as number\n let last = first\n return parsed.map((t) => (t === null ? last : (last = t)))\n}\n\n/** `[min, max]` of `values`, or `[0, 1]` when there is nothing to span. */\nfunction extentOf(values: number[]): Range {\n let lo = Infinity\n let hi = -Infinity\n for (const v of values) {\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 * Ticks spanning `range`, each one **a sample** — a row the author can point at\n * in their file — placed as near as it can be to where an evenly spaced tick\n * would have gone.\n *\n * Two things have to hold at once, and picking either alone gets it wrong.\n * Ticks must be *samples*, because a caption between two of them has nothing to\n * say (two-thirds of the way through March is not a month, and on a category\n * column it is not anything at all). And they must be *spread*, because the\n * labels sit side by side in a row: taking every nth sample instead puts five\n * dates in the two weeks a burst of rows happens to cover and prints them on\n * top of each other, which is what a column with a gap in it does.\n *\n * Walking the targets rather than the samples is what satisfies both. A run of\n * rows too close together to caption collapses to one tick — consecutive\n * targets pick the same sample and the duplicate is dropped — so a chart asked\n * for five ticks across a gappy column draws four honest ones instead of five\n * overlapping.\n */\nfunction sampledTicks(\n positions: number[],\n [min, max]: Range,\n divisions: number\n): number[] {\n const inside = positions.filter((v) => v >= min && v <= max)\n const unique = [...new Set(inside)].sort((a, b) => a - b)\n if (unique.length === 0) return markerValues([min, max], divisions)\n\n const nearest = (target: number) =>\n unique.reduce((best, v) =>\n Math.abs(v - target) < Math.abs(best - target) ? v : best\n )\n\n const out: number[] = []\n for (const target of markerValues([min, max], divisions)) {\n const tick = nearest(target)\n if (out[out.length - 1] !== tick) out.push(tick)\n }\n return out\n}\n\n/**\n * The caption a tick carries on a sample-based scale: the cell of the row it\n * came from, or `fallback` when the tick isn't a sample after all.\n */\nfunction labelBySample(\n positions: number[],\n captions: string[],\n value: number,\n fallback: (value: number) => string\n): string {\n const i = positions.indexOf(value)\n return i >= 0 ? captions[i] : fallback(value)\n}\n\n/** Scales already resolved, by data array, then by field and pattern. */\nconst scales = new WeakMap<DataRecord[], Map<string, XScale>>()\n\n/**\n * How `field` positions the rows: the number it holds, the instant it names, or\n * failing both, the row's own place in the file.\n *\n * Kept per `data` array: a chart's bound props ask a dozen times a frame, and\n * parsing every row's instant each time was most of a frame.\n */\nexport function resolveXScale(\n data: DataRecord[],\n field: string,\n pattern?: string\n): XScale {\n let byKey = scales.get(data)\n if (!byKey) scales.set(data, (byKey = new Map()))\n const key = JSON.stringify([field, pattern ?? null])\n let scale = byKey.get(key)\n if (!scale) byKey.set(key, (scale = xScaleOf(data, field, pattern)))\n return scale\n}\n\nfunction xScaleOf(\n data: DataRecord[],\n field: string,\n /**\n * The number-format pattern for a *numeric* column. Ignored by the other two\n * kinds, and correctly so: a tick on those is captioned with the cell the\n * author wrote, and `Jan` has no decimal places.\n */\n pattern?: string\n): XScale {\n if (field !== \"\" && isNumericColumn(data, field)) {\n const positions = data.map((row) => num(row, field))\n return {\n kind: \"numeric\",\n at: (i) => positions[i] ?? 0,\n extent: extentOf(positions),\n label: (value) =>\n pattern === undefined ? formatMarker(value) : formatNumber(value, pattern),\n // Round numbers, evenly spaced — a continuous axis is read by its scale\n // rather than by which samples happen to be on it.\n ticks: markerValues,\n }\n }\n\n const captions = data.map((row) => text(row, field))\n const instants = field === \"\" ? null : instantColumn(data, field)\n\n if (instants) {\n const extent = extentOf(instants)\n const span = extent[1] - extent[0]\n return {\n kind: \"time\",\n at: (i) => instants[i] ?? 0,\n extent,\n label: (value) =>\n labelBySample(instants, captions, value, (v) =>\n formatInstant(v, span)\n ),\n ticks: (range, divisions) => sampledTicks(instants, range, divisions),\n }\n }\n\n const positions = data.map((_, i) => i)\n return {\n kind: \"ordinal\",\n at: (i) => i,\n // `[0, n-1]` rather than the tidy `[0, n]`: the first and last rows sit on\n // the plot's edges, which is where a reader looks for them.\n extent: positions.length > 0 ? [0, positions.length - 1] : [0, 1],\n label: (value) => captions[Math.round(value)] ?? \"\",\n ticks: (range, divisions) => sampledTicks(positions, range, divisions),\n }\n}\n"]}
package/dist/nodes.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"nodes.js","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;;;;;;;;GAWG;AACH,yGAAyG;AACzG,MAAM,CAAC,MAAM,KAAK,GAA6D,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC","sourcesContent":["import { LineChart } from \"./line-chart\";\r\nimport { BarChart } from \"./bar-chart\";\r\nimport { PieChart } from \"./pie-chart\";\r\n\r\n/**\r\n * Every node type this package publishes.\r\n *\r\n * A host registers these by handing the array to an engine —\r\n * `new Engine(platform, { nodes: NODES })` — which reads each component's key.\r\n * Defining a component only *declares* that key: nothing is registered by\r\n * importing a module, so a registry holds exactly what its owner asked for.\r\n *\r\n * Listing the component **values** is also what survives bundling: this package\r\n * declares `sideEffects: false`, and a document names a node type by string, so\r\n * an import kept only for its side effect is exactly what gets shaken out.\r\n */\r\n// Typed by name, so the declaration names each component rather than spelling out its type through core.\r\nexport const NODES: (typeof LineChart | typeof BarChart | typeof PieChart)[] = [LineChart, BarChart, PieChart];\r\n"]}
1
+ {"version":3,"file":"nodes.js","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;;;;;;;;GAWG;AACH,yGAAyG;AACzG,MAAM,CAAC,MAAM,KAAK,GAA6D,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC","sourcesContent":["import { LineChart } from \"./line-chart\";\nimport { BarChart } from \"./bar-chart\";\nimport { PieChart } from \"./pie-chart\";\n\n/**\n * Every node type this package publishes.\n *\n * A host registers these by handing the array to an engine —\n * `new Engine(platform, { nodes: NODES })` — which reads each component's key.\n * Defining a component only *declares* that key: nothing is registered by\n * importing a module, so a registry holds exactly what its owner asked for.\n *\n * Listing the component **values** is also what survives bundling: this package\n * declares `sideEffects: false`, and a document names a node type by string, so\n * an import kept only for its side effect is exactly what gets shaken out.\n */\n// Typed by name, so the declaration names each component rather than spelling out its type through core.\nexport const NODES: (typeof LineChart | typeof BarChart | typeof PieChart)[] = [LineChart, BarChart, PieChart];\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pie-chart/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,OAAO,EACP,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,UAAU,CAAA","sourcesContent":["/**\r\n * The pie chart: the one in the family that draws a mark per **row** rather\r\n * than per column.\r\n *\r\n * So it shares the legend and the theme machinery with its siblings and none of\r\n * the cartesian geometry. Set the theme's `ring.holeRadius` to 0 for a solid\r\n * pie; it defaults to a donut, because the hole is the only place a chart has\r\n * to put its conclusion.\r\n */\r\nexport { PieChart, isSliceSelected } from \"./pie-chart\"\r\nexport type { PieChartProps, PieChartValues } from \"./pie-chart\"\r\nexport { PieRing } from \"./pie-ring\"\r\nexport type { PieRingProps } from \"./pie-ring\"\r\nexport { PieLegend } from \"./pie-legend\"\r\nexport type { PieLegendProps } from \"./pie-legend\"\r\nexport {\r\n DEFAULT_PIE_CHART_THEME,\r\n lerpPieChartTheme,\r\n pieArcs,\r\n relaxLabels,\r\n resolvePieChartTheme,\r\n resolvePieSlices,\r\n ringPoint,\r\n sliceTotal,\r\n sliceValue,\r\n} from \"./shared\"\r\nexport type {\r\n CenterConfig,\r\n LabelConfig,\r\n PieArc,\r\n PieChartConfig,\r\n PieChartTheme,\r\n PieLabelMode,\r\n PieLabelPlacement,\r\n PieSlice,\r\n RingConfig,\r\n} from \"./shared\"\r\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pie-chart/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,OAAO,EACP,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,UAAU,CAAA","sourcesContent":["/**\n * The pie chart: the one in the family that draws a mark per **row** rather\n * than per column.\n *\n * So it shares the legend and the theme machinery with its siblings and none of\n * the cartesian geometry. Set the theme's `ring.holeRadius` to 0 for a solid\n * pie; it defaults to a donut, because the hole is the only place a chart has\n * to put its conclusion.\n */\nexport { PieChart, isSliceSelected } from \"./pie-chart\"\nexport type { PieChartProps, PieChartValues } from \"./pie-chart\"\nexport { PieRing } from \"./pie-ring\"\nexport type { PieRingProps } from \"./pie-ring\"\nexport { PieLegend } from \"./pie-legend\"\nexport type { PieLegendProps } from \"./pie-legend\"\nexport {\n DEFAULT_PIE_CHART_THEME,\n lerpPieChartTheme,\n pieArcs,\n relaxLabels,\n resolvePieChartTheme,\n resolvePieSlices,\n ringPoint,\n sliceTotal,\n sliceValue,\n} from \"./shared\"\nexport type {\n CenterConfig,\n LabelConfig,\n PieArc,\n PieChartConfig,\n PieChartTheme,\n PieLabelMode,\n PieLabelPlacement,\n PieSlice,\n RingConfig,\n} from \"./shared\"\n"]}
@@ -94,7 +94,7 @@ export declare const PieChart: import("@motionscript/sdk").Component<"pieChart",
94
94
  readonly sliceIndex: import("@motionscript/sdk").Type<number, number, false>;
95
95
  };
96
96
  readonly clearSelection: {};
97
- }, null, Node2D<Node2DProps>, import("@motionscript/sdk").CallProps<import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
97
+ }, readonly [], Node2D<Node2DProps>, import("@motionscript/sdk").CallProps<import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
98
98
  readonly data: import("@motionscript/sdk").Type<DataRecord[], DataRecord[], false>;
99
99
  readonly categoryField: "";
100
100
  readonly valueField: "";
@@ -114,7 +114,7 @@ export declare const PieChart: import("@motionscript/sdk").Component<"pieChart",
114
114
  readonly showLegend: import("@motionscript/sdk").Type<boolean, boolean, false>;
115
115
  /** Read as the chart is built: whether the hole's summary is drawn at rest. */
116
116
  readonly showCenter: import("@motionscript/sdk").Type<boolean, boolean, false>;
117
- }>, null, import("@motionscript/sdk").BuiltNode<import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
117
+ }>, readonly [], import("@motionscript/sdk").BuiltNode<import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
118
118
  readonly data: import("@motionscript/sdk").Type<DataRecord[], DataRecord[], false>;
119
119
  readonly categoryField: "";
120
120
  readonly valueField: "";
@@ -1 +1 @@
1
- {"version":3,"file":"pie-chart.d.ts","sourceRoot":"","sources":["../../src/pie-chart/pie-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,UAAU,EACf,KAAK,MAAM,EAEX,KAAK,MAAM,EACX,KAAK,WAAW,EAEjB,MAAM,mBAAmB,CAAA;AAO1B,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EAEvB,MAAM,UAAU,CAAA;AAEjB,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,iEAAiE;IACjE,IAAI,EAAE,UAAU,EAAE,CAAA;IAClB,kFAAkF;IAClF,aAAa,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,kCAAkC;IAClC,SAAS,CAAC,EAAE,YAAY,CAAA;IACxB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,iBAAiB,CAAA;IAClC,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,4FAA4F;AAC5F,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,CAAA;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CACxB;AAqBD,sHAAsH;AACtH,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzE;AAsCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;IAYjB;;;;OAIG;;IAEH,oFAAoF;;IAEpF,+EAA+E;;;;;;;;;;;;;;;;;;;;;;;;;IAR/E;;;;OAIG;;IAEH,oFAAoF;;IAEpF,+EAA+E;;;;;;;;;;;;IAR/E;;;;OAIG;;IAEH,oFAAoF;;IAEpF,+EAA+E;;;;;;;;;;;;;;;yBAsHjF,CAAA;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAA"}
1
+ {"version":3,"file":"pie-chart.d.ts","sourceRoot":"","sources":["../../src/pie-chart/pie-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,UAAU,EACf,KAAK,MAAM,EAEX,KAAK,MAAM,EACX,KAAK,WAAW,EAEjB,MAAM,mBAAmB,CAAA;AAO1B,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EAEvB,MAAM,UAAU,CAAA;AAEjB,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,iEAAiE;IACjE,IAAI,EAAE,UAAU,EAAE,CAAA;IAClB,kFAAkF;IAClF,aAAa,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,kCAAkC;IAClC,SAAS,CAAC,EAAE,YAAY,CAAA;IACxB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,iBAAiB,CAAA;IAClC,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,4FAA4F;AAC5F,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,CAAA;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CACxB;AAqBD,sHAAsH;AACtH,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzE;AAsCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;IAYjB;;;;OAIG;;IAEH,oFAAoF;;IAEpF,+EAA+E;;;;;;;;;;;;;;;;;;;;;;;;;IAR/E;;;;OAIG;;IAEH,oFAAoF;;IAEpF,+EAA+E;;;;;;;;;;;;IAR/E;;;;OAIG;;IAEH,oFAAoF;;IAEpF,+EAA+E;;;;;;;;;;;;;;;yBAyHjF,CAAA;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAA"}
@@ -107,6 +107,7 @@ export const PieChart = component2d({
107
107
  height: "fill",
108
108
  pops: (state) => slicesOf(state).map(() => 0),
109
109
  },
110
+ allowedChildren: [],
110
111
  // The slice *structure* (one label and one legend row per row) is read once;
111
112
  // the slices themselves, their paint and the selection are bound, so a theme
112
113
  // tween re-paints and a `set` of `data` re-cuts the wedges.
@@ -142,7 +143,7 @@ export const PieChart = component2d({
142
143
  }));
143
144
  }
144
145
  const row = new Rect({ flow: "horizontal", width: "fill", height: "fill", gap: state.gap, align: "center", children: parts });
145
- return asParts([...state.children, row]);
146
+ return asParts([row]);
146
147
  },
147
148
  commands: {
148
149
  /**
@@ -153,6 +154,8 @@ export const PieChart = component2d({
153
154
  */
154
155
  enter: {
155
156
  args: {},
157
+ // Held from the chart's arrival, so it never shows finished ahead of its draw.
158
+ entrance: true,
156
159
  easing: easeOut(),
157
160
  run: (timeline, _state, children) => {
158
161
  const parts = partsOf(children);
@@ -1 +1 @@
1
- {"version":3,"file":"pie-chart.js","sourceRoot":"","sources":["../../src/pie-chart/pie-chart.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n Rect,\r\n Types,\r\n component2d,\r\n easeOut,\r\n type ChildView,\r\n type DataRecord,\r\n type NodeOf,\r\n // Named so this component's emitted type reaches `Node2D` through the sdk, not core.\r\n type Node2D,\r\n type Node2DProps,\r\n type Timeline,\r\n} from \"@motionscript/sdk\"\r\n\r\nimport { asParts, fresh } from \"../kit/entries\"\r\nimport { beats, type Beat } from \"../kit/motion\"\r\nimport { clampUnit } from \"../kit/shared\"\r\nimport { PieLegend } from \"./pie-legend\"\r\nimport { PieRing } from \"./pie-ring\"\r\nimport {\r\n DEFAULT_PIE_CHART_THEME,\r\n lerpPieChartTheme,\r\n resolvePieChartTheme,\r\n resolvePieSlices,\r\n type PieChartConfig,\r\n type PieChartTheme,\r\n type PieLabelMode,\r\n type PieLabelPlacement,\r\n type PieSlice,\r\n} from \"./shared\"\r\n\r\nexport interface PieChartProps extends Node2DProps {\r\n /** One row per slice — a plain record of field name to value. */\r\n data: DataRecord[]\r\n /** Key into each row for its slice caption, used on the labels and the legend. */\r\n categoryField: string\r\n /** Key into each row for its slice magnitude — the wedge's share of the ring. */\r\n valueField: string\r\n /**\r\n * The chart's visual theme — slice palette, ring geometry, label and centre\r\n * type, legend. Every branch is optional and deep-merged onto\r\n * {@link DEFAULT_PIE_CHART_THEME}.\r\n */\r\n theme?: PieChartTheme\r\n /** What the ring's labels say. */\r\n labelMode?: PieLabelMode\r\n /** Where they sit: inside their wedge, outside on a leader, or nowhere. */\r\n labelPlacement?: PieLabelPlacement\r\n /** Whether the legend column beside the ring is drawn at all. */\r\n showLegend?: boolean\r\n /** Gap between the ring and the legend, in px. */\r\n gap?: number\r\n /** Text set in the ring's hole — see `revealCenter`. */\r\n centerLabel?: string\r\n /** Smaller second line under {@link centerLabel}. */\r\n centerCaption?: string\r\n /**\r\n * Whether the hole's summary is drawn at rest. `true` (the default) is the\r\n * plain answer; a scene that wants the conclusion to land on a later beat sets\r\n * this `false` and runs `revealCenter` when it gets there.\r\n */\r\n showCenter?: boolean\r\n}\r\n\r\n/** What the selection reads: the rows, how they are cut, and the pops the commands move. */\r\nexport interface PieChartValues {\r\n readonly data: DataRecord[]\r\n readonly categoryField: string\r\n readonly valueField: string\r\n readonly theme: PieChartConfig\r\n readonly pops: number[]\r\n}\r\n\r\n/**\r\n * How `enter` divides its duration: the wedges sweeping in, then the labels and\r\n * the legend arriving together. Shares of the whole, so they sum to 1.\r\n */\r\nconst SWEEP_SHARE = 0.65\r\nconst LABEL_SHARE = 0.35\r\n\r\nconst SLICE_ARG = Types.number(0, { min: 0, step: 1 })\r\n\r\n/** The slices the ring and the legend draw, resolved from the rows and the palette. */\r\nfunction slicesOf(s: PieChartValues): PieSlice[] {\r\n return resolvePieSlices(s.data, s.categoryField, s.valueField, s.theme.sliceFills)\r\n}\r\n\r\n/** How far slice `index` is pushed out, in `[0, 1]`. */\r\nfunction popAt(s: PieChartValues, index: number): number {\r\n return clampUnit(s.pops[index] ?? 0)\r\n}\r\n\r\n/** Whether slice `index` is grown out past the ring's rim: past half way, so a slice mid-swell is not yet counted. */\r\nexport function isSliceSelected(s: PieChartValues, index: number): boolean {\r\n return popAt(s, index) > 0.5\r\n}\r\n\r\n/**\r\n * How far each slice is dimmed by the selection, one entry per slice: 1 while\r\n * nothing is out, and for the selected slice itself; falling toward the theme's\r\n * `dimOpacity` for the rest in proportion to how far the selection has grown.\r\n */\r\nfunction sliceDims(s: PieChartValues): number[] {\r\n let focus = 0\r\n for (let i = 0; i < s.data.length; i++) focus = Math.max(focus, popAt(s, i))\r\n const dim = s.theme.spotlight.dimOpacity\r\n if (focus <= 0) return s.data.map(() => 1)\r\n return s.data.map((_, i) => 1 - (1 - clampUnit(dim)) * focus * (1 - popAt(s, i)))\r\n}\r\n\r\ntype AnyTimeline = Timeline<any, any>\r\n\r\n/**\r\n * Every slice's pop eased from where it is to what `pop` asks for. A move that\r\n * changes nothing still takes its slot, writing nothing.\r\n */\r\nfunction popping(timeline: AnyTimeline, s: PieChartValues, pop: (index: number) => number) {\r\n const from = s.data.map((_, i) => popAt(s, i))\r\n const targets = s.data.map((_, i) => clampUnit(pop(i)))\r\n if (from.every((value, i) => value === targets[i])) return timeline.wait(timeline.duration)\r\n return timeline.animate((t) => ({ pops: from.map((value, i) => value + (targets[i] - value) * t) }))\r\n}\r\n\r\n/** The ring and the legend, found through the chart's children: the last row of them is the one it built. */\r\nfunction partsOf(children: readonly ChildView[]) {\r\n for (let i = children.length - 1; i >= 0; i--) {\r\n const row = children[i]\r\n const ring = Rect.is(row) ? row.children.find(PieRing.is) : undefined\r\n if (ring) return { ring, legend: row.children.find(PieLegend.is) ?? null }\r\n }\r\n return null\r\n}\r\n\r\n/**\r\n * A pie or donut chart, composed from two parts laid out by a horizontal-flow\r\n * {@link Rect} — the ring filling whatever the hugging legend leaves, both\r\n * centred:\r\n *\r\n * - {@link PieRing} — the wedges, each sized by its share of the total, with its\r\n * label inside its own band or pinned outside on a leader line, and the\r\n * summary in the hole.\r\n * - {@link PieLegend} — a column of swatch-and-caption rows naming each slice.\r\n *\r\n * **One mark per row, not per column**: a pie's palette is indexed by row and\r\n * there is nothing to hide, since dropping a slice would re-cut every other\r\n * wedge. `selectSlice` and its siblings are what a pie has instead — a slice\r\n * swells out of the ring and everything else dims behind it.\r\n *\r\n * **Resting state is the finished chart**; `enter` arms every part on its first\r\n * frame. The one exception is the hole's summary, which a scene can hold back\r\n * with `showCenter: false`, because it is usually the chart's *conclusion*.\r\n */\r\nexport const PieChart = component2d({\r\n key: \"pieChart\",\r\n state: {\r\n data: fresh<DataRecord[]>(() => []),\r\n categoryField: \"\",\r\n valueField: \"\",\r\n theme: Types.custom<PieChartTheme, PieChartConfig>(DEFAULT_PIE_CHART_THEME, { resolve: resolvePieChartTheme, lerp: lerpPieChartTheme }),\r\n labelMode: Types.custom<PieLabelMode>(\"percent\"),\r\n labelPlacement: Types.custom<PieLabelPlacement>(\"leader\"),\r\n gap: 40,\r\n centerLabel: \"\",\r\n centerCaption: \"\",\r\n /**\r\n * How far each slice is pushed out of the ring, `0`–`1` per index. Held here\r\n * rather than on the ring because two parts read it: the wedges grow out by\r\n * it, and the legend entries dim by it. Seeded flush, one to a row.\r\n */\r\n pops: fresh<number[]>(() => []),\r\n /** Read as the chart is built: the legend is a part the chart builds or doesn't. */\r\n showLegend: Types.boolean(true, { animatable: false }),\r\n /** Read as the chart is built: whether the hole's summary is drawn at rest. */\r\n showCenter: Types.boolean(true, { animatable: false }),\r\n },\r\n defaults: {\r\n // Its parts fill it; a node made of them would hug them.\r\n width: \"fill\",\r\n height: \"fill\",\r\n pops: (state) => slicesOf(state).map(() => 0),\r\n },\r\n // The slice *structure* (one label and one legend row per row) is read once;\r\n // the slices themselves, their paint and the selection are bound, so a theme\r\n // tween re-paints and a `set` of `data` re-cuts the wedges.\r\n children: (state) => {\r\n const theme = () => state.theme\r\n const slices = () => slicesOf(state)\r\n const parts: Node2D[] = [\r\n new PieRing({\r\n width: \"fill\",\r\n height: \"fill\",\r\n slices,\r\n ring: () => theme().ring,\r\n label: () => theme().label,\r\n centerStyle: () => theme().center,\r\n labelMode: state.labelMode,\r\n labelPlacement: state.labelPlacement,\r\n pops: () => state.pops,\r\n dimOpacity: () => theme().spotlight.dimOpacity,\r\n centerLabel: state.centerLabel,\r\n centerCaption: state.centerCaption,\r\n centerReveal: state.showCenter === false ? 0 : 1,\r\n }) as unknown as Node2D,\r\n ]\r\n if (state.showLegend !== false) {\r\n parts.push(\r\n new PieLegend({\r\n slices,\r\n legend: () => theme().legend,\r\n dims: () => sliceDims(state),\r\n // The column's spacing is the theme's as the chart was built, like the\r\n // entries' type and chips.\r\n gap: state.theme.legend.itemGap / 3,\r\n padding: state.theme.legend.padding,\r\n }) as unknown as Node2D\r\n )\r\n }\r\n const row = new Rect({ flow: \"horizontal\", width: \"fill\", height: \"fill\", gap: state.gap, align: \"center\", children: parts })\r\n return asParts([...(state.children as unknown as Node2D[]), row as unknown as Node2D])\r\n },\r\n commands: {\r\n /**\r\n * Animate the whole chart in: the wedges sweep in clockwise from the theme's\r\n * `startAngle`, then the labels fade up and the legend staggers in together.\r\n * Its first frame arms the wedges, the labels and the legend; the hole's\r\n * summary keeps its own signal. `duration` is the **whole** entrance.\r\n */\r\n enter: {\r\n args: {},\r\n easing: easeOut(),\r\n run: (timeline, _state, children) => {\r\n const parts = partsOf(children)\r\n if (!parts) return timeline.wait(timeline.duration)\r\n const { ring, legend } = parts\r\n const sweep = timeline.duration * SWEEP_SHARE\r\n const labels = timeline.duration * LABEL_SHARE\r\n const at0 = (piece: Beat[\"piece\"]): Beat => ({ at: 0, duration: 0, piece })\r\n return beats(timeline, [\r\n at0(ring.to({ growth: 0, labelReveal: 0 })),\r\n ...(legend ? [at0(legend.run(\"arm\"))] : []),\r\n { at: 0, duration: sweep, piece: ring.run(\"sweep\") },\r\n { at: sweep, duration: labels, piece: ring.run(\"revealLabels\") },\r\n ...(legend ? [{ at: sweep, duration: labels, piece: legend.run(\"enter\") }] : []),\r\n ])\r\n },\r\n },\r\n /**\r\n * Fade the hole's summary up. Held out of `enter` on purpose: what sits in a\r\n * donut's hole is usually the chart's conclusion, so the scene lands it on\r\n * its own beat. Pair it with `showCenter: false`.\r\n */\r\n revealCenter: {\r\n args: {},\r\n easing: easeOut(),\r\n run: (timeline, _state, children) => partsOf(children)?.ring.run(\"revealCenter\") ?? timeline.wait(timeline.duration),\r\n },\r\n /** Grow slice `index` out past the ring's rim and dim the rest, leaving any other selected slice where it is. */\r\n selectSlice: {\r\n args: { sliceIndex: SLICE_ARG },\r\n easing: easeOut(),\r\n run: (timeline, state) => {\r\n const index = timeline.args.sliceIndex\r\n return popping(timeline, state, (i) => (i === index ? 1 : popAt(state, i)))\r\n },\r\n },\r\n /** Settle slice `index` back flush with the ring. */\r\n deselectSlice: {\r\n args: { sliceIndex: SLICE_ARG },\r\n easing: easeOut(),\r\n run: (timeline, state) => {\r\n const index = timeline.args.sliceIndex\r\n return popping(timeline, state, (i) => (i === index ? 0 : popAt(state, i)))\r\n },\r\n },\r\n /** Move the focus to slice `index` alone, settling whatever else was out. */\r\n selectOnlySlice: {\r\n args: { sliceIndex: SLICE_ARG },\r\n easing: easeOut(),\r\n run: (timeline, state) => {\r\n const index = timeline.args.sliceIndex\r\n return popping(timeline, state, (i) => (i === index ? 1 : 0))\r\n },\r\n },\r\n /** Settle every slice flush and bring the whole chart back to full. */\r\n clearSelection: {\r\n args: {},\r\n easing: easeOut(),\r\n run: (timeline, state) => popping(timeline, state, () => 0),\r\n },\r\n },\r\n})\r\n\r\nexport type PieChart = NodeOf<typeof PieChart>\r\n"]}
1
+ {"version":3,"file":"pie-chart.js","sourceRoot":"","sources":["../../src/pie-chart/pie-chart.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Rect,\n Types,\n component2d,\n easeOut,\n type ChildView,\n type DataRecord,\n type NodeOf,\n // Named so this component's emitted type reaches `Node2D` through the sdk, not core.\n type Node2D,\n type Node2DProps,\n type Timeline,\n} from \"@motionscript/sdk\"\n\nimport { asParts, fresh } from \"../kit/entries\"\nimport { beats, type Beat } from \"../kit/motion\"\nimport { clampUnit } from \"../kit/shared\"\nimport { PieLegend } from \"./pie-legend\"\nimport { PieRing } from \"./pie-ring\"\nimport {\n DEFAULT_PIE_CHART_THEME,\n lerpPieChartTheme,\n resolvePieChartTheme,\n resolvePieSlices,\n type PieChartConfig,\n type PieChartTheme,\n type PieLabelMode,\n type PieLabelPlacement,\n type PieSlice,\n} from \"./shared\"\n\nexport interface PieChartProps extends Node2DProps {\n /** One row per slice — a plain record of field name to value. */\n data: DataRecord[]\n /** Key into each row for its slice caption, used on the labels and the legend. */\n categoryField: string\n /** Key into each row for its slice magnitude — the wedge's share of the ring. */\n valueField: string\n /**\n * The chart's visual theme — slice palette, ring geometry, label and centre\n * type, legend. Every branch is optional and deep-merged onto\n * {@link DEFAULT_PIE_CHART_THEME}.\n */\n theme?: PieChartTheme\n /** What the ring's labels say. */\n labelMode?: PieLabelMode\n /** Where they sit: inside their wedge, outside on a leader, or nowhere. */\n labelPlacement?: PieLabelPlacement\n /** Whether the legend column beside the ring is drawn at all. */\n showLegend?: boolean\n /** Gap between the ring and the legend, in px. */\n gap?: number\n /** Text set in the ring's hole — see `revealCenter`. */\n centerLabel?: string\n /** Smaller second line under {@link centerLabel}. */\n centerCaption?: string\n /**\n * Whether the hole's summary is drawn at rest. `true` (the default) is the\n * plain answer; a scene that wants the conclusion to land on a later beat sets\n * this `false` and runs `revealCenter` when it gets there.\n */\n showCenter?: boolean\n}\n\n/** What the selection reads: the rows, how they are cut, and the pops the commands move. */\nexport interface PieChartValues {\n readonly data: DataRecord[]\n readonly categoryField: string\n readonly valueField: string\n readonly theme: PieChartConfig\n readonly pops: number[]\n}\n\n/**\n * How `enter` divides its duration: the wedges sweeping in, then the labels and\n * the legend arriving together. Shares of the whole, so they sum to 1.\n */\nconst SWEEP_SHARE = 0.65\nconst LABEL_SHARE = 0.35\n\nconst SLICE_ARG = Types.number(0, { min: 0, step: 1 })\n\n/** The slices the ring and the legend draw, resolved from the rows and the palette. */\nfunction slicesOf(s: PieChartValues): PieSlice[] {\n return resolvePieSlices(s.data, s.categoryField, s.valueField, s.theme.sliceFills)\n}\n\n/** How far slice `index` is pushed out, in `[0, 1]`. */\nfunction popAt(s: PieChartValues, index: number): number {\n return clampUnit(s.pops[index] ?? 0)\n}\n\n/** Whether slice `index` is grown out past the ring's rim: past half way, so a slice mid-swell is not yet counted. */\nexport function isSliceSelected(s: PieChartValues, index: number): boolean {\n return popAt(s, index) > 0.5\n}\n\n/**\n * How far each slice is dimmed by the selection, one entry per slice: 1 while\n * nothing is out, and for the selected slice itself; falling toward the theme's\n * `dimOpacity` for the rest in proportion to how far the selection has grown.\n */\nfunction sliceDims(s: PieChartValues): number[] {\n let focus = 0\n for (let i = 0; i < s.data.length; i++) focus = Math.max(focus, popAt(s, i))\n const dim = s.theme.spotlight.dimOpacity\n if (focus <= 0) return s.data.map(() => 1)\n return s.data.map((_, i) => 1 - (1 - clampUnit(dim)) * focus * (1 - popAt(s, i)))\n}\n\ntype AnyTimeline = Timeline<any, any>\n\n/**\n * Every slice's pop eased from where it is to what `pop` asks for. A move that\n * changes nothing still takes its slot, writing nothing.\n */\nfunction popping(timeline: AnyTimeline, s: PieChartValues, pop: (index: number) => number) {\n const from = s.data.map((_, i) => popAt(s, i))\n const targets = s.data.map((_, i) => clampUnit(pop(i)))\n if (from.every((value, i) => value === targets[i])) return timeline.wait(timeline.duration)\n return timeline.animate((t) => ({ pops: from.map((value, i) => value + (targets[i] - value) * t) }))\n}\n\n/** The ring and the legend, found through the chart's children: the last row of them is the one it built. */\nfunction partsOf(children: readonly ChildView[]) {\n for (let i = children.length - 1; i >= 0; i--) {\n const row = children[i]\n const ring = Rect.is(row) ? row.children.find(PieRing.is) : undefined\n if (ring) return { ring, legend: row.children.find(PieLegend.is) ?? null }\n }\n return null\n}\n\n/**\n * A pie or donut chart, composed from two parts laid out by a horizontal-flow\n * {@link Rect} — the ring filling whatever the hugging legend leaves, both\n * centred:\n *\n * - {@link PieRing} — the wedges, each sized by its share of the total, with its\n * label inside its own band or pinned outside on a leader line, and the\n * summary in the hole.\n * - {@link PieLegend} — a column of swatch-and-caption rows naming each slice.\n *\n * **One mark per row, not per column**: a pie's palette is indexed by row and\n * there is nothing to hide, since dropping a slice would re-cut every other\n * wedge. `selectSlice` and its siblings are what a pie has instead — a slice\n * swells out of the ring and everything else dims behind it.\n *\n * **Resting state is the finished chart**; `enter` arms every part on its first\n * frame. The one exception is the hole's summary, which a scene can hold back\n * with `showCenter: false`, because it is usually the chart's *conclusion*.\n */\nexport const PieChart = component2d({\n key: \"pieChart\",\n state: {\n data: fresh<DataRecord[]>(() => []),\n categoryField: \"\",\n valueField: \"\",\n theme: Types.custom<PieChartTheme, PieChartConfig>(DEFAULT_PIE_CHART_THEME, { resolve: resolvePieChartTheme, lerp: lerpPieChartTheme }),\n labelMode: Types.custom<PieLabelMode>(\"percent\"),\n labelPlacement: Types.custom<PieLabelPlacement>(\"leader\"),\n gap: 40,\n centerLabel: \"\",\n centerCaption: \"\",\n /**\n * How far each slice is pushed out of the ring, `0`–`1` per index. Held here\n * rather than on the ring because two parts read it: the wedges grow out by\n * it, and the legend entries dim by it. Seeded flush, one to a row.\n */\n pops: fresh<number[]>(() => []),\n /** Read as the chart is built: the legend is a part the chart builds or doesn't. */\n showLegend: Types.boolean(true, { animatable: false }),\n /** Read as the chart is built: whether the hole's summary is drawn at rest. */\n showCenter: Types.boolean(true, { animatable: false }),\n },\n defaults: {\n // Its parts fill it; a node made of them would hug them.\n width: \"fill\",\n height: \"fill\",\n pops: (state) => slicesOf(state).map(() => 0),\n },\n allowedChildren: [],\n // The slice *structure* (one label and one legend row per row) is read once;\n // the slices themselves, their paint and the selection are bound, so a theme\n // tween re-paints and a `set` of `data` re-cuts the wedges.\n children: (state) => {\n const theme = () => state.theme\n const slices = () => slicesOf(state)\n const parts: Node2D[] = [\n new PieRing({\n width: \"fill\",\n height: \"fill\",\n slices,\n ring: () => theme().ring,\n label: () => theme().label,\n centerStyle: () => theme().center,\n labelMode: state.labelMode,\n labelPlacement: state.labelPlacement,\n pops: () => state.pops,\n dimOpacity: () => theme().spotlight.dimOpacity,\n centerLabel: state.centerLabel,\n centerCaption: state.centerCaption,\n centerReveal: state.showCenter === false ? 0 : 1,\n }) as unknown as Node2D,\n ]\n if (state.showLegend !== false) {\n parts.push(\n new PieLegend({\n slices,\n legend: () => theme().legend,\n dims: () => sliceDims(state),\n // The column's spacing is the theme's as the chart was built, like the\n // entries' type and chips.\n gap: state.theme.legend.itemGap / 3,\n padding: state.theme.legend.padding,\n }) as unknown as Node2D\n )\n }\n const row = new Rect({ flow: \"horizontal\", width: \"fill\", height: \"fill\", gap: state.gap, align: \"center\", children: parts })\n return asParts([row as unknown as Node2D])\n },\n commands: {\n /**\n * Animate the whole chart in: the wedges sweep in clockwise from the theme's\n * `startAngle`, then the labels fade up and the legend staggers in together.\n * Its first frame arms the wedges, the labels and the legend; the hole's\n * summary keeps its own signal. `duration` is the **whole** entrance.\n */\n enter: {\n args: {},\n // Held from the chart's arrival, so it never shows finished ahead of its draw.\n entrance: true,\n easing: easeOut(),\n run: (timeline, _state, children) => {\n const parts = partsOf(children)\n if (!parts) return timeline.wait(timeline.duration)\n const { ring, legend } = parts\n const sweep = timeline.duration * SWEEP_SHARE\n const labels = timeline.duration * LABEL_SHARE\n const at0 = (piece: Beat[\"piece\"]): Beat => ({ at: 0, duration: 0, piece })\n return beats(timeline, [\n at0(ring.to({ growth: 0, labelReveal: 0 })),\n ...(legend ? [at0(legend.run(\"arm\"))] : []),\n { at: 0, duration: sweep, piece: ring.run(\"sweep\") },\n { at: sweep, duration: labels, piece: ring.run(\"revealLabels\") },\n ...(legend ? [{ at: sweep, duration: labels, piece: legend.run(\"enter\") }] : []),\n ])\n },\n },\n /**\n * Fade the hole's summary up. Held out of `enter` on purpose: what sits in a\n * donut's hole is usually the chart's conclusion, so the scene lands it on\n * its own beat. Pair it with `showCenter: false`.\n */\n revealCenter: {\n args: {},\n easing: easeOut(),\n run: (timeline, _state, children) => partsOf(children)?.ring.run(\"revealCenter\") ?? timeline.wait(timeline.duration),\n },\n /** Grow slice `index` out past the ring's rim and dim the rest, leaving any other selected slice where it is. */\n selectSlice: {\n args: { sliceIndex: SLICE_ARG },\n easing: easeOut(),\n run: (timeline, state) => {\n const index = timeline.args.sliceIndex\n return popping(timeline, state, (i) => (i === index ? 1 : popAt(state, i)))\n },\n },\n /** Settle slice `index` back flush with the ring. */\n deselectSlice: {\n args: { sliceIndex: SLICE_ARG },\n easing: easeOut(),\n run: (timeline, state) => {\n const index = timeline.args.sliceIndex\n return popping(timeline, state, (i) => (i === index ? 0 : popAt(state, i)))\n },\n },\n /** Move the focus to slice `index` alone, settling whatever else was out. */\n selectOnlySlice: {\n args: { sliceIndex: SLICE_ARG },\n easing: easeOut(),\n run: (timeline, state) => {\n const index = timeline.args.sliceIndex\n return popping(timeline, state, (i) => (i === index ? 1 : 0))\n },\n },\n /** Settle every slice flush and bring the whole chart back to full. */\n clearSelection: {\n args: {},\n easing: easeOut(),\n run: (timeline, state) => popping(timeline, state, () => 0),\n },\n },\n})\n\nexport type PieChart = NodeOf<typeof PieChart>\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"pie-legend.js","sourceRoot":"","sources":["../../src/pie-chart/pie-legend.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n Flow,\r\n Paint,\r\n Rect,\r\n Shapes,\r\n Text,\r\n Types,\r\n component2d,\r\n textOps,\r\n type NodeOf,\r\n type RectProps,\r\n // Named so the column's emitted type reaches the Rect surface it spreads through the sdk, not core.\r\n type Alignment as _Alignment,\r\n type CornerRadiusResolved as _CornerRadiusResolved,\r\n type CornerStyleResolved as _CornerStyleResolved,\r\n type Fill as _Fill,\r\n type FillResolved as _FillResolved,\r\n type FlowMode as _FlowMode,\r\n type FlowResolved as _FlowResolved,\r\n type GapSize as _GapSize,\r\n type Node2D as _Node2D,\r\n type Node2DProps as _Node2DProps,\r\n type RectCornerRadius as _RectCornerRadius,\r\n type RectCornerStyle as _RectCornerStyle,\r\n type Shadow as _Shadow,\r\n type ShadowResolved as _ShadowResolved,\r\n type Stroke as _Stroke,\r\n type StrokeResolved as _StrokeResolved,\r\n type Vector2 as _Vector2,\r\n} from \"@motionscript/sdk\"\r\n\r\nimport { fresh } from \"../kit/entries\"\r\nimport { armed, arrival, arrivalOf, arrivalsEntry, beats, frameEase, type Arrival } from \"../kit/motion\"\r\nimport {\r\n DEFAULT_LEGEND,\r\n clampUnit,\r\n type LegendConfig,\r\n} from \"../kit/shared\"\r\nimport type { PieSlice } from \"./shared\"\r\n\r\nexport interface PieLegendProps extends RectProps {\r\n /** One entry per slice, in ring order. */\r\n slices: PieSlice[]\r\n /** Padding, caption style, gaps, and swatch geometry — see {@link LegendConfig}. */\r\n legend: LegendConfig\r\n /**\r\n * Per-slice opacity multiplier, by index — the same signal that dims the ring,\r\n * so an entry fades in step with the wedge it names. Missing entries read as 1.\r\n */\r\n dims: number[]\r\n}\r\n\r\n/** How far an entry starts to the right of its slot when it enters, in px. */\r\nconst SLIDE = 16\r\n\r\n/**\r\n * The legend of a {@link PieChart}: a **vertical-flow column** of entries, one per\r\n * slice, each pairing the slice's caption with its colour swatch.\r\n *\r\n * A column rather than the cartesian charts' row, because it sits *beside* the\r\n * ring rather than beneath the plot — a pie is as tall as it is wide, so the\r\n * space it leaves over is a vertical strip. It is otherwise the same entry: the\r\n * swatch carries the colour, the caption reads in the legend's neutral ink, and\r\n * both take the shared {@link LegendConfig}.\r\n *\r\n * There is no show/hide here. A pie's marks are its *rows*, and a row that\r\n * shouldn't be in the chart shouldn't be in the table — removing one would\r\n * silently re-cut every other wedge's share, which is a different chart rather\r\n * than the same chart with something hidden. Selection is what a pie has\r\n * instead, and it arrives through {@link PieLegendProps.dims}.\r\n */\r\nexport const PieLegend = component2d({\r\n key: \"pieLegend\",\r\n state: {\r\n ...Paint.state,\r\n ...Flow.state,\r\n ...Shapes.corners,\r\n slices: fresh<PieSlice[]>(() => []),\r\n legend: Types.custom<LegendConfig>(DEFAULT_LEGEND),\r\n dims: fresh<number[]>(() => []),\r\n arrivals: arrivalsEntry(),\r\n },\r\n // A column, for the reason `ChartLegend` gives for its row: a `Rect` given no\r\n // flow lays its children out `freeform`, every caption on top of the last.\r\n defaults: {\r\n flow: \"vertical\",\r\n align: \"centerLeft\",\r\n width: \"hug\",\r\n height: \"hug\",\r\n gap: (state) => state.legend.itemGap / 3,\r\n padding: (state) => state.legend.padding,\r\n },\r\n // One entry per slice, ordered top → bottom. The selection dim rides a wrapper\r\n // rather than the entry itself, so the entrance can animate the entry's own\r\n // opacity without the two fighting over one value — opacity multiplies down\r\n // the tree, which is exactly the composition wanted here.\r\n children: (state) => {\r\n const marker = state.legend.markerStyle\r\n return state.slices.map((_, index) =>\r\n Rect({\r\n width: \"hug\",\r\n height: \"hug\",\r\n flow: \"freeform\",\r\n opacity: () => clampUnit(state.dims[index] ?? 1),\r\n children: [\r\n Rect({\r\n flow: \"horizontal\",\r\n gap: 12,\r\n align: \"center\",\r\n width: \"hug\",\r\n height: \"hug\",\r\n opacity: () => arrivalOf(state.arrivals, index).opacity,\r\n x: () => arrivalOf(state.arrivals, index).shift,\r\n children: [\r\n Rect({\r\n width: marker.height,\r\n height: marker.height,\r\n cornerRadius: marker.borderRadius,\r\n fill: () => state.slices[index]?.fill ?? \"white\",\r\n stroke: marker.stroke,\r\n shadow: marker.shadow,\r\n }),\r\n Text({\r\n ...textOps.styleProps(state.legend.textStyle),\r\n text: () => state.slices[index]?.category ?? \"\",\r\n }),\r\n ],\r\n }),\r\n ],\r\n })\r\n )\r\n },\r\n commands: {\r\n ...Paint.commands,\r\n /**\r\n * Every entry at its pre-entrance state, shifted right and transparent: what\r\n * a chart shows ahead of this legend's own beat in its entrance, since\r\n * `enter` arms the entries only on its first frame.\r\n */\r\n arm: (timeline, state) =>\r\n timeline.animate(() => ({ arrivals: armed([...state.slices.keys()], { opacity: 0, shift: SLIDE }) })),\r\n /**\r\n * Slide + fade every entry in from the right, staggered top-to-bottom. Its\r\n * first frame arms them all: shifted right and transparent.\r\n *\r\n * The timeline's duration is the **whole** entrance: the stagger is carved\r\n * out of it rather than added on top, so the command takes exactly the time\r\n * the timeline laid out for it.\r\n */\r\n enter: (timeline, state) => {\r\n const count = state.slices.length\r\n if (count === 0) return timeline.wait(timeline.duration)\r\n const from: Arrival = { opacity: 0, shift: SLIDE }\r\n const arm = timeline.animate(() => ({ arrivals: armed([...state.slices.keys()], from) }))\r\n // Each gap is a fifth of an entry's own move, the rhythm the axes use;\r\n // solved for from the budget rather than added to it.\r\n const per = timeline.duration / (1 + Math.max(0, count - 1) / 5)\r\n const step = per / 5\r\n const ease = frameEase(timeline)\r\n const entries = state.slices.map((_, i) => ({ at: i * step, duration: per, piece: arrival(timeline, i, from, ease) }))\r\n return timeline.together([arm, beats(timeline, entries)])\r\n },\r\n },\r\n measure: (measurer, state, children) => Flow.measure(measurer, state, children, Paint.inset(state)),\r\n layout: (box, state, children) => Flow.layout(box, state, children, Paint.inset(state)),\r\n render: (ctx, state) => Paint.draw(ctx, state, Shapes.rect(ctx.size, state)),\r\n})\r\n\r\nexport type PieLegend = NodeOf<typeof PieLegend>\r\n"]}
1
+ {"version":3,"file":"pie-legend.js","sourceRoot":"","sources":["../../src/pie-chart/pie-legend.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Flow,\n Paint,\n Rect,\n Shapes,\n Text,\n Types,\n component2d,\n textOps,\n type NodeOf,\n type RectProps,\n // Named so the column's emitted type reaches the Rect surface it spreads through the sdk, not core.\n type Alignment as _Alignment,\n type CornerRadiusResolved as _CornerRadiusResolved,\n type CornerStyleResolved as _CornerStyleResolved,\n type Fill as _Fill,\n type FillResolved as _FillResolved,\n type FlowMode as _FlowMode,\n type FlowResolved as _FlowResolved,\n type GapSize as _GapSize,\n type Node2D as _Node2D,\n type Node2DProps as _Node2DProps,\n type RectCornerRadius as _RectCornerRadius,\n type RectCornerStyle as _RectCornerStyle,\n type Shadow as _Shadow,\n type ShadowResolved as _ShadowResolved,\n type Stroke as _Stroke,\n type StrokeResolved as _StrokeResolved,\n type Vector2 as _Vector2,\n} from \"@motionscript/sdk\"\n\nimport { fresh } from \"../kit/entries\"\nimport { armed, arrival, arrivalOf, arrivalsEntry, beats, frameEase, type Arrival } from \"../kit/motion\"\nimport {\n DEFAULT_LEGEND,\n clampUnit,\n type LegendConfig,\n} from \"../kit/shared\"\nimport type { PieSlice } from \"./shared\"\n\nexport interface PieLegendProps extends RectProps {\n /** One entry per slice, in ring order. */\n slices: PieSlice[]\n /** Padding, caption style, gaps, and swatch geometry — see {@link LegendConfig}. */\n legend: LegendConfig\n /**\n * Per-slice opacity multiplier, by index — the same signal that dims the ring,\n * so an entry fades in step with the wedge it names. Missing entries read as 1.\n */\n dims: number[]\n}\n\n/** How far an entry starts to the right of its slot when it enters, in px. */\nconst SLIDE = 16\n\n/**\n * The legend of a {@link PieChart}: a **vertical-flow column** of entries, one per\n * slice, each pairing the slice's caption with its colour swatch.\n *\n * A column rather than the cartesian charts' row, because it sits *beside* the\n * ring rather than beneath the plot — a pie is as tall as it is wide, so the\n * space it leaves over is a vertical strip. It is otherwise the same entry: the\n * swatch carries the colour, the caption reads in the legend's neutral ink, and\n * both take the shared {@link LegendConfig}.\n *\n * There is no show/hide here. A pie's marks are its *rows*, and a row that\n * shouldn't be in the chart shouldn't be in the table — removing one would\n * silently re-cut every other wedge's share, which is a different chart rather\n * than the same chart with something hidden. Selection is what a pie has\n * instead, and it arrives through {@link PieLegendProps.dims}.\n */\nexport const PieLegend = component2d({\n key: \"pieLegend\",\n state: {\n ...Paint.state,\n ...Flow.state,\n ...Shapes.corners,\n slices: fresh<PieSlice[]>(() => []),\n legend: Types.custom<LegendConfig>(DEFAULT_LEGEND),\n dims: fresh<number[]>(() => []),\n arrivals: arrivalsEntry(),\n },\n // A column, for the reason `ChartLegend` gives for its row: a `Rect` given no\n // flow lays its children out `freeform`, every caption on top of the last.\n defaults: {\n flow: \"vertical\",\n align: \"centerLeft\",\n width: \"hug\",\n height: \"hug\",\n gap: (state) => state.legend.itemGap / 3,\n padding: (state) => state.legend.padding,\n },\n // One entry per slice, ordered top → bottom. The selection dim rides a wrapper\n // rather than the entry itself, so the entrance can animate the entry's own\n // opacity without the two fighting over one value — opacity multiplies down\n // the tree, which is exactly the composition wanted here.\n children: (state) => {\n const marker = state.legend.markerStyle\n return state.slices.map((_, index) =>\n Rect({\n width: \"hug\",\n height: \"hug\",\n flow: \"freeform\",\n opacity: () => clampUnit(state.dims[index] ?? 1),\n children: [\n Rect({\n flow: \"horizontal\",\n gap: 12,\n align: \"center\",\n width: \"hug\",\n height: \"hug\",\n opacity: () => arrivalOf(state.arrivals, index).opacity,\n x: () => arrivalOf(state.arrivals, index).shift,\n children: [\n Rect({\n width: marker.height,\n height: marker.height,\n cornerRadius: marker.borderRadius,\n fill: () => state.slices[index]?.fill ?? \"white\",\n stroke: marker.stroke,\n shadow: marker.shadow,\n }),\n Text({\n ...textOps.styleProps(state.legend.textStyle),\n text: () => state.slices[index]?.category ?? \"\",\n }),\n ],\n }),\n ],\n })\n )\n },\n commands: {\n ...Paint.commands,\n /**\n * Every entry at its pre-entrance state, shifted right and transparent: what\n * a chart shows ahead of this legend's own beat in its entrance, since\n * `enter` arms the entries only on its first frame.\n */\n arm: (timeline, state) =>\n timeline.animate(() => ({ arrivals: armed([...state.slices.keys()], { opacity: 0, shift: SLIDE }) })),\n /**\n * Slide + fade every entry in from the right, staggered top-to-bottom. Its\n * first frame arms them all: shifted right and transparent.\n *\n * The timeline's duration is the **whole** entrance: the stagger is carved\n * out of it rather than added on top, so the command takes exactly the time\n * the timeline laid out for it.\n */\n enter: (timeline, state) => {\n const count = state.slices.length\n if (count === 0) return timeline.wait(timeline.duration)\n const from: Arrival = { opacity: 0, shift: SLIDE }\n const arm = timeline.animate(() => ({ arrivals: armed([...state.slices.keys()], from) }))\n // Each gap is a fifth of an entry's own move, the rhythm the axes use;\n // solved for from the budget rather than added to it.\n const per = timeline.duration / (1 + Math.max(0, count - 1) / 5)\n const step = per / 5\n const ease = frameEase(timeline)\n const entries = state.slices.map((_, i) => ({ at: i * step, duration: per, piece: arrival(timeline, i, from, ease) }))\n return timeline.together([arm, beats(timeline, entries)])\n },\n },\n measure: (measurer, state, children) => Flow.measure(measurer, state, children, Paint.inset(state)),\n layout: (box, state, children) => Flow.layout(box, state, children, Paint.inset(state)),\n render: (ctx, state) => Paint.draw(ctx, state, Shapes.rect(ctx.size, state)),\n})\n\nexport type PieLegend = NodeOf<typeof PieLegend>\n"]}