做监控大屏这几年,我用到最多的图表类型,除了趋势类的折线图,剩下的可能就是测量图了。设备温度、电机转速、管道压力、机房湿度、CPU使用率、水位高低,几乎所有需要“实时盯住一个参数有没有越界”的场景,最后都会落到这种半圆或整圆的仪表盘上。而 Highcharts 的测量图(gauge)系列,基本是我见过最好上手、也最不容易出幺蛾子的一套方案。这篇文章就用一个设备温度实时监控的完整例子,把 Highcharts 测量图从选型、配置、数据刷新到踩坑,一次性讲透。
先说测量图能做什么。它的核心价值就一句话:把一个数值放到预设区间里,让人一眼看出它“正不正常”。绿区代表安全,黄区代表警告,红区代表危险,指针指到哪,问题一目了然。它适合两类人看:一类是要做实时监控大屏的前端或物联网开发,一类是数据分析师想快速给指标加一个状态面板。不会写复杂配置没关系,Highcharts 的测量图本身就是一个高度封装的配置型图表,反射式使用就行。
1. 测量图是什么,为什么选 Highcharts
1.1 三种测量图类型怎么选
很多人在 Highcharts 里找“测量图”,会看到好几个接近的名字:gauge、solidgauge、还有带指针的仪表盘。简单明确一下,它们不是同一个东西,使用场景也略有差别。
gauge(角度表/指针表):最传统的仪表盘形态,半圆或近似半圆,有刻度、有指针、有中心轴。适合模拟速度表、压力表、电压表这类让人联想到机械仪表的场景。solidgauge(实心表):没有指针,用色块在环形或半环轨道上“填充”来指示数值。风格更现代,适合监控大屏、Dashboard 卡片,视觉上比指针表更干净。vU-meter(音量表样式):其实就是把gauge的样式调得类似音频电平表,多用于音量、信号强度、负载率这类连续波动场景,本质上还是仪表盘的路子。
选型逻辑不复杂。如果你是做传统的工业控制界面,甲方脑子里已经有了“指针卡在红区”这种画面,直接用gauge。如果你在做一个 Web 大数据可视化平台,希望卡片跟现代 UI 风格统一,选solidgauge更漂亮。二者在代码层面的差别只在chart.type和指针配置,后面我会把两套代码都给你。
1.2 和 ECharts、Chart.js 对比,我为什么坚持用 Highcharts
市面上做图表的库不少,国内用 ECharts 的也很多。但具体到测量图,我的选择始终是 Highcharts,理由有三。
第一,SVG 渲染。Highcharts 默认用 SVG 绘制图表,测量图里大量涉及精细的刻度线、指针旋转、弧线边界,SVG 在处理这类万级节点以内的图形时,清晰度和多端一致性都很好。放大缩小不模糊,打印导 PDF 质量也高。ECharts 默认 Canvas,处理十万级数据点有优势,但仪表盘这种几张卡片而已的场景,真用不上。
第二,配置心智负担低。Highcharts 的测量图本质上是围绕“一个 y 轴 + 一个 series”组织起来的,核心配置项非常集中:pane管表盘角度,yAxis管量程和分区,series管数值和指针。一个图表库如果让你为了画个半圆仪表盘去深入研究坐标系变换,那就跑偏了。我用 ECharts 也画过仪表盘,配置思路类似,但 Highcharts 的文档和示例更完整,遇到问题查起来明显顺手。
第三,采样和兼容性好。老项目里可能还有 jQuery 时代遗留的逻辑,Highcharts 不会跟你整什么“重新发明轮子”的别扭。它跟 React、Vue 也能正常共处,配合chart.destroy()做好生命周期管理就行。另外,它的导出、响应式布局、主题定制都是开箱即用,不需要额外找插件。
当然,Highcharts 商业项目要买授权,这是绕不开的成本。如果你所在团队已经有授权,或用在高保真原型、内部工具里,那完全不用纠结。
2. 动手前必须理解的核心配置
2.1 pane:先把表盘的角度逻辑吃透
刚开始用 Highcharts 测量图时,最容易懵的一个概念是pane。它单独看不是图表坐标系,而是专门用来描述“仪表面板”的几何范围。说白了,它告诉 Highcharts:你的表盘从几度开始,到几度结束。
最常见的配置是这样的:
pane: { startAngle: -120, endAngle: 120 }这两个值的意思是,表盘从 -120 度顺时针画到 120 度,正好是 240 度,留出下面 120 度的缺口,视觉上就是一个经典半圆表盘。为什么要留缺口?因为在 270 度整圆上,指针的最低点和最高点分别位于左下角和右下角,下面留白不会遮挡数值标签,也比整圆更有“仪表感”。
如果你想要的是一个圆环形的solidgauge效果,而不只是半圆,可以把角度拉满或者设置成更接近整圆的角度:
pane: { startAngle: 0, endAngle: 360, size: ['85%', '85%'] // 外半径85%,内半径85%,这样会得到一个细圆环 }solidgauge里size传的是[outerRadius, innerRadius]形式的百分数字符串,用来控制环形的粗细。内半径越接近外半径,环越细。很多设计稿里的“甜甜圈式仪表盘”,就是这么配出来的。普通gauge则基本不用配size,因为指针得从圆心伸出来,内外半径一起约束反而容易让指针位置奇怪。
2.2 yAxis 才是测量图的主角
测量图从数据角度看只有一个数值,但真正复杂的部分是“量程怎么划分”。这部分全在yAxis上。很多初学者把精力放在series上,改了半天颜色和指针,结果刻度、分区、颜色全不对,症结就在于yAxis没理解透。
先说量程。仪表盘的显示范围由yAxis.min和yAxis.max决定。比如温度监控量程是 0 到 100 摄氏度,就写:
yAxis: { min: 0, max: 100, tickAmount: 10, // 刻度数量 lineWidth: 0, tickWidth: 1, labels: { distance: 20, // 刻度数字离表盘的距离 style: { color: '#666', fontSize: '12px' } } }lineWidth: 0是去掉 y 轴轴线本身,因为表盘上要的是刻度线而不是一条贯穿的轴。tickAmount控制刻度分几格,这里设 10 就是每格 10 度。labels.distance控制数字离刻度线的距离,通常根据表盘半径微调,距离太小会和刻度线叠在一起,太大又会超出容器。
接下来是分区颜色,也就是绿区、黄区、红区。这在 Highcharts 里叫plotBands,是测量图里最有业务价值的部分:
plotBands: [ { from: 0, to: 60, color: '#4CAF50' }, { from: 60, to: 85, color: '#FF9800' }, { from: 85, to: 100, color: '#F44336' } ]from和to决定一个色带覆盖的量程范围。比如温度超过 85 度就进红区,立刻报警。颜色的选择建议不要用纯度和明度太接近的几种颜色,绿橙红有天然的顺序感,用户哪怕不看文字也知道状态好差。在solidgauge里,plotBands实际上就是轨道上的色带,填充区域会呈现出对应的颜色;在gauge里,plotBands则把仪表盘的底层背景分成若干色块,指针越过哪个颜色,立刻就能看出来。
2.3 指针、数据标签和数据的绑定关系
gauge和solidgauge在数据绑定上的套路是一模一样的,都要靠series.data里面的具体数值来驱动指针或填充带的位置。区别只是呈现方式:gauge需要配dial和pivot,solidgauge不需要指针,它靠填充色块的“长度”来表示数值。
gauge的指针配置长这样:
series: [{ name: '温度', data: [26], dial: { radius: '90%', // 指针长度,相对半径的百分比 baseLength: '10%', // 指针根部的延伸长度 backgroundColor: '#333', borderColor: '#333', borderWidth: 0 }, pivot: { radius: 8, // 中心轴圈的大小 backgroundColor: '#333' } }]这里最反直觉的是baseLength。它不是指针长度,而是指针根部往圆心另一侧延伸的那一小段。设成10%可以做出指针“尾部略露出轴心”的效果,更像真实表盘。radius: '90%'表示指针长度是半径的 90%,留出 10% 的空间给刻度数字,不会出现指针直接盖住数字的情况。
solidgauge虽然没有指针,但数值标签反而更重要,因为用户只能靠数字感知精确值。数据标签通常做成大号数字加单位:
dataLabels: { format: '<div style="text-align:center"><span style="font-size:28px">{y}</span>' + '<span style="font-size:14px">°C</span></div>', borderWidth: 0, backgroundColor: 'transparent' }{y}是 Highcharts 内置的占位符,会替换成当前数值。这里注意,仪表盘的yAxis在默认情况下会在图表右侧自动生成坐标轴标签,测量图里一般需要关掉或者隐藏,否则排版会乱。
多指针的问题也顺带说一句。一个gauge图表可以同时显示两个指针,比如“实际温度”和“目标温度”做对比,那就是往data里再塞一个数值:
data: [26, 30]两个指针会叠加在同一个刻度盘上,适合做偏差分析。但指针太多会视觉爆炸,超过两个就建议拆成多个图表了。
3. 完整实操:做一个设备温度实时监控仪表盘
3.1 页面结构与环境准备
这次咱们从零写一个设备温度监控页面,用solidgauge做主体,最后顺手演示怎么把普通gauge的指针版也实现出来。这个案例基本能覆盖测量图的日常用法。
建议动手前先把环境跑起来。你可以直接建一个 HTML 文件,用 CDN 方式引入依赖:
<script src="https://code.highcharts.com/highcharts.js"></script> <script src="https://code.highcharts.com/highcharts-more.js"></script> <script src="https://code.highcharts.com/modules/solid-gauge.js"></script>这里有三行,顺序千万别乱。highcharts.js是核心,highcharts-more.js提供gauge这类额外系列类型,solid-gauge.js才提供实心表盘。如果只引入前两个就写type: 'solidgauge',控制台会直接报“Highcharts has no such method for solidgauge series”,你甚至看不到图表。angular gauge只要前两个文件就够了,但solidgauge必须带第三个模块。
容器就放一个普通div:
<div id="tempGauge" style="width: 100%; height: 380px;"></div>宽高建议直接用内联样式或者 CSS 给,Highcharts 初始化时会读取容器尺寸做自适应。容器高度不设的话,默认会算成 400px 左右,但灵活布局时很容易被外层撑出奇怪高度,宁可一开始就给死一个值。
3.2 基础表盘:从零跑通一个能看的版本
下面是最小可运行版本。这个版本的代码量控制在 60 行以内,适合拿来验证环境:
const chart = Highcharts.chart('tempGauge', { chart: { type: 'solidgauge' }, title: { text: '设备温度实时监控', style: { fontSize: '18px' } }, pane: { startAngle: -120, endAngle: 120, background: [{ outerRadius: '100%', innerRadius: '70%', backgroundColor: Highcharts.color(Highcharts.getOptions().colors[0]) .setOpacity(0.12).get(), borderWidth: 0 }] }, yAxis: { min: 0, max: 100, lineWidth: 0, tickWidth: 0, minorTickInterval: null, tickAmount: 10, labels: { distance: 16, style: { fontSize: '12px', color: '#888' } }, plotBands: [ { from: 0, to: 60, color: '#4CAF50' }, { from: 60, to: 85, color: '#FF9800' }, { from: 85, to: 100, color: '#F44336' } ] }, series: [{ name: '温度', data: [26], dataLabels: { format: '<div style="text-align:center">' + '<span style="font-size:28px">{y}</span>' + '<span style="font-size:14px">°C</span></div>', borderWidth: 0, backgroundColor: 'transparent', y: 12 } }] });跑起来你会看到,一个半圆轨道分成了三段颜色,轨道内有一条青色或蓝色的填充带,长度停在 26 的位置,中间显示 26°C。
这里有个细节想单独说一下:pane.background。它控制表盘底面滑轨的颜色和范围。outerRadius: '100%', innerRadius: '70%'表示轨道占图表半径的 100% 到 70%,是一个圆环。颜色我用了 Highcharts 主题色加透明度,做的是浅色底层轨道,后面plotBands的色带会覆盖在上面,形成类似“底色轨道 + 分区色块”的效果。如果你不做background,整个表盘内部会是白色或透明,视觉上比较空。
3.3 加上报警阈值和视觉分层
上面版本的配色已经能用了,但真正放到监控大屏上,还是显得有点“素”。我的习惯是再做两层修饰:刻度线颜色加深、数据标签带一点状态色。
先说刻度线。目前tickWidth: 0实际上把刻度线全隐藏了,只显示了数字。如果想保留刻度分割线,调整成这样:
yAxis: { tickAmount: 10, tickLength: 8, tickWidth: 2, tickColor: '#666', minorTickInterval: 'auto', minorTickLength: 4, minorTickWidth: 1, minorTickColor: '#aaa' }tickLength是主刻度线的长度,minorTick系列是细分刻度,能增强“精密仪器”的质感。不过注意,minorTickInterval不要设得太小,否则 100 的量程可能会生成几十条小刻度,看久了很累眼。设成'auto'让 Highcharts 自己判断通常就行。
再说数据标签的状态色。根据当前温度处于哪个区间,数字颜色跟着变,报警时直接红色。实现方法是利用dataLabels.formatter而不是静态format:
dataLabels: { formatter: function () { const v = this.y; let color = '#333'; if (v >= 85) color = '#F44336'; else if (v >= 60) color = '#FF9800'; return '<div style="text-align:center">' + '<span style="font-size:28px;color:' + color + '">{y}</span>' + '<br/><span style="font-size:14px;color:' + color + '">°C</span></div>'; }, borderWidth: 0, backgroundColor: 'transparent' }formatter里用this.y拿到当前数据点的值,然后动态拼接 HTML。这段代码虽然只是改个颜色,但监控场景里“数字变红”这个反馈比任何报警声都直观。有条件的话,还可以在超过阈值时让数字加个闪烁动画,那属于锦上添花。
如果你更喜欢传统指针仪表,把上面的配置改成chart.type: 'gauge',去掉pane.background那段(或者保留作底色),然后在series里加上dial和pivot,基本指针表就出来了。刻度、分区、数据标签的配置全都可以复用。所以建议不急着二选一,两个都试一下,反正代码迁移成本特别低。
3.4 模拟实时数据刷新:setInterval 的正确用法
静态仪表盘只能当图看,实时监控才是测量图的灵魂。Highcharts 提供了好几个更新数据的方法,最常用的是series.setData()和point.update()。测量图推荐用point.update(),它只更新一个点的值,并且默认带动画过渡,看起来非常顺滑。
下面这段代码模拟每 2 秒从传感器读取一次温度,然后更新仪表盘:
setInterval(() => { const nextTemp = 20 + Math.round(Math.random() * 80); if (chart && chart.series && chart.series[0]) { chart.series[0].points[0].update(nextTemp, true, false); } }, 2000);update的三个参数分别是:新数值、是否触发重绘(true表示重绘)、是否启用动画(false表示沿用全局动画设置)。平时用update(value, true, true)也可以,但连续高频刷新时会觉得动画还没转完就又开始下一段,反而显得卡。实测下来,用true, false保留默认过渡就够了。
有一点要注意,setInterval一定要处理好生命周期,否则页面切换或组件销毁后定时器还在跑,轻则内存泄漏,重则报错。单页场景好办,直接在页面卸载时clearInterval。如果是大屏那种永不销毁的页面,也得留一个手动停止刷新的按钮,方便调试。
真实项目里,这里的随机数会换成 WebSocket 推送或 HTTP 轮询。比如用 WebSocket 收到消息后,拿message.data.temperature作为nextTemp更新。高频推送时建议做个节流,比如最多每秒刷新一次,因为仪表盘动画是有时长的,推送频率超过 10Hz 人眼也感知不到。
4. 常见问题与排查技巧
测量图本身不难,但实际开发里坑也不少。我整理了这几个高频问题,基本覆盖了日常会遇到的 90% 的情况。
4.1 图表白屏、模块引入顺序不对
表现:页面一片空白,控制台报错Highcharts error #17或Highcharts has no such method for solidgauge series。
原因:大概率是highcharts-more.js或solid-gauge.js没引入,或者引入顺序错了。highcharts-more.js必须在highcharts.js之后,solid-gauge.js必须在highcharts-more.js之后。
排查方法很简单,把依赖清单列出来逐一核对:
| 图表类型 | 必须引入的模块 |
|---|---|
| gauge(指针表) | highcharts.js + highcharts-more.js |
| solidgauge(实心表) | highcharts.js + highcharts-more.js + solid-gauge.js |
如果你用的是 Webpack/Vite 的 npm 包方式,也同理,import Highcharts from 'highcharts'之后要import 'highcharts/modules/solid-gauge.source'并执行SolidGauge(Highcharts)注册模块。一个文件都不要漏。
4.2 动画不生效或者指针跳动很生硬
表现:调用point.update()后数值变了,但指针是“瞬移”过去的,没有平滑过渡。
原因一:update第三个参数传了false,导致动画被禁用。改成true或者不放第三个参数就行。
原因二:图表加载时设置过animation: false,全局把动画关了。想保留动画,把全局配置改成:
Highcharts.setOptions({ animation: { duration: 600 } });duration是动画时长,单位毫秒。测量图指针从 20 转到 90,600 毫秒是比较舒服的节奏。设太短显得急,太长又会拖沓,尤其是在多仪表盘同屏更新时,动画不同步会显得很乱。
另一个“跳动”原因是数据更新频率太高。如果每 200 毫秒就update一次,动画还没播放完就被打断,视觉上跟闪跳一样。这种情况别优化动画了,直接在数据源侧做节流,或者把图表更新频率降到 1 秒一次。
4.3 大屏缩放以后图表变形、字体太小
测量图经常放在监控大屏上,屏幕分辨率五花八门。我的经验是,100% 尺寸的图表在高分辨率显示器上会显得很小,字体也看不清。这里有两个思路。
一是用 Highcharts 的chart.reflow自适应容器。默认reflow是开启的,容器宽度变化时图表跟着变,但有时候缩放浏览器窗口,图表不会自动重绘,可以手动监听:
window.addEventListener('resize', () => { chart.reflow(); });二是给仪表盘加responsive规则,在不同屏幕下调整尺寸和字体:
responsive: { rules: [{ condition: { maxWidth: 500 }, chartOptions: { yAxis: { labels: { distance: 10, style: { fontSize: '10px' } } } } }] }这段的意思是,容器宽度小于 500px 时,把刻度数字距离调近、字体调小,避免在小屏上挤成一团。大屏项目我还会预先算好比例,把外层容器设成基于vw的宽度,让图表随屏幕等比放大缩小,视觉上更统一。
4.4 在 Vue / React 里集成时的实例销毁问题
在框架里用 Highcharts,最容易翻车的就是生命周期。如果在组件销毁时没有调用chart.destroy(),图表实例会残留,绑定的事件、定时器、DOM 引用全都散在内存里。多次切换页面后,页面越来越卡,就是这样堆出来的。
Vue 3 组合式 API 里,标准写法是这样:
<script setup> import Highcharts from 'highcharts'; import HighchartsMore from 'highcharts/highcharts-more'; import SolidGauge from 'highcharts/modules/solid-gauge'; import { onMounted, onBeforeUnmount, ref } from 'vue'; HighchartsMore(Highcharts); SolidGauge(Highcharts); const containerRef = ref(null); let chart = null; onMounted(() => { chart = Highcharts.chart(containerRef.value, { /* 配置 */ }); }); onBeforeUnmount(() => { if (chart) { chart.destroy(); chart = null; } }); </script> <template> <div ref="containerRef" style="width: 100%; height: 360px;"></div> </template>React 的useEffect里也是同样思路:useEffect创建图表,cleanup 函数里chart.destroy()。另外注意,如果数据更新逻辑用了setInterval,无论图表实例是否销毁,定时器都要在清理函数里一并清除,这是内存泄漏的重灾区。
5. 几个做测量图时的加分技巧
技术层面讲得差不多了,最后分享一些实用技巧,都是这些年做项目沉淀下来的习惯。
第一个技巧是plotBands里面可以加label文字。比如在黄区中间显示“警告”,红区显示“危险”,这样比单靠颜色更明确。在高分辨率大屏上,红区里加一个白色小字“危险”,值班人员离屏幕三米远都能看清。
plotBands: [{ from: 85, to: 100, color: '#F44336', label: { text: '危险', align: 'center', verticalAlign: 'top', style: { color: '#fff', fontSize: '12px' } } }]第二个技巧是给仪表盘加“目标线”。监控场景里经常有“目标值 80,当前值 75”这种需求。除了用第二个指针,还可以用plotLines在 y 轴上加一条竖线:
yAxis: { plotLines: [{ value: 80, color: '#666', dashStyle: 'dash', width: 2, label: { text: '目标 80', align: 'right' } }] }虚线目标线加实心状态填充,比双指针更不容易让人看错。
第三个技巧是solidgauge配合series.data里每个点自己的颜色,做出“超阈值变色”的效果。我看到有不少人试图动态修改plotBands的颜色,其实没必要,而且容易把分区颜色弄乱。正确的做法是给数据点单独设color,比如超过 85 度时让让轨道填充色从绿色变红色,这样报警感更强。实现也不难,在更新数据前比较一下数值,然后chart.series[0].points[0].update({ y: value, color: tempColor })就行。
第四个技巧是关于仪表盘在多个指标同屏展示的场景。如果你要在一个页面放 6 个仪表盘分别显示温度、湿度、压力、流量等,别真建 6 个Highcharts.chart实例。用一个公共配置函数生成 options,然后循环初始化,既省代码又方便统一改主题:
const containers = ['temp', 'humidity', 'pressure']; containers.forEach(id => { Highcharts.chart(id, buildGaugeOptions(id)); });buildGaugeOptions里根据id动态生成标题、量程、单位、系列名,6 个仪表盘一套配置函数搞定,后续要统一加报警阈值也只在函数里改一处。
我个人在实际操作中的体会是,测量图这种图表类型,真正难的不是图形绘制,而是业务状态的梳理。量程设多少、安全区间怎么划、报警阈值定在哪,这些都得跟业务方反复确认。技术配置反而简单,你把pane、yAxis、series这三层理解透了,再去写指针表、实心表、环形表、双指针表,都是举一反三的事。希望这篇用 Highcharts 测量图搭建实时监控仪表盘的经验,能帮你少走几步弯路。