ForecastCone
React forecast cone chart — history plus a widening prediction cone for landing risk. Tiny accessible SVG microchart; RSC-safe static or /interactive.
ForecastCone answers "will we land where we need to?". History is a solid line; the forecast is a fan of prediction bands that widen over the horizon, with a dashed median — because an estimate should never render as fact. The whole point of a fan chart is visible confidence decay, so three rules are not style options: at most two bands (50/80 — a 95% band reads as false tail confidence at micro scale), the median is always dashed, and a cone that fails to widen is flagged, never quietly inflated.
Install
import { ForecastCone } from "@microcharts/react/forecast-cone";// history, forecast — real values under “Sample data” below<ForecastCone data={history} forecast={forecast} target={45} title="Q4 revenue" />Sample data
// weekly revenue ($M): 7 weeks of history, a 4-week widening forecastconst history = [30, 32, 31, 34, 36, 35, 38];const forecast = { mid: [39, 40, 41, 42], p80: [[36, 42], [35, 45], [34, 50], [33, 55]], p50: [[37, 41], [37, 43], [36, 46], [35, 49]],};Try it
When to use it
- Good for — a "will we hit Q4?" forecast in a KPI card, a projection with honest uncertainty in a sentence, band-vs-target landing reads.
- Avoid for — a forecast with no uncertainty (Sparkline) or one estimate's spread (GradedBand).
Sizing
Variants
format also takes Intl.NumberFormatOptions — with a locale, the in-chart landing label and the accessible
summary's median, interval, and target numbers all follow that locale's own grouping and currency placement (the label
above reads "4.200 €", not "€4,200").
Edge cases
With no history the cone still draws from the first forecast point, and the accessible summary quietly drops its "from N today" clause instead of naming a value that doesn't exist. A cone whose bands narrow instead of widen is an input-honesty failure — it renders exactly as supplied (never auto-inflated to look right) and logs a one-time dev warning so the mistake surfaces in development rather than silently misleading a reader.
Four homes
Why this default
50/80 bands, not 95, because micro-scale tails invite overreading — a 95% band paints false confidence about the extremes. The median is dashed so it never reads as a committed number, and the cone must visibly widen: if the supplied bands don't (they narrow, or stay flat), that is an input-honesty failure the component surfaces rather than papering over — a forecast whose uncertainty doesn't grow with the horizon is misrepresenting confidence decay.
Accessibility
The accessible name states the median, the horizon interval, today's actual, and — with a target — whether the band clears it: "Median forecast 42 by week 11 (80% between 33 and 55), from 38 today. The 80% band straddles the 50 target.". The interactive entry is region-aware: history points announce a value, forecast points announce the median and 80% interval.
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[] | Historical actuals. |
| forecast* | { mid: number[]; p80: [lo,hi][]; p50?: [lo,hi][] } | Median + prediction bands (at most 2: 50/80). |
| target | number | The landing reference the cone must clear (adds a clearance clause). |
| unit | string | Period noun for the summary (default "week"). |
| label | "landing" | "none" | Median endpoint value in a right gutter. |
| 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
ControlStrip
React control strip chart — process readings against control limits for out-of-control signals. Tiny accessible SVG microchart; RSC-safe static or /interactive.
QuantileDots
React quantile dots chart — sample or posterior as quantile dots against a decision threshold. Tiny accessible SVG microchart; RSC-safe static or /interactive.