Okay, as mentioned last time, I am going to add an input field to the sidebar which will allow me to select the date to use for displaying the charts and data.
Date Input
If there is no date entered in the input field, I will use today’s date. Otherwise I will display the data for the date in the input field. I am going to start slow and just add the field and refactor the date related code to behave accordingly. Because this is just me, I will expect the date to be entered as yyyy.mm.dd.
However, if the date is not carried over for Streamlit reruns, which it likely won’t, I will need to set up and use cached values for the date variables. Maybe a dictionary or list.
But, let’s start slow and set up the input field and test it. I have decided to give the field a default value of today’s date. Hoping that may make life a little easier.
... ...
def dt_chg():
"""Change the session_state variables as appropriate based on value of text input field"""
if 'c_dtm' in st.session_state and 'c_dt' in st.session_state:
if st.session_state.c_dt != st.session_state.db_dt:
st.session_state.c_dt = st.session_state.db_dt
st.session_state.c_dtm = time.strptime(st.session_state.c_dt, "%Y.%m.%d")
st.set_page_config(
page_title="Surrey Weather Dashboard",
page_icon="🌧️",
layout="wide",
initial_sidebar_state="expanded")
rfall = init_wdb()
alt.theme.enable("dark")
# if c_dtm and c_dt not in session_state initialize them with today's date
if 'c_dtm' not in st.session_state:
st.session_state.c_dtm = time.localtime()
st.session_state.c_dt = time.strftime("%Y.%m.%d", st.session_state.c_dtm)
with st.sidebar:
st.title('🌧️ Surrey Weather')
# allow for user selected display date, store input in session state with key='db_dt'
# when field value changed, run function to update session variables before anything displayed
w_date = st.text_input("Weather Date", value=st.session_state.c_dt, max_chars=10, key='db_dt', on_change=dt_chg)
w_items = ["Month-to-date rainfall", "Year-to-date rainfall", "Current month's temperatures"]
w_item = st.selectbox("Select item to display:", w_items, index=0)
if w_item == "Current month's temperatures":
grid2 = [col.container(height=1000) for col in st.columns([4, 1])]
else:
row1 = st.columns([3, 2])
grid1 = [col.container(height=500) for col in row1]
row2 = st.container(height=500)
# based on session variables set date variables used by code to display data and charts
c_dt = time.strftime("%Y.%m.%d", st.session_state.c_dtm)
# year and month as "yyyy.mm"
c_ym = time.strftime("%Y.%m", st.session_state.c_dtm)
# month and day as "mm.dd"
c_md = time.strftime("%m.%d", st.session_state.c_dtm)
# month name as string
s_mon = time.strftime("%B", st.session_state.c_dtm)
if c_ym[:4] > "2023":
y_strt = str(int(c_ym[:4]) - 10)
else:
y_strt = ""
if w_item == "Month-to-date rainfall":
... ...
And that was it. Change the date in the text input field and the dashboard changed to display data/charts based on that date for the currently selected display type. Select a different dashboard display type and it displayed that for the date currently in the field. Wonder if I should add a reset button.
Reset Button
I know I could just enter today’s date, but a reset button seems to make more sense to me. So I am going to add one. Hopefully without too many issues. I will also likely add a divider to clearly separate the date selection area from the display selection area.
But, we need to consider the following.
When a button is used to modify or reset another widget, it is the same as the above examples to modify st.session_state. However, an extra consideration exists: you cannot modify a key-value pair in st.session_state if the widget with that key has already been rendered on the page for the current script run.
Button behavior and examples
The following appears to work as intended. A bit of before and after code. Note the modified if block at the top of the code block.
# if c_dtm and c_dt not in session_state initialize them with today's date
if ('c_dtm' not in st.session_state) or st.session_state.get('reset'):
st.session_state.c_dtm = time.localtime()
st.session_state.c_dt = time.strftime("%Y.%m.%d", st.session_state.c_dtm)
with st.sidebar:
st.title('🌧️ Surrey Weather')
# allow for user selected display date, store input in session state with key='db_dt'
# when field value changed, run function to update session variables before anything displayed
w_date = st.text_input("Weather Date", value=st.session_state.c_dt, max_chars=10, key='db_dt', on_change=dt_chg)
st.button('Reset Date', key='reset', type="primary")
st.divider()
w_items = ["Month-to-date rainfall", "Year-to-date rainfall", "Current month's temperatures"]
I am not going to try and capture a video showing the button working. But, you can believe me, it does work. Or you can code it yourself and test my assertions.
Bug Fix
When I started working on the forms to add new rainfall or weather related data to the database, I noticed a problem when trying to display temperature/weather data for a date without any such data. The Weather_db class method get_c_t_wc() didn’t like/handle getting a empty dataframe when querying the database. So, I reworked the method to return a temperature of \(-1000\) and an empty string for the state of the weather in that case. I then reworked the db_charts.temp_gauge() function to handle getting that set of values. Edge cases!
Sorry, I didn’t save the error message; but, it wasn’t the most helpful thing I have ever read.
Not sure I am dealing with this the best way possible. But didn’t feel like doing a serious refactoring right now. Here’s the full code for both the method and function.
# in weather_db.py
def get_c_t_wc(self, c_dt):
""" Get the temperature and weather condition for a selected date.
params:
c_dt: date for which data wanted, yyyy.mm.dd (str)
returns: datetime, temperature and condition as retrieved from database
for condition, if not found, returns a temperature value of -1000
and an empty string for the weather condition.
"""
# get temp and weather condition for selected date
n_tbl = self.tnms["tp_tnm"]
q_dtmp = f"""SELECT datetime, temperature
FROM {n_tbl}
WHERE SUBSTR(datetime, 1, 10)='{c_dt}' AND temperature!='' AND temperature IS NOT Null;"""
t_day = self.qry_pd(q_dtmp)
ix_lrw = len(t_day) - 1
if ix_lrw != -1:
c_dttm, c_tmp = t_day.loc[ix_lrw, :].values.tolist()
q_cond = f"""SELECT condition
FROM {n_tbl}
WHERE SUBSTR(datetime, 1, 10)='{c_dt}' AND condition!='' AND condition IS NOT Null;
"""
t_cw = self.qry_pd(q_cond)
c_cnd = t_cw["condition"].iloc[0]
else:
# if nothing found in database, return ridiculous value for temperature as notice
c_dttm, c_tmp, c_cnd = c_dt, -1000, ""
return c_dttm, c_tmp, c_cnd
# in db_charts
def temp_gauge(dttm, c_temp, t_avg, t_min, t_max):
"""Generate and return figure for gauge showing today's temperature compared to
the historical minimum, average and maximum for the current month
Params:
dttm: datetime for current temperature
c_temp: current day's temperature
t_avg: historical average temperature for current month
t_min: historical minimum rainfall for period
t_max: historical maximum rainfall for period
"""
g_ttl = f"Temperature: {dttm}"
# was temperature data found in database, if temp == -1000 was not
if c_temp == -1000:
t_cur = 0
g_md = "gauge"
g_ttl = f"Temperature: {dttm}<br>(Not available)"
c_ht = 440
else:
t_cur = c_temp
g_md = "gauge+number+delta"
g_ttl = f"Temperature: {dttm}"
c_ht = 400
if t_cur <= t_max:
g_rng = int(t_max * 1.2)
else:
g_rng = int(t_cur * 1.2)
fig = go.Figure(go.Indicator(
domain = {'x': [0, 1], 'y': [0, 1]},
value = t_cur,
mode = g_md,
title = {'text': g_ttl, 'font': {'size': 36}},
delta = {'reference': t_avg, 'increasing': {'color': "#FA8100"}},
number = {'font': {'size': 54}},
gauge = {
'axis': {'range': [None, g_rng], 'tickwidth': 1, 'tickcolor': "maroon"},
'bar': {'color': "#FBA94B"},
'bgcolor': "white",
'borderwidth': 2,
'bordercolor': "gray",
'steps' : [
{'range': [0, t_min], 'color': "#FEEFDE"},
{'range': [t_min, t_max], 'color': "#FBC587"},
{'range': [t_max, g_rng], 'color': "#FEEFDE"}
],
'threshold' : {'line': {'color': "#FA8100", 'width': 4}, 'thickness': 0.75, 'value': t_avg}}))
fig.update_layout(autosize=False, width=600, height=c_ht,
margin=dict(l=25, r=25, b=0, t=20, pad=0),
paper_bgcolor = "#FFF9F5", font = {'color': "maroon", 'family': "Arial"})
return fig
And here’s a view of the dashboard when no temperature data is available for the current date. Note the lack of a curve marking the current date’s temperature. And, the lack of any text under the gauge curve.

