{"spec_id":"band-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// band-basic: Basic Band Plot\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-08-24\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Projected global mean sea level rise since a 1900 baseline, moderate-emissions\n// scenario. The 95% prediction interval widens with the forecast horizon, as is\n// typical for cumulative climate projections.\nlet seed = 42;\nfunction nextRandom() {\n  seed = (seed * 1103515245 + 12345) % 2147483648;\n  return seed / 2147483648;\n}\n\nconst years = [];\nconst lowerBase = []; // invisible stack base = y_lower\nconst bandHeight = []; // stacked on top of lowerBase to reach y_upper\nconst centerLine = []; // y_center trend\n\nfor (let t_yr = 0; t_yr <= 50; t_yr += 1) {\n  const year = 2025 + t_yr;\n  const trend = 228 + 4.4 * t_yr + 0.03 * t_yr * t_yr; // mm since 1900\n  const wobble = (nextRandom() - 0.5) * 6; // year-to-year natural variability\n  const center = trend + wobble;\n  const halfWidth = 8 + 0.5 * t_yr; // widening 95% prediction interval\n\n  years.push(year);\n  lowerBase.push(Math.round((center - halfWidth) * 10) / 10);\n  bandHeight.push(Math.round(halfWidth * 2 * 10) / 10);\n  centerLine.push(Math.round(center * 10) / 10);\n}\n\nconst bandFill = Highcharts.color(t.palette[0]).setOpacity(0.25).get();\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"band-basic · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: \"Projected sea level rise, moderate-emissions scenario · 95% prediction interval\",\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    title: { text: \"Year\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    tickInterval: 10,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    title: {\n      text: \"Sea Level Rise Since 1900 (mm)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    // The stacking trick anchors the invisible spacer series at 0, which would\n    // otherwise force the axis to start at 0 and waste ~40% of the canvas below\n    // the data (~220-550mm). Cropping to a tighter min is safe: the spacer stays\n    // fully transparent, so the stacking math is unaffected — only the visible\n    // viewport tightens.\n    min: 150,\n    gridLineColor: t.grid,\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  tooltip: { shared: true, valueSuffix: \" mm\" },\n  plotOptions: {\n    series: { animation: false, marker: { enabled: false } },\n  },\n  series: [\n    // Stacking order matters: Highcharts stacks the FIRST series in this array\n    // as the cumulative top of the stack, and the LAST as the base at 0. So the\n    // visible band goes first (rendering from y_lower to y_upper) and the\n    // invisible spacer goes second (rendering from 0 to y_lower, hidden).\n    {\n      name: \"95% prediction interval\",\n      type: \"areaspline\",\n      data: years.map((year, i) => [year, bandHeight[i]]),\n      stacking: \"normal\",\n      color: bandFill,\n      lineWidth: 0,\n      enableMouseTracking: false,\n      showInLegend: true,\n    },\n    {\n      name: \"Lower bound\",\n      type: \"areaspline\",\n      data: years.map((year, i) => [year, lowerBase[i]]),\n      stacking: \"normal\",\n      color: \"transparent\",\n      fillOpacity: 0,\n      lineWidth: 0,\n      enableMouseTracking: false,\n      showInLegend: false,\n    },\n    {\n      name: \"Projected mean\",\n      type: \"spline\",\n      data: years.map((year, i) => {\n        const point = [year, centerLine[i]];\n        if (i !== years.length - 1) return point;\n        // Callout on the final projected value gives the trend line a clear\n        // focal point instead of just trailing off at the plot edge.\n        return {\n          x: year,\n          y: centerLine[i],\n          dataLabels: {\n            enabled: true,\n            format: `${centerLine[i]} mm by ${year}`,\n            align: \"right\",\n            x: -8,\n            y: -12,\n            style: {\n              color: t.ink,\n              fontSize: \"14px\",\n              fontWeight: \"600\",\n              textOutline: \"none\",\n            },\n          },\n          marker: { enabled: true, radius: 5, fillColor: t.palette[0] },\n        };\n      }),\n      color: t.palette[0],\n      lineWidth: 3,\n      showInLegend: true,\n    },\n  ],\n});\n"}