antv/f2实现柱状底色背景和折线图叠加显示效果

2,640 阅读7分钟

版权声明:本文为博主原创文章,未经博主允许不得转载。 文章底部留言可联系作者。

一、背景

实际开发过程中,有时候UI的设计稿为了图表显示美观,可能发现设计稿上的图表与官方示例图表有很大差别。就比如我遇到的,以柱状图作为背景,每个柱形和折线图上每个点的数据一一对应,并显示出来。如下图效果,下面我们就讲解一下如何实现。

示意图.png

二、图形实现

首先,因为我是在移动端开发的,所以使用的 antv/f2 这个库进行开发的。因为F2是针对移动端的可视化方案,非常方便。

1.安装

npm install @antv/f2 --save

2.引入

import F2 from '@antv/f2';

在 F2 引入页面后,我们就已经做好了创建一个图表的准备了。

3.创建图表

在页面上创建一个 <canvas> 并指定 id

<canvas id="chart" style={{ width: "90%", height: "200px" }} />

1)创建 Chart 图表对象,指定图表 ID、指定图表的宽高、边距等信息

我demo中只下面我设置了pixelRatio,其他属性可以根据自己需要进行设置,参考文档 Chart

const lineChart = new F2.Chart({
    id: `chart`, // 指定对应 canvas 的 id
    // pixelRatio屏幕画布的像素比
    // 由于 canvas 在高清屏上显示时会模糊,所以需要设置 `pixelRatio`,一般情况下设置如下
    pixelRatio: window.devicePixelRatio 
});

2)数据源处理

这里由于图形的特殊性,需要对数据源进行特殊处理再使用。先看一下我们要使用的数据源格式:

const resData = [
    {
        date: "02.01",    // 日期:2月1日
        number: 0,        // 值
        isDisplay: false  // 标记这个点是否显示
    },
    {
        date: "02.02",
        number: 119,
        isDisplay: true
    }
    ...
]

首先我们需要从获取的数据源中找到最大值的项,如果返回的数据为空,我们需要设置一个默认的最大值,这里设置的是 20,如果最大值项存在则将最大值覆盖为我们找到的值。

import { maxBy } from "lodash";
...
     // 数组
    let newDataArr = [];
    let maxDataItem = maxBy(resData, (o) => o.number); // 找到值最大项
    let maximum = 20; // 默认最大值
    
    if (maxDataItem && maxDataItem.number) {
      maximum = maxDataItem.number; // 最大值存在就把取最大值
    }

做完这个准备工作以后,肯定要疑惑要最大值干什么用呢?

因为当数据返回为空,我们默认只显示灰色背景的柱状图,默认最大值其实是给灰色背景数据做填充使用的,效果如下图。

数据为空的情况.png

所以,如果返回的数据存在时,我们需要对数据进行改造,将灰色柱状图的数据的值用 backupNumber 来进行填充:

// 返回数据不为空
if (resData.length) {
  resData.map((item) => {
    // 给每个数据源添加灰色背景数据,这里加 10 的作用是为了让柱状图可以比折线图的最大值点可以高一些距离
    newDataArr.push({ ...item, backupNumber: maximum + 10 });
  });
}

如果数据是空的话,newDataArr则是空数组,为了显示一个上面图所示的灰色柱状背景图作为缺省图,所以要手动对数据做个填充。默认显示14个柱。

// 数据为空使用灰色数据部位
if (!newDataArr.length) {
  for (let i = 0; i < 14; i++) {
    newDataArr.push({
      date: i,
      isDisplay: false,
      backupNumber: maximum + 10
    });
  }
}

上面的数据处理完,下面针对一些特殊日期显示效果做个处理:

我们用数组 xAxisTicks来记录横坐标显示点,在后面载入数据源的时候会用到。

  1. 第一天和最后一天的日期要显示在图上

    • 数据源存在的时候,第一天([resData[0].date])和最后一天(resData[arrLength - 1].date)的日期分别放入数组 xAxisTicks
  2. 针对 isDisplaytrue 的点要显示日期,并在折线图中做标记

    • 对数据源进行map遍历,给每个数据家里索引index字段,对下一步有用
    • 然后过滤出 isDisplaytrue 的数据,得到 displayDotArr
  3. 如果有多个连续日期需要显示,只显示连续日期中最后的一个日期

    • 实现一个 getDisplayDotSort 方法,可以把连续点的日期处理后只返回最有一点的日期

具体实现如下:

// 具体实现
let arrLength; //数组长度
let xAxisTicks = []; //横坐标显示点

// 数据源存在
if (resData.length) {
    // 记录数据源长度
    arrLength = resData.length;
    // 第一天日期
    xAxisTicks = [resData[0].date];

    // 找到要显示坐标点的数组
    let displayDotArr = resData
        .map((item, i) => ({ ...item, index: i }))
        .filter((d) => d.isDisplay);

    // 横坐标要显示点的日期数组
    getDisplayDotSort(displayDotArr).forEach((d) => {
        xAxisTicks.push(d.date);
    });

    // 最后一天的日期
    xAxisTicks.push(resData[arrLength - 1].date);
}

getDisplayDotSort方法实现:

export const getDisplayDotSort = (array) => {
  
  let res = [];
  // 遍历要显示的日期数组
  array.forEach((item, i) => {
    // 设置一个临时变量,可以取到数组的最后1位
    let temp = res[res.length - 1] || [];
    // 遍历的数据的索引 和 临时变量temp中最后一位数据的索引相差1则证明两数相邻,则temp改完遍历的当前的这个数据
    if (item.index - temp[temp.length - 1].index === 1 ) {
      // 相邻的数据都放到一个数组中
      temp.push(item);
    }
    // 其他的情况才放到res这个数组中
    else {
      //这里存的是[] 主要是为了将每个连续的点作为一组数据存放到一个数组中,和不相邻的区分开,最后形成2维数组
      res.push([item]);
    }
  });
  // 连续点切割成数组,并取每个数组最后一个点返回
  return res.map((v) => v[v.length - 1]);
};

最后处理的res 是一个二维数组,可以看到 res[2] 表示的是一个相邻的日期集合,针对相邻的日期,我们只取最后一个日期。 数据处理示意图.png

处理后的数据源:

const newDataArr = [
    {
        date: "02.01",    // 日期:2月1日
        number: 0,        // 值
        backupNumber: 432, // 灰色背景最大值
        isDisplay: false  // 标记这个点是否显示
    },
    {
        date: "02.02",
        number: 119,
        isDisplay: true,
        backupNumber: 432
    }
    ...
]

处理后的xAxisTicks 横坐标显示点数据如下:

const xAxisTicks = ["02.01", "02.03", "02.07", "02.15", "02.18"]

这样我们就把 横坐标要显示的点处理完毕了。

3)载入数据源

针对数据的设置,参考scale

// newDataArr处理后的数据
lineChart.source(newDataArr, {
    // 各个属性配置
    number: {
        tickCount: 5, // 坐标轴上刻度点的个数,不同的度量类型对应不同的默认值。
        min: 0        // 手动指定最小值
    },
    date: {
        type: "cat", // 分类, ['男','女'];
        range: [0, 1], //输出数据的范围,数值类型的默认值为 [0, 1],
        tickCount: 3, // 定义坐标轴刻度线的条数
        ticks: xAxisTicks // 用于指定坐标轴上刻度点的文本信息,这里是首尾点和连续点的最后一个点
    }
});

4)图表其他配置

参考文档中 axistooltip 使用

axis说明.png

lineChart.tooltip(false); // 隐藏配置提示信息
lineChart.axis("number", false); // 关闭 number 对应的Y轴坐标轴。折线图要显示值的
lineChart.axis("backupNumber", false); // 关闭 backupNumber 对应的Y轴坐标轴。灰色柱状图的

lineChart.axis("date", {
    label: (text, index, total) => { // 底部日期x轴坐标文本设置
        const config = {};
        if (index === 0) {
            config.textAlign = "left"; // 第一个靠左显示
        } else if (index === total - 1) {
            config.textAlign = "right"; // 最后一个靠右显示
        }

        return config;
    },
    line: null, // 轴线隐藏
    labelOffset: 1 // 坐标轴文本距离轴线的距离
});

5)创建图形语法

参考文档几何标记和图表类型

几何标记.png

图表类型.png

1. interval 创建柱状背景图

几何标记 interval 创建柱状图,position 确定 x 轴和 y 轴的数据字段, color渐变色设置。

这里color用的是线性渐变:

color线性渐变.png

lineChart
      .interval()
      // 将 'date' 数据值映射至 x 轴坐标点,'backupNumber' 数据值映射至 y 轴坐标点
      .position("date*backupNumber")
      .color("l(90) 0:rgba(245,245,245,0.18) 1:#eee");

效果如下: 柱状背景.png

2. line 创建折线图

这里使用的属性同上,style参考这里 ,统一为所有 shape 设置固定的样式,具体设置内容可以看绘图属性

lineChart
    .line()
    .position("date*number")
    .color("#096dd9")
    .style({
      lineWidth: 1 // 设置线段厚度的属性
    });

叠加折线图后效果如下: 叠加折线图后.png

3. point 创建点图

这里的属性 size 是将数据值映射到图形的大小上的方法

注意:  不同图形的 size 的含义有所差别:

  • point 图形的 size 影响点的半径大小;
  • line, area, path 中的 size 影响线的粗细;
  • interval 的 size 影响柱状图的宽度。
lineChart
      .point()
      .position("date*number")
      .color("#096dd9")
      .size("date*isDisplay", (date, isDisplay) => {
        if (isDisplay) return 3; // 要显示的点大小
        return 0; // 不显示的带你大小为0
      })
      .style({
        // 统一为所有 shape 设置固定的样式
        lineWidth: 1,
        stroke: "#fff" // 绘制图形颜色
      });

叠加点图后效果如下: 叠加点图后.png

4. area 创建面积图

lineChart
      .area()
      .position("date*number")
      .color("l(90) 0:#096dd9 1:rgba(9,109,217,.4)");

叠加面积图后:

叠加面积图后.png

6)渲染图表

渲染图表,在最后调用,这样我们的图表就绘制完成了:

lineChart.render();

最终效果如妥所示: 叠加面积图后.png

点击查看以上完整demo代码

参考文献