Newer
Older
intelligentRobot / src / components / Echart / BarChartVertical.vue
wangxitong on 3 Sep 11 KB first commit
<script lang="ts" setup name="BarChartVertical">
/**
 * 垂直条形图,支持渐变,支持增加背景颜色
 */
import * as echarts from 'echarts/core'
import type { ECharts } from 'echarts'
import { init } from 'echarts'
import type { Ref } from 'vue'
import type { ECBasicOption } from 'echarts/types/dist/shared'
import type { barOption, barSeriesOption, lineDataI } from './echart-interface'
import tdTheme from './theme.json' // 引入默认主题

const props = defineProps({
  /**
   * id
   */
  id: {
    type: String,
    default: 'chart',
  },
  minValue: {
    type: Number,
    default: null,
  },
  maxValue: {
    type: Number,
    default: null,
  },
  isDataZoom: {
    type: Boolean,
    default: true,
  },
  yType: {
    type: Boolean,
    default: true,
  },
  /**
   * 标题
   */
  title: {
    type: String,
    default: '',
  },
  /**
   * 加载状态
   */
  loading: {
    type: Boolean,
    default: false,
  },
  /**
   * 网格配置
   */
  grid: {
    type: Object,
    default: () => {
      return {
        top: 50,
        left: 20,
        right: 20,
        bottom: 10,
        containLabel: true, // 是否包含坐标轴的刻度标签
      }
    },
  },
  /**
   * 图例设置对象
   */
  legend: {
    type: Object,
    default: () => {
      return {
        show: true,
        icon: 'circle',
        orient: 'vertile', // 图例方向
        align: 'left', // 图例标记和文本的对齐,默认自动
        top: 20,
        right: 60,
        itemWidth: 12,
        itemHeight: 12,
        textStyle: {
          color: '#96989b',
        },
        padding: [0, 0, 0, 120],
      }
    },
  },
  /**
   * 图例列表,非必传
   */
  legendData: {
    type: Array,
    default: () => { return [] },
  },
  /**
   * 图表宽
   */
  width: {
    type: String,
    default: '100%',
  },
  /**
   * 图表高
   */
  height: {
    type: String,
    default: '100%',
  },
  /**
   * X轴刻度数据,格式为['周一','周二'...]
   */
  xAxisData: {
    type: Array,
    default: () => { return [] },
  },
  /**
   * 数据,格式为[{name:'系列名',data:[0,0,0,...0], color:'可选'},...]
   */
  data: {
    type: Array,
    default: () => { return [] },
  },
  /**
   * 固定纵轴最大值,非必填
   */
  min: {
    type: [Number, String],
    default: 0,
  },
  max: {
    type: [Number, String],
    default: 4095,
  },
  /**
   * 单位
   */
  unit: {
    type: String,
    default: '',
  },
  /**
   * 调色盘取色策略: series按照系列,data按照数据项
   */
  colorBy: {
    type: String,
    default: 'series',
  },
  /**
   * 柱子颜色,渐变,
   * 若colorBy为data, 格式[['柱1颜色1','柱1颜色2','柱1颜色3'],['柱2颜色1','柱2颜色2','柱2颜色3']]
   * 若colorBy为series, 格式[['柱1颜色1','柱1颜色2','柱1颜色3']]
   */
  colors: {
    type: Array,
    default: () => {
      return [
        ['#ffff00', '#ffff00'],
        ['#ff0000', '#ff0000'],
        ['#fed700', '#fff'],
        ['#feb501', '#fff'],
      ]
    },
  },
  /**
   * 柱子背景颜色
   */
  backgroundColor: {
    type: String,
    default: '',
  },
  /**
   * 柱子圆角
   */
  barConer: {
    type: [Number, Array],
    default: 0,
  },
  /**
   * 柱子宽度
   */
  barWidth: {
    type: [Number, String],
    default: 1,
  },
  // 是否显示X轴线
  showXAxis: {
    type: Boolean,
    default: true,
  },
  // 是否显示Y轴线
  showYAxis: {
    type: Boolean,
    default: false,
  },
  // 是否显示Y轴线
  showYSplitLine: {
    type: Boolean,
    default: true,
  },
  /**
   * 轴线颜色
   */
  axisLineColor: {
    type: String,
    default: '#96989b',
  },
  /**
   * 轴线上文字颜色
   */
  fontColor: {
    type: String,
    default: '#96989b',
  },
  // 轴线文本宽度
  axisLabelWidth: {
    type: [Number, String],
    default: '',
  },
  /**
   * 是否显示标签
   */
  showLabel: {
    type: Boolean,
    default: true,
  },
  /**
   * 图形上文本标签颜色
   */
  labelColor: {
    type: String,
    default: '#96989b',
  },
  /**
   * 标签位置,top / left / right / bottom / inside / insideLeft / insideRight / insideTop / insideBottom / insideTopLeft / insideBottomLeft / insideTopRight / insideBottomRight
   */
  labelPosition: {
    type: String,
    default: 'top',
  },
  /**
   * 标签格式化
   */
  labelFormatter: {
    type: String,
    default: '{c}',
  },
  /**
   * 是否为渐变柱状图
   */
  gradient: {
    type: Boolean,
    default: false,
  },
})

