@motionscript/charts 0.1.1-alpha.1 → 0.1.1-alpha.3

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 (81) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +21 -12
  3. package/dist/bar-chart/bar-chart.d.ts +113 -81
  4. package/dist/bar-chart/bar-chart.d.ts.map +1 -1
  5. package/dist/bar-chart/bar-chart.js +9 -5
  6. package/dist/bar-chart/bar-chart.js.map +1 -1
  7. package/dist/bar-chart/chart-bars.d.ts +29 -5
  8. package/dist/bar-chart/chart-bars.d.ts.map +1 -1
  9. package/dist/bar-chart/chart-bars.js +33 -8
  10. package/dist/bar-chart/chart-bars.js.map +1 -1
  11. package/dist/bar-chart/shared.d.ts +10 -4
  12. package/dist/bar-chart/shared.d.ts.map +1 -1
  13. package/dist/bar-chart/shared.js +12 -5
  14. package/dist/bar-chart/shared.js.map +1 -1
  15. package/dist/browser/chunks/chunk-FJHNBNPQ.js +2 -0
  16. package/dist/browser/chunks/chunk-FJHNBNPQ.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/kit/cartesian.d.ts +6 -4
  22. package/dist/kit/cartesian.d.ts.map +1 -1
  23. package/dist/kit/cartesian.js +5 -3
  24. package/dist/kit/cartesian.js.map +1 -1
  25. package/dist/kit/style.d.ts +344 -76
  26. package/dist/kit/style.d.ts.map +1 -1
  27. package/dist/kit/style.js +198 -101
  28. package/dist/kit/style.js.map +1 -1
  29. package/dist/line-chart/line-chart.d.ts +97 -73
  30. package/dist/line-chart/line-chart.d.ts.map +1 -1
  31. package/dist/line-chart/line-chart.js +9 -7
  32. package/dist/line-chart/line-chart.js.map +1 -1
  33. package/dist/line-chart/shared.d.ts +2 -1
  34. package/dist/line-chart/shared.d.ts.map +1 -1
  35. package/dist/line-chart/shared.js +2 -0
  36. package/dist/line-chart/shared.js.map +1 -1
  37. package/dist/pie-chart/pie-chart.d.ts +43 -49
  38. package/dist/pie-chart/pie-chart.d.ts.map +1 -1
  39. package/dist/pie-chart/pie-chart.js +2 -1
  40. package/dist/pie-chart/pie-chart.js.map +1 -1
  41. package/dist/pie-chart/shared.d.ts +15 -10
  42. package/dist/pie-chart/shared.d.ts.map +1 -1
  43. package/dist/pie-chart/shared.js +18 -20
  44. package/dist/pie-chart/shared.js.map +1 -1
  45. package/dist/radar-chart/radar-chart.d.ts +37 -49
  46. package/dist/radar-chart/radar-chart.d.ts.map +1 -1
  47. package/dist/radar-chart/radar-chart.js +10 -11
  48. package/dist/radar-chart/radar-chart.js.map +1 -1
  49. package/dist/radar-chart/radar-grid.d.ts +7 -1
  50. package/dist/radar-chart/radar-grid.d.ts.map +1 -1
  51. package/dist/radar-chart/radar-grid.js +17 -15
  52. package/dist/radar-chart/radar-grid.js.map +1 -1
  53. package/dist/radar-chart/shared.d.ts +4 -2
  54. package/dist/radar-chart/shared.d.ts.map +1 -1
  55. package/dist/radar-chart/shared.js +3 -0
  56. package/dist/radar-chart/shared.js.map +1 -1
  57. package/dist/radial-chart/radial-chart.d.ts +37 -49
  58. package/dist/radial-chart/radial-chart.d.ts.map +1 -1
  59. package/dist/radial-chart/radial-chart.js +2 -1
  60. package/dist/radial-chart/radial-chart.js.map +1 -1
  61. package/dist/radial-chart/shared.d.ts +13 -11
  62. package/dist/radial-chart/shared.d.ts.map +1 -1
  63. package/dist/radial-chart/shared.js +12 -16
  64. package/dist/radial-chart/shared.js.map +1 -1
  65. package/package.json +4 -4
  66. package/src/bar-chart/bar-chart.ts +17 -6
  67. package/src/bar-chart/chart-bars.ts +48 -9
  68. package/src/bar-chart/shared.ts +23 -5
  69. package/src/kit/cartesian.ts +10 -7
  70. package/src/kit/style.ts +216 -118
  71. package/src/line-chart/line-chart.ts +14 -8
  72. package/src/line-chart/shared.ts +3 -1
  73. package/src/pie-chart/pie-chart.ts +3 -1
  74. package/src/pie-chart/shared.ts +26 -22
  75. package/src/radar-chart/radar-chart.ts +14 -13
  76. package/src/radar-chart/radar-grid.ts +18 -13
  77. package/src/radar-chart/shared.ts +5 -2
  78. package/src/radial-chart/radial-chart.ts +3 -1
  79. package/src/radial-chart/shared.ts +26 -18
  80. package/dist/browser/chunks/chunk-R3Q5EE6E.js +0 -2
  81. package/dist/browser/chunks/chunk-R3Q5EE6E.js.map +0 -7
