{"spec_id":"network-directed","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' network-directed: Directed Network Graph\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 82/100 | Created: 2026-09-05\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(tibble)\nlibrary(ragg)\n\nset.seed(42)\n\n# --- Theme tokens -------------------------------------------------------\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nELEVATED_BG <- if (THEME == \"light\") \"#FFFDF6\" else \"#242420\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nIMPRINT_PALETTE <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\")\n\n# --- Data: a software package dependency graph, laid out in layers ------\n# layer 0 = foundational packages, layer 4 = application entry points.\n# An edge (from -> to) means \"from\" imports \"to\".\ngroup_names <- c(\n  \"0\" = \"Foundation\", \"1\" = \"Core services\", \"2\" = \"Domain logic\",\n  \"3\" = \"Application\", \"4\" = \"Entry point\"\n)\n\nnodes <- tribble(\n  ~id,             ~layer, ~y,\n  \"utils\",              0,   -1,\n  \"config\",             0,    0,\n  \"logging\",            0,    1,\n  \"database\",           1,   -1,\n  \"cache\",              1,    0,\n  \"auth\",               1,    1,\n  \"validators\",         2, -0.7,\n  \"api_client\",         2,  0.7,\n  \"orders_svc\",         3,   -1,\n  \"users_svc\",          3,    0,\n  \"payments_svc\",       3,    1,\n  \"web_app\",            4, -0.5,\n  \"cli_tool\",           4,  0.5\n) %>%\n  mutate(\n    x     = layer,\n    group = factor(group_names[as.character(layer)], levels = group_names)\n  )\n\nedges <- tribble(\n  ~from,           ~to,\n  \"database\",      \"utils\",\n  \"database\",      \"config\",\n  \"cache\",         \"config\",\n  \"auth\",          \"utils\",\n  \"auth\",          \"logging\",\n  \"validators\",    \"utils\",\n  \"api_client\",    \"logging\",\n  \"api_client\",    \"config\",\n  \"orders_svc\",    \"database\",\n  \"orders_svc\",    \"validators\",\n  \"orders_svc\",    \"api_client\",\n  \"users_svc\",     \"database\",\n  \"users_svc\",     \"auth\",\n  \"users_svc\",     \"validators\",\n  \"payments_svc\",  \"database\",\n  \"payments_svc\",  \"api_client\",\n  \"payments_svc\",  \"auth\",\n  \"web_app\",       \"orders_svc\",\n  \"web_app\",       \"users_svc\",\n  \"web_app\",       \"payments_svc\",\n  \"cli_tool\",      \"orders_svc\",\n  \"cli_tool\",      \"payments_svc\"\n) %>%\n  mutate(calls_per_day = sample(5:40, n(), replace = TRUE))\n\n# Trim each edge toward the node radius so the arrowhead lands just short\n# of the target circle instead of disappearing underneath it.\ntrim_frac <- 0.16\nedge_coords <- edges %>%\n  left_join(nodes %>% select(id, x, y), by = c(\"from\" = \"id\")) %>%\n  rename(x0 = x, y0 = y) %>%\n  left_join(nodes %>% select(id, x, y), by = c(\"to\" = \"id\")) %>%\n  rename(x1 = x, y1 = y) %>%\n  mutate(\n    xs = x0 + trim_frac * (x1 - x0),\n    ys = y0 + trim_frac * (y1 - y0),\n    xe = x1 - trim_frac * (x1 - x0),\n    ye = y1 - trim_frac * (y1 - y0),\n    # Long edges that skip the layer-2 column (validators/api_client) and\n    # travel diagonally cut straight through those nodes' positions. Bow\n    # just those three edges out of the way; everything else stays a\n    # straight segment so the layout itself is untouched.\n    curvature = case_when(\n      from == \"users_svc\"    & to == \"auth\"     ~ -0.35,\n      from == \"users_svc\"    & to == \"database\" ~  0.35,\n      from == \"payments_svc\" & to == \"database\" ~  0.35,\n      TRUE ~ 0\n    )\n  )\n\nstraight_edges <- filter(edge_coords, curvature == 0)\ncurved_pos     <- filter(edge_coords, curvature > 0)\ncurved_neg     <- filter(edge_coords, curvature < 0)\n\nedge_arrow <- arrow(length = unit(3, \"mm\"), type = \"closed\", angle = 25)\n\n# --- Plot ----------------------------------------------------------------\np <- ggplot() +\n  geom_segment(\n    data = straight_edges,\n    aes(x = xs, y = ys, xend = xe, yend = ye, linewidth = calls_per_day),\n    color = INK_SOFT, alpha = 0.6, lineend = \"round\", arrow = edge_arrow\n  ) +\n  geom_curve(\n    data = curved_pos,\n    aes(x = xs, y = ys, xend = xe, yend = ye, linewidth = calls_per_day),\n    color = INK_SOFT, alpha = 0.6, lineend = \"round\", arrow = edge_arrow,\n    curvature = 0.35\n  ) +\n  geom_curve(\n    data = curved_neg,\n    aes(x = xs, y = ys, xend = xe, yend = ye, linewidth = calls_per_day),\n    color = INK_SOFT, alpha = 0.6, lineend = \"round\", arrow = edge_arrow,\n    curvature = -0.35\n  ) +\n  scale_linewidth(range = c(0.5, 2.2), guide = \"none\") +\n  geom_point(\n    data = nodes, aes(x = x, y = y, color = group),\n    size = 16\n  ) +\n  geom_label(\n    data = nodes, aes(x = x, y = y - 0.34, label = id),\n    size = 3.2, color = INK, fill = PAGE_BG, label.size = 0,\n    label.padding = unit(0.12, \"lines\")\n  ) +\n  scale_color_manual(values = IMPRINT_PALETTE, name = \"Architecture layer\") +\n  guides(color = guide_legend(nrow = 2, byrow = TRUE,\n                               override.aes = list(size = 6))) +\n  labs(title = \"network-directed · r · ggplot2 · anyplot.ai\") +\n  coord_cartesian(xlim = c(-0.4, 4.4), ylim = c(-1.6, 1.5), expand = FALSE) +\n  theme_void(base_size = 8) +\n  theme(\n    plot.background   = element_rect(fill = PAGE_BG, color = PAGE_BG),\n    panel.background  = element_rect(fill = PAGE_BG, color = NA),\n    plot.title        = element_text(color = INK, size = 12, hjust = 0.5,\n                                      margin = margin(b = 10)),\n    plot.margin       = margin(15, 20, 15, 20),\n    legend.position   = \"bottom\",\n    legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT),\n    legend.title      = element_text(color = INK, size = 10),\n    legend.text       = element_text(color = INK_SOFT, size = 8),\n    legend.key.size   = unit(0.8, \"lines\"),\n    legend.margin     = margin(4, 4, 4, 4)\n  )\n\n# --- Save ------------------------------------------------------------------\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 8,\n  height   = 4.5,\n  units    = \"in\",\n  dpi      = 400\n)\n"}