@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
@@ -0,0 +1,43 @@
1
+ import { type NodeOf, type Node2D as _Node2D, type Node2DProps as _Node2DProps, type Stroke, type StrokeResolved } from "@motionscript/sdk";
2
+ import { type GridShape } from "./shared.js";
3
+ /**
4
+ * The web of a {@link RadarChart}: `levels` rings and, if asked, a spoke to each
5
+ * of `count` categories, drawn from the node's centre out to `radius`. `reveal`
6
+ * grows the web from nothing.
7
+ */
8
+ export declare const RadarGrid: import("@motionscript/sdk").Component<"radarGrid", import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
9
+ readonly count: 0;
10
+ /** The rim, in px. */
11
+ readonly radius: 0;
12
+ readonly levels: 4;
13
+ readonly shape: import("@motionscript/sdk").Type<GridShape, GridShape, false>;
14
+ readonly stroke: import("@motionscript/sdk").Type<Stroke, StrokeResolved[], false>;
15
+ /** The line out to each category; empty draws none. */
16
+ readonly spokeStroke: import("@motionscript/sdk").Type<Stroke, StrokeResolved[], false>;
17
+ /** How far the web has grown out, `0`-`1`. Rests at 1 so a paused canvas paints it. */
18
+ readonly reveal: 1;
19
+ }>, {}, null, _Node2D<_Node2DProps>, import("@motionscript/sdk").CallProps<import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
20
+ readonly count: 0;
21
+ /** The rim, in px. */
22
+ readonly radius: 0;
23
+ readonly levels: 4;
24
+ readonly shape: import("@motionscript/sdk").Type<GridShape, GridShape, false>;
25
+ readonly stroke: import("@motionscript/sdk").Type<Stroke, StrokeResolved[], false>;
26
+ /** The line out to each category; empty draws none. */
27
+ readonly spokeStroke: import("@motionscript/sdk").Type<Stroke, StrokeResolved[], false>;
28
+ /** How far the web has grown out, `0`-`1`. Rests at 1 so a paused canvas paints it. */
29
+ readonly reveal: 1;
30
+ }>, null, import("@motionscript/sdk").BuiltNode<import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
31
+ readonly count: 0;
32
+ /** The rim, in px. */
33
+ readonly radius: 0;
34
+ readonly levels: 4;
35
+ readonly shape: import("@motionscript/sdk").Type<GridShape, GridShape, false>;
36
+ readonly stroke: import("@motionscript/sdk").Type<Stroke, StrokeResolved[], false>;
37
+ /** The line out to each category; empty draws none. */
38
+ readonly spokeStroke: import("@motionscript/sdk").Type<Stroke, StrokeResolved[], false>;
39
+ /** How far the web has grown out, `0`-`1`. Rests at 1 so a paused canvas paints it. */
40
+ readonly reveal: 1;
41
+ }>, {}, _Node2D<_Node2DProps>>>>;
42
+ export type RadarGrid = NodeOf<typeof RadarGrid>;
43
+ //# sourceMappingURL=radar-grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radar-grid.d.ts","sourceRoot":"","sources":["../../src/radar-chart/radar-grid.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,MAAM,EAEX,KAAK,MAAM,IAAI,OAAO,EACtB,KAAK,WAAW,IAAI,YAAY,EAChC,KAAK,MAAM,EACX,KAAK,cAAc,EAEpB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzD;;;;GAIG;AACH,eAAO,MAAM,SAAS;;IAIlB,sBAAsB;;;;;IAKtB,uDAAuD;;IAEvD,uFAAuF;;;;IAPvF,sBAAsB;;;;;IAKtB,uDAAuD;;IAEvD,uFAAuF;;;;IAPvF,sBAAsB;;;;;IAKtB,uDAAuD;;IAEvD,uFAAuF;;gCAyCzF,CAAA;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,SAAS,CAAC,CAAA"}
@@ -0,0 +1,61 @@
1
+ import { Graphics2D, Types, clamp, component2d, strokeOps, } from "@motionscript/sdk";
2
+ import { loadPaints } from "../kit/paints.js";
3
+ import { spokeDirection } from "./shared.js";
4
+ /**
5
+ * The web of a {@link RadarChart}: `levels` rings and, if asked, a spoke to each
6
+ * of `count` categories, drawn from the node's centre out to `radius`. `reveal`
7
+ * grows the web from nothing.
8
+ */
9
+ export const RadarGrid = component2d({
10
+ key: "radarGrid",
11
+ state: {
12
+ count: 0,
13
+ /** The rim, in px. */
14
+ radius: 0,
15
+ levels: 4,
16
+ shape: Types.custom("polygon"),
17
+ stroke: Types.custom([], { resolve: strokeOps.resolve, lerp: strokeOps.lerp }),
18
+ /** The line out to each category; empty draws none. */
19
+ spokeStroke: Types.custom([], { resolve: strokeOps.resolve, lerp: strokeOps.lerp }),
20
+ /** How far the web has grown out, `0`-`1`. Rests at 1 so a paused canvas paints it. */
21
+ reveal: 1,
22
+ },
23
+ defaults: { width: "fill", height: "fill" },
24
+ load: (loader, state) => loadPaints(loader, { strokes: [state.stroke, state.spokeStroke] }),
25
+ render: (ctx, state) => {
26
+ const grow = clamp(state.reveal, 0, 1);
27
+ const rim = state.radius * grow;
28
+ if (rim <= 0 || grow <= 0)
29
+ return;
30
+ const count = Math.max(0, Math.round(state.count));
31
+ const levels = Math.max(1, Math.round(state.levels));
32
+ const round = state.shape === "circle" || count < 3;
33
+ // One draw per ring: rings chained into one surface would stroke only their union's outline.
34
+ for (let level = 1; state.stroke.length > 0 && level <= levels; level++) {
35
+ const r = (rim * level) / levels;
36
+ const ring = new Graphics2D();
37
+ if (round) {
38
+ ring.ellipse({ width: r * 2, height: r * 2 });
39
+ }
40
+ else {
41
+ const points = [];
42
+ for (let i = 0; i < count; i++) {
43
+ const d = spokeDirection(i, count);
44
+ points.push({ x: d.x * r, y: d.y * r });
45
+ }
46
+ ring.line({ points, closed: true });
47
+ }
48
+ ctx.draw(ring.stroke(state.stroke).opacity(grow));
49
+ }
50
+ if (state.spokeStroke.length === 0)
51
+ return;
52
+ for (let i = 0; i < count; i++) {
53
+ const d = spokeDirection(i, count);
54
+ ctx.draw(new Graphics2D()
55
+ .line({ points: [{ x: 0, y: 0 }, { x: d.x * rim, y: d.y * rim }] })
56
+ .stroke(state.spokeStroke)
57
+ .opacity(grow));
58
+ }
59
+ },
60
+ });
61
+ //# sourceMappingURL=radar-grid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radar-grid.js","sourceRoot":"","sources":["../../src/radar-chart/radar-grid.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Graphics2D,\n Types,\n clamp,\n component2d,\n strokeOps,\n type NodeOf,\n // Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.\n type Node2D as _Node2D,\n type Node2DProps as _Node2DProps,\n type Stroke,\n type StrokeResolved,\n type Vector2,\n} from \"@motionscript/sdk\"\n\nimport { loadPaints } from \"../kit/paints\"\nimport { spokeDirection, type GridShape } from \"./shared\"\n\n/**\n * The web of a {@link RadarChart}: `levels` rings and, if asked, a spoke to each\n * of `count` categories, drawn from the node's centre out to `radius`. `reveal`\n * grows the web from nothing.\n */\nexport const RadarGrid = component2d({\n key: \"radarGrid\",\n state: {\n count: 0,\n /** The rim, in px. */\n radius: 0,\n levels: 4,\n shape: Types.custom<GridShape>(\"polygon\"),\n stroke: Types.custom<Stroke, StrokeResolved[]>([], { resolve: strokeOps.resolve, lerp: strokeOps.lerp }),\n /** The line out to each category; empty draws none. */\n spokeStroke: Types.custom<Stroke, StrokeResolved[]>([], { resolve: strokeOps.resolve, lerp: strokeOps.lerp }),\n /** How far the web has grown out, `0`-`1`. Rests at 1 so a paused canvas paints it. */\n reveal: 1,\n },\n defaults: { width: \"fill\", height: \"fill\" },\n load: (loader, state) => loadPaints(loader, { strokes: [state.stroke, state.spokeStroke] }),\n render: (ctx, state) => {\n const grow = clamp(state.reveal, 0, 1)\n const rim = state.radius * grow\n if (rim <= 0 || grow <= 0) return\n const count = Math.max(0, Math.round(state.count))\n const levels = Math.max(1, Math.round(state.levels))\n const round = state.shape === \"circle\" || count < 3\n\n // One draw per ring: rings chained into one surface would stroke only their union's outline.\n for (let level = 1; state.stroke.length > 0 && level <= levels; level++) {\n const r = (rim * level) / levels\n const ring = new Graphics2D()\n if (round) {\n ring.ellipse({ width: r * 2, height: r * 2 })\n } else {\n const points: Vector2[] = []\n for (let i = 0; i < count; i++) {\n const d = spokeDirection(i, count)\n points.push({ x: d.x * r, y: d.y * r })\n }\n ring.line({ points, closed: true })\n }\n ctx.draw(ring.stroke(state.stroke).opacity(grow))\n }\n\n if (state.spokeStroke.length === 0) return\n for (let i = 0; i < count; i++) {\n const d = spokeDirection(i, count)\n ctx.draw(\n new Graphics2D()\n .line({ points: [{ x: 0, y: 0 }, { x: d.x * rim, y: d.y * rim }] })\n .stroke(state.spokeStroke)\n .opacity(grow)\n )\n }\n },\n})\n\nexport type RadarGrid = NodeOf<typeof RadarGrid>\n"]}
@@ -0,0 +1,59 @@
1
+ import { type Fill, type FillResolved, type NodeOf, type Node2D as _Node2D, type Node2DProps as _Node2DProps, type Stroke, type StrokeResolved, type Vector2 } from "@motionscript/sdk";
2
+ /** The vertices of a series' polygon: spoke `i` at `fractions[i]` of the rim, scaled by `grow` about the centre. */
3
+ export declare function seriesVertices(fractions: readonly number[], radius: number, grow?: number): Vector2[];
4
+ /** One series of a {@link RadarChart}: a closed polygon through its values, washed in its colour, with a dot on each vertex. `progress` grows it out of the centre. */
5
+ export declare const RadarSeries: import("@motionscript/sdk").Component<"radarSeries", import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
6
+ /** Each spoke's value as a fraction of the rim, `0`-`1`, in spoke order. */
7
+ readonly fractions: import("@motionscript/sdk").Type<number[], number[], false>;
8
+ /** The rim, in px. */
9
+ readonly radius: 0;
10
+ readonly fill: import("@motionscript/sdk").Type<Fill, FillResolved[], false>;
11
+ readonly stroke: import("@motionscript/sdk").Type<Stroke, StrokeResolved[], false>;
12
+ readonly areaOpacity: 0.25;
13
+ readonly dots: true;
14
+ readonly dotSize: 10;
15
+ /** Growth out of the centre, `0`-`1`. Rests at 1 so a paused canvas paints it; `enter` drives it from 0. */
16
+ readonly progress: 1;
17
+ /** Whether the series is drawn at all, flipped alongside its legend entry. */
18
+ readonly enabled: true;
19
+ }>, {
20
+ readonly enter: {
21
+ readonly from: import("@motionscript/sdk").Type<number, number, false>;
22
+ readonly to: import("@motionscript/sdk").Type<number, number, false>;
23
+ };
24
+ }, null, _Node2D<_Node2DProps>, import("@motionscript/sdk").CallProps<import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
25
+ /** Each spoke's value as a fraction of the rim, `0`-`1`, in spoke order. */
26
+ readonly fractions: import("@motionscript/sdk").Type<number[], number[], false>;
27
+ /** The rim, in px. */
28
+ readonly radius: 0;
29
+ readonly fill: import("@motionscript/sdk").Type<Fill, FillResolved[], false>;
30
+ readonly stroke: import("@motionscript/sdk").Type<Stroke, StrokeResolved[], false>;
31
+ readonly areaOpacity: 0.25;
32
+ readonly dots: true;
33
+ readonly dotSize: 10;
34
+ /** Growth out of the centre, `0`-`1`. Rests at 1 so a paused canvas paints it; `enter` drives it from 0. */
35
+ readonly progress: 1;
36
+ /** Whether the series is drawn at all, flipped alongside its legend entry. */
37
+ readonly enabled: true;
38
+ }>, null, import("@motionscript/sdk").BuiltNode<import("@motionscript/sdk").Merge<import("@motionscript/sdk").Box2DState, {
39
+ /** Each spoke's value as a fraction of the rim, `0`-`1`, in spoke order. */
40
+ readonly fractions: import("@motionscript/sdk").Type<number[], number[], false>;
41
+ /** The rim, in px. */
42
+ readonly radius: 0;
43
+ readonly fill: import("@motionscript/sdk").Type<Fill, FillResolved[], false>;
44
+ readonly stroke: import("@motionscript/sdk").Type<Stroke, StrokeResolved[], false>;
45
+ readonly areaOpacity: 0.25;
46
+ readonly dots: true;
47
+ readonly dotSize: 10;
48
+ /** Growth out of the centre, `0`-`1`. Rests at 1 so a paused canvas paints it; `enter` drives it from 0. */
49
+ readonly progress: 1;
50
+ /** Whether the series is drawn at all, flipped alongside its legend entry. */
51
+ readonly enabled: true;
52
+ }>, {
53
+ readonly enter: {
54
+ readonly from: import("@motionscript/sdk").Type<number, number, false>;
55
+ readonly to: import("@motionscript/sdk").Type<number, number, false>;
56
+ };
57
+ }, _Node2D<_Node2DProps>>>>;
58
+ export type RadarSeries = NodeOf<typeof RadarSeries>;
59
+ //# sourceMappingURL=radar-series.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radar-series.d.ts","sourceRoot":"","sources":["../../src/radar-chart/radar-series.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,IAAI,EACT,KAAK,YAAY,EACjB,KAAK,MAAM,EAEX,KAAK,MAAM,IAAI,OAAO,EACtB,KAAK,WAAW,IAAI,YAAY,EAChC,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,OAAO,EACb,MAAM,mBAAmB,CAAA;AAO1B,oHAAoH;AACpH,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,SAAI,GAAG,OAAO,EAAE,CAMhG;AAED,uKAAuK;AACvK,eAAO,MAAM,WAAW;IAGpB,4EAA4E;;IAE5E,sBAAsB;;;;;;;IAOtB,4GAA4G;;IAE5G,8EAA8E;;;;;;;;IAX9E,4EAA4E;;IAE5E,sBAAsB;;;;;;;IAOtB,4GAA4G;;IAE5G,8EAA8E;;;IAX9E,4EAA4E;;IAE5E,sBAAsB;;;;;;;IAOtB,4GAA4G;;IAE5G,8EAA8E;;;;;;;2BAsChF,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAA"}
@@ -0,0 +1,71 @@
1
+ import { Graphics2D, Types, clamp, component2d, fillOps, strokeOps, } from "@motionscript/sdk";
2
+ import { fresh } from "../kit/entries.js";
3
+ import { frameEase } from "../kit/motion.js";
4
+ import { loadPaints } from "../kit/paints.js";
5
+ import { spokeDirection } from "./shared.js";
6
+ /** The vertices of a series' polygon: spoke `i` at `fractions[i]` of the rim, scaled by `grow` about the centre. */
7
+ export function seriesVertices(fractions, radius, grow = 1) {
8
+ return fractions.map((f, i) => {
9
+ const d = spokeDirection(i, fractions.length);
10
+ const r = radius * f * grow;
11
+ return { x: d.x * r, y: d.y * r };
12
+ });
13
+ }
14
+ /** One series of a {@link RadarChart}: a closed polygon through its values, washed in its colour, with a dot on each vertex. `progress` grows it out of the centre. */
15
+ export const RadarSeries = component2d({
16
+ key: "radarSeries",
17
+ state: {
18
+ /** Each spoke's value as a fraction of the rim, `0`-`1`, in spoke order. */
19
+ fractions: fresh(() => []),
20
+ /** The rim, in px. */
21
+ radius: 0,
22
+ fill: Types.custom([], { resolve: fillOps.resolve, lerp: fillOps.lerp }),
23
+ stroke: Types.custom([], { resolve: strokeOps.resolve, lerp: strokeOps.lerp }),
24
+ areaOpacity: 0.25,
25
+ dots: true,
26
+ dotSize: 10,
27
+ /** Growth out of the centre, `0`-`1`. Rests at 1 so a paused canvas paints it; `enter` drives it from 0. */
28
+ progress: 1,
29
+ /** Whether the series is drawn at all, flipped alongside its legend entry. */
30
+ enabled: true,
31
+ },
32
+ defaults: { width: "fill", height: "fill" },
33
+ load: (loader, state) => loadPaints(loader, { fills: [state.fill], strokes: [state.stroke] }),
34
+ commands: {
35
+ /** Grow the polygon from `from` to `to` (of its full size); `showSeries`/`hideSeries` are this with the ends stated. */
36
+ enter: {
37
+ args: { from: Types.number(0, { min: 0, max: 1 }), to: Types.number(1, { min: 0, max: 1 }) },
38
+ run: (timeline) => {
39
+ const { from, to } = timeline.args;
40
+ const ease = frameEase(timeline);
41
+ return timeline.animate((t) => {
42
+ const progress = from + (to - from) * ease(t);
43
+ // Hidden only once fully shrunk, so a hide does not vanish a frame early.
44
+ return { progress, enabled: progress > 0 };
45
+ });
46
+ },
47
+ },
48
+ },
49
+ render: (ctx, state) => {
50
+ if (!state.enabled)
51
+ return;
52
+ const grow = clamp(state.progress, 0, 1);
53
+ if (grow <= 0 || state.radius <= 0 || state.fractions.length === 0)
54
+ return;
55
+ const points = seriesVertices(state.fractions, state.radius, grow);
56
+ if (points.length >= 2) {
57
+ const fill = new Graphics2D().line({ points, closed: true });
58
+ if (state.areaOpacity > 0 && state.fill.length > 0)
59
+ ctx.draw(fill.fill(state.fill).opacity(clamp(state.areaOpacity, 0, 1)));
60
+ if (state.stroke.length > 0)
61
+ ctx.draw(new Graphics2D().line({ points, closed: true }).stroke(state.stroke));
62
+ }
63
+ if (state.dots && state.dotSize > 0 && state.fill.length > 0) {
64
+ const dots = new Graphics2D();
65
+ for (const p of points)
66
+ dots.ellipse({ x: p.x, y: p.y, width: state.dotSize, height: state.dotSize });
67
+ ctx.draw(dots.fill(state.fill));
68
+ }
69
+ },
70
+ });
71
+ //# sourceMappingURL=radar-series.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radar-series.js","sourceRoot":"","sources":["../../src/radar-chart/radar-series.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Graphics2D,\n Types,\n clamp,\n component2d,\n fillOps,\n strokeOps,\n type Fill,\n type FillResolved,\n type NodeOf,\n // Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.\n type Node2D as _Node2D,\n type Node2DProps as _Node2DProps,\n type Stroke,\n type StrokeResolved,\n type Vector2,\n} from \"@motionscript/sdk\"\n\nimport { fresh } from \"../kit/entries\"\nimport { frameEase } from \"../kit/motion\"\nimport { loadPaints } from \"../kit/paints\"\nimport { spokeDirection } from \"./shared\"\n\n/** The vertices of a series' polygon: spoke `i` at `fractions[i]` of the rim, scaled by `grow` about the centre. */\nexport function seriesVertices(fractions: readonly number[], radius: number, grow = 1): Vector2[] {\n return fractions.map((f, i) => {\n const d = spokeDirection(i, fractions.length)\n const r = radius * f * grow\n return { x: d.x * r, y: d.y * r }\n })\n}\n\n/** One series of a {@link RadarChart}: a closed polygon through its values, washed in its colour, with a dot on each vertex. `progress` grows it out of the centre. */\nexport const RadarSeries = component2d({\n key: \"radarSeries\",\n state: {\n /** Each spoke's value as a fraction of the rim, `0`-`1`, in spoke order. */\n fractions: fresh<number[]>(() => []),\n /** The rim, in px. */\n radius: 0,\n fill: Types.custom<Fill, FillResolved[]>([], { resolve: fillOps.resolve, lerp: fillOps.lerp }),\n stroke: Types.custom<Stroke, StrokeResolved[]>([], { resolve: strokeOps.resolve, lerp: strokeOps.lerp }),\n areaOpacity: 0.25,\n dots: true,\n dotSize: 10,\n /** Growth out of the centre, `0`-`1`. Rests at 1 so a paused canvas paints it; `enter` drives it from 0. */\n progress: 1,\n /** Whether the series is drawn at all, flipped alongside its legend entry. */\n enabled: true,\n },\n defaults: { width: \"fill\", height: \"fill\" },\n load: (loader, state) => loadPaints(loader, { fills: [state.fill], strokes: [state.stroke] }),\n commands: {\n /** Grow the polygon from `from` to `to` (of its full size); `showSeries`/`hideSeries` are this with the ends stated. */\n enter: {\n args: { from: Types.number(0, { min: 0, max: 1 }), to: Types.number(1, { min: 0, max: 1 }) },\n run: (timeline) => {\n const { from, to } = timeline.args\n const ease = frameEase(timeline)\n return timeline.animate((t) => {\n const progress = from + (to - from) * ease(t)\n // Hidden only once fully shrunk, so a hide does not vanish a frame early.\n return { progress, enabled: progress > 0 }\n })\n },\n },\n },\n render: (ctx, state) => {\n if (!state.enabled) return\n const grow = clamp(state.progress, 0, 1)\n if (grow <= 0 || state.radius <= 0 || state.fractions.length === 0) return\n const points = seriesVertices(state.fractions, state.radius, grow)\n\n if (points.length >= 2) {\n const fill = new Graphics2D().line({ points, closed: true })\n if (state.areaOpacity > 0 && state.fill.length > 0) ctx.draw(fill.fill(state.fill).opacity(clamp(state.areaOpacity, 0, 1)))\n if (state.stroke.length > 0) ctx.draw(new Graphics2D().line({ points, closed: true }).stroke(state.stroke))\n }\n\n if (state.dots && state.dotSize > 0 && state.fill.length > 0) {\n const dots = new Graphics2D()\n for (const p of points) dots.ellipse({ x: p.x, y: p.y, width: state.dotSize, height: state.dotSize })\n ctx.draw(dots.fill(state.fill))\n }\n },\n})\n\nexport type RadarSeries = NodeOf<typeof RadarSeries>\n"]}
@@ -0,0 +1,73 @@
1
+ import { type DataRecord, type Size2D, type StrokeResolved, type Stroke as _Stroke, type Vector2 } from "@motionscript/sdk";
2
+ import { type ChartSeries } from "../kit/shared.js";
3
+ export type GridShape = "polygon" | "circle";
4
+ /** The radar chart's own grid group: the web the polygons are drawn over. */
5
+ export declare const RADAR_GRID: {
6
+ /** Whether the rings are straight-edged (a polygon through the spokes) or round. */
7
+ gridShape: import("@motionscript/sdk").Type<"circle" | "polygon", "circle" | "polygon", false>;
8
+ /** How many rings, outermost at the scale's maximum. */
9
+ gridLevels: import("@motionscript/sdk").Type<number, number, false>;
10
+ /** The rings; empty draws none. */
11
+ gridStroke: import("@motionscript/sdk").Type<_Stroke, StrokeResolved[], false>;
12
+ /** The line from the centre out to each category; empty draws none. */
13
+ spokeStroke: import("@motionscript/sdk").Type<_Stroke, StrokeResolved[], false>;
14
+ /** Read as the chart is built: whether the category names stand around the rim. */
15
+ spokeLabels: import("@motionscript/sdk").Type<boolean, boolean, false>;
16
+ };
17
+ export declare const RADAR_MARKS: {
18
+ /** Outline weight of each series' polygon. */
19
+ strokeWidth: import("@motionscript/sdk").Type<number, number, false>;
20
+ /** How strongly each polygon is filled in its series colour; 0 leaves an outline. */
21
+ areaOpacity: import("@motionscript/sdk").Type<number, number, false>;
22
+ /** Whether a dot marks each value on its spoke. */
23
+ dots: import("@motionscript/sdk").Type<boolean, boolean, false>;
24
+ dotSize: import("@motionscript/sdk").Type<number, number, false>;
25
+ };
26
+ /** The grid and marks fields the parts read, whichever way the chart's style is spelled. */
27
+ export interface RadarStyle {
28
+ readonly gridShape: GridShape;
29
+ readonly gridLevels: number;
30
+ readonly gridStroke: StrokeResolved[];
31
+ readonly spokeStroke: StrokeResolved[];
32
+ readonly spokeLabels: boolean;
33
+ readonly strokeWidth: number;
34
+ readonly areaOpacity: number;
35
+ readonly dots: boolean;
36
+ readonly dotSize: number;
37
+ readonly fontSize: number;
38
+ }
39
+ /** Spoke `index` of `count`, as a unit direction: 12 o'clock first, clockwise on screen, y up. */
40
+ export declare function spokeDirection(index: number, count: number): Vector2;
41
+ /** One category per data row. */
42
+ export declare function categoriesOf(data: readonly DataRecord[], field: string): string[];
43
+ /** One list of values per series, a value per row. */
44
+ export declare function valuesOf(data: readonly DataRecord[], series: readonly ChartSeries[]): number[][];
45
+ /** `value` rounded up to a tidy 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8 times a power of ten. */
46
+ export declare function niceCeil(value: number): number;
47
+ /**
48
+ * The scale the web is drawn to: `min` (default 0) to `max`, which when unset
49
+ * (or not above `min`) is the largest value rounded up tidily.
50
+ */
51
+ export declare function scaleOf(values: readonly (readonly number[])[], min: number, max: number): {
52
+ min: number;
53
+ max: number;
54
+ };
55
+ /** Where `value` stands between the scale's ends, `0`-`1`. */
56
+ export declare function fractionOf(value: number, scale: {
57
+ min: number;
58
+ max: number;
59
+ }): number;
60
+ export type LabelAnchor = "centerLeft" | "centerRight" | "bottomCenter" | "topCenter";
61
+ /** Which corner of a category's label touches the point beside its spoke's end. */
62
+ export declare function labelAnchor(index: number, count: number): LabelAnchor;
63
+ export declare function labelPad(fontSize: number): number;
64
+ /** Rough size of a label, since the web is fitted before any text is measured. */
65
+ export declare function labelSize(caption: string, fontSize: number): Size2D;
66
+ /**
67
+ * The rim's radius for a box: the largest the web can be with every label
68
+ * (when there are labels) still inside it.
69
+ */
70
+ export declare function fitRadius(box: Size2D, categories: readonly string[], fontSize: number, labelled: boolean): number;
71
+ /** Where spoke `index`'s label is pinned: just past the rim, centre-relative, y up. */
72
+ export declare function labelPoint(index: number, count: number, radius: number, fontSize: number): Vector2;
73
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/radar-chart/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,UAAU,EAAE,KAAK,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,IAAI,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAElI,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3D,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAA;AAE5C,6EAA6E;AAC7E,eAAO,MAAM,UAAU;IACrB,oFAAoF;;IAEpF,wDAAwD;;IAExD,mCAAmC;;IAEnC,uEAAuE;;IAEvE,mFAAmF;;CAEpF,CAAA;AAED,eAAO,MAAM,WAAW;IACtB,8CAA8C;;IAE9C,qFAAqF;;IAErF,mDAAmD;;;CAGpD,CAAA;AAED,4FAA4F;AAC5F,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,cAAc,EAAE,CAAA;IACrC,QAAQ,CAAC,WAAW,EAAE,cAAc,EAAE,CAAA;IACtC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAWD,kGAAkG;AAClG,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAGpE;AAED,iCAAiC;AACjC,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEjF;AAED,sDAAsD;AACtD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,UAAU,EAAE,EAAE,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,EAAE,EAAE,CAEhG;AAID,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAMtH;AAED,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAIrF;AAED,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,aAAa,GAAG,cAAc,GAAG,WAAW,CAAA;AAErF,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAKrE;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAgBjH;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIlG"}
@@ -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"]}