Files
labweb/public/js/Search_results.js
2025-12-16 11:39:15 +08:00

887 lines
31 KiB
JavaScript

// API Base URL
const API_BASE_URL = '/api';
// Global variables to store related microbial data
let relatedMicrobes = [];
let allRelatedMicrobes = []; // Store all related microbial data
let currentRelatedPage = 1;
let relatedRowsPerPage = 20;
let totalRelatedRows = 0;
let totalRelatedPages = 0;
let currentSort = { column: null, direction: 'asc' }; // Current sort state
// Get URL parameter
function getUrlParameter(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
// Update page title
function updatePageTitle(microbialName) {
document.title = `${microbialName} - Microbial Detail Information`;
}
// Load microbial detail information
async function loadMicrobialDetail(microbialName, level = 'genus') {
try {
showLoadingState();
console.log('Loading details for microbial:', microbialName, 'level:', level);
const response = await fetch(`${API_BASE_URL}/microbial-detail?name=${encodeURIComponent(microbialName)}&level=${level}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Received data:', data);
hideLoadingState();
displayMicrobialDetail(data);
} catch (error) {
console.error('Error loading microbial detail:', error);
hideLoadingState();
showErrorState(error.message);
}
}
// Display microbial detail information
function displayMicrobialDetail(data) {
const microbialData = data?.data;
if (!microbialData) {
showMicrobialNotFound();
return;
}
updatePageTitle(microbialData.microbial_name);
updateMicrobialInfo(microbialData);
updateRelatedMicrobes(microbialData.related_microbes || []);
updateStatistics(microbialData.related_microbes || []);
// Important: Use allRelatedMicrobes if available to ensure charts reflect all data
updateChartsWithData(microbialData.related_microbes || []);
showDetailContent();
}
// Show loading state
function showLoadingState() {
document.getElementById('loading-state').style.display = 'block';
document.getElementById('error-message').style.display = 'none';
document.getElementById('detail-content').style.display = 'none';
}
// Hide loading state
function hideLoadingState() {
document.getElementById('loading-state').style.display = 'none';
}
// Show microbial not found
function showMicrobialNotFound() {
document.getElementById('error-message').innerHTML = `
<div class="alert alert-warning">
<strong>Microbial information not found</strong><br>
Please check if the microbial name is correct.
</div>
`;
document.getElementById('error-message').style.display = 'block';
document.getElementById('detail-content').style.display = 'none';
}
// Show error state
function showErrorState(message) {
document.getElementById('error-message').innerHTML = `
<div class="alert alert-danger">
<strong>Load failed</strong><br>
${message}
</div>
`;
document.getElementById('error-message').style.display = 'block';
document.getElementById('detail-content').style.display = 'none';
}
// Show detail content
function showDetailContent() {
document.getElementById('detail-content').style.display = 'block';
document.getElementById('error-message').style.display = 'none';
}
// Show no microbial selected state
function showNoMicrobialSelected() {
document.getElementById('error-message').innerHTML = `
<div class="alert alert-info">
<strong>Please select microorganisms</strong><br>
Click on the microorganism name from the Browse page to view detailed information.
</div>
`;
document.getElementById('error-message').style.display = 'block';
document.getElementById('detail-content').style.display = 'none';
}
// Go back button
function goBack() {
window.history.back();
}
// Format probability value, keep two decimal places
function formatProbability(probability) {
if (!probability || probability === 'N/A') {
return 'N/A';
}
const probValue = parseFloat(probability);
if (isNaN(probValue)) {
return 'N/A';
}
// Keep two decimal places
return probValue.toFixed(2);
}
// Get background color based on probability value
function getConfidenceColor(probability) {
if (!probability || probability === 'N/A') {
return '#f0f0f0'; // Default gray
}
// Convert probability value to number (handle string format like "0.95" or "95")
let probValue = parseFloat(probability);
if (isNaN(probValue)) {
return '#f0f0f0'; // Return gray for invalid values
}
// If value is between 0-1, convert to 0-100
if (probValue <= 1) {
probValue = probValue * 100;
}
// Return different colors based on range
if (probValue >= 0 && probValue < 50) {
return '#D0E7ED';
} else if (probValue >= 50 && probValue < 70) {
return '#9392BE';
} else if (probValue >= 70 && probValue <= 100) {
return '#D5E4A8';
} else {
return '#f0f0f0';
}
}
// Update microbial info
function updateMicrobialInfo(microbial) {
// Update basic info
console.log('Updating microbial info with:', microbial);
const micInfoElement = document.querySelector('.mic_info');
if (micInfoElement) {
// Get background color for temperature confidence
const tempConfidenceColor = getConfidenceColor(microbial.ptemperature_probability);
// Get background color for O2 confidence
const o2ConfidenceColor = getConfidenceColor(microbial.po2_probability);
// Format probability value, keep two decimal places
const formattedTempProbability = formatProbability(microbial.ptemperature_probability);
const formattedO2Probability = formatProbability(microbial.po2_probability);
let infoHtml = `
<div class="mic_info_item">
<span class="mic_info_label">microbial:</span>
<span class="mic_info_value">${microbial.microbial_name || 'N/A'}</span>
</div>
<div class="mic_info_item">
<span class="mic_info_label">taxonomy:</span>
<span class="mic_info_value">${microbial.taxonomy || 'N/A'}</span>
</div>
<div class="mic_info_item">
<span class="mic_info_label">pH:</span>
<span class="mic_info_value">${microbial.ppH || 'N/A'}</span>
</div>
<div class="mic_info_item">
<span class="mic_info_label">temperature:</span>
<span class="mic_info_value">${microbial.ptemperature || 'N/A'}</span>
<span class="mic_info_value" style="margin-left: 5%; padding:5px; border-radius:4px; background: ${tempConfidenceColor};">Confidence: ${formattedTempProbability}</span>
</div>
<div class="mic_info_item">
<span class="mic_info_label">O2:</span>
<span class="mic_info_value">${microbial.po2 || 'N/A'}</span>
<span class="mic_info_value" style="margin-left: 5%; padding:5px; border-radius:4px; background: ${o2ConfidenceColor};">Confidence: ${formattedO2Probability}</span>
</div>
<div class="mic_info_item">
<span class="mic_info_label">gram stain:</span>
<span class="mic_info_value">${microbial.gram_stain || 'Unknown'}</span>
</div>
`;
// Add culture medium info
if (microbial.nutrition) {
// Parse nutrition string to array
const nutritionArrays = microbial.nutrition.split(';').map(arrStr => {
// Extract content within brackets and split into array
const trimmed = arrStr.trim().replace(/^\[|\]$/g, ''); // Remove brackets from both ends
return trimmed
? trimmed
.split(',') // Split by comma
.map(item => item.trim().replace(/'/g, '')) // Clean up spaces and single quotes
: [];
});
if (nutritionArrays && nutritionArrays.length > 0) {
infoHtml += `
<div class="mic_info_item">
<span class="mic_info_label">Media:</span>
`;
nutritionArrays.forEach((media, index) => {
if (media && media.length > 0) {
infoHtml += `
<div class="mic_info_value" style="border-left: none;">
<h4 style="display: inline; font-weight:400; margin-right: 8px;">${index + 1}:</h4>
<span>${Array.isArray(media) ? media.join(', ') : media}</span>
</div>
`;
}
});
infoHtml += `</div>`;
}
}
micInfoElement.innerHTML = infoHtml;
}
}
// Update related microbes table
function updateRelatedMicrobes(relatedMicrobesData) {
// Store all data
allRelatedMicrobes = relatedMicrobesData || [];
totalRelatedRows = allRelatedMicrobes.length;
totalRelatedPages = Math.ceil(totalRelatedRows / relatedRowsPerPage);
currentRelatedPage = 1; // Reset to first page
// If there is current sort state, apply sort
if (currentSort.column) {
sortData(currentSort.column, false); // false means do not toggle direction, keep current
} else {
updateRelatedTable();
}
updateRelatedPaginationInfo();
}
// Data sorting function
function sortData(column, toggle = true) {
if (toggle) {
if (currentSort.column === column) {
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
} else {
currentSort.column = column;
currentSort.direction = 'asc';
}
}
allRelatedMicrobes.sort((a, b) => {
let valA = a[column];
let valB = b[column];
// Handle numeric type strings
if (column === 'pH' || column === 'temperature') {
// Extract the first number
valA = valA ? parseFloat(valA.split(',')[0]) : -Infinity;
valB = valB ? parseFloat(valB.split(',')[0]) : -Infinity;
} else {
// Handle empty values
valA = valA || '';
valB = valB || '';
}
if (valA < valB) return currentSort.direction === 'asc' ? -1 : 1;
if (valA > valB) return currentSort.direction === 'asc' ? 1 : -1;
return 0;
});
currentRelatedPage = 1; // Reset to first page after sorting
updateRelatedTable();
updateRelatedPaginationInfo();
}
// Update related microbes table display
function updateRelatedTable() {
const relatedTableElement = document.querySelector('.related-table');
if (!relatedTableElement) {
return;
}
if (!allRelatedMicrobes || allRelatedMicrobes.length === 0) {
relatedTableElement.innerHTML = '<p>No relevant microbial data available</p>';
return;
}
// Calculate current page data
const startIndex = (currentRelatedPage - 1) * relatedRowsPerPage;
const endIndex = Math.min(startIndex + relatedRowsPerPage, totalRelatedRows);
const currentPageData = allRelatedMicrobes.slice(startIndex, endIndex);
// Helper function: generate sort icon
const getSortIcon = (colName) => {
if (currentSort.column !== colName) return '';
return `<span class="sort-icon ${currentSort.direction}"></span>`;
};
// Generate table HTML
let tableHtml = `
<table>
<thead>
<tr style="font-weight: 700; font-size: 100%;">
<th style="cursor: default;">Number</th>
<th onclick="sortData('microbial_name')">Microbial ${getSortIcon('microbial_name')}</th>
<th style="cursor: default;">Nutritional combination</th>
<th onclick="sortData('domain')">Taxo ${getSortIcon('domain')}</th>
<th onclick="sortData('pH')">pH ${getSortIcon('pH')}</th>
<th onclick="sortData('temperature')">Temperature ${getSortIcon('temperature')}</th>
<th onclick="sortData('o2')">O2 ${getSortIcon('o2')}</th>
<th onclick="sortData('cultured_type')">Type ${getSortIcon('cultured_type')}</th>
</tr>
</thead>
<tbody>
`;
currentPageData.forEach((microbe, index) => {
const tempera = microbe.temperature ? microbe.temperature.split(',').map(t => t.trim())[0] : 'N/A';
const rowNumber = startIndex + index + 1;
tableHtml += `
<tr onclick="navigateToMicrobialDetail('${microbe.microbial_name}')" style="cursor: pointer;">
<td>${rowNumber}</td>
<td><a href="#" class="microbial-link">${microbe.microbial_name || 'N/A'}</a></td>
<td><a href="#"><i class="iconfont icon-yingyangbaojian"></i></a></td>
<td>${microbe.domain || 'N/A'}</td>
<td>${microbe.pH || 'N/A'}</td>
<td>${tempera}</td>
<td>${microbe.o2 || 'N/A'}</td>
<td>${microbe.cultured_type || 'N/A'}</td>
</tr>
`;
});
tableHtml += `
</tbody>
</table>
`;
relatedTableElement.innerHTML = tableHtml;
}
// Update related microbes pagination info
function updateRelatedPaginationInfo() {
const showingStart = (currentRelatedPage - 1) * relatedRowsPerPage + 1;
const showingEnd = Math.min(currentRelatedPage * relatedRowsPerPage, totalRelatedRows);
document.getElementById('showing-related-text').textContent =
`Showing ${showingStart} to ${showingEnd} of ${totalRelatedRows} entries.`;
// Update button status
document.getElementById('related-prev-btn').disabled = currentRelatedPage === 1;
document.getElementById('related-next-btn').disabled = currentRelatedPage === totalRelatedPages;
// Update page numbers display
updateRelatedPageNumbers();
}
// Update related microbes page number buttons
function updateRelatedPageNumbers() {
const pageNumbers = document.getElementById('related-page-numbers');
pageNumbers.innerHTML = '';
const startPage = Math.max(1, currentRelatedPage - 2);
const endPage = Math.min(totalRelatedPages, startPage + 4);
for (let i = startPage; i <= endPage; i++) {
const button = document.createElement('button');
button.className = `page-num ${i === currentRelatedPage ? 'active' : ''}`;
button.textContent = i;
button.addEventListener('click', () => {
currentRelatedPage = i;
updateRelatedTable();
updateRelatedPaginationInfo();
});
pageNumbers.appendChild(button);
}
}
// Download related microbes CSV
async function downloadRelatedCSV() {
try {
if (!allRelatedMicrobes || allRelatedMicrobes.length === 0) {
alert('No data available for download');
return;
}
const headers = ['Number', 'Microbial', 'Nutritional combination', 'Taxo', 'pH', 'Temperature', 'O2'];
const csvContent = [
headers.join(','),
...allRelatedMicrobes.map((row, index) => {
const tempera = row.temperature ? row.temperature.split(',').map(t => t.trim())[0] : '';
return [
index + 1,
`"${row.microbial_name || ''}"`,
`"${row.nutrition || ''}"`,
`"${row.domain || ''}"`,
row.pH || '',
tempera,
row.o2 || ''
].join(',');
})
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', 'related_microbes_data.csv');
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (error) {
console.error('Error downloading CSV:', error);
alert('Download failed, please try again later');
}
}
// Update statistics - now only responsible for updating charts
function updateStatistics(relatedMicrobes) {
// Update charts with all related microbial data
updateChartsWithData(allRelatedMicrobes.length > 0 ? allRelatedMicrobes : relatedMicrobes);
}
// Navigate to microbial detail page
function navigateToMicrobialDetail(microbialName) {
const url = `Search_result.html?microbial=${encodeURIComponent(microbialName)}`;
window.location.href = url;
}
// Update charts with related microbial data
function updateChartsWithData(relatedMicrobes) {
if (!relatedMicrobes || relatedMicrobes.length === 0) {
return;
}
// Calculate pH distribution
const phDistribution = calculatePHDistribution(relatedMicrobes);
updatePHChart(phDistribution);
// Calculate O2 distribution
const o2Distribution = calculateO2Distribution(relatedMicrobes);
updateO2Chart(o2Distribution);
// Calculate temperature distribution
const temperatureDistribution = calculateTemperatureDistribution(relatedMicrobes);
updateTemperatureChart(temperatureDistribution);
// Calculate nutrition distribution
const nutritionDistribution = calculateNutritionDistribution(relatedMicrobes);
updateNutritionChart(nutritionDistribution);
}
// Calculate pH distribution
function calculatePHDistribution(relatedMicrobes) {
const phRanges = {
'acidic (pH < 6)': 0,
'neutral (pH 6-8)': 0,
'alkaline (pH > 8)': 0
};
relatedMicrobes.forEach(microbe => {
if (microbe.pH && !isNaN(microbe.pH)) {
const ph = parseFloat(microbe.pH);
if (ph < 6) {
phRanges['acidic (pH < 6)']++;
} else if (ph >= 6 && ph <= 8) {
phRanges['neutral (pH 6-8)']++;
} else {
phRanges['alkaline (pH > 8)']++;
}
}
});
return Object.entries(phRanges).map(([name, value]) => ({
name: name.replace(' ', '\n'),
value }));
}
// Calculate O2 distribution
function calculateO2Distribution(relatedMicrobes) {
const o2Types = {};
relatedMicrobes.forEach(microbe => {
if (microbe.o2 && microbe.o2 !== 'N/A') {
o2Types[microbe.o2] = (o2Types[microbe.o2] || 0) + 1;
}
});
return Object.entries(o2Types).map(([name, value]) => ({ name, value }));
}
// Calculate temperature distribution
function calculateTemperatureDistribution(relatedMicrobes) {
const tempRanges = {
'low (< 20°C)': 0,
'medium (20-40°C)': 0,
'high (> 40°C)': 0
};
relatedMicrobes.forEach(microbe => {
if (microbe.temperature && !isNaN(microbe.temperature)) {
const temp = parseFloat(microbe.temperature ? microbe.temperature.split(',').map(t => t.trim())[0] : 'N/A');
if (temp < 20) {
tempRanges['low (< 20°C)']++;
} else if (temp >= 20 && temp <= 40) {
tempRanges['medium (20-40°C)']++;
} else {
tempRanges['high (> 40°C)']++;
}
}
});
return Object.entries(tempRanges).map(([name, value]) => ({
name: name.replace(' ', '\n'),
value }));
}
// Calculate nutrition distribution
function calculateNutritionDistribution(relatedMicrobes) {
const nutritionCounts = {};
relatedMicrobes.forEach((microbe, index) => {
if (microbe.nutrition) {
const nutrients = microbe.nutrition.replace(/^\[|\]$/g, '').split(', ').map(item => item.trim().replace(/'/g, ''));
nutrients.forEach(nutrient => {
if (nutrient && nutrient.trim() !== '') {
nutritionCounts[nutrient] = (nutritionCounts[nutrient] || 0) + 1;
}
});
}
});
// Sort by frequency descending
return Object.entries(nutritionCounts)
.sort((a, b) => b[1] - a[1])
.map(([name, value]) => ({
name,
value,
frequency: (value / relatedMicrobes.length).toFixed(2)}));
}
// Update pH chart
function updatePHChart(data) {
const chartElement = document.querySelector('.chart-pH');
if (!chartElement) return;
// Check for existing instance
let chart = echarts.getInstanceByDom(chartElement);
if (!chart) {
chart = echarts.init(chartElement);
window.addEventListener('resize', () => chart.resize());
}
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' }
},
grid: {
left: '5%',
right: '5%',
bottom: '5%',
top: '10%',
containLabel: true
},
xAxis: {
type: 'category',
data: data.map(item => item.name),
axisLabel: {
fontSize: 12
}
},
yAxis: {
type: 'value',
axisLabel: {
fontSize: 12
}
},
series: [{
type: 'bar',
data: data.map(item => item.value),
itemStyle: { color: '#9fbaae' }
}]
};
chart.setOption(option);
setTimeout(() => { chart.resize(); }, 50); // Force resize to fit current container width
}
// Update O2 chart
function updateO2Chart(data) {
const chartElement = document.querySelector('.chart-O2');
if (!chartElement) return;
// Check for existing instance
let chart = echarts.getInstanceByDom(chartElement);
if (!chart) {
chart = echarts.init(chartElement);
window.addEventListener('resize', () => chart.resize());
}
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' }
},
grid: {
left: '5%',
right: '5%',
bottom: '5%',
top: '10%',
containLabel: true
},
xAxis: {
type: 'value',
axisLabel: {
fontSize: 12
}
},
yAxis: {
type: 'category',
data: data.map(item => item.name),
axisLabel: {
interval: 0,
fontSize: 12,
formatter: function(value) {
return value.length > 11 ? value.substring(0, 11) + '...' : value;
}
}
},
series: [{
type: 'bar',
data: data.map(item => item.value),
itemStyle: { color: '#9fb3ba' }
}]
};
chart.setOption(option);
setTimeout(() => { chart.resize(); }, 50); // Force resize to fit current container width
}
// Update temperature chart
function updateTemperatureChart(data) {
const chartElement = document.querySelector('.chart-temp');
if (!chartElement) return;
// Check for existing instance
let chart = echarts.getInstanceByDom(chartElement);
if (!chart) {
chart = echarts.init(chartElement);
window.addEventListener('resize', () => chart.resize());
}
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' }
},
grid: {
left: '5%',
right: '5%',
bottom: '5%',
top: '10%',
containLabel: true
},
xAxis: {
type: 'category',
data: data.map(item => item.name),
axisLabel: {
fontSize: 12
}
},
yAxis: {
type: 'value',
axisLabel: {
fontSize: 12
}
},
series: [{
type: 'bar',
data: data.map(item => item.value),
itemStyle: { color: '#9ea0b9' }
}]
};
chart.setOption(option);
setTimeout(() => { chart.resize(); }, 50); // Force resize to fit current container width
}
// Update nutrition chart
function updateNutritionChart(data) {
const chartElement = document.querySelector('.chart-nutrition');
if (!chartElement) return;
// Sort by frequency descending
const sortedData = data.sort((a, b) => parseFloat(b.frequency) - parseFloat(a.frequency));
// Clear container content
chartElement.innerHTML = '';
// Create scroll container
const scrollContainer = document.createElement('div');
scrollContainer.style.cssText = `
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
`;
// Create chart container
const chartContainer = document.createElement('div');
const itemHeight = 30;
const calculatedHeight = Math.max(200, sortedData.length * itemHeight);
chartContainer.style.cssText = `
width: 100%;
height: ${calculatedHeight}px;
position: relative;
`;
// Append container to DOM
chartElement.appendChild(scrollContainer);
scrollContainer.appendChild(chartContainer);
// Initialize ECharts
// Note: Since we are creating a new DOM element every time, we don't check for existing instance here
const chart = echarts.init(chartContainer);
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: function(params) {
const dataIndex = params[0].dataIndex;
const item = sortedData[dataIndex];
return item.name + '<br/>' +
'Count: ' + item.value + '<br/>' +
'Frequency: ' + item.frequency;
}
},
grid: {
left: '5%',
right: '15%', /* Leave space for labels */
bottom: '5%',
top: '5%',
containLabel: true
},
xAxis: {
type: 'value',
name: 'Frequency',
nameLocation: 'middle',
nameGap: 30,
axisLabel: {
formatter: function(value) {
return value.toFixed(1);
}
}
},
yAxis: {
type: 'category',
data: sortedData.map(item => item.name),
axisLabel: {
interval: 0,
formatter: function(value) {
return value.length > 10 ? value.substring(0, 10) + '...' : value;
}
}
},
series: [{
type: 'bar',
data: sortedData.map(item => parseFloat(item.frequency)),
itemStyle: { color: '#af9eb9' },
label: {
show: true,
position: 'right',
formatter: function(params) {
const dataIndex = params.dataIndex;
return sortedData[dataIndex].frequency;
}
}
}]
};
chart.setOption(option);
setTimeout(() => { chart.resize(); }, 50); // Ensure it fits the newly created container
// Add resize listener for the window
window.addEventListener('resize', () => chart.resize());
}
// Execute after page load
document.addEventListener('DOMContentLoaded', function() {
const microbialName = getUrlParameter('microbial');
const savedLevel = localStorage.getItem('lastTaxonomyLevel') || 'genus';
const taxonomySelect = document.getElementById('taxonomy-level');
if(taxonomySelect) {
taxonomySelect.value = savedLevel;
}
if (microbialName) {
// Set global variable currentMicrobialName to the name in URL
window.currentMicrobialName = microbialName;
loadMicrobialDetail(microbialName, savedLevel);
} else {
showNoMicrobialSelected();
}
// Add click event to related microbes table
document.addEventListener('click', function(e) {
if (e.target.closest('.related-table tbody tr')) {
const row = e.target.closest('tr');
const microbialName = row.cells[1].textContent; // Microbial name is in the second column
navigateToMicrobialDetail(microbialName);
}
});
// Related microbes pagination events
document.getElementById('related-prev-btn').addEventListener('click', function() {
if (currentRelatedPage > 1) {
currentRelatedPage--;
updateRelatedTable();
updateRelatedPaginationInfo();
}
});
document.getElementById('related-next-btn').addEventListener('click', function() {
if (currentRelatedPage < totalRelatedPages) {
currentRelatedPage++;
updateRelatedTable();
updateRelatedPaginationInfo();
}
});
// Related microbes rows per page change
document.getElementById('related_per_page').addEventListener('change', function() {
relatedRowsPerPage = parseInt(this.value);
currentRelatedPage = 1;
totalRelatedPages = Math.ceil(totalRelatedRows / relatedRowsPerPage);
updateRelatedTable();
updateRelatedPaginationInfo();
});
// Download related microbes CSV button
document.getElementById('download-related-btn').addEventListener('click', downloadRelatedCSV);
// Taxonomy level selection
document.getElementById('taxonomy-level').addEventListener('change', function() {
const newLevel = this.value;
localStorage.setItem('lastTaxonomyLevel', newLevel);
if (window.currentMicrobialName) {
loadMicrobialDetail(window.currentMicrobialName, newLevel);
}
});
});
// Bind global click events to window object for onclick calls in HTML
window.sortData = sortData;
window.goBack = goBack;
window.downloadRelatedCSV = downloadRelatedCSV;