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

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.

```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.

## Try it

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

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

## 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

**service cell**

```tsx
<PhaseTrace data={trajectory} xLabel="CPU" yLabel="Latency" width={32} height={28} />
```

**quadrant grid**

```tsx
<PhaseTrace data={trajectory} grid startDot />
```

## 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
/>
```

`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.

```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 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

```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 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

**In a sentence**

```tsx
<p>
  CPU × latency phase{" "}
  <span className="mc-inline">
    <PhaseTrace data={trajectory} xLabel="CPU" yLabel="Latency" summary={false} />
  </span>{" "}
  — lag loop in the upper-right regime.
</p>
```

**In a table cell**

```tsx
<td>
  <PhaseTrace data={trajectory} xLabel="CPU" yLabel="Latency" />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">loop</span>
  <span className="unit">CPU × latency</span>
  <PhaseTrace data={trajectory} xLabel="CPU" yLabel="Latency" />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  api <PhaseTrace data={trajectory} xLabel="CPU" yLabel="Latency" />
</button>
```

Best at KPI/card scale — phase loops need a square plot.

## 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, press `Escape`, or
press outside the chart. 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).
