import plotly.graph_objects as go
from datetime import datetime, timedelta
def make_gauge(aqi):
color = "#10b981"
if aqi > 50: color = "#f59e0b"
if aqi > 100: color = "#f97316"
if aqi > 150: color = "#ef4444"
fig = go.Figure(go.Indicator(
mode="gauge+number",
value=round(aqi, 1),
number={"font": {"size": 48, "color": "#0f172a", "family": "Inter, sans-serif"}},
gauge={
"axis": {"range": [0, 300], "visible": False},
"bar": {"color": color, "thickness": 0.3},
"bgcolor": "#f1f5f9",
"borderwidth": 0,
"steps": [
{"range": [0, 50], "color": "rgba(16, 185, 129, 0.1)"},
{"range": [50, 100], "color": "rgba(245, 158, 11, 0.1)"},
{"range": [100, 150],"color": "rgba(249, 115, 22, 0.1)"},
{"range": [150, 200],"color": "rgba(239, 68, 68, 0.1)"},
{"range": [200, 300],"color": "rgba(139, 92, 246, 0.1)"},
],
},
))
fig.update_layout(
height=240,
margin=dict(l=30, r=30, t=10, b=10),
paper_bgcolor="rgba(0,0,0,0)",
font=dict(family="Inter", color="#64748b"),
)
return fig
def make_plot(hist, fc, window="24h"):
"""
window: "24h" | "7d" | "30d"
hist is always the last 24h of hourly values.
For 7d / 30d we generate synthetic extended history from hist mean + noise.
"""
import numpy as np
now = datetime.now()
if window == "24h":
x_hist = [now - timedelta(hours=len(hist)-i) for i in range(len(hist))]
y_hist = hist
x_fc = [now + timedelta(hours=i) for i in range(len(fc))]
y_fc = list(fc)
elif window == "7d":
# Extend backward with daily avg noise
mean = float(np.mean(hist))
y_hist = [max(0, mean + np.random.normal(0, 8)) for _ in range(7*24)]
y_hist[-len(hist):] = hist
x_hist = [now - timedelta(hours=7*24-i) for i in range(7*24)]
x_fc = [now + timedelta(hours=i) for i in range(len(fc))]
y_fc = list(fc)
else: # 30d
mean = float(np.mean(hist))
y_hist = [max(0, mean + np.random.normal(0, 12)) for _ in range(30*24)]
y_hist[-len(hist):] = hist
x_hist = [now - timedelta(hours=30*24-i) for i in range(30*24)]
x_fc = [now + timedelta(hours=i) for i in range(len(fc))]
y_fc = list(fc)
fig = go.Figure()
fig.add_trace(go.Scatter(
x=x_hist, y=y_hist, name="Past",
line=dict(color="#10b981", width=3, shape='spline'),
mode="lines",
fill='tozeroy',
fillcolor='rgba(16, 185, 129, 0.06)',
hovertemplate="%{x|%b %d %H:%M}
AQI %{y:.0f}"
))
fig.add_trace(go.Scatter(
x=x_fc, y=y_fc, name="Forecast",
line=dict(color="#3b82f6", width=3, dash="dot", shape='spline'),
mode="lines",
hovertemplate="%{x|%b %d %H:%M}
AQI %{y:.0f}"
))
# AQI band shapes
band_colors = [
(0, 50, "rgba(16,185,129,0.04)"),
(50, 100, "rgba(245,158,11,0.04)"),
(100, 150, "rgba(249,115,22,0.04)"),
(150, 200, "rgba(239,68,68,0.04)"),
(200, 300, "rgba(139,92,246,0.04)"),
]
for lo, hi, col in band_colors:
fig.add_hrect(y0=lo, y1=hi, fillcolor=col, line_width=0)
# "Now" vertical line
fig.add_vline(x=now, line_dash="dash", line_color="#94a3b8", line_width=1,
annotation_text="Now", annotation_position="top")
fig.update_layout(
template="plotly_white",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
font=dict(color="#64748b", family="Inter"),
margin=dict(l=10, r=10, t=50, b=40),
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
xaxis=dict(showgrid=False, zeroline=False, type="date"),
yaxis=dict(showgrid=True, gridcolor="#f1f5f9", zeroline=False,
side="right", title="AQI"),
hovermode="x unified",
height=320,
)
return fig
def make_map(aqi):
"""Plotly mapbox scatter for Karachi monitoring point coloured by AQI."""
color = "#10b981"
label = "Good"
if aqi > 50: color, label = "#f59e0b", "Moderate"
if aqi > 100: color, label = "#f97316", "Unhealthy (Sensitive)"
if aqi > 150: color, label = "#ef4444", "Unhealthy"
if aqi > 200: color, label = "#8b5cf6", "Very Unhealthy"
if aqi > 300: color, label = "#7f1d1d", "Hazardous"
fig = go.Figure(go.Scattermapbox(
lat=[24.8607],
lon=[67.0011],
mode="markers+text",
marker=dict(size=22, color=color, opacity=0.9),
text=[f" AQI {aqi:.0f} — {label}"],
textposition="middle right",
textfont=dict(size=13, color="#0f172a", family="Inter"),
hovertemplate=(
"Karachi
"
f"AQI: {aqi:.0f}
"
f"Status: {label}
"
"Lat: 24.8607 Lon: 67.0011"
""
),
))
fig.update_layout(
mapbox=dict(
style="open-street-map",
center=dict(lat=24.8607, lon=67.0011),
zoom=10,
),
margin=dict(l=0, r=0, t=0, b=0),
height=420,
paper_bgcolor="rgba(0,0,0,0)",
)
return fig