{"spec_id":"bar-error","library":"d3","language":"javascript","code":"// anyplot.ai\n// bar-error: Bar Chart with Error Bars\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 110, right: 60, bottom: 90, left: 100 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Mean tensile strength (MPa) of alloy samples across annealing treatments,\n// with standard deviation as the error range.\nconst data = [\n  { treatment: \"Untreated\", mean: 312, sd: 18 },\n  { treatment: \"200°C\", mean: 348, sd: 14 },\n  { treatment: \"400°C\", mean: 401, sd: 21 },\n  { treatment: \"600°C\", mean: 372, sd: 26 },\n  { treatment: \"800°C\", mean: 289, sd: 33 },\n];\n\n// --- SVG mount ----------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Scales ---------------------------------------------------------------\nconst x = d3.scaleBand().domain(data.map((d) => d.treatment)).range([0, iw]).padding(0.35);\nconst yMax = d3.max(data, (d) => d.mean + d.sd) * 1.08;\nconst y = d3.scaleLinear().domain([0, yMax]).nice().range([ih, 0]);\n\n// --- Gridlines (y-axis only) ------------------------------------------------\ng.append(\"g\")\n  .selectAll(\"line\")\n  .data(y.ticks(6))\n  .join(\"line\")\n  .attr(\"x1\", 0)\n  .attr(\"x2\", iw)\n  .attr(\"y1\", (d) => y(d))\n  .attr(\"y2\", (d) => y(d))\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1);\n\n// --- Bars --------------------------------------------------------------------\ng.selectAll(\".bar\")\n  .data(data)\n  .join(\"rect\")\n  .attr(\"class\", \"bar\")\n  .attr(\"x\", (d) => x(d.treatment))\n  .attr(\"y\", (d) => y(d.mean))\n  .attr(\"width\", x.bandwidth())\n  .attr(\"height\", (d) => ih - y(d.mean))\n  .attr(\"fill\", t.palette[0])\n  .attr(\"rx\", 4);\n\n// --- Error bars (stem + caps) ------------------------------------------------\nconst capWidth = Math.min(x.bandwidth() * 0.4, 36);\nconst errorGroups = g.selectAll(\".error\").data(data).join(\"g\").attr(\"class\", \"error\");\n\nerrorGroups\n  .append(\"line\")\n  .attr(\"x1\", (d) => x(d.treatment) + x.bandwidth() / 2)\n  .attr(\"x2\", (d) => x(d.treatment) + x.bandwidth() / 2)\n  .attr(\"y1\", (d) => y(d.mean + d.sd))\n  .attr(\"y2\", (d) => y(d.mean - d.sd))\n  .attr(\"stroke\", t.ink)\n  .attr(\"stroke-width\", 2.5);\n\nerrorGroups\n  .selectAll(\".cap\")\n  .data((d) => [d.mean + d.sd, d.mean - d.sd].map((v) => ({ treatment: d.treatment, v })))\n  .join(\"line\")\n  .attr(\"class\", \"cap\")\n  .attr(\"x1\", (d) => x(d.treatment) + x.bandwidth() / 2 - capWidth / 2)\n  .attr(\"x2\", (d) => x(d.treatment) + x.bandwidth() / 2 + capWidth / 2)\n  .attr(\"y1\", (d) => y(d.v))\n  .attr(\"y2\", (d) => y(d.v))\n  .attr(\"stroke\", t.ink)\n  .attr(\"stroke-width\", 2.5);\n\n// --- Peak callout (data storytelling) -----------------------------------\nconst peak = data.reduce((a, b) => (b.mean > a.mean ? b : a));\nconst peakX = x(peak.treatment) + x.bandwidth() / 2;\nconst peakY = y(peak.mean + peak.sd) - 14;\ng.append(\"path\")\n  .attr(\"d\", `M${peakX - 6},${peakY} L${peakX + 6},${peakY} L${peakX},${peakY + 9} Z`)\n  .attr(\"fill\", t.ink);\ng.append(\"text\")\n  .attr(\"x\", peakX)\n  .attr(\"y\", peakY - 8)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"14px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"Peak\");\n\n// --- Axes ----------------------------------------------------------------\nconst xAxis = g.append(\"g\").attr(\"transform\", `translate(0,${ih})`).call(d3.axisBottom(x).tickSizeOuter(0));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).ticks(6).tickSizeOuter(0));\n\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"16px\");\n  ax.selectAll(\"line\").attr(\"stroke\", t.inkSoft);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\ng.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\n\n// Emphasize the peak treatment's x-axis label to guide the reader to it.\nxAxis\n  .selectAll(\"text\")\n  .filter((d) => d === peak.treatment)\n  .style(\"font-weight\", \"700\")\n  .attr(\"fill\", t.ink);\n\n// --- Axis labels ---------------------------------------------------------\ng.append(\"text\")\n  .attr(\"x\", iw / 2)\n  .attr(\"y\", ih + 66)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"18px\")\n  .text(\"Annealing Treatment\");\n\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -70)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"18px\")\n  .text(\"Tensile Strength (MPa)\");\n\n// --- Annotation explaining error bars ---------------------------------------\ng.append(\"text\")\n  .attr(\"x\", iw)\n  .attr(\"y\", -18)\n  .attr(\"text-anchor\", \"end\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"15px\")\n  .text(\"Error bars: ±1 SD (n = 20 samples per group)\");\n\n// --- Title -----------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 56)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"30px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"bar-error · javascript · d3 · anyplot.ai\");\n"}