// 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 // --- Search Functions --- // Search function (with auto scroll) async function performSearch() { const microbialInput = document.getElementById('microbial-name-input').value; if (microbialInput.trim()) { try { showLoadingState(); console.log('Searching for:', microbialInput); // Call Search API const response = await fetch(`${API_BASE_URL}/microbial-detail?name=${encodeURIComponent(microbialInput)}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); const data = result.data; console.log('Search results:', data); hideLoadingState(); if (data) { // Show results displaySearchResult(data); // Scroll to results document.getElementById('results-section').scrollIntoView({ behavior: 'smooth' }); } else { showNoResults(); } } catch (error) { console.error('Search error:', error); hideLoadingState(); showErrorState(error.message); } } else { alert('Please enter a microbial name.'); } } // Search function (without scroll - for initial load) async function performSearchWithoutScroll() { const microbialInput = document.getElementById('microbial-name-input').value; if (microbialInput.trim()) { try { showLoadingState(); console.log('Searching for:', microbialInput); const response = await fetch(`${API_BASE_URL}/microbial-detail?name=${encodeURIComponent(microbialInput)}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); const data = result.data; console.log('Search results:', data); hideLoadingState(); if (data) { displaySearchResult(data); } else { showNoResults(); } } catch (error) { console.error('Search error:', error); hideLoadingState(); showErrorState(error.message); } } } // Display search results function displaySearchResult(microbial) { console.log('Displaying result for:', microbial); if (!microbial) { console.error('No microbial data provided'); showNoResults(); return; } // Update basic info updateMicrobialInfo(microbial); // Update culture medium info updateCultureMediumInfo(microbial); // Update related species statistics and table if (microbial.related_microbes && microbial.related_microbes.length > 0) { updateRelatedMicrobes(microbial.related_microbes); updateStatistics(microbial.related_microbes); } else { // Clear related data if none updateRelatedMicrobes([]); updateStatistics([]); } console.log('Search result displayed successfully'); } // Format probability function formatProbability(probability) { if (!probability || probability === 'N/A') { return 'N/A'; } const probValue = parseFloat(probability); if (isNaN(probValue)) { return 'N/A'; } return probValue.toFixed(2); } // Get confidence color function getConfidenceColor(probability) { if (!probability || probability === 'N/A') { return '#f0f0f0'; // Default gray } let probValue = parseFloat(probability); if (isNaN(probValue)) { return '#f0f0f0'; } if (probValue <= 1) { probValue = probValue * 100; } 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'; } } // --- DOM Update Functions --- function updateMicrobialInfo(microbial) { console.log('Updating microbial info with:', microbial); // Specie name const specie_name = document.getElementById('specie-name'); if (specie_name) { const specieNameElement = specie_name.querySelector('p'); if (specieNameElement) { specieNameElement.textContent = microbial.microbial_name || 'N/A'; } } // Taxonomy const Taxonomy = document.getElementById('Taxonomy'); if (Taxonomy) { const taxonomyElement = Taxonomy.querySelector('p'); if (taxonomyElement) { taxonomyElement.textContent = microbial.taxonomy || 'N/A'; } } // pH const pH = document.getElementById('pH'); if (pH) { const pHValue = pH.querySelector('p'); if (pHValue) { pHValue.textContent = microbial.ppH ? parseFloat(microbial.ppH).toFixed(0) : 'N/A'; } } // Temperature const Temperature = document.getElementById('Temperature'); if (Temperature) { const tempValue = Temperature.querySelector('p'); const tempConfidenceColor = getConfidenceColor(microbial.ptemperature_probability); const formattedTempProbability = formatProbability(microbial.ptemperature_probability); if (tempValue) { tempValue.textContent = microbial.ptemperature || 'N/A'; const existingConfidence = tempValue.parentElement.querySelector('.confidence-span'); if (existingConfidence) { existingConfidence.remove(); } if (microbial.ptemperature_probability) { const confidenceSpan = document.createElement('span'); confidenceSpan.style.cssText = 'margin-left: 10px; padding: 5px; font-weight: 400; border-radius: 4px; background: ' + tempConfidenceColor + ';'; confidenceSpan.textContent = 'Confidence: ' + formattedTempProbability; confidenceSpan.className = 'confidence-span'; tempValue.parentElement.insertBefore(confidenceSpan, tempValue.nextSibling); } } } // Oxygen const Oxygen = document.getElementById('Oxygen-resistance'); if (Oxygen) { const o2Value = Oxygen.querySelector('p'); const o2ConfidenceColor = getConfidenceColor(microbial.po2_probability); const formattedO2Probability = formatProbability(microbial.po2_probability); if (o2Value) { o2Value.textContent = microbial.po2 || 'N/A'; const existingConfidence = o2Value.parentElement.querySelector('.confidence-span'); if (existingConfidence) { existingConfidence.remove(); } if (microbial.po2_probability) { const confidenceSpan = document.createElement('span'); confidenceSpan.style.cssText = 'margin-left: 10px; padding: 5px; font-weight: 400; border-radius: 4px; background: ' + o2ConfidenceColor + ';'; confidenceSpan.textContent = 'Confidence: ' + formattedO2Probability; confidenceSpan.className = 'confidence-span'; o2Value.parentElement.insertBefore(confidenceSpan, o2Value.nextSibling); } } } } function updateCultureMediumInfo(microbial) { const cultureMediumCard = document.getElementById('culture-medium'); if (!cultureMediumCard || !microbial.nutrition) return; const nutritionArrays = microbial.nutrition.split(';').map(arrStr => { const trimmed = arrStr.trim().replace(/^\[|\]$/g, ''); return trimmed ? trimmed .split(',') .map(item => item.trim().replace(/'/g, '')) : []; }); if (nutritionArrays && nutritionArrays.length > 0) { let mediaHtml = ''; mediaHtml += `Media`; nutritionArrays.forEach((media, index) => { if (media && media.length > 0) { mediaHtml += `

