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.
Files changed (180) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +38 -2
  4. package/agent/rules/basalt-charts.md +260 -108
  5. package/agent/rules/basalt-mantine.md +4 -2
  6. package/agent/skills/basalt-charts/SKILL.md +114 -64
  7. package/agent/skills/basalt-design/SKILL.md +4 -3
  8. package/configs/oxlint-plugin.js +152 -0
  9. package/configs/oxlint-plugin.test.ts +107 -0
  10. package/configs/oxlint.json +2 -0
  11. package/dist/charts/cursor/resolve.d.ts +33 -0
  12. package/dist/charts/cursor/resolve.d.ts.map +1 -0
  13. package/dist/charts/cursor/resolve.js +61 -0
  14. package/dist/charts/cursor/resolve.test.d.ts +2 -0
  15. package/dist/charts/cursor/resolve.test.d.ts.map +1 -0
  16. package/dist/charts/cursor/scope.d.ts +17 -0
  17. package/dist/charts/cursor/scope.d.ts.map +1 -0
  18. package/dist/charts/cursor/scope.js +20 -0
  19. package/dist/charts/cursor/store.d.ts +31 -0
  20. package/dist/charts/cursor/store.d.ts.map +1 -0
  21. package/dist/charts/cursor/store.js +25 -0
  22. package/dist/charts/cursor/store.test.d.ts +2 -0
  23. package/dist/charts/cursor/store.test.d.ts.map +1 -0
  24. package/dist/charts/hooks/useChartCursor.d.ts +39 -0
  25. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -0
  26. package/dist/charts/hooks/useChartCursor.js +104 -0
  27. package/dist/charts/hooks/useChartSize.d.ts +8 -4
  28. package/dist/charts/hooks/useChartSize.d.ts.map +1 -1
  29. package/dist/charts/index.d.ts +16 -13
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/index.js +38 -19
  32. package/dist/charts/kinds/Bars.d.ts +25 -47
  33. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  34. package/dist/charts/kinds/Bars.js +230 -390
  35. package/dist/charts/kinds/Donut.d.ts +5 -4
  36. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  37. package/dist/charts/kinds/Donut.js +17 -14
  38. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  39. package/dist/charts/kinds/DualPanel.js +136 -90
  40. package/dist/charts/kinds/Heatmap.d.ts +15 -11
  41. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  42. package/dist/charts/kinds/Heatmap.js +38 -22
  43. package/dist/charts/kinds/MultiLine.d.ts +27 -35
  44. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  45. package/dist/charts/kinds/MultiLine.js +114 -237
  46. package/dist/charts/kinds/StackedArea.d.ts +21 -10
  47. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  48. package/dist/charts/kinds/StackedArea.js +74 -132
  49. package/dist/charts/kinds/ZonedLine.d.ts +24 -40
  50. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  51. package/dist/charts/kinds/ZonedLine.js +103 -184
  52. package/dist/charts/layout/auto-margin.d.ts +60 -0
  53. package/dist/charts/layout/auto-margin.d.ts.map +1 -0
  54. package/dist/charts/layout/auto-margin.js +37 -0
  55. package/dist/charts/layout/auto-margin.test.d.ts +2 -0
  56. package/dist/charts/layout/auto-margin.test.d.ts.map +1 -0
  57. package/dist/charts/primitives/CartesianChart.d.ts +143 -0
  58. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -0
  59. package/dist/charts/primitives/CartesianChart.js +349 -0
  60. package/dist/charts/primitives/CartesianChart.test.d.ts +2 -0
  61. package/dist/charts/primitives/CartesianChart.test.d.ts.map +1 -0
  62. package/dist/charts/primitives/ChartFrame.d.ts +16 -5
  63. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  64. package/dist/charts/primitives/ChartFrame.js +17 -2
  65. package/dist/charts/primitives/ChartLegend.d.ts +12 -1
  66. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  67. package/dist/charts/primitives/ChartLegend.js +21 -3
  68. package/dist/charts/primitives/ChartLegend.test.d.ts +2 -0
  69. package/dist/charts/primitives/ChartLegend.test.d.ts.map +1 -0
  70. package/dist/charts/primitives/ChartTooltip.d.ts +16 -16
  71. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  72. package/dist/charts/primitives/ChartTooltip.js +45 -19
  73. package/dist/charts/primitives/HoverOverlay.d.ts +19 -4
  74. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  75. package/dist/charts/primitives/HoverOverlay.js +22 -1
  76. package/dist/charts/primitives/XZoneRects.d.ts +40 -0
  77. package/dist/charts/primitives/XZoneRects.d.ts.map +1 -0
  78. package/dist/charts/primitives/XZoneRects.js +28 -0
  79. package/dist/charts/primitives/XZoneRects.test.d.ts +2 -0
  80. package/dist/charts/primitives/XZoneRects.test.d.ts.map +1 -0
  81. package/dist/charts/series.d.ts +16 -1
  82. package/dist/charts/series.d.ts.map +1 -1
  83. package/dist/charts/series.js +3 -1
  84. package/dist/charts/series.test.d.ts +2 -0
  85. package/dist/charts/series.test.d.ts.map +1 -0
  86. package/dist/charts/utils/measure-text.d.ts +25 -0
  87. package/dist/charts/utils/measure-text.d.ts.map +1 -0
  88. package/dist/charts/utils/measure-text.js +64 -0
  89. package/dist/charts/utils/measure-text.test.d.ts +2 -0
  90. package/dist/charts/utils/measure-text.test.d.ts.map +1 -0
  91. package/dist/content/fence-block.d.ts.map +1 -1
  92. package/dist/shell/app-sidebar.d.ts +19 -1
  93. package/dist/shell/app-sidebar.d.ts.map +1 -1
  94. package/dist/shell/app-sidebar.js +30 -26
  95. package/dist/shell/app-sidebar.module.css +11 -0
  96. package/dist/shell/app-sidebar.module.css.d.ts +1 -0
  97. package/dist/shell/app-sidebar.test.d.ts +2 -0
  98. package/dist/shell/app-sidebar.test.d.ts.map +1 -0
  99. package/dist/shell/index.d.ts +10 -1
  100. package/dist/shell/index.d.ts.map +1 -1
  101. package/dist/shell/index.js +2 -0
  102. package/dist/shell/index.test.d.ts +2 -0
  103. package/dist/shell/index.test.d.ts.map +1 -0
  104. package/dist/surfaces.d.ts +1 -1
  105. package/dist/surfaces.d.ts.map +1 -1
  106. package/dist/surfaces.js +1 -1
  107. package/dist/tokens/chart-margin.test.d.ts +2 -0
  108. package/dist/tokens/chart-margin.test.d.ts.map +1 -0
  109. package/dist/tokens/index.d.ts +29 -0
  110. package/dist/tokens/index.d.ts.map +1 -1
  111. package/dist/tokens/index.js +10 -0
  112. package/dist/tokens/palette.d.ts +4 -0
  113. package/dist/tokens/palette.d.ts.map +1 -1
  114. package/dist/tokens/palette.js +3 -0
  115. package/llms.txt +3 -3
  116. package/package.json +1 -1
  117. package/src/charts/cursor/resolve.test.ts +61 -0
  118. package/src/charts/cursor/resolve.ts +102 -0
  119. package/src/charts/cursor/scope.tsx +32 -0
  120. package/src/charts/cursor/store.test.ts +46 -0
  121. package/src/charts/cursor/store.ts +53 -0
  122. package/src/charts/hooks/useChartCursor.ts +163 -0
  123. package/src/charts/hooks/useChartSize.ts +8 -4
  124. package/src/charts/index.ts +34 -14
  125. package/src/charts/kinds/Bars.tsx +327 -460
  126. package/src/charts/kinds/Donut.tsx +31 -23
  127. package/src/charts/kinds/DualPanel.tsx +228 -134
  128. package/src/charts/kinds/Heatmap.tsx +72 -47
  129. package/src/charts/kinds/MultiLine.tsx +158 -294
  130. package/src/charts/kinds/StackedArea.tsx +116 -175
  131. package/src/charts/kinds/ZonedLine.tsx +148 -255
  132. package/src/charts/layout/auto-margin.test.ts +42 -0
  133. package/src/charts/layout/auto-margin.ts +110 -0
  134. package/src/charts/primitives/CartesianChart.test.ts +99 -0
  135. package/src/charts/primitives/CartesianChart.tsx +566 -0
  136. package/src/charts/primitives/ChartFrame.test.tsx +33 -0
  137. package/src/charts/primitives/ChartFrame.tsx +40 -4
  138. package/src/charts/primitives/ChartLegend.test.tsx +48 -0
  139. package/src/charts/primitives/ChartLegend.tsx +45 -2
  140. package/src/charts/primitives/ChartTooltip.tsx +67 -37
  141. package/src/charts/primitives/HoverOverlay.tsx +39 -3
  142. package/src/charts/primitives/XZoneRects.test.tsx +63 -0
  143. package/src/charts/primitives/XZoneRects.tsx +67 -0
  144. package/src/charts/series.test.ts +61 -0
  145. package/src/charts/series.ts +18 -1
  146. package/src/charts/utils/measure-text.test.ts +36 -0
  147. package/src/charts/utils/measure-text.ts +109 -0
  148. package/src/content/fence-block.tsx +2 -3
  149. package/src/shell/app-sidebar.module.css +11 -0
  150. package/src/shell/app-sidebar.module.css.d.ts +1 -0
  151. package/src/shell/app-sidebar.test.tsx +98 -0
  152. package/src/shell/app-sidebar.tsx +22 -0
  153. package/src/shell/index.test.tsx +35 -0
  154. package/src/shell/index.tsx +11 -0
  155. package/src/surfaces.ts +2 -1
  156. package/src/theme/spacing.test.ts +2 -0
  157. package/src/tokens/chart-margin.test.ts +39 -0
  158. package/src/tokens/density.test.ts +1 -0
  159. package/src/tokens/index.ts +36 -0
  160. package/src/tokens/palette.ts +3 -0
  161. package/dist/charts/hooks/useChartTooltip.d.ts +0 -13
  162. package/dist/charts/hooks/useChartTooltip.d.ts.map +0 -1
  163. package/dist/charts/hooks/useChartTooltip.js +0 -26
  164. package/dist/charts/hooks/useHoverSync.d.ts +0 -46
  165. package/dist/charts/hooks/useHoverSync.d.ts.map +0 -1
  166. package/dist/charts/hooks/useHoverSync.js +0 -75
  167. package/dist/charts/hover-context.d.ts +0 -21
  168. package/dist/charts/hover-context.d.ts.map +0 -1
  169. package/dist/charts/hover-context.js +0 -12
  170. package/dist/charts/hover-sync.d.ts +0 -14
  171. package/dist/charts/hover-sync.d.ts.map +0 -1
  172. package/dist/charts/hover-sync.js +0 -17
  173. package/dist/charts/primitives/ResponsiveChart.d.ts +0 -45
  174. package/dist/charts/primitives/ResponsiveChart.d.ts.map +0 -1
  175. package/dist/charts/primitives/ResponsiveChart.js +0 -24
  176. package/src/charts/hooks/useChartTooltip.ts +0 -31
  177. package/src/charts/hooks/useHoverSync.ts +0 -152
  178. package/src/charts/hover-context.ts +0 -28
  179. package/src/charts/hover-sync.tsx +0 -37
  180. 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, useMemo, useState } from 'react'
