// 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 if (typeof initializeDragAndDrop === 'function') { initializeDragAndDrop(); } else { console.warn('initializeDragAndDrop function not found'); } // Network configuration (from UI controls) window.networkConfig = { learningRate: 0.1, activation: 'relu', batchSize: 32, epochs: 10, optimizer: 'sgd' }; // Make sure window.networkConfig is available globally for other scripts if (!window.networkConfig) { window.networkConfig = networkConfig; } // Initialize UI controls setupUIControls(); // Force activation function graph update setTimeout(() => { const activationType = document.getElementById('activation')?.value || 'relu'; console.log('Ensuring activation function graph is rendered:', activationType); updateActivationFunctionGraph(activationType); }, 200); // 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() { console.log('Setting up UI controls...'); // Learning rate slider const learningRateSlider = document.getElementById('learning-rate'); const learningRateValue = document.getElementById('learning-rate-value'); if (learningRateSlider && learningRateValue) { // Set initial value - default to 0.1 if not set in networkConfig window.networkConfig.learningRate = window.networkConfig.learningRate || 0.1; learningRateSlider.value = window.networkConfig.learningRate; learningRateValue.textContent = window.networkConfig.learningRate.toFixed(3); learningRateSlider.addEventListener('input', (e) => { window.networkConfig.learningRate = parseFloat(e.target.value); learningRateValue.textContent = window.networkConfig.learningRate.toFixed(3); console.log(`Learning rate updated: ${window.networkConfig.learningRate}`); // Trigger network configuration update event document.dispatchEvent(new CustomEvent('networkConfigUpdated', { detail: { type: 'learningRate', value: window.networkConfig.learningRate } })); }); console.log('Learning rate slider initialized with value:', window.networkConfig.learningRate); } else { console.warn('Learning rate controls not found in the DOM'); } // Activation function dropdown const activationSelect = document.getElementById('activation'); if (activationSelect) { // Set initial value - default to 'relu' if not set in networkConfig window.networkConfig.activation = window.networkConfig.activation || 'relu'; activationSelect.value = window.networkConfig.activation; activationSelect.addEventListener('change', (e) => { window.networkConfig.activation = e.target.value; console.log(`Activation function updated: ${window.networkConfig.activation}`); // Update activation function graph updateActivationFunctionGraph(window.networkConfig.activation); // Trigger network configuration update event document.dispatchEvent(new CustomEvent('networkConfigUpdated', { detail: { type: 'activation', value: window.networkConfig.activation } })); }); console.log('Activation select initialized with value:', window.networkConfig.activation); // Initialize activation function graph with current value updateActivationFunctionGraph(window.networkConfig.activation); } else { console.warn('Activation select not found in the DOM'); } // Optimizer dropdown const optimizerSelect = document.getElementById('optimizer'); if (optimizerSelect) { // Set initial value - default to 'sgd' if not set in networkConfig window.networkConfig.optimizer = window.networkConfig.optimizer || 'sgd'; optimizerSelect.value = window.networkConfig.optimizer; optimizerSelect.addEventListener('change', (e) => { window.networkConfig.optimizer = e.target.value; console.log(`Optimizer updated: ${window.networkConfig.optimizer}`); // Trigger network configuration update event document.dispatchEvent(new CustomEvent('networkConfigUpdated', { detail: { type: 'optimizer', value: window.networkConfig.optimizer } })); }); console.log('Optimizer select initialized with value:', window.networkConfig.optimizer); } else { console.warn('Optimizer select not found in the DOM'); } // Button event listeners const runButton = document.getElementById('run-network'); if (runButton) { runButton.addEventListener('click', () => { console.log('Run network button clicked'); runNetwork(); }); console.log('Run network button initialized'); } else { console.warn('Run network button not found in the DOM'); } const clearButton = document.getElementById('clear-canvas'); if (clearButton) { clearButton.addEventListener('click', () => { console.log('Clear canvas button clicked'); clearCanvas(); }); console.log('Clear canvas button initialized'); } else { console.warn('Clear canvas button not found in the DOM'); } // Modal handlers setupModals(); console.log('UI controls setup complete'); } // 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:', window.networkConfig); // Get the current network architecture if possible let networkLayers = { layers: [], connections: [] }; if (window.dragDrop && typeof window.dragDrop.getNetworkArchitecture === 'function') { try { networkLayers = window.dragDrop.getNetworkArchitecture(); console.log('Network architecture retrieved:', networkLayers); } catch (error) { console.error('Error getting network architecture:', error); } } else { console.warn('dragDrop.getNetworkArchitecture is not available, using fallback'); // Fallback: Get nodes and connections manually const canvas = document.getElementById('network-canvas'); if (canvas) { const nodes = canvas.querySelectorAll('.canvas-node'); const connections = canvas.querySelectorAll('.connection'); if (nodes.length === 0) { alert('Please add some nodes to the network first!'); return; } // Just animate what's visible on the canvas console.log(`Found ${nodes.length} nodes and ${connections.length} connections on canvas`); } } // Check if we have a valid network if (networkLayers.layers.length === 0) { // Check for nodes on the canvas directly const canvas = document.getElementById('network-canvas'); const nodes = canvas ? canvas.querySelectorAll('.canvas-node') : []; if (nodes.length === 0) { alert('Please add some nodes to the network first!'); return; } } // Validate the network if possible let validationResult = { valid: true, errors: [] }; if (window.neuralNetwork && typeof window.neuralNetwork.validateNetwork === 'function') { try { validationResult = window.neuralNetwork.validateNetwork( networkLayers.layers, networkLayers.connections ); if (!validationResult.valid) { alert('Network is not valid: ' + validationResult.errors.join('\n')); return; } } catch (error) { console.error('Error validating network:', error); // Continue anyway since we'll just animate } } else { console.warn('neuralNetwork.validateNetwork is not available, skipping validation'); } // Add animation class to all nodes const nodes = document.querySelectorAll('.canvas-node'); nodes.forEach(node => { node.classList.add('highlight-pulse'); // Add a delay to remove the animation class setTimeout(() => { node.classList.remove('highlight-pulse'); }, 1500); }); // Animate connections to show data flow document.querySelectorAll('.connection').forEach((conn, index) => { // Apply sequential animation to show data flow direction setTimeout(() => { conn.style.transition = 'box-shadow 0.3s ease-in-out'; conn.style.boxShadow = '0 0 15px rgba(52, 152, 219, 0.8)'; // Add a delay to remove the highlight setTimeout(() => { conn.style.boxShadow = '0 0 8px rgba(52, 152, 219, 0.5)'; }, 600); }, index * 150); // Stagger the animations }); // Update training progress visualization simulateTrainingProgress(); console.log('Network animation complete'); } // Simulate training progress for visualization function simulateTrainingProgress() { const progressBar = document.querySelector('.progress-bar'); const lossValue = document.getElementById('loss-value'); const accuracyValue = document.getElementById('accuracy-value'); if (progressBar && lossValue && accuracyValue) { // Reset progress bar progressBar.style.width = '0%'; lossValue.textContent = '1.0000'; accuracyValue.textContent = '0%'; // Simulate training progress with animation let progress = 0; let loss = 1.0; let accuracy = 0.0; const interval = setInterval(() => { progress += 2; loss = Math.max(0.05, loss * 0.95); accuracy = Math.min(99, accuracy + 2); progressBar.style.width = `${progress}%`; lossValue.textContent = loss.toFixed(4); accuracyValue.textContent = `${accuracy.toFixed(1)}%`; if (progress >= 100) { clearInterval(interval); // Final values lossValue.textContent = '0.0342'; accuracyValue.textContent = '98.7%'; console.log('Training simulation complete'); } }, 50); } } // Function to clear all nodes from the canvas function clearCanvas() { // Show confirmation dialog if (confirm('Are you sure you want to clear the canvas? This will remove all nodes and connections.')) { // Use the drag-drop module's clear function if available if (window.dragDrop && typeof window.dragDrop.clearAllNodes === 'function') { window.dragDrop.clearAllNodes(); } else { // Fallback: manually remove all canvas nodes const canvas = document.getElementById('network-canvas'); const nodes = canvas.querySelectorAll('.canvas-node'); const connections = canvas.querySelectorAll('.connection'); // Remove all connections connections.forEach(conn => conn.remove()); // Remove all nodes nodes.forEach(node => node.remove()); // Add canvas hint if (canvas.querySelector('.canvas-hint') === null) { const hint = document.createElement('div'); hint.className = 'canvas-hint'; hint.innerHTML = ` Build Your Neural Network Drag components from the left panel and drop them here.
Connect them by dragging from output (right) to input (left) ports. `; canvas.appendChild(hint); } console.log('Canvas cleared manually'); } // 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 = '-'; console.log('Canvas cleared and progress indicators reset'); } } // Update activation function graph function updateActivationFunctionGraph(activationType) { const activationGraph = document.querySelector('.activation-graph'); if (!activationGraph) return; // Get SVG element const svg = activationGraph.querySelector('.activation-curve'); if (!svg) return; // Clear previous paths while (svg.firstChild) { svg.removeChild(svg.firstChild); } // Create path for the activation function const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('stroke', '#3498db'); path.setAttribute('stroke-width', '2'); path.setAttribute('fill', 'none'); // Draw axes const xAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line'); xAxis.setAttribute('x1', '0'); xAxis.setAttribute('y1', '50'); xAxis.setAttribute('x2', '100'); xAxis.setAttribute('y2', '50'); xAxis.setAttribute('stroke', '#ccc'); xAxis.setAttribute('stroke-width', '1'); const yAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line'); yAxis.setAttribute('x1', '50'); yAxis.setAttribute('y1', '0'); yAxis.setAttribute('x2', '50'); yAxis.setAttribute('y2', '100'); yAxis.setAttribute('stroke', '#ccc'); yAxis.setAttribute('stroke-width', '1'); // Add axes to SVG svg.appendChild(xAxis); svg.appendChild(yAxis); // Calculate path based on activation type let pathData = ''; switch(activationType) { case 'relu': pathData = 'M0,50 L50,50 L100,0'; break; case 'sigmoid': pathData = generateSigmoidPath(); break; case 'tanh': pathData = generateTanhPath(); break; default: // Linear pathData = 'M0,80 L100,20'; } path.setAttribute('d', pathData); svg.appendChild(path); // Add label const label = document.createElementNS('http://www.w3.org/2000/svg', 'text'); label.setAttribute('x', '50'); label.setAttribute('y', '95'); label.setAttribute('text-anchor', 'middle'); label.setAttribute('font-size', '10'); label.setAttribute('fill', '#333'); label.textContent = activationType.charAt(0).toUpperCase() + activationType.slice(1); svg.appendChild(label); console.log(`Activation function graph updated: ${activationType}`); } // Generate path data for sigmoid function function generateSigmoidPath() { let pathData = ''; for (let x = 0; x <= 100; x += 2) { const normalizedX = (x / 100 - 0.5) * 10; const sigmoidY = 1 / (1 + Math.exp(-normalizedX)); const y = 100 - sigmoidY * 100; if (x === 0) pathData += `M${x},${y}`; else pathData += ` L${x},${y}`; } return pathData; } // Generate path data for tanh function function generateTanhPath() { let pathData = ''; for (let x = 0; x <= 100; x += 2) { const normalizedX = (x / 100 - 0.5) * 6; const tanhY = Math.tanh(normalizedX); const y = 50 - tanhY * 50; if (x === 0) pathData += `M${x},${y}`; else pathData += ` L${x},${y}`; } return pathData; } // 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`; } }); });