Skip to content
microcharts
ReferenceExpressivePolarClock

PolarClock

React polar clock chart — cyclic magnitudes around a clock-like ring. Tiny accessible SVG microchart; RSC-safe static or /interactive.

PolarClock answers "what's the shape of the day or week cycle — when is it busy?" Each value in the cycle becomes a radial bar at its fixed angle, growing outward from an inner baseline; longer bars are busier times. Midnight (or your week-start) sits at 12 o'clock and the cycle runs clockwise, so the rhythm of a metric across the cycle is immediately legible.

PolarClock
interactive · 5.54 kB · static · 3.14 kB

Install

Import & usereact/polar-clock
import { PolarClock } from "@microcharts/react/polar-clock";// byHour — real values under “Sample data” below<PolarClock data={byHour} now={14} title="Traffic by hour" />
Sample data
// 24 hourly values, midnight → 23:00const byHour = [  80, 81, 82, 83, 20, 85, 86, 87, 88, 89, 90, 91, 92, 93, 312, 95, 96, 97, 98, 99, 100, 101, 102,  103,];
Needs package + stylesheetSet up with AI

Try it

When to use it

  • Good for — the shape of a 24-hour or 7-day cycle, when a metric is busy across the cycle, or a compact seasonal read in a KPI card.
  • Avoid for — exact value comparison (unroll the cycle into a SparkBar), a non-cyclic trend (Sparkline), or more than a few dozen segments.

Sizing

Variants

the peak numeral, or opacity mode for tiny sizes
ticks off — pattern-only reads where the axis doesn't matter
locale — the peak numeral and the summary both follow it

Why this default

The channel is radial length from the inner baseline, not sector area. Equal-value bars at the rim span more area than ones near the hub, so an area reading would exaggerate the outer segments — the inner radius is nonzero precisely to curb that distortion, and the docs ask you to compare lengths, not wedges. Bars are always zero-anchored at that baseline. For very small sizes where length is hard to judge, mode="opacity" switches the channel to a five-step fill — a radial ActivityGrid — which is a deliberate, named change of encoding, not a cosmetic one. A null segment leaves a gap (the baseline ring shows the hole) because missing is not the same as zero.

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, so "when is it busy" can't be answered from the static frame alone. The four ticks are merged into one path (a single node), so the orientation cue is free of the usual label-drop-out tradeoff — set labels={false} only when the shape itself is the whole story and the axis genuinely doesn't matter.

Edge cases

all-null cycle
flat cycle — every hour equal

Four homes

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

PropTypeNotes
data*(number | null)[]One value per cycle division (24 hourly, 7 daily, any n).
nownumberIndex of the current segment to accent.
innernumberInner radius fraction r0 — the zero baseline bars grow from (default 0.35).
mode"length" | "opacity"Radial bars (default) or fixed-length 5-step fill.
originnumberIndex rendered at 12 o'clock (week-start / midnight).
labelsbooleanHairline cardinal ticks at 0/¼/½/¾ — the at-rest orientation cue. Default true.
segmentFormat(index, n) => stringSegment index → label (default: HH:00 for n=24, weekday for n=7, else index).
animatebooleaninteractiveOpt-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.

Related charts