{"spec_id":"gain-curve","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// gain-curve: Cumulative Gains Chart\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\nconst N_TRANSACTIONS = 2000;\nconst FRAUD_RATE = 0.05;\n\nfunction mulberry32(seed) {\n  return function () {\n    seed = (seed + 0x6d2b79f5) | 0;\n    let z = Math.imul(seed ^ (seed >>> 15), 1 | seed);\n    z = (z + Math.imul(z ^ (z >>> 7), 61 | z)) ^ z;\n    return ((z ^ (z >>> 14)) >>> 0) / 4294967296;\n  };\n}\nconst rand = mulberry32(42);\n\nfunction randNormal() {\n  let u1 = rand();\n  while (u1 === 0) u1 = rand();\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// A fraud-detection risk score: fraudulent transactions skew higher, but\n// overlap with legitimate ones, mimicking a realistic (imperfect) model.\nconst transactions = [];\nfor (let i = 0; i < N_TRANSACTIONS; i++) {\n  const isFraud = rand() < FRAUD_RATE ? 1 : 0;\n  const riskScore = isFraud ? 0.62 + 0.22 * randNormal() : 0.22 + 0.18 * randNormal();\n  transactions.push({ isFraud, riskScore });\n}\ntransactions.sort((a, b) => b.riskScore - a.riskScore);\n\nconst totalFraud = transactions.reduce((sum, tx) => sum + tx.isFraud, 0);\n\nconst modelGains = [[0, 0]];\nlet capturedFraud = 0;\ntransactions.forEach((tx, i) => {\n  capturedFraud += tx.isFraud;\n  const targetedPct = ((i + 1) / N_TRANSACTIONS) * 100;\n  const capturedPct = (capturedFraud / totalFraud) * 100;\n  modelGains.push([targetedPct, capturedPct]);\n});\n\nconst baselineGains = [\n  [0, 0],\n  [100, 100],\n];\n\n// Highlight the point where the model first captures 95% of fraud cases —\n// the \"diminishing returns\" transition called out via a plotLine dropline.\nconst PLATEAU_CAPTURE_PCT = 95;\nconst [plateauTargetedPct, plateauCapturedPct] =\n  modelGains.find(([, capturedPct]) => capturedPct >= PLATEAU_CAPTURE_PCT) ??\n  modelGains[modelGains.length - 1];\n\n// --- Chart -------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"line\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  title: {\n    text: \"gain-curve · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: \"Fraud investigations ranked by model risk score vs. random selection\",\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    title: {\n      text: \"Transactions Investigated (%)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    min: 0,\n    max: 100,\n    tickInterval: 20,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    labels: { format: \"{value}%\", style: { color: t.inkSoft, fontSize: \"14px\" } },\n    plotLines: [\n      {\n        value: plateauTargetedPct,\n        color: t.inkSoft,\n        width: 1,\n        dashStyle: \"ShortDot\",\n        zIndex: 4,\n        label: {\n          text: `${PLATEAU_CAPTURE_PCT}% captured at ${plateauTargetedPct.toFixed(0)}% targeted`,\n          style: { color: t.inkSoft, fontSize: \"12px\" },\n          rotation: 0,\n          align: \"left\",\n          x: 8,\n          verticalAlign: \"bottom\",\n          y: -10,\n        },\n      },\n    ],\n  },\n  yAxis: {\n    title: {\n      text: \"Fraud Cases Captured (%)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    min: 0,\n    max: 100,\n    tickInterval: 20,\n    gridLineColor: t.grid,\n    labels: { format: \"{value}%\", style: { color: t.inkSoft, fontSize: \"14px\" } },\n    plotLines: [\n      { value: plateauCapturedPct, color: t.inkSoft, width: 1, dashStyle: \"ShortDot\", zIndex: 4 },\n    ],\n  },\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  tooltip: {\n    formatter: function () {\n      const lift = this.x > 0 ? (this.y / this.x).toFixed(2) : \"—\";\n      return `<b>${this.series.name}</b><br/>Targeted: ${this.x.toFixed(1)}%<br/>Captured: ${this.y.toFixed(1)}%<br/>Lift vs. random: ${lift}×`;\n    },\n  },\n  plotOptions: {\n    series: { animation: false, marker: { enabled: false } },\n  },\n  series: [\n    {\n      name: \"Fraud Risk Model\",\n      type: \"area\",\n      data: modelGains,\n      color: t.palette[0],\n      fillOpacity: 0.15,\n      threshold: 0,\n      lineWidth: 3,\n      zIndex: 2,\n    },\n    {\n      name: \"Random Selection\",\n      type: \"line\",\n      data: baselineGains,\n      color: t.ink,\n      lineWidth: 2,\n      dashStyle: \"Dash\",\n      zIndex: 3,\n    },\n  ],\n});\n"}