📊 Dashboard de Música en Tiempo Real

Monitoriza reproducciones, BPM y más con Chart.js y WebSockets

Conexión: Desconectado
Simulación:
Frecuencia: 500ms

🎵 Reproducciones por Minuto

0
reproducciones/min

💓 BPM Promedio

120
beats per minute

📈 Popularidad por Género

🎸 Artistas Más Escuchados

🌊 Flujo de Datos en Tiempo Real

Sistema iniciado...

🔌 Conexión WebSocket

class MusicDashboard { constructor() { this.ws = null; this.isConnected = false; this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.charts = {}; this.dataHistory = { rpm: [], bpm: [], genres: {}, artists: {} }; } connect() { try { // Conectar a WebSocket this.ws = new WebSocket('ws://localhost:8080/music-stream'); this.ws.onopen = () => { console.log('WebSocket connected'); this.isConnected = true; this.reconnectAttempts = 0; this.updateConnectionStatus('connected'); this.addStreamEntry('Conexión establecida', 'success'); }; this.ws.onmessage = (event) => { const data = JSON.parse(event.data); this.processData(data); }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); this.addStreamEntry('Error en conexión', 'error'); }; this.ws.onclose = () => { console.log('WebSocket disconnected'); this.isConnected = false; this.updateConnectionStatus('disconnected'); // Intentar reconectar if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; setTimeout(() => this.connect(), 3000 * this.reconnectAttempts); this.addStreamEntry(`Reconectando... intento ${this.reconnectAttempts}`, 'warning'); } }; } catch (error) { console.error('Failed to connect:', error); this.addStreamEntry('Error al conectar: ' + error.message, 'error'); } } disconnect() { if (this.ws) { this.ws.close(); this.ws = null; } } processData(data) { // Procesar diferentes tipos de datos switch (data.type) { case 'rpm': this.updateRPM(data.value, data.timestamp); break; case 'bpm': this.updateBPM(data.value, data.timestamp); break; case 'genre': this.updateGenre(data.genre, data.count); break; case 'artist': this.updateArtist(data.artist, data.count); break; } // Agregar entrada al stream this.addStreamEntry(`${data.type.toUpperCase()}: ${JSON.stringify(data)}`, 'info'); } updateRPM(value, timestamp) { this.dataHistory.rpm.push({ value, timestamp }); // Mantener solo últimos 60 valores if (this.dataHistory.rpm.length > 60) { this.dataHistory.rpm.shift(); } // Actualizar UI document.getElementById('rpmValue').textContent = Math.round(value); // Actualizar gráfico if (this.charts.rpm) { this.updateLineChart(this.charts.rpm, this.dataHistory.rpm); } } updateBPM(value, timestamp) { this.dataHistory.bpm.push({ value, timestamp }); if (this.dataHistory.bpm.length > 60) { this.dataHistory.bpm.shift(); } document.getElementById('bpmValue').textContent = Math.round(value); if (this.charts.bpm) { this.updateLineChart(this.charts.bpm, this.dataHistory.bpm); } } updateGenre(genre, count) { this.dataHistory.genres[genre] = count; if (this.charts.genre) { this.updateBarChart(this.charts.genre, this.dataHistory.genres); } } updateArtist(artist, count) { this.dataHistory.artists[artist] = count; if (this.charts.artists) { this.updateBarChart(this.charts.artists, this.dataHistory.artists); } } updateConnectionStatus(status) { const indicator = document.getElementById('connectionStatus'); const indicators = document.querySelectorAll('.status-indicator'); indicators.forEach(ind => { ind.className = `status-indicator status-${status}`; }); indicator.innerHTML = ` ${status === 'connected' ? 'Conectado' : status === 'disconnected' ? 'Desconectado' : 'Sincronizando'} `; } addStreamEntry(message, type) { const stream = document.getElementById('dataStream'); const entry = document.createElement('div'); entry.className = 'stream-entry'; entry.style.borderLeftColor = type === 'error' ? '#f44336' : type === 'success' ? '#4caf50' : '#ff9800'; entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; stream.appendChild(entry); stream.scrollTop = stream.scrollHeight; // Limitar entradas if (stream.children.length > 100) { stream.removeChild(stream.firstChild); } } }

📊 Configuración de Gráficos con Chart.js

class ChartManager { constructor() { this.charts = {}; this.chartColors = { primary: '#bb86fc', secondary: '#03dac6', accent: '#ff6b6b', warning: '#ff9800', success: '#4caf50' }; } initCharts() { // Gráfico de línea para RPM this.charts.rpm = new Chart(document.getElementById('rpmChart'), { type: 'line', data: { labels: [], datasets: [{ label: 'RPM', data: [], borderColor: this.chartColors.primary, backgroundColor: this.chartColors.primary + '20', tension: 0.4, fill: true }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { beginAtZero: true, max: 200, ticks: { color: '#e0e0e0' }, grid: { color: '#333' } } } } }); // Gráfico de línea para BPM this.charts.bpm = new Chart(document.getElementById('bpmChart'), { type: 'line', data: { labels: [], datasets: [{ label: 'BPM', data: [], borderColor: this.chartColors.secondary, backgroundColor: this.chartColors.secondary + '20', tension: 0.4, fill: true }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { min: 60, max: 200, ticks: { color: '#e0e0e0' }, grid: { color: '#333' } } } } }); // Gráfico de barras para géneros this.charts.genre = new Chart(document.getElementById('genreChart'), { type: 'doughnut', data: { labels: [], datasets: [{ data: [], backgroundColor: [ this.chartColors.primary, this.chartColors.secondary, this.chartColors.accent, this.chartColors.warning, this.chartColors.success ] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#e0e0e0' } } } } }); // Gráfico de barras para artistas this.charts.artists = new Chart(document.getElementById('artistsChart'), { type: 'bar', data: { labels: [], datasets: [{ label: 'Reproducciones', data: [], backgroundColor: this.chartColors.primary }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { ticks: { color: '#e0e0e0', maxRotation: 45 }, grid: { display: false } }, y: { ticks: { color: '#e0e0e0' }, grid: { color: '#333' } } } } }); } updateLineChart(chart, data) { const labels = data.map((_, index) => index); const values = data.map(item => item.value); chart.data.labels = labels; chart.data.datasets[0].data = values; chart.update('none'); // Update sin animación para mejor performance } updateBarChart(chart, data) { const sortedData = Object.entries(data) .sort(([,a], [,b]) => b - a) .slice(0, 10); chart.data.labels = sortedData.map(([key]) => key); chart.data.datasets[0].data = sortedData.map(([, value]) => value); chart.update('none'); } updateDoughnutChart(chart, data) { const sortedData = Object.entries(data) .sort(([,a], [,b]) => b - a) .slice(0, 5); chart.data.labels = sortedData.map(([key]) => key); chart.data.datasets[0].data = sortedData.map(([, value]) => value); chart.update('none'); } }
← Volver a Tutoriales