echarts实例:最高最低标识-并列立体柱形图

0 阅读4分钟

记录下工作中使用echarts做出的特殊组件

image.png

import { defineComponent, ref, watchPostEffect, onBeforeUnmount } from 'vue'
import { FONT_SIZE, getImagePath, getMax } from './createChart'

const propData = [
  ['年份', '日整点最低负荷', '日整点最高负荷', '年供电量', '年售电量'],
  [2021, 125, 455, 20.62, 19.52],
  [2022, 116, 580, 20.62, 19.52],
  [2023, 131, 510, 20.62, 19.52],
  [2024, 137, 577, 20.62, 19.52],
]
let props = {
  propData: {
    type: Array,
    default: () => propData,
  },
  labelPosition: {
    type: Array,
    default: () => [-30, 10],
  },
  chartExtraOption: {
    type: Object,
    default() {
      return {}
    },
  },
  areaColorArray: {
    type: Array,
    default: () => ['#FFFB00', '#00D8FF', '#28C3FF', '#54D26A'],
  },
  imageVeArray: {
    type: Array,
    default: () =>
      ['黄色圆圈icon', '蓝色圆圈icon'].map((name) => {
        return {
          imageUrl: `scn/file/xxx/assets/图例icon/${name}.png`,
        }
      }),
  },
}


export default defineComponent({
  props,
  setup(props) {
    const chartRef = ref(null)

    let chart
    const drawChart = () => {
      const data = props.propData
    // 下面几行 需要调用初始化绘画图形的方法 类似:
      // const dom = this.$refs.chart
      // if (!this.chart) {
      //   this.chart = echarts.init(dom, null, {
      //     renderer: 'canvas',
      //   })
      // }
      // this.chart.setOption(option, true)
    }
    watchVeProps(props, drawChart)

    watchPostEffect(() => {
      if (!chartRef.value) return
      drawChart()
    })

    onBeforeUnmount(() => {
      if (chart?.dispose) chart.dispose()
    })

    return { chartRef }
  },
})

