1

基本的实现思想,见https://segmentfault.com/a/11...,时隔接近一年,新项目中,对于多层x轴的实现进行了分装。

多层不等分x轴的实现逻辑是在呈现原始图形的下方,继续画出多个grid,每一层x轴对应一个grid,故该封装旨在返回这些多出的grid、xAxis、yAxis、seires、以及原始图形要使用的xAxisData。

export const buildMultiXExtends: ({
  points,
  xNames,
  width,
  dataSeriesMaxGridIndex,
  marginLeft,
  marginRight
}: {
  points: EchartsPoint<any>[];
  xNames: string[];
  width: number;
  dataSeriesMaxGridIndex: number;
  marginLeft: number;
  marginRight: number;
}) => {
  xAxisData: string[];
  xAxisExtends: XAXisComponentOption[];
  yAxisExtends: YAXisComponentOption[];
  gridExtends: GridComponentOption[];
  seriesextends: SeriesOption[];
} = (params) => {
  const { points, xNames, width, dataSeriesMaxGridIndex, marginLeft, marginRight } = params;
  const xNum = points.length;
  const xlevel: number = xNames.length;
  // 设置x轴的数据
  const xAxisData: string[] = [
    // {value:星期1}
  ];
  // 设置多层的x轴,配置series,grid,xAxis,yAxis
  const xAxisExtends: XAXisComponentOption[] = [];
  const yAxisExtends: YAXisComponentOption[] = [];
  const gridExtends: GridComponentOption[] = [];
  const seriesextends: SeriesOption[] = [];

  // 存放所有层维度数据的数组
  const xAxisArray: { [key: string]: number }[][] = [];
  // 先按维度的层次拼接x的名字
  const tempxaxisname: string[][] = [];
  for (let i: number = 0; i < xNum; i++) {
    const tempxaxislevelname: string[] = [];
    tempxaxislevelname.push(points[i].x[0]);

    for (let j: number = 1; j < xlevel; j++) {
      tempxaxislevelname.push(tempxaxislevelname[j - 1] + ',' + points[i].x[j]);
    }
    tempxaxisname.push(tempxaxislevelname);
  }
  // console.log(tempxaxisname, '按层次拼接好的x的名字---------------');

  // 设置x轴的数据
  for (let i: number = 0; i < xNum; i++) {
    xAxisData.push(tempxaxisname[i][xlevel - 1]);
  }

  // 分维度取出x轴的名字数组
  for (let i: number = 0; i < xlevel; i++) {
    const tempxxaisvalue: { [key: string]: number }[] = [];
    // 该层循环确定一个维度上的名称和所占的单元格的长度
    //   记录当前数据是该层第几块
    let group: number = 1;
    for (let j = 0; j < xNum; j++) {
      if (
        tempxxaisvalue.length > 0 &&
        Object.keys(tempxxaisvalue[tempxxaisvalue.length - 1])[0].slice(
          0,
          Object.keys(tempxxaisvalue[tempxxaisvalue.length - 1])[0].indexOf('%^&group')
        ) === tempxaxisname[j][i].substring(tempxaxisname[j][i].lastIndexOf(',') + 1)
      ) {
        //如果和最后一位重复,则合并单元格,长度+1

        // console.log("重复,需要合并");
        const lastkey: string = Object.keys(tempxxaisvalue[tempxxaisvalue.length - 1])[0] + '';
        const lastvalue: number = Object.values(tempxxaisvalue[tempxxaisvalue.length - 1])[0] as number;
        // console.log(lastkey,'lastkey');
        // console.log(lastvalue,'lastvalue');
        // console.log(tempxxaisvalue[tempxxaisvalue.length-1],'tempxxaisvalue[tempxxaisvalue.length-1][0]');
        tempxxaisvalue[tempxxaisvalue.length - 1] = Object.assign({}, tempxxaisvalue[tempxxaisvalue.length - 1], {
          [lastkey]: lastvalue + 1
        });
      } else {
        // console.log("不重复,不需要合并");
        tempxxaisvalue.push({
          [tempxaxisname[j][i].substring(tempxaxisname[j][i].lastIndexOf(',') + 1) + '%^&group' + group]: 1
        });
        group++;
      }
      //    console.log(tempxxaisvalue,'tempxxaisvalue___________--');
    }
    xAxisArray.push(tempxxaisvalue);
  }

  //    外层循环走完所有的维度都已经拼成了一个对象数组,对象里面分别包裹着每一层维度的名称和对应的长度,一个对象就是一个维度

  // console.log(xAxisArray, '要给多层x轴进行渲染的xAxisArray对象数据----------------');

  // xAxisArray.reverse();

  for (let i: number = 0; i < xlevel; i++) {
    gridExtends.push({
      height: 20,
      bottom: (xlevel - i) * 20 + dataSeriesMaxGridIndex * 20,
      left: marginLeft,
      right: marginRight,
      tooltip: {
        // show: true,
        show: false
      }
    });
    xAxisExtends.push({
      type: 'category',
      gridIndex: i + 1 + dataSeriesMaxGridIndex,
      axisLine: { show: false },
      axisLabel: { show: false },
      axisTick: { show: false }
    });
    yAxisExtends.push({
      type: 'value',
      gridIndex: i + 1 + dataSeriesMaxGridIndex,
      axisLine: { show: false },
      axisLabel: { show: false },
      axisTick: { show: false }
    });
    // 当前该维度的名字字符串和对应的所占单元格长度的字符串

    // console.log(xAxisArray);

    // 当该层的小柱子的个数小于1000的时候,才进行数据的填入,否则返回一个大的整块的柱子
    if (xAxisArray[i].length < 1000) {
      // 依次填入该维度的所有出现的名称与匹配的所占单元格的长度
      for (let j: number = 0; j < xAxisArray[i].length; j++) {
        // 设置填充进bar中的name 的内容和样式,当长度大于当前bar的宽度时,以省略号显示

        // 未处理的原始的拼接后带标志的名字字符串
        const originEverytempXAxisName: string = Object.keys(xAxisArray[i][j])[0];
        // console.log(origintempXAxisName,'origintempXAxisName');
        //每个多层x轴的小柱子的长度
        const originEverytempXAxisValue: number = Object.values(xAxisArray[i][j])[0] as number;

        let tempXAxisname: string = '';
        // tempsingleXAxisName[j].substring(tempsingleXAxisName[j].lastIndexOf(',')+1)为要展示的全部的字符串,末尾带有 %^&group
        let everysingleXAxisName = originEverytempXAxisName.substring(originEverytempXAxisName.lastIndexOf(',') + 1);
        // everysingleXAxisName为处理好的要显示的最简单的对应的名字的全部字符串
        everysingleXAxisName = everysingleXAxisName.slice(0, everysingleXAxisName.indexOf('%^&group'));
        if (everysingleXAxisName.length > (originEverytempXAxisValue / xNum) * 60 * (width / 800) - 1)
          tempXAxisname =
            everysingleXAxisName.substring(0, Math.floor((originEverytempXAxisValue / xNum) * 60 * (width / 800))) +
            '..';
        else tempXAxisname = everysingleXAxisName;

        // console.log('处理完成一个label的显示');

        seriesextends.push(
          buildMultiXSeriesExtendSingle({
            barWidth: originEverytempXAxisValue / xNum,
            dataName: tempXAxisname,
            gridIndex: i + 1 + dataSeriesMaxGridIndex,
            tooltipFormatter: everysingleXAxisName
          })
        );
        // console.log('完成一个seris的设置');
      }
    } else {
      seriesextends.push(
        buildMultiXSeriesExtendSingle({
          barWidth: 1,
          dataName: `!Zoom in to display ${xNames[i]}`,
          gridIndex: i + 1 + dataSeriesMaxGridIndex,
          tooltipFormatter: `${xNames[i]}`
        })
      );
    }

    // console.log(seriesextends, 'seriesextends+++++++++++');
  }

  return {
    xAxisExtends,
    yAxisExtends,
    xAxisData,
    seriesextends,
    gridExtends
  };
};

