view test-data/meme_output_test2.html @ 2:b48e673af4e8 draft

planemo upload for repository https://github.com/galaxyproject/tools-iuc/tree/master/tools/meme commit e2cf796f991cbe8c96e0cc5a0056b7255ac3ad6b
author iuc
date Thu, 17 May 2018 14:11:15 -0400
parents
children ff2f53a32d0e
line wrap: on
line source

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <title>MEME</title>
    <script>
      // @JSON_VAR data
      var data = {
        "program": "MEME",
        "version": "4.12.0",
        "release": "Tue Jun 27 16:22:50 2017 -0700",
        "stop_reason": "Stopped because requested number of motifs (1) found.",
        "cmd": [
          "meme", "meme_input_1.fasta", "-o", "meme_test2_out", "-nostatus",
          "-maxsize", "1000000", "-sf", "Galaxy_FASTA_Input", "-dna", "-mod",
          "zoops", "-nmotifs", "1", "-wnsites", "0.8", "-minw", "8", "-maxw",
          "50", "-wg", "11", "-ws", "1", "-maxiter", "50", "-distance",
          "0.001", "-prior", "dirichlet", "-b", "0.01", "-plib",
          "prior30.plib", "-spmap", "uni", "-spfuzz", "0.5"
        ],
        "options": {
          "mod": "zoops",
          "revcomp": false,
          "nmotifs": 1,
          "minw": 8,
          "maxw": 50,
          "minsites": 2,
          "maxsites": 30,
          "wnsites": 0.8,
          "spmap": "uni",
          "spfuzz": 0.5,
          "maxwords": -1,
          "prior": "dirichlet",
          "b": 0.01,
          "maxiter": 50,
          "distance": 0.001,
          "wg": 11,
          "ws": 1,
          "noendgaps": false,
          "substring": true
        },
        "alphabet": {
          "name": "DNA",
          "like": "dna",
          "ncore": 4,
          "symbols": [
            {
              "symbol": "A",
              "name": "Adenine",
              "colour": "CC0000",
              "complement": "T"
            }, {
              "symbol": "C",
              "name": "Cytosine",
              "colour": "0000CC",
              "complement": "G"
            }, {
              "symbol": "G",
              "name": "Guanine",
              "colour": "FFB300",
              "complement": "C"
            }, {
              "symbol": "T",
              "aliases": "U",
              "name": "Thymine",
              "colour": "008000",
              "complement": "A"
            }, {
              "symbol": "N",
              "aliases": "X.",
              "name": "Any base",
              "equals": "ACGT"
            }, {
              "symbol": "V",
              "name": "Not T",
              "equals": "ACG"
            }, {
              "symbol": "H",
              "name": "Not G",
              "equals": "ACT"
            }, {
              "symbol": "D",
              "name": "Not C",
              "equals": "AGT"
            }, {
              "symbol": "B",
              "name": "Not A",
              "equals": "CGT"
            }, {
              "symbol": "M",
              "name": "Amino",
              "equals": "AC"
            }, {
              "symbol": "R",
              "name": "Purine",
              "equals": "AG"
            }, {
              "symbol": "W",
              "name": "Weak",
              "equals": "AT"
            }, {
              "symbol": "S",
              "name": "Strong",
              "equals": "CG"
            }, {
              "symbol": "Y",
              "name": "Pyrimidine",
              "equals": "CT"
            }, {
              "symbol": "K",
              "name": "Keto",
              "equals": "GT"
            }
          ]
        },
        "background": {
          "freqs": [0.294, 0.231, 0.257, 0.217]
        },
        "sequence_db": {
          "source": "Galaxy_FASTA_Input",
          "psp_source": "prior30.plib",
          "freqs": [0.294, 0.231, 0.257, 0.217],
          "sequences": [
            {
              "name": "chr21_19617074_19617124_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_26934381_26934431_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_28217753_28217803_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_31710037_31710087_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_31744582_31744632_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_31768316_31768366_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_31914206_31914256_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_31933633_31933683_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_31962741_31962791_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_31964683_31964733_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_31973364_31973414_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_31992870_31992920_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_32185595_32185645_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_32202076_32202126_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_32253899_32253949_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_32410820_32410870_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_36411748_36411798_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_37838750_37838800_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_45705687_45705737_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_45971413_45971463_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_45978668_45978718_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_45993530_45993580_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_46020421_46020471_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_46031920_46031970_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_46046964_46047014_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_46057197_46057247_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_46086869_46086919_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_46102103_46102153_-",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_47517957_47518007_+",
              "length": 50,
              "weight": 1.000000
            }, {
              "name": "chr21_47575506_47575556_-",
              "length": 50,
              "weight": 1.000000
            }
          ]
        },
        "motifs": [
          {
            "db": 0,
            "id": "GGSRTATAAAA",
            "alt": "MEME-1",
            "len": 11,
            "nsites": 30,
            "evalue": "5.1e-040",
            "ic": 13.0,
            "re": 12.2,
            "llr": 254,
            "bt": 5.2854,
            "time": 0.376000,
            "psm": [
              [-14, -179, 114, -112], [3, -1155, 137, -270], 
              [-114, 20, 86, -71], [3, -279, 122, -170], 
              [-1155, -1155, -295, 215], [156, -179, -1155, -170], 
              [-1155, -1155, -1155, 220], [172, -279, -1155, -1155], 
              [125, -1155, -1155, 46], [167, -179, -1155, -1155], 
              [144, -1155, -63, -270]
            ],
            "pwm": [
              [0.266667, 0.066667, 0.566667, 0.100000], 
              [0.300000, 0.000000, 0.666667, 0.033333], 
              [0.133333, 0.266667, 0.466667, 0.133333], 
              [0.300000, 0.033333, 0.600000, 0.066667], 
              [0.000000, 0.000000, 0.033333, 0.966667], 
              [0.866667, 0.066667, 0.000000, 0.066667], 
              [0.000000, 0.000000, 0.000000, 1.000000], 
              [0.966667, 0.033333, 0.000000, 0.000000], 
              [0.700000, 0.000000, 0.000000, 0.300000], 
              [0.933333, 0.066667, 0.000000, 0.000000], 
              [0.800000, 0.000000, 0.166667, 0.033333]
            ],
            "sites": [
              {
                "seq": 24,
                "pos": 12,
                "rc": false,
                "pvalue": 4.51e-07,
                "lflank": "AAGGCCAGGA",
                "match": "GGGGTATAAAA",
                "rflank": "GCCTGAGAGC"
              }, {
                "seq": 23,
                "pos": 15,
                "rc": false,
                "pvalue": 2.22e-06,
                "lflank": "ATACCCAGGG",
                "match": "AGGGTATAAAA",
                "rflank": "CCTCAGCAGC"
              }, {
                "seq": 13,
                "pos": 13,
                "rc": false,
                "pvalue": 2.74e-06,
                "lflank": "CCACCAGCTT",
                "match": "GAGGTATAAAA",
                "rflank": "AGCCCTGTAC"
              }, {
                "seq": 25,
                "pos": 36,
                "rc": false,
                "pvalue": 4.86e-06,
                "lflank": "ACAGGCCCTG",
                "match": "GGCATATAAAA",
                "rflank": "GCC"
              }, {
                "seq": 21,
                "pos": 7,
                "rc": false,
                "pvalue": 4.86e-06,
                "lflank": "CCAAGGA",
                "match": "GGAGTATAAAA",
                "rflank": "GCCCCACAAA"
              }, {
                "seq": 19,
                "pos": 9,
                "rc": false,
                "pvalue": 4.86e-06,
                "lflank": "CAGGCCCTG",
                "match": "GGCATATAAAA",
                "rflank": "GCCCCAGCAG"
              }, {
                "seq": 9,
                "pos": 13,
                "rc": false,
                "pvalue": 4.86e-06,
                "lflank": "GATTCACTGA",
                "match": "GGCATATAAAA",
                "rflank": "GGCCCTCTGC"
              }, {
                "seq": 28,
                "pos": 32,
                "rc": false,
                "pvalue": 6.48e-06,
                "lflank": "CCGGCGGGGC",
                "match": "GGGGTATAAAG",
                "rflank": "GGGGCGG"
              }, {
                "seq": 20,
                "pos": 4,
                "rc": false,
                "pvalue": 6.48e-06,
                "lflank": "CAGA",
                "match": "GGGGTATAAAG",
                "rflank": "GTTCCGACCA"
              }, {
                "seq": 12,
                "pos": 18,
                "rc": false,
                "pvalue": 6.48e-06,
                "lflank": "CACCAGAGCT",
                "match": "GGGATATATAA",
                "rflank": "AGAAGGTTCT"
              }, {
                "seq": 15,
                "pos": 21,
                "rc": false,
                "pvalue": 1.38e-05,
                "lflank": "AATCACTGAG",
                "match": "GATGTATAAAA",
                "rflank": "GTCCCAGGGA"
              }, {
                "seq": 11,
                "pos": 16,
                "rc": false,
                "pvalue": 1.38e-05,
                "lflank": "CACTATTGAA",
                "match": "GATGTATAAAA",
                "rflank": "TTTCATTTGC"
              }, {
                "seq": 0,
                "pos": 39,
                "rc": false,
                "pvalue": 1.41e-05,
                "lflank": "CCTCGGGACG",
                "match": "TGGGTATATAA",
                "rflank": ""
              }, {
                "seq": 6,
                "pos": 15,
                "rc": false,
                "pvalue": 1.61e-05,
                "lflank": "CCCACTACTT",
                "match": "AGAGTATAAAA",
                "rflank": "TCATTCTGAG"
              }, {
                "seq": 22,
                "pos": 2,
                "rc": false,
                "pvalue": 1.95e-05,
                "lflank": "GA",
                "match": "GACATATAAAA",
                "rflank": "GCCAACATCC"
              }, {
                "seq": 14,
                "pos": 17,
                "rc": false,
                "pvalue": 1.95e-05,
                "lflank": "CCCACCAGCA",
                "match": "AGGATATATAA",
                "rflank": "AAGCTCAGGA"
              }, {
                "seq": 18,
                "pos": 37,
                "rc": false,
                "pvalue": 2.16e-05,
                "lflank": "CGTGGTCGCG",
                "match": "GGGGTATAACA",
                "rflank": "GC"
              }, {
                "seq": 29,
                "pos": 30,
                "rc": false,
                "pvalue": 3.04e-05,
                "lflank": "GCTGCCGGTG",
                "match": "AGCGTATAAAG",
                "rflank": "GCCCTGGCG"
              }, {
                "seq": 4,
                "pos": 12,
                "rc": false,
                "pvalue": 3.04e-05,
                "lflank": "CAGGTCTAAG",
                "match": "AGCATATATAA",
                "rflank": "CTTGGAGTCC"
              }, {
                "seq": 5,
                "pos": 0,
                "rc": false,
                "pvalue": 3.67e-05,
                "lflank": "",
                "match": "AACGTATATAA",
                "rflank": "ATGGTCCTGT"
              }, {
                "seq": 1,
                "pos": 27,
                "rc": false,
                "pvalue": 3.93e-05,
                "lflank": "AGTCACAAGT",
                "match": "GAGTTATAAAA",
                "rflank": "GGGTCGCACG"
              }, {
                "seq": 7,
                "pos": 4,
                "rc": false,
                "pvalue": 5.65e-05,
                "lflank": "TCAG",
                "match": "AGTATATATAA",
                "rflank": "ATGTTCCTGT"
              }, {
                "seq": 3,
                "pos": 14,
                "rc": false,
                "pvalue": 6.24e-05,
                "lflank": "CCCAGGTTTC",
                "match": "TGAGTATATAA",
                "rflank": "TCGCCGCACC"
              }, {
                "seq": 16,
                "pos": 22,
                "rc": false,
                "pvalue": 7.15e-05,
                "lflank": "AGTTTCAGTT",
                "match": "GGCATCTAAAA",
                "rflank": "attatataac"
              }, {
                "seq": 27,
                "pos": 36,
                "rc": false,
                "pvalue": 1.39e-04,
                "lflank": "TGCCTGGGTC",
                "match": "CAGGTATAAAG",
                "rflank": "GCT"
              }, {
                "seq": 26,
                "pos": 37,
                "rc": false,
                "pvalue": 1.39e-04,
                "lflank": "TGCCTGGGCC",
                "match": "CAGGTATAAAG",
                "rflank": "GC"
              }, {
                "seq": 17,
                "pos": 2,
                "rc": false,
                "pvalue": 4.81e-04,
                "lflank": "ga",
                "match": "TGGTTTTATAA",
                "rflank": "ggggcctcac"
              }, {
                "seq": 8,
                "pos": 13,
                "rc": false,
                "pvalue": 8.57e-04,
                "lflank": "TATAACTCAG",
                "match": "GTTGGATAAAA",
                "rflank": "TAATTTGTAC"
              }, {
                "seq": 10,
                "pos": 7,
                "rc": false,
                "pvalue": 1.47e-03,
                "lflank": "aaactta",
                "match": "AAACTCTATAA",
                "rflank": "acttaaaact"
              }, {
                "seq": 2,
                "pos": 26,
                "rc": false,
                "pvalue": 2.64e-03,
                "lflank": "GGTGGGGGTG",
                "match": "GGGGTTTCACT",
                "rflank": "GGTCCACTAT"
              }
            ]
          }
        ],
        "scan": [
          {
            "pvalue": 5.63e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 39,
                "rc": false,
                "pvalue": 1.41e-05
              }
            ]
          }, {
            "pvalue": 1.57e-03,
            "sites": [
              {
                "motif": 0,
                "pos": 27,
                "rc": false,
                "pvalue": 3.93e-05
              }
            ]
          }, {
            "pvalue": 1.00e-01,
            "sites": []
          }, {
            "pvalue": 2.49e-03,
            "sites": [
              {
                "motif": 0,
                "pos": 14,
                "rc": false,
                "pvalue": 6.24e-05
              }
            ]
          }, {
            "pvalue": 1.22e-03,
            "sites": [
              {
                "motif": 0,
                "pos": 12,
                "rc": false,
                "pvalue": 3.04e-05
              }
            ]
          }, {
            "pvalue": 1.47e-03,
            "sites": [
              {
                "motif": 0,
                "pos": 0,
                "rc": false,
                "pvalue": 3.67e-05
              }
            ]
          }, {
            "pvalue": 6.45e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 15,
                "rc": false,
                "pvalue": 1.61e-05
              }
            ]
          }, {
            "pvalue": 2.26e-03,
            "sites": [
              {
                "motif": 0,
                "pos": 4,
                "rc": false,
                "pvalue": 5.65e-05
              }
            ]
          }, {
            "pvalue": 3.37e-02,
            "sites": []
          }, {
            "pvalue": 1.95e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 13,
                "rc": false,
                "pvalue": 4.86e-06
              }
            ]
          }, {
            "pvalue": 5.73e-02,
            "sites": []
          }, {
            "pvalue": 5.52e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 16,
                "rc": false,
                "pvalue": 1.38e-05
              }
            ]
          }, {
            "pvalue": 2.59e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 18,
                "rc": false,
                "pvalue": 6.48e-06
              }
            ]
          }, {
            "pvalue": 1.10e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 13,
                "rc": false,
                "pvalue": 2.74e-06
              }
            ]
          }, {
            "pvalue": 7.78e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 17,
                "rc": false,
                "pvalue": 1.95e-05
              }
            ]
          }, {
            "pvalue": 5.52e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 21,
                "rc": false,
                "pvalue": 1.38e-05
              }
            ]
          }, {
            "pvalue": 2.85e-03,
            "sites": [
              {
                "motif": 0,
                "pos": 22,
                "rc": false,
                "pvalue": 7.15e-05
              }
            ]
          }, {
            "pvalue": 1.90e-02,
            "sites": []
          }, {
            "pvalue": 8.63e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 37,
                "rc": false,
                "pvalue": 2.16e-05
              }
            ]
          }, {
            "pvalue": 1.95e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 9,
                "rc": false,
                "pvalue": 4.86e-06
              }
            ]
          }, {
            "pvalue": 2.59e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 4,
                "rc": false,
                "pvalue": 6.48e-06
              }
            ]
          }, {
            "pvalue": 1.95e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 7,
                "rc": false,
                "pvalue": 4.86e-06
              }
            ]
          }, {
            "pvalue": 7.78e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 2,
                "rc": false,
                "pvalue": 1.95e-05
              }
            ]
          }, {
            "pvalue": 8.89e-05,
            "sites": [
              {
                "motif": 0,
                "pos": 15,
                "rc": false,
                "pvalue": 2.22e-06
              }
            ]
          }, {
            "pvalue": 1.80e-05,
            "sites": [
              {
                "motif": 0,
                "pos": 12,
                "rc": false,
                "pvalue": 4.51e-07
              }
            ]
          }, {
            "pvalue": 1.95e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 36,
                "rc": false,
                "pvalue": 4.86e-06
              }
            ]
          }, {
            "pvalue": 5.54e-03,
            "sites": []
          }, {
            "pvalue": 5.54e-03,
            "sites": []
          }, {
            "pvalue": 2.59e-04,
            "sites": [
              {
                "motif": 0,
                "pos": 32,
                "rc": false,
                "pvalue": 6.48e-06
              }
            ]
          }, {
            "pvalue": 1.22e-03,
            "sites": [
              {
                "motif": 0,
                "pos": 30,
                "rc": false,
                "pvalue": 3.04e-05
              }
            ]
          }
        ]
      };
    </script>
    <script>
var site_url = "http://meme-suite.org";
</script>
    <script>

/*
 * $
 *
 * Shorthand function for getElementById
 */
function $(el) {
  return document.getElementById(el);
}


/*
 * See http://stackoverflow.com/a/5450113/66387
 * Does string multiplication like the perl x operator.
 */
function string_mult(pattern, count) {
    if (count < 1) return '';
    var result = '';
    while (count > 1) {
        if (count & 1) result += pattern;
        count >>= 1, pattern += pattern;
    }
    return result + pattern;
}

/*
 * See http://stackoverflow.com/questions/814613/how-to-read-get-data-from-a-url-using-javascript
 * Slightly modified with information from
 * https://developer.mozilla.org/en/DOM/window.location
 */
function parse_params() {
  "use strict";
  var search, queryStart, queryEnd, query, params, nvPairs, i, nv, n, v;
  search = window.location.search;
  queryStart = search.indexOf("?") + 1;
  queryEnd   = search.indexOf("#") + 1 || search.length + 1;
  query      = search.slice(queryStart, queryEnd - 1);

  if (query === search || query === "") return {};

  params  = {};
  nvPairs = query.replace(/\+/g, " ").split("&");

  for (i = 0; i < nvPairs.length; i++) {
    nv = nvPairs[i].split("=");
    n  = decodeURIComponent(nv[0]);
    v  = decodeURIComponent(nv[1]);
    // allow a name to be used multiple times
    // storing each value in the array
    if (!(n in params)) {
      params[n] = [];
    }
    params[n].push(nv.length === 2 ? v : null);
  }
  return params;
}

/*
 * coords
 *
 * Calculates the x and y offset of an element.
 * From http://www.quirksmode.org/js/findpos.html
 * with alterations to take into account scrolling regions
 */
function coords(elem) {
  var myX = myY = 0;
  if (elem.getBoundingClientRect) {
    var rect;
    rect = elem.getBoundingClientRect();
    myX = rect.left + ((typeof window.pageXOffset !== "undefined") ?
        window.pageXOffset : document.body.scrollLeft);
    myY = rect.top + ((typeof window.pageYOffset !== "undefined") ?
        window.pageYOffset : document.body.scrollTop);
  } else {
    // this fall back doesn't properly handle absolutely positioned elements
    // inside a scrollable box
    var node;
    if (elem.offsetParent) {
      // subtract all scrolling
      node = elem;
      do {
        myX -= node.scrollLeft ? node.scrollLeft : 0;
        myY -= node.scrollTop ? node.scrollTop : 0;
      } while (node = node.parentNode);
      // this will include the page scrolling (which is unwanted) so add it back on
      myX += (typeof window.pageXOffset !== "undefined") ? window.pageXOffset : document.body.scrollLeft;
      myY += (typeof window.pageYOffset !== "undefined") ? window.pageYOffset : document.body.scrollTop;
      // sum up offsets
      node = elem;
      do {
        myX += node.offsetLeft;
        myY += node.offsetTop;
      } while (node = node.offsetParent);
    }
  }
  return [myX, myY];
}

/*
 * position_popup
 *
 * Positions a popup relative to an anchor element.
 *
 * The avaliable positions are:
 * 0 - Centered below the anchor.
 */
function position_popup(anchor, popup, position) {
  "use strict";
  var a_x, a_y, a_w, a_h, p_x, p_y, p_w, p_h;
  var a_xy, spacer, margin, scrollbar, page_w;
  // define constants
  spacer = 5;
  margin = 15;
  scrollbar = 15;
  // define the positions and widths
  a_xy = coords(anchor);
  a_x = a_xy[0];
  a_y = a_xy[1];
  a_w = anchor.offsetWidth;
  a_h = anchor.offsetHeight;
  p_w = popup.offsetWidth;
  p_h = popup.offsetHeight;
  page_w = null;
  if (window.innerWidth) {
    page_w = window.innerWidth;
  } else if (document.body) {
    page_w = document.body.clientWidth;
  }
  // check the position type is defined
  if (typeof position !== "number") {
    position = 0;
  }
  // calculate the popup position
  switch (position) {
    case 1:
      p_x = a_x + a_w + spacer;
      p_y = a_y + (a_h / 2) - (p_h / 2);
      break;
    case 0:
    default:
      p_x = a_x + (a_w / 2) - (p_w / 2);
      p_y = a_y + a_h + spacer;
      break;
  }
  // constrain the popup position
  if (p_x < margin) {
    p_x = margin;
  } else if (page_w != null && (p_x + p_w) > (page_w - margin - scrollbar)) {
    p_x = page_w - margin - scrollbar - p_w;
  }
  if (p_y < margin) {
    p_y = margin;
  }
  // position the popup
  popup.style.left = p_x + "px";
  popup.style.top = p_y + "px";
}

function lookup_help_popup(popup_id) {
  var _body, pop, info;
  pop = document.getElementById(popup_id);
  if (pop == null) {
    _body = document.getElementsByTagName("body")[0];
    pop = document.createElement("div");
    pop.className = "pop_content";
    pop.id = popup_id;
    pop.style.backgroundColor = "#FFC";
    pop.style.borderColor = "black";
    info = document.createElement("p");
    info.style.fontWeight = "bold";
    info.appendChild(document.createTextNode("Error: No popup for topic \"" + popup_id + "\"."));
    pop.appendChild(info);
    // this might cause problems with the menu, but as this only happens
    // when something is already wrong I don't think that's too much of a problem
    _body.insertBefore(pop, _body.firstChild);
  }
  if (document.getElementsByTagName('body')[0].hasAttribute("data-autobtns")) {
    if (!/\bauto_buttons\b/.test(pop.className)) {
      pop.className += " auto_buttons";
      var back_btn_sec = document.createElement("div");
      back_btn_sec.className = "nested_only pop_back_sec";
      var back_btn = document.createElement("span");
      back_btn.className = "pop_back";
      back_btn.appendChild(document.createTextNode("<< back"));
      back_btn.addEventListener("click", function(e) {
        help_return();
      }, false);
      back_btn_sec.appendChild(back_btn);
      pop.insertBefore(back_btn_sec, pop.firstChild);
      var close_btn_sec = document.createElement("div");
      close_btn_sec.className = "pop_close_sec";
      var close_btn = document.createElement("span");
      close_btn.className = "pop_close";
      close_btn.appendChild(document.createTextNode("close"));
      close_btn.addEventListener("click", function(e) {
        help_popup();
      }, false);
      close_btn_sec.appendChild(close_btn);
      pop.appendChild(close_btn_sec);
    }
  }
  return pop;
}

/*
 * help_popup
 *
 * Moves around help pop-ups so they appear
 * below an activator.
 */
function help_popup(activator, popup_id) {
  "use strict";
  var pop;
  // set default values
  if (typeof help_popup.popup === "undefined") {
    help_popup.popup = [];
  }
  if (typeof help_popup.activator === "undefined") {
    help_popup.activator = null;
  }
  var last_pop = (help_popup.popup.length > 0 ? help_popup.popup[help_popup.popup.length - 1] : null);
  if (typeof(activator) == "undefined") { // no activator so hide
    if (last_pop != null) {
      last_pop.style.display = 'none';
      help_popup.popup = [];
    }
    return;
  }
  pop = lookup_help_popup(popup_id);
  if (pop == last_pop) {
    if (activator == help_popup.activator) {
      //hide popup (as we've already shown it for the current help button)
      last_pop.style.display = 'none';
      help_popup.popup = [];
      return; // toggling complete!
    }
  } else if (last_pop != null) {
    //activating different popup so hide current one
    last_pop.style.display = 'none';
  }
  help_popup.popup = [pop];
  help_popup.activator = activator;
  toggle_class(pop, "nested", false);
  //must make the popup visible to measure it or it has zero width
  pop.style.display = 'block';
  position_popup(activator, pop);
}

/*
 * help_refine
 * 
 * Intended for links within a help popup. Stores a stack of state so
 * you can go back.
 */
function help_refine(popup_id) {
  if (help_popup.popup == null || help_popup.popup.length == 0 || help_popup.activator == null) {
    throw new Error("Can not refine a help popup when one is not shown!");
  }
  var pop = lookup_help_popup(popup_id);
  var last_pop = help_popup.popup[help_popup.popup.length - 1];
  if (pop == last_pop) return; // slightly odd, but no real cause for alarm
  help_popup.popup.push(pop);
  toggle_class(pop, "nested", true);
  last_pop.style.display = "none";
  //must make the popup visible to measure it or it has zero width
  pop.style.display = "block";
  position_popup(help_popup.activator, pop);
}

/*
 * help_return
 * 
 * Intended for links within a help popup. Stores a stack of state so
 * you can go back.
 */
function help_return() {
  if (help_popup.popup == null || help_popup.popup.length == 0 || help_popup.activator == null) {
    throw new Error("Can not return to a earlier help popup when one is not shown!");
  }
  var last_pop = help_popup.popup.pop();
  last_pop.style.display = "none";
  var pop = (help_popup.popup.length > 0 ? help_popup.popup[help_popup.popup.length - 1] : null);
  if (pop != null) {
    toggle_class(pop, "nested", help_popup.popup.length > 1);
    pop.style.display = "block";
    position_popup(help_popup.activator, pop);
  } else {
    help_popup.activator = null;
  }
}

/*
 * update_scroll_pad
 *
 * Creates padding at the bottom of the page to allow
 * scrolling of anything into view.
 */
function update_scroll_pad() {
  var page, pad;
  page = (document.compatMode === "CSS1Compat") ? document.documentElement : document.body;
  pad = $("scrollpad");
  if (pad === null) {
    pad = document.createElement("div");
    pad.id = 'scrollpad';
    document.getElementsByTagName('body')[0].appendChild(pad);
  }
  pad.style.height = Math.abs(page.clientHeight - 100) + "px";
}

function substitute_classes(node, remove, add) {
  "use strict";
  var list, all, i, cls, classes;
  list = node.className.split(/\s+/);
  all = {};
  for (i = 0; i < list.length; i++) {
    if (list[i].length > 0) all[list[i]] = true;
  }
  for (i = 0; i < remove.length; i++) {
    if (all.hasOwnProperty(remove[i])) {
      delete all[remove[i]];
    }
  }
  for (i = 0; i < add.length; i++) {
    all[add[i]] = true;
  }
  classes = "";
  for (cls in all) {
    classes += cls + " ";
  }
  node.className = classes;
}

/*
 * toggle_class
 *
 * Adds or removes a class from the node. If the parameter 'enabled' is not 
 * passed then the existence of the class will be toggled, otherwise it will be
 * included if enabled is true.
 */
function toggle_class(node, cls, enabled) {
  var classes = node.className;
  var list = classes.replace(/^\s+/, '').replace(/\s+$/, '').split(/\s+/);
  var found = false;
  for (var i = 0; i < list.length; i++) {
    if (list[i] == cls) {
      list.splice(i, 1);
      i--;
      found = true;
    }
  }
  if (typeof enabled == "undefined") {
    if (!found) list.push(cls);
  } else {
    if (enabled) list.push(cls);
  }
  node.className = list.join(" ");
}

/*
 * find_child
 *
 * Searches child nodes in depth first order and returns the first it finds
 * with the className specified.
 * TODO replace with querySelector
 */
function find_child(node, className) {
  var pattern;
  if (node == null || typeof node !== "object") {
    return null;
  }
  if (typeof className === "string") {
    pattern = new RegExp("\\b" + className + "\\b");
  } else {
    pattern = className;
  }
  if (node.nodeType == Node.ELEMENT_NODE && 
      pattern.test(node.className)) {
    return node;
  } else {
    var result = null;
    for (var i = 0; i < node.childNodes.length; i++) {
      result = find_child(node.childNodes[i], pattern);
      if (result != null) break;
    }
    return result;
  }
}

/*
 * find_parent
 *
 * Searches parent nodes outwards from the node and returns the first it finds
 * with the className specified.
 */
function find_parent(node, className) {
  var pattern;
  pattern = new RegExp("\\b" + className + "\\b");
  do {
    if (node.nodeType == Node.ELEMENT_NODE && 
        pattern.test(node.className)) {
      return node;
    }
  } while (node = node.parentNode);
  return null;
}

/*
 * find_parent_tag
 *
 * Searches parent nodes outwards from the node and returns the first it finds
 * with the tag name specified. HTML tags should be specified in upper case.
 */
function find_parent_tag(node, tag_name) {
  do {
    if (node.nodeType == Node.ELEMENT_NODE && node.tagName == tag_name) {
      return node;
    }
  } while (node = node.parentNode);
  return null;
}

/*
 * __toggle_help
 *
 * Uses the 'topic' property of the this object to
 * toggle display of a help topic.
 *
 * This function is not intended to be called directly.
 */
function __toggle_help(e) {
  if (!e) e = window.event;
  if (e.type === "keydown") {
    if (e.keyCode !== 13 && e.keyCode !== 32) {
      return;
    }
    // stop a submit or something like that
    e.preventDefault();
  }

  help_popup(this, this.getAttribute("data-topic"));
}

function setup_help_button(button) {
  "use strict";
  var topic;
  if (button.hasAttribute("data-topic")) {
    topic = button.getAttribute("data-topic");
    if (document.getElementById(topic) != null) {
      button.tabIndex = "0"; // make keyboard selectable
      button.addEventListener("click", function() {
        help_popup(button, topic);
      }, false);
      button.addEventListener("keydown", function(e) {
        // toggle only on Enter or Spacebar, let other keys do their thing
        if (e.keyCode !== 13 && e.keyCode !== 32) return;
        // stop a submit or something like that
        e.preventDefault();
        help_popup(button, topic);
      }, false);
    } else {
      button.style.visibility = "hidden";
    }
  }
  button.className += " active";
}

/*
 * help_button
 *
 * Makes a help button for the passed topic.
 */
function help_button(topic) {
  var btn = document.createElement("div");
  btn.className = "help";
  btn.setAttribute("data-topic", topic);
  setup_help_button(btn);
  return btn;
}

/*
 * prepare_download
 *
 * Sets the attributes of a link to setup a file download using the given content.
 * If no link is provided then create one and click it.
 */
function prepare_download(content, mimetype, filename, link) {
  "use strict";
  // if no link is provided then create one and click it
  var click_link = false;
  if (!link) {
    link = document.createElement("a");
    click_link = true;
  }
  try {
    // Use a BLOB to convert the text into a data URL.
    // We could do this manually with a base 64 conversion.
    // This will only be supported on modern browsers,
    // hence the try block.
    var blob = new Blob([content], {type: mimetype});
    var reader = new FileReader();
    reader.onloadend = function() {
      // If we're lucky the browser will also support the download
      // attribute which will let us suggest a file name to save the link.
      // Otherwise it is likely that the filename will be unintelligible. 
      link.setAttribute("download", filename);
      link.href = reader.result;
      if (click_link) {
        // must add the link to click it
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
      }
    }
    reader.readAsDataURL(blob);
  } catch (error) {
    if (console && console.log) console.log(error);
    // probably an old browser
    link.href = "";
    link.visible = false;
  }
}

/*
 * add_cell
 *
 * Add a cell to the table row.
 */
function add_cell(row, node, cls, click_action) {
  var cell = row.insertCell(row.cells.length);
  if (node) cell.appendChild(node);
  if (cls && cls !== "") cell.className = cls;
  if (click_action) cell.addEventListener("click", click_action, false);
}

/*
 * add_header_cell
 *
 * Add a header cell to the table row.
 */
