Public
Edited
Apr 16, 2024
Insert cell
Insert cell
Insert cell
chart = {
// 设置一些关于尺寸的参数
const width = 928;
const height = 500;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 20;
const marginLeft = 40;

/**
*
* 对数据进行转换
*
*/
// Determine the series that need to be stacked.
// 决定有哪些系列进行堆叠可视化
// 返回结果可以查看 👇 下一个 📝 cell
const series = d3.stack()
// 💡 设置堆叠基线函数,这里采用 D3 所提供的一种基线函数 d3.stackOffsetExpand
// 对数据进行标准化(相当于把各系列的绝对数值转换为所占的百分比),基线是零,上边界线是 1
// 所以每个横坐标值所对应的总堆叠高度都一致(即纵坐标值为 1)
// 具体可以参考官方文档 https://d3js.org/d3-shape/stack#stackOffsetExpand
.offset(d3.stackOffsetExpand)
.keys(d3.union(unemployment.map(d => d.industry))) // distinct series keys, in input order
.value(([, D], key) => D.get(key).unemployed) // get value for each series key and stack
(d3.index(unemployment, d => d.date, d => d.industry)); // group by stack then series key

/**
*
* 构建比例尺
*
*/
// Prepare the scales for positional and color encodings.
// 设置横坐标轴的比例尺
const x = d3.scaleUtc()
.domain(d3.extent(unemployment, d => d.date))
.range([marginLeft, width - marginRight]);

// 设置纵坐标轴的比例尺
const y = d3.scaleLinear()
// 💡 省略设置纵坐标轴比例尺的定义域范围
// 因为标准化后,堆叠面积图的纵轴定义域范围就是 [0, 1] 与线性比例尺的默认定义域相同
.rangeRound([height - marginBottom, marginTop]);

// 设置颜色比例尺
const color = d3.scaleOrdinal()
.domain(series.map(d => d.key))
.range(d3.schemeTableau10);

/**
*
* 创建 svg 容器
*
*/
// 返回的是一个包含 svg 元素的选择集
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");
/**
*
* 绘制坐标轴
*
*/
// 绘制横坐标轴
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
// 💡 删掉上一步所生成的坐标轴的轴线(它含有 domain 类名)
// 👇 在后面使用纵坐标轴的刻度线来绘制
.call(g => g.select(".domain").remove());

// 绘制纵坐标轴
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
// 并使用坐标轴对象的方法 axis.ticks() 设置坐标轴的刻度数量和刻度值格式
// 其中第一个参数用于设置刻度数量(这里设置的是预期值,并不是最终值,D3 会基于出入的数量进行调整,以便刻度更可视)
// 这里设置为 (height / 80) 基于 svg 的高度来设置纵坐标轴的预期刻度数量
// 而第二个参数用于设置刻度值格式,这里设置为 "%" 表示数值采用百分比表示
.call(d3.axisLeft(y).ticks(height / 80, "%"))
.call(g => g.select(".domain").remove())
// 💡 复制顶部和底部的刻度线,用以绘制图中横向的参考线,作为面积图的上下的边界(但是视觉上其实并不明显,感觉可以省略 ❓)
.call(g => g.selectAll(".tick line")
.filter(d => d === 0 || d === 1) // 筛选出零刻度线和顶部的刻度线
.clone()
// 调整复制后的刻度线的终点位置(往右移动)
.attr("x2", width - marginLeft - marginRight))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text("↑ Unemployed persons"));

/**
*
* 绘制面积图内的面积形状
*
*/
// Construct an area shape.
const area = d3.area()
.x(d => x(d.data[0]))
.y0(d => y(d[0]))
.y1(d => y(d[1]));

// Append a path for each series.
svg.append("g")
.selectAll()
.data(series)
.join("path")
.attr("fill", d => color(d.key))
.attr("d", area)
.append("title")
.text(d => d.key);

// Return the chart with the color scale as a property (for the legend).
return Object.assign(svg.node(), {scales: {color}});
}
Insert cell
// 📝 该 cell 只是用于演示效果
series = d3.stack()
.offset(d3.stackOffsetExpand)
.keys(d3.union(unemployment.map(d => d.industry))) // distinct series keys, in input order
.value(([, D], key) => D.get(key).unemployed) // get value for each series key and stack
(d3.index(unemployment, d => d.date, d => d.industry)); // group by stack then series key
Insert cell
// 读取 csv 文件
unemployment = FileAttachment("unemployment.csv").csv({typed: true})
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