@pond-ts/charts 0.69.0 → 0.71.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 (46) hide show
  1. package/AGENTS.md +3 -1
  2. package/API.md +55 -56
  3. package/CHANGELOG.md +177 -1
  4. package/dist/AreaChart.d.ts +30 -22
  5. package/dist/AreaChart.js +37 -22
  6. package/dist/BandChart.d.ts +20 -1
  7. package/dist/BandChart.js +18 -2
  8. package/dist/BoxPlot.js +5 -3
  9. package/dist/Candlestick.d.ts +3 -3
  10. package/dist/Candlestick.js +5 -5
  11. package/dist/ChartContainer.d.ts +8 -149
  12. package/dist/ChartContainer.js +68 -111
  13. package/dist/ChartRow.d.ts +1 -12
  14. package/dist/ChartRow.js +15 -30
  15. package/dist/HeatMap.d.ts +3 -3
  16. package/dist/HeatMap.js +3 -3
  17. package/dist/Layers.js +84 -34
  18. package/dist/XAxis.js +1 -0
  19. package/dist/area.d.ts +6 -5
  20. package/dist/area.js +13 -6
  21. package/dist/axis-events.d.ts +4 -4
  22. package/dist/band.d.ts +10 -2
  23. package/dist/band.js +38 -10
  24. package/dist/brush.d.ts +13 -21
  25. package/dist/brush.js +22 -36
  26. package/dist/context.d.ts +81 -96
  27. package/dist/cursors.d.ts +81 -51
  28. package/dist/cursors.js +164 -127
  29. package/dist/decimate.d.ts +10 -1
  30. package/dist/decimate.js +51 -10
  31. package/dist/elapsed.d.ts +1 -1
  32. package/dist/elapsed.js +1 -1
  33. package/dist/format.d.ts +2 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/selection-fixtures.d.ts +4 -7
  36. package/dist/selection-fixtures.js +0 -9
  37. package/dist/selection-stories.d.ts +4 -6
  38. package/dist/selection-stories.js +4 -4
  39. package/dist/selectors.d.ts +2 -3
  40. package/dist/selectors.js +2 -3
  41. package/dist/tracker.d.ts +10 -28
  42. package/dist/tracker.js +17 -40
  43. package/dist/tradingAxis.fixture.d.ts +19 -0
  44. package/dist/tradingAxis.fixture.js +24 -0
  45. package/dist/tradingTimeScale.d.ts +2 -2
  46. package/package.json +3 -3
package/dist/AreaChart.js CHANGED
@@ -16,8 +16,9 @@ import { useBandLadder } from './use-band-ladder.js';
16
16
  * it to the call signature, so this reads the bound through a localized,
17
17
  * documented shape rather than widening `drawArea`'s contract to d3-scale. */
18
18
  /**
19
- * The area's baseline in **data** units: the caller's `baseline` when it has a
20
- * finite position on this axis, else the axis floor.
19
+ * The area's baseline in **data** units: the caller's `baseline`, clamped into
20
+ * the axis domain, when it has a finite position on this axis; else the axis
21
+ * floor.
21
22
  *
22
23
  * The fallback is not defensive padding — it's the log case. `baseline={0}` is
23
24
  * the natural thing to write and is correct on a linear axis; on a log axis
@@ -28,14 +29,20 @@ import { useBandLadder } from './use-band-ladder.js';
28
29
  * layer's meaning ("fill from the bottom") on both scale kinds.
29
30
  */
30
31
  export function resolveAreaBaseline(baseline, yScale) {
31
- const floor = domainFloor(yScale);
32
+ const d = yScale.domain?.();
33
+ const floor = d && d.length > 0 ? d[0] : 0;
32
34
  if (baseline === undefined)
33
35
  return floor;
34
- return Number.isFinite(yScale(baseline)) ? baseline : floor;
35
- }
36
- function domainFloor(yScale) {
37
- const d = yScale.domain?.();
38
- return d && d.length > 0 ? d[0] : 0;
36
+ if (!Number.isFinite(yScale(baseline)))
37
+ return floor;
38
+ if (!d || d.length === 0)
39
+ return baseline;
40
+ // Clamped into the domain, as a bar's baseline is: under an explicit
41
+ // `<YAxis min={40}>` the default 0 is off the plot, and a fill resting
42
+ // there would anchor its fade (and a gap's `'fade'` connector) off-plot.
43
+ const lo = Math.min(d[0], d[d.length - 1]);
44
+ const hi = Math.max(d[0], d[d.length - 1]);
45
+ return Math.min(hi, Math.max(lo, baseline));
39
46
  }
