前面写了一篇 【d3.js入门】基本柱状图,这次我们在这个柱状图基础上添加一些动画效果。
好,废话多说,我们再画一遍柱状图。
绘制柱状图
D3.js 是一个 JavaScript 库,用于创建动态、交互式的数据可视化,它能够将数据与一系列 DOM 元素绑定,进而让开发者可以使用数据驱动的方式来操作 DOM 元素,从而实现各种可视化效果,包括柱状图、折线图、饼图等等。
使用 D3.js 来制作一个简单的柱状图,以下是代码和讲解:
// 创建 div 容器,并将其添加到 body 中
var dom = document.createElement('div');
document.body.appendChild(dom);
// 数据集,每个对象都有一个 name 和 value 属性
var dataset = [
{ name: "1", value: 30 },
{ name: "2", value: 20 },
{ name: "3", value: 50 },
{ name: "4", value: 20 },
{ name: "5", value: 100 },
{ name: "6", value: 30 },
{ name: "7", value: 60 }
];
// 设定柱状图的 padding、宽度和高度
var padding = 30;
var svgWidth = 600;
var svgHeight = 400;
// 创建 x 轴的比例尺,使用 scaleBand() 函数
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length)) // 指定输入域,使用 d3.range() 函数生成长度为 dataset.length 的数组
.range([padding, svgWidth - padding]) // 指定输出域,从 padding 到 svgWidth - padding
.padding(0.3); // 设置柱状之间的间隔,默认为 0.1
// 创建 y 轴的比例尺,使用 scaleLinear() 函数
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d.value; })]) // 指定输入域,使用 d3.max() 函数获取 dataset 中的最大值
.range([svgHeight - padding, padding]); // 指定输出域,从 svgHeight - padding 到 padding
// 创建 svg 元素,并设定其宽度和高度
var svg = d3.select(dom)
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style('border', '1px solid #999999');
// 计算每个柱状的宽度
var barWidth = xScale.bandwidth();
// 创建柱状图的矩形元素
var bars = svg.selectAll(".bar")
.data(dataset) // 绑定数据
.join('rect') // 使用 join() 函数创建矩形元素
.attr("class", "bar") // 添加类名
.attr("fill", "#69b3a2") // 柱状图的颜色
.attr("x", function(d, i) { // 指定 x 坐标,使用 xScale 函数
return xScale(i);
})
.attr("y", function(d) { // 指定 y 坐标,使用 yScale 函数
return yScale(d.value);
})
.attr("width", barWidth) // 指定柱状的宽度
.attr("height", function(d) { // 指定柱状的高度,使用 yScale 函数
return svgHeight - yScale(d.value) - padding;
});
// 创建 x 轴,使用 axisBottom() 函数
var xAxis = d3.axisBottom(xScale)
.tickFormat(function(d, i) { // 指定刻度标签的格式,使用 dataset 中的 name 属性
return dataset[i].name;
});
// 创建 y 轴,使用 axisLeft() 函数
var yAxis = d3.axisLeft(yScale);
// 将 x 轴添加到 svg 中,并设定其位置
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + (svgHeight - padding) + ")")
.call(xAxis);
// 将 y 轴添加到 svg 中,并设定其位置
svg.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
上述代码创建了一个简单的柱状图,其中包含了以下几个关键的知识点:
-
D3.js 的比例尺(scale):比例尺用于将输入域(domain)中的数据映射到输出域(range)中的一组值。在本例中,使用了 scaleBand() 和 scaleLinear() 函数分别创建了 x 轴和 y 轴的比例尺。
-
D3.js 的选择集(selection):选择集是 D3.js 中最基本的概念之一,它用于选择 DOM 元素并进行操作。在本例中,使用了 select() 和 selectAll() 函数来选择 DOM 元素。
-
D3.js 的数据绑定(data binding):数据绑定是 D3.js 中最重要的概念之一,它用于将数据集与 DOM 元素绑定,并根据数据集的内容来动态地创建、更新、删除 DOM 元素。在本例中,使用了 data() 函数将数据集与柱状图的矩形元素绑定,并使用 join() 函数来创建矩形元素。
-
D3.js 的坐标轴(axis):坐标轴用于显示比例尺的刻度标签和线条。在本例中,使用了 axisBottom() 和 axisLeft() 函数来创建 x 轴和 y 轴,并使用 call() 函数来将坐标轴添加到 svg 元素中。
添加动画
我们添加几个按钮用来更新数据和对数据排序,由于D3属于数据驱动的,所以我们只需要对数据进行操作即可。
首先,我们使用 d3.select()
方法选择一个 div
元素,并将其作为 SVG 容器。然后,我们定义了一些数据集,用于生成图表。我们创建了两个比例尺(x 和 y),它们将数据值映射到坐标轴上。接下来,我们创建了一组矩形元素,它们代表数据集中的每个数据点。我们使用 selectAll()
和 data()
方法将数据集绑定到这些矩形元素上,并使用 enter()
方法将新元素添加到 DOM 中。最后,我们使用 append()
方法向每个元素添加一个矩形,并为每个矩形设置位置、大小和颜色。
然后,我们添加了三个按钮。第一个按钮用于更新数据集中的值。当用户单击“更新数据”按钮时,我们使用 Math.random()
方法生成一个新的随机值,并将其分配给每个数据点。然后,我们使用 transition()
方法将更改应用到每个矩形元素上,并使用 duration()
方法指定动画持续时间。
第二个和第三个按钮用于对数据进行排序。当用户单击“正向排序”按钮时,我们使用 sort()
方法将数据集按升序排序,并将新的 x 轴域应用于比例尺。然后,我们使用 transition()
方法将更改应用到每个矩形元素上,并使用 duration()
方法指定动画持续时间。当用户单击“反向排序”按钮时,我们使用 sort()
方法将数据集按降序排序,并使用相同的过渡方法更新图表。
最后是添加数据和删除数据。
代码如下:
// 添加按钮 更新数据
var butdiv = document.createElement('div');
dom.appendChild(butdiv);
var but1 = document.createElement('button');
but1.innerHTML = '更新数据';
butdiv.appendChild(but1);
but1.onclick = function () {
dataset.forEach(function (d) {
d.value = Math.floor(Math.random() * 90) + 10;
});
svg.selectAll(".bar")
.data(dataset)
.join('rect')
.transition()
.duration(1000)
.attr("y", function (d) {
return yScale(d.value);
})
.attr("height", function (d) {
return svgHeight - yScale(d.value) - padding;
});
}
// 添加按钮 正向排序
var but2 = document.createElement('button');
but2.innerHTML = '正向排序';
butdiv.appendChild(but2);
but2.onclick = function () {
dataset.sort(function (a, b) {
return a.value - b.value;
});
xScale.domain(d3.range(dataset.length));
svg.selectAll(".bar")
.data(dataset)
.join('rect')
.transition()
.duration(1000)
.attr("x", function (d, i) {
return xScale(i);
})
.attr("y", function (d) {
return yScale(d.value);
})
.attr("height", function (d) {
return svgHeight - yScale(d.value) - padding;
});
}
// 添加按钮 反向排序
var but3 = document.createElement('button');
but3.innerHTML = '反向排序';
butdiv.appendChild(but3);
but3.onclick = function () {
dataset.sort(function (a, b) {
return b.value - a.value;
});
xScale.domain(d3.range(dataset.length));
svg.selectAll(".bar")
.data(dataset)
.join('rect')
.transition()
.duration(1000)
.attr("x", function (d, i) {
return xScale(i);
})
.attr("y", function (d) {
return yScale(d.value);
})
.attr("height", function (d) {
return svgHeight - yScale(d.value) - padding;
});
}
// 添加按钮 添加数据
var but4 = document.createElement('button');
but4.innerHTML = '添加数据';
butdiv.appendChild(but4);
but4.onclick = function () {
var newNum = Math.floor(Math.random() * 90) + 10;
dataset.push({ name: dataset.length + 1, value: newNum });
xScale.domain(d3.range(dataset.length));
// 更新x轴
svg.select(".x-axis")
.transition()
.duration(1000)
.call(xAxis);
// 更新柱子
barWidth = xScale.bandwidth();
svg.selectAll(".bar")
.data(dataset)
.join('rect')
.attr("class", "bar")
.attr("fill", "#69b3a2")
.attr("x", function (d, i) {
return xScale(i);
})
.attr("width", barWidth)
.transition()
.duration(1000)
.attr("y", function (d) {
return yScale(d.value);
})
.attr("height", function (d) {
return svgHeight - yScale(d.value) - padding;
});
}
// 添加按钮 删除数据
var but5 = document.createElement('button');
but5.innerHTML = '删除数据';
butdiv.appendChild(but5);
but5.onclick = function () {
if(dataset.length<=7) return;
// 删除最后一项
dataset.pop();
xScale.domain(d3.range(dataset.length));
// 更新x轴
svg.select(".x-axis")
.transition()
.duration(1000)
.call(xAxis);
// 更新柱子
barWidth = xScale.bandwidth();
svg.selectAll(".bar")
.data(dataset)
.join('rect')
.attr("class", "bar")
.attr("fill", "#69b3a2")
.attr("x", function (d, i) {
return xScale(i);
})
.attr("width", barWidth)
.transition()
.duration(1000)
.attr("y", function (d) {
return yScale(d.value);
})
.attr("height", function (d) {
return svgHeight - yScale(d.value) - padding;
});
}