关于echarts的N个图表N条线N种单位的封装

前言:我们业务有种图表展示是后端返回N个图表,然后每个图表里面的线不一定,每个线都有各自的单位换算,所有需要多个Y轴,且如果几条线的单位相同,那就只出现一种单位轴线,多个Y轴需要推算每个Y轴的偏移量(offset),一旦legend点击某个线条置灰,那个Y轴就要消失,重新计算Y轴偏移,确保图表不拥挤,美观。

放个效果吧
请添加图片描述

基于此,我封装了一个方法。代码有点乱糟糟,仅作自己备忘。
新建EchartsUtils.js,如下

import React from 'react';
import * as echarts from 'echarts';
import { EleResize } from '@/utils/esresize';
import dayjs from 'dayjs';
import { clearDeep } from '@/utils/validate';
const color = [
  'rgba(79,128,220,0.9)',
  'rgba(209,52,186,0.9)',
  'rgba(245,200,118,0.9)',
  'rgba(255,243,108,0.9)',
  'rgba(149,196,101,0.9)',
  'rgba(171,205,248,0.9)',
  'rgba(10,141,255,0.9)',
  'rgba(204,153,255,0.9)',
  'rgba(212,108,34,0.9)',
  'rgba(153,255,255,0.9)',
  'rgba(255,153,204,0.9)',
  'rgba(255,204,204,0.9)',
  'rgba(153,204,204,0.9)',
  'rgba(204,153,153,0.9)',
  'rgba(153,255,153,0.9)',
  'rgba(149,196,101,0.9)',
  'rgba(215,129,19,0.9)',
  'rgba(185,109,248,0.9)',
];
/**
 * 封装图表方法二  通用监控图表  结合后端
 * @author 
 * charts   所有图表数据  [Array]
   如下 :
        [
          {
            "chartName": "",//图表名称
            "metricLines": [//指标线集合
              {
                "chartNodes": [//x,y轴
                  {
                    "metricValue": "", //值
                    "timestamp": 0 //时间
                  }
                ],
                "metricName": "",//指标名,即线条
                "unit": ""//指标单位
              }
            ]
          }
        ]
 * chartById  图表所存放容器id标识
 * ***/
