Acronis UIKit
Components

ConfidenceCone

A forecast chart with a widening prediction band (the confidence cone).

Usage

import { ConfidenceCone } from '@acronis-platform/ui-react';
import type { ChartConfig } from '@acronis-platform/ui-react';

ConfidenceCone plots a projection with explicit uncertainty as one metric in one color: a solid line + filled area over the known/actual period, a dashed line over the forecast, and a shaded band (the "cone") between a lower and upper bound that widens with the horizon. A dashed divider and a subtle shaded region set the forecast off from the actuals (toggle with showForecastRegion). Point the field-name props at your columns (actualKey, forecastKey, lowerKey, upperKey); the band is drawn behind the lines and kept out of the tooltip and legend, and actual vs forecast differ by line style, not hue. Because the three recharts series are one metric, the legend names it once — a single swatch under the actual series' label — and the forecast key's color is re-pointed at the actual series' color, so config can't paint the metric in two hues.

Metric colors come from the palette prop, not from config — see Palettes. Each config entry maps a key to a label and an optional tone that re-points it within the palette, resolved into the --color-<key> custom properties the lines and areas paint from. The actual area and the cone band reuse the metric's color at low opacity.

Pass series to plot several metrics against one shared axis, omit the bound keys for a projection with no cone, and layer on referenceLine thresholds, showDots and styleForecastTicks — each covered below.

Examples

Give the hand-off point both an actual and a forecast (with lower = upper so the cone starts at a point) to make the two lines meet:

const data = [
  { month: 'Apr', actual: 130 },
  { month: 'May', actual: 141 },
  { month: 'Jun', actual: 150, forecast: 150, lower: 150, upper: 150 }, // hand-off
  { month: 'Jul', forecast: 162, lower: 150, upper: 176 },
  { month: 'Aug', forecast: 173, lower: 154, upper: 196 },
];

const config = {
  actual: { label: 'Actual' },
  forecast: { label: 'Forecast' },
} satisfies ChartConfig;

<ConfidenceCone
  config={config}
  data={data}
  xKey="month"
  actualKey="actual"
  forecastKey="forecast"
  lowerKey="lower"
  upperKey="upper"
  className="h-[320px] w-[560px]"
/>;

Add axis titles and a y-axis unit suffix with xAxisLabel / yAxisLabel / yUnit (the x-axis is categorical). The titles inherit the theme token:

<ConfidenceCone
  config={config}
  data={data}
  xKey="month"
  actualKey="actual"
  forecastKey="forecast"
  lowerKey="lower"
  upperKey="upper"
  xAxisLabel="Month"
  yAxisLabel="Storage"
  yUnit="GB"
/>

Several metrics on one axis

Pass series to plot more than one metric against the same axis. Each entry names its own actual / forecast / bound columns and takes its hue from config[actualKey], so every metric gets an independent cone. Every cone is drawn behind every line, and the legend names each metric once:

<ConfidenceCone
  config={{
    storage: { label: 'Storage' },
    storageForecast: { label: 'Storage forecast' },
    backups: { label: 'Backups' },
    backupsForecast: { label: 'Backups forecast' },
  }}
  data={data}
  xKey="month"
  series={[
    {
      actualKey: 'storage',
      forecastKey: 'storageForecast',
      lowerKey: 'storageLower',
      upperKey: 'storageUpper',
    },
    {
      actualKey: 'backups',
      forecastKey: 'backupsForecast',
      lowerKey: 'backupsLower',
      upperKey: 'backupsUpper',
    },
  ]}
/>

series supersedes the single-series actualKey / forecastKey / lowerKey / upperKey shorthand — pass one form or the other, not both. One of the two is required: the types reject a chart that names no columns at all.

Actual as a line

By default the observed period is an area: a solid line with a low-opacity fill beneath it. Set actualType="line" to drop that fill and leave the cone as the only shaded region — worth doing as soon as a chart carries more than one metric, where overlapping fills muddy each other and the bands:

<ConfidenceCone
  config={config}
  data={data}
  xKey="month"
  actualKey="actual"
  forecastKey="forecast"
  lowerKey="lower"
  upperKey="upper"
  actualType="line"
/>

Projection without a cone

Omit lowerKey and upperKey when a model gives a point estimate but no interval: the actual line hands off to a bare dashed forecast, with no band. It works per series, so a coned metric and a band-less one can share a chart:

<ConfidenceCone
  config={config}
  data={data}
  xKey="month"
  actualKey="actual"
  forecastKey="forecast"
/>

Thresholds, dots and forecast ticks

referenceLine draws a dashed horizontal threshold on the value axis — a target, a capacity limit — with an optional caption at its right end; pass an array to draw several. A threshold beyond the data max extends the domain so it stays visible.

showDots marks each point: filled on the observed values, hollow on the projection, so a point reads as measured or predicted without tracing the line style. styleForecastTicks italicizes the X ticks over the projected period and paints them in the first series' hue — useful when showForecastRegion is off and the shaded region isn't there to mark the horizon:

<ConfidenceCone
  config={config}
  data={data}
  xKey="month"
  actualKey="actual"
  forecastKey="forecast"
  lowerKey="lower"
  upperKey="upper"
  referenceLine={{ value: 190, label: 'Capacity' }}
  showDots
  styleForecastTicks
