/* FreeSize — squarified treemap visual panel */ // ---- squarified treemap layout ---- function squarify(items, x, y, w, h) { // items: [{node, value}] sorted desc; returns [{node,x,y,w,h}] const out = []; const total = items.reduce((s, it) => s + it.value, 0); if (total <= 0 || w <= 0 || h <= 0) return out; const scale = (w * h) / total; const scaled = items.map((it) => ({ node: it.node, area: it.value * scale })); let rx = x, ry = y, rw = w, rh = h; let i = 0; while (i < scaled.length) { const vertical = rw < rh; // lay row along shorter side const sideLen = vertical ? rw : rh; let row = [scaled[i]]; let rowArea = scaled[i].area; let best = worst(row, rowArea, sideLen); let j = i + 1; while (j < scaled.length) { const na = rowArea + scaled[j].area; const nw = worst([...row, scaled[j]], na, sideLen); if (nw > best) break; row.push(scaled[j]); rowArea = na; best = nw; j++; } // place row const rowThickness = rowArea / sideLen; let off = 0; for (const cell of row) { const cellLen = cell.area / rowThickness; if (vertical) { out.push({ node: cell.node, x: rx + off, y: ry, w: cellLen, h: rowThickness }); } else { out.push({ node: cell.node, x: rx, y: ry + off, w: rowThickness, h: cellLen }); } off += cellLen; } if (vertical) { ry += rowThickness; rh -= rowThickness; } else { rx += rowThickness; rw -= rowThickness; } i = j; } return out; } function worst(row, rowArea, sideLen) { const thickness = rowArea / sideLen; let maxR = 0; for (const c of row) { const len = c.area / thickness; const ar = Math.max(thickness / len, len / thickness); if (ar > maxR) maxR = ar; } return maxR; } function useSize() { const ref = React.useRef(null); const [size, setSize] = React.useState({ w: 380, h: 380 }); React.useLayoutEffect(() => { if (!ref.current) return; const ro = new ResizeObserver((entries) => { const r = entries[0].contentRect; if (r.width > 0 && r.height > 0) setSize({ w: r.width, h: r.height }); }); ro.observe(ref.current); return () => ro.disconnect(); }, []); return [ref, size]; } function Treemap({ root, globalMax, onDrill, selected, onSelect }) { const { heatColor, heatT, fmt } = window.FZ; const [ref, size] = useSize(); const children = (root.children || []).filter((c) => c.size > 0); const items = children.map((c) => ({ node: c, value: c.size })).sort((a, b) => b.value - a.value); const pad = 3; const tiles = squarify(items, pad, pad, Math.max(0, size.w - pad * 2), Math.max(0, size.h - pad * 2)); return (