change charts corlor, update AFI
This commit is contained in:
@@ -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, {
|
||||
|
||||
@@ -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();
|
||||
|
||||
59
dashboard/public/js/globals.js
Normal file
59
dashboard/public/js/globals.js
Normal 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
Reference in New Issue
Block a user