我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

ECharts 3.8.5 老项目实战:配置、避坑与版本共存

ECharts 3.8.5 老项目实战:配置、避坑与版本共存 简介面向前端开发者的 ECharts 3.8.5 数据可视化资源包适合需要快速搭建图表页面、学习配置项与 API 的初中级开发者。包内共 3 个文件以 2 个 JavaScript 库文件和 1 个压缩包为主整体约 15.33MB可用于项目引入、离线查阅与版本对照学习。资源涵盖柱状图、折线图、饼图、地图等常见图表以及悬停高亮、点击事件、缩放、响应式布局、Canvas 高性能渲染和模块化按需加载等能力。配套文档对图表类型、配置项、API 与交互处理做了系统说明便于从实例入手理解数据驱动绘图流程同时含有不同版本库文件可对照了解 3.x 与 4.x 在性能和接口上的差异。已有 461 人学习适合作为前端数据可视化入门与查阅工具。1. 为什么现在还有人装 echart 3.8.5一份老版本发行包的适用边界接手一套跑了七八年的内网报表后台前端是 jQuery 加模板字符串浏览器版本由运维统一锁死第一件事不是重构而是先把图表跑起来。搜 echart 能翻到的现成包大多是 3.x 时代的 dist 文件其中 echart 3.8.5 被问得最多。它属于 3.x 这条线的末段发行版canvas 渲染、normal/emphasis 分层配置、地图注册、扩展目录都在不依赖构建工具一个 script 标签就能落地。适合两类人一类是维护老系统、只能加不能改的另一类是要在离线环境里快速出图的。边界也清楚dataset、SVG 渲染器、按需 tree-shaking 这些都没有想要就得往上换大版本。2. 把 3.8.5 跑起来三种引入方式与一个最小可跑配置老版本的坑一半出在“包选错了”另一半出在“容器没准备好就 init”。这一章先把引入方式和初始化流程固定下来后面所有图表都复用这套骨架。2.1 完整包、common 包、simple 包怎么选3.8.5 的 dist 目录里不止一个 echarts.js官方按体积裁了三档。选之前先确认你实际要用到的图表类型别凭感觉。构建文件内含的图表与组件适合的场景echarts.js / echarts.min.js全部图表类型与全部组件图表类型不确定或要用地图、关系图、桑基图、日历echarts.common.js常用图表与基础组件裁剪过一部分后台报表只有折线、柱状、饼图、散点这几类echarts.simple.js折线、柱状、饼图加最基础坐标系首屏体积敏感、图表类型单一的老门户我一般直接用完整包。原因很实际精简包省下的那点体积在内网环境里几乎感觉不到但一旦某个图表类型没打进去series.type写上去就是空白排查方向还会被带偏到“是不是数据问题”上。如果确实要用 common 包务必在引入后先跑一遍你实际要用的每个type跑不通就换完整包别硬调。2.2 最小可跑容器、init、setOption 三段式下面的例子是后面所有配置的基线HTML 和 JS 放在一起直接存成 .html 双击就能看。!DOCTYPE html html langzh-CN head meta charsetutf-8 !-- 老内核浏览器必须显式声明字符集否则中文标签会乱码 -- title3.8.5 最小示例/title /head body !-- 容器宽高必须由 CSS 给出确定值否则 init 拿到的是 0 -- div idchart stylewidth: 100%; height: 420px;/div script src./dist/echarts.min.js/script script // 1. 先取容器再 init避免脚本在节点挂载前执行 var dom document.getElementById(chart); var chart echarts.init(dom, null, { renderer: canvas, // 3.8.5 只有 canvas 渲染器 devicePixelRatio: window.devicePixelRatio || 1 // 高分屏下不糊 }); // 2. option 是纯数据描述里面不写任何 DOM 操作 var option { title: { text: 日活趋势 }, tooltip: { trigger: axis }, legend: { data: [访问量] }, grid: { left: 48, right: 24, top: 64, bottom: 40, containLabel: true }, xAxis: { type: category, boundaryGap: false, data: [周一, 周二, 周三, 周四, 周五] }, yAxis: { type: value, name: 人次 }, series: [{ name: 访问量, type: line, smooth: true, data: [820, 932, 901, 934, 1290] }] }; // 3. 第二个参数 notMerge这里传 false 表示合并 chart.setOption(option, false); // 4. 页面卸载时释放实例 window.addEventListener(beforeunload, function () { chart.dispose(); }); /script /body /html逻辑说明echarts.init做了三件事——按容器尺寸建画布、按 devicePixelRatio 决定实际像素、把实例挂到该 DOM 上。所以容器尺寸必须在 init 之前就是最终值这一条后面会反复出现。第二个参数是主题名传null就是默认主题自定义主题名要先registerTheme才有意义。参数说明renderer在 3.8.5 里只有canvas一个合法值写它纯粹是为了将来换到 5.x 时不用改代码。devicePixelRatio不传时框架会自己取window.devicePixelRatio但在 iframe 或被缩放过的页面里取到的值可能不对显式传更稳。containLabel: true让 grid 的边距把坐标轴标签也算进去中文轴名很长时不开这个标签直接被裁掉。2.3 resize、dispose 与实例复用容器尺寸变化后图表不会自己跟着变这是 3.8.5 最常见的使用缺口。var dom document.getElementById(chart); var chart echarts.init(dom); function bindResize(el, instance) { if (window.ResizeObserver) { var ro new ResizeObserver(function () { // 回调里只做 resize不要在里面重新 setOption instance.resize(); }); ro.observe(el); } else { // 老内核没有 ResizeObserver退回窗口级监听 window.addEventListener(resize, function () { instance.resize(); }); } } bindResize(dom, chart); // 侧边栏折叠、tab 切换这类非窗口尺寸变化要手动补一刀 function afterLayoutChange() { // 等过渡动画结束、DOM 尺寸稳定后再调立刻调多半拿到旧宽高 setTimeout(function () { chart.resize(); }, 300); }逻辑说明resize()会重新读取容器宽高并重建画布代价比setOption低可以放心高频调用。但在 CSS 过渡动画没结束时调用读到的是中间态的宽度图会缩成一条所以上面用setTimeout兜一下。参数说明resize()支持对象入参{width, height, silent}在容器被display: none隐藏、拿不到尺寸时可以显式传宽高让它先按固定值渲染silent: true表示不触发resize事件回调用于你不想让监听器连锁触发的场景。注意单页应用里切换路由时一定要对已经不用的实例调dispose()。3.8.5 不会自动回收实例挂在 DOM 上DOM 被移除后实例还在内存里路由切十几次标签页就能明显感觉到卡。3. option 的分层写法把柱线混合、饼图、地图三张图配到位3.8.5 的配置结构和 5.x 差别最大的地方是样式必须写在normal和emphasis两层里。这一层不写对改颜色、改高亮全是白费劲。3.1 normal 与 emphasis这套层级不能省3.8.5 里itemStyle、lineStyle、label、labelLine、areaStyle这些样式对象内部都分成两个状态键normal是常态emphasis是鼠标悬停或dispatchAction高亮后的状态。直接写成itemStyle: { color: #3b7dd8 }在部分图表上能蒙对是因为框架内部做了兜底合并但一旦你在emphasis里也写了同名属性兜底顺序就会让人抓狂——典型的“玄学”现场颜色明明写了却不生效。我一般把两个状态成对写哪怕暂时没区别itemStyle: { normal: { color: #3b7dd8, borderColor: #2f66b0, borderWidth: 1 }, emphasis: { color: #2f66b0, borderWidth: 2 } }这样做的另一个好处是迁移成本低4.x 仍然认这套层级写习惯了不会踩错等真要升到 5.x 时把normal这一层拆掉、把里面的属性上提一层就是等价改写机械替换即可不需要重新设计样式。3.2 柱线混合 双 Y 轴 dataZoom 的完整 option双 Y 轴最容易出的问题是网格线打架和坐标轴绑错。下面这份配置可以直接抄改数据和单位就能用。var option { color: [#3b7dd8, #f2a93b], tooltip: { trigger: axis, axisPointer: { type: shadow }, // formatter 每条分支都必须 return 字符串返回 undefined 会渲染出 undefined formatter: function (params) { var head params[0].axisValue; var lines params.map(function (p) { var unit p.seriesIndex 0 ? 单 : %; return p.marker p.seriesName p.value unit; }); return head br/ lines.join(br/); } }, legend: { data: [订单量, 转化率], top: 8 }, grid: { left: 56, right: 56, top: 64, bottom: 72, containLabel: true }, xAxis: { type: category, data: [1月, 2月, 3月, 4月, 5月, 6月], axisLabel: { interval: 0 } // interval:0 强制全部显示默认会隔一个藏一个 }, yAxis: [ { type: value, name: 订单量, min: 0 }, { type: value, name: 转化率, min: 0, max: 10, axisLabel: { formatter: {value} % }, splitLine: { show: false } // 第二根轴关掉网格线否则和主轴叠成双线 } ], dataZoom: [ { type: inside, start: 0, end: 100 }, { type: slider, height: 24, bottom: 16, start: 0, end: 100 } ], series: [ { name: 订单量, type: bar, barMaxWidth: 28, itemStyle: { normal: { color: #3b7dd8, barBorderRadius: [4, 4, 0, 0] }, emphasis: { color: #2f66b0 } }, data: [1200, 1380, 1010, 1560, 1720, 1900] }, { name: 转化率, type: line, yAxisIndex: 1, // 关键绑到第二根 y 轴 smooth: true, symbol: circle, symbolSize: 8, lineStyle: { normal: { width: 3 } }, data: [3.2, 4.1, 2.8, 4.6, 5.1, 5.8] } ] };逻辑说明yAxis是数组时系列通过yAxisIndex指定挂哪根轴不写就是 0。tooltip.trigger: axis下 formatter 收到的是数组顺序按series声明顺序来所以上面用seriesIndex 0判断单位是安全的。参数说明barMaxWidth限制柱子最大宽度数据只有三五条时柱子不会撑成一堵墙。barBorderRadius在 3.8.5 是itemStyle.normal下的合法属性注意拼写5.x 改成了borderRadius迁移时这里会静默失效而不报错。dataZoom的inside类型支持鼠标滚轮缩放slider类型给一条可拖的滑块两个都开时要显式给 slider 留底部空间否则滑块压在 x 轴标签上。3.3 饼图标签与引导线把重叠的 label 拉回来饼图翻车基本集中在标签挤成一团和最小扇区点不中。var pieOption { tooltip: { trigger: item, formatter: {b}br/{c} 单{d}% }, legend: { orient: vertical, right: 16, top: middle, type: scroll }, series: [{ name: 渠道构成, type: pie, radius: [38%, 62%], // 数组形态是环形图单值则是实心饼 center: [40%, 50%], avoidLabelOverlap: true, // 默认 true节点密集时千万别关 minAngle: 3, // 小于 3 度的扇区强制给个可见角度 label: { normal: { show: true, formatter: {b}\n{d}%, lineHeight: 16 }, emphasis: { show: true, textStyle: { fontSize: 14, fontWeight: bold } } }, labelLine: { normal: { length: 12, length2: 16, smooth: false } }, data: [ { value: 435, name: 自然搜索 }, { value: 310, name: 活动页 }, { value: 234, name: 外部投放 }, { value: 155, name: 直接访问 }, { value: 42, name: 其他 } ] }] };逻辑说明avoidLabelOverlap会做一轮位置微调代价是渲染时多一次计算条目特别多几十个扇区时有感知minAngle是给“其他”这类小数值兜底的不设的话 0.3% 的扇区在图上根本点不中。参数说明labelLine.length是从扇区边缘到折点的长度length2是折点到文字的横向长度两个值调小会让标签更贴图表。radius的百分比是相对容器短边计算的所以容器宽高比极端时环形会变形这种场景改用像素值更稳。3.4 地图registerMap 的两步走3.8.5 不内置任何地理数据地图必须自己注册。// 1. 先用 geoJSON 注册名字自己定之后 series.map 用同一个名字 fetch(./geo/region.json) .then(function (r) { return r.json(); }) .then(function (geoJson) { echarts.registerMap(region-custom, geoJson); var mapChart echarts.init(document.getElementById(map)); mapChart.setOption({ tooltip: { trigger: item, // 区域内没有数据时 value 是 NaN直接拼会显示 NaN formatter: function (p) { return p.name (isNaN(p.value) ? 暂无数据 : p.value); } }, visualMap: { type: continuous, min: 0, max: 1000, left: 16, bottom: 16, text: [高, 低], calculable: true, inRange: { color: [#e8f1fb, #3b7dd8] } }, series: [{ type: map, map: region-custom, // 必须与 registerMap 的第一个参数完全一致 roam: true, label: { normal: { show: false } }, itemStyle: { normal: { borderColor: #c9d6e4, borderWidth: 0.6 }, emphasis: { areaColor: #f2a93b, label: { show: true } } }, data: [] }] }); });逻辑说明registerMap只是把地理边界塞进内部注册表不建实例series.map按名字取。注册必须在setOption之前完成异步加载时容易写成“先 setOption 再注册”结果是空白地图加一句控制台警告。参数说明visualMap.inRange.color是渐变的两端值域用min/max卡死不卡的话每次数据变化配色都会漂。roam: true开缩放拖拽低配机器上建议关掉地图的缩放重绘比折线图重得多。4. 避坑3.8.5 上五次翻车记录与排查顺序这些坑我基本都踩过一遍按“先看渲染、再看配置、最后看构建”的顺序排查命中率最高。4.1 渲染与容器类现象图表区域一片空白控制台干干净净没有报错。原因容器在隐藏的 tab 页里clientWidth和clientHeight都是 0init 按 0 尺寸建了画布之后即使显示出来也不会自动重算。解决切到可见之后再 init已经在隐藏态 init 的在 tab 显示回调里补一次chart.resize()。如果容器是用display: none控制的别指望 resize 能救它读到的还是 0必须显式传宽高。现象图表宽度只有一百来像素或者右侧一截被裁掉。原因容器写的是width: 100%但父级没有确定宽度常见于 flex 布局里没写min-width: 0的子项宽度塌陷了另一类是被grid默认边距裁掉了轴标签。解决给父级链路定宽或给容器一个像素级最小宽度grid上开containLabel: true。4.2 option 与交互类现象第二次setOption之后旧的系列还在图例越点越多或者柱状图和折线图叠在了一起。原因3.8.5 的默认行为是合并series按数组下标对齐新数据少一项就只覆盖前几项多出来的原样保留。解决结构变化时用setOption(option, true)整体替换结构稳定的场景则保证每次series的name和顺序完全一致靠合并换数据性能更好。现象tooltip 里冒出一个undefined或者整块提示框不出现。原因formatter的某个分支没写 return函数返回 undefined另一种是trigger为axis时误把params当成对象用实际它是数组。解决formatter 保证所有分支都返回字符串需要换行用br/不要返回 DOM 节点。写的时候先console.log(params)看一眼结构比猜快得多。现象formatter里拼了用户填的文本提示框渲染出错或者内容错位。原因3.8.5 的 tooltip 内容是当 HTML 塞进去的未转义的尖括号会被当标签解析。解决拼字符串前把、、、替换成实体或者干脆只用{a}、{b}、{c}这类占位符别自己拼用户输入。4.3 构建与版本类现象本地一切正常发到内网某台机器上报echarts is not defined或者某个图表类型直接不出现。原因八成是引了 simple 或 common 精简包缺对应的图表模块少数情况是脚本 404 被浏览器缓存掩盖了。解决先console.log(echarts.version)确认版本对不对再换完整包验证。这条血泪经验值一个固定流程——上线前在目标机器上过一遍所有图表页面别只在自己机器上看。提示把echarts.version打在页面角落或者控制台首行运维报障时能直接排除掉一大半“版本不对”的可能。5. 版本边界3.8.5 与 4.x、5.x 的差异清单和共存方案很多项目不是不想升而是升不动——依赖它的页面太多动一处要回归一片。搞清楚差异点才能判断哪些是能绕过的哪些是必须忍的。5.1 被移除与被新增的 API能力3.8.54.x5.xsetOption签名(option, notMerge, lazyUpdate)兼容旧签名推荐对象入参只接受对象入参样式层级必须写normal/emphasis仍兼容normal层已移除dataset组件不支持支持支持SVG 渲染器无仅 canvas有独立构建内置可切换柱条圆角itemStyle.normal.barBorderRadius兼容旧名改名borderRadius自定义系列有custom有有扩展更多这张表里最要命的是第一行和第三行。setOption(option, true)这种写法在 3.8.5 是标准做法升到 5.x 会直接把第二个参数当配置对象解析true被忽略图表变成合并刷新——表现出来就是“升完级数据不刷新了”。dataset的缺失影响更大老项目里数据到 option 的映射是手写的一行行map拼series.data升级时这部分代码理论上可以留着不动但要享受 dataset 就得重写数据层。至于样式层级短期建议统一按 3.8.5 的写法写因为 4.x 全兼容等于给自己留了条后路。等哪天真的只跑 5.x 了做一次机械替换即可。5.2 同一页面共存两个版本老系统里新加一个模块新模块想用新版老模块不能动最省事的办法是让两个版本共存。!-- 老模块先引入 3.8.5它会把 echarts 挂到 window 上 -- script src./vendor/echarts-3.8.5/echarts.min.js/script script // 立刻把引用收进自己的命名空间随后全局名被覆盖也不影响 window.LegacyChart window.echarts; /script !-- 新模块再引入新版同样用全局名但换个命名空间保存 -- script src./vendor/echarts-5/echarts.min.js/script script window.ModernChart window.echarts; /script script // 之后各用各的不要互相传实例 var c1 window.LegacyChart.init(document.getElementById(old)); var c2 window.ModernChart.init(document.getElementById(neo)); /script逻辑说明两个版本都是 UMD 构建加载时都会写window.echarts所以必须在各自加载完成后立刻保存引用顺序不能乱。这样做的代价是页面里有两份完整的图表库首屏体积翻倍只适合过渡期。参数说明如果两个模块的加载时机不可控懒加载、微前端子应用用 iframe 隔离更干净——每个 iframe 里各引一个版本互不干扰代价是跨 iframe 拿数据和调方法要走postMessage。我一般优先选命名空间方案实在冲突了才上 iframe。5.3 锁版本的工程做法老版本最怕的不是功能不够是“某天装完依赖发现版本变了”。{ dependencies: { echarts: 3.8.5 } }版本号前面不要加^或~这是唯一有效的锁法。^3.8.5允许装到 3.x 的最新版而 3.x 内部小版本之间也存在行为差异某些setOption的合并细节在不同小版本上表现不一致。装完之后把 lock 文件一起提交并在 CI 里加一条校验# 版本不符直接让流水线失败避免本地好好的、线上不对这种黑匣子问题 node -e var vrequire(echarts/package.json).version; if(v!3.8.5){console.error(echarts 版本漂移:,v);process.exit(1)}离线部署的场景再加一层回退本地包缺失时不至于白屏script src./vendor/echarts-3.8.5/echarts.min.js/script script // 本地文件缺失时退回备用目录 if (typeof window.echarts undefined) { document.write(script src/static/backup/echarts.min.js\/script); } /script这段写法在模块化工程里看着很原始但内网老项目里它确实能救命——静态资源服务器抽风的时候至少图表还能出来。用document.write是因为它同步执行能保证后续脚本拿到已加载的 echarts异步方案在老代码里容易引出入顺序问题。6. 把 3.8.5 用到生产主题注册、导出与两个收尾技巧老版本要长期用下去得把“每张图单独配色”和“导出图片糊成一片”这两个高频返工点一次性解决掉。前者靠主题后者靠导出参数。// 注册主题键名随意但必须在 init 之前调用 echarts.registerTheme(corp-blue, { color: [#3b7dd8, #f2a93b, #5cb85c, #d9534f, #8e6fd8], backgroundColor: #ffffff, // 不设的话导出是透明底 textStyle: { fontFamily: Microsoft YaHei, sans-serif, fontSize: 12 }, title: { textStyle: { color: #1f2d3d, fontSize: 15 } }, legend: { textStyle: { color: #5a6b80 } }, categoryAxis: { axisLine: { lineStyle: { color: #c9d6e4 } } }, valueAxis: { splitLine: { lineStyle: { color: #eef2f7 } } } }); var chart echarts.init(document.getElementById(chart), corp-blue);主题对象的键名和 option 里的组件名一一对应写错了不会报错只是不生效所以改完一定要肉眼比对一次。配好之后所有图表统一走init(dom, corp-blue)新同事加页面不用再抄色值。导出图片这块用getDataURL而不是截屏var url chart.getDataURL({ type: png, // png 或 jpeg pixelRatio: 2, // 2 倍图贴进文档放大不糊 backgroundColor: #fff // 不传是透明底贴到 Word 里会变黑 });参数作用建议值type输出格式图表用png照片底色多的用jpegpixelRatio输出倍率2报表打印场景给 3backgroundColor背景色#fff透明底在多数文档里显示异常excludeComponents排除组件导出时排除[toolbox, dataZoom]pixelRatio调大有代价2000 个点的散点图给到 3 倍导出耗时能到一两秒界面上得配个 loading 提示否则用户会连点。另一个技巧是导出前先dispatchAction把dataZoom复位到全量不然导出来的只是当前视窗那一截数据——这个坑我在一个报表项目里翻过车客户拿着导出的图问“后面几个月的数据去哪了”。从那以后我每次做导出功能都强制走一遍「复位 dataZoom → 关闭动画 → 生成 URL → 恢复原视窗」这个顺序多写四行代码省掉一轮扯皮。希望帮到你。本文还有配套的精品资源点击获取
返回列表