{"spec_id":"logistic-regression","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// logistic-regression: Logistic Regression Curve Plot\n// Library: highcharts 12.6.0 | 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// Marketing conversion: probability of converting as a function of an\n// engagement score (0-100). A small LCG stands in for a seeded RNG (the\n// browser has no seedable Math.random).\nfunction lcg(seed) {\n  let state = seed >>> 0;\n  return () => {\n    state = (Math.imul(state, 1664525) + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst rand = lcg(42);\n\nconst midpoint = 52; // engagement score at 50% conversion probability\nconst steepness = 0.11;\nconst sigmoid = (x) => 1 / (1 + Math.exp(-steepness * (x - midpoint)));\nconst clamp01 = (v) => Math.min(1, Math.max(0, v));\n\n// Fitted probability curve, sampled across the full engagement range.\nconst curveX = [];\nfor (let x = 0; x <= 100; x += 1) curveX.push(x);\nconst curveY = curveX.map(sigmoid);\n\n// Approximate 95% confidence band — narrowest near the midpoint (where\n// observations are densest), widening toward the extremes.\nconst seMin = 0.025;\nconst seScale = 0.11;\nconst standardError = (x) => seMin + seScale * Math.pow(Math.abs(x - midpoint) / 50, 1.4);\nconst ciLower = curveX.map((x) => [x, clamp01(sigmoid(x) - 1.96 * standardError(x))]);\nconst ciWidth = curveX.map((x, i) => [x, clamp01(sigmoid(x) + 1.96 * standardError(x)) - ciLower[i][1]]);\n\n// Observed binary outcomes, jittered on the y-axis so points near 0/1 don't\n// stack exactly on top of each other.\nconst notConverted = [];\nconst converted = [];\nconst pointCount = 180;\nfor (let i = 0; i < pointCount; i += 1) {\n  const x = rand() * 100;\n  const p = sigmoid(x);\n  const outcome = rand() < p ? 1 : 0;\n  const jitter = (rand() - 0.5) * 0.09;\n  const point = [x, outcome + jitter];\n  (outcome === 1 ? converted : notConverted).push(point);\n}\n\n// --- Chart -------------------------------------------------------------------\nconst classColor0 = t.palette[0]; // brand green — always the first series\nconst classColor1 = t.palette[1];\nconst curveColor = t.ink;\nconst bandColor = Highcharts.color(curveColor).setOpacity(0.16).get(\"rgba\");\nconst markerFill0 = Highcharts.color(classColor0).setOpacity(0.6).get(\"rgba\");\nconst markerFill1 = Highcharts.color(classColor1).setOpacity(0.6).get(\"rgba\");\n\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"scatter\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  title: {\n    text: \"logistic-regression · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    title: { text: \"Customer Engagement Score\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    min: 0,\n    max: 100,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    gridLineWidth: 1,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    title: { text: \"Probability\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    min: -0.07,\n    max: 1.07,\n    tickPositions: [0, 0.25, 0.5, 0.75, 1],\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    plotLines: [\n      {\n        value: 0.5,\n        color: t.amber,\n        width: 2,\n        dashStyle: \"Dash\",\n        zIndex: 4,\n        label: {\n          text: \"Decision threshold (p = 0.5)\",\n          align: \"right\",\n          x: -8,\n          y: -6,\n          style: { color: t.inkSoft, fontSize: \"14px\" },\n        },\n      },\n    ],\n  },\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  plotOptions: {\n    series: { animation: false, enableMouseTracking: true },\n    scatter: { marker: { radius: 4.5, lineWidth: 0 } },\n  },\n  series: [\n    {\n      name: \"ci-lower\",\n      type: \"area\",\n      data: ciLower,\n      color: \"transparent\",\n      fillOpacity: 0,\n      lineWidth: 0,\n      marker: { enabled: false },\n      enableMouseTracking: false,\n      showInLegend: false,\n      stacking: \"normal\",\n      stack: \"ci\",\n    },\n    {\n      name: \"95% confidence interval\",\n      type: \"area\",\n      data: ciWidth,\n      color: bandColor,\n      fillOpacity: 1,\n      lineWidth: 0,\n      marker: { enabled: false },\n      enableMouseTracking: false,\n      showInLegend: true,\n      stacking: \"normal\",\n      stack: \"ci\",\n    },\n    {\n      name: \"Did not convert (0)\",\n      type: \"scatter\",\n      data: notConverted,\n      color: classColor0,\n      marker: { fillColor: markerFill0 },\n    },\n    {\n      name: \"Converted (1)\",\n      type: \"scatter\",\n      data: converted,\n      color: classColor1,\n      marker: { fillColor: markerFill1, lineColor: t.ink, lineWidth: 1 },\n    },\n    {\n      name: \"Fitted probability\",\n      type: \"line\",\n      data: curveX.map((x, i) => [x, curveY[i]]),\n      color: curveColor,\n      lineWidth: 2.5,\n      marker: { enabled: false },\n      enableMouseTracking: false,\n    },\n  ],\n});\n"}