From 737b94c65bf275ce66df24f65dfbf418d4835902 Mon Sep 17 00:00:00 2001 From: Brian Cully Date: Thu, 18 Feb 2021 13:16:56 -0500 Subject: Initial pass at getting codon display working. --- NOTES.org | 24 ++++++++++- amino-acid.mjs | 132 +++++++++++++++++++++++++++++---------------------------- codon.mjs | 47 ++++++++++++++++++++ genome.mjs | 33 +++++++++------ index.html | 2 +- mobile.css | 27 ++++++++---- 6 files changed, 177 insertions(+), 88 deletions(-) create mode 100644 codon.mjs diff --git a/NOTES.org b/NOTES.org index 6be8975..c77b460 100644 --- a/NOTES.org +++ b/NOTES.org @@ -20,7 +20,10 @@ Continue to mutate for 10 rounds, though not all 10 of your sequences will survi :PROPERTIES: :header-args: :noweb yes :END: - Found [[https://en.wikipedia.org/wiki/DNA_and_RNA_codon_tables#Inverse_DNA_codon_table][on Wikipedia]]: + +The javascript code will be primarily concerned with translating codons into amino acids, so I want to create a javascript hash table (which is really just an object) in order to do the lookup. So first I need a table that describes those translations, which I can then manipulate in code to get the desired javascript object which maps a given codon to an amino acid. + +[[https://en.wikipedia.org/wiki/DNA_and_RNA_codon_tables#Inverse_DNA_codon_table][This table on Wikipedia]] is a good starting point, so I copied it into the table below. I also removed the redundancies, such as =Gln or Glu=, which had more specific definitions. #+name: amino-acid-to-codon | Amino acid | Codon | @@ -47,6 +50,8 @@ Continue to mutate for 10 rounds, though not all 10 of your sequences will survi | Val | GTT GTC GTA GTG | | STOP | TAA TGA TAG | +Now I take the raw table and translate it into a cons cell of ~(amino-acid . (list-of-codons))~ that is more natural in lisp. Since the =Codon= column above is a single string, I need to split it on white space into multiple codons. + #+name: aa-table-to-form #+begin_src elisp :var raw-data=amino-acid-to-codon (mapcar (lambda (kvp) @@ -77,6 +82,8 @@ Continue to mutate for 10 rounds, though not all 10 of your sequences will survi | Val | GTT | GTC | GTA | GTG | | | | STOP | TAA | TGA | TAG | | | | +The last thing that needs to be done for usable output is changing the data from a list of ~(amino-acid . (list-of-codons))~ into ~((codon . amino-acid) (codon . amino-acid) …)~ because the final target of this manipulation is going to be a =json= object in the form ~{ codon: amino-acid }~. + #+name: aa-table-inverted #+begin_src elisp :var raw-data=amino-acid-to-codon (let ((codon-alist (mapcar (lambda (aa-to-codons) (cons (cdr aa-to-codons) (car aa-to-codons))) @@ -92,6 +99,8 @@ Continue to mutate for 10 rounds, though not all 10 of your sequences will survi #+RESULTS: aa-table-inverted : ((GCT . Ala) (GCC . Ala) (GCA . Ala) (GCG . Ala) (CGT . Arg) (CGC . Arg) (CGA . Arg) (CGG . Arg) (AGA . Arg) (AGG . Arg) (AAT . Asn) (AAC . Asn) (GAT . Asp) (GAC . Asp) (TGT . Cys) (TGC . Cys) (CAA . Gln) (CAG . Gln) (GAA . Glu) (GAG . Glu) (GGT . Gly) (GGC . Gly) (GGA . Gly) (GGG . Gly) (CAT . His) (CAC . His) (ATT . Ile) (ATC . Ile) (ATA . Ile) (CTT . Leu) (CTC . Leu) (CTA . Leu) (CTG . Leu) (TTA . Leu) (TTG . Leu) (AAA . Lys) (AAG . Lys) (ATG . Met) (TTT . Phe) (TTC . Phe) (CCT . Pro) (CCC . Pro) (CCA . Pro) (CCG . Pro) (TCT . Ser) (TCC . Ser) (TCA . Ser) (TCG . Ser) (AGT . Ser) (AGC . Ser) (ACT . Thr) (ACC . Thr) (ACA . Thr) (ACG . Thr) (TGG . Trp) (TAT . Tyr) (TAC . Tyr) (GTT . Val) (GTC . Val) (GTA . Val) (GTG . Val) (TAA . STOP) (TGA . STOP) (TAG . STOP)) +Now that the lisp data are organized correctly, it’s a simple matter of translating the =sexp= into =json= with some simple string manipulation. + #+name: tbl-to-json #+begin_src elisp :var raw-data=amino-acid-to-codon (let ((json-map (mapcar (lambda (kvp) (format "'%s': '%s'," (car kvp) (cdr kvp))) @@ -169,6 +178,19 @@ Continue to mutate for 10 rounds, though not all 10 of your sequences will survi } #+end_example +Finally, I need the complete amino acid list for a selector, so generate one from the initial table. + +#+name: tbl-to-aa-list +#+begin_src elisp :var raw-data=amino-acid-to-codon :results raw + (let ((aa-strings (mapcar (lambda (aalist) + (format "’%s’" (car aalist))) + raw-data))) + (format "[%s]" (string-join aa-strings ", "))) +#+end_src + +#+RESULTS: tbl-to-aa-list +[’Ala’, ’Arg’, ’Asn’, ’Asp’, ’Cys’, ’Gln’, ’Glu’, ’Gly’, ’His’, ’Ile’, ’Leu’, ’Lys’, ’Met’, ’Phe’, ’Pro’, ’Ser’, ’Thr’, ’Trp’, ’Tyr’, ’Val’, ’STOP’] + * work steps 1. group nucleotides by codon 2. add amino acid selection area to codon group diff --git a/amino-acid.mjs b/amino-acid.mjs index 7df50d4..cce1e0e 100644 --- a/amino-acid.mjs +++ b/amino-acid.mjs @@ -1,76 +1,78 @@ class AminoAcid { // Create a protein from three nucleotides. constructor(n1, n2, n3) { - this.codon = n1+n2+n3 - this.value = AminoAcid.codonMap[this.codon] + this.value = AminoAcid.codonMap[n1+n2+n3] + this._boundClickHandler = this.clickHandler.bind(this) + } + + get elt() { + if (this._elt === undefined) { + this._elt = document.createElement('div') + this._elt.classList.add('amino-acid') + } + return this._elt + } + + get value() { + return this.elt.innerText + } + + set value(val) { + this.elt.innerText = val + } + + lock() { + this.elt.removeEventListener('click', this._boundClickHandler) + } + + unlock() { + this.elt.addEventListener('click', this._boundClickHandler) + } + + get onClick() { + if (this._onClick !== undefined) { + return this._onClick + } + return () => {} + } + + set onClick(fn) { + this._onClick = fn + } + + clickHandler(evt) { + this.onClick(this) } } +AminoAcid.list = [ + 'Ala', 'Arg', 'Asn', 'Asp', 'Cys', 'Gln', 'Glu', 'Gly', 'His', 'Ile', + 'Leu', 'Lys', 'Met', 'Phe', 'Pro', 'Ser', 'Thr', 'Trp', 'Tyr', 'Val', + 'STOP' +] + AminoAcid.codonMap = { - 'TAA': 'STOP', - 'TGA': 'STOP', - 'TAG': 'STOP', - 'GTT': 'Val', - 'GTC': 'Val', - 'GTA': 'Val', - 'GTG': 'Val', - 'TAT': 'Tyr', - 'TAC': 'Tyr', - 'TGG': 'Trp', - 'ACT': 'Thr', - 'ACC': 'Thr', - 'ACA': 'Thr', - 'ACG': 'Thr', - 'TCT': 'Ser', - 'TCC': 'Ser', - 'TCA': 'Ser', - 'TCG': 'Ser', - 'AGT': 'Ser', - 'AGC': 'Ser', - 'CCT': 'Pro', - 'CCC': 'Pro', - 'CCA': 'Pro', - 'CCG': 'Pro', - 'TTT': 'Phe', - 'TTC': 'Phe', + 'GCT': 'Ala', 'GCC': 'Ala', 'GCA': 'Ala', 'GCG': 'Ala', + 'CGT': 'Arg', 'CGC': 'Arg', 'CGA': 'Arg', 'CGG': 'Arg', 'AGA': 'Arg', 'AGG': 'Arg', + 'AAT': 'Asn', 'AAC': 'Asn', + 'GAT': 'Asp', 'GAC': 'Asp', + 'TGT': 'Cys', 'TGC': 'Cys', + 'CAA': 'Gln', 'CAG': 'Gln', + 'GAA': 'Glu', 'GAG': 'Glu', + 'GGT': 'Gly', 'GGC': 'Gly', 'GGA': 'Gly', 'GGG': 'Gly', + 'CAT': 'His', 'CAC': 'His', + 'ATT': 'Ile', 'ATC': 'Ile', 'ATA': 'Ile', + 'CTT': 'Leu', 'CTC': 'Leu', 'CTA': 'Leu', 'CTG': 'Leu', 'TTA': 'Leu', 'TTG': 'Leu', + 'AAA': 'Lys', 'AAG': 'Lys', 'ATG': 'Met', - 'AAA': 'Lys', - 'AAG': 'Lys', - 'CTT': 'Leu', - 'CTC': 'Leu', - 'CTA': 'Leu', - 'CTG': 'Leu', - 'TTA': 'Leu', - 'TTG': 'Leu', - 'ATT': 'Ile', - 'ATC': 'Ile', - 'ATA': 'Ile', - 'CAT': 'His', - 'CAC': 'His', - 'GGT': 'Gly', - 'GGC': 'Gly', - 'GGA': 'Gly', - 'GGG': 'Gly', - 'GAA': 'Glu', - 'GAG': 'Glu', - 'CAA': 'Gln', - 'CAG': 'Gln', - 'TGT': 'Cys', - 'TGC': 'Cys', - 'GAT': 'Asp', - 'GAC': 'Asp', - 'AAT': 'Asn', - 'AAC': 'Asn', - 'CGT': 'Arg', - 'CGC': 'Arg', - 'CGA': 'Arg', - 'CGG': 'Arg', - 'AGA': 'Arg', - 'AGG': 'Arg', - 'GCT': 'Ala', - 'GCC': 'Ala', - 'GCA': 'Ala', - 'GCG': 'Ala', + 'TTT': 'Phe', 'TTC': 'Phe', + 'CCT': 'Pro', 'CCC': 'Pro', 'CCA': 'Pro', 'CCG': 'Pro', + 'TCT': 'Ser', 'TCC': 'Ser', 'TCA': 'Ser', 'TCG': 'Ser', 'AGT': 'Ser', 'AGC': 'Ser', + 'ACT': 'Thr', 'ACC': 'Thr', 'ACA': 'Thr', 'ACG': 'Thr', + 'TGG': 'Trp', + 'TAT': 'Tyr', 'TAC': 'Tyr', + 'GTT': 'Val', 'GTC': 'Val', 'GTA': 'Val', 'GTG': 'Val', + 'TAA': 'STOP', 'TGA': 'STOP', 'TAG': 'STOP', } export default AminoAcid diff --git a/codon.mjs b/codon.mjs new file mode 100644 index 0000000..7cc5ea1 --- /dev/null +++ b/codon.mjs @@ -0,0 +1,47 @@ +import AminoAcid from './amino-acid.mjs' +import Nucleotide from './nucleotide.mjs' + +class Codon { + constructor(n1, n2, n3) { + this.bases = [n1, n2, n3] + this.aminoAcid = new AminoAcid(n1, n2, n3) + + const nucleotideList = document.createElement('ol') + this.bases = [n1, n2, n3].map(base => { + const n = new Nucleotide(base) + n.onClick = this._boundNucleotideClickedHandler + nucleotideList.appendChild(n.elt) + return n + }) + this.elt.appendChild(nucleotideList) + this.elt.appendChild(this.aminoAcid.elt) + } + + get elt() { + if (this._elt === undefined) { + this._elt = document.createElement('li') + this._elt.classList.add('codon') + } + return this._elt + } + + get aaElt() { + if (this._aaElt === undefined) { + this._aaElt = document.createElement('div') + this._aaElt.classList.add('amino-acid') + } + return this._aaElt + } + + lock() { + this.bases.forEach(n => n.lock()) + this.aminoAcid.lock() + } + + unlock() { + this.bases.forEach(n => n.unlock()) + this.aminoAcid.unlock() + } +} + +export default Codon diff --git a/genome.mjs b/genome.mjs index fcab4ff..c90d10a 100644 --- a/genome.mjs +++ b/genome.mjs @@ -1,4 +1,4 @@ -import Nucleotide from './nucleotide.mjs' +import Codon from './codon.mjs' import Die from './die.mjs' import { randomItem } from './utils.mjs' @@ -9,17 +9,23 @@ class Genome { } } - constructor(gen) { - const nucleotideList = document.createElement('ol') + constructor(nucleotideGenerator) { + const codonList = document.createElement('ol') this._boundNucleotideClickedHandler = this.nucleotideClickedHandler.bind(this) - this.nucleotides = [...gen].map(base => { - const n = new Nucleotide(base) - n.onClick = this._boundNucleotideClickedHandler - nucleotideList.appendChild(n.elt) - return n + + this.codons = [] + let tmpCodon = [] + nucleotideGenerator.forEach(base => { + tmpCodon.push(base) + if (tmpCodon.length == 3) { + const c = new Codon(...tmpCodon) + codonList.appendChild(c.elt) + this.codons.push(c) + tmpCodon = [] + } }) - this.elt.appendChild(nucleotideList) + this.elt.appendChild(codonList) this.lock() } @@ -44,16 +50,16 @@ class Genome { lock() { this.elt.classList.add('locked') - this.nucleotides.forEach(n => n.lock()) + this.codons.forEach(n => n.lock()) } unlock() { this.elt.classList.remove('locked') - this.nucleotides.forEach(n => n.unlock()) + this.coons.forEach(n => n.unlock()) } clone() { - return new Genome(this.nucleotides.map(n => n.value)) + return new Genome(this.codons.map(c => c.value)) } get selectedNucleotide() { @@ -75,6 +81,7 @@ class Genome { this.selectedNucleotide = nucleotide } } -Genome.size = 18 +// Size of the genome in codons. +Genome.size = 6 export default Genome diff --git a/index.html b/index.html index bc05cad..cb6137f 100644 --- a/index.html +++ b/index.html @@ -18,7 +18,7 @@ -
    +
    diff --git a/mobile.css b/mobile.css index a6ddd29..0f7f87b 100644 --- a/mobile.css +++ b/mobile.css @@ -130,23 +130,36 @@ body { margin: 0.5ex; } +.genome>ol { + padding: 0; +} + .genome.locked { cursor: text } -.genome>ol { - display: inline-flex; - flex-wrap: wrap; - padding: 0; +.genome .codon { + display: inline-block; + margin: 0 0.5ex; border: 1px solid black; background-color: white; } +.genome .codon>ol { + padding: 1ex 0.5em; +} + +.genome .codon .amino-acid { + border-top: 1px solid black; + text-align: center; + padding-top: 0.5ex; + padding-bottom: 0.5ex; +} + .genome .nucleotide { display: inline-block; - height: 32px; - width: 32px; + width: 28px; font-size: 18px; text-align: center; flex-wrap: wrap; @@ -156,6 +169,4 @@ body { .genome .nucleotide span { display: inline-block; - padding-top: 5px; - padding-bottom: 5px; } -- cgit v1.2.3