${index + 1}:

${Array.isArray(media) ? media.join(', ') : media}
`; } }); cultureMediumCard.innerHTML = mediaHtml; } } // --- Related Microbes Table Logic --- function updateRelatedMicrobes(relatedMicrobes) { allRelatedMicrobes = relatedMicrobes || []; totalRelatedRows = allRelatedMicrobes.length; totalRelatedPages = Math.ceil(totalRelatedRows / relatedRowsPerPage); if(totalRelatedPages === 0) totalRelatedPages = 1; updateRelatedTable(); updateRelatedPaginationInfo(); } function updateRelatedTable() { const tableBody = document.querySelector('.table-container tbody'); if (!tableBody) { console.error('Table body not found'); return; } if (!allRelatedMicrobes || allRelatedMicrobes.length === 0) { tableBody.innerHTML = 'No relevant microbial data available'; return; } const startIndex = (currentRelatedPage - 1) * relatedRowsPerPage; const endIndex = Math.min(startIndex + relatedRowsPerPage, totalRelatedRows); const currentPageData = allRelatedMicrobes.slice(startIndex, endIndex); let tableHtml = ''; currentPageData.forEach((microbe, index) => { const micppH = microbe.ppH ? parseFloat(microbe.ppH).toFixed(0) : 'Unknown'; const rowNumber = startIndex + index + 1; tableHtml += ` ${rowNumber} ${microbe.microbial_name || 'N/A'} ${microbe.nutrition || 'N/A'} ${microbe.domain || 'N/A'} ${micppH} ${microbe.ptemperature || 'Unknown'} ${microbe.po2 || 'Unknown'} ${microbe.cultured_type || 'Unknown'} `; }); tableBody.innerHTML = tableHtml; } window.navigateToDetail = function(microbialName) { // If you want to redirect to a detail page, uncomment below // window.location.href = `Search_result.html?microbial=${encodeURIComponent(microbialName)}`; // If you want to update the current page (SPA style) since we are on Search_merged document.getElementById('microbial-name-input').value = microbialName; performSearch(); // Scroll to top to see new input window.scrollTo({ top: 0, behavior: 'smooth' }); } function updateRelatedPaginationInfo() { const showingText = document.getElementById('showing-text'); const prevBtn = document.getElementById('prev-btn'); const nextBtn = document.getElementById('next-btn'); if (showingText) { const startIndex = totalRelatedRows > 0 ? (currentRelatedPage - 1) * relatedRowsPerPage + 1 : 0; const endIndex = Math.min(currentRelatedPage * relatedRowsPerPage, totalRelatedRows); showingText.textContent = `Showing ${startIndex} to ${endIndex} of ${totalRelatedRows} entries.`; } if (prevBtn) { prevBtn.disabled = currentRelatedPage <= 1; } if (nextBtn) { nextBtn.disabled = currentRelatedPage >= totalRelatedPages; } updateRelatedPageNumbers(); } function updateRelatedPageNumbers() { const pageNumbers = document.getElementById('page-numbers'); if (!pageNumbers) return; pageNumbers.innerHTML = ''; if (totalRelatedPages <= 7) { for (let i = 1; i <= totalRelatedPages; i++) { const pageBtn = document.createElement('button'); pageBtn.textContent = i; pageBtn.className = `page-num ${i === currentRelatedPage ? 'active' : ''}`; pageBtn.addEventListener('click', () => { currentRelatedPage = i; updateRelatedTable(); updateRelatedPaginationInfo(); }); pageNumbers.appendChild(pageBtn); } } else { const startPage = Math.max(1, currentRelatedPage - 2); const endPage = Math.min(totalRelatedPages, startPage + 4); if (startPage > 1) { const firstButton = document.createElement('button'); firstButton.className = `page-num ${1 === currentRelatedPage ? 'active' : ''}`; firstButton.textContent = '1'; firstButton.addEventListener('click', () => { currentRelatedPage = 1; updateRelatedTable(); updateRelatedPaginationInfo(); }); pageNumbers.appendChild(firstButton); if (startPage > 2) { const ellipsis = document.createElement('span'); ellipsis.className = 'page-ellipsis'; ellipsis.textContent = '...'; ellipsis.style.cssText = `padding: 8px 4px; color: #666; font-weight: bold; display: flex; align-items: center; justify-content: center; min-width: 20px;`; pageNumbers.appendChild(ellipsis); } } for (let i = startPage; i <= endPage; i++) { if (i === 1 && startPage > 1) continue; const pageBtn = document.createElement('button'); pageBtn.textContent = i; pageBtn.className = `page-num ${i === currentRelatedPage ? 'active' : ''}`; pageBtn.addEventListener('click', () => { currentRelatedPage = i; updateRelatedTable(); updateRelatedPaginationInfo(); }); pageNumbers.appendChild(pageBtn); } if (endPage < totalRelatedPages) { if (endPage < totalRelatedPages - 1) { const ellipsis = document.createElement('span'); ellipsis.className = 'page-ellipsis'; ellipsis.textContent = '...'; ellipsis.style.cssText = `padding: 8px 4px; color: #666; font-weight: bold; display: flex; align-items: center; justify-content: center; min-width: 20px;`; pageNumbers.appendChild(ellipsis); } const lastButton = document.createElement('button'); lastButton.className = `page-num ${totalRelatedPages === currentRelatedPage ? 'active' : ''}`; lastButton.textContent = totalRelatedPages; lastButton.addEventListener('click', () => { currentRelatedPage = totalRelatedPages; updateRelatedTable(); updateRelatedPaginationInfo(); }); pageNumbers.appendChild(lastButton); } } } // --- Statistics and Charts --- function updateStatistics(relatedMicrobes) { if (!relatedMicrobes || relatedMicrobes.length === 0) { // Clear charts if no data updatePHChart([]); updateO2Chart([]); updateTemperatureChart([]); updateNutritionChart([]); return; } const pHData = calculatePHDistribution(relatedMicrobes); updatePHChart(pHData); const o2Data = calculateO2Distribution(relatedMicrobes); updateO2Chart(o2Data); const tempData = calculateTemperatureDistribution(relatedMicrobes); updateTemperatureChart(tempData); const nutritionData = calculateNutritionDistribution(relatedMicrobes); updateNutritionChart(nutritionData); } function calculatePHDistribution(relatedMicrobes) { const pHCounts = {}; relatedMicrobes.forEach(microbe => { if (microbe.pH || microbe.ppH) { const pH = parseFloat(microbe.pH || microbe.ppH); let category; if (pH < 6) category = 'acidic (pH < 6)'; else if (pH > 8) category = 'alkaline (pH > 8)'; else category = 'neutral (pH 6-8)'; pHCounts[category] = (pHCounts[category] || 0) + 1; } }); return Object.entries(pHCounts).map(([name, value]) => ({ name: name.replace(' ', '\n'), value })); } function calculateO2Distribution(relatedMicrobes) { const o2Counts = {}; relatedMicrobes.forEach(microbe => { const o2 = microbe.o2 || microbe.po2; if (o2) { const o2Lower = o2.toLowerCase(); o2Counts[o2Lower] = (o2Counts[o2Lower] || 0) + 1; } }); return Object.entries(o2Counts).map(([name, value]) => ({ name, value })); } function calculateTemperatureDistribution(relatedMicrobes) { const tempCounts = {}; relatedMicrobes.forEach(microbe => { const tVal = microbe.temperature || microbe.ptemperature; if (tVal) { const temp = parseFloat(tVal); let category; if (temp < 20) category = 'low (< 20°C)'; else if (temp > 40) category = 'high (> 40°C)'; else category = 'medium (20-40°C)'; tempCounts[category] = (tempCounts[category] || 0) + 1; } }); return Object.entries(tempCounts).map(([name, value]) => ({ name: name.replace(' ', '\n'), value })); } function calculateNutritionDistribution(relatedMicrobes) { const nutritionCounts = {}; relatedMicrobes.forEach(microbe => { if (microbe.nutrition) { let nutrients = []; if (typeof microbe.nutrition === 'string') { const nutritionStr = microbe.nutrition.replace(/[\[\]']/g, ''); nutrients = nutritionStr.split(',').map(n => n.trim()).filter(n => n !== ''); } nutrients.forEach(nutrient => { if (nutrient.trim() !== '') { nutritionCounts[nutrient] = (nutritionCounts[nutrient] || 0) + 1; } }); } }); return Object.entries(nutritionCounts) .sort((a, b) => b[1] - a[1]) .map(([name, value]) => ({ name, value, frequency: (relatedMicrobes.length > 0) ? (value / relatedMicrobes.length).toFixed(2) : 0 })); } // Chart Updating Functions (using ECharts) function updatePHChart(data) { const chartElement = document.querySelector('.chart-pH'); if (!chartElement) return; let chart = echarts.getInstanceByDom(chartElement); if (!chart) chart = echarts.init(chartElement); const option = { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, grid: { left: '1%', right: '4%', bottom: '3%', top: '6%', containLabel: true }, xAxis: { type: 'category', data: data.map(item => item.name), axisLabel: { fontSize: 10 } }, yAxis: { type: 'value' }, series: [{ type: 'bar', data: data.map(item => item.value), itemStyle: { color: '#9fbaae' } }] }; chart.setOption(option); window.addEventListener('resize', () => chart.resize()); } function updateO2Chart(data) { const chartElement = document.querySelector('.chart-O2'); if (!chartElement) return; let chart = echarts.getInstanceByDom(chartElement); if (!chart) chart = echarts.init(chartElement); const option = { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, grid: { left: '1%', right: '4%', bottom: '3%', top: '6%', containLabel: true }, xAxis: { type: 'value' }, yAxis: { type: 'category', data: data.map(item => item.name), axisLabel: { fontSize: 10, formatter: function(value) { return value.length > 10 ? value.substring(0, 10) + '...' : value; } } }, series: [{ type: 'bar', data: data.map(item => item.value), itemStyle: { color: '#9fb3ba' } }] }; chart.setOption(option); window.addEventListener('resize', () => chart.resize()); } function updateTemperatureChart(data) { const chartElement = document.querySelector('.chart-temp'); if (!chartElement) return; let chart = echarts.getInstanceByDom(chartElement); if (!chart) chart = echarts.init(chartElement); const option = { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, grid: { left: '1%', right: '4%', bottom: '3%', top: '6%', containLabel: true }, xAxis: { type: 'category', data: data.map(item => item.name), axisLabel: { fontSize: 10 } }, yAxis: { type: 'value' }, series: [{ type: 'bar', data: data.map(item => item.value), itemStyle: { color: '#9ea0b9' } }] }; chart.setOption(option); window.addEventListener('resize', () => chart.resize()); } function updateNutritionChart(data) { const chartElement = document.querySelector('.chart-nutrition'); if (!chartElement) return; const sortedData = data.sort((a, b) => parseFloat(b.frequency) - parseFloat(a.frequency)).slice(0, 15); // Top 15 chartElement.innerHTML = ''; const scrollContainer = document.createElement('div'); scrollContainer.style.cssText = `width: 100%; height: 100%; overflow-y: auto; overflow-x: hidden;`; const chartContainer = document.createElement('div'); const itemHeight = 30; const calculatedHeight = Math.max(200, sortedData.length * itemHeight); chartContainer.style.cssText = `width: 100%; height: ${calculatedHeight}px;`; chartElement.appendChild(scrollContainer); scrollContainer.appendChild(chartContainer); 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 + '
Count: ' + item.value + '
Freq: ' + item.frequency; } }, grid: { left: '0%', right: '12%', bottom: '3%', top: '1%', containLabel: true }, xAxis: { type: 'value', name: 'Freq' }, yAxis: { type: 'category', data: sortedData.map(item => item.name), axisLabel: { 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: '{@score}' } }] }; chart.setOption(option); window.addEventListener('resize', () => chart.resize()); } // --- Utils --- function showLoadingState() { console.log('Loading...'); } function hideLoadingState() { console.log('Loading complete'); } function showErrorState(message) { alert('Error: ' + message); } function showNoResults() { alert('No results found'); } async function downloadCSV() { if (!allRelatedMicrobes || allRelatedMicrobes.length === 0) { alert('No data available for download'); return; } const headers = ['Number', 'Microbial', 'Nutritional combination', 'Taxo', 'pH', 'Temperature', 'O2', 'Type']; const csvContent = [ headers.join(','), ...allRelatedMicrobes.map((row, index) => { return [ index + 1, `"${row.microbial_name || ''}"`, `"${(row.nutrition || '').replace(/"/g, '""')}"`, `"${row.domain || ''}"`, row.ppH || row.pH || '', row.ptemperature || '', row.po2 || '', row.cultured_type || '' ].join(','); }) ].join('\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.setAttribute('download', 'related_microbes_data.csv'); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } // --- Initialization --- document.addEventListener('DOMContentLoaded', function() { // Tabs const tabButtons = document.querySelectorAll('.tab-btn'); const tabPanels = document.querySelectorAll('.tab-panel'); tabButtons.forEach(button => { button.addEventListener('click', function() { const targetTab = this.getAttribute('data-tab'); tabButtons.forEach(btn => btn.classList.remove('active')); tabPanels.forEach(panel => panel.classList.remove('active')); this.classList.add('active'); document.getElementById(targetTab).classList.add('active'); }); }); // Pagination Controls document.getElementById('per_page').addEventListener('change', function() { relatedRowsPerPage = parseInt(this.value); currentRelatedPage = 1; updateRelatedTable(); updateRelatedPaginationInfo(); }); document.getElementById('prev-btn').addEventListener('click', function() { if (currentRelatedPage > 1) { currentRelatedPage--; updateRelatedTable(); updateRelatedPaginationInfo(); } }); document.getElementById('next-btn').addEventListener('click', function() { if (currentRelatedPage < totalRelatedPages) { currentRelatedPage++; updateRelatedTable(); updateRelatedPaginationInfo(); } }); // Buttons document.getElementById('reset-btn').addEventListener('click', function() { document.getElementById('microbial-name-input').value = ''; window.scrollTo({ top: 0, behavior: 'smooth' }); }); document.getElementById('search-btn').addEventListener('click', performSearch); document.getElementById('download-btn').addEventListener('click', downloadCSV); // Auto-search if sample data present (Simulation) const microbialInput = document.getElementById('microbial-name-input'); setTimeout(() => { if (microbialInput && !microbialInput.value) { microbialInput.value = 'Escherichia coli B3'; performSearchWithoutScroll(); } }, 500); });