前面写了一篇 【d3.js入门】基本面积图,这次我们在这个面积图基础上添加一些动画效果。
我们先回忆一下如何画面积图。 这段代码使用D3.js绘制了一个基本面积图,并将图形插入到HTML页面中。以下是对主要代码的解释:
首先,通过以下代码创建一个div元素并将其添加到文档的body中:
let dom = document.createElement('div');
document.body.appendChild(dom);
然后,定义了一个包含12个对象的数据集,每个对象都有一个名称和一个值,表示不同种类水果的数量。这是绘制面积图的数据源:
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 }
];
定义了一些变量,包括图形的内边距、SVG画布的宽度和高度。这些变量将在后面定义比例尺和绘制图形时使用:
let padding = 30;
let svgWidth = 600;
let svgHeight = 400;
使用D3.js的scaleBand
和scaleLinear
函数创建比例尺对象,这些比例尺将数据集中的值映射到实际的像素值。比例尺对象将用于绘制面积图和坐标轴:
let xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([padding, svgWidth - padding])
.padding(0)
.paddingInner(1)
let yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) { return d.value; })])
.range([svgHeight - padding, padding]);
接下来,使用D3.js的area
函数定义了一个面积生成器对象,该对象将用于绘制面积图。该函数将数据集中的x和y值映射到实际的像素坐标:
let area = d3.area()
.x(function (d, i) { return xScale(i) + xScale.bandwidth() / 2; })
.y0(svgHeight - padding)
.y1(function (d) { return yScale(d.value); })
.curve(d3.curveCardinal);
使用D3.js的select
函数选中DOM元素,并使用append
函数向该元素添加SVG元素。然后,添加一个路径元素,并使用datum
函数将数据集与路径元素绑定。最后,使用attr
函数为路径元素设置一些属性,包括CSS类、绘制路径和填充颜色:
let svg = d3.select(dom)
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style('border', '1px solid #999999')
svg.append("path")
.datum(dataset)
.attr("class", "area")
.attr("d", area)
.attr("fill", "#69b3a2");
为x轴和y轴创建比例尺,并使用D3.js的axisBottom
和axisLeft
函数创建x轴和y轴对象。然后,使用attr
函数设置一些属性来定义x轴和y轴的外观:
let xAxis = d3.axisBottom(xScale)
.tickFormat(function (d, i) { return dataset[i].name; });
let yAxis = d3.axisLeft(yScale);
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);
这段代码绘制了一个基本面积图,显示了不同种类水果的数量。面积图中的x轴显示水果的名称,y轴显示水果的数量。面积图的颜色是绿色,整个图形被包含在一个带有边框的SVG元素中。
然后我们来更新数据,做一些动画。 使用JavaScript和D3.js创建动态图表时,我们有时需要更新已有数据或添加新数据。这段代码展示了如何通过点击按钮来更新、排序、添加和删除数据,并且实时刷新面积图。
首先,我们在DOM中创建一个div
元素,然后利用map()
方法遍历一个包含操作名称的数组,并创建相应数量的button
按钮。将每个按钮添加到div
元素中,同时将每个按钮加入一个名为buts
的数组中,以便后续对它们进行操作。
接下来,为每个按钮添加一个点击事件监听器,分别实现以下操作:
- 更新数据:遍历数据集合,为每个数据项的值生成一个介于10和99之间的随机整数。然后选取面积图上所有区域(
.area
),将数据集合绑定到这些区域上并进行动画过渡,以更新数据。 - 正向排序:根据每个数据项的值升序排序数据集合,并相应地更新x轴和面积图。
- 反向排序:与正向排序操作相似,但按降序排序数据集合。
- 添加数据:生成一个随机数据项,并将其添加到数据集合末尾。然后相应地更新x轴和面积图。
- 删除数据:从数据集合的末尾删除一个数据项,并相应地更新x轴和面积图。如果数据集合只剩下3个数据项或更少,则此操作不会执行任何操作。
使用这些代码,我们可以轻松地实现动态数据更新和刷新。在面积图中添加和删除数据时,我们可以看到曲线的平滑过渡和流畅动画效果。
//更新数据
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);
buts.push(but);
})
buts[0].onclick = function () {
dataset.forEach(function (d) {
d.value = Math.floor(Math.random() * 90) + 10;
});
svg.selectAll(".area")
.data([dataset])
.transition()
.duration(1000)
.attr("d", area);
}
buts[1].onclick = function () {
dataset.sort(function (a, b) {
return d3.ascending(a.value, b.value);
});
xScale.domain(d3.range(dataset.length));
// 更新x轴
svg.select(".x-axis")
.transition()
.duration(1000)
.call(xAxis);
// 更新面积图
svg.selectAll(".area")
.data([dataset])
.transition()
.duration(1000)
.attr("d", area);
}
buts[2].onclick = function () {
dataset.sort(function (a, b) {
return d3.descending(a.value, b.value);
});
xScale.domain(d3.range(dataset.length));
// 更新x轴
svg.select(".x-axis")
.transition()
.duration(1000)
.call(xAxis);
// 更新面积图
svg.selectAll(".area")
.data([dataset])
.transition()
.duration(1000)
.attr("d", area);
}
buts[3].onclick = function () {
let newdata = { name: "新来的", value: Math.floor(Math.random() * 90) + 10 };
dataset.push(newdata);
xScale.domain(d3.range(dataset.length));
// 更新x轴
svg.select(".x-axis")
.transition()
.duration(1000)
.call(xAxis);
// 更新面积图
svg.selectAll(".area")
.data([dataset])
.transition()
.duration(1000)
.attr("d", area);
}
buts[4].onclick = function () {
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(".area")
.data([dataset])
.transition()
.duration(1000)
.attr("d", area);
}
在增加和删除数据的时候,你会发现动画效果有点瑕疵,这是因为动画是基于上次的结果,这个问题我们可以处理,后面再讲。
封面也是面积图制作的,下节讲。