{"spec_id":"network-directed","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nnetwork-directed: Directed Network Graph\nLibrary: plotnine 0.15.8 | Python 3.13.15\nQuality: 88/100 | Created: 2026-08-24\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    arrow,\n    coord_cartesian,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_path,\n    geom_point,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_manual,\n    theme,\n)\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\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\n# Data: Python package import graph for a web service — arrows point from an\n# importing module to the module it depends on, showing build order bottom-up.\nmodules = [\n    {\"id\": \"config\", \"tier\": \"Foundation\"},\n    {\"id\": \"logging\", \"tier\": \"Foundation\"},\n    {\"id\": \"types\", \"tier\": \"Foundation\"},\n    {\"id\": \"database\", \"tier\": \"Data access\"},\n    {\"id\": \"cache\", \"tier\": \"Data access\"},\n    {\"id\": \"http_client\", \"tier\": \"Data access\"},\n    {\"id\": \"auth\", \"tier\": \"Domain services\"},\n    {\"id\": \"user_service\", \"tier\": \"Domain services\"},\n    {\"id\": \"payment_service\", \"tier\": \"Domain services\"},\n    {\"id\": \"notification_service\", \"tier\": \"Domain services\"},\n    {\"id\": \"api_gateway\", \"tier\": \"Gateway\"},\n    {\"id\": \"admin_panel\", \"tier\": \"Gateway\"},\n    {\"id\": \"public_api\", \"tier\": \"Gateway\"},\n    {\"id\": \"web_app\", \"tier\": \"Application\"},\n    {\"id\": \"worker\", \"tier\": \"Application\"},\n]\n\nimports = [\n    (\"database\", \"config\"),\n    (\"database\", \"logging\"),\n    (\"cache\", \"config\"),\n    (\"http_client\", \"logging\"),\n    (\"http_client\", \"types\"),\n    (\"auth\", \"database\"),\n    (\"auth\", \"cache\"),\n    (\"user_service\", \"database\"),\n    (\"user_service\", \"auth\"),\n    (\"payment_service\", \"database\"),\n    (\"payment_service\", \"http_client\"),\n    (\"notification_service\", \"http_client\"),\n    (\"notification_service\", \"cache\"),\n    (\"api_gateway\", \"auth\"),\n    (\"api_gateway\", \"user_service\"),\n    (\"admin_panel\", \"user_service\"),\n    (\"admin_panel\", \"payment_service\"),\n    (\"public_api\", \"payment_service\"),\n    (\"public_api\", \"notification_service\"),\n    (\"web_app\", \"api_gateway\"),\n    (\"web_app\", \"public_api\"),\n    (\"worker\", \"notification_service\"),\n    (\"worker\", \"payment_service\"),\n]\n\n# Layered layout: one row per tier, modules spread evenly across the row.\ntier_order = [\"Foundation\", \"Data access\", \"Domain services\", \"Gateway\", \"Application\"]\ntier_rows = {tier: [] for tier in tier_order}\nfor module in modules:\n    tier_rows[module[\"tier\"]].append(module[\"id\"])\n\npositions = {}\nfor row, tier in enumerate(tier_order):\n    members = tier_rows[tier]\n    count = len(members)\n    xs = np.linspace(0.03, 0.97, count) if count > 1 else np.array([0.5])\n    y = 0.06 + row * (0.88 / (len(tier_order) - 1))\n    for module_id, x in zip(members, xs, strict=True):\n        positions[module_id] = (float(x), float(y))\n\nnode_df = pd.DataFrame(\n    {\n        \"x\": [positions[m[\"id\"]][0] for m in modules],\n        \"y\": [positions[m[\"id\"]][1] for m in modules],\n        \"label\": [m[\"id\"] for m in modules],\n        \"tier\": pd.Categorical([m[\"tier\"] for m in modules], categories=tier_order, ordered=True),\n    }\n)\n\n# Every edge bows along a quadratic Bezier with the same curvature ratio, so\n# parallel/overlapping straight lines fan out into distinguishable arcs while\n# the arrow style stays consistent across the whole graph (per spec notes).\n# Endpoints are trimmed by arc length so the line starts clear of the source\n# marker and the arrowhead lands just outside the target marker.\nCURVATURE = 0.12\nSTART_MARGIN = 0.018\nEND_MARGIN = 0.032\n\nedge_rows = []\nfor edge_id, (src, tgt) in enumerate(imports):\n    p0 = np.array(positions[src])\n    p2 = np.array(positions[tgt])\n    direction = p2 - p0\n    dist = max(float(np.hypot(*direction)), 1e-6)\n    unit = direction / dist\n    normal = np.array([-unit[1], unit[0]])\n    control = (p0 + p2) / 2 + normal * CURVATURE * dist\n\n    t = np.linspace(0, 1, 40)\n    curve_x = (1 - t) ** 2 * p0[0] + 2 * (1 - t) * t * control[0] + t**2 * p2[0]\n    curve_y = (1 - t) ** 2 * p0[1] + 2 * (1 - t) * t * control[1] + t**2 * p2[1]\n\n    seg_len = np.hypot(np.diff(curve_x), np.diff(curve_y))\n    dist_from_start = np.concatenate([[0], np.cumsum(seg_len)])\n    dist_from_end = dist_from_start[-1] - dist_from_start\n    keep = (dist_from_start >= START_MARGIN) & (dist_from_end >= END_MARGIN)\n    if keep.sum() < 2:\n        keep = np.array([True] * len(t))\n\n    for x, y in zip(curve_x[keep], curve_y[keep], strict=True):\n        edge_rows.append({\"edge_id\": edge_id, \"x\": x, \"y\": y})\nedge_df = pd.DataFrame(edge_rows)\n\ntier_colors = dict(zip(tier_order, IMPRINT_PALETTE, strict=True))\n\n# Plot\nplot = (\n    ggplot()\n    + geom_path(\n        data=edge_df,\n        mapping=aes(x=\"x\", y=\"y\", group=\"edge_id\"),\n        color=INK_SOFT,\n        size=0.5,\n        alpha=0.6,\n        arrow=arrow(angle=22, length=0.09, ends=\"last\", type=\"closed\"),\n    )\n    + geom_point(data=node_df, mapping=aes(x=\"x\", y=\"y\", color=\"tier\"), size=8, alpha=0.95, stroke=0.6)\n    + geom_text(\n        data=node_df,\n        mapping=aes(x=\"x\", y=\"y\", label=\"label\"),\n        color=INK,\n        size=4.2,\n        fontweight=\"bold\",\n        nudge_y=0.046,\n        va=\"bottom\",\n    )\n    + scale_color_manual(values=tier_colors, name=\"Build tier\")\n    + coord_cartesian(xlim=(-0.03, 1.03), ylim=(-0.03, 1.0))\n    + labs(title=\"network-directed · python · plotnine · anyplot.ai\")\n    + theme(\n        figure_size=(8, 4.5),\n        text=element_text(size=7),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_border=element_blank(),\n        axis_title=element_blank(),\n        axis_text=element_blank(),\n        axis_ticks=element_blank(),\n        axis_line=element_blank(),\n        plot_title=element_text(color=INK, size=12, ha=\"center\"),\n        legend_background=element_rect(fill=ELEVATED_BG, color=None),\n        legend_text=element_text(color=INK_SOFT, size=8),\n        legend_title=element_text(color=INK, size=9),\n        legend_key=element_rect(fill=ELEVATED_BG),\n        legend_box_spacing=0.01,\n        plot_margin=0.01,\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\")\n"}