{"spec_id":"climograph-walter-lieth","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nclimograph-walter-lieth: Walter-Lieth Climate Diagram\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 84/100 | Created: 2026-06-15\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_hline,\n    geom_line,\n    geom_point,\n    geom_ribbon,\n    geom_segment,\n    geom_text,\n    ggplot,\n    ggsize,\n    labs,\n    scale_color_manual,\n    scale_linetype_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme tokens\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\"\n\n# Imprint palette — semantic mapping: temperature=red, precipitation=blue\nTEMP_COLOR = \"#AE3030\"  # matte red (pos 5) — heat/temperature semantic\nPRECIP_COLOR = \"#4467A3\"  # blue (pos 3) — water/precipitation semantic\n\n# Ribbon alpha: higher in dark mode for visibility on near-black background\nribbon_alpha = 0.28 if THEME == \"light\" else 0.50\n\n# Athens, Greece — classic Mediterranean climate, 1991-2020 normals\nmonth_labels = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\nmonth_num = np.arange(1, 13)\ntemp_c = np.array([9.3, 10.2, 12.5, 16.4, 21.4, 26.1, 29.0, 28.8, 24.4, 19.3, 14.4, 10.7])\nprecip_mm = np.array([57, 37, 37, 23, 15, 7, 6, 7, 15, 51, 56, 71])\n\n# Walter-Lieth 1:2 convention: 20 mm precipitation ↔ 10°C (precip_scaled = precip / 2)\nprecip_scaled = precip_mm / 2.0\n\n# Fill regions — zero-height ribbons are invisible, so no masking needed\nhumid_top = np.maximum(temp_c, precip_scaled)  # = precip_scaled where humid, else = temp_c\narid_bottom = np.minimum(temp_c, precip_scaled)  # = precip_scaled where arid, else = temp_c\n\ndf = pd.DataFrame(\n    {\n        \"month\": month_num,\n        \"temp\": temp_c,\n        \"precip_scaled\": precip_scaled,\n        \"humid_top\": humid_top,\n        \"arid_bottom\": arid_bottom,\n    }\n)\n\n# Long-format data for legend-mapped lines and points\ndf_lines = pd.concat(\n    [\n        pd.DataFrame({\"month\": month_num, \"y\": temp_c, \"series\": \"Temperature\"}),\n        pd.DataFrame({\"month\": month_num, \"y\": precip_scaled, \"series\": \"Precipitation\"}),\n    ],\n    ignore_index=True,\n)\n\n# Manual right y-axis (precipitation scale) — sec_axis not available in lets-plot 4.x\nx_rax = 12.55  # right axis vertical line\nx_rtick = 12.72  # tick mark end and label start\n# Walter-Lieth convention: 0, 20, 40, 60, 100 mm ticks\nright_y = [0, 10, 20, 30, 50]  # °C-scale positions (mm/2)\nright_labels = [\"0\", \"20\", \"40\", \"60\", \"100\"]  # mm values\n\ndf_rax_line = pd.DataFrame({\"x\": [x_rax], \"xend\": [x_rax], \"y\": [-4], \"yend\": [51]})\ndf_rtick_marks = pd.DataFrame({\"x\": [x_rax] * 5, \"xend\": [x_rax + 0.13] * 5, \"y\": right_y, \"yend\": right_y})\ndf_rtick_labels = pd.DataFrame({\"x\": [x_rtick] * 5, \"y\": right_y, \"label\": right_labels})\ndf_rax_title = pd.DataFrame({\"x\": [14.2], \"y\": [23.0], \"label\": [\"Precipitation (mm)\"]})\n\n# Title fontsize: scaled for long title string\ntitle_text = \"Athens, Greece · climograph-walter-lieth · python · letsplot · anyplot.ai\"\ntitle_fs = max(11, round(16 * 67 / len(title_text)))\n\nsubtitle_text = \"107 m a.s.l. │ Tmean = 18.5°C │ P = 383 mm yr⁻¹ │  1991–2020 normals\"\n\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_border=element_blank(),\n    panel_grid_major=element_line(color=INK_SOFT, size=0.2),\n    panel_grid_minor=element_blank(),\n    axis_title=element_text(color=INK, size=12),\n    axis_text=element_text(color=INK_SOFT, size=10),\n    axis_line=element_line(color=INK_SOFT),\n    plot_title=element_text(color=INK, size=title_fs),\n    plot_subtitle=element_text(color=INK_SOFT, size=9),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=10),\n    legend_title=element_text(color=PAGE_BG, size=1),\n    legend_position=\"bottom\",\n)\n\nplot = (\n    ggplot(df, aes(x=\"month\"))\n    # Humid fill (blue): precipitation curve above temperature curve (Jan-Mar, Oct-Dec)\n    + geom_ribbon(aes(ymin=\"temp\", ymax=\"humid_top\"), fill=PRECIP_COLOR, alpha=ribbon_alpha)\n    # Arid fill (red): temperature curve above precipitation curve (Apr-Sep)\n    + geom_ribbon(aes(ymin=\"arid_bottom\", ymax=\"temp\"), fill=TEMP_COLOR, alpha=ribbon_alpha)\n    # Freezing-point reference\n    + geom_hline(yintercept=0, color=INK_SOFT, size=0.5, linetype=\"dashed\")\n    # Temperature and precipitation curves (color + linetype → CVD-safe combined legend)\n    + geom_line(data=df_lines, mapping=aes(x=\"month\", y=\"y\", color=\"series\", linetype=\"series\"), size=1.2)\n    + geom_point(data=df_lines, mapping=aes(x=\"month\", y=\"y\", color=\"series\"), size=2.2)\n    + scale_color_manual(values={\"Temperature\": TEMP_COLOR, \"Precipitation\": PRECIP_COLOR}, name=\" \")\n    + scale_linetype_manual(values={\"Temperature\": \"solid\", \"Precipitation\": \"dashed\"}, name=\" \")\n    # X-axis: month labels; extended range accommodates manual right axis\n    + scale_x_continuous(breaks=list(range(1, 13)), labels=month_labels, limits=(0.4, 15.5))\n    # Left y-axis: temperature (°C) — extended to 53 to show 100mm right-axis tick\n    + scale_y_continuous(name=\"Temperature (°C)\", breaks=[0, 10, 20, 30, 40], limits=(-5, 53))\n    # Manual right y-axis — precipitation (mm), color-coded blue\n    + geom_segment(data=df_rax_line, mapping=aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"yend\"), color=INK_SOFT, size=0.4)\n    + geom_segment(data=df_rtick_marks, mapping=aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"yend\"), color=INK_SOFT, size=0.4)\n    + geom_text(data=df_rtick_labels, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=PRECIP_COLOR, size=3.0, hjust=0)\n    + geom_text(\n        data=df_rax_title, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=PRECIP_COLOR, size=3.5, angle=270, hjust=0.5\n    )\n    + labs(x=\"\", title=title_text, subtitle=subtitle_text)\n    + anyplot_theme\n    + ggsize(800, 450)\n)\n\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}