{"spec_id":"lift-curve","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// lift-curve: Model Lift Chart\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-05\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: simulate a retention-campaign response model on 2000 customers --\n// Deterministic PRNG (mulberry32) — the browser has no seeded Math.random.\nfunction mulberry32(seed) {\n  return function () {\n    seed = (seed + 0x6d2b79f5) | 0;\n    let x = Math.imul(seed ^ (seed >>> 15), 1 | seed);\n    x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;\n    return ((x ^ (x >>> 14)) >>> 0) / 4294967296;\n  };\n}\nconst rand = mulberry32(42);\n\nconst CUSTOMER_COUNT = 2000;\nlet totalResponders = 0;\nconst customers = [];\nfor (let i = 0; i < CUSTOMER_COUNT; i++) {\n  // Most customers have low churn-retention-offer propensity; a few are highly likely to respond.\n  const propensity = Math.pow(rand(), 4);\n  const trueResponseProb = Math.min(0.9, propensity);\n  const responded = rand() < trueResponseProb ? 1 : 0;\n  totalResponders += responded;\n  // The model score correlates with true propensity but is imperfect (added noise).\n  const score = propensity + (rand() - 0.5) * 0.3;\n  customers.push({ responded, score });\n}\ncustomers.sort((a, b) => b.score - a.score);\n\nconst baselineRate = totalResponders / CUSTOMER_COUNT;\n\nconst liftData = [];\nlet cumResponders = 0;\nlet cumCustomers = 0;\nfor (let pct = 1; pct <= 100; pct++) {\n  const cutoff = Math.round((pct / 100) * CUSTOMER_COUNT);\n  while (cumCustomers < cutoff) {\n    cumResponders += customers[cumCustomers].responded;\n    cumCustomers++;\n  }\n  const lift = cumResponders / cumCustomers / baselineRate;\n  const isDecile = pct % 10 === 0;\n  liftData.push({\n    x: pct,\n    y: Number(lift.toFixed(3)),\n    marker: { enabled: isDecile, radius: isDecile ? 6 : 0 },\n    dataLabels: { enabled: isDecile },\n    custom: { responders: cumResponders, customers: cumCustomers },\n  });\n}\n\nconst randomSelectionData = [\n  { x: 0, y: 1 },\n  { x: 100, y: 1 },\n];\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  colors: t.palette,\n  title: {\n    text: \"lift-curve · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"24px\", fontWeight: \"700\" },\n  },\n  subtitle: {\n    text: `Retention offer targeting · baseline response rate ${(baselineRate * 100).toFixed(1)}%`,\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    title: { text: \"Population Targeted (%)\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    min: 0,\n    max: 100,\n    tickInterval: 10,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" }, format: \"{value}%\" },\n  },\n  yAxis: {\n    title: { text: \"Cumulative Lift\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    min: 0,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" }, format: \"{value}x\" },\n  },\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  plotOptions: {\n    series: { animation: false },\n    line: { lineWidth: 3, marker: { enabled: false } },\n    area: { lineWidth: 3, marker: { enabled: false } },\n  },\n  series: [\n    {\n      name: \"Model\",\n      type: \"area\",\n      data: liftData,\n      color: t.palette[0],\n      // Fill only the wedge between the curve and the y=1 baseline, not down to 0,\n      // to visually emphasize the lift magnitude being captured.\n      threshold: 1,\n      fillOpacity: 0.18,\n      fillColor: Highcharts.color(t.palette[0]).setOpacity(0.18).get(\"rgba\"),\n      zIndex: 2,\n      dataLabels: {\n        enabled: false,\n        format: \"{y:.1f}x\",\n        style: { color: t.ink, fontSize: \"14px\", fontWeight: \"600\", textOutline: \"none\" },\n        y: -14,\n      },\n      tooltip: {\n        pointFormatter: function () {\n          const c = this.custom || {};\n          return (\n            `<span style=\"color:${this.color}\">●</span> ${this.series.name}: <b>${this.y}x</b> lift<br/>` +\n            `Captured ${c.responders} of ${totalResponders} responders (${c.customers} customers targeted)<br/>`\n          );\n        },\n      },\n    },\n    {\n      name: \"Random selection (no lift)\",\n      data: randomSelectionData,\n      color: t.ink,\n      dashStyle: \"Dash\",\n      lineWidth: 2,\n      marker: { enabled: false },\n      enableMouseTracking: false,\n      zIndex: 1,\n    },\n  ],\n});\n"}