@motionscript/charts 0.1.0 → 0.1.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/bar-chart/bar-chart.d.ts +22 -93
- package/dist/bar-chart/bar-chart.d.ts.map +1 -1
- package/dist/bar-chart/bar-chart.js +48 -121
- package/dist/bar-chart/bar-chart.js.map +1 -1
- package/dist/bar-chart/category-axis.d.ts +11 -38
- package/dist/bar-chart/category-axis.d.ts.map +1 -1
- package/dist/bar-chart/category-axis.js +15 -38
- package/dist/bar-chart/category-axis.js.map +1 -1
- package/dist/bar-chart/chart-bars.d.ts +43 -74
- package/dist/bar-chart/chart-bars.d.ts.map +1 -1
- package/dist/bar-chart/chart-bars.js +31 -59
- package/dist/bar-chart/chart-bars.js.map +1 -1
- package/dist/bar-chart/index.d.ts +2 -10
- package/dist/bar-chart/index.d.ts.map +1 -1
- package/dist/bar-chart/index.js +0 -7
- package/dist/bar-chart/index.js.map +1 -1
- package/dist/bar-chart/shared.d.ts +27 -90
- package/dist/bar-chart/shared.d.ts.map +1 -1
- package/dist/bar-chart/shared.js +15 -54
- package/dist/bar-chart/shared.js.map +1 -1
- package/dist/browser/chunks/chunk-4SMRCU24.js +2 -0
- package/dist/browser/chunks/chunk-4SMRCU24.js.map +7 -0
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +3 -3
- package/dist/browser/kit.js +1 -1
- package/dist/browser/manifest.json +1 -1
- package/dist/engine.d.ts +4 -16
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +4 -16
- package/dist/engine.js.map +1 -1
- package/dist/kit/cartesian.d.ts +8 -28
- package/dist/kit/cartesian.d.ts.map +1 -1
- package/dist/kit/cartesian.js +7 -19
- package/dist/kit/cartesian.js.map +1 -1
- package/dist/kit/chart-border.d.ts +9 -18
- package/dist/kit/chart-border.d.ts.map +1 -1
- package/dist/kit/chart-border.js +4 -9
- package/dist/kit/chart-border.js.map +1 -1
- package/dist/kit/chart-canvas.d.ts +25 -36
- package/dist/kit/chart-canvas.d.ts.map +1 -1
- package/dist/kit/chart-canvas.js +13 -13
- package/dist/kit/chart-canvas.js.map +1 -1
- package/dist/kit/chart-legend.d.ts +14 -61
- package/dist/kit/chart-legend.d.ts.map +1 -1
- package/dist/kit/chart-legend.js +17 -65
- 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/motion.d.ts +11 -3
- package/dist/kit/motion.d.ts.map +1 -1
- package/dist/kit/motion.js +7 -1
- package/dist/kit/motion.js.map +1 -1
- package/dist/kit/shared.d.ts +60 -175
- package/dist/kit/shared.d.ts.map +1 -1
- package/dist/kit/shared.js +40 -157
- package/dist/kit/shared.js.map +1 -1
- package/dist/kit/vertical-axis.d.ts +16 -45
- package/dist/kit/vertical-axis.d.ts.map +1 -1
- package/dist/kit/vertical-axis.js +17 -42
- package/dist/kit/vertical-axis.js.map +1 -1
- package/dist/line-chart/chart-line.d.ts +16 -41
- package/dist/line-chart/chart-line.d.ts.map +1 -1
- package/dist/line-chart/chart-line.js +14 -43
- 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 +8 -22
- package/dist/line-chart/chart-region.js.map +1 -1
- package/dist/line-chart/horizontal-axis.d.ts +16 -43
- package/dist/line-chart/horizontal-axis.d.ts.map +1 -1
- package/dist/line-chart/horizontal-axis.js +13 -42
- package/dist/line-chart/horizontal-axis.js.map +1 -1
- package/dist/line-chart/index.d.ts +1 -13
- package/dist/line-chart/index.d.ts.map +1 -1
- package/dist/line-chart/index.js +0 -12
- package/dist/line-chart/index.js.map +1 -1
- package/dist/line-chart/line-chart.d.ts +23 -109
- package/dist/line-chart/line-chart.d.ts.map +1 -1
- package/dist/line-chart/line-chart.js +45 -141
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/line-chart/shared.d.ts +33 -107
- package/dist/line-chart/shared.d.ts.map +1 -1
- package/dist/line-chart/shared.js +28 -81
- package/dist/line-chart/shared.js.map +1 -1
- package/dist/line-chart/x-scale.d.ts +14 -55
- package/dist/line-chart/x-scale.d.ts.map +1 -1
- package/dist/line-chart/x-scale.js +29 -111
- package/dist/line-chart/x-scale.js.map +1 -1
- package/dist/nodes.d.ts +0 -12
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +0 -12
- package/dist/nodes.js.map +1 -1
- package/dist/pie-chart/index.d.ts +1 -12
- package/dist/pie-chart/index.d.ts.map +1 -1
- package/dist/pie-chart/index.js +0 -9
- package/dist/pie-chart/index.js.map +1 -1
- package/dist/pie-chart/pie-chart.d.ts +37 -71
- package/dist/pie-chart/pie-chart.d.ts.map +1 -1
- package/dist/pie-chart/pie-chart.js +31 -65
- package/dist/pie-chart/pie-chart.js.map +1 -1
- package/dist/pie-chart/pie-legend.d.ts +11 -26
- package/dist/pie-chart/pie-legend.d.ts.map +1 -1
- package/dist/pie-chart/pie-legend.js +14 -39
- package/dist/pie-chart/pie-legend.js.map +1 -1
- package/dist/pie-chart/pie-ring.d.ts +50 -192
- package/dist/pie-chart/pie-ring.d.ts.map +1 -1
- package/dist/pie-chart/pie-ring.js +75 -247
- package/dist/pie-chart/pie-ring.js.map +1 -1
- package/dist/pie-chart/shared.d.ts +45 -131
- package/dist/pie-chart/shared.d.ts.map +1 -1
- package/dist/pie-chart/shared.js +29 -72
- package/dist/pie-chart/shared.js.map +1 -1
- package/package.json +4 -4
- package/src/bar-chart/bar-chart.ts +51 -165
- package/src/bar-chart/category-axis.ts +16 -57
- package/src/bar-chart/chart-bars.ts +43 -120
- package/src/bar-chart/index.ts +2 -10
- package/src/bar-chart/shared.ts +29 -97
- package/src/engine.ts +4 -16
- package/src/kit/cartesian.ts +11 -26
- package/src/kit/chart-border.ts +6 -20
- package/src/kit/chart-canvas.ts +15 -40
- package/src/kit/chart-legend.ts +17 -86
- package/src/kit/entries.ts +4 -1
- package/src/kit/motion.ts +11 -2
- package/src/kit/shared.ts +58 -219
- package/src/kit/vertical-axis.ts +18 -71
- package/src/line-chart/chart-line.ts +16 -44
- package/src/line-chart/chart-region.ts +10 -23
- package/src/line-chart/horizontal-axis.ts +13 -42
- package/src/line-chart/index.ts +1 -13
- package/src/line-chart/line-chart.ts +50 -184
- package/src/line-chart/shared.ts +51 -145
- package/src/line-chart/x-scale.ts +31 -121
- package/src/nodes.ts +0 -12
- package/src/pie-chart/index.ts +1 -12
- package/src/pie-chart/pie-chart.ts +34 -102
- package/src/pie-chart/pie-legend.ts +14 -55
- package/src/pie-chart/pie-ring.ts +80 -307
- package/src/pie-chart/shared.ts +50 -139
- package/dist/browser/chunks/chunk-TJNHLQGV.js +0 -2
- package/dist/browser/chunks/chunk-TJNHLQGV.js.map +0 -7
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
Graphics2D,
|
|
3
3
|
Text,
|
|
4
4
|
Types,
|
|
5
|
+
clamp,
|
|
5
6
|
component2d,
|
|
6
7
|
fillOps,
|
|
7
8
|
shadowOps,
|
|
@@ -10,9 +11,9 @@ import {
|
|
|
10
11
|
type Fill,
|
|
11
12
|
type FillResolved,
|
|
12
13
|
type NodeOf,
|
|
13
|
-
// Named so this component's emitted type reaches `Node2D` through the sdk, not core.
|
|
14
|
+
// Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.
|
|
14
15
|
type Node2D as _Node2D,
|
|
15
|
-
type Node2DProps,
|
|
16
|
+
type Node2DProps as _Node2DProps,
|
|
16
17
|
type Shadow,
|
|
17
18
|
type ShadowResolved,
|
|
18
19
|
type Stroke,
|
|
@@ -23,35 +24,28 @@ import {
|
|
|
23
24
|
import { fresh } from "../kit/entries"
|
|
24
25
|
import { frameEase } from "../kit/motion"
|
|
25
26
|
import { loadPaints } from "../kit/paints"
|
|
26
|
-
import {
|
|
27
|
+
import { formatMarker } from "../kit/shared"
|
|
27
28
|
import { DEFAULT_BAR_CHART_THEME } from "./shared"
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
* One drawn column
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* for the same reason {@link BarSpan} is a span: it is the one shape that fits a
|
|
35
|
-
* grouped bar, a stacked segment and a negative value without a special case.
|
|
31
|
+
* One drawn column in the plot's centred pixel space (origin at the centre,
|
|
32
|
+
* y-up), mapped by the parent {@link BarChart}. `from`/`to` are the two ends
|
|
33
|
+
* rather than a base and a height, like {@link BarSpan}: one shape fits grouped,
|
|
34
|
+
* stacked and negative.
|
|
36
35
|
*/
|
|
37
36
|
export interface BarGeometry {
|
|
38
|
-
/** Centre x
|
|
37
|
+
/** Centre x. */
|
|
39
38
|
x: number
|
|
40
|
-
/** Column width in px. */
|
|
41
39
|
width: number
|
|
42
|
-
/** Pixel y of the
|
|
40
|
+
/** Pixel y of the base: the baseline when grouped, the running stack top when stacked. */
|
|
43
41
|
from: number
|
|
44
|
-
/** Pixel y of
|
|
42
|
+
/** Pixel y of the far end at full growth. */
|
|
45
43
|
to: number
|
|
46
|
-
/** Whether the far end takes the
|
|
44
|
+
/** Whether the far end takes the corner radius (the stack's cap). */
|
|
47
45
|
round: boolean
|
|
48
|
-
/** The value
|
|
46
|
+
/** The value plotted, for the printed figure. */
|
|
49
47
|
value: number
|
|
50
|
-
/**
|
|
51
|
-
* The figure printed on it, written as the chart's value axis writes its
|
|
52
|
-
* ticks, so the two cannot disagree. Absent, the bare marker reading of
|
|
53
|
-
* {@link value}.
|
|
54
|
-
*/
|
|
48
|
+
/** The printed figure, written as the value axis writes its ticks; absent, the bare marker reading of `value`. */
|
|
55
49
|
caption?: string
|
|
56
50
|
}
|
|
57
51
|
|
|
@@ -62,49 +56,6 @@ const VALUE_FIT = 1.6
|
|
|
62
56
|
/** Fallback type size when the theme's value style leaves it unset or autofit. */
|
|
63
57
|
const VALUE_SIZE = 24
|
|
64
58
|
|
|
65
|
-
export interface ChartBarsProps extends Node2DProps {
|
|
66
|
-
/** This series' columns, one per row of the chart's data. */
|
|
67
|
-
bars: BarGeometry[]
|
|
68
|
-
/** Pixel y of value 0 — where every column grows *from*. */
|
|
69
|
-
baseline: number
|
|
70
|
-
/** Column paint — this series' own, or the theme palette's entry for its index. */
|
|
71
|
-
fill: Fill
|
|
72
|
-
/** Optional outline on every column. */
|
|
73
|
-
barStroke: Stroke
|
|
74
|
-
/** Optional drop shadow on every column. */
|
|
75
|
-
barShadow: Shadow
|
|
76
|
-
/** Radius of the far-end corners, in px. */
|
|
77
|
-
cornerRadius: number
|
|
78
|
-
/**
|
|
79
|
-
* Rise of every column in `[0, 1]`, measured **about the baseline**: 0
|
|
80
|
-
* collapses them all onto it, 1 draws them at full length.
|
|
81
|
-
*
|
|
82
|
-
* Scaling about the baseline rather than about each column's own base is what
|
|
83
|
-
* keeps a stack flush mid-animation — every segment's base and end travel by
|
|
84
|
-
* the same factor, so the segments stay stacked instead of the upper ones
|
|
85
|
-
* floating above the still-growing lower ones. It also means a negative
|
|
86
|
-
* column grows *down* out of the same signal.
|
|
87
|
-
*/
|
|
88
|
-
growth: number
|
|
89
|
-
/**
|
|
90
|
-
* Per-column opacity multiplier, by row index — how the chart's category
|
|
91
|
-
* spotlight reaches a single column without touching the series it belongs
|
|
92
|
-
* to. Missing entries read as 1.
|
|
93
|
-
*/
|
|
94
|
-
dims: number[]
|
|
95
|
-
/** Whether the figure each column plots is printed on it. */
|
|
96
|
-
showValues: boolean
|
|
97
|
-
/** Type style for those figures. */
|
|
98
|
-
valueStyle: TextStyle
|
|
99
|
-
/**
|
|
100
|
-
* Whether this series draws at all — the wholesale switch behind
|
|
101
|
-
* `BarChart.showSeries`/`hideSeries`, alongside the slot weight that animates
|
|
102
|
-
* the reflow. Off skips the columns *and* their figures.
|
|
103
|
-
*/
|
|
104
|
-
enabled: boolean
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** What the column geometry reads. */
|
|
108
59
|
interface Columns {
|
|
109
60
|
readonly bars: readonly BarGeometry[]
|
|
110
61
|
readonly baseline: number
|
|
@@ -115,51 +66,37 @@ interface Columns {
|
|
|
115
66
|
}
|
|
116
67
|
|
|
117
68
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* The captions read this rather than being hidden by `render`'s guard: they are
|
|
122
|
-
* real child nodes, so a `return` in the draw would leave them on screen over a
|
|
123
|
-
* series that isn't there.
|
|
69
|
+
* Column `i`'s dimming: the category spotlight, then the series switch. Captions
|
|
70
|
+
* read this because they are child nodes, which a `render` guard would leave on
|
|
71
|
+
* screen over a series that is not there.
|
|
124
72
|
*/
|
|
125
73
|
function dimAt(s: Columns, index: number): number {
|
|
126
74
|
if (!s.enabled) return 0
|
|
127
|
-
return
|
|
75
|
+
return clamp(s.dims[index] ?? 1, 0, 1)
|
|
128
76
|
}
|
|
129
77
|
|
|
130
|
-
/** The two ends of column `i` at the current growth. */
|
|
131
78
|
function endsAt(s: Columns, index: number): { base: number; end: number } | null {
|
|
132
79
|
const bar = s.bars[index]
|
|
133
80
|
if (!bar) return null
|
|
134
|
-
const g =
|
|
81
|
+
const g = clamp(s.growth, 0, 1)
|
|
135
82
|
return { base: s.baseline + (bar.from - s.baseline) * g, end: s.baseline + (bar.to - s.baseline) * g }
|
|
136
83
|
}
|
|
137
84
|
|
|
138
|
-
/** The type size the figures are set at, guarding the autofit/unset cases. */
|
|
139
85
|
function valueSize(s: Columns): number {
|
|
140
86
|
const size = s.valueStyle.fontSize
|
|
141
87
|
return typeof size === "number" ? size : VALUE_SIZE
|
|
142
88
|
}
|
|
143
89
|
|
|
144
|
-
/** Whether column `i`'s figure sits inside the column (a stacked segment) or past its end. */
|
|
145
90
|
function insideAt(s: Columns, index: number): boolean {
|
|
146
91
|
const bar = s.bars[index]
|
|
147
92
|
return bar !== undefined && Math.abs(bar.from - s.baseline) > 0.5
|
|
148
93
|
}
|
|
149
94
|
|
|
150
|
-
/**
|
|
151
|
-
* The figure printed on column `i`, or `""` where there is no room for it.
|
|
152
|
-
*
|
|
153
|
-
* Blanking the text rather than dropping the node keeps the caption list
|
|
154
|
-
* index-aligned with the columns, so a chart that rescales into more room simply
|
|
155
|
-
* starts printing again.
|
|
156
|
-
*/
|
|
95
|
+
/** Column `i`'s figure, or `""` where there is no room. Blanking rather than dropping the node keeps captions index-aligned, so a rescale into more room prints again. */
|
|
157
96
|
function captionAt(s: Columns, index: number): string {
|
|
158
97
|
const bar = s.bars[index]
|
|
159
98
|
if (!bar || bar.value === 0) return ""
|
|
160
|
-
// An interior segment prints inside itself
|
|
161
|
-
// hold a line of type; a grouped column prints in the headroom above its end,
|
|
162
|
-
// where the only constraint is the plot's own top.
|
|
99
|
+
// An interior segment prints inside itself and must hold a line of type; a grouped column prints past its end.
|
|
163
100
|
if (insideAt(s, index)) {
|
|
164
101
|
const ends = endsAt(s, index)
|
|
165
102
|
if (!ends) return ""
|
|
@@ -168,53 +105,51 @@ function captionAt(s: Columns, index: number): string {
|
|
|
168
105
|
return bar.caption ?? formatMarker(bar.value)
|
|
169
106
|
}
|
|
170
107
|
|
|
171
|
-
/** Where column `i`'s figure sits, in the node's centred y-up space. */
|
|
172
108
|
function captionY(s: Columns, index: number): number {
|
|
173
109
|
const ends = endsAt(s, index)
|
|
174
110
|
if (!ends) return 0
|
|
175
111
|
if (insideAt(s, index)) return (ends.base + ends.end) / 2
|
|
176
|
-
// Text is centred on
|
|
177
|
-
// line — on whichever side of the baseline the column actually reached.
|
|
112
|
+
// Text is centred on `y`: clear the end by the gap plus half a line, on the side the column reached.
|
|
178
113
|
const direction = ends.end >= ends.base ? 1 : -1
|
|
179
114
|
return ends.end + direction * (VALUE_GAP + valueSize(s) / 2)
|
|
180
115
|
}
|
|
181
116
|
|
|
182
117
|
/**
|
|
183
|
-
* One
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* unit `ChartLine` is, which is what lets `showSeries`/`hideSeries`/
|
|
188
|
-
* `spotlightSeries` mean exactly the same thing on a bar chart as on a line
|
|
189
|
-
* chart: enable the node, or tween its opacity. The columns' *positions* come
|
|
190
|
-
* from the parent (it owns the slot arithmetic both modes share), so this part
|
|
191
|
-
* only has to know how to draw a span and grow it.
|
|
118
|
+
* One series of a {@link BarChart}: its column in every category plus the
|
|
119
|
+
* printed figures. A node per series, like `ChartLine`, so `showSeries` and
|
|
120
|
+
* `spotlightSeries` mean the same on both: enable the node or tween its opacity.
|
|
121
|
+
* Positions come from the parent, which owns the slot arithmetic.
|
|
192
122
|
*
|
|
193
|
-
* The figures are
|
|
194
|
-
*
|
|
195
|
-
* and ride the node's own opacity — which is why hiding the series takes its
|
|
196
|
-
* figures with it for free.
|
|
123
|
+
* The figures are {@link Text} children, so they take the theme's type style,
|
|
124
|
+
* are measured by layout, and hide with the series.
|
|
197
125
|
*/
|
|
198
126
|
export const ChartBars = component2d({
|
|
199
127
|
key: "chartBars",
|
|
200
128
|
state: {
|
|
201
129
|
bars: fresh<BarGeometry[]>(() => []),
|
|
130
|
+
/** Pixel y of value 0, where every column grows from. */
|
|
202
131
|
baseline: 0,
|
|
203
132
|
fill: Types.custom<Fill, FillResolved[]>([], { resolve: fillOps.resolve, lerp: fillOps.lerp }),
|
|
204
133
|
barStroke: Types.custom<Stroke, StrokeResolved[]>(DEFAULT_BAR_CHART_THEME.bar.stroke, { resolve: strokeOps.resolve, lerp: strokeOps.lerp }),
|
|
205
134
|
barShadow: Types.custom<Shadow, ShadowResolved[]>(DEFAULT_BAR_CHART_THEME.bar.shadow, { resolve: shadowOps.resolve, lerp: shadowOps.lerp }),
|
|
206
135
|
cornerRadius: DEFAULT_BAR_CHART_THEME.bar.cornerRadius,
|
|
136
|
+
/**
|
|
137
|
+
* Rise of every column in `[0, 1]` about the baseline, not each column's own
|
|
138
|
+
* base: every segment travels by the same factor, so a stack stays flush
|
|
139
|
+
* mid-animation and a negative column grows down.
|
|
140
|
+
*/
|
|
207
141
|
growth: 1,
|
|
142
|
+
/** Per-column opacity by row index, how the category spotlight reaches one column. Missing reads as 1. */
|
|
208
143
|
dims: fresh<number[]>(() => []),
|
|
209
144
|
showValues: false,
|
|
210
145
|
valueStyle: Types.custom<TextStyle>(DEFAULT_BAR_CHART_THEME.valueStyle),
|
|
146
|
+
/** The wholesale switch behind `showSeries`/`hideSeries`; off skips the columns and their figures. */
|
|
211
147
|
enabled: true,
|
|
212
148
|
},
|
|
213
|
-
// A
|
|
149
|
+
// A node made of its figures would otherwise hug them.
|
|
214
150
|
defaults: { width: "fill", height: "fill" },
|
|
215
|
-
// One caption per column, built once (the row count is fixed at construction
|
|
216
|
-
//
|
|
217
|
-
// with its column as the chart grows, rescales or reflows.
|
|
151
|
+
// One caption per column, built once (the row count is fixed at construction)
|
|
152
|
+
// and bound to the live geometry so it travels with its column.
|
|
218
153
|
children: (state) => {
|
|
219
154
|
if (!state.showValues) return []
|
|
220
155
|
return state.bars.map((_, i) =>
|
|
@@ -227,18 +162,11 @@ export const ChartBars = component2d({
|
|
|
227
162
|
})
|
|
228
163
|
)
|
|
229
164
|
},
|
|
230
|
-
// The
|
|
165
|
+
// The stroke and shadow sit past the conventional slots, so they are named here.
|
|
231
166
|
load: (loader, state) =>
|
|
232
167
|
loadPaints(loader, { fills: [state.fill], shadows: [state.barShadow], strokes: [state.barStroke] }),
|
|
233
168
|
commands: {
|
|
234
|
-
/**
|
|
235
|
-
* Raise every column from the baseline by tweening `growth` `0 → 1`.
|
|
236
|
-
*
|
|
237
|
-
* The mirror of `ChartLine.enter`: same contract that the resting state is
|
|
238
|
-
* the finished one, so the chart arms this back to 0 before animating rather
|
|
239
|
-
* than the node hiding itself at construction. No priming to 0 first: the
|
|
240
|
-
* value at `t` is a function of `t`, so a repeat call re-raises by definition.
|
|
241
|
-
*/
|
|
169
|
+
/** Raise every column from the baseline (`growth` 0 to 1). The resting state is the finished one; the chart arms it back to 0. */
|
|
242
170
|
enter: (timeline) => {
|
|
243
171
|
const ease = frameEase(timeline)
|
|
244
172
|
return timeline.animate((t) => ({ growth: ease(t) }))
|
|
@@ -254,19 +182,14 @@ export const ChartBars = component2d({
|
|
|
254
182
|
if (height <= 0) continue
|
|
255
183
|
|
|
256
184
|
const radius = bar.round ? Math.min(state.cornerRadius, height) : 0
|
|
257
|
-
//
|
|
258
|
-
// category spotlight dims individual columns, and opacity is a property
|
|
259
|
-
// of the command list rather than of a shape within it.
|
|
185
|
+
// One `Graphics2D` per column: opacity belongs to the command list, and the spotlight dims single columns.
|
|
260
186
|
const g = new Graphics2D()
|
|
261
187
|
const rect = g.rect({
|
|
262
188
|
x: bar.x,
|
|
263
189
|
y: (ends.base + ends.end) / 2,
|
|
264
190
|
width: bar.width,
|
|
265
191
|
height,
|
|
266
|
-
// Corner keys are visual (
|
|
267
|
-
// so a column reaching *up* rounds its `top*` corners and one reaching
|
|
268
|
-
// down its `bottom*` ones. Only the far end rounds, which is what makes
|
|
269
|
-
// a stack read as a single capped column.
|
|
192
|
+
// Corner keys are visual (y-up): a column reaching up rounds `top*`, one reaching down `bottom*`. Only the far end rounds.
|
|
270
193
|
cornerRadius:
|
|
271
194
|
radius > 0
|
|
272
195
|
? ends.end >= ends.base
|
package/src/bar-chart/index.ts
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The bar chart: grouped or stacked columns off a category axis.
|
|
3
|
-
*
|
|
4
|
-
* Shares the plot panel, the value axis and the legend with the line chart —
|
|
5
|
-
* which is what lets a scene swap one for the other without re-theming — and
|
|
6
|
-
* brings its own category axis, since a band scale is not a continuous one.
|
|
7
|
-
*/
|
|
8
1
|
export { BarChart, barChartDomain } from "./bar-chart"
|
|
9
|
-
export type { BarChartDomain,
|
|
2
|
+
export type { BarChartDomain, BarChartValues } from "./bar-chart"
|
|
10
3
|
export { ChartBars } from "./chart-bars"
|
|
11
|
-
export type { BarGeometry
|
|
4
|
+
export type { BarGeometry } from "./chart-bars"
|
|
12
5
|
export { CategoryAxis } from "./category-axis"
|
|
13
|
-
export type { CategoryAxisProps } from "./category-axis"
|
|
14
6
|
export {
|
|
15
7
|
DEFAULT_BAR_CHART_THEME,
|
|
16
8
|
barCategories,
|
package/src/bar-chart/shared.ts
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Types and pure helpers specific to the {@link BarChart} family: how a row's
|
|
3
|
-
* values become the spans its columns cover, how the value axis is scaled around
|
|
4
|
-
* them, and the theme the bars add to the shared chart vocabulary.
|
|
5
|
-
*
|
|
6
|
-
* Everything a line or a pie chart would want too — what a series is, the axis
|
|
7
|
-
* domain model, the theme leaf resolvers, the legend and plot-panel branches —
|
|
8
|
-
* lives in `../kit/shared.ts` and is imported here.
|
|
9
|
-
*
|
|
10
|
-
* Kept renderer-agnostic (no `Node` imports) so a host's inspector mapping
|
|
11
|
-
* can build a partial {@link BarChartTheme} without
|
|
12
|
-
* pulling the components in.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
1
|
import {
|
|
16
2
|
fillOps,
|
|
17
3
|
lerpNumber,
|
|
@@ -26,6 +12,7 @@ import {
|
|
|
26
12
|
type StrokeResolved,
|
|
27
13
|
type TextStyle,
|
|
28
14
|
} from "@motionscript/sdk"
|
|
15
|
+
import { niceStep } from "@motionscript/sdk/component"
|
|
29
16
|
|
|
30
17
|
import {
|
|
31
18
|
CHART_PALETTE,
|
|
@@ -39,7 +26,6 @@ import {
|
|
|
39
26
|
lerpLegendConfig,
|
|
40
27
|
lerpPlotArea,
|
|
41
28
|
lerpSeriesFills,
|
|
42
|
-
niceStep,
|
|
43
29
|
num,
|
|
44
30
|
resolveAxisConfig,
|
|
45
31
|
resolveLegendConfig,
|
|
@@ -54,41 +40,25 @@ import {
|
|
|
54
40
|
type Range,
|
|
55
41
|
} from "../kit/shared"
|
|
56
42
|
|
|
57
|
-
/**
|
|
58
|
-
* One series in a {@link BarChart}: a column of the table drawn as one bar per
|
|
59
|
-
* row — side by side with its siblings within each category's slot, or stacked
|
|
60
|
-
* on top of them.
|
|
61
|
-
*
|
|
62
|
-
* The family-wide {@link ChartSeries} under a name that says what it draws here.
|
|
63
|
-
*/
|
|
43
|
+
/** One bar per row, beside its siblings in the category slot or stacked on them: {@link ChartSeries} under the name for what it draws here. */
|
|
64
44
|
export type BarSeries = ChartSeries
|
|
65
45
|
|
|
66
46
|
/**
|
|
67
|
-
* One
|
|
68
|
-
* value
|
|
69
|
-
*
|
|
70
|
-
* A span rather than a height because that is the one shape both modes fit. A
|
|
71
|
-
* grouped bar runs from zero to its value; a stacked segment runs from the
|
|
72
|
-
* running total below it to that total plus its own value. Negative values fall
|
|
73
|
-
* out of the same arithmetic — the span simply runs the other way — so a chart
|
|
74
|
-
* with a loss in it needs no special case, only a baseline that isn't the floor.
|
|
47
|
+
* One bar's span in data units. A span rather than a height because both modes
|
|
48
|
+
* fit it, and a negative value just runs the other way with no special case.
|
|
75
49
|
*/
|
|
76
50
|
export interface BarSpan {
|
|
77
|
-
/**
|
|
51
|
+
/** Where the base sits: 0 when grouped, the running total below when stacked. */
|
|
78
52
|
from: number
|
|
79
|
-
/** Value its far end reaches. */
|
|
80
53
|
to: number
|
|
81
|
-
/**
|
|
54
|
+
/** `to - from`, kept for the figure printed on the bar. */
|
|
82
55
|
value: number
|
|
83
56
|
}
|
|
84
57
|
|
|
85
58
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* the order they're drawn: series 0 sits on the baseline and each later one
|
|
90
|
-
* rides on what's below it. A zero-valued series still yields a (degenerate)
|
|
91
|
-
* span so the returned list stays index-aligned with the series list.
|
|
59
|
+
* One row's spans, in series order. Stacking is a running sum in listed order,
|
|
60
|
+
* so series 0 sits on the baseline. A zero value still yields a degenerate span,
|
|
61
|
+
* keeping the list index-aligned with the series.
|
|
92
62
|
*/
|
|
93
63
|
export function barSpans(
|
|
94
64
|
row: DataRecord,
|
|
@@ -106,13 +76,9 @@ export function barSpans(
|
|
|
106
76
|
}
|
|
107
77
|
|
|
108
78
|
/**
|
|
109
|
-
* The
|
|
110
|
-
* the
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* Read off the *last non-zero* series rather than the last one listed, so a
|
|
114
|
-
* series that happens to be 0 in this row doesn't take the cap and leave the
|
|
115
|
-
* segment below it square.
|
|
79
|
+
* The series ending a row's stack, which gets the rounded cap; `-1` when the row
|
|
80
|
+
* is all zero. It is the last non-zero series, so a zero one does not take the
|
|
81
|
+
* cap and leave the segment below square.
|
|
116
82
|
*/
|
|
117
83
|
export function stackCapIndex(row: DataRecord, series: BarSeries[]): number {
|
|
118
84
|
let last = -1
|
|
@@ -122,18 +88,14 @@ export function stackCapIndex(row: DataRecord, series: BarSeries[]): number {
|
|
|
122
88
|
return last
|
|
123
89
|
}
|
|
124
90
|
|
|
125
|
-
/** The captions beneath the baseline: one per row, read off the category column. */
|
|
126
91
|
export function barCategories(data: DataRecord[], field: string): string[] {
|
|
127
92
|
return data.map((row) => text(row, field))
|
|
128
93
|
}
|
|
129
94
|
|
|
130
95
|
/**
|
|
131
|
-
* The `[min, max]` of everything
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* zero: a scale starting at 40 draws a column three times its neighbour for a
|
|
135
|
-
* 10% difference, which is the classic way to lie with one. A chart that really
|
|
136
|
-
* does want a cropped axis says so with an explicit range.
|
|
96
|
+
* The `[min, max]` of everything drawn, zero always included: a scale starting
|
|
97
|
+
* at 40 draws a column three times its neighbour for a 10% difference. A cropped
|
|
98
|
+
* axis takes an explicit range.
|
|
137
99
|
*/
|
|
138
100
|
export function barValueExtent(
|
|
139
101
|
data: DataRecord[],
|
|
@@ -151,14 +113,7 @@ export function barValueExtent(
|
|
|
151
113
|
return lo < hi ? [lo, hi] : [0, 1]
|
|
152
114
|
}
|
|
153
115
|
|
|
154
|
-
/**
|
|
155
|
-
* Effective value scale: the explicit `range`, else the data's extent nudged out
|
|
156
|
-
* to tidy step bounds so the markers read as round numbers.
|
|
157
|
-
*
|
|
158
|
-
* `divisions` is how many segments the auto-range is stepped into (one fewer
|
|
159
|
-
* than the marker count), so the ticks the axis draws land on the bounds this
|
|
160
|
-
* picks.
|
|
161
|
-
*/
|
|
116
|
+
/** The explicit `range`, else the data's extent nudged to tidy bounds; `divisions` (one fewer than the marker count) makes the drawn ticks land on them. */
|
|
162
117
|
export function resolveBarScale(
|
|
163
118
|
data: DataRecord[],
|
|
164
119
|
series: BarSeries[],
|
|
@@ -173,50 +128,36 @@ export function resolveBarScale(
|
|
|
173
128
|
return [Math.floor(lo / step) * step, Math.ceil(hi / step) * step]
|
|
174
129
|
}
|
|
175
130
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
/** How the columns themselves are drawn and how the slot is divided up. */
|
|
131
|
+
/** How the columns are drawn and the slot divided. */
|
|
179
132
|
export interface BarStyleConfig {
|
|
180
|
-
/**
|
|
181
|
-
* Radius of a column's far-end corners, in px. Only the outer end rounds — an
|
|
182
|
-
* interior stacked segment stays square, so the stack reads as one column.
|
|
183
|
-
*/
|
|
133
|
+
/** Radius in px of a column's far-end corners; an interior stacked segment stays square. */
|
|
184
134
|
cornerRadius: number
|
|
185
|
-
/**
|
|
186
|
-
* Fraction of its category's slot the bars fill, `[0, 1]`. The remainder is
|
|
187
|
-
* the channel between one category and the next, which is what makes a bar
|
|
188
|
-
* chart read as groups rather than as a solid block.
|
|
189
|
-
*/
|
|
135
|
+
/** Fraction of the category slot the bars fill, `[0, 1]`; the rest separates groups. */
|
|
190
136
|
slotFill: number
|
|
191
|
-
/** Gap between
|
|
137
|
+
/** Gap in px between grouped bars within a slot; ignored when stacked. */
|
|
192
138
|
groupGap: number
|
|
193
|
-
/** Optional outline on every column. */
|
|
194
139
|
stroke: Stroke
|
|
195
|
-
/** Optional drop shadow on every column. */
|
|
196
140
|
shadow: Shadow
|
|
197
141
|
}
|
|
198
142
|
|
|
199
143
|
/** A {@link BarChart}'s full, resolved visual theme. */
|
|
200
144
|
export interface BarChartConfig {
|
|
201
|
-
/**
|
|
202
|
-
* Default per-series bar paint, cycled by series index (`i % length`). A
|
|
203
|
-
* series' own `fill`, when set, replaces its entry outright.
|
|
204
|
-
*/
|
|
145
|
+
/** Cycled by series index; a series' own `fill` replaces its entry outright. */
|
|
205
146
|
seriesFills: Fill[]
|
|
206
147
|
bar: BarStyleConfig
|
|
207
|
-
/** The category axis
|
|
148
|
+
/** The category axis. */
|
|
208
149
|
xAxis: AxisConfig
|
|
209
|
-
/** The
|
|
150
|
+
/** The value axis. */
|
|
210
151
|
yAxis: AxisConfig
|
|
211
152
|
plotArea: PlotAreaConfig
|
|
212
153
|
legend: LegendConfig
|
|
213
|
-
/** The figure printed on each column
|
|
154
|
+
/** The figure printed on each column. */
|
|
214
155
|
valueStyle: TextStyle
|
|
215
|
-
/** How far everything
|
|
156
|
+
/** How far everything outside a spotlit series or category fades. */
|
|
216
157
|
spotlight: { dimOpacity: number }
|
|
217
158
|
}
|
|
218
159
|
|
|
219
|
-
/**
|
|
160
|
+
/** The author's partial theme, deep-merged onto {@link DEFAULT_BAR_CHART_THEME}. */
|
|
220
161
|
export type BarChartTheme = {
|
|
221
162
|
seriesFills?: Fill[]
|
|
222
163
|
bar?: Partial<BarStyleConfig>
|
|
@@ -228,7 +169,6 @@ export type BarChartTheme = {
|
|
|
228
169
|
spotlight?: Partial<BarChartConfig["spotlight"]>
|
|
229
170
|
}
|
|
230
171
|
|
|
231
|
-
/** The bar chart's out-of-the-box look. */
|
|
232
172
|
export const DEFAULT_BAR_CHART_THEME: BarChartConfig = {
|
|
233
173
|
seriesFills: DEFAULT_SERIES_FILLS,
|
|
234
174
|
bar: {
|
|
@@ -238,9 +178,7 @@ export const DEFAULT_BAR_CHART_THEME: BarChartConfig = {
|
|
|
238
178
|
stroke: [],
|
|
239
179
|
shadow: [],
|
|
240
180
|
},
|
|
241
|
-
//
|
|
242
|
-
// rather than like a numeric marker: same size, less weight than the line
|
|
243
|
-
// chart's ticks, and centred on the slot.
|
|
181
|
+
// Captions rather than numeric markers: less weight than the line chart's ticks.
|
|
244
182
|
xAxis: {
|
|
245
183
|
...DEFAULT_X_AXIS,
|
|
246
184
|
labelStyle: { ...DEFAULT_X_AXIS.labelStyle, fontWeight: 600 },
|
|
@@ -293,12 +231,7 @@ function lerpBarStyle(
|
|
|
293
231
|
}
|
|
294
232
|
}
|
|
295
233
|
|
|
296
|
-
/**
|
|
297
|
-
* {@link BarChart}'s `theme` entry's resolve: merges the author's partial
|
|
298
|
-
* {@link BarChartTheme} onto `previous` (falling back to
|
|
299
|
-
* {@link DEFAULT_BAR_CHART_THEME} on first resolution), fully resolving every
|
|
300
|
-
* leaf so the tween below always has resolved data to interpolate.
|
|
301
|
-
*/
|
|
234
|
+
/** The `theme` entry's resolve: merges a partial {@link BarChartTheme} onto `previous` (first resolution: the default). */
|
|
302
235
|
export function resolveBarChartTheme(
|
|
303
236
|
theme: BarChartTheme | undefined,
|
|
304
237
|
previous?: BarChartConfig
|
|
@@ -318,7 +251,6 @@ export function resolveBarChartTheme(
|
|
|
318
251
|
}
|
|
319
252
|
}
|
|
320
253
|
|
|
321
|
-
/** {@link BarChart}'s `theme` entry's lerp — leaf by leaf. */
|
|
322
254
|
export function lerpBarChartTheme(
|
|
323
255
|
from: BarChartConfig,
|
|
324
256
|
to: BarChartConfig,
|
|
@@ -342,7 +274,7 @@ export function lerpBarChartTheme(
|
|
|
342
274
|
}
|
|
343
275
|
}
|
|
344
276
|
|
|
345
|
-
/**
|
|
277
|
+
/** Series `index`'s own `fill`, else the palette's entry. */
|
|
346
278
|
export function resolveBarFill(
|
|
347
279
|
palette: Fill[],
|
|
348
280
|
series: BarSeries,
|
package/src/engine.ts
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The chart
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* reach for these to `@motionscript/charts`, so the plot panel, the value
|
|
7
|
-
* axis, the legend and the number formatting stay a real dependency. That is
|
|
8
|
-
* not a size decision — it is the only arrangement that works.
|
|
9
|
-
*
|
|
10
|
-
* A chart identifies its own parts by component identity (`VerticalAxis.is`).
|
|
11
|
-
* Copy `VerticalAxis` alongside your fork and there are now two of it — two
|
|
12
|
-
* components answering one name, and whichever one your chart did not get is
|
|
13
|
-
* a part it never finds, with no error. Keeping the kit external makes that
|
|
14
|
-
* impossible rather than unlikely.
|
|
15
|
-
*
|
|
16
|
-
* It also means a fix to the axis reaches a forked chart without a re-add,
|
|
17
|
-
* which is the half of the bargain you get back for it.
|
|
2
|
+
* The chart kit: what the three charts share, and what a copied chart does not
|
|
3
|
+
* copy. A chart finds its parts by component identity (`VerticalAxis.is`), so a
|
|
4
|
+
* second copy of `VerticalAxis` would be a second component that the chart never
|
|
5
|
+
* finds, with no error. Copies import the kit from `@motionscript/charts`.
|
|
18
6
|
*/
|
|
19
7
|
export * from "./kit/shared"
|
|
20
8
|
export * from "./kit/cartesian"
|
package/src/kit/cartesian.ts
CHANGED
|
@@ -3,21 +3,15 @@ import { Rect, type ChildView, type Node2D, type Size2D } from "@motionscript/sd
|
|
|
3
3
|
import { VerticalAxis } from "./vertical-axis"
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Headroom above the plot, in px.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* than a number the point-mapping math imagines: a series peaking at the top of
|
|
10
|
-
* the scale isn't flush to the top edge, **and** the top marker, which is
|
|
11
|
-
* centred on that top grid line, has somewhere to be. Without it the marker's
|
|
12
|
-
* upper half is drawn above the chart's own box.
|
|
6
|
+
* Headroom above the plot, in px. Real padding on the composition, so the top
|
|
7
|
+
* marker, centred on the top grid line, does not draw its upper half above the
|
|
8
|
+
* chart's own box.
|
|
13
9
|
*/
|
|
14
|
-
|
|
10
|
+
const TOP_HEADROOM = 24
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
export interface CartesianParts {
|
|
18
|
-
/** The value axis, left of the plot. */
|
|
12
|
+
interface CartesianParts {
|
|
19
13
|
readonly axis: Node2D
|
|
20
|
-
/** The
|
|
14
|
+
/** The box the grid and the marks share. */
|
|
21
15
|
readonly plot: Node2D
|
|
22
16
|
/** An invisible twin of the value axis, reserving its gutter under the plot. */
|
|
23
17
|
readonly gutter: Node2D
|
|
@@ -25,11 +19,7 @@ export interface CartesianParts {
|
|
|
25
19
|
readonly below: readonly Node2D[]
|
|
26
20
|
}
|
|
27
21
|
|
|
28
|
-
/**
|
|
29
|
-
* The frame the line and bar charts share: the value axis beside the plot, and
|
|
30
|
-
* under them the gutter beside the rows below the plot, so those rows start at
|
|
31
|
-
* the plot's left edge rather than the chart's.
|
|
32
|
-
*/
|
|
22
|
+
/** The frame the line and bar charts share: the value axis beside the plot, and under them the gutter, so the rows below start at the plot's left edge. */
|
|
33
23
|
export function cartesianFrame(parts: CartesianParts): Node2D {
|
|
34
24
|
return new Rect({
|
|
35
25
|
flow: "vertical",
|
|
@@ -47,19 +37,14 @@ export function cartesianFrame(parts: CartesianParts): Node2D {
|
|
|
47
37
|
}) as unknown as Node2D
|
|
48
38
|
}
|
|
49
39
|
|
|
50
|
-
|
|
51
|
-
export interface CartesianViews {
|
|
40
|
+
interface CartesianViews {
|
|
52
41
|
readonly axis: ChildView
|
|
53
|
-
/** The plot panel's own children:
|
|
42
|
+
/** The plot panel's own children: grid, marks, border. */
|
|
54
43
|
readonly plot: readonly ChildView[]
|
|
55
44
|
readonly below: readonly ChildView[]
|
|
56
45
|
}
|
|
57
46
|
|
|
58
|
-
/**
|
|
59
|
-
* The frame among a chart's children: the last child shaped as
|
|
60
|
-
* {@link cartesianFrame} builds it. The chart builds it after the children it
|
|
61
|
-
* was given, and a document adds its own later still, so it is found by shape.
|
|
62
|
-
*/
|
|
47
|
+
/** The last child shaped as {@link cartesianFrame} builds it: the chart builds it after the children it was given and a document adds its own later, so it is found by shape. */
|
|
63
48
|
export function cartesianViews(children: readonly ChildView[]): CartesianViews | null {
|
|
64
49
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
65
50
|
const frame = children[i]
|
|
@@ -74,7 +59,7 @@ export function cartesianViews(children: readonly ChildView[]): CartesianViews |
|
|
|
74
59
|
return null
|
|
75
60
|
}
|
|
76
61
|
|
|
77
|
-
/** A part's laid-out size
|
|
62
|
+
/** A part's laid-out size; zero before it is built or laid out. */
|
|
78
63
|
export function laidOutSize(node: Node2D | undefined): Size2D {
|
|
79
64
|
const bounds = node?.layoutBounds
|
|
80
65
|
return { width: bounds?.width ?? 0, height: bounds?.height ?? 0 }
|