This commit is contained in:
2026-06-22 08:47:13 +07:00
parent a6f8551ab8
commit f518d7d184
7 changed files with 346 additions and 263 deletions

View File

@@ -318,7 +318,7 @@ function buildMixedTrendChart(canvasId, labels, samples, lines) {
type: 'bar',
label: 'Total Cases',
data: samples,
backgroundColor: '#d34646',
backgroundColor: '#007ce8',
maxBarThickness: 60,
yAxisID: 'y',

View File

@@ -242,7 +242,7 @@ function prepareMapForExport() {
[14.7, 107.6]
]);
map.fitBounds(bounds);
window.map.fitBounds(bounds);
}
async function exportFullDashboard() {
@@ -273,7 +273,7 @@ async function exportFullDashboard() {
if (mapEl && originalMapHTML !== null) {
mapEl.innerHTML = originalMapHTML;
map.invalidateSize();
window.map.invalidateSize();
}
const img = canvas.toDataURL("image/jpeg", 0.95);
@@ -329,8 +329,8 @@ async function getMapImage() {
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, width, height);
const zoom = map.getZoom();
const projection = map.options.crs;
const zoom = window.map.getZoom();
const projection = window.map.options.crs;
const projectedRings = [];
@@ -341,8 +341,7 @@ async function getMapImage() {
const rows = window.latestProvinceData || [];
rows.forEach(r => {
const province = normalizeProvince(r.patient_province, window.validProvinces);
console.log(province, totals[province]);
const province = window.normalizeProvince(r.patient_province, window.validProvinces);
if (!province) return;
if (!totals[province]) {
@@ -360,7 +359,7 @@ async function getMapImage() {
return "#f3f4f600";
}
map.eachLayer(layer => {
window.map.eachLayer(layer => {
if (!layer.toGeoJSON) return;
if (layer instanceof L.CircleMarker) {

View File

@@ -1,6 +1,6 @@
export const COLORS = [
'#2563eb', // blue
'#ef4444', // blue
'#10b981', // emerald
'#f59e0b', // amber
'#ef4444', // red

View File

@@ -93,7 +93,8 @@ function renderTrend(valueId, changeId, current, previous, suffix = '') {
function getSubtypeColor(label, index = 0) {
const specialColors = {
'A/H5N1': '#dc2626'
'A/H5N1': '#dc2626',
'Influenza': '#b90c00'
};
return specialColors[label]
@@ -131,7 +132,10 @@ function renderDashboard(data = {}) {
'doughnut',
(data.pathogen_distribution || [])
.sort((a, b) => b.total - a.total),
'pathogen'
'pathogen',
'total',
getSubtypeColor
);
buildDistributionChart(
@@ -254,6 +258,8 @@ function normalizeProvince(name, validSet) {
return match || null;
}
window.normalizeProvince = normalizeProvince;
function renderProvinceHeatmap(rows = []) {
window.latestProvinceData = rows;
@@ -264,7 +270,7 @@ function renderProvinceHeatmap(rows = []) {
map = L.map('provinceMap')
.setView([12.7, 104.9], 7);
window.map = map;
L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
@@ -593,7 +599,7 @@ function renderProgramTrend(rows = []) {
}),
color: '#1976D2'
color: '#d21919'
},
{
@@ -722,20 +728,33 @@ function renderProgramTrend(rows = []) {
);
}
function renderAFITrend(
rows = [],
data = {},
canvasId,
COLORS,
type = 'trend'
) {
/*
|--------------------------------------------------------------------------
| DATA
|--------------------------------------------------------------------------
*/
const rows =
data.rows || [];
const totals =
data.totals || [];
/*
|--------------------------------------------------------------------------
| EMPTY
|--------------------------------------------------------------------------
*/
if (!rows.length) {
if (!totals.length) {
if (type === 'donut') {
@@ -761,6 +780,57 @@ function renderAFITrend(
return;
}
/*
|--------------------------------------------------------------------------
| LABELS
|--------------------------------------------------------------------------
*/
const labels = [...new Set(
totals.map(r =>
`${r.year}-W${r.period}`
)
)].sort((a, b) => {
const [yearA, weekA] =
a.split('-W').map(Number);
const [yearB, weekB] =
b.split('-W').map(Number);
if (yearA !== yearB) {
return yearA - yearB;
}
return weekA - weekB;
});
/*
|--------------------------------------------------------------------------
| TOTAL CASES (BLUE BARS)
|--------------------------------------------------------------------------
*/
const totalCases = labels.map(label => {
const row = totals.find(r =>
`${r.year}-W${r.period}`
=== label
);
return Number(
row?.total_cases || 0
);
});
/*
|--------------------------------------------------------------------------
| DONUT
@@ -800,16 +870,26 @@ function renderAFITrend(
'total'
);
if (charts[canvasId]) {
/*
|--------------------------------------------------------------------------
| DONUT CENTER TOTAL
|--------------------------------------------------------------------------
|
| MUST MATCH SUM OF BLUE BARS
|
*/
const totalCases = rows.reduce(
(sum, r) =>
sum + Number(r.total_cases || 0),
const donutTotal =
totalCases.reduce(
(a, b) => a + b,
0
);
if (charts[canvasId]) {
charts[canvasId].$afiTotalCases =
totalCases;
donutTotal;
charts[canvasId].update();
@@ -818,90 +898,6 @@ function renderAFITrend(
return;
}
/*
|--------------------------------------------------------------------------
| YEARLY VIEW
|--------------------------------------------------------------------------
*/
const years = [...new Set(
rows.map(r => Number(r.year))
)];
const totalYears =
Math.max(...years) - Math.min(...years);
const useYearlyView =
totalYears >= 5;
/*
|--------------------------------------------------------------------------
| LABELS
|--------------------------------------------------------------------------
*/
let labels;
if (useYearlyView) {
labels = [...new Set(
rows.map(r => String(r.year))
)].sort();
} else {
labels = [...new Set(
rows.map(r =>
`${r.year}-W${r.period}`
)
)].sort((a, b) => {
const [yearA, weekA] =
a.split('-W').map(Number);
const [yearB, weekB] =
b.split('-W').map(Number);
if (yearA !== yearB) {
return yearA - yearB;
}
return weekA - weekB;
});
}
/*
|--------------------------------------------------------------------------
| TOTAL CASES
|--------------------------------------------------------------------------
*/
const totalCases = labels.map(label => {
if (useYearlyView) {
return rows
.filter(r =>
String(r.year) === label
)
.reduce(
(sum, r) =>
sum + Number(r.total_cases || 0),
0
);
}
const row = rows.find(r =>
`${r.year}-W${r.period}` === label
);
return row?.total_cases || 0;
});
/*
|--------------------------------------------------------------------------
| PATHOGENS
@@ -929,26 +925,6 @@ function renderAFITrend(
data: labels.map(label => {
if (useYearlyView) {
const filtered = rows.filter(r =>
String(r.year) === label &&
r.pathogen === pathogen
);
const total =
filtered.reduce(
(sum, r) =>
sum + Number(r.positivity_rate || 0),
0
);
return filtered.length
? total / filtered.length
: 0;
}
const row = rows.find(r =>
`${r.year}-W${r.period}`
@@ -958,7 +934,9 @@ function renderAFITrend(
);
return row?.positivity_rate || 0;
return Number(
row?.positivity_rate || 0
);
}),
@@ -970,6 +948,12 @@ function renderAFITrend(
})
);
/*
|--------------------------------------------------------------------------
| BUILD
|--------------------------------------------------------------------------
*/
buildMixedTrendChart(
canvasId,
labels,
@@ -978,6 +962,8 @@ function renderAFITrend(
);
}
function renderPathogenChart(rows = []) {
buildDistributionChart(
'pathogenChart',