@motionscript/charts 0.1.0-alpha.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +201 -201
  3. package/NOTICE +49 -49
  4. package/README.md +99 -99
  5. package/dist/bar-chart/bar-chart.js.map +1 -1
  6. package/dist/bar-chart/category-axis.js.map +1 -1
  7. package/dist/bar-chart/chart-bars.js.map +1 -1
  8. package/dist/bar-chart/index.js.map +1 -1
  9. package/dist/bar-chart/shared.js.map +1 -1
  10. package/dist/browser/chunks/{chunk-XROBIRQK.js → chunk-TJNHLQGV.js} +1 -1
  11. package/dist/browser/chunks/chunk-TJNHLQGV.js.map +7 -0
  12. package/dist/browser/index.js +1 -1
  13. package/dist/browser/index.js.map +1 -1
  14. package/dist/browser/kit.js +1 -1
  15. package/dist/browser/manifest.json +1 -1
  16. package/dist/engine.d.ts +1 -1
  17. package/dist/engine.js +1 -1
  18. package/dist/engine.js.map +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/kit/cartesian.js.map +1 -1
  21. package/dist/kit/chart-border.js.map +1 -1
  22. package/dist/kit/chart-canvas.js.map +1 -1
  23. package/dist/kit/chart-legend.js.map +1 -1
  24. package/dist/kit/entries.js.map +1 -1
  25. package/dist/kit/motion.js.map +1 -1
  26. package/dist/kit/paints.js.map +1 -1
  27. package/dist/kit/shared.js.map +1 -1
  28. package/dist/kit/vertical-axis.js.map +1 -1
  29. package/dist/line-chart/chart-line.js.map +1 -1
  30. package/dist/line-chart/chart-region.js.map +1 -1
  31. package/dist/line-chart/horizontal-axis.js.map +1 -1
  32. package/dist/line-chart/index.d.ts +1 -1
  33. package/dist/line-chart/index.js +1 -1
  34. package/dist/line-chart/index.js.map +1 -1
  35. package/dist/line-chart/line-chart.js.map +1 -1
  36. package/dist/line-chart/shared.js.map +1 -1
  37. package/dist/line-chart/x-scale.js.map +1 -1
  38. package/dist/nodes.js.map +1 -1
  39. package/dist/pie-chart/index.js.map +1 -1
  40. package/dist/pie-chart/pie-chart.js.map +1 -1
  41. package/dist/pie-chart/pie-legend.js.map +1 -1
  42. package/dist/pie-chart/pie-ring.js.map +1 -1
  43. package/dist/pie-chart/shared.js.map +1 -1
  44. package/package.json +4 -4
  45. package/registry.json +23 -23
  46. package/src/bar-chart/bar-chart.ts +542 -542
  47. package/src/bar-chart/category-axis.ts +162 -162
  48. package/src/bar-chart/chart-bars.ts +287 -287
  49. package/src/bar-chart/index.ts +31 -31
  50. package/src/bar-chart/shared.ts +354 -354
  51. package/src/engine.ts +28 -28
  52. package/src/index.ts +5 -5
  53. package/src/kit/cartesian.ts +81 -81
  54. package/src/kit/chart-border.ts +50 -50
  55. package/src/kit/chart-canvas.ts +122 -122
  56. package/src/kit/chart-legend.ts +229 -229
  57. package/src/kit/entries.ts +20 -20
  58. package/src/kit/motion.ts +90 -90
  59. package/src/kit/paints.ts +15 -15
  60. package/src/kit/shared.ts +782 -782
  61. package/src/kit/vertical-axis.ts +194 -194
  62. package/src/line-chart/chart-line.ts +156 -156
  63. package/src/line-chart/chart-region.ts +90 -90
  64. package/src/line-chart/horizontal-axis.ts +151 -151
  65. package/src/line-chart/index.ts +39 -39
  66. package/src/line-chart/line-chart.ts +598 -598
  67. package/src/line-chart/shared.ts +473 -473
  68. package/src/line-chart/x-scale.ts +382 -382
  69. package/src/nodes.ts +18 -18
  70. package/src/pie-chart/index.ts +37 -37
  71. package/src/pie-chart/pie-chart.ts +296 -296
  72. package/src/pie-chart/pie-legend.ts +169 -169
  73. package/src/pie-chart/pie-ring.ts +848 -848
  74. package/src/pie-chart/shared.ts +496 -496
  75. package/dist/browser/chunks/chunk-XROBIRQK.js.map +0 -7
  76. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -1,169 +1,169 @@
