{"spec_id":"errorbar-asymmetric","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// errorbar-asymmetric: Asymmetric Error Bars Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 86/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n// Mean symptom improvement (%) per treatment group, with asymmetric 90% CI\n// bounds (5th-95th percentile) back-transformed from a log-scale model\n// (upper bound stretches further than the lower bound for every group).\nconst treatmentGroups = [\n  \"Control\",\n  \"Treatment A\",\n  \"Treatment B\",\n  \"Treatment C\",\n  \"Treatment D\",\n  \"Treatment E\",\n];\nconst meanImprovement = [2.1, 8.4, 15.2, 22.7, 11.3, 18.9];\nconst errorLower = [1.8, 3.1, 4.5, 5.2, 7.4, 4.0];\nconst errorUpper = [3.4, 6.7, 9.8, 12.1, 9.9, 8.3];\n\nconst ciBounds = meanImprovement.flatMap((mean, i) => [\n  mean - errorLower[i],\n  mean + errorUpper[i],\n]);\nconst yAxisMax = Math.ceil((Math.max(...ciBounds) * 1.08) / 2) * 2;\nconst bestIndex = meanImprovement.indexOf(Math.max(...meanImprovement));\n\n// --- Mount -----------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Custom plugin: asymmetric error bar caps -------------------------------\nconst asymmetricErrorBars = {\n  id: \"asymmetricErrorBars\",\n  afterDatasetsDraw(chart) {\n    const { ctx, scales } = chart;\n    const capHalfWidth = 20;\n    ctx.save();\n    ctx.strokeStyle = t.palette[0];\n    ctx.lineWidth = 3;\n    ctx.lineCap = \"round\";\n    treatmentGroups.forEach((_, i) => {\n      const xPixel = scales.x.getPixelForValue(i);\n      const yLowPixel = scales.y.getPixelForValue(meanImprovement[i] - errorLower[i]);\n      const yHighPixel = scales.y.getPixelForValue(meanImprovement[i] + errorUpper[i]);\n\n      ctx.beginPath();\n      ctx.moveTo(xPixel, yLowPixel);\n      ctx.lineTo(xPixel, yHighPixel);\n      ctx.stroke();\n\n      ctx.beginPath();\n      ctx.moveTo(xPixel - capHalfWidth, yLowPixel);\n      ctx.lineTo(xPixel + capHalfWidth, yLowPixel);\n      ctx.moveTo(xPixel - capHalfWidth, yHighPixel);\n      ctx.lineTo(xPixel + capHalfWidth, yHighPixel);\n      ctx.stroke();\n    });\n    ctx.restore();\n\n    // Callout on the standout group to give the chart a point of focus.\n    const bestXPixel = scales.x.getPixelForValue(bestIndex);\n    const bestYPixel = scales.y.getPixelForValue(\n      meanImprovement[bestIndex] + errorUpper[bestIndex]\n    );\n    ctx.save();\n    ctx.fillStyle = t.inkSoft;\n    ctx.font = \"italic 13px sans-serif\";\n    ctx.textAlign = \"center\";\n    ctx.fillText(\"Largest response\", bestXPixel, bestYPixel - 16);\n    ctx.restore();\n  },\n};\n\n// --- Chart -------------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    labels: treatmentGroups,\n    datasets: [\n      {\n        label: \"Mean Symptom Improvement (%)\",\n        data: meanImprovement,\n        showLine: false,\n        pointBackgroundColor: t.palette[0],\n        pointBorderColor: t.pageBg,\n        pointBorderWidth: 2,\n        pointRadius: 10,\n        pointHoverRadius: 10,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 10, right: 30, bottom: 10, left: 10 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"errorbar-asymmetric · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { bottom: 6 },\n      },\n      subtitle: {\n        display: true,\n        text: \"Points show mean improvement; bars show asymmetric 90% CI (5th–95th percentile)\",\n        color: t.inkSoft,\n        font: { size: 14, style: \"italic\" },\n        padding: { bottom: 16 },\n      },\n      legend: { display: false },\n    },\n    scales: {\n      x: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { display: false },\n        title: { display: true, text: \"Treatment Group\", color: t.ink, font: { size: 16 } },\n      },\n      y: {\n        min: 0,\n        max: yAxisMax,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Symptom Improvement (%)\", color: t.ink, font: { size: 16 } },\n      },\n    },\n  },\n  plugins: [asymmetricErrorBars],\n});\n"}