@motionscript/charts 0.1.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 (215) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +98 -28
  3. package/dist/bar-chart/bar-chart.d.ts +242 -119
  4. package/dist/bar-chart/bar-chart.d.ts.map +1 -1
  5. package/dist/bar-chart/bar-chart.js +231 -260
  6. package/dist/bar-chart/bar-chart.js.map +1 -1
  7. package/dist/bar-chart/category-axis.d.ts +16 -38
  8. package/dist/bar-chart/category-axis.d.ts.map +1 -1
  9. package/dist/bar-chart/category-axis.js +59 -68
  10. package/dist/bar-chart/category-axis.js.map +1 -1
  11. package/dist/bar-chart/chart-bars.d.ts +60 -91
  12. package/dist/bar-chart/chart-bars.d.ts.map +1 -1
  13. package/dist/bar-chart/chart-bars.js +88 -125
  14. package/dist/bar-chart/chart-bars.js.map +1 -1
  15. package/dist/bar-chart/index.d.ts +5 -13
  16. package/dist/bar-chart/index.d.ts.map +1 -1
  17. package/dist/bar-chart/index.js +2 -9
  18. package/dist/bar-chart/index.js.map +1 -1
  19. package/dist/bar-chart/shared.d.ts +40 -132
  20. package/dist/bar-chart/shared.d.ts.map +1 -1
  21. package/dist/bar-chart/shared.js +70 -158
  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 +8 -17
  30. package/dist/engine.d.ts.map +1 -1
  31. package/dist/engine.js +8 -17
  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 -35
  38. package/dist/kit/cartesian.d.ts.map +1 -1
  39. package/dist/kit/cartesian.js +80 -42
  40. package/dist/kit/cartesian.js.map +1 -1
  41. package/dist/kit/chart-canvas.d.ts +37 -40
  42. package/dist/kit/chart-canvas.d.ts.map +1 -1
  43. package/dist/kit/chart-canvas.js +53 -47
  44. package/dist/kit/chart-canvas.js.map +1 -1
  45. package/dist/kit/chart-legend.d.ts +16 -59
  46. package/dist/kit/chart-legend.d.ts.map +1 -1
  47. package/dist/kit/chart-legend.js +38 -76
  48. package/dist/kit/chart-legend.js.map +1 -1
  49. package/dist/kit/entries.d.ts +2 -0
  50. package/dist/kit/entries.d.ts.map +1 -1
  51. package/dist/kit/entries.js +2 -0
  52. package/dist/kit/entries.js.map +1 -1
  53. package/dist/kit/horizontal-axis.d.ts +77 -0
  54. package/dist/kit/horizontal-axis.d.ts.map +1 -0
  55. package/dist/kit/horizontal-axis.js +99 -0
  56. package/dist/kit/horizontal-axis.js.map +1 -0
  57. package/dist/kit/motion.d.ts +16 -3
  58. package/dist/kit/motion.d.ts.map +1 -1
  59. package/dist/kit/motion.js +15 -1
  60. package/dist/kit/motion.js.map +1 -1
  61. package/dist/{pie-chart → kit}/pie-legend.d.ts +14 -29
  62. package/dist/kit/pie-legend.d.ts.map +1 -0
  63. package/dist/kit/pie-legend.js +93 -0
  64. package/dist/kit/pie-legend.js.map +1 -0
  65. package/dist/kit/polar.d.ts +31 -0
  66. package/dist/kit/polar.d.ts.map +1 -0
  67. package/dist/kit/polar.js +33 -0
  68. package/dist/kit/polar.js.map +1 -0
  69. package/dist/kit/shared.d.ts +60 -214
  70. package/dist/kit/shared.d.ts.map +1 -1
  71. package/dist/kit/shared.js +91 -291
  72. package/dist/kit/shared.js.map +1 -1
  73. package/dist/kit/style.d.ts +254 -0
  74. package/dist/kit/style.d.ts.map +1 -0
  75. package/dist/kit/style.js +197 -0
  76. package/dist/kit/style.js.map +1 -0
  77. package/dist/kit/vertical-axis.d.ts +36 -45
  78. package/dist/kit/vertical-axis.d.ts.map +1 -1
  79. package/dist/kit/vertical-axis.js +69 -81
  80. package/dist/kit/vertical-axis.js.map +1 -1
  81. package/dist/line-chart/chart-line.d.ts +107 -40
  82. package/dist/line-chart/chart-line.d.ts.map +1 -1
  83. package/dist/line-chart/chart-line.js +172 -89
  84. package/dist/line-chart/chart-line.js.map +1 -1
  85. package/dist/line-chart/chart-region.d.ts +14 -27
  86. package/dist/line-chart/chart-region.d.ts.map +1 -1
  87. package/dist/line-chart/chart-region.js +13 -28
  88. package/dist/line-chart/chart-region.js.map +1 -1
  89. package/dist/line-chart/index.d.ts +5 -18
  90. package/dist/line-chart/index.d.ts.map +1 -1
  91. package/dist/line-chart/index.js +3 -16
  92. package/dist/line-chart/index.js.map +1 -1
  93. package/dist/line-chart/line-chart.d.ts +272 -119
  94. package/dist/line-chart/line-chart.d.ts.map +1 -1
  95. package/dist/line-chart/line-chart.js +309 -257
  96. package/dist/line-chart/line-chart.js.map +1 -1
  97. package/dist/line-chart/shared.d.ts +73 -130
  98. package/dist/line-chart/shared.d.ts.map +1 -1
  99. package/dist/line-chart/shared.js +195 -156
  100. package/dist/line-chart/shared.js.map +1 -1
  101. package/dist/line-chart/x-scale.d.ts +16 -55
  102. package/dist/line-chart/x-scale.d.ts.map +1 -1
  103. package/dist/line-chart/x-scale.js +44 -111
  104. package/dist/line-chart/x-scale.js.map +1 -1
  105. package/dist/nodes.d.ts +3 -13
  106. package/dist/nodes.d.ts.map +1 -1
  107. package/dist/nodes.js +9 -13
  108. package/dist/nodes.js.map +1 -1
  109. package/dist/pie-chart/index.d.ts +3 -15
  110. package/dist/pie-chart/index.d.ts.map +1 -1
  111. package/dist/pie-chart/index.js +1 -11
  112. package/dist/pie-chart/index.js.map +1 -1
  113. package/dist/pie-chart/pie-chart.d.ts +147 -85
  114. package/dist/pie-chart/pie-chart.d.ts.map +1 -1
  115. package/dist/pie-chart/pie-chart.js +65 -86
  116. package/dist/pie-chart/pie-chart.js.map +1 -1
  117. package/dist/pie-chart/pie-ring.d.ts +65 -196
  118. package/dist/pie-chart/pie-ring.d.ts.map +1 -1
  119. package/dist/pie-chart/pie-ring.js +154 -292
  120. package/dist/pie-chart/pie-ring.js.map +1 -1
  121. package/dist/pie-chart/shared.d.ts +78 -172
  122. package/dist/pie-chart/shared.d.ts.map +1 -1
  123. package/dist/pie-chart/shared.js +54 -177
  124. package/dist/pie-chart/shared.js.map +1 -1
  125. package/dist/radar-chart/index.d.ts +5 -0
  126. package/dist/radar-chart/index.d.ts.map +1 -0
  127. package/dist/radar-chart/index.js +5 -0
  128. package/dist/radar-chart/index.js.map +1 -0
  129. package/dist/radar-chart/radar-chart.d.ts +218 -0
  130. package/dist/radar-chart/radar-chart.d.ts.map +1 -0
  131. package/dist/radar-chart/radar-chart.js +224 -0
  132. package/dist/radar-chart/radar-chart.js.map +1 -0
  133. package/dist/radar-chart/radar-grid.d.ts +43 -0
  134. package/dist/radar-chart/radar-grid.d.ts.map +1 -0
  135. package/dist/radar-chart/radar-grid.js +61 -0
  136. package/dist/radar-chart/radar-grid.js.map +1 -0
  137. package/dist/radar-chart/radar-series.d.ts +59 -0
  138. package/dist/radar-chart/radar-series.d.ts.map +1 -0
  139. package/dist/radar-chart/radar-series.js +71 -0
  140. package/dist/radar-chart/radar-series.js.map +1 -0
  141. package/dist/radar-chart/shared.d.ts +73 -0
  142. package/dist/radar-chart/shared.d.ts.map +1 -0
  143. package/dist/radar-chart/shared.js +123 -0
  144. package/dist/radar-chart/shared.js.map +1 -0
  145. package/dist/radial-chart/index.d.ts +6 -0
  146. package/dist/radial-chart/index.d.ts.map +1 -0
  147. package/dist/radial-chart/index.js +4 -0
  148. package/dist/radial-chart/index.js.map +1 -0
  149. package/dist/radial-chart/radial-chart.d.ts +216 -0
  150. package/dist/radial-chart/radial-chart.d.ts.map +1 -0
  151. package/dist/radial-chart/radial-chart.js +174 -0
  152. package/dist/radial-chart/radial-chart.js.map +1 -0
  153. package/dist/radial-chart/radial-rings.d.ts +121 -0
  154. package/dist/radial-chart/radial-rings.d.ts.map +1 -0
  155. package/dist/radial-chart/radial-rings.js +273 -0
  156. package/dist/radial-chart/radial-rings.js.map +1 -0
  157. package/dist/radial-chart/shared.d.ts +73 -0
  158. package/dist/radial-chart/shared.d.ts.map +1 -0
  159. package/dist/radial-chart/shared.js +79 -0
  160. package/dist/radial-chart/shared.js.map +1 -0
  161. package/package.json +4 -4
  162. package/registry.json +21 -6
  163. package/src/bar-chart/bar-chart.ts +274 -315
  164. package/src/bar-chart/category-axis.ts +53 -88
  165. package/src/bar-chart/chart-bars.ts +108 -205
  166. package/src/bar-chart/index.ts +8 -19
  167. package/src/bar-chart/shared.ts +81 -309
  168. package/src/engine.ts +8 -17
  169. package/src/index.ts +2 -0
  170. package/src/kit/cartesian.ts +118 -55
  171. package/src/kit/chart-canvas.ts +50 -80
  172. package/src/kit/chart-legend.ts +36 -97
  173. package/src/kit/entries.ts +4 -1
  174. package/src/kit/horizontal-axis.ts +114 -0
  175. package/src/kit/motion.ts +18 -2
  176. package/src/{pie-chart → kit}/pie-legend.ts +25 -65
  177. package/src/kit/polar.ts +54 -0
  178. package/src/kit/shared.ts +104 -487
  179. package/src/kit/style.ts +257 -0
  180. package/src/kit/vertical-axis.ts +81 -115
  181. package/src/line-chart/chart-line.ts +185 -91
  182. package/src/line-chart/chart-region.ts +14 -31
  183. package/src/line-chart/index.ts +14 -22
  184. package/src/line-chart/line-chart.ts +377 -315
  185. package/src/line-chart/shared.ts +229 -304
  186. package/src/line-chart/x-scale.ts +44 -121
  187. package/src/nodes.ts +9 -13
  188. package/src/pie-chart/index.ts +3 -35
  189. package/src/pie-chart/pie-chart.ts +83 -134
  190. package/src/pie-chart/pie-ring.ts +162 -368
  191. package/src/pie-chart/shared.ts +92 -379
  192. package/src/radar-chart/index.ts +4 -0
  193. package/src/radar-chart/radar-chart.ts +287 -0
  194. package/src/radar-chart/radar-grid.ts +78 -0
  195. package/src/radar-chart/radar-series.ts +88 -0
  196. package/src/radar-chart/shared.ts +146 -0
  197. package/src/radial-chart/index.ts +5 -0
  198. package/src/radial-chart/radial-chart.ts +210 -0
  199. package/src/radial-chart/radial-rings.ts +328 -0
  200. package/src/radial-chart/shared.ts +114 -0
  201. package/dist/browser/chunks/chunk-TJNHLQGV.js +0 -2
  202. package/dist/browser/chunks/chunk-TJNHLQGV.js.map +0 -7
  203. package/dist/kit/chart-border.d.ts +0 -27
  204. package/dist/kit/chart-border.d.ts.map +0 -1
  205. package/dist/kit/chart-border.js +0 -28
  206. package/dist/kit/chart-border.js.map +0 -1
  207. package/dist/line-chart/horizontal-axis.d.ts +0 -79
  208. package/dist/line-chart/horizontal-axis.d.ts.map +0 -1
  209. package/dist/line-chart/horizontal-axis.js +0 -129
  210. package/dist/line-chart/horizontal-axis.js.map +0 -1
  211. package/dist/pie-chart/pie-legend.d.ts.map +0 -1
  212. package/dist/pie-chart/pie-legend.js +0 -117
  213. package/dist/pie-chart/pie-legend.js.map +0 -1
  214. package/src/kit/chart-border.ts +0 -50
  215. package/src/line-chart/horizontal-axis.ts +0 -151
