DNA Sequence Input & Basic Statistics
Analysis Summary:
Original Sequence Length: N/A
Valid Sequence Length (A,T,C,G): N/A
Nucleotide Counts:
- A: 0
- T: 0
- C: 0
- G: 0
- N (Unknown): 0
- Other Characters: 0
GC Content: N/A%
Sequence Manipulations
RNA Transcript (5' to 3'):
Reverse Sequence (3' to 5'):
Complement Sequence (5' to 3'):
Reverse Complement Sequence (3' to 5'):
Protein Translation
Motif Finder
Motif: N/A
Occurrences: N/A
Positions (0-indexed): N/A
No translation results. Sequence might be too short or lack valid codons for selected frames.
'; } else { dnaTranslationResults.innerHTML = 'Analyze a valid DNA sequence first.
'; } } // Tab 4: Motif Finder (This is updated separately by its own button action) updateMotifResultsUI(); } function updateMotifResultsUI() { if (motifSearchedEl) motifSearchedEl.textContent = analysisData.motifSearch.motif || 'N/A'; if (motifCountEl) motifCountEl.textContent = analysisData.motifSearch.count; if (motifPositionsEl) motifPositionsEl.textContent = analysisData.motifSearch.positions.length > 0 ? analysisData.motifSearch.positions.join(', ') : 'Not found'; } // --- Event Listeners --- if (analyzeDnaButton) { analyzeDnaButton.addEventListener('click', function() { const seq = dnaSequenceInput ? dnaSequenceInput.value : ""; if (!validateAndCleanDna(seq)) { // Reset and hide data if validation fails badly analysisData = { ...analysisData, isValid: false, cleanedSequence: "", rnaTranscript: "", reverseSequence: "", complementSequence: "", reverseComplementSequence: "", translations: {}, gcContent: 0, validLength: 0, nucleotideCounts: { A: 0, T: 0, C: 0, G: 0, N: 0, Other: analysisData.nucleotideCounts.Other }}; updateAllResultsUI(); return; } if (analysisData.isValid) { if (dnaValidationError) dnaValidationError.style.display = 'none'; // Clear previous errors if now valid } calculateBasicStats(); performSequenceManipulations(); performTranslation(); // Perform initial translation with default frame options updateAllResultsUI(); }); } // Re-run translation when frame or genetic code options change, if DNA is already analyzed if (readingFrameSelect) { readingFrameSelect.addEventListener('change', function() { if (analysisData.isValid && analysisData.cleanedSequence) { performTranslation(); updateAllResultsUI(); // Specifically update translation part or all } }); } // Add similar for geneticCodeSelect if multiple codes are implemented if (findMotifButton) { findMotifButton.addEventListener('click', function() { if (!analysisData.isValid || !analysisData.cleanedSequence) { if (dnaValidationError) { dnaValidationError.textContent = `Please analyze a valid DNA sequence first before finding motifs.`; dnaValidationError.style.display = 'block'; } analysisData.motifSearch = { motif: motifInput.value || "", count: 0, positions: [] }; // Store attempted motif updateMotifResultsUI(); return; } performMotifSearch(); updateMotifResultsUI(); }); } // --- PDF Download Functionality --- if (downloadDnaPdfButton) { downloadDnaPdfButton.addEventListener('click', function() { if (!analysisData.isValid && !analysisData.rawSequence) { alert("Please analyze a DNA sequence first to generate a PDF."); return; } const { jsPDF } = window.jspdf; if (!jsPDF) { alert("Error: jsPDF library not loaded. PDF generation failed."); return; } const doc = new jsPDF(); let yPos = 15; const lineSpacing = 7; const indent = 10; const indent2 = 15; const maxWidth = 180; // Max width for text before wrapping function addText(text, x, y, options = {}) { doc.text(text, x, y, options); return y + lineSpacing * (options.lineFactor || 1) ; // Adjust yPos based on text height } function addWrappedText(text, x, y, maxWidth, lineHeight) { const lines = doc.splitTextToSize(text, maxWidth); doc.text(lines, x, y); return y + lines.length * lineHeight; } doc.setFontSize(16); yPos = addText("DNA Sequence Analysis Report", doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center', lineFactor: 1.5 }); doc.setFontSize(12); yPos = addText(`Date: ${new Date().toLocaleDateString()}`, indent, yPos, {lineFactor: 2}); doc.setFontSize(11); doc.setFont(undefined, 'bold'); yPos = addText("Input Sequence:", indent, yPos); doc.setFont(undefined, 'normal'); const sequenceSnippet = analysisData.rawSequence.substring(0, 200) + (analysisData.rawSequence.length > 200 ? "..." : ""); yPos = addWrappedText(sequenceSnippet || "N/A", indent + 5, yPos, maxWidth - 5, lineSpacing * 0.7); yPos += lineSpacing * 0.5; doc.setFont(undefined, 'bold'); yPos = addText("Basic Statistics:", indent, yPos); doc.setFont(undefined, 'normal'); yPos = addText(`Original Length: ${analysisData.originalLength}`, indent2, yPos); yPos = addText(`Valid Bases (A,T,C,G): ${analysisData.validLength}`, indent2, yPos); yPos = addText(`GC Content: ${analysisData.gcContent.toFixed(2)}%`, indent2, yPos); yPos = addText("Nucleotide Counts:", indent2, yPos); yPos = addText(` A: ${analysisData.nucleotideCounts.A}, T: ${analysisData.nucleotideCounts.T}, C: ${analysisData.nucleotideCounts.C}, G: ${analysisData.nucleotideCounts.G}, N: ${analysisData.nucleotideCounts.N}, Other: ${analysisData.nucleotideCounts.Other}`, indent2 + 5, yPos); yPos += lineSpacing * 0.5; doc.setFont(undefined, 'bold'); yPos = addText("Sequence Manipulations:", indent, yPos); doc.setFont(undefined, 'normal'); yPos = addText("RNA Transcript (5' to 3'):", indent2, yPos); yPos = addWrappedText(analysisData.rnaTranscript || "N/A", indent2 + 5, yPos, maxWidth - 10, lineSpacing * 0.7); yPos = addText("Reverse Complement (3' to 5'):", indent2, yPos); yPos = addWrappedText(analysisData.reverseComplementSequence || "N/A", indent2 + 5, yPos, maxWidth - 10, lineSpacing * 0.7); yPos += lineSpacing * 0.5; if (Object.keys(analysisData.translations).length > 0) { doc.setFont(undefined, 'bold'); yPos = addText("Protein Translations:", indent, yPos); doc.setFont(undefined, 'normal'); for (const frameKey in analysisData.translations) { if (yPos > 270) { doc.addPage(); yPos = 15; } // Page break yPos = addText(`${frameKey}:`, indent2, yPos); yPos = addWrappedText(analysisData.translations[frameKey] || "N/A", indent2 + 5, yPos, maxWidth - 10, lineSpacing * 0.7); } yPos += lineSpacing * 0.5; } if (analysisData.motifSearch.motif) { if (yPos > 260) { doc.addPage(); yPos = 15; } // Page break doc.setFont(undefined, 'bold'); yPos = addText("Motif Search:", indent, yPos); doc.setFont(undefined, 'normal'); yPos = addText(`Motif: ${analysisData.motifSearch.motif}`, indent2, yPos); yPos = addText(`Occurrences: ${analysisData.motifSearch.count}`, indent2, yPos); yPos = addText(`Positions (0-indexed): ${analysisData.motifSearch.positions.length > 0 ? analysisData.motifSearch.positions.join(', ') : 'Not found'}`, indent2, yPos); } doc.save("dna_sequence_analysis_report.pdf"); }); } // Initial UI state based on potentially empty analysisData updateAllResultsUI(); });