{"spec_id":"dumbbell-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// dumbbell-basic: Basic Dumbbell Chart\n// Library: echarts 5.5.1 | JavaScript 22.23.0\n// Quality: 93/100 | Created: 2026-06-30\n//# anyplot-orientation: landscape\n\nconst tokens = window.ANYPLOT_TOKENS;\n\n// Department efficiency scores (0–100) before/after digital transformation\n// Legal and Finance show slight decline; remaining departments all improved\n// Sorted ascending by delta so largest gain appears at top (Y-axis bottom→top)\nconst departments = [\n  \"Legal\", \"Finance\", \"Operations\", \"HR\",\n  \"Logistics\", \"R&D\", \"Marketing\", \"IT\", \"Engineering\", \"Sales\"\n];\nconst scoreBefore = [74, 73, 70, 61, 69, 64, 55, 67, 62, 58];\nconst scoreAfter  = [71, 70, 85, 78, 86, 83, 79, 91, 88, 84];\n\nconst deltas = departments.map(function(_, i) { return scoreAfter[i] - scoreBefore[i]; });\nconst maxAbsDelta = Math.max.apply(null, deltas.map(Math.abs));\n\nconst chart = echarts.init(document.getElementById(\"container\"));\n\nconst titleText =\n  \"Digital Transformation · dumbbell-basic · javascript · echarts · anyplot.ai\";\nconst titleFontSize = Math.round(22 * Math.min(1, 67 / titleText.length));\n\nchart.setOption({\n  animation: false,\n  color: tokens.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: titleText,\n    left: \"center\",\n    top: 20,\n    textStyle: { color: tokens.ink, fontSize: titleFontSize, fontWeight: \"500\" }\n  },\n  legend: {\n    data: [\n      { name: \"Before\", icon: \"circle\", itemStyle: { color: tokens.palette[0] } },\n      { name: \"After\",  icon: \"circle\", itemStyle: { color: tokens.palette[1] } }\n    ],\n    top: 58,\n    left: \"center\",\n    itemGap: 32,\n    itemWidth: 14,\n    itemHeight: 14,\n    textStyle: { color: tokens.inkSoft, fontSize: 14 }\n  },\n  tooltip: {\n    trigger: \"item\",\n    backgroundColor: tokens.elevatedBg,\n    borderColor: tokens.grid,\n    textStyle: { color: tokens.ink, fontSize: 13 },\n    formatter: function(params) {\n      if (params.seriesType !== \"scatter\") return \"\";\n      var idx = params.dataIndex;\n      var delta = deltas[idx];\n      var sign = delta >= 0 ? \"+\" : \"\";\n      return (\n        \"<b>\" + departments[idx] + \"</b><br/>\" +\n        \"Before: \" + scoreBefore[idx] + \"<br/>\" +\n        \"After: \"  + scoreAfter[idx]  + \"<br/>\" +\n        \"Change: \" + sign + delta\n      );\n    }\n  },\n  grid: { left: 130, right: 90, top: 100, bottom: 70 },\n  xAxis: {\n    type: \"value\",\n    min: 40,\n    max: 100,\n    name: \"Efficiency Score\",\n    nameLocation: \"middle\",\n    nameGap: 42,\n    nameTextStyle: { color: tokens.inkSoft, fontSize: 14 },\n    axisLabel: { color: tokens.inkSoft, fontSize: 13 },\n    axisLine: { show: false },\n    axisTick: { show: false },\n    splitLine: { show: false }\n  },\n  yAxis: {\n    type: \"category\",\n    data: departments,\n    axisLabel: { color: tokens.inkSoft, fontSize: 13 },\n    axisLine: { lineStyle: { color: tokens.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false }\n  },\n  series: [\n    // Connector lines: color encodes direction (inkSoft=gain / matte-red=decline),\n    // lineWidth and opacity scale with |delta| to visually rank the magnitude of change\n    {\n      type: \"custom\",\n      name: \"_connector\",\n      renderItem: function(params, api) {\n        var delta = deltas[params.dataIndex];\n        var frac = Math.abs(delta) / maxAbsDelta;\n        var lineWidth = 1.5 + 3.5 * frac;\n        var opacity   = 0.25 + 0.45 * frac;\n        var color = delta >= 0 ? tokens.inkSoft : \"#AE3030\";\n        var s = api.coord([api.value(0), params.dataIndex]);\n        var e = api.coord([api.value(1), params.dataIndex]);\n        return {\n          type: \"line\",\n          shape: { x1: s[0], y1: s[1], x2: e[0], y2: e[1] },\n          style: { stroke: color, lineWidth: lineWidth, opacity: opacity }\n        };\n      },\n      data: departments.map(function(_, i) { return [scoreBefore[i], scoreAfter[i]]; }),\n      encode: { x: [0, 1] },\n      z: 1,\n      silent: true\n    },\n    // \"Before\" endpoints — Imprint palette[0] (brand green)\n    // Dot size scales with |delta| to emphasize departments with larger changes\n    {\n      type: \"scatter\",\n      name: \"Before\",\n      data: departments.map(function(d, i) {\n        return [scoreBefore[i], d, Math.abs(deltas[i])];\n      }),\n      encode: { x: 0, y: 1 },\n      symbolSize: function(data) { return 14 + 12 * (data[2] / maxAbsDelta); },\n      itemStyle: { color: tokens.palette[0] },\n      z: 2\n    },\n    // \"After\" endpoints — Imprint palette[1] (lavender)\n    // Delta labels displayed to the right of each After dot for instant readability\n    {\n      type: \"scatter\",\n      name: \"After\",\n      data: departments.map(function(d, i) {\n        return [scoreAfter[i], d, Math.abs(deltas[i])];\n      }),\n      encode: { x: 0, y: 1 },\n      symbolSize: function(data) { return 14 + 12 * (data[2] / maxAbsDelta); },\n      itemStyle: { color: tokens.palette[1] },\n      label: {\n        show: true,\n        position: \"right\",\n        formatter: function(params) {\n          var delta = deltas[params.dataIndex];\n          return (delta >= 0 ? \"+\" : \"\") + delta;\n        },\n        color: tokens.inkSoft,\n        fontSize: 12\n      },\n      z: 2\n    }\n  ]\n});\n"}