前阵子接手一个小程序项目,源包里统计模块用的还是网页那套思路,把 echarts 的 CDN 直接挂到 web-view 里跑,结果真机一打开就白屏,报错信息全是 xxx is not defined。排查到最后才明白,微信小程序环境里没有 window、没有 document,echarts 在浏览器里正常工作的初始化流程在这里一步都走不通。
这篇就把“微信小程序引用 echarts 做统计图”这件事一次性讲透:从选型(官方 echarts-for-weixin)、组件接入,到折线图、柱状图、饼图、地图的完整配置,再到底层 canvas 层级、包体积、真机渲染这些容易翻车的点。如果你是刚从网页端转小程序,或者第一次在小程序里做数据可视化,不需要再去找零散的文章,直接按这篇的顺序操作就能跑起来。
1. 为什么网页那套在微信小程序里跑不通,解决方法是什么
1.1 小程序环境里没有 DOM,echarts 的初始化逻辑直接失效
先理解 echarts 在网页里是怎么工作的。我们通常写:
const chart = echarts.init(document.getElementById('main'));这背后依赖三样东西:
- DOM:一个真实存在的 HTML 节点
- window:全局对象,用于读取视口尺寸、监听 resize、注册事件
- document:用于计算节点位置、样式
小程序两大架构特点决定了这条路走不通:
- 逻辑层和渲染层分离,逻辑层跑在独立的 JavaScript 引擎里,不是浏览器内核,window 和 document 都不存在
- UI 由自定义组件构成,没有 HTML 节点
所以“把网页版 echarts 引到小程序”这种念头最好尽早放下。就算你真的能用 web-view 把网页包进去,体验也很差:白屏时间长、无法和小程序其他页面通信、纠错能力也弱。
1.2 echarts-for-weixin 的适配原理
针对这个问题,echarts 官方给出了 echarts-for-weixin 方案。它的核心是提供了一个 ec-canvas 小程序自定义组件,内部干了几件事:
- 用小程序的 canvas 组件当渲染层
- 在组件内部模拟了一套 echarts 需要的 DOM 接口
- 把 echarts.init 需要的 width、height、devicePixelRatio 从 canvas 节点里取出来传入
从开发者角度看,你不需要关心这些模拟细节,只需要按它的约定调用即可。
1.3 为什么不用 wx-charts 或者 F2
| 方案 | 图表类型 | 交互丰富度 | 维护方 | 定制成本 |
|---|---|---|---|---|
| echarts-for-weixin | 折线、柱状、饼、散点、地图等 | 高,tooltip/联动/富文本 | echarts 官方 | 配置项一套到底,可复用网页经验 |
| wx-charts | 折线、柱状、饼等基础图 | 低,主要靠点击事件 | 个人/社区 | 轻量,但画复杂图形费劲 |
| F2 | 移动端常见图表 | 中等,专为移动端优化 | 蚂蚁 | API 和 echarts 不通用,学习成本高 |
结论:既然需求明确是统计图,而且你对 echarts 的熟悉度会从网页端延伸过来,选 echarts-for-weixin 是最省力的。
2. 把 echarts-for-weixin 装进项目:组件目录与页面接入
2.1 获取组件包,别把整个仓库塞进来
从 GitHub 的 echarts-for-weixin 仓库下载后,只需要拷贝 ec-canvas 这一个目录到你的项目里。推荐放在 components/ec-canvas 下。
目录结构大概是:
components/ └── ec-canvas/ ├── ec-canvas.js ├── ec-canvas.json ├── ec-canvas.wxml ├── ec-canvas.wxss └── echarts.js # 这是打包好的 echarts 库这里坑很多。一是 echarts.js 体积不小,默认是全量包,后面我会专门讲怎么瘦身;二是不同版本的 echarts-for-weixin 对 echarts 版本支持不同,不要拿新版 echarts.min.js 直接覆盖旧目录,容易跑出一些奇怪的兼容问题。
2.2 页面接入的三步配置
第一步,页面 JSON 注册组件:
{ "usingComponents": { "ec-canvas": "/components/ec-canvas/ec-canvas" } }第二步,WXML 放置容器:
<view class="chart-wrap"> <ec-canvas id="lineChart" canvas-id="lineChart" ec="{{ ec }}"></ec-canvas> </view>第三步,页面的 JS 里定义 ec 对象:
import * as echarts from '../../components/ec-canvas/echarts'; function initChart(canvas, width, height, dpr) { const chart = echarts.init(canvas, null, { width: width, height: height, devicePixelRatio: dpr }); canvas.setChart(chart); chart.setOption({ // 这里放图表配置 }); return chart; } Page({ data: { ec: { onInit: initChart } }, onReady() { // 页面就绪后可以在这里做后续操作 } });2.3 几个必须注意的初始化时机
很多第一次用的人会习惯在 onLoad 里直接写echarts.init,然后发现图表死活不渲染。原因在于 ec-canvas 是自定义组件,它的画布节点要等组件初始化完成才存在。官方的一整套回调机制就是为此设计的,不要在 onLoad 里抢跑。
另外一个常见问题是容器高度。chart-wrap 如果没有明确高度,canvas 的宽高会算成 0,图表自然就不出来。你可以在 WXSS 里写定值,比如height: 400rpx;,也可以用 flex 布局撑开。总之高度不能依赖内容撑开,因为 canvas 内部绘图是绝对定位的。
注意:一个页面如果有多个 ec-canvas 实例,canvas-id 必须各自唯一,否则后初始化的图表会覆盖前面的。
3. 第一张统计图:折线图从数据到渲染
3.1 维护折线图的最小 setOption
继续用上文的 initChart,把 setOption 换成折线图配置:
chart.setOption({ grid: { left: 36, right: 16, top: 24, bottom: 28, containLabel: true }, xAxis: { type: 'category', data: ['3月1日', '3月2日', '3月3日', '3月4日', '3月5日'] }, yAxis: { type: 'value' }, series: [{ name: '营收', type: 'line', smooth: true, data: [120, 200, 150, 80, 270] }] });这里我特别花了篇幅写 grid。小程序屏幕窄,默认的 grid 四周留白偏大,图表区域会被压缩。你把 left/right/top/bottom 收紧后,可视区域会明显变大。containLabel 是防止 y 轴文字溢出到图表外。
3.2 x 轴刻度避碰:rotate 与 formatter
数据一多,x 轴 label 重叠是我们最常碰到的问题。热搜词里“echarts折线图x轴刻度”说的就是这件事。常见的处理方式就两种:
一是旋转:
xAxis: { axisLabel: { interval: 0, rotate: 30, fontSize: 10, color: '#666' } }interval: 0 表示强制每个刻度都显示,否则 echarts 会自动抽稀。
二是格式化截断:
axisLabel: { formatter(value) { return value.length > 4 ? value.slice(0, 4) + '...' : value; } }两种思路可以叠加。如果你的刻度是时间序列,还可以考虑把 x 轴换成 time 类型,用 axisLabel 的 formatter 控制显示粒度。
3.3 请求接口数据之后再绘图
真实项目里数据肯定不是写死的。比如你要展示最近 7 天统计数据,推荐做法是在 onLoad 里请求接口,拿到数据后再初始化图表,而不是在拿不到数据时先画一个空图。
使用 lazyLoad 模式更合适。先在 data 里声明:
data: { ec: { lazyLoad: true } }然后请求成功后:
async fetchData() { const res = await request({ url: 'xxx', method: 'GET' }); const list = res.data.list.map(item => item.value); this.selectComponent('#lineChart').init((canvas, width, height, dpr) => { const chart = echarts.init(canvas, null, { width, height, devicePixelRatio: dpr }); canvas.setChart(chart); chart.setOption({ xAxis: { type: 'category', data: res.data.dates }, series: [{ type: 'line', data: list }] }); return chart; }); }注意 wx.request 默认不返回 Promise,平时我会自己封装一层 Promise,或者直接在回调里写。用这种模式的要点是:lazyLoad: true 会让 onInit 不执行,等组件 ready 后你再主动调 init。这样避免了一次空白渲染,也方便 loading 转圈占位。
3.4 smooth 与 areaStyle 的视觉细节
折线图的 smooth: true 会把线变成平滑曲线,但在数据点很少的时候,平滑曲线反而会产生比较夸张的“甩尾”,看起来不严谨。我一般会先看数据分布再决定要不要开平滑。如果需要面积渐变,可以给 series 加:
areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: 'rgba(47, 140, 240, 0.3)' }, { offset: 1, color: 'rgba(47, 140, 240, 0.02)' } ]) }渐变总面积图在真机上性能稍差,如果一页有多个折线图,我建议先不开,等主流程跑通再追加。
4. 柱状图:渐变、横向条、标签显示这些需求一次讲完
4.1 基础柱状图与间距设置
柱状图在统计场景里出现频率最高。基础配置只比折线图多一步,series.type 换成 bar:
series: [{ type: 'bar', barWidth: 16, itemStyle: { borderRadius: [4, 4, 0, 0], color: '#2f8cf0' } }]barWidth 建议显式指定,否则在小屏上柱条会被中间空白拉得很窄。borderRadius 让柱顶圆角化,视觉上柔和一点。
4.2 柱状图设置渐变色的正确姿势
很多人在网页版 echarts 里用 LinearGradient 都顺手,到了小程序里发现同样写法报错。原因是没有正确导入 image 或 graphic 命名空间。在小程序版里,只要保证 import 的是完整 echarts 对象,就可以直接这样写:
import * as echarts from '../../components/ec-canvas/echarts'; color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: 'rgba(46, 132, 255, 0.9)' }, { offset: 1, color: 'rgba(46, 132, 255, 0.2)' } ])注意 LinearGradient 构造参数表示渐变方向:0,0,0,1 是从上到下,0,0,1,0 是从左到右。颜色 stop 的 offset 是从 0 到 1。
4.3 横向柱状图与“横向进度条”的实现
业务上看业绩完成率、看设备使用率时,横向柱状图比纵向柱状图更直观,很多产品管它叫“进度条式统计图”。实现方法很简单:交换 xAxis 和 yAxis 的 type。
xAxis: { type: 'value', max: 100 }, yAxis: { type: 'category', data: ['设备A', '设备B', '设备C'] }, series: [{ type: 'bar', label: { show: true, position: 'right', formatter: '{c}%' }, data: [75, 92, 64] }]这里 max 显式设为 100,配合 formatter 显示百分比,就得到了一个非常干净的完成率条形图。
4.4 让标签显示在每个柱子上面
纵向柱状图的经典需求是让数值标签出现在每个柱顶。正常配置是:
label: { show: true, position: 'top', color: '#333', fontSize: 12 }position 可取值有 top、insideTop、insideBottom 等。当柱子高度不一、数值差距大时,建议用 insideTop 避免大柱子的标签溢出画布,小柱子的标签则会自动贴近柱顶,不易造成视觉混乱。
4.5 双柱对比图的小技巧
两个系列并排对比时,需要让它们同用一套 category 数据,并且开启 barGap:
series: [{ name: '本月', type: 'bar', data: [120, 200, 150] }, { name: '上月', type: 'bar', data: [90, 180, 120] }]默认两根柱子会自动并排。如果你觉得柱子间距不合适,可以用 barGap: 0.3 调整。颜色上建议一深一浅,并配套 legend,否则真机上容易分不清两个系列。
5. 饼图与环形图:百分比、中间文字、图例细节
5.1 饼图基础配置
饼图的数据结构比较简单:
series: [{ type: 'pie', radius: '60%', data: [ { name: '已完成', value: 68 }, { name: '未完成', value: 32 } ], label: { formatter: '{b}: {d}%' } }]{d} 表示百分比,{b} 表示 name。在移动端,饼图比例小,默认 label 会拥挤,可以把 label 的 fontSize 调小一点,或者只在选中时显示。
5.2 环形图与“饼图中间的字”
环形图就是把 radius 改成区间:radius: ['40%', '70%'],外半径 70%,内半径 40%。
中间那行字是另一个很常见的需求,比如“完成率 86%”。它的实现不是饼图配置,而是用 title 组件:
title: { text: '86%', subtext: '完成率', left: 'center', top: 'center' }关键是让 title 的位置对准圆心。left: 'center',top: 'center',直接居中。echarts 会把标题按 canvas 中心对齐。
如果你想在中间放多行文字,或者更自由的样式,可以用 graphic 元素手绘,但那属于进阶玩法,新手没必要一上来就用。
5.3 饼图图例的小程序困局
网页版饼图可以有很多个 legend 换行排列,但小程序屏幕宽度就那么大,legend 数据多了会排成一个拥挤的方块。我的建议是:
- 图例项 3 个以内:用 legend,放在底部
- 图例项 3 个以上:关掉 legend,直接用 label 把名称和百分比标在图上
另外 legend 的 icon 尺寸可以调小:
legend: { icon: 'circle', itemWidth: 8, itemHeight: 8, textStyle: { fontSize: 11 } }这样在真机上不至于挤成一团。
6. 中国地图与天地图:地图类统计图的接入思路
6.1 echarts 地图数据需要手动注册
如果你要做中国地图统计图,需要注意 echarts 4 之后官方不再内置地图 GeoJSON 数据,需要自己准备 China 地图数据,然后调用:
echarts.registerMap('china', chinaJson); chart.setOption({ tooltip: { trigger: 'item' }, visualMap: { min: 0, max: 100, left: 10, bottom: 10, text: ['高', '低'] }, series: [{ type: 'map', map: 'china', data: [ { name: '广东', value: 88 }, { name: '浙江', value: 72 } ] }] });registerMap 的时机要放在 setOption 之前,而且建议放在 initChart 函数内部,避免全局注册污染。
6.2 地图数据体积与加载策略
完整的中国地图 GeoJSON 压缩后也有几百 KB。微信小程序的主包/分包体积限制很死,所以一般把地图 JSON 放到分包里,或者等页面真正打开时再通过 wx.request 拉取远程数据,拉回来再 registerMap。
这里还有一个常用技巧:如果你的地图只需要展示省份颜色,可以在获取到数据后做一次降级处理,比如按需求裁剪掉非业务省份的坐标。裁剪精度不高没关系,省下的体积很可观。
6.3 天地图底图与 echarts 图层的结合
有些项目要求用天地图当底图,再在底图上绘制 echarts 统计散点、迁徙线。直接说结论:微信小程序里天地图更多走的是原生 map 组件,而 echarts 的 canvas 要浮在地图上方,需要让两者的坐标系对齐。
比较稳的方案是:
- 底层用小程序 map 组件加载天地图图源
- 上层用覆盖物或者同层渲染的 canvas 图层叠加 echarts
- 通过地图可视区域变化事件,重新计算 echarts 里点的坐标位置
这个方案复杂度比较高,涉及经纬度换算、视口偏移、地图手势联动。如果业务上只是要“一张带省份颜色的统计图”,用前面的 GeoJSON 着色方案就足够,不必为天地图徒增工作量。
6.4 富文本提示框与引导线案例的参考
如果你偏向做地理坐标图、客流分析这类场景,可以参考 echarts 社区里的“地理坐标图视觉引导线及富文本提示框”案例。它的核心是 markLine 配合 label 的 rich 字段,比如在标记线上方显示箭头和文字。小程序的 echarts-for-weixin 对这块支持相对完整,但字体渲染会比网页版精简,视觉上要降低预期。
7. 那些不查文档根本找不到的坑
7.1 canvas 层级遮挡与列表滚动
小程序 canvas 是原生组件,早期版本会天然盖在普通 view 之上。如果你在页面里放了一个 position: fixed 的悬浮按钮,结果按钮被图表盖住,多半就是这个原因。随着基础库同层渲染的推进,现在大部分机型上 canvas 已经能和普通组件共存,但部分低版本微信或者 iOS 老机型上,遮挡问题依然会出现。
如果统计图所在页面还要做列表滚动加载更多,比如上方是图表、下方是数据列表,滚动过程中 canvas 会周期性重绘,低端机容易卡顿。我的做法是把图表的 setOption 在滚动停止后再触发,或者在 onPageScroll 里做节流。
稳妥做法:
- 页面上需要浮在图表上方的交互元素,优先用 cover-view
- 图表区域内部不要叠加普通 view 做 toast 或气泡提示
- 如果真的必须叠加,把基础库版本提升到支持同层渲染的 2.4.0 以上,并保证真机验证
7.2 图表不渲染的排查清单
我自己遇到最多的“图表不出现”场景,按概率排是这样:
- canvas-id 重复。一个页面有多个 ec-canvas,canvas-id 必须各自唯一,否则后面初始化的会覆盖前面的
- 初始化时机不对。在 onLoad 里 init,组件未 ready,canvas 节点还没有宽高
- 容器高度为 0。chart-wrap 没有设置高度,canvas 是绝对定位,父容器高度无法被撑开
- 数据为空。series.data 传了空数组,图表会显示空白,不是报错
排查顺序我建议先看 wxml 容器有没有高,再看 console 有没有报错,最后再检查数据。
7.3 tooltip 在真机上偏移或不显示
网页上 tooltip 用得好好的,小程序真机上可能出现提示框跑到画布外面、或者不跟随手指的情况。解决办法优先这两个:
tooltip: { trigger: 'axis' // 或 'item' }以及:
tooltip: { confine: true // 把提示框限制在画布内,避免溢出 }如果你用了自定义 formatter 返回 HTML 片段,小程序里大概率渲染不出效果。小程序 canvas 的 tooltip 内容是走 echarts 内部的绘图逻辑,不支持真正意义上的 HTML,复杂的富文本要用代码片段而不是 HTML 标签。
7.4 包体积超限与 echarts 按需构建
很多人在 uniapp 或原生小程序里引入 echarts 后,真机预览直接报错,像source size 2612kb exceed max limit 2mb这种。2600 多 KB 说白了就是全量 echarts.js 加其他依赖超了。
处理思路分三层:
- 第一层:把统计页面放进分包。分包只在进入对应页面时才加载,主包体积就不会被 echarts 撑爆
- 第二层:用 echarts 官方提供的按需构建,勾选你实际用到的图表类型,下载定制包替换 ec-canvas/echarts.js
- 第三层:如果是 uniapp 项目,检查 vendor 里是否有多份 echarts 重复打包,必要时配置 manualChunks 拆包
提示:如果只做基础统计图,定制构建时尽量别把地图、富文本这些模块一起打进去,体积差非常多。
我个人的顺序是:先用全量包把功能调通,上线前再按需构建。只保留折线、柱状、饼图时,构建出来的 echarts.js 体积能从 800+ KB 降到 400 KB 上下,效果非常明显。
7.5 开发中的小程序怎么发给别人试用
最后说一个和统计图无关、但每个做小程序的人都会遇到的事:微信开发者工具里想把手头版本发给同事、朋友试用收集反馈,直接点工具栏的“预览”,会生成一个二维码,扫码后就是体验版。需要注意两点:
- 体验版要配置 request 合法域名,否则接口请求会被拦截,图表数据加载不出来
- 预览二维码是动态的,隔一段时间会失效,需要再生成,适合收集“几天内试用反馈”这种短期场景
更长期的做法是上传源码后在后台设为体验版,把体验版二维码固定下来,让多人持续试用。
最后,关于 echarts 社区那些零散案例,我的使用习惯是:先把业务里需要的图表类型定死,遇到具体配置项记不清时,去社区搜对应关键词,比如“echarts 柱状图设置渐变色”“echarts pie 中间的字”“折线图 x 轴刻度”,大部分都能直接找到可复用的 setOption。真正能让你省时间的,是搞清这些配置在小程序里哪些可用、哪些要绕路。