{"spec_id":"heatmap-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nheatmap-basic: Basic Heatmap\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 94/100 | Updated: 2026-05-28\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove current script directory to prevent self-import (file is named altair.py)\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if p not in (\"\", \".\") and os.path.abspath(p) != _here]\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint diverging colormap midpoint — theme-adaptive\nDIV_MID = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\n\n# Data — correlation matrix for 8 weather variables\nnp.random.seed(42)\nvariables = [\n    \"Temperature\",\n    \"Humidity\",\n    \"Wind Speed\",\n    \"Pressure\",\n    \"Visibility\",\n    \"Cloud Cover\",\n    \"Precipitation\",\n    \"UV Index\",\n]\n\nn_samples = 200\nraw = np.random.randn(n_samples, len(variables))\n\n# Inject realistic correlations; multiplier 1.2 on Visibility pushes it below -0.8\nraw[:, 1] += raw[:, 0] * 0.6  # Humidity ~ Temperature\nraw[:, 5] += raw[:, 1] * 0.7  # Cloud Cover ~ Humidity\nraw[:, 6] += raw[:, 5] * 0.65  # Precipitation ~ Cloud Cover\nraw[:, 4] -= raw[:, 5] * 1.2  # Visibility strongly inversely ~ Cloud Cover (< -0.8)\nraw[:, 7] -= raw[:, 5] * 0.7  # UV Index inversely ~ Cloud Cover\nraw[:, 7] += raw[:, 0] * 0.5  # UV Index ~ Temperature\nraw[:, 3] -= raw[:, 0] * 0.4  # Pressure inversely ~ Temperature\nraw[:, 2] += raw[:, 3] * 0.3  # Wind Speed ~ Pressure\n\ncorr = np.corrcoef(raw.T)\n\naxis_order = list(variables)\n\ndf = pd.DataFrame(\n    [\n        {\"Row\": row_var, \"Column\": col_var, \"value\": round(corr[i, j], 2)}\n        for i, row_var in enumerate(variables)\n        for j, col_var in enumerate(variables)\n    ]\n)\n\n# Title — 44 chars < 67 baseline → ratio = 1.0 → default fontSize 16\ntitle_str = \"heatmap-basic · python · altair · anyplot.ai\"\n\n# Text annotation color: all light on dark theme; adaptive on light theme\nif THEME == \"dark\":\n    text_color_enc = alt.value(\"#F0EFE8\")\nelse:\n    text_color_enc = (\n        alt.when((alt.datum.value > 0.55) | (alt.datum.value < -0.55))\n        .then(alt.value(\"#ffffff\"))\n        .otherwise(alt.value(INK))\n    )\n\n# Heatmap layer — Imprint diverging colormap (#AE3030 → midpoint → #4467A3)\nheatmap = (\n    alt.Chart(df)\n    .mark_rect(stroke=\"#ffffff\", strokeWidth=1.5, cornerRadius=2)\n    .encode(\n        x=alt.X(\n            \"Column:N\",\n            title=None,\n            sort=axis_order,\n            axis=alt.Axis(labelFontSize=12, labelAngle=-45, orient=\"top\", labelPadding=6),\n        ),\n        y=alt.Y(\n            \"Row:N\",\n            title=\"Weather Variable\",\n            sort=axis_order,\n            axis=alt.Axis(labelFontSize=12, titleFontSize=12, labelPadding=6, titlePadding=8),\n        ),\n        color=alt.Color(\n            \"value:Q\",\n            scale=alt.Scale(range=[\"#AE3030\", DIV_MID, \"#4467A3\"], domain=[-1, 1], domainMid=0),\n            legend=alt.Legend(\n                title=\"Correlation\",\n                titleFontSize=12,\n                labelFontSize=11,\n                gradientLength=240,\n                gradientThickness=14,\n                titlePadding=6,\n                offset=10,\n                direction=\"vertical\",\n                values=[-1, -0.5, 0, 0.5, 1],\n                format=\"+.1f\",\n            ),\n        ),\n        tooltip=[\n            alt.Tooltip(\"Column:N\", title=\"Column\"),\n            alt.Tooltip(\"Row:N\", title=\"Row\"),\n            alt.Tooltip(\"value:Q\", title=\"Correlation\", format=\".2f\"),\n        ],\n    )\n)\n\n# Bold borders for strong correlations (|r| ≥ 0.7) — theme-adaptive stroke\nhighlight = (\n    alt.Chart(df)\n    .transform_filter((alt.datum.value >= 0.7) | (alt.datum.value <= -0.7))\n    .mark_rect(stroke=INK, strokeWidth=2.5, filled=False, cornerRadius=2)\n    .encode(x=alt.X(\"Column:N\", sort=axis_order), y=alt.Y(\"Row:N\", sort=axis_order))\n)\n\n# Cell value annotations\ntext = (\n    alt.Chart(df)\n    .mark_text(fontSize=11, fontWeight=\"bold\")\n    .encode(\n        x=alt.X(\"Column:N\", sort=axis_order),\n        y=alt.Y(\"Row:N\", sort=axis_order),\n        text=alt.Text(\"value:Q\", format=\".2f\"),\n        color=text_color_enc,\n    )\n)\n\nchart = (\n    (heatmap + highlight + text)\n    .properties(\n        width=350,\n        height=380,\n        background=PAGE_BG,\n        title=alt.Title(\n            title_str,\n            subtitle=[\n                \"Visibility tracks inversely with Cloud Cover (r ≈ −0.85) — the dominant structural link.\",\n                \"Warm colors = negative, cool = positive. Bold borders mark |r| ≥ 0.7.\",\n            ],\n            fontSize=14,\n            subtitleFontSize=11,\n            subtitleColor=INK_MUTED,\n            color=INK,\n            anchor=\"start\",\n            offset=10,\n        ),\n        padding={\"left\": 10, \"right\": 10, \"top\": 10, \"bottom\": 10},\n    )\n    .configure_axis(grid=False, domainColor=INK_SOFT, tickColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n    .configure_view(fill=PAGE_BG, strokeWidth=0)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\n# Save PNG — target: 2400 × 2400 (square, 1:1 for symmetric heatmap)\nTW, TH = 2400, 2400\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\n# Save HTML\nchart.save(f\"plot-{THEME}.html\")\n"}