1

echarts本身并不支持直方图,在官方的示例文档上,直方图是利用自定义图表实现的,之前在项目中的实现也是直接使用了官方示例的代码。
在今年3、4月份疫情隔离居家办公的时候,我们的产品经理,说我们的直方图的缩放只能针对x轴进行缩放,不能放大y轴,这不简单,我把y轴的缩放直接放开!
结果人傻了,看下官方示例的缩放效果image.png
看来只能自己动手diy了。

首先要理解直方图的renderItem属性是如何渲染出直方图的,这是整体的series

// 正常的直方图的series
        const tempseries = {
          type: 'custom',
          renderItem: function (params: any, api: any) {
            let yValue = api.value(2);
            // console.log(yValue,'yValue');
            let start = api.coord([api.value(0), yValue]);
            let size = api.size([(api.value(1) as number) - (api.value(0) as number), yValue]) as number[];

            // console.log(start,'start');
            // console.log(size,'size');

            const style = api.style();
            return {
              type: 'rect',
              shape: {
                x: start[0],
                y: start[1],
                width: size[0],
                height: size[1],
              },
              style,
            };
          },
          label: {
            show: false,
            position: 'top',
          },
          dimensions: ['from', 'to', 'count'],
          encode: {
            x: [0, 1],
            y: 2,
            tooltip: [0, 1, 2],
            itemName: 3,
          },
          markLine: {
            symbol: 'none',
            silent: false,
            precision: 100,
            label: {
              position: 'insideEndTop',
              silent: true,
            },
            tooltip: {
              formatter: '{c}',
            },
            data: marklineData,
          },
          name: key,
          yAxisIndex: 0,
          data: histogramData.map((item: any, index: number) => {
            return {
              value: [item[1], item[2], histogramDisplayLog ? Math.log10(item[3]) : item[3], item[0]],
              // itemStyle: {
              //   color: colorList[index],
              // },
            };
          }),
        };

        series.push(tempseries);

首先看一下data里的数据,这里的histogramLog是因为项目里有转log的需求,若正常,将三元表达式histogramDisplayLog ? Math.log10(item[3]) : item[3] 替换成 item[3]即可

data: histogramData.map((item: any, index: number) => {
            return {
              value: [item[1], item[2], histogramDisplayLog ? Math.log10(item[3]) : item[3], item[0]],
              // itemStyle: {
              //   color: colorList[index],
              // },
            };
          }),

是一个对象数组,里面有字段value,value是一个长度为4的数组,再看data数组与renderItem的对应关系:

 renderItem: function (params: any, api: any) {
            let yValue = api.value(2);
            let start = api.coord([api.value(0), yValue]);
            let size = api.size([(api.value(1) as number) - (api.value(0) as number), yValue]) as number[];

            const style = api.style();
            return {
              type: 'rect',
              shape: {
                x: start[0],
                y: start[1],
                width: size[0],
                height: size[1],
              },
              style,
            };
          },
dimensions: ['from', 'to', 'count'],
encode: {
            x: [0, 1],
            y: 2,
            tooltip: [0, 1, 2],
            itemName: 3,
          },

可知,依据官方示例的格式,我们的每个直方图柱子的data的value数组可以这么理解
[x轴的开始位置坐标,x轴的结束位置坐标,y轴的值,柱子的名称]

其中shape下的x、y、width、height分别是这样的对应每个柱子
image.png
PS:(文档上更详细。。。。。。)


知道了所有的画法,就可以着手开始重写缩放后的整个series了
首先获取缩放的范围的信息

