Skip to content

Commit 31b1def

Browse files
committed
cockpit/fma: nested "outer wall" rendering — the address IS the wall
The /fma view drew floating nodes; that throws away the [container:identity] nesting. Add one rounded "outer wall" box per container (Organ→Tissue; cells are leaves), drawn behind the nodes via the beforeDrawing canvas hook, colored by class, nested so the Heart's box is the outermost wall (its epicardium). Each box bounds its tier-prefix descendants: a node is inside container c's wall iff its 8:8 tier instances match c's down to c's depth — c's address is a prefix of n's. So the walls fall straight out of the address, no geometry heuristic. Verified against the baked asset: descendant counts per container match the partonomy exactly (Heart 89, chamber 22, wall 7, tissue 3). tsc clean; browser render not exercised here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TzqvDqbFRzyx17EkLKBoZF
1 parent a860351 commit 31b1def

1 file changed

Lines changed: 75 additions & 0 deletions

File tree

cockpit/src/FmaGraph.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,75 @@ function tierPos(
7474
return { x: ((lo + hi) / 2) * COL, y: soa.cls[i] * ROW };
7575
}
7676

77+
const isCeiling = (soa: Soa, i: number) => soa.ceiling[i] === 1 || soa.cls[i] === 5;
78+
79+
// A node n is INSIDE container c's wall iff its 8:8 tier instances match c's down
80+
// to c's depth — i.e. c's address is a prefix of n's. The address IS the wall.
81+
function inside(soa: Soa, c: number, n: number): boolean {
82+
const d = soa.cls[c];
83+
if (soa.cls[n] < d) return false;
84+
if (d >= 1 && inst(soa.hip[n]) !== inst(soa.hip[c])) return false;
85+
if (d >= 2 && inst(soa.twig[n]) !== inst(soa.twig[c])) return false;
86+
if (d >= 3 && inst(soa.leaf[n]) !== inst(soa.leaf[c])) return false;
87+
return true;
88+
}
89+
90+
interface Wall {
91+
x0: number;
92+
y0: number;
93+
x1: number;
94+
y1: number;
95+
color: string;
96+
depth: number;
97+
}
98+
99+
// One nested "outer wall" rectangle per container (Organ→Tissue; cells are
100+
// leaves). Each box bounds its tier-prefix descendants, so the walls nest exactly
101+
// like the partonomy — the Heart's box is the outermost wall (its epicardium).
102+
function containerWalls(soa: Soa): Wall[] {
103+
const center = new Map<number, { x: number; y: number }>();
104+
for (let i = 0; i < soa.nodeCount; i++) if (!isCeiling(soa, i)) center.set(i, tierPos(soa, i));
105+
const walls: Wall[] = [];
106+
for (let c = 0; c < soa.nodeCount; c++) {
107+
if (isCeiling(soa, c) || soa.cls[c] > 3) continue; // only Organ..Tissue contain
108+
let x0 = Infinity;
109+
let y0 = Infinity;
110+
let x1 = -Infinity;
111+
let y1 = -Infinity;
112+
let found = false;
113+
for (const [n, p] of center) {
114+
if (!inside(soa, c, n)) continue;
115+
found = true;
116+
x0 = Math.min(x0, p.x);
117+
y0 = Math.min(y0, p.y);
118+
x1 = Math.max(x1, p.x);
119+
y1 = Math.max(y1, p.y);
120+
}
121+
if (!found) continue;
122+
const pad = 42 - soa.cls[c] * 7; // coarser container → roomier wall
123+
walls.push({ x0: x0 - pad, y0: y0 - pad, x1: x1 + pad, y1: y1 + pad, color: classColor(soa.cls[c]), depth: soa.cls[c] });
124+
}
125+
return walls.sort((a, b) => a.depth - b.depth); // coarsest drawn first (behind)
126+
}
127+
128+
// Stroke a rounded rect in vis-network coordinates (the beforeDrawing ctx is
129+
// already in network space, so it aligns with node positions).
130+
function strokeWall(ctx: CanvasRenderingContext2D, w: Wall): void {
131+
const r = 16;
132+
ctx.beginPath();
133+
ctx.moveTo(w.x0 + r, w.y0);
134+
ctx.arcTo(w.x1, w.y0, w.x1, w.y1, r);
135+
ctx.arcTo(w.x1, w.y1, w.x0, w.y1, r);
136+
ctx.arcTo(w.x0, w.y1, w.x0, w.y0, r);
137+
ctx.arcTo(w.x0, w.y0, w.x1, w.y0, r);
138+
ctx.closePath();
139+
ctx.fillStyle = `${w.color}0f`; // very faint compartment fill
140+
ctx.fill();
141+
ctx.lineWidth = w.depth === 0 ? 2.6 : 1.4; // the Heart's outer wall is boldest
142+
ctx.strokeStyle = `${w.color}66`;
143+
ctx.stroke();
144+
}
145+
77146
const OPTIONS: Options = {
78147
nodes: { shape: 'dot', borderWidth: 2.5, font: { color: '#d9e9f9', size: 13, strokeWidth: 3, strokeColor: PAGE_BG } },
79148
edges: {
@@ -183,6 +252,12 @@ export function FmaGraph() {
183252
const visNodes = new DataSet<any>(Array.from({ length: soa.nodeCount }, (_, i) => baseNode(i)));
184253
const visEdges = new DataSet<any>(soa.edges.map((e, id) => baseEdge(e, id)));
185254
const net = new Network(hostRef.current, { nodes: visNodes, edges: visEdges }, OPTIONS);
255+
// nested "outer walls" — one rounded box per container, drawn behind the
256+
// nodes; they nest exactly like the partonomy (the address IS the wall).
257+
const walls = containerWalls(soa);
258+
net.on('beforeDrawing', (ctx: CanvasRenderingContext2D) => {
259+
for (const w of walls) strokeWall(ctx, w);
260+
});
186261
// fixed 8:8-tier slots, no simulation — just frame the nested cascade.
187262
net.once('afterDrawing', () => net.fit({ animation: false }));
188263
setStatus(`${soa.nodeCount} nodes · ${soa.edgeCount} edges — Z-order tile pyramid; click a tissue for its dual membership`);

0 commit comments

Comments
 (0)