// DiagramCanvas — softened SysML diagram (rounded blocks w/ compartments, // associations as gentle splines). Themed entirely via CSS variables read // from the wrapping aesthetic. Pure visual, with hover + selection. function DiagramCanvas({ data, density = "comfortable", variant = "softened", focusBlockId, onSelect }) { const W = 720, H = 460; const pad = density === "compact" ? 8 : 12; const titleSize = density === "compact" ? 12 : 13; const propSize = density === "compact" ? 10.5 : 11.5; const stroke = variant === "formal" ? 1.4 : 1; const blockMap = React.useMemo( () => Object.fromEntries(data.blocks.map((b) => [b.id, b])), [data] ); function blockRect(b) { return { x: b.x * W, y: b.y * H, w: b.w, h: b.h }; } // Compute a clean orthogonal-ish path between blocks: exit center→nearest edge, // then a soft S-curve. function pathFor(a) { const f = blockRect(blockMap[a.from]); const t = blockRect(blockMap[a.to]); const fc = { x: f.x + f.w / 2, y: f.y + f.h / 2 }; const tc = { x: t.x + t.w / 2, y: t.y + t.h / 2 }; // Exit/enter on horizontal axis if dx dominates, else vertical. const dx = tc.x - fc.x, dy = tc.y - fc.y; let p1, p2, mid; if (Math.abs(dx) > Math.abs(dy)) { p1 = { x: fc.x + Math.sign(dx) * f.w / 2, y: fc.y }; p2 = { x: tc.x - Math.sign(dx) * t.w / 2, y: tc.y }; const cx = (p1.x + p2.x) / 2; mid = `C ${cx} ${p1.y}, ${cx} ${p2.y}, ${p2.x} ${p2.y}`; } else { p1 = { x: fc.x, y: fc.y + Math.sign(dy) * f.h / 2 }; p2 = { x: tc.x, y: tc.y - Math.sign(dy) * t.h / 2 }; const cy = (p1.y + p2.y) / 2; mid = `C ${p1.x} ${cy}, ${p2.x} ${cy}, ${p2.x} ${p2.y}`; } return { d: `M ${p1.x} ${p1.y} ${mid}`, mid: { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 } }; } const radius = variant === "formal" ? 3 : variant === "graph" ? 14 : 8; return (
{/* Edges */} {data.associations.map((a) => { const p = pathFor(a); const isComposition = a.kind === "composition"; const isConstraint = a.kind === "constraint"; return ( {a.label} ); })} {/* Blocks */} {data.blocks.map((b) => { const r = blockRect(b); const focus = focusBlockId === b.id; const isConstraint = b.kind === "constraint"; const isActor = b.kind === "actor"; return ( onSelect && onSelect(b.id)}> {/* shadow */} {variant !== "formal" && ( )} {/* Header bar */} {!isConstraint && ( )} {/* Stereotype */} «{isConstraint ? "constraint" : isActor ? "actor" : "block"}» {/* Title */} {b.label} {/* Properties */} {!isConstraint && b.properties.slice(0, 4).map((p, i) => ( · {p} ))} {isConstraint && ( {"{ tenancy = institutional }"} )} ); })}
); } window.DiagramCanvas = DiagramCanvas;