Skip to content
microcharts
ReferenceExpressiveGradeProfile

GradeProfile

React grade profile chart — distance vs elevation with grade readable as slope. Tiny accessible SVG microchart; RSC-safe static or /interactive.

GradeProfile answers "how hard is this route, and where does it bite?" Each segment between two points is filled from the baseline and coloured by its grade — gentle to brutal — with the elevation ridge riding on top. The steepest pitch is called out, so the wall in the route is the first thing you see.

How to read it — colour is a quantized grade bin, never a smooth ramp: flats and every descent take the faint band, then a mid tone, the negative colour, and the darkest fill for the brutal pitches. Height is the elevation ridge for shape; the encoded channel is the colour, and the interactive readout gives the exact grade.

GradeProfile
interactive · 4.83 kB · static · 2.49 kB

d and elev must share a unit (both metres, say) so that grade — rise ÷ run — is a true percent. Distance need only be monotonic; the values themselves can be any scale.

Install

Import & usereact/grade-profile
import { GradeProfile } from "@microcharts/react/grade-profile";// trail — real values under “Sample data” below<GradeProfile data={trail} format={(n) => `${n} m`} title="Queen stage" />
Sample data
const trail = [  { d: 0, elev: 800 },  { d: 100, elev: 809 },  { d: 250, elev: 812 },  { d: 350, elev: 817 },  { d: 500, elev: 835 },  { d: 700, elev: 833 },  { d: 900, elev: 865 },];
Needs package + stylesheetSet up with AI

Try it

When to use it

  • Good for — route and climb profiles (cycling, running, hiking); showing where the hard pitches fall.
  • Avoid for — a single elevation series (Sparkline), or an unordered track (distance must be monotonic).

Sizing

Variants

no summit label
custom bins
locale

format accepts Intl.NumberFormatOptions (with a locale) or a formatting function, and applies to distance and elevation numbers in the summary and readout — not grade. Grades are always percent-encoded; the locale still decides the decimal mark: the alpine profile above reads "12.000, 1.250 gain; steepest 12,3% at 3.500." in German.

Edge cases

descent-only — no climb to grade
a gap in the elevation track

A descent-only route has no climb to grade, so no pitch is emphasised and the summary reports "600 m, no real climb." — climbing difficulty is the whole story, so descents always take the gentlest colour rather than borrowing a climb's. A non-finite elevation breaks the profile into a gap: the segments touching it drop out and the ridge splits, so a missing reading never invents a grade. A flat route reports the "no real climb" sentence too, and a single point has no segment to grade at all, so its accessible name falls back to "No data."

Four homes

Why this default

Two decisions carry the chart. Grade is quantized into bins, not drawn as a continuous colour ramp — a route reads as "gentle / rolling / steep / wall", categories a rider can act on, instead of a gradient nobody can decode at a glance. And the default bins of [3, 6, 10] percent match how climbs are actually described, with the steepest pitch labelled so the hardest moment is never buried. Below 72 pixels wide the four bins collapse to a simple flat-versus-climb read, because the finer classes stop being separable there.

Accessibility

The accessible name states the route's shape in words — "900, 67 gain; steepest 16% at 800." — distance, total climb, and where the hardest pitch falls, none of it dependent on colour. The interactive entry announces each pitch as you rove it (/), giving the true grade and the cumulative climb, so the encoding never has to be learned to be used.

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*{ d: number; elev: number }[]Distance + elevation, monotonic in d, same unit so grade is a true percent.
bins[number, number, number]Ascending grade-% thresholds (always percent) that quantize the four difficulty bins.
formatIntl.NumberFormatOptions | (n) => stringFormats distance and elevation in the summary and readout; grades always render as percent.
label"max" | "none"Mark the steepest pitch, or render the profile alone.
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