echarts柱状图+增长率

194 阅读3分钟

目的:

需在柱状图中展示每个月份的数据增长率,如下图效果:

实现:

使用到echarts中的custom series,通过编写renderItem来实现相邻两个数据的增长率曲线,该图表有两个custom series实现。

完整代码如下:

<template>
  <div class="chart-bar" ref="chartRef"></div>
</template>
<script setup lang="ts">
  import type { Ref } from 'vue';
  import echarts from '/@/utils/echarts';
  import { useECharts } from '/@/hooks/useECharts';

  const props = defineProps({
    loading: Boolean,
  });

  const chartRef = ref<HTMLDivElement | null>(null);
  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);

  const heatCount = 3; // 供暖方式数量
  const categoryCount = 5; //x轴展示数目

  const xAxisData: string[] = ['11月', '12月', '1月', '2月', '3月']; // x轴数据
  const customData: number[][] = []; // 同比增长率数据
  let legendData: string[] = []; // 图示数据
  const dataList: number[][] = []; // 柱状图数据
  let color = ['rgba(7, 147, 162', 'rgba(133, 123, 96', 'rgba(16, 126, 77'];

  legendData.push('同比增长率');
  legendData = legendData.concat(['数据1', '数据2', '数据3']);

  const encodeY = []; // 定义映射到y轴的数据维度
  for (let i = 0; i < heatCount; i++) {
    // legendData.push(2010 + i + '');
    dataList.push([]);
    encodeY.push(1 + i);
  }

  for (let i = 0; i < categoryCount; i++) {
    let val = Math.random() * 200;
    // xAxisData.push('category' + i);
    let customVal = [i];
    customData.push(customVal);

    for (let j = 0; j < dataList.length; j++) {
      let value =
        j === 0
          ? echarts.number.round(val, 2)
          : echarts.number.round(Math.max(0, dataList[j - 1][i] + Math.random() * 100), 2);
      dataList[j].push(value);
      customVal.push(value);
    }
  }
  watch(
    () => props.loading,
    () => {
      if (props.loading) {
        return;
      }

      setOptions({
        tooltip: {
          show: true,
          trigger: 'item',
          textStyle: {
            color: '#fff',
          },
          backgroundColor: 'rgba(0,0,0,0.2)', // 背景
          padding: [8, 10], //内边距
          extraCssText: 'box-shadow: 0 0 3px rgba(255, 255, 255, 0.4);', //添加阴影
          formatter: function (params) {
            if (params.componentSubType !== 'custom')
              return params.name + '<br>' + params.seriesName + ' ' + params.value + ' kW';
            else return '';
          },
        },
        grid: {
          left: '5%',
          top: '30%',
          right: '5%',
          bottom: '1%',
          containLabel: true,
        },
        legend: [
          {
            data: ['数据1', '数据2', '数据3'],
            top: 10,
            right: 10,
            itemHeight: 10,
            itemWidth: 20,
            textStyle: {
              color: '#fff',
            },
          },
          {
            data: ['同比增长率'],
            top: 35,
            right: 10,
            icon: 'rect',
            itemWidth: 16,
            itemHeight: 4,
            textStyle: {
              color: '#fff',
            },
          },
        ],
        xAxis: {
          type: 'category',
          // data: data.map((n) => n.time),
          axisLine: {
            lineStyle: {
              color: '#18649e',
              width: 2,
            },
          },
          axisLabel: {
            // rotate: 40,
            margin: 16,
            color: '#fff',
          },
          axisTick: {
            show: false,
          },
          data: xAxisData,
        },
        yAxis: {
          type: 'value',
          name: '万元',
          min: 0,
          // max: 150,
          nameTextStyle: {
            color: '#fff',
            verticalAlign: 'middle',
          },
          axisLabel: {
            // rotate: 40,
            margin: 16,
            color: '#fff',
          },
          axisLine: {
            show: true,
            lineStyle: {
              color: '#18649e',
              width: 2,
            },
          },
          splitLine: {
            lineStyle: {
              color: '#224180',
              type: 'dashed',
            },
          },
        },
        series: [
          {
            type: 'custom', // custom 系列需要开发者自己提供图形渲染的逻辑
            name: '同比增长率',
            color: 'rgba(0, 255, 255)',
            // 绘制一个x轴坐标点的数据
            renderItem: function (params, api) {
              // params:包含了当前数据信息和坐标系的信息。 api:是一些开发者可调用的方法集合。
              let xValue = api.value(0); // 表示取出当前 dataItem 中第一个维度的数值。api.value(0) 为x轴数据
              let currentSeriesIndices = [1, 2]; // 前两组数
              let barLayout = api.barLayout({
                // 在柱状图上附加信息时,使用barLayout方法得到layout信息
                barGap: '30%',
                barCategoryGap: '20%',
                count: 3,
              });
              let points = [];
              // 绘制前两组数据增长率  params.seriesIndex0,
              for (let i = 0; i < currentSeriesIndices.length; i++) {
                let seriesIndex = currentSeriesIndices[i];
                // debugger;
                // if (seriesIndex !== params.seriesIndex) {
                let point = api.coord([xValue, api.value(seriesIndex)]);
                point[0] += barLayout[i].offsetCenter; // 数据点x轴位置
                point[1] -= 10; // 数据点y轴位置
                points.push(point);
                // }
              }
              let style = api.style({
                stroke: api.visual('color') as string,
                fill: 'none',
                lineJoin: 'bevel',
              });
              return {
                type: 'polyline',
                shape: {
                  points: points,
                },
                style: style,
              };
            },
            label: {
              show: true,
              position: 'middle',
              formatter: (params) => {
                return `${(((params.data[2] - params.data[1]) / params.data[1]) * 100).toFixed(
                  1,
                )}%`;
              },
            },
            labelLayout: function (params) {
              return {
                x: params.rect.x - 10,
                y: params.rect.y - 10,
                verticalAlign: 'middle',
                align: 'left',
              };
            },
            itemStyle: {
              borderWidth: 2,
            },
            encode: {
              x: 0, // 可以定义 data 的哪个维度被编码成什么, data的每一列称为一个『维度』。这里维度0映射到x轴,维度encodeY映射到y轴
              y: encodeY,
            },
            data: customData,
            // data: arrYLabel2,
            z: 100,
          },
          {
            type: 'custom', // custom 系列需要开发者自己提供图形渲染的逻辑
            name: '同比增长率2',
            color: 'rgba(0, 255, 255)',
            renderItem: function (params, api) {
              // params:包含了当前数据信息和坐标系的信息。 api:是一些开发者可调用的方法集合。
              let xValue = api.value(0); // 表示取出当前 dataItem 中第一个维度的数值。
              // let currentSeriesIndices = api.currentSeriesIndices(); // 得到series的当前index [0,1,2,3]
              let currentSeriesIndices = [2, 3]; // 后两组数
              let barLayout = api.barLayout({
                // 在柱状图上附加信息时,使用barLayout方法得到layout信息
                barGap: '30%',
                barCategoryGap: '20%',
                count: 3,
              });
              let points = [];
              for (let i = 0; i < currentSeriesIndices.length; i++) {
                let seriesIndex = currentSeriesIndices[i];
                let point = api.coord([xValue, api.value(seriesIndex)]);
                point[0] += barLayout[i + 1].offsetCenter;
                point[1] -= 10;
                points.push(point);
              }
              let style = api.style({
                stroke: api.visual('color') as string,
                fill: 'none',
                lineJoin: 'bevel',
              });
              return {
                type: 'polyline',
                shape: {
                  points: points,
                },
                style: style,
              };
            },
            label: {
              show: true,
              position: 'middle',
              formatter: (params) => {
                return `${(((params.data[3] - params.data[2]) / params.data[2]) * 100).toFixed(
                  1,
                )}%`;
              },
            },
            labelLayout: function (params) {
              return {
                x: params.rect.x + 10,
                y: params.rect.y - 5,
                verticalAlign: 'middle',
                align: 'left',
              };
            },
            itemStyle: {
              borderWidth: 2,
            },
            encode: {
              x: 0, // 可以定义 data 的哪个维度被编码成什么, data的每一列称为一个『维度』。这里维度0映射到x轴,维度encodeY映射到y轴
              y: encodeY,
            },
            data: customData,
            // data: arrYLabel2,
            z: 100,
          },
          ...dataList.map(function (data, index) {
            return {
              type: 'bar',
              animation: false,
              name: legendData[index + 1],
              color: color[index] + ')',
              data: data,
            } as echarts.BarSeriesOption;
          }),
        ],
      });
    },
    { immediate: true },
  );
</script>
<style lang="less" scoped>
  .chart-bar {
    width: 100%;
    height: 200px;
    // margin-top: 30px;
  }
</style>