前端3d环形图,vue2 echarts5 echarts-gl

724 阅读4分钟

3d环形图看着效果和2d 显示效果好点,所以咱也研究研究 3d环形图和环形图写法差的多点,使用echarts@5和echarts-gl@2 下面是找的demo,稍做加工下,效果如下

PixPin_2024-12-13_14-00-03.gif

<template>
    <div id="main-3d-chart"></div>
</template>
<script>
import * as $echarts from 'echarts'
import 'echarts-gl'
export default {
    name: "pieChart3d",
    props: {
        echartsData: {
            type: Array,
            default: () => []
        },
    },
    data() {
        return {
            selectedIndex: "",
            hoveredIndex: "",
            pieData: [],
            echartsInstance: null,
            centerTitleX: '29%',
            centerTitleY: '30%',
            centerTitle: '总数',
            option: [],
            colors: ['#93DBFF','#5AF3B8', '#4D74FF', '#FF9931']
        };
    },
    mounted() {
        this.$nextTick(() => {
            this.echartsData.forEach((item, index) => {
                item.itemStyle = {
                    color: this.colors[index%this.colors.length],
                }
            })
            this.initEcharts()
        })
    },
    beforeMount(){
        this.echartsInstance && this.echartsInstance.off("mouseover",this.mouseover)
        this.echartsInstance && this.echartsInstance.off("globalout",this.globalout)
    },
    methods: {
        initEcharts() {
            this.echartsInstance = $echarts.init(document.getElementById("main-3d-chart"));
            this.option = this.getPie3D(this.echartsData, 0.59)
            this.echartsInstance.setOption(this.option)
            this.echartsInstance.on("mouseover",this.mouseover)
            this.echartsInstance.on("globalout",this.globalout)
        },
        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],
                    // originValue: pieData[i].value,
                    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,也就是实现每一个扇形。
            console.log(series);
            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 = this.getParametricEquation(
                    series[i].pieData.startRatio,
                    series[i].pieData.endRatio,
                    false,
                    false,
                    k,
                    // 我这里做了一个处理,使除了第一个之外的值都是10
                    series[i].pieData.value === series[0].pieData.value
                        ? 35
                        : 10
                );

                startValue = endValue;

                legendData.push(series[i].name);
            }

            // 准备待返回的配置项,把准备好的 legendData、series 传入。
            const sum = pieData.reduce((a, b) => a + b.value, 0);
            console.log(sum, "sum");
            // let centerTitle = "总数";
            let centerTitleNum = sum;

            const option = {
                title: [
                    {
                        show: this.centerTitle ? true : false,
                        text: [
                            "{value|" + centerTitleNum + "}",
                            "{name|" + this.centerTitle + "}"
                        ].join("\n"),
                        textStyle: {
                            rich: {
                                value: {
                                    color: "#222B45", // 字体颜色
                                    fontSize: 20, // 字体大小
                                    fontWeight: "bold", // 字体大小
                                    lineHeight: 24
                                },
                                name: {
                                    color: "#222B45", // 字体颜色
                                    fontSize: 14, // 字体大小
                                    lineHeight: 14
                                }
                            }
                        },
                        left: this.centerTitleX,
                        top: this.centerTitleY,
                        textAlign: "center"
                    }
                ],
                // animation: false,
                tooltip: {
                    formatter: params => {
                        const data = pieData[params.componentIndex];
                        return (
                            data.name +
                            ":" +
                            data.value +
                            "辆" +
                            "<br> 占比:" +
                            ((data.value / sum) * 100).toFixed(2) +
                            "%"
                        );
                    }
                },
                legend: {
                    icon: "rect",
                    show: true,
                    right: 50,
                    top: 26,
                    itemWidth: 8,
                    itemHeight: 8,
                    itemGap: 30,
                    orient: "vertical",
                    formatter: params => {
                        const val = pieData.filter(item => {
                            return item.name === params;
                        });
                        return `${params}         ${val[0].value}`;
                    },
                    textStyle: {
                        color: '#000',
                        fontSize: 14,
                        lineHeight: 14,
                        padding: [0, 1, 0, 8],
                    }
                },
                xAxis3D: {
                    min: -1,
                    max: 1
                },
                yAxis3D: {
                    min: -1,
                    max: 1
                },
                zAxis3D: {
                    min: -1,
                    max: 1
                },
                grid3D: {
                    show: false,
                    boxHeight: 5,
                    top: "-10%",
                    left: "-20%",
                    center: ["30%", "50%"],
                    radius: ["60%", "90%"],
                    viewControl: {
                        // 3d效果可以放大、旋转等,请自己去查看官方配置
                        alpha: 35,
                        // beta: 30,
                        rotateSensitivity: 1,
                        zoomSensitivity: 0,
                        panSensitivity: 0,
                        autoRotate: false,
                        distance: 150
                    },
                    // 后处理特效可以为画面添加高光、景深、环境光遮蔽(SSAO)、调色等效果。可以让整个画面更富有质感。
                    postEffect: {
                        // 配置这项会出现锯齿,请自己去查看官方配置有办法解决
                        enable: false,
                        bloom: {
                            enable: true,
                            bloomIntensity: 0.1
                        },
                        SSAO: {
                            enable: true,
                            quality: "medium",
                            radius: 2
                        }
                        // temporalSuperSampling: {
                        //   enable: true,
                        // },
                    }
                },
                series
            };
            return option;
        },
        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;
                }
            };
        },
        mouseover(params) {
            // 准备重新渲染扇形所需的参数
            let isSelected;
            let isHovered;
            let startRatio;
            let endRatio;
            let k;
            let i;

            const _this = this
            // 如果触发 mouseover 的扇形当前已高亮,则不做操作
            if (_this.hoveredIndex === params.seriesIndex) {
                return;

                // 否则进行高亮及必要的取消高亮操作
            } else {
                // 如果当前有高亮的扇形,取消其高亮状态(对 option 更新)
                if (_this.hoveredIndex !== "") {
                    // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 false。
                    isSelected = _this.option.series[_this.hoveredIndex].pieStatus.selected;
                    isHovered = false;
                    startRatio = _this.option.series[_this.hoveredIndex].pieData.startRatio;
                    endRatio = _this.option.series[_this.hoveredIndex].pieData.endRatio;
                    k = _this.option.series[_this.hoveredIndex].pieStatus.k;
                    i =
                        _this.option.series[_this.hoveredIndex].pieData.value ===
                        _this.option.series[0].pieData.value
                            ? 35
                            : 10;
                    // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
                    _this.option.series[
                        _this.hoveredIndex
                    ].parametricEquation = _this.getParametricEquation(
                        startRatio,
                        endRatio,
                        isSelected,
                        isHovered,
                        k,
                        i
                    );
                    _this.option.series[_this.hoveredIndex].pieStatus.hovered = isHovered;

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

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

                    // 对当前点击的扇形,执行高亮操作(对 option 更新)
                    _this.option.series[
                        params.seriesIndex
                    ].parametricEquation = _this.getParametricEquation(
                        startRatio,
                        endRatio,
                        isSelected,
                        isHovered,
                        k,
                        50
                    );
                    _this.option.series[
                        params.seriesIndex
                    ].pieStatus.hovered = isHovered;

                    // 记录上次高亮的扇形对应的系列号 seriesIndex
                    _this.hoveredIndex = params.seriesIndex;
                }

                // 使用更新后的 option,渲染图表
                this.echartsInstance.setOption(_this.option);
            }
        },
        globalout() {
            if (this.hoveredIndex !== "") {
                // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
                let isSelected = this.option.series[this.hoveredIndex].pieStatus.selected;
                let isHovered = false;
                let k = this.option.series[this.hoveredIndex].pieStatus.k;
                let startRatio = this.option.series[this.hoveredIndex].pieData.startRatio;
                let endRatio = this.option.series[this.hoveredIndex].pieData.endRatio;
                // 对当前点击的扇形,执行取消高亮操作(对 this.option 更新)
                let i =
                    this.option.series[this.hoveredIndex].pieData.value ===
                    this.option.series[0].pieData.value
                        ? 35
                        : 10;
                this.option.series[
                    this.hoveredIndex
                ].parametricEquation = this.getParametricEquation(
                    startRatio,
                    endRatio,
                    isSelected,
                    isHovered,
                    k,
                    i
                );
                this.option.series[this.hoveredIndex].pieStatus.hovered = isHovered;

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

            // 使用更新后的 this.option,渲染图表
            this.echartsInstance.setOption(this.option);
        }
    }
};
</script>

参考: www.makeapie.cn/echarts_con…