{"spec_id":"heatmap-adjacency","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nheatmap-adjacency: Network Adjacency Matrix Heatmap\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 87/100 | Created: 2026-05-08\n\"\"\"\n\n# ruff: noqa: E402\nimport sys\n\n\n# Prevent this script's directory from shadowing the installed bokeh package\n_script_dir = sys.path[0] if sys.path else \"\"\nif _script_dir and \"implementations/python\" in _script_dir:\n    sys.path = sys.path[1:]\n\nimport math\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, LinearColorMapper\nfrom bokeh.palettes import Viridis256\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\"\nNAN_COLOR = \"#E8E5DE\" if THEME == \"light\" else \"#252520\"\n\n# Data — 24 researchers across 4 departments, ordered by department\nnp.random.seed(42)\ndept_codes = [\"Phy\", \"Chem\", \"Bio\", \"Math\"]\nn_per_dept = 6\nnode_names = [f\"{code}{i:02d}\" for code in dept_codes for i in range(1, n_per_dept + 1)]\nnode_community = [idx for idx in range(len(dept_codes)) for _ in range(n_per_dept)]\n\nn = len(node_names)\n\n# Weighted symmetric adjacency matrix\nadj = np.zeros((n, n))\nfor i in range(n):\n    for j in range(i + 1, n):\n        same_dept = node_community[i] == node_community[j]\n        if same_dept and np.random.rand() < 0.80:\n            w = np.random.uniform(0.5, 1.0)\n            adj[i, j] = w\n            adj[j, i] = w\n        elif not same_dept and np.random.rand() < 0.08:\n            w = np.random.uniform(0.1, 0.4)\n            adj[i, j] = w\n            adj[j, i] = w\n\n# NaN encodes absent edges (no self-loops shown)\nadj_nan = adj.astype(float)\nadj_nan[adj == 0] = np.nan\n\n# Flatten to row-per-cell format for ColumnDataSource\nxs, ys, weights = [], [], []\nfor i in range(n):\n    for j in range(n):\n        xs.append(node_names[j])  # column = target node\n        ys.append(node_names[i])  # row = source node\n        weights.append(adj_nan[i, j])\n\nsource = ColumnDataSource({\"x\": xs, \"y\": ys, \"weight\": weights})\n\n# Continuous color mapper — viridis for edge weights\nmapper = LinearColorMapper(palette=Viridis256, low=0.1, high=1.0, nan_color=NAN_COLOR)\n\n# Figure (square canvas for symmetric matrix)\np = figure(\n    width=3600,\n    height=3600,\n    x_range=node_names,\n    y_range=list(reversed(node_names)),\n    toolbar_location=None,\n    title=\"Researcher Collaboration · heatmap-adjacency · bokeh · anyplot.ai\",\n)\n\n# Heatmap rectangles\np.rect(\n    x=\"x\", y=\"y\", width=1, height=1, source=source, fill_color={\"field\": \"weight\", \"transform\": mapper}, line_color=None\n)\n\n# Colorbar\ncolorbar = ColorBar(\n    color_mapper=mapper,\n    ticker=BasicTicker(desired_num_ticks=6),\n    label_standoff=20,\n    border_line_color=None,\n    location=(0, 0),\n    title=\"Connection Strength\",\n    title_text_font_size=\"20pt\",\n    title_text_color=INK,\n    major_label_text_font_size=\"18pt\",\n    major_label_text_color=INK_SOFT,\n    background_fill_color=ELEVATED_BG,\n    width=60,\n)\np.add_layout(colorbar, \"right\")\n\n# Typography\np.title.text_font_size = \"28pt\"\np.title.text_color = INK\np.title.text_font_style = \"normal\"\n\np.xaxis.axis_label = \"Researcher (Target)\"\np.yaxis.axis_label = \"Researcher (Source)\"\np.xaxis.axis_label_text_font_size = \"22pt\"\np.yaxis.axis_label_text_font_size = \"22pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\n\np.xaxis.major_label_text_font_size = \"15pt\"\np.yaxis.major_label_text_font_size = \"15pt\"\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.major_label_orientation = math.pi / 3\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\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\n\np.xgrid.grid_line_color = None\np.ygrid.grid_line_color = None\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome via Selenium\nW, H = 3600, 3600\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()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}