echarts实现24小时的时间轴 renderItem的实践

1,812 阅读4分钟

最近有一个需求,要画一个24小时的时间轴。类似于一个简化的甘特图,可以参考苹果手机的睡眠,不过只有一种颜色。

image.png

如果直接用网络上的甘特图,有点太复杂了,所以准备使用echarts自己写一个类似的。

一、引入echarts

可以参考官方的 文档1 文档2 引入,我创建的vue项目,直接npm install echarts --save安装,按照上面的教程进行初始化。

二、开始绘制

x轴初始化:

echarts中,x轴的具体数据可以在xAxis.data中配置。在这个时间轴中则是从0-24的一个数组。

image.png

一个一个写比较麻烦,可以直接写一个方法。这样生成的就带有分钟。

    getXData() {
      let list = [];
      for (let i = 0; i <= 24; i++) {
        let str = `${i}:00`;
        list.push(str);
      }
      return list;
    },

如果希望x轴每个数据强制展示, 可以用xAxis.axisLabel. interval为0配置。 但是这样的话可能比较拥挤一些,还要画滚动条,设置文字的偏移度之类的。 我最后选择了直接写数字。

xAxis: {
  min: 0,
  max: 24,
  interval:1
}

具体柱形图决定使用renderItem绘制。我做过很多大屏可视化的图表,常见的都是什么bar,pie,line之类的,最多来个map,scatter之类的,renderItem倒是没有用过,今天学习一下,也是拓宽视野了。 可以参考 一下 文档

image.png 感觉还是比较好理解的,只要设置series的type是custom,然后child里放一些type是react的数据应该就可以了,shape中传入x和y,宽度、高度,就可以绘制矩形的形状了。这里的值都是px。

按照教程,我随便画了几个矩形,如下图参考。这里别的难度都不大,主要难度在于计算计算矩形的x和width,别的值都是可以固定的。这里的 image.png 计算矩形,这里我们可以借鉴echarts官方教程的甘特图,里面renderGanttItem方法。就是看着挺复杂的。

当echarts图表的宽度被我们写死时,计算x和width应该还是比较容易的,但是如果要做图表自适应,就比较麻烦了。 我们将renderItem的两个参数输出,可以发现有一个getWidth方法。这个方法可以获得echarts图表的宽度。

renderItem(params,api) {
  console.log( 'params',params)
  console.log( 'api',api)
}

image.png

或者params里的coordSys也可以获得我们要的数据,我自己本身设置的图表的宽度和高度其实分别是600px和200px,我在grid里设置了边距,coordSys的结果应该是去除空白边距的大小。我们用这个计算应该效果会更好。

image.png

这里我们自己算也可以了,我们有时间段的起始时间和结束时间,可以计算每段时间占一天的百分比,然后乘以coordSys.width,就得到了宽度。x的值可以通过比例获得,x-coordSys.x:coordSys.width = 转化为小数的时间:24。

这我就不具体写了。因为我看echarts有自己的方法,我们自己不用算。 我们把时间转化为小数,调用下面这个方法,应该就可以直接算出来的,比如6:30转化完成后是6.5。理论上宽度用series-custom.renderItem.arguments.api.size,但是我算出来的结果不对,所以就不用这个了。调用两次coord相减。

例如下图是6:30-11:00的柱形图。这里我时间是写死的,其实可以通过api.value(0)和api.value(1)分别获得时间的开始值和结束值。

          renderItem(params, api) {
            let x1 = api.coord([6.5, 1])[0];
            let x2 = api.coord([11, 1])[0];
            return {
              type: "group",
              children: [
                {
                  type: "rect",
                  shape: { x: x1, y: 100, width: x2-x1, height: 20 },
                },
              ],
            };
          },

image.png

最后整理

地图上画一个按钮,模拟接口调用重新渲染图表。按钮绑定formateData事件。用一些模拟数据测试。后端给的数据应该是x:xx这样的格式,或者x时x分,需要对数据进行格式化,重新设置echarts的option,并渲染。

image.png methos中写入以下代码:

 formateData() {
      let testData = ["6:30-6:45", "11:30-11:35", "11:36-11:37", "16:45-19:50"];
      const data = [];
      testData.forEach((e) => {
        const time = e.split("-");
        const time1 =
          Number(time[0].split(":")[0]) + Number(time[0].split(":")[1]) / 60;
        const time2 =
          Number(time[1].split(":")[0]) + Number(time[1].split(":")[1]) / 60;
        data.push([time1, time2]);
      });
      this.option.series[0].data = data;
      this.myChart.setOption(this.option);
    },

data中option代码如下:

 option: {
        xAxis: {
          min: 0,
          max: 24,
          interval: 1,
        },
        yAxis: {
          show: false,
          splitLine: false,
        },
        series: [
          {
            type: "custom",
            data: [],
            renderItem(params, api) {
              const data1 = api.value(0);
              const data2 = api.value(1);
              let x1 = api.coord([data1, 1])[0];
              let x2 = api.coord([data2, 1])[0];
              var categoryIndex = api.value(0);
              return {
                type: "group",
                children: [
                  {
                    type: "rect",
                    shape: { x: x1, y: 100, width: x2 - x1, height: 20 },
                  },
                ],
              };
            },
          },
        ],
      },

最终效果如下图:

image.png 这里我们画了一个横向的时间轴,我想如果画纵向的其实也是差不多的。今天也只画了一条,很多时候甘特图是有很多行的,这时候series这个数组里面再push进入一些数据,应该就没问题了。