40
47
  /**
41
48
  * An area draw layer: fills between a value `column` and a `baseline`, with a
@@ -45,15 +52,15 @@ function domainFloor(yScale) {
45
52
  * against its `axis`), and renders nothing to the DOM — the row draws it. The
46
53
  * fill + outline break at gaps rather than spanning them.
47
54
  *
48
- * Two forms via `baseline` (see {@link AreaChartProps.baseline}): omit it for
49
- * the **elevation** form (rest on the axis floor) or pass `0` for the
50
- * **above/below-axis** form (positive up, negative down). The esnet two-colour
51
- * traffic look composes two layers, each with its own `as` role:
55
+ * The fill rests on `baseline` (see {@link AreaChartProps.baseline}): **`0`
56
+ * by default** (positive up, negative down), any other number for a different
57
+ * reference level, or `'floor'` for the bottom of the plot. The esnet
58
+ * two-colour traffic look composes two layers, each with its own `as` role:
52
59
  *
53
60
  * ```tsx
54
61
  * <Layers>
55
- * <AreaChart series={s} column="in" baseline={0} as="in" />
56
- * <AreaChart series={s} column="out" baseline={0} as="out" />
62
+ * <AreaChart series={s} column="in" as="in" />
63
+ * <AreaChart series={s} column="out" as="out" />
57
64
  * </Layers>
58
65
  * ```
59
66
  */
@@ -66,6 +73,8 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
66
73
  if (layers === null) {
67
74
  throw new Error('<AreaChart> must be rendered inside a <Layers>');
68
75
  }
76
+ // `undefined` below means "the axis floor" — the internal helpers' spelling.
77
+ const baseValue = baseline === 'floor' ? undefined : (baseline ?? 0);
69
78
  const cs = useMemo(() => series instanceof ValueSeries
70
79
  ? fromValueSeries(series, column)
71
80
  : fromTimeSeries(series, column), [series, column]);
