Spaces:
Runtime error
Runtime error
Update index.html
Browse files- index.html +102 -45
index.html
CHANGED
|
@@ -57,7 +57,8 @@
|
|
| 57 |
<span id="b-data" class="px-1.5 py-0.5 rounded bg-pink-900/60 text-pink-300 border border-pink-800/60">HOUSNG</span>
|
| 58 |
</div>
|
| 59 |
<div class="flex items-center gap-2 ml-1">
|
| 60 |
-
<
|
|
|
|
| 61 |
<span id="q-lbl" class="text-[8px] text-slate-600">Q:0</span>
|
| 62 |
<div id="run-dot" class="w-2 h-2 rounded-full bg-slate-700"></div>
|
| 63 |
<button onclick="openDrawer()" class="text-[10px] bg-blue-700 hover:bg-blue-600 px-2 py-1 rounded font-bold">β DIALS</button>
|
|
@@ -137,11 +138,12 @@
|
|
| 137 |
<div class="col-span-2 bg-slate-900 rounded p-3 border border-violet-900/50">
|
| 138 |
<div class="text-violet-400 text-[9px] font-bold mb-1">
|
| 139 |
CROSS-CONNECT
|
| 140 |
-
<span class="text-slate-600 font-normal ml-1">
|
| 141 |
</div>
|
| 142 |
<div class="text-[9px] text-slate-500 mb-2">
|
| 143 |
-
OFF β n independent parallel hourglasses<br>
|
| 144 |
-
ON β
|
|
|
|
| 145 |
</div>
|
| 146 |
<button id="drawer-cross-btn" onclick="toggleCross()"
|
| 147 |
class="w-full py-2 rounded text-xs font-bold border border-slate-700 bg-slate-800 text-slate-400">
|
|
@@ -254,44 +256,62 @@
|
|
| 254 |
const cfg = { mode: 'training', architecture: 'additive' };
|
| 255 |
const topo = { inputs: 1, upper: 3, lower: 3 };
|
| 256 |
let crossConnect = false;
|
|
|
|
| 257 |
|
| 258 |
// ββ CROSS CONNECT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 259 |
async function toggleCross() {
|
| 260 |
const res = await fetch('/toggle_cross', { method: 'POST' });
|
| 261 |
const data = await res.json();
|
| 262 |
crossConnect = data.cross_connect;
|
|
|
|
| 263 |
updateCrossUI();
|
| 264 |
meshPlotted = false;
|
| 265 |
}
|
| 266 |
|
| 267 |
function updateCrossUI() {
|
| 268 |
-
const btn1
|
| 269 |
-
const btn2
|
| 270 |
-
const info
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
if (crossConnect) {
|
| 272 |
btn1.className = 'px-1.5 py-0.5 rounded border font-bold text-[8px] transition-all bg-violet-800 text-violet-200 border-violet-600';
|
| 273 |
btn1.innerText = 'CROSS:ON';
|
| 274 |
btn2.className = 'w-full py-2 rounded text-xs font-bold border border-violet-600 bg-violet-900 text-violet-200';
|
| 275 |
btn2.innerText = 'CROSS-CONNECT: ON (click to disable)';
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
|
|
|
|
|
|
| 279 |
} else {
|
| 280 |
btn1.className = 'px-1.5 py-0.5 rounded border font-bold text-[8px] transition-all bg-slate-900 text-slate-600 border-slate-700';
|
| 281 |
btn1.innerText = 'CROSS:OFF';
|
| 282 |
btn2.className = 'w-full py-2 rounded text-xs font-bold border border-slate-700 bg-slate-800 text-slate-400';
|
| 283 |
btn2.innerText = 'CROSS-CONNECT: OFF (click to enable)';
|
| 284 |
-
info.innerText
|
|
|
|
| 285 |
}
|
| 286 |
}
|
| 287 |
|
| 288 |
// ββ TOPOLOGY ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 289 |
function updateSpringCount() {
|
| 290 |
const { inputs: n, upper: u, lower: l } = topo;
|
| 291 |
-
|
| 292 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
document.getElementById('spring-count').innerText =
|
| 294 |
-
`${
|
| 295 |
document.getElementById('custom-dim-hint').innerText =
|
| 296 |
n === 1 ? '(single value)' : `(${n} values, comma-separated)`;
|
| 297 |
updateCrossUI();
|
|
@@ -454,23 +474,29 @@ function buildPos(layers, n_inputs, n_upper, n_lower) {
|
|
| 454 |
const halfSp = COL_W * (n_inputs - 1) / 2;
|
| 455 |
const bSprd = n_inputs === 1 ? 3.8 : Math.min(COL_W * 0.55, 1.8);
|
| 456 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 457 |
layers.forEach((layer, li) => {
|
| 458 |
const y = Y[li];
|
| 459 |
layer.forEach(nid => {
|
| 460 |
const kind = nid[0];
|
| 461 |
-
|
|
|
|
| 462 |
if ('ABC'.includes(kind)) {
|
| 463 |
dim = parseInt(nid.slice(1));
|
| 464 |
} else {
|
| 465 |
-
|
|
|
|
|
|
|
|
|
|
| 466 |
}
|
| 467 |
const cx = n_inputs === 1 ? 0 : -halfSp + (dim-1)*COL_W;
|
| 468 |
if ('ABC'.includes(kind)) {
|
| 469 |
pos[nid] = [cx, y];
|
| 470 |
} else {
|
| 471 |
-
const
|
| 472 |
-
const total = kind === 'U' ? n_upper : n_lower;
|
| 473 |
-
const t = total === 1 ? 0 : (2*(j-1)/(total-1) - 1);
|
| 474 |
pos[nid] = [cx + bSprd * t, y];
|
| 475 |
}
|
| 476 |
});
|
|
@@ -494,42 +520,58 @@ function busShapes(pos, n_inputs) {
|
|
| 494 |
return sh;
|
| 495 |
}
|
| 496 |
|
| 497 |
-
function springColor(k
|
| 498 |
-
// Lateral springs shown in violet to distinguish from vertical
|
| 499 |
-
if (isLateral) {
|
| 500 |
-
const t = Math.min(Math.abs(k) / 6, 1);
|
| 501 |
-
return [`rgb(${Math.round(120+t*80)},50,${Math.round(180+t*75)})`, 0.8 + t*2.5];
|
| 502 |
-
}
|
| 503 |
const t = Math.min(Math.abs(k) / 6, 1);
|
| 504 |
if (k >= 0) return [`rgb(${Math.round(180+t*70)},${Math.round(100+t*80)},30)`, 1.0+t*3.5];
|
| 505 |
return [`rgb(30,${Math.round(80+t*100)},${Math.round(140+t*115)})`, 1.0+t*3.5];
|
| 506 |
}
|
| 507 |
|
| 508 |
-
|
| 509 |
-
|
|
|
|
|
|
|
| 510 |
const [u, v] = key.split('β');
|
| 511 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
}
|
| 513 |
|
| 514 |
function buildTraces(nodes, springs, layers, n_inputs, n_upper, n_lower) {
|
| 515 |
const pos = buildPos(layers, n_inputs, n_upper, n_lower);
|
| 516 |
const traces = [];
|
| 517 |
|
| 518 |
-
//
|
| 519 |
-
const
|
| 520 |
for (const [key, k] of Object.entries(springs)) {
|
| 521 |
const [u, v] = key.split('β');
|
| 522 |
if (!pos[u] || !pos[v]) continue;
|
| 523 |
-
(
|
| 524 |
}
|
| 525 |
|
| 526 |
-
for (const [key, k, u, v] of
|
| 527 |
-
const
|
| 528 |
-
const [col, wd] = springColor(k, lat);
|
| 529 |
traces.push({
|
| 530 |
type:'scatter', mode:'lines',
|
| 531 |
x:[pos[u][0], pos[v][0]], y:[pos[u][1], pos[v][1]],
|
| 532 |
-
line:{color:col, width:wd
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
hoverinfo:'none', showlegend:false
|
| 534 |
});
|
| 535 |
}
|
|
@@ -543,6 +585,16 @@ function buildTraces(nodes, springs, layers, n_inputs, n_upper, n_lower) {
|
|
| 543 |
};
|
| 544 |
const isIO = id => 'ABC'.includes(id[0]);
|
| 545 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 546 |
traces.push({
|
| 547 |
type:'scatter', mode:'markers+text',
|
| 548 |
x: allN.map(id => pos[id]?.[0] ?? 0),
|
|
@@ -556,13 +608,17 @@ function buildTraces(nodes, springs, layers, n_inputs, n_upper, n_lower) {
|
|
| 556 |
marker:{
|
| 557 |
size: allN.map(id => {
|
| 558 |
const v = Math.abs(nodes[id]?.vel ?? 0);
|
| 559 |
-
|
|
|
|
| 560 |
}),
|
| 561 |
-
color:
|
| 562 |
opacity: allN.map(id => 0.75 + Math.min(Math.abs(nodes[id]?.vel??0)*1.8, 0.25)),
|
| 563 |
line:{
|
| 564 |
-
width:2.5,
|
| 565 |
-
color: allN.map(id =>
|
|
|
|
|
|
|
|
|
|
| 566 |
}
|
| 567 |
},
|
| 568 |
hoverinfo:'none', showlegend:false
|
|
@@ -602,8 +658,8 @@ setInterval(async () => {
|
|
| 602 |
|
| 603 |
syncTopoUI(d.n_inputs, d.n_upper, d.n_lower);
|
| 604 |
crossConnect = d.cross_connect;
|
|
|
|
| 605 |
updateCrossUI();
|
| 606 |
-
document.getElementById('lat-lbl').innerText = `Β±${d.n_lateral}`;
|
| 607 |
document.getElementById('b-alpha').innerText = `Ξ±:${d.back_alpha.toFixed(2)}`;
|
| 608 |
document.getElementById('b-data').innerText = (d.dataset_type||'').slice(0,6).toUpperCase();
|
| 609 |
|
|
@@ -648,14 +704,15 @@ setInterval(async () => {
|
|
| 648 |
}
|
| 649 |
document.getElementById('pane-nodes').innerHTML = nh;
|
| 650 |
|
| 651 |
-
// Springs pane β
|
| 652 |
let sh = '';
|
| 653 |
for (const [key, k] of Object.entries(d.springs)) {
|
| 654 |
-
const
|
| 655 |
-
const kc =
|
| 656 |
-
|
|
|
|
| 657 |
sh += `<div class="flex justify-between py-0.5 border-b border-slate-900">
|
| 658 |
-
${pfx}<span class="text-slate-500 text-[9px]">${key}</span>
|
| 659 |
<span class="${kc} font-bold text-[10px]">${k.toFixed(4)}</span></div>`;
|
| 660 |
}
|
| 661 |
document.getElementById('pane-springs').innerHTML = sh;
|
|
|
|
| 57 |
<span id="b-data" class="px-1.5 py-0.5 rounded bg-pink-900/60 text-pink-300 border border-pink-800/60">HOUSNG</span>
|
| 58 |
</div>
|
| 59 |
<div class="flex items-center gap-2 ml-1">
|
| 60 |
+
<!-- shared-vertex counter, hidden when 0 -->
|
| 61 |
+
<span id="shared-lbl" class="text-[7px] text-slate-700"></span>
|
| 62 |
<span id="q-lbl" class="text-[8px] text-slate-600">Q:0</span>
|
| 63 |
<div id="run-dot" class="w-2 h-2 rounded-full bg-slate-700"></div>
|
| 64 |
<button onclick="openDrawer()" class="text-[10px] bg-blue-700 hover:bg-blue-600 px-2 py-1 rounded font-bold">β DIALS</button>
|
|
|
|
| 138 |
<div class="col-span-2 bg-slate-900 rounded p-3 border border-violet-900/50">
|
| 139 |
<div class="text-violet-400 text-[9px] font-bold mb-1">
|
| 140 |
CROSS-CONNECT
|
| 141 |
+
<span class="text-slate-600 font-normal ml-1">structural shared vertices</span>
|
| 142 |
</div>
|
| 143 |
<div class="text-[9px] text-slate-500 mb-2">
|
| 144 |
+
OFF β n independent parallel hourglasses (default)<br>
|
| 145 |
+
ON β boundary hidden nodes that visually overlap become one<br>
|
| 146 |
+
shared vertex with springs to both neighbouring dims
|
| 147 |
</div>
|
| 148 |
<button id="drawer-cross-btn" onclick="toggleCross()"
|
| 149 |
class="w-full py-2 rounded text-xs font-bold border border-slate-700 bg-slate-800 text-slate-400">
|
|
|
|
| 256 |
const cfg = { mode: 'training', architecture: 'additive' };
|
| 257 |
const topo = { inputs: 1, upper: 3, lower: 3 };
|
| 258 |
let crossConnect = false;
|
| 259 |
+
let nShared = 0;
|
| 260 |
|
| 261 |
// ββ CROSS CONNECT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 262 |
async function toggleCross() {
|
| 263 |
const res = await fetch('/toggle_cross', { method: 'POST' });
|
| 264 |
const data = await res.json();
|
| 265 |
crossConnect = data.cross_connect;
|
| 266 |
+
nShared = data.n_shared;
|
| 267 |
updateCrossUI();
|
| 268 |
meshPlotted = false;
|
| 269 |
}
|
| 270 |
|
| 271 |
function updateCrossUI() {
|
| 272 |
+
const btn1 = document.getElementById('b-cross');
|
| 273 |
+
const btn2 = document.getElementById('drawer-cross-btn');
|
| 274 |
+
const info = document.getElementById('cross-info');
|
| 275 |
+
const slbl = document.getElementById('shared-lbl');
|
| 276 |
+
const n = topo.inputs;
|
| 277 |
+
const u = topo.upper;
|
| 278 |
+
const l = topo.lower;
|
| 279 |
+
const ns = nShared || (crossConnect && n >= 2
|
| 280 |
+
? (n-1) * ((u >= 2 ? 1 : 0) + (l >= 2 ? 1 : 0))
|
| 281 |
+
: 0);
|
| 282 |
+
|
| 283 |
if (crossConnect) {
|
| 284 |
btn1.className = 'px-1.5 py-0.5 rounded border font-bold text-[8px] transition-all bg-violet-800 text-violet-200 border-violet-600';
|
| 285 |
btn1.innerText = 'CROSS:ON';
|
| 286 |
btn2.className = 'w-full py-2 rounded text-xs font-bold border border-violet-600 bg-violet-900 text-violet-200';
|
| 287 |
btn2.innerText = 'CROSS-CONNECT: ON (click to disable)';
|
| 288 |
+
info.innerText = ns
|
| 289 |
+
? `${ns} shared vertex${ns !== 1 ? 'es' : ''} β boundary nodes merged`
|
| 290 |
+
: 'No merges (need Dβ₯2 and U/Lβ₯2)';
|
| 291 |
+
slbl.innerText = ns ? `β${ns}` : '';
|
| 292 |
+
slbl.className = 'text-[7px] text-violet-400';
|
| 293 |
} else {
|
| 294 |
btn1.className = 'px-1.5 py-0.5 rounded border font-bold text-[8px] transition-all bg-slate-900 text-slate-600 border-slate-700';
|
| 295 |
btn1.innerText = 'CROSS:OFF';
|
| 296 |
btn2.className = 'w-full py-2 rounded text-xs font-bold border border-slate-700 bg-slate-800 text-slate-400';
|
| 297 |
btn2.innerText = 'CROSS-CONNECT: OFF (click to enable)';
|
| 298 |
+
info.innerText = `${n} independent parallel hourglass${n !== 1 ? 'es' : ''}`;
|
| 299 |
+
slbl.innerText = '';
|
| 300 |
}
|
| 301 |
}
|
| 302 |
|
| 303 |
// ββ TOPOLOGY ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 304 |
function updateSpringCount() {
|
| 305 |
const { inputs: n, upper: u, lower: l } = topo;
|
| 306 |
+
// With structural merge: each merge removes 2 springs and adds 2 (net=0),
|
| 307 |
+
// but removes 1 node. Spring count stays the same; node count reduces.
|
| 308 |
+
const ns = crossConnect && n >= 2
|
| 309 |
+
? (n-1) * ((u >= 2 ? 1 : 0) + (l >= 2 ? 1 : 0))
|
| 310 |
+
: 0;
|
| 311 |
+
const totalSprings = n * (2*u + 2*l); // unchanged by merge
|
| 312 |
+
const totalNodes = 3*n + n*u + n*l - ns;
|
| 313 |
document.getElementById('spring-count').innerText =
|
| 314 |
+
`${totalSprings} springs | ${totalNodes} nodes${ns ? ` (${ns} shared vertices)` : ''}`;
|
| 315 |
document.getElementById('custom-dim-hint').innerText =
|
| 316 |
n === 1 ? '(single value)' : `(${n} values, comma-separated)`;
|
| 317 |
updateCrossUI();
|
|
|
|
| 474 |
const halfSp = COL_W * (n_inputs - 1) / 2;
|
| 475 |
const bSprd = n_inputs === 1 ? 3.8 : Math.min(COL_W * 0.55, 1.8);
|
| 476 |
|
| 477 |
+
// Build a flat lookup: nid β layer index (to get Y)
|
| 478 |
+
const layerOf = {};
|
| 479 |
+
layers.forEach((layer, li) => layer.forEach(nid => { layerOf[nid] = li; }));
|
| 480 |
+
|
| 481 |
layers.forEach((layer, li) => {
|
| 482 |
const y = Y[li];
|
| 483 |
layer.forEach(nid => {
|
| 484 |
const kind = nid[0];
|
| 485 |
+
// Parse dim and j from node ID
|
| 486 |
+
let dim = 1, j = 1, total = 1;
|
| 487 |
if ('ABC'.includes(kind)) {
|
| 488 |
dim = parseInt(nid.slice(1));
|
| 489 |
} else {
|
| 490 |
+
const parts = nid.slice(1).split('_');
|
| 491 |
+
dim = parseInt(parts[0]);
|
| 492 |
+
j = parseInt(parts[1]);
|
| 493 |
+
total = kind === 'U' ? n_upper : n_lower;
|
| 494 |
}
|
| 495 |
const cx = n_inputs === 1 ? 0 : -halfSp + (dim-1)*COL_W;
|
| 496 |
if ('ABC'.includes(kind)) {
|
| 497 |
pos[nid] = [cx, y];
|
| 498 |
} else {
|
| 499 |
+
const t = total === 1 ? 0 : (2*(j-1)/(total-1) - 1);
|
|
|
|
|
|
|
| 500 |
pos[nid] = [cx + bSprd * t, y];
|
| 501 |
}
|
| 502 |
});
|
|
|
|
| 520 |
return sh;
|
| 521 |
}
|
| 522 |
|
| 523 |
+
function springColor(k) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 524 |
const t = Math.min(Math.abs(k) / 6, 1);
|
| 525 |
if (k >= 0) return [`rgb(${Math.round(180+t*70)},${Math.round(100+t*80)},30)`, 1.0+t*3.5];
|
| 526 |
return [`rgb(30,${Math.round(80+t*100)},${Math.round(140+t*115)})`, 1.0+t*3.5];
|
| 527 |
}
|
| 528 |
|
| 529 |
+
// A spring is a "shared-vertex spring" if it crosses dimensions:
|
| 530 |
+
// e.g. A2βU1_3 (dim2 input β dim1's canonical shared node).
|
| 531 |
+
// We detect this by checking if the input side dim differs from the node's dim.
|
| 532 |
+
function isSharedSpring(key) {
|
| 533 |
const [u, v] = key.split('β');
|
| 534 |
+
if (!u || !v) return false;
|
| 535 |
+
const getDim = s => {
|
| 536 |
+
if ('ABC'.includes(s[0])) return parseInt(s.slice(1));
|
| 537 |
+
const p = s.slice(1).split('_'); return parseInt(p[0]);
|
| 538 |
+
};
|
| 539 |
+
try {
|
| 540 |
+
const du = getDim(u), dv = getDim(v);
|
| 541 |
+
return !isNaN(du) && !isNaN(dv) && du !== dv;
|
| 542 |
+
} catch { return false; }
|
| 543 |
}
|
| 544 |
|
| 545 |
function buildTraces(nodes, springs, layers, n_inputs, n_upper, n_lower) {
|
| 546 |
const pos = buildPos(layers, n_inputs, n_upper, n_lower);
|
| 547 |
const traces = [];
|
| 548 |
|
| 549 |
+
// Draw shared-vertex springs on top (after normal springs)
|
| 550 |
+
const normalEdges = [], sharedEdges = [];
|
| 551 |
for (const [key, k] of Object.entries(springs)) {
|
| 552 |
const [u, v] = key.split('β');
|
| 553 |
if (!pos[u] || !pos[v]) continue;
|
| 554 |
+
(isSharedSpring(key) ? sharedEdges : normalEdges).push([key, k, u, v]);
|
| 555 |
}
|
| 556 |
|
| 557 |
+
for (const [key, k, u, v] of normalEdges) {
|
| 558 |
+
const [col, wd] = springColor(k);
|
|
|
|
| 559 |
traces.push({
|
| 560 |
type:'scatter', mode:'lines',
|
| 561 |
x:[pos[u][0], pos[v][0]], y:[pos[u][1], pos[v][1]],
|
| 562 |
+
line:{color:col, width:wd},
|
| 563 |
+
hoverinfo:'none', showlegend:false
|
| 564 |
+
});
|
| 565 |
+
}
|
| 566 |
+
|
| 567 |
+
// Shared-vertex springs drawn in violet with slight dash to distinguish
|
| 568 |
+
for (const [key, k, u, v] of sharedEdges) {
|
| 569 |
+
const t = Math.min(Math.abs(k) / 6, 1);
|
| 570 |
+
const col = `rgb(${Math.round(160+t*60)},80,${Math.round(200+t*55)})`;
|
| 571 |
+
traces.push({
|
| 572 |
+
type:'scatter', mode:'lines',
|
| 573 |
+
x:[pos[u][0], pos[v][0]], y:[pos[u][1], pos[v][1]],
|
| 574 |
+
line:{color:col, width:1.8+t*2, dash:'dot'},
|
| 575 |
hoverinfo:'none', showlegend:false
|
| 576 |
});
|
| 577 |
}
|
|
|
|
| 585 |
};
|
| 586 |
const isIO = id => 'ABC'.includes(id[0]);
|
| 587 |
|
| 588 |
+
// Mark shared vertices (appear in springs from multiple dims)
|
| 589 |
+
const sharedNodes = new Set();
|
| 590 |
+
for (const key of Object.keys(springs)) {
|
| 591 |
+
if (isSharedSpring(key)) {
|
| 592 |
+
const [u, v] = key.split('β');
|
| 593 |
+
if ('UL'.includes((u||'')[0])) sharedNodes.add(u);
|
| 594 |
+
if ('UL'.includes((v||'')[0])) sharedNodes.add(v);
|
| 595 |
+
}
|
| 596 |
+
}
|
| 597 |
+
|
| 598 |
traces.push({
|
| 599 |
type:'scatter', mode:'markers+text',
|
| 600 |
x: allN.map(id => pos[id]?.[0] ?? 0),
|
|
|
|
| 608 |
marker:{
|
| 609 |
size: allN.map(id => {
|
| 610 |
const v = Math.abs(nodes[id]?.vel ?? 0);
|
| 611 |
+
const base = isIO(id) ? 18 : (sharedNodes.has(id) ? 14 : 10);
|
| 612 |
+
return base + Math.min(v*30, 8);
|
| 613 |
}),
|
| 614 |
+
color: allN.map(id => sharedNodes.has(id) ? '#a78bfa' : NCOL(id)),
|
| 615 |
opacity: allN.map(id => 0.75 + Math.min(Math.abs(nodes[id]?.vel??0)*1.8, 0.25)),
|
| 616 |
line:{
|
| 617 |
+
width: allN.map(id => sharedNodes.has(id) ? 3.5 : 2.5),
|
| 618 |
+
color: allN.map(id =>
|
| 619 |
+
sharedNodes.has(id) ? '#7c3aed'
|
| 620 |
+
: nodes[id]?.anchored ? '#ef4444' : '#22c55e'
|
| 621 |
+
)
|
| 622 |
}
|
| 623 |
},
|
| 624 |
hoverinfo:'none', showlegend:false
|
|
|
|
| 658 |
|
| 659 |
syncTopoUI(d.n_inputs, d.n_upper, d.n_lower);
|
| 660 |
crossConnect = d.cross_connect;
|
| 661 |
+
nShared = d.n_shared || 0;
|
| 662 |
updateCrossUI();
|
|
|
|
| 663 |
document.getElementById('b-alpha').innerText = `Ξ±:${d.back_alpha.toFixed(2)}`;
|
| 664 |
document.getElementById('b-data').innerText = (d.dataset_type||'').slice(0,6).toUpperCase();
|
| 665 |
|
|
|
|
| 704 |
}
|
| 705 |
document.getElementById('pane-nodes').innerHTML = nh;
|
| 706 |
|
| 707 |
+
// Springs pane β shared-vertex springs shown with violet marker
|
| 708 |
let sh = '';
|
| 709 |
for (const [key, k] of Object.entries(d.springs)) {
|
| 710 |
+
const shared = isSharedSpring(key);
|
| 711 |
+
const kc = shared ? 'text-violet-300'
|
| 712 |
+
: (k < 0 ? 'text-blue-300' : k > 4 ? 'text-yellow-200' : 'text-purple-300');
|
| 713 |
+
const pfx = shared ? '<span class="text-violet-500 mr-1">β</span>' : '';
|
| 714 |
sh += `<div class="flex justify-between py-0.5 border-b border-slate-900">
|
| 715 |
+
<span>${pfx}<span class="text-slate-500 text-[9px]">${key}</span></span>
|
| 716 |
<span class="${kc} font-bold text-[10px]">${k.toFixed(4)}</span></div>`;
|
| 717 |
}
|
| 718 |
document.getElementById('pane-springs').innerHTML = sh;
|