Implementación y simulación de una cola en JavaScript utilizando el elemento canvas.
Las estructuras de datos nos ayudan a organizar la información de manera que podamos acceder y modificarla según nuestras necesidades. Una de las más comunes es la cola (queue), que sigue el principio de “primero en entrar, primero en salir” (FIFO). En este artículo, creamos una implementación en JavaScript y la representamos gráficamente con el elemento <canvas>
.
Una estructura de datos de tipo cola se caracteriza por tener dos métodos básicos, uno para cuando se añade un elemento en la cola enqueue
y otro para cuando un elemento sale de la cola dequeue
.
Para añadir un elemento a la cola basta con añadir el elemento en el array:
function enqueue(timestamp) { let newDot = { id: nextId++, }; queueDots.push(newDot)}
Cuando un elemento sale de la cola lo que haremos es quitar el primer elemento del array:
function dequeue() { let dot = queueDots.shift();}
A partir de estos dos conceptos de enqueue
y dequeue
podemos desarrollar una simulación animada parar ver como van llegando elementos de forma aleatoria a una cola y luego van saliendo por orden de llegada:
<html>
<head> <meta charset="utf-8" /> <title>Queue Simulation</title> <style> canvas { border: 1px solid #ccc; background-color: #f9f9f9; }
body { font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; flex-direction: column; height: 100vh; margin: 0; background-color: #e0e0e0; }
.actions { padding-top: 4px; } </style></head>
<body> <h1>Cola FIFO</h1> <canvas id="canvas" width="800" height="200"></canvas> <div class="actions"> <button id="pauseBtn">Pausa</button> <button id="resetBtn">Reiniciar</button> </div> <script> // Get canvas and context const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d');
// Initialize arrays and variables let queueDots = []; // Dots in the queue let exitingDots = []; // Dots animating out let nextEnqueueTime = 0; // Time for next enqueue let nextDequeueTime = 0; // Time for next dequeue let nextId = 1; // Dot ID counter let lastEvent = null; // Last event for display let isPaused = false; // Pause state
// Constants const POSITION_START_X = 50; // Queue front X const POSITION_SPACING = 35; // Spacing between dots const DOT_RADIUS = 15; // Dot radius const DOT_Y = 100; // Fixed Y position const MAX_DOTS = 10; // Max queue size
// Cubic Bézier easing function function easeInOutCubic(t) { if (t < 0) return 0; if (t > 1) return 1; return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }
// Enqueue a new dot function enqueue(timestamp) { let newDot = { currentX: canvas.width, currentY: DOT_Y, id: nextId++, startX: canvas.width, startTime: timestamp, duration: 500, previousTargetX: null }; queueDots.push(newDot);
}
// Dequeue the first dot function dequeue() { let dot = queueDots.shift(); exitingDots.push(dot); }
// Animation loop function animate(timestamp) { if (isPaused) return;
// Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw dashed finish line ctx.setLineDash([5, 5]); ctx.beginPath(); ctx.moveTo(POSITION_START_X - DOT_RADIUS - 8, 70); ctx.lineTo(POSITION_START_X - DOT_RADIUS - 8, 130); ctx.strokeStyle = 'black'; ctx.stroke(); ctx.setLineDash([]);
// Display queue info ctx.font = '16px Arial'; ctx.fillStyle = 'black'; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; ctx.fillText(`Tamaño cola: ${queueDots.length}`, 10, 20); if (lastEvent && timestamp - lastEvent.time < 1000) { ctx.fillStyle = '#333'; ctx.fillText(lastEvent.type, 10, 40); }
// Set targets queueDots.forEach((dot, index) => { dot.targetX = POSITION_START_X + index * POSITION_SPACING; }); exitingDots.forEach(dot => { dot.targetX = -50; });
// Update positions with easing const allDots = [...queueDots, ...exitingDots]; allDots.forEach(dot => { if (dot.targetX !== dot.previousTargetX) { dot.startX = dot.currentX; dot.startTime = timestamp; dot.duration = 500; dot.previousTargetX = dot.targetX; } let t = (timestamp - dot.startTime) / dot.duration; if (t > 1) t = 1; const easedT = easeInOutCubic(t); dot.currentX = dot.startX + (dot.targetX - dot.startX) * easedT; });
// Remove exited dots exitingDots = exitingDots.filter(dot => dot.currentX > -50);
// Draw dots ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; allDots.forEach(dot => { ctx.beginPath(); ctx.arc(dot.currentX, dot.currentY, DOT_RADIUS, 0, 2 * Math.PI); ctx.fillStyle = dot.targetX === -50 ? 'red' : 'blue'; ctx.fill(); ctx.fillStyle = 'white'; ctx.fillText(dot.id, dot.currentX, dot.currentY); });
// Handle events if (timestamp > nextEnqueueTime && queueDots.length < MAX_DOTS) { enqueue(timestamp); lastEvent = { type: 'Entrar en la cola', time: timestamp }; nextEnqueueTime = timestamp + Math.random() * 2000 + 1000; } if (timestamp > nextDequeueTime && queueDots.length >= 5 && queueDots.length >= MAX_DOTS) { dequeue(); lastEvent = { type: 'Salir de la cola', time: timestamp }; nextDequeueTime = timestamp + Math.random() * 2000 + 1000; }
requestAnimationFrame(animate); }
// Start animation requestAnimationFrame(animate);
// Button event listeners const pauseBtn = document.getElementById('pauseBtn'); const resetBtn = document.getElementById('resetBtn');
pauseBtn.addEventListener('click', () => { isPaused = !isPaused; pauseBtn.textContent = isPaused ? 'Continuar' : 'Pausar'; if (!isPaused) requestAnimationFrame(animate); });
resetBtn.addEventListener('click', () => { queueDots = []; exitingDots = []; nextId = 1; lastEvent = null; nextEnqueueTime = 0; nextDequeueTime = 0; isPaused = false; pauseBtn.textContent = 'Pausar'; requestAnimationFrame(animate); }); </script></body>
</html>
Esta implementación nos permite comprender cómo funciona una cola y visualizar su comportamiento en tiempo real. Podemos extenderla con animaciones o agregar validaciones para hacerla más interactiva.