# Annotations (/docs/annotations)

Annotations answer "what should the reader compare this against?". They're plain children — `<Threshold>`,
`<TargetZone>`, `<Marker>`, `<Callout>` — that the host chart draws in its own coordinate space, so the same child lands
identically on any chart that accepts it. Reference ink whispers: the hairlines stroke at 0.7 opacity, labels take the
muted `--mc-neutral` label ink, and a target band uses the same faint `--mc-band` fill the charts do — so the data never
competes with its context.

```tsx
import { Sparkline } from "@microcharts/react/sparkline";
import { Threshold, TargetZone, Marker } from "@microcharts/react/annotations";

<Sparkline data={latency} title="p95 latency" width={200} height={48}>
  <TargetZone y={[40, 60]} />
  <Threshold y={65} label="SLA" />
  <Marker x={3} label="deploy" />
</Sparkline>
```

## Install

The annotation layer is its own ~1.5 kB entry. A host that renders no annotations pays only a tiny children walker; the
mark renderers ship with this import.

```tsx
import { Threshold, TargetZone, Marker, Callout } from "@microcharts/react/annotations";
```

## The vocabulary

- **`<Threshold y={65} label="SLA" />`** — a dashed reference hairline at a data-space `y`.
- **`<TargetZone y={[40, 60]} />`** — a normal-range band, always drawn _beneath_ the data ink. It takes a `label` too.
- **`<Marker x={8} label="deploy" />`** — a vertical moment mark; `x` addresses the host's primary axis (the data
  **index** on index-based charts like Sparkline and SparkBar).
- **`<Marker x={8} celebrate />`** — six deterministic particles burst once on entrance, for milestone crossings only;
  under reduced motion they rest as a quiet starburst.
- **`<Callout x={5} y={45} label="dip" />`** — one narrated point on an elbow hairline. `label` is required; `y` is
  optional and defaults to the frame's mid-height.

`label` is optional on the other three, and all four take a `color` to override the reference ink on that one mark. A
fragment works as a container — `<><Threshold … /><Marker … /></>` — the host's walker unwraps it.

```tsx
<Sparkline data={signups} width={160} height={36}>
  <Marker x={9} celebrate label="10k!" />
</Sparkline>
```

```tsx
<Sparkline data={latency} width={160} height={40}>
  <Callout x={3} y={58} label="cache miss" />
</Sparkline>
```

## The same child, any host

The children don't know which chart drew them — they resolve against the host's scale. Move the exact `<TargetZone>` and
`<Threshold>` from a line onto bars and they land in the same data-space, unchanged.

```tsx
<SparkBar data={deploys} width={160} height={44}>
  <TargetZone y={[3, 8]} />
  <Threshold y={10} label="cap" />
</SparkBar>
```

## Every value-series host

Every **value-series** chart hosts annotations — the charts with a continuous value axis and an index / slot / time
x-axis, so a data-space `x` and `y` map cleanly onto the frame. That's Sparkline, SparkBar, MiniBar, CyclePlot,
CitySkyline, ChangePoint, DualSparkline, SpreadBand, ForecastCone, ControlStrip, QueueDepth, BurnChart, Waterfall,
PercentileTrace, RetentionCurve, WinProbWorm, ErrorBudget, NetFlow, Horizon, EnsembleGhosts, PairedBars (vertical), and
Slope. The same `<Threshold>` or `<Marker>` lands in each chart's own data-space, unchanged. (MiniBar and PairedBars
host in their default **vertical** orientation, where value lives on the y-axis; a horizontal layout flips the axes, so
annotation children pass through.)

Charts without a single continuous value-y don't host them, by design — there's no shared `y` for a `<Threshold>` to
land on:

| Why it can't host             | Examples                                 |
| ----------------------------- | ---------------------------------------- |
| Scalar readout — no axis      | Delta, FatDigits, StatusDot, Thermometer |
| Part-to-whole — y is a share  | MicroDonut, SegmentedBar, Funnel         |
| Value encoded as colour       | HeatStrip, ActivityGrid, the calendars   |
| Value against value           | QuadrantDot, MicroScatter, BiasStrip     |
| Pure timeline — no value axis | the event/phase timelines                |

Most still accept children as a plain pass-through, so an annotation placed inside one renders nothing and warns in dev;
a few — Delta among them — take no children at all, and it's a type error.

<AnnotationHostShowcase />

## Honesty rules

- A coordinate outside the host's domain **clamps to the edge at 0.4 opacity** — visibly off-scale, never silently
  dropped.
- Annotations never touch the auto summary: the data description stays pure. To narrate an annotation on a static chart,
  pass an explicit `summary` string.
- `celebrate` particles are seeded from the marker's `x` and the host's size, never `Math.random`, so server and client
  render the same burst.
- A `label` you supply is the one text the containment rule can't pre-reserve a gutter for, so it's kept inside the
  chart by one of two mechanisms. `Threshold`, `TargetZone` and `Callout` **truncate**: a label longer than its
  available run is cut and given a trailing `…`. `Marker` instead **flips its anchor** — a label near the right edge
  draws leftward from the mark rather than off the chart — so a moment mark never loses characters.
- Reference hairlines draw at a fixed 1-unit weight, deliberately exempt from `--mc-density`, so tightening the density
  never thickens the context ink along with the data. The one exception is the `Callout` elbow, drawn a shade lighter at
  0.75, because it's a leader line pointing at data rather than a reference the reader measures against.
