{"spec_id":"dumbbell-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// dumbbell-basic: Basic Dumbbell Chart\n// Library: chartjs 4.4.7 | JavaScript 22.23.1\n// Quality: 92/100 | Created: 2026-06-30\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Training scores before and after a skills development program (10 departments)\nconst rawData = [\n  { dept: \"Legal\",       before: 65, after: 72 },\n  { dept: \"Admin\",       before: 58, after: 68 },\n  { dept: \"Finance\",     before: 69, after: 83 },\n  { dept: \"HR\",          before: 70, after: 86 },\n  { dept: \"Marketing\",   before: 74, after: 90 },\n  { dept: \"Support\",     before: 61, after: 78 },\n  { dept: \"Operations\",  before: 57, after: 75 },\n  { dept: \"Sales\",       before: 63, after: 81 },\n  { dept: \"Product\",     before: 72, after: 91 },\n  { dept: \"Engineering\", before: 68, after: 88 },\n];\n\n// Sort ascending by improvement so the largest gain appears at the top of the chart\nrawData.sort((a, b) => (a.after - a.before) - (b.after - b.before));\n\nconst labels     = rawData.map(d => d.dept);\nconst beforeData = rawData.map((d, i) => ({ x: d.before, y: i }));\nconst afterData  = rawData.map((d, i) => ({ x: d.after,  y: i }));\n\n// Mount\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// Plugin: fill full canvas with theme background\nconst bgPlugin = {\n  id: \"background\",\n  beforeDraw(chart) {\n    const ctx = chart.ctx;\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 dumbbell connecting lines behind the dots\nconst dumbbellPlugin = {\n  id: \"dumbbell\",\n  beforeDatasetsDraw(chart) {\n    const ctx   = chart.ctx;\n    const meta0 = chart.getDatasetMeta(0); // Before points\n    const meta1 = chart.getDatasetMeta(1); // After points\n\n    ctx.save();\n    ctx.strokeStyle = t.inkSoft;\n    ctx.lineWidth   = 2.5;\n    ctx.globalAlpha = 0.55;\n    ctx.lineCap     = \"round\";\n\n    for (let i = 0; i < meta0.data.length; i++) {\n      const p0 = meta0.data[i];\n      const p1 = meta1.data[i];\n      if (!p0 || !p1) continue;\n      ctx.beginPath();\n      ctx.moveTo(p0.x, p0.y);\n      ctx.lineTo(p1.x, p1.y);\n      ctx.stroke();\n    }\n    ctx.restore();\n  },\n};\n\n// Chart\nnew Chart(canvas, {\n  type: \"scatter\",\n  plugins: [bgPlugin, dumbbellPlugin],\n  data: {\n    datasets: [\n      {\n        label: \"Before Program\",\n        data: beforeData,\n        backgroundColor: t.palette[0],  // #009E73 brand green — Imprint pos 1\n        borderColor: t.pageBg,\n        borderWidth: 2,\n        pointRadius: 11,\n        pointHoverRadius: 13,\n        clip: false,\n      },\n      {\n        label: \"After Program\",\n        data: afterData,\n        backgroundColor: t.palette[1],  // #C475FD lavender — Imprint pos 2\n        borderColor: t.pageBg,\n        borderWidth: 2,\n        pointRadius: 11,\n        pointHoverRadius: 13,\n        clip: false,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"dumbbell-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { top: 16, bottom: 18 },\n      },\n      legend: {\n        position: \"top\",\n        labels: {\n          color: t.ink,\n          font: { size: 16 },\n          boxWidth: 18,\n          padding: 20,\n        },\n      },\n    },\n    scales: {\n      x: {\n        title: {\n          display: true,\n          text: \"Training Score\",\n          color: t.ink,\n          font: { size: 16 },\n          padding: { top: 8 },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n        },\n        grid: { color: t.grid },\n        min: 50,\n        max: 100,\n      },\n      y: {\n        type: \"linear\",\n        min: -0.5,\n        max: rawData.length - 0.5,\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          stepSize: 1,\n          callback(value) {\n            const idx = Math.round(value);\n            return labels[idx] !== undefined ? labels[idx] : \"\";\n          },\n        },\n        grid: { color: t.grid },\n      },\n    },\n    layout: {\n      padding: { left: 10, right: 24, top: 8, bottom: 12 },\n    },\n  },\n});\n"}