{"spec_id":"bar-grouped","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// bar-grouped: Grouped Bar Chart\n// Library: chartjs 4.4.7 | JavaScript 22.23.1\n// Quality: 92/100 | Created: 2026-08-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\nconst quarters = [\"Q1\", \"Q2\", \"Q3\", \"Q4\"];\nconst products = [\"Hardware\", \"Software\", \"Services\"];\nconst revenueByProduct = {\n  Hardware: [42, 38, 45, 51],\n  Software: [61, 68, 72, 79],\n  Services: [29, 33, 31, 37],\n};\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Peak callout (custom Chart.js plugin, no external deps) -----------------\n// Highlights the standout data point (Software, Q4) with a speech-bubble\n// callout drawn directly on the canvas via the plugin lifecycle hooks.\nconst peakProduct = \"Software\";\nconst peakQuarterIndex = 3;\nconst peakValue = revenueByProduct[peakProduct][peakQuarterIndex];\n\nconst peakCalloutPlugin = {\n  id: \"peakCallout\",\n  afterDatasetsDraw(chart) {\n    const datasetIndex = products.indexOf(peakProduct);\n    const meta = chart.getDatasetMeta(datasetIndex);\n    const bar = meta.data[peakQuarterIndex];\n    if (!bar) return;\n\n    const { ctx } = chart;\n    const { x, y } = bar.getProps([\"x\", \"y\"], true);\n    const label = `Peak: $${peakValue}M`;\n\n    ctx.save();\n    ctx.font = \"600 16px sans-serif\";\n    const textWidth = ctx.measureText(label).width;\n    const boxWidth = textWidth + 24;\n    const boxHeight = 32;\n    const boxX = x - boxWidth / 2;\n    const boxY = y - boxHeight - 16;\n\n    ctx.fillStyle = t.ink;\n    ctx.beginPath();\n    ctx.roundRect(boxX, boxY, boxWidth, boxHeight, 6);\n    ctx.moveTo(x - 7, boxY + boxHeight);\n    ctx.lineTo(x + 7, boxY + boxHeight);\n    ctx.lineTo(x, boxY + boxHeight + 9);\n    ctx.closePath();\n    ctx.fill();\n\n    ctx.fillStyle = t.pageBg;\n    ctx.textAlign = \"center\";\n    ctx.textBaseline = \"middle\";\n    ctx.fillText(label, x, boxY + boxHeight / 2);\n    ctx.restore();\n  },\n};\n\n// --- Chart ---------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"bar\",\n  data: {\n    labels: quarters,\n    datasets: products.map((product, i) => ({\n      label: product,\n      data: revenueByProduct[product],\n      backgroundColor: t.palette[i % t.palette.length],\n      hoverBackgroundColor: t.palette[i % t.palette.length],\n      borderWidth: 0,\n      borderRadius: 4,\n    })),\n  },\n  plugins: [peakCalloutPlugin],\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 44, right: 24, bottom: 8, left: 8 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"Quarterly Revenue by Product Line · bar-grouped · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { bottom: 20 },\n      },\n      legend: {\n        position: \"top\",\n        align: \"start\",\n        labels: { color: t.ink, font: { size: 16 }, boxWidth: 18, boxHeight: 18, usePointStyle: true, pointStyle: \"rect\" },\n      },\n      tooltip: {\n        callbacks: {\n          label: (item) => `${item.dataset.label}: $${item.formattedValue}M`,\n        },\n      },\n    },\n    scales: {\n      x: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { display: false },\n        title: { display: true, text: \"Fiscal Quarter\", color: t.ink, font: { size: 15 } },\n      },\n      y: {\n        ticks: { color: t.inkSoft, font: { size: 14 }, callback: (v) => `$${v}M` },\n        grid: { color: t.grid },\n        border: { display: false },\n        title: { display: true, text: \"Revenue (USD Millions)\", color: t.ink, font: { size: 15 } },\n        beginAtZero: true,\n        suggestedMax: 92,\n      },\n    },\n  },\n});\n"}