/>

Custom tooltip

Replace the tooltip with a configured ChartTooltipContent — imported from the same library, so you never compose recharts yourself. The synthetic cone band is already excluded from the payload:

import { ConfidenceCone, ChartTooltipContent } from '@acronis-platform/ui-react';

<ConfidenceCone
  config={config}
  data={data}
  xKey="month"
  actualKey="actual"
  forecastKey="forecast"
  lowerKey="lower"
  upperKey="upper"
  tooltipContent={
    <ChartTooltipContent
      labelFormatter={(label) => `${label} · projection`}
      formatter={(value, name) => `${name}: ${value.toLocaleString()}`}
    />
  }
/>;

Format the tick values with xTickFormatter / yTickFormatter, or hide an axis with showXAxis / showYAxis — the shared axis knobs described under Formatting and hiding axes. This is how an MRR axis reads as $146.5K:

import { ConfidenceCone, createTickFormatter } from '@acronis-platform/ui-react';

<ConfidenceCone
  config={config}
  data={data}
  xKey="month"
  actualKey="actual"
  forecastKey="forecast"
  lowerKey="lower"
  upperKey="upper"
  yTickFormatter={createTickFormatter({
    style: 'currency', currency: 'USD', notation: 'compact', maximumFractionDigits: 1,
  })}
/>;

Animation

Charts render statically by default. Opt in to an entrance animation with animate, and tune it with animationDuration / animationBegin / animationEasing:

<ConfidenceCone config={config} data={data} animate animationDuration={800} animationEasing="ease-out" />

animate honors prefers-reduced-motion: for a visitor who has asked their system to reduce motion, the series render at their final geometry with no animation (the same applies when rendering on the server).

Accessibility

Neither ConfidenceCone nor the shared ChartContainer adds a role or an aria-* attribute of its own. What semantics the chart has come from recharts, whose accessibility layer is on by default and is not switched off here: the plot's <svg> renders role="application" with tabindex="0", so Tab reaches it and focusing it opens the tooltip on the first category. The left/right arrow keys step it along the categories. Enter toggles the tooltip rather than pinning it, and since focus has already opened one, the first Enter closes it — press Enter again, or an arrow key, to bring it back.

That surface has no accessible name, though, and the layer to fix that is this one rather than recharts. recharts takes title, desc and role as first-class chart props and writes them straight into the plot's <svg>; ConfidenceCone passes none of them down, because it spreads the props it doesn't consume onto its own root <div> and hands the recharts chart only the data and margins it computes itself. The <title> and <desc> recharts always emits therefore stay empty and a screen reader announces an unnamed application region. The tooltip is not a live region either: ChartTooltipContent renders plain markup with no role="status" or aria-live, so the values it reveals as you arrow along are shown but never announced.

So name the chart from the outside: role and aria-label (or aria-labelledby) land on that same root <div>. Choose the role before you copy the snippet, though. role="img" names the chart and also makes its subtree presentational, hiding the plot from assistive technology while it stays in the tab order; that is the right trade when the picture is the whole message. Where the keyboard tooltip should stay reachable, wrap the chart in a <figure> with a <figcaption>, or pass role="group" with the same aria-label, and leave the plot's own semantics alone.

<ConfidenceCone
  role="img"
  aria-label="Storage used, with a projection and its confidence interval"
  config={config}
  data={data}
  xKey="month"
  actualKey="actual"
  forecastKey="forecast"
  lowerKey="lower"
  upperKey="upper"
/>

A name is not a text alternative, and it matters more here than on a plain series chart: the whole point of a cone is that the projected values are uncertain, and none of that is in the picture for a reader who can't see it. Put the numbers and the caveat in text as well — a caption naming the horizon and the interval, a summary sentence, or the same rows in an adjacent Table. The component's own visual distinctions are already non-chromatic and worth keeping: actual and forecast differ by line style rather than hue (solid versus dashed, one color per metric), showDots marks observed points filled and projected ones hollow, and styleForecastTicks italicizes the ticks over the projected period.

Direction (RTL)

Under dir="rtl" the chrome around the plot mirrors on its own — the legend, the tooltip, and any readouts you compose alongside the chart are ordinary flow layout built from logical CSS utilities. The plot area itself deliberately stays left-to-right: recharts lays every mark out in physical SVG coordinates, so a mirrored category axis would flip the reading order of the data without flipping the geometry that encodes it — and here that order is also the direction of time, with the forecast region and its widening cone reading toward the future. This is what mainstream charting libraries do by default.

ChartContainer pins .recharts-surface to direction: ltr so that holds whatever the page inherits, and the pin is load-bearing rather than a formality: recharts anchors axis tick text with the direction-relative SVG keywords text-anchor: start / end while placing each mark at a coordinate it computed in physical space, so an inherited dir="rtl" mirrors the text away from the geometry — end-anchored Y-axis ticks land inside the plot area, rotated X-axis ticks drift, and a ReferenceLine label at the right edge overflows the surface. Text anchored middle is direction-immune either way: unrotated X-axis ticks and axis titles. Because the pin stops at the surface, the tooltip and the legend — HTML outside it — still mirror. Chart carries the measured detail.

API Reference

Prop

Type

Edit on GitHub

On this page