| 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": |
| |
| 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: |
| 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}<br>AQI %{y:.0f}<extra></extra>" |
| )) |
|
|
| 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}<br>AQI %{y:.0f}<extra></extra>" |
| )) |
|
|
| |
| 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) |
|
|
| |
| 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=( |
| "<b>Karachi</b><br>" |
| f"AQI: {aqi:.0f}<br>" |
| f"Status: {label}<br>" |
| "Lat: 24.8607 Lon: 67.0011" |
| "<extra></extra>" |
| ), |
| )) |
|
|
| 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 |