Total Impressions
${(totalImp/1000000).toFixed(2)}M
Engagement Rate
${(totalEng / totalImp * 100).toFixed(2)}%
Avg. Sentiment
${getSentimentLabel(avgSent)}
Merch Sales
${formatCurrency(totalSales)}
`;
}
const renderChart = (id, type, data, options) => {
const ctx = document.getElementById(id)?.getContext('2d');
if(!ctx) return;
if(charts[id]) charts[id].destroy();
charts[id] = new Chart(ctx, { type, data, options });
};
function renderCharts(data) {
const chartOptions = { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: '#e2e8f0' } } }, scales: { x: { ticks:{color:'#94a3b8'} }, y: { ticks:{color:'#94a3b8'} } } };
// Trend Chart
const dailyData = data.reduce((acc, r) => {
const date = r.date.split('T')[0];
if(!acc[date]) acc[date] = { eng: 0, sent: [], count: 0 };
acc[date].eng += r.engagement_count;
acc[date].sent.push(r.sentiment_score);
acc[date].count++;
return acc;
}, {});
const labels = Object.keys(dailyData).sort();
renderChart('fan-trend-chart', 'bar', { labels, datasets: [
{ type: 'bar', label: 'Total Engagement', data: labels.map(d => dailyData[d].eng), backgroundColor: 'rgba(59, 130, 246, 0.5)', yAxisID: 'y' },
{ type: 'line', label: 'Avg Sentiment', data: labels.map(d => dailyData[d].sent.reduce((a,b)=>a+b,0)/dailyData[d].count), borderColor: '#f97316', yAxisID: 'y1' }
]}, { ...chartOptions, scales: { ...chartOptions.scales, y: {position:'left', title:{display:true, text:'Engagement'}}, y1: {position:'right', title:{display:true, text:'Sentiment Score'}, grid:{drawOnChartArea:false}} }});
// Platform Pie
const byPlatform = data.reduce((acc, r) => { acc[r.platform] = (acc[r.platform] || 0) + r.engagement_count; return acc; }, {});
renderChart('fan-platform-pie-chart', 'doughnut', {
labels: Object.keys(byPlatform), datasets: [{ data: Object.values(byPlatform) }]
}, { ...chartOptions, plugins: { ...chartOptions.plugins, title: { display: true, text: 'Engagement Share', color: '#fff' } } });
// Platform Sentiment Bar
const platforms = [...new Set(data.map(r => r.platform))];
const sentimentByPlatform = platforms.map(p => {
const platformData = data.filter(r => r.platform === p);
return {
Positive: platformData.filter(r => getSentimentLabel(r.sentiment_score) === 'Positive').reduce((s,r)=>s+r.engagement_count,0),
Neutral: platformData.filter(r => getSentimentLabel(r.sentiment_score) === 'Neutral').reduce((s,r)=>s+r.engagement_count,0),
Negative: platformData.filter(r => getSentimentLabel(r.sentiment_score) === 'Negative').reduce((s,r)=>s+r.engagement_count,0),
}
});
renderChart('fan-platform-bar-chart', 'bar', {
labels: platforms, datasets: [
{ label: 'Positive', data: sentimentByPlatform.map(d => d.Positive), backgroundColor: '#22c55e' },
{ label: 'Neutral', data: sentimentByPlatform.map(d => d.Neutral), backgroundColor: '#64748b' },
{ label: 'Negative', data: sentimentByPlatform.map(d => d.Negative), backgroundColor: '#ef4444' }
]}, { ...chartOptions, scales: { x: { stacked: true }, y: { stacked: true } } });
}
window.fanDownloadPDF = () => {
html2canvas(document.getElementById('fan-pdf-content'), { backgroundColor: '#0f172a', scale: 2 }).then(canvas => {
const pdf = new jspdf.jsPDF({ orientation: 'landscape', unit: 'pt', format: 'a4' });
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 40, 40, pdf.internal.pageSize.getWidth() - 80, 0);
pdf.save('Fan_Engagement_Dashboard.pdf');
});
};
loadSampleData();
});