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

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 +19 -0
  2. package/README.md +98 -28
  3. package/dist/bar-chart/bar-chart.d.ts +233 -39
  4. package/dist/bar-chart/bar-chart.d.ts.map +1 -1
  5. package/dist/bar-chart/bar-chart.js +215 -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 +52 -52
  12. package/dist/bar-chart/chart-bars.d.ts.map +1 -1
  13. package/dist/bar-chart/chart-bars.js +80 -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 +36 -65
  20. package/dist/bar-chart/shared.d.ts.map +1 -1
  21. package/dist/bar-chart/shared.js +68 -117
  22. package/dist/bar-chart/shared.js.map +1 -1
  23. package/dist/browser/chunks/chunk-R3Q5EE6E.js +2 -0
  24. package/dist/browser/chunks/chunk-R3Q5EE6E.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 +49 -15
  38. package/dist/kit/cartesian.d.ts.map +1 -1
  39. package/dist/kit/cartesian.js +79 -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 +254 -0
  70. package/dist/kit/style.d.ts.map +1 -0
  71. package/dist/kit/style.js +197 -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 +257 -18
  90. package/dist/line-chart/line-chart.d.ts.map +1 -1
  91. package/dist/line-chart/line-chart.js +282 -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 +37 -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 +53 -61
  118. package/dist/pie-chart/shared.d.ts.map +1 -1
  119. package/dist/pie-chart/shared.js +41 -121
  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 +218 -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 +43 -0
  130. package/dist/radar-chart/radar-grid.d.ts.map +1 -0
  131. package/dist/radar-chart/radar-grid.js +61 -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 +73 -0
  138. package/dist/radar-chart/shared.d.ts.map +1 -0
  139. package/dist/radar-chart/shared.js +123 -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 +216 -0
  146. package/dist/radial-chart/radial-chart.d.ts.map +1 -0
  147. package/dist/radial-chart/radial-chart.js +174 -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 +73 -0
  154. package/dist/radial-chart/shared.d.ts.map +1 -0
  155. package/dist/radial-chart/shared.js +79 -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 +252 -179
  160. package/src/bar-chart/category-axis.ts +46 -40
  161. package/src/bar-chart/chart-bars.ts +97 -117
  162. package/src/bar-chart/index.ts +7 -10
  163. package/src/bar-chart/shared.ts +78 -238
  164. package/src/engine.ts +5 -2
  165. package/src/index.ts +2 -0
  166. package/src/kit/cartesian.ts +118 -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 +257 -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 +347 -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 +52 -35
  185. package/src/pie-chart/pie-ring.ts +92 -71
  186. package/src/pie-chart/shared.ts +68 -266
  187. package/src/radar-chart/index.ts +4 -0
  188. package/src/radar-chart/radar-chart.ts +287 -0
  189. package/src/radar-chart/radar-grid.ts +78 -0
  190. package/src/radar-chart/radar-series.ts +88 -0
  191. package/src/radar-chart/shared.ts +146 -0
  192. package/src/radial-chart/index.ts +5 -0
  193. package/src/radial-chart/radial-chart.ts +210 -0
  194. package/src/radial-chart/radial-rings.ts +328 -0
  195. package/src/radial-chart/shared.ts +114 -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
