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

/**
*
* 构建比例尺
*
*/
// 设置横坐标轴的比例尺
const x = d3.scaleUtc()
.domain(d3.extent(temperatures, d => d.date))
.range([marginLeft, width - marginRight]);

// 设置纵坐标轴的比例尺
const y = d3.scaleLinear()
.domain(d3.extent(temperatures, d => d.temperature)).nice()
.range([height - marginBottom, marginTop]);

// 💡 计算数据集 temperatures 中所有温度值的中位数,以其作为阈值
const threshold = d3.median(temperatures, d => d.temperature);

/**
*
* 创建 svg 容器
*
*/
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto;");

/**
*
* 绘制坐标轴
*
*/
// 绘制横坐标轴
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0))
.call(g => g.select(".domain").remove());

// 绘制纵坐标轴
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
// 💡 为纵坐标轴添加注释(温度的单位标记)
// 拷贝最后一个刻度(通过 class 选择器 .tick:last-of-type)里面的 `<text>` 标签
.call(g => g.select(".tick:last-of-type text").clone()
// 调整定位,水平向右偏移 3px
.attr("x", 3)
// 设置文本对齐方式
.attr("text-anchor", "start")
// 设置字体样式为加粗
.attr("font-weight", "bold")
// 设置/覆写文本内容
.text("°F"));

/**
*
* 创建线性渐变色
*
*/
// 使用 Observable 平台的标准库所提供的方法 DOM.uid(namespace) 创建一个唯一 ID 号
// 具体参考 https://observablehq.com/@observablehq/stdlib#cell-790
// 用作元素 <linearGradient> 的 id 属性值
const gradient = DOM.uid();

// 使用 svg 元素 <linearGradient> 定义线性渐变色,用于图形元素的填充或描边
svg.append("linearGradient")
.attr("id", gradient.id)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0) // 渐变色的起始点的横坐标
.attr("y1", 0) // 渐变色的起始点的纵坐标
.attr("x2", 0) // 渐变色的终止点的横坐标
.attr("y2", height) // 渐变色的终止点的纵坐标
// 这里设置起始点和终止点的横坐标都是 0,由于渐变色是沿纵坐标轴变化的(以显示温度的变化),所以横坐标采用 0 即可
// ⚠️ 注意 svg 的坐标体系中向下是正方向,所以渐变色的起始点 (0, 0) 是在 y 轴的顶部,终止点 (0, height) 是在 y 轴的底部
// 进行二次选择,在元素 <linearGradient> 内添加一系列的 <stop> 元素,以切换渐变色
.selectAll("stop")
// 绑定数据
// 手动构建出一个数组,每个元素都是一个对象,其中包含了属性 offset(偏移量)和属性 color(对应的颜色)
// 其中第一个元素的偏移量是一个百分比 y(threshold) / height 即中位数相对于 y 轴的位置,颜色是红色
// 而第二个元素的偏移量是一样的,也是 y(threshold) / height 中位数相对于 y 轴的位置,颜色是黑色的
.data([
{offset: y(threshold) / height, color: "red"},
{offset: y(threshold) / height, color: "black"}
])
// 将一系列的 <stop> 元素添加到 <linearGradient> 元素里
.join("stop")
.attr("offset", d => d.offset) // 设置 offset 偏移量
.attr("stop-color", d => d.color); // 设置 stop-color 颜色值
// 根据以上手动构建的数组,可以知道会对应生成两个 <stop> 元素
// ⚠️ 因为 svg 的坐标体系中向下是正方向,渐变色的起始点是 (0, 0) 终止点是 (0, height)
// 所以从起始点到第一个 <stop> 所设置的偏移量的位置(y 轴的中间)为止,这一个范围的线段都是红色
// 由于第二个 <stop> 所设置的偏移量和第一个 <stop> 的偏移量一样,所以从 y 轴的中间位置到 y 轴底部,这一个范围的线段都是黑色

/**
*
* 绘制折线图内的线段
*
*/
// 使用方法 d3.line() 创建一个线段生成器
const line = d3.line()
.curve(d3.curveStep)
.defined(d => !isNaN(d.temperature))
.x(d => x(d.date))
.y(d => y(d.temperature));

// 将线段路径绘制到页面上
svg.append("path")
.datum(temperatures)
.attr("fill", "none")
.attr("stroke", gradient)
.attr("stroke-width", 1.5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("d", line);

return svg.node();
}
Insert cell
// 读取数据,并将字符串自动解析转换为相应的数据类型
temperatures = FileAttachment("temperature.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