Published
Edited
Sep 16, 2021
2 forks
6 stars
Insert cell
Insert cell
chart = {
const root = partition(data);
let focus = root;

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("font", "10px sans-serif");

/*
This line is needed so that we can calculate the length of the text labels
in the labelVisible function during the initial rendering of the visualization.
Without this line, all labels would be shown before any node is clicked,
since getComputedTextLength returns 0 for text elements that aren't added
to the DOM. This line lets us add the labels to the DOM before we calculate
their length. The same technique is used in this notebook:
https://observablehq.com/@analyzer2004/fancy-radial-bars
*/
yield svg.node();

const cell = svg
.selectAll("g")
.data(root.descendants())
.join("g")
.attr("transform", d => `translate(${d.x0},${d.y0})`);

const rect = cell.append("rect")
.attr("height", d => d.y1 - d.y0 - 1)
.attr("width", d => rectWidth(d))
.attr("fill-opacity", 0.6)
.attr("fill", d => {
if (!d.depth) return "#ccc";
while (d.depth > 1) d = d.parent;
return color(d.data.name);
})
.style("cursor", "pointer")
.on("click", clicked);

const textPadding = 4;

const text = cell.append("text")
.style("user-select", "none")
.attr("pointer-events", "none")
.attr("x", textPadding)
.attr("y", 13)
.text(d => `${d.data.name} ${format(d.value)}`)
.attr("fill-opacity", function(d) { return +labelVisible(d, this) });

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

function clicked(event, p) {
focus = focus === p ? p = p.parent : p;

root.each(d => d.target = {
y0: d.y0 - p.y0,
y1: d.y1 - p.y0,
x0: (d.x0 - p.x0) / (p.x1 - p.x0) * width,
x1: (d.x1 - p.x0) / (p.x1 - p.x0) * width
});

const t = cell.transition().duration(750)
.attr("transform", d => `translate(${d.target.x0},${d.target.y0})`);

rect.transition(t).attr("width", d => rectWidth(d.target));
text.transition(t).attr("fill-opacity", function(d) { return +labelVisible(d.target, this) });
}

function rectWidth(d) {
return d.x1 - d.x0 - Math.min(1, (d.x1 - d.x0) / 2);
}

function labelVisible(d, label) {
return (
d.x1 <= width &&
d.x0 >= 0 &&
// don't show the label if it's wider than the rectangle that it is in
d.x1 - d.x0 - 2 * textPadding > label.getComputedTextLength()
);
}

return svg.node();
}
Insert cell
data = FileAttachment("flare-2.json").json()
Insert cell
partition = data => {
const root = d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.height - a.height || b.value - a.value);
return d3.partition()
.size([width, (root.height + 1) * height / 3])
(root);
}
Insert cell
color = d3.scaleOrdinal(d3.quantize(d3.interpolateRainbow, data.children.length + 1))
Insert cell
format = d3.format(",d")
Insert cell
width = 975
Insert cell
height = 600
Insert cell
d3 = require("d3@7")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more