Hybrid Drill-Down Pie/Bar Chart
A donut that drives a bar chart: click a slice and the bars show what it’s made of. Here, 280 support tickets rated Critical, Acceptable or Good, split by type.
About this chart
When to pair a donut with bars
The donut answers the first question, how the whole splits up, and the bars answer the next one, what one part is made of. Matching colors tie the two together, and the bars compare the parts more exactly than a second ring of slices would.
Good for
- Tickets, sales or costs split two ways
- Dashboards where people dig into one part
- A total with three to five parts
Think twice when
- Comparing breakdowns side by side: a stacked bar chart shows all at once
- Print: only one breakdown shows
- Many slices: the donut gets hard to click
For developers
Get the code.
Built with amCharts 5, a JavaScript library. The code includes your theme and renderer (canvas or SVG), but not the changes you make with the controls.
For projects that use npm:
How it reads:
Using Claude? Install the amCharts plugin first, so it works from our docs and examples.
npm install @amcharts/amcharts5. See Getting started.
The same chart as a JSON config. Chart Maker reads and writes this format, the library's JSON parser turns it into a chart, and the <am5-chart> element puts it on a page with no code.How it reads:
"type" is the class to create and "settings" what you would pass to its new() in code; "properties" fill in its parts, lists such as series and axes or templates such as columns.template; "refs" holds objects used in more than one place ("#series-0" stands for the one named series-0); a string starting with "@" takes a value from another object, such as "@series.get('fill')".
A complete page. Save it as an .html file and open it in a browser.
Paste this into an AI coding assistant to build the chart from scratch. More in amCharts & AI.Using Claude? Install the amCharts plugin first, so it works from our docs and examples.
// Set up data
var data = [{
category: "Critical",
value: 89,
breakdown: [{
category: "Sales inquiries",
value: 29
}, {
category: "Support requests",
value: 40
}, {
category: "Bug reports",
value: 11
}, {
category: "Other",
value: 9
}]
}, {
category: "Acceptable",
value: 71,
breakdown: [{
category: "Sales inquiries",
value: 22
}, {
category: "Support requests",
value: 30
}, {
category: "Bug reports",
value: 11
}, {
category: "Other",
value: 10
}]
}, {
category: "Good",
value: 120,
breakdown: [{
category: "Sales inquiries",
value: 60
}, {
category: "Support requests",
value: 35
}, {
category: "Bug reports",
value: 15
}, {
category: "Other",
value: 10
}]
}]
// Create root element
// https://docs.amcharts.com/v5/getting-started/#root-element
var root = am5.Root.new("chartdiv");
// Set themes
// https://docs.amcharts.com/v5/concepts/themes/
var responsive = am5themes_Responsive.new(root);
root.setThemes([
am5themes_Animated.new(root),
responsive
]);
// On a chart 400px wide or narrower, the bar chart's category names stand upright, reading
// up along their bars, so the bars get the width
responsive.addRule({
name: "AxisLabel",
tags: ["y"],
relevant: am5themes_Responsive.widthM,
settings: {
rotation: -90,
centerX: am5.p50, // centered on the row...
centerY: am5.p100, // ...beside the axis
paddingBottom: 6 // turned upright, the bottom padding is the gap to the axis
}
});
// Create wrapper container
var container = root.container.children.push(am5.Container.new(root, {
width: am5.p100, // fills the whole chart area
height: am5.p100,
layout: root.horizontalLayout // the bar chart and the donut side by side
}));
// ==============================================
// Column chart
// ==============================================
// Create chart
// https://docs.amcharts.com/v5/charts/xy-chart/
var columnChart = container.children.push(am5xy.XYChart.new(root, {
width: am5.p50, // half the width
panX: false, // no dragging the plot
panY: false,
wheelX: "none", // the mouse wheel scrolls the page, not the chart
wheelY: "none",
layout: root.verticalLayout
}));
// Create axes
// https://docs.amcharts.com/v5/charts/xy-chart/axes/
var yRenderer = am5xy.AxisRendererY.new(root, {});
var yAxis = columnChart.yAxes.push(am5xy.CategoryAxis.new(root, {
categoryField: "category",
renderer: yRenderer
}));
yRenderer.grid.template.setAll({
location: 1 // grid lines at the end of each row, between the bars
})
var xAxis = columnChart.xAxes.push(am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererX.new(root, {
strokeOpacity: 0.1 // a faint axis line
})
}));
// Add series
// https://docs.amcharts.com/v5/charts/xy-chart/series/
var columnSeries = columnChart.series.push(am5xy.ColumnSeries.new(root, {
xAxis: xAxis,
yAxis: yAxis,
valueXField: "value",
categoryYField: "category"
}));
columnSeries.columns.template.setAll({
tooltipText: "{categoryY}: {valueX}" // as "Support requests: 40"
});
// Make stuff animate on load
// https://docs.amcharts.com/v5/concepts/animations/
columnChart.appear(1000, 100);
// ==============================================
// Pie chart
// ==============================================
var pieChart = container.children.push(
am5percent.PieChart.new(root, {
width: am5.p50, // the other half
innerRadius: am5.percent(50) // a hole half the radius turns the pie into a donut
})
);
// Create series
var pieSeries = pieChart.series.push(
am5percent.PieSeries.new(root, {
valueField: "value",
categoryField: "category"
})
);
pieSeries.slices.template.setAll({
// each slice's color comes from sliceSettings in its data
templateField: "sliceSettings",
strokeOpacity: 0 // no outline
});
// Clicking a slice selects it and shows its breakdown in the bar chart. One slice stays selected: clicking the
// selected slice again does not deselect it
var currentSlice;
pieSeries.slices.template.on("active", function(active, slice) {
if (!active) {
if (slice == currentSlice) {
slice.set("active", true);
}
return;
}
var previousSlice = currentSlice;
currentSlice = slice;
if (previousSlice && previousSlice != slice) {
previousSlice.set("active", false);
}
var color = slice.get("fill");
label1.setAll({
fill: color,
text: root.numberFormatter.format(slice.dataItem.get("valuePercentTotal"), "#.'%'") // as "32%"
});
label2.set("text", slice.dataItem.get("category"));
// the bars take the slice's color
columnSeries.columns.template.setAll({
fill: slice.get("fill"),
stroke: slice.get("fill")
});
columnSeries.data.setAll(slice.dataItem.dataContext.breakdown);
yAxis.data.setAll(slice.dataItem.dataContext.breakdown);
});
pieSeries.labels.template.set("forceHidden", true); // no labels around the donut...
pieSeries.ticks.template.set("forceHidden", true); // ...and no ticks
// Each slice's color, by how good it is: the theme's color for negative values (red) for Critical, its warning color
// (yellow) for Acceptable and its positive one (green) for Good, so they follow the theme and dark mode
var goodness = { Critical: "negative", Acceptable: "warning", Good: "positive" };
data.forEach(function(item) {
item.sliceSettings = {
fill: root.interfaceColors.get(goodness[item.category])
};
});
pieSeries.data.setAll(data);
// Add label
var label1 = pieChart.seriesContainer.children.push(am5.Label.new(root, {
text: "", // filled in when a slice is selected
fontSize: 35, // big...
fontWeight: "bold", // ...bold text...
centerX: am5.p50, // ...centered in the hole
centerY: am5.p50
}));
var label2 = pieChart.seriesContainer.children.push(am5.Label.new(root, {
text: "",
fontSize: 12, // small text...
centerX: am5.p50, // ...centered too
centerY: am5.p50,
// the category name, under the percentage in the donut's hole
dy: 30
}));
// Pre-select first slice
pieSeries.events.on("datavalidated", function() {
pieSeries.slices.getIndex(0).set("active", true);
});



