mutatex / index.html
gradsyntax's picture
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
raw
history blame
49.2 kB
<!DOCTYPE html>
<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>