Bootstrap

ehcarts示例

echarts水滴图配置

function initChart () {
    const chartDom = document.getElementById('xxx')
    chartDom.removeAttribute('_echarts_instance_')
    echarts.init(chartDom).dispose()
    const myChart = markRaw(echarts.init(chartDom))
    const value = 50
    const option = {
      series: [
        {
          type: 'liquidFill',
          radius: '100%',
          data: [value / 100, value / 100],
          backgroundStyle: {
            color: {
              type: 'radial',
              x: 0.5,
              y: 0.5,
              r: 0.5,
              colorStops: [
                {
                  offset: 0,
                  color: 'rgba(0,24,55, 0)'
                },

                {
                  offset: 1,
                  color: 'rgba(147,255,252,0.24)'
                }
              ],
              globalCoord: false
            }
          },
          outline: {
            borderDistance: 2,
            itemStyle: {
              borderWidth: 2,
              borderColor: '#155BA7'
            }
          },
          color: [
            {
              type: 'linear',
              x: 0,
              y: 0,
              x2: 0,
              y2: 1,
              colorStops: [
                {
                  offset: 1,
                  color: color1 // 下
                },
                {
                  offset: 0,
                  color: color2
                }
              ],
              globalCoord: false
            },
            {
              type: 'linear',
              x: 0,
              y: 0,
              x2: 0,
              y2: 1,
              colorStops: [
                {
                  offset: 1,
                  color: color1 // 下
                },
                {
                  offset: 0,
                  color: color2
                }
              ],
              globalCoord: false
            }
          ],
          label: {
            normal: {
              formatter: value + '%',
              fontSize: 18,
              color: '#fff'
            }
          }
        }
      ]
    }
    window.addEventListener('resize', function () {
      if (myChart) myChart .resize()
    })
    option && myChart.setOption(option)
}

水滴图实现效果

封装3D环形图

<!-- 3D饼图 -->
<template>
  <div :id="props.chartData.id" class="pieChart"></div>
</template>
<script setup>
import * as echarts from 'echarts'
import 'echarts-gl'
import { ref, defineProps, defineExpose, markRaw } from 'vue'

