Trading-time axis
A trading-time axis is still a time axis — but it renders in trading time: closed-market spans (overnight, weekends, holidays) collapse out, and pixels are proportional to time within each session. A gap-free intraday candle chart, no dead space between sessions.
import {
Candlestick,
ChartContainer,
ChartRow,
Layers,
YAxis,
} from '@pond-ts/charts';
import { useSiteChartTheme } from '@site/src/theme/useSiteChartTheme';
import { marketBars, sessionWindow } from './lib/financial-fixtures';
export default function FinancialCalendarChart() {
const theme = useSiteChartTheme();
const set = marketBars();
const { range, bars } = sessionWindow(set, 30);
return (
<ChartContainer
range={range}
width={560}
theme={theme}
calendar={set.calendar}
cursor="crosshair"
>
<ChartRow height={220}>
<YAxis id="price" side="right" format="$,.0f" width={50} />
<Layers>
<Candlestick series={bars} as={set.symbol} showOHLC />
</Layers>
</ChartRow>
</ChartContainer>
);
}
For the tutorial framing see Learn charts, chapter 9; the full financial walkthrough is the @pond-ts/financial section.
Getting one — the calendar prop
Hand <ChartContainer> a calendar and the axis becomes session-aware.
calendar is the high-level sugar: the container derives the gap model from it
itself. A @pond-ts/financial TradingCalendar is the usual source:
import { TradingCalendar } from '@pond-ts/financial';
const cal = TradingCalendar.fromRules(
{ timeZone: 'America/New_York', open: '09:30', close: '16:00' },
{ from: '2026-01-05', to: '2026-02-13' },
);
<ChartContainer calendar={cal} range={view} width={560}>
<ChartRow height={220}>
<YAxis id="price" side="right" format="$,.0f" />
<Layers>
<Candlestick series={bars} />
</Layers>
</ChartRow>
</ChartContainer>;
TradingCalendar.fromRules(rules, range) is the minimal path — rules is a
{ timeZone, open, close } (plus optional weekmask, breaks, holidays,
earlyCloses), and the second range is { from, to } (the calendar's own
span). The chart's view range is a separate [start, end] you choose;
cal.sessions() gives you the session bounds to derive it from.
For irregular sessions, TradingCalendar.fromSessions(...) takes an explicit
session list.
@pond-ts/charts never imports @pond-ts/financial. The calendar prop
is typed against a structural TradingCalendarLike shape (anything with a
.discontinuities() method), so the charts package stays free of the financial
dependency — you supply the calendar.
Build the calendar (or provider) once and pass a stable reference — the x
scale and frame rebuild whenever its identity changes. Construct it in a
useMemo/useRef, never inline in the render.
Tuning
All of these are <ChartContainer> props, and all are time-axis only —
ignored on a value or category axis.
| Prop | Type | Default | Purpose |
|---|---|---|---|
calendar | TradingCalendarLike | — | High-level: the container derives the gap model from it. |
spacing | 'proportional' | 'uniform' | 'proportional' | proportional = true time within a session; uniform = equal width per session (the TradingView ordinal look). Only with calendar. |
discontinuities | DiscontinuityProvider | — | Low-level: pass the gap model yourself (calendar.discontinuities(...)). Takes precedence over calendar. |
sessionDividers | 'none' | 'labeled' | 'all' | 'none' | Solid verticals at collapse seams (see below). 'all' = every seam (the TradingView separators), 'labeled' = only labelled seams. |
Most charts want calendar + the default proportional spacing. Reach for
discontinuities only when you're constructing the provider some other way, or
need uniform bar-width spacing over a custom period. The dateStyle (on
<XAxis>) and grid (on <ChartContainer>) props also shape this axis — see
What changes on the axis below and
Axes / Layout.
What changes on the axis
- Gaps collapse. Closed-market spans have zero pixel width; the axis walks a session-aware tick ladder (hour → day → month → quarter → year, with the day grain thinned by a per-month uniform session stride — each month's first session, then every k-th session, truncated so the gap to the next month start stays ≥ the stride. Marks sit an equal number of sessions apart, so they're evenly spaced in pixels on the collapsed axis, with the slack at the month end — the TradingView behaviour, validated against its output) rather than raw wall-clock ticks.
- Inline date context (the
dateStyleprop). By default (dateStyle="flat") the axis is a single row the TradingView way: each tick that opens a coarser calendar period is relabelled inline to it — the month at a month turn, the year at a year turn, the date at a day turn under an intraday grain — while every other tick stays terse (5,Feb,14:00); a promoted tick renders bold. PassdateStyle="stacked"for the two-row layout: the same terse top row over a segmented band row of the next-coarser period — day bands under intraday ticks, month bands under day ticks, year bands under month/quarter ticks. Each band left-aligns its label with a divider at its turn, zebra-shaded by the band's calendar parity; the top-row turn tick joins its divider as one boundary line. Themeable viatheme.axis.band. - Calendar grid. The vertical gridlines are the full grain populations
— every day, month, and aligned clock instant in view, not just the labelled
ticks (the labels decorate the grid; they don't define it). Each grain fades
as a unit by its calendar density, so zooming out dissolves the fine
grain (days) into the coarser one (months) with no pop when the label grain
switches — a map-style hierarchical grid. Because the metric is calendar
density rather than on-screen spacing, hiding weekends draws fewer day
lines at the same weight, not brighter ones.
grid={false}drops the grid for a clean backdrop. (This applies to any time axis, but is most visible here, where collapsed sessions pack the day grain tightly.) - Session dividers (the
sessionDividersprop). Optional solid verticals at collapse seams — the boundaries where closed time was actually removed (on a real exchange calendar, every session open; on a contiguous-session calendar, only where days/hours were excised). Default'none'— the calendar grid already marks the structure, 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 the seams the axis also labels. Independent ofgrid—sessionDividers="all"withgrid={false}is separators on a clean plot. - Line breaks at sessions. A
LineChartcan setsessionBreaksto break the line at each session discontinuity instead of connecting across the collapsed gap. This is a scale break (driven by the axis's collapsed gaps), orthogonal togaps(a data break) — set both independently.
See also
- @pond-ts/financial — constructing a
TradingCalendar, the calendar-aware side of this axis. - Axes overview — the shared axis mechanics.
- Learn charts, chapter 9 — the tutorial.
- Storybook:
Axes/TradingTimeAxis(WeekendSkip,HolidayGap,HalfDay,SessionBreaks,SpacingProportionalVsUniform,DateStylePanZoom— the interactive grid / divider / pan-zoom showcase, …) andAxes/TradingTimeAxis/Interactions.