robot-folding / app /src /content /embeds /folding /dagger-explainer.html
pepijn223's picture
pepijn223 HF Staff
Improve DAgger explainer, add conclusion and expand references
f0f3d44 unverified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: transparent; font-family: system-ui, -apple-system, sans-serif; }
.dagger-wrap { position: relative; width: 100%; }
.dagger-desc {
text-align: center; font-size: 14px; font-weight: 500;
min-height: 36px; padding: 2px 20px; line-height: 1.6;
transition: color 0.3s;
}
</style>
</head>
<body>
<div class="dagger-wrap">
<svg id="dagger-svg"></svg>
<div class="dagger-desc" id="dagger-desc"></div>
</div>
<script>
function _initDAgger() {
var container = document.getElementById("dagger-svg").parentElement;
var svg = d3.select("#dagger-svg");
var descEl = document.getElementById("dagger-desc");
function isLight() {
try {
var el = document.querySelector("[data-theme]") || document.documentElement;
return el.getAttribute("data-theme") === "light";
} catch(e) { return false; }
}
var DARK = {
text: "#e8eaf0", subtext: "#8b8fa8", dim: "#333", dimArrow: "#3a3d50",
nodeBg: "#1a1d2e", glowAlpha: 0.22, barBg: "#252540", barFill: "#10b981",
};
var LIGHT = {
text: "#1a1a2e", subtext: "#6b7280", dim: "#d1d5db", dimArrow: "#bfc3cc",
nodeBg: "#f9fafb", glowAlpha: 0.14, barBg: "#e5e7eb", barFill: "#10b981",
};
function th() { return isLight() ? LIGHT : DARK; }
var stages = [
{
id: "policy", label: "Policy runs",
dark: "#22d3ee", light: "#0891b2",
desc: "The learned policy executes autonomously on a new task attempt",
},
{
id: "failure", label: "Failure",
dark: "#ef4444", light: "#dc2626",
desc: "Policy drifts into unfamiliar states. Traditional cloning suffers T\u00B2\u03B5 compounding errors.",
},
{
id: "human", label: "Human takeover",
dark: "#f59e0b", light: "#d97706",
desc: "Operator pauses the policy and teleoperates the correct recovery",
},
{
id: "record", label: "Record correction",
dark: "#10b981", light: "#059669",
desc: "Correction trajectory added to the aggregate dataset: D \u2190 D \u222A D\u1D62",
},
{
id: "retrain", label: "Retrain",
dark: "#8b5cf6", light: "#7c3aed",
desc: "Policy retrained on all data. Each iteration closes the distribution gap.",
},
];
var N = stages.length;
var activeIdx = 0;
var iteration = 1;
var animTimer = null;
function col(i) { return isLight() ? stages[i].light : stages[i].dark; }
function drawIcon(g, id, cx, cy, size, fill) {
var s = size;
if (id === "policy") {
g.append("polygon")
.attr("points", (cx-s*0.22)+","+(cy-s*0.42)+" "+(cx+s*0.48)+","+cy+" "+(cx-s*0.22)+","+(cy+s*0.42))
.attr("fill", fill);
} else if (id === "failure") {
g.append("rect")
.attr("x", cx-1.6).attr("y", cy-s*0.34).attr("width", 3.2).attr("height", s*0.4)
.attr("rx", 1.6).attr("fill", fill);
g.append("circle").attr("cx", cx).attr("cy", cy+s*0.26).attr("r", 2).attr("fill", fill);
} else if (id === "human") {
g.append("circle").attr("cx", cx).attr("cy", cy-s*0.2).attr("r", s*0.16).attr("fill", fill);
g.append("path")
.attr("d", "M"+(cx-s*0.32)+","+(cy+s*0.38)+" Q"+(cx-s*0.32)+","+(cy+s*0.06)+" "+cx+","+(cy+s*0.06)+" Q"+(cx+s*0.32)+","+(cy+s*0.06)+" "+(cx+s*0.32)+","+(cy+s*0.38))
.attr("fill", fill);
} else if (id === "record") {
var lw = 2.6;
g.append("line").attr("x1", cx-s*0.28).attr("y1", cy).attr("x2", cx+s*0.28).attr("y2", cy)
.attr("stroke", fill).attr("stroke-width", lw).attr("stroke-linecap", "round");
g.append("line").attr("x1", cx).attr("y1", cy-s*0.28).attr("x2", cx).attr("y2", cy+s*0.28)
.attr("stroke", fill).attr("stroke-width", lw).attr("stroke-linecap", "round");
} else if (id === "retrain") {
var rr = s * 0.3;
var sa = -Math.PI * 0.65, ea = Math.PI * 0.8;
g.append("path")
.attr("d", "M"+(cx+rr*Math.cos(sa))+","+(cy+rr*Math.sin(sa))+" A"+rr+","+rr+" 0 1,1 "+(cx+rr*Math.cos(ea))+","+(cy+rr*Math.sin(ea)))
.attr("fill", "none").attr("stroke", fill).attr("stroke-width", 2.4)
.attr("stroke-linecap", "round");
var tx = cx + rr * Math.cos(ea), ty = cy + rr * Math.sin(ea);
var tang = ea + Math.PI / 2;
var as = 4.5;
g.append("polygon")
.attr("points",
(tx + as * Math.cos(tang - 0.6))+","+(ty + as * Math.sin(tang - 0.6))+" "+
tx+","+ty+" "+
(tx + as * Math.cos(tang + 0.6))+","+(ty + as * Math.sin(tang + 0.6)))
.attr("fill", fill);
}
}
function render() {
svg.selectAll("*").remove();
var c = th();
var W = container.clientWidth || 500;
var padX = 74;
var R = Math.min((W - padX * 2) * 0.42, 118);
var nodeR = Math.min(26, R * 0.24);
var labelH = nodeR + 14 + 8;
var cx = W / 2, cy = R + labelH + 6;
var H = cy + R + labelH + 10;
svg.attr("width", W).attr("height", H);
var g = svg.append("g");
var defs = svg.append("defs");
var angleStart = -Math.PI / 2;
var positions = stages.map(function(s, i) {
var a = angleStart + (2 * Math.PI * i) / N;
return { x: cx + R * Math.cos(a), y: cy + R * Math.sin(a), angle: a };
});
for (var i = 0; i < N; i++) {
var isAct = i === activeIdx;
var ac = isAct ? col(i) : c.dimArrow;
defs.append("marker").attr("id", "da-" + i)
.attr("viewBox", "0 0 10 8").attr("refX", 9).attr("refY", 4)
.attr("markerWidth", 6).attr("markerHeight", 5).attr("orient", "auto")
.append("polygon").attr("points", "0,0 10,4 0,8").attr("fill", ac);
}
for (var i = 0; i < N; i++) {
var from = positions[i], to = positions[(i + 1) % N];
var isAct = i === activeIdx;
var dx = to.x - from.x, dy = to.y - from.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var ux = dx / dist, uy = dy / dist;
var gap = nodeR + 6;
var x1 = from.x + ux * gap, y1 = from.y + uy * gap;
var x2 = to.x - ux * (gap + 4), y2 = to.y - uy * (gap + 4);
var mx = (x1 + x2) / 2, my = (y1 + y2) / 2;
var bow = dist * 0.1;
var pmx = (from.x + to.x) / 2 - cx, pmy = (from.y + to.y) / 2 - cy;
var pDist = Math.sqrt(pmx * pmx + pmy * pmy) || 1;
var cpx = mx + (pmx / pDist) * bow, cpy = my + (pmy / pDist) * bow;
g.append("path")
.attr("d", "M"+x1+","+y1+" Q"+cpx+","+cpy+" "+x2+","+y2)
.attr("fill", "none")
.attr("stroke", isAct ? col(i) : c.dimArrow)
.attr("stroke-width", isAct ? 2.2 : 1.4)
.attr("marker-end", "url(#da-" + i + ")")
.attr("opacity", isAct ? 1 : 0.35);
}
positions.forEach(function(pos, i) {
var s = stages[i];
var isAct = i === activeIdx;
var sc = col(i);
if (isAct) {
g.append("circle")
.attr("cx", pos.x).attr("cy", pos.y).attr("r", nodeR + 6)
.attr("fill", sc).attr("opacity", c.glowAlpha);
}
g.append("circle")
.attr("cx", pos.x).attr("cy", pos.y).attr("r", nodeR)
.attr("fill", isAct ? sc : c.nodeBg)
.attr("stroke", isAct ? sc : c.dim)
.attr("stroke-width", isAct ? 2 : 1.4)
.attr("opacity", isAct ? 1 : 0.55);
var iconG = g.append("g").attr("opacity", isAct ? 1 : 0.5);
drawIcon(iconG, s.id, pos.x, pos.y, nodeR * 0.8, isAct ? "#fff" : sc);
var a = pos.angle;
var cosA = Math.cos(a), sinA = Math.sin(a);
var labelDist = nodeR + 14;
var lx = pos.x + labelDist * cosA;
var ly = pos.y + labelDist * sinA;
var anchor = "middle";
if (cosA < -0.3) anchor = "end";
else if (cosA > 0.3) anchor = "start";
if (sinA < -0.5) ly -= 3;
if (sinA > 0.5) ly += 5;
lx = Math.max(4, Math.min(W - 4, lx));
g.append("text")
.attr("x", lx).attr("y", ly)
.attr("text-anchor", anchor).attr("dominant-baseline", "middle")
.attr("fill", isAct ? (isLight() ? sc : "#e8eaf0") : c.subtext)
.attr("font-size", 12).attr("font-weight", isAct ? 700 : 400)
.attr("font-family", "system-ui, sans-serif")
.text(s.label);
});
g.append("text")
.attr("x", cx).attr("y", cy - 8)
.attr("text-anchor", "middle").attr("dominant-baseline", "middle")
.attr("font-size", 12).attr("font-weight", 600)
.attr("fill", c.subtext).attr("font-family", "system-ui, sans-serif")
.text("Iteration " + iteration);
var barW = R * 0.55, barH = 3.5, barY = cy + 5;
g.append("rect")
.attr("x", cx - barW / 2).attr("y", barY)
.attr("width", barW).attr("height", barH)
.attr("rx", 1.75).attr("fill", c.barBg);
var fillW = barW * Math.min(1, iteration / 6);
if (fillW > 0) {
g.append("rect")
.attr("x", cx - barW / 2).attr("y", barY)
.attr("width", fillW).attr("height", barH)
.attr("rx", 1.75).attr("fill", c.barFill).attr("opacity", 0.7);
}
g.append("text")
.attr("x", cx).attr("y", barY + barH + 10)
.attr("text-anchor", "middle").attr("dominant-baseline", "middle")
.attr("font-size", 10).attr("fill", c.subtext).attr("opacity", 0.6)
.attr("font-family", "system-ui, sans-serif")
.text("dataset size");
descEl.textContent = stages[activeIdx].desc;
descEl.style.color = col(activeIdx);
}
function step() {
activeIdx = (activeIdx + 1) % N;
if (activeIdx === 0) iteration = iteration >= 6 ? 1 : iteration + 1;
render();
}
render();
animTimer = setInterval(step, 2200);
window.addEventListener("resize", render);
svg.node().addEventListener("click", function() {
clearInterval(animTimer);
step();
animTimer = setInterval(step, 2200);
});
var obs = new MutationObserver(function() { render(); });
var themeEl = document.querySelector("[data-theme]") || document.documentElement;
obs.observe(themeEl, { attributes: true, attributeFilter: ["data-theme"] });
}
if (typeof d3 !== "undefined") { _initDAgger(); }
else { var s = document.createElement("script"); s.src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"; s.onload=_initDAgger; document.head.appendChild(s); }
</script>
</body>
</html>