@motionscript/charts 0.1.0 → 0.1.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/bar-chart/bar-chart.d.ts +22 -93
  3. package/dist/bar-chart/bar-chart.d.ts.map +1 -1
  4. package/dist/bar-chart/bar-chart.js +48 -121
  5. package/dist/bar-chart/bar-chart.js.map +1 -1
  6. package/dist/bar-chart/category-axis.d.ts +11 -38
  7. package/dist/bar-chart/category-axis.d.ts.map +1 -1
  8. package/dist/bar-chart/category-axis.js +15 -38
  9. package/dist/bar-chart/category-axis.js.map +1 -1
  10. package/dist/bar-chart/chart-bars.d.ts +43 -74
  11. package/dist/bar-chart/chart-bars.d.ts.map +1 -1
  12. package/dist/bar-chart/chart-bars.js +31 -59
  13. package/dist/bar-chart/chart-bars.js.map +1 -1
  14. package/dist/bar-chart/index.d.ts +2 -10
  15. package/dist/bar-chart/index.d.ts.map +1 -1
  16. package/dist/bar-chart/index.js +0 -7
  17. package/dist/bar-chart/index.js.map +1 -1
  18. package/dist/bar-chart/shared.d.ts +27 -90
  19. package/dist/bar-chart/shared.d.ts.map +1 -1
  20. package/dist/bar-chart/shared.js +15 -54
  21. package/dist/bar-chart/shared.js.map +1 -1
  22. package/dist/browser/chunks/chunk-4SMRCU24.js +2 -0
  23. package/dist/browser/chunks/chunk-4SMRCU24.js.map +7 -0
  24. package/dist/browser/index.js +1 -1
  25. package/dist/browser/index.js.map +3 -3
  26. package/dist/browser/kit.js +1 -1
  27. package/dist/browser/manifest.json +1 -1
  28. package/dist/engine.d.ts +4 -16
  29. package/dist/engine.d.ts.map +1 -1
  30. package/dist/engine.js +4 -16
  31. package/dist/engine.js.map +1 -1
  32. package/dist/kit/cartesian.d.ts +8 -28
  33. package/dist/kit/cartesian.d.ts.map +1 -1
  34. package/dist/kit/cartesian.js +7 -19
  35. package/dist/kit/cartesian.js.map +1 -1
  36. package/dist/kit/chart-border.d.ts +9 -18
  37. package/dist/kit/chart-border.d.ts.map +1 -1
  38. package/dist/kit/chart-border.js +4 -9
  39. package/dist/kit/chart-border.js.map +1 -1
  40. package/dist/kit/chart-canvas.d.ts +25 -36
  41. package/dist/kit/chart-canvas.d.ts.map +1 -1
  42. package/dist/kit/chart-canvas.js +13 -13
  43. package/dist/kit/chart-canvas.js.map +1 -1
  44. package/dist/kit/chart-legend.d.ts +14 -61
  45. package/dist/kit/chart-legend.d.ts.map +1 -1
  46. package/dist/kit/chart-legend.js +17 -65
  47. package/dist/kit/chart-legend.js.map +1 -1
  48. package/dist/kit/entries.d.ts +2 -0
  49. package/dist/kit/entries.d.ts.map +1 -1
  50. package/dist/kit/entries.js +2 -0
  51. package/dist/kit/entries.js.map +1 -1
  52. package/dist/kit/motion.d.ts +11 -3
  53. package/dist/kit/motion.d.ts.map +1 -1
  54. package/dist/kit/motion.js +7 -1
  55. package/dist/kit/motion.js.map +1 -1
  56. package/dist/kit/shared.d.ts +60 -175
  57. package/dist/kit/shared.d.ts.map +1 -1
  58. package/dist/kit/shared.js +40 -157
  59. package/dist/kit/shared.js.map +1 -1
  60. package/dist/kit/vertical-axis.d.ts +16 -45
  61. package/dist/kit/vertical-axis.d.ts.map +1 -1
  62. package/dist/kit/vertical-axis.js +17 -42
  63. package/dist/kit/vertical-axis.js.map +1 -1
  64. package/dist/line-chart/chart-line.d.ts +16 -41
  65. package/dist/line-chart/chart-line.d.ts.map +1 -1
  66. package/dist/line-chart/chart-line.js +14 -43
  67. package/dist/line-chart/chart-line.js.map +1 -1
  68. package/dist/line-chart/chart-region.d.ts +14 -27
  69. package/dist/line-chart/chart-region.d.ts.map +1 -1
  70. package/dist/line-chart/chart-region.js +8 -22
  71. package/dist/line-chart/chart-region.js.map +1 -1
  72. package/dist/line-chart/horizontal-axis.d.ts +16 -43
  73. package/dist/line-chart/horizontal-axis.d.ts.map +1 -1
  74. package/dist/line-chart/horizontal-axis.js +13 -42
  75. package/dist/line-chart/horizontal-axis.js.map +1 -1
  76. package/dist/line-chart/index.d.ts +1 -13
  77. package/dist/line-chart/index.d.ts.map +1 -1
  78. package/dist/line-chart/index.js +0 -12
  79. package/dist/line-chart/index.js.map +1 -1
  80. package/dist/line-chart/line-chart.d.ts +23 -109
  81. package/dist/line-chart/line-chart.d.ts.map +1 -1
  82. package/dist/line-chart/line-chart.js +45 -141
  83. package/dist/line-chart/line-chart.js.map +1 -1
  84. package/dist/line-chart/shared.d.ts +33 -107
  85. package/dist/line-chart/shared.d.ts.map +1 -1
  86. package/dist/line-chart/shared.js +28 -81
  87. package/dist/line-chart/shared.js.map +1 -1
  88. package/dist/line-chart/x-scale.d.ts +14 -55
  89. package/dist/line-chart/x-scale.d.ts.map +1 -1
  90. package/dist/line-chart/x-scale.js +29 -111
  91. package/dist/line-chart/x-scale.js.map +1 -1
  92. package/dist/nodes.d.ts +0 -12
  93. package/dist/nodes.d.ts.map +1 -1
  94. package/dist/nodes.js +0 -12
  95. package/dist/nodes.js.map +1 -1
  96. package/dist/pie-chart/index.d.ts +1 -12
  97. package/dist/pie-chart/index.d.ts.map +1 -1
  98. package/dist/pie-chart/index.js +0 -9
  99. package/dist/pie-chart/index.js.map +1 -1
  100. package/dist/pie-chart/pie-chart.d.ts +37 -71
  101. package/dist/pie-chart/pie-chart.d.ts.map +1 -1
  102. package/dist/pie-chart/pie-chart.js +31 -65
  103. package/dist/pie-chart/pie-chart.js.map +1 -1
  104. package/dist/pie-chart/pie-legend.d.ts +11 -26
  105. package/dist/pie-chart/pie-legend.d.ts.map +1 -1
  106. package/dist/pie-chart/pie-legend.js +14 -39
  107. package/dist/pie-chart/pie-legend.js.map +1 -1
  108. package/dist/pie-chart/pie-ring.d.ts +50 -192
  109. package/dist/pie-chart/pie-ring.d.ts.map +1 -1
  110. package/dist/pie-chart/pie-ring.js +75 -247
  111. package/dist/pie-chart/pie-ring.js.map +1 -1
  112. package/dist/pie-chart/shared.d.ts +45 -131
  113. package/dist/pie-chart/shared.d.ts.map +1 -1
  114. package/dist/pie-chart/shared.js +29 -72
  115. package/dist/pie-chart/shared.js.map +1 -1
  116. package/package.json +4 -4
  117. package/src/bar-chart/bar-chart.ts +51 -165
  118. package/src/bar-chart/category-axis.ts +16 -57
  119. package/src/bar-chart/chart-bars.ts +43 -120
  120. package/src/bar-chart/index.ts +2 -10
  121. package/src/bar-chart/shared.ts +29 -97
  122. package/src/engine.ts +4 -16
  123. package/src/kit/cartesian.ts +11 -26
  124. package/src/kit/chart-border.ts +6 -20
  125. package/src/kit/chart-canvas.ts +15 -40
  126. package/src/kit/chart-legend.ts +17 -86
  127. package/src/kit/entries.ts +4 -1
  128. package/src/kit/motion.ts +11 -2
  129. package/src/kit/shared.ts +58 -219
  130. package/src/kit/vertical-axis.ts +18 -71
  131. package/src/line-chart/chart-line.ts +16 -44
  132. package/src/line-chart/chart-region.ts +10 -23
  133. package/src/line-chart/horizontal-axis.ts +13 -42
  134. package/src/line-chart/index.ts +1 -13
  135. package/src/line-chart/line-chart.ts +50 -184
  136. package/src/line-chart/shared.ts +51 -145
  137. package/src/line-chart/x-scale.ts +31 -121
  138. package/src/nodes.ts +0 -12
  139. package/src/pie-chart/index.ts +1 -12
  140. package/src/pie-chart/pie-chart.ts +34 -102
  141. package/src/pie-chart/pie-legend.ts +14 -55
  142. package/src/pie-chart/pie-ring.ts +80 -307
  143. package/src/pie-chart/shared.ts +50 -139
  144. package/dist/browser/chunks/chunk-TJNHLQGV.js +0 -2
  145. package/dist/browser/chunks/chunk-TJNHLQGV.js.map +0 -7
