Packed Circle Chart

A packed circle chart nests circles inside circles, each sized by its value. Here, one listener’s year of music: 1,000 hours, grouped by genre and style.

  • Try it:
  • Click a genre to zoom in, again to zoom out
  • Hide one in the legend
Get the code JavaScript React, Vue, Angular, Svelte JSON
About this chart

When packed circles work

Packed circles show sizes and groups at the same time: the big circles stand out, and the nesting shows what belongs together. People judge areas less precisely than lengths, so they make a striking overview rather than a ranking.

Good for

  • Time, money or votes split by group and item
  • An overview where the biggest items should stand out
  • Groups of very different size

Think twice when

  • Exact comparisons: a bar chart ranks values better
  • Using all the space: a treemap fills the rectangle
  • Values close in size: the circles look alike
For developers

Get the code.

Built with amCharts 5, a JavaScript library. The JavaScript includes your theme and renderer (canvas or SVG); the JSON also follows the changes you make with the controls.

// Create root element
// https://docs.amcharts.com/v5/getting-started/#root-element
var root = am5.Root.new("chartdiv");

// Only the styles are labeled: a genre's name would sit behind its styles,
// so the genres are named in the legend instead
var myTheme = am5.Theme.new(root);

myTheme.rule("Label", ["pack", "node", "depth0"]).set("forceHidden", true);
myTheme.rule("Label", ["pack", "node", "depth1"]).set("forceHidden", true);

// Set themes
// https://docs.amcharts.com/v5/concepts/themes/
root.setThemes([
  am5themes_Animated.new(root),
  myTheme,
  am5themes_Responsive.new(root)
]);

// Create wrapper container
var chart = root.container.children.push(am5.SerialChartContainer.new(root, {
  width: am5.percent(100),    // full width...
  height: am5.percent(100),   // ...and height
  layout: root.verticalLayout // the circles and the legend stacked top to bottom
}));

// Create series
// https://docs.amcharts.com/v5/charts/hierarchy/#adding
var series = chart.series.push(am5hierarchy.Pack.new(root, {
  singleBranchOnly: false, // other branches stay open when one opens
  // every level opens at once: 10 is more levels than the data has
  downDepth: 10,
  initialDepth: 10,
  valueField: "value",
  categoryField: "name",
  childDataField: "children",
  legendValueText: "{sum} h" // the legend shows each genre's total hours
}));

// Hours; {sum} adds up a genre's styles
series.nodes.template.set("tooltipText", "{category}: [bold]{sum} hours[/]");

// Set data: one listener's year of music, 1,000 hours by genre and style
// https://docs.amcharts.com/v5/charts/hierarchy/#setting-data
series.data.setAll([{
  name: "Music",
  children: [{
    name: "Rock",
    children: [
      { name: "Indie", value: 120 },
      { name: "Classic rock", value: 80 },
      { name: "Punk", value: 35 },
      { name: "Metal", value: 25 }
    ]
  }, {
    name: "Pop",
    children: [
      { name: "Synth-pop", value: 90 },
      { name: "Dance", value: 70 },
      { name: "K-pop", value: 40 }
    ]
  }, {
    name: "Electronic",
    children: [
      { name: "House", value: 85 },
      { name: "Ambient", value: 60 },
      { name: "Techno", value: 45 }
    ]
  }, {
    name: "Hip-hop",
    children: [
      { name: "Lo-fi", value: 60 },
      { name: "Trap", value: 50 },
      { name: "Boom bap", value: 40 }
    ]
  }, {
    name: "Jazz",
    children: [
      { name: "Swing", value: 30 },
      { name: "Bebop", value: 25 },
      { name: "Fusion", value: 20 }
    ]
  }, {
    name: "Classical",
    children: [
      { name: "Baroque", value: 40 },
      { name: "Romantic", value: 35 }
    ]
  }, {
    name: "Folk",
    children: [
      { name: "Americana", value: 30 },
      { name: "Celtic", value: 20 }
    ]
  }]
}]);

series.set("selectedDataItem", series.dataItems[0]); // start with the root selected, so everything shows

// Legend of the genres: click one to hide it
// https://docs.amcharts.com/v5/charts/hierarchy/#legend
var legend = chart.children.push(am5.Legend.new(root, {
  centerX: am5.percent(50), // the legend's middle...
  x: am5.percent(50)        // ...at the middle of the chart's width
}));

legend.data.setAll(series.dataItems[0].get("children")); // one legend item per genre

// Make stuff animate on load
series.appear(1000, 100);