BarChart
Categorical comparison as bars, vertical or horizontal, with multiple series grouped, stacked, or normalized to 100%. Reach for `horizontal` whenever the category labels are words rather than dates.
<UiBarChart :data="..." :categoryKey="..." :series="..." :ariaLabel="..." />Needs the charts module
This component ships in @jgastager/bundy-ui/charts, a separate module so projects that never draw a chart don't carry the paint classes these need. Nothing extra to install — just add the module entry. See Charts.
Data
requireddata is one object per category with one field per series — the wide shape your API already returns.
- Desktop
- Mobile
data stays in the wide shape an API returns ({ month: "Jan", desktop: 120, mobile: 80 }); the component folds it into the long rows the marks want. Rows whose value isn't a finite number are dropped rather than zeroed, so a gap in the data reads as a gap.
The hover fade is a CSS transition over the library's own 250ms duration, so it costs nothing and honours prefers-reduced-motion. There's no animate prop and nothing tweens on a data change — the marks are plain SVG elements Vue patches directly.
Category key
requiredcategoryKey sets which field on each row names its category.
Series
requiredseries is field names, or { key, label, color } when a series needs a specific colour — a forecast in a lighter shade of the actual, a target in neutral.
- Actual
- Forecast
- Target
Colors are the library's own semantic ones — primary, secondary, success, warning, error, info, pending, neutral — and any shade of them, primary-400 through primary-950, exactly as the bg-* utilities spell them. Set color per series (or per item) to pin one; leave it unset to take the next slot of the default sequence. neutral and secondary are never handed out automatically, so an unlabelled series never lands on them.
group-padding spaces the bars inside a group, and the gap is taken *out* of each bar's slot rather than added between them — so a group still spans exactly its category band however many series it holds. Keep it well below the gap between groups; once the two look equal, the eye stops seeing groups and sees one long row.
ARIA label
requiredariaLabel sets what the chart shows, in one sentence. Required — an unlabelled chart is an unreadable one.
ARIA description
ariaDescription is the detail the label shouldn't carry.
Orientation
orientation turns the bars on their side. horizontal is the better choice whenever the category labels are words rather than dates: they get a full line each instead of being crammed under a vertical bar or rotated.
orientation="horizontal" is the better default whenever categories are words — each label gets a full line of width instead of being rotated or thinned. Sort the rows yourself; a ranked list reads much faster than an alphabetical one.
Variant
variant decides how multiple series share a category. grouped keeps a shared baseline, so bars can be compared directly. stacked trades that for a total per category. normalized stacks to 100%, making it a share read where the total disappears.
- Desktop
- Mobile
- Desktop
- Mobile
- Desktop
- Mobile
variant is ignored for a single series, where there's nothing to group or stack. With several, grouped keeps a shared baseline for direct comparison, while stacked trades that away for a readable total.
value-labels places itself from the variant rather than taking a position. Grouped bars have a free end, so the number goes just past it and the plot reserves the room — a nice domain often ends exactly at the largest value, so without that the tallest bar's label would be clipped. Stacked and normalized segments have no free end, so the number goes inside, and one too small to hold it is dropped rather than overflowing its neighbours.
Radius
radius rounds each bar's corners. Drop it to zero for a histogram, where the bars are meant to read as adjacent bins rather than as separate objects.
Max thickness
maxThickness caps how thick a bar gets, so a handful of categories in a wide box don't become slabs.
max-thickness defaults to 48px. Without a cap, three categories in a wide container become three slabs — length is still the encoding, but it stops reading as one.
Group padding
groupPadding is the space between bars inside one group, as a fraction of the slot each gets. It only applies to grouped — stacked segments share a slot, so there's nothing to separate.
- Desktop
- Mobile
- Desktop
- Mobile
Value labels
valueLabels prints each bar's value on it. Grouped bars get the number just past their end; stacked and normalized ones get it inside the segment, and drop it when the segment is too small to hold it.
- Desktop
- Mobile
X label
xLabel titles the category axis — the horizontal one, or the vertical one once orientation is horizontal. It follows the categories, not the screen.
Y label
yLabel titles the value axis, which is where the unit belongs when the tick labels are bare numbers.
Value format
valueFormat formats the value-axis ticks and the tooltip together.
- Actual
- Forecast
- Target
Category format
categoryFormat rewrites the category tick labels without touching your data.
Grid
grid are the rules from the value-axis ticks.
Tooltip
tooltip is the panel that follows the pointer.
Hovering dims every bar except the one under the pointer to 0.7, rather than drawing a ring on it — a ring is a dot, which reads as a stray mark sitting on a bar. The dimming is per bar but the tooltip is per category: on a grouped chart one bar lights up while the panel still lists every series at that position. Hovering the gap between bars dims nothing rather than dimming everything.
Legend
legend is on by default from two series up, and off for one. Force it off when the surrounding heading already names what's plotted.
Legend position
legendPosition sets where the legend sits relative to the plot.
- Desktop
- Mobile
Legend align
legendAlign places the key horizontally — start lines it up with the plot's left edge, which is where the eye already is after reading the axis.
- Desktop
- Mobile
- Desktop
- Mobile
- Desktop
- Mobile
Legend orientation
legendOrientation flows the entries in a row or a column. vertical gives long labels their own line.
- Desktop · 8,680 visits
- Mobile · 7,630 visits
Guides
guides drops the axes and the margins they reserve, so the bars fill the box — the bar equivalent of a sparkline, for a stat tile or a table cell.
guides="false" drops the axes, ticks, labels and grid *and* the margins they reserve, so the marks fill the whole box. That plus a small height is the inline-sparkline shape — there's no separate component for it.
Height
height is the plot's height in pixels. Ignored once aspectRatio is set.
Aspect ratio
aspectRatio sizes the chart from its container's width instead of a fixed height.
- Desktop
- Mobile
Loading
loading is a skeleton at the chart's exact height. An empty data array gets the empty state instead — both hold the height, so a dashboard doesn't reflow as its panels resolve.
- Desktop
- Mobile
- Desktop
- Mobile
@focus-change
The category under the pointer, or null once it leaves — for driving a readout or a linked row outside the chart.
Month:—
- Desktop
- Mobile
API Reference
Generated from the component's source — props, slots and emits as the component actually declares them.
Props
seriesrequiredChartSeriesInput[]ariaLabelrequiredstring"vertical" | "horizontal"'vertical'"grouped" | "stacked" | "normalized"'grouped'number0.16booleanfalseEmits
focusChange[category: string | null]Types
interface ChartPlot {
x: number;
y: number;
width: number;
height: number;
/** Right edge, since marks need it more often than the width. */
right: number;
/** Bottom edge, which is also the x axis' baseline. */
bottom: number;
}type ChartSeriesInput = string | ChartSeries;