Range Chart
Trends
"How wide is the spread?" When a single line lies about your data, draw the band instead. Best case to worst case, the forecast cone, the 5th-to-95th percentile - this shades the whole range per series so uncertainty is something the reader can see, not guess at.
The chart above is the same engine in every framework - only the integration code below differs.
When to reach for it
- Whenever a single line would overstate your certainty. Forecast cones, 5th-to-95th percentile bands, best-to-worst scenarios: the band's width is the honest answer.
- Comparing volatility across series. A wide band next to a narrow one is a risk statement no average conveys - portfolio spreads, SLA jitter, temperature ranges.
- Nested confidence levels around one forecast? That is exactly what the Fan chart composes for you, bands and median in one call.
Heavy data on WebGPU Experimental
RangeChart has an opt-in renderer="webgpu" that paints the min/max bands as GPU-instanced geometry while axes, labels and tooltips stay on the SVG layer. It is capability-gated: on a browser without WebGPU it downgrades to canvas automatically, and getContext().renderer reports whichever actually painted.
Reveal animation
The chart wipes in from left to right on mount, revealing its marks in sequence before settling into place. Off by default - a chart opts in with the progressiveDraw prop.
The marks wipe in from left to right; axes and titles stay put. With reduced motion enabled, the chart renders fully drawn instantly.
progressiveDraw: true enables the defaults (1200 ms, easeInOutCubic). A config object tunes it:
const ref = useRef<RangeChartHandle>(null);
<RangeChart
ref={ref}
{...props}
progressiveDraw={{ durationMs: 2000 }}
/>;
// ref.current?.replay() re-runs the reveal on demand<RangeChart :options="{ ...props, progressiveDraw: { durationMs: 2000 } }" /><div use:rangeChart={{ ...props, progressiveDraw: { durationMs: 2000 } }}></div>applyRangeChartProps(this.c.nativeElement, {
...props,
progressiveDraw: { durationMs: 2000 },
});<michi-vz-range-chart id="c"></michi-vz-range-chart>
<script>
const el = document.getElementById("c");
el.progressiveDraw = { durationMs: 2000 };
// el.replay() re-runs the reveal
</script>durationMsandeasing("linear", "easeOutQuad", "easeInOutCubic", or a custom(t) => tfunction) shape the sweep.autoplay: falserenders the chart fully drawn; callreplay()(React ref handle, web-component method, or the core instance) to run the reveal on demand.replayOnUpdate: truere-runs it on every data change.- Respects
prefers-reduced-motion: the chart renders fully drawn instantly.
Play through the years
The data already spans years, so there is nothing to tag. Flip on timeline and the chart's own play button and scrubber step through those years: at each step the bands draw only up to the active year, and playing forward smoothly extends them further as the sweep advances. Scrub backward and the bands retract to match. Hover only ever inspects what has actually been drawn. Off by default - nothing changes until a chart opts in.
Press the play button under the chart: it steps through the years, one snapshot at a time. Drag the scrubber to jump to any year.
const ref = useRef<RangeChartHandle>(null);
<RangeChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / stepForward()<RangeChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" /><div use:rangeChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>applyRangeChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });<michi-vz-range-chart id="c"></michi-vz-range-chart>
<script>
const el = document.getElementById("c");
el.timeline = { speedMs: 1000, loop: true };
// el.getTimeline() -> play() / pause() / seek(year)
</script>speedMssets the pace,loopwraps around,autoplay: truestarts on mount,showControl: falsehides the built-in bar.- The headless controller is always available:
chart.timeline()exposesplay() / pause() / toggle() / seek(period) / stepForward() / stepBack(), plusonStepandformatPeriodin the config for custom UI. - Values glide between years by default (
interpolate); setinterpolate: falsefor hard jump-cuts. Reduced motion always jump-cuts. timelinewins overprogressiveDrawwhen both are set on the same chart.
Usage
import { RangeChart } from "@michi-vz/react";
export default () => <RangeChart {...props} />; // props = the chart options<script setup>
import { RangeChart } from "@michi-vz/vue";
</script>
<template>
<RangeChart :options="props" />
</template><script>
import { rangeChart } from "@michi-vz/svelte";
</script>
<div use:rangeChart={props}></div>// main.ts - register the elements once
import "@michi-vz/angular";
import { applyRangeChartProps } from "@michi-vz/angular";
// component (uses CUSTOM_ELEMENTS_SCHEMA)
// template: <michi-vz-range-chart #c></michi-vz-range-chart>
applyRangeChartProps(this.c.nativeElement, props);<script type="module" src="https://cdn.jsdelivr.net/npm/@michi-vz/wc/dist/michi-vz-wc.bundle.js"></script>
<michi-vz-range-chart id="c"></michi-vz-range-chart>
<script>
Object.assign(document.getElementById("c"), props); // dataSet/series, title, …
</script>import { mountRangeChart } from "@michi-vz/core";
const chart = mountRangeChart(el, props);
chart.update(next);
chart.getContext(); // renderer-agnostic, LLM-ready
chart.destroy();API
Props are typed as RangeChartProps in @michi-vz/core. Shared across all charts: width, height, margin, colors / colorsMapping, renderer ("svg", "canvas", or experimental "webgpu"), highlightItems, disabledItems, and the on* callbacks. onChartDataProcessed / getContext() return the renderer-agnostic ChartContext.
