diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000..74e7e2b --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["*"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: "." + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/alignment.js b/alignment.js index c506247..aefbd79 100644 --- a/alignment.js +++ b/alignment.js @@ -1,35 +1,46 @@ // JavaScript implementation of global alignment // Arthur G. Goetzee 2024-11-27 -const GAP_PENALTY = -2 -const MISMATCH_PENALTY = -1 -const MATCH_SCORE = 2 +// Alignment parameters +const GAP_PENALTY = -2; +const MISMATCH_PENALTY = -1; +const MATCH_SCORE = 2; function constructMatrix(seq1, seq2) { let matrix = []; - for (let i = 0; i current[1] > max[1] ? current : max)[0]; + tracebackMatrix[i][j] = Object.entries(choices).reduce( + (max, current) => (current[1] > max[1] ? current : max) + )[0]; } } - return scoreMatrix, tracebackMatrix + return scoreMatrix, tracebackMatrix; } function traceback(tracebackMatrix, seq1, seq2) { + let alignment = ""; + let alignmentComplement = ""; let i = seq1.length - 1; let j = seq2.length - 1; while (i >= 0 && j >= 0) { - switch (tracebackMatrix[i][j]) { - case 'D': - alignment = `${seq1[i]}${alignment}`; //seq1 - alignmentComplement = `${seq2[j]}${alignmentComplement}`; //seq2 - i--; - j--; - break - case 'U': //go a row Up - alignment = `${seq1[i]}${alignment}`; - alignmentComplement = `-${alignmentComplement}`; - i--; - break; - case 'L': //go a column Left - alignment = `-${alignment}`; - alignmentComplement = `${seq2[j]}${alignmentComplement}`; - j--; - break; - } - - + switch (tracebackMatrix[i][j]) { + case "D": + alignment = `${seq1[i]}${alignment}`; //seq1 + alignmentComplement = `${seq2[j]}${alignmentComplement}`; //seq2 + i--; + j--; + break; + case "U": //go a row Up + alignment = `${seq1[i]}${alignment}`; + alignmentComplement = `-${alignmentComplement}`; + i--; + break; + case "L": //go a column Left + alignment = `-${alignment}`; + alignmentComplement = `${seq2[j]}${alignmentComplement}`; + j--; + break; + } } - return alignment, alignmentComplement; - + return { alignment, alignmentComplement }; } function prettyPrintMatrix(matrix, seq1, seq2) { - header = ' '; - for (char of seq2) { + const header = " "; + for (let char of seq2) { header += ` ${char}`; } - console.log(header) + console.log(header); - for (let i = 0; i < seq1.length; i++){ - row = `${seq1[i]} `; - for (let j = 0 ;j < seq2.length; j++) { - row += `${matrix[i][j]}`.padEnd(4, ' '); + for (let i = 0; i < seq1.length; i++) { + let row = `${seq1[i]} `; + for (let j = 0; j < seq2.length; j++) { + row += `${matrix[i][j]}`.padEnd(4, " "); } console.log(row); } } function prettyPrintAlignment(alignment, alignmentComplement) { - const alignedMatch = (aa1, aa2) => aa1 === aa2 ? '|' : ' '; - - let topAlignmentRow = ''; - let matchAlignmentRow = ''; - let bottomAlignmentRow = ''; - - for (let i = 0; i < alignment.length; i++){ - + const alignedMatch = (aa1, aa2) => (aa1 === aa2 ? "|" : " "); + + let topAlignmentRow = ""; + let matchAlignmentRow = ""; + let bottomAlignmentRow = ""; + + for (let i = 0; i < alignment.length; i++) { topAlignmentRow += `${alignment[i]} `; - matchAlignmentRow += `${alignedMatch(alignment[i],alignmentComplement[i])} `; + matchAlignmentRow += `${alignedMatch( + alignment[i], + alignmentComplement[i] + )} `; bottomAlignmentRow += `${alignmentComplement[i]} `; } - console.log(topAlignmentRow); - console.log(matchAlignmentRow); - console.log(bottomAlignmentRow); + + let result = ` + ${topAlignmentRow} + ${matchAlignmentRow} + ${bottomAlignmentRow}`; + + return result; } -function printResults(alignment, alignmentComplement, scoreMatrix) { - console.log('***** Alignment Report *******'); - - console.log('----Parameters----'); - console.log(`Gap penalty: ${GAP_PENALTY}`); - console.log(`Mismatch penalty: ${MISMATCH_PENALTY}`); - console.log(`Match Score: ${MATCH_SCORE}`); - - console.log('------Input-------'); - console.log(`Sequence 1: ${seq1}`); - console.log(`Length: ${seq1.length}`); - - console.log(`\nSequence 2: ${seq2}`); - console.log(`Length: ${seq2.length}`); - - - console.log('\n------Results------'); - console.log(`Alignment score: ${scoreMatrix[seq1.length-1][seq2.length-1]}`); - prettyPrintAlignment(alignment,alignmentComplement); +function printResults(alignment, alignmentComplement, scoreMatrix, seq1, seq2) { + const result = ` + ***** Alignment Report ******* + + ----Parameters---- + Gap penalty: ${GAP_PENALTY} + Mismatch penalty: ${MISMATCH_PENALTY} + Match Score: ${MATCH_SCORE} + ------Input------- + Sequence 1: ${seq1} + Length: ${seq1.length} + Sequence 2: ${seq2} + Length: ${seq2.length} + + ------Results------ + Alignment score: ${scoreMatrix[seq1.length - 1][seq2.length - 1]} + ${prettyPrintAlignment(alignment, alignmentComplement)}`; + + return result; } function validateSequences(seq1, seq2) { - if (seq1.length === 0 || seq2.length === 0){ - throw new Error('Sequences cannot be empty!'); + if (seq1.length === 0 || seq2.length === 0) { + throw new Error("Sequences cannot be empty!"); } - if (typeof seq1 != 'string'|| typeof seq2 != 'string') { - throw new Error('Sequences must be strings!'); + if (typeof seq1 != "string" || typeof seq2 != "string") { + throw new Error("Sequences must be strings!"); } } -let seq1 = 'AGCT'; //rows or i -let seq2 = 'AGCT'; //columns or j -validateSequences(seq1, seq2); - -let alignment = ''; //seq1 -let alignmentComplement = ''; //seq2 - -// step 1, initialization -let scoreMatrix = constructMatrix(seq1, seq2); -let tracebackMatrix = constructMatrix(seq1, seq2); - -scoreMatrix = initializeMatrix(scoreMatrix, seq1, seq2); - -// step 2, calculation -scoreMatrix, tracebackMatrix = calculateScores(scoreMatrix, tracebackMatrix, seq1, seq2); +export function runAlignment(seq1, seq2) { + // step 1, initialization + let scoreMatrix = constructMatrix(seq1, seq2); + let tracebackMatrix = constructMatrix(seq1, seq2); + + scoreMatrix = initializeScoreMatrix(scoreMatrix, seq1, seq2); + tracebackMatrix = initializeTracebackMatrix(tracebackMatrix, seq1, seq2)[ + // step 2, calculation + (scoreMatrix, tracebackMatrix) + ] = calculateScores(scoreMatrix, tracebackMatrix, seq1, seq2); + + //step 3, traceback + const { alignment, alignmentComplement } = traceback( + tracebackMatrix, + seq1, + seq2 + ); + + //step 4, print the results! + return printResults( + alignment, + alignmentComplement, + scoreMatrix, + seq1, + seq2 + ); +} -//step 3, traceback -alignment, alignmentComplement = traceback(tracebackMatrix, seq1, seq2); +// Input variables +let seq1 = "AGCT"; //rows or i +let seq2 = "AGCT"; //columns or j +validateSequences(seq1, seq2); -//step 4, print the results! -printResults(alignment, alignmentComplement, scoreMatrix); \ No newline at end of file +console.log(runAlignment(seq1, seq2)); diff --git a/index.html b/index.html new file mode 100644 index 0000000..32ad137 --- /dev/null +++ b/index.html @@ -0,0 +1,54 @@ + + + + + AlignmentJS + + +
+
+

AlignmentJS

+
+ +
+

Inputs and parameters

+ +

+ + + +
+ + + +

+ +

+ Note, these parameters dont work yet. +
+ + + + + + + + +

+ + + + +
+

Outputs

+ +
+                    Results will be displayed here
+                
+ +

+
+
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..4720025 --- /dev/null +++ b/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/readme.md b/readme.md index 2d724ba..3d13c88 100644 --- a/readme.md +++ b/readme.md @@ -1,20 +1,30 @@ # AlignmentJS -This is a javascript implementation of the [Needleman-Wunsch algorithm](https://en.wikipedia.org/wiki/Needleman–Wunsch_algorithm) for global sequence alignment. It is a simple implementation that I made to get a better understanding of javascript, as such you may encounter some beginner mistakes. The code is not optimized for performance, but it should work fine for small sequences. Feel free to give feedback or suggestions for improvements in the issues section. -## Usage +This is a javascript implementation of the [Needleman-Wunsch algorithm](https://en.wikipedia.org/wiki/Needleman–Wunsch_algorithm) for global sequence alignment. It is a simple implementation that I made to get a better understanding of javascript and web development, as such you may encounter some beginner mistakes. The code is not optimized for performance, but it should work fine for small sequences. Feel free to give feedback or suggestions for improvements in the issues section. + +## Web version + +A live web version of the script can be found [here](https://agoetzee.github.io/AlignmentJS/). _Note that not all functionality is implemented yet._ This web version is still a work in progress, and I will be adding more features in the future. Feedback is welcome in the issues section. Go easy on me, this is my first web project. + +## Offline usage + +The script can also be used offline. You can download the repository and run the script with node.js. Currently you need to hardcode the sequences and parameters in the script. The variables you need to change are: -- `seq1` and `seq2`: the sequences to align -- `GAP_PENALTY`, `MISMATCH_PENALTY` and `MATCH_SCORE`: the scoring parameters -Then you can run the script in a browser, or with node.js: +- `seq1` and `seq2`: the sequences to align +- `GAP_PENALTY`, `MISMATCH_PENALTY` and `MATCH_SCORE`: the scoring parameters + +Then you can run the script in a browser, or with node.js: + ```bash node alignment.js -``` +``` The results will be printed to the console. ## Example -Currently, the script is set up to align the sequences "AGCT" and "AGCT". The output should be: + +Currently, the script is set up to align the sequences `AGCT` and `AGCT`. The output should be: ```bash >>> node alignment.js @@ -36,4 +46,4 @@ Alignment score: 6 A G C T | | | | A G C T -``` \ No newline at end of file +``` diff --git a/script.js b/script.js new file mode 100644 index 0000000..17114a6 --- /dev/null +++ b/script.js @@ -0,0 +1,32 @@ +import { runAlignment } from "./alignment.js"; + +document.getElementById("run").addEventListener("click", function () { + const seq1 = document.getElementById("seq1").value.toUpperCase(); + const seq2 = document.getElementById("seq2").value.toUpperCase(); + + if (!seq1 || !seq2) { + document.getElementById("results").textContent = + "Please enter both sequences."; + return; + } + + if (!/^[a-zA-Z]+$/.test(seq1) || !/^[a-zA-Z]+$/.test(seq2)) { + document.getElementById("results").textContent = + "Please enter alphabetic characters only."; + return; + } + + const results = runAlignment(seq1, seq2); + + document.getElementById("results").textContent = results; +}); + +document.getElementById("copyButton").addEventListener("click", function () { + const copyText = document.getElementById("results").textContent; + navigator.clipboard.writeText(copyText); + document.getElementById("copyAlert").textContent = "Copied!"; + + setTimeout(function () { + document.getElementById("copyAlert").textContent = ""; + }, 1000); +});