Public
Edited
Nov 7, 2023
Fork of OHLC Chart
Insert cell
Insert cell
chart = {
const svg = d3.select(DOM.svg(width, height));

// 绘制横坐标轴
svg.append("g")
.call(xAxis);

// 绘制纵坐标轴
svg.append("g")
.call(yAxis);

// 💡 绘制股价图
svg.append("g")
.attr("stroke-width", 2)
.attr("fill", "none")
.selectAll("path") // 使用 <path> 元素绘制线段
// 绑定数据
.data(data)
.join("path")
// 手动编写出每个线段的绘制路径
// 首先 M${x(d.date)},${y(d.low)} 是将画笔移动到对应的位置,横坐标值是基于所绑定的数据点的 d.date 日期(并通过通过横坐标轴的比例尺 x 映射得到的);纵坐标值是基于当天股价的最低值(并通过纵坐标轴比例尺 y 映射得到的)
// 然后 V${y(d.high)} 是垂直画出一条线段,终点位置是基于当天股价的最高值
// 接着 M${x(d.date)},${y(d.open)} 将画笔(纵坐标值)移动到当天开盘价的位置(横坐标值基于日期)
// 然后 h-4 是小写字母的命令,采用相对定位(即操作时基于上一个点的定位),这里表示从上一个点开始,向左绘制一小段水平线,长度为 4px
// 接着 M${x(d.date)},${y(d.close)} 将画笔(纵坐标值)移动到当天收盘价的位置(横坐标值基于日期)
// 然后 h4 是小写字母的命令,这里表示从上一个点开始,向右绘制一小段水平线,长度为 4px
.attr("d", d => `
M${x(d.date)},${y(d.low)}V${y(d.high)}
M${x(d.date)},${y(d.open)}h-4
M${x(d.date)},${y(d.close)}h4
`)
// 基于开盘价 d.Open 和收盘价 d.Close 的大小关系为线段设置不同的颜色
.attr("stroke", d => d.open > d.close ? d3.schemeSet1[0]
: d.close > d.open ? d3.schemeSet1[2]
: d3.schemeSet1[8])
// 为每条线段添加注释信息
// 以 tooltip 的方式展示注释信息,即鼠标 hover 到特定的区域时才显示一个带有注释信息的浮窗
.append("title")
.text(d => `${formatDate(d.date)}
Open: ${formatValue(d.open)}
Close: ${formatValue(d.close)} (${formatChange(d.open, d.close)})
Low: ${formatValue(d.low)}
High: ${formatValue(d.high)}`);

return svg.node();
}
Insert cell
height = 500
Insert cell
margin = ({top: 20, right: 30, bottom: 30, left: 40})
Insert cell
Insert cell
// 设置横坐标轴的比例尺
x = d3.scaleBand()
.domain(d3.timeDay
.range(data[0].date, +data[data.length - 1].date + 1)
.filter(d => d.getDay() !== 0 && d.getDay() !== 6))
.range([margin.left, width - margin.right])
.padding(0.2)
Insert cell
Insert cell
// 设置纵坐标轴的比例尺
y = d3.scaleLog()
.domain([d3.min(data, d => d.low), d3.max(data, d => d.high)])
.rangeRound([height - margin.bottom, margin.top])
Insert cell
Insert cell
// 以下函数封装用于绘制横坐标轴的代码
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x)
.tickValues(d3.timeMonday
.every(width > 720 ? 1 : 2)
.range(data[0].date, data[data.length - 1].date))
.tickFormat(d3.timeFormat("%-m/%-d")))
.call(g => g.select(".domain").remove())
Insert cell
// 以下函数封装用于绘制纵坐标轴的代码
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y)
.tickFormat(d3.format("$~f"))
.tickValues(d3.scaleLinear().domain(y.domain()).ticks()))
.call(g => g.selectAll(".tick line").clone()
.attr("stroke-opacity", 0.2)
.attr("x2", width - margin.left - margin.right))
.call(g => g.select(".domain").remove())
Insert cell
// 时间格式器(用于设置图表注释信息的格式)
formatDate = d3.timeFormat("%B %-d, %Y")
Insert cell
// 数字格式器(用于设置图表注释信息的格式)
formatValue = d3.format(".2f")
Insert cell
// 对开盘价 d.Open 和收盘价 d.Close 进行转换的函数
// 得到当天股价变化相对于开盘价的值,按百分比计算
formatChange = {
const f = d3.format("+.2%");
return (y0, y1) => f((y1 - y0) / y0);
}
Insert cell
// 💡 创建一个时间解释器 parser,可以将特定格式的字符串解析为时间对象 Date
// 具体参考 d3-time-format 模块的官方文档 https://d3js.org/d3-time-format
// 或这一篇笔记 https://datavis-note.benbinbin.com/article/d3/core-concept/d3-concept-data-process#时间格式器
// 参数 `%Y-%m-%d` 称为 specifier 时间格式说明符,这里用于匹配的字符串格式是「年-月-日」
// %Y 表示年份(用四个数字表示)
// %m 表示月份(用两个数字表示,不足双位数的月份在前面添加 0 来补足)
// %d 表示日期(用两个数字表示,不足的双位数的日期在前面添加 0 来补足)
parseDate = d3.timeParse("%Y-%m-%d")
Insert cell
// 读取数据,并将字符串解析为相应的数据类型
data = d3.csvParse(await FileAttachment("aapl-2.csv").text(),
// 每一个数据点(对应于 csv 表格的一行)都会调用以下处理函数,对其进行转换
d => {
// 使用前面 ☝️ 创建的时间解释器,将数据点的属性 d["Date"] 其值是字符串,转换为 Date 时间对象
const date = parseDate(d["Date"]);
return {
date,
// 将其他属性值(字符串)转换为数字
high: +d["High"],
low: +d["Low"],
open: +d["Open"],
close: +d["Close"]
};
}).slice(-90)
Insert cell
d3 = require("d3@6")
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