{"spec_id":"roc-curve","library":"echarts","language":"javascript","code":"// anyplot.ai\n// roc-curve: ROC Curve with AUC\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 87/100 | Created: 2026-09-05\n\n//# anyplot-orientation: square\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Two binary classifiers scored on the same 500-sample holdout set (disease\n// diagnosis: positive = disease present). Model scores are simulated via a\n// tiny fixed-seed LCG so the ROC staircase is reproducible across renders.\nfunction makeLcg(seed) {\n  let state = seed >>> 0;\n  return function random() {\n    state = (state * 1664525 + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\n\nfunction sampleScores(seed, mean, stdDev, count) {\n  const random = makeLcg(seed);\n  return Array.from({ length: count }, () => {\n    const u1 = Math.max(random(), 1e-9);\n    const u2 = random();\n    const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n    return mean + z * stdDev;\n  });\n}\n\nfunction rocCurve(negScores, posScores) {\n  const P = posScores.length;\n  const N = negScores.length;\n  const thresholds = Array.from(new Set([...negScores, ...posScores])).sort(\n    (a, b) => b - a,\n  );\n\n  const points = [[0, 0]];\n  for (const threshold of thresholds) {\n    const tp = posScores.filter((s) => s >= threshold).length;\n    const fp = negScores.filter((s) => s >= threshold).length;\n    points.push([fp / N, tp / P]);\n  }\n  points.push([1, 1]);\n  return points.sort((a, b) => a[0] - b[0]);\n}\n\nfunction auc(points) {\n  let area = 0;\n  for (let i = 1; i < points.length; i++) {\n    const [x0, y0] = points[i - 1];\n    const [x1, y1] = points[i];\n    area += ((x1 - x0) * (y0 + y1)) / 2;\n  }\n  return area;\n}\n\nconst SAMPLE_SIZE = 250;\nconst negScoresGbm = sampleScores(1001, 0, 1, SAMPLE_SIZE);\nconst posScoresGbm = sampleScores(2002, 2.4, 1.05, SAMPLE_SIZE);\nconst negScoresLogReg = sampleScores(3003, 0, 1, SAMPLE_SIZE);\nconst posScoresLogReg = sampleScores(4004, 1.15, 1.2, SAMPLE_SIZE);\n\nconst gbmPoints = rocCurve(negScoresGbm, posScoresGbm);\nconst logRegPoints = rocCurve(negScoresLogReg, posScoresLogReg);\nconst gbmAuc = auc(gbmPoints);\nconst logRegAuc = auc(logRegPoints);\n\nconst gbmName = `Gradient Boosting (AUC = ${gbmAuc.toFixed(2)})`;\nconst logRegName = `Logistic Regression (AUC = ${logRegAuc.toFixed(2)})`;\nconst randomName = \"Random classifier (AUC = 0.50)\";\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option --------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"roc-curve · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 24,\n    textStyle: { color: t.ink, fontSize: 22 },\n  },\n  legend: {\n    top: 84,\n    left: \"center\",\n    itemWidth: 24,\n    itemHeight: 3,\n    textStyle: { color: t.inkSoft, fontSize: 15 },\n  },\n  tooltip: {\n    trigger: \"axis\",\n    axisPointer: { type: \"cross\" },\n  },\n  grid: {\n    left: 150,\n    top: 150,\n    width: 900,\n    height: 900,\n  },\n  xAxis: {\n    type: \"value\",\n    name: \"False Positive Rate\",\n    nameLocation: \"middle\",\n    nameGap: 40,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    min: 0,\n    max: 1,\n    interval: 0.2,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { show: false },\n    axisTick: { show: false },\n    splitLine: { show: true, lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"True Positive Rate\",\n    nameLocation: \"middle\",\n    nameGap: 55,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    min: 0,\n    max: 1,\n    interval: 0.2,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { show: false },\n    axisTick: { show: false },\n    splitLine: { show: true, lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      name: randomName,\n      type: \"line\",\n      data: [\n        [0, 0],\n        [1, 1],\n      ],\n      showSymbol: false,\n      lineStyle: { color: t.inkSoft, width: 2, type: \"dashed\" },\n      itemStyle: { color: t.inkSoft },\n      emphasis: { disabled: true },\n      z: 1,\n    },\n    {\n      name: gbmName,\n      type: \"line\",\n      step: \"end\",\n      data: gbmPoints,\n      showSymbol: false,\n      lineStyle: { color: t.palette[0], width: 3.5 },\n      itemStyle: { color: t.palette[0] },\n      areaStyle: { color: t.palette[0], opacity: 0.12 },\n      markArea: {\n        silent: true,\n        label: { show: false },\n        itemStyle: { color: t.palette[0], opacity: 0.07 },\n        data: [[{ coord: [0, 1] }, { coord: [0.22, 0.82] }]],\n      },\n      z: 3,\n    },\n    {\n      name: logRegName,\n      type: \"line\",\n      step: \"end\",\n      data: logRegPoints,\n      showSymbol: false,\n      lineStyle: { color: t.palette[1], width: 3.5 },\n      itemStyle: { color: t.palette[1] },\n      z: 2,\n    },\n  ],\n});\n"}