Appearance
Are you an LLM? You can read better optimized documentation at /embed/docs/chart-types/clustered-bar.md for this page in Markdown format
Clustered bar
Side-by-side bars that compare series within each category.
Identity
chartType | clusteredBar |
| Option bag | bar |
seriesData layout | seriesRows – row 0 = category header; later rows = series |
| Min grid | 2 rows × 2 columns (header included) |
| Orientation | Yes – vertical (default) or horizontal |
| Sample | Clustered bar · Horizontal bar |
Minimal chartData
js
{
chartType: 'clusteredBar',
isDataTransposed: true,
seriesData: [
['', 'Q1', 'Q2', 'Q3', 'Q4'],
['Revenue', 100, 112, 125, 140],
['Costs', 60, 66, 70, 78],
],
title: { visible: true, text: 'Revenue vs Costs' },
subtitle: { visible: false, text: '' },
}seriesData
With isDataTransposed: true (usual):
js
[
['', 'Q1', 'Q2', 'Q3'], // categories
['Revenue', 100, 112, 125], // series 0
['Costs', 60, 66, 70], // series 1
]| Rule | Detail |
|---|---|
| Row 0 | Category labels (after the corner cell) |
| Later rows | One series each; first cell is the series name |
| Cells | string | number | boolean | null |
| Ragged rows | Warn, still load – pad when columns must align |
Shared layout rules: chartData schema · seriesData.
Option bag: bar
clusteredBar, stackedBar, and stackedBar100 all read cd.bar. Stacking / 100% behaviour comes from chartType, not from a different bag.
| Field | Default (new charts) | Role |
|---|---|---|
padding | 0.3 | Gap between bars / clusters, 0–1 |
cornerRadius | 0 | Bar corner radius (px) |
reverseStackOrder | false | Stack order (mainly stacked types; harmless on clustered) |
seriesStrokes / seriesStrokeWidths / seriesStrokeDashArrays | [] | Per-series stroke chrome |
series | [] | Per-series / per-point override slots |
overrides | – | Indexed style patches |
js
{
chartType: 'clusteredBar',
seriesData: [/* … */],
bar: {
padding: 0.2,
cornerRadius: 2,
},
}Axes & scales
| Role | Vertical (default) | Horizontal |
|---|---|---|
| Domain (categories) | bottom | left |
| Primary range (values) | left | bottom |
- Keep 0 in range for honest bar lengths (engine bias for bars).
- Format / tick interval / gridlines on the primary range side after orientation.
- Secondary axis is uncommon on pure clustered bars; use combo or dual-axis line when units differ.
Details: Axes · by chart type · Value axis · Orientation.
Horizontal
js
{
chartType: 'clusteredBar',
orientation: 'horizontal',
isDataTransposed: true,
seriesData: [/* same layout */],
title: { visible: true, text: 'Pageview Sources' },
subtitle: { visible: false, text: '' },
legend: { visible: false },
}After the flip, put value formats and preferredTickInterval on axes.bottom, not axes.left. Live: Horizontal bar sample.
Type-specific behaviour
- Palette lives on
legend.colors, not on thebarbag. - Category totals default on. Hide them with
annotations.totals.visible: false– see Labels. Do not inventshowTotalLabels. - Data labels / series emphasis are mostly annotation and override trees – polish in the editor, then
getChartData(). - Changing to
stackedBar/stackedBar100keeps the sameseriesDatashape and the samebarbag; only the type (and axis % defaults for 100%) change.
Pitfalls
- Using the
lineorareabag on a bar chart – rejected asforeign-option-bag - Formatting
axes.leftafter settingorientation: 'horizontal'(range moved tobottom) - Pinning
customMinabove zero so bars look taller than the data - Expecting
reverseStackOrderto change clustered side-by-side order in a meaningful way – it targets stacks - Wrong
seriesDataorientation (isDataTransposed: false) without matching the grid
Related
- Stacked bar / 100% · Combo
- chartData schema · Axes · Legend
- Samples: Clustered bar · Horizontal bar · Currency ticks