Music Streaming Trends Dashboard

${formatCurrency(totalRevenue)}

Avg. $/Stream

$${avgRevenuePerStream.toFixed(6)}

`; }; const renderDataTable = () => { dataTableBody.innerHTML = ''; state.genres.forEach(g => { const row = ` ${g.name} ${formatNumber(g.streams)} ${formatNumber(g.listeners)} ${formatCurrency(g.revenue)} `; dataTableBody.innerHTML += row; }); }; const renderCharts = () => { // Genre Doughnut Chart if (genreChart) genreChart.destroy(); genreChart = new Chart(genreCanvas.getContext('2d'), { type: 'doughnut', data: { labels: state.genres.map(g => g.name), datasets: [{ data: state.genres.map(g => g.streams), backgroundColor: ['#8b5cf6', '#ec4899', '#3b82f6', '#10b981', '#f97316'], }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' } } } }); // Top Artists Bar Chart const allArtists = state.genres.flatMap(g => g.artists); allArtists.sort((a, b) => b.streams - a.streams); const topArtists = allArtists.slice(0, 5); if (artistChart) artistChart.destroy(); artistChart = new Chart(artistCanvas.getContext('2d'), { type: 'bar', data: { labels: topArtists.map(a => a.name), datasets: [{ label: 'Streams', data: topArtists.map(a => a.streams), backgroundColor: 'rgba(124, 58, 237, 0.7)', borderColor: 'rgba(124, 58, 237, 1)', borderWidth: 1 }] }, options: { indexAxis: 'y', responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { ticks: { callback: (value) => formatNumber(value) } } } } }); }; const renderConfigRows = () => { configRowsContainer.innerHTML = ''; state.genres.forEach(g => { let artistInputs = ''; g.artists.forEach(a => { artistInputs += `
`; }); const genreBlock = `

Artists

${artistInputs}
`; configRowsContainer.innerHTML += genreBlock; }); addConfigEventListeners(); }; // --- EVENT HANDLERS --- const handleConfigChange = (e) => { const genreId = parseInt(e.target.dataset.genreId); const field = e.target.dataset.field; const genre = state.genres.find(g => g.id === genreId); if (!genre) return; if (e.target.classList.contains('config-genre-input')) { genre[field] = field === 'name' ? e.target.value : parseFloat(e.target.value) || 0; } else if (e.target.classList.contains('config-artist-input')) { const artistId = parseInt(e.target.dataset.artistId); const artist = genre.artists.find(a => a.id === artistId); if (artist) { artist[field] = field === 'name' ? e.target.value : parseFloat(e.target.value) || 0; } } renderAll(); }; const handleAddGenre = () => { const newId = state.genres.length > 0 ? Math.max(...state.genres.map(g => g.id)) + 1 : 1; state.genres.push({ id: newId, name: "New Genre", streams: 0, listeners: 0, revenue: 0, artists: [] }); renderAll(); }; const handleRemoveGenre = (e) => { const genreId = parseInt(e.target.dataset.genreId); state.genres = state.genres.filter(g => g.id !== genreId); renderAll(); }; const handleAddArtist = (e) => { const genreId = parseInt(e.target.dataset.genreId); const genre = state.genres.find(g => g.id === genreId); if (genre) { const newArtistId = genre.artists.length > 0 ? Math.max(...genre.artists.map(a => a.id)) + 1 : genreId * 100 + 1; genre.artists.push({ id: newArtistId, name: "New Artist", streams: 0 }); } renderAll(); }; const handleRemoveArtist = (e) => { const genreId = parseInt(e.target.dataset.genreId); const artistId = parseInt(e.target.dataset.artistId); const genre = state.genres.find(g => g.id === genreId); if (genre) { genre.artists = genre.artists.filter(a => a.id !== artistId); } renderAll(); }; const addConfigEventListeners = () => { document.querySelectorAll('.config-genre-input, .config-artist-input').forEach(input => input.addEventListener('change', handleConfigChange)); document.querySelectorAll('.remove-genre-btn').forEach(btn => btn.addEventListener('click', handleRemoveGenre)); document.querySelectorAll('.add-artist-btn').forEach(btn => btn.addEventListener('click', handleAddArtist)); document.querySelectorAll('.remove-artist-btn').forEach(btn => btn.addEventListener('click', handleRemoveArtist)); }; const handleDownloadPdf = () => { const { jsPDF } = window.jspdf; const pdfContent = document.getElementById('pdf-content'); // Hide non-PDF elements document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'hidden'); // Temporarily disable chart animations for clean capture Chart.defaults.animation = false; html2canvas(pdfContent, { scale: 2, useCORS: true, backgroundColor: '#ffffff' }).then(canvas => { // Restore visibility and animations document.querySelectorAll('.no-print').forEach(el => el.style.visibility = 'visible'); Chart.defaults.animation = true; const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const imgWidth = pdfWidth - 20; // with margin const imgHeight = canvas.height * imgWidth / canvas.width; pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight); pdf.save('Music-Streaming-Trends.pdf'); }); }; // --- TABBING LOGIC --- let currentTabIndex = 0; const updateTabButtons = () => { prevTabBtn.disabled = currentTabIndex === 0; nextTabBtn.disabled = currentTabIndex === tabContents.length - 1; }; const switchTab = (index) => { tabButtons.forEach(btn => btn.classList.remove('active')); tabContents.forEach(content => content.classList.remove('active')); tabButtons[index].classList.add('active'); tabContents[index].classList.add('active'); currentTabIndex = index; updateTabButtons(); }; tabButtons.forEach((button, index) => button.addEventListener('click', () => switchTab(index))); prevTabBtn.addEventListener('click', () => { if (currentTabIndex > 0) switchTab(currentTabIndex - 1); }); nextTabBtn.addEventListener('click', () => { if (currentTabIndex < tabContents.length - 1) switchTab(currentTabIndex + 1); }); // --- INITIALIZATION --- if (kpiCardsContainer && addGenreBtn && downloadPdfBtn) { addGenreBtn.addEventListener('click', handleAddGenre); downloadPdfBtn.addEventListener('click', handleDownloadPdf); renderAll(); updateTabButtons(); } else { console.error("Essential dashboard elements could not be found."); } });
Scroll to Top