@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
package/src/kit/chart-border.ts
CHANGED
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
component2d,
|
|
5
5
|
strokeOps,
|
|
6
6
|
type NodeOf,
|
|
7
|
-
// Named so this component's emitted type reaches `Node2D` through the sdk, not core.
|
|
7
|
+
// Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.
|
|
8
8
|
type Node2D as _Node2D,
|
|
9
|
-
type Node2DProps,
|
|
9
|
+
type Node2DProps as _Node2DProps,
|
|
10
10
|
type Stroke,
|
|
11
11
|
type StrokeResolved,
|
|
12
12
|
} from "@motionscript/sdk"
|
|
@@ -14,35 +14,21 @@ import {
|
|
|
14
14
|
import { loadPaints } from "./paints"
|
|
15
15
|
import { DEFAULT_PLOT_AREA } from "./shared"
|
|
16
16
|
|
|
17
|
-
export interface ChartBorderProps extends Node2DProps {
|
|
18
|
-
/**
|
|
19
|
-
* The plot's thick frame stroke. The stroke resolver's default `align` is
|
|
20
|
-
* `'inside'`, so the whole weight sits within the layout box unless the theme
|
|
21
|
-
* overrides it.
|
|
22
|
-
*/
|
|
23
|
-
stroke: Stroke
|
|
24
|
-
}
|
|
25
|
-
|
|
26
17
|
/**
|
|
27
|
-
* The
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* Split out of {@link ChartCanvas} (the filled + gridded backdrop) so it can be
|
|
31
|
-
* placed *after* the marks in the tree — the box then frames the plot **over**
|
|
32
|
-
* them, so a line or a column running to the edge tucks cleanly under the border
|
|
33
|
-
* rather than sitting on top of it.
|
|
18
|
+
* The frame around a cartesian plot: an empty rect with an inside stroke. Split
|
|
19
|
+
* from {@link ChartCanvas} so it is placed after the marks and frames the plot
|
|
20
|
+
* over them, tucking a line or column running to the edge under the border.
|
|
34
21
|
*/
|
|
35
22
|
export const ChartBorder = component2d({
|
|
36
23
|
key: "chartBorder",
|
|
37
24
|
state: {
|
|
25
|
+
/** The stroke resolver's default `align` is `'inside'`, so the whole weight sits within the box unless the theme overrides it. */
|
|
38
26
|
stroke: Types.custom<Stroke, StrokeResolved[]>(DEFAULT_PLOT_AREA.stroke, { resolve: strokeOps.resolve, lerp: strokeOps.lerp }),
|
|
39
27
|
},
|
|
40
28
|
load: (loader, state) => loadPaints(loader, { strokes: [state.stroke] }),
|
|
41
29
|
render: (ctx, state) => {
|
|
42
30
|
const { width, height } = ctx.size
|
|
43
31
|
if (width <= 0 || height <= 0) return
|
|
44
|
-
// Empty rect (no fill) with the themed stroke, so the frame sits over the
|
|
45
|
-
// marks but the box interior stays clear.
|
|
46
32
|
ctx.draw(new Graphics2D().rect({ x: 0, y: 0, width, height }).stroke(state.stroke))
|
|
47
33
|
},
|
|
48
34
|
})
|
package/src/kit/chart-canvas.ts
CHANGED
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
type Fill,
|
|
9
9
|
type FillResolved,
|
|
10
10
|
type NodeOf,
|
|
11
|
-
// Named so this component's emitted type reaches `Node2D` through the sdk, not core.
|
|
11
|
+
// Named so this component's emitted type reaches `Node2D` and its props through the sdk, not core.
|
|
12
12
|
type Node2D as _Node2D,
|
|
13
|
-
type Node2DProps,
|
|
13
|
+
type Node2DProps as _Node2DProps,
|
|
14
14
|
type Shadow,
|
|
15
15
|
type ShadowResolved,
|
|
16
16
|
type Stroke,
|
|
@@ -26,44 +26,14 @@ import {
|
|
|
26
26
|
type Range,
|
|
27
27
|
} from "./shared"
|
|
28
28
|
|
|
29
|
-
export interface ChartCanvasProps extends Node2DProps {
|
|
30
|
-
/** Fill of the plot panel behind the marks. */
|
|
31
|
-
fill: Fill
|
|
32
|
-
/** Drop shadow of the panel. Default none. */
|
|
33
|
-
shadow: Shadow
|
|
34
|
-
/** Stroke of the vertical grid lines (one per {@link xMarkers}). */
|
|
35
|
-
xGridStroke: Stroke
|
|
36
|
-
/** Stroke of the horizontal grid lines (one per {@link yMarkers}). */
|
|
37
|
-
yGridStroke: Stroke
|
|
38
|
-
/**
|
|
39
|
-
* Data-unit x positions to draw a vertical grid line at — one per horizontal
|
|
40
|
-
* axis marker, so a line lands under every marker regardless of spacing.
|
|
41
|
-
* Positions at (or past) an {@link xExtent} edge coincide with the border and
|
|
42
|
-
* are skipped. Pass an empty list for a chart whose horizontal axis is
|
|
43
|
-
* categorical and so has no numeric position to rule at.
|
|
44
|
-
*/
|
|
45
|
-
xMarkers: number[]
|
|
46
|
-
/** Data-unit y positions to draw a horizontal grid line at. */
|
|
47
|
-
yMarkers: number[]
|
|
48
|
-
/** `[min, max]` of the horizontal scale — maps {@link xMarkers} into the panel. */
|
|
49
|
-
xExtent: Range
|
|
50
|
-
/** `[min, max]` of the vertical scale — maps {@link yMarkers} into the panel. */
|
|
51
|
-
yExtent: Range
|
|
52
|
-
}
|
|
53
|
-
|
|
54
29
|
const strokeEntry = (initial: Stroke) =>
|
|
55
30
|
Types.custom<Stroke, StrokeResolved[]>(initial, { resolve: strokeOps.resolve, lerp: strokeOps.lerp })
|
|
56
31
|
|
|
57
32
|
/**
|
|
58
|
-
* The plot
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* The thick frame is a **separate** {@link ChartBorder} node so it can be drawn
|
|
63
|
-
* *over* the marks while this panel sits under them. Grid lines are placed from
|
|
64
|
-
* the axis marker positions (in data units) mapped into the panel through
|
|
65
|
-
* {@link ChartCanvasProps.xExtent}/`yExtent` — so each line lands under its
|
|
66
|
-
* marker whether or not the markers are evenly spaced.
|
|
33
|
+
* The plot backdrop: a filled panel and a grid, sized from its box. The frame is
|
|
34
|
+
* a separate {@link ChartBorder} drawn over the marks. Grid lines are placed from
|
|
35
|
+
* the axis markers' data-unit positions mapped through the extents, so each lands
|
|
36
|
+
* under its marker however the markers are spaced.
|
|
67
37
|
*/
|
|
68
38
|
export const ChartCanvas = component2d({
|
|
69
39
|
key: "chartCanvas",
|
|
@@ -72,27 +42,32 @@ export const ChartCanvas = component2d({
|
|
|
72
42
|
shadow: Types.custom<Shadow, ShadowResolved[]>(DEFAULT_PLOT_AREA.shadow, { resolve: shadowOps.resolve, lerp: shadowOps.lerp }),
|
|
73
43
|
xGridStroke: strokeEntry(DEFAULT_X_AXIS.lineStyle),
|
|
74
44
|
yGridStroke: strokeEntry(DEFAULT_Y_AXIS.lineStyle),
|
|
45
|
+
/**
|
|
46
|
+
* Data-unit positions of the vertical grid lines, one per horizontal marker.
|
|
47
|
+
* Positions at or past an extent edge coincide with the border and are
|
|
48
|
+
* skipped. Empty for a categorical axis, which has no numeric position to rule at.
|
|
49
|
+
*/
|
|
75
50
|
xMarkers: fresh<number[]>(() => []),
|
|
76
51
|
yMarkers: fresh<number[]>(() => []),
|
|
52
|
+
/** `[min, max]` of each scale, mapping the markers into the panel. */
|
|
77
53
|
xExtent: fresh<Range>(() => [0, 1]),
|
|
78
54
|
yExtent: fresh<Range>(() => [0, 1]),
|
|
79
55
|
},
|
|
80
|
-
// The grid strokes
|
|
56
|
+
// The grid strokes sit past the conventional slots, so they are named here.
|
|
81
57
|
load: (loader, state) =>
|
|
82
58
|
loadPaints(loader, { fills: [state.fill], shadows: [state.shadow], strokes: [state.xGridStroke, state.yGridStroke] }),
|
|
83
59
|
render: (ctx, state) => {
|
|
84
60
|
const { width: W, height: H } = ctx.size
|
|
85
61
|
if (W <= 0 || H <= 0) return
|
|
86
62
|
|
|
87
|
-
// Plot panel, centred on the origin (node space is y-up, origin centre).
|
|
88
63
|
ctx.draw(new Graphics2D().rect({ x: 0, y: 0, width: W, height: H }).shadow(state.shadow).fill(state.fill))
|
|
89
64
|
|
|
90
65
|
const [xMin, xMax] = state.xExtent
|
|
91
66
|
const [yMin, yMax] = state.yExtent
|
|
92
|
-
//
|
|
67
|
+
// A zero-width extent maps to 0.
|
|
93
68
|
const xFrac = (v: number) => (xMax <= xMin ? 0 : (v - xMin) / (xMax - xMin))
|
|
94
69
|
const yFrac = (v: number) => (yMax <= yMin ? 0 : (v - yMin) / (yMax - yMin))
|
|
95
|
-
//
|
|
70
|
+
// Markers on or past the edges would sit under the border.
|
|
96
71
|
const interior = (f: number) => f > 1e-6 && f < 1 - 1e-6
|
|
97
72
|
|
|
98
73
|
const xStroke = state.xGridStroke[0]
|
package/src/kit/chart-legend.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
textOps,
|
|
10
10
|
type Fill,
|
|
11
11
|
type NodeOf,
|
|
12
|
-
type RectProps,
|
|
13
12
|
// Named so the row's emitted type reaches the Rect surface it spreads through the sdk, not core.
|
|
14
13
|
type Alignment as _Alignment,
|
|
15
14
|
type CornerRadiusResolved as _CornerRadiusResolved,
|
|
@@ -38,36 +37,13 @@ import {
|
|
|
38
37
|
type LegendConfig,
|
|
39
38
|
} from "./shared"
|
|
40
39
|
|
|
41
|
-
export interface ChartLegendProps extends RectProps {
|
|
42
|
-
/**
|
|
43
|
-
* One entry per series: legend `label`, its `enabled` default, and — through
|
|
44
|
-
* {@link ChartLegendProps.swatches} — the colour its chip is painted. A series
|
|
45
|
-
* that starts enabled (the default) is added to the row up front; one that
|
|
46
|
-
* starts `false` is hidden until a later `showSeries`.
|
|
47
|
-
*/
|
|
48
|
-
series: ChartSeries[]
|
|
49
|
-
/**
|
|
50
|
-
* The swatch colour for each series, by index — resolved by the chart, whose
|
|
51
|
-
* business it is whether a series' colour comes out of a stroke palette (a
|
|
52
|
-
* line) or a fill palette (a bar). Shorter than the series list means the
|
|
53
|
-
* missing entries fall back to white.
|
|
54
|
-
*/
|
|
55
|
-
swatches: Fill[]
|
|
56
|
-
/** Padding, caption style, gaps, and swatch geometry — see {@link LegendConfig}. */
|
|
57
|
-
legend: LegendConfig
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** A row's entries are a `Rect`'s: paint, the flow of its children, its corners. */
|
|
61
40
|
const row = { ...Paint.state, ...Flow.state, ...Shapes.corners }
|
|
62
41
|
|
|
63
42
|
/**
|
|
64
|
-
* One legend entry
|
|
65
|
-
* from nothing
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* never depends on whether the entry was once seen fully shown. A collapse needs a
|
|
69
|
-
* number to scale, and `"hug"` is not one until something measures it — this is
|
|
70
|
-
* the something.
|
|
43
|
+
* One legend entry, hugging its caption and swatch; `shown` scales that width
|
|
44
|
+
* from nothing. The width is measured from the content at any reveal, so a frame
|
|
45
|
+
* never depends on the entry once having been fully shown (`"hug"` is not a
|
|
46
|
+
* number to scale until something measures it).
|
|
71
47
|
*/
|
|
72
48
|
const LegendEntry = component2d({
|
|
73
49
|
key: "legendEntry",
|
|
@@ -81,11 +57,7 @@ const LegendEntry = component2d({
|
|
|
81
57
|
render: (ctx, state) => Paint.draw(ctx, state, Shapes.rect(ctx.size, state)),
|
|
82
58
|
})
|
|
83
59
|
|
|
84
|
-
/**
|
|
85
|
-
* How far each entry is shown, written whole (a document, a `set`) or one
|
|
86
|
-
* entry at a time (a command, as `{ [index]: reveal }`), which leaves the others
|
|
87
|
-
* as the frame left them — so two series moving at once each move their own.
|
|
88
|
-
*/
|
|
60
|
+
/** Written whole, or one entry at a time as `{ [index]: reveal }`, which leaves the others as the frame left them so two series can move at once. */
|
|
89
61
|
function revealOf(value: readonly number[] | Readonly<Record<number, number>>, previous?: number[]): number[] {
|
|
90
62
|
if (Array.isArray(value)) return value as number[]
|
|
91
63
|
const next = [...(previous ?? [])]
|
|
@@ -93,42 +65,23 @@ function revealOf(value: readonly number[] | Readonly<Record<number, number>>, p
|
|
|
93
65
|
return next
|
|
94
66
|
}
|
|
95
67
|
|
|
96
|
-
/**
|
|
97
|
-
* The legend of a chart: a **horizontal-flow row of entries**, one per series, laid out
|
|
98
|
-
* left-to-right with the theme's `itemGap`. Each entry pairs the series label
|
|
99
|
-
* with a rounded colour swatch; only the swatch carries the series colour, the
|
|
100
|
-
* caption reads in the legend's neutral ink.
|
|
101
|
-
*
|
|
102
|
-
* `showSeries`/`hideSeries` flip a series on or off — showing one grows its
|
|
103
|
-
* entry back in at its stable series position, hiding one shrinks it away. So
|
|
104
|
-
* the row only ever shows the currently-enabled series, in series order, and
|
|
105
|
-
* closes the gap when one leaves.
|
|
106
|
-
*
|
|
107
|
-
* This node *is* the row, so the parent places it (centred below the chart) and
|
|
108
|
-
* it hugs its entries.
|
|
109
|
-
*/
|
|
68
|
+
/** A chart's legend: a horizontal row with one entry per series, each a caption and a colour swatch. This node is the row, so the parent places it. */
|
|
110
69
|
export const ChartLegend = component2d({
|
|
111
70
|
key: "chartLegend",
|
|
112
71
|
state: {
|
|
113
72
|
...row,
|
|
114
73
|
series: fresh<ChartSeries[]>(() => []),
|
|
74
|
+
/** Swatch colour by series index, resolved by the chart; a missing entry is white. */
|
|
115
75
|
swatches: fresh<Fill[]>(() => []),
|
|
116
76
|
legend: Types.custom<LegendConfig>(DEFAULT_LEGEND),
|
|
117
77
|
/**
|
|
118
|
-
* How far each entry is shown, `0
|
|
119
|
-
* together
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* This replaces adding and removing the entry from the row: a command is
|
|
123
|
-
* asked what it looks like at a time, and "is this node a child" is not a
|
|
124
|
-
* value a time can produce. Collapsed-but-present is the same picture reached
|
|
125
|
-
* by a route that *is* a function of `t`.
|
|
78
|
+
* How far each entry is shown, `0`-`1`, driving its width, opacity and gap
|
|
79
|
+
* together. Entries are never added or removed: "is this node a child" is not
|
|
80
|
+
* a function of time, while collapsed-but-present is.
|
|
126
81
|
*/
|
|
127
82
|
reveal: fresh<number[], number[] | Readonly<Record<number, number>>>(() => [], { resolve: revealOf }),
|
|
128
83
|
},
|
|
129
|
-
//
|
|
130
|
-
// stated — a `Rect` given no flow lays its children out `freeform`, which
|
|
131
|
-
// stacks every entry on the same spot. The gap and padding are the theme's.
|
|
84
|
+
// The flow must be stated: a `Rect` with none lays children out `freeform`, stacking every entry on one spot.
|
|
132
85
|
defaults: {
|
|
133
86
|
flow: "horizontal",
|
|
134
87
|
width: "hug",
|
|
@@ -137,9 +90,7 @@ export const ChartLegend = component2d({
|
|
|
137
90
|
padding: (state) => state.legend.padding,
|
|
138
91
|
reveal: (state) => state.series.map((s) => ((s.enabled ?? true) ? 1 : 0)),
|
|
139
92
|
},
|
|
140
|
-
//
|
|
141
|
-
// fixed rather than depending on which siblings happen to be shown, so an entry
|
|
142
|
-
// always comes back where it belongs.
|
|
93
|
+
// Every entry stays a child, so one always comes back where it belongs.
|
|
143
94
|
children: (state) => {
|
|
144
95
|
const marker = state.legend.markerStyle
|
|
145
96
|
return state.series.map((series, index) =>
|
|
@@ -149,9 +100,7 @@ export const ChartLegend = component2d({
|
|
|
149
100
|
align: "center",
|
|
150
101
|
width: "hug",
|
|
151
102
|
height: "hug",
|
|
152
|
-
//
|
|
153
|
-
// than a child arriving and leaving. The width follows it in `measure`,
|
|
154
|
-
// and the flanking gap closes with the entry rather than snapping.
|
|
103
|
+
// The width follows the reveal in `measure`, and the flanking gap closes with the entry.
|
|
155
104
|
opacity: () => state.reveal[index] ?? 0,
|
|
156
105
|
gapScale: () => state.reveal[index] ?? 0,
|
|
157
106
|
shown: () => state.reveal[index] ?? 0,
|
|
@@ -171,11 +120,7 @@ export const ChartLegend = component2d({
|
|
|
171
120
|
},
|
|
172
121
|
commands: {
|
|
173
122
|
...Paint.commands,
|
|
174
|
-
/**
|
|
175
|
-
* Grow series `index`'s entry back in at its series-order position, the
|
|
176
|
-
* siblings sliding over to make room. A reveal tween rather than an animated
|
|
177
|
-
* `addChildAt`: the entry never left, so the whole thing is a function of `t`.
|
|
178
|
-
*/
|
|
123
|
+
/** Grow series `index`'s entry back in at its position, the siblings sliding over. */
|
|
179
124
|
showSeries: {
|
|
180
125
|
args: { index: Types.number(0, { min: 0, step: 1 }) },
|
|
181
126
|
// An index that is not a series takes its slot and moves nothing.
|
|
@@ -183,20 +128,14 @@ export const ChartLegend = component2d({
|
|
|
183
128
|
? timeline.animate(revealFrame(state, timeline.args.index, 1, frameEase(timeline)))
|
|
184
129
|
: timeline.wait(timeline.duration)),
|
|
185
130
|
},
|
|
186
|
-
/**
|
|
131
|
+
/** The mirror of `showSeries`. */
|
|
187
132
|
hideSeries: {
|
|
188
133
|
args: { index: Types.number(0, { min: 0, step: 1 }) },
|
|
189
134
|
run: (timeline, state) => (timeline.args.index in state.series
|
|
190
135
|
? timeline.animate(revealFrame(state, timeline.args.index, 0, frameEase(timeline)))
|
|
191
136
|
: timeline.wait(timeline.duration)),
|
|
192
137
|
},
|
|
193
|
-
/**
|
|
194
|
-
* Dim every entry but series `index` to {@link SPOTLIGHT_DIM} and bring that
|
|
195
|
-
* one back to full — the legend half of a chart's `spotlightSeries`. Entries
|
|
196
|
-
* stay in place; only their opacity moves, so the row doesn't reflow. Only
|
|
197
|
-
* the entries shown when it is built are touched: a hidden series' entry is
|
|
198
|
-
* left as `hideSeries` left it.
|
|
199
|
-
*/
|
|
138
|
+
/** Dim every entry but `index` to {@link SPOTLIGHT_DIM}, in place. A hidden series' entry is left as `hideSeries` left it. */
|
|
200
139
|
spotlightSeries: {
|
|
201
140
|
args: { index: Types.number(0, { min: 0, step: 1 }) },
|
|
202
141
|
run: (timeline, state, children) =>
|
|
@@ -212,18 +151,10 @@ export const ChartLegend = component2d({
|
|
|
212
151
|
render: (ctx, state) => Paint.draw(ctx, state, Shapes.rect(ctx.size, state)),
|
|
213
152
|
})
|
|
214
153
|
|
|
215
|
-
/**
|
|
216
|
-
* One entry's reveal ramping to `target` from where the command finds it — so a
|
|
217
|
-
* hide that follows a show starts where the show left it.
|
|
218
|
-
*/
|
|
154
|
+
/** One entry's reveal ramping to `target` from where the command finds it, so a hide after a show starts where the show left it. */
|
|
219
155
|
function revealFrame(state: { readonly reveal: readonly number[] }, index: number, target: number, ease: (t: number) => number) {
|
|
220
156
|
const from = state.reveal[index] ?? 0
|
|
221
157
|
return (t: number) => ({ reveal: { [index]: from + (target - from) * ease(t) } })
|
|
222
158
|
}
|
|
223
159
|
|
|
224
|
-
/** Whether series `index` is showing at all on `legend`. */
|
|
225
|
-
export function legendShows(legend: { readonly reveal: readonly number[] }, index: number): boolean {
|
|
226
|
-
return (legend.reveal[index] ?? 0) > 0
|
|
227
|
-
}
|
|
228
|
-
|
|
229
160
|
export type ChartLegend = NodeOf<typeof ChartLegend>
|
package/src/kit/entries.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Types, type Element, type Node2D, type Type } from "@motionscript/sdk"
|
|
1
|
+
import { Types, type Element, type EntryOptions, type Node2D, type Type } from "@motionscript/sdk"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* An entry made fresh for every node: each node gets its own array or box
|
|
@@ -18,3 +18,6 @@ export function fresh<T, In = T>(make: () => T, options?: { readonly resolve?: (
|
|
|
18
18
|
export function asParts(nodes: readonly Node2D[]): readonly Element[] {
|
|
19
19
|
return nodes as unknown as readonly Element[]
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
/** The argument every per-series command takes: an index into `series`, offered by name. */
|
|
23
|
+
export const SERIES_ARG = Types.custom<number, number, EntryOptions & { readonly min: number; readonly step: number }>(0, { kind: "series", min: 0, step: 1 })
|
package/src/kit/motion.ts
CHANGED
|
@@ -3,7 +3,16 @@ import { Types, easeOut, type EasingFunction, type Piece, type Timeline } from "
|
|
|
3
3
|
const identity: EasingFunction = (t) => t
|
|
4
4
|
|
|
5
5
|
/** Any component's timeline: these helpers only build pieces, which every timeline builds alike. */
|
|
6
|
-
type AnyTimeline = Timeline<any, any>
|
|
6
|
+
export type AnyTimeline = Timeline<any, any>
|
|
7
|
+
|
|
8
|
+
/** How far a legend starts below its slot when `enter` brings it in, in px. */
|
|
9
|
+
export const LEGEND_SLIDE = 20
|
|
10
|
+
|
|
11
|
+
/** Marker count when an axis states no ticks. */
|
|
12
|
+
export const DEFAULT_MARKERS = 5
|
|
13
|
+
|
|
14
|
+
/** How a cartesian `enter` splits its duration: axes arriving, marks drawing, legend coming up. Sums to 1. */
|
|
15
|
+
export const ENTER_SHARE = { axes: 0.35, marks: 0.5, legend: 0.15 } as const
|
|
7
16
|
|
|
8
17
|
/**
|
|
9
18
|
* The ease a part's own frames apply to `t`: none when its command was given
|
|
@@ -49,7 +58,7 @@ export interface Arrival {
|
|
|
49
58
|
/** Each piece's arrival, by key. An unlisted piece has landed. */
|
|
50
59
|
export type Arrivals = Readonly<Record<string, Arrival>>
|
|
51
60
|
|
|
52
|
-
|
|
61
|
+
const LANDED: Arrival = { opacity: 1, shift: 0 }
|
|
53
62
|
|
|
54
63
|
/**
|
|
55
64
|
* A part's private record of its pieces' arrivals. A write merges into it, so
|