{"spec_id":"climograph-walter-lieth","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nclimograph-walter-lieth: Walter-Lieth Climate Diagram\nLibrary: plotly 6.8.0 | Python 3.13.13\nQuality: 88/100 | Created: 2026-06-15\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent the local plotly.py from shadowing the installed plotly package.\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if p != _here]\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom scipy.interpolate import PchipInterpolator\n\n\n# Theme tokens — Imprint style guide\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette — semantic roles for this diagram\nTEMP_COLOR = \"#AE3030\"  # matte red — temperature / heat (Imprint pos 5)\nPRECIP_COLOR = \"#4467A3\"  # blue — water / precipitation  (Imprint pos 3)\n\n# Station: Naples, Italy — 1991–2020 climate normals (Mediterranean Cfsa)\nSTATION = \"Naples, Italy\"\nELEVATION = 17  # m a.s.l.\nTEMP_MEAN = 17.4  # °C annual mean\nPRECIP_TOT = 902  # mm annual total\n\nMONTHS = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\nTEMP = [9.2, 9.7, 11.5, 14.7, 19.2, 23.6, 26.8, 27.1, 23.8, 18.5, 14.1, 10.4]\nPRECIP = [97, 80, 72, 63, 38, 22, 14, 26, 80, 142, 148, 120]\n\n\ndef p_to_y(mm):\n    \"\"\"Walter-Lieth transform: precipitation mm → temperature-axis units.\n    2:1 scale up to 100 mm (10°C ↔ 20 mm); compressed 10:1 above 100 mm.\"\"\"\n    return mm / 2.0 if mm <= 100 else 50.0 + (mm - 100) / 10.0\n\n\n# Smooth curves via PCHIP interpolation in original units, then transform\nxi = np.arange(12, dtype=float)\nxf = np.linspace(0, 11, 1200)\n\nt_fine = PchipInterpolator(xi, TEMP)(xf)\np_fine_mm = np.clip(PchipInterpolator(xi, PRECIP)(xf), 0, None)\np_fine = np.array([p_to_y(v) for v in p_fine_mm])\n\nP100 = 50.0  # 100 mm line in y-axis (temperature) units\nY_MIN, Y_MAX = -10.0, 62.0\n\n\ndef extract_segs(x, lo, hi, mask):\n    \"\"\"Split arrays into contiguous segments where mask is True.\"\"\"\n    segs, n, i = [], len(mask), 0\n    while i < n:\n        if mask[i]:\n            j = i\n            while j < n and mask[j]:\n                j += 1\n            segs.append((x[i:j], lo[i:j], hi[i:j]))\n            i = j\n        else:\n            i += 1\n    return segs\n\n\n# Classify each fine-grid point\nhumid_mask = p_fine > t_fine\nperhumid_mask = p_fine > P100\n\nhumid_segs = extract_segs(xf, t_fine, p_fine, humid_mask)\narid_segs = extract_segs(xf, p_fine, t_fine, ~humid_mask)\nperhumid_segs = extract_segs(xf, np.full_like(p_fine, P100), p_fine, perhumid_mask)\n\nfig = go.Figure()\n\n# Humid fill — blue, between temp curve and precipitation curve (below 100 mm)\nfor sx, sl, sh in humid_segs:\n    sh_clip = np.minimum(sh, P100)\n    if np.any(sh_clip > sl + 0.05):\n        px = np.concatenate([sx, sx[::-1]]).tolist()\n        py = np.concatenate([sh_clip, sl[::-1]]).tolist()\n        fig.add_trace(\n            go.Scatter(\n                x=px,\n                y=py,\n                fill=\"toself\",\n                fillcolor=\"rgba(68,103,163,0.28)\",\n                line={\"width\": 0},\n                showlegend=False,\n                hoverinfo=\"skip\",\n            )\n        )\n\n# Perhumid fill — solid blue above the 100 mm threshold line\nfor sx, sl, sh in perhumid_segs:\n    px = np.concatenate([sx, sx[::-1]]).tolist()\n    py = np.concatenate([sh, sl[::-1]]).tolist()\n    fig.add_trace(\n        go.Scatter(\n            x=px,\n            y=py,\n            fill=\"toself\",\n            fillcolor=\"rgba(68,103,163,0.78)\",\n            line={\"width\": 0},\n            showlegend=False,\n            hoverinfo=\"skip\",\n        )\n    )\n\n# Arid fill — light red, between precipitation curve and temperature curve\nfor sx, sl, sh in arid_segs:\n    px = np.concatenate([sx, sx[::-1]]).tolist()\n    py = np.concatenate([sh, sl[::-1]]).tolist()\n    fig.add_trace(\n        go.Scatter(\n            x=px,\n            y=py,\n            fill=\"toself\",\n            fillcolor=\"rgba(174,48,48,0.22)\",\n            line={\"width\": 0},\n            showlegend=False,\n            hoverinfo=\"skip\",\n        )\n    )\n\n# Legend swatches for fill regions\nfig.add_trace(\n    go.Scatter(\n        x=[None],\n        y=[None],\n        mode=\"markers\",\n        name=\"Humid period\",\n        marker={\"symbol\": \"square\", \"size\": 13, \"color\": \"rgba(68,103,163,0.45)\"},\n        showlegend=True,\n    )\n)\nfig.add_trace(\n    go.Scatter(\n        x=[None],\n        y=[None],\n        mode=\"markers\",\n        name=\"Perhumid (> 100 mm)\",\n        marker={\"symbol\": \"square\", \"size\": 13, \"color\": \"rgba(68,103,163,0.85)\"},\n        showlegend=True,\n    )\n)\nfig.add_trace(\n    go.Scatter(\n        x=[None],\n        y=[None],\n        mode=\"markers\",\n        name=\"Arid period\",\n        marker={\"symbol\": \"square\", \"size\": 13, \"color\": \"rgba(174,48,48,0.42)\"},\n        showlegend=True,\n    )\n)\n\n# Precipitation curve (values transformed to temperature-axis units)\nfig.add_trace(\n    go.Scatter(\n        x=list(range(12)),\n        y=[p_to_y(p) for p in PRECIP],\n        mode=\"lines+markers\",\n        name=\"Precipitation\",\n        line={\"color\": PRECIP_COLOR, \"width\": 3},\n        marker={\"color\": PRECIP_COLOR, \"size\": 7, \"line\": {\"color\": PAGE_BG, \"width\": 1.5}},\n        customdata=PRECIP,\n        hovertemplate=\"%{customdata} mm<extra>Precipitation</extra>\",\n        showlegend=True,\n    )\n)\n\n# Temperature curve\nfig.add_trace(\n    go.Scatter(\n        x=list(range(12)),\n        y=TEMP,\n        mode=\"lines+markers\",\n        name=\"Temperature\",\n        line={\"color\": TEMP_COLOR, \"width\": 3},\n        marker={\"color\": TEMP_COLOR, \"size\": 7, \"line\": {\"color\": PAGE_BG, \"width\": 1.5}},\n        hovertemplate=\"%{y:.1f} °C<extra>Temperature</extra>\",\n        showlegend=True,\n    )\n)\n\n# Ghost trace to anchor the right (precipitation) y-axis\nfig.add_trace(\n    go.Scatter(\n        x=[0, 11],\n        y=[Y_MIN, Y_MAX],\n        yaxis=\"y2\",\n        mode=\"markers\",\n        marker={\"color\": \"rgba(0,0,0,0.01)\", \"size\": 1},\n        showlegend=False,\n        hoverinfo=\"skip\",\n    )\n)\n\n# Reference lines\nfig.add_shape(type=\"line\", x0=-0.5, x1=11.5, y0=P100, y1=P100, line={\"color\": INK_MUTED, \"width\": 1, \"dash\": \"dot\"})\nfig.add_shape(type=\"line\", x0=-0.5, x1=11.5, y0=0, y1=0, line={\"color\": INK_SOFT, \"width\": 1})\n\n# Frost bands — months with mean temperature below 0°C (Naples: none)\nfor i, t in enumerate(TEMP):\n    if t < 0:\n        fig.add_shape(\n            type=\"rect\",\n            x0=i - 0.4,\n            x1=i + 0.4,\n            y0=Y_MIN,\n            y1=Y_MIN + 2,\n            fillcolor=PRECIP_COLOR,\n            line={\"width\": 0},\n            opacity=0.85,\n        )\n\n# Title — scaled for length per style-guide formula\ntitle_str = \"Naples, Italy · climograph-walter-lieth · python · plotly · anyplot.ai\"\nn = len(title_str)\ntitle_fs = max(round(16 * 67 / n), 11) if n > 67 else 16\n\n# Right-axis tick positions (precipitation mm mapped to y-axis units)\nraxis_mm = [0, 20, 40, 60, 80, 100, 150]\nraxis_y = [p_to_y(m) for m in raxis_mm]\n\nfig.update_layout(\n    autosize=False,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK, \"family\": \"Arial, sans-serif\"},\n    title={\"text\": title_str, \"font\": {\"size\": title_fs, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"},\n    margin={\"l\": 80, \"r\": 85, \"t\": 75, \"b\": 55},\n    xaxis={\n        \"tickvals\": list(range(12)),\n        \"ticktext\": MONTHS,\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"linecolor\": INK_SOFT,\n        \"showgrid\": False,\n        \"range\": [-0.5, 11.5],\n        \"title\": {\"text\": \"Month\", \"font\": {\"size\": 12, \"color\": INK}},\n    },\n    yaxis={\n        \"title\": {\"text\": \"Temperature (°C)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickvals\": [-10, 0, 10, 20, 30],\n        \"ticktext\": [\"-10\", \"0\", \"10\", \"20\", \"30\"],\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": INK_SOFT,\n        \"zerolinewidth\": 1.5,\n        \"range\": [Y_MIN, Y_MAX],\n        \"showgrid\": True,\n    },\n    yaxis2={\n        \"overlaying\": \"y\",\n        \"side\": \"right\",\n        \"range\": [Y_MIN, Y_MAX],\n        \"tickvals\": raxis_y,\n        \"ticktext\": [f\"{m}\" for m in raxis_mm],\n        \"title\": {\"text\": \"Precipitation (mm)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"linecolor\": INK_SOFT,\n        \"showgrid\": False,\n    },\n    legend={\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n        \"font\": {\"color\": INK_SOFT, \"size\": 10},\n        \"x\": 0.99,\n        \"y\": 0.99,\n        \"xanchor\": \"right\",\n        \"yanchor\": \"top\",\n        \"tracegroupgap\": 2,\n    },\n)\n\n# Station metadata annotation — top-left inside the plot area\nmeta_text = f\"<b>{STATION}</b><br>{ELEVATION} m a.s.l.<br>T = {TEMP_MEAN}°C  ·  P = {PRECIP_TOT} mm\"\nfig.add_annotation(\n    xref=\"x\",\n    yref=\"y\",\n    x=0.2,\n    y=59.5,\n    text=meta_text,\n    showarrow=False,\n    align=\"left\",\n    bgcolor=ELEVATED_BG,\n    bordercolor=INK_SOFT,\n    borderwidth=1,\n    font={\"size\": 10, \"color\": INK},\n    xanchor=\"left\",\n    yanchor=\"top\",\n    opacity=0.95,\n)\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}