{"spec_id":"line-impurity-comparison","library":"d3","language":"javascript","code":"// anyplot.ai\n// line-impurity-comparison: Gini Impurity vs Entropy Comparison\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 94/100 | Created: 2026-08-26\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 150, right: 60, bottom: 90, left: 90 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Gini impurity: 2p(1-p). Entropy (log2, normalized): -p*log2(p) - (1-p)*log2(1-p).\nconst gini = (p) => 2 * p * (1 - p);\nconst entropy = (p) => (p === 0 || p === 1 ? 0 : -p * Math.log2(p) - (1 - p) * Math.log2(1 - p));\n\nconst probabilities = d3.range(0, 101).map((i) => i / 100);\nconst giniCurve = probabilities.map((p) => ({ p, value: gini(p) }));\nconst entropyCurve = probabilities.map((p) => ({ p, value: entropy(p) }));\n// Entropy >= Gini across the whole domain, so a single area band between the\n// two curves is well-defined (no crossing) and reads as \"how closely they track\".\nconst trackBand = probabilities.map((p) => ({ p, lo: gini(p), hi: entropy(p) }));\n\nconst muted = window.ANYPLOT_THEME === \"dark\" ? \"#A8A79F\" : \"#6B6A63\";\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 x = d3.scaleLinear().domain([0, 1]).range([0, iw]);\nconst y = d3.scaleLinear().domain([0, 1]).nice().range([ih, 0]);\n\n// --- Grid (light, y-axis only) ---------------------------------------------\ng.append(\"g\")\n  .attr(\"class\", \"grid\")\n  .call(d3.axisLeft(y).tickSize(-iw).tickFormat(\"\"))\n  .call((sel) => sel.select(\".domain\").remove())\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid);\n\n// --- Axes -------------------------------------------------------------------\nconst xAxis = g\n  .append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(6));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).ticks(6));\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}\n\n// --- Axis labels --------------------------------------------------------------\ng.append(\"text\")\n  .attr(\"x\", iw / 2)\n  .attr(\"y\", ih + 60)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Probability p\");\n\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -62)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Impurity Measure\");\n\n// --- Track band: fill between the curves (D3-distinctive d3.area) -------------\n// Emphasizes the spec's core insight — Gini and Entropy track closely across\n// the whole range, not just at their shared maximum.\nconst band = d3\n  .area()\n  .x((d) => x(d.p))\n  .y0((d) => y(d.lo))\n  .y1((d) => y(d.hi));\n\ng.append(\"path\").datum(trackBand).attr(\"fill\", muted).attr(\"opacity\", 0.14).attr(\"d\", band);\n\n// --- Lines --------------------------------------------------------------------\nconst line = d3\n  .line()\n  .x((d) => x(d.p))\n  .y((d) => y(d.value));\n\ng.append(\"path\")\n  .datum(giniCurve)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 4)\n  .attr(\"d\", line);\n\ng.append(\"path\")\n  .datum(entropyCurve)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[1])\n  .attr(\"stroke-width\", 4)\n  .attr(\"stroke-dasharray\", \"10,6\")\n  .attr(\"d\", line);\n\n// --- Annotation: shared maximum at p=0.5 (spec-requested) ---------------------\nconst guideX = x(0.5);\ng.append(\"line\")\n  .attr(\"x1\", guideX)\n  .attr(\"x2\", guideX)\n  .attr(\"y1\", y(0))\n  .attr(\"y2\", y(1))\n  .attr(\"stroke\", t.inkSoft)\n  .attr(\"stroke-width\", 1.5)\n  .attr(\"stroke-dasharray\", \"4,4\")\n  .attr(\"opacity\", 0.6);\n\ng.append(\"circle\").attr(\"cx\", guideX).attr(\"cy\", y(gini(0.5))).attr(\"r\", 7).attr(\"fill\", t.palette[0]);\ng.append(\"circle\").attr(\"cx\", guideX).attr(\"cy\", y(entropy(0.5))).attr(\"r\", 7).attr(\"fill\", t.palette[1]);\n\n// Label sits in the open gap between the two curves, clear of both lines.\n// Two-tier typography: bold value line, lighter-weight caption line.\ng.append(\"text\")\n  .attr(\"x\", guideX + 18)\n  .attr(\"y\", y((gini(0.5) + entropy(0.5)) / 2))\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"14px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"p = 0.5\")\n  .append(\"tspan\")\n  .attr(\"x\", guideX + 18)\n  .attr(\"dy\", 20)\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-weight\", \"400\")\n  .text(\"shared maximum\");\n\n// --- Legend (with formulas) ----------------------------------------------------\nconst legend = svg\n  .append(\"g\")\n  .attr(\"transform\", `translate(${margin.left + 20},${margin.top - 78})`);\n\nconst legendRows = [\n  { name: \"Gini impurity\", formula: \"2p(1 − p)\", color: t.palette[0], dash: null },\n  { name: \"Entropy\", formula: \"−p·log₂p − (1 − p)·log₂(1 − p)\", color: t.palette[1], dash: \"10,6\" },\n];\n\n// Two-tier typography: bold metric name, lighter-weight formula beneath it —\n// gives the legend a clearer hierarchy than a single undifferentiated line.\nlegendRows.forEach((d, i) => {\n  const row = legend.append(\"g\").attr(\"transform\", `translate(0,${i * 36})`);\n  row\n    .append(\"line\")\n    .attr(\"x1\", 0)\n    .attr(\"x2\", 32)\n    .attr(\"y1\", -4)\n    .attr(\"y2\", -4)\n    .attr(\"stroke\", d.color)\n    .attr(\"stroke-width\", 4)\n    .attr(\"stroke-dasharray\", d.dash);\n  row\n    .append(\"text\")\n    .attr(\"x\", 42)\n    .attr(\"y\", 0)\n    .attr(\"fill\", t.ink)\n    .style(\"font-size\", \"15px\")\n    .style(\"font-weight\", \"600\")\n    .style(\"letter-spacing\", \"0.2px\")\n    .text(d.name);\n  row\n    .append(\"text\")\n    .attr(\"x\", 42)\n    .attr(\"y\", 19)\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"14px\")\n    .style(\"font-weight\", \"400\")\n    .text(d.formula);\n});\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  .style(\"letter-spacing\", \"0.4px\")\n  .text(\"line-impurity-comparison · javascript · d3 · anyplot.ai\");\n"}