{"spec_id":"bar-diverging-likert","library":"echarts","language":"javascript","code":"// anyplot.ai\n// bar-diverging-likert: Likert Scale Diverging Bar Chart\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-08-24\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Employee engagement survey, 5-point Likert scale, percentages sum to 100 per question.\nconst responses = [\n  { question: \"Understand role's contribution\", sd: 3, d: 6, n: 9, a: 44, sa: 38 },\n  { question: \"Would recommend as workplace\", sd: 4, d: 7, n: 10, a: 40, sa: 39 },\n  { question: \"Receive regular feedback\", sd: 5, d: 9, n: 13, a: 41, sa: 32 },\n  { question: \"Have needed tools & resources\", sd: 6, d: 11, n: 14, a: 39, sa: 30 },\n  { question: \"See growth opportunities\", sd: 6, d: 12, n: 16, a: 38, sa: 28 },\n  { question: \"Leadership vision is clear\", sd: 7, d: 13, n: 18, a: 36, sa: 26 },\n  { question: \"Feel recognized for work\", sd: 9, d: 15, n: 17, a: 34, sa: 25 },\n  { question: \"Workload is manageable\", sd: 10, d: 17, n: 19, a: 32, sa: 22 },\n  { question: \"Cross-team collaboration works well\", sd: 18, d: 24, n: 18, a: 25, sa: 15 },\n  { question: \"Compensation is fair compared to market\", sd: 48, d: 30, n: 6, a: 10, sa: 6 },\n];\n\n// Sort by net agreement (agree + strongly_agree - disagree - strongly_disagree), best first.\nresponses.sort((r1, r2) => (r2.a + r2.sa - r2.d - r2.sd) - (r1.a + r1.sa - r1.d - r1.sd));\n\nconst questions = responses.map((r) => r.question);\n\n// Sentiment diverging scheme (Imprint semantic exception): red = disagreement,\n// blue = agreement, muted = neutral — avoids a red/green pairing that would be\n// ambiguous for deuteranopia/protanopia readers, per the spec's own guidance.\nconst RED = t.palette[4];\nconst BLUE = t.palette[2];\nconst MUTED = t.inkSoft; // theme-adaptive muted gray — no dedicated \"muted\" token in the JS harness\n\nconst maxExtent = Math.max(\n  ...responses.map((r) => r.sd + r.d + r.n / 2),\n  ...responses.map((r) => r.sa + r.a + r.n / 2)\n);\n// Round up to a multiple of 30 so the axis renders 3 evenly-spaced ticks per side.\nconst axisExtent = Math.ceil(maxExtent / 30) * 30;\n\nconst insideLabel = (color) => ({\n  show: true,\n  position: \"inside\",\n  color,\n  fontSize: 13,\n  formatter: (p) => (Math.abs(p.value) >= 6 ? `${Math.round(Math.abs(p.value))}%` : \"\"),\n});\n\nconst series = [\n  {\n    name: \"Neutral\",\n    type: \"bar\",\n    stack: \"likert\",\n    data: responses.map((r) => -r.n / 2),\n    itemStyle: { color: MUTED },\n    label: { show: false },\n  },\n  {\n    name: \"Neutral\",\n    type: \"bar\",\n    stack: \"likert\",\n    data: responses.map((r) => r.n / 2),\n    itemStyle: { color: MUTED },\n    label: {\n      show: true,\n      position: \"inside\",\n      color: t.pageBg,\n      fontSize: 13,\n      formatter: (p) => (responses[p.dataIndex].n >= 6 ? `${responses[p.dataIndex].n}%` : \"\"),\n    },\n    markLine: {\n      silent: true,\n      symbol: \"none\",\n      lineStyle: { color: t.inkSoft, width: 1 },\n      label: { show: false },\n      data: [{ xAxis: 0 }],\n    },\n  },\n  {\n    name: \"Disagree\",\n    type: \"bar\",\n    stack: \"likert\",\n    data: responses.map((r) => -r.d),\n    itemStyle: { color: RED, opacity: 0.55 },\n    label: insideLabel(t.ink),\n  },\n  {\n    name: \"Agree\",\n    type: \"bar\",\n    stack: \"likert\",\n    data: responses.map((r) => r.a),\n    itemStyle: { color: BLUE, opacity: 0.55 },\n    label: insideLabel(t.ink),\n  },\n  {\n    name: \"Strongly Disagree\",\n    type: \"bar\",\n    stack: \"likert\",\n    data: responses.map((r) => -r.sd),\n    itemStyle: { color: RED },\n    label: insideLabel(\"#FFFFFF\"),\n  },\n  {\n    name: \"Strongly Agree\",\n    type: \"bar\",\n    stack: \"likert\",\n    data: responses.map((r) => r.sa),\n    itemStyle: { color: BLUE },\n    label: insideLabel(\"#FFFFFF\"),\n  },\n];\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option --------------------------------------------------------------------\nconst title = \"Employee Engagement Survey · bar-diverging-likert · javascript · echarts · anyplot.ai\";\nconst titleFontSize = Math.max(14, Math.round(22 * Math.min(1, 67 / title.length)));\n\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: title,\n    left: \"center\",\n    top: 20,\n    textStyle: { color: t.ink, fontSize: titleFontSize, fontWeight: 500 },\n  },\n  legend: {\n    data: [\"Strongly Disagree\", \"Disagree\", \"Neutral\", \"Agree\", \"Strongly Agree\"],\n    top: 70,\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n    itemWidth: 16,\n    itemHeight: 12,\n    selectedMode: false,\n    icon: \"roundRect\",\n  },\n  grid: { left: 440, right: 60, top: 120, bottom: 70, containLabel: false },\n  xAxis: {\n    type: \"value\",\n    min: -axisExtent,\n    max: axisExtent,\n    interval: axisExtent / 3,\n    axisLabel: { color: t.inkSoft, fontSize: 14, formatter: (v) => `${Math.round(Math.abs(v))}%` },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"category\",\n    inverse: true,\n    data: questions,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  series,\n});\n"}