{"spec_id":"energy-level-atomic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' energy-level-atomic: Atomic Energy Level Diagram\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 88/100 | Created: 2026-05-30\n\nlibrary(ggplot2)\nlibrary(ragg)\n\n# Theme tokens\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nIMPRINT_PALETTE <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n                     \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\n\n# Hydrogen atom energy levels (E_n = -13.6 / n^2 eV, n = 1..5)\n# Display y = -1/n: spaces levels proportionally to quantum number\n# so the crowded upper levels stay legible. Y-axis labels show actual energies.\nn_vals  <- 1:5\ne_n     <- -13.6 / n_vals^2\ny_disp  <- -1.0 / n_vals\n\nlevels_df <- data.frame(\n  n     = n_vals,\n  energy = e_n,\n  y     = y_disp,\n  label = paste0(\"n = \", n_vals)\n)\n\n# Emission transitions (downward arrows): y_start at upper level, y_end near lower level\n# Arrow stops 0.015 display-units above the lower level line so the head clears the line\ntransitions_df <- data.frame(\n  n_upper  = c(2, 3, 4,    3, 4,    4, 5),\n  n_lower  = c(1, 1, 1,    2, 2,    3, 3),\n  series   = c(\n    \"Lyman (UV)\", \"Lyman (UV)\", \"Lyman (UV)\",\n    \"Balmer (visible)\", \"Balmer (visible)\",\n    \"Paschen (IR)\", \"Paschen (IR)\"\n  ),\n  x_pos    = c(0.17, 0.25, 0.33,   0.47, 0.57,   0.71, 0.80),\n  wl_label = c(\"Lyα 122 nm\", \"\", \"\", \"Hα 656 nm\", \"\", \"Paα 1875 nm\", \"\"),\n  stringsAsFactors = FALSE\n)\ntransitions_df$y_start <- -1.0 / transitions_df$n_upper\ntransitions_df$y_end   <- -1.0 / transitions_df$n_lower + 0.015\ntransitions_df$y_mid   <- (transitions_df$y_start + transitions_df$y_end) / 2\n\nseries_colors <- c(\n  \"Lyman (UV)\"       = IMPRINT_PALETTE[1],\n  \"Balmer (visible)\" = IMPRINT_PALETTE[2],\n  \"Paschen (IR)\"     = IMPRINT_PALETTE[3]\n)\n\n# Subset for wavelength annotations (only labeled transitions)\nwl_df <- transitions_df[transitions_df$wl_label != \"\", ]\n\n# Adaptive title size\ntitle_str  <- \"Hydrogen Atom · energy-level-atomic · r · ggplot2 · anyplot.ai\"\ntitle_size <- max(8, round(12 * 67 / nchar(title_str)))\n\n# Y-axis breaks at the display positions, labeled with actual energies\ny_breaks <- c(y_disp, 0)\ny_labels <- c(sprintf(\"%.2f eV\", e_n), \"0 eV\")\n\n# Plot\np <- ggplot() +\n  # Ionization limit\n  geom_hline(yintercept = 0, color = INK_SOFT,\n             linetype = \"dashed\", linewidth = 0.45) +\n  # Energy level horizontal lines\n  geom_segment(\n    data = levels_df,\n    aes(x = 0.08, xend = 0.90, y = y, yend = y),\n    color = INK, linewidth = 0.9\n  ) +\n  # Quantum number labels (right side)\n  geom_text(\n    data = levels_df,\n    aes(x = 0.92, y = y, label = label),\n    hjust = 0, color = INK_SOFT, size = 3.0\n  ) +\n  # Emission transition arrows\n  geom_segment(\n    data = transitions_df,\n    aes(x = x_pos, xend = x_pos,\n        y = y_start, yend = y_end,\n        color = series),\n    arrow       = arrow(length = unit(0.065, \"inches\"), type = \"closed\"),\n    linewidth   = 1.1,\n    show.legend = FALSE\n  ) +\n  # Wavelength annotations for representative transitions in each series\n  geom_text(\n    data = wl_df,\n    aes(x = x_pos + 0.018, y = y_mid, label = wl_label),\n    color = INK_SOFT, size = 2.4, hjust = 0, fontface = \"italic\"\n  ) +\n  # Series labels below the n=1 level\n  annotate(\"text\", x = 0.25, y = -1.09, label = \"Lyman (UV)\",\n           color = IMPRINT_PALETTE[1], size = 2.9, fontface = \"bold\") +\n  annotate(\"text\", x = 0.52, y = -1.09, label = \"Balmer (visible)\",\n           color = IMPRINT_PALETTE[2], size = 2.9, fontface = \"bold\") +\n  annotate(\"text\", x = 0.755, y = -1.09, label = \"Paschen (IR)\",\n           color = IMPRINT_PALETTE[3], size = 2.9, fontface = \"bold\") +\n  # Ionization label (size increased to 3.0 for better mobile readability)\n  annotate(\"text\", x = 0.50, y = 0.032, label = \"Ionization limit (0 eV)\",\n           color = INK_SOFT, size = 3.0, hjust = 0.5) +\n  scale_color_manual(values = series_colors) +\n  scale_x_continuous(limits = c(-0.05, 1.20), expand = c(0, 0)) +\n  scale_y_continuous(\n    name   = \"Energy (eV)\",\n    breaks = y_breaks,\n    labels = y_labels,\n    limits = c(-1.15, 0.06)\n  ) +\n  coord_cartesian(clip = \"off\") +\n  labs(title = title_str, x = NULL) +\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    panel.border     = element_blank(),\n    axis.title.y     = element_text(color = INK,      size = 10),\n    axis.title.x     = element_blank(),\n    axis.text.y      = element_text(color = INK_SOFT, size = 8),\n    axis.text.x      = element_blank(),\n    axis.ticks.x     = element_blank(),\n    axis.ticks.y     = element_line(color = INK_SOFT, linewidth = 0.4),\n    axis.line.y      = element_line(color = INK_SOFT, linewidth = 0.5),\n    plot.title       = element_text(color = INK, size = title_size, face = \"plain\"),\n    legend.position  = \"none\",\n    plot.margin      = margin(15, 15, 15, 5)\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"}