{"spec_id":"titration-curve","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' titration-curve: Acid-Base Titration Curve\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 90/100 | Created: 2026-06-24\n\nlibrary(ggplot2)\nlibrary(ragg)\n\n# Theme tokens (Imprint palette, theme-adaptive chrome)\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_COLOR  <- if (THEME == \"light\") \"#C5C4BD\" else \"#494945\"\nIMPRINT_PALETTE <- c(\n  \"#009E73\",  # 1 brand green — titration curve (first series, always)\n  \"#C475FD\",  # 2 lavender    — derivative overlay\n  \"#4467A3\",  # 3 blue\n  \"#BD8233\",  # 4 ochre\n  \"#AE3030\",  # 5 matte red\n  \"#2ABCCD\",  # 6 cyan\n  \"#954477\",  # 7 rose\n  \"#99B314\"   # 8 lime\n)\n\n# Data: 25 mL of 0.1 M HCl titrated with 0.1 M NaOH\nc_acid   <- 0.1    # mol/L\nv_acid   <- 25.0   # mL\nc_base   <- 0.1    # mol/L\nvol_step <- 0.25   # mL\n\nvolume_ml <- seq(0, 50, by = vol_step)\nmmol_acid <- c_acid * v_acid  # 2.5 mmol\n\nmmol_base <- c_base * volume_ml\ntotal_vol <- v_acid + volume_ml\nexcess    <- mmol_acid - mmol_base\n\npH <- ifelse(\n  abs(excess) < 1e-9,\n  7.0,\n  ifelse(excess > 0, -log10(abs(excess) / total_vol), 14 + log10(abs(excess) / total_vol))\n)\n\n# Derivative dpH/dV (forward finite difference; last point set to NA)\ndphdv_raw <- c(diff(pH) / diff(volume_ml), NA_real_)\n\n# Scale derivative to primary axis range [0, 12] — leaves headroom below pH 14\nmax_dphdv    <- max(dphdv_raw, na.rm = TRUE)\ndphdv_scaled <- dphdv_raw / max_dphdv * 12\n\ndf <- data.frame(\n  volume_ml    = volume_ml,\n  pH           = pH,\n  dphdv_scaled = dphdv_scaled\n)\n\n# Equivalence point: 25 mL NaOH, pH 7 for strong acid/base\nequiv_vol <- v_acid\nequiv_ph  <- 7.0\n\n# Secondary axis breaks in dpH/dV units\nsec_breaks <- pretty(c(0, max_dphdv), n = 5)\n\n# Title — 42 chars (< 67 baseline), no font-size scaling required\ntitle_str <- \"titration-curve · r · ggplot2 · anyplot.ai\"\n\np <- ggplot(df, aes(x = volume_ml)) +\n  # Steep-slope transition zone (pH 4–10): key region around equivalence\n  annotate(\"rect\",\n    xmin = -Inf, xmax = Inf,\n    ymin = 4, ymax = 10,\n    fill = IMPRINT_PALETTE[3], alpha = 0.07\n  ) +\n  # Derivative overlay (scaled to primary axis, dotted)\n  geom_line(\n    aes(y = dphdv_scaled, color = \"dpH/dV\", linetype = \"dpH/dV\"),\n    linewidth = 1.0, na.rm = TRUE\n  ) +\n  # Main titration curve\n  geom_line(\n    aes(y = pH, color = \"pH\", linetype = \"pH\"),\n    linewidth = 1.5\n  ) +\n  # Equivalence point: vertical dashed line (capped at y=11 so derivative spike has breathing room)\n  annotate(\"segment\",\n    x = equiv_vol, xend = equiv_vol,\n    y = 0,         yend = 11,\n    linetype  = \"dashed\",\n    color     = INK_SOFT,\n    linewidth = 0.7\n  ) +\n  # Equivalence point dot on the curve\n  annotate(\"point\",\n    x     = equiv_vol,\n    y     = equiv_ph,\n    shape = 21,\n    size  = 3.5,\n    fill  = IMPRINT_PALETTE[1],\n    color = PAGE_BG\n  ) +\n  # Equivalence point text annotation\n  annotate(\"text\",\n    x     = equiv_vol + 1.3,\n    y     = 1.8,\n    label = paste0(\"EP: \", equiv_vol, \" mL\\npH = \", equiv_ph),\n    hjust = 0,\n    size  = 3.0,\n    color = INK_MUTED\n  ) +\n  # Transition zone label (right side, in empty area above plateau)\n  annotate(\"text\",\n    x     = 40,\n    y     = 7.0,\n    label = \"Transition\\nzone\",\n    hjust = 0.5,\n    size  = 2.8,\n    color = IMPRINT_PALETTE[3],\n    alpha = 0.75\n  ) +\n  # Color + linetype scales (merged into single legend)\n  scale_color_manual(\n    name   = NULL,\n    values = c(\"pH\" = IMPRINT_PALETTE[1], \"dpH/dV\" = IMPRINT_PALETTE[2]),\n    guide  = guide_legend(override.aes = list(linewidth = c(1.5, 1.0)))\n  ) +\n  scale_linetype_manual(\n    name   = NULL,\n    values = c(\"pH\" = \"solid\", \"dpH/dV\" = \"dotted\")\n  ) +\n  # Dual y-axis: pH primary, dpH/dV secondary\n  scale_y_continuous(\n    name     = \"pH\",\n    limits   = c(0, 14),\n    breaks   = seq(0, 14, by = 2),\n    expand   = expansion(0, 0),\n    sec.axis = sec_axis(\n      ~ . * max_dphdv / 12,\n      name   = \"dpH/dV (pH/mL)\",\n      breaks = sec_breaks\n    )\n  ) +\n  scale_x_continuous(\n    name   = \"Volume of NaOH added (mL)\",\n    limits = c(0, 50),\n    breaks = seq(0, 50, by = 10),\n    expand = expansion(0.01, 0)\n  ) +\n  labs(title = title_str) +\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_blank(),\n    panel.grid.major.y  = element_line(color = GRID_COLOR, linewidth = 0.3),\n    panel.grid.minor    = element_blank(),\n    panel.border        = element_blank(),\n    axis.title.x        = element_text(color = INK,                size = 10),\n    axis.title.y.left   = element_text(color = INK,                size = 10),\n    axis.title.y.right  = element_text(color = IMPRINT_PALETTE[2], size = 9),\n    axis.text.x         = element_text(color = INK_SOFT,           size = 8),\n    axis.text.y.left    = element_text(color = INK_SOFT,           size = 8),\n    axis.text.y.right   = element_text(color = IMPRINT_PALETTE[2], size = 7.5),\n    axis.line.x         = element_line(color = INK_SOFT, linewidth = 0.4),\n    axis.line.y         = element_line(color = INK_SOFT, linewidth = 0.4),\n    plot.title          = element_text(color = INK, size = 12, face = \"bold\"),\n    legend.background   = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.3),\n    legend.text         = element_text(color = INK_SOFT, size = 8),\n    legend.key.width    = unit(1.5, \"cm\"),\n    legend.position        = \"inside\",\n    legend.position.inside = c(0.08, 0.88),\n    legend.justification   = c(0, 1),\n    plot.margin         = margin(15, 20, 12, 12)\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"}