{"spec_id":"roc-curve","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// roc-curve: ROC Curve with AUC\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 86/100 | Created: 2026-09-05\n\n//# anyplot-orientation: square\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Reproducible PRNG (LCG) + Box-Muller normal sampler --------------------\nfunction makeLcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\nfunction sampleNormal(rand) {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// --- Data: synthetic classifier scores for a tumor-malignancy screen -------\n// 220 benign cases, 220 malignant cases; two candidate classifiers scored on\n// the same cohort with different separability between the score distributions.\nconst N_PER_CLASS = 220;\nconst rand = makeLcg(42);\nconst labels = [];\nconst deepModelScores = [];\nconst baselineModelScores = [];\nfor (let i = 0; i < N_PER_CLASS; i++) {\n  labels.push(0);\n  deepModelScores.push(sampleNormal(rand) * 1.0 + 0.0);\n  baselineModelScores.push(sampleNormal(rand) * 1.0 + 0.0);\n}\nfor (let i = 0; i < N_PER_CLASS; i++) {\n  labels.push(1);\n  deepModelScores.push(sampleNormal(rand) * 1.0 + 2.1);\n  baselineModelScores.push(sampleNormal(rand) * 1.0 + 1.0);\n}\n\n// --- ROC curve + AUC (trapezoidal rule) -------------------------------------\nfunction computeRoc(scores, classLabels) {\n  const positives = classLabels.reduce((sum, l) => sum + l, 0);\n  const negatives = classLabels.length - positives;\n  const order = scores\n    .map((score, i) => i)\n    .sort((a, b) => scores[b] - scores[a]);\n\n  const points = [{ x: 0, y: 0 }];\n  let truePositives = 0;\n  let falsePositives = 0;\n  for (const i of order) {\n    if (classLabels[i] === 1) truePositives++;\n    else falsePositives++;\n    points.push({ x: falsePositives / negatives, y: truePositives / positives });\n  }\n\n  let auc = 0;\n  for (let i = 1; i < points.length; i++) {\n    const dx = points[i].x - points[i - 1].x;\n    auc += (dx * (points[i].y + points[i - 1].y)) / 2;\n  }\n  return { points, auc };\n}\n\nconst deepRoc = computeRoc(deepModelScores, labels);\nconst baselineRoc = computeRoc(baselineModelScores, labels);\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: `Deep Ensemble (AUC = ${deepRoc.auc.toFixed(2)})`,\n        data: deepRoc.points,\n        borderColor: t.palette[0],\n        backgroundColor: t.palette[0],\n        borderWidth: 4,\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n      },\n      {\n        label: `Logistic Baseline (AUC = ${baselineRoc.auc.toFixed(2)})`,\n        data: baselineRoc.points,\n        borderColor: t.palette[1],\n        backgroundColor: t.palette[1],\n        borderWidth: 3,\n        borderDash: [10, 6],\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n      },\n      {\n        label: \"Random Classifier\",\n        data: [\n          { x: 0, y: 0 },\n          { x: 1, y: 1 },\n        ],\n        borderColor: t.inkSoft,\n        backgroundColor: t.inkSoft,\n        borderWidth: 2,\n        borderDash: [4, 4],\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    aspectRatio: 1,\n    plugins: {\n      title: {\n        display: true,\n        text: \"roc-curve · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 26 },\n        padding: { bottom: 24 },\n      },\n      legend: {\n        position: \"bottom\",\n        labels: { color: t.ink, font: { size: 18 }, boxWidth: 28, padding: 20 },\n      },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        min: 0,\n        max: 1,\n        ticks: { color: t.inkSoft, font: { size: 15 }, stepSize: 0.2 },\n        grid: { color: t.grid },\n        title: { display: true, text: \"False Positive Rate\", color: t.ink, font: { size: 18 } },\n      },\n      y: {\n        type: \"linear\",\n        min: 0,\n        max: 1,\n        ticks: { color: t.inkSoft, font: { size: 15 }, stepSize: 0.2 },\n        grid: { color: t.grid },\n        title: { display: true, text: \"True Positive Rate\", color: t.ink, font: { size: 18 } },\n      },\n    },\n  },\n});\n"}