
简介面向Web前端与.NET开发者的AjaxECharts动态数据可视化示例包解决异步获取JSON数据并实时渲染ECharts图表的常见需求适合正在学习ASP.NET数据展示或需要快速搭建实时监控图表的开发者。压缩包共29个文件核心为aspx页面、cs后台代码及js脚本另有config配置文件与dll程序集属于可直接运行的ASP.NET项目示例配套Web.config等环境配置便于本地调试。已有1193人学习。资源内含“InitialChartByAjax”和“ClickBtnLoadChartByAjax”两种触发方式演示了初始化图表、按钮点击后通过Ajax请求服务器、解析JSON并调用setOption更新图表同时涉及定时刷新、错误处理、数据格式化等实用思路。对于想掌握Ajax异步交互与ECharts整合的开发者是份简洁实用的参考代码。 搞前端这些年ECharts 图表的需求我接过不下二十次。最早都是写死静态数据后来真正上线要对接接口才发现从 Ajax 动态获取数据到图表渲染中间每一步都有讲究。最近一个“销售数据看板”项目又让我踩了几个坑顺手把整条链路梳理一遍Ajax 怎么传参、编码格式怎么处理、后端 JSON 怎么转成 ECharts 能用的数据结构再到饼图、柱状图、雷达图、多 Y 轴、3D 效果、legend 全选全不选这些配置怎么落地最后是几个特别容易让人懵的问题比如[echarts] cant get dom width or height、移动端图表点不动这类希望给正在做类似需求的朋友省点时间。1. 项目整体思路从写死的数据到动态数据流1.1 为什么必须用 Ajax 动态获取数据写 demo 的时候大家习惯在页面里直接放一个数组然后扔给 ECharts 的setOption。真到上线就会发现完全不行数据会变、不同角色看到的范围不同、用户要按时间筛选、点击某个图表要联动刷新另一个图表这些全都依赖后端动态数据。Ajax 的核心价值恰恰在这里不刷新整个页面也能向服务器请求数据并局部更新视图。放在图表场景里这个特性几乎就是量身定做的。用户切换日期、切换产品线时图表重新请求数据的过程不会打断操作体验哪怕数据量再大也只是局部 loading而不是整页白屏。我手头这个销售看板项目就是典型页面顶部有日期范围选择器、区域下拉框中间放了 4 个图表分别是销售趋势柱状图、品类占比饼图、区域销售雷达图和一个带数字占比的科技感环形图。用户一改筛选条件4 个图表要同时刷新。这种需求如果用整页刷新体验会非常差所以必须走 Ajax 动态获取数据这条路。1.2 整体数据流设计与选型思路一套完整的图表数据流大概是这个顺序页面模板渲染完成 → 初始化图表可以先给个空配置或 loading 状态 → 发送 Ajax 请求 → 后端接口查询数据库 → 返回 JSON → 前端解析并转换成 ECharts 配置项 → 调用setOption渲染 → 用户交互触发重新请求。设计这个链路时有三个点必须提前想清楚不然后面返工很痛苦。第一接口返回的数据结构和 ECharts 配置项结构尽量对齐。这不是说后端必须懂 ECharts而是在接口设计阶段就约定好字段名。比如后端返回categories表示 X 轴类目而不是返回data这种模糊名字前端就能少写很多转换逻辑。要是后端返回的数据没办法直接用前端一定要做一层适配不要在图表配置里强行处理复杂的嵌套数据。第二加载状态和错误状态不能省。有请求就要有 loading请求失败要有明确提示。很多新手只处理 success 分支一旦接口超时或者后端报错图表就永远停在空白状态用户根本不知道发生了什么。我会在成功回调里先判断业务状态码再决定是否渲染图表请求失败时用 layer 或前端 UI 组件直接弹提示。第三想清楚请求时机。是页面加载时自动请求还是用户点击查询按钮才请求还是两者都要这个看板项目的做法是页面加载时先请求一次默认时间范围的数据用户点击“查询”时再重新请求。另外如果用户连续快速点击按钮前一个请求还没回来后一个又发出去了这会产生数据错乱。我的做法是给请求加一个时序标记或者干脆用abort()把上一次请求取消掉。2. Ajax 请求参数、编码与数据解析2.1 给 ajax 请求参数动态赋值这个项目的筛选条件有三个开始日期、结束日期、区域。用户选择后点击查询就需要把这几个值动态塞进 Ajax 请求里。最常见的写法是从页面控件读取值然后放到data字段里function loadChartData() { var startDate $(#startDate).val(); var endDate $(#endDate).val(); var region $(#regionSelect).val(); $.ajax({ url: /api/sales/report, method: GET, data: { startDate: startDate, endDate: endDate, region: region }, dataType: json, success: function(res) { if (res.code 0) { renderAllCharts(res.data); } else { showError(res.msg); } }, error: function() { showError(请求失败请稍后重试); } }); }这里有个细节容易被忽略data里的值最好做一层判空。比如用户没选区域region是空字符串如果后端比较严格空字符串会导致 SQL 查询条件异常。前端可以统一处理成region || 或者干脆在后端接口里做默认值兜底。如果项目里用的是 layui那么写法会稍微不同。layui 内置了 jQuery所以可以通过layui.jquery拿到的$来发请求layui.use([jquery, layer], function() { var $ layui.jquery; $.get(/api/sales/report, { startDate: startDate, endDate: endDate, region: region }, function(res) { if (res.code 0) { renderAllCharts(res.data); } else { layui.layer.msg(res.msg); } }, json); });$.get的特点是把参数放在 URL 上适合 GET 请求、参数量小、不涉及敏感信息的场景。参数多或者需要 POST 提交时我还是建议用$.ajax配合method: POST因为 POST 的参数放在请求体里不会因为 URL 过长被浏览器截断。2.2 请求编码格式与乱码问题Ajax 的编码问题主要出在中文参数和中文返回内容上。常见现象是接口请求成功了但后端拿到的中文是乱码或者前端拿到的响应里中文变成了问号。先说请求侧。如果使用$.ajax的默认设置contentType是application/x-www-form-urlencoded; charsetUTF-8这时参数会以表单格式提交中文一般没问题。但如果你改成提交 JSON 数据就要明确指定编码$.ajax({ url: /api/sales/save, method: POST, contentType: application/json;charsetUTF-8, data: JSON.stringify({ regionName: 华东区, remark: 季度汇总 }), dataType: json });很多乱码的根源是前端用JSON.stringify把对象转成字符串但contentType还保持默认的application/x-www-form-urlencoded服务端拿到的 body 就不是标准表单格式再用表单解析器去解析自然就乱码或者解析失败。记住一点提交什么格式的数据contentType就要匹配什么格式。响应侧的编码问题通常要靠服务端解决。比如 Java 接口要设置response.setCharacterEncoding(UTF-8)Node.js 里要确保Content-Type带charsetutf-8。前端能做的是在dataType: json的同时尽量保证页面本身也是 UTF-8 编码不然浏览器解析响应时可能沿用页面编码导致中文乱码。2.3 回调数据处理把后端 JSON 转成图表数据后端返回的数据几乎不可能正好是 ECharts 需要的结构。比如这个看板项目后端接口返回的销售趋势数据长这样{ code: 0, data: { months: [1月, 2月, 3月], series: [ { name: 销售额, type: bar, values: [120, 150, 90] }, { name: 订单量, type: line, values: [320, 480, 260] } ] } }ECharts 需要的 series 结构里每个系列要有name、type、data而这里后端返回的是values字段。我习惯在渲染前做一个专门的数据适配函数把后端的values重命名成data同时补上默认的barWidth、itemStyle这些样式字段function buildSeries(rawSeries) { return rawSeries.map(function(item) { return { name: item.name, type: item.type || bar, data: item.values || [], barMaxWidth: 30, itemStyle: { borderRadius: [4, 4, 0, 0] } }; }); }为什么不直接改后端因为后端接口往往是复用的可能同一个接口同时服务移动端列表和 PC 端图表字段改动会影响其他端。前端做适配层既隔离风险又方便针对图表做样式增强这是更稳妥的做法。另一个常见情况是后端返回的数据需要汇总。比如饼图需要把 A 部门和 B 部门的销售额汇总成占比后端很可能返回明细数据前端就自己做个reduce累加再拼装成{ value: 总销售额, name: 部门名 }的格式。这类逻辑放在前端比要求后端为每个图表单独写一个接口要高效得多。3. ECharts 图表配置从基础图表到炫酷效果3.1 基础图表配置饼图、柱状图、雷达图这个项目的 4 个图表里饼图、柱状图、雷达图是最基础的但配置细节直接影响最终效果。饼图我用的是环形样式通过radius控制内外半径内圈留白可以放中心文字。核心配置如下option { tooltip: { trigger: item, formatter: {b}: {c} ({d}%) }, legend: { orient: vertical, right: 10, top: center }, series: [{ name: 品类占比, type: pie, radius: [45%, 70%], avoidLabelOverlap: true, itemStyle: { borderRadius: 6, borderColor: #fff, borderWidth: 2 }, label: { show: false, position: center }, emphasis: { label: { show: true, fontSize: 18, fontWeight: bold } }, data: pieData }] };这里我关了默认 label只在鼠标悬停时显示中心文字视觉上更干净。avoidLabelOverlap: true是为了防止扇区标签重叠数据多的时候特别有用。柱状图相对简单但要注意 X 轴类目过多时的显示问题数据量大时要开启axisLabel的interval设置或者配合dataZoom组件实现缩放。销售趋势柱状图我做成了圆角柱体视觉上不会太生硬。雷达图展示单点信息是这个项目的另一个需求给某个员工的能力评估打分。雷达图的核心配置是指标和最大值option { radar: { indicator: [ { name: 销售能力, max: 100 }, { name: 技术能力, max: 100 }, { name: 服务意识, max: 100 }, { name: 协作能力, max: 100 } ], radius: 65%, splitArea: { areaStyle: { color: [rgba(34, 34, 34, 0.3), rgba(34, 34, 34, 0.1)] } } }, series: [{ type: radar, data: [{ name: 当前员工, value: [82, 93, 74, 88], areaStyle: { opacity: 0.35 } }] }] };单点展示时没有对比对象所以data数组里只有一项。重点是把雷达图的网格底色和分割线调淡让数据区域更突出。3.2 visualmap pieces 与多 Y 轴图表这个看板项目里有一个“区域销售热度图”我用了visualMap的pieces模式把销售数值按区间分成低中高三个档位分别映射不同颜色。这样做的好处是用户不需要理解连续色阶直接看颜色就能判断档位。visualMap: { type: piecewise, pieces: [ { min: 0, max: 100, label: 低, color: #5470c6 }, { min: 100, max: 500, label: 中, color: #fac858 }, { min: 500, label: 高, color: #ee6666 } ], left: 20, bottom: 20 }多 Y 轴图表是我觉得 ECharts 里最能体现配置功力的需求之一。比如同一张图里要显示销售额单位万元和增长率单位百分比两者量纲完全不同必须用双 Y 轴。配置要点是定义两个yAxis再让不同系列通过yAxisIndex指定使用哪个轴option { xAxis: { type: category, data: months }, yAxis: [ { type: value, name: 销售额(万元), position: left }, { type: value, name: 增长率(%), position: right, splitLine: { show: false } } ], series: [ { name: 销售额, type: bar, yAxisIndex: 0, data: salesData }, { name: 增长率, type: line, yAxisIndex: 1, data: growthData } ] };右边的 Y 轴之所以要隐藏splitLine是因为横向网格线只保留一组就够了两组会显得特别乱。3.3 3D 效果与科技感图表实现热词里好几条都在问 3D 饼图、柱状图 3D 效果、地图立体效果我挨个说下我的方案。第一3D 饼图。ECharts 本身没有原生的 3D 饼图但有两个方向的方案一是引入echarts-gl扩展用pie3D系列实现真正的 3D 效果二是在不引入额外依赖的情况下用普通饼图配合roseType、渐变、阴影和透明度的视觉伪装做出“看着像 3D“的效果。后者更适合对体积敏感的管理系统项目我实测下来观感差距不大series: [{ type: pie, roseType: radius, radius: [20%, 70%], itemStyle: { shadowBlur: 20, shadowColor: rgba(0, 0, 0, 0.3), color: function(params) { // 这里可以用线性渐变形成立体层次 return new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: params.color }, { offset: 1, color: #111 } ]); } } }]第二柱状图仿 3D。不需要任何扩展靠itemStyle的渐变和阴影就能做出立体柱子的视觉效果itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: #58a1ff }, { offset: 1, color: #145fa8 } ]), shadowBlur: 8, shadowColor: rgba(20, 95, 168, 0.5) }如果是真正的地图立体效果D 系列里设置regionHeight和viewControl可以做地图板块的立体拉伸适合大屏展示但配置复杂度会高不少建议先评估是否需要。第三科技感图表。热词里“中心是数字占比周围散发长短不一的动态线条”这个需求在大屏项目里特别常见。我的实现思路是组合多个 series中心放一个环形饼图或graphic文字组件显示占比数字外层用lines类型的 series 生成多条从中心向外发散的动态线条配合effect的trailLength和period做出流光效果。大致配置思路如下series: [ { type: pie, radius: [55%, 75%], label: { show: false }, data: [ { value: percent, name: 占比 }, { value: 100 - percent, name: 剩余, itemStyle: { color: rgba(255,255,255,0.1) } } ] }, { type: lines, coordinateSystem: polar, data: linesData, effect: { show: true, trailLength: 0.2, period: 4, symbol: circle, symbolSize: 3 }, lineStyle: { width: 2, color: #58a1ff, opacity: 0.6 } } ]实际做的时候linesData需要用循环生成多个角度不同、长度不同的线条端点加上点随机抖动就能模拟出“周围散发长短不一动态线条”的效果。3.4 交互功能legend 全选/全不选、hover、框选看板页面上方我放了“全选”“全不选”两个按钮用来快速控制图例的显示状态。ECharts 提供了现成的dispatchAction方法// 全选所有图例 chart.dispatchAction({ type: legendAllSelect }); // 图例全不选 chart.dispatchAction({ type: legendInverseSelect });这里有个小坑legendInverseSelect的实际效果是“反选”如果当前只有部分被选中执行后选中的会变成未选中未选中的变成选中并不是严格意义的“全不选”。要实现真正全不选可以手动遍历 legend 数据逐个legendToggleSelectfunction deselectAllLegend(chart, legendData) { legendData.forEach(function(name) { chart.dispatchAction({ type: legendToggleSelect, name: name }); }); }调用前要先判断当前图例是不是已经全选了否则会执行两次反选又变回原样。柱状图的 hover 事件是常见的联动场景。比如柱状图 hover 时旁边的指标卡片同步显示对应数据。监听方式很简单chart.on(mouseover, function(params) { if (params.componentType series params.seriesType bar) { updateIndicatorCard(params.name, params.value); } });注意params.componentType判断是必写的否则点击坐标轴、图例等非系列元素时也会触发导致数据混乱。框选事件我用的是brush组件。用户可以用鼠标在图表上拖拽框选区域ECharts 会触发brushSelected事件我在事件回调里拿到选中的 dataIndex再联动刷新页面下部的明细表格brush: { toolbox: [rect, clear], xAxisIndex: 0 }, chart.on(brushSelected, function(params) { var selectedIndices params.batch[0].selected[0].dataIndex; // 用 selectedIndices 去查询明细数据并刷新表格 });brush组件需要引入如果是按需加载方式记得把BrushComponent加进去不然 toolbox 里根本显示不出刷子图标。4. 踩坑实录常见问题与排查技巧4.1 DOM 宽度高度为 0 导致图表不渲染热词里那条log.js:72 [echarts] cant get dom width or height. please check dom.clientWidth是 ECharts 最经典的报错之一。我第一次遇到是在一个 tab 切换页面里图表容器放在隐藏的 tab 面板中页面加载时就初始化图表此时容器display: noneclientWidth为 0ECharts 就抛这个错而且后续切换到该 tab 时图表往往还是空白。解决办法有几种最推荐的做法在容器可见后再初始化图表。可以用监听 tab 切换的回调切换完成后再调用init。如果必须在隐藏状态下初始化可以在setTimeout里延迟执行或者等requestAnimationFrame后再初始化。已经出现这个问题后在容器显示时调用chart.resize()可以补救因为resize()会重新读取容器的宽高。另外init时容器如果设置了width: 100%但父容器没有明确宽度也会取不到宽度。我的习惯是给每个图表容器直接设置固定的高度宽度交给样式表解决确实需要响应式时再配合resize监听。4.2 移动端无法点击图表的排查思路ECharts 在移动端“点不动”通常不是 ECharts 本身的问题而是页面环境问题。最常见的三种原因第一有透明遮罩层盖在 canvas 上方。比如某些 UI 组件的弹层、下拉框选项或者自己写的悬浮层因为z-index更高把 canvas 挡住了。点击事件被遮罩层吃掉了。排查时用浏览器开发者工具检查点击位置到底是什么元素。第二图表所在区域外层滚动容器的touchmove被拦截。移动端为了实现“滚动穿透”或者自定义滚动逻辑经常会给父容器加touchmove的preventDefault()这会让 canvas 上的手势完全失效。我踩过这个坑最后是在监听函数里判断目标元素是否是 canvas 或 echarts 实例容器再决定要不要拦截。第三canvas 的默认触摸行为干扰。可以给 canvas 加样式canvas { touch-action: manipulation; }touch-action: manipulation可以保留缩放和滚动手势同时禁用双击缩放减少误触。如果是小程序里的echarts wx-canvas要检查小程序 canvas 的type配置老版本项目用 2d canvas 可能会遇到触摸事件不上报的问题需要做适配。4.3 echarts require 与按需引入的坑开发模式里用 CDN 引入完整版 echarts 很方便所有组件和图表类型都内置了。但到了打包上线阶段完整版体积太大通常要做按需引入。按需引入的写法比较固定// echarts/core 提供核心初始化方法 import * as echarts from echarts/core; // 按需引入图表类型 import { BarChart, PieChart, RadarChart } from echarts/charts; // 按需引入组件 import { TooltipComponent, GridComponent, LegendComponent, DatasetComponent, BrushComponent } from echarts/components; // 引入 canvas 渲染器 import { CanvasRenderer } from echarts/renderers; echarts.use([ BarChart, PieChart, RadarChart, TooltipComponent, GridComponent, LegendComponent, DatasetComponent, BrushComponent, CanvasRenderer ]);忘加组件的现象很隐蔽比如用了Tooltip但没引入TooltipComponent图表不会报错只是 tooltip 不显示。用Brush但没引入BrushComponentbrush配置会被忽略。我的排查习惯是当某个配置项不起作用时先检查是否引入了对应组件。另外如果想在按需引入时继续使用echarts.graphic.LinearGradient需要把GraphicComponent也引进来。4.4 大屏与多图表场景的性能优化这种图表看板项目页面一多、图表一多就容易卡。我的几个优化经验关闭不必要的动画。初始化加载时有个动画效果确实好看但用户在图之间切换、筛选时反复请求数据过度动画会让交互显得迟钝。我一般在重新setOption时设animation: false或者用notMerge: true直接替换配置避免新旧数据结构不一致导致的拼接问题。大数据量的柱状图开启sampling。对于几千上万条数据的折线图、柱状图可以设置sampling: lttb有效降低渲染点数图还是那张图但性能提升明显。图表被销毁时移除 resize 监听。我在window.addEventListener(resize, handler)对应的地方一定会在页面卸载或容器销毁时removeEventListener不然多页应用里会积累一堆没用的监听页面越来越卡。大屏分辨率适配用缩放方案。我试过 rem、vw 方案最后还是回到最直接的方式根据设计稿比例对图表容器做 scale 缩放配合chart.resize()在窗口尺寸变化时重新计算。这样能少处理很多响应式细节。最后分享一点经验这个销售看板项目从接口对接到最后样式打磨前前后后花了两天。最大的体会是Ajax 动态获取数据 ECharts 这套组合真正难的不是某个图表配置而是把整个数据链路理顺。你先别急着研究 3D、科技感这些炫酷效果先把一个简单柱状图的“请求 - 转换 - 渲染 - 交互 - 重新请求”闭环跑通再逐步往里面加视觉和交互能力。踩坑的时候也别慌像cant get dom width or height这种报错绝大多数是容器尺寸和初始化时机问题按上面说的方法排查基本都能解决。如果后面有时间我会再写一篇关于大屏可视化技术选型的文章把 echarts-gl、地图立体效果和性能优化单独展开聊聊。本文还有配套的精品资源点击获取