对于入参的几个参数,进行简单的解释:
xNames:多层x轴每一层x轴的名称,若有三层,名字分别为 年份,月份,日,该值即为['year','month','date']

width:echarts图表的宽度,计算每个小柱子是否省略显示对应名字所需

dataSeriesMaxGridIndex:画原始图形的区域的grid的Index的最大值。例子,若只有一个grid,girdIndex为0,此参数传0,若原始有3个grid区域,gridIndex最大为2,此参数传2

marginLeft, marginRight:图形区域离整个echarts的canvas左右的间距,与原始的grid的left,top设置为一样的值即可。(原始grid一定要设置left与right,且不可使用containLabel:tru属性!!!!否则对不齐多层x轴的grid与原始图形的grid)

points:点的数据,此用的结构为项目中定义好的数据结构

export interface EchartsPoint<T> {
  x: string[];

  y: T;
}

但在多层x轴的代码段中,只用到了字段ximage.png,此时的x的数组的长度一定是与xNames的长度一样,且位置互相对应。继续刚刚的例子,
若xNames:['year','month','date'],
points的mock数据可以是

points: [
  {
    x: ['2021', '10', '2'],
    y: {}
  },
  {
    x: ['2022', '10', '2'],
    y: {}
  },
  {
    x: ['2022', '10', '2'],
    y: {}
  },
  {
    x: ['2022', '10', '3'],
    y: {}
  },
  {
    x: ['2022', '11', '2'],
    y: {}
  },
  {
    x: ['2022', '11', '3'],
    y: {}
  },
  {
    x: ['2022', '11', '3'],
    y: {}
  }
];

