made the user app presentable for the demo
Browse files- app.py +91 -50
- predictions_history.csv +6 -6
- src/helper_functions.py +11 -8
app.py
CHANGED
@@ -7,7 +7,7 @@ from src.helper_functions import custom_metric_box, pollution_box
|
|
7 |
from src.predict import get_data_and_predictions
|
8 |
|
9 |
st.set_page_config(
|
10 |
-
page_title="Utrecht Pollution Dashboard",
|
11 |
page_icon="🌱",
|
12 |
layout="wide",
|
13 |
initial_sidebar_state="expanded",
|
@@ -37,87 +37,102 @@ dates = dates_past + dates_future
|
|
37 |
df = pd.DataFrame({"Date": dates, "O3": o3_values, "NO2": no2_values})
|
38 |
|
39 |
# App Title
|
40 |
-
st.title("Utrecht Pollution Dashboard🌱")
|
41 |
|
42 |
-
col1, col2 = st.columns((
|
43 |
# Create a 3-column layout
|
44 |
with col1:
|
45 |
st.subheader("Current Weather")
|
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 |
-
custom_metric_box(
|
75 |
-
label="Wind Speed",
|
76 |
-
value=f"{round(today['wind_speed'] * 0.1, 1)} m/s",
|
77 |
-
delta=f"{round(today['wind_speed'] * 0.1, 1) - round(previous_day['wind_speed'] * 0.1, 1)} m/s",
|
78 |
-
)
|
79 |
|
80 |
with col2:
|
81 |
st.subheader("Current Pollution Levels")
|
82 |
sub1, sub2 = st.columns((1, 1))
|
83 |
-
|
84 |
-
#
|
85 |
with sub1:
|
86 |
pollution_box(
|
87 |
label="O<sub>3</sub>",
|
88 |
value=f"{round(today['O3'])} µg/m³",
|
89 |
delta=f"{round(int(today['O3']) - int(previous_day['O3']))} µg/m³",
|
|
|
90 |
)
|
91 |
with st.expander("Learn more about O3", expanded=False):
|
92 |
st.markdown(
|
93 |
-
"
|
|
|
|
|
|
|
94 |
unsafe_allow_html=True,
|
95 |
)
|
|
|
|
|
96 |
with sub2:
|
97 |
pollution_box(
|
98 |
label="NO<sub>2</sub>",
|
99 |
value=f"{round(today['NO2'])} µg/m³",
|
100 |
delta=f"{round(int(today['NO2']) - int(previous_day['NO2']))} µg/m³",
|
|
|
101 |
)
|
102 |
-
with st.expander("Learn more about
|
103 |
st.markdown(
|
104 |
-
"
|
|
|
|
|
|
|
105 |
unsafe_allow_html=True,
|
106 |
)
|
107 |
|
108 |
# Create two columns for two separate graphs
|
109 |
# Plot O3 in the first subcolumn
|
110 |
-
st.subheader("O3
|
111 |
# Plot NO2 in the second subcolumn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
fig_o3 = go.Figure()
|
113 |
fig_o3.add_trace(
|
114 |
-
go.
|
115 |
x=df["Date"],
|
116 |
y=df["O3"],
|
117 |
-
mode="lines+markers",
|
118 |
name="O3",
|
119 |
-
|
120 |
-
hovertemplate="%{x|%d-%b-%Y}<br
|
121 |
)
|
122 |
)
|
123 |
fig_o3.add_shape(
|
@@ -148,14 +163,17 @@ with col2:
|
|
148 |
)
|
149 |
st.plotly_chart(fig_o3, key="fig_o3")
|
150 |
|
|
|
|
|
|
|
151 |
fig_no2 = go.Figure()
|
152 |
fig_no2.add_trace(
|
153 |
-
go.
|
154 |
x=df["Date"],
|
155 |
y=df["NO2"],
|
156 |
-
mode="lines+markers",
|
157 |
name="NO2",
|
158 |
-
|
|
|
159 |
)
|
160 |
)
|
161 |
fig_no2.add_shape(
|
@@ -165,7 +183,7 @@ with col2:
|
|
165 |
x1=pd.Timestamp.today(),
|
166 |
y0=min(no2_values),
|
167 |
y1=max(no2_values),
|
168 |
-
line=dict(color="
|
169 |
)
|
170 |
)
|
171 |
fig_no2.update_layout(
|
@@ -185,3 +203,26 @@ with col2:
|
|
185 |
),
|
186 |
)
|
187 |
st.plotly_chart(fig_no2, key="fig_no2")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
from src.predict import get_data_and_predictions
|
8 |
|
9 |
st.set_page_config(
|
10 |
+
page_title="Utrecht Pollution Dashboard ",
|
11 |
page_icon="🌱",
|
12 |
layout="wide",
|
13 |
initial_sidebar_state="expanded",
|
|
|
37 |
df = pd.DataFrame({"Date": dates, "O3": o3_values, "NO2": no2_values})
|
38 |
|
39 |
# App Title
|
40 |
+
st.title("Utrecht Pollution Dashboard 🌱")
|
41 |
|
42 |
+
col1, col2 = st.columns((1, 3))
|
43 |
# Create a 3-column layout
|
44 |
with col1:
|
45 |
st.subheader("Current Weather")
|
46 |
+
|
47 |
+
|
48 |
+
custom_metric_box(
|
49 |
+
label="Temperature",
|
50 |
+
value=f"{round(today['mean_temp'] * 0.1)} °C",
|
51 |
+
)
|
52 |
+
custom_metric_box(
|
53 |
+
label="Humidity",
|
54 |
+
value=f"{round(today['humidity'])} %",
|
55 |
+
)
|
56 |
+
custom_metric_box(
|
57 |
+
label="Pressure",
|
58 |
+
value=f"{round(today['pressure'] * 0.1)} hPa",
|
59 |
+
)
|
60 |
+
|
61 |
+
custom_metric_box(
|
62 |
+
label="Precipitation",
|
63 |
+
value=f"{round(today['percipitation'] * 0.1)} mm",
|
64 |
+
)
|
65 |
+
custom_metric_box(
|
66 |
+
label="Solar Radiation",
|
67 |
+
value=f"{round(today['global_radiation'])} J/m²",
|
68 |
+
)
|
69 |
+
custom_metric_box(
|
70 |
+
label="Wind Speed",
|
71 |
+
value=f"{round(today['wind_speed'] * 0.1, 1)} m/s",
|
72 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
|
74 |
with col2:
|
75 |
st.subheader("Current Pollution Levels")
|
76 |
sub1, sub2 = st.columns((1, 1))
|
77 |
+
|
78 |
+
# Ozone (O₃) Pollution Box
|
79 |
with sub1:
|
80 |
pollution_box(
|
81 |
label="O<sub>3</sub>",
|
82 |
value=f"{round(today['O3'])} µg/m³",
|
83 |
delta=f"{round(int(today['O3']) - int(previous_day['O3']))} µg/m³",
|
84 |
+
threshold=120
|
85 |
)
|
86 |
with st.expander("Learn more about O3", expanded=False):
|
87 |
st.markdown(
|
88 |
+
"""
|
89 |
+
*Ozone (O<sub>3</sub>)*: A harmful gas at ground level that can irritate the respiratory system and aggravate asthma.<br>
|
90 |
+
**Good/Bad**: "Good" means safe levels for most people, while "Bad" suggests harmful levels, especially for sensitive groups.
|
91 |
+
""",
|
92 |
unsafe_allow_html=True,
|
93 |
)
|
94 |
+
|
95 |
+
# Nitrogen Dioxide (NO₂) Pollution Box
|
96 |
with sub2:
|
97 |
pollution_box(
|
98 |
label="NO<sub>2</sub>",
|
99 |
value=f"{round(today['NO2'])} µg/m³",
|
100 |
delta=f"{round(int(today['NO2']) - int(previous_day['NO2']))} µg/m³",
|
101 |
+
threshold=40
|
102 |
)
|
103 |
+
with st.expander("Learn more about NO2", expanded=False):
|
104 |
st.markdown(
|
105 |
+
"""
|
106 |
+
*Nitrogen Dioxide (NO<sub>2</sub>)*: A toxic gas that contributes to lung irritation and worsens asthma and other respiratory issues.<br>
|
107 |
+
**Good/Bad**: "Good" means safe air quality, while "Bad" indicates levels that could cause respiratory problems, especially for vulnerable individuals.
|
108 |
+
""",
|
109 |
unsafe_allow_html=True,
|
110 |
)
|
111 |
|
112 |
# Create two columns for two separate graphs
|
113 |
# Plot O3 in the first subcolumn
|
114 |
+
st.subheader("O3 Forecast")
|
115 |
# Plot NO2 in the second subcolumn
|
116 |
+
# Define the new color logic: green, orange, and red based on the threshold
|
117 |
+
def get_simple_color_scale(values, threshold):
|
118 |
+
"""Returns green for values below the threshold, orange for values between the threshold and 2x the threshold, and red for values above 2x the threshold."""
|
119 |
+
return [
|
120 |
+
"#77C124" if v < threshold else
|
121 |
+
"#E68B0A" if v < 2 * threshold else
|
122 |
+
"#E63946" for v in values
|
123 |
+
]
|
124 |
+
|
125 |
+
# O3 Bar Plot (threshold: 40)
|
126 |
+
o3_colors = get_simple_color_scale(o3_values, 40) # Green below 40, Orange above 40, Red above 80
|
127 |
+
|
128 |
fig_o3 = go.Figure()
|
129 |
fig_o3.add_trace(
|
130 |
+
go.Bar(
|
131 |
x=df["Date"],
|
132 |
y=df["O3"],
|
|
|
133 |
name="O3",
|
134 |
+
marker=dict(color=o3_colors), # Apply the color scale
|
135 |
+
hovertemplate="%{x|%d-%b-%Y}<br>%{y} µg/m³<extra></extra>",
|
136 |
)
|
137 |
)
|
138 |
fig_o3.add_shape(
|
|
|
163 |
)
|
164 |
st.plotly_chart(fig_o3, key="fig_o3")
|
165 |
|
166 |
+
# NO2 Bar Plot (threshold: 120)
|
167 |
+
no2_colors = get_simple_color_scale(no2_values, 120) # Green below 120, Orange above 120, Red above 240
|
168 |
+
st.subheader("NO2 Forecast")
|
169 |
fig_no2 = go.Figure()
|
170 |
fig_no2.add_trace(
|
171 |
+
go.Bar(
|
172 |
x=df["Date"],
|
173 |
y=df["NO2"],
|
|
|
174 |
name="NO2",
|
175 |
+
marker=dict(color=no2_colors), # Apply the color scale
|
176 |
+
hovertemplate="%{x|%d-%b-%Y}<br>%{y} µg/m³<extra></extra>",
|
177 |
)
|
178 |
)
|
179 |
fig_no2.add_shape(
|
|
|
183 |
x1=pd.Timestamp.today(),
|
184 |
y0=min(no2_values),
|
185 |
y1=max(no2_values),
|
186 |
+
line=dict(color="White", width=3, dash="dash"),
|
187 |
)
|
188 |
)
|
189 |
fig_no2.update_layout(
|
|
|
203 |
),
|
204 |
)
|
205 |
st.plotly_chart(fig_no2, key="fig_no2")
|
206 |
+
|
207 |
+
import matplotlib.pyplot as plt
|
208 |
+
import numpy as np
|
209 |
+
|
210 |
+
fig, ax = plt.subplots()
|
211 |
+
|
212 |
+
bar = ax.bar([1,2,3,4,5,6],[4,5,6,3,7,5])
|
213 |
+
|
214 |
+
def gradientbars(bars):
|
215 |
+
grad = np.atleast_2d(np.linspace(0,1,256)).T
|
216 |
+
ax = bars[0].axes
|
217 |
+
lim = ax.get_xlim()+ax.get_ylim()
|
218 |
+
for bar in bars:
|
219 |
+
bar.set_zorder(1)
|
220 |
+
bar.set_facecolor("none")
|
221 |
+
x,y = bar.get_xy()
|
222 |
+
w, h = bar.get_width(), bar.get_height()
|
223 |
+
ax.imshow(grad, extent=[x,x+w,y,y+h], aspect="auto", zorder=0)
|
224 |
+
ax.axis(lim)
|
225 |
+
|
226 |
+
gradientbars(bar)
|
227 |
+
|
228 |
+
plt.show()
|
predictions_history.csv
CHANGED
@@ -5,9 +5,9 @@ O3,2024-10-24,2024-10-26,16.000984317626852
|
|
5 |
NO2,2024-10-24,2024-10-26,25.760307451092384
|
6 |
O3,2024-10-24,2024-10-27,19.64377495640328
|
7 |
NO2,2024-10-24,2024-10-27,31.210576791105115
|
8 |
-
O3,2024-10-24,2024-10-25,10.
|
9 |
-
NO2,2024-10-24,2024-10-25,25.
|
10 |
-
O3,2024-10-24,2024-10-26,16.
|
11 |
-
NO2,2024-10-24,2024-10-26,25.
|
12 |
-
O3,2024-10-24,2024-10-27,19.
|
13 |
-
NO2,2024-10-24,2024-10-27,31.
|
|
|
5 |
NO2,2024-10-24,2024-10-26,25.760307451092384
|
6 |
O3,2024-10-24,2024-10-27,19.64377495640328
|
7 |
NO2,2024-10-24,2024-10-27,31.210576791105115
|
8 |
+
O3,2024-10-24,2024-10-25,10.338088594245967
|
9 |
+
NO2,2024-10-24,2024-10-25,25.685199915579293
|
10 |
+
O3,2024-10-24,2024-10-26,16.00098431760588
|
11 |
+
NO2,2024-10-24,2024-10-26,25.7603074510804
|
12 |
+
O3,2024-10-24,2024-10-27,19.643774956417968
|
13 |
+
NO2,2024-10-24,2024-10-27,31.21057679109889
|
src/helper_functions.py
CHANGED
@@ -2,7 +2,7 @@ import streamlit as st
|
|
2 |
|
3 |
|
4 |
# Custom function to create styled metric boxes with compact layout
|
5 |
-
def custom_metric_box(label, value
|
6 |
st.markdown(f"""
|
7 |
<div style="
|
8 |
padding: 5px;
|
@@ -17,14 +17,18 @@ def custom_metric_box(label, value, delta):
|
|
17 |
</div>
|
18 |
<div>
|
19 |
<p style="font-size: 18px; font-weight: bold; margin: 0;">{value}</p> <!-- Smaller metric -->
|
20 |
-
<p style="color: {'green' if '+' in delta else 'orange'}; font-size: 12px; margin: 0;">{delta}</p> <!-- Smaller delta text -->
|
21 |
</div>
|
22 |
</div>
|
23 |
""", unsafe_allow_html=True)
|
24 |
|
25 |
# Custom function to create pollution metric boxes with side-by-side layout for label and value
|
26 |
# Custom function to create pollution metric boxes with side-by-side layout and fixed width
|
27 |
-
def pollution_box(label, value, delta):
|
|
|
|
|
|
|
|
|
|
|
28 |
st.markdown(f"""
|
29 |
<div style="
|
30 |
background: rgba(255, 255, 255, 0.05);
|
@@ -35,10 +39,9 @@ def pollution_box(label, value, delta):
|
|
35 |
border: 1px solid rgba(255, 255, 255, 0.15);
|
36 |
padding: 15px;
|
37 |
margin-bottom: 10px;
|
38 |
-
width: 300px; /* Fixed width */
|
39 |
">
|
40 |
-
<h4 style="font-size:
|
41 |
-
<p style="font-size: 36px; font-weight: bold; margin: 0;">{
|
42 |
-
<p style="
|
43 |
</div>
|
44 |
-
|
|
|
2 |
|
3 |
|
4 |
# Custom function to create styled metric boxes with compact layout
|
5 |
+
def custom_metric_box(label, value):
|
6 |
st.markdown(f"""
|
7 |
<div style="
|
8 |
padding: 5px;
|
|
|
17 |
</div>
|
18 |
<div>
|
19 |
<p style="font-size: 18px; font-weight: bold; margin: 0;">{value}</p> <!-- Smaller metric -->
|
|
|
20 |
</div>
|
21 |
</div>
|
22 |
""", unsafe_allow_html=True)
|
23 |
|
24 |
# Custom function to create pollution metric boxes with side-by-side layout for label and value
|
25 |
# Custom function to create pollution metric boxes with side-by-side layout and fixed width
|
26 |
+
def pollution_box(label, value, delta, threshold):
|
27 |
+
# Determine if the pollution level is "Good" or "Bad"
|
28 |
+
status = "Good" if float(value.split()[0]) < threshold else "Bad"
|
29 |
+
status_color = "#77C124" if status == "Good" else "#E68B0A"
|
30 |
+
|
31 |
+
# Render the pollution box
|
32 |
st.markdown(f"""
|
33 |
<div style="
|
34 |
background: rgba(255, 255, 255, 0.05);
|
|
|
39 |
border: 1px solid rgba(255, 255, 255, 0.15);
|
40 |
padding: 15px;
|
41 |
margin-bottom: 10px;
|
|
|
42 |
">
|
43 |
+
<h4 style="font-size: 24px; font-weight: bold; margin: 0;">{label}</h4> <!-- Bigger label -->
|
44 |
+
<p style="font-size: 36px; font-weight: bold; color: {status_color}; margin: 0;">{status}</p> <!-- Good/Bad with color -->
|
45 |
+
<p style="font-size: 18px; margin: 0;">{value}</p> <!-- Smaller value where delta used to be -->
|
46 |
</div>
|
47 |
+
""", unsafe_allow_html=True)
|