首要污染物准确率评估echarts category配置项内容和展示

AQI预测 - 预测准确性评估 - 首要污染物准确率评估

配置项如下
      const uploadedDataURL = '/asset/get/s/data-1635390802457-HRSoVX4EN.json';

const symbolPass =
  'image://data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjM0MjAwNDAzNDA1IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjI1MDAiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PGRlZnM+PHN0eWxlIHR5cGU9InRleHQvY3NzIj48L3N0eWxlPjwvZGVmcz48cGF0aCBkPSJNNTEyIDEwMjRDMjI5LjI0OCAxMDI0IDAgNzk0Ljc1MiAwIDUxMlMyMjkuMjQ4IDAgNTEyIDBzNTEyIDIyOS4yNDggNTEyIDUxMi0yMjkuMjQ4IDUxMi01MTIgNTEyeiBtLTExNC4xNzYtMzEwLjk1NDY2N2E1My4zMzMzMzMgNTMuMzMzMzMzIDAgMCAwIDc1LjQzNDY2NyAwbDMyMy4zMjgtMzIzLjMyOGE1My4zMzMzMzMgNTMuMzMzMzMzIDAgMSAwLTc1LjQzNDY2Ny03NS40MzQ2NjZsLTI4Ny45MTQ2NjcgMjgzLjMwNjY2Ni0xMjguODUzMzMzLTEyOC44NTMzMzNhNTMuMzMzMzMzIDUzLjMzMzMzMyAwIDEgMC03NS40MzQ2NjcgNzUuNDM0NjY3bDE2OC44NzQ2NjcgMTY4Ljg3NDY2NnoiIGZpbGw9IiMxYWZhMjkiIHAtaWQ9IjI1MDEiIGRhdGEtc3BtLWFuY2hvci1pZD0iYTMxM3guNzc4MTA2OS4wLmkwIiBjbGFzcz0iIj48L3BhdGg+PC9zdmc+';
const symbolFail =
  'image://data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjM0MjgwMDM2OTY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjIwOTkiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PGRlZnM+PHN0eWxlIHR5cGU9InRleHQvY3NzIj48L3N0eWxlPjwvZGVmcz48cGF0aCBkPSJNNTEyIDMyQzI1MS40Mjg1NzE1NjI1IDMyIDMyIDI1MS40Mjg1NzE1NjI1IDMyIDUxMnMyMTkuNDI4NTcxNTYyNSA0ODAgNDgwIDQ4MCA0ODAtMjE5LjQyODU3MTU2MjUgNDgwLTQ4MC0yMTkuNDI4NTcxNTYyNS00ODAtNDgwLTQ4MHogbTIwNS43MTQyODUzMTI1IDYxNy4xNDI4NTY4NzVjMjAuNTcxNDI4NDM3NSAyMC41NzE0Mjg0Mzc1IDIwLjU3MTQyODQzNzUgNDggMCA2MS43MTQyODYyNDk5OTk5OTQtMjAuNTcxNDI4NDM3NSAyMC41NzE0Mjg0Mzc1LTQ4IDIwLjU3MTQyODQzNzUtNjEuNzE0Mjg1MzEyNDk5OTk2IDBsLTEzNy4xNDI4NTY4NzUtMTM3LjE0Mjg1NzgxMjVMMzc0Ljg1NzE0MzEyNSA3MTcuNzE0Mjg1MzEyNWMtMjAuNTcxNDI4NDM3NSAyMC41NzE0Mjg0Mzc1LTQ4IDIwLjU3MTQyODQzNzUtNjguNTcxNDI4NDM3NSAwcy0yMC41NzE0Mjg0Mzc1LTU0Ljg1NzE0MzEyNSAwLTY4LjU3MTQyODQzNzVsMTQ0LTE0NC0xMzcuMTQyODU3ODEyNS0xMzcuMTQyODU2ODc1Yy0yMC41NzE0Mjg0Mzc1LTEzLjcxNDI4NTMxMjUwMDAwMS0yMC41NzE0Mjg0Mzc1LTQxLjE0Mjg1Njg3NSAwLTYxLjcxNDI4NTMxMjQ5OTk5NiAyMC41NzE0Mjg0Mzc1LTIwLjU3MTQyODQzNzUgNDgtMjAuNTcxNDI4NDM3NSA2MS43MTQyODYyNDk5OTk5OTQgMGwxMzcuMTQyODU2ODc1IDEzNy4xNDI4NTY4NzUgMTQ0LTE0NGMyMC41NzE0Mjg0Mzc1LTIwLjU3MTQyODQzNzUgNDgtMjAuNTcxNDI4NDM3NSA2OC41NzE0Mjg0Mzc1IDAgMjAuNTcxNDI4NDM3NSAyMC41NzE0Mjg0Mzc1IDIwLjU3MTQyODQzNzUgNDggMCA2OC41NzE0Mjg0Mzc1TDU4MC41NzE0Mjg0Mzc1IDUxMmwxMzcuMTQyODU2ODc1IDEzNy4xNDI4NTY4NzV6IiBmaWxsPSIjZjE1MDQ3IiBwLWlkPSIyMTAwIj48L3BhdGg+PC9zdmc+';

