let sequencingChart; let covidLineageFrequencyChart; let influenzaSubtypeFrequencyChart; document.addEventListener("DOMContentLoaded", () => { //const canvas = document.getElementById('sequencingChart'); //if (!canvas) return; new DashboardFilter((startYear, startWeek, endYear, endWeek) => { fetch(`/api/dashboard/sequencing?surveillance_id=${window.SURVEILLANCE_ID}&start_year=${startYear}&start_week=${startWeek}&end_year=${endYear}&end_week=${endWeek}`) .then(res => res.json()) .then(data => { //renderSequencingChart(data.trend || []); renderSequencingCountChart(data.trend || []); renderSequencingPieChart(data.distribution || []); renderSequencingTotalChart(data.trend || []); }); loadCovidLineageFrequency('week', startYear, startWeek, endYear, endWeek) loadInfluenzaSubtypeFrequency('week', startYear, startWeek, endYear, endWeek) const elements = document.querySelectorAll(".report-period"); elements.forEach(el => { el.textContent = 'Week ' + startWeek + ' of '+startYear+' to ' + 'Week ' + endWeek + ' of ' + endYear }); }); }); function renderSequencingCountChart(rows) { const ctx = document.getElementById('sequencingCountChart'); Chart.getChart('sequencingCountChart')?.destroy(); rows = processTopSubtypes(rows); const weeks = [...new Set(rows.map(r => r.period))]; const subtypes = [...new Set(rows.map(r => r.subtype))]; const colors = [ '#2563eb', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#14b8a6', '#f97316', '#84cc16' ]; const datasets = subtypes.map((sub, i) => ({ label: sub, data: weeks.map(w => { const found = rows.find(r => r.period === w && r.subtype === sub); return found ? found.total : 0; }), backgroundColor: hexToRGBA(colors[i % colors.length], 0.3)// colors[i % colors.length] })); new Chart(ctx, { type: 'bar', data: { labels: weeks.map(w => `W${w}`), datasets }, options: { maintainAspectRatio: false, scales: { x: { stacked: true }, y: { stacked: true } }, plugins: { legend: { display: true, position: 'bottom', align: 'center' }, datalabels: { display: true } } } }); } function renderSequencingPieChart(rows) { const ctx = document.getElementById('sequencingPieChart'); Chart.getChart('sequencingPieChart')?.destroy(); const top = rows.slice(0, 8); const labels = top.map(r => r.subtype); const values = top.map(r => r.total); new Chart(ctx, { type: 'doughnut', data: { labels, datasets: [{ data: values }] }, options: { maintainAspectRatio: false, plugins: { legend: { position: 'bottom' }, } } }); } function renderSequencingTotalChart(rows) { const ctx = document.getElementById('sequencingTotalChart'); Chart.getChart('sequencingTotalChart')?.destroy(); const totals = {}; rows.forEach(r => { totals[r.period] = (totals[r.period] || 0) + Number(r.total); }); const weeks = Object.keys(totals); const values = Object.values(totals); new Chart(ctx, { type: 'bar', data: { labels: weeks.map(w => `W${w}`), datasets: [{ label: 'Total Samples', data: values, backgroundColor: '#0B8F3C' }] }, options: { maintainAspectRatio: false, plugins: { legend: { display: false }, datalabels: { display: false } }, } }); } function processTopSubtypes(rows) { const totals = {}; rows.forEach(r => { totals[r.subtype] = (totals[r.subtype] || 0) + Number(r.total); }); const sorted = Object.entries(totals) .sort((a, b) => b[1] - a[1]); const top = sorted.slice(0, 8).map(([k]) => k); return rows.map(r => { if (!top.includes(r.subtype)) { return { ...r, subtype: 'Others' }; } return r; }); } function renderSequencingChart(rows) { rows = processTopSubtypes(rows); const ctx = document.getElementById('sequencingChart'); if (sequencingChart) { sequencingChart.destroy(); } const colors = [ '#2563eb', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#14b8a6', '#f97316', '#84cc16', '#6b7280' ]; const aggregated = {}; rows.forEach(r => { const key = `${r.period}_${r.subtype}`; if (!aggregated[key]) { aggregated[key] = { ...r }; } else { aggregated[key].total += Number(r.total); } }); console.log('aggregated', aggregated) const cleanRows = Object.values(aggregated); const weeks = [...new Set(cleanRows.map(r => r.period))]; const subtypes = [...new Set(cleanRows.map(r => r.subtype))] .sort((a, b) => { const sum = s => cleanRows .filter(r => r.subtype === s) .reduce((t, r) => t + r.total, 0); return sum(b) - sum(a); }); const datasets = subtypes.map((sub, i) => { return { label: sub, data: weeks.map(w => { const weekRows = cleanRows.filter(r => r.period === w); const total = weekRows.reduce((s, r) => s + Number(r.total), 0); const found = weekRows.find(r => r.subtype === sub); return total ? ((found?.total || 0) / total) * 100 : 0; }), fill: true, tension: 0.3, backgroundColor: colors[i % colors.length], borderColor: colors[i % colors.length] }; }); sequencingChart = new Chart(ctx, { type: 'line', data: { labels: weeks.map(w => `W${w}`), datasets: datasets }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'bottom', align: 'center' }, datalabels: { display: false } }, scales: { x: { stacked: true }, y: { stacked: true, max: 100, ticks: { callback: v => v + '%' } } } } }); } function hexToRGBA(hex, alpha) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } function loadCovidLineageFrequency(periodType, startYear, startWeek, endYear, endWeek) { fetch(`/api/dashboard/covid-lineage-frequency?period_type=${periodType}&start_year=${startYear}&start_week=${startWeek}&end_year=${endYear}&end_week=${endWeek}`) .then(res => res.json()) .then(data => { // Extract unique weeks (X-axis) const weeks = [...new Set(data.map(item => item.week))].sort(); // Extract unique lineages const lineages = [...new Set(data.map(item => item.lineage))]; // Color palette const colors = [ '#84cc16','#22c55e','#06b6d4','#3b82f6', '#6366f1','#a855f7','#ec4899','#ef4444', '#f97316','#eab308' ]; // Build datasets const datasets = lineages.map((lineage, index) => { const lineageData = weeks.map(week => { const found = data.find( item => item.week === week && item.lineage === lineage ); return found ? found.total : 0; }); return { label: lineage, data: lineageData, fill: true, // area fill tension: 0.4, // smooth curve borderColor: 'transparent', // hide the line borderWidth: 0, pointRadius: 0, // hide points backgroundColor: hexToRGBA(colors[index % colors.length], 0.3), stack: 'total' }; }); // Destroy previous chart if exists if (covidLineageFrequencyChart) covidLineageFrequencyChart.destroy(); const ctx = document.getElementById('covidLineageFrequency').getContext('2d'); covidLineageFrequencyChart = new Chart(ctx, { type: 'line', data: { labels: weeks, datasets: datasets }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, plugins: { legend: { display: false // hide default legend }, tooltip: { mode: 'index', intersect: false }, datalabels: { display: false // hide labels } }, scales: { x: { stacked: true, title: { display: true, text: 'Week' }, grid:{ display: false } }, y: { stacked: true, beginAtZero: true, title: { display: true, text: 'Relative Frequency' }, } } } }); // ------------------------- // Custom right-side scrollable legend // ------------------------- const legendContainer = document.getElementById('legendContainer'); legendContainer.innerHTML = ''; // clear old legend datasets.forEach((dataset, index) => { const item = document.createElement('div'); item.style.display = 'flex'; item.style.alignItems = 'center'; item.style.marginBottom = '4px'; item.style.fontSize = '11px'; item.style.cursor = 'pointer'; item.innerHTML = ` ${dataset.label} `; item.addEventListener('click', () => { const meta = covidLineageFrequencyChart.getDatasetMeta(index); // If the clicked dataset is already the only visible one, show all const allHidden = datasets.every((d, i) => covidLineageFrequencyChart.getDatasetMeta(i).hidden || i === index); if (!allHidden) { // Hide all datasets datasets.forEach((d, i) => { covidLineageFrequencyChart.getDatasetMeta(i).hidden = true; }); // Show only clicked meta.hidden = false; } else { // Show all datasets datasets.forEach((d, i) => { covidLineageFrequencyChart.getDatasetMeta(i).hidden = false; }); } covidLineageFrequencyChart.update(); // Update legend opacity Array.from(legendContainer.children).forEach((child, i) => { const metaItem = covidLineageFrequencyChart.getDatasetMeta(i); child.style.opacity = metaItem.hidden ? 0.5 : 1; }); }); legendContainer.appendChild(item); }); // Scrollable CSS (in case legend is long) legendContainer.style.maxHeight = '375px'; legendContainer.style.overflowY = 'auto'; legendContainer.style.padding = '8px'; legendContainer.style.borderRadius = '0px'; }); } function loadInfluenzaSubtypeFrequency(periodType, startYear, startWeek, endYear, endWeek) { fetch(`/api/dashboard/influenza-relative-frequency-sequencing?period_type=${periodType}&start_year=${startYear}&start_week=${startWeek}&end_year=${endYear}&end_week=${endWeek}`) .then(res => res.json()) .then(data => { // Extract unique weeks (X-axis) const weeks = [...new Set(data.map(item => item.week))].sort(); const colors = [ '#84cc16','#22c55e','#06b6d4','#3b82f6', '#6366f1','#a855f7','#ec4899','#ef4444', '#f97316','#eab308' ]; // Extract unique lineages const lineages = [...new Set(data.map(item => item.lineage))]; // Build datasets const datasets = lineages.map((lineage, index) => { const lineageData = weeks.map(week => { const found = data.find( item => item.week === week && item.lineage === lineage ); return found ? found.total : 0; }); return { label: lineage, data: lineageData, fill: true, // area fill tension: 0.4, // smooth curve borderColor: 'transparent', // hide the line borderWidth: 0, pointRadius: 0, // hide points backgroundColor: hexToRGBA(colors[(index*2) % colors.length], 0.8), stack: 'total' }; }); // Destroy previous chart if exists if (influenzaSubtypeFrequencyChart) influenzaSubtypeFrequencyChart.destroy(); const ctx = document.getElementById('influenzaSubtypeFrequency').getContext('2d'); influenzaSubtypeFrequencyChart = new Chart(ctx, { type: 'line', data: { labels: weeks, datasets: datasets }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, plugins: { legend: { display: false // hide default legend }, tooltip: { mode: 'index', intersect: false }, datalabels: { display: false // hide labels } }, scales: { x: { stacked: true, title: { display: true, text: 'Week' }, grid:{ display: false } }, y: { stacked: true, beginAtZero: true, title: { display: true, text: 'Relative Frequency' }, } } } }); const legendContainer = document.getElementById('legendContainerInfluenzaSubtypeFrequency'); legendContainer.innerHTML = ''; // clear old legend datasets.forEach((dataset, index) => { const item = document.createElement('div'); item.style.display = 'flex'; item.style.alignItems = 'center'; item.style.marginBottom = '4px'; item.style.fontSize = '11px'; item.style.cursor = 'pointer'; item.innerHTML = ` ${dataset.label} `; item.addEventListener('click', () => { const meta = influenzaSubtypeFrequencyChart.getDatasetMeta(index); // If the clicked dataset is already the only visible one, show all const allHidden = datasets.every((d, i) => influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden || i === index); if (!allHidden) { // Hide all datasets datasets.forEach((d, i) => { influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden = true; }); // Show only clicked meta.hidden = false; } else { // Show all datasets datasets.forEach((d, i) => { influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden = false; }); } influenzaSubtypeFrequencyChart.update(); // Update legend opacity Array.from(legendContainer.children).forEach((child, i) => { const metaItem = influenzaSubtypeFrequencyChart.getDatasetMeta(i); child.style.opacity = metaItem.hidden ? 0.5 : 1; }); }); legendContainer.appendChild(item); }); // Scrollable CSS (in case legend is long) legendContainer.style.maxHeight = '375px'; legendContainer.style.overflowY = 'auto'; legendContainer.style.padding = '8px'; legendContainer.style.borderRadius = '0px'; }); }