function getBarChartOption(data, props) {
  const textStyle = {
    fontSize: FONT_SIZE,
    color: '#fff',
    // formatter: ({ value }) => value.toFixed(2),
  }
  const lineStyle = {
    color: '#fff',
    width: 1,
  }

  // const data.slice(1) = data.slice(1)
  const { areaColorArray, imageVeArray, labelPosition } = props
  const legends = (data[0] || []).slice(1)

  const option = _.merge(
    {},
    {
      grid: {
        left: '3%',
        right: '5%',
        bottom: '3%',
        top: '15%',
        containLabel: true,
      },
      tooltip: {
        show: true,
        trigger: 'axis',
        axisPointer: {
          type: 'none',
        },
        appendToBody: true,
        symbolSize: 100,
        formatter: (params) => {
          if (!(params || params[0])) return
          let result = '<div class="chart-tooltip">' + params[0].axisValue + '<br>'
          params.forEach((item, index) => {
            let icon = `<span class="icon" style = "background:${areaColorArray[index]}" > </span>`
            if (item.componentSubType === 'pictorialBar') {
              icon = `<img class="icon" src="${imageVeArray[index].imageUrl}"/>`
            }
            result += `
              <div class="${item.componentSubType}">
              ${icon}
              <span class="name">${item.seriesName}:</span>
              <span class="value" style="color:${areaColorArray[index]}">${item.value}
              <span class="unit" style="color:#fff; font-size: 36px;">${item.componentSubType === 'custom' ? '亿度' : '万千瓦'}</sapn>
              </span>
              </div>`
          })
          result += '</div >'
          return result
        },
        textStyle: {
          fontSize: 50,
          color: '#fff',
        },
        backgroundColor: '#0C3E5F',
        borderColor: '#0CB6FF',
        borderWidth: 4,
      },
      legend: [
        {
          ...getCommonLegend([legends[0]], areaColorArray[0]),
          icon: getImagePath(imageVeArray[1].imageUrl, '', true),
          left: '15%',
        },
        {
          ...getCommonLegend([legends[1]], areaColorArray[1]),
          icon: getImagePath(imageVeArray[0].imageUrl, '', true),
          left: '32%',
        },

        {
          ...getCommonLegend(legends.slice(2, 4), areaColorArray.slice(2, 4)),
          right: '25%',
          itemWidth: 50,
          itemHeight: 30,
          itemGap: 80,
          icon: 'rect',
          itemStyle: {
            borderWidth: 0,
            borderColor: 'transparent',
          },
        },
      ],
      xAxis: [
        {
          data: data.slice(1).map(([month]) => +month + '年'),
          axisLabel: {
            fontSize: FONT_SIZE,
            color: '#fff',
            margin: 10,
          },
          axisLine: {
            show: true,
            lineStyle,
          },
          axisTick: {
            show: false,
          },
          splitLine: {
            show: false,
          },
        },
        {
          data: data.slice(1).map(([month]) => +month + '年'),
          axisLabel: {
            show: false,
          },
          axisLine: {
            show: true,
            lineStyle,
          },
          axisTick: {
            show: false,
          },
          splitLine: {
            show: false,
          },
        },
      ],
      yAxis: [
        {
          type: 'value',
          name: '亿度',
          nameGap: 0,
          nameLocation: 'end',
          position: 'left',
          nameTextStyle: {
            ...textStyle,
            padding: [0, 50, 40, 0], // 上、右、下、左
          },
          axisLine: {
            show: true,
            lineStyle,
          },
          axisLabel: {
            ...textStyle,
            margin: 25,
          },
          axisTick: {
            show: false,
          },
          splitLine: {
            show: false,
          },
        },
        {
          type: 'value',
          name: '万千瓦',
          nameGap: 0,
          nameLocation: 'end',
          position: 'right',
          max: (value) => getMax(value.max, 1.1),
          min: 0,
          nameTextStyle: {
            ...textStyle,
            padding: [0, 0, 40, 70], // 上、右、下、左
          },
          axisLine: {
            show: true,
            lineStyle,
          },
          axisLabel: {
            ...textStyle,
            margin: 25,
          },
          axisTick: {
            show: false,
          },
          splitLine: {
            show: false,
          },
        },
      ],
      series: [
        getSeriesOptionTop(
          legends[1],
          data.slice(1).map(([, , max]) => {
            return (+max).toFixed(0)
          }),
          areaColorArray,
          imageVeArray,
        ),
        getSeriesOptionTop(
          legends[0],
          data.slice(1).map(([, min]) => {
            return (+min).toFixed(0)
          }),
          areaColorArray,
          imageVeArray,
        ),
        getSeriesOption(
          legends[0],
          data.slice(1).map(([, min]) => {
            return (+min).toFixed(0)
          }),
          areaColorArray,
        ),
        getSeriesOption(
          legends[1],
          data.slice(1).map(([, min, max]) => {
            return (max - min).toFixed(0)
          }),
          areaColorArray,
        ),

        // // 虚线折线图
        // getSeriesDottedLine(
        //   legends[2],
        //   data.slice(1).map((item) => {
        //     return item[3]
        //   }),
        //   areaColorArray,
        // ),
        getSeries3DBar(
          legends[2],
          data.slice(1).map((item) => {
            return item[3]
          }),
          areaColorArray[2],
          -50,
        ),
        getSeries3DBar(
          legends[3],
          data.slice(1).map((item) => {
            return item[4]
          }),
          areaColorArray[3],
          80,
        ),
        getSeries3DBarLabel(
          data.slice(1).map((item) => {
            return item[3]
          }),
          areaColorArray[2],
          labelPosition[0],
        ),
        getSeries3DBarLabel(
          data.slice(1).map((item) => {
            return item[4]
          }),
          areaColorArray[3],
          labelPosition[1],
        ),
      ],
    },
  )

  return option
}
function getSeriesOption(name, data, areaColorArray) {
  return {
    name,
    type: 'bar',
    stack: '负荷',
    yAxisIndex: 1,
    tooltip: { show: false },
    color: name.includes('最高')
      ? new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          { offset: 0, color: areaColorArray[0] },

          {
            offset: 1,
            color: areaColorArray[1],
          },
        ])
      : 'transparent',
    label: {
      show: false,
    },
    symbol: 'circle',
    symbolSize: FONT_SIZE / 5,
    itemStyle: {
      borderRadius: 10,
    },
    barWidth: FONT_SIZE / 4,
    data,
  }
}