1
- import {
2
- Flow,
3
- Paint,
4
- Rect,
5
- Shapes,
6
- Text,
7
- Types,
8
- component2d,
9
- textOps,
10
- type NodeOf,
11
- type RectProps,
12
- // Named so the column's emitted type reaches the Rect surface it spreads through the sdk, not core.
13
- type Alignment as _Alignment,
14
- type CornerRadiusResolved as _CornerRadiusResolved,
15
- type CornerStyleResolved as _CornerStyleResolved,
16
- type Fill as _Fill,
17
- type FillResolved as _FillResolved,
18
- type FlowMode as _FlowMode,
19
- type FlowResolved as _FlowResolved,
20
- type GapSize as _GapSize,
21
- type Node2D as _Node2D,
22
- type Node2DProps as _Node2DProps,
23
- type RectCornerRadius as _RectCornerRadius,
24
- type RectCornerStyle as _RectCornerStyle,
25
- type Shadow as _Shadow,
26
- type ShadowResolved as _ShadowResolved,
27
- type Stroke as _Stroke,
28
- type StrokeResolved as _StrokeResolved,
29
- type Vector2 as _Vector2,
30
- } from "@motionscript/sdk"
31
-
32
- import { fresh } from "../kit/entries"
33
- import { armed, arrival, arrivalOf, arrivalsEntry, beats, frameEase, type Arrival } from "../kit/motion"
34
- import {
35
- DEFAULT_LEGEND,
36
- clampUnit,
37
- type LegendConfig,
38
- } from "../kit/shared"
39
- import type { PieSlice } from "./shared"
40
-
41
- export interface PieLegendProps extends RectProps {
42
- /** One entry per slice, in ring order. */
43
- slices: PieSlice[]
44
- /** Padding, caption style, gaps, and swatch geometry — see {@link LegendConfig}. */
45
- legend: LegendConfig
46
- /**
47
- * Per-slice opacity multiplier, by index — the same signal that dims the ring,
48
- * so an entry fades in step with the wedge it names. Missing entries read as 1.
49
- */
50
- dims: number[]
51
- }
52
-
53
- /** How far an entry starts to the right of its slot when it enters, in px. */
54
- const SLIDE = 16
55
-
56
- /**
57
- * The legend of a {@link PieChart}: a **vertical-flow column** of entries, one per
58
- * slice, each pairing the slice's caption with its colour swatch.
59
- *
60
- * A column rather than the cartesian charts' row, because it sits *beside* the
61
- * ring rather than beneath the plot — a pie is as tall as it is wide, so the
62
- * space it leaves over is a vertical strip. It is otherwise the same entry: the
63
- * swatch carries the colour, the caption reads in the legend's neutral ink, and
64
- * both take the shared {@link LegendConfig}.
65
- *
66
- * There is no show/hide here. A pie's marks are its *rows*, and a row that
67
- * shouldn't be in the chart shouldn't be in the table — removing one would
68
- * silently re-cut every other wedge's share, which is a different chart rather
69
- * than the same chart with something hidden. Selection is what a pie has
70
- * instead, and it arrives through {@link PieLegendProps.dims}.
71
- */
72
- export const PieLegend = component2d({
73
- key: "pieLegend",
74
- state: {
75
- ...Paint.state,
76
- ...Flow.state,
77
- ...Shapes.corners,
78
- slices: fresh<PieSlice[]>(() => []),
79
- legend: Types.custom<LegendConfig>(DEFAULT_LEGEND),
80
- dims: fresh<number[]>(() => []),
81
- arrivals: arrivalsEntry(),
82
- },
83
- // A column, for the reason `ChartLegend` gives for its row: a `Rect` given no
84
- // flow lays its children out `freeform`, every caption on top of the last.
85
- defaults: {
86
- flow: "vertical",
87
- align: "centerLeft",
88
- width: "hug",
89
- height: "hug",
90
- gap: (state) => state.legend.itemGap / 3,
91
- padding: (state) => state.legend.padding,
92
- },
93
- // One entry per slice, ordered top → bottom. The selection dim rides a wrapper
94
- // rather than the entry itself, so the entrance can animate the entry's own
95
- // opacity without the two fighting over one value — opacity multiplies down
96
- // the tree, which is exactly the composition wanted here.
97
- children: (state) => {
98
- const marker = state.legend.markerStyle
99
- return state.slices.map((_, index) =>
100
- Rect({
101
- width: "hug",
102
- height: "hug",
103
- flow: "freeform",
104
- opacity: () => clampUnit(state.dims[index] ?? 1),
105
- children: [
106
- Rect({
107
- flow: "horizontal",
108
- gap: 12,
109
- align: "center",
110
- width: "hug",
111
- height: "hug",
112
- opacity: () => arrivalOf(state.arrivals, index).opacity,
113
- x: () => arrivalOf(state.arrivals, index).shift,
114
- children: [
115
- Rect({
116
- width: marker.height,
117
- height: marker.height,
118
- cornerRadius: marker.borderRadius,
119
- fill: () => state.slices[index]?.fill ?? "white",
120
- stroke: marker.stroke,
121
- shadow: marker.shadow,
122
- }),
123
- Text({
124
- ...textOps.styleProps(state.legend.textStyle),
125
- text: () => state.slices[index]?.category ?? "",
126
- }),
127
- ],
128
- }),
129
- ],
130
- })
131
- )
132
- },
133
- commands: {
134
- ...Paint.commands,
135
- /**
136
- * Every entry at its pre-entrance state, shifted right and transparent: what
137
- * a chart shows ahead of this legend's own beat in its entrance, since
138
- * `enter` arms the entries only on its first frame.
139
- */
140
- arm: (timeline, state) =>
141
- timeline.animate(() => ({ arrivals: armed([...state.slices.keys()], { opacity: 0, shift: SLIDE }) })),
142
- /**
143
- * Slide + fade every entry in from the right, staggered top-to-bottom. Its
144
- * first frame arms them all: shifted right and transparent.
145
- *
146
- * The timeline's duration is the **whole** entrance: the stagger is carved
147
- * out of it rather than added on top, so the command takes exactly the time
148
- * the timeline laid out for it.
149
- */
150
- enter: (timeline, state) => {
151
- const count = state.slices.length
152
- if (count === 0) return timeline.wait(timeline.duration)
153
- const from: Arrival = { opacity: 0, shift: SLIDE }
154
- const arm = timeline.animate(() => ({ arrivals: armed([...state.slices.keys()], from) }))
155
- // Each gap is a fifth of an entry's own move, the rhythm the axes use;
156
- // solved for from the budget rather than added to it.
157
- const per = timeline.duration / (1 + Math.max(0, count - 1) / 5)
158
- const step = per / 5
159
- const ease = frameEase(timeline)
160
- const entries = state.slices.map((_, i) => ({ at: i * step, duration: per, piece: arrival(timeline, i, from, ease) }))
161
- return timeline.together([arm, beats(timeline, entries)])
162
- },
163
- },
164
- measure: (measurer, state, children) => Flow.measure(measurer, state, children, Paint.inset(state)),
165
- layout: (box, state, children) => Flow.layout(box, state, children, Paint.inset(state)),
166
- render: (ctx, state) => Paint.draw(ctx, state, Shapes.rect(ctx.size, state)),
167
- })
168
-
169
- export type PieLegend = NodeOf<typeof PieLegend>
1
+ import {
2
+ Flow,
3
+ Paint,
4
+ Rect,
5
+ Shapes,
6
+ Text,
7
+ Types,
8
+ component2d,
9
+ textOps,
10
+ type NodeOf,
11
+ type RectProps,
12
+ // Named so the column's emitted type reaches the Rect surface it spreads through the sdk, not core.
13
+ type Alignment as _Alignment,
14
+ type CornerRadiusResolved as _CornerRadiusResolved,
15
+ type CornerStyleResolved as _CornerStyleResolved,
16
+ type Fill as _Fill,
17
+ type FillResolved as _FillResolved,
18
+ type FlowMode as _FlowMode,
19
+ type FlowResolved as _FlowResolved,
20
+ type GapSize as _GapSize,
21
+ type Node2D as _Node2D,
22
+ type Node2DProps as _Node2DProps,
23
+ type RectCornerRadius as _RectCornerRadius,
24
+ type RectCornerStyle as _RectCornerStyle,
25
+ type Shadow as _Shadow,
26
+ type ShadowResolved as _ShadowResolved,
27
+ type Stroke as _Stroke,
28
+ type StrokeResolved as _StrokeResolved,
29
+ type Vector2 as _Vector2,
30
+ } from "@motionscript/sdk"
31
+
32
+ import { fresh } from "../kit/entries"
33
+ import { armed, arrival, arrivalOf, arrivalsEntry, beats, frameEase, type Arrival } from "../kit/motion"
34
+ import {
35
+ DEFAULT_LEGEND,
36
+ clampUnit,
37
+ type LegendConfig,
38
+ } from "../kit/shared"
39
+ import type { PieSlice } from "./shared"
40
+
41
+ export interface PieLegendProps extends RectProps {
42
+ /** One entry per slice, in ring order. */
43
+ slices: PieSlice[]
44
+ /** Padding, caption style, gaps, and swatch geometry — see {@link LegendConfig}. */
45
+ legend: LegendConfig
46
+ /**
47
+ * Per-slice opacity multiplier, by index — the same signal that dims the ring,
48
+ * so an entry fades in step with the wedge it names. Missing entries read as 1.
49
+ */
50
+ dims: number[]
51
+ }
52
+
53
+ /** How far an entry starts to the right of its slot when it enters, in px. */
54
+ const SLIDE = 16
55
+
56
+ /**
57
+ * The legend of a {@link PieChart}: a **vertical-flow column** of entries, one per
58
+ * slice, each pairing the slice's caption with its colour swatch.
59
+ *
60
+ * A column rather than the cartesian charts' row, because it sits *beside* the
61
+ * ring rather than beneath the plot — a pie is as tall as it is wide, so the
62
+ * space it leaves over is a vertical strip. It is otherwise the same entry: the
63
+ * swatch carries the colour, the caption reads in the legend's neutral ink, and
64
+ * both take the shared {@link LegendConfig}.
65
+ *
66
+ * There is no show/hide here. A pie's marks are its *rows*, and a row that
67
+ * shouldn't be in the chart shouldn't be in the table — removing one would
68
+ * silently re-cut every other wedge's share, which is a different chart rather
69
+ * than the same chart with something hidden. Selection is what a pie has
70
+ * instead, and it arrives through {@link PieLegendProps.dims}.
71
+ */
72
+ export const PieLegend = component2d({
73
+ key: "pieLegend",
74
+ state: {
75
+ ...Paint.state,
76
+ ...Flow.state,
77
+ ...Shapes.corners,
78
+ slices: fresh<PieSlice[]>(() => []),
79
+ legend: Types.custom<LegendConfig>(DEFAULT_LEGEND),
80
+ dims: fresh<number[]>(() => []),
81
+ arrivals: arrivalsEntry(),
82
+ },
83
+ // A column, for the reason `ChartLegend` gives for its row: a `Rect` given no
84
+ // flow lays its children out `freeform`, every caption on top of the last.
85
+ defaults: {
86
+ flow: "vertical",
87
+ align: "centerLeft",
88
+ width: "hug",
89
+ height: "hug",
90
+ gap: (state) => state.legend.itemGap / 3,
91
+ padding: (state) => state.legend.padding,
92
+ },
93
+ // One entry per slice, ordered top → bottom. The selection dim rides a wrapper
94
+ // rather than the entry itself, so the entrance can animate the entry's own
95
+ // opacity without the two fighting over one value — opacity multiplies down
96
+ // the tree, which is exactly the composition wanted here.
97
+ children: (state) => {
98
+ const marker = state.legend.markerStyle
99
+ return state.slices.map((_, index) =>
100
+ Rect({
101
+ width: "hug",
102
+ height: "hug",
103
+ flow: "freeform",
104
+ opacity: () => clampUnit(state.dims[index] ?? 1),
105
+ children: [
106
+ Rect({
107
+ flow: "horizontal",
108
+ gap: 12,
109
+ align: "center",
110
+ width: "hug",
111
+ height: "hug",
112
+ opacity: () => arrivalOf(state.arrivals, index).opacity,
113
+ x: () => arrivalOf(state.arrivals, index).shift,
114
+ children: [
115
+ Rect({
116
+ width: marker.height,
117
+ height: marker.height,
118
+ cornerRadius: marker.borderRadius,
119
+ fill: () => state.slices[index]?.fill ?? "white",
120
+ stroke: marker.stroke,
121
+ shadow: marker.shadow,
122
+ }),
123
+ Text({
124
+ ...textOps.styleProps(state.legend.textStyle),
125
+ text: () => state.slices[index]?.category ?? "",
126
+ }),
127
+ ],
128
+ }),
129
+ ],
130
+ })
131
+ )
132
+ },
133
+ commands: {
134
+ ...Paint.commands,
135
+ /**
136
+ * Every entry at its pre-entrance state, shifted right and transparent: what
137
+ * a chart shows ahead of this legend's own beat in its entrance, since
138
+ * `enter` arms the entries only on its first frame.
139
+ */
140
+ arm: (timeline, state) =>
141
+ timeline.animate(() => ({ arrivals: armed([...state.slices.keys()], { opacity: 0, shift: SLIDE }) })),
142
+ /**
143
+ * Slide + fade every entry in from the right, staggered top-to-bottom. Its
144
+ * first frame arms them all: shifted right and transparent.
145
+ *
146
+ * The timeline's duration is the **whole** entrance: the stagger is carved
147
+ * out of it rather than added on top, so the command takes exactly the time
148
+ * the timeline laid out for it.
149
+ */
150
+ enter: (timeline, state) => {
151
+ const count = state.slices.length
152
+ if (count === 0) return timeline.wait(timeline.duration)
153
+ const from: Arrival = { opacity: 0, shift: SLIDE }
154
+ const arm = timeline.animate(() => ({ arrivals: armed([...state.slices.keys()], from) }))
155
+ // Each gap is a fifth of an entry's own move, the rhythm the axes use;
156
+ // solved for from the budget rather than added to it.
157
+ const per = timeline.duration / (1 + Math.max(0, count - 1) / 5)
158
+ const step = per / 5
159
+ const ease = frameEase(timeline)
160
+ const entries = state.slices.map((_, i) => ({ at: i * step, duration: per, piece: arrival(timeline, i, from, ease) }))
161
+ return timeline.together([arm, beats(timeline, entries)])
162
+ },
163
+ },
164
+ measure: (measurer, state, children) => Flow.measure(measurer, state, children, Paint.inset(state)),
165
+ layout: (box, state, children) => Flow.layout(box, state, children, Paint.inset(state)),
166
+ render: (ctx, state) => Paint.draw(ctx, state, Shapes.rect(ctx.size, state)),
167
+ })
168
+
169
+ export type PieLegend = NodeOf<typeof PieLegend>