const props = defineProps({
  chartData: {
    type: Object
  },
  projectId: Number,
  siteCodeStatistics: Function
})
function init () {
  const chartDom = document.getElementById(props.chartData.id)
  chartDom.removeAttribute('_echarts_instance_')
  echarts.dispose(chartDom)
  const myChart = markRaw(echarts.init(chartDom))
  const option = getPie3D(props.chartData.pieData, props.chartData.ratio)
  window.addEventListener('resize', function () {
    myChart.resize()
  })
  myChart.setOption(option)
  myChart.off('legendselectchanged')
  myChart.on('legendselectchanged', legendselectchanged)
  let hoveredIndex = ''
  // 监听 mouseover,近似实现高亮(放大)效果
  myChart.on('mouseover', function (params) {
    // 准备重新渲染扇形所需的参数
    let isSelected
    let isHovered
    let startRatio
    let endRatio
    let k
    let i
    // 如果触发 mouseover 的扇形当前已高亮,则不做操作
    if (hoveredIndex === params.seriesIndex) {
      // 否则进行高亮及必要的取消高亮操作
    } else {
      // 如果当前有高亮的扇形,取消其高亮状态(对 option 更新)
      if (hoveredIndex !== '') {
        // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 false。
        isSelected = option.series[hoveredIndex].pieStatus.selected
        isHovered = false
        startRatio = option.series[hoveredIndex].pieData.startRatio
        endRatio = option.series[hoveredIndex].pieData.endRatio
        k = option.series[hoveredIndex].pieStatus.k
        i = option.series[hoveredIndex].pieData.value
        // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
        option.series[hoveredIndex].parametricEquation = getParametricEquation(
          startRatio,
          endRatio,
          isSelected,
          isHovered,
          k,
          i
        )
        option.series[hoveredIndex].pieStatus.hovered = isHovered

        // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
        hoveredIndex = ''
      }

      // 如果触发 mouseover 的扇形不是透明圆环,将其高亮(对 option 更新)
      if (params.seriesName !== 'mouseoutSeries') {
        // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
        isSelected = option.series[params.seriesIndex].pieStatus.selected
        isHovered = true
        startRatio = option.series[params.seriesIndex].pieData.startRatio
        endRatio = option.series[params.seriesIndex].pieData.endRatio
        k = option.series[params.seriesIndex].pieStatus.k

        // 对当前点击的扇形,执行高亮操作(对 option 更新)
        option.series[params.seriesIndex].parametricEquation = getParametricEquation(
          startRatio,
          endRatio,
          isSelected,
          isHovered,
          k,
          option.series[params.seriesIndex].pieData.value + 5
        )
        option.series[params.seriesIndex].pieStatus.hovered = isHovered
        // 记录上次高亮的扇形对应的系列号 seriesIndex
        hoveredIndex = params.seriesIndex
      }
      // 使用更新后的 option,渲染图表
      myChart.setOption(option)
    }
  })
  // 修正取消高亮失败的 bug
  myChart.on('globalout', function () {
    if (hoveredIndex !== '') {
      // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
      const isSelected = option.series[hoveredIndex].pieStatus.selected
      const isHovered = false
      const k = option.series[hoveredIndex].pieStatus.k
      const startRatio = option.series[hoveredIndex].pieData.startRatio
      const endRatio = option.series[hoveredIndex].pieData.endRatio
      // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
      const i =
            option.series[hoveredIndex].pieData.value
      option.series[hoveredIndex].parametricEquation = getParametricEquation(
        startRatio,
        endRatio,
        isSelected,
        isHovered,
        k,
        i
      )
      option.series[hoveredIndex].pieStatus.hovered = isHovered
      // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
      hoveredIndex = ''
    }
    // 使用更新后的 option,渲染图表
    myChart.setOption(option)
  })
}
// 生成扇形的曲面参数方程
function getParametricEquation (
  startRatio,
  endRatio,
  isSelected,
  isHovered,
  k,
  h
) {
  // 计算
  const midRatio = (startRatio + endRatio) / 2

  const startRadian = startRatio * Math.PI * 2
  const endRadian = endRatio * Math.PI * 2
  const midRadian = midRatio * Math.PI * 2

  // 如果只有一个扇形,则不实现选中效果。
  if (startRatio === 0 && endRatio === 1) {
    // eslint-disable-next-line no-param-reassign
    isSelected = false
  }
  // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
  // eslint-disable-next-line no-param-reassign
  k = typeof k !== 'undefined' ? k : 1 / 3

  // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
  const offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0
  const offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0
  // 计算高亮效果的放大比例(未高亮,则比例为 1)
  const hoverRate = isHovered ? 1.05 : 1
  // 返回曲面参数方程
  return {
    u: {
      min: -Math.PI,
      max: Math.PI * 3,
      step: Math.PI / 32
    },

    v: {
      min: 0,
      max: Math.PI * 2,
      step: Math.PI / 20
    },

    x (u, v) {
      if (u < startRadian) {
        return (
          offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
        )
      }
      if (u > endRadian) {
        return (
          offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
        )
      }
      return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate
    },

    y (u, v) {
      if (u < startRadian) {
        return (
          offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
        )
      }
      if (u > endRadian) {
        return (
          offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
        )
      }
      return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate
    },

    z (u, v) {
      if (u < -Math.PI * 0.5) {
        return Math.sin(u)
      }
      if (u > Math.PI * 2.5) {
        return Math.sin(u) * h * 0.1
      }
      // 当前图形的高度是Z根据h(每个value的值决定的)
      return Math.sin(v) > 0 ? 1 * h * 0.1 : -1
    }
  }
}
// 生成模拟 3D 饼图的配置项
function getPie3D (pieData, internalDiameterRatio) {
  const series = []
  // 总和
  let sumValue = 0
  let startValue = 0
  let endValue = 0
  const legendData = []
  const k =
    typeof internalDiameterRatio !== 'undefined'
      ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio)
      : 1 / 3

  // 为每一个饼图数据,生成一个 series-surface 配置
  for (let i = 0; i < pieData.length; i += 1) {
    sumValue += pieData[i].value

    const seriesItem = {
      name:
        typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
      type: 'surface',
      parametric: true,
      wireframe: {
        show: false
      },
      pieData: pieData[i],
      pieStatus: {
        selected: false,
        hovered: false,
        k
      }
    }

    if (typeof pieData[i].itemStyle !== 'undefined') {
      const { itemStyle } = pieData[i]

      // eslint-disable-next-line no-unused-expressions
      typeof pieData[i].itemStyle.color !== 'undefined'
        ? (itemStyle.color = pieData[i].itemStyle.color)
        : null
      // eslint-disable-next-line no-unused-expressions
      typeof pieData[i].itemStyle.opacity !== 'undefined'
        ? (itemStyle.opacity = pieData[i].itemStyle.opacity)
        : null

      seriesItem.itemStyle = itemStyle
    }
    series.push(seriesItem)
  }
  // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
  // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
  for (let i = 0; i < series.length; i += 1) {
    endValue = startValue + series[i].pieData.value

    series[i].pieData.startRatio = startValue / sumValue
    series[i].pieData.endRatio = endValue / sumValue
    series[i].parametricEquation = getParametricEquation(
      series[i].pieData.startRatio,
      series[i].pieData.endRatio,
      false,
      false,
      k,
      props.chartData.boxHeight             // 这里为根据每项value值计算每一项高度,传入常量如(50),则每一项高度都为50
        ? props.chartData.boxHeight
        : series[i].pieData.value
    )

    startValue = endValue

    legendData.push(series[i].name)
  }
  // 准备待返回的配置项,把准备好的 legendData、series 传入。
  const option = {
    // animation: false,
    tooltip: {
      formatter: (params) => {
        if (params.seriesName !== 'mouseoutSeries') {
          return `${
            params.seriesName
          }<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${
            params.color
          };"></span>${option.series[params.seriesIndex].pieData.value}`
        }
        return ''
      }
    },
    legend: props.chartData.legend,
    xAxis3D: {
      min: -1,
      max: 1
    },
    yAxis3D: {
      min: -1,
      max: 1
    },
    zAxis3D: {
      min: -1,
      max: 'dataMax'
    },
    grid3D: {
      show: false,
      boxHeight: props.chartData.boxHeight ? props.chartData.boxHeight : 35,
      top: 'center',
      left: '-20%',
      viewControl: {
        // 3d效果可以放大、旋转等,请自己去查看官方配置
        alpha: 13, // 视角绕 x 轴,即上下旋转的角度
        // beta: 30, // 视角绕 y 轴,即左右旋转的角度。
        rotateSensitivity: 1, // 旋转操作的灵敏度,值越大越灵敏。
        zoomSensitivity: 0, // 缩放操作的灵敏度,值越大越灵敏。
        panSensitivity: 0, // 平移操作的灵敏度,值越大越灵敏。
        autoRotate: true, // 是否开启视角绕物体的自动旋转查看。
        distance: 170 // 默认视角距离主体的距离
      },
      // 后处理特效可以为画面添加高光、景深、环境光遮蔽(SSAO)、调色等效果。可以让整个画面更富有质感。
      postEffect: {
        // 配置这项会出现锯齿,请自己去查看官方配置有办法解决
        enable: false,
        bloom: {
          enable: true,
          bloomIntensity: 0.1
        },
        SSAO: {
          enable: true,
          quality: 'medium',
          radius: 2
        },
        temporalSuperSampling: {
          enable: true
        }
      }
    },
    series
  }
  if (props.chartData.grid3D) {
    Object.assign(option.grid3D, props.chartData.grid3D)
  }
  return option
}
const selected = ref()
function legendselectchanged (params) {
  const arr = Object.entries(params.selected).filter(([key, val]) => val === true)
  const obj = Object.fromEntries(arr)
  const keysArr = Object.keys(obj)
  selected.value = keysArr.join()
  props.siteCodeStatistics(props.projectId, selected.value)
}
defineExpose({ init, selected })
</script>
<style lang="less" scoped>
.pieChart {
  width: 100%;
  height: 100%;
}
</style>

