-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcovid-visualization.py
274 lines (235 loc) · 9.04 KB
/
covid-visualization.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Output
from dash.dependencies import Input
import requests, json
import pandas as pd
import plotly.graph_objects as go
import urllib.request
from flask_caching import Cache
#Load Dash Bootstrap package for CSS theme
#dash-bootstrap-components
external_stylesheets = [dbc.themes.BOOTSTRAP]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = "COVID-19 Visualization"
app.config.suppress_callback_exceptions = True
server = app.server
#Configure Flask-Caching
#Holds maximum of 20 files
cache = Cache(app.server, config={
'CACHE_TYPE':'filesystem',
'CACHE_DIR':'cache',
'CACHE_THRESHOLD':20
})
TIMEOUT_API = 300 #How often API data is pulled
TIMEOUT_GEO = 1800 #How often GeoJSON is read
#Funciton to retrieve COVID-19 data from API
#Data received is cached for 5 minutes
@cache.memoize(timeout=TIMEOUT_API)
def update_data():
#Grab data from API
url = requests.get("https://covid-api.com/api/reports?iso=CAN")
url_json = url.json()
#Changing name of Yukon Territory in API data to match GeoJSON data
url_json['data'][15]['region']['province'] = "Yukon Territory"
#Converting JSON data to a pandas dataframe
return pd.json_normalize(url_json['data'])
#Get COVID-19 data for the whole world
#Data received is cahced for 5 minutes
@cache.memoize(timeout=TIMEOUT_API)
def world_data():
url = requests.get("https://corona.lmao.ninja/v2/countries?yesterday=True")
url_json = url.json()
return pd.json_normalize(url_json)
#Funciton to retreive Canada GeoJSON data
#Data is stored for 30 min
@cache.memoize(timeout=TIMEOUT_GEO)
def canada_geojson():
#Grab GeoJSON data of Canada
with open("canada.geojson", "r") as f:
canada = f.read()
canada = json.loads(canada)
return canada
#Create website navbar
navbar = dbc.NavbarSimple(
brand="COVID-19",
color="dark",
dark=True,
children=[
dcc.Location(id='url', refresh=False),
dbc.NavItem(dbc.NavLink("Canada", href="/")),
dbc.NavItem(dbc.NavLink("World", href="/world")),
html.Div(id='content') #page is rendered here?
]
)
#Create website layout
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content')
])
#Main page of website that displays Canada's COVID-19 data
main_page = html.Div(
children=[
navbar,
html.Div(
style={"padding-top":30, "padding-left":100,"padding-right":100,"padding-bottom":10,"width":"auto"},
className="mx-auto",
children=[
dbc.Card([
dbc.CardHeader("COVID-19 Data In Canada"),
dbc.CardBody([
html.Div([
"Filters:",
dcc.Dropdown(
id="filter-values",
value="confirmed",
options=[
{"label":"Confirmed cases", "value":"confirmed"},
{"label":"Confirmed cases since yesterday", "value":"confirmed_diff"},
{"label":"Active cases", "value":"active"},
{"label":"Active cases since yesterday", "value":"active_diff"},
{"label":"Total deaths", "value":"deaths"},
{"label":"Deaths since yesterday", "value":"deaths_diff"},
{"label":"Recovered", "value":"recovered"},
{"label":"Recovered since yesterday", "value":"recovered_diff"}
]
)
]),
dcc.Graph(id="my-graph")
])
])
]
)
]
)
#Page of website that displays the world's COVID-19 data
world_layout = html.Div(
children=[
navbar,
html.Div(
style={"padding-top":30, "padding-left":100,"padding-right":100,"padding-bottom":10, "width":"auto"},
className="mx-auto",
children=[
dbc.Card([
dbc.CardHeader("COVID-19 Data World Wide"),
dbc.CardBody([
html.Div([
"Filters:",
dcc.Dropdown(
id="world-filters",
value="cases",
options=[
{"label":"Confirmed cases", "value":"cases"},
{"label":"Active cases", "value":"active"},
{"label":"Active cases per million", "value":"activePerOneMillion"},
{"label":"Cases today", "value":"todayCases"},
{"label":"Cases per million", "value":"casesPerOneMillion"},
{"label":"Tests", "value":"tests"},
{"label":"Tests per million", "value":"testsPerOneMillion"},
{"label":"Critical cases", "value":"critical"},
{"label":"Critical per million", "value":"criticalPerOneMillion"},
{"label":"Deaths", "value":"deaths"},
{"label":"Deaths today", "value":"todayDeaths"},
{"label":"Deaths per million", "value":"deathsPerOneMillion"},
{"label":"Recovered", "value":"recovered"},
{"label":"Recovered today", "value":"todayRecovered"},
{"label":"Recovered per million", "value":"recoveredPerOneMillion"}
]
),
dcc.Graph(id="world-graph")
])
])
])
]
)
]
)
#Callback to update Canada's map when different filters are selected
@app.callback(
Output("my-graph", "figure"),
[Input("filter-values", "value")]
)
def update_chart(selected):
#Load COVID-19 data from API
dff = update_data()
#Grab Canada GeoJSON data
canada = canada_geojson()
#Data to be displayed when each province is hovered over
dff['hover_text'] = "<b>"+dff['region.province']+"</b>"+"<br>"+selected[0].upper()+selected[1:]+": "+dff[selected].apply(str)+"<br>"+"As of Date: "+dff['date']+"<br>"+"Last Updated: "+dff['last_update']
#Create the map
fig = go.Figure(data=go.Choropleth(
locations=dff['region.province'],
text=dff['hover_text'],
hoverinfo='text',
geojson=canada,
featureidkey='properties.name',
z=dff[selected].astype(float),
colorscale='spectral',
colorbar_title = "<b>"+selected[0].upper()+selected[1:]+"</b>",
autocolorscale=False
))
#Only show region of map that relates to what's set in locations (in this case the provinces of Canada)
fig.update_layout(
geo={
'showframe':False,
'fitbounds':'locations',
'visible':False
},
margin={
"r":0,
"t":20,
"l":0,
"b":0
}
)
return fig
#Callback that updates the world map when different filters are chosen
@app.callback(
Output("world-graph", "figure"),
[Input("world-filters", "value")]
)
def update_world_map(selected):
dff = world_data()
#What's displayed when each country is hovered over
dff['hover_text'] = "<b>"+dff['country']+"</b>"+"<br><br>"+"Continent: "+dff['continent']+"<br>"+"Population: "+dff['population'].astype(str)+"<br>"+selected[0].upper()+selected[1:]+": "+dff[selected].astype(str)
#Create world map
fig = go.Figure(data=go.Choropleth(
locations=dff['countryInfo.iso3'],
text=dff['hover_text'],
hoverinfo='text',
z=dff[selected].astype(float),
colorscale='sunset',
colorbar_title="<b>"+selected[0].upper()+selected[1:]+"</b>"
))
fig.update_layout(
geo={
'showframe':False,
'showocean':True,
'showlakes':True,
'lakecolor':'#66a3ff',
'oceancolor':'#66a3ff',
'projection_type':'orthographic'
},
margin={
"r":0,
"t":20,
"l":0,
"b":0
},
height=600
)
return fig
#Display the webpages to the user
@app.callback(
Output('page-content', 'children'),
[Input('url', 'pathname')]
)
def display_page(pathname):
if pathname == "/":
return main_page
elif pathname == "/world":
return world_layout
if __name__ == "__main__":
app.run_server(threaded=True)