7
- import { AxisBottomDate, AxisLeftNumeric } from '../primitives/Axes'
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 the internal `ChartFrame`. Default 240. */
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
- formatValue: (v: number) => string
34
- yLabel?: string
35
- numTicksY?: number
36
- numTicksX?: number
37
- yAutoMaxFloor?: number
38
- /** Legend config forwarded to `ChartFrame`; `false` disables the legend (sparkline escape).
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 to `ChartFrame` as `aria-label` (+ `role="img"`). */
27
+ /** Accessible text alternative, forwarded as `aria-label` (+ `role="img"`). */
42
28
  ariaLabel?: string
43
- /** Forwarded to `ChartFrame` — see `ChartPending`'s JSDoc for the three-state rationale. */
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
- * The tooltip renders rows in reversed (top-to-bottom) stack order so it matches the visual stack.
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 { series, chartId, height, legend, ariaLabel, isPending } = props
54
- const [highlightedKey, setHighlightedKey] = useState<string | null>(null)
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
- <ChartFrame
58
- series={series}
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
- {(plot) => <StackedAreaPlot {...props} plot={plot} highlightedKey={highlightedKey} />}
69
- </ChartFrame>
107
+ {(ctx) => <StackedAreaMarks getX={getX} ctx={ctx} />}
108
+ </CartesianChart>
70
109
  )
