function drawPieChart(data) {
const width = 600;
const height = 600;
const radius = Math.min(width, height) / 2;
const padding = 100;
const color = d3.scaleOrdinal()
.domain(data.map(d => d.platform))
.range(d3.schemeTableau10);
const pie = d3.pie()
.value(d => d.count)
.sort(null);
const arc = d3.arc()
.innerRadius(0)
.outerRadius(radius - 10);
const labelArc = d3.arc()
.innerRadius(radius * 0.5)
.outerRadius(radius * 0.5);
const outerLabelArc = d3.arc()
.innerRadius(radius + 20)
.outerRadius(radius + 20);
const lineStartArc = d3.arc()
.innerRadius(radius * 0.7)
.outerRadius(radius * 0.7);
const total = d3.sum(data, d => d.count);
const svg = d3.create("svg")
.attr("width", width + padding * 2)
.attr("height", height + padding * 2)
.attr("viewBox", [-width / 2 - padding, -height / 2 - padding, width + padding * 2, height + padding * 2])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");
const g = svg.append("g");
const slices = g.selectAll("g")
.data(pie(data))
.enter()
.append("g");
slices.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.platform))
.append("title")
.text(d => `${d.data.platform}: ${d.data.count} games (${((d.data.count / total) * 100).toFixed(1)}%)`);
slices.append("text")
.attr("transform", d => `translate(${labelArc.centroid(d)})`)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.attr("font-size", "10px")
.text(d => `${((d.data.count / total) * 100).toFixed(1)}%`);
slices.append("polyline")
.attr("points", d => {
const lineStart = lineStartArc.centroid(d);
const intermediate = outerLabelArc.centroid(d);
const midAngle = (d.startAngle + d.endAngle) / 2;
const offset = 30;
const outsidePos = [
intermediate[0] + (midAngle > Math.PI ? -offset : offset),
intermediate[1]
];
return [lineStart, intermediate, outsidePos];
})
.attr("stroke", "black")
.attr("fill", "none")
.attr("stroke-width", 0.5);
const labels = slices.append("text")
.attr("dy", "0.35em")
.attr("font-size", "10px")
.attr("text-anchor", d => {
const midAngle = (d.startAngle + d.endAngle) / 2;
return midAngle > Math.PI ? "end" : "start";
})
.text(d => d.data.platform)
.each(function(d) {
const pos = outerLabelArc.centroid(d);
const midAngle = (d.startAngle + d.endAngle) / 2;
const offset = 35;
const outsidePos = [
pos[0] + (midAngle > Math.PI ? -offset : offset),
pos[1]
];
d.labelPos = outsidePos;
});
function relaxLabels() {
let again = false;
const spacing = 14;
labels.each(function(d, i) {
const a = d.labelPos;
labels.each(function(d2, j) {
if (i === j) return;
const b = d2.labelPos;
if (Math.abs(a[1] - b[1]) < spacing) {
again = true;
const adjust = (spacing - Math.abs(a[1] - b[1])) / 2;
if (a[1] > b[1]) {
a[1] += adjust;
b[1] -= adjust;
} else {
a[1] -= adjust;
b[1] += adjust;
}
}
});
});
labels.attr("transform", d => `translate(${d.labelPos})`);
slices.selectAll("polyline")
.attr("points", d => {
const lineStart = lineStartArc.centroid(d);
const intermediate = outerLabelArc.centroid(d);
return [lineStart, intermediate, d.labelPos];
});
if (again) setTimeout(relaxLabels, 20);
}
relaxLabels();
return svg.node();
}