basalt-ui 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/MIGRATING.md +46 -12
- package/configs/oxlint.json +1 -0
- package/dist/charts/BandStrip.d.ts +7 -7
- package/dist/charts/BandStrip.d.ts.map +1 -1
- package/dist/charts/Bars.d.ts +4 -4
- package/dist/charts/Bars.d.ts.map +1 -1
- package/dist/charts/ChartCard.d.ts +10 -10
- package/dist/charts/ChartCard.d.ts.map +1 -1
- package/dist/charts/ChartCard.js +2 -1
- package/dist/charts/Donut.d.ts +5 -5
- package/dist/charts/Donut.d.ts.map +1 -1
- package/dist/charts/DualPanel.d.ts +9 -9
- package/dist/charts/DualPanel.d.ts.map +1 -1
- package/dist/charts/Heatmap.d.ts +18 -16
- package/dist/charts/Heatmap.d.ts.map +1 -1
- package/dist/charts/Heatmap.js +8 -2
- package/dist/charts/MirroredBars.d.ts +9 -9
- package/dist/charts/MirroredBars.d.ts.map +1 -1
- package/dist/charts/Sparkline.d.ts +9 -5
- package/dist/charts/Sparkline.d.ts.map +1 -1
- package/dist/charts/Sparkline.js +18 -12
- package/dist/charts/StackedArea.d.ts +2 -2
- package/dist/charts/StackedArea.d.ts.map +1 -1
- package/dist/charts/compile.d.ts +17 -1
- package/dist/charts/compile.d.ts.map +1 -1
- package/dist/charts/compile.js +56 -2
- package/dist/charts/index.d.ts +1 -0
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +3 -1
- package/dist/charts/state.d.ts +3 -0
- package/dist/charts/state.d.ts.map +1 -1
- package/dist/charts/state.js +14 -3
- package/dist/charts/types.d.ts +64 -61
- package/dist/charts/types.d.ts.map +1 -1
- package/dist/cli/doctor.js +2 -2
- package/dist/dashboard/index.d.ts +1 -1
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +3 -1
- package/dist/dashboard/stat-card.d.ts +16 -1
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +14 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/charts/BandStrip.tsx +12 -10
- package/src/charts/Bars.tsx +4 -4
- package/src/charts/ChartCard.tsx +17 -11
- package/src/charts/Donut.tsx +5 -5
- package/src/charts/DualPanel.tsx +13 -6
- package/src/charts/Heatmap.tsx +23 -15
- package/src/charts/MirroredBars.tsx +8 -8
- package/src/charts/Sparkline.tsx +14 -6
- package/src/charts/StackedArea.tsx +2 -2
- package/src/charts/compile.ts +92 -1
- package/src/charts/index.ts +1 -0
- package/src/charts/state.tsx +15 -3
- package/src/charts/types.ts +70 -57
- package/src/cli/doctor.ts +2 -2
- package/src/dashboard/index.ts +6 -1
- package/src/dashboard/stat-card.tsx +34 -3
- package/src/index.ts +1 -0
|
@@ -39,6 +39,17 @@ export type StatCardBreakdownRow = {
|
|
|
39
39
|
export type StatCardSparklineSize = {
|
|
40
40
|
width: number;
|
|
41
41
|
height: number;
|
|
42
|
+
/**
|
|
43
|
+
* The card's `query` as a chart state — present only while the sparkline has nothing to draw
|
|
44
|
+
* (pending, or failed with no data), so `<Sparkline {...size} />` forwards it and the tile shows a
|
|
45
|
+
* sized placeholder instead of a hand-rolled `'—'`. Structurally a `ChartState`: this module
|
|
46
|
+
* stays chart-free. Absent without a `query`, and once data has arrived.
|
|
47
|
+
*/
|
|
48
|
+
state?: {
|
|
49
|
+
pending?: boolean;
|
|
50
|
+
error?: unknown;
|
|
51
|
+
empty?: boolean;
|
|
52
|
+
};
|
|
42
53
|
};
|
|
43
54
|
/**
|
|
44
55
|
* `StatCard` re-publishes three NAMED `WidgetHeader` slices (`WidgetHeaderTitleProps` /
|
|
@@ -97,8 +108,12 @@ export type StatCardProps = BasaltProps & SlotStylesProps<StatCardSlot> & Widget
|
|
|
97
108
|
* measured width is the card's own inner width including the bled inset; with `'right'` it is the
|
|
98
109
|
* fixed 72×26 slot (see {@link SPARKLINE_RIGHT_WIDTH} for why that one is not measured).
|
|
99
110
|
*
|
|
111
|
+
* A render prop is a sanctioned pattern here, so `react/no-unstable-nested-components` is
|
|
112
|
+
* configured with `allowAsProps` in the shipped preset. Spread the size (`{...size}`) to forward
|
|
113
|
+
* `state` along with the box.
|
|
114
|
+
*
|
|
100
115
|
* @example
|
|
101
|
-
* sparkline={(
|
|
116
|
+
* sparkline={(size) => <Sparkline ariaLabel="Active users" data={history} {...size} />}
|
|
102
117
|
*/
|
|
103
118
|
sparkline?: ReactNode | ((size: StatCardSparklineSize) => ReactNode);
|
|
104
119
|
/** Where `sparkline` sits. `'bleed'` (default) is today's full-width row bled to the card edges;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../src/dashboard/stat-card.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../src/dashboard/stat-card.tsx"],"names":[],"mappings":"AAiGA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAErD,OAAO,KAAK,EACV,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,iBAAiB,CAAA;AAIxB,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAEzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAInD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,CAAA;AAE/B;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAA;AAEnE;;;;;;;GAOG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAA;CAC7B,CAAA;AAcD;;;;;;;;GAQG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAChE,CAAA;AA6DD;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,aAAa,GAAG,WAAW,GACrC,eAAe,CAAC,YAAY,CAAC,GAC7B,sBAAsB,GACtB,IAAI,CAAC,uBAAuB,EAAE,OAAO,CAAC,GACtC,sBAAsB,GAAG;IACvB;iEAC6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAA;IAC3C;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAA;IAC/B;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE,qBAAqB,KAAK,SAAS,CAAC,CAAA;IACpE;4GACwG;IACxG,kBAAkB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;IACtC;mGAC+F;IAC/F,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB;;;+EAG2E;IAC3E,IAAI,CAAC,EAAE,YAAY,CAAA;CACpB,CAAA;AAEH,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,SAAS,EACT,KAAK,EACL,KAAK,EACL,WAAW,EACX,aAAa,EACb,WAAW,EACX,UAAU,EACV,SAAS,EACT,kBAA4B,EAC5B,OAAO,EACP,IAAI,EACJ,SAAS,EACT,KAAK,EACL,UAAU,GACX,EAAE,aAAa,+BA0Kf"}
|
|
@@ -6,6 +6,7 @@ import { CtlSlot } from "../theme/index.js";
|
|
|
6
6
|
import { VX } from "../tokens/index.js";
|
|
7
7
|
import { cx } from "../common/props.js";
|
|
8
8
|
import { QueryState } from "./query-state.js";
|
|
9
|
+
import { assertQueryStateLike } from "../common/query-state-like.js";
|
|
9
10
|
import classes from "./stat-card.module.css";
|
|
10
11
|
const SPARKLINE_RIGHT_WIDTH = 72;
|
|
11
12
|
const SPARKLINE_RIGHT_HEIGHT = 26;
|
|
@@ -33,6 +34,13 @@ function useSlotWidth(active) {
|
|
|
33
34
|
width
|
|
34
35
|
};
|
|
35
36
|
}
|
|
37
|
+
function sparklineState(query) {
|
|
38
|
+
if (query === void 0) return void 0;
|
|
39
|
+
assertQueryStateLike("StatCard", query);
|
|
40
|
+
if (query.data !== void 0) return void 0;
|
|
41
|
+
if (query.isError) return { error: query.error ?? true };
|
|
42
|
+
return query.fetchStatus === "idle" ? void 0 : { pending: true };
|
|
43
|
+
}
|
|
36
44
|
const TONE_RAIL_WIDTH = 3;
|
|
37
45
|
const TONE_LABEL = {
|
|
38
46
|
good: "Within the good threshold",
|
|
@@ -64,7 +72,12 @@ function StatCard({
|
|
|
64
72
|
const isRender = typeof sparkline === "function";
|
|
65
73
|
const bleeds = sparklinePlacement === "bleed";
|
|
66
74
|
const { ref: slotRef, width: slotWidth } = useSlotWidth(isRender);
|
|
67
|
-
const
|
|
75
|
+
const state = sparklineState(query);
|
|
76
|
+
const measured = {
|
|
77
|
+
width: slotWidth,
|
|
78
|
+
height: SPARKLINE_RIGHT_HEIGHT,
|
|
79
|
+
...state !== void 0 && { state }
|
|
80
|
+
};
|
|
68
81
|
const breakdownRows = breakdown === void 0 || breakdown.length === 0 ? null : /* @__PURE__ */ jsx("dl", { className: classes.breakdown, children: breakdown.map((row) => /* @__PURE__ */ jsxs(
|
|
69
82
|
"div",
|
|
70
83
|
{
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { scrollParentOf, useCoarsePointer } from './common/index.js';
|
|
|
13
13
|
export { createBasaltTheme } from './theme/index.js';
|
|
14
14
|
export { ThemeToggle } from './theme/theme-toggle.js';
|
|
15
15
|
export { BasaltShell, PageBar, PageAside, useSizeClass, PageTitle, type ShellBaseProps, type SidebarBlock, type SettingsMenuItem, } from './shell/index.js';
|
|
16
|
-
export { WidgetHeader, DeltaBadge, Section, StatCard, type StatCardBreakdownRow, type StatCardTone, StatGroup, EmptyState, SettingsSection, SettingsRow, DangerZone, } from './dashboard/index.js';
|
|
16
|
+
export { WidgetHeader, DeltaBadge, Section, StatCard, type StatCardBreakdownRow, type StatCardSparklineSize, type StatCardTone, StatGroup, EmptyState, SettingsSection, SettingsRow, DangerZone, } from './dashboard/index.js';
|
|
17
17
|
export { unwrap } from './query-client.js';
|
|
18
18
|
export type { BasaltRegister, SeriesKey } from './register.js';
|
|
19
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EACL,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAA;AACjC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EACL,SAAS,EACT,QAAQ,EACR,SAAS,EACT,UAAU,EACV,YAAY,EACZ,KAAK,SAAS,GACf,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EACL,WAAW,EACX,OAAO,EACP,SAAS,EACT,YAAY,EACZ,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,gBAAgB,GACtB,MAAM,SAAS,CAAA;AAChB,OAAO,EACL,YAAY,EACZ,UAAU,EACV,OAAO,EACP,QAAQ,EACR,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,SAAS,EACT,UAAU,EACV,eAAe,EACf,WAAW,EACX,UAAU,GACX,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAMvC,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EACL,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAA;AACjC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EACL,SAAS,EACT,QAAQ,EACR,SAAS,EACT,UAAU,EACV,YAAY,EACZ,KAAK,SAAS,GACf,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EACL,WAAW,EACX,OAAO,EACP,SAAS,EACT,YAAY,EACZ,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,gBAAgB,GACtB,MAAM,SAAS,CAAA;AAChB,OAAO,EACL,YAAY,EACZ,UAAU,EACV,OAAO,EACP,QAAQ,EACR,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,SAAS,EACT,UAAU,EACV,eAAe,EACf,WAAW,EACX,UAAU,GACX,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAMvC,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA"}
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# basalt-ui v2.
|
|
1
|
+
# basalt-ui v2.3.0
|
|
2
2
|
# Opinionated framework for Mantine-based React apps — theme, app shell, TanStack Charts, toolchain presets
|
|
3
3
|
#
|
|
4
4
|
# Entry points: use the exact import specifier below; do not guess.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# A subpath's `Install:` line is its required peers; `OptionalPeers:` are import()-only, install on use.
|
|
14
14
|
|
|
15
15
|
Package: basalt-ui
|
|
16
|
-
Version: 2.
|
|
16
|
+
Version: 2.3.0
|
|
17
17
|
|
|
18
18
|
## basalt-ui
|
|
19
19
|
Import: import { ... } from 'basalt-ui'
|
package/package.json
CHANGED
package/src/charts/BandStrip.tsx
CHANGED
|
@@ -39,7 +39,7 @@ export type BandStripSeries<T> = Pick<
|
|
|
39
39
|
* The tooltip row's value for a band in this state. `null` is an ABSENT reading and renders an
|
|
40
40
|
* em dash; omit the function when the state's name is the whole reading ("Not measured").
|
|
41
41
|
*/
|
|
42
|
-
formatValue?: (d: T) => string | null
|
|
42
|
+
formatValue?: ((d: T) => string | null) | undefined
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/** One band: its state, and the two ways to qualify it. */
|
|
@@ -47,23 +47,25 @@ export type BandSpan = {
|
|
|
47
47
|
/** A `states` key: the band's legend entry, fill and tooltip row. */
|
|
48
48
|
state: string
|
|
49
49
|
/** Overrides the state's fill — an intensity ramp WITHIN a state. `alpha(VX.*, …)`, never a hex. */
|
|
50
|
-
fill?: string
|
|
50
|
+
fill?: string | undefined
|
|
51
51
|
/**
|
|
52
52
|
* A shorter, inset bar over the band, for a fact that must not be read off the fill ramp (a
|
|
53
53
|
* renegotiation, a restart). Its color is a `states` entry, so the legend names it.
|
|
54
54
|
*/
|
|
55
|
-
marker?:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
marker?:
|
|
56
|
+
| {
|
|
57
|
+
/** Defaults to the band's own `state`. */
|
|
58
|
+
state?: string | undefined
|
|
59
|
+
/** Vertical inset from the band's top and bottom, in px. Default 6. */
|
|
60
|
+
inset?: number | undefined
|
|
61
|
+
}
|
|
62
|
+
| undefined
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
/** Collapse adjacent data where one band per datum would be thinner than `minBandPx`. */
|
|
64
66
|
export type BandFold<T> = {
|
|
65
67
|
/** Floor on a drawn band's px width. Default 3. */
|
|
66
|
-
minBandPx?: number
|
|
68
|
+
minBandPx?: number | undefined
|
|
67
69
|
/**
|
|
68
70
|
* The consumer's merge of a group of adjacent data into the datum drawn in its place — whether
|
|
69
71
|
* that is a max, a sum or a recomputed rate is a question about the measurement. Keep the first
|
|
@@ -79,7 +81,7 @@ export type BandStripProps<T, X extends ChartX = ChartX> = Omit<
|
|
|
79
81
|
states: readonly BandStripSeries<T>[]
|
|
80
82
|
/** Which state, fill and marker each datum has. Memoize it: a new function recompiles the chart. */
|
|
81
83
|
getBand: (d: T) => BandSpan
|
|
82
|
-
fold?: BandFold<T>
|
|
84
|
+
fold?: BandFold<T> | undefined
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
const DEFAULT_MIN_BAND_PX = 3
|
package/src/charts/Bars.tsx
CHANGED
|
@@ -27,7 +27,7 @@ export type BarsBar<T> = Pick<
|
|
|
27
27
|
'key' | 'label' | 'color' | 'getValue' | 'formatValue' | 'tooltip' | 'legend' | 'role' | 'note'
|
|
28
28
|
> & {
|
|
29
29
|
/** Default 0.85. */
|
|
30
|
-
fillOpacity?: number
|
|
30
|
+
fillOpacity?: number | undefined
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/** A line overlay; `axis: 'right'` reads the chart's `y2`. */
|
|
@@ -54,11 +54,11 @@ export type BarsProps<T, X extends ChartX = ChartX> = Omit<
|
|
|
54
54
|
'marks' | 'series'
|
|
55
55
|
> & {
|
|
56
56
|
bars: readonly BarsBar<T>[]
|
|
57
|
-
lines?: readonly BarsLine<T>[]
|
|
57
|
+
lines?: readonly BarsLine<T>[] | undefined
|
|
58
58
|
/** Default `'stacked'`. `'normalized'` stacks each x to its share of the total. */
|
|
59
|
-
layout?: 'stacked' | 'grouped' | 'normalized'
|
|
59
|
+
layout?: 'stacked' | 'grouped' | 'normalized' | undefined
|
|
60
60
|
/** Per-bar opacity (a partial week, an estimate). Overrides the series' `fillOpacity`. */
|
|
61
|
-
barOpacity?: (d: T, key: string) => number
|
|
61
|
+
barOpacity?: ((d: T, key: string) => number) | undefined
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const DEFAULT_BAR_OPACITY = 0.85
|
package/src/charts/ChartCard.tsx
CHANGED
|
@@ -92,32 +92,38 @@ export type ChartCardSlot = 'root' | 'header' | 'body'
|
|
|
92
92
|
|
|
93
93
|
export type ChartCardProps = BasaltProps &
|
|
94
94
|
SlotStylesProps<ChartCardSlot> & {
|
|
95
|
-
title?: string
|
|
95
|
+
title?: string | undefined
|
|
96
96
|
icon?: ReactNode
|
|
97
97
|
/** A muted line below the title row; does not by itself render the header. */
|
|
98
|
-
subtitle?: string
|
|
98
|
+
subtitle?: string | undefined
|
|
99
99
|
/** `WidgetHeader`'s info glyph beside the title. */
|
|
100
|
-
info?: string
|
|
100
|
+
info?: string | undefined
|
|
101
101
|
/** A pre-formatted metric value, with `delta` on the hero row. */
|
|
102
|
-
value?: string
|
|
103
|
-
delta?: number
|
|
104
|
-
deltaPeriod?: string
|
|
105
|
-
deltaPolarity?: DeltaPolarity
|
|
102
|
+
value?: string | undefined
|
|
103
|
+
delta?: number | undefined
|
|
104
|
+
deltaPeriod?: string | undefined
|
|
105
|
+
deltaPolarity?: DeltaPolarity | undefined
|
|
106
106
|
/** A mono count tag after the title. */
|
|
107
|
-
count?: number
|
|
107
|
+
count?: number | undefined
|
|
108
108
|
/** Right-aligned controls. */
|
|
109
109
|
actions?: ReactNode
|
|
110
110
|
/** A resolved state replaces `children` with its placeholder; the header stays. */
|
|
111
|
-
state?: ChartState
|
|
111
|
+
state?: ChartState | undefined
|
|
112
112
|
/** Body height while a placeholder shows. Default: the chart height law, i.e. exactly the box an
|
|
113
113
|
* unsized chart takes — pass the chart's own `height` when it states one, so a `<Suspense
|
|
114
114
|
* fallback={<ChartCard state={{ pending: true }} …/>}>` reserves the final size. */
|
|
115
|
-
placeholderHeight?: number | ResponsiveChartHeight
|
|
115
|
+
placeholderHeight?: number | ResponsiveChartHeight | undefined
|
|
116
116
|
/** A retry button or a "clear filters" link under the placeholder. */
|
|
117
117
|
stateAction?: ReactNode
|
|
118
118
|
children?: ReactNode
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/** Drops explicit `undefined`s: `WidgetHeader` is exact-optional, this props type is not. */
|
|
122
|
+
const definedOnly = <T extends object>(o: T): { [K in keyof T]?: Exclude<T[K], undefined> } =>
|
|
123
|
+
Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined)) as {
|
|
124
|
+
[K in keyof T]?: Exclude<T[K], undefined>
|
|
125
|
+
}
|
|
126
|
+
|
|
121
127
|
/** A header renders when one of these is set (a subtitle or delta alone does not). */
|
|
122
128
|
const HEADER_TRIGGERS = ['title', 'info', 'value', 'icon', 'count'] as const
|
|
123
129
|
|
|
@@ -150,7 +156,7 @@ export function ChartCard({
|
|
|
150
156
|
<div className={cx(classes.header, classNames?.header)}>
|
|
151
157
|
<WidgetHeader
|
|
152
158
|
tier="widget"
|
|
153
|
-
{...header}
|
|
159
|
+
{...definedOnly(header)}
|
|
154
160
|
title={header.title ?? ''}
|
|
155
161
|
{...(actions !== undefined && {
|
|
156
162
|
actions: <span data-basalt-tier="widget">{actions}</span>,
|
package/src/charts/Donut.tsx
CHANGED
|
@@ -34,13 +34,13 @@ export type DonutProps<K extends string = string> = BasaltProps & {
|
|
|
34
34
|
colorForKey: (key: K) => string
|
|
35
35
|
formatValue: (v: number) => string
|
|
36
36
|
/** Legend and tooltip label for a key. Default: the key. */
|
|
37
|
-
seriesLabel?: (key: K) => string
|
|
37
|
+
seriesLabel?: ((key: K) => string) | undefined
|
|
38
38
|
/** The ring's centre text, and the smaller line under it. */
|
|
39
|
-
centerLabel?: string
|
|
40
|
-
centerSubLabel?: string
|
|
39
|
+
centerLabel?: string | undefined
|
|
40
|
+
centerSubLabel?: string | undefined
|
|
41
41
|
/** Default 240. */
|
|
42
|
-
height?: number | ResponsiveChartHeight
|
|
43
|
-
state?: ChartState
|
|
42
|
+
height?: number | ResponsiveChartHeight | undefined
|
|
43
|
+
state?: ChartState | undefined
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Slices kept before the rest fold into "Other". */
|
package/src/charts/DualPanel.tsx
CHANGED
|
@@ -64,12 +64,12 @@ export type DualPanelBar<T> = {
|
|
|
64
64
|
/** Tick and tooltip formatter. */
|
|
65
65
|
format: (v: number) => string
|
|
66
66
|
/** The tooltip row's value, for a unit or a decimal `format` cannot carry. */
|
|
67
|
-
formatValue?: (v: number, d: T) => string
|
|
67
|
+
formatValue?: ((v: number, d: T) => string) | undefined
|
|
68
68
|
/** Default `'auto'`: a symmetric `[-maxAbs, maxAbs]` from the data, rounded to nice ticks. A tuple
|
|
69
69
|
* is used verbatim. */
|
|
70
|
-
domain?: readonly [number, number] | 'auto'
|
|
70
|
+
domain?: readonly [number, number] | 'auto' | undefined
|
|
71
71
|
/** When `'auto'`: floors `maxAbs`, so a plateau does not amplify to fill the pane. */
|
|
72
|
-
maxAbsFloor?: number
|
|
72
|
+
maxAbsFloor?: number | undefined
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export type DualPanelProps<T, X extends Date | number = Date | number> = Pick<
|
|
@@ -91,15 +91,22 @@ export type DualPanelProps<T, X extends Date | number = Date | number> = Pick<
|
|
|
91
91
|
series: readonly DualPanelLine<T>[]
|
|
92
92
|
/** Top pane y axis. Default domain `'auto'`: the visible lines' span plus 8%, rounded to nice
|
|
93
93
|
* ticks (not zero-based: the interesting range is often a narrow band far from zero). */
|
|
94
|
-
top?:
|
|
94
|
+
top?:
|
|
95
|
+
| {
|
|
96
|
+
domain?: readonly [number, number] | 'auto' | undefined
|
|
97
|
+
format?: ((v: number) => string) | undefined
|
|
98
|
+
}
|
|
99
|
+
| undefined
|
|
95
100
|
/**
|
|
96
101
|
* Shades the band between two top lines (by key), where both are visible. `aboveFill` colours the
|
|
97
102
|
* side where `to` runs above `from`; omitted, both sides take `fill`.
|
|
98
103
|
*/
|
|
99
|
-
fillBetween?:
|
|
104
|
+
fillBetween?:
|
|
105
|
+
| { from: string; to: string; fill: string; aboveFill?: string | undefined }
|
|
106
|
+
| undefined
|
|
100
107
|
bar: DualPanelBar<T>
|
|
101
108
|
/** The top pane's share of the chart height. Default 0.62. */
|
|
102
|
-
topFraction?: number
|
|
109
|
+
topFraction?: number | undefined
|
|
103
110
|
}
|
|
104
111
|
|
|
105
112
|
type FillRow<T, X> = { x: X; from: number; to: number; series: string; datum: T }
|
package/src/charts/Heatmap.tsx
CHANGED
|
@@ -18,6 +18,7 @@ import type { CSSProperties, ReactElement } from 'react'
|
|
|
18
18
|
import type { BasaltProps } from '../common/props'
|
|
19
19
|
import { alpha, VX } from '../tokens'
|
|
20
20
|
import { resolveContainerClass } from '../tokens/size-classes'
|
|
21
|
+
import { strideTicks } from './compile'
|
|
21
22
|
import type { CartesianDefinition } from './compile'
|
|
22
23
|
import { ChartEngine, idPrefixOf } from './host'
|
|
23
24
|
import { useCoarsePointer } from '../common/use-media-query'
|
|
@@ -37,26 +38,28 @@ export type HeatmapProps<T> = BasaltProps & {
|
|
|
37
38
|
/** The intensity; `null` is an absent cell (flat neutral), never a zero. */
|
|
38
39
|
getValue: (d: T) => number | null
|
|
39
40
|
/** Explicit band order. Default: first seen in `data`. */
|
|
40
|
-
rows?: readonly string[]
|
|
41
|
-
cols?: readonly string[]
|
|
41
|
+
rows?: readonly string[] | undefined
|
|
42
|
+
cols?: readonly string[] | undefined
|
|
42
43
|
/** The ink at full intensity, a `VX.*` ref or a series token. Default `VX.line`. */
|
|
43
|
-
color?: string
|
|
44
|
+
color?: string | undefined
|
|
44
45
|
/** The value range the color scale spans. Default `[0, max]` of the data. */
|
|
45
|
-
domain?: readonly [number, number]
|
|
46
|
+
domain?: readonly [number, number] | undefined
|
|
46
47
|
/** Tooltip, legend-end and `aria` value format. Default: the number as-is. */
|
|
47
|
-
formatValue?: (v: number) => string
|
|
48
|
+
formatValue?: ((v: number) => string) | undefined
|
|
48
49
|
/** Band tick and tooltip header labels. Default: the band key. */
|
|
49
|
-
formatRow?: (row: string) => string
|
|
50
|
-
formatCol?: (col: string) => string
|
|
50
|
+
formatRow?: ((row: string) => string) | undefined
|
|
51
|
+
formatCol?: ((col: string) => string) | undefined
|
|
51
52
|
/** `false` hides the gradient legend; `label` titles it. */
|
|
52
|
-
legend?: false | { label?: string }
|
|
53
|
+
legend?: false | { label?: string | undefined } | undefined
|
|
53
54
|
/** Rows after the value row, per cell (a sample count, a failure reason). Called with the datum. */
|
|
54
|
-
tooltip?: false | { rows?: (d: T) => readonly ChartTooltipRow[] }
|
|
55
|
+
tooltip?: (false | { rows?: ((d: T) => readonly ChartTooltipRow[]) | undefined }) | undefined
|
|
55
56
|
/** A fixed height, or one per container class. Default: by row count (160–420px). */
|
|
56
|
-
height?: number | ResponsiveChartHeight
|
|
57
|
-
/** Fill the parent's height instead
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
height?: number | ResponsiveChartHeight | undefined
|
|
58
|
+
/** Fill the parent's height instead. Overrides `height`. CSS-owned like every other kind's box
|
|
59
|
+
* (`ChartBox`, no measuring, SSR-stable), so the parent needs a definite CSS height — a grid or
|
|
60
|
+
* flex stretch, `h-full`, an explicit height — which no kind can read off an auto-height parent. */
|
|
61
|
+
matchHeight?: boolean | undefined
|
|
62
|
+
state?: ChartState | undefined
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
/** Intensity steps: quantized, so the gradient legend and the cells are the same ramp. */
|
|
@@ -149,8 +152,9 @@ export function Heatmap<T>(props: HeatmapProps<T>): ReactElement {
|
|
|
149
152
|
return {
|
|
150
153
|
chart: ({ width }: ChartBuildContext): StoredChartSpec => {
|
|
151
154
|
const ticks = { line: false as const, size: 0 }
|
|
155
|
+
const cls = resolveContainerClass(width)
|
|
152
156
|
const tickLabels = {
|
|
153
|
-
fontSize:
|
|
157
|
+
fontSize: cls === 'wide' ? VX.text.micro : VX.text.nano,
|
|
154
158
|
opacity: 1,
|
|
155
159
|
thin: true,
|
|
156
160
|
}
|
|
@@ -190,7 +194,11 @@ export function Heatmap<T>(props: HeatmapProps<T>): ReactElement {
|
|
|
190
194
|
scale: scaleBand<string>().domain(cols).paddingInner(0).paddingOuter(0),
|
|
191
195
|
axis: {
|
|
192
196
|
line: AXIS_LINE,
|
|
193
|
-
ticks: {
|
|
197
|
+
ticks: {
|
|
198
|
+
values: strideTicks(cols, cls),
|
|
199
|
+
format: (v) => formatCol(String(v)),
|
|
200
|
+
...ticks,
|
|
201
|
+
},
|
|
194
202
|
tickLabels,
|
|
195
203
|
},
|
|
196
204
|
},
|
|
@@ -35,11 +35,11 @@ export type MirroredBarPane = {
|
|
|
35
35
|
* entry throws: the pane would vanish, which reads as a measured zero. */
|
|
36
36
|
key: string
|
|
37
37
|
/** The pane's upper bound. `'auto'` (default) is its own visible maximum; a number pins it. */
|
|
38
|
-
max?: number | 'auto'
|
|
38
|
+
max?: number | 'auto' | undefined
|
|
39
39
|
/** When `'auto'`: floors the upper bound, so a quiet window does not amplify noise to full height. */
|
|
40
|
-
autoMaxFloor?: number
|
|
40
|
+
autoMaxFloor?: number | undefined
|
|
41
41
|
/** Tick count for this pane's own axis. Default 3. */
|
|
42
|
-
ticks?: number
|
|
42
|
+
ticks?: number | undefined
|
|
43
43
|
/** Tick and tooltip formatter, in the pane's OWN units. */
|
|
44
44
|
format: (v: number) => string
|
|
45
45
|
}
|
|
@@ -71,18 +71,18 @@ export type MirroredBarsProps<T, X extends ChartX = ChartX> = Pick<
|
|
|
71
71
|
/** Drawn below the baseline. */
|
|
72
72
|
down: MirroredBarPane
|
|
73
73
|
/** The up pane's share of the height. Default 0.35: the pair is usually asymmetric. */
|
|
74
|
-
upFraction?: number
|
|
74
|
+
upFraction?: number | undefined
|
|
75
75
|
/** 0..1 of a bar's width that no source sample covers, drawn neutral across both panes. */
|
|
76
|
-
getAbsentFraction?: (d: T) => number
|
|
76
|
+
getAbsentFraction?: ((d: T) => number) | undefined
|
|
77
77
|
/** Per-datum bar opacity: a real but qualified measurement (a short bucket) drawn dimmer. */
|
|
78
|
-
getBarOpacity?: (d: T) => number
|
|
78
|
+
getBarOpacity?: ((d: T) => number) | undefined
|
|
79
79
|
/**
|
|
80
80
|
* Collapse adjacent data where one band per datum would be sub-pixel. `merge` is the consumer's
|
|
81
81
|
* (max, sum, or a rate from summed parts) and must keep `group[0]`'s x, the slot start.
|
|
82
82
|
*/
|
|
83
|
-
fold?: { minBandPx?: number; merge: (group: T[]) => T }
|
|
83
|
+
fold?: { minBandPx?: number | undefined; merge: (group: T[]) => T } | undefined
|
|
84
84
|
/** Default `'leading'`: x is a slot's start. */
|
|
85
|
-
cursorResolution?: CartesianChartProps<T, X>['cursorResolution']
|
|
85
|
+
cursorResolution?: CartesianChartProps<T, X>['cursorResolution'] | undefined
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
const DEFAULT_UP_FRACTION = 0.35
|
package/src/charts/Sparkline.tsx
CHANGED
|
@@ -18,7 +18,9 @@ import type { CartesianDefinition } from './compile'
|
|
|
18
18
|
import { chartCurve } from './curves'
|
|
19
19
|
import { barMark } from './marks'
|
|
20
20
|
import { ChartEngine, idPrefixOf } from './host'
|
|
21
|
+
import { ChartPlaceholder, resolveChartState } from './state'
|
|
21
22
|
import { CHART_THEME } from './theme'
|
|
23
|
+
import type { ChartState } from './types'
|
|
22
24
|
|
|
23
25
|
export type SparklineProps = BasaltProps & {
|
|
24
26
|
/** The accessible name. Required: a chart without one is an unlabeled image. */
|
|
@@ -27,15 +29,18 @@ export type SparklineProps = BasaltProps & {
|
|
|
27
29
|
/** Height in px. */
|
|
28
30
|
height: number
|
|
29
31
|
/** Width in px. Default: the container's, measured by the host. */
|
|
30
|
-
width?: number
|
|
32
|
+
width?: number | undefined
|
|
31
33
|
/** Default `'line'`. */
|
|
32
|
-
mark?: 'line' | 'bar'
|
|
34
|
+
mark?: 'line' | 'bar' | undefined
|
|
33
35
|
/** A `VX.*` ref or a series token. Default `VX.faint`. */
|
|
34
|
-
color?: string
|
|
36
|
+
color?: string | undefined
|
|
35
37
|
/** Paints the last point in `emphasisColor` — the one reading a trend beside a KPI is for. */
|
|
36
|
-
emphasizeLast?: boolean
|
|
38
|
+
emphasizeLast?: boolean | undefined
|
|
37
39
|
/** Default `VX.accent`. */
|
|
38
|
-
emphasisColor?: string
|
|
40
|
+
emphasisColor?: string | undefined
|
|
41
|
+
/** Pending / error / empty, the same contract as every other kind. The placeholder fills the
|
|
42
|
+
* sparkline's own `width` × `height`, so a loading tile does not shift when the trend lands. */
|
|
43
|
+
state?: ChartState | undefined
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
type Point = { i: number; v: number | null }
|
|
@@ -145,6 +150,7 @@ export function Sparkline(props: SparklineProps): ReactElement {
|
|
|
145
150
|
color = VX.faint,
|
|
146
151
|
emphasizeLast = false,
|
|
147
152
|
emphasisColor = VX.accent,
|
|
153
|
+
state,
|
|
148
154
|
className,
|
|
149
155
|
style,
|
|
150
156
|
} = props
|
|
@@ -156,13 +162,15 @@ export function Sparkline(props: SparklineProps): ReactElement {
|
|
|
156
162
|
if (range === null) return null
|
|
157
163
|
return compileSparkline(rows, range, mark, color, emphasizeLast ? emphasisColor : null)
|
|
158
164
|
}, [data, mark, color, emphasizeLast, emphasisColor])
|
|
165
|
+
const resolved = resolveChartState(state)
|
|
159
166
|
return (
|
|
160
167
|
<div
|
|
161
168
|
data-basalt-sparkline=""
|
|
162
169
|
{...(className !== undefined && { className })}
|
|
163
170
|
style={{ width: width ?? '100%', height, minWidth: 0, ...style }}
|
|
164
171
|
>
|
|
165
|
-
{
|
|
172
|
+
{resolved !== null && <ChartPlaceholder state={state} height={height} compact />}
|
|
173
|
+
{resolved === null && definition !== null && (
|
|
166
174
|
<ChartEngine
|
|
167
175
|
definition={definition}
|
|
168
176
|
ariaLabel={ariaLabel}
|
|
@@ -25,7 +25,7 @@ export type StackedAreaBand<T> = Pick<
|
|
|
25
25
|
'key' | 'label' | 'color' | 'getValue' | 'formatValue' | 'tooltip' | 'legend' | 'role' | 'note'
|
|
26
26
|
> & {
|
|
27
27
|
/** Default 0.85. */
|
|
28
|
-
fillOpacity?: number
|
|
28
|
+
fillOpacity?: number | undefined
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export type StackedAreaProps<T, X extends ChartX = ChartX> = Omit<
|
|
@@ -35,7 +35,7 @@ export type StackedAreaProps<T, X extends ChartX = ChartX> = Omit<
|
|
|
35
35
|
/** The bands, bottom to top. */
|
|
36
36
|
series: readonly StackedAreaBand<T>[]
|
|
37
37
|
/** No `scale`: a log axis has no additive zero to stack from. */
|
|
38
|
-
y?: Omit<NonNullable<CartesianChartProps<T, X>['y']>, 'scale'>
|
|
38
|
+
y?: Omit<NonNullable<CartesianChartProps<T, X>['y']>, 'scale'> | undefined
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export function StackedArea<T, X extends ChartX = ChartX>(
|
package/src/charts/compile.ts
CHANGED
|
@@ -39,6 +39,7 @@ import type {
|
|
|
39
39
|
ChartTooltipRow,
|
|
40
40
|
ChartX,
|
|
41
41
|
PlotRow,
|
|
42
|
+
ZoneSpec,
|
|
42
43
|
} from './types'
|
|
43
44
|
|
|
44
45
|
/**
|
|
@@ -196,6 +197,16 @@ function xTickPolicy<X extends ChartX>(
|
|
|
196
197
|
return { count: xTicks }
|
|
197
198
|
}
|
|
198
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Every n-th value, n the smallest stride that leaves at most the class's x tick count (the same
|
|
202
|
+
* ladder a continuous x axis counts to). A band axis labels every value it is given and only drops
|
|
203
|
+
* the ones that collide, so a short-label grid (18 hour columns) would otherwise draw all of them.
|
|
204
|
+
*/
|
|
205
|
+
export function strideTicks<V>(values: readonly V[], cls: ContainerClass): V[] {
|
|
206
|
+
const stride = Math.max(1, Math.ceil(values.length / LADDER[cls].xTicks))
|
|
207
|
+
return values.filter((_, i) => i % stride === 0)
|
|
208
|
+
}
|
|
209
|
+
|
|
199
210
|
export function xGuide<X extends ChartX>(opts: {
|
|
200
211
|
model: XScaleModel<X>
|
|
201
212
|
xTicks: number | readonly X[] | undefined
|
|
@@ -239,6 +250,86 @@ export function areaGradient(index: number, color: string): ChartGradient {
|
|
|
239
250
|
}
|
|
240
251
|
}
|
|
241
252
|
|
|
253
|
+
const areaTint = (color: string): string =>
|
|
254
|
+
`color-mix(in srgb, ${color} var(--vx-area-top), transparent)`
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* A series area filled by value zone: a vertical gradient whose stops sit at the zone boundaries.
|
|
258
|
+
* The engine writes `<linearGradient>` in the default `objectBoundingBox` units, so offsets are
|
|
259
|
+
* fractions of the AREA's own box, not of the plot: from the larger of the series' maximum and 0 down
|
|
260
|
+
* to the smaller of its minimum and 0, the baseline the area closes to even when the y domain
|
|
261
|
+
* excludes 0. A boundary at value `v` sits at `(ceiling - v) / (ceiling - floor)`. Hard edges:
|
|
262
|
+
* neighbouring zones meet at one offset; overlapping zones never run backwards. `null` when no zone
|
|
263
|
+
* reaches the area (the caller keeps the plain series-colour fade). Linear axes only.
|
|
264
|
+
*/
|
|
265
|
+
export function zoneAreaGradient(
|
|
266
|
+
index: number,
|
|
267
|
+
zones: readonly ZoneSpec[],
|
|
268
|
+
values: readonly (number | null)[],
|
|
269
|
+
): ChartGradient | null {
|
|
270
|
+
let floor = 0
|
|
271
|
+
let ceiling = 0
|
|
272
|
+
for (const v of values) {
|
|
273
|
+
if (v === null || !Number.isFinite(v)) continue
|
|
274
|
+
if (v < floor) floor = v
|
|
275
|
+
if (v > ceiling) ceiling = v
|
|
276
|
+
}
|
|
277
|
+
const span = ceiling - floor
|
|
278
|
+
if (span <= 0) return null
|
|
279
|
+
const offsetOf = (v: number): number => (ceiling - Math.min(ceiling, Math.max(floor, v))) / span
|
|
280
|
+
const reaching = zones
|
|
281
|
+
.filter((z) => !Number.isNaN(z.from) && !Number.isNaN(z.to) && z.to > floor && z.from < ceiling)
|
|
282
|
+
.toSorted((p, q) => q.to - p.to)
|
|
283
|
+
let previous = 0
|
|
284
|
+
const stops = reaching.flatMap((z) =>
|
|
285
|
+
[offsetOf(z.to), offsetOf(z.from)].map((raw) => {
|
|
286
|
+
previous = Math.max(previous, raw)
|
|
287
|
+
return { offset: previous, color: areaTint(z.fill) }
|
|
288
|
+
}),
|
|
289
|
+
)
|
|
290
|
+
if (stops.length === 0) return null
|
|
291
|
+
return { id: areaGradientId(index), x1: 0, y1: 0, x2: 0, y2: 1, stops }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let warnedLogZones = false
|
|
295
|
+
|
|
296
|
+
/** Zone stops are placed linearly in value; a log axis would put them in the wrong place (V2-SPEC
|
|
297
|
+
* § 9.9 precedent): dev throws, production logs once and keeps the series-colour fade. */
|
|
298
|
+
function guardLogZones(onLog: boolean): boolean {
|
|
299
|
+
if (!onLog) return false
|
|
300
|
+
const message = "area: 'zones' cannot use a log axis; use area: true"
|
|
301
|
+
if (process.env.NODE_ENV !== 'production') throw new Error(message)
|
|
302
|
+
// oxlint-disable-next-line no-console -- the production leg of a dev-only throw
|
|
303
|
+
if (!warnedLogZones) console.error(message)
|
|
304
|
+
warnedLogZones = true
|
|
305
|
+
return true
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** The gradient resource for a series' `area` fill: zones, a colour ref, or the series colour. */
|
|
309
|
+
function seriesAreaGradient<T, X extends ChartX>(
|
|
310
|
+
s: ChartSeries<T>,
|
|
311
|
+
index: number,
|
|
312
|
+
input: CartesianInput<T, X>,
|
|
313
|
+
axes: Axes<T>,
|
|
314
|
+
): ChartGradient {
|
|
315
|
+
const plain = areaGradient(
|
|
316
|
+
index,
|
|
317
|
+
typeof s.area === 'string' && s.area !== 'zones' ? s.area : s.color,
|
|
318
|
+
)
|
|
319
|
+
if (s.area !== 'zones') return plain
|
|
320
|
+
const side = axes.onRight(s) ? 'right' : 'left'
|
|
321
|
+
const axisConfig = side === 'right' ? input.y2 : input.y
|
|
322
|
+
if (guardLogZones(axisConfig?.scale === 'log')) return plain
|
|
323
|
+
const zones = (input.zones ?? []).filter((z) => axes.scaleId(z.axisSide) === axes.scaleId(side))
|
|
324
|
+
return (
|
|
325
|
+
zoneAreaGradient(
|
|
326
|
+
index,
|
|
327
|
+
zones,
|
|
328
|
+
input.data.map((d) => s.getValue(d)),
|
|
329
|
+
) ?? plain
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
242
333
|
const isBar = <T>(s: ChartSeries<T>): boolean => s.mark === 'bar'
|
|
243
334
|
|
|
244
335
|
/** The two synthetic totals standing in for a stack's bars in its AUTO domain only: the positive
|
|
@@ -538,7 +629,7 @@ export function compileCartesian<T, X extends ChartX>(
|
|
|
538
629
|
const formatX = (v: ChartValue): string =>
|
|
539
630
|
input.formatX ? input.formatX(v as X) : defaultFormatX(v)
|
|
540
631
|
const gradients = input.series.flatMap((s, i) =>
|
|
541
|
-
s.area ? [
|
|
632
|
+
s.area ? [seriesAreaGradient(s, i, input, axes)] : [],
|
|
542
633
|
)
|
|
543
634
|
|
|
544
635
|
return {
|
package/src/charts/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export {
|
|
|
28
28
|
} from './MirroredBars'
|
|
29
29
|
export { ChartCard, type ChartCardProps, type ChartCardSlot } from './ChartCard'
|
|
30
30
|
export { chartCursor } from './cursor'
|
|
31
|
+
export { chartCurve, type CurveName } from './curves'
|
|
31
32
|
export type {
|
|
32
33
|
AxisConfig,
|
|
33
34
|
CartesianChartProps,
|