diff test-data/dreme_output_test1.html @ 13:4eb02864e5df 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:00 -0400
parents
children 0c5239d1aa65
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dreme_output_test1.html	Thu May 17 14:11:00 2018 -0400
@@ -0,0 +1,6198 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset="UTF-8">
+    <title>DREME</title>
+    <script>
+      // @JSON_VAR data
+      var data = {
+        "program": "dreme",
+        "version": "4.12.0",
+        "release": "Tue Jun 27 16:22:50 2017 -0700",
+        "cmd": [
+          "dreme", "-o", "./dreme_test1_out", "-p", "dreme_test_sites.fa",
+          "-norc", "-rna", "-s", "1"
+        ],
+        "options": {
+          "revcomp": false,
+          "ngen": 100,
+          "add_pv_thresh": 0.01,
+          "seed": 1,
+          "stop": {
+            "evalue": "0.05"
+          }
+        },
+        "alphabet": {
+          "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",
+              "aliases": "T",
+              "name": "Uracil",
+              "colour": "008000"
+            }, {
+              "symbol": "N",
+              "aliases": "X.",
+              "name": "Any base",
+              "equals": "ACGU"
+            }, {
+              "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"
+            }
+          ]
+        },
+        "background": {
+          "freqs": [0.221, 0.245, 0.221, 0.312]
+        },
+        "sequence_db": {
+          "name": "dreme test sites",
+          "file": "dreme_test_sites.fa",
+          "lmod": "Thu Apr 26 15:09:03 CEST 2018",
+          "count": 1000
+        },
+        "control_db": {
+          "name": "shuffled positive sequences",
+          "from": "shuffled",
+          "count": 1000,
+          "freqs": [0.221, 0.245, 0.221, 0.312]
+        },
+        "motifs": [
+          {
+            "db": 0,
+            "id": "UUYUCY",
+            "alt": "DREME-1",
+            "len": 6,
+            "nsites": 459,
+            "evalue": "1.2e-013",
+            "p": 387,
+            "n": 210,
+            "pvalue": "2.6e-018",
+            "unerased_evalue": "1.2e-013",
+            "pwm": [
+              [0.000000, 0.000000, 0.000000, 1.000000], 
+              [0.000000, 0.000000, 0.000000, 1.000000], 
+              [0.000000, 0.294118, 0.000000, 0.705882], 
+              [0.000000, 0.000000, 0.000000, 1.000000], 
+              [0.000000, 1.000000, 0.000000, 0.000000], 
+              [0.000000, 0.474946, 0.000000, 0.525054]
+            ],
+            "matches": [
+              {
+                "seq": "UUUUCC",
+                "p": 147,
+                "n": 75,
+                "pvalue": "1.8e-007",
+                "evalue": "8.1e-003"
+              }, {
+                "seq": "UUUUCU",
+                "p": 155,
+                "n": 94,
+                "pvalue": "2.2e-005",
+                "evalue": "1.0e+000"
+              }, {
+                "seq": "UUCUCU",
+                "p": 94,
+                "n": 51,
+                "pvalue": "1.3e-004",
+                "evalue": "6.1e+000"
+              }, {
+                "seq": "UUCUCC",
+                "p": 75,
+                "n": 42,
+                "pvalue": "1.1e-003",
+                "evalue": "5.0e+001"
+              }
+            ]
+          }, {
+            "db": 0,
+            "id": "YAGG",
+            "alt": "DREME-2",
+            "len": 4,
+            "nsites": 793,
+            "evalue": "5.1e-012",
+            "p": 600,
+            "n": 416,
+            "pvalue": "1.1e-016",
+            "unerased_evalue": "2.4e-012",
+            "pwm": [
+              [0.000000, 0.692308, 0.000000, 0.307692], 
+              [1.000000, 0.000000, 0.000000, 0.000000], 
+              [0.000000, 0.000000, 1.000000, 0.000000], 
+              [0.000000, 0.000000, 1.000000, 0.000000]
+            ],
+            "matches": [
+              {
+                "seq": "CAGG",
+                "p": 441,
+                "n": 304,
+                "pvalue": "1.5e-010",
+                "evalue": "6.6e-006"
+              }, {
+                "seq": "UAGG",
+                "p": 232,
+                "n": 165,
+                "pvalue": "1.1e-004",
+                "evalue": "4.7e+000"
+              }
+            ]
+          }, {
+            "db": 0,
+            "id": "GAAGAW",
+            "alt": "DREME-3",
+            "len": 6,
+            "nsites": 89,
+            "evalue": "3.4e-005",
+            "p": 81,
+            "n": 22,
+            "pvalue": "8.2e-010",
+            "unerased_evalue": "3.5e-004",
+            "pwm": [
+              [0.000000, 0.000000, 1.000000, 0.000000], 
+              [1.000000, 0.000000, 0.000000, 0.000000], 
+              [1.000000, 0.000000, 0.000000, 0.000000], 
+              [0.000000, 0.000000, 1.000000, 0.000000], 
+              [1.000000, 0.000000, 0.000000, 0.000000], 
+              [0.494382, 0.000000, 0.000000, 0.505618]
+            ],
+            "matches": [
+              {
+                "seq": "GAAGAU",
+                "p": 45,
+                "n": 7,
+                "pvalue": "2.4e-008",
+                "evalue": "9.9e-004"
+              }, {
+                "seq": "GAAGAA",
+                "p": 40,
+                "n": 16,
+                "pvalue": "7.9e-004",
+                "evalue": "3.3e+001"
+              }
+            ]
+          }, {
+            "db": 0,
+            "id": "SMUGGA",
+            "alt": "DREME-4",
+            "len": 6,
+            "nsites": 119,
+            "evalue": "3.7e-003",
+            "p": 110,
+            "n": 47,
+            "pvalue": "9.1e-008",
+            "unerased_evalue": "2.6e-005",
+            "pwm": [
+              [0.000000, 0.529412, 0.470588, 0.000000], 
+              [0.428571, 0.571429, 0.000000, 0.000000], 
+              [0.000000, 0.000000, 0.000000, 1.000000], 
+              [0.000000, 0.000000, 1.000000, 0.000000], 
+              [0.000000, 0.000000, 1.000000, 0.000000], 
+              [1.000000, 0.000000, 0.000000, 0.000000]
+            ],
+            "matches": [
+              {
+                "seq": "GAUGGA",
+                "p": 22,
+                "n": 6,
+                "pvalue": "1.7e-003",
+                "evalue": "7.1e+001"
+              }, {
+                "seq": "GCUGGA",
+                "p": 33,
+                "n": 14,
+                "pvalue": "3.6e-003",
+                "evalue": "1.5e+002"
+              }, {
+                "seq": "CCUGGA",
+                "p": 32,
+                "n": 15,
+                "pvalue": "8.6e-003",
+                "evalue": "3.5e+002"
+              }, {
+                "seq": "CAUGGA",
+                "p": 29,
+                "n": 13,
+                "pvalue": "9.1e-003",
+                "evalue": "3.7e+002"
+              }
+            ]
+          }
+        ],
+        "runtime": {
+          "host": "ThinkPad-T450s",
+          "when": "Thu May 03 13:22:29 CEST 2018",
+          "cpu": 13.95,
+          "real": 13.95,
+          "stop": "evalue"
+        }
+      };
+    </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>
+function motif_logo_template(inputs) {
+  function _input(name) {
+    if (typeof inputs[name] === "undefined") {
+      throw new Error("Missing template variable: " + name);
+    }
+    return inputs[name];
+  }
+  return (
+"%!PS-Adobe-3.0 EPSF-3.0\n" +
+"%%Title: Sequence Logo : " + _input("TITLE") + "\n" +
+"%%Creator: " + _input("CREATOR") + "\n" +
+"%%CreationDate: " + _input("CREATIONDATE") + "\n" +
+"%%BoundingBox:   0  0  " + _input("BOUNDINGWIDTH") + " " + _input("BOUNDINGHEIGHT") + " \n" +
+"%%Pages: 0\n" +
+"%%DocumentFonts: \n" +
+"%%EndComments\n" +
+"\n" +
+"% ---- CONSTANTS ----\n" +
+"\/cmfactor 72 2.54 div def % defines points -> cm conversion\n" +
+"\/cm {cmfactor mul} bind def % defines centimeters\n" +
+"\n" +
+"% ---- VARIABLES ----\n" +
+"\n" +
+"% NA = Nucleic Acid, AA = Amino Acid\n" +
+"\/logoType (" + _input("LOGOTYPE") + ") def \n" +
+"\n" +
+"\/logoTitle (" + _input("TITLE") + ") def\n" +
+"\n" +
+"% Dimensions in cm\n" +
+"\/logoWidth " + _input("LOGOWIDTH") + " cm def\n" +
+"\/logoHeight " + _input("LOGOLINEHEIGHT") + " cm def\n" +
+"\/totalHeight " + _input("LOGOHEIGHT") + " cm def\n" +
+"\n" +
+"\/yaxis " + _input("YAXIS") + " def\n" +
+"\/yaxisLabel (" + _input("YAXISLABEL") + ") def\n" +
+"\/yaxisBits  " + _input("BARBITS") + " def % bits\n" +
+"\/yaxisTicBits " + _input("TICBITS") + " def\n" +
+"\n" +
+"\/xaxis " + _input("NUMBERING") + " def\n" +
+"\/xaxisLabel (" + _input("XAXISLABEL") + ") def\n" +
+"\/showEnds (" + _input("SHOWENDS") + ") def \n" +
+"\n" +
+"\/showFineprint true def\n" +
+"\/fineprint (" + _input("FINEPRINT") + ") def\n" +
+"\n" +
+"\/charsPerLine " + _input("CHARSPERLINE") + " def\n" +
+"\n" +
+"\/showingBox " + _input("SHOWINGBOX") + " def    \n" +
+"\/shrinking false def   % true falses\n" +
+"\/shrink  1.0 def\n" +
+"\/outline " + _input("OUTLINE") + " def\n" +
+"\n" +
+"\/IbeamFraction  " + _input("ERRORBARFRACTION") + " def\n" +
+"\/IbeamGray      0.50 def\n" +
+"\/IbeamLineWidth 0.5 def\n" +
+"\n" +
+"\/fontsize       " + _input("FONTSIZE") + " def\n" +
+"\/titleFontsize  " + _input("TITLEFONTSIZE") + " def\n" +
+"\/smallFontsize  " + _input("SMALLFONTSIZE") + " def\n" +
+"\n" +
+"\/topMargin      " + _input("TOPMARGIN") + " cm def\n" +
+"\/bottomMargin   " + _input("BOTTOMMARGIN") + " cm def\n" +
+"\n" +
+"\/defaultColor [0 0 0] def \n" +
+"\n" +
+_input("COLORDICT") + "\n" +
+"\n" +
+"\/colorDict fullColourDict def\n" +
+"\n" +
+"% ---- DERIVED PARAMETERS ----\n" +
+"\n" +
+"\/leftMargin\n" +
+"  fontsize 3.5 mul\n" +
+"\n" +
+"def \n" +
+"\n" +
+"\/rightMargin \n" +
+"  %Add extra room if showing ends\n" +
+"  showEnds (false) eq { fontsize}{fontsize 1.5 mul} ifelse\n" +
+"def\n" +
+"\n" +
+"\/yaxisHeight \n" +
+"  logoHeight \n" +
+"  bottomMargin sub  \n" +
+"  topMargin sub\n" +
+"def\n" +
+"\n" +
+"\/ticWidth fontsize 2 div def\n" +
+"\n" +
+"\/pointsPerBit yaxisHeight yaxisBits div  def\n" +
+"\n" +
+"\/stackMargin 1 def\n" +
+"\n" +
+"% Do not add space aroung characters if characters are boxed\n" +
+"\/charRightMargin \n" +
+"  showingBox { 0.0 } {stackMargin} ifelse\n" +
+"def\n" +
+"\n" +
+"\/charTopMargin \n" +
+"  showingBox { 0.0 } {stackMargin} ifelse\n" +
+"def\n" +
+"\n" +
+"\/charWidth\n" +
+"  logoWidth\n" +
+"  leftMargin sub\n" +
+"  rightMargin sub\n" +
+"  charsPerLine div\n" +
+"  charRightMargin sub\n" +
+"def\n" +
+"\n" +
+"\/charWidth4 charWidth 4 div def\n" +
+"\/charWidth2 charWidth 2 div def\n" +
+"\n" +
+"\/stackWidth \n" +
+"  charWidth charRightMargin add\n" +
+"def\n" +
+" \n" +
+"\/numberFontsize \n" +
+"  fontsize charWidth lt {fontsize}{charWidth} ifelse\n" +
+"def\n" +
+"\n" +
+"% movements to place 5'\/N and 3'\/C symbols\n" +
+"\/leftEndDeltaX  fontsize neg         def\n" +
+"\/leftEndDeltaY  fontsize 1.5 mul neg def\n" +
+"\/rightEndDeltaX fontsize 0.25 mul     def\n" +
+"\/rightEndDeltaY leftEndDeltaY        def\n" +
+"\n" +
+"% Outline width is proporional to charWidth, \n" +
+"% but no less that 1 point\n" +
+"\/outlinewidth \n" +
+"  charWidth 32 div dup 1 gt  {}{pop 1} ifelse\n" +
+"def\n" +
+"\n" +
+"\n" +
+"% ---- PROCEDURES ----\n" +
+"\n" +
+"\/StartLogo { \n" +
+"  % Save state\n" +
+"  save \n" +
+"  gsave \n" +
+"\n" +
+"  % Print Logo Title, top center \n" +
+"  gsave \n" +
+"    SetStringFont\n" +
+"\n" +
+"    logoWidth 2 div\n" +
+"    logoTitle\n" +
+"    stringwidth pop 2 div sub\n" +
+"    totalHeight\n" +
+"    titleFontsize sub\n" +
+"    moveto\n" +
+"\n" +
+"    logoTitle\n" +
+"    show\n" +
+"  grestore\n" +
+"\n" +
+"  % Print X-axis label, bottom center\n" +
+"  gsave\n" +
+"    SetStringFont\n" +
+"\n" +
+"    logoWidth 2 div\n" +
+"    xaxisLabel\n" +
+"    stringwidth pop 2 div sub\n" +
+"    0\n" +
+"    titleFontsize 3 div\n" +
+"    add\n" +
+"    moveto\n" +
+"\n" +
+"    xaxisLabel\n" +
+"    show\n" +
+"  grestore\n" +
+"\n" +
+"  % Show Fine Print\n" +
+"  showFineprint {\n" +
+"    gsave\n" +
+"      SetSmallFont\n" +
+"      logoWidth\n" +
+"        fineprint stringwidth pop sub\n" +
+"        smallFontsize sub\n" +
+"          smallFontsize 3 div\n" +
+"      moveto\n" +
+"    \n" +
+"      fineprint show\n" +
+"    grestore\n" +
+"  } if\n" +
+"\n" +
+"  % Move to lower left corner of last line, first stack\n" +
+"  leftMargin bottomMargin translate\n" +
+"\n" +
+"  % Move above first line ready for StartLine \n" +
+"  0 totalHeight translate\n" +
+"\n" +
+"  SetLogoFont\n" +
+"} bind def\n" +
+"\n" +
+"\/EndLogo { \n" +
+"  grestore \n" +
+"  showpage \n" +
+"  restore \n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/StartLine { \n" +
+"  % move down to the bottom of the line:\n" +
+"  0 logoHeight neg translate\n" +
+"  \n" +
+"  gsave \n" +
+"    yaxis { MakeYaxis } if\n" +
+"    xaxis { showEnds (true) eq {ShowLeftEnd} if } if\n" +
+"} bind def\n" +
+"\n" +
+"\/EndLine{ \n" +
+"    xaxis { showEnds (true) eq {ShowRightEnd} if } if\n" +
+"  grestore \n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/MakeYaxis {\n" +
+"  gsave    \n" +
+"    stackMargin neg 0 translate\n" +
+"    ShowYaxisBar\n" +
+"    ShowYaxisLabel\n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/ShowYaxisBar { \n" +
+"  gsave  \n" +
+"    SetStringFont\n" +
+"\n" +
+"    \/str 10 string def % string to hold number  \n" +
+"    \/smallgap stackMargin 2 div def\n" +
+"\n" +
+"    % Draw first tic and bar\n" +
+"    gsave    \n" +
+"      ticWidth neg 0 moveto \n" +
+"      ticWidth 0 rlineto \n" +
+"      0 yaxisHeight rlineto\n" +
+"      stroke\n" +
+"    grestore\n" +
+"\n" +
+"   \n" +
+"    % Draw the tics\n" +
+"    % initial increment limit proc for\n" +
+"    0 yaxisTicBits yaxisBits abs %cvi\n" +
+"    {\/loopnumber exch def\n" +
+"\n" +
+"      % convert the number coming from the loop to a string\n" +
+"      % and find its width\n" +
+"      loopnumber 10 str cvrs\n" +
+"      \/stringnumber exch def % string representing the number\n" +
+"\n" +
+"      stringnumber stringwidth pop\n" +
+"      \/numberwidth exch def % width of number to show\n" +
+"\n" +
+"      \/halfnumberheight\n" +
+"         stringnumber CharBoxHeight 2 div\n" +
+"      def\n" +
+"\n" +
+"      numberwidth % move back width of number\n" +
+"      neg loopnumber pointsPerBit mul % shift on y axis\n" +
+"      halfnumberheight sub % down half the digit\n" +
+"\n" +
+"      moveto % move back the width of the string\n" +
+"\n" +
+"      ticWidth neg smallgap sub % Move back a bit more  \n" +
+"      0 rmoveto % move back the width of the tic  \n" +
+"\n" +
+"      stringnumber show\n" +
+"      smallgap 0 rmoveto % Make a small gap  \n" +
+"\n" +
+"      % now show the tic mark\n" +
+"      0 halfnumberheight rmoveto % shift up again\n" +
+"      ticWidth 0 rlineto\n" +
+"      stroke\n" +
+"    } for\n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+"\/ShowYaxisLabel {\n" +
+"  gsave\n" +
+"    SetStringFont\n" +
+"\n" +
+"    % How far we move left depends on the size of\n" +
+"    % the tic labels.\n" +
+"    \/str 10 string def % string to hold number  \n" +
+"    yaxisBits yaxisTicBits div cvi yaxisTicBits mul \n" +
+"    str cvs stringwidth pop\n" +
+"    ticWidth 1.5 mul  add neg  \n" +
+"\n" +
+"\n" +
+"    yaxisHeight\n" +
+"    yaxisLabel stringwidth pop\n" +
+"    sub 2 div\n" +
+"\n" +
+"    translate\n" +
+"    90 rotate\n" +
+"    0 0 moveto\n" +
+"    yaxisLabel show\n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/StartStack {  % <stackNumber> startstack\n" +
+"  xaxis {MakeNumber}{pop} ifelse\n" +
+"  gsave\n" +
+"} bind def\n" +
+"\n" +
+"\/EndStack {\n" +
+"  grestore\n" +
+"  stackWidth 0 translate\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"% Draw a character whose height is proportional to symbol bits\n" +
+"\/MakeSymbol{ % charbits character MakeSymbol\n" +
+"  gsave\n" +
+"    \/char exch def\n" +
+"    \/bits exch def\n" +
+"\n" +
+"    \/bitsHeight \n" +
+"       bits pointsPerBit mul \n" +
+"    def\n" +
+"\n" +
+"    \/charHeight \n" +
+"       bitsHeight charTopMargin sub\n" +
+"       dup \n" +
+"       0.0 gt {}{pop 0.0} ifelse % if neg replace with zero \n" +
+"    def \n" +
+" \n" +
+"    charHeight 0.0 gt {\n" +
+"      char SetColor\n" +
+"      charWidth charHeight char ShowChar\n" +
+"\n" +
+"      showingBox { % Unfilled box\n" +
+"        0 0 charWidth charHeight false ShowBox\n" +
+"      } if\n" +
+"\n" +
+"\n" +
+"    } if\n" +
+"\n" +
+"  grestore\n" +
+"\n" +
+"  0 bitsHeight translate \n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/ShowChar { % <width> <height> <char> ShowChar\n" +
+"  gsave\n" +
+"    \/tc exch def    % The character\n" +
+"    \/ysize exch def % the y size of the character\n" +
+"    \/xsize exch def % the x size of the character\n" +
+"\n" +
+"    \/xmulfactor 1 def \n" +
+"    \/ymulfactor 1 def\n" +
+"    \/limmulfactor 0.01 def\n" +
+"    \/drawable true def\n" +
+"\n" +
+"  \n" +
+"    % if ysize is negative, make everything upside down!\n" +
+"    ysize 0 lt {\n" +
+"      % put ysize normal in this orientation\n" +
+"      \/ysize ysize abs def\n" +
+"      xsize ysize translate\n" +
+"      180 rotate\n" +
+"    } if\n" +
+"\n" +
+"    shrinking {\n" +
+"      xsize 1 shrink sub 2 div mul\n" +
+"        ysize 1 shrink sub 2 div mul translate \n" +
+"\n" +
+"      shrink shrink scale\n" +
+"    } if\n" +
+"\n" +
+"    % Calculate the font scaling factors\n" +
+"    % Loop twice to catch small correction due to first scaling\n" +
+"    2 {\n" +
+"      gsave\n" +
+"        xmulfactor ymulfactor scale\n" +
+"      \n" +
+"        ysize % desired size of character in points\n" +
+"        tc CharBoxHeight \n" +
+"        dup 0.0 ne {\n" +
+"          div % factor by which to scale up the character\n" +
+"          \/ymulfactor exch def\n" +
+"        } % end if\n" +
+"        {pop pop}\n" +
+"        ifelse\n" +
+"\n" +
+"        xsize % desired size of character in points\n" +
+"        tc CharBoxWidth  \n" +
+"        dup 0.0 ne {\n" +
+"          div % factor by which to scale up the character\n" +
+"          \/xmulfactor exch def\n" +
+"        } % end if\n" +
+"        {pop pop}\n" +
+"        ifelse\n" +
+"      grestore\n" +
+"      % if the multiplication factors get too small we need to avoid a crash\n" +
+"      xmulfactor limmulfactor lt {\n" +
+"        \/xmulfactor 1 def\n" +
+"        \/drawable false def\n" +
+"      } if\n" +
+"      ymulfactor limmulfactor lt {\n" +
+"        \/ymulfactor 1 def\n" +
+"        \/drawable false def\n" +
+"      } if\n" +
+"    } repeat\n" +
+"\n" +
+"    % Adjust horizontal position if the symbol is an I\n" +
+"    tc (I) eq {\n" +
+"      charWidth 2 div % half of requested character width\n" +
+"      tc CharBoxWidth 2 div % half of the actual character\n" +
+"      sub 0 translate\n" +
+"      % Avoid x scaling for I \n" +
+"      \/xmulfactor 1 def \n" +
+"    } if\n" +
+"\n" +
+"\n" +
+"    % ---- Finally, draw the character\n" +
+"    drawable { \n" +
+"      newpath\n" +
+"      xmulfactor ymulfactor scale\n" +
+"\n" +
+"      % Move lower left corner of character to start point\n" +
+"      tc CharBox pop pop % llx lly : Lower left corner\n" +
+"      exch neg exch neg\n" +
+"      moveto\n" +
+"\n" +
+"      outline {  % outline characters:\n" +
+"        outlinewidth setlinewidth\n" +
+"        tc true charpath\n" +
+"        gsave 1 setgray fill grestore\n" +
+"        clip stroke\n" +
+"      } { % regular characters\n" +
+"        tc show\n" +
+"      } ifelse\n" +
+"    } if\n" +
+"\n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/ShowBox { % x1 y1 x2 y2 filled ShowBox\n" +
+"  gsave\n" +
+"    \/filled exch def \n" +
+"    \/y2 exch def\n" +
+"    \/x2 exch def\n" +
+"    \/y1 exch def\n" +
+"    \/x1 exch def\n" +
+"    newpath\n" +
+"    x1 y1 moveto\n" +
+"    x2 y1 lineto\n" +
+"    x2 y2 lineto\n" +
+"    x1 y2 lineto\n" +
+"    closepath\n" +
+"\n" +
+"    clip\n" +
+"    \n" +
+"    filled {\n" +
+"      fill\n" +
+"    }{ \n" +
+"      0 setgray stroke   \n" +
+"    } ifelse\n" +
+"\n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/MakeNumber { % number MakeNumber\n" +
+"  gsave\n" +
+"    SetNumberFont\n" +
+"    stackWidth 0 translate\n" +
+"    90 rotate % rotate so the number fits\n" +
+"    dup stringwidth pop % find the length of the number\n" +
+"    neg % prepare for move\n" +
+"    stackMargin sub % Move back a bit\n" +
+"    charWidth (0) CharBoxHeight % height of numbers\n" +
+"    sub 2 div %\n" +
+"    moveto % move back to provide space\n" +
+"    show\n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/Ibeam{ % heightInBits Ibeam\n" +
+"  gsave\n" +
+"    % Make an Ibeam of twice the given height in bits\n" +
+"    \/height exch  pointsPerBit mul def \n" +
+"    \/heightDRAW height IbeamFraction mul def\n" +
+"\n" +
+"    IbeamLineWidth setlinewidth\n" +
+"    IbeamGray setgray \n" +
+"\n" +
+"    charWidth2 height neg translate\n" +
+"    ShowIbar\n" +
+"    newpath\n" +
+"      0 0 moveto\n" +
+"      0 heightDRAW rlineto\n" +
+"    stroke\n" +
+"    newpath\n" +
+"      0 height moveto\n" +
+"      0 height rmoveto\n" +
+"      currentpoint translate\n" +
+"    ShowIbar\n" +
+"    newpath\n" +
+"    0 0 moveto\n" +
+"    0 heightDRAW neg rlineto\n" +
+"    currentpoint translate\n" +
+"    stroke\n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/ShowIbar { % make a horizontal bar\n" +
+"  gsave\n" +
+"    newpath\n" +
+"      charWidth4 neg 0 moveto\n" +
+"      charWidth4 0 lineto\n" +
+"    stroke\n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/ShowLeftEnd {\n" +
+"  gsave\n" +
+"    SetStringFont\n" +
+"    leftEndDeltaX leftEndDeltaY moveto\n" +
+"    logoType (NA) eq {(5) show ShowPrime} if\n" +
+"    logoType (AA) eq {(N) show} if\n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/ShowRightEnd { \n" +
+"  gsave\n" +
+"    SetStringFont\n" +
+"    rightEndDeltaX rightEndDeltaY moveto\n" +
+"    logoType (NA) eq {(3) show ShowPrime} if\n" +
+"    logoType (AA) eq {(C) show} if\n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"\/ShowPrime {\n" +
+"  gsave\n" +
+"    SetPrimeFont\n" +
+"    (\\242) show \n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+" \n" +
+"\/SetColor{ % <char> SetColor\n" +
+"  dup colorDict exch known {\n" +
+"    colorDict exch get aload pop setrgbcolor\n" +
+"  } {\n" +
+"    pop\n" +
+"    defaultColor aload pop setrgbcolor\n" +
+"  } ifelse \n" +
+"} bind def\n" +
+"\n" +
+"% define fonts\n" +
+"\/SetTitleFont {\/Times-Bold findfont titleFontsize scalefont setfont} bind def\n" +
+"\/SetLogoFont  {\/Helvetica-Bold findfont charWidth  scalefont setfont} bind def\n" +
+"\/SetStringFont{\/Helvetica-Bold findfont fontsize scalefont setfont} bind def\n" +
+"\/SetPrimeFont {\/Symbol findfont fontsize scalefont setfont} bind def\n" +
+"\/SetSmallFont {\/Helvetica findfont smallFontsize scalefont setfont} bind def\n" +
+"\n" +
+"\/SetNumberFont {\n" +
+"    \/Helvetica-Bold findfont \n" +
+"    numberFontsize\n" +
+"    scalefont\n" +
+"    setfont\n" +
+"} bind def\n" +
+"\n" +
+"%Take a single character and return the bounding box\n" +
+"\/CharBox { % <char> CharBox <lx> <ly> <ux> <uy>\n" +
+"  gsave\n" +
+"    newpath\n" +
+"    0 0 moveto\n" +
+"    % take the character off the stack and use it here:\n" +
+"    true charpath \n" +
+"    flattenpath \n" +
+"    pathbbox % compute bounding box of 1 pt. char => lx ly ux uy\n" +
+"    % the path is here, but toss it away ...\n" +
+"  grestore\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"% The height of a characters bounding box\n" +
+"\/CharBoxHeight { % <char> CharBoxHeight <num>\n" +
+"  CharBox\n" +
+"  exch pop sub neg exch pop\n" +
+"} bind def\n" +
+"\n" +
+"\n" +
+"% The width of a characters bounding box\n" +
+"\/CharBoxWidth { % <char> CharBoxHeight <num>\n" +
+"  CharBox\n" +
+"  pop exch pop sub neg \n" +
+"} bind def\n" +
+"\n" +
+"% Set the colour scheme to be faded to indicate trimming\n" +
+"\/MuteColour {\n" +
+"  \/colorDict mutedColourDict def\n" +
+"} def\n" +
+"\n" +
+"% Restore the colour scheme to the normal colours\n" +
+"\/RestoreColour {\n" +
+"  \/colorDict fullColourDict def\n" +
+"} def\n" +
+"\n" +
+"% Draw the background for a trimmed section\n" +
+"% takes the number of columns as a parameter\n" +
+"\/DrawTrimBg { % <num> DrawTrimBox\n" +
+"  \/col exch def\n" +
+"  \n" +
+"  \/boxwidth \n" +
+"    col stackWidth mul \n" +
+"  def\n" +
+" \n" +
+"  gsave\n" +
+"    0.97 setgray\n" +
+"\n" +
+"    newpath\n" +
+"    0 0 moveto\n" +
+"    boxwidth 0 rlineto\n" +
+"    0 yaxisHeight rlineto\n" +
+"    0 yaxisHeight lineto\n" +
+"    closepath\n" +
+"    \n" +
+"    fill\n" +
+"  grestore\n" +
+"} def\n" +
+"\n" +
+"\/DrawTrimEdge {\n" +
+"  gsave\n" +
+"    0.2 setgray\n" +
+"    [2] 0 setdash\n" +
+"\n" +
+"    newpath\n" +
+"    0 0 moveto\n" +
+"    0 yaxisHeight lineto\n" +
+"    \n" +
+"    stroke\n" +
+"\n" +
+"} def\n" +
+"\n" +
+"\n" +
+"% Deprecated names\n" +
+"\/startstack {StartStack} bind  def\n" +
+"\/endstack {EndStack}     bind def\n" +
+"\/makenumber {MakeNumber} bind def\n" +
+"\/numchar { MakeSymbol }  bind def\n" +
+"\n" +
+"%%EndProlog\n" +
+"\n" +
+"%%Page: 1 1\n" +
+"StartLogo\n" +
+"\n" +
+_input("DATA") + "\n" +
+"\n" +
+"EndLogo\n" +
+"\n" +
+"%%EOF\n"
+  );
+}</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>
+var current_motif = 0;
+var dreme_alphabet = new Alphabet(data.alphabet, data.control_db.freqs);
+
+/*
+ * Create a pspm for the given motif data
+ */
+function motif_pspm(m) {
+  return new Pspm(m.pwm, m.id, 0, 0, m.nsites, m.evalue);
+}
+
+/*
+ * Create a count matrix from the given motif data
+ */
+function motif_count_matrix(motif) {
+  return motif_pspm(motif).as_count_matrix();
+}
+
+/*
+ * Create a probablity matrix from the given motif data
+ */
+function motif_prob_matrix(motif) {
+  return motif_pspm(motif).as_probability_matrix();
+}
+
+/*
+ * Create a minimal meme format motif from the given motif data
+ */
+function motif_minimal_meme(motif) {
+  return motif_pspm(motif).as_meme({
+    "with_header": true, 
+    "with_pspm": true,
+    "with_pssm": false,
+    "version": data["version"],
+    "alphabet": dreme_alphabet,
+    "strands": (data.options.revcomp ? 2 : 1)
+  });
+}
+
+/*
+ * Fill in a template variable
+ */
+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);
+  }
+}
+
+/*
+ * Make a canvas with the motif logo drawn on it. 
+ */
+function make_logo(motif, height, rc) {
+  var pspm = new Pspm(motif["pwm"]);
+  if (rc) pspm = pspm.copy().reverse_complement(dreme_alphabet);
+  var logo = new Logo(dreme_alphabet);
+  logo.add_pspm(pspm, 0);
+  var canvas = document.createElement('canvas');
+  canvas.height = height;
+  canvas.width = 0;
+  draw_logo_on_canvas(logo, canvas, false);
+  return canvas;
+}
+
+/*
+ * Create a button designed to contain a single symbol
+ */
+function make_sym_btn(symbol, title, action) {
+  var box, sbox;
+  box = document.createElement("div");
+  box.tabIndex = 0;
+  box.className = "sym_btn";
+  sbox = document.createElement("span");
+  if (typeof symbol == "string") {
+    sbox.appendChild(document.createTextNode(symbol));
+  } else {
+    sbox.appendChild(symbol);
+  }
+  box.appendChild(sbox);
+  box.title = title;
+  box.addEventListener('click', action, false);
+  box.addEventListener('keydown', action, false);
+  return box;
+}
+
+/*
+ * Create a pair of text spans with different classes.
+ * This is useful when using CSS to only display one of them.
+ */
+function text_pair(txt1, cls1, txt2, cls2) {
+  var container, part1, part2;
+  container = document.createElement("span");
+  part1 = document.createElement("span");
+  part1.appendChild(document.createTextNode(txt1));
+  part1.className = cls1;
+  container.appendChild(part1);
+  part2 = document.createElement("span");
+  part2.appendChild(document.createTextNode(txt2));
+  part2.className = cls2;
+  container.appendChild(part2);
+  return container;
+}
+
+/*
+ * Make a colourised sequence.
+ */
+function make_seq(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 = dreme_alphabet.get_colour(dreme_alphabet.get_index(letter));
+    lbox.appendChild(document.createTextNode(seq.substring(i, j)));
+    sbox.appendChild(lbox);
+  }
+  return sbox;
+}
+
+/*
+ * Create a description element taking into account the newlines in the source text.
+ */
+function make_description(text) {
+  var i, j, lines, p;
+  var container = document.createElement("div");
+  var paragraphs = text.split(/\n\n+/);
+  for (i = 0; i < paragraphs.length; i++) {
+    lines = paragraphs[i].split(/\n/);
+    p = document.createElement("p");
+    p.appendChild(document.createTextNode(lines[0]));
+    for (j = 1; j < lines.length; j++) {
+      p.appendChild(document.createElement("br"));
+      p.appendChild(document.createTextNode(lines[j]));
+    }
+    container.appendChild(p);
+  }
+  return container;
+}
+
+/*
+ * Make the table header for the discovered motifs.
+ */
+function make_motif_header() {
+  var row = document.createElement("tr");
+  add_text_header_cell(row, "", "", "motif_ordinal");
+  add_text_header_cell(row, "Motif", "pop_motifs_word", "motif_word");
+  add_text_header_cell(row, "Logo", "pop_motifs_logo", "motif_logo");
+  if (data.options.revcomp) {
+    add_text_header_cell(row, "RC Logo", "pop_motifs_rc_logo", "motif_logo");
+  }
+  add_text_header_cell(row, "E-value", "pop_motifs_evalue", "motif_evalue");
+  add_text_header_cell(row, "Unerased E-value", "pop_motifs_uevalue", "motif_evalue");
+  add_text_header_cell(row, "More", "pop_more", "motif_more");
+  add_text_header_cell(row, "Submit/Download", "pop_submit_dl", "motif_submit");
+  row.className = "more";
+  return row;
+}
+
+/*
+ * Make a compact motif summary row for the discovered motifs.
+ */
+function make_motif_row(tbody, ordinal, motif) {
+  var row = document.createElement("tr");
+  add_text_cell(row, "" + ordinal + ".", "motif_ordinal");
+  add_text_cell(row, motif["id"], "motif_word");
+  add_cell(row, make_logo(motif, 50, false), "motif_logo");
+  if (data.options.revcomp) {
+    add_cell(row, make_logo(motif, 50, true), "motif_logo");
+  }
+  add_text_cell(row, motif["evalue"], "motif_evalue");
+  add_text_cell(row, motif["unerased_evalue"], "motif_evalue");
+  add_cell(row, make_sym_btn(text_pair("\u21A7", "less", "\u21A5", "more"), "Show more information.", function(e) { toggle_class(tbody, "collapsed"); }, "\u21A5", ""), "motif_more");
+  add_cell(row, make_sym_btn("\u21E2", "Submit the motif to another MEME Suite program or download it.", function(e) { action_show_outpop(e, ordinal); }), "motif_submit");
+  return row;
+}
+
+/*
+ * Make a sortable table of enriched matching rows.
+ */
+function make_motif_words(motif) {
+  var row, i, match;
+  var table = document.createElement("table");
+  var thead = document.createElement("thead");
+  row = document.createElement("tr");
+  add_text_header_cell(row, "Word", "pop_match_word", "match_word", function(e) {sort_table(this, compare_words);});
+  add_text_header_cell(row, "Positives", "pop_match_pos", "match_count", function(e) {sort_table(this, compare_counts);});
+  add_text_header_cell(row, "Negatives", "pop_match_neg", "match_count", function(e) {sort_table(this, compare_counts);});
+  add_text_header_cell(row, "P-value", "pop_match_pval", "match_evalue", function(e) {sort_table(this, compare_evalues);});
+  add_text_header_cell(row, "E-value", "pop_match_eval", "match_evalue", function(e) {sort_table(this, compare_evalues);});
+  thead.appendChild(row);
+  table.appendChild(thead);
+  var tbody = document.createElement("tbody");
+  for (i = 0; i < motif.matches.length; i++) {
+    match = motif.matches[i];
+    row = document.createElement("tr");
+    add_cell(row, make_seq(match.seq), "match_word");
+    add_text_cell(row, match.p + " / " + data.sequence_db.count, "match_count");
+    add_text_cell(row, match.n + " / " + data.control_db.count, "match_count");
+    add_text_cell(row, match.pvalue, "match_evalue");
+    add_text_cell(row, match.evalue, "match_evalue");
+    tbody.appendChild(row);
+  }
+  table.appendChild(tbody);
+  return table;
+}
+
+/*
+ * Make an expanded view of a discovered motif.
+ */
+function make_motif_exp(tbody, ordinal, motif) {
+  "use strict";
+  var box, pspm, logo_box;
+  box = $("tmpl_motif_expanded").cloneNode(true);
+  toggle_class(box, "template", false);
+  box.id = "";
+  find_child(box, "tvar_logo").appendChild(make_logo(motif, 150, false));
+  if (data.options.revcomp) {
+    find_child(box, "tvar_rclogo").appendChild(make_logo(motif, 150, true));
+  }
+  set_tvar(box, "tvar_p", motif["p"]);
+  set_tvar(box, "tvar_p_total", data.sequence_db.count);
+  set_tvar(box, "tvar_n", motif["n"]);
+  set_tvar(box, "tvar_n_total", data.control_db.count);
+  set_tvar(box, "tvar_pvalue", motif["pvalue"]);
+  set_tvar(box, "tvar_evalue", motif["evalue"]);
+  set_tvar(box, "tvar_uevalue", motif["unerased_evalue"]);
+  set_tvar(box, "tvar_words", make_motif_words(motif));
+  var cell = document.createElement("td");
+  cell.colSpan = 8;
+  cell.appendChild(box);
+  var row = document.createElement("tr");
+  row.className = "more";
+  row.appendChild(cell);
+  return row;
+}
+
+/*
+ * Convert a string containing a scientific number into the log of that number
+ * without having an intermediate representation of the number.
+ * This is intended to avoid underflow problems with the tiny evalues that
+ * MEME and DREME can create.
+ */
+function sci2log(scinum) {
+  "use strict";
+  var ev_re, match, sig, exp;
+  ev_re = /^(.*)e(.*)$/;
+  if (match = ev_re.exec(scinum)) {
+    sig = parseFloat(match[1]);
+    exp = parseInt(match[2]);
+    return Math.log(sig) + (exp * Math.log(10));
+  }
+  return 0;
+}
+
+/*
+ * Create a table of discovered motifs. A fresh table body is used for each
+ * motif to make hiding/showing rows with css easier.
+ */
+function make_motifs() {
+  "use strict";
+  var i, row, tbody, motif, ordinal;
+  // make the motifs table
+  var container = $("motifs");
+  container.innerHTML = ""; // clear content
+  var table = document.createElement("table");
+  // add a header that is always shown
+  var thead = document.createElement("thead");
+  thead.appendChild(make_motif_header());
+  table.appendChild(thead);
+  for (i = 0; i < data.motifs.length; i++) {
+    ordinal = i + 1;
+    motif = data.motifs[i];
+    tbody = document.createElement("tbody");
+    tbody.className = "collapsed";
+    tbody.appendChild(make_motif_row(tbody, ordinal, motif));
+    tbody.appendChild(make_motif_exp(tbody, ordinal, motif));
+    // create a following header for every row except the last one
+    if ((i + 1) < data.motifs.length) tbody.appendChild(make_motif_header());
+    table.appendChild(tbody);
+  }
+  container.appendChild(table);
+}
+
+/*
+ * Create a table showing all the alphabet symbols, their names and frequencies.
+ */
+function make_alpha_bg(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 = "inputs";
+  // 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");
+    add_text_header_cell(row, "Bg.", "pop_alph_control");
+    add_text_header_cell(row, "");
+    add_text_header_cell(row, "");
+    add_text_header_cell(row, "");
+    add_text_header_cell(row, "Bg.", "pop_alph_control");
+    add_text_header_cell(row, "Name", "pop_alph_name");
+  } else {
+    add_text_header_cell(row, "");
+    add_text_header_cell(row, "Name", "pop_alph_name");
+    add_text_header_cell(row, "Bg.", "pop_alph_control");
+  }
+  // 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));
+      add_text_cell(row, "" + freqs[i].toFixed(3));
+      add_cell(row, colour_symbol(i)); 
+      add_text_cell(row, "~");
+      add_cell(row, colour_symbol(c)); 
+      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));
+      add_text_cell(row, "" + freqs[i].toFixed(3));
+    }
+  }
+  return table;
+}
+
+/*
+ * Updates the format download text in the popup.
+ * This is called when either the format or current motif changes.
+ */
+function update_outpop_format(index) {
+  var motif = data.motifs[index];
+  var fn = [motif_count_matrix, motif_prob_matrix, motif_minimal_meme];
+  var suffix = ["_counts.txt", "_freqs.txt", ".meme"];
+  var format = parseInt($("text_format").value);
+  var text = fn[format](motif);
+  prepare_download(text, "text/plain", motif.id + suffix[format], $("outpop_text_dl"));
+  $("outpop_text").value = text;
+}
+
+/*
+ * Updates the motif logos and format download text in the popup.
+ * This is called whenever the current motif changes.
+ */
+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(dreme_alphabet, "");
+  logo.add_pspm(pspm, 0);
+  canvas = $("outpop_logo");
+  canvas.width = canvas.width; // clear canvas
+  draw_logo_on_canvas(logo, canvas, false);
+  canvas = $("outpop_logo_rc");
+  canvas.width = canvas.width; // clear rc canvas
+  if (data.options.revcomp) {
+    pspm.reverse_complement(dreme_alphabet);
+    logo = new Logo(dreme_alphabet, "");
+    logo.add_pspm(pspm, 0);
+    draw_logo_on_canvas(logo, canvas, false);
+  }
+  num = $("outpop_num");
+  num.innerHTML = "";
+  num.appendChild(document.createTextNode("" + (index + 1)));
+  update_outpop_format(index);
+}
+
+
+/*
+ * Initialise and display the download popup.
+ */
+function action_show_outpop(e, ordinal) {
+  "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", dreme_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(dreme_alphabet.has_complement() ? "submit_tomtom" : "submit_fimo");
+    default_prog.checked = true;
+    action_outpop_program.call(default_prog);
+    // disable reverse-complement when not DNA
+    $("logo_rc_option").disabled = !dreme_alphabet.has_complement(); 
+    // set errorbars on when ssc is on
+    $("logo_ssc").addEventListener("change", action_outpop_ssc, false);
+  }
+  // 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;
+  }
+  update_outpop_motif(ordinal - 1);
+  // display the download popup
+  $("grey_out_page").style.display = "block";
+  $("download").style.display = "block";
+  $("outpop_close").focus();
+}
+
+/*
+ * Hide the download popup.
+ */
+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();
+  }
+}
+
+/*
+ * Show the next motif in the download popup.
+ */
+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);
+}
+
+/*
+ * Show the previous motif in the download popup.
+ */
+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);
+}
+
+/*
+ * Highlight the selected row in the program list.
+ */
+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);
+  }
+}
+
+/*
+ * Enable error bars when small sample correction is enabled.
+ */
+function action_outpop_ssc() {
+  "use strict";
+  $("logo_err").value = $("logo_ssc").value;
+}
+
+/*
+ * Submit the motif to the selected program.
+ */
+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(data.motifs[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);
+}
+
+/*
+ * Download the format text.
+ * Wire the link containing the data URI text to a download button so it looks
+ * the same as the server submit stuff.
+ */
+function action_outpop_download_motif(e) {
+  $("outpop_text_dl").click();
+}
+
+/*
+ * Download the motif logo.
+ * The EPS format can be calculated locally in Javascript
+ */
+function action_outpop_download_logo(e) {
+  "use strict";
+  var pspm, logo, eps;
+  var logo_rc, logo_ssc, logo_width, logo_height;
+  var motif = data.motifs[current_motif];
+  if ($("logo_format").value == "0") { // EPS
+    logo_rc = ($("logo_rc").value == "1");
+    logo_ssc = ($("logo_ssc").value == "1");
+    logo_width = parseFloat($("logo_width").value);
+    if (isNaN(logo_width) || !isFinite(logo_width) || logo_width <= 0) logo_width = null;
+    logo_height = parseFloat($("logo_height").value);
+    if (isNaN(logo_height) || !isFinite(logo_height) || logo_height <= 0) logo_height = null;
+    // create a PSPM from the motif
+    pspm = motif_pspm(motif);
+    if (logo_rc) pspm.reverse_complement(dreme_alphabet);
+    logo = new Logo(dreme_alphabet);
+    logo.add_pspm(pspm, 0);
+    eps = logo.as_eps({"ssc": logo_ssc, "logo_width": logo_width, "logo_height": logo_height});
+    prepare_download(eps, "application/postscript", motif.id + ".eps");
+  } else {
+    $("logo_motifs").value = motif_minimal_meme(motif);
+    $("logo_form").submit();
+  }
+}
+
+/*
+ * Change the selected tab in the download popup.
+ */
+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);
+  }
+}
+
+/*
+ * Update the text in the download format popup.
+ */
+function action_outpop_format() {
+  update_outpop_format(current_motif);
+}
+
+/*
+ * Find all text nodes in the given container.
+ */
+function text_nodes(container) {
+  var textNodes = [];
+  var stack = [container];
+  // depth first search to maintain ordering when flattened 
+  while (stack.length > 0) {
+    var node = stack.pop();
+    if (node.nodeType == Node.TEXT_NODE) {
+      textNodes.push(node);
+    } else {
+      for (var i = node.childNodes.length-1; i >= 0; i--) {
+        stack.push(node.childNodes[i]);
+      }
+    }
+  }
+  return textNodes;
+}
+
+/*
+ * Get the text out of a specific text node.
+ */
+function node_text(node) {
+  if (node === undefined) {
+    return '';
+  } else if (node.textContent) {
+    return node.textContent;
+  } else if (node.innerText) {
+    return node.innerText;
+  } else {
+    return '';
+  }
+}
+
+/*
+ * Get the text contained within the element.
+ */
+function elem_text(elem, separator) {
+  if (separator === undefined) separator = '';
+  return text_nodes(elem).map(node_text).join(separator);
+}
+
+/*
+ * Sort all rows in the first table body based on the column of the given element and the comparison function.
+ * The sort is not very fast and is intended for small tables only.
+ */
+function sort_table(colEle, compare_function) {
+  //find the parent of colEle that is either a td or th
+  var i, j;
+  var cell = colEle;
+  while (true) {
+    if (cell == null) return;
+    if (cell.nodeType == Node.ELEMENT_NODE && 
+        (cell.tagName.toLowerCase() == "td" || cell.tagName.toLowerCase() == "th")) {
+      break;
+    }
+    cell = cell.parentNode;
+  }
+  //find the parent of cell that is a tr
+  var row = cell;
+  while (true) {
+    if (row == null) return;
+    if (row.nodeType == Node.ELEMENT_NODE && row.tagName.toLowerCase() == "tr") {
+      break;
+    }
+    row = row.parentNode;
+  }
+  //find the parent of row that is a table
+  var table = row;
+  while (true) {
+    if (table == null) return;
+    if (table.nodeType == Node.ELEMENT_NODE && table.tagName.toLowerCase() == "table") {
+      break;
+    }
+    table = table.parentNode;
+  }
+  var column_index = cell.cellIndex;
+  // do a bubble sort, because the tables are so small it doesn't matter
+  var change;
+  var trs = table.tBodies[0].getElementsByTagName('tr');
+  var already_sorted = true;
+  var reverse = false;
+  while (true) {
+    do {
+      change = false;
+      for (i = 0; i < trs.length -1; i++) {
+        var v1 = elem_text(trs[i].cells[column_index]);
+        var v2 = elem_text(trs[i+1].cells[column_index]);
+        if (reverse) {
+          var tmp = v1;
+          v1 = v2;
+          v2 = tmp;
+        }
+        if (compare_function(v1, v2) > 0) {
+          exchange(trs[i], trs[i+1], table);
+          change = true;
+          already_sorted = false;
+          trs = table.tBodies[0].getElementsByTagName('tr');
+        }
+      }
+    } while (change);
+    if (reverse) break;// we've sorted twice so exit
+    if (!already_sorted) break;// sort did something so exit
+    // when it's sorted one way already then sort the opposite way
+    reverse = true;
+  }
+  // put arrows on the headers
+  var headers = table.tHead.getElementsByTagName('tr');
+  for (i = 0; i < headers.length; i++) {
+    for (j = 0; j < headers[i].cells.length; j++) {
+      var cell = headers[i].cells[j];
+      var arrows = cell.getElementsByClassName("sort_arrow");
+      var arrow;
+      if (arrows.length == 0) {
+        arrow = document.createElement("span");
+        arrow.className = "sort_arrow";
+        cell.insertBefore(arrow, cell.firstChild);
+      } else {
+        arrow = arrows[0];
+      }
+      arrow.innerHTML = "";
+      if (j == column_index) {
+        arrow.appendChild(document.createTextNode(reverse ? "\u25B2" : "\u25BC"));
+      }
+    }
+  }
+}
+
+/*
+ * Swap two rows in a table.
+ */
+function exchange(oRowI, oRowJ, oTable) {
+  var i = oRowI.rowIndex;
+  var j = oRowJ.rowIndex;
+   if (i == j+1) {
+    oTable.tBodies[0].insertBefore(oRowI, oRowJ);
+  } if (j == i+1) {
+    oTable.tBodies[0].insertBefore(oRowJ, oRowI);
+  } else {
+    var tmpNode = oTable.tBodies[0].replaceChild(oRowI, oRowJ);
+    if(typeof(oRowI) != "undefined") {
+      oTable.tBodies[0].insertBefore(tmpNode, oRowI);
+    } else {
+      oTable.appendChild(tmpNode);
+    }
+  }
+}
+
+/*
+ * Compare two E-values which may be very small.
+ */
+function compare_evalues(v1, v2) {
+  var e1 = sci2log(v1);
+  var e2 = sci2log(v2);
+  if (e1 < e2) return -1;
+  else if (e1 > e2) return 1;
+  return 0;
+}
+
+/*
+ * Compare two counts.
+ */
+function compare_counts(v1, v2) {
+  var re = /(\d+)\s*\/\s*\d+/;
+  var m1 = re.exec(v1);
+  var m2 = re.exec(v2);
+  if (m1 == null && m2 == null) return 0;
+  if (m1 == null) return -1;
+  if (m2 == null) return 1;
+  return parseInt(m2[1]) - parseInt(m1[1]);
+}
+
+/*
+ * Compare two sequence words.
+ */
+function compare_words(v1, v2) {
+  return v1.localeCompare(v2);
+}
+
+
+</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>
+/* dreme output specific css */
+div.header {
+  position: relative;
+  overflow: hidden;
+  margin-top: 15px;
+  margin-bottom: 5px;
+  margin-right: 3px;
+  margin-left: 3px;
+}
+div.header > h2 {
+  font-size: 1.5em;
+  font-style: normal;
+  margin: 0;
+  font-variant: small-caps; 
+  font-family: Georgia, "Times New Roman", Times, serif;
+}
+div.header > span {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+}
+
+div.template {
+  position: absolute;
+  z-index: 1;
+  left: 0;
+  top: 0;
+  visibility: hidden;
+}
+
+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.box + div.box {
+  margin-top: 5px;
+}
+
+th.motif_ordinal {
+
+}
+td.motif_ordinal {
+  text-align: right;
+  padding-right: 10px;
+  font-weight: bold;
+  font-size: large;
+}
+th.motif_word {
+  padding-right: 10px;
+}
+td.motif_word {
+  font-size:15px; 
+  font-family: 'Courier New', Courier, monospace;
+  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_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.match_word {
+  padding-right: 10px;
+}
+td.match_word {
+  padding-right: 10px;
+  font-weight: bold;
+  font-size: large; 
+  font-family: 'Courier New', Courier, monospace;
+}
+th.match_evalue, th.match_count {
+  text-align: right;
+  padding-right: 10px;
+}
+td.match_evalue, td.match_count {
+  text-align: right;
+  white-space: nowrap;
+  padding-right: 20px;
+}
+
+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.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;
+}
+
+table.inputs {
+  margin-top: 20px;
+  border-collapse:collapse;
+}
+table.inputs * td, table.inputs * th {
+  padding-left: 15px;
+  padding-right: 15px;
+  padding-top: 1px;
+  padding-bottom: 1px;
+}
+
+/* program settings */
+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;
+}
+
+/* show the expanded motif only when the collapsed one is hidden */
+tbody *.less, tbody.collapsed *.more {
+  display: none;
+}
+
+tbody.collapsed *.less {
+  display: inline;
+}
+
+</style>
+  </head>
+  <body data-scrollpad="true">
+    <!--  -->
+    <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 class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+
+    <div class="pop_content" id="pop_motifs_word">
+      <p>
+        The name of the motif uses the IUPAC codes for nucleotides which has 
+        a different letter to represent each of the 15 possible combinations.
+      </p>
+      <p>
+        The name is itself a representation of the motif though the position
+        weight matrix is not directly equalivant as it is generated from the
+        sites found that matched the letters given in the name.
+      </p>
+      <p>
+        <a id="doc_alphabets_url" href="#">
+        Read more about the MEME suite's use of the IUPAC alphabets.
+        </a>
+        <script>$("doc_alphabets_url").href = site_url + "/doc/alphabets.html";</script>
+      </p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_motifs_logo">
+      <p>The logo of the motif.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_motifs_rc_logo">
+      <p>The logo of the reverse complement motif.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_motifs_evalue">
+      <p>The E-value is the enrichment p-value times the number of candidate 
+        motifs tested.</p>
+      <p>The enrichment p-value is calculated using Fisher's Exact Test for 
+        enrichment of the motif in the positive sequences.</p>
+      <p>Note that the counts used in Fisher's Exact Test are made after 
+        erasing sites that match previously found motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_motifs_uevalue">
+      <p>The E-value of the motif calculated without erasing the sites of 
+        previously found motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_more">
+      <p>Show more information on the motif.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_submit_dl">
+      <p>Submit your motif to another MEME Suite program or download your motif.</p>
+      <h5>Supported Programs</h5>
+      <dl>
+        <dt>Tomtom</dt>
+        <dd>Tomtom is a tool for searching for similar known motifs. 
+          </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.
+          </dd>
+        <dt>FIMO</dt>
+        <dd>FIMO is a tool for searching biological sequence databases for 
+          sequences that contain one or more known motifs.
+          </dd>
+        <dt>GOMO</dt>
+        <dd>GOMO is a tool for identifying possible roles (Gene Ontology 
+          terms) for DNA binding motifs.
+          </dd>
+        <dt>SpaMo</dt>
+        <dd>SpaMo is a tool for inferring possible transcription factor
+          complexes by finding motifs with enriched spacings.
+          </dd>
+      </dl>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_motif_positives">
+      <p># positive sequences matching the motif / # positive sequences.</p>
+      <p>Note these counts are made after erasing sites that match previously
+        found motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_motif_negatives">
+      <p># negative sequences matching the motif / # negative sequences.</p>
+      <p>Note these counts are made after erasing sites that match previously
+        found motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_motif_pvalue">
+      <p>The p-value of Fisher's Exact Test for enrichment of the motif in 
+        the positive sequences.</p>
+      <p>Note that the counts used in Fisher's Exact Test are made after 
+        erasing sites that match previously found motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_motif_evalue">
+      <p>The E-value is the motif p-value times the number of candidate motifs 
+        tested.</p>
+      <p>Note that the p-value was calculated with counts made after 
+        erasing sites that match previously found motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_motif_uevalue">
+      <p>The E-value of the motif calculated without erasing the sites of 
+        previously found motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_match_word">
+      <p>All words matching the motif whose uncorrected p-value is less than
+      <span id="help_add_pv_thresh"></span>.</p>
+      <script>$("help_add_pv_thresh").innerHTML = data.options.add_pv_thresh;</script>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_match_pos">
+      <p># positive sequences with matches to the word / # positive sequences.</p>
+      <p>Note these counts are made after erasing sites that match previously
+        found motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_match_neg">
+      <p># negative sequences with matches to the word / # negative sequences.</p>
+      <p>Note these counts are made after erasing sites that match previously
+        found motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_match_pval">
+      <p>The p-value of Fisher's Exact Test for enrichment of the word in 
+        the positive sequences.</p>
+      <p>Note that the counts used in Fisher's Exact Test are made after 
+        erasing sites that match previously found motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_match_eval">
+      <p>The word p-value times the number of candidates tested.</p>
+      <p>Note that the p-value was calculated with counts made after 
+        erasing sites that match previously found motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+
+    <div class="pop_content" id="pop_seq_source">
+      <p>The sequence file used by DREME to find the motifs.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_seq_alph">
+      <p>The alphabet of the sequences.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+    <div class="pop_content" id="pop_seq_count">
+      <p>The count of the sequences.</p>
+      <div class="pop_close">[<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 class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+
+    <div class="pop_content" id="pop_alph_control">
+      <p>The frequency of the alphabet symbol in the control dataset.</p>
+      <div class="pop_close">[<a href="javascript:help_popup()">close</a> ]</div>
+    </div>
+
+    <!-- templates -->
+
+    <div class="template box expanded_motif" id="tmpl_motif_expanded">
+      <div>
+        <span class="tvar_logo"></span>
+        <span class="tvar_rclogo"></span>
+      </div>
+      <h4>Details</h4>
+      <table class="details">
+        <thead>
+          <tr>
+            <th class="match_count">Positives <div class="help" data-topic="pop_motif_positives"></div></th>
+            <th class="match_count">Negatives <div class="help" data-topic="pop_motif_negatives"></div></th>
+            <th class="match_evalue">P-value <div class="help" data-topic="pop_motif_pvalue"></div></th>
+            <th class="match_evalue">E-value <div class="help" data-topic="pop_motif_evalue"></div></th>
+            <th class="match_evalue">Unerased E-value <div class="help" data-topic="pop_motif_uevalue"></div></th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td class="match_count">
+              <span class="tvar_p"></span> / <span class="tvar_p_total"></span>
+            </td>
+            <td class="match_count">
+              <span class="tvar_n"></span> / <span class="tvar_n_total"></span>
+            </td>
+            <td class="tvar_pvalue match_evalue"></td>
+            <td class="tvar_evalue match_evalue"></td>
+            <td class="tvar_uevalue match_evalue"></td>
+          </tr>
+        </tbody>
+      </table>
+      <h4>Enriched Matching Words</h4>
+      <div class="tvar_words"></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="250"></canvas>
+              <canvas id="outpop_logo_rc" height="100" width="250"></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>
+                  </select>
+                </div>
+                <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>
+                <a id="outpop_text_dl" download="meme.txt" href=""></a>
+              </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="DREME"/>
+                  <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,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" alt="DREME Logo"/>
+        <h1>DREME</h1>
+        <h2>Discriminative Regular Expression Motif Elicitation</h2>
+      </div>
+      <p class="spaced">
+        For further information on how to interpret these results or to get a 
+        copy of the MEME software please access 
+        <a href="http://meme.nbcr.net/">http://meme.nbcr.net</a>. 
+      </p>
+      <p>
+        If you use DREME in your research please cite the following paper:<br />
+        <span class="citation">
+          Timothy L. Bailey, "DREME: Motif discovery in transcription factor ChIP-seq data", <i>Bioinformatics</i>, <b>27</b>(12):1653-1659, 2011.
+          <a href="http://bioinformatics.oxfordjournals.org/content/27/12/1653">[full text]</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="#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>
+    <!-- description -->
+    <div id="description_section" style="display:none">
+      <div id="description" class="header">
+        <h2>Description</h2>
+      </div>
+      <div id="description_text" class="box">
+      </div>
+    </div>
+    <script>
+      if (data.description) {
+        $("description_text").innerHTML = "";
+        $("description_text").appendChild(make_description(data.description));
+        $("description_section").style.display = "block";
+      }
+    </script>
+    <!-- motifs -->
+    <div id="motifs_sec" class="header">
+      <h2>Discovered Motifs</h2>
+      <span><a href="#inputs_sec">Next</a>&nbsp;<a href="#">Top</a></span>
+    </div>
+    <div id="motifs" class="box">
+      <p>No motifs were discovered!</p>
+    </div>
+    <script>make_motifs();</script>
+    <!-- inputs and settings -->
+    <div id="inputs_sec" class="header">
+      <h2>Inputs &amp; Settings</h2>
+      <span><a href="#motifs_sec">Previous</a>&nbsp;<a href="#info_sec">Next</a>&nbsp;<a href="#">Top</a></span>
+    </div>
+    <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>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_alphabet"></td>
+          <td id="ins_seq_count"></td>
+        </tr>
+      </table>
+      <script>
+      {
+        var db = data.sequence_db;
+        $("ins_seq_source").innerHTML = db.file;
+        $("ins_seq_alphabet").innerHTML = dreme_alphabet.get_alphabet_name();
+        $("ins_seq_count").innerHTML = db.count;
+      }
+      </script>
+      <h4>Control Sequences</h4>
+      <table id="seq_info" class="inputs">
+        <tr><th>Source <div class="help" data-topic="pop_seq_source"></div></th>
+          <th>Sequence Count <div class="help" data-topic="pop_seq_count"></div></th>
+        </tr>
+        <tr>
+          <td id="ins_cseq_source"></td>
+          <td id="ins_cseq_count"></td>
+        </tr>
+      </table>
+      <script>
+      {
+        var db = data.control_db;
+        if (db.from == "shuffled") {
+          $("ins_cseq_source").innerHTML = "Shuffled Sequences";
+        } else {
+          $("ins_cseq_source").innerHTML = db.file;
+        }
+        $("ins_cseq_count").innerHTML = db.count;
+      }
+      </script>
+      <h4>Background</h4>
+      <span id="alpha_bg"></span>
+      <script>
+      {
+        $("alpha_bg").appendChild(make_alpha_bg(dreme_alphabet, data.control_db.freqs));
+      }
+      </script>
+      <h4>Other Settings</h4>
+      <table id="tbl_settings" class="inputs hide_advanced">
+        <tr>
+          <th>Strand Handling</th>
+          <td id="opt_strand">
+            <span class="strand_none">This alphabet only has one strand</span>
+            <span class="strand_given">Only the given strand is processed</span>
+            <span class="strand_both">Both the given and reverse complement strands are processed</span>
+          </td>
+        </tr>
+        <tr><th># REs to Generalize</th><td id="opt_ngen"></td></tr>
+        <tr><th>Shuffle Seed</th><td id="opt_seed"></td></tr>
+        <tr><th>E-value Threshold</th><td id="opt_stop_evalue"></td></tr>
+        <tr><th>Max Motif Count</th><td id="opt_stop_count"></td></tr>
+        <tr><th>Max Run Time</th><td id="opt_stop_time"></td></tr>
+      </table>
+      <script>
+      {
+        $("opt_strand").className = (dreme_alphabet.has_complement() ? (data.options.revcomp ? "both" : "given") : "none");
+        $("opt_ngen").innerHTML = data.options.ngen;
+        $("opt_seed").innerHTML = data.options.seed;
+        $("opt_stop_evalue").innerHTML = data.options.stop.evalue;
+        $("opt_stop_count").innerHTML = (typeof data.options.stop.count == "number" ? data.options.stop.count : "No maximum motif count.");
+        $("opt_stop_time").innerHTML = (typeof data.options.stop.time == "number" ? data.options.stop.time + " seconds." : "No maximum running time.");
+      }
+      </script>
+    </div>
+    <!-- list information on this program -->
+    <div id="info_sec" class="bar" style="position:relative">
+      <div style="position: absolute; right: 0;"><a href="#inputs_sec">Previous</a> <a href="#">Top</a></div>
+      <div class="subsection">
+        <h5 id="version">DREME 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, "DREME: Motif discovery in transcription factor ChIP-seq data", <i>Bioinformatics</i>, <b>27</b>(12):1653-1659, 2011.
+          <a href="http://bioinformatics.oxfordjournals.org/content/27/12/1653">[full text]</a>
+        </span>
+      </div>
+      <div class="subsection">
+        <h5 id="command">Command line</h5>
+        <textarea id="cmd" rows="3" style="width:100%;" readonly="readonly">
+        </textarea>
+        <script>$("cmd").value = data["cmd"].join(" ");</script>
+      </div>
+    </div>
+    
+  </body>
+</html>