import { useMemo, useState } from "react"; import { seqColor } from "@/lib/colors"; export default function PatchMatrix({ matrix = [], layers = [3, 7, 10], n_steps = 0, layer, setLayer, onCellClick, selectedCell, }) { const view = useMemo(() => { const out = matrix.filter((m) => m.patch_layer === layer); const max = out.reduce((a, b) => Math.max(a, b.kl), 0) || 1; return { rows: out, max }; }, [matrix, layer]); const cellSize = 36; const padL = 28; const padT = 22; const w = padL + n_steps * (cellSize + 2); const h = padT + n_steps * (cellSize + 2); const [hover, setHover] = useState(null); function cellAt(src, tgt) { return view.rows.find( (r) => r.source_step === src && r.target_step === tgt, ); } return (
layer
{layers.map((L) => ( ))}
max KL {view.max.toFixed(3)}
{Array.from({ length: n_steps }).map((_, i) => ( t{i + 1} ))} {Array.from({ length: n_steps }).map((_, i) => ( s{i + 1} ))} {Array.from({ length: n_steps }).map((_, srcIdx) => Array.from({ length: n_steps }).map((_, tgtIdx) => { const src = srcIdx + 1; const tgt = tgtIdx + 1; const cell = cellAt(src, tgt); const isDiag = src === tgt; const v = cell?.kl ?? 0; const t = view.max > 0 ? v / view.max : 0; const isSel = selectedCell && selectedCell.source === src && selectedCell.target === tgt && selectedCell.layer === layer; return ( setHover({ src, tgt, v, sig: cell?.significant }) } onMouseLeave={() => setHover(null)} onClick={() => !isDiag && cell && onCellClick && onCellClick({ source: src, target: tgt, layer }) } style={{ cursor: isDiag ? "default" : "pointer" }} data-testid={`cross-step-patch-matrix-cell-${src}-${tgt}`} /> ); }), )} {hover && !Number.isNaN(hover.v) && (
src=s{hover.src} → tgt=s{hover.tgt}
KL = {hover.v?.toFixed(4) ?? "—"} {hover.sig ? " · sig" : ""}
)}
row = source step, col = target step · patch resid_post @ L{layer}
); }