############ Imports #############
import vizro.plotly.express as px
import vizro.models as vm
from vizro import Vizro
import pandas as pd
from vizro.managers import data_manager
####### Load & prepare data #####
df = pd.read_csv("athens_pollution_csv.txt", sep=",", parse_dates=["date"]).sort_values("date")
season_order = ["Winter", "Spring", "Summer", "Autumn"]
if "month_name" not in df.columns:
df["month_name"] = df["date"].dt.strftime("%B")
if "season" in df.columns:
df["season"] = pd.Categorical(df["season"], categories=season_order, ordered=True)
# Pollutants for radar (exclude AQI)
pollutants = ["pm2_5", "pm10", "no2", "o3", "so2", "nh3", "co"]
# ---- Radar data (median by season, min–max normalized per pollutant) ----
season_meds = df.groupby("season", observed=True)[pollutants].median(numeric_only=True).reset_index()
radar = season_meds.melt(id_vars="season", var_name="pollutant", value_name="value")
radar["value_norm"] = radar.groupby("pollutant")["value"].transform(
lambda s: (s - s.min()) / (s.max() - s.min() + 1e-9)
)
# Register with Vizro
data_manager["athens_pollution"] = df
data_manager["radar_season"] = radar
# ---- Prebuild any figures that need layout tweaks ----
fig_aqi_hist = px.histogram(
data_frame="athens_pollution",
x="aqi",
color="season",
opacity=0.65,
labels={"aqi": "Air Quality Index", "season": "Season"},
category_orders={"season": season_order},
)
fig_aqi_hist.update_layout(barmode="overlay")
########### Model code ############
model = vm.Dashboard(
pages=[
# ================= Page 1: AQI + main distributions =================
vm.Page(
title="AQI & Distributions",
components=[
vm.Graph(
id="aqi_hist",
type="graph",
figure=fig_aqi_hist,
title="AQI Histogram by Season (overlay)",
),
vm.Graph(
id="pm25_box",
type="graph",
figure=px.box(
data_frame="athens_pollution",
x="season",
y="pm2_5",
color="season",
points=False,
labels={"season": "Season", "pm2_5": "PM2.5 (μg/m³)"},
category_orders={"season": season_order},
),
title="PM2.5 Distribution by Season",
),
vm.Graph(
id="pm10_box",
type="graph",
figure=px.box(
data_frame="athens_pollution",
x="season",
y="pm10",
color="season",
points=False,
labels={"season": "Season", "pm10": "PM10 (μg/m³)"},
category_orders={"season": season_order},
),
title="PM10 Distribution by Season",
),
vm.Graph(
id="no2_box",
type="graph",
figure=px.box(
data_frame="athens_pollution",
x="season",
y="no2",
color="season",
points=False,
labels={"season": "Season", "no2": "NO₂ (μg/m³)"},
category_orders={"season": season_order},
),
title="NO₂ Distribution by Season",
),
],
# Balanced 2×2 grid — use indices, not IDs
layout=vm.Layout(
grid=[[0, 1],
[2, 3]]
),
controls=[
vm.Filter(
id="season_filter_pg1",
type="filter",
column="season",
targets=["aqi_hist", "pm25_box", "pm10_box", "no2_box"],
selector=vm.Dropdown(type="dropdown", multi=True),
),
vm.Filter(
id="aqi_range",
type="filter",
column="aqi",
targets=["aqi_hist"],
selector=vm.RangeSlider(type="range_slider"),
),
],
),
# ================= Page 2: Radar + more distributions =================
vm.Page(
title="Seasonal Profile (Radar) + More",
components=[
vm.Graph(
id="radar_plot",
type="graph",
figure=px.line_polar(
data_frame="radar_season",
r="value_norm",
theta="pollutant",
color="season",
line_close=True,
category_orders={"season": season_order, "pollutant": pollutants},
labels={"value_norm": "Normalized Median (0–1)", "pollutant": "Pollutant", "season": "Season"},
range_r=[0, 1],
),
title="Seasonal Median Profiles (Radar, normalized per pollutant)",
),
vm.Graph(
id="o3_box",
type="graph",
figure=px.box(
data_frame="athens_pollution",
x="season",
y="o3",
color="season",
points=False,
labels={"season": "Season", "o3": "O₃ (μg/m³)"},
category_orders={"season": season_order},
),
title="O₃ Distribution by Season",
),
vm.Graph(
id="co_box",
type="graph",
figure=px.box(
data_frame="athens_pollution",
x="season",
y="co",
color="season",
points=False,
labels={"season": "Season", "co": "CO (μg/m³)"},
category_orders={"season": season_order},
),
title="CO Distribution by Season",
),
vm.Graph(
id="nh3_box",
type="graph",
figure=px.box(
data_frame="athens_pollution",
x="season",
y="nh3",
color="season",
points=False,
labels={"season": "Season", "nh3": "NH₃ (μg/m³)"},
category_orders={"season": season_order},
),
title="NH₃ Distribution by Season",
),
],
layout=vm.Layout(
grid=[[0, 1],
[2, 3]]
),
controls=[
vm.Filter(
id="season_filter_pg2",
type="filter",
column="season",
targets=["radar_plot", "o3_box", "co_box", "nh3_box"],
selector=vm.Dropdown(type="dropdown", multi=True),
),
vm.Filter(
id="month_filter_pg2",
type="filter",
column="month_name",
targets=["o3_box", "co_box", "nh3_box"],
selector=vm.Dropdown(type="dropdown", multi=True),
),
],
),
],
theme="vizro_dark",
title="Athens Air Pollution — Balanced Visual Dashboard",
)
Vizro().build(model).run()