'use client'; import { useMemo } from 'react'; import { motion } from 'framer-motion'; import { cn } from '@/lib/utils'; import type { HeapState, HeapNodeState } from '@/lib/visualizations/types'; import { HeapNode } from '../primitives/heap-node'; interface HeapViewProps { heap: HeapState; className?: string; showArrayView?: boolean; } const NODE_RADIUS = 24; const LEVEL_HEIGHT = 72; const MIN_NODE_SPACING = 60; const SVG_PADDING = 32; const ARRAY_HEIGHT = 60; interface NodePosition { node: HeapNodeState; x: number; y: number; parentX?: number; parentY?: number; } function calculateHeapLayout(elements: HeapNodeState[], totalWidth: number): NodePosition[] { const positions: NodePosition[] = []; if (elements.length === 0) return positions; for (let i = 0; i < elements.length; i++) { const level = Math.floor(Math.log2(i + 1)); const posInLevel = i - (Math.pow(2, level) - 1); const nodesInLevel = Math.pow(2, level); const levelWidth = totalWidth / nodesInLevel; const x = levelWidth * (posInLevel + 0.5); const y = SVG_PADDING + level * LEVEL_HEIGHT + NODE_RADIUS; let parentX: number | undefined; let parentY: number | undefined; if (i > 0) { const parentIndex = Math.floor((i - 1) / 2); const parentLevel = Math.floor(Math.log2(parentIndex + 1)); const parentPosInLevel = parentIndex - (Math.pow(2, parentLevel) - 1); const parentNodesInLevel = Math.pow(2, parentLevel); const parentLevelWidth = totalWidth / parentNodesInLevel; parentX = parentLevelWidth * (parentPosInLevel + 0.5); parentY = SVG_PADDING + parentLevel * LEVEL_HEIGHT + NODE_RADIUS; } positions.push({ node: elements[i], x, y, parentX, parentY, }); } return positions; } export function HeapView({ heap, className, showArrayView = true, }: HeapViewProps) { const { positions, svgWidth, svgHeight, treeHeight } = useMemo(() => { if (heap.elements.length === 0) { return { positions: [], svgWidth: 200, svgHeight: 100, treeHeight: 50 }; } const depth = Math.floor(Math.log2(heap.elements.length)) + 1; const maxNodesAtBottom = Math.pow(2, depth - 1); const width = Math.max( maxNodesAtBottom * MIN_NODE_SPACING + SVG_PADDING * 2, 200 ); const treeH = depth * LEVEL_HEIGHT + SVG_PADDING; const height = treeH + (showArrayView ? ARRAY_HEIGHT : SVG_PADDING); const pos = calculateHeapLayout(heap.elements, width); return { positions: pos, svgWidth: width, svgHeight: height, treeHeight: treeH }; }, [heap.elements, showArrayView]); const heapTypeLabel = heap.heapType === 'min' ? 'Min-Heap' : 'Max-Heap'; const sizeLabel = heap.maxSize ? ` (size ≤ ${heap.maxSize})` : ''; return (
{(heap.label || true) && ( {heap.label || heapTypeLabel}{sizeLabel} )} {/* Draw edges first (behind nodes) */} {positions.map(({ node, x, y, parentX, parentY }) => parentX !== undefined && parentY !== undefined ? ( ) : null )} {/* Draw nodes */} {positions.map(({ node, x, y }) => ( ))} {/* Root indicator (kth largest for min-heap top-k) */} {positions.length > 0 && positions[0].node.state === 'highlighted' && ( root )} {/* Array representation below tree */} {showArrayView && heap.elements.length > 0 && ( Array: [{heap.elements.map(e => e.value).join(', ')}] {/* Index markers */} idx: {heap.elements.map((_, i) => i).join(' ')} )} {/* Empty state indicator */} {heap.elements.length === 0 && ( (empty) )}
); }