WinProbWorm
React win-prob worm chart — win probability over a contest as a worm. Tiny accessible SVG microchart; RSC-safe static or /interactive.
WinProbWorm answers "who's winning, and when did the lead flip?". It plots one win-probability series on a fixed 0–100 axis that is never truncated — the honesty rule for a probability — and splits the worm at the 50% line: while your side leads the line reads accent, when it trails it reads neutral, and a dot marks each lead change. Because a win-probability curve is a modelled read, the summary always says so, "per the supplied model".
Install
import { WinProbWorm } from "@microcharts/react/win-prob-worm";<WinProbWorm data={[50, 48, 45, 52, 60, 58, 42, 38, 55, 68, 82, 90, 88, 94, 98]} sides={["home", "away"]} title="Win probability"/>Try it
When to use it
- Good for — live win or election probability where the lead flips, a modelled forecast whose crossings are the story, and any 0–100 probability you must not truncate.
- Avoid for — a raw score or margin (Sparkline), or a value that isn't a bounded 0–100 probability.
Sizing
Variants
At a taller size the biggest single momentum swing gets a hair connector and a signed delta; drop it with
markSwing={false}, and drop the endpoint probability with label="none".
Edge cases
With a format and locale, the endpoint label and the summary format their numbers in that locale's own decimal mark
— [50, 55.5, 61.2, 58.4, 63.5] in de-DE ends at "63,5%". Values outside 0–100 are clamped to the axis and
dev-warn — a probability cannot exceed 100. A constant series has no crossing and no swing: an all-50 series reads
"even… throughout", any other constant reads "…holds X% throughout". A null breaks the worm into disconnected runs the
way a gap should; a single point has no line to draw, so it renders just the endpoint dot and its label; data={[]}
renders the frame with the "no data" summary.
Four homes
Why this default
The fixed 0–100 axis is the whole point: a win probability that truncated its own scale would exaggerate a lead, so the axis never moves and a 2-point edge always looks like a 2-point edge. The 50% split does the reasoning for the reader — accent while your side leads, neutral while it trails, a dot at each flip — so "who's ahead, and did it ever change?" is answered before the second glance. The endpoint carries the current number, and the biggest momentum swing is marked only when there's room for it. The worm is a modelled read, never a fact, so its accessible name is framed "per the supplied model" and the chart never editorializes the forecast.
Accessibility
The accessible name states the current leader, their probability, the number of lead changes, and the biggest momentum swing — "Per the supplied model, home leads at 98%; 3 lead changes, biggest swing +17 at point 8." — or, for a constant series, "Per the supplied model, even at 50% throughout." The interactive entry roves the points with ←/→ (Home/End jump to the ends), each announcing the leader and probability at that point, with a live readout chip.
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[] | A single win-probability series, clamped to 0–100. |
| sides | [string, string] | Names for the two sides — [>50, <50]. Default ["A", "B"]. |
| label | "last" | "none" | Print the current leader's probability at the endpoint (default "last"). |
| markSwing | boolean | Mark the biggest momentum swing (default true; seat-gated). |
| 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.
Related charts
StationGlyph
React station glyph chart — transit-style station mark for status in a network. Tiny accessible SVG microchart; RSC-safe static or /interactive.
BiasStrip
React bias strip chart — bland–Altman offset between two measurement methods. Tiny accessible SVG microchart; RSC-safe static or /interactive.