{"spec_id":"bar-tornado-sensitivity","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' bar-tornado-sensitivity: Tornado Diagram for Sensitivity Analysis\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 89/100 | Created: 2026-06-02\n\nlibrary(ggplot2)\nlibrary(dplyr)\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 — Low Input (position 1) and High Input (position 2)\nCOLOR_LOW  <- \"#009E73\"  # brand green — first series\nCOLOR_HIGH <- \"#C475FD\"  # lavender — second series\n\n# Base case NPV for a solar energy project\nbase_npv <- 42.5\n\n# Sensitivity analysis: NPV (USD millions) when each parameter is at its low/high bound\nparams <- tibble::tibble(\n  parameter = c(\n    \"Electricity Price\",\n    \"Discount Rate\",\n    \"Construction Cost\",\n    \"Capacity Factor\",\n    \"Operating Cost\",\n    \"Project Lifetime\",\n    \"Financing Rate\",\n    \"Tax Rate\",\n    \"Land Lease Cost\"\n  ),\n  low_value  = c(28.0, 58.5, 51.8, 34.8, 46.2, 37.5, 48.3, 46.0, 43.5),\n  high_value = c(57.0, 26.5, 33.2, 51.5, 38.8, 47.5, 37.2, 39.0, 41.8)\n)\n\n# Sort by range ascending — smallest range at bottom, widest at top (tornado shape)\ndf <- params |>\n  mutate(range = abs(high_value - low_value)) |>\n  arrange(range) |>\n  mutate(\n    parameter = factor(parameter, levels = parameter),\n    y_pos     = as.integer(parameter)\n  )\n\n# Bold the top parameter (widest range = most sensitive) to guide viewer's eye\ny_faces <- c(rep(\"plain\", nrow(df) - 1L), \"bold\")\n\n# Build bar segments: one row per (parameter x scenario) with tip label coords\nbar_h <- 0.35\n\ndf_bars <- bind_rows(\n  df |> transmute(\n    y_pos,\n    xmin        = pmin(low_value,  base_npv),\n    xmax        = pmax(low_value,  base_npv),\n    outer_x     = low_value,\n    label_hjust = if_else(low_value  > base_npv, 0, 1),\n    scenario    = \"Low Input\"\n  ),\n  df |> transmute(\n    y_pos,\n    xmin        = pmin(high_value, base_npv),\n    xmax        = pmax(high_value, base_npv),\n    outer_x     = high_value,\n    label_hjust = if_else(high_value > base_npv, 0, 1),\n    scenario    = \"High Input\"\n  )\n) |>\n  mutate(\n    ymin     = y_pos - bar_h,\n    ymax     = y_pos + bar_h,\n    scenario = factor(scenario, levels = c(\"Low Input\", \"High Input\")),\n    label    = sprintf(\"$%.1fM\", outer_x),\n    label_x  = if_else(label_hjust == 0L, outer_x + 0.5, outer_x - 0.5)\n  )\n\ntitle_text <- \"bar-tornado-sensitivity · r · ggplot2 · anyplot.ai\"\n\np <- ggplot(df_bars) +\n  geom_rect(\n    aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = scenario),\n    color = NA,\n    alpha = 0.88\n  ) +\n  geom_text(\n    aes(x = label_x, y = y_pos, label = label, hjust = label_hjust),\n    color = INK_SOFT,\n    size  = 1.9\n  ) +\n  geom_vline(\n    xintercept = base_npv,\n    color      = INK,\n    linewidth  = 0.9\n  ) +\n  annotate(\n    \"text\",\n    x     = base_npv,\n    y     = Inf,\n    label = sprintf(\"Base: $%.1fM\", base_npv),\n    color = INK_MUTED,\n    size  = 2.5,\n    hjust = 0.5,\n    vjust = 1.4\n  ) +\n  scale_fill_manual(\n    values = c(\"Low Input\" = COLOR_LOW, \"High Input\" = COLOR_HIGH),\n    name   = \"Input Scenario\"\n  ) +\n  scale_x_continuous(\n    labels = function(x) sprintf(\"$%gM\", x),\n    expand = expansion(mult = 0.12)\n  ) +\n  scale_y_continuous(\n    breaks = df$y_pos,\n    labels = levels(df$parameter),\n    expand = expansion(add = c(0.5, 0.8))\n  ) +\n  labs(\n    title = title_text,\n    x     = \"Net Present Value (USD Millions)\",\n    y     = NULL\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 = INK_SOFT,   linewidth = 0.2),\n    panel.grid.major.y = element_blank(),\n    panel.grid.minor   = element_blank(),\n    panel.border       = element_blank(),\n    axis.line          = element_blank(),\n    axis.title.x       = element_text(color = INK,        size = 10),\n    axis.text.x        = element_text(color = INK_SOFT,   size = 8),\n    axis.text.y        = element_text(color = INK,        size = 9,  hjust = 1,\n                                      face = y_faces),\n    axis.ticks.y       = element_blank(),\n    plot.title         = element_text(color = INK,        size = 12, hjust = 0.5),\n    legend.background  = element_rect(fill = ELEVATED_BG, color = NA),\n    legend.text        = element_text(color = INK_SOFT,   size = 8),\n    legend.title       = element_text(color = INK,        size = 9),\n    legend.key.size    = unit(0.4, \"cm\"),\n    legend.position    = \"bottom\",\n    legend.direction   = \"horizontal\",\n    legend.key         = element_rect(fill = ELEVATED_BG, color = NA),\n    plot.margin        = margin(t = 15, r = 25, b = 10, l = 10)\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"}