Compare categories with vertical or horizontal bars
Tags: chart comparison categories ranking
The BarChart component excels at comparing discrete categories of data. It supports both vertical and horizontal orientations, stacked bars for showing composition, and value labels directly on the bars.
Reporting props marked Svelte only (
valueFormat,showDataTable,exportable, reference lines, data zoom and so on) are available in@classic-homes/charts-svelte; React support is pending.
Import
<script>
import { BarChart } from '@classic-homes/charts-svelte';
</script>// React
import { BarChart } from '@classic-homes/charts-react';Basic Usage
<script>
import { BarChart } from '@classic-homes/charts-svelte';
const data = {
categories: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
series: [
{ name: 'Sales', data: [320, 332, 301, 334, 390] },
{ name: 'Returns', data: [120, 132, 101, 134, 90] },
],
};
</script>
<BarChart title="Weekly Sales" {data} height={400} />Props
Chart-Specific Props
| Prop | Type | Default | Description |
|---|---|---|---|
data |
BarChartData |
required | Chart data with categories and series |
orientation |
'vertical' | 'horizontal' |
'vertical' |
Bar orientation. Horizontal bars list categories top to bottom in data order (the first category is at the top) |
stacked |
boolean | 'percent' |
false |
Stack bars to show composition; 'percent' stacks each category's share of 100% (Svelte only) |
showValues |
boolean |
false |
Show value labels on bars |
showTotal |
boolean |
false |
Label each stack's total at the end of the bar, formatted with valueFormat; percent stacks show the raw total |
rowHeight |
number |
28 |
Height per category row when height="auto" on horizontal bars (Svelte only) |
labelFormatter |
string | ((params: LabelFormatterParams) => string) |
- | Data label template or function |
xAxisConfig |
AxisConfig |
- | Horizontal axis name, tick formatter, rotation and range |
yAxisConfig |
AxisConfig |
- | Vertical axis name, tick formatter, rotation and range |
referenceLines |
ReferenceLine[] |
- | Lines marking targets or statistics: { value: number | 'average' | 'median' | 'min' | 'max', label?, series?, style?, color? } (Svelte only) |
referenceBands |
ReferenceBand[] |
- | Shaded value ranges: { from, to, label?, color?, series? } (Svelte only) |
dataZoom |
boolean | 'inside' | 'slider' |
- | Zoom and pan the category axis: 'inside' (wheel and drag), 'slider', or true for both (Svelte only) |
legend |
ChartLegendConfig |
- | Built-in legend position, alignment, icon and formatter |
onLegendChange |
(visible: Record<string, boolean>) => void |
- | Called when legend items are shown or hidden |
renderLegend |
Snippet<[LegendRenderProps]> |
- | Custom HTML legend (e.g. ChartLegend) instead of the built-in one; React takes a function returning a node |
onClick |
(params: DataPointEventParams) => void |
- | Callback when a bar is clicked |
onHover |
(params: DataPointEventParams) => void |
- | Callback when hovering over a bar |
Inherited Base Props
| Prop | Type | Default | Description |
|---|---|---|---|
title |
string |
required | Chart title for accessibility |
description |
string |
- | Description for screen readers |
height |
number | string |
400 |
Chart height |
theme |
'light' | 'dark' | 'auto' |
'auto' |
Theme preference |
showLegend |
boolean |
true |
Show chart legend |
showTooltip |
boolean |
true |
Show tooltip on hover |
showGrid |
boolean |
true |
Show grid lines |
tooltipFormatter |
string | ((items: TooltipFormatterParams[]) => string) |
- | Custom tooltip template or HTML function |
tooltipValueFormatter |
(value: number) => string |
- | Format values in the default tooltip |
option |
EChartsOption |
- | Raw ECharts option deep-merged over the generated one |
minHeight |
number |
- | Minimum height in pixels; also bounds an 'auto' height |
maxHeight |
number |
- | Maximum height in pixels; also bounds an 'auto' height |
loading |
boolean |
false |
Show loading skeleton; over existing data a loading overlay is shown instead (Svelte) |
error |
string | null |
- | Error message to display |
onRetry |
() => void |
- | Show a Retry button on the error state, calling this (Svelte only) |
emptyMessage |
string |
- | Message shown when data is empty |
valueFormat |
ValueFormatPreset | Intl.NumberFormatOptions | ((value: number) => string) |
- | How values are written in value axes, tooltips, labels and the data table: 'number', 'integer', 'compact', 'currency', 'currencyCents', 'percent', 'percentValue', 'days', Intl options or a function (Svelte only) |
locale |
string |
'en-US' |
Locale for valueFormat (Svelte only) |
onReady |
(chart: EChartsType) => void |
- | Called with the ECharts instance when it is created (Svelte only) |
events |
Record<string, (params) => void> |
- | Raw ECharts event handlers keyed by event name, e.g. datazoom (Svelte only) |
showDataTable |
boolean | 'sr-only' |
'sr-only' |
Render the data as a table: 'sr-only' for screen readers, true adds a visible "Show data" toggle, false for none (Svelte only) |
exportable |
boolean | ChartExportConfig |
false |
Show PNG and CSV download buttons; or { image, csv, filename } (Svelte only) |
patterns |
boolean |
false |
Fill series with decal patterns as well as colour, for colour-blind readers (Svelte only) |
class |
string |
- | Custom CSS class |
animation |
boolean |
true |
Enable/disable animations (respects prefers-reduced-motion) |
Data Structure
interface BarChartData {
categories: string[]; // Category labels
series: Array<{
name: string; // Series name for legend
data: (number | null)[]; // Values for each category; null leaves a gap
color?: string; // Optional custom color
labels?: (string | null)[]; // Optional per-point label text
stack?: string; // Stack group id; overrides `stacked` for this series
}>;
}Examples
Horizontal Bar Chart
Horizontal bars list categories top to bottom in data order, so a ranking sorted largest first
reads from the top. (Earlier versions drew the first category at the bottom; if you reversed
your data to compensate, stop reversing it.) With height="auto" the chart grows with its
rows (rowHeight, bounded by minHeight/maxHeight). height="auto" is Svelte only.
<BarChart
title="Closings by Builder"
{data}
orientation="horizontal"
height="auto"
rowHeight={32}
/>Stacked Bar Chart
<BarChart title="Revenue Breakdown" {data} stacked height={400} />Stack Totals
showTotal labels each stack's total at the end of the bar, formatted with
valueFormat. Horizontal charts widen the right edge to fit the labels.
<BarChart
title="Homes by Status"
{data}
stacked
showTotal
orientation="horizontal"
valueFormat="integer"
/>With Value Labels
<BarChart title="Monthly Performance" {data} showValues height={400} />Custom Labels and Axes
Format data labels, name and rotate axes, and format tooltip values. Per-point
labels annotate individual bars.
<script>
import { BarChart } from '@classic-homes/charts-svelte';
const data = {
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [
{ name: 'Revenue', data: [42000, 51000, 48000, 67000], labels: [null, null, null, 'Record'] },
],
};
</script>
<BarChart
title="Quarterly Revenue"
{data}
showValues
labelFormatter={(p) => p.label ?? `$${p.value / 1000}k`}
xAxisConfig={{ name: 'Quarter', rotate: 30 }}
yAxisConfig={{ formatter: (v) => `$${Number(v) / 1000}k` }}
tooltipValueFormatter={(v) => `$${v.toLocaleString()}`}
/>Legend Placement
<BarChart title="Weekly Sales" {data} legend={{ position: 'right', icon: 'circle' }} />Custom Legend
Render your own legend with the renderLegend snippet. ChartLegend is an accessible default
(toggle buttons with aria-pressed); see ChartLegend.
<script>
import { BarChart, ChartLegend } from '@classic-homes/charts-svelte';
</script>
{#snippet legend(props)}
<ChartLegend {...props} />
{/snippet}
<BarChart title="Weekly Sales" {data} renderLegend={legend} legend={{ position: 'top' }} />// React
<BarChart
title="Weekly Sales"
data={data}
legend={{ position: 'top' }}
renderLegend={(props) => <ChartLegend {...props} />}
/>Raw ECharts Overrides
Anything the props don't cover can be set with option, which is deep-merged over the
generated ECharts option. Top-level arrays such as series merge by index.
<BarChart title="Weekly Sales" {data} option={{ series: [{ barWidth: 24 }], grid: { top: 40 } }} />Percent Stacking and Stack Groups (Svelte only)
stacked="percent" shows each category's share of 100%. A series' stack id groups it with
other series that share the id, so you can stack some series and keep others side by side.
<BarChart title="Sales Mix by Community" {data} stacked="percent" />
<BarChart
title="Starts and Closings"
data={{
categories: ['Q1', 'Q2'],
series: [
{ name: 'Starts (spec)', data: [20, 24], stack: 'starts' },
{ name: 'Starts (to-be-built)', data: [31, 28], stack: 'starts' },
{ name: 'Closings', data: [44, 50] },
],
}}
/>Value Format and Average Line (Svelte only)
<BarChart
title="Revenue by Builder"
{data}
valueFormat="currency"
referenceLines={[{ value: 'average', label: 'Average' }]}
/>From Records (Svelte only)
fromRecords pivots API rows into { categories, series }, with aggregation, sorting, top N
and an "Other" bucket.
<script>
import { BarChart, fromRecords } from '@classic-homes/charts-svelte';
// rows: [{ builder: 'Classic Homes', status: 'Closed', homes: 12 }, ...]
const data = fromRecords(rows, {
category: 'builder',
series: 'status',
value: 'homes',
sort: 'total',
topN: 8,
otherLabel: 'Other',
});
</script>
<BarChart title="Homes by Builder" {data} orientation="horizontal" height="auto" stacked />Accessibility
- The
titleprop is used asaria-labelfor screen readers - Animations respect
prefers-reduced-motionmedia query - Color contrast maintained in light and dark modes
Related Charts
- LineChart - Trend visualization
- FunnelChart - Process flow