{"spec_id":"titration-curve","library":"makie","language":"julia","code":"# anyplot.ai\n# titration-curve: Acid-Base Titration Curve\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 91/100 | Created: 2026-06-24\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\nconst THEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst INK_MUTED   = THEME == \"light\" ? colorant\"#6B6A63\" : colorant\"#A8A79F\"\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",\n    colorant\"#C475FD\",\n    colorant\"#4467A3\",\n    colorant\"#BD8233\",\n    colorant\"#AE3030\",\n    colorant\"#2ABCCD\",\n    colorant\"#954477\",\n    colorant\"#99B314\",\n]\n\n# Data: 25 mL of 0.1 M HCl titrated with 0.1 M NaOH\nconst V_EQ  = 25.0\nconst C     = 0.1\nconst V_HCL = 25.0\n\nn_points = 200\nvolumes  = collect(range(0.0, 50.0; length = n_points))\n\nph = clamp.([\n    v < V_EQ - 1e-4 ? -log10((V_EQ - v) * C * 1e-3 / ((V_HCL + v) * 1e-3)) :\n    v > V_EQ + 1e-4 ? 14.0 + log10((v - V_EQ) * C * 1e-3 / ((V_HCL + v) * 1e-3)) :\n    7.0\n    for v in volumes\n], 0.0, 14.0)\n\ndph_dv = zeros(n_points)\nfor i in 2:(n_points - 1)\n    dph_dv[i] = (ph[i + 1] - ph[i - 1]) / (volumes[i + 1] - volumes[i - 1])\nend\ndph_dv[1]   = dph_dv[2]\ndph_dv[end] = dph_dv[end - 1]\n\n# Title with length-aware fontsize\ntitle_str = \"HCl/NaOH Titration · titration-curve · julia · makie · anyplot.ai\"\ntitlesize = max(16, round(Int, 20.0 * min(1.0, 67.0 / length(title_str))))\n\n# Figure — extra right padding gives the secondary y-axis label room\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n    figure_padding  = (10, 35, 10, 10),\n)\n\n# Primary axis (pH)\nax = Axis(\n    fig[1, 1];\n    title              = title_str,\n    titlesize          = titlesize,\n    titlecolor         = INK,\n    xlabel             = \"Volume of NaOH added (mL)\",\n    ylabel             = \"pH\",\n    xlabelsize         = 14,\n    ylabelsize         = 14,\n    xticklabelsize     = 12,\n    yticklabelsize     = 12,\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xtickcolor         = INK_SOFT,\n    ytickcolor         = INK_SOFT,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridvisible       = false,\n    ygridvisible       = true,\n    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.12f0),\n    xminorgridvisible  = false,\n    yminorgridvisible  = false,\n)\n\n# Secondary axis (dpH/dV derivative, right side)\nax2 = Axis(\n    fig[1, 1];\n    yaxisposition      = :right,\n    ylabel             = \"dpH/dV (pH·mL⁻¹)\",\n    ylabelsize         = 12,\n    ylabelcolor        = IMPRINT_PALETTE[2],\n    yticklabelsize     = 12,\n    yticklabelcolor    = IMPRINT_PALETTE[2],\n    ytickcolor         = IMPRINT_PALETTE[2],\n    rightspinecolor    = IMPRINT_PALETTE[2],\n    backgroundcolor    = :transparent,\n    topspinevisible    = false,\n    leftspinevisible   = false,\n    bottomspinevisible = false,\n    xticklabelsvisible = false,\n    xticksvisible      = false,\n    xgridvisible       = false,\n    ygridvisible       = false,\n    xminorgridvisible  = false,\n    yminorgridvisible  = false,\n)\n\nlinkxaxes!(ax, ax2)\nxlims!(ax, 0, 50)\nylims!(ax, 0, 14)\nylims!(ax2, -2, 22)\n\n# Rapid transition zone highlight (±2 mL around equivalence point)\npoly!(\n    ax,\n    [Point2f(23, 0), Point2f(27, 0), Point2f(27, 14), Point2f(23, 14)];\n    color       = (IMPRINT_PALETTE[6], 0.15),\n    strokewidth = 0,\n)\n\n# pH=7 reference line — marks neutral point at strong acid/base equivalence\nhlines!(ax, [7.0];\n    color     = (INK_MUTED, 0.5),\n    linewidth = 1.0,\n    linestyle = :dot,\n)\n\n# pH titration curve\nph_line = lines!(ax, volumes, ph;\n    color     = IMPRINT_PALETTE[1],\n    linewidth = 3.0,\n)\n\n# Equivalence point vertical dashed line\nvlines!(ax, [V_EQ];\n    color     = (INK_MUTED, 0.75),\n    linewidth = 1.5,\n    linestyle = :dash,\n)\n\n# Equivalence point annotation\ntext!(ax, \"Equivalence point\\n(V = 25 mL, pH = 7)\";\n    position = Point2f(V_EQ + 1.5, 4.5),\n    fontsize  = 11,\n    color     = INK_SOFT,\n    align     = (:left, :center),\n)\n\n# Derivative curve on secondary axis\nderiv_line = lines!(ax2, volumes, dph_dv;\n    color     = IMPRINT_PALETTE[2],\n    linewidth = 2.0,\n    linestyle = :dash,\n)\n\n# Legend\npoly_elem = PolyElement(color = (IMPRINT_PALETTE[6], 0.15), strokewidth = 0)\n\nLegend(\n    fig[1, 1],\n    [ph_line, deriv_line, poly_elem],\n    [\"pH curve\", \"dpH/dV (derivative)\", \"Transition zone\"];\n    tellheight      = false,\n    tellwidth       = false,\n    halign          = :left,\n    valign          = :top,\n    framecolor      = (INK_SOFT, 0.3),\n    backgroundcolor = ELEVATED_BG,\n    labelcolor      = INK_SOFT,\n    labelsize       = 11,\n    padding         = (10, 10, 10, 10),\n    rowgap          = 4,\n)\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}