使用python-flask和echarts完成数据可视化
使用python-flask和echarts完成数据可视化
一、工具介绍
flask是一个Python实现的Web开发微框架,类似的还有django/dash等。这篇文章是一个讲述如何用它实现数据可视化的详细教程。
echarts是一个纯JavaScript的数据可视化图标库,兼容绝大部分的浏览器。
本文利用Python Flask框架与echarts相结合,展示了一个从建立数据库,到Python封装数据库信息为json格式数据,前端接受json格式数据并执行响应,最终展示数据的到页面的一个完整的流程。以下是项目的文件结构:
项目结构
Goods文件夹
|-static
||-js
|||-jquery.min.js
|||-echarts.js
||-css
|||-style.css
|-templates html在该文件夹下
||-index.html
|-app.py
二、代码讲解
1. sqlalchemy
使用 SQLAlchemy 的首要原因是,它将你的代码从底层数据库及其相关的 SQL 特性中抽象出来。下面是sqlalchemy的数据库链接与查询(注意需要提前构建好基类才能进行查询)
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, INTEGER, String
from sqlalchemy.orm import sessionmaker
HOST_NAME = 'localhost' # 数据库所在服务器ip,因为我是本地数据库所以这里是127.0.0.1
HOST_PORT = '3306' # 数据库端口
DATABASE_NAME = 'flasktest' # 数据库名
USER_NAME = 'root' # 链接数据的用户名
PWD = 'root' # 链接数据库的密码
DB_URI = 'mysql+pymysql://{0}:{1}@{2}:{3}/{4}?charset=utf8'.format(USER_NAME,PWD,HOST_NAME,HOST_PORT,DATABASE_NAME)
# 创建数据库连接
engine = create_engine(DB_URI)
# 操作数据库基类
Base = declarative_base(engine)
class UserModule(Base):
"""
创建一个用户的数据模型
"""
__tablename__ = 'flasktest'
id = Column(INTEGER, primary_key=True, autoincrement=True, comment='用户id')
Goods_name = Column(String(30), nullable=False, unique=True, comment='商品名')
Goods_sales_volume = Column(INTEGER, nullable=False, comment='产量')
Goods_inventory = Column(INTEGER, nullable=False, comment='销量')
def __repr__(self):
return 'User(id={id}, Goods_name={Goods_name}, Goods_sales_volume={Goods_sales_volume}, money={Goods_inventory})'.format(
id=self.id, Goods_name=self.Goods_name, Goods_sales_volume=self.Goods_sales_volume, Goods_inventory=self.Goods_inventory)
def get_db():
Session = sessionmaker(bind=engine)
session = Session() # 实例化了一个会话(或叫事务),之后的所有操作都是基于这个对象的
inventQuery = session.query(UserModule.Goods_inventory).all()
scores = [c[0] for c in inventQuery]
salesQuery = session.query(UserModule.Goods_sales_volume).all()
money = [c[0] for c in salesQuery]
nameQuery = session.query(UserModule.Goods_name).all()
names = [c[0] for c in nameQuery]
return names, scores, money
2. flask框架
render_template
返回对应的html链接。
两个viewdata路由用于分别对两个表格传输数据,完成后端对前端的数据回传。
jsonify
将dict数据打包成json格式,方便前端读取。
from flask import Flask, request
from flask import render_template
from flask import jsonify
@app.route('/')
def index():
return render_template("index.html")
# 前端会到这个url请求数据,通过ajax动态刷新数
@app.route("/viewdata", methods=["GET"])
def viewdata():
if request.method == "GET":
names, scores, money = get_db()
return jsonify(name = [x for x in names],
score = [x for x in scores],
money = [x for x in money])
@app.route("/viewdata2", methods=["GET"]) # url匹配ajax
def viewdata2():
if request.method == "GET":
names, scores, money = get_db()
return jsonify(name = [x for x in names],
score = [x for x in money],
money = [x for x in scores])
if __name__ == '__main__':
app.run(debug=True, port=5000) #port:端口,默认端口为5000,访问地址为localhost:5000/
3. index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ECharts3 Ajax</title>
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='echarts.js') }}"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" >
</head>
<body>
<!--为ECharts准备一个具备大小(宽高)的Dom-->
<div id="main" style="height:500px;border:1px solid #ccc;padding:10px;"></div>
<div id="example" style="height:500px;border:1px solid #ccc;padding:10px;"></div>
<div id="view"></div>
<script type="text/javascript">
var myChart = echarts.init(document.getElementById('main'));
var exChart = echarts.init(document.getElementById('example'));
// 显示标题,图例和空的坐标轴
myChart.setOption({
title: {
text: '异步数据加载示例'
},
tooltip: {},
legend: {
data:['score','money']
},
xAxis: {
data: []
},
yAxis: {},
series: [{
name: 'score',
type: 'line',
data: []
},{
name: 'money',
type: 'bar',
data: []
}]
});
myChart.showLoading(); // 显示加载动画
// 从url请求数据,异步加载
$.get('/viewdata').done(function (data) {
myChart.hideLoading(); // 隐藏加载动画
// 填入数据
myChart.setOption({
xAxis: {
data: data.name
},
series: [{
name: 'score', // 根据名字对应到相应的系列
data: data.score.map(parseFloat) // 转化为数字(注意map)
},{
name: 'money',
data: data.money.map(parseFloat)
}]
});
});
// 第二个表格
exChart.setOption({
title: {
text: '异步数据加载示例'
},
tooltip: {},
legend: {
data:['score','money']
},
xAxis: {
data: []
},
yAxis: {},
series: [{
name: 'score',
type: 'line',
data: []
},{
name: 'money',
type: 'bar',
data: []
}]
});
exChart.showLoading(); // 显示加载动画
// 异步加载数据
$.get('/viewdata2').done(function (data) {
exChart.hideLoading(); // 隐藏加载动画
// 填入数据
exChart.setOption({
xAxis: {
data: data.name
},
series: [{
name: 'score', // 根据名字对应到相应的系列
data: data.score.map(parseFloat) // 转化为数字(注意map)
},{
name: 'money',
data: data.money.map(parseFloat)
}]
});
});
</script>
</body>
</html>
代码有点多,选重点部分讲。第一部分如下,主要是读取了div容器,然后设置好不需要动态加载的图表的横纵坐标轴等信息。
<script type="text/javascript">
var myChart = echarts.init(document.getElementById('main'));
// 显示标题,图例和空的坐标轴
myChart.setOption({
title: {
text: '异步数据加载示例'
},
tooltip: {},
legend: {
data:['score','money']
},
xAxis: {
data: []
},
yAxis: {},
series: [{
name: 'score',
type: 'line',
data: []
},{
name: 'money',
type: 'bar',
data: []
}]
});
第二部分是重点,主要负责通过ajax技术动态的接收数据库回传的data,实时地加载到前端的echarts组件中。$.get('/viewdata').done(function (data))
表示从url请求数据,然后执行函数。其中$.get(url,data,success(response,status,xhr),dataType)
是简写的ajax函数,等价于如下代码。
$.ajax({
url: url,
data: data,
success: success,
dataType: dataType
});
myChart.showLoading(); // 显示加载动画
// 从url请求数据,异步加载
$.get('/viewdata').done(function (data) {
myChart.hideLoading(); // 隐藏加载动画
// 填入数据
myChart.setOption({
xAxis: {
data: data.name
},
series: [{
name: 'score', // 根据名字对应到相应的系列
data: data.score.map(parseFloat) // 转化为数字(注意map)
},{
name: 'money',
data: data.money.map(parseFloat)
}]
});
});
因此以上内容也可以改写如下:
function getData() {
$.ajax({
cache: false,
type: "GET",
url: "/viewdata", //把表单数据发送到/viewdata
data: {}, // 发送的数据,这里是前端接收数据,所以是空
dataType : "json", //返回数据形式为json
async: false,
error: function(request) {
alert("发送请求失败!");
},
success: function(result) {
myChart.hideLoading(); // 隐藏加载动画
// console.log(result);
myChart.setOption({
xAxis: {
data: result.name
},
series: [{
name: 'score', // 根据名字对应到相应的系列
data: result.score.map(parseFloat) // 转化为数字(注意map)
},{
name: 'money',
data: result.money.map(parseFloat)
}]
});
}
});
};
//页面加载完毕,发送ajax请求
$(document).ready(function () {
getData();
});
4. 前后端交互
讲到这里,笔者觉得有必要记录以下前后端交互的操作方法。Web应用基于ajax进行前后端数据交互,一般利用Get或者Post方式来实现。比较流行的做法是前端提交表单数据,后端处理完毕后返回Json数据到前端进行显示。以下是前端发送数据到后端的流程代码。
4.1 前端发送到后端
get请求:
前端:
<script src="{{url_for('static',filename='js/jquery.js')}}"></script>
<script>
var data={
'name':'kikay',
'age':18
}
$.ajax({
type:'GET',
url:'{{url_for("test_get")}}',
data:data,
dataType:'json',//希望服务器返回json格式的数据
success:function(data){
alert(JSON.stringify(data));
alert(data['test'])
}
});
</script>
后端:request.args.get
获取get请求数据
@test.route('/test_get/',methods=['POST','GET'])
def test_get():
#获取Get数据
name=request.args.get('name')
age=int(request.args.get('age'))
#返回
if name=='kikay' and age==18:
return jsonify({'result':'ok'})
else:
return jsonify({'result':'error'})
post请求:
前端:
<script src="{{url_for('static',filename='js/jquery.js')}}"></script>
<script>
var data={
'name':'kikay',
'age':18
}
$.ajax({
type:'POST',
url:'{{url_for("test_post")}}',
data:data,
dataType:'json',//希望服务器返回json格式的数据
success:function(data){
alert(JSON.stringify(data));
}
});
</script>
后端:request.form.get
获取get请求数据
@test.route('/test_post/',methods=['POST','GET'])
def test_post():
#获取POST数据
name=request.form.get('name')
age=int(request.form.get('age'))
#返回
if name=='kikay' and age==18:
return jsonify({'result':'ok'})
else:
return jsonify({'result':'error'})
4.2 后端发送给前端
后端发送数据给前端主要通过return回dic/json。
@app.route("/")
def index():
dic={}
dic['name']='xiaomin'
dic['num']=1
return render_template('index.html',data=dic)
前端通过接收数据只需要把ajax相关代码中的data设置为空。
$.ajax({
type:'GET',
url:'{{url_for("test_get")}}',
data:{}, //不发送data
dataType:'json',//希望服务器返回json格式的数据
success:function(data){ //从这里接收到data
alert(JSON.stringify(data));
alert(data['test'])
}
5. CSS样式设置
最后就是调整两个div容器的位置和样式,插入到html中。
三、结果
数据仅供显示,无任何意义。运行成功后界面如下:
四、展望
-
文件分层:当项目较大的时候,可以把数据的查询功能(sql)放在单独的脚本里utils.py,在app.py里只负责调用接口和创建路由。同样当js样式较多时候,可以把ajax请求放在controller.js中中转,在其他js文件里渲染静态样式。
- 还可以通过china.js/lefatlet.js等第三方库添加地图进行显示。
- 前后端交互的原理相当重要,前后端数据的发送和接收方法值得注意。
- 后续还需部署服务,以实现在别的电脑上进行访问。
- 今天做的只是一个简单样例,后续可以实现https://blog.csdn.net/hxxjxw/article/details/105336981中的疫情可视化系统完成完整系统的设计。