Skip to content
microcharts
ReferenceExpressiveSpiralYear

SpiralYear

React spiral year chart — a full year of values wound as a spiral by day or week. Tiny accessible SVG microchart; RSC-safe static or /interactive.

SpiralYear answers "how did the year breathe?" It winds a calendar series onto an Archimedean spiral: the angle is the position in the year (January at 12 o'clock, running clockwise) and each turn outward is the next year. The value becomes a five-step opacity, so busy stretches read as darker arcs of the spiral. This is a pattern instrument — opacity is the weakest ordered channel, so for an exact day's value reach for ActivityGrid or HeatStrip.

SpiralYear
interactive · 5.35 kB · static · 2.89 kB

Install

Import & usereact/spiral-year
import { SpiralYear } from "@microcharts/react/spiral-year";// byWeek — real values under “Sample data” below<SpiralYear data={byWeek} title="Seasonality" />
Sample data
// 52 weekly values, a summer peak in week 30const byWeek = [  85, 95, 107, 120, 135, 150, 166, 183, 200, 217, 234, 250, 265, 280, 293, 305, 315, 324, 331, 336,  339, 340, 339, 336, 331, 324, 315, 305, 293, 480, 265, 250, 234, 217, 200, 183, 166, 150, 135,  120, 107, 95, 85, 76, 69, 64, 61, 60, 61, 64, 69, 76,];
Needs package + stylesheetSet up with AI

Try it

When to use it

  • Good for — the seasonal shape of a year at a glance, spotting a busy season or a quiet stretch, or a compact "the year in one square".
  • Avoid for — reading an exact day's value (ActivityGrid / HeatStrip), a non-cyclic trend (Sparkline), or more than about three years.

Variants

arc marks, or three steps for tiny sizes

locale doesn't change any in-chart mark — opacity steps carry the value, never a printed number — but it does localize the peak and low values named in the accessible summary:

locale — the accessible summary follows it (no visible mark changes)

Sizing

Edge cases

a gap — a null week leaves a hole, not a low mark
no data yet

Four homes

Why this default

Month ticks are on by default because without a temporal anchor the spiral is just texture — the ticks are what make "when" readable. Five steps is the ceiling: ordered opacity supports about five discriminable levels, and more would be false precision. The spiral radius encodes time only, never value — an outer mark is simply a later date, not a bigger number. Two adjacent turns place different years at the same angle; that radial adjacency is calendar alignment, not similarity, and the chart never asks you to compare across it. A null day leaves a gap in the spiral, distinct from a faint step-one mark, because missing is not the same as low.

Accessibility

The accessible name states the count, the peak, and the low — "52 weeks; peak 1.480 in week 30, low in week 48." (the de-DE localized demo). The interactive entry lets you arrow along the spiral chronologically, each mark announced with its period and value through a polite live region. Because the channel is ordinal opacity, the docs and summary steer any exact read to ActivityGrid.

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 day or week (cadence inferred from length).
cadence"day" | "week"Data cadence; inferred from length (≈52 → week, else day) when omitted.
startDatestringISO date anchoring index 0 to a calendar angle.
steps3 | 5Opacity quantization (default 5).
monthTicksbooleanFaint radial month ticks (default true).
mark"dot" | "arc"Dots (default) or short arc segments.
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