modified Sequencing Page
This commit is contained in:
@@ -184,7 +184,6 @@ class DashboardController extends Controller
|
|||||||
public function covidLineageRelativeOverTime(Request $request)
|
public function covidLineageRelativeOverTime(Request $request)
|
||||||
{
|
{
|
||||||
$range = $this->getEpiRange($request);
|
$range = $this->getEpiRange($request);
|
||||||
|
|
||||||
if (!$range) {
|
if (!$range) {
|
||||||
return response()->json(['error' => 'Missing epiweek range'], 400);
|
return response()->json(['error' => 'Missing epiweek range'], 400);
|
||||||
}
|
}
|
||||||
@@ -202,7 +201,6 @@ class DashboardController extends Controller
|
|||||||
public function influenzaRelativeOverTime(Request $request)
|
public function influenzaRelativeOverTime(Request $request)
|
||||||
{
|
{
|
||||||
$range = $this->getEpiRange($request);
|
$range = $this->getEpiRange($request);
|
||||||
|
|
||||||
if (!$range) {
|
if (!$range) {
|
||||||
return response()->json(['error' => 'Missing epiweek range'], 400);
|
return response()->json(['error' => 'Missing epiweek range'], 400);
|
||||||
}
|
}
|
||||||
@@ -218,6 +216,24 @@ class DashboardController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function influenzaRelativeOverTimeSequencing(Request $request)
|
||||||
|
{
|
||||||
|
$range = $this->getEpiRange($request);
|
||||||
|
if (!$range) {
|
||||||
|
return response()->json(['error' => 'Missing epiweek range'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->service->influenzaRelativeOverTimeSequencing(
|
||||||
|
$range['startYear'],
|
||||||
|
$range['startWeek'],
|
||||||
|
$range['endYear'],
|
||||||
|
$range['endWeek']
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1020,7 +1020,7 @@ class DashboardService
|
|||||||
})
|
})
|
||||||
|
|
||||||
->selectRaw("
|
->selectRaw("
|
||||||
surveillance_cases.week_data as period,
|
concat(surveillance_cases.year_data,'-',surveillance_cases.week_data) as period,
|
||||||
subtype,
|
subtype,
|
||||||
COUNT(DISTINCT surveillance_cases.lab_code) as total
|
COUNT(DISTINCT surveillance_cases.lab_code) as total
|
||||||
")
|
")
|
||||||
@@ -1029,4 +1029,34 @@ class DashboardService
|
|||||||
->orderBy('period')
|
->orderBy('period')
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function influenzaRelativeOverTimeSequencing($startYear, $startWeek, $endYear, $endWeek)
|
||||||
|
{
|
||||||
|
return SurveillanceCase::join('case_lab_results', function ($join) {
|
||||||
|
$join->on('surveillance_cases.lab_code', '=', 'case_lab_results.lab_code')
|
||||||
|
->on('surveillance_cases.surveillance_id', '=', 'case_lab_results.surveillance_id');
|
||||||
|
})
|
||||||
|
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
|
||||||
|
$q->whereRaw(
|
||||||
|
"(surveillance_cases.year_data * 100 + surveillance_cases.week_data) BETWEEN ? AND ?",
|
||||||
|
[
|
||||||
|
$startYear * 100 + $startWeek,
|
||||||
|
$endYear * 100 + $endWeek
|
||||||
|
]
|
||||||
|
);
|
||||||
|
})
|
||||||
|
->whereRaw('case_lab_results.is_positive = 1 and surveillance_cases.surveillance_id in(6) and case_lab_results.indicator="Influenza"')
|
||||||
|
->selectRaw("
|
||||||
|
case_lab_results.pathogen_name as lineage,
|
||||||
|
concat(surveillance_cases.year_data,'-',surveillance_cases.week_data) as week,
|
||||||
|
COUNT(DISTINCT surveillance_cases.lab_code) as total
|
||||||
|
")
|
||||||
|
->groupBy(
|
||||||
|
'case_lab_results.pathogen_name',
|
||||||
|
'week'
|
||||||
|
)
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Chart.register({
|
|||||||
(ds.data || []).some(v => Number(v) > 0)
|
(ds.data || []).some(v => Number(v) > 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
chart.$noData = !hasData;
|
chart.$noData = !hasData;
|
||||||
|
|
||||||
if (hasData) return;
|
if (hasData) return;
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ function buildStackedChart(canvasId, labels, datasets) {
|
|||||||
bottom: 30
|
bottom: 30
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
@@ -159,9 +159,9 @@ function buildChart(id, type, labels, data) {
|
|||||||
layout: {
|
layout: {
|
||||||
padding: 30
|
padding: 30
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
indexAxis: isHorizontal ? 'y' : 'x',
|
|
||||||
|
indexAxis: isHorizontal ? 'y' : 'x',
|
||||||
plugins: {
|
plugins: {
|
||||||
|
|
||||||
legend: {
|
legend: {
|
||||||
@@ -189,7 +189,7 @@ function buildChart(id, type, labels, data) {
|
|||||||
},
|
},
|
||||||
datalabels: {
|
datalabels: {
|
||||||
color: "#282626",
|
color: "#282626",
|
||||||
offset: 6,
|
offset: 6,
|
||||||
clip: false,
|
clip: false,
|
||||||
display: (ctx) => {
|
display: (ctx) => {
|
||||||
const chart = ctx.chart;
|
const chart = ctx.chart;
|
||||||
@@ -257,7 +257,7 @@ function buildChart(id, type, labels, data) {
|
|||||||
arc: {
|
arc: {
|
||||||
borderWidth: (ctx) => ctx.chart.$noData ? 0 : 1
|
borderWidth: (ctx) => ctx.chart.$noData ? 0 : 1
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +306,7 @@ function buildMixedTrendChart(canvasId, labels, samples, fluRate, covidRate) {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
type: 'line',
|
type: 'line',
|
||||||
label: 'COVID-19 %',
|
label: 'COVID-19 %',
|
||||||
@@ -318,7 +318,7 @@ function buildMixedTrendChart(canvasId, labels, samples, fluRate, covidRate) {
|
|||||||
pointStyle: 'line',
|
pointStyle: 'line',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
label: 'Total Cases',
|
label: 'Total Cases',
|
||||||
@@ -327,7 +327,7 @@ function buildMixedTrendChart(canvasId, labels, samples, fluRate, covidRate) {
|
|||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
barPercentage: 0.8,
|
barPercentage: 0.8,
|
||||||
categoryPercentage: 0.7,
|
categoryPercentage: 0.7,
|
||||||
yAxisID: 'y',
|
yAxisID: 'y',
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
@@ -338,7 +338,6 @@ function buildMixedTrendChart(canvasId, labels, samples, fluRate, covidRate) {
|
|||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
|
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
@@ -351,6 +350,7 @@ function buildMixedTrendChart(canvasId, labels, samples, fluRate, covidRate) {
|
|||||||
align: "top",
|
align: "top",
|
||||||
anchor: "end",
|
anchor: "end",
|
||||||
color: "#555",
|
color: "#555",
|
||||||
|
display: false,
|
||||||
font: {
|
font: {
|
||||||
size: 10
|
size: 10
|
||||||
},
|
},
|
||||||
@@ -387,7 +387,7 @@ function buildMixedTrendChart(canvasId, labels, samples, fluRate, covidRate) {
|
|||||||
display: true,
|
display: true,
|
||||||
text: '% Positivity'
|
text: '% Positivity'
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,7 +416,7 @@ function buildMixedTrendChart(canvasId, labels, samples, fluRate, covidRate) {
|
|||||||
// pointStyle: 'line',
|
// pointStyle: 'line',
|
||||||
// yAxisID: 'y1',
|
// yAxisID: 'y1',
|
||||||
|
|
||||||
|
|
||||||
// },
|
// },
|
||||||
|
|
||||||
// {
|
// {
|
||||||
@@ -428,7 +428,7 @@ function buildMixedTrendChart(canvasId, labels, samples, fluRate, covidRate) {
|
|||||||
// barPercentage: 0.8,
|
// barPercentage: 0.8,
|
||||||
// categoryPercentage: 0.7,
|
// categoryPercentage: 0.7,
|
||||||
// yAxisID: 'y',
|
// yAxisID: 'y',
|
||||||
|
|
||||||
// }
|
// }
|
||||||
// ]
|
// ]
|
||||||
// },
|
// },
|
||||||
@@ -502,4 +502,4 @@ function buildMixedTrendChart(canvasId, labels, samples, fluRate, covidRate) {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -1,20 +1,33 @@
|
|||||||
let sequencingChart;
|
let sequencingChart;
|
||||||
|
let covidLineageFrequencyChart;
|
||||||
|
let influenzaSubtypeFrequencyChart;
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const canvas = document.getElementById('sequencingChart');
|
//const canvas = document.getElementById('sequencingChart');
|
||||||
if (!canvas) return;
|
//if (!canvas) return;
|
||||||
new DashboardFilter((startYear, startWeek, endYear, endWeek) => {
|
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}`)
|
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(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
renderSequencingChart(data.trend || []);
|
//renderSequencingChart(data.trend || []);
|
||||||
renderSequencingCountChart(data.trend || []);
|
renderSequencingCountChart(data.trend || []);
|
||||||
renderSequencingPieChart(data.distribution || []);
|
renderSequencingPieChart(data.distribution || []);
|
||||||
renderSequencingTotalChart(data.trend || []);
|
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 renderSequencingCountChart(rows) {
|
function renderSequencingCountChart(rows) {
|
||||||
@@ -39,7 +52,7 @@ function renderSequencingCountChart(rows) {
|
|||||||
const found = rows.find(r => r.period === w && r.subtype === sub);
|
const found = rows.find(r => r.period === w && r.subtype === sub);
|
||||||
return found ? found.total : 0;
|
return found ? found.total : 0;
|
||||||
}),
|
}),
|
||||||
backgroundColor: colors[i % colors.length]
|
backgroundColor: hexToRGBA(colors[i % colors.length], 0.3)// colors[i % colors.length]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
new Chart(ctx, {
|
new Chart(ctx, {
|
||||||
@@ -59,6 +72,9 @@ function renderSequencingCountChart(rows) {
|
|||||||
display: true,
|
display: true,
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
align: 'center'
|
align: 'center'
|
||||||
|
},
|
||||||
|
datalabels: {
|
||||||
|
display: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,9 +139,13 @@ function renderSequencingTotalChart(rows) {
|
|||||||
options: {
|
options: {
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: false }
|
legend: { display: false },
|
||||||
|
datalabels: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -176,6 +196,8 @@ function renderSequencingChart(rows) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('aggregated', aggregated)
|
||||||
|
|
||||||
const cleanRows = Object.values(aggregated);
|
const cleanRows = Object.values(aggregated);
|
||||||
|
|
||||||
const weeks = [...new Set(cleanRows.map(r => r.period))];
|
const weeks = [...new Set(cleanRows.map(r => r.period))];
|
||||||
@@ -235,4 +257,310 @@ function renderSequencingChart(rows) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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 => {
|
||||||
|
|
||||||
|
// Extract unique weeks (X-axis)
|
||||||
|
const weeks = [...new Set(data.map(item => item.week))].sort();
|
||||||
|
|
||||||
|
// Extract unique lineages
|
||||||
|
const lineages = [...new Set(data.map(item => item.lineage))];
|
||||||
|
|
||||||
|
// Color palette
|
||||||
|
const colors = [
|
||||||
|
'#84cc16','#22c55e','#06b6d4','#3b82f6',
|
||||||
|
'#6366f1','#a855f7','#ec4899','#ef4444',
|
||||||
|
'#f97316','#eab308'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Build datasets
|
||||||
|
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, // area fill
|
||||||
|
tension: 0.4, // smooth curve
|
||||||
|
borderColor: 'transparent', // hide the line
|
||||||
|
borderWidth: 0,
|
||||||
|
pointRadius: 0, // hide points
|
||||||
|
backgroundColor: hexToRGBA(colors[index % colors.length], 0.3),
|
||||||
|
stack: 'total'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Destroy previous chart if exists
|
||||||
|
if (covidLineageFrequencyChart) covidLineageFrequencyChart.destroy();
|
||||||
|
|
||||||
|
const ctx = document.getElementById('covidLineageFrequency').getContext('2d');
|
||||||
|
|
||||||
|
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'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// Custom right-side scrollable legend
|
||||||
|
// -------------------------
|
||||||
|
const legendContainer = document.getElementById('legendContainer');
|
||||||
|
legendContainer.innerHTML = ''; // clear old legend
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// If the clicked dataset is already the only visible one, show all
|
||||||
|
const allHidden = datasets.every((d, i) => covidLineageFrequencyChart.getDatasetMeta(i).hidden || i === index);
|
||||||
|
if (!allHidden) {
|
||||||
|
// Hide all datasets
|
||||||
|
datasets.forEach((d, i) => {
|
||||||
|
covidLineageFrequencyChart.getDatasetMeta(i).hidden = true;
|
||||||
|
});
|
||||||
|
// Show only clicked
|
||||||
|
meta.hidden = false;
|
||||||
|
} else {
|
||||||
|
// Show all datasets
|
||||||
|
datasets.forEach((d, i) => {
|
||||||
|
covidLineageFrequencyChart.getDatasetMeta(i).hidden = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
covidLineageFrequencyChart.update();
|
||||||
|
|
||||||
|
// Update legend opacity
|
||||||
|
Array.from(legendContainer.children).forEach((child, i) => {
|
||||||
|
const metaItem = covidLineageFrequencyChart.getDatasetMeta(i);
|
||||||
|
child.style.opacity = metaItem.hidden ? 0.5 : 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
legendContainer.appendChild(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scrollable CSS (in case legend is long)
|
||||||
|
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 => {
|
||||||
|
|
||||||
|
// Extract unique weeks (X-axis)
|
||||||
|
const weeks = [...new Set(data.map(item => item.week))].sort();
|
||||||
|
|
||||||
|
const colors = [
|
||||||
|
'#84cc16','#22c55e','#06b6d4','#3b82f6',
|
||||||
|
'#6366f1','#a855f7','#ec4899','#ef4444',
|
||||||
|
'#f97316','#eab308'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Extract unique lineages
|
||||||
|
const lineages = [...new Set(data.map(item => item.lineage))];
|
||||||
|
// Build datasets
|
||||||
|
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, // area fill
|
||||||
|
tension: 0.4, // smooth curve
|
||||||
|
borderColor: 'transparent', // hide the line
|
||||||
|
borderWidth: 0,
|
||||||
|
pointRadius: 0, // hide points
|
||||||
|
backgroundColor: hexToRGBA(colors[(index*2) % colors.length], 0.8),
|
||||||
|
stack: 'total'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Destroy previous chart if exists
|
||||||
|
if (influenzaSubtypeFrequencyChart) influenzaSubtypeFrequencyChart.destroy();
|
||||||
|
|
||||||
|
const ctx = document.getElementById('influenzaSubtypeFrequency').getContext('2d');
|
||||||
|
|
||||||
|
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 // 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'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const legendContainer = document.getElementById('legendContainerInfluenzaSubtypeFrequency');
|
||||||
|
legendContainer.innerHTML = ''; // clear old legend
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// If the clicked dataset is already the only visible one, show all
|
||||||
|
const allHidden = datasets.every((d, i) => influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden || i === index);
|
||||||
|
if (!allHidden) {
|
||||||
|
// Hide all datasets
|
||||||
|
datasets.forEach((d, i) => {
|
||||||
|
influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden = true;
|
||||||
|
});
|
||||||
|
// Show only clicked
|
||||||
|
meta.hidden = false;
|
||||||
|
} else {
|
||||||
|
// Show all datasets
|
||||||
|
datasets.forEach((d, i) => {
|
||||||
|
influenzaSubtypeFrequencyChart.getDatasetMeta(i).hidden = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
influenzaSubtypeFrequencyChart.update();
|
||||||
|
|
||||||
|
// Update legend opacity
|
||||||
|
Array.from(legendContainer.children).forEach((child, i) => {
|
||||||
|
const metaItem = influenzaSubtypeFrequencyChart.getDatasetMeta(i);
|
||||||
|
child.style.opacity = metaItem.hidden ? 0.5 : 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
legendContainer.appendChild(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scrollable CSS (in case legend is long)
|
||||||
|
legendContainer.style.maxHeight = '375px';
|
||||||
|
legendContainer.style.overflowY = 'auto';
|
||||||
|
legendContainer.style.padding = '8px';
|
||||||
|
legendContainer.style.borderRadius = '0px';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@
|
|||||||
|
|
||||||
<div class="card shadow-sm mb-3" style="height:60vh;">
|
<div class="card shadow-sm mb-3" style="height:60vh;">
|
||||||
<div class="card-body" >
|
<div class="card-body" >
|
||||||
|
|
||||||
<h5 class="fw-bold">Epidemic Trend</h5>
|
<h5 class="fw-bold">Epidemic Trend</h5>
|
||||||
<p class="text-muted small report-period">
|
<p class="text-muted small report-period">
|
||||||
(based on selected epiweek range)
|
(based on selected epiweek range)
|
||||||
@@ -109,11 +108,11 @@
|
|||||||
<p class="text-muted small report-period">
|
<p class="text-muted small report-period">
|
||||||
(based on selected epiweek range)
|
(based on selected epiweek range)
|
||||||
</p>
|
</p>
|
||||||
<canvas id="covidLineageFrequency" style=" flex:1; max-width: 90%; max-height:45vh; float: left;"></canvas>
|
<canvas id="covidLineageFrequency" style=" flex:1; max-width: 90%; max-height:40vh; float: left;"></canvas>
|
||||||
<div id="legendContainer" style="
|
<div id="legendContainer" style="
|
||||||
width:10%;
|
width:10%;
|
||||||
margin-left:20px;
|
margin-left:20px;
|
||||||
max-height:375px;
|
max-height:360px;
|
||||||
overflow-y:auto;
|
overflow-y:auto;
|
||||||
padding:8px;
|
padding:8px;
|
||||||
|
|
||||||
@@ -135,7 +134,7 @@
|
|||||||
<p class="text-muted small report-period">
|
<p class="text-muted small report-period">
|
||||||
(based on selected epiweek range)
|
(based on selected epiweek range)
|
||||||
</p>
|
</p>
|
||||||
<canvas id="influenzaSubtypeFrequency" style=" flex:1; max-width: 90%; max-height:45vh; float: left;"></canvas>
|
<canvas id="influenzaSubtypeFrequency" style=" flex:1; max-width: 90%; max-height:40vh; float: left;"></canvas>
|
||||||
<div id="legendContainerInfluenzaSubtypeFrequency" style="
|
<div id="legendContainerInfluenzaSubtypeFrequency" style="
|
||||||
width:10%;
|
width:10%;
|
||||||
margin-left:20px;
|
margin-left:20px;
|
||||||
|
|||||||
@@ -27,32 +27,75 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="row mt-3">
|
||||||
<div class="card-body" style="height:600px;">
|
|
||||||
<canvas id="sequencingChart"></canvas>
|
<div class="col-md-5">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body" style="height:500px;">
|
||||||
|
<h5 class="fw-bold">Total Sequenced Samples Over Time</h5>
|
||||||
|
<p class="text-muted small report-period">
|
||||||
|
(based on selected epiweek range)
|
||||||
|
</p>
|
||||||
|
<canvas id="sequencingTotalChart" style=" flex:1; max-height:40vh; float: left;"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-7">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body" style="height:500px;">
|
||||||
|
<h5 class="fw-bold">Influenza Subtypes Relative Frequencies Over Time</h5>
|
||||||
|
<p class="text-muted small report-period">
|
||||||
|
(based on selected epiweek range)
|
||||||
|
</p>
|
||||||
|
<canvas id="influenzaSubtypeFrequency" style=" flex:1; max-width: 85%; max-height:40vh; float: left;"></canvas>
|
||||||
|
<div id="legendContainerInfluenzaSubtypeFrequency" style="
|
||||||
|
width:15%;
|
||||||
|
margin-left:20px;
|
||||||
|
max-height:375px;
|
||||||
|
overflow-y:auto;
|
||||||
|
padding:8px;
|
||||||
|
"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-3">
|
<div class="row mt-3">
|
||||||
|
|
||||||
<!-- Counts -->
|
<!-- Counts -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body" style="height:600px;">
|
<div class="card-body" style="height:500px;">
|
||||||
<h6 class="fw-bold">Lineage Counts Over Time</h6>
|
<h6 class="fw-bold">SARS-CoV-2 Lineage/Sublineage Relative Frequencies Over Time</h6>
|
||||||
<canvas id="sequencingCountChart"></canvas>
|
<p class="text-muted small report-period">
|
||||||
|
(based on selected epiweek range)
|
||||||
|
</p>
|
||||||
|
{{-- <canvas id="sequencingCountChart"></canvas>--}}
|
||||||
|
<canvas id="covidLineageFrequency" style=" flex:1; max-width: 90%; max-height:40vh; float: left;"></canvas>
|
||||||
|
<div id="legendContainer" style="
|
||||||
|
width:10%;
|
||||||
|
margin-left:20px;
|
||||||
|
max-height:360px;
|
||||||
|
overflow-y:auto;
|
||||||
|
padding:8px;
|
||||||
|
|
||||||
|
"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- <div class="col-md-4">--}}
|
||||||
|
{{-- <div class="card">--}}
|
||||||
|
{{-- <div class="card-body" style="height:500px;">--}}
|
||||||
|
{{-- <canvas id="sequencingChart"></canvas>--}}
|
||||||
|
{{-- <canvas id="sequencingCountChart"></canvas>--}}
|
||||||
|
{{-- </div>--}}
|
||||||
|
{{-- </div>--}}
|
||||||
|
{{-- </div>--}}
|
||||||
|
|
||||||
<!-- Distribution -->
|
<!-- Distribution -->
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body" style="height:600px;">
|
|
||||||
<h6 class="fw-bold">Total Sequenced Samples Over Time</h6>
|
|
||||||
<canvas id="sequencingTotalChart" style="height: 550px;"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -62,8 +105,8 @@
|
|||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
<script>
|
<script>
|
||||||
window.SURVEILLANCE_ID = 6;
|
window.SURVEILLANCE_ID = 6;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/js/sequencing.js"></script>
|
<script src="/js/sequencing.js"></script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -46,6 +46,9 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
.btn{
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-theme-outline {
|
.btn-theme-outline {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
@@ -263,4 +266,4 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Route::get('/dashboard/influenza-subtype-distribution', [DashboardController::cl
|
|||||||
Route::get('/dashboard/covid-distributed-by-age-group', [DashboardController::class, 'covidDistributedByAgeGroup']);
|
Route::get('/dashboard/covid-distributed-by-age-group', [DashboardController::class, 'covidDistributedByAgeGroup']);
|
||||||
Route::get('/dashboard/covid-lineage-frequency', [DashboardController::class, 'covidLineageRelativeOverTime']);
|
Route::get('/dashboard/covid-lineage-frequency', [DashboardController::class, 'covidLineageRelativeOverTime']);
|
||||||
Route::get('/dashboard/influenza-relative-frequency', [DashboardController::class, 'influenzaRelativeOverTime']);
|
Route::get('/dashboard/influenza-relative-frequency', [DashboardController::class, 'influenzaRelativeOverTime']);
|
||||||
|
Route::get('/dashboard/influenza-relative-frequency-sequencing', [DashboardController::class, 'influenzaRelativeOverTimeSequencing']);
|
||||||
Route::get('/dashboard/sentinel-map', [DashboardController::class, 'sentinelMap']);
|
Route::get('/dashboard/sentinel-map', [DashboardController::class, 'sentinelMap']);
|
||||||
Route::get('/dashboard/reload', [DashboardController::class, 'fetchSourceData']);
|
Route::get('/dashboard/reload', [DashboardController::class, 'fetchSourceData']);
|
||||||
Route::get('/dashboard/sequencing', [DashboardController::class, 'sequencing']);
|
Route::get('/dashboard/sequencing', [DashboardController::class, 'sequencing']);
|
||||||
|
|||||||
Reference in New Issue
Block a user