neural-network-playground / js /background-animation.js
ameerazam08's picture
Upload 22 files
a895648 verified
// Neural Network Background Animation
document.addEventListener('DOMContentLoaded', () => {
// Set initialization flag
window.backgroundAnimationInitialized = true;
console.log('Background animation script initialized');
// Canvas initialization function
function initializeCanvas() {
console.log('Initializing background animation canvas');
const canvas = document.getElementById('background-canvas');
if (!canvas) {
console.error('Background animation canvas not found!');
return;
}
const ctx = canvas.getContext('2d');
if (!ctx) {
console.error('Could not get 2D context for background animation canvas');
return;
}
// Set canvas dimensions
const container = canvas.parentElement;
if (container) {
canvas.width = container.clientWidth || 800;
canvas.height = container.clientHeight || 400;
} else {
canvas.width = 800;
canvas.height = 400;
}
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Reset animation state and redraw
resetAnimation();
updateCanvas(); // Initial draw
}
// Register the canvas initialization function with tab manager
if (typeof window !== 'undefined') {
window.initBackgroundCanvas = initializeCanvas;
}
// Canvas and context
const canvas = document.getElementById('background-canvas');
const ctx = canvas.getContext('2d');
// Control elements
const startButton = document.getElementById('start-background-animation');
const pauseButton = document.getElementById('pause-background-animation');
const resetButton = document.getElementById('reset-background-animation');
// Slider controls
const neuronCountSlider = document.getElementById('neuron-count');
const neuronCountValue = document.getElementById('neuron-count-value');
const connectionDistanceSlider = document.getElementById('connection-distance');
const connectionDistanceValue = document.getElementById('connection-distance-value');
const firingSpeedSlider = document.getElementById('firing-speed');
const firingSpeedValue = document.getElementById('firing-speed-value');
const firingColorSelect = document.getElementById('firing-color');
// Stats display elements
const activeNeuronsCount = document.getElementById('active-neurons-count');
const connectionsCount = document.getElementById('connections-count');
const firingRateElement = document.getElementById('firing-rate');
// Animation state
let animationState = {
running: false,
neurons: [],
connections: [],
config: {
neuronCount: 150,
connectionDistance: 100,
firingSpeed: 5,
firingColor: 'blue'
},
stats: {
activeNeurons: 0,
connectionCount: 0,
firingRate: 0,
firingHistory: []
},
animationFrameId: null,
lastTimestamp: 0
};
// Neuron class
class Neuron {
constructor(x, y) {
this.x = x;
this.y = y;
this.radius = 3 + Math.random() * 2; // 3-5 pixels
this.connections = [];
this.firing = false;
this.fireProgress = 0; // 0-1 for animation progress
this.lastFireTime = 0;
this.refractionPeriod = 500 + Math.random() * 1500; // 0.5-2 seconds
this.activated = false;
this.activationLevel = 0; // 0-1
this.threshold = 0.4 + Math.random() * 0.3; // 0.4-0.7
this.speedMultiplier = 0.8 + Math.random() * 0.4; // 0.8-1.2x speed
this.activationDecay = 0.02; // How fast activation decreases
}
// Add a connection to another neuron
addConnection(neuron, strength) {
this.connections.push({
target: neuron,
strength: strength,
active: false,
progress: 0
});
}
// Update neuron state
update(deltaTime, speed) {
// Scale the time increment based on speed (0-10)
const timeIncrement = deltaTime * (speed / 5);
// Update activation level (decay over time)
if (this.activationLevel > 0 && !this.firing) {
this.activationLevel = Math.max(0, this.activationLevel - this.activationDecay * (timeIncrement / 16));
}
// Check if neuron should fire
const currentTime = Date.now();
if (!this.firing && this.activationLevel >= this.threshold &&
(currentTime - this.lastFireTime > this.refractionPeriod)) {
this.firing = true;
this.fireProgress = 0;
this.lastFireTime = currentTime;
animationState.stats.firingHistory.push(currentTime);
}
// Update firing animation
if (this.firing) {
this.fireProgress += 0.05 * this.speedMultiplier * (timeIncrement / 16);
// Activate connections when fireProgress reaches 1
if (this.fireProgress >= 1) {
this.firing = false;
this.activationLevel = 0; // Reset activation after firing
// Activate outgoing connections
this.connections.forEach(conn => {
if (Math.random() < conn.strength) {
conn.active = true;
conn.progress = 0;
}
});
}
}
// Update connection animations
this.connections.forEach(conn => {
if (conn.active) {
conn.progress += 0.04 * this.speedMultiplier * (timeIncrement / 16);
// Activate target neuron when signal reaches the end
if (conn.progress >= 1) {
conn.active = false;
conn.target.activationLevel += conn.strength;
}
}
});
}
// Draw the neuron
draw(ctx, colorScheme) {
// Draw connections
this.connections.forEach(conn => {
const { target, active, progress, strength } = conn;
// Set line style based on connection state
if (active) {
// Active connection (signal traveling)
const lineWidth = 1 + strength * 1.5;
// Calculate position along the line based on progress
const signalX = this.x + (target.x - this.x) * progress;
const signalY = this.y + (target.y - this.y) * progress;
// Draw the inactive part of the connection
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(target.x, target.y);
ctx.strokeStyle = `rgba(200, 200, 200, ${strength * 0.3})`;
ctx.lineWidth = 0.5;
ctx.stroke();
// Draw the active signal
ctx.beginPath();
ctx.arc(signalX, signalY, lineWidth + 1, 0, Math.PI * 2);
// Use the appropriate color based on the selected scheme
if (colorScheme === 'rainbow') {
const hue = (Date.now() / 20) % 360;
ctx.fillStyle = `hsl(${hue}, 80%, 60%)`;
} else {
switch (colorScheme) {
case 'blue':
ctx.fillStyle = `rgba(52, 152, 219, ${0.7 + progress * 0.3})`;
break;
case 'purple':
ctx.fillStyle = `rgba(155, 89, 182, ${0.7 + progress * 0.3})`;
break;
case 'green':
ctx.fillStyle = `rgba(46, 204, 113, ${0.7 + progress * 0.3})`;
break;
default:
ctx.fillStyle = `rgba(52, 152, 219, ${0.7 + progress * 0.3})`;
}
}
ctx.fill();
} else if (this.firing || this.activationLevel > 0.2) {
// Connection from an active neuron
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(target.x, target.y);
ctx.strokeStyle = `rgba(200, 200, 200, ${strength * 0.5})`;
ctx.lineWidth = 0.5;
ctx.stroke();
} else {
// Inactive connection
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(target.x, target.y);
ctx.strokeStyle = `rgba(200, 200, 200, ${strength * 0.2})`;
ctx.lineWidth = 0.2;
ctx.stroke();
}
});
// Draw the neuron body
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
// Color based on firing/activation state
if (this.firing) {
// Use the selected color scheme for firing neurons
if (colorScheme === 'rainbow') {
const hue = (Date.now() / 20) % 360;
ctx.fillStyle = `hsl(${hue}, 80%, 60%)`;
} else {
switch (colorScheme) {
case 'blue':
ctx.fillStyle = `rgba(52, 152, 219, ${0.7 + this.fireProgress * 0.3})`;
break;
case 'purple':
ctx.fillStyle = `rgba(155, 89, 182, ${0.7 + this.fireProgress * 0.3})`;
break;
case 'green':
ctx.fillStyle = `rgba(46, 204, 113, ${0.7 + this.fireProgress * 0.3})`;
break;
default:
ctx.fillStyle = `rgba(52, 152, 219, ${0.7 + this.fireProgress * 0.3})`;
}
}
// Add glow effect for firing neurons
ctx.shadowColor = ctx.fillStyle;
ctx.shadowBlur = 10;
} else if (this.activationLevel > 0) {
// Partially activated
ctx.fillStyle = `rgba(127, 140, 141, ${0.3 + this.activationLevel * 0.7})`;
ctx.shadowBlur = 0;
} else {
// Inactive
ctx.fillStyle = 'rgba(127, 140, 141, 0.2)';
ctx.shadowBlur = 0;
}
ctx.fill();
ctx.shadowBlur = 0; // Reset shadow blur
}
}
// Initialize the network
function initNetwork() {
// Clear existing state
animationState.neurons = [];
animationState.connections = [];
animationState.stats.activeNeurons = 0;
animationState.stats.connectionCount = 0;
animationState.stats.firingHistory = [];
const { neuronCount, connectionDistance } = animationState.config;
// Create neurons with random positions
for (let i = 0; i < neuronCount; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
animationState.neurons.push(new Neuron(x, y));
}
// Create connections between nearby neurons
let connectionCount = 0;
animationState.neurons.forEach(neuron => {
animationState.neurons.forEach(target => {
if (neuron !== target) {
// Calculate distance between neurons
const dx = neuron.x - target.x;
const dy = neuron.y - target.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Connect if within range (with random chance based on distance)
if (distance < connectionDistance) {
const probability = 1 - (distance / connectionDistance);
if (Math.random() < probability * 0.3) {
// Connection strength decreases with distance
const strength = 0.2 + (1 - distance / connectionDistance) * 0.6;
neuron.addConnection(target, strength);
connectionCount++;
}
}
}
});
});
animationState.stats.connectionCount = connectionCount;
// Randomly activate a few neurons to start
for (let i = 0; i < 3; i++) {
const randomIndex = Math.floor(Math.random() * animationState.neurons.length);
animationState.neurons[randomIndex].firing = true;
animationState.neurons[randomIndex].lastFireTime = Date.now();
}
// Update stats display
updateStatsDisplay();
}
// Canvas resize functionality
function resizeCanvas() {
const container = canvas.parentElement;
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
// Reinitialize the network when the canvas is resized
if (animationState.neurons.length > 0) {
initNetwork();
}
}
// Update stats display
function updateStatsDisplay() {
if (!activeNeuronsCount || !connectionsCount || !firingRateElement) return;
// Count active neurons
const activeCount = animationState.neurons.filter(n => n.firing || n.activationLevel > 0.2).length;
animationState.stats.activeNeurons = activeCount;
// Calculate firing rate (fires per second)
const now = Date.now();
const recentFirings = animationState.stats.firingHistory.filter(time => now - time < 1000).length;
animationState.stats.firingRate = recentFirings;
// Clean up old firing history
animationState.stats.firingHistory = animationState.stats.firingHistory.filter(time => now - time < 1000);
// Update display
activeNeuronsCount.textContent = activeCount;
connectionsCount.textContent = animationState.stats.connectionCount;
firingRateElement.textContent = `${recentFirings} Hz`;
}
// Animation loop
function animate(timestamp) {
if (!animationState.running) return;
// Calculate delta time
const deltaTime = timestamp - (animationState.lastTimestamp || timestamp);
animationState.lastTimestamp = timestamp;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update and draw neurons
animationState.neurons.forEach(neuron => {
neuron.update(deltaTime, animationState.config.firingSpeed);
neuron.draw(ctx, animationState.config.firingColor);
});
// Randomly activate neurons occasionally
if (Math.random() < 0.01 * (animationState.config.firingSpeed / 5)) {
const randomIndex = Math.floor(Math.random() * animationState.neurons.length);
const randomNeuron = animationState.neurons[randomIndex];
if (!randomNeuron.firing && Date.now() - randomNeuron.lastFireTime > randomNeuron.refractionPeriod) {
randomNeuron.activationLevel = randomNeuron.threshold;
}
}
// Update stats periodically (every ~500ms)
if (timestamp % 500 < 20) {
updateStatsDisplay();
}
// Continue animation
animationState.animationFrameId = requestAnimationFrame(animate);
}
// Start animation
function startAnimation() {
if (!animationState.running) {
animationState.running = true;
animationState.lastTimestamp = 0;
animationState.animationFrameId = requestAnimationFrame(animate);
startButton.disabled = true;
pauseButton.disabled = false;
resetButton.disabled = false;
}
}
// Pause animation
function pauseAnimation() {
if (animationState.running) {
animationState.running = false;
if (animationState.animationFrameId) {
cancelAnimationFrame(animationState.animationFrameId);
}
startButton.disabled = false;
pauseButton.disabled = true;
resetButton.disabled = false;
}
}
// Reset animation
function resetAnimation() {
pauseAnimation();
initNetwork();
startButton.disabled = false;
pauseButton.disabled = true;
resetButton.disabled = false;
}
// Initialize the visualization
function initVisualization() {
if (!canvas) return;
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Set up initial configuration from sliders if they exist
if (neuronCountSlider) {
animationState.config.neuronCount = parseInt(neuronCountSlider.value, 10);
neuronCountValue.textContent = animationState.config.neuronCount;
}
if (connectionDistanceSlider) {
animationState.config.connectionDistance = parseInt(connectionDistanceSlider.value, 10);
connectionDistanceValue.textContent = animationState.config.connectionDistance;
}
if (firingSpeedSlider) {
animationState.config.firingSpeed = parseInt(firingSpeedSlider.value, 10);
firingSpeedValue.textContent = animationState.config.firingSpeed;
}
if (firingColorSelect) {
animationState.config.firingColor = firingColorSelect.value;
}
// Initialize the network
initNetwork();
// Set button states
startButton.disabled = false;
pauseButton.disabled = true;
resetButton.disabled = true;
}
// Handle configuration changes
function setupControlListeners() {
if (neuronCountSlider) {
neuronCountSlider.addEventListener('input', () => {
animationState.config.neuronCount = parseInt(neuronCountSlider.value, 10);
neuronCountValue.textContent = animationState.config.neuronCount;
});
neuronCountSlider.addEventListener('change', () => {
// Only reinitialize network when slider interaction ends
resetAnimation();
});
}
if (connectionDistanceSlider) {
connectionDistanceSlider.addEventListener('input', () => {
animationState.config.connectionDistance = parseInt(connectionDistanceSlider.value, 10);
connectionDistanceValue.textContent = animationState.config.connectionDistance;
});
connectionDistanceSlider.addEventListener('change', () => {
resetAnimation();
});
}
if (firingSpeedSlider) {
firingSpeedSlider.addEventListener('input', () => {
animationState.config.firingSpeed = parseInt(firingSpeedSlider.value, 10);
firingSpeedValue.textContent = animationState.config.firingSpeed;
});
}
if (firingColorSelect) {
firingColorSelect.addEventListener('change', () => {
animationState.config.firingColor = firingColorSelect.value;
});
}
// Button event listeners
if (startButton) {
startButton.addEventListener('click', startAnimation);
}
if (pauseButton) {
pauseButton.addEventListener('click', pauseAnimation);
}
if (resetButton) {
resetButton.addEventListener('click', resetAnimation);
}
// Tab switching event from the main tab controller
document.addEventListener('tabSwitch', (e) => {
if (e.detail.tab === 'background-animation') {
// Restart animation when switching to this tab
if (animationState.neurons.length === 0) {
initNetwork();
}
if (!animationState.running) {
startAnimation();
}
} else if (animationState.running) {
// Pause animation when switching away from this tab
pauseAnimation();
}
});
}
// Initialize everything
initVisualization();
setupControlListeners();
});