viz: support collapsing individual CALL nodes (#15006)

* all

* contracted all by default

* simple call mask

* work

* minus not hyphen

* color / cleanup

* detail
This commit is contained in:
qazal
2026-02-25 21:27:25 +09:00
committed by GitHub
parent 68831cd852
commit c58e91942c
3 changed files with 28 additions and 11 deletions
+8
View File
@@ -102,6 +102,14 @@
fill: #FFD700;
stroke: #B8860B;
}
g.tag.collapsed circle {
fill: #5CD68D;
stroke: #4a4b57;
}
g.tag.expanded circle {
fill: #9FDDE6;
stroke: #4a4b57;
}
g.port circle {
fill: #b3dcc2;
}
+17 -9
View File
@@ -50,18 +50,23 @@ function intersectRect(r1, r2) {
return {x:r1.x+dx*scale, y:r1.y+dy*scale};
}
function addTags(root) {
function addTags(root, dy) {
root.selectAll("circle").data(d => [d]).join("circle").attr("r", 5);
root.selectAll("text").data(d => [d]).join("text").text(d => d).attr("dy", "0.35em");
root.selectAll("text").data(d => [d]).join("text").text(d => d).attr("dy", `${dy ?? 0.35}em`);
}
const drawGraph = (data) => {
const g = dagre.graphlib.json.read(data);
// draw nodes
d3.select("#graph-svg").on("click", () => d3.selectAll(".highlight").classed("highlight", false));
const callCount = g.nodes().filter(n => g.node(n).label.startsWith("CALL\n")).length;
const nodes = d3.select("#nodes").selectAll("g").data(g.nodes().map(id => g.node(id)), d => d).join("g").attr("class", d => d.className ?? "node")
.attr("transform", d => `translate(${d.x},${d.y})`).classed("clickable", d => d.ref != null).on("click", (e,d) => {
if (d.ref != null) return switchCtx(d.ref);
.attr("transform", d => `translate(${d.x},${d.y})`).on("click", (e,d) => {
if (d.label.startsWith("CALL")) {
if (state.callSrcMask.has(d.id)) state.callSrcMask.delete(d.id); else state.callSrcMask.add(d.id);
if (state.callSrcMask.size >= callCount) { showCallSrc.toggle.checked = !showCallSrc.toggle.checked; state.callSrcMask.clear(); }
return setState({});
}
const parents = g.predecessors(d.id);
const children = g.successors(d.id);
if (parents == null && children == null) return;
@@ -105,6 +110,9 @@ const drawGraph = (data) => {
});
addTags(nodes.selectAll("g.tag").data(d => d.tag != null ? [d] : []).join("g").attr("class", "tag")
.attr("transform", d => `translate(${-d.width/2+8}, ${-d.height/2+8})`).datum(e => e.tag));
addTags(nodes.selectAll("g.type").data(d => d.label.startsWith("CALL\n") ? [d] : []).join("g")
.attr("class", d => `tag ${d.collapsed ? 'collapsed' : 'expanded'}`)
.attr("transform", d => `translate(${-d.width/2}, ${0})`).datum(d => d.collapsed ? "+" : ""), 0.25);
// draw edges
const line = d3.line().x(d => d.x).y(d => d.y).curve(d3.curveBasis), edges = g.edges();
d3.select("#edges").selectAll("path.edgePath").data(edges).join("path").attr("class", "edgePath").attr("d", (e) => {
@@ -707,7 +715,7 @@ const evtSources = [];
// rewrite: a single UOp transformation
// step: collection of rewrites
// context: collection of steps
const state = {currentCtx:-1, currentStep:0, currentRewrite:0, expandSteps:false};
const state = {currentCtx:-1, currentStep:0, currentRewrite:0, expandSteps:false, callSrcMask:new Set()};
function setState(ns) {
saveToHistory(state);
const { ctx:prevCtx, step:prevStep } = select(state.currentCtx, state.currentStep);
@@ -755,7 +763,7 @@ const createToggle = (id, text) => {
return { toggle, label };
}
const showIndexing = createToggle("show-indexing", "Show indexing (r)");
const showCallSrc = createToggle("show-call-src", "Show CALL src (c)");
const showCallSrc = createToggle("show-call-src", "Show all CALL src (c)"); showCallSrc.toggle.checked = false;
const showSink = createToggle("show-sink", "Show SINK (s)");
showSink.toggle.checked = false;
const showGraph = createToggle("show-graph", "Show graph (g)");
@@ -907,10 +915,10 @@ async function main() {
// ** center graph
const data = ret[currentRewrite];
const render = (opts) => renderDag({ data, opts }, { recenter:currentRewrite === 0 });
const getOpts = () => ({ showIndexing:showIndexing.toggle.checked, showCallSrc:showCallSrc.toggle.checked, showSink:showSink.toggle.checked });
const getOpts = () => ({ showIndexing:showIndexing.toggle.checked, showCallSrc:showCallSrc.toggle.checked, showSink:showSink.toggle.checked, callSrcMask:state.callSrcMask });
render(getOpts());
showIndexing.toggle.onchange = () => render(getOpts());
showCallSrc.toggle.onchange = () => render(getOpts());
showCallSrc.toggle.onchange = () => { state.callSrcMask.clear(); render(getOpts()); }
showSink.toggle.onchange = () => render(getOpts());
// ** right sidebar metadata
metadata.innerHTML = "";
@@ -942,7 +950,7 @@ async function main() {
metadata.appendChild(codeBlock(upat[1], "python", { loc:upat[0], wrap:true }));
const diffCode = metadata.appendChild(document.createElement("pre")).appendChild(document.createElement("code"));
for (const line of diff) {
diffCode.appendChild(colored([{st:line, color:line.startsWith("+") ? "#3aa56d" : line.startsWith("-") ? "#d14b4b" : "#f0f0f5"}]));
diffCode.appendChild(colored([{st:line, color:line.startsWith("+") ? "#3aa56d" : line.startsWith("") ? "#d14b4b" : "#f0f0f5"}]));
diffCode.appendChild(document.createElement("br"));
}
diffCode.className = "wrap";
+3 -2
View File
@@ -73,12 +73,13 @@ const layoutUOp = (g, { graph, change }, opts) => {
if (node.label.includes("dtypes.index")) g.removeNode(n);
}
}
if (!opts.showCallSrc) {
if (!opts.showCallSrc || opts.callSrcMask.size > 0) {
// remove edges from src[0] to CALL nodes, track affected nodes
const disconnected = new Set();
for (const n of g.nodes()) {
const node = g.node(n);
if (node.label.startsWith("CALL\n")) {
if (node.label.startsWith("CALL\n") && (opts.showCallSrc ? opts.callSrcMask.has(n) : !opts.callSrcMask.has(n))) {
node.collapsed = true;
for (const pred of (g.predecessors(n) || [])) {
const edge = g.edge(pred, n);
if (edge?.label?.text === 0) {