File size: 5,385 Bytes
2da5913
0813777
2da5913
 
 
 
 
 
761fcf3
2da5913
 
 
761fcf3
2da5913
 
 
 
 
 
0813777
2da5913
0813777
 
 
2da5913
 
 
 
 
 
 
 
 
 
 
0813777
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
761fcf3
2da5913
761fcf3
2da5913
0813777
 
2da5913
 
0813777
 
2da5913
761fcf3
2da5913
0813777
 
2da5913
0813777
2da5913
761fcf3
0813777
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2da5913
 
 
 
 
0813777
2da5913
0813777
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80a2229
0813777
 
 
80a2229
 
 
 
0813777
80a2229
0813777
 
80a2229
0813777
2da5913
0813777
 
 
80a2229
0813777
 
 
 
 
 
 
2da5913
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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}<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>"
    ))

    # 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=(
            "<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