echarts 餅圖巢狀 二級餅圖 子餅圖 複合餅圖

一文搞懂發表於2024-06-27

<template>
    <div class="box" ref="chartRef"></div>
</template>
  
<script setup>

import * as echarts from "echarts";
import { watch, onMounted, ref, defineProps } from "vue";

let myChart;
let chartRef = ref(null);

const props = defineProps({
    title: { type: String, default: "標題" }
});


const data1 = [
    { value: 33, name: 'rose 1' },
    { value: 33, name: 'rose 2' },
    { value: 30, name: 'rose 3' }
]

const data2 = [
    { value: 30, name: 'ddd 1' },
    { value: 28, name: 'ddd 2' },
    { value: 26, name: 'ddd 3' },
    { value: 24, name: 'ddd 4' },
    { value: 22, name: 'ddd 5' },
    { value: 20, name: 'ddd 6' },
    { value: 18, name: 'ddd 7' },
    { value: 16, name: 'ddd 8' }
]


let textColor = "white"
let markLineData = []


const option = {
    legend: {},
    tooltip: {},

    //圖例
    legend: {
        show: false,
        right: '5%',//靠右
        orient: 'vertical',//垂直排列
        top: 'center', // 上下居中
        align: 'left',//文字在圖例左側
        itemGap: 20, //圖例間距
        textStyle: {
            color: textColor
        }
    },
    series: [
        {
            type: 'pie',
            radius: "70%",
            center: ['30%', '50%'],
            label: {
                show: true,
                position: "inside",
            },
            startAngle: 45, // 起始角度 45 
            clockwise: false, // 逆時針 
            markLine: {
                lineStyle: {
                    type: 'solid',
                    color: "#BFBFBF"
                },
                symbol: 'none',
                data: markLineData
            },
            data: data1
        },
        {
            type: 'pie',
            radius: "40%",
            center: ['65%', '30%'],
            encode: {
                itemName: 'product',
                value: '2016',
            },
            label: {
                show: true,
                position: "inside"
            },
            data: data2
        }
    ]
};


// 獲取表標線 對應點座標
function getMarkLineData(percent) {
    // 1.獲取畫布 width,height
    let height = myChart.getHeight()
    let width = myChart.getWidth()

    //2.根據 series[0].center,獲取配置 轉 數值
    let pie1CenterXpercent = convertPercentageStringToDecimal(option.series[0].center[0]) // 圓心1軸座標X百分比
    let pie1CenterYpercent = convertPercentageStringToDecimal(option.series[0].center[1]) // 圓心1軸座標Y百分比
    let pie2CenterXpercent = convertPercentageStringToDecimal(option.series[1].center[0]) // 圓心2軸座標X百分比
    let pie2CenterYpercent = convertPercentageStringToDecimal(option.series[1].center[1]) // 圓心2軸座標Y百分比
    let pie1RadiusPercent = convertPercentageStringToDecimal(option.series[0].radius) //圓心1半徑百分比
    let pie2RadiusPercent = convertPercentageStringToDecimal(option.series[1].radius) //圓心2半徑百分比

    let pie1Radius = height / 2 * pie1RadiusPercent  // 圓心半徑
    let pie2Radius = height / 2 * pie2RadiusPercent  // 圓心半徑

    let pie1CenterX = width * pie1CenterXpercent  // 圓心1軸座標X
    let pie1CenterY = height * pie1CenterYpercent // 圓心1軸座標Y
    let pie2CenterX = width * pie2CenterXpercent  // 圓心2軸座標X
    let pie2CenterY = height * pie2CenterYpercent // 圓心2軸座標Y

    //3.圓邊上點座標
    let line1StartX = pie1CenterX + pie1Radius * Math.cos(45 * 3.14 / 180)     // let line1StartX   =   pie1CenterX   +   r   *   cos(ao   *   3.14   /180   )
    let line1StartY = pie1CenterY - pie1Radius * Math.sin(45 * 3.14 / 180)     // let line1StartY   =   pie1CenterY   -   r   *   sin(ao   *   3.14   /180   )
    let line1EndX = pie2CenterX
    let line1EndY = pie2CenterY - pie2Radius

    console.log("線1結束", pie2CenterY, pie2Radius)

    let ao = 360 * (percent / 100) // 扇形角度

    let ao1 = 0 // 用來計算的座標角度
    ao1 = (ao <= 45) ? (45 - ao) : (360 - (ao - 45))
    if (ao1 < 270 && ao1 > 45) ao1 = 270 // 角度當270用,要不樣式不好看

    let line2StartX = pie1CenterX + pie1Radius * Math.cos(ao1 * 3.14 / 180)
    let line2StartY = pie1CenterY - pie1Radius * Math.sin(ao1 * 3.14 / 180)
    let line2EndX = pie2CenterX
    let line2EndY = pie2CenterY + pie2Radius


    return [
        [{
            x: line1StartX,
            y: line1StartY
        }, {
            x: line1EndX, // 線1,終點x值
            y: line1EndY  // 線1,終點y值
        }],
        [{
            x: line2StartX,
            y: line2StartY
        }, {
            x: line2EndX,// 線2,終點x值
            y: line2EndY // 線2,終點y值
        }]
    ]
}

// 帶百分號字串轉小數
function convertPercentageStringToDecimal(percentageString) {

    const percentageValue = parseFloat(percentageString.replace("%", "")) / 100;
    // console.log("帶百分號字串轉小數",percentageString,percentageValue)
    return percentageValue;
}


//透過區塊的佔比(最後一個區塊),計算線條起點和終點
const addOtherData = (arrData) => {
    let end = arrData.length - 1
    let sum = 0;
    for (let i = 0; i < arrData.length; i++) {
        let value = arrData[i] ? 0 : Number(arrData[i].value)
        console.log("item", value)
        sum += value
    }
    let percent = sum ? ((arrData[end].value / sum) * 100).toFixed(2) : 100
    console.log("佔比", sum, arrData[end].value, percent)
    option.series[0].markLine.data = getMarkLineData(percent)
    console.log(markLineData)
}


const initChart = () => {
    addOtherData(data1);
    myChart.setOption(option);
};


watch(props, () => {
    console.log("表格資料變動");
    initChart();
});

onMounted(() => {
    myChart = echarts.init(chartRef.value);
    initChart();
});

</script>
  
<style scoped>
.box {
    width: 100%;
    height: 100%;

    /* width: 100px;
    height: 100px; */
}
</style>

相關文章