@motionscript/charts 0.0.0-stage → 0.1.0-alpha.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 (137) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +201 -0
  3. package/dist/bar-chart/bar-chart.d.ts +286 -0
  4. package/dist/bar-chart/bar-chart.d.ts.map +1 -0
  5. package/dist/bar-chart/bar-chart.js +610 -0
  6. package/dist/bar-chart/bar-chart.js.map +1 -0
  7. package/dist/bar-chart/category-axis.d.ts +77 -0
  8. package/dist/bar-chart/category-axis.d.ts.map +1 -0
  9. package/dist/bar-chart/category-axis.js +141 -0
  10. package/dist/bar-chart/category-axis.js.map +1 -0
  11. package/dist/bar-chart/chart-bars.d.ts +149 -0
  12. package/dist/bar-chart/chart-bars.d.ts.map +1 -0
  13. package/dist/bar-chart/chart-bars.js +236 -0
  14. package/dist/bar-chart/chart-bars.js.map +1 -0
  15. package/dist/bar-chart/index.d.ts +16 -0
  16. package/dist/bar-chart/index.d.ts.map +1 -0
  17. package/dist/bar-chart/index.js +12 -0
  18. package/dist/bar-chart/index.js.map +1 -0
  19. package/dist/bar-chart/shared.d.ts +146 -0
  20. package/dist/bar-chart/shared.d.ts.map +1 -0
  21. package/dist/bar-chart/shared.js +182 -0
  22. package/dist/bar-chart/shared.js.map +1 -0
  23. package/dist/browser/chunks/chunk-ZMBOBHYL.js +2 -0
  24. package/dist/browser/chunks/chunk-ZMBOBHYL.js.map +7 -0
  25. package/dist/browser/index.js +2 -0
  26. package/dist/browser/index.js.map +7 -0
  27. package/dist/browser/kit.js +2 -0
  28. package/dist/browser/kit.js.map +7 -0
  29. package/dist/browser/manifest.json +12 -0
  30. package/dist/engine.d.ts +27 -0
  31. package/dist/engine.d.ts.map +1 -0
  32. package/dist/engine.js +27 -0
  33. package/dist/engine.js.map +1 -0
  34. package/dist/index.d.ts +6 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +6 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/kit/chart-border.d.ts +28 -0
  39. package/dist/kit/chart-border.d.ts.map +1 -0
  40. package/dist/kit/chart-border.js +45 -0
  41. package/dist/kit/chart-border.js.map +1 -0
  42. package/dist/kit/chart-canvas.d.ts +56 -0
  43. package/dist/kit/chart-canvas.d.ts.map +1 -0
  44. package/dist/kit/chart-canvas.js +125 -0
  45. package/dist/kit/chart-canvas.js.map +1 -0
  46. package/dist/kit/chart-legend.d.ts +126 -0
  47. package/dist/kit/chart-legend.d.ts.map +1 -0
  48. package/dist/kit/chart-legend.js +212 -0
  49. package/dist/kit/chart-legend.js.map +1 -0
  50. package/dist/kit/shared.d.ts +251 -0
  51. package/dist/kit/shared.d.ts.map +1 -0
  52. package/dist/kit/shared.js +401 -0
  53. package/dist/kit/shared.js.map +1 -0
  54. package/dist/kit/vertical-axis.d.ts +99 -0
  55. package/dist/kit/vertical-axis.d.ts.map +1 -0
  56. package/dist/kit/vertical-axis.js +176 -0
  57. package/dist/kit/vertical-axis.js.map +1 -0
  58. package/dist/line-chart/chart-line.d.ts +91 -0
  59. package/dist/line-chart/chart-line.d.ts.map +1 -0
  60. package/dist/line-chart/chart-line.js +156 -0
  61. package/dist/line-chart/chart-line.js.map +1 -0
  62. package/dist/line-chart/chart-region.d.ts +47 -0
  63. package/dist/line-chart/chart-region.d.ts.map +1 -0
  64. package/dist/line-chart/chart-region.js +101 -0
  65. package/dist/line-chart/chart-region.js.map +1 -0
  66. package/dist/line-chart/horizontal-axis.d.ts +85 -0
  67. package/dist/line-chart/horizontal-axis.d.ts.map +1 -0
  68. package/dist/line-chart/horizontal-axis.js +158 -0
  69. package/dist/line-chart/horizontal-axis.js.map +1 -0
  70. package/dist/line-chart/index.d.ts +22 -0
  71. package/dist/line-chart/index.d.ts.map +1 -0
  72. package/dist/line-chart/index.js +19 -0
  73. package/dist/line-chart/index.js.map +1 -0
  74. package/dist/line-chart/line-chart.d.ts +347 -0
  75. package/dist/line-chart/line-chart.d.ts.map +1 -0
  76. package/dist/line-chart/line-chart.js +701 -0
  77. package/dist/line-chart/line-chart.js.map +1 -0
  78. package/dist/line-chart/shared.d.ts +171 -0
  79. package/dist/line-chart/shared.d.ts.map +1 -0
  80. package/dist/line-chart/shared.js +270 -0
  81. package/dist/line-chart/shared.js.map +1 -0
  82. package/dist/line-chart/x-scale.d.ts +84 -0
  83. package/dist/line-chart/x-scale.d.ts.map +1 -0
  84. package/dist/line-chart/x-scale.js +302 -0
  85. package/dist/line-chart/x-scale.js.map +1 -0
  86. package/dist/nodes.d.ts +20 -0
  87. package/dist/nodes.d.ts.map +1 -0
  88. package/dist/nodes.js +20 -0
  89. package/dist/nodes.js.map +1 -0
  90. package/dist/pie-chart/index.d.ts +18 -0
  91. package/dist/pie-chart/index.d.ts.map +1 -0
  92. package/dist/pie-chart/index.js +14 -0
  93. package/dist/pie-chart/index.js.map +1 -0
  94. package/dist/pie-chart/pie-chart.d.ts +170 -0
  95. package/dist/pie-chart/pie-chart.d.ts.map +1 -0
  96. package/dist/pie-chart/pie-chart.js +291 -0
  97. package/dist/pie-chart/pie-chart.js.map +1 -0
  98. package/dist/pie-chart/pie-legend.d.ts +60 -0
  99. package/dist/pie-chart/pie-legend.d.ts.map +1 -0
  100. package/dist/pie-chart/pie-legend.js +131 -0
  101. package/dist/pie-chart/pie-legend.js.map +1 -0
  102. package/dist/pie-chart/pie-ring.d.ts +266 -0
  103. package/dist/pie-chart/pie-ring.d.ts.map +1 -0
  104. package/dist/pie-chart/pie-ring.js +708 -0
  105. package/dist/pie-chart/pie-ring.js.map +1 -0
  106. package/dist/pie-chart/shared.d.ts +210 -0
  107. package/dist/pie-chart/shared.d.ts.map +1 -0
  108. package/dist/pie-chart/shared.js +240 -0
  109. package/dist/pie-chart/shared.js.map +1 -0
  110. package/package.json +69 -3
  111. package/registry.json +23 -0
  112. package/src/bar-chart/bar-chart.ts +804 -0
  113. package/src/bar-chart/category-axis.ts +177 -0
  114. package/src/bar-chart/chart-bars.ts +310 -0
  115. package/src/bar-chart/index.ts +31 -0
  116. package/src/bar-chart/shared.ts +354 -0
  117. package/src/engine.ts +26 -0
  118. package/src/index.ts +5 -0
  119. package/src/kit/chart-border.ts +61 -0
  120. package/src/kit/chart-canvas.ts +157 -0
  121. package/src/kit/chart-legend.ts +250 -0
  122. package/src/kit/shared.ts +675 -0
  123. package/src/kit/vertical-axis.ts +224 -0
  124. package/src/line-chart/chart-line.ts +208 -0
  125. package/src/line-chart/chart-region.ts +135 -0
  126. package/src/line-chart/horizontal-axis.ts +202 -0
  127. package/src/line-chart/index.ts +39 -0
  128. package/src/line-chart/line-chart.ts +894 -0
  129. package/src/line-chart/shared.ts +459 -0
  130. package/src/line-chart/x-scale.ts +367 -0
  131. package/src/nodes.ts +20 -0
  132. package/src/pie-chart/index.ts +37 -0
  133. package/src/pie-chart/pie-chart.ts +374 -0
  134. package/src/pie-chart/pie-legend.ts +159 -0
  135. package/src/pie-chart/pie-ring.ts +903 -0
  136. package/src/pie-chart/shared.ts +490 -0
  137. package/README.md +0 -4
