{"spec_id":"dumbbell-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// dumbbell-basic: Basic Dumbbell Chart\n// Library: highcharts 12.6.0 | JavaScript 22.23.0\n// Quality: 89/100 | Created: 2026-06-30\n\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Data — Employee satisfaction scores (0–10) before/after a workplace wellness initiative\nconst departments = [\"Engineering\", \"Marketing\", \"Sales\", \"HR\", \"Finance\", \"Operations\", \"Legal\", \"Product\"];\nconst scoreBefore = [6.2, 5.8, 6.5, 7.1, 5.4, 6.8, 6.0, 6.9];\nconst scoreAfter  = [7.7, 7.4, 8.0, 8.3, 7.0, 8.2, 7.3, 8.4];\n\n// Sort ascending by improvement so the largest improvement appears at top\nconst sortIdx = departments.map((_, i) => i)\n  .sort((a, b) => (scoreAfter[a] - scoreBefore[a]) - (scoreAfter[b] - scoreBefore[b]));\nconst labels  = sortIdx.map(i => departments[i]);\nconst vBefore = sortIdx.map(i => scoreBefore[i]);\nconst vAfter  = sortIdx.map(i => scoreAfter[i]);\nconst n       = labels.length;\n\n// [score, categoryIndex] pairs — x = score, y = integer row index\nconst afterData  = vAfter.map((v,  i) => [v, i]);\nconst beforeData = vBefore.map((v, i) => [v, i]);\n\nconst chart = Highcharts.chart(\"container\", {\n  chart: {\n    type: \"scatter\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    marginLeft: 160,\n    marginRight: 50,\n    marginTop: 90,\n    marginBottom: 70,\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"dumbbell-basic · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: \"Employee satisfaction before/after workplace wellness initiative\",\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    title: {\n      text: \"Satisfaction Score (0–10)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    gridLineWidth: 1,\n    min: 4.5,\n    max: 9.5,\n    tickInterval: 1,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    title: { text: null },\n    min: -0.5,\n    max: n - 0.5,\n    tickPositions: labels.map((_, i) => i),\n    labels: {\n      formatter() { return labels[this.value] || \"\"; },\n      style: { color: t.inkSoft, fontSize: \"14px\" },\n    },\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: \"transparent\",\n  },\n  legend: {\n    verticalAlign: \"top\",\n    align: \"right\",\n    layout: \"vertical\",\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  plotOptions: {\n    series: { animation: false },\n    scatter: {\n      marker: { radius: 9, lineWidth: 0 },\n    },\n  },\n  series: [\n    {\n      name: \"After\",\n      data: afterData,\n      color: t.palette[0],\n      marker: { symbol: \"circle\" },\n      zIndex: 5,\n    },\n    {\n      name: \"Before\",\n      data: beforeData,\n      color: t.palette[1],\n      marker: { symbol: \"circle\" },\n      zIndex: 5,\n    },\n  ],\n});\n\n// Draw connecting lines between Before and After dots via the SVG renderer\nconst renderer = chart.renderer;\nconst plotLeft  = chart.plotLeft;\nconst plotTop   = chart.plotTop;\n\n// Build a lookup from category index → before-series point\nconst beforeByIdx = {};\nchart.series[1].data.forEach(p => { beforeByIdx[p.y] = p; });\n\n// Improvement per sorted row (label top 3 for focal point)\nconst diffs = vAfter.map((v, i) => +(v - vBefore[i]).toFixed(1));\nconst topN  = 3;\n\nchart.series[0].data.forEach((afterPt) => {\n  const beforePt = beforeByIdx[afterPt.y];\n  if (!beforePt) return;\n\n  renderer.path([\n    \"M\", plotLeft + beforePt.plotX, plotTop + beforePt.plotY,\n    \"L\", plotLeft + afterPt.plotX,  plotTop + afterPt.plotY,\n  ])\n    .attr({ stroke: t.inkSoft, \"stroke-width\": 2.5, \"stroke-opacity\": 0.35 })\n    .add();\n\n  // Annotate top-improvement rows with the gain value\n  if (afterPt.y >= n - topN) {\n    const midX = plotLeft + (beforePt.plotX + afterPt.plotX) / 2;\n    const midY = plotTop + afterPt.plotY - 14;\n    renderer.text(`+${diffs[afterPt.y].toFixed(1)}`, midX, midY)\n      .attr({ align: \"center\", zIndex: 6, fill: t.inkSoft })\n      .css({ fontSize: \"11px\", fontWeight: \"700\" })\n      .add();\n  }\n});\n"}