{"spec_id":"heatmap-adjacency","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nheatmap-adjacency: Network Adjacency Matrix Heatmap\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 81/100 | Created: 2026-05-08\n\"\"\"\n\nimport importlib\nimport os\nimport sys\n\nimport cairosvg\nimport numpy as np\n\n\ndef _get_viridis():\n    # The sibling matplotlib.py would shadow the installed package at module\n    # import time.  By loading matplotlib inside this function, after removing\n    # the current directory from sys.path, we reach the installed package.\n    _here = os.path.dirname(os.path.abspath(__file__))\n    _saved, sys.path = sys.path, [p for p in sys.path if p and os.path.abspath(p) != _here]\n    try:\n        mpl = importlib.import_module(\"matplotlib\")\n        return mpl.colormaps[\"viridis\"]\n    finally:\n        sys.path = _saved\n\n\nviridis = _get_viridis()\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nABSENT_COLOR = \"#DEDBD4\" if THEME == \"light\" else \"#2E2E2A\"\n\n# Data: research collaboration network, 24 researchers in 3 departments\nnp.random.seed(42)\nN = 24\nDEPT_SIZE = 8\ncommunities = [i // DEPT_SIZE for i in range(N)]\nnode_labels = [f\"{'ABC'[i // DEPT_SIZE]}{(i % DEPT_SIZE) + 1}\" for i in range(N)]\n\nadj = np.zeros((N, N))\nfor i in range(N):\n    for j in range(i + 1, N):\n        same_dept = communities[i] == communities[j]\n        prob = 0.78 if same_dept else 0.07\n        if np.random.random() < prob:\n            w = np.random.uniform(0.55, 1.0) if same_dept else np.random.uniform(0.1, 0.45)\n            adj[i, j] = round(w, 2)\n            adj[j, i] = round(w, 2)\n\n# Canvas (square — best for a symmetric matrix)\nW, H = 3600, 3600\nMARGIN_TOP = 180\nMARGIN_LEFT = 160\nMARGIN_RIGHT = 220\nMARGIN_BOTTOM = 130\n\navail_w = W - MARGIN_LEFT - MARGIN_RIGHT\navail_h = H - MARGIN_TOP - MARGIN_BOTTOM\ncell = min(avail_w, avail_h) // N\nmatrix_size = cell * N\nmx = MARGIN_LEFT + (avail_w - matrix_size) // 2\nmy = MARGIN_TOP + (avail_h - matrix_size) // 2\n\n# Colorbar layout\nCB_GAP = 35\nCB_W = 55\ncb_x = W - MARGIN_RIGHT + CB_GAP\ncb_y = my\ncb_h = matrix_size\n\n\ndef to_hex(rgba):\n    r = min(255, int(rgba[0] * 255))\n    g = min(255, int(rgba[1] * 255))\n    b = min(255, int(rgba[2] * 255))\n    return f\"#{r:02x}{g:02x}{b:02x}\"\n\n\n# Build SVG\nparts = [\n    f'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{W}\" height=\"{H}\">',\n    f'<rect width=\"{W}\" height=\"{H}\" fill=\"{PAGE_BG}\"/>',\n    # Title\n    f'<text x=\"{W // 2}\" y=\"108\" font-family=\"Arial,sans-serif\" font-size=\"52\"'\n    f' font-weight=\"bold\" fill=\"{INK}\" text-anchor=\"middle\">'\n    f\"heatmap-adjacency · pygal · anyplot.ai</text>\",\n    # Subtitle\n    f'<text x=\"{W // 2}\" y=\"153\" font-family=\"Arial,sans-serif\" font-size=\"30\"'\n    f' fill=\"{INK_MUTED}\" text-anchor=\"middle\">'\n    f\"Research Collaboration Network · 24 Researchers · 3 Departments</text>\",\n]\n\n# Matrix cells\nfor i in range(N):\n    for j in range(N):\n        x = mx + j * cell\n        y = my + i * cell\n        v = adj[i, j]\n        color = ABSENT_COLOR if v == 0 else to_hex(viridis(v))\n        parts.append(\n            f'<rect x=\"{x}\" y=\"{y}\" width=\"{cell}\" height=\"{cell}\"'\n            f' fill=\"{color}\" stroke=\"{PAGE_BG}\" stroke-width=\"0.8\"/>'\n        )\n\n# Department boundary lines\nfor b in [DEPT_SIZE, 2 * DEPT_SIZE]:\n    bx = mx + b * cell\n    by = my + b * cell\n    parts.extend(\n        [\n            f'<line x1=\"{bx}\" y1=\"{my}\" x2=\"{bx}\" y2=\"{my + matrix_size}\"'\n            f' stroke=\"{INK}\" stroke-width=\"3\" opacity=\"0.5\"/>',\n            f'<line x1=\"{mx}\" y1=\"{by}\" x2=\"{mx + matrix_size}\" y2=\"{by}\"'\n            f' stroke=\"{INK}\" stroke-width=\"3\" opacity=\"0.5\"/>',\n        ]\n    )\n\n# Row labels (left side, vertically centered per cell)\nfor i, label in enumerate(node_labels):\n    y_center = my + i * cell + cell // 2\n    parts.append(\n        f'<text x=\"{mx - 14}\" y=\"{y_center}\" dominant-baseline=\"middle\"'\n        f' font-family=\"monospace\" font-size=\"24\"'\n        f' fill=\"{INK_SOFT}\" text-anchor=\"end\">{label}</text>'\n    )\n\n# Column labels (bottom, rotated 90° clockwise — reads top-to-bottom when tilted right)\nfor j, label in enumerate(node_labels):\n    x_center = mx + j * cell + cell // 2\n    y_top = my + matrix_size + 18\n    parts.append(\n        f'<text transform=\"translate({x_center},{y_top}) rotate(90)\"'\n        f' text-anchor=\"start\" font-family=\"monospace\" font-size=\"24\"'\n        f' fill=\"{INK_SOFT}\">{label}</text>'\n    )\n\n# Colorbar gradient (200 steps)\nN_STEPS = 200\nfor k in range(N_STEPS):\n    v = 1.0 - k / N_STEPS\n    color = to_hex(viridis(v))\n    y_k = cb_y + k * cb_h / N_STEPS\n    h_k = cb_h / N_STEPS + 1\n    parts.append(f'<rect x=\"{cb_x}\" y=\"{y_k:.1f}\" width=\"{CB_W}\" height=\"{h_k:.1f}\" fill=\"{color}\"/>')\n\n# Colorbar border\nparts.append(\n    f'<rect x=\"{cb_x}\" y=\"{cb_y}\" width=\"{CB_W}\" height=\"{cb_h}\"'\n    f' fill=\"none\" stroke=\"{INK_SOFT}\" stroke-width=\"1.5\" opacity=\"0.4\"/>'\n)\n\n# Colorbar tick marks and labels\nfor v_tick in [1.0, 0.75, 0.5, 0.25, 0.0]:\n    y_t = cb_y + (1.0 - v_tick) * cb_h\n    parts.extend(\n        [\n            f'<line x1=\"{cb_x + CB_W}\" y1=\"{y_t:.1f}\"'\n            f' x2=\"{cb_x + CB_W + 12}\" y2=\"{y_t:.1f}\"'\n            f' stroke=\"{INK_SOFT}\" stroke-width=\"2\"/>',\n            f'<text x=\"{cb_x + CB_W + 18}\" y=\"{y_t:.1f}\" dominant-baseline=\"middle\"'\n            f' font-family=\"Arial,sans-serif\" font-size=\"24\"'\n            f' fill=\"{INK_SOFT}\">{v_tick:.2f}</text>',\n        ]\n    )\n\n# Colorbar title\nparts.append(\n    f'<text x=\"{cb_x + CB_W // 2}\" y=\"{cb_y - 18}\"'\n    f' font-family=\"Arial,sans-serif\" font-size=\"26\"'\n    f' fill=\"{INK_MUTED}\" text-anchor=\"middle\">Strength</text>'\n)\n\nparts.append(\"</svg>\")\n\nsvg_content = \"\\n\".join(parts)\n\n# Save PNG via cairosvg (same backend pygal uses for render_to_png)\ncairosvg.svg2png(bytestring=svg_content.encode(), write_to=f\"plot-{THEME}.png\")\n\n# Save HTML (pygal is an interactive/web library)\nhtml = f\"\"\"<!DOCTYPE html>\n<html>\n<head><style>body{{margin:0;background:{PAGE_BG};}}</style></head>\n<body>{svg_content}</body>\n</html>\"\"\"\nwith open(f\"plot-{THEME}.html\", \"w\") as f:\n    f.write(html)\n"}