import { useEffect, useRef } from 'react'; import { Box } from '@mui/material'; import { valueToColor } from '../utils/colorUtils'; interface GCDTableProps { n: number; onLoadingChange?: (isLoading: boolean) => void; showColorBar: boolean; } export function GCDTable({ n, onLoadingChange, showColorBar }: GCDTableProps) { const canvasRef = useRef(null); const colorBarRef = useRef(null); const containerRef = useRef(null); const workerRef = useRef(null); // Effect for computing GCD table using Web Worker useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; // Terminate existing worker if any (this handles interruption) if (workerRef.current) { workerRef.current.terminate(); } // Set loading state onLoadingChange?.(true); // Create new worker const worker = new Worker(new URL('../workers/gcdWorker.ts', import.meta.url), { type: 'module' }); workerRef.current = worker; // Handle messages from worker worker.onmessage = (e: MessageEvent<{ type: string; imageData: ImageData }>) => { if (e.data.type === 'complete') { const { imageData } = e.data; // Set canvas dimensions to match grid size (1 pixel per cell) const gridSize = n; // Resize canvas only when we have the new image ready (to prevent flickering) if (canvas.width !== gridSize || canvas.height !== gridSize) { canvas.width = gridSize; canvas.height = gridSize; } // Create ImageBitmap from ImageData for efficient rendering createImageBitmap(imageData).then((bitmap) => { // Draw the bitmap at 1:1 scale (1 pixel per cell) ctx.imageSmoothingEnabled = false; // Disable smoothing for crisp pixels ctx.drawImage(bitmap, 0, 0); onLoadingChange?.(false); }); } }; // Send computation request to worker worker.postMessage({ n }); // Cleanup function return () => { worker.terminate(); }; }, [n, onLoadingChange]); // Effect for drawing color bar (remains synchronous as it's simple) useEffect(() => { if (!showColorBar) return; const colorBarCanvas = colorBarRef.current; if (!colorBarCanvas) return; const colorBarCtx = colorBarCanvas.getContext('2d'); if (!colorBarCtx) return; // Draw color bar const colorBarHeight = 400; const colorBarWidth = 20; colorBarCanvas.width = colorBarWidth; colorBarCanvas.height = colorBarHeight; // Draw gradient - use log(n) as max value to match data range const maxLogValue = Math.log(n); const steps = 256; // Use more steps for smoother gradient const stepHeight = colorBarHeight / steps; for (let i = 0; i < steps; i++) { // Map i to value in range [0, log(n)] correctly const value = (i / (steps - 1)) * maxLogValue; const color = valueToColor(value, maxLogValue); colorBarCtx.fillStyle = color; colorBarCtx.fillRect(0, colorBarHeight - (i + 1) * stepHeight, colorBarWidth, stepHeight); } // Draw border around color bar colorBarCtx.strokeStyle = '#000'; colorBarCtx.strokeRect(0, 0, colorBarWidth, colorBarHeight); }, [n, showColorBar]); return ( {showColorBar && ( log({n}) ≈ {Math.log(n).toFixed(2)} 0 )} ); }