ECharts 伪 3D 柱状图完整注释 + 实现原理说明

代码注释顶部标注 // 多系列无法定位:该方案只支持单组数据,如果多系列多根柱子并排,pictorialBar 菱形底座 / 顶盖无法精准对齐每一根柱子,会错位。

核心实现原理前置说明

ECharts 没有原生 3D 柱状图 bar3D (echarts-gl 是独立 3D 组件,此代码未引入 gl),本方案是2D 图层叠加模拟立体柱子:

  • 底层:普通bar矩形柱体(柱子主体)

  • 下层装饰:pictorialBar菱形(柱子底部切面,模拟立体底座)

  • 上层装饰:pictorialBar菱形(柱子顶部切面,模拟立体封顶) 通过三层 series 设置不同z层级上下叠加,搭配渐变填充、菱形斜面,视觉欺骗出 3D 立体效果。

  • 如果需要支持多系列、真 3D 旋转视角,需要引入 echarts-gl 插件,使用 bar3D 系列,不需要多层 pictorialBar 叠加。

  • 单系列正常逻辑 只有一组 bar 数据时,pictorialBar 的 data 长度、类目位置和 bar 完全匹配,菱形底座 / 顶盖能精准落在每根柱子上下。

  • 多系列失效原因 当存在两组及以上 bar(多组并列柱子):

    • pictorialBar 是全局类目渲染,无法区分第 1 组柱、第 2 组柱;
    • 菱形图形会居中在类目格子中间,和多根并列柱子错位;
    • 没有配置参数可以控制 pictorialBar 偏移到对应系列柱子位置,因此多系列场景该伪 3D 方案不可用。
    javascript 复制代码
    // 全局通用文字线条灰色
    var normalColor = "#94a1a9";
    
    /**
     * 渐变颜色数组,两套线性渐变用于柱子填充
     * 渐变类型:水平线性渐变(x从0到1,y不变=横向渐变)
     */
    var colors = [{
        type: 'linear',       // 渐变类型:线性渐变
        x: 0, x2: 1,          // 渐变起点x=0,终点x=1 → 水平左右渐变
        y: 0, y2: 0,          // y全程不变,无上下渐变
        colorStops: [{         // 渐变分段色标
            offset: 0, color: '#0a4368'
        }, {
            offset: 0.5, color: '#0a4368'
        }, {
            offset: 0.5, color: '#0a4368'
        }, {
            offset: 1, color: '#0a4368'
        }]
    }, {
        type: 'linear',
        x: 0, x2: 1,
        y: 0, y2: 0,
        colorStops: [{
            offset: 0, color: 'rgba(10, 67, 104,0.6)'
        }, {
            offset: 0, color: '#fff'
        }, {
            offset: 0.05, color: 'rgba(10, 67, 104,0.6)'
        }, {
            offset: 0.5, color: 'rgba(10, 67, 104,0.6)'
        }, {
            offset: 0.5, color: '#fff'
        }, {
            offset: 0.55, color: 'rgba(10, 67, 104,0.6)'
        }, {
            offset: 0.95, color: 'rgba(10, 67, 104,0.9)'
        }, {
            offset: 1, color: '#fff'
        }]
    }];
    
    // 柱子宽度统一变量,三层图表共用宽度保证对齐
    var barWidth = 80;
    
    // ECharts 配置项
    option = {
        animation: false, // 关闭图表初始化加载动画,避免立体分层动画错位
        backgroundColor: "#ccc", // 画布背景浅灰色
        title: {
            text: '' // 无图表标题
        },
        // X轴:类目轴,年份分类
        xAxis: {
            type: 'category',
            boundaryGap: true, // 坐标轴两端留白,柱子不会贴紧画布左右边缘
            axisLabel: { // X轴文字样式
                color: normalColor,
                fontSize: 16,
            },
            axisTick: { // 隐藏X轴刻度小短线
                show: false
            },
            axisLine: { // X轴线样式
                show: true,
                lineStyle: {
                    type: "dashed", // 虚线坐标轴
                    color: normalColor
                }
            },
            splitLine: { // X轴纵向分割线
                show: true,
                lineStyle: {
                    type: "dashed",
                    color: normalColor
                }
            },
            data: ['2016', '2017', '2018', '2019'] // X轴类目数据
        },
        // Y轴:数值轴,单根数组表示单Y轴
        yAxis: [{
            name: "",
            type: 'value',
            axisLine: { // Y轴线
                show: true,
                lineStyle: {
                    color: normalColor,
                }
            },
            axisTick: { // 隐藏Y轴刻度短线
                show: false,
            },
            axisLabel: { // Y轴数值文字
                color: normalColor,
                fontSize: 16,
                margin: 25, // 文字距离Y轴线间距
                formatter: '{value}' // 数值原样展示
            },
            splitLine: { // Y轴横向网格分割线
                show: true,
                lineStyle: {
                    type: "dashed",
                    color: normalColor
                }
            },
        }, ],
        /**
         * series三层图层叠加实现伪3D柱子,z值越大层级越靠上
         * z=1:底层主体矩形柱子
         * z=2:底层菱形底座(柱子底部斜面)
         * z=3:顶层菱形顶盖(柱子顶部斜面)
         */
        series: [
            // 第一层:柱状图主体(矩形柱身,核心载体)
            {
                z: 1, // 层级最低,在最下方
                type: 'bar', // 基础柱状图
                barWidth: barWidth, // 柱宽统一80px
                label: { // 柱子顶部数值标签
                    show: true,
                    position: "top", // 文字在柱子上方
                    padding: 10,
                    textStyle: {
                        color:normalColor,
                        fontSize: 30 // 数值放大突出
                    }
                },
                data: [220, 182, 191, 234], // 柱子高度数值
                itemStyle: {
                    normal: {
                        color: colors[1] // 柱子填充为第二套渐变
                    }
                },
            },
            // 第二层:pictorialBar 菱形底座,模拟柱子底部立体切面
            {
                z: 2, // 层级高于柱身,叠在柱子底部上方
                name: '底部',
                type: 'pictorialBar', // 象形柱状图,自定义图形标记
                data: [1, 1, 1, 1, 1, 1, 1], // 数值固定1,只渲染图形不控制高度
                symbol: 'diamond', // 自定义图形:菱形
                symbolOffset: [0, '50%'], // 图形垂直向下偏移50%,贴在柱子底端
                symbolSize: [barWidth, 10], // 菱形宽=柱宽,高度10px(底座厚度)
                itemStyle: {
                    normal: {
                        color: colors[1],
                        borderColor: '#fff', // 白色描边增强立体分割感
                        borderWidth: 2,
                    }
                },
            },
            // 第三层:pictorialBar 菱形顶盖,模拟柱子顶部立体切面
            {
                z: 3, // 层级最高,覆盖在柱子最顶端
                name: '上部1',
                type: 'pictorialBar',
                symbolPosition: 'end', // 图形吸附在数值终点(柱子顶端)
                data: [220, 182, 191, 234], // 和柱身高度数值完全一致,精准对齐顶端
                symbol: 'diamond', // 顶部菱形切面
                symbolOffset: [0, '-50%'], // 垂直向上偏移50%,贴合柱子顶端上沿
                symbolSize: [barWidth - 4, 15 * (barWidth - 4) / barWidth], // 菱形略窄于柱身,比例缩小高度
                itemStyle: {
                    normal: {
                        borderColor: '#fff', // 白色边框区分顶盖和柱身
                        borderWidth: 2,
                        color: '#0a4368' // 顶盖纯色加深,强化立体明暗对比
                    }
                }
            }
        ]
    };
相关推荐
卸任11 小时前
Vibe coding工具使用心得
前端·ai编程
brien912 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance12 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M12 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写12 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李12 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易12 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马13 小时前
前沿视界
前端
kyriewen13 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易13 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员