function add_header_cell(row, node, help_topic, cls, colspan) {
  var th = document.createElement("th");
  if (node) th.appendChild(node);
  if (help_topic && help_topic !== "") th.appendChild(help_button(help_topic));
  if (cls && cls !== "") th.className = cls;
  if (typeof colspan == "number" && colspan > 1) th.colSpan = colspan;
  row.appendChild(th);
}

/*
 * add_text_cell
 *
 * Add a text cell to the table row.
 */
function add_text_cell(row, text, cls, action) {
  var node = null;
  if (typeof(text) != 'undefined') node = document.createTextNode(text);
  add_cell(row, node, cls, action);
}

/*
 * add_text_header_cell
 *
 * Add a text header cell to the table row.
 */
function add_text_header_cell(row, text, help_topic, cls, action, colspan) {
  var node = null;
  if (typeof(text) != 'undefined') {
    var nbsp = (help_topic ? "\u00A0" : "");
    var str = "" + text;
    var parts = str.split(/\n/);
    if (parts.length === 1) {
      if (action) {
        node = document.createElement("span");
        node.appendChild(document.createTextNode(str + nbsp));
      } else {
        node = document.createTextNode(str + nbsp);
      }
    } else {
      node = document.createElement("span");
      for (var i = 0; i < parts.length; i++) {
        if (i !== 0) {
          node.appendChild(document.createElement("br"));
        }
        node.appendChild(document.createTextNode(parts[i]));
      }
    }
    if (action) {
      node.addEventListener("click", action, false);
      node.style.cursor = "pointer";
    }
  }
  add_header_cell(row, node, help_topic, cls, colspan);
}

function setup_help() {
  "use strict";
  var help_buttons, i;
  help_buttons = document.querySelectorAll(".help:not(.active)");
  for (i = 0; i < help_buttons.length; i++) {
    setup_help_button(help_buttons[i]);
  }
}

function setup_scrollpad() {
  "use strict";
  if (document.getElementsByTagName('body')[0].hasAttribute("data-scrollpad") && document.getElementById("scrollpad") == null) {
    window.addEventListener("resize", update_scroll_pad, false);
    update_scroll_pad();
  }
}

// anon function to avoid polluting global scope
(function() {
  "use strict";
  window.addEventListener("load", function load(evt) {
    window.removeEventListener("load", load, false);
    setup_help();
    setup_scrollpad();
  }, false);
})();

/*
 *  make_link
 *
 *  Creates a text node and if a URL is specified it surrounds it with a link.
 *  If the URL doesn't begin with "http://" it automatically adds it, as
 *  relative links don't make much sense in this context.
 */
function make_link(text, url) {
  var textNode = null;
  var link = null;
  if (typeof text !== "undefined" && text !== null) textNode = document.createTextNode(text);
  if (typeof url === "string") {
    if (url.indexOf("//") == -1) {
      url = "http://" + url;
    }
    link = document.createElement('a');
    link.href = url;
    if (textNode) link.appendChild(textNode);
    return link;
  }
  return textNode;
}
</script>
    <script>
      // 
      // return true if any part of the passed element is visible in the viewport
      //
      function element_in_viewport(elem) {
        var rect;
        try {
          rect = elem.getBoundingClientRect();
        } catch (e) {
          return false;
        }
        return (
            rect.top < (window.innerHeight || document.body.clientHeight) &&
            rect.bottom > 0 &&
            rect.left < (window.innerWidth || document.body.clientWidth) &&
            rect.right > 0
            );
      }

      //
      // Functions to delay a drawing task until it is required or it would not lag the display to do so
      //

      // a list of items still to be drawn
      var drawable_list = [];
      // the delay between drawing objects that are not currently visible
      var draw_delay = 1;
      // the delay after a user interaction
      var user_delay = 300;
      // the delay after a user has stopped scrolling and is viewing the stuff drawn on the current page
      var stop_delay = 300;
      // the timer handle; allows resetting of the timer after user interactions
      var draw_timer = null;

      //
      // Drawable
      //
      // elem - a page element which defines the position on the page that drawing is to be done
      // task - an object with the method run which takes care of painting the object
      //
      var Drawable = function(elem, task) {
        this.elem = elem;
        this.task = task;
      }

      //
      // Drawable.is_visible
      //
      // Determines if the element is visible in the viewport
      //
      Drawable.prototype.is_visible = function() {
        return element_in_viewport(this.elem);
      }

      //
      // Drawable.run
      //
      // Run the task held by the drawable
      Drawable.prototype.run = function() {
        if (this.task) this.task.run();
        this.task = null;
      }

      //
      // Drawable.run
      //
      // Run the task iff visible
      // returns true if the task ran or has already run
      Drawable.prototype.run_visible = function() {
        if (this.task) {
          if (element_in_viewport(this.elem)) {
            this.task.run();
            this.task = null;
            return true;
          }
          return false;
        } else {
          return true;
        }
      }

      //
      // draw_on_screen
      //
      // Checks each drawable object and draws those on screen.
      //
      function draw_on_screen() {
        var found = false;
        for (var i = 0; i < drawable_list.length; i++) {
          if (drawable_list[i].run_visible()) {
            drawable_list.splice(i--, 1);
            found = true;
          }
        }
        return found;
      }

      //
      // process_draw_tasks
      //
      // Called on a delay to process the next avaliable
      // draw task.
      //
      function process_draw_tasks() {
        var delay = draw_delay;
        draw_timer = null;
        if (drawable_list.length == 0) return; //no more tasks
        if (draw_on_screen()) {
          delay = stop_delay; //give the user a chance to scroll
        } else {
          //get next task
          var drawable = drawable_list.shift();
          drawable.task.run();
        }
        //allow UI updates between tasks
        draw_timer = window.setTimeout("process_draw_tasks()", delay);
      }

      //
      // delayed_process_draw_tasks
      //
      // Call process_draw_tasks after a short delay.
      // The delay serves to group multiple redundant events.       
      // Should be set as event handler for onscroll and onresize.
      //
      function delayed_process_draw_tasks() {
        //reset the timer
        if (drawable_list.length > 0) { 
          if (draw_timer != null) clearTimeout(draw_timer);
          draw_timer = window.setTimeout("process_draw_tasks()", user_delay);
        }
      }

      //
      // add_draw_task
      //
      // Add a drawing task to be called immediately if it is
      // visible, or to be called on a delay to reduce stuttering
      // effect on the web browser.
      function add_draw_task(elem, task) {
        drawable = new Drawable(elem, task);
        if (drawable.is_visible()) {
          task.run();
        } else {
          drawable_list.push(drawable);
          //reset timer
          if (draw_timer != null) clearTimeout(draw_timer);
          draw_timer = window.setTimeout("process_draw_tasks()", user_delay);
        }
      }

</script>
    <script>
//======================================================================
// start Alphabet object
//======================================================================
var Alphabet = function(alphabet, background) {
  "use strict";
  var i, j, sym, aliases, complement, comp_e_sym, ambigs, generate_background;
  generate_background = (background == null);
  if (generate_background) {
    background = [];
    for (i = 0; i < alphabet.ncore; i++) background[i] = 1.0 / alphabet.ncore;
  } else if (alphabet.ncore != background.length) {
    throw new Error("The background length does not match the alphabet length.");
  }
  this.name = alphabet.name;
  this.like = (alphabet.like != null ? alphabet.like.toUpperCase() : null);
  this.ncore = alphabet.ncore;
  this.symbols = alphabet.symbols;
  this.background = background;
  this.genbg = generate_background;
  this.encode = {};
  this.encode2core = {};
  this.complement = {};
  // check if all symbols are same case
  var seen_uc = false;
  var seen_lc = false;
  var check_case = function (syms) {
    var s, sym;
    if (typeof syms === "string") {
      for (s = 0; s < syms.length; s++) {
        sym = syms.charAt(s);
        if (sym >= 'a' && sym <= 'z') seen_lc = true;
        else if (sym >= 'A' && sym <= 'Z') seen_uc = true;
      }
    }
  };
  for (i = 0; i < this.symbols.length; i++) {
    check_case(this.symbols[i].symbol);
    check_case(this.symbols[i].aliases);
  }
  // now map symbols to indexes
  var update_array = function(array, syms, index) {
    var s, sym;
    if (typeof syms === "string") {
      for (s = 0; s < syms.length; s++) {
        sym = syms.charAt(s);
        array[sym] = index;
        // when only a single case is used, then encode as case insensitive
        if (seen_uc != seen_lc) {
          if (sym >= 'a' && sym <= 'z') {
            array[sym.toUpperCase()] = index;
          } else if (sym >= 'A' && sym <= 'Z') {
            array[sym.toLowerCase()] = index;
          }
        }
      }
    }
  }
  // map core symbols to index
  for (i = 0; i < this.ncore; i++) {
    update_array(this.encode2core, this.symbols[i].symbol, i);
    update_array(this.encode, this.symbols[i].symbol, i);
    update_array(this.encode2core, this.symbols[i].aliases, i);
    update_array(this.encode, this.symbols[i].aliases, i);
  }
  // map ambigous symbols to index
  ambigs = {};
  for (i = this.ncore; i < this.symbols.length; i++) {
    update_array(this.encode, this.symbols[i].symbol, i);
    update_array(this.encode, this.symbols[i].aliases, i);
    ambigs[this.symbols[i].equals] = i;
  }
  // determine complements
  for (i = 0; i < this.ncore; i++) {
    complement = this.symbols[i].complement;
    if (typeof complement === "string") {
      this.complement[i] = this.encode2core[complement];
    }
  }
  next_symbol:
  for (i = this.ncore; i < this.symbols.length; i++) {
    complement = "";
    for (j = 0; j < this.symbols[i].equals.length; j++) {
      comp_e_sym = this.complement[this.encode2core[this.symbols[i].equals.charAt(j)]];
      if (typeof comp_e_sym !== "number") continue next_symbol;
      complement += this.symbols[comp_e_sym].symbol;
    }
    complement = complement.split("").sort().join("");
    if (typeof ambigs[complement] === "number") {
      this.complement[i] = ambigs[complement];
    }
  }
  // determine case insensitivity
  this.case_insensitive = true;
  if (seen_uc == seen_lc) {
    // when there is a mixture of cases it probably won't
    // be case insensitive but we still need to check
    loop:
    for (i = 0; i < this.symbols.length; i++) {
      sym = this.symbols[i].symbol;
      if (sym >= 'A' && sym <= 'Z') {
        if (this.encode[sym.toLowerCase()] != i) {
          this.case_insensitive = false;
          break loop;
        }
      } else if (sym >= 'a' && sym <= 'z') {
        if (this.encode[sym.toUpperCase()] != i) {
          this.case_insensitive = false;
          break loop;
        }
      }
      aliases = this.symbols[i].aliases;
      if (aliases != null) {
        for (j = 0; j < aliases.length; j++) {
          sym = aliases.charAt(j);
          if (sym >= 'A' && sym <= 'Z') {
            if (this.encode[sym.toLowerCase()] != i) {
              this.case_insensitive = false;
              break loop;
            }
          } else if (sym >= 'a' && sym <= 'z') {
            if (this.encode[sym.toUpperCase()] != i) {
              this.case_insensitive = false;
              break loop;
            }
          }
        }
      }
    }
  }
  // normalise aliases to remove the prime symbol and eliminate
  // the alternate cases when the alphabet is case insensitive
  var seen, out;
  for (i = 0; i < this.symbols.length; i++) {
    sym = this.symbols[i].symbol;
    aliases = this.symbols[i].aliases;
    if (typeof aliases != "string") aliases = "";
    seen = {};
    out = [];
    if (this.case_insensitive) {
      sym = sym.toUpperCase();
      aliases = aliases.toUpperCase();
    }
    seen[sym] = true;
    for (j = 0; j < aliases.length; j++) {
      if (!seen[aliases.charAt(j)]) {
        seen[aliases.charAt(j)] = true;
        out.push(aliases.charAt(j));
      }
    }
    this.symbols[i].aliases = out.sort().join("");
  }
};
// return the name of the alphabet
Alphabet.prototype.get_alphabet_name = function() {
  return this.name;
};
// return if the alphabet can be complemented
Alphabet.prototype.has_complement = function() {
  return (typeof this.symbols[0].complement === "string");
};
// return true if an uppercase letter has the same meaning as the lowercase form
Alphabet.prototype.is_case_insensitive = function() {
  return this.case_insensitive;
};
// return the information content of an alphabet letter
Alphabet.prototype.get_ic = function() {
  return Math.log(this.ncore) / Math.LN2;
};
// return the count of the core alphabet symbols
Alphabet.prototype.get_size_core = function() {
  return this.ncore;
};
// return the count of all alphabet symbols
Alphabet.prototype.get_size_full = function() {
  return this.symbols.length;
};
// return the symbol for the given alphabet index
Alphabet.prototype.get_symbol = function(alph_index) {
  "use strict";
  if (alph_index < 0 || alph_index >= this.symbols.length) {
    throw new Error("Alphabet index out of bounds");
  }
  return this.symbols[alph_index].symbol;
};
// return the aliases for the given alphabet index
Alphabet.prototype.get_aliases = function(alph_index) {
  "use strict";
  if (alph_index < 0 || alph_index >= this.symbols.length) {
    throw new Error("Alphabet index out of bounds");
  }
  var sym_obj = this.symbols[alph_index];
  return (sym_obj.aliases != null ? sym_obj.aliases : "");
};
// return the name for the given alphabet index
Alphabet.prototype.get_name = function(alph_index) {
  "use strict";
  var sym;
  if (alph_index < 0 || alph_index >= this.symbols.length) {
    throw new Error("Alphabet index out of bounds");
  }
  sym = this.symbols[alph_index];
  return (typeof sym.name === "string" ? sym.name : sym.symbol);
};
// return the alphabet it is like or null
Alphabet.prototype.get_like = function() {
  "use strict";
  return this.like;
};
// return the index of the complement for the given alphabet index
Alphabet.prototype.get_complement = function(alph_index) {
  var comp_e_sym = this.complement[alph_index];
  if (typeof comp_e_sym === "number") {
    return comp_e_sym;
  } else {
    return -1;
  }
};
// return a string containing the core symbols
Alphabet.prototype.get_symbols = function() {
  "use strict";
  var i, core_symbols;
  core_symbols = "";
  for (i = 0; i < this.ncore; i++) {
    core_symbols += this.symbols[i].symbol;
  }
  return core_symbols;
};
// return if the background was not a uniform generated background
Alphabet.prototype.has_bg = function() {
  "use strict";
  return !this.genbg;
};
// get the background frequency for the index
Alphabet.prototype.get_bg_freq = function(alph_index) {
  "use strict";
  var freq, i, symbols;
  if (alph_index >= 0) {
    if (alph_index < this.ncore) {
      return this.background[alph_index];
    } else if (alph_index < this.symbols.length) {
      freq = 0;
      symbols = this.symbols[alph_index].equals;
      for (i = 0; i < symbols.length; i++) {
        freq += this.background[this.encode2core[symbols.charAt(i)]];
      }
      return freq;
    } 
  }
  throw new Error("The alphabet index is out of range.");
};
// get the colour of the index
Alphabet.prototype.get_colour = function(alph_index) {
  "use strict";
  if (alph_index < 0 || alph_index >= this.symbols.length) {
    throw new Error("BAD_ALPHABET_INDEX");
  }
  if (typeof this.symbols[alph_index].colour != "string") {
    return "black";
  }
  return "#" + this.symbols[alph_index].colour;
};
// get the rgb componets of the colour at the index
Alphabet.prototype.get_rgb = function(alph_index) {
  "use strict";
  if (alph_index < 0 || alph_index >= this.symbols.length) {
    throw new Error("BAD_ALPHABET_INDEX");
  }
  if (typeof this.symbols[alph_index].colour != "string") {
    return {"red": 0, "green": 0, "blue": 0};
  }
  var colour = this.symbols[alph_index].colour;
  var red = parseInt(colour.substr(0, 2), 16) / 255;
  var green = parseInt(colour.substr(2, 2), 16) / 255;
  var blue = parseInt(colour.substr(4, 2), 16) / 255;
  return {"red": red, "green": green, "blue": blue};
};
// convert a symbol into the index
Alphabet.prototype.get_index = function(letter) {
  "use strict";
  var alph_index;
  alph_index = this.encode[letter];
  if (typeof alph_index === "undefined") {
    return -1;
  }
  return alph_index;
};
// convert a symbol into the list of core indexes that it equals
Alphabet.prototype.get_indexes = function(letter) {
  "use strict";
  var alph_index, comprise_str, i, comprise_list;
  alph_index = this.encode[letter];
  if (typeof alph_index === "undefined") {
    throw new Error("Unknown letter");
  }
  comprise_str = this.symbols[alph_index].equals;
  comprise_list = [];
  if (typeof comprise_str == "string") {
    for (i = 0; i < comprise_str.length; i++) {
      comprise_list.push(this.encode2core[comprise_str.charAt(i)]);
    }
  } else {
    comprise_list.push(alph_index);
  }
  return comprise_list;
};
// check if a symbol is the primary way of representing the symbol in the alphabet
Alphabet.prototype.is_prime_symbol = function(letter) {
  var alph_index;
  alph_index = this.encode[letter];
  if (alph_index == null) return false;
  if (this.is_case_insensitive()) {
    return (this.symbols[alph_index].symbol.toUpperCase() == letter.toUpperCase());
  } else {
    return (this.symbols[alph_index].symbol == letter);
  }
};
// compare 2 alphabets
Alphabet.prototype.equals = function(other) {
  "use strict";
  var i, sym1, sym2;
  // first check that it's actually an alphabet object
  if (!(typeof other === "object" && other != null && other instanceof Alphabet)) {
    return false;
  }
  // second shortcircuit if it's the same object
  if (this === other) return true;
  // compare
  if (this.name !== other.name) return false;
  if (this.ncore !== other.ncore) return false;
  if (this.symbols.length !== other.symbols.length) return false;
  for (i = 0; i < this.symbols.length; i++) {
    sym1 = this.symbols[i];
    sym2 = other.symbols[i];
    if (sym1.symbol !== sym2.symbol) return false;
    if (sym1.aliases !== sym2.aliases) return false;
    if (sym1.name !== sym2.name) return false;
    if (typeof sym1.colour !== typeof sym2.colour || 
        (typeof sym1.colour === "string" && typeof sym2.colour === "string" &&
         parseInt(sym1.colour, 16) != parseInt(sym2.colour, 16))) {
      return false;
    }
    if (sym1.complement !== sym2.complement) return false;
    if (sym1.equals !== sym2.equals) return false;
  }
  return true;
};
Alphabet.prototype.check_core_subset = function(super_alph) {
  var complement_same = true;
  var seen_set = {};
  var sub_i, sub_symbol, super_i, super_symbol;
  for (sub_i = 0; sub_i < this.ncore; sub_i++) {
    sub_symbol = this.symbols[sub_i];
    super_i = super_alph.encode[sub_symbol.symbol]; 
    if (super_i == null) return 0;
    super_symbol = super_alph.symbols[super_i];
    if (seen_set[super_i]) return 0;
    seen_set[super_i] = true;
    // check complement
    if (sub_symbol.complement != null && super_symbol.complement != null) {
      if (super_alph.encode[sub_symbol.complement] != super_alph.encode[super_symbol.complement]) {
        complement_same = false;
      }
    } else if (sub_symbol.complement != null || super_symbol.complement != null) {
      complement_same = false;
    }
  }
  return (complement_same ? 1 : -1);
};
// convert a sequence to its reverse complement
Alphabet.prototype.invcomp_seq = function(seq) {
  "use strict";
  var syms, i, e_sym, comp_e_sym;
  if (!this.has_complement()) throw new Error("Alphabet must be complementable");
  syms = seq.split("");
  for (i = 0; i < syms.length; i++) {
    e_sym = this.encode[syms[i]];
    if (typeof e_sym === "undefined") {
      e_sym = this.ncore; // wildcard
    }
    comp_e_sym = this.complement[e_sym];
    if (typeof comp_e_sym === "undefined") {
      comp_e_sym = e_sym; // not complementable
    }
    syms[i] = this.symbols[comp_e_sym].symbol;
  }
  return syms.reverse().join("");
};
// convert the alphabet to the text version
Alphabet.prototype.as_text = function() {
  "use strict";
  function name_as_text(name) {
    var i, c, out;
    out = "\"";
    for (i = 0; i < name.length; i++) {
      c = name.charAt(i);
      if (c == "\"") {
        out += "\\\"";
      } else if (c == "/") {
        out += "\\/";
      } else if (c == "\\") {
        out += "\\\\";
      } else {
        out += c;
      }
    }
    out += "\"";
    return out;
  }
  function symbol_as_text(sym) {
    var out;
    out = sym.symbol;
    if (typeof sym.name === "string" && sym.name != sym.symbol) {
      out += " " + name_as_text(sym.name);
    }
    if (typeof sym.colour === "string") {
      out += " " + sym.colour;
    }
    return out;
  }
  var out, i, j, c, sym;
  out = "";
  // output core symbols with 2 way complements
  for (i = 0; i < this.ncore; i++) {
    c = this.complement[i];
    if (typeof c === "number" && i < c && this.complement[c] === i) {
      out += symbol_as_text(this.symbols[i]) + " ~ " + symbol_as_text(this.symbols[c]) + "\n";  
    }
  }
  // output core symbols with no complement
  for (i = 0; i < this.ncore; i++) {
    if (typeof this.complement[i] === "undefined") {
      out += symbol_as_text(this.symbols[i]) + "\n";
    }
  }
  // output ambiguous symbols that have comprising characters
  for (i = this.ncore; i < this.symbols.length; i++) {
    if (this.symbols[i].equals.length == 0) break;
    out += symbol_as_text(this.symbols[i]) + " = " + this.symbols[i].equals + "\n";
    if (typeof this.symbols[i].aliases === "string") {
      for (j = 0; j < this.symbols[i].aliases.length; j++) {
        if (this.symbols[i].aliases.charAt(j) == this.symbols[i].symbol) continue;
        out += this.symbols[i].aliases.charAt(j) + " = " + this.symbols[i].equals + "\n";
      }
    }
  }
  // output aliases of core symbols
  for (i = 0; i < this.ncore; i++) {
    if (typeof this.symbols[i].aliases === "string") {
      for (j = 0; j < this.symbols[i].aliases.length; j++) {
        if (this.symbols[i].aliases.charAt(j) == this.symbols[i].symbol) continue;
        out += this.symbols[i].aliases.charAt(j) + " = " + this.symbols[i].symbol + "\n";
      }
    }
  }
  // output gap symbols
  i = this.symbols.length - 1;
  if (this.symbols[i].equals.length == 0) {
    out += symbol_as_text(this.symbols[i]) + " =\n";
    if (typeof this.symbols[i].aliases === "string") {
      for (j = 0; j < this.symbols[i].aliases.length; j++) {
        if (this.symbols[i].aliases.charAt(j) == this.symbols[i].symbol) continue;
        out += this.symbols[i].aliases.charAt(j) + " =\n";
      }
    }
  }
  return out;
};
// output the alphabet as it appears in minimal MEME format
Alphabet.prototype.as_meme = function() {
  "use strict";
  function name_as_text(name) {
    var i, c, out;
    out = "\"";
    for (i = 0; i < name.length; i++) {
      c = name.charAt(i);
      if (c == "\"") {
        out += "\\\"";
      } else if (c == "/") {
        out += "\\/";
      } else if (c == "\\") {
        out += "\\\\";
      } else {
        out += c;
      }
    }
    out += "\"";
    return out;
  }
  if (this.equals(AlphStd.DNA)) {
    return "ALPHABET= ACGT\n";
  } else if (this.equals(AlphStd.PROTEIN)) {
    return "ALPHABET= ACDEFGHIKLMNPQRSTVWY\n";
  } else {
    return "ALPHABET" + 
      (this.name != null ? " " + name_as_text(this.name) : "") + 
      (this.like != null ? " " + this.like + "-LIKE" : "") + "\n" +
      this.as_text() + "END ALPHABET\n";
  }
};

// Returns a table showing all the letters in the alphabet
Alphabet.prototype.as_table = function() {
  "use strict";
  var i, j, row, th, td, aliases, equals, sym;
  var table = document.createElement("table");
  // create the core symbol header
  row = table.insertRow(table.rows.length);
  th = document.createElement("th");
  th.appendChild(document.createTextNode("Symbol(s)"));
  row.appendChild(th);
  th = document.createElement("th");
  th.appendChild(document.createTextNode("Name"));
  row.appendChild(th);
  th = document.createElement("th");
  if (this.has_complement()) {
    th.appendChild(document.createTextNode("Complement"));
  }
  row.appendChild(th);
  // list the core symbols
  for (i = 0; i < this.ncore; i++) {
    row = table.insertRow(table.rows.length);
    td = document.createElement("td");
    if (this.symbols[i].colour != null) {
      td.style.color = '#' + this.symbols[i].colour;
    }
    td.appendChild(document.createTextNode(this.symbols[i].symbol));
    aliases = this.get_aliases(i);
    if (aliases.length > 0) {
      td.appendChild(document.createTextNode(' ' + aliases.split('').join(' ')));
    }
    row.appendChild(td);
    td = document.createElement("td");
    if (this.symbols[i].name != null) {
      td.appendChild(document.createTextNode(this.symbols[i].name));
    }
    row.appendChild(td);
    td = document.createElement("td");
    if (this.symbols[i].complement != null) {
      td.style.color = this.get_colour(this.get_index(this.symbols[i].complement));
      td.appendChild(document.createTextNode(this.symbols[i].complement));
    }
    row.appendChild(td);
  }
  // create the ambiguous symbol header
  row = table.insertRow(table.rows.length);
  th = document.createElement("th");
  th.appendChild(document.createTextNode("Symbol(s)"));
  row.appendChild(th);
  th = document.createElement("th");
  th.appendChild(document.createTextNode("Name"));
  row.appendChild(th);
  th = document.createElement("th");
  th.appendChild(document.createTextNode("Matches"));
  row.appendChild(th);
  // list the ambiguous symbols
  for (i = this.ncore; i < this.symbols.length; i++) {
    row = table.insertRow(table.rows.length);
    td = document.createElement("td");
    if (this.symbols[i].colour != null) {
      td.style.color = '#' + this.symbols[i].colour;
    }
    td.appendChild(document.createTextNode(this.symbols[i].symbol));
    aliases = this.get_aliases(i);
    if (aliases.length > 0) {
      td.appendChild(document.createTextNode(' ' + aliases.split('').join(' ')));
    }
    row.appendChild(td);
    td = document.createElement("td");
    if (this.symbols[i].name != null) {
      td.appendChild(document.createTextNode(this.symbols[i].name));
    }
    row.appendChild(td);
    td = document.createElement("td");
    equals = this.symbols[i].equals.split('');
    for (j = 0; j < equals.length; j++) {
      if (j != 0) td.appendChild(document.createTextNode(' '));
      sym = document.createElement("span");
      sym.style.color = this.get_colour(this.get_index(equals[j]));
      sym.appendChild(document.createTextNode(equals[j]));
      td.appendChild(sym);
    }
    row.appendChild(td);
  }
  return table;
};

// returns a dictionary of the colours for EPS
Alphabet.prototype._as_eps_dict = function() {
  "use strict";
  var i, sym, rgb;
  var out = "/fullColourDict <<\n";
  for (i = 0; i < this.ncore; i++) {
    sym = this.get_symbol(i);
    sym = sym.replace(/\\/g, "\\\\");
    sym = sym.replace(/\(/g, "\\(");
    sym = sym.replace(/\)/g, "\\)");
    rgb = this.get_rgb(i);
    out += " (" + sym + ") [" + rgb.red.toFixed(4) + " " + rgb.green.toFixed(4) + " " + rgb.blue.toFixed(4) + "]\n";
  }
  out += ">> def\n";
  out += "/mutedColourDict <<\n";
  for (i = 0; i < this.ncore; i++) {
    sym = this.get_symbol(i);
    sym = sym.replace(/\\/g, "\\\\");
    sym = sym.replace(/\(/g, "\\(");
    sym = sym.replace(/\)/g, "\\)");
    rgb = Alphabet.lighten_colour(this.get_rgb(i));
    out += " (" + sym + ") [" + rgb.red.toFixed(4) + " " + rgb.green.toFixed(4) + " " + rgb.blue.toFixed(4) + "]\n";
  }
  out += ">> def\n";
  return out;
};

