{"spec_id":"heatmap-mandelbrot","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nheatmap-mandelbrot: Mandelbrot Set Fractal Visualization\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-30\n\"\"\"\n\nimport io\nimport os\nimport sys\n\n\n# Prevent self-import: this file is named bokeh.py, so Python's path search would\n# find it before the installed bokeh package. Remove the script's own directory\n# from sys.path so imports resolve to the installed package.\n_own_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p) != _own_dir]\n\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import BasicTicker, ColorBar, LinearColorMapper, NumeralTickFormatter\nfrom bokeh.plotting import figure\nfrom PIL import Image\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\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\"\n\n# Imprint sequential colormap — brand green (#009E73) → blue (#4467A3), 256 stops\n_t = np.linspace(0, 1, 256)\n_c0 = np.array([0x00, 0x9E, 0x73])\n_c1 = np.array([0x44, 0x67, 0xA3])\n_ramp = np.clip(np.round(_c0 + np.outer(_t, _c1 - _c0)).astype(int), 0, 255)\nANYPLOT_SEQ256 = [f\"#{r:02X}{g:02X}{b:02X}\" for r, g, b in _ramp]\n\n# Data — compute Mandelbrot set on the complex plane\n# y-range padded to maintain 1:1 pixel ratio on the 2400x2400 canvas\n# (inner area ≈ 2000px wide × 2130px tall after min_borders;\n#  x spans 3.5 units → 571.4 px/unit; y needs 2130/571.4 ≈ 3.73 units)\nx_min, x_max = -2.5, 1.0\ny_min, y_max = -1.865, 1.865\ngrid_w, grid_h = 1400, 1050\nmax_iter = 200\n\nreal = np.linspace(x_min, x_max, grid_w)\nimag = np.linspace(y_min, y_max, grid_h)\nreal_grid, imag_grid = np.meshgrid(real, imag)\nc = real_grid + 1j * imag_grid\n\nz = np.zeros_like(c, dtype=complex)\niteration_count = np.zeros(c.shape, dtype=float)\nescaped = np.zeros(c.shape, dtype=bool)\n\nfor i in range(max_iter):\n    mask = ~escaped\n    z[mask] = z[mask] ** 2 + c[mask]\n    newly_escaped = mask & (np.abs(z) > 2.0)\n    # Smooth coloring: normalized iteration count eliminates discrete banding\n    iteration_count[newly_escaped] = i + 1 - np.log2(np.log2(np.abs(z[newly_escaped])))\n    escaped |= newly_escaped\n\n# Interior points (non-escaping) → NaN → rendered as near-black via nan_color\niteration_count[~escaped] = np.nan\n\nvalid = ~np.isnan(iteration_count)\n# Clip low to 5th percentile so the full palette spreads across the visible\n# gradient range instead of being compressed near the fast-escape floor.\nlow_val = float(np.percentile(iteration_count[valid], 5)) if np.any(valid) else 0.0\nhigh_val = float(np.nanmax(iteration_count[valid])) if np.any(valid) else float(max_iter)\n\n# Title — 48 chars, within 67-char baseline, no fontsize scaling needed\ntitle = \"heatmap-mandelbrot · python · bokeh · anyplot.ai\"\n\n# Plot — 2400x2400 square canvas (heatmap category per style guide)\nW, H = 2400, 2400\np = figure(\n    width=W,\n    height=H,\n    x_range=(x_min, x_max),\n    y_range=(y_min, y_max),\n    title=title,\n    x_axis_label=\"Re(c)\",\n    y_axis_label=\"Im(c)\",\n    toolbar_location=None,\n    tools=\"\",\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=220,\n)\n\n# LinearColorMapper with 5th-percentile low clips the flat fast-escape floor,\n# spreading the gradient evenly across the visible range; interior (NaN) → black\nmapper = LinearColorMapper(palette=ANYPLOT_SEQ256, low=low_val, high=high_val, nan_color=\"#000000\")\n\np.image(image=[iteration_count], x=x_min, y=y_min, dw=x_max - x_min, dh=y_max - y_min, color_mapper=mapper)\n\n# Color bar\ncolor_bar = ColorBar(\n    color_mapper=mapper,\n    ticker=BasicTicker(desired_num_ticks=8),\n    formatter=NumeralTickFormatter(format=\"0\"),\n    label_standoff=20,\n    width=55,\n    title=\"Escape Iterations\",\n    title_text_font_size=\"34pt\",\n    title_text_color=INK,\n    major_label_text_font_size=\"34pt\",\n    major_label_text_color=INK_SOFT,\n    title_standoff=24,\n    border_line_color=None,\n    padding=16,\n    background_fill_color=ELEVATED_BG,\n)\np.add_layout(color_bar, \"right\")\n\n# Style — canonical bokeh font sizes for 2400x2400 canvas\np.title.text_font_size = \"50pt\"\np.title.text_color = INK\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\np.xaxis.minor_tick_line_color = None\np.yaxis.minor_tick_line_color = None\np.xgrid.grid_line_color = None\np.ygrid.grid_line_color = None\np.outline_line_color = INK_SOFT\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\n\n# Save HTML artifact (interactive catalog output)\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot via headless Chrome — Chrome's viewport is ~139px shorter than\n# --window-size, so use H + 200 buffer then crop to exact canvas dimensions.\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H + 200}\",\n    \"--hide-scrollbars\",\n    \"--force-device-scale-factor=1\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H + 200)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\nraw = driver.get_screenshot_as_png()\ndriver.quit()\nImage.open(io.BytesIO(raw)).crop((0, 0, W, H)).save(f\"plot-{THEME}.png\")\n"}