3D环形图传入数据示例

const chartData = reactive({
  id: 'pieChart',
  pieData: [
    {
      name: '正常状态',
      value: 40,
      itemStyle: {
        color: 'rgba(0, 255, 210, 1)'
      }
    },
    {
      name: '三级预警',
      value: 20,
      itemStyle: {
        color: 'rgba(255, 239, 0, 1)'
      }
    },
    {
      name: '二级预警',
      value: 10,
      itemStyle: {
        color: 'rgba(246, 138, 95, 1)'
      }
    },
    {
      name: '一级预警',
      value: 20,
      itemStyle: {
        color: 'rgba(238, 46, 53, 1)'
      }
    },
    {
      name: '数据中断',
      value: 10,
      itemStyle: {
        color: 'rgba(187, 191, 195, 1)'
      }
    }
  ],
  ratio: 0.59, // 空心圆的占比
  legend: {
    show: true,
    right: '10%',
    top: 'center',
    icon: 'circle',
    itemWidth: 10,
    width: 70,
    textStyle: {
      color: 'auto',
      fontSize: 14,
      rich: {
        a: {
          width: 70,
          fontSize: 14,
          color: 'rgba(255, 255, 255, 1)'
        },
        b: {
          width: 50,
          fontSize: 14,
          color: 'rgba(255, 255, 255, 1)'
        }
      }
    },
    formatter: (name) => {
      const data = chartData.pieData
      let target
      let v
      for (let i = 0; i < data.length; i++) {
        if (data[i].name === name) {
          target = data[i].value
          v = ((target / total.value) * 100).toFixed(2) + '%'
          return `{a|${name}}{b|${target}个}${v}`
        }
      }
    }
  }
})

3D环形图实现效果

;