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