{"spec_id":"bar-diverging-likert","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// bar-diverging-likert: Likert Scale Diverging Bar Chart\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-08-24\n//# anyplot-orientation: landscape\n\nconst THEME = window.ANYPLOT_THEME;\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Employee engagement survey, 5-point Likert scale, percentages sum to 100 per\n// question. Pre-sorted by net agreement (Agree + Strongly Agree - Disagree -\n// Strongly Disagree), highest net agreement first.\nconst rows = [\n  { question: \"I'd recommend this company as a great place to work\", sd: 3, d: 7, n: 10, a: 45, sa: 35 },\n  { question: \"I understand how my role contributes to company goals\", sd: 2, d: 8, n: 15, a: 42, sa: 33 },\n  { question: \"I feel valued by my manager\", sd: 4, d: 9, n: 12, a: 40, sa: 35 },\n  { question: \"I have the resources I need to do my job well\", sd: 5, d: 12, n: 18, a: 38, sa: 27 },\n  { question: \"I see a clear path for career growth here\", sd: 8, d: 18, n: 22, a: 32, sa: 20 },\n  { question: \"I trust leadership to make the right decisions during organizational change\", sd: 25, d: 9, n: 8, a: 15, sa: 43 },\n  { question: \"I receive regular, constructive feedback\", sd: 9, d: 19, n: 24, a: 31, sa: 17 },\n  { question: \"Leadership communicates changes effectively\", sd: 12, d: 23, n: 20, a: 28, sa: 17 },\n  { question: \"I feel comfortable raising concerns to leadership\", sd: 15, d: 22, n: 18, a: 27, sa: 18 },\n  { question: \"I'm satisfied with our compensation and benefits\", sd: 18, d: 27, n: 15, a: 25, sa: 15 },\n];\n\nconst categories = rows.map((r) => r.question);\n\n// --- Diverging red-to-blue palette, muted neutral -----------------------------\nconst RED = \"#AE3030\"; // Imprint matte red — semantic anchor for disagreement\n// Precomputed opaque tint (RED blended 55% over white) — a plain hex, not an\n// alpha-blend over the mount background, so it stays pixel-identical across\n// the light (#FAF8F1) and dark (#1A1A17) themes.\nconst RED_SOFT = \"#D28D8D\";\nconst BLUE = \"#4467A3\"; // Imprint blue — agreement\nconst BLUE_SOFT = \"#98ABCC\"; // Precomputed opaque tint (BLUE blended 55% over white)\n// ANYPLOT_TOKENS has no \"muted\" key (only pageBg/elevatedBg/ink/inkSoft/grid/\n// palette/amber/seq/div) — hardcode the theme-adaptive muted anchor from\n// prompts/default-style-guide.md \"Semantic anchors\".\nconst NEUTRAL = THEME === \"dark\" ? \"#A8A79F\" : \"#6B6A63\";\n\nconst LABEL_STYLE = {\n  fontSize: \"13px\",\n  fontWeight: \"600\",\n  color: \"#FFFFFF\",\n  textOutline: \"1px contrast\",\n};\n\n// Highcharts stacks each subsequent same-signed series CLOSER to zero than the\n// one before it, so the array order (per sign) is farthest-from-zero first:\n// Strongly D/A, then Disagree/Agree, then the Neutral half last (closest to\n// the center, where the two Neutral halves meet). legendIndex keeps the\n// legend in the natural SD -> D -> Neutral -> A -> SA reading order.\nconst series = [\n  {\n    name: \"Strongly Disagree\",\n    legendIndex: 0,\n    data: rows.map((r) => ({ y: -r.sd, pct: r.sd })),\n    color: RED,\n    dataLabels: {\n      formatter: function () {\n        return Math.abs(this.y) >= 6 ? this.point.pct + \"%\" : null;\n      },\n    },\n  },\n  {\n    name: \"Strongly Agree\",\n    legendIndex: 4,\n    data: rows.map((r) => ({ y: r.sa, pct: r.sa })),\n    color: BLUE,\n    dataLabels: {\n      formatter: function () {\n        return Math.abs(this.y) >= 6 ? this.point.pct + \"%\" : null;\n      },\n    },\n  },\n  {\n    name: \"Disagree\",\n    legendIndex: 1,\n    data: rows.map((r) => ({ y: -r.d, pct: r.d })),\n    color: RED_SOFT,\n    dataLabels: {\n      formatter: function () {\n        return Math.abs(this.y) >= 6 ? this.point.pct + \"%\" : null;\n      },\n    },\n  },\n  {\n    name: \"Agree\",\n    legendIndex: 3,\n    data: rows.map((r) => ({ y: r.a, pct: r.a })),\n    color: BLUE_SOFT,\n    dataLabels: {\n      formatter: function () {\n        return Math.abs(this.y) >= 6 ? this.point.pct + \"%\" : null;\n      },\n    },\n  },\n  {\n    id: \"neutral-left\",\n    name: \"Neutral\",\n    data: rows.map((r) => ({ y: -r.n / 2, pct: r.n })),\n    color: NEUTRAL,\n    showInLegend: false,\n    dataLabels: { enabled: false },\n  },\n  {\n    id: \"neutral-right\",\n    name: \"Neutral\",\n    legendIndex: 2,\n    data: rows.map((r) => ({ y: r.n / 2, pct: r.n })),\n    color: NEUTRAL,\n    dataLabels: {\n      formatter: function () {\n        return this.point.pct >= 8 ? this.point.pct + \"%\" : null;\n      },\n    },\n  },\n];\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"bar\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  title: {\n    text: \"bar-diverging-likert · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: \"Employee engagement survey · sorted by net agreement\",\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    categories,\n    reversed: true,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    min: -100,\n    max: 100,\n    tickInterval: 25,\n    gridLineColor: t.grid,\n    title: {\n      text: \"Share of responses\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    labels: {\n      style: { color: t.inkSoft, fontSize: \"14px\" },\n      formatter: function () {\n        return Math.abs(this.value) + \"%\";\n      },\n    },\n    plotLines: [{ value: 0, width: 2, color: t.inkSoft, zIndex: 5 }],\n  },\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  tooltip: {\n    shared: true,\n    formatter: function () {\n      const rowIndex = this.points[0].point.index;\n      const r = rows[rowIndex];\n      return (\n        `<b>${r.question}</b><br/>` +\n        `Strongly Disagree: ${r.sd}%<br/>` +\n        `Disagree: ${r.d}%<br/>` +\n        `Neutral: ${r.n}%<br/>` +\n        `Agree: ${r.a}%<br/>` +\n        `Strongly Agree: ${r.sa}%`\n      );\n    },\n  },\n  plotOptions: {\n    series: {\n      animation: false,\n      stacking: \"normal\",\n      dataLabels: { enabled: true, inside: true, style: LABEL_STYLE },\n    },\n    bar: { groupPadding: 0.12, pointPadding: 0.02, borderWidth: 0, borderRadius: 2 },\n  },\n  series,\n});\n"}