Live Line Chart
A real-time line chart — streamed readings over a sliding time window, a live dot, a crosshair and a pause, with throttled screen-reader updates and a stepped reduced-motion mode.
pnpm add @tessinaui/uiWhen to use
- One value read as it arrives. A price, a latency, requests per second, a model's tokens per second — where the reader watches the value move.
- A recent window, not a history. The chart shows the last seconds to the last hour and lets the rest scroll away.
- When someone needs to stop and read.
pausedfreezes the window while readings keep arriving, and the chart catches up on resume.
When not to use
- For a fixed history. Last month's revenue, last week's sessions: use LineChart. A live chart redraws every frame for data that does not move.
- For several live series. This chart draws one value. Compare two live values in two charts side by side, or snapshot them into a LineChart.
- When only the latest number matters. A readout, a Meter or a Badge says it with nothing moving.
- For a value that changes every few minutes. Poll and append to a LineChart; a sliding window of an hour with four readings is mostly empty.
Installation
pnpm add @tessinaui/ui rechartsUsage
import * as React from "react";
import { LiveLineChart, type LiveLineChartPoint } from "@tessinaui/ui";
export function Throughput({ feed }: { feed: EventSource }) {
const [data, setData] = React.useState<LiveLineChartPoint[]>([]);
React.useEffect(() => {
const onReading = (event: MessageEvent) => {
const value = Number(event.data);
// time in epoch milliseconds; keep your own array bounded too.
setData((points) => [...points, { time: Date.now(), value }].slice(-1000));
};
feed.addEventListener("message", onReading);
return () => feed.removeEventListener("message", onReading);
}, [feed]);
return (
<LiveLineChart
data={data}
window={30}
valueFormatter={(value) => `${value} tok/s`}
label="Generation speed, tokens per second"
/>
);
}time is epoch milliseconds — what Date.now() returns. Append readings in
arrival order; the chart draws only what is inside the window, at most
maxPoints of them.
Examples
Default
Press Start to run the feed. Pause freezes the window while readings keep arriving; Resume jumps to the present.
Momentum
momentum colours the line by its direction across the window. Say the
direction in words or with a sign beside the chart, as here: the colour must
never be the only cue.
Threshold
children are drawn inside the chart. A ReferenceLine marks a target the
reading should stay under.
Dense feed
Fifty readings a second over a 20-second window: a thousand on screen, the
maxPoints default. The chart keeps the newest maxPoints and drops what has
scrolled away, so a fast feed costs no more to draw than the window holds.
Sizes
size sets the aspect ratio: sm is 3:1, md 2:1 and lg 16:9.
On a coloured plate
Inside a coloured Surface, give the line the plate's ink. The axes, grid,
crosshair, live dot and focus ring follow the plate on their own.
States
loading while the feed connects, emptyState before the first reading and
error when the feed is lost.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
data | { time: number; value: number }[] | — | Readings, oldest first, time in epoch milliseconds. |
window | number | 30 | How much time the chart shows, in seconds. |
paused | boolean | false | Freeze the window to read it. Readings keep arriving; the chart jumps to the present on resume. |
maxPoints | number | 1000 | The most readings the chart keeps. |
liveDot | boolean | true | Mark the newest reading. A halo pings from it while the chart runs. |
momentum | boolean | false | Colour the line by its direction across the window. |
config | ChartConfig | var(--chart-1), "Value" | The series' name and colour, keyed value. |
size | "sm" | "md" | "lg" | "md" | Aspect ratio. |
showGrid | boolean | true | Horizontal grid lines. |
showXAxis | boolean | true | The time axis. |
showYAxis | boolean | true | The value axis. |
showTooltip | boolean | true | The crosshair and its tooltip. |
yAxisPosition | "left" | "right" | the reading end | Defaults to the side the newest reading arrives on. |
autoMinValue | boolean | true | Fit the value axis to the readings in view instead of anchoring it at zero. |
minValue / maxValue | number | — | Pin an end of the value axis. |
valueFormatter | (value: number) => string | toLocaleString() | Formats a value on the axis, in the tooltip and when spoken. |
timeFormatter | (time: number) => string | HH:MM:SS | Formats a time on the axis and in the tooltip. |
strings | Partial<LiveLineChartStrings> | English, Arabic under rtl | Spoken and empty-state text. |
label | string | — | Accessible name; renders the chart as a role="figure". |
description | string | — | Visually hidden summary for a static read. |
dir | "ltr" | "rtl" | from the page | rtl runs time right to left and moves the value axis to the left. |
loading | boolean | false | Show the loading skeleton. |
error | ReactNode | — | Show an error message instead of the chart. |
emptyState | ReactNode | "No data to display" | Shown when data is empty. |
children | ReactNode | — | Extra Recharts children, plotted on time. |
Strings
Pass any of these in strings to replace one sentence. {value} is the
reading, formatted by valueFormatter.
| Key | Default | When |
|---|---|---|
latest | "Latest value {value}" | Spoken at most every 10 seconds while the chart runs, only when the value changed. |
paused | "Paused at {value}" | Spoken once when paused turns on. |
live | "Live" | Spoken once when paused turns off. |
value | "Value" | The series name in the tooltip when config gives none. |
empty | "No data to display" | Shown when data is empty and there is no emptyState. |
<LiveLineChart data={data} strings={{ latest: "Now {value}", live: "Streaming" }} />Motion
The window scrolls every frame while the chart runs; a new reading is never animated in, because a draw-in restarts on each one. Ticks sit on whole seconds, so a label keeps its value while it slides.
Under prefers-reduced-motion: reduce nothing scrolls: the window steps once a
second and the moment a reading arrives, and the live dot's halo does not
ping. A browser tab in the background stops drawing; the chart jumps to the
present when the tab returns.
Accessibility
VoiceOver: not yet tested.
- Name the insight.
labelbecomes the chart's accessible name. Say what the value is doing ("p95 latency crossed its 200 ms target twice"), not that it is a live chart. - Updates are throttled. A polite live region beside the chart says the latest value at most every ten seconds, and only when it changed. A pause and a resume are said at once ("Paused at 182 req/s", "Live"). The first render is silent.
- The tooltip waits for a pause. It is a polite live region too, so while the chart runs it is silenced: a reading under a held pointer changes constantly. Pause to read point by point.
- Keyboard. Tab reaches the plot; the arrow keys step the crosshair from reading to reading and Escape hides the tooltip. Put a pause control next to the chart, as the examples do, so a keyboard user can stop it first.
- Colour is never the only cue.
momentumrepeats the line's direction; say it in text beside the chart as well.
Theming
| Token | Used for |
|---|---|
--chart-1 | The default line colour |
--success-border / --error-border | momentum up and down on the page |
--muted-foreground | Ticks, grid, crosshair, and a flat momentum |
--on-ink-success / --on-ink-error | momentum on a coloured plate |