# PolarClock (/docs/charts/polar-clock)

PolarClock turns each value in a cycle into a radial bar at its fixed angle, growing outward from an inner baseline and
zero-anchored there. Longer bars are busier times. Midnight (or your week start) sits at 12 o'clock and the cycle runs
clockwise. The channel is length, not sector area: equal-value bars at the rim cover more area than ones near the hub,
which is why the inner radius is nonzero. Compare lengths, not wedges.

```tsx
<PolarClock
  data={Array.from({ length: 24 }, (_, h) => (h === 14 ? 312 : h === 4 ? 20 : 80 + h))}
  now={14}
  title="Traffic by hour"
  size={72}
/>
```

## Install

```tsx
import { PolarClock } from "@microcharts/react/polar-clock";

<PolarClock data={byHour} now={14} title="Traffic by hour" />
```

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 { PolarClock } from "@microcharts/react/polar-clock/interactive";

<PolarClock
  data={byHour}
  now={14}
/>
```

## When to use it

Use it for the shape of a 24-hour or 7-day cycle, for when a metric is busy across that cycle, and for a compact
seasonal read in a KPI card. For exact value comparison unroll the cycle into a `SparkBar`; for a non-cyclic trend use
`Sparkline`. Keep it under a few dozen segments.

## Sizing

**a 7-day week, opacity mode for tiny sizes**

```tsx
<PolarClock data={[120,200,180,210,260,90,60]} mode="opacity" />
```

**rotate a weekday to the top with origin**

```tsx
<PolarClock data={week} origin={1} /> // Monday at 12 o'clock
```

## Variants

```tsx
const byHour = Array.from({ length: 24 }, (_, h) => (h === 14 ? 312 : h === 4 ? 20 : 80 + h));
const week = [120, 200, 180, 210, 260, 90, 60];

<PolarClock data={byHour} label="max" />
<PolarClock data={week} mode="opacity" />
```

At very small sizes, where length is hard to judge, `mode="opacity"` switches the channel to a five-step fill — a radial
`ActivityGrid`. That is a named change of encoding, not a cosmetic one.

```tsx
const byHour = Array.from({ length: 24 }, (_, h) => (h === 14 ? 312 : h === 4 ? 20 : 80 + h));

<PolarClock data={byHour} labels={false} />
```

The four cardinal ticks default on because a bare ring of bars is rotationally ambiguous: without a mark for 12 o'clock
there is no way to tell where the cycle starts. They are merged into one path, so the orientation cue costs a single
node. Set `labels={false}` only when the shape itself is the whole story and the axis genuinely doesn't matter.

```tsx
const byHour = Array.from({ length: 24 }, (_, h) => (h === 14 ? 1240 : h === 4 ? 20 : 80 + h));

<PolarClock data={byHour} label="max" locale="de-DE" />
```

## Edge cases

```tsx
<PolarClock data={Array(24).fill(null)} title="No data yet" />
```

```tsx
<PolarClock data={Array(24).fill(50)} title="Steady" />
```

A `null` segment leaves a gap and the baseline ring shows the hole, because missing is not the same as zero. A cycle
where every value is equal draws every bar at the same length.

## Four homes

**In a sentence**

```tsx
<p>
  Traffic by hour{" "}
  <span className="mc-inline">
    <PolarClock data={byHour} now={14} summary={false} />
  </span>{" "}
  — peak at 2pm, quiet after midnight.
</p>
```

**In a table cell**

```tsx
<td>
  <PolarClock data={byHour} now={14} />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">14:00</span>
  <span className="unit">busiest hour</span>
  <PolarClock data={byHour} now={14} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  Mon <PolarClock data={byHour} now={14} />
</button>
```

Best at KPI/card scale — hour wedges need room to resolve.

## Accessibility

The accessible name states the peak and the quiet point of the cycle — **"Peaks at 14:00 (1.240); quietest 04:00."** —
with hour labels for a 24-segment cycle and weekday names for a 7-segment one. The interactive entry lets you arrow
through the segments circularly, each announced with its label and value through a polite live region, and the accented
`now` segment carries position and color, never color 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) | `(number \| null)[]` | One value per cycle division (24 hourly, 7 daily, any n). |
| `now` | `number` | Index of the current segment to accent. |
| `inner` | `number` | Inner radius fraction r0 — the zero baseline bars grow from (default 0.35). |
| `mode` | `"length" \| "opacity"` | Radial bars (default) or fixed-length 5-step fill. |
| `origin` | `number` | Index rendered at 12 o'clock (week-start / midnight). |
| `labels` | `boolean` | Hairline cardinal ticks at 0/¼/½/¾ — the at-rest orientation cue. Default true. |
| `segmentFormat` | `(index, n) => string` | Segment index → label (default: HH:00 for n=24, weekday for n=7, else index). |
| `size` | `number` | Dial box edge in viewBox units (default 24). |
| `fontSize` | `number` | Type size of the peak label under the dial, in viewBox units. Defaults from `size`. |
| `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).
