【d3.js入门】基本面积图动画

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

【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的scaleBandscaleLinear函数创建比例尺对象,这些比例尺将数据集中的值映射到实际的像素值。比例尺对象将用于绘制面积图和坐标轴:

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的axisBottomaxisLeft函数创建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);
}

在增加和删除数据的时候,你会发现动画效果有点瑕疵,这是因为动画是基于上次的结果,这个问题我们可以处理,后面再讲。

封面也是面积图制作的,下节讲。

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

(0)
上一篇 2023年6月18日 上午10:06
下一篇 2023年6月18日 上午10:16

相关推荐

发表回复

登录后才能评论