Flask+Echarts搭建全国疫情可视化大屏

目录

需求分析

项目实施

1.数据采集

2.搭建flask应用

3.可视化展示

第一板块

第二板块

第三板块

第四板块

4.添加定时任务

项目总结

本项目是基于flask+echarts搭建的全国疫情实时追踪的可视化大屏,主要涉及到的技术有爬虫,mysql数据库,flask框架,echarts图表。关于flask知识点,可学习另一篇文章Flask全套知识点从入门到精通,学完可直接做项目

最终效果如下:

Flask+Echarts搭建全国疫情可视化大屏

需求分析

从最终效果图可以看出,我们将屏幕分为4大板块(页面排布是左中右+上),第一板块是最上面的部分,包括大屏标题以及当前的实时时间;第二板块是最左边,上面的全国新增趋势折线图(新增确诊、治愈、死亡),下面是全国累计趋势折线图(累计确诊、治愈、死亡);第三板块是中间,上面是当天的一些数据,下面是全国累计确诊的疫情地图;第四板块是右边,上面是新增确诊人数Top前五的省份柱状图,下面是微博热搜话题的词云图。

开发工具:

vscode编辑器

python3.8

如果你在开发的过程中发现你编写的html或css文件在页面中没有更新,而是你上次编写的代码,也就是缓存的问题,这时候你需要在app.py中添加如下代码即可解决:

@app.after_request
def apply_caching(response):
    response.headers["Cache-Control"] = "no-cache"
    return response

项目实施

项目的最终文件目录结构如下:

Flask+Echarts搭建全国疫情可视化大屏

1.数据采集

首先,我们得要有数据才能进行展示,这里我们选择用爬虫来进行数据采集并保存到mysql数据库中,考虑到平台限制,这里就不方便展示爬虫代码,需要的评论留言或私信。

2.搭建flask应用

这里我们先搭建一个基础的flask应用

from flask import Flask,render_template

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False

@app.route('/')
def index():
    return render_template('main.html')

if __name__ == '__main__':
    app.run(debug=True)

接着,需要编写main.html页面(这里我就直接放最终的代码)


    全国疫情实时追踪

  全国疫情实时追踪

      累计确诊
      新增确诊
      累计治愈
      累计死亡

其次,我们还需要编写css来进行板块划分

main.css

