Fork me on GitHub

使用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中。

三、结果

​ 数据仅供显示,无任何意义。运行成功后界面如下:image-20210228012018542

四、展望
  1. 文件分层:当项目较大的时候,可以把数据的查询功能(sql)放在单独的脚本里utils.py,在app.py里只负责调用接口和创建路由。同样当js样式较多时候,可以把ajax请求放在controller.js中中转,在其他js文件里渲染静态样式。

    1. 还可以通过china.js/lefatlet.js等第三方库添加地图进行显示。
    2. 前后端交互的原理相当重要,前后端数据的发送和接收方法值得注意。
    3. 后续还需部署服务,以实现在别的电脑上进行访问。
    4. 今天做的只是一个简单样例,后续可以实现https://blog.csdn.net/hxxjxw/article/details/105336981中的疫情可视化系统完成完整系统的设计。
posted @ 2021-02-28 01:33  Rser_ljw  阅读(4808)  评论(0编辑  收藏  举报