Newer
Older
CloudBrainNew / src / components / pieChart / simplePieChartCarousel.vue
<template>
  <div
    :id="id"
    :style="`width:${curWidth==0?'100%':curWidth};height:${curHeight==0?'100%':curHeight}`"
  >
  </div>
</template>
<script>
/**
   * @author 王晓颖
   * @date 2020/08/18
   * @Description 普通饼图
   * @props 数据格式:{
   *    id: '',
      width: 0,
      height: 0,
      xAxisData:[],
      seriesData: []
    }
   *
   */
export default {
  name: 'SimplePieChart',
  props: {
    id: { // id
      type: String,
      default: 'simplePieChart'
    },
    width: { // 宽
      type: Number | String,
      default: '100%'
    },
    height: { // 高
      type: Number | String,
      default: '100%'
    },
    radius: { // 饼图的半径[内半径,外半径]
      type: Array,
      default: () => {
        return ['30%', '45%']
      }
    },
    fontFamily: { // 值显示字体
      type: String,
      default: ''
    },
    roseType: { // 是否要展示成玫瑰图,或玫瑰图类型:radius|area
      type: String | Boolean,
      default: false
    },
    colors: { // 颜色组
      type: Array,
      default: () => { return ['#95a2ff', '#fa8080', '#ffc076', '#fae768', '#87e885', '#3cb9fc', '#73abf5', '#cb9bff', '#0078d4', '#90ed7d', '#f7a35c', '#8085e9'] }
    },
    fontColor: { // 标签文字颜色
      type: String,
      default: '#ffffff'
    },
    valueType: { // 显示值类型:percentage百分比|value数值
      type: String,
      default: 'percentage'
    },
    labelShow: {
      type: Boolean,
      default: true
    },
    labelFormatter: {
      type: String,
      default: ''
    },
    hoverFormatter: {
      type: String,
      default: '{b}<br/>{c}<br/>{d}%'
    },
    seriesData: { // 数据
      type: Array,
      default: () => { return [] }
    },
    rotateInterval: { // 轮播间隔时间(ms)
      type: Number,
      default: 3000
    },
    isRotate: { // 是否开启轮播
      type: Boolean,
      default: false
    },
    activeFontColor: { // 激活标签字体颜色
      type: String,
      default: '#fff'
    },
    labelLineColor: { // 标签线颜色
      type: String,
      default: '#ccc'
    },
    labelLineWidth: { // 标签线宽度
      type: Number,
      default: 1.5
    }
  },
  data () {
    return {
      rotateIndex: 0,
      rotateTimer: null,
      activeLabelIndex: -1,
      isMouseOver: false, // 鼠标是否悬停
      hoveredIndex: -1, // 鼠标悬停的数据项索引
      curWidth: this.width,
      curHeight: this.height,
      option: {
        tooltip: {
          trigger: 'item',
          formatter: this.hoverFormatter,
          textStyle: {
            fontSize: 15
          }
        },
        grid: {
          top: '10%',
          left: '5%',
          right: '5%',
          bottom: 20,
          containLabel: true // 是否包含坐标轴的刻度标签
        }, // 网格
        color: this.colors,
        series: [
          {
            animationType: 'expansion', // 沿圆弧展开效果 expansion|scale
            type: 'pie',
            avoidLabelOverlap: true, // 是否启用防止标签重叠策略
            roseType: this.roseType, // 玫瑰图类型
            radius: this.radius, // 饼图的半径[内半径,外半径]
            center: ['50%', '50%'], // 圆心坐标[横坐标,纵坐标]
            startAngle: 140,
            label: {
              show: this.labelShow,
              formatter: this.labelFormatter,
              fontSize: 19,
              lineHeight: 19,
              rich: {
                style1: {
                  color: this.fontColor,
                  fontSize: 16
                },
                style2: {
                  fontWeight: 'bolder',
                  fontSize: 19,
                  fontFamily: this.fontFamily
                }
              },
              emphasis: {
                show: true,
                textStyle: {
                  fontSize: 17,
                  fontWeight: 'bold'
                }
              }
            },
            labelLine: {
              length: 10,
              length2: 15
            },
            data: [
              // {value:34, name:'申报立案'},
            ]
          }
        ]
      }
    }
  },
  watch: {
    width (newVal, oldVal) {
      this.curWidth = newVal
      this.refreshEchart()
    },
    height (newVal, oldVal) {
      this.curHeight = newVal
      this.refreshEchart()
    },
    seriesData (newVal, oldVal) {
      this.option.series[0].data = newVal
      this.refreshEchart()
    }
  },
  computed: {
    // 动态计算标签格式化函数
    labelFormatter () {
      return (params) => {
        // 只在非鼠标悬停状态下显示当前轮播项的标签
        if (!this.isMouseOver && params.dataIndex === this.activeLabelIndex) {
          return `${params.name}\n${params.value} (${params.percent}%)`
        }
        return '' // 鼠标悬停时或非当前项不显示标签
      }
    }
  },
  mounted () {
    if (this.seriesData && this.seriesData.length) {
      // 格式化
      if (this.labelFormatter) {
        this.option.series[0].label.formatter = this.labelFormatter
      } else {
        if (this.valueType === 'percentage') {
          this.option.series[0].label.formatter = '{style1|{b}}\n{style2|{d}%}'
        } else if (this.valueType === 'value') {
          this.option.series[0].label.formatter = '{style1|{b}}\n{style2|{c}}'
        }
      }
      this.option.series[0].data = this.seriesData
      this.initEchart()
    }
    if (this.isRotate && this.seriesData && this.seriesData.length > 1) {
      this.startRotation()
    }
  },
  beforeDestroy () {
    this.stopRotation() // 组件销毁前停止轮播
  },
  methods: {
    // refreshEchart () {
    //   if (this.curWidth && this.curHeight && this.seriesData && this.xAxisData) {
    //     this.initEchart()
    //   }
    // },
    refreshEchart () {
      if (this.curWidth && this.curHeight && this.seriesData) { // 去掉多余的xAxisData判断(饼图不需要xAxisData)
        this.initEchart()
      }
    },
    // initEchart () {
    //   const _div = document.getElementById(this.id)
    //   setTimeout(() => {
    //     let myChart = this.$echarts.init(this.$el)
    //     myChart.setOption(this.option, true)
    //     window.onresize = myChart.resize
    //   }, 500)
    // }
    // 开始轮播
    startRotation () {
      if (this.rotateTimer) clearInterval(this.rotateTimer)

      this.rotateTimer = setInterval(() => {
        // 仅在鼠标未悬停时更新轮播
        if (!this.isMouseOver) {
          this.rotateIndex = (this.rotateIndex + 1) % this.seriesData.length
          this.updateActiveLabel(this.rotateIndex)
        }
      }, this.rotateInterval)
    },

    // 停止轮播
    stopRotation () {
      if (this.rotateTimer) {
        clearInterval(this.rotateTimer)
        this.rotateTimer = null
      }
    },

    // 更新激活的标签
    updateActiveLabel (index) {
      const chart = this.$echarts.getInstanceByDom(this.$el)
      if (chart) {
        this.activeLabelIndex = index

        // 更新标签配置
        chart.setOption({
          series: [{
            data: this.seriesData.map((item, i) => ({
              ...item,
              label: {
                show: !this.isMouseOver && i === index,
                formatter: this.labelFormatter,
                fontSize: 14,
                fontWeight: 'bold',
                // color: this.activeFontColor,
                borderRadius: 4
              },
              labelLine: {
                show: !this.isMouseOver && i === index, // 明确控制显示/隐藏
                length: this.isMouseOver ? 0 : 12,
                length2: this.isMouseOver ? 0 : 15,
                lineStyle: {
                  // color: this.labelLineColor,
                  // width: this.labelLineWidth
                }
              }
            }))
          }]
        }) // 第二个参数设为true,强制覆盖配置

        // 取消上一项的高亮(避免残留)
        const prevIndex = (index - 1 + this.seriesData.length) % this.seriesData.length;
        chart.dispatchAction({
          type: 'downplay',
          seriesIndex: 0,
          dataIndex: prevIndex
        })
      }
    },

    // 重写initEchart方法
    initEchart () {
      const _div = document.getElementById(this.id)
      setTimeout(() => {
        let myChart = this.$echarts.init(this.$el)

        // 修改默认配置
        const option = {
          ...this.option,
          tooltip: {
            show: true, // 启用tooltip
            trigger: 'item',
            formatter: '{b}<br/>{c} ({d}%)',
            backgroundColor: 'rgba(36, 37, 51, .6)',
            textStyle: {
              color: '#fff'
            }
            // extraCssText: 'box-shadow: 0 0 10px rgba(0,0,0,0.15);'
          },
          series: [{
            ...this.option.series[0],
            label: {
              // show: false // 默认不显示标签
            },
            labelLine: {
              // show: false // 默认不显示标签线
            }
          }]
        }

        myChart.setOption(option, true)

        // 窗口大小变化时重绘图表
        window.addEventListener('resize', myChart.resize)

        // 鼠标悬停事件
        myChart.on('mouseover', (params) => {
          this.isMouseOver = true
          this.hoveredIndex = params.dataIndex

          // 停止轮播
          this.stopRotation()

          // 隐藏所有标签和标签线
          this.updateActiveLabel(this.activeLabelIndex)

          // 显示tooltip
          myChart.dispatchAction({
            type: 'showTip',
            seriesIndex: 0,
            dataIndex: params.dataIndex
          })
        })

        // 鼠标移出事件
        myChart.on('mouseout', () => {
          this.isMouseOver = false
          this.hoveredIndex = -1

          // 恢复轮播
          this.startRotation()

          // 隐藏tooltip
          myChart.dispatchAction({
            type: 'hideTip'
          })
        })

        // 启动轮播
        if (this.isRotate && this.seriesData && this.seriesData.length > 1) {
          this.startRotation()
        }
      }, 500)
    }
  }
}
</script>

<style scoped>
</style>