@@ -4,9 +4,9 @@ import {
4
4
  component2d,
5
5
  strokeOps,
6
6
  type NodeOf,
7
- // Named so this component's emitted type reaches `Node2D` through the sdk, not core.
7
+ // Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.
8
8
  type Node2D as _Node2D,
9
- type Node2DProps,
9
+ type Node2DProps as _Node2DProps,
10
10
  type Stroke,
11
11
  type StrokeResolved,
12
12
  } from "@motionscript/sdk"
@@ -14,35 +14,21 @@ import {
14
14
  import { loadPaints } from "./paints"
15
15
  import { DEFAULT_PLOT_AREA } from "./shared"
16
16
 
17
- export interface ChartBorderProps extends Node2DProps {
18
- /**
19
- * The plot's thick frame stroke. The stroke resolver's default `align` is
20
- * `'inside'`, so the whole weight sits within the layout box unless the theme
21
- * overrides it.
22
- */
23
- stroke: Stroke
24
- }
25
-
26
17
  /**
27
- * The thick **frame** around a cartesian chart's plot: an empty rect wearing an
28
- * inside stroke, sized from its box.
29
- *
30
- * Split out of {@link ChartCanvas} (the filled + gridded backdrop) so it can be
31
- * placed *after* the marks in the tree — the box then frames the plot **over**
32
- * them, so a line or a column running to the edge tucks cleanly under the border
33
- * rather than sitting on top of it.
18
+ * The frame around a cartesian plot: an empty rect with an inside stroke. Split
19
+ * from {@link ChartCanvas} so it is placed after the marks and frames the plot
20
+ * over them, tucking a line or column running to the edge under the border.
34
21
  */
35
22
  export const ChartBorder = component2d({
36
23
  key: "chartBorder",
37
24
  state: {
25
+ /** The stroke resolver's default `align` is `'inside'`, so the whole weight sits within the box unless the theme overrides it. */
38
26
  stroke: Types.custom<Stroke, StrokeResolved[]>(DEFAULT_PLOT_AREA.stroke, { resolve: strokeOps.resolve, lerp: strokeOps.lerp }),
39
27
  },
40
28
  load: (loader, state) => loadPaints(loader, { strokes: [state.stroke] }),
41
29
  render: (ctx, state) => {
42
30
  const { width, height } = ctx.size
43
31
  if (width <= 0 || height <= 0) return
44
- // Empty rect (no fill) with the themed stroke, so the frame sits over the
45
- // marks but the box interior stays clear.
46
32
  ctx.draw(new Graphics2D().rect({ x: 0, y: 0, width, height }).stroke(state.stroke))
47
33
  },
48
34
  })
@@ -8,9 +8,9 @@ import {
8
8
  type Fill,
9
9
  type FillResolved,
10
10
  type NodeOf,
11
- // Named so this component's emitted type reaches `Node2D` through the sdk, not core.
11
+ // Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.
12
12
  type Node2D as _Node2D,
13
- type Node2DProps,
13
+ type Node2DProps as _Node2DProps,
14
14
  type Shadow,
15
15
  type ShadowResolved,
16
16
  type Stroke,
@@ -26,44 +26,14 @@ import {
26
26
  type Range,
27
27
  } from "./shared"
28
28
 
29
- export interface ChartCanvasProps extends Node2DProps {
30
- /** Fill of the plot panel behind the marks. */
31
- fill: Fill
32
- /** Drop shadow of the panel. Default none. */
33
- shadow: Shadow
34
- /** Stroke of the vertical grid lines (one per {@link xMarkers}). */
35
- xGridStroke: Stroke
36
- /** Stroke of the horizontal grid lines (one per {@link yMarkers}). */
37
- yGridStroke: Stroke
38
- /**
39
- * Data-unit x positions to draw a vertical grid line at — one per horizontal
40
- * axis marker, so a line lands under every marker regardless of spacing.
41
- * Positions at (or past) an {@link xExtent} edge coincide with the border and
42
- * are skipped. Pass an empty list for a chart whose horizontal axis is
43
- * categorical and so has no numeric position to rule at.
44
- */
45
- xMarkers: number[]
46
- /** Data-unit y positions to draw a horizontal grid line at. */
47
- yMarkers: number[]
48
- /** `[min, max]` of the horizontal scale — maps {@link xMarkers} into the panel. */
49
- xExtent: Range
50
- /** `[min, max]` of the vertical scale — maps {@link yMarkers} into the panel. */
51
- yExtent: Range
52
- }
53
-
54
29
  const strokeEntry = (initial: Stroke) =>
55
30
  Types.custom<Stroke, StrokeResolved[]>(initial, { resolve: strokeOps.resolve, lerp: strokeOps.lerp })
56
31
 
57
32
  /**
58
- * The plot **backdrop** of a cartesian chart: a filled panel with a grid of
59
- * faint lines spanning both directions across it, sized from its box and centred
60
- * on its origin.
61
- *
62
- * The thick frame is a **separate** {@link ChartBorder} node so it can be drawn
63
- * *over* the marks while this panel sits under them. Grid lines are placed from
64
- * the axis marker positions (in data units) mapped into the panel through
65
- * {@link ChartCanvasProps.xExtent}/`yExtent` — so each line lands under its
66
- * marker whether or not the markers are evenly spaced.
33
+ * The plot backdrop: a filled panel and a grid, sized from its box. The frame is
34
+ * a separate {@link ChartBorder} drawn over the marks. Grid lines are placed from
35
+ * the axis markers' data-unit positions mapped through the extents, so each lands
36
+ * under its marker however the markers are spaced.
67
37
  */
68
38
  export const ChartCanvas = component2d({
69
39
  key: "chartCanvas",
@@ -72,27 +42,32 @@ export const ChartCanvas = component2d({
72
42
  shadow: Types.custom<Shadow, ShadowResolved[]>(DEFAULT_PLOT_AREA.shadow, { resolve: shadowOps.resolve, lerp: shadowOps.lerp }),
73
43
  xGridStroke: strokeEntry(DEFAULT_X_AXIS.lineStyle),
74
44
  yGridStroke: strokeEntry(DEFAULT_Y_AXIS.lineStyle),
45
+ /**
46
+ * Data-unit positions of the vertical grid lines, one per horizontal marker.
47
+ * Positions at or past an extent edge coincide with the border and are
48
+ * skipped. Empty for a categorical axis, which has no numeric position to rule at.
49
+ */
75
50
  xMarkers: fresh<number[]>(() => []),
76
51
  yMarkers: fresh<number[]>(() => []),
52
+ /** `[min, max]` of each scale, mapping the markers into the panel. */
77
53
  xExtent: fresh<Range>(() => [0, 1]),
78
54
  yExtent: fresh<Range>(() => [0, 1]),
79
55
  },
80
- // The grid strokes are named past the conventional slots, so they are named here.
56
+ // The grid strokes sit past the conventional slots, so they are named here.
81
57
  load: (loader, state) =>
82
58
  loadPaints(loader, { fills: [state.fill], shadows: [state.shadow], strokes: [state.xGridStroke, state.yGridStroke] }),
83
59
  render: (ctx, state) => {
84
60
  const { width: W, height: H } = ctx.size
85
61
  if (W <= 0 || H <= 0) return
86
62
 
87
- // Plot panel, centred on the origin (node space is y-up, origin centre).
88
63
  ctx.draw(new Graphics2D().rect({ x: 0, y: 0, width: W, height: H }).shadow(state.shadow).fill(state.fill))
89
64
 
90
65
  const [xMin, xMax] = state.xExtent
91
66
  const [yMin, yMax] = state.yExtent
92
- // Fraction of a marker across its extent; guard a zero-width extent.
67
+ // A zero-width extent maps to 0.
93
68
  const xFrac = (v: number) => (xMax <= xMin ? 0 : (v - xMin) / (xMax - xMin))
94
69
  const yFrac = (v: number) => (yMax <= yMin ? 0 : (v - yMin) / (yMax - yMin))
95
- // Skip markers landing on (or outside) the edges — they'd sit under the border.
70
+ // Markers on or past the edges would sit under the border.
96
71
  const interior = (f: number) => f > 1e-6 && f < 1 - 1e-6
97
72
 
98
73
  const xStroke = state.xGridStroke[0]
@@ -9,7 +9,6 @@ import {
9
9
  textOps,
10
10
  type Fill,
11
11
  type NodeOf,
12
- type RectProps,
13
12
  // Named so the row's emitted type reaches the Rect surface it spreads through the sdk, not core.
14
13
  type Alignment as _Alignment,
15
14
  type CornerRadiusResolved as _CornerRadiusResolved,
@@ -38,36 +37,13 @@ import {
38
37
  type LegendConfig,
39
38
  } from "./shared"
40
39
 
41
- export interface ChartLegendProps extends RectProps {
42
- /**
43
- * One entry per series: legend `label`, its `enabled` default, and — through
44
- * {@link ChartLegendProps.swatches} — the colour its chip is painted. A series
45
- * that starts enabled (the default) is added to the row up front; one that
46
- * starts `false` is hidden until a later `showSeries`.
47
- */
48
- series: ChartSeries[]
49
- /**
50
- * The swatch colour for each series, by index — resolved by the chart, whose
51
- * business it is whether a series' colour comes out of a stroke palette (a
52
- * line) or a fill palette (a bar). Shorter than the series list means the
53
- * missing entries fall back to white.
54
- */
55
- swatches: Fill[]
56
- /** Padding, caption style, gaps, and swatch geometry — see {@link LegendConfig}. */
57
- legend: LegendConfig
58
- }
59
-
60
- /** A row's entries are a `Rect`'s: paint, the flow of its children, its corners. */
61
40
  const row = { ...Paint.state, ...Flow.state, ...Shapes.corners }
62
41
 
63
42
  /**
64
- * One legend entry. It hugs its caption and swatch; `shown` scales that width
65
- * from nothing to the whole of it.
66
- *
67
- * The width is taken from the content on every measure, at any reveal, so a frame
68
- * never depends on whether the entry was once seen fully shown. A collapse needs a
69
- * number to scale, and `"hug"` is not one until something measures it — this is
70
- * the something.
43
+ * One legend entry, hugging its caption and swatch; `shown` scales that width
44
+ * from nothing. The width is measured from the content at any reveal, so a frame
45
+ * never depends on the entry once having been fully shown (`"hug"` is not a
46
+ * number to scale until something measures it).
71
47
  */
72
48
  const LegendEntry = component2d({
73
49
  key: "legendEntry",
@@ -81,11 +57,7 @@ const LegendEntry = component2d({
81
57
  render: (ctx, state) => Paint.draw(ctx, state, Shapes.rect(ctx.size, state)),
82
58
  })
83
59
 
84
- /**
85
- * How far each entry is shown, written whole (a document, a `set`) or one
86
- * entry at a time (a command, as `{ [index]: reveal }`), which leaves the others
87
- * as the frame left them — so two series moving at once each move their own.
88
- */
60
+ /** Written whole, or one entry at a time as `{ [index]: reveal }`, which leaves the others as the frame left them so two series can move at once. */
89
61
  function revealOf(value: readonly number[] | Readonly<Record<number, number>>, previous?: number[]): number[] {
90
62
  if (Array.isArray(value)) return value as number[]
91
63
  const next = [...(previous ?? [])]
@@ -93,42 +65,23 @@ function revealOf(value: readonly number[] | Readonly<Record<number, number>>, p
93
65
  return next
94
66
  }
95
67
 
96
- /**
97
- * The legend of a chart: a **horizontal-flow row of entries**, one per series, laid out
98
- * left-to-right with the theme's `itemGap`. Each entry pairs the series label
99
- * with a rounded colour swatch; only the swatch carries the series colour, the
100
- * caption reads in the legend's neutral ink.
101
- *
102
- * `showSeries`/`hideSeries` flip a series on or off — showing one grows its
103
- * entry back in at its stable series position, hiding one shrinks it away. So
104
- * the row only ever shows the currently-enabled series, in series order, and
105
- * closes the gap when one leaves.
106
- *
107
- * This node *is* the row, so the parent places it (centred below the chart) and
108
- * it hugs its entries.
109
- */
68
+ /** A chart's legend: a horizontal row with one entry per series, each a caption and a colour swatch. This node is the row, so the parent places it. */
110
69
  export const ChartLegend = component2d({
111
70
  key: "chartLegend",
112
71
  state: {
113
72
  ...row,
114
73
  series: fresh<ChartSeries[]>(() => []),
74
+ /** Swatch colour by series index, resolved by the chart; a missing entry is white. */
115
75
  swatches: fresh<Fill[]>(() => []),
116
76
  legend: Types.custom<LegendConfig>(DEFAULT_LEGEND),
117
77
  /**
118
- * How far each entry is shown, `0`–`1`. Drives its width, opacity and gap
119
- * together, so `0` is an entry that takes no room and `1` is one at its
120
- * natural size.
121
- *
122
- * This replaces adding and removing the entry from the row: a command is
123
- * asked what it looks like at a time, and "is this node a child" is not a
124
- * value a time can produce. Collapsed-but-present is the same picture reached
125
- * by a route that *is* a function of `t`.
78
+ * How far each entry is shown, `0`-`1`, driving its width, opacity and gap
79
+ * together. Entries are never added or removed: "is this node a child" is not
80
+ * a function of time, while collapsed-but-present is.
126
81
  */
127
82
  reveal: fresh<number[], number[] | Readonly<Record<number, number>>>(() => [], { resolve: revealOf }),
128
83
  },
129
- // This node *is* the row, so the direction is its own answer. It has to be
130
- // stated — a `Rect` given no flow lays its children out `freeform`, which
131
- // stacks every entry on the same spot. The gap and padding are the theme's.
84
+ // The flow must be stated: a `Rect` with none lays children out `freeform`, stacking every entry on one spot.
132
85
  defaults: {
133
86
  flow: "horizontal",
134
87
  width: "hug",
@@ -137,9 +90,7 @@ export const ChartLegend = component2d({
137
90
  padding: (state) => state.legend.padding,
138
91
  reveal: (state) => state.series.map((s) => ((s.enabled ?? true) ? 1 : 0)),
139
92
  },
140
- // One entry per series, in series order, every one a child for good: order is
141
- // fixed rather than depending on which siblings happen to be shown, so an entry
142
- // always comes back where it belongs.
93
+ // Every entry stays a child, so one always comes back where it belongs.
143
94
  children: (state) => {
144
95
  const marker = state.legend.markerStyle
145
96
  return state.series.map((series, index) =>
@@ -149,9 +100,7 @@ export const ChartLegend = component2d({
149
100
  align: "center",
150
101
  width: "hug",
151
102
  height: "hug",
152
- // Bound to the reveal, so showing and hiding is a number moving rather
153
- // than a child arriving and leaving. The width follows it in `measure`,
154
- // and the flanking gap closes with the entry rather than snapping.
103
+ // The width follows the reveal in `measure`, and the flanking gap closes with the entry.
155
104
  opacity: () => state.reveal[index] ?? 0,
156
105
  gapScale: () => state.reveal[index] ?? 0,
157
106
  shown: () => state.reveal[index] ?? 0,
@@ -171,11 +120,7 @@ export const ChartLegend = component2d({
171
120
  },
172
121
  commands: {
173
122
  ...Paint.commands,
174
- /**
175
- * Grow series `index`'s entry back in at its series-order position, the
176
- * siblings sliding over to make room. A reveal tween rather than an animated
177
- * `addChildAt`: the entry never left, so the whole thing is a function of `t`.
178
- */
123
+ /** Grow series `index`'s entry back in at its position, the siblings sliding over. */
179
124
  showSeries: {
180
125
  args: { index: Types.number(0, { min: 0, step: 1 }) },
181
126
  // An index that is not a series takes its slot and moves nothing.
@@ -183,20 +128,14 @@ export const ChartLegend = component2d({
183
128
  ? timeline.animate(revealFrame(state, timeline.args.index, 1, frameEase(timeline)))
184
129
  : timeline.wait(timeline.duration)),
185
130
  },
186
- /** Shrink series `index`'s entry away, the gap closing behind it — the mirror of `showSeries`. */
131
+ /** The mirror of `showSeries`. */
187
132
  hideSeries: {
188
133
  args: { index: Types.number(0, { min: 0, step: 1 }) },
189
134
  run: (timeline, state) => (timeline.args.index in state.series
190
135
  ? timeline.animate(revealFrame(state, timeline.args.index, 0, frameEase(timeline)))
191
136
  : timeline.wait(timeline.duration)),
192
137
  },
193
- /**
194
- * Dim every entry but series `index` to {@link SPOTLIGHT_DIM} and bring that
195
- * one back to full — the legend half of a chart's `spotlightSeries`. Entries
196
- * stay in place; only their opacity moves, so the row doesn't reflow. Only
197
- * the entries shown when it is built are touched: a hidden series' entry is
198
- * left as `hideSeries` left it.
199
- */
138
+ /** Dim every entry but `index` to {@link SPOTLIGHT_DIM}, in place. A hidden series' entry is left as `hideSeries` left it. */
200
139
  spotlightSeries: {
201
140
  args: { index: Types.number(0, { min: 0, step: 1 }) },
202
141
  run: (timeline, state, children) =>
@@ -212,18 +151,10 @@ export const ChartLegend = component2d({
212
151
  render: (ctx, state) => Paint.draw(ctx, state, Shapes.rect(ctx.size, state)),
213
152
  })
214
153
 
215
- /**
216
- * One entry's reveal ramping to `target` from where the command finds it — so a
217
- * hide that follows a show starts where the show left it.
218
- */
154
+ /** One entry's reveal ramping to `target` from where the command finds it, so a hide after a show starts where the show left it. */
219
155
  function revealFrame(state: { readonly reveal: readonly number[] }, index: number, target: number, ease: (t: number) => number) {
220
156
  const from = state.reveal[index] ?? 0
221
157
  return (t: number) => ({ reveal: { [index]: from + (target - from) * ease(t) } })
222
158
  }
223
159
 
224
- /** Whether series `index` is showing at all on `legend`. */
225
- export function legendShows(legend: { readonly reveal: readonly number[] }, index: number): boolean {
226
- return (legend.reveal[index] ?? 0) > 0
227
- }
228
-
229
160
  export type ChartLegend = NodeOf<typeof ChartLegend>
@@ -1,4 +1,4 @@
1
- import { Types, type Element, type Node2D, type Type } from "@motionscript/sdk"
1
+ import { Types, type Element, type EntryOptions, type Node2D, type Type } from "@motionscript/sdk"
2
2
 
3
3
  /**
4
4
  * An entry made fresh for every node: each node gets its own array or box
@@ -18,3 +18,6 @@ export function fresh<T, In = T>(make: () => T, options?: { readonly resolve?: (
18
18
  export function asParts(nodes: readonly Node2D[]): readonly Element[] {
19
19
  return nodes as unknown as readonly Element[]
20
20
  }
21
+
22
+ /** The argument every per-series command takes: an index into `series`, offered by name. */
23
+ export const SERIES_ARG = Types.custom<number, number, EntryOptions & { readonly min: number; readonly step: number }>(0, { kind: "series", min: 0, step: 1 })
package/src/kit/motion.ts CHANGED
@@ -3,7 +3,16 @@ import { Types, easeOut, type EasingFunction, type Piece, type Timeline } from "
3
3
  const identity: EasingFunction = (t) => t
4
4
 
5
5
  /** Any component's timeline: these helpers only build pieces, which every timeline builds alike. */
6
- type AnyTimeline = Timeline<any, any>
6
+ export type AnyTimeline = Timeline<any, any>
7
+
8
+ /** How far a legend starts below its slot when `enter` brings it in, in px. */
9
+ export const LEGEND_SLIDE = 20
10
+
11
+ /** Marker count when an axis states no ticks. */
12
+ export const DEFAULT_MARKERS = 5
13
+
14
+ /** How a cartesian `enter` splits its duration: axes arriving, marks drawing, legend coming up. Sums to 1. */
15
+ export const ENTER_SHARE = { axes: 0.35, marks: 0.5, legend: 0.15 } as const
7
16
 
8
17
  /**
9
18
  * The ease a part's own frames apply to `t`: none when its command was given
@@ -49,7 +58,7 @@ export interface Arrival {
49
58
  /** Each piece's arrival, by key. An unlisted piece has landed. */
50
59
  export type Arrivals = Readonly<Record<string, Arrival>>
51
60
 
52
- export const LANDED: Arrival = { opacity: 1, shift: 0 }
61
+ const LANDED: Arrival = { opacity: 1, shift: 0 }
53
62
 
54
63
  /**
55
64
  * A part's private record of its pieces' arrivals. A write merges into it, so