@motionscript/charts 0.1.0-alpha.3 → 0.1.0-alpha.5

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 (93) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE +201 -201
  3. package/NOTICE +48 -0
  4. package/README.md +85 -0
  5. package/dist/bar-chart/bar-chart.d.ts +28 -13
  6. package/dist/bar-chart/bar-chart.d.ts.map +1 -1
  7. package/dist/bar-chart/bar-chart.js +60 -56
  8. package/dist/bar-chart/bar-chart.js.map +1 -1
  9. package/dist/bar-chart/category-axis.js.map +1 -1
  10. package/dist/bar-chart/chart-bars.js.map +1 -1
  11. package/dist/bar-chart/index.js.map +1 -1
  12. package/dist/bar-chart/shared.d.ts +3 -3
  13. package/dist/bar-chart/shared.js +3 -3
  14. package/dist/bar-chart/shared.js.map +1 -1
  15. package/dist/browser/chunks/chunk-5ZPBV6BU.js +2 -0
  16. package/dist/browser/chunks/chunk-5ZPBV6BU.js.map +7 -0
  17. package/dist/browser/index.js +1 -1
  18. package/dist/browser/index.js.map +3 -3
  19. package/dist/browser/kit.js +1 -1
  20. package/dist/browser/manifest.json +3 -2
  21. package/dist/engine.js.map +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/kit/chart-border.js.map +1 -1
  24. package/dist/kit/chart-canvas.js.map +1 -1
  25. package/dist/kit/chart-legend.d.ts +4 -25
  26. package/dist/kit/chart-legend.d.ts.map +1 -1
  27. package/dist/kit/chart-legend.js +31 -55
  28. package/dist/kit/chart-legend.js.map +1 -1
  29. package/dist/kit/shared.d.ts +29 -6
  30. package/dist/kit/shared.d.ts.map +1 -1
  31. package/dist/kit/shared.js +64 -8
  32. package/dist/kit/shared.js.map +1 -1
  33. package/dist/kit/vertical-axis.js.map +1 -1
  34. package/dist/line-chart/chart-line.d.ts +2 -2
  35. package/dist/line-chart/chart-line.js.map +1 -1
  36. package/dist/line-chart/chart-region.js.map +1 -1
  37. package/dist/line-chart/horizontal-axis.js.map +1 -1
  38. package/dist/line-chart/index.js.map +1 -1
  39. package/dist/line-chart/line-chart.d.ts +9 -1
  40. package/dist/line-chart/line-chart.d.ts.map +1 -1
  41. package/dist/line-chart/line-chart.js +32 -8
  42. package/dist/line-chart/line-chart.js.map +1 -1
  43. package/dist/line-chart/shared.d.ts +3 -3
  44. package/dist/line-chart/shared.d.ts.map +1 -1
  45. package/dist/line-chart/shared.js +19 -4
  46. package/dist/line-chart/shared.js.map +1 -1
  47. package/dist/line-chart/x-scale.d.ts +1 -1
  48. package/dist/line-chart/x-scale.js +1 -1
  49. package/dist/line-chart/x-scale.js.map +1 -1
  50. package/dist/nodes.js.map +1 -1
  51. package/dist/pie-chart/index.js.map +1 -1
  52. package/dist/pie-chart/pie-chart.d.ts +2 -2
  53. package/dist/pie-chart/pie-chart.d.ts.map +1 -1
  54. package/dist/pie-chart/pie-chart.js +9 -7
  55. package/dist/pie-chart/pie-chart.js.map +1 -1
  56. package/dist/pie-chart/pie-legend.js +4 -4
  57. package/dist/pie-chart/pie-legend.js.map +1 -1
  58. package/dist/pie-chart/pie-ring.d.ts +13 -4
  59. package/dist/pie-chart/pie-ring.d.ts.map +1 -1
  60. package/dist/pie-chart/pie-ring.js +44 -14
  61. package/dist/pie-chart/pie-ring.js.map +1 -1
  62. package/dist/pie-chart/shared.d.ts +3 -3
  63. package/dist/pie-chart/shared.js +3 -3
  64. package/dist/pie-chart/shared.js.map +1 -1
  65. package/package.json +6 -5
  66. package/registry.json +23 -23
  67. package/src/bar-chart/bar-chart.ts +820 -814
  68. package/src/bar-chart/category-axis.ts +177 -177
  69. package/src/bar-chart/chart-bars.ts +310 -310
  70. package/src/bar-chart/index.ts +31 -31
  71. package/src/bar-chart/shared.ts +354 -354
  72. package/src/engine.ts +26 -26
  73. package/src/index.ts +5 -5
  74. package/src/kit/chart-border.ts +61 -61
  75. package/src/kit/chart-canvas.ts +157 -157
  76. package/src/kit/chart-legend.ts +245 -250
  77. package/src/kit/shared.ts +733 -675
  78. package/src/kit/vertical-axis.ts +224 -224
  79. package/src/line-chart/chart-line.ts +208 -208
  80. package/src/line-chart/chart-region.ts +135 -135
  81. package/src/line-chart/horizontal-axis.ts +202 -202
  82. package/src/line-chart/index.ts +39 -39
  83. package/src/line-chart/line-chart.ts +961 -929
  84. package/src/line-chart/shared.ts +473 -459
  85. package/src/line-chart/x-scale.ts +367 -367
  86. package/src/nodes.ts +20 -20
  87. package/src/pie-chart/index.ts +37 -37
  88. package/src/pie-chart/pie-chart.ts +383 -381
  89. package/src/pie-chart/pie-legend.ts +159 -159
  90. package/src/pie-chart/pie-ring.ts +935 -903
  91. package/src/pie-chart/shared.ts +490 -490
  92. package/dist/browser/chunks/chunk-RMHQ6GYU.js +0 -2
  93. package/dist/browser/chunks/chunk-RMHQ6GYU.js.map +0 -7