PS:此处结构可以简化,项目中统一定义了如此结构,故直接复用了points的结构,其实只需要点的x的数据既可以了


方法中还使用了另一个封装的方法buildMultiXSeriesExtendSingle

// 生成多层x轴多的单个的扩展series数据的函数
export const buildMultiXSeriesExtendSingle: ({
  barWidth,
  dataName,
  gridIndex,
  tooltipFormatter
}: {
  barWidth: number;
  dataName: string;
  gridIndex: number;
  tooltipFormatter: string;
}) => SeriesOption = (params) => {
  const { barWidth, dataName, tooltipFormatter, gridIndex } = params;
  return {
    type: 'bar',
    large: true,
    barWidth: `${barWidth * 100}%`,
    data: [{ name: dataName, value: 1 }],
    barGap: 0,
    label: {
      show: true,
      position: 'inside',
      formatter: '{b}'
    },
    itemStyle: {
      color: '#fff',
      borderColor: 'lightgray',
      borderWidth: 1
    },
    emphasis: {
      disabled: true
    },

    animation: false,
    tooltip: {
      formatter: tooltipFormatter,
      show: true
    },
    xAxisIndex: gridIndex,
    yAxisIndex: gridIndex
  };
};

该方法的返回值,如何在实际场景中使用,即将返回的grid、xAxis、yAxis、series拼接在options的这些字段的后面,返回的xAxisData设置为原始的x轴的data即可

const multiXExtends = buildMultiXExtends({
    points,
    xNames,
    width,
    dataSeriesMaxGridIndex: 0,
    marginLeft: 80,
    marginRight: 25
  });

  // 设置x轴的数据
  const xAxisData: string[] = multiXExtends.xAxisData;
  // 设置多层的x轴,配置series,grid,xAxis,yAxis
  const xAxisExtends: XAXisComponentOption[] = multiXExtends.xAxisExtends;
  const yAxisExtends: YAXisComponentOption[] = multiXExtends.yAxisExtends;
  const gridExtends: GridComponentOption[] = multiXExtends.gridExtends;

  const seriesextends: SeriesOption[] = multiXExtends.seriesextends;



  const yAxis_grid0: YAXisComponentOption = {
    type: 'value',
    scale: true,
    gridIndex: 0,
    axisLine: {
      onZero: false
    },
    name: yAxisName,
    nameGap: 65,
    nameLocation: 'middle',
    nameTextStyle: {
      fontWeight: 'bold'
    },
  };

  const xAxis_grid0: XAXisComponentOption = {
    type: 'category',
    data: xAxisData,
    position: 'bottom',
    gridIndex: 0,
    name: xAxisName,
    nameLocation: 'middle',
    nameGap: xlevel * 20 + 5,
    nameTextStyle: {
      fontWeight: 'bold'
    },
    axisTick: { show: false },
    axisLine: { show: false },
    axisLabel: { show: false }
  };

  const grid_grid0 =  {
      top: 60,
      bottom: (xlevel + 1) * 20,
      left: '80px',
      right: '25px'
    }

  const option: EChartsOption = {
    // title: {
    //   text: titleinfo,
    // },
    tooltip: {
      trigger: 'item',
      confine: true,
      show: true
    },

    grid: [grid_grid0, ...gridExtends],
    legend: {
      type: 'scroll',
      orient: 'horizontal',
      width: '60%',
      height: '10px',
      top: '5px',
      left: '10px',
      tooltip: {
        show: true,
        extraCssText: 'white-space:pre-wrap'
      },
      data: yNames
    },
    xAxis: [
      xAxis_grid0,
      // 下面都是多的x轴
      ...xAxisExtends
    ],

    yAxis: [
      yAxis_grid0,
      // 下面都是多的y轴
      ...yAxisExtends
    ],
    series: [...series, ...seriesextends]
  };

总结:传入对应的结构匹配的入参之后,将返回的返回结构,拼入options的对应的结构上。
注意点:
①入参中的marginLeft和marginRight 需要和options的原始grid的left和right相同
②入参中的ponits的结构,在不改变封装内部代码的情况下,要与定义的结构相同,最简形式:

export interface EchartsPoint<T> {
  x: string[];
}

③points下x数组与xNames的数组的值要一一对应(没有采用KeyValue的形式,省传输时的空间)


2 声望2 粉丝