D3 入门笔记
2026-07-25 23:59:34npm、yarnan安装依赖包
npm i d3 / yarn add d3
import * as d3 from "d3";
3.D3对象、选择集(d3-selection)、操作
引入d3.js后,就可以获得要使用的d3对象了。每次打点调用 d3 提供的函数方法,都会返回一个 d3 对象,这样可以继续调用 d3 的其他函数方法。
创建画布,绘制图形(Draw canvas)
1 // Create chart dimensions
2 // 定义svg图形宽高,以及柱状图间距
3 const svgWidth = 500, svgHeight = 300, barPadding = 14;
4 const margin = {
5 top: 20,
6 right: 40,
7 bottom: 20,
8 left: 40
9 }
10
11 // Draw canvas
12 // 绘制图形
13 const svg = d3.select('.d3-demo')
14 .attr('width', svgWidth)
15 .attr('height', svgHeight);
D3 选择元素提供了两个函数:select 和 selectAll。
select 选中符合条件的第一个元素。
selectAll 选择所有与指定的 selector 匹配的元素。
详情:https://www.d3js.org.cn/document/d3-selection/#installing
选择元素后可以操作元素,操作元素主要分为增加 ( append )、插入 ( insert )、删除元素 ( remove ) 和设置获取元素属性 ( attr, style )。
1 console.log(d3.select('.d3-demo3'));
2 d3.select('.d3-demo3')
3 .style('background-color', '#c3272b')
4 .append('text')
5 .text('D3-demo-1')
6 .style('color', 'red')
7 .attr('x', 20)
8 .attr('y', 20)
9
运行结果:
4. 数据绑定
数据绑定是将数据绑定到 DOM 元素上,D3 中数据绑定提供了 datum() 和data()两个函数,一般都使用data()函数。
1 d3.select("body")
2 .selectAll("p")
3 .data([4, 8, 15, 16, 23, 42])
4 .enter()
5 .append("p")
6 .text(function(d) { return "I’m number " + d + "!"; });
运行结果:
enter和exit操作
数据绑定的时候可能出现 DOM 元素与数据元素个数不匹配的问题,那么 enter 和 exit 就是用来处理这个问题的。enter 操作用来添加新的 DOM 元素,exit 操作用来移除多余的 DOM 元素。
如果数据元素多于 DOM 个数时用 enter,如果数据元素少于 DOM元素,则用 exit。
在数据绑定时候存在三种情形:
数据元素个数多于 DOM 元素个数
数据元素与 DOM 元素个数一样
数据元素个数少于 DOM 元素个数
情形 1:数据元素个数多于 DOM 元素个数
如以下例子,如果文档中p标签的个数少于数组个数(6个),则使用 enter 和 append 操作来补齐 DOM 元素:
d3.select("body")
.selectAll("p")
.data([4, 8, 15, 16, 23, 42])
.enter().append("p")
.text(function(d) { return "I’m number " + d + "!"; });
情形 2:数据元素与 DOM 元素个数一样
如果这种情况再使用 data 来绑定数据,相当于是更新了每个 DOM 元素所对应的数据,此时不需要加入新节点也不需要删除多余的节点:
//update
let p = d3.select("body")
.selectAll("p")
.data([4, 8, 15, 16, 23, 42])
.text(function(d) { return d; });
情形 3:数据元素个数少于 DOM 元素个数
假设p元素的个数多于 6 个,数据元素个数为 6,则:
let p = d3.select("body")
.selectAll("p")
.data([4, 8, 15, 16, 23, 42])
.text(function(d) { return d; });
p.exit().remove() //移除多余的元素
但是在实际应用中,不可能先去统计一下元素个数,因此这三种情形可以一起使用:(在画图表时一般都是直接使用append添加元素,因为画布是从空白开始添加元素的)
// Update 情形2
let p = d3.select("body")
.selectAll("p")
.data([4, 8, 15, 16, 23, 42])
.text(function(d) { return d; });
// Enter 情形1
p.enter().append("p")
.text(function(d) { return d; });
// Exit 情形3
p.exit().remove();
5. 过渡(d3-transition)
D3 支持动画效果,这种动画效果可以通过对样式属性的过渡实现。
比如对元素的背景颜色进行过渡:
d3.select("body").transition()
.style("background-color", "black");
详情:https://www.d3js.org.cn/document/d3-transition/#installing
https://observablehq.com/@d3/learn-d3-animation?collection=@d3/learn-d3
6. 比例尺(d3-scale)
比例尺是 D3 重要概念。
对于可视化来说,比例尺是一个很方便的工具:将抽象的维度数据映射为可视化表示。比例尺这个概念跟地图比例尺的概念是一样的,要一个画布上绘制一幅世界地图,需要按一定的比例将整个世界投射到画布上。
虽然经常使用位置编码定量数据,比如将测量单位米使用像素映射。但是比例尺可以对任何视觉编码进行映射,比如颜色,描边的宽度或者符号的大小。比例尺也可以用来对任意类型的数据进行映射,比如分类数据或离散的数据。
三个要素:定义域、值域、对应法则。
其中 scaleLinear() 对应法则,domain() 是定义域,range() 是值域。
以线性比例尺为例:
let x = d3.scaleLinear()
.domain([10, 130])
.range([0, 960]);
x(20); // 80
x(50); // 320
以线性比例尺为例(数值映射为颜色):
let color = d3.scaleLinear()
.domain([10, 100])
.range(["brown", "steelblue"]);
color(20); // "#9a3439"
color(50); // "#7b5167"
详情:https://www.d3js.org.cn/document/d3-scale/#installing
7. 坐标轴(d3-axis)
坐标轴组件可以将 scales (opens new window)显示为人类友好的刻度标尺参考,减轻了在可视化中的视觉任务。
例:
// Create chart dimensions
// 定义svg图形宽高,以及柱状图间距
const svgWidth = 500, svgHeight = 300, barPadding = 14;
const margin = {
top: 20,
right: 40,
bottom: 20,
left: 40
}
// Draw canvas
// 绘制图形
const svg = d3.select('.d3-demo')
.attr('width', svgWidth)
.attr('height', svgHeight);
// Create scales
// 首先是拿到最大值构建x轴坐标
let xScale = d3.scaleLinear()
.domain([0, d3.max(dataX)])
.range([margin.left, svgWidth - margin.right]);
// 接下来是反转值,用作y轴坐标
let yScale = d3.scaleLinear()
.domain([0, d3.max(dataY) + 20])
.range([svgHeight - margin.bottom, margin.top])
// 横轴的API使用
let x_axis = d3.axisBottom()
.scale(xScale)
// 纵轴的API使用
let y_axis = d3.axisLeft()
.scale(yScale);
svg.append("g")
.attr("transform", `translate(${0}, ${svgHeight - margin.bottom})`)
.call(x_axis);
svg.append("g")
.attr("transform", `translate(${margin.left}, 0)`)
.call(y_axis);
View Code
运行结果:
8. 基本图表
数据来源:使d3-fetch模块中的d3.json()读取json文件的数据
{
"code": 200,
"message": "操作成功",
"data": [
{
"name": 0,
"value": 20
},
{
"name": 1,
"value": 40
},
{
"name": 2,
"value": 100
},
{
"name": 3,
"value": 56
},
{
"name": 4,
"value": 120
},
{
"name": 5,
"value": 180
},
{
"name": 6,
"value": 30
},
{
"name": 7,
"value": 40
},
{
"name": 8,
"value": 40
}
]
}
View Code
折线图
import * as d3 from "d3";
import { defineComponent, reactive, ref, toRefs, h, watch, nextTick } from "vue";
export default defineComponent({
name: "D3Line",
setup(props, context) {
let dataList = [];
let dataX = [];
let dataY = [];
// 获取数据
async function getData() {
const xyData = await d3.json('/dataFiles/demo1.json');
let { data: datas } = xyData;
dataList = datas;
datas.forEach(element => {
dataX.push(element.name);
dataY.push(element.value);
});
}
// D3相关函数
const d3Function = async () =>{
// Access data
await getData();
// Create chart dimensions
// 定义svg图形宽高,以及柱状图间距
const svgWidth = 500, svgHeight = 300, barPadding = 14;
const margin = {
top: 20,
right: 40,
bottom: 20,
left: 40
}
// Draw canvas
// 绘制图形
const svg = d3.select('.d3-demo')
.attr('width', svgWidth)
.attr('height', svgHeight);
// Create scales
// 首先是拿到最大值构建x轴坐标
let xScale = d3.scaleLinear()
.domain([0, d3.max(dataX)])
.range([margin.left, svgWidth - margin.right]);
// 接下来是反转值,用作y轴坐标
let yScale = d3.scaleLinear()
.domain([0, d3.max(dataY) + 20])
.range([svgHeight - margin.bottom, margin.top])
// 横轴的API使用
let x_axis = d3.axisBottom()
.scale(xScale)
// 纵轴的API使用
let y_axis = d3.axisLeft()
.scale(yScale);
// Draw data
const t = (path) => path.transition()
.duration(1000)
.ease(d3.easeLinear)
.attrTween('stroke-dasharray', function() {
const length = this.getTotalLength();
return d3.interpolate(`0, ${length}`, `${length}, ${length}`);
})
let line = d3.line()
.x(function(d) {
return xScale(d.name);
})
.y(function(d) {
return yScale(d.value);
})
.curve(d3.curveCatmullRom.alpha(0.5));
// Draw peripherals
// 在svg中提供了如g元素这样的将多个元素组织在一起的元素
// 由g元素编组在一起的可以设置相同的颜色,可以进行坐标变化等,类似于Vue中的
svg.append("path")
// .attr("transform", `translate(${0}, ${svgHeight - margin.bottom})`)
.attr('d', line(dataList))
.attr('class', 'line-path')
.attr('fill', 'none')
.attr('stroke-dasharray', '0,1')
.attr('stroke', '#c3272b')
.attr('stroke-width', 2)
.call(t)
svg.append("g")
.attr("transform", `translate(${0}, ${svgHeight - margin.bottom})`)
.call(x_axis);
svg.append("g")
.attr("transform", `translate(${margin.left}, 0)`)
.call(y_axis);
}
nextTick(() => {
d3Function();
})
},
});
#d3Demo {
width: 100%;
height: 100%;
color: $test-color;
display: flex;
align-items: center;
background-size: 100% 100%;
justify-content: center;
}
View Code
运行结果:
柱状图
import * as d3 from "d3";
import { defineComponent, reactive, ref, toRefs, h, watch, nextTick } from "vue";
export default defineComponent({
name: "D3Line",
setup(props, context) {
let dataList = [];
let dataX = [];
let dataY = [];
// 获取数据
async function getData() {
const xyData = await d3.json('/dataFiles/demo1.json');
let { data: datas } = xyData;
dataList = datas;
datas.forEach(element => {
dataX.push(element.name);
dataY.push(element.value);
});
}
// D3相关函数
const d3Function = async () =>{
// Access data
await getData();
// Create chart dimensions
// 定义svg图形宽高,以及柱状图间距
const svgWidth = 500, svgHeight = 300, barPadding = 7;
const durationTime = 1000; // 动画时长
const margin = {
top: 20,
right: 40,
bottom: 20,
left: 40
}
// 通过图形计算每个柱状宽度
let barWidth = (svgWidth - margin.left - margin.right) / dataX.length;
// Draw canvas
// 绘制图形
const svg = d3.select('.d3-demo')
.attr('width', svgWidth)
.attr('height', svgHeight);
// Create scales
// 首先是拿到最大值构建x轴坐标
let xScale = d3.scaleBand()
.domain((dataX))
.range([margin.left, svgWidth - margin.right])
// 接下来是反转值,用作y轴坐标
let yScale = d3.scaleLinear()
.domain([0, d3.max(dataY) + 20])
.range([svgHeight - margin.bottom, margin.top])
// y轴值得缩放
let yDataScale = d3.scaleLinear()
.domain([0, d3.max(dataY) + 20])
.range([0, svgHeight - margin.top - margin.bottom])
// 横轴的API使用
let x_axis = d3.axisBottom()
.scale(xScale)
// 纵轴的API使用
let y_axis = d3.axisLeft()
.scale(yScale);
// Draw data
const t = (val) => val.transition()
.duration(durationTime)
.ease(d3.easeLinear)
.attrTween('height', function(d) {
return d3.interpolate('0', yDataScale(d));
})
//
const t1 = (val) => val.transition()
.duration(durationTime)
.ease(d3.easeLinear)
.attrTween('transform', function(d, i) {
return d3.interpolate(`translate(${barWidth * i + margin.left + barPadding}, ${(svgHeight - margin.bottom)})`, `translate(${barWidth * i + margin.left + barPadding}, ${(svgHeight - margin.bottom) - yDataScale(d)})`);
})
// 处理文字动画
const t2 = (val) => val.transition()
.duration(durationTime)
// .delay(1000)
.ease(d3.easeLinear)
.textTween((d) => {
return d3.interpolateRound(0, d);
})
// Draw peripherals
// 在svg中提供了如g元素这样的将多个元素组织在一起的元素
// 由g元素编组在一起的可以设置相同的颜色,可以进行坐标变化等,类似于Vue中的
let bar = svg.selectAll(".bar")
.data(dataY)
// .enter()
// .append("g")
.join("g")
.attr("class", "bar")
.call(t1)
bar.append('rect') // 添加足够数量的矩形
.attr('class', 'rect')
.attr("width", barWidth - barPadding * 2) // 设定宽度
.attr("height", d => yDataScale(d)) // 设定高度
.style("fill", '#c3272b')
.each(function() {
d3.select(this)
.call(t);
})
bar.append('text')
.attr("transform", (d, i) => {
let translate = [barWidth / 2 - barPadding, -2];
return `translate(${translate})`;
})
.attr("text-anchor", "middle")
.style("fill", '#000000')
.each(function() {
d3.select(this)
.call(t2);
})
svg.append("g")
.attr("transform", `translate(${0}, ${svgHeight - margin.bottom})`)
.call(x_axis);
svg.append("g")
.attr("transform", `translate(${margin.left}, 0)`)
.call(y_axis);
}
nextTick(() => {
d3Function();
})
},
});
#d3Demo {
width: 100%;
height: 100%;
color: $test-color;
display: flex;
align-items: center;
background-size: 100% 100%;
justify-content: center;
}
View Code
运行结果:
9. 小结
这篇文章只是介绍了d3的基本操作,官网有很多的知识点需要自行查看。当你看完这篇入门笔记后,能对d3有一个简单的认识,加上对SVG基本图形的了解可以创造出一些基础的图表,加油,加油!!!
最后给一个d3画图表的基本步骤,按这个步骤可以指导你画图思路。上面的实例就是按这个步骤实现的,可以作为参考。
Demo项目开源地址:https://gitee.com/userzp/vue3-d3.git