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