887 lines
31 KiB
JavaScript
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; |