{"spec_id":"campbell-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' campbell-basic: Campbell Diagram\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 89/100 | Created: 2026-05-28\n\nlibrary(ggplot2)\nlibrary(ragg)\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\"\nGRID        <- adjustcolor(INK, alpha.f = 0.12)\n\nIMPRINT_PALETTE <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\")\nCRITICAL_COLOR  <- \"#AE3030\"\n\n# Rotational speed range: 0 to 6000 RPM (turbomachinery operating range)\nspeed <- seq(0, 6000, length.out = 100)\n\n# Natural frequency modes (Hz) with slight gyroscopic speed dependence\nmode_levels <- c(\"1st Bending\", \"1st Torsional\", \"2nd Bending\", \"Axial\")\nmode_data <- rbind(\n  data.frame(speed = speed, frequency = 30 + 0.001 * speed,   mode = \"1st Bending\"),\n  data.frame(speed = speed, frequency = 52 + 0.0005 * speed,  mode = \"1st Torsional\"),\n  data.frame(speed = speed, frequency = 78 - 0.001 * speed,   mode = \"2nd Bending\"),\n  data.frame(speed = speed, frequency = 108 - 0.0008 * speed, mode = \"Axial\")\n)\nmode_data$mode <- factor(mode_data$mode, levels = mode_levels)\n\n# Engine order excitation lines (frequency = order * speed / 60)\norder_data <- rbind(\n  data.frame(speed = speed, frequency = 1 * speed / 60, order = \"1x\"),\n  data.frame(speed = speed, frequency = 2 * speed / 60, order = \"2x\"),\n  data.frame(speed = speed, frequency = 3 * speed / 60, order = \"3x\")\n)\n\n# Critical speed intersections: mode f = a + b*s, engine order f = n*s/60\n# Setting equal: a + b*s = n*s/60 → s_crit = a / (n/60 - b)\ny_max <- 130\nmode_params <- list(\n  list(a = 30,  b =  0.001),\n  list(a = 52,  b =  0.0005),\n  list(a = 78,  b = -0.001),\n  list(a = 108, b = -0.0008)\n)\n\ncritical_rows <- list()\nfor (n in 1:3) {\n  for (m in mode_params) {\n    denom  <- n / 60 - m$b\n    s_crit <- m$a / denom\n    f_crit <- n * s_crit / 60\n    if (s_crit > 0 & s_crit <= 6000 & f_crit <= y_max) {\n      critical_rows[[length(critical_rows) + 1]] <-\n        data.frame(speed = s_crit, frequency = f_crit)\n    }\n  }\n}\ncritical_pts <- do.call(rbind, critical_rows)\n\n# Semi-transparent shading bands centered on each critical speed\nzone_data <- data.frame(\n  xmin = critical_pts$speed - 150,\n  xmax = critical_pts$speed + 150,\n  ymin = 0,\n  ymax = y_max\n)\n\n# Engine order label positions — \"3x\" nudged extra to avoid Axial mode overlap\norder_labels <- data.frame(\n  speed     = c(5700, 3550, 2050),\n  frequency = c(5700 / 60 + 4, 2 * 3550 / 60 + 4, 3 * 2050 / 60 + 10),\n  label     = c(\"1x\", \"2x\", \"3x\")\n)\n\n# Build plot\np <- ggplot() +\n  geom_rect(\n    data = zone_data,\n    aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),\n    fill = CRITICAL_COLOR, alpha = 0.05, inherit.aes = FALSE\n  ) +\n  geom_line(\n    data = order_data,\n    aes(x = speed, y = frequency, group = order),\n    color = INK_MUTED, linewidth = 0.7, linetype = \"dashed\"\n  ) +\n  geom_line(\n    data = mode_data,\n    aes(x = speed, y = frequency, color = mode),\n    linewidth = 1.2\n  ) +\n  geom_point(\n    data = critical_pts,\n    aes(x = speed, y = frequency, shape = \"Critical Speed\"),\n    size = 3.5, color = CRITICAL_COLOR\n  ) +\n  geom_text(\n    data = order_labels,\n    aes(x = speed, y = frequency, label = label),\n    color = INK_MUTED, size = 3.5, hjust = 0.5, fontface = \"italic\"\n  ) +\n  scale_color_manual(\n    name   = \"Natural Frequency Mode\",\n    values = setNames(IMPRINT_PALETTE, mode_levels)\n  ) +\n  scale_shape_manual(\n    name   = \"\",\n    values = c(\"Critical Speed\" = 18)\n  ) +\n  scale_x_continuous(\n    name   = \"Rotational Speed (RPM)\",\n    breaks = seq(0, 6000, by = 1000)\n  ) +\n  scale_y_continuous(\n    name   = \"Frequency (Hz)\",\n    breaks = seq(0, 120, by = 20)\n  ) +\n  coord_cartesian(xlim = c(0, 6400), ylim = c(0, y_max)) +\n  labs(title = \"campbell-basic · r · ggplot2 · anyplot.ai\") +\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_line(color = GRID, linewidth = 0.4),\n    panel.grid.minor  = element_blank(),\n    panel.border      = element_blank(),\n    axis.line         = element_line(color = INK_SOFT, linewidth = 0.5),\n    axis.title        = element_text(color = INK,      size = 10),\n    axis.text         = element_text(color = INK_SOFT, size = 8),\n    plot.title        = element_text(color = INK,      size = 12, face = \"bold\"),\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   = \"right\",\n    plot.margin       = margin(15, 15, 15, 15)\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"}