{"spec_id":"bar-pareto","library":"d3","language":"javascript","code":"// anyplot.ai\n// bar-pareto: Pareto Chart with Cumulative Line\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 89/100 | Created: 2026-06-20\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Data: e-commerce complaint categories, sorted descending by count ---\nconst raw = [\n  { category: \"Late Delivery\",   count: 312 },\n  { category: \"Wrong Item\",      count: 198 },\n  { category: \"Damaged Package\", count: 143 },\n  { category: \"Poor Quality\",    count:  97 },\n  { category: \"Missing Parts\",   count:  74 },\n  { category: \"Billing Error\",   count:  52 },\n  { category: \"Rude Service\",    count:  31 },\n  { category: \"Other\",           count:  18 },\n];\n\n// Compute cumulative percentage (data already sorted descending)\nconst total = d3.sum(raw, d => d.count);\nlet cum = 0;\nconst data = raw.map(d => {\n  cum += d.count;\n  return { category: d.category, count: d.count, cumPct: (cum / total) * 100 };\n});\n\n// --- Layout ---\nconst margin = { top: 90, right: 120, bottom: 100, left: 90 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- SVG ---\nconst svg = d3.select(\"#container\").append(\"svg\")\n  .attr(\"width\", width).attr(\"height\", height);\n\nconst g = svg.append(\"g\")\n  .attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Scales ---\nconst x = d3.scaleBand()\n  .domain(data.map(d => d.category))\n  .range([0, iw])\n  .padding(0.28);\n\nconst maxCount = d3.max(data, d => d.count);\nconst y = d3.scaleLinear()\n  .domain([0, maxCount * 1.08]).nice()\n  .range([ih, 0]);\n\nconst y2 = d3.scaleLinear()\n  .domain([0, 100])\n  .range([ih, 0]);\n\n// --- Subtle horizontal gridlines ---\ny.ticks(6).forEach(tick => {\n  g.append(\"line\")\n    .attr(\"x1\", 0).attr(\"x2\", iw)\n    .attr(\"y1\", y(tick)).attr(\"y2\", y(tick))\n    .attr(\"stroke\", t.grid)\n    .attr(\"stroke-opacity\", 0.15);\n});\n\n// --- Bars (descending, Imprint palette position 1) ---\ng.selectAll(\".bar\").data(data).join(\"rect\")\n  .attr(\"class\", \"bar\")\n  .attr(\"x\", d => x(d.category))\n  .attr(\"y\", d => y(d.count))\n  .attr(\"width\", x.bandwidth())\n  .attr(\"height\", d => ih - y(d.count))\n  .attr(\"fill\", t.palette[0]);\n\n// --- 80% threshold reference line (amber, dashed) ---\ng.append(\"line\")\n  .attr(\"x1\", 0).attr(\"x2\", iw)\n  .attr(\"y1\", y2(80)).attr(\"y2\", y2(80))\n  .attr(\"stroke\", t.amber)\n  .attr(\"stroke-width\", 2)\n  .attr(\"stroke-dasharray\", \"10,5\");\n\ng.append(\"text\")\n  .attr(\"x\", iw - 6)\n  .attr(\"y\", y2(80) - 7)\n  .attr(\"text-anchor\", \"end\")\n  .attr(\"fill\", t.amber)\n  .style(\"font-size\", \"13px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"80% threshold\");\n\n// --- Cumulative percentage line (Imprint palette position 3, blue) ---\nconst lineGen = d3.line()\n  .x(d => x(d.category) + x.bandwidth() / 2)\n  .y(d => y2(d.cumPct));\n\ng.append(\"path\").datum(data)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[2])\n  .attr(\"stroke-width\", 3)\n  .attr(\"d\", lineGen);\n\n// Markers at center-top of each bar on the cumulative line\ng.selectAll(\".dot\").data(data).join(\"circle\")\n  .attr(\"class\", \"dot\")\n  .attr(\"cx\", d => x(d.category) + x.bandwidth() / 2)\n  .attr(\"cy\", d => y2(d.cumPct))\n  .attr(\"r\", 6)\n  .attr(\"fill\", t.palette[2])\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 2);\n\n// --- Left y-axis (raw count) ---\nconst leftAx = g.append(\"g\").call(\n  d3.axisLeft(y).ticks(6).tickFormat(d3.format(\",\")).tickSize(4)\n);\nleftAx.select(\".domain\").attr(\"stroke\", t.inkSoft);\nleftAx.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\nleftAx.selectAll(\".tick text\")\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n\n// --- Right y-axis (cumulative %) ---\nconst rightAx = g.append(\"g\")\n  .attr(\"transform\", `translate(${iw},0)`)\n  .call(d3.axisRight(y2).ticks(5).tickFormat(d => `${d}%`).tickSize(4));\nrightAx.select(\".domain\").attr(\"stroke\", t.inkSoft);\nrightAx.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\nrightAx.selectAll(\".tick text\")\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n\n// --- Bottom x-axis (categories) ---\nconst bottomAx = g.append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).tickSize(0));\nbottomAx.select(\".domain\").attr(\"stroke\", t.inkSoft);\nbottomAx.selectAll(\".tick text\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"14px\")\n  .attr(\"dy\", \"1.4em\");\n\n// --- Axis labels ---\n// Left y-axis label (rotate -90 so text reads bottom-to-top)\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -66)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"15px\")\n  .text(\"Complaint Count\");\n\n// Right y-axis label (same rotation, positioned to the right)\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", iw + 85)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"15px\")\n  .text(\"Cumulative %\");\n\n// Bottom x-axis label\ng.append(\"text\")\n  .attr(\"x\", iw / 2)\n  .attr(\"y\", ih + 70)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"15px\")\n  .text(\"Complaint Category\");\n\n// --- Legend (top-right of inner area) ---\nconst lx = iw - 192;\nconst ly = 14;\n\n// Bars series\ng.append(\"rect\")\n  .attr(\"x\", lx).attr(\"y\", ly)\n  .attr(\"width\", 16).attr(\"height\", 14)\n  .attr(\"fill\", t.palette[0]);\ng.append(\"text\")\n  .attr(\"x\", lx + 24).attr(\"y\", ly + 12)\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"13px\")\n  .text(\"Complaint Count\");\n\n// Cumulative line series\ng.append(\"line\")\n  .attr(\"x1\", lx).attr(\"x2\", lx + 16)\n  .attr(\"y1\", ly + 32).attr(\"y2\", ly + 32)\n  .attr(\"stroke\", t.palette[2]).attr(\"stroke-width\", 3);\ng.append(\"circle\")\n  .attr(\"cx\", lx + 8).attr(\"cy\", ly + 32)\n  .attr(\"r\", 4)\n  .attr(\"fill\", t.palette[2])\n  .attr(\"stroke\", t.pageBg).attr(\"stroke-width\", 2);\ng.append(\"text\")\n  .attr(\"x\", lx + 24).attr(\"y\", ly + 36)\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"13px\")\n  .text(\"Cumulative %\");\n\n// 80% threshold series\ng.append(\"line\")\n  .attr(\"x1\", lx).attr(\"x2\", lx + 16)\n  .attr(\"y1\", ly + 54).attr(\"y2\", ly + 54)\n  .attr(\"stroke\", t.amber).attr(\"stroke-width\", 2)\n  .attr(\"stroke-dasharray\", \"6,3\");\ng.append(\"text\")\n  .attr(\"x\", lx + 24).attr(\"y\", ly + 58)\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"13px\")\n  .text(\"80% threshold\");\n\n// --- Title ---\nsvg.append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 50)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"bar-pareto · javascript · d3 · anyplot.ai\");\n"}