@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
package/src/line-chart/shared.ts
CHANGED
|
@@ -1,109 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
* Types and pure helpers specific to the {@link LineChart} family: the traced
|
|
3
|
-
* polylines, the spotlit region they dim outside of, and the theme those two
|
|
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
|
-
*/
|
|
1
|
+
import { Types, type DataRecord, type Vector2 } from "@motionscript/sdk"
|
|
17
2
|
|
|
18
|
-
import {
|
|
19
|
-
Fills,
|
|
20
|
-
fillOps,
|
|
21
|
-
lerpNumber,
|
|
22
|
-
strokeOps,
|
|
23
|
-
type DataRecord,
|
|
24
|
-
type Fill,
|
|
25
|
-
type FillResolved,
|
|
26
|
-
type Stroke,
|
|
27
|
-
type StrokeResolved,
|
|
28
|
-
} from "@motionscript/sdk"
|
|
29
|
-
|
|
30
|
-
import {
|
|
31
|
-
CHART_PALETTE,
|
|
32
|
-
DEFAULT_LEGEND,
|
|
33
|
-
DEFAULT_PLOT_AREA,
|
|
34
|
-
DEFAULT_SERIES_STROKES,
|
|
35
|
-
DEFAULT_X_AXIS,
|
|
36
|
-
DEFAULT_Y_AXIS,
|
|
37
|
-
SPOTLIGHT_DIM,
|
|
38
|
-
lerpAxisConfig,
|
|
39
|
-
lerpLegendConfig,
|
|
40
|
-
lerpPlotArea,
|
|
41
|
-
lerpSeriesStrokes,
|
|
42
|
-
niceStep,
|
|
43
|
-
num,
|
|
44
|
-
resolveAxisConfig,
|
|
45
|
-
resolveLegendConfig,
|
|
46
|
-
resolvePlotArea,
|
|
47
|
-
resolveSeriesStrokes,
|
|
48
|
-
type AxisConfig,
|
|
49
|
-
type ChartSeries,
|
|
50
|
-
type LegendConfig,
|
|
51
|
-
type LegendTheme,
|
|
52
|
-
type PlotAreaConfig,
|
|
53
|
-
type Range,
|
|
54
|
-
} from "../kit/shared"
|
|
3
|
+
import { niceRange, num, type ChartSeries, type Range } from "../kit/shared"
|
|
55
4
|
import type { XScale } from "./x-scale"
|
|
56
5
|
|
|
57
|
-
/**
|
|
58
|
-
* One series in a {@link LineChart}: a polyline traced through the values in one
|
|
59
|
-
* column of the chart's `data`, drawn in `fill` (or the theme's `seriesStyles`
|
|
60
|
-
* palette when omitted) and captioned by `label` in the legend.
|
|
61
|
-
*
|
|
62
|
-
* The family-wide {@link ChartSeries} under a name that says what it draws here;
|
|
63
|
-
* the chart's own `xField` prop names the column carrying the shared x values,
|
|
64
|
-
* so every series is read off the same rows — one row per sample, one column per
|
|
65
|
-
* series.
|
|
66
|
-
*/
|
|
6
|
+
/** One polyline of a {@link LineChart}: {@link ChartSeries} under the name for what it draws here. */
|
|
67
7
|
export type LineSeries = ChartSeries
|
|
68
8
|
|
|
69
|
-
// ---- Plot regions -------------------------------------------------------
|
|
70
|
-
|
|
71
9
|
/**
|
|
72
|
-
* A rectangular slice of the plot
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* Either axis may be omitted, and that's the point: `{ x: [60, 100] }` is a
|
|
76
|
-
* vertical band spanning the full height, `{ y: [8, 10] }` a horizontal band
|
|
77
|
-
* spanning the full width, and both together a box. A reversed range is read
|
|
78
|
-
* the same as the forward one — a region is a span, not a direction.
|
|
10
|
+
* A rectangular slice of the plot in data units, the input to `spotlightRegion`
|
|
11
|
+
* and `zoomTo`. Either axis may be omitted: `{ x: [60, 100] }` is a full-height
|
|
12
|
+
* band. A reversed range reads the same as a forward one.
|
|
79
13
|
*/
|
|
80
14
|
export interface PlotRegion {
|
|
81
|
-
/**
|
|
15
|
+
/** Omit to span the full plot width. */
|
|
82
16
|
x?: Range
|
|
83
|
-
/**
|
|
17
|
+
/** Omit to span the full plot height. */
|
|
84
18
|
y?: Range
|
|
85
19
|
}
|
|
86
20
|
|
|
87
21
|
/**
|
|
88
|
-
* A {@link PlotRegion}
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* `xBounded`/`yBounded` carry which axes the author actually constrained: an
|
|
93
|
-
* unbounded axis has its edges pinned to the plot's own and draws no rule.
|
|
22
|
+
* A {@link PlotRegion} in the plot's centred pixel space (origin at the centre,
|
|
23
|
+
* y-up), the frame the series points live in. An axis the author left unbounded
|
|
24
|
+
* has its edges pinned to the plot's own and draws no rule.
|
|
94
25
|
*/
|
|
95
26
|
export interface RegionBox {
|
|
96
27
|
left: number
|
|
97
28
|
right: number
|
|
98
29
|
bottom: number
|
|
99
30
|
top: number
|
|
100
|
-
/** The author gave an x range — the left/right edges are real data bounds. */
|
|
101
31
|
xBounded: boolean
|
|
102
|
-
/** The author gave a y range — the bottom/top edges are real data bounds. */
|
|
103
32
|
yBounded: boolean
|
|
104
33
|
}
|
|
105
34
|
|
|
106
|
-
/** The
|
|
35
|
+
/** The "no region" resting value. */
|
|
107
36
|
export const EMPTY_REGION_BOX: RegionBox = {
|
|
108
37
|
left: 0,
|
|
109
38
|
right: 0,
|
|
@@ -113,19 +42,15 @@ export const EMPTY_REGION_BOX: RegionBox = {
|
|
|
113
42
|
yBounded: false,
|
|
114
43
|
}
|
|
115
44
|
|
|
116
|
-
/**
|
|
45
|
+
/** Whether `box` is non-degenerate, so there is something to spotlight. */
|
|
117
46
|
export function hasRegion(box: RegionBox): boolean {
|
|
118
47
|
return box.right > box.left && box.top > box.bottom
|
|
119
48
|
}
|
|
120
49
|
|
|
121
50
|
/**
|
|
122
|
-
* Resolve a {@link PlotRegion} against the plot
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* An omitted axis takes the plot's own edges, so the region spans it. A given
|
|
127
|
-
* axis is clamped to the plot, so a range running off the scale still produces
|
|
128
|
-
* a box inside the panel.
|
|
51
|
+
* Resolve a {@link PlotRegion} against the plot rectangle (centred pixel space)
|
|
52
|
+
* and the data ranges its edges span. An omitted axis takes the plot's own
|
|
53
|
+
* edges; a given one is clamped to the plot.
|
|
129
54
|
*/
|
|
130
55
|
export function resolveRegionBox(
|
|
131
56
|
region: PlotRegion | null | undefined,
|
|
@@ -159,40 +84,31 @@ export function resolveRegionBox(
|
|
|
159
84
|
return { left, right, bottom, top, xBounded, yBounded }
|
|
160
85
|
}
|
|
161
86
|
|
|
162
|
-
/** A bare `{x, y}` — declared locally so this module stays free of node imports. */
|
|
163
|
-
interface Vector2Like {
|
|
164
|
-
x: number
|
|
165
|
-
y: number
|
|
166
|
-
}
|
|
167
|
-
|
|
168
87
|
/**
|
|
169
|
-
* The parts of `points`
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* Segments are clipped against the box rather than filtered by endpoint, so a
|
|
174
|
-
* line entering the region mid-segment is cut exactly on the boundary.
|
|
88
|
+
* The parts of `points` inside `box`, as contiguous sub-polylines: a series is
|
|
89
|
+
* drawn dimmed in full, then these runs are re-drawn at full strength. Segments
|
|
90
|
+
* are clipped, not filtered by endpoint, so a line entering mid-segment is cut
|
|
91
|
+
* exactly on the boundary.
|
|
175
92
|
*/
|
|
176
93
|
export function clipPolylineToBox(
|
|
177
|
-
points:
|
|
94
|
+
points: Vector2[],
|
|
178
95
|
box: RegionBox
|
|
179
|
-
):
|
|
96
|
+
): Vector2[][] {
|
|
180
97
|
if (points.length < 2 || !hasRegion(box)) return []
|
|
181
98
|
|
|
182
|
-
const runs:
|
|
183
|
-
let run:
|
|
99
|
+
const runs: Vector2[][] = []
|
|
100
|
+
let run: Vector2[] = []
|
|
184
101
|
|
|
185
102
|
for (let i = 1; i < points.length; i++) {
|
|
186
103
|
const seg = clipSegment(points[i - 1], points[i], box)
|
|
187
104
|
if (!seg) {
|
|
188
|
-
//
|
|
105
|
+
// Misses the box: close the open run.
|
|
189
106
|
if (run.length >= 2) runs.push(run)
|
|
190
107
|
run = []
|
|
191
108
|
continue
|
|
192
109
|
}
|
|
193
110
|
const [a, b] = seg
|
|
194
|
-
//
|
|
195
|
-
// ended; otherwise the line left the box and came back, which is a new run.
|
|
111
|
+
// Continues the open run if it starts where the last ended; otherwise the line left and came back.
|
|
196
112
|
if (run.length > 0 && samePoint(run[run.length - 1], a)) run.push(b)
|
|
197
113
|
else {
|
|
198
114
|
if (run.length >= 2) runs.push(run)
|
|
@@ -204,25 +120,22 @@ export function clipPolylineToBox(
|
|
|
204
120
|
}
|
|
205
121
|
|
|
206
122
|
/** Points within half a pixel are the same point, for run-joining purposes. */
|
|
207
|
-
function samePoint(a:
|
|
123
|
+
function samePoint(a: Vector2, b: Vector2): boolean {
|
|
208
124
|
return Math.abs(a.x - b.x) < 0.5 && Math.abs(a.y - b.y) < 0.5
|
|
209
125
|
}
|
|
210
126
|
|
|
211
|
-
/**
|
|
212
|
-
* Liang–Barsky clip of one segment against the axis-aligned `box`. Returns the
|
|
213
|
-
* surviving `[start, end]`, or `null` when the segment lies wholly outside.
|
|
214
|
-
*/
|
|
127
|
+
/** Liang-Barsky clip of one segment against `box`: the surviving `[start, end]`, or `null` when wholly outside. */
|
|
215
128
|
function clipSegment(
|
|
216
|
-
a:
|
|
217
|
-
b:
|
|
129
|
+
a: Vector2,
|
|
130
|
+
b: Vector2,
|
|
218
131
|
box: RegionBox
|
|
219
|
-
): [
|
|
132
|
+
): [Vector2, Vector2] | null {
|
|
220
133
|
const dx = b.x - a.x
|
|
221
134
|
const dy = b.y - a.y
|
|
222
135
|
let t0 = 0
|
|
223
136
|
let t1 = 1
|
|
224
137
|
|
|
225
|
-
// Each edge as (p, q): the segment survives where p
|
|
138
|
+
// Each edge as (p, q): the segment survives where p*t <= q.
|
|
226
139
|
const edges: [number, number][] = [
|
|
227
140
|
[-dx, a.x - box.left],
|
|
228
141
|
[dx, box.right - a.x],
|
|
@@ -231,7 +144,7 @@ function clipSegment(
|
|
|
231
144
|
]
|
|
232
145
|
for (const [p, q] of edges) {
|
|
233
146
|
if (p === 0) {
|
|
234
|
-
// Parallel to this edge: outside it
|
|
147
|
+
// Parallel to this edge: outside it, the whole segment is out.
|
|
235
148
|
if (q < 0) return null
|
|
236
149
|
continue
|
|
237
150
|
}
|
|
@@ -250,170 +163,10 @@ function clipSegment(
|
|
|
250
163
|
]
|
|
251
164
|
}
|
|
252
165
|
|
|
253
|
-
// ---- Theme ---------------------------------------------------------------
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* How a spotlit {@link PlotRegion} is drawn — the dashed rules on its bounded
|
|
257
|
-
* edges, the wash filling it, and how far the series fade outside it.
|
|
258
|
-
*/
|
|
259
|
-
export interface RegionConfig {
|
|
260
|
-
/** The dashed rule marking each bounded edge. */
|
|
261
|
-
stroke: Stroke
|
|
262
|
-
/** Wash filling the region, drawn under the series lines. */
|
|
263
|
-
fill: Fill
|
|
264
|
-
/** Opacity the stretch of every series *outside* the region fades to. */
|
|
265
|
-
dimOpacity: number
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** A {@link LineChart}'s full, resolved visual theme. */
|
|
269
|
-
export interface ChartConfig {
|
|
270
|
-
/**
|
|
271
|
-
* Default per-series line stroke, cycled by series index (`i % length`). A
|
|
272
|
-
* series' own `fill`, when set, overrides just the colour.
|
|
273
|
-
*/
|
|
274
|
-
seriesStyles: Stroke[]
|
|
275
|
-
xAxis: AxisConfig
|
|
276
|
-
yAxis: AxisConfig
|
|
277
|
-
/** The plot panel: border, fill, and drop shadow. */
|
|
278
|
-
plotArea: PlotAreaConfig
|
|
279
|
-
legend: LegendConfig
|
|
280
|
-
/** The spotlit-region annotation — see {@link RegionConfig}. */
|
|
281
|
-
region: RegionConfig
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Author-facing partial theme, passed as {@link LineChart}'s `theme` prop —
|
|
286
|
-
* every branch optional and deep-merged onto {@link DEFAULT_CHART_THEME}.
|
|
287
|
-
*/
|
|
288
|
-
export type ChartTheme = {
|
|
289
|
-
seriesStyles?: Stroke[]
|
|
290
|
-
xAxis?: Partial<AxisConfig>
|
|
291
|
-
yAxis?: Partial<AxisConfig>
|
|
292
|
-
plotArea?: Partial<PlotAreaConfig>
|
|
293
|
-
legend?: LegendTheme
|
|
294
|
-
region?: Partial<RegionConfig>
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* The legend of a line chart: the shared one, with a small rounded-square
|
|
299
|
-
* swatch in place of the wide chip. A bar's or a wedge's swatch stands in for
|
|
300
|
-
* an area of paint and is drawn like one; a line's stands in for a colour, and
|
|
301
|
-
* a square reads as the sample it is.
|
|
302
|
-
*/
|
|
303
|
-
const LINE_CHART_LEGEND: LegendConfig = {
|
|
304
|
-
...DEFAULT_LEGEND,
|
|
305
|
-
markerStyle: {
|
|
306
|
-
...DEFAULT_LEGEND.markerStyle,
|
|
307
|
-
width: 20,
|
|
308
|
-
height: 20,
|
|
309
|
-
borderRadius: 6,
|
|
310
|
-
},
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/** The chart's out-of-the-box look. */
|
|
314
|
-
export const DEFAULT_CHART_THEME: ChartConfig = {
|
|
315
|
-
seriesStyles: DEFAULT_SERIES_STROKES,
|
|
316
|
-
xAxis: DEFAULT_X_AXIS,
|
|
317
|
-
yAxis: DEFAULT_Y_AXIS,
|
|
318
|
-
plotArea: DEFAULT_PLOT_AREA,
|
|
319
|
-
legend: LINE_CHART_LEGEND,
|
|
320
|
-
region: {
|
|
321
|
-
// Brighter than the grid and dashed, so the boundary reads as a call-out
|
|
322
|
-
// rather than another axis line; `butt` caps keep the dash lengths exact.
|
|
323
|
-
stroke: {
|
|
324
|
-
weight: 3,
|
|
325
|
-
fill: CHART_PALETTE.regionColor,
|
|
326
|
-
dash: [14, 12],
|
|
327
|
-
cap: "butt",
|
|
328
|
-
},
|
|
329
|
-
// A wash barely above the panel — enough to tint the band, not enough to
|
|
330
|
-
// mute the series drawn over it.
|
|
331
|
-
fill: Fills.color(CHART_PALETTE.regionColor, { opacity: 0.08 }),
|
|
332
|
-
dimOpacity: SPOTLIGHT_DIM,
|
|
333
|
-
},
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function resolveRegionConfig(
|
|
337
|
-
region: Partial<RegionConfig> | undefined,
|
|
338
|
-
previous: RegionConfig
|
|
339
|
-
): RegionConfig {
|
|
340
|
-
return {
|
|
341
|
-
stroke: strokeOps.resolve(region?.stroke ?? previous.stroke),
|
|
342
|
-
fill: fillOps.resolve(region?.fill ?? previous.fill),
|
|
343
|
-
dimOpacity: region?.dimOpacity ?? previous.dimOpacity,
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function lerpRegionConfig(
|
|
348
|
-
from: RegionConfig,
|
|
349
|
-
to: RegionConfig,
|
|
350
|
-
t: number
|
|
351
|
-
): RegionConfig {
|
|
352
|
-
return {
|
|
353
|
-
stroke: strokeOps.lerp(
|
|
354
|
-
from.stroke as StrokeResolved[],
|
|
355
|
-
to.stroke as StrokeResolved[],
|
|
356
|
-
t
|
|
357
|
-
),
|
|
358
|
-
fill: fillOps.lerp(
|
|
359
|
-
from.fill as FillResolved[],
|
|
360
|
-
to.fill as FillResolved[],
|
|
361
|
-
t
|
|
362
|
-
),
|
|
363
|
-
dimOpacity: lerpNumber(from.dimOpacity, to.dimOpacity, t),
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* {@link LineChart}'s `theme` entry's resolve: merges the author's partial
|
|
369
|
-
* {@link ChartTheme} onto `previous` (falling back to
|
|
370
|
-
* {@link DEFAULT_CHART_THEME} on first resolution), fully resolving every leaf.
|
|
371
|
-
*/
|
|
372
|
-
export function resolveChartTheme(
|
|
373
|
-
theme: ChartTheme | undefined,
|
|
374
|
-
previous?: ChartConfig
|
|
375
|
-
): ChartConfig {
|
|
376
|
-
const base = previous ?? DEFAULT_CHART_THEME
|
|
377
|
-
return {
|
|
378
|
-
seriesStyles: resolveSeriesStrokes(theme?.seriesStyles, base.seriesStyles),
|
|
379
|
-
xAxis: resolveAxisConfig(theme?.xAxis, base.xAxis),
|
|
380
|
-
yAxis: resolveAxisConfig(theme?.yAxis, base.yAxis),
|
|
381
|
-
plotArea: resolvePlotArea(theme?.plotArea, base.plotArea),
|
|
382
|
-
legend: resolveLegendConfig(theme?.legend, base.legend),
|
|
383
|
-
region: resolveRegionConfig(theme?.region, base.region),
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/** {@link LineChart}'s `theme` entry's lerp — leaf by leaf. */
|
|
388
|
-
export function lerpChartTheme(
|
|
389
|
-
from: ChartConfig,
|
|
390
|
-
to: ChartConfig,
|
|
391
|
-
t: number
|
|
392
|
-
): ChartConfig {
|
|
393
|
-
return {
|
|
394
|
-
seriesStyles: lerpSeriesStrokes(from.seriesStyles, to.seriesStyles, t),
|
|
395
|
-
xAxis: lerpAxisConfig(from.xAxis, to.xAxis, t),
|
|
396
|
-
yAxis: lerpAxisConfig(from.yAxis, to.yAxis, t),
|
|
397
|
-
plotArea: lerpPlotArea(from.plotArea, to.plotArea, t),
|
|
398
|
-
legend: lerpLegendConfig(from.legend, to.legend, t),
|
|
399
|
-
region: lerpRegionConfig(from.region, to.region, t),
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// ---- Scales --------------------------------------------------------------
|
|
404
|
-
|
|
405
166
|
/**
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
* in.
|
|
410
|
-
*
|
|
411
|
-
* A row missing its y column contributes a 0 rather than a gap — these charts
|
|
412
|
-
* draw continuous lines, so dropping the point would silently bridge the hole.
|
|
413
|
-
*
|
|
414
|
-
* Takes the resolved scale rather than the column *name*, because what a row's x
|
|
415
|
-
* is depends on what kind of column it came out of: a number, an instant, or a
|
|
416
|
-
* place in the file. See `x-scale.ts`.
|
|
167
|
+
* One series' `[x, y]` points, sorted along x so the polyline traces left to
|
|
168
|
+
* right. A row missing its y contributes 0 rather than a gap, since dropping it
|
|
169
|
+
* would silently bridge the hole.
|
|
417
170
|
*/
|
|
418
171
|
export function seriesPoints(
|
|
419
172
|
data: DataRecord[],
|
|
@@ -425,7 +178,6 @@ export function seriesPoints(
|
|
|
425
178
|
.sort((a, b) => a[0] - b[0])
|
|
426
179
|
}
|
|
427
180
|
|
|
428
|
-
/** Min/max across every series' column, the fallback when unset. */
|
|
429
181
|
export function yExtent(data: DataRecord[], series: LineSeries[]): Range {
|
|
430
182
|
let lo = Infinity
|
|
431
183
|
let hi = -Infinity
|
|
@@ -439,11 +191,9 @@ export function yExtent(data: DataRecord[], series: LineSeries[]): Range {
|
|
|
439
191
|
}
|
|
440
192
|
|
|
441
193
|
/**
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
* `divisions` is how many segments the auto-range is stepped into (one fewer
|
|
446
|
-
* than a marker count) — it only shapes the tidy step when `range` is omitted.
|
|
194
|
+
* The explicit `range`, else the data's y extent nudged out to tidy step bounds.
|
|
195
|
+
* `divisions` (one fewer than the marker count) only shapes the step when
|
|
196
|
+
* `range` is omitted.
|
|
447
197
|
*/
|
|
448
198
|
export function resolveYScale(
|
|
449
199
|
data: DataRecord[],
|
|
@@ -451,23 +201,198 @@ export function resolveYScale(
|
|
|
451
201
|
range: Range | undefined,
|
|
452
202
|
divisions: number
|
|
453
203
|
): Range {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
204
|
+
return range ?? niceRange(yExtent(data, series), divisions)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** How the series sit on one another: apart, each on the sum of those before it, or as shares of the row's total. */
|
|
208
|
+
export type LineStack = "none" | "stacked" | "percent"
|
|
209
|
+
|
|
210
|
+
/** One series as drawn: its `[x, y]` points along x, and the line its area fills down to (`null`: the baseline). */
|
|
211
|
+
export interface StackedSeries {
|
|
212
|
+
readonly points: [x: number, y: number][]
|
|
213
|
+
readonly base: [x: number, y: number][] | null
|
|
460
214
|
}
|
|
461
215
|
|
|
462
216
|
/**
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
* no data at all.
|
|
217
|
+
* Every series' points under `stack`, rows in x order. A series contributes its
|
|
218
|
+
* values times its weight (`1` when missing), so a series sinking out of a stack
|
|
219
|
+
* lowers the ones above it. `percent` reads each row as shares of its total
|
|
220
|
+
* magnitude, so the top of a full stack is 100.
|
|
468
221
|
*/
|
|
469
|
-
function
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
222
|
+
export function stackSeries(
|
|
223
|
+
data: DataRecord[],
|
|
224
|
+
x: XScale,
|
|
225
|
+
series: readonly LineSeries[],
|
|
226
|
+
stack: LineStack,
|
|
227
|
+
weights: readonly number[] = []
|
|
228
|
+
): StackedSeries[] {
|
|
229
|
+
const order = data.map((_, i) => i).sort((a, b) => x.at(a) - x.at(b))
|
|
230
|
+
if (stack !== "stacked" && stack !== "percent") {
|
|
231
|
+
return series.map((s) => ({ points: order.map((r): [number, number] => [x.at(r), num(data[r], s.field)]), base: null }))
|
|
232
|
+
}
|
|
233
|
+
const weight = (i: number) => Math.max(0, weights[i] ?? 1)
|
|
234
|
+
const totals = order.map((r) => series.reduce((sum, s, i) => sum + Math.abs(num(data[r], s.field) * weight(i)), 0))
|
|
235
|
+
const running = order.map(() => 0)
|
|
236
|
+
return series.map((s, i) => {
|
|
237
|
+
const base = order.map((r, k): [number, number] => [x.at(r), running[k]])
|
|
238
|
+
const points = order.map((r, k): [number, number] => {
|
|
239
|
+
const value = num(data[r], s.field) * weight(i)
|
|
240
|
+
running[k] += stack === "percent" ? (totals[k] > 0 ? (value / totals[k]) * 100 : 0) : value
|
|
241
|
+
return [x.at(r), running[k]]
|
|
242
|
+
})
|
|
243
|
+
return { points, base }
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** The lowest and highest y anything in `stacks` reaches, its bases included. */
|
|
248
|
+
export function stackExtent(stacks: readonly StackedSeries[]): Range {
|
|
249
|
+
let lo = Infinity
|
|
250
|
+
let hi = -Infinity
|
|
251
|
+
for (const { points, base } of stacks)
|
|
252
|
+
for (const [, y] of [...points, ...(base ?? [])]) {
|
|
253
|
+
if (y < lo) lo = y
|
|
254
|
+
if (y > hi) hi = y
|
|
255
|
+
}
|
|
256
|
+
return lo <= hi ? [lo, hi] : [0, 1]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** How a series' line runs between its points. */
|
|
260
|
+
export type LineCurve = "smooth" | "linear" | "step"
|
|
261
|
+
|
|
262
|
+
/** Points along a cubic Bezier segment, its start left out. */
|
|
263
|
+
const SAMPLES = 16
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The drawn path through `points` (ordered along x) as a polyline: the points
|
|
267
|
+
* themselves for `linear`; for `step`, level runs that change at the midpoint
|
|
268
|
+
* between neighbours; for `smooth`, a monotone cubic (Fritsch-Carlson, as d3's
|
|
269
|
+
* `curveMonotoneX`), which never overshoots the data between two points.
|
|
270
|
+
*/
|
|
271
|
+
export function curvePoints(points: readonly Vector2[], curve: LineCurve): Vector2[] {
|
|
272
|
+
if (curve === "step") {
|
|
273
|
+
const out: Vector2[] = points.length > 0 ? [points[0]] : []
|
|
274
|
+
for (let i = 1; i < points.length; i++) {
|
|
275
|
+
const mid = (points[i - 1].x + points[i].x) / 2
|
|
276
|
+
out.push({ x: mid, y: points[i - 1].y }, { x: mid, y: points[i].y }, points[i])
|
|
277
|
+
}
|
|
278
|
+
return out
|
|
279
|
+
}
|
|
280
|
+
if (curve !== "smooth" || points.length < 3) return [...points]
|
|
281
|
+
const tangents = monotoneTangents(points)
|
|
282
|
+
const out: Vector2[] = [points[0]]
|
|
283
|
+
for (let i = 1; i < points.length; i++) {
|
|
284
|
+
const a = points[i - 1]
|
|
285
|
+
const b = points[i]
|
|
286
|
+
const dx = (b.x - a.x) / 3
|
|
287
|
+
const c1 = { x: a.x + dx, y: a.y + dx * tangents[i - 1] }
|
|
288
|
+
const c2 = { x: b.x - dx, y: b.y - dx * tangents[i] }
|
|
289
|
+
for (let k = 1; k <= SAMPLES; k++) {
|
|
290
|
+
const t = k / SAMPLES
|
|
291
|
+
const u = 1 - t
|
|
292
|
+
out.push({
|
|
293
|
+
x: u * u * u * a.x + 3 * u * u * t * c1.x + 3 * u * t * t * c2.x + t * t * t * b.x,
|
|
294
|
+
y: u * u * u * a.y + 3 * u * u * t * c1.y + 3 * u * t * t * c2.y + t * t * t * b.y,
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return out
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function monotoneTangents(points: readonly Vector2[]): number[] {
|
|
302
|
+
const n = points.length
|
|
303
|
+
const slope = (i: number) => {
|
|
304
|
+
const h = points[i + 1].x - points[i].x
|
|
305
|
+
return h === 0 ? 0 : (points[i + 1].y - points[i].y) / h
|
|
306
|
+
}
|
|
307
|
+
const m = new Array<number>(n).fill(0)
|
|
308
|
+
for (let i = 1; i < n - 1; i++) {
|
|
309
|
+
const h0 = points[i].x - points[i - 1].x
|
|
310
|
+
const h1 = points[i + 1].x - points[i].x
|
|
311
|
+
const s0 = slope(i - 1)
|
|
312
|
+
const s1 = slope(i)
|
|
313
|
+
const p = h0 + h1 === 0 ? 0 : (s0 * h1 + s1 * h0) / (h0 + h1)
|
|
314
|
+
m[i] = (Math.sign(s0) + Math.sign(s1)) * Math.min(Math.abs(s0), Math.abs(s1), 0.5 * Math.abs(p)) || 0
|
|
315
|
+
}
|
|
316
|
+
m[0] = endTangent(slope(0), m[1])
|
|
317
|
+
m[n - 1] = endTangent(slope(n - 2), m[n - 2])
|
|
318
|
+
return m
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** 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. */
|
|
322
|
+
function endTangent(secant: number, inner: number): number {
|
|
323
|
+
const t = (3 * secant - inner) / 2
|
|
324
|
+
if (secant === 0 || Math.sign(t) !== Math.sign(secant)) return 0
|
|
325
|
+
return Math.abs(t) > 3 * Math.abs(secant) ? 3 * secant : t
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** The part of `points` (ordered along x) left of `x`, ending on the point where it crosses `x`. */
|
|
329
|
+
export function cutAt(points: readonly Vector2[], x: number): Vector2[] {
|
|
330
|
+
if (points.length === 0 || x < points[0].x) return []
|
|
331
|
+
const out: Vector2[] = []
|
|
332
|
+
for (let i = 0; i < points.length; i++) {
|
|
333
|
+
const p = points[i]
|
|
334
|
+
if (p.x <= x) {
|
|
335
|
+
out.push(p)
|
|
336
|
+
continue
|
|
337
|
+
}
|
|
338
|
+
const a = points[i - 1]
|
|
339
|
+
if (a && p.x > a.x) out.push({ x, y: a.y + ((p.y - a.y) * (x - a.x)) / (p.x - a.x) })
|
|
340
|
+
break
|
|
341
|
+
}
|
|
342
|
+
return out
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* `polygon` clipped to `box` (Sutherland-Hodgman against each edge in turn).
|
|
347
|
+
* The box is convex, so the result is the part inside, possibly with seams of
|
|
348
|
+
* no width where a concave outline leaves and re-enters, which fill as nothing.
|
|
349
|
+
*/
|
|
350
|
+
export function clipPolygonToBox(polygon: readonly Vector2[], box: RegionBox): Vector2[] {
|
|
351
|
+
if (polygon.length < 3 || !hasRegion(box)) return []
|
|
352
|
+
const edges: ((p: Vector2) => number)[] = [
|
|
353
|
+
(p) => p.x - box.left,
|
|
354
|
+
(p) => box.right - p.x,
|
|
355
|
+
(p) => p.y - box.bottom,
|
|
356
|
+
(p) => box.top - p.y,
|
|
357
|
+
]
|
|
358
|
+
let out: Vector2[] = [...polygon]
|
|
359
|
+
for (const inside of edges) {
|
|
360
|
+
const input = out
|
|
361
|
+
out = []
|
|
362
|
+
for (let i = 0; i < input.length; i++) {
|
|
363
|
+
const cur = input[i]
|
|
364
|
+
const prev = input[(i + input.length - 1) % input.length]
|
|
365
|
+
const dc = inside(cur)
|
|
366
|
+
const dp = inside(prev)
|
|
367
|
+
if (dc >= 0) {
|
|
368
|
+
if (dp < 0) out.push(crossing(prev, cur, dp, dc))
|
|
369
|
+
out.push(cur)
|
|
370
|
+
} else if (dp >= 0) out.push(crossing(prev, cur, dp, dc))
|
|
371
|
+
}
|
|
372
|
+
if (out.length === 0) return []
|
|
373
|
+
}
|
|
374
|
+
return out
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function crossing(a: Vector2, b: Vector2, da: number, db: number): Vector2 {
|
|
378
|
+
const t = da / (da - db)
|
|
379
|
+
return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** A line chart's own look: how each series is stroked, dotted and filled. */
|
|
383
|
+
export const LINE_MARKS = {
|
|
384
|
+
strokeWidth: Types.number(4, { min: 0, max: 24, step: 0.5, unit: "px" }),
|
|
385
|
+
curve: Types.oneOf(["smooth", "linear", "step"] as const),
|
|
386
|
+
dots: Types.boolean(false),
|
|
387
|
+
/** Each dot's diameter. */
|
|
388
|
+
dotSize: Types.number(12, { min: 0, max: 48, step: 1, unit: "px" }),
|
|
389
|
+
/** Fill under each line: none, the series colour, or the colour fading to nothing at the baseline. */
|
|
390
|
+
area: Types.oneOf(["none", "solid", "gradient"] as const),
|
|
391
|
+
/** The fill's opacity at the line. */
|
|
392
|
+
areaOpacity: Types.number(0.4, { min: 0, max: 1, step: 0.05 }),
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export const LINE_LABELS = {
|
|
396
|
+
/** Read as the chart is built: each point's value printed above or below it, or none. */
|
|
397
|
+
values: Types.oneOf(["none", "above", "below"] as const, { animatable: false }),
|
|
473
398
|
}
|