Skip to main content

Beyond the time axis

You will learn
  • The x axis is inferred from your data, never declared
  • byValue/fromColumns → a linear value axis
  • transposeRow → 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:

src/examples/learn-09-value-axis.tsx
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:

src/examples/learn-09-category-axis.tsx
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:

src/examples/financial-calendar-chart.tsx
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 GalleryScatterChart, BoxPlot, and Candlestick never 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, byColumn histograms, 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.