D3.js 完整详细使用教程(从入门到实战)
一、D3.js 基础介绍
1. 什么是 D3.js
D3(Data-Driven Documents,数据驱动文档)是基于SVG、Canvas、HTML的数据可视化 JS 库,核心思想:绑定数据 → 操作 DOM,把数据映射成图形元素(柱状图、折线图、地图、散点图等)。
2. 优势
- 高度自定义,无封装死图表,可自由控制每一个图形细节
- 内置强大比例尺、坐标轴、动画、布局、插值、地理计算工具
- 兼容现代浏览器,轻量无依赖
- 生态丰富:图表、力导向图、热力图、树形图、地图可视化
3. 引入 D3
方式 1:CDN(推荐,快速上手)
<!-- D3 v7 稳定最新版 --> <script src="https://d3js.org/d3.v7.min.js"></script>方式 2:npm 项目
npm install d3import * as d3 from 'd3'二、核心核心概念:数据绑定 enter/update/exit(D3 灵魂)
D3 通过select/selectAll选中 DOM,用.data()将数组数据和 DOM 元素一一绑定,分为三种状态:
- enter:数据比 DOM 多 → 新增元素(画新图形)
- update:数据和 DOM 数量相等 → 更新现有元素(修改位置、颜色、尺寸)
- exit:DOM 比数据多 → 删除多余元素
基础绑定示例
<svg width="400" height="200"></svg> <script src="https://d3js.org/d3.v7.min.js"></script> <script> const data = [30, 80, 50, 120, 60]; const svg = d3.select("svg"); // 1. 绑定数据,获取三组状态 const rects = svg.selectAll("rect") .data(data); // exit:多余矩形删除 rects.exit().remove(); // update:已有矩形更新属性 rects.attr("x", (d, i) => i * 70) .attr("y", d => 200 - d) .attr("width", 60) .attr("height", d => d) .fill("#4285f4"); // enter:新增缺失矩形 rects.enter() .append("rect") .attr("x", (d, i) => i * 70) .attr("y", d => 200 - d) .attr("width", 60) .attr("height", d => d) .fill("#4285f4"); </script>三、D3 基础 API 分类详解
1. 选择器 Select(操作 DOM)
// 单选 d3.select("#box") d3.select(".item") d3.select("svg") // 多选(返回集合) d3.selectAll("rect") d3.selectAll(".circle") // 链式修改属性/样式/文本 d3.select("rect") .attr("width", 100) // SVG属性 .style("fill", "red") // css样式 .text("文字") // 文本内容 .html("<b>富文本</b>")2. 比例尺 Scale(可视化核心:数据→像素映射)
原始数据范围(域domain)→ 画布像素范围(范围range)
(1)线性比例尺 scaleLinear(柱状图 / 折线图)
数值均匀映射
// 数据0~200 映射到画布高度0~180 const yScale = d3.scaleLinear() .domain([0, 200]) // 原始数据区间 .range([180, 0]); // 画布区间(svg y轴向下,倒置) console.log(yScale(100)); // 90(2)序数比例尺 scaleOrdinal(分类颜色、分类 X 轴)
离散分类数据映射
const colorScale = d3.scaleOrdinal() .domain(["苹果", "香蕉", "橙子"]) .range(["red", "yellow", "orange"]); colorScale("香蕉"); // yellow(3)band 分段比例尺 scaleBand(柱状图 X 轴均分)
自动均分宽度、设置内边距
const xData = ["1月","2月","3月","4月"] const xScale = d3.scaleBand() .domain(xData) .range([20, 380]) .padding(0.1); // 柱子间距 xScale.bandWidth() // 单根柱子宽度其他常用比例尺
scaleLog:对数比例尺(跨度极大数据)scaleTime:时间比例尺(时间轴折线图)scalePow:幂等比例尺scaleQuantize:分阶比例尺(热力图分级)
3. 坐标轴 Axis(自动生成 x/y 轴)
依赖比例尺,自动生成刻度、轴线、文字
// 生成x轴渲染函数 const xAxis = d3.axisBottom(xScale); // 底部x轴,刻度文字朝下 const yAxis = d3.axisLeft(yScale); // 左侧y轴 // 在svg中渲染坐标轴 svg.append("g") .attr("transform", "translate(0, 200)") // 下移放到底部 .call(xAxis); svg.append("g") .attr("transform", "translate(20, 0)") .call(yAxis);transform="translate(x,y)":SVG 平移,解决坐标轴偏移问题
4. 动画 Transition
.transition()实现平滑过渡
rects.enter() .append("rect") .attr("height", 0) // 初始高度0 .transition() // 开启动画 .duration(800) // 动画时长ms .delay((d,i)=>i*100) // 每个柱子延迟错开 .attr("height", d=>d)5. 数据处理工具 d3-array
const data = [10,50,30,90]; d3.max(data) // 最大值 90 d3.min(data) // 最小值 10 d3.sum(data) // 总和 d3.mean(data) // 平均值 d3.range(0,10) // [0,1,2...9]四、实战 1:完整柱状图(可直接复制运行)
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>D3 柱状图完整示例</title> <style> svg { border: 1px solid #eee; } </style> </head> <body> <svg width="500" height="300"></svg> <script src="https://d3js.org/d3.v7.min.js"></script> <script> // 1. 数据源 const dataset = [ {month:"1月",value:80}, {month:"2月",value:120}, {month:"3月",value:60}, {month:"4月",value:150}, {month:"5月",value:90} ] const width = 500; const height = 300; const margin = {top:20, right:20, bottom:40, left:40}; // 边距 // 画布 = 总宽高 - 边距 const innerW = width - margin.left - margin.right; const innerH = height - margin.top - margin.bottom; // 2. 创建svg画布,整体右移下移留出边距 const svg = d3.select("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", `translate(${margin.left},${margin.top})`); // 3. 创建比例尺 // X轴 band比例尺 const xScale = d3.scaleBand() .domain(dataset.map(d=>d.month)) .range([0, innerW]) .padding(0.15); // Y轴线性比例尺 const yScale = d3.scaleLinear() .domain([0, d3.max(dataset, d=>d.value)]) .range([innerH, 0]); // 4. 坐标轴 const xAxis = d3.axisBottom(xScale); const yAxis = d3.axisLeft(yScale); // 渲染X轴(放在画布底部) svg.append("g") .attr("transform", `translate(0,${innerH})`) .call(xAxis); // 渲染Y轴 svg.append("g") .call(yAxis); // 5. 绘制柱状图 enter/update/exit const bars = svg.selectAll("rect") .data(dataset); bars.exit().remove(); bars.attr("x", d=>xScale(d.month)) .attr("y", d=>yScale(d.value)) .attr("width", xScale.bandwidth()) .attr("height", d=>innerH - yScale(d.value)) .fill("#3498db"); bars.enter() .append("rect") .attr("x", d=>xScale(d.month)) .attr("width", xScale.bandwidth()) .attr("y", innerH) // 初始在底部 .transition().duration(1000) .attr("y", d=>yScale(d.value)) .attr("height", d=>innerH - yScale(d.value)) .fill("#3498db"); </script> </body> </html>五、实战 2:折线图(时间轴示例)
<svg width="600" height="300"></svg> <script src="https://d3js.org/d3.v7.min.js"></script> <script> const data = [ {date:new Date("2025-01-01"), val:20}, {date:new Date("2025-02-01"), val:45}, {date:new Date("2025-03-01"), val:32}, {date:new Date("2025-04-01"), val:60}, {date:new Date("2025-05-01"), val:48} ] const margin = {t:20, r:20, b:30, l:40}; const w = 600 - margin.l - margin.r; const h = 300 - margin.t - margin.b; const svg = d3.select("svg") .append("g") .attr("transform",`translate(${margin.l},${margin.t})`); // 时间比例尺 const x = d3.scaleTime() .domain(d3.extent(data, d=>d.date)) .range([0,w]); const y = d3.scaleLinear() .domain([0, d3.max(data, d=>d.val)]) .range([h,0]); // 折线生成器 line const line = d3.line() .x(d=>x(d.date)) .y(d=>y(d.val)) .curve(d3.curveMonotoneX); // 平滑曲线 // 绘制折线path svg.append("path") .datum(data) // 单组数据绑定用datum .attr("fill","none") .attr("stroke","#e74c3c") .attr("stroke-width",2) .attr("d", line); // 坐标轴 svg.append("g").attr("transform",`translate(0,${h})`).call(d3.axisBottom(x)); svg.append("g").call(d3.axisLeft(y)); // 圆点标记 svg.selectAll("circle") .data(data) .enter() .append("circle") .attr("cx",d=>x(d.date)) .attr("cy",d=>y(d.val)) .attr("r",4) .fill("#e74c3c"); </script>六、常用布局 Layout(复杂图表)
D3 内置布局函数,专门处理复杂图形数据转换,只需要传入数据,返回图形坐标:
- d3.pie():饼图 / 环形图布局
- d3.forceSimulation():力导向网络图
- d3.tree() / d3.hierarchy:树形图、组织架构图
- d3.arc():饼图扇形生成器
- d3.map/geo:地图地理投影
饼图极简示例
const pieData = [10,20,30,40]; const pie = d3.pie(); // 饼图布局 const arcs = pie(pieData); // 转换为扇形坐标数据 const arc = d3.arc() .innerRadius(0) // 内半径,大于0就是环形图 .outerRadius(100); svg.selectAll("path") .data(arcs) .enter() .append("path") .attr("d", arc) .fill((d,i)=>d3.schemeSet2[i]);七、交互:鼠标事件、tooltip
1. 鼠标监听
bars.on("mouseover", function(event, d){ d3.select(this).style("fill","#ff6600"); }) .on("mouseout", function(){ d3.select(this).style("fill","#3498db"); }) .on("click", (e,d)=>{ console.log("点击数据", d) })2. Tooltip 悬浮提示
.tooltip { position:absolute; padding:6px 10px; background:#222; color:#fff; border-radius:4px; pointer-events:none; opacity:0; }<div class="tooltip"></div>const tooltip = d3.select(".tooltip"); bars.on("mouseover", (e,d)=>{ tooltip.html(`数值:${d.value}`) .style("left", e.pageX + "px") .style("top", e.pageY - 20 + "px") .style("opacity",1); }).on("mouseout", ()=>{ tooltip.style("opacity",0) })八、D3 分层开发规范(工程化推荐)
- 边距模式 margin convention:统一预留上下左右边距,避免坐标轴被截断(上面示例全部使用)
- 分离数据、比例尺、坐标轴、图形渲染逻辑
- 图表封装成函数,支持传入配置(宽高、颜色、数据)
- 数据更新只调用一次渲染函数,依赖 enter/update/exit 自动 diff
- 样式分离,尽量用 class 代替 inline style
九、常见踩坑问题
- SVG y 轴向下,比例尺 range 必须倒置 [maxH, 0],否则柱子倒置
.data()绑定数组,单条数据用.datum()- 坐标轴需要
.call(axis)才能渲染 - 折线 path 使用
fill:none,否则会填充闭合区域 - 动画 transition 只能写在修改属性前
- 页面缩放模糊:svg 设置宽高,不要用 css 拉伸
十、官方资源 & 拓展学习
- 官方文档:https://d3js.org/
- 官方示例图库:https://observablehq.com/@d3/gallery(海量可编辑可视化案例)
- D3 API 完整手册:https://github.com/d3/d3/blob/main/API.md
- 进阶方向:地图可视化、3D 结合、Canvas 渲染大数据、交互式大屏
十一、扩展练习路线
- 基础:柱状图 → 横向柱状图 → 折线图 → 散点图
- 中级:饼图 / 环形图、堆叠柱状图、面积图、Tooltip 交互
- 高级:树形图、力导向网络图、中国地图、热力图、动态实时更新图表
