Spatial Data Infrastructure – Dashboard using Flask & Plotly Dash

04.06.2021: Back ONLINE

Table of Contents

Server & Domain

Domain: https://www.icu-dashboard.donike.net

Also published on the Open Data Portal Austria: https://www.data.gv.at/anwendungen/icu-dashboard/

Flask Installation on Server via Apache & WSGI

Following a Tutorial by Harrison Kinsely and sentdex.

Setting up from a fresh Ubuntu 16.04 installation. curl, nano and other functionalities need to be installed separately. SQL installation not strictly necessary. Warning: don’t forget to call “server” as app in WSGI config file.

sudo apt-get update && sudo apt-get upgrade
sudo apt-get install apache2 mysql-client mysql-server
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install python3.6 python3.6-dev
curl https://bootstrap.pypa.io/get-pip.py | sudo python3.6
sudo apt-get install apache2 apache2-dev
pip3.6 install mod_wsgi
mod_wsgi-express module-config

#returns:
LoadModule wsgi_module "/usr/local/lib/python3.6/dist-packages/mod_wsgi/server/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so"
WSGIPythonHome "/usr"

#paste return in:
nano /etc/apache2/mods-available/wsgi.load

a2enmod wsgi
service apache2 restart
pip3.6 install Flask

#paste server info into
nano /etc/apache2/sites-available/FlaskApp.conf

# Paste this into nano
<VirtualHost *:80>
                ServerName 192.168.0.1 # Server IP or Domain Name here
                ServerAdmin youremail@email.com
                WSGIScriptAlias / /var/www/FlaskApp/FlaskApp.wsgi
                <Directory /var/www/FlaskApp/FlaskApp/>
                        Order allow,deny
                        Allow from all
                </Directory>
                ErrorLog ${APACHE_LOG_DIR}/FlaskApp-error.log
                LogLevel warn
                CustomLog ${APACHE_LOG_DIR}/FlaskApp-access.log combined
</VirtualHost>

sudo a2ensite FlaskApp
service apache2 reload

mkdir /var/www/FlaskApp
cd /var/www/FlaskApp
nano FlaskApp.wsgi

# Paste dummy app into nano
#!/usr/bin/python3.6
import sys
import logging
logging.basicConfig(stream=sys.stderr)
sys.path.insert(0,"/var/www/FlaskApp/")


from FlaskApp import app as application

mkdir FlaskApp cd FlaskApp
nano __init__.py

from flask import Flask
import sys

app = Flask(__name__)

@app.route('/')
def homepage():
    return "Hi there, how ya doin?"

if __name__ == "__main__":
    app.run()


service apache2 reload 

Installing and Configuring Plotly Dash in Flask

Install Dash & packages in Python env.

# install dash & components
sudo pip3.6 install dash dash-renderer dash-html-components dash-core-components plotly


hown -R www-data:www-data /var/www/FlaskApp
service apache2 reload 

Requirements.txt file for this environment.

attrs==21.2.0
Brotli==1.0.9
certifi==2020.12.5
chardet==4.0.0
click==7.1.2
click-plugins==1.1.1
cligj==0.7.1
dash==1.20.0
dash-core-components==1.16.0
dash-html-components==1.1.3
dash-renderer==1.9.1
dash-table==4.11.3
dataclasses==0.8
DateTime==4.3
Fiona==1.8.19
Flask==2.0.0
Flask-Compress==1.9.0
future==0.18.2
geojson==2.5.0
geopandas==0.9.0
idna==2.10
itsdangerous==2.0.0
Jinja2==3.0.0
lxml==4.6.3
MarkupSafe==2.0.0
mod-wsgi==4.7.1
munch==2.5.0
numpy==1.19.5
pandas==1.1.5
pandas-datareader==0.9.0
plotly==4.14.3
pycurl==7.43.0
pygobject==3.20.0
pyproj==3.0.1
python-apt==1.1.0b1+ubuntu0.16.4.11
python-dateutil==2.8.1
pytz==2021.1
requests==2.25.1
retrying==1.3.3
Shapely==1.7.1
six==1.16.0
unattended-upgrades==0.1
urllib3==1.26.4
 

__init__.py File

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
import plotly.express as px
import geopandas
import numpy as np
from dash.dependencies import Input, Output

app = dash.Dash()
app.title = "ICU-Dashboard Austria"
"""
____________________________________________________

"""
"""
LOAD  DATA AND PERFORM MUTATIONS
"""
# load ICU data
df = pd.read_csv("/var/www/FlaskApp/FlaskApp/data/ICU_data.csv",sep=",") #full path neces.

# list of states
states = list(df["Bundesland"].unique())

# list of DFs by states
ls_df = []
for i in states:
    temp = df[df["Bundesland"]==i]
    ls_df.append(temp)

# create list of dicts for droptown menue
options = []
for i in states:
    options.append({"label":i,"value":i})

# get data for most recent day & keep only this data
most_recent_date = df['MeldeDatum'].max()
df2 = pd.read_pickle("/var/www/FlaskApp/FlaskApp/data/df2.pkl")

# load json file containing state geometries
df_map =geopandas.read_file("/var/www/FlaskApp/FlaskApp/data/austria_adm1.geojson")

# join geometries with most recent data per state
df_map["Bundesland"] = df_map["NAME_1"]
df_map = pd.merge(df2,df_map,on="Bundesland")
df_map = geopandas.GeoDataFrame(df_map, geometry="geometry")

"""
____________________________________________________

"""
"""
CREATE MAP FIGURE
"""
# make map figure
fig_map = px.choropleth(df_map,geojson=df_map.geometry,color_continuous_scale="reds",range_color=[0,100],
    projection="equirectangular",locations=df_map.index,color="ICU_perc",hover_data=["Bundesland","ICU_perc"],width=1000, height=500)
    
