Skip to main content

<ChartContainer>

component@pond-ts/chartssource

The top of the chart layout (react-timeseries-charts-style). Owns the shared x geometry: it collects each row's per-slot gutter widths, reserves each slot's max across rows (so the innermost axis aligns column-by-column and every row's plot left-aligns), and from the slot sums derives plotWidth and the shared time xScale. It renders its rows (separated by rowGap) then one TimeAxis at the bottom, aligned under the plots. Y axes are per-row (<YAxis>).

Props

widthnumberrequired

Total width in CSS pixels (plot + axis gutters).

calendarTradingCalendarLike

The high-level sugar for discontinuities: a trading calendar the container derives the provider from itself (calendar.discontinuities({ spacing })), so you don't wire the low-level prop. A @pond-ts/financial TradingCalendar satisfies the structural TradingCalendarLike shape (charts never imports that package). Combine with spacing. For the full option matrix (a bar period, a scoped range) use the low-level discontinuities prop instead. Only affects a time axis.

The provider is memoized on (calendar, spacing), so pass a stable calendar reference (build it once, not inline in JSX).

childrenReactNode
creatingAnnotationKindAnnotationKindtype@pond-ts/charts'region' | 'marker' | 'baseline'

The kind of an annotation, and of a creation tool.

| null

The armed annotation creation tool (the consumer's toolbar sets it), or null/omitted for idle. When set, the plot captures a create gesture — a preview tracks the pointer, and on release onCreate fires. The consumer then adds the mark, disarms (back to null), and selects it (spring-loaded); keep it set to place several. Requires editAnnotations.

cursor="crosshair" reticle y snapping. Default true — the crosshair centres on the nearest data point (the horizontal line snaps to that sample's value). false — the horizontal line + centre follow the pointer y freely, the value read as yScale.invert(pointerY). Either way the vertical line snaps its x to the data grid (so the time readout is clean), and both draw a full-height dashed vertical + full-width dashed horizontal line.

cursorCursorModeCursorModetype@pond-ts/charts'none' | 'line' | 'point' | 'inline' | 'flag' | 'crosshair' | 'region'

The in-chart cursor presentation for a row (the synced vertical line is shared across rows). Exclusive modes — pick one:

In-chart cursor presentation — the default for all rows (a row may override via <ChartRow cursor>). Default 'line' — the synced vertical line, with values surfaced outside the chart via onTrackerChanged. 'point' / 'inline' / 'flag' add per-series marks; 'none' hides it. 'region' shades the bucket under the pointer (needs cursorSequence). See CursorMode.

cursorFormatCursorFormatCursorFormattype@pond-ts/chartsstring | (epochMs: number, ctx: { defaultText: string; grain: TimeGrain }) => string

How to format the cursor / marker readout on a time axis (ChartContainerProps.cursorFormat). Either:

The cursor / marker readout format — the crosshair time pill, marker axis indicators, and annotation auto-labels — independent of the tick labels, so it does not disqualify the dateStyle ladder. Omitted ⇒ a grain-aware default: the readout formats at the axis's own granularity, so a day-or-coarser axis reads a date (never a time-of-day) and a sub-day axis reads date + clock — a daily bar at a foreign-tz midnight no longer renders as 02 AM.

A d3 time-specifier string formats uniformly at every zoom; a function (epochMs, { grain, defaultText }) => string receives the axis's resolved coarse TimeGrain and the grain-aware default text, so it can branch on the zoom level and pass defaultText through for grains it doesn't override (no re-deriving the grain from the range). See CursorFormat. This is the independent readout channel; timeFormat owns the labels.

cursorSequenceSequenceSequenceclasspond-ts

An unbounded fixed-step grid definition used for alignment or aggregation.

| BoundedSequenceBoundedSequenceclasspond-ts

A finite ordered list of Interval buckets.

The bucketing for cursor="region" — the interval highlighted under the pointer. A pond Sequence (duration or calendar-aware — Sequence.every('1d'), Sequence.calendar('month')) is realized over the current view; a BoundedSequence (e.g. a TradingCalendar's sessionSequence() / barSequence()) is used as-is, so the band can track whole sessions. Either way the band maps through xScale, so on a trading-time axis the closed part of the bucket collapses. Ignored unless cursor="region".

Time axis only. A bucket is a time interval, so the region cursor is gated to a time x-axis — on a value axis (a horizontal histogram, a value-keyed chart) it's a no-op (highlighting a value band on a horizontal histogram would be a different, y-oriented cursor).

Pass a stable reference. The buckets are memoized on this value + the view range; a Sequence/BoundedSequence rebuilt inline every render re-realizes the buckets on each pointer move (harmless for a coarse day/session sequence, wasteful for a fine one over a wide view) — hoist it or useMemo it.

cursorTimeboolean

Show the cursor's time atop the in-chart readout (when a row's cursor draws one). Default false. Formatted by timeFormat to match the time axis.

discontinuitiesDiscontinuityProviderDiscontinuityProviderinterface@pond-ts/charts{ boundaries?: unknown; clampDown: unknown; clampUp: unknown; copy: unknown; distance: unknown; offset: unknown }

The structural discontinuity-provider surface scaleTradingTime consumes to collapse closed-market time. Charts declares this shape itself and never imports @pond-ts/financial — a TradingCalendar.discontinuities() provider satisfies it structurally, so the packages stay decoupled (trading-calendar RFC §6.1). Domain values are epoch-milliseconds.

A trading-calendar discontinuity provider — closed-market time (weekends, holidays, overnight, lunch breaks) collapsed. Supply it to turn the shared x axis into a trading-time axis: gaps disappear and time stays proportional within each session. A @pond-ts/financial TradingCalendar.discontinuities() satisfies this structurally (charts never imports that package). The low-level primitive: pass calendar.discontinuities() (or a { spacing, period } variant) directly. Only affects a time axis (ignored on a value axis). Takes precedence over calendar if both are given.

Pass a stable reference. The scale (and container frame) rebuild when this prop's identity changes, so memoize it — const disc = useMemo(() => calendar.discontinuities(), [calendar]) — rather than calling .discontinuities() inline in JSX, which would rebuild every render.

Accepts an explicit undefined (a cond ? provider : undefined toggle under exactOptionalPropertyTypes), same as omitting it.

Enter annotation-edit mode: suppresses the data cursor and makes editable annotations (those given an onChange) interactive — hovering one reveals its handles + highlights it, and dragging edits it. Default false. Pairs with each annotation's onChange (where the edit goes); this is the mode that turns the affordances on and gets the cursor out of the way.

gridboolean

Draw the reference gridlines behind the data. On a calendar (time) axis the verticals are the full grain populations — every day / month / aligned clock instant in view, each grain fading by its calendar density — not just the labelled ticks (the labels decorate the grid; they don't define it). Default true. Set false for a clean backdrop — session dividers (below) are independent and still draw when enabled.

hoveredSelectInfoSelectInfointerface@pond-ts/charts{ color: string; id: string; key: number; label: string; mark?: string; value: number }

One selection — what RowLayer.hitTest returns and onSelect reports. Selection identity is the series id, not the sample: key/value are click provenance (the nearest sample under the pointer, informational); equality, dedup, and the controlled echo all key on id. Because id is a stable series identity — distinct from the as theme role,…

| null

Controlled hover-highlight — the transiently lit mark (echo the onHover arg back), or null. Omitted ⇒ uncontrolled (the pointer over a selectable layer manages it internally). The hover analog of selected: pass it to pin a lit mark from outside the chart (e.g. hovering a legend / list row lights the matching BarChart bar). Only layers with a hover-highlight (currently BarChart) render it; keyed by the same SelectInfo identity as selection.

Zoom-in floor — the minimum visible duration in ms. Default 1.

onCreate(spec: CreateSpecCreateSpectype@pond-ts/charts{ at: number; kind: 'marker' } | { axis: string; kind: 'baseline'; value: number } | { from: number; kind: 'region'; to: number }

What a completed create gesture reports to ContainerFrame.onCreate — the new mark's kind + position in axis units (+ the y-axis id for a baseline). (Which row a mark lands on is the consumer's call for now; multi-row routing is a follow-up.)

) => void

Fired when a create gesture completes (on release). See CreateSpec.

onEditAnnotation(id: string) => void

Fired when a mark is double-clicked — the request to edit just that one (set its editing prop in response). Single click selects (inspect); double click edits. Works in any mode.

onHover(hit: SelectInfoSelectInfointerface@pond-ts/charts{ color: string; id: string; key: number; label: string; mark?: string; value: number }

One selection — what RowLayer.hitTest returns and onSelect reports. Selection identity is the series id, not the sample: key/value are click provenance (the nearest sample under the pointer, informational); equality, dedup, and the controlled echo all key on id. Because id is a stable series identity — distinct from the as theme role,…

| null) => void

Fires when the pointer enters a selectable layer's mark (the hit mark) or leaves every mark (null) — the hover analog of onSelect. Notification only (works controlled or uncontrolled), and deduped: it fires on a mark transition, not on every pointer move. Wire it to mirror hover out-of-band (e.g. a list row ↔ the bar), pairing with hovered to sync both ways. (The annotation counterpart is onHoverAnnotation.)

Dedup key: by the mark's key + label only (not value/color). So on a live chart where a bar's value changes while the cursor stays on it, this won't re-fire — read the current value from your series, not the last onHover payload. (Matches the internal hover-highlight, which repaints on key transitions.)

onHoverAnnotation(id: string | null) => void

Fired when the pointer enters an annotation (its id) or leaves it (null). Mirror it to a controlled hovered prop on each mark to sync hover both ways (e.g. a legend row ↔ the mark). Fires in any mode.

onRegionSelect(range: readonly [number, number]) => void

Makes the region cursor draggable: drag across the plot and the band extends bucket by bucket (snapping to cursorSequence points); on release this fires once with the selected [lo, hi] span, and the cursor reverts to the single-bucket highlight (it does not keep the range). Typical use — zoom the view to the returned span (the container doesn't zoom itself; that's the consumer's call), or map it onto a data subscription's range params.

The span is a neutral numeric pair in axis units — epoch ms on a time axis, the axis value (strike, distance, …) on a value axis — mirroring the polymorphic range input. A time consumer that wants a TimeRange builds one from the pair.

With no cursorSequence the region cursor is the degenerate case — it renders as a line on hover and the drag is freeform (raw [lo, hi], no bucket snapping); the same callback fires on release. Bucket snapping needs a cursorSequence, which is time-axis only (a time interval over a value domain is meaningless), so a value axis is always freeform. No-op unless cursor="region" on a time or value x-axis (a category axis is excluded — an ordinal-slot select is a different gesture).

onSelect(hit: SelectInfoSelectInfointerface@pond-ts/charts{ color: string; id: string; key: number; label: string; mark?: string; value: number }

One selection — what RowLayer.hitTest returns and onSelect reports. Selection identity is the series id, not the sample: key/value are click provenance (the nearest sample under the pointer, informational); equality, dedup, and the controlled echo all key on id. Because id is a stable series identity — distinct from the as theme role,…

| null) => void

Fires when a selectable layer's mark is clicked, with the hit mark, or null when a click misses every mark (or hits a layer with no id — display-only, so it reads as empty space). Notification only — works in both controlled and uncontrolled mode. If this or selected is set but no layer has an id, a dev-warning notes that nothing is selectable.

onSelectAnnotation(id: string | null) => void

Fired when an annotation is clicked (its id), the plot is clicked empty (null), or a region is double-clicked (the shortcut into edit). The consumer holds the selected id and sets each mark's selected={id === sel}.

onTimeRangeChange(range: [number, number]) => void

Controlled view range — fires on pan/zoom with the new [start, end]. Wire it back to range for a controlled chart; omit for uncontrolled (the container holds the view internally). Uncontrolled + panZoom seeds the internal view from range whenever it isn't actively holding one — so toggling panZoom on, or a controlled→uncontrolled switch, starts from the current range, not the mount-time one. Once uncontrolled, later range changes are ignored so they can't fight the user's pan. To drive the range externally — or to follow a live sliding window — use controlled mode (this callback).

onTrackerChanged(info: TrackerInfoTrackerInfointerface@pond-ts/charts{ time: number; values: readonly TrackerSample[] }

The hover snapshot handed to onTrackerChanged — the cursor time + every series' value there, so a consumer can render the readout outside the chart.

| null) => void

Fires on pointer move with the hovered time + every series' value there (so you can render a readout outside the chart), and null on leave.

panZoomboolean

Enable pan/zoom: drag the plot to pan the time range, wheel to zoom around the cursor. Default off — so it doesn't capture drag/scroll unless asked.

rangeTimeRangeTimeRangeclasspond-ts

A time interval event key with inclusive start and end boundaries. Example: new TimeRange({ start, end }).

| readonly [number, number]

The shared x domain [begin, end] — a tuple, or a TimeRange (series.timeRange()). Units follow the data: epoch-ms for a time axis, the value units (distance, …) for a value axis. Omit to auto-fit to the rows' extents. The axis kind is never taken from here — it's inferred from the data — so a tuple stays a time domain on a time chart.

Which modifier a region-drag needs — set 'shift' when you also enable panZoom and want plain drag to pan, shift-drag to select. It's only enforced while panZoom is on (with pan off there's no gesture conflict, so shift is optional — either drag selects). Omitted ⇒ a region-drag preempts pan (drag always selects; document that precedence for users). Wheel-zoom is unaffected in every case.

rowGapnumber

Vertical space between rows in CSS pixels (not under the axis). Default 0.

selectedSelectInfoSelectInfointerface@pond-ts/charts{ color: string; id: string; key: number; label: string; mark?: string; value: number }

One selection — what RowLayer.hitTest returns and onSelect reports. Selection identity is the series id, not the sample: key/value are click provenance (the nearest sample under the pointer, informational); equality, dedup, and the controlled echo all key on id. Because id is a stable series identity — distinct from the as theme role,…

| null

Controlled selection — the selected mark (echo the onSelect arg back), or null. Omitted ⇒ uncontrolled (a click on a selectable layer manages it internally; pass null to force nothing selected). A layer is selectable only when it carries an id (the stable series identity) — BarChart / ScatterChart highlight the mark matching the selection's id (the series) and its key (the sample), so two series sharing a timestamp don't both light up, and the selection survives a data update (it keys on the stable id, not the sample key). A layer with no id renders + reads out but can't be selected.

sessionDividers'none' | 'labeled' | 'all'

Where to draw session dividers — the solid verticals at a trading calendar's collapse seams: boundaries that removed (closed-market) time actually precedes, not every session roll (only with a discontinuities / calendar provider). On a real exchange calendar every session open follows an overnight gap, so seams = session opens; a calendar of contiguous full-day sessions has seams only where days were excised (the weekend). Default 'none' — the hierarchical grid already marks the calendar structure at every zoom, so dividers are opt-in emphasis: 'all' draws one at every seam in view (the TradingView session-separator look, crowding lines fading out), 'labeled' only at seams the axis also labels. Dividers are independent of grid'all' + grid={false} is the separators-on-a-clean-plot look.

showAxisboolean

Auto-render the shared x axis under the rows. Default true. Set false for a bare plot (a sparkline), or when you place your own <XAxis> child (e.g. with a label, custom ticks, or on side="top"). Named showAxis (not axis) to avoid clashing with a layer's axis prop, which picks which <YAxis> it scales against — a different axis entirely.

snapboolean

Snap mode (the toolbar's "Snap"). Default true. When on, a dragged mark snaps to other marks' guidelines (their x-positions, within a few px) so spans align; off = free placement. (Snapping to the nearest data sample is not implemented — guideline alignment only.)

spacing'proportional' | 'uniform'

The trading axis metric, when a calendar is supplied (trading-calendar RFC Q7). 'proportional' (default) keeps time proportional within and across sessions — a half-day is half as wide. 'uniform' gives every session equal width (the TradingView ordinal look). Ignored without calendar (a low-level discontinuities provider already carries its own metric).

themeChartThemeChartThemeinterface@pond-ts/charts{ annotation?: { color: string; depth: readonly [number, number, number]; fillOpacity: number }; area: { default: AreaStyle }; axis: { band?: { divider?: string; fill: string; label?: string }; grid: string; gridDash: readonly number[]; lab…

Visual styling for a chart, threaded through ChartContainer via context. Canvas has no CSS cascade into drawn pixels, so this typed object is the single styling channel for the drawn layers; DOM chrome (axis labels) derives from it too.

Visual theme for all rows; defaults to defaultTheme.

timeFormatAxisFormatAxisFormattype@pond-ts/chartsstring | (value: number) => string

How to format an axis's values — a d3 [format specifier] (https://github.com/d3/d3-format#locale_format) string, or a custom (value) => string function. Omit for the scale's d3 default.

Time-axis label formatting — a d3 time specifier string (e.g. '%H:%M') or a (epochMs) => string function (AxisFormat). A custom format owns the labels, so it opts the axis out of the dateStyle ladder (flat / stacked) by design. Omitted ⇒ the flat/stacked date style. To shape only the cursor readout while keeping a date style, use cursorFormat instead. (For back-compat this also shapes the readout when cursorFormat is absent.)

trackerPositionnumber | null

Controlled tracker position (epoch ms) — pins the synced crosshair across rows. Omit for uncontrolled (the chart tracks the pointer itself); pass null to force it hidden. See onTrackerChanged.