export const toShowGeneralGraphQueryEchartsOptionUtils = (charts, chartById) => {
  const bigNumberTransform = (value) => {
    const newValue = ['', '', ''];
    let fr = 1000;
    let num = 3;
    let text1 = '';
    let fm = 1;
    while (value / fr >= 1) {
      fr *= 10;
      num += 1;
      // console.log('数字', value / fr, 'num:', num)
    }
    if (num <= 4) {
      // 千
      newValue[0] = parseInt(value / 1000) + '';
      newValue[1] = '千';
    } else if (num <= 8) {
      // 万
      text1 = parseInt(num - 4) / 3 > 1 ? '千万' : '万';
      // tslint:disable-next-line:no-shadowed-variable
      fm = text1 === '万' ? 10000 : 10000000;
      if (value % fm === 0) {
        newValue[0] = parseInt(value / fm) + '';
      } else {
        newValue[0] = parseFloat(value / fm).toFixed(2) + '';
      }
      newValue[1] = text1;
    } else if (num <= 16) {
      // 亿
      text1 = (num - 8) / 3 > 1 ? '千亿' : '亿';
      text1 = (num - 8) / 4 > 1 ? '万亿' : text1;
      text1 = (num - 8) / 7 > 1 ? '千万亿' : text1;
      // tslint:disable-next-line:no-shadowed-variable
      fm = 1;
      if (text1 === '亿') {
        fm = 100000000;
      } else if (text1 === '千亿') {
        fm = 100000000000;
      } else if (text1 === '万亿') {
        fm = 1000000000000;
      } else if (text1 === '千万亿') {
        fm = 1000000000000000;
      }
      if (value % fm === 0) {
        newValue[0] = parseInt(value / fm) + '';
      } else {
        newValue[0] = parseFloat(value / fm).toFixed(2) + '';
      }
      newValue[1] = text1;
    }
    if (value < 1000) {
      newValue[0] = value + '';
      newValue[1] = '';
    }
    return newValue.join('');
  };

  const toChartValues = (data) => {
    let obj = {
      max: '-',
      min: '-',
      avg: '-',
    };
    if (typeof data === 'object' && data.length > 0 && data?.some((i) => i == 0 || Number(i))) {
      let arr = data.map((i) => i && Number(i)).filter((o) => Number.isFinite(o));
      obj.max = Math.max(...arr).toFixed(2);
      obj.min = Math.min(...arr).toFixed(2);
      let sum = 0;
      for (let i = 0; i < arr.length; i++) {
        sum += Number(arr[i]);
        obj.avg = (sum / arr.length).toFixed(2);
      }
    }
    return obj;
  };

//单位换算方法,里面的分别换算就不赘述了
  const toShowSeriesValue = (val, unit) => {
    let value = val;
    if (value === '-') {
      return value;
    }
    if (Number(value) || Number(value) == 0) {
      value = Number(value);
    } else {
      return value;
    }
    switch (unit) {
      case '℃':
        return value.toFixed(2) + '℃';
      case '%':
        return value.toFixed(2) + '%';
      case 'ms':
        return value.toFixed(2) + 'ms';
      case 'kbps':
        return bandwidthDisplay(value);
      case 'bps':
        return bandwidthToSize(value);
      case 'Bps':
        return bandwidthToSize1(value);
      case 'bit':
        return throughputDisplay4(value);
      case 'Byte':
        return throughputDisplay(value);
      case 'KB':
        return throughputDisplay2(value);
      case 'MB':
        return throughputDisplay3(value);
      case 's':
        return formatSeconds(value);
      default:
        return value + unit;
      //return bigNumberTransform(value)
    }
  };

  const deWeight = (data) => {
    let arr = [...data];
    for (var i = 0; i < arr.length - 1; i++) {
      for (var j = i + 1; j < arr.length; j++) {
        if (arr[i].unit == arr[j].unit) {
          arr.splice(j, 1);
          //因为数组长度减小1,所以直接 j++ 会漏掉一个元素,所以要 j--
          j--;
        }
      }
    }
    return arr;
  };

  const handleIndex = (arr1, arr2) => {
    for (var i = 0; i < arr1.length; i++) {
      for (var j = 0; j < arr2.length; j++) {
        if (arr1[i].unit == arr2[j].unit) {
          arr1[i].yAxisIndex = j;
        }
      }
    }
    return arr1;
  };

  const handleIndexUnit = (seriesName, data) => {
    for (var i = 0; i < data.length; i++) {
      if (seriesName == data[i].name) {
        return data[i].unit;
      }
    }
  };



  if (charts.length > 0) {
    for (let i = 0; i < charts.length; i++) {
      //console.log("图表个数=== ",  charts[i]);//图表个数
      let totalSeries = [];
      let totalYAxis = [];
      for (let j = 0; j < charts[i].metricLines.length; j++) {
        //data的数量就是图表中线的数量,实现动态加载
        //console.log('--------------图表的线条',charts[i].metricLines[j])
        let max =
          toChartValues(charts[i]?.metricLines[j]?.chartNodes?.map((i) => i.metricValue))?.max ||
          '-';
        let min =
          toChartValues(charts[i].metricLines[j]?.chartNodes?.map((i) => i.metricValue))?.min ||
          '-';
        let avg =
          toChartValues(charts[i].metricLines[j]?.chartNodes?.map((i) => i.metricValue))?.avg ||
          '-';
        let mySeries = {
          unit: charts[i]?.metricLines[j]?.unit,
          name:
            charts[i].metricLines[j].metricName +
            ' ' +
            ' max: ' +
            toShowSeriesValue(max, charts[i]?.metricLines[j]?.unit) +
            ' min: ' +
            toShowSeriesValue(min, charts[i]?.metricLines[j]?.unit) +
            ' avg: ' +
            toShowSeriesValue(avg, charts[i]?.metricLines[j]?.unit),
          type: 'line',
          symbol: 'circle', //实心圆
          showSymbol: true, //小点点。每个数据的点是否显示
          smooth: true, //平滑
          symbolSize: [2, 2],
          lineStyle: {
            color: color[j % 14],
            width: 2,
          },
          animation: true,
          itemStyle: {
            color: color[j % 14],
          },
          yAxisIndex: j,
          data:
            charts[i]?.metricLines[j]?.chartNodes?.map((o) => {
              return [dayjs(o.timestamp).format('YYYY-MM-DD HH:mm:ss'), o.metricValue];
            }) || [],
        }; //series每条线的数据end
        totalSeries.push(mySeries);
        let myAxis = {
          unit: charts[i]?.metricLines[j]?.unit,
          position: j % 2 == 0 ? 'left' : 'right',
          offset: j % 2 == 0 ? (j != 0 ? 30 * (j - 1) + 30 : 0) : 30 * (j - 1),
          show: true,
          nameTextStyle: {
            color: color[j % 14],
            fontWeight: 'normal',
            fontSize: 10,
          },
          axisLine: {
            show: false, //坐标轴是否显示
            lineStyle: {
              color: color[j % 14],
            },
          },
          axisLabel: {
            formatter: function (value, index) {
              return toShowSeriesValue(value, charts[i]?.metricLines[j]?.unit);
            },
          },
          axisTick: {
            show: false, //坐标轴刻度
          },
          bz:
            charts[i].metricLines[j].metricName +
            ' ' +
            ' max: ' +
            toShowSeriesValue(max, charts[i]?.metricLines[j]?.unit) +
            ' min: ' +
            toShowSeriesValue(min, charts[i]?.metricLines[j]?.unit) +
            ' avg: ' +
            toShowSeriesValue(avg, charts[i]?.metricLines[j]?.unit),
        };
        totalYAxis.push(myAxis);
      }
      // const headerStyle = document.getElementById(chartById+i);
      // if(headerStyle) {
      //     if((totalSeries.length)/2<3){
      //       headerStyle.style = `height:100%;padding-right:-70px`
      //     }else{
      //       headerStyle.style = `height:100%;padding-bottom:-${(totalSeries.length)/2*32}px`
      //     }
      // }

      let option = {
        title: {
          text: charts[i].chartName,
          textStyle: {
            fontSize: 14,
            align: 'left',
            color: 'rgba(42,25,91,1)', //title颜色灰色应该就可以
          },
          x: 'center',
        },
        grid: {
          //bottom:(totalSeries.length)/2<1?(totalSeries.length)*50:(totalSeries.length)/2*66,
          bottom:
            totalSeries.length / 2 < 1
              ? totalSeries.length * 50
              : totalSeries.length < 4
              ? (totalSeries.length / 2) * 66
              : 60,
          right: 40,
          left: 40,
          //right:(totalSeries.length)/2<1?'8%': `${(totalSeries.length)/2*8}%`,
          //left:(totalSeries.length)/2<1?'8%':(totalSeries.length)%2==0 ? `${(totalSeries.length)/2*8}%` :`${(totalSeries.length)/2*9}%`,
          containLabel: true,
        },
        // grid:{
        //     top:'10%',right:'8%', left:'12%',bottom:70
        // },
        dataZoom: [
          {
            type: 'inside',
            start: 0,
            end: 100,
          },
        ],
        tooltip: {
          confine: true,
          trigger: 'axis',
          backgroundColor: '#fff',
          textStyle: {
            fontSize: 14,
            align: 'left',
            color: '#808080', //title颜色灰色应该就可以
          },
          borderWidth: 1,
          borderColor: '#ccc',
          borderRadius: 8, //边框圆角
          padding: [10, 10, 10, 10], // 内边距
          extraCssText: 'height:auto',
          formatter: function (data1) {
            //console.log('data1===========',data1);
            let detial = data1[0].axisValueLabel + '<br/>';
            if (data1.length > 0) {
              data1.map((item, index) => {
                let itemValue =
                  item.value[1] == 0 || item.value[1]
                    ? toShowSeriesValue(item.value[1],totalSeries.find(array => array.name === item.seriesName)?.unit)
                    : '-';
                detial =
                  detial + item.marker + item.seriesName.split(' ')[0] + ' ' + itemValue + '<br/>';
              });
            }
            return detial;
          },
        },
        legend: [
          {
            icon: 'circle',
            bottom: 0,
            type: 'scroll',
            itemGap: 8,
            itemWidth: 10,
            data: totalSeries?.map((i) => i.name)?.filter((o, index) => index % 2 == 0),
          },
          {
            icon: 'circle',
            bottom: 30,
            type: 'scroll',
            itemGap: 8,
            itemWidth: 10,
            data: totalSeries?.map((i) => i.name)?.filter((o, index) => index % 2 != 0),
          },
        ],
        //这里legend,grid二次覆盖上面的是支持两种legend样式,不必纠结
        legend: {
          icon: 'circle',
          itemGap: 6,
          type: 'scroll',
          top: '78%',
          left: 'center',
          orient: 'vertical',
          textStyle: {
            fontSize: 10,
          },
          formatter(val) {
            return val;
          },
          data: totalSeries?.map((i) => i.name),
        },
        grid: {
          right: 30,
          left: 30,
          top: '16%',
          bottom: '30%',
          containLabel: true,
        },
        xAxis: [
          {
            type: 'category',
            nameTextStyle: {
              color: 'rgba(147,159,189,1)',
              fontSize: 12,
            },
            axisLine: {
              show: false,
              lineStyle: {
                color: 'rgba(147,159,189,1)',
              },
            },
            axisTick: {
              show: false,
            },
          },
        ],
        yAxis: deWeight(totalYAxis),
        series: handleIndex(totalSeries, deWeight(totalYAxis)),
        };

      if (document.getElementById(chartById + i)) {
        let myChart = echarts.init(document.getElementById(chartById + i));
        myChart.clear();
        myChart.setOption(option, true);
        let echartsid = document.getElementById(chartById + i);
        let listener = function () {
          myChart.resize();
        };
        //EleResize是让图表随着容器大小变化自由伸缩的方法,下面会贴出来
        EleResize.on(echartsid, listener);
        myChart.on('legendselectchanged', function (params) {
          let select_value = Object.values(params.selected);
          let YAxis = totalYAxis.filter((item, index) => {
            return select_value[index];
          });
          totalYAxis = totalYAxis.map((o,index)=>{
            let j = YAxis.findIndex(array => array.bz === o.bz)<0?999:YAxis.findIndex(array => array.bz === o.bz);
            return {
              ...o,
              position: j % 2 == 0 ? 'left' : 'right',
              offset: j % 2 == 0 ? (j != 0 ? 30 * (j - 1) + 30 : 0) : 30 * (j - 1),
            }
           });
           let option = {
            title: {
              text: charts[i].chartName,
              textStyle: {
                fontSize: 14,
                align: 'left',
                color: 'rgba(42,25,91,1)', //title颜色灰色应该就可以
              },
              x: 'center',
            },
            grid: {
              //bottom:(totalSeries.length)/2<1?(totalSeries.length)*50:(totalSeries.length)/2*66,
              bottom:
                totalSeries.length / 2 < 1
                  ? totalSeries.length * 50
                  : totalSeries.length < 4
                  ? (totalSeries.length / 2) * 66
                  : 60,
              right: 40,
              left: 40,
              //right:(totalSeries.length)/2<1?'8%': `${(totalSeries.length)/2*8}%`,
              //left:(totalSeries.length)/2<1?'8%':(totalSeries.length)%2==0 ? `${(totalSeries.length)/2*8}%` :`${(totalSeries.length)/2*9}%`,
              containLabel: true,
            },
            // grid:{
            //     top:'10%',right:'8%', left:'12%',bottom:70
            // },
            dataZoom: [
              {
                type: 'inside',
                start: 0,
                end: 100,
              },
            ],
            tooltip: {
              confine: true,
              trigger: 'axis',
              backgroundColor: '#fff',
              textStyle: {
                fontSize: 14,
                align: 'left',
                color: '#808080', //title颜色灰色应该就可以
              },
              borderWidth: 1,
              borderColor: '#ccc',
              borderRadius: 8, //边框圆角
              padding: [10, 10, 10, 10], // 内边距
              extraCssText: 'height:auto',
              formatter: function (data1) {
                //console.log('data1===========',data1);
                let detial = data1[0].axisValueLabel + '<br/>';
                if (data1.length > 0) {
                  data1.map((item, index) => {
                    let itemValue =
                      item.value[1] == 0 || item.value[1]
                        ? toShowSeriesValue(item.value[1],totalSeries.find(array => array.name === item.seriesName)?.unit)
                        : '-';
                    detial =
                      detial + item.marker + item.seriesName.split(' ')[0] + ' ' + itemValue + '<br/>';
                  });
                }
                return detial;
              },
            },
            legend: [
              {
                icon: 'circle',
                bottom: 0,
                type: 'scroll',
                itemGap: 8,
                itemWidth: 10,
                data: totalSeries?.map((i) => i.name)?.filter((o, index) => index % 2 == 0),
              },
              {
                icon: 'circle',
                bottom: 30,
                type: 'scroll',
                itemGap: 8,
                itemWidth: 10,
                data: totalSeries?.map((i) => i.name)?.filter((o, index) => index % 2 != 0),
              },
            ],
            legend: {
              icon: 'circle',
              itemGap: 6,
              type: 'scroll',
              top: '78%',
              left: 'center',
              orient: 'vertical',
              textStyle: {
                fontSize: 10,
              },
              formatter(val) {
                return val;
              },
              data: totalSeries?.map((i) => i.name),
              selected: Object.fromEntries(totalSeries?.map((i,index) => {
                let bool = select_value[index] ? true : false;
                console.log(bool)
                return [
                  [i.name],bool
                ]
              }))
            },
            grid: {
              right: 30,
              left: 30,
              top: '16%',
              bottom: '30%',
              containLabel: true,
            },
            xAxis: [
              {
                type: 'category',
                nameTextStyle: {
                  color: 'rgba(147,159,189,1)',
                  fontSize: 12,
                },
                axisLine: {
                  show: false,
                  lineStyle: {
                    color: 'rgba(147,159,189,1)',
                  },
                },
                axisTick: {
                  show: false,
                },
              },
            ],
            yAxis: deWeight(totalYAxis),
            series: handleIndex(totalSeries, deWeight(totalYAxis)),
           };
           myChart.clear();
           myChart.setOption(option, true);
        });
      }
    }
  }
};

