basalt-ui 1.16.0 → 1.18.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 (69) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/agent/rules/basalt-charts.md +58 -4
  3. package/agent/skills/basalt-design/SKILL.md +2 -2
  4. package/dist/charts/cursor/resolve.d.ts +29 -5
  5. package/dist/charts/cursor/resolve.d.ts.map +1 -1
  6. package/dist/charts/cursor/resolve.js +25 -4
  7. package/dist/charts/hooks/useChartCursor.d.ts +8 -1
  8. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  9. package/dist/charts/hooks/useChartCursor.js +6 -2
  10. package/dist/charts/index.d.ts +1 -0
  11. package/dist/charts/index.d.ts.map +1 -1
  12. package/dist/charts/kinds/Bars.d.ts +9 -0
  13. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  14. package/dist/charts/kinds/Bars.js +4 -0
  15. package/dist/charts/kinds/DualPanel.d.ts +24 -0
  16. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  17. package/dist/charts/kinds/DualPanel.js +39 -9
  18. package/dist/charts/kinds/MultiLine.d.ts +9 -0
  19. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  20. package/dist/charts/kinds/MultiLine.js +10 -8
  21. package/dist/charts/kinds/StackedArea.d.ts +9 -0
  22. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  23. package/dist/charts/kinds/StackedArea.js +16 -1
  24. package/dist/charts/kinds/StackedArea.test.d.ts +2 -0
  25. package/dist/charts/kinds/StackedArea.test.d.ts.map +1 -0
  26. package/dist/charts/kinds/ZonedLine.d.ts +9 -0
  27. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  28. package/dist/charts/kinds/ZonedLine.js +4 -0
  29. package/dist/charts/primitives/CartesianChart.d.ts +40 -2
  30. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  31. package/dist/charts/primitives/CartesianChart.js +16 -6
  32. package/dist/charts/primitives/ChartFrame.d.ts +8 -1
  33. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  34. package/dist/charts/primitives/ChartFrame.js +1 -1
  35. package/dist/charts/primitives/ChartTooltip.d.ts +12 -2
  36. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  37. package/dist/charts/primitives/ChartTooltip.js +5 -3
  38. package/dist/charts/primitives/ChartTooltip.test.d.ts +2 -0
  39. package/dist/charts/primitives/ChartTooltip.test.d.ts.map +1 -0
  40. package/dist/charts/series.d.ts +9 -1
  41. package/dist/charts/series.d.ts.map +1 -1
  42. package/dist/charts/utils/domain.d.ts +15 -0
  43. package/dist/charts/utils/domain.d.ts.map +1 -1
  44. package/dist/charts/utils/domain.js +6 -1
  45. package/llms.txt +2 -2
  46. package/package.json +1 -1
  47. package/src/charts/cursor/resolve.test.ts +62 -0
  48. package/src/charts/cursor/resolve.ts +71 -7
  49. package/src/charts/hooks/useChartCursor.ts +12 -1
  50. package/src/charts/index.ts +1 -0
  51. package/src/charts/kinds/Bars.test.tsx +85 -0
  52. package/src/charts/kinds/Bars.tsx +13 -0
  53. package/src/charts/kinds/DualPanel.test.tsx +391 -1
  54. package/src/charts/kinds/DualPanel.tsx +74 -8
  55. package/src/charts/kinds/MultiLine.test.tsx +181 -0
  56. package/src/charts/kinds/MultiLine.tsx +19 -8
  57. package/src/charts/kinds/StackedArea.test.tsx +114 -0
  58. package/src/charts/kinds/StackedArea.tsx +25 -1
  59. package/src/charts/kinds/ZonedLine.test.tsx +110 -0
  60. package/src/charts/kinds/ZonedLine.tsx +13 -0
  61. package/src/charts/primitives/CartesianChart.test.tsx +284 -3
  62. package/src/charts/primitives/CartesianChart.tsx +64 -12
  63. package/src/charts/primitives/ChartFrame.test.tsx +42 -0
  64. package/src/charts/primitives/ChartFrame.tsx +9 -2
  65. package/src/charts/primitives/ChartTooltip.test.tsx +44 -0
  66. package/src/charts/primitives/ChartTooltip.tsx +14 -2
  67. package/src/charts/series.ts +8 -2
  68. package/src/charts/utils/domain.test.ts +31 -1
  69. package/src/charts/utils/domain.ts +19 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [1.18.0](https://github.com/jkrumm/basalt-ui/compare/v1.17.0...v1.18.0) (2026-08-19)
2
+
3
+
4
+ ### Features
5
+
6
+ * render tooltips on cursor followers, and gate managed-doc drift ([6b46613](https://github.com/jkrumm/basalt-ui/commit/6b466136e1b1dbd88934d339420179768b3e9f8b)), closes [#52](https://github.com/jkrumm/basalt-ui/issues/52)
7
+
8
+ # [1.17.0](https://github.com/jkrumm/basalt-ui/compare/v1.16.0...v1.17.0) (2026-08-18)
9
+
10
+
11
+ ### Features
12
+
13
+ * fix seven chart defects, six of them found by consumers ([c3df0b8](https://github.com/jkrumm/basalt-ui/commit/c3df0b8439c51effaf425c54644cd9a15328ef12))
14
+
1
15
  # [1.16.0](https://github.com/jkrumm/basalt-ui/compare/v1.15.0...v1.16.0) (2026-08-18)
2
16
 
3
17
 
@@ -137,7 +137,26 @@ ships no map kind.
137
137
  `tickFormat` (`TickFormatter<string>`, defaults to `fmtAxisDate` — `DD.MM`) — the only supported
138
138
  exit for a sub-day window, where the default collapses every tick to the same label and a raw
139
139
  `<AxisBottom>` isn't an option. `CartesianChart`'s own `formatX` prop is the higher-level way to
140
- set this without touching the axis component at all.
140
+ set this without touching the axis component at all — and every cartesian kind (`Bars`/
141
+ `MultiLine`/`StackedArea`/`ZonedLine`/`DualPanel`) forwards its own `formatX?: (key) => string`
142
+ now too, not just `CartesianChart` itself. Without it, the only route to a custom x label was
143
+ pre-formatting it into the domain key, which makes one string serve as display value, scale
144
+ identity, AND cursor key simultaneously — and a truncating formatter then collapses two points
145
+ onto one domain value, silently dropping one from the plot (see #6 below for why that's the
146
+ cursor's problem too). `Heatmap` is deliberately excluded: its existing `colLabel`/`rowLabel`
147
+ already are that seam, and a second prop over one concern would fork them.
148
+ **The x axis is CATEGORICAL, and this is not inferable from the API.** `CartesianChart` builds
149
+ its x scale as `scalePoint<string>`, so N points are N evenly spaced positions whatever the
150
+ values behind the keys — there is no linear or time x scale. Correct and invisible for a domain
151
+ that is already a regular grid (five-minute buckets ARE evenly spaced); wrong for an event-shaped
152
+ series (speed-test runs, deploys, sessions). Two silent consequences: on a page sharing one
153
+ cursor, event points draw at equal spacing, so the crosshair marks the right point at a different
154
+ screen x than a regularly-sampled sibling — the correlation stays legible in the numbers and not
155
+ in the geometry; and because the domain value must be unique, two events at the same instant
156
+ collapse onto one position and one stops being drawn. `getX` returning a date string reads like a
157
+ time axis and is not one. Tracked as issue #52; a chart whose x is a measured quantity currently
158
+ needs a `basalt/hand-rolled-plot` exemption.
159
+
141
160
  6. **The cursor is shared by default.** No provider needed — `useChartCursor` reads a module-level
142
161
  external store (`useSyncExternalStore`), so every `CartesianChart`/`ChartFrame`-composed chart on
143
162
  the page shares one cursor out of the box. `ChartCursorScope` **isolates** a subtree onto a
@@ -160,10 +179,26 @@ ships no map kind.
160
179
  into weekly buckets still tracks a hover from a sibling plotting daily points, with no extra
161
180
  wiring — `resolveKey` no longer exists because there is nothing left for it to patch.
162
181
 
182
+ `CursorResolution = 'nearest' | 'leading'` (exported from `basalt-ui/charts`) picks WHICH own
183
+ point wins that resolution, reachable as `cursorResolution` on `CartesianChart`/every cartesian
184
+ kind/`DualPanel` and as `useChartCursor`'s `resolution` option — default `'nearest'`, unchanged.
185
+ Reach for `'leading'` when `getX` returns a bucket's LEADING EDGE (a weekly series keyed by its
186
+ Monday, a monthly series keyed by its 1st): under `'nearest'`, a hover landing in the back half
187
+ of a bucket resolves to the FOLLOWING bucket instead of the one it's actually inside, so the
188
+ shared crosshair sits one column right of the data being pointed at — reproducibly, for every
189
+ back-half hover.
190
+
163
191
  7. **Keyboard-operable by construction.** The hover overlay `CartesianChart` renders is focusable;
164
192
  `←`/`→` scrub the shared cursor one point at a time, `Escape` clears it. The tooltip is
165
193
  `aria-live="polite"`. This comes for free from composing `CartesianChart`/`ChartFrame` — nothing
166
- to wire per chart.
194
+ to wire per chart. That's exactly why `ChartFrame`'s outer container uses `role="group"` on its
195
+ `ariaLabel`, **never** `role="img"`: per the ARIA spec every descendant of a `role="img"`
196
+ element is presentational, which would erase the hover overlay's `role="slider"` from the
197
+ accessibility tree entirely — the label still announces, but the keyboard-scrubbable control
198
+ underneath it becomes unreachable, silently, with no error anywhere. jsdom does not implement
199
+ that pruning, so no `getByRole` test can ever catch this regression; it is a structural rule, not
200
+ a test-covered one. Do not "simplify" `ChartFrame`'s container role back to `img` — it looks like
201
+ a no-op refactor and it is not.
167
202
  8. **Theme-aware colors** via `VX.*` tokens + `alpha()`. **Never** a raw hex literal in a chart file.
168
203
  **Never** `localStorage.getItem('theme')` — the scheme resolves via CSS vars (see Dark/light
169
204
  below).
@@ -197,6 +232,13 @@ and its crosshair dots entirely):
197
232
  the crosshair at the plot's top edge instead, which reads better across a column of charts sharing
198
233
  one cursor (every tooltip lines up on the same x). Anchoring costs one `getBoundingClientRect` per
199
234
  hovered frame, which is why following is the default.
235
+ - `formatHeader?: (key, d) => string` — overrides the tooltip header's date text (also
236
+ `TooltipHeader`'s own `format` prop directly, and the identical seam on `DualPanel`'s
237
+ `formatHeader`, so the two never diverge). Default: today's `fmtTooltipDate` behavior, unchanged.
238
+ The seam exists because `fmtTooltipDate` regexes `YYYY-MM-DD` out of the domain key and builds a
239
+ LOCAL `Date` from it — a UTC ISO key then names a different day than `formatX`, the tooltip badge,
240
+ and every sibling chart, all of which resolve locally; the only prior workaround was carrying a
241
+ local-offset ISO key. Receives the raw `getX` key alongside the hovered datum.
200
242
 
201
243
  Per-series, `SeriesStyle.tooltip: false` means "draw the mark and legend it, but never give it a
202
244
  tooltip row" — it lives on the series, not on the kind. `ChartSeries.formatValue` is
@@ -261,7 +303,10 @@ per-series `getValue` accessors on `ChartSeries<T>`):
261
303
  - **`MultiLine`** — N series on a shared y-axis (or two, via `y2`): legend-hover dimming, per-series
262
304
  synced dots, dashed companion (MA) lines, per-point markers (PR stars / status dots), zones +
263
305
  refLines, fixed or auto domain (covers z-score/σ via a fixed symmetric domain + zero refLine).
264
- Composes `CartesianChart`.
306
+ Composes `CartesianChart`. `ChartSeries.getMarker` returns `{ color?, r?, fillOpacity?, ring? }`
307
+ — `ring` defaults `true` (today's punched-out stroke, unchanged), `ring: false` omits the stroke
308
+ entirely, `fillOpacity` defaults `1`. Existed because a consumer's plain `fillOpacity: 0.7` dots
309
+ were unreproducible after moving their chart onto the kind — the old shape had no seam for either.
265
310
  - **`DualPanel`** — top line-pane + bottom signed-histogram pane sharing one x-scale and one cursor;
266
311
  optional fill-between two top lines, zones, refLines, per-point markers on the top pane
267
312
  (`ChartSeries.getMarker`). Composes `ChartFrame` directly (two panes, not one plot rect). The
@@ -311,7 +356,7 @@ One object per axis on `CartesianChart` and every kind that composes it — this
311
356
  ```tsx
312
357
  y={{
313
358
  domain: 'auto', // 'auto' (default) | [min, max] | (data, visible) => [min, max]
314
- autoMaxFloor: 100, // when 'auto': upper bound is at least this
359
+ autoMaxFloor: 100, // when 'auto': raw upper bound is at least this, clamped BEFORE padding
315
360
  autoMinCeil: 0, // when 'auto': lower bound is at most this (Infinity = pad from raw min)
316
361
  autoPad: 1.1, // when 'auto': padding multiplier away from the data
317
362
  ticks: 5,
@@ -321,6 +366,15 @@ y={{
321
366
  }}
322
367
  ```
323
368
 
369
+ **Behavior change (2026-08-19) — the one item in this doc that can move an existing chart's
370
+ rendering:** `autoMaxFloor` now clamps the raw upper bound BEFORE padding, mirroring `autoMinCeil`,
371
+ which has always clamped first and padded second — two different laws used to live in one function.
372
+ The old order padded the raw max, then applied the floor last, so when the floor won it landed
373
+ exactly on the axis top with zero headroom: a target line sitting at precisely the floor value was
374
+ glued to the plot edge. Measured case: dataMax 3.2, pad 1.1, floor 6 → axis top was 6.0, is now
375
+ 6.6. A consumer relying on the old ordering will see their axis top move — lower the floor or pin
376
+ `domain` explicitly to opt back out.
377
+
324
378
  `nice` defaults `false` deliberately: flipping the default would move the domain of every
325
379
  already-migrated chart, so it's opt-in per axis.
326
380
 
@@ -227,7 +227,7 @@ Every chart composes the shipped primitives — never hand-rolled equivalents:
227
227
 
228
228
  - **`ChartCard`** wrapper (title + info-tooltip + extra slot, consistent margin).
229
229
  - **`ChartLegend`** (`line | bar | split | splitLine` shapes) — never hand-rolled legend markup.
230
- - **`ChartTooltip`** + `TooltipHeader` / `TooltipRow` / `TooltipBody` — never import `@visx/tooltip`
230
+ - **`ChartTooltipFloat`** + `TooltipHeader` / `TooltipRow` / `TooltipBody` — never import `@visx/tooltip`
231
231
  directly (oxlint bans it in chart files).
232
232
  - **`AxisBottomDate`** / **`AxisLeftNumeric`** / **`AxisRightNumeric`** — never raw
233
233
  `<AxisLeft>`/`<AxisBottom>` (they miss theme tokens + smart ticks). Now lint-enforced:
@@ -262,7 +262,7 @@ resolve/render with no `@mantine/*` installed — CI-tested, repo-local only, no
262
262
  - Every color comes from a token (`VX.*` / palette) — `basalt-ui check-theme` green, zero raw hex.
263
263
  - One hue per metric; categorical multi-color only where the data is genuinely categorical.
264
264
  - Neutral line/axis/grid; soft `AreaGradient` on plain metric lines, opaque stacked-area bands.
265
- - Tooltip = `ChartCard` / `ChartTooltip` primitives, one swatch per row — no hand-rolled markup.
265
+ - Tooltip = `ChartCard` / `ChartTooltipFloat` primitives, one swatch per row — no hand-rolled markup.
266
266
  - Looks right in BOTH schemes (toggled and checked: glow on dark, contrast on light).
267
267
  - New colors landed as `{ light, dark }` pairs in the palette, not inline.
268
268
  - Nothing reads `localStorage.getItem('theme')`.
@@ -13,6 +13,26 @@
13
13
  * since "nearest category" is meaningless.
14
14
  */
15
15
  export declare function parseKey(key: string): number | null;
16
+ /**
17
+ * How a foreign key maps onto a chart's own points.
18
+ *
19
+ * `'nearest'` (default) treats every key as a POINT — the foreign key resolves to whichever own
20
+ * point sits closest to it. Correct when a chart's x-domain is a set of instants.
21
+ *
22
+ * `'leading'` treats every key as the LEADING EDGE of a bucket covering `[key, nextKey)` — the
23
+ * foreign key resolves to the LAST own key `<=` it. Use this when a chart's keys are bucket
24
+ * starts (a weekly series keyed by the Monday of each week, a monthly series keyed by the 1st):
25
+ * under `'nearest'`, a target landing in the back half of a bucket resolves to the FOLLOWING
26
+ * bucket instead of the one it's actually inside, and the shared crosshair lands one column off
27
+ * for every back-half hover.
28
+ *
29
+ * The two modes differ at the DOMAIN EDGES too, and deliberately: `'nearest'` tolerates one step
30
+ * past each end (the target is an instant, so the closest point is still a defensible answer),
31
+ * while `'leading'` bounds strictly to `[first, last + step)` — outside that range no bucket
32
+ * CONTAINS the key, and answering with the nearest one would paint a crosshair on a bucket that
33
+ * provably excludes it.
34
+ */
35
+ export type CursorResolution = 'nearest' | 'leading';
16
36
  export type DomainIndex<T> = {
17
37
  byKey: Map<string, T>;
18
38
  /** Parsed positions, ascending. Empty when the domain isn't parseable. */
@@ -21,13 +41,17 @@ export type DomainIndex<T> = {
21
41
  points: T[];
22
42
  /** Median spacing — the tolerance within which a foreign key still resolves. */
23
43
  step: number;
44
+ /** The resolution strategy this index was built for. */
45
+ resolution: CursorResolution;
24
46
  };
25
47
  /** Build the lookup structure once per `data` identity. */
26
- export declare function buildDomainIndex<T>(data: readonly T[], getKey: (d: T) => string): DomainIndex<T>;
48
+ export declare function buildDomainIndex<T>(data: readonly T[], getKey: (d: T) => string, resolution?: CursorResolution): DomainIndex<T>;
27
49
  /**
28
- * The own point a broadcast `key` maps to: exact match first, else the nearest parsed position
29
- * within one domain step. Out of range → null, so charts over unrelated domains never paint a
30
- * cursor for each other — which is what makes "shared by default" safe.
50
+ * The own point a broadcast `key` maps to: exact match first, else — per `index.resolution` — the
51
+ * nearest parsed position or the last position at/before the target, both within one domain step.
52
+ * Out of range → null, so charts over unrelated domains never paint a cursor for each other —
53
+ * which is what makes "shared by default" safe. A categorical (unparseable) domain falls back to
54
+ * exact-match-only in BOTH resolutions, since "nearest"/"leading" over a category is meaningless.
31
55
  */
32
- export declare function resolveCursorPoint<T>(index: DomainIndex<T>, key: string): T | null;
56
+ export declare function resolveCursorPoint<T>(index: DomainIndex<T>, key: string, resolution?: CursorResolution): T | null;
33
57
  //# sourceMappingURL=resolve.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/charts/cursor/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOnD;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IACrB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,mCAAmC;IACnC,MAAM,EAAE,CAAC,EAAE,CAAA;IACX,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CA4BhG;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CA+BlF"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/charts/cursor/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOnD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,CAAA;AAEpD,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IACrB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,mCAAmC;IACnC,MAAM,EAAE,CAAC,EAAE,CAAA;IACX,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,wDAAwD;IACxD,UAAU,EAAE,gBAAgB,CAAA;CAC7B,CAAA;AAED,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,EACxB,UAAU,GAAE,gBAA4B,GACvC,WAAW,CAAC,CAAC,CAAC,CA8BhB;AAoBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EACrB,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,gBAAmC,GAC9C,CAAC,GAAG,IAAI,CA0CV"}
@@ -6,7 +6,7 @@ function parseKey(key) {
6
6
  const t = Date.parse(key);
7
7
  return Number.isNaN(t) ? null : t;
8
8
  }
9
- function buildDomainIndex(data, getKey) {
9
+ function buildDomainIndex(data, getKey, resolution = "nearest") {
10
10
  const byKey = /* @__PURE__ */ new Map();
11
11
  const parsed = [];
12
12
  let parseable = true;
@@ -17,7 +17,9 @@ function buildDomainIndex(data, getKey) {
17
17
  if (value === null) parseable = false;
18
18
  else parsed.push({ value, point: d });
19
19
  }
20
- if (!parseable || parsed.length < 2) return { byKey, values: [], points: [], step: 0 };
20
+ if (!parseable || parsed.length < 2) {
21
+ return { byKey, values: [], points: [], step: 0, resolution };
22
+ }
21
23
  parsed.sort((a, b) => a.value - b.value);
22
24
  const values = parsed.map((p) => p.value);
23
25
  const points = parsed.map((p) => p.point);
@@ -26,15 +28,34 @@ function buildDomainIndex(data, getKey) {
26
28
  diffs.push(values[i] - values[i - 1]);
27
29
  }
28
30
  diffs.sort((a, b) => a - b);
29
- return { byKey, values, points, step: diffs[Math.floor(diffs.length / 2)] ?? 0 };
31
+ return { byKey, values, points, step: diffs[Math.floor(diffs.length / 2)] ?? 0, resolution };
32
+ }
33
+ function resolveLeading(index, target) {
34
+ let lo = 0;
35
+ let hi = index.values.length - 1;
36
+ let result = 0;
37
+ while (lo <= hi) {
38
+ const mid = lo + hi >> 1;
39
+ if (index.values[mid] <= target) {
40
+ result = mid;
41
+ lo = mid + 1;
42
+ } else {
43
+ hi = mid - 1;
44
+ }
45
+ }
46
+ return index.points[result];
30
47
  }
31
- function resolveCursorPoint(index, key) {
48
+ function resolveCursorPoint(index, key, resolution = index.resolution) {
32
49
  const exact = index.byKey.get(key);
33
50
  if (exact !== void 0) return exact;
34
51
  const target = parseKey(key);
35
52
  if (target === null || index.values.length === 0) return null;
36
53
  const first = index.values[0];
37
54
  const last = index.values[index.values.length - 1];
55
+ if (resolution === "leading") {
56
+ if (target < first || target >= last + index.step) return null;
57
+ return resolveLeading(index, target);
58
+ }
38
59
  if (target < first - index.step || target > last + index.step) return null;
39
60
  let lo = 0;
40
61
  let hi = index.values.length - 1;
@@ -1,4 +1,5 @@
1
1
  import type { KeyboardEvent, PointerEvent } from 'react';
2
+ import type { CursorResolution } from '../cursor/resolve.js';
2
3
  /** Viewport-space pointer anchor the tooltip positions against. */
3
4
  export type CursorAnchor = {
4
5
  x: number;
@@ -28,12 +29,18 @@ export type ChartCursor<T> = {
28
29
  * costs nothing while a real bucket change reaches siblings on the same tick rather than a frame
29
30
  * late.
30
31
  */
31
- export declare function useChartCursor<T>({ data, chartId, getKey, xScale, marginLeft, }: {
32
+ export declare function useChartCursor<T>({ data, chartId, getKey, xScale, marginLeft, resolution, }: {
32
33
  data: readonly T[];
33
34
  chartId: string;
34
35
  getKey: (d: T) => string;
35
36
  /** Own x scale — maps a domain key to a plot-local x offset. */
36
37
  xScale: (key: string) => number | undefined;
37
38
  marginLeft: number;
39
+ /**
40
+ * How a sibling's broadcast key resolves against this chart's own points. Default `'nearest'`
41
+ * (point domains). Pass `'leading'` when `getKey` returns a bucket's leading edge (a weekly
42
+ * series keyed by its Monday, a monthly series keyed by its 1st) — see {@link CursorResolution}.
43
+ */
44
+ resolution?: CursorResolution;
38
45
  }): ChartCursor<T>;
39
46
  //# sourceMappingURL=useChartCursor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useChartCursor.d.ts","sourceRoot":"","sources":["../../../src/charts/hooks/useChartCursor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,OAAO,CAAA;AAIxD,mEAAmE;AACnE,MAAM,MAAM,YAAY,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEnD,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,kGAAkG;IAClG,KAAK,EAAE,CAAC,GAAG,IAAI,CAAA;IACf,kGAAkG;IAClG,QAAQ,EAAE,OAAO,CAAA;IACjB,4EAA4E;IAC5E,MAAM,EAAE,YAAY,GAAG,IAAI,CAAA;IAC3B,aAAa,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,cAAc,CAAC,KAAK,IAAI,CAAA;IAC5D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,KAAK,IAAI,CAAA;IACzD,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,EAChC,IAAI,EACJ,OAAO,EACP,MAAM,EACN,MAAM,EACN,UAAU,GACX,EAAE;IACD,IAAI,EAAE,SAAS,CAAC,EAAE,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACxB,gEAAgE;IAChE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IAC3C,UAAU,EAAE,MAAM,CAAA;CACnB,GAAG,WAAW,CAAC,CAAC,CAAC,CAmHjB"}
1
+ {"version":3,"file":"useChartCursor.d.ts","sourceRoot":"","sources":["../../../src/charts/hooks/useChartCursor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,OAAO,CAAA;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAIzD,mEAAmE;AACnE,MAAM,MAAM,YAAY,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEnD,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,kGAAkG;IAClG,KAAK,EAAE,CAAC,GAAG,IAAI,CAAA;IACf,kGAAkG;IAClG,QAAQ,EAAE,OAAO,CAAA;IACjB,4EAA4E;IAC5E,MAAM,EAAE,YAAY,GAAG,IAAI,CAAA;IAC3B,aAAa,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,cAAc,CAAC,KAAK,IAAI,CAAA;IAC5D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,KAAK,IAAI,CAAA;IACzD,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,EAChC,IAAI,EACJ,OAAO,EACP,MAAM,EACN,MAAM,EACN,UAAU,EACV,UAAsB,GACvB,EAAE;IACD,IAAI,EAAE,SAAS,CAAC,EAAE,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACxB,gEAAgE;IAChE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IAC3C,UAAU,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;CAC9B,GAAG,WAAW,CAAC,CAAC,CAAC,CAsHjB"}
@@ -7,7 +7,8 @@ function useChartCursor({
7
7
  chartId,
8
8
  getKey,
9
9
  xScale,
10
- marginLeft
10
+ marginLeft,
11
+ resolution = "nearest"
11
12
  }) {
12
13
  const store = useCursorStore();
13
14
  const cursor = useCursorState();
@@ -16,7 +17,10 @@ function useChartCursor({
16
17
  getKeyRef.current = getKey;
17
18
  const xScaleRef = useRef(xScale);
18
19
  xScaleRef.current = xScale;
19
- const index = useMemo(() => buildDomainIndex(data, getKeyRef.current), [data]);
20
+ const index = useMemo(
21
+ () => buildDomainIndex(data, getKeyRef.current, resolution),
22
+ [data, resolution]
23
+ );
20
24
  const frameRef = useRef(null);
21
25
  const pendingRef = useRef(null);
22
26
  useEffect(
@@ -29,6 +29,7 @@ export { ZoneRects, type ZoneSpec } from './primitives/ZoneRects.js';
29
29
  export { XZoneRects, type XZoneSpec } from './primitives/XZoneRects.js';
30
30
  export { AreaGradient, areaFillUrl } from './primitives/AreaGradient.js';
31
31
  export { useChartCursor, type ChartCursor, type CursorAnchor } from './hooks/useChartCursor.js';
32
+ export type { CursorResolution } from './cursor/resolve.js';
32
33
  export { useChartSize, type UseChartSizeResult, type ChartSize } from './hooks/useChartSize.js';
33
34
  export { fmtAxisDate, fmtTooltipDate } from './utils/format.js';
34
35
  export { smartTicks, smartTicksEvery } from './utils/ticks.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/charts/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/charts/index.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import type { CursorResolution } from '../cursor/resolve.js';
1
2
  import type { ChartMargin } from '../../tokens/index.js';
2
3
  import type { AxisConfig, CartesianTooltipConfig } from '../primitives/CartesianChart.js';
3
4
  import type { ZoneSpec } from '../primitives/ZoneRects.js';
@@ -88,6 +89,14 @@ export type BarsProps<T> = {
88
89
  barOpacity?: (d: T, key: string) => number;
89
90
  /** X tick count override. Default: as many as fit. */
90
91
  xTicks?: number;
92
+ /** X tick label formatter. Default `fmtAxisDate` (DD.MM). */
93
+ formatX?: (key: string) => string;
94
+ /**
95
+ * How a sibling chart's broadcast cursor key resolves against this chart's points. Default
96
+ * `'nearest'`. Pass `'leading'` when `getX` returns a bucket's leading edge (a weekly series
97
+ * keyed by its Monday) — see `CursorResolution`.
98
+ */
99
+ cursorResolution?: CursorResolution;
91
100
  /** `false` disables the tooltip entirely (and with it the crosshair dots). */
92
101
  tooltip?: CartesianTooltipConfig<T> | false;
93
102
  /** Per-side overrides of the measured margins — the escape hatch, applied last. */
@@ -1 +1 @@
1
- {"version":3,"file":"Bars.d.ts","sourceRoot":"","sources":["../../../src/charts/kinds/Bars.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Bars.d.ts","sourceRoot":"","sources":["../../../src/charts/kinds/Bars.tsx"],"names":[],"mappings":"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"}
@@ -24,6 +24,8 @@ function BarsInner(props) {
24
24
  barLayout = "stacked",
25
25
  barOpacity,
26
26
  xTicks,
27
+ formatX,
28
+ cursorResolution,
27
29
  tooltip,
28
30
  margin,
29
31
  legend,
@@ -126,6 +128,8 @@ function BarsInner(props) {
126
128
  y: yConfig,
127
129
  ...y2 !== void 0 && { y2 },
128
130
  ...xTicks !== void 0 && { xTicks },
131
+ ...formatX !== void 0 && { formatX },
132
+ ...cursorResolution !== void 0 && { cursorResolution },
129
133
  ...zones !== void 0 && { zones },
130
134
  ...mappedRefLines !== void 0 && { refLines: mappedRefLines },
131
135
  ...height !== void 0 && { height },
@@ -1,4 +1,5 @@
1
1
  import type { ZoneSpec } from '../primitives/ZoneRects.js';
2
+ import type { CursorResolution } from '../cursor/resolve.js';
2
3
  import type { ChartLegendConfig, ChartSeries } from '../series.js';
3
4
  export type DualPanelProps<T> = {
4
5
  data: T[];
@@ -6,6 +7,15 @@ export type DualPanelProps<T> = {
6
7
  height?: number;
7
8
  chartId: string;
8
9
  getX: (d: T) => string;
10
+ /** X tick label formatter. Default `fmtAxisDate` (DD.MM) — same default `CartesianChart` uses,
11
+ * so omitting it is byte-identical to today's rendering. */
12
+ formatX?: (key: string) => string;
13
+ /**
14
+ * How a sibling chart's broadcast cursor key resolves against this chart's points. Default
15
+ * `'nearest'`. Pass `'leading'` when `getX` returns a bucket's leading edge (a weekly series
16
+ * keyed by its Monday) — see `CursorResolution`.
17
+ */
18
+ cursorResolution?: CursorResolution;
9
19
  /** 1+ line series in the top pane — the single source of truth for color, dash, legend, and
10
20
  * tooltip rows. E.g. acute (solid) / chronic (dashed). */
11
21
  series: ChartSeries<T>[];
@@ -52,6 +62,20 @@ export type DualPanelProps<T> = {
52
62
  text: string;
53
63
  color: string;
54
64
  } | null;
65
+ /**
66
+ * Overrides the tooltip header's date text — the same seam `CartesianChart`'s
67
+ * `CartesianTooltipConfig.formatHeader` exposes, so the two don't diverge. Default:
68
+ * `TooltipHeader`'s own `fmtTooltipDate` behavior, unchanged. Receives the raw `getX` key
69
+ * alongside the hovered datum.
70
+ */
71
+ formatHeader?: (key: string, d: T) => string;
72
+ /**
73
+ * Render this chart's tooltip when it is a cursor FOLLOWER, not only when it is the SOURCE —
74
+ * same seam and same anchored-positioning behaviour as `CartesianTooltipConfig.onFollow`. Default
75
+ * false (today's behaviour). Only the SOURCE's tooltip is `aria-live`; a follower's is visual
76
+ * only.
77
+ */
78
+ onFollow?: boolean;
55
79
  /** Legend config forwarded to `ChartFrame`; `false` disables the legend (sparkline escape).
56
80
  * Default `{ placement: 'bottom' }`. */
57
81
  legend?: ChartLegendConfig | false;
@@ -1 +1 @@
1
- {"version":3,"file":"DualPanel.d.ts","sourceRoot":"","sources":["../../../src/charts/kinds/DualPanel.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAIvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAe,MAAM,WAAW,CAAA;AAK5E,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACtB;8DAC0D;IAC1D,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAA;IACxB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IACtC,2EAA2E;IAC3E,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACxD,wGAAwG;IACxG,WAAW,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IAClE,oDAAoD;IACpD,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAA;IACrB,8DAA8D;IAC9D,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,CAAA;IACxB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAChC,+FAA+F;IAC/F,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IACnC;;yFAEqF;IACrF,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACvC;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IACzC;iGAC6F;IAC7F,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,kEAAkE;IAClE,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC/D;4CACwC;IACxC,MAAM,CAAC,EAAE,iBAAiB,GAAG,KAAK,CAAA;IAClC,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAID;;;;;;;;;;;GAWG;AACH,iBAAS,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,+BAmClD;AAsdD;;;GAGG;AACH,eAAO,MAAM,SAAS,EAA2B,OAAO,cAAc,CAAA"}
1
+ {"version":3,"file":"DualPanel.d.ts","sourceRoot":"","sources":["../../../src/charts/kinds/DualPanel.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAIzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAe,MAAM,WAAW,CAAA;AAK5E,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACtB;gEAC4D;IAC5D,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IACjC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC;8DAC0D;IAC1D,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAA;IACxB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IACtC,2EAA2E;IAC3E,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACxD,wGAAwG;IACxG,WAAW,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IAClE,oDAAoD;IACpD,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAA;IACrB,8DAA8D;IAC9D,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,CAAA;IACxB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAChC,+FAA+F;IAC/F,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IACnC;;yFAEqF;IACrF,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACvC;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IACzC;iGAC6F;IAC7F,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,kEAAkE;IAClE,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC/D;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;4CACwC;IACxC,MAAM,CAAC,EAAE,iBAAiB,GAAG,KAAK,CAAA;IAClC,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAID;;;;;;;;;;;GAWG;AACH,iBAAS,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,+BAmClD;AAggBD;;;GAGG;AACH,eAAO,MAAM,SAAS,EAA2B,OAAO,cAAc,CAAA"}
@@ -5,7 +5,7 @@ import { Group } from "@visx/group";
5
5
  import { scaleLinear, scalePoint } from "@visx/scale";
6
6
  import { Bar, LinePath } from "@visx/shape";
7
7
  import { Threshold } from "@visx/threshold";
8
- import { memo, useMemo } from "react";
8
+ import { memo, useMemo, useRef } from "react";
9
9
  import { AxisBottomDate, AxisLeftNumeric } from "../primitives/Axes.js";
10
10
  import {
11
11
  ChartTooltipFloat,
@@ -55,6 +55,9 @@ function DualPanelPlot(props) {
55
55
  data,
56
56
  chartId,
57
57
  getX,
58
+ formatX = fmtAxisDate,
59
+ cursorResolution,
60
+ ariaLabel,
58
61
  series,
59
62
  topYDomain = "auto",
60
63
  fillBetween,
@@ -71,6 +74,8 @@ function DualPanelPlot(props) {
71
74
  bottomYDomain = "auto",
72
75
  bottomMaxAbsFloor,
73
76
  tooltipLabel,
77
+ formatHeader,
78
+ onFollow,
74
79
  plot
75
80
  } = props;
76
81
  const { hidden } = plot;
@@ -126,7 +131,7 @@ function DualPanelPlot(props) {
126
131
  }).labels,
127
132
  [bottomDomain, formatBottom, useNiceBottom]
128
133
  );
129
- const xLabelsAll = useMemo(() => data.map((d) => fmtAxisDate(getX(d))), [data, getX]);
134
+ const xLabelsAll = useMemo(() => data.map((d) => formatX(getX(d))), [data, getX, formatX]);
130
135
  const margin = useMemo(
131
136
  () => autoMargin({ left: [...topLabels, ...bottomLabels], bottom: xLabelsAll }),
132
137
  [topLabels, bottomLabels, xLabelsAll]
@@ -162,7 +167,8 @@ function DualPanelPlot(props) {
162
167
  chartId,
163
168
  getKey: getX,
164
169
  xScale,
165
- marginLeft: margin.left
170
+ marginLeft: margin.left,
171
+ ...cursorResolution !== void 0 && { resolution: cursorResolution }
166
172
  });
167
173
  const tickValues = useMemo(() => smartTicks(data.map(getX), xMax), [data, xMax, getX]);
168
174
  const barWidth = data.length > 0 ? Math.max(xMax / data.length * 0.6, 2) : 2;
@@ -234,6 +240,7 @@ function DualPanelPlot(props) {
234
240
  if (m === null) continue;
235
241
  const cx = xScale(getX(p.__d)) ?? 0;
236
242
  const cy = topYScale(p.__y);
243
+ const ring = m.ring ?? true;
237
244
  markers.push(
238
245
  /* @__PURE__ */ jsx(
239
246
  "circle",
@@ -242,8 +249,8 @@ function DualPanelPlot(props) {
242
249
  cy,
243
250
  r: m.r ?? VX.dotR,
244
251
  fill: m.color ?? s.color,
245
- stroke: VX.dotStroke,
246
- strokeWidth: 2
252
+ fillOpacity: m.fillOpacity ?? 1,
253
+ ...ring && { stroke: VX.dotStroke, strokeWidth: 2 }
247
254
  },
248
255
  `mk-${s.key}-${getX(p.__d)}`
249
256
  )
@@ -253,11 +260,17 @@ function DualPanelPlot(props) {
253
260
  }),
254
261
  [visibleSeries, lineValid, xScale, topYScale, getX]
255
262
  );
263
+ const svgRef = useRef(null);
256
264
  const point = cursor.point;
257
265
  const sx = point !== null ? xScale(getX(point)) ?? 0 : 0;
258
266
  const syncedBar = point !== null ? getBar(point) : null;
267
+ const isFollowerRender = !cursor.isSource && onFollow === true;
268
+ const showTooltip = point !== null && (cursor.isSource || isFollowerRender);
269
+ const anchorX = isFollowerRender && point !== null ? sx : null;
270
+ const svgRect = anchorX === null ? void 0 : svgRef.current?.getBoundingClientRect();
271
+ const tooltipAnchor = svgRect === void 0 || anchorX === null ? cursor.anchor : { x: svgRect.left + margin.left + anchorX, y: svgRect.top + margin.top };
259
272
  return /* @__PURE__ */ jsxs(Fragment, { children: [
260
- /* @__PURE__ */ jsxs("svg", { width: plot.width, height: plot.height, children: [
273
+ /* @__PURE__ */ jsxs("svg", { ref: svgRef, width: plot.width, height: plot.height, children: [
261
274
  /* @__PURE__ */ jsxs(Group, { left: margin.left, top: margin.top, children: [
262
275
  /* @__PURE__ */ jsx(GridRows, { scale: topYScale, width: xMax, stroke: VX.grid, numTicks: 4 }),
263
276
  /* @__PURE__ */ jsx(ZoneRects, { zones: topZones, width: xMax, leftScale: topYScale }),
@@ -321,7 +334,13 @@ function DualPanelPlot(props) {
321
334
  onMove: cursor.onPointerMove,
322
335
  onLeave: cursor.onPointerLeave,
323
336
  onKeyDown: cursor.onKeyDown,
324
- onBlur: cursor.onBlur
337
+ onBlur: cursor.onBlur,
338
+ valueMax: Math.max(data.length - 1, 0),
339
+ ...point !== null && {
340
+ valueNow: data.indexOf(point),
341
+ valueText: formatX(getX(point))
342
+ },
343
+ ...ariaLabel !== void 0 && { ariaLabel }
325
344
  }
326
345
  )
327
346
  ] }),
@@ -359,7 +378,15 @@ function DualPanelPlot(props) {
359
378
  }
360
379
  ),
361
380
  /* @__PURE__ */ jsx(AxisLeftNumeric, { scale: bottomYScale, numTicks: 3, tickFormat: formatBottom }),
362
- /* @__PURE__ */ jsx(AxisBottomDate, { top: bottomH, scale: xScale, tickValues }),
381
+ /* @__PURE__ */ jsx(
382
+ AxisBottomDate,
383
+ {
384
+ top: bottomH,
385
+ scale: xScale,
386
+ tickValues,
387
+ tickFormat: formatX
388
+ }
389
+ ),
363
390
  /* @__PURE__ */ jsx(
364
391
  HoverOverlay,
365
392
  {
@@ -372,11 +399,14 @@ function DualPanelPlot(props) {
372
399
  ] }),
373
400
  point !== null && /* @__PURE__ */ jsx(Group, { left: margin.left, top: margin.top, children: /* @__PURE__ */ jsx(Crosshair, { x: sx, top: 0, bottom: innerH }) })
374
401
  ] }),
375
- cursor.isSource && point !== null && /* @__PURE__ */ jsxs(ChartTooltipFloat, { anchor: cursor.anchor, children: [
402
+ showTooltip && /* @__PURE__ */ jsxs(ChartTooltipFloat, { anchor: tooltipAnchor, ariaLive: cursor.isSource, children: [
376
403
  /* @__PURE__ */ jsx(
377
404
  TooltipHeader,
378
405
  {
379
406
  date: getX(point),
407
+ ...formatHeader !== void 0 && {
408
+ format: (key) => formatHeader(key, point)
409
+ },
380
410
  ...(() => {
381
411
  const lbl = tooltipLabel?.(point) ?? null;
382
412
  return lbl !== null ? { label: lbl.text, labelColor: lbl.color } : {};
@@ -1,3 +1,4 @@
1
+ import type { CursorResolution } from '../cursor/resolve.js';
1
2
  import type { CartesianTooltipConfig, AxisConfig } from '../primitives/CartesianChart.js';
2
3
  import type { XZoneSpec } from '../primitives/XZoneRects.js';
3
4
  import type { ZoneSpec } from '../primitives/ZoneRects.js';
@@ -31,6 +32,14 @@ export type MultiLineProps<T> = {
31
32
  }[];
32
33
  /** Exact number of x ticks. Default: as many as fit. */
33
34
  xTicks?: number;
35
+ /** X tick label formatter. Default `fmtAxisDate` (DD.MM). */
36
+ formatX?: (key: string) => string;
37
+ /**
38
+ * How a sibling chart's broadcast cursor key resolves against this chart's points. Default
39
+ * `'nearest'`. Pass `'leading'` when `getX` returns a bucket's leading edge (a weekly series
40
+ * keyed by its Monday) — see `CursorResolution`.
41
+ */
42
+ cursorResolution?: CursorResolution;
34
43
  /** Tooltip config — `label` for a right-aligned header badge, `extraRows` for rows appended
35
44
  * after the derived per-series rows. `false` disables the tooltip entirely. */
36
45
  tooltip?: CartesianTooltipConfig<T> | false;
@@ -1 +1 @@
1
- {"version":3,"file":"MultiLine.d.ts","sourceRoot":"","sources":["../../../src/charts/kinds/MultiLine.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,UAAU,EAAe,MAAM,8BAA8B,CAAA;AAEnG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAG/D,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,oEAAoE;IACpE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACtB;mFAC+E;IAC/E,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAA;IACxB;gEAC4D;IAC5D,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IACjB;kEAC8D;IAC9D,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IAClB,iFAAiF;IACjF,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClB;sBACkB;IAClB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;IACpB,wFAAwF;IACxF,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IAC/D,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;mFAC+E;IAC/E,OAAO,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;IAC3C,4DAA4D;IAC5D,WAAW,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;IAC/B;4CACwC;IACxC,MAAM,CAAC,EAAE,iBAAiB,GAAG,KAAK,CAAA;IAClC,mGAAmG;IACnG,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gGAAgG;IAChG,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AA4BD;;;;;;;;;;;;GAYG;AACH,iBAAS,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,+BAgDlD;AAqHD;;;GAGG;AACH,eAAO,MAAM,SAAS,EAA2B,OAAO,cAAc,CAAA"}
1
+ {"version":3,"file":"MultiLine.d.ts","sourceRoot":"","sources":["../../../src/charts/kinds/MultiLine.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,UAAU,EAAe,MAAM,8BAA8B,CAAA;AAEnG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAG/D,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,oEAAoE;IACpE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACtB;mFAC+E;IAC/E,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAA;IACxB;gEAC4D;IAC5D,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IACjB;kEAC8D;IAC9D,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IAClB,iFAAiF;IACjF,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClB;sBACkB;IAClB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;IACpB,wFAAwF;IACxF,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IAC/D,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6DAA6D;IAC7D,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IACjC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC;mFAC+E;IAC/E,OAAO,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;IAC3C,4DAA4D;IAC5D,WAAW,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;IAC/B;4CACwC;IACxC,MAAM,CAAC,EAAE,iBAAiB,GAAG,KAAK,CAAA;IAClC,mGAAmG;IACnG,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gGAAgG;IAChG,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AA4BD;;;;;;;;;;;;GAYG;AACH,iBAAS,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,+BAoDlD;AAmHD;;;GAGG;AACH,eAAO,MAAM,SAAS,EAA2B,OAAO,cAAc,CAAA"}