Vertical Stack Bar
Composition
"What's each category made of, and how does the mix shift across them?" Stack the parts in one bar per category and the composition reads at a glance. When a segment is missing, an explicit guard marks the gap instead of quietly flattening it to zero.
Need to compare two things side by side? Pass more than one series in dataSet and the bars group: per x-category you get one stacked bar per series, clustered together. Here, two regions across three years, each bar split into five product lines - so you read which region is bigger and how its mix differs, at once:
The chart above is the same engine in every framework - only the integration code below differs.
Heavy data on WebGPU Experimental
VerticalStackBarChart has an opt-in renderer="webgpu" that paints its bars on the GPU 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<VerticalStackBarChartHandle>(null);
<VerticalStackBarChart
ref={ref}
{...props}
progressiveDraw={{ durationMs: 2000 }}
/>;
// ref.current?.replay() re-runs the reveal on demand<VerticalStackBarChart :options="{ ...props, progressiveDraw: { durationMs: 2000 } }" /><div use:verticalStackBarChart={{ ...props, progressiveDraw: { durationMs: 2000 } }}></div>applyVerticalStackBarChartProps(this.c.nativeElement, {
...props,
progressiveDraw: { durationMs: 2000 },
});<michi-vz-vertical-stack-bar-chart id="c"></michi-vz-vertical-stack-bar-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 every stacked bar draws only up to the active year, and playing forward smoothly extends the stack further as the sweep advances. Scrub backward and the bars 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<VerticalStackBarChartHandle>(null);
<VerticalStackBarChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / stepForward()<VerticalStackBarChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" /><div use:verticalStackBarChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>applyVerticalStackBarChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });<michi-vz-vertical-stack-bar-chart id="c"></michi-vz-vertical-stack-bar-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 { VerticalStackBarChart } from "@michi-vz/react";
export default () => <VerticalStackBarChart {...props} />; // props = the chart options<script setup>
import { VerticalStackBarChart } from "@michi-vz/vue";
</script>
<template>
<VerticalStackBarChart :options="props" />
</template><script>
import { verticalStackBarChart } from "@michi-vz/svelte";
</script>
<div use:verticalStackBarChart={props}></div>// main.ts - register the elements once
import "@michi-vz/angular";
import { applyVerticalStackBarChartProps } from "@michi-vz/angular";
// component (uses CUSTOM_ELEMENTS_SCHEMA)
// template: <michi-vz-vertical-stack-bar-chart #c></michi-vz-vertical-stack-bar-chart>
applyVerticalStackBarChartProps(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-vertical-stack-bar-chart id="c"></michi-vz-vertical-stack-bar-chart>
<script>
Object.assign(document.getElementById("c"), props); // dataSet/series, title, …
</script>import { mountVerticalStackBarChart } from "@michi-vz/core";
const chart = mountVerticalStackBarChart(el, props);
chart.update(next);
chart.getContext(); // renderer-agnostic, LLM-ready
chart.destroy();API
Props are typed as VerticalStackBarChartProps 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.
Behaviour notes
These behaviours are automatic (no extra wiring) and match the legacy michi-vz chart for drop-in parity.
Dense x-axis - auto rotate / thin
The band axis measures its labels and adapts: horizontal when they fit, rotated −45° (all labels still shown) when they don't, and thinned to an evenly-spaced subset only at extreme density. The bottom margin is reserved automatically so rotated labels never clip. No prop needed - pass xAxisFormat to format the tick text (e.g. 202401 → 01-2024).
date accepts numbers
A row's date may be a number (e.g. date: 2024) or a string; the engine String()-coerces it. The band scale is scaleBand<string>, so mixed types are normalised consistently.
keysOrder and colour order
keysOrder ("topToBottom" default | "bottomToTop") chooses which end of the stack keys[0] sits at. With "bottomToTop" the legend / colour order is reversed relative to the stack draw order - a consumer colour authority that assigns colours by appearance order in legendData therefore binds slot 0 to the top key, not the bottom one. The stack draw (pixel) order is decided independently and is unaffected.
filter - Top/Bottom-N groups
filter = { limit, sortingDir } ranks the DataSets (groups) by their grand total across all rows + keys and keeps the top ("desc") or bottom ("asc") limit. Everything downstream (keys, dates, y-domain, bars and legend) derives from the filtered set, so the legend always mirrors exactly the drawn bars.
disabledItems
Names in disabledItems drop matching segment keys and DataSet groups. Disabling a group makes the remaining bars widen to split the band between the visible groups.
tooltipFormatter
Receives { item, key, seriesKey, series, isMissing } - item is the full data row, key the hovered segment, series the hovered segment's rows across dates. The built-in tooltip is edge-aware: it flips to the left of the cursor near the right edge and drops below the cursor near the top, so it never spills off-screen.
Interaction (canvas)
Hovering a segment dims the others same-frame (no input lag); leaving the chart clears the dim. Click a bar to pin the tooltip, click it again to re-pin, and click outside the chart to unpin.
