@motionscript/charts 0.1.0 → 0.1.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +98 -28
- package/dist/bar-chart/bar-chart.d.ts +242 -119
- package/dist/bar-chart/bar-chart.d.ts.map +1 -1
- package/dist/bar-chart/bar-chart.js +231 -260
- package/dist/bar-chart/bar-chart.js.map +1 -1
- package/dist/bar-chart/category-axis.d.ts +16 -38
- package/dist/bar-chart/category-axis.d.ts.map +1 -1
- package/dist/bar-chart/category-axis.js +59 -68
- package/dist/bar-chart/category-axis.js.map +1 -1
- package/dist/bar-chart/chart-bars.d.ts +60 -91
- package/dist/bar-chart/chart-bars.d.ts.map +1 -1
- package/dist/bar-chart/chart-bars.js +88 -125
- package/dist/bar-chart/chart-bars.js.map +1 -1
- package/dist/bar-chart/index.d.ts +5 -13
- package/dist/bar-chart/index.d.ts.map +1 -1
- package/dist/bar-chart/index.js +2 -9
- package/dist/bar-chart/index.js.map +1 -1
- package/dist/bar-chart/shared.d.ts +40 -132
- package/dist/bar-chart/shared.d.ts.map +1 -1
- package/dist/bar-chart/shared.js +70 -158
- package/dist/bar-chart/shared.js.map +1 -1
- package/dist/browser/chunks/chunk-R3Q5EE6E.js +2 -0
- package/dist/browser/chunks/chunk-R3Q5EE6E.js.map +7 -0
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +4 -4
- package/dist/browser/kit.js +1 -1
- package/dist/browser/manifest.json +1 -1
- package/dist/engine.d.ts +8 -17
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +8 -17
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/kit/cartesian.d.ts +49 -35
- package/dist/kit/cartesian.d.ts.map +1 -1
- package/dist/kit/cartesian.js +80 -42
- package/dist/kit/cartesian.js.map +1 -1
- package/dist/kit/chart-canvas.d.ts +37 -40
- package/dist/kit/chart-canvas.d.ts.map +1 -1
- package/dist/kit/chart-canvas.js +53 -47
- package/dist/kit/chart-canvas.js.map +1 -1
- package/dist/kit/chart-legend.d.ts +16 -59
- package/dist/kit/chart-legend.d.ts.map +1 -1
- package/dist/kit/chart-legend.js +38 -76
- package/dist/kit/chart-legend.js.map +1 -1
- package/dist/kit/entries.d.ts +2 -0
- package/dist/kit/entries.d.ts.map +1 -1
- package/dist/kit/entries.js +2 -0
- package/dist/kit/entries.js.map +1 -1
- package/dist/kit/horizontal-axis.d.ts +77 -0
- package/dist/kit/horizontal-axis.d.ts.map +1 -0
- package/dist/kit/horizontal-axis.js +99 -0
- package/dist/kit/horizontal-axis.js.map +1 -0
- package/dist/kit/motion.d.ts +16 -3
- package/dist/kit/motion.d.ts.map +1 -1
- package/dist/kit/motion.js +15 -1
- package/dist/kit/motion.js.map +1 -1
- package/dist/{pie-chart → kit}/pie-legend.d.ts +14 -29
- package/dist/kit/pie-legend.d.ts.map +1 -0
- package/dist/kit/pie-legend.js +93 -0
- package/dist/kit/pie-legend.js.map +1 -0
- package/dist/kit/polar.d.ts +31 -0
- package/dist/kit/polar.d.ts.map +1 -0
- package/dist/kit/polar.js +33 -0
- package/dist/kit/polar.js.map +1 -0
- package/dist/kit/shared.d.ts +60 -214
- package/dist/kit/shared.d.ts.map +1 -1
- package/dist/kit/shared.js +91 -291
- package/dist/kit/shared.js.map +1 -1
- package/dist/kit/style.d.ts +254 -0
- package/dist/kit/style.d.ts.map +1 -0
- package/dist/kit/style.js +197 -0
- package/dist/kit/style.js.map +1 -0
- package/dist/kit/vertical-axis.d.ts +36 -45
- package/dist/kit/vertical-axis.d.ts.map +1 -1
- package/dist/kit/vertical-axis.js +69 -81
- package/dist/kit/vertical-axis.js.map +1 -1
- package/dist/line-chart/chart-line.d.ts +107 -40
- package/dist/line-chart/chart-line.d.ts.map +1 -1
- package/dist/line-chart/chart-line.js +172 -89
- package/dist/line-chart/chart-line.js.map +1 -1
- package/dist/line-chart/chart-region.d.ts +14 -27
- package/dist/line-chart/chart-region.d.ts.map +1 -1
- package/dist/line-chart/chart-region.js +13 -28
- package/dist/line-chart/chart-region.js.map +1 -1
- package/dist/line-chart/index.d.ts +5 -18
- package/dist/line-chart/index.d.ts.map +1 -1
- package/dist/line-chart/index.js +3 -16
- package/dist/line-chart/index.js.map +1 -1
- package/dist/line-chart/line-chart.d.ts +272 -119
- package/dist/line-chart/line-chart.d.ts.map +1 -1
- package/dist/line-chart/line-chart.js +309 -257
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/line-chart/shared.d.ts +73 -130
- package/dist/line-chart/shared.d.ts.map +1 -1
- package/dist/line-chart/shared.js +195 -156
- package/dist/line-chart/shared.js.map +1 -1
- package/dist/line-chart/x-scale.d.ts +16 -55
- package/dist/line-chart/x-scale.d.ts.map +1 -1
- package/dist/line-chart/x-scale.js +44 -111
- package/dist/line-chart/x-scale.js.map +1 -1
- package/dist/nodes.d.ts +3 -13
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +9 -13
- package/dist/nodes.js.map +1 -1
- package/dist/pie-chart/index.d.ts +3 -15
- package/dist/pie-chart/index.d.ts.map +1 -1
- package/dist/pie-chart/index.js +1 -11
- package/dist/pie-chart/index.js.map +1 -1
- package/dist/pie-chart/pie-chart.d.ts +147 -85
- package/dist/pie-chart/pie-chart.d.ts.map +1 -1
- package/dist/pie-chart/pie-chart.js +65 -86
- package/dist/pie-chart/pie-chart.js.map +1 -1
- package/dist/pie-chart/pie-ring.d.ts +65 -196
- package/dist/pie-chart/pie-ring.d.ts.map +1 -1
- package/dist/pie-chart/pie-ring.js +154 -292
- package/dist/pie-chart/pie-ring.js.map +1 -1
- package/dist/pie-chart/shared.d.ts +78 -172
- package/dist/pie-chart/shared.d.ts.map +1 -1
- package/dist/pie-chart/shared.js +54 -177
- package/dist/pie-chart/shared.js.map +1 -1
- package/dist/radar-chart/index.d.ts +5 -0
- package/dist/radar-chart/index.d.ts.map +1 -0
- package/dist/radar-chart/index.js +5 -0
- package/dist/radar-chart/index.js.map +1 -0
- package/dist/radar-chart/radar-chart.d.ts +218 -0
- package/dist/radar-chart/radar-chart.d.ts.map +1 -0
- package/dist/radar-chart/radar-chart.js +224 -0
- package/dist/radar-chart/radar-chart.js.map +1 -0
- package/dist/radar-chart/radar-grid.d.ts +43 -0
- package/dist/radar-chart/radar-grid.d.ts.map +1 -0
- package/dist/radar-chart/radar-grid.js +61 -0
- package/dist/radar-chart/radar-grid.js.map +1 -0
- package/dist/radar-chart/radar-series.d.ts +59 -0
- package/dist/radar-chart/radar-series.d.ts.map +1 -0
- package/dist/radar-chart/radar-series.js +71 -0
- package/dist/radar-chart/radar-series.js.map +1 -0
- package/dist/radar-chart/shared.d.ts +73 -0
- package/dist/radar-chart/shared.d.ts.map +1 -0
- package/dist/radar-chart/shared.js +123 -0
- package/dist/radar-chart/shared.js.map +1 -0
- package/dist/radial-chart/index.d.ts +6 -0
- package/dist/radial-chart/index.d.ts.map +1 -0
- package/dist/radial-chart/index.js +4 -0
- package/dist/radial-chart/index.js.map +1 -0
- package/dist/radial-chart/radial-chart.d.ts +216 -0
- package/dist/radial-chart/radial-chart.d.ts.map +1 -0
- package/dist/radial-chart/radial-chart.js +174 -0
- package/dist/radial-chart/radial-chart.js.map +1 -0
- package/dist/radial-chart/radial-rings.d.ts +121 -0
- package/dist/radial-chart/radial-rings.d.ts.map +1 -0
- package/dist/radial-chart/radial-rings.js +273 -0
- package/dist/radial-chart/radial-rings.js.map +1 -0
- package/dist/radial-chart/shared.d.ts +73 -0
- package/dist/radial-chart/shared.d.ts.map +1 -0
- package/dist/radial-chart/shared.js +79 -0
- package/dist/radial-chart/shared.js.map +1 -0
- package/package.json +4 -4
- package/registry.json +21 -6
- package/src/bar-chart/bar-chart.ts +274 -315
- package/src/bar-chart/category-axis.ts +53 -88
- package/src/bar-chart/chart-bars.ts +108 -205
- package/src/bar-chart/index.ts +8 -19
- package/src/bar-chart/shared.ts +81 -309
- package/src/engine.ts +8 -17
- package/src/index.ts +2 -0
- package/src/kit/cartesian.ts +118 -55
- package/src/kit/chart-canvas.ts +50 -80
- package/src/kit/chart-legend.ts +36 -97
- package/src/kit/entries.ts +4 -1
- package/src/kit/horizontal-axis.ts +114 -0
- package/src/kit/motion.ts +18 -2
- package/src/{pie-chart → kit}/pie-legend.ts +25 -65
- package/src/kit/polar.ts +54 -0
- package/src/kit/shared.ts +104 -487
- package/src/kit/style.ts +257 -0
- package/src/kit/vertical-axis.ts +81 -115
- package/src/line-chart/chart-line.ts +185 -91
- package/src/line-chart/chart-region.ts +14 -31
- package/src/line-chart/index.ts +14 -22
- package/src/line-chart/line-chart.ts +377 -315
- package/src/line-chart/shared.ts +229 -304
- package/src/line-chart/x-scale.ts +44 -121
- package/src/nodes.ts +9 -13
- package/src/pie-chart/index.ts +3 -35
- package/src/pie-chart/pie-chart.ts +83 -134
- package/src/pie-chart/pie-ring.ts +162 -368
- package/src/pie-chart/shared.ts +92 -379
- package/src/radar-chart/index.ts +4 -0
- package/src/radar-chart/radar-chart.ts +287 -0
- package/src/radar-chart/radar-grid.ts +78 -0
- package/src/radar-chart/radar-series.ts +88 -0
- package/src/radar-chart/shared.ts +146 -0
- package/src/radial-chart/index.ts +5 -0
- package/src/radial-chart/radial-chart.ts +210 -0
- package/src/radial-chart/radial-rings.ts +328 -0
- package/src/radial-chart/shared.ts +114 -0
- package/dist/browser/chunks/chunk-TJNHLQGV.js +0 -2
- package/dist/browser/chunks/chunk-TJNHLQGV.js.map +0 -7
- package/dist/kit/chart-border.d.ts +0 -27
- package/dist/kit/chart-border.d.ts.map +0 -1
- package/dist/kit/chart-border.js +0 -28
- package/dist/kit/chart-border.js.map +0 -1
- package/dist/line-chart/horizontal-axis.d.ts +0 -79
- package/dist/line-chart/horizontal-axis.d.ts.map +0 -1
- package/dist/line-chart/horizontal-axis.js +0 -129
- package/dist/line-chart/horizontal-axis.js.map +0 -1
- package/dist/pie-chart/pie-legend.d.ts.map +0 -1
- package/dist/pie-chart/pie-legend.js +0 -117
- package/dist/pie-chart/pie-legend.js.map +0 -1
- package/src/kit/chart-border.ts +0 -50
- package/src/line-chart/horizontal-axis.ts +0 -151
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* add to the shared chart vocabulary.
|
|
5
|
-
*
|
|
6
|
-
* Everything a bar or a pie chart would want too — what a series is, how an axis
|
|
7
|
-
* is scaled and formatted, the theme leaf resolvers, the legend and plot-panel
|
|
8
|
-
* branches — lives in `../kit/shared.ts` and is imported here. What's
|
|
9
|
-
* left below is what only a line chart has: points sorted along an x column,
|
|
10
|
-
* a region clipped out of a polyline, and the `region` branch of the theme.
|
|
11
|
-
*
|
|
12
|
-
* Kept renderer-agnostic (no `Node` imports) so every part shares one
|
|
13
|
-
* scale/format/palette/theme vocabulary, and so a host's own inspector
|
|
14
|
-
* mapping can build a partial {@link ChartTheme} without
|
|
15
|
-
* pulling the components in.
|
|
16
|
-
*/
|
|
17
|
-
import { Fills, fillOps, lerpNumber, strokeOps, } from "@motionscript/sdk";
|
|
18
|
-
import { CHART_PALETTE, DEFAULT_LEGEND, DEFAULT_PLOT_AREA, DEFAULT_SERIES_STROKES, DEFAULT_X_AXIS, DEFAULT_Y_AXIS, SPOTLIGHT_DIM, lerpAxisConfig, lerpLegendConfig, lerpPlotArea, lerpSeriesStrokes, niceStep, num, resolveAxisConfig, resolveLegendConfig, resolvePlotArea, resolveSeriesStrokes, } from "../kit/shared.js";
|
|
19
|
-
/** The empty box, used as the "no region" resting value. */
|
|
1
|
+
import { Types } from "@motionscript/sdk";
|
|
2
|
+
import { niceRange, num } from "../kit/shared.js";
|
|
3
|
+
/** The "no region" resting value. */
|
|
20
4
|
export const EMPTY_REGION_BOX = {
|
|
21
5
|
left: 0,
|
|
22
6
|
right: 0,
|
|
@@ -25,18 +9,14 @@ export const EMPTY_REGION_BOX = {
|
|
|
25
9
|
xBounded: false,
|
|
26
10
|
yBounded: false,
|
|
27
11
|
};
|
|
28
|
-
/**
|
|
12
|
+
/** Whether `box` is non-degenerate, so there is something to spotlight. */
|
|
29
13
|
export function hasRegion(box) {
|
|
30
14
|
return box.right > box.left && box.top > box.bottom;
|
|
31
15
|
}
|
|
32
16
|
/**
|
|
33
|
-
* Resolve a {@link PlotRegion} against the plot
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* An omitted axis takes the plot's own edges, so the region spans it. A given
|
|
38
|
-
* axis is clamped to the plot, so a range running off the scale still produces
|
|
39
|
-
* a box inside the panel.
|
|
17
|
+
* Resolve a {@link PlotRegion} against the plot rectangle (centred pixel space)
|
|
18
|
+
* and the data ranges its edges span. An omitted axis takes the plot's own
|
|
19
|
+
* edges; a given one is clamped to the plot.
|
|
40
20
|
*/
|
|
41
21
|
export function resolveRegionBox(region, plot, xScale, yScale) {
|
|
42
22
|
if (!region || (region.x === undefined && region.y === undefined)) {
|
|
@@ -59,12 +39,10 @@ export function resolveRegionBox(region, plot, xScale, yScale) {
|
|
|
59
39
|
return { left, right, bottom, top, xBounded, yBounded };
|
|
60
40
|
}
|
|
61
41
|
/**
|
|
62
|
-
* The parts of `points`
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* Segments are clipped against the box rather than filtered by endpoint, so a
|
|
67
|
-
* line entering the region mid-segment is cut exactly on the boundary.
|
|
42
|
+
* The parts of `points` inside `box`, as contiguous sub-polylines: a series is
|
|
43
|
+
* drawn dimmed in full, then these runs are re-drawn at full strength. Segments
|
|
44
|
+
* are clipped, not filtered by endpoint, so a line entering mid-segment is cut
|
|
45
|
+
* exactly on the boundary.
|
|
68
46
|
*/
|
|
69
47
|
export function clipPolylineToBox(points, box) {
|
|
70
48
|
if (points.length < 2 || !hasRegion(box))
|
|
@@ -74,15 +52,14 @@ export function clipPolylineToBox(points, box) {
|
|
|
74
52
|
for (let i = 1; i < points.length; i++) {
|
|
75
53
|
const seg = clipSegment(points[i - 1], points[i], box);
|
|
76
54
|
if (!seg) {
|
|
77
|
-
//
|
|
55
|
+
// Misses the box: close the open run.
|
|
78
56
|
if (run.length >= 2)
|
|
79
57
|
runs.push(run);
|
|
80
58
|
run = [];
|
|
81
59
|
continue;
|
|
82
60
|
}
|
|
83
61
|
const [a, b] = seg;
|
|
84
|
-
//
|
|
85
|
-
// ended; otherwise the line left the box and came back, which is a new run.
|
|
62
|
+
// Continues the open run if it starts where the last ended; otherwise the line left and came back.
|
|
86
63
|
if (run.length > 0 && samePoint(run[run.length - 1], a))
|
|
87
64
|
run.push(b);
|
|
88
65
|
else {
|
|
@@ -99,16 +76,13 @@ export function clipPolylineToBox(points, box) {
|
|
|
99
76
|
function samePoint(a, b) {
|
|
100
77
|
return Math.abs(a.x - b.x) < 0.5 && Math.abs(a.y - b.y) < 0.5;
|
|
101
78
|
}
|
|
102
|
-
/**
|
|
103
|
-
* Liang–Barsky clip of one segment against the axis-aligned `box`. Returns the
|
|
104
|
-
* surviving `[start, end]`, or `null` when the segment lies wholly outside.
|
|
105
|
-
*/
|
|
79
|
+
/** Liang-Barsky clip of one segment against `box`: the surviving `[start, end]`, or `null` when wholly outside. */
|
|
106
80
|
function clipSegment(a, b, box) {
|
|
107
81
|
const dx = b.x - a.x;
|
|
108
82
|
const dy = b.y - a.y;
|
|
109
83
|
let t0 = 0;
|
|
110
84
|
let t1 = 1;
|
|
111
|
-
// Each edge as (p, q): the segment survives where p
|
|
85
|
+
// Each edge as (p, q): the segment survives where p*t <= q.
|
|
112
86
|
const edges = [
|
|
113
87
|
[-dx, a.x - box.left],
|
|
114
88
|
[dx, box.right - a.x],
|
|
@@ -117,7 +91,7 @@ function clipSegment(a, b, box) {
|
|
|
117
91
|
];
|
|
118
92
|
for (const [p, q] of edges) {
|
|
119
93
|
if (p === 0) {
|
|
120
|
-
// Parallel to this edge: outside it
|
|
94
|
+
// Parallel to this edge: outside it, the whole segment is out.
|
|
121
95
|
if (q < 0)
|
|
122
96
|
return null;
|
|
123
97
|
continue;
|
|
@@ -142,103 +116,15 @@ function clipSegment(a, b, box) {
|
|
|
142
116
|
];
|
|
143
117
|
}
|
|
144
118
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* a square reads as the sample it is.
|
|
149
|
-
*/
|
|
150
|
-
const LINE_CHART_LEGEND = {
|
|
151
|
-
...DEFAULT_LEGEND,
|
|
152
|
-
markerStyle: {
|
|
153
|
-
...DEFAULT_LEGEND.markerStyle,
|
|
154
|
-
width: 20,
|
|
155
|
-
height: 20,
|
|
156
|
-
borderRadius: 6,
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
/** The chart's out-of-the-box look. */
|
|
160
|
-
export const DEFAULT_CHART_THEME = {
|
|
161
|
-
seriesStyles: DEFAULT_SERIES_STROKES,
|
|
162
|
-
xAxis: DEFAULT_X_AXIS,
|
|
163
|
-
yAxis: DEFAULT_Y_AXIS,
|
|
164
|
-
plotArea: DEFAULT_PLOT_AREA,
|
|
165
|
-
legend: LINE_CHART_LEGEND,
|
|
166
|
-
region: {
|
|
167
|
-
// Brighter than the grid and dashed, so the boundary reads as a call-out
|
|
168
|
-
// rather than another axis line; `butt` caps keep the dash lengths exact.
|
|
169
|
-
stroke: {
|
|
170
|
-
weight: 3,
|
|
171
|
-
fill: CHART_PALETTE.regionColor,
|
|
172
|
-
dash: [14, 12],
|
|
173
|
-
cap: "butt",
|
|
174
|
-
},
|
|
175
|
-
// A wash barely above the panel — enough to tint the band, not enough to
|
|
176
|
-
// mute the series drawn over it.
|
|
177
|
-
fill: Fills.color(CHART_PALETTE.regionColor, { opacity: 0.08 }),
|
|
178
|
-
dimOpacity: SPOTLIGHT_DIM,
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
function resolveRegionConfig(region, previous) {
|
|
182
|
-
return {
|
|
183
|
-
stroke: strokeOps.resolve(region?.stroke ?? previous.stroke),
|
|
184
|
-
fill: fillOps.resolve(region?.fill ?? previous.fill),
|
|
185
|
-
dimOpacity: region?.dimOpacity ?? previous.dimOpacity,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
function lerpRegionConfig(from, to, t) {
|
|
189
|
-
return {
|
|
190
|
-
stroke: strokeOps.lerp(from.stroke, to.stroke, t),
|
|
191
|
-
fill: fillOps.lerp(from.fill, to.fill, t),
|
|
192
|
-
dimOpacity: lerpNumber(from.dimOpacity, to.dimOpacity, t),
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* {@link LineChart}'s `theme` entry's resolve: merges the author's partial
|
|
197
|
-
* {@link ChartTheme} onto `previous` (falling back to
|
|
198
|
-
* {@link DEFAULT_CHART_THEME} on first resolution), fully resolving every leaf.
|
|
199
|
-
*/
|
|
200
|
-
export function resolveChartTheme(theme, previous) {
|
|
201
|
-
const base = previous ?? DEFAULT_CHART_THEME;
|
|
202
|
-
return {
|
|
203
|
-
seriesStyles: resolveSeriesStrokes(theme?.seriesStyles, base.seriesStyles),
|
|
204
|
-
xAxis: resolveAxisConfig(theme?.xAxis, base.xAxis),
|
|
205
|
-
yAxis: resolveAxisConfig(theme?.yAxis, base.yAxis),
|
|
206
|
-
plotArea: resolvePlotArea(theme?.plotArea, base.plotArea),
|
|
207
|
-
legend: resolveLegendConfig(theme?.legend, base.legend),
|
|
208
|
-
region: resolveRegionConfig(theme?.region, base.region),
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
/** {@link LineChart}'s `theme` entry's lerp — leaf by leaf. */
|
|
212
|
-
export function lerpChartTheme(from, to, t) {
|
|
213
|
-
return {
|
|
214
|
-
seriesStyles: lerpSeriesStrokes(from.seriesStyles, to.seriesStyles, t),
|
|
215
|
-
xAxis: lerpAxisConfig(from.xAxis, to.xAxis, t),
|
|
216
|
-
yAxis: lerpAxisConfig(from.yAxis, to.yAxis, t),
|
|
217
|
-
plotArea: lerpPlotArea(from.plotArea, to.plotArea, t),
|
|
218
|
-
legend: lerpLegendConfig(from.legend, to.legend, t),
|
|
219
|
-
region: lerpRegionConfig(from.region, to.region, t),
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
// ---- Scales --------------------------------------------------------------
|
|
223
|
-
/**
|
|
224
|
-
* Resolve one series' `[x, y]` points out of the chart's rows: each row's
|
|
225
|
-
* position on the {@link XScale} paired with its own `field` column, sorted
|
|
226
|
-
* along x so the polyline traces left-to-right whatever order the rows arrived
|
|
227
|
-
* in.
|
|
228
|
-
*
|
|
229
|
-
* A row missing its y column contributes a 0 rather than a gap — these charts
|
|
230
|
-
* draw continuous lines, so dropping the point would silently bridge the hole.
|
|
231
|
-
*
|
|
232
|
-
* Takes the resolved scale rather than the column *name*, because what a row's x
|
|
233
|
-
* is depends on what kind of column it came out of: a number, an instant, or a
|
|
234
|
-
* place in the file. See `x-scale.ts`.
|
|
119
|
+
* One series' `[x, y]` points, sorted along x so the polyline traces left to
|
|
120
|
+
* right. A row missing its y contributes 0 rather than a gap, since dropping it
|
|
121
|
+
* would silently bridge the hole.
|
|
235
122
|
*/
|
|
236
123
|
export function seriesPoints(data, x, series) {
|
|
237
124
|
return data
|
|
238
125
|
.map((row, i) => [x.at(i), num(row, series.field)])
|
|
239
126
|
.sort((a, b) => a[0] - b[0]);
|
|
240
127
|
}
|
|
241
|
-
/** Min/max across every series' column, the fallback when unset. */
|
|
242
128
|
export function yExtent(data, series) {
|
|
243
129
|
let lo = Infinity;
|
|
244
130
|
let hi = -Infinity;
|
|
@@ -253,33 +139,186 @@ export function yExtent(data, series) {
|
|
|
253
139
|
return lo <= hi ? [lo, hi] : [0, 1];
|
|
254
140
|
}
|
|
255
141
|
/**
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* `divisions` is how many segments the auto-range is stepped into (one fewer
|
|
260
|
-
* than a marker count) — it only shapes the tidy step when `range` is omitted.
|
|
142
|
+
* The explicit `range`, else the data's y extent nudged out to tidy step bounds.
|
|
143
|
+
* `divisions` (one fewer than the marker count) only shapes the step when
|
|
144
|
+
* `range` is omitted.
|
|
261
145
|
*/
|
|
262
146
|
export function resolveYScale(data, series, range, divisions) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
147
|
+
return range ?? niceRange(yExtent(data, series), divisions);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Every series' points under `stack`, rows in x order. A series contributes its
|
|
151
|
+
* values times its weight (`1` when missing), so a series sinking out of a stack
|
|
152
|
+
* lowers the ones above it. `percent` reads each row as shares of its total
|
|
153
|
+
* magnitude, so the top of a full stack is 100.
|
|
154
|
+
*/
|
|
155
|
+
export function stackSeries(data, x, series, stack, weights = []) {
|
|
156
|
+
const order = data.map((_, i) => i).sort((a, b) => x.at(a) - x.at(b));
|
|
157
|
+
if (stack !== "stacked" && stack !== "percent") {
|
|
158
|
+
return series.map((s) => ({ points: order.map((r) => [x.at(r), num(data[r], s.field)]), base: null }));
|
|
159
|
+
}
|
|
160
|
+
const weight = (i) => Math.max(0, weights[i] ?? 1);
|
|
161
|
+
const totals = order.map((r) => series.reduce((sum, s, i) => sum + Math.abs(num(data[r], s.field) * weight(i)), 0));
|
|
162
|
+
const running = order.map(() => 0);
|
|
163
|
+
return series.map((s, i) => {
|
|
164
|
+
const base = order.map((r, k) => [x.at(r), running[k]]);
|
|
165
|
+
const points = order.map((r, k) => {
|
|
166
|
+
const value = num(data[r], s.field) * weight(i);
|
|
167
|
+
running[k] += stack === "percent" ? (totals[k] > 0 ? (value / totals[k]) * 100 : 0) : value;
|
|
168
|
+
return [x.at(r), running[k]];
|
|
169
|
+
});
|
|
170
|
+
return { points, base };
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/** The lowest and highest y anything in `stacks` reaches, its bases included. */
|
|
174
|
+
export function stackExtent(stacks) {
|
|
175
|
+
let lo = Infinity;
|
|
176
|
+
let hi = -Infinity;
|
|
177
|
+
for (const { points, base } of stacks)
|
|
178
|
+
for (const [, y] of [...points, ...(base ?? [])]) {
|
|
179
|
+
if (y < lo)
|
|
180
|
+
lo = y;
|
|
181
|
+
if (y > hi)
|
|
182
|
+
hi = y;
|
|
183
|
+
}
|
|
184
|
+
return lo <= hi ? [lo, hi] : [0, 1];
|
|
185
|
+
}
|
|
186
|
+
/** Points along a cubic Bezier segment, its start left out. */
|
|
187
|
+
const SAMPLES = 16;
|
|
188
|
+
/**
|
|
189
|
+
* The drawn path through `points` (ordered along x) as a polyline: the points
|
|
190
|
+
* themselves for `linear`; for `step`, level runs that change at the midpoint
|
|
191
|
+
* between neighbours; for `smooth`, a monotone cubic (Fritsch-Carlson, as d3's
|
|
192
|
+
* `curveMonotoneX`), which never overshoots the data between two points.
|
|
193
|
+
*/
|
|
194
|
+
export function curvePoints(points, curve) {
|
|
195
|
+
if (curve === "step") {
|
|
196
|
+
const out = points.length > 0 ? [points[0]] : [];
|
|
197
|
+
for (let i = 1; i < points.length; i++) {
|
|
198
|
+
const mid = (points[i - 1].x + points[i].x) / 2;
|
|
199
|
+
out.push({ x: mid, y: points[i - 1].y }, { x: mid, y: points[i].y }, points[i]);
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
if (curve !== "smooth" || points.length < 3)
|
|
204
|
+
return [...points];
|
|
205
|
+
const tangents = monotoneTangents(points);
|
|
206
|
+
const out = [points[0]];
|
|
207
|
+
for (let i = 1; i < points.length; i++) {
|
|
208
|
+
const a = points[i - 1];
|
|
209
|
+
const b = points[i];
|
|
210
|
+
const dx = (b.x - a.x) / 3;
|
|
211
|
+
const c1 = { x: a.x + dx, y: a.y + dx * tangents[i - 1] };
|
|
212
|
+
const c2 = { x: b.x - dx, y: b.y - dx * tangents[i] };
|
|
213
|
+
for (let k = 1; k <= SAMPLES; k++) {
|
|
214
|
+
const t = k / SAMPLES;
|
|
215
|
+
const u = 1 - t;
|
|
216
|
+
out.push({
|
|
217
|
+
x: u * u * u * a.x + 3 * u * u * t * c1.x + 3 * u * t * t * c2.x + t * t * t * b.x,
|
|
218
|
+
y: u * u * u * a.y + 3 * u * u * t * c1.y + 3 * u * t * t * c2.y + t * t * t * b.y,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
function monotoneTangents(points) {
|
|
225
|
+
const n = points.length;
|
|
226
|
+
const slope = (i) => {
|
|
227
|
+
const h = points[i + 1].x - points[i].x;
|
|
228
|
+
return h === 0 ? 0 : (points[i + 1].y - points[i].y) / h;
|
|
229
|
+
};
|
|
230
|
+
const m = new Array(n).fill(0);
|
|
231
|
+
for (let i = 1; i < n - 1; i++) {
|
|
232
|
+
const h0 = points[i].x - points[i - 1].x;
|
|
233
|
+
const h1 = points[i + 1].x - points[i].x;
|
|
234
|
+
const s0 = slope(i - 1);
|
|
235
|
+
const s1 = slope(i);
|
|
236
|
+
const p = h0 + h1 === 0 ? 0 : (s0 * h1 + s1 * h0) / (h0 + h1);
|
|
237
|
+
m[i] = (Math.sign(s0) + Math.sign(s1)) * Math.min(Math.abs(s0), Math.abs(s1), 0.5 * Math.abs(p)) || 0;
|
|
238
|
+
}
|
|
239
|
+
m[0] = endTangent(slope(0), m[1]);
|
|
240
|
+
m[n - 1] = endTangent(slope(n - 2), m[n - 2]);
|
|
241
|
+
return m;
|
|
242
|
+
}
|
|
243
|
+
/** d3's one-sided end tangent, held to the end segment's own direction and to three times its slope, past which it would overshoot. */
|
|
244
|
+
function endTangent(secant, inner) {
|
|
245
|
+
const t = (3 * secant - inner) / 2;
|
|
246
|
+
if (secant === 0 || Math.sign(t) !== Math.sign(secant))
|
|
247
|
+
return 0;
|
|
248
|
+
return Math.abs(t) > 3 * Math.abs(secant) ? 3 * secant : t;
|
|
249
|
+
}
|
|
250
|
+
/** The part of `points` (ordered along x) left of `x`, ending on the point where it crosses `x`. */
|
|
251
|
+
export function cutAt(points, x) {
|
|
252
|
+
if (points.length === 0 || x < points[0].x)
|
|
253
|
+
return [];
|
|
254
|
+
const out = [];
|
|
255
|
+
for (let i = 0; i < points.length; i++) {
|
|
256
|
+
const p = points[i];
|
|
257
|
+
if (p.x <= x) {
|
|
258
|
+
out.push(p);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const a = points[i - 1];
|
|
262
|
+
if (a && p.x > a.x)
|
|
263
|
+
out.push({ x, y: a.y + ((p.y - a.y) * (x - a.x)) / (p.x - a.x) });
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
271
267
|
}
|
|
272
268
|
/**
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
* `[3, 7]` and `5000` gets `[3000, 7000]`), or to `[0, 1]` for all zeros, as for
|
|
277
|
-
* no data at all.
|
|
269
|
+
* `polygon` clipped to `box` (Sutherland-Hodgman against each edge in turn).
|
|
270
|
+
* The box is convex, so the result is the part inside, possibly with seams of
|
|
271
|
+
* no width where a concave outline leaves and re-enters, which fill as nothing.
|
|
278
272
|
*/
|
|
279
|
-
function
|
|
280
|
-
if (
|
|
281
|
-
return [
|
|
282
|
-
const
|
|
283
|
-
|
|
273
|
+
export function clipPolygonToBox(polygon, box) {
|
|
274
|
+
if (polygon.length < 3 || !hasRegion(box))
|
|
275
|
+
return [];
|
|
276
|
+
const edges = [
|
|
277
|
+
(p) => p.x - box.left,
|
|
278
|
+
(p) => box.right - p.x,
|
|
279
|
+
(p) => p.y - box.bottom,
|
|
280
|
+
(p) => box.top - p.y,
|
|
281
|
+
];
|
|
282
|
+
let out = [...polygon];
|
|
283
|
+
for (const inside of edges) {
|
|
284
|
+
const input = out;
|
|
285
|
+
out = [];
|
|
286
|
+
for (let i = 0; i < input.length; i++) {
|
|
287
|
+
const cur = input[i];
|
|
288
|
+
const prev = input[(i + input.length - 1) % input.length];
|
|
289
|
+
const dc = inside(cur);
|
|
290
|
+
const dp = inside(prev);
|
|
291
|
+
if (dc >= 0) {
|
|
292
|
+
if (dp < 0)
|
|
293
|
+
out.push(crossing(prev, cur, dp, dc));
|
|
294
|
+
out.push(cur);
|
|
295
|
+
}
|
|
296
|
+
else if (dp >= 0)
|
|
297
|
+
out.push(crossing(prev, cur, dp, dc));
|
|
298
|
+
}
|
|
299
|
+
if (out.length === 0)
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
284
303
|
}
|
|
304
|
+
function crossing(a, b, da, db) {
|
|
305
|
+
const t = da / (da - db);
|
|
306
|
+
return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
|
|
307
|
+
}
|
|
308
|
+
/** A line chart's own look: how each series is stroked, dotted and filled. */
|
|
309
|
+
export const LINE_MARKS = {
|
|
310
|
+
strokeWidth: Types.number(4, { min: 0, max: 24, step: 0.5, unit: "px" }),
|
|
311
|
+
curve: Types.oneOf(["smooth", "linear", "step"]),
|
|
312
|
+
dots: Types.boolean(false),
|
|
313
|
+
/** Each dot's diameter. */
|
|
314
|
+
dotSize: Types.number(12, { min: 0, max: 48, step: 1, unit: "px" }),
|
|
315
|
+
/** Fill under each line: none, the series colour, or the colour fading to nothing at the baseline. */
|
|
316
|
+
area: Types.oneOf(["none", "solid", "gradient"]),
|
|
317
|
+
/** The fill's opacity at the line. */
|
|
318
|
+
areaOpacity: Types.number(0.4, { min: 0, max: 1, step: 0.05 }),
|
|
319
|
+
};
|
|
320
|
+
export const LINE_LABELS = {
|
|
321
|
+
/** Read as the chart is built: each point's value printed above or below it, or none. */
|
|
322
|
+
values: Types.oneOf(["none", "above", "below"], { animatable: false }),
|
|
323
|
+
};
|
|
285
324
|
//# sourceMappingURL=shared.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/line-chart/shared.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Types and pure helpers specific to the {@link LineChart} family: the traced\n * polylines, the spotlit region they dim outside of, and the theme those two\n * add to the shared chart vocabulary.\n *\n * Everything a bar or a pie chart would want too — what a series is, how an axis\n * is scaled and formatted, the theme leaf resolvers, the legend and plot-panel\n * branches — lives in `../kit/shared.ts` and is imported here. What's\n * left below is what only a line chart has: points sorted along an x column,\n * a region clipped out of a polyline, and the `region` branch of the theme.\n *\n * Kept renderer-agnostic (no `Node` imports) so every part shares one\n * scale/format/palette/theme vocabulary, and so a host's own inspector\n * mapping can build a partial {@link ChartTheme} without\n * pulling the components in.\n */\n\nimport {\n Fills,\n fillOps,\n lerpNumber,\n strokeOps,\n type DataRecord,\n type Fill,\n type FillResolved,\n type Stroke,\n type StrokeResolved,\n} from \"@motionscript/sdk\"\n\nimport {\n CHART_PALETTE,\n DEFAULT_LEGEND,\n DEFAULT_PLOT_AREA,\n DEFAULT_SERIES_STROKES,\n DEFAULT_X_AXIS,\n DEFAULT_Y_AXIS,\n SPOTLIGHT_DIM,\n lerpAxisConfig,\n lerpLegendConfig,\n lerpPlotArea,\n lerpSeriesStrokes,\n niceStep,\n num,\n resolveAxisConfig,\n resolveLegendConfig,\n resolvePlotArea,\n resolveSeriesStrokes,\n type AxisConfig,\n type ChartSeries,\n type LegendConfig,\n type LegendTheme,\n type PlotAreaConfig,\n type Range,\n} from \"../kit/shared\"\nimport type { XScale } from \"./x-scale\"\n\n/**\n * One series in a {@link LineChart}: a polyline traced through the values in one\n * column of the chart's `data`, drawn in `fill` (or the theme's `seriesStyles`\n * palette when omitted) and captioned by `label` in the legend.\n *\n * The family-wide {@link ChartSeries} under a name that says what it draws here;\n * the chart's own `xField` prop names the column carrying the shared x values,\n * so every series is read off the same rows — one row per sample, one column per\n * series.\n */\nexport type LineSeries = ChartSeries\n\n// ---- Plot regions -------------------------------------------------------\n\n/**\n * A rectangular slice of the plot, in **data units** — the author-facing input\n * to {@link LineChart.spotlightRegion} / {@link LineChart.zoomTo}.\n *\n * Either axis may be omitted, and that's the point: `{ x: [60, 100] }` is a\n * vertical band spanning the full height, `{ y: [8, 10] }` a horizontal band\n * spanning the full width, and both together a box. A reversed range is read\n * the same as the forward one — a region is a span, not a direction.\n */\nexport interface PlotRegion {\n /** Horizontal span in x-axis data units. Omit to span the full plot width. */\n x?: Range\n /** Vertical span in y-axis data units. Omit to span the full plot height. */\n y?: Range\n}\n\n/**\n * A {@link PlotRegion} resolved into the plot's **centred pixel space** (origin\n * at the plot centre, y-up) — the same local frame the series points live in,\n * so a line can be clipped against it directly.\n *\n * `xBounded`/`yBounded` carry which axes the author actually constrained: an\n * unbounded axis has its edges pinned to the plot's own and draws no rule.\n */\nexport interface RegionBox {\n left: number\n right: number\n bottom: number\n top: number\n /** The author gave an x range — the left/right edges are real data bounds. */\n xBounded: boolean\n /** The author gave a y range — the bottom/top edges are real data bounds. */\n yBounded: boolean\n}\n\n/** The empty box, used as the \"no region\" resting value. */\nexport const EMPTY_REGION_BOX: RegionBox = {\n left: 0,\n right: 0,\n bottom: 0,\n top: 0,\n xBounded: false,\n yBounded: false,\n}\n\n/** Is `box` a real, non-degenerate region — i.e. is there anything to spotlight? */\nexport function hasRegion(box: RegionBox): boolean {\n return box.right > box.left && box.top > box.bottom\n}\n\n/**\n * Resolve a {@link PlotRegion} against the plot's pixel box and the chart's\n * scales. `plot` is the plot rectangle in centred pixel space, and\n * `xScale`/`yScale` the data ranges those edges span.\n *\n * An omitted axis takes the plot's own edges, so the region spans it. A given\n * axis is clamped to the plot, so a range running off the scale still produces\n * a box inside the panel.\n */\nexport function resolveRegionBox(\n region: PlotRegion | null | undefined,\n plot: { left: number; right: number; bottom: number; top: number },\n xScale: Range,\n yScale: Range\n): RegionBox {\n if (!region || (region.x === undefined && region.y === undefined)) {\n return EMPTY_REGION_BOX\n }\n\n const span = (\n range: Range | undefined,\n [min, max]: Range,\n lo: number,\n hi: number\n ): [number, number, boolean] => {\n if (range === undefined || max <= min) return [lo, hi, false]\n const a = Math.min(range[0], range[1])\n const b = Math.max(range[0], range[1])\n const toPx = (v: number) => lo + ((v - min) / (max - min)) * (hi - lo)\n return [\n Math.max(lo, Math.min(hi, toPx(a))),\n Math.max(lo, Math.min(hi, toPx(b))),\n true,\n ]\n }\n\n const [left, right, xBounded] = span(region.x, xScale, plot.left, plot.right)\n const [bottom, top, yBounded] = span(region.y, yScale, plot.bottom, plot.top)\n return { left, right, bottom, top, xBounded, yBounded }\n}\n\n/** A bare `{x, y}` — declared locally so this module stays free of node imports. */\ninterface Vector2Like {\n x: number\n y: number\n}\n\n/**\n * The parts of `points` that fall **inside** `box`, as a list of contiguous\n * sub-polylines — the geometry behind the region spotlight: a series is drawn\n * dimmed in full, then these runs are re-drawn over it at full strength.\n *\n * Segments are clipped against the box rather than filtered by endpoint, so a\n * line entering the region mid-segment is cut exactly on the boundary.\n */\nexport function clipPolylineToBox(\n points: Vector2Like[],\n box: RegionBox\n): Vector2Like[][] {\n if (points.length < 2 || !hasRegion(box)) return []\n\n const runs: Vector2Like[][] = []\n let run: Vector2Like[] = []\n\n for (let i = 1; i < points.length; i++) {\n const seg = clipSegment(points[i - 1], points[i], box)\n if (!seg) {\n // Segment misses the box entirely — close whatever run was open.\n if (run.length >= 2) runs.push(run)\n run = []\n continue\n }\n const [a, b] = seg\n // Continue the open run when this clipped segment starts where the last one\n // ended; otherwise the line left the box and came back, which is a new run.\n if (run.length > 0 && samePoint(run[run.length - 1], a)) run.push(b)\n else {\n if (run.length >= 2) runs.push(run)\n run = [a, b]\n }\n }\n if (run.length >= 2) runs.push(run)\n return runs\n}\n\n/** Points within half a pixel are the same point, for run-joining purposes. */\nfunction samePoint(a: Vector2Like, b: Vector2Like): boolean {\n return Math.abs(a.x - b.x) < 0.5 && Math.abs(a.y - b.y) < 0.5\n}\n\n/**\n * Liang–Barsky clip of one segment against the axis-aligned `box`. Returns the\n * surviving `[start, end]`, or `null` when the segment lies wholly outside.\n */\nfunction clipSegment(\n a: Vector2Like,\n b: Vector2Like,\n box: RegionBox\n): [Vector2Like, Vector2Like] | null {\n const dx = b.x - a.x\n const dy = b.y - a.y\n let t0 = 0\n let t1 = 1\n\n // Each edge as (p, q): the segment survives where p·t <= q.\n const edges: [number, number][] = [\n [-dx, a.x - box.left],\n [dx, box.right - a.x],\n [-dy, a.y - box.bottom],\n [dy, box.top - a.y],\n ]\n for (const [p, q] of edges) {\n if (p === 0) {\n // Parallel to this edge: outside it means the whole segment is out.\n if (q < 0) return null\n continue\n }\n const r = q / p\n if (p < 0) {\n if (r > t1) return null\n if (r > t0) t0 = r\n } else {\n if (r < t0) return null\n if (r < t1) t1 = r\n }\n }\n return [\n { x: a.x + t0 * dx, y: a.y + t0 * dy },\n { x: a.x + t1 * dx, y: a.y + t1 * dy },\n ]\n}\n\n// ---- Theme ---------------------------------------------------------------\n\n/**\n * How a spotlit {@link PlotRegion} is drawn — the dashed rules on its bounded\n * edges, the wash filling it, and how far the series fade outside it.\n */\nexport interface RegionConfig {\n /** The dashed rule marking each bounded edge. */\n stroke: Stroke\n /** Wash filling the region, drawn under the series lines. */\n fill: Fill\n /** Opacity the stretch of every series *outside* the region fades to. */\n dimOpacity: number\n}\n\n/** A {@link LineChart}'s full, resolved visual theme. */\nexport interface ChartConfig {\n /**\n * Default per-series line stroke, cycled by series index (`i % length`). A\n * series' own `fill`, when set, overrides just the colour.\n */\n seriesStyles: Stroke[]\n xAxis: AxisConfig\n yAxis: AxisConfig\n /** The plot panel: border, fill, and drop shadow. */\n plotArea: PlotAreaConfig\n legend: LegendConfig\n /** The spotlit-region annotation — see {@link RegionConfig}. */\n region: RegionConfig\n}\n\n/**\n * Author-facing partial theme, passed as {@link LineChart}'s `theme` prop —\n * every branch optional and deep-merged onto {@link DEFAULT_CHART_THEME}.\n */\nexport type ChartTheme = {\n seriesStyles?: Stroke[]\n xAxis?: Partial<AxisConfig>\n yAxis?: Partial<AxisConfig>\n plotArea?: Partial<PlotAreaConfig>\n legend?: LegendTheme\n region?: Partial<RegionConfig>\n}\n\n/**\n * The legend of a line chart: the shared one, with a small rounded-square\n * swatch in place of the wide chip. A bar's or a wedge's swatch stands in for\n * an area of paint and is drawn like one; a line's stands in for a colour, and\n * a square reads as the sample it is.\n */\nconst LINE_CHART_LEGEND: LegendConfig = {\n ...DEFAULT_LEGEND,\n markerStyle: {\n ...DEFAULT_LEGEND.markerStyle,\n width: 20,\n height: 20,\n borderRadius: 6,\n },\n}\n\n/** The chart's out-of-the-box look. */\nexport const DEFAULT_CHART_THEME: ChartConfig = {\n seriesStyles: DEFAULT_SERIES_STROKES,\n xAxis: DEFAULT_X_AXIS,\n yAxis: DEFAULT_Y_AXIS,\n plotArea: DEFAULT_PLOT_AREA,\n legend: LINE_CHART_LEGEND,\n region: {\n // Brighter than the grid and dashed, so the boundary reads as a call-out\n // rather than another axis line; `butt` caps keep the dash lengths exact.\n stroke: {\n weight: 3,\n fill: CHART_PALETTE.regionColor,\n dash: [14, 12],\n cap: \"butt\",\n },\n // A wash barely above the panel — enough to tint the band, not enough to\n // mute the series drawn over it.\n fill: Fills.color(CHART_PALETTE.regionColor, { opacity: 0.08 }),\n dimOpacity: SPOTLIGHT_DIM,\n },\n}\n\nfunction resolveRegionConfig(\n region: Partial<RegionConfig> | undefined,\n previous: RegionConfig\n): RegionConfig {\n return {\n stroke: strokeOps.resolve(region?.stroke ?? previous.stroke),\n fill: fillOps.resolve(region?.fill ?? previous.fill),\n dimOpacity: region?.dimOpacity ?? previous.dimOpacity,\n }\n}\n\nfunction lerpRegionConfig(\n from: RegionConfig,\n to: RegionConfig,\n t: number\n): RegionConfig {\n return {\n stroke: strokeOps.lerp(\n from.stroke as StrokeResolved[],\n to.stroke as StrokeResolved[],\n t\n ),\n fill: fillOps.lerp(\n from.fill as FillResolved[],\n to.fill as FillResolved[],\n t\n ),\n dimOpacity: lerpNumber(from.dimOpacity, to.dimOpacity, t),\n }\n}\n\n/**\n * {@link LineChart}'s `theme` entry's resolve: merges the author's partial\n * {@link ChartTheme} onto `previous` (falling back to\n * {@link DEFAULT_CHART_THEME} on first resolution), fully resolving every leaf.\n */\nexport function resolveChartTheme(\n theme: ChartTheme | undefined,\n previous?: ChartConfig\n): ChartConfig {\n const base = previous ?? DEFAULT_CHART_THEME\n return {\n seriesStyles: resolveSeriesStrokes(theme?.seriesStyles, base.seriesStyles),\n xAxis: resolveAxisConfig(theme?.xAxis, base.xAxis),\n yAxis: resolveAxisConfig(theme?.yAxis, base.yAxis),\n plotArea: resolvePlotArea(theme?.plotArea, base.plotArea),\n legend: resolveLegendConfig(theme?.legend, base.legend),\n region: resolveRegionConfig(theme?.region, base.region),\n }\n}\n\n/** {@link LineChart}'s `theme` entry's lerp — leaf by leaf. */\nexport function lerpChartTheme(\n from: ChartConfig,\n to: ChartConfig,\n t: number\n): ChartConfig {\n return {\n seriesStyles: lerpSeriesStrokes(from.seriesStyles, to.seriesStyles, t),\n xAxis: lerpAxisConfig(from.xAxis, to.xAxis, t),\n yAxis: lerpAxisConfig(from.yAxis, to.yAxis, t),\n plotArea: lerpPlotArea(from.plotArea, to.plotArea, t),\n legend: lerpLegendConfig(from.legend, to.legend, t),\n region: lerpRegionConfig(from.region, to.region, t),\n }\n}\n\n// ---- Scales --------------------------------------------------------------\n\n/**\n * Resolve one series' `[x, y]` points out of the chart's rows: each row's\n * position on the {@link XScale} paired with its own `field` column, sorted\n * along x so the polyline traces left-to-right whatever order the rows arrived\n * in.\n *\n * A row missing its y column contributes a 0 rather than a gap — these charts\n * draw continuous lines, so dropping the point would silently bridge the hole.\n *\n * Takes the resolved scale rather than the column *name*, because what a row's x\n * is depends on what kind of column it came out of: a number, an instant, or a\n * place in the file. See `x-scale.ts`.\n */\nexport function seriesPoints(\n data: DataRecord[],\n x: XScale,\n series: LineSeries\n): [x: number, y: number][] {\n return data\n .map((row, i): [number, number] => [x.at(i), num(row, series.field)])\n .sort((a, b) => a[0] - b[0])\n}\n\n/** Min/max across every series' column, the fallback when unset. */\nexport function yExtent(data: DataRecord[], series: LineSeries[]): Range {\n let lo = Infinity\n let hi = -Infinity\n for (const row of data)\n for (const s of series) {\n const v = num(row, s.field)\n if (v < lo) lo = v\n if (v > hi) hi = v\n }\n return lo <= hi ? [lo, hi] : [0, 1]\n}\n\n/**\n * Effective vertical scale: the explicit `range`, else the data's y extent\n * nudged out to tidy step bounds so markers read as round numbers.\n *\n * `divisions` is how many segments the auto-range is stepped into (one fewer\n * than a marker count) — it only shapes the tidy step when `range` is omitted.\n */\nexport function resolveYScale(\n data: DataRecord[],\n series: LineSeries[],\n range: Range | undefined,\n divisions: number\n): Range {\n if (range) return range\n const [lo, hi] = yExtent(data, series)\n const segments = Math.max(1, Math.round(divisions))\n if (hi === lo) return constantScale(lo, segments)\n const step = niceStep((hi - lo) / segments)\n return [Math.floor(lo / step) * step, Math.ceil(hi / step) * step]\n}\n\n/**\n * The scale for data that never moves. A zero-width scale puts every tick on the\n * same value and the line on the plot's floor, so it is widened to a few steps\n * either side of the value (the step sized to its magnitude, so `5` gets\n * `[3, 7]` and `5000` gets `[3000, 7000]`), or to `[0, 1]` for all zeros, as for\n * no data at all.\n */\nfunction constantScale(value: number, segments: number): Range {\n if (value === 0) return [0, 1]\n const step = niceStep(Math.abs(value) / segments)\n return [value - step, value + step]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/line-chart/shared.ts"],"names":[],"mappings":"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","sourcesContent":["import { Types, type DataRecord, type Vector2 } from \"@motionscript/sdk\"\n\nimport { niceRange, num, type ChartSeries, type Range } from \"../kit/shared\"\nimport type { XScale } from \"./x-scale\"\n\n/** One polyline of a {@link LineChart}: {@link ChartSeries} under the name for what it draws here. */\nexport type LineSeries = ChartSeries\n\n/**\n * A rectangular slice of the plot in data units, the input to `spotlightRegion`\n * and `zoomTo`. Either axis may be omitted: `{ x: [60, 100] }` is a full-height\n * band. A reversed range reads the same as a forward one.\n */\nexport interface PlotRegion {\n /** Omit to span the full plot width. */\n x?: Range\n /** Omit to span the full plot height. */\n y?: Range\n}\n\n/**\n * A {@link PlotRegion} in the plot's centred pixel space (origin at the centre,\n * y-up), the frame the series points live in. An axis the author left unbounded\n * has its edges pinned to the plot's own and draws no rule.\n */\nexport interface RegionBox {\n left: number\n right: number\n bottom: number\n top: number\n xBounded: boolean\n yBounded: boolean\n}\n\n/** The \"no region\" resting value. */\nexport const EMPTY_REGION_BOX: RegionBox = {\n left: 0,\n right: 0,\n bottom: 0,\n top: 0,\n xBounded: false,\n yBounded: false,\n}\n\n/** Whether `box` is non-degenerate, so there is something to spotlight. */\nexport function hasRegion(box: RegionBox): boolean {\n return box.right > box.left && box.top > box.bottom\n}\n\n/**\n * Resolve a {@link PlotRegion} against the plot rectangle (centred pixel space)\n * and the data ranges its edges span. An omitted axis takes the plot's own\n * edges; a given one is clamped to the plot.\n */\nexport function resolveRegionBox(\n region: PlotRegion | null | undefined,\n plot: { left: number; right: number; bottom: number; top: number },\n xScale: Range,\n yScale: Range\n): RegionBox {\n if (!region || (region.x === undefined && region.y === undefined)) {\n return EMPTY_REGION_BOX\n }\n\n const span = (\n range: Range | undefined,\n [min, max]: Range,\n lo: number,\n hi: number\n ): [number, number, boolean] => {\n if (range === undefined || max <= min) return [lo, hi, false]\n const a = Math.min(range[0], range[1])\n const b = Math.max(range[0], range[1])\n const toPx = (v: number) => lo + ((v - min) / (max - min)) * (hi - lo)\n return [\n Math.max(lo, Math.min(hi, toPx(a))),\n Math.max(lo, Math.min(hi, toPx(b))),\n true,\n ]\n }\n\n const [left, right, xBounded] = span(region.x, xScale, plot.left, plot.right)\n const [bottom, top, yBounded] = span(region.y, yScale, plot.bottom, plot.top)\n return { left, right, bottom, top, xBounded, yBounded }\n}\n\n/**\n * The parts of `points` inside `box`, as contiguous sub-polylines: a series is\n * drawn dimmed in full, then these runs are re-drawn at full strength. Segments\n * are clipped, not filtered by endpoint, so a line entering mid-segment is cut\n * exactly on the boundary.\n */\nexport function clipPolylineToBox(\n points: Vector2[],\n box: RegionBox\n): Vector2[][] {\n if (points.length < 2 || !hasRegion(box)) return []\n\n const runs: Vector2[][] = []\n let run: Vector2[] = []\n\n for (let i = 1; i < points.length; i++) {\n const seg = clipSegment(points[i - 1], points[i], box)\n if (!seg) {\n // Misses the box: close the open run.\n if (run.length >= 2) runs.push(run)\n run = []\n continue\n }\n const [a, b] = seg\n // Continues the open run if it starts where the last ended; otherwise the line left and came back.\n if (run.length > 0 && samePoint(run[run.length - 1], a)) run.push(b)\n else {\n if (run.length >= 2) runs.push(run)\n run = [a, b]\n }\n }\n if (run.length >= 2) runs.push(run)\n return runs\n}\n\n/** Points within half a pixel are the same point, for run-joining purposes. */\nfunction samePoint(a: Vector2, b: Vector2): boolean {\n return Math.abs(a.x - b.x) < 0.5 && Math.abs(a.y - b.y) < 0.5\n}\n\n/** Liang-Barsky clip of one segment against `box`: the surviving `[start, end]`, or `null` when wholly outside. */\nfunction clipSegment(\n a: Vector2,\n b: Vector2,\n box: RegionBox\n): [Vector2, Vector2] | null {\n const dx = b.x - a.x\n const dy = b.y - a.y\n let t0 = 0\n let t1 = 1\n\n // Each edge as (p, q): the segment survives where p*t <= q.\n const edges: [number, number][] = [\n [-dx, a.x - box.left],\n [dx, box.right - a.x],\n [-dy, a.y - box.bottom],\n [dy, box.top - a.y],\n ]\n for (const [p, q] of edges) {\n if (p === 0) {\n // Parallel to this edge: outside it, the whole segment is out.\n if (q < 0) return null\n continue\n }\n const r = q / p\n if (p < 0) {\n if (r > t1) return null\n if (r > t0) t0 = r\n } else {\n if (r < t0) return null\n if (r < t1) t1 = r\n }\n }\n return [\n { x: a.x + t0 * dx, y: a.y + t0 * dy },\n { x: a.x + t1 * dx, y: a.y + t1 * dy },\n ]\n}\n\n/**\n * One series' `[x, y]` points, sorted along x so the polyline traces left to\n * right. A row missing its y contributes 0 rather than a gap, since dropping it\n * would silently bridge the hole.\n */\nexport function seriesPoints(\n data: DataRecord[],\n x: XScale,\n series: LineSeries\n): [x: number, y: number][] {\n return data\n .map((row, i): [number, number] => [x.at(i), num(row, series.field)])\n .sort((a, b) => a[0] - b[0])\n}\n\nexport function yExtent(data: DataRecord[], series: LineSeries[]): Range {\n let lo = Infinity\n let hi = -Infinity\n for (const row of data)\n for (const s of series) {\n const v = num(row, s.field)\n if (v < lo) lo = v\n if (v > hi) hi = v\n }\n return lo <= hi ? [lo, hi] : [0, 1]\n}\n\n/**\n * The explicit `range`, else the data's y extent nudged out to tidy step bounds.\n * `divisions` (one fewer than the marker count) only shapes the step when\n * `range` is omitted.\n */\nexport function resolveYScale(\n data: DataRecord[],\n series: LineSeries[],\n range: Range | undefined,\n divisions: number\n): Range {\n return range ?? niceRange(yExtent(data, series), divisions)\n}\n\n/** How the series sit on one another: apart, each on the sum of those before it, or as shares of the row's total. */\nexport type LineStack = \"none\" | \"stacked\" | \"percent\"\n\n/** One series as drawn: its `[x, y]` points along x, and the line its area fills down to (`null`: the baseline). */\nexport interface StackedSeries {\n readonly points: [x: number, y: number][]\n readonly base: [x: number, y: number][] | null\n}\n\n/**\n * Every series' points under `stack`, rows in x order. A series contributes its\n * values times its weight (`1` when missing), so a series sinking out of a stack\n * lowers the ones above it. `percent` reads each row as shares of its total\n * magnitude, so the top of a full stack is 100.\n */\nexport function stackSeries(\n data: DataRecord[],\n x: XScale,\n series: readonly LineSeries[],\n stack: LineStack,\n weights: readonly number[] = []\n): StackedSeries[] {\n const order = data.map((_, i) => i).sort((a, b) => x.at(a) - x.at(b))\n if (stack !== \"stacked\" && stack !== \"percent\") {\n return series.map((s) => ({ points: order.map((r): [number, number] => [x.at(r), num(data[r], s.field)]), base: null }))\n }\n const weight = (i: number) => Math.max(0, weights[i] ?? 1)\n const totals = order.map((r) => series.reduce((sum, s, i) => sum + Math.abs(num(data[r], s.field) * weight(i)), 0))\n const running = order.map(() => 0)\n return series.map((s, i) => {\n const base = order.map((r, k): [number, number] => [x.at(r), running[k]])\n const points = order.map((r, k): [number, number] => {\n const value = num(data[r], s.field) * weight(i)\n running[k] += stack === \"percent\" ? (totals[k] > 0 ? (value / totals[k]) * 100 : 0) : value\n return [x.at(r), running[k]]\n })\n return { points, base }\n })\n}\n\n/** The lowest and highest y anything in `stacks` reaches, its bases included. */\nexport function stackExtent(stacks: readonly StackedSeries[]): Range {\n let lo = Infinity\n let hi = -Infinity\n for (const { points, base } of stacks)\n for (const [, y] of [...points, ...(base ?? [])]) {\n if (y < lo) lo = y\n if (y > hi) hi = y\n }\n return lo <= hi ? [lo, hi] : [0, 1]\n}\n\n/** How a series' line runs between its points. */\nexport type LineCurve = \"smooth\" | \"linear\" | \"step\"\n\n/** Points along a cubic Bezier segment, its start left out. */\nconst SAMPLES = 16\n\n/**\n * The drawn path through `points` (ordered along x) as a polyline: the points\n * themselves for `linear`; for `step`, level runs that change at the midpoint\n * between neighbours; for `smooth`, a monotone cubic (Fritsch-Carlson, as d3's\n * `curveMonotoneX`), which never overshoots the data between two points.\n */\nexport function curvePoints(points: readonly Vector2[], curve: LineCurve): Vector2[] {\n if (curve === \"step\") {\n const out: Vector2[] = points.length > 0 ? [points[0]] : []\n for (let i = 1; i < points.length; i++) {\n const mid = (points[i - 1].x + points[i].x) / 2\n out.push({ x: mid, y: points[i - 1].y }, { x: mid, y: points[i].y }, points[i])\n }\n return out\n }\n if (curve !== \"smooth\" || points.length < 3) return [...points]\n const tangents = monotoneTangents(points)\n const out: Vector2[] = [points[0]]\n for (let i = 1; i < points.length; i++) {\n const a = points[i - 1]\n const b = points[i]\n const dx = (b.x - a.x) / 3\n const c1 = { x: a.x + dx, y: a.y + dx * tangents[i - 1] }\n const c2 = { x: b.x - dx, y: b.y - dx * tangents[i] }\n for (let k = 1; k <= SAMPLES; k++) {\n const t = k / SAMPLES\n const u = 1 - t\n out.push({\n x: u * u * u * a.x + 3 * u * u * t * c1.x + 3 * u * t * t * c2.x + t * t * t * b.x,\n y: u * u * u * a.y + 3 * u * u * t * c1.y + 3 * u * t * t * c2.y + t * t * t * b.y,\n })\n }\n }\n return out\n}\n\nfunction monotoneTangents(points: readonly Vector2[]): number[] {\n const n = points.length\n const slope = (i: number) => {\n const h = points[i + 1].x - points[i].x\n return h === 0 ? 0 : (points[i + 1].y - points[i].y) / h\n }\n const m = new Array<number>(n).fill(0)\n for (let i = 1; i < n - 1; i++) {\n const h0 = points[i].x - points[i - 1].x\n const h1 = points[i + 1].x - points[i].x\n const s0 = slope(i - 1)\n const s1 = slope(i)\n const p = h0 + h1 === 0 ? 0 : (s0 * h1 + s1 * h0) / (h0 + h1)\n m[i] = (Math.sign(s0) + Math.sign(s1)) * Math.min(Math.abs(s0), Math.abs(s1), 0.5 * Math.abs(p)) || 0\n }\n m[0] = endTangent(slope(0), m[1])\n m[n - 1] = endTangent(slope(n - 2), m[n - 2])\n return m\n}\n\n/** d3's one-sided end tangent, held to the end segment's own direction and to three times its slope, past which it would overshoot. */\nfunction endTangent(secant: number, inner: number): number {\n const t = (3 * secant - inner) / 2\n if (secant === 0 || Math.sign(t) !== Math.sign(secant)) return 0\n return Math.abs(t) > 3 * Math.abs(secant) ? 3 * secant : t\n}\n\n/** The part of `points` (ordered along x) left of `x`, ending on the point where it crosses `x`. */\nexport function cutAt(points: readonly Vector2[], x: number): Vector2[] {\n if (points.length === 0 || x < points[0].x) return []\n const out: Vector2[] = []\n for (let i = 0; i < points.length; i++) {\n const p = points[i]\n if (p.x <= x) {\n out.push(p)\n continue\n }\n const a = points[i - 1]\n if (a && p.x > a.x) out.push({ x, y: a.y + ((p.y - a.y) * (x - a.x)) / (p.x - a.x) })\n break\n }\n return out\n}\n\n/**\n * `polygon` clipped to `box` (Sutherland-Hodgman against each edge in turn).\n * The box is convex, so the result is the part inside, possibly with seams of\n * no width where a concave outline leaves and re-enters, which fill as nothing.\n */\nexport function clipPolygonToBox(polygon: readonly Vector2[], box: RegionBox): Vector2[] {\n if (polygon.length < 3 || !hasRegion(box)) return []\n const edges: ((p: Vector2) => number)[] = [\n (p) => p.x - box.left,\n (p) => box.right - p.x,\n (p) => p.y - box.bottom,\n (p) => box.top - p.y,\n ]\n let out: Vector2[] = [...polygon]\n for (const inside of edges) {\n const input = out\n out = []\n for (let i = 0; i < input.length; i++) {\n const cur = input[i]\n const prev = input[(i + input.length - 1) % input.length]\n const dc = inside(cur)\n const dp = inside(prev)\n if (dc >= 0) {\n if (dp < 0) out.push(crossing(prev, cur, dp, dc))\n out.push(cur)\n } else if (dp >= 0) out.push(crossing(prev, cur, dp, dc))\n }\n if (out.length === 0) return []\n }\n return out\n}\n\nfunction crossing(a: Vector2, b: Vector2, da: number, db: number): Vector2 {\n const t = da / (da - db)\n return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t }\n}\n\n/** A line chart's own look: how each series is stroked, dotted and filled. */\nexport const LINE_MARKS = {\n strokeWidth: Types.number(4, { min: 0, max: 24, step: 0.5, unit: \"px\" }),\n curve: Types.oneOf([\"smooth\", \"linear\", \"step\"] as const),\n dots: Types.boolean(false),\n /** Each dot's diameter. */\n dotSize: Types.number(12, { min: 0, max: 48, step: 1, unit: \"px\" }),\n /** Fill under each line: none, the series colour, or the colour fading to nothing at the baseline. */\n area: Types.oneOf([\"none\", \"solid\", \"gradient\"] as const),\n /** The fill's opacity at the line. */\n areaOpacity: Types.number(0.4, { min: 0, max: 1, step: 0.05 }),\n}\n\nexport const LINE_LABELS = {\n /** Read as the chart is built: each point's value printed above or below it, or none. */\n values: Types.oneOf([\"none\", \"above\", \"below\"] as const, { animatable: false }),\n}\n"]}
|