/ concept-collection / finite-field-visualizer
Sign in
concept-collection / finite-field-visualizer
finite-field-visualizer / src / components / MultiplicationTable.tsx
135 lines · 4.3 KBCodeBlameHistory
56938f5initialJeremy Magland 1import { useEffect, useRef } from 'react';
2import { Box } from '@mui/material';
3import { valueToColor } from '../utils/colorUtils';
5interface MultiplicationTableProps {
6 prime: number;
7 onLoadingChange?: (isLoading: boolean) => void;
8 showColorBar: boolean;
9}
11export function MultiplicationTable({ prime, onLoadingChange, showColorBar }: MultiplicationTableProps) {
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 multiplication 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/multiplicationWorker.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 = prime - 1;
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({ prime });
67 // Cleanup function
68 return () => {
69 worker.terminate();
70 };
71 }, [prime]);
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 prime-1 as max value to match data range
90 const steps = 256; // Use more steps for smoother gradient
91 const stepHeight = colorBarHeight / steps;
93 for (let i = 0; i < steps; i++) {
94 // Map i to value in range [0, prime-1] correctly
95 const value = Math.floor((i / (steps - 1)) * (prime - 1));
96 const color = valueToColor(value, prime - 1);
97 colorBarCtx.fillStyle = color;
98 colorBarCtx.fillRect(0, colorBarHeight - (i + 1) * stepHeight, colorBarWidth, stepHeight);
99 }
101 // Draw border around color bar
102 colorBarCtx.strokeStyle = '#000';
103 colorBarCtx.strokeRect(0, 0, colorBarWidth, colorBarHeight);
104 }, [prime, showColorBar]);
106 return (
107 <Box ref={containerRef} sx={{ display: 'flex', gap: 3, alignItems: 'flex-start', justifyContent: 'flex-start', height: '100%', overflow: 'auto' }}>
108 <Box sx={{ flexShrink: 0 }}>
109 <canvas
110 ref={canvasRef}
111 style={{
112 border: '1px solid #000',
113 display: 'block'
114 }}
115 />
116 </Box>
117 {showColorBar && (
118 <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1 }}>
119 <Box sx={{ fontSize: '0.875rem', fontWeight: 'bold' }}>
120 {prime - 1}
121 </Box>
122 <canvas
123 ref={colorBarRef}
124 style={{
125 display: 'block'
126 }}
127 />
128 <Box sx={{ fontSize: '0.875rem', fontWeight: 'bold' }}>
129 0
130 </Box>
131 </Box>
132 )}
133 </Box>
134 );
moveopenescclose