@@ -141,7 +150,13 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
141
150
  const entry = useMemo(() => ({
142
151
  layer: {
143
152
  as: semantic,
144
- yExtent: () => areaExtent(cs, baseline),
153
+ // A baseline with no position on a log axis (zero, the default) is
154
+ // left out of the fit: the draw rests it on the floor anyway, and an
155
+ // extent of `[0, max]` gives the log fit no positive low end, so it
156
+ // would collapse the domain onto the max and clip the series.
157
+ yExtent: (scale) => areaExtent(cs, scale === 'log' && baseValue !== undefined && baseValue <= 0
158
+ ? undefined
159
+ : baseValue),
145
160
  // The container infers the shared x scale's kind from its layers — a
146
161
  // ValueSeries plots on a value axis, a TimeSeries on time.
147
162
  xKind: series instanceof ValueSeries ? 'value' : 'time',
@@ -155,7 +170,7 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
155
170
  sweepAxis: 'x',
156
171
  sweepSpanOnly: true,
157
172
  hitTest: (px, py, xScale, yScale) => {
158
- const i = areaHitIndex(cs, baseline, px, py, xScale, yScale);
173
+ const i = areaHitIndex(cs, baseValue, px, py, xScale, yScale);
159
174
  if (i === null)
160
175
  return null;
161
176
  return {
@@ -266,15 +281,15 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
266
281
  : { ...inStyle, color: style.spanColor, fill: style.spanColor }, 1), true, 0, plotExtentOf(ctx, xScale, yScale).width);
267
282
  function drawAreaWith(st) {
268
283
  return drawArea(ctx, cs, xScale, yScale, st,
269
- // Omitted baseline rests on the axis floor (resolved late from the
270
- // scale, so it tracks the auto-fit domain); a fixed baseline is used
271
- // verbatim.
284
+ // `'floor'` rests on the axis floor (resolved late from the
285
+ // scale, so it tracks the auto-fit domain); a number (default 0)
286
+ // is used verbatim.
272
287
  // A log axis has no position for zero — or anything at or below
273
288
  // it — so an explicit out-of-domain `baseline` would scale to
274
289
  // `NaN` and poison every coordinate in the fill path. Fall back
275
- // to the axis floor, which is exactly what an omitted baseline
290
+ // to the axis floor, which is exactly what `'floor'`
276
291
  // already resolves to.
277
- resolveAreaBaseline(baseline, yScale), curveFactory, gaps, gapConnectorOpacity, decimate, bandLadder);
292
+ resolveAreaBaseline(baseValue, yScale), curveFactory, gaps, gapConnectorOpacity, decimate, bandLadder);
278
293
  }
279
294
  },
280
295
  },
@@ -288,7 +303,7 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
288
303
  readoutY,
289
304
  style,
290
305
  label,
291
- baseline,
306
+ baseValue,
292
307
  curveFactory,
293
308
  gaps,
294
309
  gapConnectorOpacity,
@@ -27,6 +27,23 @@ export interface BandChartCommon<S extends SeriesSchema = SeriesSchema, VS exten
27
27
  * Denoise the underlying values with `smooth()`, not this.
28
28
  */
29
29
  curve?: Curve;
30
+ /**
31
+ * Break the envelope at each **trading-axis discontinuity** (a session / day /
32
+ * lunch close→open) when the container renders on a trading-time axis (a
33
+ * `discontinuities` / `calendar` provider). **Omitted ⇒ `false`**: the fill
34
+ * connects the last pre-close sample straight to the next open across the
35
+ * collapsed gap (a near-vertical sliver). `true` ends the fill at the close
36
+ * and re-starts it at the open — the intraday look, where one session's
37
+ * envelope shouldn't visually flow into the next. Same semantics as
38
+ * {@link LineChart}'s `sessionBreaks`, so a band and its centre line break in
39
+ * step.
40
+ *
41
+ * This is a **scale** break (driven by the axis's collapsed gaps), orthogonal
42
+ * to a **data** break (a NaN run on either edge, which a band always breaks
43
+ * at). A no-op on a continuous axis (no provider) or a provider without
44
+ * `boundaries`.
45
+ */
46
+ sessionBreaks?: boolean;
30
47
  /**
31
48
  * **M4 viewport decimation** (charts decimator wave). **Omitted ⇒ `true`**:
32
49
  * once the visible envelope is denser than ~2 samples per device pixel, it is
@@ -34,6 +51,8 @@ export interface BandChartCommon<S extends SeriesSchema = SeriesSchema, VS exten
34
51
  * the samples span, so it covers the same pixels from O(plot width) points.
35
52
  * Applies with a linear `curve`; pass `false` to always fill every sample, or
36
53
  * `{ threshold }` to tune. Shares {@link LineChart}'s `DecimateOption`.
54
+ * Composes with {@link sessionBreaks}: the break instants are folded into the
55
+ * pixel-column edges so no column merges two sessions' envelopes.
37
56
  */
38
57
  decimate?: DecimateOption;
39
58
  /**
@@ -92,6 +111,6 @@ export type BandChartProps<S extends SeriesSchema = SeriesSchema, VS extends Val
92
111
  * </Layers>
93
112
  * ```
94
113
  */
95
- export declare function BandChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, lower, upper, as: semantic, axis, curve, decimate, legend, index, }: BandChartProps<S, VS>): null;
114
+ export declare function BandChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, lower, upper, as: semantic, axis, curve, sessionBreaks, decimate, legend, index, }: BandChartProps<S, VS>): null;
96
115
  export {};
97
116
  //# sourceMappingURL=BandChart.d.ts.map
