|
42 | 42 | return tr; |
43 | 43 | } |
44 | 44 |
|
| 45 | + // Residue coloring by physicochemical property |
| 46 | + const AA_CLASSES = {}; |
| 47 | + "AILMFWV".split("").forEach(c => AA_CLASSES[c] = "aa-hydrophobic"); |
| 48 | + "KR".split("").forEach(c => AA_CLASSES[c] = "aa-positive"); |
| 49 | + "DE".split("").forEach(c => AA_CLASSES[c] = "aa-negative"); |
| 50 | + "NQST".split("").forEach(c => AA_CLASSES[c] = "aa-polar"); |
| 51 | + "C".split("").forEach(c => AA_CLASSES[c] = "aa-cysteine"); |
| 52 | + "G".split("").forEach(c => AA_CLASSES[c] = "aa-glycine"); |
| 53 | + "P".split("").forEach(c => AA_CLASSES[c] = "aa-proline"); |
| 54 | + "HY".split("").forEach(c => AA_CLASSES[c] = "aa-aromatic"); |
| 55 | + |
| 56 | + function renderAlignment(alignment) { |
| 57 | + if (!alignment || !alignment.sequences) return ""; |
| 58 | + |
| 59 | + const ids = Object.keys(alignment.sequences); |
| 60 | + const seqs = Object.values(alignment.sequences); |
| 61 | + const triad = alignment.catalytic_triad || ""; |
| 62 | + const len = seqs[0].length; |
| 63 | + const BLOCK = 80; |
| 64 | + const pad = Math.max(...ids.map(s => s.length)); |
| 65 | + |
| 66 | + let html = '<div class="alignment"><h3>Protein alignment</h3>'; |
| 67 | + html += '<div class="aln-legend">'; |
| 68 | + html += '<span class="aa-hydrophobic">hydrophobic</span> '; |
| 69 | + html += '<span class="aa-positive">positive</span> '; |
| 70 | + html += '<span class="aa-negative">negative</span> '; |
| 71 | + html += '<span class="aa-polar">polar</span> '; |
| 72 | + html += '<span class="aa-cysteine">cysteine</span> '; |
| 73 | + html += '<span class="aa-glycine">glycine</span> '; |
| 74 | + html += '<span class="aa-aromatic">aromatic</span> '; |
| 75 | + html += '<span class="aa-triad">triad</span>'; |
| 76 | + html += '</div><pre class="aln-block">'; |
| 77 | + |
| 78 | + for (let start = 0; start < len; start += BLOCK) { |
| 79 | + const end = Math.min(start + BLOCK, len); |
| 80 | + |
| 81 | + // Sequence rows |
| 82 | + for (let s = 0; s < ids.length; s++) { |
| 83 | + const label = ids[s].padEnd(pad + 2); |
| 84 | + let row = `<span class="aln-label">${label}</span>`; |
| 85 | + for (let i = start; i < end; i++) { |
| 86 | + const aa = seqs[s][i]; |
| 87 | + const isTriad = triad[i] && "DdE".includes(triad[i]); |
| 88 | + const cls = isTriad ? "aa-triad" : (AA_CLASSES[aa] || ""); |
| 89 | + row += cls ? `<span class="${cls}">${aa}</span>` : aa; |
| 90 | + } |
| 91 | + html += row + "\n"; |
| 92 | + } |
| 93 | + |
| 94 | + // Triad annotation row |
| 95 | + let triadRow = " ".repeat(pad + 2); |
| 96 | + for (let i = start; i < end; i++) { |
| 97 | + const ch = triad[i] || "."; |
| 98 | + if ("DdE".includes(ch)) { |
| 99 | + triadRow += `<span class="aa-triad">${ch}</span>`; |
| 100 | + } else { |
| 101 | + triadRow += " "; |
| 102 | + } |
| 103 | + } |
| 104 | + html += triadRow + "\n"; |
| 105 | + |
| 106 | + // Conservation row |
| 107 | + let consRow = " ".repeat(pad + 2); |
| 108 | + for (let i = start; i < end; i++) { |
| 109 | + const col = seqs.map(s => s[i]); |
| 110 | + const unique = new Set(col); |
| 111 | + if (unique.size === 1) consRow += "*"; |
| 112 | + else if (col.every(r => "AILMFWV".includes(r))) consRow += ":"; |
| 113 | + else if (col.every(r => "DE".includes(r))) consRow += ":"; |
| 114 | + else if (col.every(r => "KR".includes(r))) consRow += ":"; |
| 115 | + else if (col.every(r => "NQST".includes(r))) consRow += ":"; |
| 116 | + else consRow += " "; |
| 117 | + } |
| 118 | + html += consRow + "\n\n"; |
| 119 | + } |
| 120 | + |
| 121 | + html += "</pre></div>"; |
| 122 | + return html; |
| 123 | + } |
| 124 | + |
45 | 125 | function buildDetailRow(teamData) { |
46 | 126 | const tr = document.createElement("tr"); |
47 | 127 | tr.className = "detail-row"; |
|
75 | 155 | } |
76 | 156 | } |
77 | 157 |
|
78 | | - td.innerHTML = `<div class="detail-panel">${checksHTML}${issuesHTML}</div>`; |
| 158 | + const alignmentHTML = renderAlignment(teamData.alignment); |
| 159 | + |
| 160 | + td.innerHTML = `<div class="detail-panel">${checksHTML}${issuesHTML}${alignmentHTML}</div>`; |
79 | 161 | tr.appendChild(td); |
80 | 162 | tr.style.display = "none"; |
81 | 163 | return tr; |
|
0 commit comments