{"spec_id":"network-directed","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nnetwork-directed: Directed Network Graph\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 73/100 | Created: 2026-05-14\n\"\"\"\n\nimport os\n\n# Ensure we import the installed pygal, not this file\nimport site\nimport sys\n\n\nsys.path.insert(0, site.getsitepackages()[0])\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\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\"\n\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\")\n\n# Data: Software module dependency network\nedges = [\n    (\"api\", \"auth\"),\n    (\"api\", \"database\"),\n    (\"auth\", \"cache\"),\n    (\"cache\", \"database\"),\n    (\"frontend\", \"api\"),\n    (\"frontend\", \"auth\"),\n    (\"worker\", \"database\"),\n    (\"worker\", \"queue\"),\n    (\"queue\", \"cache\"),\n    (\"monitoring\", \"api\"),\n    (\"monitoring\", \"database\"),\n]\n\n# Force-directed layout with numpy\nnp.random.seed(42)\nnodes = sorted({src for src, _ in edges} | {dst for _, dst in edges})\nnode_idx = {node: i for i, node in enumerate(nodes)}\nn_nodes = len(nodes)\n\n# Initialize positions randomly\npos = np.random.randn(n_nodes, 2) * 50\n\n# Simple force-directed layout algorithm\nfor _ in range(50):\n    forces = np.zeros_like(pos)\n\n    # Repulsive forces between all node pairs\n    for i in range(n_nodes):\n        for j in range(n_nodes):\n            if i != j:\n                delta = pos[i] - pos[j]\n                dist = np.linalg.norm(delta) + 0.1\n                forces[i] += delta / (dist**2) * 0.5\n\n    # Attractive forces for edges\n    for src, dst in edges:\n        i, j = node_idx[src], node_idx[dst]\n        delta = pos[j] - pos[i]\n        dist = np.linalg.norm(delta) + 0.1\n        forces[i] += delta * 0.1\n        forces[j] -= delta * 0.1\n\n    # Update positions\n    pos += forces * 0.1\n\n# Normalize and scale positions\npos = (pos - pos.min(axis=0)) / (pos.max(axis=0) - pos.min(axis=0) + 1e-8)\npos = pos * 100\n\npos_dict = {node: pos[node_idx[node]] for node in nodes}\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=IMPRINT,\n    title_font_size=28,\n    label_font_size=18,\n    major_label_font_size=16,\n    legend_font_size=16,\n    value_font_size=14,\n    stroke_width=2,\n)\n\n# Create XY chart for nodes and edges\nchart = pygal.XY(\n    width=4800,\n    height=2700,\n    title=\"network-directed · pygal · anyplot.ai\",\n    x_title=\"\",\n    y_title=\"\",\n    show_legend=False,\n    show_x_guides=False,\n    show_y_guides=False,\n    style=custom_style,\n    explicit_size=True,\n)\n\nchart.title_style = {\"font_size\": 28, \"font_family\": \"sans-serif\", \"fill\": INK}\n\n# Plot edges as thin lines\nfor source, target in edges:\n    x1, y1 = pos_dict[source]\n    x2, y2 = pos_dict[target]\n\n    edge_series = [(x1, y1), (x2, y2)]\n    chart.add(None, edge_series, stroke_style={\"width\": 2, \"color\": INK_MUTED, \"opacity\": 0.4})\n\n# Plot nodes as points\nfor node in nodes:\n    x, y = pos_dict[node]\n    chart.add(node, [(x, y)], points_values_show=False, dots_size=20, stroke=False)\n\n# Manually set x and y limits to ensure proper scaling\nx_coords = [pos_dict[node][0] for node in nodes]\ny_coords = [pos_dict[node][1] for node in nodes]\nmargin = 30\nchart.range = (min(x_coords) - margin, max(x_coords) + margin)\n\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}