first commit

This commit is contained in:
2026-06-16 10:45:41 +07:00
commit ccecc0bc6b
144 changed files with 124547 additions and 0 deletions

View File

@@ -0,0 +1,778 @@
import { COLORS, SUBTYPE_COLORS } from "./globals.js";
let sequencingTotalChart;
let covidLineageFrequencyChart;
let influenzaSubtypeFrequencyChart;
document.addEventListener("DOMContentLoaded", () => {
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 => {
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 renderSequencingTotalChart(rows) {
const ctx = document.getElementById('sequencingTotalChart')?.getContext('2d');
if (!ctx) return;
if (sequencingTotalChart) 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);
sequencingTotalChart = new Chart(ctx, {
type: 'bar',
data: {
labels: weeks.map(w => `${w}`),
datasets: [{
label: 'Total Samples',
data: values,
backgroundColor: '#0B8F3C'
}]
},
options: {
maintainAspectRatio: false,
plugins: {
legend: { display: false },
datalabels: {
display: false
}
},
}
});
charts['sequencingTotalChart'] = sequencingTotalChart;
}
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 => {
// const weeks = [...new Set(data.map(item => item.week))].sort();
const totalYears = endYear - startYear;
const useYearlyView = totalYears >= 5;
let periods;
if (useYearlyView) {
periods = [...new Set(
data.map(item => item.week.split('-')[0])
)].sort((a, b) => a - b);
} else {
periods = [...new Set(
data.map(item => item.week)
)].sort();
}
const lineages = [...new Set(data.map(item => item.lineage))];
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;
// });
const lineageData = periods.map(period => {
if (useYearlyView) {
return data
.filter(item =>
item.week.split('-')[0] == period &&
item.lineage === lineage
)
.reduce((sum, item) => sum + item.total, 0);
}
const found = data.find(
item =>
item.week === period &&
item.lineage === lineage
);
return found ? found.total : 0;
});
return {
label: lineage,
data: lineageData,
fill: true,
tension: 0.4,
// borderColor: 'transparent',
borderWidth: 0,
pointRadius: 0,
backgroundColor: hexToRGBA(COLORS[index % COLORS.length], 0.3),
stack: 'total'
};
});
if (covidLineageFrequencyChart) covidLineageFrequencyChart.destroy();
const ctx = document.getElementById('covidLineageFrequency').getContext('2d');
covidLineageFrequencyChart = new Chart(ctx, {
type: 'line',
data: {
labels: periods,
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false
},
plugins: {
legend: {
display: false
},
tooltip: {
mode: 'index',
intersect: false
},
datalabels: {
display: false
}
},
scales: {
x: {
stacked: true,
grid: {
display: false
}
},
y: {
stacked: true,
beginAtZero: true,
title: {
display: true,
text: 'Relative Frequency'
},
}
}
}
});
charts['covidLineageFrequency'] = covidLineageFrequencyChart;
// -------------------------
// Custom right-side scrollable legend
// -------------------------
const legendContainer = document.getElementById('legendContainer');
legendContainer.innerHTML = '';
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 = `
<span style="width:15px;height:15px;background:${dataset.backgroundColor};display:inline-block;margin-right:8px;"></span>
${dataset.label}
`;
item.addEventListener('click', () => {
const meta = covidLineageFrequencyChart.getDatasetMeta(index);
const allHidden = datasets.every((d, i) => covidLineageFrequencyChart.getDatasetMeta(i).hidden || i === index);
if (!allHidden) {
datasets.forEach((d, i) => {
covidLineageFrequencyChart.getDatasetMeta(i).hidden = true;
});
meta.hidden = false;
} else {
datasets.forEach((d, i) => {
covidLineageFrequencyChart.getDatasetMeta(i).hidden = false;
});
}
covidLineageFrequencyChart.update();
Array.from(legendContainer.children).forEach((child, i) => {
const metaItem = covidLineageFrequencyChart.getDatasetMeta(i);
child.style.opacity = metaItem.hidden ? 0.5 : 1;
});
});
legendContainer.appendChild(item);
});
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 => {
const totalYears = endYear - startYear;
const useYearlyView = totalYears >= 5;
let periods;
if (useYearlyView) {
periods = [...new Set(
data.map(item => item.week.split('-')[0])
)].sort((a, b) => a - b);
} else {
periods = [...new Set(
data.map(item => item.week)
)].sort();
}
const lineages = [...new Set(
data.map(item => item.lineage)
)];
const lineageColors = lineages.map(
label => SUBTYPE_COLORS[label] || '#9ca3af'
);
const datasets = lineages.map((lineage, index) => {
const lineageData = periods.map(period => {
if (useYearlyView) {
return data
.filter(item =>
item.week.split('-')[0] == period &&
item.lineage === lineage
)
.reduce((sum, item) => sum + item.total, 0);
}
const found = data.find(
item =>
item.week === period &&
item.lineage === lineage
);
return found ? found.total : 0;
});
return {
label: lineage,
data: lineageData,
fill: true,
tension: 0.4,
borderWidth: 0,
pointRadius: 0,
backgroundColor: hexToRGBA(
lineageColors[index % lineageColors.length],
0.6
),
stack: 'total'
};
});
if (influenzaSubtypeFrequencyChart) {
influenzaSubtypeFrequencyChart.destroy();
}
const ctx = document
.getElementById('influenzaSubtypeFrequency')
.getContext('2d');
influenzaSubtypeFrequencyChart = new Chart(ctx, {
type: 'line',
data: {
labels: periods,
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false
},
plugins: {
legend: {
display: false
},
tooltip: {
mode: 'index',
intersect: false
},
datalabels: {
display: false
}
},
scales: {
x: {
stacked: true,
grid: {
display: false
}
},
y: {
stacked: true,
beginAtZero: true,
title: {
display: true,
text: 'Relative Frequency'
}
}
}
}
});
charts['influenzaSubtypeFrequency'] =
influenzaSubtypeFrequencyChart;
/*
|--------------------------------------------------------------------------
| CUSTOM LEGEND
|--------------------------------------------------------------------------
*/
const legendContainer =
document.getElementById(
'legendContainerInfluenzaSubtypeFrequency'
);
legendContainer.innerHTML = '';
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 = `
<span style="
width:15px;
height:15px;
background:${dataset.backgroundColor};
display:inline-block;
margin-right:8px;
"></span>
${dataset.label}
`;
item.addEventListener('click', () => {
const meta =
influenzaSubtypeFrequencyChart
.getDatasetMeta(index);
const allHidden = datasets.every(
(d, i) =>
influenzaSubtypeFrequencyChart
.getDatasetMeta(i).hidden ||
i === index
);
if (!allHidden) {
datasets.forEach((d, i) => {
influenzaSubtypeFrequencyChart
.getDatasetMeta(i)
.hidden = true;
});
meta.hidden = false;
} else {
datasets.forEach((d, i) => {
influenzaSubtypeFrequencyChart
.getDatasetMeta(i)
.hidden = false;
});
}
influenzaSubtypeFrequencyChart.update();
Array.from(legendContainer.children)
.forEach((child, i) => {
const metaItem =
influenzaSubtypeFrequencyChart
.getDatasetMeta(i);
child.style.opacity =
metaItem.hidden ? 0.5 : 1;
});
});
legendContainer.appendChild(item);
});
legendContainer.style.maxHeight = '375px';
legendContainer.style.overflowY = 'auto';
legendContainer.style.padding = '8px';
legendContainer.style.borderRadius = '0px';
});
}
// 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 => {
// const weeks = [...new Set(data.map(item => item.week))].sort();
// const lineages = [...new Set(data.map(item => item.lineage))];
// 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,
// tension: 0.4,
// borderColor: 'transparent',
// borderWidth: 0,
// pointRadius: 0,
// backgroundColor: hexToRGBA(colors[index % colors.length], 0.3),
// stack: 'total'
// };
// });
// if (covidLineageFrequencyChart) covidLineageFrequencyChart.destroy();
// const ctx = document.getElementById('covidLineageFrequency').getContext('2d');
// if (!ctx) return;
// 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'
// },
// }
// }
// }
// });
// charts['covidLineageFrequencyChart'] = covidLineageFrequencyChart;
// // -------------------------
// // Custom right-side scrollable legend
// // -------------------------
// const legendContainer = document.getElementById('legendContainer');
// legendContainer.innerHTML = '';
// 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 = `
// <span style="width:15px;height:15px;background:${dataset.backgroundColor};display:inline-block;margin-right:8px;"></span>
// ${dataset.label}
// `;
// item.addEventListener('click', () => {
// const meta = covidLineageFrequencyChart.getDatasetMeta(index);
// const allHidden = datasets.every((d, i) => covidLineageFrequencyChart.getDatasetMeta(i).hidden || i === index);
// if (!allHidden) {
// datasets.forEach((d, i) => {
// covidLineageFrequencyChart.getDatasetMeta(i).hidden = true;
// });
// meta.hidden = false;
// } else {
// datasets.forEach((d, i) => {
// covidLineageFrequencyChart.getDatasetMeta(i).hidden = false;
// });
// }
// covidLineageFrequencyChart.update();
// Array.from(legendContainer.children).forEach((child, i) => {
// const metaItem = covidLineageFrequencyChart.getDatasetMeta(i);
// child.style.opacity = metaItem.hidden ? 0.5 : 1;
// });
// });
// legendContainer.appendChild(item);
// });
// 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 => {
// const weeks = [...new Set(data.map(item => item.week))].sort();
// const lineages = [...new Set(data.map(item => item.lineage))];
// 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,
// tension: 0.4,
// borderColor: 'transparent',
// borderWidth: 0,
// pointRadius: 0,
// backgroundColor: hexToRGBA(colors[(index * 2) % colors.length], 0.8),
// stack: 'total'
// };
// });
// if (influenzaSubtypeFrequencyChart) influenzaSubtypeFrequencyChart.destroy();
// const ctx = document.getElementById('influenzaSubtypeFrequency').getContext('2d');
// if (!ctx) return;
// 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
// },
// tooltip: {
// mode: 'index',
// intersect: false
// },
// datalabels: {
// display: false
// }
// },
// scales: {
// x: {
// stacked: true,
// title: {
// display: true,
// text: 'Week'
// },
// grid: {
// display: false
// }
// },
// y: {
// stacked: true,
// beginAtZero: true,
// title: {
// display: true,
// text: 'Relative Frequency'
// },
// }
// }
// }
// });
// charts['influenzaSubtypeFrequencyChart'] = influenzaSubtypeFrequencyChart;
// const legendContainer = document.getElementById('legendContainerInfluenzaSubtypeFrequency');
// legendContainer.innerHTML = '';
// 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 = `
// <span style="width:15px;height:15px;background:${dataset.backgroundColor};display:inline-block;margin-right:8px;"></span>
// ${dataset.label}
// `;
// item.addEventListener('click', () => {
// const meta = influenzaSubtypeFrequencyChart.getDatasetMeta(index);
// const allHidden = datasets.every((d, i) => influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden || i === index);
// if (!allHidden) {
// datasets.forEach((d, i) => {
// influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden = true;
// });
// meta.hidden = false;
// } else {
// datasets.forEach((d, i) => {
// influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden = false;
// });
// }
// influenzaSubtypeFrequencyChart.update();
// Array.from(legendContainer.children).forEach((child, i) => {
// const metaItem = influenzaSubtypeFrequencyChart.getDatasetMeta(i);
// child.style.opacity = metaItem.hidden ? 0.5 : 1;
// });
// });
// legendContainer.appendChild(item);
// });
// legendContainer.style.maxHeight = '375px';
// legendContainer.style.overflowY = 'auto';
// legendContainer.style.padding = '8px';
// legendContainer.style.borderRadius = '0px';
// });
// }