{"spec_id":"network-basic","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nnetwork-basic: Basic Network Graph\nLibrary: matplotlib 3.11.1 | Python 3.13.14\nQuality: 86/100 | Updated: 2026-07-24\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom matplotlib.patches import FancyArrowPatch\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 palette for 4 departments\nGROUP_COLORS = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\nGROUP_NAMES = [\"Engineering\", \"Research\", \"Marketing\", \"Design\"]\nBRIDGE_COLOR = \"#AE3030\"  # Imprint palette position 5 (matte red) — cross-department highlight\n\n# Data: social network of 20 people across 4 company departments\nnp.random.seed(42)\nnodes = [\n    {\"id\": 0, \"label\": \"Alice\", \"group\": 0},\n    {\"id\": 1, \"label\": \"Bob\", \"group\": 0},\n    {\"id\": 2, \"label\": \"Carol\", \"group\": 0},\n    {\"id\": 3, \"label\": \"David\", \"group\": 0},\n    {\"id\": 4, \"label\": \"Eve\", \"group\": 0},\n    {\"id\": 5, \"label\": \"Frank\", \"group\": 1},\n    {\"id\": 6, \"label\": \"Grace\", \"group\": 1},\n    {\"id\": 7, \"label\": \"Henry\", \"group\": 1},\n    {\"id\": 8, \"label\": \"Ivy\", \"group\": 1},\n    {\"id\": 9, \"label\": \"Jack\", \"group\": 1},\n    {\"id\": 10, \"label\": \"Kate\", \"group\": 2},\n    {\"id\": 11, \"label\": \"Leo\", \"group\": 2},\n    {\"id\": 12, \"label\": \"Mia\", \"group\": 2},\n    {\"id\": 13, \"label\": \"Noah\", \"group\": 2},\n    {\"id\": 14, \"label\": \"Olivia\", \"group\": 2},\n    {\"id\": 15, \"label\": \"Paul\", \"group\": 3},\n    {\"id\": 16, \"label\": \"Quinn\", \"group\": 3},\n    {\"id\": 17, \"label\": \"Ryan\", \"group\": 3},\n    {\"id\": 18, \"label\": \"Sara\", \"group\": 3},\n    {\"id\": 19, \"label\": \"Tom\", \"group\": 3},\n]\n\nedges = [\n    # Engineering internal\n    (0, 1),\n    (0, 2),\n    (1, 2),\n    (1, 3),\n    (2, 4),\n    (3, 4),\n    # Research internal\n    (5, 6),\n    (5, 7),\n    (6, 8),\n    (7, 8),\n    (7, 9),\n    (8, 9),\n    # Marketing internal\n    (10, 11),\n    (10, 12),\n    (11, 13),\n    (12, 13),\n    (12, 14),\n    (13, 14),\n    # Design internal\n    (15, 16),\n    (15, 17),\n    (16, 18),\n    (17, 18),\n    (17, 19),\n    (18, 19),\n    # Cross-department bridges\n    (0, 5),\n    (4, 10),\n    (9, 15),\n    (14, 19),\n    (2, 6),\n    (8, 11),\n    (13, 16),\n]\ncross_edge_set = {(src, tgt) for src, tgt in edges if nodes[src][\"group\"] != nodes[tgt][\"group\"]}\n\n# Force-directed spring layout (Fruchterman-Reingold style, no networkx),\n# run independently within each department. Laying out each community on\n# its own — instead of one global simulation — guarantees 4 spatially\n# distinct clusters: cross-department bridge edges are drawn afterwards as\n# pure visual connectors and never distort the local layouts.\nquadrant_centers = {\n    0: np.array([-0.62, 0.62]),  # Engineering: upper-left\n    1: np.array([0.62, 0.62]),  # Research: upper-right\n    2: np.array([0.62, -0.62]),  # Marketing: lower-right\n    3: np.array([-0.62, -0.62]),  # Design: lower-left\n}\nK_LOCAL = 0.45\npos = {}\nfor group in range(4):\n    group_nodes = [node[\"id\"] for node in nodes if node[\"group\"] == group]\n    local_edges = [(src, tgt) for src, tgt in edges if (src, tgt) not in cross_edge_set and src in group_nodes]\n    m = len(group_nodes)\n    idx = {node_id: i for i, node_id in enumerate(group_nodes)}\n    local_pos = np.random.randn(m, 2) * 0.3\n\n    for iteration in range(150):\n        displacement = np.zeros((m, 2))\n        for i in range(m):\n            for j in range(i + 1, m):\n                diff = local_pos[i] - local_pos[j]\n                dist = max(np.linalg.norm(diff), 0.01)\n                force = (K_LOCAL * K_LOCAL / dist) * (diff / dist)\n                displacement[i] += force\n                displacement[j] -= force\n        for src, tgt in local_edges:\n            i, j = idx[src], idx[tgt]\n            diff = local_pos[i] - local_pos[j]\n            dist = max(np.linalg.norm(diff), 0.01)\n            force = (dist * dist / K_LOCAL) * (diff / dist)\n            displacement[i] -= force\n            displacement[j] += force\n        cooling = 1 - iteration / 150\n        for i in range(m):\n            disp_norm = np.linalg.norm(displacement[i])\n            if disp_norm > 0:\n                local_pos[i] += (displacement[i] / disp_norm) * min(disp_norm, 0.08 * cooling)\n\n    local_pos -= local_pos.mean(axis=0)\n    radius = np.linalg.norm(local_pos, axis=1).max()\n    local_pos = local_pos / radius * 0.44\n    for node_id, i in idx.items():\n        pos[node_id] = quadrant_centers[group] + local_pos[i]\n\nall_pos = np.array([pos[node[\"id\"]] for node in nodes])\npos_min = all_pos.min(axis=0)\npos_max = all_pos.max(axis=0)\nfor node in nodes:\n    pos[node[\"id\"]] = (pos[node[\"id\"]] - pos_min) / (pos_max - pos_min) * 0.82 + 0.09\n\n# Node degrees for size encoding\ndegrees = {node[\"id\"]: 0 for node in nodes}\nfor src, tgt in edges:\n    degrees[src] += 1\n    degrees[tgt] += 1\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nfig.subplots_adjust(left=0.02, right=0.98, top=0.80, bottom=0.03)\nax.set_facecolor(PAGE_BG)\n\n# Draw curved edges using FancyArrowPatch\nfor src, tgt in edges:\n    is_cross = (src, tgt) in cross_edge_set\n    patch = FancyArrowPatch(\n        tuple(pos[src]),\n        tuple(pos[tgt]),\n        connectionstyle=\"arc3,rad=0.18\",\n        arrowstyle=\"-\",\n        color=BRIDGE_COLOR if is_cross else INK_SOFT,\n        linewidth=2.5 if is_cross else 1.6,\n        alpha=0.80 if is_cross else 0.45,\n        zorder=1,\n    )\n    ax.add_patch(patch)\n\n# Draw nodes (size encodes degree; large enough to fully contain the label below)\nfor node in nodes:\n    x, y = pos[node[\"id\"]]\n    size = 1000 + degrees[node[\"id\"]] * 200\n    color = GROUP_COLORS[node[\"group\"]]\n    ax.scatter(x, y, s=size, c=color, edgecolors=PAGE_BG, linewidths=2.5, alpha=0.93, zorder=2)\n\n# Draw labels inside nodes — fontsize kept small enough that even the\n# longest name (\"Olivia\") stays within the smallest-degree node's circle\nfor node in nodes:\n    x, y = pos[node[\"id\"]]\n    ax.text(x, y, node[\"label\"], fontsize=9, fontweight=\"bold\", ha=\"center\", va=\"center\", color=INK, zorder=3)\n\n# Style\ntitle = \"Social Network · network-basic · python · matplotlib · anyplot.ai\"\nfig.suptitle(title, fontsize=14, fontweight=\"medium\", color=INK, y=0.965)\nax.set_xlim(-0.05, 1.05)\nax.set_ylim(-0.05, 1.05)\nax.axis(\"off\")\n\n# Legend — placed as a horizontal row in the reserved top margin, clear of\n# the network area, so it never overlaps a department cluster\nlegend_handles = [\n    ax.scatter([], [], c=color, s=350, edgecolors=PAGE_BG, linewidths=2, label=name)\n    for color, name in zip(GROUP_COLORS, GROUP_NAMES, strict=True)\n]\nleg = fig.legend(\n    handles=legend_handles,\n    loc=\"upper center\",\n    bbox_to_anchor=(0.5, 0.885),\n    ncol=4,\n    fontsize=10,\n    title=\"Departments\",\n    title_fontsize=11,\n    frameon=True,\n)\nleg.get_frame().set_facecolor(ELEVATED_BG)\nleg.get_frame().set_edgecolor(INK_SOFT)\nleg.get_frame().set_alpha(0.92)\nplt.setp(leg.get_texts(), color=INK_SOFT)\nleg.get_title().set_color(INK)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)  # bbox_inches MUST stay default (None)\n"}