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> |