巧用ECharts的多层series画出一个好看的仪表盘

578 阅读6分钟

巧用ECharts的多层series画出一个好看的仪表盘

ECharts相信大家在平时开发中经常会用到,但是一般都是一些比较基本的单层图表,例如折线图、柱状图等等。

有一天UI设计了这样一个仪表盘组件:

组 1553 拷贝.png

我第一反应是打开echarts的官网去看看仪表盘的example有没有类似的样式我可以借鉴一下的,结果官网给出的样式少的可怜,只有这一些样式,可把我愁坏了。

image-20250506151551722.png

根本没有可以直接参考的仪表盘,于是我就放弃了这个念头,转而去思考如何通过css、js的方法手动就进行绘制,发现这样更加复杂,在时间上是来不及了。

搜索半天没有发现类似的仪表盘样式,打算问问ai,把仪表盘的图片放上去,结果...

image-20250506152113283.png

幽默识图😅,最后在我不断的搜索下,我突然想起来,以前做过一个立体的地图样式,是通过多层地图series堆叠出来的,这一下给我打开了新世界的大门,我这个仪表盘组件是不是也可以通过多层仪表盘堆叠出来。

首先,复制一份官网的模板(按需导入)到本地,改为函数式渲染仪表盘组件。

import * as echarts from 'echarts/core'
import {GaugeChart, type GaugeSeriesOption} from 'echarts/charts';
import {CanvasRenderer} from 'echarts/renderers';

export function renderGauge(el: HTMLElement) {
    echarts.use([GaugeChart, CanvasRenderer]);

    type EChartsOption = echarts.ComposeOption<GaugeSeriesOption>;
    const myChart = echarts.init(el);
    let option: EChartsOption;

    option = {
        series: [
            {
                type: 'gauge',
                center: ['50%', '60%'],
                startAngle: 200,
                endAngle: -20,
                min: 0,
                max: 60,
                splitNumber: 12,
                itemStyle: {
                    color: '#FFAB91'
                },
                progress: {
                    show: true,
                    width: 30
                },

                pointer: {
                    show: false
                },
                axisLine: {
                    lineStyle: {
                        width: 30
                    }
                },
                axisTick: {
                    distance: -45,
                    splitNumber: 5,
                    lineStyle: {
                        width: 2,
                        color: '#999'
                    }
                },
                splitLine: {
                    distance: -52,
                    length: 14,
                    lineStyle: {
                        width: 3,
                        color: '#999'
                    }
                },
                axisLabel: {
                    distance: -20,
                    color: '#999',
                    fontSize: 20
                },
                anchor: {
                    show: false
                },
                title: {
                    show: false
                },
                detail: {
                    valueAnimation: true,
                    width: '60%',
                    lineHeight: 40,
                    borderRadius: 8,
                    offsetCenter: [0, '-15%'],
                    fontSize: 60,
                    fontWeight: 'bolder',
                    formatter: '{value} °C',
                    color: 'inherit'
                },
                data: [
                    {
                        value: 20
                    }
                ]
            },

            {
                type: 'gauge',
                center: ['50%', '60%'],
                startAngle: 200,
                endAngle: -20,
                min: 0,
                max: 60,
                itemStyle: {
                    color: '#FD7347'
                },
                progress: {
                    show: true,
                    width: 8
                },

                pointer: {
                    show: false
                },
                axisLine: {
                    show: false
                },
                axisTick: {
                    show: false
                },
                splitLine: {
                    show: false
                },
                axisLabel: {
                    show: false
                },
                detail: {
                    show: false
                },
                data: [
                    {
                        value: 20
                    }
                ]
            }
        ]
    };

    option && myChart.setOption(option);
}

渲染到浏览器的效果是这样的

image-20250506153410636.png

在代码里可以看到,这个仪表盘也是通过两层实现的,一层是基础的轨道,一层是左边的描边效果。

image-20250506154353383.png 接下来,需要对UI给出的仪表盘进行分层,大致可以分为5层

image.png

  1. 第一层是数据轨道刻度层,主要负责渲染数据条、轨道和背景刻度。
  2. 第二层是分割线层,需要把第二层中的数据轨道分割成6块。
  3. 第三层是高亮刻度层,需要把第三层中对应的分割线的刻度用黄色的刻度显示。
  4. 第四层是外部曲线层,负责渲染指针到轨道的曲线。
  5. 第五层是内部曲线层,负责渲染最外围的曲线。

分析完主要部分,就可以开始修改官网的demo了,首先先把公共部分的属性定义为baseGaugeConfig,然后把中间最大的数据轨道刻度层画出来。

// 多层gauge的公共属性
const baseGaugeConfig: GaugeSeriesOption = {
    type: 'gauge',
    center: ['50%', '99%'],
    radius: '80%',
    min: 0,
    max: 100,
    startAngle: 180,
    endAngle: 0,
    splitNumber: 6
};
// 轨道宽度
const axisLineWidth = 25;
// 刻度线与轨道之间的距离
const axisTickDistance = 40;

// 省略option={series:[...]}

// 数据轨道刻度层
{
    z: 1,
    ...baseGaugeConfig,
    axisLine: {
        lineStyle: {
            color: [[1, '#e8e8e8']],
            width: axisLineWidth
        }
    },
    splitLine: {show: false},
    axisTick: {
        distance: -axisTickDistance,
        length: 4,
        lineStyle: {
            color: '#e8e8e8',
            width: 2
        }
    },
    axisLabel: {show: false},
    progress: {
        show: true,
        width: axisLineWidth,
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 1,
                y2: 0,
                colorStops: [
                    {offset: 1, color: '#ffd776'},
                    {offset: 0, color: '#76bdff'}
                ],
                global: false
            }
        }
    },
    anchor: {
        show: true,
        showAbove: true,
        size: 12,
        icon: 'circle',
        keepAspect: false,
        itemStyle: {color: '#43a0f7'}
    },
    pointer: {
        offsetCenter: [0, 0],
        icon: 'path://M3.905,-0.000 L3.905,-0.000 L6.903,36.1000 L4.904,48.1000 L4.904,48.1000 L2.906,48.1000 L2.906,48.1000 L0.907,36.1000 L3.905,-0.000 Z',
        width: 6,
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    {offset: 0, color: '#69b2f590'},
                    {offset: 1, color: '#69b2f5'}
                ],
                global: false
            }
        }
    },
    detail: {show: false},
    data: [
        {value: 50}
    ]
},

echarts容器的尺寸为width: 210px; height: 200px,渲染到浏览器的效果是这样的:

image-20250506161641624

center: ['50%', '99%']属性让图片在x轴居中,y轴上距离底部1%,如果设置为100%会让两边第一根刻度线显示少一半,radius: '80%'设置半径为容器高度的80%。

到这里基础的样子已经出来啦,接下来开始实现分割线层。在echarts官网的demo中有多段颜色的仪表盘,但是只能给每段单独设置颜色,不能给一整段的颜色进行分割。

image-20250506162916660.png

于是我们只能手动去画分割线来实现分段的效果,但是怎么画呢,用css画一根白色的矩形然后旋转复制六份放上去吗,这样实现可以,但是在尺寸变化时不好调整,我们还是可以通过仪表盘的方式来实现,可以注意到图里的刻度线不就正好可以跟着轨道变化吗,我们只要调整好刻度线的长度、跟轨道之间的距离就可以实现白色的分割线了。

// 省略公共属性、option={series:[...]}

// 分割线层
{
    z: 2,
    ...baseGaugeConfig,
    axisLine: {
        lineStyle: {
            color: [[1, 'transparent']],
            width: axisLineWidth
        }
    },
    splitLine: {
        distance: -axisLineWidth,
        length: axisLineWidth,
        lineStyle: {
            color: '#fff',
            width: 4
        }
    },
    axisTick: {show: false},
    axisLabel: {show: false},
    progress: {show: false}
},

这里要注意,需要给一个透明的轨道用来定位刻度线,如果不显示轨道的话会导致刻度线定位出现问题。

// 透明轨道
axisLine: {
    lineStyle: {
        color: [[1, 'transparent']],
        width: axisLineWidth
    }
}

// 刻度线的定位会有问题
axisLine: {
    show: false
},

渲染到浏览器的效果是这样的,由于分割线是手动设置为#fff白色,因此本方案只能在白色背景下使用,如果背景颜色改变需要手动修改分割线的颜色,如果背景不为纯色则此方案则会穿帮。

image-20250506165908439.png

接下来要去实现高亮的刻度,也是按照这个方法,定位好刻度线,修改刻度线的颜色、宽度、与轨道之间的距离即可。

// 省略公共属性、option={series:[...]}
// 高亮刻度层
{
    z: 3,
    ...baseGaugeConfig,
    axisLine: {
        lineStyle: {
            color: [[1, 'transparent']],
            width: axisLineWidth
        }
    },
    splitLine: {
        distance: -axisTickDistance - 3,
        length: 8,
        lineStyle: {
            color: '#ffbe4d',
            width: 2
        }
    },
    axisTick: {show: false},
    axisLabel: {show: false},
    progress: {show: false}
},

渲染到浏览器的效果是这样的

image-20250506165849131.png

接下来只要实现内外两条曲线就可以了,曲线的实现办法可以通过透明的饼图来实现,用饼图的描边来画出半圆形的曲线。

// 引入饼图需要额外引入饼图组件和饼图属性的type
import {PieChart, type PieSeriesOption} from 'echarts/charts';
// use方法中加入PieChart
echarts.use([GaugeChart, PieChart, CanvasRenderer]);
// EChartsOption类型需要加上PieSeriesOption
type EChartsOption = echarts.ComposeOption<GaugeSeriesOption|PieSeriesOption>;

// 省略公共属性、option={series:[...]}
// 外部曲线层
{
    z: 4,
    type: 'pie',
    center: ['50%', '98%'],
    radius: '105%',
    animation: false,
    itemStyle: {
        borderColor: '#edeffd',
        borderWidth: 1
    },
    data: [
        {value: 100, itemStyle: {color: 'transparent'}}
    ]
},
// 内部曲线层
{
    z: 5,
    type: 'pie',
    center: ['50%', '100%'],
    radius: '50%',
    animation: false,
    itemStyle: {
        borderColor: '#edeffd',
        borderWidth: 1
    },
    data: [
        {value: 100, itemStyle: {color: 'transparent'}}
    ]
}

最终渲染出来的效果

image-20250506171810123.png

至此,一个漂亮的仪表盘就全部完成了。

完整代码

github仓库:github.com/liliagrisin…