@@ -0,0 +1,610 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var BarChart_1;
8
+ import { command, node, Node2D, Rect, easeOut, property, } from "@motionscript/core";
9
+ import { ChartBorder } from "../kit/chart-border.js";
10
+ import { ChartCanvas } from "../kit/chart-canvas.js";
11
+ import { ChartLegend } from "../kit/chart-legend.js";
12
+ import { VerticalAxis } from "../kit/vertical-axis.js";
13
+ import { clampUnit, formatMarker, lerpAxisDomain, markerValues, num, resolveAxisDomain, } from "../kit/shared.js";
14
+ import { formatNumber } from "@motionscript/core/component";
15
+ import { drive, hold, sequence as sequenceOf, together, } from "@motionscript/core/component";
16
+ import { CategoryAxis } from "./category-axis.js";
17
+ import { ChartBars } from "./chart-bars.js";
18
+ import { DEFAULT_BAR_CHART_THEME, barCategories, barSpans, lerpBarChartTheme, resolveBarChartTheme, resolveBarFill, resolveBarScale, stackCapIndex, } from "./shared.js";
19
+ /**
20
+ * Layout constants for the composition, all in px — the line chart's, so the two
21
+ * sit at the same size in the same box and a scene can swap one for the other
22
+ * without re-tuning its layout.
23
+ */
24
+ /** Left gutter reserved for the value axis markers. */
25
+ const Y_AXIS_GUTTER = 64;
26
+ /** Bottom gutter (within the chart area) reserved for the category captions. */
27
+ const X_AXIS_GUTTER = 40;
28
+ /** Height reserved below the chart area for the legend row. */
29
+ const LEGEND_GUTTER = 56;
30
+ /** Extra left/bottom room when an axis caption is shown. */
31
+ const Y_LABEL_GUTTER = 34;
32
+ const X_LABEL_GUTTER = 30;
33
+ /** Headroom above the plot, so a column reaching the top marker isn't flush to it. */
34
+ const TOP_HEADROOM = 24;
35
+ /** Right gutter so the last slot isn't flush with the box edge. */
36
+ const RIGHT_GUTTER = 20;
37
+ /** How far the legend starts below its slot when it enters, in px. */
38
+ const LEGEND_SLIDE = 20;
39
+ const AXES_SHARE = 0.35;
40
+ const BARS_SHARE = 0.5;
41
+ const LEGEND_SHARE = 0.15;
42
+ /**
43
+ * A grouped or stacked bar chart, composed from independent parts rather than
44
+ * one monolithic draw — the sibling of the {@link LineChart} family, built the
45
+ * same way and out of several of the same pieces:
46
+ *
47
+ * - `ChartCanvas` — the plot panel with a horizontal grid, under everything.
48
+ * - `VerticalAxis` — the numeric value markers, staggering in bottom-to-top.
49
+ * - {@link CategoryAxis} — the row of names beneath the baseline, one per row of
50
+ * the data, staggering in left-to-right.
51
+ * - {@link ChartBars} (one per series) — that series' column in every category,
52
+ * plus the figures printed on them.
53
+ * - `ChartLegend` — a row of entries below the chart, one per series.
54
+ *
55
+ * The parts are laid out from this node's `layoutBounds` exactly as the line
56
+ * chart's are: a left gutter for the value axis, a bottom gutter for the
57
+ * captions, the plot filling what's left, and the legend row beneath the whole
58
+ * thing. Every child's position and size is bound reactively, so the chart
59
+ * re-flows if its box changes, and the bars read the same resolved scale the
60
+ * axis does, so a column's end lands exactly on its marker.
61
+ *
62
+ * **Resting state is the finished chart** — the same bargain the line chart
63
+ * makes: the studio paints a node's Initial state on a paused canvas, so a chart
64
+ * that only appeared once a command had run would be edited blind. {@link enter}
65
+ * arms every part back to its pre-entrance state before animating.
66
+ *
67
+ * **Zero is always on the scale** (see `barValueExtent`), and the columns
68
+ * grow about it rather than about the plot floor, so a negative value draws
69
+ * downward out of the same `growth` signal that draws a positive one up.
70
+ *
71
+ * Past the entrance, two things move the chart:
72
+ * {@link showSeries}/{@link hideSeries}/{@link spotlightSeries} pick out a
73
+ * *series* — and hiding one **reflows the group**, the remaining columns
74
+ * widening into the space as it goes, rather than leaving a hole where it was —
75
+ * while {@link spotlightCategory}/{@link clearCategory} pick out a *column of
76
+ * the table*, dimming every other category and its caption with it.
77
+ */
78
+ let BarChart = class BarChart extends Node2D {
79
+ static { BarChart_1 = this; }
80
+ // ---- Part handles (built in the constructor) --------------------------
81
+ vAxis;
82
+ cAxis;
83
+ legend = null;
84
+ barSets = [];
85
+ /**
86
+ * `this.theme` read back through its *resolved* shape — the same
87
+ * loose-declared/resolved-stored idiom as a shape's `fill`/`stroke`, and as
88
+ * the line chart's own theme.
89
+ */
90
+ get resolvedTheme() {
91
+ return this.theme;
92
+ }
93
+ // ---- Derived scale ----------------------------------------------------
94
+ /** Fallback marker count when the value axis states no ticks. */
95
+ static DEFAULT_MARKERS = 5;
96
+ get valueScale() {
97
+ const divisions = (this.valueAxis.ticks?.length ?? BarChart_1.DEFAULT_MARKERS) - 1;
98
+ return resolveBarScale(this.data, this.series, this.stacked, this.valueAxis.range, divisions);
99
+ }
100
+ /**
101
+ * A value written down as this chart's value axis says — the axis ticks and
102
+ * the figures printed on the columns both read through here, so the two
103
+ * cannot disagree about what a number looks like.
104
+ */
105
+ valueLabel(value) {
106
+ const pattern = this.valueAxis.format;
107
+ return pattern === undefined
108
+ ? formatMarker(value)
109
+ : formatNumber(value, pattern);
110
+ }
111
+ /** The value axis ticks: the explicit ones, else five across the scale. */
112
+ get resolvedMarkers() {
113
+ return (this.valueAxis.ticks ??
114
+ markerValues(this.valueScale, BarChart_1.DEFAULT_MARKERS - 1));
115
+ }
116
+ // ---- Plot geometry, all derived from layoutBounds -----------------------
117
+ get hasValueLabel() {
118
+ return this.valueAxis.label !== "";
119
+ }
120
+ get hasCategoryLabel() {
121
+ return this.categoryAxis.label !== "";
122
+ }
123
+ get leftInset() {
124
+ return Y_AXIS_GUTTER + (this.hasValueLabel ? Y_LABEL_GUTTER : 0);
125
+ }
126
+ get bottomInset() {
127
+ return X_AXIS_GUTTER + (this.hasCategoryLabel ? X_LABEL_GUTTER : 0);
128
+ }
129
+ /** The plot rectangle in node space: `{ left, right, top, bottom }` (y-up). */
130
+ plotBox() {
131
+ const W = this.layoutBounds.width;
132
+ const H = this.layoutBounds.height;
133
+ const areaTop = H / 2;
134
+ const areaBottom = -H / 2 + (this.legend ? LEGEND_GUTTER : 0);
135
+ const left = -W / 2 + this.leftInset;
136
+ const right = W / 2 - RIGHT_GUTTER;
137
+ const top = areaTop - TOP_HEADROOM;
138
+ const bottom = areaBottom + this.bottomInset;
139
+ return { left, right, top, bottom };
140
+ }
141
+ /** Width and height of the plot, in px. */
142
+ plotSize() {
143
+ const { left, right, top, bottom } = this.plotBox();
144
+ return { w: right - left, h: top - bottom };
145
+ }
146
+ /** Map a value onto the plot's **centred** pixel space (origin at its centre, y-up). */
147
+ valueToY(value) {
148
+ const [min, max] = this.valueScale;
149
+ const { h } = this.plotSize();
150
+ if (max <= min)
151
+ return -h / 2;
152
+ return ((value - min) / (max - min) - 0.5) * h;
153
+ }
154
+ /**
155
+ * Pixel y of the line every column grows from.
156
+ *
157
+ * Zero, clamped onto the scale — so an author who *did* crop the axis to
158
+ * `[40, 100]` gets columns rising from the plot floor rather than from a
159
+ * baseline off the bottom of the panel.
160
+ */
161
+ get baselinePx() {
162
+ const [min, max] = this.valueScale;
163
+ return this.valueToY(Math.min(Math.max(0, min), max));
164
+ }
165
+ // ---- Slot geometry ----------------------------------------------------
166
+ /** Width of one category's slot across the plot. */
167
+ get slotWidth() {
168
+ const n = this.data.length;
169
+ return n > 0 ? this.plotSize().w / n : 0;
170
+ }
171
+ /** Centre x of category `i`'s slot, in the plot's centred space. */
172
+ slotCenter(index) {
173
+ return -this.plotSize().w / 2 + this.slotWidth * (index + 0.5);
174
+ }
175
+ /** The part of a slot the columns fill — the rest is the channel between categories. */
176
+ get slotInner() {
177
+ return this.slotWidth * clampUnit(this.resolvedTheme.bar.slotFill);
178
+ }
179
+ /** Series `i`'s live share of the slot; 0 once it has been hidden. */
180
+ weightAt(index) {
181
+ return Math.max(0, this.weights[index] ?? 1);
182
+ }
183
+ /** Total live share across the series — the slot's inner width divides by this. */
184
+ get totalWeight() {
185
+ let sum = 0;
186
+ for (let i = 0; i < this.series.length; i++)
187
+ sum += this.weightAt(i);
188
+ return sum > 0 ? sum : 1;
189
+ }
190
+ /** Live share of every series *before* `index`, for the grouped offset. */
191
+ weightBefore(index) {
192
+ let sum = 0;
193
+ for (let i = 0; i < index; i++)
194
+ sum += this.weightAt(i);
195
+ return sum;
196
+ }
197
+ /**
198
+ * Series `index`'s columns, mapped into the plot's centred pixel space.
199
+ *
200
+ * Both modes come out of one walk over the rows: a grouped series divides the
201
+ * slot with its siblings and every column runs from zero, a stacked one takes
202
+ * the whole slot and each column runs from the running total below it. The
203
+ * live {@link weights} scale the grouped widths and the stacked contributions
204
+ * alike, which is what lets `hideSeries` reflow rather than punch a hole.
205
+ */
206
+ seriesBars(index) {
207
+ const series = this.series[index];
208
+ if (!series)
209
+ return [];
210
+ const stacked = this.stacked;
211
+ const inner = this.slotInner;
212
+ const gap = this.resolvedTheme.bar.groupGap;
213
+ const unit = inner / this.totalWeight;
214
+ const weight = this.weightAt(index);
215
+ return this.data.map((row, rowIndex) => {
216
+ // The stack is built from the *live* contributions, so a series fading out
217
+ // lowers the segments above it as it goes instead of dropping them by a
218
+ // step when it finally switches off. A grouped row needs no such scaling —
219
+ // there its weight moves the column's width, not its length.
220
+ const spans = barSpans(stacked ? this.weightedRow(row) : row, this.series, stacked);
221
+ const span = spans[index] ?? { from: 0, to: 0, value: 0 };
222
+ const cap = stacked ? stackCapIndex(row, this.series) : index;
223
+ const slotLeft = this.slotCenter(rowIndex) - inner / 2;
224
+ const x = stacked
225
+ ? this.slotCenter(rowIndex)
226
+ : slotLeft + unit * this.weightBefore(index) + (unit * weight) / 2;
227
+ const width = stacked
228
+ ? inner
229
+ : Math.max(0, unit * weight - (this.series.length > 1 ? gap : 0));
230
+ return {
231
+ x,
232
+ width,
233
+ from: this.valueToY(span.from),
234
+ to: this.valueToY(span.to),
235
+ round: cap === index,
236
+ // The figure printed is the row's own value, not the weighted one — a
237
+ // series on its way out should not be seen counting down.
238
+ value: num(row, series.field),
239
+ };
240
+ });
241
+ }
242
+ /**
243
+ * `row` with each series' value scaled by that series' live {@link weights}
244
+ * entry — what the stacked spans are measured from.
245
+ */
246
+ weightedRow(row) {
247
+ const scaled = { ...row };
248
+ this.series.forEach((s, i) => {
249
+ const value = row[s.field];
250
+ if (typeof value === "number")
251
+ scaled[s.field] = value * this.weightAt(i);
252
+ });
253
+ return scaled;
254
+ }
255
+ /**
256
+ * How far each category is dimmed by the spotlight, one entry per row —
257
+ * 1 while nothing is spotlit, falling to the theme's `dimOpacity` for every
258
+ * category but the chosen one as the reveal climbs.
259
+ */
260
+ categoryDims() {
261
+ const focus = clampUnit(this.spotlightReveal);
262
+ const dim = this.resolvedTheme.spotlight.dimOpacity;
263
+ const chosen = this.spotlight;
264
+ return this.data.map((_, i) => focus <= 0 || chosen === i ? 1 : 1 - (1 - dim) * focus);
265
+ }
266
+ // ---- Build ------------------------------------------------------------
267
+ constructor(props) {
268
+ super(props);
269
+ // The whole chart is composed from plain props/`data` — no context needed,
270
+ // so the parts are built here (super() applied the props above). Everything
271
+ // a *leaf* reads live in `renderSelf` — each series' geometry, paint and
272
+ // dim, the panel's fill, the border's stroke — is passed as a closure rather
273
+ // than a snapshot, so `chart.to({ theme })` and
274
+ // `chart.to({ valueAxis: { range } })` genuinely animate the drawn chart.
275
+ const enabled = this.series.map((s) => s.enabled ?? true);
276
+ this.weights = enabled.map((on) => (on ? 1 : 0));
277
+ const showLegend = props?.showLegend !== false;
278
+ const showValues = props?.showValues === true;
279
+ this.vAxis = new VerticalAxis({
280
+ range: () => this.valueScale,
281
+ markers: () => this.resolvedMarkers,
282
+ axis: () => this.resolvedTheme.yAxis,
283
+ label: this.valueAxis.label ?? "",
284
+ width: "hug",
285
+ height: "fill",
286
+ });
287
+ this.vAxis.labelFor = (value) => this.valueLabel(value);
288
+ this.cAxis = new CategoryAxis({
289
+ categories: barCategories(this.data, this.categoryField),
290
+ axis: () => this.resolvedTheme.xAxis,
291
+ label: this.categoryAxis.label ?? "",
292
+ dims: () => this.categoryDims(),
293
+ width: "fill",
294
+ height: "hug",
295
+ });
296
+ this.barSets = this.series.map((s, i) => new ChartBars({
297
+ bars: () => this.seriesBars(i),
298
+ baseline: () => this.baselinePx,
299
+ fill: () => resolveBarFill(this.resolvedTheme.seriesFills, s, i),
300
+ barStroke: () => this.resolvedTheme.bar.stroke,
301
+ barShadow: () => this.resolvedTheme.bar.shadow,
302
+ cornerRadius: () => this.resolvedTheme.bar.cornerRadius,
303
+ growth: () => this.growth,
304
+ dims: () => this.categoryDims(),
305
+ showValues,
306
+ valueStyle: this.resolvedTheme.valueStyle,
307
+ enabled: enabled[i],
308
+ }));
309
+ // The figures on the columns are the same numbers the value axis ticks, so
310
+ // they read through the same format.
311
+ for (const set of this.barSets)
312
+ set.labelFor = (value) => this.valueLabel(value);
313
+ if (showLegend) {
314
+ this.legend = new ChartLegend({
315
+ series: this.series,
316
+ swatches: () => this.series.map((s, i) => resolveBarFill(this.resolvedTheme.seriesFills, s, i)),
317
+ legend: () => this.resolvedTheme.legend,
318
+ });
319
+ }
320
+ const plot = new Rect({
321
+ clip: true,
322
+ children: [
323
+ new ChartCanvas({
324
+ fill: () => this.resolvedTheme.plotArea.fill,
325
+ shadow: () => this.resolvedTheme.plotArea.shadow,
326
+ xGridStroke: () => this.resolvedTheme.xAxis.lineStyle,
327
+ yGridStroke: () => this.resolvedTheme.yAxis.lineStyle,
328
+ // No vertical rules: the horizontal axis is categorical, so there is
329
+ // no numeric position along it for a grid line to mark. The channel
330
+ // between slots does that job, and better.
331
+ xMarkers: [],
332
+ yMarkers: () => this.resolvedMarkers,
333
+ xExtent: () => [0, 1],
334
+ yExtent: () => this.valueScale,
335
+ }),
336
+ ...this.barSets,
337
+ new ChartBorder({
338
+ stroke: () => this.resolvedTheme.plotArea.stroke,
339
+ }),
340
+ ],
341
+ });
342
+ // Invisible twin of the value axis, reserving the same left gutter under the
343
+ // plot so the category row starts at the plot's left edge rather than the
344
+ // chart's — and so a caption lands under the columns it names.
345
+ const gutterSpacer = new VerticalAxis({
346
+ opacity: 0,
347
+ range: () => this.valueScale,
348
+ markers: () => this.resolvedMarkers,
349
+ axis: () => this.resolvedTheme.yAxis,
350
+ label: this.valueAxis.label ?? "",
351
+ width: "hug",
352
+ height: 0,
353
+ });
354
+ const below = [this.cAxis];
355
+ if (this.legend)
356
+ below.push(this.legend);
357
+ this.add(new Rect({ flow: "vertical",
358
+ width: "fill",
359
+ height: "fill",
360
+ children: [
361
+ new Rect({ flow: "horizontal",
362
+ height: "fill",
363
+ width: "fill",
364
+ children: [this.vAxis, plot],
365
+ }),
366
+ new Rect({ flow: "horizontal",
367
+ width: "fill",
368
+ children: [
369
+ gutterSpacer,
370
+ new Rect({ flow: "vertical", width: "fill", gap: 20, children: below }),
371
+ ],
372
+ }),
373
+ ],
374
+ }));
375
+ }
376
+ // ---- Orchestrated entrance --------------------------------------------
377
+ /**
378
+ * Prime every part at its pre-entrance state — axis markers and captions
379
+ * shifted and transparent, columns collapsed onto the baseline, legend dropped
380
+ * and transparent.
381
+ *
382
+ * Called by {@link enter} before its first frame so nothing flashes ahead of
383
+ * its beat. Idempotent, and public so a scene can hide a chart ahead of time
384
+ * without starting the entrance.
385
+ */
386
+ arm() {
387
+ this.vAxis.arm();
388
+ this.cAxis.arm();
389
+ this.growth = 0;
390
+ this.legend?.set({ opacity: 0, y: -LEGEND_SLIDE });
391
+ }
392
+ /**
393
+ * Animate the whole chart in: the two axes stagger their markers and captions
394
+ * in, the columns rise from the baseline, and the legend fades up last.
395
+ *
396
+ * `duration` is the **whole** entrance. The phases are shares of it, not
397
+ * multiples — a command that declares 1.4 seconds has to take 1.4 seconds,
398
+ * because that is the slot the timeline lays out for it, the length its
399
+ * parallel siblings are timed against, and its contribution to the scene's own
400
+ * duration.
401
+ */
402
+ enter(args) {
403
+ const { duration } = args;
404
+ const easing = args.easing ?? easeOut();
405
+ this.arm();
406
+ return sequenceOf(together(this.vAxis.enter({ data: { slide: 24 }, duration: duration * AXES_SHARE, easing }), this.cAxis.enter({ data: { slide: 24 }, duration: duration * AXES_SHARE, easing })), this.riseBars(duration * BARS_SHARE, easing), ...(this.legend
407
+ ? [this.legend.to({ data: { opacity: 1, y: 0 }, duration: duration * LEGEND_SHARE, easing })]
408
+ : []));
409
+ }
410
+ /** Tween {@link growth} `0 → 1`, raising every column about the baseline. */
411
+ riseBars(duration, easing) {
412
+ // No priming to 0 first: the value at `t` is a function of `t`, so a repeat
413
+ // call re-raises by definition rather than by having reset something.
414
+ return this.command((t) => ({ growth: t }), duration, easing);
415
+ }
416
+ /**
417
+ * Show series `index`: bring its columns back, the group reflowing to make
418
+ * room as they arrive, in lockstep with its legend entry growing in.
419
+ */
420
+ showSeries(args) {
421
+ const { seriesIndex = 0 } = args.data ?? {};
422
+ return this.toggleSeries(seriesIndex, 1, args.duration ?? 0.35, args.easing ?? easeOut());
423
+ }
424
+ /**
425
+ * Hide series `index`: its columns narrow away (or, stacked, sink out of the
426
+ * stack) while the rest of the group widens into the space, its legend entry
427
+ * shrinking beside them — the mirror of {@link showSeries}.
428
+ */
429
+ hideSeries(args) {
430
+ const { seriesIndex = 0 } = args.data ?? {};
431
+ return this.toggleSeries(seriesIndex, 0, args.duration ?? 0.35, args.easing ?? easeOut());
432
+ }
433
+ /**
434
+ * Move series `index`'s slot share to `target`, with its legend entry.
435
+ *
436
+ * One method for both directions, so the pair cannot drift apart. `enabled`
437
+ * rides in the same value as the weight rather than being set before or after,
438
+ * which is what makes arriving at a time backwards show what arriving forwards
439
+ * does.
440
+ */
441
+ toggleSeries(index, target, duration, easing) {
442
+ const bars = this.barSets[index];
443
+ if (!bars)
444
+ return hold(duration);
445
+ return together(this.tweenWeight(index, target, duration, easing), drive(duration, (t) => {
446
+ const share = t >= 1 ? target : Math.max(target, 1 - t);
447
+ bars.set({ enabled: target > 0 || share > 0 });
448
+ }), ...(this.legend
449
+ ? [
450
+ target > 0
451
+ ? this.legend.showSeries({ data: { index }, duration, easing })
452
+ : this.legend.hideSeries({ data: { index }, duration, easing }),
453
+ ]
454
+ : []));
455
+ }
456
+ /**
457
+ * Fade out every other series — its columns **and its legend entry** — to
458
+ * focus attention on `index`, which comes back to full. Nothing moves, so the
459
+ * group doesn't reflow.
460
+ */
461
+ spotlightSeries(args) {
462
+ const { seriesIndex = 0 } = args.data ?? {};
463
+ const duration = args.duration ?? 0.35;
464
+ const easing = args.easing ?? easeOut();
465
+ const dim = this.resolvedTheme.spotlight.dimOpacity;
466
+ return together(...this.barSets.map((bars, i) => bars.to({ data: { opacity: i === seriesIndex ? 1 : dim }, duration, easing })), ...(this.legend
467
+ ? [this.legend.spotlightSeries({ data: { index: seriesIndex }, duration, easing })]
468
+ : []));
469
+ }
470
+ /** Ease series `index`'s slot share toward `target`, reflowing the group. */
471
+ tweenWeight(index, target, duration, easing) {
472
+ // Read through `weightAt` rather than off the array, so a chart whose stored
473
+ // weights are short (or absent) still tweens from the 1 it is drawing at.
474
+ const from = this.series.map((_, i) => this.weightAt(i));
475
+ const start = from[index] ?? 1;
476
+ if (start === target)
477
+ return hold(duration);
478
+ return drive(duration, (t) => {
479
+ const next = [...from];
480
+ next[index] = start + (target - start) * easing(t);
481
+ this.weights = next;
482
+ });
483
+ }
484
+ // ---- Per-category ------------------------------------------------------
485
+ /**
486
+ * Spotlight one **category** — one column of the table rather than one series:
487
+ * every other category's bars and its caption fade to the theme's
488
+ * `dimOpacity`, and the chosen one stays full.
489
+ *
490
+ * The counterpart of the line chart's `spotlightRegion`, and the same shape of
491
+ * thing: it picks out a slice of the plot without changing what is drawn.
492
+ * Calling it while another category is already lit moves the focus directly —
493
+ * only the dimming changes, so there is nothing to retract first.
494
+ */
495
+ spotlightCategory(args) {
496
+ const { categoryIndex = 0 } = args.data ?? {};
497
+ const duration = args.duration ?? 0.4;
498
+ const easing = args.easing ?? easeOut();
499
+ if (categoryIndex < 0 || categoryIndex >= this.data.length)
500
+ return this.holdSpotlight(duration);
501
+ const from = this.spotlightReveal;
502
+ // Which category is lit is part of the value, set from the first frame: the
503
+ // focus moves directly, so unlike the line chart's region there is nothing to
504
+ // retract before it.
505
+ return this.command((t) => ({ spotlight: categoryIndex, spotlightReveal: from + (1 - from) * t }), duration, easing);
506
+ }
507
+ /**
508
+ * Drop the category spotlight, bringing every category back to full. A no-op
509
+ * when nothing is lit — but it still takes its slot on the timeline, for the
510
+ * reason `LineChart.clearRegion` does.
511
+ */
512
+ clearCategory(args) {
513
+ const duration = args.duration ?? 0.4;
514
+ const easing = args.easing ?? easeOut();
515
+ const from = this.spotlightReveal;
516
+ if (from === 0)
517
+ return this.holdSpotlight(duration);
518
+ // The lit category is held until the fade lands — cleared at `t === 1` rather
519
+ // than up front, or there would be nothing left to fade.
520
+ return this.command((t) => t >= 1
521
+ ? { spotlight: null, spotlightReveal: 0 }
522
+ : { spotlightReveal: from * (1 - t) }, duration, easing);
523
+ }
524
+ /** The no-op both of the above return — see {@link hold}. */
525
+ holdSpotlight(duration) {
526
+ return this.command(() => ({}), duration);
527
+ }
528
+ };
529
+ __decorate([
530
+ property({ default: () => [] })
531
+ ], BarChart.prototype, "data", void 0);
532
+ __decorate([
533
+ property({ default: "" })
534
+ ], BarChart.prototype, "categoryField", void 0);
535
+ __decorate([
536
+ property({ default: () => [] })
537
+ ], BarChart.prototype, "series", void 0);
538
+ __decorate([
539
+ property({ default: false })
540
+ ], BarChart.prototype, "stacked", void 0);
541
+ __decorate([
542
+ property({
543
+ default: {},
544
+ mapper: resolveAxisDomain,
545
+ tween: lerpAxisDomain,
546
+ })
547
+ ], BarChart.prototype, "valueAxis", void 0);
548
+ __decorate([
549
+ property({
550
+ default: {},
551
+ mapper: resolveAxisDomain,
552
+ tween: lerpAxisDomain,
553
+ })
554
+ ], BarChart.prototype, "categoryAxis", void 0);
555
+ __decorate([
556
+ property({
557
+ default: DEFAULT_BAR_CHART_THEME,
558
+ mapper: resolveBarChartTheme,
559
+ tween: lerpBarChartTheme,
560
+ })
561
+ ], BarChart.prototype, "theme", void 0);
562
+ __decorate([
563
+ property({ default: 1 })
564
+ ], BarChart.prototype, "growth", void 0);
565
+ __decorate([
566
+ property({ default: () => [] })
567
+ ], BarChart.prototype, "weights", void 0);
568
+ __decorate([
569
+ property({ default: null })
570
+ ], BarChart.prototype, "spotlight", void 0);
571
+ __decorate([
572
+ property({ default: 0 })
573
+ ], BarChart.prototype, "spotlightReveal", void 0);
574
+ __decorate([
575
+ command()
576
+ ], BarChart.prototype, "enter", null);
577
+ __decorate([
578
+ command({ args: [{ key: "seriesIndex", kind: "series", default: 0, min: 0, step: 1 }] })
579
+ ], BarChart.prototype, "showSeries", null);
580
+ __decorate([
581
+ command({ args: [{ key: "seriesIndex", kind: "series", default: 0, min: 0, step: 1 }] })
582
+ ], BarChart.prototype, "hideSeries", null);
583
+ __decorate([
584
+ command({ args: [{ key: "seriesIndex", kind: "series", default: 0, min: 0, step: 1 }] })
585
+ ], BarChart.prototype, "spotlightSeries", null);
586
+ __decorate([
587
+ command({ args: [{ key: "categoryIndex", kind: "number", default: 0, min: 0, step: 1 }] })
588
+ ], BarChart.prototype, "spotlightCategory", null);
589
+ __decorate([
590
+ command()
591
+ ], BarChart.prototype, "clearCategory", null);
592
+ BarChart = BarChart_1 = __decorate([
593
+ node({
594
+ key: "barChart",
595
+ parentKey: "node",
596
+ forkable: true,
597
+ layout: {
598
+ children: "freeform",
599
+ defaultWidthMode: "fixed",
600
+ defaultHeightMode: "fixed",
601
+ acceptsChildren: true,
602
+ },
603
+ seed: {
604
+ width: 760,
605
+ height: 460,
606
+ },
607
+ })
608
+ ], BarChart);
609
+ export { BarChart };
610
+ //# sourceMappingURL=bar-chart.js.map