@motionscript/charts 0.1.1-alpha.0 → 0.1.1-alpha.2

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 (207) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +109 -28
  3. package/dist/bar-chart/bar-chart.d.ts +265 -39
  4. package/dist/bar-chart/bar-chart.d.ts.map +1 -1
  5. package/dist/bar-chart/bar-chart.js +219 -171
  6. package/dist/bar-chart/bar-chart.js.map +1 -1
  7. package/dist/bar-chart/category-axis.d.ts +12 -7
  8. package/dist/bar-chart/category-axis.d.ts.map +1 -1
  9. package/dist/bar-chart/category-axis.js +56 -42
  10. package/dist/bar-chart/category-axis.js.map +1 -1
  11. package/dist/bar-chart/chart-bars.d.ts +76 -52
  12. package/dist/bar-chart/chart-bars.d.ts.map +1 -1
  13. package/dist/bar-chart/chart-bars.js +105 -89
  14. package/dist/bar-chart/chart-bars.js.map +1 -1
  15. package/dist/bar-chart/index.d.ts +4 -4
  16. package/dist/bar-chart/index.d.ts.map +1 -1
  17. package/dist/bar-chart/index.js +2 -2
  18. package/dist/bar-chart/index.js.map +1 -1
  19. package/dist/bar-chart/shared.d.ts +41 -65
  20. package/dist/bar-chart/shared.d.ts.map +1 -1
  21. package/dist/bar-chart/shared.js +73 -117
  22. package/dist/bar-chart/shared.js.map +1 -1
  23. package/dist/browser/chunks/chunk-7XCRX6GE.js +2 -0
  24. package/dist/browser/chunks/chunk-7XCRX6GE.js.map +7 -0
  25. package/dist/browser/index.js +1 -1
  26. package/dist/browser/index.js.map +4 -4
  27. package/dist/browser/kit.js +1 -1
  28. package/dist/browser/manifest.json +1 -1
  29. package/dist/engine.d.ts +5 -2
  30. package/dist/engine.d.ts.map +1 -1
  31. package/dist/engine.js +5 -2
  32. package/dist/engine.js.map +1 -1
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/kit/cartesian.d.ts +51 -15
  38. package/dist/kit/cartesian.d.ts.map +1 -1
  39. package/dist/kit/cartesian.js +81 -29
  40. package/dist/kit/cartesian.js.map +1 -1
  41. package/dist/kit/chart-canvas.d.ts +32 -24
  42. package/dist/kit/chart-canvas.d.ts.map +1 -1
  43. package/dist/kit/chart-canvas.js +52 -46
  44. package/dist/kit/chart-canvas.js.map +1 -1
  45. package/dist/kit/chart-legend.d.ts +8 -4
  46. package/dist/kit/chart-legend.d.ts.map +1 -1
  47. package/dist/kit/chart-legend.js +30 -20
  48. package/dist/kit/chart-legend.js.map +1 -1
  49. package/dist/{line-chart → kit}/horizontal-axis.d.ts +31 -6
  50. package/dist/kit/horizontal-axis.d.ts.map +1 -0
  51. package/dist/{line-chart → kit}/horizontal-axis.js +23 -24
  52. package/dist/kit/horizontal-axis.js.map +1 -0
  53. package/dist/kit/motion.d.ts +5 -0
  54. package/dist/kit/motion.d.ts.map +1 -1
  55. package/dist/kit/motion.js +8 -0
  56. package/dist/kit/motion.js.map +1 -1
  57. package/dist/{pie-chart → kit}/pie-legend.d.ts +3 -3
  58. package/dist/kit/pie-legend.d.ts.map +1 -0
  59. package/dist/{pie-chart → kit}/pie-legend.js +11 -10
  60. package/dist/kit/pie-legend.js.map +1 -0
  61. package/dist/kit/polar.d.ts +31 -0
  62. package/dist/kit/polar.d.ts.map +1 -0
  63. package/dist/kit/polar.js +33 -0
  64. package/dist/kit/polar.js.map +1 -0
  65. package/dist/kit/shared.d.ts +11 -50
  66. package/dist/kit/shared.d.ts.map +1 -1
  67. package/dist/kit/shared.js +58 -141
  68. package/dist/kit/shared.js.map +1 -1
  69. package/dist/kit/style.d.ts +517 -0
  70. package/dist/kit/style.d.ts.map +1 -0
  71. package/dist/kit/style.js +292 -0
  72. package/dist/kit/style.js.map +1 -0
  73. package/dist/kit/vertical-axis.d.ts +22 -2
  74. package/dist/kit/vertical-axis.d.ts.map +1 -1
  75. package/dist/kit/vertical-axis.js +56 -43
  76. package/dist/kit/vertical-axis.js.map +1 -1
  77. package/dist/line-chart/chart-line.d.ts +95 -3
  78. package/dist/line-chart/chart-line.d.ts.map +1 -1
  79. package/dist/line-chart/chart-line.js +165 -53
  80. package/dist/line-chart/chart-line.js.map +1 -1
  81. package/dist/line-chart/chart-region.d.ts +1 -1
  82. package/dist/line-chart/chart-region.d.ts.map +1 -1
  83. package/dist/line-chart/chart-region.js +7 -8
  84. package/dist/line-chart/chart-region.js.map +1 -1
  85. package/dist/line-chart/index.d.ts +5 -6
  86. package/dist/line-chart/index.d.ts.map +1 -1
  87. package/dist/line-chart/index.js +3 -4
  88. package/dist/line-chart/index.js.map +1 -1
  89. package/dist/line-chart/line-chart.d.ts +281 -18
  90. package/dist/line-chart/line-chart.d.ts.map +1 -1
  91. package/dist/line-chart/line-chart.js +284 -134
  92. package/dist/line-chart/line-chart.js.map +1 -1
  93. package/dist/line-chart/shared.d.ts +51 -34
  94. package/dist/line-chart/shared.d.ts.map +1 -1
  95. package/dist/line-chart/shared.js +175 -83
  96. package/dist/line-chart/shared.js.map +1 -1
  97. package/dist/line-chart/x-scale.d.ts +2 -0
  98. package/dist/line-chart/x-scale.d.ts.map +1 -1
  99. package/dist/line-chart/x-scale.js +15 -0
  100. package/dist/line-chart/x-scale.js.map +1 -1
  101. package/dist/nodes.d.ts +3 -1
  102. package/dist/nodes.d.ts.map +1 -1
  103. package/dist/nodes.js +9 -1
  104. package/dist/nodes.js.map +1 -1
  105. package/dist/pie-chart/index.d.ts +2 -3
  106. package/dist/pie-chart/index.d.ts.map +1 -1
  107. package/dist/pie-chart/index.js +1 -2
  108. package/dist/pie-chart/index.js.map +1 -1
  109. package/dist/pie-chart/pie-chart.d.ts +114 -18
  110. package/dist/pie-chart/pie-chart.d.ts.map +1 -1
  111. package/dist/pie-chart/pie-chart.js +38 -24
  112. package/dist/pie-chart/pie-chart.js.map +1 -1
  113. package/dist/pie-chart/pie-ring.d.ts +16 -5
  114. package/dist/pie-chart/pie-ring.d.ts.map +1 -1
  115. package/dist/pie-chart/pie-ring.js +89 -55
  116. package/dist/pie-chart/pie-ring.js.map +1 -1
  117. package/dist/pie-chart/shared.d.ts +61 -62
  118. package/dist/pie-chart/shared.d.ts.map +1 -1
  119. package/dist/pie-chart/shared.js +44 -123
  120. package/dist/pie-chart/shared.js.map +1 -1
  121. package/dist/radar-chart/index.d.ts +5 -0
  122. package/dist/radar-chart/index.d.ts.map +1 -0
  123. package/dist/radar-chart/index.js +5 -0
  124. package/dist/radar-chart/index.js.map +1 -0
  125. package/dist/radar-chart/radar-chart.d.ts +212 -0
  126. package/dist/radar-chart/radar-chart.d.ts.map +1 -0
  127. package/dist/radar-chart/radar-chart.js +224 -0
  128. package/dist/radar-chart/radar-chart.js.map +1 -0
  129. package/dist/radar-chart/radar-grid.d.ts +49 -0
  130. package/dist/radar-chart/radar-grid.d.ts.map +1 -0
  131. package/dist/radar-chart/radar-grid.js +63 -0
  132. package/dist/radar-chart/radar-grid.js.map +1 -0
  133. package/dist/radar-chart/radar-series.d.ts +59 -0
  134. package/dist/radar-chart/radar-series.d.ts.map +1 -0
  135. package/dist/radar-chart/radar-series.js +71 -0
  136. package/dist/radar-chart/radar-series.js.map +1 -0
  137. package/dist/radar-chart/shared.d.ts +78 -0
  138. package/dist/radar-chart/shared.d.ts.map +1 -0
  139. package/dist/radar-chart/shared.js +129 -0
  140. package/dist/radar-chart/shared.js.map +1 -0
  141. package/dist/radial-chart/index.d.ts +6 -0
  142. package/dist/radial-chart/index.d.ts.map +1 -0
  143. package/dist/radial-chart/index.js +4 -0
  144. package/dist/radial-chart/index.js.map +1 -0
  145. package/dist/radial-chart/radial-chart.d.ts +210 -0
  146. package/dist/radial-chart/radial-chart.d.ts.map +1 -0
  147. package/dist/radial-chart/radial-chart.js +175 -0
  148. package/dist/radial-chart/radial-chart.js.map +1 -0
  149. package/dist/radial-chart/radial-rings.d.ts +121 -0
  150. package/dist/radial-chart/radial-rings.d.ts.map +1 -0
  151. package/dist/radial-chart/radial-rings.js +273 -0
  152. package/dist/radial-chart/radial-rings.js.map +1 -0
  153. package/dist/radial-chart/shared.d.ts +77 -0
  154. package/dist/radial-chart/shared.d.ts.map +1 -0
  155. package/dist/radial-chart/shared.js +78 -0
  156. package/dist/radial-chart/shared.js.map +1 -0
  157. package/package.json +4 -4
  158. package/registry.json +21 -6
  159. package/src/bar-chart/bar-chart.ts +263 -179
  160. package/src/bar-chart/category-axis.ts +46 -40
  161. package/src/bar-chart/chart-bars.ts +136 -117
  162. package/src/bar-chart/index.ts +7 -10
  163. package/src/bar-chart/shared.ts +88 -233
  164. package/src/engine.ts +5 -2
  165. package/src/index.ts +2 -0
  166. package/src/kit/cartesian.ts +121 -40
  167. package/src/kit/chart-canvas.ts +49 -54
  168. package/src/kit/chart-legend.ts +28 -20
  169. package/src/kit/horizontal-axis.ts +114 -0
  170. package/src/kit/motion.ts +7 -0
  171. package/src/{pie-chart → kit}/pie-legend.ts +12 -11
  172. package/src/kit/polar.ts +54 -0
  173. package/src/kit/shared.ts +55 -277
  174. package/src/kit/style.ts +353 -0
  175. package/src/kit/vertical-axis.ts +67 -48
  176. package/src/line-chart/chart-line.ts +176 -54
  177. package/src/line-chart/chart-region.ts +6 -10
  178. package/src/line-chart/index.ts +14 -10
  179. package/src/line-chart/line-chart.ts +353 -151
  180. package/src/line-chart/shared.ts +193 -174
  181. package/src/line-chart/x-scale.ts +13 -0
  182. package/src/nodes.ts +9 -1
  183. package/src/pie-chart/index.ts +2 -23
  184. package/src/pie-chart/pie-chart.ts +54 -35
  185. package/src/pie-chart/pie-ring.ts +92 -71
  186. package/src/pie-chart/shared.ts +80 -270
  187. package/src/radar-chart/index.ts +4 -0
  188. package/src/radar-chart/radar-chart.ts +290 -0
  189. package/src/radar-chart/radar-grid.ts +83 -0
  190. package/src/radar-chart/radar-series.ts +88 -0
  191. package/src/radar-chart/shared.ts +153 -0
  192. package/src/radial-chart/index.ts +5 -0
  193. package/src/radial-chart/radial-chart.ts +212 -0
  194. package/src/radial-chart/radial-rings.ts +328 -0
  195. package/src/radial-chart/shared.ts +126 -0
  196. package/dist/browser/chunks/chunk-4SMRCU24.js +0 -2
  197. package/dist/browser/chunks/chunk-4SMRCU24.js.map +0 -7
  198. package/dist/kit/chart-border.d.ts +0 -18
  199. package/dist/kit/chart-border.d.ts.map +0 -1
  200. package/dist/kit/chart-border.js +0 -23
  201. package/dist/kit/chart-border.js.map +0 -1
  202. package/dist/line-chart/horizontal-axis.d.ts.map +0 -1
  203. package/dist/line-chart/horizontal-axis.js.map +0 -1
  204. package/dist/pie-chart/pie-legend.d.ts.map +0 -1
  205. package/dist/pie-chart/pie-legend.js.map +0 -1
  206. package/src/kit/chart-border.ts +0 -36
  207. package/src/line-chart/horizontal-axis.ts +0 -122
