SpiralYear
SpiralYear winds a year of daily or weekly values onto a spiral and encodes each value as a five-step opacity.
- encodes
- 5-step opacity of marks along a calendar spiral
- precision
- low
- nodes
- ≤ 6 (merged month-tick path + ≤ 5 step paths)
SpiralYear 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, so the radius encodes time only, never value: an
outer mark is a later date, not a bigger number. The value becomes a five-step opacity, and busy stretches render as
darker arcs. Two adjacent turns place different years at the same angle; read that radial adjacency as calendar
alignment. This is a pattern instrument: opacity is the weakest ordered channel, so for an exact day's value reach
for ActivityGrid or HeatStrip.
Install
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,];Try it
When to use it
Use it for the seasonal shape of a year, for spotting a busy season or a quiet stretch, or as a compact "the year in one
square". For an exact day's value use ActivityGrid or HeatStrip, and for a non-cyclic trend use Sparkline. Past
about three years the turns pack too tightly to read.
Sizing
Variants
Month ticks are on by default: without a temporal anchor the spiral is texture, and the ticks are what make "when"
readable. Five opacity steps is the ceiling, since ordered opacity supports about five discriminable levels; steps={3}
drops to three for tiny sizes.
locale changes no in-chart mark, since opacity steps carry the value and no number is printed. It does localize the
peak and low values named in the accessible summary:
Edge cases
A null day or week leaves a gap in the spiral, distinct from a faint step-one mark, because missing is not the same as
low.
Four homes
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, and announces each mark
with its period and value through a polite live region.
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
| Prop | Type | Notes |
|---|---|---|
| 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. |
| startDate | string | ISO date anchoring index 0 to a calendar angle. |
| steps | 3 | 5 | Opacity quantization (default 5). |
| monthTicks | boolean | Faint radial month ticks (default true). |
| mark | "dot" | "arc" | Dots (default) or short arc segments. |
| size | number | Spiral box edge in viewBox units (default 24). |
| animate | boolean | interactiveOpt-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.