change charts corlor, update AFI

This commit is contained in:
2026-05-28 13:04:40 +07:00
parent e8c321c4eb
commit c08bd1af1b
76 changed files with 8758 additions and 1139 deletions

View File

@@ -35,7 +35,10 @@ Chart.register({
const { ctx, chartArea } = chart;
const data = chart.data.datasets[0].data;
const total = data.reduce((a, b) => a + b, 0);
const total =
chart.$totalTested ||
chart.$afiTotalCases ||
data.reduce((a, b) => a + b, 0);
if (!chartArea) return;
@@ -59,10 +62,11 @@ Chart.register({
}
});
Chart.register(ChartDataLabels);
Chart.defaults.devicePixelRatio = 2;
const charts = {};
function buildStackedChart(canvasId, labels, datasets) {
function buildStackedChart(canvasId, labels, data) {
const ctx = document.getElementById(canvasId);
if (!ctx) return;
@@ -77,8 +81,13 @@ function buildStackedChart(canvasId, labels, datasets) {
data: {
labels: labels,
datasets: datasets,
datasets: data.map(ds => ({
...ds,
borderWidth: 1,
barPercentage: 0.9,
categoryPercentage: 0.8,
maxBarThickness: 60
}))
},
plugins: [ChartDataLabels],
@@ -228,7 +237,9 @@ function buildChart(id, type, labels, data) {
if (ctx.chart.$noData) return '';
const data = ctx.chart.data.datasets[0].data;
const total = data.reduce((a, b) => a + b, 0);
const total =
ctx.chart.$totalTested ||
data.reduce((a, b) => a + b, 0);
if (!total) return '';
@@ -242,12 +253,12 @@ function buildChart(id, type, labels, data) {
x: {
beginAtZero: true,
grid: {
display: false
display: false
}
},
y: {
grid: {
color: '#f3f4f6'
color: '#f3f4f6'
}
}
};
@@ -264,6 +275,7 @@ function buildChart(id, type, labels, data) {
}
charts[id] = new Chart(ctx, {
type: type,
data: {
labels: labels,
@@ -278,6 +290,7 @@ function buildChart(id, type, labels, data) {
},
options: options
});
charts[id].$totalTested = 0;
}
function buildMixedTrendChart(canvasId, labels, samples, lines) {
@@ -305,8 +318,10 @@ function buildMixedTrendChart(canvasId, labels, samples, lines) {
type: 'bar',
label: 'Total Cases',
data: samples,
backgroundColor: '#0B8F3C',
yAxisID: 'y'
backgroundColor: '#d34646',
maxBarThickness: 60,
yAxisID: 'y',
});
charts[canvasId] = new Chart(ctx, {

View File

@@ -59,20 +59,25 @@ function closeChartSelector() {
function formatChartName(id) {
const map = {
trendChart: "Case Trend & Positivity",
trendChart: "Case Trend",
pathogenChart: "Pathogen Distribution",
provinceMap: "Geographic Distribution",
ageChart: "Age Distribution",
sexChart: "Sex Distribution",
subtypeChart: "Influenza Subtypes",
sentinelChart: "Sentinel Site Distribution"
sentinelChart: "Sentinel Site Distribution",
sequencingTotalChart: "Sequencing Total",
covidLineageFrequency: "Covid Lineage Frequency",
influenzaSubtypeDistribution: "Influenza Subtype Frequency",
covidDistributedByAgeGroup: "Covid Cases by Age",
influenzaSubtypeFrequency: "Influenza Cases by Age",
};
return map[id] || id;
}
async function exportSelectedCharts() {
console.log("Exporting selected charts...");
const { jsPDF } = window.jspdf;
const pdf = new jsPDF("l", "mm", "a4");
@@ -186,16 +191,23 @@ async function exportSelectedCharts() {
} else {
const canvas = item.chart.canvas;
const tempCanvas = document.createElement("canvas");
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
const scale = 4;
const tempCanvas = document.createElement("canvas");
tempCanvas.width = canvas.width * scale;
tempCanvas.height = canvas.height * scale;
const ctx = tempCanvas.getContext("2d");
ctx.scale(scale, scale);
const ctx = tempCanvas.getContext("2d", { willReadFrequently: true });
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(canvas, 0, 0);
img = tempCanvas.toDataURL("image/png");
img = tempCanvas.toDataURL("image/jpeg", 0.95);
const ratio = canvas.height / canvas.width;
@@ -209,7 +221,7 @@ async function exportSelectedCharts() {
}
const x = cardX + (cardWidth - width) / 2;
const contentTopOffset = 14;
const contentTopOffset = 14;
const availableHeight = cardHeight - contentTopOffset;
const y = cardY + contentTopOffset + (availableHeight - height) / 2;
@@ -234,7 +246,6 @@ function prepareMapForExport() {
}
async function exportFullDashboard() {
console.log("Exporting full charts...");
const el = document.querySelector(".content-area");
const mapEl = document.getElementById("provinceMap");
@@ -254,9 +265,10 @@ async function exportFullDashboard() {
await new Promise(resolve => setTimeout(resolve, 800));
const canvas = await html2canvas(el, {
scale: 3,
scale: 4,
useCORS: true,
scrollY: -window.scrollY
scrollY: -window.scrollY,
backgroundColor: "#ffffff"
});
if (mapEl && originalMapHTML !== null) {
@@ -264,10 +276,15 @@ async function exportFullDashboard() {
map.invalidateSize();
}
const img = canvas.toDataURL("image/png");
const img = canvas.toDataURL("image/jpeg", 0.95);
const { jsPDF } = window.jspdf;
const pdf = new jsPDF("l", "mm", "a4");
const pdf = new jsPDF({
orientation: "landscape",
unit: "mm",
format: "a4",
compress: true
});
const pageWidth = 297;
const pageHeight = 210;
@@ -283,7 +300,7 @@ async function exportFullDashboard() {
const x = (pageWidth - imgWidth) / 2;
const y = (pageHeight - imgHeight) / 2;
pdf.addImage(img, "PNG", x, y, imgWidth, imgHeight);
pdf.addImage(img, "JPEG", x, y, imgWidth, imgHeight);
pdf.save("full_dashboard.pdf");
closeChartSelector();
@@ -308,7 +325,7 @@ async function getMapImage() {
const ctx = canvas.getContext("2d");
ctx.scale(3, 3);
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, width, height);
@@ -345,6 +362,28 @@ async function getMapImage() {
map.eachLayer(layer => {
if (!layer.toGeoJSON) return;
if (layer instanceof L.CircleMarker) {
const latlng = layer.getLatLng();
const point = projection.latLngToPoint(latlng, zoom);
minX = Math.min(minX, point.x);
maxX = Math.max(maxX, point.x);
minY = Math.min(minY, point.y);
maxY = Math.max(maxY, point.y);
projectedRings.push({
type: "circle",
x: point.x,
y: point.y,
radius: layer.getRadius(),
fillColor: layer.options.fillColor || "#000",
strokeColor: layer.options.color || "#000"
});
return;
}
const geo = layer.toGeoJSON();
@@ -353,7 +392,10 @@ async function getMapImage() {
: [geo];
features.forEach(f => {
if (!f.geometry) return;
if (
!f.geometry ||
!f.geometry.coordinates
) return;
const coords = f.geometry.coordinates;
@@ -362,9 +404,26 @@ async function getMapImage() {
: [coords];
polygons.forEach(poly => {
if (!Array.isArray(poly)) return;
poly.forEach(ring => {
const projected = ring.map(([lng, lat]) => {
if (
!Array.isArray(ring) ||
!Array.isArray(ring[0])
) {
return;
}
const projected = ring.map(coord => {
if (!Array.isArray(coord) || coord.length < 2) {
return null;
}
const [lng, lat] = coord;
const latlng = L.latLng(lat, lng);
const point = projection.latLngToPoint(latlng, zoom);
@@ -374,12 +433,15 @@ async function getMapImage() {
maxY = Math.max(maxY, point.y);
return [point.x, point.y];
});
}).filter(Boolean);
if (!projected.length) return;
projectedRings.push({
points: projected,
properties: f.properties
properties: f.properties || {}
});
});
});
});
@@ -394,15 +456,51 @@ async function getMapImage() {
const offsetX = (width - (maxX - minX) * scale) / 2;
const offsetY = (height - (maxY - minY) * scale) / 2;
projectedRings.forEach(({ points, properties }) => {
projectedRings.forEach(item => {
// -------------------------
// Draw circle markers
// -------------------------
if (item.type === "circle") {
const drawX = (item.x - minX) * scale + offsetX;
const drawY = (item.y - minY) * scale + offsetY;
ctx.beginPath();
ctx.arc(
drawX,
drawY,
item.radius,
0,
Math.PI * 2
);
ctx.fillStyle = item.fillColor;
ctx.fill();
ctx.strokeStyle = item.strokeColor;
ctx.lineWidth = 2;
ctx.stroke();
return;
}
// -------------------------
// Draw polygons
// -------------------------
const { points, properties } = item;
ctx.beginPath();
points.forEach(([x, y], i) => {
const drawX = (x - minX) * scale + offsetX;
const drawY = (y - minY) * scale + offsetY;
if (i === 0) ctx.moveTo(drawX, drawY);
else ctx.lineTo(drawX, drawY);
});
ctx.closePath();

View File

@@ -0,0 +1,59 @@
export const COLORS = [
'#2563eb', // blue
'#10b981', // emerald
'#f59e0b', // amber
'#ef4444', // red
'#8b5cf6', // violet
'#14b8a6', // teal
'#f97316', // orange
'#84cc16', // lime
'#e11dba', // fuchsia
'#f6f63b', // yellow
'#0ea5e9', // sky
'#22c55e', // green
'#a855f7', // purple
'#ec4899', // pink
'#06b6d4', // cyan
'#65a30d', // olive
'#dc2626', // dark red
'#1d4ed8', // strong blue
'#7c3aed', // deep violet
'#059669', // dark emerald
'#c2410c', // burnt orange
'#be123c', // rose
'#4338ca', // indigo
'#0f766e', // dark teal
'#9333ea', // bright purple
'#15803d', // forest green
'#ea580c', // deep orange
'#0284c7', // ocean blue
'#ca8a04', // mustard
'#db2777' // magenta
];
export const SUBTYPE_COLORS = {
'A/H1N1pdm': '#f0d401',
'A/H3N2': '#00ffff',
'A/H9N2': '#2563eb',
'A/H5N1': '#dc2626',
'A/Unsubtypable': '#f455d7',
'B/Yam': '#9333ea',
'B/Vic': '#086037',
'B/Unsubtypable': '#66ff00',
'B/Victoria': '#9333ea',
'H1N1pdm': '#f0d401',
'H3N2': '#00ffff',
'H9N2': '#2563eb',
'J.2.4': '#8c6060',
'K': '#55f49a',
};
export const SURVEILLANCE_COLORS = {
'LBM': '#f0d401',
'ILI': '#2563eb',
'SARI': '#dc2626',
'NDS': '#9333ea',
'AFI': '#086037',
'SEQ': '#66ff00'
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff