
很多后台项目做到一半产品会突然递过来一句话“这部分数据做成图表吧。”我第一次接到这种需求时第一反应是找个现成的图表库然后就一头扎进文档里翻。折腾过几个方案之后我形成了一个习惯做折线图、柱状图、饼图这类常规可视化我会优先用 ECharts而且第一张图九成是从折线图开始的。倒不是因为它有什么魔法而是它把“配置化”这件事贯彻得很彻底——你不需要手动画坐标轴也不用自己算刻度只要把数据和关键配置喂进去一张能交互、能缩放的图就出来了。这篇文章我不打算讲虚的就站在实际项目的角度把 ECharts 入门的完整路径拆开从基础概念、怎么加载到折线图的核心配置再到动态更新和项目里常见的坑。适合刚接触数据可视化的前端开发者也适合偶尔要在后台里画图、又不想被图表代码折磨的人。1. 为什么到现在我仍然推荐用 ECharts 画折线图1.1 折线图看着简单真正画好却不容易先说个反直觉的事折线图是入门图表里最常用的但它远没有“把点连成线”那么简单。一次完整的图表渲染至少要用到坐标系计算、刻度划分、像素到数据值的映射、坐标轴文案格式化、提示框定位、图例开关还要处理数据缺失和极值。这些事情如果从零开始手写工作量一次比一次大而且每次需求不一样代码很难复用。我见过不少同事一开始自己用原生 Canvas 画图前两个点、三根线画得很开心等产品经理提出“鼠标放上去要显示具体数值”“横轴日期太长要隔几个才显示”“数据请求失败的时候要显示占位图”这些需求时整个人就麻了。因为这些功能不是简单加几行代码的事而是要把整个绘图架构重做一遍。ECharts 存在的价值就是把这堆通用能力都内置好了你只需要关心业务数据长什么样、希望图表呈现什么效果剩下的坐标轴生成、事件绑定、动画、缩放交给它处理。1.2 配置化带来的是“思路统一”接触过别的图表库再回来看 ECharts会发现它的核心思想非常一致一切皆 option。option 是一个普通的 JavaScript 对象里面用 xAxis、yAxis、series、tooltip、legend 这些顶层字段描述图表。你学会一个图表类型再学第二个、第三个就会非常快因为坐标轴、图例、提示框这套骨架是通用的变化的只是 series 里的 type 和 data 形状。举个例子画折线图时 series 里写type: line画柱状图时把同一个字段改成type: bar其他大部分配置可以直接复用。这种统一性对有多个图表要维护的后台项目特别友好因为团队里任何一个人接手都能顺着 option 结构快速定位“这张图配置在哪里”。我带过几个新人培训成本比想象中低很多他们不需要理解底层绘图原理只需要掌握一套配置规则。下面这张表是我自己常用的选型对比可以给还在纠结“要不要用 ECharts”的人一个参考方案上手成本交互动效维护成本典型场景原生 Canvas/SVG 手绘很高要做的事太杂全部自己实现高需求一变就重构定制化极强的私人项目轻量级迷你图表库低但功能有限弱中固定格式的简单图表ECharts中低配置项多但有文档强内置丰富交互低改配置即可后台、大屏、数据报表1.3 加载方式按场景选别盲目照抄很多人第一次接触 ECharts 是直接从网上复制 CDN 标签这对本地 Demo 没问题但放到真实项目里要分情况。如果你的项目还是传统的多页面没有用前端框架直接在 HTML 里用 script 标签引入最省事script srchttps://unpkg.com/echarts5.4.0/dist/echarts.min.js/script引入之后浏览器会注册一个全局变量echarts后面所有代码都能直接echarts.init(...)。如果你的项目用的是 React、Vue 这类工程化方案更推荐通过 npm 安装npm install echarts在需要使用的组件或模块里按需引入import * as echarts from echarts;这里要注意一点从 npm 引包时最好只引入自己用到的图表类型和组件而不是全部塞进来。ECharts 本身就支持按需引入只是很多教程为了图省事会直接import * as echarts from echarts这样打包体积会明显变大。项目里图表类型比较少时可以改成“核心模块 折线图包”的写法import * as echarts from echarts/core; import { LineChart } from echarts/charts; import { GridComponent, TooltipComponent, LegendComponent } from echarts/components; import { CanvasRenderer } from echarts/renderers; echarts.use([LineChart, GridComponent, TooltipComponent, LegendComponent, CanvasRenderer]);这套写法的收益是构建产物小一些缺点是多了一行use注册。我个人的建议是项目里图表类型一旦超过三个别纠结按需引入了直接全量引代码可读性优先。2. 从零到一先让第一张折线图跑起来2.1 容器必须有一个确定的高度第一次用 ECharts 的人十有八九会遇到“初始化了、配置了、页面却什么都没有”的问题。用户第一反应是代码哪里写错了实际上问题往往出在容器上ECharts 初始化时会读取容器当前的宽高如果容器高度是 0那么图表画布的尺寸就是 0自然什么都看不见。正确的容器写法很简单div idchart stylewidth: 100%; height: 400px;/div宽度用 100% 通常没问题父级多宽它就多宽。但高度千万不要只写height: 100%因为父级如果没有明确高度这个百分比会一直向上找基准最后落到一个无效值上。最稳妥的做法是给一个具体的像素高度比如 400px或者用min-height保证最小高度。如果你在真实项目里遇到容器高度为 0 的情况可以先在浏览器开发者工具里看元素的计算样式再适当调整布局。这个问题属于“图表库自身没问题是 CSS 姿势不对”的典型。2.2 最小可运行的 option 长什么样有了容器下一步就是初始化实例并写入配置。完整的入门代码大概是这样的!DOCTYPE html html langzh-CN head meta charsetUTF-8 / title第一个 ECharts 折线图/title script srchttps://unpkg.com/echarts5.4.0/dist/echarts.min.js/script style #chart { width: 100%; height: 400px; } /style /head body div idchart/div script var chartDom document.getElementById(chart); var myChart echarts.init(chartDom); var option { xAxis: { type: category, data: [周一, 周二, 周三, 周四, 周五] }, yAxis: { type: value }, series: [ { type: line, name: 访问量, data: [820, 932, 901, 934, 1290] } ] }; myChart.setOption(option); /script /body /html这几行代码里其实已经把 ECharts 的核心骨架全包括了xAxis 声明了一个类目轴data 是横轴上的刻度名称。yAxis 声明了一个数值轴类型是 value它不需要你手动指定范围ECharts 会根据数据自己算。series 是图表的“数据系列”type 指定 linedata 是一组和 xAxis 数据一一对应的数值。只要 xAxis 的 data 长度和 series 的 data 长度一致图表就会把数据按顺序画到对应的横坐标上。2.3 init 与 setOption 的职责要分清很多刚上手的人把init和setOption当成一对“每次刷新都一起调用”的组合这是需要纠正的。init的作用是在某个 DOM 节点上创建一个图表实例它本质上是“初始化画布和坐标系环境”。一个容器只需要初始化一次重复初始化会创建多个实例内存会浪费旧实例还可能残留事件监听。setOption的作用则是把配置数据交给这个实例去渲染和更新。它的粒度很细可以只更新某几个字段。比如后端数据变了你只需要重新调用setOption并传入新的 data不需要从头再来。如果你担心页面里某个容器可能已经初始化过可以用echarts.getInstanceByDom(dom)检查一下var myChart echarts.getInstanceByDom(document.getElementById(chart)); if (!myChart) { myChart echarts.init(document.getElementById(chart)); }这个检查在 Tab 切换、弹窗反复打开这类场景里非常实用能避免重复建实例的坑。3. 折线图的核心配置从坐标轴到数据点3.1 数据是怎么样一步步“变成图”的ECharts 的数据模型并不复杂核心就一句话series 提供“画什么”xAxis 和 yAxis 提供“画在哪”。折线图的每个数据点本质上就是二维平面上的一个坐标点横坐标来自 xAxis 的数据项纵坐标来自 series.data 里的数值。这里有一个新手容易忽略的点如果 xAxis 的type是category并且提供了data: [周一, 周二, 周三, 周四, 周五]那么 series.data 里的第一个值就画在“周一”上方第二个值画在“周二”上方位置完全靠数组下标对应。这个顺序一旦错了图表就会很隐晦地“错位”——数值没丢、形状没坏但对应关系完全不对。如果横轴是时间值比如要画最近 30 天的趋势我建议把 xAxis 的 type 改成time这样 ECharts 会帮你处理时间刻度的格式化和间隔不用自己把时间戳挨个转成字符串。多条折线也很常见series 数组里多放一个对象就行series: [ { type: line, name: 访问量, data: [820, 932, 901, 934, 1290] }, { type: line, name: 订单量, data: [120, 200, 150, 210, 260] } ]如果项目里图表数据来源很杂字段层级深可以考虑用dataset把数据和配置解耦。dataset 的思路是先声明一份数据再通过 series 的encode指定哪一列是横轴、哪一列是纵轴。对小项目来说不是必须的但数据量大、列很多时会清爽不少。3.2 坐标轴细节默认值只适合第一版ECharts 默认不写 xAxis、yAxis 也能画出图它会自动推断类型但真实项目里几乎都要微调。我最常调的几项可以先列出来配置项作用我常用的值xAxis.boundaryGap类目轴首尾是否留白false折线图贴近两侧xAxis.axisLabel.rotate文案旋转角度横轴标签多时用 30 或 45yAxis.min / maxY 轴范围根据业务决定不一定用默认值yAxis.splitNumber分割段数5控制网格线密度yAxis.name坐标轴名称“单位件”这类提示axisLabel.formatter标签格式化把整数显示成“1.2k”之类另外Y 轴是不是一定从 0 开始是个很值得聊的问题。默认情况下 ECharts 的 Y 轴最小值是 0这符合大多数人的直觉。但如果你的数据集中在 80 到 100 之间从 0 开始画会让折线看起来“平得没什么波动”这时可以这样设置yAxis: { type: value, min: dataMin, max: dataMax }这样一来图表会聚焦在数据的真实波动区间。不过我要特别提醒一句这种截断 Y 轴的做法会放大视觉效果产品同学看到会很兴奋但如果你做的图表面向外部用户截断 Y 轴也可能传递误导信息。我的原则是内部管理后台可以用对外公开报表要谨慎。3.3 tooltip、legend 与空状态用户看得懂才算数折线图画出来以后第一件事不是调颜色而是把提示框和空状态配好。tooltip 用来在鼠标悬停时显示数据详情建议把trigger设成axis这样鼠标在横轴上任何一个位置都会把当前刻度下的所有系列都列出来tooltip: { trigger: axis, formatter: function (params) { var p params[0]; return p.name br/ p.marker p.value 次; } }formatter 可以返回 HTML 字符串marker 会自动生成一个小色块和对应系列颜色保持一致。这里也可以返回更复杂的结构比如把多个系列的值拼成多行后台报表经常这么用。legend 是右上角的图例默认显示所有系列的名称点击图例可以隐藏或显示对应折线。不想显示时设置legend: { show: false }即可。如果系列名称是从后端接口动态返回的记得保证 name 字段一定存在否则图例上会出现一片空白。还有一个容易忽略的是空数据。接口还没返回、或者接口返回空数组时ECharts 画出来的就是一块空白画布用户会以为网页出 bug 了。通常我的做法是在拿到数据后先判断长度为空就展示一张占位图或者利用 title 字段提示“暂无数据”if (!data || data.length 0) { myChart.setOption({ title: { text: 暂无数据, left: center, top: middle, textStyle: { fontSize: 14, color: #666 } }, series: [] }); }这个细节花不到一分钟用户体验上的收益却很大。3.4 让折线“有性格”平滑、渐变与数据点基础折线图是直来直去的折线但很多时候我们希望曲线更平滑或者让整个折线和横轴之间的面积带上渐变视觉上更有“数据感”。平滑效果用smooth: true就能做到series: [ { type: line, smooth: true, data: [820, 932, 901, 934, 1290], lineStyle: { width: 2, color: #3b82f6 } } ]这里要提醒一下smooth 是用贝塞尔曲线做插值它不会改变数据点的真实位置只是让折线看起来圆润。所以它适合表现趋势不适合做精确读值的场景因为它会引入不存在于原始数据里的“弧度”。区域面积图是后台项目里很常见的美化需求。给 series 加上 areaStyle就能把折线下方填充出一个半透明的面areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: rgba(59, 130, 246, 0.3) }, { offset: 1, color: rgba(59, 130, 246, 0) } ]) }这个渐变的含义是从上到下由浅蓝慢慢透明视觉上会有一种“数据从上方流下来”的层次感比纯色填充耐看。数据点样式用 symbol 控制。默认每个点都会画出来数据点少的时候没什么问题数据点一旦上百个密密麻麻会影响阅读。这时可以只让鼠标悬停时才显示符号showSymbol: false4. 动态更新、缩放与图表生命周期管理4.1 dataZoom长序列折线图的可读性救星如果你要展示的时间范围很长比如一整年的流水数据折线图全部挤在一张图里节点会重叠趋势反而不清楚。ECharts 提供了 dataZoom 组件专门解决“数据多、画布有限”的问题。常见的配置是同时加一个内置的滚轮缩放和一个底部的滑块dataZoom: [ { type: inside, xAxisIndex: 0 }, { type: slider, xAxisIndex: 0, height: 20, bottom: 10 } ]inside意味着用户在图表区域内滚动鼠标滚轮就能缩放体验很自然slider则会在底部生成一个可拖动的滑块条用户可以框选要看的时间区间。实际项目里我一般会把 slider 的 start 和 end 设为某个初始比例比如默认显示最近 20% 的数据剩下的等用户自己拖出来。这个交互对大屏、实时监控类页面很实用。4.2 setOption 的增量更新实时刷新不用重建后端接口每 5 秒推送一次新数据或者前端定时轮询这种“数据在跑”的场景会让很多人误以为每次都要init一次。其实 ECharts 的setOption天生就是增量合并的。比如我维护两个数组新的数据到了以后直接追加再setOption更新它们var xData []; var yData []; setInterval(function () { var now new Date(); xData.push(now.getHours() : now.getMinutes()); yData.push(Math.round(Math.random() * 100)); if (xData.length 30) { xData.shift(); yData.shift(); } myChart.setOption({ xAxis: { data: xData }, series: [{ data: yData }] }); }, 1000);关键点在于我只需要更新变化的部分其他配置比如 lineStyle、tooltip、dataZoom 都保留在上一次的状态里。这是 ECharts 默认的 merge 行为大多数情况下正是我们要的。如果你想做一次“完全重置”再传一个truemyChart.setOption(newOption, true);第二个参数是 notMerge设为 true 时会把旧配置整个替换掉。这个参数平时少用但切换不同图表类型、切换仪表盘主题时会用到。4.3 resize 与 dispose别让图表变成内存黑洞图表在窗口尺寸变化时不会自己跟着变。最常见的处理是监听 window 的 resize 事件然后调用实例的 resize 方法window.addEventListener(resize, function () { myChart.resize(); });如果你的项目里有多个图表实例建议只监听一次 resize遍历所有实例统一调用。另外现代前端框架里可以用 ResizeObserver 监听容器自身尺寸变化比 window 缩放更精确尤其适合侧边栏折叠、弹窗拉伸这类局部变化。当图表所在的组件要被卸载掉或者在单页应用里路由切换时记得手动销毁实例myChart.dispose();dispose 会释放实例占用的内存和事件监听。如果不销毁用户反复切换页面可能会积累几个甚至十几个图表实例页面会越来越卡。这个坑在后台管理系统里特别常见因为 Tab 页往往不会真正关闭。4.4 markPoint 与 markLine把业务重点画在图上折线图不只是展示数据更多时候还要承担“发现问题”的职责。比如我有个监控页要看一周内每天的订单量产品希望把最高点、最低点直接标出来这时候 markPoint 很合适series: [ { type: line, data: [820, 932, 901, 934, 1290], markPoint: { data: [ { type: max, name: 最高值 }, { type: min, name: 最低值 } ] } } ]markLine 则适合画一条平均值线或阈值线。比如设置一个“目标值 1000”所有低于这条线的数据一眼就能看出来markLine: { data: [ { type: average, name: 平均值 }, { yAxis: 1000, name: 目标线 } ] }这些标记不仅能给图表加分还减少了用户反复对照坐标轴猜数据的过程。不过要留意图表上标记不要加太多超过三到四层就会显得杂乱。5. 真实踩坑记录可以直接抄的避坑清单5.1 最沉默的空白 bug容器高度为 0前面提到过容器高度问题但这里值得再展开一次因为它的表现形态太多了。我遇到过两种常见的进阶变体第一种是页面初始化时图表所在的模块处于 display: none 状态比如放在 Tab 的第二个页签里。Tab 默认隐藏图表初始化时容器宽度高度都是 0等用户切到该页签时看到的是一片空白。解决办法是切到该页签后再调用一次chart.resize()或者直接在切换后重新 setOption。第二种是父容器用了 flex 布局子元素在主轴方向分配了高度但画布没有拿到实际高度。Chrome 的开发者工具很好查直接看容器元素有没有高度就知道问题出在哪。我的建议是统一给图表容器一个显式的最小高度比如min-height: 300px。宁可用固定高度守住底线也不要依赖不确定的父级高度。5.2 Y 轴起点的杠杆效应Y 轴从 0 开始还是从 dataMin 开始对折线的视觉冲击力影响巨大。一组 80 到 100 的数据从 0 开始画是一条很平缓的线但把 Y 轴范围改成 70 到 105同样的数据会变成一条波动明显的曲线。这既是美化技巧也是风险源。我一度把所有图表都设置成 dataMin结果有一次产品经理对着图说“今天数据怎么这么抖”仔细一看其实只波动了 3%。后来我的做法是视觉优化可以但一定要保证坐标轴标签清晰最好让用户能看见完整刻度线不能让图表说谎。5.3 null 与 NaN数据清洗是 ECharts 之前的事接口返回的数据不可能永远干净。某一天数据缺失后端可能返回 null或者根本少一个元素。ECharts 对null的处理是默认把这个点位断掉折线会从上一个点直接跳到下一个点。如果你的趋势是连续的希望断点两端连接起来可以加connectNulls: true但如果缺失的时间段太长连接反而会误导用户所以我通常加一个小提示或者保留断点效果。比 null 更危险的是 NaN。ECharts 对数值计算时如果遇到 NaN可能会出现坐标轴范围异常或者整条线画不出来的情况。我的原则是所有图表数据进图之前先过一次数据清洗把非数字替换成 null或者直接用 filter 过滤掉非法项。这一步放在接口层或 utils 函数里统一做不要指望图表自己消化脏数据。5.4 数据点太多时的性能取舍折线图里的数据量一旦过万Canvas 渲染的压力就会显现。这个时候不要急着换方案可以先用 ECharts 自带的降采样能力把showSymbol设为 false避免每个点都画符号。把动画关掉animation: false减少连续 render 的负担。设置sampling: lttb让 ECharts 自动对数据进行降采样保留趋势特征。LTTB 算法的思路是“挑选最能够代表原始趋势的点”它不会盲目均匀抽稀而是优先保留峰值和突变位置。设置之后肉眼几乎看不出差别但点的数量能降一个数量级。另外如果你的页面设备主要是移动端或者场景更看重清晰度和缩放效果渲染器可以换成 SVG因为 SVG 对单个节点的精细控制更好也更容易做无障碍支持。ECharts 默认用 Canvas切换方法是echarts.init(dom, null, { renderer: svg });但说实话普通后台项目的折线图数据量到不了需要频繁性能调优的程度这个知识点更多是防患于未然。最后说一点个人体会。我第一次用 ECharts 的时候也是照着网上的模板把代码复制过来图表出来了但后续需求一变改得相当痛苦。真正让我效率提升的是静下心把 option 的层级结构摸了一遍。折线图只是最小的一环它背后的坐标轴、数据系列、tooltip 这套模型和柱状图、散点图是共用的。把基础逻辑吃透以后再去写其他图表更多是靠思考而不是通过搜索“某某图怎么写”。折线图是很好的入口希望这篇内容能帮你把这一步走稳。