github-actions[bot] commited on
Commit ·
5cf93b5
1
Parent(s): 5af8aff
Deploy from GitHub Actions
Browse files- components.py +41 -0
- pages.py +75 -43
components.py
CHANGED
|
@@ -62,6 +62,42 @@ def render_sidebar_analyte_multiselect(
|
|
| 62 |
)
|
| 63 |
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
def render_filtered_data_preview(
|
| 66 |
df: pd.DataFrame,
|
| 67 |
display_columns: List[str],
|
|
@@ -168,12 +204,17 @@ def render_records_by_year(raw_df: pd.DataFrame, reporting_month: int) -> None:
|
|
| 168 |
|
| 169 |
yearly_data = yearly_data[["Reporting Year", "Reporting Period", "Records"]]
|
| 170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
st.dataframe(
|
| 172 |
yearly_data.style.format(
|
| 173 |
{"Records": "{:,}"}
|
| 174 |
), # use this instead of column_config for Records column
|
| 175 |
hide_index=True,
|
| 176 |
use_container_width=True,
|
|
|
|
| 177 |
)
|
| 178 |
|
| 179 |
|
|
|
|
| 62 |
)
|
| 63 |
|
| 64 |
|
| 65 |
+
def render_sidebar_analyte_pills(
|
| 66 |
+
all_analytes: List[str],
|
| 67 |
+
default_analytes: Optional[List[str]] = None,
|
| 68 |
+
key_prefix: str = "",
|
| 69 |
+
help_text: str = "Choose one or more analytes to display.",
|
| 70 |
+
) -> List[str | None]:
|
| 71 |
+
"""Render analyte pills"""
|
| 72 |
+
if default_analytes is None:
|
| 73 |
+
default_analytes = []
|
| 74 |
+
|
| 75 |
+
# Filter out any default values that aren't in the options list
|
| 76 |
+
valid_defaults = [
|
| 77 |
+
analyte for analyte in default_analytes if analyte in all_analytes
|
| 78 |
+
]
|
| 79 |
+
|
| 80 |
+
widget_key = f"{key_prefix}_analyte_select"
|
| 81 |
+
|
| 82 |
+
if f"{widget_key}_previous_value" not in st.session_state:
|
| 83 |
+
values = valid_defaults
|
| 84 |
+
else:
|
| 85 |
+
values = st.session_state[f"{widget_key}_previous_value"]
|
| 86 |
+
|
| 87 |
+
def on_analyte_pills_change():
|
| 88 |
+
st.session_state[f"{widget_key}_previous_value"] = st.session_state[widget_key]
|
| 89 |
+
|
| 90 |
+
return st.sidebar.pills(
|
| 91 |
+
"Select Analytes:",
|
| 92 |
+
selection_mode="multi",
|
| 93 |
+
default=values,
|
| 94 |
+
options=all_analytes,
|
| 95 |
+
key=widget_key,
|
| 96 |
+
help=help_text,
|
| 97 |
+
on_change=on_analyte_pills_change,
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
def render_filtered_data_preview(
|
| 102 |
df: pd.DataFrame,
|
| 103 |
display_columns: List[str],
|
|
|
|
| 204 |
|
| 205 |
yearly_data = yearly_data[["Reporting Year", "Reporting Period", "Records"]]
|
| 206 |
|
| 207 |
+
row_height = 36
|
| 208 |
+
max_height = 600
|
| 209 |
+
calculated_height = min(len(yearly_data) * row_height, max_height) + 38
|
| 210 |
+
|
| 211 |
st.dataframe(
|
| 212 |
yearly_data.style.format(
|
| 213 |
{"Records": "{:,}"}
|
| 214 |
), # use this instead of column_config for Records column
|
| 215 |
hide_index=True,
|
| 216 |
use_container_width=True,
|
| 217 |
+
height=calculated_height,
|
| 218 |
)
|
| 219 |
|
| 220 |
|
pages.py
CHANGED
|
@@ -22,7 +22,6 @@ from components import (
|
|
| 22 |
render_correlation_plots,
|
| 23 |
render_filtered_data_preview,
|
| 24 |
render_sidebar_analyte_multiselect,
|
| 25 |
-
render_sidebar_position_filter_selectbox,
|
| 26 |
render_stations_map,
|
| 27 |
)
|
| 28 |
from dashboard_analytics import log_visit
|
|
@@ -350,46 +349,60 @@ def calendar_heatmaps_section():
|
|
| 350 |
st.title("Calendar Heatmaps")
|
| 351 |
st.info(
|
| 352 |
"""
|
| 353 |
-
💡 You can customize the colormaps using the **Plot Settings** expander in the sidebar.
|
| 354 |
-
|
| 355 |
📅 Calendar heatmaps are grouped by calendar year instead of reporting year.
|
|
|
|
|
|
|
| 356 |
"""
|
| 357 |
)
|
| 358 |
raw_df = st.session_state.data["raw_df"]
|
| 359 |
raw_df["Date"] = pd.to_datetime(raw_df["Activity_Start_Date_Time"]).dt.date
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
default_analytes = [
|
| 362 |
"Temperature, Water",
|
| 363 |
"Dissolved Oxygen",
|
| 364 |
"Salinity",
|
| 365 |
"pH",
|
| 366 |
"Turbidity",
|
| 367 |
-
"Depth, Secchi Disk Depth",
|
| 368 |
-
"Fecal Coliform (MPN)",
|
| 369 |
-
"Total Nitrogen",
|
| 370 |
-
"Total Phosphorus",
|
| 371 |
-
"Chlorophyll-uncorrected",
|
| 372 |
]
|
| 373 |
|
| 374 |
selected_analytes = render_sidebar_analyte_multiselect(
|
| 375 |
all_analytes=sorted(raw_df["Org_Analyte_Name"].unique()),
|
| 376 |
default_analytes=default_analytes,
|
| 377 |
key_prefix="calendar",
|
| 378 |
-
help_text="
|
| 379 |
)
|
| 380 |
if not selected_analytes:
|
| 381 |
st.warning("Please select at least one analyte to display.")
|
| 382 |
return
|
| 383 |
|
| 384 |
# Filter Options
|
| 385 |
-
st.sidebar.markdown("### Filter Options")
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
|
|
|
| 391 |
)
|
| 392 |
-
|
| 393 |
|
| 394 |
def format_colormap_option(option):
|
| 395 |
append = ""
|
|
@@ -432,7 +445,7 @@ def calendar_heatmaps_section():
|
|
| 432 |
`Depth, Secchi Disk Depth` : `Blues_r` _(reversed blues)_
|
| 433 |
`All other analytes` : `Blues` _(blue)_
|
| 434 |
"""
|
| 435 |
-
with st.sidebar.expander("
|
| 436 |
colormap = st.radio(
|
| 437 |
"Color Scheme",
|
| 438 |
options=[
|
|
@@ -467,14 +480,23 @@ def calendar_heatmaps_section():
|
|
| 467 |
|
| 468 |
# Filter data
|
| 469 |
plot_df = raw_df.copy()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 470 |
if sector_filter != "All":
|
| 471 |
plot_df = plot_df[plot_df["Sector"] == sector_filter]
|
| 472 |
-
if
|
| 473 |
-
plot_df = plot_df[plot_df["Sample_Position"] ==
|
| 474 |
if not plot_df.empty:
|
| 475 |
for analyte in selected_analytes:
|
|
|
|
|
|
|
| 476 |
try:
|
| 477 |
-
fig = plot_calendar_heatmap(
|
|
|
|
|
|
|
| 478 |
st.pyplot(fig)
|
| 479 |
except ValueError as e:
|
| 480 |
st.warning(str(e))
|
|
@@ -752,6 +774,23 @@ def trends_by_station_section():
|
|
| 752 |
)
|
| 753 |
)
|
| 754 |
raw_df = st.session_state.data["raw_df"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 755 |
default_analytes = [
|
| 756 |
"Dissolved Oxygen",
|
| 757 |
"Salinity",
|
|
@@ -762,21 +801,6 @@ def trends_by_station_section():
|
|
| 762 |
"Total Nitrogen",
|
| 763 |
"Total Phosphorus",
|
| 764 |
]
|
| 765 |
-
st.sidebar.markdown("### Filter Options")
|
| 766 |
-
|
| 767 |
-
selected_station = st.sidebar.selectbox(
|
| 768 |
-
"Station:",
|
| 769 |
-
sorted(raw_df["Station_Number"].unique()),
|
| 770 |
-
index=sorted(raw_df["Station_Number"].unique()).index("3.20"),
|
| 771 |
-
)
|
| 772 |
-
selected_position = st.sidebar.segmented_control(
|
| 773 |
-
"Sample Position:",
|
| 774 |
-
("All", "Surface", "Bottom"),
|
| 775 |
-
default="All",
|
| 776 |
-
selection_mode="single",
|
| 777 |
-
)
|
| 778 |
-
# if no position is selected, default to "All"
|
| 779 |
-
selected_position = selected_position or "All"
|
| 780 |
selected_analytes = render_sidebar_analyte_multiselect(
|
| 781 |
all_analytes=sorted(raw_df["Org_Analyte_Name"].unique()),
|
| 782 |
default_analytes=default_analytes,
|
|
@@ -786,20 +810,22 @@ def trends_by_station_section():
|
|
| 786 |
st.warning("Please select at least one analyte to display.")
|
| 787 |
return
|
| 788 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 789 |
filtered_df = raw_df.query("Station_Number == @selected_station")
|
| 790 |
if selected_position != "All":
|
| 791 |
filtered_df = filtered_df.query("Sample_Position == @selected_position")
|
| 792 |
|
| 793 |
csv_buffer = io.StringIO()
|
| 794 |
filtered_df.to_csv(csv_buffer, index=False)
|
| 795 |
-
st.sidebar.download_button(
|
| 796 |
-
label="Download Filtered Data (CSV)",
|
| 797 |
-
data=csv_buffer.getvalue(),
|
| 798 |
-
file_name=f"station_{selected_station}_{selected_position.lower()}_data.csv",
|
| 799 |
-
mime="text/csv",
|
| 800 |
-
)
|
| 801 |
|
| 802 |
-
with st.sidebar.expander("
|
| 803 |
display_columns = [
|
| 804 |
"Activity_Start_Date_Time",
|
| 805 |
"Sample_Position",
|
|
@@ -809,6 +835,12 @@ def trends_by_station_section():
|
|
| 809 |
"Reporting_Year",
|
| 810 |
]
|
| 811 |
render_filtered_data_preview(filtered_df, display_columns)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 812 |
|
| 813 |
if not filtered_df.empty:
|
| 814 |
fig = plot_trends_by_station(filtered_df, selected_analytes, selected_position)
|
|
|
|
| 22 |
render_correlation_plots,
|
| 23 |
render_filtered_data_preview,
|
| 24 |
render_sidebar_analyte_multiselect,
|
|
|
|
| 25 |
render_stations_map,
|
| 26 |
)
|
| 27 |
from dashboard_analytics import log_visit
|
|
|
|
| 349 |
st.title("Calendar Heatmaps")
|
| 350 |
st.info(
|
| 351 |
"""
|
|
|
|
|
|
|
| 352 |
📅 Calendar heatmaps are grouped by calendar year instead of reporting year.
|
| 353 |
+
|
| 354 |
+
💡 You can customize the colormaps using the **Plot Settings** expander in the sidebar.
|
| 355 |
"""
|
| 356 |
)
|
| 357 |
raw_df = st.session_state.data["raw_df"]
|
| 358 |
raw_df["Date"] = pd.to_datetime(raw_df["Activity_Start_Date_Time"]).dt.date
|
| 359 |
+
sector_filter = st.sidebar.selectbox(
|
| 360 |
+
"Sector:",
|
| 361 |
+
["All"] + sorted(raw_df["Sector"].unique().tolist()),
|
| 362 |
+
index=0,
|
| 363 |
+
key="calendar_sector_select",
|
| 364 |
+
)
|
| 365 |
+
# Add year filter slider
|
| 366 |
+
years = sorted(raw_df["Activity_Start_Date_Time"].dt.year.unique())
|
| 367 |
+
default_min_year = 2018 if 2018 in years else min(years)
|
| 368 |
+
if len(years) >= 2:
|
| 369 |
+
year_range = st.sidebar.slider(
|
| 370 |
+
"Year Range:",
|
| 371 |
+
min_value=min(years),
|
| 372 |
+
max_value=max(years),
|
| 373 |
+
value=(default_min_year, max(years)),
|
| 374 |
+
key="calendar_year_range",
|
| 375 |
+
)
|
| 376 |
+
else:
|
| 377 |
+
year_range = (min(years), max(years))
|
| 378 |
default_analytes = [
|
| 379 |
"Temperature, Water",
|
| 380 |
"Dissolved Oxygen",
|
| 381 |
"Salinity",
|
| 382 |
"pH",
|
| 383 |
"Turbidity",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
]
|
| 385 |
|
| 386 |
selected_analytes = render_sidebar_analyte_multiselect(
|
| 387 |
all_analytes=sorted(raw_df["Org_Analyte_Name"].unique()),
|
| 388 |
default_analytes=default_analytes,
|
| 389 |
key_prefix="calendar",
|
| 390 |
+
help_text="Select analytes to display.",
|
| 391 |
)
|
| 392 |
if not selected_analytes:
|
| 393 |
st.warning("Please select at least one analyte to display.")
|
| 394 |
return
|
| 395 |
|
| 396 |
# Filter Options
|
| 397 |
+
# st.sidebar.markdown("### Filter Options")
|
| 398 |
+
|
| 399 |
+
selected_position = st.sidebar.segmented_control(
|
| 400 |
+
"Sample Position:",
|
| 401 |
+
("Surface", "Bottom"),
|
| 402 |
+
default="Surface",
|
| 403 |
+
selection_mode="single",
|
| 404 |
)
|
| 405 |
+
selected_position = selected_position or "Surface"
|
| 406 |
|
| 407 |
def format_colormap_option(option):
|
| 408 |
append = ""
|
|
|
|
| 445 |
`Depth, Secchi Disk Depth` : `Blues_r` _(reversed blues)_
|
| 446 |
`All other analytes` : `Blues` _(blue)_
|
| 447 |
"""
|
| 448 |
+
with st.sidebar.expander("Color Settings", expanded=False):
|
| 449 |
colormap = st.radio(
|
| 450 |
"Color Scheme",
|
| 451 |
options=[
|
|
|
|
| 480 |
|
| 481 |
# Filter data
|
| 482 |
plot_df = raw_df.copy()
|
| 483 |
+
# Add year range filter
|
| 484 |
+
plot_df = plot_df[
|
| 485 |
+
(plot_df["Activity_Start_Date_Time"].dt.year >= year_range[0])
|
| 486 |
+
& (plot_df["Activity_Start_Date_Time"].dt.year <= year_range[1])
|
| 487 |
+
]
|
| 488 |
if sector_filter != "All":
|
| 489 |
plot_df = plot_df[plot_df["Sector"] == sector_filter]
|
| 490 |
+
if selected_position != "All":
|
| 491 |
+
plot_df = plot_df[plot_df["Sample_Position"] == selected_position]
|
| 492 |
if not plot_df.empty:
|
| 493 |
for analyte in selected_analytes:
|
| 494 |
+
if not analyte:
|
| 495 |
+
continue
|
| 496 |
try:
|
| 497 |
+
fig = plot_calendar_heatmap(
|
| 498 |
+
plot_df, analyte, colormap, selected_position
|
| 499 |
+
)
|
| 500 |
st.pyplot(fig)
|
| 501 |
except ValueError as e:
|
| 502 |
st.warning(str(e))
|
|
|
|
| 774 |
)
|
| 775 |
)
|
| 776 |
raw_df = st.session_state.data["raw_df"]
|
| 777 |
+
# Create a list of station options combining number and name
|
| 778 |
+
station_options = sorted(
|
| 779 |
+
[
|
| 780 |
+
f"{num}. {raw_df[raw_df['Station_Number'] == num]['Name'].iloc[0]}"
|
| 781 |
+
for num in sorted(raw_df["Station_Number"].unique())
|
| 782 |
+
]
|
| 783 |
+
)
|
| 784 |
+
|
| 785 |
+
selected_station_full = st.sidebar.selectbox(
|
| 786 |
+
"Station:",
|
| 787 |
+
station_options,
|
| 788 |
+
index=station_options.index(
|
| 789 |
+
next(opt for opt in station_options if opt.startswith("3.20"))
|
| 790 |
+
),
|
| 791 |
+
)
|
| 792 |
+
# Extract just the station number from the selection
|
| 793 |
+
selected_station = selected_station_full.split(". ")[0]
|
| 794 |
default_analytes = [
|
| 795 |
"Dissolved Oxygen",
|
| 796 |
"Salinity",
|
|
|
|
| 801 |
"Total Nitrogen",
|
| 802 |
"Total Phosphorus",
|
| 803 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 804 |
selected_analytes = render_sidebar_analyte_multiselect(
|
| 805 |
all_analytes=sorted(raw_df["Org_Analyte_Name"].unique()),
|
| 806 |
default_analytes=default_analytes,
|
|
|
|
| 810 |
st.warning("Please select at least one analyte to display.")
|
| 811 |
return
|
| 812 |
|
| 813 |
+
selected_position = st.sidebar.segmented_control(
|
| 814 |
+
"Sample Position:",
|
| 815 |
+
("Surface", "Bottom"),
|
| 816 |
+
default="Surface",
|
| 817 |
+
selection_mode="single",
|
| 818 |
+
)
|
| 819 |
+
selected_position = selected_position or "Surface"
|
| 820 |
+
|
| 821 |
filtered_df = raw_df.query("Station_Number == @selected_station")
|
| 822 |
if selected_position != "All":
|
| 823 |
filtered_df = filtered_df.query("Sample_Position == @selected_position")
|
| 824 |
|
| 825 |
csv_buffer = io.StringIO()
|
| 826 |
filtered_df.to_csv(csv_buffer, index=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 827 |
|
| 828 |
+
with st.sidebar.expander("View and Download Data"):
|
| 829 |
display_columns = [
|
| 830 |
"Activity_Start_Date_Time",
|
| 831 |
"Sample_Position",
|
|
|
|
| 835 |
"Reporting_Year",
|
| 836 |
]
|
| 837 |
render_filtered_data_preview(filtered_df, display_columns)
|
| 838 |
+
st.download_button(
|
| 839 |
+
label="Download Station Data",
|
| 840 |
+
data=csv_buffer.getvalue(),
|
| 841 |
+
file_name=f"station_{selected_station}_{selected_position.lower()}_data.csv",
|
| 842 |
+
mime="text/csv",
|
| 843 |
+
)
|
| 844 |
|
| 845 |
if not filtered_df.empty:
|
| 846 |
fig = plot_trends_by_station(filtered_df, selected_analytes, selected_position)
|