71
110
  }
72
111
 
73
- type StackedAreaPlotProps<T> = StackedAreaProps<T> & {
74
- plot: { width: number; height: number }
75
- highlightedKey: string | null
76
- }
77
-
78
- /** The measured plot — split from {@link StackedAreaInner} so its scale/hover-sync hooks only run
79
- * once `ChartFrame` has resolved a non-empty plot rect. */
80
- function StackedAreaPlot<T>(props: StackedAreaPlotProps<T>) {
81
- const {
82
- data,
83
- chartId,
84
- getX,
85
- series,
86
- formatValue,
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
- const upper = Math.max(maxTotal, yAutoMaxFloor ?? maxTotal) * 1.1
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
- const tickValues = useMemo(
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
- <svg width={plot.width} height={plot.height}>
144
- <Group left={MARGIN.left} top={MARGIN.top}>
145
- <GridRows scale={yScale} width={xMax} stroke={VX.grid} numTicks={numTicksY} />
146
-
147
- <AreaStack<T, string>
148
- data={data}
149
- keys={groups}
150
- x={(d) => xScale(getX(d.data)) ?? 0}
151
- y0={(d) => yScale(d[0]) ?? 0}
152
- y1={(d) => yScale(d[1]) ?? 0}
153
- value={(d, key) => seriesByKey.get(key)?.getValue(d) ?? 0}
154
- curve={curveMonotoneX}
155
- >
156
- {({ stacks, path }) =>
157
- stacks.map((stack) => (
158
- <path
159
- key={`stack-${stack.key}`}
160
- d={path(stack) || ''}
161
- fill={seriesByKey.get(stack.key)?.color}
162
- stroke="transparent"
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