@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
@@ -1,29 +1,9 @@
1
- /**
2
- * Types and pure helpers specific to the {@link PieChart} family: how rows
3
- * become slices, how slices become arcs on a ring, where the labels around it
4
- * end up, and the theme all of that is drawn from.
5
- *
6
- * The one chart in the family that draws **one mark per row** rather than one
7
- * per column, which is why almost nothing about its geometry is shared: there is
8
- * no scale, no axis and no baseline, only a circle divided by share. What it
9
- * *does* share — the legend, the theme leaf resolvers, the palette, the
10
- * percentage format — comes from `../kit/shared.ts`.
11
- *
12
- * Kept renderer-agnostic (no `Node` imports) so a host's inspector mapping
13
- * can build a partial {@link PieChartTheme} without
14
- * pulling the node classes in.
15
- */
16
- import { lerpNumber, shadowOps, strokeOps, textOps, } from "@motionscript/sdk";
1
+ import { clamp, lerpNumber, shadowOps, strokeOps, textOps, } from "@motionscript/sdk";
17
2
  import { CHART_PALETTE, DEFAULT_LEGEND, DEFAULT_SERIES_FILLS, lerpLegendConfig, lerpSeriesFills, num, resolveLegendConfig, resolveSeriesFills, text, } from "../kit/shared.js";
18
3
  /**
19
- * Resolve tabular rows into slices: one per row, captioned by the `category`
20
- * column, sized by the `value` column, painted from `palette` by row order
21
- * (cycling when the palette is shorter than the data).
22
- *
23
- * Paint comes from the palette rather than from the rows because a pie draws one
24
- * mark per *row* — unlike the line and bar charts, where a colour belongs to a
25
- * series (a whole column) and rides on the series entry instead. Keeping it out
26
- * of the rows is what lets a pie be fed straight from a CSV of plain numbers.
4
+ * One slice per row, painted from `palette` by row order (cycling). Paint comes
5
+ * from the palette, not the rows, because a pie's mark is a row where a line's
6
+ * colour belongs to a column; that lets a pie be fed a CSV of plain numbers.
27
7
  */