package/dist/BandChart.js CHANGED
@@ -6,6 +6,9 @@ import { resolveCurve } from './curve.js';
6
6
  import { ContainerContext, LayersContext } from './context.js';
7
7
  import { legendLabelFor, useLegendItems, } from './swatch.js';
8
8
  import { useSlotKey } from './use-slot-key.js';
9
+ /** Stable empty boundary list — so `sessionBreaks={false}` keeps a referentially
10
+ * constant array and the layer entry isn't rebuilt every render. */
11
+ const NO_BREAKS = [];
9
12
  /**
10
13
  * A variance-band draw layer: fills the envelope between the `lower` and `upper`
11
14
  * columns of `series` (typically `rollingByColumn` percentiles), gap-aware, and
@@ -23,7 +26,7 @@ import { useSlotKey } from './use-slot-key.js';
23
26
  * </Layers>
24
27
  * ```
25
28
  */
26
- export function BandChart({ series, lower, upper, as: semantic, axis, curve, decimate = true, legend, index = 0, }) {
29
+ export function BandChart({ series, lower, upper, as: semantic, axis, curve, sessionBreaks = false, decimate = true, legend, index = 0, }) {
27
30
  const container = useContext(ContainerContext);
28
31
  if (container === null) {
29
32
  throw new Error('<BandChart> must be rendered inside a <ChartContainer>');
@@ -35,6 +38,18 @@ export function BandChart({ series, lower, upper, as: semantic, axis, curve, dec
35
38
  const bs = useMemo(() => series instanceof ValueSeries
36
39
  ? bandFromValueSeries(series, lower, upper)
37
40
  : bandFromTimeSeries(series, lower, upper), [series, lower, upper]);
41
+ // Trading-axis session breaks: the collapse instants inside this band's span
42
+ // (session/day/lunch opens the axis skips). Data instants, not pixels — so the
43
+ // set is view-independent (pan/zoom reuse it). Only computed when opted in and
44
+ // the container carries a boundary-reporting discontinuity provider. The same
45
+ // lookup `<LineChart>` does, so a band and its centre line break identically.
46
+ const sessionBreakInstants = useMemo(() => {
47
+ const provider = container.discontinuities;
48
+ if (!sessionBreaks || provider?.boundaries === undefined || bs.length < 2) {
49
+ return NO_BREAKS;
50
+ }
51
+ return provider.boundaries(bs.x[0], bs.x[bs.length - 1]);
52
+ }, [sessionBreaks, container.discontinuities, bs]);
38
53
  // Styling: semantic identifier → theme band style. The single styling channel.
39
54
  const { band } = container.theme;
40
55
  const style = (semantic !== undefined ? band[semantic] : undefined) ?? band.default;
@@ -118,7 +133,7 @@ export function BandChart({ series, lower, upper, as: semantic, axis, curve, dec
118
133
  },
119
134
  ];
120
135
  },
121
- draw: (ctx, xScale, yScale) => drawBand(ctx, bs, xScale, yScale, style, curveFactory, decimate),
136
+ draw: (ctx, xScale, yScale) => drawBand(ctx, bs, xScale, yScale, style, curveFactory, sessionBreakInstants, decimate),
122
137
  },
123
138
  axisId: axis,
124
139
  index,
@@ -129,6 +144,7 @@ export function BandChart({ series, lower, upper, as: semantic, axis, curve, dec
129
144
  upper,
130
145
  style,
131
146
  curveFactory,
147
+ sessionBreakInstants,
132
148
  decimate,
133
149
  axis,
134
150
  index,
package/dist/BoxPlot.js CHANGED
@@ -139,9 +139,11 @@ export function BoxPlot({ series, lower, q1, median, q3, upper, as: semantic, ax
139
139
  // The readout reads the box **under the cursor** (boxIndexAtTime — span
140
140
  // containment, not nearest-by-begin which flips past a wide box's
141
141
  // midpoint), anchored at the box **centre** `(x + xEnd) / 2`. Outside
142
- // every box → no readout. Off-chart fan-in only; the in-chart flag is
143
- // `cursorFlag`. `push` skips a non-finite quantile, so an absent
144
- // (range-only) q1/q3/median simply doesn't read out.
142
+ // every box → no readout. Feeds the off-chart readout and the
143
+ // crosshair (its x-snap lands mid-box, its y-snap on the nearest
144
+ // quantile); the in-chart flag is `cursorFlag`. `push` skips a
145
+ // non-finite quantile, so an absent (range-only) q1/q3/median simply
146
+ // doesn't read out.
145
147
  if (bx.length === 0)
146
148
  return [];
147
149
  const i = boxIndexAtTime(bx, x);
@@ -111,9 +111,9 @@ export interface CandlestickProps<S extends SeriesSchema> {
111
111
  * supersedes `BoxPlot shape='solid'` for OHLC (which needed a quantile remap, a
112
112
  * body precompute, two overlaid layers for green/red, and a column-name tracker).
113
113
  *
114
- * **Cursor.** Unlike `BoxPlot`, a candle **participates in the crosshair x-snap**
115
- * (it exposes plain `sampleAt`, not a consolidated `cursorFlag`), so the reticle
116
- * lands on candles. The readout keys on `as` and shows `close` by default; pass
114
+ * **Cursor.** A candle **participates in the crosshair snap** (as a box plot
115
+ * does), so the reticle lands on candles; unlike a box plot it has no
116
+ * consolidated `cursorFlag`, so the flag cursor shows a chip per value. The readout keys on `as` and shows `close` by default; pass
117
117
  * `showOHLC` for the full four-pill quote.
118
118
  *
119
119
  * ```tsx
@@ -37,9 +37,9 @@ import { spansForLayer } from './span.js';
37
37
  * supersedes `BoxPlot shape='solid'` for OHLC (which needed a quantile remap, a
38
38
  * body precompute, two overlaid layers for green/red, and a column-name tracker).
39
39
  *
40
- * **Cursor.** Unlike `BoxPlot`, a candle **participates in the crosshair x-snap**
41
- * (it exposes plain `sampleAt`, not a consolidated `cursorFlag`), so the reticle
42
- * lands on candles. The readout keys on `as` and shows `close` by default; pass
40
+ * **Cursor.** A candle **participates in the crosshair snap** (as a box plot
41
+ * does), so the reticle lands on candles; unlike a box plot it has no
42
+ * consolidated `cursorFlag`, so the flag cursor shows a chip per value. The readout keys on `as` and shows `close` by default; pass
43
43
  * `showOHLC` for the full four-pill quote.
44
44
  *
45
45
  * ```tsx
@@ -94,8 +94,8 @@ export function Candlestick({ series, open = 'open', high = 'high', low = 'low',
94
94
  // The readout reads the candle **under the cursor** (containment span,
95
95
  // not nearest-by-begin), anchored at the slot centre. Outside every
96
96
  // candle → no readout. No `cursorFlag`: the samples flow through the
97
- // normal per-series tracker path, which is also what keeps the candle
98
- // in the crosshair x-snap (BoxPlot's cursorFlag opts out of both).
97
+ // normal per-series tracker path (BoxPlot's cursorFlag takes its
98
+ // values out of the per-series dots and chips).
99
99
  if (ohlc.length === 0)
100
100
  return [];
101
101
  const i = ohlcIndexAtTime(ohlc, time);
@@ -1,9 +1,8 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { type DiscontinuityProvider, type TradingCalendarLike } from './tradingTimeScale.js';
3
- import { Sequence, BoundedSequence } from 'pond-ts';
4
3
  import type { TimeRange } from 'pond-ts';
5
- import { type AnnotationKind, type CreateSpec, type CursorMode, type TrackerInfo, type DrawStatsFrame } from './context.js';
6
- import { type AxisFormat, type CursorFormat } from './format.js';
4
+ import { type AnnotationKind, type CreateSpec, type TrackerInfo, type DrawStatsFrame } from './context.js';
5
+ import { type AxisFormat } from './format.js';
7
6
  import { type ChartTheme } from './theme.js';
8
7
  export interface ChartContainerProps {
9
8
  /**
@@ -210,7 +209,7 @@ export interface ChartContainerProps {
210
209
  * (`TimeZone`) places these ticks and cuts `Sequence.calendar` buckets, so
211
210
  * `Sequence.calendar('day', { timeZone })` and `<ChartContainer timeZone>`
212
211
  * given the same zone put a bucket edge and its tick on one instant.
213
- * Function formatters (`timeFormat`, `cursorFormat`) still receive epoch ms;
212
+ * Function formatters (`timeFormat`, a cursor's `format`) still receive epoch ms;
214
213
  * read the resolved zone from the chart context. An unknown id throws
215
214
  * `RangeError`. Only affects a **time** axis.
216
215
  */
@@ -387,93 +386,10 @@ export interface ChartContainerProps {
387
386
  *
388
387
  * **Omit or pass `null`** (equivalent) for no controlled position — a hovered
389
388
  * chart still tracks its pointer, a non-hovered one shows nothing. To force a
390
- * chart to *never* show a cursor, use `cursor="none"`, not `trackerPosition`.
391
- * See {@link onTrackerChanged}.
389
+ * chart to *never* show a cursor, mount no cursor component, rather than
390
+ * clearing `trackerPosition`. See {@link onTrackerChanged}.
392
391
  */
393
392
  trackerPosition?: number | null;
394
- /**
395
- * In-chart cursor presentation — the default for all rows (a row may override
396
- * via `<ChartRow cursor>`). **Default `'line'`** — the synced vertical line,
397
- * with values surfaced *outside* the chart via {@link onTrackerChanged}.
398
- * `'point'` / `'inline'` / `'flag'` add per-series marks; `'none'` hides it.
399
- * `'region'` shades the bucket under the pointer (needs {@link cursorSequence}).
400
- * See {@link CursorMode}.
401
- *
402
- * @deprecated Mount a **cursor component** instead — `<LineCursor>` /
403
- * `<PointCursor>` / `<InlineCursor>` / `<FlagCursor>` / `<CrosshairCursor>` /
404
- * `<RangeCursor>` as a child of the container (or inside a `<ChartRow>` for
405
- * the per-row override); mount nothing for `'none'`. This prop keeps working
406
- * for one more minor by synthesizing the equivalent preset internally; a
407
- * mounted cursor component overrides it. See `docs/rfcs/interaction.md` §9.
408
- */
409
- cursor?: CursorMode;
410
- /**
411
- * The bucketing for `cursor="region"` — the interval highlighted under the
412
- * pointer. A pond {@link Sequence} (duration or calendar-aware —
413
- * `Sequence.every('1d')`, `Sequence.calendar('month')`) is realized over the
414
- * current view; a {@link BoundedSequence} (e.g. a `TradingCalendar`'s
415
- * `sessionSequence()` / `barSequence()`) is used as-is, so the band can track
416
- * whole **sessions**. Either way the band maps through `xScale`, so on a
417
- * trading-time axis the closed part of the bucket collapses. Ignored unless
418
- * `cursor="region"`.
419
- *
420
- * **Time axis only.** A bucket is a *time* interval, so the region cursor is
421
- * gated to a **time** x-axis — on a **value** axis (a horizontal histogram, a
422
- * value-keyed chart) it's a no-op (highlighting a value *band* on a horizontal
423
- * histogram would be a different, y-oriented cursor).
424
- *
425
- * **Pass a stable reference.** The buckets are memoized on this value + the
426
- * view range; a `Sequence`/`BoundedSequence` rebuilt inline every render
427
- * re-realizes the buckets on each pointer move (harmless for a coarse
428
- * day/session sequence, wasteful for a fine one over a wide view) — hoist it or
429
- * `useMemo` it.
430
- *
431
- * @deprecated Use `<RangeCursor sequence={…}>` — the prop moved onto the
432
- * component that uses it, where it is no longer mode-conditional. Works for
433
- * one more minor; a mounted `<RangeCursor>`'s sequence wins over this.
434
- */
435
- cursorSequence?: Sequence | BoundedSequence;
436
- /**
437
- * Makes the `region` cursor **draggable**: drag across the plot and the band
438
- * extends **bucket by bucket** (snapping to `cursorSequence` points); on
439
- * release this fires **once** with the selected `[lo, hi]` span, and the cursor
440
- * reverts to the single-bucket highlight (it does not keep the range). Typical
441
- * use — zoom the view to the returned span (the container doesn't zoom itself;
442
- * that's the consumer's call), or map it onto a data subscription's range params.
443
- *
444
- * The span is a **neutral numeric pair in axis units** — epoch ms on a **time**
445
- * axis, the axis value (strike, distance, …) on a **value** axis — mirroring the
446
- * polymorphic `range` input. A time consumer that wants a `TimeRange` builds one
447
- * from the pair.
448
- *
449
- * With **no `cursorSequence`** the region cursor is the degenerate case — it
450
- * renders as a **line** on hover and the drag is **freeform** (raw `[lo, hi]`, no
451
- * bucket snapping); the same callback fires on release. Bucket snapping needs a
452
- * `cursorSequence`, which is **time-axis only** (a time interval over a value
453
- * domain is meaningless), so a **value** axis is always freeform. No-op unless
454
- * `cursor="region"` on a **time** or **value** x-axis (a **category** axis is
455
- * excluded — an ordinal-slot select is a different gesture).
456
- *
457
- * @deprecated Use `<RangeCursor onDragRelease>` — the drag moved onto the
458
- * component, and the payload becomes `{ x: [lo, hi] }` (a {@link RangeSpan},
459
- * forward-compatible with the 2-D drag's optional `y`) instead of the bare
460
- * pair. Works for one more minor; a mounted `<RangeCursor onDragRelease>`
461
- * takes over the gesture.
462
- */
463
- onRegionSelect?: (range: readonly [number, number]) => void;
464
- /**
465
- * Which modifier a region-drag needs — set `'shift'` when you also enable
466
- * **pan** (`panZoom="pan"` or `"panZoom"`) and want **plain drag to pan,
467
- * shift-drag to select**. It's only enforced while pan is enabled (with pan
468
- * off there's no gesture conflict, so shift is optional — either drag
469
- * selects). **Omitted** ⇒ a region-drag
470
- * **preempts** pan (drag always selects; document that precedence for users).
471
- * Wheel-zoom is unaffected in every case.
472
- *
473
- * @deprecated Use `<RangeCursor dragModifier>` — the prop moved onto the
474
- * component alongside `onDragRelease`. Works for one more minor.
475
- */
476
- regionSelectModifier?: 'shift';
477
393
  /**
478
394
  * Fires on pointer move with the hovered time + every series' value there (so
479
395
  * you can render a readout outside the chart), and `null` on leave.
@@ -561,30 +477,6 @@ export interface ChartContainerProps {
561
477
  onTimeRangeChange?: (range: [number, number]) => void;
562
478
  /** Zoom-in floor — the minimum visible duration in ms. Default `1`. */
563
479
  minDuration?: number;
564
- /**
565
- * Show the cursor's time atop the in-chart readout (when a row's `cursor` draws
566
- * one). **Default `false`.** Formatted by {@link timeFormat} to match the time
567
- * axis.
568
- *
569
- * @deprecated Use `showTime` on the mounted cursor component
570
- * (`<LineCursor showTime>` / `<FlagCursor showTime>` / …). Works for one
571
- * more minor via the shim.
572
- */
573
- cursorTime?: boolean;
574
- /**
575
- * `cursor="crosshair"` reticle **y** snapping. **Default `true`** — the
576
- * crosshair centres on the nearest **data point** (the horizontal line snaps to
577
- * that sample's value). `false` — the horizontal line + centre follow the
578
- * pointer **y** freely, the value read as `yScale.invert(pointerY)`. Either way
579
- * the vertical line snaps its **x** to the data grid (so the time readout is
580
- * clean), and both draw a full-height dashed vertical + full-width dashed
581
- * horizontal line.
582
- *
583
- * @deprecated Use `<CrosshairCursor snap={…}>` — the prop moved onto the
584
- * component, where it is no longer mode-conditional. Works for one more
585
- * minor via the shim.
586
- */
587
- crosshairSnap?: boolean;
588
480
  /**
589
481
  * Enter **annotation-edit mode**: suppresses the data cursor and makes editable
590
482
  * annotations (those given an `onChange`) interactive — hovering one reveals its
@@ -634,43 +526,10 @@ export interface ChartContainerProps {
634
526
  * **owns the labels**, so it opts the axis out of the `dateStyle` ladder
635
527
  * (flat / stacked) by design. **Omitted ⇒ the flat/stacked date style.** To
636
528
  * shape only the cursor readout while keeping a date style, use
637
- * {@link cursorFormat} instead. (For back-compat this also shapes the readout
638
- * when `cursorFormat` is absent.)
529
+ * a mounted cursor's `format` (e.g. `<CrosshairCursor format>`) instead. (For
530
+ * back-compat this also shapes the readout when no cursor `format` is set.)
639
531
  */
640
532
  timeFormat?: AxisFormat;
641
- /**
642
- * The **cursor / marker readout** format — the crosshair x pill, marker
643
- * axis indicators, and annotation auto-labels — **independent of the tick
644
- * labels** on both axis kinds: it does **not** disqualify the `dateStyle`
645
- * ladder (time), and it never moves the tick labels (value). It beats an
646
- * explicit `<XAxis format>` for the **readout only** — pill precedence is
647
- * `cursorFormat → axis format → container` — so terse ticks can pair with a
648
- * precise readout (`+2.0σ` labels, `+1.83σ` pill).
649
- *
650
- * **Omitted ⇒ the axis's own formatter.** On a time axis that default is
651
- * grain-aware: the readout formats at the axis's granularity, so a
652
- * day-or-coarser axis reads a **date** (never a time-of-day) and a sub-day
653
- * axis reads date + clock — a daily bar at a foreign-tz midnight no longer
654
- * renders as `02 AM`. On a value axis it is the tick formatter
655
- * ({@link timeFormat}-shaped, else the d3 default).
656
- *
657
- * A d3 specifier **string** formats uniformly (time specifier on a time
658
- * axis, number specifier on a value axis); a **function**
659
- * `(value, { grain, defaultText }) => string` receives the axis's resolved
660
- * coarse {@link TimeGrain} (`undefined` on a value axis) and the default
661
- * readout text, so it can branch on the zoom level and pass `defaultText`
662
- * through for grains it doesn't override (no re-deriving the grain from the
663
- * range). See {@link CursorFormat}. This is the independent readout channel;
664
- * {@link timeFormat} owns the labels. (A category axis reads names, and a
665
- * `transform`ed axis's pill speaks its derived unit — neither consults
666
- * `cursorFormat`.)
667
- *
668
- * @deprecated Use `format` on the mounted cursor (`<CrosshairCursor
669
- * format={…}>`) — it feeds the same shared readout channel (marker
670
- * indicators and annotation auto-labels included). Works for one more
671
- * minor; a mounted cursor's `format` wins over this.
672
- */
673
- cursorFormat?: CursorFormat;
674
533
  /**
675
534
  * Label the x axis as **offsets from a zero point** instead of absolute
676
535
  * values — the *duration* (elapsed-time) axis. A time axis reads
@@ -690,7 +549,7 @@ export interface ChartContainerProps {
690
549
  * and so does the cursor pill (one grain finer, as ever: `00:05:12`).
691
550
  *
692
551
  * This is a **labelling** mode, not a data transform: `range`, an annotation's
693
- * `at`, an `onRegionSelect` span, `trackerPosition` are all still absolute
552
+ * `at`, a `<RangeCursor onDragRelease>` span, `trackerPosition` are all still absolute
694
553
  * axis units. Ignored on a category axis. An explicit `timeFormat` /
695
554
  * `<XAxis format>` still wins — on a time axis a d3 *time* specifier can only
696
555
  * describe an instant, so it labels the underlying wall clock (the lever for