{"spec_id":"errorbar-asymmetric","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// errorbar-asymmetric: Asymmetric Error Bars Plot\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-05\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Ozone readings at 10 monitoring stations. Bounds are asymmetric because the\n// instrument's detection ceiling clips high excursions more than low ones.\nconst stations = [\n  \"Riverside\", \"Lakeview\", \"Hillcrest\", \"Downtown\", \"Northgate\",\n  \"Eastport\", \"Westfield\", \"Southbay\", \"Midtown\", \"Parkside\",\n];\nconst ozoneLevels = [42, 38, 55, 61, 47, 35, 58, 44, 51, 39]; // ppb, median reading\nconst lowerBounds = [6, 4, 9, 12, 5, 3, 10, 7, 8, 4]; // ppb below median (10th pct)\nconst upperBounds = [11, 7, 14, 9, 13, 6, 16, 10, 12, 8]; // ppb above median (90th pct)\n\nconst lowerValues = ozoneLevels.map((v, i) => v - lowerBounds[i]);\nconst upperValues = ozoneLevels.map((v, i) => v + upperBounds[i]);\nconst rangePad = (Math.max(...upperValues) - Math.min(...lowerValues)) * 0.08;\n\n// --- Custom whiskers ---------------------------------------------------------\n// The core bundle has no errorbar series (that lives in highcharts-more, which\n// is not loaded), so the asymmetric whiskers are drawn with the renderer,\n// positioned through the real axis-to-pixel mapping on every chart render.\nconst capHalfWidth = 10;\n\nconst drawErrorBars = (chart) => {\n  if (chart.errorBarGroup) chart.errorBarGroup.destroy();\n  const group = chart.renderer.g(\"error-bars\").add();\n  const xAxis = chart.xAxis[0];\n  const yAxis = chart.yAxis[0];\n\n  stations.forEach((_, i) => {\n    const xPixel = xAxis.toPixels(i);\n    const yTop = yAxis.toPixels(upperValues[i]);\n    const yBottom = yAxis.toPixels(lowerValues[i]);\n\n    [\n      [\"M\", xPixel, yTop, \"L\", xPixel, yBottom],\n      [\"M\", xPixel - capHalfWidth, yTop, \"L\", xPixel + capHalfWidth, yTop],\n      [\"M\", xPixel - capHalfWidth, yBottom, \"L\", xPixel + capHalfWidth, yBottom],\n    ].forEach((path) => {\n      chart.renderer.path(path).attr({ \"stroke-width\": 2.5, stroke: t.palette[0] }).add(group);\n    });\n  });\n\n  chart.errorBarGroup = group;\n};\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"scatter\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    events: { render: function () { drawErrorBars(this); } },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"errorbar-asymmetric · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: \"Whiskers span the 10th–90th percentile range at each station\",\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    categories: stations,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    title: { text: \"Monitoring Station\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n  },\n  yAxis: {\n    min: Math.floor(Math.min(...lowerValues) - rangePad),\n    max: Math.ceil(Math.max(...upperValues) + rangePad),\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    title: { text: \"Ozone Concentration (ppb)\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n  },\n  legend: { enabled: false },\n  tooltip: {\n    backgroundColor: t.elevatedBg,\n    borderColor: t.inkSoft,\n    style: { color: t.ink, fontSize: \"14px\" },\n    formatter: function () {\n      const i = this.point.index;\n      return `<b>${stations[i]}</b><br/>Median: ${ozoneLevels[i]} ppb<br/>` +\n        `10th–90th pct: ${lowerValues[i]}–${upperValues[i]} ppb`;\n    },\n  },\n  plotOptions: {\n    series: { animation: false },\n    scatter: { marker: { radius: 7, fillColor: t.palette[0], lineColor: t.pageBg, lineWidth: 1 } },\n  },\n  series: [{\n    name: \"Median ozone level\",\n    data: stations.map((_, i) => ({ x: i, y: ozoneLevels[i] })),\n    color: t.palette[0],\n  }],\n});\n"}