fig_map.update_geos(fitbounds="locations",visible=True,
    resolution=110,
    showcoastlines=True, coastlinecolor="Black",
    showland=True, landcolor="LightGray",
    showocean=True, oceancolor="LightBlue",
    showlakes=False, lakecolor="Blue",
    showrivers=False, rivercolor="Blue",
    showcountries=False,countrycolor="Black"
    )
fig_map.update_layout(title_text="Current ICU Occupancy Percentage by State ("+str(most_recent_date)+")")
#fig_map.update_layout(autosize=False)

"""
LOAD DFs FOR UPDATE GRAPHS
"""
df_perc = pd.read_pickle("/var/www/FlaskApp/FlaskApp/data/df_perc.pkl")
df_FZICU = pd.read_pickle("/var/www/FlaskApp/FlaskApp/data/df_FZICU.pkl")
df_ICU_cap = pd.read_pickle("/var/www/FlaskApp/FlaskApp/data/df_ICU_cap.pkl")

"""
CREATE GRAPH OBJECTS
"""

fig_graph = go.Figure()
fig_graph_FZICU = go.Figure()

"""
____________________________________________________

"""
"""
APP LAYOUT
"""

app.layout = html.Div([
    # Image Logo
    html.Div([
    html.A([
    html.Img(src="https://www.donike.net/wp-content/uploads/corona_Dashboard_Cover_2.jpg", style={'height':'55%', 'width':'55%'})
    ], href='https://www.donike.net'),
    ],style={'textAlign': 'center'}), # close image div tag
    
    
    #Headers
    #html.H1(children="COVID-19 ICU Dashboard - Austria",style={"text-align": "center"}),
    html.H3("SDI Project by Anna Porti Suarez & Simon Donike",style={"text-align":"center"}),

# Map graph
    html.Div([
    dcc.Graph(id="map1", figure=fig_map,style={"vertical-align":"middle"}) 
    ]),
    
# ICU Occ. Drop-Down & Graph Objects
    html.Div([
    html.H3("ICU Occupancy Percentage Timeline by Federal States",style={"text-align":"center"}), 
    html.H3("Select State",style={"text-align":"left"}),
    
    html.Div([
    dcc.Dropdown(id='dropdown',options=options,value='Alle',placeholder="Select a state",searchable=False,clearable=False,),
    ],style={"width": "15%"}),
    dcc.Graph(id="graph_icu",figure=fig_graph),
    dcc.Graph(id="graph_FZICU",figure=fig_graph_FZICU),
    ]),

# Footer
    html.Div([
    html.Img(src="https://www.donike.net/wp-content/uploads/copernicus_eu_logos_combined.jpg", style={'height':'35%', 'width':'35%'}),
    html.P(['UPDATED NIGHTLY',
        html.Br(),'Rendered via Flask & Plotly Dash',
        html.Br(),"COVID-19 ICU Data Source: https://covid19-dashboard.ages.at/data/'CovidFallzahlen.csv",
        html.Br(),"Data collected by BMSGPK, Austria COVID-19 Open Data Information Portal",
        html.Br(),'Administrative Units Data Source: https://gadm.org/ (Academic Use)'],
        style={'textAlign': 'center'})
    ])
]) # close app layout div


"""
____________________________________________________

"""
# Callback for PERC graph
@app.callback(
    Output(component_id='graph_icu', component_property='figure'),
    Input(component_id='dropdown',component_property="value") #, component_property='value')
    )
def update_output_div(input_value):
    if input_value == "Alle":
        name = "all states combined"
    else:
        name=input_value
    fig_graph = px.line(df_perc, x="MeldeDatum",y=input_value,title="ICU Occupancy Timeline in "+name+" - Percentage",
        labels={"MeldeDatum": "Date"})
    fig_graph.update_yaxes(range=[0,100])
    #fig.graph.update_layout(xaxis_title="Date",yaxis_title="ICU Occupancy Percentage")
    return fig_graph

# Callback for absolute numbers graph
@app.callback(
    Output(component_id='graph_FZICU', component_property='figure'),
    Input(component_id='dropdown',component_property="value") #, component_property='value')
    )
def update_output_div(input_value):
    if input_value == "Alle":
        name = "all states combined"
    else:
        name=input_value
    #fig_graph_FZICU = px.line(df_FZICU, x="MeldeDatum",y=input_value,title="ICU Occupancy and Capacity in "+name+" - Absolute Numbers")
    fig_graph_FZICU = go.Figure()
    fig_graph_FZICU.add_trace(go.Scatter(x=df_FZICU["MeldeDatum"],y=df_FZICU[input_value],name="ICU beds w/ COVID-19 Patients",
        mode="lines",line=go.scatter.Line(color="red"),showlegend=True))
    fig_graph_FZICU.add_trace(go.Scatter(x=df_ICU_cap["MeldeDatum"],y=df_ICU_cap[input_value],name="Available ICU beds",
        mode="lines",line=go.scatter.Line(color="green"),showlegend=True))
    fig_graph_FZICU.update_layout(title_text="ICU Occupancy and Capacity in "+name+" - Absolute Numbers",
        xaxis_title="Date",
        yaxis_title="No. of ICU beds",)
    fig_graph_FZICU.update_layout(legend=dict(
        orientation="h",
        yanchor="bottom",
        y=1.02,
        xanchor="right",
        x=1
    ))
    return fig_graph_FZICU
"""
____________________________________________________

"""
"""
SERVER SETTINGS
"""
# added this
server = app.server

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

Discussion

Leave a Reply

Your email address will not be published. Required fields are marked *

Further Reading
Recent Updates