Spaces:
Running
Running
Enhance the MutateX web app with: - A professional header area for GradSyntax - A clearly visible footer: "Developed by GradSyntax – www.gradsyntax.com" - Improved spacing, fonts, and color scheme for better readability Make it look like a production-ready tool suitable for research or healthcare use. - Initial Deployment
38d1c62
verified
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>MutateX - DNA Mutation Detection</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.25/jspdf.plugin.autotable.min.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
body { | |
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
} | |
.dna-bg { | |
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M50 10 L30 30 L50 50 L30 70 L50 90 M50 10 L70 30 L50 50 L70 70 L50 90" stroke="rgba(16, 185, 129, 0.08)" stroke-width="2" fill="none"/></svg>'); | |
background-size: 200px; | |
background-repeat: repeat; | |
} | |
h1, h2, h3 { | |
font-weight: 600; | |
color: #1f2937; | |
} | |
.file-upload { | |
border: 2px dashed rgba(74, 222, 128, 0.5); | |
transition: all 0.3s ease; | |
} | |
.file-upload:hover { | |
border-color: rgba(74, 222, 128, 0.8); | |
background-color: rgba(74, 222, 128, 0.05); | |
} | |
.file-upload.drag-over { | |
border-color: rgba(74, 222, 128, 1); | |
background-color: rgba(74, 222, 128, 0.1); | |
} | |
.mutation-highlight { | |
animation: pulse 2s infinite; | |
} | |
@keyframes pulse { | |
0% { background-color: rgba(239, 68, 68, 0.1); } | |
50% { background-color: rgba(239, 68, 68, 0.3); } | |
100% { background-color: rgba(239, 68, 68, 0.1); } | |
} | |
.sequence-viewer { | |
font-family: 'Courier New', monospace; | |
letter-spacing: 2px; | |
line-height: 1.8; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 dna-bg min-h-screen"> | |
<div class="container mx-auto px-4 py-8 max-w-7xl"> | |
<!-- Header --> | |
<header class="text-center mb-12"> | |
<div class="flex items-center justify-center mb-4"> | |
<i class="fas fa-dna text-4xl text-emerald-400 mr-3"></i> | |
<h1 class="text-4xl font-bold text-gray-800">MutateX</h1> | |
</div> | |
<p class="text-lg text-gray-600 max-w-2xl mx-auto"> | |
Advanced DNA mutation detection powered by AI models like DNABERT and HyenaDNA. | |
Upload your CSV file containing DNA sequences for comprehensive mutation analysis. | |
</p> | |
</header> | |
<!-- Main Content --> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
<!-- Upload Section --> | |
<div class="lg:col-span-1 bg-white rounded-xl shadow-md p-6 border border-gray-100"> | |
<h2 class="text-2xl font-semibold text-gray-800 mb-4 flex items-center"> | |
<i class="fas fa-file-upload text-emerald-500 mr-2"></i> Upload DNA Data | |
</h2> | |
<div id="dropZone" class="file-upload rounded-lg p-8 text-center cursor-pointer mb-6"> | |
<i class="fas fa-dna text-4xl text-emerald-400 mb-3"></i> | |
<p class="text-gray-600 mb-2">Drag & drop your CSV file here</p> | |
<p class="text-sm text-gray-500 mb-4">or</p> | |
<label for="fileInput" class="bg-emerald-500 hover:bg-emerald-600 text-white font-medium py-2 px-4 rounded-lg cursor-pointer transition"> | |
<i class="fas fa-folder-open mr-2"></i> Browse Files | |
</label> | |
<input id="fileInput" type="file" accept=".csv" class="hidden"> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 text-sm font-medium mb-2">Select AI Model</label> | |
<select id="modelSelect" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-emerald-500 focus:border-emerald-500"> | |
<option value="dnabert">DNABERT (Default)</option> | |
<option value="hyenadna">HyenaDNA</option> | |
<option value="ensemble">Ensemble (Both Models)</option> | |
</select> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 text-sm font-medium mb-2">Analysis Parameters</label> | |
<div class="space-y-2"> | |
<div class="flex items-center"> | |
<input id="checkSNP" type="checkbox" checked class="h-4 w-4 text-emerald-600 focus:ring-emerald-500 border-gray-300 rounded"> | |
<label for="checkSNP" class="ml-2 block text-sm text-gray-700">Detect SNPs</label> | |
</div> | |
<div class="flex items-center"> | |
<input id="checkIndels" type="checkbox" checked class="h-4 w-4 text-emerald-600 focus:ring-emerald-500 border-gray-300 rounded"> | |
<label for="checkIndels" class="ml-2 block text-sm text-gray-700">Detect Indels</label> | |
</div> | |
<div class="flex items-center"> | |
<input id="checkStructural" type="checkbox" class="h-4 w-4 text-emerald-600 focus:ring-emerald-500 border-gray-300 rounded"> | |
<label for="checkStructural" class="ml-2 block text-sm text-gray-700">Structural Variations</label> | |
</div> | |
</div> | |
</div> | |
<div class="flex space-x-4"> | |
<button id="analyzeBtn" class="flex-1 bg-emerald-600 hover:bg-emerald-700 text-white font-semibold py-3 px-4 rounded-lg transition flex items-center justify-center disabled:opacity-50 shadow-sm hover:shadow-md disabled:shadow-none" disabled> | |
<i class="fas fa-search mr-2"></i> Analyze Sequences | |
</button> | |
<button id="exampleBtn" class="flex-1 bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-4 rounded-lg transition flex items-center justify-center"> | |
<i class="fas fa-vial mr-2"></i> Example Data | |
</button> | |
</div> | |
<div class="mt-4 text-sm text-gray-500"> | |
<p><i class="fas fa-info-circle mr-1"></i> CSV Format: Sequence ID, DNA Sequence</p> | |
<p class="mt-1"><i class="fas fa-exclamation-triangle mr-1"></i> Max file size: 5MB</p> | |
</div> | |
</div> | |
<!-- Results Section --> | |
<div class="lg:col-span-2 space-y-6"> | |
<!-- Loading State --> | |
<div id="loadingState" class="hidden bg-white rounded-xl shadow-lg p-6 text-center"> | |
<div class="animate-pulse flex flex-col items-center"> | |
<i class="fas fa-dna text-4xl text-emerald-400 mb-4 animate-spin"></i> | |
<h3 class="text-xl font-medium text-gray-800 mb-2">Analyzing DNA Sequences</h3> | |
<p class="text-gray-600">This may take a few moments...</p> | |
<div class="w-full bg-gray-200 rounded-full h-2.5 mt-4"> | |
<div id="progressBar" class="bg-emerald-600 h-2.5 rounded-full" style="width: 0%"></div> | |
</div> | |
</div> | |
</div> | |
<!-- Results Overview --> | |
<div id="resultsOverview" class="hidden bg-white rounded-xl shadow-lg p-6"> | |
<h2 class="text-2xl font-semibold text-gray-800 mb-4 flex items-center"> | |
<i class="fas fa-chart-bar text-emerald-500 mr-2"></i> Analysis Results | |
</h2> | |
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> | |
<div class="bg-emerald-50 border border-emerald-100 rounded-lg p-4"> | |
<div class="flex items-center"> | |
<div class="p-2 rounded-full bg-emerald-100 text-emerald-600 mr-3"> | |
<i class="fas fa-dna"></i> | |
</div> | |
<div> | |
<p class="text-sm text-gray-500">Sequences Analyzed</p> | |
<p id="totalSequences" class="text-xl font-bold text-gray-800">0</p> | |
</div> | |
</div> | |
</div> | |
<div class="bg-red-50 border border-red-100 rounded-lg p-4"> | |
<div class="flex items-center"> | |
<div class="p-2 rounded-full bg-red-100 text-red-600 mr-3"> | |
<i class="fas fa-exclamation-triangle"></i> | |
</div> | |
<div> | |
<p class="text-sm text-gray-500">Mutations Found</p> | |
<p id="totalMutations" class="text-xl font-bold text-gray-800">0</p> | |
</div> | |
</div> | |
</div> | |
<div class="bg-blue-50 border border-blue-100 rounded-lg p-4"> | |
<div class="flex items-center"> | |
<div class="p-2 rounded-full bg-blue-100 text-blue-600 mr-3"> | |
<i class="fas fa-percentage"></i> | |
</div> | |
<div> | |
<p class="text-sm text-gray-500">Mutation Rate</p> | |
<p id="mutationRate" class="text-xl font-bold text-gray-800">0%</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="mb-6"> | |
<h3 class="text-lg font-medium text-gray-800 mb-3">Mutation Distribution</h3> | |
<div class="h-64"> | |
<canvas id="mutationChart"></canvas> | |
</div> | |
</div> | |
<div class="mb-6"> | |
<h3 class="text-lg font-medium text-gray-800 mb-3">Mutation Types</h3> | |
<div class="h-80"> | |
<div id="mutationTypeChart" style="width: 100%; height: 100%;"></div> | |
</div> | |
</div> | |
<div class="mt-6"> | |
<h3 class="text-lg font-medium text-gray-800 mb-3">Export Full Analysis</h3> | |
<div class="flex flex-col sm:flex-row space-y-3 sm:space-y-0 sm:space-x-4"> | |
<button id="downloadFullCsv" class="bg-emerald-500 hover:bg-emerald-600 text-white font-medium py-3 px-6 rounded-lg transition flex items-center justify-center"> | |
<i class="fas fa-file-csv mr-2"></i> Download Full Report (CSV) | |
</button> | |
<button id="downloadFullPdf" class="bg-red-500 hover:bg-red-600 text-white font-medium py-3 px-6 rounded-lg transition flex items-center justify-center"> | |
<i class="fas fa-file-pdf mr-2"></i> Download Full Report (PDF) | |
</button> | |
</div> | |
</div> | |
<div class="mt-6"> | |
<h3 class="text-lg font-medium text-gray-800 mb-3">Export Mutation Details</h3> | |
<div class="flex space-x-4"> | |
<button id="downloadCsv" class="bg-emerald-500 hover:bg-emerald-600 text-white font-medium py-2 px-4 rounded-lg transition flex items-center"> | |
<i class="fas fa-file-csv mr-2"></i> Download CSV | |
</button> | |
<button id="downloadPdf" class="bg-red-500 hover:bg-red-600 text-white font-medium py-2 px-4 rounded-lg transition flex items-center"> | |
<i class="fas fa-file-pdf mr-2"></i> Download PDF | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Detailed Results Table --> | |
<div id="resultsTable" class="hidden bg-white rounded-xl shadow-lg overflow-hidden"> | |
<div class="p-6 pb-0"> | |
<h2 class="text-2xl font-semibold text-gray-800 mb-4 flex items-center"> | |
<i class="fas fa-table text-emerald-500 mr-2"></i> Mutation Details | |
</h2> | |
</div> | |
<div class="overflow-x-auto"> | |
<table class="min-w-full divide-y divide-gray-200"> | |
<thead class="bg-gray-50"> | |
<tr> | |
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Sequence ID</th> | |
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Position</th> | |
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Reference</th> | |
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Mutation</th> | |
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th> | |
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Confidence</th> | |
</tr> | |
</thead> | |
<tbody id="resultsTableBody" class="bg-white divide-y divide-gray-200"> | |
<!-- Results will be populated here --> | |
</tbody> | |
</table> | |
</div> | |
<div class="px-6 py-4 bg-gray-50 border-t border-gray-200 flex items-center justify-between"> | |
<div class="text-sm text-gray-500"> | |
Showing <span id="startItem">1</span> to <span id="endItem">10</span> of <span id="totalItems">0</span> mutations | |
</div> | |
<div class="flex space-x-2"> | |
<button id="prevPage" class="px-3 py-1 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50" disabled> | |
Previous | |
</button> | |
<button id="nextPage" class="px-3 py-1 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50" disabled> | |
Next | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Sequence Viewer --> | |
<div id="sequenceViewer" class="hidden bg-white rounded-xl shadow-lg p-6"> | |
<h2 class="text-2xl font-semibold text-gray-800 mb-4 flex items-center"> | |
<i class="fas fa-eye text-emerald-500 mr-2"></i> Sequence Viewer | |
</h2> | |
<div class="mb-4"> | |
<label class="block text-gray-700 text-sm font-medium mb-2">Select Sequence</label> | |
<select id="sequenceSelect" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-emerald-500 focus:border-emerald-500"> | |
<option value="">Select a sequence to view</option> | |
</select> | |
</div> | |
<div class="bg-gray-50 p-4 rounded-lg overflow-x-auto"> | |
<div id="sequenceDisplay" class="sequence-viewer text-sm"> | |
<!-- Sequence will be displayed here --> | |
</div> | |
</div> | |
<div class="mt-4 flex flex-wrap gap-2"> | |
<div class="flex items-center"> | |
<div class="w-3 h-3 rounded-full bg-emerald-400 mr-1"></div> | |
<span class="text-xs">Normal</span> | |
</div> | |
<div class="flex items-center"> | |
<div class="w-3 h-3 rounded-full bg-red-400 mr-1"></div> | |
<span class="text-xs">SNP</span> | |
</div> | |
<div class="flex items-center"> | |
<div class="w-3 h-3 rounded-full bg-yellow-400 mr-1"></div> | |
<span class="text-xs">Insertion</span> | |
</div> | |
<div class="flex items-center"> | |
<div class="w-3 h-3 rounded-full bg-blue-400 mr-1"></div> | |
<span class="text-xs">Deletion</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Footer --> | |
<footer class="mt-16 bg-gray-100 py-6 border-t border-gray-200"> | |
<div class="container mx-auto px-4"> | |
<div class="flex flex-col md:flex-row justify-between items-center"> | |
<div class="mb-4 md:mb-0"> | |
<p class="text-gray-700 font-medium">MutateX - Professional DNA Analysis Tool</p> | |
<p class="text-gray-500 text-sm">Powered by DNABERT & HyenaDNA AI models</p> | |
</div> | |
<div class="text-center md:text-right"> | |
<p class="text-gray-600">Developed by <a href="https://www.gradsyntax.com" target="_blank" class="text-emerald-600 hover:text-emerald-800 font-medium">GradSyntax</a></p> | |
<p class="text-gray-400 text-xs mt-1">© 2023 GradSyntax. All rights reserved.</p> | |
</div> | |
</div> | |
</div> | |
</footer> | |
</div> | |
<script> | |
// DOM Elements | |
const dropZone = document.getElementById('dropZone'); | |
const fileInput = document.getElementById('fileInput'); | |
const analyzeBtn = document.getElementById('analyzeBtn'); | |
const loadingState = document.getElementById('loadingState'); | |
const resultsOverview = document.getElementById('resultsOverview'); | |
const resultsTable = document.getElementById('resultsTable'); | |
const resultsTableBody = document.getElementById('resultsTableBody'); | |
const sequenceViewer = document.getElementById('sequenceViewer'); | |
const sequenceSelect = document.getElementById('sequenceSelect'); | |
const sequenceDisplay = document.getElementById('sequenceDisplay'); | |
const progressBar = document.getElementById('progressBar'); | |
const totalSequences = document.getElementById('totalSequences'); | |
const totalMutations = document.getElementById('totalMutations'); | |
const mutationRate = document.getElementById('mutationRate'); | |
const prevPage = document.getElementById('prevPage'); | |
const nextPage = document.getElementById('nextPage'); | |
const startItem = document.getElementById('startItem'); | |
const endItem = document.getElementById('endItem'); | |
const totalItems = document.getElementById('totalItems'); | |
// App State | |
let uploadedFile = null; | |
let analysisResults = []; | |
let filteredResults = []; | |
let currentPage = 1; | |
const itemsPerPage = 10; | |
let sequences = {}; | |
let mutationStats = { | |
total: 0, | |
snps: 0, | |
insertions: 0, | |
deletions: 0, | |
others: 0 | |
}; | |
// Event Listeners | |
dropZone.addEventListener('click', () => fileInput.click()); | |
dropZone.addEventListener('dragover', (e) => { | |
e.preventDefault(); | |
dropZone.classList.add('drag-over'); | |
}); | |
dropZone.addEventListener('dragleave', () => { | |
dropZone.classList.remove('drag-over'); | |
}); | |
dropZone.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
dropZone.classList.remove('drag-over'); | |
if (e.dataTransfer.files.length) { | |
handleFileUpload(e.dataTransfer.files[0]); | |
} | |
}); | |
fileInput.addEventListener('change', (e) => { | |
if (e.target.files.length) { | |
handleFileUpload(e.target.files[0]); | |
} | |
}); | |
analyzeBtn.addEventListener('click', analyzeSequences); | |
document.getElementById('exampleBtn').addEventListener('click', loadExampleData); | |
sequenceSelect.addEventListener('change', displaySelectedSequence); | |
prevPage.addEventListener('click', () => updateTable(currentPage - 1)); | |
nextPage.addEventListener('click', () => updateTable(currentPage + 1)); | |
// Functions | |
function handleFileUpload(file) { | |
if (file.type !== 'text/csv' && !file.name.endsWith('.csv')) { | |
alert('Please upload a CSV file.'); | |
return; | |
} | |
if (file.size > 5 * 1024 * 1024) { | |
alert('File size exceeds 5MB limit.'); | |
return; | |
} | |
uploadedFile = file; | |
analyzeBtn.disabled = false; | |
// Update UI | |
dropZone.innerHTML = ` | |
<div class="flex flex-col items-center"> | |
<i class="fas fa-check-circle text-4xl text-emerald-400 mb-2"></i> | |
<p class="font-medium text-gray-800">${file.name}</p> | |
<p class="text-sm text-gray-500 mt-1">${(file.size / 1024).toFixed(1)} KB</p> | |
<button id="changeFile" class="mt-3 text-emerald-500 hover:text-emerald-600 text-sm"> | |
<i class="fas fa-sync-alt mr-1"></i> Change File | |
</button> | |
</div> | |
`; | |
document.getElementById('changeFile').addEventListener('click', () => { | |
fileInput.value = ''; | |
uploadedFile = null; | |
analyzeBtn.disabled = true; | |
dropZone.innerHTML = ` | |
<i class="fas fa-dna text-4xl text-emerald-400 mb-3"></i> | |
<p class="text-gray-600 mb-2">Drag & drop your CSV file here</p> | |
<p class="text-sm text-gray-500 mb-4">or</p> | |
<label for="fileInput" class="bg-emerald-500 hover:bg-emerald-600 text-white font-medium py-2 px-4 rounded-lg cursor-pointer transition"> | |
<i class="fas fa-folder-open mr-2"></i> Browse Files | |
</label> | |
`; | |
}); | |
} | |
function loadExampleData() { | |
// Simulate loading example data | |
dropZone.innerHTML = ` | |
<div class="flex flex-col items-center"> | |
<i class="fas fa-vial text-4xl text-blue-400 mb-2"></i> | |
<p class="font-medium text-gray-800">example_data.csv</p> | |
<p class="text-sm text-gray-500 mt-1">Example Data</p> | |
<button id="changeFile" class="mt-3 text-emerald-500 hover:text-emerald-600 text-sm"> | |
<i class="fas fa-sync-alt mr-1"></i> Change File | |
</button> | |
</div> | |
`; | |
document.getElementById('changeFile').addEventListener('click', () => { | |
fileInput.value = ''; | |
uploadedFile = null; | |
analyzeBtn.disabled = true; | |
dropZone.innerHTML = ` | |
<i class="fas fa-dna text-4xl text-emerald-400 mb-3"></i> | |
<p class="text-gray-600 mb-2">Drag & drop your CSV file here</p> | |
<p class="text-sm text-gray-500 mb-4">or</p> | |
<label for="fileInput" class="bg-emerald-500 hover:bg-emerald-600 text-white font-medium py-2 px-4 rounded-lg cursor-pointer transition"> | |
<i class="fas fa-folder-open mr-2"></i> Browse Files | |
</label> | |
`; | |
}); | |
// Enable analyze button | |
analyzeBtn.disabled = false; | |
// Set flag for example data | |
uploadedFile = { name: 'example_data.csv', size: 1024 }; | |
} | |
function analyzeSequences() { | |
if (!uploadedFile) return; | |
// Show loading state | |
loadingState.classList.remove('hidden'); | |
resultsOverview.classList.add('hidden'); | |
resultsTable.classList.add('hidden'); | |
sequenceViewer.classList.add('hidden'); | |
// Simulate analysis progress | |
let progress = 0; | |
const progressInterval = setInterval(() => { | |
progress += Math.random() * 10; | |
if (progress > 100) progress = 100; | |
progressBar.style.width = `${progress}%`; | |
if (progress === 100) { | |
clearInterval(progressInterval); | |
simulateAnalysisComplete(); | |
} | |
}, 300); | |
} | |
function simulateAnalysisComplete() { | |
// This would be replaced with actual API calls to DNABERT/HyenaDNA models | |
// For demo purposes, we'll generate mock data | |
// Generate mock sequences | |
sequences = { | |
'seq1': generateRandomSequence(150), | |
'seq2': generateRandomSequence(200), | |
'seq3': generateRandomSequence(180), | |
'seq4': generateRandomSequence(220), | |
'seq5': generateRandomSequence(190) | |
}; | |
// Generate mock mutations | |
analysisResults = []; | |
mutationStats = { | |
total: 0, | |
snps: 0, | |
insertions: 0, | |
deletions: 0, | |
others: 0 | |
}; | |
Object.keys(sequences).forEach(seqId => { | |
const seq = sequences[seqId]; | |
const seqLength = seq.length; | |
// Random number of mutations per sequence (0-5) | |
const mutationCount = Math.floor(Math.random() * 6); | |
for (let i = 0; i < mutationCount; i++) { | |
const position = Math.floor(Math.random() * seqLength) + 1; | |
const refBase = seq[position - 1]; | |
let mutationType, altBase, confidence; | |
// Random mutation type | |
const typeRand = Math.random(); | |
if (typeRand < 0.6) { | |
mutationType = 'SNP'; | |
mutationStats.snps++; | |
altBase = ['A', 'T', 'C', 'G'].filter(b => b !== refBase)[Math.floor(Math.random() * 3)]; | |
confidence = (0.8 + Math.random() * 0.2).toFixed(2); | |
} else if (typeRand < 0.85) { | |
mutationType = 'Insertion'; | |
mutationStats.insertions++; | |
altBase = refBase + ['A', 'T', 'C', 'G'][Math.floor(Math.random() * 4)]; | |
confidence = (0.7 + Math.random() * 0.25).toFixed(2); | |
} else if (typeRand < 0.95) { | |
mutationType = 'Deletion'; | |
mutationStats.deletions++; | |
altBase = '-'; | |
confidence = (0.6 + Math.random() * 0.3).toFixed(2); | |
} else { | |
mutationType = 'Complex'; | |
mutationStats.others++; | |
altBase = 'VAR'; | |
confidence = (0.5 + Math.random() * 0.4).toFixed(2); | |
} | |
analysisResults.push({ | |
seqId, | |
position, | |
refBase, | |
altBase, | |
type: mutationType, | |
confidence | |
}); | |
mutationStats.total++; | |
} | |
}); | |
// Update UI with results | |
loadingState.classList.add('hidden'); | |
displayResults(); | |
} | |
function generateRandomSequence(length) { | |
const bases = ['A', 'T', 'C', 'G']; | |
let sequence = ''; | |
for (let i = 0; i < length; i++) { | |
sequence += bases[Math.floor(Math.random() * 4)]; | |
} | |
return sequence; | |
} | |
function displayResults() { | |
// Update overview stats | |
totalSequences.textContent = Object.keys(sequences).length; | |
totalMutations.textContent = mutationStats.total; | |
mutationRate.textContent = ((mutationStats.total / Object.keys(sequences).length) * 100).toFixed(1) + '%'; | |
// Create charts | |
createMutationChart(); | |
createMutationTypeChart(); | |
// Populate sequence selector | |
sequenceSelect.innerHTML = '<option value="">Select a sequence to view</option>'; | |
Object.keys(sequences).forEach(seqId => { | |
const option = document.createElement('option'); | |
option.value = seqId; | |
option.textContent = seqId; | |
sequenceSelect.appendChild(option); | |
}); | |
// Filter results based on selected options | |
filterResults(); | |
// Show results sections | |
resultsOverview.classList.remove('hidden'); | |
resultsTable.classList.remove('hidden'); | |
sequenceViewer.classList.remove('hidden'); | |
// Initialize table with first page | |
updateTable(1); | |
} | |
function filterResults() { | |
const model = document.getElementById('modelSelect').value; | |
const checkSNP = document.getElementById('checkSNP').checked; | |
const checkIndels = document.getElementById('checkIndels').checked; | |
const checkStructural = document.getElementById('checkStructural').checked; | |
filteredResults = analysisResults.filter(mutation => { | |
// Filter by mutation type | |
if (mutation.type === 'SNP' && !checkSNP) return false; | |
if ((mutation.type === 'Insertion' || mutation.type === 'Deletion') && !checkIndels) return false; | |
if (mutation.type === 'Complex' && !checkStructural) return false; | |
// In a real app, we would also filter by model results here | |
return true; | |
}); | |
totalItems.textContent = filteredResults.length; | |
} | |
function updateTable(page) { | |
currentPage = page; | |
const startIdx = (page - 1) * itemsPerPage; | |
const endIdx = Math.min(startIdx + itemsPerPage, filteredResults.length); | |
resultsTableBody.innerHTML = ''; | |
for (let i = startIdx; i < endIdx; i++) { | |
const mutation = filteredResults[i]; | |
const row = document.createElement('tr'); | |
// Highlight row based on mutation type | |
let rowClass = ''; | |
if (mutation.type === 'SNP') rowClass = 'bg-red-50'; | |
else if (mutation.type === 'Insertion') rowClass = 'bg-yellow-50'; | |
else if (mutation.type === 'Deletion') rowClass = 'bg-blue-50'; | |
else rowClass = 'bg-purple-50'; | |
row.className = rowClass; | |
row.innerHTML = ` | |
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${mutation.seqId}</td> | |
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${mutation.position}</td> | |
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${mutation.refBase}</td> | |
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium ${mutation.type === 'SNP' ? 'text-red-600' : mutation.type === 'Insertion' ? 'text-yellow-600' : mutation.type === 'Deletion' ? 'text-blue-600' : 'text-purple-600'}">${mutation.altBase}</td> | |
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full | |
${mutation.type === 'SNP' ? 'bg-red-100 text-red-800' : | |
mutation.type === 'Insertion' ? 'bg-yellow-100 text-yellow-800' : | |
mutation.type === 'Deletion' ? 'bg-blue-100 text-blue-800' : 'bg-purple-100 text-purple-800'}"> | |
${mutation.type} | |
</span> | |
</td> | |
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
<div class="w-full bg-gray-200 rounded-full h-2.5"> | |
<div class="bg-emerald-600 h-2.5 rounded-full" style="width: ${mutation.confidence * 100}%"></div> | |
</div> | |
<span class="text-xs">${mutation.confidence}</span> | |
</td> | |
`; | |
resultsTableBody.appendChild(row); | |
} | |
// Update pagination info | |
startItem.textContent = startIdx + 1; | |
endItem.textContent = endIdx; | |
// Update pagination buttons | |
prevPage.disabled = page === 1; | |
nextPage.disabled = endIdx >= filteredResults.length; | |
} | |
function displaySelectedSequence() { | |
const seqId = sequenceSelect.value; | |
if (!seqId) { | |
sequenceDisplay.innerHTML = ''; | |
return; | |
} | |
const sequence = sequences[seqId]; | |
const mutations = analysisResults.filter(m => m.seqId === seqId); | |
let html = ''; | |
let position = 1; | |
let lineCount = 0; | |
// Split sequence into chunks of 50 bases for better readability | |
for (let i = 0; i < sequence.length; i += 50) { | |
const chunk = sequence.slice(i, i + 50); | |
let chunkHtml = ''; | |
for (let j = 0; j < chunk.length; j++) { | |
const currentPos = i + j + 1; | |
const mutation = mutations.find(m => m.position === currentPos); | |
if (mutation) { | |
let baseClass = ''; | |
if (mutation.type === 'SNP') baseClass = 'text-red-600 font-bold'; | |
else if (mutation.type === 'Insertion') baseClass = 'text-yellow-600 font-bold'; | |
else if (mutation.type === 'Deletion') baseClass = 'text-blue-600 font-bold'; | |
else baseClass = 'text-purple-600 font-bold'; | |
chunkHtml += `<span class="${baseClass} mutation-highlight" title="${mutation.type} at position ${currentPos}: ${mutation.refBase}→${mutation.altBase} (Confidence: ${mutation.confidence})">${chunk[j]}</span>`; | |
} else { | |
chunkHtml += `<span class="text-emerald-800">${chunk[j]}</span>`; | |
} | |
// Add space every 10 bases | |
if ((j + 1) % 10 === 0 && j !== chunk.length - 1) { | |
chunkHtml += ' '; | |
} | |
} | |
// Add position numbers | |
const startPos = i + 1; | |
const endPos = Math.min(i + 50, sequence.length); | |
html += ` | |
<div class="mb-1"> | |
<span class="text-gray-400 text-xs mr-4">${startPos.toString().padStart(5, ' ')}</span> | |
${chunkHtml} | |
<span class="text-gray-400 text-xs ml-4">${endPos}</span> | |
</div> | |
`; | |
lineCount++; | |
if (lineCount % 5 === 0) { | |
html += '<div class="my-2 border-t border-gray-200"></div>'; | |
} | |
} | |
sequenceDisplay.innerHTML = html; | |
} | |
function createMutationChart() { | |
const ctx = document.getElementById('mutationChart').getContext('2d'); | |
// Group mutations by sequence | |
const mutationCounts = {}; | |
Object.keys(sequences).forEach(seqId => { | |
mutationCounts[seqId] = analysisResults.filter(m => m.seqId === seqId).length; | |
}); | |
new Chart(ctx, { | |
type: 'bar', | |
data: { | |
labels: Object.keys(mutationCounts), | |
datasets: [{ | |
label: 'Mutations per Sequence', | |
data: Object.values(mutationCounts), | |
backgroundColor: 'rgba(74, 222, 128, 0.7)', | |
borderColor: 'rgba(74, 222, 128, 1)', | |
borderWidth: 1 | |
}] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false, | |
scales: { | |
y: { | |
beginAtZero: true, | |
title: { | |
display: true, | |
text: 'Number of Mutations' | |
} | |
}, | |
x: { | |
title: { | |
display: true, | |
text: 'Sequence ID' | |
} | |
} | |
} | |
} | |
}); | |
} | |
function createMutationTypeChart() { | |
const data = [{ | |
values: [mutationStats.snps, mutationStats.insertions, mutationStats.deletions, mutationStats.others], | |
labels: ['SNPs', 'Insertions', 'Deletions', 'Others'], | |
marker: { | |
colors: ['#ef4444', '#f59e0b', '#3b82f6', '#8b5cf6'] | |
}, | |
type: 'pie', | |
textinfo: 'label+percent', | |
insidetextorientation: 'radial', | |
hoverinfo: 'label+value+percent', | |
hole: 0.4 | |
}]; | |
const layout = { | |
margin: { t: 0, b: 0, l: 0, r: 0 }, | |
showlegend: false | |
}; | |
Plotly.newPlot('mutationTypeChart', data, layout); | |
} | |
// Download functionality | |
document.getElementById('downloadCsv').addEventListener('click', downloadCsv); | |
document.getElementById('downloadPdf').addEventListener('click', downloadPdf); | |
function downloadCsv() { | |
if (filteredResults.length === 0) { | |
alert('No results to download'); | |
return; | |
} | |
// Create CSV header | |
let csvContent = "Sequence ID,Position,Reference,Mutation,Type,Confidence\n"; | |
// Add each mutation result | |
filteredResults.forEach(mutation => { | |
csvContent += `${mutation.seqId},${mutation.position},${mutation.refBase},${mutation.altBase},${mutation.type},${mutation.confidence}\n`; | |
}); | |
// Create download link | |
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); | |
const url = URL.createObjectURL(blob); | |
const link = document.createElement('a'); | |
link.setAttribute('href', url); | |
link.setAttribute('download', `mutatex_results_${new Date().toISOString().slice(0,10)}.csv`); | |
link.style.visibility = 'hidden'; | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); | |
} | |
function downloadPdf() { | |
if (filteredResults.length === 0) { | |
alert('No results to download'); | |
return; | |
} | |
const { jsPDF } = window.jspdf; | |
const doc = new jsPDF(); | |
// Add title | |
doc.setFontSize(18); | |
doc.text('MutateX - Mutation Results', 14, 20); | |
doc.setFontSize(12); | |
doc.text(`Generated on: ${new Date().toLocaleString()}`, 14, 30); | |
// Prepare data for table | |
const tableData = filteredResults.map(mutation => [ | |
mutation.seqId, | |
mutation.position, | |
mutation.refBase, | |
mutation.altBase, | |
mutation.type, | |
mutation.confidence | |
]); | |
// Add table | |
doc.autoTable({ | |
head: [['Sequence ID', 'Position', 'Reference', 'Mutation', 'Type', 'Confidence']], | |
body: tableData, | |
startY: 40, | |
styles: { | |
cellPadding: 5, | |
fontSize: 10, | |
valign: 'middle' | |
}, | |
columnStyles: { | |
0: { cellWidth: 'auto' }, | |
1: { cellWidth: 'auto' }, | |
2: { cellWidth: 'auto' }, | |
3: { cellWidth: 'auto' }, | |
4: { cellWidth: 'auto' }, | |
5: { cellWidth: 'auto' } | |
} | |
}); | |
// Save the PDF | |
doc.save(`mutatex_results_${new Date().toISOString().slice(0,10)}.pdf`); | |
} | |
// Full report download functionality | |
document.getElementById('downloadFullCsv').addEventListener('click', downloadFullReportCsv); | |
document.getElementById('downloadFullPdf').addEventListener('click', downloadFullReportPdf); | |
function downloadFullReportCsv() { | |
if (analysisResults.length === 0) { | |
alert('No analysis results to download'); | |
return; | |
} | |
// Create CSV header | |
let csvContent = "MutateX Full Analysis Report\n\n"; | |
csvContent += "Overview Statistics\n"; | |
csvContent += `Sequences Analyzed,${Object.keys(sequences).length}\n`; | |
csvContent += `Total Mutations Found,${mutationStats.total}\n`; | |
csvContent += `Mutation Rate,${((mutationStats.total / Object.keys(sequences).length) * 100).toFixed(1)}%\n\n`; | |
csvContent += "Mutation Type Distribution\n"; | |
csvContent += `SNPs,${mutationStats.snps}\n`; | |
csvContent += `Insertions,${mutationStats.insertions}\n`; | |
csvContent += `Deletions,${mutationStats.deletions}\n`; | |
csvContent += `Other Mutations,${mutationStats.others}\n\n`; | |
csvContent += "Detailed Mutation Results\n"; | |
csvContent += "Sequence ID,Position,Reference,Mutation,Type,Confidence\n"; | |
// Add each mutation result | |
analysisResults.forEach(mutation => { | |
csvContent += `${mutation.seqId},${mutation.position},${mutation.refBase},${mutation.altBase},${mutation.type},${mutation.confidence}\n`; | |
}); | |
// Create download link | |
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); | |
const url = URL.createObjectURL(blob); | |
const link = document.createElement('a'); | |
link.setAttribute('href', url); | |
link.setAttribute('download', `mutatex_full_report_${new Date().toISOString().slice(0,10)}.csv`); | |
link.style.visibility = 'hidden'; | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); | |
} | |
function downloadFullReportPdf() { | |
if (analysisResults.length === 0) { | |
alert('No analysis results to download'); | |
return; | |
} | |
const { jsPDF } = window.jspdf; | |
const doc = new jsPDF(); | |
// Add title and overview | |
doc.setFontSize(18); | |
doc.text('MutateX - Full Analysis Report', 14, 20); | |
doc.setFontSize(12); | |
doc.text(`Generated on: ${new Date().toLocaleString()}`, 14, 30); | |
// Add overview statistics | |
doc.setFontSize(14); | |
doc.text('Overview Statistics', 14, 50); | |
doc.setFontSize(12); | |
doc.text(`Sequences Analyzed: ${Object.keys(sequences).length}`, 14, 60); | |
doc.text(`Total Mutations Found: ${mutationStats.total}`, 14, 70); | |
doc.text(`Mutation Rate: ${((mutationStats.total / Object.keys(sequences).length) * 100).toFixed(1)}%`, 14, 80); | |
// Add mutation type distribution | |
doc.setFontSize(14); | |
doc.text('Mutation Type Distribution', 14, 100); | |
doc.setFontSize(12); | |
doc.text(`SNPs: ${mutationStats.snps}`, 14, 110); | |
doc.text(`Insertions: ${mutationStats.insertions}`, 14, 120); | |
doc.text(`Deletions: ${mutationStats.deletions}`, 14, 130); | |
doc.text(`Other Mutations: ${mutationStats.others}`, 14, 140); | |
// Add detailed results table | |
const tableData = analysisResults.map(mutation => [ | |
mutation.seqId, | |
mutation.position, | |
mutation.refBase, | |
mutation.altBase, | |
mutation.type, | |
mutation.confidence | |
]); | |
doc.autoTable({ | |
head: [['Sequence ID', 'Position', 'Reference', 'Mutation', 'Type', 'Confidence']], | |
body: tableData, | |
startY: 160, | |
styles: { | |
cellPadding: 5, | |
fontSize: 8, | |
valign: 'middle' | |
}, | |
columnStyles: { | |
0: { cellWidth: 'auto' }, | |
1: { cellWidth: 'auto' }, | |
2: { cellWidth: 'auto' }, | |
3: { cellWidth: 'auto' }, | |
4: { cellWidth: 'auto' }, | |
5: { cellWidth: 'auto' } | |
} | |
}); | |
// Save the PDF | |
doc.save(`mutatex_full_report_${new Date().toISOString().slice(0,10)}.pdf`); | |
} | |
// Initialize with empty results | |
filterResults(); | |
updateTable(1); | |
</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=gradsyntax/mutatex" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |