A matrix-based representation of a network or graph where rows and columns represent nodes and cell color indicates the presence or weight of edges between them. This visualization complements node-link diagrams by excelling at revealing clusters, structural patterns, and density in large or dense networks where node-link layouts become cluttered. Reordering nodes by cluster, degree, or community membership exposes block-diagonal structure and makes group boundaries immediately visible.

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