{"spec_id":"residual-plot","library":"echarts","language":"javascript","code":"// anyplot.ai\n// residual-plot: Residual Plot\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 84/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\nlet seed = 42;\nconst lcg = () => {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n};\nconst gaussian = () => {\n  const u1 = Math.max(lcg(), 1e-9);\n  const u2 = lcg();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n};\n\nconst n = 300;\nconst fitted = [];\nconst residuals = [];\nfor (let i = 0; i < n; i++) {\n  const predictedMinutes = 15 + lcg() * 45; // predicted delivery time, 15-60 min\n  const noiseScale = 1.2 + 0.06 * predictedMinutes; // mild heteroscedasticity\n  fitted.push(predictedMinutes);\n  residuals.push(gaussian() * noiseScale);\n}\n\nconst mean = residuals.reduce((a, b) => a + b, 0) / n;\nconst variance = residuals.reduce((a, b) => a + (b - mean) ** 2, 0) / n;\nconst std = Math.sqrt(variance);\nconst outlierThreshold = 2.5 * std;\n\nconst normalPoints = [];\nconst outlierPoints = [];\nfitted.forEach((value, i) => {\n  const point = [value, residuals[i]];\n  if (Math.abs(residuals[i]) > outlierThreshold) {\n    outlierPoints.push(point);\n  } else {\n    normalPoints.push(point);\n  }\n});\n\nconst xMin = Math.floor((Math.min(...fitted) - 2) / 5) * 5;\nconst xMax = Math.ceil((Math.max(...fitted) + 2) / 5) * 5;\nconst bandLow = -2 * std;\nconst bandHigh = 2 * std;\n\n// --- Init -------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option -----------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: [t.palette[0], t.palette[4]],\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"residual-plot · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 20,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  legend: {\n    data: [\"Normal\", \"Outlier (|z| > 2.5σ)\"],\n    top: 72,\n    textStyle: { color: t.ink, fontSize: 16 },\n  },\n  tooltip: {\n    trigger: \"item\",\n    axisPointer: { type: \"cross\", lineStyle: { color: t.inkSoft, type: \"dashed\" } },\n    formatter: (p) =>\n      `${p.seriesName}<br/>Predicted: ${p.value[0].toFixed(1)} min<br/>Residual: ${p.value[1].toFixed(2)} min`,\n  },\n  grid: { left: 110, right: 60, top: 130, bottom: 90 },\n  xAxis: {\n    type: \"value\",\n    name: \"Predicted Delivery Time (min)\",\n    nameLocation: \"middle\",\n    nameGap: 45,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    min: xMin,\n    max: xMax,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Residual (min)\",\n    nameLocation: \"middle\",\n    nameGap: 60,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      name: \"Normal\",\n      type: \"scatter\",\n      data: normalPoints,\n      symbolSize: 12,\n      itemStyle: { color: t.palette[0], opacity: 0.6 },\n      markLine: {\n        symbol: \"none\",\n        silent: true,\n        lineStyle: { color: t.ink, width: 2, type: \"solid\" },\n        label: { show: false },\n        data: [{ yAxis: 0 }],\n      },\n      markArea: {\n        silent: true,\n        itemStyle: { color: t.grid },\n        data: [\n          [\n            { yAxis: bandLow, xAxis: \"min\" },\n            { yAxis: bandHigh, xAxis: \"max\" },\n          ],\n        ],\n      },\n    },\n    {\n      name: \"Outlier (|z| > 2.5σ)\",\n      type: \"scatter\",\n      data: outlierPoints,\n      symbolSize: 18,\n      itemStyle: { color: t.palette[4], opacity: 0.9 },\n    },\n  ],\n});\n"}