☰
Echarts热力图配置详解:从数据格式到visualMap实战
2026/9/29 1:13:27 网站建设 项目流程

做前端可视化这几年,Echarts热力图是我使用率最高的图形之一。它用色块颜色深浅来映射数值强弱,一眼扫过去就知道“哪里高、哪里低”,非常适合信号覆盖、客流分布、负载监控这类场景。这篇把Echarts热力图的配置项从零到一拆透,从series-heatmap的数据格式、visualMap的颜色映射与阈值设置,到坐标轴、tooltip细节,再结合两个实际案例(业务时段热力图、地图热力图)给出完整代码,最后附上我踩过的坑和排查思路。不管你是刚接触Echarts的新手,还是已经写了几个大屏但总在热力图上栽跟头的老手,这篇都能直接拿来用。

很多人一提热力图就只想到“在地图上画一团红的绿的”,其实热力图的应用范围远不止地理场景。它本质上是一种用颜色编码数值的矩阵可视化技术,数据密度高、趋势对比直观,是数据可视化大屏里的常客。这篇我就围绕热力图配置项展开,尽量说人话,把每个参数背后的逻辑讲清楚。

1. 先把热力图的定位搞明白

1.1 热力图、色斑图与密度图是什么关系

先理清几个容易混的概念。Echarts里官方叫法是Heatmap,中文一般叫热力图。气象行业喜欢叫“色斑图”,比如降水预报图、温度分布图,本质就是热力图加了一个地理底图。还有一部分人管散点密度图叫热力图,比如地图上那种“信号热力图”,点是随机分布、按密度着色的,这个在Echarts里对应的是series-scatter配合visualMap实现,不是我们今天讲的series-heatmap。

在实际项目中我见过不少把两种图混着用的。series-heatmap的特点是有明确的行列坐标轴,数据是一格一格的,比如“星期 x 小时”的流量矩阵、CPU核数 x 时间的利用率矩阵。而散点热力图是连续坐标,数据点没有固定网格,适合做轨迹、信号覆盖这类空间分布。

搞清楚这个区别很重要,因为配置项完全不同。如果你拿散点的数据格式去配heatmap,图表基本不会正常渲染。我做这个区分是希望大家在选型的时候心里有数,不然后面改配置改到怀疑人生。

1.2 Echarts热力图的核心配置链路

Echarts里实现一张热力图,核心就是两条配置链路:一条是series-heatmap负责画格子,另一条是visualMap负责给格子填颜色。这两条链路缺一不可。

const option = { xAxis: { type: 'category' }, yAxis: { type: 'category' }, visualMap: { min: 0, max: 100, inRange: { color: [...] } }, series: [{ type: 'heatmap', data: [[x索引, y索引, value], ...] }] }

xAxis和yAxis都是类目轴,决定了热力图的网格结构。series.data里的前两个值不是具体坐标,而是类目的索引值,第三个值才是要映射颜色的数值。visualMap则负责把数值区间映射成颜色渐变。

还有一个隐藏链路是geo。当地理坐标系的热力图,比如echarts中国地图上的热力分布,就得用coordinateSystem: 'geo'把heatmap绑定到地理坐标系上,这时data格式也会发生变化,后面实操部分我会详细展开。

1.3 适用场景与不适合场景

我自己的判断标准是:数据能被整理成“两个维度 + 一个数值”的结构,就适合用热力图。两个维度比如星期和时段、日期和小时、CPU核数和时间点,一个数值就是你想观察的指标,比如请求量、耗电量、在线人数。

反过来,如果只有一个维度的变化,或者维度超过三个,热力图就很别扭。只有一个维度直接画折线图、柱状图更直观;维度太多热力图会变成“花格子衬衫”,人眼根本读不出信息。还有,如果数据量特别稀疏,比如一个几十乘几十的矩阵里只有三五个有值,那也别用热力图,一眼望去全是背景色,没有意义。

当时我做一个全国性的网络质量监控大屏,涉及几百个地市的上百个指标,把指标切成“城市 x 响应时长”的矩阵,热力图选点非常合适,但是后来数据维度增加到十几个指标,矩阵一下就乱了。最后是把指标拆成多个热力图,用tab切换才解决。

