# PhaseTrace (/docs/charts/phase-trace)

PhaseTrace answers "how do two coupled signals move _together_?". Plotting one against the other as a single trajectory
turns lag into loops and regimes into clusters — structure that two separate sparklines hide. Path order carries time,
and the current state is a directed endpoint with an arrowhead, so "now, and how it got here" reads at a glance.

```tsx
import { PhaseTrace } from "@microcharts/react/phase-trace";

<PhaseTrace
  data={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 };
  })}
  xLabel="CPU"
  yLabel="Latency"
  title="Phase portrait"
/>
```

## Install

```tsx
import { PhaseTrace } from "@microcharts/react/phase-trace";

<PhaseTrace data={trajectory} xLabel="CPU" yLabel="Latency" title="Phase portrait" />
```

Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.


## When to use it

- **Good for** — coupled-signal phase portraits (CPU × latency, inflation × unemployment).
- **Avoid for** — reading exact values (DualSparkline) or a single series (Sparkline).


## Variants

```tsx
<PhaseTrace
  data={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 };
  })}
  tail={0.4}
  startDot
/>
```

```tsx
<PhaseTrace
  data={[
    { x: 1200, y: 4500 },
    { x: 1450, y: 4100 },
    { x: 1800, y: 3600 },
  ]}
  xLabel="Requests"
  yLabel="Errors"
  locale="de-DE"
/>
```

With a `locale`, the accessible summary's coordinates follow that locale's own grouping — "1.800" in German, not
"1,800". In-chart marks carry no text, so only the announced numbers change.

## Edge cases

```tsx
// one point: no trail, no tail, no arrowhead — just the current position
<PhaseTrace data={[{ x: 55, y: 62 }]} xLabel="CPU" yLabel="Latency" />
```

```tsx
// every point identical — heading resolves to "steady", not a division by zero
<PhaseTrace
  data={Array.from({ length: 8 }, () => ({ x: 40, y: 40 }))}
  xLabel="CPU"
  yLabel="Latency"
/>
```

A single observation draws only the accent endpoint dot — there's 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.


## Why this default

A muted trail with an accent tail because "now, and how it got here" is the question — the eye lands on the recent
motion first, then reads the history. Axis and scale choices are stated, never silently transformed: the axes are named
in the summary, and the domains are always linear. The tail, endpoint, and arrowhead trio may be restyled but never all
removed, because the recoverable time direction is what separates a phase trace from a scatter.

## 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](/docs/accessibility#one-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 | Description |
| --- | --- | --- |
| `data` (required) | `{ 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` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
