{"spec_id":"dendrogram-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' dendrogram-basic: Basic Dendrogram\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 88/100 | Created: 2026-06-18\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(ragg)\nlibrary(gapminder)\n\n# Theme tokens (Imprint palette)\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(\n  \"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n  \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"\n)\n\n# Data: first 20 European countries (2007) clustered by development indicators\neurope_2007 <- gapminder |>\n  filter(continent == \"Europe\", year == 2007) |>\n  arrange(country) |>\n  slice(1:20) |>\n  as.data.frame()\n\nfeat_mat <- scale(as.matrix(europe_2007[, c(\"lifeExp\", \"gdpPercap\", \"pop\")]))\nrownames(feat_mat) <- as.character(europe_2007$country)\n\n# Hierarchical clustering with Ward's linkage\nhc       <- hclust(dist(feat_mat), method = \"ward.D2\")\nn_leaves <- nrow(feat_mat)\nn_merges <- n_leaves - 1\n\n# Leaf x positions in dendrogram display order\nx_pos <- integer(n_leaves)\nfor (pos in seq_along(hc$order)) {\n  x_pos[hc$order[pos]] <- pos\n}\n\n# Internal node x (midpoint of children) and y (merge height)\nnode_x <- numeric(n_merges)\nnode_y <- hc$height\n\nfor (k in seq_len(n_merges)) {\n  lft <- hc$merge[k, 1]\n  rgt <- hc$merge[k, 2]\n  xl  <- if (lft < 0) x_pos[-lft] else node_x[lft]\n  xr  <- if (rgt < 0) x_pos[-rgt] else node_x[rgt]\n  node_x[k] <- (xl + xr) / 2\n}\n\n# Build segment data frame: 3 segments per merge (left vert, horiz, right vert)\nn_segs   <- 3L * n_merges\nseg_x    <- numeric(n_segs)\nseg_xend <- numeric(n_segs)\nseg_y    <- numeric(n_segs)\nseg_yend <- numeric(n_segs)\n\nfor (k in seq_len(n_merges)) {\n  lft <- hc$merge[k, 1]\n  rgt <- hc$merge[k, 2]\n  xl  <- if (lft < 0) x_pos[-lft] else node_x[lft]\n  yl  <- if (lft < 0) 0            else node_y[lft]\n  xr  <- if (rgt < 0) x_pos[-rgt] else node_x[rgt]\n  yr  <- if (rgt < 0) 0            else node_y[rgt]\n  i   <- (k - 1L) * 3L + 1L\n  seg_x[i]       <- xl; seg_xend[i]       <- xl; seg_y[i]       <- yl;         seg_yend[i]       <- node_y[k]\n  seg_x[i + 1L]  <- xl; seg_xend[i + 1L]  <- xr; seg_y[i + 1L]  <- node_y[k]; seg_yend[i + 1L]  <- node_y[k]\n  seg_x[i + 2L]  <- xr; seg_xend[i + 2L]  <- xr; seg_y[i + 2L]  <- yr;        seg_yend[i + 2L]  <- node_y[k]\n}\n\nseg_df <- data.frame(x = seg_x, xend = seg_xend, y = seg_y, yend = seg_yend)\n\n# Cut tree into 3 clusters; assign Imprint palette colors to leaves\nclust_assign <- cutree(hc, k = 3)\nleaf_clust   <- clust_assign[hc$order]\nmax_h        <- max(node_y)\nlabel_y      <- -max_h * 0.02\n\nlabel_df <- data.frame(\n  x       = seq_len(n_leaves),\n  y       = label_y,\n  label   = hc$labels[hc$order],\n  cluster = factor(leaf_clust)\n)\n\nplot_title    <- \"dendrogram-basic · r · ggplot2 · anyplot.ai\"\nplot_subtitle <- \"European countries clustered by life expectancy, GDP per capita & population · gapminder 2007 · Ward's linkage\"\n\n# Plot — horizontal dendrogram via coord_flip; leaves on the right, root on the left\np <- ggplot() +\n  geom_segment(\n    data = seg_df,\n    aes(x = x, xend = xend, y = y, yend = yend),\n    color = INK_SOFT, linewidth = 0.7\n  ) +\n  geom_text(\n    data  = label_df,\n    aes(x = x, y = y, label = label, color = cluster),\n    hjust = 1, size = 3.2\n  ) +\n  scale_color_manual(\n    values = setNames(IMPRINT_PALETTE[1:3], c(\"1\", \"2\", \"3\")),\n    name   = \"Cluster\"\n  ) +\n  guides(color = guide_legend(override.aes = list(label = \"■\", size = 4))) +\n  scale_x_continuous(breaks = NULL, expand = c(0.03, 0.03)) +\n  scale_y_continuous(\n    limits = c(-max_h * 0.35, max_h * 1.05),\n    breaks = pretty(c(0, max_h), n = 5)\n  ) +\n  coord_flip() +\n  labs(\n    title    = plot_title,\n    subtitle = plot_subtitle,\n    x        = NULL,\n    y        = \"Ward's Distance\"\n  ) +\n  theme_minimal(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    panel.grid.major   = element_blank(),\n    panel.grid.minor   = element_blank(),\n    axis.text.x        = element_text(color = INK_SOFT, size = 8),\n    axis.text.y        = element_blank(),\n    axis.ticks.y       = element_blank(),\n    axis.title.x       = element_text(color = INK, size = 10),\n    axis.title.y       = element_blank(),\n    plot.title         = element_text(color = INK, size = 12, face = \"bold\"),\n    plot.subtitle      = element_text(color = INK_SOFT, size = 8),\n    legend.background  = element_rect(fill = ELEVATED_BG, color = INK_SOFT,\n                                      linewidth = 0.3),\n    legend.text        = element_text(color = INK_SOFT, size = 8),\n    legend.title       = element_text(color = INK, size = 10),\n    legend.position    = \"top\",\n    plot.margin        = margin(20, 20, 20, 20)\n  )\n\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"}