{"spec_id":"bullet-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' bullet-basic: Basic Bullet Chart\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 89/100 | Created: 2026-05-29\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\"\nGRID        <- paste0(INK, \"26\")  # INK at ~15% opacity for subtle gridlines\n\n# Qualitative band fills (grayscale, theme-adaptive: dark = poor, light = good)\nBAND_POOR <- if (THEME == \"light\") \"#C4C4BE\" else \"#353530\"\nBAND_SATS <- if (THEME == \"light\") \"#D8D8D2\" else \"#464641\"\nBAND_GOOD <- if (THEME == \"light\") \"#EBEBEA\" else \"#575752\"\n\n# Imprint palette — position 1 used for the actual performance bar\nIMPRINT_PALETTE <- c(\n  \"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n  \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"\n)\n\n# --- Data -------------------------------------------------------------------\n# Q2 departmental KPI dashboard: actual performance vs. targets (% of goal)\nmetrics <- data.frame(\n  label      = c(\"Revenue\", \"Customer Sat.\", \"Cost Efficiency\", \"Product Quality\", \"Market Share\"),\n  actual     = c(87, 72, 63, 91, 58),\n  target     = c(95, 80, 75, 85, 70),\n  range_poor = c(60, 50, 40, 60, 40),\n  range_sats = c(80, 70, 65, 80, 65),\n  range_good = c(100, 100, 100, 100, 100),\n  stringsAsFactors = FALSE\n)\n\nn_met     <- nrow(metrics)\n# Reversed so first metric appears at the top of the chart\nmetrics$y_pos <- seq(n_met, 1)\n\nband_h <- 0.40  # half-height of qualitative background bands\nbar_h  <- 0.20  # half-height of actual performance bar\n\n# Background qualitative bands (poor / satisfactory / good)\nbands_df <- rbind(\n  data.frame(\n    xmin = rep(0, n_met), xmax = metrics$range_poor,\n    ymin = metrics$y_pos - band_h, ymax = metrics$y_pos + band_h,\n    fill_color = BAND_POOR, stringsAsFactors = FALSE\n  ),\n  data.frame(\n    xmin = metrics$range_poor, xmax = metrics$range_sats,\n    ymin = metrics$y_pos - band_h, ymax = metrics$y_pos + band_h,\n    fill_color = BAND_SATS, stringsAsFactors = FALSE\n  ),\n  data.frame(\n    xmin = metrics$range_sats, xmax = metrics$range_good,\n    ymin = metrics$y_pos - band_h, ymax = metrics$y_pos + band_h,\n    fill_color = BAND_GOOD, stringsAsFactors = FALSE\n  )\n)\n\n# Actual performance bars (narrower than bands)\nbars_df <- data.frame(\n  xmin = 0, xmax = metrics$actual,\n  ymin = metrics$y_pos - bar_h, ymax = metrics$y_pos + bar_h\n)\n\n# Target markers (vertical lines taller than the bar but shorter than the bands)\ntargets_df <- data.frame(\n  x    = metrics$target,\n  ymin = metrics$y_pos - band_h * 0.9,\n  ymax = metrics$y_pos + band_h * 0.9\n)\n\n# Value labels — above-target metric highlighted with bar color for emphasis\nabove_target <- metrics$actual >= metrics$target\nlabels_df <- data.frame(\n  x      = metrics$actual + 1.5,\n  y      = metrics$y_pos,\n  label  = paste0(metrics$actual, \"%\"),\n  lcolor = ifelse(above_target, IMPRINT_PALETTE[1], INK_SOFT),\n  stringsAsFactors = FALSE\n)\n\n# Zone midpoints for annotate() labels in the top row (Revenue, y_pos = 5)\ntop_y    <- metrics$y_pos[1]\npoor_mid <- metrics$range_poor[1] / 2\nsats_mid <- metrics$range_poor[1] + (metrics$range_sats[1] - metrics$range_poor[1]) / 2\ngood_mid <- metrics$range_sats[1] + (metrics$range_good[1] - metrics$range_sats[1]) / 2\n\n# --- Plot -------------------------------------------------------------------\ntitle_str  <- \"bullet-basic · r · ggplot2 · anyplot.ai\"\nn_ch       <- nchar(title_str)\ntitle_size <- max(8, round(12 * (if (n_ch > 67) 67 / n_ch else 1.0)))\n\np <- ggplot() +\n  # Qualitative background bands\n  geom_rect(\n    data = bands_df,\n    aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = fill_color),\n    color = NA\n  ) +\n  scale_fill_identity(guide = \"none\") +\n  # Zone labels via annotate() — ggplot2-idiomatic alternative to a side legend\n  annotate(\"text\", x = poor_mid, y = top_y + band_h * 0.62,\n           label = \"Poor\", color = INK_MUTED, size = 2.3, fontface = \"italic\") +\n  annotate(\"text\", x = sats_mid, y = top_y + band_h * 0.62,\n           label = \"Satisfactory\", color = INK_MUTED, size = 2.3, fontface = \"italic\") +\n  annotate(\"text\", x = good_mid, y = top_y + band_h * 0.62,\n           label = \"Good\", color = INK_MUTED, size = 2.3, fontface = \"italic\") +\n  # Actual performance bars (Imprint palette position 1)\n  geom_rect(\n    data = bars_df,\n    aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),\n    fill = IMPRINT_PALETTE[1], color = NA\n  ) +\n  # Target markers as thin vertical lines\n  geom_segment(\n    data = targets_df,\n    aes(x = x, xend = x, y = ymin, yend = ymax),\n    color = INK, linewidth = 1.5\n  ) +\n  # Value labels — above-target metric rendered in bar color for clear emphasis\n  geom_text(\n    data = labels_df,\n    aes(x = x, y = y, label = label, color = lcolor),\n    size = 3.0, hjust = 0\n  ) +\n  scale_color_identity() +\n  scale_y_continuous(\n    breaks = metrics$y_pos,\n    labels = metrics$label,\n    expand = expansion(add = 0.7)\n  ) +\n  scale_x_continuous(\n    limits = c(0, 107),\n    expand = expansion(mult = 0),\n    labels = function(x) paste0(x, \"%\"),\n    breaks = seq(0, 100, by = 20)\n  ) +\n  labs(\n    title   = title_str,\n    x       = \"Performance (% of target)\",\n    y       = NULL,\n    caption = \"Bar = actual performance  ·  Vertical line = target  ·  Green label = above target\"\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.x = element_line(color = GRID, linewidth = 0.3),\n    panel.grid.major.y = element_blank(),\n    panel.grid.minor   = element_blank(),\n    axis.title.x       = element_text(color = INK, size = 10, margin = margin(t = 8)),\n    axis.text.x        = element_text(color = INK_SOFT, size = 8),\n    axis.text.y        = element_text(color = INK, size = 9, hjust = 1),\n    axis.ticks         = element_blank(),\n    plot.title         = element_text(\n      color = INK, size = title_size, face = \"bold\", margin = margin(b = 10)\n    ),\n    plot.caption       = element_text(color = INK_MUTED, size = 8, hjust = 0),\n    legend.position    = \"none\",\n    plot.margin        = margin(t = 15, r = 20, b = 12, l = 10)\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"}