Public
Edited
Nov 8, 2024
Fork of Donut chart
Insert cell
Insert cell
chart = {
// 设置一些关于尺寸的参数
// 💡 width 是页面的宽度,它由 Observable 标准库中提供,可响应性随页面变化
// 具体可参考 Observable 的官方文档 https://observablehq.com/documentation/misc/standard-library#width
const height = Math.min(width, 500); // svg 元素的高,从 width 和 500px 之间取最小值
// 该变量用于计算环形内外半径
// 取宽度和高度两者之中较小值的一半
const radius = Math.min(width, height) / 2;

/**
*
* 构建比例尺
*
*/
// 设置颜色比例尺
// 为不同环状扇形设置不同的配色
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(d3.quantize(t => d3.interpolateSpectral(t * 0.8 + 0.1), data.length).reverse());

/**
*
* 创建 svg 容器
*
*/
// 返回的是一个包含 svg 元素的选择集
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("style", "max-width: 100%; height: auto;");
/**
*
* 对数据进行转换
*
*/
// Create the pie layout and arc generator.
// 使用 d3.pie() 创建一个 pie 饼图角度生成器
const pie = d3.pie()
// 💡 设置相邻环状扇形之间的间隔角,最终效果会在每个环状扇形之间有留白间隙(便于区分)
.padAngle(1 / radius)
.sort(null)
.value(d => d.value);

/**
*
* 绘制环形图内的环状扇形形状
*
*/
// 使用 d3.arc() 创建一个 arc 扇形生成器
const arc = d3.arc()
// 💡 设置内半径,由于这里所传递的参数不为 0,所以生成环状扇形(如果参数为 0 则生成完整扇形)
.innerRadius(radius * 0.67)
// 设置外半径
.outerRadius(radius - 1);
// 💡 环形的宽度就是内外半径之差 (radius - 1) - (radius * 0.67) = 0.33 * radius - 1

// 将每个环状扇形的面积形状绘制到页面上
svg.append("g")
.selectAll()
.data(pie(data))
.join("path")
.attr("fill", d => color(d.data.name))
.attr("d", arc)
.append("title")
.text(d => `${d.data.name}: ${d.data.value.toLocaleString()}`);

/**
*
* 添加标注信息
*
*/
// 为各扇形添加文本标注信息
svg.append("g")
.attr("font-family", "sans-serif") // 设置字体家族
.attr("font-size", 12) // 设置字体大小
.attr("text-anchor", "middle") // 设置文本对齐方式,居中对齐
.selectAll()
.data(pie(data))
.join("text")
// 通过设置 CSS 的 transform 属性将文本元素「移动」到相应的环状扇形的中点,该位置使用方法 arc.centroid(d) 计算而得
// ⚠️ 环状扇形的中点不一定在面积里,可能是在内部空白区域,如果要确保文本标注信息定位到环形上,需要根据具体情况而调整尺寸
.attr("transform", d => `translate(${arc.centroid(d)})`)
.call(text => text.append("tspan")
.attr("y", "-0.4em")
.attr("font-weight", "bold")
.text(d => d.data.name))
.call(text => text.filter(d => (d.endAngle - d.startAngle) > 0.25).append("tspan")
.attr("x", 0)
.attr("y", "0.7em")
.attr("fill-opacity", 0.7)
.text(d => d.data.value.toLocaleString("en-US")));

return svg.node();
}
Insert cell
// 读取 csv 文件
data = FileAttachment("population-by-age.csv").csv({typed: true})
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