// 图表对象
let chart: ECharts
const chartRef: Ref<HTMLElement | null> = ref(null)

// 监听数据变化
watch(
  [() => props.xAxisData, props.data], ([newName, newNums]) => {
    refreshChart()
    if(props.minValue !== null) {
      refreshArea()
    }
  },
  {
    immediate: true,
    deep: true,
  },
)
watch(() => props.max, () => {
  refreshChart()
}, {
  immediate: true,
})
watch(() => props.maxValue, () => {
  refreshArea()
}, {
  immediate: true,
})
watch(() => props.minValue, () => {
  refreshArea()
}, {
  immediate: true,
})

// 构建option
function buildOption() {
  const zoom = [{
    show: false,
    realtime: true,
    start: 0,
    end: 100,
    right: '23',  // 距离容器右侧侧的距离
    filterMode: 'none'
  },
    {
      type: 'inside',
      realtime: true,
      start: 0,
      end: 100,
    },
    {
      show: false,
      type: 'slider',
      yAxisIndex: 0,
      filterMode: 'none',
    },
    {
      type: 'inside',
      yAxisIndex: 0,
      filterMode: 'none',
    }
  ]
  const option: barOption = {
    title: [{ text: props.title }],
    grid: props.grid,
    legend: props.legend, // 图例
    dataZoom: props.isDataZoom ? (props.minValue !== null ? zoom.slice(0,2) : zoom) : [], // 缩略轴
    tooltip: {
      trigger: 'axis',
      // textStyle: {
      //   fontSize: 16,
      // },
      axisPointer: {
        type: 'cross',
        axis: 'x',
        label: {
          fontSize: 13,
        },
      },
      valueFormatter: (value: string | number) => {
        return value + props.unit
      },
    }, // 提示框
    yAxis: [
      {
        name: props.unit,
        type: props.yType ? 'value': 'log',
        boundaryGap: true,
        axisLine: {
          show: props.showYAxis,
          lineStyle: {
            color: props.axisLineColor, // 轴线的颜色
          },
        },
        nameTextStyle: { // 坐标轴名称的文字样式
          color: props.fontColor,
          fontSize: '60%',
          verticalAlign: 'middle',
        },
        axisLabel: {
          show: true,
          color: props.fontColor, // y轴名称颜色
          fontSize: 14,
        },
        splitLine: {
          show: props.showYSplitLine,
          lineStyle: {
            color: ['#7a6b74'],
            type: 'dashed',
          },
        },
      },
    ],
    xAxis: [
      {
        // type: props.yType ? 'value': 'log',
        type: 'value',
        min: props.min,
        max: props.max,
        boundaryGap: true,
        axisLine: {
          show: props.showXAxis,
          lineStyle: {
            color: props.axisLineColor, // 轴线的颜色
          },
        },
        axisLabel: {
          color: props.fontColor,
          fontSize: 15,
          showMaxLabel: true, // 显示最右侧的标签
        },
        splitLine: {
          show: false,
        },
      },
    ],
    series: [] as barSeriesOption[],
    toolbox: props.isDataZoom ? {}: {
      feature: {
        brush: {
          type: ['lineX'],
          title: {
            lineX: '横向选择',
          }
        }
      }
    },
    brush:  props.isDataZoom ? {}: {
      toolbox: ['lineX'],
      xAxisIndex: 0,
      defaultBrush: true,
    },
    selectedMode: 'multiple',
  }
  // 标题
  if (props.title) {
    option.title = {
      show: false,
      text: props.title,
    }
  }
  // 图例
  if (props.legend && props.legendData.length > 0) {
    option.legend!.data = props.legendData
  }
  // 横轴数据
  if (props.xAxisData && props.xAxisData.length > 0) {
    if (Array.isArray(option.xAxis) && option.xAxis.length > 0) {
      option.xAxis[0].data = props.xAxisData // 横轴, 水平柱状图,y轴为横轴
    }
  }
  // 轴线文本宽度
  if (props.axisLabelWidth && Array.isArray(option.xAxis) && option.xAxis.length > 0) {
    option.xAxis[0].axisLabel!.width = props.axisLabelWidth
  }
  // // 如果有最大值规定,固定最大值
  // if (props.max && Array.isArray(option.yAxis) && option.yAxis.length > 0) {
  //   option.yAxis[0].max = props.max
  // }
  // 数据
  if (props.data) {
    const newSeries: barSeriesOption[] = []
    // 遍历data, 拆分柱状图名称,数据和颜色
    for (let itemIndex = 0; itemIndex < props.data.length; itemIndex++) {
      const item = props.data[itemIndex]

      const series: barSeriesOption = {
        name: item.name,
        type: 'bar',
        colorBy: props.colorBy,
        animation: false,
        showBackground: !!props.backgroundColor,
        backgroundStyle: {
          color: props.backgroundColor,
          borderRadius: Array.isArray(props.barConer) ? props.barConer as number[] : props.barConer,
        },
        itemStyle: {
          borderRadius: Array.isArray(props.barConer) ? props.barConer as number[] : props.barConer,
        },
        data: item.data,
        zlevel: item.z,
        z: item.z,
      }
      // 如果规定了柱宽度
      if (props.barWidth) {
        series.barWidth = props.barWidth
      }
      // 如果要固定显示在图标右边
      if (props.labelPosition == 'fixedEnd' && chartRef.value?.clientWidth) {
        let chartWidth = chartRef.value?.clientWidth - props.grid.left || 0 - props.grid.right || 0
        if (props.axisLabelWidth && typeof props.axisLabelWidth === 'number') {
          chartWidth = chartWidth - props.axisLabelWidth
        }
        else {
          chartWidth = chartWidth - 80
        }
        series.label.normal!.position = [chartWidth, '50%']
      }
      series.itemStyle!.color = props.colors[itemIndex][0]
      newSeries.push(series)
    }
    option.series = newSeries
  }
  return option
}
// 初始化图表
function initChart() {
  chart = init(chartRef.value as HTMLElement, tdTheme)
  chart.setOption({})
  refreshChart()
}
function getChart() {
  return chart
}
defineExpose({ getChart })

// 刷新图表
function refreshChart() {
  if (chart) {
    const option = buildOption()
    chart.setOption(option as unknown as ECBasicOption,false)
  }
}

function refreshArea() {
  if(props.minValue === null || props.maxValue === null) return
  if (chart) {
    var dataZoomOption = chart.getOption().dataZoom;
    chart.setOption({
      series: [{
        markArea: {
          label: {
            color: 'rgba(255, 255, 255, 1)'
          },
          itemStyle: {
            color: 'rgb(68, 76, 159, 0.4)'
          },
          data: [
            [
              {name: props.minValue!.toFixed(2) + ' -- ' + props.maxValue!.toFixed(2), xAxis: props.minValue,},
              {xAxis: props.maxValue,}
            ],
          ]
        }
      }]
    })
    // 重新渲染图表,并将之前获取的 dataZoom 配置传入
    chart.setOption({
      dataZoom: dataZoomOption
    })
  }
}

window.addEventListener('resize', () => {
  chart.resize()
})

onMounted(() => {
  initChart()
})
</script>

<template>
  <div :id="id" ref="chartRef" v-loading="loading" class="chart" :style="{ height, width }" />
</template>

<style lang="scss" scoped>
.chart {
  width: 100%;
  height: 100%;
}
</style>