@@ -0,0 +1,123 @@
1
+ import { Types } from "@motionscript/sdk";
2
+ import { num, text } from "../kit/shared.js";
3
+ /** The radar chart's own grid group: the web the polygons are drawn over. */
4
+ export const RADAR_GRID = {
5
+ /** Whether the rings are straight-edged (a polygon through the spokes) or round. */
6
+ gridShape: Types.oneOf(["polygon", "circle"]),
7
+ /** How many rings, outermost at the scale's maximum. */
8
+ gridLevels: Types.number(4, { min: 1, max: 10, step: 1 }),
9
+ /** The rings; empty draws none. */
10
+ gridStroke: Types.stroke({ weight: 2, fill: "white/15" }),
11
+ /** The line from the centre out to each category; empty draws none. */
12
+ spokeStroke: Types.stroke({ weight: 2, fill: "white/15" }),
13
+ /** Read as the chart is built: whether the category names stand around the rim. */
14
+ spokeLabels: Types.boolean(true, { animatable: false }),
15
+ };
16
+ export const RADAR_MARKS = {
17
+ /** Outline weight of each series' polygon. */
18
+ strokeWidth: Types.number(4, { min: 0, max: 24, step: 1, unit: "px" }),
19
+ /** How strongly each polygon is filled in its series colour; 0 leaves an outline. */
20
+ areaOpacity: Types.number(0.25, { min: 0, max: 1, step: 0.05 }),
21
+ /** Whether a dot marks each value on its spoke. */
22
+ dots: Types.boolean(true),
23
+ dotSize: Types.number(10, { min: 2, max: 40, step: 1, unit: "px" }),
24
+ };
25
+ /** Fraction of the label's font size between the rim and the label. */
26
+ const LABEL_PAD = 0.55;
27
+ /** Glyph width as a fraction of the font size, for fitting labels. */
28
+ const GLYPH = 0.58;
29
+ /** Room kept clear around the web when nothing else claims it, in px. */
30
+ const MARGIN = 10;
31
+ /** Below this a spoke counts as vertical, and its label centres over it. */
32
+ const SIDE = 0.35;
33
+ /** Spoke `index` of `count`, as a unit direction: 12 o'clock first, clockwise on screen, y up. */
34
+ export function spokeDirection(index, count) {
35
+ const angle = (index / Math.max(1, count)) * Math.PI * 2;
36
+ return { x: Math.sin(angle), y: Math.cos(angle) };
37
+ }
38
+ /** One category per data row. */
39
+ export function categoriesOf(data, field) {
40
+ return data.map((row) => text(row, field));
41
+ }
42
+ /** One list of values per series, a value per row. */
43
+ export function valuesOf(data, series) {
44
+ return series.map((s) => data.map((row) => num(row, s.field)));
45
+ }
46
+ const MANTISSAS = [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10];
47
+ /** `value` rounded up to a tidy 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8 times a power of ten. */
48
+ export function niceCeil(value) {
49
+ if (!(value > 0))
50
+ return 1;
51
+ const power = 10 ** Math.floor(Math.log10(value));
52
+ const mantissa = value / power;
53
+ const pick = MANTISSAS.find((m) => m >= mantissa - 1e-9) ?? 10;
54
+ return pick * power;
55
+ }
56
+ /**
57
+ * The scale the web is drawn to: `min` (default 0) to `max`, which when unset
58
+ * (or not above `min`) is the largest value rounded up tidily.
59
+ */
60
+ export function scaleOf(values, min, max) {
61
+ if (max > min)
62
+ return { min, max };
63
+ let top = min;
64
+ for (const list of values)
65
+ for (const v of list)
66
+ top = Math.max(top, v);
67
+ const span = niceCeil(top - min);
68
+ return { min, max: min + span };
69
+ }
70
+ /** Where `value` stands between the scale's ends, `0`-`1`. */
71
+ export function fractionOf(value, scale) {
72
+ const span = scale.max - scale.min;
73
+ if (span <= 0)
74
+ return 0;
75
+ return Math.min(1, Math.max(0, (value - scale.min) / span));
76
+ }
77
+ /** Which corner of a category's label touches the point beside its spoke's end. */
78
+ export function labelAnchor(index, count) {
79
+ const { x, y } = spokeDirection(index, count);
80
+ if (x > SIDE)
81
+ return "centerLeft";
82
+ if (x < -SIDE)
83
+ return "centerRight";
84
+ return y >= 0 ? "bottomCenter" : "topCenter";
85
+ }
86
+ export function labelPad(fontSize) {
87
+ return fontSize * LABEL_PAD;
88
+ }
89
+ /** Rough size of a label, since the web is fitted before any text is measured. */
90
+ export function labelSize(caption, fontSize) {
91
+ return { width: caption.length * fontSize * GLYPH, height: fontSize * 1.25 };
92
+ }
93
+ /**
94
+ * The rim's radius for a box: the largest the web can be with every label
95
+ * (when there are labels) still inside it.
96
+ */
97
+ export function fitRadius(box, categories, fontSize, labelled) {
98
+ let radius = Math.min(box.width, box.height) / 2 - MARGIN;
99
+ if (!labelled)
100
+ return Math.max(0, radius);
101
+ const pad = labelPad(fontSize);
102
+ const count = categories.length;
103
+ categories.forEach((caption, i) => {
104
+ const { x, y } = spokeDirection(i, count);
105
+ const size = labelSize(caption, fontSize);
106
+ const side = labelAnchor(i, count);
107
+ // How much of the label hangs past its anchor point on each axis.
108
+ const reachX = side === "centerLeft" || side === "centerRight" ? size.width : size.width / 2;
109
+ const reachY = side === "bottomCenter" || side === "topCenter" ? size.height : size.height / 2;
110
+ if (Math.abs(x) > 1e-6)
111
+ radius = Math.min(radius, (box.width / 2 - reachX - MARGIN / 2) / Math.abs(x) - pad);
112
+ if (Math.abs(y) > 1e-6)
113
+ radius = Math.min(radius, (box.height / 2 - reachY - MARGIN / 2) / Math.abs(y) - pad);
114
+ });
115
+ return Math.max(0, radius);
116
+ }
117
+ /** Where spoke `index`'s label is pinned: just past the rim, centre-relative, y up. */
118
+ export function labelPoint(index, count, radius, fontSize) {
119
+ const { x, y } = spokeDirection(index, count);
120
+ const r = radius + labelPad(fontSize);
121
+ return { x: x * r, y: y * r };
122
+ }
123
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/radar-chart/shared.ts"],"names":[],"mappings":"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","sourcesContent":["import { Types, type DataRecord, type Size2D, type StrokeResolved, type Stroke as _Stroke, type Vector2 } from \"@motionscript/sdk\"\n\nimport { num, text, type ChartSeries } from \"../kit/shared\"\n\nexport type GridShape = \"polygon\" | \"circle\"\n\n/** The radar chart's own grid group: the web the polygons are drawn over. */\nexport const RADAR_GRID = {\n /** Whether the rings are straight-edged (a polygon through the spokes) or round. */\n gridShape: Types.oneOf([\"polygon\", \"circle\"] as const),\n /** How many rings, outermost at the scale's maximum. */\n gridLevels: Types.number(4, { min: 1, max: 10, step: 1 }),\n /** The rings; empty draws none. */\n gridStroke: Types.stroke({ weight: 2, fill: \"white/15\" }),\n /** The line from the centre out to each category; empty draws none. */\n spokeStroke: Types.stroke({ weight: 2, fill: \"white/15\" }),\n /** Read as the chart is built: whether the category names stand around the rim. */\n spokeLabels: Types.boolean(true, { animatable: false }),\n}\n\nexport const RADAR_MARKS = {\n /** Outline weight of each series' polygon. */\n strokeWidth: Types.number(4, { min: 0, max: 24, step: 1, unit: \"px\" }),\n /** How strongly each polygon is filled in its series colour; 0 leaves an outline. */\n areaOpacity: Types.number(0.25, { min: 0, max: 1, step: 0.05 }),\n /** Whether a dot marks each value on its spoke. */\n dots: Types.boolean(true),\n dotSize: Types.number(10, { min: 2, max: 40, step: 1, unit: \"px\" }),\n}\n\n/** The grid and marks fields the parts read, whichever way the chart's style is spelled. */\nexport interface RadarStyle {\n readonly gridShape: GridShape\n readonly gridLevels: number\n readonly gridStroke: StrokeResolved[]\n readonly spokeStroke: StrokeResolved[]\n readonly spokeLabels: boolean\n readonly strokeWidth: number\n readonly areaOpacity: number\n readonly dots: boolean\n readonly dotSize: number\n readonly fontSize: number\n}\n\n/** Fraction of the label's font size between the rim and the label. */\nconst LABEL_PAD = 0.55\n/** Glyph width as a fraction of the font size, for fitting labels. */\nconst GLYPH = 0.58\n/** Room kept clear around the web when nothing else claims it, in px. */\nconst MARGIN = 10\n/** Below this a spoke counts as vertical, and its label centres over it. */\nconst SIDE = 0.35\n\n/** Spoke `index` of `count`, as a unit direction: 12 o'clock first, clockwise on screen, y up. */\nexport function spokeDirection(index: number, count: number): Vector2 {\n const angle = (index / Math.max(1, count)) * Math.PI * 2\n return { x: Math.sin(angle), y: Math.cos(angle) }\n}\n\n/** One category per data row. */\nexport function categoriesOf(data: readonly DataRecord[], field: string): string[] {\n return data.map((row) => text(row, field))\n}\n\n/** One list of values per series, a value per row. */\nexport function valuesOf(data: readonly DataRecord[], series: readonly ChartSeries[]): number[][] {\n return series.map((s) => data.map((row) => num(row, s.field)))\n}\n\nconst MANTISSAS = [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10]\n\n/** `value` rounded up to a tidy 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8 times a power of ten. */\nexport function niceCeil(value: number): number {\n if (!(value > 0)) return 1\n const power = 10 ** Math.floor(Math.log10(value))\n const mantissa = value / power\n const pick = MANTISSAS.find((m) => m >= mantissa - 1e-9) ?? 10\n return pick * power\n}\n\n/**\n * The scale the web is drawn to: `min` (default 0) to `max`, which when unset\n * (or not above `min`) is the largest value rounded up tidily.\n */\nexport function scaleOf(values: readonly (readonly number[])[], min: number, max: number): { min: number; max: number } {\n if (max > min) return { min, max }\n let top = min\n for (const list of values) for (const v of list) top = Math.max(top, v)\n const span = niceCeil(top - min)\n return { min, max: min + span }\n}\n\n/** Where `value` stands between the scale's ends, `0`-`1`. */\nexport function fractionOf(value: number, scale: { min: number; max: number }): number {\n const span = scale.max - scale.min\n if (span <= 0) return 0\n return Math.min(1, Math.max(0, (value - scale.min) / span))\n}\n\nexport type LabelAnchor = \"centerLeft\" | \"centerRight\" | \"bottomCenter\" | \"topCenter\"\n\n/** Which corner of a category's label touches the point beside its spoke's end. */\nexport function labelAnchor(index: number, count: number): LabelAnchor {\n const { x, y } = spokeDirection(index, count)\n if (x > SIDE) return \"centerLeft\"\n if (x < -SIDE) return \"centerRight\"\n return y >= 0 ? \"bottomCenter\" : \"topCenter\"\n}\n\nexport function labelPad(fontSize: number): number {\n return fontSize * LABEL_PAD\n}\n\n/** Rough size of a label, since the web is fitted before any text is measured. */\nexport function labelSize(caption: string, fontSize: number): Size2D {\n return { width: caption.length * fontSize * GLYPH, height: fontSize * 1.25 }\n}\n\n/**\n * The rim's radius for a box: the largest the web can be with every label\n * (when there are labels) still inside it.\n */\nexport function fitRadius(box: Size2D, categories: readonly string[], fontSize: number, labelled: boolean): number {\n let radius = Math.min(box.width, box.height) / 2 - MARGIN\n if (!labelled) return Math.max(0, radius)\n const pad = labelPad(fontSize)\n const count = categories.length\n categories.forEach((caption, i) => {\n const { x, y } = spokeDirection(i, count)\n const size = labelSize(caption, fontSize)\n const side = labelAnchor(i, count)\n // How much of the label hangs past its anchor point on each axis.\n const reachX = side === \"centerLeft\" || side === \"centerRight\" ? size.width : size.width / 2\n const reachY = side === \"bottomCenter\" || side === \"topCenter\" ? size.height : size.height / 2\n if (Math.abs(x) > 1e-6) radius = Math.min(radius, (box.width / 2 - reachX - MARGIN / 2) / Math.abs(x) - pad)\n if (Math.abs(y) > 1e-6) radius = Math.min(radius, (box.height / 2 - reachY - MARGIN / 2) / Math.abs(y) - pad)\n })\n return Math.max(0, radius)\n}\n\n/** Where spoke `index`'s label is pinned: just past the rim, centre-relative, y up. */\nexport function labelPoint(index: number, count: number, radius: number, fontSize: number): Vector2 {\n const { x, y } = spokeDirection(index, count)\n const r = radius + labelPad(fontSize)\n return { x: x * r, y: y * r }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export { RadialChart } from "./radial-chart.js";
2
+ export type { RadialChartValues } from "./radial-chart.js";
3
+ export { RadialRings, RadialGeometry, bandPath } from "./radial-rings.js";
4
+ export { RADIAL_STYLE_GROUPS, radialFractions, radialMax, resolveRadialRows, rowValue } from "./shared.js";
5
+ export type { RadialMarks, RadialRow, RadialStyle } from "./shared.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/radial-chart/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACtE,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACvG,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { RadialChart } from "./radial-chart.js";
2
+ export { RadialRings, RadialGeometry, bandPath } from "./radial-rings.js";
3
+ export { RADIAL_STYLE_GROUPS, radialFractions, radialMax, resolveRadialRows, rowValue } from "./shared.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/radial-chart/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE5C,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACtE,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA","sourcesContent":["export { RadialChart } from \"./radial-chart\"\nexport type { RadialChartValues } from \"./radial-chart\"\nexport { RadialRings, RadialGeometry, bandPath } from \"./radial-rings\"\nexport { RADIAL_STYLE_GROUPS, radialFractions, radialMax, resolveRadialRows, rowValue } from \"./shared\"\nexport type { RadialMarks, RadialRow, RadialStyle } from \"./shared\"\n"]}
@@ -0,0 +1,216 @@
1
+ import { type DataRecord, type NodeOf, type Node2D, type Node2DProps as _Node2DProps, type Fill as _Fill, type FillResolved as _FillResolved } from "@motionscript/sdk";
2
+ import { type RadialStyle } from "./shared.js";
3
+ export interface RadialChartValues {
4
+ readonly data: DataRecord[];
5
+ readonly categoryField: string;
6
+ readonly valueField: string;
7
+ readonly max: number;
8
+ readonly style: RadialStyle;
9
+ readonly pops: number[];
10
+ }
11
+ /**
12
+ * A radial bar chart: one concentric ring per row, the first row outermost,
13
+ * each an arc from `startAngle` sweeping `value / max` of `sweep` degrees.
14
+ * Rows are the marks and none can be hidden; `selectRing` thickens a ring and
15
+ * dims the rest.
16
+ */
17
+ export declare const RadialChart: import("@motionscript/sdk").Component<"radialChart", import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
18
+ readonly data: import("@motionscript/sdk").Type<DataRecord[], DataRecord[], false>;
19
+ /** Ring caption, used on the ring labels and the legend. */
20
+ readonly categoryField: "";
21
+ /** Ring magnitude: the arc's share of the full sweep. */
22
+ readonly valueField: "";
23
+ /** The value a full sweep stands for. 0 lets the largest value fill its ring. */
24
+ readonly max: import("@motionscript/sdk").Type<number, number, false>;
25
+ readonly style: import("@motionscript/sdk").Type<import("@motionscript/sdk").StyleInput<{
26
+ legend: import("@motionscript/sdk").Type<"right" | "top" | "bottom" | "none", "right" | "top" | "bottom" | "none", false>;
27
+ swatch: import("@motionscript/sdk").Type<"square" | "circle" | "line", "square" | "circle" | "line", false>;
28
+ } & {
29
+ fontFamily: import("@motionscript/sdk").Type<string, string, false>;
30
+ fontSize: import("@motionscript/sdk").Type<number, number, false>;
31
+ fontWeight: import("@motionscript/sdk").Type<number, number, false>;
32
+ } & {
33
+ palette: import("@motionscript/sdk").Type<_Fill[], _FillResolved[][], false>;
34
+ foreground: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
35
+ muted: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
36
+ background: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
37
+ } & {
38
+ innerRadius: import("@motionscript/sdk").Type<number, number, false>;
39
+ ringGap: import("@motionscript/sdk").Type<number, number, false>;
40
+ sweep: import("@motionscript/sdk").Type<number, number, false>;
41
+ startAngle: import("@motionscript/sdk").Type<number, number, false>;
42
+ track: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
43
+ roundCaps: import("@motionscript/sdk").Type<boolean, boolean, false>;
44
+ } & {
45
+ ringLabels: import("@motionscript/sdk").Type<boolean, boolean, false>;
46
+ }>, import("@motionscript/sdk").StyleValues<{
47
+ legend: import("@motionscript/sdk").Type<"right" | "top" | "bottom" | "none", "right" | "top" | "bottom" | "none", false>;
48
+ swatch: import("@motionscript/sdk").Type<"square" | "circle" | "line", "square" | "circle" | "line", false>;
49
+ } & {
50
+ fontFamily: import("@motionscript/sdk").Type<string, string, false>;
51
+ fontSize: import("@motionscript/sdk").Type<number, number, false>;
52
+ fontWeight: import("@motionscript/sdk").Type<number, number, false>;
53
+ } & {
54
+ palette: import("@motionscript/sdk").Type<_Fill[], _FillResolved[][], false>;
55
+ foreground: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
56
+ muted: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
57
+ background: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
58
+ } & {
59
+ innerRadius: import("@motionscript/sdk").Type<number, number, false>;
60
+ ringGap: import("@motionscript/sdk").Type<number, number, false>;
61
+ sweep: import("@motionscript/sdk").Type<number, number, false>;
62
+ startAngle: import("@motionscript/sdk").Type<number, number, false>;
63
+ track: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
64
+ roundCaps: import("@motionscript/sdk").Type<boolean, boolean, false>;
65
+ } & {
66
+ ringLabels: import("@motionscript/sdk").Type<boolean, boolean, false>;
67
+ }>, false>;
68
+ /** Gap between the rings and the legend, in px. */
69
+ readonly gap: 40;
70
+ /** Text set in the centre hole. */
71
+ readonly centerLabel: "";
72
+ /** Smaller second line under `centerLabel`. */
73
+ readonly centerCaption: "";
74
+ /** How far each ring is emphasised, `0`-`1` per index. Here because the rings and the legend both read it. */
75
+ readonly pops: import("@motionscript/sdk").Type<number[], number[], false>;
76
+ /** Read as the chart is built: whether the centre summary is drawn at rest. A scene that wants it to land later sets `false` and runs `revealCenter`. */
77
+ readonly showCenter: import("@motionscript/sdk").Type<boolean, boolean, false>;
78
+ }>, {
79
+ readonly enter: {};
80
+ readonly revealCenter: {};
81
+ readonly selectRing: {
82
+ readonly ringIndex: import("@motionscript/sdk").Type<number, number, false>;
83
+ };
84
+ readonly clearSelection: {};
85
+ }, readonly [], Node2D<_Node2DProps>, import("@motionscript/sdk").CallProps<import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
86
+ readonly data: import("@motionscript/sdk").Type<DataRecord[], DataRecord[], false>;
87
+ /** Ring caption, used on the ring labels and the legend. */
88
+ readonly categoryField: "";
89
+ /** Ring magnitude: the arc's share of the full sweep. */
90
+ readonly valueField: "";
91
+ /** The value a full sweep stands for. 0 lets the largest value fill its ring. */
92
+ readonly max: import("@motionscript/sdk").Type<number, number, false>;
93
+ readonly style: import("@motionscript/sdk").Type<import("@motionscript/sdk").StyleInput<{
94
+ legend: import("@motionscript/sdk").Type<"right" | "top" | "bottom" | "none", "right" | "top" | "bottom" | "none", false>;
95
+ swatch: import("@motionscript/sdk").Type<"square" | "circle" | "line", "square" | "circle" | "line", false>;
96
+ } & {
97
+ fontFamily: import("@motionscript/sdk").Type<string, string, false>;
98
+ fontSize: import("@motionscript/sdk").Type<number, number, false>;
99
+ fontWeight: import("@motionscript/sdk").Type<number, number, false>;
100
+ } & {
101
+ palette: import("@motionscript/sdk").Type<_Fill[], _FillResolved[][], false>;
102
+ foreground: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
103
+ muted: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
104
+ background: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
105
+ } & {
106
+ innerRadius: import("@motionscript/sdk").Type<number, number, false>;
107
+ ringGap: import("@motionscript/sdk").Type<number, number, false>;
108
+ sweep: import("@motionscript/sdk").Type<number, number, false>;
109
+ startAngle: import("@motionscript/sdk").Type<number, number, false>;
110
+ track: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
111
+ roundCaps: import("@motionscript/sdk").Type<boolean, boolean, false>;
112
+ } & {
113
+ ringLabels: import("@motionscript/sdk").Type<boolean, boolean, false>;
114
+ }>, import("@motionscript/sdk").StyleValues<{
115
+ legend: import("@motionscript/sdk").Type<"right" | "top" | "bottom" | "none", "right" | "top" | "bottom" | "none", false>;
116
+ swatch: import("@motionscript/sdk").Type<"square" | "circle" | "line", "square" | "circle" | "line", false>;
117
+ } & {
118
+ fontFamily: import("@motionscript/sdk").Type<string, string, false>;
119
+ fontSize: import("@motionscript/sdk").Type<number, number, false>;
120
+ fontWeight: import("@motionscript/sdk").Type<number, number, false>;
121
+ } & {
122
+ palette: import("@motionscript/sdk").Type<_Fill[], _FillResolved[][], false>;
123
+ foreground: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
124
+ muted: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
125
+ background: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
126
+ } & {
127
+ innerRadius: import("@motionscript/sdk").Type<number, number, false>;
128
+ ringGap: import("@motionscript/sdk").Type<number, number, false>;
129
+ sweep: import("@motionscript/sdk").Type<number, number, false>;
130
+ startAngle: import("@motionscript/sdk").Type<number, number, false>;
131
+ track: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
132
+ roundCaps: import("@motionscript/sdk").Type<boolean, boolean, false>;
133
+ } & {
134
+ ringLabels: import("@motionscript/sdk").Type<boolean, boolean, false>;
135
+ }>, false>;
136
+ /** Gap between the rings and the legend, in px. */
137
+ readonly gap: 40;
138
+ /** Text set in the centre hole. */
139
+ readonly centerLabel: "";
140
+ /** Smaller second line under `centerLabel`. */
141
+ readonly centerCaption: "";
142
+ /** How far each ring is emphasised, `0`-`1` per index. Here because the rings and the legend both read it. */
143
+ readonly pops: import("@motionscript/sdk").Type<number[], number[], false>;
144
+ /** Read as the chart is built: whether the centre summary is drawn at rest. A scene that wants it to land later sets `false` and runs `revealCenter`. */
145
+ readonly showCenter: import("@motionscript/sdk").Type<boolean, boolean, false>;
146
+ }>, readonly [], import("@motionscript/sdk").BuiltNode<import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
147
+ readonly data: import("@motionscript/sdk").Type<DataRecord[], DataRecord[], false>;
148
+ /** Ring caption, used on the ring labels and the legend. */
149
+ readonly categoryField: "";
150
+ /** Ring magnitude: the arc's share of the full sweep. */
151
+ readonly valueField: "";
152
+ /** The value a full sweep stands for. 0 lets the largest value fill its ring. */
153
+ readonly max: import("@motionscript/sdk").Type<number, number, false>;
154
+ readonly style: import("@motionscript/sdk").Type<import("@motionscript/sdk").StyleInput<{
155
+ legend: import("@motionscript/sdk").Type<"right" | "top" | "bottom" | "none", "right" | "top" | "bottom" | "none", false>;
156
+ swatch: import("@motionscript/sdk").Type<"square" | "circle" | "line", "square" | "circle" | "line", false>;
157
+ } & {
158
+ fontFamily: import("@motionscript/sdk").Type<string, string, false>;
159
+ fontSize: import("@motionscript/sdk").Type<number, number, false>;
160
+ fontWeight: import("@motionscript/sdk").Type<number, number, false>;
161
+ } & {
162
+ palette: import("@motionscript/sdk").Type<_Fill[], _FillResolved[][], false>;
163
+ foreground: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
164
+ muted: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
165
+ background: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
166
+ } & {
167
+ innerRadius: import("@motionscript/sdk").Type<number, number, false>;
168
+ ringGap: import("@motionscript/sdk").Type<number, number, false>;
169
+ sweep: import("@motionscript/sdk").Type<number, number, false>;
170
+ startAngle: import("@motionscript/sdk").Type<number, number, false>;
171
+ track: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
172
+ roundCaps: import("@motionscript/sdk").Type<boolean, boolean, false>;
173
+ } & {
174
+ ringLabels: import("@motionscript/sdk").Type<boolean, boolean, false>;
175
+ }>, import("@motionscript/sdk").StyleValues<{
176
+ legend: import("@motionscript/sdk").Type<"right" | "top" | "bottom" | "none", "right" | "top" | "bottom" | "none", false>;
177
+ swatch: import("@motionscript/sdk").Type<"square" | "circle" | "line", "square" | "circle" | "line", false>;
178
+ } & {
179
+ fontFamily: import("@motionscript/sdk").Type<string, string, false>;
180
+ fontSize: import("@motionscript/sdk").Type<number, number, false>;
181
+ fontWeight: import("@motionscript/sdk").Type<number, number, false>;
182
+ } & {
183
+ palette: import("@motionscript/sdk").Type<_Fill[], _FillResolved[][], false>;
184
+ foreground: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
185
+ muted: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
186
+ background: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
187
+ } & {
188
+ innerRadius: import("@motionscript/sdk").Type<number, number, false>;
189
+ ringGap: import("@motionscript/sdk").Type<number, number, false>;
190
+ sweep: import("@motionscript/sdk").Type<number, number, false>;
191
+ startAngle: import("@motionscript/sdk").Type<number, number, false>;
192
+ track: import("@motionscript/sdk").Type<_Fill, _FillResolved[], false>;
193
+ roundCaps: import("@motionscript/sdk").Type<boolean, boolean, false>;
194
+ } & {
195
+ ringLabels: import("@motionscript/sdk").Type<boolean, boolean, false>;
196
+ }>, false>;
197
+ /** Gap between the rings and the legend, in px. */
198
+ readonly gap: 40;
199
+ /** Text set in the centre hole. */
200
+ readonly centerLabel: "";
201
+ /** Smaller second line under `centerLabel`. */
202
+ readonly centerCaption: "";
203
+ /** How far each ring is emphasised, `0`-`1` per index. Here because the rings and the legend both read it. */
204
+ readonly pops: import("@motionscript/sdk").Type<number[], number[], false>;
205
+ /** Read as the chart is built: whether the centre summary is drawn at rest. A scene that wants it to land later sets `false` and runs `revealCenter`. */
206
+ readonly showCenter: import("@motionscript/sdk").Type<boolean, boolean, false>;
207
+ }>, {
208
+ readonly enter: {};
209
+ readonly revealCenter: {};
210
+ readonly selectRing: {
211
+ readonly ringIndex: import("@motionscript/sdk").Type<number, number, false>;
212
+ };
213
+ readonly clearSelection: {};
214
+ }, Node2D<_Node2DProps>>>>;
215
+ export type RadialChart = NodeOf<typeof RadialChart>;
216
+ //# sourceMappingURL=radial-chart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radial-chart.d.ts","sourceRoot":"","sources":["../../src/radial-chart/radial-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,UAAU,EACf,KAAK,MAAM,EAEX,KAAK,MAAM,EACX,KAAK,WAAW,IAAI,YAAY,EAChC,KAAK,IAAI,IAAI,KAAK,EAClB,KAAK,YAAY,IAAI,aAAa,EACnC,MAAM,mBAAmB,CAAA;AAQ1B,OAAO,EAQL,KAAK,WAAW,EACjB,MAAM,UAAU,CAAA;AAEjB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,CAAA;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CACxB;AAwCD;;;;;GAKG;AACH,eAAO,MAAM,WAAW;;IAIpB,4DAA4D;;IAE5D,yDAAyD;;IAEzD,iFAAiF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAGjF,mDAAmD;;IAEnD,mCAAmC;;IAEnC,+CAA+C;;IAE/C,8GAA8G;;IAE9G,yJAAyJ;;;;;;;;;;;IAfzJ,4DAA4D;;IAE5D,yDAAyD;;IAEzD,iFAAiF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAGjF,mDAAmD;;IAEnD,mCAAmC;;IAEnC,+CAA+C;;IAE/C,8GAA8G;;IAE9G,yJAAyJ;;;;IAfzJ,4DAA4D;;IAE5D,yDAAyD;;IAEzD,iFAAiF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAGjF,mDAAmD;;IAEnD,mCAAmC;;IAEnC,+CAA+C;;IAE/C,8GAA8G;;IAE9G,yJAAyJ;;;;;;;;;0BAsG3J,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAA"}
@@ -0,0 +1,174 @@
1
+ import { Rect, Types, clamp, component2d, easeOut, } from "@motionscript/sdk";
2
+ import { SERIES_ARG, asParts, fresh } from "../kit/entries.js";
3
+ import { beats } from "../kit/motion.js";
4
+ import { CHART_STYLE_FAMILY, CHART_STYLE_PRESETS } from "../kit/style.js";
5
+ import { PieLegend } from "../kit/pie-legend.js";
6
+ import { PIE_SPOTLIGHT_DIM, selectionFade } from "../kit/polar.js";
7
+ import { RadialRings } from "./radial-rings.js";
8
+ import { RADIAL_STYLE_GROUPS, centerOf, legendOf, marksOf, resolveRadialRows, ringLabelOf, } from "./shared.js";
9
+ /** How `enter` divides its duration: rings sweeping in, then captions and legend together. Sums to 1. */
10
+ const SWEEP_SHARE = 0.7;
11
+ const LABEL_SHARE = 0.3;
12
+ function rowsOf(s) {
13
+ return resolveRadialRows(s.data, s.categoryField, s.valueField, s.style);
14
+ }
15
+ function popAt(s, index) {
16
+ return clamp(s.pops[index] ?? 0, 0, 1);
17
+ }
18
+ /** Per-row opacity for the legend: 1 while nothing is selected and for the selected ring, falling toward the dim for the rest. */
19
+ function ringDims(s) {
20
+ let focus = 0;
21
+ for (let i = 0; i < s.data.length; i++)
22
+ focus = Math.max(focus, popAt(s, i));
23
+ return s.data.map((_, i) => selectionFade(focus, RING_DIM, popAt(s, i)));
24
+ }
25
+ const RING_DIM = Math.max(PIE_SPOTLIGHT_DIM, 0.25);
26
+ function popping(timeline, s, pop) {
27
+ const from = s.data.map((_, i) => popAt(s, i));
28
+ const targets = s.data.map((_, i) => clamp(pop(i), 0, 1));
29
+ if (from.every((value, i) => value === targets[i]))
30
+ return timeline.wait(timeline.duration);
31
+ return timeline.animate((t) => ({ pops: from.map((value, i) => value + (targets[i] - value) * t) }));
32
+ }
33
+ /** The rings and legend among the children; the last row is the one the chart built. */
34
+ function partsOf(children) {
35
+ for (let i = children.length - 1; i >= 0; i--) {
36
+ const row = children[i];
37
+ const rings = Rect.is(row) ? row.children.find(RadialRings.is) : undefined;
38
+ if (rings)
39
+ return { rings, legend: row.children.find(PieLegend.is) ?? null };
40
+ }
41
+ return null;
42
+ }
43
+ /**
44
+ * A radial bar chart: one concentric ring per row, the first row outermost,
45
+ * each an arc from `startAngle` sweeping `value / max` of `sweep` degrees.
46
+ * Rows are the marks and none can be hidden; `selectRing` thickens a ring and
47
+ * dims the rest.
48
+ */
49
+ export const RadialChart = component2d({
50
+ key: "radialChart",
51
+ state: {
52
+ data: fresh(() => [], { kind: "dataset" }),
53
+ /** Ring caption, used on the ring labels and the legend. */
54
+ categoryField: "",
55
+ /** Ring magnitude: the arc's share of the full sweep. */
56
+ valueField: "",
57
+ /** The value a full sweep stands for. 0 lets the largest value fill its ring. */
58
+ max: Types.number(0, { min: 0, step: 1 }),
59
+ style: Types.style(CHART_STYLE_FAMILY, RADIAL_STYLE_GROUPS, { presets: CHART_STYLE_PRESETS }),
60
+ /** Gap between the rings and the legend, in px. */
61
+ gap: 40,
62
+ /** Text set in the centre hole. */
63
+ centerLabel: "",
64
+ /** Smaller second line under `centerLabel`. */
65
+ centerCaption: "",
66
+ /** How far each ring is emphasised, `0`-`1` per index. Here because the rings and the legend both read it. */
67
+ pops: fresh(() => []),
68
+ /** Read as the chart is built: whether the centre summary is drawn at rest. A scene that wants it to land later sets `false` and runs `revealCenter`. */
69
+ showCenter: Types.boolean(true, { animatable: false }),
70
+ },
71
+ defaults: {
72
+ width: "fill",
73
+ height: "fill",
74
+ pops: (state) => state.data.map(() => 0),
75
+ },
76
+ allowedChildren: [],
77
+ children: (state) => {
78
+ const style = () => state.style;
79
+ const rows = () => rowsOf(state);
80
+ const parts = [
81
+ new RadialRings({
82
+ width: "fill",
83
+ height: "fill",
84
+ rows,
85
+ max: () => state.max,
86
+ marks: () => marksOf(style()),
87
+ label: () => ringLabelOf(style()),
88
+ centerStyle: () => centerOf(style()),
89
+ trackFill: () => style().track,
90
+ ringLabels: () => style().ringLabels,
91
+ pops: () => state.pops,
92
+ dimOpacity: RING_DIM,
93
+ centerLabel: state.centerLabel,
94
+ centerCaption: state.centerCaption,
95
+ centerReveal: state.showCenter === false ? 0 : 1,
96
+ }),
97
+ ];
98
+ const position = state.style.legend;
99
+ if (position !== "none") {
100
+ const side = position === "right";
101
+ const config = legendOf(state.style);
102
+ const legend = new PieLegend({
103
+ slices: rows,
104
+ legend: () => legendOf(style()),
105
+ dims: () => ringDims(state),
106
+ flow: side ? "vertical" : "horizontal",
107
+ align: "center",
108
+ gap: side ? config.itemGap / 3 : config.itemGap,
109
+ padding: 0,
110
+ });
111
+ if (position === "top")
112
+ parts.unshift(legend);
113
+ else
114
+ parts.push(legend);
115
+ }
116
+ const row = new Rect({
117
+ flow: position === "right" || position === "none" ? "horizontal" : "vertical",
118
+ width: "fill",
119
+ height: "fill",
120
+ gap: state.gap,
121
+ padding: 24,
122
+ align: "center",
123
+ fill: () => state.style.background,
124
+ children: parts,
125
+ });
126
+ return asParts([row]);
127
+ },
128
+ commands: {
129
+ /** Rings sweep in from `startAngle`, outer ring first, then captions fade up and the legend staggers in. `duration` is the whole entrance; the centre summary keeps its own signal. */
130
+ enter: {
131
+ args: {},
132
+ entrance: true,
133
+ easing: easeOut(),
134
+ run: (timeline, _state, children) => {
135
+ const parts = partsOf(children);
136
+ if (!parts)
137
+ return timeline.wait(timeline.duration);
138
+ const { rings, legend } = parts;
139
+ const sweep = timeline.duration * SWEEP_SHARE;
140
+ const labels = timeline.duration * LABEL_SHARE;
141
+ const at0 = (piece) => ({ at: 0, duration: 0, piece });
142
+ return beats(timeline, [
143
+ at0(rings.to({ growth: 0, labelReveal: 0 })),
144
+ ...(legend ? [at0(legend.run("arm"))] : []),
145
+ { at: 0, duration: sweep, piece: rings.run("sweep") },
146
+ { at: sweep, duration: labels, piece: rings.run("revealLabels") },
147
+ ...(legend ? [{ at: sweep, duration: labels, piece: legend.run("enter") }] : []),
148
+ ]);
149
+ },
150
+ },
151
+ /** Fade the centre summary up. Not part of `enter`: it is usually the conclusion, which a scene lands on its own beat. */
152
+ revealCenter: {
153
+ args: {},
154
+ easing: easeOut(),
155
+ run: (timeline, _state, children) => partsOf(children)?.rings.run("revealCenter") ?? timeline.wait(timeline.duration),
156
+ },
157
+ /** Thicken ring `ringIndex` and dim the rest, leaving any other selected ring where it is. */
158
+ selectRing: {
159
+ args: { ringIndex: SERIES_ARG },
160
+ easing: easeOut(),
161
+ run: (timeline, state) => {
162
+ const index = timeline.args.ringIndex;
163
+ return popping(timeline, state, (i) => (i === index ? 1 : popAt(state, i)));
164
+ },
165
+ },
166
+ /** Settle every ring and bring the whole chart back to full. */
167
+ clearSelection: {
168
+ args: {},
169
+ easing: easeOut(),
170
+ run: (timeline, state) => popping(timeline, state, () => 0),
171
+ },
172
+ },
173
+ });
174
+ //# sourceMappingURL=radial-chart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radial-chart.js","sourceRoot":"","sources":["../../src/radial-chart/radial-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 Fill as _Fill,\n type FillResolved as _FillResolved,\n} from \"@motionscript/sdk\"\n\nimport { SERIES_ARG, asParts, fresh } from \"../kit/entries\"\nimport { beats, type AnyTimeline, type Beat } from \"../kit/motion\"\nimport { CHART_STYLE_FAMILY, CHART_STYLE_PRESETS } from \"../kit/style\"\nimport { PieLegend } from \"../kit/pie-legend\"\nimport { PIE_SPOTLIGHT_DIM, selectionFade } from \"../kit/polar\"\nimport { RadialRings } from \"./radial-rings\"\nimport {\n RADIAL_STYLE_GROUPS,\n centerOf,\n legendOf,\n marksOf,\n resolveRadialRows,\n ringLabelOf,\n type RadialRow,\n type RadialStyle,\n} from \"./shared\"\n\nexport interface RadialChartValues {\n readonly data: DataRecord[]\n readonly categoryField: string\n readonly valueField: string\n readonly max: number\n readonly style: RadialStyle\n readonly pops: number[]\n}\n\n/** How `enter` divides its duration: rings sweeping in, then captions and legend together. Sums to 1. */\nconst SWEEP_SHARE = 0.7\nconst LABEL_SHARE = 0.3\n\nfunction rowsOf(s: RadialChartValues): RadialRow[] {\n return resolveRadialRows(s.data, s.categoryField, s.valueField, s.style)\n}\n\nfunction popAt(s: RadialChartValues, index: number): number {\n return clamp(s.pops[index] ?? 0, 0, 1)\n}\n\n/** Per-row opacity for the legend: 1 while nothing is selected and for the selected ring, falling toward the dim for the rest. */\nfunction ringDims(s: RadialChartValues): 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, RING_DIM, popAt(s, i)))\n}\n\nconst RING_DIM = Math.max(PIE_SPOTLIGHT_DIM, 0.25)\n\nfunction popping(timeline: AnyTimeline, s: RadialChartValues, 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 rings 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 rings = Rect.is(row) ? row.children.find(RadialRings.is) : undefined\n if (rings) return { rings, legend: row.children.find(PieLegend.is) ?? null }\n }\n return null\n}\n\n/**\n * A radial bar chart: one concentric ring per row, the first row outermost,\n * each an arc from `startAngle` sweeping `value / max` of `sweep` degrees.\n * Rows are the marks and none can be hidden; `selectRing` thickens a ring and\n * dims the rest.\n */\nexport const RadialChart = component2d({\n key: \"radialChart\",\n state: {\n data: fresh<DataRecord[]>(() => [], { kind: \"dataset\" }),\n /** Ring caption, used on the ring labels and the legend. */\n categoryField: \"\",\n /** Ring magnitude: the arc's share of the full sweep. */\n valueField: \"\",\n /** The value a full sweep stands for. 0 lets the largest value fill its ring. */\n max: Types.number(0, { min: 0, step: 1 }),\n style: Types.style(CHART_STYLE_FAMILY, RADIAL_STYLE_GROUPS, { presets: CHART_STYLE_PRESETS }),\n /** Gap between the rings and the legend, in px. */\n gap: 40,\n /** Text set in the centre hole. */\n centerLabel: \"\",\n /** Smaller second line under `centerLabel`. */\n centerCaption: \"\",\n /** How far each ring is emphasised, `0`-`1` per index. Here because the rings and the legend both read it. */\n pops: fresh<number[]>(() => []),\n /** Read as the chart is built: whether the centre 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 width: \"fill\",\n height: \"fill\",\n pops: (state) => state.data.map(() => 0),\n },\n allowedChildren: [],\n children: (state) => {\n const style = () => state.style\n const rows = () => rowsOf(state)\n const parts: Node2D[] = [\n new RadialRings({\n width: \"fill\",\n height: \"fill\",\n rows,\n max: () => state.max,\n marks: () => marksOf(style()),\n label: () => ringLabelOf(style()),\n centerStyle: () => centerOf(style()),\n trackFill: () => style().track,\n ringLabels: () => style().ringLabels,\n pops: () => state.pops,\n dimOpacity: RING_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 config = legendOf(state.style)\n const legend = new PieLegend({\n slices: rows,\n legend: () => legendOf(style()),\n dims: () => ringDims(state),\n flow: side ? \"vertical\" : \"horizontal\",\n align: \"center\",\n gap: side ? config.itemGap / 3 : config.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 /** Rings sweep in from `startAngle`, outer ring first, then captions fade up and the legend staggers in. `duration` is the whole entrance; the centre summary keeps its own signal. */\n enter: {\n args: {},\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 { rings, 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(rings.to({ growth: 0, labelReveal: 0 })),\n ...(legend ? [at0(legend.run(\"arm\"))] : []),\n { at: 0, duration: sweep, piece: rings.run(\"sweep\") },\n { at: sweep, duration: labels, piece: rings.run(\"revealLabels\") },\n ...(legend ? [{ at: sweep, duration: labels, piece: legend.run(\"enter\") }] : []),\n ])\n },\n },\n /** Fade the centre 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)?.rings.run(\"revealCenter\") ?? timeline.wait(timeline.duration),\n },\n /** Thicken ring `ringIndex` and dim the rest, leaving any other selected ring where it is. */\n selectRing: {\n args: { ringIndex: SERIES_ARG },\n easing: easeOut(),\n run: (timeline, state) => {\n const index = timeline.args.ringIndex\n return popping(timeline, state, (i) => (i === index ? 1 : popAt(state, i)))\n },\n },\n /** Settle every ring 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 RadialChart = NodeOf<typeof RadialChart>\n"]}