Add New Weather Data to Database
I have been using my utility scripts, but it seems to me that that functionality should be available in the dashboard. So that’s where I am going to go next. I am currently thinking there will be a selection list in the sidebar to add temperature/weather data or rainfall data. That will display a suitable form in the main dashboard area. After entering data the display will return to the selected weather view. Though perhaps I should allow for repeated entries and provide a done button to return to the regular dashboard display?
Will sort that out eventually. For now, I have decided to put the database entry decision into a form in the sidebar. So a bit of refactoring.
Sidebar Add Data Form
I did think about using a single form for both data types: rainfall and temperature/weather. But figured that in the real world that would likely not be done. Could possibly generate a confusing user interface situation. So, two different forms.
Pretty straightforward so here’s the revelevant code.
... ...
with st.sidebar:
... ...
st.divider()
with st.form("add_db"):
add_items = ["Add rainfall data", "Add temperature/weather data"]
add_itm = st.selectbox("Select data to add to database", add_items, index=1)
db_submitted = st.form_submit_button("Proceed")
if db_submitted:
st.header("Add data to database")
else:
if w_item == "Current month's temperatures":
... ...
if db_submitted:
if add_itm == "Add rainfall data":
st.subheader("Add rainfall data")
elif add_itm == "Add temperature/weather data":
st.subheader("Add temperature/weather data")
else:
if w_item == "Month-to-date rainfall":
... ...
And, when I select “Add rainfall data” and click “Proceed”, here’s what I see in the dashboard.

And, fini
You know I think that’s it for this post. Covered a bit of decision making, design, fixed a bug, etc. Reasonable amount of code and project progress. So, will leave the input forms and database updating until next time.
Until then, remember the rabbit and turtle fable. Slow and steady is usually best. Though in our modern world, that seems to have been forgotten.
Resources
- Streamlit | API reference | Input widgets | st.button
- Streamlit | API reference | Execution flow | st.form
- Streamlit | API reference | Execution flow | st.form_submit_button
- Streamlit | Concepts | App design | Button behavior and examples