@@ -1,15 +1,16 @@
1
1
  import { Rect, Types, clamp, component2d, easeOut, } from "@motionscript/sdk";
2
2
  import { asParts, fresh } from "../kit/entries.js";
3
3
  import { beats } from "../kit/motion.js";
4
- import { PieLegend } from "./pie-legend.js";
4
+ import { PieLegend } from "../kit/pie-legend.js";
5
5
  import { PieRing } from "./pie-ring.js";
6
- import { DEFAULT_PIE_CHART_THEME, lerpPieChartTheme, resolvePieChartTheme, resolvePieSlices, selectionFade, } from "./shared.js";
6
+ import { CHART_STYLE_FAMILY, CHART_STYLE_PRESETS, migrateChartStyle, paletteFill } from "../kit/style.js";
7
+ import { PIE_SPOTLIGHT_DIM, PIE_STYLE_GROUPS, centerConfigOf, labelConfigOf, legendOf, resolvePieSlices, ringConfigOf, selectionFade, } from "./shared.js";
7
8
  /** How `enter` divides its duration: wedges sweeping in, then labels and legend together. Sums to 1. */
8
9
  const SWEEP_SHARE = 0.65;
9
10
  const LABEL_SHARE = 0.35;
10
11
  const SLICE_ARG = Types.number(0, { min: 0, step: 1 });
11
12
  function slicesOf(s) {
12
- return resolvePieSlices(s.data, s.categoryField, s.valueField, s.theme.sliceFills);
13
+ return resolvePieSlices(s.data, s.categoryField, s.valueField, s.data.map((_, i) => paletteFill(s.style, i)));
13
14
  }