body{
    margin: 0;
    background-color: #333;
}
.title{
    position: absolute;
    width: 40%;
    height: 10%;
    top: 0;
    left: 30%;
    color: white;
    font-size: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.l1{
    position: absolute;
    width: 30%;
    height: 45%;
    top: 10%;
    left: 0;
    background-color: aquamarine;
}
.l2{
    position: absolute;
    width: 30%;
    height: 45%;
    top: 55%;
    left: 0;
    background-color: blue;
}
.c1{
    position: absolute;
    width: 40%;
    height: 25%;
    top: 10%;
    left: 30%;
    /* background-color: blue; */
}
.num{
    width: 25%;
    float: left;
    display: flex;
    align-items: center;
    justify-content: center;
    color: gold;
    font-size: 16px;
}
.txt{
    width: 25%;
    float: left;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: "幼圆";
    color: whitesmoke;
    font-size: 14px;
}
.c2{
    position: absolute;
    width: 40%;
    height: 65%;
    top: 35%;
    left: 30%;
    /* background-color: whitesmoke; */
}
.r1{
    position: absolute;
    width: 30%;
    height: 45%;
    top: 10%;
    right: 0;
    background-color: burlywood;
}
.r2{
    position: absolute;
    width: 30%;
    height: 45%;
    top: 55%;
    right: 0;
    background-color: brown;
}
.tim{
    position: absolute;
    /* width: 30%; */
    height: 10%;
    top: 5%;
    right: 2%;
    /* background-color: blueviolet; */
    font-size: 20px;
    color: whitesmoke;
}

3.可视化展示

接下来我将按照4大板块进行介绍

第一板块

那个大屏标题文字在上面的html页面中有,这里就不说了。还有一个就是右上角的时间显示,这里我们需要编写一个获取时间的接口,然后通过ajax来发送请求进行调用.

utils.py

import time
import pymysql
import collections
import jieba
import re

def get_time():
    time_str = time.strftime('%Y{}%m{}%d{} %X ')
    return time_str.format('年','月','日')

app.py

import utils
@app.route('/time')
def time():
    return utils.get_time()

get_data.js

function gettime(){
    $.ajax({
        url:'/time',
        timeout:10000,//超时时间
        success:function(data){
            $('.tim').html(data)
        },
        error:function(data){

        }
    });
};

第二板块

第二板块是左边的两个图

流程步骤就是先从数据库中获取数据,在flask应用中编写接口,最后在页面中通过ajax来发送进行调用,这是图表展示的通用步骤,下面我就不再叙述了。

这里因为我们要多次从数据库获取数据,所以 我们先封装一下方法,便于后面获取数据

utils.py

def get_conn():
"""
    :return: 连接,游标
"""
    # 创建连接
    conn = pymysql.connect(host="127.0.0.1",
                           user="xxx", # 这里写你的mysql用户名
                           password="xxx", # 这里写你的mysql密码
                           db="yiqing",  # 这里写你的mysql中创建的数据库
                           charset="utf8")
    # 创建游标
    cursor = conn.cursor()# 执行完毕返回的结果集默认以元组显示
    return conn, cursor

def close_conn(conn, cursor):
    cursor.close()
    conn.close()

def query(sql,*args):
"""
    封装通用查询
    :param sql:
    :param args:
    :return: 返回查询到的结果,((),(),)的形式
"""
    conn, cursor = get_conn()
    cursor.execute(sql,args)
    res = cursor.fetchall()
    close_conn(conn, cursor)
    return res

左上的图:

utils.py

def get_l1_data():
    # 因为会更新多次数据,取时间戳最新的那组数据
    sql = '''
    SELECT ds,confirm_add,heal_add,dead_add
    FROM history
    '''
    res = query(sql)
    return res

app.py

from flask import jsonify
@app.route('/l1')
def get_l1_data():
    data = utils.get_l1_data()
    day,confirm_add,heal_add,dead_add = [],[],[],[]
    for item in data:
        day.append(item[0].strftime('%m-%d'))
        confirm_add.append(item[1])
        heal_add.append(item[2])
        dead_add.append(item[3])
    return jsonify({'day':day,'confirm_add':confirm_add,'heal_add':heal_add,'dead_add':dead_add})

get_data.js

function get_l1_data(){
    $.ajax({
        url:'/l1',
        success:function(data){
            ec_left1_Option.xAxis[0].data=data.day
            ec_left1_Option.series[0].data=data.confirm_add
            ec_left1_Option.series[1].data=data.heal_add
            ec_left1_Option.series[2].data=data.dead_add
            ec_left1.setOption(ec_left1_Option)
        },
        error:function(data){
        }
    });
}

ec_left1.js


var ec_left1 = echarts.init(document.getElementById('l1'), "dark");
var ec_left1_Option = {
    tooltip: {
        trigger: 'axis',
        //指示器
        axisPointer: {
            type: 'line',
            lineStyle: {
                color: '#7171C6'
            }
        },
    },
    legend: {
        data: ['新增确诊', '新增治愈','新增死亡'],
        left: "right"
    },
    //标题样式
    title: {
        text: "全国新增趋势",
        textStyle: {
            color: 'white',
        },
        left: 'left'
    },
    //图形位置
    grid: {
        left: '4%',
        right: '6%',
        bottom: '4%',
        top: 50,
        containLabel: true
    },
    xAxis: [{
        type: 'category',
        data: []
    }],
    yAxis: [{
        type: 'value',
        //y轴线设置显示
        axisLine: {
            show: true
        },
        position:'left',
        axisLabel: {
            show: true,
            color: 'white',
            fontSize: 12,
            formatter: function(value) {
                if (value >= 1000) {
                    value = value / 1000 + 'k';
                }
                return value;
            }
        },
        //与x轴平行的线样式
        splitLine: {
            show: true,
            lineStyle: {
                width: 1,
            }
        }
    },
    {
        type: 'value',
        //y轴线设置显示
        axisLine: {
            show: true
        },
        position:'right',
        axisLabel: {
            show: true,
            color: 'white',
            fontSize: 12,
            formatter: function(value) {
                return value;
            }
        },
        //与x轴平行的线样式
        splitLine: {
            show: true,
            lineStyle: {
                width: 1,
            }
        }
    }
],
    series: [{
        name: "新增确诊",
        type: 'line',
        smooth: true,
        yAxisIndex:0,
        data: []
    }, {
        name: "新增治愈",
        type: 'line',
        smooth: true,
        yAxisIndex:1,
        data: []
    },{
        name: "新增死亡",
        type: 'line',
        smooth: true,
        yAxisIndex:1,
        data: []
    }
]
};

ec_left1.setOption(ec_left1_Option)

左下:

utils.py

def get_l2_data():
    sql = '''
    SELECT ds,confirm,heal,dead
    FROM history;
    '''
    res = query(sql)
    return res

app.py

@app.route('/l2')
def get_l2_data():
    data = utils.get_l2_data()
    day,confirm,heal,dead = [],[],[],[]
    for item in data:
        day.append(item[0].strftime('%m-%d'))
        confirm.append(item[1])
        heal.append(item[2])
        dead.append(item[3])
    return jsonify({'day':day,'confirm':confirm,'heal':heal,'dead':dead})

get_data.js

function get_l2_data(){
    $.ajax({
        url:'/l2',
        success:function(data){
            ec_left2_Option.xAxis[0].data=data.day
            ec_left2_Option.series[0].data=data.confirm
            ec_left2_Option.series[1].data=data.heal
            ec_left2_Option.series[2].data=data.dead
            ec_left2.setOption(ec_left2_Option)
        },
        error:function(data){
        }
    });
}

ec_left2.js

var ec_left2 = echarts.init(document.getElementById('l2'), "dark");
var ec_left2_Option = {
    tooltip: {
        trigger: 'axis',
        //指示器
        axisPointer: {
            type: 'line',
            lineStyle: {
                color: '#7171C6'
            }
        },
    },
    legend: {
        data: ['累计确诊', '累计治愈','累计死亡'],
        left: "right"
    },
    //标题样式
    title: {
        text: "全国累计趋势",
        textStyle: {
            color: 'white',
        },
        left: 'left'
    },
    //图形位置
    grid: {
        left: '4%',
        right: '6%',
        bottom: '4%',
        top: 50,
        containLabel: true
    },
    xAxis: [{
        type: 'category',

        data: []
    }],
    yAxis: [{
        type: 'value',
        //y轴字体设置

        //y轴线设置显示
        axisLine: {
            show: true
        },
        axisLabel: {
            show: true,
            color: 'white',
            fontSize: 12,
            formatter: function(value) {
                if (value >= 1000) {
                    value = value / 1000 + 'k';
                }
                return value;
            }
        },
        //与x轴平行的线样式
        splitLine: {
            show: true,
            lineStyle: {
                // color: '#FFF',
                width: 1,
                // type: 'solid',
            }
        }
    },
    {
        type: 'value',
        //y轴线设置显示
        axisLine: {
            show: true
        },
        position:'right',
        axisLabel: {
            show: true,
            color: 'white',
            fontSize: 12,
            formatter: function(value) {
                if (value >= 1000) {
                    value = value / 1000 + 'k';
                }
                return value;
            }
        },
        //与x轴平行的线样式
        splitLine: {
            show: true,
            lineStyle: {
                width: 1,
            }
        }
    }   ],
    series: [{
        name: "累计确诊",
        type: 'line',
        smooth: true,
        yAxisIndex:0,
        data: []
    }, {
        name: "累计治愈",
        type: 'line',
        smooth: true,
        yAxisIndex:1,
        data: []
    },{
        name: "累计死亡",
        type: 'line',
        smooth: true,
        yAxisIndex:1,
        data: []
    }
]
};

ec_left2.setOption(ec_left2_Option)

第三板块

第三板块是中间部分

先完成上面的数值数据填充

utils.py

def get_c1_data():
"""
    :return: 返回大屏div id=c1 的数据
"""
    # 因为会更新多次数据,取时间戳最新的那组数据
    sql = """
    SELECT confirm,confirm_add,heal,dead
    FROM history
    ORDER BY ds DESC LIMIT 1;
"""
    res = query(sql)
    return res[0]

app.py

@app.route('/c1')
def get_c1_data():
    data = utils.get_c1_data()
    return jsonify({'confirm':int(data[0]),'confirm_add':int(data[1]),'heal':int(data[2]),'dead':int(data[3])})

get_data.js

function get_c1_data(){
    $.ajax({
        url:'/c1',
        success:function(data){
            $(".num h1").eq(0).text(data.confirm)
            $(".num h1").eq(1).text(data.confirm_add)
            $(".num h1").eq(2).text(data.heal)
            $(".num h1").eq(3).text(data.dead)
        },
        error:function(data){
        }
    });
}

接着完成下面的地图

utils.py

def get_c2_data():
"""
    :return:  返回各省数据
"""
    # 因为会更新多次数据,取时间戳最新的那组数据
    sql = '''
    SELECT province,sum(confirm_now)
    FROM details
    GROUP BY province;
    '''
    res = query(sql)
    return res

app.py

@app.route('/c2')
def get_c2_data():
    res = []
    for item in utils.get_c2_data():
        res.append({'name':item[0],'value':int(item[1])})
    return jsonify({'data':res})

get_data.py

function get_c2_data(){
    $.ajax({
        url:'/c2',
        success:function(data){
            ec_center_option.series[0].data=data.data
            ec_center_option.series[0].data.push({
                name:"南海诸岛",value:0
            })
            ec_center.setOption(ec_center_option)
        },
        error:function(data){
        }
    });
}

ec_center.js

const ec_center_option = {

    tooltip: {
        trigger: 'item',
        formatter: '名称:{a}省份:{b}确诊人数:{c}'
    },
    //左侧小导航图标
    visualMap: {
        show: true,
        x: 'left',
        y: 'bottom',
        textStyle: {
            fontSize: 8,
            color:['#FFFFFF']
        },
        splitList: [{ start: 0,end: 9 },
            {start: 10, end: 99 },
            { start: 100, end: 999 },
            {  start: 1000, end: 9999 },
            { start: 10000 }],
        color: ['#8A3310', '#C64918', '#E55B25', '#F2AD92', '#F9DCD1']
    },
    series: [
        {
            name: '数据',
            type: 'map',
            mapType: 'china',
            roam: false,
            itemStyle: {
                normal: {
                    borderWidth: .5, //区域边框宽度
                    borderColor: '#62d3ff', //区域边框颜色
                    areaColor: "#b7ffe6", //区域颜色
                    label: { show: true }
                },
                emphasis: { //鼠标滑过地图高亮的相关设置
                    borderWidth: .5,
                    borderColor: '#fff',
                    areaColor: "#fff",
                    label: { show: true }
                }},
            data: [] // data_list
        }
    ]
};
ec_center = echarts.init(document.getElementById('main'));
ec_center.setOption(ec_center_option)

到这里第三板块就完成了!

第四板块

首先完成上面的柱状图

utils.py

def get_r1_data():
"""
    :return:  返回新增确诊人数前5名的省份
"""
    sql = '''
            SELECT province,confirm FROM
            (select province ,sum(confirm_add) as confirm from details
            where update_time=(select update_time from details
            order by update_time desc limit 1)
            group by province) as a
            ORDER BY confirm DESC LIMIT 7;
            '''
    res = query(sql)
    return res

app.py

@app.route('/r1')
def get_r1_data():
    name,value = [],[]
    for n,v in utils.get_r1_data()[2:]:
        name.append(n)
        value.append(int(v))
    return jsonify({'name':name,'value':value})

get_data.js

function get_r1_data(){
    $.ajax({
        url:'/r1',
        success:function(data){
            ec_right1_option.xAxis.data=data.name
            ec_right1_option.series[0].data=data.value
            ec_right1.setOption(ec_right1_option)
        }
    });
}

ec_right1.js

var ec_right1 = echarts.init(document.getElementById('r1'),"dark");
var ec_right1_option = {
    //标题样式
    title : {
        text : "新增确诊人数TOP5",
        textStyle : {
            color : 'white',
        },
        left : 'left'
    },
      color: ['#3398DB'],
        tooltip: {
            trigger: 'axis',
            axisPointer: {            // 坐标轴指示器,坐标轴触发有效
                type: 'shadow'        // 默认为直线,可选为:'line' | 'shadow'
            }
        },
    xAxis: {
        type: 'category',
        color : 'white',
        data: []
    },
    yAxis: {
        type: 'value',
         color : 'white',
    },
    series: [{
        data: [],
        type: 'bar',
        barMaxWidth:"50%"
    }]
};
ec_right1.setOption(ec_right1_option)

接着完成下面的词云图

utils.py

def get_r2_data():
"""
    :return:  返回最近的20条热搜
"""
    sql = 'select title from focu_news order by news_time desc limit 20'
    res = query(sql)
    all_word = ''
    for item in res:
        all_word += item[0]
    new_data = re.findall('[\u4e00-\u9fa5]+', all_word, re.S)
    new_data = "/".join(new_data)
    seg_list_exact = jieba.cut(new_data, cut_all=True)
    result_list = []
    with open('停用词库.txt', encoding='utf-8') as f:
        con = f.readlines()
        stop_words = set()
        for i in con:
            i = i.replace("\n", "")   # 去掉读取每一行数据的\n
            stop_words.add(i)
    for word in seg_list_exact:
        if word not in stop_words and len(word) > 1:
            result_list.append(word)
    word_counts = collections.Counter(result_list)
    # 词频统计:获取前80最高频的词
    word_counts_top = word_counts.most_common(80)
    return word_counts_top

app.py

@app.route('/r2')
def get_r2_data():
    data = utils.get_r2_data()
    d = []
    for i in data:
        k = i[0]
        v = int(i[1])
        d.append({"name": k, "value": v})
    return jsonify({"kws": d})

get_data.py

function get_r2_data() {
    $.ajax({
        url: "/r2",
        success: function (data) {
            ec_right2_option.series[0].data=data.kws;
            ec_right2.setOption(ec_right2_option);
        }
    })
}

ec_right2.js

var ec_right2 = echarts.init(document.getElementById('r2'), "dark");

var ec_right2_option = {
    // backgroundColor: '#515151',
    title: {
        text: "微博热搜话题",
        textStyle: {
            color: 'white',
        },
        left: 'left'
    },
    tooltip: {
        show: false
    },
    series: [{
        type: 'wordCloud',
        // drawOutOfBound:true,
        gridSize: 1,
        sizeRange: [12, 55],
        rotationRange: [-45, 0, 45, 90],
        // maskImage: maskImage,
        textStyle: {
            normal: {
                color: function () {
                    return 'rgb(' +
                        Math.round(Math.random() * 255) +
                        ', ' + Math.round(Math.random() * 255) +
                        ', ' + Math.round(Math.random() * 255) + ')'
                }
            }
        },
        // left: 'center',
        // top: 'center',
        // // width: '96%',
        // // height: '100%',
        right: null,
        bottom: null,
        // width: 300,
        // height: 200,
        // top: 20,
        data: []
    }]
}

ec_right2.setOption(ec_right2_option);

到这里,全部的页面及渲染就编写完成!

4.添加定时任务

这里忘记说了,前面每次在get_data.js中编写的函数,最后要调用才能使用

get_c1_data()
get_c2_data()
get_l1_data()
get_l2_data()
get_r1_data()
get_r2_data()

这里我们还需要给定义发送请求的ajax函数设置定时,也就是在get_data.js里面添加

setInterval(gettime,1000) # 时间是1s获取一次
setInterval(get_c1_data,1000*60*60) # 1小时发送一次请求
setInterval(get_c2_data,1000*60*60*6)
setInterval(get_l1_data,1000*60*60*12)
setInterval(get_l2_data,1000*60*60*12)
setInterval(get_r1_data,1000*60*60*6)
setInterval(get_r2_data,1000*10)

项目总结

本项目适合flask初学者来进行练手,当然前提也要会一些前端的知识,关于Echarts的使用可以去官网进行学习。关于页面的布局,可自由发挥来进行设计,或者在此基础上来进行创新,开发新的功能。

Original: https://blog.csdn.net/m0_64336780/article/details/126949053
Author: 艾派森
Title: Flask+Echarts搭建全国疫情可视化大屏

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/750442/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球