package/src/kit/style.ts CHANGED
@@ -2,11 +2,15 @@
2
2
  * The chart family's style: one `style` entry per chart, built from the groups
3
3
  * below, so a look saved off one chart lays onto any other (a field the other
4
4
  * chart does not draw is dropped). A chart spreads the shared groups and adds a
5
- * `marks` group of its own.
5
+ * `marks` group of its own. Every run of text a chart draws is its own
6
+ * `Types.typography` field, set the way a text node is, in the group of the
7
+ * part it labels: axis text with its axis, values with the labels, the
8
+ * legend's captions with the legend.
6
9
  */
7
10
 
8
11
  import {
9
12
  Types,
13
+ textOps,
10
14
  type Fill,
11
15
  type StrokeResolved,
12
16
  type Stroke as _Stroke,
@@ -19,29 +23,36 @@ import {
19
23
 
20
24
  import type { AxisConfig, ChartSeries, LegendConfig } from "./shared"
21
25
 
22
- /** The style family every chart shares; a host keys saved styles by it. */
26
+ /** The style family every chart shares; the built-in presets are written for it. */
23
27
  export const CHART_STYLE_FAMILY = "chart"
24
28
 
25
- /** The out-of-the-box series colours, cycled by series (or row) index. */
29
+ /** The out-of-the-box series fills, cycled by series (or row) index. */
26
30
  export const CHART_PALETTE_DEFAULT = ["#4f8cff", "#2fbf8f", "#f5a524", "#e5547a", "#9b6cf0"] as const
27
31
 
28
- export const CHART_COLORS = {
29
- /** A colour per series (a pie's or radial's per row), cycled. */
30
- palette: Types.palette(CHART_PALETTE_DEFAULT),
31
- /** Values, legend captions, centre text. */
32
- foreground: Types.fill("#f4f4f5"),
33
- /** Axis markers and captions, spoke labels. */
34
- muted: Types.fill("#a1a1aa"),
35
- /** Behind the plot; none by default, so the chart reads against the stage. */
32
+ /** The default ink of values, legend captions and centre text, on the dark stage. */
33
+ export const CHART_FOREGROUND = "#f4f4f5"
34
+ /** The default ink of axis markers, axis captions and spoke labels. */
35
+ export const CHART_MUTED = "#a1a1aa"
36
+
37
+ export const CHART_SERIES = {
38
+ /** A fill per series (a pie's or radial's per row), cycled; each may stack several paints. */
39
+ seriesFills: Types.palette(CHART_PALETTE_DEFAULT),
40
+ }
41
+
42
+ export const CHART_BACKGROUND = {
43
+ /** Behind the whole chart, legend and axes included; none by default, so the chart reads against the stage. */
36
44
  background: Types.fill([]),
37
45
  }
38
46
 
39
- export const CHART_TEXT = {
40
- /** Empty: the scene's default face. */
41
- fontFamily: Types.font(""),
42
- /** Marker and legend size at 1080p; titles and values scale from it. */
43
- fontSize: Types.number(28, { min: 8, max: 160, step: 1, unit: "px" }),
44
- fontWeight: Types.number(500, { min: 100, max: 900, step: 100 }),
47
+ export const GRID_BACKGROUND = {
48
+ ...CHART_BACKGROUND,
49
+ /** Behind the plot only, under the grid lines; none by default. */
50
+ gridBackground: Types.fill([]),
51
+ }
52
+
53
+ /** The figures printed by the marks, for a chart's `labels` group. */
54
+ export const VALUE_TEXT = {
55
+ valueText: Types.typography({ fontSize: 24, fontWeight: 600, fill: CHART_FOREGROUND }),
45
56
  }
46
57
 
47
58
  export const CARTESIAN_GRID = {
@@ -49,12 +60,23 @@ export const CARTESIAN_GRID = {
49
60
  horizontalGrid: Types.stroke({ weight: 2, fill: "white/15" }),
50
61
  /** The lines under the category markers; empty draws none. */
51
62
  verticalGrid: Types.stroke([]),
63
+ }
64
+
65
+ export const CARTESIAN_AXES = {
52
66
  /** The baseline and the value-axis rule; empty draws none. */
53
67
  axisStroke: Types.stroke([]),
54
68
  /** Read as the chart is built: whether the category (x) axis row is drawn. */
55
69
  showXAxis: Types.boolean(true, { animatable: false }),
70
+ /** The markers under the plot: categories on a column chart, values on a horizontal bar chart. */
71
+ xLabelText: Types.typography({ fontSize: 28, fontWeight: 500, fill: CHART_MUTED }),
72
+ /** The caption under the plot. */
73
+ xTitleText: Types.typography({ fontSize: 31, fontWeight: 600, fill: CHART_MUTED }),
56
74
  /** Read as the chart is built: whether the value (y) axis column is drawn. */
57
75
  showYAxis: Types.boolean(true, { animatable: false }),
76
+ /** The markers beside the plot. */
77
+ yLabelText: Types.typography({ fontSize: 28, fontWeight: 500, fill: CHART_MUTED }),
78
+ /** The caption beside the plot, turned on its side. */
79
+ yTitleText: Types.typography({ fontSize: 31, fontWeight: 600, fill: CHART_MUTED }),
58
80
  }
59
81
 
60
82
  export type LegendPosition = "bottom" | "top" | "right" | "none"
@@ -64,27 +86,27 @@ export const CHART_LEGEND = {
64
86
  /** Read as the chart is built: where the legend sits, or `none`. */
65
87
  legend: Types.oneOf(["bottom", "top", "right", "none"] as const, { animatable: false }),
66
88
  swatch: Types.oneOf(["square", "circle", "line"] as const),
67
- }
68
-
69
- /** The type fields, and the two text colours: what every text helper reads. */
70
- export interface ChartTextStyle {
71
- readonly foreground: Fill
72
- readonly muted: Fill
73
- readonly fontFamily: string
74
- readonly fontSize: number
75
- readonly fontWeight: number
89
+ /** The legend's captions. */
90
+ legendText: Types.typography({ fontSize: 28, fontWeight: 500, fill: CHART_FOREGROUND }),
76
91
  }
77
92
 
78
93
  /** The fields every chart's style resolves, whichever groups it spreads. */
79
- export interface ChartStyleBase extends ChartTextStyle {
80
- readonly palette: readonly Fill[]
94
+ export interface ChartStyleBase {
95
+ readonly seriesFills: readonly Fill[]
81
96
  readonly background: Fill
97
+ readonly legendText: TextStyle
82
98
  readonly legend: LegendPosition
83
99
  readonly swatch: LegendSwatch
84
100
  }
85
101
 
86
- /** The cartesian grid fields, for the line and bar charts. */
102
+ /** The cartesian fields, for the line and bar charts. */
87
103
  export interface CartesianStyle extends ChartStyleBase {
104
+ readonly gridBackground: Fill
105
+ readonly xLabelText: TextStyle
106
+ readonly xTitleText: TextStyle
107
+ readonly yLabelText: TextStyle
108
+ readonly yTitleText: TextStyle
109
+ readonly valueText: TextStyle
88
110
  readonly horizontalGrid: StrokeResolved[]
89
111
  readonly verticalGrid: StrokeResolved[]
90
112
  readonly axisStroke: StrokeResolved[]
@@ -92,8 +114,16 @@ export interface CartesianStyle extends ChartStyleBase {
92
114
  readonly showYAxis: boolean
93
115
  }
94
116
 
95
- /** The rule and track fields a preset restates so its rules read on its background (a chart keeps those it draws). */
96
- const _PRESET_RULES = {
117
+ /** The fields a preset restates so its rules and text read on its background (a chart keeps those it draws). */
118
+ const _PRESET_FIELDS = {
119
+ ...CHART_SERIES,
120
+ ...GRID_BACKGROUND,
121
+ ...CARTESIAN_AXES,
122
+ ...VALUE_TEXT,
123
+ legendText: CHART_LEGEND.legendText,
124
+ labelText: Types.typography(),
125
+ centerText: Types.typography(),
126
+ spokeText: Types.typography(),
97
127
  horizontalGrid: CARTESIAN_GRID.horizontalGrid,
98
128
  gridStroke: Types.stroke([]),
99
129
  spokeStroke: Types.stroke([]),
@@ -101,147 +131,154 @@ const _PRESET_RULES = {
101
131
  }
102
132
 
103
133
  /** A preset's fields: any chart's, so one preset dresses every chart. */
104
- export type ChartStylePreset = StyleInput<typeof CHART_COLORS & typeof CHART_TEXT & typeof _PRESET_RULES>
134
+ export type ChartStylePreset = StyleInput<typeof _PRESET_FIELDS>
135
+
136
+ /** Every text field's ink: `foreground` for values, the legend and centre text, `muted` for markers and captions. */
137
+ function inks(foreground: string, muted: string) {
138
+ const on = (fill: string) => ({ fill })
139
+ return {
140
+ xLabelText: on(muted),
141
+ xTitleText: on(muted),
142
+ yLabelText: on(muted),
143
+ yTitleText: on(muted),
144
+ valueText: on(foreground),
145
+ legendText: on(foreground),
146
+ labelText: on(muted),
147
+ centerText: on(foreground),
148
+ spokeText: on(muted),
149
+ } satisfies ChartStylePreset
150
+ }
151
+
152
+ /** Every rule a chart draws, and a radial's track, in one paint. */
153
+ function rules(fill: string) {
154
+ return {
155
+ horizontalGrid: { weight: 2, fill },
156
+ gridStroke: { weight: 2, fill },
157
+ spokeStroke: { weight: 2, fill },
158
+ track: fill,
159
+ } satisfies ChartStylePreset
160
+ }
105
161
 
106
162
  /** Ready-made looks, each stating every colour so applying one replaces the last. */
107
163
  export const CHART_STYLE_PRESETS = {
108
164
  midnight: {
109
- palette: [...CHART_PALETTE_DEFAULT],
110
- foreground: "#f4f4f5",
111
- muted: "#a1a1aa",
112
- horizontalGrid: { weight: 2, fill: "white/15" },
113
- gridStroke: { weight: 2, fill: "white/15" },
114
- spokeStroke: { weight: 2, fill: "white/15" },
165
+ seriesFills: [...CHART_PALETTE_DEFAULT],
166
+ ...inks(CHART_FOREGROUND, CHART_MUTED),
167
+ ...rules("white/15"),
115
168
  track: "white/10",
116
169
  background: [],
170
+ gridBackground: [],
117
171
  },
118
172
  paper: {
119
- palette: ["#2563eb", "#059669", "#d97706", "#db2777", "#7c3aed"],
120
- foreground: "#1c1917",
121
- muted: "#57534e",
122
- horizontalGrid: { weight: 2, fill: "#1c1917/12" },
123
- gridStroke: { weight: 2, fill: "#1c1917/12" },
124
- spokeStroke: { weight: 2, fill: "#1c1917/12" },
125
- track: "#1c1917/12",
173
+ seriesFills: ["#2563eb", "#059669", "#d97706", "#db2777", "#7c3aed"],
174
+ ...inks("#1c1917", "#57534e"),
175
+ ...rules("#1c1917/12"),
126
176
  background: "#fafaf9",
177
+ gridBackground: [],
127
178
  },
128
179
  ember: {
129
- palette: ["#d9603b", "#8194ad", "#e0a93b", "#5e9d6b", "#9ec85a"],
130
- foreground: "#e9e1d4",
131
- muted: "#8a7f70",
132
- horizontalGrid: { weight: 2, fill: "#2c2820" },
133
- gridStroke: { weight: 2, fill: "#2c2820" },
134
- spokeStroke: { weight: 2, fill: "#2c2820" },
135
- track: "#2c2820",
180
+ seriesFills: ["#d9603b", "#8194ad", "#e0a93b", "#5e9d6b", "#9ec85a"],
181
+ ...inks("#e9e1d4", "#8a7f70"),
182
+ ...rules("#2c2820"),
136
183
  background: "#15130e",
184
+ gridBackground: [],
137
185
  },
138
186
  neon: {
139
- palette: ["#22d3ee", "#a3e635", "#f472b6", "#facc15", "#818cf8"],
140
- foreground: "#ffffff",
141
- muted: "#94a3b8",
142
- horizontalGrid: { weight: 2, fill: "white/10" },
143
- gridStroke: { weight: 2, fill: "white/10" },
144
- spokeStroke: { weight: 2, fill: "white/10" },
145
- track: "white/10",
187
+ seriesFills: ["#22d3ee", "#a3e635", "#f472b6", "#facc15", "#818cf8"],
188
+ ...inks("#ffffff", "#94a3b8"),
189
+ ...rules("white/10"),
146
190
  background: [],
191
+ gridBackground: [],
147
192
  },
148
193
  ocean: {
149
- palette: ["#0ea5e9", "#14b8a6", "#6366f1", "#38bdf8", "#2dd4bf"],
150
- foreground: "#e0f2fe",
151
- muted: "#7dd3fc",
152
- horizontalGrid: { weight: 2, fill: "#7dd3fc/20" },
153
- gridStroke: { weight: 2, fill: "#7dd3fc/20" },
154
- spokeStroke: { weight: 2, fill: "#7dd3fc/20" },
155
- track: "#7dd3fc/20",
194
+ seriesFills: ["#0ea5e9", "#14b8a6", "#6366f1", "#38bdf8", "#2dd4bf"],
195
+ ...inks("#e0f2fe", "#7dd3fc"),
196
+ ...rules("#7dd3fc/20"),
156
197
  background: "#082f49",
198
+ gridBackground: [],
157
199
  },
158
200
  sunset: {
159
- palette: ["#f97316", "#ef4444", "#ec4899", "#f59e0b", "#a855f7"],
160
- foreground: "#fff7ed",
161
- muted: "#fdba74",
162
- horizontalGrid: { weight: 2, fill: "#fdba74/20" },
163
- gridStroke: { weight: 2, fill: "#fdba74/20" },
164
- spokeStroke: { weight: 2, fill: "#fdba74/20" },
165
- track: "#fdba74/20",
201
+ seriesFills: ["#f97316", "#ef4444", "#ec4899", "#f59e0b", "#a855f7"],
202
+ ...inks("#fff7ed", "#fdba74"),
203
+ ...rules("#fdba74/20"),
166
204
  background: [],
205
+ gridBackground: [],
167
206
  },
168
207
  pastel: {
169
- palette: ["#a5b4fc", "#f9a8d4", "#86efac", "#fde68a", "#7dd3fc"],
170
- foreground: "#f8fafc",
171
- muted: "#cbd5e1",
172
- horizontalGrid: { weight: 2, fill: "white/12" },
173
- gridStroke: { weight: 2, fill: "white/12" },
174
- spokeStroke: { weight: 2, fill: "white/12" },
175
- track: "white/12",
208
+ seriesFills: ["#a5b4fc", "#f9a8d4", "#86efac", "#fde68a", "#7dd3fc"],
209
+ ...inks("#f8fafc", "#cbd5e1"),
210
+ ...rules("white/12"),
176
211
  background: [],
212
+ gridBackground: [],
177
213
  },
178
214
  mono: {
179
- palette: ["#fafafa", "#a3a3a3", "#737373", "#d4d4d4", "#525252"],
180
- foreground: "#fafafa",
181
- muted: "#a3a3a3",
182
- horizontalGrid: { weight: 2, fill: "white/12" },
183
- gridStroke: { weight: 2, fill: "white/12" },
184
- spokeStroke: { weight: 2, fill: "white/12" },
185
- track: "white/12",
215
+ seriesFills: ["#fafafa", "#a3a3a3", "#737373", "#d4d4d4", "#525252"],
216
+ ...inks("#fafafa", "#a3a3a3"),
217
+ ...rules("white/12"),
186
218
  background: [],
219
+ gridBackground: [],
187
220
  },
188
221
  } satisfies Record<string, ChartStylePreset>
189
222
 
190
- /** Series `index`'s colour: its own `fill`, else the palette's, cycled. */
191
- export function paletteFill(style: Pick<ChartStyleBase, "palette">, index: number, own?: Fill): Fill {
223
+ /** Series `index`'s fill: its own `fill`, else the style's series fills, cycled. */
224
+ export function paletteFill(style: Pick<ChartStyleBase, "seriesFills">, index: number, own?: Fill): Fill {
192
225
  if (own != null) return own
193
- const palette = style.palette
194
- return palette.length > 0 ? palette[index % palette.length] : "white"
226
+ const fills = style.seriesFills
227
+ return fills.length > 0 ? fills[index % fills.length] : "white"
195
228
  }
196
229
 
197
230
  /** {@link paletteFill} for a series. */
198
- export function seriesFill(style: Pick<ChartStyleBase, "palette">, series: ChartSeries, index: number): Fill {
231
+ export function seriesFill(style: Pick<ChartStyleBase, "seriesFills">, series: ChartSeries, index: number): Fill {
199
232
  return paletteFill(style, index, series.fill)
200
233
  }
201
234
 
202
- function face(style: Pick<ChartTextStyle, "fontFamily">): Pick<TextStyle, "fontFamily"> {
203
- return style.fontFamily ? { fontFamily: style.fontFamily } : {}
235
+ /** A text style's size in px; an autofit or unset size reads as `fallback`. */
236
+ export function sizeOf(text: TextStyle | undefined, fallback = 28): number {
237
+ const size = text?.fontSize
238
+ return typeof size === "number" && Number.isFinite(size) ? size : fallback
204
239
  }
205
240
 
206
- /** Axis markers, spoke labels: muted, at the base size. */
207
- export function markerText(style: ChartTextStyle, textAlign: TextStyle["textAlign"] = "center"): TextStyle {
208
- return { ...face(style), fontSize: style.fontSize, fontWeight: style.fontWeight, textAlign, fill: style.muted }
241
+ /**
242
+ * `text` as a text node takes it, aligned where the chart puts it, with `over`
243
+ * laid on top. Unset keys are dropped: an `undefined` spread onto a node is a
244
+ * value, and an unset face must stay unset so the host's default applies.
245
+ */
246
+ export function placedText(text: TextStyle, textAlign: TextStyle["textAlign"] = "center", over?: TextStyle): TextStyle {
247
+ return textOps.styleProps({ ...textOps.mergeStyle(text, over), textAlign }) as TextStyle
209
248
  }
210
249
 
211
- /** Axis captions: muted, a step larger and heavier than the markers. */
212
- export function titleText(style: ChartTextStyle): TextStyle {
213
- return {
214
- ...face(style),
215
- fontSize: Math.round(style.fontSize * 1.1),
216
- fontWeight: Math.min(900, style.fontWeight + 100),
217
- textAlign: "center",
218
- fill: style.muted,
219
- }
250
+ /** Axis markers on `axis`, aligned as that axis sets them. */
251
+ export function markerText(style: Pick<CartesianStyle, "xLabelText" | "yLabelText">, axis: "x" | "y"): TextStyle {
252
+ return placedText(axis === "x" ? style.xLabelText : style.yLabelText, axis === "x" ? "center" : "right")
220
253
  }
221
254
 
222
- /** Values on marks and legend captions: the foreground, at the base size. */
223
- export function valueText(style: ChartTextStyle, textAlign: TextStyle["textAlign"] = "center"): TextStyle {
224
- return { ...face(style), fontSize: style.fontSize, fontWeight: Math.min(900, style.fontWeight + 100), textAlign, fill: style.foreground }
255
+ /** The figures printed by the marks. */
256
+ export function valueText(style: Pick<CartesianStyle, "valueText">, textAlign: TextStyle["textAlign"] = "center"): TextStyle {
257
+ return placedText(style.valueText, textAlign)
225
258
  }
226
259
 
227
260
  /**
228
261
  * The kit axis parts' config from a style. `x` is the axis under the plot;
229
262
  * its grid lines are vertical (one per x marker), the `y` axis's horizontal.
230
263
  */
231
- export function axisConfigOf(style: Pick<CartesianStyle, "verticalGrid" | "horizontalGrid"> & ChartTextStyle, axis: "x" | "y"): AxisConfig {
232
- const gap = Math.round(style.fontSize * 0.6)
264
+ export function axisConfigOf(
265
+ style: Pick<CartesianStyle, "verticalGrid" | "horizontalGrid" | "xLabelText" | "xTitleText" | "yLabelText" | "yTitleText">,
266
+ axis: "x" | "y"
267
+ ): AxisConfig {
268
+ const gap = Math.round(sizeOf(axis === "x" ? style.xLabelText : style.yLabelText) * 0.6)
233
269
  return {
234
- labelStyle: markerText(style, axis === "x" ? "center" : "right"),
235
- titleStyle: titleText(style),
270
+ labelStyle: markerText(style, axis),
271
+ titleStyle: placedText(axis === "x" ? style.xTitleText : style.yTitleText),
236
272
  lineStyle: axis === "x" ? style.verticalGrid : style.horizontalGrid,
237
273
  tickPadding: axis === "x" ? { top: gap } : { right: gap },
238
274
  titlePadding: axis === "x" ? { top: Math.round(gap / 2) } : { right: gap },
239
275
  }
240
276
  }
241
277
 
242
- /** The kit legend's config: captions in the foreground, the swatch shaped by `swatch`. */
243
- export function legendConfigOf(style: ChartTextStyle & Pick<ChartStyleBase, "swatch">): LegendConfig {
244
- const size = Math.max(8, Math.round(style.fontSize * 0.6))
278
+ /** The kit legend's config: captions in `legendText`, the swatch shaped by `swatch` and sized off the captions. */
279
+ export function legendConfigOf(style: Pick<ChartStyleBase, "swatch" | "legendText">): LegendConfig {
280
+ const type = sizeOf(style.legendText)
281
+ const size = Math.max(8, Math.round(type * 0.6))
245
282
  const marker =
246
283
  style.swatch === "line"
247
284
  ? { width: Math.round(size * 2), height: Math.max(3, Math.round(size / 4)), borderRadius: size }
@@ -250,8 +287,69 @@ export function legendConfigOf(style: ChartTextStyle & Pick<ChartStyleBase, "swa
250
287
  : { width: size, height: size, borderRadius: Math.round(size / 4) }
251
288
  return {
252
289
  padding: 0,
253
- textStyle: { ...face(style), fontSize: style.fontSize, fontWeight: style.fontWeight, textAlign: "left", fill: style.foreground },
254
- itemGap: Math.round(style.fontSize * 1.4),
290
+ textStyle: placedText(style.legendText, "left"),
291
+ itemGap: Math.round(type * 1.4),
255
292
  markerStyle: { ...marker, stroke: [], shadow: [] },
256
293
  }
257
294
  }
295
+
296
+ /** Which text a chart drew from the retired shared text fields. */
297
+ export type ChartTextLayout = "cartesian" | "pie" | "radar" | "radial"
298
+
299
+ /**
300
+ * The `style` entry's hook for documents written before every run of text got
301
+ * its own field (0.1.1-alpha.1): `barRadius` became `barCornerRadius`, `palette` became `seriesFills`; `foreground`,
302
+ * `muted` and the one `fontFamily`/`fontSize`/`fontWeight` became the text
303
+ * fields as each chart derived them; a cartesian or radar `background`, which
304
+ * painted the plot, became `gridBackground`. A current style comes back as is.
305
+ */
306
+ export function migrateChartStyle(layout: ChartTextLayout): (value: unknown) => unknown {
307
+ return (value) => {
308
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return value
309
+ const old = value as Record<string, unknown>
310
+ const retired = ["palette", "foreground", "muted", "fontFamily", "fontSize", "fontWeight", "barRadius"]
311
+ const plotBackground = layout === "cartesian" || layout === "radar"
312
+ if (!retired.some((key) => key in old) && !(plotBackground && "background" in old && !("gridBackground" in old))) return value
313
+
314
+ const { palette, foreground, muted, fontFamily, fontSize, fontWeight, barRadius, ...rest } = old
315
+ const out: Record<string, unknown> = { ...rest }
316
+ // A bar radius rounded only the far end; its replacement states every corner.
317
+ if (typeof barRadius === "number" && out.barCornerRadius === undefined) {
318
+ out.barCornerRadius = { topLeft: barRadius, topRight: barRadius, bottomRight: 0, bottomLeft: 0 }
319
+ }
320
+ if (palette !== undefined && out.seriesFills === undefined) out.seriesFills = palette
321
+ if (plotBackground && "background" in old && !("gridBackground" in old)) {
322
+ out.gridBackground = old.background
323
+ delete out.background
324
+ }
325
+ const size = typeof fontSize === "number" ? fontSize : undefined
326
+ const weight = typeof fontWeight === "number" ? fontWeight : undefined
327
+ const text = (ink: unknown, scale = 1, heavier = 0): Record<string, unknown> | undefined => {
328
+ const t: Record<string, unknown> = {}
329
+ if (typeof fontFamily === "string" && fontFamily !== "") t.fontFamily = fontFamily
330
+ if (size !== undefined) t.fontSize = Math.round(size * scale)
331
+ if (weight !== undefined) t.fontWeight = Math.min(900, weight + heavier)
332
+ if (ink !== undefined) t.fill = ink
333
+ return Object.keys(t).length > 0 ? t : undefined
334
+ }
335
+ const fields: Record<string, Record<string, unknown> | undefined> = { legendText: text(foreground) }
336
+ if (layout === "cartesian") {
337
+ fields.xLabelText = text(muted)
338
+ fields.yLabelText = text(muted)
339
+ fields.xTitleText = text(muted, 1.1, 100)
340
+ fields.yTitleText = text(muted, 1.1, 100)
341
+ fields.valueText = text(foreground, 0.85, 100)
342
+ } else if (layout === "pie") {
343
+ fields.labelText = text(muted)
344
+ fields.valueText = text(foreground, 1, 200)
345
+ fields.centerText = text(foreground, 1.8, 300)
346
+ } else if (layout === "radar") {
347
+ fields.spokeText = text(muted)
348
+ } else {
349
+ fields.labelText = text(muted)
350
+ fields.centerText = text(foreground, 1.8, 300)
351
+ }
352
+ for (const [key, field] of Object.entries(fields)) if (field && out[key] === undefined) out[key] = field
353
+ return out
354
+ }
355
+ }
@@ -12,6 +12,7 @@ import {
12
12
  // Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.
13
13
  type Node2D,
14
14
  type Node2DProps as _Node2DProps,
15
+ type TextStyle as _TextStyle,
15
16
  type Fill as _Fill,
16
17
  type FillResolved as _FillResolved,
17
18
  type Stroke as _Stroke,
@@ -46,14 +47,17 @@ import {
46
47
  } from "../kit/shared"
47
48
  import {
48
49
  CARTESIAN_GRID,
49
- CHART_COLORS,
50
+ CARTESIAN_AXES,
50
51
  CHART_LEGEND,
52
+ CHART_SERIES,
51
53
  CHART_STYLE_FAMILY,
52
54
  CHART_STYLE_PRESETS,
53
- CHART_TEXT,
55
+ GRID_BACKGROUND,
54
56
  axisConfigOf,
55
57
  legendConfigOf,
58
+ migrateChartStyle,
56
59
  seriesFill,
60
+ sizeOf,
57
61
  valueText,
58
62
  } from "../kit/style"
59
63
  import { VerticalAxis, axisPads } from "../kit/vertical-axis"
@@ -75,7 +79,7 @@ import { resolveXScale, type XScale } from "./x-scale"
75
79
 
76
80
  const LINE_STYLE = Types.style(
77
81
  CHART_STYLE_FAMILY,
78
- { colors: CHART_COLORS, text: CHART_TEXT, grid: CARTESIAN_GRID, marks: LINE_MARKS, labels: LINE_LABELS, legend: CHART_LEGEND },
82
+ { series: CHART_SERIES, background: GRID_BACKGROUND, grid: CARTESIAN_GRID, axes: CARTESIAN_AXES, marks: LINE_MARKS, labels: LINE_LABELS, legend: CHART_LEGEND },
79
83
  { presets: CHART_STYLE_PRESETS }
80
84
  )
81
85
 
@@ -352,6 +356,7 @@ export const LineChart = component2d({
352
356
  height: "fill",
353
357
  weights: (state) => state.series.map((s) => ((s.enabled ?? true) ? 1 : 0)),
354
358
  },
359
+ migrate: { style: migrateChartStyle("cartesian") },
355
360
  allowedChildren: [],
356
361
  children: (state) => {
357
362
  const style = () => state.style
@@ -450,7 +455,7 @@ export const LineChart = component2d({
450
455
  state.data.forEach((_row, k) => {
451
456
  const line = lines[i]
452
457
  const label: Node2D = new Text({
453
- ...boundText(() => ({ ...valueText(style()), fontSize: Math.round(style().fontSize * 0.8) })),
458
+ ...boundText(() => valueText(style())),
454
459
  text: () => {
455
460
  const value = pixels(i).values[k]
456
461
  return value === undefined ? "" : yLabel(state, value, domains().y.ticks)
@@ -462,7 +467,7 @@ export const LineChart = component2d({
462
467
  },
463
468
  y: () => {
464
469
  const point = pixels(i).points[k]
465
- const clear = label.layoutBounds.height / 2 + (style().dots ? style().dotSize / 2 : style().strokeWidth / 2) + Math.round(style().fontSize * 0.25)
470
+ const clear = label.layoutBounds.height / 2 + (style().dots ? style().dotSize / 2 : style().strokeWidth / 2) + Math.round(sizeOf(style().valueText) * 0.25)
466
471
  return (point?.y ?? 0) + (below ? -clear : clear)
467
472
  },
468
473
  // In once the trace has passed its point.
@@ -492,7 +497,7 @@ export const LineChart = component2d({
492
497
  clip: () => state.clipped || overflows(state, domains()),
493
498
  children: [
494
499
  new ChartCanvas({
495
- fill: () => style().background,
500
+ fill: () => style().gridBackground,
496
501
  xGridStroke: () => style().verticalGrid,
497
502
  yGridStroke: () => style().horizontalGrid,
498
503
  xMarkers: () => domains().x.ticks,
@@ -506,8 +511,8 @@ export const LineChart = component2d({
506
511
  // Under the marks, so the highlight does not tint the stretch it picks out.
507
512
  new ChartRegion({
508
513
  box: spotlight,
509
- stroke: () => ({ weight: 2, fill: fade(style().foreground, 0.7), dash: [14, 12], cap: "butt", align: "center" }),
510
- fill: () => fade(style().foreground, 0.06),
514
+ stroke: () => ({ weight: 2, fill: fade(style().valueText.fill ?? "white", 0.7), dash: [14, 12], cap: "butt", align: "center" }),
515
+ fill: () => fade(style().valueText.fill ?? "white", 0.06),
511
516
  reveal: () => state.regionReveal,
512
517
  }),
513
518
  ...areas,
@@ -518,6 +523,7 @@ export const LineChart = component2d({
518
523
 
519
524
  return asParts([
520
525
  cartesianFrame({
526
+ background: () => style().background,
521
527
  left: vAxis,
522
528
  ghost,
523
529
  plot,
@@ -1,6 +1,7 @@
1
- import { Types, type DataRecord, type Vector2 } from "@motionscript/sdk"
1
+ import { Types, type DataRecord, type Vector2, type TextStyle as _TextStyle } from "@motionscript/sdk"
2
2
 
3
3
  import { niceRange, num, type ChartSeries, type Range } from "../kit/shared"
4
+ import { VALUE_TEXT } from "../kit/style"
4
5
  import type { XScale } from "./x-scale"
5
6
 
6
7
  /** One polyline of a {@link LineChart}: {@link ChartSeries} under the name for what it draws here. */
@@ -395,4 +396,5 @@ export const LINE_MARKS = {
395
396
  export const LINE_LABELS = {
396
397
  /** Read as the chart is built: each point's value printed above or below it, or none. */
397
398
  values: Types.oneOf(["none", "above", "below"] as const, { animatable: false }),
399
+ ...VALUE_TEXT,
398
400
  }
@@ -10,6 +10,7 @@ import {
10
10
  // Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.
11
11
  type Node2D,
12
12
  type Node2DProps as _Node2DProps,
13
+ type TextStyle as _TextStyle,
13
14
  type Fill as _Fill,
14
15
  type FillResolved as _FillResolved,
15
16
  } from "@motionscript/sdk"
@@ -18,7 +19,7 @@ import { asParts, fresh } from "../kit/entries"
18
19
  import { beats, type AnyTimeline, type Beat } from "../kit/motion"
19
20
  import { PieLegend } from "../kit/pie-legend"
20
21
  import { PieRing } from "./pie-ring"
21
- import { CHART_STYLE_FAMILY, CHART_STYLE_PRESETS, paletteFill } from "../kit/style"
22
+ import { CHART_STYLE_FAMILY, CHART_STYLE_PRESETS, migrateChartStyle, paletteFill } from "../kit/style"
22
23
  import {
23
24
  PIE_SPOTLIGHT_DIM,
24
25
  PIE_STYLE_GROUPS,
@@ -127,6 +128,7 @@ export const PieChart = component2d({
127
128
  height: "fill",
128
129
  pops: (state) => slicesOf(state).map(() => 0),
129
130
  },
131
+ migrate: { style: migrateChartStyle("pie") },
130
132
  allowedChildren: [],
131
133
  // The structure (a label and legend row per row) is read once; slices, paint and selection are bound, so a theme tween re-paints and a `set` of `data` re-cuts.
132
134
  children: (state) => {