{"spec_id":"hive-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' hive-basic: Basic Hive Plot\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 90/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\"\nINK_MUTED   <- if (THEME == \"light\") \"#6B6A63\" else \"#A8A79F\"\nIMPRINT_PALETTE <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n                     \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\n\n# --- Data: software module dependency network -------------------------------\nmodule_types <- c(\"core\", \"utility\", \"interface\")\nn_per_type   <- c(core = 12, utility = 10, interface = 8)\n\nnodes <- bind_rows(lapply(module_types, function(m) {\n  tibble(id = paste0(m, \"_\", seq_len(n_per_type[[m]])), type = m)\n}))\n\nsample_edges <- function(from_type, to_type, n) {\n  from_ids <- nodes$id[nodes$type == from_type]\n  to_ids   <- nodes$id[nodes$type == to_type]\n  tibble(\n    from   = sample(from_ids, n, replace = TRUE),\n    to     = sample(to_ids, n, replace = TRUE),\n    weight = sample(1:5, n, replace = TRUE)\n  )\n}\n\n# Layered dependency edges: core -> utility -> interface, plus core -> interface\nedges <- bind_rows(\n  sample_edges(\"core\", \"utility\", 22),\n  sample_edges(\"utility\", \"interface\", 18),\n  sample_edges(\"core\", \"interface\", 10)\n) %>%\n  filter(from != to) %>%\n  distinct(from, to, .keep_all = TRUE) %>%\n  mutate(edge_id = row_number())\n\n# Node position along its axis is degree-based: higher-degree modules sit\n# farther from the hive center, surfacing the busiest dependency hubs.\ndegree_tbl <- tibble(id = c(edges$from, edges$to)) %>% count(id, name = \"degree\")\n\nnodes <- nodes %>%\n  left_join(degree_tbl, by = \"id\") %>%\n  mutate(degree = ifelse(is.na(degree), 0, degree)) %>%\n  group_by(type) %>%\n  mutate(radius = 0.18 + 0.78 * (rank(degree, ties.method = \"first\") - 1) / (n() - 1)) %>%\n  ungroup()\n\n# --- Axis geometry: 3 radial axes, 120 degrees apart ------------------------\naxis_angle <- c(core = pi / 2, utility = pi / 2 + 2 * pi / 3, interface = pi / 2 + 4 * pi / 3)\nmax_radius <- 1.0\n\nnodes <- nodes %>%\n  mutate(angle = axis_angle[type], x = radius * cos(angle), y = radius * sin(angle))\n\naxis_lines <- tibble(\n  type = module_types,\n  angle = axis_angle[module_types],\n  x0 = 0, y0 = 0,\n  x1 = max_radius * cos(axis_angle[module_types]),\n  y1 = max_radius * sin(axis_angle[module_types])\n)\n\naxis_labels <- axis_lines %>%\n  mutate(\n    x = 1.14 * cos(angle),\n    y = 1.14 * sin(angle),\n    label = tools::toTitleCase(type)\n  )\n\n# --- Edge curves: quadratic Bezier bent toward the hive center --------------\n# Bundling curves through a shared interior control point (rather than\n# straight chords) is the classic hive-plot device for keeping dense\n# cross-axis connections legible.\nbezier_points <- function(x0, y0, x1, y1, n = 30) {\n  cx <- (x0 + x1) / 2.5\n  cy <- (y0 + y1) / 2.5\n  t <- seq(0, 1, length.out = n)\n  tibble(\n    x = (1 - t)^2 * x0 + 2 * (1 - t) * t * cx + t^2 * x1,\n    y = (1 - t)^2 * y0 + 2 * (1 - t) * t * cy + t^2 * y1\n  )\n}\n\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\nedge_curves <- bind_rows(lapply(seq_len(nrow(edge_coords)), function(i) {\n  row <- edge_coords[i, ]\n  bind_cols(bezier_points(row$x0, row$y0, row$x1, row$y1),\n            edge_id = row$edge_id, weight = row$weight)\n}))\n\n# --- Plot --------------------------------------------------------------------\np <- ggplot() +\n  geom_segment(\n    data = axis_lines,\n    aes(x = x0, y = y0, xend = x1, yend = y1),\n    color = INK_SOFT, linewidth = 0.6\n  ) +\n  geom_path(\n    data = edge_curves,\n    aes(x = x, y = y, group = edge_id, alpha = weight),\n    color = INK_MUTED, linewidth = 0.5\n  ) +\n  scale_alpha_continuous(range = c(0.12, 0.55), guide = \"none\") +\n  annotate(\n    \"point\", x = 0, y = 0, shape = 21,\n    size = 3.4, stroke = 0.7, color = INK_SOFT, fill = PAGE_BG\n  ) +\n  geom_point(\n    data = nodes,\n    aes(x = x, y = y, color = type),\n    size = 2.8\n  ) +\n  scale_color_manual(\n    values = setNames(IMPRINT_PALETTE[1:3], module_types),\n    labels = setNames(tools::toTitleCase(module_types), module_types),\n    name   = \"Module type\"\n  ) +\n  geom_text(\n    data = axis_labels,\n    aes(x = x, y = y, label = label),\n    color = INK, size = 3.5, fontface = \"bold\"\n  ) +\n  labs(title = \"hive-basic · r · ggplot2 · anyplot.ai\") +\n  coord_equal(xlim = c(-1.05, 1.05), ylim = c(-0.62, 1.18), clip = \"off\") +\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, margin = margin(b = 14)),\n    legend.position    = \"bottom\",\n    legend.background  = element_rect(fill = ELEVATED_BG, color = NA),\n    legend.text        = element_text(color = INK_SOFT, size = 8),\n    legend.title       = element_text(color = INK, size = 10),\n    plot.margin        = margin(20, 30, 20, 30)\n  )\n\n# --- Save --------------------------------------------------------------------\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 6,\n  height   = 6,\n  units    = \"in\",\n  dpi      = 400\n)\n"}