做数据可视化大屏和业务报表这几年,我发现很多人对 ECharts 的抱怨都集中在同一个地方:图怎么又顶格了?标题离上边框就剩一口气,Y 轴文字往左冒出半截,想把图表往下压一压给下面的指标卡让个位,结果 setOption 写了十几行也没动静。说实话,这类问题十有八九都出在同一个家伙身上——grid 组件。
grid 是 ECharts 直角坐标系图表的“地基”,所有柱状图、折线图、散点图的绘图区边界,都由它来控制。你看到的图表“贴边”“被裁”“挤在一起”,本质上不是 ECharts 画错了,而是 grid 区域的上下左右留白不对。这篇文章就围绕 grid 组件,把图表位置调整这件事彻底捋清楚,从最常用的单图定位、多图分块,到 containLabel 的隐性规则、rem 适配下位置失效的坑,全部过一遍。适合刚上手 ECharts 的前端新手,也适合做大屏做到头秃、想救一下布局的“老手”。
1. grid 组件到底是什么,它管哪块地
1.1 grid 对应的就是绘图区的“边界”
很多人一打开官方文档,看到 grid 的配置项那一大屏就有点发怵。其实你可以把它想成一张画布上给图表“圈地”的橡皮筋。
一个常见的直角坐标系图表,画布上存在这些元素:标题 title、图例 legend、x 轴和 x 轴的刻度文字、y 轴和 y 轴的刻度文字、数据图形(柱子、线条、散点)。这些元素不可能都叠在一起,总得有块区域专门画数据图形,轨道四周留出空间给坐标轴文字和别的组件。grid 组件就是用来定义这块“数据图形专用区域”的。
所以 grid 只对直角坐标系系列生效,比如 line、bar、scatter、candlestick。饼图 pie 不走 grid,它用的是 center 定位,这个细节我在第 4 部分专门说。
grid 的默认值其实非常保守,它会让绘图区占满整个容器、然后四周留一点点边距。你要是没配 grid,ECharts 也会按默认参数渲染,所以看到图“顶天立地”很正常。
1.2 定位属性怎么用:left / top / right / bottom 与百分比
grid 里最常用的是四个位置属性:
- left:grid 区域离容器左边界的距离
- top:grid 区域离容器上边界的距离
- right:grid 区域离容器右边界的距离
- bottom:grid 区域离容器下边界的距离
- width:grid 区域的宽度
- height:grid 区域的高度
先说取值。这四个值都可以是数字或字符串。数字就是像素值,比如 left: 80 表示离左边 80px。字符串一般写成百分比,比如 left: '10%',这个百分比是相对容器宽度的。top 和 bottom 的百分比相对容器高度。
这里有个容易绕晕的点:同时设置 left 和 right 时,ECharts 会自动算出 grid 的宽度,这时候 width 可以不写。同时设置 top 和 bottom,height 可以不写。但如果你把 left、right、width 三个都写了,ECharts 实际计算时优先用 left + width,right 相当于被忽略。我在项目里踩过这个坑,调了半天以为 right 没生效,最后发现是 width 写死了。
我来写一个最普通、最常用的例子。
option = { grid: { left: 80, top: 60, right: 40, bottom: 50 }, xAxis: {}, yAxis: {}, series: [ { type: 'line', data: [120, 200, 150, 80, 170] } ] };假设容器宽 800、高 600,那 grid 区域的计算就是:x 从 80 开始,到 800 - 40 = 760 结束,宽度 680;y 从 60 开始,到 600 - 50 = 550 结束,高度 490。数据图形就画在这块 680 × 490 的区域内。
2. 单图位置调整:从默认“顶满”到自由留白
2.1 一个最常用的“留白”配置
实际开发里,大多数需求并不是要做出多么复杂的多图联动,而是想让一张图好好待在页面里,四周别碰边。这时候我推荐直接用一组相对保守的值,再配合 containLabel 一起用。
先看这段配置:
grid: { top: 70, left: 16, right: 24, bottom: 40, containLabel: true }为什么 top 要刻意给到 70?因为如果你的容器上方有标题,或者大屏页面上方有一条很占高度的导航栏,那么 grid 区域离顶部太近,数据图形就会和标题贴脸。给到 70px 算是给标题和留白都留了余地。
left 给 16 在 containLabel: true 的情况下通常够用,因为 containLabel 会把 y 轴的刻度文字算进 grid 区域内。right 给 24 是为了让最后一个柱子或者折线末端不贴右边框。bottom 给 40 是因为 x 轴下方除了刻度文字,有时候还要塞 legend、dataZoom,甚至是自己用 HTML 拼的单位说明。
有的同学会问:既然 containLabel 已经把刻度文字算进去了,left 为什么还要给 16?因为 containLabel 只是包含刻度文字,不代表左边一点呼吸感都没有,而且 offscreen 容器或特殊字体渲染时,过小的 left 仍可能造成文字贴边。稳妥起见我习惯至少留 16px。
2.2 containLabel 与刻度文字溢出的账
containLabel 是 grid 组件里最具迷惑性的属性,没有之一。
默认情况下,containLabel 是 false。这时候 grid 区域到底是怎么画坐标轴文字的?我举个例子:你要画一个柱状图,Y 轴刻度文字是“1,500”“2,000”这种很宽的数字。如果 containLabel 为 false,grid 的 left 只控制“柱子区域的左边”在哪里,Y 轴文字是画在 grid 区域左侧之外的。
换句话说,left: 80 时,柱子从 x=80 开始往右排,而 Y 轴文字在这个 80px 宽的左侧区域里渲染。一旦文字宽度超过 left 指定的 80px,文字就会溢出画布左边界,出现“刻度被切掉一半”的效果。
把 containLabel 设为 true 之后,ECharts 会做一次额外的测量:它先算出 grid 区域,再把坐标轴刻度文字的实际尺寸测量出来,然后自动把 grid 区域往右“顶”一点,确保文字完整显示在画布内。
听起来很美好,但注意:containLabel: true 不等于说 left 可以随便填 0。如果 Y 轴文字特别宽,加上 left:0,文字被包含进去了,但数据图形区域会被压得很窄。所以我在调 containLabel 的时候,会先用一个大致预留值,再开 containLabel,看最终渲染效果再微调。
有一个调试技巧:把 grid 的 backgroundColor 临时设成一个半透明颜色,比如 'rgba(255,0,0,0.2)',就能一眼看到 grid 区域到底占多大、数据图形画在哪。调好位置后记得删掉这个属性。
2.3 手动估算绘图区范围
做数据大屏时,经常会遇到“图表在 1920×1080 下刚刚好,换到 1366×768 就崩了”的情况。这时有个手工估算绘图区的思路很实用。
比如容器宽 1920,我要让图表区域大致落在 x=100 到 x=1820 之间。那么 grid.left 设为 100,grid.right 设为 1920 - 1820 = 100。如果容器宽度变了,这两个像素值是按绝对像素来的,不会自动缩放,所以大屏适配时我更推荐用百分比。
grid: { left: '8%', right: '8%', top: '12%', bottom: '8%' }百分比的好处在于它天然是响应式的,容器怎么变,grid 边界都会按比例跟着变。但它的缺点是,当容器很扁时,top 和 bottom 的百分比会显得过大或过小,因为百分比是相对高度的。实际项目中我经常混用:左右用像素,上下用百分比。比如:
grid: { left: 80, right: 40, top: '10%', bottom: '6%' }这样左右保持稳定的留白,上下随高度自适应。这也是我在大屏项目中比较推荐的一种混搭策略。
3. 一屏多图:用 grid 数组管理多块“地皮”
3.1 上下双图布局写法
一个容器里塞两张图,最简单的办法是拆成两个 div、分别初始化两个 ECharts 实例。但有些场景必须在一个实例里画多组图,比如两个 x 轴共用、两张图联动 tooltip。这时候 grid 就必须写成数组。
举一个很常见的上下双折线图布局:
option = { grid: [ { left: 70, right: 30, top: 40, height: '35%' }, { left: 70, right: 30, top: '58%', height: '30%' } ], xAxis: [ { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], gridIndex: 0 }, { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], gridIndex: 1 } ], yAxis: [ { type: 'value', gridIndex: 0 }, { type: 'value', gridIndex: 1 } ], series: [ { type: 'line', data: [120, 200, 150, 80, 170], xAxisIndex: 0, yAxisIndex: 0 }, { type: 'line', data: [30, 45, 28, 60, 42], xAxisIndex: 1, yAxisIndex: 1 } ] };这里 grid 数组里有两块地皮。第一块 top: 40,height: '35%',意思是从顶部往下 40px 开始,高度占容器高度的 35%。第二块 top: '58%',意思是从容器高度 58% 的位置开始往下排,height: '30%'。
有人会问,为什么第二块不直接 top: '40%' 再 height: '35%'?因为如果第一块下方直接紧贴第二块,x 轴刻度文字和 y 轴刻度会挤在一起,视觉上很难受。我把第二块的 top 放在 58% 左右,给中间留出一段空白,相当于给第一块的 x 轴标签和第二块的标题都腾了地方。
3.2 xAxis / yAxis 与 grid 的索引绑定
多 grid 布局里最容易犯错的地方,不是 grid 本身,而是坐标轴和 series 的索引没对上。
xAxis 数组里的第一项默认对应 grid[0],xAxis[1] 对应 grid[1],yAxis 同理。但一旦 grid 之间上下错位了,或者 series 里忘了写 xAxisIndex,图就会画到意想不到的区域去。
我在自己的项目里有个习惯:只要 grid 超过一个,xAxis、yAxis、series 里的索引全部显式写明,绝不靠默认绑定。上面代码里的 gridIndex、xAxisIndex、yAxisIndex,一个都不省。
这么做还有一个好处:当我要调整某张图的位置时,只需要改对应 grid 的 top / left / height,不需要翻 series。比如想让下面的图高一点,就改 grid[1].height 和 grid[1].top,下面的图所有内容会跟着整体移动。
3.3 双图联动位置布局的注意点
很多时候双图不只是上下排布,还要求左右两列,或者一大一小嵌套排布。grid 数组对每个元素都是独立的“地皮”,可以自由组合位置。
比如左侧一个 60% 宽的主图,右侧上下堆两个 30% 左右的辅助图,grid 可以这样配:
grid: [ { left: 60, right: '38%', top: 40, bottom: 50 }, { left: '66%', right: 30, top: 40, height: '35%' }, { left: '66%', right: 30, bottom: 50, height: '25%' } ]这种布局花点时间就能调好,但要注意标题和图例的占位。如果每个子图都有自己的标题,不能直接用 title 组件——因为 title 是全局的,不能绑定到某个 grid 上,除非用 title 数组并分别指定 top、left。实际做项目时,我更喜欢把多张图拆成多个容器分别初始化,只有在需要共享 tooltip 或者轴联动时才会用单实例多 grid。
4. 那些看起来叫“位置”但不是 grid 管的事
4.1 饼图没有 grid,你调位置用的是 center 和 radius
这个问题我在社区看到过太多次了:“我给饼图配了 grid,为什么图纹丝不动?”答案很简单,饼图不走直角坐标系,grid 是倒腾 x 轴和 y 轴的,饼图上根本没这俩轴。
饼图自己的定位方式是 center 和 radius。
option = { series: [ { type: 'pie', center: ['50%', '50%'], radius: ['30%', '60%'] } ] };center 接收一个数组,第一项是圆心水平位置,第二项是圆心垂直位置,可以写百分比或像素。radius 如果是数组,第一项是内圈半径、第二项是外圈半径,这样就形成了环形饼图。
大屏布局时我常把饼图 center 设置成 ['50%', '55%'],因为顶部要留出标题和 legend 的空间,圆心视觉中心需要比容器几何中心稍微往下移一点,否则饼图和标题会挤。
另外,饼图的 labelLine 如果觉得和图形挨太近,可以单独调 labelLine 的 length 和 length2,这是控制引导线长度用的,不是 grid 能管的范畴。
4.2 地图的位置由 geo / map 的 center 和 zoom 决定
热词里有 echarts 中国地图,这个也得单独拎出来说。地图组件没有 grid,它靠 center 和 zoom 控制位置和缩放。
geo: { map: 'china', center: [104.0, 37.5], zoom: 1.2 }center 里填的是经纬度,zoom 是缩放比例。地图展示时如果发现某个区域跑到画布外面,不要想着用 grid 或者 left/top 去推,那些都不管用。正确姿势是调 center 把视野中心移到目标区域附近,再调 zoom 缩放到合适级别。
我实际做中国地图时,默认 center 会偏右或者偏下,一般会手动调到 [104, 37] 左右,再根据画布长宽比微调 zoom。这个值没有一个固定的标准答案,因为不同比例容器下地图的裁切效果差异很大,只能边看边调。
4.3 markPoint 的定位“坐标”是什么
markPoint 也经常被人当成“位置”问题来调,尤其是地图上的标记点。其实 markPoint 是 series 上的配置,坐标语义跟随坐标系:
- 直角坐标系图表里,markPoint 的 coord 是 [x轴值, y轴值],例如 line 图里 coord: ['Mon', 120] 会标在“周一”那个点上方。
- 地图系列里,coord 是经纬度,比如 coord: [116.4, 39.9] 就会标在北京附近。
如果你看见 markPoint 位置不对,先检查 coord 数组的语义是不是和当前坐标系匹配。常见错误是在地图上用像素坐标、在折线图上塞经纬度,那肯定是不对的。
5. 位置调整中的高频坑位与排查记录
5.1 containLabel 忘开导致 Y 轴文字被吃
这是新手上路最容易踩的坑,也是我一开始写代码时忽略到怀疑人生的问题。
症状:柱状图左边刻度文字“1,200”显示不全,最前面两位数字被切掉;或者折线图 y 轴单位文字离左边缘只剩半个字符。
原因:grid 默认不把坐标轴刻度文字算进布局。如果你直接写 left: 20,而 Y 轴文字实际需要 60px,那多出来的 40px 就会被画到画布外。
处理:要么把 left 调大,让出足够空间;要么把 containLabel 设为 true,让 ECharts 自动算。两种方式我都试过,结论是 containLabel: true 更省心,但要注意它会让真正绘图区变窄,数据图形整体右移。如果你需要非常精确地控制数据图形起始位置,那就关掉 containLabel、手动把 left 设置成足够大的固定值。
5.2 rem 适配下 ECharts 位置纹丝不动的真相
热搜词里有一条是“pxtorem 对 echarts 没起到效果 vue3”,这个问题我太熟了。
postcss-pxtorem 这类工具只会去转换 CSS 文件里的 px 单位,而 ECharts 的配置项是 JS 对象里的数字,是不经过 postcss 处理的。所以你在 CSS 里写 font-size: 16px,会转成 rem;但在 ECharts 配置里写 grid.left: 16,那 16 就是实打实的 16px,永远不跟着 rem 走。
如果页面整体用了 rem 适配,屏幕一变宽,ECharts 图表区域就会显得“跟不上节奏”。解决思路有三种:
- 布局时优先用百分比字符串,比如 grid.left: '10%',这样 ECharts 内部按容器尺寸计算,天然响应式。
- 根据 rem 基准换算成像素后再设置到 option 里,然后监听窗口变化重新 setOption。
- 计算当前 rem 对应的像素值,作为 grid 左右留白的基数。
我个人最推荐第一种,因为代码最干净,也不需要引入额外的监听逻辑。大屏项目里几乎所有 grid 值我都用百分比,只有少数需要严格对齐的场景才用 px。
5.3 tooltip 溢出与换行
tooltip 虽然不直接归属 grid,但它和“图表位置”强相关,尤其是大屏边缘区域的图表。
默认 tooltip 跟随鼠标位置,但如果图表太靠右,tooltip 提示框可能超出画布右边界,出现一半看不见的情况。处理办法是在 tooltip 里指定 position:
tooltip: { position: function (point, params, dom, rect, size) { // point 是鼠标位置,size 是容器尺寸,dom 是提示框节点 if (point[0] > size.viewSize[0] - 200) { return [point[0] - 220, point[1] + 20]; } return [point[0] + 20, point[1] + 20]; } }这段代码的意思是:如果鼠标太靠近右侧,提示框就出现在鼠标左边,否则出现在右边。tooltip 内容需要换行时,直接在 formatter 回调里拼<br/>就行,或者给 extraCssText 设置 white-space: pre-wrap,然后 formatter 里用 \n 换行。
5.4 我踩过的多 grid 串轴问题
多 grid 布局中,坐标轴经常出现“串台”。症状是上面图表的折线显示到了下面那块区域,或者两个图表的 y 轴刻度混在一起。
这个问题的根源基本是坐标轴索引没配对。我排查时会按这个顺序检查:
- xAxis 数组里的每一项有没有写 gridIndex,写对了没有。
- yAxis 数组里的每一项有没有写 gridIndex。
- series 里有没有写 xAxisIndex、yAxisIndex。
- 如果用的是 dataset 模式,还要检查 dataset 是否与对应 series 绑定。
这里有个小技巧:打开浏览器控制台,用chart.getModel().getComponent('xAxis')查看每个 xAxis 绑定的 grid,能很直观地看出问题出在哪。
还有一个细节,ECharts 里的 grid 和 CSS 里的 display: grid 是完全两码事。我在团队里经常遇到新人把这两个概念搞混,导致一边写 CSS 布局一边疑惑“为什么 ECharts 的 grid 不生效”。简单说,CSS Grid 是页面层面的排版工具,ECharts grid 是图表内部绘图区的定位配置,两者各自为政,不要混在一起调。
最后再分享一个小经验:调 grid 位置不要靠猜,尽量打开浏览器开发者工具,先用第 2 部分提到的 backgroundColor 调试法把 grid 区域可视化,再逐步调整四个边的值。大屏适配时优先用百分比,复杂的多图布局优先用 grid 数组并显式写明所有索引。图表位置这件事本身不复杂,但解决的过程中积累出来的这些细节,才是真正能帮你省时间的东西。