@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
|
@@ -7,26 +7,39 @@ import {
|
|
|
7
7
|
type ChildView,
|
|
8
8
|
type DataRecord,
|
|
9
9
|
type NodeOf,
|
|
10
|
+
type OutOf,
|
|
10
11
|
// Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.
|
|
11
12
|
type Node2D,
|
|
12
13
|
type Node2DProps as _Node2DProps,
|
|
14
|
+
type TextStyle as _TextStyle,
|
|
15
|
+
type CornerRadiusResolved as _CornerRadiusResolved,
|
|
16
|
+
type CornerStyleResolved as _CornerStyleResolved,
|
|
17
|
+
type RectCornerRadius as _RectCornerRadius,
|
|
18
|
+
type RectCornerStyle as _RectCornerStyle,
|
|
19
|
+
type Fill as _Fill,
|
|
20
|
+
type FillResolved as _FillResolved,
|
|
21
|
+
type Stroke as _Stroke,
|
|
22
|
+
type StrokeResolved as _StrokeResolved,
|
|
13
23
|
type Size2D,
|
|
24
|
+
type StyleInput as _StyleInput,
|
|
25
|
+
type StyleValues as _StyleValues,
|
|
14
26
|
} from "@motionscript/sdk"
|
|
15
27
|
import { formatNumber } from "@motionscript/sdk/component"
|
|
16
28
|
|
|
17
|
-
import { cartesianFrame,
|
|
18
|
-
import { ChartBorder } from "../kit/chart-border"
|
|
29
|
+
import { cartesianFrame, chartViews, columnGhost, frameGap, headroom, laidOutSize, overhang } from "../kit/cartesian"
|
|
19
30
|
import { ChartCanvas } from "../kit/chart-canvas"
|
|
20
31
|
import { ChartLegend } from "../kit/chart-legend"
|
|
21
32
|
import { SERIES_ARG, asParts, fresh } from "../kit/entries"
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
33
|
+
import { HorizontalAxis } from "../kit/horizontal-axis"
|
|
34
|
+
import { DEFAULT_MARKERS, ENTER_SHARE, beats, legendAway, type AnyTimeline, type Beat } from "../kit/motion"
|
|
24
35
|
import {
|
|
36
|
+
SPOTLIGHT_DIM,
|
|
25
37
|
formatMarker,
|
|
26
38
|
lerpAxisDomain,
|
|
27
39
|
lerpAxisEntry,
|
|
28
40
|
markerStep,
|
|
29
41
|
markerValues,
|
|
42
|
+
niceTicks,
|
|
30
43
|
num,
|
|
31
44
|
resolveAxisDomain,
|
|
32
45
|
settleAxisDomain,
|
|
@@ -34,27 +47,48 @@ import {
|
|
|
34
47
|
type Range,
|
|
35
48
|
type ResolvedAxisDomain,
|
|
36
49
|
} from "../kit/shared"
|
|
37
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
CARTESIAN_GRID,
|
|
52
|
+
CARTESIAN_TEXT,
|
|
53
|
+
CHART_LEGEND,
|
|
54
|
+
CHART_SERIES,
|
|
55
|
+
CHART_STYLE_FAMILY,
|
|
56
|
+
CHART_STYLE_PRESETS,
|
|
57
|
+
GRID_BACKGROUND,
|
|
58
|
+
axisConfigOf,
|
|
59
|
+
migrateChartStyle,
|
|
60
|
+
legendConfigOf,
|
|
61
|
+
seriesFill,
|
|
62
|
+
valueText,
|
|
63
|
+
} from "../kit/style"
|
|
64
|
+
import { VerticalAxis, axisPads } from "../kit/vertical-axis"
|
|
65
|
+
import { CategoryAxis, slotCentre } from "./category-axis"
|
|
38
66
|
import { ChartBars, type BarGeometry } from "./chart-bars"
|
|
39
67
|
import {
|
|
40
|
-
|
|
68
|
+
BAR_LABELS,
|
|
69
|
+
BAR_MARKS,
|
|
41
70
|
barCategories,
|
|
42
71
|
barSpans,
|
|
43
|
-
lerpBarChartTheme,
|
|
44
|
-
resolveBarChartTheme,
|
|
45
|
-
resolveBarFill,
|
|
46
72
|
resolveBarScale,
|
|
47
|
-
|
|
48
|
-
type BarChartConfig,
|
|
49
|
-
type BarChartTheme,
|
|
73
|
+
type BarOrientation,
|
|
50
74
|
type BarSeries,
|
|
75
|
+
type BarStack,
|
|
51
76
|
} from "./shared"
|
|
52
77
|
|
|
78
|
+
const BAR_STYLE = Types.style(
|
|
79
|
+
CHART_STYLE_FAMILY,
|
|
80
|
+
{ series: CHART_SERIES, background: GRID_BACKGROUND, text: CARTESIAN_TEXT, grid: CARTESIAN_GRID, marks: BAR_MARKS, labels: BAR_LABELS, legend: CHART_LEGEND },
|
|
81
|
+
{ presets: CHART_STYLE_PRESETS }
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
/** A bar chart's resolved style. */
|
|
85
|
+
export type BarChartStyle = OutOf<typeof BAR_STYLE>
|
|
86
|
+
|
|
53
87
|
/** What a bar chart plots, as its entries hold it. */
|
|
54
88
|
export interface BarChartValues {
|
|
55
89
|
readonly data: DataRecord[]
|
|
56
90
|
readonly series: BarSeries[]
|
|
57
|
-
readonly
|
|
91
|
+
readonly stack: BarStack
|
|
58
92
|
readonly valueAxis: AxisDomainConfig
|
|
59
93
|
}
|
|
60
94
|
|
|
@@ -62,138 +96,144 @@ export interface BarChartValues {
|
|
|
62
96
|
export type BarChartDomain = ResolvedAxisDomain
|
|
63
97
|
|
|
64
98
|
interface BarState extends BarChartValues {
|
|
65
|
-
readonly
|
|
99
|
+
readonly categoryField: string
|
|
100
|
+
readonly orientation: BarOrientation
|
|
101
|
+
readonly style: BarChartStyle
|
|
66
102
|
readonly weights: number[]
|
|
103
|
+
readonly showing: boolean[]
|
|
67
104
|
readonly spotlight: number | null
|
|
68
105
|
readonly spotlightReveal: number
|
|
69
106
|
}
|
|
70
107
|
|
|
71
108
|
function drawnValue(s: BarChartValues, domain: AxisDomainConfig): AxisDomainConfig {
|
|
72
109
|
const divisions = (domain.ticks?.length ?? DEFAULT_MARKERS) - 1
|
|
73
|
-
const range = resolveBarScale(s.data, s.series, s.
|
|
74
|
-
|
|
110
|
+
const range = resolveBarScale(s.data, s.series, s.stack, domain.range, divisions)
|
|
111
|
+
const even = domain.range !== undefined || s.stack === "percent"
|
|
112
|
+
return { ...domain, range, ticks: domain.ticks ?? (even ? markerValues(range, DEFAULT_MARKERS - 1) : niceTicks(range, DEFAULT_MARKERS - 1)) }
|
|
75
113
|
}
|
|
76
114
|
|
|
77
115
|
/** The last domain worked out from each data array, and what it was read from. */
|
|
78
|
-
const domainsDrawn = new WeakMap<DataRecord[],
|
|
116
|
+
const domainsDrawn = new WeakMap<DataRecord[], { readonly key: readonly unknown[]; readonly domain: BarChartDomain }>()
|
|
79
117
|
|
|
80
|
-
/** The value axis as drawn now. */
|
|
118
|
+
/** The value axis as drawn now. A series being hidden keeps its room, so the ticks hold still while it goes. */
|
|
81
119
|
export function barChartDomain(s: BarChartValues): BarChartDomain {
|
|
82
|
-
const
|
|
83
|
-
const kept = domainsDrawn.get(data)
|
|
84
|
-
if (kept && kept.
|
|
85
|
-
const domain = settleAxisDomain(valueAxis, (d) => drawnValue(s, d)) as BarChartDomain
|
|
86
|
-
domainsDrawn.set(data, {
|
|
120
|
+
const key = [s.series, s.stack, s.valueAxis]
|
|
121
|
+
const kept = domainsDrawn.get(s.data)
|
|
122
|
+
if (kept && kept.key.every((value, i) => value === key[i])) return kept.domain
|
|
123
|
+
const domain = settleAxisDomain(s.valueAxis, (d) => drawnValue(s, d)) as BarChartDomain
|
|
124
|
+
domainsDrawn.set(s.data, { key, domain })
|
|
87
125
|
return domain
|
|
88
126
|
}
|
|
89
127
|
|
|
90
|
-
/** Ticks and the figures printed
|
|
128
|
+
/** Ticks and the figures printed by bars both read through here, so they cannot disagree about how a number looks. */
|
|
91
129
|
function valueLabel(s: BarChartValues, domain: BarChartDomain, value: number): string {
|
|
92
130
|
const pattern = s.valueAxis.format
|
|
93
|
-
|
|
131
|
+
if (pattern !== undefined) return formatNumber(value, pattern)
|
|
132
|
+
const text = formatMarker(value, markerStep(domain.ticks))
|
|
133
|
+
return s.stack === "percent" ? `${text}%` : text
|
|
94
134
|
}
|
|
95
135
|
|
|
96
|
-
/** A value
|
|
97
|
-
function
|
|
136
|
+
/** A value along the value axis, a run of `length` px centred on 0. */
|
|
137
|
+
function valueToPx(domain: BarChartDomain, length: number, value: number): number {
|
|
98
138
|
const [min, max] = domain.range
|
|
99
|
-
if (max
|
|
100
|
-
return ((value - min) / (max - min) - 0.5) *
|
|
139
|
+
if (max === min) return -length / 2
|
|
140
|
+
return ((value - min) / (max - min) - 0.5) * length
|
|
101
141
|
}
|
|
102
142
|
|
|
103
|
-
/**
|
|
104
|
-
function
|
|
105
|
-
const [
|
|
106
|
-
return
|
|
143
|
+
/** Where bars grow from: zero held on the scale, so a cropped axis grows them from the plot's edge. */
|
|
144
|
+
function baselineValue(domain: BarChartDomain): number {
|
|
145
|
+
const [lo, hi] = domain.range
|
|
146
|
+
return clamp(0, Math.min(lo, hi), Math.max(lo, hi))
|
|
107
147
|
}
|
|
108
148
|
|
|
109
|
-
function weightAt(s:
|
|
149
|
+
function weightAt(s: { readonly weights: readonly number[] }, index: number): number {
|
|
110
150
|
return Math.max(0, s.weights[index] ?? 1)
|
|
111
151
|
}
|
|
112
152
|
|
|
113
|
-
/**
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
153
|
+
/** The plot's run along the value axis and across the categories. */
|
|
154
|
+
function runs(s: BarState, plot: Size2D): { along: number; across: number } {
|
|
155
|
+
return s.orientation === "horizontal" ? { along: plot.width, across: plot.height } : { along: plot.height, across: plot.width }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Category `row`'s slot centre across the plot: left to right, or top to bottom. */
|
|
159
|
+
function slotAt(s: BarState, row: number, across: number): number {
|
|
160
|
+
const centre = slotCentre(row, s.data.length, across)
|
|
161
|
+
return s.orientation === "horizontal" ? -centre : centre
|
|
121
162
|
}
|
|
122
163
|
|
|
123
164
|
/**
|
|
124
|
-
* Series `index`'s
|
|
125
|
-
* slot with its siblings by live weight
|
|
126
|
-
* hiding a series reflows rather than
|
|
165
|
+
* Series `index`'s bars in centred pixel space. Side by side, a series divides
|
|
166
|
+
* the slot with its siblings by live weight; stacked, it takes the whole slot
|
|
167
|
+
* and its weight scales its length, so hiding a series reflows rather than
|
|
168
|
+
* punching a hole.
|
|
127
169
|
*/
|
|
128
|
-
function seriesBars(s: BarState, index: number, plot: Size2D
|
|
170
|
+
function seriesBars(s: BarState, index: number, plot: Size2D): BarGeometry[] {
|
|
129
171
|
const series = s.series[index]
|
|
130
172
|
if (!series) return []
|
|
131
173
|
const domain = barChartDomain(s)
|
|
174
|
+
const { along, across } = runs(s, plot)
|
|
132
175
|
const n = s.data.length
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const gap = s.theme.bar.groupGap
|
|
176
|
+
const slot = n > 0 ? across / n : 0
|
|
177
|
+
const inner = slot * clamp(s.style.barWidth, 0, 1)
|
|
178
|
+
const stacked = s.stack !== "none"
|
|
137
179
|
let total = 0
|
|
138
180
|
for (let i = 0; i < s.series.length; i++) total += weightAt(s, i)
|
|
139
181
|
const unit = inner / (total > 0 ? total : 1)
|
|
140
182
|
const weight = weightAt(s, index)
|
|
141
183
|
let before = 0
|
|
142
184
|
for (let i = 0; i < index; i++) before += weightAt(s, i)
|
|
185
|
+
const gap = s.series.length > 1 ? s.style.barGap : 0
|
|
186
|
+
const values = s.style.values
|
|
143
187
|
|
|
144
188
|
return s.data.map((row, rowIndex) => {
|
|
145
|
-
// Built from live contributions, so a fading series lowers the segments
|
|
146
|
-
const spans = barSpans(
|
|
147
|
-
const span = spans[index] ?? { from: 0, to: 0, value: 0 }
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
189
|
+
// Built from live contributions, so a fading series lowers the segments beyond it.
|
|
190
|
+
const spans = barSpans(row, s.series, s.stack, s.weights)
|
|
191
|
+
const span = spans[index] ?? { from: 0, to: 0, value: 0, outer: false }
|
|
192
|
+
const centre = slotAt(s, rowIndex, across)
|
|
193
|
+
const offset = -inner / 2 + unit * before + (unit * weight) / 2
|
|
194
|
+
let caption = ""
|
|
195
|
+
if (values !== "none" && span.value !== 0) {
|
|
196
|
+
if (!stacked || values === "inside" || s.stack === "percent") caption = valueLabel(s, domain, stacked ? span.value : num(row, series.field))
|
|
197
|
+
else if (span.outer) caption = valueLabel(s, domain, span.to)
|
|
198
|
+
}
|
|
154
199
|
return {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
from:
|
|
158
|
-
to:
|
|
159
|
-
round:
|
|
160
|
-
|
|
161
|
-
|
|
200
|
+
center: stacked ? centre : centre + (s.orientation === "horizontal" ? -offset : offset),
|
|
201
|
+
thickness: stacked ? inner : Math.max(0, unit * weight - gap),
|
|
202
|
+
from: valueToPx(domain, along, span.from),
|
|
203
|
+
to: valueToPx(domain, along, span.to),
|
|
204
|
+
round: span.outer,
|
|
205
|
+
grounded: !stacked || span.from === 0,
|
|
206
|
+
caption,
|
|
162
207
|
}
|
|
163
208
|
})
|
|
164
209
|
}
|
|
165
210
|
|
|
166
|
-
/** Per-row opacity: 1 while nothing is spotlit, falling to
|
|
211
|
+
/** Per-row opacity: 1 while nothing is spotlit, falling to {@link SPOTLIGHT_DIM} for all but the chosen category. */
|
|
167
212
|
function categoryDims(s: BarState): number[] {
|
|
168
213
|
const focus = clamp(s.spotlightReveal, 0, 1)
|
|
169
|
-
const dim = s.theme.spotlight.dimOpacity
|
|
170
214
|
const chosen = s.spotlight
|
|
171
|
-
return s.data.map((_, i) => (focus <= 0 || chosen === i ? 1 : 1 - (1 -
|
|
215
|
+
return s.data.map((_, i) => (focus <= 0 || chosen === i ? 1 : 1 - (1 - SPOTLIGHT_DIM) * focus))
|
|
172
216
|
}
|
|
173
217
|
|
|
174
218
|
/** The parts a bar chart's commands drive, found through its children. */
|
|
175
219
|
function partsOf(children: readonly ChildView[]) {
|
|
176
|
-
const
|
|
177
|
-
if (!frame) return null
|
|
178
|
-
const cAxis = frame.below.find(CategoryAxis.is)
|
|
179
|
-
if (!VerticalAxis.is(frame.axis) || !cAxis) return null
|
|
220
|
+
const views = chartViews(children)
|
|
180
221
|
return {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
bars:
|
|
184
|
-
legend:
|
|
222
|
+
valueAxis: views.find((view) => VerticalAxis.is(view) || HorizontalAxis.is(view)) ?? null,
|
|
223
|
+
categoryAxis: views.find(CategoryAxis.is) ?? null,
|
|
224
|
+
bars: views.filter(ChartBars.is),
|
|
225
|
+
legend: views.find(ChartLegend.is) ?? null,
|
|
185
226
|
}
|
|
186
227
|
}
|
|
187
228
|
|
|
188
229
|
/**
|
|
189
|
-
* Move series `index`'s
|
|
190
|
-
*
|
|
191
|
-
*
|
|
230
|
+
* Move series `index`'s share to `target` with its legend entry. Its bars stay
|
|
231
|
+
* drawn for every frame the move covers and switch off only once a hide has
|
|
232
|
+
* landed, so arriving at a time backwards matches arriving forwards.
|
|
192
233
|
*/
|
|
193
|
-
function toggling(timeline: AnyTimeline, state: BarState
|
|
234
|
+
function toggling(timeline: AnyTimeline, state: BarState, children: readonly ChildView[], index: number, target: number) {
|
|
194
235
|
const parts = partsOf(children)
|
|
195
|
-
if (!parts
|
|
196
|
-
// Through `weightAt`, so short stored weights still tween from the 1 drawn.
|
|
236
|
+
if (!parts.bars[index]) return timeline.wait(timeline.duration)
|
|
197
237
|
const from = state.series.map((_, i) => weightAt(state, i))
|
|
198
238
|
const start = from[index] ?? 1
|
|
199
239
|
const showing = state.series.map((s, i) => state.showing[i] ?? (s.enabled ?? true))
|
|
@@ -210,38 +250,38 @@ function toggling(timeline: AnyTimeline, state: BarState & { readonly showing: b
|
|
|
210
250
|
}
|
|
211
251
|
|
|
212
252
|
/**
|
|
213
|
-
* A
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
253
|
+
* A bar chart, vertical or horizontal, its series side by side, stacked, or
|
|
254
|
+
* stacked as shares of 100. Built like the line chart from `ChartCanvas`, a
|
|
255
|
+
* value axis, a {@link CategoryAxis}, one {@link ChartBars} per series and
|
|
256
|
+
* `ChartLegend`. Zero is always on the scale and bars grow about it, so a
|
|
257
|
+
* negative value draws the other way from the same `growth` signal.
|
|
217
258
|
*
|
|
218
259
|
* Resting state is the finished chart; `enter` arms every part on its first
|
|
219
260
|
* frame. A `to` of the value axis's `range` or `ticks` tweens from the domain as
|
|
220
|
-
* drawn, so an auto axis rescales the
|
|
261
|
+
* drawn, so an auto axis rescales the bars continuously.
|
|
221
262
|
*/
|
|
222
263
|
export const BarChart = component2d({
|
|
223
264
|
key: "barChart",
|
|
224
265
|
state: {
|
|
225
|
-
data: fresh<DataRecord[]>(() => []),
|
|
266
|
+
data: fresh<DataRecord[]>(() => [], { kind: "dataset" }),
|
|
226
267
|
// Names a column, not a value, like the line chart's `xField`.
|
|
227
268
|
categoryField: "",
|
|
228
|
-
series: fresh<BarSeries[]>(() => []),
|
|
229
|
-
|
|
269
|
+
series: fresh<BarSeries[]>(() => [], { kind: "series" }),
|
|
270
|
+
/** `vertical`: columns rise from a row of categories; `horizontal`: bars grow right from a column of them. */
|
|
271
|
+
orientation: Types.oneOf(["vertical", "horizontal"] as const, { animatable: false }),
|
|
272
|
+
/** `stacked`: each series sits on the sum of those before it; `percent`: every category reads as shares of 100. */
|
|
273
|
+
stack: Types.oneOf(["none", "stacked", "percent"] as const),
|
|
230
274
|
valueAxis: Types.custom<AxisDomainConfig>({}, { resolve: resolveAxisDomain, lerp: lerpAxisEntry }),
|
|
231
275
|
categoryAxis: Types.custom<AxisDomainConfig>({}, { resolve: resolveAxisDomain, lerp: lerpAxisDomain }),
|
|
232
|
-
|
|
233
|
-
/** Rise of every
|
|
276
|
+
style: BAR_STYLE,
|
|
277
|
+
/** Rise of every bar about the baseline, `[0, 1]`: 0 collapses them onto it. */
|
|
234
278
|
growth: 1,
|
|
235
|
-
/** Each series' share of the slot, seeded from `enabled`: a weight rather than a switch, so hiding one widens the others. */
|
|
236
|
-
weights: fresh<number[]>(() => []),
|
|
237
279
|
/** The category spotlit, or `null`. The choice snaps; `spotlightReveal` animates it. */
|
|
238
280
|
spotlight: Types.custom<number | null>(null),
|
|
239
281
|
spotlightReveal: 0,
|
|
240
|
-
/**
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
showValues: Types.boolean(false, { animatable: false }),
|
|
244
|
-
/** Whether each series' columns are drawn: on for every frame a show or hide covers, off once a hide lands. */
|
|
282
|
+
/** Each series' share of the slot, seeded from `enabled`: a weight rather than a switch, so hiding one widens the others. */
|
|
283
|
+
weights: Types.custom<number[]>([], { private: true }),
|
|
284
|
+
/** Whether each series' bars are drawn: on for every frame a show or hide covers, off once a hide lands. */
|
|
245
285
|
showing: Types.custom<boolean[]>([], { private: true }),
|
|
246
286
|
},
|
|
247
287
|
defaults: {
|
|
@@ -251,108 +291,153 @@ export const BarChart = component2d({
|
|
|
251
291
|
weights: (state) => state.series.map((s) => ((s.enabled ?? true) ? 1 : 0)),
|
|
252
292
|
showing: (state) => state.series.map((s) => s.enabled ?? true),
|
|
253
293
|
},
|
|
294
|
+
migrate: { style: migrateChartStyle("cartesian") },
|
|
254
295
|
allowedChildren: [],
|
|
255
296
|
children: (state) => {
|
|
256
|
-
const
|
|
297
|
+
const style = () => state.style
|
|
257
298
|
const domain = () => barChartDomain(state)
|
|
258
299
|
let plot: Node2D | undefined
|
|
259
300
|
const plotSize = () => laidOutSize(plot)
|
|
260
|
-
const
|
|
301
|
+
const built = state.style
|
|
302
|
+
const horizontal = state.orientation === "horizontal"
|
|
303
|
+
const categories = barCategories(state.data, state.categoryField)
|
|
304
|
+
const valueConfig = () => axisConfigOf(style(), horizontal ? "x" : "y")
|
|
305
|
+
const categoryConfig = () => axisConfigOf(style(), horizontal ? "y" : "x")
|
|
306
|
+
const valueCaptions = () => {
|
|
307
|
+
const d = domain()
|
|
308
|
+
return d.ticks.map((value) => valueLabel(state, d, value))
|
|
309
|
+
}
|
|
310
|
+
const showValue = horizontal ? built.showXAxis : built.showYAxis
|
|
311
|
+
const showCategory = horizontal ? built.showYAxis : built.showXAxis
|
|
261
312
|
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
313
|
+
const valueAxis = showValue
|
|
314
|
+
? (horizontal
|
|
315
|
+
? new HorizontalAxis({
|
|
316
|
+
range: () => domain().range,
|
|
317
|
+
markers: () => domain().ticks,
|
|
318
|
+
captions: valueCaptions,
|
|
319
|
+
axis: valueConfig,
|
|
320
|
+
label: state.valueAxis.label ?? "",
|
|
321
|
+
overhang: () => ({ left: overhang(style()), right: overhang(style()) }),
|
|
322
|
+
width: "fill",
|
|
323
|
+
height: "hug",
|
|
324
|
+
})
|
|
325
|
+
: new VerticalAxis({
|
|
326
|
+
range: () => domain().range,
|
|
327
|
+
markers: () => domain().ticks,
|
|
328
|
+
captions: valueCaptions,
|
|
329
|
+
axis: valueConfig,
|
|
330
|
+
label: state.valueAxis.label ?? "",
|
|
331
|
+
width: "hug",
|
|
332
|
+
height: "fill",
|
|
333
|
+
})) as unknown as Node2D
|
|
334
|
+
: undefined
|
|
274
335
|
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
336
|
+
const categoryAxis = showCategory
|
|
337
|
+
? (new CategoryAxis({
|
|
338
|
+
categories,
|
|
339
|
+
axis: categoryConfig,
|
|
340
|
+
label: state.categoryAxis.label ?? "",
|
|
341
|
+
orientation: horizontal ? "vertical" : "horizontal",
|
|
342
|
+
dims: () => categoryDims(state),
|
|
343
|
+
width: horizontal ? "hug" : "fill",
|
|
344
|
+
height: horizontal ? "fill" : "hug",
|
|
345
|
+
}) as unknown as Node2D)
|
|
346
|
+
: undefined
|
|
347
|
+
|
|
348
|
+
const left = horizontal ? categoryAxis : valueAxis
|
|
349
|
+
const bottom = horizontal ? valueAxis : categoryAxis
|
|
350
|
+
const leftConfig = horizontal ? categoryConfig : valueConfig
|
|
351
|
+
const ghost = left && bottom
|
|
352
|
+
? columnGhost({
|
|
353
|
+
captions: horizontal ? () => categories : valueCaptions,
|
|
354
|
+
count: horizontal ? categories.length : barChartDomain(state).ticks.length,
|
|
355
|
+
text: () => leftConfig().labelStyle,
|
|
356
|
+
title: (horizontal ? state.categoryAxis.label : state.valueAxis.label) ?? "",
|
|
357
|
+
titleText: () => leftConfig().titleStyle,
|
|
358
|
+
tickPad: () => axisPads(leftConfig(), "right").tick,
|
|
359
|
+
titlePad: () => axisPads(leftConfig(), "right").title,
|
|
360
|
+
})
|
|
361
|
+
: undefined
|
|
283
362
|
|
|
284
363
|
const barSets = state.series.map(
|
|
285
364
|
(s, i) =>
|
|
286
365
|
new ChartBars({
|
|
287
|
-
bars: () => seriesBars(state, i, plotSize()
|
|
288
|
-
baseline: () =>
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
366
|
+
bars: () => seriesBars(state, i, plotSize()),
|
|
367
|
+
baseline: () => valueToPx(domain(), runs(state, plotSize()).along, baselineValue(domain())),
|
|
368
|
+
horizontal,
|
|
369
|
+
fill: () => seriesFill(style(), s, i),
|
|
370
|
+
cornerRadius: () => style().barCornerRadius,
|
|
371
|
+
cornerStyle: () => style().barCornerStyle,
|
|
293
372
|
growth: () => state.growth,
|
|
294
373
|
dims: () => categoryDims(state),
|
|
295
|
-
|
|
296
|
-
|
|
374
|
+
// A figure past a stack's end prints the stack's total; a percent stack has none worth printing, so prints its shares inside.
|
|
375
|
+
values: () => (built.values === "end" && state.stack === "percent" ? "inside" : built.values),
|
|
376
|
+
valueStyle: () => valueText(style()),
|
|
297
377
|
enabled: () => state.showing[i] ?? (s.enabled ?? true),
|
|
298
378
|
}) as unknown as Node2D
|
|
299
379
|
)
|
|
300
380
|
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
padding: state.theme.legend.padding,
|
|
311
|
-
}) as unknown as Node2D
|
|
312
|
-
)
|
|
313
|
-
}
|
|
381
|
+
const legendAt = built.legend
|
|
382
|
+
const legend = legendAt !== "none"
|
|
383
|
+
? (new ChartLegend({
|
|
384
|
+
series: state.series,
|
|
385
|
+
swatches: () => state.series.map((s, i) => seriesFill(style(), s, i)),
|
|
386
|
+
legend: () => legendConfigOf(style()),
|
|
387
|
+
flow: legendAt === "right" ? "vertical" : "horizontal",
|
|
388
|
+
}) as unknown as Node2D)
|
|
389
|
+
: undefined
|
|
314
390
|
|
|
391
|
+
const n = state.data.length
|
|
392
|
+
// Category lines fall between the slots, never through a bar.
|
|
393
|
+
const boundaries = () => Array.from({ length: Math.max(0, n - 1) }, (_, i) => i + 1)
|
|
394
|
+
const slots = (): Range => [0, Math.max(1, n)]
|
|
315
395
|
plot = new Rect({
|
|
316
|
-
clip: true,
|
|
317
396
|
children: [
|
|
318
397
|
new ChartCanvas({
|
|
319
|
-
fill: () =>
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
398
|
+
fill: () => style().gridBackground,
|
|
399
|
+
xGridStroke: () => style().verticalGrid,
|
|
400
|
+
yGridStroke: () => style().horizontalGrid,
|
|
401
|
+
xMarkers: horizontal ? () => domain().ticks : boundaries,
|
|
402
|
+
yMarkers: horizontal ? boundaries : () => domain().ticks,
|
|
403
|
+
xExtent: horizontal ? () => domain().range : slots,
|
|
404
|
+
yExtent: horizontal ? slots : () => domain().range,
|
|
405
|
+
axisStroke: () => style().axisStroke,
|
|
406
|
+
xAxisAt: () => (horizontal ? 0 : baselineValue(domain())),
|
|
407
|
+
yAxisAt: () => (horizontal ? baselineValue(domain()) : 0),
|
|
328
408
|
}),
|
|
329
409
|
...barSets,
|
|
330
|
-
new ChartBorder({ stroke: () => theme().plotArea.stroke }),
|
|
331
410
|
],
|
|
332
411
|
}) as unknown as Node2D
|
|
333
412
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
413
|
+
return asParts([
|
|
414
|
+
cartesianFrame({
|
|
415
|
+
background: () => style().background,
|
|
416
|
+
left,
|
|
417
|
+
ghost,
|
|
418
|
+
plot,
|
|
419
|
+
bottom,
|
|
420
|
+
legend,
|
|
421
|
+
legendAt,
|
|
422
|
+
gap: () => frameGap(style()),
|
|
423
|
+
padding: () => ({
|
|
424
|
+
top: headroom(style()),
|
|
425
|
+
right: horizontal && bottom ? overhang(style()) : 0,
|
|
426
|
+
left: horizontal && bottom && !left ? overhang(style()) : 0,
|
|
427
|
+
bottom: 0,
|
|
428
|
+
}),
|
|
429
|
+
}),
|
|
430
|
+
])
|
|
345
431
|
},
|
|
346
432
|
commands: {
|
|
347
|
-
/** Axes stagger in,
|
|
433
|
+
/** Axes stagger in, bars rise from the baseline, the legend fades up last. `duration` is the whole entrance. */
|
|
348
434
|
enter: {
|
|
349
435
|
args: {},
|
|
350
436
|
// Held from arrival, so it never shows finished ahead of its draw.
|
|
351
437
|
entrance: true,
|
|
352
438
|
easing: easeOut(),
|
|
353
|
-
run: (timeline,
|
|
439
|
+
run: (timeline, state, children) => {
|
|
354
440
|
const parts = partsOf(children)
|
|
355
|
-
if (!parts) return timeline.wait(timeline.duration)
|
|
356
441
|
const duration = timeline.duration
|
|
357
442
|
const axes = duration * ENTER_SHARE.axes
|
|
358
443
|
const rise = duration * ENTER_SHARE.marks
|
|
@@ -360,12 +445,12 @@ export const BarChart = component2d({
|
|
|
360
445
|
const at0 = (piece: Beat["piece"]): Beat => ({ at: 0, duration: 0, piece })
|
|
361
446
|
return beats(timeline, [
|
|
362
447
|
at0(timeline.animate(() => ({ growth: 0 }))),
|
|
363
|
-
...(legend ? [at0(legend.to({ opacity: 0,
|
|
364
|
-
{ at: 0, duration: axes, piece: parts.
|
|
365
|
-
{ at: 0, duration: axes, piece: parts.
|
|
448
|
+
...(legend ? [at0(legend.to({ opacity: 0, ...legendAway(state.style.legend) }))] : []),
|
|
449
|
+
...(parts.valueAxis ? [{ at: 0, duration: axes, piece: parts.valueAxis.run("enter", { slide: 24 }) }] : []),
|
|
450
|
+
...(parts.categoryAxis ? [{ at: 0, duration: axes, piece: parts.categoryAxis.run("enter", { slide: 24 }) }] : []),
|
|
366
451
|
// No priming to 0 first: the value at `t` is a function of `t`.
|
|
367
452
|
{ at: axes, duration: rise, piece: timeline.animate((t) => ({ growth: t })) },
|
|
368
|
-
...(legend ? [{ at: axes + rise, duration: duration * ENTER_SHARE.legend, piece: legend.to({ opacity: 1, y: 0 }) }] : []),
|
|
453
|
+
...(legend ? [{ at: axes + rise, duration: duration * ENTER_SHARE.legend, piece: legend.to({ opacity: 1, x: 0, y: 0 }) }] : []),
|
|
369
454
|
])
|
|
370
455
|
},
|
|
371
456
|
},
|
|
@@ -375,29 +460,28 @@ export const BarChart = component2d({
|
|
|
375
460
|
easing: easeOut(),
|
|
376
461
|
run: (timeline, state, children) => toggling(timeline, state, children, timeline.args.seriesIndex, 1),
|
|
377
462
|
},
|
|
378
|
-
/** The mirror of `showSeries`:
|
|
463
|
+
/** The mirror of `showSeries`: bars narrow away (stacked, sink out) while the rest widen into the space. */
|
|
379
464
|
hideSeries: {
|
|
380
465
|
args: { seriesIndex: SERIES_ARG },
|
|
381
466
|
easing: easeOut(),
|
|
382
467
|
run: (timeline, state, children) => toggling(timeline, state, children, timeline.args.seriesIndex, 0),
|
|
383
468
|
},
|
|
384
|
-
/** Fade every other series,
|
|
469
|
+
/** Fade every other series, bars and legend entry, to focus on `index`. Nothing moves, so the group does not reflow. */
|
|
385
470
|
spotlightSeries: {
|
|
386
471
|
args: { seriesIndex: SERIES_ARG },
|
|
387
472
|
easing: easeOut(),
|
|
388
|
-
run: (timeline,
|
|
473
|
+
run: (timeline, _state, children) => {
|
|
389
474
|
const parts = partsOf(children)
|
|
390
|
-
if (!parts) return timeline.wait(timeline.duration)
|
|
391
475
|
const index = timeline.args.seriesIndex
|
|
392
|
-
const dim = state.theme.spotlight.dimOpacity
|
|
393
476
|
return timeline.together([
|
|
394
|
-
...parts.bars.map((bars, i) => bars.to({ opacity: i === index ? 1 :
|
|
477
|
+
...parts.bars.map((bars, i) => bars.to({ opacity: i === index ? 1 : SPOTLIGHT_DIM })),
|
|
395
478
|
...(parts.legend ? [parts.legend.run("spotlightSeries", { index })] : []),
|
|
479
|
+
timeline.wait(timeline.duration),
|
|
396
480
|
])
|
|
397
481
|
},
|
|
398
482
|
},
|
|
399
483
|
/**
|
|
400
|
-
* Fade every other category's
|
|
484
|
+
* Fade every other category's bars and caption to {@link SPOTLIGHT_DIM}. While
|
|
401
485
|
* another is lit the focus moves directly. A missing category takes its slot
|
|
402
486
|
* and changes nothing.
|
|
403
487
|
*/
|