Public
Edited
Jul 25, 2024
Insert cell
Insert cell
chart = {
const height = width;
const innerRadius = 70;
const outerRadius = Math.min(width, height) * 0.37;

// Stack the data into series by age
const series = d3.stack()
.keys(d3.union(data.map(d => d.category))) // distinct series keys, in input order
.value(([, D], key) => D.get(key).value) // get value for each series key and stack
(d3.index(data, d => d.state, d => d.category)); // group by stack then series key

const arc = d3.arc()
.innerRadius(d => y(d[0]))
.outerRadius(d => y(d[1]))
.startAngle(d => x(d.data[0]))
.endAngle(d => x(d.data[0]) + x.bandwidth())
.padAngle(1 / innerRadius)
.padRadius(innerRadius);

// An angular x-scale
const x = d3.scaleBand()
.domain(d3.groupSort(data, D => -d3.sum(D, d => d.value), d => d.state))
.range([0, 2 * Math.PI])
.align(0);

// A radial y-scale maintains area proportionality of radial bars
const y = d3.scaleRadial()
.domain([0, d3.max(series, d => d3.max(d, d => d[1]))])
.range([innerRadius, outerRadius]);

const color = d3.scaleOrdinal()
.domain(series.map(d => d.key))
.range(WRPcolours)
.unknown("#ccc");

// A function to format the value in the tooltip
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height /2, width, height])
.attr("style", "width: 100%; height: 800px; font: 10px sans-serif; background-color: white;");

// A group for each series, and a rect for each element in the series
svg.append("g")
.selectAll()
.data(series)
.join("g")
.attr("fill", d => color(d.key))
.selectAll("path")
.data(D => D.map(d => (d.key = D.key, d)))
.join("path")
.attr("d", arc)
// When the mouse moves over an item, quickly fade its colour to black
.on('mouseover', function(e, d) {
d3.select(this)
.transition()
.duration(100)
.style('fill', 'black')
})
// When the mouse leaves an item, slowly fade its colour back to its original colour
.on('mouseout', function(e, d) {
d3.select(this)
.transition()
.duration(100)
.style('fill', d => color(d.key))
})
.append("title")
.text(d => `${d.data[1].get(d.key).value} / ${d.data[1].get(d.key).state} / ${d.data[1].get(d.key).category}`);

// x axis
svg.append("g")
.selectAll()
.data(x.domain())
.join("g")
.attr("transform", d => `
rotate(${((x(d) + x.bandwidth() / 2) * 180 / Math.PI - 90)})
translate(${outerRadius},0)
`)
.call(g => g.append("text")
.attr("transform", d => (x(d) + x.bandwidth() / 2 + Math.PI / 1) % (2 * Math.PI) < Math.PI
? "rotate(180) translate(-2,2)"
: "translate(2,2)")
.attr("text-anchor", d => (x(d) + x.bandwidth() / 2 + Math.PI / 1) % (2 * Math.PI) < Math.PI
? "end"
: "start")
.text(d => d));

// y axis
svg.append("g")
.attr("text-anchor", "end")

.call(g => g.selectAll("g")
.data(y.ticks(10).slice(1))
.join("g")
.attr("fill", "none")
.call(g => g.append("circle")
.attr("stroke", "#000")
.attr("stroke-width", "0.05em")
.attr("stroke-opacity", 0.1)
.attr("r", y))
);

// color legend
svg.append("g")
.selectAll()
.data(color.domain())
.join("g")
.attr("transform", (d, i, nodes) => `translate(-40,${(nodes.length / 2 - i - 1) * 20})`)
.call(g => g.append("rect")
.attr("width", 18)
.attr("height", 18)
.attr("fill", color))
.call(g => g.append("text")
.attr("x", 24)
.attr("y", 9)
.attr("dy", "0.35em")
.text(d => d));


// Zoom on mouse scroll
svg.call(d3.zoom()
.extent([[-width / 2, -height /2], [width, height]])
.scaleExtent([1, 5])
.on("zoom", zoomed))
.on("mousedown.zoom", null);


function zoomed({transform}) {
svg.attr("transform", transform);
};

return svg.node();
}
Insert cell
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