function getCommonLegend(legends, color) {
  return {
    show: true,
    top: '0%',
    itemWidth: 30,
    itemHeight: 30,
    itemGap: 25,
    textStyle: {
      color: '#fff',
      fontSize: FONT_SIZE - 5,
      padding: 10,
    },
    data: legends.map((item, index) => ({
      name: item,
      itemStyle: {
        color: Array.isArray(color) ? color[index] : color || '#FFFFFF', // 设置图例图标颜色
      },
    })),
  }
}

function getSeriesOptionTop(name, data, areaColorArray, imageColorArray) {
  return {
    name,
    type: 'pictorialBar',
    yAxisIndex: 1,
    color: name.includes('最高') ? areaColorArray[0] : areaColorArray[1],
    symbol: getImagePath(name.includes('最高') ? imageColorArray[0].imageUrl : imageColorArray[1].imageUrl, '', true),
    label: {
      show: true,
      color: '#fff',
      fontSize: FONT_SIZE - 5,
      fontWeight: 'bold',
      position: 'top',
      offset: name.includes('最高') ? [0, 0] : [0, FONT_SIZE + 50],
    },
    symbolSize: FONT_SIZE - 5,
    symbolPosition: 'end',
    symbolOffset: name.includes('最高') ? [0, -FONT_SIZE / 3] : [0, -FONT_SIZE / 2],
    data,
  }
}

function getSeriesDottedLine(name, data, areaColorArray) {
  return {
    name,
    type: 'line',
    yAxisIndex: 1,
    color: areaColorArray[2] || '#FFF',
    label: {
      show: false,
    },
    // symbol: 'circle',
    symbolSize: 25,
    itemStyle: {},
    lineStyle: {
      width: 3, // 增加线条粗细
      type: 'dashed', // 设置为虚线
    },
    data,
  }
}

function getSeries3DBar(name, data, color, gap = 0) {
  const barWidth = 15 // 定义柱形宽度

  return {
    name,
    type: 'custom',
    yAxisIndex: 0,
    renderItem: (params, api) => {
      const location = api.coord([api.value(0), api.value(1)])
      const xlocation = api.coord([api.value(0), 0])

      // 根据gap参数调整位置
      const xOffset = gap // 使用传入的gap值
      const barCenterX = location[0] + xOffset
      const barLeftX = barCenterX - barWidth / 2

      return {
        type: 'group',
        children: [
          {
            type: 'CubeLeft',
            shape: {
              api,
              xValue: api.value(0),
              yValue: api.value(1),
              x: barLeftX, // 使用计算后的位置
              y: location[1],
              xAxisPoint: xlocation,
              width: barWidth, // 添加宽度控制
              offset: xOffset, // 传递偏移量
            },
            style: {
              fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: echarts.color.modifyAlpha(color, 0.9),
                },
                {
                  offset: 1,
                  color: echarts.color.modifyAlpha(color, 0.12),
                },
              ]),
            },
          },
          {
            type: 'CubeRight',
            shape: {
              api,
              xValue: api.value(0),
              yValue: api.value(1),
              x: barLeftX, // 使用计算后的位置
              y: location[1],
              xAxisPoint: xlocation,
              width: barWidth, // 添加宽度控制
              offset: xOffset, // 传递偏移量
            },
            style: {
              fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: echarts.color.modifyAlpha(color, 0.4),
                },
                {
                  offset: 1,
                  color: echarts.color.modifyAlpha(color, 0.01),
                },
              ]),
            },
          },
          {
            type: 'CubeTop',
            shape: {
              api,
              xValue: api.value(0),
              yValue: api.value(1),
              x: barLeftX, // 使用计算后的位置
              y: location[1],
              xAxisPoint: xlocation,
              width: barWidth, // 添加宽度控制
              offset: xOffset, // 传递偏移量
            },
            style: {
              fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: echarts.color.modifyAlpha(color, 0.2),
                },
                {
                  offset: 1,
                  color,
                },
              ]),
            },
          },
        ],
      }
    },
    color,
    data,
  }
}

function getSeries3DBarLabel(data, color, offsetX) {
  return {
    type: 'bar',
    color,
    tooltip: { show: false },
    yAxisIndex: 0,
    xAxisIndex: 1,
    data,
    label: {
      show: true,
      position: 'top',
      fontSize: FONT_SIZE,
      color,
      offset: [offsetX, -10],
    },
    itemStyle: {
      color: 'transparent',
    },
  }
}