2. Echarts热力图配置项逐项拆解

2.1 series-heatmap:数据格式与基础属性

先看series-heatmap本身。它的核心属性不多,但每个都很关键。

series: [ { type: 'heatmap', data: [ [0, 0, 5], [0, 1, 12], [1, 0, 8], [1, 1, 20] ], label: { show: true, color: '#333' }, itemStyle: { borderWidth: 1, borderColor: '#fff', borderRadius: 2 }, emphasis: { itemStyle: { shadowBlur: 8, shadowColor: 'rgba(0, 0, 0, 0.3)' } } } ]

data数组里的每一项都是一个长度为3的小数组,顺序固定:第一个是x轴类目索引,第二个是y轴类目索引,第三个是映射颜色的值。索引从0开始,对应xAxis.data和yAxis.data里的第几个类目。顺序千万不能搞反,我第一次写的时候把经度纬度习惯带进来了,结果图直接乱掉。

label.show控制是否在格子上显示数值。数据量小的时候显示数值是很加分的,一眼能看到具体是多少;数据量一大就要关掉,否则标签重叠,看起来像一锅粥。

itemStyle里的borderWidth和borderColor是给格子加边框,白色边框配合深色格子会形成一种“九宫格”的视觉分隔,观感会好很多。borderRadius可以让格子变成圆角,适合数据量少、偏展示型的场景。强调态emphasis.itemStyle是鼠标悬停时的效果,热力图默认悬停会有个高亮,我习惯加一层阴影让它更明显。

2.2 visualMap:颜色映射与threshold阈值设置

visualMap是热力图真正的灵魂。它把数值翻译成颜色,直接决定了整张图的观感。

visualMap: { type: 'continuous', min: 0, max: 100, calculable: true, orient: 'vertical', left: 'right', top: 'middle', inRange: { color: ['#313695', '#4575b4', '#74add1', '#abd9e9', '#fee090', '#fdae61', '#f46d43', '#d73027'] } }

min和max就是阈值边界。这两个值设得好不好,直接决定热力图能不能看出层次。如果业务上数值是0到100,你只设到100但实际数据最大只有10,那图上所有格子都接近同一个颜色,等于白画。反过来max设太小,超过的数据会被统一映射成最深色,也丢信息。

设置threshold的一般做法是先看一下数据分布。我当时做流量热力图,请求量中位数是3000,但偶尔有峰值到5万,如果max直接设50000,90%的格子都淡得看不见。后来我按百分位取,max设为P95的值,也就是12000,虽然峰值格子会“顶格变红”,但这个视觉反差反而挺合理,用户看到几个红点就知道发生了异常。

calculable: true会在图表上生成一个可拖动的滑块,用户可以直接拖动阈值范围,实时观察不同数值区间的分布情况。交互上非常实用,但要注意如果图表空间很局促,滑块会挤占布局。

inRange.color是核心参数,一个颜色数组,按数值从低到高排列。选色时有个原则:数值低的一头用冷色(蓝、青),数值高的一头用暖色(黄、橙、红),这是人类视觉习惯,看到红色会下意识觉得“高、危险”。中间可以穿插几个过渡色,让变化更平滑。

如果你需要分级着色,比如信号强度大于-60显示绿色,-60到-90显示黄色,小于-90显示红色,那就把type改成piecewise,用pieces阈值区间来精确控制:

visualMap: { type: 'piecewise', pieces: [ { min: -90, max: -70, color: '#d73027' }, { min: -70, max: -50, color: '#fdae61' }, { min: -50, max: 0, color: '#1a9850' } ] }

这种分段模式在“相关热力图阈值”这种需要对特定数值区间做告警或分级的场景里特别好用,比连续渐变更符合业务直觉。

2.3 xAxis / yAxis:热力图的坐标轴细节

热力图的坐标轴跟普通柱状图折线图不太一样,必须用类目轴,而且一般都要开boundaryGap。

xAxis: { type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], splitArea: { show: true }, axisLine: { lineStyle: { type: 'solid' } } }, yAxis: { type: 'category', data: ['0点', '1点', '2点', '3点', '4点'], splitArea: { show: true } }

