如何使用echarts直观的展示每台设备每天的用气时间

发布于:2024-04-25 ⋅ 阅读:(19) ⋅ 点赞:(0)

前言

开发中收到一个新的需求,要求把用户家里每台使用燃气的设备和燃气表用量做一个用气时间对比,检测燃气表在走表的时候家中的燃气设备是否有在使用。

方案

多维度堆叠柱状图

假设我们有燃气表设备,同时使用燃气工作的设备有热水器油烟机;那么可以画一个 柱状图,横坐标X轴上的刻度就是每天的时刻(0 ~ 24),纵坐标Y轴就是每台设备在某个整点刻度的用气量,效果图如下:

bar-label-rotation.png

缺点

  • 设备名称的维度不够明确,没有直接在图中显示出来,只能单靠颜色进行区分
  • 用气时间不一定都是整点的,用气时间段描述不够精确
  • 如果设备多了图就会显得更加杂乱

仿甘特图

大家都知道 甘特图 的横坐标是可以以时间区间做维度的,并且纵坐标可以以设备名称作为维度,那么问题来了,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 为例,改造后的数据就是在 startTimeendTime 的格式都变成了 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轴 上我们现在是时间戳的数据,所以我们需要设置一个 minmax 值,否则在使用 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的时候把这个区间透明展示,这样就可以很直观的看出来每台设备的用气时间段了。

效果图

企业微信截图_17140260141984.png

总结

iPhone 手机上也是有 APP 的使用时间统计,它所使用统计方式就是开头说的第一种,这种方式不能有太多的类型,所以手机上就会把 APP 进行分类,只直观展示 创意旅游社交 三部分。如果使用第二种展现方式,就可以把每款 APP 的使用时间精确展示了。


网站公告

今日签到

点亮在社区的每一天
去签到