{"spec_id":"bar-feature-importance","library":"d3","language":"javascript","code":"// anyplot.ai\n// bar-feature-importance: Feature Importance Bar Chart\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 110, right: 150, bottom: 80, left: 250 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Gradient-boosted churn model: feature importances, sorted descending, with\n// per-feature std across cross-validation folds (ensemble variability).\nconst features = [\n  { name: \"Monthly Charges\", importance: 0.243, std: 0.021 },\n  { name: \"Tenure (Months)\", importance: 0.198, std: 0.019 },\n  { name: \"Contract Type\", importance: 0.156, std: 0.017 },\n  { name: \"Total Charges\", importance: 0.121, std: 0.015 },\n  { name: \"Support Tickets\", importance: 0.089, std: 0.012 },\n  { name: \"Internet Service\", importance: 0.067, std: 0.011 },\n  { name: \"Payment Method\", importance: 0.048, std: 0.009 },\n  { name: \"Dependents\", importance: 0.032, std: 0.007 },\n  { name: \"Paperless Billing\", importance: 0.028, std: 0.006 },\n  { name: \"Senior Citizen\", importance: 0.018, std: 0.005 },\n];\n\n// --- SVG mount ----------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Scales ---------------------------------------------------------------\nconst y = d3\n  .scaleBand()\n  .domain(features.map((d) => d.name))\n  .range([0, ih])\n  .padding(0.28);\n\nconst maxExtent = d3.max(features, (d) => d.importance + d.std);\nconst x = d3.scaleLinear().domain([0, maxExtent]).nice().range([0, iw]);\n\nconst color = d3\n  .scaleSequential(d3.interpolateRgbBasis(t.seq))\n  .domain(d3.extent(features, (d) => d.importance));\n\n// --- Gridlines (value axis only) -------------------------------------------\ng.append(\"g\")\n  .attr(\"class\", \"grid\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).tickSize(-ih).tickFormat(\"\"))\n  .call((sel) => sel.select(\".domain\").remove())\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-opacity\", 0.5);\n\n// --- Error bars (ensemble std) -----------------------------------------------\nconst errorGroups = g\n  .selectAll(\".error-bar\")\n  .data(features)\n  .join(\"g\")\n  .attr(\"class\", \"error-bar\")\n  .attr(\"transform\", (d) => `translate(0,${y(d.name) + y.bandwidth() / 2})`);\n\nerrorGroups\n  .append(\"line\")\n  .attr(\"x1\", (d) => x(Math.max(0, d.importance - d.std)))\n  .attr(\"x2\", (d) => x(d.importance + d.std))\n  .attr(\"stroke\", t.inkSoft)\n  .attr(\"stroke-width\", 2);\n\nfor (const cap of [-1, 1]) {\n  errorGroups\n    .append(\"line\")\n    .attr(\"x1\", (d) => x(cap < 0 ? Math.max(0, d.importance - d.std) : d.importance + d.std))\n    .attr(\"x2\", (d) => x(cap < 0 ? Math.max(0, d.importance - d.std) : d.importance + d.std))\n    .attr(\"y1\", -6)\n    .attr(\"y2\", 6)\n    .attr(\"stroke\", t.inkSoft)\n    .attr(\"stroke-width\", 2);\n}\n\n// --- Bars -----------------------------------------------------------------\ng.selectAll(\".bar\")\n  .data(features)\n  .join(\"rect\")\n  .attr(\"class\", \"bar\")\n  .attr(\"x\", 0)\n  .attr(\"y\", (d) => y(d.name))\n  .attr(\"width\", (d) => x(d.importance))\n  .attr(\"height\", y.bandwidth())\n  .attr(\"fill\", (d) => color(d.importance))\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 1);\n\n// --- Value labels (importance score, right of the error cap) ----------------\ng.selectAll(\".value-label\")\n  .data(features)\n  .join(\"text\")\n  .attr(\"class\", \"value-label\")\n  .attr(\"x\", (d) => x(d.importance + d.std) + 14)\n  .attr(\"y\", (d) => y(d.name) + y.bandwidth() / 2)\n  .attr(\"dy\", \"0.35em\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"15px\")\n  .style(\"font-variant-numeric\", \"tabular-nums\")\n  .text((d) => d.importance.toFixed(3));\n\n// --- Axes -------------------------------------------------------------------\nconst xAxis = g.append(\"g\").attr(\"transform\", `translate(0,${ih})`).call(d3.axisBottom(x).ticks(6));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).tickSize(0));\n\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n  ax.selectAll(\"line\").attr(\"stroke\", t.grid);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\nyAxis.select(\".domain\").remove();\nyAxis.selectAll(\"text\").attr(\"dx\", \"-0.4em\");\n\n// --- Axis label ---------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", margin.left + iw / 2)\n  .attr(\"y\", height - 24)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"16px\")\n  .text(\"Feature Importance (Gini)\");\n\n// --- Title ------------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 48)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"bar-feature-importance · javascript · d3 · anyplot.ai\");\n"}