Spaces:
Running
Running
| <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> | |