{"spec_id":"titration-curve","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\ntitration-curve: Acid-Base Titration Curve\nLibrary: plotly 6.8.0 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-06-24\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prioritize venv's site-packages over current directory\nif sys.prefix not in sys.path:\n    import site\n\n    site_packages = site.getsitepackages()\n    if isinstance(site_packages, list):\n        sys.path = site_packages + sys.path\n    else:\n        sys.path.insert(0, site_packages)\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\n\n\n# Theme — Imprint palette chrome tokens\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint categorical palette — 8 hues, theme-independent\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 = 0.1\nv_acid = 25.0\nc_base = 0.1\nvolume_ml = np.unique(\n    np.concatenate([np.linspace(0.0, 24.0, 80), np.linspace(24.0, 26.0, 30), np.linspace(26.0, 50.0, 50)])\n)\n\nph = np.zeros_like(volume_ml)\nfor i, v in enumerate(volume_ml):\n    total_vol = (v_acid + v) / 1000.0\n    moles_acid = c_acid * v_acid / 1000.0\n    moles_base = c_base * v / 1000.0\n    diff = moles_acid - moles_base\n    if diff > 1e-10:\n        ph[i] = -np.log10(diff / total_vol)\n    elif diff < -1e-10:\n        ph[i] = 14.0 + np.log10(-diff / total_vol)\n    else:\n        ph[i] = 7.0\n\nph = np.clip(ph, 0, 14)\n\n# Derivative — log-scaled on secondary axis to reveal both the spike and subtle\n# pre/post-equivalence variation (linear scale compressed everything to near zero)\ndph_dv = np.gradient(ph, volume_ml)\ndph_dv_log = np.clip(dph_dv, 1e-4, None)\n\n# Equivalence point (strong acid/base: pH 7 at 25 mL)\neq_volume = 25.0\neq_ph = 7.0\n\n# Excess acid region — gradual pH change zone before the steep transition\n# Renamed from \"Buffer Region\": strong acid-base titrations have no true buffer capacity\nea_start, ea_end = 5.0, 20.0\nea_mask = (volume_ml >= ea_start) & (volume_ml <= ea_end)\n\n# Figure\nfig = make_subplots(specs=[[{\"secondary_y\": True}]])\n\n# Excess acid region shading\nea_v = volume_ml[ea_mask]\nea_p = ph[ea_mask]\nfig.add_trace(\n    go.Scatter(\n        x=np.concatenate([ea_v, ea_v[::-1]]),\n        y=np.concatenate([ea_p, np.zeros(len(ea_p))]),\n        fill=\"toself\",\n        fillcolor=\"rgba(68,103,163,0.10)\",\n        line={\"width\": 0},\n        name=\"Excess Acid Region\",\n        hoverinfo=\"skip\",\n    ),\n    secondary_y=False,\n)\n\n# pH curve — Imprint pos 1 (brand green), always first series\nfig.add_trace(\n    go.Scatter(\n        x=volume_ml,\n        y=ph,\n        mode=\"lines\",\n        name=\"pH\",\n        line={\"color\": IMPRINT[0], \"width\": 3.0},\n        hovertemplate=\"Volume: %{x:.1f} mL<br>pH: %{y:.2f}<extra></extra>\",\n    ),\n    secondary_y=False,\n)\n\n# Derivative curve — Imprint pos 2 (lavender), log scale reveals shape throughout\nfig.add_trace(\n    go.Scatter(\n        x=volume_ml,\n        y=dph_dv_log,\n        mode=\"lines\",\n        name=\"dpH/dV\",\n        line={\"color\": IMPRINT[1], \"width\": 2.5, \"dash\": \"dot\"},\n        hovertemplate=\"Volume: %{x:.1f} mL<br>dpH/dV: %{y:.3f} mL⁻¹<extra></extra>\",\n    ),\n    secondary_y=True,\n)\n\n# Equivalence point marker + vertical reference + annotation\nfig.add_vline(x=eq_volume, line_dash=\"dash\", line_color=INK_MUTED, line_width=1.5)\nfig.add_trace(\n    go.Scatter(\n        x=[eq_volume],\n        y=[eq_ph],\n        mode=\"markers\",\n        name=\"Equivalence Point\",\n        marker={\"size\": 14, \"color\": IMPRINT[4], \"symbol\": \"diamond\", \"line\": {\"width\": 2, \"color\": PAGE_BG}},\n        showlegend=False,\n        hovertemplate=\"Equivalence Point<br>%{x:.1f} mL, pH %{y:.1f}<extra></extra>\",\n    ),\n    secondary_y=False,\n)\nfig.add_annotation(\n    x=eq_volume,\n    y=eq_ph,\n    text=f\"Equivalence Point<br>{eq_volume:.1f} mL, pH {eq_ph:.1f}\",\n    showarrow=True,\n    arrowhead=2,\n    arrowwidth=1.5,\n    arrowcolor=INK_SOFT,\n    ax=90,\n    ay=-60,\n    font={\"size\": 10, \"color\": INK, \"family\": \"Arial\"},\n    bgcolor=ELEVATED_BG,\n    bordercolor=INK_SOFT,\n    borderwidth=1,\n    borderpad=6,\n)\n\n# Region label\nfig.add_annotation(\n    x=(ea_start + ea_end) / 2,\n    y=1.8,\n    text=\"Excess Acid Region\",\n    showarrow=False,\n    font={\"size\": 10, \"color\": INK_MUTED, \"family\": \"Arial\"},\n)\n\n# Layout — canvas: width=800 height=450 scale=4 → 3200×1800 px\nfig.update_layout(\n    autosize=False,\n    title={\n        \"text\": \"HCl + NaOH Titration · titration-curve · plotly · anyplot.ai\",\n        \"font\": {\"size\": 16, \"family\": \"Arial\", \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK, \"family\": \"Arial\"},\n    legend={\n        \"font\": {\"size\": 10, \"family\": \"Arial\", \"color\": INK_SOFT},\n        \"x\": 0.02,\n        \"y\": 0.98,\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    margin={\"l\": 80, \"r\": 90, \"t\": 80, \"b\": 60},\n    hovermode=\"x unified\",\n)\n\nfig.update_xaxes(\n    title={\"text\": \"Volume of NaOH added (mL)\", \"font\": {\"size\": 12, \"family\": \"Arial\", \"color\": INK}},\n    tickfont={\"size\": 10, \"family\": \"Arial\", \"color\": INK_SOFT},\n    showgrid=False,\n    showline=True,\n    linewidth=1,\n    linecolor=INK_SOFT,\n    zeroline=False,\n    ticks=\"outside\",\n    ticklen=5,\n    tickcolor=INK_SOFT,\n)\n\nfig.update_yaxes(\n    title={\"text\": \"pH\", \"font\": {\"size\": 12, \"family\": \"Arial\", \"color\": INK}},\n    tickfont={\"size\": 10, \"family\": \"Arial\", \"color\": INK_SOFT},\n    range=[0, 14],\n    dtick=2,\n    showgrid=True,\n    gridwidth=1,\n    gridcolor=GRID,\n    showline=True,\n    linewidth=1,\n    linecolor=INK_SOFT,\n    zeroline=False,\n    ticks=\"outside\",\n    ticklen=5,\n    tickcolor=INK_SOFT,\n    secondary_y=False,\n)\n\nfig.update_yaxes(\n    title={\"text\": \"dpH/dV (mL⁻¹, log)\", \"font\": {\"size\": 12, \"family\": \"Arial\", \"color\": INK}},\n    tickfont={\"size\": 10, \"family\": \"Arial\", \"color\": INK_SOFT},\n    type=\"log\",\n    showgrid=False,\n    showline=True,\n    linewidth=1,\n    linecolor=INK_SOFT,\n    zeroline=False,\n    ticks=\"outside\",\n    ticklen=5,\n    tickcolor=INK_SOFT,\n    secondary_y=True,\n)\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}