本文还有配套的精品资源点击获取简介用Python Flask搭后端服务ECharts做前端图表渲染直接跑起来就能看疫情数据变化。支持折线图展示每日确诊/治愈/死亡趋势柱状图对比各地区累计数据地图热力图呈现区域分布强度。所有代码结构清晰app.py启动服务utils.py处理数据转换逻辑templates里放main.html主页面static目录下分js/css/img管理前端资源picture和font存放自定义图片与字体。示例数据用Python字典或JSON文件模拟不依赖数据库适合本地快速运行、教学演示或疫情复盘分析。附带README.md详细说明部署步骤疫情可视化平台.png是实际界面截图.idea和COV.iml配置文件支持PyCharm一键导入.pyc预编译文件已内置requirements.txt列明依赖包.gitignore规范版本控制整体开箱即用。1. 项目概述为什么这个“轻量级疫情可视化系统”值得你花15分钟跑起来我第一次在实验室角落的旧笔记本上跑通这个项目时窗外正下着雨学生刚交完《Web开发实践》的期末作业——他们需要交一个能跑起来、有数据、带交互的完整小系统。没人想碰Docker、Kubernetes或者MySQL主从同步但又不能交个静态HTML糊弄过去。这时候这个基于Flask与ECharts的疫情数据可视化演示系统成了我连续三年课堂演示的“压舱石”。它不炫技不堆栈不造轮子就用最朴素的Python字典存数据、最干净的Flask路由吐JSON、最成熟的ECharts渲染图表三者咬合得严丝合缝。关键词里提到的Flask不是为了凑热门框架而是因为它启动快、无依赖、单文件可运行ECharts也不是图新鲜而是它对中文地图、热力图、时间轴联动的支持在所有开源图表库中至今仍属第一梯队至于疫情可视化它早已超越公共卫生范畴成了数据工程师理解“时序地理分类”三维数据建模的通用教学沙盒而数据图表和Web展示这两个词恰恰点破了本质这不是一个生产系统而是一套可拆解、可替换、可延展的“数据表达语法”。它解决的不是“如何构建国家级疫情监测平台”这种宏大命题而是更实际的问题当你手头有一份Excel里的每日新增确诊数想3分钟内让领导看到趋势、让同事看清地域分布、让学生理解API怎么把后端数据喂给前端图表——这个系统就是你的最小可行答案。它不强制你学Vue或React不逼你配Nginx反向代理甚至不需要你装Node.js——只要Python 3.8和pippython app.py回车浏览器打开http://127.0.0.1:5000一张带缩放、拖拽、悬停提示的地图热力图就铺在眼前。所有代码结构像乐高积木一样清晰app.py是总开关utils.py是数据翻译官templates/main.html是舞台中央的画布static/js/里躺着ECharts初始化脚本static/font/里塞着防止中文乱码的思源黑体。示例数据就藏在utils.py开头那个嵌套字典里改几行就能换成你本地的社区检测数据想换地图只用替换static/js/china.json想加新指标在app.py里加个新路由前端JS里配个新图表容器就行。它不承诺替代专业BI工具但它保证你今天下午三点拿到数据四点就能做出带交互的演示页。这才是“开箱即用”的真实含义——不是包装精美而是路径最短、阻力最小、容错最强。2. 整体架构设计与技术选型逻辑拆解2.1 为什么是Flask而不是FastAPI或Django很多人看到“Web服务”第一反应是FastAPI毕竟它自带异步、自动生成文档、性能参数漂亮。但在这个项目里Flask是经过三次迭代后锁定的选择理由非常务实启动复杂度归零FastAPI依赖Starlette和Pydantic仅安装就要处理asyncio兼容性问题Django则自带ORM、Admin后台、模板引擎三层抽象。而Flask核心只有WerkzeugWSGI工具包和Jinja2模板引擎两个依赖pip install flask后一个from flask import Flask就能启动服务。我在给大二学生讲授时发现当他们第一次写app.run(debugTrue)看到控制台输出“* Running on http://127.0.0.1:5000”时那种即时反馈带来的掌控感远胜于等待FastAPI生成OpenAPI文档的30秒。数据流极简可控疫情数据可视化本质是“请求→查字典→转JSON→返回”没有用户认证、没有事务回滚、没有并发写入。Flask的app.route装饰器直接映射URL到函数中间不夹杂任何中间件或信号机制。比如获取全国每日数据的接口app.py里就这12行python app.route(/api/daily) def get_daily_data(): # 直接调用utils.py里的函数返回纯字典 data utils.get_daily_summary() # Flask自动序列化为JSON设置Content-Type return jsonify(data)对比FastAPI需要定义Pydantic模型、声明响应类型、处理异常类这里连try...except都不需要——因为数据就在内存里不可能抛出IOError。调试友好性碾压级优势.idea和COV.iml配置文件的存在不是偶然。PyCharm对Flask的调试支持是开箱即用的断点打在get_daily_data()函数里F9启动调试模式浏览器访问/api/daily变量窗口实时显示data字典的每一层嵌套。而FastAPI在PyCharm里调试需额外配置Uvicorn参数Django则要区分manage.py和wsgi.py两种入口。对于教学场景少一个配置步骤就多一分学生坚持下去的概率。提示如果你真想升级为FastAPI只需将app.py重命名为main.py把app.route改成app.getjsonify()换成return再补一行app FastAPI()——整个迁移成本低于10分钟。这恰恰证明Flask在此处的价值它不是技术上限而是最佳起点。2.2 为什么选ECharts而非Chart.js或AntV前端图表库选型时我对比了Chart.js、AntV G2和ECharts三个主流方案最终ECharts胜出的关键在于中文生态的深度适配而这恰恰是疫情可视化不可绕过的硬需求地图热力图的“开箱即用”Chart.js原生不支持地理坐标系要实现省级热力图必须引入Leaflet或Mapbox再写大量坐标转换逻辑AntV G2虽支持地理视图但其中国地图JSON需自行投影转换新手极易卡在EPSG:4326坐标系校准上。而ECharts官网直接提供标准中国地图JSON下载后放入static/js/三行代码即可激活javascript// 在main.html中引入// 初始化图表时指定地图option {geo: { map: ‘china’ },series: [{type: ‘heatmap’,coordinateSystem: ‘geo’,data: convertToGeoData(rawData) // 将[省名, 值]转为[{name: ‘广东’, value: 123}]}]}这种“下载即用”的体验让地理可视化从工程问题降维成配置问题。时间轴联动的成熟方案疫情数据必然涉及“按日查看”“按周聚合”“滑动选择时间段”等交互。ECharts的dataZoom组件和timeline组件已打磨十年支持平滑缩放、区域选择、播放控制且与折线图、柱状图天然耦合。比如实现“拖动时间轴三张图表同步更新”只需在option中配置javascript timeline: { data: [2022-01-01, 2022-01-02, ...], playInterval: 2000, axisType: category }而Chart.js需借助第三方插件AntV G2则要手动监听事件并触发重绘代码量翻倍且易出错。字体与符号的本土化细节font/目录存在的意义在此刻凸显。ECharts默认使用系统字体但在Windows上常出现中文方块、Mac上标点错位。项目预置的SourceHanSansSC-Regular.otf思源黑体简体常规版通过CSSfont-face注入确保所有图表标题、坐标轴标签、提示框文字100%清晰。这种对“看不见的细节”的把控是教学演示时避免PPT翻车的关键。2.3 “无数据库”设计背后的工程哲学项目摘要强调“无需复杂数据库”这绝非偷懒而是针对教学与原型场景的精准克制数据规模决定技术选型一份典型疫情数据集如国家卫健委每日通报包含约34个省级行政区×365天×3指标确诊/治愈/死亡总量不足4万条记录。用SQLite存储固然可行但会引入额外的建表语句、连接管理、SQL注入防护等概念偏离“可视化表达”的核心目标。而Python字典在内存中的查询复杂度是O(1)读取10万条数据耗时仍低于1ms——这正是utils.py中DATA_POOL字典的设计依据。数据变更模式匹配静态结构疫情数据具有强时间序列特性历史数据一旦发布即固定极少修改。这与电商订单频繁增删改或社交动态实时推送截然不同。因此将数据固化为JSON文件如data/cn_daily.json并通过json.load()一次性加载比建立数据库连接池更符合实际使用模式。我在utils.py里特意设计了双模式加载python def load_data(): if os.path.exists(data/cn_daily.json): with open(data/cn_daily.json, r, encodingutf-8) as f: return json.load(f) else: return DEFAULT_DATA_DICT # 回退到内置字典这样既支持快速启动又为后续接入真实数据源留出扩展口。版本控制友好性JSON文件可直接用Git追踪变更git diff能清晰看到某日新增病例数从123变为145而数据库二进制文件无法diff导出SQL又增加维护负担。在学生分组作业中这种透明性极大降低了协作冲突概率。3. 核心模块解析与实操要点精讲3.1 后端服务层app.py的127行代码如何撑起整个系统app.py是项目的神经中枢全文仅127行不含空行和注释却完成了路由注册、数据接口、静态资源托管三大核心职能。我们逐段拆解其设计逻辑第一部分基础配置与实例化第1-15行import os from flask import Flask, render_template, jsonify, send_from_directory import utils app Flask(__name__) app.config[JSON_AS_ASCII] False # 关键禁用ASCII编码否则中文变\uXXXX app.config[SEND_FILE_MAX_AGE_DEFAULT] 0 # 禁用静态文件缓存方便开发调试这里有两个极易被忽略但致命的配置JSON_AS_ASCIIFalse确保jsonify()返回的JSON中中文不被转义否则前端收到{province: \u5e7f\u4e1c}还得额外解码SEND_FILE_MAX_AGE_DEFAULT0强制浏览器每次重新请求CSS/JS文件避免修改样式后刷新页面无变化的尴尬。我在第一次部署时因漏掉第二项花了40分钟排查“为什么改了CSS颜色没生效”。第二部分核心API路由第17-68行共定义5个接口覆盖全部可视化需求-/api/daily返回全国每日确诊/治愈/死亡汇总折线图数据源-/api/province返回各省份累计数据柱状图数据源-/api/heatmap返回省份名称数值数组热力图数据源-/api/timeline返回时间轴日期列表时间轴组件数据源-/api/detail/province返回指定省份详细日志点击地图钻取每个路由都遵循统一模式调用utils.py对应函数 → 处理异常如省份不存在→ 返回jsonify()。特别注意/api/detail/province的路径参数设计app.route(/api/detail/province) def get_province_detail(province): try: data utils.get_province_detail(province) return jsonify(data) except KeyError: return jsonify({error: fProvince {province} not found}), 404这种显式异常捕获比Flask默认的500错误页更友好前端可据此提示“该地区暂无数据”。第三部分前端页面与静态资源第70-127行app.route(/) def index(): return render_template(main.html) app.route(/static/path:filename) def static_files(filename): return send_from_directory(static, filename) app.route(/picture/path:filename) def picture_files(filename): return send_from_directory(picture, filename) app.route(/font/path:filename) def font_files(filename): return send_from_directory(font, filename)这里暴露了一个重要设计原则静态资源路径与目录结构严格一一对应。/static/js/chart.js对应static/js/chart.js文件/picture/logo.png对应picture/logo.png。这种直白映射避免了Flask的url_for()函数在复杂路径下的调试困惑也方便学生直接在浏览器地址栏测试资源是否可访问。实操心得在PyCharm中右键app.py选择“Debug ‘app’”控制台会输出所有注册路由。若发现/api/daily未列出大概率是app.route装饰器位置错误如写在函数定义之外或缩进问题。这是新手最常见的报错原因。3.2 数据处理中枢utils.py的字典魔法与JSON转换技巧utils.py是项目的“数据翻译官”全文213行核心价值在于将原始数据结构转化为ECharts可消费的格式。我们聚焦三个关键函数get_daily_summary()时间序列标准化第25-58行原始数据可能是这样的嵌套字典RAW_DATA { 2022-01-01: {guangdong: {confirmed: 123, cured: 89, dead: 2}}, 2022-01-02: {guangdong: {confirmed: 135, cured: 95, dead: 2}}, ... }但ECharts折线图要求数据为[ [x1,y1], [x2,y2], ... ]或{x: [...], y: [...]}格式。该函数做了三件事1.时间戳标准化遍历所有日期键用datetime.strptime(date_str, %Y-%m-%d)转为datetime对象再格式化为Jan 01供X轴显示2.指标分离将confirmed/cured/dead分别提取为独立数组确保三条折线数据长度严格一致3.空值填充若某日某省数据缺失自动补0而非跳过避免图表出现断裂。def get_daily_summary(): dates sorted(RAW_DATA.keys()) confirmed [] cured [] dead [] for date in dates: province_data RAW_DATA[date].get(national, {}) confirmed.append(province_data.get(confirmed, 0)) cured.append(province_data.get(cured, 0)) dead.append(province_data.get(dead, 0)) return { dates: [d[5:] for d in dates], # 取YYYY-MM-DD后5位得MM-DD confirmed: confirmed, cured: cured, dead: dead }get_province_heatmap()地理编码映射第60-95行热力图数据要求[{name: 广东, value: 123}, ...]但原始数据中省份名为拼音guangdong或英文Guangdong。该函数内置映射表PROVINCE_MAP { guangdong: 广东, beijing: 北京, shanghai: 上海, # ... 全部34个省级行政区 }并调用pypinyin库将拼音转中文备用方案确保即使数据源用拼音命名前端也能正确显示。load_china_geojson()地图数据预加载第97-122行为避免每次请求都读取static/js/china.json该函数在模块导入时一次性加载并缓存CHINA_GEOJSON None def load_china_geojson(): global CHINA_GEOJSON if CHINA_GEOJSON is None: with open(static/js/china.json, r, encodingutf-8) as f: CHINA_GEOJSON json.load(f) return CHINA_GEOJSON这种单例模式将地图JSON加载耗时从每次请求的5ms降至0对高频访问的热力图至关重要。注意事项utils.py中所有函数必须返回纯Python数据结构dict/list/int/str严禁返回Pandas DataFrame或NumPy数组——ECharts无法解析这些对象jsonify()会直接报错TypeError: Object of type DataFrame is not JSON serializable。3.3 前端渲染层main.html与ECharts初始化的黄金组合templates/main.html是项目的视觉心脏全文386行其精妙之处在于将ECharts配置与HTML结构解耦同时保持极致简洁结构设计第1-85行页面采用经典三栏布局-div idchart-line容纳全国趋势折线图-div idchart-bar容纳省份对比柱状图-div idchart-map容纳中国地图热力图每个容器均设固定宽高stylewidth: 100%; height: 400px;避免ECharts因父容器尺寸为0导致渲染失败——这是新手踩坑最多的地方。ECharts初始化第87-386行核心逻辑封装在initCharts()函数中采用“先创建实例再设置option”的两阶段模式// 1. 创建实例必须在DOM元素存在后执行 const lineChart echarts.init(document.getElementById(chart-line)); const barChart echarts.init(document.getElementById(chart-bar)); const mapChart echarts.init(document.getElementById(chart-map)); // 2. 统一设置全局主题防止单个图表配置重复 echarts.registerTheme(dark, { backgroundColor: #1a1a2e, textStyle: { color: #e6e6e6 } }); lineChart.setOption(getLineOption(), { theme: dark }); // 3. 定义option生成函数解耦配置逻辑 function getLineOption() { return { tooltip: { trigger: axis }, legend: { data: [确诊, 治愈, 死亡] }, xAxis: { type: category, data: [] }, // 空数组后续AJAX填充 yAxis: { type: value }, series: [ { name: 确诊, type: line, data: [] }, { name: 治愈, type: line, data: [] }, { name: 死亡, type: line, data: [] } ] }; }这种设计带来两大好处一是option可复用getBarOption()与getLineOption()共享大部分配置二是便于调试——在浏览器控制台直接调用getLineOption()即可看到完整配置结构。数据加载与图表联动第210-320行所有图表数据通过fetch()异步加载关键技巧在于错误隔离与加载状态管理Promise.all([ fetch(/api/daily).then(r r.json()), fetch(/api/province).then(r r.json()), fetch(/api/heatmap).then(r r.json()) ]).then(([daily, province, heatmap]) { // 成功时统一更新三个图表 lineChart.setOption({ xAxis: { data: daily.dates }, series: [...] }); barChart.setOption({ xAxis: { data: province.names }, series: [...] }); mapChart.setOption({ series: [{ data: heatmap }] }); }).catch(err { console.error(数据加载失败:, err); document.getElementById(loading).innerText 数据加载失败请刷新重试; });用Promise.all()确保三个接口全部成功才更新图表避免出现“折线图有数据柱状图空白”的割裂体验。而catch块将错误导向用户可见的提示而非静默失败。实操心得在Chrome开发者工具中Network标签页可监控所有/api/*请求。若某个图表空白优先检查对应接口是否返回200状态码及有效JSON。常见错误包括后端jsonify()传入None、前端fetch()未加await导致data为Promise对象、ECharts容器DOM元素ID拼写错误如chart-lin少个e。4. 完整实操流程与关键环节实现4.1 一分钟环境搭建从零到首页显示以下步骤经实测可在Windows/macOS/Linux三端复现全程无需管理员权限步骤1确认Python环境# 检查Python版本需3.8 python --version # 若未安装从python.org下载安装包勾选Add Python to PATH注意不要使用系统自带Python如macOS的/usr/bin/python它通常为2.7版本且权限受限。步骤2克隆项目并进入目录# 解压下载的ZIP包或使用git若已配置 git clone https://github.com/xxx/covid-viz.git cd covid-viz此时目录结构应与输入描述完全一致app.py,utils.py,templates/,static/等同级存在。步骤3创建虚拟环境强烈推荐# 创建venvPython 3.3内置 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate.bat # macOS/Linux: source venv/bin/activate虚拟环境能隔离项目依赖避免与系统其他Python项目冲突。若跳过此步后续pip install可能污染全局环境。步骤4安装依赖# 查看requirements.txt内容确认无敏感包 cat requirements.txt # 安装输出应显示Successfully installed flask-2.3.3 pyecharts-2.0.3等 pip install -r requirements.txtrequirements.txt中关键依赖为-Flask2.3.3稳定版避免新版breaking change-pyecharts2.0.3虽项目主要用原生ECharts JS但此包提供地图JSON便捷下载pip install echarts-china-provinces-pypkg-pypinyin0.48.0用于拼音转中文备用方案步骤5启动服务# 执行app.py注意不是python app.py而是flask命令 set FLASK_APPapp.py set FLASK_ENVdevelopment flask run # 或直接运行效果相同 python app.py终端将输出* Serving Flask app app.py * Debug mode: on * Running on http://127.0.0.1:5000 Press CTRLC to quit步骤6浏览器访问打开Chrome/Firefox访问http://127.0.0.1:5000。首次加载可能需5-8秒ECharts JS文件较大成功后应看到三张图表- 顶部折线图显示“确诊/治愈/死亡”三条曲线- 中部柱状图显示各省累计确诊数- 底部中国地图呈现热力分布颜色越深表示数值越高排查技巧若页面空白按F12打开开发者工具切换到Console标签页查看是否有Failed to load resource错误。常见原因static/js/echarts.min.js路径错误检查main.html中script src/static/js/echarts.min.js是否与实际文件位置匹配若报Access to fetch at http://127.0.0.1:5000/api/daily from origin null has been blocked说明HTML是双击打开的file://协议必须通过flask run启动的服务器访问。4.2 数据替换实战用你自己的疫情数据替换示例数据项目内置数据位于utils.py开头的RAW_DATA字典但真实场景中你需要注入自有数据。以下是三种替换方案按推荐度排序方案1JSON文件替换最推荐1. 准备你的数据文件data/my_covid.json格式如下json { 2023-10-01: {national: {confirmed: 500, cured: 480, dead: 5}, guangdong: {confirmed: 120}}, 2023-10-02: {national: {confirmed: 520, cured: 495, dead: 5}, guangdong: {confirmed: 125}} }2. 修改utils.py中load_data()函数指向新路径python def load_data(): # 注释掉原有DEFAULT_DATA_DICT # return DEFAULT_DATA_DICT with open(data/my_covid.json, r, encodingutf-8) as f: return json.load(f)3. 重启Flask服务数据即生效。方案2CSV导入适合Excel导出场景若你的数据在Excel中先导出为UTF-8编码的CSVdate,province,confirmed,cured,dead 2023-10-01,广东,120,115,0 2023-10-01,北京,85,82,0 2023-10-02,广东,125,118,0然后在utils.py中添加CSV解析函数import csv def load_csv_data(filepathdata/covid.csv): data {} with open(filepath, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: date row[date] province row[province] if date not in data: data[date] {} if province not in data[date]: data[date][province] {} data[date][province][confirmed] int(row[confirmed]) data[date][province][cured] int(row[cured]) data[date][province][dead] int(row[dead]) return data再将load_data()改为调用此函数。方案3API实时拉取进阶若数据源提供HTTP API如https://api.example.com/covid?date2023-10-01可改造get_daily_summary()import requests def get_daily_summary(): # 拉取最近30天数据 dates [(datetime.now() - timedelta(daysi)).strftime(%Y-%m-%d) for i in range(30)] all_data [] for date in dates: resp requests.get(fhttps://api.example.com/covid?date{date}) if resp.status_code 200: all_data.append(resp.json()) # 合并数据逻辑... return processed_data注意事项requests需添加到requirements.txt生产环境务必添加超时timeout5和重试机制避免API不可用导致整个服务挂起。4.3 图表定制化修改主题、颜色与交互行为ECharts的灵活性体现在其配置项的丰富性。以下是最常用定制场景修改全局主题色找到main.html中getLineOption()函数修改series配置series: [ { name: 确诊, type: line, data: [], itemStyle: { color: #e74c3c }, // 红色 smooth: true // 启用平滑曲线 }, { name: 治愈, type: line, data: [], itemStyle: { color: #2ecc71 }, // 绿色 areaStyle: {} // 添加面积填充 } ]颜色值可参考Material Design调色板确保对比度足够如红绿搭配需避免色盲用户无法区分。启用地图下钻交互当前热力图点击省份无反应。添加下钻功能// 在mapChart初始化后 mapChart.on(click, function (params) { if (params.componentType series) { const provinceName params.name; // 请求该省份详情 fetch(/api/detail/${provinceName}) .then(r r.json()) .then(data { // 更新折线图显示该省数据 lineChart.setOption({ title: { text: ${provinceName}疫情趋势 }, series: [ { name: 确诊, data: data.confirmed }, { name: 治愈, data: data.cured } ] }); }); } });此时点击地图任意省份上方折线图将自动切换为该省数据。添加数据导出按钮在main.html的body底部添加按钮button onclickexportData()导出当前图表数据/button script function exportData() { const data lineChart.getOption(); const blob new Blob([JSON.stringify(data, null, 2)], {type: application/json}); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download covid-data.json; a.click(); } /script点击按钮即可下载当前折线图的完整配置与数据便于离线分析。5. 常见问题与排查技巧实录5.1 启动失败类问题速查表现象可能原因排查命令/方法解决方案终端报错ModuleNotFoundError: No module named flask虚拟环境未激活或pip安装失败which pipmacOS/Linux或where pipWindows确认pip路径pip list \| findstr flask重新执行pip install flask若权限不足加--user参数浏览器显示This site can’t be reachedFlask服务未启动或端口被占用netstat -ano \| findstr :5000Windows或lsof -i :5000macOS/Linux杀死占用进程taskkill /PID PID /FWindows或kill -9 PIDmacOS/Linux或改用其他端口flask run --port 5001页面空白Console报Uncaught ReferenceError: echarts is not definedECharts JS文件未正确加载在浏览器地址栏直接访问http://127.0.0.1:5000/static/js/echarts.min.js检查static/js/目录是否存在该文件若为404确认main.html中script标签路径是否为/static/js/echarts.min.js注意开头斜杠折线图X轴显示undefinedutils.py中get_daily_summary()返回的dates字段为空在app.py中get_daily_data()路由里添加print(data)调试检查RAW_DATA字典是否为空确认日期键格式为YYYY-MM-DD非YYYY/MM/DD5.2 图表渲染类问题深度解析问题热力图颜色全部为浅蓝无深色区域-根因分析ECharts热力图默认颜色映射范围是数据最小值到最大值。若所有省份数值都在100-150之间颜色梯度会压缩在浅蓝色区间。-解决方案在getMapOption()中手动指定visualMap范围javascript visualMap: { min: 0, max: 1000, // 设定合理上限如全国最高值 calculable: true, inRange: { color: [#bfefff, #40c4ff, #0091ea, #0060b2, #003782] } }此时数值0-200显示浅蓝800-1000显示深蓝梯度清晰可见。问题柱状图省份名称重叠显示不全-根因分析X轴标签过长如“内蒙古自治区”且数量过多34个ECharts默认不旋转标签。-解决方案在getBarOption()中配置xAxisjavascript xAxis: { type: category, data: provinceNames, axisLabel: { rotate: 45, // 向右旋转45度 interval: 0, // 强制显示所有标签 fontSize: 12 } }若仍重叠可改用interval: 1隔一个显示一个或启用formatter截断文字javascript formatter: function(value) { return value.length 4 ? value.substring(0, 4) ... : value; }问题地图点击后折线图不更新Console报Cannot read property setOption of undefined-根因分析lineChart实例在mapChart.on(click)回调中未定义因变量作用域问题。-解决方案将图表实例声明为全局变量不推荐或在闭包中传递javascript// 在initCharts()函数外声明let lineChart, barChart, mapChart;function initCharts() {lineChart echarts.init(…);barChart echarts.init(…);mapChart echarts.init(…);mapChart.on(click, function(params) { // 此处lineChart已定义 lineChart.setOption({...}); });}5.3 部署上线注意事项虽然项目定位为本地演示但若需部署到云服务器如阿里云ECS需注意生产环境禁用debug模式app.py中app.run(debugTrue)必须删除改用Gunicornbash pip install gunicorn gunicorn -w 4 -b 0.0.0.0:8000 app:app-w 4启动4个工作进程-b绑定到8000端口。静态资源托管优化Flask自带静态文件服务不适合高并发。建议用Nginx反向代理nginx location /static/ { alias /path/to/your/project/static/; expires 1h; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; }数据安全加固若接入真实API务必在utils.py中添加请求头伪装防爬虫拦截python headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } requests.get(url, headersheaders, timeout5)最后分享一个小技巧在README.md中补充“快速验证清单”包含三行命令bash python -c import flask; print(flask.__version__) # 检查Flask curl -s http://127.0.0.1:5000/api/daily \| head -20 # 检查API ls static/js/echarts.min.js # 检查JS文件新人按顺序执行5分钟内即可确认环境是否完备大幅降低入门门槛。本文还有配套的精品资源点击获取简介用Python Flask搭后端服务ECharts做前端图表渲染直接跑起来就能看疫情数据变化。支持折线图展示每日确诊/治愈/死亡趋势柱状图对比各地区累计数据地图热力图呈现区域分布强度。所有代码结构清晰app.py启动服务utils.py处理数据转换逻辑templates里放main.html主页面static目录下分js/css/img管理前端资源picture和font存放自定义图片与字体。示例数据用Python字典或JSON文件模拟不依赖数据库适合本地快速运行、教学演示或疫情复盘分析。附带README.md详细说明部署步骤疫情可视化平台.png是实际界面截图.idea和COV.iml配置文件支持PyCharm一键导入.pyc预编译文件已内置requirements.txt列明依赖包.gitignore规范版本控制整体开箱即用。本文还有配套的精品资源点击获取
基于Flask与ECharts的轻量级疫情数据可视化演示系统
本文还有配套的精品资源点击获取简介用Python Flask搭后端服务ECharts做前端图表渲染直接跑起来就能看疫情数据变化。支持折线图展示每日确诊/治愈/死亡趋势柱状图对比各地区累计数据地图热力图呈现区域分布强度。所有代码结构清晰app.py启动服务utils.py处理数据转换逻辑templates里放main.html主页面static目录下分js/css/img管理前端资源picture和font存放自定义图片与字体。示例数据用Python字典或JSON文件模拟不依赖数据库适合本地快速运行、教学演示或疫情复盘分析。附带README.md详细说明部署步骤疫情可视化平台.png是实际界面截图.idea和COV.iml配置文件支持PyCharm一键导入.pyc预编译文件已内置requirements.txt列明依赖包.gitignore规范版本控制整体开箱即用。1. 项目概述为什么这个“轻量级疫情可视化系统”值得你花15分钟跑起来我第一次在实验室角落的旧笔记本上跑通这个项目时窗外正下着雨学生刚交完《Web开发实践》的期末作业——他们需要交一个能跑起来、有数据、带交互的完整小系统。没人想碰Docker、Kubernetes或者MySQL主从同步但又不能交个静态HTML糊弄过去。这时候这个基于Flask与ECharts的疫情数据可视化演示系统成了我连续三年课堂演示的“压舱石”。它不炫技不堆栈不造轮子就用最朴素的Python字典存数据、最干净的Flask路由吐JSON、最成熟的ECharts渲染图表三者咬合得严丝合缝。关键词里提到的Flask不是为了凑热门框架而是因为它启动快、无依赖、单文件可运行ECharts也不是图新鲜而是它对中文地图、热力图、时间轴联动的支持在所有开源图表库中至今仍属第一梯队至于疫情可视化它早已超越公共卫生范畴成了数据工程师理解“时序地理分类”三维数据建模的通用教学沙盒而数据图表和Web展示这两个词恰恰点破了本质这不是一个生产系统而是一套可拆解、可替换、可延展的“数据表达语法”。它解决的不是“如何构建国家级疫情监测平台”这种宏大命题而是更实际的问题当你手头有一份Excel里的每日新增确诊数想3分钟内让领导看到趋势、让同事看清地域分布、让学生理解API怎么把后端数据喂给前端图表——这个系统就是你的最小可行答案。它不强制你学Vue或React不逼你配Nginx反向代理甚至不需要你装Node.js——只要Python 3.8和pippython app.py回车浏览器打开http://127.0.0.1:5000一张带缩放、拖拽、悬停提示的地图热力图就铺在眼前。所有代码结构像乐高积木一样清晰app.py是总开关utils.py是数据翻译官templates/main.html是舞台中央的画布static/js/里躺着ECharts初始化脚本static/font/里塞着防止中文乱码的思源黑体。示例数据就藏在utils.py开头那个嵌套字典里改几行就能换成你本地的社区检测数据想换地图只用替换static/js/china.json想加新指标在app.py里加个新路由前端JS里配个新图表容器就行。它不承诺替代专业BI工具但它保证你今天下午三点拿到数据四点就能做出带交互的演示页。这才是“开箱即用”的真实含义——不是包装精美而是路径最短、阻力最小、容错最强。2. 整体架构设计与技术选型逻辑拆解2.1 为什么是Flask而不是FastAPI或Django很多人看到“Web服务”第一反应是FastAPI毕竟它自带异步、自动生成文档、性能参数漂亮。但在这个项目里Flask是经过三次迭代后锁定的选择理由非常务实启动复杂度归零FastAPI依赖Starlette和Pydantic仅安装就要处理asyncio兼容性问题Django则自带ORM、Admin后台、模板引擎三层抽象。而Flask核心只有WerkzeugWSGI工具包和Jinja2模板引擎两个依赖pip install flask后一个from flask import Flask就能启动服务。我在给大二学生讲授时发现当他们第一次写app.run(debugTrue)看到控制台输出“* Running on http://127.0.0.1:5000”时那种即时反馈带来的掌控感远胜于等待FastAPI生成OpenAPI文档的30秒。数据流极简可控疫情数据可视化本质是“请求→查字典→转JSON→返回”没有用户认证、没有事务回滚、没有并发写入。Flask的app.route装饰器直接映射URL到函数中间不夹杂任何中间件或信号机制。比如获取全国每日数据的接口app.py里就这12行python app.route(/api/daily) def get_daily_data(): # 直接调用utils.py里的函数返回纯字典 data utils.get_daily_summary() # Flask自动序列化为JSON设置Content-Type return jsonify(data)对比FastAPI需要定义Pydantic模型、声明响应类型、处理异常类这里连try...except都不需要——因为数据就在内存里不可能抛出IOError。调试友好性碾压级优势.idea和COV.iml配置文件的存在不是偶然。PyCharm对Flask的调试支持是开箱即用的断点打在get_daily_data()函数里F9启动调试模式浏览器访问/api/daily变量窗口实时显示data字典的每一层嵌套。而FastAPI在PyCharm里调试需额外配置Uvicorn参数Django则要区分manage.py和wsgi.py两种入口。对于教学场景少一个配置步骤就多一分学生坚持下去的概率。提示如果你真想升级为FastAPI只需将app.py重命名为main.py把app.route改成app.getjsonify()换成return再补一行app FastAPI()——整个迁移成本低于10分钟。这恰恰证明Flask在此处的价值它不是技术上限而是最佳起点。2.2 为什么选ECharts而非Chart.js或AntV前端图表库选型时我对比了Chart.js、AntV G2和ECharts三个主流方案最终ECharts胜出的关键在于中文生态的深度适配而这恰恰是疫情可视化不可绕过的硬需求地图热力图的“开箱即用”Chart.js原生不支持地理坐标系要实现省级热力图必须引入Leaflet或Mapbox再写大量坐标转换逻辑AntV G2虽支持地理视图但其中国地图JSON需自行投影转换新手极易卡在EPSG:4326坐标系校准上。而ECharts官网直接提供标准中国地图JSON下载后放入static/js/三行代码即可激活javascript// 在main.html中引入// 初始化图表时指定地图option {geo: { map: ‘china’ },series: [{type: ‘heatmap’,coordinateSystem: ‘geo’,data: convertToGeoData(rawData) // 将[省名, 值]转为[{name: ‘广东’, value: 123}]}]}这种“下载即用”的体验让地理可视化从工程问题降维成配置问题。时间轴联动的成熟方案疫情数据必然涉及“按日查看”“按周聚合”“滑动选择时间段”等交互。ECharts的dataZoom组件和timeline组件已打磨十年支持平滑缩放、区域选择、播放控制且与折线图、柱状图天然耦合。比如实现“拖动时间轴三张图表同步更新”只需在option中配置javascript timeline: { data: [2022-01-01, 2022-01-02, ...], playInterval: 2000, axisType: category }而Chart.js需借助第三方插件AntV G2则要手动监听事件并触发重绘代码量翻倍且易出错。字体与符号的本土化细节font/目录存在的意义在此刻凸显。ECharts默认使用系统字体但在Windows上常出现中文方块、Mac上标点错位。项目预置的SourceHanSansSC-Regular.otf思源黑体简体常规版通过CSSfont-face注入确保所有图表标题、坐标轴标签、提示框文字100%清晰。这种对“看不见的细节”的把控是教学演示时避免PPT翻车的关键。2.3 “无数据库”设计背后的工程哲学项目摘要强调“无需复杂数据库”这绝非偷懒而是针对教学与原型场景的精准克制数据规模决定技术选型一份典型疫情数据集如国家卫健委每日通报包含约34个省级行政区×365天×3指标确诊/治愈/死亡总量不足4万条记录。用SQLite存储固然可行但会引入额外的建表语句、连接管理、SQL注入防护等概念偏离“可视化表达”的核心目标。而Python字典在内存中的查询复杂度是O(1)读取10万条数据耗时仍低于1ms——这正是utils.py中DATA_POOL字典的设计依据。数据变更模式匹配静态结构疫情数据具有强时间序列特性历史数据一旦发布即固定极少修改。这与电商订单频繁增删改或社交动态实时推送截然不同。因此将数据固化为JSON文件如data/cn_daily.json并通过json.load()一次性加载比建立数据库连接池更符合实际使用模式。我在utils.py里特意设计了双模式加载python def load_data(): if os.path.exists(data/cn_daily.json): with open(data/cn_daily.json, r, encodingutf-8) as f: return json.load(f) else: return DEFAULT_DATA_DICT # 回退到内置字典这样既支持快速启动又为后续接入真实数据源留出扩展口。版本控制友好性JSON文件可直接用Git追踪变更git diff能清晰看到某日新增病例数从123变为145而数据库二进制文件无法diff导出SQL又增加维护负担。在学生分组作业中这种透明性极大降低了协作冲突概率。3. 核心模块解析与实操要点精讲3.1 后端服务层app.py的127行代码如何撑起整个系统app.py是项目的神经中枢全文仅127行不含空行和注释却完成了路由注册、数据接口、静态资源托管三大核心职能。我们逐段拆解其设计逻辑第一部分基础配置与实例化第1-15行import os from flask import Flask, render_template, jsonify, send_from_directory import utils app Flask(__name__) app.config[JSON_AS_ASCII] False # 关键禁用ASCII编码否则中文变\uXXXX app.config[SEND_FILE_MAX_AGE_DEFAULT] 0 # 禁用静态文件缓存方便开发调试这里有两个极易被忽略但致命的配置JSON_AS_ASCIIFalse确保jsonify()返回的JSON中中文不被转义否则前端收到{province: \u5e7f\u4e1c}还得额外解码SEND_FILE_MAX_AGE_DEFAULT0强制浏览器每次重新请求CSS/JS文件避免修改样式后刷新页面无变化的尴尬。我在第一次部署时因漏掉第二项花了40分钟排查“为什么改了CSS颜色没生效”。第二部分核心API路由第17-68行共定义5个接口覆盖全部可视化需求-/api/daily返回全国每日确诊/治愈/死亡汇总折线图数据源-/api/province返回各省份累计数据柱状图数据源-/api/heatmap返回省份名称数值数组热力图数据源-/api/timeline返回时间轴日期列表时间轴组件数据源-/api/detail/province返回指定省份详细日志点击地图钻取每个路由都遵循统一模式调用utils.py对应函数 → 处理异常如省份不存在→ 返回jsonify()。特别注意/api/detail/province的路径参数设计app.route(/api/detail/province) def get_province_detail(province): try: data utils.get_province_detail(province) return jsonify(data) except KeyError: return jsonify({error: fProvince {province} not found}), 404这种显式异常捕获比Flask默认的500错误页更友好前端可据此提示“该地区暂无数据”。第三部分前端页面与静态资源第70-127行app.route(/) def index(): return render_template(main.html) app.route(/static/path:filename) def static_files(filename): return send_from_directory(static, filename) app.route(/picture/path:filename) def picture_files(filename): return send_from_directory(picture, filename) app.route(/font/path:filename) def font_files(filename): return send_from_directory(font, filename)这里暴露了一个重要设计原则静态资源路径与目录结构严格一一对应。/static/js/chart.js对应static/js/chart.js文件/picture/logo.png对应picture/logo.png。这种直白映射避免了Flask的url_for()函数在复杂路径下的调试困惑也方便学生直接在浏览器地址栏测试资源是否可访问。实操心得在PyCharm中右键app.py选择“Debug ‘app’”控制台会输出所有注册路由。若发现/api/daily未列出大概率是app.route装饰器位置错误如写在函数定义之外或缩进问题。这是新手最常见的报错原因。3.2 数据处理中枢utils.py的字典魔法与JSON转换技巧utils.py是项目的“数据翻译官”全文213行核心价值在于将原始数据结构转化为ECharts可消费的格式。我们聚焦三个关键函数get_daily_summary()时间序列标准化第25-58行原始数据可能是这样的嵌套字典RAW_DATA { 2022-01-01: {guangdong: {confirmed: 123, cured: 89, dead: 2}}, 2022-01-02: {guangdong: {confirmed: 135, cured: 95, dead: 2}}, ... }但ECharts折线图要求数据为[ [x1,y1], [x2,y2], ... ]或{x: [...], y: [...]}格式。该函数做了三件事1.时间戳标准化遍历所有日期键用datetime.strptime(date_str, %Y-%m-%d)转为datetime对象再格式化为Jan 01供X轴显示2.指标分离将confirmed/cured/dead分别提取为独立数组确保三条折线数据长度严格一致3.空值填充若某日某省数据缺失自动补0而非跳过避免图表出现断裂。def get_daily_summary(): dates sorted(RAW_DATA.keys()) confirmed [] cured [] dead [] for date in dates: province_data RAW_DATA[date].get(national, {}) confirmed.append(province_data.get(confirmed, 0)) cured.append(province_data.get(cured, 0)) dead.append(province_data.get(dead, 0)) return { dates: [d[5:] for d in dates], # 取YYYY-MM-DD后5位得MM-DD confirmed: confirmed, cured: cured, dead: dead }get_province_heatmap()地理编码映射第60-95行热力图数据要求[{name: 广东, value: 123}, ...]但原始数据中省份名为拼音guangdong或英文Guangdong。该函数内置映射表PROVINCE_MAP { guangdong: 广东, beijing: 北京, shanghai: 上海, # ... 全部34个省级行政区 }并调用pypinyin库将拼音转中文备用方案确保即使数据源用拼音命名前端也能正确显示。load_china_geojson()地图数据预加载第97-122行为避免每次请求都读取static/js/china.json该函数在模块导入时一次性加载并缓存CHINA_GEOJSON None def load_china_geojson(): global CHINA_GEOJSON if CHINA_GEOJSON is None: with open(static/js/china.json, r, encodingutf-8) as f: CHINA_GEOJSON json.load(f) return CHINA_GEOJSON这种单例模式将地图JSON加载耗时从每次请求的5ms降至0对高频访问的热力图至关重要。注意事项utils.py中所有函数必须返回纯Python数据结构dict/list/int/str严禁返回Pandas DataFrame或NumPy数组——ECharts无法解析这些对象jsonify()会直接报错TypeError: Object of type DataFrame is not JSON serializable。3.3 前端渲染层main.html与ECharts初始化的黄金组合templates/main.html是项目的视觉心脏全文386行其精妙之处在于将ECharts配置与HTML结构解耦同时保持极致简洁结构设计第1-85行页面采用经典三栏布局-div idchart-line容纳全国趋势折线图-div idchart-bar容纳省份对比柱状图-div idchart-map容纳中国地图热力图每个容器均设固定宽高stylewidth: 100%; height: 400px;避免ECharts因父容器尺寸为0导致渲染失败——这是新手踩坑最多的地方。ECharts初始化第87-386行核心逻辑封装在initCharts()函数中采用“先创建实例再设置option”的两阶段模式// 1. 创建实例必须在DOM元素存在后执行 const lineChart echarts.init(document.getElementById(chart-line)); const barChart echarts.init(document.getElementById(chart-bar)); const mapChart echarts.init(document.getElementById(chart-map)); // 2. 统一设置全局主题防止单个图表配置重复 echarts.registerTheme(dark, { backgroundColor: #1a1a2e, textStyle: { color: #e6e6e6 } }); lineChart.setOption(getLineOption(), { theme: dark }); // 3. 定义option生成函数解耦配置逻辑 function getLineOption() { return { tooltip: { trigger: axis }, legend: { data: [确诊, 治愈, 死亡] }, xAxis: { type: category, data: [] }, // 空数组后续AJAX填充 yAxis: { type: value }, series: [ { name: 确诊, type: line, data: [] }, { name: 治愈, type: line, data: [] }, { name: 死亡, type: line, data: [] } ] }; }这种设计带来两大好处一是option可复用getBarOption()与getLineOption()共享大部分配置二是便于调试——在浏览器控制台直接调用getLineOption()即可看到完整配置结构。数据加载与图表联动第210-320行所有图表数据通过fetch()异步加载关键技巧在于错误隔离与加载状态管理Promise.all([ fetch(/api/daily).then(r r.json()), fetch(/api/province).then(r r.json()), fetch(/api/heatmap).then(r r.json()) ]).then(([daily, province, heatmap]) { // 成功时统一更新三个图表 lineChart.setOption({ xAxis: { data: daily.dates }, series: [...] }); barChart.setOption({ xAxis: { data: province.names }, series: [...] }); mapChart.setOption({ series: [{ data: heatmap }] }); }).catch(err { console.error(数据加载失败:, err); document.getElementById(loading).innerText 数据加载失败请刷新重试; });用Promise.all()确保三个接口全部成功才更新图表避免出现“折线图有数据柱状图空白”的割裂体验。而catch块将错误导向用户可见的提示而非静默失败。实操心得在Chrome开发者工具中Network标签页可监控所有/api/*请求。若某个图表空白优先检查对应接口是否返回200状态码及有效JSON。常见错误包括后端jsonify()传入None、前端fetch()未加await导致data为Promise对象、ECharts容器DOM元素ID拼写错误如chart-lin少个e。4. 完整实操流程与关键环节实现4.1 一分钟环境搭建从零到首页显示以下步骤经实测可在Windows/macOS/Linux三端复现全程无需管理员权限步骤1确认Python环境# 检查Python版本需3.8 python --version # 若未安装从python.org下载安装包勾选Add Python to PATH注意不要使用系统自带Python如macOS的/usr/bin/python它通常为2.7版本且权限受限。步骤2克隆项目并进入目录# 解压下载的ZIP包或使用git若已配置 git clone https://github.com/xxx/covid-viz.git cd covid-viz此时目录结构应与输入描述完全一致app.py,utils.py,templates/,static/等同级存在。步骤3创建虚拟环境强烈推荐# 创建venvPython 3.3内置 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate.bat # macOS/Linux: source venv/bin/activate虚拟环境能隔离项目依赖避免与系统其他Python项目冲突。若跳过此步后续pip install可能污染全局环境。步骤4安装依赖# 查看requirements.txt内容确认无敏感包 cat requirements.txt # 安装输出应显示Successfully installed flask-2.3.3 pyecharts-2.0.3等 pip install -r requirements.txtrequirements.txt中关键依赖为-Flask2.3.3稳定版避免新版breaking change-pyecharts2.0.3虽项目主要用原生ECharts JS但此包提供地图JSON便捷下载pip install echarts-china-provinces-pypkg-pypinyin0.48.0用于拼音转中文备用方案步骤5启动服务# 执行app.py注意不是python app.py而是flask命令 set FLASK_APPapp.py set FLASK_ENVdevelopment flask run # 或直接运行效果相同 python app.py终端将输出* Serving Flask app app.py * Debug mode: on * Running on http://127.0.0.1:5000 Press CTRLC to quit步骤6浏览器访问打开Chrome/Firefox访问http://127.0.0.1:5000。首次加载可能需5-8秒ECharts JS文件较大成功后应看到三张图表- 顶部折线图显示“确诊/治愈/死亡”三条曲线- 中部柱状图显示各省累计确诊数- 底部中国地图呈现热力分布颜色越深表示数值越高排查技巧若页面空白按F12打开开发者工具切换到Console标签页查看是否有Failed to load resource错误。常见原因static/js/echarts.min.js路径错误检查main.html中script src/static/js/echarts.min.js是否与实际文件位置匹配若报Access to fetch at http://127.0.0.1:5000/api/daily from origin null has been blocked说明HTML是双击打开的file://协议必须通过flask run启动的服务器访问。4.2 数据替换实战用你自己的疫情数据替换示例数据项目内置数据位于utils.py开头的RAW_DATA字典但真实场景中你需要注入自有数据。以下是三种替换方案按推荐度排序方案1JSON文件替换最推荐1. 准备你的数据文件data/my_covid.json格式如下json { 2023-10-01: {national: {confirmed: 500, cured: 480, dead: 5}, guangdong: {confirmed: 120}}, 2023-10-02: {national: {confirmed: 520, cured: 495, dead: 5}, guangdong: {confirmed: 125}} }2. 修改utils.py中load_data()函数指向新路径python def load_data(): # 注释掉原有DEFAULT_DATA_DICT # return DEFAULT_DATA_DICT with open(data/my_covid.json, r, encodingutf-8) as f: return json.load(f)3. 重启Flask服务数据即生效。方案2CSV导入适合Excel导出场景若你的数据在Excel中先导出为UTF-8编码的CSVdate,province,confirmed,cured,dead 2023-10-01,广东,120,115,0 2023-10-01,北京,85,82,0 2023-10-02,广东,125,118,0然后在utils.py中添加CSV解析函数import csv def load_csv_data(filepathdata/covid.csv): data {} with open(filepath, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: date row[date] province row[province] if date not in data: data[date] {} if province not in data[date]: data[date][province] {} data[date][province][confirmed] int(row[confirmed]) data[date][province][cured] int(row[cured]) data[date][province][dead] int(row[dead]) return data再将load_data()改为调用此函数。方案3API实时拉取进阶若数据源提供HTTP API如https://api.example.com/covid?date2023-10-01可改造get_daily_summary()import requests def get_daily_summary(): # 拉取最近30天数据 dates [(datetime.now() - timedelta(daysi)).strftime(%Y-%m-%d) for i in range(30)] all_data [] for date in dates: resp requests.get(fhttps://api.example.com/covid?date{date}) if resp.status_code 200: all_data.append(resp.json()) # 合并数据逻辑... return processed_data注意事项requests需添加到requirements.txt生产环境务必添加超时timeout5和重试机制避免API不可用导致整个服务挂起。4.3 图表定制化修改主题、颜色与交互行为ECharts的灵活性体现在其配置项的丰富性。以下是最常用定制场景修改全局主题色找到main.html中getLineOption()函数修改series配置series: [ { name: 确诊, type: line, data: [], itemStyle: { color: #e74c3c }, // 红色 smooth: true // 启用平滑曲线 }, { name: 治愈, type: line, data: [], itemStyle: { color: #2ecc71 }, // 绿色 areaStyle: {} // 添加面积填充 } ]颜色值可参考Material Design调色板确保对比度足够如红绿搭配需避免色盲用户无法区分。启用地图下钻交互当前热力图点击省份无反应。添加下钻功能// 在mapChart初始化后 mapChart.on(click, function (params) { if (params.componentType series) { const provinceName params.name; // 请求该省份详情 fetch(/api/detail/${provinceName}) .then(r r.json()) .then(data { // 更新折线图显示该省数据 lineChart.setOption({ title: { text: ${provinceName}疫情趋势 }, series: [ { name: 确诊, data: data.confirmed }, { name: 治愈, data: data.cured } ] }); }); } });此时点击地图任意省份上方折线图将自动切换为该省数据。添加数据导出按钮在main.html的body底部添加按钮button onclickexportData()导出当前图表数据/button script function exportData() { const data lineChart.getOption(); const blob new Blob([JSON.stringify(data, null, 2)], {type: application/json}); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download covid-data.json; a.click(); } /script点击按钮即可下载当前折线图的完整配置与数据便于离线分析。5. 常见问题与排查技巧实录5.1 启动失败类问题速查表现象可能原因排查命令/方法解决方案终端报错ModuleNotFoundError: No module named flask虚拟环境未激活或pip安装失败which pipmacOS/Linux或where pipWindows确认pip路径pip list \| findstr flask重新执行pip install flask若权限不足加--user参数浏览器显示This site can’t be reachedFlask服务未启动或端口被占用netstat -ano \| findstr :5000Windows或lsof -i :5000macOS/Linux杀死占用进程taskkill /PID PID /FWindows或kill -9 PIDmacOS/Linux或改用其他端口flask run --port 5001页面空白Console报Uncaught ReferenceError: echarts is not definedECharts JS文件未正确加载在浏览器地址栏直接访问http://127.0.0.1:5000/static/js/echarts.min.js检查static/js/目录是否存在该文件若为404确认main.html中script标签路径是否为/static/js/echarts.min.js注意开头斜杠折线图X轴显示undefinedutils.py中get_daily_summary()返回的dates字段为空在app.py中get_daily_data()路由里添加print(data)调试检查RAW_DATA字典是否为空确认日期键格式为YYYY-MM-DD非YYYY/MM/DD5.2 图表渲染类问题深度解析问题热力图颜色全部为浅蓝无深色区域-根因分析ECharts热力图默认颜色映射范围是数据最小值到最大值。若所有省份数值都在100-150之间颜色梯度会压缩在浅蓝色区间。-解决方案在getMapOption()中手动指定visualMap范围javascript visualMap: { min: 0, max: 1000, // 设定合理上限如全国最高值 calculable: true, inRange: { color: [#bfefff, #40c4ff, #0091ea, #0060b2, #003782] } }此时数值0-200显示浅蓝800-1000显示深蓝梯度清晰可见。问题柱状图省份名称重叠显示不全-根因分析X轴标签过长如“内蒙古自治区”且数量过多34个ECharts默认不旋转标签。-解决方案在getBarOption()中配置xAxisjavascript xAxis: { type: category, data: provinceNames, axisLabel: { rotate: 45, // 向右旋转45度 interval: 0, // 强制显示所有标签 fontSize: 12 } }若仍重叠可改用interval: 1隔一个显示一个或启用formatter截断文字javascript formatter: function(value) { return value.length 4 ? value.substring(0, 4) ... : value; }问题地图点击后折线图不更新Console报Cannot read property setOption of undefined-根因分析lineChart实例在mapChart.on(click)回调中未定义因变量作用域问题。-解决方案将图表实例声明为全局变量不推荐或在闭包中传递javascript// 在initCharts()函数外声明let lineChart, barChart, mapChart;function initCharts() {lineChart echarts.init(…);barChart echarts.init(…);mapChart echarts.init(…);mapChart.on(click, function(params) { // 此处lineChart已定义 lineChart.setOption({...}); });}5.3 部署上线注意事项虽然项目定位为本地演示但若需部署到云服务器如阿里云ECS需注意生产环境禁用debug模式app.py中app.run(debugTrue)必须删除改用Gunicornbash pip install gunicorn gunicorn -w 4 -b 0.0.0.0:8000 app:app-w 4启动4个工作进程-b绑定到8000端口。静态资源托管优化Flask自带静态文件服务不适合高并发。建议用Nginx反向代理nginx location /static/ { alias /path/to/your/project/static/; expires 1h; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; }数据安全加固若接入真实API务必在utils.py中添加请求头伪装防爬虫拦截python headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } requests.get(url, headersheaders, timeout5)最后分享一个小技巧在README.md中补充“快速验证清单”包含三行命令bash python -c import flask; print(flask.__version__) # 检查Flask curl -s http://127.0.0.1:5000/api/daily \| head -20 # 检查API ls static/js/echarts.min.js # 检查JS文件新人按顺序执行5分钟内即可确认环境是否完备大幅降低入门门槛。本文还有配套的精品资源点击获取简介用Python Flask搭后端服务ECharts做前端图表渲染直接跑起来就能看疫情数据变化。支持折线图展示每日确诊/治愈/死亡趋势柱状图对比各地区累计数据地图热力图呈现区域分布强度。所有代码结构清晰app.py启动服务utils.py处理数据转换逻辑templates里放main.html主页面static目录下分js/css/img管理前端资源picture和font存放自定义图片与字体。示例数据用Python字典或JSON文件模拟不依赖数据库适合本地快速运行、教学演示或疫情复盘分析。附带README.md详细说明部署步骤疫情可视化平台.png是实际界面截图.idea和COV.iml配置文件支持PyCharm一键导入.pyc预编译文件已内置requirements.txt列明依赖包.gitignore规范版本控制整体开箱即用。本文还有配套的精品资源点击获取