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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more