// Initialize the application when the DOM is fully loaded document.addEventListener('DOMContentLoaded', () => { console.log('Neural Network Playground Initialized'); // Initialize the canvas and tooltip const canvas = document.getElementById('network-canvas'); const tooltip = document.createElement('div'); tooltip.className = 'canvas-tooltip'; tooltip.innerHTML = `
`; document.body.appendChild(tooltip); // Initialize drag and drop functionality initializeDragAndDrop(); // Network configuration (from UI controls) let networkConfig = { learningRate: 0.01, activation: 'relu', batchSize: 32, epochs: 10 }; // Initialize UI controls setupUIControls(); // Layer editor modal setupLayerEditor(); // Listen for network updates document.addEventListener('networkUpdated', handleNetworkUpdate); // Listen for layer editor events document.addEventListener('openLayerEditor', handleOpenLayerEditor); // Setup UI controls and event listeners function setupUIControls() { // Learning rate slider const learningRateSlider = document.getElementById('learning-rate'); const learningRateValue = document.getElementById('learning-rate-value'); if (learningRateSlider && learningRateValue) { learningRateSlider.value = networkConfig.learningRate; learningRateValue.textContent = networkConfig.learningRate.toFixed(3); learningRateSlider.addEventListener('input', (e) => { networkConfig.learningRate = parseFloat(e.target.value); learningRateValue.textContent = networkConfig.learningRate.toFixed(3); }); } // Activation function dropdown const activationSelect = document.getElementById('activation'); if (activationSelect) { activationSelect.value = networkConfig.activation; activationSelect.addEventListener('change', (e) => { networkConfig.activation = e.target.value; updateActivationFunctionGraph(networkConfig.activation); }); } // Initialize activation function graph updateActivationFunctionGraph(networkConfig.activation); // Sample data event handlers const sampleItems = document.querySelectorAll('.sample-item'); sampleItems.forEach(item => { item.addEventListener('click', () => { const sampleId = item.getAttribute('data-sample'); handleSampleSelection(sampleId); }); }); // Button event listeners const runButton = document.getElementById('run-network'); if (runButton) { runButton.addEventListener('click', runNetwork); } const clearButton = document.getElementById('clear-canvas'); if (clearButton) { clearButton.addEventListener('click', clearCanvas); } // Modal handlers setupModals(); } // Setup modal handlers function setupModals() { const aboutModal = document.getElementById('about-modal'); const aboutLink = document.getElementById('about-link'); if (aboutLink && aboutModal) { aboutLink.addEventListener('click', (e) => { e.preventDefault(); openModal(aboutModal); }); const closeButtons = aboutModal.querySelectorAll('.close-modal'); closeButtons.forEach(btn => { btn.addEventListener('click', () => { closeModal(aboutModal); }); }); // Close modal when clicking outside aboutModal.addEventListener('click', (e) => { if (e.target === aboutModal) { closeModal(aboutModal); } }); } } // Setup layer editor modal function setupLayerEditor() { const layerEditorModal = document.getElementById('layer-editor-modal'); if (layerEditorModal) { const closeButtons = layerEditorModal.querySelectorAll('.close-modal'); closeButtons.forEach(btn => { btn.addEventListener('click', () => { closeModal(layerEditorModal); }); }); // Close modal when clicking outside layerEditorModal.addEventListener('click', (e) => { if (e.target === layerEditorModal) { closeModal(layerEditorModal); } }); // Save button const saveButton = layerEditorModal.querySelector('.save-layer-btn'); if (saveButton) { saveButton.addEventListener('click', saveLayerConfig); } } } // Open modal function openModal(modal) { if (modal) { modal.style.display = 'flex'; } } // Close modal function closeModal(modal) { if (modal) { modal.style.display = 'none'; } } // Handle network updates function handleNetworkUpdate(e) { const networkLayers = e.detail; console.log('Network updated:', networkLayers); // Update the properties panel updatePropertiesPanel(networkLayers); } // Update properties panel with network information function updatePropertiesPanel(networkLayers) { const propertiesPanel = document.querySelector('.props-panel'); if (!propertiesPanel) return; // Find the properties content section const propsContent = propertiesPanel.querySelector('.props-content'); if (!propsContent) return; // Basic network stats const layerCount = networkLayers.layers.length; const connectionCount = networkLayers.connections.length; let layerTypeCounts = {}; networkLayers.layers.forEach(layer => { layerTypeCounts[layer.type] = (layerTypeCounts[layer.type] || 0) + 1; }); // Check network validity const validationResult = window.neuralNetwork.validateNetwork( networkLayers.layers, networkLayers.connections ); // Update network architecture section let networkArchitectureHTML = `
🔍 Network Architecture
Total Layers
${layerCount}
Connections
${connectionCount}
`; // Add layer type counts Object.entries(layerTypeCounts).forEach(([type, count]) => { networkArchitectureHTML += `
${type.charAt(0).toUpperCase() + type.slice(1)} Layers
${count}
`; }); // Add validation status networkArchitectureHTML += `
Validity
${validationResult.valid ? 'Valid' : 'Invalid'}
`; // If there are validation errors, show them if (!validationResult.valid && validationResult.errors.length > 0) { networkArchitectureHTML += `
Errors
${validationResult.errors.join('
')}
`; } networkArchitectureHTML += `
`; // Calculate total parameters if we have layers let totalParameters = 0; let totalFlops = 0; let totalMemory = 0; if (layerCount > 0) { // Calculate model stats const modelStatsHTML = `
📊 Model Statistics
Parameters
${formatNumber(totalParameters)}
FLOPs
${formatNumber(totalFlops)}
Memory
${formatMemorySize(totalMemory)}
`; // Update the properties content propsContent.innerHTML = networkArchitectureHTML + modelStatsHTML; } else { // Just show basic architecture info propsContent.innerHTML = networkArchitectureHTML; } } // Format number with K, M, B suffixes function formatNumber(num) { if (num === 0) return '0'; if (!num) return 'N/A'; if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B'; if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M'; if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K'; return num.toString(); } // Format memory size in bytes to KB, MB, GB function formatMemorySize(bytes) { if (bytes === 0) return '0 Bytes'; if (!bytes) return 'N/A'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Handle opening the layer editor function handleOpenLayerEditor(e) { const node = e.detail.node; const nodeType = node.getAttribute('data-type'); const layerId = node.getAttribute('data-id'); // Get current configuration const layerConfig = node.layerConfig || window.neuralNetwork.createNodeConfig(nodeType); // Update modal title const modalTitle = document.querySelector('.layer-editor-modal .modal-title'); if (modalTitle) { modalTitle.textContent = `Edit ${nodeType.charAt(0).toUpperCase() + nodeType.slice(1)} Layer`; } // Get layer form const layerForm = document.querySelector('.layer-form'); if (!layerForm) return; // Clear previous form fields layerForm.innerHTML = ''; // Create form fields based on layer type switch (nodeType) { case 'input': // Input shape fields layerForm.innerHTML += `
Input shape: [${layerConfig.shape.join(' × ')}]
`; break; case 'hidden': // Units and activation function layerForm.innerHTML += `
Output shape: [${layerConfig.units}]
${layerConfig.dropoutRate}
`; // Add listener for dropout rate slider setTimeout(() => { const dropoutSlider = document.getElementById('dropout-rate'); const dropoutValue = document.getElementById('dropout-value'); if (dropoutSlider && dropoutValue) { dropoutSlider.addEventListener('input', (e) => { dropoutValue.textContent = e.target.value; }); } }, 100); break; case 'output': // Output units and activation layerForm.innerHTML += `
Output shape: [${layerConfig.units}]
`; break; case 'conv': // Convolutional layer parameters layerForm.innerHTML += `
Output channels
Filter dimensions: ${layerConfig.kernelSize.join(' × ')}
`; break; case 'pool': // Pooling layer parameters layerForm.innerHTML += `
`; break; case 'linear': // Linear regression layer parameters layerForm.innerHTML += `
Input shape: [${layerConfig.inputFeatures}]
Output shape: [${layerConfig.outputFeatures}]
${layerConfig.learningRate}
`; // Add listener for learning rate slider setTimeout(() => { const learningRateSlider = document.getElementById('learning-rate-slider'); const learningRateValue = document.getElementById('learning-rate-value'); if (learningRateSlider && learningRateValue) { learningRateSlider.addEventListener('input', (e) => { learningRateValue.textContent = parseFloat(e.target.value).toFixed(3); }); } }, 100); break; default: layerForm.innerHTML = '

No editable properties for this layer type.

'; } // Add a preview of calculated parameters if available if (nodeType !== 'input') { const parameterCount = window.neuralNetwork.calculateParameters(nodeType, layerConfig); if (parameterCount) { layerForm.innerHTML += `

Total parameters: ${formatNumber(parameterCount)}

Memory usage (32-bit): ~${formatMemorySize(parameterCount * 4)}

`; } } // Add save and cancel buttons layerForm.innerHTML += `
`; // Open the modal const modal = document.getElementById('layer-editor-modal'); if (modal) { openModal(modal); // Add event listeners for buttons const saveButton = document.getElementById('save-layer-config'); if (saveButton) { saveButton.addEventListener('click', () => { saveLayerConfig(node, nodeType, layerId); closeModal(modal); }); } const cancelButton = document.getElementById('cancel-layer-edit'); if (cancelButton) { cancelButton.addEventListener('click', () => { closeModal(modal); }); } } } // Save layer configuration function saveLayerConfig(node, nodeType, layerId) { // Get form values const form = document.querySelector('.layer-form'); if (!form) return; const values = {}; const inputs = form.querySelectorAll('input, select'); inputs.forEach(input => { values[input.id] = input.value; }); // Update node configuration node.layerConfig = { type: nodeType, shape: [ parseInt(values['input-height']), parseInt(values['input-width']), parseInt(values['input-channels']) ], batchSize: parseInt(values['batch-size']), units: parseInt(values['hidden-units']), activation: values['hidden-activation'], dropoutRate: parseFloat(values['dropout-rate']), useBias: values['use-bias'] === 'true', learningRate: parseFloat(values['learning-rate-slider']), lossFunction: values['loss-function'], optimizer: values['optimizer'], inputFeatures: parseInt(values['input-features']), outputFeatures: parseInt(values['output-features']) }; // Update node title const nodeTitle = node.querySelector('.node-title'); if (nodeTitle) { nodeTitle.textContent = nodeType.charAt(0).toUpperCase() + nodeType.slice(1); } // Update node data attribute node.setAttribute('data-name', nodeType.charAt(0).toUpperCase() + nodeType.slice(1)); // Update dimensions based on layer type let dimensions = ''; switch (nodeType) { case 'input': dimensions = values['input-height'] + ' × ' + values['input-width'] + ' × ' + values['input-channels']; break; case 'hidden': case 'output': dimensions = values['hidden-units']; break; case 'conv': dimensions = values['conv-filters'] + ' × ' + values['kernel-size-h'] + ' × ' + values['kernel-size-w']; break; case 'pool': dimensions = values['pool-size-h'] + ' × ' + values['pool-size-w']; break; case 'linear': dimensions = values['input-features'] + ' → ' + values['output-features']; break; } // Update node dimensions const nodeDimensions = node.querySelector('.node-dimensions'); if (nodeDimensions) { nodeDimensions.textContent = dimensions; } // Update node data attribute node.setAttribute('data-dimensions', dimensions); // Update network layers in drag-drop module const networkLayers = window.dragDrop.getNetworkArchitecture(); const layerIndex = networkLayers.layers.findIndex(layer => layer.id === layerId); if (layerIndex !== -1) { networkLayers.layers[layerIndex].name = nodeType.charAt(0).toUpperCase() + nodeType.slice(1); networkLayers.layers[layerIndex].dimensions = dimensions; } // Trigger network updated event const event = new CustomEvent('networkUpdated', { detail: networkLayers }); document.dispatchEvent(event); } // Handle sample selection function handleSampleSelection(sampleId) { // Set active sample document.querySelectorAll('.sample-item').forEach(item => { item.classList.remove('active'); if (item.getAttribute('data-sample') === sampleId) { item.classList.add('active'); } }); // Get sample data const sampleData = window.neuralNetwork.sampleData[sampleId]; if (!sampleData) return; console.log(`Selected sample: ${sampleData.name}`); // Update properties panel to show sample info const propertiesPanel = document.querySelector('.props-panel'); if (!propertiesPanel) return; const propsContent = propertiesPanel.querySelector('.props-content'); if (!propsContent) return; propsContent.innerHTML = `
📊 ${sampleData.name}
Input Shape
${sampleData.inputShape.join(' × ')}
Classes
${sampleData.numClasses}
Training Samples
${sampleData.trainSamples.toLocaleString()}
Test Samples
${sampleData.testSamples.toLocaleString()}
Description
${sampleData.description}

Click "Run Network" to train on this dataset

`; } // Function to run the neural network simulation function runNetwork() { console.log('Running neural network simulation with config:', networkConfig); // Get the current network architecture const networkLayers = window.dragDrop.getNetworkArchitecture(); // Check if we have a valid network if (networkLayers.layers.length === 0) { alert('Please add some nodes to the network first!'); return; } // Validate the network const validationResult = window.neuralNetwork.validateNetwork( networkLayers.layers, networkLayers.connections ); if (!validationResult.valid) { alert('Network is not valid: ' + validationResult.errors.join('\n')); return; } // Add animation class to all nodes document.querySelectorAll('.canvas-node').forEach(node => { node.classList.add('highlight-pulse'); }); // Animate connections to show data flow document.querySelectorAll('.connection').forEach((connection, index) => { setTimeout(() => { connection.style.background = 'linear-gradient(90deg, var(--primary-color), var(--accent-color))'; // Reset after animation setTimeout(() => { connection.style.background = ''; }, 800); }, 300 * index); }); // Simulate training simulateTraining(); // Reset animations after completion setTimeout(() => { document.querySelectorAll('.canvas-node').forEach(node => { node.classList.remove('highlight-pulse'); }); }, 3000); } // Simulate training progress function simulateTraining() { const progressBar = document.querySelector('.progress-bar'); const lossValue = document.getElementById('loss-value'); const accuracyValue = document.getElementById('accuracy-value'); if (!progressBar || !lossValue || !accuracyValue) return; // Reset progress progressBar.style.width = '0%'; lossValue.textContent = '2.3021'; accuracyValue.textContent = '0.12'; // Simulate progress over time let progress = 0; let loss = 2.3021; let accuracy = 0.12; const interval = setInterval(() => { progress += 10; loss *= 0.85; // Decrease loss over time accuracy = Math.min(0.99, accuracy * 1.2); // Increase accuracy over time progressBar.style.width = `${progress}%`; lossValue.textContent = loss.toFixed(4); accuracyValue.textContent = accuracy.toFixed(2); if (progress >= 100) { clearInterval(interval); } }, 300); } // Function to clear all nodes from the canvas function clearCanvas() { if (window.dragDrop && typeof window.dragDrop.clearAllNodes === 'function') { window.dragDrop.clearAllNodes(); } // Reset progress indicators const progressBar = document.querySelector('.progress-bar'); const lossValue = document.getElementById('loss-value'); const accuracyValue = document.getElementById('accuracy-value'); if (progressBar) progressBar.style.width = '0%'; if (lossValue) lossValue.textContent = '-'; if (accuracyValue) accuracyValue.textContent = '-'; } // Update activation function graph function updateActivationFunctionGraph(activationType) { const activationGraph = document.querySelector('.activation-function'); if (!activationGraph) return; // Clear previous graph let canvas = activationGraph.querySelector('canvas'); if (!canvas) { canvas = document.createElement('canvas'); canvas.width = 200; canvas.height = 100; activationGraph.appendChild(canvas); } const ctx = canvas.getContext('2d'); // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Set background ctx.fillStyle = '#f8f9fa'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw axes ctx.strokeStyle = '#ccc'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(0, canvas.height / 2); ctx.lineTo(canvas.width, canvas.height / 2); ctx.moveTo(canvas.width / 2, 0); ctx.lineTo(canvas.width / 2, canvas.height); ctx.stroke(); // Draw function ctx.strokeStyle = 'var(--primary-color)'; ctx.lineWidth = 2; ctx.beginPath(); switch(activationType) { case 'relu': ctx.moveTo(0, canvas.height / 2); ctx.lineTo(canvas.width / 2, canvas.height / 2); ctx.lineTo(canvas.width, 0); break; case 'sigmoid': for (let x = 0; x < canvas.width; x++) { const normalizedX = (x / canvas.width - 0.5) * 10; const sigmoidY = 1 / (1 + Math.exp(-normalizedX)); const y = canvas.height - sigmoidY * canvas.height; if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } break; case 'tanh': for (let x = 0; x < canvas.width; x++) { const normalizedX = (x / canvas.width - 0.5) * 6; const tanhY = Math.tanh(normalizedX); const y = canvas.height / 2 - tanhY * canvas.height / 2; if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } break; case 'softmax': // Just a representative curve for softmax ctx.moveTo(0, canvas.height * 0.8); ctx.bezierCurveTo( canvas.width * 0.3, canvas.height * 0.7, canvas.width * 0.6, canvas.height * 0.3, canvas.width, canvas.height * 0.2 ); break; default: // Linear ctx.moveTo(0, canvas.height * 0.8); ctx.lineTo(canvas.width, canvas.height * 0.2); } ctx.stroke(); // Add label ctx.fillStyle = 'var(--text-color)'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.fillText(activationType, canvas.width / 2, canvas.height - 10); } // Setup node hover effects for tooltips canvas.addEventListener('mouseover', (e) => { const node = e.target.closest('.canvas-node'); if (node) { const rect = node.getBoundingClientRect(); const nodeType = node.getAttribute('data-type'); const nodeName = node.getAttribute('data-name'); const dimensions = node.getAttribute('data-dimensions'); // Show tooltip tooltip.style.display = 'block'; tooltip.style.left = `${rect.right + 10}px`; tooltip.style.top = `${rect.top}px`; const tooltipHeader = tooltip.querySelector('.tooltip-header'); const tooltipContent = tooltip.querySelector('.tooltip-content'); if (tooltipHeader && tooltipContent) { tooltipHeader.textContent = nodeName; let content = ''; content += `
Type:
${nodeType.charAt(0).toUpperCase() + nodeType.slice(1)}
`; content += `
Dimensions:
${dimensions}
`; // Get config template const configTemplate = window.neuralNetwork.nodeConfigTemplates[nodeType]; if (configTemplate) { if (configTemplate.activation) { content += `
Activation:
${configTemplate.activation}
`; } if (configTemplate.description) { content += `
Description:
${configTemplate.description}
`; } } tooltipContent.innerHTML = content; } } }); canvas.addEventListener('mouseout', (e) => { const node = e.target.closest('.canvas-node'); if (node) { tooltip.style.display = 'none'; } }); // Make sure tooltip follows cursor for nodes that are being dragged canvas.addEventListener('mousemove', (e) => { const node = e.target.closest('.canvas-node'); if (node && node.classList.contains('dragging')) { const rect = node.getBoundingClientRect(); tooltip.style.left = `${rect.right + 10}px`; tooltip.style.top = `${rect.top}px`; } }); });