|
D3(Data-Driven Documents)官方定位:**The JavaScript library for bespoke data visualization**——用于定制数据可视化的 JavaScript 库,以无与伦比的灵活性创建自定义动态可视化。
v7.9.0(2023 年发布)是 7.x 系列的最新稳定版,相对早期版本的主要变化:
- **ESM 优先**:官方推荐以 ES 模块方式引入;同时仍提供 UMD 包(`dist/d3.min.js`),普通 script 标签加载后得到全局 `d3`
- **模块化**:d3 是 30 余个子模块(d3-selection、d3-scale、d3-shape、d3-force……)的合集,可整包引入也可按需安装
- **API 现代化**:`d3.csv/d3.json` 等 fetch 系列返回 Promise;数据连接新增 `selection.join()` 简写 enter/update/exit
- **时间处理推荐 UTC**:`d3.scaleUtc` 替代本地时区的 `d3.scaleTime`,行为可预期
本页示例均来自 d3-7.9.0 自带 `docs/` 目录的官方文档示例(已适配为可直接运行的写法),页面内嵌的演示由本站点内置的 `d3.790.js`(v7.9.0 UMD)驱动。
|
|
官网与下载:
```text
官网
https://d3js.org/
历史版本下载(换成想要的版本号)
https://registry.npmjs.org/d3/-/d3-7.9.0.tgz
```
本页直接引入站点内置的 7.9.0 UMD 包:
```html
<script type="text/javascript" src="/static/js/d3.790.js"></script>
```
npm / yarn 方式:
```bash
npm i d3@7.9.0
# 或 yarn add d3@7.9.0
```
```js
import * as d3 from "d3"; // 整包
import { select, scaleLinear } from "d3"; // 按需
import { mean, median } from "d3-array"; // 子包
```
|
|
官方推荐 ESM + CDN 方式(`type="module"` 中 import):
```html
<div id="container"></div>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
// ……此处写 d3 代码……
</script>
```
传统 UMD 方式(普通 script 标签,得到全局 `d3`):
```html
<div id="container"></div>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script>
// ……此处写 d3 代码,直接使用全局 d3……
</script>
```
二者 API 完全一致;本页演示采用 UMD 全局 `d3` 写法。
|
|
出处:docs/getting-started.md。一张图的骨架 = 边距约定 + 两个比例尺 + 两个 `.call(axis)`,六步完成:
```js
// 1. 声明图表尺寸与边距(D3 经典边距约定)
const width = 640, height = 400;
const marginTop = 20, marginRight = 20, marginBottom = 30, marginLeft = 40;
// 2. 声明 x(水平方向)比例尺:UTC 时间
const x = d3.scaleUtc()
.domain([new Date("2023-01-01"), new Date("2024-01-01")])
.range([marginLeft, width - marginRight]);
// 3. 声明 y(垂直方向)比例尺:线性
const y = d3.scaleLinear()
.domain([0, 100])
.range([height - marginBottom, marginTop]);
// 4. 创建 SVG 容器
const svg = d3.select("#container").append("svg")
.attr("width", width)
.attr("height", height);
// 5. 添加 x 轴
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));
// 6. 添加 y 轴
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y));
```
要点:`scaleUtc` 处理时间、`scaleLinear` 处理数值;`append("g")` 分组后用 `call` 把坐标轴生成器应用到分组上。
|
|
出处:docs/d3-selection/joining.md。嵌套 data join——外层绑定行数组,内层用 `d => d` 把每行数组再绑定到 td;`.join("tr")` 是 enter+update+exit 的简写:
```js
const matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
d3.select("body")
.append("table")
.selectAll("tr")
.data(matrix)
.join("tr")
.selectAll("td")
.data(d => d)
.join("td")
.text(d => d);
```
|
|
`enter().append()` 创建缺失元素,`exit().remove()` 移除多余元素;给 `data()` 传 key 函数(`d => d`)可按值追踪元素。点击上面按钮观察增删效果。
```js
const div = d3.select("body")
.selectAll("div")
.data([4, 8, 15, 16, 23, 42])
.enter().append("div")
.text(d => d);
// 更新:用 key 函数重绑后
// div = div.data([1, 2, 4, 8, 16, 32], d => d);
// div.enter().append("div").text(d => d);
// div.exit().remove();
```
链式缩进约定(docs/d3-selection/selecting.md):返回**新**选择集的方法缩进两格(如 selectAll/data/enter),返回**原**选择集的方法缩进四格(如 attr/text)。
|
|
出处:docs/d3-scale/linear.md。比例尺就是"定义域 domain → 值域 range"的映射函数;数值值域可用 `invert` 反查(交互拾取场景)。
```js
const x = d3.scaleLinear([10, 130], [0, 960]);
x(20); // 80
x(50); // 320
x.invert(80); // 20
// 值域也可以是颜色——线性插值混色:
d3.scaleLinear([10, 100], ["brown", "steelblue"])(50);
```
|
|
出处:docs/d3-scale/band.md。柱状图标配——`x(d.category)` 给柱子左缘,`x.bandwidth()` 给柱宽,`paddingInner/paddingOuter` 控制间隙比例:
```js
const x = d3.scaleBand(["a", "b", "c"], [0, 960]).paddingInner(0.1);
x("a"); // 0
x("b"); // 320
x("c"); // 640
x("d"); // undefined(domain 外的值)
x.bandwidth(); // 每个档位的宽度(画柱子的宽)
x.step(); // 档距 = bandwidth + 间距
```
|
|
出处:docs/d3-scale/time.md。`scaleTime` 用本地时区、`scaleUtc` 用 UTC(官方推荐 UTC,行为可预期);`ticks` 自动产生"整点"等可读刻度,也可按时间间隔定制。
```js
const x = d3.scaleUtc([new Date("2000-01-01"), new Date("2000-01-02")], [0, 960]);
x(new Date("2000-01-01T05:00Z")); // 200
x.invert(200); // 2000-01-01T05:00Z
x.ticks(10); // 自动产生可读刻度
x.ticks(d3.utcMinute.every(15)); // 每 15 分钟一个刻度
```
|
|
出处:docs/d3-scale/ordinal.md。离散 → 离散映射,常用于色板;不显式设 domain 时会"隐式推断"(官方警示:建议显式 domain 保证确定性);`unknown` 设置未知值兜底。
```js
const color = d3.scaleOrdinal(["a", "b", "c"], ["red", "green", "blue"]);
color("a"); // "red"
// 用内置色板 + 未知值兜底:
const color2 = d3.scaleOrdinal(["a", "b", "c"], d3.schemeTableau10).unknown(null);
color2("d"); // null
```
|
|
出处:docs/d3-axis.md。四种朝向:`axisTop / axisRight / axisBottom / axisLeft(scale)`——名称指刻度文字的朝向,轴线起点默认在 `[0,0]`,用 `transform` 平移到位后 `call` 渲染:
```js
const gx = svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));
// 比例尺变化后可平滑更新(详见右侧演示):
gx.transition()
.duration(750)
.call(d3.axisBottom(x));
```
|
|
出处:docs/d3-axis.md。`ticks(数量, 格式)` 把参数传给比例尺的 ticks/tickFormat;`"s"` 为 SI 前缀格式(10k 等);时间轴可传 `d3.timeMinute.every(15)`。
```js
axis.ticks(20, "s"); // 约 20 个刻度,SI 前缀格式
axis.ticks(d3.timeMinute.every(15)); // 时间轴每 15 分钟一个刻度
axis.tickFormat(d3.format(".0%")); // 百分比格式
axis.tickValues([1, 3, 5, 9]); // 指定刻度值
```
|
|
出处:docs/d3-scale/band.md、docs/d3-axis.md(经典字母频率柱状图)。核心要点:
- `scaleBand` 负责 x:domain 是字母数组,`x(d.letter)` 给左缘、`x.bandwidth()` 给柱宽、`padding(0.1)` 设间隙
- `scaleLinear` 负责 y:domain 到 `d3.max`,`.nice()` 把刻度取整更美观
- y 轴 `ticks(10, "%")` 以百分比格式化刻度
```js
const x = d3.scaleBand()
.domain(data.map(d => d.letter))
.range([marginLeft, width - marginRight])
.padding(0.1);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.frequency)]).nice()
.range([height - marginBottom, marginTop]);
svg.append("g")
.attr("fill", "steelblue")
.selectAll("rect")
.data(data)
.join("rect")
.attr("x", d => x(d.letter))
.attr("y", d => y(d.frequency))
.attr("height", d => y(0) - y(d.frequency))
.attr("width", x.bandwidth());
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y).ticks(10, "%"));
```
|
|
出处:docs/d3-shape/line.md、area.md。`d3.line()` / `d3.area()` 返回的**生成器**把数据数组转成 path 的 `d` 字符串:
```js
const line = d3.line()
.x((d) => x(d.Date))
.y((d) => y(d.Close));
svg.append("path")
.attr("d", line(data))
.attr("stroke", "currentColor")
.attr("fill", "none");
```
```js
const area = d3.area()
.x((d) => x(d.Date))
.y0(y(0)) // 基线:通常固定为零线
.y1((d) => y(d.Close)); // 顶线
svg.append("path").attr("d", area(data));
```
要点:面积 = 顶线 `(x, y1)` + 基线 `(x0, y0)`,水平面积图只需 `x / y0 / y1` 三个访问器;折线可配 `.defined(...)` 处理缺失数据、`.curve(d3.curveStep)` 更换插值方式。
|
|
出处:docs/d3-shape/pie.md、arc.md。分工:**pie 只算角度不画图**,**arc 把角度对象转成 path**;弧以原点为圆心,靠 transform 移到画布中心。
```js
const data = [1, 1, 2, 3, 5, 8, 13, 21];
const pie = d3.pie(); // 对象数据时用: d3.pie().value((d) => d.value)
const arcs = pie(data); // 每项含 {data, value, index, startAngle, endAngle, padAngle}
```
```js
const arc = d3.arc()
.innerRadius(80) // 内半径 > 0 即成圆环;为 0 是饼图
.outerRadius(180)
.cornerRadius(8) // 扇形圆角
.padAngle(0.03); // 扇区间隙
// arc.centroid(d) 返回分片中心点,常用于定位标签
```
```js
// 也可以一次性传角度对象给 arc():
svg.append("path")
.attr("transform", "translate(100,100)")
.attr("d", d3.arc()({
innerRadius: 100,
outerRadius: 200,
startAngle: -Math.PI / 2,
endAngle: Math.PI / 2
}));
```
|
|
出处:docs/d3-transition.md(按官方 API 组合的可运行示例)。绿色 = enter 进入、蓝色 = update 更新、红色 = exit 退出。join 的 enter/update/exit 各自接 transition 即完成三态动画:
```js
svg.selectAll("rect")
.data(data, d => d.name) // key:按名字追踪
.join(
enter => enter.append("rect") /* …初始属性… */
.call(enter => enter.transition().duration(750)
.attr("y", d => y(d.value))
.attr("height", d => y(0) - y(d.value))),
update => update
.call(update => update.transition().duration(750)
.attr("y", d => y(d.value))
.attr("height", d => y(0) - y(d.value))),
exit => exit
.call(exit => exit.transition().duration(750)
.attr("height", 0).attr("y", y(0))
.remove())
);
```
|
|
出处:docs/d3-transition.md、timing.md。最小过渡:
```js
d3.select("body")
.transition()
.style("background-color", "red");
```
时序参数:
```js
transition.delay((d, i) => i * 10); // 按索引错峰(交错动画)
transition.duration(750); // 毫秒
transition.ease(d3.easeCubic); // 缓动函数,默认 easeCubic
```
常用缓动家族(docs/d3-ease.md),每个家族都有 In / Out / InOut 三变体(如 `d3.easeCubicInOut`):
| 名称 | 效果 |
| --- | --- |
| easeLinear | 线性,速度不变 |
| easePoly | 多项式(可设 exponent) |
| easeQuad / easeCubic | 二次 / 三次(默认) |
| easeSin / easeExp | 正弦 / 指数 |
| easeCircle | 圆形加速进入 |
| easeElastic | 有弹性(可设 amplitude/period) |
| easeBack | 先回退再到位(可设 overshoot) |
| easeBounce | 像皮球落地反复弹跳 |
|
|
在图表区域滚轮缩放、按住拖动平移;橙色圆点可拖拽
出处:docs/d3-zoom.md。zoom 状态存在元素上;`event.transform`(含 k/x/y)直接当 transform 用,或用 `rescaleX/rescaleY` 重建比例尺驱动坐标轴。
```js
const zoom = d3.zoom()
.scaleExtent([1, 8]) // 缩放范围
.on("zoom", zoomed);
svg.call(zoom);
function zoomed(event) {
g.attr("transform", event.transform); // 内容组整体变换
// 或重映射比例尺画轴:
// gx.call(xAxis.scale(event.transform.rescaleX(x)));
}
// 程序化复位:
svg.transition().duration(750).call(zoom.transform, d3.zoomIdentity);
```
|
|
出处:docs/d3-drag.md。拖拽三事件 start / drag / end;`event.x/y` 是相对 container 的指针坐标,写回属性即可驱动渲染(左侧演示中的橙色圆点即此模式):
```js
d3.selectAll("circle.node")
.call(d3.drag()
.on("start", function (event) {
d3.select(this).raise(); // 提到最上层
})
.on("drag", function (event, d) {
d.x = event.x; d.y = event.y; // 写回 datum
d3.select(this).attr("cx", d.x).attr("cy", d.y);
}));
```
|
|
出处:docs/d3-force/simulation.md、many-body.md、link.md、collide.md。注册四种力:
```js
const simulation = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-100)) // 排斥力(负值)
.force("link", d3.forceLink(links)) // 链接按 source/target 找节点
.force("collide", d3.forceCollide((d) => d.r)) // 碰撞力
.force("center", d3.forceCenter(width / 2, height / 2));
```
模拟器启动后每个 tick 自动运行;渲染方只需在 tick 回调里把 `node.x/y` 写回 DOM;`forceLink(links).id(d => d.id)` 指定用哪个字段解析链接端点:
```js
simulation.on("tick", () => {
link.attr("x1", d => d.source.x).attr("y1", d => d.source.y)
.attr("x2", d => d.target.x).attr("y2", d => d.target.y);
node.attr("cx", d => d.x).attr("cy", d => d.y);
});
```
注意(官方警告):`forceSimulation` 是 impure 的——它会**直接改写**传入的 nodes 数组,给每个节点添加 `x/y/vx/vy` 属性。
|
|
出处:docs/d3-hierarchy/hierarchy.md、tree.md。三步:包装 → 布局 → 渲染:
```js
// ① d3.hierarchy(data) 把嵌套 JSON 包装成节点树
const root = d3.hierarchy(data);
// ② d3.tree().size([w, h])(root) 做 Reingold–Tilford 布局,之后每个 node 有 x, y
d3.tree().size([600, 360])(root);
// ③ root.links() 给连线数据,root.descendants() 给节点数据
g.selectAll("line").data(root.links()).join("line") /* … */;
g.selectAll("circle").data(root.descendants()).join("circle") /* … */;
```
同族布局(docs/d3-hierarchy/ 目录):
- `d3.cluster`:所有叶子对齐到同一深度的树
- `d3.treemap`:矩形树图,需先 `root.sum(...)`,布局后得 `x0/y0/x1/y1` 矩形
- `d3.pack`:圆形嵌套打包图;`d3.partition`:分区图(常用于旭日图)
|
|
出处:docs/d3-fetch.md、d3-dsv.md。fetch 系列返回 Promise;CSV 字段默认都是字符串,行转换函数(或 `d3.autoType`)负责类型化,返回 null/undefined 可过滤行。上面演示加载本站 `/static/js/data/cars.csv` 绘制柱状图。
```js
const data = await d3.csv("example.csv"); // [{"Hello": "world"}, …]
const json = await d3.json("example.json");
// 行转换函数:
const rows = await d3.dsv(",", "example.csv", (d) => {
return {
year: new Date(+d.Year, 0, 1), // "Year" 列 → Date
make: d.Make,
model: d.Model,
length: +d.Length // "Length" 列 → number
};
});
// 自动类型推断(演示所用):
d3.csv("example.csv", d3.autoType);
```
|
|
出处:docs/d3-array/(summarize.md、group.md、bin.md)。最常用的聚合与分组工具:
```js
d3.extent([3, 2, 1, 1, 6, 2, 4]); // [1, 6](最小/最大值一次拿到)
d3.extent(alphabet, (d) => d.frequency); // [0.00074, 0.12702]
// rollup:分组 + 聚合 → InternMap
const speciesCount = d3.rollup(penguins, (D) => D.length, (d) => d.species);
speciesCount.get("Adelie"); // 152
// rollups:数组版;group:只分组不聚合
d3.rollups(penguins, (D) => D.length, (d) => d.species); // [["Adelie", 152], …]
d3.group(penguins, (d) => d.species); // 键 → 原行数组
// bin:直方图分箱,每个 bin 附带 x0/x1 区间端点,可直接画直方图
const bins = d3.bin().value((d) => d.culmen_length_mm)(penguins);
```
|
|
出处:docs/d3-format.md、d3-time-format.md。
```js
// d3-format:数值格式化(坐标轴 tickFormat 常配)
const f = d3.format(".1f");
f(0.1 * 3); // "0.3"
d3.format(".0%")(0.123); // "12%"
d3.format("($.2f")(-3.5); // 本地化货币
// d3-time-format:Date ↔ 字符串
const format = d3.timeFormat("%b %d"); // Date → "Mar 29"
const parse = d3.timeParse("%b %d"); // 字符串 → Date
d3.isoParse("2023-05-31T18:17:36.788Z"); // ISO 字符串 → Date
```
|
|
出处:docs/d3-scale-chromatic.md。三类内置色带:
- **分类色板**:`schemeCategory10`、`schemeObservable10`、`schemeTableau10`、`schemeAccent`、`schemeDark2`、`schemePaired`、`schemePastel1/2`、`schemeSet1/2/3`
- **顺序色(连续插值)**:`interpolateViridis`、`interpolateInferno`、`interpolateMagma`、`interpolatePlasma`、`interpolateCividis`、`interpolateTurbo`、`interpolateRainbow`,及 ColorBrewer 单/多色系 `interpolateBlues/Greens/Oranges/Purples/Reds/BuGn/GnBu/OrRd/PuBu/YlGn/YlOrBr` 等(均有 `scheme*` 离散数组版,如 `schemeBlues[5]`)
- **发散色**:`interpolateBrBG/PRGn/PiYG/PuOr/RdBu/RdGy/RdYlBu/RdYlGn/Spectral`(同名 `scheme*` 版)
用法:
```js
// 分类:直接作为 scaleOrdinal 的 range
const color = d3.scaleOrdinal(d3.schemeAccent);
// 连续:配合 scaleSequential,用 domain 映射数值到颜色
const heat = d3.scaleSequential(d3.interpolateViridis).domain([0, 100]);
heat(50); // 中间色
```
|
D3 官网(v7.9 文档即本页示例出处)
D3 Gallery(官方图表示例库)