Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Bioinformatics Toolkit</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
<style> | |
.tool-card:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); | |
} | |
.file-upload { | |
border: 2px dashed #cbd5e0; | |
transition: all 0.3s ease; | |
} | |
.file-upload:hover { | |
border-color: #4f46e5; | |
} | |
.file-upload.dragover { | |
border-color: #4f46e5; | |
background-color: #f0f7ff; | |
} | |
#volcanoPlot { | |
width: 100%; | |
height: 500px; | |
} | |
.gene-item:hover { | |
background-color: #f3f4f6; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 min-h-screen"> | |
<!-- Navigation --> | |
<nav class="bg-indigo-600 text-white shadow-lg"> | |
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
<div class="flex justify-between h-16 items-center"> | |
<div class="flex items-center"> | |
<div class="flex-shrink-0 flex items-center"> | |
<svg class="h-8 w-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"> | |
<path d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5a2.5 2.5 0 00-5 0V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7 0 1.49-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5a2.5 2.5 0 000-5z" /> | |
</svg> | |
<span class="ml-2 text-xl font-bold">BioTools</span> | |
</div> | |
</div> | |
<div> | |
<button id="homeBtn" class="px-3 py-2 rounded-md text-sm font-medium bg-indigo-700">Home</button> | |
</div> | |
</div> | |
</div> | |
</nav> | |
<!-- Main Content --> | |
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> | |
<!-- Home Page --> | |
<div id="homePage" class="space-y-8"> | |
<div class="text-center"> | |
<h1 class="text-4xl font-bold text-gray-900 mb-2">Bioinformatics Toolkit</h1> | |
<p class="text-xl text-gray-600">A collection of tools for biological data analysis</p> | |
</div> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
<!-- Volcano Plot Tool Card --> | |
<div class="bg-white rounded-lg shadow-md overflow-hidden tool-card transition-all duration-300 cursor-pointer" id="volcanoToolCard"> | |
<div class="p-6"> | |
<div class="flex items-center"> | |
<div class="flex-shrink-0 bg-indigo-100 p-3 rounded-lg"> | |
<svg class="h-8 w-8 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /> | |
</svg> | |
</div> | |
<div class="ml-4"> | |
<h3 class="text-lg font-medium text-gray-900">Volcano Plot</h3> | |
<p class="text-gray-500">Visualize differential gene expression</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Enrichment Tool Card --> | |
<div class="bg-white rounded-lg shadow-md overflow-hidden tool-card transition-all duration-300 cursor-pointer" id="enrichmentToolCard"> | |
<div class="p-6"> | |
<div class="flex items-center"> | |
<div class="flex-shrink-0 bg-green-100 p-3 rounded-lg"> | |
<svg class="h-8 w-8 text-green-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" /> | |
</svg> | |
</div> | |
<div class="ml-4"> | |
<h3 class="text-lg font-medium text-gray-900">Gene Enrichment</h3> | |
<p class="text-gray-500">Analyze DEGs for pathway enrichment</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Volcano Plot Tool --> | |
<div id="volcanoToolPage" class="hidden bg-white rounded-lg shadow-md p-6 space-y-6"> | |
<div class="flex items-center"> | |
<button id="volcanoBackBtn" class="mr-4 p-2 rounded-full hover:bg-gray-100"> | |
<svg class="h-6 w-6 text-gray-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /> | |
</svg> | |
</button> | |
<h2 class="text-2xl font-bold text-gray-900">Volcano Plot Tool</h2> | |
</div> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
<div class="lg:col-span-1 space-y-4"> | |
<div class="file-upload p-8 rounded-lg text-center cursor-pointer" id="volcanoFileUpload"> | |
<input type="file" id="volcanoFileInput" class="hidden" accept=".csv,.tsv,.txt"> | |
<svg class="mx-auto h-12 w-12 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> | |
</svg> | |
<h3 class="mt-2 text-sm font-medium text-gray-900">Upload your data</h3> | |
<p class="mt-1 text-sm text-gray-500">CSV, TSV or TXT file with gene expression data</p> | |
<p class="mt-1 text-xs text-gray-500">(symbol, log2FoldChange, padj columns required)</p> | |
</div> | |
<div class="bg-gray-50 p-4 rounded-lg"> | |
<h3 class="text-sm font-medium text-gray-900 mb-2">Plot Settings</h3> | |
<div class="space-y-3"> | |
<div> | |
<label for="logFCCutoff" class="block text-sm font-medium text-gray-700">logFC Cutoff</label> | |
<input type="number" id="logFCCutoff" value="1" step="0.1" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"> | |
</div> | |
<div> | |
<label for="pValueCutoff" class="block text-sm font-medium text-gray-700">p-value Cutoff</label> | |
<input type="number" id="pValueCutoff" value="0.05" step="0.01" min="0" max="1" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"> | |
</div> | |
<button id="generateVolcanoBtn" class="w-full bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> | |
Generate Plot | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="lg:col-span-2"> | |
<div class="bg-gray-50 p-4 rounded-lg h-full"> | |
<h3 class="text-sm font-medium text-gray-900 mb-2">Volcano Plot</h3> | |
<div id="volcanoPlotContainer"> | |
<canvas id="volcanoPlot"></canvas> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Enrichment Tool --> | |
<div id="enrichmentToolPage" class="hidden bg-white rounded-lg shadow-md p-6 space-y-6"> | |
<div class="flex items-center"> | |
<button id="enrichmentBackBtn" class="mr-4 p-2 rounded-full hover:bg-gray-100"> | |
<svg class="h-6 w-6 text-gray-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /> | |
</svg> | |
</button> | |
<h2 class="text-2xl font-bold text-gray-900">Gene Enrichment Tool</h2> | |
</div> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
<div class="lg:col-span-1 space-y-4"> | |
<div class="file-upload p-8 rounded-lg text-center cursor-pointer" id="enrichmentFileUpload"> | |
<input type="file" id="enrichmentFileInput" class="hidden" accept=".csv,.tsv,.txt"> | |
<svg class="mx-auto h-12 w-12 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> | |
</svg> | |
<h3 class="mt-2 text-sm font-medium text-gray-900">Upload your DEGs</h3> | |
<p class="mt-1 text-sm text-gray-500">CSV, TSV or TXT file with gene list</p> | |
<p class="mt-1 text-xs text-gray-500">(One gene per line or GeneID column)</p> | |
</div> | |
<div class="bg-gray-50 p-4 rounded-lg"> | |
<h3 class="text-sm font-medium text-gray-900 mb-2">Enrichment Settings</h3> | |
<div class="space-y-3"> | |
<div> | |
<label for="databaseSelect" class="block text-sm font-medium text-gray-700">Database</label> | |
<select id="databaseSelect" class="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"> | |
<option>GO Biological Process</option> | |
<option>GO Molecular Function</option> | |
<option>GO Cellular Component</option> | |
<option>KEGG Pathways</option> | |
<option>Reactome Pathways</option> | |
</select> | |
</div> | |
<div> | |
<label for="pValueEnrichCutoff" class="block text-sm font-medium text-gray-700">p-value Cutoff</label> | |
<input type="number" id="pValueEnrichCutoff" value="0.05" step="0.01" min="0" max="1" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"> | |
</div> | |
<button id="runEnrichmentBtn" class="w-full bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"> | |
Run Enrichment | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="lg:col-span-2"> | |
<div class="bg-gray-50 p-4 rounded-lg h-full"> | |
<h3 class="text-sm font-medium text-gray-900 mb-2">Enrichment Results</h3> | |
<div id="enrichmentResults" class="overflow-auto max-h-96"> | |
<div class="text-center text-gray-500 py-8"> | |
<svg class="mx-auto h-12 w-12 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> | |
</svg> | |
<p class="mt-2">Upload your DEGs and run enrichment analysis to see results</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</main> | |
<script> | |
// DOM Elements | |
const homePage = document.getElementById('homePage'); | |
const volcanoToolPage = document.getElementById('volcanoToolPage'); | |
const enrichmentToolPage = document.getElementById('enrichmentToolPage'); | |
const volcanoToolCard = document.getElementById('volcanoToolCard'); | |
const enrichmentToolCard = document.getElementById('enrichmentToolCard'); | |
const homeBtn = document.getElementById('homeBtn'); | |
const volcanoBackBtn = document.getElementById('volcanoBackBtn'); | |
const enrichmentBackBtn = document.getElementById('enrichmentBackBtn'); | |
// File upload elements | |
const volcanoFileUpload = document.getElementById('volcanoFileUpload'); | |
const volcanoFileInput = document.getElementById('volcanoFileInput'); | |
const enrichmentFileUpload = document.getElementById('enrichmentFileUpload'); | |
const enrichmentFileInput = document.getElementById('enrichmentFileInput'); | |
// Buttons | |
const generateVolcanoBtn = document.getElementById('generateVolcanoBtn'); | |
const runEnrichmentBtn = document.getElementById('runEnrichmentBtn'); | |
// Results containers | |
const enrichmentResults = document.getElementById('enrichmentResults'); | |
// Chart variables | |
let volcanoChart = null; | |
let enrichmentChart = null; | |
// Event Listeners | |
volcanoToolCard.addEventListener('click', () => { | |
homePage.classList.add('hidden'); | |
volcanoToolPage.classList.remove('hidden'); | |
enrichmentToolPage.classList.add('hidden'); | |
}); | |
enrichmentToolCard.addEventListener('click', () => { | |
homePage.classList.add('hidden'); | |
enrichmentToolPage.classList.remove('hidden'); | |
volcanoToolPage.classList.add('hidden'); | |
}); | |
homeBtn.addEventListener('click', () => { | |
homePage.classList.remove('hidden'); | |
volcanoToolPage.classList.add('hidden'); | |
enrichmentToolPage.classList.add('hidden'); | |
}); | |
volcanoBackBtn.addEventListener('click', () => { | |
homePage.classList.remove('hidden'); | |
volcanoToolPage.classList.add('hidden'); | |
}); | |
enrichmentBackBtn.addEventListener('click', () => { | |
homePage.classList.remove('hidden'); | |
enrichmentToolPage.classList.add('hidden'); | |
}); | |
// File upload handling | |
volcanoFileUpload.addEventListener('click', () => volcanoFileInput.click()); | |
enrichmentFileUpload.addEventListener('click', () => enrichmentFileInput.click()); | |
// Drag and drop for file uploads | |
[volcanoFileUpload, enrichmentFileUpload].forEach(uploadArea => { | |
uploadArea.addEventListener('dragover', (e) => { | |
e.preventDefault(); | |
uploadArea.classList.add('dragover'); | |
}); | |
uploadArea.addEventListener('dragleave', () => { | |
uploadArea.classList.remove('dragover'); | |
}); | |
uploadArea.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
uploadArea.classList.remove('dragover'); | |
const fileInput = uploadArea === volcanoFileUpload ? volcanoFileInput : enrichmentFileInput; | |
if (e.dataTransfer.files.length) { | |
fileInput.files = e.dataTransfer.files; | |
updateFileUploadUI(uploadArea, e.dataTransfer.files[0].name); | |
} | |
}); | |
}); | |
volcanoFileInput.addEventListener('change', () => { | |
if (volcanoFileInput.files.length) { | |
updateFileUploadUI(volcanoFileUpload, volcanoFileInput.files[0].name); | |
} | |
}); | |
enrichmentFileInput.addEventListener('change', () => { | |
if (enrichmentFileInput.files.length) { | |
updateFileUploadUI(enrichmentFileUpload, enrichmentFileInput.files[0].name); | |
} | |
}); | |
function updateFileUploadUI(uploadArea, fileName) { | |
uploadArea.innerHTML = ` | |
<svg class="mx-auto h-12 w-12 text-green-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /> | |
</svg> | |
<h3 class="mt-2 text-sm font-medium text-gray-900">File ready</h3> | |
<p class="mt-1 text-sm text-gray-500">${fileName}</p> | |
<p class="mt-1 text-xs text-gray-500">Click to change file</p> | |
`; | |
} | |
// Generate Volcano Plot | |
generateVolcanoBtn.addEventListener('click', () => { | |
if (!volcanoFileInput.files.length) { | |
alert('Please upload a file first'); | |
return; | |
} | |
// In a real app, you would parse the file here | |
// For demo purposes, we'll use mock data | |
const mockData = generateMockVolcanoData(); | |
createVolcanoPlot(mockData); | |
}); | |
// Run Enrichment Analysis | |
runEnrichmentBtn.addEventListener('click', () => { | |
if (!enrichmentFileInput.files.length) { | |
alert('Please upload a file first'); | |
return; | |
} | |
// In a real app, you would parse the file and run enrichment | |
// For demo purposes, we'll use mock results | |
const mockResults = generateMockEnrichmentResults(); | |
displayEnrichmentResults(mockResults); | |
}); | |
// Helper functions | |
function generateMockVolcanoData() { | |
// Generate mock data for the volcano plot | |
const data = []; | |
const geneNames = ['TP53', 'BRCA1', 'EGFR', 'MYC', 'AKT1', 'PTEN', 'CDKN2A', 'RB1', 'NF1', 'APC']; | |
// Generate non-significant points | |
for (let i = 0; i < 1000; i++) { | |
const log2FoldChange = (Math.random() - 0.5) * 4; | |
const padj = Math.pow(10, -Math.random() * 5); | |
data.push({ | |
symbol: i < 10 ? geneNames[i] : `Gene_${i}`, | |
log2FoldChange: log2FoldChange, | |
padj: padj | |
}); | |
} | |
// Add significant points (more dramatic fold changes) | |
for (let i = 0; i < 20; i++) { | |
const log2FoldChange = (Math.random() > 0.5 ? 1 : -1) * (1.5 + Math.random() * 3); | |
const padj = Math.pow(10, -(3 + Math.random() * 4)); | |
data.push({ | |
symbol: `SigGene_${i}`, | |
log2FoldChange: log2FoldChange, | |
padj: padj | |
}); | |
} | |
return data; | |
} | |
function createVolcanoPlot(data) { | |
const ctx = document.getElementById('volcanoPlot').getContext('2d'); | |
const logFCCutoff = parseFloat(document.getElementById('logFCCutoff').value); | |
const pValueCutoff = parseFloat(document.getElementById('pValueCutoff').value); | |
// Prepare data for Chart.js | |
const plotData = { | |
datasets: [{ | |
label: 'Not significant', | |
data: data.filter(d => | |
Math.abs(d.log2FoldChange) < logFCCutoff || d.padj > pValueCutoff | |
).map(d => ({ | |
x: d.log2FoldChange, | |
y: -Math.log10(d.padj), | |
symbol: d.symbol | |
})), | |
backgroundColor: 'rgba(120, 120, 120, 0.3)', | |
borderColor: 'rgba(120, 120, 120, 0.5)', | |
borderWidth: 1, | |
pointRadius: 3, | |
pointHoverRadius: 5 | |
}, { | |
label: 'Up-regulated', | |
data: data.filter(d => | |
d.log2FoldChange >= logFCCutoff && d.padj <= pValueCutoff | |
).map(d => ({ | |
x: d.log2FoldChange, | |
y: -Math.log10(d.padj), | |
symbol: d.symbol | |
})), | |
backgroundColor: 'rgba(220, 50, 50, 0.8)', | |
borderColor: 'rgba(180, 40, 40, 1)', | |
borderWidth: 1, | |
pointRadius: 4, | |
pointHoverRadius: 6 | |
}, { | |
label: 'Down-regulated', | |
data: data.filter(d => | |
d.log2FoldChange <= -logFCCutoff && d.padj <= pValueCutoff | |
).map(d => ({ | |
x: d.log2FoldChange, | |
y: -Math.log10(d.padj), | |
symbol: d.symbol | |
})), | |
backgroundColor: 'rgba(50, 120, 220, 0.8)', | |
borderColor: 'rgba(40, 90, 180, 1)', | |
borderWidth: 1, | |
pointRadius: 4, | |
pointHoverRadius: 6 | |
}] | |
}; | |
// Destroy previous chart if it exists | |
if (volcanoChart) { | |
volcanoChart.destroy(); | |
} | |
// Create new chart | |
volcanoChart = new Chart(ctx, { | |
type: 'scatter', | |
data: plotData, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false, | |
scales: { | |
x: { | |
title: { | |
display: true, | |
text: 'log2 Fold Change (log2FC)', | |
font: { | |
weight: 'bold' | |
} | |
}, | |
grid: { | |
color: 'rgba(0, 0, 0, 0.05)' | |
} | |
}, | |
y: { | |
title: { | |
display: true, | |
text: '-log10(adj. p-value)', | |
font: { | |
weight: 'bold' | |
} | |
}, | |
grid: { | |
color: 'rgba(0, 0, 0, 0.05)' | |
} | |
} | |
}, | |
plugins: { | |
tooltip: { | |
callbacks: { | |
title: function(context) { | |
return context[0].raw.symbol; | |
}, | |
label: function(context) { | |
return [ | |
`log2FC: ${context.parsed.x.toFixed(2)}`, | |
`adj. p-value: ${Math.pow(10, -context.parsed.y).toExponential(2)}` | |
]; | |
} | |
} | |
}, | |
legend: { | |
position: 'top', | |
} | |
}, | |
onClick: (e) => { | |
const points = volcanoChart.getElementsAtEventForMode( | |
e, 'nearest', { intersect: true }, true | |
); | |
if (points.length) { | |
const point = points[0]; | |
const symbol = volcanoChart.data.datasets[point.datasetIndex].data[point.index].symbol; | |
alert(`Selected gene: ${symbol}`); | |
} | |
} | |
} | |
}); | |
} | |
function generateMockEnrichmentResults() { | |
const databases = [ | |
'GO Biological Process', | |
'GO Molecular Function', | |
'GO Cellular Component', | |
'KEGG Pathways', | |
'Reactome Pathways' | |
]; | |
const selectedDB = document.getElementById('databaseSelect').value; | |
const results = []; | |
for (let i = 1; i <= 10; i++) { | |
results.push({ | |
term: `${selectedDB} term ${i}`, | |
pValue: (Math.random() * 0.04).toFixed(4), | |
genes: Math.floor(Math.random() * 50) + 5, | |
overlap: `${Math.floor(Math.random() * 15) + 1}/${Math.floor(Math.random() * 20) + 5}` | |
}); | |
} | |
// Sort by p-value | |
return results.sort((a, b) => parseFloat(a.pValue) - parseFloat(b.pValue)); | |
} | |
function displayEnrichmentResults(results) { | |
const pValueCutoff = parseFloat(document.getElementById('pValueEnrichCutoff').value); | |
const filteredResults = results.filter(r => parseFloat(r.pValue) <= pValueCutoff); | |
// Destroy previous chart if exists | |
if (enrichmentChart) { | |
enrichmentChart.destroy(); | |
} | |
if (filteredResults.length === 0) { | |
document.getElementById('enrichmentResults').innerHTML = ` | |
<div class="text-center text-gray-500 py-8"> | |
<svg class="mx-auto h-12 w-12 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
</svg> | |
<p class="mt-2">No significant enrichment results found</p> | |
<p class="text-sm">Try adjusting the p-value cutoff</p> | |
</div> | |
`; | |
return; | |
} | |
let html = ` | |
<div class="overflow-x-auto"> | |
<table class="min-w-full divide-y divide-gray-200"> | |
<thead class="bg-gray-100"> | |
<tr> | |
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Term</th> | |
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">p-value</th> | |
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Genes</th> | |
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Overlap</th> | |
</tr> | |
</thead> | |
<tbody class="bg-white divide-y divide-gray-200"> | |
`; | |
filteredResults.forEach(result => { | |
const pValueColor = parseFloat(result.pValue) < 0.01 ? 'text-red-600' : 'text-yellow-600'; | |
html += ` | |
<tr class="hover:bg-gray-50 cursor-pointer gene-item" onclick="alert('${result.term}')"> | |
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${result.term}</td> | |
<td class="px-6 py-4 whitespace-nowrap text-sm ${pValueColor}">${result.pValue}</td> | |
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${result.genes}</td> | |
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${result.overlap}</td> | |
</tr> | |
`; | |
}); | |
html += ` | |
</tbody> | |
</table> | |
</div> | |
<div class="mt-4 text-sm text-gray-500"> | |
Showing ${filteredResults.length} of ${results.length} terms (p-value ≤ ${pValueCutoff}) | |
</div> | |
`; | |
document.getElementById('enrichmentResults').innerHTML = html; | |
// Create enrichment chart if we have results | |
if (filteredResults.length > 0) { | |
const ctx = document.getElementById('enrichmentChart').getContext('2d'); | |
const labels = filteredResults.map(r => r.term); | |
const pValues = filteredResults.map(r => -Math.log10(parseFloat(r.pValue))); | |
const geneCounts = filteredResults.map(r => r.genes); | |
// Sort by p-value (most significant first) | |
const sortedIndices = [...Array(filteredResults.length).keys()] | |
.sort((a, b) => pValues[b] - pValues[a]); | |
enrichmentChart = new Chart(ctx, { | |
type: 'bar', | |
data: { | |
labels: sortedIndices.map(i => labels[i]), | |
datasets: [{ | |
label: '-log10(p-value)', | |
data: sortedIndices.map(i => pValues[i]), | |
backgroundColor: 'rgba(75, 192, 192, 0.6)', | |
borderColor: 'rgba(75, 192, 192, 1)', | |
borderWidth: 1, | |
yAxisID: 'y' | |
}, { | |
label: 'Gene Count', | |
data: sortedIndices.map(i => geneCounts[i]), | |
backgroundColor: 'rgba(153, 102, 255, 0.6)', | |
borderColor: 'rgba(153, 102, 255, 1)', | |
borderWidth: 1, | |
type: 'line', | |
yAxisID: 'y1' | |
}] | |
}, | |
options: { | |
responsive: true, | |
plugins: { | |
title: { | |
display: true, | |
text: 'Enrichment Results', | |
font: { | |
size: 16 | |
} | |
}, | |
tooltip: { | |
callbacks: { | |
label: function(context) { | |
let label = context.dataset.label || ''; | |
if (label) { | |
label += ': '; | |
} | |
if (context.datasetIndex === 0) { | |
label += context.raw.toFixed(2); | |
} else { | |
label += context.raw; | |
} | |
return label; | |
} | |
} | |
} | |
}, | |
scales: { | |
y: { | |
type: 'linear', | |
display: true, | |
position: 'left', | |
title: { | |
display: true, | |
text: '-log10(p-value)' | |
} | |
}, | |
y1: { | |
type: 'linear', | |
display: true, | |
position: 'right', | |
title: { | |
display: true, | |
text: 'Gene Count' | |
}, | |
grid: { | |
drawOnChartArea: false | |
} | |
} | |
} | |
} | |
}); | |
} | |
} | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Dobator/bioinformatics" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |