basalt-ui 1.13.0 → 1.15.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/AGENTS.md +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +38 -2
- package/agent/rules/basalt-charts.md +260 -108
- package/agent/rules/basalt-mantine.md +4 -2
- package/agent/skills/basalt-charts/SKILL.md +114 -64
- package/agent/skills/basalt-design/SKILL.md +4 -3
- package/configs/oxlint-plugin.js +152 -0
- package/configs/oxlint-plugin.test.ts +107 -0
- package/configs/oxlint.json +2 -0
- package/dist/charts/cursor/resolve.d.ts +33 -0
- package/dist/charts/cursor/resolve.d.ts.map +1 -0
- package/dist/charts/cursor/resolve.js +61 -0
- package/dist/charts/cursor/resolve.test.d.ts +2 -0
- package/dist/charts/cursor/resolve.test.d.ts.map +1 -0
- package/dist/charts/cursor/scope.d.ts +17 -0
- package/dist/charts/cursor/scope.d.ts.map +1 -0
- package/dist/charts/cursor/scope.js +20 -0
- package/dist/charts/cursor/store.d.ts +31 -0
- package/dist/charts/cursor/store.d.ts.map +1 -0
- package/dist/charts/cursor/store.js +25 -0
- package/dist/charts/cursor/store.test.d.ts +2 -0
- package/dist/charts/cursor/store.test.d.ts.map +1 -0
- package/dist/charts/hooks/useChartCursor.d.ts +39 -0
- package/dist/charts/hooks/useChartCursor.d.ts.map +1 -0
- package/dist/charts/hooks/useChartCursor.js +104 -0
- package/dist/charts/hooks/useChartSize.d.ts +8 -4
- package/dist/charts/hooks/useChartSize.d.ts.map +1 -1
- package/dist/charts/index.d.ts +16 -13
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +38 -19
- package/dist/charts/kinds/Bars.d.ts +25 -47
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +230 -390
- package/dist/charts/kinds/Donut.d.ts +5 -4
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +17 -14
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +136 -90
- package/dist/charts/kinds/Heatmap.d.ts +15 -11
- package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
- package/dist/charts/kinds/Heatmap.js +38 -22
- package/dist/charts/kinds/MultiLine.d.ts +27 -35
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.js +114 -237
- package/dist/charts/kinds/StackedArea.d.ts +21 -10
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.js +74 -132
- package/dist/charts/kinds/ZonedLine.d.ts +24 -40
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.js +103 -184
- package/dist/charts/layout/auto-margin.d.ts +60 -0
- package/dist/charts/layout/auto-margin.d.ts.map +1 -0
- package/dist/charts/layout/auto-margin.js +37 -0
- package/dist/charts/layout/auto-margin.test.d.ts +2 -0
- package/dist/charts/layout/auto-margin.test.d.ts.map +1 -0
- package/dist/charts/primitives/CartesianChart.d.ts +143 -0
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -0
- package/dist/charts/primitives/CartesianChart.js +349 -0
- package/dist/charts/primitives/CartesianChart.test.d.ts +2 -0
- package/dist/charts/primitives/CartesianChart.test.d.ts.map +1 -0
- package/dist/charts/primitives/ChartFrame.d.ts +16 -5
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +17 -2
- package/dist/charts/primitives/ChartLegend.d.ts +12 -1
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +21 -3
- package/dist/charts/primitives/ChartLegend.test.d.ts +2 -0
- package/dist/charts/primitives/ChartLegend.test.d.ts.map +1 -0
- package/dist/charts/primitives/ChartTooltip.d.ts +16 -16
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +45 -19
- package/dist/charts/primitives/HoverOverlay.d.ts +19 -4
- package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
- package/dist/charts/primitives/HoverOverlay.js +22 -1
- package/dist/charts/primitives/XZoneRects.d.ts +40 -0
- package/dist/charts/primitives/XZoneRects.d.ts.map +1 -0
- package/dist/charts/primitives/XZoneRects.js +28 -0
- package/dist/charts/primitives/XZoneRects.test.d.ts +2 -0
- package/dist/charts/primitives/XZoneRects.test.d.ts.map +1 -0
- package/dist/charts/series.d.ts +16 -1
- package/dist/charts/series.d.ts.map +1 -1
- package/dist/charts/series.js +3 -1
- package/dist/charts/series.test.d.ts +2 -0
- package/dist/charts/series.test.d.ts.map +1 -0
- package/dist/charts/utils/measure-text.d.ts +25 -0
- package/dist/charts/utils/measure-text.d.ts.map +1 -0
- package/dist/charts/utils/measure-text.js +64 -0
- package/dist/charts/utils/measure-text.test.d.ts +2 -0
- package/dist/charts/utils/measure-text.test.d.ts.map +1 -0
- package/dist/content/fence-block.d.ts.map +1 -1
- package/dist/shell/app-sidebar.d.ts +19 -1
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +30 -26
- package/dist/shell/app-sidebar.module.css +11 -0
- package/dist/shell/app-sidebar.module.css.d.ts +1 -0
- package/dist/shell/app-sidebar.test.d.ts +2 -0
- package/dist/shell/app-sidebar.test.d.ts.map +1 -0
- package/dist/shell/index.d.ts +10 -1
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +2 -0
- package/dist/shell/index.test.d.ts +2 -0
- package/dist/shell/index.test.d.ts.map +1 -0
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +1 -1
- package/dist/tokens/chart-margin.test.d.ts +2 -0
- package/dist/tokens/chart-margin.test.d.ts.map +1 -0
- package/dist/tokens/index.d.ts +29 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +10 -0
- package/dist/tokens/palette.d.ts +4 -0
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +3 -0
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/charts/cursor/resolve.test.ts +61 -0
- package/src/charts/cursor/resolve.ts +102 -0
- package/src/charts/cursor/scope.tsx +32 -0
- package/src/charts/cursor/store.test.ts +46 -0
- package/src/charts/cursor/store.ts +53 -0
- package/src/charts/hooks/useChartCursor.ts +163 -0
- package/src/charts/hooks/useChartSize.ts +8 -4
- package/src/charts/index.ts +34 -14
- package/src/charts/kinds/Bars.tsx +327 -460
- package/src/charts/kinds/Donut.tsx +31 -23
- package/src/charts/kinds/DualPanel.tsx +228 -134
- package/src/charts/kinds/Heatmap.tsx +72 -47
- package/src/charts/kinds/MultiLine.tsx +158 -294
- package/src/charts/kinds/StackedArea.tsx +116 -175
- package/src/charts/kinds/ZonedLine.tsx +148 -255
- package/src/charts/layout/auto-margin.test.ts +42 -0
- package/src/charts/layout/auto-margin.ts +110 -0
- package/src/charts/primitives/CartesianChart.test.ts +99 -0
- package/src/charts/primitives/CartesianChart.tsx +566 -0
- package/src/charts/primitives/ChartFrame.test.tsx +33 -0
- package/src/charts/primitives/ChartFrame.tsx +40 -4
- package/src/charts/primitives/ChartLegend.test.tsx +48 -0
- package/src/charts/primitives/ChartLegend.tsx +45 -2
- package/src/charts/primitives/ChartTooltip.tsx +67 -37
- package/src/charts/primitives/HoverOverlay.tsx +39 -3
- package/src/charts/primitives/XZoneRects.test.tsx +63 -0
- package/src/charts/primitives/XZoneRects.tsx +67 -0
- package/src/charts/series.test.ts +61 -0
- package/src/charts/series.ts +18 -1
- package/src/charts/utils/measure-text.test.ts +36 -0
- package/src/charts/utils/measure-text.ts +109 -0
- package/src/content/fence-block.tsx +2 -3
- package/src/shell/app-sidebar.module.css +11 -0
- package/src/shell/app-sidebar.module.css.d.ts +1 -0
- package/src/shell/app-sidebar.test.tsx +98 -0
- package/src/shell/app-sidebar.tsx +22 -0
- package/src/shell/index.test.tsx +35 -0
- package/src/shell/index.tsx +11 -0
- package/src/surfaces.ts +2 -1
- package/src/theme/spacing.test.ts +2 -0
- package/src/tokens/chart-margin.test.ts +39 -0
- package/src/tokens/density.test.ts +1 -0
- package/src/tokens/index.ts +36 -0
- package/src/tokens/palette.ts +3 -0
- package/dist/charts/hooks/useChartTooltip.d.ts +0 -13
- package/dist/charts/hooks/useChartTooltip.d.ts.map +0 -1
- package/dist/charts/hooks/useChartTooltip.js +0 -26
- package/dist/charts/hooks/useHoverSync.d.ts +0 -46
- package/dist/charts/hooks/useHoverSync.d.ts.map +0 -1
- package/dist/charts/hooks/useHoverSync.js +0 -75
- package/dist/charts/hover-context.d.ts +0 -21
- package/dist/charts/hover-context.d.ts.map +0 -1
- package/dist/charts/hover-context.js +0 -12
- package/dist/charts/hover-sync.d.ts +0 -14
- package/dist/charts/hover-sync.d.ts.map +0 -1
- package/dist/charts/hover-sync.js +0 -17
- package/dist/charts/primitives/ResponsiveChart.d.ts +0 -45
- package/dist/charts/primitives/ResponsiveChart.d.ts.map +0 -1
- package/dist/charts/primitives/ResponsiveChart.js +0 -24
- package/src/charts/hooks/useChartTooltip.ts +0 -31
- package/src/charts/hooks/useHoverSync.ts +0 -152
- package/src/charts/hover-context.ts +0 -28
- package/src/charts/hover-sync.tsx +0 -37
- package/src/charts/primitives/ResponsiveChart.tsx +0 -64
|
@@ -1,215 +1,156 @@
|
|
|
1
1
|
import { curveMonotoneX } from '@visx/curve'
|
|
2
|
-
import { GridRows } from '@visx/grid'
|
|
3
|
-
import { Group } from '@visx/group'
|
|
4
|
-
import { scaleLinear, scalePoint } from '@visx/scale'
|
|
5
2
|
import { AreaStack } from '@visx/shape'
|
|
6
|
-
import { memo,
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
ChartTooltip,
|
|
10
|
-
TooltipBody,
|
|
11
|
-
TooltipHeader,
|
|
12
|
-
TooltipRow,
|
|
13
|
-
useTooltipStyles,
|
|
14
|
-
} from '../primitives/ChartTooltip'
|
|
15
|
-
import { ChartFrame, resolveLegend } from '../primitives/ChartFrame'
|
|
16
|
-
import { Crosshair, SeriesDot } from '../primitives/Crosshair'
|
|
17
|
-
import { HoverOverlay } from '../primitives/HoverOverlay'
|
|
18
|
-
import { useHoverSync } from '../hooks/useHoverSync'
|
|
19
|
-
import { deriveTooltipRows } from '../series'
|
|
3
|
+
import { memo, useCallback, useMemo } from 'react'
|
|
4
|
+
import { CartesianChart } from '../primitives/CartesianChart'
|
|
5
|
+
import type { AxisConfig, PlotContext } from '../primitives/CartesianChart'
|
|
20
6
|
import type { ChartLegendConfig, ChartSeries } from '../series'
|
|
21
|
-
import { VX } from '../../tokens'
|
|
22
|
-
import { smartTicks, smartTicksEvery } from '../utils/ticks'
|
|
23
7
|
|
|
24
8
|
export type StackedAreaProps<T> = {
|
|
25
9
|
data: T[]
|
|
26
|
-
/** Fixed height in pixels, forwarded to
|
|
10
|
+
/** Fixed height in pixels, forwarded to `CartesianChart`. Default 240. */
|
|
27
11
|
height?: number
|
|
28
12
|
chartId: string
|
|
29
13
|
getX: (d: T) => string
|
|
30
14
|
/** The stacked bands, bottom to top — the single source of truth for color, legend, and tooltip
|
|
31
15
|
* rows. Typically `mark: 'area'` (rendered as a `bar`-shaped legend/tooltip swatch). */
|
|
32
16
|
series: ChartSeries<T>[]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/**
|
|
17
|
+
/** Axis config for ticks/format/grid. `domain` defaults to the summed per-point stack total
|
|
18
|
+
* (padded ×1.1, or `autoMaxFloor` if higher) rather than a per-series max — the seam a stacked
|
|
19
|
+
* chart needs, since the plotted quantity is the cumulative band top, not any one series' own
|
|
20
|
+
* value. Pass a fixed tuple or your own function to override. */
|
|
21
|
+
y?: AxisConfig<T>
|
|
22
|
+
/** X tick count override. Default: as many as fit. */
|
|
23
|
+
xTicks?: number
|
|
24
|
+
/** Legend config forwarded to `CartesianChart`; `false` disables the legend (sparkline escape).
|
|
39
25
|
* Default `{ placement: 'bottom' }`. */
|
|
40
26
|
legend?: ChartLegendConfig | false
|
|
41
|
-
/** Accessible text alternative, forwarded
|
|
27
|
+
/** Accessible text alternative, forwarded as `aria-label` (+ `role="img"`). */
|
|
42
28
|
ariaLabel?: string
|
|
43
|
-
/** Forwarded to `
|
|
29
|
+
/** Forwarded to `CartesianChart` — see `ChartPending`'s JSDoc for the three-state rationale. */
|
|
44
30
|
isPending?: boolean
|
|
45
31
|
}
|
|
46
32
|
|
|
47
33
|
/**
|
|
48
34
|
* Multi-series stacked-area chart with an optional derived legend (default on) and legend-hover
|
|
49
35
|
* dimming. Each band's fillOpacity is dimmed when a different key is highlighted via the legend.
|
|
50
|
-
*
|
|
36
|
+
*
|
|
37
|
+
* Composes `CartesianChart` (`docs/CHARTS-SPEC.md` §2) — margin, scale, grid, axes, cursor, and
|
|
38
|
+
* tooltip are the primitive's job. This file draws only the `AreaStack` bands.
|
|
39
|
+
*
|
|
40
|
+
* The tooltip must render top-to-bottom (matching the visual stack read from the top), but the
|
|
41
|
+
* marks must stack bottom-to-top (matching `series`' own order) — the two can't share one order.
|
|
42
|
+
* `CartesianChart`'s `series` prop drives BOTH the derived tooltip rows and legend, so `series` is
|
|
43
|
+
* passed reversed to it (tooltip/legend read top-first); the marks then reverse `ctx.visible` back
|
|
44
|
+
* to restore the original bottom-to-top stacking order for `AreaStack`'s `keys`.
|
|
51
45
|
*/
|
|
52
46
|
function StackedAreaInner<T>(props: StackedAreaProps<T>) {
|
|
53
|
-
const {
|
|
54
|
-
|
|
47
|
+
const { data, chartId, getX, series, y, xTicks, height, legend, ariaLabel, isPending } = props
|
|
48
|
+
|
|
49
|
+
// The plotted quantity is the CUMULATIVE stack top, not any one series' value — the built-in
|
|
50
|
+
// 'auto' domain (per-series max) would clip a multi-band stack. Skipped when the caller already
|
|
51
|
+
// supplies a fixed tuple or their own domain function.
|
|
52
|
+
const yConfig = useMemo<AxisConfig<T>>(() => {
|
|
53
|
+
const base = y ?? {}
|
|
54
|
+
if (base.domain !== undefined && base.domain !== 'auto') return base
|
|
55
|
+
const pad = base.autoPad ?? 1.1
|
|
56
|
+
return {
|
|
57
|
+
...base,
|
|
58
|
+
// Sums the VISIBLE bands only, so toggling one off in the legend shrinks the axis instead
|
|
59
|
+
// of leaving a permanent empty gap above the stack.
|
|
60
|
+
domain: (rows: readonly T[], visible: readonly ChartSeries<T>[]) => {
|
|
61
|
+
let maxTotal = 0
|
|
62
|
+
for (const d of rows) {
|
|
63
|
+
let total = 0
|
|
64
|
+
for (const s of visible) total += s.getValue(d) ?? 0
|
|
65
|
+
if (total > maxTotal) maxTotal = total
|
|
66
|
+
}
|
|
67
|
+
return [0, Math.max(maxTotal, base.autoMaxFloor ?? maxTotal) * pad]
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}, [y])
|
|
71
|
+
|
|
72
|
+
// A stacked band's crosshair dot belongs at the CUMULATIVE band top — the edge the reader is
|
|
73
|
+
// tracking — not at the band's own raw value, which sits somewhere inside the fill.
|
|
74
|
+
const stackedCursorValue = useCallback(
|
|
75
|
+
(d: T, s: ChartSeries<T>, visible: readonly ChartSeries<T>[]): number | null => {
|
|
76
|
+
let total = 0
|
|
77
|
+
// `visible` arrives top-to-bottom (the `series.toReversed()` handed to the primitive);
|
|
78
|
+
// stacking accumulates bottom-up.
|
|
79
|
+
for (let i = visible.length - 1; i >= 0; i--) {
|
|
80
|
+
const band = visible[i] as ChartSeries<T>
|
|
81
|
+
total += band.getValue(d) ?? 0
|
|
82
|
+
if (band.key === s.key) return total
|
|
83
|
+
}
|
|
84
|
+
return null
|
|
85
|
+
},
|
|
86
|
+
[],
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
// Memoized: this array is `CartesianChart`'s `series` prop, and a fresh identity every render
|
|
90
|
+
// would bust every memo keyed on it inside the primitive (domains, tick labels, margin, visible).
|
|
91
|
+
const reversedSeries = useMemo(() => series.toReversed(), [series])
|
|
55
92
|
|
|
56
93
|
return (
|
|
57
|
-
<
|
|
58
|
-
|
|
94
|
+
<CartesianChart
|
|
95
|
+
data={data}
|
|
59
96
|
chartId={chartId}
|
|
97
|
+
getX={getX}
|
|
98
|
+
series={reversedSeries}
|
|
99
|
+
y={yConfig}
|
|
100
|
+
cursorValue={stackedCursorValue}
|
|
101
|
+
{...(xTicks !== undefined && { xTicks })}
|
|
60
102
|
{...(height !== undefined && { height })}
|
|
103
|
+
{...(legend !== undefined && { legend })}
|
|
61
104
|
{...(ariaLabel !== undefined && { ariaLabel })}
|
|
62
105
|
{...(isPending !== undefined && { isPending })}
|
|
63
|
-
legend={resolveLegend(legend, {
|
|
64
|
-
highlighted: highlightedKey,
|
|
65
|
-
onHighlight: setHighlightedKey,
|
|
66
|
-
})}
|
|
67
106
|
>
|
|
68
|
-
{(
|
|
69
|
-
</
|
|
107
|
+
{(ctx) => <StackedAreaMarks getX={getX} ctx={ctx} />}
|
|
108
|
+
</CartesianChart>
|
|
70
109
|
)
|
|
71
110
|
}
|
|
72
111
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
numTicksY = 5,
|
|
88
|
-
numTicksX,
|
|
89
|
-
yAutoMaxFloor,
|
|
90
|
-
plot,
|
|
91
|
-
highlightedKey,
|
|
92
|
-
} = props
|
|
93
|
-
|
|
94
|
-
const MARGIN = VX.margin
|
|
95
|
-
const xMax = plot.width - MARGIN.left - MARGIN.right
|
|
96
|
-
const yMax = plot.height - MARGIN.top - MARGIN.bottom
|
|
97
|
-
|
|
98
|
-
const seriesByKey = useMemo(() => new Map(series.map((s) => [s.key, s])), [series])
|
|
99
|
-
const groups = useMemo(() => series.map((s) => s.key), [series])
|
|
100
|
-
|
|
101
|
-
const xScale = useMemo(
|
|
102
|
-
() =>
|
|
103
|
-
scalePoint<string>({
|
|
104
|
-
domain: data.map(getX),
|
|
105
|
-
range: [0, xMax],
|
|
106
|
-
padding: 0,
|
|
107
|
-
}),
|
|
108
|
-
[data, xMax, getX],
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
const yScale = useMemo(() => {
|
|
112
|
-
let maxTotal = 0
|
|
113
|
-
for (const d of data) {
|
|
114
|
-
let total = 0
|
|
115
|
-
for (const s of series) {
|
|
116
|
-
total += s.getValue(d) ?? 0
|
|
117
|
-
}
|
|
118
|
-
if (total > maxTotal) maxTotal = total
|
|
112
|
+
/**
|
|
113
|
+
* The bands. A component rather than an inline render-prop body so the stack bookkeeping can be
|
|
114
|
+
* memoized: the cursor is a shared store, so every pointer frame re-renders every chart, and
|
|
115
|
+
* rebuilding the key list + lookup Map + a copy of `data` per frame is pure waste.
|
|
116
|
+
*/
|
|
117
|
+
function StackedAreaMarks<T>({ getX, ctx }: { getX: (d: T) => string; ctx: PlotContext<T> }) {
|
|
118
|
+
const { visible, xScale, yScale, highlighted } = ctx
|
|
119
|
+
|
|
120
|
+
// Restore bottom-to-top order for the actual stacking geometry (see the component doc above).
|
|
121
|
+
const { groups, seriesByKey } = useMemo(() => {
|
|
122
|
+
const stackOrder = visible.toReversed()
|
|
123
|
+
return {
|
|
124
|
+
groups: stackOrder.map((s) => s.key),
|
|
125
|
+
seriesByKey: new Map(stackOrder.map((s) => [s.key, s])),
|
|
119
126
|
}
|
|
120
|
-
|
|
121
|
-
return scaleLinear<number>({ domain: [0, upper], range: [yMax, 0], nice: true })
|
|
122
|
-
}, [data, series, yMax, yAutoMaxFloor])
|
|
123
|
-
|
|
124
|
-
const tooltipStyles = useTooltipStyles()
|
|
125
|
-
const { tip, tooltipRef, syncedPoint, isDirectHover, handleMouse, handleLeave } = useHoverSync<T>(
|
|
126
|
-
{
|
|
127
|
-
data,
|
|
128
|
-
chartId,
|
|
129
|
-
getKey: getX,
|
|
130
|
-
xScale,
|
|
131
|
-
marginLeft: MARGIN.left,
|
|
132
|
-
},
|
|
133
|
-
)
|
|
127
|
+
}, [visible])
|
|
134
128
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
numTicksX ? smartTicksEvery(data.map(getX), numTicksX) : smartTicks(data.map(getX), xMax),
|
|
138
|
-
[data, xMax, getX, numTicksX],
|
|
139
|
-
)
|
|
129
|
+
// `AreaStack` wants a mutable array; `ctx.data` is readonly. Copy once per data change.
|
|
130
|
+
const rows = useMemo(() => [...ctx.data], [ctx.data])
|
|
140
131
|
|
|
141
132
|
return (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
{(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
fillOpacity={highlightedKey === null || highlightedKey === stack.key ? 1 : 0.25}
|
|
164
|
-
/>
|
|
165
|
-
))
|
|
166
|
-
}
|
|
167
|
-
</AreaStack>
|
|
168
|
-
|
|
169
|
-
{syncedPoint &&
|
|
170
|
-
(() => {
|
|
171
|
-
const sx = xScale(getX(syncedPoint)) ?? 0
|
|
172
|
-
// A dot on each band's TOP edge (its cumulative stacked value), so the tooltip's
|
|
173
|
-
// per-series rows visibly attach to the boundary between that series and the one above.
|
|
174
|
-
let cum = 0
|
|
175
|
-
return (
|
|
176
|
-
<>
|
|
177
|
-
<Crosshair x={sx} top={0} bottom={yMax} />
|
|
178
|
-
{series.map((s) => {
|
|
179
|
-
cum += s.getValue(syncedPoint) ?? 0
|
|
180
|
-
return (
|
|
181
|
-
<SeriesDot key={`dot-${s.key}`} cx={sx} cy={yScale(cum)} color={s.color} />
|
|
182
|
-
)
|
|
183
|
-
})}
|
|
184
|
-
</>
|
|
185
|
-
)
|
|
186
|
-
})()}
|
|
187
|
-
|
|
188
|
-
<AxisLeftNumeric scale={yScale} numTicks={numTicksY} tickFormat={formatValue} />
|
|
189
|
-
<AxisBottomDate top={yMax} scale={xScale} tickValues={tickValues} />
|
|
190
|
-
|
|
191
|
-
<HoverOverlay width={xMax} height={yMax} onMove={handleMouse} onLeave={handleLeave} />
|
|
192
|
-
</Group>
|
|
193
|
-
</svg>
|
|
194
|
-
<ChartTooltip tip={isDirectHover ? tip : null} tooltipRef={tooltipRef} styles={tooltipStyles}>
|
|
195
|
-
{tip && isDirectHover && (
|
|
196
|
-
<>
|
|
197
|
-
<TooltipHeader date={getX(tip.data)} />
|
|
198
|
-
<TooltipBody>
|
|
199
|
-
{deriveTooltipRows(series.toReversed(), tip.data, formatValue).map((row) => (
|
|
200
|
-
<TooltipRow
|
|
201
|
-
key={row.key}
|
|
202
|
-
color={row.color}
|
|
203
|
-
label={row.label}
|
|
204
|
-
value={row.value}
|
|
205
|
-
shape={row.shape}
|
|
206
|
-
/>
|
|
207
|
-
))}
|
|
208
|
-
</TooltipBody>
|
|
209
|
-
</>
|
|
210
|
-
)}
|
|
211
|
-
</ChartTooltip>
|
|
212
|
-
</>
|
|
133
|
+
<AreaStack<T, string>
|
|
134
|
+
data={rows}
|
|
135
|
+
keys={groups}
|
|
136
|
+
x={(d) => xScale(getX(d.data)) ?? 0}
|
|
137
|
+
y0={(d) => yScale(d[0]) ?? 0}
|
|
138
|
+
y1={(d) => yScale(d[1]) ?? 0}
|
|
139
|
+
value={(d, key) => seriesByKey.get(key)?.getValue(d) ?? 0}
|
|
140
|
+
curve={curveMonotoneX}
|
|
141
|
+
>
|
|
142
|
+
{({ stacks, path }) =>
|
|
143
|
+
stacks.map((stack) => (
|
|
144
|
+
<path
|
|
145
|
+
key={`stack-${stack.key}`}
|
|
146
|
+
d={path(stack) || ''}
|
|
147
|
+
fill={seriesByKey.get(stack.key)?.color}
|
|
148
|
+
stroke="transparent"
|
|
149
|
+
fillOpacity={highlighted === null || highlighted === stack.key ? 1 : 0.25}
|
|
150
|
+
/>
|
|
151
|
+
))
|
|
152
|
+
}
|
|
153
|
+
</AreaStack>
|
|
213
154
|
)
|
|
214
155
|
}
|
|
215
156
|
|