vue3+echarts两饼图连接映射

199 阅读3分钟

最近开发中,突然来了一个echarts还要很快开发出来。。

要老命了,遭老罪了。。

先看需求图

微信截图_20240927113331.png

微信截图_20240927113348.png

思考了半天还是有点思路,前端画图不就是搭积木嘛。

  1. 中间蓝色阴影部分用canvas来做
  2. 获取容器和饼图在页面上的位置,绘制梯形
  3. 使用2个mask作为饼图圆心,用于遮挡echarts中心位置的梯形canvas

首先获取容器和饼图在页面上的位置

获取图表实例中的扇形图坐标

难点在于绘制梯形,我们需要获取到梯形在饼图中的四个顶点

然后利用定位将mask部分,放在饼图中间遮挡住多余的梯形

废话不多说直接上代码

html部分

<template>
  <div ref="chartRef" v-resize="chart" class="chart"></div>
  <div class="mask"></div>
  <div class="mask2"></div>
  <canvas class="canvasImg" ref="canvas" width="200" height="300"></canvas>
</template>

使用2个mask作为饼图圆心,用于遮挡echarts中心位置的梯形canvas

js部分

<script setup>
  import { ref, onMounted, nextTick } from 'vue';
  import * as echarts from 'echarts';
 
  const props = defineProps({
    electircData: { type: Array, default: [] }, // 电量结构占比值
    legendData: { type: Array, default: [] }, // 电量结构占比值
    allData: { type: String, default: false }, // 总数值
    unit: { type: String, default: false } // 单位
  })
 
  const chartRef = ref(null);
 
  let chart;
  // 后端返回数据。。。
  let legendData = []
 
  // echarts部分。。。。
  legendData = [...props.electircData, ...props.legendData]
  const canvas = ref(null)
 
  onMounted(() => {
    chart = echarts.init(chartRef.value);
 
 
    const option = {
      // animation: true,
      tooltip: {
        trigger: 'item',
        show: false
      },
      legend: {
        show: false
      },
      graphic: [
        {
          type: 'text',// 类型,可以是文字、图片或其它类型
          id: 'text1',
          left: '16%',
          top: '27%',
          style: {
            text: props.allData,
            fill: '#7fabf0', // 文字的颜色
            fontSize: 18,
            fontWeight: 600
          }
        },
        {
          type: 'text',// 类型,可以是文字、图片或其它类型
          id: 'text2',
          left: '18%',
          top: '33%',
          style: {
            text: props.unit,
            fill: '#d9dadc', // 文字的颜色
            fontSize: 12
          }
        },
        {
          type: 'text',// 类型,可以是文字、图片或其它类型
          id: 'text3',
          left: '17%',
          top: '76%',
          style: {
            text: props.allData,
            fill: '#7fabf0', // 文字的颜色
            fontSize: 16,
            fontWeight: 600
          }
        },
        {
          type: 'text',// 类型,可以是文字、图片或其它类型
          id: 'text4',
          left: '18%',
          top: '82%',
          style: {
            text: props.unit,
            fill: '#d9dadc', // 文字的颜色
            fontSize: 12
          }
        }
      ],
      color: ['#8fb6f2', '#ebc17e', '#8fb6f2', '#a7cdff', '#f9c956', '#3BA272'],
      series: [
        {
          name: 'Access From',
          type: 'pie',
          radius: ['30%', '45%'],
          center: ['25%', '30%'],
          itemStyle: {
            itemWidth: 200,
            itemHeight: 300
          },
          label: {
            show: false,
            position: 'center'
          },
          emphasis: {
            label: {
              show: false,
              fontSize: 10,
              fontWeight: 'bold'
            }
          },
          labelLine: {
            show: false
          },
          data: props.electircData
        },
        {
          name: 'Access From',
          type: 'pie',
          radius: ['20%', '35%'],
          center: ['25%', '80%'],
          label: {
            show: false,
            position: 'center'
          },
          emphasis: {
            label: {
              show: false,
              fontSize: 10,
              fontWeight: 'bold'
            }
          },
          labelLine: {
            show: false
          },
          data: props.legendData
          }
    ]
  };
 
  chart.setOption(option);
  const pieCenters = [];
 
  // 监听饼图动画执行完成之后再绘制
  chart.on('finished', () => {
    getSectorPosition();
    cavansMap();
  })
 
  // 绘制t形对比区
  const cavansMap = async () => {
    await nextTick()
    const ctx = canvas.value.getContext('2d');
    // 定义梯形的四个点
    const topLeft = { x: pieCenters[0].left, y: pieCenters[0].y };
    const topRight = { x: pieCenters[0].right, y: pieCenters[0].y };
    const bottomLeft = { x: pieCenters[1].left, y: pieCenters[1].y };
    const bottomRight = { x: pieCenters[1].right, y: pieCenters[1].y };
 
    // 开始绘制梯形
    ctx.beginPath();
    ctx.moveTo(topLeft.x, topLeft.y);  // 移动到梯形的左上角
    ctx.lineTo(topRight.x, topRight.y); // 画线到右上角
    ctx.lineTo(bottomRight.x, bottomRight.y); // 画线到右下角
    ctx.lineTo(bottomLeft.x, bottomLeft.y); // 画线到左下角
    ctx.closePath(); // 关闭路径,画到起点
 
    // 填充区
    ctx.fillStyle = '#dfeafb';
    ctx.fill();
  }
 
  function resizeCanvas() {
    // 获取容器的尺寸
    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;
    // 重新绘制内容(可选)
    cavansMap();
  }
 
  // 获取容器和饼图在页面上的位置
  const getSectorPosition = () => {
    const container = chart.getDom();
    if (container && chart) {
      // 获取图表实例中的扇形图坐标
      const seriesData = chart.getOption().series;
      seriesData.forEach((series, index) => {
        let tmp = chart._chartsViews[index]._data._itemLayouts[0]
        const x = tmp.cx;
        const y = tmp.cy;
        const left = tmp.cx - tmp.r
        const right = tmp.cx + tmp.r
        // 存储坐标
        pieCenters.push({ index: index + 1, x, y, left, right });
      });
    }
  };
  // 初始化 Canvas 尺寸
  window.addEventListener('resize', resizeCanvas);
})
 
</script>

css部分

<style scoped lang="scss">
.chart {
  width: 300px;
  height: 360px;
  z-index: 3;
}
 
.mask {
  position: absolute;
  width: 110px;
  height: 70px;
  border-radius: 40px;
  top: 92px;
  left: 20px;
  z-index: 2;
  background-color: white;
}
 
 
.mask2 {
  position: absolute;
  width: 92px;
  height: 70px;
  border-radius: 40px;
  top: 251px;
  left: 32px;
  z-index: 2;
  background-color: white;
}
 
.canvasImg {
  position: absolute;
  top: 0px;
  // left: 65px;
  z-index: 1;
}
</style>

自适应什么的来不及做,先应付一下测试小姐姐,完结撒花!!!