{"spec_id":"errorbar-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// errorbar-basic: Basic Error Bar Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.0\n// Quality: 90/100 | Created: 2026-06-30\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Temperature × plant growth study: mean stem length (cm) ± 1 SD across 5 conditions\nconst labels = [\"15 °C\", \"20 °C\", \"25 °C\", \"30 °C\", \"35 °C\"];\nconst means  = [12.3, 18.7, 24.2, 21.5, 16.1];\nconst errors = [2.1,  1.8,  2.3,  2.9,  3.4];\n\n// Peak at 25°C — full opacity to draw the eye to the inverted-U apex\nconst peakIdx = 2;\n\n// Plugin: fill canvas with theme background before anything is drawn\nconst bgPlugin = {\n  id: \"bg\",\n  beforeDraw(chart) {\n    const { ctx } = chart;\n    ctx.save();\n    ctx.fillStyle = t.pageBg;\n    ctx.fillRect(0, 0, chart.width, chart.height);\n    ctx.restore();\n  },\n};\n\n// Plugin: draw L-shaped frame (left + bottom spines only) — Chart.js border.display is\n// disabled on both scales so this replaces the default 4-sided box with a clean L-shape\nconst spinePlugin = {\n  id: \"spine\",\n  afterDraw(chart) {\n    const { ctx, chartArea } = chart;\n    ctx.save();\n    ctx.strokeStyle = t.ink;\n    ctx.lineWidth = 1.5;\n    ctx.beginPath();\n    ctx.moveTo(chartArea.left, chartArea.top);\n    ctx.lineTo(chartArea.left, chartArea.bottom);\n    ctx.lineTo(chartArea.right, chartArea.bottom);\n    ctx.stroke();\n    ctx.restore();\n  },\n};\n\n// Plugin: I-bar error whiskers drawn over each bar after bars are rendered\nconst errorBarPlugin = {\n  id: \"errorBar\",\n  afterDatasetsDraw(chart) {\n    const { ctx, scales } = chart;\n    const meta = chart.getDatasetMeta(0);\n\n    ctx.save();\n    ctx.strokeStyle = t.ink;\n    ctx.lineWidth = 2.5;\n    ctx.lineCap = \"square\";\n\n    meta.data.forEach((bar, i) => {\n      const x   = bar.x;\n      const CAP = bar.width * 0.35; // cap width proportional to bar width\n      const yHi = scales.y.getPixelForValue(means[i] + errors[i]);\n      const yLo = scales.y.getPixelForValue(means[i] - errors[i]);\n\n      // Vertical stem\n      ctx.beginPath();\n      ctx.moveTo(x, yHi);\n      ctx.lineTo(x, yLo);\n      ctx.stroke();\n\n      // Top cap\n      ctx.beginPath();\n      ctx.moveTo(x - CAP, yHi);\n      ctx.lineTo(x + CAP, yHi);\n      ctx.stroke();\n\n      // Bottom cap\n      ctx.beginPath();\n      ctx.moveTo(x - CAP, yLo);\n      ctx.lineTo(x + CAP, yLo);\n      ctx.stroke();\n    });\n\n    ctx.restore();\n  },\n};\n\n// Mount\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// Chart\nnew Chart(canvas, {\n  type: \"bar\",\n  plugins: [bgPlugin, spinePlugin, errorBarPlugin],\n  data: {\n    labels,\n    datasets: [\n      {\n        label: \"Mean stem length (cm)\",\n        data: means,\n        // Peak bar (25°C) at full opacity; remaining bars at 60% to guide attention\n        backgroundColor: means.map((_, i) => i === peakIdx ? t.palette[0] : t.palette[0] + \"99\"),\n        borderColor: t.palette[0],\n        borderWidth: means.map((_, i) => i === peakIdx ? 3 : 1),\n        borderRadius: 3,\n        barPercentage: 0.55,\n        categoryPercentage: 0.85,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 10, right: 24, bottom: 10, left: 10 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"errorbar-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { top: 12, bottom: 18 },\n      },\n      legend: {\n        labels: { color: t.inkSoft, font: { size: 16 }, boxWidth: 14 },\n      },\n    },\n    scales: {\n      x: {\n        border: { display: false }, // spinePlugin draws the L-frame instead\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { display: false },   // Y-axis only grid for bar charts\n        title: {\n          display: true,\n          text: \"Growth Temperature\",\n          color: t.ink,\n          font: { size: 15 },\n        },\n      },\n      y: {\n        border: { display: false }, // spinePlugin draws the L-frame instead\n        min: 0,\n        suggestedMax: 30,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: {\n          display: true,\n          text: \"Stem Length (cm)\",\n          color: t.ink,\n          font: { size: 15 },\n        },\n      },\n    },\n  },\n});\n"}