@motionscript/charts 0.1.0 → 0.1.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +98 -28
- package/dist/bar-chart/bar-chart.d.ts +242 -119
- package/dist/bar-chart/bar-chart.d.ts.map +1 -1
- package/dist/bar-chart/bar-chart.js +231 -260
- package/dist/bar-chart/bar-chart.js.map +1 -1
- package/dist/bar-chart/category-axis.d.ts +16 -38
- package/dist/bar-chart/category-axis.d.ts.map +1 -1
- package/dist/bar-chart/category-axis.js +59 -68
- package/dist/bar-chart/category-axis.js.map +1 -1
- package/dist/bar-chart/chart-bars.d.ts +60 -91
- package/dist/bar-chart/chart-bars.d.ts.map +1 -1
- package/dist/bar-chart/chart-bars.js +88 -125
- package/dist/bar-chart/chart-bars.js.map +1 -1
- package/dist/bar-chart/index.d.ts +5 -13
- package/dist/bar-chart/index.d.ts.map +1 -1
- package/dist/bar-chart/index.js +2 -9
- package/dist/bar-chart/index.js.map +1 -1
- package/dist/bar-chart/shared.d.ts +40 -132
- package/dist/bar-chart/shared.d.ts.map +1 -1
- package/dist/bar-chart/shared.js +70 -158
- package/dist/bar-chart/shared.js.map +1 -1
- package/dist/browser/chunks/chunk-R3Q5EE6E.js +2 -0
- package/dist/browser/chunks/chunk-R3Q5EE6E.js.map +7 -0
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +4 -4
- package/dist/browser/kit.js +1 -1
- package/dist/browser/manifest.json +1 -1
- package/dist/engine.d.ts +8 -17
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +8 -17
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/kit/cartesian.d.ts +49 -35
- package/dist/kit/cartesian.d.ts.map +1 -1
- package/dist/kit/cartesian.js +80 -42
- package/dist/kit/cartesian.js.map +1 -1
- package/dist/kit/chart-canvas.d.ts +37 -40
- package/dist/kit/chart-canvas.d.ts.map +1 -1
- package/dist/kit/chart-canvas.js +53 -47
- package/dist/kit/chart-canvas.js.map +1 -1
- package/dist/kit/chart-legend.d.ts +16 -59
- package/dist/kit/chart-legend.d.ts.map +1 -1
- package/dist/kit/chart-legend.js +38 -76
- package/dist/kit/chart-legend.js.map +1 -1
- package/dist/kit/entries.d.ts +2 -0
- package/dist/kit/entries.d.ts.map +1 -1
- package/dist/kit/entries.js +2 -0
- package/dist/kit/entries.js.map +1 -1
- package/dist/kit/horizontal-axis.d.ts +77 -0
- package/dist/kit/horizontal-axis.d.ts.map +1 -0
- package/dist/kit/horizontal-axis.js +99 -0
- package/dist/kit/horizontal-axis.js.map +1 -0
- package/dist/kit/motion.d.ts +16 -3
- package/dist/kit/motion.d.ts.map +1 -1
- package/dist/kit/motion.js +15 -1
- package/dist/kit/motion.js.map +1 -1
- package/dist/{pie-chart → kit}/pie-legend.d.ts +14 -29
- package/dist/kit/pie-legend.d.ts.map +1 -0
- package/dist/kit/pie-legend.js +93 -0
- package/dist/kit/pie-legend.js.map +1 -0
- package/dist/kit/polar.d.ts +31 -0
- package/dist/kit/polar.d.ts.map +1 -0
- package/dist/kit/polar.js +33 -0
- package/dist/kit/polar.js.map +1 -0
- package/dist/kit/shared.d.ts +60 -214
- package/dist/kit/shared.d.ts.map +1 -1
- package/dist/kit/shared.js +91 -291
- package/dist/kit/shared.js.map +1 -1
- package/dist/kit/style.d.ts +254 -0
- package/dist/kit/style.d.ts.map +1 -0
- package/dist/kit/style.js +197 -0
- package/dist/kit/style.js.map +1 -0
- package/dist/kit/vertical-axis.d.ts +36 -45
- package/dist/kit/vertical-axis.d.ts.map +1 -1
- package/dist/kit/vertical-axis.js +69 -81
- package/dist/kit/vertical-axis.js.map +1 -1
- package/dist/line-chart/chart-line.d.ts +107 -40
- package/dist/line-chart/chart-line.d.ts.map +1 -1
- package/dist/line-chart/chart-line.js +172 -89
- package/dist/line-chart/chart-line.js.map +1 -1
- package/dist/line-chart/chart-region.d.ts +14 -27
- package/dist/line-chart/chart-region.d.ts.map +1 -1
- package/dist/line-chart/chart-region.js +13 -28
- package/dist/line-chart/chart-region.js.map +1 -1
- package/dist/line-chart/index.d.ts +5 -18
- package/dist/line-chart/index.d.ts.map +1 -1
- package/dist/line-chart/index.js +3 -16
- package/dist/line-chart/index.js.map +1 -1
- package/dist/line-chart/line-chart.d.ts +272 -119
- package/dist/line-chart/line-chart.d.ts.map +1 -1
- package/dist/line-chart/line-chart.js +309 -257
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/line-chart/shared.d.ts +73 -130
- package/dist/line-chart/shared.d.ts.map +1 -1
- package/dist/line-chart/shared.js +195 -156
- package/dist/line-chart/shared.js.map +1 -1
- package/dist/line-chart/x-scale.d.ts +16 -55
- package/dist/line-chart/x-scale.d.ts.map +1 -1
- package/dist/line-chart/x-scale.js +44 -111
- package/dist/line-chart/x-scale.js.map +1 -1
- package/dist/nodes.d.ts +3 -13
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +9 -13
- package/dist/nodes.js.map +1 -1
- package/dist/pie-chart/index.d.ts +3 -15
- package/dist/pie-chart/index.d.ts.map +1 -1
- package/dist/pie-chart/index.js +1 -11
- package/dist/pie-chart/index.js.map +1 -1
- package/dist/pie-chart/pie-chart.d.ts +147 -85
- package/dist/pie-chart/pie-chart.d.ts.map +1 -1
- package/dist/pie-chart/pie-chart.js +65 -86
- package/dist/pie-chart/pie-chart.js.map +1 -1
- package/dist/pie-chart/pie-ring.d.ts +65 -196
- package/dist/pie-chart/pie-ring.d.ts.map +1 -1
- package/dist/pie-chart/pie-ring.js +154 -292
- package/dist/pie-chart/pie-ring.js.map +1 -1
- package/dist/pie-chart/shared.d.ts +78 -172
- package/dist/pie-chart/shared.d.ts.map +1 -1
- package/dist/pie-chart/shared.js +54 -177
- package/dist/pie-chart/shared.js.map +1 -1
- package/dist/radar-chart/index.d.ts +5 -0
- package/dist/radar-chart/index.d.ts.map +1 -0
- package/dist/radar-chart/index.js +5 -0
- package/dist/radar-chart/index.js.map +1 -0
- package/dist/radar-chart/radar-chart.d.ts +218 -0
- package/dist/radar-chart/radar-chart.d.ts.map +1 -0
- package/dist/radar-chart/radar-chart.js +224 -0
- package/dist/radar-chart/radar-chart.js.map +1 -0
- package/dist/radar-chart/radar-grid.d.ts +43 -0
- package/dist/radar-chart/radar-grid.d.ts.map +1 -0
- package/dist/radar-chart/radar-grid.js +61 -0
- package/dist/radar-chart/radar-grid.js.map +1 -0
- package/dist/radar-chart/radar-series.d.ts +59 -0
- package/dist/radar-chart/radar-series.d.ts.map +1 -0
- package/dist/radar-chart/radar-series.js +71 -0
- package/dist/radar-chart/radar-series.js.map +1 -0
- package/dist/radar-chart/shared.d.ts +73 -0
- package/dist/radar-chart/shared.d.ts.map +1 -0
- package/dist/radar-chart/shared.js +123 -0
- package/dist/radar-chart/shared.js.map +1 -0
- package/dist/radial-chart/index.d.ts +6 -0
- package/dist/radial-chart/index.d.ts.map +1 -0
- package/dist/radial-chart/index.js +4 -0
- package/dist/radial-chart/index.js.map +1 -0
- package/dist/radial-chart/radial-chart.d.ts +216 -0
- package/dist/radial-chart/radial-chart.d.ts.map +1 -0
- package/dist/radial-chart/radial-chart.js +174 -0
- package/dist/radial-chart/radial-chart.js.map +1 -0
- package/dist/radial-chart/radial-rings.d.ts +121 -0
- package/dist/radial-chart/radial-rings.d.ts.map +1 -0
- package/dist/radial-chart/radial-rings.js +273 -0
- package/dist/radial-chart/radial-rings.js.map +1 -0
- package/dist/radial-chart/shared.d.ts +73 -0
- package/dist/radial-chart/shared.d.ts.map +1 -0
- package/dist/radial-chart/shared.js +79 -0
- package/dist/radial-chart/shared.js.map +1 -0
- package/package.json +4 -4
- package/registry.json +21 -6
- package/src/bar-chart/bar-chart.ts +274 -315
- package/src/bar-chart/category-axis.ts +53 -88
- package/src/bar-chart/chart-bars.ts +108 -205
- package/src/bar-chart/index.ts +8 -19
- package/src/bar-chart/shared.ts +81 -309
- package/src/engine.ts +8 -17
- package/src/index.ts +2 -0
- package/src/kit/cartesian.ts +118 -55
- package/src/kit/chart-canvas.ts +50 -80
- package/src/kit/chart-legend.ts +36 -97
- package/src/kit/entries.ts +4 -1
- package/src/kit/horizontal-axis.ts +114 -0
- package/src/kit/motion.ts +18 -2
- package/src/{pie-chart → kit}/pie-legend.ts +25 -65
- package/src/kit/polar.ts +54 -0
- package/src/kit/shared.ts +104 -487
- package/src/kit/style.ts +257 -0
- package/src/kit/vertical-axis.ts +81 -115
- package/src/line-chart/chart-line.ts +185 -91
- package/src/line-chart/chart-region.ts +14 -31
- package/src/line-chart/index.ts +14 -22
- package/src/line-chart/line-chart.ts +377 -315
- package/src/line-chart/shared.ts +229 -304
- package/src/line-chart/x-scale.ts +44 -121
- package/src/nodes.ts +9 -13
- package/src/pie-chart/index.ts +3 -35
- package/src/pie-chart/pie-chart.ts +83 -134
- package/src/pie-chart/pie-ring.ts +162 -368
- package/src/pie-chart/shared.ts +92 -379
- package/src/radar-chart/index.ts +4 -0
- package/src/radar-chart/radar-chart.ts +287 -0
- package/src/radar-chart/radar-grid.ts +78 -0
- package/src/radar-chart/radar-series.ts +88 -0
- package/src/radar-chart/shared.ts +146 -0
- package/src/radial-chart/index.ts +5 -0
- package/src/radial-chart/radial-chart.ts +210 -0
- package/src/radial-chart/radial-rings.ts +328 -0
- package/src/radial-chart/shared.ts +114 -0
- package/dist/browser/chunks/chunk-TJNHLQGV.js +0 -2
- package/dist/browser/chunks/chunk-TJNHLQGV.js.map +0 -7
- package/dist/kit/chart-border.d.ts +0 -27
- package/dist/kit/chart-border.d.ts.map +0 -1
- package/dist/kit/chart-border.js +0 -28
- package/dist/kit/chart-border.js.map +0 -1
- package/dist/line-chart/horizontal-axis.d.ts +0 -79
- package/dist/line-chart/horizontal-axis.d.ts.map +0 -1
- package/dist/line-chart/horizontal-axis.js +0 -129
- package/dist/line-chart/horizontal-axis.js.map +0 -1
- package/dist/pie-chart/pie-legend.d.ts.map +0 -1
- package/dist/pie-chart/pie-legend.js +0 -117
- package/dist/pie-chart/pie-legend.js.map +0 -1
- package/src/kit/chart-border.ts +0 -50
- package/src/line-chart/horizontal-axis.ts +0 -151
|
@@ -1,102 +1,84 @@
|
|
|
1
|
-
import { Rect, Types, component2d, easeOut, } from "@motionscript/sdk";
|
|
1
|
+
import { Rect, Types, clamp, component2d, easeOut, } from "@motionscript/sdk";
|
|
2
2
|
import { formatNumber } from "@motionscript/sdk/component";
|
|
3
|
-
import { cartesianFrame,
|
|
4
|
-
import { ChartBorder } from "../kit/chart-border.js";
|
|
3
|
+
import { cartesianFrame, chartViews, columnGhost, frameGap, headroom, laidOutSize, overhang } from "../kit/cartesian.js";
|
|
5
4
|
import { ChartCanvas } from "../kit/chart-canvas.js";
|
|
6
5
|
import { ChartLegend } from "../kit/chart-legend.js";
|
|
7
|
-
import { asParts, fresh } from "../kit/entries.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
6
|
+
import { SERIES_ARG, asParts, fresh } from "../kit/entries.js";
|
|
7
|
+
import { HorizontalAxis } from "../kit/horizontal-axis.js";
|
|
8
|
+
import { DEFAULT_MARKERS, ENTER_SHARE, beats, legendAway } from "../kit/motion.js";
|
|
9
|
+
import { SPOTLIGHT_DIM, formatMarker, lerpAxisDomain, lerpAxisEntry, markerStep, markerValues, niceTicks, num, resolveAxisDomain, settleAxisDomain, } from "../kit/shared.js";
|
|
10
|
+
import { CARTESIAN_GRID, CHART_COLORS, CHART_LEGEND, CHART_STYLE_FAMILY, CHART_STYLE_PRESETS, CHART_TEXT, axisConfigOf, legendConfigOf, seriesFill, valueText, } from "../kit/style.js";
|
|
11
|
+
import { VerticalAxis, axisPads } from "../kit/vertical-axis.js";
|
|
12
|
+
import { CategoryAxis, slotCentre } from "./category-axis.js";
|
|
12
13
|
import { ChartBars } from "./chart-bars.js";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
const SERIES_ARG = Types.custom(0, { kind: "series", min: 0, step: 1 });
|
|
16
|
-
/** How far the legend starts below its slot when it enters, in px. */
|
|
17
|
-
const LEGEND_SLIDE = 20;
|
|
18
|
-
/** Fallback marker count when the value axis states no ticks. */
|
|
19
|
-
const DEFAULT_MARKERS = 5;
|
|
20
|
-
/**
|
|
21
|
-
* How `enter` divides its duration between its three beats: the axes arriving,
|
|
22
|
-
* the columns rising, and the legend coming up. Shares of the whole, so they
|
|
23
|
-
* must sum to 1.
|
|
24
|
-
*/
|
|
25
|
-
const AXES_SHARE = 0.35;
|
|
26
|
-
const BARS_SHARE = 0.5;
|
|
27
|
-
const LEGEND_SHARE = 0.15;
|
|
28
|
-
/** The value axis with its range and ticks resolved: the explicit ones, else five across the scale. */
|
|
14
|
+
import { BAR_LABELS, BAR_MARKS, barCategories, barSpans, resolveBarScale, } from "./shared.js";
|
|
15
|
+
const BAR_STYLE = Types.style(CHART_STYLE_FAMILY, { colors: CHART_COLORS, text: CHART_TEXT, grid: CARTESIAN_GRID, marks: BAR_MARKS, labels: BAR_LABELS, legend: CHART_LEGEND }, { presets: CHART_STYLE_PRESETS });
|
|
29
16
|
function drawnValue(s, domain) {
|
|
30
17
|
const divisions = (domain.ticks?.length ?? DEFAULT_MARKERS) - 1;
|
|
31
|
-
const range = resolveBarScale(s.data, s.series, s.
|
|
32
|
-
|
|
18
|
+
const range = resolveBarScale(s.data, s.series, s.stack, domain.range, divisions);
|
|
19
|
+
const even = domain.range !== undefined || s.stack === "percent";
|
|
20
|
+
return { ...domain, range, ticks: domain.ticks ?? (even ? markerValues(range, DEFAULT_MARKERS - 1) : niceTicks(range, DEFAULT_MARKERS - 1)) };
|
|
33
21
|
}
|
|
34
|
-
/** The last domain worked out from each data array, and what
|
|
22
|
+
/** The last domain worked out from each data array, and what it was read from. */
|
|
35
23
|
const domainsDrawn = new WeakMap();
|
|
36
|
-
/** The value axis as
|
|
24
|
+
/** The value axis as drawn now. A series being hidden keeps its room, so the ticks hold still while it goes. */
|
|
37
25
|
export function barChartDomain(s) {
|
|
38
|
-
const
|
|
39
|
-
const kept = domainsDrawn.get(data);
|
|
40
|
-
if (kept && kept.
|
|
26
|
+
const key = [s.series, s.stack, s.valueAxis];
|
|
27
|
+
const kept = domainsDrawn.get(s.data);
|
|
28
|
+
if (kept && kept.key.every((value, i) => value === key[i]))
|
|
41
29
|
return kept.domain;
|
|
42
|
-
const domain = settleAxisDomain(valueAxis, (d) => drawnValue(s, d));
|
|
43
|
-
domainsDrawn.set(data, {
|
|
30
|
+
const domain = settleAxisDomain(s.valueAxis, (d) => drawnValue(s, d));
|
|
31
|
+
domainsDrawn.set(s.data, { key, domain });
|
|
44
32
|
return domain;
|
|
45
33
|
}
|
|
46
|
-
/**
|
|
47
|
-
* A value written down as this chart's value axis says: the ticks and the
|
|
48
|
-
* figures printed on the columns both read through here, so the two cannot
|
|
49
|
-
* disagree about what a number looks like.
|
|
50
|
-
*/
|
|
34
|
+
/** Ticks and the figures printed by bars both read through here, so they cannot disagree about how a number looks. */
|
|
51
35
|
function valueLabel(s, domain, value) {
|
|
52
36
|
const pattern = s.valueAxis.format;
|
|
53
|
-
|
|
37
|
+
if (pattern !== undefined)
|
|
38
|
+
return formatNumber(value, pattern);
|
|
39
|
+
const text = formatMarker(value, markerStep(domain.ticks));
|
|
40
|
+
return s.stack === "percent" ? `${text}%` : text;
|
|
54
41
|
}
|
|
55
|
-
/** A value
|
|
56
|
-
function
|
|
42
|
+
/** A value along the value axis, a run of `length` px centred on 0. */
|
|
43
|
+
function valueToPx(domain, length, value) {
|
|
57
44
|
const [min, max] = domain.range;
|
|
58
|
-
if (max
|
|
59
|
-
return -
|
|
60
|
-
return ((value - min) / (max - min) - 0.5) *
|
|
45
|
+
if (max === min)
|
|
46
|
+
return -length / 2;
|
|
47
|
+
return ((value - min) / (max - min) - 0.5) * length;
|
|
61
48
|
}
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
function baselinePx(domain, plot) {
|
|
67
|
-
const [min, max] = domain.range;
|
|
68
|
-
return valueToY(domain, plot, Math.min(Math.max(0, min), max));
|
|
49
|
+
/** Where bars grow from: zero held on the scale, so a cropped axis grows them from the plot's edge. */
|
|
50
|
+
function baselineValue(domain) {
|
|
51
|
+
const [lo, hi] = domain.range;
|
|
52
|
+
return clamp(0, Math.min(lo, hi), Math.max(lo, hi));
|
|
69
53
|
}
|
|
70
|
-
/** Series `i`'s live share of the slot; 0 once it has been hidden. */
|
|
71
54
|
function weightAt(s, index) {
|
|
72
55
|
return Math.max(0, s.weights[index] ?? 1);
|
|
73
56
|
}
|
|
74
|
-
/**
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return scaled;
|
|
57
|
+
/** The plot's run along the value axis and across the categories. */
|
|
58
|
+
function runs(s, plot) {
|
|
59
|
+
return s.orientation === "horizontal" ? { along: plot.width, across: plot.height } : { along: plot.height, across: plot.width };
|
|
60
|
+
}
|
|
61
|
+
/** Category `row`'s slot centre across the plot: left to right, or top to bottom. */
|
|
62
|
+
function slotAt(s, row, across) {
|
|
63
|
+
const centre = slotCentre(row, s.data.length, across);
|
|
64
|
+
return s.orientation === "horizontal" ? -centre : centre;
|
|
83
65
|
}
|
|
84
66
|
/**
|
|
85
|
-
* Series `index`'s
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
67
|
+
* Series `index`'s bars in centred pixel space. Side by side, a series divides
|
|
68
|
+
* the slot with its siblings by live weight; stacked, it takes the whole slot
|
|
69
|
+
* and its weight scales its length, so hiding a series reflows rather than
|
|
70
|
+
* punching a hole.
|
|
89
71
|
*/
|
|
90
|
-
function seriesBars(s, index, plot
|
|
72
|
+
function seriesBars(s, index, plot) {
|
|
91
73
|
const series = s.series[index];
|
|
92
74
|
if (!series)
|
|
93
75
|
return [];
|
|
94
76
|
const domain = barChartDomain(s);
|
|
77
|
+
const { along, across } = runs(s, plot);
|
|
95
78
|
const n = s.data.length;
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const gap = s.theme.bar.groupGap;
|
|
79
|
+
const slot = n > 0 ? across / n : 0;
|
|
80
|
+
const inner = slot * clamp(s.style.barWidth, 0, 1);
|
|
81
|
+
const stacked = s.stack !== "none";
|
|
100
82
|
let total = 0;
|
|
101
83
|
for (let i = 0; i < s.series.length; i++)
|
|
102
84
|
total += weightAt(s, i);
|
|
@@ -105,66 +87,56 @@ function seriesBars(s, index, plot, captioned) {
|
|
|
105
87
|
let before = 0;
|
|
106
88
|
for (let i = 0; i < index; i++)
|
|
107
89
|
before += weightAt(s, i);
|
|
90
|
+
const gap = s.series.length > 1 ? s.style.barGap : 0;
|
|
91
|
+
const values = s.style.values;
|
|
108
92
|
return s.data.map((row, rowIndex) => {
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
93
|
+
// Built from live contributions, so a fading series lowers the segments beyond it.
|
|
94
|
+
const spans = barSpans(row, s.series, s.stack, s.weights);
|
|
95
|
+
const span = spans[index] ?? { from: 0, to: 0, value: 0, outer: false };
|
|
96
|
+
const centre = slotAt(s, rowIndex, across);
|
|
97
|
+
const offset = -inner / 2 + unit * before + (unit * weight) / 2;
|
|
98
|
+
let caption = "";
|
|
99
|
+
if (values !== "none" && span.value !== 0) {
|
|
100
|
+
if (!stacked || values === "inside" || s.stack === "percent")
|
|
101
|
+
caption = valueLabel(s, domain, stacked ? span.value : num(row, series.field));
|
|
102
|
+
else if (span.outer)
|
|
103
|
+
caption = valueLabel(s, domain, span.to);
|
|
104
|
+
}
|
|
119
105
|
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
from:
|
|
123
|
-
to:
|
|
124
|
-
round:
|
|
125
|
-
|
|
126
|
-
...(captioned && { caption: valueLabel(s, domain, value) }),
|
|
106
|
+
center: stacked ? centre : centre + (s.orientation === "horizontal" ? -offset : offset),
|
|
107
|
+
thickness: stacked ? inner : Math.max(0, unit * weight - gap),
|
|
108
|
+
from: valueToPx(domain, along, span.from),
|
|
109
|
+
to: valueToPx(domain, along, span.to),
|
|
110
|
+
round: span.outer,
|
|
111
|
+
caption,
|
|
127
112
|
};
|
|
128
113
|
});
|
|
129
114
|
}
|
|
130
|
-
/**
|
|
131
|
-
* How far each category is dimmed by the spotlight, one entry per row: 1 while
|
|
132
|
-
* nothing is spotlit, falling to the theme's `dimOpacity` for every category but
|
|
133
|
-
* the chosen one as the reveal climbs.
|
|
134
|
-
*/
|
|
115
|
+
/** Per-row opacity: 1 while nothing is spotlit, falling to {@link SPOTLIGHT_DIM} for all but the chosen category. */
|
|
135
116
|
function categoryDims(s) {
|
|
136
|
-
const focus =
|
|
137
|
-
const dim = s.theme.spotlight.dimOpacity;
|
|
117
|
+
const focus = clamp(s.spotlightReveal, 0, 1);
|
|
138
118
|
const chosen = s.spotlight;
|
|
139
|
-
return s.data.map((_, i) => (focus <= 0 || chosen === i ? 1 : 1 - (1 -
|
|
119
|
+
return s.data.map((_, i) => (focus <= 0 || chosen === i ? 1 : 1 - (1 - SPOTLIGHT_DIM) * focus));
|
|
140
120
|
}
|
|
141
121
|
/** The parts a bar chart's commands drive, found through its children. */
|
|
142
122
|
function partsOf(children) {
|
|
143
|
-
const
|
|
144
|
-
if (!frame)
|
|
145
|
-
return null;
|
|
146
|
-
const cAxis = frame.below.find(CategoryAxis.is);
|
|
147
|
-
if (!VerticalAxis.is(frame.axis) || !cAxis)
|
|
148
|
-
return null;
|
|
123
|
+
const views = chartViews(children);
|
|
149
124
|
return {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
bars:
|
|
153
|
-
legend:
|
|
125
|
+
valueAxis: views.find((view) => VerticalAxis.is(view) || HorizontalAxis.is(view)) ?? null,
|
|
126
|
+
categoryAxis: views.find(CategoryAxis.is) ?? null,
|
|
127
|
+
bars: views.filter(ChartBars.is),
|
|
128
|
+
legend: views.find(ChartLegend.is) ?? null,
|
|
154
129
|
};
|
|
155
130
|
}
|
|
156
131
|
/**
|
|
157
|
-
* Move series `index`'s
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* arriving forwards does.
|
|
132
|
+
* Move series `index`'s share to `target` with its legend entry. Its bars stay
|
|
133
|
+
* drawn for every frame the move covers and switch off only once a hide has
|
|
134
|
+
* landed, so arriving at a time backwards matches arriving forwards.
|
|
161
135
|
*/
|
|
162
136
|
function toggling(timeline, state, children, index, target) {
|
|
163
137
|
const parts = partsOf(children);
|
|
164
|
-
if (!parts
|
|
138
|
+
if (!parts.bars[index])
|
|
165
139
|
return timeline.wait(timeline.duration);
|
|
166
|
-
// Read through `weightAt`, so a chart whose stored weights are short still
|
|
167
|
-
// tweens from the 1 it is drawing at.
|
|
168
140
|
const from = state.series.map((_, i) => weightAt(state, i));
|
|
169
141
|
const start = from[index] ?? 1;
|
|
170
142
|
const showing = state.series.map((s, i) => state.showing[i] ?? (s.enabled ?? true));
|
|
@@ -180,61 +152,42 @@ function toggling(timeline, state, children, index, target) {
|
|
|
180
152
|
]);
|
|
181
153
|
}
|
|
182
154
|
/**
|
|
183
|
-
* A
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* - {@link CategoryAxis} — the row of names beneath the baseline, one per row.
|
|
189
|
-
* - {@link ChartBars} (one per series) — that series' column in every category,
|
|
190
|
-
* plus the figures printed on them.
|
|
191
|
-
* - `ChartLegend` — a row of entries below the chart, one per series.
|
|
155
|
+
* A bar chart, vertical or horizontal, its series side by side, stacked, or
|
|
156
|
+
* stacked as shares of 100. Built like the line chart from `ChartCanvas`, a
|
|
157
|
+
* value axis, a {@link CategoryAxis}, one {@link ChartBars} per series and
|
|
158
|
+
* `ChartLegend`. Zero is always on the scale and bars grow about it, so a
|
|
159
|
+
* negative value draws the other way from the same `growth` signal.
|
|
192
160
|
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* a negative value draws downward out of the same `growth` signal.
|
|
197
|
-
*
|
|
198
|
-
* **Resting state is the finished chart**; `enter` arms every part on its first
|
|
199
|
-
* frame. `showSeries`/`hideSeries`/`spotlightSeries` pick out a *series* (hiding
|
|
200
|
-
* one reflows the group), `spotlightCategory`/`clearCategory` a *column of the
|
|
201
|
-
* table*. A `to` of the value axis's `range` or `ticks` tweens from the domain
|
|
202
|
-
* as drawn, so an auto axis rescales the columns continuously.
|
|
161
|
+
* Resting state is the finished chart; `enter` arms every part on its first
|
|
162
|
+
* frame. A `to` of the value axis's `range` or `ticks` tweens from the domain as
|
|
163
|
+
* drawn, so an auto axis rescales the bars continuously.
|
|
203
164
|
*/
|
|
204
165
|
export const BarChart = component2d({
|
|
205
166
|
key: "barChart",
|
|
206
167
|
state: {
|
|
207
|
-
data: fresh(() => []),
|
|
168
|
+
data: fresh(() => [], { kind: "dataset" }),
|
|
169
|
+
// Names a column, not a value, like the line chart's `xField`.
|
|
208
170
|
categoryField: "",
|
|
209
|
-
series: fresh(() => []),
|
|
210
|
-
|
|
171
|
+
series: fresh(() => [], { kind: "series" }),
|
|
172
|
+
/** `vertical`: columns rise from a row of categories; `horizontal`: bars grow right from a column of them. */
|
|
173
|
+
orientation: Types.oneOf(["vertical", "horizontal"], { animatable: false }),
|
|
174
|
+
/** `stacked`: each series sits on the sum of those before it; `percent`: every category reads as shares of 100. */
|
|
175
|
+
stack: Types.oneOf(["none", "stacked", "percent"]),
|
|
211
176
|
valueAxis: Types.custom({}, { resolve: resolveAxisDomain, lerp: lerpAxisEntry }),
|
|
212
177
|
categoryAxis: Types.custom({}, { resolve: resolveAxisDomain, lerp: lerpAxisDomain }),
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Global rise of every column in `[0, 1]`, measured about the baseline: 0
|
|
216
|
-
* collapses them all onto it, 1 draws them at full length.
|
|
217
|
-
*/
|
|
178
|
+
style: BAR_STYLE,
|
|
179
|
+
/** Rise of every bar about the baseline, `[0, 1]`: 0 collapses them onto it. */
|
|
218
180
|
growth: 1,
|
|
219
|
-
/**
|
|
220
|
-
* How much of the slot each series claims, one entry per series: a weight
|
|
221
|
-
* rather than a switch, so hiding one is the others widening into the space.
|
|
222
|
-
* Seeded from each series' `enabled`.
|
|
223
|
-
*/
|
|
224
|
-
weights: fresh(() => []),
|
|
225
|
-
/** The category spotlit, or `null` for none. The choice snaps; `spotlightReveal` is the animation. */
|
|
181
|
+
/** The category spotlit, or `null`. The choice snaps; `spotlightReveal` animates it. */
|
|
226
182
|
spotlight: Types.custom(null),
|
|
227
|
-
/** How far the category spotlight is drawn in, `[0, 1]`. */
|
|
228
183
|
spotlightReveal: 0,
|
|
229
|
-
/**
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
showValues: Types.boolean(false, { animatable: false }),
|
|
233
|
-
/** Whether each series' columns are drawn: on for every frame a show or hide covers, off once a hide lands. */
|
|
184
|
+
/** Each series' share of the slot, seeded from `enabled`: a weight rather than a switch, so hiding one widens the others. */
|
|
185
|
+
weights: Types.custom([], { private: true }),
|
|
186
|
+
/** Whether each series' bars are drawn: on for every frame a show or hide covers, off once a hide lands. */
|
|
234
187
|
showing: Types.custom([], { private: true }),
|
|
235
188
|
},
|
|
236
189
|
defaults: {
|
|
237
|
-
//
|
|
190
|
+
// A node made of parts that fill would otherwise hug them.
|
|
238
191
|
width: "fill",
|
|
239
192
|
height: "fill",
|
|
240
193
|
weights: (state) => state.series.map((s) => ((s.enabled ?? true) ? 1 : 0)),
|
|
@@ -242,162 +195,184 @@ export const BarChart = component2d({
|
|
|
242
195
|
},
|
|
243
196
|
allowedChildren: [],
|
|
244
197
|
children: (state) => {
|
|
245
|
-
const
|
|
198
|
+
const style = () => state.style;
|
|
246
199
|
const domain = () => barChartDomain(state);
|
|
247
200
|
let plot;
|
|
248
|
-
// Before the panel is built or laid out it has no size, and the columns map onto nothing.
|
|
249
201
|
const plotSize = () => laidOutSize(plot);
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
202
|
+
const built = state.style;
|
|
203
|
+
const horizontal = state.orientation === "horizontal";
|
|
204
|
+
const categories = barCategories(state.data, state.categoryField);
|
|
205
|
+
const valueConfig = () => axisConfigOf(style(), horizontal ? "x" : "y");
|
|
206
|
+
const categoryConfig = () => axisConfigOf(style(), horizontal ? "y" : "x");
|
|
207
|
+
const valueCaptions = () => {
|
|
208
|
+
const d = domain();
|
|
209
|
+
return d.ticks.map((value) => valueLabel(state, d, value));
|
|
210
|
+
};
|
|
211
|
+
const showValue = horizontal ? built.showXAxis : built.showYAxis;
|
|
212
|
+
const showCategory = horizontal ? built.showYAxis : built.showXAxis;
|
|
213
|
+
const valueAxis = showValue
|
|
214
|
+
? (horizontal
|
|
215
|
+
? new HorizontalAxis({
|
|
216
|
+
range: () => domain().range,
|
|
217
|
+
markers: () => domain().ticks,
|
|
218
|
+
captions: valueCaptions,
|
|
219
|
+
axis: valueConfig,
|
|
220
|
+
label: state.valueAxis.label ?? "",
|
|
221
|
+
overhang: () => ({ left: overhang(style()), right: overhang(style()) }),
|
|
222
|
+
width: "fill",
|
|
223
|
+
height: "hug",
|
|
224
|
+
})
|
|
225
|
+
: new VerticalAxis({
|
|
226
|
+
range: () => domain().range,
|
|
227
|
+
markers: () => domain().ticks,
|
|
228
|
+
captions: valueCaptions,
|
|
229
|
+
axis: valueConfig,
|
|
230
|
+
label: state.valueAxis.label ?? "",
|
|
231
|
+
width: "hug",
|
|
232
|
+
height: "fill",
|
|
233
|
+
}))
|
|
234
|
+
: undefined;
|
|
235
|
+
const categoryAxis = showCategory
|
|
236
|
+
? new CategoryAxis({
|
|
237
|
+
categories,
|
|
238
|
+
axis: categoryConfig,
|
|
239
|
+
label: state.categoryAxis.label ?? "",
|
|
240
|
+
orientation: horizontal ? "vertical" : "horizontal",
|
|
241
|
+
dims: () => categoryDims(state),
|
|
242
|
+
width: horizontal ? "hug" : "fill",
|
|
243
|
+
height: horizontal ? "fill" : "hug",
|
|
244
|
+
})
|
|
245
|
+
: undefined;
|
|
246
|
+
const left = horizontal ? categoryAxis : valueAxis;
|
|
247
|
+
const bottom = horizontal ? valueAxis : categoryAxis;
|
|
248
|
+
const leftConfig = horizontal ? categoryConfig : valueConfig;
|
|
249
|
+
const ghost = left && bottom
|
|
250
|
+
? columnGhost({
|
|
251
|
+
captions: horizontal ? () => categories : valueCaptions,
|
|
252
|
+
count: horizontal ? categories.length : barChartDomain(state).ticks.length,
|
|
253
|
+
text: () => leftConfig().labelStyle,
|
|
254
|
+
title: (horizontal ? state.categoryAxis.label : state.valueAxis.label) ?? "",
|
|
255
|
+
titleText: () => leftConfig().titleStyle,
|
|
256
|
+
tickPad: () => axisPads(leftConfig(), "right").tick,
|
|
257
|
+
titlePad: () => axisPads(leftConfig(), "right").title,
|
|
258
|
+
})
|
|
259
|
+
: undefined;
|
|
271
260
|
const barSets = state.series.map((s, i) => new ChartBars({
|
|
272
|
-
bars: () => seriesBars(state, i, plotSize()
|
|
273
|
-
baseline: () =>
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
cornerRadius: () => theme().bar.cornerRadius,
|
|
261
|
+
bars: () => seriesBars(state, i, plotSize()),
|
|
262
|
+
baseline: () => valueToPx(domain(), runs(state, plotSize()).along, baselineValue(domain())),
|
|
263
|
+
horizontal,
|
|
264
|
+
fill: () => seriesFill(style(), s, i),
|
|
265
|
+
cornerRadius: () => style().barRadius,
|
|
278
266
|
growth: () => state.growth,
|
|
279
267
|
dims: () => categoryDims(state),
|
|
280
|
-
|
|
281
|
-
|
|
268
|
+
// A figure past a stack's end prints the stack's total; a percent stack has none worth printing, so prints its shares inside.
|
|
269
|
+
values: () => (built.values === "end" && state.stack === "percent" ? "inside" : built.values),
|
|
270
|
+
valueStyle: () => ({ ...valueText(style()), fontSize: Math.round(style().fontSize * 0.85) }),
|
|
282
271
|
enabled: () => state.showing[i] ?? (s.enabled ?? true),
|
|
283
272
|
}));
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
273
|
+
const legendAt = built.legend;
|
|
274
|
+
const legend = legendAt !== "none"
|
|
275
|
+
? new ChartLegend({
|
|
287
276
|
series: state.series,
|
|
288
|
-
swatches: () => state.series.map((s, i) =>
|
|
289
|
-
legend: () =>
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
277
|
+
swatches: () => state.series.map((s, i) => seriesFill(style(), s, i)),
|
|
278
|
+
legend: () => legendConfigOf(style()),
|
|
279
|
+
flow: legendAt === "right" ? "vertical" : "horizontal",
|
|
280
|
+
})
|
|
281
|
+
: undefined;
|
|
282
|
+
const n = state.data.length;
|
|
283
|
+
// Category lines fall between the slots, never through a bar.
|
|
284
|
+
const boundaries = () => Array.from({ length: Math.max(0, n - 1) }, (_, i) => i + 1);
|
|
285
|
+
const slots = () => [0, Math.max(1, n)];
|
|
296
286
|
plot = new Rect({
|
|
297
|
-
clip: true,
|
|
298
287
|
children: [
|
|
299
288
|
new ChartCanvas({
|
|
300
|
-
fill: () =>
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
289
|
+
fill: () => style().background,
|
|
290
|
+
xGridStroke: () => style().verticalGrid,
|
|
291
|
+
yGridStroke: () => style().horizontalGrid,
|
|
292
|
+
xMarkers: horizontal ? () => domain().ticks : boundaries,
|
|
293
|
+
yMarkers: horizontal ? boundaries : () => domain().ticks,
|
|
294
|
+
xExtent: horizontal ? () => domain().range : slots,
|
|
295
|
+
yExtent: horizontal ? slots : () => domain().range,
|
|
296
|
+
axisStroke: () => style().axisStroke,
|
|
297
|
+
xAxisAt: () => (horizontal ? 0 : baselineValue(domain())),
|
|
298
|
+
yAxisAt: () => (horizontal ? baselineValue(domain()) : 0),
|
|
310
299
|
}),
|
|
311
300
|
...barSets,
|
|
312
|
-
new ChartBorder({ stroke: () => theme().plotArea.stroke }),
|
|
313
301
|
],
|
|
314
302
|
});
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
303
|
+
return asParts([
|
|
304
|
+
cartesianFrame({
|
|
305
|
+
left,
|
|
306
|
+
ghost,
|
|
307
|
+
plot,
|
|
308
|
+
bottom,
|
|
309
|
+
legend,
|
|
310
|
+
legendAt,
|
|
311
|
+
gap: () => frameGap(style()),
|
|
312
|
+
padding: () => ({
|
|
313
|
+
top: headroom(style()),
|
|
314
|
+
right: horizontal && bottom ? overhang(style()) : 0,
|
|
315
|
+
left: horizontal && bottom && !left ? overhang(style()) : 0,
|
|
316
|
+
bottom: 0,
|
|
317
|
+
}),
|
|
318
|
+
}),
|
|
319
|
+
]);
|
|
325
320
|
},
|
|
326
321
|
commands: {
|
|
327
|
-
/**
|
|
328
|
-
* Animate the whole chart in: the two axes stagger their markers and
|
|
329
|
-
* captions in, the columns rise from the baseline, and the legend fades up
|
|
330
|
-
* last. Its first frame arms every part: markers and captions shifted and
|
|
331
|
-
* transparent, columns collapsed, the legend dropped and transparent.
|
|
332
|
-
* `duration` is the **whole** entrance; the beats are shares of it.
|
|
333
|
-
*/
|
|
322
|
+
/** Axes stagger in, bars rise from the baseline, the legend fades up last. `duration` is the whole entrance. */
|
|
334
323
|
enter: {
|
|
335
324
|
args: {},
|
|
336
|
-
// Held from
|
|
325
|
+
// Held from arrival, so it never shows finished ahead of its draw.
|
|
337
326
|
entrance: true,
|
|
338
327
|
easing: easeOut(),
|
|
339
|
-
run: (timeline,
|
|
328
|
+
run: (timeline, state, children) => {
|
|
340
329
|
const parts = partsOf(children);
|
|
341
|
-
if (!parts)
|
|
342
|
-
return timeline.wait(timeline.duration);
|
|
343
330
|
const duration = timeline.duration;
|
|
344
|
-
const axes = duration *
|
|
345
|
-
const rise = duration *
|
|
331
|
+
const axes = duration * ENTER_SHARE.axes;
|
|
332
|
+
const rise = duration * ENTER_SHARE.marks;
|
|
346
333
|
const { legend } = parts;
|
|
347
334
|
const at0 = (piece) => ({ at: 0, duration: 0, piece });
|
|
348
|
-
// Without a legend the beat is still its share of the whole.
|
|
349
335
|
return beats(timeline, [
|
|
350
336
|
at0(timeline.animate(() => ({ growth: 0 }))),
|
|
351
|
-
...(legend ? [at0(legend.to({ opacity: 0,
|
|
352
|
-
{ at: 0, duration: axes, piece: parts.
|
|
353
|
-
{ at: 0, duration: axes, piece: parts.
|
|
337
|
+
...(legend ? [at0(legend.to({ opacity: 0, ...legendAway(state.style.legend) }))] : []),
|
|
338
|
+
...(parts.valueAxis ? [{ at: 0, duration: axes, piece: parts.valueAxis.run("enter", { slide: 24 }) }] : []),
|
|
339
|
+
...(parts.categoryAxis ? [{ at: 0, duration: axes, piece: parts.categoryAxis.run("enter", { slide: 24 }) }] : []),
|
|
354
340
|
// No priming to 0 first: the value at `t` is a function of `t`.
|
|
355
341
|
{ at: axes, duration: rise, piece: timeline.animate((t) => ({ growth: t })) },
|
|
356
|
-
...(legend ? [{ at: axes + rise, duration: duration *
|
|
342
|
+
...(legend ? [{ at: axes + rise, duration: duration * ENTER_SHARE.legend, piece: legend.to({ opacity: 1, x: 0, y: 0 }) }] : []),
|
|
357
343
|
]);
|
|
358
344
|
},
|
|
359
345
|
},
|
|
360
|
-
/**
|
|
346
|
+
/** Bring series `index` back, the group reflowing to make room, with its legend entry. */
|
|
361
347
|
showSeries: {
|
|
362
348
|
args: { seriesIndex: SERIES_ARG },
|
|
363
349
|
easing: easeOut(),
|
|
364
350
|
run: (timeline, state, children) => toggling(timeline, state, children, timeline.args.seriesIndex, 1),
|
|
365
351
|
},
|
|
366
|
-
/**
|
|
367
|
-
* Hide series `index`: its columns narrow away (or, stacked, sink out of the
|
|
368
|
-
* stack) while the rest of the group widens into the space — the mirror of
|
|
369
|
-
* `showSeries`.
|
|
370
|
-
*/
|
|
352
|
+
/** The mirror of `showSeries`: bars narrow away (stacked, sink out) while the rest widen into the space. */
|
|
371
353
|
hideSeries: {
|
|
372
354
|
args: { seriesIndex: SERIES_ARG },
|
|
373
355
|
easing: easeOut(),
|
|
374
356
|
run: (timeline, state, children) => toggling(timeline, state, children, timeline.args.seriesIndex, 0),
|
|
375
357
|
},
|
|
376
|
-
/**
|
|
377
|
-
* Fade every other series — its columns **and its legend entry** — to focus
|
|
378
|
-
* on `index`, which comes back to full. Nothing moves, so the group doesn't
|
|
379
|
-
* reflow.
|
|
380
|
-
*/
|
|
358
|
+
/** Fade every other series, bars and legend entry, to focus on `index`. Nothing moves, so the group does not reflow. */
|
|
381
359
|
spotlightSeries: {
|
|
382
360
|
args: { seriesIndex: SERIES_ARG },
|
|
383
361
|
easing: easeOut(),
|
|
384
|
-
run: (timeline,
|
|
362
|
+
run: (timeline, _state, children) => {
|
|
385
363
|
const parts = partsOf(children);
|
|
386
|
-
if (!parts)
|
|
387
|
-
return timeline.wait(timeline.duration);
|
|
388
364
|
const index = timeline.args.seriesIndex;
|
|
389
|
-
const dim = state.theme.spotlight.dimOpacity;
|
|
390
365
|
return timeline.together([
|
|
391
|
-
...parts.bars.map((bars, i) => bars.to({ opacity: i === index ? 1 :
|
|
366
|
+
...parts.bars.map((bars, i) => bars.to({ opacity: i === index ? 1 : SPOTLIGHT_DIM })),
|
|
392
367
|
...(parts.legend ? [parts.legend.run("spotlightSeries", { index })] : []),
|
|
368
|
+
timeline.wait(timeline.duration),
|
|
393
369
|
]);
|
|
394
370
|
},
|
|
395
371
|
},
|
|
396
372
|
/**
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
* category that is not there takes its slot and changes nothing.
|
|
373
|
+
* Fade every other category's bars and caption to {@link SPOTLIGHT_DIM}. While
|
|
374
|
+
* another is lit the focus moves directly. A missing category takes its slot
|
|
375
|
+
* and changes nothing.
|
|
401
376
|
*/
|
|
402
377
|
spotlightCategory: {
|
|
403
378
|
args: { categoryIndex: Types.number(0, { min: 0, step: 1 }) },
|
|
@@ -410,11 +385,7 @@ export const BarChart = component2d({
|
|
|
410
385
|
return timeline.animate((t) => ({ spotlight: index, spotlightReveal: from + (1 - from) * t }));
|
|
411
386
|
},
|
|
412
387
|
},
|
|
413
|
-
/**
|
|
414
|
-
* Drop the category spotlight, every category back to full. The lit
|
|
415
|
-
* category is held until the fade lands; a no-op when nothing is lit, which
|
|
416
|
-
* still takes its slot.
|
|
417
|
-
*/
|
|
388
|
+
/** Every category back to full; the lit one is held until the fade lands. A no-op when nothing is lit, which still takes its slot. */
|
|
418
389
|
clearCategory: {
|
|
419
390
|
args: {},
|
|
420
391
|
easing: easeOut(),
|