{"spec_id":"heatmap-stripes-climate","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nheatmap-stripes-climate: Climate Warming Stripes\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-06-02\n\"\"\"\n\nimport importlib\nimport os\nimport sys\n\nimport numpy as np\n\n\n# Defer pygal import to avoid self-shadowing (this file is named pygal.py)\n_here = sys.path.pop(0)\npygal = importlib.import_module(\"pygal\")\nStyle = importlib.import_module(\"pygal.style\").Style\ncairosvg = importlib.import_module(\"cairosvg\")\nsys.path.insert(0, _here)\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Data - Simulated global temperature anomalies (relative to 1961-1990 baseline)\nnp.random.seed(42)\nyears = list(range(1850, 2025))\nn_years = len(years)\n\nbase_trend = np.concatenate(\n    [\n        np.linspace(-0.35, -0.15, 50),\n        np.linspace(-0.15, -0.25, 30),\n        np.linspace(-0.25, -0.05, 30),\n        np.linspace(-0.05, 0.30, 25),\n        np.linspace(0.30, 1.20, 40),\n    ]\n)\nnoise = np.random.normal(0, 0.10, n_years)\nanomalies = np.round(base_trend + noise, 2)\n\n# Color scale symmetric around zero\nvmax = float(np.percentile(np.abs(anomalies), 90))\nvmin = -vmax\n\n# Diverging blue-to-red color stops (Ed Hawkins warming stripes palette — semantic exception to Imprint continuous cmaps)\nCOLOR_STOPS = [\n    (0.00, (8, 48, 107)),\n    (0.12, (33, 102, 172)),\n    (0.25, (67, 147, 195)),\n    (0.42, (146, 197, 222)),\n    (0.50, (245, 245, 245)),\n    (0.58, (244, 165, 130)),\n    (0.75, (214, 96, 77)),\n    (0.88, (178, 24, 43)),\n    (1.00, (103, 0, 13)),\n]\n\nbar_colors = []\nfor a in anomalies:\n    t = max(0.0, min(1.0, (a - vmin) / (vmax - vmin)))\n    r, g, b = COLOR_STOPS[-1][1]\n    for k in range(len(COLOR_STOPS) - 1):\n        t0, c0 = COLOR_STOPS[k]\n        t1, c1 = COLOR_STOPS[k + 1]\n        if t <= t1:\n            f = (t - t0) / (t1 - t0) if t1 > t0 else 0\n            r = int(c0[0] + (c1[0] - c0[0]) * f)\n            g = int(c0[1] + (c1[1] - c0[1]) * f)\n            b = int(c0[2] + (c1[2] - c0[2]) * f)\n            break\n    bar_colors.append(f\"#{r:02x}{g:02x}{b:02x}\")\n\n# Title — mandatory format; scale fontsize if longer than 67-char baseline\ntitle = \"heatmap-stripes-climate · python · pygal · anyplot.ai\"\ntitle_font_size = max(44, round(66 * min(1.0, 67 / len(title))))\n\n# Pygal style with theme-adaptive chrome\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=(\"#009E73\",),\n    title_font_size=title_font_size,\n    label_font_size=56,\n    major_label_font_size=44,\n    value_font_size=36,\n    legend_font_size=44,\n    font_family=\"sans-serif\",\n    stroke_width=0,\n)\n\n# Shared chart parameters (avoids duplicating config between PNG and HTML)\n_base = {\n    \"width\": 3200,\n    \"height\": 1800,\n    \"style\": custom_style,\n    \"title\": title,\n    \"show_legend\": False,\n    \"show_y_guides\": False,\n    \"show_x_guides\": False,\n    \"show_y_labels\": False,\n    \"margin\": 0,\n    \"margin_top\": 150,\n    \"margin_left\": 10,\n    \"margin_right\": 10,\n    \"spacing\": 0,\n    \"print_values\": False,\n    \"range\": (0, 1),\n    \"stroke\": False,\n    \"truncate_label\": -1,\n}\n\n# PNG — no x-axis labels per spec (pure data visualization, no axes/labels/gridlines)\nchart = pygal.Bar(**_base, show_x_labels=False, margin_bottom=80)\nchart.add(\"Temperature Anomaly\", [{\"value\": 1, \"color\": bar_colors[i]} for i in range(n_years)])\ncairosvg.svg2png(bytestring=chart.render(), write_to=f\"plot-{THEME}.png\")\n\n# Interactive HTML — year/anomaly tooltips leverage pygal SVG interactivity\nhtml_chart = pygal.Bar(**_base, show_x_labels=True, margin_bottom=140)\nhtml_chart.x_labels = [str(y) for y in years]\nhtml_chart.x_labels_major = [str(y) for y in years if y % 25 == 0]\nhtml_chart.show_minor_x_labels = False\nhtml_chart.add(\n    \"Temperature Anomaly\",\n    [{\"value\": 1, \"color\": bar_colors[i], \"label\": f\"{years[i]}: {anomalies[i]:+.2f}°C\"} for i in range(n_years)],\n)\n\nhtml_content = f\"\"\"<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>heatmap-stripes-climate - python - pygal - anyplot.ai</title>\n    <style>\n        body {{ margin: 0; display: flex; justify-content: center; align-items: center;\n               min-height: 100vh; background: {PAGE_BG}; }}\n        .chart {{ max-width: 100%; height: auto; }}\n    </style>\n</head>\n<body>\n    <figure class=\"chart\">\n        {html_chart.render(is_unicode=True)}\n    </figure>\n</body>\n</html>\n\"\"\"\n\nwith open(f\"plot-{THEME}.html\", \"w\", encoding=\"utf-8\") as fout:\n    fout.write(html_content)\n"}