@@ -6,12 +6,9 @@ import {
6
6
  Types,
7
7
  clamp,
8
8
  component2d,
9
- fillOps,
10
- shadowOps,
11
9
  strokeOps,
12
10
  textOps,
13
11
  type Fill,
14
- type FillResolved,
15
12
  type NodeOf,
16
13
  // Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.
17
14
  type Node2D,
@@ -23,9 +20,11 @@ import {
23
20
  import { asParts, fresh } from "../kit/entries"
24
21
  import { frameEase } from "../kit/motion"
25
22
  import { loadPaints } from "../kit/paints"
23
+ import { resolvedFill, ringFills } from "../kit/polar"
26
24
  import { formatPercent } from "../kit/shared"
25
+
27
26
  import {
28
- DEFAULT_PIE_CHART_THEME,
27
+ PIE_SPOTLIGHT_DIM,
29
28
  pieArcs,
30
29
  relaxLabels,
31
30
  ringPoint,
@@ -39,6 +38,27 @@ import {
39
38
  type RingConfig,
40
39
  } from "./shared"
41
40
 
41
+ const DEFAULT_RING: RingConfig = { gap: 3, holeRadius: 0, startAngle: 0, selectedGrow: 1.08, cornerRadius: 4 }
42
+ const DEFAULT_LABEL: LabelConfig = {
43
+ textStyle: { fontSize: 28, fontWeight: 500, textAlign: "center", fill: "#a1a1aa" },
44
+ percentStyle: { fontSize: 28, fontWeight: 700, textAlign: "center", fill: "#f4f4f5" },
45
+ insideStyle: { fontSize: 52, fontWeight: 700, textAlign: "center", fill: "#111113" },
46
+ leaderStroke: { weight: 2, fill: "#a1a1aa" },
47
+ }
48
+ const DEFAULT_CENTER: CenterConfig = {
49
+ labelStyle: { fontSize: 50, fontWeight: 800, textAlign: "center", fill: "#f4f4f5" },
50
+ captionStyle: { fontSize: 22, fontWeight: 500, textAlign: "center", fill: "#a1a1aa" },
51
+ }
52
+
53
+ /** Black or white, whichever reads better on a solid `fill`; black for any other paint. */
54
+ export function onFill(fill: Fill): string {
55
+ const layers = resolvedFill(fill)
56
+ const solid = layers.find((layer) => layer.type === "solid") as { color: [number, number, number, number] } | undefined
57
+ if (!solid) return "#111113"
58
+ const [r, g, b] = solid.color
59
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.45 ? "#111113" : "#ffffff"
60
+ }
61
+
42
62
  const LABEL_GAP = 12
43
63
  const LABEL_PAD = 14
44
64
  /** Rough glyph width as a fraction of the font size, for gutter sizing. */
@@ -79,57 +99,72 @@ interface LabelPlacement {
79
99
 
80
100
  /**
81
101
  * A wedge over `a0`..`a1` (radians clockwise from 12 o'clock) from `rInner` to
82
- * `rOuter`, centred on `cx`; with `rInner <= 0` it closes through the centre.
83
- * `halfGap` is half the px gap to the neighbour, converted to an angle per
84
- * radius (wider near the hole) so the channel keeps one width across the band.
85
- * The inset is capped at the wedge's half-span so a sliver narrows to nothing
86
- * rather than turning inside out.
102
+ * `rOuter`, centred on `cx`; with `rInner <= 0` it closes at an apex pulled
103
+ * off the centre by the gap. `halfGap` is half the px gap to the neighbour,
104
+ * converted to an angle per radius so the channel keeps one width across the
105
+ * band; the inset is capped at the wedge's half-span so a sliver narrows to
106
+ * nothing rather than turning inside out. Corners are rounded by `corner` px
107
+ * (clamped to what the wedge can hold), except a pie's apex.
87
108
  */
88
- function wedgePath(
89
- a0: number,
90
- a1: number,
91
- rInner: number,
92
- rOuter: number,
93
- halfGap: number,
94
- cx: number
95
- ): PathBuilder {
109
+ export function wedgePath(a0: number, a1: number, rInner: number, rOuter: number, halfGap: number, corner: number, cx: number): PathBuilder {
96
110
  const path = new PathBuilder()
97
111
  const span = a1 - a0
98
- const steps = Math.max(
99
- 2,
100
- Math.ceil((Math.abs(span) / (Math.PI * 2)) * ARC_STEPS)
101
- )
102
- const insetAt = (r: number): number =>
103
- r <= 0 ? 0 : Math.min(halfGap / r, span / 2.2)
104
- const arcTo = (r: number, i: number): Vector2 => {
105
- const inset = insetAt(r)
106
- const from = a0 + inset
107
- const to = a1 - inset
108
- const point = ringPoint(r, from + ((to - from) * i) / steps)
109
- return { x: point.x + cx, y: point.y }
110
- }
111
-
112
- if (rInner <= 0) {
113
- // The sides converge on the centre, so only the rim carries the gap.
114
- path.moveTo(cx, 0)
112
+ const steps = Math.max(2, Math.ceil((Math.abs(span) / (Math.PI * 2)) * ARC_STEPS))
113
+ const insetAt = (r: number): number => (r <= 0 ? 0 : Math.min(halfGap / r, span / 2.2))
114
+ const at = (r: number, a: number): Vector2 => {
115
+ const p = ringPoint(r, a)
116
+ return { x: p.x + cx, y: p.y }
117
+ }
118
+ const hasHole = rInner > 0
119
+ const oa0 = a0 + insetAt(rOuter)
120
+ const oa1 = a1 - insetAt(rOuter)
121
+ const ia0 = a0 + insetAt(rInner)
122
+ const ia1 = a1 - insetAt(rInner)
123
+ const O0 = at(rOuter, oa0)
124
+ const O1 = at(rOuter, oa1)
125
+ const half = Math.max(1e-3, Math.min(span / 2, Math.PI / 2))
126
+ const apex = at(halfGap > 0 ? Math.min(halfGap / Math.sin(half), rOuter * 0.8) : 0, (a0 + a1) / 2)
127
+ const I0 = hasHole ? at(rInner, ia0) : apex
128
+ const I1 = hasHole ? at(rInner, ia1) : apex
129
+
130
+ const band = hasHole ? rOuter - rInner : rOuter
131
+ const rho = Math.max(0, Math.min(corner, band / 2.2, ((oa1 - oa0) * rOuter) / 2.2, hasHole ? ((ia1 - ia0) * rInner) / 2.2 : Infinity))
132
+ const rounded = rho >= 0.5
133
+ const toward = (from: Vector2, to: Vector2, d: number): Vector2 => {
134
+ const len = Math.hypot(to.x - from.x, to.y - from.y) || 1
135
+ const k = Math.min(0.5, d / len)
136
+ return { x: from.x + (to.x - from.x) * k, y: from.y + (to.y - from.y) * k }
137
+ }
138
+ const dOuter = rounded ? rho / Math.max(1, rOuter) : 0
139
+ const dInner = rounded && hasHole ? rho / rInner : 0
140
+
141
+ const arc = (r: number, from: number, to: number) => {
115
142
  for (let i = 0; i <= steps; i++) {
116
- const p = arcTo(rOuter, i)
143
+ const p = at(r, from + ((to - from) * i) / steps)
117
144
  path.lineTo(p.x, p.y)
118
145
  }
119
- path.close()
120
- return path
121
146
  }
122
-
123
- for (let i = 0; i <= steps; i++) {
124
- const p = arcTo(rOuter, i)
125
- if (i === 0) path.moveTo(p.x, p.y)
126
- else path.lineTo(p.x, p.y)
147
+ const round = (corner: Vector2, next: Vector2) => {
148
+ if (rounded) path.quadraticCurveTo(corner.x, corner.y, next.x, next.y)
149
+ else path.lineTo(corner.x, corner.y)
127
150
  }
128
- // The inner arc's own wider inset makes each side edge a chord `halfGap` px off the divide at both ends.
129
- for (let i = steps; i >= 0; i--) {
130
- const p = arcTo(rInner, i)
131
- path.lineTo(p.x, p.y)
151
+
152
+ const first = at(rOuter, oa0 + dOuter)
153
+ path.moveTo(first.x, first.y)
154
+ arc(rOuter, oa0 + dOuter, oa1 - dOuter)
155
+ round(O1, toward(O1, I1, rho))
156
+ if (hasHole) {
157
+ const beforeI1 = toward(I1, O1, rho)
158
+ path.lineTo(beforeI1.x, beforeI1.y)
159
+ round(I1, at(rInner, ia1 - dInner))
160
+ arc(rInner, ia1 - dInner, ia0 + dInner)
161
+ round(I0, toward(I0, O0, rho))
162
+ } else {
163
+ path.lineTo(apex.x, apex.y)
132
164
  }
165
+ const beforeO0 = toward(O0, I0, rounded ? rho : 0)
166
+ path.lineTo(beforeO0.x, beforeO0.y)
167
+ round(O0, first)
133
168
  path.close()
134
169
  return path
135
170
  }
@@ -161,7 +196,7 @@ export class RingGeometry {
161
196
 
162
197
  /** Leader labels are the only thing that costs a gutter. */
163
198
  get hasLeaderLabels(): boolean {
164
- return this.s.labelPlacement === "leader" && this.s.labelMode !== "none" && this.arcs.length > 0
199
+ return this.s.labelPlacement === "outside" && this.s.labelMode !== "none" && this.arcs.length > 0
165
200
  }
166
201
 
167
202
  /**
@@ -382,19 +417,6 @@ function sideOf(mid: number): 1 | -1 {
382
417
  const NO_BOX = { width: 0, height: 0 }
383
418
 
384
419
  /** 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. */
385
- const resolvedFills = new WeakMap<object, FillResolved[]>()
386
-
387
- function resolvedFill(fill: Fill): FillResolved[] {
388
- if (typeof fill !== "object" || fill === null) return fillOps.resolve(fill)
389
- let resolved = resolvedFills.get(fill)
390
- if (resolved === undefined) resolvedFills.set(fill, resolved = fillOps.resolve(fill))
391
- return resolved
392
- }
393
-
394
- export function ringFills(slices: readonly PieSlice[]): FillResolved[][] {
395
- return slices.map((slice) => resolvedFill(slice.fill))
396
- }
397
-
398
420
  /** 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. */
399
421
  function ringLabels(s: RingState & { readonly labelReveal: number; readonly centerReveal: number }) {
400
422
  const blocks: Node2D[] = []
@@ -413,6 +435,7 @@ function ringLabels(s: RingState & { readonly labelReveal: number; readonly cent
413
435
  inside.push(new Text({
414
436
  ...textOps.styleProps(s.label.insideStyle),
415
437
  text: () => geometry().percentText(arc.index),
438
+ fill: () => onFill(s.slices[arc.index]?.fill ?? "white"),
416
439
  fontSize: () => geometry().insideFont,
417
440
  }))
418
441
  }
@@ -420,6 +443,7 @@ function ringLabels(s: RingState & { readonly labelReveal: number; readonly cent
420
443
  inside.push(new Text({
421
444
  ...textOps.styleProps(s.label.insideStyle),
422
445
  text: () => s.slices[arc.index]?.category ?? "",
446
+ fill: () => onFill(s.slices[arc.index]?.fill ?? "white"),
423
447
  fontSize: () => (s.labelMode === "category" ? geometry().insideFont : geometry().insideFont * INSIDE_CAPTION_RATIO),
424
448
  }))
425
449
  }
@@ -493,7 +517,7 @@ function ringLabels(s: RingState & { readonly labelReveal: number; readonly cent
493
517
  height: "hug",
494
518
  gap: 2,
495
519
  align: "center",
496
- opacity: () => clamp(s.centerReveal, 0, 1),
520
+ opacity: () => clamp(s.centerReveal, 0, 1) * clamp(geometry().holeRadius / 20, 0, 1),
497
521
  center: () => ({ x: geometry().centerX, y: 0 }),
498
522
  children,
499
523
  }))
@@ -513,12 +537,12 @@ export const PieRing = component2d({
513
537
  key: "pieRing",
514
538
  state: {
515
539
  slices: fresh<PieSlice[]>(() => []),
516
- ring: Types.custom<RingConfig>(DEFAULT_PIE_CHART_THEME.ring),
517
- label: Types.custom<LabelConfig>(DEFAULT_PIE_CHART_THEME.label),
540
+ ring: Types.custom<RingConfig>(DEFAULT_RING),
541
+ label: Types.custom<LabelConfig>(DEFAULT_LABEL),
518
542
  // Not `center`: every node already has one, the anchor its position is measured from.
519
- centerStyle: Types.custom<CenterConfig>(DEFAULT_PIE_CHART_THEME.center),
543
+ centerStyle: Types.custom<CenterConfig>(DEFAULT_CENTER),
520
544
  labelMode: Types.custom<PieLabelMode>("percent"),
521
- labelPlacement: Types.custom<PieLabelPlacement>("leader"),
545
+ labelPlacement: Types.custom<PieLabelPlacement>("outside"),
522
546
  /** Sweep `[0, 1]`: the wedges grow clockwise from `startAngle` as it rises. */
523
547
  growth: 1,
524
548
  /** Fade of leaders and labels, separate from `growth` so they arrive after the sweep. */
@@ -526,7 +550,7 @@ export const PieRing = component2d({
526
550
  /** Per-slice pop `[0, 1]`, owned by the chart so the ring and legend read one signal. */
527
551
  pops: fresh<number[]>(() => []),
528
552
  /** Opacity everything unselected falls back to while any slice is selected. */
529
- dimOpacity: DEFAULT_PIE_CHART_THEME.spotlight.dimOpacity,
553
+ dimOpacity: PIE_SPOTLIGHT_DIM,
530
554
  /** Empty leaves the hole clear; ignored when there is no hole. */
531
555
  centerLabel: "",
532
556
  centerCaption: "",
@@ -540,8 +564,7 @@ export const PieRing = component2d({
540
564
  load: (loader, state) =>
541
565
  loadPaints(loader, {
542
566
  fills: ringFills(state.slices),
543
- shadows: [shadowOps.resolve(state.ring.shadow)],
544
- strokes: [strokeOps.resolve(state.ring.stroke), strokeOps.resolve(state.label.leaderStroke)],
567
+ strokes: [strokeOps.resolve(state.label.leaderStroke)],
545
568
  }),
546
569
  commands: {
547
570
  /** Wipe the wedges in around the ring (`growth` 0 to 1). */
@@ -591,11 +614,9 @@ export const PieRing = component2d({
591
614
  const a1 = Math.min(arc.a1, revealEnd)
592
615
  if (a1 <= arc.a0) continue
593
616
 
594
- const path = wedgePath(arc.a0, a1, hole, g.anchorRadius(arc.index), halfGap, cx)
617
+ const path = wedgePath(arc.a0, a1, hole, g.anchorRadius(arc.index), halfGap, Math.max(0, state.ring.cornerRadius), cx)
595
618
  const wedge = new Graphics2D().path(path.toPathState({ centerBounds, width: span, height: span }))
596
- wedge.shadow(state.ring.shadow)
597
619
  wedge.fill(fills[arc.index] ?? [])
598
- wedge.stroke(state.ring.stroke)
599
620
  ctx.draw(wedge.opacity(g.sliceOpacity(arc.index)))
600
621
  }
601
622