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
Browse files- README.md +7 -5
- index.html +996 -19
README.md
CHANGED
@@ -1,10 +1,12 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: static
|
7 |
pinned: false
|
|
|
|
|
8 |
---
|
9 |
|
10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
+
title: mutatex
|
3 |
+
emoji: 🐳
|
4 |
+
colorFrom: red
|
5 |
+
colorTo: blue
|
6 |
sdk: static
|
7 |
pinned: false
|
8 |
+
tags:
|
9 |
+
- deepsite
|
10 |
---
|
11 |
|
12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
index.html
CHANGED
@@ -1,19 +1,996 @@
|
|
1 |
-
<!
|
2 |
-
<html>
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>MutateX - DNA Mutation Detection</title>
|
7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
8 |
+
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
9 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
10 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
11 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.25/jspdf.plugin.autotable.min.js"></script>
|
12 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
13 |
+
<style>
|
14 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
15 |
+
|
16 |
+
body {
|
17 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
18 |
+
}
|
19 |
+
|
20 |
+
.dna-bg {
|
21 |
+
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>');
|
22 |
+
background-size: 200px;
|
23 |
+
background-repeat: repeat;
|
24 |
+
}
|
25 |
+
|
26 |
+
h1, h2, h3 {
|
27 |
+
font-weight: 600;
|
28 |
+
color: #1f2937;
|
29 |
+
}
|
30 |
+
.file-upload {
|
31 |
+
border: 2px dashed rgba(74, 222, 128, 0.5);
|
32 |
+
transition: all 0.3s ease;
|
33 |
+
}
|
34 |
+
.file-upload:hover {
|
35 |
+
border-color: rgba(74, 222, 128, 0.8);
|
36 |
+
background-color: rgba(74, 222, 128, 0.05);
|
37 |
+
}
|
38 |
+
.file-upload.drag-over {
|
39 |
+
border-color: rgba(74, 222, 128, 1);
|
40 |
+
background-color: rgba(74, 222, 128, 0.1);
|
41 |
+
}
|
42 |
+
.mutation-highlight {
|
43 |
+
animation: pulse 2s infinite;
|
44 |
+
}
|
45 |
+
@keyframes pulse {
|
46 |
+
0% { background-color: rgba(239, 68, 68, 0.1); }
|
47 |
+
50% { background-color: rgba(239, 68, 68, 0.3); }
|
48 |
+
100% { background-color: rgba(239, 68, 68, 0.1); }
|
49 |
+
}
|
50 |
+
.sequence-viewer {
|
51 |
+
font-family: 'Courier New', monospace;
|
52 |
+
letter-spacing: 2px;
|
53 |
+
line-height: 1.8;
|
54 |
+
}
|
55 |
+
</style>
|
56 |
+
</head>
|
57 |
+
<body class="bg-gray-50 dna-bg min-h-screen">
|
58 |
+
<div class="container mx-auto px-4 py-8 max-w-7xl">
|
59 |
+
<!-- Header -->
|
60 |
+
<header class="text-center mb-12">
|
61 |
+
<div class="flex items-center justify-center mb-4">
|
62 |
+
<i class="fas fa-dna text-4xl text-emerald-400 mr-3"></i>
|
63 |
+
<h1 class="text-4xl font-bold text-gray-800">MutateX</h1>
|
64 |
+
</div>
|
65 |
+
<p class="text-lg text-gray-600 max-w-2xl mx-auto">
|
66 |
+
Advanced DNA mutation detection powered by AI models like DNABERT and HyenaDNA.
|
67 |
+
Upload your CSV file containing DNA sequences for comprehensive mutation analysis.
|
68 |
+
</p>
|
69 |
+
</header>
|
70 |
+
|
71 |
+
<!-- Main Content -->
|
72 |
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
73 |
+
<!-- Upload Section -->
|
74 |
+
<div class="lg:col-span-1 bg-white rounded-xl shadow-md p-6 border border-gray-100">
|
75 |
+
<h2 class="text-2xl font-semibold text-gray-800 mb-4 flex items-center">
|
76 |
+
<i class="fas fa-file-upload text-emerald-500 mr-2"></i> Upload DNA Data
|
77 |
+
</h2>
|
78 |
+
|
79 |
+
<div id="dropZone" class="file-upload rounded-lg p-8 text-center cursor-pointer mb-6">
|
80 |
+
<i class="fas fa-dna text-4xl text-emerald-400 mb-3"></i>
|
81 |
+
<p class="text-gray-600 mb-2">Drag & drop your CSV file here</p>
|
82 |
+
<p class="text-sm text-gray-500 mb-4">or</p>
|
83 |
+
<label for="fileInput" class="bg-emerald-500 hover:bg-emerald-600 text-white font-medium py-2 px-4 rounded-lg cursor-pointer transition">
|
84 |
+
<i class="fas fa-folder-open mr-2"></i> Browse Files
|
85 |
+
</label>
|
86 |
+
<input id="fileInput" type="file" accept=".csv" class="hidden">
|
87 |
+
</div>
|
88 |
+
|
89 |
+
<div class="mb-4">
|
90 |
+
<label class="block text-gray-700 text-sm font-medium mb-2">Select AI Model</label>
|
91 |
+
<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">
|
92 |
+
<option value="dnabert">DNABERT (Default)</option>
|
93 |
+
<option value="hyenadna">HyenaDNA</option>
|
94 |
+
<option value="ensemble">Ensemble (Both Models)</option>
|
95 |
+
</select>
|
96 |
+
</div>
|
97 |
+
|
98 |
+
<div class="mb-4">
|
99 |
+
<label class="block text-gray-700 text-sm font-medium mb-2">Analysis Parameters</label>
|
100 |
+
<div class="space-y-2">
|
101 |
+
<div class="flex items-center">
|
102 |
+
<input id="checkSNP" type="checkbox" checked class="h-4 w-4 text-emerald-600 focus:ring-emerald-500 border-gray-300 rounded">
|
103 |
+
<label for="checkSNP" class="ml-2 block text-sm text-gray-700">Detect SNPs</label>
|
104 |
+
</div>
|
105 |
+
<div class="flex items-center">
|
106 |
+
<input id="checkIndels" type="checkbox" checked class="h-4 w-4 text-emerald-600 focus:ring-emerald-500 border-gray-300 rounded">
|
107 |
+
<label for="checkIndels" class="ml-2 block text-sm text-gray-700">Detect Indels</label>
|
108 |
+
</div>
|
109 |
+
<div class="flex items-center">
|
110 |
+
<input id="checkStructural" type="checkbox" class="h-4 w-4 text-emerald-600 focus:ring-emerald-500 border-gray-300 rounded">
|
111 |
+
<label for="checkStructural" class="ml-2 block text-sm text-gray-700">Structural Variations</label>
|
112 |
+
</div>
|
113 |
+
</div>
|
114 |
+
</div>
|
115 |
+
|
116 |
+
<div class="flex space-x-4">
|
117 |
+
<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>
|
118 |
+
<i class="fas fa-search mr-2"></i> Analyze Sequences
|
119 |
+
</button>
|
120 |
+
<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">
|
121 |
+
<i class="fas fa-vial mr-2"></i> Example Data
|
122 |
+
</button>
|
123 |
+
</div>
|
124 |
+
|
125 |
+
<div class="mt-4 text-sm text-gray-500">
|
126 |
+
<p><i class="fas fa-info-circle mr-1"></i> CSV Format: Sequence ID, DNA Sequence</p>
|
127 |
+
<p class="mt-1"><i class="fas fa-exclamation-triangle mr-1"></i> Max file size: 5MB</p>
|
128 |
+
</div>
|
129 |
+
</div>
|
130 |
+
|
131 |
+
<!-- Results Section -->
|
132 |
+
<div class="lg:col-span-2 space-y-6">
|
133 |
+
<!-- Loading State -->
|
134 |
+
<div id="loadingState" class="hidden bg-white rounded-xl shadow-lg p-6 text-center">
|
135 |
+
<div class="animate-pulse flex flex-col items-center">
|
136 |
+
<i class="fas fa-dna text-4xl text-emerald-400 mb-4 animate-spin"></i>
|
137 |
+
<h3 class="text-xl font-medium text-gray-800 mb-2">Analyzing DNA Sequences</h3>
|
138 |
+
<p class="text-gray-600">This may take a few moments...</p>
|
139 |
+
<div class="w-full bg-gray-200 rounded-full h-2.5 mt-4">
|
140 |
+
<div id="progressBar" class="bg-emerald-600 h-2.5 rounded-full" style="width: 0%"></div>
|
141 |
+
</div>
|
142 |
+
</div>
|
143 |
+
</div>
|
144 |
+
|
145 |
+
<!-- Results Overview -->
|
146 |
+
<div id="resultsOverview" class="hidden bg-white rounded-xl shadow-lg p-6">
|
147 |
+
<h2 class="text-2xl font-semibold text-gray-800 mb-4 flex items-center">
|
148 |
+
<i class="fas fa-chart-bar text-emerald-500 mr-2"></i> Analysis Results
|
149 |
+
</h2>
|
150 |
+
|
151 |
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
152 |
+
<div class="bg-emerald-50 border border-emerald-100 rounded-lg p-4">
|
153 |
+
<div class="flex items-center">
|
154 |
+
<div class="p-2 rounded-full bg-emerald-100 text-emerald-600 mr-3">
|
155 |
+
<i class="fas fa-dna"></i>
|
156 |
+
</div>
|
157 |
+
<div>
|
158 |
+
<p class="text-sm text-gray-500">Sequences Analyzed</p>
|
159 |
+
<p id="totalSequences" class="text-xl font-bold text-gray-800">0</p>
|
160 |
+
</div>
|
161 |
+
</div>
|
162 |
+
</div>
|
163 |
+
<div class="bg-red-50 border border-red-100 rounded-lg p-4">
|
164 |
+
<div class="flex items-center">
|
165 |
+
<div class="p-2 rounded-full bg-red-100 text-red-600 mr-3">
|
166 |
+
<i class="fas fa-exclamation-triangle"></i>
|
167 |
+
</div>
|
168 |
+
<div>
|
169 |
+
<p class="text-sm text-gray-500">Mutations Found</p>
|
170 |
+
<p id="totalMutations" class="text-xl font-bold text-gray-800">0</p>
|
171 |
+
</div>
|
172 |
+
</div>
|
173 |
+
</div>
|
174 |
+
<div class="bg-blue-50 border border-blue-100 rounded-lg p-4">
|
175 |
+
<div class="flex items-center">
|
176 |
+
<div class="p-2 rounded-full bg-blue-100 text-blue-600 mr-3">
|
177 |
+
<i class="fas fa-percentage"></i>
|
178 |
+
</div>
|
179 |
+
<div>
|
180 |
+
<p class="text-sm text-gray-500">Mutation Rate</p>
|
181 |
+
<p id="mutationRate" class="text-xl font-bold text-gray-800">0%</p>
|
182 |
+
</div>
|
183 |
+
</div>
|
184 |
+
</div>
|
185 |
+
</div>
|
186 |
+
|
187 |
+
<div class="mb-6">
|
188 |
+
<h3 class="text-lg font-medium text-gray-800 mb-3">Mutation Distribution</h3>
|
189 |
+
<div class="h-64">
|
190 |
+
<canvas id="mutationChart"></canvas>
|
191 |
+
</div>
|
192 |
+
</div>
|
193 |
+
|
194 |
+
<div class="mb-6">
|
195 |
+
<h3 class="text-lg font-medium text-gray-800 mb-3">Mutation Types</h3>
|
196 |
+
<div class="h-80">
|
197 |
+
<div id="mutationTypeChart" style="width: 100%; height: 100%;"></div>
|
198 |
+
</div>
|
199 |
+
</div>
|
200 |
+
|
201 |
+
<div class="mt-6">
|
202 |
+
<h3 class="text-lg font-medium text-gray-800 mb-3">Export Full Analysis</h3>
|
203 |
+
<div class="flex flex-col sm:flex-row space-y-3 sm:space-y-0 sm:space-x-4">
|
204 |
+
<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">
|
205 |
+
<i class="fas fa-file-csv mr-2"></i> Download Full Report (CSV)
|
206 |
+
</button>
|
207 |
+
<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">
|
208 |
+
<i class="fas fa-file-pdf mr-2"></i> Download Full Report (PDF)
|
209 |
+
</button>
|
210 |
+
</div>
|
211 |
+
</div>
|
212 |
+
|
213 |
+
<div class="mt-6">
|
214 |
+
<h3 class="text-lg font-medium text-gray-800 mb-3">Export Mutation Details</h3>
|
215 |
+
<div class="flex space-x-4">
|
216 |
+
<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">
|
217 |
+
<i class="fas fa-file-csv mr-2"></i> Download CSV
|
218 |
+
</button>
|
219 |
+
<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">
|
220 |
+
<i class="fas fa-file-pdf mr-2"></i> Download PDF
|
221 |
+
</button>
|
222 |
+
</div>
|
223 |
+
</div>
|
224 |
+
</div>
|
225 |
+
|
226 |
+
<!-- Detailed Results Table -->
|
227 |
+
<div id="resultsTable" class="hidden bg-white rounded-xl shadow-lg overflow-hidden">
|
228 |
+
<div class="p-6 pb-0">
|
229 |
+
<h2 class="text-2xl font-semibold text-gray-800 mb-4 flex items-center">
|
230 |
+
<i class="fas fa-table text-emerald-500 mr-2"></i> Mutation Details
|
231 |
+
</h2>
|
232 |
+
</div>
|
233 |
+
|
234 |
+
<div class="overflow-x-auto">
|
235 |
+
<table class="min-w-full divide-y divide-gray-200">
|
236 |
+
<thead class="bg-gray-50">
|
237 |
+
<tr>
|
238 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Sequence ID</th>
|
239 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Position</th>
|
240 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Reference</th>
|
241 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Mutation</th>
|
242 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
|
243 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Confidence</th>
|
244 |
+
</tr>
|
245 |
+
</thead>
|
246 |
+
<tbody id="resultsTableBody" class="bg-white divide-y divide-gray-200">
|
247 |
+
<!-- Results will be populated here -->
|
248 |
+
</tbody>
|
249 |
+
</table>
|
250 |
+
</div>
|
251 |
+
|
252 |
+
<div class="px-6 py-4 bg-gray-50 border-t border-gray-200 flex items-center justify-between">
|
253 |
+
<div class="text-sm text-gray-500">
|
254 |
+
Showing <span id="startItem">1</span> to <span id="endItem">10</span> of <span id="totalItems">0</span> mutations
|
255 |
+
</div>
|
256 |
+
<div class="flex space-x-2">
|
257 |
+
<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>
|
258 |
+
Previous
|
259 |
+
</button>
|
260 |
+
<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>
|
261 |
+
Next
|
262 |
+
</button>
|
263 |
+
</div>
|
264 |
+
</div>
|
265 |
+
</div>
|
266 |
+
|
267 |
+
<!-- Sequence Viewer -->
|
268 |
+
<div id="sequenceViewer" class="hidden bg-white rounded-xl shadow-lg p-6">
|
269 |
+
<h2 class="text-2xl font-semibold text-gray-800 mb-4 flex items-center">
|
270 |
+
<i class="fas fa-eye text-emerald-500 mr-2"></i> Sequence Viewer
|
271 |
+
</h2>
|
272 |
+
|
273 |
+
<div class="mb-4">
|
274 |
+
<label class="block text-gray-700 text-sm font-medium mb-2">Select Sequence</label>
|
275 |
+
<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">
|
276 |
+
<option value="">Select a sequence to view</option>
|
277 |
+
</select>
|
278 |
+
</div>
|
279 |
+
|
280 |
+
<div class="bg-gray-50 p-4 rounded-lg overflow-x-auto">
|
281 |
+
<div id="sequenceDisplay" class="sequence-viewer text-sm">
|
282 |
+
<!-- Sequence will be displayed here -->
|
283 |
+
</div>
|
284 |
+
</div>
|
285 |
+
|
286 |
+
<div class="mt-4 flex flex-wrap gap-2">
|
287 |
+
<div class="flex items-center">
|
288 |
+
<div class="w-3 h-3 rounded-full bg-emerald-400 mr-1"></div>
|
289 |
+
<span class="text-xs">Normal</span>
|
290 |
+
</div>
|
291 |
+
<div class="flex items-center">
|
292 |
+
<div class="w-3 h-3 rounded-full bg-red-400 mr-1"></div>
|
293 |
+
<span class="text-xs">SNP</span>
|
294 |
+
</div>
|
295 |
+
<div class="flex items-center">
|
296 |
+
<div class="w-3 h-3 rounded-full bg-yellow-400 mr-1"></div>
|
297 |
+
<span class="text-xs">Insertion</span>
|
298 |
+
</div>
|
299 |
+
<div class="flex items-center">
|
300 |
+
<div class="w-3 h-3 rounded-full bg-blue-400 mr-1"></div>
|
301 |
+
<span class="text-xs">Deletion</span>
|
302 |
+
</div>
|
303 |
+
</div>
|
304 |
+
</div>
|
305 |
+
</div>
|
306 |
+
</div>
|
307 |
+
|
308 |
+
<!-- Footer -->
|
309 |
+
<footer class="mt-16 bg-gray-100 py-6 border-t border-gray-200">
|
310 |
+
<div class="container mx-auto px-4">
|
311 |
+
<div class="flex flex-col md:flex-row justify-between items-center">
|
312 |
+
<div class="mb-4 md:mb-0">
|
313 |
+
<p class="text-gray-700 font-medium">MutateX - Professional DNA Analysis Tool</p>
|
314 |
+
<p class="text-gray-500 text-sm">Powered by DNABERT & HyenaDNA AI models</p>
|
315 |
+
</div>
|
316 |
+
<div class="text-center md:text-right">
|
317 |
+
<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>
|
318 |
+
<p class="text-gray-400 text-xs mt-1">© 2023 GradSyntax. All rights reserved.</p>
|
319 |
+
</div>
|
320 |
+
</div>
|
321 |
+
</div>
|
322 |
+
</footer>
|
323 |
+
</div>
|
324 |
+
|
325 |
+
<script>
|
326 |
+
// DOM Elements
|
327 |
+
const dropZone = document.getElementById('dropZone');
|
328 |
+
const fileInput = document.getElementById('fileInput');
|
329 |
+
const analyzeBtn = document.getElementById('analyzeBtn');
|
330 |
+
const loadingState = document.getElementById('loadingState');
|
331 |
+
const resultsOverview = document.getElementById('resultsOverview');
|
332 |
+
const resultsTable = document.getElementById('resultsTable');
|
333 |
+
const resultsTableBody = document.getElementById('resultsTableBody');
|
334 |
+
const sequenceViewer = document.getElementById('sequenceViewer');
|
335 |
+
const sequenceSelect = document.getElementById('sequenceSelect');
|
336 |
+
const sequenceDisplay = document.getElementById('sequenceDisplay');
|
337 |
+
const progressBar = document.getElementById('progressBar');
|
338 |
+
const totalSequences = document.getElementById('totalSequences');
|
339 |
+
const totalMutations = document.getElementById('totalMutations');
|
340 |
+
const mutationRate = document.getElementById('mutationRate');
|
341 |
+
const prevPage = document.getElementById('prevPage');
|
342 |
+
const nextPage = document.getElementById('nextPage');
|
343 |
+
const startItem = document.getElementById('startItem');
|
344 |
+
const endItem = document.getElementById('endItem');
|
345 |
+
const totalItems = document.getElementById('totalItems');
|
346 |
+
|
347 |
+
// App State
|
348 |
+
let uploadedFile = null;
|
349 |
+
let analysisResults = [];
|
350 |
+
let filteredResults = [];
|
351 |
+
let currentPage = 1;
|
352 |
+
const itemsPerPage = 10;
|
353 |
+
let sequences = {};
|
354 |
+
let mutationStats = {
|
355 |
+
total: 0,
|
356 |
+
snps: 0,
|
357 |
+
insertions: 0,
|
358 |
+
deletions: 0,
|
359 |
+
others: 0
|
360 |
+
};
|
361 |
+
|
362 |
+
// Event Listeners
|
363 |
+
dropZone.addEventListener('click', () => fileInput.click());
|
364 |
+
dropZone.addEventListener('dragover', (e) => {
|
365 |
+
e.preventDefault();
|
366 |
+
dropZone.classList.add('drag-over');
|
367 |
+
});
|
368 |
+
dropZone.addEventListener('dragleave', () => {
|
369 |
+
dropZone.classList.remove('drag-over');
|
370 |
+
});
|
371 |
+
dropZone.addEventListener('drop', (e) => {
|
372 |
+
e.preventDefault();
|
373 |
+
dropZone.classList.remove('drag-over');
|
374 |
+
if (e.dataTransfer.files.length) {
|
375 |
+
handleFileUpload(e.dataTransfer.files[0]);
|
376 |
+
}
|
377 |
+
});
|
378 |
+
|
379 |
+
fileInput.addEventListener('change', (e) => {
|
380 |
+
if (e.target.files.length) {
|
381 |
+
handleFileUpload(e.target.files[0]);
|
382 |
+
}
|
383 |
+
});
|
384 |
+
|
385 |
+
analyzeBtn.addEventListener('click', analyzeSequences);
|
386 |
+
document.getElementById('exampleBtn').addEventListener('click', loadExampleData);
|
387 |
+
sequenceSelect.addEventListener('change', displaySelectedSequence);
|
388 |
+
prevPage.addEventListener('click', () => updateTable(currentPage - 1));
|
389 |
+
nextPage.addEventListener('click', () => updateTable(currentPage + 1));
|
390 |
+
|
391 |
+
// Functions
|
392 |
+
function handleFileUpload(file) {
|
393 |
+
if (file.type !== 'text/csv' && !file.name.endsWith('.csv')) {
|
394 |
+
alert('Please upload a CSV file.');
|
395 |
+
return;
|
396 |
+
}
|
397 |
+
|
398 |
+
if (file.size > 5 * 1024 * 1024) {
|
399 |
+
alert('File size exceeds 5MB limit.');
|
400 |
+
return;
|
401 |
+
}
|
402 |
+
|
403 |
+
uploadedFile = file;
|
404 |
+
analyzeBtn.disabled = false;
|
405 |
+
|
406 |
+
// Update UI
|
407 |
+
dropZone.innerHTML = `
|
408 |
+
<div class="flex flex-col items-center">
|
409 |
+
<i class="fas fa-check-circle text-4xl text-emerald-400 mb-2"></i>
|
410 |
+
<p class="font-medium text-gray-800">${file.name}</p>
|
411 |
+
<p class="text-sm text-gray-500 mt-1">${(file.size / 1024).toFixed(1)} KB</p>
|
412 |
+
<button id="changeFile" class="mt-3 text-emerald-500 hover:text-emerald-600 text-sm">
|
413 |
+
<i class="fas fa-sync-alt mr-1"></i> Change File
|
414 |
+
</button>
|
415 |
+
</div>
|
416 |
+
`;
|
417 |
+
|
418 |
+
document.getElementById('changeFile').addEventListener('click', () => {
|
419 |
+
fileInput.value = '';
|
420 |
+
uploadedFile = null;
|
421 |
+
analyzeBtn.disabled = true;
|
422 |
+
dropZone.innerHTML = `
|
423 |
+
<i class="fas fa-dna text-4xl text-emerald-400 mb-3"></i>
|
424 |
+
<p class="text-gray-600 mb-2">Drag & drop your CSV file here</p>
|
425 |
+
<p class="text-sm text-gray-500 mb-4">or</p>
|
426 |
+
<label for="fileInput" class="bg-emerald-500 hover:bg-emerald-600 text-white font-medium py-2 px-4 rounded-lg cursor-pointer transition">
|
427 |
+
<i class="fas fa-folder-open mr-2"></i> Browse Files
|
428 |
+
</label>
|
429 |
+
`;
|
430 |
+
});
|
431 |
+
}
|
432 |
+
|
433 |
+
function loadExampleData() {
|
434 |
+
// Simulate loading example data
|
435 |
+
dropZone.innerHTML = `
|
436 |
+
<div class="flex flex-col items-center">
|
437 |
+
<i class="fas fa-vial text-4xl text-blue-400 mb-2"></i>
|
438 |
+
<p class="font-medium text-gray-800">example_data.csv</p>
|
439 |
+
<p class="text-sm text-gray-500 mt-1">Example Data</p>
|
440 |
+
<button id="changeFile" class="mt-3 text-emerald-500 hover:text-emerald-600 text-sm">
|
441 |
+
<i class="fas fa-sync-alt mr-1"></i> Change File
|
442 |
+
</button>
|
443 |
+
</div>
|
444 |
+
`;
|
445 |
+
|
446 |
+
document.getElementById('changeFile').addEventListener('click', () => {
|
447 |
+
fileInput.value = '';
|
448 |
+
uploadedFile = null;
|
449 |
+
analyzeBtn.disabled = true;
|
450 |
+
dropZone.innerHTML = `
|
451 |
+
<i class="fas fa-dna text-4xl text-emerald-400 mb-3"></i>
|
452 |
+
<p class="text-gray-600 mb-2">Drag & drop your CSV file here</p>
|
453 |
+
<p class="text-sm text-gray-500 mb-4">or</p>
|
454 |
+
<label for="fileInput" class="bg-emerald-500 hover:bg-emerald-600 text-white font-medium py-2 px-4 rounded-lg cursor-pointer transition">
|
455 |
+
<i class="fas fa-folder-open mr-2"></i> Browse Files
|
456 |
+
</label>
|
457 |
+
`;
|
458 |
+
});
|
459 |
+
|
460 |
+
// Enable analyze button
|
461 |
+
analyzeBtn.disabled = false;
|
462 |
+
|
463 |
+
// Set flag for example data
|
464 |
+
uploadedFile = { name: 'example_data.csv', size: 1024 };
|
465 |
+
}
|
466 |
+
|
467 |
+
function analyzeSequences() {
|
468 |
+
if (!uploadedFile) return;
|
469 |
+
|
470 |
+
// Show loading state
|
471 |
+
loadingState.classList.remove('hidden');
|
472 |
+
resultsOverview.classList.add('hidden');
|
473 |
+
resultsTable.classList.add('hidden');
|
474 |
+
sequenceViewer.classList.add('hidden');
|
475 |
+
|
476 |
+
// Simulate analysis progress
|
477 |
+
let progress = 0;
|
478 |
+
const progressInterval = setInterval(() => {
|
479 |
+
progress += Math.random() * 10;
|
480 |
+
if (progress > 100) progress = 100;
|
481 |
+
progressBar.style.width = `${progress}%`;
|
482 |
+
|
483 |
+
if (progress === 100) {
|
484 |
+
clearInterval(progressInterval);
|
485 |
+
simulateAnalysisComplete();
|
486 |
+
}
|
487 |
+
}, 300);
|
488 |
+
}
|
489 |
+
|
490 |
+
function simulateAnalysisComplete() {
|
491 |
+
// This would be replaced with actual API calls to DNABERT/HyenaDNA models
|
492 |
+
// For demo purposes, we'll generate mock data
|
493 |
+
|
494 |
+
// Generate mock sequences
|
495 |
+
sequences = {
|
496 |
+
'seq1': generateRandomSequence(150),
|
497 |
+
'seq2': generateRandomSequence(200),
|
498 |
+
'seq3': generateRandomSequence(180),
|
499 |
+
'seq4': generateRandomSequence(220),
|
500 |
+
'seq5': generateRandomSequence(190)
|
501 |
+
};
|
502 |
+
|
503 |
+
// Generate mock mutations
|
504 |
+
analysisResults = [];
|
505 |
+
mutationStats = {
|
506 |
+
total: 0,
|
507 |
+
snps: 0,
|
508 |
+
insertions: 0,
|
509 |
+
deletions: 0,
|
510 |
+
others: 0
|
511 |
+
};
|
512 |
+
|
513 |
+
Object.keys(sequences).forEach(seqId => {
|
514 |
+
const seq = sequences[seqId];
|
515 |
+
const seqLength = seq.length;
|
516 |
+
|
517 |
+
// Random number of mutations per sequence (0-5)
|
518 |
+
const mutationCount = Math.floor(Math.random() * 6);
|
519 |
+
|
520 |
+
for (let i = 0; i < mutationCount; i++) {
|
521 |
+
const position = Math.floor(Math.random() * seqLength) + 1;
|
522 |
+
const refBase = seq[position - 1];
|
523 |
+
let mutationType, altBase, confidence;
|
524 |
+
|
525 |
+
// Random mutation type
|
526 |
+
const typeRand = Math.random();
|
527 |
+
if (typeRand < 0.6) {
|
528 |
+
mutationType = 'SNP';
|
529 |
+
mutationStats.snps++;
|
530 |
+
altBase = ['A', 'T', 'C', 'G'].filter(b => b !== refBase)[Math.floor(Math.random() * 3)];
|
531 |
+
confidence = (0.8 + Math.random() * 0.2).toFixed(2);
|
532 |
+
} else if (typeRand < 0.85) {
|
533 |
+
mutationType = 'Insertion';
|
534 |
+
mutationStats.insertions++;
|
535 |
+
altBase = refBase + ['A', 'T', 'C', 'G'][Math.floor(Math.random() * 4)];
|
536 |
+
confidence = (0.7 + Math.random() * 0.25).toFixed(2);
|
537 |
+
} else if (typeRand < 0.95) {
|
538 |
+
mutationType = 'Deletion';
|
539 |
+
mutationStats.deletions++;
|
540 |
+
altBase = '-';
|
541 |
+
confidence = (0.6 + Math.random() * 0.3).toFixed(2);
|
542 |
+
} else {
|
543 |
+
mutationType = 'Complex';
|
544 |
+
mutationStats.others++;
|
545 |
+
altBase = 'VAR';
|
546 |
+
confidence = (0.5 + Math.random() * 0.4).toFixed(2);
|
547 |
+
}
|
548 |
+
|
549 |
+
analysisResults.push({
|
550 |
+
seqId,
|
551 |
+
position,
|
552 |
+
refBase,
|
553 |
+
altBase,
|
554 |
+
type: mutationType,
|
555 |
+
confidence
|
556 |
+
});
|
557 |
+
mutationStats.total++;
|
558 |
+
}
|
559 |
+
});
|
560 |
+
|
561 |
+
// Update UI with results
|
562 |
+
loadingState.classList.add('hidden');
|
563 |
+
displayResults();
|
564 |
+
}
|
565 |
+
|
566 |
+
function generateRandomSequence(length) {
|
567 |
+
const bases = ['A', 'T', 'C', 'G'];
|
568 |
+
let sequence = '';
|
569 |
+
for (let i = 0; i < length; i++) {
|
570 |
+
sequence += bases[Math.floor(Math.random() * 4)];
|
571 |
+
}
|
572 |
+
return sequence;
|
573 |
+
}
|
574 |
+
|
575 |
+
function displayResults() {
|
576 |
+
// Update overview stats
|
577 |
+
totalSequences.textContent = Object.keys(sequences).length;
|
578 |
+
totalMutations.textContent = mutationStats.total;
|
579 |
+
mutationRate.textContent = ((mutationStats.total / Object.keys(sequences).length) * 100).toFixed(1) + '%';
|
580 |
+
|
581 |
+
// Create charts
|
582 |
+
createMutationChart();
|
583 |
+
createMutationTypeChart();
|
584 |
+
|
585 |
+
// Populate sequence selector
|
586 |
+
sequenceSelect.innerHTML = '<option value="">Select a sequence to view</option>';
|
587 |
+
Object.keys(sequences).forEach(seqId => {
|
588 |
+
const option = document.createElement('option');
|
589 |
+
option.value = seqId;
|
590 |
+
option.textContent = seqId;
|
591 |
+
sequenceSelect.appendChild(option);
|
592 |
+
});
|
593 |
+
|
594 |
+
// Filter results based on selected options
|
595 |
+
filterResults();
|
596 |
+
|
597 |
+
// Show results sections
|
598 |
+
resultsOverview.classList.remove('hidden');
|
599 |
+
resultsTable.classList.remove('hidden');
|
600 |
+
sequenceViewer.classList.remove('hidden');
|
601 |
+
|
602 |
+
// Initialize table with first page
|
603 |
+
updateTable(1);
|
604 |
+
}
|
605 |
+
|
606 |
+
function filterResults() {
|
607 |
+
const model = document.getElementById('modelSelect').value;
|
608 |
+
const checkSNP = document.getElementById('checkSNP').checked;
|
609 |
+
const checkIndels = document.getElementById('checkIndels').checked;
|
610 |
+
const checkStructural = document.getElementById('checkStructural').checked;
|
611 |
+
|
612 |
+
filteredResults = analysisResults.filter(mutation => {
|
613 |
+
// Filter by mutation type
|
614 |
+
if (mutation.type === 'SNP' && !checkSNP) return false;
|
615 |
+
if ((mutation.type === 'Insertion' || mutation.type === 'Deletion') && !checkIndels) return false;
|
616 |
+
if (mutation.type === 'Complex' && !checkStructural) return false;
|
617 |
+
|
618 |
+
// In a real app, we would also filter by model results here
|
619 |
+
return true;
|
620 |
+
});
|
621 |
+
|
622 |
+
totalItems.textContent = filteredResults.length;
|
623 |
+
}
|
624 |
+
|
625 |
+
function updateTable(page) {
|
626 |
+
currentPage = page;
|
627 |
+
const startIdx = (page - 1) * itemsPerPage;
|
628 |
+
const endIdx = Math.min(startIdx + itemsPerPage, filteredResults.length);
|
629 |
+
|
630 |
+
resultsTableBody.innerHTML = '';
|
631 |
+
|
632 |
+
for (let i = startIdx; i < endIdx; i++) {
|
633 |
+
const mutation = filteredResults[i];
|
634 |
+
const row = document.createElement('tr');
|
635 |
+
|
636 |
+
// Highlight row based on mutation type
|
637 |
+
let rowClass = '';
|
638 |
+
if (mutation.type === 'SNP') rowClass = 'bg-red-50';
|
639 |
+
else if (mutation.type === 'Insertion') rowClass = 'bg-yellow-50';
|
640 |
+
else if (mutation.type === 'Deletion') rowClass = 'bg-blue-50';
|
641 |
+
else rowClass = 'bg-purple-50';
|
642 |
+
|
643 |
+
row.className = rowClass;
|
644 |
+
row.innerHTML = `
|
645 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${mutation.seqId}</td>
|
646 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${mutation.position}</td>
|
647 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${mutation.refBase}</td>
|
648 |
+
<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>
|
649 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
650 |
+
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
651 |
+
${mutation.type === 'SNP' ? 'bg-red-100 text-red-800' :
|
652 |
+
mutation.type === 'Insertion' ? 'bg-yellow-100 text-yellow-800' :
|
653 |
+
mutation.type === 'Deletion' ? 'bg-blue-100 text-blue-800' : 'bg-purple-100 text-purple-800'}">
|
654 |
+
${mutation.type}
|
655 |
+
</span>
|
656 |
+
</td>
|
657 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
658 |
+
<div class="w-full bg-gray-200 rounded-full h-2.5">
|
659 |
+
<div class="bg-emerald-600 h-2.5 rounded-full" style="width: ${mutation.confidence * 100}%"></div>
|
660 |
+
</div>
|
661 |
+
<span class="text-xs">${mutation.confidence}</span>
|
662 |
+
</td>
|
663 |
+
`;
|
664 |
+
|
665 |
+
resultsTableBody.appendChild(row);
|
666 |
+
}
|
667 |
+
|
668 |
+
// Update pagination info
|
669 |
+
startItem.textContent = startIdx + 1;
|
670 |
+
endItem.textContent = endIdx;
|
671 |
+
|
672 |
+
// Update pagination buttons
|
673 |
+
prevPage.disabled = page === 1;
|
674 |
+
nextPage.disabled = endIdx >= filteredResults.length;
|
675 |
+
}
|
676 |
+
|
677 |
+
function displaySelectedSequence() {
|
678 |
+
const seqId = sequenceSelect.value;
|
679 |
+
if (!seqId) {
|
680 |
+
sequenceDisplay.innerHTML = '';
|
681 |
+
return;
|
682 |
+
}
|
683 |
+
|
684 |
+
const sequence = sequences[seqId];
|
685 |
+
const mutations = analysisResults.filter(m => m.seqId === seqId);
|
686 |
+
|
687 |
+
let html = '';
|
688 |
+
let position = 1;
|
689 |
+
let lineCount = 0;
|
690 |
+
|
691 |
+
// Split sequence into chunks of 50 bases for better readability
|
692 |
+
for (let i = 0; i < sequence.length; i += 50) {
|
693 |
+
const chunk = sequence.slice(i, i + 50);
|
694 |
+
let chunkHtml = '';
|
695 |
+
|
696 |
+
for (let j = 0; j < chunk.length; j++) {
|
697 |
+
const currentPos = i + j + 1;
|
698 |
+
const mutation = mutations.find(m => m.position === currentPos);
|
699 |
+
|
700 |
+
if (mutation) {
|
701 |
+
let baseClass = '';
|
702 |
+
if (mutation.type === 'SNP') baseClass = 'text-red-600 font-bold';
|
703 |
+
else if (mutation.type === 'Insertion') baseClass = 'text-yellow-600 font-bold';
|
704 |
+
else if (mutation.type === 'Deletion') baseClass = 'text-blue-600 font-bold';
|
705 |
+
else baseClass = 'text-purple-600 font-bold';
|
706 |
+
|
707 |
+
chunkHtml += `<span class="${baseClass} mutation-highlight" title="${mutation.type} at position ${currentPos}: ${mutation.refBase}→${mutation.altBase} (Confidence: ${mutation.confidence})">${chunk[j]}</span>`;
|
708 |
+
} else {
|
709 |
+
chunkHtml += `<span class="text-emerald-800">${chunk[j]}</span>`;
|
710 |
+
}
|
711 |
+
|
712 |
+
// Add space every 10 bases
|
713 |
+
if ((j + 1) % 10 === 0 && j !== chunk.length - 1) {
|
714 |
+
chunkHtml += ' ';
|
715 |
+
}
|
716 |
+
}
|
717 |
+
|
718 |
+
// Add position numbers
|
719 |
+
const startPos = i + 1;
|
720 |
+
const endPos = Math.min(i + 50, sequence.length);
|
721 |
+
|
722 |
+
html += `
|
723 |
+
<div class="mb-1">
|
724 |
+
<span class="text-gray-400 text-xs mr-4">${startPos.toString().padStart(5, ' ')}</span>
|
725 |
+
${chunkHtml}
|
726 |
+
<span class="text-gray-400 text-xs ml-4">${endPos}</span>
|
727 |
+
</div>
|
728 |
+
`;
|
729 |
+
|
730 |
+
lineCount++;
|
731 |
+
if (lineCount % 5 === 0) {
|
732 |
+
html += '<div class="my-2 border-t border-gray-200"></div>';
|
733 |
+
}
|
734 |
+
}
|
735 |
+
|
736 |
+
sequenceDisplay.innerHTML = html;
|
737 |
+
}
|
738 |
+
|
739 |
+
function createMutationChart() {
|
740 |
+
const ctx = document.getElementById('mutationChart').getContext('2d');
|
741 |
+
|
742 |
+
// Group mutations by sequence
|
743 |
+
const mutationCounts = {};
|
744 |
+
Object.keys(sequences).forEach(seqId => {
|
745 |
+
mutationCounts[seqId] = analysisResults.filter(m => m.seqId === seqId).length;
|
746 |
+
});
|
747 |
+
|
748 |
+
new Chart(ctx, {
|
749 |
+
type: 'bar',
|
750 |
+
data: {
|
751 |
+
labels: Object.keys(mutationCounts),
|
752 |
+
datasets: [{
|
753 |
+
label: 'Mutations per Sequence',
|
754 |
+
data: Object.values(mutationCounts),
|
755 |
+
backgroundColor: 'rgba(74, 222, 128, 0.7)',
|
756 |
+
borderColor: 'rgba(74, 222, 128, 1)',
|
757 |
+
borderWidth: 1
|
758 |
+
}]
|
759 |
+
},
|
760 |
+
options: {
|
761 |
+
responsive: true,
|
762 |
+
maintainAspectRatio: false,
|
763 |
+
scales: {
|
764 |
+
y: {
|
765 |
+
beginAtZero: true,
|
766 |
+
title: {
|
767 |
+
display: true,
|
768 |
+
text: 'Number of Mutations'
|
769 |
+
}
|
770 |
+
},
|
771 |
+
x: {
|
772 |
+
title: {
|
773 |
+
display: true,
|
774 |
+
text: 'Sequence ID'
|
775 |
+
}
|
776 |
+
}
|
777 |
+
}
|
778 |
+
}
|
779 |
+
});
|
780 |
+
}
|
781 |
+
|
782 |
+
function createMutationTypeChart() {
|
783 |
+
const data = [{
|
784 |
+
values: [mutationStats.snps, mutationStats.insertions, mutationStats.deletions, mutationStats.others],
|
785 |
+
labels: ['SNPs', 'Insertions', 'Deletions', 'Others'],
|
786 |
+
marker: {
|
787 |
+
colors: ['#ef4444', '#f59e0b', '#3b82f6', '#8b5cf6']
|
788 |
+
},
|
789 |
+
type: 'pie',
|
790 |
+
textinfo: 'label+percent',
|
791 |
+
insidetextorientation: 'radial',
|
792 |
+
hoverinfo: 'label+value+percent',
|
793 |
+
hole: 0.4
|
794 |
+
}];
|
795 |
+
|
796 |
+
const layout = {
|
797 |
+
margin: { t: 0, b: 0, l: 0, r: 0 },
|
798 |
+
showlegend: false
|
799 |
+
};
|
800 |
+
|
801 |
+
Plotly.newPlot('mutationTypeChart', data, layout);
|
802 |
+
}
|
803 |
+
|
804 |
+
// Download functionality
|
805 |
+
document.getElementById('downloadCsv').addEventListener('click', downloadCsv);
|
806 |
+
document.getElementById('downloadPdf').addEventListener('click', downloadPdf);
|
807 |
+
|
808 |
+
function downloadCsv() {
|
809 |
+
if (filteredResults.length === 0) {
|
810 |
+
alert('No results to download');
|
811 |
+
return;
|
812 |
+
}
|
813 |
+
|
814 |
+
// Create CSV header
|
815 |
+
let csvContent = "Sequence ID,Position,Reference,Mutation,Type,Confidence\n";
|
816 |
+
|
817 |
+
// Add each mutation result
|
818 |
+
filteredResults.forEach(mutation => {
|
819 |
+
csvContent += `${mutation.seqId},${mutation.position},${mutation.refBase},${mutation.altBase},${mutation.type},${mutation.confidence}\n`;
|
820 |
+
});
|
821 |
+
|
822 |
+
// Create download link
|
823 |
+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
824 |
+
const url = URL.createObjectURL(blob);
|
825 |
+
const link = document.createElement('a');
|
826 |
+
link.setAttribute('href', url);
|
827 |
+
link.setAttribute('download', `mutatex_results_${new Date().toISOString().slice(0,10)}.csv`);
|
828 |
+
link.style.visibility = 'hidden';
|
829 |
+
document.body.appendChild(link);
|
830 |
+
link.click();
|
831 |
+
document.body.removeChild(link);
|
832 |
+
}
|
833 |
+
|
834 |
+
function downloadPdf() {
|
835 |
+
if (filteredResults.length === 0) {
|
836 |
+
alert('No results to download');
|
837 |
+
return;
|
838 |
+
}
|
839 |
+
|
840 |
+
const { jsPDF } = window.jspdf;
|
841 |
+
const doc = new jsPDF();
|
842 |
+
|
843 |
+
// Add title
|
844 |
+
doc.setFontSize(18);
|
845 |
+
doc.text('MutateX - Mutation Results', 14, 20);
|
846 |
+
doc.setFontSize(12);
|
847 |
+
doc.text(`Generated on: ${new Date().toLocaleString()}`, 14, 30);
|
848 |
+
|
849 |
+
// Prepare data for table
|
850 |
+
const tableData = filteredResults.map(mutation => [
|
851 |
+
mutation.seqId,
|
852 |
+
mutation.position,
|
853 |
+
mutation.refBase,
|
854 |
+
mutation.altBase,
|
855 |
+
mutation.type,
|
856 |
+
mutation.confidence
|
857 |
+
]);
|
858 |
+
|
859 |
+
// Add table
|
860 |
+
doc.autoTable({
|
861 |
+
head: [['Sequence ID', 'Position', 'Reference', 'Mutation', 'Type', 'Confidence']],
|
862 |
+
body: tableData,
|
863 |
+
startY: 40,
|
864 |
+
styles: {
|
865 |
+
cellPadding: 5,
|
866 |
+
fontSize: 10,
|
867 |
+
valign: 'middle'
|
868 |
+
},
|
869 |
+
columnStyles: {
|
870 |
+
0: { cellWidth: 'auto' },
|
871 |
+
1: { cellWidth: 'auto' },
|
872 |
+
2: { cellWidth: 'auto' },
|
873 |
+
3: { cellWidth: 'auto' },
|
874 |
+
4: { cellWidth: 'auto' },
|
875 |
+
5: { cellWidth: 'auto' }
|
876 |
+
}
|
877 |
+
});
|
878 |
+
|
879 |
+
// Save the PDF
|
880 |
+
doc.save(`mutatex_results_${new Date().toISOString().slice(0,10)}.pdf`);
|
881 |
+
}
|
882 |
+
|
883 |
+
// Full report download functionality
|
884 |
+
document.getElementById('downloadFullCsv').addEventListener('click', downloadFullReportCsv);
|
885 |
+
document.getElementById('downloadFullPdf').addEventListener('click', downloadFullReportPdf);
|
886 |
+
|
887 |
+
function downloadFullReportCsv() {
|
888 |
+
if (analysisResults.length === 0) {
|
889 |
+
alert('No analysis results to download');
|
890 |
+
return;
|
891 |
+
}
|
892 |
+
|
893 |
+
// Create CSV header
|
894 |
+
let csvContent = "MutateX Full Analysis Report\n\n";
|
895 |
+
csvContent += "Overview Statistics\n";
|
896 |
+
csvContent += `Sequences Analyzed,${Object.keys(sequences).length}\n`;
|
897 |
+
csvContent += `Total Mutations Found,${mutationStats.total}\n`;
|
898 |
+
csvContent += `Mutation Rate,${((mutationStats.total / Object.keys(sequences).length) * 100).toFixed(1)}%\n\n`;
|
899 |
+
|
900 |
+
csvContent += "Mutation Type Distribution\n";
|
901 |
+
csvContent += `SNPs,${mutationStats.snps}\n`;
|
902 |
+
csvContent += `Insertions,${mutationStats.insertions}\n`;
|
903 |
+
csvContent += `Deletions,${mutationStats.deletions}\n`;
|
904 |
+
csvContent += `Other Mutations,${mutationStats.others}\n\n`;
|
905 |
+
|
906 |
+
csvContent += "Detailed Mutation Results\n";
|
907 |
+
csvContent += "Sequence ID,Position,Reference,Mutation,Type,Confidence\n";
|
908 |
+
|
909 |
+
// Add each mutation result
|
910 |
+
analysisResults.forEach(mutation => {
|
911 |
+
csvContent += `${mutation.seqId},${mutation.position},${mutation.refBase},${mutation.altBase},${mutation.type},${mutation.confidence}\n`;
|
912 |
+
});
|
913 |
+
|
914 |
+
// Create download link
|
915 |
+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
916 |
+
const url = URL.createObjectURL(blob);
|
917 |
+
const link = document.createElement('a');
|
918 |
+
link.setAttribute('href', url);
|
919 |
+
link.setAttribute('download', `mutatex_full_report_${new Date().toISOString().slice(0,10)}.csv`);
|
920 |
+
link.style.visibility = 'hidden';
|
921 |
+
document.body.appendChild(link);
|
922 |
+
link.click();
|
923 |
+
document.body.removeChild(link);
|
924 |
+
}
|
925 |
+
|
926 |
+
function downloadFullReportPdf() {
|
927 |
+
if (analysisResults.length === 0) {
|
928 |
+
alert('No analysis results to download');
|
929 |
+
return;
|
930 |
+
}
|
931 |
+
|
932 |
+
const { jsPDF } = window.jspdf;
|
933 |
+
const doc = new jsPDF();
|
934 |
+
|
935 |
+
// Add title and overview
|
936 |
+
doc.setFontSize(18);
|
937 |
+
doc.text('MutateX - Full Analysis Report', 14, 20);
|
938 |
+
doc.setFontSize(12);
|
939 |
+
doc.text(`Generated on: ${new Date().toLocaleString()}`, 14, 30);
|
940 |
+
|
941 |
+
// Add overview statistics
|
942 |
+
doc.setFontSize(14);
|
943 |
+
doc.text('Overview Statistics', 14, 50);
|
944 |
+
doc.setFontSize(12);
|
945 |
+
doc.text(`Sequences Analyzed: ${Object.keys(sequences).length}`, 14, 60);
|
946 |
+
doc.text(`Total Mutations Found: ${mutationStats.total}`, 14, 70);
|
947 |
+
doc.text(`Mutation Rate: ${((mutationStats.total / Object.keys(sequences).length) * 100).toFixed(1)}%`, 14, 80);
|
948 |
+
|
949 |
+
// Add mutation type distribution
|
950 |
+
doc.setFontSize(14);
|
951 |
+
doc.text('Mutation Type Distribution', 14, 100);
|
952 |
+
doc.setFontSize(12);
|
953 |
+
doc.text(`SNPs: ${mutationStats.snps}`, 14, 110);
|
954 |
+
doc.text(`Insertions: ${mutationStats.insertions}`, 14, 120);
|
955 |
+
doc.text(`Deletions: ${mutationStats.deletions}`, 14, 130);
|
956 |
+
doc.text(`Other Mutations: ${mutationStats.others}`, 14, 140);
|
957 |
+
|
958 |
+
// Add detailed results table
|
959 |
+
const tableData = analysisResults.map(mutation => [
|
960 |
+
mutation.seqId,
|
961 |
+
mutation.position,
|
962 |
+
mutation.refBase,
|
963 |
+
mutation.altBase,
|
964 |
+
mutation.type,
|
965 |
+
mutation.confidence
|
966 |
+
]);
|
967 |
+
|
968 |
+
doc.autoTable({
|
969 |
+
head: [['Sequence ID', 'Position', 'Reference', 'Mutation', 'Type', 'Confidence']],
|
970 |
+
body: tableData,
|
971 |
+
startY: 160,
|
972 |
+
styles: {
|
973 |
+
cellPadding: 5,
|
974 |
+
fontSize: 8,
|
975 |
+
valign: 'middle'
|
976 |
+
},
|
977 |
+
columnStyles: {
|
978 |
+
0: { cellWidth: 'auto' },
|
979 |
+
1: { cellWidth: 'auto' },
|
980 |
+
2: { cellWidth: 'auto' },
|
981 |
+
3: { cellWidth: 'auto' },
|
982 |
+
4: { cellWidth: 'auto' },
|
983 |
+
5: { cellWidth: 'auto' }
|
984 |
+
}
|
985 |
+
});
|
986 |
+
|
987 |
+
// Save the PDF
|
988 |
+
doc.save(`mutatex_full_report_${new Date().toISOString().slice(0,10)}.pdf`);
|
989 |
+
}
|
990 |
+
|
991 |
+
// Initialize with empty results
|
992 |
+
filterResults();
|
993 |
+
updateTable(1);
|
994 |
+
</script>
|
995 |
+
<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>
|
996 |
+
</html>
|