Skip to content

Commit 99e7bb5

Browse files
authored
Added a visualiser
1 parent 510395c commit 99e7bb5

1 file changed

Lines changed: 392 additions & 0 deletions

File tree

alignment_viz.html

Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
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>Needleman-Wunsch Visualizer</title>
7+
<style>
8+
:root {
9+
--bg-color: #f4f4f9;
10+
--cell-size: 60px;
11+
--highlight: #3498db;
12+
--path-color: #e74c3c;
13+
--active-cell: #f1c40f;
14+
--header-bg: #2c3e50;
15+
--text-color: #333;
16+
}
17+
18+
body {
19+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
20+
background-color: var(--bg-color);
21+
color: var(--text-color);
22+
display: flex;
23+
flex-direction: column;
24+
align-items: center;
25+
padding: 20px;
26+
}
27+
28+
h1 { color: var(--header-bg); }
29+
30+
.controls {
31+
background: white;
32+
padding: 20px;
33+
border-radius: 8px;
34+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
35+
margin-bottom: 20px;
36+
display: grid;
37+
grid-template-columns: repeat(3, 1fr);
38+
gap: 15px;
39+
max-width: 800px;
40+
}
41+
42+
.input-group { display: flex; flex-direction: column; }
43+
label { font-size: 0.9em; font-weight: bold; margin-bottom: 5px; }
44+
input { padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
45+
46+
button {
47+
grid-column: span 3;
48+
padding: 10px;
49+
background-color: var(--header-bg);
50+
color: white;
51+
border: none;
52+
border-radius: 4px;
53+
cursor: pointer;
54+
font-size: 1.1em;
55+
transition: background 0.3s;
56+
}
57+
button:hover { background-color: #34495e; }
58+
59+
#grid-container {
60+
overflow: auto;
61+
margin-top: 20px;
62+
max-width: 100%;
63+
}
64+
65+
table {
66+
border-collapse: collapse;
67+
margin: 0 auto;
68+
}
69+
70+
th, td {
71+
width: var(--cell-size);
72+
height: var(--cell-size);
73+
border: 1px solid #bdc3c7;
74+
text-align: center;
75+
position: relative;
76+
background: white;
77+
transition: background 0.3s;
78+
}
79+
80+
th {
81+
background-color: #ecf0f1;
82+
font-weight: bold;
83+
color: var(--header-bg);
84+
}
85+
86+
/* Specific Cell States */
87+
td.active { background-color: var(--active-cell); transform: scale(1.05); z-index: 10; border: 2px solid #000;}
88+
td.path { background-color: var(--path-color); color: white; font-weight: bold; }
89+
td.calculated { background-color: #fff; }
90+
91+
/* Arrow Styling */
92+
.arrow {
93+
position: absolute;
94+
top: 2px;
95+
left: 2px;
96+
font-size: 10px;
97+
color: #7f8c8d;
98+
}
99+
.path .arrow { color: white; }
100+
101+
.score-val { font-size: 1.2em; }
102+
103+
.result-box {
104+
margin-top: 20px;
105+
font-size: 1.2em;
106+
font-family: monospace;
107+
background: white;
108+
padding: 15px;
109+
border-left: 5px solid var(--path-color);
110+
}
111+
</style>
112+
</head>
113+
<body>
114+
115+
<h1>Needleman-Wunsch Simulator</h1>
116+
117+
<div class="controls">
118+
<div class="input-group">
119+
<label>Sequence S (Side)</label>
120+
<input type="text" id="seqS" value="AAGC" style="text-transform: uppercase;">
121+
</div>
122+
<div class="input-group">
123+
<label>Sequence T (Top)</label>
124+
<input type="text" id="seqT" value="AGT" style="text-transform: uppercase;">
125+
</div>
126+
<div class="input-group">
127+
<label>Animation Speed (ms)</label>
128+
<input type="number" id="speed" value="100" min="10">
129+
</div>
130+
131+
<div class="input-group">
132+
<label>Match Score</label>
133+
<input type="number" id="match" value="1">
134+
</div>
135+
<div class="input-group">
136+
<label>Mismatch Penalty</label>
137+
<input type="number" id="mismatch" value="1">
138+
</div>
139+
<div class="input-group">
140+
<label>Gap Penalty</label>
141+
<input type="number" id="gap" value="2">
142+
</div>
143+
144+
<button onclick="startSimulation()">Run Simulation</button>
145+
</div>
146+
147+
<div id="status" style="font-weight: bold; margin-bottom: 10px; color: var(--header-bg);">Ready</div>
148+
<div id="grid-container"></div>
149+
<div id="final-result" class="result-box" style="display:none;"></div>
150+
151+
<script>
152+
// Global variables to control animation
153+
let timer = null;
154+
let gridData = [];
155+
let S, T, gap, mis, match;
156+
157+
function createTable(m, n) {
158+
const container = document.getElementById('grid-container');
159+
container.innerHTML = '';
160+
const table = document.createElement('table');
161+
162+
// Header Row (Sequence T)
163+
const trHead = document.createElement('tr');
164+
trHead.appendChild(document.createElement('th')); // Corner
165+
trHead.appendChild(document.createElement('th')); // Gap
166+
for(let c of T) {
167+
const th = document.createElement('th');
168+
th.innerText = c;
169+
trHead.appendChild(th);
170+
}
171+
table.appendChild(trHead);
172+
173+
// Rows
174+
for (let i = 0; i <= m; i++) {
175+
const tr = document.createElement('tr');
176+
177+
// Row Header (Sequence S)
178+
const th = document.createElement('th');
179+
if (i === 0) th.innerText = ''; // Gap corner
180+
else th.innerText = S[i-1];
181+
tr.appendChild(th);
182+
183+
// Cells
184+
for (let j = 0; j <= n; j++) {
185+
const td = document.createElement('td');
186+
td.id = `cell-${i}-${j}`;
187+
tr.appendChild(td);
188+
}
189+
table.appendChild(tr);
190+
}
191+
container.appendChild(table);
192+
}
193+
194+
function startSimulation() {
195+
// Clear previous interval
196+
if(timer) clearInterval(timer);
197+
document.getElementById('final-result').style.display = 'none';
198+
199+
// Get Inputs
200+
S = document.getElementById('seqS').value.toUpperCase();
201+
T = document.getElementById('seqT').value.toUpperCase();
202+
match = parseInt(document.getElementById('match').value);
203+
mis = parseInt(document.getElementById('mismatch').value);
204+
gap = parseInt(document.getElementById('gap').value);
205+
const speed = parseInt(document.getElementById('speed').value);
206+
207+
const m = S.length;
208+
const n = T.length;
209+
210+
createTable(m, n);
211+
212+
// Initialize Logic Matrix (Your C++ Logic Ported)
213+
// F[i][j] object: { score: int, dir: int (-1 left, 1 up, 2 diag) }
214+
gridData = Array(m + 1).fill().map(() => Array(n + 1).fill(null));
215+
216+
// Initialization Step
217+
for(let j = 0; j <= n; j++) {
218+
gridData[0][j] = { score: -j * gap, dir: -1 }; // -1 is LEFT in your code
219+
updateCellDisplay(0, j, -j * gap, '←');
220+
}
221+
for(let i = 0; i <= m; i++) {
222+
gridData[i][0] = { score: -i * gap, dir: 1 }; // 1 is UP in your code
223+
updateCellDisplay(i, 0, -i * gap, '↑');
224+
}
225+
// Fix 0,0 direction technically undefined, but visuals look better if empty
226+
updateCellDisplay(0, 0, 0, '');
227+
228+
let i = 1, j = 1;
229+
let phase = 'filling';
230+
231+
document.getElementById('status').innerText = "Calculating Grid...";
232+
233+
timer = setInterval(() => {
234+
if (phase === 'filling') {
235+
// --- YOUR C++ LOGIC PORTED HERE ---
236+
// Note: accessing F[i][j-1].score (Left) and F[i-1][j].score (Up)
237+
238+
let Sgap = gridData[i][j-1].score - gap;
239+
let Tgap = gridData[i-1][j].score - gap;
240+
let diagScore = gridData[i-1][j-1].score;
241+
242+
let finalScore, finalDir;
243+
let arrowSymbol = '';
244+
245+
// Strict translation of your nested IF logic
246+
if(Tgap > Sgap) {
247+
if(S[i-1] === T[j-1]) { // Match
248+
if(Tgap > diagScore + match) {
249+
finalScore = Tgap;
250+
finalDir = 1; // UP
251+
arrowSymbol = '↑';
252+
} else {
253+
finalScore = diagScore + match;
254+
finalDir = 2; // DIAG
255+
arrowSymbol = '↖';
256+
}
257+
} else { // Mismatch
258+
if(Tgap > diagScore - mis) {
259+
finalScore = Tgap;
260+
finalDir = 1; // UP
261+
arrowSymbol = '↑';
262+
} else {
263+
finalScore = diagScore - mis;
264+
finalDir = 2; // DIAG
265+
arrowSymbol = '↖';
266+
}
267+
}
268+
} else { // Sgap >= Tgap
269+
if(S[i-1] === T[j-1]) { // Match
270+
if(Sgap > diagScore + match) {
271+
finalScore = Sgap;
272+
finalDir = -1; // LEFT
273+
arrowSymbol = '←';
274+
} else {
275+
finalScore = diagScore + match;
276+
finalDir = 2; // DIAG
277+
arrowSymbol = '↖';
278+
}
279+
} else { // Mismatch
280+
if(Sgap > diagScore - mis) {
281+
finalScore = Sgap;
282+
finalDir = -1; // LEFT
283+
arrowSymbol = '←';
284+
} else {
285+
finalScore = diagScore - mis;
286+
finalDir = 2; // DIAG
287+
arrowSymbol = '↖';
288+
}
289+
}
290+
}
291+
292+
// Save Data
293+
gridData[i][j] = { score: finalScore, dir: finalDir };
294+
295+
// Visual Update
296+
updateCellDisplay(i, j, finalScore, arrowSymbol);
297+
highlightActive(i, j);
298+
299+
// Iterator logic
300+
j++;
301+
if(j > n) {
302+
j = 1;
303+
i++;
304+
}
305+
if(i > m) {
306+
phase = 'backtracking';
307+
// Reset iterators for backtracking
308+
i = m;
309+
j = n;
310+
document.getElementById('status').innerText = "Backtracking Path...";
311+
// clear last active
312+
document.querySelector('.active')?.classList.remove('active');
313+
}
314+
315+
} else if (phase === 'backtracking') {
316+
317+
if (i > 0 || j > 0) {
318+
const cell = document.getElementById(`cell-${i}-${j}`);
319+
cell.classList.add('path');
320+
321+
// Ported Backtrack Logic
322+
const currentDir = gridData[i][j].dir;
323+
324+
if (currentDir === 2) { // Diagonal
325+
i--;
326+
j--;
327+
} else if (currentDir === -1) { // Left
328+
j--;
329+
} else if (currentDir === 1) { // Up
330+
i--;
331+
}
332+
333+
// Edge case handle for the loop end (0,0)
334+
if(i===0 && j===0) {
335+
document.getElementById(`cell-0-0`).classList.add('path');
336+
finishSimulation();
337+
}
338+
} else {
339+
finishSimulation();
340+
}
341+
}
342+
}, speed);
343+
}
344+
345+
function updateCellDisplay(i, j, val, arrow) {
346+
const cell = document.getElementById(`cell-${i}-${j}`);
347+
cell.innerHTML = `<span class="arrow">${arrow}</span><span class="score-val">${val}</span>`;
348+
cell.classList.add('calculated');
349+
}
350+
351+
function highlightActive(i, j) {
352+
// Remove old active
353+
const old = document.querySelector('.active');
354+
if(old) old.classList.remove('active');
355+
// Add new active
356+
const cell = document.getElementById(`cell-${i}-${j}`);
357+
if(cell) cell.classList.add('active');
358+
}
359+
360+
function finishSimulation() {
361+
clearInterval(timer);
362+
document.getElementById('status').innerText = "Simulation Complete";
363+
364+
// Reconstruct String (Instant calculation for display)
365+
// This mirrors your final while loop
366+
let S_al = "";
367+
let T_al = "";
368+
let i = S.length, j = T.length;
369+
370+
while(i > 0 || j > 0) {
371+
if(gridData[i][j].dir == 2) {
372+
S_al = S[i-1] + S_al;
373+
T_al = T[j-1] + T_al;
374+
i--; j--;
375+
} else if (gridData[i][j].dir == -1) {
376+
S_al = "-" + S_al;
377+
T_al = T[j-1] + T_al;
378+
j--;
379+
} else if (gridData[i][j].dir == 1) {
380+
S_al = S[i-1] + S_al;
381+
T_al = "-" + T_al;
382+
i--;
383+
}
384+
}
385+
386+
const resBox = document.getElementById('final-result');
387+
resBox.style.display = 'block';
388+
resBox.innerHTML = `Aligned Sequence S: ${S_al}<br>Aligned Sequence T: ${T_al}`;
389+
}
390+
</script>
391+
</body>
392+
</html>

0 commit comments

Comments
 (0)