{"spec_id":"scatter-regression-polynomial","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// scatter-regression-polynomial: Scatter Plot with Polynomial Regression\n// Library: highcharts 12.6.0 | JavaScript 22.23.1\n// Quality: 90/100 | Created: 2026-08-11\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Small fixed-seed LCG — Math.random() is not reproducible in the browser.\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1664525 + 1013904223) % 4294967296;\n  return seed / 4294967296;\n}\nfunction gaussian() {\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// Crop yield response to fertilizer dose — a textbook diminishing-returns\n// curve: yield climbs steeply at first, then flattens and tips over past the\n// agronomic optimum, so a quadratic captures the pattern a line cannot.\nconst pointCount = 80;\nconst fertilizer = [];\nconst cropYield = [];\nfor (let i = 0; i < pointCount; i++) {\n  const dose = 5 + rand() * 195; // kg/hectare, 5-200\n  const trueYield = 2.1 + 0.085 * dose - 0.00021 * dose * dose;\n  fertilizer.push(dose);\n  cropYield.push(Math.max(0, trueYield + gaussian() * 0.9));\n}\n\n// --- Quadratic least-squares fit (normal equations + Gaussian elimination) -\nfunction polyfit(xs, ys, degree) {\n  const m = degree + 1;\n  const xtx = Array.from({ length: m }, () => new Array(m).fill(0));\n  const xty = new Array(m).fill(0);\n  for (let i = 0; i < xs.length; i++) {\n    const powers = new Array(2 * m - 1);\n    let p = 1;\n    for (let k = 0; k < powers.length; k++) {\n      powers[k] = p;\n      p *= xs[i];\n    }\n    for (let r = 0; r < m; r++) {\n      xty[r] += powers[r] * ys[i];\n      for (let c = 0; c < m; c++) xtx[r][c] += powers[r + c];\n    }\n  }\n  for (let col = 0; col < m; col++) {\n    let pivot = col;\n    for (let r = col + 1; r < m; r++) {\n      if (Math.abs(xtx[r][col]) > Math.abs(xtx[pivot][col])) pivot = r;\n    }\n    [xtx[col], xtx[pivot]] = [xtx[pivot], xtx[col]];\n    [xty[col], xty[pivot]] = [xty[pivot], xty[col]];\n    for (let r = col + 1; r < m; r++) {\n      const factor = xtx[r][col] / xtx[col][col];\n      for (let c = col; c < m; c++) xtx[r][c] -= factor * xtx[col][c];\n      xty[r] -= factor * xty[col];\n    }\n  }\n  const coeffs = new Array(m).fill(0);\n  for (let r = m - 1; r >= 0; r--) {\n    let sum = xty[r];\n    for (let c = r + 1; c < m; c++) sum -= xtx[r][c] * coeffs[c];\n    coeffs[r] = sum / xtx[r][r];\n  }\n  return coeffs; // [c0, c1, c2] for y = c0 + c1*x + c2*x^2\n}\n\nconst [c0, c1, c2] = polyfit(fertilizer, cropYield, 2);\nconst predict = (x) => c0 + c1 * x + c2 * x * x;\n\nconst meanYield = cropYield.reduce((a, b) => a + b, 0) / pointCount;\nlet ssRes = 0;\nlet ssTot = 0;\nfor (let i = 0; i < pointCount; i++) {\n  ssRes += (cropYield[i] - predict(fertilizer[i])) ** 2;\n  ssTot += (cropYield[i] - meanYield) ** 2;\n}\nconst rSquared = 1 - ssRes / ssTot;\n\nconst xMin = Math.min(...fertilizer);\nconst xMax = Math.max(...fertilizer);\n\n// Approximate 90% prediction band around the fit — reuses the standard OLS\n// leverage formula (se widens away from x̄) applied to the quadratic's\n// residual SE. Not the exact leverage of a 3-parameter design matrix, but a\n// close, honest visual approximation: pinches near the data's center of mass,\n// widens at the extremes.\nconst xBar = fertilizer.reduce((a, b) => a + b, 0) / pointCount;\nconst sxx = fertilizer.reduce((acc, x) => acc + (x - xBar) ** 2, 0);\nconst residualStdErr = Math.sqrt(ssRes / (pointCount - 3)); // 3 fitted params\nconst tCrit90 = 1.665; // ~90% two-tail critical value at df=77\n\nconst curveSteps = 100;\nconst curvePoints = [];\nconst bandUpper = [];\nconst bandLower = [];\nfor (let i = 0; i <= curveSteps; i++) {\n  const x = xMin + ((xMax - xMin) * i) / curveSteps;\n  const y = predict(x);\n  curvePoints.push([x, y]);\n  const se = residualStdErr * Math.sqrt(1 / pointCount + ((x - xBar) ** 2) / sxx);\n  bandUpper.push([x, y + tCrit90 * se]);\n  bandLower.push([x, y - tCrit90 * se]);\n}\n\n// Diminishing-returns onset: the dose past which the marginal yield gain has\n// fallen to a quarter of its initial (low-dose) rate — the agronomic point\n// where extra fertilizer stops paying off.\nconst initialSlope = c1 + 2 * c2 * xMin;\nconst thresholdSlope = 0.25 * initialSlope;\nconst plateauStart = Math.min(Math.max((thresholdSlope - c1) / (2 * c2), xMin), xMax);\n\nconst equation = `y = ${c2.toFixed(4)}x² ${c1 >= 0 ? \"+\" : \"−\"} ${Math.abs(c1).toFixed(3)}x ${c0 >= 0 ? \"+\" : \"−\"} ${Math.abs(c0).toFixed(2)}`;\nconst fitSummary = `${equation}    |    R² = ${rSquared.toFixed(3)}`;\n\nfunction hexToRgba(hex, alpha) {\n  const r = parseInt(hex.slice(1, 3), 16);\n  const g = parseInt(hex.slice(3, 5), 16);\n  const b = parseInt(hex.slice(5, 7), 16);\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\n// Imprint \"muted\" semantic anchor — not in ANYPLOT_TOKENS on the JS side, so\n// it's hard-coded per prompts/default-style-guide.md (theme-adaptive).\nconst muted = t.theme === \"dark\" ? \"#A8A79F\" : \"#6B6A63\";\nconst bandFill = Highcharts.color(muted).setOpacity(0.16).get();\n\n// --- Chart -------------------------------------------------------------------\n// The 90% prediction band is drawn as a plain SVG path in the core renderer,\n// redrawn on every chart render — arearange lives in highcharts-more, which\n// isn't vendored here.\nlet bandPath;\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"scatter\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    events: {\n      render: function () {\n        const xAxis = this.xAxis[0];\n        const yAxis = this.yAxis[0];\n        const upper = bandUpper.map(\n          (p, i) => `${i === 0 ? \"M\" : \"L\"} ${xAxis.toPixels(p[0], false)} ${yAxis.toPixels(p[1], false)}`,\n        );\n        const lower = bandLower\n          .slice()\n          .reverse()\n          .map((p) => `L ${xAxis.toPixels(p[0], false)} ${yAxis.toPixels(p[1], false)}`);\n        const d = `${upper.join(\" \")} ${lower.join(\" \")} Z`;\n        if (bandPath) {\n          bandPath.attr({ d });\n        } else {\n          bandPath = this.renderer.path().attr({ d, fill: bandFill, zIndex: 2 }).add();\n        }\n      },\n    },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"scatter-regression-polynomial · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: fitSummary,\n    style: { color: t.inkSoft, fontSize: \"16px\" },\n  },\n  xAxis: {\n    title: {\n      text: \"Fertilizer Applied (kg/hectare)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    gridLineWidth: 1,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    plotBands: [\n      {\n        from: plateauStart,\n        to: xMax,\n        color: hexToRgba(muted, 0.1),\n        label: {\n          text: \"Diminishing returns\",\n          verticalAlign: \"top\",\n          align: \"center\",\n          y: 16,\n          style: { color: t.inkSoft, fontSize: \"12px\", fontStyle: \"italic\" },\n        },\n      },\n    ],\n  },\n  yAxis: {\n    title: {\n      text: \"Crop Yield (tons/hectare)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\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  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  plotOptions: {\n    series: { animation: false },\n  },\n  series: [\n    {\n      type: \"scatter\",\n      name: \"Field Trials\",\n      data: fertilizer.map((x, i) => [x, cropYield[i]]),\n      color: hexToRgba(t.palette[0], 0.65),\n      zIndex: 5,\n      marker: { radius: 5.5, lineWidth: 0.5, lineColor: t.pageBg },\n    },\n    {\n      type: \"spline\",\n      name: \"Quadratic Fit\",\n      data: curvePoints,\n      color: t.palette[1],\n      lineWidth: 3,\n      zIndex: 4,\n      marker: { enabled: false },\n      enableMouseTracking: false,\n    },\n    {\n      type: \"column\",\n      name: \"90% Prediction Band\",\n      data: [],\n      color: bandFill,\n      legendSymbol: \"rectangle\",\n      showInLegend: true,\n      enableMouseTracking: false,\n    },\n  ],\n});\n"}