Public
Edited
Aug 21, 2023
141 forks
Importers
145 stars
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto;");

x.domain([0, root.value]);

svg.append("rect")
.attr("class", "background")
.attr("fill", "none")
.attr("pointer-events", "all")
.attr("width", width)
.attr("height", height)
.attr("cursor", "pointer")
.on("click", (event, d) => up(svg, d));

svg.append("g")
.call(xAxis);

svg.append("g")
.call(yAxis);

down(svg, root);

return svg.node();
}
Insert cell
// Creates a set of bars for the given data node, at the specified index.
function bar(svg, down, d, selector) {
const g = svg.insert("g", selector)
.attr("class", "enter")
.attr("transform", `translate(0,${marginTop + barStep * barPadding})`)
.attr("text-anchor", "end")
.style("font", "10px sans-serif");

const bar = g.selectAll("g")
.data(d.children)
.join("g")
.attr("cursor", d => !d.children ? null : "pointer")
.on("click", (event, d) => down(svg, d));

bar.append("text")
.attr("x", marginLeft - 6)
.attr("y", barStep * (1 - barPadding) / 2)
.attr("dy", ".35em")
.text(d => d.data.name);

bar.append("rect")
.attr("x", x(0))
.attr("width", d => x(d.value) - x(0))
.attr("height", barStep * (1 - barPadding));

return g;
}
Insert cell
function down(svg, d) {
if (!d.children || d3.active(svg.node())) return;

// Rebind the current node to the background.
svg.select(".background").datum(d);

// Define two sequenced transitions.
const transition1 = svg.transition().duration(duration);
const transition2 = transition1.transition();

// Mark any currently-displayed bars as exiting.
const exit = svg.selectAll(".enter")
.attr("class", "exit");

// Entering nodes immediately obscure the clicked-on bar, so hide it.
exit.selectAll("rect")
.attr("fill-opacity", p => p === d ? 0 : null);

// Transition exiting bars to fade out.
exit.transition(transition1)
.attr("fill-opacity", 0)
.remove();

// Enter the new bars for the clicked-on data.
// Per above, entering bars are immediately visible.
const enter = bar(svg, down, d, ".y-axis")
.attr("fill-opacity", 0);

// Have the text fade-in, even though the bars are visible.
enter.transition(transition1)
.attr("fill-opacity", 1);

// Transition entering bars to their new y-position.
enter.selectAll("g")
.attr("transform", stack(d.index))
.transition(transition1)
.attr("transform", stagger());

// Update the x-scale domain.
x.domain([0, d3.max(d.children, d => d.value)]);

// Update the x-axis.
svg.selectAll(".x-axis").transition(transition2)
.call(xAxis);

// Transition entering bars to the new x-scale.
enter.selectAll("g").transition(transition2)
.attr("transform", (d, i) => `translate(0,${barStep * i})`);

// Color the bars as parents; they will fade to children if appropriate.
enter.selectAll("rect")
.attr("fill", color(true))
.attr("fill-opacity", 1)
.transition(transition2)
.attr("fill", d => color(!!d.children))
.attr("width", d => x(d.value) - x(0));
}
Insert cell
function up(svg, d) {
if (!d.parent || !svg.selectAll(".exit").empty()) return;

// Rebind the current node to the background.
svg.select(".background").datum(d.parent);

// Define two sequenced transitions.
const transition1 = svg.transition().duration(duration);
const transition2 = transition1.transition();

// Mark any currently-displayed bars as exiting.
const exit = svg.selectAll(".enter")
.attr("class", "exit");

// Update the x-scale domain.
x.domain([0, d3.max(d.parent.children, d => d.value)]);

// Update the x-axis.
svg.selectAll(".x-axis").transition(transition1)
.call(xAxis);

// Transition exiting bars to the new x-scale.
exit.selectAll("g").transition(transition1)
.attr("transform", stagger());

// Transition exiting bars to the parent’s position.
exit.selectAll("g").transition(transition2)
.attr("transform", stack(d.index));

// Transition exiting rects to the new scale and fade to parent color.
exit.selectAll("rect").transition(transition1)
.attr("width", d => x(d.value) - x(0))
.attr("fill", color(true));

// Transition exiting text to fade out.
// Remove exiting nodes.
exit.transition(transition2)
.attr("fill-opacity", 0)
.remove();

// Enter the new bars for the clicked-on data's parent.
const enter = bar(svg, down, d.parent, ".exit")
.attr("fill-opacity", 0);

enter.selectAll("g")
.attr("transform", (d, i) => `translate(0,${barStep * i})`);

// Transition entering bars to fade in over the full duration.
enter.transition(transition2)
.attr("fill-opacity", 1);

// Color the bars as appropriate.
// Exiting nodes will obscure the parent bar, so hide it.
// Transition entering rects to the new x-scale.
// When the entering parent rect is done, make it visible!
enter.selectAll("rect")
.attr("fill", d => color(!!d.children))
.attr("fill-opacity", p => p === d ? 0 : null)
.transition(transition2)
.attr("width", d => x(d.value) - x(0))
.on("end", function(p) { d3.select(this).attr("fill-opacity", 1); });
}
Insert cell
function stack(i) {
let value = 0;
return d => {
const t = `translate(${x(value) - x(0)},${barStep * i})`;
value += d.value;
return t;
};
}
Insert cell
function stagger() {
let value = 0;
return (d, i) => {
const t = `translate(${x(value) - x(0)},${barStep * i})`;
value += d.value;
return t;
};
}
Insert cell
root = d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value)
.eachAfter(d => d.index = d.parent ? d.parent.index = d.parent.index + 1 || 0 : 0)
Insert cell
data = FileAttachment("flare-2.json").json()
Insert cell
x = d3.scaleLinear().range([marginLeft, width - marginRight])
Insert cell
xAxis = g => g
.attr("class", "x-axis")
.attr("transform", `translate(0,${marginTop})`)
.call(d3.axisTop(x).ticks(width / 80, "s"))
.call(g => (g.selection ? g.selection() : g).select(".domain").remove())
Insert cell
yAxis = g => g
.attr("class", "y-axis")
.attr("transform", `translate(${marginLeft + 0.5},0)`)
.call(g => g.append("line")
.attr("stroke", "currentColor")
.attr("y1", marginTop)
.attr("y2", height - marginBottom))
Insert cell
color = d3.scaleOrdinal([true, false], ["steelblue", "#aaa"])
Insert cell
barStep = 27
Insert cell
barPadding = 3 / barStep
Insert cell
duration = 750
Insert cell
height = {
let max = 1;
root.each(d => d.children && (max = Math.max(max, d.children.length)));
return max * barStep + marginTop + marginBottom;
}
Insert cell
marginTop = 30
Insert cell
marginRight = 30
Insert cell
marginBottom = 0
Insert cell
marginLeft = 100
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more