Marking up charts: annotations and indicators
- The three annotation primitives:
Region,Marker,Baseline - Why annotations render in a deliberately different register than data
YAxisIndicator— a live value pill pinned to an axis edge
Everything drawn so far has been data — a series, read straight off a
column. This chapter is about marks you place: an incident window, a
deploy instant, a target line.
import {
Baseline,
ChartContainer,
ChartRow,
Layers,
LineChart,
Marker,
Region,
YAxis,
YAxisIndicator,
} from '@pond-ts/charts';
import { useSiteChartTheme } from '@site/src/theme/useSiteChartTheme';
import { singleHostSeries } from './lib/server-metrics';
const STEP_MS = 60_000;
export default function LearnAnnotations() {
const theme = useSiteChartTheme();
const series = singleHostSeries();
// singleHostSeries() always returns a non-empty, fixed-length series, so
// timeRange() is never undefined here.
const base = series.timeRange()!.begin();
const latest = series.at(series.length - 1)?.get('cpu');
return (
<ChartContainer range={series.timeRange()} width={560} theme={theme}>
<ChartRow height={220}>
<YAxis id="pct" side="right" format=".0%" />
<Layers>
<LineChart series={series} column="cpu" axis="pct" />
<Region
from={base + 40 * STEP_MS}
to={base + 55 * STEP_MS}
label="busy"
/>
<Marker at={base + 40 * STEP_MS} label="deploy" />
<Baseline value={0.4} axis="pct" label="target" />
{latest !== undefined && <YAxisIndicator value={latest} axis="pct" />}
</Layers>
</ChartRow>
</ChartContainer>
);
}
<Layers>
<LineChart series={series} column="cpu" axis="pct" />
<Region from={busyStart} to={busyEnd} label="busy" />
<Marker at={deployInstant} label="deploy" />
<Baseline value={0.4} axis="pct" label="target" />
<YAxisIndicator value={latestCpu} axis="pct" />
</Layers>
<Region from={} to={}>— a shaded x-span. An incident window, a trading session, any range you want to call out.<Marker at={}>— a vertical line at one x. A deploy, an alert fire, any instant.<Baseline value={}>— a horizontal line at one y. A target, a threshold, an SLA.<YAxisIndicator value={}>— not an annotation on the plot; a pinned pill at the axis edge showing one live number. Chapter 8 pairs it with asourceprop for the high-frequency case — a live price tag that updates without re-rendering the whole chart.
All four live inside <Layers>, alongside the data layers — annotations
are just more things in the z-stack, drawn in the order you write them
(chapter 2).
Two registers, never mixed
Every annotation renders in the theme's dedicated annotation colour — a
hue deliberately different from every data role (primary,
secondary, and the rest). The rule: data and the marks you place never
share a hue family, so a placed mark is never mistaken for a series at a
glance, and a series is never mistaken for something you annotated. Look
back at the chart above — the cpu line and the three marks read as two
unmistakably different registers, not four colours competing for
attention.
Recap
Region/Marker/Baseline are the three ways to mark a chart up —
a span, an instant, a threshold — plus YAxisIndicator for a pinned live
value. All four are Layers children like any draw layer. Selection,
depth, and interactively editing or creating marks are deeper topics than
this chapter covers — see the
Charts/Annotations
Storybook group for that ground today; a dedicated reference section is
on the roadmap.
Next: Live charts — the running example, finally live.