@pond-ts/charts 0.70.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.
- package/AGENTS.md +3 -1
- package/API.md +30 -31
- package/CHANGELOG.md +111 -1
- package/dist/AreaChart.d.ts +30 -22
- package/dist/AreaChart.js +37 -22
- package/dist/BoxPlot.js +5 -3
- package/dist/Candlestick.d.ts +3 -3
- package/dist/Candlestick.js +5 -5
- package/dist/ChartContainer.d.ts +8 -149
- package/dist/ChartContainer.js +68 -111
- package/dist/ChartRow.d.ts +1 -12
- package/dist/ChartRow.js +15 -30
- package/dist/HeatMap.d.ts +3 -3
- package/dist/HeatMap.js +3 -3
- package/dist/Layers.js +84 -34
- package/dist/XAxis.js +1 -0
- package/dist/area.d.ts +6 -5
- package/dist/area.js +13 -6
- package/dist/axis-events.d.ts +4 -4
- package/dist/brush.d.ts +13 -21
- package/dist/brush.js +22 -36
- package/dist/context.d.ts +81 -96
- package/dist/cursors.d.ts +81 -51
- package/dist/cursors.js +164 -127
- package/dist/elapsed.d.ts +1 -1
- package/dist/elapsed.js +1 -1
- package/dist/format.d.ts +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/selection-fixtures.d.ts +4 -7
- package/dist/selection-fixtures.js +0 -9
- package/dist/selection-stories.d.ts +4 -6
- package/dist/selection-stories.js +4 -4
- package/dist/selectors.d.ts +2 -3
- package/dist/selectors.js +2 -3
- package/dist/tracker.d.ts +10 -28
- package/dist/tracker.js +17 -40
- package/dist/tradingTimeScale.d.ts +2 -2
- package/package.json +3 -3
package/dist/ChartContainer.d.ts
CHANGED
|
@@ -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
|
|
6
|
-
import { type AxisFormat
|
|
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`, `
|
|
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,
|
|
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
|
-
*
|
|
638
|
-
* when `
|
|
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`,
|
|
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
|
package/dist/ChartContainer.js
CHANGED
|
@@ -6,12 +6,12 @@ import { scaleBand } from './bandScale.js';
|
|
|
6
6
|
import { scaleElapsed } from './elapsed.js';
|
|
7
7
|
import { Sequence, TimeZone } from 'pond-ts';
|
|
8
8
|
import { ContainerContext, CursorContext, } from './context.js';
|
|
9
|
-
import {
|
|
9
|
+
import { warnOnDuplicateGestureOwners } from './cursors.js';
|
|
10
10
|
import { effectiveSelectorEntries, resolveControlledHovered, resolveControlledSelected, selectorEntryEqual, warnInertClick, } from './selectors.js';
|
|
11
11
|
import { isDev } from './dev.js';
|
|
12
12
|
import { maxSlotWidths, sum } from './slots.js';
|
|
13
13
|
import { computeLabelLanes } from './annotations.js';
|
|
14
|
-
import { resolveCursorX
|
|
14
|
+
import { resolveCursorX } from './tracker.js';
|
|
15
15
|
import { clampToBounds } from './viewport.js';
|
|
16
16
|
import { resolveAxisFormat, resolveTimeFormat, } from './format.js';
|
|
17
17
|
import { TimeAxis } from './TimeAxis.js';
|
|
@@ -198,14 +198,7 @@ function AutoSizeContainer(props) {
|
|
|
198
198
|
* pass; a chart legitimately gated this long is not painting anyway. */
|
|
199
199
|
const ZERO_SIZE_WARNING_MS = 600;
|
|
200
200
|
/** {@link ChartContainer} with its width resolved to a concrete pixel number. */
|
|
201
|
-
function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidth, bandAlign = 'start', width, height, rowGap = 0, showAxis = true, trackerPosition, onTrackerChanged, onDrawStats, panZoom = false, axisPanZoom = false, bounds, onTimeRangeChange, minDuration = 1,
|
|
202
|
-
// ── Legacy cursor props (deprecated) ───────────────────────────────────────
|
|
203
|
-
// The string surface keeps working for one minor: the resolved mode is
|
|
204
|
-
// synthesized into the equivalent mounted preset below (`<LegacyCursor>`),
|
|
205
|
-
// and a dev warning names the replacement whenever any of the props is
|
|
206
|
-
// *explicitly* set (never on the defaults). Mounted cursor components in the
|
|
207
|
-
// same scope override the shim. See docs/rfcs/interaction.md §9 / A4.4.
|
|
208
|
-
const cursor = cursorProp ?? DEFAULT_CURSOR_MODE;
|
|
201
|
+
function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidth, bandAlign = 'start', width, height, rowGap = 0, showAxis = true, trackerPosition, onTrackerChanged, onDrawStats, panZoom = false, axisPanZoom = false, bounds, onTimeRangeChange, minDuration = 1, editAnnotations = false, creating = null, onCreate, onSelectAnnotation, onHoverAnnotation, onEditAnnotation, snap = true, timeFormat, origin, theme, discontinuities, calendar, timeZone: timeZoneProp, spacing, xScale: xScaleKind = 'linear', grid = true, sessionDividers = 'none', children, }) {
|
|
209
202
|
// [PND-HEIGHT] Whether this container owns vertical layout (see the
|
|
210
203
|
// `height` prop). Carried on the frame so a `<ChartRow flex>` can tell a
|
|
211
204
|
// home that can size it from one that never will.
|
|
@@ -223,43 +216,8 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
223
216
|
const declaredCategories = useMemo(() => categoriesProp === undefined ? null : [...categoriesProp],
|
|
224
217
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- content identity
|
|
225
218
|
[categoriesKey]);
|
|
226
|
-
const cursorTime = cursorTimeProp ?? false;
|
|
227
|
-
const crosshairSnap = crosshairSnapProp ?? true;
|
|
228
|
-
const warnedLegacyRef = useRef(false);
|
|
229
|
-
useEffect(() => {
|
|
230
|
-
if (!isDev || warnedLegacyRef.current)
|
|
231
|
-
return;
|
|
232
|
-
const legacy = [];
|
|
233
|
-
if (cursorProp !== undefined)
|
|
234
|
-
legacy.push(`cursor="${cursorProp}" → mount ${presetNameFor(cursorProp)}`);
|
|
235
|
-
if (crosshairSnapProp !== undefined)
|
|
236
|
-
legacy.push('crosshairSnap → <CrosshairCursor snap>');
|
|
237
|
-
if (cursorTimeProp !== undefined)
|
|
238
|
-
legacy.push('cursorTime → showTime on the mounted cursor');
|
|
239
|
-
if (cursorFormatProp !== undefined)
|
|
240
|
-
legacy.push('cursorFormat → format on <CrosshairCursor>');
|
|
241
|
-
if (cursorSequenceProp !== undefined)
|
|
242
|
-
legacy.push('cursorSequence → <RangeCursor sequence>');
|
|
243
|
-
if (onRegionSelect !== undefined)
|
|
244
|
-
legacy.push('onRegionSelect → <RangeCursor onDragRelease> (the payload becomes ' +
|
|
245
|
-
'{ x: [lo, hi] })');
|
|
246
|
-
if (regionSelectModifier !== undefined)
|
|
247
|
-
legacy.push('regionSelectModifier → <RangeCursor dragModifier>');
|
|
248
|
-
if (legacy.length === 0)
|
|
249
|
-
return;
|
|
250
|
-
warnedLegacyRef.current = true;
|
|
251
|
-
console.warn(legacyCursorWarning(legacy));
|
|
252
|
-
}, [
|
|
253
|
-
cursorProp,
|
|
254
|
-
crosshairSnapProp,
|
|
255
|
-
cursorTimeProp,
|
|
256
|
-
cursorFormatProp,
|
|
257
|
-
cursorSequenceProp,
|
|
258
|
-
onRegionSelect,
|
|
259
|
-
regionSelectModifier,
|
|
260
|
-
]);
|
|
261
219
|
// Mounted-cursor registry ({@link ContainerFrame.registerCursor}): the
|
|
262
|
-
// presets
|
|
220
|
+
// presets register their specs here; rows and
|
|
263
221
|
// `<XAxis>` render the effective set. Same per-instance-slot discipline as
|
|
264
222
|
// the tracker sources; register is idempotent under reference equality (the
|
|
265
223
|
// presets memoize their entries on props).
|
|
@@ -282,16 +240,12 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
282
240
|
useEffect(() => {
|
|
283
241
|
warnOnDuplicateGestureOwners(cursors, warnedGestureRef);
|
|
284
242
|
}, [cursors]);
|
|
285
|
-
// The registered cursors' resolution inputs
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
// entry wins (the shim registers before the children mount, so a bare
|
|
289
|
-
// first-wins would let the legacy synthesis shadow a real mount); the
|
|
290
|
-
// legacy prop is the fallback during the window. (`sequence` resolves the
|
|
243
|
+
// The registered cursors' resolution inputs: a mounted cursor's `format`
|
|
244
|
+
// feeds the shared readout channel (the x pill, marker indicators,
|
|
245
|
+
// annotation auto-labels). First mounted wins. (`sequence` resolves the
|
|
291
246
|
// same way, below the selector registry — a `<MultiSelector sequence>`
|
|
292
247
|
// feeds the same channel.)
|
|
293
|
-
const cursorFormat = useMemo(() => cursors.find((e) =>
|
|
294
|
-
cursorFormatProp, [cursors, cursorFormatProp]);
|
|
248
|
+
const cursorFormat = useMemo(() => cursors.find((e) => e.format !== undefined)?.format, [cursors]);
|
|
295
249
|
// Which axes the gestures own. **Pan follows zoom's degrees of freedom**: an
|
|
296
250
|
// axis that can be zoomed can be panned, because a zoomed axis shows less than
|
|
297
251
|
// all of itself and the reader needs to reach the rest. `'pan'` is the one
|
|
@@ -445,7 +399,7 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
445
399
|
setSelectableKeys(next);
|
|
446
400
|
}, []);
|
|
447
401
|
// Mounted-selector registry ({@link ContainerFrame.registerSelector}):
|
|
448
|
-
// `<Selector>`
|
|
402
|
+
// `<Selector>` / `<MultiSelector>` register here, and **the
|
|
449
403
|
// registration is what enables a plot click** (interaction RFC §7.1). Same
|
|
450
404
|
// per-instance-slot discipline as the cursors/tracker sources. Mirrored to a
|
|
451
405
|
// ref because `select` / `setHovered` are `[]`-stable callbacks that must not
|
|
@@ -477,15 +431,13 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
477
431
|
}, []);
|
|
478
432
|
const selectors = useMemo(() => Array.from(selectorMap.values()), [selectorMap]);
|
|
479
433
|
const selectorsRef = useRef(selectors);
|
|
480
|
-
// The shared snap-bucket sequence,
|
|
481
|
-
//
|
|
482
|
-
//
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
const cursorSequence = useMemo(() => cursors.find((e) =>
|
|
486
|
-
selectors.find((e) => e.sequence !== undefined)?.sequence
|
|
487
|
-
cursors.find((e) => e.sequence !== undefined)?.sequence ??
|
|
488
|
-
cursorSequenceProp, [cursors, selectors, cursorSequenceProp]);
|
|
434
|
+
// The shared snap-bucket sequence, resolved like `cursorFormat` above: a
|
|
435
|
+
// mounted `<RangeCursor sequence>` wins, then a mounted `<MultiSelector
|
|
436
|
+
// sequence>` (its sweep extends bucket by bucket over the same realized
|
|
437
|
+
// buckets — one channel, so the band and the sweep can never snap
|
|
438
|
+
// differently).
|
|
439
|
+
const cursorSequence = useMemo(() => cursors.find((e) => e.sequence !== undefined)?.sequence ??
|
|
440
|
+
selectors.find((e) => e.sequence !== undefined)?.sequence, [cursors, selectors]);
|
|
489
441
|
// Annotations register here so the container can do what a mark can't in
|
|
490
442
|
// isolation: draw its guide line across other rows, order regions, serve snap
|
|
491
443
|
// targets. Keyed by per-instance slot key (same discipline as the sources).
|
|
@@ -1201,10 +1153,10 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
1201
1153
|
// briefly stale until the next pointer move — rare, and the bounds check below
|
|
1202
1154
|
// hides an out-of-plot crosshair meanwhile.
|
|
1203
1155
|
const cursorX = resolveCursorX(trackerPosition, hoverX, xScale);
|
|
1204
|
-
//
|
|
1156
|
+
// `<RangeCursor>` snap buckets — the intervals the band snaps to (and a drag
|
|
1205
1157
|
// extends bucket by bucket over). Two sources, in precedence order:
|
|
1206
1158
|
//
|
|
1207
|
-
// 1. **An explicit `
|
|
1159
|
+
// 1. **An explicit `sequence`** (time axis only): realized over the view
|
|
1208
1160
|
// (a `Sequence` → `.bounded`; a `BoundedSequence` used as-is). A `Sequence`
|
|
1209
1161
|
// bucket is a *time* interval, so it's gated to a time axis — realizing time
|
|
1210
1162
|
// buckets over a value domain is meaningless (it would shade the whole plot).
|
|
@@ -1231,9 +1183,9 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
1231
1183
|
// or a time-axis histogram with no explicit sequence). `binIntervals` is only
|
|
1232
1184
|
// published by a vertical bar layer, so this is a no-op for
|
|
1233
1185
|
// line/area/scatter rows. On a **category** axis the bins are the unit
|
|
1234
|
-
// slots `[i, i+1)`: the
|
|
1235
|
-
//
|
|
1236
|
-
// them so it runs slot-edge to slot-edge — the band scale's `invert`
|
|
1186
|
+
// slots `[i, i+1)`: the `<RangeCursor>` band shades the slot under the
|
|
1187
|
+
// pointer ([PND-ORDCURSOR]), and the `<MultiSelector>` sweep's band snaps
|
|
1188
|
+
// over them so it runs slot-edge to slot-edge — the band scale's `invert`
|
|
1237
1189
|
// returns slot *centres*, and a centre-to-centre band disagreed with the
|
|
1238
1190
|
// snapped-outward span the release commits (RFC A7.6's edge rule).
|
|
1239
1191
|
//
|
|
@@ -1294,6 +1246,25 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
1294
1246
|
'axis; key the data on the quantity itself (a `ValueSeries`) to get a ' +
|
|
1295
1247
|
'value axis you can scale.');
|
|
1296
1248
|
}
|
|
1249
|
+
// [PND-ORDCURSOR] A `<RangeCursor onDragRelease>` on a category axis draws
|
|
1250
|
+
// its slot band but never starts a drag (`resolveRangeDrag`): the callback
|
|
1251
|
+
// would silently never fire, so say so once. Same `sources.size` guard as
|
|
1252
|
+
// above — the kind is 'time' until the layers register.
|
|
1253
|
+
const rangeDragOnCategory = resolvedKind === 'category' &&
|
|
1254
|
+
sources.size > 0 &&
|
|
1255
|
+
cursors.some((e) => e.onDragRelease !== undefined && e.enableDrag !== false);
|
|
1256
|
+
const warnedRangeDragOnCategoryRef = useRef(false);
|
|
1257
|
+
useEffect(() => {
|
|
1258
|
+
if (!isDev || !rangeDragOnCategory)
|
|
1259
|
+
return;
|
|
1260
|
+
if (warnedRangeDragOnCategoryRef.current)
|
|
1261
|
+
return;
|
|
1262
|
+
warnedRangeDragOnCategoryRef.current = true;
|
|
1263
|
+
console.warn('[pond-charts] <RangeCursor onDragRelease> is mounted on a category x ' +
|
|
1264
|
+
'axis, where the range drag is off: the band shades the slot under the ' +
|
|
1265
|
+
'pointer, but `onDragRelease` never fires. To drag across bars and get ' +
|
|
1266
|
+
'them back, mount a <MultiSelector> instead.');
|
|
1267
|
+
}, [rangeDragOnCategory]);
|
|
1297
1268
|
const xIsLog = ((s) => {
|
|
1298
1269
|
const probe = s;
|
|
1299
1270
|
return (typeof probe.base === 'function' || typeof probe.constant === 'function');
|
|
@@ -1342,15 +1313,12 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
1342
1313
|
rowGap,
|
|
1343
1314
|
setHoverX,
|
|
1344
1315
|
setHoverY,
|
|
1345
|
-
crosshairSnap,
|
|
1346
1316
|
cursorBuckets,
|
|
1347
1317
|
regionAnchor,
|
|
1348
1318
|
previewSpans,
|
|
1349
1319
|
setPreviewSpans,
|
|
1350
1320
|
setRegionAnchor,
|
|
1351
|
-
onRegionSelect,
|
|
1352
1321
|
reportDrawStats,
|
|
1353
|
-
regionSelectModifier,
|
|
1354
1322
|
draggingKey,
|
|
1355
1323
|
setDragging,
|
|
1356
1324
|
selected: selectedValue,
|
|
@@ -1358,8 +1326,6 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
1358
1326
|
select,
|
|
1359
1327
|
hovered: hoveredValue,
|
|
1360
1328
|
setHovered,
|
|
1361
|
-
cursor,
|
|
1362
|
-
cursorTime,
|
|
1363
1329
|
editAnnotations,
|
|
1364
1330
|
creating,
|
|
1365
1331
|
snap,
|
|
@@ -1428,15 +1394,12 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
1428
1394
|
rightGutter,
|
|
1429
1395
|
rowGap,
|
|
1430
1396
|
setHoverY,
|
|
1431
|
-
crosshairSnap,
|
|
1432
1397
|
cursorBuckets,
|
|
1433
1398
|
regionAnchor,
|
|
1434
1399
|
previewSpans,
|
|
1435
1400
|
setPreviewSpans,
|
|
1436
1401
|
setRegionAnchor,
|
|
1437
|
-
onRegionSelect,
|
|
1438
1402
|
reportDrawStats,
|
|
1439
|
-
regionSelectModifier,
|
|
1440
1403
|
draggingKey,
|
|
1441
1404
|
setDragging,
|
|
1442
1405
|
selectedValue,
|
|
@@ -1444,8 +1407,6 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
1444
1407
|
select,
|
|
1445
1408
|
hoveredValue,
|
|
1446
1409
|
setHovered,
|
|
1447
|
-
cursor,
|
|
1448
|
-
cursorTime,
|
|
1449
1410
|
editAnnotations,
|
|
1450
1411
|
creating,
|
|
1451
1412
|
snap,
|
|
@@ -1499,37 +1460,33 @@ function ResolvedChartContainer({ range, categories: categoriesProp, maxBandWidt
|
|
|
1499
1460
|
registerRow,
|
|
1500
1461
|
firstRowKey,
|
|
1501
1462
|
]);
|
|
1502
|
-
return (_jsx(ContainerContext.Provider, { value: frame, children:
|
|
1503
|
-
|
|
1504
|
-
//
|
|
1505
|
-
//
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
: {}
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
// child's floor is its content and nothing can ever get
|
|
1531
|
-
// smaller.
|
|
1532
|
-
...(height !== undefined ? { flex: '1 1 0%', minHeight: 0 } : {}),
|
|
1533
|
-
}, children: children }), showAxis && _jsx(TimeAxis, {})] })] }) }));
|
|
1463
|
+
return (_jsx(ContainerContext.Provider, { value: frame, children: _jsx(CursorContext.Provider, { value: cursorFrame, children: _jsxs("div", { style: {
|
|
1464
|
+
width: `${width}px`,
|
|
1465
|
+
// [PND-HEIGHT] A managed height makes the outer box a flex
|
|
1466
|
+
// column: the rows block below flexes, the axis strip keeps its
|
|
1467
|
+
// natural height at the bottom, and CSS subtracts one from the
|
|
1468
|
+
// other. That subtraction being layout rather than arithmetic is
|
|
1469
|
+
// the feature — the strip's height varies with label, font size,
|
|
1470
|
+
// calendar bands and pill lanes, so no constant is correct.
|
|
1471
|
+
...(height !== undefined
|
|
1472
|
+
? {
|
|
1473
|
+
height: `${height}px`,
|
|
1474
|
+
display: 'flex',
|
|
1475
|
+
flexDirection: 'column',
|
|
1476
|
+
}
|
|
1477
|
+
: {}),
|
|
1478
|
+
}, children: [_jsx("div", { style: {
|
|
1479
|
+
display: 'flex',
|
|
1480
|
+
flexDirection: 'column',
|
|
1481
|
+
gap: `${rowGap}px`,
|
|
1482
|
+
// The positioned ancestor for overlay chrome (`<Legend>`): the
|
|
1483
|
+
// card anchors to the rows block, never the axis strip below.
|
|
1484
|
+
position: 'relative',
|
|
1485
|
+
// The rows block takes what the axis strip leaves. `minHeight:
|
|
1486
|
+
// 0` lets it shrink below its content — without it a flex
|
|
1487
|
+
// child's floor is its content and nothing can ever get
|
|
1488
|
+
// smaller.
|
|
1489
|
+
...(height !== undefined ? { flex: '1 1 0%', minHeight: 0 } : {}),
|
|
1490
|
+
}, children: children }), showAxis && _jsx(TimeAxis, {})] }) }) }));
|
|
1534
1491
|
}
|
|
1535
1492
|
//# sourceMappingURL=ChartContainer.js.map
|
package/dist/ChartRow.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import { type CursorMode } from './context.js';
|
|
3
2
|
export interface ChartRowProps {
|
|
4
3
|
/**
|
|
5
4
|
* Row height in CSS pixels — the **fixed** sizing mode. Omit it (or pass
|
|
@@ -39,16 +38,6 @@ export interface ChartRowProps {
|
|
|
39
38
|
* hidden, so a `display: none` tab switch does not discard its scales.
|
|
40
39
|
*/
|
|
41
40
|
flex?: number;
|
|
42
|
-
/**
|
|
43
|
-
* Cursor presentation for this row, overriding the container's default
|
|
44
|
-
* ({@link ChartContainerProps.cursor}). Omit to inherit. See {@link CursorMode}.
|
|
45
|
-
*
|
|
46
|
-
* @deprecated Mount a cursor component **inside the row** instead
|
|
47
|
-
* (`<ChartRow><CrosshairCursor /> …</ChartRow>`) — the per-row override with
|
|
48
|
-
* the same nearest-mount-wins semantics. Works for one more minor; a mounted
|
|
49
|
-
* cursor in the row overrides this prop.
|
|
50
|
-
*/
|
|
51
|
-
cursor?: CursorMode;
|
|
52
41
|
children?: ReactNode;
|
|
53
42
|
}
|
|
54
43
|
/**
|
|
@@ -68,5 +57,5 @@ export interface ChartRowProps {
|
|
|
68
57
|
* Children lay out left-to-right in author order, so `<YAxis side="left"/>` goes
|
|
69
58
|
* before `<Layers/>` and `<YAxis side="right"/>` after.
|
|
70
59
|
*/
|
|
71
|
-
export declare function ChartRow({ height: heightProp, flex,
|
|
60
|
+
export declare function ChartRow({ height: heightProp, flex, children, }: ChartRowProps): import("react/jsx-runtime").JSX.Element;
|
|
72
61
|
//# sourceMappingURL=ChartRow.d.ts.map
|
package/dist/ChartRow.js
CHANGED
|
@@ -8,7 +8,6 @@ import { resolveAxisFormat } from './format.js';
|
|
|
8
8
|
import { resolveYTickCount } from './yticks.js';
|
|
9
9
|
import { placeAxisSlots } from './slots.js';
|
|
10
10
|
import { useSlotKey } from './use-slot-key.js';
|
|
11
|
-
import { LegacyCursor } from './cursors.js';
|
|
12
11
|
import { YAxis } from './YAxis.js';
|
|
13
12
|
import { ContainerContext, RowContext, } from './context.js';
|
|
14
13
|
/**
|
|
@@ -139,7 +138,7 @@ const TOP_LABEL_HEADER = 16;
|
|
|
139
138
|
* Children lay out left-to-right in author order, so `<YAxis side="left"/>` goes
|
|
140
139
|
* before `<Layers/>` and `<YAxis side="right"/>` after.
|
|
141
140
|
*/
|
|
142
|
-
export function ChartRow({ height: heightProp, flex,
|
|
141
|
+
export function ChartRow({ height: heightProp, flex, children, }) {
|
|
143
142
|
const container = useContext(ContainerContext);
|
|
144
143
|
if (container === null) {
|
|
145
144
|
throw new Error('<ChartRow> must be rendered inside a <ChartContainer>');
|
|
@@ -212,18 +211,6 @@ export function ChartRow({ height: heightProp, flex, cursor, children, }) {
|
|
|
212
211
|
const { registerRow } = container;
|
|
213
212
|
useEffect(() => registerRow(rowKey), [registerRow, rowKey]);
|
|
214
213
|
const isFirstRow = container.firstRowKey === rowKey;
|
|
215
|
-
// Deprecation notice for the legacy `cursor` prop (dev, once per row): the
|
|
216
|
-
// per-row override is now a cursor component mounted inside the row. The
|
|
217
|
-
// prop keeps working via the shim rendered below.
|
|
218
|
-
const warnedCursorRef = useRef(false);
|
|
219
|
-
useEffect(() => {
|
|
220
|
-
if (!isDev || cursor === undefined || warnedCursorRef.current)
|
|
221
|
-
return;
|
|
222
|
-
warnedCursorRef.current = true;
|
|
223
|
-
console.warn(`[pond-charts] <ChartRow cursor="${cursor}"> is deprecated (it keeps ` +
|
|
224
|
-
'working this minor, removed next) — mount the cursor component ' +
|
|
225
|
-
'inside the row instead (docs/rfcs/interaction.md §9).');
|
|
226
|
-
}, [cursor]);
|
|
227
214
|
// Keyed by a stable per-instance id (Map preserves insertion order; setting an
|
|
228
215
|
// existing key updates in place). So a re-register on a prop change keeps the
|
|
229
216
|
// entry's slot — the axis-default (first axis) and layer z-order stay stable
|
|
@@ -378,7 +365,7 @@ export function ChartRow({ height: heightProp, flex, cursor, children, }) {
|
|
|
378
365
|
const extents = needsExtents(ax)
|
|
379
366
|
? layerList
|
|
380
367
|
.filter((entry) => (entry.axisId ?? defaultAxisId) === ax.id)
|
|
381
|
-
.map((entry) => entry.layer.yExtent())
|
|
368
|
+
.map((entry) => entry.layer.yExtent(ax.scale))
|
|
382
369
|
: [];
|
|
383
370
|
const [lo, hi] = resolveYDomain(ax.min, ax.max, extents, ax.pad, ax.scale);
|
|
384
371
|
// Reserve a header band at the top when any axis draws a `'top'` title,
|
|
@@ -483,7 +470,7 @@ export function ChartRow({ height: heightProp, flex, cursor, children, }) {
|
|
|
483
470
|
// about.
|
|
484
471
|
const message = logAxisWarning(ax, layerList
|
|
485
472
|
.filter((entry) => (entry.axisId ?? defaultAxisId) === ax.id)
|
|
486
|
-
.map((entry) => entry.layer.yExtent()));
|
|
473
|
+
.map((entry) => entry.layer.yExtent(ax.scale)));
|
|
487
474
|
if (message === null)
|
|
488
475
|
warned.delete(ax.id);
|
|
489
476
|
else if (warned.get(ax.id) !== message) {
|
|
@@ -626,7 +613,6 @@ export function ChartRow({ height: heightProp, flex, cursor, children, }) {
|
|
|
626
613
|
const frame = useMemo(() => ({
|
|
627
614
|
height,
|
|
628
615
|
topInset: topHeader,
|
|
629
|
-
cursor,
|
|
630
616
|
isFirstRow,
|
|
631
617
|
rowKey,
|
|
632
618
|
yScales,
|
|
@@ -649,7 +635,6 @@ export function ChartRow({ height: heightProp, flex, cursor, children, }) {
|
|
|
649
635
|
}), [
|
|
650
636
|
height,
|
|
651
637
|
topHeader,
|
|
652
|
-
cursor,
|
|
653
638
|
isFirstRow,
|
|
654
639
|
rowKey,
|
|
655
640
|
yScales,
|
|
@@ -737,17 +722,17 @@ export function ChartRow({ height: heightProp, flex, cursor, children, }) {
|
|
|
737
722
|
"wrap the row's <Layers>, leaving each <YAxis> a direct child of the " +
|
|
738
723
|
'<ChartRow>.');
|
|
739
724
|
}, [axisInsideWrapper]);
|
|
740
|
-
return (
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
725
|
+
return (_jsx(RowContext.Provider, { value: frame, children: _jsxs("div", { ref: boxRef, style: {
|
|
726
|
+
display: 'flex',
|
|
727
|
+
flexDirection: 'row',
|
|
728
|
+
width: `${container.width}px`,
|
|
729
|
+
// Fixed rows keep their pixels; a flex row is sized by the
|
|
730
|
+
// container's column layout and reads the result back
|
|
731
|
+
// ([PND-HEIGHT]). `minHeight: 0` is what lets it actually shrink —
|
|
732
|
+
// a flex child's default min-height is its content.
|
|
733
|
+
...(isFlex
|
|
734
|
+
? { flex: `${flexGrow} 1 0%`, minHeight: 0 }
|
|
735
|
+
: { height: `${height}px` }),
|
|
736
|
+
}, children: [leftPad > 0 && _jsx("div", { style: { flex: `0 0 ${leftPad}px` } }), leftAxisEls, plotEls, rightAxisEls, rightPad > 0 && _jsx("div", { style: { flex: `0 0 ${rightPad}px` } })] }) }));
|
|
752
737
|
}
|
|
753
738
|
//# sourceMappingURL=ChartRow.js.map
|