{"spec_id":"calibration-curve","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// calibration-curve: Calibration Curve\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Loan default risk model: y_true = actual default (0/1), y_prob = predicted\n// default probability from a mildly overconfident classifier. Small LCG for\n// reproducible pseudo-randomness (the browser has no seeded RNG).\nlet seed = 42;\nconst rand = () => {\n  seed = (1103515245 * seed + 12345) % 2147483648;\n  return seed / 2147483648;\n};\n\nconst sampleCount = 3000;\nconst yTrue = [];\nconst yProb = [];\nfor (let i = 0; i < sampleCount; i++) {\n  const trueRisk = rand();\n  const defaulted = rand() < trueRisk ? 1 : 0;\n  // Overconfident model: pushes predictions away from 0.5 toward the extremes.\n  const predictedRisk = Math.min(0.99, Math.max(0.01, 0.5 + (trueRisk - 0.5) * 1.4));\n  yTrue.push(defaulted);\n  yProb.push(predictedRisk);\n}\n\n// Brier score: mean squared error between predicted probability and outcome\nconst brierScore = yProb.reduce((sum, p, i) => sum + (p - yTrue[i]) ** 2, 0) / sampleCount;\n\n// Bin predictions into 10 equal-width intervals, then compute per-bin means\nconst binCount = 10;\nconst bins = Array.from({ length: binCount }, () => ({ probSum: 0, positives: 0, count: 0 }));\nfor (let i = 0; i < sampleCount; i++) {\n  const binIndex = Math.min(binCount - 1, Math.floor(yProb[i] * binCount));\n  bins[binIndex].probSum += yProb[i];\n  bins[binIndex].positives += yTrue[i];\n  bins[binIndex].count += 1;\n}\nconst calibrationPoints = bins\n  .filter((bin) => bin.count > 0)\n  .map((bin) => ({ x: bin.probSum / bin.count, y: bin.positives / bin.count }));\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart -------------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    datasets: [\n      {\n        label: \"Perfect calibration\",\n        data: [\n          { x: 0, y: 0 },\n          { x: 1, y: 1 },\n        ],\n        borderColor: t.ink,\n        borderDash: [8, 6],\n        borderWidth: 2,\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n      },\n      {\n        label: `Loan default model (Brier score: ${brierScore.toFixed(3)})`,\n        data: calibrationPoints,\n        borderColor: t.palette[0],\n        backgroundColor: `${t.palette[0]}33`,\n        borderWidth: 3,\n        pointRadius: 7,\n        pointHoverRadius: 7,\n        pointBackgroundColor: t.palette[0],\n        pointBorderColor: t.pageBg,\n        pointBorderWidth: 1.5,\n        fill: \"-1\",\n        tension: 0,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: 24 },\n    plugins: {\n      title: {\n        display: true,\n        text: \"calibration-curve · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { bottom: 20 },\n      },\n      legend: {\n        position: \"bottom\",\n        labels: { color: t.ink, font: { size: 16 }, boxWidth: 24, padding: 20 },\n      },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        min: 0,\n        max: 1,\n        ticks: { color: t.inkSoft, font: { size: 14 }, stepSize: 0.2 },\n        grid: { display: false },\n        title: { display: true, text: \"Mean Predicted Probability\", color: t.ink, font: { size: 16 } },\n      },\n      y: {\n        type: \"linear\",\n        min: 0,\n        max: 1,\n        ticks: { color: t.inkSoft, font: { size: 14 }, stepSize: 0.2 },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Fraction of Positives\", color: t.ink, font: { size: 16 } },\n      },\n    },\n  },\n});\n"}