1
0
mirror of https://github.com/d3/d3.git synced 2025-12-08 19:46:24 +00:00
d3/examples/pack.md
2024-09-24 17:09:14 +02:00

2.5 KiB
Raw Blame History

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 layouts 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 ? "var(--theme-background)" : "var(--theme-foreground-faintest)"))
  .attr("stroke", (d) => (d.children ? "var(--theme-foreground-fainter)" : 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("fill", "currentColor")
  .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 nodes 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();