{"spec_id":"network-directed","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nnetwork-directed: Directed Network Graph\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\n\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom matplotlib.patches import Circle, FancyArrowPatch\n\n\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# Okabe-Ito palette for group colors (first 4 positions)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data: Software package dependencies (arrows show import direction)\nnp.random.seed(42)\n\n# Define modules with groups (core, utils, api, tests)\nnodes = {\n    \"main\": {\"group\": \"core\", \"pos\": (0.45, 0.88)},\n    \"config\": {\"group\": \"core\", \"pos\": (0.18, 0.68)},\n    \"database\": {\"group\": \"core\", \"pos\": (0.45, 0.55)},\n    \"auth\": {\"group\": \"api\", \"pos\": (0.72, 0.73)},\n    \"routes\": {\"group\": \"api\", \"pos\": (0.88, 0.52)},\n    \"handlers\": {\"group\": \"api\", \"pos\": (0.68, 0.32)},\n    \"validators\": {\"group\": \"utils\", \"pos\": (0.32, 0.38)},\n    \"helpers\": {\"group\": \"utils\", \"pos\": (0.48, 0.15)},\n    \"logger\": {\"group\": \"utils\", \"pos\": (0.12, 0.38)},\n    \"cache\": {\"group\": \"utils\", \"pos\": (0.05, 0.58)},\n    \"test_auth\": {\"group\": \"tests\", \"pos\": (0.92, 0.88)},\n    \"test_routes\": {\"group\": \"tests\", \"pos\": (0.95, 0.32)},\n    \"test_db\": {\"group\": \"tests\", \"pos\": (0.25, 0.12)},\n}\n\n# Define dependencies (arrow from source to target means source imports target)\nedges = [\n    (\"main\", \"config\"),\n    (\"main\", \"database\"),\n    (\"main\", \"routes\"),\n    (\"main\", \"logger\"),\n    (\"config\", \"validators\"),\n    (\"database\", \"logger\"),\n    (\"database\", \"config\"),\n    (\"auth\", \"database\"),\n    (\"auth\", \"validators\"),\n    (\"auth\", \"logger\"),\n    (\"routes\", \"auth\"),\n    (\"routes\", \"handlers\"),\n    (\"routes\", \"validators\"),\n    (\"handlers\", \"database\"),\n    (\"handlers\", \"helpers\"),\n    (\"handlers\", \"logger\"),\n    (\"validators\", \"helpers\"),\n    (\"cache\", \"logger\"),\n    (\"cache\", \"config\"),\n    (\"test_auth\", \"auth\"),\n    (\"test_routes\", \"routes\"),\n    (\"test_db\", \"database\"),\n]\n\n# Group colors (Okabe-Ito)\ngroup_colors = {\"core\": IMPRINT[0], \"api\": IMPRINT[1], \"utils\": IMPRINT[2], \"tests\": IMPRINT[3]}\n\n# Plot\nfig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Node radius for arrow endpoint calculations\nnode_radius = 0.055\n\n# Draw edges with arrows\nedge_color = INK_SOFT if THEME == \"light\" else \"#999999\"\nfor source, target in edges:\n    pos1 = nodes[source][\"pos\"]\n    pos2 = nodes[target][\"pos\"]\n\n    # Calculate edge start/end points outside node circles\n    dx = pos2[0] - pos1[0]\n    dy = pos2[1] - pos1[1]\n    dist = np.sqrt(dx**2 + dy**2)\n    dx_norm, dy_norm = dx / dist, dy / dist\n    start = (pos1[0] + dx_norm * node_radius, pos1[1] + dy_norm * node_radius)\n    end = (pos2[0] - dx_norm * node_radius * 1.6, pos2[1] - dy_norm * node_radius * 1.6)\n\n    # Create curved arrow\n    arrow = FancyArrowPatch(\n        start,\n        end,\n        arrowstyle=\"-|>\",\n        mutation_scale=25,\n        color=edge_color,\n        linewidth=2.5,\n        alpha=0.6,\n        connectionstyle=\"arc3,rad=0.12\",\n    )\n    ax.add_patch(arrow)\n\n# Draw nodes\nfor name, props in nodes.items():\n    pos = props[\"pos\"]\n    color = group_colors[props[\"group\"]]\n\n    # Draw node circle\n    circle = Circle(pos, radius=node_radius, facecolor=color, edgecolor=INK_SOFT, linewidth=2.5, alpha=0.9, zorder=10)\n    ax.add_patch(circle)\n\n    # Draw label\n    ax.text(pos[0], pos[1], name, ha=\"center\", va=\"center\", fontsize=13, fontweight=\"bold\", color=INK, zorder=11)\n\n# Legend\nlegend_handles = [\n    mpatches.Patch(color=color, label=group.capitalize(), alpha=0.9) for group, color in group_colors.items()\n]\nleg = ax.legend(\n    handles=legend_handles, loc=\"upper left\", fontsize=16, framealpha=0.95, title=\"Module Type\", title_fontsize=18\n)\nleg.get_frame().set_facecolor(ELEVATED_BG)\nleg.get_frame().set_edgecolor(INK_SOFT)\nfor text in leg.get_texts():\n    text.set_color(INK_SOFT)\n\n# Style\nax.set_title(\"network-directed · matplotlib · anyplot.ai\", fontsize=24, fontweight=\"medium\", color=INK, pad=20)\nax.set_xlim(-0.02, 1.02)\nax.set_ylim(0.0, 1.0)\nax.set_aspect(\"equal\")\nax.axis(\"off\")\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}