@@ -1,177 +1,177 @@
1
- import {
2
- command,
3
- Node2D,
4
- Rect,
5
- Text,
6
- easeOut,
7
- property,
8
- insetsOps,
9
- textOps,
10
- type CommandArgs,
11
- type NodeConfig,
12
- type Node2DProps,
13
- } from "@motionscript/sdk"
14
-
15
- import {
16
- DEFAULT_X_AXIS,
17
- clampUnit,
18
- enterBudget,
19
- type AxisConfig,
20
- } from "../kit/shared"
21
- import { at, compose, hold, type Seekable } from "@motionscript/sdk/component"
22
-
23
- export interface CategoryAxisProps extends Node2DProps {
24
- /** One caption per category slot, left to right — the chart's rows, in order. */
25
- categories: string[]
26
- /** Caption/title text style and gutters — see {@link AxisConfig}. */
27
- axis: AxisConfig
28
- /**
29
- * Caption drawn centred in the gutter beneath the category row. Empty string
30
- * (the default) hides it.
31
- */
32
- label: string
33
- /**
34
- * Per-category opacity multiplier — how the chart's category spotlight reaches
35
- * the captions, so the name under a dimmed column dims with it. Missing
36
- * entries read as 1.
37
- */
38
- dims: number[]
39
- }
40
-
41
- /** How far a caption starts above its slot when it enters, in px. */
42
- const SLIDE = 24
43
-
44
- /**
45
- * The **categorical** horizontal axis of a {@link BarChart}: a row of
46
- * {@link Text} captions, one per row of the data, each centred on its slot
47
- * beneath the baseline.
48
- *
49
- * Not a variant of the line chart's `HorizontalAxis` and deliberately not
50
- * folded into it: that axis places markers by *value* across a numeric range,
51
- * this one divides its width into equal slots and centres a name in each. The
52
- * two look alike on screen and share nothing underneath — one reads a scale,
53
- * the other counts rows.
54
- *
55
- * The slot arithmetic is the same as the plot's, because this node is laid out
56
- * to exactly the plot's width (the chart reserves the axis gutter with an
57
- * invisible twin of the value axis, as the line chart does), so a caption lands
58
- * under the columns it names without either side being told about the other.
59
- *
60
- * {@link enter} staggers the captions in left-to-right, each lifted a little
61
- * above its slot and transparent; the resting state is the landed one, so a
62
- * chart that is never told to enter still reads as finished.
63
- */
64
- export class CategoryAxis extends Node2D<CategoryAxisProps> {
65
- @property({ default: () => [] as string[] }) declare categories: string[]
66
- @property({ default: DEFAULT_X_AXIS }) declare axis: AxisConfig
67
- @property({ default: "" }) declare label: string
68
- @property({ default: () => [] as number[] }) declare dims: number[]
69
-
70
- /** One caption per category, ordered left → right, for the staggered enter. */
71
- private captionNodes: Text[] = []
72
- /** The axis title, or null when `label` is empty. */
73
- private labelNode: Text | null = null
74
-
75
- /** Centre x of slot `i`, in the node's centred space. */
76
- private slotCenter(index: number): number {
77
- const n = this.categories.length
78
- if (n <= 0) return 0
79
- const width = this.layoutBounds.width
80
- return -width / 2 + (width / n) * (index + 0.5)
81
- }
82
-
83
- constructor(props?: NodeConfig<CategoryAxis, CategoryAxisProps>) {
84
- super(props)
85
-
86
- const tickPad = insetsOps.resolve(this.axis.tickPadding).top
87
- const titlePad = insetsOps.resolve(this.axis.titlePadding).top
88
-
89
- // Each caption sits in a hug box carrying the spotlight dim, so the
90
- // entrance can animate the text's own opacity without the two fighting over
91
- // one value — opacity multiplies down the tree, which is exactly the
92
- // composition wanted here.
93
- const slots = this.categories.map((caption, i) => {
94
- const text = new Text({
95
- ...textOps.styleProps(this.axis.labelStyle),
96
- text: caption,
97
- })
98
- this.captionNodes.push(text)
99
- return new Rect({
100
- width: "hug",
101
- height: "hug",
102
- flow: "freeform",
103
- opacity: () => clampUnit(this.dims[i] ?? 1),
104
- x: () => this.slotCenter(i),
105
- children: [text],
106
- })
107
- })
108
-
109
- const children: Node2D[] = [
110
- new Rect({
111
- width: "fill",
112
- height: "hug",
113
- padding: { top: tickPad },
114
- children: slots,
115
- }),
116
- ]
117
- if (this.label !== "") {
118
- this.labelNode = new Text({
119
- ...textOps.styleProps(this.axis.titleStyle),
120
- text: this.label,
121
- })
122
- children.push(this.labelNode)
123
- }
124
-
125
- this.add(new Rect({ flow: "vertical", gap: titlePad, height: "hug", width: "fill", children }))
126
- }
127
-
128
- /**
129
- * Primes every caption (and the title) at its pre-entrance state: lifted above
130
- * its slot and transparent. Idempotent, so the chart can arm its whole
131
- * composition before the first frame is drawn.
132
- */
133
- arm(slide = SLIDE): void {
134
- for (const caption of this.captionNodes) caption.set({ opacity: 0, y: slide })
135
- this.labelNode?.set({ opacity: 0, y: slide })
136
- }
137
-
138
- /**
139
- * Slide + fade every caption in from above, staggered left-to-right, then
140
- * bring the title down behind them.
141
- *
142
- * `args.duration` is the **whole** entrance — the stagger and the title are
143
- * carved out of it rather than added on top. See {@link enterBudget}.
144
- *
145
- * `args.data.stagger` is seconds between successive captions (derived when
146
- * omitted); `args.data.slide` is the starting upward offset in px (node space
147
- * is y-up, so a positive start slides *down* into place).
148
- */
149
- @command()
150
- enter(args: CommandArgs<{ stagger?: number; slide?: number }> & { duration: number }): Seekable {
151
- const { stagger, slide = SLIDE } = args.data ?? {}
152
- const easing = args.easing ?? easeOut()
153
- const nodes = this.captionNodes
154
- if (nodes.length === 0) return hold(args.duration)
155
- const { step, per, caption } = enterBudget(
156
- args.duration,
157
- nodes.length,
158
- this.labelNode !== null,
159
- stagger
160
- )
161
-
162
- this.arm(slide)
163
-
164
- // The stagger was `wait(i * step)` inside a generator, so its offsets only
165
- // existed while it ran. They were always `i * step`; placed, the same
166
- // entrance can be asked what it looks like partway through.
167
- const lastCaptionEnds = (nodes.length - 1) * step + per
168
- return compose([
169
- ...nodes.map((node, i) =>
170
- at(i * step, node.to({ data: { opacity: 1, y: 0 }, duration: per, easing }))
171
- ),
172
- ...(this.labelNode
173
- ? [at(lastCaptionEnds, this.labelNode.to({ data: { opacity: 1, y: 0 }, duration: caption, easing }))]
174
- : []),
175
- ])
176
- }
177
- }
1
+ import {
2
+ command,
3
+ Node2D,
4
+ Rect,
5
+ Text,
6
+ easeOut,
7
+ property,
8
+ insetsOps,
9
+ textOps,
10
+ type CommandArgs,
11
+ type NodeConfig,
12
+ type Node2DProps,
13
+ } from "@motionscript/sdk"
14
+
15
+ import {
16
+ DEFAULT_X_AXIS,
17
+ clampUnit,
18
+ enterBudget,
19
+ type AxisConfig,
20
+ } from "../kit/shared"
21
+ import { at, compose, hold, type Seekable } from "@motionscript/sdk/component"
22
+
23
+ export interface CategoryAxisProps extends Node2DProps {
24
+ /** One caption per category slot, left to right — the chart's rows, in order. */
25
+ categories: string[]
26
+ /** Caption/title text style and gutters — see {@link AxisConfig}. */
27
+ axis: AxisConfig
28
+ /**
29
+ * Caption drawn centred in the gutter beneath the category row. Empty string
30
+ * (the default) hides it.
31
+ */
32
+ label: string
33
+ /**
34
+ * Per-category opacity multiplier — how the chart's category spotlight reaches
35
+ * the captions, so the name under a dimmed column dims with it. Missing
36
+ * entries read as 1.
37
+ */
38
+ dims: number[]
39
+ }
40
+
41
+ /** How far a caption starts above its slot when it enters, in px. */
42
+ const SLIDE = 24
43
+
44
+ /**
45
+ * The **categorical** horizontal axis of a {@link BarChart}: a row of
46
+ * {@link Text} captions, one per row of the data, each centred on its slot
47
+ * beneath the baseline.
48
+ *
49
+ * Not a variant of the line chart's `HorizontalAxis` and deliberately not
50
+ * folded into it: that axis places markers by *value* across a numeric range,
51
+ * this one divides its width into equal slots and centres a name in each. The
52
+ * two look alike on screen and share nothing underneath — one reads a scale,
53
+ * the other counts rows.
54
+ *
55
+ * The slot arithmetic is the same as the plot's, because this node is laid out
56
+ * to exactly the plot's width (the chart reserves the axis gutter with an
57
+ * invisible twin of the value axis, as the line chart does), so a caption lands
58
+ * under the columns it names without either side being told about the other.
59
+ *
60
+ * {@link enter} staggers the captions in left-to-right, each lifted a little
61
+ * above its slot and transparent; the resting state is the landed one, so a
62
+ * chart that is never told to enter still reads as finished.
63
+ */
64
+ export class CategoryAxis extends Node2D<CategoryAxisProps> {
65
+ @property({ default: () => [] as string[] }) declare categories: string[]
66
+ @property({ default: DEFAULT_X_AXIS }) declare axis: AxisConfig
67
+ @property({ default: "" }) declare label: string
68
+ @property({ default: () => [] as number[] }) declare dims: number[]
69
+
70
+ /** One caption per category, ordered left → right, for the staggered enter. */
71
+ private captionNodes: Text[] = []
72
+ /** The axis title, or null when `label` is empty. */
73
+ private labelNode: Text | null = null
74
+
75
+ /** Centre x of slot `i`, in the node's centred space. */
76
+ private slotCenter(index: number): number {
77
+ const n = this.categories.length
78
+ if (n <= 0) return 0
79
+ const width = this.layoutBounds.width
80
+ return -width / 2 + (width / n) * (index + 0.5)
81
+ }
82
+
83
+ constructor(props?: NodeConfig<CategoryAxis, CategoryAxisProps>) {
84
+ super(props)
85
+
86
+ const tickPad = insetsOps.resolve(this.axis.tickPadding).top
87
+ const titlePad = insetsOps.resolve(this.axis.titlePadding).top
88
+
89
+ // Each caption sits in a hug box carrying the spotlight dim, so the
90
+ // entrance can animate the text's own opacity without the two fighting over
91
+ // one value — opacity multiplies down the tree, which is exactly the
92
+ // composition wanted here.
93
+ const slots = this.categories.map((caption, i) => {
94
+ const text = new Text({
95
+ ...textOps.styleProps(this.axis.labelStyle),
96
+ text: caption,
97
+ })
98
+ this.captionNodes.push(text)
99
+ return new Rect({
100
+ width: "hug",
101
+ height: "hug",
102
+ flow: "freeform",
103
+ opacity: () => clampUnit(this.dims[i] ?? 1),
104
+ x: () => this.slotCenter(i),
105
+ children: [text],
106
+ })
107
+ })
108
+
109
+ const children: Node2D[] = [
110
+ new Rect({
111
+ width: "fill",
112
+ height: "hug",
113
+ padding: { top: tickPad },
114
+ children: slots,
115
+ }),
116
+ ]
117
+ if (this.label !== "") {
118
+ this.labelNode = new Text({
119
+ ...textOps.styleProps(this.axis.titleStyle),
120
+ text: this.label,
121
+ })
122
+ children.push(this.labelNode)
123
+ }
124
+
125
+ this.add(new Rect({ flow: "vertical", gap: titlePad, height: "hug", width: "fill", children }))
126
+ }
127
+
128
+ /**
129
+ * Primes every caption (and the title) at its pre-entrance state: lifted above
130
+ * its slot and transparent. Idempotent, so the chart can arm its whole
131
+ * composition before the first frame is drawn.
132
+ */
133
+ arm(slide = SLIDE): void {
134
+ for (const caption of this.captionNodes) caption.set({ opacity: 0, y: slide })
135
+ this.labelNode?.set({ opacity: 0, y: slide })
136
+ }
137
+
138
+ /**
139
+ * Slide + fade every caption in from above, staggered left-to-right, then
140
+ * bring the title down behind them.
141
+ *
142
+ * `args.duration` is the **whole** entrance — the stagger and the title are
143
+ * carved out of it rather than added on top. See {@link enterBudget}.
144
+ *
145
+ * `args.data.stagger` is seconds between successive captions (derived when
146
+ * omitted); `args.data.slide` is the starting upward offset in px (node space
147
+ * is y-up, so a positive start slides *down* into place).
148
+ */
149
+ @command()
150
+ enter(args: CommandArgs<{ stagger?: number; slide?: number }> & { duration: number }): Seekable {
151
+ const { stagger, slide = SLIDE } = args.data ?? {}
152
+ const easing = args.easing ?? easeOut()
153
+ const nodes = this.captionNodes
154
+ if (nodes.length === 0) return hold(args.duration)
155
+ const { step, per, caption } = enterBudget(
156
+ args.duration,
157
+ nodes.length,
158
+ this.labelNode !== null,
159
+ stagger
160
+ )
161
+
162
+ this.arm(slide)
163
+
164
+ // The stagger was `wait(i * step)` inside a generator, so its offsets only
165
+ // existed while it ran. They were always `i * step`; placed, the same
166
+ // entrance can be asked what it looks like partway through.
167
+ const lastCaptionEnds = (nodes.length - 1) * step + per
168
+ return compose([
169
+ ...nodes.map((node, i) =>
170
+ at(i * step, node.to({ data: { opacity: 1, y: 0 }, duration: per, easing }))
171
+ ),
172
+ ...(this.labelNode
173
+ ? [at(lastCaptionEnds, this.labelNode.to({ data: { opacity: 1, y: 0 }, duration: caption, easing }))]
174
+ : []),
175
+ ])
176
+ }
177
+ }