Public
Edited
Oct 22, 2024
Insert cell
Insert cell
Insert cell
chart = {
/**
*
* 构建比例尺
*
*/
// 设置颜色比例尺
const color = d3.scaleOrdinal()
.domain(data.columns.slice(1))
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
/**
*
* 对数据进行转换
*
*/
// 使用 d3.pie() 创建一个 pie 饼图角度生成器
// 饼图角度生成器会基于给定的数据,计算各数据项所对应的扇形在饼图中所占的角度
const pie = d3.pie()
.sort(null)
.padAngle(0.02)
.value((d) => d.population);

/**
*
* 创建图例
* ⚠️ 但以下代码似乎有问题,在页面并没有成功渲染显示出来
* ☝️ 在前一个 cell 使用另一种方法来创建图例
*
*/
// const legend = d3.select("body").append("svg")
// .attr("class", "legend")
// .attr("width", 120)
// .attr("height", (data.columns.length - 1) * 20)
// .selectAll("g")
// .data(data.columns.slice(1).reverse())
// .enter().append("g")
// .attr("transform", (d, i) => `translate(0,${i * 20})`);

// legend.append("rect")
// .attr("width", 18)
// .attr("height", 18)
// .style("fill", color);

// legend.append("text")
// .attr("x", 24)
// .attr("y", 9)
// .attr("dy", ".35em")
// .text((d) => d);

/**
*
* 绘制环形图
* 每个环形图都使用单独的一个 <svg> 容器进行渲染
* 💡 由于 <svg> 默认是 inline 元素,所以页面会自动将多个 <svg> 进行排版,最终效果是多个环形图不重叠地展示在页面上
* 如果只采用一个 <svg> 来渲染所有的环形图,则需要(繁琐的坐标计算)手动在它们定位到 svg 画布中
*
*/
// 💡 构建一个比例尺,用于将各州的人口总数映射为环形图的半径
// ⚠️ 由于要实现环形的**面积**大小比例与相应各州人口数量比例一致(线性关系),而面积与半径 r 是呈平方关系
// 💡 所以各州的人口总数与环形图的半径呈幂函数关系
// 💡 使用 d3.scaleSqrt 构建一个幂比例尺(幂为 0.5)进行人口数量与半径之间的映射
// 💡 这里将比例尺的定义域范围设置为 [0, sumMax] 其中上限 sumMax 是各州人口总数中的最大值,值域范围是 [0, 220] 即最大的环形半径是 220 像素
// 💡 方法 d3.scaleSqrt() 是 d3.scalePow().exponent(0.5) 的简写形式
// 具体可以参考官方文档 https://d3js.org/d3-scale/pow#scaleSqrt
const radius = d3.scaleSqrt([0, d3.max(data, (d) => d.sum)], [0, 220]);
// 使用 d3.arc() 创建一个 arc 扇形生成器
const arc = d3.arc()
.padRadius(50);

// 在页面创建一个 <div> 作为最外层容器,包裹一系列的 <svg> 元素
const wrapper = d3.create("div");

// 绘制一系列环形图
const svg = wrapper.selectAll(".pie") // 返回一个选择集,其中虚拟/占位元素是一系列的 <svg class="pie"> 元素,用于分别绘制一个个环形图
.data(data.sort((a, b) => b.sum - a.sum))
.enter().append("svg") // 将元素绘制到页面上
.attr("class", "pie")
// 使用方法 selection.each(func) 为选择集中的每个元素都调用一次函数 func 执行相应的操作
.each(multiple)
.select("g");

// 绘制环形图的核心代码
// 传入的参数 `d` 是(包含一系列 svg 元素的)选择集当前遍历的元素所绑定的数据
// 它是一个对象 {state: string, sum: number, ages: array} 其中属性 sum 是该州人口总数
// 而函数内的 this 指向当前所遍历的元素,即相当于 nodes[i]
function multiple(d) {
// 💡 基于该州的人口总数,使用幂比例尺比例尺 radius 进行映射,计算出对应环形图的半径大小
const r = radius(d.sum);

// 返回的选择集所包含的元素是(在 <svg> 元素里所添加的)<g> 元素
const svg = d3.select(this)
.attr("width", r * 2)
.attr("height", r * 2)
.append("g")
.attr("transform", `translate(${r},${r})`);

// 在 <g> 容器里绘制一个环形图
svg.selectAll(".arc")
.data((d) => pie(d.ages))
.enter().append("path")
.attr("class", "arc")
.attr("d", arc.outerRadius(r).innerRadius(r * 0.6))
.style("fill", (d) => color(d.data.age));
}

/**
*
* 添加标注信息
*
*/
// 这里使用了 d3.format() 构建一个数值格式器,对数字进行修约等处理,生成更易于阅读的刻度值
const formatSum = d3.format(".1s");

// svg 是一个选择集,它包含多个分组,每个分组包含一个环形图的容器(即 <svg> 里的 <g> 容器)
// 在每个环形图容器里添加 <text> 元素,用于设置文本标注
const label = svg.append("text")
.attr("class", "label");

// 在每个 <text> 元素里添加一个 <tspan> 元素
// 它相当于在 svg 语境下的 span 元素,用于为部分文本添加样式(这里用于实现文本的换行效果)
label.append("tspan")
.attr("class", "label-name")
.attr("x", 0)
// 设置 <tspan> 元素的纵向偏移量,是 -.2em 表示向上移动,相当于在第一行(em 单位是与字体大小相同的长度)
.attr("dy", "-.2em")
.text((d) => d.state);

// 再在每个 <text> 元素里添加一个 <tspan> 元素
label.append("tspan")
.attr("class", "label-value")
.attr("x", 0)
// 设置 <tspan> 元素的纵向偏移量,是 1.1em 表示向下移动,相当于在第二行(em 单位是与字体大小相同的长度)
.attr("dy", "1.1em")
.text((d) => formatSum(d.sum));

// 在最外层容器的最后添加一个 <style> 元素,并在里面编写样式
wrapper.append("style").text(`
.pie {margin: 4px;}
.legend {vertical-align: top;}
.label {font: 10px sans-serif;text-anchor: middle;}
.label-name {font-weight: bold;}
`);
// return wrapper.node();
// Return the chart with the color scale as a property (for the legend).
return Object.assign(wrapper.node(), {scales: {color}});
}
Insert cell
// 读取 csv 文件,并进行转换处理
data = FileAttachment("state-age.csv")
.csv()
.then((data) => {
const columns = data.columns.slice(1);
data.forEach((d, i) => data[i] = ({
state: d.State,
sum: d3.sum(columns, (key) => +d[key]),
ages: columns.map((key) => ({ age: key, population: +d[key] }))
}));
return data;
})
Insert cell
import {swatches} from "@d3/color-legend"
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