import React, { useState, useEffect } from 'react'; import { RefreshCw } from 'lucide-react'; interface Dot { id: number; type: 'red' | 'blue'; x: number; y: number; } const SamplingVisualizerWidget: React.FC = () => { const [population, setPopulation] = useState([]); const [sample, setSample] = useState([]); // IDs of selected dots const [mode, setMode] = useState<'none' | 'random' | 'biased'>('none'); // Generate population on mount useEffect(() => { const dots: Dot[] = []; for (let i = 0; i < 100; i++) { // Biased distribution: Reds cluster in top-right const isClustered = i < 40; // 40% Red let x, y; if (isClustered) { // Cluster Reds (Type A) in top right (50-100, 0-50) x = 50 + Math.random() * 50; y = Math.random() * 50; } else { // Blues scattered everywhere else, but mostly bottom/left // To make it simple, just uniform random, but if we hit the "Red Zone" we retry or accept overlap // Let's force Blues to be mostly Bottom or Left if (Math.random() > 0.5) { x = Math.random() * 50; // Left half y = Math.random() * 100; } else { x = 50 + Math.random() * 50; // Right half y = 50 + Math.random() * 50; // Bottom right } } dots.push({ id: i, type: isClustered ? 'red' : 'blue', x, y }); } setPopulation(dots); }, []); const takeRandomSample = () => { const indices: number[] = []; const pool = [...population]; // Pick 10 random for (let i = 0; i < 10; i++) { const idx = Math.floor(Math.random() * pool.length); indices.push(pool[idx].id); pool.splice(idx, 1); } setSample(indices); setMode('random'); }; const takeBiasedSample = () => { // Simulate "Convenience": Pick from top-right (the Red cluster) // Find dots with x > 50 and y < 50 const candidates = population.filter(d => d.x > 50 && d.y < 50); // Take 10 from there const selected = candidates.slice(0, 10).map(d => d.id); setSample(selected); setMode('biased'); }; // Stats const sampleDots = population.filter(d => sample.includes(d.id)); const sampleRedCount = sampleDots.filter(d => d.type === 'red').length; const samplePercent = sampleDots.length > 0 ? (sampleRedCount / sampleDots.length) * 100 : 0; const truePercent = 40; // Hardcoded based on generation logic return (
{population.map(dot => { const isSelected = sample.includes(dot.id); const isRed = dot.type === 'red'; return (
); })} {/* Labels for Bias Zone */}
Cluster Zone

Population: 100 individuals (40% Red)

Sample Result (n=10)

{mode === 'none' ? (

Select a method...

) : (
Estimated Red % 15 ? 'text-rose-600' : 'text-emerald-600'}`}> {samplePercent}%
15 ? 'bg-rose-500' : 'bg-emerald-500'}`} style={{ width: `${samplePercent}%` }} >

True Population: 40%

{mode === 'biased' && (

⚠ Bias Alert: Selecting only from the "easy to reach" cluster overestimates the Red group.

)}
)}
); }; export default SamplingVisualizerWidget;