if (typeof params.batch[0].startValue === 'number') {
        const startX = params.batch[0].startValue;
        const endX = params.batch[0].endValue;
        const startY = params.batch[1].startValue;
        const endY = params.batch[1].endValue;

重写的关键的两个个点:
1、data
x轴层面只取完全在缩放x范围内的柱子。即只有x的开始位置坐标大于startX且x的结束位置坐标小于startX的柱子才可以入选我们的data

            data: histogramData.map((item: any, index: number) => {
                if (
                  (histogramDisplayLog ? Math.log10(item[3]) : item[3]) > startY &&
                  item[1] > startX &&
                  item[2] < endX
                )
                  return {
                    value: [item[1], item[2], histogramDisplayLog ? Math.log10(item[3]) : item[3], item[0]],
                    // itemStyle: {
                    //   color: colorList[index],
                    // },
                  };
              }),

2、renderItem

首先是 return的shape中的x和y

分类讨论y,
①若框选的y的较大值endY大于我们柱子的值,则取柱子的值即可,缩放之后,还是柱子高度距离顶部有距离
image.png
②若框选的y的较大值endY小于我们柱子的值,则取endY,即舍弃掉柱子超出框选范围的部分,该柱子在高度上填充满整个高度(产品经理的需求。。。。。。)
image.png

let yValue = api.value(2) > endY ? endY : api.value(2);

x还是之前的每个柱子的x的起始位置坐标

let start = api.coord([api.value(0), yValue]);

然后是柱子的height和width
①width不变还是x的结束位置坐标减去x的开始位置坐标
②height应为柱子的y的值(yValue)减去缩放的y的开始位置(startY)

 let size = api.size([
                  (api.value(1) as number) - (api.value(0) as number),
                  bignumberFc.subtract(yValue, startY),
                ]) as number[];

综合的renderItem应为

renderItem: function (params: any, api: any) {
                let yValue = api.value(2) > endY ? endY : api.value(2);
                let start = api.coord([api.value(0), yValue]);
                let size = api.size([
                  (api.value(1) as number) - (api.value(0) as number),
                  bignumberFc.subtract(yValue, startY),
                ]) as number[];
                const style = api.style();
                return {
                  type: 'rect',
                  shape: {
                    x: start[0],
                    y: start[1],
                    width: size[0],
                    height: size[1],
                  },
                  style,
                };
              },

最后贴上整体的datazoom响应部分的函数(如果是回退到初始状态,需要额外判断特殊处理,echarts的缩放的坑)

 myChart.on('datazoom', (params: any) => {
      //回退到最初始的状态时
      if (params.batch[0].start === 0) {
        myChart.setOption(
          {
            series: [...series],
            
          },
          {
            replaceMerge: ['series'], // 替换合并series,默认普通合并
          },
        );
      }
      // 有startValue值的时候(常规缩放)
      else if (typeof params.batch[0].startValue === 'number') {
        const startX = params.batch[0].startValue;
        const endX = params.batch[0].endValue;
        const startY = params.batch[1].startValue;
        const endY = params.batch[1].endValue;

        const newSeries: any[] = [];
        if (data && Object.keys(data).length > 0) {
          for (let key in data) {
            const histogramData = data[key]['data'];
            const tempseries = {
              type: 'custom',
              renderItem: function (params: any, api: any) {
                let yValue = api.value(2) > endY ? endY : api.value(2);
                let start = api.coord([api.value(0), yValue]);
                let size = api.size([
                  (api.value(1) as number) - (api.value(0) as number),
                  bignumberFc.subtract(yValue, startY),
                ]) as number[];
                const style = api.style();
                return {
                  type: 'rect',
                  shape: {
                    x: start[0],
                    y: start[1],
                    width: size[0],
                    height: size[1],
                  },
                  style,
                };
              },
              label: {
                show: false,
                position: 'top',
              },
              dimensions: ['from', 'to', 'count'],
              encode: {
                x: [0, 1],
                y: 2,
                tooltip: [0, 1, 2],
                itemName: 3,
              },
             
              name: key,
              data: histogramData.map((item: any, index: number) => {
                if (
                  (histogramDisplayLog ? Math.log10(item[3]) : item[3]) > startY &&
                  item[1] > startX &&
                  item[2] < endX
                )
                  return {
                    value: [item[1], item[2], histogramDisplayLog ? Math.log10(item[3]) : item[3], item[0]],
                   
                  };
              }),
            };
            newSeries.push(tempseries);
          }
        }

        myChart.setOption(
          {
            series: [...newSeries],
           
          },
          {
            replaceMerge: ['series'], // 替换合并series,默认普通合并
          },
        );
      }
    });

PS:
image.png
项目中数据结构如此,依据个人的实际结构进行组织即可

PS2:data中的的histogramLog是因为项目里有转log的需求,若正常使用,将三元表达式histogramDisplayLog ? Math.log10(item[3]) : item[3] 替换成 item[3]即可

效果展示:
缩放前image.png
缩放动作image.png
缩放后的效果image.png


2 声望2 粉丝