Circle packing

The area of each leaf circle in a circle-packing diagram is proportional its value (here, file size). Although nested circles do not use space as efficiently as a treemap, the “wasted” space better reveals the hierarchical structure.

// Specify the dimensions of the chart.
const width = 928;
const height = width;
const margin = 1; // to avoid clipping the root circle stroke

// Specify the number format for values.
const format = d3.format(",d");

// Create the pack layout.
const pack = d3.pack()
    .size([width - margin * 2, height - margin * 2])
    .padding(3);

// Compute the hierarchy from the JSON data; recursively sum the
// values for each node; sort the tree by descending value; lastly
// apply the pack layout.
const root = pack(d3.hierarchy(data)
    .sum((d) => d.value)
    .sort((a, b) => b.value - a.value));

// Create the SVG container.
const svg = d3.create("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("viewBox", [-margin, -margin, width, height])
    .attr("style", "width: 100%; height: auto; font: 10px sans-serif;")
    .attr("text-anchor", "middle");

// Place each node according to the layout’s x and y values.
const node = svg.append("g")
  .selectAll()
  .data(root.descendants())
  .join("g")
    .attr("transform", (d) => `translate(${d.x},${d.y})`);

// Add a title.
node.append("title")
    .text((d) => `${d.ancestors().map((d) => d.data.name).reverse().join("/")}\n${format(d.value)}`);

// Add a filled or stroked circle.
node.append("circle")
    .attr("fill", (d) => d.children ? "#fff" : "#ddd")
    .attr("stroke", (d) => d.children ? "#bbb" : null)
    .attr("r", (d) => d.r);

// Add a label to leaf nodes.
const text = node
  .filter((d) => !d.children && d.r > 10)
  .append("text")
    .attr("clip-path", (d) => `circle(${d.r})`);

// Add a tspan for each CamelCase-separated word.
text.selectAll()
  .data((d) => d.data.name.split(/(?=[A-Z][a-z])|\s+/g))
  .join("tspan")
    .attr("x", 0)
    .attr("y", (d, i, nodes) => `${i - nodes.length / 2 + 0.35}em`)
    .text((d) => d);

// Add a tspan for the node’s value.
text.append("tspan")
    .attr("x", 0)
    .attr("y", (d) => `${d.data.name.split(/(?=[A-Z][a-z])|\s+/g).length / 2 + 0.35}em`)
    .attr("fill-opacity", 0.7)
    .text((d) => format(d.value));

display(svg.node());
const data = FileAttachment("data/flare.json").json().then(display);
✎ Suggest changes to this page