{"spec_id":"calibration-beer-lambert","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// calibration-beer-lambert: Beer-Lambert Calibration Curve\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 91/100 | Created: 2026-06-03\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data -------------------------------------------------------------------\nconst concentrations = [0, 2, 4, 6, 8, 10, 12];\nconst absorbances    = [0.003, 0.103, 0.196, 0.301, 0.397, 0.499, 0.601];\n\nconst n     = concentrations.length;\nconst sumX  = concentrations.reduce((a, b) => a + b, 0);\nconst sumY  = absorbances.reduce((a, b) => a + b, 0);\nconst xMean = sumX / n;\nconst yMean = sumY / n;\nconst sumXY = concentrations.reduce((acc, x, i) => acc + x * absorbances[i], 0);\nconst sumX2 = concentrations.reduce((acc, x) => acc + x * x, 0);\nconst ssXX  = sumX2 - n * xMean * xMean;\n\nconst slope     = (sumXY - n * xMean * yMean) / ssXX;\nconst intercept = yMean - slope * xMean;\nconst ssRes     = absorbances.reduce((acc, y, i) => acc + (y - (slope * concentrations[i] + intercept)) ** 2, 0);\nconst ssTot     = absorbances.reduce((acc, y) => acc + (y - yMean) ** 2, 0);\nconst r2        = 1 - ssRes / ssTot;\nconst se        = Math.sqrt(ssRes / (n - 2));\n\n// Dense grid for smooth regression + PI curves\nconst gridX    = Array.from({ length: 60 }, (_, i) => -0.5 + i * 13.5 / 59);\nconst gridY    = gridX.map(x => slope * x + intercept);\nconst tCrit    = 2.571;  // t_{0.025, df=5} for 95% prediction interval\nconst piMargin = gridX.map(x =>\n  tCrit * se * Math.sqrt(1 + 1 / n + (x - xMean) ** 2 / ssXX)\n);\n\n// Unknown sample: measured absorbance → determined concentration\nconst unknownAbs  = 0.350;\nconst unknownConc = (unknownAbs - intercept) / slope;\n\n// Annotation strings\nconst intSign = intercept >= 0 ? '+' : '−';\nconst eqLine  = `A = ${slope.toFixed(4)}c ${intSign} ${Math.abs(intercept).toFixed(4)}`;\nconst r2Line  = `R² = ${r2.toFixed(5)}`;\n\n// --- Mount ------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Plugins ----------------------------------------------------------------\nconst bgPlugin = {\n  id: 'bg',\n  beforeDraw(chart) {\n    const { ctx, width, height } = chart;\n    ctx.save();\n    ctx.fillStyle = t.pageBg;\n    ctx.fillRect(0, 0, width, height);\n    ctx.restore();\n  }\n};\n\nconst annotPlugin = {\n  id: 'annot',\n  afterDraw(chart) {\n    const { ctx, chartArea: { left, top, bottom }, scales } = chart;\n    ctx.save();\n\n    // Regression equation (top-left of plot area)\n    ctx.fillStyle = t.ink;\n    ctx.textAlign = 'left';\n    ctx.font = `bold 16px -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif`;\n    ctx.fillText(eqLine, left + 20, top + 34);\n    ctx.font = `16px -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif`;\n    ctx.fillText(r2Line, left + 20, top + 56);\n\n    // Axis readouts for the unknown sample\n    const xPx = scales.x.getPixelForValue(unknownConc);\n    const yPx = scales.y.getPixelForValue(unknownAbs);\n    ctx.fillStyle = t.palette[4];\n    ctx.font = `bold 15px -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif`;\n    ctx.textAlign = 'left';\n    ctx.fillText(`A = ${unknownAbs.toFixed(3)}`, left + 6, yPx - 8);\n    ctx.textAlign = 'center';\n    ctx.fillText(`c = ${unknownConc.toFixed(1)} mg/L`, xPx, bottom - 28);\n\n    ctx.restore();\n  }\n};\n\n// --- Chart ------------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    datasets: [\n      {\n        label: \"95% Prediction Band\",\n        data: gridX.map((x, i) => ({ x, y: gridY[i] + piMargin[i] })),\n        borderColor: t.palette[0] + \"66\",\n        backgroundColor: t.palette[0] + \"33\",\n        fill: \"+1\",\n        pointRadius: 0,\n        tension: 0,\n        borderWidth: 1,\n        showLine: true,\n        order: 6,\n      },\n      {\n        label: \"_pi_lower\",\n        data: gridX.map((x, i) => ({ x, y: gridY[i] - piMargin[i] })),\n        borderColor: t.palette[0] + \"66\",\n        backgroundColor: \"transparent\",\n        fill: false,\n        pointRadius: 0,\n        tension: 0,\n        borderWidth: 1,\n        showLine: true,\n        order: 6,\n      },\n      {\n        label: \"Linear Fit\",\n        data: gridX.map((x, i) => ({ x, y: gridY[i] })),\n        borderColor: t.palette[0],\n        backgroundColor: \"transparent\",\n        fill: false,\n        pointRadius: 0,\n        tension: 0,\n        borderWidth: 3,\n        showLine: true,\n        order: 4,\n      },\n      {\n        label: \"Calibration Standards\",\n        data: concentrations.map((x, i) => ({ x, y: absorbances[i] })),\n        backgroundColor: t.palette[0],\n        borderColor: t.pageBg,\n        borderWidth: 2,\n        pointRadius: 10,\n        showLine: false,\n        order: 2,\n      },\n      {\n        label: \"Unknown Sample\",\n        data: [{ x: unknownConc, y: unknownAbs }],\n        backgroundColor: t.palette[4],\n        borderColor: t.pageBg,\n        borderWidth: 2,\n        pointRadius: 13,\n        pointStyle: \"triangle\",\n        showLine: false,\n        order: 1,\n      },\n      {\n        label: \"_h_dash\",\n        data: [{ x: -0.5, y: unknownAbs }, { x: unknownConc, y: unknownAbs }],\n        borderColor: t.palette[4],\n        borderDash: [8, 5],\n        borderWidth: 2,\n        pointRadius: 0,\n        fill: false,\n        showLine: true,\n        order: 3,\n      },\n      {\n        label: \"_v_dash\",\n        data: [{ x: unknownConc, y: -0.02 }, { x: unknownConc, y: unknownAbs }],\n        borderColor: t.palette[4],\n        borderDash: [8, 5],\n        borderWidth: 2,\n        pointRadius: 0,\n        fill: false,\n        showLine: true,\n        order: 3,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"calibration-beer-lambert · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { top: 20, bottom: 16 },\n      },\n      legend: {\n        position: \"top\",\n        labels: {\n          color: t.ink,\n          font: { size: 16 },\n          filter: item => !item.text.startsWith(\"_\"),\n          usePointStyle: true,\n          padding: 24,\n        },\n      },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        min: -0.5,\n        max: 13,\n        title: {\n          display: true,\n          text: \"Concentration (mg/L)\",\n          color: t.ink,\n          font: { size: 18 },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          stepSize: 2,\n        },\n        grid: { color: t.grid },\n        border: { display: false },\n      },\n      y: {\n        min: -0.02,\n        max: 0.68,\n        title: {\n          display: true,\n          text: \"Absorbance\",\n          color: t.ink,\n          font: { size: 18 },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          stepSize: 0.1,\n        },\n        grid: { color: t.grid },\n        border: { display: false },\n      },\n    },\n  },\n  plugins: [bgPlugin, annotPlugin],\n});\n"}