// return the alphabet name or a list of primary symbols
Alphabet.prototype.toString = function() {
  "use strict";
  if (this.name != null) {
    return this.name;
  } else {
    return this.get_symbols();
  }
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Helper functions
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Convert a colour specified in RGB colourspace values into LAB colourspace
Alphabet.rgb2lab = function(rgb) {
  "use strict";
  var xyzHelper, labHelper;
  // XYZ helper
  xyzHelper = function(value) {
    if (value > 0.0445) {
      value = (value + 0.055) / 1.055;
      value = Math.pow(value, 2.4);
    } else {
      value /= 12.92;
    }
    value *= 100;
    return value;
  };
  // lab helper
  labHelper = function(value) {
    if (value > 0.008856) {
      value = Math.pow(value, 1.0 / 3.0);
    } else {
      value = (7.787 * value) + (16.0 / 116.0);
    }
    return value;
  };
  // convert into XYZ colourspace
  var c1, c2, c3;
  if (typeof rgb == "number") {
    c1 = xyzHelper(((rgb >> 16) & 0xFF) / 255.0);
    c2 = xyzHelper(((rgb >> 8) & 0xFF) / 255.0);
    c3 = xyzHelper((rgb & 0xFF) / 255.0);
  } else {
    c1 = xyzHelper(rgb.red);
    c2 = xyzHelper(rgb.green);
    c3 = xyzHelper(rgb.blue);
  }
  var x = (c1 * 0.4124) + (c2 * 0.3576) + (c3 * 0.1805);
  var y = (c1 * 0.2126) + (c2 * 0.7152) + (c3 * 0.0722);
  var z = (c1 * 0.0193) + (c2 * 0.1192) + (c3 * 0.9505);
  // convert into Lab colourspace
  c1 = labHelper(x / 95.047);
  c2 = labHelper(y / 100.0);
  c3 = labHelper(z / 108.883);
  var l = (116.0 * c2) - 16;
  var a = 500.0 * (c1 - c2);
  var b = 200.0 * (c2 - c3);
  return {"l": l, "a": a, "b": b};
};

// Convert a colour specified in HSV colourspace into RGB colourspace
Alphabet.hsv2rgb = function(hue, sat, value, output_object) {
  // achromatic (grey)
  var r = value;
  var g = value;
  var b = value;
  if (sat != 0) {
    var h = hue / 60.0;
    var i = Math.floor(h);
    var f = h - i;
    var p = value * (1.0 - sat);
    var q = value * (1.0 - (sat * f));
    var t = value * (1.0 - (sat * (1.0 - f)));
    if (i == 0) {
      r = value;
      g = t;
      b = p;
    } else if (i == 1) {
      r = q;
      g = value;
      b = p;
    } else if (i == 2) {
      r = p;
      g = value;
      b = t;
    } else if (i == 3) {
      r = p;
      g = q;
      b = value;
    } else if (i == 4) {
      r = t;
      g = p;
      b = value;
    } else {
      r = value;
      g = p;
      b = q;
    }
  }
  if (output_object) {
    return {"red": r, "green": g, "blue": b};
  } else {
    return (Math.floor(r * 255) << 15) | (Math.floor(g * 255) << 8) | (Math.floor(b * 255));
  }
};

// Calculate a distance score between two colours in LAB colourspace
Alphabet.lab_dist = function(lab1, lab2) {
  var c1 = Math.sqrt((lab1.l * lab1.l) + (lab1.a * lab1.a));
  var c2 = Math.sqrt((lab2.l * lab2.l) + (lab2.a * lab2.a));
  var dc = c1 - c2;
  var dl = lab1.l - lab2.l;
  var da = lab1.a - lab2.a;
  var db = lab1.b - lab2.b;
  // we don't want NaN due to rounding errors so fudge things a bit...
  var dh = 0;
  var dh_squared = (da * da) + (db * db) - (dc * dc);
  if (dh_squared > 0) {
    dh = Math.sqrt(dh_squared);
  }
  var first = dl;
  var second = dc / (1.0 + (0.045 * c1));
  var third = dh / (1.0 + (0.015 * c1));
  return Math.sqrt((first * first) + (second * second) + (third * third));
};

// convert an RGB value into a HSL value
Alphabet.rgb2hsl = function(rgb) {
  "use strict";
  var min, max, delta, h, s, l, r, g, b;
  if (typeof rgb == "number") {
    r = ((rgb >> 16) & 0xFF) / 255.0;
    g = ((rgb >> 8) & 0xFF) / 255.0;
    b = (rgb & 0xFF) / 255.0;
  } else {
    r = rgb.red;
    g = rgb.green;
    b = rgb.blue;
  }
  min = Math.min(r, g, b);
  max = Math.max(r, g, b);
  delta = max - min;
  l = min + (delta / 2);
  if (max == min) {
    h = 0; // achromatic (grayscale)
    s = 0;
  } else {
    if (l > 0.5) {
      s = delta / (2 - max - min);
    } else {
      s = delta / (max + min);
    }
    if (max == r) {
      h = (g - b) / delta;
      if (g < b) h += 6;
    } else if (max == g) {
      h = ((b - r) / delta) + 2;
    } else {
      h = ((r - g) / delta) + 4;
    }
    h /= 6;
  }
  return {"h": h, "s": s, "l": l};
};

// convert a HSL value into an RGB value
Alphabet.hsl2rgb = function(hsl, output_object) {
  "use strict";
  function _hue(p, q, t) {
    "use strict";
    if (t < 0) t += 1;
    else if (t > 1) t -= 1;
    if (t < (1.0 / 6.0)) {
      return p + ((q - p) * 6.0 * t);
    } else if (t < 0.5) {
      return q;
    } else if (t < (2.0 / 3.0)) {
      return p + ((q - p) * ((2.0 / 3.0) - t) * 6.0);
    } else {
      return p;
    }
  }
  var r, g, b, p, q;
  if (hsl.s == 0) {
    // achromatic (grayscale)
    r = hsl.l;
    g = hsl.l;
    b = hsl.l;
  } else {
    if (hsl.l < 0.5) {
      q = hsl.l * (1 + hsl.s);
    } else {
      q = hsl.l + hsl.s - (hsl.l * hsl.s);
    }
    p = (2 * hsl.l) - q;
    r = _hue(p, q, hsl.h + (1.0 / 3.0));
    g = _hue(p, q, hsl.h);
    b = _hue(p, q, hsl.h - (1.0 / 3.0));
  }
  if (output_object) {
    return {"red": r, "green": g, "blue": b};
  } else {
    return (Math.floor(r * 255) << 15) | (Math.floor(g * 255) << 8) | (Math.floor(b * 255));
  }
};

Alphabet.lighten_colour = function(rgb) {
  "use strict";
  var hsl = Alphabet.rgb2hsl(rgb);
  hsl.l += (1.0 - hsl.l) * 2 / 3;
  return Alphabet.hsl2rgb(hsl, typeof rgb != "number");
};

//======================================================================
// end Alphabet object
//======================================================================

//======================================================================
// start StandardAlphabet object
//======================================================================

// an extension of the alphabet object to support some additional fields 
// only present in standard alphabets.
var StandardAlphabet = function(enum_code, enum_name, alphabet_data) {
  Alphabet.apply(this, [alphabet_data]);
  this.enum_code = enum_code;
  this.enum_name = enum_name;
};
StandardAlphabet.prototype = Alphabet.prototype;
StandardAlphabet.prototype.constructor = StandardAlphabet;

// A unique code for this standard alphabet.
// This code will be a power of 2 to enable creation of bitsets for
// a selection of standard alphabets.
StandardAlphabet.prototype.get_code = function() {
  return this.enum_code;
};

// A unique name for this standard alphabet.
// this name will be all upper case and the same as the property that
// refers to this alphabet in the AlphStd collection.
StandardAlphabet.prototype.get_enum = function() {
  return this.enum_name;
};

//======================================================================
// end StandardAlphabet object
//======================================================================

// A collection of standard alphabets.
var AlphStd = {
  RNA: new StandardAlphabet(1, "RNA", {
    "name": "RNA",
    "like": "RNA",
    "ncore": 4,
    "symbols": [
      {"symbol": "A", "name": "Adenine", "colour": "CC0000"},
      {"symbol": "C", "name": "Cytosine", "colour": "0000CC"},
      {"symbol": "G", "name": "Guanine", "colour": "FFB300"},
      {"symbol": "U", "name": "Uracil", "colour": "008000",
        "aliases": "T"},
      {"symbol": "N", "name": "Any base", "equals": "ACGU", "aliases": "X."},
      {"symbol": "V", "name": "Not U", "equals": "ACG"},
      {"symbol": "H", "name": "Not G", "equals": "ACU"},
      {"symbol": "D", "name": "Not C", "equals": "AGU"},
      {"symbol": "B", "name": "Not A", "equals": "CGU"},
      {"symbol": "M", "name": "Amino", "equals": "AC"},
      {"symbol": "R", "name": "Purine", "equals": "AG"},
      {"symbol": "W", "name": "Weak", "equals": "AU"}, 
      {"symbol": "S", "name": "Strong", "equals": "CG"},
      {"symbol": "Y", "name": "Pyrimidine", "equals": "CU"},
      {"symbol": "K", "name": "Keto", "equals": "GU"}
    ]
  }), 
  DNA: new StandardAlphabet(2, "DNA", {
    "name": "DNA",
    "like": "DNA",
    "ncore": 4,
    "symbols": [
      {"symbol": "A", "name": "Adenine", "colour": "CC0000", "complement": "T"},
      {"symbol": "C", "name": "Cytosine", "colour": "0000CC", "complement": "G"},
      {"symbol": "G", "name": "Guanine", "colour": "FFB300", "complement": "C"},
      {"symbol": "T", "name": "Thymine", "colour": "008000", "complement": "A",
        "aliases": "U"},
      {"symbol": "N", "name": "Any base", "equals": "ACGT", "aliases": "X."},
      {"symbol": "V", "name": "Not T", "equals": "ACG"},
      {"symbol": "H", "name": "Not G", "equals": "ACT"},
      {"symbol": "D", "name": "Not C", "equals": "AGT"},
      {"symbol": "B", "name": "Not A", "equals": "CGT"},
      {"symbol": "M", "name": "Amino", "equals": "AC"},
      {"symbol": "R", "name": "Purine", "equals": "AG"},
      {"symbol": "W", "name": "Weak", "equals": "AT"}, 
      {"symbol": "S", "name": "Strong", "equals": "CG"},
      {"symbol": "Y", "name": "Pyrimidine", "equals": "CT"},
      {"symbol": "K", "name": "Keto", "equals": "GT"}
    ]
  }), 
  PROTEIN: new StandardAlphabet(4, "PROTEIN", {
    "name": "Protein",
    "like": "PROTEIN",
    "ncore": 20,
    "symbols": [
      {"symbol": "A", "name": "Alanine", "colour": "0000CC"},
      {"symbol": "C", "name": "Cysteine", "colour": "0000CC"},
      {"symbol": "D", "name": "Aspartic acid", "colour": "FF00FF"},
      {"symbol": "E", "name": "Glutamic acid", "colour": "FF00FF"},
      {"symbol": "F", "name": "Phenylalanine", "colour": "0000CC"},
      {"symbol": "G", "name": "Glycine", "colour": "FFB300"},
      {"symbol": "H", "name": "Histidine", "colour": "FFCCCC"},
      {"symbol": "I", "name": "Isoleucine", "colour": "0000CC"},
      {"symbol": "K", "name": "Lysine", "colour": "CC0000"},
      {"symbol": "L", "name": "Leucine", "colour": "0000CC"},
      {"symbol": "M", "name": "Methionine", "colour": "0000CC"},
      {"symbol": "N", "name": "Asparagine", "colour": "008000"},
      {"symbol": "P", "name": "Proline", "colour": "FFFF00"},
      {"symbol": "Q", "name": "Glutamine", "colour": "008000"},
      {"symbol": "R", "name": "Arginine", "colour": "CC0000"},
      {"symbol": "S", "name": "Serine", "colour": "008000"},
      {"symbol": "T", "name": "Threonine", "colour": "008000"},
      {"symbol": "V", "name": "Valine", "colour": "0000CC"},
      {"symbol": "W", "name": "Tryptophan", "colour": "0000CC"},
      {"symbol": "Y", "name": "Tyrosine", "colour": "33E6CC"},
      {"symbol": "X", "name": "Any amino acid", "equals": "ACDEFGHIKLMNPQRSTVWY", "aliases": "*."},
      {"symbol": "B", "name": "Asparagine or Aspartic acid", "equals": "DN"}, 
      {"symbol": "Z", "name": "Glutamine or Glutamic acid", "equals": "EQ"}, 
      {"symbol": "J", "name": "Leucine or Isoleucine", "equals": "IL"}
    ]
  })
};

//======================================================================
// start Symbol object
//======================================================================
var Symbol = function(alph_index, scale, alphabet) {
  "use strict";
  //variable prototype
  this.symbol = alphabet.get_symbol(alph_index);
  this.scale = scale;
  this.colour = alphabet.get_colour(alph_index);
};

Symbol.prototype.get_symbol = function() {
  "use strict";
  return this.symbol;
};

Symbol.prototype.get_scale = function() {
  "use strict";
  return this.scale;
};

Symbol.prototype.get_colour = function() {
  "use strict";
  return this.colour;
};

Symbol.prototype.toString = function() {
  "use strict";
  return this.symbol + " " + (Math.round(this.scale*1000)/10) + "%";
};

function compare_symbol(sym1, sym2) {
  "use strict";
  if (sym1.get_scale() < sym2.get_scale()) {
    return -1;
  } else if (sym1.get_scale() > sym2.get_scale()) {
    return 1;
  } else {
    return 0;
  }
}
//======================================================================
// end Symbol object
//======================================================================

//======================================================================
// start Pspm object
//======================================================================
var Pspm = function(matrix, name, ltrim, rtrim, nsites, evalue, pssm, alt) {
  "use strict";
  var row, col, data, row_sum, delta, evalue_re;
  if (typeof name !== "string") {
    name = "";
  }
  this.name = name;
  //construct
  if (matrix instanceof Pspm) {
    // copy constructor
    this.alph_length = matrix.alph_length;
    this.motif_length = matrix.motif_length;
    this.name = matrix.name;
    this.alt = matrix.alt;
    this.nsites = matrix.nsites;
    this.evalue = matrix.evalue;
    this.ltrim = matrix.ltrim;
    this.rtrim = matrix.rtrim;
    this.pspm = [];
    for (row = 0; row < matrix.motif_length; row++) {
      this.pspm[row] = [];
      for (col = 0; col < matrix.alph_length; col++) {
        this.pspm[row][col] = matrix.pspm[row][col];
      }
    }
    if (matrix.pssm != null) {
      this.pssm = [];
      for (row = 0; row < matrix.motif_length; row++) {
        this.pspm[row] = [];
        for (col = 0; col < matrix.alph_length; col++) {
          this.pssm[row][col] = matrix.pssm[row][col];
        }
      }
    }
  } else {
    // check parameters
    if (ltrim == null) {
      ltrim = 0;
    } else if (typeof ltrim !== "number" || ltrim % 1 !== 0 || ltrim < 0) {
      throw new Error("ltrim must be a non-negative integer, got: " + ltrim);
    }
    if (rtrim == null) {
      rtrim = 0;
    } else if (typeof rtrim !== "number" || rtrim % 1 !== 0 || rtrim < 0) {
      throw new Error("rtrim must be a non-negative integer, got: " + rtrim);
    }
    if (nsites != null) {
      if (typeof nsites !== "number" || nsites < 0) {
        throw new Error("nsites must be a positive number, got: " + nsites);
      } else if (nsites == 0) {
        nsites = null;
      }
    }
    if (evalue != null) {
      if (typeof evalue === "number") {
        if (evalue < 0) {
          throw new Error("evalue must be a non-negative number, got: " + evalue);
        }
      } else if (typeof evalue === "string") {
        evalue_re = /^((?:[+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)|inf)$/;
        if (!evalue_re.test(evalue)) {
          throw new Error("evalue must be a non-negative number, got: " + evalue);
        }
      } else {
        throw new Error("evalue must be a non-negative number, got: " + evalue);
      }
    }
    // set properties
    this.name = name;
    this.alt = alt;
    this.nsites = nsites;
    this.evalue = evalue;
    this.ltrim = ltrim;
    this.rtrim = rtrim;
    if (typeof matrix === "string") {
      // string constructor
      data = parse_pspm_string(matrix);
      this.alph_length = data["alph_length"];
      this.motif_length = data["motif_length"];
      this.pspm = data["pspm"];
      if (this.evalue == null) {
        if (data["evalue"] != null) {
          this.evalue = data["evalue"];
        } else {
          this.evalue = 0;
        }
      }
      if (this.nsites == null) {
        if (typeof data["nsites"] === "number") {
          this.nsites = data["nsites"];
        } else {
          this.nsites = 20;
        }
      }
    } else {
      // assume pspm is a nested array
      this.motif_length = matrix.length;
      this.alph_length = (matrix.length > 0 ? matrix[0].length : 0);
      if (this.nsites == null) {
        this.nsites = 20;
      }
      if (this.evalue == null) {
        this.evalue = 0;
      }
      this.pspm = [];
      // copy pspm and check
      for (row = 0; row < this.motif_length; row++) {
        if (this.alph_length != matrix[row].length) {
          throw new Error("COLUMN_MISMATCH");
        }
        this.pspm[row] = [];
        row_sum = 0;
        for (col = 0; col < this.alph_length; col++) {
          this.pspm[row][col] = matrix[row][col];
          row_sum += this.pspm[row][col];
        }
        delta = 0.1;
        if (isNaN(row_sum) || (row_sum > 1 && (row_sum - 1) > delta) || 
            (row_sum < 1 && (1 - row_sum) > delta)) {
          throw new Error("INVALID_SUM");
        }
      }
      // copy pssm
      if (pssm != null) {
        this.pssm = [];
        for (row = 0; row < this.motif_length; row++) {
          this.pssm[row] = [];
          for (col = 0; col < this.alph_length; col++) {
            this.pssm[row][col] = pssm[row][col];
          }
        }
      }
    }
  }
};

Pspm.prototype.copy = function() {
  "use strict";
  return new Pspm(this);
};

Pspm.prototype.reverse = function() {
  "use strict";
  var x, y, temp, temp_trim;
  //reverse
  x = 0;
  y = this.motif_length-1;
  while (x < y) {
    temp = this.pspm[x];
    this.pspm[x] = this.pspm[y];
    this.pspm[y] = temp;
    x++;
    y--;
  }
  // reverse pssm (if defined)
  if (typeof this.pssm !== "undefined") {
    //reverse
    x = 0;
    y = this.motif_length-1;
    while (x < y) {
      temp = this.pssm[x];
      this.pspm[x] = this.pssm[y];
      this.pssm[y] = temp;
      x++;
      y--;
    }
  }
  //swap triming
  temp_trim = this.ltrim;
  this.ltrim = this.rtrim;
  this.rtrim = temp_trim;
  return this; //allow function chaining...
};

Pspm.prototype.reverse_complement = function(alphabet) {
  "use strict";
  var x, y, temp, i, row, c, temp_trim;
  if (this.alph_length != alphabet.get_size_core()) {
    throw new Error("The alphabet size does not match the size of the pspm.");
  }
  if (!alphabet.has_complement()) {
    throw new Error("The specified alphabet can not be complemented.");
  }
  // reverse motif
  this.reverse();
  //complement
  for (x = 0; x < this.motif_length; x++) {
    row = this.pspm[x];
    for (i = 0; i < row.length; i++) {
      c = alphabet.get_complement(i);
      if (c < i) continue;
      temp = row[i];
      row[i] = row[c];
      row[c] = temp;
    }
  }
  // complement pssm (if defined)
  if (typeof this.pssm !== "undefined") {
    //complement
    for (x = 0; x < this.motif_length; x++) {
      row = this.pssm[x];
      for (i = 0; i < row.length; i++) {
        c = alphabet.get_complement(i);
        if (c < i) continue;
        temp = row[i];
        row[i] = row[c];
        row[c] = temp;
      }
    }
  }
  return this; //allow function chaining...
};

Pspm.prototype.get_stack = function(position, alphabet, ssc) {
  "use strict";
  var row, stack_ic, alphabet_ic, stack, i, sym;
  if (this.alph_length != alphabet.get_size_core()) {
    throw new Error("The alphabet size does not match the size of the pspm.");
  }
  row = this.pspm[position];
  stack_ic = this.get_stack_ic(position, alphabet);
  if (ssc) stack_ic -= this.get_error(alphabet);
  alphabet_ic = alphabet.get_ic();
  stack = [];
  for (i = 0; i < this.alph_length; i++) {
    sym = new Symbol(i, row[i]*stack_ic/alphabet_ic, alphabet);
    if (sym.get_scale() <= 0) {
      continue;
    }
    stack.push(sym);
  }
  stack.sort(compare_symbol);
  return stack;
};

Pspm.prototype.get_stack_ic = function(position, alphabet) {
  "use strict";
  var row, H, i;
  if (this.alph_length != alphabet.get_size_core()) {
    throw new Error("The alphabet size does not match the size fo the pspm.");
  }
  row = this.pspm[position];
  H = 0;
  for (i = 0; i < this.alph_length; i++) {
    if (row[i] === 0) {
      continue;
    }
    H -= (row[i] * (Math.log(row[i]) / Math.LN2));
  }
  return alphabet.get_ic() - H;
};

Pspm.prototype.get_error = function(alphabet) {
  "use strict";
  if (this.nsites === 0) {
    return 0;
  }
  return (alphabet.get_size_core()-1) / (2 * Math.LN2 * this.nsites);
};

Pspm.prototype.get_motif_length = function() {
  "use strict";
  return this.motif_length;
};

Pspm.prototype.get_alph_length = function() {
  "use strict";
  return this.alph_length;
};

Pspm.prototype.get_left_trim = function() {
  "use strict";
  return this.ltrim;
};

Pspm.prototype.get_right_trim = function() {
  "use strict";
  return this.rtrim;
};

Pspm.prototype.as_best_match = function(alphabet) {
  "use strict";
  var match, odds, best_odds, best_index;
  var i, j;
  match = "";
  for (i = 0; i < this.motif_length; i++) {
    best_index = 0;
    best_odds = this.pspm[i][0] / alphabet.get_bg_freq(0);
    for (j = 1; j < this.alph_length; j++) {
      odds = this.pspm[i][j] / alphabet.get_bg_freq(j);
      if (odds > best_odds) {
        best_odds = odds;
        best_index = j;
      }
    }
    match += alphabet.get_symbol(best_index);
  }
  return match;
};

Pspm.prototype.as_count_matrix = function() {
  "use strict";
  var count, count_text, text;
  var i, j;
  text = "";
  for (i = 0; i < this.motif_length; i++) {
    if (i !== 0) {
      text += "\n";
    }
    for (j = 0; j < this.alph_length; j++) {
      if (j !== 0) {
        text += " ";
      }
      count = Math.round(this.nsites * this.pspm[i][j]);
      count_text = "" + count;
      // pad up to length of 4
      if (count_text.length < 4) {
        text += (new Array(5 - count_text.length)).join(" ") + count_text;
      } else {
        text += count_text;
      }
    }
  }
  return text; 
};

Pspm.prototype.as_probability_matrix = function() {
  "use strict";
  var text;
  var i, j;
  text = "";
  for (i = 0; i < this.motif_length; i++) {
    if (i !== 0) {
      text += "\n";
    }
    for (j = 0; j < this.alph_length; j++) {
      if (j !== 0) {
        text += " ";
      }
      text += this.pspm[i][j].toFixed(6);
    }
  }
  return text; 
};

Pspm.prototype.as_score_matrix = function(alphabet, pseudo) {
  "use strict";
  var me, score, out, row, col, score_text;
  me = this;
  if (typeof this.pssm === "undefined") {
    if (!(typeof alphabet === "object" && alphabet != null && alphabet instanceof Alphabet)) {
      throw new Error("The alphabet is required to generate the pssm.");
    }
    if (typeof pseudo === "undefined") {
      pseudo = 0.01;
    } else if (typeof pseudo !== "number" || pseudo < 0) {
      throw new Error("Expected positive number for pseudocount");
    }
    score = function(row, col) {
      "use strict";
      var p, bg, p2;
      p = me.pspm[row][col];
      bg = alphabet.get_bg_freq(col);
      p2 = (p * me.nsites + bg * pseudo) / (me.nsites + pseudo);
      return (p2 > 0 ? Math.round((Math.log(p2 / bg) / Math.LN2) * 100) : -10000);
    };
  } else {
    score = function(row, col) {
      "use strict";
      return me.pssm[row][col];
    };
  }
  out = "";
  for (row = 0; row < this.motif_length; row++) {
    for (col = 0; col < this.alph_length; col++) {
      if (col !== 0) {
        out += " ";
      }
      score_text = "" + score(row, col);
      // pad out to 6 characters
      if (score_text.length < 6) {
        out += (new Array(7 - score_text.length)).join(" ") + score_text;
      } else {
        out += score_text;
      }
    }
    out += "\n";
  }
  return out;
}

Pspm.prototype.as_pspm = function() {
  "use strict";
  return "letter-probability matrix: alength= " + this.alph_length + 
      " w= " + this.motif_length + " nsites= " + this.nsites + 
      " E= " + (typeof this.evalue === "number" ? 
          this.evalue.toExponential() : this.evalue) + "\n" +
      this.as_probability_matrix();
};

Pspm.prototype.as_pssm = function(alphabet, pseudo) {
  "use strict";
  return "log-odds matrix: alength= " + this.alph_length + 
      " w= " + this.motif_length + 
      " E= " + (typeof this.evalue == "number" ?
          this.evalue.toExponential() : this.evalue) + "\n" +
      this.as_score_matrix(alphabet, pseudo);
};

Pspm.prototype.as_meme = function(options) {
  var with_header, with_pspm, with_pssm, version, alphabet, bg_source, pseudocount, strands;
  var out, alen, i;
  // get the options
  if (typeof options !== "object" || options === null) {
    options = {};
  }
  with_header = (typeof options["with_header"] === "boolean" ? options["with_header"] : false);
  with_pspm = (typeof options["with_pspm"] === "boolean" ? options["with_pspm"] : false);
  with_pssm = (typeof options["with_pssm"] === "boolean" ? options["with_pssm"] : false);
  if (!with_pspm && !with_pssm) with_pspm = true;
  if (with_header) {
    if (typeof options["version"] === "string" && /^\d+(?:\.\d+){0,2}$/.test(options["version"])) {
      version = options["version"];
    } else if (typeof options["version"] === "number") {
      version = options["version"].toFixed(0);
    } else {
      version = "4";
    }
    if (typeof options["strands"] === "number" && options["strands"] === 1) {
      strands = 1;
    } else {
      strands = 2;
    }
    if (typeof options["bg_source"] === "string") {
      bg_source = options["bg_source"];
    } else {
      bg_source = "unknown source";
    }
    if (typeof options["alphabet"] === "object" && options["alphabet"] != null
        && options["alphabet"] instanceof Alphabet) {
      alphabet = options["alphabet"];
    } else {
      throw new Error("The alphabet is required to generate the header.");
    }
  }
  // now create the output
  out = "";
  if (with_header) {
    out = "MEME version " + version + "\n\n";
    out += alphabet.as_meme() + "\n";
    if (alphabet.has_complement()) { // assume DNA has both strands unless otherwise specified
      out += "strands: " + (strands === 1 ? "+" : "+ -") + "\n\n";
    }
    out += "Background letter frequencies (from " + bg_source + "):\n";
    alen = alphabet.get_size_core();
    for (i = 0; i < alen; i++) {
      if (i !== 0) {
        if (i % 9 === 0) { // maximum of nine entries per line
          out += "\n";
        } else {
          out += " ";
        }
      }
      out += alphabet.get_symbol(i) + " " + alphabet.get_bg_freq(i).toFixed(3);
    }
  }
  out += "\n\n";
  out += "MOTIF " + this.name + (this.alt == null ? "" : " " + this.alt);
  if (with_pssm) {
    out += "\n\n";
    out += this.as_pssm(options["alphabet"], options["pseudocount"]);
  }
  if (with_pspm) {
    out += "\n\n";
    out += this.as_pspm();
  }
  return out;
}

Pspm.prototype.toString = function() {
  "use strict";
  var str, i, row;
  str = "";
  for (i = 0; i < this.pspm.length; i++) {
    row = this.pspm[i];
    str += row.join("\t") + "\n";
  }
  return str;
};

function parse_pspm_properties(str) {
  "use strict";
  var parts, i, eqpos, before, after, properties, prop, num, num_re;
  num_re = /^((?:[+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)|inf)$/;
  parts = trim(str).split(/\s+/);
  // split up words containing =
  for (i = 0; i < parts.length;) {
    eqpos = parts[i].indexOf("=");
    if (eqpos != -1) {
      before = parts[i].substr(0, eqpos);
      after = parts[i].substr(eqpos+1);
      if (before.length > 0 && after.length > 0) {
        parts.splice(i, 1, before, "=", after);
        i += 3;
      } else if (before.length > 0) {
        parts.splice(i, 1, before, "=");
        i += 2;
      } else if (after.length > 0) {
        parts.splice(i, 1, "=", after);
        i += 2;
      } else {
        parts.splice(i, 1, "=");
        i++;
      }
    } else {
      i++;
    }
  }
  properties = {};
  for (i = 0; i < parts.length; i += 3) {
    if (parts.length - i < 3) {
      throw new Error("Expected PSPM property was incomplete. "+
          "Remaing parts are: " + parts.slice(i).join(" "));
    }
    if (parts[i+1] !== "=") {
      throw new Error("Expected '=' in PSPM property between key and " +
          "value but got " + parts[i+1]); 
    }
    prop = parts[i].toLowerCase();
    num = parts[i+2];
    if (!num_re.test(num)) {
      throw new Error("Expected numeric value for PSPM property '" + 
          prop + "' but got '" + num + "'");
    }
    properties[prop] = num;
  }
  return properties;
}

function parse_pspm_string(pspm_string) {
  "use strict";
  var header_re, lines, first_line, line_num, col_num, alph_length, 
      motif_length, nsites, evalue, pspm, i, line, match, props, parts,
      j, prob;
  header_re = /^letter-probability\s+matrix:(.*)$/i;
  lines = pspm_string.split(/\n/);
  first_line = true;
  line_num = 0;
  col_num = 0;
  alph_length;
  motif_length;
  nsites;
  evalue;
  pspm = [];
  for (i = 0; i < lines.length; i++) {
    line = trim(lines[i]);
    if (line.length === 0) { 
      continue;
    }
    // check the first line for a header though allow matrices without it
    if (first_line) {
      first_line = false;
      match = header_re.exec(line);
      if (match !== null) {
        props = parse_pspm_properties(match[1]);
        if (props.hasOwnProperty("alength")) {
          alph_length = parseFloat(props["alength"]);
          if (alph_length != 4 && alph_length != 20) {
            throw new Error("PSPM property alength should be 4 or 20" +
                " but got " + alph_length);
          }
        }
        if (props.hasOwnProperty("w")) {
          motif_length = parseFloat(props["w"]);
          if (motif_length % 1 !== 0 || motif_length < 1) {
            throw new Error("PSPM property w should be an integer larger " +
                "than zero but got " + motif_length);
          }
        }
        if (props.hasOwnProperty("nsites")) {
          nsites = parseFloat(props["nsites"]);
          if (nsites <= 0) {
            throw new Error("PSPM property nsites should be larger than " +
                "zero but got " + nsites);
          }
        }
        if (props.hasOwnProperty("e")) {
          evalue = props["e"];
          if (evalue < 0) {
            throw new Error("PSPM property evalue should be " +
                "non-negative but got " + evalue);
          }
        }
        continue;
      }
    }
    pspm[line_num] = [];
    col_num = 0;
    parts = line.split(/\s+/);
    for (j = 0; j < parts.length; j++) {
      prob = parseFloat(parts[j]);
      if (prob != parts[j] || prob < 0 || prob > 1) {
        throw new Error("Expected probability but got '" + parts[j] + "'"); 
      }
      pspm[line_num][col_num] = prob;
      col_num++;
    }
    line_num++;
  }
  if (typeof motif_length === "number") {
    if (pspm.length != motif_length) {
      throw new Error("Expected PSPM to have a motif length of " + 
          motif_length + " but it was actually " + pspm.length);
    }
  } else {
    motif_length = pspm.length;
  }
  if (typeof alph_length !== "number") {
    alph_length = pspm[0].length;
    if (alph_length != 4 && alph_length != 20) {
      throw new Error("Expected length of first row in the PSPM to be " +
          "either 4 or 20 but got " + alph_length);
    }
  }
  for (i = 0; i < pspm.length; i++) {
    if (pspm[i].length != alph_length) {
      throw new Error("Expected PSPM row " + i + " to have a length of " + 
          alph_length + " but the length was " + pspm[i].length);
    }
  }
  return {"pspm": pspm, "motif_length": motif_length, 
    "alph_length": alph_length, "nsites": nsites, "evalue": evalue};
}
//======================================================================
// end Pspm object
//======================================================================

//======================================================================
// start Logo object
//======================================================================

var Logo = function(alphabet, options) {
  "use strict";
  this.alphabet = alphabet;
  this.fine_text = "";
  this.x_axis = 1;
  this.y_axis = true;
  this.xlate_nsyms = 1;
  this.xlate_start = null;
  this.xlate_end = null;
  this.pspm_list = [];
  this.pspm_column = [];
  this.rows = 0;
  this.columns = 0;
  if (typeof options === "string") {
    // the old method signature had fine_text here so we support that
    this.fine_text = options;
  } else if (typeof options === "object" && options != null) {
    this.fine_text = (typeof options.fine_text === "string" ? options.fine_text : "");
    this.x_axis = (typeof options.x_axis === "boolean" ? (options.x_axis ? 1 : 0) : 1);
    if (options.x_axis_hidden != null && options.x_axis_hidden) this.x_axis = -1;
    this.y_axis = (typeof options.y_axis === "boolean" ? options.y_axis : true);
    this.xlate_nsyms = (typeof options.xlate_nsyms === "number" ? options.xlate_nsyms : this.xlate_nsyms);
    this.xlate_start = (typeof options.xlate_start === "number" ? options.xlate_start : this.xlate_start);
    this.xlate_end = (typeof options.xlate_end === "number" ? options.xlate_end : this.xlate_end);
  }
};

Logo.prototype.add_pspm = function(pspm, column) {
  "use strict";
  var col;
  if (typeof column === "undefined") {
    column = 0;
  } else if (column < 0) {
    throw new Error("Column index out of bounds.");
  }
  this.pspm_list[this.rows] = pspm;
  this.pspm_column[this.rows] = column;
  this.rows++;
  col = column + pspm.get_motif_length();
  if (col > this.columns) {
    this.columns = col;
  }
};

Logo.prototype.get_columns = function() {
  "use strict";
  return this.columns;
};

Logo.prototype.get_xlate_nsyms = function() {
  "use strict";
  return this.xlate_nsyms;
};

Logo.prototype.get_xlate_start = function() {
  "use strict";
  return (this.xlate_start != null ? this.xlate_start : 0);
};

Logo.prototype.get_xlate_end = function() {
  "use strict";
  return (this.xlate_end != null ? this.xlate_end : this.columns * this.xlate_nsyms);
};

Logo.prototype.get_xlate_columns = function() {
  "use strict";
  return this.get_xlate_end() - this.get_xlate_start();
};

Logo.prototype.get_rows = function() {
  "use strict";
  return this.rows;
};

Logo.prototype.get_pspm = function(row_index) {
  "use strict";
  if (row_index < 0 || row_index >= this.rows) {
    throw new Error("INDEX_OUT_OF_BOUNDS");
  }
  return this.pspm_list[row_index];
};

Logo.prototype.get_offset = function(row_index) {
  "use strict";
  if (row_index < 0 || row_index >= this.rows) {
    throw new Error("INDEX_OUT_OF_BOUNDS");
  }
  return this.pspm_column[row_index];
};

Logo.prototype._as_eps_data = function(ssc, errbars) {
  var i, j, pos, stack_pos, pspm, stack, sym, out;
  out = "";
  for (i = 0; i < this.rows; i++) {
    out += "\nStartLine\n";
    // Indent
    for (j = 0; j < this.pspm_column[i]; j++) {
      out += "() startstack\nendstack\n\n";
    }
    pspm = this.pspm_list[i];
    if (pspm.get_left_trim() > 0) {
      out += "MuteColour\nDrawTrimEdge\n" + pspm.get_left_trim() + " DrawTrimBg\n";
    }
    for (pos = 0; pos < pspm.get_motif_length(); pos++) {
      if (pos != 0 && pos == pspm.get_left_trim()) { // enable full colour
        out += "DrawTrimEdge\nRestoreColour\n";
      } else if (pos == (pspm.get_motif_length() - pspm.get_right_trim())) {
        out += "MuteColour\n" + pspm.get_right_trim() + " DrawTrimBg\n";
      }
      out += "(" + (pos + 1) + ") startstack\n";
      stack = pspm.get_stack(pos, this.alphabet, ssc);
      for (stack_pos = 0; stack_pos < stack.length; stack_pos++) {
        sym = stack[stack_pos];
        out += " " + (sym.get_scale() * this.alphabet.get_ic()) + " (" + sym.get_symbol() + ") numchar\n";
      }
      if (errbars) {
        out += " " + pspm.get_error(this.alphabet) + " Ibeam\n";
      }
      out += "endstack\n\n";
    }
    if (pspm.get_right_trim() > 0 || pspm.get_left_trim() == pspm.get_motif_length()) {
      out += "RestoreColour\n";
    }
    out += "EndLine\n";
  }
  return out;
};

Logo.prototype.as_eps = function(options) {
  "use strict";
  if (this.xlate_nsyms != 1) throw new Error("Unsupported setting xlate_nsyms for EPS");
  if (this.xlate_start != null) throw new Error("Unsupported setting xlate_start for EPS");
  if (this.xlate_end != null) throw new Error("Unsupported setting xlate_end for EPS");

  var LOGOHEIGHT = 7.5; // default height of line in cm
  var cm2pts, height, width, now, ssc, errbars;
  if (typeof options === "undefined") {
    options = {};
  }
  cm2pts = 72 / 2.54;
  if (typeof options.logo_height == "number") {
    height = options.logo_height;
  } else {
    height = LOGOHEIGHT * this.rows;
  }
  if (typeof options.logo_width == "number") {
    width = options.logo_width;
  } else {
    width = this.columns + 2;
  }
  now = new Date();
  ssc = (typeof options.ssc == "boolean" ? options.ssc : false);
  errbars = (typeof options.show_error_bar == "boolean" ? options.show_error_bar : ssc);
  var values = {
    "LOGOHEIGHT": height,
    "LOGOWIDTH": width,
    "BOUNDINGHEIGHT": Math.round(height * cm2pts),
    "BOUNDINGWIDTH": Math.round(width * cm2pts),
    "LOGOLINEHEIGHT": (height / this.rows),
    "CHARSPERLINE": this.columns,
    "BARBITS": this.alphabet.get_ic(),
    "LOGOTYPE": (this.alphabet.has_complement() ? "NA" : "AA"),
    "CREATIONDATE": now.getDate() + "." + (now.getMonth() + 1) + "." + now.getFullYear() + " " + now.getHours() + ":" + now.getMinutes() + ":" + now.getSeconds(),
    "ERRORBARFRACTION": (typeof options.error_bar_fraction == "number" ? options.error_bar_fraction : 1.0),
    "TICBITS": (typeof options.ticbits == "number" ? options.ticbits : 1.0),
    "TITLE": (typeof options.title == "string" ? options.title : ""),
    "FINEPRINT": (typeof options.fineprint == "string" ? options.fineprint : this.fine_text),
    "XAXISLABEL": (typeof options.xaxislabel == "string" ? options.xaxislabel : ""),
    "YAXISLABEL": (typeof options.yaxislabel == "string" ? options.yaxislabel : "bits"),
    "SSC": ssc,
    "YAXIS": (typeof options.show_y_axis == "boolean" ? options.show_y_axis : this.y_axis),
    "SHOWENDS": (typeof options.show_ends == "boolean" ? options.show_ends : false),
    "ERRBAR": errbars,
    "OUTLINE": (typeof options.show_outline == "boolean" ? options.show_outline : false),
    "NUMBERING": (typeof options.show_numbering == "boolean" ? options.show_numbering : this.x_axis != 0),
    "SHOWINGBOX": (typeof options.show_box == "boolean" ? options.show_box : false),
    "CREATOR": (typeof options.creator == "string" ? options.creator : "motif_logo.js"),
    "FONTSIZE": (typeof options.label_font_size == "number" ? options.label_font_size : 12),
    "TITLEFONTSIZE": (typeof options.title_font_size == "number" ? options.title_font_size : 12),
    "SMALLFONTSIZE": (typeof options.small_font_size == "number" ? options.small_font_size : 6),
    "TOPMARGIN" : (typeof options.top_margin == "number" ? options.top_margin : 0.9),
    "BOTTOMMARGIN": (typeof options.bottom_margin == "number" ? options.bottom_margin : 0.9),
    "COLORDICT": this.alphabet._as_eps_dict(),
    "DATA": this._as_eps_data(ssc, errbars)
  };
  // now this requires that the script containing the template has been imported!
  return motif_logo_template(values);
};

//======================================================================
// end Logo object
//======================================================================

// calculate the exact size (in pixels) of an object drawn on the
// canvas assuming that the background of the canvas is transparent.
function canvas_bounds(ctx, cwidth, cheight) {
  "use strict";
  var data, r, c, top_line, bottom_line, left_line, right_line, 
      txt_width, txt_height;

  // extract the image data
  data = ctx.getImageData(0, 0, cwidth, cheight).data;

  // set initial values
  top_line = -1; bottom_line = -1; left_line = -1; right_line = -1;
  txt_width = 0; txt_height = 0;

  // Find the top-most line with a non-transparent pixel
  for (r = 0; r < cheight; r++) {
    for (c = 0; c < cwidth; c++) {
      if (data[r * cwidth * 4 + c * 4 + 3]) {
        top_line = r;
        break;
      }
    }
    if (top_line != -1) {
      break;
    }
  }
  
  // Only bother looking if we found at least one set pixel... 
  if (top_line != -1) {

    //find the last line with a non-transparent pixel
    for (r = cheight-1; r >= top_line; r--) {
      for(c = 0; c < cwidth; c++) {
        if(data[r * cwidth * 4 + c * 4 + 3]) {
          bottom_line = r;
          break;
        }
      }
      if (bottom_line != -1) {
        break;
      }
    }
    // calculate height
    txt_height = bottom_line - top_line + 1;

    // Find the left-most line with a non-transparent pixel
    for (c = 0; c < cwidth; c++) {
      for (r = top_line; r <= bottom_line; r++) {
        if (data[r * cwidth * 4 + c * 4 + 3]) {
          left_line = c;
          break;
        }
      }
      if (left_line != -1) {
        break;
      }
    }

    //find the right most line with a non-transparent pixel
    for (c = cwidth-1; c >= left_line; c--) {
      for(r = top_line; r <= bottom_line; r++) {
        if(data[r * cwidth * 4 + c * 4 + 3]) {
          right_line = c;
          break;
        }
      }
      if (right_line != -1) {
        break;
      }
    }
    txt_width = right_line - left_line + 1;
  }

  //return the bounds
  return {bound_top: top_line, bound_bottom: bottom_line, 
    bound_left: left_line, bound_right: right_line, width: txt_width, 
    height: txt_height};
}

//======================================================================
// start RasterizedAlphabet
//======================================================================

// Rasterize Alphabet
// 1) Measure width of text at default font for all symbols in alphabet
// 2) sort in width ascending
// 3) Drop the top and bottom 10% (designed to ignore outliers like 'W' and 'I')
// 4) Calculate the average as the maximum scaling factor (designed to stop I becoming a rectangular blob).
// 5) Assume scale of zero would result in width of zero, interpolate scale required to make perfect width font
// 6) Draw text onto temp canvas at calculated scale
// 7) Find bounds of drawn text
// 8) Paint on to another canvas at the desired height (but only scaling width to fit if larger).
var RasterizedAlphabet = function(alphabet, logo_scale, font, width) {
  "use strict";
  var default_size, safety_pad, canvas, ctx, middle, baseline, widths, sizes,
      i, sym, size, tenpercent, avg_width, scale, 
      target_width, target_height;
  //variable prototypes
  this.alphabet = alphabet;
  this.scale = logo_scale;
  this.sym_cache = {};
  this.stack_num_cache = [];
  this.scale_num_cache = [];
  // size of canvas
  default_size = 60; // size of measuring canvas
  safety_pad = 20; // pixels to pad around so we don't miss the edges
  // create a canvas to do our measuring
  canvas = document.createElement("canvas");
  if (!canvas.getContext) throw new Error("No canvas support");
  canvas.width = default_size + 2 * safety_pad;
  canvas.height = default_size + 2 * safety_pad;
  middle = Math.round(canvas.width / 2);
  baseline = Math.round(canvas.height - safety_pad);
  ctx = canvas.getContext('2d');
  if (!supports_text(ctx)) throw new Error("Canvas does not support text");
  ctx.font = font;
  ctx.textAlign = "center";
  ctx.translate(middle, baseline);
  // list of widths
  widths = [];
  sizes = [];
  //now measure each letter in the alphabet
  for (i = 0; i < alphabet.get_size_core(); ++i) {
    // reset the canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = alphabet.get_colour(i);
    // draw the test text
    ctx.fillText(alphabet.get_symbol(i), 0, 0);
    //measure
    size = canvas_bounds(ctx, canvas.width, canvas.height);
    if (size.width === 0) throw new Error("Invisible symbol!");
    widths.push(size.width);
    sizes[i] = size;
  }
  //sort the widths
  widths.sort(function(a,b) {return a - b;});
  //drop 10% of the items off each end
  tenpercent = Math.floor(widths.length / 10);
  for (i = 0; i < tenpercent; ++i) {
    widths.pop();
    widths.shift();
  }
  //calculate average width
  avg_width = 0;
  for (i = 0; i < widths.length; ++i) {
    avg_width += widths[i];
  }
  avg_width /= widths.length;
  // calculate the target width
  target_width = width * this.scale * 2;
  // calculate scales
  for (i = 0; i < alphabet.get_size_core(); ++i) {
    sym = alphabet.get_symbol(i);
    size = sizes[i];
    // calculate scale
    scale = target_width / Math.max(avg_width, size.width);
    // estimate scaled height
    target_height = size.height * scale;
    // create an appropriately sized canvas
    canvas = document.createElement("canvas");
    canvas.width = target_width;
    canvas.height = target_height + safety_pad * 2;
    // calculate the middle
    middle = Math.round(canvas.width / 2);
    // calculate the baseline
    baseline = Math.round(canvas.height - safety_pad);
    // get the context and prepare to draw the rasterized text
    ctx = canvas.getContext('2d');
    ctx.font = font;
    ctx.fillStyle = alphabet.get_colour(i);
    ctx.textAlign = "center";
    ctx.translate(middle, baseline);
    ctx.save();
    ctx.scale(scale, scale);
    // draw the text
    ctx.fillText(sym, 0, 0);
    ctx.restore();
    this.sym_cache[sym] = {"image": canvas, "size": canvas_bounds(ctx, canvas.width, canvas.height)};
  }
};

RasterizedAlphabet.prototype.get_alphabet = function() {
  return this.alphabet;
};

RasterizedAlphabet.prototype.get_scale = function() {
  return this.scale;
};

RasterizedAlphabet.prototype.draw_stack_sym = function(ctx, letter, dx, dy, dWidth, dHeight) {
  "use strict";
  var entry, image, size;
  entry = this.sym_cache[letter];
  image = entry.image;
  size = entry.size;
  ctx.drawImage(image, 0, size.bound_top -1, image.width, size.height+1, dx, dy, dWidth, dHeight);
};

RasterizedAlphabet.prototype.draw_stack_num = function(ctx, font, stack_width, index) {
  var image, image_ctx, text_length;
  if (index >= this.stack_num_cache.length) {
    image = document.createElement("canvas");
    // measure the text
    image_ctx = image.getContext('2d');
    image_ctx.save();
    image_ctx.font = font;
    text_length = image_ctx.measureText("" + (index + 1)).width;
    image_ctx.restore();
    // resize the canvas to fit
    image.width = Math.ceil(stack_width);
    image.height = Math.ceil(text_length);
    // draw the text
    image_ctx = image.getContext('2d');
    image_ctx.translate(Math.round(stack_width / 2), 0);
    image_ctx.font = font;
    image_ctx.textBaseline = "middle";
    image_ctx.textAlign = "right";
    image_ctx.rotate(-(Math.PI / 2));
    image_ctx.fillText("" + (index + 1), 0, 0);
    this.stack_num_cache[index] = image;
  } else {
    image = this.stack_num_cache[index];
  }
  ctx.drawImage(image, 0, 0);
}

RasterizedAlphabet.prototype.draw_scale_num = function(ctx, font, num) {
  var image, image_ctx, text_size, m_length;
  if (num >= this.scale_num_cache.length) {
    image = document.createElement("canvas");
    // measure the text
    image_ctx = image.getContext('2d');
    image_ctx.font = font;
    text_size = image_ctx.measureText("" + num);
    if (text_size.actualBoundingBoxAscent && text_size.actualBoundingBoxDesent) {
      // resize the canvas to fit
      image.width = Math.ceil(text_size.width);
      image.height = Math.ceil(text_size.actualBoundingBoxAscent + text_size.actualBoundingBoxDesent);
      // draw the text
      image_ctx = image.getContext('2d');
      image_ctx.font = font;
      image_ctx.textAlign = "right";
      image_ctx.fillText("" + num, image.width, text_size.actualBoundingBoxAscent);
    } else {
      // measure width of 'm' to approximate height, we double it later anyway
      m_length = image_ctx.measureText("m").width;
      // resize the canvas to fit
      image.width = Math.ceil(text_size.width);
      image.height = Math.ceil(2 * m_length);
      // draw the text
      image_ctx = image.getContext('2d');
      image_ctx.font = font;
      image_ctx.textAlign = "right";
      image_ctx.textBaseline = "middle";
      image_ctx.fillText("" + num, image.width, m_length);
    }
    this.scale_num_cache[num] = image;
  } else {
    image = this.scale_num_cache[num];
  }
  ctx.drawImage(image, -image.width, -Math.round(image.height / 2))
}

//======================================================================
// end RasterizedAlphabet
//======================================================================

//======================================================================
// start LogoMetrics object
//======================================================================

var LogoMetrics = function(ctx, logo_columns, logo_rows, has_names, has_finetext, x_axis, y_axis) {
  "use strict";
  var i, row_height;
  //variable prototypes
  this.pad_top = (has_names ? 5 : 0);
  this.pad_left = (y_axis ? 10 : 0);
  this.pad_right = (has_finetext ? 15 : 0);
  this.pad_bottom = 0;
  this.pad_middle = 20;
  this.name_height = 14;
  this.name_font = "bold " + this.name_height + "px Times, sans-serif";
  this.name_spacer = 0;
  this.y_axis = y_axis;
  this.y_label = "bits";
  this.y_label_height = 12;
  this.y_label_font = "bold " + this.y_label_height + "px Helvetica, sans-serif";
  this.y_label_spacer = 3;
  this.y_num_height = 12;
  this.y_num_width = 0;
  this.y_num_font = "bold " + this.y_num_height + "px Helvetica, sans-serif";
  this.y_tic_width = 5;
  this.stack_pad_left = 0;
  this.stack_font = "bold 25px Helvetica, sans-serif";
  this.stack_height = 90;
  this.stack_width = 26;
  this.stacks_pad_right = 5;
  this.x_axis = x_axis;
  this.x_num_above = 2;
  this.x_num_height = 12;
  this.x_num_width = 0;
  this.x_num_font = "bold " + this.x_num_height + "px Helvetica, sans-serif";
  this.fine_txt_height = 6;
  this.fine_txt_above = 2;
  this.fine_txt_font = "normal " + this.fine_txt_height + "px Helvetica, sans-serif";
  this.letter_metrics = new Array();
  this.summed_width = 0;
  this.summed_height = 0;
  //calculate the width of the y axis numbers
  ctx.font = this.y_num_font;
  for (i = 0; i <= 2; i++) {
    this.y_num_width = Math.max(this.y_num_width, ctx.measureText("" + i).width);
  }
  //calculate the width of the x axis numbers (but they are rotated so it becomes height)
  if (x_axis == 1) {
    ctx.font = this.x_num_font;
    for (i = 1; i <= logo_columns; i++) {
      this.x_num_width = Math.max(this.x_num_width, ctx.measureText("" + i).width);
    }
  } else if (x_axis == 0) {
    this.x_num_height = 4;
    this.x_num_width = 4;
  } else {
    this.x_num_height = 0;
    this.x_num_width = 0;
  }
  
  //calculate how much vertical space we want to draw this
  //first we add the padding at the top and bottom since that's always there
  this.summed_height += this.pad_top + this.pad_bottom;
  //all except the last row have the same amount of space allocated to them
  if (logo_rows > 1) {
    row_height = this.stack_height + this.pad_middle;
    if (has_names) {
      row_height += this.name_height;
      //the label is allowed to overlap into the spacer
      row_height += Math.max(this.y_num_height/2, this.name_spacer); 
      //the label is allowed to overlap the space used by the other label
      row_height += Math.max(this.y_num_height/2, this.x_num_height + this.x_num_above); 
    } else {
      row_height += this.y_num_height/2; 
      //the label is allowed to overlap the space used by the other label
      row_height += Math.max(this.y_num_height/2, this.x_num_height + this.x_num_above); 
    }
    this.summed_height += row_height * (logo_rows - 1);
  }
  //the last row has the name and fine text below it but no padding
  this.summed_height += this.stack_height + (this.y_axis ? this.y_num_height/2 : 0);

  var fine_txt_total = (has_finetext ? this.fine_txt_height + this.fine_txt_above : 0);
  if (has_names) {
    this.summed_height += fine_txt_total + this.name_height;
    this.summed_height += Math.max((this.y_axis ? this.y_num_height/2 : 0), 
        this.x_num_height + this.x_num_above + this.name_spacer);
  } else {
    this.summed_height += Math.max((this.y_axis ? this.y_num_height/2 : 0), 
        this.x_num_height + this.x_num_above + fine_txt_total);
  }

  //calculate how much horizontal space we want to draw this
  //first add the padding at the left and right since that's always there
  this.summed_width += this.pad_left + this.pad_right;
  if (this.y_axis) {
    //add on the space for the y-axis label
    this.summed_width += this.y_label_height + this.y_label_spacer;
    //add on the space for the y-axis
    this.summed_width += this.y_num_width + this.y_tic_width;
  }
  //add on the space for the stacks
  this.summed_width += (this.stack_pad_left + this.stack_width) * logo_columns;
  //add on the padding after the stacks (an offset from the fine text)
  this.summed_width += this.stacks_pad_right;

};

//======================================================================
// end LogoMetrics object
//======================================================================

//found this trick at http://talideon.com/weblog/2005/02/detecting-broken-images-js.cfm
function image_ok(img) {
  "use strict";
  // During the onload event, IE correctly identifies any images that
  // weren't downloaded as not complete. Others should too. Gecko-based
  // browsers act like NS4 in that they report this incorrectly.
  if (!img.complete) {
    return false;
  }
  // However, they do have two very useful properties: naturalWidth and
  // naturalHeight. These give the true size of the image. If it failed
  // to load, either of these should be zero.
  if (typeof img.naturalWidth !== "undefined" && img.naturalWidth === 0) {
    return false;
  }
  // No other way of checking: assume it's ok.
  return true;
}
  
function supports_text(ctx) {
  "use strict";
  if (!ctx.fillText) {
    return false;
  }
  if (!ctx.measureText) {
    return false;
  }
  return true;
}

//draws the scale, returns the width
function draw_scale(ctx, metrics, alphabet_ic, raster) {
  "use strict";
  var tic_height, i;
  tic_height = metrics.stack_height / alphabet_ic;
  ctx.save();
  ctx.translate(metrics.y_label_height, metrics.y_num_height/2);
  //draw the axis label
  ctx.save();
  ctx.font = metrics.y_label_font;
  ctx.translate(0, metrics.stack_height/2);
  ctx.rotate(-(Math.PI / 2));
  ctx.textAlign = "center";
  ctx.fillText("bits", 0, 0);
  ctx.restore();

  ctx.translate(metrics.y_label_spacer + metrics.y_num_width, 0);

  //draw the axis tics
  ctx.save();
  ctx.translate(0, metrics.stack_height);
  for (i = 0; i <= alphabet_ic; i++) {
    //draw the number
    ctx.save();
    ctx.translate(-1, 0);
    raster.draw_scale_num(ctx, metrics.y_num_font, i);
    ctx.restore();
    //draw the tic
    ctx.fillRect(0, -1, metrics.y_tic_width, 2);
    //prepare for next tic
    ctx.translate(0, -tic_height);
  }
  ctx.restore();

  ctx.fillRect(metrics.y_tic_width - 2, 0, 2, metrics.stack_height)

  ctx.restore();
}

function draw_stack_num(ctx, metrics, row_index, raster) {
  "use strict";
  ctx.save();
  ctx.translate(0, Math.round(metrics.stack_height + metrics.x_num_above));
  if (metrics.x_axis == 1) {
    raster.draw_stack_num(ctx, metrics.x_num_font, metrics.stack_width, row_index);
  } else if (metrics.x_axis == 0) {
    // draw dots instead of the numbers (good for small logos)
    ctx.beginPath();
    var radius = Math.round(metrics.x_num_height / 2);
    ctx.arc(Math.round(metrics.stack_width / 2), radius, radius, 0, 2 * Math.PI, false);
    ctx.fill();
  }
  ctx.restore();
}

function draw_stack(ctx, metrics, symbols, raster) {
  "use strict";
  var preferred_pad, sym_min, i, sym, sym_height, pad;
  preferred_pad = 0;
  sym_min = 5;

  ctx.save();//1
  ctx.translate(0, metrics.stack_height);
  for (i = 0; i < symbols.length; i++) {
    sym = symbols[i];
    sym_height = metrics.stack_height * sym.get_scale();
    
    pad = preferred_pad;
    if (sym_height - pad < sym_min) {
      pad = Math.min(pad, Math.max(0, sym_height - sym_min));
    }
    sym_height -= pad;

    //translate to the correct position
    ctx.translate(0, -(pad/2 + sym_height));

    //draw
    raster.draw_stack_sym(ctx, sym.get_symbol(), 0, 0, metrics.stack_width, sym_height);
    //translate past the padding
    ctx.translate(0, -(pad/2));
  }
  ctx.restore();//1
}

function draw_dashed_line(ctx, pattern, start, x1, y1, x2, y2) {
  "use strict";
  var x, y, len, i, dx, dy, tlen, theta, mulx, muly, lx, ly;
  dx = x2 - x1;
  dy = y2 - y1;
  tlen = Math.pow(dx*dx + dy*dy, 0.5);
  theta = Math.atan2(dy,dx);
  mulx = Math.cos(theta);
  muly = Math.sin(theta);
  lx = [];
  ly = [];
  for (i = 0; i < pattern; ++i) {
    lx.push(pattern[i] * mulx);
    ly.push(pattern[i] * muly);
  }
  i = start;
  x = x1;
  y = y1;
  len = 0;
  ctx.beginPath();
  while (len + pattern[i] < tlen) {
    ctx.moveTo(x, y);
    x += lx[i];
    y += ly[i];
    ctx.lineTo(x, y);
    len += pattern[i];
    i = (i + 1) % pattern.length;
    x += lx[i];
    y += ly[i];
    len += pattern[i];
    i = (i + 1) % pattern.length;
  }
  if (len < tlen) {
    ctx.moveTo(x, y);
    x += mulx * (tlen - len);
    y += muly * (tlen - len);
    ctx.lineTo(x, y);
  }
  ctx.stroke();
}

function draw_trim_background(ctx, metrics, left_start, left_end, left_divider, right_start, right_end, right_divider) {
  "use strict";
  var left_size = left_end - left_start;
  var right_size = right_end - right_start;
  var line_x;

  ctx.save();//s8
  ctx.fillStyle = "rgb(240, 240, 240)";
  if (left_size > 0) {
    ctx.fillRect(left_start * metrics.stack_width, 0, left_size * metrics.stack_width, metrics.stack_height);
  }
  if (right_size > 0) {
    ctx.fillRect(right_start * metrics.stack_width, 0, right_size * metrics.stack_width, metrics.stack_height);
  }
  ctx.fillStyle = "rgb(51, 51, 51)";
  if (left_size > 0 && left_divider) {
    line_x = (left_end * metrics.stack_width) - 0.5;
    draw_dashed_line(ctx, [3], 0, line_x, 0, line_x, metrics.stack_height);
  }
  if (right_size > 0 && right_divider) {
    line_x = (right_start * metrics.stack_width) + 0.5;
    draw_dashed_line(ctx, [3], 0, line_x, 0, line_x, metrics.stack_height);
  }
  ctx.restore();//s8
}

function size_logo_on_canvas(logo, canvas, show_names, scale) {
  "use strict";
  var draw_name, draw_finetext, metrics;
  draw_name = (typeof show_names === "boolean" ? show_names : (logo.get_rows() > 1));
  draw_finetext = (logo.fine_text.length > 0);
  if (canvas.width !== 0 && canvas.height !== 0) {
    return;
  }
  metrics = new LogoMetrics(canvas.getContext('2d'), 
      logo.get_xlate_columns(), logo.get_rows(), draw_name, draw_finetext, logo.x_axis, logo.y_axis);
  if (typeof scale == "number") {
    //resize the canvas to fit the scaled logo
    canvas.width = metrics.summed_width * scale;
    canvas.height = metrics.summed_height * scale;
  } else {
    if (canvas.width === 0 && canvas.height === 0) {
      canvas.width = metrics.summed_width;
      canvas.height = metrics.summed_height;
    } else if (canvas.width === 0) {
      canvas.width = metrics.summed_width * (canvas.height / metrics.summed_height);
    } else if (canvas.height === 0) {
      canvas.height = metrics.summed_height * (canvas.width / metrics.summed_width);
    }
  }
}

function draw_logo_on_canvas(logo, canvas, show_names, scale) {
  "use strict";
  var i, draw_name, draw_finetext, ctx, metrics, raster, pspm_i, pspm, 
      offset, col_index, motif_position, ssc;
  ssc = false;
  draw_name = (typeof show_names === "boolean" ? show_names : (logo.get_rows() > 1));
  draw_finetext = (logo.fine_text.length > 0);
  ctx = canvas.getContext('2d');
  //assume that the user wants the canvas scaled equally so calculate what the best width for this image should be
  metrics = new LogoMetrics(ctx, logo.get_xlate_columns(), logo.get_rows(), draw_name, draw_finetext, logo.x_axis, logo.y_axis);
  if (typeof scale == "number") {
    //resize the canvas to fit the scaled logo
    canvas.width = metrics.summed_width * scale;
    canvas.height = metrics.summed_height * scale;
  } else {
    if (canvas.width === 0 && canvas.height === 0) {
      scale = 1;
      canvas.width = metrics.summed_width;
      canvas.height = metrics.summed_height;
    } else if (canvas.width === 0) {
      scale = canvas.height / metrics.summed_height;
      canvas.width = metrics.summed_width * scale;
    } else if (canvas.height === 0) {
      scale = canvas.width / metrics.summed_width;
      canvas.height = metrics.summed_height * scale;
    } else {
      scale = Math.min(canvas.width / metrics.summed_width, canvas.height / metrics.summed_height);
    }
  }
  // cache the raster based on the assumption that we will be drawing a lot
  // of logos the same size and alphabet
  if (typeof draw_logo_on_canvas.raster_cache === "undefined") {
    draw_logo_on_canvas.raster_cache = [];
  }
  for (i = 0; i < draw_logo_on_canvas.raster_cache.length; i++) {
    raster = draw_logo_on_canvas.raster_cache[i];
    if (raster.get_alphabet().equals(logo.alphabet) &&
        Math.abs(raster.get_scale() - scale) < 0.1) break;
    raster = null;
  }
  if (raster == null) {
    raster = new RasterizedAlphabet(logo.alphabet, scale, metrics.stack_font, metrics.stack_width);
    draw_logo_on_canvas.raster_cache.push(raster);
  }
  ctx = canvas.getContext('2d');
  ctx.save();//s1
  ctx.scale(scale, scale);
  ctx.save();//s2
  ctx.save();//s7
  //create margin
  ctx.translate(Math.round(metrics.pad_left), Math.round(metrics.pad_top));
  for (pspm_i = 0; pspm_i < logo.get_rows(); ++pspm_i) {
    pspm = logo.get_pspm(pspm_i);
    offset = logo.get_offset(pspm_i);
    //optionally draw name if this isn't the last row or is the only row 
    if (draw_name && (logo.get_rows() == 1 || pspm_i != (logo.get_rows()-1))) {
      ctx.save();//s4
      ctx.translate(Math.round(metrics.summed_width/2), Math.round(metrics.name_height));
      ctx.font = metrics.name_font;
      ctx.textAlign = "center";
      ctx.fillText(pspm.name, 0, 0);
      ctx.restore();//s4
      ctx.translate(0, Math.round(metrics.name_height + 
          Math.min(0, metrics.name_spacer - metrics.y_num_height/2)));
    }
    //draw scale
    if (logo.y_axis) draw_scale(ctx, metrics, logo.alphabet.get_ic(), raster);
    ctx.save();//s5
    //translate across past the scale
    if (logo.y_axis) {
      ctx.translate(Math.round(metrics.y_label_height + metrics.y_label_spacer + 
        metrics.y_num_width + metrics.y_tic_width), Math.round(metrics.y_num_height / 2));
    }
    //draw the trimming background
    if (pspm.get_left_trim() > 0 || pspm.get_right_trim() > 0) {
      var left_start = offset * logo.get_xlate_nsyms();
      var left_end = (offset + pspm.get_left_trim()) * logo.get_xlate_nsyms();
      var left_divider = true;
      if (left_end < logo.get_xlate_start() || left_start > logo.get_xlate_end()) {
        // no overlap
        left_start = 0;
        left_end = 0;
        left_divider = false;
      } else {
        if (left_start < logo.get_xlate_start()) {
          left_start = logo.get_xlate_start();
        }
        if (left_end > logo.get_xlate_end()) {
          left_end = logo.get_xlate_end();
          left_divider = false;
        }
        left_start -= logo.get_xlate_start();
        left_end -= logo.get_xlate_start();
        if (left_end < left_start) {
          left_start = 0;
          left_end = 0;
          left_divider = false;
        }
      }
      var right_end = (offset + pspm.get_motif_length()) * logo.get_xlate_nsyms();
      //var right_start = right_end - (pspm.get_left_trim() * logo.get_xlate_nsyms());
      var right_start = right_end - (pspm.get_right_trim() * logo.get_xlate_nsyms());
      var right_divider = true;
      if (right_end < logo.get_xlate_start() || right_start > logo.get_xlate_end()) {
        // no overlap
        right_start = 0;
        right_end = 0;
        right_divider = false;
      } else {
        if (right_start < logo.get_xlate_start()) {
          right_start = logo.get_xlate_start();
          right_divider = false;
        }
        if (right_end > logo.get_xlate_end()) {
          right_end = logo.get_xlate_end();
        }
        right_start -= logo.get_xlate_start();
        right_end -= logo.get_xlate_start();
        if (right_end < right_start) {
          right_start = 0;
          right_end = 0;
          right_divider = false;
        }
      }
      draw_trim_background(ctx, metrics, left_start, left_end, left_divider, right_start, right_end, right_divider);
    }
    //draw letters
    var xlate_col;
    for (xlate_col = logo.get_xlate_start(); xlate_col < logo.get_xlate_end(); xlate_col++) {
      ctx.translate(metrics.stack_pad_left,0);
      col_index = Math.floor(xlate_col / logo.get_xlate_nsyms());
      if (xlate_col % logo.get_xlate_nsyms() == 0) {
        if (col_index >= offset && col_index < (offset + pspm.get_motif_length())) {
          motif_position = col_index - offset;
          draw_stack_num(ctx, metrics, motif_position, raster);
          draw_stack(ctx, metrics, pspm.get_stack(motif_position, logo.alphabet, ssc), raster);
        }
      } else {
        if (col_index >= offset && col_index < (offset + pspm.get_motif_length())) {
          ctx.save();// s5.1
          ctx.translate(0, Math.round(metrics.stack_height));
          // TODO draw a dot or dash or something to indicate continuity of the motif
          ctx.restore(); //s5.1
        }
      }
      ctx.translate(Math.round(metrics.stack_width), 0);
    }
    ctx.restore();//s5
    ////optionally draw name if this is the last row but isn't the only row 
    if (draw_name && (logo.get_rows() != 1 && pspm_i == (logo.get_rows()-1))) {
      //translate vertically past the stack and axis's        
      ctx.translate(0, metrics.y_num_height/2 + metrics.stack_height + 
          Math.max(metrics.y_num_height/2, metrics.x_num_above + metrics.x_num_width + metrics.name_spacer));

      ctx.save();//s6
      ctx.translate(metrics.summed_width/2, metrics.name_height);
      ctx.font = metrics.name_font;
      ctx.textAlign = "center";
      ctx.fillText(pspm.name, 0, 0);
      ctx.restore();//s6
      ctx.translate(0, metrics.name_height);
    } else {
      //translate vertically past the stack and axis's        
      ctx.translate(0, metrics.y_num_height/2 + metrics.stack_height + 
          Math.max(metrics.y_num_height/2, metrics.x_num_above + metrics.x_num_width));
    }
    //if not the last row then add middle padding
    if (pspm_i != (logo.get_rows() -1)) {
      ctx.translate(0, metrics.pad_middle);
    }
  }
  ctx.restore();//s7
  if (logo.fine_text.length > 0) {
    ctx.translate(metrics.summed_width - metrics.pad_right, metrics.summed_height - metrics.pad_bottom);
    ctx.font = metrics.fine_txt_font;
    ctx.textAlign = "right";
    ctx.fillText(logo.fine_text, 0,0);
  }
  ctx.restore();//s2
  ctx.restore();//s1
}

function create_canvas(c_width, c_height, c_id, c_title, c_display) {
  "use strict";
  var canvas = document.createElement("canvas");
  //check for canvas support before attempting anything
  if (!canvas.getContext) {
    return null;
  }
  var ctx = canvas.getContext('2d');
  //check for html5 text drawing support
  if (!supports_text(ctx)) {
    return null;
  }
  //size the canvas
  canvas.width = c_width;
  canvas.height = c_height;
  canvas.id = c_id;
  canvas.title = c_title;
  canvas.style.display = c_display;
  return canvas;
}

function logo_1(alphabet, fine_text, pspm) {
  "use strict";
  var logo = new Logo(alphabet, fine_text);
  logo.add_pspm(pspm);
  return logo;
}

function logo_2(alphabet, fine_text, target, query, query_offset) {
  "use strict";
  var logo = new Logo(alphabet, fine_text);
  if (query_offset < 0) {
    logo.add_pspm(target, -query_offset);
    logo.add_pspm(query);
  } else {
    logo.add_pspm(target);
    logo.add_pspm(query, query_offset);
  }      
  return logo;
}

/*
 * Specifies an alternate source for an image.
 * If the image with the image_id specified has
 * not loaded then a generated logo will be used 
 * to replace it.
 *
 * Note that the image must either have dimensions
 * or a scale must be set.
 */
function alternate_logo(logo, image_id, scale) {
  "use strict";
  var image = document.getElementById(image_id);
  if (!image) {
    alert("Can't find specified image id (" +  image_id + ")");
    return;
  }
  //if the image has loaded then there is no reason to use the canvas
  if (image_ok(image)) {
    return;
  }
  //the image has failed to load so replace it with a canvas if we can.
  var canvas = create_canvas(image.width, image.height, image_id, image.title, image.style.display);
  if (canvas === null) {
    return;
  }
  //draw the logo on the canvas
  draw_logo_on_canvas(logo, canvas, null, scale);
  //replace the image with the canvas
  image.parentNode.replaceChild(canvas, image);
}

/*
 * Specifes that the element with the specified id
 * should be replaced with a generated logo.
 */
function replace_logo(logo, replace_id, scale, title_txt, display_style) {
  "use strict";
  var element = document.getElementById(replace_id);
  if (!replace_id) {
    alert("Can't find specified id (" + replace_id + ")");
    return;
  }
  //found the element!
  var canvas = create_canvas(50, 120, replace_id, title_txt, display_style);
  if (canvas === null) {
    return;
  }
  //draw the logo on the canvas
  draw_logo_on_canvas(logo, canvas, null, scale);
  //replace the element with the canvas
  element.parentNode.replaceChild(canvas, element);
}

/*
 * Fast string trimming implementation found at
 * http://blog.stevenlevithan.com/archives/faster-trim-javascript
 *
 * Note that regex is good at removing leading space but
 * bad at removing trailing space as it has to first go through
 * the whole string.
 */
function trim (str) {
  "use strict";
  var ws, i;
  str = str.replace(/^\s\s*/, '');
  ws = /\s/; i = str.length;
  while (ws.test(str.charAt(--i)));
  return str.slice(0, i + 1);
}
</script>
    <script>

// PRIVATE GLOBAL (uhoh)
var _block_colour_lookup = {};

function block_colour(index) {
  function hsl2rgb(hue, saturation, lightness) {
    "use strict";
    function _hue(p, q, t) {
      "use strict";
      if (t < 0) t += 1;
      else if (t > 1) t -= 1;
      if (t < (1.0 / 6.0)) {
        return p + ((q - p) * 6.0 * t);
      } else if (t < 0.5) {
        return q;
      } else if (t < (2.0 / 3.0)) {
        return p + ((q - p) * ((2.0 / 3.0) - t) * 6.0);
      } else {
        return p;
      }
    }
    function _pad_hex(value) {
      var hex = Math.round(value * 255).toString(16);
      if (hex.length < 2) hex = "0" + hex;
      return hex;
    }
    var r, g, b, p, q;
    if (saturation == 0) {
      // achromatic (grayscale)
      r = lightness;
      g = lightness;
      b = lightness;
    } else {
      if (lightness < 0.5) {
        q = lightness * (1 + saturation);
      } else {
        q = lightness + saturation - (lightness * saturation);
      }
      p = (2 * lightness) - q;
      r = _hue(p, q, hue + (1.0 / 3.0));
      g = _hue(p, q, hue);
      b = _hue(p, q, hue - (1.0 / 3.0));
    }
    return "#" + _pad_hex(r) + _pad_hex(g) + _pad_hex(b);
  }
  if (typeof index !== "number" || index % 1 !== 0 || index < 0) return "#000000";
  // check for override
  if (_block_colour_lookup[index] == null) {
    var start = 0; //red
    var sat = 100;
    var light = 50;
    var divisions = 1 << Math.ceil(Math.log(index + 1) / Math.LN2);
    hue = start + (360 / divisions) * ((index - (divisions >> 1)) * 2 + 1);
    // colour input fields only support values in the form #RRGGBB
    _block_colour_lookup[index] = hsl2rgb(hue / 360, sat / 100, light / 100);
  }
  return _block_colour_lookup[index];
}

function set_block_colour(index, new_colour) {
  _block_colour_lookup[index] = new_colour;
  var blocks = document.querySelectorAll("div.block_motif[data-colour-index=\"" + index + "\"]");
  var i;
  for (i = 0; i < blocks.length; i++) {
    blocks[i].style.backgroundColor = new_colour;
  }
  var swatches = document.querySelectorAll("div.legend_swatch[data-colour-index=\"" + index + "\"]");
  var picker;
  for (i = 0; i < swatches.length; i++) {
    swatches[i].style.backgroundColor = new_colour;
    picker = swatches[i].querySelector("input[type=\"color\"]");
    if (picker != null) picker.value = new_colour;
  }
}

function make_block_legend_entry(motif_name, motif_colour_index) {
  if (typeof make_block_legend_entry.has_colour_picker !== "boolean") {
    // test if colour picker is supported, based off Modernizer
    // see http://stackoverflow.com/a/7787648/66387
    make_block_legend_entry.has_colour_picker = (function() {
      var doc_ele = document.documentElement;
      // We first check to see if the type we give it sticks..
      var input_ele = document.createElement('input');
      input_ele.setAttribute('type', 'color');
      var value_ok = input_ele.type !== 'text';
      if (value_ok) {
        // If the type does, we feed it a textual value, which shouldn't be valid.
        // If the value doesn't stick, we know there's input sanitization which infers a custom UI
        var smile = ':)';
        input_ele.value = smile;
        input_ele.style.cssText = 'position:absolute;visibility:hidden;';
        // chuck into DOM and force reflow for Opera bug in 11.00
        // github.com/Modernizr/Modernizr/issues#issue/159
        doc_ele.appendChild(input_ele);
        doc_ele.offsetWidth;
        value_ok = input_ele.value != smile;
        doc_ele.removeChild(input_ele);
      }
      return value_ok;
    })();
  }
  var entry = document.createElement("div");
  entry.className = "legend_entry";
  var swatch;
  swatch = document.createElement("div");
  swatch.className = "legend_swatch";
  swatch.setAttribute("data-colour-index", motif_colour_index);
  swatch.style.backgroundColor = block_colour(motif_colour_index);
  if (make_block_legend_entry.has_colour_picker) {
    var picker = document.createElement("input");
    picker.type = "color";
    picker.value = block_colour(motif_colour_index);
    picker.addEventListener("change", function(e) {
      set_block_colour(motif_colour_index, picker.value);
    }, false);
    swatch.addEventListener("click", function(e) {
      picker.click();
    }, false);
    swatch.appendChild(picker);
  }
  entry.appendChild(swatch);
  var name = document.createElement("div");
  name.className = "legend_text";
  name.appendChild(document.createTextNode(motif_name));
  entry.appendChild(name);
  return entry;
}

function make_block_ruler(max_len) {
  var container = document.createElement("div");
  container.className = "block_container";
  var step;
  if (max_len < 50) {
    step = 1;
  } else if (max_len < 100) {
    step = 2;
  } else if (max_len < 200) {
    step = 4;
  } else if (max_len < 500) {
    step = 10;
  } else if (max_len < 1000) {
    step = 20;
  } else if (max_len < 2000) {
    step = 40;
  } else if (max_len < 5000) {
    step = 100;
  } else if (max_len < 10000) {
    step = 200;
  } else if (max_len < 20000) {
    step = 400;
  } else {
    step = Math.floor(max_len / 20000) * 400;
  }
  var peroid;
  if (max_len < 10) {
    peroid = 1;
  } else if (max_len < 20) {
    peroid = 2;
  } else {
    peroid = 5;
  }
  var i, cycle, offset, tic, label;
  for (i = 0, cycle = 0; i < max_len; i += step, cycle = (cycle + 1) % peroid) {
    offset = "" + ((i / max_len) * 100) + "%";
    tic = document.createElement("div");
    tic.style.left = offset;
    tic.className = (cycle == 0 ? "tic_major" : "tic_minor");
    container.appendChild(tic);
    if (cycle == 0) {
      label = document.createElement("div");
      label.className = "tic_label";
      label.style.left = offset;
      label.appendChild(document.createTextNode(i));
      container.appendChild(label);
    }
  }
  return container;
}

function _calculate_block_needle_drag_pos(e, data) {
  var mouse;
  e = e || window.event;
  if (e.pageX || ev.pageY) {
    mouse = {"x": e.pageX, "y": e.pageY};
  } else {
    mouse = {
      x:e.clientX + document.body.scrollLeft - document.body.clientLeft, 
      y:e.clientY + document.body.scrollTop  - document.body.clientTop 
    };
  }
  var cont = data.container;
  var dragable_length = cont.clientWidth - 
    (cont.style.paddingLeft ? cont.style.paddingLeft : 0) -
    (cont.style.paddingRight ? cont.style.paddingRight : 0);
  //I believe that the offset parent is the body
  //otherwise I would need to make this recursive
  //maybe clientLeft would work, but the explanation of
  //it is hard to understand and it apparently doesn't work
  //in firefox 2.
  var diff = mouse.x - cont.offsetLeft;
  if (diff < 0) diff = 0;
  if (diff > dragable_length) diff = dragable_length;
  var pos = Math.round(diff / dragable_length * data.max);
  if (pos > data.len) pos = data.len;
  return pos;
}

function _update_block_needle_drag(e, data, done) {
  "use strict";
  var pos = _calculate_block_needle_drag_pos(e, data);
  // read the needle positions
  var left = parseInt(data.llabel.textContent, 10) - data.off - 1;
  var right = parseInt(data.rlabel.textContent, 10) - data.off;
  // validate needle positions
  if (left >= data.len) left = data.len - 1;
  if (left < 0) left = 0;
  if (right > data.len) right = data.len;
  if (right <= left) right = left + 1;
  // calculate the new needle positions
  if (data.moveboth) {
    var size = right - left;
    if (data.isleft) {
      if ((pos + size) > data.len) pos = data.len - size;
      left = pos;
      right = pos + size;
    } else {
      if ((pos - size) < 0) pos = size;
      left = pos - size;
      right = pos;
    }
  } else {
    if (data.isleft) {
      if (pos >= right) pos = right - 1;
      left = pos;
    } else {
      if (pos <= left) pos = left + 1;
      right = pos;
    }
  }
  // update the needle positions
  data.lneedle.style.left = "" + (left / data.max * 100) + "%";
  data.llabel.textContent = "" + (left + data.off + 1);
  data.rneedle.style.left = "" + (right / data.max * 100) + "%";
  data.rlabel.textContent = "" + (right + data.off);
  data.handler(left, right, done);
}

function _make_block_needle_drag_start_handler(isleft, data) {
  return function (e) {
    data.isleft = isleft;
    data.moveboth = !(e.shiftKey);
    document.addEventListener("mousemove", data.drag_during, false);
    document.addEventListener("mouseup", data.drag_end, false);
  };
}

function _make_block_needle_drag_end_handler(data) {
  return function (e) {
    document.removeEventListener("mousemove", data.drag_during, false);
    document.removeEventListener("mouseup", data.drag_end, false);
    _update_block_needle_drag(e, data, true);
  };
}

function _make_block_needle_drag_during_handler(data) {
  return function (e) {
    _update_block_needle_drag(e, data, false);
  };
}

// private function used by make_block_container
function _make_block_needle(isleft, value, data) {
  var vbar = document.createElement('div');
  vbar.className = "block_needle " + (isleft ? "left" : "right");
  vbar.style.left = "" + (value / data.max * 100)+ "%";
  var label = document.createElement('div');
  label.className = "block_handle " + (isleft ? "left" : "right");
  // The needles sit between the sequence positions, so the left one sits at the
  // start and the right at the end. This is why 1 is added to the displayed
  // value for a left handle as the user doesn't need to know about this detail
  label.textContent = "" + (isleft ? value + data.off + 1 : value + data.off);
  label.unselectable = "on"; // so IE and Opera don't select the text, others are done in css
  label.title = "Drag to move the displayed range. Hold shift and drag to change " + (isleft ? "lower" : "upper") + " bound of the range.";
  vbar.appendChild(label);
  if (isleft) {
    data.lneedle = vbar;
    data.llabel = label;
  } else {
    data.rneedle = vbar;
    data.rlabel = label;
  }
  label.addEventListener("mousedown", _make_block_needle_drag_start_handler(isleft, data), false);
  return vbar;
}

function make_block_container(is_stranded, has_both_strands, max_len, show_len, offset, range_handler) {
  offset = (offset != null ? offset : 0);
  // make the container for the block diagram
  var container = document.createElement("div");
  container.className = "block_container";
  container.setAttribute("data-max", max_len);
  container.setAttribute("data-off", offset);
  if (is_stranded) {
    var plus = document.createElement("div");
    plus.appendChild(document.createTextNode("+"));
    plus.className = "block_plus_sym";
    container.appendChild(plus);
    if (has_both_strands) {
      var minus = document.createElement("div");
      minus.appendChild(document.createTextNode("-"));
      minus.className = "block_minus_sym";
      container.appendChild(minus);
    }
  }
  var rule = document.createElement("div");
  rule.className = "block_rule";
  rule.style.width = ((show_len / max_len) * 100) + "%";
  container.appendChild(rule);
  if (range_handler != null) {
    var range_data = {
      "max": max_len,
      "len": show_len,
      "off": offset,
      "handler": range_handler,
      "container": container,
      "lneedle": null, "llabel": null,
      "rneedle": null, "rlabel": null,
      "isleft": false, "moveboth" : false
    };
    range_data.drag_during = _make_block_needle_drag_during_handler(range_data);
    range_data.drag_end = _make_block_needle_drag_end_handler(range_data);
    container.appendChild(_make_block_needle(false, 1, range_data)); // add right first so z-index works
    container.appendChild(_make_block_needle(true, 0, range_data));
  }
  return container;
}

function make_block_label(container, max_len, pos, length, message) {
  "use strict";
  var label = document.createElement("div");
  label.className = "block_label";
  label.style.left = (((pos + (length / 2)) / max_len) * 100) + "%";
  label.appendChild(document.createTextNode(message));
  container.appendChild(label);
}

function make_block(container, max_len,
    site_pos, site_len, site_pvalue, site_rc, site_colour_index, site_secondary) {
  "use strict";
  var block_height, block, block_region1, block_region2;
  var max_block_height = 12;
  var max_pvalue = 1e-10;
  // calculate the height of the block
  block_height = (site_pvalue < max_pvalue ? max_block_height : 
      (Math.log(site_pvalue) / Math.log(max_pvalue)) * max_block_height);
  if (block_height < 1) block_height = 1;
  // create a block to represent the motif
  block = document.createElement("div");
  block.className = "block_motif" + (site_secondary ? " scanned_site" : "") + (site_rc ? " bottom" : " top");
  block.style.left = ((site_pos / max_len) * 100) + "%";
  block.style.top = (!site_rc ? max_block_height - block_height : 
      max_block_height + 1) + "px";
  block.style.width = ((site_len / max_len) * 100) + "%";
  block.style.height = block_height + "px";
  block.style.backgroundColor = block_colour(site_colour_index);
  block.setAttribute("data-colour-index", site_colour_index);
  // add to container
  container.appendChild(block);
  var activator = function (e) {
    toggle_class(block, "active", true);
    var new_e = new e.constructor(e.type, e);
    block.dispatchEvent(new_e);
  };
  var deactivator = function (e) {
    toggle_class(block, "active", false);
    var new_e = new e.constructor(e.type, e);
    block.dispatchEvent(new_e);
  }
  // create a larger region to detect mouseover for the block
  block_region1 = document.createElement("div");
  block_region1.className = "block_region top" + 
    (site_secondary ? " scanned_site" : "") + (site_rc ? "" : " main");
  block_region1.style.left = block.style.left;
  block_region1.style.width = block.style.width;
  block_region1.addEventListener('mouseover', activator, false);
  block_region1.addEventListener('mouseout', deactivator, false);
  container.appendChild(block_region1);
  block_region2 = document.createElement("div");
  block_region2.className = "block_region bottom" + 
    (site_secondary ? " scanned_site" : "") + (site_rc ? " main" : "");
  block_region2.style.left = block.style.left;
  block_region2.style.width = block.style.width;
  block_region2.addEventListener('mouseover', activator, false);
  block_region2.addEventListener('mouseout', deactivator, false);
  container.appendChild(block_region2);
  return block;
}

function set_block_needle_positions(containingNode, start, end) {
  var container, lneedle, llabel, rneedle, rlabel, max, off, left, right;
  container = (/\bblock_container\b/.test(containingNode.className) ? containingNode : containingNode.querySelector(".block_container"));
  max = parseInt(container.getAttribute("data-max"), 10);
  off = parseInt(container.getAttribute("data-off"), 10);
  left = start - off;
  right = end - off;
  lneedle = containingNode.querySelector(".block_needle.left");
  llabel = lneedle.querySelector(".block_handle.left");
  rneedle = containingNode.querySelector(".block_needle.right");
  rlabel = rneedle.querySelector(".block_handle.right");
  // update the needle positions
  lneedle.style.left = "" + (left / max * 100) + "%";
  llabel.textContent = "" + (left + off + 1);
  rneedle.style.left = "" + (right / max * 100) + "%";
  rlabel.textContent = "" + (right + off);
}

function get_block_needle_positions(containingNode) {
  var container, llabel, rlabel, max, off, left, right;
  container = (/\bblock_container\b/.test(containingNode.className) ? containingNode : containingNode.querySelector(".block_container"));
  max = parseInt(container.getAttribute("data-max"), 10);
  off = parseInt(container.getAttribute("data-off"), 10);
  llabel = containingNode.querySelector(".block_needle.left > .block_handle.left");
  rlabel = containingNode.querySelector(".block_needle.right > .block_handle.right");
  left = parseInt(llabel.textContent, 10) - off - 1;
  right = parseInt(rlabel.textContent, 10) - off;
  return {"start": left + off, "end": right + off};
}
</script>
    <script>
function make_alpha_bg_table(alph, freqs) {
  function colour_symbol(index) {
    var span = document.createElement("span");
    span.appendChild(document.createTextNode(alph.get_symbol(index)));
    span.style.color = alph.get_colour(index);
    span.className = "alpha_symbol";
    return span;
  }
  var table, thead, tbody, row, th, span, i;
  // create table
  table = document.createElement("table");
  table.className = "alpha_bg_table";
  // create header
  thead = document.createElement("thead");
  table.appendChild(thead);
  row = thead.insertRow(thead.rows.length);
  if (alph.has_complement()) {
    add_text_header_cell(row, "Name", "pop_alph_name");
    if (freqs != null) add_text_header_cell(row, "Freq.", "pop_alph_freq");
    if (alph.has_bg()) add_text_header_cell(row, "Bg.", "pop_alph_bg");
    add_text_header_cell(row, "");
    add_text_header_cell(row, "");
    add_text_header_cell(row, "");
    if (alph.has_bg()) add_text_header_cell(row, "Bg.", "pop_alph_bg");
    if (freqs != null) add_text_header_cell(row, "Freq.", "pop_alph_freq");
    add_text_header_cell(row, "Name", "pop_alph_name");
  } else {
    add_text_header_cell(row, "");
    add_text_header_cell(row, "Name", "pop_alph_name");
    if (freqs != null) add_text_header_cell(row, "Freq.", "pop_alph_freq");
    if (alph.has_bg()) add_text_header_cell(row, "Bg.", "pop_alph_bg");
  }
  // add alphabet entries
  tbody = document.createElement("tbody");
  table.appendChild(tbody);
  if (alph.has_complement()) {
    for (i = 0; i < alph.get_size_core(); i++) {
      var c = alph.get_complement(i);
      if (i > c) continue;
      row = tbody.insertRow(tbody.rows.length);
      add_text_cell(row, alph.get_name(i));
      if (freqs != null) add_text_cell(row, "" + freqs[i].toFixed(3));
      if (alph.has_bg()) add_text_cell(row, "" + alph.get_bg_freq(i).toFixed(3));
      add_cell(row, colour_symbol(i)); 
      add_text_cell(row, "~");
      add_cell(row, colour_symbol(c)); 
      if (alph.has_bg()) add_text_cell(row, "" + alph.get_bg_freq(c).toFixed(3));
      if (freqs != null) add_text_cell(row, "" + freqs[c].toFixed(3));
      add_text_cell(row, alph.get_name(c));
    }
  } else {
    for (i = 0; i < alph.get_size_core(); i++) {
      row = tbody.insertRow(tbody.rows.length);
      add_cell(row, colour_symbol(i)); 
      add_text_cell(row, alph.get_name(i));
      if (freqs != null) add_text_cell(row, "" + freqs[i].toFixed(3));
      if (alph.has_bg()) add_text_cell(row, "" + alph.get_bg_freq(i).toFixed(3));
    }
  }
  return table;
}

</script>
    <script>
var current_motif = 0;
var meme_alphabet = new Alphabet(data.alphabet, data.background.freqs);

var DelayLogoTask = function(logo, canvas) {
  this.logo = logo;
  this.canvas = canvas;
};

DelayLogoTask.prototype.run = function () {
  draw_logo_on_canvas(this.logo, this.canvas, false);
};

function motif_pspm(index) {
  var motif, pwm, psm, name, ltrim, rtrim, nsites, evalue;
  // get motif
  motif = data["motifs"][index];
  // get motif paramters
  pwm = motif["pwm"]; 
  psm = motif["psm"];
  name = "" + (index + 1); ltrim = 0; rtrim = 0; 
  nsites = motif["nsites"]; evalue = motif["evalue"];
  // make pspm
  return new Pspm(pwm, name, ltrim, rtrim, nsites, evalue, psm);
}

function motif_count_matrix(index) {
  return motif_pspm(index).as_count_matrix();
}

function motif_prob_matrix(index) {
  return motif_pspm(index).as_probability_matrix();
}

function motif_minimal_meme(index) {
  return motif_pspm(index).as_meme({
    "with_header": true, 
    "with_pspm": true,
    "with_pssm": true,
    "version": data["version"],
    "alphabet": meme_alphabet,
    "strands": (meme_alphabet.has_complement() && data.options.revcomp ? 2 : 1)
  });
}

function motif_fasta(index) {
  "use strict";
  var motif, sites, site, seq, sequences, sequence, i, num, counter, out;
  counter = {};
  sequences = data["sequence_db"]["sequences"];
  motif = data["motifs"][index];
  sites = motif["sites"];
  out = "";
  for (i = 0; i < sites.length; i++) {
    site = sites[i];
    seq = site["seq"];
    sequence = sequences[seq];
    counter[seq] = (num = counter[seq]) ? (++num) : (num = 1); // inc counter
    if (i !== 0) {out += "\n";}
    out += ">" + sequence["name"] + "_site_" + num + " offset= " + site["pos"] + 
      (site["rc"] ? " RC\n" : "\n");
    out += site["match"];
  }
  return out;
}

function motif_raw(index) {
  "use strict";
  var sites, i, out;
  sites = data["motifs"][index]["sites"];
  out = "";
  for (i = 0; i < sites.length; i++) {
    if (i !== 0) {out += "\n";}
    out += sites[i]["match"];
  }
  return out;
}

function clone_template(template) {
  "use strict";
  var node, help_btns, i, button;
  node = $(template).cloneNode(true);
  toggle_class(node, "template", false);
  node.id = "";
  help_btns = node.querySelectorAll(".help");
  for (i = 0; i < help_btns.length; i++) {
    button = help_btns[i];
    if (button.hasAttribute("data-topic")) {
      button.tabIndex = "0";
      button.addEventListener("click", __toggle_help, false);
      button.addEventListener("keydown", __toggle_help, false);
    }
  }
  return node;
}

function set_tvar(template, tvar, value) {
  var node;
  node = find_child(template, tvar);
  if (node === null) {
    throw new Error("Template does not contain variable " + tvar);
  }
  node.innerHTML = "";
  if (typeof value !== "object") {
    node.appendChild(document.createTextNode(value));
  } else {
    node.appendChild(value);
  }
}

function make_logo(alphabet, pspm, rc, offset, className) {
  if (rc) pspm = pspm.copy().reverse_complement(alphabet);
  var logo = new Logo(alphabet, "");
  logo.add_pspm(pspm, offset);
  var canvas = document.createElement('canvas');
  canvas.height = 50;
  canvas.width = 0;
  canvas.className = className;
  size_logo_on_canvas(logo, canvas, false);
  add_draw_task(canvas, new DelayLogoTask(logo, canvas));
  return canvas;
}

function make_small_logo(alphabet, pspm, options) {
  if (typeof options === "undefined") options = {};
  if (options.rc) pspm = pspm.copy().reverse_complement(alphabet);
  var logo = new Logo(alphabet, {x_axis: false, y_axis: false});
  logo.add_pspm(pspm, (typeof options.offset === "number" ? options.offset : 0));
  var canvas = document.createElement('canvas');
  if (typeof options.className === "string") canvas.className = options.className;
  if (typeof options.width === "number" && options.width > 0) {
    canvas.height = 0;
    canvas.width = options.width;
    draw_logo_on_canvas(logo, canvas, false);
  } else {
    draw_logo_on_canvas(logo, canvas, false, 1/3);
  }
  return canvas;
}

function make_large_logo(alphabet, pspm, rc, offset, className) {
  if (rc) pspm = pspm.copy().reverse_complement(alphabet);
  var logo = new Logo(alphabet, "");
  logo.add_pspm(pspm, offset);
  var canvas = document.createElement('canvas');
  canvas.height = 200;
  canvas.width = 0;
  canvas.className = className;
  size_logo_on_canvas(logo, canvas, false);
  add_draw_task(canvas, new DelayLogoTask(logo, canvas));
  return canvas;
}

function make_sym_btn(symbol, title, action) {
  var box;
  box = document.createElement("div");
  box.tabIndex = 0;
  box.className = "sym_btn";
  box.appendChild(document.createTextNode(symbol));
  box.title = title;
  box.addEventListener('click', action, false);
  box.addEventListener('keydown', action, false);
  return box;
}

function make_seq(alphabet, seq) {
  var i, j, letter, lbox, sbox;
  sbox = document.createElement("span");
  for (i = 0; i < seq.length; i = j) {
    letter = seq.charAt(i);
    for (j = i+1; j < seq.length; j++) {
      if (seq.charAt(j) !== letter) {
        break;
      }
    }
    lbox = document.createElement("span");
    lbox.style.color = alphabet.get_colour(alphabet.get_index(letter));
    lbox.appendChild(document.createTextNode(seq.substring(i, j)));
    sbox.appendChild(lbox);
  }
  return sbox;
}

//
// make_pv_text
//
// Returns the string p-value, with the p italicised.
///
function make_pv_text() {
  var pv_text = document.createElement("span");
  var pv_italic_text = document.createElement("span");
  pv_italic_text.appendChild(document.createTextNode("p"));
  pv_italic_text.style.fontStyle = "italic";
  pv_text.appendChild(pv_italic_text);
  pv_text.appendChild(document.createTextNode("-value"));
  return pv_text;
}

function append_site_entries(tbody, motif, site_index, count) {
  "use strict";
  var i, end;
  var sites, site, sequences, sequence;
  var rbody;
  if (typeof count !== "number") {
    count = 20;
  }
  sequences = data["sequence_db"]["sequences"];
  sites = motif["sites"];
  end = Math.min(site_index + count, sites.length);
  for (i = site_index; i < end; i++) {
    site = sites[i];
    sequence = sequences[site["seq"]];

    rbody = tbody.insertRow(tbody.rows.length);
    add_text_cell(rbody, "" + (site["seq"] + 1) + ".", "site_num");
    add_text_cell(rbody, sequence["name"], "site_name");
    add_text_cell(rbody, site["rc"] ? "-" : "+", "site_strand");
    add_text_cell(rbody, site["pos"] + 1, "site_start");
    add_text_cell(rbody, site["pvalue"].toExponential(2), "site_pvalue");
    add_text_cell(rbody, site["lflank"], "site lflank");
    add_cell(rbody, make_seq(meme_alphabet, site["match"]), "site match");
    add_text_cell(rbody, site["rflank"], "site rflank");
  }
  return i;
}

function make_site_entries() {
  "use strict";
  var region;
  region = this;
  if (region.data_site_index >= region.data_motif["sites"].length) {
    // all sites created
    region.removeEventListener('scroll', make_site_entries, false);
    return;
  }
  // if there's still 100 pixels to scroll than don't do anything yet
  if (region.scrollHeight - (region.scrollTop + region.offsetHeight) > 100) {
    return;
  }

  region.data_site_index = append_site_entries(
      find_child(region, "sites_tbl").tBodies[0], 
      region.data_motif, region.data_site_index, 20
    ); 
}

function make_sites(motif) {
  "use strict";
  function add_site_header(row, title, nopad, help_topic, tag_class) {
    var div, divcp, th;
    th = document.createElement("th");
    div = document.createElement("div");
    div.className = "sites_th_inner";
    if (typeof title !== "object") {
      title = document.createTextNode("" + title);
    }
    div.appendChild(title);
    if (help_topic) {
      div.appendChild(document.createTextNode("\xA0"));
      div.appendChild(help_button(help_topic));
    }
    divcp = div.cloneNode(true);
    divcp.className = "sites_th_hidden";
    th.appendChild(div);
    th.appendChild(divcp);
    if (nopad) {
      th.className = "nopad";
    }
    if (tag_class) {
      th.className += " " + tag_class;
    }
    row.appendChild(th);
  }
  var outer_tbl, inner_tbl, tbl, thead, tbody, rhead;

  outer_tbl = document.createElement("div");
  outer_tbl.className = "sites_outer";

  inner_tbl = document.createElement("div");
  inner_tbl.className = "sites_inner";
  outer_tbl.appendChild(inner_tbl);

  tbl = document.createElement("table");
  tbl.className = "sites_tbl";
  inner_tbl.appendChild(tbl);

  thead = document.createElement("thead");
  tbl.appendChild(thead);
  tbody = document.createElement("tbody");
  tbl.appendChild(tbody);

  rhead = thead.insertRow(thead.rows.length);
  add_site_header(rhead, "", true);
  add_site_header(rhead, "Name", false, "pop_seq_name");
  add_site_header(rhead, "Strand", false, "pop_site_strand", "site_strand_title");
  add_site_header(rhead, "Start", false, "pop_site_start");
  add_site_header(rhead, make_pv_text(), false, "pop_site_pvalue");
  add_site_header(rhead, "", false);
  add_site_header(rhead, "Sites", true, "pop_site_match");
  add_site_header(rhead, "", false);

  inner_tbl.data_motif = motif;
  inner_tbl.data_site_index = append_site_entries(tbody, motif, 0, 20);
  if (inner_tbl.data_site_index < motif["sites"].length) {
    inner_tbl.addEventListener('scroll', make_site_entries, false);
  }
  return outer_tbl;
}

function make_motif_table_entry(row, alphabet, ordinal, motif, colw) {
  "use strict";
  function ev_sig(evalue_str) {
    "use strict";
    var ev_re, match, sig, exp, num;
    ev_re = /^(.*)e(.*)$/;
    if (match = ev_re.exec(evalue_str)) {
      sig = parseFloat(match[1]);
      exp = parseInt(match[2]);
      if (exp >= 0) {
        return false;
      } else if (exp <= -3) {
        return true;
      } else {
        return sig * Math.pow(10, exp) <= 0.05;
      }
    }
    return true;
  }
  function make_preview(alphabet, motif) {
    "use strict";
    var pspm, preview, preview_rc;
    var box, btn_box, logo_box, btn_plus, btn_minus;
    if (motif["preview_logo"]) {
      preview = motif["preview_logo"];
      preview_rc = motif["preview_logo_rc"];
    } else {
      pspm = new Pspm(motif["pwm"]);
      preview = make_logo(alphabet, pspm);
      motif["preview_logo"] = preview;
      if (alphabet.has_complement()) {
        preview_rc = make_logo(alphabet, pspm, true, 0, "logo_rc");
        motif["preview_logo_rc"] = preview_rc;
      }
    }
    if (preview_rc) {
      btn_plus = document.createElement("div");
      btn_plus.appendChild(document.createTextNode("+"));
      btn_plus.className = "preview_btn plus";
      btn_plus.tabIndex = "0";
      btn_plus.addEventListener("click", action_btn_rc, false);
      btn_plus.addEventListener("keydown", action_btn_rc, false);
      btn_minus = document.createElement("div");
      btn_minus.appendChild(document.createTextNode("-"));
      btn_minus.className = "preview_btn minus";
      btn_minus.tabIndex = "0";
      btn_minus.addEventListener("click", action_btn_rc, false);
      btn_minus.addEventListener("keydown", action_btn_rc, false);
      btn_box = document.createElement("div");
      btn_box.className = "preview_btn_box";
      btn_box.appendChild(btn_plus);
      btn_box.appendChild(btn_minus);
    }
    logo_box = document.createElement("div");
    logo_box.className = "preview_logo_box";
    logo_box.appendChild(preview);
    if (preview_rc) logo_box.appendChild(preview_rc);
    box = document.createElement("div");
    box.className = "preview_box";
    if (preview_rc) box.appendChild(btn_box);
    box.appendChild(logo_box);
    if (preview_rc) {
      if (motif["rc"]) {
        btn_minus.className += " active";
        logo_box.className += " show_rc_logo";
      } else {
        btn_plus.className += " active";
      }
    }
    return box;
  }
  var pspm, preview, preview_rc, c;
  row.data_motif = motif;
  row.data_ordinal = ordinal;
  if (!ev_sig(motif["evalue"])) {
    row.style.opacity = 0.4;
  }
  add_text_cell(row, "" + ordinal + ".", "motif_ordinal");
  add_cell(row, make_preview(alphabet, motif), "motif_logo");
  add_text_cell(row, motif["evalue"], "motif_evalue");
  add_text_cell(row, motif["nsites"], "motif_nsites");
  add_text_cell(row, motif["len"], "motif_width");
  add_cell(row, make_sym_btn("\u21A7", "Show more information.", 
        action_show_more), "motif_more");
  add_cell(row, 
      make_sym_btn("\u21E2", 
        "Submit the motif to another MEME Suite program or download it.",
        action_show_outpop), 
      "motif_submit");
  if (colw) {
    for (c = 0; c < row.cells.length; c++) {
      row.cells[c].style.minWidth = colw[c] + "px";
    }
  }
}

function make_motifs_table(alphabet, start_ordinal, motifs, colw, stop_reason) {
  var i, j;
  var tbl, thead, tbody, tfoot, row, preview;
  var motif, pspm;

  tbl = document.createElement("table");
  
  thead = document.createElement("thead");
  tbl.appendChild(thead);
  tbody = document.createElement("tbody");
  tbl.appendChild(tbody);
  tfoot = document.createElement("tfoot");
  tbl.appendChild(tfoot);

  row = thead.insertRow(thead.rows.length);
  add_text_header_cell(row, "", "", "motif_ordinal");
  add_text_header_cell(row, "Logo", "", "motif_logo");
  add_text_header_cell(row, "E-value", "pop_ev", "motif_evalue");
  add_text_header_cell(row, "Sites", "pop_sites", "motif_nsites");
  add_text_header_cell(row, "Width", "pop_width", "motif_width");
  add_text_header_cell(row, "More", "pop_more", "motif_more");
  add_text_header_cell(row, "Submit/Download", "pop_submit_dl", "motif_submit");

  for (i = 0; i < motifs.length; i++) {
    row = tbody.insertRow(tbody.rows.length);
    make_motif_table_entry(row, alphabet, start_ordinal + i, motifs[i], colw);
  }

  row = tfoot.insertRow(tfoot.rows.length);
  add_text_header_cell(row, stop_reason, "", "stop_reason", "", 6);

  return tbl;
}

function make_expanded_motif(alphabet, ordinal, motif, less_x, submit_x) {
  "use strict";
  var box, pspm, logo_box, large_logo, large_logo_rc, tab_logo, tab_logo_rc;
  var btn, offset, norc;

  box = clone_template("tmpl_motif_expanded");
  box.data_motif = motif;
  box.data_ordinal = ordinal;

  pspm = new Pspm(motif["pwm"]);
  if (typeof motif["rc"] !== "boolean") {
    motif["rc"] = false;
  }
  if (motif["large_logo"]) {
    large_logo = motif["large_logo"];
    large_logo_rc = motif["large_logo_rc"];
  } else {
    large_logo = make_large_logo(alphabet, pspm, false, 0);
    motif["large_logo"] = large_logo;
    if (alphabet.has_complement()) {
      large_logo_rc = make_large_logo(alphabet, pspm, true, 0, "logo_rc");
      motif["large_logo_rc"] = large_logo_rc;
    }
  }
  norc = (large_logo_rc == null);
  toggle_class(box, "norc", norc);

  logo_box = find_child(box, "tvar_logo");
  logo_box.appendChild(large_logo);
  if (large_logo_rc) logo_box.appendChild(large_logo_rc);
  toggle_class(logo_box, "show_rc_logo", motif["rc"]);

  tab_logo = find_child(box, "tvar_tab");
  tab_logo_rc = find_child(box, "tvar_tab_rc");

  toggle_class(tab_logo, "activeTab", !motif["rc"]);
  toggle_class(tab_logo_rc, "activeTab", motif["rc"]);

  tab_logo.addEventListener('click', action_rc_tab, false);
  tab_logo.addEventListener('keydown', action_rc_tab, false);
  tab_logo_rc.addEventListener('click', action_rc_tab, false);
  tab_logo_rc.addEventListener('keydown', action_rc_tab, false);

  set_tvar(box, "tvar_ordinal", ordinal); 
  set_tvar(box, "tvar_evalue", motif["evalue"]);
  set_tvar(box, "tvar_width", motif["len"]);
  set_tvar(box, "tvar_site_count", motif["nsites"]);
  set_tvar(box, "tvar_llr", motif["llr"]);
  set_tvar(box, "tvar_ic", motif["ic"]);
  set_tvar(box, "tvar_re", motif["re"]);
  set_tvar(box, "tvar_bt", motif["bt"]);
  set_tvar(box, "tvar_sites", make_sites(motif));

  offset = 32; // 1* 5px padding + 2 * 10px padding + 2 * 2px border + 3px ??

  btn = find_child(box, "tvar_less");
  btn.style.left = (less_x - offset) + "px";
  btn.addEventListener('click', action_show_less, false);
  btn.addEventListener('keydown', action_show_less, false);
  btn = find_child(box, "tvar_submit");
  btn.style.left = (submit_x - offset) + "px";
  btn.addEventListener('click', action_show_outpop, false);
  btn.addEventListener('keydown', action_show_outpop, false);
  return box;
}


//
//
///
function make_motifs() {
  "use strict";
  function pixel_value(str_in) {
    "use strict";
    var px_re, match;
    px_re = /^(\d+)px$/;
    if (match = px_re.exec(str_in)) {
      return parseInt(match[1], 10);
    }
    return 0;
  }
  var container, tbl;
  var colw, r, row, c, cell, cell_style, pad_left, pad_right;

  // make the motifs table
  container = $("motifs");
  container.innerHTML = ""; // clear content

  tbl = make_motifs_table(meme_alphabet, 1, data["motifs"], colw, data["stop_reason"]);
  container.appendChild(tbl);

  // measure table column widths
  colw = [];
  row = tbl.tBodies[0].rows[0];
  for (c = 0; c < row.cells.length; c++) {
    var padLeft, padRight;
    cell = row.cells[c];
    cell_style = window.getComputedStyle(cell, null);
    pad_left = pixel_value(cell_style.getPropertyValue("padding-left"));
    pad_right = pixel_value(cell_style.getPropertyValue("padding-right"));
    colw[c] = cell.clientWidth - pad_left - pad_right;
    if (typeof colw[c] !== "number" || colw[c] < 0) {
      colw[c] = 1;
    }
  }

  // set minimum table column widths on each row so later when we remove rows it still aligns
  for (r = 0; r < tbl.tBodies[0].rows.length; r++) {
    row = tbl.tBodies[0].rows[r];
    for (c = 0; c < row.cells.length; c++) {
      row.cells[c].style.minWidth = colw[c] + "px";
    }
  }

  // store the table column widths so we can create rows latter with the same minimums
  container.data_colw = colw;

  // calculate the x offset for the buttons
  row = tbl.tBodies[0].rows[0];
  container.data_more_x = coords(find_child(find_child(row, "motif_more"), "sym_btn"))[0];
  container.data_submit_x = coords(find_child(find_child(row, "motif_submit"), "sym_btn"))[0];

  draw_on_screen();
}

function make_meme_block(container, max_seq_len, is_scan, site) {
  "use strict";
  var motif = data.motifs[site.motif];
  var block = make_block(container, max_seq_len, site.pos, motif.len,
      site.pvalue, site.rc, site.motif, is_scan);
  var handler = (is_scan ?
      make_scan_popup(site, motif, block) :
      make_block_popup(site, motif, block));
  block.addEventListener("mouseover", handler, false);
  block.addEventListener("mouseout", handler, false);
}

function append_blocks_entries(tbody, seq_index, count) {
  "use strict";
  var i, end, j;
  var max_pvalue, max_block_height, max_seq_len, sequences;
  var sequence, sites, scans, scan;
  var container, plus, minus, rule, row;
  // define some constants
  max_seq_len = data.sequence_db.max_length;
  // determine how many to load
  end = Math.min(seq_index + count, data.sequence_db.sequences.length);
  for (i = seq_index; i < end; i++) {
    // get the sequence
    sequence = data.sequence_db.sequences[i];
    // make the containers for the block diagram
    container = make_block_container(meme_alphabet.has_complement(),
        data.options.revcomp, max_seq_len, sequence.length);
    // create blocks for the motif sites
    sites = sequence["sites"];
    for (j = 0; j < sites.length; j++)
      make_meme_block(container, max_seq_len, false, sites[j]);
    // create blocks for the scanned sites
    scan = data.scan[i];
    for (j = 0; j < scan.sites.length; j++)
      make_meme_block(container, max_seq_len, true, scan.sites[j]);
    // create a row for the sequence
    row = tbody.insertRow(tbody.rows.length);
    toggle_class(row, "empty_seq", sites.length == 0 && scan.sites.length == 0);
    toggle_class(row, "only_scan", sites.length == 0 && scan.sites.length > 0);
    add_text_cell(row, (i + 1) + ".", "blockdiag_num");
    add_text_cell(row, sequence["name"], "blockdiag_name");
    add_text_cell(row, scan["pvalue"].toExponential(2), "blockdiag_pvalue");
    add_cell(row, container, "block_td"); 
  }
  return end;
}

function make_blocks_entries() {
  "use strict";
  var region;
  region = this;
  if (region.data_blocks_index >= data["sequence_db"]["sequences"].length) {
    // all sites created
    region.removeEventListener('scroll', make_blocks_entries, false);
    return;
  }
  // if there's still 100 pixels to scroll than don't do anything yet
  if (region.scrollHeight - (region.scrollTop + region.offsetHeight) > 100) {
    return;
  }

  region.data_blocks_index = append_blocks_entries(
      find_child(region, "blocks_tbl").tBodies[0], 
      region.data_blocks_index, 20
    ); 
}

function make_blocks() {
  "use strict";
  function add_seqs_filter(container, id, checked, label_text, help_topic) {
    "use strict";
    var label, radio;
    radio = document.createElement("input");
    radio.type = "radio";
    radio.name = "seqs_display";
    radio.id = id;
    radio.checked = checked;
    radio.addEventListener('click', action_seqs_filter, false);
    label = document.createElement("label");
    label.appendChild(document.createTextNode(label_text));
    label.htmlFor = id;
    container.appendChild(radio);
    container.appendChild(label);
    if (help_topic) {
      container.appendChild(document.createTextNode("\xA0"));
      container.appendChild(help_button(help_topic));
    }
  }
  function add_blocks_header(row, title, nopad, help_topic) {
    "use strict";
    var div, divcp, th;
    th = document.createElement("th");
    div = document.createElement("div");
    div.className = "blocks_th_inner";
    if (typeof title !== "object") {
      title = document.createTextNode("" + title);
    }
    div.appendChild(title);
    if (help_topic) {
      div.appendChild(document.createTextNode("\xA0"));
      div.appendChild(help_button(help_topic));
    }
    divcp = div.cloneNode(true);
    divcp.className = "blocks_th_hidden";
    th.appendChild(div);
    th.appendChild(divcp);
    if (nopad) {
      th.className = "nopad";
    }
    row.appendChild(th);
  }
  var container;
  var page, view_height, outer_tbl, inner_tbl, tbl, thead, tbody, rhead;
  var in_view, i, seq_count;
  
  page = (document.compatMode === "CSS1Compat") ? document.documentElement : document.body;
  view_height = Math.max(page.clientHeight - 300, 300);

  container = $("blocks");
  toggle_class(container, "hide_empty_seqs", true);
  toggle_class(container, "hide_only_scan", true);
  container.innerHTML = "";
  add_seqs_filter(container, "rdo_sites_only", true, "Only Motif Sites", "pop_motif_sites");
  add_seqs_filter(container, "rdo_sites_and_scan", false, "Motif Sites+Scanned Sites", "pop_scanned_sites");
  add_seqs_filter(container, "rdo_all_seqs", false, "All Sequences", "pop_all_sequences");

  outer_tbl = document.createElement("div");
  outer_tbl.className = "blocks_outer";

  inner_tbl = document.createElement("div");
  inner_tbl.id = "blocks_scroll";
  inner_tbl.className = "blocks_inner";
  inner_tbl.style.maxHeight = view_height + "px";
  outer_tbl.appendChild(inner_tbl);

  tbl = document.createElement("table");
  tbl.className = "blocks_tbl";
  inner_tbl.appendChild(tbl);

  thead = document.createElement("thead");
  tbl.appendChild(thead);
  tbody = document.createElement("tbody");
  tbl.appendChild(tbody);

  rhead = thead.insertRow(thead.rows.length);
  add_blocks_header(rhead, "", true);
  add_blocks_header(rhead, "Name", false, "pop_seq_name");
  add_blocks_header(rhead, make_pv_text(), false, "pop_seq_pvalue");
  add_blocks_header(rhead, "Motif Location", false, "pop_motif_location");

  container.appendChild(outer_tbl);

  
  seq_count = data["sequence_db"]["sequences"].length;
  in_view = Math.max(Math.ceil(view_height / 25), 1);
  i = append_blocks_entries(tbody, 0, in_view);

  while (i < seq_count && inner_tbl.scrollHeight - (inner_tbl.scrollTop + inner_tbl.offsetHeight) < 400) {
    i = append_blocks_entries(tbody, i, 20);
  }
  inner_tbl.data_blocks_index = i;
  if (i < seq_count) {
    inner_tbl.addEventListener('scroll', make_blocks_entries, false);
  }
}

function make_scan_popup(site, motif) {
  return function (e) {
    "use strict";
    var pop, xy, padding, edge_padding, pop_left, pop_top, page_width;
    var lflank, match, rflank, pspm;
    if (!e) var e = window.event;
    pop = make_scan_popup.pop;
    if (e.type === "mouseover") {
      if (pop) return;
      pop = clone_template("tmpl_scan_info");
      pspm = new Pspm(motif.pwm);
      if (site.rc) pspm.reverse_complement(meme_alphabet);
      set_tvar(pop, "tvar_logo", make_small_logo(meme_alphabet, pspm, {"className": "scan_logo"}));
      set_tvar(pop, "tvar_motif", motif.id);
      set_tvar(pop, "tvar_pvalue", site.pvalue.toExponential(2));
      set_tvar(pop, "tvar_start", site.pos + 1);
      set_tvar(pop, "tvar_end", site.pos + motif.len);

      document.body.appendChild(pop);
      position_popup(this, pop);
      make_scan_popup.pop = pop;
    } else if (e.type === "mouseout") {
      if (pop) {
        pop.parentNode.removeChild(pop);
        make_scan_popup.pop = null;
      }
    }
  };
}

function make_block_popup(site, motif, block) {
  return function (e) {
    "use strict";
    var pop;
    var lflank, match, rflank, pspm, ruler, match_seq, match_width;
    if (!e) var e = window.event;
    pop = make_block_popup.pop;
    if (e.type === "mouseover") {
      if (pop) return;
      pop = clone_template("tmpl_block_info");
      pspm = new Pspm(motif.pwm);
      if (site.rc) { // must be dna
        pspm.reverse_complement(meme_alphabet);
        lflank = meme_alphabet.invcomp_seq(site.rflank);
        match = meme_alphabet.invcomp_seq(site.match);
        rflank = meme_alphabet.invcomp_seq(site.lflank);
      } else {
        lflank = site.lflank;
        match = site.match;
        rflank = site.rflank;
      }
      ruler = document.getElementById("measure_match");
      match_seq = make_seq(meme_alphabet, match);
      ruler.innerHTML = "";
      ruler.appendChild(match_seq);
      match_width = ruler.clientWidth;
      ruler.removeChild(match_seq);
      set_tvar(pop, "tvar_lflank", lflank);
      set_tvar(pop, "tvar_match", match_seq);
      set_tvar(pop, "tvar_rflank", rflank);
      set_tvar(pop, "tvar_logo_pad", lflank);
      set_tvar(pop, "tvar_logo", make_small_logo(meme_alphabet, pspm, {"width": match_width}));
      set_tvar(pop, "tvar_motif", motif.id);
      set_tvar(pop, "tvar_pvalue", site.pvalue.toExponential(2));
      set_tvar(pop, "tvar_start", site.pos + 1);
      set_tvar(pop, "tvar_end", site.pos + motif.len);

      document.body.appendChild(pop);
      position_popup(block, pop);
      make_block_popup.pop = pop;
    } else if (e.type === "mouseout") {
      if (pop) {
        pop.parentNode.removeChild(pop);
        make_block_popup.pop = null;
      }
    }
  };
}

function update_outpop_format(index) {
  switch(parseInt($("text_format").value)) {
    case 0: // count matrix
      $("outpop_text").value = motif_count_matrix(index);
      $("text_name").value = "motif_" + (index + 1) + "_counts.txt";
      break;
    case 1: // prob matrix
      $("outpop_text").value = motif_prob_matrix(index);
      $("text_name").value = "motif_" + (index + 1) + "_freqs.txt";
      break;
    case 2: // minimal meme
      $("outpop_text").value = motif_minimal_meme(index);
      $("text_name").value = "motif_" + (index + 1) + ".txt";
      break;
    case 3: // fasta
      $("outpop_text").value = motif_fasta(index);
      $("text_name").value = "motif_" + (index + 1) + "_fasta.txt";
      break;
    case 4: // raw
      $("outpop_text").value = motif_raw(index);
      $("text_name").value = "motif_" + (index + 1) + "_raw.txt";
      break;
    default:
      throw new Error("Unknown motif format");
  }
}

function update_outpop_motif(index) {
  "use strict";
  var motifs, motif, pspm, logo, canvas, num;
  motifs = data["motifs"];
  if (index < 0 || index >= motifs.length) {return;}
  current_motif = index;
  motif = motifs[index];
  pspm = new Pspm(motif["pwm"]);
  logo = new Logo(meme_alphabet, "");
  logo.add_pspm(pspm, 0);
  canvas = $("outpop_logo");
  canvas.width = canvas.width; // clear canvas
  draw_logo_on_canvas(logo, canvas, false);
  if (meme_alphabet.has_complement()) {
    pspm.reverse_complement(meme_alphabet);
    logo = new Logo(meme_alphabet, "");
    canvas = $("outpop_logo_rc");
    canvas.width = canvas.width; // clear canvas
    draw_logo_on_canvas(logo, canvas, false);
  }
  num = $("outpop_num");
  num.innerHTML = "";
  num.appendChild(document.createTextNode("" + (index + 1)));
  update_outpop_format(index);
}

//
// action_show_more
//
// Show more information on the motif.
///
function action_show_more(e) {
  var node, tr, tbody, table, container, motif, ordinal;
  var expanded_motif;
  if (!e) e = window.event;
  if (e.type === "keydown") {
    if (e.keyCode !== 13 && e.keyCode !== 32) {
      return;
    }
    // stop a submit or something like that
    e.preventDefault();
  }
  // find the row that contains the cell
  node = this;
  do {
    if (node.tagName === "TR") break;
  } while (node = node.parentNode);
  if (!node) throw new Error("Expected to find row!?");
  tr = node;
  // get info
  motif = tr.data_motif;
  ordinal = tr.data_ordinal;
  // find tbody
  do {
    if (node.tagName === "TBODY") break;
  } while (node = node.parentNode);
  if (!node) throw new Error("Expected to find tbody!?");
  tbody = node;
  // find table
  do {
    if (node.tagName === "TABLE") break;
  } while (node = node.parentNode);
  if (!node) throw new Error("Expected to find table!?");
  table = node;
  // find container
  container = node.parentNode;
  // make a expanded motif
  motif["expanded"] = true;
  expanded_motif = make_expanded_motif(meme_alphabet, ordinal, motif, 
      container.data_more_x, container.data_submit_x);
  // now determine how to place it
  if (tbody.rows.length === 1) {
    // only us in the table so the table can be replaced
    container.replaceChild(expanded_motif, table);
  } else if (tbody.rows[0] === tr) {
    // first row, so remove and insert an expanded motif before
    table.deleteRow(tr.rowIndex);
    container.insertBefore(expanded_motif, table);
  } else if (tbody.rows[tbody.rows.length -1] === tr) {
    // last row, so remove and insert an expanded motif after
    table.deleteRow(tr.rowIndex);
    container.insertBefore(expanded_motif, table.nextSibling);
  } else {
    var table2, tbody2;
    table2 = table.cloneNode(false);
    table2.appendChild(table.tHead.cloneNode(true));
    tbody2 = table.tBodies[0].cloneNode(false);
    table2.appendChild(tbody2);
    container.insertBefore(table2, table.nextSibling);
    for (i = tbody.rows.length - 1; i >= 0; i--) {
      row = tbody.rows[i];
      row.parentNode.removeChild(row);
      if (row === tr) {
        break;
      }
      tbody2.insertBefore(row, tbody2.rows[0]);
    }
    container.insertBefore(expanded_motif, table2);
  }
  find_child(expanded_motif, "tvar_less").focus();
}

//
// action_show_less
//
// Show less information on the motif.
///
function action_show_less(e) {
  var btn;
  var expanded_motif, container, motif, ordinal, colw, focus_target;
  var table, tbody, tbody2, row, table_before, table_after;
  if (!e) e = window.event;
  if (e.type === "keydown") {
    if (e.keyCode !== 13 && e.keyCode !== 32) {
      return;
    }
    // stop a submit or something like that
    e.preventDefault();
  }
  btn = this;
  // find expanded motif
  expanded_motif = find_parent(btn, "expanded_motif");
  if (!expanded_motif) throw new Error("Expected expanded motif.");
  // find the container
  container = expanded_motif.parentNode;
  // get data
  motif = expanded_motif.data_motif;
  ordinal = expanded_motif.data_ordinal;
  colw = container.data_colw;
  // get the table before
  table_before = expanded_motif.previousSibling;
  if (table_before && table_before.tagName !== "TABLE") {
    table_before = null;
  }
  // get the table after
  table_after = expanded_motif.nextSibling;
  if (table_after && table_after.tagName !== "TABLE") {
    table_after = null;
  }
  // see if there is a table below or above that we can put this in.
  // if there is a table both below and above then add this motif and
  // all ones below to the above table
  motif["expanded"] = false;
  if (table_before && table_after) {
    tbody = table_before.tBodies[0];
    row = tbody.insertRow(tbody.rows.length);
    make_motif_table_entry(row, meme_alphabet, ordinal, motif, colw);
    focus_target = find_child(row.cells[5], "sym_btn");
    container.removeChild(expanded_motif);
    tbody2 = table_after.tBodies[0];
    while (tbody2.rows.length > 0) {
      row = tbody2.rows[0];
      row.parentNode.removeChild(row);
      tbody.appendChild(row);
    }
    container.removeChild(table_after);
  } else if (table_before) {
    tbody = table_before.tBodies[0];
    row = tbody.insertRow(tbody.rows.length);
    make_motif_table_entry(row, meme_alphabet, ordinal, motif, colw);
    focus_target = find_child(row.cells[5], "sym_btn");
    container.removeChild(expanded_motif);
  } else if (table_after) {
    tbody = table_after.tBodies[0];
    row = tbody.insertRow(0);
    make_motif_table_entry(row, meme_alphabet, ordinal, motif, colw);
    focus_target = find_child(row.cells[5], "sym_btn");
    container.removeChild(expanded_motif);
  } else {
    //no table above or below!
    // make a new table
    table = make_motifs_table(meme_alphabet, ordinal, [motif], colw, data["stop_reason"]);
    focus_target = find_child(table.tBodies[0].rows[0].cells[5], "sym_btn");
    container.replaceChild(table, expanded_motif);
  }
  focus_target.focus();
}

function action_show_outpop(e) {
  "use strict";
  function init() {
    "use strict";
    var close_btn, next_btn, prev_btn, cancel_btn, do_btn;
    var tab1, tab2, tab3;
    var pnl1, pnl2, pnl3;
    var format_list;
    var tbl_submit, inputs, i, default_prog;
    close_btn = $("outpop_close");
    close_btn.addEventListener("click", action_hide_outpop, false);
    close_btn.addEventListener("keydown", action_hide_outpop, false);
    next_btn = $("outpop_next");
    next_btn.addEventListener("click", action_outpop_next, false);
    next_btn.addEventListener("keydown", action_outpop_next, false);
    prev_btn = $("outpop_prev");
    prev_btn.addEventListener("click", action_outpop_prev, false);
    prev_btn.addEventListener("keydown", action_outpop_prev, false);
    cancel_btn = $("outpop_cancel");
    cancel_btn.addEventListener("click", action_hide_outpop, false);
    do_btn = $("outpop_do");
    do_btn.addEventListener("click", action_outpop_submit, false);
    tab1 = $("outpop_tab_1");
    tab1.tabIndex = 0;
    tab1.addEventListener("click", action_outpop_tab, false);
    tab1.addEventListener("keydown", action_outpop_tab, false);
    tab2 = $("outpop_tab_2");
    tab2.tabIndex = 0;
    tab2.addEventListener("click", action_outpop_tab, false);
    tab2.addEventListener("keydown", action_outpop_tab, false);
    tab3 = $("outpop_tab_3");
    tab3.tabIndex = 0;
    tab3.addEventListener("click", action_outpop_tab, false);
    tab3.addEventListener("keydown", action_outpop_tab, false);
    pnl1 = $("outpop_pnl_1");
    pnl2 = $("outpop_pnl_2");
    pnl3 = $("outpop_pnl_3");
    toggle_class(tab1, "activeTab", true);
    toggle_class(tab2, "activeTab", false);
    toggle_class(tab3, "activeTab", false);
    pnl1.style.display = "block";
    pnl2.style.display = "none";
    pnl3.style.display = "none";
    format_list = $("text_format");
    format_list.addEventListener("change", action_outpop_format, false);
    // setup program selection
    tbl_submit = $("programs");
    // when not dna, hide the inputs for programs that require dna motifs
    toggle_class(tbl_submit, "alphabet_dna", meme_alphabet.has_complement());//TODO FIXME alphabet_dna is a bad name for a field when allowing custom alphabets
    // add a click listener for the radio buttons
    inputs = tbl_submit.querySelectorAll("input[type='radio']");
    for (i = 0; i < inputs.length; i++) {
      inputs[i].addEventListener("click", action_outpop_program, false);
    }
    // ensure that a default program option is selected for DNA and Protein
    default_prog = document.getElementById(meme_alphabet.has_complement() ? "submit_tomtom" : "submit_fimo"); //TODO FIXME Tomtom might require a more strict definition of DNA
    default_prog.checked = true;
    action_outpop_program.call(default_prog);
    // disable reverse-complement when not DNA
    $("logo_rc_option").disabled = !meme_alphabet.has_complement(); 
    // set errorbars on when ssc is on
    $("logo_ssc").addEventListener("change", action_outpop_ssc, false);
  }
  var node;
  // store the focused element
  action_hide_outpop.last_active = document.activeElement;
  if (!e) e = window.event;
  if (e.type === "keydown") {
    if (e.keyCode !== 13 && e.keyCode !== 32) {
      return;
    }
    // stop a submit or something like that
    e.preventDefault();
  }
  // hide the help popup
  help_popup();
  // on first load initilize the popup
  if (!action_show_outpop.ready) {
    init();
    action_show_outpop.ready = true;
  }
  // load the motif logo
  node = this;
  do {
    if (/\bexpanded_motif\b/.test(node.className) || node.tagName === "TR") break;
  } while (node = node.parentNode);
  if (node === null) throw new Error("Expected node!");
  update_outpop_motif(node.data_ordinal - 1);
  // display the download popup
  $("grey_out_page").style.display = "block";
  $("download").style.display = "block";
  $("outpop_close").focus();
}

function action_hide_outpop(e) {
  if (!e) e = window.event;
  if (e.type === "keydown") {
    if (e.keyCode !== 13 && e.keyCode !== 32) {
      return;
    }
    // stop a submit or something like that
    e.preventDefault();
  }
  $("download").style.display = "none";
  $("grey_out_page").style.display = "none";
  if (typeof action_hide_outpop.last_active !== "undefined") {
    action_hide_outpop.last_active.focus();
  }
}

function action_outpop_next(e) {
  if (!e) e = window.event;
  if (e.type === "keydown") {
    if (e.keyCode !== 13 && e.keyCode !== 32) {
      return;
    }
    // stop a submit or something like that
    e.preventDefault();
  }
  update_outpop_motif(current_motif + 1);
}

function action_outpop_prev(e) {
  if (!e) e = window.event;
  if (e.type === "keydown") {
    if (e.keyCode !== 13 && e.keyCode !== 32) {
      return;
    }
    // stop a submit or something like that
    e.preventDefault();
  }
  update_outpop_motif(current_motif - 1);
}

function action_outpop_program() {
  "use strict";
  var table, tr, rows, i;
  tr = find_parent_tag(this, "TR");
  table = find_parent_tag(tr, "TABLE");
  rows = table.querySelectorAll("tr");
  for (i = 0; i < rows.length; i++) {
    toggle_class(rows[i], "selected", rows[i] === tr);
  }
}

function action_outpop_ssc() {
  "use strict";
  $("logo_err").value = $("logo_ssc").value;
}

function action_outpop_submit(e) {
  "use strict";
  var form, input, program, motifs;
  // find out which program is selected
  var radios, i;
  radios = document.getElementsByName("program");
  program = "fimo"; // default to fimo, since it works with all alphabet types
  for (i = 0; i < radios.length; i++) {
    if (radios[i].checked) program = radios[i].value;
  }

  motifs = motif_minimal_meme(current_motif);
  form = document.createElement("form");
  form.setAttribute("method", "post");
  form.setAttribute("action", site_url + "/tools/" + program);
  
  input = document.createElement("input");
  input.setAttribute("type", "hidden");
  input.setAttribute("name", "motifs_embed");
  input.setAttribute("value", motifs);
  form.appendChild(input);

  document.body.appendChild(form);
  form.submit();
  document.body.removeChild(form);
}

function action_outpop_download_motif(e) {
  $("text_form").submit();
}

function action_outpop_download_logo(e) {
  "use strict";
  $("logo_motifs").value = motif_minimal_meme(current_motif);
  $("logo_form").submit();
}

function action_btn_rc(e) {
  "use strict";
  var node, tr, motif, box, logo_box, tab_st, tab_rc, rc;
  if (!e) e = window.event;
  if (e.type === "keydown") {
    if (e.keyCode !== 13 && e.keyCode !== 32) {
      return;
    }
    // stop a submit or something like that
    e.preventDefault();
  }
  node = this;
  do {
    if (node.tagName === "TR") break;
  } while (node = node.parentNode);
  if (!node) throw new Error("Expected to find row!?");
  tr = node;
  // get info
  motif = tr.data_motif;
  box = find_parent(this, "preview_box");
  logo_box = find_child(box, "preview_logo_box");
  tab_st = find_child(box, "plus");
  tab_rc = find_child(box, "minus");
  rc = (this === tab_rc);
  motif["rc"] = rc;
  toggle_class(logo_box, "show_rc_logo", rc);
  toggle_class(tab_st, "active", !rc);
  toggle_class(tab_rc, "active", rc);
}

function action_rc_tab(e) {
  "use strict";
  var box, logo_box, tab_st, tab_rc, rc;
  if (!e) e = window.event;
  if (e.type === "keydown") {
    if (e.keyCode !== 13 && e.keyCode !== 32) {
      return;
    }
    // stop a submit or something like that
    e.preventDefault();
  }
  box = find_parent(this, "expanded_motif");
  logo_box = find_child(box, "tvar_logo");
  tab_st = find_child(box, "tvar_tab");
  tab_rc = find_child(box, "tvar_tab_rc");
  rc = (this === tab_rc);
  box.data_motif["rc"] = rc;
  toggle_class(logo_box, "show_rc_logo", rc);
  toggle_class(tab_st, "activeTab", !rc);
  toggle_class(tab_rc, "activeTab", rc);
}

function action_outpop_tab(e) {
  "use strict";
  var tab1, tab2, tab3, pnl1, pnl2, pnl3, do_btn;
  if (!e) e = window.event;
  if (e.type === "keydown") {
    if (e.keyCode !== 13 && e.keyCode !== 32) {
      return;
    }
    // stop a submit or something like that
    e.preventDefault();
  }
  tab1 = $("outpop_tab_1");
  tab2 = $("outpop_tab_2");
  tab3 = $("outpop_tab_3");
  pnl1 = $("outpop_pnl_1");
  pnl2 = $("outpop_pnl_2");
  pnl3 = $("outpop_pnl_3");
  do_btn = $("outpop_do");

  toggle_class(tab1, "activeTab", (this === tab1));
  toggle_class(tab2, "activeTab", (this === tab2));
  toggle_class(tab3, "activeTab", (this === tab3));
  pnl1.style.display = ((this === tab1) ? "block" : "none");
  pnl2.style.display = ((this === tab2) ? "block" : "none");
  pnl3.style.display = ((this === tab3) ? "block" : "none");
  do_btn.value = ((this === tab1) ? "Submit" : "Download");
  do_btn.removeEventListener("click", action_outpop_submit, false);
  do_btn.removeEventListener("click", action_outpop_download_logo, false);
  do_btn.removeEventListener("click", action_outpop_download_motif, false);
  if (this === tab1) {
    do_btn.addEventListener("click", action_outpop_submit, false);
  } else if (this === tab2) {
    do_btn.addEventListener("click", action_outpop_download_motif, false);
  } else {
    do_btn.addEventListener("click", action_outpop_download_logo, false);
  }
}

function action_seqs_filter() {
  "use strict";
  var block_container;
  block_container = $("blocks");
  if ($("rdo_all_seqs").checked) {
    toggle_class(block_container, "hide_empty_seqs", false);
    toggle_class(block_container, "hide_only_scan", false);
  } else if ($("rdo_sites_and_scan").checked) {
    toggle_class(block_container, "hide_empty_seqs", true);
    toggle_class(block_container, "hide_only_scan", false);
  } else if ($("rdo_sites_only").checked) {
    toggle_class(block_container, "hide_empty_seqs", true);
    toggle_class(block_container, "hide_only_scan", true);
  }
}

function action_outpop_format() {
  update_outpop_format(current_motif);
}

//
// page_loaded
//
// Called when the page has loaded for the first time.
///
function page_loaded() {
  post_load_setup();
}

//
// page_loaded
//
// Called when a cached page is reshown.
///
function page_shown(e) {
  if (e.persisted) post_load_setup();
}

//
// page_loaded
//
// Called when the page is resized
///
function page_resized() {
  var page, blocks_scroll;
  update_scroll_pad();
  page = (document.compatMode === "CSS1Compat") ? document.documentElement : document.body;
  blocks_scroll = $("blocks_scroll");
  if (blocks_scroll) {
    blocks_scroll.style.maxHeight = Math.max(page.clientHeight - 300, 300) + "px";
  }
}

//
// pre_load_setup
//
// Run before the page is displayed
///
function pre_load_setup() {
  var start, hue, sat, light, divisions;
  var i, j, motifs, motif, sites, site, sequences, sequence;
  var max_seq_len;
  motifs = data["motifs"];
  sequences = data["sequence_db"]["sequences"];
  max_seq_len = 1;
  for (i = 0; i < sequences.length; i++) {
    sequence = sequences[i];
    sequence["sites"] = [];
    if (sequence["length"] > max_seq_len) {
      max_seq_len = sequence["length"];
    }
  }
  data["sequence_db"]["max_length"] = max_seq_len;
  // use hsl colours
  start = 0; //red
  sat = 100;
  light = 50;
  for (i = 0; i < motifs.length; i++) {
    motif = motifs[i];
    // give the motif a colour
    divisions = 1 << Math.ceil(Math.log(i + 1) / Math.LN2);
    hue = start + (360 / divisions) * ((i - (divisions >> 1)) * 2 + 1);
    motif["colour"] = "hsl(" + hue + ", " + sat + "%, " + light + "%)";
    // associate sites with sequences as well 
    // to make generating the block diagram easier
    sites = motif["sites"];
    for (j = 0; j < sites.length; j++) {
      site = sites[j];
      sequence = sequences[site["seq"]];
      // record the motif index
      site["motif"] = i;
      // add the site to the sequence
      sequence["sites"].push(site);
    }
  }
}

//
// post_load_setup
//
// Run when the page has loaded, or been reloaded.
//
function post_load_setup() {
  update_scroll_pad();
  if (data["motifs"].length > 0) {
    make_motifs();
    make_blocks();
  } else {
    $("motifs").innerHTML = "<p>No significant motifs found!</p>"; // clear content
    $("motifs").innerHTML += "<p><b>" + data["stop_reason"] + "</b></p>";
    $("blocks").innerHTML = "<p>No significant motifs found!</p>";
  }
}

pre_load_setup();
</script>
    <style>
/* The following is the content of meme.css */
body { background-color:white; font-size: 12px; font-family: Verdana, Arial, Helvetica, sans-serif;}

div.help {
  display: inline-block;
  margin: 0px;
  padding: 0px;
  width: 12px;
  height: 13px;
  cursor: pointer;
  background-image: url(data:image/gif;base64,R0lGODlhDAANAIABANR0AP///yH5BAEAAAEALAAAAAAMAA0AAAIdhI8Xy22MIFgv1DttrrJ7mlGNNo4c+aFg6SQuUAAAOw==);
}

div.help:hover {
  background-image: url(data:image/gif;base64,R0lGODlhDAANAKEAANR0AP///9R0ANR0ACH+EUNyZWF0ZWQgd2l0aCBHSU1QACH5BAEAAAIALAAAAAAMAA0AAAIdDGynCe3PgoxONntvwqz2/z2K2ImjR0KhmSIZUgAAOw==);
}

p.spaced { line-height: 1.8em;}

span.citation { font-family: "Book Antiqua", "Palatino Linotype", serif; color: #004a4d;}

p.pad { padding-left: 30px; padding-top: 5px; padding-bottom: 10px;}

td.jump { font-size: 13px; color: #ffffff; background-color: #00666a;
  font-family: Georgia, "Times New Roman", Times, serif;}

a.jump { margin: 15px 0 0; font-style: normal; font-variant: small-caps;
  font-weight: bolder; font-family: Georgia, "Times New Roman", Times, serif;}

h2.mainh {font-size: 1.5em; font-style: normal; margin: 15px 0 0;
  font-variant: small-caps; font-family: Georgia, "Times New Roman", Times, serif;}

h2.line {border-bottom: 1px solid #CCCCCC; font-size: 1.5em; font-style: normal;
  margin: 15px 0 0; padding-bottom: 3px; font-variant: small-caps;
  font-family: Georgia, "Times New Roman", Times, serif;}

h4 {border-bottom: 1px solid #CCCCCC; font-size: 1.2em; font-style: normal;
  margin: 10px 0 0; padding-bottom: 3px; font-family: Georgia, "Times New Roman", Times, serif;}

h5 {margin: 0px}

a.help { font-size: 9px; font-style: normal; text-transform: uppercase;
  font-family: Georgia, "Times New Roman", Times, serif;}

div.pad { padding-left: 30px; padding-top: 5px; padding-bottom: 10px;}

div.pad1 { margin: 10px 5px;}

div.pad2 { margin: 25px 5px 5px;}
h2.pad2 { padding: 25px 5px 5px;}

div.pad3 { padding: 5px 0px 10px 30px;}

div.box { border: 2px solid #CCCCCC; padding:10px; overflow: hidden;}

div.bar { border-left: 7px solid #00666a; padding:5px; margin-top:25px; }

div.subsection {margin:25px 0px;}

img {border:0px none;}

th.majorth {text-align:left;}
th.minorth {font-weight:normal; text-align:left; width:8em; padding: 3px 0px;}
th.actionth {font-weight:normal; text-align:left;}

.explain h5 {font-size:1em; margin-left: 1em;}

div.doc {margin-left: 2em; margin-bottom: 3em;}

th.trainingset {
  border-bottom: thin dashed black; 
  font-weight:normal; 
  padding:0px 10px;
}
div.pop_content {
  position:absolute;
  z-index:50;
  width:300px;
  padding: 5px;
  background: #E4ECEC;
  font-size: 12px;
  font-family: Arial;
  border-style: double;
  border-width: 3px;
  border-color: #AA2244;
  display:none;
}

div.pop_content > *:first-child {
  margin-top: 0px;
}

div.pop_content h1, div.pop_content h2, div.pop_content h3, div.pop_content h4, 
div.pop_content h5, div.pop_content h6, div.pop_content p {
  margin: 0px;
}

div.pop_content p + h1, div.pop_content p + h2, div.pop_content p + h3, 
div.pop_content p + h4, div.pop_content p + h5, div.pop_content p + h6 {
  margin-top: 5px;
}

div.pop_content p + p {
  margin-top: 5px;
}

div.pop_content > *:last-child {
  margin-bottom: 0px;
}

div.pop_content div.pop_close {
  /* old definition */
  float:right;
  bottom: 0;
}

div.pop_content span.pop_close, div.pop_content span.pop_back {
  display: inline-block;
  border: 2px outset #661429;
  background-color: #CCC;
  padding-left: 1px;
  padding-right: 1px;
  padding-top: 0px;
  padding-bottom: 0px;
  cursor: pointer;
  color: #AA2244; /*#661429;*/
  font-weight: bold;
}

div.pop_content span.pop_close:active, div.pop_content span.pop_back:active {
  border-style: inset;
}

div.pop_content span.pop_close {
  float:right;
  /*border: 2px outset #AA002B;*/
  /*color: #AA2244;*/
}

div.pop_content:not(.nested) .nested_only {
  display: none;
}

div.pop_back_sec {
  margin-bottom: 5px;
}

div.pop_close_sec {
  margin-top: 5px;
}

table.hide_advanced tr.advanced {
  display: none;
}
span.show_more {
  display: none;
}
table.hide_advanced span.show_more {
  display: inline;
}
table.hide_advanced span.show_less {
  display: none;
}


/*****************************************************************************
 * Program logo styling
 ****************************************************************************/
div.prog_logo {
  border-bottom: 0.25em solid #0f5f60;
  height: 4.5em;
  width: 24em;
  display:inline-block;
}
div.prog_logo img {
  float:left;
  width: 4em;
  border-style: none;
  margin-right: 0.2em;
}
div.prog_logo h1, div.prog_logo h1:hover, div.prog_logo h1:active, div.prog_logo h1:visited {
  margin:0;
  padding:0;
  font-family: Arial, Helvetica,  sans-serif;
  font-size: 3.2em;
  line-height: 1em;
  vertical-align: top;
  display: block;
  color: #026666;
  letter-spacing: -0.06em;
  text-shadow: 0.04em 0.06em 0.05em #666;
}
div.prog_logo h2, div.prog_logo h2:hover, div.prog_logo h2:active, div.prog_logo h2:visited {
  display: block;
  margin:0;
  padding:0;
  font-family: Helvetica, sans-serif;
  font-size: 0.9em;
  line-height: 1em;
  letter-spacing: -0.06em;
  color: black;
}

div.big.prog_logo {
  font-size: 18px;
}

</style>
    <style>
.block_td {
  height:25px;
}
.block_container {
  position:relative;
  box-sizing: border-box;
  height: 25px;
  padding: 0px;
  margin: 0px;
  margin-left: 1em;
}
.block_label {
  position: absolute; 
  display: inline-block;
  padding: 3px;
  z-index: 4;
  top: 6px;
  height: 12px;
  line-height: 12px;
  font-size: 12px;
  background-color: white;
  border: 1px solid black;
  -moz-border-radius: 12px;
  -webkit-border-radius: 12px;
  border-radius: 12px;
  transform: translateX(-50%);
}
.block_motif {
  position: absolute; 
  z-index: 3;
  top: 0px;
  box-sizing: border-box;
  border: 1px solid black;
  height: 12px;
  background-color: cyan;
}
.block_motif.top {
  border-bottom-width: 0;
}
.block_motif.bottom {
  border-top-width: 0;
}
.block_motif.scanned_site {
  opacity: 0.3;
}
.block_motif.scanned_site.active {
  opacity: 0.9;
}
.block_region {
  position:absolute; 
  z-index:6; 
  height:25px; 
  top:0px; 
}
.block_region.main {
  z-index:8;
}
.block_region.scanned_site {
  z-index:5;
}
.block_region.scanned_site.main {
  z-index:7;
}
.block_region.top {
  height:13px;
}
.block_region.bottom {
  height:13px;
  top:12px;
}
.block_rule {
  position:absolute;
  z-index:2;
  width:100%;
  height:1px;
  top:12px;
  left:0px;
  background-color:gray;
}
.block_plus_sym {
  position:absolute;
  z-index:4;
  line-height:12px;
  top:0px;
  left:-1em;
}
.block_minus_sym {
  position:absolute;
  z-index:4;
  line-height:12px;
  top:13px;
  left:-1em;
}

.tic_major {
  position:absolute;
  top:0em;
  height:0.5em;
  width: 2px;
  margin-left: -1px;
  background-color: blue;
}
.tic_minor {
  position:absolute;
  top:0em;
  height:0.2em;
  width: 1px;
  margin-left: -0.5px;
  background-color: blue;
}
.tic_label {
  position:absolute;
  display: inline-block;
  top:0.5em;
  height: 1em;
  color: blue;
  transform: translateX(-50%);
}

.block_needle {
  position:absolute;
  z-index:4;
  height:30px; 
  width:1px; 
  top:-2px; 
  background-color:gray;
}
.block_needle.right {
  height: 60px;
}
.block_handle {
  position: absolute; 
  display: inline-block;
  z-index: 5; 
  top: 27px; 
  min-width: 3ex;
  text-align: center;
  font-size: 12px;
  line-height: 12px;
  transform: translateX(-50%);
  background-color: LightGrey; 
  border:3px outset grey;
  cursor: pointer;
  -webkit-user-select: none; /* Chrome/Safari */        
  -moz-user-select: none; /* Firefox */
  -ms-user-select: none; /* IE10+ */
  /* Rules below not implemented in browsers yet */
  -o-user-select: none;
  user-select: none;
}
.block_handle.right {
  top: 47px;
}

.legend_container {
  text-align: right;
}
.legend_entry {
  display: inline-block;
  padding: 5px;
}
div.legend_swatch {
  box-sizing: border-box;
  width: 15px;
  height: 15px;
  border: 1px solid black;
  background-color: cyan;
  float: left;
}
div.legend_swatch input {
  display: none;
}
.legend_text {
  line-height: 15px;
  margin-left: 20px;
}
</style>
    <style>
/* meme output specific css */

div.pop_block {
  position:absolute;
  z-index:5;
  padding: 5px;
  border: 1px solid black;
  display: inline-block;
  background-color: white;
}

#measure_match {
  position: absolute;
  visibility: hidden;
  height: auto;
  width: auto;
  white-space: nowrap;
}

div.template {
  position: absolute;
  z-index: 1;
  left: 0;
  top: 0;
  visibility: hidden;
}

table.block_information {
  margin-left: auto;
  margin-right: auto;
}

table.block_information * th {
  text-align: right;
}

*.hide_empty_seqs * tr.empty_seq {
  display: none;
}

*.hide_only_scan * tr.only_scan {
  display: none;
}

*.hide_only_scan * div.scanned_site {
  display: none;
}

td.symaction {
  text-align: center;
  text-decoration: underline;
  font-size: 20px;
  cursor: pointer;
}
div.sym_btn {
  display:inline-block;
  text-decoration: underline;
  cursor: pointer;
  font-size: 20px;
  line-height:20px; 
  text-align: center;
  width: 20px;
  height: 20px;
  color: blue;
}
div.sym_btn:hover {
  color: white;
  background-color: blue;
}

div.sym_btn.positioned {
  position: absolute;
  top: 0px;
}

div.actionbutton { 
  display:inline-block;
  cursor: pointer;
  font-size: 18px;
  line-height:20px; 
  padding: 5px; 
  margin: 10px 0; 
  border: 1px solid black;
}

div.actionbutton:hover {
  color:#FFF;
  background-color:#000;
}

div.param_box {
  display: inline-block;
  margin-right: 20px;
}

span.param {
  font-weight: bold;
}

div.box + div.box {
  margin-top: 5px;
}

div.sites_outer {
  position: relative;
  padding-top: 20px; /* height of header */
  display: inline-block;
}

div.sites_inner {
  overflow-x: hidden;
  overflow-y: auto;
  max-height: 200px;
}
table.sites_tbl {
  border-collapse: collapse;
}

div.sites_th_inner {
  position: absolute;
  top: 0;
  line-height: 20px; /* height of header */
  text-align: left;
  padding-left: 5px;
}
th.nopad div.sites_th_inner {
  padding-left: 0;
}
div.sites_th_hidden {
  visibility: hidden;
  height: 0;
  padding: 0 10px;
}
th.nopad div.sites_th_hidden {
  padding: 0;
}
div.sites_inner * th {
  height: 0;
}

table.sites_tbl {
  overflow-x: hidden;
  overflow-y: auto;
}

.site_num {
  text-align: right;
}
.site_name {
  padding:0px 5px; 
  text-align:left;
}
.site_strand {
  padding:0px 5px; 
  text-align:center;
}
.norc .site_strand, .norc .site_strand_title {
  display: none;
}
.site_start {
  padding:0px 15px; 
  text-align: right;
}
.site_pvalue {
  text-align:center; 
  padding:0px 15px; 
  text-align:right;
  white-space: nowrap;
}
.lflank, .rflank, .match, .alpha_symbol {
  font-weight:bold; 
  font-size:15px; 
  font-family: 'Courier New', Courier, monospace;
  color:gray; 
}

.site.lflank {
  text-align:right; 
  padding-right:5px; 
  color:gray;
}
.site.match {
  text-align:center; 
}
.site.rflank {
  text-align:left; 
  padding-left:5px; 
  padding-right: 20px;
}

th.stop_reason {
  text-align: left;
  padding-right: 10px;
}

th.motif_ordinal {

}
td.motif_ordinal {
  text-align: right;
  padding-right: 10px;
}
th.motif_logo {
  padding-right: 10px;
}
td.motif_logo {
  padding-right: 10px;
}
th.motif_evalue {
  text-align:right;
  padding-right: 10px;
}
td.motif_evalue {
  text-align: right;
  white-space: nowrap;
  padding-right: 20px;
}
th.motif_nsites {
  text-align: right;
  padding-right: 10px;
}
td.motif_nsites {
  text-align: right;
  padding-right: 20px;
}
th.motif_width {
  text-align: right;
  padding-right: 5px;
}
td.motif_width {
  text-align: right;
  padding-right: 15px;
}
th.motif_more {
  padding: 0 5px;
}
td.motif_more {
  text-align: center;
  padding: 0 5px;
}
th.motif_submit {
  padding: 0 5px;
}
td.motif_submit {
  text-align: center;
  padding: 0 5px;
}
th.motif_download {
  padding-left: 5px;
}
td.motif_download {
  text-align: center;
  padding-left: 5px;
}


div.tabArea {
  font-size: 80%;
  font-weight: bold;
}

.norc div.tabArea {
  display: none;
}

span.tab, span.tab:visited {
  cursor: pointer;
  color: #888;
  background-color: #ddd;
  border: 2px solid #ccc;
  padding: 2px 1em;
  text-decoration: none;
}
span.tab.middle {
  border-left-width: 0px;
}
div.tabArea.base span.tab {
  border-top-width: 0px;
}
div.tabArea.top span.tab {
  border-bottom-width: 0px;
}

span.tab:hover {
  background-color: #bbb;
  border-color: #bbb;
  color: #666;
}
span.tab.activeTab, span.tab.activeTab:hover, span.tab.activeTab:visited {
  background-color: white;
  color: black;
  cursor: default;
}
div.tabMain {
  border: 2px solid #ccc;
  background-color: white;
  padding: 10px;
}
div.tabMain.base {
  margin-top: 5px;
  display: inline-block;
  max-width: 98%;
}

div.tabMain.top {
  margin-bottom: 5px;
}

div.tabCenter {
  max-width: 100%;
  overflow-x: auto;
  height: 200px;
  overflow-y: hidden;
}

canvas.logo_rc {
  display:none;
}
.show_rc_logo > canvas {
  display: none;
}
.show_rc_logo > canvas.logo_rc {
  display: block;
}

canvas.scan_logo {
  margin-left: 10px;
}

div.blocks_outer {
  position: relative;
  padding-top: 20px; /* height of header */
}

div.blocks_inner {
  overflow-x: hidden;
  overflow-y: auto;
  max-height: 200px;
}
table.blocks_tbl {
  border-collapse: collapse;
  width: 100%;
}

div.blocks_th_inner {
  position: absolute;
  top: 0;
  line-height: 20px; /* height of header */
  text-align: left;
  padding-left: 5px;
}
th.nopad div.blocks_th_inner {
  padding-left: 0;
}
div.blocks_th_hidden {
  visibility: hidden;
  height: 0;
  padding: 0 10px;
}
th.nopad div.blocks_th_hidden {
  padding: 0;
}
div.blocks_inner * th {
  height: 0;
}

table.blocks_tbl {
  overflow-x: hidden;
  overflow-y: auto;
}
td.block_td {
  width: 99%;
}

*.blockdiag_num {
  text-align: right;
}

td.blockdiag_name {
  text-align: left;
  padding:0px 10px; 
}

td.blockdiag_pvalue {
  padding:0px 10px; 
  text-align:right;
  white-space: nowrap;
}

div.preview_btn {
  border: 2px solid white;
  height: 16px;
  width: 16px;
  font-size: 12px;
  line-height: 16px;
  text-align: center;
  cursor: pointer;
}
div.preview_btn + div.preview_btn {
  margin-top: 3px;
}

div.preview_btn.active {
  border: 2px solid black;
  cursor: default;
}

div.preview_btn:hover {
  background-color: black;
  color: white;
  border-color: black;
}

div.preview_btn.active:hover {
  background-color: white;
  color: black;
  border-color: black;
}


div.preview_btn_box {
  position: absolute;
  left: 0px;
  top: 0px;
  padding: 3px;
}

div.preview_logo_box {
  height: 50px;
  overflow-y: hidden;
}

div.preview_btn_box + div.preview_logo_box {
  margin-left: 25px;
}

div.preview_box {
  position: relative;
}

div.grey_background {
  position:fixed; 
  z-index: 8;
  background-color: #000;
  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
  opacity: 0.5;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
}

div.popup_wrapper {
  position:fixed; 
  z-index:9;
  width:100%; 
  height:0; 
  top:50%; 
  left:0;
}

div.popup {
  width: 600px; 
  z-index:9;
  margin-left: auto;
  margin-right: auto;
  padding: 5px;
  background-color: #FFF;
  border-style: double;
  border-width: 5px;
  border-color: #00666a;
  position:relative; 
}
div.close {
  cursor: pointer;
  border: 1px solid black; 
  width:15px; 
  height:15px; 
  line-height:15px; /* this causes vertical centering */
  text-align:center; 
  background-color:#FFF; 
  color:#000; 
  font-size:15px;
  font-family:monospace;
}

div.close:hover {
  color:#FFF;
  background-color:#000; 
}

div.navnum {
  width:100%; 
  height:20px; 
  line-height:20px; 
  text-align:center; 
  font-size:medium;
}

div.navarrow {
  font-size: 30px;
  text-decoration:none;
  cursor: pointer;
  -moz-user-select: none;  
  -webkit-user-select: none;  
  -ms-user-select: none;
}

div.navarrow > span.inactive {
  display: inline;
}
div.navarrow > span.active {
  display: none;
}

div.navarrow:hover > span.active {
  display: inline;
}
div.navarrow:hover > span.inactive {
  display: none;
}

table.programs {
  width: 100%;
}

table.programs tr {
  background-color: #EFE;
}

table.programs tr.selected {
  background-color: #262;
  color: #FFF;
}

table.programs tr.dna_only {
  display: none;
}

table.programs.alphabet_dna tr.dna_only {
  display: table-row;
}

div.programs_scroll {
  width: 100%; 
  height: 90px; 
  overflow-y: auto; 
  overflow-x: hidden;
  margin: 0 auto; 
}
table.inputs, table.alpha_bg_table {
  margin-top: 20px;
  border-collapse:collapse;
}
table.inputs * td, table.inputs * th, table.alpha_bg_table * td, table.alpha_bg_table * th {
  padding-left: 15px;
  padding-right: 15px;
  padding-top: 1px;
  padding-bottom: 1px;
}

table.hide_psp td.col_psp, table.hide_psp th.col_psp {
  display: none;
}

/* program settings */
span.mod_oops, span.mod_zoops, span.mod_anr {
  display: none;
}
td.oops span.mod_oops,td.zoops span.mod_zoops, td.anr span.mod_anr  {
  display: inline;
}
span.strand_none, span.strand_given, span.strand_both {
  display: none;
}
td.none span.strand_none, td.given span.strand_given, td.both span.strand_both {
  display: inline;
}
span.spmap_uni, span.spmap_pam {
  display: none;
}
td.uni span.spmap_uni, td.pam span.spmap_pam {
  display: inline;
}
span.prior_dirichlet, span.prior_dmix, span.prior_mega, span.prior_megap, span.prior_addone {
  display: none;
}
td.dirichlet span.prior_dirichlet, td.dmix span.prior_dmix, td.mega span.prior_mega,
td.megap span.prior_megap, td.addone span.prior_addone {
  display: inline;
}
span.noendgaps_on, span.noendgaps_off {
  display: none;
}
td.on span.noendgaps_on, td.off span.noendgaps_off {
  display: inline;
}
span.substring_on, span.substring_off {
  display: none;
}
td.on span.substring_on, td.off span.substring_off {
  display: inline;
}
</style>
  </head>
  <body onload="page_loaded()" onpageshow="page_shown(event)" onresize="page_resized()">
    <!--  -->
    <div id="grey_out_page" class="grey_background" style="display:none;">
    </div>
    <!-- Help popups -->
    <div class="pop_content" id="pop_">
      <p>Help poup.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_ev">
      <p>The statistical significance of the motif. MEME usually finds the most
      statistically significant (low E-value) motifs first. It is unusual to
      consider a motif with an E-value larger than 0.05 significant so, as an
      additional indicator, MEME displays these partially transparent.</p> 
      <p>The E-value of a motif is based on its log likelihood ratio, width,
      sites, the background letter frequencies (given in the command line
      summary), and the size of the training set.</p>
      <p>The E-value is an estimate of the expected number of motifs with the
      given log likelihood ratio (or higher), and with the same width and site
      count, that one would find in a similarly sized set of random
      sequences (sequences where each position is independent and letters are
      chosen according to the background letter frequencies).</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sites">
      <p>The number of sites contributing to the construction of the motif.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_width">
      <p>The width of the motif. Each motif describes a pattern of a fixed
      width, as no gaps are allowed in MEME motifs.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_more">
      <p>Click on the blue symbol below to reveal more information about this motif.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_submit_dl">
      <p>Click on the blue symbol below to reveal options allowing you
      to submit this motif to another MEME Suite motif analysis program, to download this
      motif in various text formats, or to download a sequence "logo" of
      this motif PNG or EPS format.</p>
      <h5>Supported Programs</h5>
      <dl>
        <dt>Tomtom</dt>
        <dd>Tomtom is a tool for searching for similar known motifs. 
          [<a href="http://meme-suite.org/doc/tomtom.html?man_type=web">manual</a>]</dd>
        <dt>MAST</dt>
        <dd>MAST is a tool for searching biological sequence databases for 
          sequences that contain one or more of a group of known motifs.
          [<a href="http://meme-suite.org/doc/mast.html?man_type=web">manual</a>]</dd>
        <dt>FIMO</dt>
        <dd>FIMO is a tool for searching biological sequence databases for 
          sequences that contain one or more known motifs.
          [<a href="http://meme-suite.org/doc/fimo.html?man_type=web">manual</a>]</dd>
        <dt>GOMO</dt>
        <dd>GOMO is a tool for identifying possible roles (Gene Ontology 
          terms) for DNA binding motifs.
          [<a href="http://meme-suite.org/doc/gomo.html?man_type=web">manual</a>]</dd>
        <dt>SpaMo</dt>
        <dd>SpaMo is a tool for inferring possible transcription factor
          complexes by finding motifs with enriched spacings.
          [<a href="http://meme-suite.org/doc/spamo.html?man_type=web">manual</a>]</dd>
      </dl>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_llr">
      <p>The log likelihood ratio of the motif.The log likelihood ratio is the 
      logarithm of the ratio of the probability of the occurrences of the motif
      given the motif model (likelihood given the motif) versus their
      probability given the background model (likelihood given the null model).
      (Normally the background model is a 0-order Markov model using the
      background letter frequencies, but higher order Markov models may be
      specified via the -bfile option to MEME.).</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_ic">
      <p>The information content of the motif in bits. It is equal to the sum
      of the uncorrected information content, R(), in the columns of the pwm.
      This is equal relative entropy of the motif relative to a uniform
      background frequency model.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_re">
      <p>The relative entropy of the motif.</p>

      <p style="font-family: monospace;">re = llr / (sites * ln(2))</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_bt">
      <p>The Bayes Threshold.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_site_strand">
      <p>The strand used for the motif site.</p>
      <dl>
        <dt>+</dt>
        <dd>The motif site was found in the sequence as it was supplied.</dd>
        <dt>-</dt>
        <dd>The motif site was found in the reverse complement of the supplied sequence.</dd>
      </dl>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_site_start">
      <p>The position in the sequence where the motif site starts. If a motif
      started right at the begining of a sequence it would be described as
      starting at position 1.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_site_pvalue">
      <p>The probability that an equal or better site would be found in a
      random sequence of the same length conforming to the background letter
      frequencies.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_site_match">
      <p>A motif site with the 10 flanking letters on either side.</p> 
      <p>When the site is not on the given strand then the site
      and both flanks are reverse complemented so they align.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>

    <div class="pop_content" id="pop_seq_name">
      <p>The name of the sequences as given in the FASTA file.</p>
      <p>The number to the left of the sequence name is the ordinal
      of the sequence.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>

    <div class="pop_content" id="pop_motif_sites">
      <p>These are the motif sites predicted by MEME and used to build the motif.</p>
      <p>These sites are shown in solid color and hovering the cursor
      over a site will reveal details about the site.  Only sequences
      that contain a motif site are shown.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>

    <div class="pop_content" id="pop_scanned_sites">
      <p>These are the motif sites predicted by MEME plus
      any additional sites detected using a motif scanning
      algorithm.</p>
      <p>These MEME sites are shown in solid color and 
      additional scanned sites are shown in transparent color.
      Hovering the cursor over a site will reveal details about the site.
      Only sequences containing a predicted or scanned motif site are shown.</p>
      <p>The scanned sites are predicted using a
      log-odds scoring matrix constructed from the MEME sites.
      Only scanned sites with position <i>p</i>-values less
      than 0.0001 are shown.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>

    <div class="pop_content" id="pop_all_sequences">
      <p>These are the same sites as shown by selecting the
      "Motif Sites + Scanned Sites" button except that all
      sequences, including those with no sites, are included
      in the diagram.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>

    <div class="pop_content" id="pop_seq_pvalue">
      <p>This is the combined match <i>p</i>-value.</p>
      <p>The combined match <i>p</i>-value is defined as the probability that a
      random sequence (with the same length and conforming to the background) 
      would have position <i>p</i>-values such that the product is smaller
      or equal to the value calulated for the sequence under test.</p>
      <p>The position <i>p</i>-value is defined as the probability that a
      random sequence (with the same length and conforming to the background)
      would have a match to the motif under test with a score greater or equal
      to the largest found in the sequence under test.</p>
      <p>Hovering your mouse over a motif site in the motif location 
      block diagram will show its position <i>p</i>-value and other information
      about the site.</p>
       
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_motif_location">
      <p>This diagram shows the location of motif sites.</p>
      <p>Each block shows the position and strength of a motif
      site.  The height of a block gives an indication of the 
      significance of the site as taller blocks are more significant. 
      The height is calculated to be proportional to the negative 
      logarithm of the <i>p</i>-value of the site, truncated at 
      the height for a <i>p</i>-value of 1e-10.</p>
      <p>For complementable alphabets (like DNA), sites on the 
      positive strand are shown above the line,
      sites on the negative strand are shown below.</p>
      <p>Placing the cursor
      over a motif site will reveal more information about the site
      including its position <i>p</i>-value.  (See the help
      for the <i>p</i>-value column for an explanation of position 
      <i>p</i>-values.)</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>

    <div class="pop_content" id="pop_seq_source">
      <p>The name of the file of sequences input to MEME.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_psp_source">
      <p>The position specific priors file used by MEME to find the motifs.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_seq_alph">
      <p>The alphabet used by the sequences.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_seq_count">
      <p>The number of sequences provided as input to MEME.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>

    <div class="pop_content" id="pop_alph_name">
      <p>The name of the alphabet symbol.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_alph_freq">
      <p>The frequency of the alphabet symbol in the dataset with a pseudocount
      so it is never zero.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_alph_bg">
      <p>The frequency of the alphabet symbol as defined by the background model.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>

    <!-- templates -->
    <div id="measure_match" class="match"></div>
    <div class="template pop_block" id="tmpl_block_info">
      <div>
        <span class="tvar_logo_pad lflank" style="visibility:hidden;"></span>
        <span class="tvar_logo"></span>
      </div>
      <div class="block_sequence_fragment">
        <span class="tvar_lflank lflank"></span>
        <span class="tvar_match match"></span>
        <span class="tvar_rflank rflank"></span>
      </div>
      <table class="block_information">
        <tr><th>Motif</th><td class="tvar_motif">1</td></tr>
        <tr><th><i>p</i>-value</th><td class="tvar_pvalue">8.23e-7</td></tr>
        <tr><th>Start</th><td class="tvar_start">23</td></tr>
        <tr><th>End</th><td class="tvar_end">33</td></tr>
      </table>
    </div>

    <div class="template pop_block" id="tmpl_scan_info">
      <h5>Scanned Site</h5>
      <div class="tvar_logo"></div>
      <table class="block_information">
        <tr><th>Motif</th><td class="tvar_motif">1</td></tr>
        <tr><th><i>p</i>-value</th><td class="tvar_pvalue">8.23e-7</td></tr>
        <tr><th>Start</th><td class="tvar_start">23</td></tr>
        <tr><th>End</th><td class="tvar_end">33</td></tr>
      </table>
    </div>

    <div class="template box expanded_motif" id="tmpl_motif_expanded">
      <div style="position: relative; min-height: 20px">
        <div class="param_box">
          <span class="param"><span class="tvar_ordinal"></span>.</span>
        </div>
        <div class="sym_btn positioned tvar_less" tabindex="0" 
          title="Show less information.">&#8613;</div>
        <div class="sym_btn positioned tvar_submit" tabindex="0"
          title="Submit the motif to another MEME Suite program or download it.">&#8674;</div>
      </div>
      <div>
        <div class="param_box">
          <span class="param"><i>E</i>-value:</span>
          <span class="tvar_evalue"></span>
          <div class="help" data-topic="pop_ev"></div>
        </div>
        <div class="param_box">
          <span class="param">Site Count:</span>
          <span class="tvar_site_count"></span>
          <div class="help" data-topic="pop_sites"></div>
        </div>
        <div class="param_box">
          <span class="param">Width:</span>
          <span class="tvar_width"></span>
          <div class="help" data-topic="pop_width"></div>
        </div>
      </div>
      <div class="tabMain base">
        <div class="tabCenter tvar_logo"></div>
      </div>
      <div class="tabArea base">
        <span class="tvar_tab tab" tabindex="0">Standard</span><span 
          class="tvar_tab_rc tab middle" tabindex="0">Reverse 
          Complement</span>
      </div>
      <div style="padding: 10px 0">
        <div class="param_box">
          <span class="param">Log Likelihood Ratio:</span>
          <span class="tvar_llr"></span>
          <div class="help" data-topic="pop_llr"></div>
        </div>
        <div class="param_box">
          <span class="param">Information Content:</span>
          <span class="tvar_ic"></span>
          <div class="help" data-topic="pop_ic"></div>
        </div>
        <div class="param_box">
          <span class="param">Relative Entropy:</span>
          <span class="tvar_re"></span>
          <div class="help" data-topic="pop_re"></div>
        </div>
        <div class="param_box">
          <span class="param">Bayes Threshold:</span>
          <span class="tvar_bt"></span>
          <div class="help" data-topic="pop_bt"></div>
        </div>
      </div>
      <div class="tvar_sites"></div>
    </div>


    <div class="popup_wrapper">
      <div class="popup" style="display:none; top: -150px;" id="download">
        <div>
          <div style="float:right; ">
            <div id="outpop_close" class="close" tabindex="0">x</div>
          </div>
          <h2 class="mainh" style="margin:0; padding:0;">Submit or Download</h2>
          <div style="clear:both"></div>
        </div>
          <div style="height:100px">
            <div style="float:right; width: 30px;">
              <div id="outpop_prev" class="navarrow" tabindex="0">
                <span class="inactive">&#8679;</span><span class="active">&#11014;</span>
              </div>
              <div id="outpop_num" class="navnum"></div>
              <div id="outpop_next" class="navarrow" tabindex="0">
                <span class="inactive">&#8681;</span><span class="active">&#11015;</span>
              </div>
            </div>
            <div id="logo_box" style="height: 100px; margin-right: 40px;">
              <canvas id="outpop_logo" height="100" width="580"></canvas>
              <canvas id="outpop_logo_rc" class="logo_rc" height="100" width="580"></canvas>
            </div>
          </div>
          <div>
            <!-- tabs start -->
            <div class="tabArea top">
              <span id="outpop_tab_1" class="tab">Submit Motif</span><span
                id="outpop_tab_2" class="tab middle">Download Motif</span><span 
                id="outpop_tab_3" class="tab middle">Download Logo</span>
            </div>
            <div class="tabMain top">
              <!-- Submit to another program -->
              <div id="outpop_pnl_1">
                <h4 class="compact">Submit to program</h4>
                <table id="programs" class="programs">
                  <tr class="dna_only">
                    <td><input type="radio" name="program" value="tomtom" id="submit_tomtom"></td>
                    <td><label for="submit_tomtom">Tomtom</label></td>
                    <td><label for="submit_tomtom">Find similar motifs in
                        published libraries or a library you supply.</label></td>
                  </tr>
                  <tr>
                    <td><input type="radio" name="program" value="fimo" id="submit_fimo"></td>
                    <td><label for="submit_fimo">FIMO</label></td>
                    <td><label for="submit_fimo">Find motif occurrences in
                        sequence data.</label></td>
                  </tr>
                  <tr>
                    <td><input type="radio" name="program" value="mast" id="submit_mast"></td>
                    <td><label for="submit_mast">MAST</label></td>
                    <td><label for="submit_mast">Rank sequences by affinity to
                        groups of motifs.</label></td>
                  </tr>
                  <tr class="dna_only">
                    <td><input type="radio" name="program" value="gomo" id="submit_gomo"></td>
                    <td><label for="submit_gomo">GOMo</label></td>
                    <td><label for="submit_gomo">Identify possible roles (Gene
                        Ontology terms) for motifs.</label></td>
                  </tr>
                  <tr class="dna_only">
                    <td><input type="radio" name="program" value="spamo" id="submit_spamo"></td>
                    <td><label for="submit_spamo">SpaMo</label></td>
                    <td><label for="submit_spamo">Find other motifs that are
                        enriched at specific close spacings which might imply the existance of a complex.</label></td>
                  </tr>
                </table>
              </div>
              <!-- download text format  -->
              <div id="outpop_pnl_2">
                <div>
                  <label for="text_format">Format:</label>
                  <select id="text_format">
                    <option value="0">Count Matrix</option>
                    <option value="1">Probability Matrix</option>
                    <option value="2">Minimal MEME</option>
                    <option value="3">FASTA</option>
                    <option value="4">Raw</option>
                  </select>
                </div>
                <form id="text_form" method="post" action="">
                  <script>$("text_form").action = site_url + "/utilities/save_generated_file";</script>
                  <input type="hidden" id="text_name" name="name" value="motif.txt">
                  <input type="hidden" name="mime_type" value="text/plain">
                  <textarea id="outpop_text" name="content"
                    style="width:99%; white-space: pre; word-wrap: normal; overflow-x: scroll;" 
                    rows="8" readonly="readonly" wrap="off"></textarea>
                </form>
              </div>
              <!-- download logo format -->
              <div id="outpop_pnl_3">
                <form id="logo_form" method="post" action="">
                  <script>$("logo_form").action = site_url + "/utilities/generate_logo";</script>
                  <input type="hidden" name="program" value="MEME"/>
                  <input type="hidden" id="logo_motifs" name="motifs" value=""/>
                  <table>
                    <tr>
                      <td><label for="logo_format">Format:</label></td>
                      <td>
                        <select id="logo_format" name="png">
                          <option value="1">PNG (for web)</option>
                          <option value="0">EPS (for publication)</option>
                        </select>
                      </td>
                    </tr>
                    <tr>
                      <td><label for="logo_rc">Orientation:</label></td>
                      <td>
                        <select id="logo_rc" name="rc1">
                          <option value="0">Normal</option>
                          <option value="1" id="logo_rc_option">Reverse Complement</option>
                        </select>
                      </td>
                    </tr>
                    <tr>
                      <td><label for="logo_ssc">Small Sample Correction:</label></td>
                      <td>
                        <input type="hidden" id="logo_err" name="errbars" value="0"/>
                        <select id="logo_ssc" name="ssc">
                          <option value="0">Off</option>
                          <option value="1">On</option>
                        </select>
                      </td>
                    </tr>
                    <tr>
                      <td><label for="logo_width">Width:</label></td>
                      <td>
                        <input type="text" id="logo_width" size="4" placeholder="default" name="width"/>&nbsp;cm
                      </td>
                    </tr>
                    <tr>
                      <td><label for="logo_height">Height:</label></td>
                      <td>
                        <input type="text" id="logo_height" size="4" placeholder="default" name="height"/>&nbsp;cm
                      </td>
                    </tr>
                  </table>
                </form>
              </div>
              <!-- Buttons -->
              <div>
                <div style="float:left;">
                  <input type="button" id="outpop_do" value="Submit" />
                </div>
                <div style="float:right;">
                  <input id="outpop_cancel" type="button" value="Cancel" />
                </div>
                <div style="clear:both;"></div>
              </div>
            </div>
        </div>
      </div>
    </div>



    <!-- Page starts here -->
    <div id="top" class="pad1">
      <div class="prog_logo big">
        <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABGCAYAAACUsCfoAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEgAACxIB0t1+/AAAAAd0SU1FB9wLHAInL7an9JAAAA8eSURBVHja7ZpZbxv3ucZ/s3AZ7qQkypZELZS3WHKiRJbTFnHqOjZcGzCQBf0A7cUB8glOb89dz30vChRp7xK0aZo4TYq6dtKqSew6iuJWpmPJlrVFFCmKI+7LLJw5F81MFZyec5rTILJaP8AAnAty+Pzf933ebeAhHuIhHuIhHmJPo6TpaeGLfimTydhLS0sUCgWazSbVahXDMOh0Ong8HiKRCAMDAxw+fJhHH31UeFDJ/91/bHp62r558ya6rmOaJuvr6+TzeYrFIoZhAOD1epFlmWAwSDqdZnR0lCNHjjA+Ps7+/fuFPUU8k8nYV69epVKpEA6HuXPnDrOzs7RaLbxeL93d3fj9foLBIB6Ph0ajQaVSAUBRFNLpNI888ggHDhzg3Llzwp4g/vbbb9szMzPE43FyuRwffPAB2WyWnp4ehoaGiMVi2LZNvV6n3W7j8/mIRqN4vV4ajQa1Wo2trS0OHTqEaZo89dRTfO973xMeaOKvvfaaffv2bcLhMCsrK/z+979HVVWOHz/O0NAQxWIRVVVRVRW/348oihiGgSiKxGIxurq6CAQCBINB3n//fcbGxiiXyxw7dowXX3yx1Nvbm3igiBcKhY9u3rw5OTMzQzgc5vbt23zwwQfIsszExAS2bZPP55EkiVAoRCQSIRwOI8sy5XKZ7e1tWq0WnU6HTqfD4OAggUAAVVXpdDo0m01GRkb44Q9/KDxQxH/729/as7Oz+Hw+Pv30U37zm9/g9Xrp6emht7eXYDCI3+/H6/UiCALtdhvLsuh0OkiSRCAQQJZl6vU65XKZ1dVV+vv7SSQS5PN5vF4vhUKBgwcP8qMf/WjXyIs7b+7cuWMvLi4iCAL1ep133nkHXdcZHh4mlUoRDofp7+8nHo9jGAaCIBCPx+nr6yOdTtPX14fH40HTNGRZpqenh76+PrLZLMVikYGBARRFwTRN5ubm+PGPf2zvFnF5583c3BzlchlZlrl16xa1Wo2xsTGi0SiDg4OutUzTJBKJIIoiXq+XQCBAIpEgFAphmiaqqlIoFKjVaoyOjhKJRFhfXycajRKNRunp6WFhYYG33nqLQqHwUTKZPP5VE5ecD5cvX7bv37+Poijkcjl++ctfMjo6ytDQEMlkEo/HQ7lcpt1u4/F4kGUZQRDw+Xz09fVx4MABRkZGSKVSdHV10el0KJVK1Ot14vE4kiRRKpVQFIV4PI5t23z66ad0Op2+K1eu/MeuWVxVVUzTRNd17t27RzKZJJFIEI1GEUWRdruNaZqIoogkSYiiiCiK+P1+AoEA0WiUQ4cOCQCbm5vb5XI5Xi6XWVtbw7ZtZFnG7/e7D1YUhWAwSLVaJZvN2v39/cJXHuOLi4t2sVhEEAS2t7eZn59n//79JBIJwuEwlmXRbDbRdR1BEBAEAcuysCwL2/5LmIriX+Wit7c3EY/HSSQSeDweDMPA4/EQCoWwbRvbtgmFQoTDYUqlErlcbnfE7f79+64gbWxs0G63icViRCIRBEHANE0Mw0CSJLxer0vSUfVKpUKxWGRtbc12UmKr1cI0TQD3t4PBoHtwgUAAv99Ps9kkn8/vjrjlcjlkWabdbrO8vEx3dzfhcJhoNEq9XkfTNERRxOfz4fV6sW0bSZJcBS+VSqytrdFqtbh+/bq9sLBALpdje3sbXddd4jutLwh/8WzLslBVdXeI12o1RFFkc3PTLTFjsRixWAxVVbEsC1mWkSTJdVXnMgyDarWKrusUi0X8fj+WZVGtVimXyxiGgW3bblh4PB78fj/tdhvbthFF0a3tv3Liuq4jiiKFQgFZlolEInR3d6MoConEXytLp1Bx0Ol0sG0bXdfRdZ1arYYgCNi2jaZptFot1zOcQ3LSnxMGzsHvCnHH/RqNBtFoFEmSCIfDAAwNDWFZFu12m0ajgaZpWJbl/oDH4/ncoTii5xBz1N+2bTqdDrIs4/P53MxgWRaapu2exQOBgHsAnU6HdrvN5uYmgUCASCRCJBIhkUhgmibNZpNarUaj0XAt71jfET1RFN1YliTJfUa73SaZTLK1tUWtVkPTNKampnaHuFNGyrKM1+t141bTNGq1GrVazS1EIpEI8XjcLWJqtdrnYthNF58pv23beL1eJEly1VzXder1Oq1Wi2AwiK7ru0PcSSs73bBYLBIMBpEkCdM0qVQq6LpOq9UiHA673Vmn08E0TfdyRM85CNM08Xq9bkoMhULugRqGgd/v3z1Xl2X5c24aDodRVZV4PO7W406/raoqtVqNYDCIz+dDURQ3vp3LuXeEz4nlTqdDOBxG13U3TBRF2b0mJRAI0Gg0EAQBTdPw+/2Uy2VXoJzRkkPEifNWq0UgEHCLEsedLcty1X1nzIdCIRRFoVQqUSqVsCyLYDBIPB7fncotlUq5JaWu6xiGgWmabG1tuanINE0EQXAPwSlk6vU6zWaTdruNpmnud3eWsk6fnkwmMU2Tzc1NVFVFURSi0SjpdHp3iB89ehRRFOnt7XWHCNFolFwu58ar08AYhuEWNH6/H4/H8zlL71R4J5XJskwgECAWi5HP51lZWaHVajE8PEw8Hufw4cO7Q3zfvn0C4E5MNU2jq6uLarVKPp//nPA5hYgzcYlEIoRCIfcQHLd3SDvEPR4PHo+HfD5PPp9HEARGRkYIhUIMDQ0Ju0Ic4NixY3g8Hnp7e8lms8RiMcLhMNlsFk3TXHW3bZtgMOjmfScEvF6vW8s7qczxgGazSSAQ4M9//jOqqlKpVHj88cfx+XyMjY3t7ujp1KlTQrlcdicupVKJkZER1tbWuHfvHqqqEolEiMVitNtt6vW6q+iNRoNms4kkSfh8PtfC0WgURVHo7+9nc3OTjY0N3n33XQYGBkilUiSTSU6ePCnsKnGA559/nkqlwtGjR8nn8/j9fh5//HEsy+LWrVvMz8+j6zqhUAifz4dhGG7z0mq1UFUVXdeJRCIEg0F32loqlbh37x53796lq6sLRVGIRCJMTEzs2pT1c8Qfe+wxYWpqyv1j2WyWVCpFIpGgUqkwNzfHnTt33JTnDCUlSaK7u5v+/n66u7vxeDxun16r1SgUCly/fp3bt29z+vRpBgcHqdVqRKPRB2uh8LOf/cyemZlhYWGBYDBIJBIBoFgs0mw2CYfDxONxfD4fg4ODros7vXWhUKDT6bg5/v3332dlZYWhoSHS6TSnTp1i3759VKtVzpw5w1c9dvpfNymvvPKKff36dbLZLIqiuBVWs9mk0WhgWZbbn4fDYURRRNM0AoEAvb29+Hw+Go0Gly9fZmNjg4mJCcbGxti/f787mJyfn6darTI5OcmJEyeEB4J4sVj8wfT09L/Pzs6SzWbdpWEikXBJGoZBs9kkGo0iyzKmabrLhtXVVTKZDJubm5w9e5YjR44wMDDAd7/7XWF2dtZeXFzkyJEj7pZm//79pFIpenp6vhIP+D8f8NZbb9nr6+ssLy+ztbXl1t5er9fdhxeLRSRJoquri0qlQiaTYXt7m0gkwpkzZ5BlmbGxMV544QX3eR9//LE9Pz/PsWPHuHHjBouLi0iSxMDAAMePH2dqakrYVeIAKysr9u3bt1leXnZn65qmoeu6W9AYhkGj0aBYLGKaJocOHeLRRx8lkUgwOTnpjp4d5HI5++OPP6ZcLjM+Ps61a9dYWFig2Wzi9Xp5/vnnOX36tLCrxHfi1q1b9tLSkuv+9XrdbTEVRSEWi5FMJhkdHWV4eJiDBw/+j8/Y2tr6+WuvvfYdwzBIpVJIkoSqqszPz1Ov17lw4QJf+9rXziYSiau7TvxvoVQqpUul0n2n2/oibz+899579k9/+lPC4TCnTp3i61//Orlcjp/85CdomsbTTz/NiRMn/pvH/CMotrQfyF/GD8Xj8aX/7yEePnx4dnx8fPJ3v/sdiqIwODjI5OSkcOPGDfvNN9/kypUr5PN5MpmMPT4+/qWQ71Z835fZZSSTyeMrKyt2vV7n7t27vPrqq1y7ds1+8sknhT/+8Y/2pUuXuHnzJs1mk6WlJTudTn8p5EUeAAwPDwvPPfccBw4cYHV1lc+KJ3t0dPTVixcvcvDgQebn53n55ZeZmZn5h1fLalv/twfqTaRMJmP/4he/YH19nSNHjnDhwgVisRj37t3jnXfeYWFhgQMHDnD+/HmeeuopYc9b3MH4+LjwrW99C0EQuHXrFtPT09RqNfr7+zlx4gTpdJqNjQ3efPNN3njjDbtYLP5gV1X9y8bly5ft119/nUqlwsmTJzl9+jQej4ePPvqITz75hLW1NYLBIJOTk5w7d46+vr4vzEN+EImfO3dOuHr1qv3666/z3nvvoWkaZ8+e5YknnnBXU5qmcfPmTQzDYG1tzR4cHBT2vMUdTE9P25cuXaJQKDA5OcmFCxfQdZ3Z2VkWFhbY2NhAURRSqRTPPvssY2Njwp62uINvfvObwp/+9Cf7V7/6FTdu3KDT6XD+/HmmpqbcoaaqqszOzmIYxhfK9Q80cYCJiQlhbm7OFgSBubk5vF4vzzzzDBMTE+7CMRgMsrCwwEsvvcS1a9fsb3zjG8KedvWd+OSTT+y3336bubk5JicnOXnyJJZl8eGHH7K2tkahUKDRaLBv3z4uXrzI2bNnhT1tcQdHjx4VMpmM3el0WFxcJBQKMTU1xbFjx6hWqwC0221WVlb49a9/zY0bN+wnn3xS2BN5/O/J88899xzDw8NkMhnm5uaIx+M8/fTT9PT0YFkWg4ODrK6u8sorrzA/P2/veVffidXVVfvdd99lenqa8+fPMzo6iqZp/OEPf2BhYYFWq4Usy4yMjHDx4kX+luXlvUh8aGhImJmZsS3L4vr165imSSqVYt++fbTbbWq1GtVqlWq1yt27d7l//749Ojoq7HniAFNTU8LS0pJdLpe5e/cu6+vrRCIRhoeHqVarrK6uous66+vrZLNZSqVS+rP2eW8TB0in00I2m3XVHqCvr49YLEYikXBfRiqXy+Ryufs7Q1tkj6O/v1/49re/zTPPPIMgCMzPz7sraJ/Ph9/vd/d3pVIp/U9hcQdOnX7p0iU7k8kgiqK76lIUxX19dKerC/wTYXt7+8zm5uaV5eVlNjY2iMfj+P1+hoeH6enpmd35evg/FXEHm1uF7ZXltXgg6EcSPSR7u/+zO9H1ff6VsFWq/pyHeIiHeIh/FfwXjVDdIW9O2PAAAAAASUVORK5CYII=" alt="MEME Logo">
        <h1>MEME</h1>
        <h2>Multiple Em for Motif Elicitation</h2>
      </div>
      <p>
        For further information on how to interpret these results or to get a 
        copy of the MEME software please access 
        <a href="http://meme-suite.org/">http://meme-suite.org</a>. 
      </p>
      <p>If you use MEME in your research, please cite the following paper:<br />
        <span class="citation">
          Timothy L. Bailey and Charles Elkan, 
          "Fitting a mixture model by expectation maximization to discover motifs in biopolymers", 
          <em>Proceedings of the Second International Conference on Intelligent Systems 
          for Molecular Biology</em>, pp. 28-36, AAAI Press, Menlo Park, California, 1994. 
          <a href="http://meme-suite.org/doc/ismb94.pdf">[pdf]</a> 
        </span>
      </p>
    </div>
    <!-- navigation -->
    <div class="pad2">
      <a class="jump" href="#motifs_sec">Discovered Motifs</a>
      &nbsp;&nbsp;|&nbsp;&nbsp;
      <a class="jump" href="#sites_sec">Motif Locations</a>
      &nbsp;&nbsp;|&nbsp;&nbsp;
      <a class="jump" href="#inputs_sec">Inputs &amp; Settings</a>
      &nbsp;&nbsp;|&nbsp;&nbsp;
      <a class="jump" href="#info_sec">Program information</a>
    </div>
    <!-- alert the user when their browser is not up to the task -->
    <noscript><h1 style="color:red">Javascript is required to view these results!</h1></noscript>
    <h1 id="html5_warning" style="color:red; display:none;">Your browser does not support canvas!</h1>
    <script>
      if (!window.HTMLCanvasElement) $("html5_warning").style.display = "block";
    </script>
    <h2 class="mainh pad2" id="motifs_sec">Discovered Motifs</h2>
    <div id="motifs" class="box">
      <p>Please wait... Loading...</p>
      <p>If the page has fully loaded and this message does not disappear then an error may have occurred.</p>
    </div>
    <h2 class="mainh pad2" id="sites_sec">Motif Locations</h2>
    <div id="blocks" class="box">
      <p>Please wait... Loading...</p>
      <p>If the page has fully loaded and this message does not disappear then an error may have occurred.</p>
    </div>
    <h2 class="mainh pad2" id="inputs_sec">Inputs &amp; Settings</h2>
    <div class="box">
      <h4>Sequences</h4>
      <table id="seq_info" class="inputs">
        <tr><th>Source <div class="help" data-topic="pop_seq_source"></div></th>
          <th class="col_psp">PSP Source <div class="help" data-topic="pop_psp_source"></div></th>
          <th>Alphabet <div class="help" data-topic="pop_seq_alph"></div></th>
          <th>Sequence Count <div class="help" data-topic="pop_seq_count"></div></th>
        </tr>
        <tr>
          <td id="ins_seq_source"></td>
          <td id="ins_seq_psp" class="col_psp"></td>
          <td id="ins_seq_alphabet"></td>
          <td id="ins_seq_count"></td>
        </tr>
      </table>
      <script>
      {
        var db = data.sequence_db;
        $("ins_seq_source").innerHTML = db.source;
        $("ins_seq_alphabet").innerHTML = meme_alphabet.get_alphabet_name();
        $("ins_seq_count").innerHTML = db.sequences.length;
        if (db.psp) {
          $("ins_seq_psp").innerHTML = db.psp;
        }
        toggle_class($("seq_info"), "hide_psp", !(db.psp));
      }
      </script>
      <h4>Background</h4>
      <span id="alpha_bg"></span>
      <script>
      {
        $("alpha_bg").appendChild(make_alpha_bg_table(meme_alphabet, data.sequence_db.freqs));
      }
      </script>
      <h4>Other Settings</h4>
      <table id="tbl_settings" class="inputs hide_advanced">
        <tr>
          <th>Motif Site Distribution</th>
          <td id="opt_mod">
            <span class="mod_zoops">ZOOPS: Zero or one site per sequence</span>
            <span class="mod_oops">OOPS: Exactly one site per sequence</span>
            <span class="mod_anr">ANR: Any number of sites per sequence</span>
          </td>
        </tr>
        <tr>
          <th>Site Strand Handling</th>
          <td id="opt_strand">
            <span class="strand_none">This alphabet only has one strand</span>
            <span class="strand_given">Sites must be on the given strand</span>
            <span class="strand_both">Sites may be on either strand</span>
          </td>
        </tr>
        <tr>
          <th>Maximum Number of Motifs</th>
          <td id="opt_nmotifs"></td>
        </tr>
        <tr>
          <th>Motif E-value Threshold</th>
          <td id="opt_evt"></td>
        </tr>
        <tr>
          <th>Minimum Motif Width</th>
          <td id="opt_minw"></td>
        </tr>
        <tr>
          <th>Maximum Motif Width</th>
          <td id="opt_maxw"></td>
        </tr>
        <tr>
          <th>Minimum Sites per Motif</th>
          <td id="opt_minsites"></td>
        </tr>
        <tr>
          <th>Maximum Sites per Motif</th>
          <td id="opt_maxsites"></td>
        </tr>
        <tr class="advanced">
          <th>Bias on Number of Sites</th>
          <td id="opt_wnsites"></td>
        </tr>
        <tr class="advanced">
          <th>Sequence Prior</th>
          <td id="opt_prior">
            <span class="prior_dirichlet">Simple Dirichlet</span>
            <span class="prior_dmix">Dirichlets Mix</span>
            <span class="prior_mega">Mega-weight Dirichlets Mix</span>
            <span class="prior_megap">Mega-weight Dirichlets Mix Plus</span>
            <span class="prior_addone">Add One</span>
          </td>
        </tr>
        <tr class="advanced">
          <th>Sequence Prior Strength</th>
          <td id="opt_b"></td>
        </tr>
        <tr class="advanced">
          <th>EM Starting Point Source</th>
          <td id="opt_substring">
            <span class="substring_on">From substrings in input sequences</span>
            <span class="substring_off">From strings on command line (-cons)</span>
          </td>
        </tr>
        <tr class="advanced">
          <th>EM Starting Point Map Type</th>
          <td id="opt_spmap">
            <span class="spmap_uni">Uniform</span>
            <span class="spmap_pam">Point Accepted Mutation</span>
          </td>
        </tr>
        <tr class="advanced">
          <th>EM Starting Point Fuzz</th>
          <td id="opt_spfuzz"></td>
        </tr>
        <tr class="advanced">
          <th>EM Maximum Iterations</th>
          <td id="opt_maxiter"></td>
        </tr>
        <tr class="advanced">
          <th>EM Improvement Threshold</th>
          <td id="opt_distance"></td>
        </tr>
        <tr class="advanced">
          <th>Trim Gap Open Cost</th>
          <td id="opt_wg"></td>
        </tr>
        <tr class="advanced">
          <th>Trim Gap Extend Cost</th>
          <td id="opt_ws"></td>
        </tr>
        <tr class="advanced">
          <th>End Gap Treatment</th>
          <td id="opt_noendgaps">
            <span class="noendgaps_on">No cost</span>
            <span class="noendgaps_off">Same cost as other gaps</span>
          </td>
        </tr>
        <tr>
          <td colspan="2" style="text-align: center">
            <a href="javascript:toggle_class(document.getElementById('tbl_settings'), 'hide_advanced')">
              <span class="show_more">Show Advanced Settings</span>
              <span class="show_less">Hide Advanced Settings</span>
            </a>
          </td>
        </tr>
      </table>
      <script>
      {
        $("opt_mod").className = data.options.mod;
        $("opt_strand").className = (meme_alphabet.has_complement() ? (data.options.revcomp ? "both" : "given") : "none");
        $("opt_nmotifs").textContent = data.options.nmotifs;
        $("opt_evt").textContent = (typeof data.options.evt === "number" ? data.options.evt : "no limit");
        $("opt_minw").textContent = data.options.minw;
        $("opt_maxw").textContent = data.options.maxw;
        $("opt_minsites").textContent = data.options.minsites;
        $("opt_maxsites").textContent = data.options.maxsites;
        $("opt_wnsites").textContent = data.options.wnsites;
        $("opt_spmap").className = data.options.spmap;
        $("opt_spfuzz").textContent = data.options.spfuzz;
        $("opt_prior").className = data.options.prior;
        $("opt_b").textContent = data.options.b;
        $("opt_maxiter").textContent = data.options.maxiter;
        $("opt_distance").textContent = data.options.distance;
        $("opt_wg").textContent = data.options.wg;
        $("opt_ws").textContent = data.options.ws;
        $("opt_noendgaps").className = (data.options.noendgaps ? "on" : "off");
        $("opt_substring").className = (data.options.substring ? "on" : "off");
      }
      </script>
    </div>
    <!-- list information on this program -->
    <div id="info_sec" class="bar">
      <div class="subsection">
        <h5 id="version">MEME version</h5>
        <span id="ins_version"></span> 
        (Release date: <span id="ins_release"></span>)<br>
      </div>
      <script>
        $("ins_version").innerHTML = data["version"];
        $("ins_release").innerHTML = data["release"];
      </script>
      <div class="subsection">
        <h5 id="reference">Reference</h5>
        <span class="citation">
          Timothy L. Bailey and Charles Elkan, 
          "Fitting a mixture model by expectation maximization to discover motifs in biopolymers", 
          <em>Proceedings of the Second International Conference on Intelligent Systems 
          for Molecular Biology</em>, pp. 28-36, AAAI Press, Menlo Park, California, 1994. 
        </span>
      </div>
      <div class="subsection">
        <h5 id="command">Command line</h5>
        <textarea id="cmd" rows="5" style="width:100%;" readonly="readonly">
        </textarea>
        <script>$("cmd").value = data["cmd"].join(" ");</script>
      </div>
    </div>
    
  </body>
</html>