Published
Edited
May 20, 2021
18 stars
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("font-size", "9pt")
.style("cursor", "default")
.attr("viewBox", [0, 0, width, height]);
const g = svg.selectAll("g")
.data(bubbles(chartData).leaves())
.join("g")
.attr("opacity", 1)
.attr("transform", d => `translate(${d.x},${d.y})`)
.on("mouseenter", e => {
e.currentTarget.parentElement.appendChild(e.currentTarget);
legend.node().parentElement.appendChild(legend.node());
})
.on("mouseover", (e, d) => {
g.transition().duration(500).attr("opacity", a => a === d ? 1 : 0.3)
g.selectAll(".ctext").transition().duration(500).attr("opacity", 0.5);
showLegend(legend, d);
})
.on("mouseout", () => {
g.transition().duration(500).attr("opacity", 1);
g.selectAll(".ctext").transition().duration(500).attr("opacity", 1);
legend.attr("opacity", 0);
});
g.append("g")
.call(g => g.append("circle").attr("r", d => d.r).attr("fill", d => bubbleColor(d.value)))
.call(g => g.append("g").attr("fill", d => invertedColor(d.value)).call(g => bubbleText(g, true, "ctext")));

g.append("g")
.attr("class", "pie")
.call(g => g.append("g")
.attr("font-weight", "bold")
.attr("transform", d => `translate(0,${d.r + 10})`)
.call(bubbleText)
)
.selectAll("path")
.data(d => d3.pie()(d.data.values).map(p => ({pie: p, data: d})))
.join("path")
.attr("d", drawPie)
.attr("fill", (d, i) => pieColor(years[i]));
const legend = svg.append("g").attr("font-weight", "bold").attr("opacity", 1);
return svg.node();
}
Insert cell
bubbles = data => d3.pack()
.size([width, height])
.padding(3)(
d3.hierarchy({children: data})
.sum(d => d.total)
)
Insert cell
drawPie = d => d3.arc()
.innerRadius(0)
.outerRadius(d.data.r)
.startAngle(d.pie.startAngle)
.endAngle(d.pie.endAngle)()
Insert cell
bubbleText = (g, short, className) => {
g.append("text")
.attr("class", className)
.attr("text-anchor", "middle")
.text(d => short ? d.data.code : d.data.state);
g.append("text")
.attr("class", className)
.attr("text-anchor", "middle")
.attr("dy", "1em")
.text(d => short ? d3.format("$.2s")(d.value) : toCurrency(d.value));
}
Insert cell
showLegend = (legend, d) => {
legend.selectAll("g").remove();
legend
.selectAll("g")
.data(years)
.join("g")
.attr("transform", (d, i) => `translate(0,${15 * i})`)
.call(g => g.append("rect")
.attr("width", 15).attr("height", 12)
.attr("rx", 3).attr("ry", 3)
.attr("fill", y => pieColor(y)))
.call(g => g.append("text")
.attr("dx", 20)
.attr("dy", "0.8em")
.text((y, i) => {
const v = d.data.values[i];
return `${y} ${toCurrency(v)} (${(v / d.value * 100).toFixed(1)}%)`
}));
legend.attr("opacity", 1)
.attr("transform", `translate(${d.x + d.r + 10},${d.y - d.r})`);
}
Insert cell
pieColor = d3.scaleOrdinal()
.domain(years)
.range(["#4e79a7","#f28e2c","#e15759","#76b7b2","#59a14f","#edc949","#af7aa1","#ff9da7","#9c755f","#bab0ab"])
Insert cell
bubbleColor = d3.scaleSequential(d3.interpolateOrRd)
.domain(d3.extent(chartData.map(d => d.total)))
Insert cell
invertedColor = d3.scaleSequential(d3.interpolateCubehelixDefault)
.domain(d3.extent(chartData.map(d => d.total)))
Insert cell
toCurrency = (num) => d3.format("$,.2f")(num)
Insert cell
data = d3.csvParse(await FileAttachment("profit_states@2.csv").text(), d3.autoType)
Insert cell
years = data.columns.slice(2)
Insert cell
chartData = data.map(d => {
const values = years.map(y => d[y])
return {
state: d["State"],
code: d["Code"],
values: values,
total: values.reduce((a, b) => a + b)
}
})
Insert cell
width = 1024
Insert cell
height = 768
Insert cell
d3 = require("d3@6")
Insert cell
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