Atomic Energy Level Diagram — ggplot2

An energy level diagram displays quantized energy states as horizontal lines positioned at their respective energy values, with vertical arrows representing transitions between levels. This visualization reveals the discrete nature of atomic and molecular energy structures, making it essential for understanding spectral series, electron configurations, and quantum state transitions. It provides an intuitive way to connect abstract quantum numbers to observable spectral phenomena.

Atomic Energy Level Diagram rendered with ggplot2

Renders

R source (ggplot2)

#' anyplot.ai
#' energy-level-atomic: Atomic Energy Level Diagram
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 88/100 | Created: 2026-05-30

library(ggplot2)
library(ragg)

# Theme tokens
THEME       <- Sys.getenv("ANYPLOT_THEME", "light")
PAGE_BG     <- if (THEME == "light") "#FAF8F1" else "#1A1A17"
INK         <- if (THEME == "light") "#1A1A17" else "#F0EFE8"
INK_SOFT    <- if (THEME == "light") "#4A4A44" else "#B8B7B0"
IMPRINT_PALETTE <- c("#009E73", "#C475FD", "#4467A3", "#BD8233",
                     "#AE3030", "#2ABCCD", "#954477", "#99B314")

# Hydrogen atom energy levels (E_n = -13.6 / n^2 eV, n = 1..5)
# Display y = -1/n: spaces levels proportionally to quantum number
# so the crowded upper levels stay legible. Y-axis labels show actual energies.
n_vals  <- 1:5
e_n     <- -13.6 / n_vals^2
y_disp  <- -1.0 / n_vals

levels_df <- data.frame(
  n     = n_vals,
  energy = e_n,
  y     = y_disp,
  label = paste0("n = ", n_vals)
)

# Emission transitions (downward arrows): y_start at upper level, y_end near lower level
# Arrow stops 0.015 display-units above the lower level line so the head clears the line
transitions_df <- data.frame(
  n_upper  = c(2, 3, 4,    3, 4,    4, 5),
  n_lower  = c(1, 1, 1,    2, 2,    3, 3),
  series   = c(
    "Lyman (UV)", "Lyman (UV)", "Lyman (UV)",
    "Balmer (visible)", "Balmer (visible)",
    "Paschen (IR)", "Paschen (IR)"
  ),
  x_pos    = c(0.17, 0.25, 0.33,   0.47, 0.57,   0.71, 0.80),
  wl_label = c("Lyα 122 nm", "", "", "Hα 656 nm", "", "Paα 1875 nm", ""),
  stringsAsFactors = FALSE
)
transitions_df$y_start <- -1.0 / transitions_df$n_upper
transitions_df$y_end   <- -1.0 / transitions_df$n_lower + 0.015
transitions_df$y_mid   <- (transitions_df$y_start + transitions_df$y_end) / 2

series_colors <- c(
  "Lyman (UV)"       = IMPRINT_PALETTE[1],
  "Balmer (visible)" = IMPRINT_PALETTE[2],
  "Paschen (IR)"     = IMPRINT_PALETTE[3]
)

# Subset for wavelength annotations (only labeled transitions)
wl_df <- transitions_df[transitions_df$wl_label != "", ]

# Adaptive title size
title_str  <- "Hydrogen Atom · energy-level-atomic · r · ggplot2 · anyplot.ai"
title_size <- max(8, round(12 * 67 / nchar(title_str)))

# Y-axis breaks at the display positions, labeled with actual energies
y_breaks <- c(y_disp, 0)
y_labels <- c(sprintf("%.2f eV", e_n), "0 eV")

# Plot
p <- ggplot() +
  # Ionization limit
  geom_hline(yintercept = 0, color = INK_SOFT,
             linetype = "dashed", linewidth = 0.45) +
  # Energy level horizontal lines
  geom_segment(
    data = levels_df,
    aes(x = 0.08, xend = 0.90, y = y, yend = y),
    color = INK, linewidth = 0.9
  ) +
  # Quantum number labels (right side)
  geom_text(
    data = levels_df,
    aes(x = 0.92, y = y, label = label),
    hjust = 0, color = INK_SOFT, size = 3.0
  ) +
  # Emission transition arrows
  geom_segment(
    data = transitions_df,
    aes(x = x_pos, xend = x_pos,
        y = y_start, yend = y_end,
        color = series),
    arrow       = arrow(length = unit(0.065, "inches"), type = "closed"),
    linewidth   = 1.1,
    show.legend = FALSE
  ) +
  # Wavelength annotations for representative transitions in each series
  geom_text(
    data = wl_df,
    aes(x = x_pos + 0.018, y = y_mid, label = wl_label),
    color = INK_SOFT, size = 2.4, hjust = 0, fontface = "italic"
  ) +
  # Series labels below the n=1 level
  annotate("text", x = 0.25, y = -1.09, label = "Lyman (UV)",
           color = IMPRINT_PALETTE[1], size = 2.9, fontface = "bold") +
  annotate("text", x = 0.52, y = -1.09, label = "Balmer (visible)",
           color = IMPRINT_PALETTE[2], size = 2.9, fontface = "bold") +
  annotate("text", x = 0.755, y = -1.09, label = "Paschen (IR)",
           color = IMPRINT_PALETTE[3], size = 2.9, fontface = "bold") +
  # Ionization label (size increased to 3.0 for better mobile readability)
  annotate("text", x = 0.50, y = 0.032, label = "Ionization limit (0 eV)",
           color = INK_SOFT, size = 3.0, hjust = 0.5) +
  scale_color_manual(values = series_colors) +
  scale_x_continuous(limits = c(-0.05, 1.20), expand = c(0, 0)) +
  scale_y_continuous(
    name   = "Energy (eV)",
    breaks = y_breaks,
    labels = y_labels,
    limits = c(-1.15, 0.06)
  ) +
  coord_cartesian(clip = "off") +
  labs(title = title_str, x = NULL) +
  theme_minimal(base_size = 8) +
  theme(
    plot.background  = element_rect(fill = PAGE_BG, color = PAGE_BG),
    panel.background = element_rect(fill = PAGE_BG, color = NA),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border     = element_blank(),
    axis.title.y     = element_text(color = INK,      size = 10),
    axis.title.x     = element_blank(),
    axis.text.y      = element_text(color = INK_SOFT, size = 8),
    axis.text.x      = element_blank(),
    axis.ticks.x     = element_blank(),
    axis.ticks.y     = element_line(color = INK_SOFT, linewidth = 0.4),
    axis.line.y      = element_line(color = INK_SOFT, linewidth = 0.5),
    plot.title       = element_text(color = INK, size = title_size, face = "plain"),
    legend.position  = "none",
    plot.margin      = margin(15, 15, 15, 5)
  )

# Save
ggsave(
  filename = sprintf("plot-%s.png", THEME),
  plot     = p,
  device   = ragg::agg_png,
  width    = 8,
  height   = 4.5,
  units    = "in",
  dpi      = 400
)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/energy-level-atomic/ggplot2/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "energy-level-atomic",
  "language": "r",
  "library": "ggplot2",
  "page": "https://anyplot.ai/energy-level-atomic/r/ggplot2",
  "hub": "https://anyplot.ai/energy-level-atomic",
  "code_json": "https://api.anyplot.ai/specs/energy-level-atomic/ggplot2/code",
  "spec_json": "https://api.anyplot.ai/specs/energy-level-atomic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/energy-level-atomic/r/ggplot2/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/energy-level-atomic/r/ggplot2/plot-dark.png",
  "quality_score": 88.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Atomic Energy Level Diagram on anyplot.ai.

Other implementations