/ concept-collection / gcd-visualizer
concept-collection / gcd-visualizer
gcd-visualizer / src / components / GCDTable.tsx
136 lines · 4.3 KBCodeBlameHistory
455d9bbinitialJeremy Magland 1import { useEffect, useRef } from 'react';
2import { Box } from '@mui/material';
3import { valueToColor } from '../utils/colorUtils';
5interface GCDTableProps {
6 n: number;
7 onLoadingChange?: (isLoading: boolean) => void;
8 showColorBar: boolean;
9}
11export function GCDTable({ n, onLoadingChange, showColorBar }: GCDTableProps) {
12 const canvasRef = useRef<HTMLCanvasElement>(null);
13 const colorBarRef = useRef<HTMLCanvasElement>(null);
14 const containerRef = useRef<HTMLDivElement>(null);
15 const workerRef = useRef<Worker | null>(null);
17 // Effect for computing GCD table using Web Worker
18 useEffect(() => {
19 const canvas = canvasRef.current;
20 if (!canvas) return;
22 const ctx = canvas.getContext('2d');
23 if (!ctx) return;
25 // Terminate existing worker if any (this handles interruption)
26 if (workerRef.current) {
27 workerRef.current.terminate();
28 }
30 // Set loading state
31 onLoadingChange?.(true);
33 // Create new worker
34 const worker = new Worker(new URL('../workers/gcdWorker.ts', import.meta.url), {
35 type: 'module'
36 });
37 workerRef.current = worker;
39 // Handle messages from worker
40 worker.onmessage = (e: MessageEvent<{ type: string; imageData: ImageData }>) => {
41 if (e.data.type === 'complete') {
42 const { imageData } = e.data;
44 // Set canvas dimensions to match grid size (1 pixel per cell)
45 const gridSize = n;
47 // Resize canvas only when we have the new image ready (to prevent flickering)
48 if (canvas.width !== gridSize || canvas.height !== gridSize) {
49 canvas.width = gridSize;
50 canvas.height = gridSize;
51 }
53 // Create ImageBitmap from ImageData for efficient rendering
54 createImageBitmap(imageData).then((bitmap) => {
55 // Draw the bitmap at 1:1 scale (1 pixel per cell)
56 ctx.imageSmoothingEnabled = false; // Disable smoothing for crisp pixels
57 ctx.drawImage(bitmap, 0, 0);
59 onLoadingChange?.(false);
60 });
61 }
62 };
64 // Send computation request to worker
65 worker.postMessage({ n });
67 // Cleanup function
68 return () => {
69 worker.terminate();
70 };
71 }, [n, onLoadingChange]);
73 // Effect for drawing color bar (remains synchronous as it's simple)
74 useEffect(() => {
75 if (!showColorBar) return;
77 const colorBarCanvas = colorBarRef.current;
78 if (!colorBarCanvas) return;
80 const colorBarCtx = colorBarCanvas.getContext('2d');
81 if (!colorBarCtx) return;
83 // Draw color bar
84 const colorBarHeight = 400;
85 const colorBarWidth = 20;
86 colorBarCanvas.width = colorBarWidth;
87 colorBarCanvas.height = colorBarHeight;
89 // Draw gradient - use log(n) as max value to match data range
90 const maxLogValue = Math.log(n);
91 const steps = 256; // Use more steps for smoother gradient
92 const stepHeight = colorBarHeight / steps;
94 for (let i = 0; i < steps; i++) {
95 // Map i to value in range [0, log(n)] correctly
96 const value = (i / (steps - 1)) * maxLogValue;
97 const color = valueToColor(value, maxLogValue);
98 colorBarCtx.fillStyle = color;
99 colorBarCtx.fillRect(0, colorBarHeight - (i + 1) * stepHeight, colorBarWidth, stepHeight);
100 }
102 // Draw border around color bar
103 colorBarCtx.strokeStyle = '#000';
104 colorBarCtx.strokeRect(0, 0, colorBarWidth, colorBarHeight);
105 }, [n, showColorBar]);
107 return (
108 <Box ref={containerRef} sx={{ display: 'flex', gap: 3, alignItems: 'flex-start', justifyContent: 'flex-start', height: '100%', overflow: 'auto' }}>
109 <Box sx={{ flexShrink: 0 }}>
110 <canvas
111 ref={canvasRef}
112 style={{
113 border: '1px solid #000',
114 display: 'block'
115 }}
116 />
117 </Box>
118 {showColorBar && (
119 <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1 }}>
120 <Box sx={{ fontSize: '0.875rem', fontWeight: 'bold' }}>
121 log({n}) ≈ {Math.log(n).toFixed(2)}
122 </Box>
123 <canvas
124 ref={colorBarRef}
125 style={{
126 display: 'block'
127 }}
128 />
129 <Box sx={{ fontSize: '0.875rem', fontWeight: 'bold' }}>
130 0
131 </Box>
132 </Box>
133 )}
134 </Box>
135 );