简介:本资源是一份完整的数据可视化课程结课作业报告,面向计算机、信息工程等专业本科生及Web开发初学者,聚焦真实业务场景下的用电量趋势分析与可视化落地。报告以虚构但具代表性的日用电量数据集为载体,系统实践了MySQL数据库设计(含time/powers双字段建表与Python批量插入)、Flask轻量级后端搭建(实现数据库查询与HTML模板渲染)、ECharts动态图表嵌入(柱状图/折线图展示时段用电变化)及前端交互调试(解决中文乱码、图表不显示等典型问题)。资源为单个543KB PDF文件,内容结构清晰,涵盖项目摘要、数据库实现、Flask路由逻辑、前端集成、问题排查与经验总结共5大模块,附有完整代码片段与执行流程说明。目前已有136人学习下载,可直接用于课程作业参考、技术栈整合实践或Web可视化入门复盘。
1. 一个用 Flask + ECharts 实现的用电量可视化小系统,为什么值得拆解?
这不是一个炫技型大屏项目,而是一份2017年计算机专业本科生的结课作业——但恰恰是这种“轻量、真实、有边界”的工程实践,最能暴露数据可视化落地时的真实断点:数据库字段只有 time 和 powers 两列,Flask 路由仅暴露/和/powers两个接口,前端用 jQuery 的$.get拉取 JSON,ECharts 渲染折线图和柱状图。它没用 Vue/React,没接 WebSocket,没做权限控制,甚至没做响应式适配。但正因如此,它把「从原始数据到浏览器图表」这条链路里每个环节的依赖、编码约定、隐含假设都摊开在了明处。比如:MySQL 连接必须显式指定charset='utf8',否则中文插入会失败;HTML 文件若被 Notepad++ 以 ANSI 编码保存,整个页面中文就全乱码;ECharts 的data.powers.map(parseFloat)这一行,本质是在补救后端返回的字符串型数值——这些不是理论漏洞,而是你搭第一个可视化服务时,90% 会卡住的实操细节。适合刚学完 Python 基础、想用最小成本跑通 Web 可视化闭环的开发者,也适合带新人时用来讲清「为什么模板引擎要配 Jinja2」「为什么异步请求必须设 Content-Type」。
2. 数据库与 Flask 后端:如何让两行数据变成可查询的 API
2.1 MySQL 表结构设计与 Python 初始化脚本解析
项目采用最简表结构:单表epower,仅含time(VARCHAR(20))和powers(VARCHAR(20))两字段。这种设计看似粗糙,实则精准匹配用电量场景——时间粒度为小时(如"00:00"),用电量为整数字符串(如"1250")。关键在于初始化逻辑的健壮性:create_data.py在建表前执行DROP TABLE IF EXISTS epower,避免重复运行报错。建表语句明确指定字符集:
CREATE TABLE epower(time VARCHAR(20), powers VARCHAR(20)) DEFAULT CHARSET=utf8;提示:
DEFAULT CHARSET=utf8不可省略。MySQL 5.7 默认字符集为latin1,若不显式声明,后续插入中文时间(如"凌晨0点")会直接报错或存为问号。
插入数据使用executemany批量写入,比循环execute效率高一个数量级:
datas = [("00:00", "1250"), ("01:00", "1180"), ..., ("23:00", "1420")] cur.executemany('INSERT INTO epower VALUES (%s,%s)', datas)此处%s占位符是 PyMySQL 的安全写法,自动处理字符串转义,杜绝 SQL 注入。若直接拼接字符串(如'INSERT ... VALUES ("' + time + '","' + power + '")'),遇到含单引号的时间描述(如"上午10:00")将导致语法错误。
2.2 Flask 路由与数据库查询的完整链路
Flask 作为胶水层,核心任务是将数据库查询结果转换为前端可消费的 JSON 格式。app.py中两个路由构成最小闭环:
from flask import Flask, render_template, request, jsonify import pymysql app = Flask(__name__) @app.route("/", methods=["GET"]) def index(): return render_template("index.html") # 渲染 templates/index.html @app.route("/powers", methods=["GET"]) def powers(): conn = pymysql.connect( host='localhost', user='root', passwd='root', db='stu2', # 数据库名需提前创建 port=3306, charset='utf8' # 关键!必须与建表字符集一致 ) cur = conn.cursor() cur.execute('SELECT * FROM epower') # 查询全部记录 res = cur.fetchall() # 返回元组列表:[('00:00','1250'), ('01:00','1180')] cur.close() conn.close() # 将查询结果拆解为两个独立列表,适配 ECharts 数据格式 time_list = [x[0] for x in res] # ['00:00', '01:00', ...] power_list = [x[1] for x in res] # ['1250', '1180', ...] return jsonify(time=time_list, powers=power_list)参数说明与常见陷阱
| 参数 | 作用 | 修改建议 |
|---|---|---|
host='localhost' | MySQL 服务地址 | 若 MySQL 运行在 Docker 容器中,需改为容器 IP 或host.docker.internal |
db='stu2' | 数据库名 | 必须提前用CREATE DATABASE stu2 CHARACTER SET utf8;创建,且字符集必须为utf8 |
charset='utf8' | 连接字符集 | 不可写成utf-8,PyMySQL 仅识别utf8(MySQL 的 utf8mb3) |
jsonify(...) | 自动设置Content-Type: application/json | 若前端 jQuery 请求未设dataType: 'json',需手动JSON.parse()解析 |
注意:
pymysql.connect()未设置autocommit=True,但本项目无写操作,故无需conn.commit()。若后续增加数据更新功能,必须显式调用commit(),否则修改不生效。
2.3 Flask 模板渲染与静态资源路径配置
render_template("index.html")要求文件位于templates/目录下,而 ECharts JS 文件需放在static/目录(Flask 默认静态文件路径)。典型目录结构如下:
project/ ├── app.py ├── create_data.py ├── static/ │ └── echarts.min.js # ECharts 官方 CDN 下载的压缩版 ├── templates/ │ └── index.html └── data/ # (可选)存放原始 CSV 数据index.html中引用 ECharts 的标准写法:
<script src="{{ url_for('static', filename='echarts.min.js') }}"></script>url_for('static', ...)是 Flask 提供的安全路径生成函数,自动拼接/static/前缀,避免硬编码路径导致部署失败。若直接写<script src="/static/echarts.min.js">,当应用部署在子路径(如https://example.com/myapp/)时,资源将 404。
3. 前端实现:ECharts 图表渲染与 jQuery 异步数据加载
3.1 HTML 页面结构与 ECharts 初始化
templates/index.html骨架需包含唯一 DOM 容器和初始化脚本:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>用电量可视化</title> <script src="{{ url_for('static', filename='echarts.min.js') }}"></script> </head> <body> <!-- 图表容器,必须设置宽高 --> <div id="main" style="width: 800px; height: 400px;"></div> <script type="text/javascript"> // 初始化 ECharts 实例 var myChart = echarts.init(document.getElementById('main')); // 显示加载动画(可选) myChart.showLoading(); // 发起异步请求获取数据 $.get('/powers', function (data) { myChart.hideLoading(); // 配置图表选项 myChart.setOption({ title: { text: '日用电量分布' }, tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: data.time }, yAxis: { type: 'value' }, series: [{ name: '用电量', type: 'line', // 或 'bar' 切换图表类型 data: data.powers.map(parseFloat) // 字符串转数字 }] }); }); </script> </body> </html>关键细节说明
div#main的style="width: 800px; height: 400px;"不可省略。ECharts 初始化时需读取容器尺寸,若 CSS 未定义宽高,图表将渲染为 0×0 区域。data.powers.map(parseFloat)是必要转换:后端返回的powers是字符串数组(["1250","1180"]),ECharts 数值轴要求number[],否则图表空白。tooltip: { trigger: 'axis' }设置为坐标轴触发,鼠标悬停时显示该时刻完整数据,比item触发更符合时序分析场景。
3.2 ECharts 折线图与柱状图双模式切换实现
报告中展示了折线图和柱状图两种视图。实际只需修改series.type即可切换,但需保证 HTML 中存在两个独立容器:
<!-- 折线图容器 --> <div id="line-chart" style="width: 800px; height: 400px;"></div> <!-- 柱状图容器 --> <div id="bar-chart" style="width: 800px; height: 400px;"></div>对应 JavaScript 初始化:
// 折线图 var lineChart = echarts.init(document.getElementById('line-chart')); lineChart.setOption({ series: [{ type: 'line', data: data.powers.map(parseFloat) }] }); // 柱状图 var barChart = echarts.init(document.getElementById('bar-chart')); barChart.setOption({ series: [{ type: 'bar', data: data.powers.map(parseFloat) }] });ECharts 常用配置项对照表
| 配置项 | 作用 | 本项目取值 | 备注 |
|---|---|---|---|
xAxis.type | X 轴类型 | 'category' | 适用于离散时间点(小时) |
yAxis.type | Y 轴类型 | 'value' | 连续数值轴,自动计算刻度 |
series.name | 系列名称 | '用电量' | 影响图例和 tooltip 显示 |
series.type | 图表类型 | 'line'或'bar' | 支持'pie','scatter'等 |
tooltip.formatter | 提示框格式 | {a} <br/>{b} : {c} | {a}=系列名,{b}=X轴值,{c}=Y轴值 |
提示:若需支持中文时间(如
"上午8点"),xAxis.data必须为字符串数组,且xAxis.type保持'category'。若误设为'time',ECharts 会尝试解析为时间戳,导致 X 轴显示为 Unix 时间。
3.3 jQuery 异步请求的容错与调试技巧
原代码$.get('/powers', function(data){...})未处理请求失败场景。生产环境必须添加错误回调:
$.ajax({ url: '/powers', type: 'GET', dataType: 'json', success: function(data) { myChart.setOption({ /* 渲染逻辑 */ }); }, error: function(xhr, status, error) { console.error('数据加载失败:', status, error); myChart.setOption({ title: { text: '数据加载失败,请检查后端服务' }, series: [{ data: [] }] }); } });调试时可直接在浏览器控制台执行:
# 检查 Flask 是否正常响应 curl http://127.0.0.1:5000/powers # 验证返回 JSON 格式(应为 {"time":["00:00",...],"powers":["1250",...]}) curl -H "Accept: application/json" http://127.0.0.1:5000/powers | python -m json.tool若返回{"error":"Not Found"},说明 Flask 路由未注册或 URL 路径错误;若返回空内容,检查pymysql连接是否成功、SELECT语句是否查到数据。
4. 中文乱码与图表不显示:两个高频问题的根因定位与修复
4.1 网页中文乱码的三层编码校验法
乱码本质是编码链断裂,需逐层验证:
| 层级 | 检查点 | 正确状态 | 修复命令 |
|---|---|---|---|
| 文件存储层 | index.html文件实际编码 | UTF-8 without BOM | Notepad++ → 编码 → 转为 UTF-8 |
| HTTP 传输层 | 响应头Content-Type | text/html; charset=utf-8 | Flask 中@app.route无需额外设置,render_template自动注入 |
| HTML 声明层 | <meta charset="UTF-8"> | 存在于<head>中 | 确保标签闭合,且无其他冲突<meta> |
注意:若
index.html在 VS Code 中打开显示正常,但在浏览器中乱码,大概率是文件保存编码非 UTF-8。VS Code 右下角状态栏显示当前编码,点击可快速转换。
4.2 图表不显示的五步诊断清单
图表空白是前端最典型的“黑盒”问题,按此顺序排查:
- 检查容器尺寸:
document.getElementById('main').offsetWidth返回0?→ 补充style="width:100%;height:400px" - 验证 ECharts 加载:
typeof echarts在控制台输出undefined?→ 检查echarts.min.js路径是否 404(F12 → Network → Filterjs) - 确认数据格式:
console.log(data)查看/powers返回值 → 若为null或undefined,检查 Flask 路由是否 500 错误 - 检查数值类型:
data.powers[0]是字符串"1250"还是数字1250?→map(parseFloat)不可省略 - 排除 CSS 冲突:
#main是否被父元素display:none或visibility:hidden?→ Chrome DevTools → Elements → 检查 computed styles
典型修复案例:Chrome 与 Edge 渲染差异
报告提到“换浏览器图表正常显示”,这通常指向两个原因:
- CSS Flex/Grid 兼容性:若容器父元素使用
display: flex,旧版 Edge 对width:100%解析异常 → 改用固定像素宽高 - ECharts 版本兼容性:ECharts 5.x 移除了对 IE 的支持,但部分旧版 Chrome 扩展可能拦截
canvas渲染 → 在无痕窗口测试排除插件干扰
5. 从结课作业到企业级可视化:三个可立即落地的升级技巧
5.1 使用 SQLAlchemy 替代原生 PyMySQL,提升数据库可维护性
原项目直接拼接 SQL 字符串,扩展性差。引入 SQLAlchemy Core 可实现类型安全与连接池管理:
from sqlalchemy import create_engine, text from sqlalchemy.pool import QueuePool # 创建连接池(复用连接,避免频繁建立) engine = create_engine( 'mysql+pymysql://root:root@localhost:3306/stu2', poolclass=QueuePool, pool_size=5, max_overflow=10, echo=False # 设为 True 可打印 SQL 日志 ) # 安全查询(自动参数化) with engine.connect() as conn: result = conn.execute(text("SELECT * FROM epower WHERE time >= :start_time"), {"start_time": "08:00"}) data = result.fetchall()优势:
text()函数自动处理参数转义;连接池减少 TCP 握手开销;echo=True便于调试慢查询。
5.2 ECharts 配置复用:封装通用图表工厂函数
避免重复编写setOption,提取为可复用函数:
function createPowerChart(domId, chartType = 'line') { const chart = echarts.init(document.getElementById(domId)); chart.showLoading(); $.get('/powers', function(data) { chart.hideLoading(); chart.setOption({ tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: data.time }, yAxis: { type: 'value' }, series: [{ name: '用电量', type: chartType, data: data.powers.map(Number) // Number() 比 parseFloat() 更严格 }] }); }); return chart; // 返回实例便于后续调用 resize() } // 使用 const lineChart = createPowerChart('line-chart', 'line'); const barChart = createPowerChart('bar-chart', 'bar'); // 响应式适配(窗口大小变化时重绘) window.addEventListener('resize', () => { lineChart.resize(); barChart.resize(); });5.3 添加实时数据模拟:用 setInterval 替代静态数据
结课作业数据是静态的,但真实场景需模拟实时更新。在$.get外层加定时器:
function loadRealTimeData() { $.get('/powers', function(data) { // 更新图表数据(非全量重绘,提升性能) myChart.setOption({ series: [{ data: data.powers.map(Number) }] }); }); } // 每30秒拉取一次新数据 setInterval(loadRealTimeData, 30000); // 首次加载 loadRealTimeData();注意:若后端
/powers接口无缓存控制,浏览器可能返回 304(Not Modified),需在 Flask 中添加@app.after_request设置Cache-Control: no-cache。
本文还有配套的精品资源,点击获取