boundaryGap默认是true,意思是一个类目占据一个区间段,格子跟格子之间不会有空隙。如果你把它设成false,格子就压缩成一条线,热力图直接变成“斑马线”,这不是我们想要的。坑爹的是,柱状图里boundaryGap默认是true,折线图默认是false,很多人从柱状图切过来就忘了这个差异。

splitArea.show: true会给每个类目区域画淡色的分割背景,这样热力图的每个格子边界会更清楚,尤其在没有格子边框的时候特别有用。但注意splitArea的颜色要调淡一点,不然格子本身有颜色,背景又有颜色,两层层叠看起来脏兮兮的。

坐标轴类目如果特别多,比如x轴是24小时、y轴是60分钟,那就是1440个格子,标签肯定放不下。这时候要么开axisLabel.interval控制显示间距,要么用dataZoom组件让用户可以缩放查看。Echarts的dataZoom在热力图上跟普通图表用法一样,直接配置type: 'inside'或slider,就能对x轴做区域缩放,大数据量时非常实用。

2.4 tooltip:气泡内容、换行与联动

热力图的tooltip默认只显示x轴、y轴和数值,格式是固定的。实际项目中往往需要显示更多上下文信息,比如“星期一的12点,请求量是3500,环比上升12%”,这就要重写formatter。

tooltip: { trigger: 'item', formatter: function(params) { var xLabel = params.name[0]; var yLabel = params.name[1]; var value = params.value[2]; return [ yLabel + ',' + xLabel, '请求量:' + value + ' QPS', '环比:' + (value > 3000 ? '+12%' : '-3%') ].join('<br>'); } }

注意params.name在热力图里是一个数组,params.name[0]是x轴类目名,params.name[1]是y轴类目名,这点跟普通图表不一样,我第一次写的时候还以为name是字符串。

还有一个高频需求是tooltip自动换行。Echarts的tooltip默认宽度是根据内容自适应,如果内容太长,尤其在大屏上中文和英文混排,气泡会横向拉得很宽,非常难看。解决办法是在formatter返回的HTML里包一层div,限制最大宽度并设置换行:

formatter: function(params) { return '<div style="max-width: 240px; white-space: normal; word-break: break-all;">' + '设备编号:' + params.value[0] + '<br>' + '信号强度:' + params.value[2] + ' dBm<br>' + '说明:该设备信号衰减较明显' + '</div>'; }

另外,如果你的大屏是多个图表联动,热力图的tooltip也可以配合dispatchAction来实现联动效果。比如鼠标悬浮在某个格子上,同时高亮旁边的柱状图对应的柱子,实现思路是在热力图的mouseover事件里手动触发柱状图的highlight动作,配置不复杂但很出效果。

3. 实操一:用热力图做一周分时段流量矩阵

3.1 数据组织

先来一个最常用的业务场景:一周7天、每天24小时的网站流量热力图。这种图适合看“什么时候是流量高峰”“周末和工作日的节奏差异”,信息密度很高。

数据组织上,我习惯用一个二维数组,行是小时,列是星期。然后遍历生成data数组:

var days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']; var hours = []; for (var h = 0; h < 24; h++) { hours.push(h + ':00'); } var matrix = [ // 00:00, 01:00, ..., 23:00 [120, 80, 60, ...], [140, 90, 50, ...], // 共7行,对应7天 ]; var data = []; for (var i = 0; i < days.length; i++) { for (var j = 0; j < hours.length; j++) { data.push([j, i, matrix[i][j]]); } }

这里[j, i, matrix[i][j]]第一个值是x轴索引(小时),第二个是y轴索引(星期),我为了可读性把i换成j,大家理解坐标系就行。matrix的维度是 7 x 24,正好对应坐标轴。

3.2 完整配置代码

