前言
开发中收到一个新的需求,要求把用户家里每台使用燃气的设备和燃气表用量做一个用气时间对比,检测燃气表在走表的时候家中的燃气设备是否有在使用。
方案
多维度堆叠柱状图
假设我们有燃气表设备,同时使用燃气工作的设备有热水器和油烟机;那么可以画一个 柱状图
,横坐标X轴上的刻度就是每天的时刻(0 ~ 24),纵坐标Y轴就是每台设备在某个整点刻度的用气量,效果图如下:
缺点
- 设备名称的维度不够明确,没有直接在图中显示出来,只能单靠颜色进行区分
- 用气时间不一定都是整点的,用气时间段描述不够精确
- 如果设备多了图就会显得更加杂乱
仿甘特图
大家都知道 甘特图
的横坐标是可以以时间区间做维度的,并且纵坐标可以以设备名称作为维度,那么问题来了,echarts
没有 甘特图
的示例,那么怎么去实现呢?
参考上面的 堆叠柱状图
,如果把整个图横过来,同时蓝色区域变成透明色,那样看起来是不是就有点 甘特图
样子了,其实 echarts
实现 甘特图
就是通过横向 堆叠柱状图
来实现的。
我们把 X轴
维度变成时间,type
设置成 value
,把 Y轴
维度改成设备名称, type
设置成 category
,理想效果就是这样。但事实上,type
设置成 value
后,data
接受的数据类型只能是数字呀,不能把时间字符串格式作为数据的。
换个思路,我们把时间转 成时间戳
不就ok了。思路有了,我们就来实现一下。
数据结构
因为我们要展示具体设备的用气时间段,所以我们可以要求服务端的返回格式如下:
const mockData = [
{
name: "热水器",
dataList: [
{
startTime: "19:20:00",
endTime: "20:25:56",
useGas: 10,
},
],
},
{
name: "油烟机",
dataList: [
{
startTime: "11:20:00",
endTime: "12:25:56",
useGas: 10,
},
{
startTime: "19:20:00",
endTime: "20:25:56",
useGas: 10,
},
],
}
]
字段说明
name
:设备名称startTime
:开始用气时间endTime
:结束用气时间useGas
:时间段的用气量
数据补充
因为服务端给我们的数据只包含用气的时间段,没有用气的时间段就需要我们自己通过 JS
填充上去
function fillEmptydata(data) {
for (let i = 0; i < data.length; i++) {
const dataList = data[i].dataList;
if (!dataList.length) {
dataList.push({
startTime: "00:00:00",
endTime: "23:59:59",
useGas: 0,
});
continue;
}
for (let k = 0; k < dataList.length; k++) {
if (k === 0 && dataList[k].startTime !== "00:00:00") {
dataList.unshift({
startTime: "00:00:00",
endTime: dataList[k].startTime,
useGas: 0,
});
k++;
} else {
dataList.splice(k, 0, {
startTime: dataList[k - 1].endTime,
endTime: dataList[k].startTime,
useGas: 0,
});
k++;
}
if (k === dataList.length - 1 && dataList[k].endTime !== "23:59:59") {
dataList.push({
startTime: dataList[k].endTime,
endTime: "23:59:59",
useGas: 0,
});
k++;
continue;
}
}
}
}
数据填充以后我们可以得到新的数据
mockData = [
{
name: "热水器",
dataList: [
{
startTime: "00:00:00",
endTime: "19:20:00",
useGas: 0,
},
{
startTime: "19:20:00",
endTime: "20:25:56",
useGas: 10,
},
{
startTime: "20:25:56",
endTime: "23:59:59",
useGas: 0,
},
],
},
{
name: "油烟机",
dataList: [
{
startTime: "00:00:00",
endTime: "11:20:00",
useGas: 0,
},
{
startTime: "11:20:00",
endTime: "12:25:56",
useGas: 10,
},
{
startTime: "12:25:56",
endTime: "19:20:00",
useGas: 0,
},
{
startTime: "19:20:00",
endTime: "20:25:56",
useGas: 10,
},
{
startTime: "20:25:56",
endTime: "23:59:59",
useGas: 0,
},
],
}
]
时间格式处理
因为我们要直观的展现每天每台设备的用气时间区间,所以我们还需要把服务端返回的数据手动拼接上年/月/日信息(这里也可以直接让服务端返回,因为前端查询的时候是必须带上日期的,所以我的业务代码里面就自己手动拼接了),这样才可以把时间转成 时间戳
进行展现。这里我们以 2024-04-24
为例,改造后的数据就是在 startTime
和 endTime
的格式都变成了 YYYY-MM-DD HH:mm:ss
。
Y轴数据
我们的 Y轴
需要以设备的名称作为维度,所以我们可以这么设置 yAxis
yAxis: {
type: "category",
data: mockData.map(o => o.name)
axisLabel: {
color: "#000",
fontSize: 12,
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
}
X轴数据
X轴
上我们现在是时间戳的数据,所以我们需要设置一个 min
和 max
值,否则在使用 formatter
的时候 axisLabel
就不能正确的显示时间了。最小值 min
就是指定日期的 00:00:00
, 最大值 max
就是指定日期的 23:59:59
。
这里涉及到了时间的操作,为了能更快地格式化时间,所以引用了 dayjs
。
npm install dayjs --dev
import dayjs from 'dayjs';
const minT = new Date("2024-04-24 00:00:00").getTime();
const maxT = new Date("2024-04-24 23:59:59").getTime();
// x轴配置项
xAxis: {
type: "value",
min: minT,
max: maxT,
interval: 60 * 60 * 1000 * 1.5, // 间隔1个小时展示一个坐标,一天就会有24个刻度点
axisLabel: {
formatter: (value) => {
return dayjs(value).format("HH:mm:ss"); // 因为会展示24个刻度,所以就把年月日信息隐藏了
},
color: "#000",
},
splitLine: {
lineStyle: {
color: "#f1f1f180",
},
},
},
Series
X轴
和 Y轴
的数据处理好了,重点就要来处理 Series
的数据了。
我们使用的是 柱状图
模拟 甘特图
效果,所以我们需要找到时间区间最多的那台设备的时间区间数量作为 series
的长度。(这里可能不太好理解,但是只要你能懂多维度柱状图的数据分布那就好理解了)
const L = Math.max(...mockData.map(o => o.dataList.length))
const series = new Array(L).fill(null).map((o, index) => {
return {
name: `Gantt${index}`,
type: "bar",
stack: "Total",
barWidth: 18,
data: new Array(mockData.length).fill(null)
};
});
为什么上面 data
的长度是 mockData
的长度。这里 data
数据的顺序就是 Y轴
里面对应设备的数据,有几台设备,数据的长度就是多少。
接下来填充每个 series
里面 data
的数据
// 这里我们可以先定义一个colors的数组,确保后面每台设备的时间区间展示颜色是相同的,容易对设备进行区分
const colors = ["#1ED974", "#567CED", "#FFAC05", "#ED5672", "#F4753F"];
for (let i = 0; i < series.length; i++) {
for (let k = 0; k < mockData.length; k++) {
const useGas = mockData[k].dataList[i]?.useGas;
const endV = mockData[k].dataList[i]?.endTime;
const startV = mockData[k].dataList[i]?.startTime;
const v = new Date(endV).getTime() - new Date(startV).getTime();
series[i].data[k] = {
// 第一条数据需要加上minT,后面堆叠的数据就是时间区间时间戳的差值
value: v + (i === 0 ? minT : 0),
timeRange: `${startV} ~ ${endV}`,
useGas,
itemStyle: {
// 没有用气量颜色就变成透明
color: useGas > 0 ? colors[k] : "transparent",
borderColor: useGas > 0 ? colors[k] : "transparent",
borderRadius: 1,
},
};
}
}
以上就是最重要的 X轴
、 Y轴
和 Series
的数据配置了
解释说明
最主要的就是需要把时间区间转成 时间戳差值
,随后理解一下柱状图的堆叠效果如何配置,然后就是在 useGas
用气量为0的时候把这个区间透明展示,这样就可以很直观的看出来每台设备的用气时间段了。
效果图
总结
在 iPhone
手机上也是有 APP
的使用时间统计,它所使用统计方式就是开头说的第一种,这种方式不能有太多的类型,所以手机上就会把 APP
进行分类,只直观展示 创意
、旅游
、社交
三部分。如果使用第二种展现方式,就可以把每款 APP
的使用时间精确展示了。