【d3.js入门】基本折线图动画

前面写了一篇 【d3.js入门】基本折线图,这次我们在这个折线图基础上添加一些动画效果。

【d3.js入门】基本折线图动画

好,废话多说,我们又画一遍折线图。

折线图

// 创建一个div元素,并将其添加到文档的body中
let dom = document.createElement('div');
document.body.appendChild(dom);
// 定义数据集
let dataset = [
{ name: "苹果", value: 50 },
{ name: "橙子", value: 30 },
{ name: "香蕉", value: 70 },
{ name: "核桃", value: 20 },
{ name: "芒果", value: 60 },
{ name: "梨子", value: 100 },
{ name: "菠萝", value: 80 },
{ name: "葡萄", value: 90 },
{ name: "草莓", value: 35 },
{ name: "西瓜", value: 75 },
{ name: "桃子", value: 55 },
{ name: "樱桃", value: 25 }
];
// 定义图表的一些参数
let padding = 30; // 边距
let svgWidth = 600; // SVG图表的宽度
let svgHeight = 400; // SVG图表的高度
// 定义x轴的比例尺
let xScale = d3.scaleBand()
.domain(d3.range(dataset.length)) // x轴的取值范围
.range([padding, svgWidth - padding]) // x轴的位置
.padding(0) // 设置柱子之间的间隙
.paddingInner(1) // 设置柱子内部的间隙
// 定义y轴的比例尺
let yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) { return d.value; })]) // y轴的取值范围
.range([svgHeight - padding, padding]); // y轴的位置
// 创建SVG元素
let svg = d3.select(dom)
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style('border', '1px solid #999999')
// 定义折线的生成器
let line = d3.line()
.x(function (d, i) {
return xScale(i) + xScale.bandwidth() / 2;
})
.y(function (d) {
return yScale(d.value);
})
.curve(d3.curveCardinal); // 设置折线的曲线类型
// 绘制折线
svg.append("path")
.datum(dataset)
.attr("class", "line")
.attr("d", line)
.attr("fill", "none")
.attr("stroke", "#69b3a2")
.attr("stroke-width", "3px");
// 定义x轴和y轴的刻度
let xAxis = d3.axisBottom(xScale)
.tickFormat(function (d, i) {
return dataset[i].name;
})
let yAxis = d3.axisLeft(yScale);
// 绘制x轴和y轴
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + (svgHeight - padding) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);

在这段代码中,我们使用了D3.js这个JavaScript库来生成折线图。首先,我们创建了一个包含SVG元素的div,并将其添加到文档的body中。然后,我们定义了数据集和图表的一些参数,包括边距、SVG图表的宽度和高度等。接着,我们定义了x轴和y轴的比例尺,用来将数据集中的值转化为图表中的坐标位置。

d3.line

  • .x(function (d, i) { return xScale(i) + xScale.bandwidth() / 2; }) 定义了X轴的位置,它将每个数据点的索引值i乘以X轴的比例尺,然后加上X轴的每个条形的宽度的一半,以确保每个数据点能够精确对齐到X轴上的条形中心。
  • .y(function (d) { return yScale(d.value); }) 定义了Y轴的位置,它将每个数据点的值d.value乘以Y轴的比例尺,以确保每个数据点能够精确对齐到Y轴上的刻度线上。
  • .curve(d3.curveCardinal) 定义了折线的曲线形状,这里使用了Cardinal曲线,它是一种平滑的曲线形状,可以更好地展示数据的趋势。
    在绘制折线之前,我们先定义了折线的生成器,它将数据集中的值映射到折线的坐标位置。我们使用了D3.js提供的默认的曲线类型(curveCardinal),它可以使折线更加平滑。

最后,我们绘制了x轴和y轴,并在x轴上显示数据集中的类别名称。这样,我们就可以清楚地看到每个类别的值大小,并了解它们之间的差异和变化趋势。

动画

  • 最后我们添加5个按钮,来执行 ‘更新数据’, ‘正向排序’, ‘反向排序’, ‘添加数据’, ‘删除数据’等操作。
  • 更新数据的时候,我们只需要更新数据集dataset,然后再次调用line函数,就可以更新折线了。
  • 排序的时候,我们需要对数据集dataset进行排序,然后再次调用xScale.domain()函数,更新x轴的定义域,再次调用xAxis函数,更新x轴。
  • 添加数据的时候,我们只需要向数据集dataset中添加一个新的数据,然后再次调用xScale.domain()函数,更新x轴的定义域,再次调用xAxis函数,更新x轴。
  • 删除数据的时候,我们只需要从数据集dataset中删除一个数据,然后再次调用xScale.domain()函数,更新x轴的定义域,再次调用xAxis函数,更新x轴。

以下是对该代码的注释:

let innerHtml = ['更新数据', '正向排序', '反向排序', '添加数据', '删除数据'];
let buts = [];
let butdiv = document.createElement('div');
dom.appendChild(butdiv);
innerHtml.map(item => {
let but = document.createElement('button'); // 创建按钮元素
but.innerHTML = item; // 设置按钮文本
butdiv.appendChild(but); // 添加按钮到DOM中
buts.push(but); // 将按钮元素存储到数组中
})
// 给第一个按钮添加点击事件
buts[0].onclick = function () {
// 更新数据集中每个数据的值
dataset.forEach(function (d) {
d.value = Math.floor(Math.random() * 80) + 20;
});
// 更新折线
svg.selectAll(".line")
.data([dataset])
.transition()
.duration(1000)
.attr("d", line); 
};
// 给第二个按钮添加点击事件
buts[1].onclick = function () {
// 对数据集进行正向排序
dataset.sort(function (a, b) {
return a.value - b.value;
});
xScale.domain(d3.range(dataset.length));
// 更新x轴
svg.select(".x-axis")
.transition()
.duration(1000)
.call(xAxis);
// 更新折线
svg.selectAll(".line")
.data([dataset])
.transition()
.duration(1000)
.attr("d", line);
};
// 给第三个按钮添加点击事件
buts[2].onclick = function () {
// 对数据集进行反向排序
dataset.sort(function (a, b) {
return b.value - a.value;
});
xScale.domain(d3.range(dataset.length));
// 更新x轴
svg.select(".x-axis")
.transition()
.duration(1000)
.call(xAxis);
// 更新折线
svg.selectAll(".line")
.data([dataset])
.transition()
.duration(1000)
.attr("d", line);
}
// 为第四个按钮添加点击事件  
buts[3].onclick = function () {  
// 生成一个随机数  
let newNum = Math.floor(Math.random() * 80) + 20;  
// 将新的数据添加到数据集中  
dataset.push({ name: "新来的", value: newNum });  
// 根据新的数据长度调整比例尺的定义域  
xScale.domain(d3.range(dataset.length));  
// 更新x轴  
svg.select(".x-axis")  
.transition()  
.duration(1000)  
.call(xAxis);  
// 更新折线  
svg.selectAll(".line")  
.data([dataset])  
.transition()  
.duration(1000)  
.attr("d", line);  
}
// 为第五个按钮添加点击事件  
buts[4].onclick = function () {  
// 如果数据集长度小于等于3,则不允许继续删除  
if (dataset.length <= 3) {  
return;  
}  
// 删除最后一个数据点  
dataset.pop();  
// 根据新的数据长度调整比例尺的定义域  
xScale.domain(d3.range(dataset.length));  
// 更新x轴  
svg.select(".x-axis")  
.transition()  
.duration(1000)  
.call(xAxis);  
// 更新折线  
svg.selectAll(".line")  
.data([dataset])  
.transition()  
.duration(1000)  
.attr("d", line);  
}

原文链接:https://juejin.cn/post/7244708523963334693 作者:Data_Adventure

(0)
上一篇 2023年6月17日 上午10:37
下一篇 2023年6月17日 上午10:47

相关推荐

发表回复

登录后才能评论