// ── Confounded DAG grid ─────────────────────────────────────────────────────
// Same 64-cell XMY layout as dagGrid. Cell background shading encodes the
// count of consistent U configurations (0–64): deeper blue = more U ambiguity.
// A small dashed "U" node (top-right of each cell) marks the unobserved variable.
dagGrid2 = {
const W = 86, H = 70, GAP = 4;
const NX = [15, 58], NY = [71, 58], NM = [43, 12];
const NU = [76, 9]; // U node: small dashed circle, top-right corner
const NR = 9, NRU = 6, APAD = 10;
const LPAD = 58, SGAP = 16;
const SHEAD = 26, CHEAD = 16;
const secW = 4 * (W + GAP) - GAP;
const secH = 4 * (H + GAP) - GAP;
const totalW = LPAD + 2 * secW + SGAP + 4;
const totalH = 2 * (SHEAD + CHEAD + secH) + SGAP + 32; // extra 32 for legend
const svg = d3.create("svg")
.attr("width", totalW).attr("height", totalH)
.style("display", "block").style("overflow", "visible")
.style("font-family", "system-ui, -apple-system, sans-serif");
svg.append("defs").html(`
<marker id="dah2" viewBox="0 0 8 8" refX="6" refY="4"
markerWidth="5" markerHeight="5" orient="auto">
<path d="M0,1 L0,7 L8,4 z" fill="#374151"/>
</marker>
<marker id="dahf2" viewBox="0 0 8 8" refX="6" refY="4"
markerWidth="5" markerHeight="5" orient="auto">
<path d="M0,1 L0,7 L8,4 z" fill="#d1d5db"/>
</marker>
<marker id="dah2-s" viewBox="0 0 8 8" refX="6" refY="4"
markerWidth="5" markerHeight="5" orient="auto-start-reverse">
<path d="M0,1 L0,7 L8,4 z" fill="#374151"/>
</marker>
<marker id="dahf2-s" viewBox="0 0 8 8" refX="6" refY="4"
markerWidth="5" markerHeight="5" orient="auto-start-reverse">
<path d="M0,1 L0,7 L8,4 z" fill="#d1d5db"/>
</marker>
`);
function putArrow2(g, ax, ay, bx, by, off, faded) {
const dx = bx-ax, dy = by-ay, len = Math.sqrt(dx*dx+dy*dy)||1;
const ux = dx/len, uy = dy/len, ox = -uy*off, oy = ux*off;
g.append("line")
.attr("x1", ax+ux*APAD+ox).attr("y1", ay+uy*APAD+oy)
.attr("x2", bx-ux*APAD+ox).attr("y2", by-uy*APAD+oy)
.attr("stroke", faded ? "#d1d5db" : "#374151")
.attr("stroke-width", faded ? 1.1 : 1.4)
.attr("marker-end", `url(#${faded ? "dahf2" : "dah2"})`);
}
// Bidirectional: one line, arrowheads at both ends
function putBiArrow2(g, ax, ay, bx, by, faded) {
const dx = bx-ax, dy = by-ay, len = Math.sqrt(dx*dx+dy*dy)||1;
const ux = dx/len, uy = dy/len;
g.append("line")
.attr("x1", ax+ux*APAD).attr("y1", ay+uy*APAD)
.attr("x2", bx-ux*APAD).attr("y2", by-uy*APAD)
.attr("stroke", faded ? "#d1d5db" : "#374151")
.attr("stroke-width", faded ? 1.1 : 1.4)
.attr("marker-start", `url(#${faded ? "dahf2-s" : "dah2-s"})`)
.attr("marker-end", `url(#${faded ? "dahf2" : "dah2"})`);
}
// Interpolate white → blue-200 (#bfdbfe) based on fraction t ∈ [0,1]
function uFill(count) {
if (count === 0) return "rgba(220,38,38,0.07)";
const t = count / 64;
return `rgb(${Math.round(255+t*(191-255))},${Math.round(255+t*(219-255))},255)`;
}
function uStroke(count) {
if (count === 0) return "rgba(220,38,38,0.28)";
const t = count / 64;
return `rgb(${Math.round(255+t*(147-255))},${Math.round(255+t*(197-255))},253)`;
}
function drawCell2(g, s, count, cx, cy) {
const gc = g.append("g").attr("transform", `translate(${cx},${cy})`);
const f = count === 0;
gc.append("rect")
.attr("width", W).attr("height", H).attr("rx", 4)
.attr("fill", uFill(count)).attr("stroke", uStroke(count))
.attr("stroke-width", 0.8);
// XMY arrows
if (s.xm===1) putArrow2(gc, NX[0],NX[1], NM[0],NM[1], 0, f);
if (s.xm===2) putArrow2(gc, NM[0],NM[1], NX[0],NX[1], 0, f);
if (s.xm===3) putBiArrow2(gc, NX[0],NX[1], NM[0],NM[1], f);
if (s.my===1) putArrow2(gc, NM[0],NM[1], NY[0],NY[1], 0, f);
if (s.my===2) putArrow2(gc, NY[0],NY[1], NM[0],NM[1], 0, f);
if (s.my===3) putBiArrow2(gc, NM[0],NM[1], NY[0],NY[1], f);
if (s.xy===1) putArrow2(gc, NX[0],NX[1], NY[0],NY[1], 0, f);
if (s.xy===2) putArrow2(gc, NY[0],NY[1], NX[0],NX[1], 0, f);
if (s.xy===3) putBiArrow2(gc, NX[0],NX[1], NY[0],NY[1], f);
// XMY node circles
const ns = f ? "#d1d5db" : "#374151";
const nf = f ? "#f9fafb" : "#ffffff";
const tf = f ? "#9ca3af" : "#111827";
[[NX,"X"],[NY,"Y"],[NM,"M"]].forEach(([pos,lbl]) => {
gc.append("circle").attr("cx",pos[0]).attr("cy",pos[1]).attr("r",NR)
.attr("fill",nf).attr("stroke",ns).attr("stroke-width",1.5);
gc.append("text").attr("x",pos[0]).attr("y",pos[1]+4)
.attr("text-anchor","middle").attr("font-size",10).attr("font-weight","700")
.attr("fill",tf).text(lbl);
});
// U node — dashed, unobserved
gc.append("circle").attr("cx",NU[0]).attr("cy",NU[1]).attr("r",NRU)
.attr("fill", f ? "#f9fafb" : "#f8fafc")
.attr("stroke", f ? "#d1d5db" : "#94a3b8")
.attr("stroke-width",1.2).attr("stroke-dasharray","2.5 1.5");
gc.append("text").attr("x",NU[0]).attr("y",NU[1]+3.5)
.attr("text-anchor","middle").attr("font-size",7).attr("font-weight","600")
.attr("fill", f ? "#d1d5db" : "#94a3b8").text("U");
// U-count badge top-left
if (!f) {
gc.append("text").attr("x",3).attr("y",10)
.attr("text-anchor","start").attr("font-size",7).attr("fill","#64748b")
.text(`${count}/64`);
}
// Red cross for ruled-out
if (f) {
gc.append("line").attr("x1",5).attr("y1",5).attr("x2",W-5).attr("y2",H-5)
.attr("stroke","rgba(220,38,38,0.28)").attr("stroke-width",1.5);
gc.append("line").attr("x1",W-5).attr("y1",5).attr("x2",5).attr("y2",H-5)
.attr("stroke","rgba(220,38,38,0.28)").attr("stroke-width",1.5);
}
}
const XY_TITLES = ["X — Y (no direct link)", "X → Y", "X ← Y", "X ↔ Y"];
const XY_COLORS = ["#334155","#1d4ed8","#7c3aed","#0e7490"];
const XM_LABELS = ["X—M: —","X→M","M→X","X↔M"];
const MY_LABELS = ["—","M→Y","Y→M","M↔Y"];
[0,1,2,3].forEach((xyVal, si) => {
const sCol = si % 2, sRow = Math.floor(si/2);
const ox = LPAD + sCol*(secW+SGAP);
const oy = sRow*(SHEAD+CHEAD+secH+SGAP);
svg.append("rect").attr("x",ox).attr("y",oy).attr("width",secW).attr("height",SHEAD-2)
.attr("rx",4).attr("fill",XY_COLORS[xyVal]);
svg.append("text").attr("x",ox+secW/2).attr("y",oy+17)
.attr("text-anchor","middle").attr("fill","white")
.attr("font-size",12).attr("font-weight","700").text(XY_TITLES[xyVal]);
for (let my = 0; my < 4; my++) {
svg.append("text")
.attr("x", ox+my*(W+GAP)+W/2).attr("y", oy+SHEAD+CHEAD-3)
.attr("text-anchor","middle").attr("font-size",8.5).attr("fill","#64748b")
.text(MY_LABELS[my]);
}
for (let xm = 0; xm < 4; xm++) {
const cy = oy+SHEAD+CHEAD+xm*(H+GAP);
svg.append("text").attr("x",ox-5).attr("y",cy+H/2+4)
.attr("text-anchor","end").attr("font-size",8.5).attr("fill","#64748b")
.text(XM_LABELS[xm]);
for (let my = 0; my < 4; my++) {
const cx = ox+my*(W+GAP);
drawCell2(svg, {xm, my, xy:xyVal}, countUConfigs({xm,my,xy:xyVal}, activeTools2), cx, cy);
}
}
});
// Color legend
const legY = totalH - 20;
const legX = LPAD;
svg.append("text").attr("x",legX).attr("y",legY)
.attr("font-size",8).attr("fill","#64748b").text("U configs consistent:");
for (let i = 0; i <= 8; i++) {
const t = i/8;
svg.append("rect")
.attr("x", legX+130+i*16).attr("y",legY-10).attr("width",15).attr("height",10).attr("rx",2)
.attr("fill",`rgb(${Math.round(255+t*(191-255))},${Math.round(255+t*(219-255))},255)`);
}
svg.append("text").attr("x",legX+128).attr("y",legY)
.attr("text-anchor","end").attr("font-size",7.5).attr("fill","#94a3b8").text("0");
svg.append("text").attr("x",legX+130+9*16+2).attr("y",legY)
.attr("font-size",7.5).attr("fill","#94a3b8").text("64");
svg.append("text")
.attr("transform",`translate(8,${(totalH-32)/2}) rotate(-90)`)
.attr("text-anchor","middle").attr("font-size",10).attr("fill","#94a3b8")
.text("← XM relationship (rows within each section) →");
return svg.node();
}