Skip to content
microcharts
ReferenceExpressiveTreeRings

TreeRings

React tree rings chart — per-period growth as concentric rings, oldest at the center. Tiny accessible SVG microchart; RSC-safe static or /interactive.

TreeRings answers "how did growth accumulate, period over period?" like the rings of a tree: the oldest period is at the centre and each new period adds a ring outward, whose thickness is that period's value. The channel is thickness, not area — equal thickness at a larger radius spans more area (the ring illusion), so read the thicknesses, not the wedges; exact per-period reads steer to SparkBar.

TreeRings
interactive · 4.59 kB · static · 2.2 kB

Install

Import & usereact/tree-rings
import { TreeRings } from "@microcharts/react/tree-rings";// years — real values under “Sample data” below<TreeRings data={years} unit="years" periodWord="year" title="Account age" />
Sample data
const years = [8, 12, 10, 18, 22, 15, 20, 14];
Needs package + stylesheetSet up with AI

Try it

When to use it

  • Good for — account or company age at a glance, a cohort-age marker in a table cell, or a per-period growth story in a KPI card.
  • Avoid for — exact per-period reads (SparkBar), many periods (over twenty-four), or non-cumulative series.

Sizing

Variants

filled annuli + cohort age (total)
locale — the last-value label and the summary both follow it

Edge cases

a zero-value period collapses cleanly, it isn't padded to a minimum ring
no data yet

Four homes

Why this default

Stroke rings are the default because at twenty-four pixels hairlines keep the disc quiet and let the accent ring — the current period, drawn 1.5× heavier in the accent color (weight and color, never color alone) — stay loud. The channel is radial thickness, and the docs say so explicitly, because equal thickness at a larger radius spans more area. TreeRings never enforces a minimum visual thickness: a near-zero period looks near-zero, and a zero-value period collapses its two boundaries onto each other. Pass total for the cohort-age story — the disc then fills only Σdata/total of the radius, so a young account reads as part-grown.

Accessibility

The accessible name summarizes the growth — "8 periods; latest 14, biggest 22 in period 5." The interactive entry steps the rings from the centre out with ←/→ (or hover), announcing each period as "Year 5: 22."

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[]Per-period growth, oldest first.
highlight"last" | "none" | numberWhich period's ring to pick out.
totalnumberExpected lifetime Σ — the disc fills only Σdata/total.
rings"stroke" | "fill"Boundary rings (default) or filled annuli.
periodWordstringSingular period noun for the summary (default "period").
unitstringPlural period noun for the summary (default "periods").
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