28
8
  export function resolvePieSlices(data, category, value, palette) {
29
9
  return data.map((row, i) => ({
@@ -32,38 +12,37 @@ export function resolvePieSlices(data, category, value, palette) {
32
12
  fill: palette.length > 0 ? palette[i % palette.length] : "white",
33
13
  }));
34
14
  }
35
- /** Degrees → radians. */
36
15
  export const DEG = Math.PI / 180;
16
+ /**
17
+ * Opacity of a slice under the selection: 1 while nothing is out (`focus` is the
18
+ * most-grown pop) and for the selected slice itself, falling toward `dimOpacity`
19
+ * for the rest as the selection grows.
20
+ */
21
+ export function selectionFade(focus, dimOpacity, pop) {
22
+ if (focus <= 0)
23
+ return 1;
24
+ return 1 - (1 - clamp(dimOpacity, 0, 1)) * focus * (1 - pop);
25
+ }
37
26
  /** A slice's plotted value: negatives and non-finite values count as 0. */
38
27
  export function sliceValue(slice) {
39
28
  return slice.value > 0 && Number.isFinite(slice.value) ? slice.value : 0;
40
29
  }
41
- /** Sum of every slice's plotted value — the denominator every share is taken against. */
30
+ /** Sum of every slice's plotted value, the denominator of every share. */
42
31
  export function sliceTotal(slices) {
43
32
  let sum = 0;
44
33
  for (const slice of slices)
45
34
  sum += sliceValue(slice);
46
35
  return sum;
47
36
  }
48
- /**
49
- * The point at radius `r` and angle `a` on a ring centred on the origin, in the
50
- * node's centred, **y-up** space. Angles run **clockwise from 12 o'clock**:
51
- * `a = 0` points straight up (+y), and increasing `a` sweeps toward +x then −y.
52
- */
37
+ /** The point at radius `r`, angle `a` on a ring about the origin in centred y-up space. Angles run clockwise from 12 o'clock. */
53
38
  export function ringPoint(r, a) {
54
39
  return { x: r * Math.sin(a), y: r * Math.cos(a) };
55
40
  }
56
41
  /**
57
- * Resolve slices into per-slice arcs: each covers exactly its share of the
58
- * circle, walking clockwise from `startDegrees` (0 = 12 o'clock), with the arcs
59
- * abutting. Zero/negative slices are skipped entirely; an all-zero list yields
60
- * no arcs.
61
- *
62
- * Arcs are the *undivided* shares — the gap between neighbours is not carved out
63
- * here, because a fixed angular gap reads as a wedge: wide at the rim, pinched
64
- * shut at the hole. It's applied when the wedge is drawn instead, as a
65
- * constant-width inset off both ends of every arc, so a slice still covers its
66
- * true percentage of the circle.
42
+ * One arc per positive slice, walking clockwise from `startDegrees` and abutting.
43
+ * Arcs are the undivided shares: a fixed angular gap would pinch shut at the
44
+ * hole, so the gap is applied when the wedge is drawn, as a constant-width inset
45
+ * off both ends.
67
46
  */
68
47
  export function pieArcs(slices, startDegrees) {
69
48
  const total = sliceTotal(slices);
@@ -84,16 +63,11 @@ export function pieArcs(slices, startDegrees) {
84
63
  return arcs;
85
64
  }
86
65
  /**
87
- * Spread one column of labels so no two overlap, keeping each as close to its
88
- * `ideal` y as possible and the whole column inside `[bottom, top]`.
89
- *
90
- * Two passes, the standard treatment. Walking top-to-bottom, each label takes
91
- * the highest slot at or below its ideal that still clears the label above it by
92
- * `minGap`; if that pushes the tail past `bottom`, a reverse pass lifts it back
93
- * up. When there are more labels than the column can hold, the reverse pass wins
94
- * and the crowding is shared evenly rather than dumped off the bottom edge.
95
- *
96
- * Mutates each slot's `y` and returns `slots` re-ordered top-to-bottom.
66
+ * Spread one column of labels so none overlap, each near its `ideal` y and all
67
+ * inside `[bottom, top]`. A top-down pass clears each label from the one above
68
+ * by `minGap`; a reverse pass lifts a tail pushed past `bottom`. When the column
69
+ * cannot hold them all, the reverse pass wins and the crowding is shared evenly.
70
+ * Mutates each `y` and returns `slots` ordered top to bottom.
97
71
  */
98
72
  export function relaxLabels(slots, minGap, top, bottom) {
99
73
  slots.sort((a, b) => b.ideal - a.ideal);
@@ -115,20 +89,13 @@ const LABEL_TEXT_STYLE = {
115
89
  textAlign: "center",
116
90
  fill: CHART_PALETTE.markerColor,
117
91
  };
118
- /**
119
- * Opacity the other slices fade to under a selection. Fainter than the line and
120
- * bar charts' `SPOTLIGHT_DIM`: a ring's slices touch, so at that a selected
121
- * slice barely separates from its neighbours.
122
- */
92
+ /** Fainter than `SPOTLIGHT_DIM`: a ring's slices touch, so at that a selected slice barely separates. */
123
93
  export const PIE_SPOTLIGHT_DIM = 0.1;
124
- /** The pie chart's out-of-the-box look. */
125
94
  export const DEFAULT_PIE_CHART_THEME = {
126
95
  sliceFills: DEFAULT_SERIES_FILLS,
127
96
  ring: {
128
97
  gap: 6,
129
- // A donut rather than a solid pie by default: the hole is the largest
130
- // uninterrupted area the chart owns and the one place a conclusion can be
131
- // set. `0` here would give that away before anyone had asked for it.
98
+ // A donut by default: the hole is the one place a conclusion can be set.
132
99
  holeRadius: 0.6,
133
100
  startAngle: 0,
134
101
  selectedGrow: 1.08,
@@ -138,8 +105,7 @@ export const DEFAULT_PIE_CHART_THEME = {
138
105
  label: {
139
106
  textStyle: LABEL_TEXT_STYLE,
140
107
  percentStyle: { ...LABEL_TEXT_STYLE, fontWeight: 700 },
141
- // Inside labels sit on the wedge's paint, which is a mid-tone from the
142
- // palette — so they lead with the panel colour rather than the axis ink.
108
+ // On a mid-tone wedge, so the panel colour rather than the axis ink.
143
109
  insideStyle: { ...LABEL_TEXT_STYLE, fontWeight: 800, fill: "#15130e" },
144
110
  leaderStroke: { weight: 2, fill: CHART_PALETTE.markerColor },
145
111
  },
@@ -208,12 +174,7 @@ function lerpCenterConfig(from, to, t) {
208
174
  captionStyle: textOps.lerpStyle(from.captionStyle, to.captionStyle, t),
209
175
  };
210
176
  }
211
- /**
212
- * {@link PieChart}'s `theme` property mapper: merges the author's partial
213
- * {@link PieChartTheme} onto `previous` (falling back to
214
- * {@link DEFAULT_PIE_CHART_THEME} on first resolution), fully resolving every
215
- * leaf so the tween below always has resolved data to interpolate.
216
- */
177
+ /** The `theme` entry's resolve: merges a partial {@link PieChartTheme} onto `previous` (first resolution: the default). */
217
178
  export function resolvePieChartTheme(theme, previous) {
218
179
  const base = previous ?? DEFAULT_PIE_CHART_THEME;
219
180
  return {
@@ -227,12 +188,8 @@ export function resolvePieChartTheme(theme, previous) {
227
188
  },
228
189
  };
229
190
  }
230
- /** {@link PieChart}'s `theme` property tween — leaf by leaf. */
231
191
  export function lerpPieChartTheme(from, to, t) {
232
192
  return {
233
- // A palette is a list of independent paints rather than one value, so it
234
- // interpolates pairwise by index; one that grows or shrinks holds the longer
235
- // side's tail, exactly as the cartesian charts' palettes do.
236
193
  sliceFills: lerpSeriesFills(from.sliceFills, to.sliceFills, t),
237
194
  ring: lerpRingConfig(from.ring, to.ring, t),
238
195
  label: lerpLabelConfig(from.label, to.label, t),
@@ -1 +1 @@
1
- {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/pie-chart/shared.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Types and pure helpers specific to the {@link PieChart} family: how rows\n * become slices, how slices become arcs on a ring, where the labels around it\n * end up, and the theme all of that is drawn from.\n *\n * The one chart in the family that draws **one mark per row** rather than one\n * per column, which is why almost nothing about its geometry is shared: there is\n * no scale, no axis and no baseline, only a circle divided by share. What it\n * *does* share — the legend, the theme leaf resolvers, the palette, the\n * percentage format — comes from `../kit/shared.ts`.\n *\n * Kept renderer-agnostic (no `Node` imports) so a host's inspector mapping\n * can build a partial {@link PieChartTheme} without\n * pulling the node classes in.\n */\n\nimport {\n lerpNumber,\n shadowOps,\n strokeOps,\n textOps,\n type DataRecord,\n type Fill,\n type Shadow,\n type ShadowResolved,\n type Stroke,\n type StrokeResolved,\n type TextStyle,\n type Vector2,\n} from \"@motionscript/sdk\"\n\nimport {\n CHART_PALETTE,\n DEFAULT_LEGEND,\n DEFAULT_SERIES_FILLS,\n lerpLegendConfig,\n lerpSeriesFills,\n num,\n resolveLegendConfig,\n resolveSeriesFills,\n text,\n type LegendConfig,\n type LegendTheme,\n} from \"../kit/shared\"\n\n/**\n * One **resolved** slice of a {@link PieChart}: its caption, its magnitude, and\n * the paint its wedge and legend swatch take.\n *\n * This is what the ring and the legend consume, not what a scene authors: the\n * chart takes tabular `data` plus two field names and resolves it into these, so\n * the parts stay unaware of where a slice's caption, magnitude and paint came\n * from.\n */\nexport interface PieSlice {\n /** Legend caption and label text for this slice. */\n category: string\n /** This slice's magnitude. Its wedge covers `value / (sum of all values)`. */\n value: number\n /** Wedge and swatch paint. */\n fill: Fill\n}\n\n/**\n * Resolve tabular rows into slices: one per row, captioned by the `category`\n * column, sized by the `value` column, painted from `palette` by row order\n * (cycling when the palette is shorter than the data).\n *\n * Paint comes from the palette rather than from the rows because a pie draws one\n * mark per *row* — unlike the line and bar charts, where a colour belongs to a\n * series (a whole column) and rides on the series entry instead. Keeping it out\n * of the rows is what lets a pie be fed straight from a CSV of plain numbers.\n */\nexport function resolvePieSlices(\n data: DataRecord[],\n category: string,\n value: string,\n palette: Fill[]\n): PieSlice[] {\n return data.map((row, i) => ({\n category: text(row, category),\n value: num(row, value),\n fill:\n palette.length > 0 ? palette[i % palette.length] : (\"white\" as Fill),\n }))\n}\n\n/** What a {@link PieChart}'s labels say. */\nexport type PieLabelMode =\n /**\n * The category and its percentage, two lines. Their order flips with the\n * placement: a **leader** label reads category over percentage, an **inside**\n * label reads the percentage big over a small category caption — inside a\n * wedge the reader already knows which slice they're on, so the magnitude is\n * the thing being read.\n */\n | \"both\"\n /** The category only. */\n | \"category\"\n /** The percentage only — the pick when a legend already names the categories. */\n | \"percent\"\n /** No labels and no leader lines at all. */\n | \"none\"\n\n/**\n * Where a slice's label sits.\n *\n * `\"inside\"` drops the leader line entirely and centres the label in the wedge's\n * own band, which costs no gutter — so the ring draws bigger — and costs the eye\n * no round trip. `\"leader\"` is the classic treatment, and what a sliver too\n * narrow to hold text needs.\n */\nexport type PieLabelPlacement = \"inside\" | \"leader\" | \"none\"\n\n/** Degrees → radians. */\nexport const DEG = Math.PI / 180\n\n/** A slice's plotted value: negatives and non-finite values count as 0. */\nexport function sliceValue(slice: PieSlice): number {\n return slice.value > 0 && Number.isFinite(slice.value) ? slice.value : 0\n}\n\n/** Sum of every slice's plotted value — the denominator every share is taken against. */\nexport function sliceTotal(slices: PieSlice[]): number {\n let sum = 0\n for (const slice of slices) sum += sliceValue(slice)\n return sum\n}\n\n/**\n * The point at radius `r` and angle `a` on a ring centred on the origin, in the\n * node's centred, **y-up** space. Angles run **clockwise from 12 o'clock**:\n * `a = 0` points straight up (+y), and increasing `a` sweeps toward +x then −y.\n */\nexport function ringPoint(r: number, a: number): Vector2 {\n return { x: r * Math.sin(a), y: r * Math.cos(a) }\n}\n\n/** One slice's resolved angular span on the ring. */\nexport interface PieArc {\n /** Index of the slice in the source list. */\n index: number\n /** Start angle, radians clockwise from 12 o'clock. */\n a0: number\n /** End angle, radians clockwise from 12 o'clock. */\n a1: number\n /** Bisector angle — where this slice's label and pop-out point. */\n mid: number\n /** This slice's share of the total, in `[0, 1]`. */\n fraction: number\n}\n\n/**\n * Resolve slices into per-slice arcs: each covers exactly its share of the\n * circle, walking clockwise from `startDegrees` (0 = 12 o'clock), with the arcs\n * abutting. Zero/negative slices are skipped entirely; an all-zero list yields\n * no arcs.\n *\n * Arcs are the *undivided* shares — the gap between neighbours is not carved out\n * here, because a fixed angular gap reads as a wedge: wide at the rim, pinched\n * shut at the hole. It's applied when the wedge is drawn instead, as a\n * constant-width inset off both ends of every arc, so a slice still covers its\n * true percentage of the circle.\n */\nexport function pieArcs(slices: PieSlice[], startDegrees: number): PieArc[] {\n const total = sliceTotal(slices)\n if (total <= 0) return []\n\n const arcs: PieArc[] = []\n let cursor = startDegrees * DEG\n slices.forEach((slice, index) => {\n const value = sliceValue(slice)\n if (value <= 0) return\n const fraction = value / total\n const a0 = cursor\n const a1 = a0 + fraction * Math.PI * 2\n cursor = a1\n arcs.push({ index, a0, a1, mid: (a0 + a1) / 2, fraction })\n })\n return arcs\n}\n\n/** A label competing for vertical space in one of the two label columns. */\nexport interface LabelSlot {\n /** Where the label would sit if nothing else were in the way. */\n ideal: number\n /** Where it ends up once the column has been de-conflicted. Written by {@link relaxLabels}. */\n y: number\n}\n\n/**\n * Spread one column of labels so no two overlap, keeping each as close to its\n * `ideal` y as possible and the whole column inside `[bottom, top]`.\n *\n * Two passes, the standard treatment. Walking top-to-bottom, each label takes\n * the highest slot at or below its ideal that still clears the label above it by\n * `minGap`; if that pushes the tail past `bottom`, a reverse pass lifts it back\n * up. When there are more labels than the column can hold, the reverse pass wins\n * and the crowding is shared evenly rather than dumped off the bottom edge.\n *\n * Mutates each slot's `y` and returns `slots` re-ordered top-to-bottom.\n */\nexport function relaxLabels<T extends LabelSlot>(\n slots: T[],\n minGap: number,\n top: number,\n bottom: number\n): T[] {\n slots.sort((a, b) => b.ideal - a.ideal)\n\n let ceiling = top\n for (const slot of slots) {\n slot.y = Math.min(slot.ideal, ceiling)\n ceiling = slot.y - minGap\n }\n\n let floor = bottom\n for (let i = slots.length - 1; i >= 0; i--) {\n slots[i].y = Math.max(slots[i].y, floor)\n floor = slots[i].y + minGap\n }\n\n return slots\n}\n\n// ---- Theme ---------------------------------------------------------------\n\n/** The ring's geometry and paint — everything about the wedges but their colour. */\nexport interface RingConfig {\n /**\n * Width of the channel between adjacent slices, in px, held constant all the\n * way across the band — the same clearance at the hole as at the rim. (An\n * angular gap would splay open toward the rim instead.)\n */\n gap: number\n /**\n * Radius of the hole punched through the middle. Below 1 it reads as a\n * fraction of the ring's outer radius (`0.6` leaves a band 40% of the radius\n * thick); 1 and up as an explicit pixel radius; `0` fills the middle in,\n * drawing solid pie wedges.\n */\n holeRadius: number\n /** Angle in degrees where the first slice begins, clockwise from 12 o'clock. */\n startAngle: number\n /**\n * How far out a selected slice's rim reaches, as a **multiple of the ring's\n * outer radius**. Being relative, the emphasis scales with the chart instead\n * of shrinking on a big one and swamping a small one. Clamped to `[1, 1.6]`.\n */\n selectedGrow: number\n /** Optional outline on every wedge. */\n stroke: Stroke\n /** Optional drop shadow on every wedge. */\n shadow: Shadow\n}\n\n/** How the ring's labels are set, and the leader lines that point at them. */\nexport interface LabelConfig {\n /** The category caption on a label. */\n textStyle: TextStyle\n /** The percentage on a label. */\n percentStyle: TextStyle\n /**\n * Ink for a label set **inside** its wedge, both lines. Separate because an\n * inside label sits on the wedge's own paint rather than on the background, so\n * it usually wants the opposite end of the value range from everything else.\n */\n insideStyle: TextStyle\n /** The leader line running from a wedge out to its label. */\n leaderStroke: Stroke\n}\n\n/** The summary set in the ring's hole. */\nexport interface CenterConfig {\n labelStyle: TextStyle\n captionStyle: TextStyle\n}\n\n/** A {@link PieChart}'s full, resolved visual theme. */\nexport interface PieChartConfig {\n /** Wedge and swatch paint, cycled by **row** index (`i % length`). */\n sliceFills: Fill[]\n ring: RingConfig\n label: LabelConfig\n center: CenterConfig\n legend: LegendConfig\n /** How far everything outside the selection fades while any slice is selected. */\n spotlight: { dimOpacity: number }\n}\n\n/** Author-facing partial theme — every branch optional and deep-merged. */\nexport type PieChartTheme = {\n sliceFills?: Fill[]\n ring?: Partial<RingConfig>\n label?: Partial<LabelConfig>\n center?: Partial<CenterConfig>\n legend?: LegendTheme\n spotlight?: Partial<PieChartConfig[\"spotlight\"]>\n}\n\nconst LABEL_TEXT_STYLE: TextStyle = {\n fontSize: 26,\n fontWeight: 600,\n textAlign: \"center\",\n fill: CHART_PALETTE.markerColor,\n}\n\n/**\n * Opacity the other slices fade to under a selection. Fainter than the line and\n * bar charts' `SPOTLIGHT_DIM`: a ring's slices touch, so at that a selected\n * slice barely separates from its neighbours.\n */\nexport const PIE_SPOTLIGHT_DIM = 0.1\n\n/** The pie chart's out-of-the-box look. */\nexport const DEFAULT_PIE_CHART_THEME: PieChartConfig = {\n sliceFills: DEFAULT_SERIES_FILLS,\n ring: {\n gap: 6,\n // A donut rather than a solid pie by default: the hole is the largest\n // uninterrupted area the chart owns and the one place a conclusion can be\n // set. `0` here would give that away before anyone had asked for it.\n holeRadius: 0.6,\n startAngle: 0,\n selectedGrow: 1.08,\n stroke: [],\n shadow: [],\n },\n label: {\n textStyle: LABEL_TEXT_STYLE,\n percentStyle: { ...LABEL_TEXT_STYLE, fontWeight: 700 },\n // Inside labels sit on the wedge's paint, which is a mid-tone from the\n // palette — so they lead with the panel colour rather than the axis ink.\n insideStyle: { ...LABEL_TEXT_STYLE, fontWeight: 800, fill: \"#15130e\" },\n leaderStroke: { weight: 2, fill: CHART_PALETTE.markerColor },\n },\n center: {\n labelStyle: {\n fontSize: 46,\n fontWeight: 800,\n textAlign: \"center\",\n fill: CHART_PALETTE.markerColor,\n },\n captionStyle: {\n fontSize: 22,\n fontWeight: 500,\n textAlign: \"center\",\n fill: CHART_PALETTE.markerColor,\n },\n },\n legend: DEFAULT_LEGEND,\n spotlight: { dimOpacity: PIE_SPOTLIGHT_DIM },\n}\n\nfunction resolveRingConfig(\n ring: Partial<RingConfig> | undefined,\n previous: RingConfig\n): RingConfig {\n return {\n gap: ring?.gap ?? previous.gap,\n holeRadius: ring?.holeRadius ?? previous.holeRadius,\n startAngle: ring?.startAngle ?? previous.startAngle,\n selectedGrow: ring?.selectedGrow ?? previous.selectedGrow,\n stroke: strokeOps.resolve(ring?.stroke ?? previous.stroke),\n shadow: shadowOps.resolve(ring?.shadow ?? previous.shadow),\n }\n}\n\nfunction lerpRingConfig(\n from: RingConfig,\n to: RingConfig,\n t: number\n): RingConfig {\n return {\n gap: lerpNumber(from.gap, to.gap, t),\n holeRadius: lerpNumber(from.holeRadius, to.holeRadius, t),\n startAngle: lerpNumber(from.startAngle, to.startAngle, t),\n selectedGrow: lerpNumber(from.selectedGrow, to.selectedGrow, t),\n stroke: strokeOps.lerp(\n from.stroke as StrokeResolved[],\n to.stroke as StrokeResolved[],\n t\n ),\n shadow: shadowOps.lerp(\n from.shadow as ShadowResolved[],\n to.shadow as ShadowResolved[],\n t\n ),\n }\n}\n\nfunction resolveLabelConfig(\n label: Partial<LabelConfig> | undefined,\n previous: LabelConfig\n): LabelConfig {\n return {\n textStyle: textOps.resolveStyle(label?.textStyle, previous.textStyle),\n percentStyle: textOps.resolveStyle(\n label?.percentStyle,\n previous.percentStyle\n ),\n insideStyle: textOps.resolveStyle(label?.insideStyle, previous.insideStyle),\n leaderStroke: strokeOps.resolve(\n label?.leaderStroke ?? previous.leaderStroke\n ),\n }\n}\n\nfunction lerpLabelConfig(\n from: LabelConfig,\n to: LabelConfig,\n t: number\n): LabelConfig {\n return {\n textStyle: textOps.lerpStyle(from.textStyle, to.textStyle, t),\n percentStyle: textOps.lerpStyle(from.percentStyle, to.percentStyle, t),\n insideStyle: textOps.lerpStyle(from.insideStyle, to.insideStyle, t),\n leaderStroke: strokeOps.lerp(\n from.leaderStroke as StrokeResolved[],\n to.leaderStroke as StrokeResolved[],\n t\n ),\n }\n}\n\nfunction resolveCenterConfig(\n center: Partial<CenterConfig> | undefined,\n previous: CenterConfig\n): CenterConfig {\n return {\n labelStyle: textOps.resolveStyle(center?.labelStyle, previous.labelStyle),\n captionStyle: textOps.resolveStyle(\n center?.captionStyle,\n previous.captionStyle\n ),\n }\n}\n\nfunction lerpCenterConfig(\n from: CenterConfig,\n to: CenterConfig,\n t: number\n): CenterConfig {\n return {\n labelStyle: textOps.lerpStyle(from.labelStyle, to.labelStyle, t),\n captionStyle: textOps.lerpStyle(from.captionStyle, to.captionStyle, t),\n }\n}\n\n/**\n * {@link PieChart}'s `theme` property mapper: merges the author's partial\n * {@link PieChartTheme} onto `previous` (falling back to\n * {@link DEFAULT_PIE_CHART_THEME} on first resolution), fully resolving every\n * leaf so the tween below always has resolved data to interpolate.\n */\nexport function resolvePieChartTheme(\n theme: PieChartTheme | undefined,\n previous?: PieChartConfig\n): PieChartConfig {\n const base = previous ?? DEFAULT_PIE_CHART_THEME\n return {\n sliceFills: resolveSeriesFills(theme?.sliceFills, base.sliceFills),\n ring: resolveRingConfig(theme?.ring, base.ring),\n label: resolveLabelConfig(theme?.label, base.label),\n center: resolveCenterConfig(theme?.center, base.center),\n legend: resolveLegendConfig(theme?.legend, base.legend),\n spotlight: {\n dimOpacity: theme?.spotlight?.dimOpacity ?? base.spotlight.dimOpacity,\n },\n }\n}\n\n/** {@link PieChart}'s `theme` property tween — leaf by leaf. */\nexport function lerpPieChartTheme(\n from: PieChartConfig,\n to: PieChartConfig,\n t: number\n): PieChartConfig {\n return {\n // A palette is a list of independent paints rather than one value, so it\n // interpolates pairwise by index; one that grows or shrinks holds the longer\n // side's tail, exactly as the cartesian charts' palettes do.\n sliceFills: lerpSeriesFills(from.sliceFills, to.sliceFills, t),\n ring: lerpRingConfig(from.ring, to.ring, t),\n label: lerpLabelConfig(from.label, to.label, t),\n center: lerpCenterConfig(from.center, to.center, t),\n legend: lerpLegendConfig(from.legend, to.legend, t),\n spotlight: {\n dimOpacity: lerpNumber(\n from.spotlight.dimOpacity,\n to.spotlight.dimOpacity,\n t\n ),\n },\n }\n}\n"]}
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/pie-chart/shared.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n clamp,\n lerpNumber,\n shadowOps,\n strokeOps,\n textOps,\n type DataRecord,\n type Fill,\n type Shadow,\n type ShadowResolved,\n type Stroke,\n type StrokeResolved,\n type TextStyle,\n type Vector2,\n} from \"@motionscript/sdk\"\n\nimport {\n CHART_PALETTE,\n DEFAULT_LEGEND,\n DEFAULT_SERIES_FILLS,\n lerpLegendConfig,\n lerpSeriesFills,\n num,\n resolveLegendConfig,\n resolveSeriesFills,\n text,\n type LegendConfig,\n type LegendTheme,\n} from \"../kit/shared\"\n\n/** One resolved slice, which the ring and legend consume; the chart resolves it from `data` and two field names. */\nexport interface PieSlice {\n /** Label and legend caption. */\n category: string\n /** The wedge covers `value / (sum of all values)`. */\n value: number\n /** Wedge and swatch paint. */\n fill: Fill\n}\n\n/**\n * One slice per row, painted from `palette` by row order (cycling). Paint comes\n * from the palette, not the rows, because a pie's mark is a row where a line's\n * colour belongs to a column; that lets a pie be fed a CSV of plain numbers.\n */\nexport function resolvePieSlices(\n data: DataRecord[],\n category: string,\n value: string,\n palette: Fill[]\n): PieSlice[] {\n return data.map((row, i) => ({\n category: text(row, category),\n value: num(row, value),\n fill:\n palette.length > 0 ? palette[i % palette.length] : (\"white\" as Fill),\n }))\n}\n\n/** What a {@link PieChart}'s labels say. */\nexport type PieLabelMode =\n /** Two lines, whose order flips with placement: a leader label reads category over percentage, an inside label reads the percentage big over a small category. */\n | \"both\"\n | \"category\"\n | \"percent\"\n /** No labels and no leader lines. */\n | \"none\"\n\n/** `\"inside\"` centres the label in the wedge's band with no leader or gutter, so the ring draws bigger; `\"leader\"` is what a sliver too narrow for text needs. */\nexport type PieLabelPlacement = \"inside\" | \"leader\" | \"none\"\n\nexport const DEG = Math.PI / 180\n\n/**\n * Opacity of a slice under the selection: 1 while nothing is out (`focus` is the\n * most-grown pop) and for the selected slice itself, falling toward `dimOpacity`\n * for the rest as the selection grows.\n */\nexport function selectionFade(focus: number, dimOpacity: number, pop: number): number {\n if (focus <= 0) return 1\n return 1 - (1 - clamp(dimOpacity, 0, 1)) * focus * (1 - pop)\n}\n\n/** A slice's plotted value: negatives and non-finite values count as 0. */\nexport function sliceValue(slice: PieSlice): number {\n return slice.value > 0 && Number.isFinite(slice.value) ? slice.value : 0\n}\n\n/** Sum of every slice's plotted value, the denominator of every share. */\nexport function sliceTotal(slices: PieSlice[]): number {\n let sum = 0\n for (const slice of slices) sum += sliceValue(slice)\n return sum\n}\n\n/** The point at radius `r`, angle `a` on a ring about the origin in centred y-up space. Angles run clockwise from 12 o'clock. */\nexport function ringPoint(r: number, a: number): Vector2 {\n return { x: r * Math.sin(a), y: r * Math.cos(a) }\n}\n\n/** One slice's resolved angular span on the ring. */\nexport interface PieArc {\n /** Index of the slice in the source list. */\n index: number\n /** Radians clockwise from 12 o'clock. */\n a0: number\n a1: number\n /** Where the slice's label and pop-out point. */\n mid: number\n /** Share of the total, `[0, 1]`. */\n fraction: number\n}\n\n/**\n * One arc per positive slice, walking clockwise from `startDegrees` and abutting.\n * Arcs are the undivided shares: a fixed angular gap would pinch shut at the\n * hole, so the gap is applied when the wedge is drawn, as a constant-width inset\n * off both ends.\n */\nexport function pieArcs(slices: PieSlice[], startDegrees: number): PieArc[] {\n const total = sliceTotal(slices)\n if (total <= 0) return []\n\n const arcs: PieArc[] = []\n let cursor = startDegrees * DEG\n slices.forEach((slice, index) => {\n const value = sliceValue(slice)\n if (value <= 0) return\n const fraction = value / total\n const a0 = cursor\n const a1 = a0 + fraction * Math.PI * 2\n cursor = a1\n arcs.push({ index, a0, a1, mid: (a0 + a1) / 2, fraction })\n })\n return arcs\n}\n\ninterface LabelSlot {\n /** Where the label would sit if nothing else were in the way. */\n ideal: number\n /** Written by {@link relaxLabels}. */\n y: number\n}\n\n/**\n * Spread one column of labels so none overlap, each near its `ideal` y and all\n * inside `[bottom, top]`. A top-down pass clears each label from the one above\n * by `minGap`; a reverse pass lifts a tail pushed past `bottom`. When the column\n * cannot hold them all, the reverse pass wins and the crowding is shared evenly.\n * Mutates each `y` and returns `slots` ordered top to bottom.\n */\nexport function relaxLabels<T extends LabelSlot>(\n slots: T[],\n minGap: number,\n top: number,\n bottom: number\n): T[] {\n slots.sort((a, b) => b.ideal - a.ideal)\n\n let ceiling = top\n for (const slot of slots) {\n slot.y = Math.min(slot.ideal, ceiling)\n ceiling = slot.y - minGap\n }\n\n let floor = bottom\n for (let i = slots.length - 1; i >= 0; i--) {\n slots[i].y = Math.max(slots[i].y, floor)\n floor = slots[i].y + minGap\n }\n\n return slots\n}\n\n/** The ring's geometry and paint: everything about the wedges but their colour. */\nexport interface RingConfig {\n /** Channel between adjacent slices in px, constant across the band (an angular gap would splay open toward the rim). */\n gap: number\n /** Below 1 a fraction of the outer radius (`0.6` leaves a band 40% thick); 1 and up a pixel radius; `0` draws solid wedges. */\n holeRadius: number\n /** Degrees clockwise from 12 o'clock where the first slice begins. */\n startAngle: number\n /** How far a selected slice's rim reaches, as a multiple of the outer radius so the emphasis scales with the chart. Clamped to `[1, 1.6]`. */\n selectedGrow: number\n stroke: Stroke\n shadow: Shadow\n}\n\n/** How the ring's labels are set, and the leader lines that point at them. */\nexport interface LabelConfig {\n /** The category caption. */\n textStyle: TextStyle\n /** The percentage. */\n percentStyle: TextStyle\n /** Both lines of a label inside its wedge: it sits on the wedge's paint, so it usually wants the opposite end of the value range from everything else. */\n insideStyle: TextStyle\n /** Runs from a wedge out to its label. */\n leaderStroke: Stroke\n}\n\n/** The summary set in the ring's hole. */\nexport interface CenterConfig {\n labelStyle: TextStyle\n captionStyle: TextStyle\n}\n\n/** A {@link PieChart}'s full, resolved visual theme. */\nexport interface PieChartConfig {\n /** Wedge and swatch paint, cycled by **row** index (`i % length`). */\n sliceFills: Fill[]\n ring: RingConfig\n label: LabelConfig\n center: CenterConfig\n legend: LegendConfig\n /** How far everything outside the selection fades while any slice is selected. */\n spotlight: { dimOpacity: number }\n}\n\n/** The author's partial theme, deep-merged onto {@link DEFAULT_PIE_CHART_THEME}. */\nexport type PieChartTheme = {\n sliceFills?: Fill[]\n ring?: Partial<RingConfig>\n label?: Partial<LabelConfig>\n center?: Partial<CenterConfig>\n legend?: LegendTheme\n spotlight?: Partial<PieChartConfig[\"spotlight\"]>\n}\n\nconst LABEL_TEXT_STYLE: TextStyle = {\n fontSize: 26,\n fontWeight: 600,\n textAlign: \"center\",\n fill: CHART_PALETTE.markerColor,\n}\n\n/** Fainter than `SPOTLIGHT_DIM`: a ring's slices touch, so at that a selected slice barely separates. */\nexport const PIE_SPOTLIGHT_DIM = 0.1\n\nexport const DEFAULT_PIE_CHART_THEME: PieChartConfig = {\n sliceFills: DEFAULT_SERIES_FILLS,\n ring: {\n gap: 6,\n // A donut by default: the hole is the one place a conclusion can be set.\n holeRadius: 0.6,\n startAngle: 0,\n selectedGrow: 1.08,\n stroke: [],\n shadow: [],\n },\n label: {\n textStyle: LABEL_TEXT_STYLE,\n percentStyle: { ...LABEL_TEXT_STYLE, fontWeight: 700 },\n // On a mid-tone wedge, so the panel colour rather than the axis ink.\n insideStyle: { ...LABEL_TEXT_STYLE, fontWeight: 800, fill: \"#15130e\" },\n leaderStroke: { weight: 2, fill: CHART_PALETTE.markerColor },\n },\n center: {\n labelStyle: {\n fontSize: 46,\n fontWeight: 800,\n textAlign: \"center\",\n fill: CHART_PALETTE.markerColor,\n },\n captionStyle: {\n fontSize: 22,\n fontWeight: 500,\n textAlign: \"center\",\n fill: CHART_PALETTE.markerColor,\n },\n },\n legend: DEFAULT_LEGEND,\n spotlight: { dimOpacity: PIE_SPOTLIGHT_DIM },\n}\n\nfunction resolveRingConfig(\n ring: Partial<RingConfig> | undefined,\n previous: RingConfig\n): RingConfig {\n return {\n gap: ring?.gap ?? previous.gap,\n holeRadius: ring?.holeRadius ?? previous.holeRadius,\n startAngle: ring?.startAngle ?? previous.startAngle,\n selectedGrow: ring?.selectedGrow ?? previous.selectedGrow,\n stroke: strokeOps.resolve(ring?.stroke ?? previous.stroke),\n shadow: shadowOps.resolve(ring?.shadow ?? previous.shadow),\n }\n}\n\nfunction lerpRingConfig(\n from: RingConfig,\n to: RingConfig,\n t: number\n): RingConfig {\n return {\n gap: lerpNumber(from.gap, to.gap, t),\n holeRadius: lerpNumber(from.holeRadius, to.holeRadius, t),\n startAngle: lerpNumber(from.startAngle, to.startAngle, t),\n selectedGrow: lerpNumber(from.selectedGrow, to.selectedGrow, t),\n stroke: strokeOps.lerp(\n from.stroke as StrokeResolved[],\n to.stroke as StrokeResolved[],\n t\n ),\n shadow: shadowOps.lerp(\n from.shadow as ShadowResolved[],\n to.shadow as ShadowResolved[],\n t\n ),\n }\n}\n\nfunction resolveLabelConfig(\n label: Partial<LabelConfig> | undefined,\n previous: LabelConfig\n): LabelConfig {\n return {\n textStyle: textOps.resolveStyle(label?.textStyle, previous.textStyle),\n percentStyle: textOps.resolveStyle(\n label?.percentStyle,\n previous.percentStyle\n ),\n insideStyle: textOps.resolveStyle(label?.insideStyle, previous.insideStyle),\n leaderStroke: strokeOps.resolve(\n label?.leaderStroke ?? previous.leaderStroke\n ),\n }\n}\n\nfunction lerpLabelConfig(\n from: LabelConfig,\n to: LabelConfig,\n t: number\n): LabelConfig {\n return {\n textStyle: textOps.lerpStyle(from.textStyle, to.textStyle, t),\n percentStyle: textOps.lerpStyle(from.percentStyle, to.percentStyle, t),\n insideStyle: textOps.lerpStyle(from.insideStyle, to.insideStyle, t),\n leaderStroke: strokeOps.lerp(\n from.leaderStroke as StrokeResolved[],\n to.leaderStroke as StrokeResolved[],\n t\n ),\n }\n}\n\nfunction resolveCenterConfig(\n center: Partial<CenterConfig> | undefined,\n previous: CenterConfig\n): CenterConfig {\n return {\n labelStyle: textOps.resolveStyle(center?.labelStyle, previous.labelStyle),\n captionStyle: textOps.resolveStyle(\n center?.captionStyle,\n previous.captionStyle\n ),\n }\n}\n\nfunction lerpCenterConfig(\n from: CenterConfig,\n to: CenterConfig,\n t: number\n): CenterConfig {\n return {\n labelStyle: textOps.lerpStyle(from.labelStyle, to.labelStyle, t),\n captionStyle: textOps.lerpStyle(from.captionStyle, to.captionStyle, t),\n }\n}\n\n/** The `theme` entry's resolve: merges a partial {@link PieChartTheme} onto `previous` (first resolution: the default). */\nexport function resolvePieChartTheme(\n theme: PieChartTheme | undefined,\n previous?: PieChartConfig\n): PieChartConfig {\n const base = previous ?? DEFAULT_PIE_CHART_THEME\n return {\n sliceFills: resolveSeriesFills(theme?.sliceFills, base.sliceFills),\n ring: resolveRingConfig(theme?.ring, base.ring),\n label: resolveLabelConfig(theme?.label, base.label),\n center: resolveCenterConfig(theme?.center, base.center),\n legend: resolveLegendConfig(theme?.legend, base.legend),\n spotlight: {\n dimOpacity: theme?.spotlight?.dimOpacity ?? base.spotlight.dimOpacity,\n },\n }\n}\n\nexport function lerpPieChartTheme(\n from: PieChartConfig,\n to: PieChartConfig,\n t: number\n): PieChartConfig {\n return {\n sliceFills: lerpSeriesFills(from.sliceFills, to.sliceFills, t),\n ring: lerpRingConfig(from.ring, to.ring, t),\n label: lerpLabelConfig(from.label, to.label, t),\n center: lerpCenterConfig(from.center, to.center, t),\n legend: lerpLegendConfig(from.legend, to.legend, t),\n spotlight: {\n dimOpacity: lerpNumber(\n from.spotlight.dimOpacity,\n to.spotlight.dimOpacity,\n t\n ),\n },\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motionscript/charts",
3
- "version": "0.1.0",
3
+ "version": "0.1.1-alpha.0",
4
4
  "type": "module",
5
5
  "description": "Animated data charts for Motion Script: a line chart, a bar chart and a pie chart sharing one axis, legend and theme kit.",
6
6
  "repository": {
@@ -50,7 +50,7 @@
50
50
  "license": "Apache-2.0",
51
51
  "author": "Motionscript",
52
52
  "peerDependencies": {
53
- "@motionscript/sdk": "^0.1.0"
53
+ "@motionscript/sdk": "^0.1.1-alpha.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/node": "^25.6.2",
@@ -61,8 +61,8 @@
61
61
  "typescript": "^6.0.3",
62
62
  "typescript-eslint": "^8.48.0",
63
63
  "vitest": "^4.1.5",
64
- "@motionscript/core": "0.1.0",
65
- "@motionscript/sdk": "0.1.0"
64
+ "@motionscript/core": "0.1.1-alpha.0",
65
+ "@motionscript/sdk": "0.1.1-alpha.0"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json --resolve-full-paths && node ../../../scripts/build-browser.mjs",