// 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); // Tab switching functionality const tabButtons = document.querySelectorAll('.tab-button'); const tabContents = document.querySelectorAll('.tab-content'); tabButtons.forEach(button => { button.addEventListener('click', () => { // Get the tab data attribute const tabId = button.getAttribute('data-tab'); // Remove active class from all buttons and contents tabButtons.forEach(btn => btn.classList.remove('active')); tabContents.forEach(content => content.classList.remove('active')); // Add active class to the clicked button button.classList.add('active'); // Add active class to the corresponding content const tabContent = document.getElementById(`${tabId}-tab`); if (tabContent) { tabContent.classList.add('active'); // Dispatch a custom event to notify tab-specific scripts document.dispatchEvent(new CustomEvent('tabSwitch', { detail: { tab: tabId } })); } }); }); // Modal functionality const aboutLink = document.getElementById('about-link'); const guideLink = document.getElementById('guide-link'); const aboutModal = document.getElementById('about-modal'); const closeModalButtons = document.querySelectorAll('.close-modal'); if (aboutLink && aboutModal) { aboutLink.addEventListener('click', (e) => { e.preventDefault(); aboutModal.style.display = 'flex'; }); } if (closeModalButtons) { closeModalButtons.forEach(button => { button.addEventListener('click', () => { const modal = button.closest('.modal'); if (modal) { modal.style.display = 'none'; } }); }); } // Close modals when clicking outside content window.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { e.target.style.display = 'none'; } }); // 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', () => { // Get node reference from modal data attributes const nodeRef = layerEditorModal.getAttribute('data-node-reference'); const nodeType = layerEditorModal.getAttribute('data-node-type'); const nodeId = layerEditorModal.getAttribute('data-node-id'); // Get actual DOM node using the ID const node = document.querySelector(`.canvas-node[data-id="${nodeId}"]`); if (node) { saveLayerConfig(node, nodeType, nodeId); } // Close the modal after saving closeModal(layerEditorModal); }); } } } // 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 = e.detail.type; const layerId = e.detail.id; // Store information in the modal for later use const layerEditorModal = document.getElementById('layer-editor-modal'); layerEditorModal.setAttribute('data-node-reference', layerId); layerEditorModal.setAttribute('data-node-type', nodeType); layerEditorModal.setAttribute('data-node-id', layerId); // 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 = ''; // Show modal openModal(layerEditorModal); // 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 // Get input and output shapes - may be calculated or null at first const inputShape = layerConfig.inputShape || ['?', '?', '?']; const outputShape = layerConfig.outputShape || ['?', '?', layerConfig.filters]; layerForm.innerHTML += `
Input dimensions: H × W × C
Output channels
Filter dimensions: ${layerConfig.kernelSize.join(' × ')}
[${outputShape.join(' × ')}]
Output dimensions: H × W × Filters
Calculating...
`; // Add event listeners to calculate output shape and parameters in real-time setTimeout(() => { const inputH = document.getElementById('conv-input-h'); const inputW = document.getElementById('conv-input-w'); const inputC = document.getElementById('conv-input-c'); const filters = document.getElementById('conv-filters'); const kernelH = document.getElementById('kernel-size-h'); const kernelW = document.getElementById('kernel-size-w'); const strideH = document.getElementById('stride-h'); const strideW = document.getElementById('stride-w'); const paddingType = document.getElementById('padding-type'); const outputShapeDisplay = document.getElementById('conv-output-shape'); const parametersDisplay = document.getElementById('conv-parameters'); const updateOutputShape = () => { const h = parseInt(inputH.value); const w = parseInt(inputW.value); const c = parseInt(inputC.value); const f = parseInt(filters.value); const kh = parseInt(kernelH.value); const kw = parseInt(kernelW.value); const sh = parseInt(strideH.value); const sw = parseInt(strideW.value); const padding = paddingType.value; // Calculate output dimensions const pH = padding === 'same' ? Math.floor(kh / 2) : 0; const pW = padding === 'same' ? Math.floor(kw / 2) : 0; const outH = Math.floor((h - kh + 2 * pH) / sh) + 1; const outW = Math.floor((w - kw + 2 * pW) / sw) + 1; // Update output shape display outputShapeDisplay.textContent = `[${outH} × ${outW} × ${f}]`; // Calculate parameters const params = kh * kw * c * f + f; // weights + bias parametersDisplay.textContent = formatNumber(params); // Store for saving layerConfig.inputShape = [h, w, c]; layerConfig.outputShape = [outH, outW, f]; layerConfig.parameters = params; }; // Attach event listeners to all inputs [inputH, inputW, inputC, filters, kernelH, kernelW, strideH, strideW, paddingType].forEach( input => input.addEventListener('input', updateOutputShape) ); // Initialize values updateOutputShape(); }, 100); break; case 'pool': // Pooling layer parameters // Get input and output shapes const poolInputShape = layerConfig.inputShape || ['?', '?', '?']; const poolOutputShape = layerConfig.outputShape || ['?', '?', '?']; layerForm.innerHTML += `
Input dimensions: H × W × C
[${poolOutputShape.join(' × ')}]
Output dimensions: H × W × C
`; // Add event listeners to calculate output shape in real-time setTimeout(() => { const inputH = document.getElementById('pool-input-h'); const inputW = document.getElementById('pool-input-w'); const inputC = document.getElementById('pool-input-c'); const poolH = document.getElementById('pool-size-h'); const poolW = document.getElementById('pool-size-w'); const strideH = document.getElementById('pool-stride-h'); const strideW = document.getElementById('pool-stride-w'); const paddingType = document.getElementById('pool-padding'); const outputShapeDisplay = document.getElementById('pool-output-shape'); const updateOutputShape = () => { const h = parseInt(inputH.value); const w = parseInt(inputW.value); const c = parseInt(inputC.value); const ph = parseInt(poolH.value); const pw = parseInt(poolW.value); const sh = parseInt(strideH.value); const sw = parseInt(strideW.value); const padding = paddingType.value; // Calculate output dimensions const padH = padding === 'same' ? Math.floor(ph / 2) : 0; const padW = padding === 'same' ? Math.floor(pw / 2) : 0; const outH = Math.floor((h - ph + 2 * padH) / sh) + 1; const outW = Math.floor((w - pw + 2 * padW) / sw) + 1; // Update output shape display outputShapeDisplay.textContent = `[${outH} × ${outW} × ${c}]`; // Store for saving layerConfig.inputShape = [h, w, c]; layerConfig.outputShape = [outH, outW, c]; layerConfig.parameters = 0; // Pooling has no parameters }; // Attach event listeners to all inputs [inputH, inputW, inputC, poolH, poolW, strideH, strideW, paddingType].forEach( input => input.addEventListener('input', updateOutputShape) ); // Initialize values updateOutputShape(); }, 100); 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)}

`; } } // Open the modal const modal = document.getElementById('layer-editor-modal'); if (modal) { openModal(modal); // Add event listeners for the buttons in the modal footer const saveButton = modal.querySelector('.modal-footer .save-layer-btn'); if (saveButton) { // Remove any existing event listeners const newSaveButton = saveButton.cloneNode(true); saveButton.parentNode.replaceChild(newSaveButton, saveButton); // Add new event listener newSaveButton.addEventListener('click', () => { saveLayerConfig(node, nodeType, layerId); closeModal(modal); }); } const cancelButtons = modal.querySelectorAll('.modal-footer .close-modal'); cancelButtons.forEach(cancelButton => { // Remove any existing event listeners const newCancelButton = cancelButton.cloneNode(true); cancelButton.parentNode.replaceChild(newCancelButton, cancelButton); // Add new event listener newCancelButton.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 => { if (input.type === 'checkbox') { values[input.id] = input.checked; } else { values[input.id] = input.value; } }); // Update node configuration node.layerConfig = node.layerConfig || {}; const layerConfig = node.layerConfig; switch (nodeType) { case 'input': layerConfig.shape = [ parseInt(values['input-height']) || 28, parseInt(values['input-width']) || 28, parseInt(values['input-channels']) || 1 ]; layerConfig.batchSize = parseInt(values['batch-size']) || 32; layerConfig.outputShape = layerConfig.shape; layerConfig.parameters = 0; break; case 'hidden': layerConfig.units = parseInt(values['hidden-units']) || 128; layerConfig.activation = values['hidden-activation'] || 'relu'; layerConfig.dropoutRate = parseFloat(values['dropout-rate']) || 0.2; layerConfig.useBias = values['use-bias'] === true; layerConfig.outputShape = [layerConfig.units]; // Calculate parameters if input shape is available if (layerConfig.inputShape) { const inputUnits = Array.isArray(layerConfig.inputShape) ? layerConfig.inputShape.reduce((a, b) => a * b, 1) : layerConfig.inputShape; layerConfig.parameters = (inputUnits * layerConfig.units) + (layerConfig.useBias ? layerConfig.units : 0); } break; case 'output': layerConfig.units = parseInt(values['output-units']) || 10; layerConfig.activation = values['output-activation'] || 'softmax'; layerConfig.useBias = values['output-use-bias'] === true; layerConfig.outputShape = [layerConfig.units]; // Calculate parameters if input shape is available if (layerConfig.inputShape) { const inputUnits = Array.isArray(layerConfig.inputShape) ? layerConfig.inputShape.reduce((a, b) => a * b, 1) : layerConfig.inputShape; layerConfig.parameters = (inputUnits * layerConfig.units) + (layerConfig.useBias ? layerConfig.units : 0); } break; case 'conv': // Process input shape if available in form if (values['conv-input-h'] && values['conv-input-w'] && values['conv-input-c']) { layerConfig.inputShape = [ parseInt(values['conv-input-h']) || 28, parseInt(values['conv-input-w']) || 28, parseInt(values['conv-input-c']) || 1 ]; } // Process configuration layerConfig.filters = parseInt(values['conv-filters']) || 32; layerConfig.kernelSize = [ parseInt(values['kernel-size-h']) || 3, parseInt(values['kernel-size-w']) || 3 ]; layerConfig.strides = [ parseInt(values['stride-h']) || 1, parseInt(values['stride-w']) || 1 ]; layerConfig.padding = values['padding-type'] || 'valid'; layerConfig.activation = values['conv-activation'] || 'relu'; layerConfig.useBias = true; // Default to true for CNN // Calculate output shape if input shape is available if (layerConfig.inputShape) { const padding = layerConfig.padding === 'same' ? Math.floor(layerConfig.kernelSize[0] / 2) : 0; const outH = Math.floor( (layerConfig.inputShape[0] - layerConfig.kernelSize[0] + 2 * padding) / layerConfig.strides[0] ) + 1; const outW = Math.floor( (layerConfig.inputShape[1] - layerConfig.kernelSize[1] + 2 * padding) / layerConfig.strides[1] ) + 1; layerConfig.outputShape = [outH, outW, layerConfig.filters]; // Calculate parameters const kernelParams = layerConfig.kernelSize[0] * layerConfig.kernelSize[1] * layerConfig.inputShape[2] * layerConfig.filters; const biasParams = layerConfig.filters; layerConfig.parameters = kernelParams + biasParams; } break; case 'pool': // Process input shape if available in form if (values['pool-input-h'] && values['pool-input-w'] && values['pool-input-c']) { layerConfig.inputShape = [ parseInt(values['pool-input-h']) || 28, parseInt(values['pool-input-w']) || 28, parseInt(values['pool-input-c']) || 1 ]; } // Process configuration layerConfig.poolSize = [ parseInt(values['pool-size-h']) || 2, parseInt(values['pool-size-w']) || 2 ]; layerConfig.strides = [ parseInt(values['pool-stride-h']) || 2, parseInt(values['pool-stride-w']) || 2 ]; layerConfig.padding = values['pool-padding'] || 'valid'; layerConfig.poolType = values['pool-type'] || 'max'; // Calculate output shape if input shape is available if (layerConfig.inputShape) { const poolPadding = layerConfig.padding === 'same' ? Math.floor(layerConfig.poolSize[0] / 2) : 0; const poolOutH = Math.floor( (layerConfig.inputShape[0] - layerConfig.poolSize[0] + 2 * poolPadding) / layerConfig.strides[0] ) + 1; const poolOutW = Math.floor( (layerConfig.inputShape[1] - layerConfig.poolSize[1] + 2 * poolPadding) / layerConfig.strides[1] ) + 1; layerConfig.outputShape = [poolOutH, poolOutW, layerConfig.inputShape[2]]; } // Pooling has no parameters layerConfig.parameters = 0; break; case 'linear': layerConfig.inputFeatures = parseInt(values['input-features']) || 1; layerConfig.outputFeatures = parseInt(values['output-features']) || 1; layerConfig.useBias = values['linear-use-bias'] === true; layerConfig.learningRate = parseFloat(values['learning-rate-slider']) || 0.01; layerConfig.activation = values['linear-activation'] || 'linear'; layerConfig.optimizer = values['optimizer'] || 'sgd'; layerConfig.lossFunction = values['loss-function'] || 'mse'; layerConfig.inputShape = [layerConfig.inputFeatures]; layerConfig.outputShape = [layerConfig.outputFeatures]; // Calculate parameters layerConfig.parameters = layerConfig.inputFeatures * layerConfig.outputFeatures; if (layerConfig.useBias) { layerConfig.parameters += layerConfig.outputFeatures; } break; } // 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 and parameter display based on layer type let dimensions = ''; switch (nodeType) { case 'input': dimensions = layerConfig.shape.join(' × '); break; case 'hidden': case 'output': dimensions = layerConfig.units.toString(); break; case 'conv': if (layerConfig.inputShape && layerConfig.outputShape) { // Show input -> output shape transformation dimensions = `${layerConfig.inputShape[0]}×${layerConfig.inputShape[1]}×${layerConfig.inputShape[2]} → ${layerConfig.outputShape[0]}×${layerConfig.outputShape[1]}×${layerConfig.outputShape[2]}`; } else { dimensions = `? → ${layerConfig.filters} filters`; } break; case 'pool': if (layerConfig.inputShape && layerConfig.outputShape) { // Show input -> output shape transformation dimensions = `${layerConfig.inputShape[0]}×${layerConfig.inputShape[1]}×${layerConfig.inputShape[2]} → ${layerConfig.outputShape[0]}×${layerConfig.outputShape[1]}×${layerConfig.outputShape[2]}`; } else { dimensions = `? → ?`; } break; case 'linear': dimensions = `${layerConfig.inputFeatures} → ${layerConfig.outputFeatures}`; break; } // Update node dimensions display const nodeDimensions = node.querySelector('.node-dimensions'); if (nodeDimensions) { nodeDimensions.textContent = dimensions; } // Update parameters display if available const nodeParameters = node.querySelector('.node-parameters'); if (nodeParameters && layerConfig.parameters !== undefined) { nodeParameters.textContent = `Params: ${formatNumber(layerConfig.parameters)}`; } else if (nodeParameters) { nodeParameters.textContent = 'Params: ?'; } // 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; networkLayers.layers[layerIndex].config = layerConfig; // Add parameter count to the layer networkLayers.layers[layerIndex].parameters = layerConfig.parameters; } // Find all connections from this node and update target nodes const connections = document.querySelectorAll(`.connection[data-source="${layerId}"]`); connections.forEach(connection => { const targetId = connection.getAttribute('data-target'); const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`); if (targetNode && targetNode.layerConfig) { // Update target node's input shape based on this node's output shape if (layerConfig.outputShape) { targetNode.layerConfig.inputShape = layerConfig.outputShape; // Recalculate parameters const targetType = targetNode.getAttribute('data-type'); const newParams = window.neuralNetwork.calculateParameters( targetType, targetNode.layerConfig, layerConfig ); if (newParams) { targetNode.layerConfig.parameters = newParams; // Update parameter display const paramsDisplay = targetNode.querySelector('.node-parameters'); if (paramsDisplay) { paramsDisplay.textContent = `Params: ${formatNumber(newParams)}`; } // Update input shape display const inputShapeDisplay = targetNode.querySelector('.input-shape'); if (inputShapeDisplay) { inputShapeDisplay.textContent = `[${targetNode.layerConfig.inputShape.join(' × ')}]`; } } } } }); // Trigger network updated event const event = new CustomEvent('networkUpdated', { detail: networkLayers }); document.dispatchEvent(event); // Update all connections to reflect the new shapes and positions window.dragDrop.updateConnections(); } // Helper function to update connections between nodes when shapes change function updateNodeConnections(sourceNode, sourceId) { // Find all connections from this source node const connections = document.querySelectorAll(`.connection[data-source="${sourceId}"]`); connections.forEach(connection => { const targetId = connection.getAttribute('data-target'); const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`); if (targetNode && sourceNode.layerConfig && sourceNode.layerConfig.outputShape) { // Update target node with source node's output shape as its input shape if (!targetNode.layerConfig) { targetNode.layerConfig = {}; } targetNode.layerConfig.inputShape = sourceNode.layerConfig.outputShape; // Update parameter calculation window.neuralNetwork.calculateParameters( targetNode.getAttribute('data-type'), targetNode.layerConfig, sourceNode.layerConfig ); // Update display updateNodeDisplay(targetNode); // Recursively update downstream nodes updateNodeConnections(targetNode, targetId); } }); } // Helper function to update a node's display function updateNodeDisplay(node) { if (!node || !node.layerConfig) return; const nodeType = node.getAttribute('data-type'); const layerConfig = node.layerConfig; // Create dimensions string let dimensions = ''; switch (nodeType) { case 'conv': case 'pool': if (layerConfig.inputShape && layerConfig.outputShape) { dimensions = `${layerConfig.inputShape[0]}×${layerConfig.inputShape[1]}×${layerConfig.inputShape[2]} → ${layerConfig.outputShape[0]}×${layerConfig.outputShape[1]}×${layerConfig.outputShape[2]}`; } break; case 'hidden': case 'output': dimensions = layerConfig.units.toString(); break; case 'linear': dimensions = `${layerConfig.inputFeatures} → ${layerConfig.outputFeatures}`; break; } // Update dimensions display if (dimensions) { const nodeDimensions = node.querySelector('.node-dimensions'); if (nodeDimensions) { nodeDimensions.textContent = dimensions; node.setAttribute('data-dimensions', dimensions); } } // Update parameters display if (layerConfig.parameters !== undefined) { const nodeParameters = node.querySelector('.node-parameters'); if (nodeParameters) { nodeParameters.textContent = `Params: ${formatNumber(layerConfig.parameters)}`; } } } // 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`; } }); });