# Constellation (/docs/charts/constellation)

Four incidents, the largest of them in September. Constellation places each event as a dot by time (x) and value (y),
with an optional magnitude driving its size by area (`r ∝ √m`), because area is how the eye reads magnitude. A hairline
connects the events in time order, so a handful of incidents shows its sequence. It is built for _rare_ events, a dozen
or fewer; dense streams belong to `Seismogram` or `EventTimeline`.

```tsx
import { Constellation } from "@microcharts/react/constellation";

const month = (x) => ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep"][x];

<Constellation
  data={[
    { x: 0, y: 40, m: 2 },
    { x: 2, y: 90, m: 7 },
    { x: 5, y: 30, m: 3 },
    { x: 8, y: 65, m: 5 },
  ]}
  xFormat={month}
  title="Incidents"
/>
```

## Install

```tsx
import { Constellation } from "@microcharts/react/constellation";

<Constellation
  data={[
    { x: 0, y: 40, m: 2 },
    { x: 2, y: 90, m: 7 },
    { x: 5, y: 30, m: 3 },
    { x: 8, y: 65, m: 5 },
  ]}
  title="Incidents"
/>
```

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 { Constellation } from "@microcharts/react/constellation/interactive";

<Constellation
  data={events}
/>
```

## When to use it

Use it for sparse incidents or outages on a timeline, milestones carrying a magnitude, or any rare events where the
sequence is the story. For dense event streams reach for `Seismogram` or `EventTimeline`; for a continuous trend,
`Sparkline`. Skip it when you need precise value comparison, since dot area is a low-precision channel.

## Sizing

**month labels in the summary**

```tsx
const monthFmt = (x: number) => ["Jan","Feb","Mar","Apr","May","Jun"][x];

<Constellation data={events} xFormat={monthFmt} />
```

**pure scatter (no chronology line)**

```tsx
<Constellation data={events} connect={false} />
```

## Variants

`connect` is on by default, since with rare events the order and rhythm are usually the story. Turn it off for a plain
scatter, or set `label="max"` to print the largest event's value.

```tsx
<Constellation data={events} label="max" />
<Constellation data={events} connect={false} />
```

## Edge cases

```tsx
<Constellation data={[]} title="No incidents" />
```

```tsx
<Constellation data={[{ x: 3, y: 40 }]} title="One incident" />
```

```tsx
<Constellation data={[{ x: 0 }, { x: 2 }, { x: 5 }, { x: 8 }]} title="No values" />
```

Empty data draws no marks at all, just the sized box, with "No data." as the summary. A single event has no line to
draw: one dot, and the summary says "1 event at …" rather than forcing a range. When every point omits `y`, the vertical
position is **deterministic jitter that encodes nothing** — it spreads the dots so they do not stack. In that mode the
connector's slope carries no meaning (it still runs in time order), and neither the summary nor the interactive readout
mentions vertical position. The x position is never jittered, so simultaneous events sit at the same x.

## Four homes

**In a sentence**

```tsx
<p>
  Incidents this quarter{" "}
  <span className="mc-inline">
    <Constellation data={[{ x: 0, y: 40, m: 2 }, { x: 2, y: 90, m: 7 }]} summary={false} />
  </span>{" "}
  — four events, largest severity in February.
</p>
```

**In a table cell**

```tsx
<td>
  <Constellation data={[{ x: 0, y: 40, m: 2 }, { x: 2, y: 90, m: 7 }]} />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">4</span>
  <span className="unit">this quarter</span>
  <Constellation data={[{ x: 0, y: 40, m: 2 }, { x: 2, y: 90, m: 7 }]} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  Prod <Constellation data={[{ x: 0, y: 40, m: 2 }, { x: 2, y: 90, m: 7 }]} />
</button>
```

Best at KPI/card scale — event clouds need height to separate.

## Accessibility

The accessible name states the count, the span, and the largest event: **"4 events between 0 and 8; largest at 8."** The
interactive entry lets you arrow through the events chronologically, each announced with its time, value, and magnitude
through a polite live region; a hover readout shows the same. Dot area is a low-precision channel, so magnitude is
always carried by a number as well, never by size alone.

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: number; y?: number; m?: number }[]` | Events: x = time, y = value, m = magnitude (area-true size). |
| `connect` | `boolean` | The faint chronology line (default true). |
| `label` | `"max" \| "none"` | Numeral at the largest event. |
| `xFormat` | `(x: number) => string` | Formats time for the summary (e.g. a month name). |
| `xDomain` | `[number, number]` | Time (x) extent (default: data extent). |
| `rBase` | `number` | Base dot radius in viewBox units (default 1.6). |
| `fontSize` | `number` | Type size of the peak label, in viewBox units. Defaults from `height`. |
| `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).
