"use client"; import { useMemo } from "react"; import type { HistogramState, ElementState } from "@/types/visualization"; interface HistogramVisualizerProps { data: HistogramState; name?: string; } const BAR_WIDTH = 48; const BAR_GAP = 4; const MAX_BAR_HEIGHT = 180; const INDEX_AREA_HEIGHT = 20; const POINTER_HEIGHT = 24; const ANNOTATION_HEIGHT = 20; const AREA_LABEL_HEIGHT = 24; const SVG_PADDING = 16; function getStateColor(state: ElementState): string { switch (state) { case "active": return "var(--viz-active)"; case "comparing": return "var(--viz-comparing)"; case "found": return "var(--viz-found)"; case "visited": return "var(--viz-visited)"; case "swapping": return "var(--viz-swapping)"; default: return "var(--viz-default)"; } } function getTextColor(state: ElementState): string { switch (state) { case "active": case "found": case "swapping": return "white"; case "comparing": return "var(--foreground)"; case "visited": return "var(--muted-foreground)"; default: return "var(--foreground)"; } } export function HistogramVisualizer({ data, name }: HistogramVisualizerProps) { const { bars, rectangle, pointers = {}, maxArea } = data; const dimensions = useMemo(() => { const hasPointers = Object.keys(pointers).length > 0; const hasAnnotations = bars.some( (b) => b.annotations && b.annotations.length > 0 ); const hasRectangle = !!rectangle; // Find max value to scale bars const maxValue = Math.max(...bars.map((b) => b.value), 1); const contentWidth = bars.length * BAR_WIDTH + (bars.length - 1) * BAR_GAP; const width = contentWidth + SVG_PADDING * 2; // Calculate height let height = MAX_BAR_HEIGHT + INDEX_AREA_HEIGHT + SVG_PADDING * 2; if (hasPointers) height += POINTER_HEIGHT + 8; if (hasAnnotations) height += ANNOTATION_HEIGHT; if (hasRectangle) height += AREA_LABEL_HEIGHT; // Calculate where bars start (from bottom, going up) const barBaseY = hasAnnotations ? SVG_PADDING + ANNOTATION_HEIGHT + MAX_BAR_HEIGHT : SVG_PADDING + MAX_BAR_HEIGHT; return { width, height, contentWidth, maxValue, hasPointers, hasAnnotations, hasRectangle, startX: SVG_PADDING, barBaseY, }; }, [bars, pointers, rectangle]); // Group pointers by position for stacking const pointersByPosition = useMemo(() => { const grouped: Record = {}; for (const [label, index] of Object.entries(pointers)) { if (!grouped[index]) { grouped[index] = []; } grouped[index].push(label); } return grouped; }, [pointers]); // Calculate bar height from value const getBarHeight = (value: number): number => { return (value / dimensions.maxValue) * MAX_BAR_HEIGHT; }; return (
{name && ( {name} )}
{/* Rectangle overlay (rendered behind bars) */} {rectangle && ( {/* Semi-transparent rectangle */} {/* Rectangle border */} {/* Area label - positioned inside the rectangle */} {rectangle.label && ( {rectangle.label} )} )} {/* Histogram bars */} {bars.map((bar, index) => { const x = dimensions.startX + index * (BAR_WIDTH + BAR_GAP); const barHeight = getBarHeight(bar.value); const y = dimensions.barBaseY - barHeight; return ( {/* Bar background */} {/* Bar border */} {/* Value label (inside bar or above if too short) */} 30 ? y + 20 : y - 6} textAnchor="middle" dominantBaseline="middle" className="viz-value" fill={barHeight > 30 ? getTextColor(bar.state) : "var(--foreground)"} fontSize={14} fontWeight={500} fontFamily="var(--font-mono, monospace)" > {bar.value} {/* Annotations (above bar) */} {bar.annotations && bar.annotations.length > 0 && ( {bar.annotations.join(", ")} )} ); })} {/* Baseline */} {/* Pointers (below baseline) */} {dimensions.hasPointers && ( {Object.entries(pointersByPosition).map(([posStr, labels]) => { const position = parseInt(posStr, 10); if (position < 0 || position >= bars.length) return null; const x = dimensions.startX + position * (BAR_WIDTH + BAR_GAP) + BAR_WIDTH / 2; const y = dimensions.barBaseY + INDEX_AREA_HEIGHT + POINTER_HEIGHT; return ( {/* Arrow */} {/* Label(s) */} {labels.join(", ")} ); })} )} {/* Index labels */} {bars.map((_, index) => { const x = dimensions.startX + index * (BAR_WIDTH + BAR_GAP); return ( {index} ); })} {/* Max area display */} {maxArea !== undefined && ( Max Area: {maxArea} )} {/* Arrow marker definition */}
); }