var myChart = echarts.init(document.getElementById('main')); myChart.setOption({ tooltip: { trigger: 'item', formatter: function(params) { return params.name[1] + ' ' + params.name[0] + '<br>访问量:' + params.value[2] + ' 次'; } }, grid: { left: 80, right: 30, top: 30, bottom: 40 }, xAxis: { type: 'category', data: hours, name: '小时', nameLocation: 'middle', nameGap: 30, splitArea: { show: true } }, yAxis: { type: 'category', data: days, name: '星期', nameLocation: 'middle', nameGap: 40, splitArea: { show: true } }, visualMap: { min: 0, max: 500, calculable: true, orient: 'horizontal', left: 'center', bottom: 0, inRange: { color: ['#313695', '#74add1', '#fee090', '#f46d43', '#d73027'] } }, series: [{ type: 'heatmap', data: data, label: { show: true, color: '#fff', fontSize: 12 }, itemStyle: { borderWidth: 2, borderColor: '#fff', borderRadius: 4 } }] });

这里grid要留出足够的边距,因为坐标轴有名称。visualMap我放在底部水平方向,适合横向矩形布局。label.show我开了,数据量是7x24=168个点,格子不算多,能显示数值,大屏上看得清;如果你换成实时监控数据比如30天x24小时,720个点就建议关掉label。

3.3 配置项说明与效果调整

你运行之后会发现格子是有圆角的,这是borderRadius的效果,数据量少的时候会显得精致。如果你希望更“数据密集”的感觉,把borderRadius改为0,borderWidth改为1,会变成标准的网格热力图。

关于visualMap的max值,我前面说过要用数据分布来定。上面代码里max设500,是因为matrix里最高的峰值我压到了480。如果你的数据峰谷差异特别大,可以先跑一遍统计再定阈值,不要拍脑袋。

还有一种常见需求是只要“高于某个阈值才显示颜色”,比如访问量低于50的格子全显示灰色。实现方式是用visualMap的visualRange或者formatter来做。直接的做法是给数据做一些预处理,把低于阈值的值设成null,配合visualMap的enable逻辑,可以做出“有颜色的区域才是有效区域”的效果。

4. 实操二:热力图叠加地图做地理分布

4.1 echarts中国地图的地理数据准备

地理热力图是另一个高频场景。很多人想做信号热力图、客流量热力图,一听要搞地图数据就头大。Echarts 5和之前的版本有个重要的区别:官方包不再内置中国地图的geoJSON数据,我用echarts 5之后每次都需要单独准备地理数据。

最稳妥的办法是找一个china.json文件,放到项目里,然后用echarts.registerMap注册。注册的方式很简单:

import chinaGeo from './assets/china.json'; echarts.registerMap('china', chinaGeo);

如果你用的是CDN方式引的echarts,不能直接用import,也要通过url获取json再注册。我通常直接放到public目录下,用fetch加载。

4.2 geo坐标系下的热力图层

有了地图底图之后,热力图需要从默认的笛卡尔坐标系切换到地理坐标系,关键配置是coordinateSystem: 'geo',同时data格式变成[经度, 纬度, value]。

