文章目录
1. 概述
前面对d3.js的使用进行了比较详细的介绍,介绍了如何创建svg画布,如何添加比例尺及坐标等,详细的内容可以看看上篇文章:【数据可视化】【1】手把手教你使用d3.js绘制折线图。
有了上面的学习,相信大家对d3.js的使用会有一个更深的认识,本文将在上篇文章的基础上继续探究d3.js在绘制图表方面的使用。其中包括同一图表多折线图的绘制与数据更新,以及一次绘制多个图表,这些内容在数据可视化方面很有意思,可以将代码里面的请求接口换成自己实际的数据接口,用以展示数据。
2. 创建图表
创建图表与之前所述的没有多少区别,主要步骤如下:
- 创建画布,设置显示区域,设置边界
- 添加x、y轴,设置长度映射
- 设置x、y轴位置
- 设置坐标轴比例尺
- 绑定数据并展示,设置线条参数
// 创建初始图表
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 400 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x轴和y轴
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
// 添加x轴
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// 添加y轴
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// 数据绑定
var line = d3.line()
.x((d, i) => x(i))
.y(d => y(d));
3. 同一坐标轴绘制多条折线
同一坐标轴上可以绘制多条折线,折线之间互不影响,只需要正确的绑定数据即可。
3.1 绘制图表
绘制过程如上所示,无多少差异。主要区别在于绑定多个数据,这里使用了
d3.schemeCategory10
函数,该函数可以创建十种不同的颜色,主要用于折线之间的颜色区分,当然也可以使用其他方式作为线条区分,比如使用不同的线条风格,都是可以的,具体可以参考d3.js的官方文档。
// 添加多条线
var paths = svg.selectAll(".line")
.data(dataset)
.enter()
.append("path")
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", (d, i) => d3.schemeCategory10[i]) // 使用不同颜色
.attr("d", line);
3.2 更新数据
数据更新有一点需要注意的是,必须保证所有的线条都是在同一个坐标轴上进行绘制,否则会显示不正常等问题。对于不同的数据其长度和元素值的大小也是不一样的,因此这里在保证坐标轴相同的条件下,主要的改动就是更新坐标轴的比例尺,取数据最长的长度及数据最大值作为比例尺的范围上限。
为了后续更新数据方便,将更新操作封装成函数的形式进行调用。
// 更新函数
function updateChart(newDataset) {
// 更新比例尺的域
x.domain([0, newDataset[0].length - 1]);
y.domain([0, d3.max(newDataset.flat())]);
// 更新轴
svg.select(".x.axis")
.transition()
.duration(500)
.call(xAxis);
svg.select(".y.axis")
.transition()
.duration(500)
.call(yAxis);
// 更新线条
paths.data(newDataset)
.transition()
.duration(500)
.attr("d", line);
}
3.3 完整代码
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>D3.js Multiple Lines Chart with Axes (Flexible) Example</title>
<script src="d3.v7.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
// 初始数据
var dataset = [
[10, 20, 30, 40, 50],
[30, 40, 10, 20, 50],
[20, 10, 30, 50, 40]
];
// 创建初始图表
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 400 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x轴和y轴
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
// 添加x轴
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// 添加y轴
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// 数据绑定
var line = d3.line()
.x((d, i) => x(i))
.y(d => y(d));
// 添加多条线
var paths = svg.selectAll(".line")
.data(dataset)
.enter()
.append("path")
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", (d, i) => d3.schemeCategory10[i]) // 使用不同颜色
.attr("d", line);
// 更新函数
function updateChart(newDataset) {
// 更新比例尺的域
x.domain([0, newDataset[0].length - 1]);
y.domain([0, d3.max(newDataset.flat())]);
// 更新轴
svg.select(".x.axis")
.transition()
.duration(500)
.call(xAxis);
svg.select(".y.axis")
.transition()
.duration(500)
.call(yAxis);
// 更新线条
paths.data(newDataset)
.transition()
.duration(500)
.attr("d", line);
}
// 初始渲染
updateChart(dataset);
// 模拟数据更新
setTimeout(function () {
var newDataset = [
[30, 40, 50, 60, 70],
[50, 10, 30, 40, 20],
[10, 30, 20, 40, 50]
];
updateChart(newDataset);
}, 2000);
</script>
</body>
</html>
另一种实现方式,使用独立的多条数据来更新图表:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>D3.js Multiple Lines Chart with Axes Example</title>
<script src="d3.v7.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
// 初始数据
var data1 = [10, 20, 30, 40, 50];
var data2 = [30, 40, 10, 20, 50];
// 创建初始图表
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 400 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x轴和y轴
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
// 添加x轴
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// 添加y轴
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// 数据绑定
var line = d3.line()
.x((d, i) => x(i))
.y(d => y(d));
// 添加第一条线
var path1 = svg.append("path")
.datum(data1)
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", "blue")
.attr("d", line);
// 添加第二条线
var path2 = svg.append("path")
.datum(data2)
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", "green")
.attr("d", line);
// 更新函数
function updateChart(newData1, newData2) {
// 更新比例尺的域
x.domain([0, newData1.length - 1]);
y.domain([0, d3.max([...newData1, ...newData2])]);
// 更新轴
svg.select(".x.axis")
.transition()
.duration(500)
.call(xAxis);
svg.select(".y.axis")
.transition()
.duration(500)
.call(yAxis);
// 更新第一条线
path1.datum(newData1)
.transition()
.duration(500)
.attr("d", line);
// 更新第二条线
path2.datum(newData2)
.transition()
.duration(500)
.attr("d", line);
}
// 初始渲染
updateChart(data1, data2);
// 模拟数据更新
setTimeout(function () {
var newData1 = [30, 40, 50, 60, 70];
var newData2 = [50, 10, 30, 40, 20];
updateChart(newData1, newData2);
}, 2000);
</script>
</body>
</html>
3.4 绘制效果展示
数据更新的时间间隔,可以在上述代码中设置,按照自己的需求更改这个2000的值即可。
setTimeout(function () {
...
}, 2000);
这里放动图不太方便就放两张静态图片吧。(此处仅展示第一中方式的绘制结果)
更新前的数据:
更新后的数据:
4. 同时绘制多个图表
同时绘制多个图表的实现方式与上述单图表绘制方式类似,主要区别就是对多条数据单独创建坐标轴。
4.1 绘制图表
主要步骤为创建初始数据,创建展示容器,设置容器参数。
// 初始数据
var data1 = [10, 20, 30, 40, 50];
var data2 = [30, 40, 10, 20, 50];
var data3 = [20, 10, 30, 50, 40];
// 创建图表容器
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 400 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
// 设置参数
var charts = d3.select("#charts")
.selectAll(".chart")
.data([data1, data2, data3])
.enter()
.append("div")
.attr("class", "chart")
.style("float", "left");
4.2 创建坐标轴及更新数据
在这里坐标轴的创建和数据更新是相互独立的。这里实现的方式主要是通过传入一个参数index用以区分不同的线条数据,必须将数据进行区分,否则绘制的结果会出现问题。
// 模拟数据更新
setTimeout(function () {
...
if (index === 0) {
updateChart(newData1);
} else if (index === 1) {
updateChart(newData2);
} else if (index === 2) {
updateChart(newData3);
}
}, 500);
坐标轴创建及数据更新:
charts.each(function (dataset, index) {
// 创建图表
var svg = d3.select(this)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x轴和y轴
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
// 添加x轴
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// 添加y轴
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// 数据绑定
var line = d3.line()
.x((d, i) => x(i))
.y(d => y(d));
// 添加线
svg.append("path")
.datum(dataset)
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", d3.schemeCategory10[index])
.attr("d", line);
// 更新函数
function updateChart(newData) {
// 更新比例尺的域
x.domain([0, newData.length - 1]);
y.domain([0, d3.max(newData)]);
// 更新轴
svg.select(".x.axis")
.transition()
.duration(500)
.call(xAxis);
svg.select(".y.axis")
.transition()
.duration(500)
.call(yAxis);
// 更新线条
svg.select(".line")
.datum(newData)
.transition()
.duration(500)
.attr("d", line);
}
// 初始渲染
updateChart(dataset);
// 模拟数据更新
setTimeout(function () {
var newData1 = [1, 2, 1, 4, 1];
var newData2 = [2, 3, 2, 3, 3];
var newData3 = [1, 2, 5, 4, 3];
if (index === 0) {
updateChart(newData1);
} else if (index === 1) {
updateChart(newData2);
} else if (index === 2) {
updateChart(newData3);
}
}, 500);
});
4.3 完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>D3.js Multiple Lines with Independent Axes Example</title>
<script src="d3.v7.js"></script>
</head>
<body>
<div id="charts"></div>
<script>
// 初始数据
var data1 = [10, 20, 30, 40, 50];
var data2 = [30, 40, 10, 20, 50];
var data3 = [20, 10, 30, 50, 40];
// 创建图表容器
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 400 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
// 创建多个图表
var charts = d3.select("#charts")
.selectAll(".chart")
.data([data1, data2, data3])
.enter()
.append("div")
.attr("class", "chart")
.style("float", "left");
charts.each(function (dataset, index) {
// 创建图表
var svg = d3.select(this)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x轴和y轴
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
// 添加x轴
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// 添加y轴
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// 数据绑定
var line = d3.line()
.x((d, i) => x(i))
.y(d => y(d));
// 添加线
svg.append("path")
.datum(dataset)
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", d3.schemeCategory10[index])
.attr("d", line);
// 更新函数
function updateChart(newData) {
// 更新比例尺的域
x.domain([0, newData.length - 1]);
y.domain([0, d3.max(newData)]);
// 更新轴
svg.select(".x.axis")
.transition()
.duration(500)
.call(xAxis);
svg.select(".y.axis")
.transition()
.duration(500)
.call(yAxis);
// 更新线条
svg.select(".line")
.datum(newData)
.transition()
.duration(500)
.attr("d", line);
}
// 初始渲染
updateChart(dataset);
// 模拟数据更新
setTimeout(function () {
var newData1 = [1, 2, 1, 4, 1];
var newData2 = [2, 3, 2, 3, 3];
var newData3 = [1, 2, 5, 4, 3];
if (index === 0) {
updateChart(newData1);
} else if (index === 1) {
updateChart(newData2);
} else if (index === 2) {
updateChart(newData3);
}
}, 500);
});
</script>
</body>
</html>
4.4 绘制效果展示
数据更新前:
数据更新后:
5. 总结
本文直接给出了可运行的代码示例,有了上篇博文的介绍,相信大家基本掌握使用d3.js绘制简单折线图了。因个人忙于其他事情,没有对代码进行详细的讲解,有疑问的读者朋友可以自行百度或者查阅官方文档,同时也欢迎大家留言交流。