Source: CodonTable.js

//////////////////////////////////////////////////////////////////////////////
// CodonTable and CodonTables
//////////////////////////////////////////////////////////////////////////////

/**
 * Holder for CodonTables.
 * This class will be populated with each [CodonTable](CodonTable.html) as it's required.
 *
 * ### Examples:
 * ```js
 * // Initially this class will have no
 * const codonTables = new CodonTables();
 * codonTables.tables;
 * // => {}
 *
 * Tables are accessed via byID
 * codonTables.byID(1)
 * // => CodonTable {name: 'Standard', ...}
 *
 * // This will also add the table to tables:
 * codonTables.tables;
 * // => { 1: {name: 'Standard', ...} }
 * ```
 */
class CodonTables {

  /**
   * Create an empty container to lazy load codon tables as needed
   */
  constructor() {
    this._tables = {};
  }

  /**
   * Return the current tables
   */
  get tables() {
    return this._tables;
  }

  /**
   * Return the table for provided code
   * @param {Number|String} id - ID of the Codon Table (e.g. 1, '1')
   */
  byID(id) {
    const availableIDs = CodonTable.availableGeneticCodeIDs;
    const idString = id.toString();
    let table;
    if (this.tables[idString]) {
      table = this.tables[idString];
    } else if (availableIDs.includes(idString)) {
      table = new CodonTable(idString);
      this.tables[idString] = table;
    } else {
      console.error(`Unknown Codon Table ID: '${id}'`)
    }
    return table;
  }

  /**
   * Returns object with table codes as the keys and the values as the table names
   * ```js
   * codonTables.names()
   * // => {1: 'Standard', 2: 'Vertebrate Mitochondrial', ...}
   * ```
   */
  names() {
    const codes = {};
    const ids = Object.keys(CodonTable.definitions);
    ids.map( id => codes[id] = CodonTable.definitions[id].name);
    return codes
  }

  /**
   * Translate a sequence
   * @param {String} seq - The sequence to translate
   * @param {Number} geneticCodeID - The genetic code ID (e.g. 1)
   * @param {Number} startCodon - Position (bp) of the first codon
   */
  translate(seq, geneticCodeID, startCodon=1) {
    const table = this.byID(geneticCodeID);
    if (table) {
      return table.translate(seq, startCodon);
    }
  }
}

/**
 * This class contains all the codon table definitions and has the ability to translate
 * DNA seqeunces to protein.
 */
class CodonTable {

  /**
   * Create a new codon table
   * @param {Number} geneticCodeID - ID for the genetic code (e.g. 1 for 'Standard' code)
   */
  constructor(geneticCodeID) {
    this._codons = this.generateCodons();
    this._geneticCodeID = geneticCodeID && geneticCodeID.toString();
    this._generateTable();
  }

  /**
   * Return the class name as a string.
   * @return {String} - 'CodonTable'
   */
  toString() {
    return 'CodonTable';
  }

  /**
   * Return array of all the available genetic code IDs
   */
  static get availableGeneticCodeIDs() {
    return Object.keys(CodonTable.definitions);
  }

  /**
   * Return a list of the 64 codons, sorted in the following order: T, C, A, G
   */
  get codons() {
    return this._codons;
  }

  /**
   * Return the genetic code for this codon table
   */
  get geneticCodeID() {
    return this._geneticCodeID;
  }

  /**
   * Return the name for this codon table
   */
  get name() {
    return this._name;
  }

  /**
   * Return the table for this codon table
   */
  get table() {
    return this._table;
  }

  /**
   * Return the start codons for this codon table
   */
  get starts() {
    return this._starts;
  }

  /**
   * Return the stop codons for this codon table
   */
  get stops() {
    return this._stops;
  }

  /**
   * Creates the table for this codon table
   * @private
   */
  _generateTable() {
    const codeID = this.geneticCodeID;
    if (CodonTable.availableGeneticCodeIDs.includes(codeID)) {
      const definition = CodonTable.definitions[codeID];
      // Name
      this._name = definition.name;
      // Table, starts, stops
      const table = {};
      const starts = [];
      const stops = [];
      for (const [i, codon] of this.codons.entries()) {
        table[codon] = definition.aa[i];
        if (definition.starts[i] === 'M') {
          starts.push(codon);
        }
        if (definition.aa[i] === '*') {
          stops.push(codon);
        }
      }
      this._table = table;
      this._starts = starts;
      this._stops = stops;
    } else {
      console.error(`Unknown Codon Table ID: '${codeID}'`)
    }
  }

