{"spec_id":"coefficient-confidence","library":"d3","language":"javascript","code":"// anyplot.ai\n// coefficient-confidence: Coefficient Plot with Confidence Intervals\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-01\n\nconst t = window.ANYPLOT_TOKENS;\nconst isLight = window.ANYPLOT_THEME !== \"dark\";\nconst muted = isLight ? \"#6B6A63\" : \"#A8A79F\"; // theme-adaptive \"other/rest\" anchor\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Data: standardized coefficients from a linear regression predicting ---\n// --- house sale price, ordered by |coefficient| descending -----------------\nconst data = [\n  { variable: \"square_footage\", coefficient: 0.42, ciLower: 0.35, ciUpper: 0.49, significant: true },\n  { variable: \"bathrooms\", coefficient: 0.31, ciLower: 0.18, ciUpper: 0.44, significant: true },\n  { variable: \"crime_rate_index\", coefficient: -0.27, ciLower: -0.4, ciUpper: -0.14, significant: true },\n  { variable: \"garage_spaces\", coefficient: 0.24, ciLower: 0.11, ciUpper: 0.37, significant: true },\n  { variable: \"walkability_score\", coefficient: 0.19, ciLower: 0.06, ciUpper: 0.32, significant: true },\n  { variable: \"age_years\", coefficient: -0.18, ciLower: -0.31, ciUpper: -0.05, significant: true },\n  { variable: \"lot_size_acres\", coefficient: 0.15, ciLower: 0.02, ciUpper: 0.28, significant: true },\n  { variable: \"renovated\", coefficient: 0.12, ciLower: -0.02, ciUpper: 0.26, significant: false },\n  { variable: \"bedrooms\", coefficient: 0.09, ciLower: -0.05, ciUpper: 0.23, significant: false },\n  { variable: \"distance_to_school\", coefficient: -0.08, ciLower: -0.22, ciUpper: 0.06, significant: false },\n  { variable: \"num_floors\", coefficient: 0.06, ciLower: -0.08, ciUpper: 0.2, significant: false },\n  { variable: \"has_pool\", coefficient: 0.04, ciLower: -0.1, ciUpper: 0.18, significant: false },\n];\n\n// --- SVG mount ---------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\n\nconst margin = { top: 150, right: 90, bottom: 90, left: 250 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Scales -------------------------------------------------------------------\nconst xMin = d3.min(data, (d) => d.ciLower);\nconst xMax = d3.max(data, (d) => d.ciUpper);\nconst xPad = (xMax - xMin) * 0.1;\nconst x = d3.scaleLinear().domain([xMin - xPad, xMax + xPad]).nice().range([0, iw]);\nconst y = d3.scaleBand().domain(data.map((d) => d.variable)).range([0, ih]).padding(0.45);\n// Marker radius scales with |coefficient|, giving the strongest effects visual weight.\nconst r = d3.scaleLinear().domain(d3.extent(data, (d) => Math.abs(d.coefficient))).range([6.5, 11]);\n\n// --- \"Practically negligible\" zone band around zero -----------------------------\nconst negligible = (xMax - xMin) * 0.03;\ng.append(\"rect\")\n  .attr(\"x\", x(-negligible)).attr(\"width\", x(negligible) - x(-negligible))\n  .attr(\"y\", 0).attr(\"height\", ih)\n  .attr(\"fill\", muted).attr(\"opacity\", isLight ? 0.1 : 0.14);\n\n// --- Zero reference line -------------------------------------------------------\ng.append(\"line\")\n  .attr(\"x1\", x(0)).attr(\"x2\", x(0))\n  .attr(\"y1\", 0).attr(\"y2\", ih)\n  .attr(\"stroke\", t.inkSoft)\n  .attr(\"stroke-width\", 1.5)\n  .attr(\"stroke-dasharray\", \"6,5\");\n\n// --- Error bars (CI) + point estimates -----------------------------------------\nconst rows = g.selectAll(\".coef-row\").data(data).join(\"g\").attr(\"class\", \"coef-row\");\nconst capHalf = 8;\n\nrows.append(\"line\")\n  .attr(\"x1\", (d) => x(d.ciLower)).attr(\"x2\", (d) => x(d.ciUpper))\n  .attr(\"y1\", (d) => y(d.variable) + y.bandwidth() / 2)\n  .attr(\"y2\", (d) => y(d.variable) + y.bandwidth() / 2)\n  .attr(\"stroke\", (d) => (d.significant ? t.palette[0] : muted))\n  .attr(\"stroke-width\", 2.5)\n  .attr(\"stroke-linecap\", \"round\")\n  .attr(\"opacity\", 0.6);\n\nfor (const bound of [\"ciLower\", \"ciUpper\"]) {\n  rows.append(\"line\")\n    .attr(\"x1\", (d) => x(d[bound])).attr(\"x2\", (d) => x(d[bound]))\n    .attr(\"y1\", (d) => y(d.variable) + y.bandwidth() / 2 - capHalf)\n    .attr(\"y2\", (d) => y(d.variable) + y.bandwidth() / 2 + capHalf)\n    .attr(\"stroke\", (d) => (d.significant ? t.palette[0] : muted))\n    .attr(\"stroke-width\", 2.5)\n    .attr(\"stroke-linecap\", \"round\");\n}\n\nrows.append(\"circle\")\n  .attr(\"cx\", (d) => x(d.coefficient))\n  .attr(\"cy\", (d) => y(d.variable) + y.bandwidth() / 2)\n  .attr(\"r\", (d) => r(Math.abs(d.coefficient)))\n  .attr(\"fill\", (d) => (d.significant ? t.palette[0] : muted))\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 1.5);\n\n// --- Axes -----------------------------------------------------------------------\nconst xAxis = g.append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(7).tickFormat(d3.format(\"+.2f\")).tickSize(0).tickPadding(12));\nxAxis.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\nxAxis.select(\".domain\").attr(\"stroke\", t.inkSoft);\n\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).tickSize(0).tickPadding(14));\nyAxis.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\nyAxis.select(\".domain\").attr(\"stroke\", t.inkSoft);\n\n// --- Axis label -------------------------------------------------------------------\nsvg.append(\"text\")\n  .attr(\"x\", margin.left + iw / 2).attr(\"y\", height - 30)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"16px\")\n  .text(\"Standardized Coefficient (β) with 95% Confidence Interval\");\n\n// --- Title ----------------------------------------------------------------------\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 50)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"24px\").style(\"font-weight\", \"600\")\n  .text(\"coefficient-confidence · javascript · d3 · anyplot.ai\");\n\n// --- Legend (significant vs. not significant) ------------------------------------\nconst legend = svg.append(\"g\").attr(\"transform\", `translate(${width - 660},100)`);\nconst legendItems = [\n  { label: \"Significant (95% CI excludes 0)\", color: t.palette[0] },\n  { label: \"Not significant\", color: muted },\n];\nlegendItems.forEach((item, i) => {\n  const row = legend.append(\"g\").attr(\"transform\", `translate(${i * 280},0)`);\n  row.append(\"circle\").attr(\"r\", 7).attr(\"cy\", -5).attr(\"fill\", item.color);\n  row.append(\"text\")\n    .attr(\"x\", 16).attr(\"y\", 0)\n    .attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\")\n    .text(item.label);\n});\n\n// --- Callout: highlight the single largest driver --------------------------------\nconst top = data[0];\nconst topCy = y(top.variable) + y.bandwidth() / 2;\nconst topCx = x(top.coefficient);\ng.append(\"text\")\n  .attr(\"x\", topCx).attr(\"y\", topCy - r(Math.abs(top.coefficient)) - 12)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.palette[0]).style(\"font-size\", \"13px\").style(\"font-weight\", \"600\")\n  .text(`Largest driver: ${top.variable} (+${top.coefficient.toFixed(2)})`);\n"}