{
const width = 800;
const height = 650;
const padding = { top: 350, right: 100, bottom: 0, left: 100 };
const contentWidth = width - padding.left - padding.right;
const contentHeight = height - padding.top - padding.bottom;
const outerRadius = Math.min(contentWidth, contentHeight) / 2;
const innerRadius = outerRadius * 0.33;
const linePadding = outerRadius * 0.1;
const lineRadius = outerRadius + linePadding;
const labelPadding = 8;
const labelRadius = lineRadius + labelPadding;
const color = d3.scaleSequential()
.domain([0, platform_counts_arr.length])
.interpolator(d3.interpolateRainbow);
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
svg.append("text")
.attr("x", width / 2)
.attr("y", height - 100)
.attr("text-anchor", "middle")
.style("font-family", "sans-serif")
.style("font-size", "24px")
.style("font-weight", "bold")
.text("Proportion of Platforms in Top 100 Games");
// Append a group element centered in the SVG
const g = svg.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
// turns platform count data into arc angles (pie generator)
const pie = d3.pie().value(d => d.count);
// turns data in drawable arc d attributes for the path element (arc generator)
const dataArc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
// arc generator for labels, giving labels spacing between pie and data
const labelArc = d3.arc()
.innerRadius(labelRadius)
.outerRadius(labelRadius);
// arc generator for line, giving line spacing between pie and data
const lineArc = d3.arc()
.innerRadius(lineRadius)
.outerRadius(lineRadius);
/**
* Determines if the label text should be rotated based on angle span.
* @param {Object} d - Pie slice data
* @returns {boolean}
*/
const isTextRotated = (d) => {
return (d.endAngle - d.startAngle) * 180 / Math.PI > 12;
}
/**
* Determines if the label is on the right side of the pie.
* @param {Object} d - Pie slice data
* @returns {boolean}
*/
const isRightSide = (d) => {
return (d.startAngle + d.endAngle) / 2 < Math.PI;
}
// Draw pie slices
g.selectAll('path')
.data(pie(platform_counts_arr))
.join('path')
.attr('d', dataArc)
.attr('fill', (d, i) => color(i))
.attr('stroke', 'white')
.style('stroke-width', '1px');
// Draw connector lines from label to arc edge
g.selectAll('polyline')
.data(pie(platform_counts_arr))
.join('polyline')
.attr('points', d => {
const lineStart = lineArc.centroid(d); // Start near label
const lineEnd = dataArc.centroid(d); // End near slice edge
return [lineStart, lineEnd];
})
.style('fill', 'none')
.style('stroke', 'black')
.style('stroke-width', 1);
// Add text labels around the pie
g.selectAll('text')
.data(pie(platform_counts_arr))
.join('text')
.attr('transform', d => {
const pos = labelArc.centroid(d);
const midAngleDegrees = (d.startAngle + d.endAngle) / 2 * 180 / Math.PI;
const rotation = isTextRotated(d) ? 0 : midAngleDegrees + 90;
return `translate(${pos[0]}, ${pos[1]}) rotate(${rotation})`;
})
.attr('text-anchor', d => isRightSide(d) ? 'start' : 'end')
.attr('alignment-baseline', 'middle')
.text(d => `${d.data.platform} (${d.data.count}%)`)
.style('font-family', 'arial')
.style('font-size', '12px')
.style('fill', 'black');
return svg.node();
}