  /**
   * Generate the codons using the nucleotides sorted by: T, C, A, G
   * @private
   */
  generateCodons() {
    // Base1 = TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG
    // Base2 = TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG
    // Base3 = TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG
    const bases = ['T', 'C', 'A', 'G'];
    const codons = [];
    for (const b1 of bases) {
      for (const b2 of bases) {
        for (const b3 of bases) {
          codons.push(`${b1}${b2}${b3}`);
        }
      }
    }
    return codons;
  }

  /**
   * Returns all the available codon table definitions
   */
  static get definitions() {
    //   Base1 = TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG
    //   Base2 = TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG
    //   Base3 = TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG
    const definitions = {
      1: {
        name:   'Standard',
        aa:     'FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
        starts: '---M---------------M---------------M----------------------------',
      },
      2: {
        name:   'Vertebrate Mitochondrial',
        aa:     'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNKKSS**VVVVAAAADDEEGGGG',
        starts: '--------------------------------MMMM---------------M------------',
      },
      3: {
        name:   'Yeast Mitochondrial',
        aa:     'FFLLSSSSYY**CCWWTTTTPPPPHHQQRRRRIIMMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
        starts: '----------------------------------MM----------------------------',
      },
      4: {
        name:   'Mold, Protozoan, Coelenterate Mitochondrial and Mycoplasma/Spiroplasma',
        aa:     'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
        starts: '--MM---------------M------------MMMM---------------M------------',
      },
      5: {
        name:   'Invertebrate Mitochondrial',
        aa:     'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNKKSSSSVVVVAAAADDEEGGGG',
        starts: '---M----------------------------MMMM---------------M------------',
      },
      6: {
        name:   'Ciliate, Dasycladacean and Hexamita Nuclear',
        aa:     'FFLLSSSSYYQQCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
        starts: '-----------------------------------M----------------------------',
      },
      9: {
        name:   'Echinoderm and Flatworm Mitochondrial',
        aa:     'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNNKSSSSVVVVAAAADDEEGGGG',
        starts: '-----------------------------------M---------------M------------',
      },
      10: {
        name:   'Euplotid Nuclear',
        aa:     'FFLLSSSSYY**CCCWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
        starts: '-----------------------------------M----------------------------',
      },
      11: {
        name:   'Bacterial and Plant Plastid',
        aa:     'FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
        starts: '---M---------------M------------MMMM---------------M------------',
      },
      12: {
        name:   'Alternative Yeast Nuclear',
        aa:     'FFLLSSSSYY**CC*WLLLSPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
        starts: '-------------------M---------------M----------------------------',
      },
      13: {
        name:   'Ascidian Mitochondrial',
        aa:     'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNKKSSGGVVVVAAAADDEEGGGG',
        starts: '---M------------------------------MM---------------M------------',
      },
      14: {
        name:   'Alternative Flatworm Mitochondrial',
        aa:     'FFLLSSSSYYY*CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNNKSSSSVVVVAAAADDEEGGGG',
        starts: '-----------------------------------M----------------------------',
      },
      15: {
        name:   'Blepharisma Nuclear',
        aa:     'FFLLSSSSYY*QCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
        starts: '-----------------------------------M----------------------------',
      },
      16: {
        name:   'Chlorophycean Mitochondrial',
        aa:     'FFLLSSSSYY*LCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
        starts: '-----------------------------------M----------------------------',
      },
      21: {
        name:   'Trematode Mitochondrial',
        aa:     'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNNKSSSSVVVVAAAADDEEGGGG',
        starts: '-----------------------------------M---------------M------------',
      },
      22: {
        name:   'Scenedesmus obliquus mitochondrial',
        aa:     'FFLLSS*SYY*LCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
        starts: '-----------------------------------M----------------------------',
      },
      23: {
        name:   'Thraustochytrium Mitochondrial',
        aa:     'FF*LSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
        starts: '--------------------------------M--M---------------M------------',
      },
    }
    return definitions;
  }

  /**
   * Translate a sequence using this codon table
   * @param {String} seq - The sequence to translate
   * @param {Number} startCodon - Position (bp) of the first codon
   */
  translate(rawSeq, codonStart=1) {
    const codonSize = 3;
    const seq = rawSeq.toUpperCase();
    let index = -1 + codonStart;
    let codon = seq.slice(index, index + codonSize);
    let translated = '';
    while (codon.length === codonSize) {
      translated += this.table[codon] || 'X';
      index += codonSize;
      codon = seq.slice(index, index + codonSize);
    }
    return translated;
  }

}

export { CodonTables, CodonTable };