Spaces:
Sleeping
Sleeping
harshini9942 commited on
Commit Β·
ab781ed
1
Parent(s): 231c749
Logo added
Browse files- streamlit/dashboard.py +153 -35
streamlit/dashboard.py
CHANGED
|
@@ -16,6 +16,7 @@ Components
|
|
| 16 |
|
| 17 |
# ==================== IMPORTS ====================
|
| 18 |
import streamlit as st
|
|
|
|
| 19 |
import os
|
| 20 |
import pandas as pd
|
| 21 |
import numpy as np
|
|
@@ -468,20 +469,19 @@ def _load_launch_date_csv(launch_csv):
|
|
| 468 |
|
| 469 |
def _scan_existing_nc_for_launch_dates(incois_wmo_set, launch_csv):
|
| 470 |
"""
|
| 471 |
-
Scan already-downloaded more_components/{wmo}_meta.nc
|
| 472 |
-
LAUNCH_DATE for any INCOIS float not yet in the CSV.
|
| 473 |
Returns count of NEW entries added.
|
| 474 |
"""
|
| 475 |
-
target_dir = BASE_DIR / "more_components"
|
| 476 |
-
if not target_dir.exists():
|
| 477 |
-
return 0
|
| 478 |
existing = _load_launch_date_csv(launch_csv)
|
| 479 |
already_have = set(existing["wmo_id"].tolist())
|
| 480 |
new_rows = []
|
| 481 |
for wmo in incois_wmo_set:
|
| 482 |
if wmo in already_have:
|
| 483 |
continue
|
| 484 |
-
meta_path =
|
|
|
|
|
|
|
| 485 |
if not meta_path.exists():
|
| 486 |
continue
|
| 487 |
ld = _read_launch_date_from_nc(meta_path)
|
|
@@ -516,8 +516,17 @@ df_prof["profiler_name"] = df_prof["wmo_id"].map(_meta_pname).fillna("Unknown")
|
|
| 516 |
|
| 517 |
@st.dialog("Float Information", width="large")
|
| 518 |
def show_float_details(wmo):
|
| 519 |
-
|
| 520 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 521 |
|
| 522 |
# Auto-download from IFREMER GDAC if files do not exist
|
| 523 |
if not meta_path.exists() or not prof_path.exists():
|
|
@@ -529,13 +538,13 @@ def show_float_details(wmo):
|
|
| 529 |
else:
|
| 530 |
dac = "incois" # fallback
|
| 531 |
|
| 532 |
-
meta_url = f"
|
| 533 |
-
prof_url = f"
|
| 534 |
|
| 535 |
-
target_dir = BASE_DIR /
|
| 536 |
target_dir.mkdir(exist_ok=True)
|
| 537 |
|
| 538 |
-
with st.spinner(f"Downloading GDAC NetCDF files for {wmo} ({dac})..."):
|
| 539 |
try:
|
| 540 |
if not meta_path.exists():
|
| 541 |
urllib.request.urlretrieve(meta_url, meta_path)
|
|
@@ -830,7 +839,97 @@ st.markdown(
|
|
| 830 |
# Read URL query params for shareable filter state
|
| 831 |
qp = st.query_params
|
| 832 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 833 |
with st.sidebar:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 834 |
st.markdown("## π Filters")
|
| 835 |
|
| 836 |
# ββ Refresh ββ
|
|
@@ -848,6 +947,7 @@ with st.sidebar:
|
|
| 848 |
value=qp.get("wmo", ""),
|
| 849 |
placeholder="e.g. 2902115, 2902116",
|
| 850 |
help="Comma-separated WMO numbers",
|
|
|
|
| 851 |
)
|
| 852 |
|
| 853 |
# ββ Parameter Filter ββ
|
|
@@ -1101,14 +1201,7 @@ col_left, col_right = st.columns([55, 45], gap="medium")
|
|
| 1101 |
with col_left:
|
| 1102 |
# ββ Component 1: Geospatial Float Position Map (PRD Β§7.1) ββ
|
| 1103 |
st.markdown('<div class="stPlotlyChart">', unsafe_allow_html=True)
|
| 1104 |
-
|
| 1105 |
-
with c_title:
|
| 1106 |
-
st.markdown("### π Geographic Float Positions")
|
| 1107 |
-
with c_btn:
|
| 1108 |
-
if st.button("π Reset Map Data", use_container_width=True):
|
| 1109 |
-
if "main_map" in st.session_state:
|
| 1110 |
-
del st.session_state["main_map"]
|
| 1111 |
-
st.rerun()
|
| 1112 |
|
| 1113 |
if len(filt_prof) > 0:
|
| 1114 |
# --- Check map selection from session state ---
|
|
@@ -1173,7 +1266,6 @@ with col_left:
|
|
| 1173 |
lon=group["longitude"].tolist(),
|
| 1174 |
mode="lines+markers+text",
|
| 1175 |
text=group["profile_seq"].astype(str).tolist(),
|
| 1176 |
-
customdata=[[wmo]] * len(group),
|
| 1177 |
textposition="top right",
|
| 1178 |
textfont=dict(size=11, color="white"),
|
| 1179 |
marker=dict(size=7, color=color, opacity=0.9),
|
|
@@ -1231,6 +1323,9 @@ with col_left:
|
|
| 1231 |
if len(map_df) > 12_000:
|
| 1232 |
map_df = map_df.sample(12_000, random_state=42)
|
| 1233 |
|
|
|
|
|
|
|
|
|
|
| 1234 |
fig_map = px.scatter_mapbox(
|
| 1235 |
map_df,
|
| 1236 |
lat="latitude",
|
|
@@ -1246,10 +1341,11 @@ with col_left:
|
|
| 1246 |
"longitude": ":.2f",
|
| 1247 |
},
|
| 1248 |
zoom=2,
|
| 1249 |
-
center={"lat":
|
| 1250 |
category_orders={"institution": list(REGION_COLORS.keys())},
|
| 1251 |
)
|
| 1252 |
fig_map.update_traces(marker=dict(size=8, opacity=0.9))
|
|
|
|
| 1253 |
fig_map.update_layout(
|
| 1254 |
mapbox_style="white-bg",
|
| 1255 |
mapbox_layers=[
|
|
@@ -1302,14 +1398,7 @@ with col_left:
|
|
| 1302 |
with col_right:
|
| 1303 |
st.markdown('<div class="stPlotlyChart">', unsafe_allow_html=True)
|
| 1304 |
# ββ Bar chart (PRD Β§7.2) ββ
|
| 1305 |
-
|
| 1306 |
-
with c_bar_title:
|
| 1307 |
-
st.markdown("### π Number of Floats per DAC")
|
| 1308 |
-
with c_bar_btn:
|
| 1309 |
-
if st.button("π Reset Chart Data", use_container_width=True):
|
| 1310 |
-
if "bar_chart" in st.session_state:
|
| 1311 |
-
del st.session_state["bar_chart"]
|
| 1312 |
-
st.rerun()
|
| 1313 |
|
| 1314 |
if len(filt_prof) > 0 and "dac" in filt_prof.columns:
|
| 1315 |
# Active floats in the last 90 days of each year
|
|
@@ -1881,9 +1970,6 @@ if len(df_meta) > 0:
|
|
| 1881 |
if st.button("π Fetch Missing Launch Dates from GDAC", key="fetch_launch_dates"):
|
| 1882 |
import urllib.request as _urlreq
|
| 1883 |
|
| 1884 |
-
target_dir = BASE_DIR / "more_components"
|
| 1885 |
-
target_dir.mkdir(exist_ok=True)
|
| 1886 |
-
|
| 1887 |
existing_csv = _load_launch_date_csv(launch_csv)
|
| 1888 |
new_rows = []
|
| 1889 |
failed = []
|
|
@@ -1895,6 +1981,18 @@ if len(df_meta) > 0:
|
|
| 1895 |
status_ph.markdown(f"Fetching **{wmo}** ({idx}/{total})β¦")
|
| 1896 |
prog.progress(idx / total)
|
| 1897 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1898 |
meta_path = target_dir / f"{wmo}_meta.nc"
|
| 1899 |
dac = _dac_lookup.get(wmo, "incois")
|
| 1900 |
|
|
@@ -2001,6 +2099,14 @@ if len(df_meta) > 0:
|
|
| 2001 |
)
|
| 2002 |
|
| 2003 |
body_html = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2004 |
for year in pivot.index:
|
| 2005 |
is_total_row = year == "Total"
|
| 2006 |
row_bg = "background:rgba(0,188,212,0.06);" if is_total_row else ""
|
|
@@ -2014,12 +2120,24 @@ if len(df_meta) > 0:
|
|
| 2014 |
for col in pivot.columns:
|
| 2015 |
val = pivot.loc[year, col]
|
| 2016 |
is_tot = is_total_row or col == "Total"
|
| 2017 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2018 |
cell = (
|
| 2019 |
f"{int(val):,}" if val > 0
|
| 2020 |
else "<span style='color:rgba(255,255,255,0.18)'>-</span>"
|
| 2021 |
)
|
| 2022 |
-
row_html += f"<td style='{style}'>{cell}</td>"
|
| 2023 |
body_html += f"<tr style='{row_bg}'>{row_html}</tr>"
|
| 2024 |
|
| 2025 |
st.markdown(
|
|
|
|
| 16 |
|
| 17 |
# ==================== IMPORTS ====================
|
| 18 |
import streamlit as st
|
| 19 |
+
import streamlit.components.v1 as components
|
| 20 |
import os
|
| 21 |
import pandas as pd
|
| 22 |
import numpy as np
|
|
|
|
| 469 |
|
| 470 |
def _scan_existing_nc_for_launch_dates(incois_wmo_set, launch_csv):
|
| 471 |
"""
|
| 472 |
+
Scan already-downloaded more_components/{wmo}_meta.nc or inactive_floats/{wmo}_meta.nc files
|
| 473 |
+
and extract LAUNCH_DATE for any INCOIS float not yet in the CSV.
|
| 474 |
Returns count of NEW entries added.
|
| 475 |
"""
|
|
|
|
|
|
|
|
|
|
| 476 |
existing = _load_launch_date_csv(launch_csv)
|
| 477 |
already_have = set(existing["wmo_id"].tolist())
|
| 478 |
new_rows = []
|
| 479 |
for wmo in incois_wmo_set:
|
| 480 |
if wmo in already_have:
|
| 481 |
continue
|
| 482 |
+
meta_path = BASE_DIR / f"more_components/{wmo}_meta.nc"
|
| 483 |
+
if not meta_path.exists():
|
| 484 |
+
meta_path = BASE_DIR / f"inactive_floats/{wmo}_meta.nc"
|
| 485 |
if not meta_path.exists():
|
| 486 |
continue
|
| 487 |
ld = _read_launch_date_from_nc(meta_path)
|
|
|
|
| 516 |
|
| 517 |
@st.dialog("Float Information", width="large")
|
| 518 |
def show_float_details(wmo):
|
| 519 |
+
# Determine if active/inactive to set target folder
|
| 520 |
+
float_profiles = df_prof[df_prof["wmo_id"] == str(wmo)]
|
| 521 |
+
is_active = False
|
| 522 |
+
if not float_profiles.empty:
|
| 523 |
+
latest_prof_date = float_profiles["date"].max()
|
| 524 |
+
if latest_prof_date is not pd.NaT and latest_prof_date.year >= 2026:
|
| 525 |
+
is_active = True
|
| 526 |
+
|
| 527 |
+
folder_name = "more_components" if is_active else "inactive_floats"
|
| 528 |
+
meta_path = BASE_DIR / f"{folder_name}/{wmo}_meta.nc"
|
| 529 |
+
prof_path = BASE_DIR / f"{folder_name}/{wmo}_prof.nc"
|
| 530 |
|
| 531 |
# Auto-download from IFREMER GDAC if files do not exist
|
| 532 |
if not meta_path.exists() or not prof_path.exists():
|
|
|
|
| 538 |
else:
|
| 539 |
dac = "incois" # fallback
|
| 540 |
|
| 541 |
+
meta_url = f"https://data-argo.ifremer.fr/dac/{dac}/{wmo}/{wmo}_meta.nc"
|
| 542 |
+
prof_url = f"https://data-argo.ifremer.fr/dac/{dac}/{wmo}/{wmo}_prof.nc"
|
| 543 |
|
| 544 |
+
target_dir = BASE_DIR / folder_name
|
| 545 |
target_dir.mkdir(exist_ok=True)
|
| 546 |
|
| 547 |
+
with st.spinner(f"Downloading GDAC NetCDF files for {wmo} ({dac}) to local {folder_name} folder..."):
|
| 548 |
try:
|
| 549 |
if not meta_path.exists():
|
| 550 |
urllib.request.urlretrieve(meta_url, meta_path)
|
|
|
|
| 839 |
# Read URL query params for shareable filter state
|
| 840 |
qp = st.query_params
|
| 841 |
|
| 842 |
+
# ββ Reset Trigger for Map Home Button ββ
|
| 843 |
+
st.markdown(
|
| 844 |
+
"""
|
| 845 |
+
<style>
|
| 846 |
+
/* Hide the reset trigger input container completely */
|
| 847 |
+
div.element-container:has(input[placeholder="reset_trigger_placeholder"]) {
|
| 848 |
+
display: none !important;
|
| 849 |
+
}
|
| 850 |
+
</style>
|
| 851 |
+
""",
|
| 852 |
+
unsafe_allow_html=True
|
| 853 |
+
)
|
| 854 |
+
reset_trigger = st.text_input("", placeholder="reset_trigger_placeholder", key="reset_trigger")
|
| 855 |
+
|
| 856 |
+
if reset_trigger == "true":
|
| 857 |
+
if "main_map" in st.session_state:
|
| 858 |
+
st.session_state.main_map = None
|
| 859 |
+
if "bar_chart" in st.session_state:
|
| 860 |
+
st.session_state.bar_chart = None
|
| 861 |
+
if "last_viewed_wmo" in st.session_state:
|
| 862 |
+
st.session_state.last_viewed_wmo = None
|
| 863 |
+
if "search_wmo_input" in st.session_state:
|
| 864 |
+
st.session_state.search_wmo_input = ""
|
| 865 |
+
st.query_params.update({"wmo": ""})
|
| 866 |
+
st.session_state.reset_trigger = ""
|
| 867 |
+
st.rerun()
|
| 868 |
+
|
| 869 |
+
components.html(
|
| 870 |
+
"""
|
| 871 |
+
<script>
|
| 872 |
+
(function() {
|
| 873 |
+
const parentDoc = window.parent.document;
|
| 874 |
+
const interval = setInterval(() => {
|
| 875 |
+
const resetButtons = parentDoc.querySelectorAll('.modebar-btn[data-val="reset"]');
|
| 876 |
+
if (resetButtons.length > 0) {
|
| 877 |
+
resetButtons.forEach(btn => {
|
| 878 |
+
if (!btn.dataset.hasResetListener) {
|
| 879 |
+
btn.dataset.hasResetListener = "true";
|
| 880 |
+
btn.addEventListener('click', function(e) {
|
| 881 |
+
const input = parentDoc.querySelector('input[placeholder="reset_trigger_placeholder"]');
|
| 882 |
+
if (input) {
|
| 883 |
+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
|
| 884 |
+
nativeInputValueSetter.call(input, "true");
|
| 885 |
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
| 886 |
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
| 887 |
+
input.blur();
|
| 888 |
+
}
|
| 889 |
+
});
|
| 890 |
+
}
|
| 891 |
+
});
|
| 892 |
+
}
|
| 893 |
+
}, 500);
|
| 894 |
+
})();
|
| 895 |
+
</script>
|
| 896 |
+
""",
|
| 897 |
+
height=0,
|
| 898 |
+
width=0
|
| 899 |
+
)
|
| 900 |
+
|
| 901 |
with st.sidebar:
|
| 902 |
+
# ββ circular logo ββ
|
| 903 |
+
try:
|
| 904 |
+
import base64
|
| 905 |
+
logo_path = BASE_DIR / "incois_logo.jpg"
|
| 906 |
+
if logo_path.exists():
|
| 907 |
+
with open(logo_path, "rb") as f:
|
| 908 |
+
logo_data = base64.b64encode(f.read()).decode("utf-8")
|
| 909 |
+
st.markdown(
|
| 910 |
+
f"""
|
| 911 |
+
<div style="display: flex; justify-content: center; margin-top: 10px; margin-bottom: 20px;">
|
| 912 |
+
<div style="
|
| 913 |
+
width: 140px;
|
| 914 |
+
height: 140px;
|
| 915 |
+
border-radius: 50%;
|
| 916 |
+
overflow: hidden;
|
| 917 |
+
border: 4px solid #1f6feb;
|
| 918 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
|
| 919 |
+
background-color: white;
|
| 920 |
+
display: flex;
|
| 921 |
+
justify-content: center;
|
| 922 |
+
align-items: center;
|
| 923 |
+
">
|
| 924 |
+
<img src="data:image/jpeg;base64,{logo_data}" style="width: 100%; height: 100%; object-fit: cover;" />
|
| 925 |
+
</div>
|
| 926 |
+
</div>
|
| 927 |
+
""",
|
| 928 |
+
unsafe_allow_html=True
|
| 929 |
+
)
|
| 930 |
+
except Exception as e:
|
| 931 |
+
pass
|
| 932 |
+
|
| 933 |
st.markdown("## π Filters")
|
| 934 |
|
| 935 |
# ββ Refresh ββ
|
|
|
|
| 947 |
value=qp.get("wmo", ""),
|
| 948 |
placeholder="e.g. 2902115, 2902116",
|
| 949 |
help="Comma-separated WMO numbers",
|
| 950 |
+
key="search_wmo_input",
|
| 951 |
)
|
| 952 |
|
| 953 |
# ββ Parameter Filter ββ
|
|
|
|
| 1201 |
with col_left:
|
| 1202 |
# ββ Component 1: Geospatial Float Position Map (PRD Β§7.1) ββ
|
| 1203 |
st.markdown('<div class="stPlotlyChart">', unsafe_allow_html=True)
|
| 1204 |
+
st.markdown("### π Geographic Float Positions")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1205 |
|
| 1206 |
if len(filt_prof) > 0:
|
| 1207 |
# --- Check map selection from session state ---
|
|
|
|
| 1266 |
lon=group["longitude"].tolist(),
|
| 1267 |
mode="lines+markers+text",
|
| 1268 |
text=group["profile_seq"].astype(str).tolist(),
|
|
|
|
| 1269 |
textposition="top right",
|
| 1270 |
textfont=dict(size=11, color="white"),
|
| 1271 |
marker=dict(size=7, color=color, opacity=0.9),
|
|
|
|
| 1323 |
if len(map_df) > 12_000:
|
| 1324 |
map_df = map_df.sample(12_000, random_state=42)
|
| 1325 |
|
| 1326 |
+
center_lat = float(map_df["latitude"].mean()) if len(map_df) > 0 else -10.0
|
| 1327 |
+
center_lon = float(map_df["longitude"].mean()) if len(map_df) > 0 else 80.0
|
| 1328 |
+
|
| 1329 |
fig_map = px.scatter_mapbox(
|
| 1330 |
map_df,
|
| 1331 |
lat="latitude",
|
|
|
|
| 1341 |
"longitude": ":.2f",
|
| 1342 |
},
|
| 1343 |
zoom=2,
|
| 1344 |
+
center={"lat": center_lat, "lon": center_lon},
|
| 1345 |
category_orders={"institution": list(REGION_COLORS.keys())},
|
| 1346 |
)
|
| 1347 |
fig_map.update_traces(marker=dict(size=8, opacity=0.9))
|
| 1348 |
+
|
| 1349 |
fig_map.update_layout(
|
| 1350 |
mapbox_style="white-bg",
|
| 1351 |
mapbox_layers=[
|
|
|
|
| 1398 |
with col_right:
|
| 1399 |
st.markdown('<div class="stPlotlyChart">', unsafe_allow_html=True)
|
| 1400 |
# ββ Bar chart (PRD Β§7.2) ββ
|
| 1401 |
+
st.markdown("### π Number of Floats per DAC")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1402 |
|
| 1403 |
if len(filt_prof) > 0 and "dac" in filt_prof.columns:
|
| 1404 |
# Active floats in the last 90 days of each year
|
|
|
|
| 1970 |
if st.button("π Fetch Missing Launch Dates from GDAC", key="fetch_launch_dates"):
|
| 1971 |
import urllib.request as _urlreq
|
| 1972 |
|
|
|
|
|
|
|
|
|
|
| 1973 |
existing_csv = _load_launch_date_csv(launch_csv)
|
| 1974 |
new_rows = []
|
| 1975 |
failed = []
|
|
|
|
| 1981 |
status_ph.markdown(f"Fetching **{wmo}** ({idx}/{total})β¦")
|
| 1982 |
prog.progress(idx / total)
|
| 1983 |
|
| 1984 |
+
# Determine if active/inactive to set target folder
|
| 1985 |
+
float_profiles = df_prof[df_prof["wmo_id"] == str(wmo)]
|
| 1986 |
+
is_active = False
|
| 1987 |
+
if not float_profiles.empty:
|
| 1988 |
+
latest_prof_date = float_profiles["date"].max()
|
| 1989 |
+
if latest_prof_date is not pd.NaT and latest_prof_date.year >= 2026:
|
| 1990 |
+
is_active = True
|
| 1991 |
+
|
| 1992 |
+
folder_name = "more_components" if is_active else "inactive_floats"
|
| 1993 |
+
target_dir = BASE_DIR / folder_name
|
| 1994 |
+
target_dir.mkdir(exist_ok=True)
|
| 1995 |
+
|
| 1996 |
meta_path = target_dir / f"{wmo}_meta.nc"
|
| 1997 |
dac = _dac_lookup.get(wmo, "incois")
|
| 1998 |
|
|
|
|
| 2099 |
)
|
| 2100 |
|
| 2101 |
body_html = ""
|
| 2102 |
+
|
| 2103 |
+
# Calculate max value for heatmap scaling (excluding Totals)
|
| 2104 |
+
try:
|
| 2105 |
+
heatmap_max = pivot.drop("Total", axis=0).drop("Total", axis=1).max().max()
|
| 2106 |
+
except:
|
| 2107 |
+
heatmap_max = 1
|
| 2108 |
+
if heatmap_max <= 0: heatmap_max = 1
|
| 2109 |
+
|
| 2110 |
for year in pivot.index:
|
| 2111 |
is_total_row = year == "Total"
|
| 2112 |
row_bg = "background:rgba(0,188,212,0.06);" if is_total_row else ""
|
|
|
|
| 2120 |
for col in pivot.columns:
|
| 2121 |
val = pivot.loc[year, col]
|
| 2122 |
is_tot = is_total_row or col == "Total"
|
| 2123 |
+
|
| 2124 |
+
if is_tot:
|
| 2125 |
+
style = "font-weight:bold;color:#FFB74D;"
|
| 2126 |
+
cell_bg = ""
|
| 2127 |
+
else:
|
| 2128 |
+
style = ""
|
| 2129 |
+
if val > 0:
|
| 2130 |
+
intensity = min(val / heatmap_max, 1.0)
|
| 2131 |
+
# Cyan color (#00BCD4) with dynamic opacity based on value
|
| 2132 |
+
cell_bg = f"background:rgba(0, 188, 212, {max(0.1, intensity * 0.9)});"
|
| 2133 |
+
else:
|
| 2134 |
+
cell_bg = ""
|
| 2135 |
+
|
| 2136 |
cell = (
|
| 2137 |
f"{int(val):,}" if val > 0
|
| 2138 |
else "<span style='color:rgba(255,255,255,0.18)'>-</span>"
|
| 2139 |
)
|
| 2140 |
+
row_html += f"<td style='{style}{cell_bg}'>{cell}</td>"
|
| 2141 |
body_html += f"<tr style='{row_bg}'>{row_html}</tr>"
|
| 2142 |
|
| 2143 |
st.markdown(
|