const colors = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'];

// AQI 等级
const AQI_LEVELS = [
  {
    icon: 'k1',
    label: '优',
    otherName: '一级',
    value: 1,
    min: 0,
    max: 50,
    proposal: '空气质量令人满意,基本无空气污染',
    influence: '各类人群可正常活动',
    color: '#00E500',
    lightColor: '#D2F5A5',
  },
  {
    icon: 'k2',
    label: '良',
    otherName: '二级',
    value: 2,
    min: 51,
    max: 100,
    proposal: '空气质量可接受,但某些污染物可能对极少数异常敏感人群健康有较弱影响',
    influence: '极少数异常敏感人群应减少户外活动',
    color: '#FFFF00',
    lightColor: '#EBEBD8',
  },
  {
    icon: 'k3',
    label: '轻度',
    otherName: '三级',
    value: 3,
    min: 101,
    max: 150,
    proposal: '易感人群症状有轻度加剧,健康人群出现刺激症状',
    influence: '儿童、老年人及心脏病、呼吸系统疾病患者应减少长时间、高强度的户外锻炼',
    color: '#FF7E00',
    lightColor: '#F2E1C7',
  },
  {
    icon: 'k4',
    label: '中度',
    otherName: '四级',
    value: 4,
    min: 151,
    max: 200,
    proposal: '进一步加剧易感人群症状,可能对健康人群心脏、呼吸系统有影响',
    influence: '儿童、老年人及心脏病、呼吸系统疾病患者避免长时间、高强度的户外锻炼,一般人群适量减少户外运动',
    color: '#FE0000',
    lightColor: '#F3D3D3',
  },
  {
    icon: 'k5',
    label: '重度',
    otherName: '五级',
    value: 5,
    min: 201,
    max: 300,
    proposal: '心脏病和肺病患者症状显著加剧,运动耐受力降低,健康人群普遍出现症状',
    influence: '儿童、老年人及心脏病、肺病患者应停留在室内,停止户外运动,一般人群减少户外运动',
    color: '#99004C',
    lightColor: '#E5D2ED',
  },
  {
    icon: 'k6',
    label: '严重',
    otherName: '六级',
    value: 6,
    min: 301,
    max: '+',
    proposal: '健康人群运动耐受力降低,有明显强烈症状,提前出现某些疾病',
    influence: '儿童、老年人和病人应停留在室内,避免体力消耗,一般人群避免户外活动',
    color: '#7E0022',
    lightColor: '#F3CBD7',
  },
];

/**
 * @description 获取AQI等级
 * @params {number} value
 */
//  获取AQI等级
const getLevel = (value) => {
  let index = 0;
  if (!value || value <= 50) index = 0;
  if (value > 50 && value <= 100) index = 1;
  if (value > 100 && value <= 150) index = 2;
  if (value > 150 && value <= 200) index = 3;
  if (value > 200 && value <= 300) index = 4;
  if (value > 300) index = 5;
  return { index, ...AQI_LEVELS[index] };
};

// 首要污染物别名配置
const PRIMARY = {
  NA: '无',
};

// 获取首要污染物Label
const getPrimaryLabel = (value) => {
  if (!value) return '';
  if (PRIMARY[value]) return PRIMARY[value];
  return value;
};

// 获取渐变颜色范围
const colorRange = (startColor, endColor) => ({
  type: 'linear',
  x: -0.33,
  y: 0.5,
  colorStops: [
    { offset: 0, color: startColor }, // 0% 处的颜色
    { offset: 0.5, color: startColor },
    { offset: 0.5, color: endColor },
    { offset: 1, color: endColor }, // 100% 处的颜色
  ],
  global: false, // 缺省为 false
});

// 获取趋势
const getTrend = (history, forecast) => {
  if (history === null || forecast === null) return '';
  const historyLevel = getLevel(history);
  const forecastLevel = getLevel(forecast);
  if (historyLevel.label === forecastLevel.label) return '正确';
  return history < forecast ? '偏高' : '偏低';
};

// 获取趋势标记符号
const getSymbol = (history, forecast) => {
  if (history === null || forecast === null) return '';
  return history === forecast ? symbolPass : symbolFail;
};

