{"spec_id":"line-confidence","library":"echarts","language":"javascript","code":"// anyplot.ai\n// line-confidence: Line Plot with Confidence Interval\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-05\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG) ------------------------------------\nfunction lcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1103515245 + 12345) % 2147483648;\n    return state / 2147483648;\n  };\n}\nconst rand = lcg(42);\n\nconst days = 60;\nconst startDate = new Date(2026, 0, 1);\nconst dates = [];\nfor (let i = 0; i < days; i++) {\n  const d = new Date(startDate);\n  d.setDate(d.getDate() + i);\n  dates.push(d.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" }));\n}\n\n// Forecast model: trend + weekly seasonality + noise. Uncertainty is tight\n// over the observed window (days 0-29) and widens across the forecast\n// horizon (days 30-59), matching how prediction intervals grow with lead time.\nconst predicted = [];\nconst lower = [];\nconst upper = [];\nconst baseUsers = 4200;\nfor (let i = 0; i < days; i++) {\n  const trend = i * 18;\n  const weekly = Math.sin((i / 7) * Math.PI * 2) * 220;\n  const noise = (rand() - 0.5) * 150;\n  const value = baseUsers + trend + weekly + noise;\n  const horizonFactor = i < 30 ? 1 : 1 + (i - 30) * 0.06;\n  const margin = 180 * horizonFactor;\n  predicted.push(Math.round(value));\n  lower.push(Math.round(value - margin));\n  upper.push(Math.round(value + margin));\n}\nconst band = upper.map((u, i) => u - lower[i]);\n\n// Zoom the y-axis to the data range (rather than starting at 0) so the trend\n// and the widening confidence band aren't diluted by empty vertical space.\nconst yMin = Math.floor((Math.min(...lower) - 100) / 200) * 200;\n\n// Boundary between the observed window (days 0-29) and the forecast horizon\n// (days 30-59), marked on the chart so viewers can see where the forecast begins.\nconst forecastStart = 30;\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option --------------------------------------------------------------------\nconst title = \"Daily Active Users Forecast · line-confidence · javascript · echarts · anyplot.ai\";\n\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  color: t.palette,\n  title: {\n    text: title,\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 18, fontWeight: 500 },\n  },\n  legend: {\n    data: [\n      { name: \"Predicted Active Users\", icon: \"roundRect\" },\n      { name: \"Prediction Interval\", icon: \"roundRect\" },\n    ],\n    top: 56,\n    textStyle: { color: t.ink, fontSize: 16 },\n  },\n  grid: { left: 100, right: 60, top: 130, bottom: 90 },\n  tooltip: {\n    trigger: \"axis\",\n    formatter: (params) => {\n      const shown = params.filter((p) => p.seriesName !== \"Lower Bound\");\n      const lines = shown.map((p) => `${p.marker} ${p.seriesName}: ${p.value.toLocaleString()}`);\n      return `${params[0].axisValueLabel}<br/>${lines.join(\"<br/>\")}`;\n    },\n  },\n  xAxis: {\n    type: \"category\",\n    data: dates,\n    axisLabel: { color: t.inkSoft, fontSize: 14, interval: 4 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Daily Active Users\",\n    min: yMin,\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      name: \"Lower Bound\",\n      type: \"line\",\n      data: lower,\n      stack: \"confidence-band\",\n      symbol: \"none\",\n      lineStyle: { opacity: 0 },\n      silent: true,\n      tooltip: { show: false },\n    },\n    {\n      name: \"Prediction Interval\",\n      type: \"line\",\n      data: band,\n      stack: \"confidence-band\",\n      symbol: \"none\",\n      lineStyle: { opacity: 0 },\n      itemStyle: { color: t.palette[0], opacity: 0.25 },\n      areaStyle: { color: t.palette[0], opacity: 0.25 },\n    },\n    {\n      name: \"Predicted Active Users\",\n      type: \"line\",\n      data: predicted,\n      symbol: \"none\",\n      lineStyle: { width: 3.5, color: t.palette[0] },\n      itemStyle: { color: t.palette[0] },\n      z: 3,\n      markLine: {\n        silent: true,\n        symbol: \"none\",\n        lineStyle: { type: \"dashed\", color: t.inkSoft, width: 1.5 },\n        label: { formatter: \"Forecast begins\", color: t.inkSoft, fontSize: 13, position: \"insideEndTop\" },\n        data: [{ xAxis: forecastStart }],\n      },\n    },\n  ],\n});\n"}