document.addEventListener('DOMContentLoaded', () => { // Components data structure const components = { magnets: [], coils: [], cores: [], wires: [], batteries: [], switches: [] }; let selectedComponent = null; let isDrawingWire = false; let wireStartPoint = null; let currentWirePoints = []; const canvas = document.getElementById('fieldCanvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const overlay = document.getElementById('component-overlay'); // Set canvas to full size of container function resizeCanvas() { const container = document.getElementById('visualization-container'); canvas.width = container.clientWidth; canvas.height = container.clientHeight; drawField(); } // Initial resize resizeCanvas(); window.addEventListener('resize', resizeCanvas); // Draw magnetic field lines and components function drawField() { ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw wires first (background) components.wires.forEach(wire => { if (wire.points.length < 2) return; ctx.strokeStyle = '#3b82f6'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(wire.points[0].x, wire.points[0].y); for (let i = 1; i < wire.points.length; i++) { ctx.lineTo(wire.points[i].x, wire.points[i].y); } ctx.stroke(); }); // Draw other components [...components.magnets, ...components.coils, ...components.cores, ...components.batteries, ...components.switches].forEach(comp => { const x = comp.x * canvas.width; const y = comp.y * canvas.height; const radius = 20 + (comp.strength || 0.5) * 30; // Highlight selected component if (selectedComponent === comp) { ctx.strokeStyle = '#f59e0b'; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(x, y, radius + 5, 0, Math.PI * 2); ctx.stroke(); } // Component body ctx.fillStyle = getComponentColor(comp.type, comp); ctx.beginPath(); if (comp.type === 'magnet') { ctx.arc(x, y, radius, 0, Math.PI * 2); // Polarity indicator ctx.fillStyle = '#ffffff'; ctx.font = 'bold 16px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(comp.polarity > 0 ? 'N' : 'S', x, y); } else if (comp.type === 'coil') { // Draw coil as concentric circles for (let i = 0; i < 3; i++) { ctx.beginPath(); ctx.arc(x, y, radius - i*5, 0, Math.PI * 2); ctx.stroke(); } } else { // Default rectangular component ctx.roundRect(x - radius, y - radius, radius*2, radius*2, 5); } ctx.fill(); }); // Draw field lines ctx.strokeStyle = 'rgba(16, 185, 129, 0.7)'; ctx.lineWidth = 2; const steps = 36; const lineSteps = 100; const stepSize = 5; for (let i = 0; i < steps; i++) { const angle = (i / steps) * Math.PI * 2; components.magnets.forEach(magnet => { let x = magnet.x * canvas.width + Math.cos(angle) * 30; let y = magnet.y * canvas.height + Math.sin(angle) * 30; ctx.beginPath(); ctx.moveTo(x, y); for (let j = 0; j < lineSteps; j++) { // Calculate field direction at this point let fx = 0, fy = 0; components.magnets.forEach(m => { const dx = x - m.x * canvas.width; const dy = y - m.y * canvas.height; const distSq = dx * dx + dy * dy; const dist = Math.sqrt(distSq); const force = m.strength * 1000 * m.polarity / (distSq + 100); fx += force * dx / dist; fy += force * dy / dist; }); // Normalize direction const len = Math.sqrt(fx * fx + fy * fy); if (len > 0) { fx = fx / len * stepSize; fy = fy / len * stepSize; } x += fx; y += fy; ctx.lineTo(x, y); // Stop if out of bounds if (x < 0 || x > canvas.width || y < 0 || y > canvas.height) break; } ctx.stroke(); }); } } // Initial draw window.requestAnimationFrame(drawField); function getComponentColor(type, component) { const colors = { magnet: component.polarity > 0 ? '#ef4444' : '#3b82f6', coil: '#f59e0b', core: '#6b7280', battery: '#10b981', switch: '#8b5cf6' }; return colors[type] || '#ffffff'; } // Handle component creation from palette document.addEventListener('component-selected', (e) => { if (e.detail.type === 'wire') { isDrawingWire = true; overlay.style.cursor = 'crosshair'; return; } const newComponent = { type: e.detail.type, x: 0.5, // Center of canvas y: 0.5, strength: 0.5, polarity: 1, turns: e.detail.type === 'coil' ? 5 : 0, isBeingDragged: true // Flag for new component being dragged }; components[e.detail.type + 's'].push(newComponent); selectedComponent = newComponent; drawField(); updateComponentDetails(); // Set up initial drag for newly created component const handleMouseMove = (moveEvent) => { const rect = canvas.getBoundingClientRect(); const x = moveEvent.clientX - rect.left; const y = moveEvent.clientY - rect.top; selectedComponent.x = x / canvas.width; selectedComponent.y = y / canvas.height; drawField(); updateComponentDetails(); }; const handleMouseUp = () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); selectedComponent.isBeingDragged = false; }; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); }); // Handle component deletion document.addEventListener('delete-selected', () => { if (selectedComponent) { const type = selectedComponent.type; const arr = components[type + 's']; const index = arr.indexOf(selectedComponent); if (index > -1) { arr.splice(index, 1); selectedComponent = null; drawField(); updateComponentDetails(); } } }); // Canvas interaction handlers canvas.addEventListener('mousedown', (e) => { const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; if (isDrawingWire) { wireStartPoint = { x, y }; currentWirePoints = [{ x, y }]; return; } // Check if clicking on a component const allComponents = [ ...components.magnets, ...components.coils, ...components.cores, ...components.batteries, ...components.switches ]; selectedComponent = null; for (const comp of allComponents) { const compX = comp.x * canvas.width; const compY = comp.y * canvas.height; const radius = 20 + (comp.strength || 0.5) * 30; if (Math.sqrt((x - compX) ** 2 + (y - compY) ** 2) < radius) { selectedComponent = comp; break; } } if (selectedComponent) { // Prevent text selection during drag e.preventDefault(); // Set up drag handlers const handleMouseMove = (moveEvent) => { const newX = moveEvent.clientX - rect.left; const newY = moveEvent.clientY - rect.top; selectedComponent.x = newX / canvas.width; selectedComponent.y = newY / canvas.height; drawField(); updateComponentDetails(); }; const handleMouseUp = () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); } drawField(); updateComponentDetails(); }); canvas.addEventListener('mousemove', (e) => { if (!isDrawingWire && !selectedComponent) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; if (isDrawingWire && wireStartPoint) { currentWirePoints.push({ x, y }); drawField(); return; } if (selectedComponent) { selectedComponent.x = x / canvas.width; selectedComponent.y = y / canvas.height; drawField(); updateComponentDetails(); } }); canvas.addEventListener('mouseup', () => { if (isDrawingWire && wireStartPoint && currentWirePoints.length > 1) { components.wires.push({ type: 'wire', points: [...currentWirePoints], current: 0.5 }); isDrawingWire = false; overlay.style.cursor = 'default'; wireStartPoint = null; currentWirePoints = []; drawField(); } }); function updateComponentDetails() { const detailsPanel = document.getElementById('component-details'); if (!detailsPanel) return; if (!selectedComponent) { detailsPanel.innerHTML = '
No component selected
'; return; } detailsPanel.innerHTML = `