swactor/docs/connectome/connectome_dashboard.html

739 lines
45 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>swactor — dependency analysis</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { background:#1a1a2e; color:#e0e0e0; font-family:system-ui,-apple-system,sans-serif; overflow:hidden; }
/* ─── Tab bar ───────────────────────────────────────────────────────────── */
.tab-bar { display:flex; align-items:center; height:42px; background:#12122a;
border-bottom:1px solid #2a2a5a; padding:0 16px; gap:8px; }
.tab-bar .title { font-size:14px; font-weight:700; letter-spacing:0.5px; margin-right:18px;
color:#8ab4f8; white-space:nowrap; }
.tab { background:none; border:none; color:#888; font-size:13px; padding:8px 16px;
cursor:pointer; border-bottom:2px solid transparent; transition:color 0.15s; }
.tab:hover { color:#ccc; }
.tab.active { color:#e0e0e0; border-bottom-color:#4fc3f7; }
/* ─── Tab content ───────────────────────────────────────────────────────── */
.tab-content { display:none; }
.tab-content.active { display:block; }
/* ─── DAG tab ───────────────────────────────────────────────────────────── */
#tab-dag { height:calc(100vh - 42px); overflow:hidden; position:relative; }
#dag-viewport { width:100%; height:100%; cursor:grab; }
#dag-viewport:active { cursor:grabbing; }
#dag-viewport svg { display:block; }
#dag-controls { position:absolute; top:12px; left:12px; z-index:10;
background:rgba(30,30,60,0.9); border-radius:8px; padding:10px 14px;
color:#ccc; font-size:13px; backdrop-filter:blur(8px); }
#dag-controls button { background:#333; color:#fff; border:1px solid #555;
border-radius:4px; padding:4px 10px; cursor:pointer; margin:0 3px; }
#dag-controls button:hover { background:#555; }
#dag-loading { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
color:#ccc; font-size:18px; }
/* ─── Spectral tab ──────────────────────────────────────────────────────── */
#tab-spectral { overflow-y:auto; max-height:calc(100vh - 42px); }
.grid { display:grid; grid-template-columns:1fr 1fr; grid-template-rows:auto auto;
gap:16px; padding:16px 20px 20px; max-width:1600px; margin:0 auto; }
.panel { background:#16213e; border-radius:10px; border:1px solid #2a2a5a;
padding:16px; position:relative; min-height:100px; }
.panel h2 { font-size:14px; font-weight:600; margin-bottom:10px; color:#8ab4f8;
display:flex; align-items:center; gap:8px; }
.panel h2 .icon { font-size:16px; }
.panel svg { width:100%; display:block; }
.tooltip { position:fixed; background:rgba(22,33,62,0.96); border:1px solid #4fc3f7;
border-radius:6px; padding:8px 12px; font-size:12px; pointer-events:none;
z-index:100; backdrop-filter:blur(8px); max-width:300px;
box-shadow:0 4px 20px rgba(0,0,0,0.4); display:none; }
.tooltip .tt-label { font-weight:600; color:#4fc3f7; }
.tooltip .tt-val { color:#e0e0e0; }
svg text { user-select:none; }
/* Metrics panel */
.metrics-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px 20px; }
.metric-item { display:flex; justify-content:space-between; font-size:12px;
padding:4px 8px; border-radius:4px; }
.metric-item:hover { background:rgba(79,195,247,0.08); }
.metric-label { opacity:0.7; }
.metric-value { font-weight:600; font-family:'SF Mono',monospace; }
.cci-box { grid-column:1/-1; text-align:center; margin-top:10px; padding:14px;
border-radius:8px; background:rgba(0,0,0,0.25); border:1px solid #333; }
.cci-score { font-size:32px; font-weight:700; }
.cci-label { font-size:14px; margin-top:2px; }
.cci-desc { font-size:11px; opacity:0.6; margin-top:4px; }
.sub-header { font-size:11px; font-weight:600; text-transform:uppercase;
letter-spacing:1px; opacity:0.4; margin:8px 0 4px; grid-column:1/-1; }
/* Heatmap */
.hm-cell { cursor:pointer; transition:opacity 0.15s; }
.hm-cell:hover { opacity:0.8; stroke:#4fc3f7; stroke-width:2; }
/* Eigenvalue bars */
.ev-bar { cursor:pointer; transition:opacity 0.15s; }
.ev-bar:hover { opacity:0.8; }
/* Fiedler bars */
.fi-bar { cursor:pointer; transition:opacity 0.15s; }
.fi-bar:hover { opacity:0.85; }
</style>
</head>
<body>
<div class="tab-bar">
<div class="title">swactor &mdash; dependency analysis</div>
<button class="tab active" data-tab="dag">Dependency DAG</button>
<button class="tab" data-tab="spectral">Spectral Analysis</button>
</div>
<div class="tab-content active" id="tab-dag">
<div id="dag-controls">
<button onclick="zoomIn()">+</button>
<button onclick="zoomOut()">&minus;</button>
<button onclick="resetView()">fit</button>
<span style="margin-left:8px;opacity:0.6">scroll to zoom &middot; drag to pan &middot; click node to focus</span>
</div>
<div id="dag-viewport"></div>
<div id="dag-loading">Loading Graphviz&hellip;</div>
</div>
<div class="tab-content" id="tab-spectral">
<div class="grid">
<div class="panel" id="panel-eigenvalues">
<h2><span class="icon">&#x03BB;</span> Laplacian Eigenvalue Spectrum</h2>
<svg id="svg-eigenvalues"></svg>
</div>
<div class="panel" id="panel-fiedler">
<h2><span class="icon">&#x2702;</span> Fiedler Vector &mdash; Spectral Bisection</h2>
<svg id="svg-fiedler"></svg>
</div>
<div class="panel" id="panel-heatmap">
<h2><span class="icon">&#x25A6;</span> Module Coupling (directed edge counts)</h2>
<svg id="svg-heatmap"></svg>
</div>
<div class="panel" id="panel-metrics">
<h2><span class="icon">&#x2211;</span> Complexity Metrics</h2>
<div id="metrics-content"></div>
</div>
</div>
</div>
<div class="tooltip" id="tooltip"></div>
<!-- ─── Script 1: synchronous — data + tab switching + spectral panels ─── -->
<script>
// ─── Data ──────────────────────────────────────────────────────────────────
const DATA = {"eigenvalues": [{"index": 0, "value": 0.0}, {"index": 1, "value": 0.0}, {"index": 2, "value": 0.18637427422819514}, {"index": 3, "value": 0.4813940269111958}, {"index": 4, "value": 0.6123548189907484}, {"index": 5, "value": 0.7985629750697533}, {"index": 6, "value": 0.8319091149970231}, {"index": 7, "value": 1.004600219615323}, {"index": 8, "value": 1.2394224070963267}, {"index": 9, "value": 1.3689639255261323}, {"index": 10, "value": 1.4526860286383532}, {"index": 11, "value": 1.626080007307936}, {"index": 12, "value": 2.321279039207482}, {"index": 13, "value": 2.3935870074779477}, {"index": 14, "value": 2.909249108581605}, {"index": 15, "value": 3.1569529438124246}, {"index": 16, "value": 3.219980753498557}, {"index": 17, "value": 3.3901681819448264}, {"index": 18, "value": 3.4799333923457128}, {"index": 19, "value": 3.605153966968332}, {"index": 20, "value": 3.8847634489335645}, {"index": 21, "value": 4.186333826694949}, {"index": 22, "value": 4.707024553452379}, {"index": 23, "value": 5.173220891347629}, {"index": 24, "value": 5.586454240023603}, {"index": 25, "value": 5.795938099946378}, {"index": 26, "value": 5.8549806331718415}, {"index": 27, "value": 6.1828765255391644}, {"index": 28, "value": 6.461944112192484}, {"index": 29, "value": 6.898584006266002}, {"index": 30, "value": 7.3807011063714905}, {"index": 31, "value": 7.896480708195232}, {"index": 32, "value": 9.160238969430825}, {"index": 33, "value": 11.171010263156152}, {"index": 34, "value": 14.04747425561517}, {"index": 35, "value": 15.533322167445291}], "fiedler": [{"name": "HybridChannel", "module": "channel", "value": -5.256955919501151e-16}, {"name": "RuntimeConfig", "module": "config", "value": -4.4166826078552935e-16}, {"name": "SenderT", "module": "runtime", "value": -2.8145783605573126e-16}, {"name": "PyRuntimeHandle", "module": "python", "value": -2.3212297109883297e-16}, {"name": "PyRuntimeConfig", "module": "python", "value": -2.172546179303562e-16}, {"name": "Placement", "module": "address_map", "value": -2.1047785816801073e-16}, {"name": "Message", "module": "actor", "value": -1.744993274089968e-16}, {"name": "PyActorAddress", "module": "python", "value": -1.3333327282927672e-16}, {"name": "PyRuntime", "module": "python", "value": -1.2067067461630528e-16}, {"name": "Envelope", "module": "runtime", "value": -1.1048855416219909e-16}, {"name": "PyActor", "module": "python", "value": -8.451175680174382e-17}, {"name": "TickContext", "module": "worker", "value": -7.635525673846673e-17}, {"name": "Sender", "module": "channel", "value": -7.037238109196825e-17}, {"name": "InboxRegistry", "module": "runtime", "value": -6.257340472605819e-17}, {"name": "Runtime", "module": "runtime", "value": -2.7901019807352287e-17}, {"name": "Receiver", "module": "channel", "value": -1.6422080940489055e-18}, {"name": "Error", "module": "error", "value": 0.0}, {"name": "Ctx", "module": "actor", "value": 2.7236604915425953e-18}, {"name": "Effect", "module": "python", "value": 1.4848475991077948e-17}, {"name": "BackoffPolicy", "module": "config", "value": 1.6667674979754847e-17}, {"name": "ActorAddress", "module": "actor", "value": 2.3690827037115515e-17}, {"name": "AddressMap", "module": "address_map", "value": 2.918795638720409e-17}, {"name": "ContextInner", "module": "actor", "value": 3.1675939003075104e-17}, {"name": "Worker", "module": "worker", "value": 4.0203070989124624e-17}, {"name": "ActorPool", "module": "worker", "value": 5.097927800469914e-17}, {"name": "WorkerContext", "module": "worker", "value": 5.607482503879278e-17}, {"name": "RuntimeHandle", "module": "runtime", "value": 7.954130131218555e-17}, {"name": "PyInbox", "module": "python", "value": 1.0987812721413363e-16}, {"name": "ActorInterface", "module": "actor", "value": 1.4176669953736474e-16}, {"name": "AnyActor", "module": "actor", "value": 1.4226827878099615e-16}, {"name": "WorkerId", "module": "address_map", "value": 1.6100142369066343e-16}, {"name": "Inbox", "module": "runtime", "value": 2.623380592723269e-16}, {"name": "PyCtx", "module": "python", "value": 2.6566833162138994e-16},
const { eigenvalues, fiedler, coupling, metrics, module_colors } = DATA;
// ─── Tab switching ─────────────────────────────────────────────────────────
document.querySelectorAll('.tab').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
btn.classList.add('active');
document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
});
});
// ─── Tooltip ───────────────────────────────────────────────────────────────
const TT = document.getElementById('tooltip');
function showTip(evt, html) {
TT.innerHTML = html;
TT.style.display = 'block';
const x = evt.clientX + 14, y = evt.clientY - 10;
TT.style.left = Math.min(x, window.innerWidth - TT.offsetWidth - 20) + 'px';
TT.style.top = Math.min(y, window.innerHeight - TT.offsetHeight - 20) + 'px';
}
function hideTip() { TT.style.display = 'none'; }
function modColor(mod) { return module_colors[mod] || '#9e9e9e'; }
// ─── Eigenvalue Spectrum ───────────────────────────────────────────────────
(function() {
const svg = document.getElementById('svg-eigenvalues');
const W = 560, H = 300, M = {t:20,r:20,b:40,l:50};
const w = W-M.l-M.r, h = H-M.t-M.b;
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
const maxVal = Math.max(...eigenvalues.map(d=>d.value), 1);
const xScale = i => M.l + (i / (eigenvalues.length-1||1)) * w;
const yScale = v => M.t + h - (v / maxVal) * h;
// Grid lines
for (let tick = 0; tick <= maxVal; tick += Math.ceil(maxVal/5)) {
const y = yScale(tick);
const line = document.createElementNS('http://www.w3.org/2000/svg','line');
Object.entries({x1:M.l,x2:W-M.r,y1:y,y2:y,stroke:'#2a2a5a','stroke-width':0.5}).forEach(([k,v])=>line.setAttribute(k,v));
svg.appendChild(line);
const txt = document.createElementNS('http://www.w3.org/2000/svg','text');
txt.setAttribute('x', M.l-6); txt.setAttribute('y', y+4);
txt.setAttribute('text-anchor','end'); txt.setAttribute('fill','#888'); txt.setAttribute('font-size','10');
txt.textContent = tick.toFixed(0);
svg.appendChild(txt);
}
// Axis labels
const xLabel = document.createElementNS('http://www.w3.org/2000/svg','text');
xLabel.setAttribute('x', M.l+w/2); xLabel.setAttribute('y', H-4);
xLabel.setAttribute('text-anchor','middle'); xLabel.setAttribute('fill','#888'); xLabel.setAttribute('font-size','11');
xLabel.textContent = 'Index';
svg.appendChild(xLabel);
const yLabel = document.createElementNS('http://www.w3.org/2000/svg','text');
yLabel.setAttribute('x', 14); yLabel.setAttribute('y', M.t+h/2);
yLabel.setAttribute('text-anchor','middle'); yLabel.setAttribute('fill','#888');
yLabel.setAttribute('font-size','11'); yLabel.setAttribute('transform', `rotate(-90,14,${M.t+h/2})`);
yLabel.textContent = 'Eigenvalue';
svg.appendChild(yLabel);
eigenvalues.forEach((d, i) => {
const x = xScale(i), y = yScale(d.value), y0 = yScale(0);
// Stem line
const line = document.createElementNS('http://www.w3.org/2000/svg','line');
Object.entries({x1:x,x2:x,y1:y0,y2:y,stroke:i===1?'#ff4444':'#4fc3f7','stroke-width':i===1?2.5:1.5,'stroke-opacity':i===1?1:0.6}).forEach(([k,v])=>line.setAttribute(k,v));
svg.appendChild(line);
// Dot
const circ = document.createElementNS('http://www.w3.org/2000/svg','circle');
circ.setAttribute('cx',x); circ.setAttribute('cy',y);
circ.setAttribute('r', i===1?6:3.5);
circ.setAttribute('fill', i===1?'#ff4444':'#4fc3f7');
circ.classList.add('ev-bar');
circ.addEventListener('mousemove', e => showTip(e,
`<span class="tt-label">&lambda;<sub>${i}</sub></span> = <span class="tt-val">${d.value.toFixed(4)}</span>`
+ (i===1 ? '<br><span style="color:#ff4444">Fiedler value (algebraic connectivity)</span>' : '')
));
circ.addEventListener('mouseleave', hideTip);
svg.appendChild(circ);
});
// Fiedler label
if (eigenvalues.length > 1) {
const lbl = document.createElementNS('http://www.w3.org/2000/svg','text');
lbl.setAttribute('x', xScale(1)+10); lbl.setAttribute('y', yScale(eigenvalues[1].value)-6);
lbl.setAttribute('fill','#ff4444'); lbl.setAttribute('font-size','11'); lbl.setAttribute('font-weight','600');
lbl.textContent = `\u03BB\u2082 = ${metrics.fiedler_value}`;
svg.appendChild(lbl);
}
})();
// ─── Fiedler Vector ────────────────────────────────────────────────────────
(function() {
const svg = document.getElementById('svg-fiedler');
const n = fiedler.length;
const barH = Math.max(12, Math.min(22, 500/n));
const W = 560, H = Math.max(300, n*barH + 60), M = {t:10,r:20,b:30,l:140};
const w = W-M.l-M.r, h = H-M.t-M.b;
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
const maxAbs = Math.max(...fiedler.map(d=>Math.abs(d.value)), 0.01);
const xScale = v => M.l + w/2 + (v/maxAbs) * (w/2);
const yScale = i => M.t + (i/n) * h + barH/2;
// Zero line
const zl = document.createElementNS('http://www.w3.org/2000/svg','line');
Object.entries({x1:xScale(0),x2:xScale(0),y1:M.t,y2:M.t+h,stroke:'#ff4444','stroke-width':1.5,'stroke-dasharray':'5,3','stroke-opacity':0.7}).forEach(([k,v])=>zl.setAttribute(k,v));
svg.appendChild(zl);
// Partition labels
const negCount = fiedler.filter(d=>d.value<0).length;
if (negCount > 0 && negCount < n) {
const lblA = document.createElementNS('http://www.w3.org/2000/svg','text');
lblA.setAttribute('x', M.l+4); lblA.setAttribute('y', M.t + (negCount/n)*h/2 + barH/2);
lblA.setAttribute('fill','#ff4444'); lblA.setAttribute('font-size','10'); lblA.setAttribute('opacity','0.5');
lblA.textContent = 'Partition A';
svg.appendChild(lblA);
}
fiedler.forEach((d, i) => {
const x0 = xScale(0), x1 = xScale(d.value);
const y = yScale(i) - barH*0.4;
const bw = Math.abs(x1-x0);
const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
rect.setAttribute('x', Math.min(x0,x1)); rect.setAttribute('y', y);
rect.setAttribute('width', Math.max(bw, 1)); rect.setAttribute('height', barH*0.8);
rect.setAttribute('rx', 2);
rect.setAttribute('fill', modColor(d.module));
rect.setAttribute('opacity', 0.85);
rect.classList.add('fi-bar');
rect.addEventListener('mousemove', e => showTip(e,
`<span class="tt-label">${d.name}</span><br>` +
`Module: <span class="tt-val">${d.module}</span><br>` +
`Fiedler: <span class="tt-val">${d.value.toFixed(4)}</span><br>` +
`Partition: <span class="tt-val">${d.value < 0 ? 'A' : 'B'}</span>`
));
rect.addEventListener('mouseleave', hideTip);
svg.appendChild(rect);
// Label
const txt = document.createElementNS('http://www.w3.org/2000/svg','text');
txt.setAttribute('x', M.l-4); txt.setAttribute('y', yScale(i)+3);
txt.setAttribute('text-anchor','end'); txt.setAttribute('fill','#ccc');
txt.setAttribute('font-size', Math.min(11, barH*0.75));
txt.textContent = d.name;
svg.appendChild(txt);
});
// X axis label
const xLabel = document.createElementNS('http://www.w3.org/2000/svg','text');
xLabel.setAttribute('x', M.l+w/2); xLabel.setAttribute('y', H-6);
xLabel.setAttribute('text-anchor','middle'); xLabel.setAttribute('fill','#888'); xLabel.setAttribute('font-size','11');
xLabel.textContent = 'Fiedler value';
svg.appendChild(xLabel);
})();
// ─── Module Coupling Heatmap ───────────────────────────────────────────────
(function() {
const mods = coupling.modules;
const mat = coupling.matrix;
const n = mods.length;
const svg = document.getElementById('svg-heatmap');
const cellSz = Math.min(55, 400/n);
const M = {t:10,r:60,b:80,l:100};
const W = M.l + n*cellSz + M.r, H = M.t + n*cellSz + M.b;
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
const maxVal = Math.max(...mat.flat(), 1);
// Color scale: 0=transparent dark, max=deep red
function heatColor(v) {
if (v === 0) return '#1a1a2e';
const t = v / maxVal;
const r = Math.round(40 + 215*t);
const g = Math.round(30 + 40*(1-t));
const b = Math.round(50*(1-t));
return `rgb(${r},${g},${b})`;
}
for (let i = 0; i < n; i++) {
// Row labels
const rl = document.createElementNS('http://www.w3.org/2000/svg','text');
rl.setAttribute('x', M.l-8); rl.setAttribute('y', M.t + i*cellSz + cellSz/2 + 4);
rl.setAttribute('text-anchor','end'); rl.setAttribute('fill', modColor(mods[i]));
rl.setAttribute('font-size','11'); rl.setAttribute('font-weight','600');
rl.textContent = mods[i];
svg.appendChild(rl);
// Column labels
const cl = document.createElementNS('http://www.w3.org/2000/svg','text');
cl.setAttribute('x', M.l + i*cellSz + cellSz/2);
cl.setAttribute('y', M.t + n*cellSz + 16);
cl.setAttribute('text-anchor','end'); cl.setAttribute('fill', modColor(mods[i]));
cl.setAttribute('font-size','11'); cl.setAttribute('font-weight','600');
cl.setAttribute('transform', `rotate(-45, ${M.l + i*cellSz + cellSz/2}, ${M.t + n*cellSz + 16})`);
cl.textContent = mods[i];
svg.appendChild(cl);
for (let j = 0; j < n; j++) {
const v = mat[i][j];
const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
rect.setAttribute('x', M.l + j*cellSz + 1);
rect.setAttribute('y', M.t + i*cellSz + 1);
rect.setAttribute('width', cellSz-2); rect.setAttribute('height', cellSz-2);
rect.setAttribute('rx', 3);
rect.setAttribute('fill', heatColor(v));
rect.classList.add('hm-cell');
rect.addEventListener('mousemove', e => showTip(e,
`<span class="tt-label">${mods[i]} &rarr; ${mods[j]}</span><br>` +
`Edges: <span class="tt-val">${v}</span>` +
(i !== j ? '<br><span style="opacity:0.6">cross-module</span>' : '<br><span style="opacity:0.6">intra-module</span>')
));
rect.addEventListener('mouseleave', hideTip);
svg.appendChild(rect);
// Cell text
if (v > 0) {
const txt = document.createElementNS('http://www.w3.org/2000/svg','text');
txt.setAttribute('x', M.l + j*cellSz + cellSz/2);
txt.setAttribute('y', M.t + i*cellSz + cellSz/2 + 4);
txt.setAttribute('text-anchor','middle'); txt.setAttribute('font-size','11');
txt.setAttribute('font-weight','700'); txt.setAttribute('pointer-events','none');
txt.setAttribute('fill', v > maxVal*0.5 ? '#fff' : '#ccc');
txt.textContent = v;
svg.appendChild(txt);
}
}
}
// Axis labels
const srcL = document.createElementNS('http://www.w3.org/2000/svg','text');
srcL.setAttribute('x', 10); srcL.setAttribute('y', M.t + n*cellSz/2);
srcL.setAttribute('text-anchor','middle'); srcL.setAttribute('fill','#666');
srcL.setAttribute('font-size','10');
srcL.setAttribute('transform', `rotate(-90,10,${M.t + n*cellSz/2})`);
srcL.textContent = 'source module';
svg.appendChild(srcL);
})();
// ─── Metrics Panel ─────────────────────────────────────────────────────────
(function() {
const c = document.getElementById('metrics-content');
const mm = metrics;
c.innerHTML = `
<div class="metrics-grid">
<div class="sub-header">Graph</div>
<div class="metric-item"><span class="metric-label">Nodes</span><span class="metric-value">${mm.n_nodes}</span></div>
<div class="metric-item"><span class="metric-label">Directed edges</span><span class="metric-value">${mm.n_edges}</span></div>
<div class="metric-item"><span class="metric-label">Modules</span><span class="metric-value">${mm.n_modules}</span></div>
<div class="metric-item"><span class="metric-label">Components</span><span class="metric-value">${mm.connected_components}</span></div>
<div class="sub-header">Spectral</div>
<div class="metric-item"><span class="metric-label">&lambda;<sub>2</sub> (alg. connectivity)</span><span class="metric-value">${mm.algebraic_connectivity}</span></div>
<div class="metric-item"><span class="metric-label">&lambda;<sub>2</sub>/n (normalized)</span><span class="metric-value">${mm.normalized_algebraic_connectivity}</span></div>
<div class="metric-item"><span class="metric-label">Spectral entropy</span><span class="metric-value">${mm.spectral_entropy}</span></div>
<div class="metric-item"><span class="metric-label">Norm. entropy</span><span class="metric-value">${mm.normalized_spectral_entropy}</span></div>
<div class="metric-item"><span class="metric-label">Spectral radius</span><span class="metric-value">${mm.spectral_radius}</span></div>
<div class="metric-item"><span class="metric-label">Norm. radius</span><span class="metric-value">${mm.normalized_spectral_radius}</span></div>
<div class="sub-header">Coupling</div>
<div class="metric-item"><span class="metric-label">Edge density</span><span class="metric-value">${mm.edge_density}</span></div>
<div class="metric-item"><span class="metric-label">Cross-module ratio</span><span class="metric-value">${(mm.cross_module_ratio*100).toFixed(1)}%</span></div>
<div class="cci-box">
<div class="cci-score" style="color:${mm.cci_color}">CCI = ${mm.cci}</div>
<div class="cci-label" style="color:${mm.cci_color}">${mm.cci_label}</div>
<div class="cci-desc">${mm.cci_desc}</div>
</div>
</div>
`;
})();
</script>
<!-- ─── Script 2: module — viz-js DAG rendering (async) ─────────────────── -->
<script type="module">
import { instance } from 'https://cdn.jsdelivr.net/npm/@viz-js/viz@3.11.0/lib/viz-standalone.mjs';
const DOT_SOURCE = `digraph swactor {
rankdir=LR;
fontname="Helvetica";
fontsize=14;
node [fontname="Helvetica", fontsize=11, style=filled, shape=record];
edge [fontname="Helvetica", fontsize=9];
label="swactor — internal dependency DAG";
labelloc=t;
compound=true;
newrank=true;
splines=ortho;
subgraph cluster_error {
label="error";
style="rounded,filled"; fillcolor="#f0f0f0"; color="#888";
Error [label="{Error|0: Box\\<dyn std :: error :: Error + Send + Sync + 'static\\>}", fillcolor="#e8f5e9"];
}
subgraph cluster_config {
label="config";
style="rounded,filled"; fillcolor="#f0f0f0"; color="#888";
BackoffPolicy [label="{BackoffPolicy|spin_threshold: u32\\nyield_threshold: u32\\nsleep_increment_us: u64\\nsleep_max_us: u64}", fillcolor="#e8f5e9"];
RuntimeConfig [label="{RuntimeConfig|max_actors: usize\\nactor_max_messages: usize\\nnum_threads: usize\\nmailbox_waterlevel: usize\\nbackoff_policy: BackoffPolicy}", fillcolor="#e8f5e9"];
}
subgraph cluster_channel {
label="channel";
style="rounded,filled"; fillcolor="#f0f0f0"; color="#888";
HybridChannel [label="{HybridChannel|ring: ArrayQueue\\<T\\>\\noverflow: SegQueue\\<T\\>}", fillcolor="#fff9c4"];
Receiver [label="{Receiver|queue: Arc\\<HybridChannel\\<T\\>\\>}", fillcolor="#fff9c4"];
Sender [label="{Sender|queue: Arc\\<HybridChannel\\<T\\>\\>}", fillcolor="#fff9c4"];
}
subgraph cluster_actor {
label="actor";
style="rounded,filled"; fillcolor="#e3f2fd"; color="#1565c0";
Message [label="{«trait» Message}", fillcolor="#bbdefb"];
ActorInterface [label="{«trait» ActorInterface|Incoming(Message)\\nResponse(Message)\\nhandle((&self, &Ctx, Self::Incoming))}", fillcolor="#bbdefb"];
ActorAddress [label="{ActorAddress|0: [u8; ..]}", fillcolor="#bbdefb"];
Actor [label="{Actor\\<A\\>|0: A}", fillcolor="#bbdefb"];
AnyActor [label="{«trait» AnyActor|handle_any((&self, &Ctx, Box\\<dyn Any + Send\\>))}", fillcolor="#bbdefb"];
ContextInner [label="{«trait» ContextInner|send_any((&self, ActorAddress, Box\\<dyn Any + Send\\>) → Result\\<(), Error\\>)\\nspawn_any((&self, ActorAddress, Box\\<dyn AnyActor\\>) → Result\\<(), Error\\>)\\nmailbox_waterlevel((&self) → usize)}", fillcolor="#bbdefb"];
Ctx [label="{Ctx|inner: &'a dyn ContextInner\\nself_addr: ActorAddress}", fillcolor="#bbdefb"];
}
subgraph cluster_address_map {
label="address_map";
style="rounded,filled"; fillcolor="#f3e5f5"; color="#7b1fa2";
WorkerId [label="{WorkerId|0: usize}", fillcolor="#e1bee7"];
AddressMap [label="{AddressMap|inner: RwLock\\<HashMap\\<ActorAddress, WorkerId\\>\\>}", fillcolor="#e1bee7"];
Placement [label="{Placement|next: AtomicUsize\\nnum_workers: usize}", fillcolor="#e1bee7"];
}
subgraph cluster_runtime {
label="runtime";
style="rounded,filled"; fillcolor="#fce4ec"; color="#c62828";
Inbox [label="{Inbox|addr: ActorAddress\\ninner: Receiver\\<M\\>}", fillcolor="#ffcdd2"];
RuntimeHandle [label="{RuntimeHandle|runtime: Arc\\<Runtime\\>\\nthreads: Vec\\<JoinHandle\\<()\\>\\>}", fillcolor="#ffcdd2"];
SenderT [label="{«trait» SenderT|try_send_any((&self, Box\\<dyn Any + Send\\>))}", fillcolor="#ffcdd2"];
Runtime [label="{Runtime|config: RuntimeConfig\\naddress_map: Arc\\<AddressMap\\>\\ninbox_registry: Arc\\<InboxRegistry\\>\\ntransfer_txs: Vec\\<Sender\\<Envelope\\>\\>\\nspawn_txs: Vec\\<Sender\\<(ActorAddress, Box\\<dyn AnyActor\\>)\\>\\>\\nplacement: Placement\\nis_running: AtomicBool\\nsingle_worker: Option\\<RefCell\\<Worker\\>\\>\\npending_workers: Option\\<Vec\\<Worker\\>\\>}", fillcolor="#ffcdd2"];
Envelope [label="{Envelope|dest: ActorAddress\\npayload: Box\\<dyn Any + Send\\>}", fillcolor="#ffcdd2"];
InboxRegistry [label="{InboxRegistry|senders: RwLock\\<HashMap\\<ActorAddress, Arc\\<dyn SenderT\\>\\>\\>}", fillcolor="#ffcdd2"];
}
subgraph cluster_worker {
label="worker";
style="rounded,filled"; fillcolor="#fff3e0"; color="#e65100";
TickContext [label="{TickContext|address_map: &'a AddressMap\\ntransfer_txs: &'a [Sender\\<Envelope\\>]\\nspawn_txs: &'a [Sender\\<(ActorAddress, Box\\<dyn AnyActor\\>)\\>]\\nplacement: &'a Placement\\ninbox_registry: &'a InboxRegistry\\nconfig: &'a RuntimeConfig}", fillcolor="#ffe0b2"];
Worker [label="{Worker|id: WorkerId\\npool: ActorPool\\ntransfer_rx: Receiver\\<Envelope\\>\\nspawn_rx: Receiver\\<(ActorAddress, Box\\<dyn AnyActor\\>)\\>}", fillcolor="#ffe0b2"];
WorkerContext [label="{WorkerContext|worker_id: WorkerId\\ntc: &'a TickContext\\<'a\\>\\npending_local: &'a RefCell\\<Vec\\<(ActorAddress, Box\\<dyn Any + Send\\>)\\>\\>}", fillcolor="#ffe0b2"];
ActorPool [label="{ActorPool|actors: HashMap\\<ActorAddress, Box\\<dyn AnyActor\\>\\>}", fillcolor="#ffe0b2"];
Mailbox [label="{Mailbox|queue: VecDeque\\<M\\>\\nwaterlevel: usize}", fillcolor="#ffe0b2"];
}
subgraph cluster_python {
label="python (feature-gated)";
style="rounded,dashed,filled"; fillcolor="#f5f5f5"; color="#999";
PyMsg [label="{PyMsg|0: PyObject}", fillcolor="#d7ccc8"];
PyActorAddress [label="{PyActorAddress|inner: ActorAddress}", fillcolor="#d7ccc8"];
Effect [label="{«enum» Effect|Send \\{ addr: ActorAddress, msg: PyObject \\}\\nSpawn \\{ addr: ActorAddress, handler: PyObject \\}}", fillcolor="#d7ccc8"];
PyCtx [label="{PyCtx|self_addr: ActorAddress\\neffects: RefCell\\<Vec\\<Effect\\>\\>}", fillcolor="#d7ccc8"];
PyActor [label="{PyActor|handler: PyObject}", fillcolor="#d7ccc8"];
PyInbox [label="{PyInbox|inner: Inbox\\<PyMsg\\>}", fillcolor="#d7ccc8"];
PyRuntimeConfig [label="{PyRuntimeConfig|num_threads: usize\\nmax_actors: usize\\nactor_max_messages: usize\\nmailbox_waterlevel: usize\\nspin_threshold: u32\\nyield_threshold: u32\\nsleep_increment_us: u64\\nsleep_max_us: u64}", fillcolor="#d7ccc8"];
PyRuntime [label="{PyRuntime|inner: Option\\<Runtime\\>}", fillcolor="#d7ccc8"];
PyRuntimeHandle [label="{PyRuntimeHandle|inner: Option\\<RuntimeHandle\\>}", fillcolor="#d7ccc8"];
}
// ═══════════════════════════════════════════════════════════════════
// INTRA-MODULE EDGES (within same cluster)
// ═══════════════════════════════════════════════════════════════════
ActorInterface -> Message [label="Incoming", style=dashed, color="#666", penwidth=1];
Actor -> AnyActor [label="impl", style=dotted, color="#666", penwidth=1];
Ctx -> ContextInner [label="inner", style=dashed, color="#666", penwidth=1];
Ctx -> ActorAddress [label="self_addr", style=dashed, color="#666", penwidth=1];
ContextInner -> ActorAddress [label="send_any", style=dashed, color="#666", penwidth=1];
ContextInner -> AnyActor [label="spawn_any", style=dashed, color="#666", penwidth=1];
AnyActor -> Ctx [label="handle_any", style=dashed, color="#666", penwidth=1];
Worker -> ActorPool [label="pool", style=dashed, color="#666", penwidth=1];
Worker -> TickContext [label="tick_once() param", style=dashed, color="#666", penwidth=1];
WorkerContext -> TickContext [label="tc", style=dashed, color="#666", penwidth=1];
Receiver -> HybridChannel [label="queue", style=dashed, color="#666", penwidth=1];
Sender -> HybridChannel [label="queue", style=dashed, color="#666", penwidth=1];
Receiver -> Sender [label="new_sender() param", style=dashed, color="#666", penwidth=1];
AddressMap -> WorkerId [label="inner", style=dashed, color="#666", penwidth=1];
Placement -> WorkerId [label="next_worker() param", style=dashed, color="#666", penwidth=1];
RuntimeConfig -> BackoffPolicy [label="backoff_policy", style=dashed, color="#666", penwidth=1];
RuntimeHandle -> Runtime [label="runtime", style=dashed, color="#666", penwidth=1];
Runtime -> InboxRegistry [label="inbox_registry", style=dashed, color="#666", penwidth=1];
Runtime -> Envelope [label="transfer_txs", style=dashed, color="#666", penwidth=1];
InboxRegistry -> SenderT [label="senders", style=dashed, color="#666", penwidth=1];
Runtime -> Inbox [label="new_inbox() param", style=dashed, color="#666", penwidth=1];
Runtime -> RuntimeHandle [label="run() param", style=dashed, color="#666", penwidth=1];
PyCtx -> Effect [label="effects", style=dashed, color="#666", penwidth=1];
PyInbox -> PyMsg [label="inner", style=dashed, color="#666", penwidth=1];
PyCtx -> PyActorAddress [label="self_addr() param", style=dashed, color="#666", penwidth=1];
PyInbox -> PyActorAddress [label="addr() param", style=dashed, color="#666", penwidth=1];
PyRuntime -> PyRuntimeConfig [label="new() param", style=dashed, color="#666", penwidth=1];
PyRuntime -> PyActorAddress [label="spawn() param", style=dashed, color="#666", penwidth=1];
PyRuntime -> PyInbox [label="inbox() param", style=dashed, color="#666", penwidth=1];
PyRuntime -> PyRuntimeHandle [label="run() param", style=dashed, color="#666", penwidth=1];
PyRuntimeHandle -> PyActorAddress [label="spawn() param", style=dashed, color="#666", penwidth=1];
PyRuntimeHandle -> PyInbox [label="inbox() param", style=dashed, color="#666", penwidth=1];
// ═══════════════════════════════════════════════════════════════════
// CROSS-MODULE EDGES (the real dependency DAG)
// ═══════════════════════════════════════════════════════════════════
// --- channel depends on runtime ---
Sender -> SenderT [label="impl", style=dotted, color="#666", penwidth=1.5];
// --- actor depends on error ---
ContextInner -> Error [label="send_any", style=solid, color="#1565c0", penwidth=1.5];
Ctx -> Error [label="send() param", style=solid, color="#1565c0", penwidth=1.5];
// --- address_map depends on actor ---
AddressMap -> ActorAddress [label="inner", style=solid, color="#7b1fa2", penwidth=1.5];
// --- runtime depends on error ---
Runtime -> Error [label="spawn() param", style=solid, color="#c62828", penwidth=1.5];
InboxRegistry -> Error [label="try_deliver() param", style=solid, color="#c62828", penwidth=1.5];
// --- runtime depends on config ---
Runtime -> RuntimeConfig [label="config", style=solid, color="#c62828", penwidth=1.5];
// --- runtime depends on channel ---
Inbox -> Receiver [label="inner", style=solid, color="#c62828", penwidth=1.5];
Runtime -> Sender [label="transfer_txs", style=solid, color="#c62828", penwidth=1.5];
// --- runtime depends on actor ---
Inbox -> ActorAddress [label="addr", style=solid, color="#c62828", penwidth=1.5];
Runtime -> ActorAddress [label="spawn_txs", style=solid, color="#c62828", penwidth=1.5];
Runtime -> AnyActor [label="spawn_txs", style=solid, color="#c62828", penwidth=1.5];
Envelope -> ActorAddress [label="dest", style=solid, color="#c62828", penwidth=1.5];
InboxRegistry -> ActorAddress [label="senders", style=solid, color="#c62828", penwidth=1.5];
Runtime -> ContextInner [label="impl", style=dotted, color="#c62828", penwidth=1.5];
// --- runtime depends on address_map ---
Runtime -> AddressMap [label="address_map", style=solid, color="#c62828", penwidth=1.5];
Runtime -> Placement [label="placement", style=solid, color="#c62828", penwidth=1.5];
// --- runtime depends on worker ---
Runtime -> Worker [label="single_worker", style=solid, color="#c62828", penwidth=1.5];
// --- worker depends on error ---
WorkerContext -> Error [label="send_any() param", style=solid, color="#e65100", penwidth=1.5];
// --- worker depends on config ---
TickContext -> RuntimeConfig [label="config", style=solid, color="#e65100", penwidth=1.5];
// --- worker depends on channel ---
TickContext -> Sender [label="transfer_txs", style=solid, color="#e65100", penwidth=1.5];
Worker -> Receiver [label="transfer_rx", style=solid, color="#e65100", penwidth=1.5];
// --- worker depends on actor ---
TickContext -> ActorAddress [label="spawn_txs", style=solid, color="#e65100", penwidth=1.5];
TickContext -> AnyActor [label="spawn_txs", style=solid, color="#e65100", penwidth=1.5];
Worker -> ActorAddress [label="spawn_rx", style=solid, color="#e65100", penwidth=1.5];
Worker -> AnyActor [label="spawn_rx", style=solid, color="#e65100", penwidth=1.5];
ActorPool -> ActorAddress [label="actors", style=solid, color="#e65100", penwidth=1.5];
ActorPool -> AnyActor [label="actors", style=solid, color="#e65100", penwidth=1.5];
WorkerContext -> ContextInner [label="impl", style=dotted, color="#e65100", penwidth=1.5];
ActorPool -> ContextInner [label="tick_all() param", style=solid, color="#e65100", penwidth=1.5];
ActorPool -> Ctx [label="tick_all() param", style=solid, color="#e65100", penwidth=1.5];
// --- worker depends on address_map ---
TickContext -> AddressMap [label="address_map", style=solid, color="#e65100", penwidth=1.5];
TickContext -> Placement [label="placement", style=solid, color="#e65100", penwidth=1.5];
Worker -> WorkerId [label="id", style=solid, color="#e65100", penwidth=1.5];
WorkerContext -> WorkerId [label="worker_id", style=solid, color="#e65100", penwidth=1.5];
// --- worker depends on runtime ---
TickContext -> Envelope [label="transfer_txs", style=solid, color="#e65100", penwidth=1.5];
TickContext -> InboxRegistry [label="inbox_registry", style=solid, color="#e65100", penwidth=1.5];
Worker -> Envelope [label="transfer_rx", style=solid, color="#e65100", penwidth=1.5];
// --- python depends on actor ---
PyActorAddress -> ActorAddress [label="inner", style=solid, color="#999", penwidth=1.5];
Effect -> ActorAddress [label="Send", style=solid, color="#999", penwidth=1.5];
PyCtx -> ActorAddress [label="self_addr", style=solid, color="#999", penwidth=1.5];
PyActor -> ActorInterface [label="impl", style=dotted, color="#999", penwidth=1.5];
PyActor -> Ctx [label="handle() param", style=solid, color="#999", penwidth=1.5];
// --- python depends on runtime ---
PyInbox -> Inbox [label="inner", style=solid, color="#999", penwidth=1.5];
PyRuntime -> Runtime [label="inner", style=solid, color="#999", penwidth=1.5];
PyRuntimeHandle -> RuntimeHandle [label="inner", style=solid, color="#999", penwidth=1.5];
}
`;
const viz = await instance();
const svg = viz.renderSVGElement(DOT_SOURCE);
document.getElementById('dag-loading').remove();
const vp = document.getElementById('dag-viewport');
vp.appendChild(svg);
// ─── Dark-mode SVG recoloring ──────────────────────────────────────────────
svg.querySelectorAll('polygon[fill="white"]').forEach(el => el.setAttribute('fill','#1a1a2e'));
svg.querySelectorAll('.graph > text, .cluster > text, .edge text').forEach(el => el.setAttribute('fill','#e0e0e0'));
svg.querySelectorAll('.node text').forEach(el => el.setAttribute('fill','#1a1a1a'));
// ─── Click-to-focus ────────────────────────────────────────────────────────
const edges = svg.querySelectorAll('.edge');
const nodes = svg.querySelectorAll('.node');
const clusterChrome = [];
svg.querySelectorAll('.cluster').forEach(c => {
c.querySelectorAll(':scope > path, :scope > polygon, :scope > text').forEach(el => clusterChrome.push(el));
});
const nodeByTitle = new Map();
nodes.forEach(n => {
const t = n.querySelector('title');
if (t) nodeByTitle.set(t.textContent.trim(), n);
});
const nodeToClusterEls = new Map();
svg.querySelectorAll('.cluster').forEach(cluster => {
const chrome = [...cluster.querySelectorAll(':scope > path, :scope > polygon, :scope > text')];
cluster.querySelectorAll('.node title').forEach(t => {
nodeToClusterEls.set(t.textContent.trim(), chrome);
});
});
const adj = new Map();
edges.forEach(edge => {
const t = edge.querySelector('title');
if (!t) return;
const parts = t.textContent.trim().split('->').map(s => s.trim());
if (parts.length !== 2) return;
const [src, dst] = parts;
if (!adj.has(src)) adj.set(src, { edges: [], neighbors: new Set() });
if (!adj.has(dst)) adj.set(dst, { edges: [], neighbors: new Set() });
adj.get(src).edges.push(edge);
adj.get(src).neighbors.add(dst);
adj.get(dst).edges.push(edge);
adj.get(dst).neighbors.add(src);
});
const DIM = 0.08;
let focused = null;
function clearFocus() {
focused = null;
nodes.forEach(n => n.style.opacity = '');
edges.forEach(e => e.style.opacity = '');
clusterChrome.forEach(el => el.style.opacity = '');
}
function focusNode(title) {
if (focused === title) { clearFocus(); return; }
focused = title;
const info = adj.get(title) || { edges: [], neighbors: new Set() };
const connected = new Set([title, ...info.neighbors]);
nodes.forEach(n => n.style.opacity = DIM);
edges.forEach(e => e.style.opacity = DIM);
clusterChrome.forEach(el => el.style.opacity = DIM);
connected.forEach(name => {
const el = nodeByTitle.get(name);
if (el) el.style.opacity = 1;
});
info.edges.forEach(e => e.style.opacity = 1);
const seen = new Set();
connected.forEach(name => {
const chrome = nodeToClusterEls.get(name);
if (chrome) chrome.forEach(el => {
if (!seen.has(el)) { seen.add(el); el.style.opacity = 1; }
});
});
}
nodes.forEach(node => {
node.style.cursor = 'pointer';
node.addEventListener('click', e => {
e.stopPropagation();
const t = node.querySelector('title');
if (t) focusNode(t.textContent.trim());
});
});
// ─── Pan & zoom ────────────────────────────────────────────────────────────
let scale = 1, tx = 0, ty = 0, dragging = false, didDrag = false, sx = 0, sy = 0;
function applyTransform() { svg.style.transform = `translate(${tx}px,${ty}px) scale(${scale})`; svg.style.transformOrigin = '0 0'; }
window.resetView = function() {
const vw = vp.clientWidth, vh = vp.clientHeight;
const bb = svg.getBBox();
scale = Math.min(vw / bb.width, vh / bb.height) * 0.92;
tx = (vw - bb.width * scale) / 2;
ty = (vh - bb.height * scale) / 2;
applyTransform();
};
resetView();
window.zoomIn = function() { scale *= 1.3; applyTransform(); };
window.zoomOut = function() { scale *= 0.7; applyTransform(); };
vp.addEventListener('wheel', e => { e.preventDefault(); const f = e.deltaY < 0 ? 1.12 : 0.89; const rect = vp.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top; tx = mx - f * (mx - tx); ty = my - f * (my - ty); scale *= f; applyTransform(); }, { passive:false });
vp.addEventListener('pointerdown', e => { dragging=true; didDrag=false; sx=e.clientX-tx; sy=e.clientY-ty; vp.setPointerCapture(e.pointerId); });
vp.addEventListener('pointermove', e => { if(!dragging) return; didDrag=true; tx=e.clientX-sx; ty=e.clientY-sy; applyTransform(); });
vp.addEventListener('pointerup', () => dragging=false);
vp.addEventListener('click', e => { if (!didDrag && !e.target.closest('.node')) clearFocus(); });
</script>
</body></html>