PhaseTrace
PhaseTrace plots two coupled signals against each other as one trajectory, with an arrowhead at the current state.
- encodes
- x×y trajectory; path order = time
- precision
- medium
- nodes
- ≤ 5
CPU climbs and latency follows a beat later. PhaseTrace plots the two signals against each other as a single trajectory, so the lag becomes a loop and a regime becomes a cluster — structure two separate sparklines hide. Path order carries time, and the current state is a directed endpoint with an arrowhead, so you read where the pair is now and how it got there. The trail is muted and the recent tail accented, so the eye lands on the recent motion first.
Install
import { PhaseTrace } from "@microcharts/react/phase-trace";// trajectory — real values under “Sample data” below<PhaseTrace data={trajectory} xLabel="CPU" yLabel="Latency" title="Phase portrait" />Sample data
// a coupled CPU×latency trajectory with a lag loopconst trajectory = Array.from({ length: 40 }, (_, i) => { const t = (i / 40) * Math.PI * 2; return { x: 55 + Math.cos(t) * 22, y: 110 + Math.sin(t - 0.9) * 40 };});Try it
When to use it
Use it for coupled-signal phase portraits: CPU × latency, inflation × unemployment. For reading exact values use DualSparkline; for a single series use Sparkline.
Sizing
Variants
tail sets how much of the path is accented, and startDot marks the first observation. You can restyle the tail,
endpoint, and arrowhead, but not remove all three: together they are what makes the time direction recoverable.
With a locale, the accessible summary's coordinates follow that locale's own grouping: "1.800" in German rather than
"1,800". In-chart marks carry no text, so only the announced numbers change. The axes are named in the summary and both
domains are always linear, never silently transformed.
Edge cases
A single observation draws only the accent endpoint dot: there is no history to trail and no motion for an arrowhead to point along. When every point in the tail window is coincident (or nearly so, within 0.5% of the combined axis span), the heading resolves to the fifth, explicit "steady" state rather than a jittery direction from noise.
Four homes
Accessibility
The accessible name states the current point and heading, with the y-axis named first — for the hero example above, "Latency vs CPU: now 76.729, 75.163; heading down-right." The interactive entry steps through the trajectory in time order with ←/→, announcing each point's index and its value on both named axes.
The interactive entry follows the shared interaction contract:
arrow keys rove between units on both axes, Home and End jump to the ends, and a click, tap, Enter or
Space selects a unit — pinning its readout so it survives blur, until you select it again or press Escape.
On touch, a tap pins and a drag scrubs.
Props
| Prop | Type | Notes |
|---|---|---|
| data* | { x, y }[] | Two synchronized signals, time-ordered. |
| xLabel / yLabel | string | Axis names — the summary reads them. |
| xDomain | [number, number] | Fix the x-axis range (default: the data's x-extent). |
| domain | [number, number] | Fix the y-axis range (default: the data's y-extent). |
| tail | number | Fraction of points drawn in accent (recent motion). |
| grid | boolean | Quadrant hairlines for regime reads. |
| startDot | boolean | Anchor the path's origin for full-journey reads (default false). |
| animate | boolean | interactiveOpt-in entrance motion when the chart mounts client-side — add import "@microcharts/react/motion" once. Inert on the server, on hydrated server HTML, and under prefers-reduced-motion. |
Plus the shared grammar — data, domain, color, title, summary, format — and the layout props (width, height, className, style) that every chart accepts. Interactive entries also share animate and live, and — wherever a chart has more than one navigable unit — onActive, onSelect, selectedIndex and defaultSelectedIndex; and — wherever the chart shows a hover value — readout. See the shared grammar.