krz/world-incarceration
An interactive map of world incarceration statistics.
clone: git clone https://gitbay.org/krz/world-incarceration.git
main: assets/js/charts/pie.js · raw
1import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
2
3const COLORS = ["#0062ff", "#da1e28", "#fdd13a", "#24ab48", "#9966ff", "#606e85"];
4const SIZE = 420;
5const RADIUS = SIZE / 2 - 30;
6const INNER_RADIUS = RADIUS * 0.45;
7
8// data: [{label, value, alpha3}] (alpha3 is null for "Rest of World")
9// onSliceClick: (alpha3) => void — called only for named-country slices
10export function renderPieChart(container, data, onSliceClick) {
11 const el = typeof container === "string"
12 ? document.querySelector(container)
13 : container;
14
15 el.innerHTML = "";
16
17 const svg = d3.select(el).append("svg")
18 .attr("viewBox", `0 0 ${SIZE} ${SIZE}`)
19 .attr("width", "100%");
20
21 const g = svg.append("g")
22 .attr("transform", `translate(${SIZE / 2},${SIZE / 2 - 10})`);
23
24 const pie = d3.pie().value(d => d.value).sort(null);
25 const arc = d3.arc().outerRadius(RADIUS).innerRadius(INNER_RADIUS);
26 const labelArc = d3.arc()
27 .outerRadius(RADIUS * 0.78)
28 .innerRadius(RADIUS * 0.78);
29
30 const arcs = g.selectAll(".arc")
31 .data(pie(data))
32 .join("g")
33 .attr("class", "arc");
34
35 arcs.append("path")
36 .attr("d", arc)
37 .attr("fill", (d, i) => COLORS[i % COLORS.length])
38 .attr("stroke", "#161616")
39 .attr("stroke-width", 2)
40 .style("cursor", d => d.data.alpha3 ? "pointer" : "default")
41 .on("click", (event, d) => {
42 if (d.data.alpha3 && onSliceClick) onSliceClick(d.data.alpha3);
43 });
44
45 arcs.append("text")
46 .attr("transform", d => `translate(${labelArc.centroid(d)})`)
47 .attr("text-anchor", "middle")
48 .attr("dy", "0.35em")
49 .style("fill", "#fff")
50 .style("font-size", "11px")
51 .style("pointer-events", "none")
52 .text(d => {
53 const angle = d.endAngle - d.startAngle;
54 return angle > 0.3 ? d.data.label : "";
55 });
56
57 // Legend
58 const legend = svg.append("g")
59 .attr("transform", `translate(10, ${SIZE - 20 - data.length * 18})`);
60
61 data.forEach((d, i) => {
62 const row = legend.append("g").attr("transform", `translate(0, ${i * 18})`);
63 row.append("rect").attr("width", 12).attr("height", 12).attr("fill", COLORS[i % COLORS.length]);
64 row.append("text")
65 .attr("x", 16)
66 .attr("y", 10)
67 .style("fill", "#ccc")
68 .style("font-size", "11px")
69 .text(d.label);
70 });
71}