Newer
Older
robot_dog_patrol_front / src / components / Echart / BarChart3D.vue
lyg on 21 Sep 12 KB 布控地图-行政区
<script lang="ts" setup name="BarChartVertical3D">
/**
 * 垂直条形3d
 */
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',
  },
  /**
   * 标题
   */
  title: {
    type: String,
    default: '',
  },
  /**
   * 标题样式
   */
  titleTextStyle: {
    type: Object,
    default: () => {
      return {
        color: '#333',
        fontStyle: 'normal',
        fontWeight: 'bolder',
        fontSize: 14,
        lineHeight: 56,
      }
    },
  },
  /**
   * 加载状态
   */
  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: 5,
        right: 20,
        itemWidth: 12,
        itemHeight: 12,
        padding: [0, 0, 0, 120],
      }
    },
  },
  /**
   * 图例列表,非必传
   */
  legendData: {
    type: Array<string>,
    default: () => { return [] },
  },
  /**
   * 图表宽
   */
  width: {
    type: String,
    default: '100%',
  },
  /**
   * 图表高
   */
  height: {
    type: String,
    default: '100%',
  },
  /**
   * X轴刻度数据,格式为['周一','周二'...]
   */
  xAxisData: {
    type: Array<string>,
    default: () => { return [] },
  },
  /**
   * 数据,格式为[0,0,0,...0]
   */
  data: {
    type: Array<lineDataI>,
    default: () => { return [] },
  },
  /**
   * 固定纵轴最大值,非必填
   */
  max: {
    type: [Number, String],
    default: '',
  },
  /**
   * 单位
   */
  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 [
        ['#2d8cf0', '#fff'],
        ['#2352d6', '#fff'],
        ['#fed700', '#fff'],
        ['#feb501', '#fff'],
      ]
    },
  },
  /**
   * 柱子背景颜色
   */
  backgroundColor: {
    type: String,
    default: '',
  },
  /**
   * 柱子圆角
   */
  barConer: {
    type: [Number, Array],
    default: 50,
  },
  /**
   * 柱子宽度
   */
  barWidth: {
    type: [Number, String],
    default: '',
  },
  // 是否显示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: '#000000',
  },
  // 轴线文本宽度
  axisLabelWidth: {
    type: [Number, String],
    default: '',
  },
  /**
   * 是否显示标签
   */
  showLabel: {
    type: Boolean,
    default: true,
  },
  /**
   * 图形上文本标签颜色
   */
  labelColor: {
    type: String,
    default: '#3d7eff',
  },
  /**
   * 标签位置,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: true,
  },
})

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

// 监听数据变化
watch(
  [() => props.xAxisData, props.data], ([newName, newNums], [oldName, oldNums]) => {
    // initChart()
    refreshChart()
  },
  {
    immediate: true,
    deep: true,
  },
)
watch(() => props, () => {
  // initChart()
  refreshChart()
}, {
  deep: true,
})

// 构建option
function buildOption() {
  const option: barOption = {
    grid: props.grid,
    legend: props.legend, // 图例
    tooltip: {
      trigger: 'axis',
      textStyle: {
        fontSize: 16,
      },
      axisPointer: {
        type: 'cross',
        label: {
          fontSize: 13,
        },
      },
      valueFormatter: (value: string | number) => {
        return value ? value + props.unit : value === 0 ? '0' : ''
      },
    }, // 提示框
    xAxis: {
      type: 'category',
      data: props.xAxisData,
      axisLine: {
        show: false,
        lineStyle: {
          color: '#ccc',
        },
      },
      // offset: 25,
      axisTick: {
        show: false,
        length: 9,
        alignWithLabel: true,
        lineStyle: {
          color: '#7DFFFD',
        },
      },
      axisLabel: {
        show: true,
        fontSize: 12,
        color: '#fff',
        // rotate: 45
      },
    },
    yAxis: [
      {
        min: 0,
        max: Math.max(...props.data.map((item: any) => Number(item || '0'))),
        // interval: 200,
        type: 'value',
        // name: '%',
        axisLine: {
          show: false,
          lineStyle: {
            color: '#ccc',
          },
        },
        splitLine: {
          show: true,
          lineStyle: {
            type: 'solid',
            color: 'rgba(255,255,255,0.4)',
          },
        },
        axisTick: {
          show: false,
        },
        axisLabel: {
          show: true,
          fontSize: 12,
          color: '#fff',
        },
        boundaryGap: ['20%', '20%'],
      },
    ],
    series: [] as barSeriesOption[],
  }
  // 标题
  if (props.title) {
    option.title = {
      show: true,
      text: props.title,
      textStyle: props.titleTextStyle,
      padding: 0,
      top: -16,
    }
  }
  // 图例
  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轴为横轴
  //     option.xAxis[1].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[] = [
      {
        type: 'custom',
        renderItem: (params: any, api: any) => {
          const location = api.coord([api.value(0), api.value(1)])
          var color
            = api.value(1) > 10000
              ? 'red'
              : new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: '#5cc4eb',
                },
                {
                  offset: 0.8,
                  color: '#21658c',
                },
              ])
          // console.log(color)
          return {
            type: 'group',
            children: [
              {
                type: 'CubeLeft',
                shape: {
                  api,
                  xValue: api.value(0),
                  yValue: api.value(1),
                  x: location[0],
                  y: location[1],
                  xAxisPoint: api.coord([api.value(0), 0]),
                },
                style: {
                  fill: (color
                    = api.value(1) > 10000
                      ? 'red'
                      : new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                        {
                          offset: 0,
                          color: '#5cc4eb',
                        },
                        {
                          offset: 0.8,
                          color: '#21658c',
                        },
                      ])),
                },
              },
              {
                type: 'CubeRight',
                shape: {
                  api,
                  xValue: api.value(0),
                  yValue: api.value(1),
                  x: location[0],
                  y: location[1],
                  xAxisPoint: api.coord([api.value(0), 0]),
                },
                style: {
                  fill: (color
                    = api.value(1) > 10000
                      ? 'red'
                      : new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                        {
                          offset: 0,
                          color: '#048fd4',
                        },
                        {
                          offset: 0.8,
                          color: '#195684',
                        },
                      ])),
                },
              },
              {
                type: 'CubeTop',
                shape: {
                  api,
                  xValue: api.value(0),
                  yValue: api.value(1),
                  x: location[0],
                  y: location[1],
                  xAxisPoint: api.coord([api.value(0), 0]),
                },
                style: {
                  fill: (color
                    = api.value(1) > 10000
                      ? 'red'
                      : new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                        {
                          offset: 0,
                          color: '#65c7ec',
                        },
                        {
                          offset: 1,
                          color: '#65c7ec',
                        },
                      ])),
                },
              },
            ],
          }
        },
        data: props.data,
      },
    ]
    option.series = newSeries
  }
  return option
}
// 初始化图表
function initChart() {
  chart = init(chartRef.value as HTMLElement, tdTheme)
  chart.setOption({})
  // 绘制左侧面
  const CubeLeft = echarts.graphic.extendShape({
    shape: {
      x: 0,
      y: 0,
    },
    buildPath(ctx, shape) {
      const xAxisPoint = shape.xAxisPoint
      const c0 = [shape.x, shape.y]
      const c1 = [shape.x - 8, shape.y - 8]
      const c2 = [xAxisPoint[0] - 8, xAxisPoint[1] - 8]
      const c3 = [xAxisPoint[0], xAxisPoint[1]]
      ctx.moveTo(c0[0], c0[1]).lineTo(c1[0], c1[1]).lineTo(c2[0], c2[1]).lineTo(c3[0], c3[1]).closePath()
    },
  })
  // 绘制右侧面
  const CubeRight = echarts.graphic.extendShape({
    shape: {
      x: 0,
      y: 0,
    },
    buildPath(ctx, shape) {
      const xAxisPoint = shape.xAxisPoint
      const c1 = [shape.x, shape.y]
      const c2 = [xAxisPoint[0], xAxisPoint[1]]
      const c3 = [xAxisPoint[0] + 13, xAxisPoint[1] - 4]
      const c4 = [shape.x + 13, shape.y - 4]
      ctx.moveTo(c1[0], c1[1]).lineTo(c2[0], c2[1]).lineTo(c3[0], c3[1]).lineTo(c4[0], c4[1]).closePath()
    },
  })
  // 绘制顶面
  const CubeTop = echarts.graphic.extendShape({
    shape: {
      x: 0,
      y: 0,
    },
    buildPath(ctx, shape) {
      const c1 = [shape.x, shape.y]
      const c2 = [shape.x + 13, shape.y - 4]
      const c3 = [shape.x + 5, shape.y - 12]
      const c4 = [shape.x - 8, shape.y - 8]
      ctx.moveTo(c1[0], c1[1]).lineTo(c2[0], c2[1]).lineTo(c3[0], c3[1]).lineTo(c4[0], c4[1]).closePath()
    },
  })
  // 注册三个面图形
  echarts.graphic.registerShape('CubeLeft', CubeLeft)
  echarts.graphic.registerShape('CubeRight', CubeRight)
  echarts.graphic.registerShape('CubeTop', CubeTop)
}

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

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

onMounted(() => {
  initChart()
  refreshChart()
})
</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>