【数据可视化】【2】使用d3.js绘制并更新图表的多条折线

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绘制简单折线图了。因个人忙于其他事情,没有对代码进行详细的讲解,有疑问的读者朋友可以自行百度或者查阅官方文档,同时也欢迎大家留言交流。

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值