{"spec_id":"heatmap-correlation","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nheatmap-correlation: Correlation Matrix Heatmap\nLibrary: bokeh 3.9.2 | Python 3.13.15\nQuality: 93/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import BasicTicker, ColorBar, ColumnDataSource, HoverTool, LabelSet, LinearColorMapper\nfrom bokeh.plotting import figure\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# Data - realistic financial/economic indicators\nvariables = [\"GDP\", \"Unemployment\", \"Inflation\", \"Interest Rate\", \"Stock Index\", \"Consumer Conf.\", \"Housing\", \"Exports\"]\nn_vars = len(variables)\n\n# Generate realistic correlation matrix with known economic relationships\nbase_corr = np.array(\n    [\n        [1.00, -0.72, 0.35, 0.28, 0.85, 0.78, 0.65, 0.72],  # GDP\n        [-0.72, 1.00, -0.15, -0.22, -0.68, -0.82, -0.55, -0.48],  # Unemployment\n        [0.35, -0.15, 1.00, 0.65, 0.12, -0.25, -0.18, 0.22],  # Inflation\n        [0.28, -0.22, 0.65, 1.00, -0.08, -0.35, -0.42, 0.15],  # Interest Rate\n        [0.85, -0.68, 0.12, -0.08, 1.00, 0.72, 0.58, 0.62],  # Stock Index\n        [0.78, -0.82, -0.25, -0.35, 0.72, 1.00, 0.68, 0.55],  # Consumer Confidence\n        [0.65, -0.55, -0.18, -0.42, 0.58, 0.68, 1.00, 0.45],  # Housing\n        [0.72, -0.48, 0.22, 0.15, 0.62, 0.55, 0.45, 1.00],  # Exports\n    ]\n)\n\n# Mask upper triangle (above the diagonal) to avoid redundant mirrored cells\nmask = np.triu(np.ones_like(base_corr, dtype=bool), k=1)\ncorr_matrix = np.where(mask, np.nan, base_corr)\n\n# Prepare data for heatmap — text color adapts per-cell so it stays legible\n# against both the strongly-saturated ends AND the near-zero midpoint, which\n# is itself theme-adaptive (near-white on light, near-black on dark).\nx_data = []\ny_data = []\nvalues = []\ntext_values = []\ntext_colors = []\ncell_ij = []\n\nfor i, var_y in enumerate(variables):\n    for j, var_x in enumerate(variables):\n        if not np.isnan(corr_matrix[i, j]):\n            x_data.append(var_x)\n            y_data.append(var_y)\n            val = corr_matrix[i, j]\n            values.append(val)\n            text_values.append(f\"{val:.2f}\")\n            text_colors.append(\"#FFFFFF\" if abs(val) > 0.45 else INK)\n            cell_ij.append((i, j))\n\n# Highlight the two strongest off-diagonal relationships with a bold outline\n# so the viewer's eye lands on the most important correlations first.\noff_diag = [(idx, abs(v)) for idx, (v, (i, j)) in enumerate(zip(values, cell_ij, strict=True)) if i != j]\nstrongest = {idx for idx, _ in sorted(off_diag, key=lambda pair: pair[1], reverse=True)[:2]}\ncell_line_colors = [INK if idx in strongest else PAGE_BG for idx in range(len(values))]\ncell_line_widths = [6 if idx in strongest else 2 for idx in range(len(values))]\n\nsource = ColumnDataSource(\n    data={\n        \"x\": x_data,\n        \"y\": y_data,\n        \"values\": values,\n        \"text\": text_values,\n        \"text_color\": text_colors,\n        \"line_color\": cell_line_colors,\n        \"line_width\": cell_line_widths,\n    }\n)\n\n\n# Imprint diverging colormap (matte-red <-> theme-adaptive midpoint <-> blue),\n# built as a 256-stop ramp — see prompts/library/bokeh.md \"Colors\".\ndef _lerp_hex(c0, c1, t):\n    r0, g0, b0 = (int(c0[i : i + 2], 16) for i in (1, 3, 5))\n    r1, g1, b1 = (int(c1[i : i + 2], 16) for i in (1, 3, 5))\n    r, g, b = (int(round(a + (b - a) * t)) for a, b in ((r0, r1), (g0, g1), (b0, b1)))\n    return f\"#{r:02X}{g:02X}{b:02X}\"\n\n\n_midpoint = PAGE_BG\nimprint_div = [_lerp_hex(\"#AE3030\", _midpoint, t / 127.0) for t in range(128)] + [\n    _lerp_hex(_midpoint, \"#4467A3\", t / 127.0) for t in range(128)\n]\n\nmapper = LinearColorMapper(palette=imprint_div, low=-1, high=1)\n\ntitle = \"heatmap-correlation · python · bokeh · anyplot.ai\"\n\n# Square canvas — see prompts/library/bokeh.md \"Canvas — hard rule, no deviation\".\n# `min_border_*` reserve room for the 34/42pt tick + axis-label stack so\n# nothing clips at the PNG edges; `toolbar_location=None` is mandatory —\n# bokeh's default toolbar adds ~30-50px above the plot that would shrink\n# the saved screenshot below the target height.\np = figure(\n    width=2400,\n    height=2400,\n    x_range=variables,\n    y_range=list(reversed(variables)),\n    x_axis_location=\"below\",\n    title=title,\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=50,\n)\n\n# Draw rectangles for heatmap\nrects = p.rect(\n    x=\"x\",\n    y=\"y\",\n    width=0.95,\n    height=0.95,\n    source=source,\n    fill_color={\"field\": \"values\", \"transform\": mapper},\n    line_color=\"line_color\",\n    line_width=\"line_width\",\n)\n\n# Refined hover tooltip — theme-aware card instead of the plain default table\nhover = HoverTool(\n    renderers=[rects],\n    tooltips=f\"\"\"\n    <div style=\"background-color:{ELEVATED_BG}; border:1px solid {INK_SOFT};\n                border-radius:4px; padding:8px 10px; font-size:14px; color:{INK};\">\n        <div><b>@y</b> &times; <b>@x</b></div>\n        <div style=\"color:{INK_SOFT}; margin-top:2px;\">Correlation: <b>@text</b></div>\n    </div>\n    \"\"\",\n)\np.add_tools(hover)\n\n# Text annotations with per-cell adaptive color (see data-prep above)\nlabels = LabelSet(\n    x=\"x\",\n    y=\"y\",\n    text=\"text\",\n    text_color=\"text_color\",\n    source=source,\n    text_align=\"center\",\n    text_baseline=\"middle\",\n    text_font_size=\"28pt\",\n    text_font_style=\"bold\",\n)\np.add_layout(labels)\n\n# Colorbar (fixed -1..1 range for consistent cross-plot interpretation)\ncolor_bar = ColorBar(\n    color_mapper=mapper,\n    ticker=BasicTicker(desired_num_ticks=11),\n    label_standoff=20,\n    width=60,\n    location=(0, 0),\n    title=\"Correlation\",\n    title_text_font_size=\"34pt\",\n    major_label_text_font_size=\"28pt\",\n    title_standoff=15,\n)\np.add_layout(color_bar, \"right\")\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\n\np.title.text_font_size = \"50pt\"\np.title.align = \"center\"\np.title.text_color = INK\n\n# Domain-specific axis labels\np.xaxis.axis_label = \"Economic Indicators\"\np.yaxis.axis_label = \"Economic Indicators\"\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.major_label_orientation = 0.785  # 45 degrees in radians\n\n# Grid and axis line styling — no grid needed on a fully-tiled matrix\np.xgrid.visible = False\np.ygrid.visible = False\np.axis.axis_line_color = None\np.axis.major_tick_line_color = None\n\n# Colorbar styling — bokeh defaults ColorBar.background_fill_color to white,\n# which stays a stark white box on the dark theme unless overridden here.\ncolor_bar.background_fill_color = PAGE_BG\ncolor_bar.title_text_color = INK\ncolor_bar.major_label_text_color = INK_SOFT\n\n# Save as HTML (required catalog artifact)\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome\nW, H = 2400, 2400\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}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\n# Headless Chrome's --window-size sets the OUTER window, which still reserves\n# a phantom title-bar height even headless; pin the viewport exactly via CDP.\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}