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