// 获取悬浮提示元素
const getTooltipRow = (params, factors) => {
  return `
  <div style="display: flex; justify-content: space-between; padding-top: 3px;">
    <span>${params.value[1] ? '实况监测' : '模式预测'}</span>
    <span style="padding-left: 15px">${params.marker || ''}</span>
    <span>${factors[params.value[2]] || '-'}</span>
  </div>`;
};

/**
 * @description 将接口数据转化为符合 echarts 的数据
 * @param {array} history 历史数据
 * @param {array} forecast 预测数据
 * @param {boolean} diff 是否按AQI等级拆分数据
 * @returns echartsData 符合echarts配置数据
 */
const transformData = (history, forecast, isDiff = false) => {
  const xAxisData = [...new Set([...Object.keys(history)])].sort();
  const yAxisData = ['模式预测', '实况监测'];

  let seriesData = [];
  let seriesDatas = [];
  const markPointData = [];
  const factors = [];

  for (let i = 0; i < xAxisData.length; i++) {
    for (let j = 0; j < yAxisData.length; j++) {
      const key = xAxisData[i];
      const historyValue = getPrimaryLabel((history[key] || {}).primary) || null;
      const forecastValue = getPrimaryLabel((forecast[key] || {}).primary) || null;
      seriesData.push([i, j, j ? historyValue : forecastValue]);
      if (!factors.includes(historyValue)) factors.push(historyValue);
      if (!factors.includes(forecastValue)) factors.push(forecastValue);
      if (j) {
        const symbol = getSymbol(historyValue, forecastValue);
        markPointData.push({
          show: Boolean(symbol),
          xAxis: i,
          yAxis: 1,
          symbolSize: 20,
          symbol,
        });
      }
    }
  }

  seriesData = seriesData.map(([x, y, factor]) => {
    const index = factors.findIndex((v) => v === factor);
    return [x, y, index];
  });

  // 按AQI等级区分数据
  if (isDiff) {
    seriesDatas = factors.map((v, i) => {
      const data = seriesData.filter((v) => v[2] === i);
      const color = v === '无' ? '#EFEFEF' : colors[i];
      return { name: v, data, color };
    });
  }

  return { xAxisData, yAxisData, seriesData, seriesDatas, markPointData, factors };
};

$.getJSON(uploadedDataURL, (data) => {
  const { history, forecast } = data;
  console.log(data);
  const { xAxisData, yAxisData, seriesDatas, markPointData, factors } = transformData(
    history || {},
    forecast || {},
    true
  );
  const myOption = {
    title: {
      text: '首要污染物准确率评估',
      left: 'center',
    },
    grid: {
      height: 80,
      top: 70,
      left: 58,
      right: 10,
      bottom: 50,
    },
    xAxis: {
      type: 'category',
      data: xAxisData,
      splitArea: {
        show: true,
      },
      axisLine: {
        show: false,
      },
      axisTick: {
        show: false,
      },
    },
    yAxis: [
      {
        type: 'category',
        data: yAxisData,
        axisLine: {
          show: false,
        },
        axisTick: {
          show: false,
        },
      },
      {
        type: 'value',
      },
    ],
    legend: {
      show: true,
      icon: 'circle',
      bottom: 0,
    },
    tooltip: {
      position: 'top',
      trigger: 'axis',
      formatter: (params) => {
        if (params.length !== 2) return '';
        const [paramsOne, paramsTwo] = params;
        const result = [];
        const dateStr = (paramsTwo || {}).axisValue || (paramsOne || {}).axisValue;
        if (dateStr) result.push(dateStr);
        if (paramsOne.value[1]) {
          if (paramsOne) result.push(getTooltipRow(paramsOne, factors));
          if (paramsTwo) result.push(getTooltipRow(paramsTwo, factors));
        } else {
          if (paramsTwo) result.push(getTooltipRow(paramsTwo, factors));
          if (paramsOne) result.push(getTooltipRow(paramsOne, factors));
        }
        return result.join('');
      },
    },
    visualMap: [
      {
        show: false,
        type: 'piecewise',
        pieces: factors.map((v, i) => ({
          value: i,
          name: v,
          color: v === '无' ? '#EFEFEF' : colors[i],
        })),
      },
    ],
    series: [
      {
        type: 'line',
        color: '#41C15A',
        markPoint: {
          symbolOffset: [0, -35],
          data: markPointData,
        },
      },
      ...seriesDatas.map(({ name, data, color }) => ({
        name,
        data,
        type: 'heatmap',
        label: {
          show: true,
          formatter: (params) => {
            if (!params.value.length) return '';
            return factors[params.value[2]];
          },
        },
        itemStyle: {
          color,
          borderWidth: 2,
          borderColor: '#fff',
        },
      })),
    ],
  };
  myChart.setOption(myOption);
});

    
截图如下