finalized overview page
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
const standardPrograms = ['SARI', 'ILI', 'LBM'];
|
||||
const programCode = (window.PROGRAM_CODE || '').trim().toUpperCase();
|
||||
|
||||
let map;
|
||||
let provinceLayer;
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
|
||||
@@ -15,6 +16,115 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
function renderProvinceHeatmap(rows) {
|
||||
|
||||
const totals = {};
|
||||
rows.forEach(r => {
|
||||
totals[r.site_province_name] = {
|
||||
total: Number(r.total),
|
||||
positive: Number(r.positive)
|
||||
};
|
||||
});
|
||||
|
||||
if (map) {
|
||||
map.remove();
|
||||
}
|
||||
|
||||
map = L.map('provinceMap').setView([12.7, 104.9], 7);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
addProvinceLegend();
|
||||
|
||||
fetch('/geo/cambodia_provinces.geojson')
|
||||
.then(r => r.json())
|
||||
.then(geo => {
|
||||
|
||||
function getColor(value) {
|
||||
if (value > 50) return "#b91c1c";
|
||||
if (value >= 10) return "#ef4444";
|
||||
if (value > 0) return "#fecaca";
|
||||
return "#f3f4f600";
|
||||
}
|
||||
|
||||
provinceLayer = L.geoJSON(geo, {
|
||||
style: feature => {
|
||||
|
||||
const province = feature.properties.ADM1_EN;
|
||||
const value = totals[province]?.total || 0;
|
||||
|
||||
return {
|
||||
color: "#444",
|
||||
weight: 1,
|
||||
fillColor: getColor(value),
|
||||
fillOpacity: 0.7
|
||||
};
|
||||
},
|
||||
onEachFeature: (feature, layer) => {
|
||||
const province = feature.properties.ADM1_EN;
|
||||
const total = totals[province]?.total || 0;
|
||||
const positive = totals[province]?.positive || 0;
|
||||
const percent = total ? ((positive / total) * 100).toFixed(1) : 0;
|
||||
console.log(province, total, positive, percent);
|
||||
|
||||
layer.bindTooltip(`
|
||||
${province}<br>
|
||||
Total: ${total}<br>
|
||||
Positivity: ${percent}%
|
||||
`);
|
||||
}
|
||||
}).addTo(map);
|
||||
|
||||
});
|
||||
}
|
||||
function addProvinceLegend() {
|
||||
|
||||
const legend = L.control({ position: "bottomright" });
|
||||
|
||||
legend.onAdd = function () {
|
||||
|
||||
const div = L.DomUtil.create("div", "map-legend");
|
||||
|
||||
div.innerHTML = `
|
||||
<div style="background:white;padding:10px 12px;border-radius:6px;
|
||||
box-shadow:0 2px 6px rgba(0,0,0,0.2);font-size:12px;">
|
||||
<div style="font-weight:600;margin-bottom:6px;">Cases</div>
|
||||
|
||||
<div style="display:flex;align-items:center;margin-bottom:4px;">
|
||||
<span style="width:12px;height:12px;background:#b91c1c;
|
||||
display:inline-block;margin-right:6px;"></span>
|
||||
> 50
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;margin-bottom:4px;">
|
||||
<span style="width:12px;height:12px;background:#ef4444;
|
||||
display:inline-block;margin-right:6px;"></span>
|
||||
10 – 50
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;margin-bottom:4px;">
|
||||
<span style="width:12px;height:12px;background:#fecaca;
|
||||
display:inline-block;margin-right:6px;"></span>
|
||||
1 – 9
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;">
|
||||
<span style="width:12px;height:12px;background:#f3f4f6;
|
||||
display:inline-block;margin-right:6px;"></span>
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return div;
|
||||
};
|
||||
|
||||
legend.addTo(map);
|
||||
}
|
||||
function renderTrend(valueId, changeId, current, previous, suffix = '') {
|
||||
|
||||
const valueEl = document.getElementById(valueId);
|
||||
@@ -49,16 +159,17 @@ function renderTrend(valueId, changeId, current, previous, suffix = '') {
|
||||
function renderProgramTrend(rows) {
|
||||
|
||||
rows = rows || [];
|
||||
|
||||
const labels = rows.map(r => `W${r.period}`);
|
||||
const samples = rows.map(r => r.total_samples || 0);
|
||||
const positivity = rows.map(r => r.positivity_rate || 0);
|
||||
const fluRate = rows.map(r => r.influenza_rate || 0);
|
||||
const covidRate = rows.map(r => r.covid_rate || 0);
|
||||
|
||||
buildMixedTrendChart(
|
||||
'trendChart',
|
||||
labels,
|
||||
samples,
|
||||
positivity
|
||||
fluRate,
|
||||
covidRate
|
||||
);
|
||||
}
|
||||
function renderSummary(summary) {
|
||||
@@ -117,26 +228,46 @@ function renderSummary(summary) {
|
||||
);
|
||||
}
|
||||
function renderDashboard(data) {
|
||||
console.log("SUMMARY:", data.summary);
|
||||
|
||||
data = data || {};
|
||||
|
||||
|
||||
renderProgramTrend(data.trend || []);
|
||||
renderSummary(data.summary || {});
|
||||
buildChart(
|
||||
'provinceChart',
|
||||
'bar',
|
||||
(data.province_distribution || []).map(r => r.site_province_name),
|
||||
(data.province_distribution || []).map(r => r.total)
|
||||
);
|
||||
renderProvinceHeatmap(data.province_distribution || []);
|
||||
// buildStackedChart(
|
||||
// "pathogenChart",
|
||||
// labels,
|
||||
// [
|
||||
// {
|
||||
// label: "Influenza",
|
||||
// data: influenza,
|
||||
// backgroundColor: "#2E7D32"
|
||||
// },
|
||||
// {
|
||||
// label: "SARS-CoV-2",
|
||||
// data: covid,
|
||||
// backgroundColor: "#A5D6A7"
|
||||
// }
|
||||
// ]
|
||||
// );
|
||||
const pathogenRows = (data.pathogen_distribution || [])
|
||||
.sort((a, b) => b.total - a.total);
|
||||
const colors = [
|
||||
'#2563eb', '#10b981', '#f59e0b', '#ef4444',
|
||||
'#8b5cf6', '#14b8a6', '#f97316', '#84cc16'
|
||||
];
|
||||
|
||||
|
||||
|
||||
buildChart(
|
||||
'pathogenChart',
|
||||
'bar',
|
||||
(data.pathogen_distribution || []).map(r => r.pathogen_name),
|
||||
(data.pathogen_distribution || []).map(r => r.total),
|
||||
'Positive'
|
||||
'doughnut',
|
||||
pathogenRows.map(r => r.pathogen),
|
||||
pathogenRows.map(r => r.total)
|
||||
);
|
||||
charts['pathogenChart'].data.datasets[0].backgroundColor = colors;
|
||||
charts['pathogenChart'].update();
|
||||
|
||||
buildChart(
|
||||
'ageChart',
|
||||
@@ -144,6 +275,8 @@ function renderDashboard(data) {
|
||||
(data.age_distribution || []).map(r => r.age_group),
|
||||
(data.age_distribution || []).map(r => r.total)
|
||||
);
|
||||
charts['ageChart'].data.datasets[0].backgroundColor = colors;
|
||||
charts['ageChart'].update();
|
||||
|
||||
buildChart(
|
||||
'sexChart',
|
||||
@@ -151,5 +284,24 @@ function renderDashboard(data) {
|
||||
(data.sex_distribution || []).map(r => r.patient_sex),
|
||||
(data.sex_distribution || []).map(r => r.total)
|
||||
);
|
||||
charts['sexChart'].data.datasets[0].backgroundColor = colors;
|
||||
charts['sexChart'].update();
|
||||
|
||||
buildChart(
|
||||
'subtypeChart',
|
||||
'bar',
|
||||
(data.subtype_distribution || []).map(r => r.subtype),
|
||||
(data.subtype_distribution || []).map(r => r.total)
|
||||
);
|
||||
charts['subtypeChart'].data.datasets[0].backgroundColor = colors;
|
||||
charts['subtypeChart'].update();
|
||||
buildChart(
|
||||
'sentinelChart',
|
||||
'pie',
|
||||
(data.sentinel_sites || []).map(r => r.name),
|
||||
(data.sentinel_sites || []).map(r => r.total)
|
||||
);
|
||||
charts['sentinelChart'].data.datasets[0].backgroundColor = colors;
|
||||
charts['sentinelChart'].update();
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user