14
15
  function popAt(s, index) {
15
16
  return clamp(s.pops[index] ?? 0, 0, 1);
@@ -23,8 +24,7 @@ function sliceDims(s) {
23
24
  let focus = 0;
24
25
  for (let i = 0; i < s.data.length; i++)
25
26
  focus = Math.max(focus, popAt(s, i));
26
- const dim = s.theme.spotlight.dimOpacity;
27
- return s.data.map((_, i) => selectionFade(focus, dim, popAt(s, i)));
27
+ return s.data.map((_, i) => selectionFade(focus, PIE_SPOTLIGHT_DIM, popAt(s, i)));
28
28
  }
29
29
  /** Every slice's pop eased to what `pop` asks for. A move that changes nothing still takes its slot. */
30
30
  function popping(timeline, s, pop) {
@@ -56,16 +56,14 @@ function partsOf(children) {
56
56
  export const PieChart = component2d({
57
57
  key: "pieChart",
58
58
  state: {
59
- data: fresh(() => []),
59
+ data: fresh(() => [], { kind: "dataset" }),
60
60
  /** Slice caption, used on the labels and the legend. */
61
61
  categoryField: "",
62
62
  /** Slice magnitude: the wedge's share of the ring. */
63
63
  valueField: "",
64
- theme: Types.custom(DEFAULT_PIE_CHART_THEME, { resolve: resolvePieChartTheme, lerp: lerpPieChartTheme }),
64
+ style: Types.style(CHART_STYLE_FAMILY, PIE_STYLE_GROUPS, { presets: CHART_STYLE_PRESETS }),
65
65
  /** What the ring's labels say. */
66
66
  labelMode: Types.custom("percent"),
67
- /** Where they sit: inside their wedge, outside on a leader, or nowhere. */
68
- labelPlacement: Types.custom("leader"),
69
67
  /** Gap between the ring and the legend, in px. */
70
68
  gap: 40,
71
69
  /** Text set in the ring's hole. */
@@ -74,8 +72,6 @@ export const PieChart = component2d({
74
72
  centerCaption: "",
75
73
  /** How far each slice is pushed out, `0`-`1` per index. Here, not on the ring, because the wedges and the legend both read it. */
76
74
  pops: fresh(() => []),
77
- /** Read as the chart is built: the legend is a part the chart builds or doesn't. */
78
- showLegend: Types.boolean(true, { animatable: false }),
79
75
  /** Read as the chart is built: whether the hole's summary is drawn at rest. A scene that wants it to land later sets `false` and runs `revealCenter`. */
80
76
  showCenter: Types.boolean(true, { animatable: false }),
81
77
  },
@@ -85,39 +81,57 @@ export const PieChart = component2d({
85
81
  height: "fill",
86
82
  pops: (state) => slicesOf(state).map(() => 0),
87
83
  },
84
+ migrate: { style: migrateChartStyle("pie") },
88
85
  allowedChildren: [],
89
86
  // 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.
90
87
  children: (state) => {
91
- const theme = () => state.theme;
88
+ const style = () => state.style;
92
89
  const slices = () => slicesOf(state);
93
90
  const parts = [
94
91
  new PieRing({
95
92
  width: "fill",
96
93
  height: "fill",
97
94
  slices,
98
- ring: () => theme().ring,
99
- label: () => theme().label,
100
- centerStyle: () => theme().center,
95
+ ring: () => ringConfigOf(style()),
96
+ label: () => labelConfigOf(style()),
97
+ centerStyle: () => centerConfigOf(style()),
101
98
  labelMode: state.labelMode,
102
- labelPlacement: state.labelPlacement,
99
+ labelPlacement: state.style.labelPlacement,
103
100
  pops: () => state.pops,
104
- dimOpacity: () => theme().spotlight.dimOpacity,
101
+ dimOpacity: PIE_SPOTLIGHT_DIM,
105
102
  centerLabel: state.centerLabel,
106
103
  centerCaption: state.centerCaption,
107
104
  centerReveal: state.showCenter === false ? 0 : 1,
108
105
  }),
109
106
  ];
110
- if (state.showLegend !== false) {
111
- parts.push(new PieLegend({
107
+ const position = state.style.legend;
108
+ if (position !== "none") {
109
+ const side = position === "right";
110
+ const legend = new PieLegend({
112
111
  slices,
113
- legend: () => theme().legend,
112
+ legend: () => legendOf(style()),
114
113
  dims: () => sliceDims(state),
114
+ flow: side ? "vertical" : "horizontal",
115
+ align: "center",
115
116
  // Spacing is fixed as built.
116
- gap: state.theme.legend.itemGap / 3,
117
- padding: state.theme.legend.padding,
118
- }));
117
+ gap: side ? legendOf(state.style).itemGap / 3 : legendOf(state.style).itemGap,
118
+ padding: 0,
119
+ });
120
+ if (position === "top")
121
+ parts.unshift(legend);
122
+ else
123
+ parts.push(legend);
119
124
  }
120
- const row = new Rect({ flow: "horizontal", width: "fill", height: "fill", gap: state.gap, align: "center", children: parts });
125
+ const row = new Rect({
126
+ flow: position === "right" || position === "none" ? "horizontal" : "vertical",
127
+ width: "fill",
128
+ height: "fill",
129
+ gap: state.gap,
130
+ padding: 24,
131
+ align: "center",
132
+ fill: () => state.style.background,
133
+ children: parts,
134
+ });
121
135
  return asParts([row]);
122
136
  },
123
137
  commands: {
@@ -1 +1 @@
1
- {"version":3,"file":"pie-chart.js","sourceRoot":"","sources":["../../src/pie-chart/pie-chart.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Rect,\n Types,\n clamp,\n component2d,\n easeOut,\n type ChildView,\n type DataRecord,\n type NodeOf,\n // Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.\n type Node2D,\n type Node2DProps as _Node2DProps,\n} from \"@motionscript/sdk\"\n\nimport { asParts, fresh } from \"../kit/entries\"\nimport { beats, type AnyTimeline, type Beat } from \"../kit/motion\"\nimport { PieLegend } from \"./pie-legend\"\nimport { PieRing } from \"./pie-ring\"\nimport {\n DEFAULT_PIE_CHART_THEME,\n lerpPieChartTheme,\n resolvePieChartTheme,\n resolvePieSlices,\n selectionFade,\n type PieChartConfig,\n type PieChartTheme,\n type PieLabelMode,\n type PieLabelPlacement,\n type PieSlice,\n} from \"./shared\"\n\nexport interface PieChartValues {\n readonly data: DataRecord[]\n readonly categoryField: string\n readonly valueField: string\n readonly theme: PieChartConfig\n readonly pops: number[]\n}\n\n/** How `enter` divides its duration: wedges sweeping in, then labels and legend together. Sums to 1. */\nconst SWEEP_SHARE = 0.65\nconst LABEL_SHARE = 0.35\n\nconst SLICE_ARG = Types.number(0, { min: 0, step: 1 })\n\nfunction slicesOf(s: PieChartValues): PieSlice[] {\n return resolvePieSlices(s.data, s.categoryField, s.valueField, s.theme.sliceFills)\n}\n\nfunction popAt(s: PieChartValues, index: number): number {\n return clamp(s.pops[index] ?? 0, 0, 1)\n}\n\n/** Whether slice `index` is past half way out, so a slice mid-swell is not yet counted. */\nexport function isSliceSelected(s: PieChartValues, index: number): boolean {\n return popAt(s, index) > 0.5\n}\n\n/** Per-slice opacity: 1 while nothing is out and for the selected slice, falling toward `dimOpacity` for the rest as the selection grows. */\nfunction sliceDims(s: PieChartValues): number[] {\n let focus = 0\n for (let i = 0; i < s.data.length; i++) focus = Math.max(focus, popAt(s, i))\n const dim = s.theme.spotlight.dimOpacity\n return s.data.map((_, i) => selectionFade(focus, dim, popAt(s, i)))\n}\n\n/** Every slice's pop eased to what `pop` asks for. A move that changes nothing still takes its slot. */\nfunction popping(timeline: AnyTimeline, s: PieChartValues, pop: (index: number) => number) {\n const from = s.data.map((_, i) => popAt(s, i))\n const targets = s.data.map((_, i) => clamp(pop(i), 0, 1))\n if (from.every((value, i) => value === targets[i])) return timeline.wait(timeline.duration)\n return timeline.animate((t) => ({ pops: from.map((value, i) => value + (targets[i] - value) * t) }))\n}\n\n/** The ring and legend among the children; the last row is the one the chart built. */\nfunction partsOf(children: readonly ChildView[]) {\n for (let i = children.length - 1; i >= 0; i--) {\n const row = children[i]\n const ring = Rect.is(row) ? row.children.find(PieRing.is) : undefined\n if (ring) return { ring, legend: row.children.find(PieLegend.is) ?? null }\n }\n return null\n}\n\n/**\n * A pie or donut chart: a {@link PieRing} filling what the hugging {@link PieLegend}\n * leaves. One mark per row, not per column, and nothing to hide, since dropping\n * a slice would re-cut every other wedge; `selectSlice` and its siblings swell\n * a slice and dim the rest instead.\n *\n * Resting state is the finished chart, except the hole's summary, which\n * `showCenter: false` holds back for `revealCenter`.\n */\nexport const PieChart = component2d({\n key: \"pieChart\",\n state: {\n data: fresh<DataRecord[]>(() => []),\n /** Slice caption, used on the labels and the legend. */\n categoryField: \"\",\n /** Slice magnitude: the wedge's share of the ring. */\n valueField: \"\",\n theme: Types.custom<PieChartTheme, PieChartConfig>(DEFAULT_PIE_CHART_THEME, { resolve: resolvePieChartTheme, lerp: lerpPieChartTheme }),\n /** What the ring's labels say. */\n labelMode: Types.custom<PieLabelMode>(\"percent\"),\n /** Where they sit: inside their wedge, outside on a leader, or nowhere. */\n labelPlacement: Types.custom<PieLabelPlacement>(\"leader\"),\n /** Gap between the ring and the legend, in px. */\n gap: 40,\n /** Text set in the ring's hole. */\n centerLabel: \"\",\n /** Smaller second line under `centerLabel`. */\n centerCaption: \"\",\n /** How far each slice is pushed out, `0`-`1` per index. Here, not on the ring, because the wedges and the legend both read it. */\n pops: fresh<number[]>(() => []),\n /** Read as the chart is built: the legend is a part the chart builds or doesn't. */\n showLegend: Types.boolean(true, { animatable: false }),\n /** Read as the chart is built: whether the hole's summary is drawn at rest. A scene that wants it to land later sets `false` and runs `revealCenter`. */\n showCenter: Types.boolean(true, { animatable: false }),\n },\n defaults: {\n // A node made of parts that fill would otherwise hug them.\n width: \"fill\",\n height: \"fill\",\n pops: (state) => slicesOf(state).map(() => 0),\n },\n allowedChildren: [],\n // 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.\n children: (state) => {\n const theme = () => state.theme\n const slices = () => slicesOf(state)\n const parts: Node2D[] = [\n new PieRing({\n width: \"fill\",\n height: \"fill\",\n slices,\n ring: () => theme().ring,\n label: () => theme().label,\n centerStyle: () => theme().center,\n labelMode: state.labelMode,\n labelPlacement: state.labelPlacement,\n pops: () => state.pops,\n dimOpacity: () => theme().spotlight.dimOpacity,\n centerLabel: state.centerLabel,\n centerCaption: state.centerCaption,\n centerReveal: state.showCenter === false ? 0 : 1,\n }) as unknown as Node2D,\n ]\n if (state.showLegend !== false) {\n parts.push(\n new PieLegend({\n slices,\n legend: () => theme().legend,\n dims: () => sliceDims(state),\n // Spacing is fixed as built.\n gap: state.theme.legend.itemGap / 3,\n padding: state.theme.legend.padding,\n }) as unknown as Node2D\n )\n }\n const row = new Rect({ flow: \"horizontal\", width: \"fill\", height: \"fill\", gap: state.gap, align: \"center\", children: parts })\n return asParts([row as unknown as Node2D])\n },\n commands: {\n /** Wedges sweep in clockwise from `startAngle`, then labels fade up and the legend staggers in. `duration` is the whole entrance; the hole's summary keeps its own signal. */\n enter: {\n args: {},\n // Held from arrival, so it never shows finished ahead of its draw.\n entrance: true,\n easing: easeOut(),\n run: (timeline, _state, children) => {\n const parts = partsOf(children)\n if (!parts) return timeline.wait(timeline.duration)\n const { ring, legend } = parts\n const sweep = timeline.duration * SWEEP_SHARE\n const labels = timeline.duration * LABEL_SHARE\n const at0 = (piece: Beat[\"piece\"]): Beat => ({ at: 0, duration: 0, piece })\n return beats(timeline, [\n at0(ring.to({ growth: 0, labelReveal: 0 })),\n ...(legend ? [at0(legend.run(\"arm\"))] : []),\n { at: 0, duration: sweep, piece: ring.run(\"sweep\") },\n { at: sweep, duration: labels, piece: ring.run(\"revealLabels\") },\n ...(legend ? [{ at: sweep, duration: labels, piece: legend.run(\"enter\") }] : []),\n ])\n },\n },\n /** Fade the hole's summary up. Not part of `enter`: it is usually the conclusion, which a scene lands on its own beat. */\n revealCenter: {\n args: {},\n easing: easeOut(),\n run: (timeline, _state, children) => partsOf(children)?.ring.run(\"revealCenter\") ?? timeline.wait(timeline.duration),\n },\n /** Grow slice `index` out past the ring's rim and dim the rest, leaving any other selected slice where it is. */\n selectSlice: {\n args: { sliceIndex: SLICE_ARG },\n easing: easeOut(),\n run: (timeline, state) => {\n const index = timeline.args.sliceIndex\n return popping(timeline, state, (i) => (i === index ? 1 : popAt(state, i)))\n },\n },\n /** Settle slice `index` back flush with the ring. */\n deselectSlice: {\n args: { sliceIndex: SLICE_ARG },\n easing: easeOut(),\n run: (timeline, state) => {\n const index = timeline.args.sliceIndex\n return popping(timeline, state, (i) => (i === index ? 0 : popAt(state, i)))\n },\n },\n /** Move the focus to slice `index` alone, settling whatever else was out. */\n selectOnlySlice: {\n args: { sliceIndex: SLICE_ARG },\n easing: easeOut(),\n run: (timeline, state) => {\n const index = timeline.args.sliceIndex\n return popping(timeline, state, (i) => (i === index ? 1 : 0))\n },\n },\n /** Settle every slice flush and bring the whole chart back to full. */\n clearSelection: {\n args: {},\n easing: easeOut(),\n run: (timeline, state) => popping(timeline, state, () => 0),\n },\n },\n})\n\nexport type PieChart = NodeOf<typeof PieChart>\n"]}
1
+ {"version":3,"file":"pie-chart.js","sourceRoot":"","sources":["../../src/pie-chart/pie-chart.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Rect,\n Types,\n clamp,\n component2d,\n easeOut,\n type ChildView,\n type DataRecord,\n type NodeOf,\n // Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.\n type Node2D,\n type Node2DProps as _Node2DProps,\n type TextStyle as _TextStyle,\n type Fill as _Fill,\n type FillResolved as _FillResolved,\n} from \"@motionscript/sdk\"\n\nimport { asParts, fresh } from \"../kit/entries\"\nimport { beats, type AnyTimeline, type Beat } from \"../kit/motion\"\nimport { PieLegend } from \"../kit/pie-legend\"\nimport { PieRing } from \"./pie-ring\"\nimport { CHART_STYLE_FAMILY, CHART_STYLE_PRESETS, migrateChartStyle, paletteFill } from \"../kit/style\"\nimport {\n PIE_SPOTLIGHT_DIM,\n PIE_STYLE_GROUPS,\n centerConfigOf,\n labelConfigOf,\n legendOf,\n resolvePieSlices,\n ringConfigOf,\n selectionFade,\n type PieLabelMode,\n type PieSlice,\n type PieStyle,\n} from \"./shared\"\n\nexport interface PieChartValues {\n readonly data: DataRecord[]\n readonly categoryField: string\n readonly valueField: string\n readonly style: PieStyle\n readonly pops: number[]\n}\n\n/** How `enter` divides its duration: wedges sweeping in, then labels and legend together. Sums to 1. */\nconst SWEEP_SHARE = 0.65\nconst LABEL_SHARE = 0.35\n\nconst SLICE_ARG = Types.number(0, { min: 0, step: 1 })\n\nfunction slicesOf(s: PieChartValues): PieSlice[] {\n return resolvePieSlices(\n s.data,\n s.categoryField,\n s.valueField,\n s.data.map((_, i) => paletteFill(s.style, i))\n )\n}\n\nfunction popAt(s: PieChartValues, index: number): number {\n return clamp(s.pops[index] ?? 0, 0, 1)\n}\n\n/** Whether slice `index` is past half way out, so a slice mid-swell is not yet counted. */\nexport function isSliceSelected(s: PieChartValues, index: number): boolean {\n return popAt(s, index) > 0.5\n}\n\n/** Per-slice opacity: 1 while nothing is out and for the selected slice, falling toward `dimOpacity` for the rest as the selection grows. */\nfunction sliceDims(s: PieChartValues): number[] {\n let focus = 0\n for (let i = 0; i < s.data.length; i++) focus = Math.max(focus, popAt(s, i))\n return s.data.map((_, i) => selectionFade(focus, PIE_SPOTLIGHT_DIM, popAt(s, i)))\n}\n\n/** Every slice's pop eased to what `pop` asks for. A move that changes nothing still takes its slot. */\nfunction popping(timeline: AnyTimeline, s: PieChartValues, pop: (index: number) => number) {\n const from = s.data.map((_, i) => popAt(s, i))\n const targets = s.data.map((_, i) => clamp(pop(i), 0, 1))\n if (from.every((value, i) => value === targets[i])) return timeline.wait(timeline.duration)\n return timeline.animate((t) => ({ pops: from.map((value, i) => value + (targets[i] - value) * t) }))\n}\n\n/** The ring and legend among the children; the last row is the one the chart built. */\nfunction partsOf(children: readonly ChildView[]) {\n for (let i = children.length - 1; i >= 0; i--) {\n const row = children[i]\n const ring = Rect.is(row) ? row.children.find(PieRing.is) : undefined\n if (ring) return { ring, legend: row.children.find(PieLegend.is) ?? null }\n }\n return null\n}\n\n/**\n * A pie or donut chart: a {@link PieRing} filling what the hugging {@link PieLegend}\n * leaves. One mark per row, not per column, and nothing to hide, since dropping\n * a slice would re-cut every other wedge; `selectSlice` and its siblings swell\n * a slice and dim the rest instead.\n *\n * Resting state is the finished chart, except the hole's summary, which\n * `showCenter: false` holds back for `revealCenter`.\n */\nexport const PieChart = component2d({\n key: \"pieChart\",\n state: {\n data: fresh<DataRecord[]>(() => [], { kind: \"dataset\" }),\n /** Slice caption, used on the labels and the legend. */\n categoryField: \"\",\n /** Slice magnitude: the wedge's share of the ring. */\n valueField: \"\",\n style: Types.style(CHART_STYLE_FAMILY, PIE_STYLE_GROUPS, { presets: CHART_STYLE_PRESETS }),\n /** What the ring's labels say. */\n labelMode: Types.custom<PieLabelMode>(\"percent\"),\n /** Gap between the ring and the legend, in px. */\n gap: 40,\n /** Text set in the ring's hole. */\n centerLabel: \"\",\n /** Smaller second line under `centerLabel`. */\n centerCaption: \"\",\n /** How far each slice is pushed out, `0`-`1` per index. Here, not on the ring, because the wedges and the legend both read it. */\n pops: fresh<number[]>(() => []),\n /** Read as the chart is built: whether the hole's summary is drawn at rest. A scene that wants it to land later sets `false` and runs `revealCenter`. */\n showCenter: Types.boolean(true, { animatable: false }),\n },\n defaults: {\n // A node made of parts that fill would otherwise hug them.\n width: \"fill\",\n height: \"fill\",\n pops: (state) => slicesOf(state).map(() => 0),\n },\n migrate: { style: migrateChartStyle(\"pie\") },\n allowedChildren: [],\n // 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.\n children: (state) => {\n const style = () => state.style\n const slices = () => slicesOf(state)\n const parts: Node2D[] = [\n new PieRing({\n width: \"fill\",\n height: \"fill\",\n slices,\n ring: () => ringConfigOf(style()),\n label: () => labelConfigOf(style()),\n centerStyle: () => centerConfigOf(style()),\n labelMode: state.labelMode,\n labelPlacement: state.style.labelPlacement,\n pops: () => state.pops,\n dimOpacity: PIE_SPOTLIGHT_DIM,\n centerLabel: state.centerLabel,\n centerCaption: state.centerCaption,\n centerReveal: state.showCenter === false ? 0 : 1,\n }) as unknown as Node2D,\n ]\n const position = state.style.legend\n if (position !== \"none\") {\n const side = position === \"right\"\n const legend = new PieLegend({\n slices,\n legend: () => legendOf(style()),\n dims: () => sliceDims(state),\n flow: side ? \"vertical\" : \"horizontal\",\n align: \"center\",\n // Spacing is fixed as built.\n gap: side ? legendOf(state.style).itemGap / 3 : legendOf(state.style).itemGap,\n padding: 0,\n }) as unknown as Node2D\n if (position === \"top\") parts.unshift(legend)\n else parts.push(legend)\n }\n const row = new Rect({\n flow: position === \"right\" || position === \"none\" ? \"horizontal\" : \"vertical\",\n width: \"fill\",\n height: \"fill\",\n gap: state.gap,\n padding: 24,\n align: \"center\",\n fill: () => state.style.background,\n children: parts,\n })\n return asParts([row as unknown as Node2D])\n },\n commands: {\n /** Wedges sweep in clockwise from `startAngle`, then labels fade up and the legend staggers in. `duration` is the whole entrance; the hole's summary keeps its own signal. */\n enter: {\n args: {},\n // Held from arrival, so it never shows finished ahead of its draw.\n entrance: true,\n easing: easeOut(),\n run: (timeline, _state, children) => {\n const parts = partsOf(children)\n if (!parts) return timeline.wait(timeline.duration)\n const { ring, legend } = parts\n const sweep = timeline.duration * SWEEP_SHARE\n const labels = timeline.duration * LABEL_SHARE\n const at0 = (piece: Beat[\"piece\"]): Beat => ({ at: 0, duration: 0, piece })\n return beats(timeline, [\n at0(ring.to({ growth: 0, labelReveal: 0 })),\n ...(legend ? [at0(legend.run(\"arm\"))] : []),\n { at: 0, duration: sweep, piece: ring.run(\"sweep\") },\n { at: sweep, duration: labels, piece: ring.run(\"revealLabels\") },\n ...(legend ? [{ at: sweep, duration: labels, piece: legend.run(\"enter\") }] : []),\n ])\n },\n },\n /** Fade the hole's summary up. Not part of `enter`: it is usually the conclusion, which a scene lands on its own beat. */\n revealCenter: {\n args: {},\n easing: easeOut(),\n run: (timeline, _state, children) => partsOf(children)?.ring.run(\"revealCenter\") ?? timeline.wait(timeline.duration),\n },\n /** Grow slice `index` out past the ring's rim and dim the rest, leaving any other selected slice where it is. */\n selectSlice: {\n args: { sliceIndex: SLICE_ARG },\n easing: easeOut(),\n run: (timeline, state) => {\n const index = timeline.args.sliceIndex\n return popping(timeline, state, (i) => (i === index ? 1 : popAt(state, i)))\n },\n },\n /** Settle slice `index` back flush with the ring. */\n deselectSlice: {\n args: { sliceIndex: SLICE_ARG },\n easing: easeOut(),\n run: (timeline, state) => {\n const index = timeline.args.sliceIndex\n return popping(timeline, state, (i) => (i === index ? 0 : popAt(state, i)))\n },\n },\n /** Move the focus to slice `index` alone, settling whatever else was out. */\n selectOnlySlice: {\n args: { sliceIndex: SLICE_ARG },\n easing: easeOut(),\n run: (timeline, state) => {\n const index = timeline.args.sliceIndex\n return popping(timeline, state, (i) => (i === index ? 1 : 0))\n },\n },\n /** Settle every slice flush and bring the whole chart back to full. */\n clearSelection: {\n args: {},\n easing: easeOut(),\n run: (timeline, state) => popping(timeline, state, () => 0),\n },\n },\n})\n\nexport type PieChart = NodeOf<typeof PieChart>\n"]}
@@ -1,5 +1,7 @@
1
- import { type FillResolved, type NodeOf, type Node2D, type Node2DProps as _Node2DProps, type Vector2 } from "@motionscript/sdk";
1
+ import { PathBuilder, type Fill, type NodeOf, type Node2D, type Node2DProps as _Node2DProps, type Vector2 } from "@motionscript/sdk";
2
2
  import { type CenterConfig, type LabelConfig, type PieArc, type PieLabelMode, type PieLabelPlacement, type PieSlice, type RingConfig } from "./shared.js";
3
+ /** Black or white, whichever reads better on a solid `fill`; black for any other paint. */
4
+ export declare function onFill(fill: Fill): string;
3
5
  interface LabelPlacement {
4
6
  index: number;
5
7
  /** +1 right, -1 left. */
@@ -9,6 +11,16 @@ interface LabelPlacement {
9
11
  /** Where the label's inner edge is pinned. */
10
12
  text: Vector2;
11
13
  }
14
+ /**
15
+ * A wedge over `a0`..`a1` (radians clockwise from 12 o'clock) from `rInner` to
16
+ * `rOuter`, centred on `cx`; with `rInner <= 0` it closes at an apex pulled
17
+ * off the centre by the gap. `halfGap` is half the px gap to the neighbour,
18
+ * converted to an angle per radius so the channel keeps one width across the
19
+ * band; the inset is capped at the wedge's half-span so a sliver narrows to
20
+ * nothing rather than turning inside out. Corners are rounded by `corner` px
21
+ * (clamped to what the wedge can hold), except a pie's apex.
22
+ */
23
+ export declare function wedgePath(a0: number, a1: number, rInner: number, rOuter: number, halfGap: number, corner: number, cx: number): PathBuilder;
12
24
  interface RingState {
13
25
  readonly slices: PieSlice[];
14
26
  readonly ring: RingConfig;
@@ -79,7 +91,6 @@ export declare class RingGeometry {
79
91
  placements(): LabelPlacement[];
80
92
  placementFor(index: number): LabelPlacement | undefined;
81
93
  }
82
- export declare function ringFills(slices: readonly PieSlice[]): FillResolved[][];
83
94
  /**
84
95
  * The ring: one wedge per {@link PieSlice} swept clockwise from `startAngle`,
85
96
  * with a constant-width `gap` (see {@link wedgePath}). A selected slice swells
@@ -102,7 +113,7 @@ export declare const PieRing: import("@motionscript/sdk").Component<"pieRing", i
102
113
  /** Per-slice pop `[0, 1]`, owned by the chart so the ring and legend read one signal. */
103
114
  readonly pops: import("@motionscript/sdk").Type<number[], number[], false>;
104
115
  /** Opacity everything unselected falls back to while any slice is selected. */
105
- readonly dimOpacity: number;
116
+ readonly dimOpacity: 0.15;
106
117
  /** Empty leaves the hole clear; ignored when there is no hole. */
107
118
  readonly centerLabel: "";
108
119
  readonly centerCaption: "";
@@ -126,7 +137,7 @@ export declare const PieRing: import("@motionscript/sdk").Component<"pieRing", i
126
137
  /** Per-slice pop `[0, 1]`, owned by the chart so the ring and legend read one signal. */
127
138
  readonly pops: import("@motionscript/sdk").Type<number[], number[], false>;
128
139
  /** Opacity everything unselected falls back to while any slice is selected. */
129
- readonly dimOpacity: number;
140
+ readonly dimOpacity: 0.15;
130
141
  /** Empty leaves the hole clear; ignored when there is no hole. */
131
142
  readonly centerLabel: "";
132
143
  readonly centerCaption: "";
@@ -146,7 +157,7 @@ export declare const PieRing: import("@motionscript/sdk").Component<"pieRing", i
146
157
  /** Per-slice pop `[0, 1]`, owned by the chart so the ring and legend read one signal. */
147
158
  readonly pops: import("@motionscript/sdk").Type<number[], number[], false>;
148
159
  /** Opacity everything unselected falls back to while any slice is selected. */
149
- readonly dimOpacity: number;
160
+ readonly dimOpacity: 0.15;
150
161
  /** Empty leaves the hole clear; ignored when there is no hole. */
151
162
  readonly centerLabel: "";
152
163
  readonly centerCaption: "";
@@ -1 +1 @@
1
- {"version":3,"file":"pie-ring.d.ts","sourceRoot":"","sources":["../../src/pie-chart/pie-ring.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pie-ring.d.ts","sourceRoot":"","sources":["../../src/pie-chart/pie-ring.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,30 @@
1
- import { Graphics2D, PathBuilder, Rect, Text, Types, clamp, component2d, fillOps, shadowOps, strokeOps, textOps, } from "@motionscript/sdk";
1
+ import { Graphics2D, PathBuilder, Rect, Text, Types, clamp, component2d, strokeOps, textOps, } from "@motionscript/sdk";
2
2
  import { asParts, fresh } from "../kit/entries.js";
3
3
  import { frameEase } from "../kit/motion.js";
4
4
  import { loadPaints } from "../kit/paints.js";
5
+ import { resolvedFill, ringFills } from "../kit/polar.js";
5
6
  import { formatPercent } from "../kit/shared.js";
6
- import { DEFAULT_PIE_CHART_THEME, pieArcs, relaxLabels, ringPoint, selectionFade, } from "./shared.js";
7
+ import { PIE_SPOTLIGHT_DIM, pieArcs, relaxLabels, ringPoint, selectionFade, } from "./shared.js";
8
+ const DEFAULT_RING = { gap: 3, holeRadius: 0, startAngle: 0, selectedGrow: 1.08, cornerRadius: 4 };
9
+ const DEFAULT_LABEL = {
10
+ textStyle: { fontSize: 28, fontWeight: 500, textAlign: "center", fill: "#a1a1aa" },
11
+ percentStyle: { fontSize: 28, fontWeight: 700, textAlign: "center", fill: "#f4f4f5" },
12
+ insideStyle: { fontSize: 52, fontWeight: 700, textAlign: "center", fill: "#111113" },
13
+ leaderStroke: { weight: 2, fill: "#a1a1aa" },
14
+ };
15
+ const DEFAULT_CENTER = {
16
+ labelStyle: { fontSize: 50, fontWeight: 800, textAlign: "center", fill: "#f4f4f5" },
17
+ captionStyle: { fontSize: 22, fontWeight: 500, textAlign: "center", fill: "#a1a1aa" },
18
+ };
19
+ /** Black or white, whichever reads better on a solid `fill`; black for any other paint. */
20
+ export function onFill(fill) {
21
+ const layers = resolvedFill(fill);
22
+ const solid = layers.find((layer) => layer.type === "solid");
23
+ if (!solid)
24
+ return "#111113";
25
+ const [r, g, b] = solid.color;
26
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.45 ? "#111113" : "#ffffff";
27
+ }
7
28
  const LABEL_GAP = 12;
8
29
  const LABEL_PAD = 14;
9
30
  /** Rough glyph width as a fraction of the font size, for gutter sizing. */
@@ -32,46 +53,72 @@ function sizeOf(style, fallback = FALLBACK_SIZE) {
32
53
  }
33
54
  /**
34
55
  * A wedge over `a0`..`a1` (radians clockwise from 12 o'clock) from `rInner` to
35
- * `rOuter`, centred on `cx`; with `rInner <= 0` it closes through the centre.
36
- * `halfGap` is half the px gap to the neighbour, converted to an angle per
37
- * radius (wider near the hole) so the channel keeps one width across the band.
38
- * The inset is capped at the wedge's half-span so a sliver narrows to nothing
39
- * rather than turning inside out.
56
+ * `rOuter`, centred on `cx`; with `rInner <= 0` it closes at an apex pulled
57
+ * off the centre by the gap. `halfGap` is half the px gap to the neighbour,
58
+ * converted to an angle per radius so the channel keeps one width across the
59
+ * band; the inset is capped at the wedge's half-span so a sliver narrows to
60
+ * nothing rather than turning inside out. Corners are rounded by `corner` px
61
+ * (clamped to what the wedge can hold), except a pie's apex.
40
62
  */
41
- function wedgePath(a0, a1, rInner, rOuter, halfGap, cx) {
63
+ export function wedgePath(a0, a1, rInner, rOuter, halfGap, corner, cx) {
42
64
  const path = new PathBuilder();
43
65
  const span = a1 - a0;
44
66
  const steps = Math.max(2, Math.ceil((Math.abs(span) / (Math.PI * 2)) * ARC_STEPS));
45
- const insetAt = (r) => r <= 0 ? 0 : Math.min(halfGap / r, span / 2.2);
46
- const arcTo = (r, i) => {
47
- const inset = insetAt(r);
48
- const from = a0 + inset;
49
- const to = a1 - inset;
50
- const point = ringPoint(r, from + ((to - from) * i) / steps);
51
- return { x: point.x + cx, y: point.y };
67
+ const insetAt = (r) => (r <= 0 ? 0 : Math.min(halfGap / r, span / 2.2));
68
+ const at = (r, a) => {
69
+ const p = ringPoint(r, a);
70
+ return { x: p.x + cx, y: p.y };
71
+ };
72
+ const hasHole = rInner > 0;
73
+ const oa0 = a0 + insetAt(rOuter);
74
+ const oa1 = a1 - insetAt(rOuter);
75
+ const ia0 = a0 + insetAt(rInner);
76
+ const ia1 = a1 - insetAt(rInner);
77
+ const O0 = at(rOuter, oa0);
78
+ const O1 = at(rOuter, oa1);
79
+ const half = Math.max(1e-3, Math.min(span / 2, Math.PI / 2));
80
+ const apex = at(halfGap > 0 ? Math.min(halfGap / Math.sin(half), rOuter * 0.8) : 0, (a0 + a1) / 2);
81
+ const I0 = hasHole ? at(rInner, ia0) : apex;
82
+ const I1 = hasHole ? at(rInner, ia1) : apex;
83
+ const band = hasHole ? rOuter - rInner : rOuter;
84
+ const rho = Math.max(0, Math.min(corner, band / 2.2, ((oa1 - oa0) * rOuter) / 2.2, hasHole ? ((ia1 - ia0) * rInner) / 2.2 : Infinity));
85
+ const rounded = rho >= 0.5;
86
+ const toward = (from, to, d) => {
87
+ const len = Math.hypot(to.x - from.x, to.y - from.y) || 1;
88
+ const k = Math.min(0.5, d / len);
89
+ return { x: from.x + (to.x - from.x) * k, y: from.y + (to.y - from.y) * k };
52
90
  };
53
- if (rInner <= 0) {
54
- // The sides converge on the centre, so only the rim carries the gap.
55
- path.moveTo(cx, 0);
91
+ const dOuter = rounded ? rho / Math.max(1, rOuter) : 0;
92
+ const dInner = rounded && hasHole ? rho / rInner : 0;
93
+ const arc = (r, from, to) => {
56
94
  for (let i = 0; i <= steps; i++) {
57
- const p = arcTo(rOuter, i);
95
+ const p = at(r, from + ((to - from) * i) / steps);
58
96
  path.lineTo(p.x, p.y);
59
97
  }
60
- path.close();
61
- return path;
62
- }
63
- for (let i = 0; i <= steps; i++) {
64
- const p = arcTo(rOuter, i);
65
- if (i === 0)
66
- path.moveTo(p.x, p.y);
98
+ };
99
+ const round = (corner, next) => {
100
+ if (rounded)
101
+ path.quadraticCurveTo(corner.x, corner.y, next.x, next.y);
67
102
  else
68
- path.lineTo(p.x, p.y);
103
+ path.lineTo(corner.x, corner.y);
104
+ };
105
+ const first = at(rOuter, oa0 + dOuter);
106
+ path.moveTo(first.x, first.y);
107
+ arc(rOuter, oa0 + dOuter, oa1 - dOuter);
108
+ round(O1, toward(O1, I1, rho));
109
+ if (hasHole) {
110
+ const beforeI1 = toward(I1, O1, rho);
111
+ path.lineTo(beforeI1.x, beforeI1.y);
112
+ round(I1, at(rInner, ia1 - dInner));
113
+ arc(rInner, ia1 - dInner, ia0 + dInner);
114
+ round(I0, toward(I0, O0, rho));
69
115
  }
70
- // The inner arc's own wider inset makes each side edge a chord `halfGap` px off the divide at both ends.
71
- for (let i = steps; i >= 0; i--) {
72
- const p = arcTo(rInner, i);
73
- path.lineTo(p.x, p.y);
116
+ else {
117
+ path.lineTo(apex.x, apex.y);
74
118
  }
119
+ const beforeO0 = toward(O0, I0, rounded ? rho : 0);
120
+ path.lineTo(beforeO0.x, beforeO0.y);
121
+ round(O0, first);
75
122
  path.close();
76
123
  return path;
77
124
  }
@@ -89,7 +136,7 @@ export class RingGeometry {
89
136
  }
90
137
  /** Leader labels are the only thing that costs a gutter. */
91
138
  get hasLeaderLabels() {
92
- return this.s.labelPlacement === "leader" && this.s.labelMode !== "none" && this.arcs.length > 0;
139
+ return this.s.labelPlacement === "outside" && this.s.labelMode !== "none" && this.arcs.length > 0;
93
140
  }
94
141
  /**
95
142
  * Px a horizontal label centred in `arc`'s band can run before leaving the
@@ -283,18 +330,6 @@ function sideOf(mid) {
283
330
  }
284
331
  const NO_BOX = { width: 0, height: 0 };
285
332
  /** Fills resolved once per paint, not per frame: a theme tween hands over new paint, but a frame that moves nothing must not re-resolve. Keyed by the paint, so it needs no node. */
286
- const resolvedFills = new WeakMap();
287
- function resolvedFill(fill) {
288
- if (typeof fill !== "object" || fill === null)
289
- return fillOps.resolve(fill);
290
- let resolved = resolvedFills.get(fill);
291
- if (resolved === undefined)
292
- resolvedFills.set(fill, resolved = fillOps.resolve(fill));
293
- return resolved;
294
- }
295
- export function ringFills(slices) {
296
- return slices.map((slice) => resolvedFill(slice.fill));
297
- }
298
333
  /** One label per drawn slice (a zero-valued slice has no wedge), then the hole's summary. Only the choice of form is settled here; the pinned points stay reactive. */
299
334
  function ringLabels(s) {
300
335
  const blocks = [];
@@ -311,6 +346,7 @@ function ringLabels(s) {
311
346
  inside.push(new Text({
312
347
  ...textOps.styleProps(s.label.insideStyle),
313
348
  text: () => geometry().percentText(arc.index),
349
+ fill: () => onFill(s.slices[arc.index]?.fill ?? "white"),
314
350
  fontSize: () => geometry().insideFont,
315
351
  }));
316
352
  }
@@ -318,6 +354,7 @@ function ringLabels(s) {
318
354
  inside.push(new Text({
319
355
  ...textOps.styleProps(s.label.insideStyle),
320
356
  text: () => s.slices[arc.index]?.category ?? "",
357
+ fill: () => onFill(s.slices[arc.index]?.fill ?? "white"),
321
358
  fontSize: () => (s.labelMode === "category" ? geometry().insideFont : geometry().insideFont * INSIDE_CAPTION_RATIO),
322
359
  }));
323
360
  }
@@ -388,7 +425,7 @@ function ringLabels(s) {
388
425
  height: "hug",
389
426
  gap: 2,
390
427
  align: "center",
391
- opacity: () => clamp(s.centerReveal, 0, 1),
428
+ opacity: () => clamp(s.centerReveal, 0, 1) * clamp(geometry().holeRadius / 20, 0, 1),
392
429
  center: () => ({ x: geometry().centerX, y: 0 }),
393
430
  children,
394
431
  }));
@@ -407,12 +444,12 @@ export const PieRing = component2d({
407
444
  key: "pieRing",
408
445
  state: {
409
446
  slices: fresh(() => []),
410
- ring: Types.custom(DEFAULT_PIE_CHART_THEME.ring),
411
- label: Types.custom(DEFAULT_PIE_CHART_THEME.label),
447
+ ring: Types.custom(DEFAULT_RING),
448
+ label: Types.custom(DEFAULT_LABEL),
412
449
  // Not `center`: every node already has one, the anchor its position is measured from.
413
- centerStyle: Types.custom(DEFAULT_PIE_CHART_THEME.center),
450
+ centerStyle: Types.custom(DEFAULT_CENTER),
414
451
  labelMode: Types.custom("percent"),
415
- labelPlacement: Types.custom("leader"),
452
+ labelPlacement: Types.custom("outside"),
416
453
  /** Sweep `[0, 1]`: the wedges grow clockwise from `startAngle` as it rises. */
417
454
  growth: 1,
418
455
  /** Fade of leaders and labels, separate from `growth` so they arrive after the sweep. */
@@ -420,7 +457,7 @@ export const PieRing = component2d({
420
457
  /** Per-slice pop `[0, 1]`, owned by the chart so the ring and legend read one signal. */
421
458
  pops: fresh(() => []),
422
459
  /** Opacity everything unselected falls back to while any slice is selected. */
423
- dimOpacity: DEFAULT_PIE_CHART_THEME.spotlight.dimOpacity,
460
+ dimOpacity: PIE_SPOTLIGHT_DIM,
424
461
  /** Empty leaves the hole clear; ignored when there is no hole. */
425
462
  centerLabel: "",
426
463
  centerCaption: "",
@@ -433,8 +470,7 @@ export const PieRing = component2d({
433
470
  // The paints sit past the paint entries: a fill per slice, the wedge outline and shadow, and the leader stroke.
434
471
  load: (loader, state) => loadPaints(loader, {
435
472
  fills: ringFills(state.slices),
436
- shadows: [shadowOps.resolve(state.ring.shadow)],
437
- strokes: [strokeOps.resolve(state.ring.stroke), strokeOps.resolve(state.label.leaderStroke)],
473
+ strokes: [strokeOps.resolve(state.label.leaderStroke)],
438
474
  }),
439
475
  commands: {
440
476
  /** Wipe the wedges in around the ring (`growth` 0 to 1). */
@@ -482,11 +518,9 @@ export const PieRing = component2d({
482
518
  const a1 = Math.min(arc.a1, revealEnd);
483
519
  if (a1 <= arc.a0)
484
520
  continue;
485
- const path = wedgePath(arc.a0, a1, hole, g.anchorRadius(arc.index), halfGap, cx);
521
+ const path = wedgePath(arc.a0, a1, hole, g.anchorRadius(arc.index), halfGap, Math.max(0, state.ring.cornerRadius), cx);
486
522
  const wedge = new Graphics2D().path(path.toPathState({ centerBounds, width: span, height: span }));
487
- wedge.shadow(state.ring.shadow);
488
523
  wedge.fill(fills[arc.index] ?? []);
489
- wedge.stroke(state.ring.stroke);
490
524
  ctx.draw(wedge.opacity(g.sliceOpacity(arc.index)));
491
525
  }
492
526
  // One `Graphics2D` per leader: open polylines chained into one command list