Beyond the time axis
- The x axis is inferred from your data, never declared
byValue/fromColumns→ a linear value axistransposeRow→ a category axis- A
TradingCalendar→ a session-aware time axis
Every chart in this track has drawn against a time axis, because every
example handed <ChartContainer> a TimeSeries. That's not a setting you
picked — it's what the container inferred from the data. Hand it a
different kind of series and the axis changes with it.
A linear value axis
TimeSeries.byValue(column) projects onto a ValueSeries — keyed by
one of its own numeric columns instead of time. The column must be
defined, finite, and non-decreasing at every row (think distance in a
pace curve, or strike in a vol smile — anything that only goes one
direction). Every draw layer reads it exactly like a TimeSeries:
import {
ChartContainer,
ChartRow,
Layers,
LineChart,
YAxis,
} from '@pond-ts/charts';
import { useSiteChartTheme } from '@site/src/theme/useSiteChartTheme';
import { singleHostSeries } from './lib/server-metrics';
export default function LearnValueAxis() {
const theme = useSiteChartTheme();
const series = singleHostSeries();
// A synthetic "minutes elapsed" column — defined, finite, and strictly
// increasing, so it's a valid byValue() axis. In a real pace-curve or
// vol-smile series this would already be the natural key (distance,
// strike) rather than something derived from time.
const elapsed = new Float64Array(series.length);
for (let i = 0; i < elapsed.length; i++) elapsed[i] = i;
const byElapsed = series.withColumn('elapsed', elapsed).byValue('elapsed');
return (
<ChartContainer range={[0, elapsed.length - 1]} width={560} theme={theme}>
<ChartRow height={200}>
<YAxis id="pct" side="right" format=".0%" />
<Layers>
<LineChart series={byElapsed} column="cpu" axis="pct" />
</Layers>
</ChartRow>
</ChartContainer>
);
}
const byElapsed = series
.withColumn('elapsed', elapsedMinutes)
.byValue('elapsed');
<ChartContainer range={[0, 89]} width={560}>
<ChartRow height={200}>
<Layers>
<LineChart series={byElapsed} column="cpu" axis="pct" />
</Layers>
<YAxis id="pct" side="right" format=".0%" />
</ChartRow>
</ChartContainer>
If your data is natively value-keyed and never had a time key at all —
struct-of-arrays samples along a value axis — ValueSeries.fromColumns
builds one directly, the value-axis counterpart of chapter 3's
TimeSeries.fromColumns.
A category axis
transposeRow reads one row of a wide series (a column per category)
and turns it into CategoryDatum[] — a bar per category on an ordinal
axis, new in pond-ts 0.43:
import {
BarChart,
ChartContainer,
ChartRow,
Layers,
YAxis,
transposeRow,
} from '@pond-ts/charts';
import { TimeSeries } from 'pond-ts';
import { useSiteChartTheme } from '@site/src/theme/useSiteChartTheme';
// A wide row — one column per host, the shape transposeRow reads. A real
// source would be the last row of a partitioned rollup; this is a fixed
// snapshot for the demo.
const wideSchema = [
{ name: 'time', kind: 'time' },
{ name: 'api-1', kind: 'number' },
{ name: 'api-2', kind: 'number' },
{ name: 'worker-1', kind: 'number' },
] as const;
function latestCpuByHost() {
return new TimeSeries({
name: 'latest-cpu',
schema: wideSchema,
rows: [[Date.UTC(2026, 0, 12, 10, 30), 0.34, 0.48, 0.61]],
});
}
export default function LearnCategoryAxis() {
const theme = useSiteChartTheme();
const data = transposeRow(latestCpuByHost(), { at: 'last' });
return (
<ChartContainer width={560} theme={theme}>
<ChartRow height={200}>
<YAxis id="pct" side="right" format=".0%" min={0} />
<Layers>
<BarChart categories={data} gap={8} />
</Layers>
</ChartRow>
</ChartContainer>
);
}
// one row, one column per host
const data = transposeRow(latestCpuByHost(), { at: 'last' });
// [{ label: 'api-1', value: 0.34 }, { label: 'api-2', value: 0.48 }, …]
<ChartContainer width={560}>
<ChartRow height={200}>
<Layers>
<BarChart categories={data} gap={8} />
</Layers>
<YAxis id="pct" side="right" format=".0%" min={0} />
</ChartRow>
</ChartContainer>
BarChart also accepts a hand-built CategoryDatum[] directly — reach
for transposeRow specifically when you already have a wide rollup row
and want its columns read off automatically instead of restating them.
A session-aware time axis
This one you've already met — chapter 4's TradingCalendar is a time
axis too, just one with closed-market time collapsed out of it:
import {
Candlestick,
ChartContainer,
ChartRow,
Layers,
YAxis,
} from '@pond-ts/charts';
import { useSiteChartTheme } from '@site/src/theme/useSiteChartTheme';
import {
demoCalendar,
demoDailyBars,
demoRange,
} from './lib/financial-fixtures';
export default function FinancialCalendarChart() {
const theme = useSiteChartTheme();
const cal = demoCalendar();
const series = demoDailyBars(cal);
return (
<ChartContainer
range={demoRange(cal)}
width={560}
theme={theme}
calendar={cal}
cursor="crosshair"
>
<ChartRow height={220}>
<YAxis id="price" side="right" format="$,.0f" width={50} />
<Layers>
<Candlestick series={series} as="demo" showOHLC />
</Layers>
</ChartRow>
</ChartContainer>
);
}
Full walkthrough: @pond-ts/financial.
Recap
The x axis follows the data, not a prop you set: a TimeSeries draws a
time axis, byValue/fromColumns draws a linear value axis,
transposeRow draws a category axis, and a TimeSeries paired with a
TradingCalendar draws a session-aware time axis. Every draw layer works
identically across all four — nothing about LineChart or BarChart
changes; only what you hand <ChartContainer> does.
That's the whole core vocabulary — nine chapters, one running example,
every primitive in @pond-ts/charts. From here:
- Chart types
and the Gallery —
ScatterChart,BoxPlot, andCandlesticknever starred in this track; that's where you'll meet them. - Value axis — the chart-level reference:
axis-kind inference, natively value-keyed data via
fromColumns,byColumnhistograms, and dual x-axes, none of which this chapter had room for. - Storybook — the systematic, per-prop reference for everything this track only had room to tour.