{"spec_id":"heatmap-polar","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nheatmap-polar: Polar Heatmap for Cyclic Two-Dimensional Data\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 82/100 | Created: 2026-05-13\n\"\"\"\n\nimport math\nimport os\nimport sys\n\n\ndef _plot():\n    # Remove current directory from sys.path to prevent self-import of this file (pygal.py)\n    this_dir = os.path.dirname(os.path.abspath(__file__))\n    sys.path[:] = [p for p in sys.path if os.path.realpath(p) != os.path.realpath(this_dir)]\n\n    import cairosvg\n    import matplotlib.cm as cm\n    import numpy as np\n    import pygal\n    from pygal.style import Style\n\n    THEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n    PAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\n    INK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\n    INK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n    INK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n    # Data: monthly household energy consumption (kWh) across 5 years\n    np.random.seed(42)\n    years = [2019, 2020, 2021, 2022, 2023]\n    months = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\n    n_years, n_months = len(years), len(months)\n\n    base = np.array([90, 85, 74, 63, 58, 65, 74, 71, 63, 67, 78, 92], dtype=float)\n    data = np.zeros((n_years, n_months))\n    for i in range(n_years):\n        data[i] = base * (1.0 + i * 0.018) + np.random.normal(0, 2.5, n_months)\n\n    vmin, vmax = data.min(), data.max()\n\n    def val_to_hex(val):\n        t = float(np.clip((val - vmin) / (vmax - vmin), 0, 1))\n        r, g, b, _ = cm.viridis(t)\n        return f\"#{int(r * 255):02x}{int(g * 255):02x}{int(b * 255):02x}\"\n\n    # Representative viridis color per year for pygal legend swatches\n    year_colors = tuple(val_to_hex(float(data[i].mean())) for i in range(n_years))\n\n    # pygal Style: all chrome derived from ANYPLOT_THEME tokens\n    custom_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=year_colors,\n        title_font_size=54,\n        legend_font_size=38,\n        label_font_size=36,\n        value_font_size=32,\n    )\n\n    # pygal.Pie with multiple series renders as concentric rings (inner → outer per year)\n    # value=1 for every sector ensures equal angular width (heatmap: color encodes kWh, not size)\n    chart = pygal.Pie(\n        width=4800,\n        height=2700,\n        style=custom_style,\n        inner_radius=0.22,\n        title=\"heatmap-polar · pygal · anyplot.ai\",\n        legend_at_bottom=True,\n        print_values=False,\n        show_legend=True,\n        half_pie=False,\n    )\n\n    for i, year in enumerate(years):\n        chart.add(\n            str(year),\n            [\n                {\"value\": 1, \"color\": val_to_hex(data[i, j]), \"label\": f\"{months[j]}: {data[i, j]:.0f} kWh\"}\n                for j in range(n_months)\n            ],\n        )\n\n    # Render pygal chart to SVG (includes interactive JS for HTML output)\n    svg_bytes = chart.render()\n    svg_str = svg_bytes.decode(\"utf-8\")\n\n    # SVG post-processing: add subtitle, month labels, and colorbar\n    # Estimated pie layout on 4800×2700 with title (~100px) and bottom legend (~130px):\n    # chart area height ≈ 2470px → radius ≈ 1080, center ≈ (2400, 1280)\n    CX, CY, R_OUTER = 2400, 1280, 1080\n\n    subtitle_svg = (\n        f'<text x=\"{CX}\" y=\"138\" text-anchor=\"middle\" '\n        f'fill=\"{INK_SOFT}\" font-size=\"36\" font-family=\"sans-serif\">'\n        f\"Monthly Household Energy Consumption by Year (kWh)</text>\"\n    )\n\n    # Month labels outside the outermost ring, clockwise from 12 o'clock\n    month_labels_svg = []\n    for j, month in enumerate(months):\n        angle = (j + 0.5) * 2 * math.pi / n_months  # radians clockwise from north\n        lx = CX + (R_OUTER + 88) * math.sin(angle)\n        ly = CY - (R_OUTER + 88) * math.cos(angle)\n        month_labels_svg.append(\n            f'<text x=\"{lx:.0f}\" y=\"{ly:.0f}\" text-anchor=\"middle\" dominant-baseline=\"middle\" '\n            f'fill=\"{INK}\" font-size=\"42\" font-weight=\"500\" font-family=\"sans-serif\">{month}</text>'\n        )\n\n    # Colorbar: SVG linearGradient (far right — viridis, top=vmax, bottom=vmin)\n    cb_x, cb_y_top, cb_h, cb_w = 4420, 320, 1900, 72\n    grad_stops = \"\".join(\n        f'<stop offset=\"{k * 5}%\" style=\"stop-color:{val_to_hex(vmax - (k / 20) * (vmax - vmin))};stop-opacity:1\"/>'\n        for k in range(21)\n    )\n    colorbar_svg = f\"\"\"\n<defs>\n  <linearGradient id=\"cbGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n    {grad_stops}\n  </linearGradient>\n</defs>\n<rect x=\"{cb_x}\" y=\"{cb_y_top}\" width=\"{cb_w}\" height=\"{cb_h}\" fill=\"url(#cbGrad)\"/>\n<rect x=\"{cb_x}\" y=\"{cb_y_top}\" width=\"{cb_w}\" height=\"{cb_h}\" fill=\"none\" stroke=\"{INK_MUTED}\" stroke-width=\"2\" opacity=\"0.4\"/>\n<text x=\"{cb_x + cb_w // 2}\" y=\"{cb_y_top - 28}\" text-anchor=\"middle\" fill=\"{INK_MUTED}\" font-size=\"30\" font-family=\"sans-serif\">kWh</text>\n<text x=\"{cb_x + cb_w + 18}\" y=\"{cb_y_top}\" dominant-baseline=\"hanging\" fill=\"{INK_SOFT}\" font-size=\"30\" font-family=\"sans-serif\">{vmax:.0f}</text>\n<text x=\"{cb_x + cb_w + 18}\" y=\"{cb_y_top + cb_h // 2}\" dominant-baseline=\"middle\" fill=\"{INK_SOFT}\" font-size=\"30\" font-family=\"sans-serif\">{(vmin + vmax) / 2:.0f}</text>\n<text x=\"{cb_x + cb_w + 18}\" y=\"{cb_y_top + cb_h}\" dominant-baseline=\"auto\" fill=\"{INK_SOFT}\" font-size=\"30\" font-family=\"sans-serif\">{vmin:.0f}</text>\n\"\"\"\n\n    extra = subtitle_svg + \"\\n\" + \"\\n\".join(month_labels_svg) + colorbar_svg\n\n    # Inject before the last </svg> tag\n    parts = svg_str.rsplit(\"</svg>\", 1)\n    svg_str = parts[0] + extra + \"\\n</svg>\" + parts[1]\n\n    # Save HTML (pygal's interactive SVG-in-browser output format)\n    html_str = (\n        f\"<!DOCTYPE html><html><head><meta charset='utf-8'>\"\n        f\"<style>body{{margin:0;background:{PAGE_BG};}}</style></head>\"\n        f\"<body>{svg_str}</body></html>\"\n    )\n    with open(f\"plot-{THEME}.html\", \"w\", encoding=\"utf-8\") as f:\n        f.write(html_str)\n\n    # Save PNG via cairosvg (same pipeline as pygal.render_to_png internally)\n    cairosvg.svg2png(\n        bytestring=svg_str.encode(\"utf-8\"), write_to=f\"plot-{THEME}.png\", output_width=4800, output_height=2700\n    )\n\n\n_plot()\n"}