{"spec_id":"heatmap-mandelbrot","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nheatmap-mandelbrot: Mandelbrot Set Fractal Visualization\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 81/100 | Updated: 2026-05-30\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Background\" + \"Theme-adaptive Chrome\")\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# Data - Mandelbrot set with smooth escape-time coloring (eliminates discrete banding)\nx_min, x_max = -2.5, 1.0\ny_min, y_max = -1.25, 1.25\nmax_iter = 100\nnx, ny = 800, 600\n\nstep_x = (x_max - x_min) / nx\nstep_y = (y_max - y_min) / ny\nreal = np.linspace(x_min, x_max, nx, endpoint=False)\nimag = np.linspace(y_min, y_max, ny, endpoint=False)\nreal_grid, imag_grid = np.meshgrid(real, imag)\nc = real_grid + 1j * imag_grid\n\nz = np.zeros_like(c)\nsmooth_iter = np.zeros(c.shape, dtype=float)\nescaped = np.zeros(c.shape, dtype=bool)\n\nfor i in range(max_iter):\n    active = ~escaped\n    z[active] = z[active] ** 2 + c[active]\n    newly_escaped = active & (np.abs(z) > 2)\n    smooth_iter[newly_escaped] = i + 1 - np.log(np.log(np.abs(z[newly_escaped]))) / np.log(2)\n    escaped[newly_escaped] = True\n\nflat_real = real_grid.ravel()\nflat_imag = imag_grid.ravel()\nflat_iter = smooth_iter.ravel()\nflat_escaped = escaped.ravel()\n\ndf_interior = pd.DataFrame(\n    {\n        \"real\": flat_real[~flat_escaped],\n        \"imaginary\": flat_imag[~flat_escaped],\n        \"real2\": flat_real[~flat_escaped] + step_x,\n        \"imaginary2\": flat_imag[~flat_escaped] + step_y,\n    }\n)\n\ndf_exterior = pd.DataFrame(\n    {\n        \"real\": flat_real[flat_escaped],\n        \"imaginary\": flat_imag[flat_escaped],\n        \"real2\": flat_real[flat_escaped] + step_x,\n        \"imaginary2\": flat_imag[flat_escaped] + step_y,\n        \"iterations\": flat_iter[flat_escaped],\n    }\n)\n\n# Plot\nalt.data_transformers.disable_max_rows()\n\nx_scale = alt.Scale(domain=[x_min, x_max])\ny_scale = alt.Scale(domain=[y_min, y_max])\n\ntitle = \"heatmap-mandelbrot · python · altair · anyplot.ai\"\nn = len(title)\nratio = 67 / n if n > 67 else 1.0\ntitle_fontsize = max(11, round(16 * ratio))\n\n# Interior layer - black for points inside the Mandelbrot set\ninterior = (\n    alt.Chart(df_interior)\n    .mark_rect(color=\"#000000\")\n    .encode(x=alt.X(\"real:Q\", scale=x_scale), x2=\"real2:Q\", y=alt.Y(\"imaginary:Q\", scale=y_scale), y2=\"imaginary2:Q\")\n)\n\n# Exterior layer - Imprint sequential colormap (brand green → blue) for escape iterations\nexterior = (\n    alt.Chart(df_exterior)\n    .mark_rect()\n    .encode(\n        x=alt.X(\"real:Q\", title=\"Real (Re)\", scale=x_scale),\n        x2=\"real2:Q\",\n        y=alt.Y(\"imaginary:Q\", title=\"Imaginary (Im)\", scale=y_scale),\n        y2=\"imaginary2:Q\",\n        color=alt.Color(\n            \"iterations:Q\",\n            scale=alt.Scale(range=[\"#009E73\", \"#4467A3\"]),\n            legend=alt.Legend(\n                title=\"Escape Iterations\", titleFontSize=10, labelFontSize=10, gradientLength=200, gradientThickness=15\n            ),\n        ),\n        tooltip=[\n            alt.Tooltip(\"real:Q\", title=\"Real\", format=\".4f\"),\n            alt.Tooltip(\"imaginary:Q\", title=\"Imaginary\", format=\".4f\"),\n            alt.Tooltip(\"iterations:Q\", title=\"Iterations\", format=\".1f\"),\n        ],\n    )\n)\n\n# Square canvas (2400×2400) - canonical for heatmaps; inner view 395×395 + 20px padding\nchart = (\n    (interior + exterior)\n    .interactive()\n    .properties(\n        width=395,\n        height=395,\n        background=PAGE_BG,\n        title=alt.Title(\n            title,\n            subtitle=[\"z(n+1) = z(n)² + c  ·  max 100 iterations  ·  escape radius 2\"],\n            fontSize=title_fontsize,\n            subtitleFontSize=12,\n            subtitleColor=INK_SOFT,\n            anchor=\"start\",\n            offset=12,\n        ),\n        padding={\"left\": 20, \"right\": 20, \"top\": 20, \"bottom\": 20},\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        grid=False,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=12,\n    )\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK, padding=8)\n    .configure_title(color=INK)\n)\n\n# Save - square canvas target: 2400×2400\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\nTW, TH = 2400, 2400\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\nchart.save(f\"plot-{THEME}.html\")\n"}