esresize.js文件如下:

var EleResize = {
  _handleResize: function (e) {
    var ele = e.target || e.srcElement;
    var trigger = ele.__resizeTrigger__;
    if (trigger) {
      var handlers = trigger.__z_resizeListeners;
      if (handlers) {
        var size = handlers.length;
        for (var i = 0; i < size; i++) {
          var h = handlers[i];
          var handler = h.handler;
          var context = h.context;
          handler.apply(context, [e]);
        }
      }
    }
  },
  _removeHandler: function (ele, handler, context) {
    var handlers = ele.__z_resizeListeners;
    if (handlers) {
      var size = handlers.length;
      for (var i = 0; i < size; i++) {
        var h = handlers[i];
        if (h.handler === handler && h.context === context) {
          handlers.splice(i, 1);
          return;
        }
      }
    }
  },
  _createResizeTrigger: function (ele) {
    var obj = document.createElement('object');
    obj.setAttribute(
      'style',
      'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden;opacity: 0; pointer-events: none; z-index: -1;',
    );
    obj.onload = EleResize._handleObjectLoad;
    obj.type = 'text/html';
    ele.appendChild(obj);
    obj.data = 'about:blank';
    return obj;
  },
  _handleObjectLoad: function (evt) {
    this.contentDocument.defaultView.__resizeTrigger__ = this.__resizeElement__;
    this.contentDocument.defaultView.addEventListener('resize', EleResize._handleResize);
  },
};
if (document.attachEvent) {
  // ie9-10
  EleResize.on = function (ele, handler, context) {
    var handlers = ele.__z_resizeListeners;
    if (!handlers) {
      handlers = [];
      ele.__z_resizeListeners = handlers;
      ele.__resizeTrigger__ = ele;
      ele.attachEvent('onresize', EleResize._handleResize);
    }
    handlers.push({
      handler: handler,
      context: context,
    });
  };
  EleResize.off = function (ele, handler, context) {
    var handlers = ele.__z_resizeListeners;
    if (handlers) {
      EleResize._removeHandler(ele, handler, context);
      if (handlers.length === 0) {
        ele.detachEvent('onresize', EleResize._handleResize);
        delete ele.__z_resizeListeners;
      }
    }
  };
} else {
  EleResize.on = function (ele, handler, context) {
    var handlers = ele.__z_resizeListeners;
    if (!handlers) {
      handlers = [];
      ele.__z_resizeListeners = handlers;

      if (getComputedStyle(ele, null).position === 'static') {
        ele.style.position = 'relative';
      }
      var obj = EleResize._createResizeTrigger(ele);
      ele.__resizeTrigger__ = obj;
      obj.__resizeElement__ = ele;
    }
    handlers.push({
      handler: handler,
      context: context,
    });
  };
  EleResize.off = function (ele, handler, context) {
    var handlers = ele.__z_resizeListeners;
    if (handlers) {
      EleResize._removeHandler(ele, handler, context);
      if (handlers.length === 0) {
        var trigger = ele.__resizeTrigger__;
        if (trigger) {
          trigger.contentDocument.defaultView.removeEventListener(
            'resize',
            EleResize._handleResize,
          );
          ele.removeChild(trigger);
          delete ele.__resizeTrigger__;
        }
        delete ele.__z_resizeListeners;
      }
    }
  };
}
export { EleResize };

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值