{"spec_id":"titration-curve","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\ntitration-curve: Acid-Base Titration Curve\nLibrary: matplotlib 3.11.0 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-24\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent this file from shadowing the installed matplotlib package on sys.path\n_sd = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _sd]\ndel _sd\n\nimport matplotlib.pyplot as plt\nimport matplotlib.ticker as ticker\nimport numpy as np\nfrom matplotlib.lines import Line2D\n\n\n# --- Theme ---\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# --- Data: 25 mL of 0.1 M HCl titrated with 0.1 M NaOH ---\nc_acid, v_acid, c_base = 0.1, 25.0, 0.1\nvolume_ml = np.linspace(0.01, 50, 1000)\nmoles_acid = c_acid * v_acid / 1000\nmoles_base = c_base * volume_ml / 1000\ntotal_volume = (v_acid + volume_ml) / 1000\n\nexcess_acid = moles_acid - moles_base\nexcess_base = moles_base - moles_acid\nph = np.where(\n    excess_acid > 1e-10,\n    -np.log10(excess_acid / total_volume),\n    np.where(excess_base > 1e-10, 14.0 + np.log10(excess_base / total_volume), 7.0),\n)\n\ndph_dv = np.gradient(ph, volume_ml)\neq_volume = v_acid * c_acid / c_base  # 25 mL\neq_ph = 7.0\n\ndph_cap = np.percentile(dph_dv, 99) * 1.3\ndph_dv_display = np.clip(dph_dv, 0, dph_cap)\n\n# --- Canvas: 3200×1800 px (landscape 16:9) ---\nfig, ax1 = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax1.set_facecolor(PAGE_BG)\nax2 = ax1.twinx()\nax2.patch.set_alpha(0)\n\nph_color = IMPRINT[0]  # #009E73 — Imprint position 1 (brand green)\ndphdv_color = IMPRINT[1]  # #C475FD — Imprint position 2 (lavender)\n\n# --- pH curve (primary) ---\nax1.plot(volume_ml, ph, color=ph_color, linewidth=2.5, zorder=3, solid_capstyle=\"round\")\n\n# --- Derivative curve (secondary) ---\nax2.plot(volume_ml, dph_dv_display, color=dphdv_color, linewidth=2.0, alpha=0.85, zorder=2)\npeak_mask = (volume_ml >= 20) & (volume_ml <= 30)\nax2.fill_between(volume_ml[peak_mask], 0, dph_dv_display[peak_mask], color=dphdv_color, alpha=0.15, zorder=1)\n\n# --- Equivalence point — dashed vertical line (spec requirement) ---\nax1.axvline(x=eq_volume, color=INK_MUTED, linestyle=\"--\", linewidth=0.9, alpha=0.5, zorder=2)\nax1.scatter([eq_volume], [eq_ph], color=IMPRINT[4], s=120, edgecolors=\"white\", linewidth=1.5, zorder=5)\nax1.annotate(\n    f\"Equivalence Point\\n{eq_volume:.0f} mL · pH {eq_ph:.0f}\",\n    xy=(eq_volume, eq_ph),\n    xytext=(eq_volume + 9, eq_ph - 3.5),\n    fontsize=7,\n    fontweight=\"medium\",\n    color=INK,\n    arrowprops={\"arrowstyle\": \"-|>\", \"color\": INK_SOFT, \"lw\": 1.2, \"connectionstyle\": \"arc3,rad=-0.15\"},\n    bbox={\n        \"boxstyle\": \"round,pad=0.4\",\n        \"facecolor\": ELEVATED_BG,\n        \"edgecolor\": IMPRINT[4],\n        \"alpha\": 0.95,\n        \"linewidth\": 1.0,\n    },\n    zorder=6,\n)\n\n# --- Buffer region shading (lighter alpha) ---\nbuf_mask = (volume_ml >= 2) & (volume_ml <= 20)\nax1.fill_between(volume_ml[buf_mask], 0, ph[buf_mask], alpha=0.08, color=ph_color, zorder=1, linewidth=0)\nax1.text(\n    11,\n    1.5,\n    \"Gradual pH Change Region\",\n    fontsize=7,\n    color=ph_color,\n    alpha=0.85,\n    ha=\"center\",\n    style=\"italic\",\n    bbox={\"boxstyle\": \"round,pad=0.25\", \"facecolor\": ELEVATED_BG, \"edgecolor\": \"none\", \"alpha\": 0.7},\n)\n\n# --- Steep rise annotation ---\nax1.annotate(\n    \"Steep rise\\nnear equivalence\",\n    xy=(24.5, 6),\n    xytext=(10, 11.5),\n    fontsize=7,\n    color=INK_SOFT,\n    ha=\"center\",\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK_MUTED, \"lw\": 1.0, \"connectionstyle\": \"arc3,rad=0.3\"},\n    bbox={\n        \"boxstyle\": \"round,pad=0.25\",\n        \"facecolor\": ELEVATED_BG,\n        \"edgecolor\": INK_SOFT,\n        \"alpha\": 0.85,\n        \"linewidth\": 0.7,\n    },\n)\n\n# --- Neutral pH reference line ---\nax1.axhline(y=7, color=INK_MUTED, linestyle=\"--\", linewidth=0.6, alpha=0.35, zorder=1)\nax1.text(49.5, 7.3, \"pH 7\", fontsize=7, color=INK_MUTED, ha=\"right\", alpha=0.7)\n\n# --- Primary axis styling ---\nax1.set_xlabel(\"Volume of NaOH added (mL)\", fontsize=10, color=INK, labelpad=6)\nax1.set_ylabel(\"pH\", fontsize=10, color=ph_color, labelpad=6)\nax1.set_ylim(0, 14)\nax1.set_xlim(0, 50)\nax1.tick_params(axis=\"x\", labelsize=8, colors=INK_SOFT)\nax1.tick_params(axis=\"y\", labelsize=8, colors=ph_color, labelcolor=ph_color)\nax1.spines[\"top\"].set_visible(False)\nax1.spines[\"right\"].set_visible(False)\nax1.spines[\"left\"].set_color(ph_color)\nax1.spines[\"bottom\"].set_color(INK_SOFT)\nax1.yaxis.grid(True, alpha=0.12, linewidth=0.6, color=INK)\nax1.xaxis.set_minor_locator(ticker.AutoMinorLocator(2))\nax1.yaxis.set_minor_locator(ticker.AutoMinorLocator(2))\nax1.tick_params(which=\"minor\", length=2, color=INK_MUTED)\n\n# --- Secondary axis styling ---\nax2.set_ylabel(\"dpH/dV (rate of pH change)\", fontsize=10, color=dphdv_color, labelpad=6)\nax2.tick_params(axis=\"y\", labelsize=8, colors=dphdv_color, labelcolor=dphdv_color)\nax2.spines[\"top\"].set_visible(False)\nax2.spines[\"right\"].set_color(dphdv_color)\n\n# --- Legend ---\nlegend_elements = [\n    Line2D([0], [0], color=ph_color, linewidth=2.5, label=\"pH curve\"),\n    Line2D([0], [0], color=dphdv_color, linewidth=2.0, label=\"dpH/dV\"),\n]\nleg = ax1.legend(handles=legend_elements, fontsize=8, loc=\"upper left\")\nleg.get_frame().set_facecolor(ELEVATED_BG)\nleg.get_frame().set_edgecolor(INK_SOFT)\nplt.setp(leg.get_texts(), color=INK_SOFT)\n\n# --- Title (fontsize scaled by length) ---\ntitle = \"HCl + NaOH Titration · titration-curve · matplotlib · anyplot.ai\"\ntitle_fontsize = max(8, round(12 * 67 / len(title))) if len(title) > 67 else 12\nax1.set_title(title, fontsize=title_fontsize, fontweight=\"medium\", pad=10, color=INK)\n\nfig.subplots_adjust(left=0.08, right=0.87, top=0.92, bottom=0.12)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}