{"spec_id":"titration-curve","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// titration-curve: Acid-Base Titration Curve\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 89/100 | Created: 2026-06-24\n\nconst t = window.ANYPLOT_TOKENS;\n\n// 25 mL of 0.1 M HCl titrated with 0.1 M NaOH (strong acid / strong base)\nconst Va = 25;   // initial HCl volume (mL)\nconst Ca = 0.1;  // HCl concentration (mol/L)\nconst Cb = 0.1;  // NaOH concentration (mol/L)\n\nconst volumes = Array.from({ length: 101 }, (_, i) => i * 0.5);\n\nconst pHValues = volumes.map(v => {\n    const molesAcid = (Ca * Va) / 1000;\n    const molesBase = (Cb * v) / 1000;\n    const totalVol = (Va + v) / 1000;\n    const netAcid = molesAcid - molesBase;\n    let ph;\n    if (Math.abs(netAcid) < 1e-8) {\n        ph = 7.0;\n    } else if (netAcid > 0) {\n        ph = -Math.log10(netAcid / totalVol);\n    } else {\n        ph = 14 + Math.log10((-netAcid) / totalVol);\n    }\n    return Math.max(0, Math.min(14, ph));\n});\n\n// Central-difference derivative dpH/dV\nconst STEP = 0.5;\nconst derivatives = volumes.map((v, i) => {\n    if (i === 0) return (pHValues[1] - pHValues[0]) / STEP;\n    if (i === volumes.length - 1) return (pHValues[i] - pHValues[i - 1]) / STEP;\n    return (pHValues[i + 1] - pHValues[i - 1]) / (2 * STEP);\n});\n\n// Equivalence point: volume at peak of derivative\nlet equivIdx = 0;\nderivatives.forEach((d, i) => { if (d > derivatives[equivIdx]) equivIdx = i; });\nconst equivVol = volumes[equivIdx];\nconst equivPH = pHValues[equivIdx];\n\n// Transition zone ±3 mL around equivalence point (steep pH-change region)\nconst shadeStart = equivVol - 3;\nconst shadeEnd = equivVol + 3;\n\n// Inline Chart.js plugin for dashed equivalence line and zone shading\nconst annotationPlugin = {\n    id: 'titrationAnnotations',\n    beforeDraw(chart) {\n        const { ctx, scales: { x, y } } = chart;\n        const x1 = x.getPixelForValue(shadeStart);\n        const x2 = x.getPixelForValue(shadeEnd);\n        ctx.save();\n        ctx.fillStyle = 'rgba(0, 158, 115, 0.09)';\n        ctx.fillRect(x1, y.top, x2 - x1, y.bottom - y.top);\n        ctx.restore();\n    },\n    afterDraw(chart) {\n        const { ctx, scales: { x, y } } = chart;\n        const xPixel = x.getPixelForValue(equivVol);\n\n        // Vertical dashed line at equivalence point\n        ctx.save();\n        ctx.setLineDash([8, 5]);\n        ctx.strokeStyle = t.inkSoft;\n        ctx.lineWidth = 1.5;\n        ctx.beginPath();\n        ctx.moveTo(xPixel, y.top);\n        ctx.lineTo(xPixel, y.bottom);\n        ctx.stroke();\n\n        // Equivalence point label\n        ctx.setLineDash([]);\n        ctx.fillStyle = t.ink;\n        ctx.font = 'bold 16px system-ui, sans-serif';\n        ctx.textAlign = 'left';\n        ctx.textBaseline = 'middle';\n        ctx.fillText(\n            `Equiv. point: ${equivVol} mL, pH ${equivPH.toFixed(1)}`,\n            xPixel + 10,\n            y.getPixelForValue(9.5)\n        );\n        ctx.restore();\n    },\n};\n\n// Mount canvas into the harness container\nconst canvas = document.createElement('canvas');\ndocument.getElementById('container').appendChild(canvas);\n\nnew Chart(canvas, {\n    type: 'line',\n    data: {\n        datasets: [\n            {\n                label: 'pH',\n                data: volumes.map((v, i) => ({ x: v, y: pHValues[i] })),\n                borderColor: t.palette[0],\n                backgroundColor: 'transparent',\n                borderWidth: 3,\n                pointRadius: 0,\n                tension: 0.2,\n                yAxisID: 'y',\n            },\n            {\n                label: 'dpH/dV (pH/mL)',\n                data: volumes.map((v, i) => ({ x: v, y: derivatives[i] })),\n                borderColor: t.palette[1],\n                backgroundColor: 'transparent',\n                borderWidth: 2,\n                borderDash: [6, 3],\n                pointRadius: 0,\n                tension: 0.2,\n                yAxisID: 'y2',\n            },\n        ],\n    },\n    options: {\n        responsive: true,\n        maintainAspectRatio: false,\n        animation: false,\n        plugins: {\n            title: {\n                display: true,\n                text: 'titration-curve · javascript · chartjs · anyplot.ai',\n                color: t.ink,\n                font: { size: 22 },\n                padding: { bottom: 16 },\n            },\n            legend: {\n                labels: {\n                    color: t.ink,\n                    font: { size: 16 },\n                    boxWidth: 24,\n                    padding: 16,\n                },\n            },\n        },\n        scales: {\n            x: {\n                type: 'linear',\n                title: {\n                    display: true,\n                    text: 'Volume of NaOH added (mL)',\n                    color: t.ink,\n                    font: { size: 14 },\n                },\n                ticks: { color: t.inkSoft, font: { size: 14 } },\n                grid: { color: t.grid },\n                border: { display: false },\n                min: 0,\n                max: 50,\n            },\n            y: {\n                title: {\n                    display: true,\n                    text: 'pH',\n                    color: t.ink,\n                    font: { size: 14 },\n                },\n                ticks: { color: t.inkSoft, font: { size: 14 } },\n                grid: { color: t.grid },\n                border: { display: false },\n                min: 0,\n                max: 14,\n            },\n            y2: {\n                type: 'linear',\n                position: 'right',\n                title: {\n                    display: true,\n                    text: 'dpH/dV (pH/mL)',\n                    color: t.palette[1],\n                    font: { size: 14 },\n                },\n                ticks: { color: t.inkSoft, font: { size: 14 } },\n                grid: { drawOnChartArea: false },\n                border: { display: false },\n                min: 0,\n                max: 10,\n            },\n        },\n    },\n    plugins: [annotationPlugin],\n});\n"}