{"spec_id":"audiogram-clinical","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' audiogram-clinical: Clinical Audiogram\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 92/100 | Created: 2026-06-15\n\nlibrary(ggplot2)\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\"\n\n# Imprint palette — semantic exception applied: audiogram uses the international\n# clinical convention of right ear = red, left ear = blue\nRIGHT_COLOR <- \"#AE3030\"  # Imprint matte red — right ear (O marker)\nLEFT_COLOR  <- \"#4467A3\"  # Imprint blue — left ear (X marker)\nBAND_ALPHA  <- 0.12       # severity band fill opacity\n\n# --- Data -------------------------------------------------------------------\n# Noise-induced bilateral high-frequency sensorineural hearing loss\n# Classic 4 kHz notch pattern from occupational noise exposure\nfrequencies <- c(125, 250, 500, 1000, 2000, 4000, 8000)\n\nright_thresholds <- c(10, 10, 15, 15, 20, 55, 65)  # right ear: high-freq slope\nleft_thresholds  <- c(10, 15, 15, 20, 25, 60, 70)  # left ear: slightly worse\n\ndf <- data.frame(\n  frequency = rep(frequencies, 2),\n  threshold = c(right_thresholds, left_thresholds),\n  ear       = factor(\n    rep(c(\"Right Ear (O)\", \"Left Ear (X)\"), each = 7),\n    levels = c(\"Right Ear (O)\", \"Left Ear (X)\")\n  )\n)\n\n# --- Plot -------------------------------------------------------------------\nPLOT_TITLE <- \"audiogram-clinical · r · ggplot2 · anyplot.ai\"\n\n# coord_cartesian extends the view to the full audiometric range without\n# removing any data rows (unlike scale limits which censor OOB points)\nX_LO <- 110   # slightly below 125 Hz for left-side breathing room\nX_HI <- 9000  # slightly above 8000 Hz for right-side breathing room\nY_LO <- -10   # best hearing at top of audiogram\nY_HI <- 120   # profound loss at bottom\n\np <- ggplot(df, aes(x = frequency, y = threshold,\n                    color    = ear,\n                    shape    = ear,\n                    linetype = ear,\n                    group    = ear)) +\n\n  # Severity bands — rendered first so data layers sit on top.\n  # xmin/xmax cover the full coord_cartesian x range to avoid edge gaps.\n  annotate(\"rect\", xmin = X_LO, xmax = X_HI, ymin = Y_LO, ymax = 25,\n           fill = \"#009E73\", alpha = BAND_ALPHA) +\n  annotate(\"rect\", xmin = X_LO, xmax = X_HI, ymin = 25,   ymax = 40,\n           fill = \"#DDCC77\", alpha = BAND_ALPHA) +\n  annotate(\"rect\", xmin = X_LO, xmax = X_HI, ymin = 40,   ymax = 55,\n           fill = \"#BD8233\", alpha = BAND_ALPHA) +\n  annotate(\"rect\", xmin = X_LO, xmax = X_HI, ymin = 55,   ymax = 70,\n           fill = \"#BD8233\", alpha = BAND_ALPHA * 1.5) +\n  annotate(\"rect\", xmin = X_LO, xmax = X_HI, ymin = 70,   ymax = 90,\n           fill = \"#AE3030\", alpha = BAND_ALPHA) +\n  annotate(\"rect\", xmin = X_LO, xmax = X_HI, ymin = 90,   ymax = Y_HI,\n           fill = \"#AE3030\", alpha = BAND_ALPHA * 1.8) +\n\n  # Severity labels at 1000 Hz (log-centre of x-axis, all data in Normal zone here)\n  annotate(\"text\", x = 1000, y = 7.5,  label = \"Normal\",\n           size = 3.5, color = INK_MUTED, hjust = 0.5, fontface = \"italic\") +\n  annotate(\"text\", x = 1000, y = 32.5, label = \"Mild\",\n           size = 3.5, color = INK_MUTED, hjust = 0.5, fontface = \"italic\") +\n  annotate(\"text\", x = 1000, y = 47.5, label = \"Moderate\",\n           size = 3.5, color = INK_MUTED, hjust = 0.5, fontface = \"italic\") +\n  annotate(\"text\", x = 1000, y = 62.5, label = \"Mod. Severe\",\n           size = 3.5, color = INK_MUTED, hjust = 0.5, fontface = \"italic\") +\n  annotate(\"text\", x = 1000, y = 80,   label = \"Severe\",\n           size = 3.5, color = INK_MUTED, hjust = 0.5, fontface = \"italic\") +\n  annotate(\"text\", x = 1000, y = 105,  label = \"Profound\",\n           size = 3.5, color = INK_MUTED, hjust = 0.5, fontface = \"italic\") +\n\n  # Connecting lines drawn before points so markers appear on top\n  geom_line(linewidth = 1.2) +\n\n  # Standard audiogram symbols: shape 1 = open circle (O), shape 4 = cross (X)\n  geom_point(size = 5, stroke = 2.0, fill = NA) +\n\n  # Logarithmic frequency axis with audiometric tick labels\n  scale_x_log10(\n    breaks = c(125, 250, 500, 1000, 2000, 4000, 8000),\n    labels = c(\"125\", \"250\", \"500\", \"1k\", \"2k\", \"4k\", \"8k\")\n  ) +\n\n  # Inverted hearing level axis (0 dB at top, increasing loss downward).\n  # Limits are set via coord_cartesian to avoid OOB row removal.\n  scale_y_reverse(\n    breaks = seq(Y_LO, Y_HI, by = 10)\n  ) +\n\n  # Fix the view to the standard audiometric range without censoring data\n  coord_cartesian(\n    xlim   = c(X_LO, X_HI),\n    ylim   = c(Y_HI, Y_LO),\n    expand = FALSE\n  ) +\n\n  # Unified legend: color + shape + linetype merged by matching NULL name\n  scale_color_manual(\n    name   = NULL,\n    values = c(\"Right Ear (O)\" = RIGHT_COLOR, \"Left Ear (X)\" = LEFT_COLOR)\n  ) +\n  scale_shape_manual(\n    name   = NULL,\n    values = c(\"Right Ear (O)\" = 1, \"Left Ear (X)\" = 4)\n  ) +\n  scale_linetype_manual(\n    name   = NULL,\n    values = c(\"Right Ear (O)\" = \"solid\", \"Left Ear (X)\" = \"dashed\")\n  ) +\n\n  labs(\n    title = PLOT_TITLE,\n    x     = \"Frequency (Hz)\",\n    y     = \"Hearing Level (dB HL)\"\n  ) +\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.border      = element_rect(fill = NA,          color = INK_SOFT,\n                                     linewidth = 0.5),\n    panel.grid.major  = element_line(color = INK_SOFT,   linewidth = 0.2),\n    panel.grid.minor  = element_blank(),\n    axis.title        = element_text(color = INK,        size = 10),\n    axis.text         = element_text(color = INK_SOFT,   size = 8),\n    axis.ticks        = element_line(color = INK_SOFT,   linewidth = 0.3),\n    axis.ticks.length = unit(0.15, \"cm\"),\n    plot.title        = element_text(color = INK,        size = 12,\n                                     margin = margin(b = 12)),\n    legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT,\n                                     linewidth = 0.3),\n    legend.text       = element_text(color = INK_SOFT,   size = 9),\n    legend.title      = element_blank(),\n    legend.position   = \"top\",\n    legend.key        = element_rect(fill = NA,          color = NA),\n    legend.key.width  = unit(1.5, \"cm\"),\n    plot.margin       = margin(20, 20, 20, 20)\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"}