option = { geo: { map: 'china', roam: true, zoom: 1.2, itemStyle: { areaColor: '#1a1a2e', borderColor: '#333' } }, visualMap: { min: 0, max: 100, calculable: true, inRange: { color: ['#313695', '#74add1', '#fee090', '#f46d43', '#d73027'] } }, series: [{ type: 'heatmap', coordinateSystem: 'geo', data: [ [116.4074, 39.9042, 85], [121.4737, 31.2304, 70], [113.2644, 23.1291, 45] // ...更多城市数据 ], pointSize: 8, blurSize: 15 }] };

这里有个注意点:coordinateSystem: 'geo'下的heatmap,渲染的不再是矩形格子,而是圆形的散点融合效果,点与点之间会通过模糊半径产生连续的渐变,这就是很多人说的“热力图”效果,在气象里叫“色斑图”。pointSize控制单个点的影响范围,blurSize控制模糊半径,这两个参数直接影响图的平滑程度。

我踩过一个坑:geo配置和series里的geoIndex搞混。如果你的图表里既有geo组件又有grid坐标系,Echarts会默认把heatmap渲染到grid上,导致地图热力不显示。解决方法是明确指定geoIndex: 0,或者在series里单独用geo: {}内联配置。我习惯只在option顶层配geo,然后series里写coordinateSystem: 'geo',这样最不容易出错。

4.3 色斑图与渐变调整

很多做气象、环境监测的朋友跟我聊过“色斑图”的配置需求,要求颜色平滑过渡,不能有马赛克一样的格子感。在地理坐标系里,把pointSize调大、blurSize调大,颜色就自然融合了。

如果要做更精细的“信号热力图阈值分级”,比如信号强度在不同区间的颜色要严格区分,就不适合用continuous的visualMap,改成piecewise分段会更准。我做过一个4G信号覆盖图,就是把强度分成四个区间:极好、良好、一般、弱覆盖,每个区间一个颜色,再用pieces精确控制边界。

还有一个技巧:地理底图颜色要压暗。热力图是前景,地图是背景,如果底图颜色太鲜艳,热力的颜色会被“抢戏”。一般我把geo.itemStyle.areaColor设为深色(深蓝、深灰),borderColor设为对比色,让地图只提供一个轮廓参考,视觉重心完全给到热力层。

5. 配置中的视觉和性能细节

5.1 色板选色的经验

热力图的色板选得好不好,直接影响整张图的可读性。我总结了一套自己的配色经验,分享出来:

  • 少用彩虹色。虽然Echarts默认的['#313695', ..., '#d73027']这种效果不错,但有些开发者随手从网上复制一个七彩虹色板,颜色太多反而让“高”“低”的判断变模糊。
  • 推荐“感知均匀”的渐变色。比如['#2c7bb6', '#abd9e9', '#ffffbf', '#fdae61', '#d7191c'],这是科学可视化里经典的色带,浅色在中间,两端深色,对于有正负值或对称阈值的数据非常合适。
  • 深色背景下用亮色渐变。大屏通常用深色背景,这时候用['#000004', '#2c115f', '#721f81', '#b73779', '#f1605d', '#fbc7a3']这种从黑到紫到橙的渐变,对比度高、又不刺眼。
  • 色盲友好。如果你不确定受众是否有色觉障碍,最好避开“红绿”对比。选择蓝橙、蓝黄这类色系更安全。

5.2 大数据量场景下的性能优化

热力图的数据量上来之后,性能会肉眼可见地下降。我做过一个设备状态监控,x轴是500台设备、y轴是1440分钟,总共72万个数据点,第一次渲染直接卡了2秒多,拖拽缩放也非常卡。

优化思路有三条线:

第一,降采样。如果数据本身是高密度矩阵,先用算法把数据点减下来,比如对相邻N个点取平均值,再喂给Echarts。这个在数据预处理阶段做,效果最明显。

第二,关闭渲染内耗。在series上设置animation: false,避免每次数据变化都触发动画;large: true开启大数据量模式,几千个格子以上会有明显提升。

第三,用dataZoom做可视化切片。不要一次性渲染全部数据,先渲染当前可视区域,配合dataZoom的filterMode: 'none'属性,让用户在缩放的区域里去看细节。这样初始渲染的数据量小了,交互也流畅。

5.3 大屏适配与pxtorem的坑

大屏项目里适配是个绕不开的话题。有人给Echarts容器设置了固定px宽度,结果屏幕分辨率一变图就溢出;有人用rem跟着根元素缩放,结果Echarts内部canvas绘制的像素没有跟着缩放,图表变得“忽大忽小”。

我在vue3项目里就遇到过一个典型问题:postcss-pxtorem对Echarts没起到效果。原因是pxtorem只处理样式表里的px单位,而Echarts通过canvas渲染的图形坐标是js在运行时直接写入的像素值,根本不会经过css编译这一层。所以你就算把容器字体改成rem,canvas里画的格子位置还是按像素算的。

正确的适配姿势是监听容器宽度变化,动态调用chart.resize(),并且把关键配置项(比如visualMap的left、grid的间距)用百分比或回调函数计算。还有一个办法是用Echarts的convertToPixel接口,把需要“像素级联动”的坐标换算出来。我用得最多的还是百分比宽度 + resize监听 + 关键词参数用计算函数生成,三个组合基本能适配所有大屏。

6. 常见问题与排查技巧实录

6.1 问题速查表

我把这些年做热力图遇到的典型问题整理成一个速查表,大家可以对照排查:

现象可能原因解决办法
热力图一片空白series.data里没有数据或格式不对检查data每项必须是[xIndex, yIndex, value]
颜色没有渐变,全是一种色visualMap缺失或min/max设置不合理补上visualMap,按数据实际范围设置min/max
格子错位,行列对不上索引顺序写反确认xAxis是第一个下标,yAxis是第二个下标
地图热力不显示没注册geoJSON或coordinateSystem没设为geo用registerMap注册地图,series加coordinateSystem: 'geo'
tooltip内容挤成长条默认宽度未限制formatter返回div并设置max-width和white-space
数据量大时卡顿未开大图模式和关动画设置large: true, animation: false,配合dataZoom
坐标轴标签挤在一起类目太多或标签太密用axisLabel.interval或dataZoom缩放
大屏缩放后图形错位没监听resize或用了固定px加resize监听,用百分比布局,关键参数动态计算

6.2 排查思路示例

举一个实际案例。有个朋友的项目里,热力图在本地开发环境显示正常,一放到大屏服务器上就变成空白。他排查了很久,最后发现远程环境里报错是Map 'china' not exists,问题在于Echarts 5不再内置中国地图geoJSON,他本地是通过某个插件自动注册的,服务器上没装那个插件,所以地图组件直接报错。

这类问题本质上是“环境依赖不一致”。我的建议是,凡是地图类图表,一律在入口文件里显式执行echarts.registerMap并打包进业务代码,不要依赖任何CDN数据源。这样不管是本地、测试还是生产环境,行为一致,排查起来也省心。

还有一个高频问题是“热力图格子颜色都是最小值的颜色”。之前有个同事做的告警热力图,无论数据怎么变化,所有格子颜色都一样。一查,发现visualMap的max设成了100000,而实际数据只有几十,映射结果自然全在低值段。把max改成50之后,图马上就正常了。

排查visualMap相关问题时,我通常会先在图表旁边临时加一个calculable: true的滑块,手动拖一下看颜色变化,如果能变化说明映射逻辑正常,问题就在min/max范围上;如果滑块拖动没反应,那就是visualMap的数据映射链路有问题。这个方法特别快,建议大家试试。

6.3 我再补几个独家避坑技巧

热力图数据里如果有异常值和缺失值,别急着塞进去。缺失值如果设成0,会跟真实低值混淆;设成null,Echarts默认会用最小颜色填充,也会误导。我一般做法是数据清洗阶段把无效点标记为null,然后在visualMap的formatter或者tooltip里做特殊说明,让看板用户知道这块是“无数据”而不是“低值”。

如果要在同一个页面上放多张热力图对比,比如不同机房的流量热力图并排展示,每张图尽量用相同的visualMap范围,否则颜色深浅无法互相比较。这个我在做监控大屏时深有体会,当时两屏图各自自适应min/max,结果A机房50的流量是红色,B机房50的流量是蓝色,用户一眼看过去全乱了。后来统一设置成全局阈值,就再也没有这种误解。

结语:我的热力图配置心得

做Echarts热力图的这几年,我最大的感觉是,热力图的上手门槛不高,但真正做好看的坑是真的多。很多人觉得配置项不就那几个嘛,但实际一跑起来,不是颜色不对,就是位置错乱,或者大屏一缩放全废。我的经验是:先把series-heatmap的数据格式吃透,再把visualMap的映射逻辑想明白,这两步走稳了,热力图基本就成功了一大半。至于颜色、性能、联动这些,都是在此基础上的加分项。

最后再分享一个小技巧:Echarts官方的示例编辑器和社区里有大量热力图相关作品,遇到不懂的配置项,最快的办法就是去社区找一个相似效果,把它的option拿下来用你的数据跑一遍,边跑边改,比死记API文档高效得多。

如果你正在做信号热力图、客流热力图或者任何需要“看分布”的场景,希望这篇文章能帮你避开我踩过的那些坑。有更好的配色方案或者性能优化思路,也欢迎交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询