版权声明:本文为博主原创文章,未经博主允许不得转载。 文章底部留言可联系作者。
一、背景
实际开发过程中,有时候UI的设计稿为了图表显示美观,可能发现设计稿上的图表与官方示例图表有很大差别。就比如我遇到的,以柱状图作为背景,每个柱形和折线图上每个点的数据一一对应,并显示出来。如下图效果,下面我们就讲解一下如何实现。
二、图形实现
首先,因为我是在移动端开发的,所以使用的 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; // 最大值存在就把取最大值
}
做完这个准备工作以后,肯定要疑惑要最大值干什么用呢?
因为当数据返回为空,我们默认只显示灰色背景的柱状图,默认最大值其实是给灰色背景数据做填充使用的,效果如下图。
所以,如果返回的数据存在时,我们需要对数据进行改造,将灰色柱状图的数据的值用 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来记录横坐标显示点,在后面载入数据源的时候会用到。
-
第一天和最后一天的日期要显示在图上
- 数据源存在的时候,第一天(
[resData[0].date])和最后一天(resData[arrLength - 1].date)的日期分别放入数组xAxisTicks。
- 数据源存在的时候,第一天(
-
针对
isDisplay为true的点要显示日期,并在折线图中做标记- 对数据源进行
map遍历,给每个数据家里索引index字段,对下一步有用 - 然后过滤出
isDisplay为true的数据,得到displayDotArr。
- 对数据源进行
-
如果有多个连续日期需要显示,只显示连续日期中最后的一个日期
- 实现一个
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] 表示的是一个相邻的日期集合,针对相邻的日期,我们只取最后一个日期。
处理后的数据源:
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)图表其他配置
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)创建图形语法
参考文档几何标记和图表类型,
1. interval 创建柱状背景图
几何标记 interval 创建柱状图,position 确定 x 轴和 y 轴的数据字段,
color渐变色设置。
这里color用的是线性渐变:
lineChart
.interval()
// 将 'date' 数据值映射至 x 轴坐标点,'backupNumber' 数据值映射至 y 轴坐标点
.position("date*backupNumber")
.color("l(90) 0:rgba(245,245,245,0.18) 1:#eee");
效果如下:
2. line 创建折线图
这里使用的属性同上,style参考这里 ,统一为所有 shape 设置固定的样式,具体设置内容可以看绘图属性
lineChart
.line()
.position("date*number")
.color("#096dd9")
.style({
lineWidth: 1 // 设置线段厚度的属性
});
叠加折线图后效果如下:
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" // 绘制图形颜色
});
叠加点图后效果如下:
4. area 创建面积图
lineChart
.area()
.position("date*number")
.color("l(90) 0:#096dd9 1:rgba(9,109,217,.4)");
叠加面积图后:
6)渲染图表
渲染图表,在最后调用,这样我们的图表就绘制完成了:
lineChart.render();
最终效果如妥所示: