about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlexander Kabui2020-12-04 21:41:28 +0300
committerAlexander Kabui2020-12-04 21:41:28 +0300
commitf67f0f0d8f62f8bb6f8b708886c6ec2252450aeb (patch)
treead01d1e1f2e118ddb263d325a8b64b43064366d6
parent9b7c09e14576a39c3f986e5b6e633b0380a72095 (diff)
parent8084309bdfa46e29d3cc941911fa25b23f8400d5 (diff)
downloadgenenetwork2-f67f0f0d8f62f8bb6f8b708886c6ec2252450aeb.tar.gz
Merge branch 'testing' of https://github.com/genenetwork/genenetwork2 into bugfix/markdown-links-fix
-rw-r--r--wqflask/wqflask/correlation/show_corr_results.py1
-rw-r--r--wqflask/wqflask/export_traits.py8
-rw-r--r--wqflask/wqflask/markdown_routes.py55
-rw-r--r--wqflask/wqflask/static/new/css/markdown.css41
-rw-r--r--wqflask/wqflask/static/new/javascript/lodheatmap.js6
-rw-r--r--wqflask/wqflask/static/new/javascript/panelutil.js1
-rw-r--r--wqflask/wqflask/static/new/javascript/search_results.js165
-rw-r--r--wqflask/wqflask/templates/collections/view.html33
-rw-r--r--wqflask/wqflask/templates/correlation_page.html54
-rw-r--r--wqflask/wqflask/templates/environment.html152
-rw-r--r--wqflask/wqflask/templates/search_result_page.html46
11 files changed, 368 insertions, 194 deletions
diff --git a/wqflask/wqflask/correlation/show_corr_results.py b/wqflask/wqflask/correlation/show_corr_results.py
index 1b801b1d..51aa1622 100644
--- a/wqflask/wqflask/correlation/show_corr_results.py
+++ b/wqflask/wqflask/correlation/show_corr_results.py
@@ -487,6 +487,7 @@ def generate_corr_json(corr_results, this_trait, dataset, target_dataset, for_ap
         results_dict['index'] = i + 1
         results_dict['trait_id'] = trait.name
         results_dict['dataset'] = trait.dataset.name
+        results_dict['hmac'] = hmac.data_hmac('{}:{}'.format(trait.name, trait.dataset.name))
         if target_dataset.type == "ProbeSet":
             results_dict['symbol'] = trait.symbol
             results_dict['description'] = "N/A"
diff --git a/wqflask/wqflask/export_traits.py b/wqflask/wqflask/export_traits.py
index 3a886537..6fb760e0 100644
--- a/wqflask/wqflask/export_traits.py
+++ b/wqflask/wqflask/export_traits.py
@@ -44,7 +44,7 @@ def export_search_results_csv(targs):
         if targs['filter_term'] != "None":
             metadata.append(["Search Filter Terms: " + targs['filter_term']])
     metadata.append(["Exported Row Number: " + str(len(table_rows))])
-    metadata.append(["Funding for The GeneNetwork: NIAAA (U01AA13499, U24AA13513), NIDA, NIMH, and NIAAA (P20-DA21131), NCI MMHCC (U01CA105417), and NCRR (U01NR 105417)"])
+    metadata.append(["Funding for The GeneNetwork: NIGMS (R01 GM123489, 2017-2021), NIDA (P30 DA044223, 2017-2022), NIA (R01AG043930, 2013-2018), NIAAA (U01 AA016662, U01 AA013499, U24 AA013513, U01 AA014425, 2006-2017), NIDA/NIMH/NIAAA (P20-DA 21131, 2001-2012), NCI MMHCC (U01CA105417), NCRR/BIRN (U24 RR021760)"])
     metadata.append([])
 
     trait_list = []
@@ -54,7 +54,7 @@ def export_search_results_csv(targs):
         trait_ob = retrieve_trait_info(trait_ob, trait_ob.dataset, get_qtl_info=True)
         trait_list.append(trait_ob)
 
-    table_headers = ['Species', 'Group', 'Dataset', 'Record ID', 'Symbol', 'Description', 'ProbeTarget', 'PubMed_ID', 'Chr', 'Mb', 'Alias', 'Gene_ID', 'Homologene_ID', 'UniGene_ID', 'Strand_Probe', 'Probe_set_specificity', 'Probe_set_BLAT_score', 'Probe_set_BLAT_Mb_start', 'Probe_set_BLAT_Mb_end', 'QTL_Chr', 'QTL_Mb', 'Locus_at_Peak', 'Max_LRS', 'P_value_of_MAX', 'Mean_Expression']
+    table_headers = ['Index', 'URL', 'Species', 'Group', 'Dataset', 'Record ID', 'Symbol', 'Description', 'ProbeTarget', 'PubMed_ID', 'Chr', 'Mb', 'Alias', 'Gene_ID', 'Homologene_ID', 'UniGene_ID', 'Strand_Probe', 'Probe_set_specificity', 'Probe_set_BLAT_score', 'Probe_set_BLAT_Mb_start', 'Probe_set_BLAT_Mb_end', 'QTL_Chr', 'QTL_Mb', 'Locus_at_Peak', 'Max_LRS', 'P_value_of_MAX', 'Mean_Expression']
 
     traits_by_group = sort_traits_by_group(trait_list)
 
@@ -77,7 +77,7 @@ def export_search_results_csv(targs):
 
         csv_rows.append(full_headers)
 
-        for trait in group_traits:
+        for i, trait in enumerate(group_traits):
             if getattr(trait, "symbol", None):
                 trait_symbol = getattr(trait, "symbol")
             elif getattr(trait, "abbreviation", None):
@@ -85,6 +85,8 @@ def export_search_results_csv(targs):
             else:
                 trait_symbol = "N/A"
             row_contents = [
+                i + 1,
+                "https://genenetwork.org/show_trait?trait_id=" + str(trait.name) + "&dataset=" + str(trait.dataset.name),
                 trait.dataset.group.species,
                 trait.dataset.group.name,
                 trait.dataset.name,
diff --git a/wqflask/wqflask/markdown_routes.py b/wqflask/wqflask/markdown_routes.py
index 3109a352..3b60bc96 100644
--- a/wqflask/wqflask/markdown_routes.py
+++ b/wqflask/wqflask/markdown_routes.py
@@ -4,6 +4,10 @@ Render pages from github, or if they are unavailable, look for it else where
 """
 import requests
 import markdown
+import os
+import sys
+
+from bs4 import BeautifulSoup
 
 from flask import Blueprint
 from flask import render_template
@@ -16,18 +20,25 @@ policies_blueprint = Blueprint("policies_blueprint", __name__)
 facilities_blueprint = Blueprint("facilities_blueprint", __name__)
 
 
-def render_markdown(file_name):
+def render_markdown(file_name, is_remote_file=True):
     """Try to fetch the file name from Github and if that fails, try to
 look for it inside the file system """
     github_url = ("https://raw.githubusercontent.com/"
                   "genenetwork/gn-docs/master/")
 
+    if not is_remote_file:
+        text = ""
+        with open(file_name, "r", encoding="utf-8") as input_file:
+            text = input_file.read()
+        return markdown.markdown(text,
+                                 extensions=['tables'])
+
     md_content = requests.get(f"{github_url}{file_name}")
+
     if md_content.status_code == 200:
+        return markdown.markdown(md_content.content.decode("utf-8"),
+                                 extensions=['tables'])
 
-        return markdown.markdown(md_content.content.decode("utf-8"), extensions=['tables'])
-    # TODO: Add fallback on our git server by checking the mirror.
-    # Content not available
     return (f"\nContent for {file_name} not available. "
             "Please check "
             "(here to see where content exists)"
@@ -35,6 +46,14 @@ look for it inside the file system """
             "Please reach out to the gn2 team to have a look at this")
 
 
+def get_file_from_python_search_path(pathname_suffix):
+    cands = [os.path.join(d, pathname_suffix) for d in sys.path]
+    try:
+        return list(filter(os.path.exists, cands))[0]
+    except IndexError:
+        return None
+
+
 @glossary_blueprint.route('/')
 def glossary():
     return render_template(
@@ -51,7 +70,33 @@ def references():
 
 @environments_blueprint.route("/")
 def environments():
-    return render_template("environment.html", rendered_markdown=render_markdown("general/environments/environments.md")), 200
+
+    md_file = get_file_from_python_search_path("wqflask/DEPENDENCIES.md")
+    svg_file = get_file_from_python_search_path(
+        "wqflask/dependency-graph.html")
+    svg_data = None
+    if svg_file:
+        with open(svg_file, 'r') as f:
+            svg_data = "".join(
+                BeautifulSoup(f.read(),
+                              'lxml').body.script.contents)
+
+    if md_file is not None:
+        return (
+            render_template("environment.html",
+                            svg_data=svg_data,
+                            rendered_markdown=render_markdown(
+                                md_file,
+                                is_remote_file=False)),
+            200
+        )
+    # Fallback: Fetch file from server
+    return (render_template(
+        "environment.html",
+        svg_data=None,
+        rendered_markdown=render_markdown(
+            "general/environments/environments.md")),
+            200)
 
 
 @links_blueprint.route("/")
diff --git a/wqflask/wqflask/static/new/css/markdown.css b/wqflask/wqflask/static/new/css/markdown.css
index dca3e31d..38d664e2 100644
--- a/wqflask/wqflask/static/new/css/markdown.css
+++ b/wqflask/wqflask/static/new/css/markdown.css
@@ -57,8 +57,47 @@
     word-spacing: 0.2em;
 }
 
+.graph-legend h1 {
+    text-align: center;
+}
+
+.graph-legend,
+#guix-graph {
+    width: 90%;
+    margin: 10px auto;
+}
+
+#guix-graph {
+    border: solid 2px black;
+}
+
+#markdown table {
+    width: 100%;
+}
+
+#markdown td {
+    padding: 1em;
+    text-align: left;
+}
+
+#markdown th {
+    text-align: center;
+}
+
+#markdown table,
+#markdown td,
+#markdown th {
+    border: solid 2px black;
+}
+
+#markdown td,
+#markdown th {
+    padding-top: 8px;
+    padding-bottom: 8px;
+}
+
 @media(max-width:650px) {
     .container {
         width: 100vw;
     }
-}
\ No newline at end of file
+}
diff --git a/wqflask/wqflask/static/new/javascript/lodheatmap.js b/wqflask/wqflask/static/new/javascript/lodheatmap.js
index 965a1d53..b82c95ad 100644
--- a/wqflask/wqflask/static/new/javascript/lodheatmap.js
+++ b/wqflask/wqflask/static/new/javascript/lodheatmap.js
@@ -44,7 +44,9 @@ lodheatmap = function() {
       _ref = data.chrnames;
       for (_i = 0, _len = _ref.length; _i < _len; _i++) {
         chr = _ref[_i];
-        xLR[chr[0]] = getLeftRight(data.posByChr[chr[0]]);
+        if (data.posByChr[chr[0]].length > 0){
+          xLR[chr[0]] = getLeftRight(data.posByChr[chr[0]]);
+        }
       }
       zmin = 0;
       zmax = 0;
@@ -144,7 +146,7 @@ lodheatmap = function() {
       }).attr("stroke", "none").attr("stroke-width", "1").on("mouseover.paneltip", function(d) {
         yaxis.select("text#yaxis" + d.lodindex).attr("opacity", 1);
         d3.select(this).attr("stroke", "black");
-        return celltip.show(d);
+        return celltip.show(d, this);
       }).on("mouseout.paneltip", function(d) {
         yaxis.select("text#yaxis" + d.lodindex).attr("opacity", 0);
         d3.select(this).attr("stroke", "none");
diff --git a/wqflask/wqflask/static/new/javascript/panelutil.js b/wqflask/wqflask/static/new/javascript/panelutil.js
index 3c715c81..ea55a7cf 100644
--- a/wqflask/wqflask/static/new/javascript/panelutil.js
+++ b/wqflask/wqflask/static/new/javascript/panelutil.js
@@ -159,7 +159,6 @@ chrscales = function(data, width, chrGap, leftMargin, pad4heatmap, mappingScale)
 
     if (mappingScale == "morgan") {
         max_pos = d3.max(data.posByChr[chr[0]])
-        console.log("max_pos:", max_pos)
         data.xscale[chr[0]] = d3.scale.linear().domain([chrStart[i], max_pos]).range([data.chrStart[i], data.chrEnd[i]]);
     }
     else {
diff --git a/wqflask/wqflask/static/new/javascript/search_results.js b/wqflask/wqflask/static/new/javascript/search_results.js
index 86660126..9ffef4f8 100644
--- a/wqflask/wqflask/static/new/javascript/search_results.js
+++ b/wqflask/wqflask/static/new/javascript/search_results.js
@@ -1,42 +1,87 @@
+change_buttons = function() {
+  var button, buttons, item, num_checked, text, _i, _j, _k, _l, _len, _len2, _len3, _len4, _results, _results2;
+  buttons = ["#add", "#remove"];
+
+  num_checked = 0
+  table_api = $('#trait_table').DataTable();
+  check_cells = table_api.column(0).nodes().to$();
+  for (let i = 0; i < check_cells.length; i++) {
+    if (check_cells[i].childNodes[0].checked){
+      num_checked += 1
+    }
+  }
+
+  if (num_checked === 0) {
+    for (_i = 0, _len = buttons.length; _i < _len; _i++) {
+      button = buttons[_i];
+      $(button).prop("disabled", true);
+    }
+  } else {
+    for (_j = 0, _len2 = buttons.length; _j < _len2; _j++) {
+      button = buttons[_j];
+      $(button).prop("disabled", false);
+    }
+  }
+};
+
 $(function() {
-  var add, change_buttons, checked_traits, deselect_all, invert, remove, removed_traits, select_all;
+  var add, checked_traits, deselect_all, invert, remove, removed_traits, select_all;
 
   checked_traits = null;
   select_all = function() {
-    console.log("selected_all");
-    $(".trait_checkbox").each(function() {
-        $(this).prop('checked', true);
-        if (!$(this).closest('tr').hasClass('selected')) {
-            $(this).closest('tr').addClass('selected')
-        }
-    });
+    table_api = $('#trait_table').DataTable();
+
+    check_cells = table_api.column(0).nodes().to$();
+    for (let i = 0; i < check_cells.length; i++) {
+      check_cells[i].childNodes[0].checked = true;
+    }
+
+    check_rows = table_api.rows().nodes();
+    for (let i =0; i < check_rows.length; i++) {
+      check_rows[i].classList.add("selected");
+    }
+
+    change_buttons();
   };
 
   deselect_all = function() {
-    $(".trait_checkbox").each(function() {
-        $(this).prop('checked', false);
-        if ($(this).closest('tr').hasClass('selected')) {
-            $(this).closest('tr').removeClass('selected')
-        }
-    });
+    table_api = $('#trait_table').DataTable();
+
+    check_cells = table_api.column(0).nodes().to$();
+    for (let i = 0; i < check_cells.length; i++) {
+      check_cells[i].childNodes[0].checked = false;
+    }
+
+    check_rows = table_api.rows().nodes();
+    for (let i =0; i < check_rows.length; i++) {
+      check_rows[i].classList.remove("selected")
+    }
+
+    change_buttons();
   };
 
   invert = function() {
-    $(".trait_checkbox").each(function() {
-        if ($(this).prop('checked') == true) {
-            $(this).prop('checked', false)
-        }
-        else {
-            $(this).prop('checked', true)
-        }
-
-        if ($(this).closest('tr').hasClass('selected')) {
-            $(this).closest('tr').removeClass('selected')
-        }
-        else {
-            $(this).closest('tr').addClass('selected')
-        }
-    });
+    table_api = $('#trait_table').DataTable();
+
+    check_cells = table_api.column(0).nodes().to$();
+    for (let i = 0; i < check_cells.length; i++) {
+      if (check_cells[i].childNodes[0].checked){
+        check_cells[i].childNodes[0].checked = false;
+      } else {
+        check_cells[i].childNodes[0].checked = true;
+      }
+    }
+
+    check_rows = table_api.rows().nodes();
+    for (let i =0; i < check_rows.length; i++) {
+      if (check_rows[i].classList.contains("selected")){
+        check_rows[i].classList.remove("selected")
+      } else {
+        check_rows[i].classList.add("selected")
+      }
+    }
+
+    change_buttons();
   };
 
   $('#searchbox').keyup(function(){
@@ -77,22 +122,6 @@ $(function() {
       change_buttons();
   });
 
-  $('.trait_checkbox:checkbox').change(function() {
-      change_buttons()
-
-      if ($(this).is(":checked")) {
-          if (!$(this).closest('tr').hasClass('selected')) {
-              $(this).closest('tr').addClass('selected')
-          }
-      }
-      else {
-          if ($(this).closest('tr').hasClass('selected')) {
-              $(this).closest('tr').removeClass('selected')
-          }
-      }
-
-  });
-
   add_to_collection = function() {
     var traits;
     traits = $("#trait_table input:checked").map(function() {
@@ -117,25 +146,8 @@ $(function() {
   };
 
   removed_traits = function() {
-    console.log('in removed_traits with checked_traits:', checked_traits);
     return checked_traits.closest("tr").fadeOut();
   };
-  change_buttons = function() {
-    var button, buttons, item, num_checked, text, _i, _j, _k, _l, _len, _len2, _len3, _len4, _results, _results2;
-    buttons = ["#add", "#remove"];
-    num_checked = $('.trait_checkbox:checked').length;
-    if (num_checked === 0) {
-      for (_i = 0, _len = buttons.length; _i < _len; _i++) {
-        button = buttons[_i];
-        $(button).prop("disabled", true);
-      }
-    } else {
-      for (_j = 0, _len2 = buttons.length; _j < _len2; _j++) {
-        button = buttons[_j];
-        $(button).prop("disabled", false);
-      }
-    }
-  };
 
   submit_bnw = function() {
     trait_data = submit_traits_to_export_or_bnw("trait_table", "submit_bnw")
@@ -157,18 +169,23 @@ $(function() {
     });
     table_dict['headers'] = headers;
 
-    rows = [];
-    trait_table.find('tbody tr').each(function (i, tr) {
-      if (trait_table.find('input[name="searchResult"]:checked').length > 0) {
-        if ($(this).find('input[name="searchResult"]').is(':checked')){
-          rows.push($(this).find('input[name="searchResult"]:checked').val())
-        }
-      }
-      else {
-        rows.push($(this).find('input[name="searchResult"]').val())
+    selected_rows = [];
+    all_rows = []; //ZS: If no rows are checked, export all
+    table_api = $('#' + table_name).DataTable();
+    check_cells = table_api.column(0).nodes().to$();
+    for (let i = 0; i < check_cells.length; i++) {
+      this_node = check_cells[i].childNodes[0];
+      all_rows.push(this_node.value)
+      if (this_node.checked){
+        selected_rows.push(this_node.value)
       }
-    });
-    table_dict['rows'] = rows;
+    }
+
+    if (selected_rows.length > 0){
+      table_dict['rows'] = selected_rows;
+    } else {
+      table_dict['rows'] = all_rows;
+    }
 
     json_table_dict = JSON.stringify(table_dict);
     $('input[name=export_data]').val(json_table_dict);
@@ -253,8 +270,6 @@ $(function() {
   $("#add").click(add_to_collection);
   $("#submit_bnw").click(submit_bnw);
   $("#export_traits").click(export_traits);
-  $('.trait_checkbox, .btn').click(change_buttons);
-
 
   let naturalAsc = $.fn.dataTableExt.oSort["natural-ci-asc"]
   let naturalDesc = $.fn.dataTableExt.oSort["natural-ci-desc"]
diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html
index e37f8104..ccec495b 100644
--- a/wqflask/wqflask/templates/collections/view.html
+++ b/wqflask/wqflask/templates/collections/view.html
@@ -73,7 +73,7 @@
                 <form id="export_form" method="POST" action="/export_traits_csv">
                     <button class="btn btn-default" id="select_all" type="button"><span class="glyphicon glyphicon-ok"></span> Select All</button>
                     <button class="btn btn-default" id="invert" type="button"><span class="glyphicon glyphicon-ok"></span> Invert</button>
-                    <button class="btn btn-success" id="add" disabled="disabled" type="button"><i class="icon-plus-sign"></i> Copy</button>
+                    <button class="btn btn-success" id="add" type="button" disabled><i class="icon-plus-sign"></i> Copy</button>
                     <input type="hidden" name="database_name" id="database_name" value="None">
                     <input type="hidden" name="export_data" id="export_data" value="">
                     <input type="hidden" name="file_name" id="file_name" value="collection_table">
@@ -81,7 +81,7 @@
                     <input type="text" id="searchbox" class="form-control" style="width: 200px; display: inline; padding-bottom: 9px;" placeholder="Search Table For ...">
                     <input type="text" id="select_top" class="form-control" style="width: 200px; display: inline; padding-bottom: 9px;" placeholder="Select Top ...">
                     <button class="btn btn-default" id="deselect_all" type="button"><span class="glyphicon glyphicon-remove"></span> Deselect</button>
-                    <button id="remove" class="btn btn-danger" data-url="/collections/remove" disabled="disabled" type="button"><i class="icon-minus-sign"></i> Delete Rows</button>
+                    <button id="remove" class="btn btn-danger" data-url="/collections/remove" type="button" disabled><i class="icon-minus-sign"></i> Delete Rows</button>
                     <button id="delete" class="btn btn-danger submit_special" data-url="/collections/delete" title="Delete this collection" > Delete Collection</button>
                 </form>
             </div>
@@ -109,10 +109,7 @@
                     <tbody>
                     {% for this_trait in trait_obs %}
                         <TR id="trait:{{ this_trait.name }}:{{ this_trait.dataset.name }}">
-                            <TD align="center" style="padding: 0px;">
-                                <INPUT TYPE="checkbox" NAME="searchResult" class="checkbox trait_checkbox"
-                                       VALUE="{{ data_hmac('{}:{}'.format(this_trait.name, this_trait.dataset.name)) }}">
-                            </TD>
+                            <TD align="center" style="padding: 0px;"><INPUT TYPE="checkbox" NAME="searchResult" class="checkbox trait_checkbox" VALUE="{{ data_hmac('{}:{}'.format(this_trait.name, this_trait.dataset.name)) }}"></TD>
                             <TD data-export="{{ loop.index }}" align="right">{{ loop.index }}</TD>
                             <TD title="{{ this_trait.dataset.fullname }}" data-export="{{ this_trait.dataset.fullname }}">{{ this_trait.dataset.fullname }}</TD>
                             <TD data-export="{{ this_trait.name }}">
@@ -178,15 +175,21 @@
 
     <script language="javascript" type="text/javascript">
         $(document).ready( function () {
-
-            $('#trait_table tr').click(function(event) {
-                if (event.target.type !== 'checkbox') {
-                    $(':checkbox', this).trigger('click');
-                }
-            });
-
-            console.time("Creating table");
             $('#trait_table').dataTable( {
+                'drawCallback': function( settings ) {
+                      $('#trait_table tr').off().on("click", function(event) {
+                        if (event.target.type !== 'checkbox' && event.target.tagName.toLowerCase() !== 'a') {
+                          var obj =$(this).find('input');
+                          obj.prop('checked', !obj.is(':checked'));
+                        }
+                        if ($(this).hasClass("selected")){
+                          $(this).removeClass("selected")
+                        } else {
+                          $(this).addClass("selected")
+                        }
+                        change_buttons()
+                      });
+                },
                 "columns": [
                     {
                         "orderDataType": "dom-checkbox",
@@ -220,7 +223,7 @@
                 "paging": false,
                 "orderClasses": true
             } );
-            console.timeEnd("Creating table");
+
 
             submit_special = function(url) {
                 $("#collection_form").attr("action", url);
diff --git a/wqflask/wqflask/templates/correlation_page.html b/wqflask/wqflask/templates/correlation_page.html
index 6419b185..aa74abf5 100644
--- a/wqflask/wqflask/templates/correlation_page.html
+++ b/wqflask/wqflask/templates/correlation_page.html
@@ -75,6 +75,7 @@
         <div>
             <form id="export_form" method="POST" action="/export_traits_csv">
                 <button class="btn btn-default" id="select_all" type="button"><span class="glyphicon glyphicon-ok"></span> Select All</button>
+                <button class="btn btn-default" id="invert" type="button"><span class="glyphicon glyphicon-adjust"></span> Invert</button>
                 <button class="btn btn-success" id="add" type="button" disabled><span class="glyphicon glyphicon-plus-sign"></span> Add</button>
                 <input type="hidden" name="database_name" id="database_name" value="None">
                 <input type="hidden" name="export_data" id="export_data" value="">
@@ -256,41 +257,6 @@
         {% endif %}
 
         $(document).ready( function () {
-
-            $('#trait_table tr').click(function(event) {
-                if (event.target.type !== 'checkbox') {
-                    $(':checkbox', this).trigger('click');
-                }
-            });
-
-            function change_buttons() {
-                buttons = ["#add", "#remove"];
-                num_checked = $('.trait_checkbox:checked').length;
-                if (num_checked === 0) {
-                    for (_i = 0, _len = buttons.length; _i < _len; _i++) {
-                        button = buttons[_i];
-                        $(button).prop("disabled", true);
-                    }
-                } else {
-                    for (_j = 0, _len2 = buttons.length; _j < _len2; _j++) {
-                        button = buttons[_j];
-                        $(button).prop("disabled", false);
-                    }
-                }
-                if ($(this).is(":checked")) {
-                    if (!$(this).closest('tr').hasClass('selected')) {
-                        $(this).closest('tr').addClass('selected')
-                    }
-                }
-                else {
-                    if ($(this).closest('tr').hasClass('selected')) {
-                        $(this).closest('tr').removeClass('selected')
-                    }
-                }
-            }
-
-            console.time("Creating table");
-
             table_conf = {
                 buttons: [
                     {
@@ -305,6 +271,20 @@
                         postfixButtons: [ 'colvisRestore' ]
                     }
                 ],
+                'drawCallback': function( settings ) {
+                      $('#trait_table tr').off().on("click", function(event) {
+                        if (event.target.type !== 'checkbox' && event.target.tagName.toLowerCase() !== 'a') {
+                          var obj =$(this).find('input');
+                          obj.prop('checked', !obj.is(':checked'));
+                        }
+                        if ($(this).hasClass("selected")){
+                          $(this).removeClass("selected")
+                        } else {
+                          $(this).addClass("selected")
+                        }
+                      });
+                      $('.trait_checkbox:checkbox').on("change", change_buttons);
+                },
                 "data": table_json,
                 "columns": [
                     {
@@ -557,7 +537,7 @@
                 "sDom": "itir",
                 "autoWidth": true,
                 "bSortClasses": false,
-                "scrollY": "50vh",
+                "scrollY": "100vh",
                 "scroller":  true,
                 "scrollCollapse": true
             }
@@ -570,8 +550,6 @@
                 } );
             } ).draw();
 
-            console.timeEnd("Creating table");
-
             $('.toggle-vis').on('click', function (e) {
               e.preventDefault();
 
diff --git a/wqflask/wqflask/templates/environment.html b/wqflask/wqflask/templates/environment.html
index 94b31464..5fe01dad 100644
--- a/wqflask/wqflask/templates/environment.html
+++ b/wqflask/wqflask/templates/environment.html
@@ -8,33 +8,143 @@
 
 {% block content %}
 
- <div class="github-btn-container">
-    <div class="github-btn ">
-        <a href="https://github.com/genenetwork/gn-docs">
-            Edit Text
-            <img src="/static/images/edit.png">
-        </a>
-    </div>
-</div>
 <div id="markdown" class="container">
 	
    <div  class="cls-table-style">{{ rendered_markdown|safe }} </div>
 </div>
 
-<style type="text/css">
-table {
-	width: 100%;
-}
-table, th, td {
-  border: 1px solid black;
+{% if svg_data %}
+
+<div class="graph-legend">
+    <h1>Chord dependency Graph of Genenetwork2</h1>
+    Graph generated from <a href="http://git.genenetwork.org/guix-bioinformatics/guix-bioinformatics/src/branch/master/gn/packages/genenetwork.scm">genenetwork.scm</a>. You can zoom in and out within the bounding box.
+</div>
+
+<div id="guix-graph"></div>
+{% endif %}
+
+{% endblock %}
+
+{% block js %}
+
+{% if svg_data %}
+<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3js/d3.min.js') }}"></script>
+<script type="text/javascript">
+ {{ svg_data|safe }}
+ // based on http://bl.ocks.org/mbostock/1046712 under GPLv3
+ // Adapted from: https://elephly.net/graph.html
+ var outerRadius = (nodeArray.length * 10) / 2,
+     innerRadius = outerRadius - 100,
+     width = outerRadius * 2,
+     height = outerRadius * 2,
+     colors = d3.scale.category20c(),
+     matrix = [];
+
+ function neighborsOf (node) {
+     return links.filter(function (e) {
+         return e.source === node;
+     }).map(function (e) {
+         return e.target;
+     });
+ }
+
+ function zoomed () {
+     zoomer.attr("transform",
+                 "translate(" + d3.event.translate + ")" +
+                 "scale(" + d3.event.scale + ")");
+ }
+
+ function fade (opacity, root) {
+     return function (g, i) {
+         root.selectAll("g path.chord")
+             .filter(function (d) {
+                 return d.source.index != i && d.target.index != i;
+             })
+             .transition()
+             .style("opacity", opacity);
+     };
+ }
+
+ // Now that we have all nodes in an object we can replace each reference
+ // with the actual node object.
+ links.forEach(function (link) {
+     link.target = nodes[link.target];
+     link.source = nodes[link.source];
+ });
+
+ // Construct a square matrix for package dependencies
+ nodeArray.forEach(function (d, index, arr) {
+     var source = index,
+         row = matrix[source];
+     if (!row) {
+         row = matrix[source] = [];
+         for (var i = -1; ++i < arr.length;) row[i] = 0;
+     }
+     neighborsOf(d).forEach(function (d) { row[d.index]++; });
+ });
+
+ // chord layout
+ var chord = d3.layout.chord()
+               .padding(0.01)
+               .sortSubgroups(d3.descending)
+               .sortChords(d3.descending)
+               .matrix(matrix);
+
+ var arc = d3.svg.arc()
+             .innerRadius(innerRadius)
+             .outerRadius(innerRadius + 20);
+
+ var zoom = d3.behavior.zoom()
+              .scaleExtent([0.1, 10])
+              .on("zoom", zoomed);
+
+ var svg = d3.select("#guix-graph").append("svg")
+             .attr("width", "100%")
+             .attr("height", "100%")
+             .attr('viewBox','0 0 '+Math.min(width,height)+' '+Math.min(width,height))
+             .attr('preserveAspectRatio','xMinYMin')
+             .call(zoom);
+
+ var zoomer = svg.append("g");
+
+ var container = zoomer.append("g")
+                       .attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");
+
+ // Group for arcs and labels
+ var g = container.selectAll(".group")
+                  .data(chord.groups)
+                  .enter().append("g")
+                  .attr("class", "group")
+                  .on("mouseout", fade(1, container))
+                  .on("mouseover", fade(0.1, container));
+
+ // Draw one segment per package
+ g.append("path")
+  .style("fill",   function (d) { return colors(d.index); })
+  .style("stroke", function (d) { return colors(d.index); })
+  .attr("d", arc);
 
-}
+ // Add circular labels
+ g.append("text")
+  .each(function (d) { d.angle = (d.startAngle + d.endAngle) / 2; })
+  .attr("dy", ".35em")
+  .attr("transform", function (d) {
+      return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+           + "translate(" + (innerRadius + 26) + ")"
+           + (d.angle > Math.PI ? "rotate(180)" : "");
+  })
+  .style("text-anchor", function (d) { return d.angle > Math.PI ? "end" : null; })
+  .text(function (d) { return nodeArray[d.index].label; });
 
-td,th{
-	padding-top:8px;
-	padding-bottom: 8px;
-	text-align: center;
-}
+ // Draw chords from source to target; color by source.
+ container.selectAll(".chord")
+          .data(chord.chords)
+          .enter().append("path")
+          .attr("class", "chord")
+          .style("stroke", function (d) { return d3.rgb(colors(d.source.index)).darker(); })
+          .style("fill", function (d) { return colors(d.source.index); })
+          .attr("d", d3.svg.chord().radius(innerRadius));
+</script>
+{% endif %}
 
-</style>
 {% endblock %}
diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html
index 2a8d6931..87c97c50 100644
--- a/wqflask/wqflask/templates/search_result_page.html
+++ b/wqflask/wqflask/templates/search_result_page.html
@@ -129,6 +129,7 @@
               {% endif %}
               <input type="hidden" name="export_data" id="export_data" value="">
               <button class="btn btn-default" id="select_all" type="button"><span class="glyphicon glyphicon-ok"></span> Select</button>
+              <button class="btn btn-default" id="invert" type="button"><span class="glyphicon glyphicon-adjust"></span> Invert</button>
               <button class="btn btn-success" id="add" type="button" disabled><span class="glyphicon glyphicon-plus-sign"></span> Add</button>
               <button class="btn btn-default" id="export_traits">Download <span class="glyphicon glyphicon-download"></span></button>
               <input type="text" id="searchbox" class="form-control" style="width: 200px; display: inline;" placeholder="Search This Table For ...">
@@ -203,42 +204,21 @@
                 }
             });
 
-            function change_buttons() {
-                buttons = ["#add", "#remove"];
-                num_checked = $('.trait_checkbox:checked').length;
-                if (num_checked === 0) {
-                    for (_i = 0, _len = buttons.length; _i < _len; _i++) {
-                        button = buttons[_i];
-                        $(button).prop("disabled", true);
-                    }
-                } else {
-                    for (_j = 0, _len2 = buttons.length; _j < _len2; _j++) {
-                        button = buttons[_j];
-                        $(button).prop("disabled", false);
-                    }
-                }
-                         //});
-                if ($(this).is(":checked")) {
-                    if (!$(this).closest('tr').hasClass('selected')) {
-                        $(this).closest('tr').addClass('selected')
-                    }
-                }
-                else {
-                    if ($(this).closest('tr').hasClass('selected')) {
-                        $(this).closest('tr').removeClass('selected')
-                    }
-                }
-            }
-
             //ZS: Need to make sort by symbol, also need to make sure blank symbol fields at the bottom and symbols starting with numbers below letters
             trait_table = $('#trait_table').DataTable( {
                 'drawCallback': function( settings ) {
-                     $('#trait_table tr').click(function(event) {
-                         if (event.target.type !== 'checkbox' && event.target.tagName.toLowerCase() !== 'a') {
-                             $(':checkbox', this).trigger('click');
-                         }
-                     });
-                     $('.trait_checkbox:checkbox').on("change", change_buttons);
+                      $('#trait_table tr').off().on("click", function(event) {
+                        if (event.target.type !== 'checkbox' && event.target.tagName.toLowerCase() !== 'a') {
+                          var obj =$(this).find('input');
+                          obj.prop('checked', !obj.is(':checked'));
+                        }
+                        if ($(this).hasClass("selected")){
+                          $(this).removeClass("selected")
+                        } else {
+                          $(this).addClass("selected")
+                        }
+                        change_buttons()
+                      });
                 },
                 'createdRow': function ( row, data, index ) {
                     $('td', row).eq(0).attr("style", "text-align: center; padding: 0px 10px 2px 10px;");