diff options
Diffstat (limited to 'gn2')
522 files changed, 72389 insertions, 0 deletions
diff --git a/gn2/.coveragerc b/gn2/.coveragerc new file mode 100644 index 00000000..939e51b9 --- /dev/null +++ b/gn2/.coveragerc @@ -0,0 +1,28 @@ +[run] +branch = True + +[report] +omit = + */site-packages/* + tests/* +# Regexes for lines to exclude from consideration +exclude_lines = +# Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + +ignore_errors = False + +[html] +directory = coverage_html_report
\ No newline at end of file diff --git a/gn2/__init__.py b/gn2/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/__init__.py diff --git a/gn2/base/GeneralObject.py b/gn2/base/GeneralObject.py new file mode 100644 index 00000000..ce8e60b8 --- /dev/null +++ b/gn2/base/GeneralObject.py @@ -0,0 +1,66 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010) +# at rwilliams@uthsc.edu and xzhou15@uthsc.edu +# +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) +# +# Created by GeneNetwork Core Team 2010/08/10 +# +# Last updated by GeneNetwork Core Team 2010/10/20 + +class GeneralObject: + """ + Base class to define an Object. + a = [Spam(1, 4), Spam(9, 3), Spam(4,6)] + a.sort(key = lambda x: x.eggs) + """ + + def __init__(self, *args, **kw): + self.contents = list(args) + for name, value in list(kw.items()): + setattr(self, name, value) + + def __setitem__(self, key, value): + setattr(self, key, value) + + def __getitem__(self, key): + return getattr(self, key) + + def __getattr__(self, key): + return eval("self.__dict__.%s" % key) + + def __len__(self): + return len(self.__dict__) - 1 + + def __str__(self): + s = '' + for key in list(self.__dict__.keys()): + if key != 'contents': + s += '%s = %s\n' % (key, self.__dict__[key]) + return s + + def __repr__(self): + s = '' + for key in list(self.__dict__.keys()): + s += '%s = %s\n' % (key, self.__dict__[key]) + return s + + def __eq__(self, other): + return (len(list(self.__dict__.keys())) + == len(list(other.__dict__.keys()))) diff --git a/gn2/base/__init__.py b/gn2/base/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/base/__init__.py diff --git a/gn2/base/data_set/__init__.py b/gn2/base/data_set/__init__.py new file mode 100644 index 00000000..40ef1c75 --- /dev/null +++ b/gn2/base/data_set/__init__.py @@ -0,0 +1,124 @@ +"The data_set package ..." + +# builtins imports +import json +import pickle as pickle + +# 3rd-party imports +from redis import Redis + +# local imports +from .dataset import DataSet +from gn2.base import webqtlConfig +from gn2.utility.tools import get_setting, USE_REDIS +from .datasettype import DatasetType +from .tempdataset import TempDataSet +from .datasetgroup import DatasetGroup +from .utils import query_table_timestamp +from .genotypedataset import GenotypeDataSet +from .phenotypedataset import PhenotypeDataSet +from .mrnaassaydataset import MrnaAssayDataSet +from gn2.wqflask.database import database_connection + +# Used by create_database to instantiate objects +# Each subclass will add to this + +DS_NAME_MAP = { + "Temp": "TempDataSet", + "Geno": "GenotypeDataSet", + "Publish": "PhenotypeDataSet", + "ProbeSet": "MrnaAssayDataSet" +} + +def __dataset_type__(dataset_name): + """Get dataset type.""" + if "Temp" in dataset_name: + return "Temp" + if "Geno" in dataset_name: + return "Geno" + if "Publish" in dataset_name: + return "Publish" + return "ProbeSet" + +def create_dataset(dataset_name, dataset_type=None, + get_samplelist=True, group_name=None, redis_conn=Redis()): + dataset_type = dataset_type or __dataset_type__(dataset_name) + + dataset_ob = DS_NAME_MAP[dataset_type] + dataset_class = globals()[dataset_ob] + if dataset_type == "Temp": + return dataset_class(dataset_name, get_samplelist, group_name) + else: + return dataset_class(dataset_name, get_samplelist) + +def datasets(group_name, this_group=None, redis_conn=Redis()): + key = "group_dataset_menu:v2:" + group_name + dataset_menu = [] + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute(''' + (SELECT '#PublishFreeze',PublishFreeze.FullName,PublishFreeze.Name + FROM PublishFreeze,InbredSet + WHERE PublishFreeze.InbredSetId = InbredSet.Id + and InbredSet.Name = '%s' + ORDER BY PublishFreeze.Id ASC) + UNION + (SELECT '#GenoFreeze',GenoFreeze.FullName,GenoFreeze.Name + FROM GenoFreeze, InbredSet + WHERE GenoFreeze.InbredSetId = InbredSet.Id + and InbredSet.Name = '%s') + UNION + (SELECT Tissue.Name, ProbeSetFreeze.FullName,ProbeSetFreeze.Name + FROM ProbeSetFreeze, ProbeFreeze, InbredSet, Tissue + WHERE ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id + and ProbeFreeze.TissueId = Tissue.Id + and ProbeFreeze.InbredSetId = InbredSet.Id + and InbredSet.Name like %s + ORDER BY Tissue.Name, ProbeSetFreeze.OrderList DESC) + ''' % (group_name, + group_name, + "'" + group_name + "'")) + the_results = cursor.fetchall() + + sorted_results = sorted(the_results, key=lambda kv: kv[0]) + + # ZS: This is kind of awkward, but need to ensure Phenotypes show up before Genotypes in dropdown + pheno_inserted = False + geno_inserted = False + for dataset_item in sorted_results: + tissue_name = dataset_item[0] + dataset = dataset_item[1] + dataset_short = dataset_item[2] + if tissue_name in ['#PublishFreeze', '#GenoFreeze']: + if tissue_name == '#PublishFreeze' and (dataset_short == group_name + 'Publish'): + dataset_menu.insert( + 0, dict(tissue=None, datasets=[(dataset, dataset_short)])) + pheno_inserted = True + elif pheno_inserted and tissue_name == '#GenoFreeze': + dataset_menu.insert( + 1, dict(tissue=None, datasets=[(dataset, dataset_short)])) + geno_inserted = True + else: + dataset_menu.append( + dict(tissue=None, datasets=[(dataset, dataset_short)])) + else: + tissue_already_exists = False + for i, tissue_dict in enumerate(dataset_menu): + if tissue_dict['tissue'] == tissue_name: + tissue_already_exists = True + break + + if tissue_already_exists: + dataset_menu[i]['datasets'].append((dataset, dataset_short)) + else: + dataset_menu.append(dict(tissue=tissue_name, + datasets=[(dataset, dataset_short)])) + + if USE_REDIS: + redis_conn.set(key, pickle.dumps(dataset_menu, pickle.HIGHEST_PROTOCOL)) + redis_conn.expire(key, 60 * 5) + + if this_group != None: + this_group._datasets = dataset_menu + return this_group._datasets + else: + return dataset_menu diff --git a/gn2/base/data_set/dataset.py b/gn2/base/data_set/dataset.py new file mode 100644 index 00000000..3a62fbde --- /dev/null +++ b/gn2/base/data_set/dataset.py @@ -0,0 +1,305 @@ +"Base Dataset class ..." + +import math +import collections + +from redis import Redis + +from gn2.base import species +from gn2.utility import chunks +from gn2.utility.tools import get_setting +from gn3.monads import MonadicDict, query_sql +from pymonad.maybe import Maybe, Nothing +from .datasetgroup import DatasetGroup +from gn2.wqflask.database import database_connection +from gn2.utility.db_tools import escape, mescape, create_in_clause +from .utils import fetch_cached_results, cache_dataset_results + + +class DataSet: + """ + DataSet class defines a dataset in webqtl, can be either Microarray, + Published phenotype, genotype, or user input dataset(temp) + + """ + + def __init__(self, name, get_samplelist=True, group_name=None, redis_conn=Redis()): + + assert name, "Need a name" + self.name = name + self.id = None + self.shortname = None + self.fullname = None + self.type = None + self.data_scale = None # ZS: For example log2 + self.accession_id = Nothing + + self.setup() + + if self.type == "Temp": # Need to supply group name as input if temp trait + # sets self.group and self.group_id and gets genotype + self.group = DatasetGroup(self, name=group_name) + else: + self.check_confidentiality() + self.retrieve_other_names() + # sets self.group and self.group_id and gets genotype + self.group = DatasetGroup(self) + self.accession_id = self.get_accession_id() + if get_samplelist == True: + self.group.get_samplelist(redis_conn) + self.species = species.TheSpecies(dataset=self) + + def as_monadic_dict(self): + _result = MonadicDict({ + 'name': self.name, + 'shortname': self.shortname, + 'fullname': self.fullname, + 'type': self.type, + 'data_scale': self.data_scale, + 'group': self.group.name + }) + _result["accession_id"] = self.accession_id + return _result + + def get_accession_id(self) -> Maybe[str]: + """Get the accession_id of this dataset depending on the + dataset type.""" + __query = "" + with database_connection(get_setting("SQL_URI")) as conn: + if self.type == "Publish": + __query = ( + "SELECT InfoFiles.GN_AccesionId AS accession_id FROM " + "InfoFiles, PublishFreeze, InbredSet " + "WHERE InbredSet.Name = " + f"'{conn.escape_string(self.group.name).decode()}' " + "AND PublishFreeze.InbredSetId = InbredSet.Id " + "AND InfoFiles.InfoPageName = PublishFreeze.Name " + "AND PublishFreeze.public > 0 AND " + "PublishFreeze.confidentiality < 1 " + "ORDER BY PublishFreeze.CreateTime DESC" + ) + elif self.type == "Geno": + __query = ( + "SELECT InfoFiles.GN_AccesionId AS accession_id FROM " + "InfoFiles, GenoFreeze, InbredSet WHERE InbredSet.Name = " + f"'{conn.escape_string(self.group.name).decode()}' AND " + "GenoFreeze.InbredSetId = InbredSet.Id " + "AND InfoFiles.InfoPageName = GenoFreeze.ShortName " + "AND GenoFreeze.public > 0 AND " + "GenoFreeze.confidentiality < 1 " + "ORDER BY GenoFreeze.CreateTime DESC" + ) + elif self.type == "ProbeSet": + __query = ( + "SELECT InfoFiles.GN_AccesionId AS accession_id " + "FROM InfoFiles WHERE InfoFiles.InfoPageName = " + f"'{conn.escape_string(self.name).decode()}'" + ) + else: # The Value passed is not present + raise LookupError + + # Should there be an empty row, query_sql returns a None + # value instead of yielding a value; this block + # accomodates this non-intuitive edge-case + for result in query_sql(conn, __query) or (): + return result["accession_id"] + return Nothing + + def retrieve_other_names(self): + """This method fetches the the dataset names in search_result. + + If the data set name parameter is not found in the 'Name' field of + the data set table, check if it is actually the FullName or + ShortName instead. + + This is not meant to retrieve the data set info if no name at + all is passed. + + """ + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + try: + if self.type == "ProbeSet": + cursor.execute( + "SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, " + "ProbeSetFreeze.FullName, ProbeSetFreeze.ShortName, " + "ProbeSetFreeze.DataScale, Tissue.Name " + "FROM ProbeSetFreeze, ProbeFreeze, Tissue " + "WHERE ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "AND ProbeFreeze.TissueId = Tissue.Id " + "AND (ProbeSetFreeze.Name = %s OR " + "ProbeSetFreeze.FullName = %s " + "OR ProbeSetFreeze.ShortName = %s)", + (self.name,)*3) + (self.id, self.name, self.fullname, self.shortname, + self.data_scale, self.tissue) = cursor.fetchone() + else: + self.tissue = "N/A" + cursor.execute( + "SELECT Id, Name, FullName, ShortName " + f"FROM {self.type}Freeze " + "WHERE (Name = %s OR FullName = " + "%s OR ShortName = %s)", + (self.name,)*3) + (self.id, self.name, self.fullname, + self.shortname) = cursor.fetchone() + except TypeError: + pass + + def chunk_dataset(self, dataset, n): + + results = {} + traits_name_dict = () + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT ProbeSetXRef.DataId,ProbeSet.Name " + "FROM ProbeSet, ProbeSetXRef, ProbeSetFreeze " + "WHERE ProbeSetFreeze.Name = %s AND " + "ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id " + "AND ProbeSetXRef.ProbeSetId = ProbeSet.Id", + (self.name,)) + # should cache this + traits_name_dict = dict(cursor.fetchall()) + + for i in range(0, len(dataset), n): + matrix = list(dataset[i:i + n]) + trait_name = traits_name_dict[matrix[0][0]] + + my_values = [value for (trait_name, strain, value) in matrix] + results[trait_name] = my_values + return results + + def get_probeset_data(self, sample_list=None, trait_ids=None): + + # improvement of get trait data--->>> + if sample_list: + self.samplelist = sample_list + + else: + self.samplelist = self.group.samplelist + + if self.group.parlist != None and self.group.f1list != None: + if (self.group.parlist + self.group.f1list) in self.samplelist: + self.samplelist += self.group.parlist + self.group.f1list + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT Strain.Name, Strain.Id FROM " + "Strain, Species WHERE Strain.Name IN " + f"{create_in_clause(self.samplelist)} " + "AND Strain.SpeciesId=Species.Id AND " + "Species.name = %s", (self.group.species,) + ) + results = dict(cursor.fetchall()) + sample_ids = [results[item] for item in self.samplelist] + + sorted_samplelist = [strain_name for strain_name, strain_id in sorted( + results.items(), key=lambda item: item[1])] + + cursor.execute( + "SELECT * from ProbeSetData WHERE StrainID IN " + f"{create_in_clause(sample_ids)} AND id IN " + "(SELECT ProbeSetXRef.DataId FROM " + "(ProbeSet, ProbeSetXRef, ProbeSetFreeze) " + "WHERE ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id " + "AND ProbeSetFreeze.Name = %s AND " + "ProbeSet.Id = ProbeSetXRef.ProbeSetId)", + (self.name,) + ) + + query_results = list(cursor.fetchall()) + data_results = self.chunk_dataset(query_results, len(sample_ids)) + self.samplelist = sorted_samplelist + self.trait_data = data_results + + def get_trait_data(self, sample_list=None): + if sample_list: + self.samplelist = sample_list + else: + self.samplelist = self.group.samplelist + + if self.group.parlist != None and self.group.f1list != None: + if (self.group.parlist + self.group.f1list) in self.samplelist: + self.samplelist += self.group.parlist + self.group.f1list + + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT Strain.Name, Strain.Id FROM Strain, Species " + f"WHERE Strain.Name IN {create_in_clause(self.samplelist)} " + "AND Strain.SpeciesId=Species.Id " + "AND Species.name = %s", + (self.group.species,) + ) + results = dict(cursor.fetchall()) + sample_ids = [ + sample_id for sample_id in + (results.get(item) for item in self.samplelist + if item is not None) + if sample_id is not None + ] + + # MySQL limits the number of tables that can be used in a join to 61, + # so we break the sample ids into smaller chunks + # Postgres doesn't have that limit, so we can get rid of this after we transition + chunk_size = 50 + number_chunks = int(math.ceil(len(sample_ids) / chunk_size)) + + cached_results = fetch_cached_results(self.name, self.type, self.samplelist) + + if cached_results is None: + trait_sample_data = [] + for sample_ids_step in chunks.divide_into_chunks(sample_ids, number_chunks): + if self.type == "Publish": + dataset_type = "Phenotype" + else: + dataset_type = self.type + temp = ['T%s.value' % item for item in sample_ids_step] + if self.type == "Publish": + query = "SELECT {}XRef.Id".format(escape(self.type)) + else: + query = "SELECT {}.Name".format(escape(dataset_type)) + data_start_pos = 1 + if len(temp) > 0: + query = query + ", " + ', '.join(temp) + query += ' FROM ({}, {}XRef, {}Freeze) '.format(*mescape(dataset_type, + self.type, + self.type)) + + for item in sample_ids_step: + query += """ + left join {}Data as T{} on T{}.Id = {}XRef.DataId + and T{}.StrainId={}\n + """.format(*mescape(self.type, item, item, self.type, item, item)) + + if self.type == "Publish": + query += """ + WHERE {}XRef.InbredSetId = {}Freeze.InbredSetId + and {}Freeze.Name = '{}' + and {}.Id = {}XRef.{}Id + order by {}.Id + """.format(*mescape(self.type, self.type, self.type, self.name, + dataset_type, self.type, dataset_type, dataset_type)) + else: + query += """ + WHERE {}XRef.{}FreezeId = {}Freeze.Id + and {}Freeze.Name = '{}' + and {}.Id = {}XRef.{}Id + order by {}.Id + """.format(*mescape(self.type, self.type, self.type, self.type, + self.name, dataset_type, self.type, self.type, dataset_type)) + cursor.execute(query) + results = cursor.fetchall() + trait_sample_data.append([list(result) for result in results]) + + trait_count = len(trait_sample_data[0]) + self.trait_data = collections.defaultdict(list) + + data_start_pos = 1 + for trait_counter in range(trait_count): + trait_name = trait_sample_data[0][trait_counter][0] + for chunk_counter in range(int(number_chunks)): + self.trait_data[trait_name] += ( + trait_sample_data[chunk_counter][trait_counter][data_start_pos:]) + + cache_dataset_results( + self.name, self.type, self.samplelist, self.trait_data) + else: + self.trait_data = cached_results diff --git a/gn2/base/data_set/datasetgroup.py b/gn2/base/data_set/datasetgroup.py new file mode 100644 index 00000000..d124283f --- /dev/null +++ b/gn2/base/data_set/datasetgroup.py @@ -0,0 +1,195 @@ +"Dataset Group class ..." + +import os +import json + + +from gn2.base import webqtlConfig +from .markers import Markers, HumanMarkers +from gn2.utility import webqtlUtil +from gn2.utility import gen_geno_ob +from gn2.db import webqtlDatabaseFunction +from gn2.maintenance import get_group_samplelists +from gn2.wqflask.database import database_connection +from gn2.utility.tools import ( + locate, + USE_REDIS, + flat_files, + get_setting, + flat_file_exists, + locate_ignore_error) + +class DatasetGroup: + """ + Each group has multiple datasets; each species has multiple groups. + + For example, Mouse has multiple groups (BXD, BXA, etc), and each group + has multiple datasets associated with it. + + """ + + def __init__(self, dataset, name=None): + """This sets self.group and self.group_id""" + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + if not name: + cursor.execute(dataset.query_for_group, + (dataset.name,)) + else: + cursor.execute( + "SELECT InbredSet.Name, " + "InbredSet.Id, " + "InbredSet.GeneticType, " + "InbredSet.InbredSetCode " + "FROM InbredSet WHERE Name = %s", + (name,)) + results = cursor.fetchone() + if results: + (self.name, self.id, self.genetic_type, self.code) = results + else: + self.name = name or dataset.name + if self.name == 'BXD300': + self.name = "BXD" + + self.f1list = None + self.parlist = None + self.get_f1_parent_strains() + + self.mapping_id, self.mapping_names = self.get_mapping_methods() + + self.species = webqtlDatabaseFunction.retrieve_species(self.name) + + self.incparentsf1 = False + self.allsamples = None + self._datasets = None + self.genofile = None + + def get_mapping_methods(self): + mapping_id = () + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT MappingMethodId FROM " + "InbredSet WHERE Name= %s", + (self.name,)) + results = cursor.fetchone() + if results and results[0]: + mapping_id = results[0] + if mapping_id == "1": + mapping_names = ["GEMMA", "QTLReaper", "R/qtl"] + elif mapping_id == "2": + mapping_names = ["GEMMA"] + elif mapping_id == "3": + mapping_names = ["R/qtl"] + elif mapping_id == "4": + mapping_names = ["GEMMA", "PLINK"] + else: + mapping_names = [] + + return mapping_id, mapping_names + + def get_markers(self): + def check_plink_gemma(): + if flat_file_exists("mapping"): + MAPPING_PATH = flat_files("mapping") + "/" + if os.path.isfile(MAPPING_PATH + self.name + ".bed"): + return True + return False + + if check_plink_gemma(): + marker_class = HumanMarkers + else: + marker_class = Markers + + if self.genofile: + self.markers = marker_class(self.genofile[:-5]) + else: + self.markers = marker_class(self.name) + + def get_f1_parent_strains(self): + try: + # NL, 07/27/2010. ParInfo has been moved from webqtlForm.py to webqtlUtil.py; + f1, f12, maternal, paternal = webqtlUtil.ParInfo[self.name] + except KeyError: + f1 = f12 = maternal = paternal = None + + if f1 and f12: + self.f1list = [f1, f12] + if maternal and paternal: + self.parlist = [maternal, paternal] + + def get_study_samplelists(self): + study_sample_file = locate_ignore_error( + self.name + ".json", 'study_sample_lists') + try: + f = open(study_sample_file) + except: + return [] + study_samples = json.load(f) + return study_samples + + def get_genofiles(self): + jsonfile = "%s/%s.json" % (webqtlConfig.GENODIR, self.name) + try: + f = open(jsonfile) + except: + return None + jsondata = json.load(f) + return jsondata['genofile'] + + def get_samplelist(self, redis_conn): + result = None + key = "samplelist:v3:" + self.name + if USE_REDIS: + result = redis_conn.get(key) + + if result is not None: + self.samplelist = json.loads(result) + else: + genotype_fn = locate_ignore_error(self.name + ".geno", 'genotype') + if genotype_fn: + self.samplelist = get_group_samplelists.get_samplelist( + "geno", genotype_fn) + else: + self.samplelist = None + + if USE_REDIS: + redis_conn.set(key, json.dumps(self.samplelist)) + redis_conn.expire(key, 60 * 5) + + def all_samples_ordered(self): + result = [] + lists = (self.parlist, self.f1list, self.samplelist) + [result.extend(l) for l in lists if l] + return result + + def read_genotype_file(self, use_reaper=False): + '''Read genotype from .geno file instead of database''' + # genotype_1 is Dataset Object without parents and f1 + # genotype_2 is Dataset Object with parents and f1 (not for intercross) + + # reaper barfs on unicode filenames, so here we ensure it's a string + if self.genofile: + if "RData" in self.genofile: # ZS: This is a temporary fix; I need to change the way the JSON files that point to multiple genotype files are structured to point to other file types like RData + full_filename = str( + locate(self.genofile.split(".")[0] + ".geno", 'genotype')) + else: + full_filename = str(locate(self.genofile, 'genotype')) + else: + full_filename = str(locate(self.name + '.geno', 'genotype')) + genotype_1 = gen_geno_ob.genotype(full_filename) + + if genotype_1.type == "group" and self.parlist: + genotype_2 = genotype_1.add( + Mat=self.parlist[0], Pat=self.parlist[1]) # , F1=_f1) + else: + genotype_2 = genotype_1 + + # determine default genotype object + if self.incparentsf1 and genotype_1.type != "intercross": + genotype = genotype_2 + else: + self.incparentsf1 = 0 + genotype = genotype_1 + + self.samplelist = list(genotype.prgy) + + return genotype diff --git a/gn2/base/data_set/datasettype.py b/gn2/base/data_set/datasettype.py new file mode 100644 index 00000000..52d41b41 --- /dev/null +++ b/gn2/base/data_set/datasettype.py @@ -0,0 +1,117 @@ +"DatasetType class ..." + +import json +import requests +from typing import Optional, Dict + + +from redis import Redis + + +from gn2.utility.tools import GN2_BASE_URL +from gn2.wqflask.database import database_connection + + +class DatasetType: + """Create a dictionary of samples where the value is set to Geno, + Publish or ProbeSet. E.g. + + {'AD-cases-controls-MyersGeno': 'Geno', + 'AD-cases-controls-MyersPublish': 'Publish', + 'AKXDGeno': 'Geno', + 'AXBXAGeno': 'Geno', + 'AXBXAPublish': 'Publish', + 'Aging-Brain-UCIPublish': 'Publish', + 'All Phenotypes': 'Publish', + 'B139_K_1206_M': 'ProbeSet', + 'B139_K_1206_R': 'ProbeSet' ... + } + """ + + def __init__(self, redis_conn): + "Initialise the object" + self.datasets = {} + self.data = {} + # self.redis_instance = redis_instance + data = redis_conn.get("dataset_structure") + if data: + self.datasets = json.loads(data) + else: + # ZS: I don't think this should ever run unless Redis is + # emptied + try: + data = json.loads(requests.get( + GN2_BASE_URL + "/api/v_pre1/gen_dropdown", + timeout=5).content) + for _species in data['datasets']: + for group in data['datasets'][_species]: + for dataset_type in data['datasets'][_species][group]: + for dataset in data['datasets'][_species][group][dataset_type]: + short_dataset_name = dataset[1] + if dataset_type == "Phenotypes": + new_type = "Publish" + elif dataset_type == "Genotypes": + new_type = "Geno" + else: + new_type = "ProbeSet" + self.datasets[short_dataset_name] = new_type + except Exception: # Do nothing + pass + + redis_conn.set("dataset_structure", json.dumps(self.datasets)) + self.data = data + + def set_dataset_key(self, t, name, redis_conn, db_cursor): + """If name is not in the object's dataset dictionary, set it, and + update dataset_structure in Redis + args: + t: Type of dataset structure which can be: 'mrna_expr', 'pheno', + 'other_pheno', 'geno' + name: The name of the key to inserted in the datasets dictionary + + """ + sql_query_mapping = { + 'mrna_expr': ("SELECT ProbeSetFreeze.Id FROM " + "ProbeSetFreeze WHERE " + "ProbeSetFreeze.Name = %s "), + 'pheno': ("SELECT InfoFiles.GN_AccesionId " + "FROM InfoFiles, PublishFreeze, InbredSet " + "WHERE InbredSet.Name = %s AND " + "PublishFreeze.InbredSetId = InbredSet.Id AND " + "InfoFiles.InfoPageName = PublishFreeze.Name"), + 'other_pheno': ("SELECT PublishFreeze.Name " + "FROM PublishFreeze, InbredSet " + "WHERE InbredSet.Name = %s AND " + "PublishFreeze.InbredSetId = InbredSet.Id"), + 'geno': ("SELECT GenoFreeze.Id FROM GenoFreeze WHERE " + "GenoFreeze.Name = %s ") + } + + dataset_name_mapping = { + "mrna_expr": "ProbeSet", + "pheno": "Publish", + "other_pheno": "Publish", + "geno": "Geno", + } + + group_name = name + if t in ['pheno', 'other_pheno']: + group_name = name.replace("Publish", "") + + db_cursor.execute(sql_query_mapping[t], (group_name,)) + if db_cursor.fetchone(): + self.datasets[name] = dataset_name_mapping[t] + redis_conn.set( + "dataset_structure", json.dumps(self.datasets)) + return True + + + def __call__(self, name, redis_conn, db_cursor): + if name not in self.datasets: + for t in ["mrna_expr", "pheno", "other_pheno", "geno"]: + # This has side-effects, with the end result being a + # truth-y value + if(self.set_dataset_key(t, name, redis_conn, db_cursor)): + break + # Return None if name has not been set + return self.datasets.get(name, None) diff --git a/gn2/base/data_set/genotypedataset.py b/gn2/base/data_set/genotypedataset.py new file mode 100644 index 00000000..77af1dad --- /dev/null +++ b/gn2/base/data_set/genotypedataset.py @@ -0,0 +1,76 @@ +"GenotypeDataSet class ..." + +from .dataset import DataSet +from gn2.utility import webqtlUtil +from gn2.utility.tools import get_setting +from gn2.db import webqtlDatabaseFunction +from .utils import geno_mrna_confidentiality +from gn2.wqflask.database import database_connection + +class GenotypeDataSet(DataSet): + + def setup(self): + # Fields in the database table + self.search_fields = ['Name', + 'Chr'] + + # Find out what display_fields is + self.display_fields = ['name', + 'chr', + 'mb', + 'source2', + 'sequence'] + + # Fields displayed in the search results table header + self.header_fields = ['Index', + 'ID', + 'Location'] + + # Todo: Obsolete or rename this field + self.type = 'Geno' + self.query_for_group = """ +SELECT InbredSet.Name, InbredSet.Id, InbredSet.GeneticType, InbredSet.InbredSetCode +FROM InbredSet, GenoFreeze WHERE GenoFreeze.InbredSetId = InbredSet.Id AND +GenoFreeze.Name = %s""" + + def check_confidentiality(self): + return geno_mrna_confidentiality(self) + + def get_trait_info(self, trait_list, species=None): + for this_trait in trait_list: + if not this_trait.haveinfo: + this_trait.retrieveInfo() + + if this_trait.chr and this_trait.mb: + this_trait.location_repr = 'Chr%s: %.6f' % ( + this_trait.chr, float(this_trait.mb)) + + def retrieve_sample_data(self, trait): + results = [] + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT Strain.Name, GenoData.value, " + "GenoSE.error, 'N/A', Strain.Name2 " + "FROM (GenoData, GenoFreeze, Strain, Geno, " + "GenoXRef) LEFT JOIN GenoSE ON " + "(GenoSE.DataId = GenoData.Id AND " + "GenoSE.StrainId = GenoData.StrainId) " + "WHERE Geno.SpeciesId = %s AND " + "Geno.Name = %s AND GenoXRef.GenoId = Geno.Id " + "AND GenoXRef.GenoFreezeId = GenoFreeze.Id " + "AND GenoFreeze.Name = %s AND " + "GenoXRef.DataId = GenoData.Id " + "AND GenoData.StrainId = Strain.Id " + "ORDER BY Strain.Name", + (webqtlDatabaseFunction.retrieve_species_id(self.group.name), + trait, self.name,)) + results = list(cursor.fetchall()) + + if self.group.name in webqtlUtil.ParInfo: + f1_1, f1_2, ref, nonref = webqtlUtil.ParInfo[self.group.name] + results.append([f1_1, 0, None, "N/A", f1_1]) + results.append([f1_2, 0, None, "N/A", f1_2]) + results.append([ref, -1, None, "N/A", ref]) + results.append([nonref, 1, None, "N/A", nonref]) + + return results diff --git a/gn2/base/data_set/markers.py b/gn2/base/data_set/markers.py new file mode 100644 index 00000000..68503577 --- /dev/null +++ b/gn2/base/data_set/markers.py @@ -0,0 +1,96 @@ +"Base Class: Markers - " + +import math + +from gn2.utility.tools import locate, flat_files + +class Markers: + """Todo: Build in cacheing so it saves us reading the same file more than once""" + + def __init__(self, name): + json_data_fh = open(locate(name + ".json", 'genotype/json')) + + markers = [] + with open("%s/%s_snps.txt" % (flat_files('genotype/bimbam'), name), 'r') as bimbam_fh: + if len(bimbam_fh.readline().split(", ")) > 2: + delimiter = ", " + elif len(bimbam_fh.readline().split(",")) > 2: + delimiter = "," + elif len(bimbam_fh.readline().split("\t")) > 2: + delimiter = "\t" + else: + delimiter = " " + for line in bimbam_fh: + marker = {} + marker['name'] = line.split(delimiter)[0].rstrip() + marker['Mb'] = float(line.split(delimiter)[ + 1].rstrip()) / 1000000 + marker['chr'] = line.split(delimiter)[2].rstrip() + markers.append(marker) + + for marker in markers: + if (marker['chr'] != "X") and (marker['chr'] != "Y") and (marker['chr'] != "M"): + marker['chr'] = int(marker['chr']) + marker['Mb'] = float(marker['Mb']) + + self.markers = markers + + def add_pvalues(self, p_values): + if isinstance(p_values, list): + # THIS IS only needed for the case when we are limiting the number of p-values calculated + # if len(self.markers) > len(p_values): + # self.markers = self.markers[:len(p_values)] + + for marker, p_value in zip(self.markers, p_values): + if not p_value: + continue + marker['p_value'] = float(p_value) + if math.isnan(marker['p_value']) or marker['p_value'] <= 0: + marker['lod_score'] = 0 + marker['lrs_value'] = 0 + else: + marker['lod_score'] = -math.log10(marker['p_value']) + # Using -log(p) for the LRS; need to ask Rob how he wants to get LRS from p-values + marker['lrs_value'] = -math.log10(marker['p_value']) * 4.61 + elif isinstance(p_values, dict): + filtered_markers = [] + for marker in self.markers: + if marker['name'] in p_values: + marker['p_value'] = p_values[marker['name']] + if math.isnan(marker['p_value']) or (marker['p_value'] <= 0): + marker['lod_score'] = 0 + marker['lrs_value'] = 0 + else: + marker['lod_score'] = -math.log10(marker['p_value']) + # Using -log(p) for the LRS; need to ask Rob how he wants to get LRS from p-values + marker['lrs_value'] = - \ + math.log10(marker['p_value']) * 4.61 + filtered_markers.append(marker) + self.markers = filtered_markers + + +class HumanMarkers(Markers): + "Markers for humans ..." + + def __init__(self, name, specified_markers=[]): + marker_data_fh = open(flat_files('mapping') + '/' + name + '.bim') + self.markers = [] + for line in marker_data_fh: + splat = line.strip().split() + if len(specified_markers) > 0: + if splat[1] in specified_markers: + marker = {} + marker['chr'] = int(splat[0]) + marker['name'] = splat[1] + marker['Mb'] = float(splat[3]) / 1000000 + else: + continue + else: + marker = {} + marker['chr'] = int(splat[0]) + marker['name'] = splat[1] + marker['Mb'] = float(splat[3]) / 1000000 + self.markers.append(marker) + + def add_pvalues(self, p_values): + super(HumanMarkers, self).add_pvalues(p_values) diff --git a/gn2/base/data_set/mrnaassaydataset.py b/gn2/base/data_set/mrnaassaydataset.py new file mode 100644 index 00000000..f641de27 --- /dev/null +++ b/gn2/base/data_set/mrnaassaydataset.py @@ -0,0 +1,179 @@ +"MrnaAssayDataSet class ..." + +import codecs + + +from .dataset import DataSet +from .utils import geno_mrna_confidentiality +from gn2.wqflask.database import database_connection +from gn2.utility.tools import get_setting + +class MrnaAssayDataSet(DataSet): + ''' + An mRNA Assay is a quantitative assessment (assay) associated with an mRNA trait + + This used to be called ProbeSet, but that term only refers specifically to the Affymetrix + platform and is far too specific. + + ''' + + def setup(self): + # Fields in the database table + self.search_fields = ['Name', + 'Description', + 'Probe_Target_Description', + 'Symbol', + 'Alias', + 'GenbankId', + 'UniGeneId', + 'RefSeq_TranscriptId'] + + # Find out what display_fields is + self.display_fields = ['name', 'symbol', + 'description', 'probe_target_description', + 'chr', 'mb', + 'alias', 'geneid', + 'genbankid', 'unigeneid', + 'omim', 'refseq_transcriptid', + 'blatseq', 'targetseq', + 'chipid', 'comments', + 'strand_probe', 'strand_gene', + 'proteinid', 'uniprotid', + 'probe_set_target_region', + 'probe_set_specificity', + 'probe_set_blat_score', + 'probe_set_blat_mb_start', + 'probe_set_blat_mb_end', + 'probe_set_strand', + 'probe_set_note_by_rw', + 'flag'] + + # Fields displayed in the search results table header + self.header_fields = ['Index', + 'Record', + 'Symbol', + 'Description', + 'Location', + 'Mean', + 'Max LRS', + 'Max LRS Location', + 'Additive Effect'] + + # Todo: Obsolete or rename this field + self.type = 'ProbeSet' + self.query_for_group = """ +SELECT InbredSet.Name, InbredSet.Id, InbredSet.GeneticType, InbredSet.InbredSetCode +FROM InbredSet, ProbeSetFreeze, ProbeFreeze WHERE ProbeFreeze.InbredSetId = InbredSet.Id AND +ProbeFreeze.Id = ProbeSetFreeze.ProbeFreezeId AND ProbeSetFreeze.Name = %s""" + + def check_confidentiality(self): + return geno_mrna_confidentiality(self) + + def get_trait_info(self, trait_list=None, species=''): + + # Note: setting trait_list to [] is probably not a great idea. + if not trait_list: + trait_list = [] + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + for this_trait in trait_list: + + if not this_trait.haveinfo: + this_trait.retrieveInfo(QTL=1) + + if not this_trait.symbol: + this_trait.symbol = "N/A" + + # XZ, 12/08/2008: description + # XZ, 06/05/2009: Rob asked to add probe target description + description_string = str( + str(this_trait.description).strip(codecs.BOM_UTF8), 'utf-8') + target_string = str( + str(this_trait.probe_target_description).strip(codecs.BOM_UTF8), 'utf-8') + + if len(description_string) > 1 and description_string != 'None': + description_display = description_string + else: + description_display = this_trait.symbol + + if (len(description_display) > 1 and description_display != 'N/A' + and len(target_string) > 1 and target_string != 'None'): + description_display = description_display + '; ' + target_string.strip() + + # Save it for the jinja2 template + this_trait.description_display = description_display + + if this_trait.chr and this_trait.mb: + this_trait.location_repr = 'Chr%s: %.6f' % ( + this_trait.chr, float(this_trait.mb)) + + # Get mean expression value + cursor.execute( + "SELECT ProbeSetXRef.mean FROM " + "ProbeSetXRef, ProbeSet WHERE " + "ProbeSetXRef.ProbeSetFreezeId = %s " + "AND ProbeSet.Id = ProbeSetXRef.ProbeSetId " + "AND ProbeSet.Name = %s", + (str(this_trait.dataset.id), this_trait.name,) + ) + result = cursor.fetchone() + + mean = result[0] if result else 0 + + if mean: + this_trait.mean = "%2.3f" % mean + + # LRS and its location + this_trait.LRS_score_repr = 'N/A' + this_trait.LRS_location_repr = 'N/A' + + # Max LRS and its Locus location + if this_trait.lrs and this_trait.locus: + cursor.execute( + "SELECT Geno.Chr, Geno.Mb FROM " + "Geno, Species WHERE " + "Species.Name = %s AND " + "Geno.Name = %s AND " + "Geno.SpeciesId = Species.Id", + (species, this_trait.locus,) + ) + if result := cursor.fetchone(): + lrs_chr, lrs_mb = result + this_trait.LRS_score_repr = '%3.1f' % this_trait.lrs + this_trait.LRS_location_repr = 'Chr%s: %.6f' % ( + lrs_chr, float(lrs_mb)) + + return trait_list + + def retrieve_sample_data(self, trait): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT Strain.Name, ProbeSetData.value, " + "ProbeSetSE.error, NStrain.count, " + "Strain.Name2 FROM (ProbeSetData, " + "ProbeSetFreeze, Strain, ProbeSet, " + "ProbeSetXRef) LEFT JOIN ProbeSetSE ON " + "(ProbeSetSE.DataId = ProbeSetData.Id AND " + "ProbeSetSE.StrainId = ProbeSetData.StrainId) " + "LEFT JOIN NStrain ON " + "(NStrain.DataId = ProbeSetData.Id AND " + "NStrain.StrainId = ProbeSetData.StrainId) " + "WHERE ProbeSet.Name = %s AND " + "ProbeSetXRef.ProbeSetId = ProbeSet.Id " + "AND ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id " + "AND ProbeSetFreeze.Name = %s AND " + "ProbeSetXRef.DataId = ProbeSetData.Id " + "AND ProbeSetData.StrainId = Strain.Id " + "ORDER BY Strain.Name", + (trait, self.name,) + ) + return cursor.fetchall() + + def retrieve_genes(self, column_name): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + f"SELECT ProbeSet.Name, ProbeSet.{column_name} " + "FROM ProbeSet,ProbeSetXRef WHERE " + "ProbeSetXRef.ProbeSetFreezeId = %s " + "AND ProbeSetXRef.ProbeSetId=ProbeSet.Id", + (str(self.id),)) + return dict(cursor.fetchall()) diff --git a/gn2/base/data_set/phenotypedataset.py b/gn2/base/data_set/phenotypedataset.py new file mode 100644 index 00000000..b14556fe --- /dev/null +++ b/gn2/base/data_set/phenotypedataset.py @@ -0,0 +1,134 @@ +"PhenotypeDataSet class ..." + +from .dataset import DataSet +from gn2.base import webqtlConfig +from gn2.utility.tools import get_setting +from gn2.wqflask.database import database_connection + +class PhenotypeDataSet(DataSet): + + def setup(self): + # Fields in the database table + self.search_fields = ['Phenotype.Post_publication_description', + 'Phenotype.Pre_publication_description', + 'Phenotype.Pre_publication_abbreviation', + 'Phenotype.Post_publication_abbreviation', + 'PublishXRef.mean', + 'Phenotype.Lab_code', + 'Publication.PubMed_ID', + 'Publication.Abstract', + 'Publication.Title', + 'Publication.Authors', + 'PublishXRef.Id'] + + # Figure out what display_fields is + self.display_fields = ['name', 'group_code', + 'pubmed_id', + 'pre_publication_description', + 'post_publication_description', + 'original_description', + 'pre_publication_abbreviation', + 'post_publication_abbreviation', + 'mean', + 'lab_code', + 'submitter', 'owner', + 'authorized_users', + 'authors', 'title', + 'abstract', 'journal', + 'volume', 'pages', + 'month', 'year', + 'sequence', 'units', 'comments'] + + # Fields displayed in the search results table header + self.header_fields = ['Index', + 'Record', + 'Description', + 'Authors', + 'Year', + 'Max LRS', + 'Max LRS Location', + 'Additive Effect'] + + self.type = 'Publish' + self.query_for_group = """ +SELECT InbredSet.Name, InbredSet.Id, InbredSet.GeneticType, InbredSet.InbredSetCode FROM InbredSet, PublishFreeze WHERE PublishFreeze.InbredSetId = InbredSet.Id AND PublishFreeze.Name = %s""" + + def check_confidentiality(self): + # (Urgently?) Need to write this + pass + + def get_trait_info(self, trait_list, species=''): + for this_trait in trait_list: + + if not this_trait.haveinfo: + this_trait.retrieve_info(get_qtl_info=True) + + description = this_trait.post_publication_description + + # If the dataset is confidential and the user has access to confidential + # phenotype traits, then display the pre-publication description instead + # of the post-publication description + if this_trait.confidential: + this_trait.description_display = "" + continue # for now, because no authorization features + + if not webqtlUtil.hasAccessToConfidentialPhenotypeTrait( + privilege=self.privilege, + userName=self.userName, + authorized_users=this_trait.authorized_users): + + description = this_trait.pre_publication_description + + if len(description) > 0: + this_trait.description_display = description.strip() + else: + this_trait.description_display = "" + + if not this_trait.year.isdigit(): + this_trait.pubmed_text = "N/A" + else: + this_trait.pubmed_text = this_trait.year + + if this_trait.pubmed_id: + this_trait.pubmed_link = webqtlConfig.PUBMEDLINK_URL % this_trait.pubmed_id + + # LRS and its location + this_trait.LRS_score_repr = "N/A" + this_trait.LRS_location_repr = "N/A" + + if this_trait.lrs: + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT Geno.Chr, Geno.Mb FROM " + "Geno, Species WHERE " + "Species.Name = %s AND " + "Geno.Name = %s AND " + "Geno.SpeciesId = Species.Id", + (species, this_trait.locus,) + ) + if result := cursor.fetchone(): + if result[0] and result[1]: + LRS_Chr, LRS_Mb = result[0], result[1] + this_trait.LRS_score_repr = LRS_score_repr = '%3.1f' % this_trait.lrs + this_trait.LRS_location_repr = LRS_location_repr = 'Chr%s: %.6f' % ( + LRS_Chr, float(LRS_Mb)) + + def retrieve_sample_data(self, trait): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT Strain.Name, PublishData.value, " + "PublishSE.error, NStrain.count, " + "Strain.Name2 FROM (PublishData, Strain, " + "PublishXRef, PublishFreeze) LEFT JOIN " + "PublishSE ON " + "(PublishSE.DataId = PublishData.Id " + "AND PublishSE.StrainId = PublishData.StrainId) " + "LEFT JOIN NStrain ON " + "(NStrain.DataId = PublishData.Id AND " + "NStrain.StrainId = PublishData.StrainId) " + "WHERE PublishXRef.InbredSetId = PublishFreeze.InbredSetId " + "AND PublishData.Id = PublishXRef.DataId AND " + "PublishXRef.Id = %s AND PublishFreeze.Id = %s " + "AND PublishData.StrainId = Strain.Id " + "ORDER BY Strain.Name", (trait, self.id)) + return cursor.fetchall() diff --git a/gn2/base/data_set/probably_unused.py b/gn2/base/data_set/probably_unused.py new file mode 100644 index 00000000..12be05a7 --- /dev/null +++ b/gn2/base/data_set/probably_unused.py @@ -0,0 +1,35 @@ +"Functions that are probably unused in the code" + +import pickle as pickle + +from gn2.wqflask.database import database_connection +from gn2.utility.tools import get_setting + +def create_datasets_list(): + if USE_REDIS: + key = "all_datasets" + result = redis_conn.get(key) + + if result: + datasets = pickle.loads(result) + + if result is None: + datasets = list() + type_dict = {'Publish': 'PublishFreeze', + 'ProbeSet': 'ProbeSetFreeze', + 'Geno': 'GenoFreeze'} + + for dataset_type in type_dict: + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute("SELECT Name FROM %s", + (type_dict[dataset_type],)) + results = cursor.fetchall(query) + if results: + for result in results: + datasets.append( + create_dataset(result.Name, dataset_type)) + if USE_REDIS: + redis_conn.set(key, pickle.dumps(datasets, pickle.HIGHEST_PROTOCOL)) + redis_conn.expire(key, 60 * 60) + + return datasets diff --git a/gn2/base/data_set/tempdataset.py b/gn2/base/data_set/tempdataset.py new file mode 100644 index 00000000..b1c26a3b --- /dev/null +++ b/gn2/base/data_set/tempdataset.py @@ -0,0 +1,23 @@ +"TempDataSet class ..." + +from .dataset import DataSet + +class TempDataSet(DataSet): + """Temporary user-generated data set""" + + def setup(self): + self.search_fields = ['name', + 'description'] + + self.display_fields = ['name', + 'description'] + + self.header_fields = ['Name', + 'Description'] + + self.type = 'Temp' + + # Need to double check later how these are used + self.id = 1 + self.fullname = 'Temporary Storage' + self.shortname = 'Temp' diff --git a/gn2/base/data_set/utils.py b/gn2/base/data_set/utils.py new file mode 100644 index 00000000..fc17026e --- /dev/null +++ b/gn2/base/data_set/utils.py @@ -0,0 +1,80 @@ +"data_set package utilities" + +import datetime +import os +import json +import hashlib +from typing import List + + +from gn2.utility.tools import get_setting, SQL_URI +from gn2.base.webqtlConfig import TMPDIR +from gn2.wqflask.database import parse_db_url, database_connection + +def geno_mrna_confidentiality(ob): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT confidentiality, " + f"AuthorisedUsers FROM {ob.type}Freeze WHERE Name = %s", + (ob.name,) + ) + result = cursor.fetchall() + if len(result) > 0 and result[0]: + return True + +def query_table_timestamp(dataset_type: str): + """function to query the update timestamp of a given dataset_type""" + + # computation data and actions + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + fetch_db_name = parse_db_url(SQL_URI) + cursor.execute( + "SELECT UPDATE_TIME FROM " + "information_schema.tables " + f"WHERE TABLE_SCHEMA = '{fetch_db_name[3]}' " + f"AND TABLE_NAME = '{dataset_type}Data'") + date_time_obj = cursor.fetchone()[0] + if not date_time_obj: + date_time_obj = datetime.datetime.now() + return date_time_obj.strftime("%Y-%m-%d %H:%M:%S") + + +def generate_hash_file(dataset_name: str, dataset_type: str, dataset_timestamp: str, samplelist: str): + """given the trait_name generate a unique name for this""" + string_unicode = f"{dataset_name}{dataset_timestamp}{samplelist}".encode() + md5hash = hashlib.md5(string_unicode) + return md5hash.hexdigest() + + +def cache_dataset_results(dataset_name: str, dataset_type: str, samplelist: List, query_results: List): + """function to cache dataset query results to file + input dataset_name and type query_results(already processed in default dict format) + """ + # data computations actions + # store the file path on redis + + table_timestamp = query_table_timestamp(dataset_type) + samplelist_as_str = ",".join(samplelist) + + file_name = generate_hash_file(dataset_name, dataset_type, table_timestamp, samplelist_as_str) + file_path = os.path.join(TMPDIR, f"{file_name}.json") + + with open(file_path, "w") as file_handler: + json.dump(query_results, file_handler) + + +def fetch_cached_results(dataset_name: str, dataset_type: str, samplelist: List): + """function to fetch the cached results""" + + table_timestamp = query_table_timestamp(dataset_type) + samplelist_as_str = ",".join(samplelist) + + file_name = generate_hash_file(dataset_name, dataset_type, table_timestamp, samplelist_as_str) + file_path = os.path.join(TMPDIR, f"{file_name}.json") + try: + with open(file_path, "r") as file_handler: + + return json.load(file_handler) + + except Exception: + pass diff --git a/gn2/base/mrna_assay_tissue_data.py b/gn2/base/mrna_assay_tissue_data.py new file mode 100644 index 00000000..7b7914aa --- /dev/null +++ b/gn2/base/mrna_assay_tissue_data.py @@ -0,0 +1,102 @@ +import collections + +from gn2.utility import Bunch + + +class MrnaAssayTissueData: + + def __init__(self, conn, gene_symbols=None): + self.gene_symbols = gene_symbols + self.conn = conn + if self.gene_symbols is None: + self.gene_symbols = [] + + self.data = collections.defaultdict(Bunch) + results = () + # Note that inner join is necessary in this query to get + # distinct record in one symbol group with highest mean value + # Due to the limit size of TissueProbeSetFreezeId table in DB, + # performance of inner join is + # acceptable.MrnaAssayTissueData(gene_symbols=symbol_list) + with conn.cursor() as cursor: + if len(self.gene_symbols) == 0: + cursor.execute( + "SELECT t.Symbol, t.GeneId, t.DataId, " + "t.Chr, t.Mb, t.description, " + "t.Probe_Target_Description FROM (SELECT Symbol, " + "max(Mean) AS maxmean " + "FROM TissueProbeSetXRef WHERE " + "TissueProbeSetFreezeId=1 AND " + "Symbol != '' AND Symbol IS NOT " + "Null GROUP BY Symbol) " + "AS x INNER JOIN " + "TissueProbeSetXRef AS t ON " + "t.Symbol = x.Symbol " + "AND t.Mean = x.maxmean") + else: + cursor.execute( + "SELECT t.Symbol, t.GeneId, t.DataId, " + "t.Chr, t.Mb, t.description, " + "t.Probe_Target_Description FROM (SELECT Symbol, " + "max(Mean) AS maxmean " + "FROM TissueProbeSetXRef WHERE " + "TissueProbeSetFreezeId=1 AND " + "Symbol IN " + f"({', '.join(['%s'] * len(self.gene_symbols))}) " + "GROUP BY Symbol) AS x INNER JOIN " + "TissueProbeSetXRef AS t ON t.Symbol = x.Symbol " + "AND t.Mean = x.maxmean", + tuple(self.gene_symbols)) + results = list(cursor.fetchall()) + lower_symbols = {} + for gene_symbol in self.gene_symbols: + if gene_symbol is not None: + lower_symbols[gene_symbol.lower()] = True + + for result in results: + (symbol, gene_id, data_id, _chr, _mb, + descr, probeset_target_descr) = result + if symbol is not None and lower_symbols.get(symbol.lower()): + symbol = symbol.lower() + self.data[symbol].gene_id = gene_id + self.data[symbol].data_id = data_id + self.data[symbol].chr = _chr + self.data[symbol].mb = _mb + self.data[symbol].description = descr + (self.data[symbol] + .probe_target_description) = probeset_target_descr + + + def get_symbol_values_pairs(self): + """Get one dictionary whose key is gene symbol and value is + tissue expression data (list type). All keys are lower case. + + The output is a symbolValuepairDict (dictionary): one + dictionary of Symbol and Value Pair; key is symbol, value is + one list of expression values of one probeSet; + + """ + id_list = [self.data[symbol].data_id for symbol in self.data] + + symbol_values_dict = {} + + if len(id_list) > 0: + results = [] + with self.conn.cursor() as cursor: + + cursor.execute( + "SELECT TissueProbeSetXRef.Symbol, TissueProbeSetData.value " + "FROM TissueProbeSetXRef, TissueProbeSetData" + f" WHERE TissueProbeSetData.Id IN ({', '.join(['%s'] * len(id_list))})" + " AND TissueProbeSetXRef.DataId = TissueProbeSetData.Id" + ,tuple(id_list)) + + results = cursor.fetchall() + for result in results: + (symbol, value) = result + if symbol.lower() not in symbol_values_dict: + symbol_values_dict[symbol.lower()] = [value] + else: + symbol_values_dict[symbol.lower()].append( + value) + return symbol_values_dict diff --git a/gn2/base/species.py b/gn2/base/species.py new file mode 100644 index 00000000..0844fada --- /dev/null +++ b/gn2/base/species.py @@ -0,0 +1,59 @@ +from dataclasses import dataclass +from typing import Optional, Union +from collections import OrderedDict + + +class TheSpecies: + """Data related to species.""" + + def __init__(self, dataset=None, species_name=None) -> None: + "Initialise the Species object" + self.dataset = dataset + self.name = self.species_name = species_name + self.chromosomes = Chromosomes(species=species_name, + dataset=dataset) + + +@dataclass +class IndChromosome: + """Data related to IndChromosome""" + name: str + length: int + + @property + def mb_length(self) -> Union[int, float]: + """Chromosome length in mega-bases""" + return self.length / 1000000 + + +@dataclass +class Chromosomes: + """Data related to a chromosome""" + + def __init__(self, dataset, species: Optional[str]) -> None: + "initialise the Chromosome object" + self.species = species + if species is None: + self.dataset = dataset + + def chromosomes(self, db_cursor) -> OrderedDict: + """Lazily fetch the chromosomes""" + chromosomes = OrderedDict() + if self.species is not None: + db_cursor.execute( + "SELECT Chr_Length.Name, Chr_Length.OrderId, Length " + "FROM Chr_Length, Species WHERE " + "Chr_Length.SpeciesId = Species.SpeciesId AND " + "Species.Name = %s " + "ORDER BY OrderId", (self.species.capitalize(),)) + else: + db_cursor.execute( + "SELECT Chr_Length.Name, Chr_Length.OrderId, " + "Length FROM Chr_Length, InbredSet WHERE " + "Chr_Length.SpeciesId = InbredSet.SpeciesId AND " + "InbredSet.Name = " + "%s ORDER BY OrderId", (self.dataset.group.name,)) + for name, _, length in db_cursor.fetchall(): + chromosomes[name] = IndChromosome( + name=name, length=length) + return chromosomes diff --git a/gn2/base/trait.py b/gn2/base/trait.py new file mode 100644 index 00000000..701958d7 --- /dev/null +++ b/gn2/base/trait.py @@ -0,0 +1,613 @@ +import requests +import simplejson as json +from gn2.wqflask import app + +import gn2.utility.hmac as hmac +from gn2.base import webqtlConfig +from gn2.base.webqtlCaseData import webqtlCaseData +from gn2.base.data_set import create_dataset +from gn2.utility.authentication_tools import check_resource_availability +from gn2.utility.tools import get_setting, GN2_BASE_URL +from gn2.utility.redis_tools import get_redis_conn, get_resource_id + +from flask import g, request, url_for + +from gn2.wqflask.database import database_connection + + +Redis = get_redis_conn() + + +def create_trait(**kw): + assert bool(kw.get('dataset')) != bool( + kw.get('dataset_name')), "Needs dataset ob. or name" + + assert bool(kw.get('name')), "Needs trait name" + + + if bool(kw.get('dataset')): + dataset = kw.get('dataset') + + + else: + if kw.get('dataset_name') != "Temp": + + + dataset = create_dataset(kw.get('dataset_name')) + else: + + dataset = create_dataset( + dataset_name="Temp", + dataset_type="Temp", + group_name= kw.get('name').split("_")[2]) + + + if dataset.type == 'Publish': + permissions = check_resource_availability( + dataset, g.user_session.user_id, kw.get('name')) + else: + permissions = check_resource_availability( + dataset, g.user_session.user_id) + + + if permissions['data'] != "no-access": + + the_trait = GeneralTrait(**dict(kw,dataset=dataset)) + if the_trait.dataset.type != "Temp": + the_trait = retrieve_trait_info( + the_trait, + the_trait.dataset, + get_qtl_info=kw.get('get_qtl_info')) + return the_trait + else: + return None + + +class GeneralTrait: + """ + Trait class defines a trait in webqtl, can be either Microarray, + Published phenotype, genotype, or user input trait + + """ + + def __init__(self, get_qtl_info=False, get_sample_info=True, **kw): + # xor assertion + assert kw.get("dataset"), "Dataset obj is needed as a kwarg" + + # Trait ID, ProbeSet ID, Published ID, etc. + self.name = kw.get('name') + self.dataset = kw.get("dataset") + self.cellid = kw.get('cellid') + self.identification = kw.get('identification', 'un-named trait') + self.haveinfo = kw.get('haveinfo', False) + # Blat sequence, available for ProbeSet + self.sequence = kw.get('sequence') + self.data = kw.get('data', {}) + self.view = True + + # Sets defaults + self.locus = None + self.lrs = None + self.pvalue = None + self.mean = None + self.additive = None + self.num_overlap = None + self.strand_probe = None + self.symbol = None + self.abbreviation = None + self.display_name = self.name + + self.LRS_score_repr = "N/A" + self.LRS_location_repr = "N/A" + self.chr = self.mb = self.locus_chr = self.locus_mb = "" + + if kw.get('fullname'): + name2 = value.split("::") + if len(name2) == 2: + self.dataset, self.name = name2 + # self.cellid is set to None above + elif len(name2) == 3: + self.dataset, self.name, self.cellid = name2 + + # Todo: These two lines are necessary most of the time, but + # perhaps not all of the time So we could add a simple if + # statement to short-circuit this if necessary + if get_sample_info is not False: + self = retrieve_sample_data(self, self.dataset) + + def export_informative(self, include_variance=0): + """ + export informative sample + mostly used in qtl regression + + """ + samples = [] + vals = [] + the_vars = [] + sample_aliases = [] + for sample_name, sample_data in list(self.data.items()): + if sample_data.value is not None: + if not include_variance or sample_data.variance is not None: + samples.append(sample_name) + vals.append(sample_data.value) + the_vars.append(sample_data.variance) + sample_aliases.append(sample_data.name2) + return samples, vals, the_vars, sample_aliases + + @property + def description_fmt(self): + """Return a text formated description""" + if self.dataset.type == 'ProbeSet': + if self.description: + formatted = self.description + if self.probe_target_description: + formatted += "; " + self.probe_target_description + else: + formatted = "Not available" + elif self.dataset.type == 'Publish': + if self.confidential: + formatted = self.pre_publication_description + else: + formatted = self.post_publication_description + else: + formatted = "Not available" + if isinstance(formatted, bytes): + formatted = formatted.decode("utf-8") + return formatted + + @property + def alias_fmt(self): + """Return a text formatted alias""" + + alias = 'Not available' + if getattr(self, "alias", None): + alias = self.alias.replace(";", " ") + alias = ", ".join(alias.split()) + + return alias + + @property + def wikidata_alias_fmt(self): + """Return a text formatted alias""" + + alias = 'Not available' + if self.symbol: + human_response = requests.get( + GN2_BASE_URL + "gn3/gene/aliases/" + self.symbol.upper()) + mouse_response = requests.get( + GN2_BASE_URL + "gn3/gene/aliases/" + self.symbol.capitalize()) + other_response = requests.get( + GN2_BASE_URL + "gn3/gene/aliases/" + self.symbol.lower()) + + if human_response and mouse_response and other_response: + alias_list = json.loads(human_response.content) + json.loads( + mouse_response.content) + \ + json.loads(other_response.content) + + filtered_aliases = [] + seen = set() + for item in alias_list: + if item in seen: + continue + else: + filtered_aliases.append(item) + seen.add(item) + alias = "; ".join(filtered_aliases) + + return alias + + @property + def location_fmt(self): + """Return a text formatted location + + While we're at it we set self.location in case we need it + later (do we?) + + """ + + if self.chr == "Un": + return 'Not available' + + if self.chr and self.mb: + self.location = 'Chr %s @ %s Mb' % (self.chr, self.mb) + elif self.chr: + self.location = 'Chr %s @ Unknown position' % (self.chr) + else: + self.location = 'Not available' + + fmt = self.location + # XZ: deal with direction + if self.strand_probe == '+': + fmt += (' on the plus strand ') + elif self.strand_probe == '-': + fmt += (' on the minus strand ') + + return fmt + + +def retrieve_sample_data(trait, dataset, samplelist=None): + if samplelist is None: + samplelist = [] + + if dataset.type == "Temp": + results = Redis.get(trait.name).split() + else: + results = dataset.retrieve_sample_data(trait.name) + # Todo: is this necessary? If not remove + trait.data.clear() + + if results: + if dataset.type == "Temp": + all_samples_ordered = dataset.group.all_samples_ordered() + for i, item in enumerate(results): + try: + trait.data[all_samples_ordered[i]] = webqtlCaseData( + all_samples_ordered[i], float(item)) + except: + pass + else: + for item in results: + name, value, variance, num_cases, name2 = item + if not samplelist or (samplelist and name in samplelist): + # name, value, variance, num_cases) + trait.data[name] = webqtlCaseData(*item) + return trait + + +@app.route("/trait/get_sample_data") +def get_sample_data(): + params = request.args + trait = params['trait'] + dataset = params['dataset'] + + trait_ob = create_trait(name=trait, dataset_name=dataset) + if trait_ob: + trait_dict = {} + trait_dict['name'] = trait + trait_dict['db'] = dataset + trait_dict['type'] = trait_ob.dataset.type + trait_dict['group'] = trait_ob.dataset.group.name + trait_dict['tissue'] = trait_ob.dataset.tissue + trait_dict['species'] = trait_ob.dataset.group.species + trait_dict['url'] = url_for( + 'show_trait_page', trait_id=trait, dataset=dataset) + if trait_ob.dataset.type == "ProbeSet": + trait_dict['symbol'] = trait_ob.symbol + trait_dict['location'] = trait_ob.location_repr + trait_dict['description'] = trait_ob.description_display + elif trait_ob.dataset.type == "Publish": + trait_dict['description'] = trait_ob.description_display + if trait_ob.pubmed_id: + trait_dict['pubmed_link'] = trait_ob.pubmed_link + trait_dict['pubmed_text'] = trait_ob.pubmed_text + else: + trait_dict['location'] = trait_ob.location_repr + + return json.dumps([trait_dict, {key: value.value for + key, value in list( + trait_ob.data.items())}]) + else: + return None + + +def jsonable(trait, dataset=None): + """Return a dict suitable for using as json + + Actual turning into json doesn't happen here though""" + + if not dataset: + dataset = create_dataset(dataset_name=trait.dataset.name, + dataset_type=trait.dataset.type, + group_name=trait.dataset.group.name) + + + trait_symbol = "N/A" + trait_mean = "N/A" + if trait.symbol: + trait_symbol = trait.symbol + if trait.mean: + trait_mean = trait.mean + + if dataset.type == "ProbeSet": + return dict(name=trait.name, + display_name=trait.display_name, + hmac=hmac.data_hmac('{}:{}'.format(trait.display_name, dataset.name)), + view=str(trait.view), + symbol=trait_symbol, + dataset=dataset.name, + dataset_name=dataset.shortname, + description=trait.description_display, + mean=trait_mean, + location=trait.location_repr, + chr=trait.chr, + mb=trait.mb, + lrs_score=trait.LRS_score_repr, + lrs_location=trait.LRS_location_repr, + lrs_chr=trait.locus_chr, + lrs_mb=trait.locus_mb, + additive=trait.additive + ) + elif dataset.type == "Publish": + if trait.pubmed_id: + return dict(name=trait.name, + display_name=trait.display_name, + hmac=hmac.data_hmac('{}:{}'.format(trait.name, dataset.name)), + view=str(trait.view), + symbol=trait.abbreviation, + dataset=dataset.name, + dataset_name=dataset.shortname, + description=trait.description_display, + abbreviation=trait.abbreviation, + authors=trait.authors, + pubmed_id=trait.pubmed_id, + pubmed_text=trait.pubmed_text, + pubmed_link=trait.pubmed_link, + mean=trait_mean, + lrs_score=trait.LRS_score_repr, + lrs_location=trait.LRS_location_repr, + lrs_chr=trait.locus_chr, + lrs_mb=trait.locus_mb, + additive=trait.additive + ) + else: + return dict(name=trait.name, + display_name=trait.display_name, + hmac=hmac.data_hmac('{}:{}'.format(trait.name, dataset.name)), + view=str(trait.view), + symbol=trait.abbreviation, + dataset=dataset.name, + dataset_name=dataset.shortname, + description=trait.description_display, + abbreviation=trait.abbreviation, + authors=trait.authors, + pubmed_text=trait.pubmed_text, + mean=trait_mean, + lrs_score=trait.LRS_score_repr, + lrs_location=trait.LRS_location_repr, + lrs_chr=trait.locus_chr, + lrs_mb=trait.locus_mb, + additive=trait.additive + ) + elif dataset.type == "Geno": + return dict(name=trait.name, + display_name=trait.display_name, + hmac=hmac.data_hmac('{}:{}'.format(trait.display_name, dataset.name)), + view=str(trait.view), + dataset=dataset.name, + dataset_name=dataset.shortname, + location=trait.location_repr, + chr=trait.chr, + mb=trait.mb + ) + elif dataset.name == "Temp": + return dict(name=trait.name, + display_name=trait.display_name, + hmac=hmac.data_hmac('{}:{}'.format(trait.display_name, dataset.name)), + view=str(trait.view), + dataset="Temp", + dataset_name="Temp") + else: + return dict() + + +def retrieve_trait_info(trait, dataset, get_qtl_info=False): + if not dataset: + raise ValueError("Dataset doesn't exist") + + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + trait_info = () + if dataset.type == 'Publish': + cursor.execute( + "SELECT PublishXRef.Id, InbredSet.InbredSetCode, " + "Publication.PubMed_ID, " + "CAST(Phenotype.Pre_publication_description AS BINARY), " + "CAST(Phenotype.Post_publication_description AS BINARY), " + "CAST(Phenotype.Original_description AS BINARY), " + "CAST(Phenotype.Pre_publication_abbreviation AS BINARY), " + "CAST(Phenotype.Post_publication_abbreviation AS BINARY), " + "PublishXRef.mean, Phenotype.Lab_code, " + "Phenotype.Submitter, Phenotype.Owner, " + "Phenotype.Authorized_Users, " + "CAST(Publication.Authors AS BINARY), " + "CAST(Publication.Title AS BINARY), " + "CAST(Publication.Abstract AS BINARY), " + "CAST(Publication.Journal AS BINARY), " + "Publication.Volume, Publication.Pages, " + "Publication.Month, Publication.Year, " + "PublishXRef.Sequence, Phenotype.Units, " + "PublishXRef.comments FROM PublishXRef, Publication, " + "Phenotype, PublishFreeze, InbredSet WHERE " + "PublishXRef.Id = %s AND " + "Phenotype.Id = PublishXRef.PhenotypeId " + "AND Publication.Id = PublishXRef.PublicationId " + "AND PublishXRef.InbredSetId = PublishFreeze.InbredSetId " + "AND PublishXRef.InbredSetId = InbredSet.Id AND " + "PublishFreeze.Id = %s", + (trait.name, dataset.id,) + ) + trait_info = cursor.fetchone() + + # XZ, 05/08/2009: Xiaodong add this block to use ProbeSet.Id to find the probeset instead of just using ProbeSet.Name + # XZ, 05/08/2009: to avoid the problem of same probeset name from different platforms. + elif dataset.type == 'ProbeSet': + display_fields_string = ', ProbeSet.'.join(dataset.display_fields) + display_fields_string = f'ProbeSet.{display_fields_string}' + cursor.execute( + f"SELECT {display_fields_string} FROM ProbeSet, ProbeSetFreeze, " + "ProbeSetXRef WHERE " + "ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id " + "AND ProbeSetXRef.ProbeSetId = ProbeSet.Id AND " + "ProbeSetFreeze.Name = %s AND " + "ProbeSet.Name = %s", + (dataset.name, str(trait.name),) + ) + trait_info = cursor.fetchone() + # XZ, 05/08/2009: We also should use Geno.Id to find marker instead of just using Geno.Name + # to avoid the problem of same marker name from different species. + elif dataset.type == 'Geno': + display_fields_string = ',Geno.'.join(dataset.display_fields) + display_fields_string = f'Geno.{display_fields_string}' + cursor.execute( + f"SELECT {display_fields_string} FROM Geno, GenoFreeze, " + "GenoXRef WHERE " + "GenoXRef.GenoFreezeId = GenoFreeze.Id " + "AND GenoXRef.GenoId = Geno.Id " + "AND GenoFreeze.Name = %s " + "AND Geno.Name = %s", + (dataset.name, trait.name) + ) + trait_info = cursor.fetchone() + else: # Temp type + cursor.execute( + f"SELECT {','.join(dataset.display_fields)} " + f"FROM {dataset.type} WHERE Name = %s", + (trait.name,) + ) + trait_info = cursor.fetchone() + + if trait_info: + trait.haveinfo = True + for i, field in enumerate(dataset.display_fields): + holder = trait_info[i] + if isinstance(holder, bytes): + holder = holder.decode("utf-8", errors="ignore") + setattr(trait, field, holder) + + if dataset.type == 'Publish': + if trait.group_code: + trait.display_name = trait.group_code + "_" + str(trait.name) + + trait.confidential = 0 + if trait.pre_publication_description and not trait.pubmed_id: + trait.confidential = 1 + + description = trait.post_publication_description + + # If the dataset is confidential and the user has access to confidential + # phenotype traits, then display the pre-publication description instead + # of the post-publication description + trait.description_display = "N/A" + trait.abbreviation = "N/A" + if not trait.pubmed_id: + if trait.pre_publication_abbreviation: + trait.abbreviation = trait.pre_publication_abbreviation + if trait.pre_publication_description: + trait.description_display = trait.pre_publication_description + else: + if trait.post_publication_abbreviation: + trait.abbreviation = trait.post_publication_abbreviation + if description: + trait.description_display = description.strip() + + if not trait.year.isdigit(): + trait.pubmed_text = "N/A" + else: + trait.pubmed_text = trait.year + + if trait.pubmed_id: + trait.pubmed_link = webqtlConfig.PUBMEDLINK_URL % trait.pubmed_id + + if dataset.type == 'ProbeSet' and dataset.group: + description_string = trait.description + target_string = trait.probe_target_description + + if str(description_string or "") != "" and description_string != 'None': + description_display = description_string + else: + description_display = trait.symbol + + if (str(description_display or "") != "" + and description_display != 'N/A' + and str(target_string or "") != "" and target_string != 'None'): + description_display = description_display + '; ' + target_string.strip() + + # Save it for the jinja2 template + trait.description_display = description_display + + trait.location_repr = 'N/A' + if trait.chr and trait.mb: + trait.location_repr = 'Chr%s: %.6f' % ( + trait.chr, float(trait.mb)) + + elif dataset.type == "Geno": + trait.location_repr = 'N/A' + if trait.chr and trait.mb: + trait.location_repr = 'Chr%s: %.6f' % ( + trait.chr, float(trait.mb)) + + if get_qtl_info: + # LRS and its location + trait.LRS_score_repr = "N/A" + trait.LRS_location_repr = "N/A" + trait.locus = trait.locus_chr = trait.locus_mb = trait.lrs = trait.pvalue = trait.additive = "" + if dataset.type == 'ProbeSet' and not trait.cellid: + trait.mean = "" + cursor.execute( + "SELECT ProbeSetXRef.Locus, ProbeSetXRef.LRS, " + "ProbeSetXRef.pValue, ProbeSetXRef.mean, " + "ProbeSetXRef.additive FROM ProbeSetXRef, " + "ProbeSet WHERE " + "ProbeSetXRef.ProbeSetId = ProbeSet.Id " + "AND ProbeSet.Name = %s AND " + "ProbeSetXRef.ProbeSetFreezeId = %s", + (trait.name, dataset.id,) + ) + trait_qtl = cursor.fetchone() + if any(trait_qtl): + trait.locus, trait.lrs, trait.pvalue, trait.mean, trait.additive = trait_qtl + if trait.locus: + cursor.execute( + "SELECT Geno.Chr, Geno.Mb FROM " + "Geno, Species WHERE " + "Species.Name = %s AND " + "Geno.Name = %s AND " + "Geno.SpeciesId = Species.Id", + (dataset.group.species, trait.locus,) + ) + if result := cursor.fetchone() : + trait.locus_chr = result[0] + trait.locus_mb = result[1] + else: + trait.locus_chr = trait.locus_mb = "" + else: + trait.locus = trait.locus_chr = trait.locus_mb = trait.additive = "" + + if dataset.type == 'Publish': + cursor.execute( + "SELECT PublishXRef.Locus, PublishXRef.LRS, " + "PublishXRef.additive FROM " + "PublishXRef, PublishFreeze WHERE " + "PublishXRef.Id = %s AND " + "PublishXRef.InbredSetId = PublishFreeze.InbredSetId " + "AND PublishFreeze.Id = %s", (trait.name, dataset.id,) + ) + if trait_qtl := cursor.fetchone(): + trait.locus, trait.lrs, trait.additive = trait_qtl + if trait.locus: + cursor.execute( + "SELECT Geno.Chr, Geno.Mb FROM Geno, " + "Species WHERE Species.Name = %s " + "AND Geno.Name = %s AND " + "Geno.SpeciesId = Species.Id", + (dataset.group.species, trait.locus,) + ) + if result := cursor.fetchone(): + trait.locus_chr = result[0] + trait.locus_mb = result[1] + else: + trait.locus = trait.locus_chr = trait.locus_mb = trait.additive = "" + else: + trait.locus = trait.locus_chr = trait.locus_mb = trait.additive = "" + else: + trait.locus = trait.lrs = trait.additive = "" + if (dataset.type == 'Publish' or dataset.type == "ProbeSet"): + if str(trait.locus_chr or "") != "" and str(trait.locus_mb or "") != "": + trait.LRS_location_repr = LRS_location_repr = 'Chr%s: %.6f' % ( + trait.locus_chr, float(trait.locus_mb)) + if str(trait.lrs or "") != "": + trait.LRS_score_repr = LRS_score_repr = '%3.1f' % trait.lrs + else: + raise KeyError( + f"{repr(trait.name)} information is not found in the database " + f"for dataset '{dataset.name}' with id '{dataset.id}'.") + return trait diff --git a/gn2/base/webqtlCaseData.py b/gn2/base/webqtlCaseData.py new file mode 100644 index 00000000..b4717b4b --- /dev/null +++ b/gn2/base/webqtlCaseData.py @@ -0,0 +1,81 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010) +# at rwilliams@uthsc.edu and xzhou15@uthsc.edu +# +# This module is used by GeneNetwork project (www.genenetwork.org) +# +# Created by GeneNetwork Core Team 2010/08/10 + + +import gn2.utility.tools + +gn2.utility.tools.show_settings() + + +class webqtlCaseData: + """one case data in one trait""" + + def __init__(self, name, value=None, variance=None, num_cases=None, name2=None): + self.name = name + # Other name (for traits like BXD65a) + self.name2 = name2 + self.value = value # Trait Value + self.variance = variance # Trait Variance + self.num_cases = num_cases # Number of individuals/cases + self.extra_attributes = None + # Set a sane default (can't be just "id" cause that's a reserved word) + self.this_id = None + self.outlier = None # Not set to True/False until later + + def __repr__(self): + case_data_string = "<webqtlCaseData> " + if self.value is not None: + case_data_string += "value=%2.3f" % self.value + if self.variance is not None: + case_data_string += " variance=%2.3f" % self.variance + if self.num_cases: + case_data_string += " ndata=%s" % self.num_cases + if self.name: + case_data_string += " name=%s" % self.name + if self.name2: + case_data_string += " name2=%s" % self.name2 + return case_data_string + + @property + def class_outlier(self): + """Template helper""" + if self.outlier: + return "outlier" + return "" + + @property + def display_value(self): + if self.value is not None: + return "%2.3f" % self.value + return "x" + + @property + def display_variance(self): + if self.variance is not None: + return "%2.3f" % self.variance + return "x" + + @property + def display_num_cases(self): + if self.num_cases is not None: + return "%s" % self.num_cases + return "x" diff --git a/gn2/base/webqtlConfig.py b/gn2/base/webqtlConfig.py new file mode 100644 index 00000000..998c0efc --- /dev/null +++ b/gn2/base/webqtlConfig.py @@ -0,0 +1,107 @@ +# ' +# Environment Variables - public +# +# Note: much of this needs to handled by the settings/environment +# scripts. But rather than migrating everything in one go, we'll +# take it a step at a time. First the hard coded paths get replaced +# with those in utility/tools.py +# +######################################### +import os +from gn2.utility.tools import valid_path, mk_dir, assert_dir, assert_writable_dir, flat_files, TEMPDIR + +# Debug Level +# 1 for debug, mod python will reload import each time +DEBUG = 1 + +# USER privilege +USERDICT = {'guest': 1, 'user': 2, 'admin': 3, 'root': 4} + +# Set privileges +SUPER_PRIVILEGES = {'data': 'edit', 'metadata': 'edit', 'admin': 'edit-admins'} +DEFAULT_PRIVILEGES = {'data': 'view', 'metadata': 'view', 'admin': 'not-admin'} + +# minimum number of informative strains +KMININFORMATIVE = 5 + +# Daily download limit from one IP +DAILYMAXIMUM = 1000 + +# maximum LRS value +MAXLRS = 460.0 + +# MINIMUM Database public value +PUBLICTHRESH = 0 + +# Groups to treat as unique when drawing correlation dropdowns (not sure if this logic even makes sense or is necessary) +BXD_GROUP_EXCEPTIONS = ['BXD-Longevity', 'BXD-AE', 'BXD-Heart-Metals', 'BXD-NIA-AD'] + +# EXTERNAL LINK ADDRESSES +PUBMEDLINK_URL = "http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=PubMed&list_uids=%s&dopt=Abstract" +UCSC_BLAT = 'http://genome.ucsc.edu/cgi-bin/hgBlat?org=%s&db=%s&type=0&sort=0&output=0&userSeq=%s' +UTHSC_BLAT = 'http://ucscbrowser.genenetwork.org/cgi-bin/hgBlat?org=%s&db=%s&type=0&sort=0&output=0&userSeq=%s' +UTHSC_BLAT2 = 'http://ucscbrowserbeta.genenetwork.org/cgi-bin/hgBlat?org=%s&db=%s&type=0&sort=0&output=0&userSeq=%s' +GENOMEBROWSER_URL = "https://genome.ucsc.edu/cgi-bin/hgTracks?db=%s&position=%s" +NCBI_LOCUSID = "http://www.ncbi.nlm.nih.gov/gene?cmd=Retrieve&dopt=Graphics&list_uids=%s" +GENBANK_ID = "http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?db=Nucleotide&cmd=search&doptcmdl=DocSum&term=%s" +OMIM_ID = "http://www.ncbi.nlm.nih.gov/omim/%s" +UNIGEN_ID = "http://www.ncbi.nlm.nih.gov/UniGene/clust.cgi?ORG=%s&CID=%s" +HOMOLOGENE_ID = "http://www.ncbi.nlm.nih.gov/homologene/?term=%s" +GENOTATION_URL = "http://www.genotation.org/Getd2g.pl?gene_list=%s" +GTEX_URL = "https://www.gtexportal.org/home/gene/%s" +GENEBRIDGE_URL = "https://www.systems-genetics.org/modules_by_gene/%s?organism=%s" +GENEMANIA_URL = "https://genemania.org/search/%s/%s" +UCSC_REFSEQ = "http://genome.cse.ucsc.edu/cgi-bin/hgTracks?db=%s&hgg_gene=%s&hgg_chrom=chr%s&hgg_start=%s&hgg_end=%s" +BIOGPS_URL = "http://biogps.org/?org=%s#goto=genereport&id=%s" +STRING_URL = "http://string-db.org/newstring_cgi/show_network_section.pl?identifier=%s" +PANTHER_URL = "http://www.pantherdb.org/genes/geneList.do?searchType=basic&fieldName=all&organism=all&listType=1&fieldValue=%s" +GEMMA_URL = "http://www.chibi.ubc.ca/Gemma/gene/showGene.html?ncbiid=%s" +ABA_URL = "http://mouse.brain-map.org/search/show?search_type=gene&search_term=%s" +EBIGWAS_URL = "https://www.ebi.ac.uk/gwas/search?query=%s" +WIKI_PI_URL = "http://severus.dbmi.pitt.edu/wiki-pi/index.php/search?q=%s" +ENSEMBLETRANSCRIPT_URL = "http://useast.ensembl.org/Mus_musculus/Transcript/Idhistory?t=%s" +DBSNP = 'http://ensembl.org/Mus_musculus/Variation/Population?v=%s' +PROTEIN_ATLAS_URL = "http://www.proteinatlas.org/search/%s" +OPEN_TARGETS_URL = "https://genetics.opentargets.org/gene/%s" +UNIPROT_URL = "https://www.uniprot.org/uniprot/%s" +RGD_URL = "https://rgd.mcw.edu/rgdweb/elasticResults.html?term=%s&category=Gene&species=%s" +PHENOGEN_URL = "https://phenogen.org/gene.jsp?speciesCB=Rn&auto=Y&geneTxt=%s&genomeVer=rn7§ion=geneEQTL" +RRID_MOUSE_URL = "https://www.jax.org/strain/%s" +RRID_RAT_URL = "https://rgd.mcw.edu/rgdweb/report/strain/main.html?id=%s" + +# Temporary storage (note that this TMPDIR can be set as an +# environment variable - use utility.tools.TEMPDIR when you +# want to reach this base dir +assert_writable_dir(TEMPDIR) + +TMPDIR = mk_dir(TEMPDIR + '/gn2/') +assert_writable_dir(TMPDIR) + +CACHEDIR = mk_dir(TMPDIR + '/cache/') +# We can no longer write into the git tree: +GENERATED_IMAGE_DIR = mk_dir(TMPDIR + 'generated/') +GENERATED_TEXT_DIR = mk_dir(TMPDIR + 'generated_text/') + +# Make sure we have permissions to access these +assert_writable_dir(CACHEDIR) +assert_writable_dir(GENERATED_IMAGE_DIR) +assert_writable_dir(GENERATED_TEXT_DIR) + +# Flat file directories +GENODIR = flat_files('genotype') + '/' +assert_dir(GENODIR) +# assert_dir(GENODIR+'bimbam') # for gemma + +# JSON genotypes are OBSOLETE +JSON_GENODIR = flat_files('genotype/json') + '/' +if not valid_path(JSON_GENODIR): + # fall back on old location (move the dir, FIXME) + JSON_GENODIR = flat_files('json') + + +TEXTDIR = os.path.join(os.environ.get( + "GNSHARE", "/gnshare/gn/"), "web/ProbeSetFreeze_DataMatrix") +# Are we using the following...? +PORTADDR = "http://50.16.251.170" +INFOPAGEHREF = '/dbdoc/%s.html' +CGIDIR = '/webqtl/' # XZ: The variable name 'CGIDIR' should be changed to 'PYTHONDIR' diff --git a/gn2/db/__init__.py b/gn2/db/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/db/__init__.py diff --git a/gn2/db/webqtlDatabaseFunction.py b/gn2/db/webqtlDatabaseFunction.py new file mode 100644 index 00000000..9438c474 --- /dev/null +++ b/gn2/db/webqtlDatabaseFunction.py @@ -0,0 +1,45 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010) +# at rwilliams@uthsc.edu and xzhou15@uthsc.edu +# +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) + +from gn2.wqflask.database import database_connection + +from gn2.utility.tools import get_setting + + +def retrieve_species(group): + """Get the species of a group (e.g. returns string "mouse" on "BXD" + + """ + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT Species.Name FROM Species, InbredSet WHERE InbredSet.Name = %s AND InbredSet.SpeciesId = Species.Id", + (group,)) + results = cursor.fetchone() + if results and results[0]: + return results[0] + + +def retrieve_species_id(group): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute("SELECT SpeciesId FROM InbredSet WHERE Name = %s", + (group,)) + return cursor.fetchone()[0] diff --git a/gn2/gn2_main.py b/gn2/gn2_main.py new file mode 100644 index 00000000..38444620 --- /dev/null +++ b/gn2/gn2_main.py @@ -0,0 +1,19 @@ +"""Main application entry point.""" + +from gn2.wqflask import app + + +from gn2.wqflask import docs +from gn2.wqflask import gsearch +from gn2.wqflask import db_info +from gn2.wqflask import user_login +from gn2.wqflask.api import router +from gn2.wqflask import user_session +from gn2.wqflask import group_manager +from gn2.wqflask import export_traits +from gn2.wqflask import search_results +from gn2.wqflask import resource_manager +from gn2.wqflask import update_search_results + +import gn2.wqflask.views +import gn2.wqflask.partial_correlations_views diff --git a/gn2/jobs/__init__.py b/gn2/jobs/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/jobs/__init__.py diff --git a/gn2/jobs/jobs.py b/gn2/jobs/jobs.py new file mode 100644 index 00000000..f796fa9a --- /dev/null +++ b/gn2/jobs/jobs.py @@ -0,0 +1,74 @@ +"""Job management functions""" + +import sys +import json +import shlex +import subprocess +from uuid import UUID, uuid4 + +from redis import Redis +from pymonad.maybe import Maybe + +JOBS_NAMESPACE="gn2:jobs" # The namespace where jobs are kept + +class NoSuchJob(Exception): + """Raised if a given job does not exist""" + + def __init__(self, job_id: UUID): + """Initialise the exception object.""" + super().__init__(f"Could not find a job with the id '{job_id}'.") + +class InvalidJobCommand(Exception): + """Raised if the job command is invalid.""" + + def __init__(self, job_command: list[str]): + """Initialise the exception object.""" + super().__init__(f"The job command '{job_command}' is invalid.") + +def job_namespace(job_id: UUID): + return f"{JOBS_NAMESPACE}:{job_id}" + +def job(redis_conn: Redis, job_id: UUID): + job = redis_conn.hgetall(job_namespace(job_id)) + return Maybe(job, bool(job)) + +def status(the_job: Maybe) -> str: + return job.maybe("NOT-FOUND", lambda val: val.get("status", "NOT-FOUND")) + +def command(job: Maybe) -> list[str]: + return job.maybe( + ["NOT-FOUND"], lambda val: shlex.split(val.get("command", "NOT-FOUND"))) + +def __validate_command__(job_command): + try: + assert isinstance(job_command, list), "Not a list" + assert all((isinstance(val, str) for val in job_command)) + assert all((len(val) > 1 for val in job_command)) + except AssertionError as assert_err: + raise InvalidJobCommand(job_command) + +def queue(redis_conn: Redis, job: dict) -> UUID: + command = job["command"] + __validate_command__(command) + job_id = uuid4() + redis_conn.hset( + name=job_namespace(job_id), + mapping={"job_id": str(job_id), **job, "command": shlex.join(command)}) + return job_id + +def run(job_id: UUID, redis_uri: str): + command = [ + sys.executable, "-m", "scripts.run_external", + f"--redis-uri={redis_uri}", "run-job", str(job_id)] + print(f"COMMAND: {shlex.join(command)}") + subprocess.Popen(command) + +def completed_successfully(job): + return ( + job.get("status") == "completed" and + job.get("completion-status") == "success") + +def completed_erroneously(job): + return ( + job.get("status") == "completed" and + job.get("completion-status") == "error") diff --git a/gn2/maintenance/README.md b/gn2/maintenance/README.md new file mode 100644 index 00000000..873eaa32 --- /dev/null +++ b/gn2/maintenance/README.md @@ -0,0 +1,4 @@ +Maintenance files have been moved into a separate repository named +*gn_extra*. See https://github.com/genenetwork/gn_extra + + diff --git a/gn2/maintenance/__init__.py b/gn2/maintenance/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/maintenance/__init__.py diff --git a/gn2/maintenance/convert_dryad_to_bimbam.py b/gn2/maintenance/convert_dryad_to_bimbam.py new file mode 100644 index 00000000..18fbb8a1 --- /dev/null +++ b/gn2/maintenance/convert_dryad_to_bimbam.py @@ -0,0 +1,72 @@ +#!/usr/bin/python + +""" +Convert data dryad files to a BIMBAM _geno and _snps file + + +""" + +import sys +sys.path.append("..") + + +def read_dryad_file(filename): + exclude_count = 0 + marker_list = [] + sample_dict = {} + sample_list = [] + geno_rows = [] + with open(filename, 'r') as the_file: + for i, line in enumerate(the_file): + if i > 0: + if line.split(" ")[1] == "no": + sample_name = line.split(" ")[0] + sample_list.append(sample_name) + sample_dict[sample_name] = line.split(" ")[2:] + else: + exclude_count += 1 + else: + marker_list = line.split(" ")[2:] + + for i, marker in enumerate(marker_list): + this_row = [] + this_row.append(marker) + this_row.append("X") + this_row.append("Y") + for sample in sample_list: + this_row.append(sample_dict[sample][i]) + geno_rows.append(this_row) + + print(exclude_count) + + return geno_rows + + # for i, marker in enumerate(marker_list): + # this_row = [] + # this_row.append(marker) + # this_row.append("X") + # this_row.append("Y") + # with open(filename, 'r') as the_file: + # for j, line in enumerate(the_file): + # if j > 0: + # this_row.append(line.split(" ")[i+2]) + # print("row: " + str(i)) + # geno_rows.append(this_row) + # + # return geno_rows + + +def write_bimbam_files(geno_rows): + with open('/home/zas1024/cfw_data/CFW_geno.txt', 'w') as geno_fh: + for row in geno_rows: + geno_fh.write(", ".join(row) + "\n") + + +def convert_dryad_to_bimbam(filename): + geno_file_rows = read_dryad_file(filename) + write_bimbam_files(geno_file_rows) + + +if __name__ == "__main__": + input_filename = "/home/zas1024/cfw_data/" + sys.argv[1] + ".txt" + convert_dryad_to_bimbam(input_filename) diff --git a/gn2/maintenance/convert_geno_to_bimbam.py b/gn2/maintenance/convert_geno_to_bimbam.py new file mode 100644 index 00000000..078be529 --- /dev/null +++ b/gn2/maintenance/convert_geno_to_bimbam.py @@ -0,0 +1,201 @@ +#!/usr/bin/python + +""" +Convert .geno files to json + +This file goes through all of the genofiles in the genofile directory (.geno) +and converts them to json files that are used when running the marker regression +code + +""" + +import sys +sys.path.append("..") +import os +import glob +import traceback +import gzip + +import simplejson as json + +from pprint import pformat as pf + + +class EmptyConfigurations(Exception): + pass + + +class Marker: + def __init__(self): + self.name = None + self.chr = None + self.cM = None + self.Mb = None + self.genotypes = [] + + +class ConvertGenoFile: + + def __init__(self, input_file, output_files): + self.input_file = input_file + self.output_files = output_files + + self.mb_exists = False + self.cm_exists = False + self.markers = [] + + self.latest_row_pos = None + self.latest_col_pos = None + + self.latest_row_value = None + self.latest_col_value = None + + def convert(self): + self.haplotype_notation = { + '@mat': "1", + '@pat': "0", + '@het': "0.5", + '@unk': "NA" + } + + self.configurations = {} + self.input_fh = open(self.input_file) + + self.process_csv() + + def process_csv(self): + for row in self.process_rows(): + row_items = row.split("\t") + + this_marker = Marker() + this_marker.name = row_items[1] + this_marker.chr = row_items[0] + if self.cm_exists and self.mb_exists: + this_marker.cM = row_items[2] + this_marker.Mb = row_items[3] + genotypes = row_items[4:] + elif self.cm_exists: + this_marker.cM = row_items[2] + genotypes = row_items[3:] + elif self.mb_exists: + this_marker.Mb = row_items[2] + genotypes = row_items[3:] + else: + genotypes = row_items[2:] + for item_count, genotype in enumerate(genotypes): + if genotype.upper().strip() in self.configurations: + this_marker.genotypes.append( + self.configurations[genotype.upper().strip()]) + else: + this_marker.genotypes.append("NA") + + self.markers.append(this_marker.__dict__) + + self.write_to_bimbam() + + def write_to_bimbam(self): + with open(self.output_files[0], "w") as geno_fh: + for marker in self.markers: + geno_fh.write(marker['name']) + geno_fh.write(", X, Y") + geno_fh.write(", " + ", ".join(marker['genotypes'])) + geno_fh.write("\n") + + with open(self.output_files[1], "w") as pheno_fh: + for sample in self.sample_list: + pheno_fh.write("1\n") + + with open(self.output_files[2], "w") as snp_fh: + for marker in self.markers: + if self.mb_exists: + snp_fh.write( + marker['name'] + ", " + str(int(float(marker['Mb']) * 1000000)) + ", " + marker['chr'] + "\n") + else: + snp_fh.write( + marker['name'] + ", " + str(int(float(marker['cM']) * 1000000)) + ", " + marker['chr'] + "\n") + + def get_sample_list(self, row_contents): + self.sample_list = [] + if self.mb_exists: + if self.cm_exists: + self.sample_list = row_contents[4:] + else: + self.sample_list = row_contents[3:] + else: + if self.cm_exists: + self.sample_list = row_contents[3:] + else: + self.sample_list = row_contents[2:] + + def process_rows(self): + for self.latest_row_pos, row in enumerate(self.input_fh): + self.latest_row_value = row + # Take care of headers + if not row.strip(): + continue + if row.startswith('#'): + continue + if row.startswith('Chr'): + if 'Mb' in row.split(): + self.mb_exists = True + if 'cM' in row.split(): + self.cm_exists = True + self.get_sample_list(row.split()) + continue + if row.startswith('@'): + key, _separater, value = row.partition(':') + key = key.strip() + value = value.strip() + if key == "@filler": + raise EmptyConfigurations + if key in self.haplotype_notation: + self.configurations[value] = self.haplotype_notation[key] + continue + if not len(self.configurations): + raise EmptyConfigurations + yield row + + @classmethod + def process_all(cls, old_directory, new_directory): + os.chdir(old_directory) + for input_file in glob.glob("*"): + if not input_file.endswith(('geno', '.geno.gz')): + continue + group_name = ".".join(input_file.split('.')[:-1]) + if group_name == "HSNIH-Palmer": + continue + geno_output_file = os.path.join( + new_directory, group_name + "_geno.txt") + pheno_output_file = os.path.join( + new_directory, group_name + "_pheno.txt") + snp_output_file = os.path.join( + new_directory, group_name + "_snps.txt") + output_files = [geno_output_file, + pheno_output_file, snp_output_file] + print("%s -> %s" % ( + os.path.join(old_directory, input_file), geno_output_file)) + convertob = ConvertGenoFile(input_file, output_files) + try: + convertob.convert() + except EmptyConfigurations as why: + print(" No config info? Continuing...") + continue + except Exception as why: + print(" Exception:", why) + print(traceback.print_exc()) + print(" Found in row %s at tabular column %s" % (convertob.latest_row_pos, + convertob.latest_col_pos)) + print(" Column is:", convertob.latest_col_value) + print(" Row is:", convertob.latest_row_value) + break + + +if __name__ == "__main__": + Old_Geno_Directory = """/export/local/home/zas1024/gn2-zach/genotype_files/genotype""" + New_Geno_Directory = """/export/local/home/zas1024/gn2-zach/genotype_files/genotype/bimbam""" + #Input_File = """/home/zas1024/gene/genotype_files/genotypes/BXD.geno""" + #Output_File = """/home/zas1024/gene/wqflask/wqflask/pylmm/data/bxd.snps""" + #convertob = ConvertGenoFile("/home/zas1024/gene/genotype_files/genotypes/SRxSHRSPF2.geno", "/home/zas1024/gene/genotype_files/new_genotypes/SRxSHRSPF2.json") + # convertob.convert() + ConvertGenoFile.process_all(Old_Geno_Directory, New_Geno_Directory) + # ConvertGenoFiles(Geno_Directory) diff --git a/gn2/maintenance/gen_ind_genofiles.py b/gn2/maintenance/gen_ind_genofiles.py new file mode 100644 index 00000000..b755c648 --- /dev/null +++ b/gn2/maintenance/gen_ind_genofiles.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +"""A script that generates the genotype files for groups of individuals, using an existing strain genotype file as a basis + +Example commands: +python3 gen_ind_genofiles.py + /home/zas1024/gn2-zach/genotype_files/genotype/ + /home/zas1024/gn2-zach/new_geno/ + BXD-Micturition.geno + BXD.json +python3 gen_ind_genofiles.py + /home/zas1024/gn2-zach/genotype_files/genotype + /home/zas1024/gn2-zach/new_geno/ + BXD-Micturition.geno + BXD.2.geno BXD.4.geno BXD.5.geno + +""" + +import json +import os +import sys +from typing import List + +import MySQLdb + +def conn(): + return MySQLdb.Connect(db=os.environ.get("DB_NAME"), + user=os.environ.get("DB_USER"), + passwd=os.environ.get("DB_PASS"), + host=os.environ.get("DB_HOST")) + +def main(args): + + # Directory in which .geno files are located + geno_dir = args[1] + + # Directory in which to output new files + out_dir = args[2] + + # The individuals group that we want to generate a .geno file for + target_file = geno_dir + args[3] + + # The source group(s) we're generating the .geno files from + # This can be passed as either a specific .geno file (or set of files as multiple arguments), + # or as a JSON file containing a set of .geno files (and their corresponding file names and sample lists) + geno_json = {} + source_files = [] + if ".json" in args[4]: + geno_json = json.load(open(geno_dir + args[4], "r")) + par_f1s = { + "mat": geno_json['mat'], + "pat": geno_json['pat'], + "f1s": geno_json['f1s'] + } + + # List of file titles and locations from JSON + source_files = [{'title': genofile['title'], 'location': geno_dir + genofile['location']} for genofile in geno_json['genofile']] + else: + par_f1s = {} + # List of files directly taken from command line arguments, with titles just set to the filename + for group in args[4:]: + file_name = geno_dir + group + ".geno" if ".geno" not in group else geno_dir + group + source_files.append({'title': file_name[:-5], 'location': file_name}) + + if len(source_files) > 1: + # Generate a JSON file pointing to the new target genotype files, in situations where there are multiple source .geno files + target_json_loc = out_dir + ".".join(args[3].split(".")[:-1]) + ".json" + target_json = {'genofile': []} + + # Generate the output .geno files + for source_file in source_files: + filename, samples = generate_new_genofile(source_file['location'], target_file, par_f1s, out_dir) + + target_json['genofile'].append({ + 'location': filename.split("/")[-1], + 'title': source_file['title'], + 'sample_list': samples + }) + + json.dump(target_json, open(target_json_loc, "w")) + else: + filename, samples = generate_new_genofile(source_files[0]['location'], target_file, par_f1s, out_dir) + +def get_strain_for_sample(sample): + query = ( + "SELECT CaseAttributeXRefNew.Value " + "FROM CaseAttributeXRefNew, Strain " + "WHERE CaseAttributeXRefNew.CaseAttributeId=11 " + "AND CaseAttributeXRefNew.StrainId = Strain.Id " + "AND Strain.Name = %(name)s" ) + + with conn().cursor() as cursor: + cursor.execute(query, {"name": sample.strip()}) + strain = cursor.fetchone()[0] + return strain + +def generate_new_genofile(source_genofile, target_genofile, par_f1s, out_dir): + source_samples = group_samples(source_genofile) + source_genotypes = strain_genotypes(source_genofile) + target_samples = group_samples(target_genofile) + strain_pos_map = map_strain_pos_to_target_group(source_samples, target_samples, par_f1s) + + if len(source_genofile.split("/")[-1].split(".")) > 2: + # The number in the source genofile; for example 4 in BXD.4.geno + source_num = source_genofile.split("/")[-1].split(".")[-2] + target_filename = ".".join(target_genofile.split("/")[-1].split(".")[:-1]) + "." + source_num + ".geno" + else: + target_filename = ".".join(target_genofile.split("/")[-1].split(".")[:-1]) + ".geno" + + file_location = out_dir + target_filename + + with open(file_location, "w") as fh: + for metadata in ["name", "type", "mat", "pat", "het", "unk"]: + fh.write("@" + metadata + ":" + source_genotypes[metadata] + "\n") + + header_line = ["Chr", "Locus", "cM", "Mb"] + target_samples + fh.write("\t".join(header_line) + "\n") + + for marker in source_genotypes['markers']: + line_items = [ + marker['Chr'], + marker['Locus'], + marker['cM'], + marker['Mb'] + ] + + for pos in strain_pos_map: + if isinstance(pos, int): + line_items.append(marker['genotypes'][pos]) + else: + if pos in ["mat", "pat"]: + line_items.append(source_genotypes[pos]) + elif pos == "f1s": + line_items.append("H") + else: + line_items.append("U") + + fh.write("\t".join(line_items) + "\n") + + return file_location, target_samples + +def map_strain_pos_to_target_group(source_samples, target_samples, par_f1s): + """ + Retrieve corresponding strain position for each sample in the target group + + This is so the genotypes from the base genofile can be mapped to the samples in the target group + + For example: + Base strains: BXD1, BXD2, BXD3 + Target samples: BXD1_1, BXD1_2, BXD2_1, BXD3_1, BXD3_2, BXD3_3 + Returns: [0, 0, 1, 2, 2, 2] + """ + pos_map = [] + for sample in target_samples: + sample_strain = get_strain_for_sample(sample) + if sample_strain in source_samples: + pos_map.append(source_samples.index(sample_strain)) + else: + val = "U" + for key in par_f1s.keys(): + if sample_strain in par_f1s[key]: + val = key + pos_map.append(val) + + return pos_map + +def group_samples(target_file: str) -> List: + """ + Get the group samples from its "dummy" .geno file (which still contains the sample list) + """ + + sample_list = [] + with open(target_file, "r") as target_geno: + for i, line in enumerate(target_geno): + # Skip header lines + if line[0] in ["#", "@"] or not len(line): + continue + + line_items = line.split() + + sample_list = [item for item in line_items if item not in ["Chr", "Locus", "Mb", "cM"]] + break + + return sample_list + +def strain_genotypes(strain_genofile: str) -> List: + """ + Read genotypes from source strain .geno file + + :param strain_genofile: string of genofile filename + :return: a list of dictionaries representing each marker's genotypes + + Example output: [ + { + 'Chr': '1', + 'Locus': 'marker1', + 'Mb': '10.0', + 'cM': '8.0', + 'genotypes': [('BXD1', 'B'), ('BXD2', 'D'), ('BXD3', 'H'), ...] + }, + ... + ] + """ + + geno_dict = {} + + geno_start_col = None + header_columns = [] + sample_list = [] + markers = [] + with open(strain_genofile, "r") as source_geno: + for i, line in enumerate(source_geno): + if line[0] == "@": + metadata_type = line[1:].split(":")[0] + if metadata_type in ['name', 'type', 'mat', 'pat', 'het', 'unk']: + geno_dict[metadata_type] = line.split(":")[1].strip() + + continue + + # Skip other header lines + if line[0] == "#" or not len(line): + continue + + line_items = line.split("\t") + if "Chr" in line_items: # Header row + # Get the first column index containing genotypes + header_columns = line_items + for j, item in enumerate(line_items): + if item not in ["Chr", "Locus", "Mb", "cM"]: + geno_start_col = j + break + + sample_list = line_items[geno_start_col:] + if not geno_start_col: + print("Check .geno file - expected columns not found") + sys.exit() + else: # Marker rows + this_marker = { + 'Chr': line_items[header_columns.index("Chr")], + 'Locus': line_items[header_columns.index("Locus")], + 'Mb': line_items[header_columns.index("Mb")], + 'cM': line_items[header_columns.index("cM")], + 'genotypes': [item.strip() for item in line_items][geno_start_col:] + } + + markers.append(this_marker) + + geno_dict['markers'] = markers + + return geno_dict + +if __name__ == "__main__": + main(sys.argv) + diff --git a/gn2/maintenance/gen_select_dataset.py b/gn2/maintenance/gen_select_dataset.py new file mode 100644 index 00000000..5f41da29 --- /dev/null +++ b/gn2/maintenance/gen_select_dataset.py @@ -0,0 +1,296 @@ +"""Script that generates the data for the main dropdown menus on the home page + +Writes out data as /static/new/javascript/dataset_menu_structure.json +It needs to be run manually when database has been changed. Run it as + + ./bin/genenetwork2 ~/my_settings.py -c ./wqflask/maintenance/gen_select_dataset.py + +""" + + +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams +# at rwilliams@uthsc.edu +# +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) + +import sys + +# NEW: Note we prepend the current path - otherwise a guix instance of GN2 may be used instead +sys.path.insert(0, './') +# NEW: import app to avoid a circular dependency on utility.tools +from gn2.wqflask import app + +from gn2.utility.tools import get_setting + +import simplejson as json +import urllib.parse + + +from pprint import pformat as pf + +from gn2.wqflask.database import database_connection + + +def get_species(cursor): + """Build species list""" + #cursor.execute("select Name, MenuName from Species where Species.Name != 'macaque monkey' order by OrderId") + cursor.execute("select Name, MenuName from Species order by OrderId") + species = list(cursor.fetchall()) + return species + + +def get_groups(cursor, species): + """Build groups list""" + groups = {} + for species_name, _species_full_name in species: + cursor.execute("""select InbredSet.Name, InbredSet.FullName from InbredSet, + Species, + ProbeFreeze, GenoFreeze, PublishFreeze where Species.Name = '%s' + and InbredSet.SpeciesId = Species.Id and + (PublishFreeze.InbredSetId = InbredSet.Id + or GenoFreeze.InbredSetId = InbredSet.Id + or ProbeFreeze.InbredSetId = InbredSet.Id) + group by InbredSet.Name + order by InbredSet.FullName""" % species_name) + results = cursor.fetchall() + groups[species_name] = list(results) + return groups + + +def get_types(groups): + """Build types list""" + types = {} + #print("Groups: ", pf(groups)) + for species, group_dict in list(groups.items()): + types[species] = {} + for group_name, _group_full_name in group_dict: + # make group an alias to shorten the code + #types[species][group_name] = [("Phenotypes", "Phenotypes"), ("Genotypes", "Genotypes")] + if phenotypes_exist(group_name): + types[species][group_name] = [("Phenotypes", "Phenotypes")] + if genotypes_exist(group_name): + if group_name in types[species]: + types[species][group_name] += [("Genotypes", "Genotypes")] + else: + types[species][group_name] = [("Genotypes", "Genotypes")] + if group_name in types[species]: + types_list = build_types(species, group_name) + if len(types_list) > 0: + types[species][group_name] += types_list + else: + if not phenotypes_exist(group_name) and not genotypes_exist(group_name): + types[species].pop(group_name, None) + groups[species] = tuple( + group for group in groups[species] if group[0] != group_name) + else: # ZS: This whole else statement might be unnecessary, need to check + types_list = build_types(species, group_name) + if len(types_list) > 0: + types[species][group_name] = types_list + else: + types[species].pop(group_name, None) + groups[species] = tuple( + group for group in groups[species] if group[0] != group_name) + return types + + +def phenotypes_exist(group_name): + #print("group_name:", group_name) + Cursor.execute("""select Name from PublishFreeze + where PublishFreeze.Name = '%s'""" % (group_name + "Publish")) + + results = Cursor.fetchone() + #print("RESULTS:", results) + + if results != None: + return True + else: + return False + + +def genotypes_exist(group_name): + #print("group_name:", group_name) + Cursor.execute("""select Name from GenoFreeze + where GenoFreeze.Name = '%s'""" % (group_name + "Geno")) + + results = Cursor.fetchone() + #print("RESULTS:", results) + + if results != None: + return True + else: + return False + + +def build_types(species, group): + """Fetches tissues + + Gets the tissues with data for this species/group + (all types except phenotype/genotype are tissues) + + """ + + Cursor.execute("""select distinct Tissue.Name + from ProbeFreeze, ProbeSetFreeze, InbredSet, Tissue, Species + where Species.Name = '%s' and Species.Id = InbredSet.SpeciesId and + InbredSet.Name = '%s' and + ProbeFreeze.TissueId = Tissue.Id and + ProbeFreeze.InbredSetId = InbredSet.Id and + ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id and + ProbeSetFreeze.public > 0 and + ProbeSetFreeze.confidentiality < 1 + order by Tissue.Name""" % (species, group)) + + results = [] + for result in Cursor.fetchall(): + if len(result): + these_datasets = build_datasets(species, group, result[0]) + if len(these_datasets) > 0: + results.append((result[0], result[0])) + + return results + + +def get_datasets(types): + """Build datasets list""" + datasets = {} + for species, group_dict in list(types.items()): + datasets[species] = {} + for group, type_list in list(group_dict.items()): + datasets[species][group] = {} + for type_name in type_list: + these_datasets = build_datasets(species, group, type_name[0]) + if len(these_datasets) > 0: + datasets[species][group][type_name[0]] = these_datasets + + return datasets + + +def build_datasets(species, group, type_name): + """Gets dataset names from database""" + dataset_text = dataset_value = None + datasets = [] + if type_name == "Phenotypes": + Cursor.execute("""select InfoFiles.GN_AccesionId, PublishFreeze.Name, PublishFreeze.FullName from InfoFiles, PublishFreeze, InbredSet where + InbredSet.Name = '%s' and + PublishFreeze.InbredSetId = InbredSet.Id and + InfoFiles.InfoPageName = PublishFreeze.Name order by + PublishFreeze.CreateTime asc""" % group) + + results = Cursor.fetchall() + if len(results) > 0: + for result in results: + print(result) + dataset_id = str(result[0]) + dataset_value = str(result[1]) + if group == 'MDP': + dataset_text = "Mouse Phenome Database" + else: + #dataset_text = "%s Phenotypes" % group + dataset_text = str(result[2]) + datasets.append((dataset_id, dataset_value, dataset_text)) + else: + dataset_id = "None" + dataset_value = "%sPublish" % group + dataset_text = "%s Phenotypes" % group + datasets.append((dataset_id, dataset_value, dataset_text)) + + elif type_name == "Genotypes": + Cursor.execute("""select InfoFiles.GN_AccesionId from InfoFiles, GenoFreeze, InbredSet where + InbredSet.Name = '%s' and + GenoFreeze.InbredSetId = InbredSet.Id and + InfoFiles.InfoPageName = GenoFreeze.ShortName and + GenoFreeze.public > 0 and + GenoFreeze.confidentiality < 1 order by + GenoFreeze.CreateTime desc""" % group) + + results = Cursor.fetchone() + if results != None: + dataset_id = str(results[0]) + else: + dataset_id = "None" + dataset_value = "%sGeno" % group + dataset_text = "%s Genotypes" % group + datasets.append((dataset_id, dataset_value, dataset_text)) + + else: # for mRNA expression/ProbeSet + Cursor.execute("""select ProbeSetFreeze.Id, ProbeSetFreeze.Name, ProbeSetFreeze.FullName from + ProbeSetFreeze, ProbeFreeze, InbredSet, Tissue, Species where + Species.Name = '%s' and Species.Id = InbredSet.SpeciesId and + InbredSet.Name = '%s' and + ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id and Tissue.Name = '%s' and + ProbeFreeze.TissueId = Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id and + ProbeSetFreeze.confidentiality < 1 and ProbeSetFreeze.public > 0 order by + ProbeSetFreeze.CreateTime desc""" % (species, group, type_name)) + + dataset_results = Cursor.fetchall() + datasets = [] + for dataset_info in dataset_results: + this_dataset_info = [] + for info in dataset_info: + this_dataset_info.append(str(info)) + datasets.append(this_dataset_info) + + return datasets + + +def main(cursor): + """Generates and outputs (as json file) the data for the main dropdown menus on the home page""" + + species = get_species(cursor) + groups = get_groups(cursor, species) + types = get_types(groups) + datasets = get_datasets(types) + + #species.append(('All Species', 'All Species')) + #groups['All Species'] = [('All Groups', 'All Groups')] + #types['All Species'] = {} + #types['All Species']['All Groups'] = [('Phenotypes', 'Phenotypes')] + #datasets['All Species'] = {} + #datasets['All Species']['All Groups'] = {} + #datasets['All Species']['All Groups']['Phenotypes'] = [('All Phenotypes','All Phenotypes')] + + data = dict(species=species, + groups=groups, + types=types, + datasets=datasets, + ) + + #print("data:", data) + + output_file = """./wqflask/static/new/javascript/dataset_menu_structure.json""" + + with open(output_file, 'w') as fh: + json.dump(data, fh, indent=" ", sort_keys=True) + + #print("\nWrote file to:", output_file) + + +def _test_it(): + """Used for internal testing only""" + types = build_types("Mouse", "BXD") + #print("build_types:", pf(types)) + datasets = build_datasets("Mouse", "BXD", "Hippocampus") + #print("build_datasets:", pf(datasets)) + + +if __name__ == '__main__': + with database_connection(get_setting("SQL_URI")) as conn: + with conn.cursor() as cursor: + main(cursor) diff --git a/gn2/maintenance/generate_kinship_from_bimbam.py b/gn2/maintenance/generate_kinship_from_bimbam.py new file mode 100644 index 00000000..9f01d094 --- /dev/null +++ b/gn2/maintenance/generate_kinship_from_bimbam.py @@ -0,0 +1,66 @@ +#!/usr/bin/python + +""" +Generate relatedness matrix files for GEMMA from BIMBAM genotype/phenotype files + +This file goes through all of the BIMBAM files in the bimbam diretory +and uses GEMMA to generate their corresponding kinship/relatedness matrix file + +""" + +import sys +sys.path.append("..") +import os +import glob + + +class GenerateKinshipMatrices: + def __init__(self, group_name, geno_file, pheno_file): + self.group_name = group_name + self.geno_file = geno_file + self.pheno_file = pheno_file + + def generate_kinship(self): + gemma_command = "/gnu/store/xhzgjr0jvakxv6h3blj8z496xjig69b0-profile/bin/gemma -g " + self.geno_file + \ + " -p " + self.pheno_file + \ + " -gk 1 -outdir /home/zas1024/genotype_files/genotype/bimbam/ -o " + self.group_name + print("command:", gemma_command) + os.system(gemma_command) + + @classmethod + def process_all(self, geno_dir, bimbam_dir): + os.chdir(geno_dir) + for input_file in glob.glob("*"): + if not input_file.endswith(('geno', '.geno.gz')): + continue + group_name = ".".join(input_file.split('.')[:-1]) + if group_name == "HSNIH-Palmer": + continue + geno_input_file = os.path.join( + bimbam_dir, group_name + "_geno.txt") + pheno_input_file = os.path.join( + bimbam_dir, group_name + "_pheno.txt") + convertob = GenerateKinshipMatrices( + group_name, geno_input_file, pheno_input_file) + try: + convertob.generate_kinship() + except EmptyConfigurations as why: + print(" No config info? Continuing...") + continue + except Exception as why: + + print(" Exception:", why) + print(traceback.print_exc()) + print(" Found in row %s at tabular column %s" % (convertob.latest_row_pos, + convertob.latest_col_pos)) + print(" Column is:", convertob.latest_col_value) + print(" Row is:", convertob.latest_row_value) + break + + +if __name__ == "__main__": + Geno_Directory = """/export/local/home/zas1024/genotype_files/genotype/""" + Bimbam_Directory = """/export/local/home/zas1024/genotype_files/genotype/bimbam/""" + GenerateKinshipMatrices.process_all(Geno_Directory, Bimbam_Directory) + + # ./gemma -g /home/zas1024/genotype_files/genotype/bimbam/BXD_geno.txt -p /home/zas1024/genotype_files/genotype/bimbam/BXD_pheno.txt -gk 1 -o BXD diff --git a/gn2/maintenance/generate_probesetfreeze_file.py b/gn2/maintenance/generate_probesetfreeze_file.py new file mode 100644 index 00000000..00c2cddf --- /dev/null +++ b/gn2/maintenance/generate_probesetfreeze_file.py @@ -0,0 +1,122 @@ +#!/usr/bin/python + +import sys + +# sys.path.insert(0, "..") - why? + +import os +import collections +import csv + +from gn2.base import webqtlConfig + +from pprint import pformat as pf + +from gn2.utility.tools import get_setting +from gn2.wqflask.database import database_connection + + +def show_progress(process, counter): + if counter % 1000 == 0: + print("{}: {}".format(process, counter)) + + +def get_strains(cursor): + cursor.execute("""select Strain.Name + from Strain, StrainXRef, InbredSet + where Strain.Id = StrainXRef.StrainId and + StrainXRef.InbredSetId = InbredSet.Id + and InbredSet.Name=%s; + """, "BXD") + + strains = [strain[0] for strain in cursor.fetchall()] + print("strains:", pf(strains)) + for strain in strains: + print(" -", strain) + + return strains + + +def get_probeset_vals(cursor, dataset_name): + cursor.execute(""" select ProbeSet.Id, ProbeSet.Name + from ProbeSetXRef, + ProbeSetFreeze, + ProbeSet + where ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id and + ProbeSetFreeze.Name = %s and + ProbeSetXRef.ProbeSetId = ProbeSet.Id; + """, dataset_name) + + probesets = cursor.fetchall() + + print("Fetched probesets") + + probeset_vals = collections.OrderedDict() + + for counter, probeset in enumerate(probesets): + cursor.execute(""" select Strain.Name, ProbeSetData.value + from ProbeSetData, ProbeSetXRef, ProbeSetFreeze, Strain + where ProbeSetData.Id = ProbeSetXRef.DataId + and ProbeSetData.StrainId = Strain.Id + and ProbeSetXRef.ProbeSetId = %s + and ProbeSetFreeze.Id = ProbeSetXRef.ProbeSetFreezeId + and ProbeSetFreeze.Name = %s; + """, (probeset[0], dataset_name)) + val_dic = collections.OrderedDict() + vals = cursor.fetchall() + for val in vals: + val_dic[val[0]] = val[1] + + probeset_vals[probeset[1]] = val_dic + show_progress("Querying DB", counter) + + return probeset_vals + + +def trim_strains(strains, probeset_vals): + trimmed_strains = [] + #print("probeset_vals is:", pf(probeset_vals)) + first_probeset = list(probeset_vals.values())[0] + print("\n**** first_probeset is:", pf(first_probeset)) + for strain in strains: + print("\n**** strain is:", pf(strain)) + if strain in first_probeset: + trimmed_strains.append(strain) + print("trimmed_strains:", pf(trimmed_strains)) + return trimmed_strains + + +def write_data_matrix_file(strains, probeset_vals, filename): + with open(filename, "wb") as fh: + csv_writer = csv.writer(fh, delimiter=",", quoting=csv.QUOTE_ALL) + #print("strains is:", pf(strains)) + csv_writer.writerow(['ID'] + strains) + for counter, probeset in enumerate(probeset_vals): + row_data = [probeset] + for strain in strains: + #print("probeset is: ", pf(probeset_vals[probeset])) + row_data.append(probeset_vals[probeset][strain]) + #print("row_data is: ", pf(row_data)) + csv_writer.writerow(row_data) + show_progress("Writing", counter) + + +def main(): + filename = os.path.expanduser( + "~/gene/wqflask/maintenance/" + "ProbeSetFreezeId_210_FullName_Eye_AXBXA_Illumina_V6.2" + "(Oct08)_RankInv_Beta.txt") + dataset_name = "Eye_AXBXA_1008_RankInv" + + with database_connection(get_setting("SQL_URI")) as conn: + with conn.cursor() as cursor: + strains = get_strains(cursor) + print("Getting probset_vals") + probeset_vals = get_probeset_vals(cursor, dataset_name) + print("Finished getting probeset_vals") + trimmed_strains = trim_strains(strains, probeset_vals) + write_data_matrix_file(trimmed_strains, probeset_vals, filename) + + +if __name__ == '__main__': + main() diff --git a/gn2/maintenance/geno_to_json.py b/gn2/maintenance/geno_to_json.py new file mode 100644 index 00000000..7be2ed83 --- /dev/null +++ b/gn2/maintenance/geno_to_json.py @@ -0,0 +1,196 @@ +#!/usr/bin/python + +""" +Convert .geno files to json + +This file goes through all of the genofiles in the genofile directory (.geno) +and converts them to json files that are used when running the marker regression +code + +""" + +import sys +sys.path.append("..") +import os +import glob +import traceback +import gzip + +#import numpy as np +#from pyLMM import lmm + +import simplejson as json + +from pprint import pformat as pf + +#from gn2.utility.tools import flat_files + + +class EmptyConfigurations(Exception): + pass + + +class Marker: + def __init__(self): + self.name = None + self.chr = None + self.cM = None + self.Mb = None + self.genotypes = [] + + +class ConvertGenoFile: + + def __init__(self, input_file, output_file): + + self.input_file = input_file + self.output_file = output_file + + self.mb_exists = False + self.cm_exists = False + self.markers = [] + + self.latest_row_pos = None + self.latest_col_pos = None + + self.latest_row_value = None + self.latest_col_value = None + + def convert(self): + + self.haplotype_notation = { + '@mat': "1", + '@pat': "0", + '@het': "0.5", + '@unk': "NA" + } + + self.configurations = {} + #self.skipped_cols = 3 + + # if self.input_file.endswith(".geno.gz"): + # print("self.input_file: ", self.input_file) + # self.input_fh = gzip.open(self.input_file) + # else: + self.input_fh = open(self.input_file) + + with open(self.output_file, "w") as self.output_fh: + # if self.file_type == "geno": + self.process_csv() + # elif self.file_type == "snps": + # self.process_snps_file() + + def process_csv(self): + for row_count, row in enumerate(self.process_rows()): + row_items = row.split("\t") + + this_marker = Marker() + this_marker.name = row_items[1] + this_marker.chr = row_items[0] + if self.cm_exists and self.mb_exists: + this_marker.cM = row_items[2] + this_marker.Mb = row_items[3] + genotypes = row_items[4:] + elif self.cm_exists: + this_marker.cM = row_items[2] + genotypes = row_items[3:] + elif self.mb_exists: + this_marker.Mb = row_items[2] + genotypes = row_items[3:] + else: + genotypes = row_items[2:] + for item_count, genotype in enumerate(genotypes): + if genotype.upper() in self.configurations: + this_marker.genotypes.append( + self.configurations[genotype.upper()]) + else: + this_marker.genotypes.append("NA") + + #print("this_marker is:", pf(this_marker.__dict__)) + # if this_marker.chr == "14": + self.markers.append(this_marker.__dict__) + + with open(self.output_file, 'w') as fh: + json.dump(self.markers, fh, indent=" ", sort_keys=True) + + # print('configurations:', str(configurations)) + #self.latest_col_pos = item_count + self.skipped_cols + #self.latest_col_value = item + + # if item_count != 0: + # self.output_fh.write(" ") + # self.output_fh.write(self.configurations[item.upper()]) + + # self.output_fh.write("\n") + + def process_rows(self): + for self.latest_row_pos, row in enumerate(self.input_fh): + # if self.input_file.endswith(".geno.gz"): + # print("row: ", row) + self.latest_row_value = row + # Take care of headers + if not row.strip(): + continue + if row.startswith('#'): + continue + if row.startswith('Chr'): + if 'Mb' in row.split(): + self.mb_exists = True + if 'cM' in row.split(): + self.cm_exists = True + continue + if row.startswith('@'): + key, _separater, value = row.partition(':') + key = key.strip() + value = value.strip() + if key in self.haplotype_notation: + self.configurations[value] = self.haplotype_notation[key] + continue + if not len(self.configurations): + raise EmptyConfigurations + yield row + + @classmethod + def process_all(cls, old_directory, new_directory): + os.chdir(old_directory) + for input_file in glob.glob("*"): + if not input_file.endswith(('geno', '.geno.gz')): + continue + group_name = ".".join(input_file.split('.')[:-1]) + output_file = os.path.join(new_directory, group_name + ".json") + print("%s -> %s" % ( + os.path.join(old_directory, input_file), output_file)) + convertob = ConvertGenoFile(input_file, output_file) + try: + convertob.convert() + except EmptyConfigurations as why: + print(" No config info? Continuing...") + #excepted = True + continue + except Exception as why: + + print(" Exception:", why) + print(traceback.print_exc()) + print(" Found in row %s at tabular column %s" % (convertob.latest_row_pos, + convertob.latest_col_pos)) + print(" Column is:", convertob.latest_col_value) + print(" Row is:", convertob.latest_row_value) + break + + # def process_snps_file(cls, snps_file, new_directory): + # output_file = os.path.join(new_directory, "mouse_families.json") + # print("%s -> %s" % (snps_file, output_file)) + # convertob = ConvertGenoFile(input_file, output_file) + + +if __name__ == "__main__": + Old_Geno_Directory = """/export/local/home/zas1024/gn2-zach/genotype_files/genotype""" + New_Geno_Directory = """/export/local/home/zas1024/gn2-zach/genotype_files/genotype/json""" + #Input_File = """/home/zas1024/gene/genotype_files/genotypes/BXD.geno""" + #Output_File = """/home/zas1024/gene/wqflask/wqflask/pylmm/data/bxd.snps""" + #convertob = ConvertGenoFile("/home/zas1024/gene/genotype_files/genotypes/SRxSHRSPF2.geno", "/home/zas1024/gene/genotype_files/new_genotypes/SRxSHRSPF2.json") + # convertob.convert() + ConvertGenoFile.process_all(Old_Geno_Directory, New_Geno_Directory) + # ConvertGenoFiles(Geno_Directory) + + #process_csv(Input_File, Output_File) diff --git a/gn2/maintenance/get_group_samplelists.py b/gn2/maintenance/get_group_samplelists.py new file mode 100644 index 00000000..6af637ea --- /dev/null +++ b/gn2/maintenance/get_group_samplelists.py @@ -0,0 +1,47 @@ +import os +import glob +import gzip + +from gn2.base import webqtlConfig + + +def get_samplelist(file_type, geno_file): + if file_type == "geno": + return get_samplelist_from_geno(geno_file) + elif file_type == "plink": + return get_samplelist_from_plink(geno_file) + + +def get_samplelist_from_geno(genofilename): + if os.path.isfile(genofilename + '.gz'): + genofilename += '.gz' + genofile = gzip.open(genofilename) + else: + genofile = open(genofilename) + + for line in genofile: + line = line.strip() + if not line: + continue + if line.startswith(("#", "@")): + continue + break + + headers = line.split("\t") + + if headers[3] == "Mb": + samplelist = headers[4:] + else: + samplelist = headers[3:] + return samplelist + + +def get_samplelist_from_plink(genofilename): + genofile = open(genofilename) + + samplelist = [] + for line in genofile: + line = line.split(" ") + samplelist.append(line[1]) + + return samplelist diff --git a/gn2/maintenance/print_benchmark.py b/gn2/maintenance/print_benchmark.py new file mode 100644 index 00000000..9d12da8a --- /dev/null +++ b/gn2/maintenance/print_benchmark.py @@ -0,0 +1,45 @@ +#!/usr/bin/python + +import time + +from pprint import pformat as pf + + +class TheCounter: + Counters = {} + + def __init__(self): + start_time = time.time() + for counter in range(170000): + self.print_it(counter) + self.time_took = time.time() - start_time + TheCounter.Counters[self.__class__.__name__] = self.time_took + + +class PrintAll(TheCounter): + def print_it(self, counter): + print(counter) + + +class PrintSome(TheCounter): + def print_it(self, counter): + if counter % 1000 == 0: + print(counter) + + +class PrintNone(TheCounter): + def print_it(self, counter): + pass + + +def new_main(): + print("Running new_main") + tests = [PrintAll, PrintSome, PrintNone] + for test in tests: + test() + + print(pf(TheCounter.Counters)) + + +if __name__ == '__main__': + new_main() diff --git a/gn2/maintenance/quantile_normalize.py b/gn2/maintenance/quantile_normalize.py new file mode 100644 index 00000000..5620b552 --- /dev/null +++ b/gn2/maintenance/quantile_normalize.py @@ -0,0 +1,98 @@ +import sys +sys.path.insert(0, './') +import urllib.parse + +import numpy as np +import pandas as pd + +from flask import Flask, g, request + +from gn2.wqflask import app +from gn2.wqflask.database import database_connection +from gn2.utility.tools import get_setting + + +def create_dataframe(input_file): + with open(input_file) as f: + ncols = len(f.readline().split("\t")) + + input_array = np.loadtxt(open( + input_file, "rb"), delimiter="\t", skiprows=1, usecols=list(range(1, ncols))) + return pd.DataFrame(input_array) + +# This function taken from https://github.com/ShawnLYU/Quantile_Normalize + + +def quantileNormalize(df_input): + df = df_input.copy() + # compute rank + dic = {} + for col in df: + dic.update({col: sorted(df[col])}) + sorted_df = pd.DataFrame(dic) + rank = sorted_df.mean(axis=1).tolist() + # sort + for col in df: + t = np.searchsorted(np.sort(df[col]), df[col]) + df[col] = [rank[i] for i in t] + return df + + +def set_data(cursor, dataset_name): + orig_file = "/home/zas1024/cfw_data/" + dataset_name + ".txt" + + sample_list = [] + with open(orig_file, 'r') as orig_fh, open('/home/zas1024/cfw_data/quant_norm.csv', 'r') as quant_fh: + for i, (line1, line2) in enumerate(zip(orig_fh, quant_fh)): + trait_dict = {} + sample_list = [] + if i == 0: + sample_names = line1.split('\t')[1:] + else: + trait_name = line1.split('\t')[0] + for i, sample in enumerate(sample_names): + this_sample = { + "name": sample, + "value": line1.split('\t')[i + 1], + "qnorm": line2.split('\t')[i + 1] + } + sample_list.append(this_sample) + query = """SELECT Species.SpeciesName, InbredSet.InbredSetName, ProbeSetFreeze.FullName + FROM Species, InbredSet, ProbeSetFreeze, ProbeFreeze, ProbeSetXRef, ProbeSet + WHERE Species.Id = InbredSet.SpeciesId and + InbredSet.Id = ProbeFreeze.InbredSetId and + ProbeFreeze.Id = ProbeSetFreeze.ProbeFreezeId and + ProbeSetFreeze.Name = '%s' and + ProbeSetFreeze.Id = ProbeSetXRef.ProbeSetFreezeId and + ProbeSetXRef.ProbeSetId = ProbeSet.Id and + ProbeSet.Name = '%s'""" % (dataset_name, line1.split('\t')[0]) + cursor.execute(query) + result_info = cursor.fetchone() + + yield { + "_index": "traits", + "_type": "trait", + "_source": { + "name": trait_name, + "species": result_info[0], + "group": result_info[1], + "dataset": dataset_name, + "dataset_fullname": result_info[2], + "samples": sample_list, + "transform_types": "qnorm" + } + } + + +if __name__ == '__main__': + with database_connection(get_setting("SQL_URI")) as conn: + with conn.cursor() as cursor: + success, _ = bulk(es, set_data(cursor, sys.argv[1])) + + response = es.search( + index="traits", doc_type="trait", body={ + "query": {"match": {"name": "ENSMUSG00000028982"}} + } + ) + + print(response) diff --git a/gn2/maintenance/set_resource_defaults.py b/gn2/maintenance/set_resource_defaults.py new file mode 100644 index 00000000..f9e5494a --- /dev/null +++ b/gn2/maintenance/set_resource_defaults.py @@ -0,0 +1,153 @@ +""" + +Script that sets default resource access masks for use with the DB proxy + +Defaults will be: +Owner - omni_gn +Mask - Public/non-confidential: { data: "view", + metadata: "view", + admin: "not-admin" } + Private/confidentia: { data: "no-access", + metadata: "no-access", + admin: "not-admin" } + +To run: +./bin/genenetwork2 ~/my_settings.py -c ./wqflask/maintenance/gen_select_dataset.py + +""" + +import sys +import json + +# NEW: Note we prepend the current path - otherwise a guix instance of GN2 may be used instead +sys.path.insert(0, './') + +# NEW: import app to avoid a circular dependency on utility.tools +from gn2.wqflask import app + +from gn2.utility import hmac +from gn2.utility.tools import get_setting +from gn2.utility.redis_tools import get_redis_conn, get_user_id, add_resource, get_resources, get_resource_info +Redis = get_redis_conn() + +import urllib.parse + +from gn2.wqflask.database import database_connection + + +def insert_probeset_resources(cursor, default_owner_id): + current_resources = Redis.hgetall("resources") + cursor.execute(""" SELECT + ProbeSetFreeze.Id, ProbeSetFreeze.Name, ProbeSetFreeze.confidentiality, ProbeSetFreeze.public + FROM + ProbeSetFreeze""") + + resource_results = cursor.fetchall() + for i, resource in enumerate(resource_results): + resource_ob = {} + resource_ob['name'] = resource[1] + resource_ob['owner_id'] = default_owner_id + resource_ob['data'] = {"dataset": str(resource[0])} + resource_ob['type'] = "dataset-probeset" + if resource[2] < 1 and resource[3] > 0: + resource_ob['default_mask'] = {"data": "view", + "metadata": "view", + "admin": "not-admin"} + else: + resource_ob['default_mask'] = {"data": "no-access", + "metadata": "no-access", + "admin": "not-admin"} + resource_ob['group_masks'] = {} + + add_resource(resource_ob, update=False) + + +def insert_publish_resources(cursor, default_owner_id): + current_resources = Redis.hgetall("resources") + cursor.execute(""" SELECT + PublishXRef.Id, PublishFreeze.Id, InbredSet.InbredSetCode + FROM + PublishXRef, PublishFreeze, InbredSet, Publication + WHERE + PublishFreeze.InbredSetId = PublishXRef.InbredSetId AND + InbredSet.Id = PublishXRef.InbredSetId AND + Publication.Id = PublishXRef.PublicationId""") + + resource_results = cursor.fetchall() + for resource in resource_results: + if resource[2]: + resource_ob = {} + if resource[2]: + resource_ob['name'] = resource[2] + "_" + str(resource[0]) + else: + resource_ob['name'] = str(resource[0]) + resource_ob['owner_id'] = default_owner_id + resource_ob['data'] = {"dataset": str(resource[1]), + "trait": str(resource[0])} + resource_ob['type'] = "dataset-publish" + resource_ob['default_mask'] = {"data": "view", + "metadata": "view", + "admin": "not-admin"} + + resource_ob['group_masks'] = {} + + add_resource(resource_ob, update=False) + else: + continue + + +def insert_geno_resources(cursor, default_owner_id): + current_resources = Redis.hgetall("resources") + cursor.execute(""" SELECT + GenoFreeze.Id, GenoFreeze.ShortName, GenoFreeze.confidentiality + FROM + GenoFreeze""") + + resource_results = cursor.fetchall() + for i, resource in enumerate(resource_results): + resource_ob = {} + resource_ob['name'] = resource[1] + if resource[1] == "HET3-ITPGeno": + resource_ob['owner_id'] = "c5ce8c56-78a6-474f-bcaf-7129d97f56ae" + else: + resource_ob['owner_id'] = default_owner_id + resource_ob['data'] = {"dataset": str(resource[0])} + resource_ob['type'] = "dataset-geno" + if resource[2] < 1: + resource_ob['default_mask'] = {"data": "view", + "metadata": "view", + "admin": "not-admin"} + else: + resource_ob['default_mask'] = {"data": "no-access", + "metadata": "no-access", + "admin": "not-admin"} + resource_ob['group_masks'] = {} + + add_resource(resource_ob, update=False) + + +def insert_resources(default_owner_id): + current_resources = get_resources() + print("START") + insert_publish_resources(cursor, default_owner_id) + print("AFTER PUBLISH") + insert_geno_resources(cursor, default_owner_id) + print("AFTER GENO") + insert_probeset_resources(cursor, default_owner_id) + print("AFTER PROBESET") + + +def main(cursor): + """Generates and outputs (as json file) the data for the main dropdown menus on the home page""" + + Redis.delete("resources") + + owner_id = "c5ce8c56-78a6-474f-bcaf-7129d97f56ae" + + insert_resources(owner_id) + + +if __name__ == '__main__': + with database_connection(get_setting("SQL_URI")) as conn: + with conn.cursor() as cursor: + main(cursor) diff --git a/gn2/run_gunicorn.py b/gn2/run_gunicorn.py new file mode 100644 index 00000000..6de470fe --- /dev/null +++ b/gn2/run_gunicorn.py @@ -0,0 +1,24 @@ +# Run with gunicorn, see ./bin/genenetwork2 for an example +# +# Run standalone with +# +# ./bin/genenetwork2 ./etc/default_settings.py -c run_gunicorn.py + +# from flask import Flask +# application = Flask(__name__) + +print("===> Starting up Gunicorn process") + +from gn2.gn2_main import app +from gn2.utility.startup_config import app_config + +app_config() + + +@app.route("/gunicorn") +def hello(): + return "<h1 style='color:blue'>Hello There!</h1>" + + +if __name__ == "__main__": + app.run(host='0.0.0.0') diff --git a/gn2/runserver.py b/gn2/runserver.py new file mode 100644 index 00000000..45a51385 --- /dev/null +++ b/gn2/runserver.py @@ -0,0 +1,50 @@ +# Starts the webserver with the ./bin/genenetwork2 command +# +# This uses Werkzeug WSGI, see ./run_gunicorn.py for the alternative +# +# Please note, running with host set externally below combined with +# debug mode is a security risk unless you have a firewall setup, e.g. +# +# /sbin/iptables -A INPUT -p tcp -i eth0 -s ! 71.236.239.43 --dport 5003 -j DROP + +from gn2_main import app +from gn2.utility.startup_config import app_config +from gn2.utility.tools import WEBSERVER_MODE, SERVER_PORT + +import logging + +BLUE = '\033[94m' +GREEN = '\033[92m' +BOLD = '\033[1m' +ENDC = '\033[0m' + +app_config() + +werkzeug_logger = logging.getLogger('werkzeug') + +if WEBSERVER_MODE == 'DEBUG': + app.debug = True + app.run(host='0.0.0.0', + port=SERVER_PORT, + debug=True, + use_debugger=False, + threaded=False, + processes=0, + use_reloader=True) +elif WEBSERVER_MODE == 'DEV': + werkzeug_logger.setLevel(logging.WARNING) + app.run(host='0.0.0.0', + port=SERVER_PORT, + debug=False, + use_debugger=False, + threaded=False, + processes=0, + use_reloader=True) +else: # staging/production modes + app.run(host='0.0.0.0', + port=SERVER_PORT, + debug=False, + use_debugger=False, + threaded=True, + processes=0, + use_reloader=True) diff --git a/gn2/scripts/__init__.py b/gn2/scripts/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/scripts/__init__.py diff --git a/gn2/scripts/corr_compute.py b/gn2/scripts/corr_compute.py new file mode 100644 index 00000000..44dcac68 --- /dev/null +++ b/gn2/scripts/corr_compute.py @@ -0,0 +1,77 @@ +"""Compute the correlations.""" + +import sys +import json +import pickle +import pathlib +import datetime + +from flask import g + +from gn2.wqflask import app +from gn2.wqflask.user_session import UserSession +from gn2.wqflask.correlation.show_corr_results import set_template_vars +from gn2.wqflask.correlation.correlation_gn3_api import compute_correlation + +class UserSessionSimulator(): + + def __init__(self, user_id): + self._user_id = user_id + + @property + def user_id(self): + return self._user_id + +error_types = { + "WrongCorrelationType": "Wrong Correlation Type", + "CalledProcessError": "Called Process Error" +} + +def e_time(): + return datetime.datetime.utcnow().isoformat() + +def compute(form): + import subprocess + try: + correlation_results = compute_correlation(form, compute_all=True) + except Exception as exc: + return { + "error-type": error_types[type(exc).__name__], + "error-message": exc.args[0] + } + + return set_template_vars(form, correlation_results) + +if __name__ == "__main__": + ARGS_COUNT = 3 + if len(sys.argv) < ARGS_COUNT: + print(f"{e_time()}: You need to pass the file with the picked form", + file=sys.stderr) + sys.exit(1) + + if len(sys.argv) > ARGS_COUNT: + print(f"{e_time()}: Unknown arguments {sys.argv[ARGS_COUNT:]}", + file=sys.stderr) + sys.exit(1) + + filepath = pathlib.Path(sys.argv[1]) + if not filepath.exists(): + print(f"File not found '{filepath}'", file=sys.stderr) + sys.exit(2) + + with open(filepath, "rb") as pfile: + form = pickle.Unpickler(pfile).load() + + with app.app_context(): + g.user_session = UserSessionSimulator(sys.argv[2]) + results = compute(form) + + print(json.dumps(results), file=sys.stdout) + + if "error-type" in results: + print( + f"{results['error-type']}: {results['error-message']}", + file=sys.stderr) + sys.exit(3) + + sys.exit(0) diff --git a/gn2/scripts/parse_corr_html_results_to_csv.py b/gn2/scripts/parse_corr_html_results_to_csv.py new file mode 100644 index 00000000..464eb2ca --- /dev/null +++ b/gn2/scripts/parse_corr_html_results_to_csv.py @@ -0,0 +1,175 @@ +import csv +import sys +import json +from pathlib import Path +from functools import reduce, partial +from typing import Any, Union, Sequence, Optional +from argparse import Action, Namespace, ArgumentError, ArgumentParser + +from lxml import etree + +def thread(value, *functions): + return reduce(lambda result, func: func(result), functions, value) + +def parse_file(filename: Path): + with open(filename, encoding="utf-8") as inpfl: + raw_html = inpfl.read() + + return etree.HTML(raw_html) + +def first_row_headers(table): + return tuple( + " ".join(text.strip() for text in cell.xpath(".//child::text()")) + for cell in table.xpath("./tbody/tr[1]/td")) + +def results_table(tables): + found = tuple(filter( + lambda table: ( + first_row_headers(table)[0:4] == + ("Index", "Record ID", "Symbol", "Description")), + tables)) + return found[0] + +def table_contents(table): + return tuple( + tuple(" ".join(text.strip() for text in cell.xpath(".//child::text()")) + for cell in row) + for row in table.xpath("./tbody/tr")) + + +def to_dicts(contents): + frow = contents[0] + return tuple(dict(zip(frow, row)) for row in contents[1:]) + +def write_csv( + input_file: Path, output_dir: Union[bool, Path], + contents: Sequence[dict]) -> Sequence[Sequence[str]]: + def __write__(stream): + writer = csv.DictWriter( + stream, fieldnames=list(contents[0].keys()), + dialect=csv.unix_dialect) + writer.writeheader() + writer.writerows(contents) + + if not bool(output_dir): + return __write__(sys.stdout) + + output_file = output_dir.joinpath( + f"{input_file.stem}__results.csv") + with open(output_file, "w", encoding="utf-8") as out_file: + return __write__(out_file) + +def output_stream(): + if not to_output_file: + return sys.stdout + + output_file = input_file.parent.joinpath( + f"{input_file.stem}.csv") + with open(output_file) as out_file: + yield out_file + +class FileCheck(Action): + """Action class to check existence of a given file path.""" + + def __init__(self, option_strings, dest, **kwargs): + "Initialise the FileCheck action class" + super().__init__(option_strings, dest, **kwargs) + + def __call__(# pylint: disable=[signature-differs] + self, parser: ArgumentParser, namespace: Namespace, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = "") -> None: + """Check existence of a given file path and set it, or raise an + exception.""" + the_path = str(values or "") + the_file = Path(the_path).absolute().resolve() + if not the_file.is_file(): + raise ArgumentError( + self, + f"The file '{values}' does not exist or is a folder/directory.") + + setattr(namespace, self.dest, the_file) + +class DirectoryCheck(Action): + """Action class to check the existence of a particular directory""" + def __init__(self, option_strings, dest, **kwargs): + """Init `DirectoryCheck` action object.""" + super().__init__(option_strings, dest, **kwargs) + + def __call__( + self, parser: ArgumentParser, namespace: Namespace, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = "") -> None: + the_dir = Path(str(values or "")).absolute().resolve() + if not the_dir.is_dir(): + raise ArgumentError( + self, f"The directory '{the_dir}' does not exist!") + + setattr(namespace, self.dest, the_dir) + +def gn1_parser(subparsers) -> None: + parser = subparsers.add_parser("gn1") + parser.add_argument( + "inputfile", help="The HTML file to parse", action=FileCheck) + parser.add_argument( + "--outputdir", help="Path to output directory", action=DirectoryCheck, + default=False) + parser.set_defaults( + func=lambda args: thread( + args.inputfile, + parse_file, + lambda tree: tree.xpath("//table"), + results_table, + table_contents, + to_dicts, + partial(write_csv, args.inputfile, args.outputdir))) + +def tablejson_script(scripts): + for script in scripts: + script_content = thread( + script.xpath('.//child::text()'), + lambda val: "".join(val).strip()) + if script_content.find("var tableJson") >= 0: + return json.loads(thread( + script_content, + lambda val: val[len("var tableJson = "):].strip())) + continue + return None + +def gn2_parser(subparsers) -> None: + parser = subparsers.add_parser("gn2") + parser.add_argument( + "inputfile", help="The HTML file to parse", action=FileCheck) + parser.add_argument( + "--outputdir", help="Path to output directory", action=DirectoryCheck, + default=False) + parser.set_defaults( + func=lambda args: thread( + args.inputfile, + parse_file, + lambda tree: tree.xpath("//script"), + tablejson_script, + partial(write_csv, args.inputfile, args.outputdir) + )) + +def parse_cli_args(): + parser = ArgumentParser( + "parse_corr_html_results_to_csv", + description = "Parse correlation results from the given HTML file.") + subparsers = parser.add_subparsers( + title="subcommands", description="Valid subcommands", + help="additional help") + gn1_parser(subparsers) + gn2_parser(subparsers) + + return parser, parser.parse_args() + +def run(): + parser, args = parse_cli_args() + try: + args.func(args) + except AttributeError as _attr_err: + parser.print_help() + +if __name__ == "__main__": + run() diff --git a/gn2/scripts/profile_corrs.py b/gn2/scripts/profile_corrs.py new file mode 100644 index 00000000..5f9a06da --- /dev/null +++ b/gn2/scripts/profile_corrs.py @@ -0,0 +1,67 @@ +import io +import sys +import pstats +import cProfile + +from flask import g, request + +from gn2.utility.startup_config import app_config + +from gn2.wqflask import app +from gn2.wqflask.user_session import UserSession +from gn2.wqflask.correlation.correlation_gn3_api import compute_correlation + +def sample_vals(): + return '{"C57BL/6J":"10.835","DBA/2J":"11.142","B6D2F1":"11.126","D2B6F1":"11.143","BXD1":"10.811","BXD2":"11.503","BXD5":"10.766","BXD6":"10.986","BXD8":"11.050","BXD9":"10.822","BXD11":"10.670","BXD12":"10.946","BXD13":"10.890","BXD14":"x","BXD15":"10.884","BXD16":"11.222","BXD18":"x","BXD19":"10.968","BXD20":"10.962","BXD21":"10.906","BXD22":"11.080","BXD23":"11.046","BXD24":"11.146","BXD24a":"x","BXD25":"x","BXD27":"11.078","BXD28":"11.034","BXD29":"10.808","BXD30":"x","BXD31":"11.087","BXD32":"11.029","BXD33":"10.662","BXD34":"11.482","BXD35":"x","BXD36":"x","BXD37":"x","BXD38":"10.836","BXD39":"10.926","BXD40":"10.638","BXD41":"x","BXD42":"10.974","BXD43":"10.828","BXD44":"10.900","BXD45":"11.358","BXD48":"11.042","BXD48a":"10.975","BXD49":"x","BXD50":"11.228","BXD51":"11.126","BXD52":"x","BXD53":"x","BXD54":"x","BXD55":"11.580","BXD56":"x","BXD59":"x","BXD60":"10.829","BXD61":"11.152","BXD62":"11.156","BXD63":"10.942","BXD64":"10.506","BXD65":"11.126","BXD65a":"11.272","BXD65b":"11.157","BXD66":"11.071","BXD67":"11.080","BXD68":"10.997","BXD69":"11.096","BXD70":"11.152","BXD71":"x","BXD72":"x","BXD73":"11.262","BXD73a":"11.444","BXD73b":"x","BXD74":"10.974","BXD75":"11.150","BXD76":"10.920","BXD77":"10.928","BXD78":"x","BXD79":"11.371","BXD81":"x","BXD83":"10.946","BXD84":"11.181","BXD85":"10.992","BXD86":"10.770","BXD87":"11.200","BXD88":"x","BXD89":"10.930","BXD90":"11.183","BXD91":"x","BXD93":"11.056","BXD94":"10.737","BXD95":"x","BXD98":"10.986","BXD99":"10.892","BXD100":"x","BXD101":"x","BXD102":"x","BXD104":"x","BXD105":"x","BXD106":"x","BXD107":"x","BXD108":"x","BXD109":"x","BXD110":"x","BXD111":"x","BXD112":"x","BXD113":"x","BXD114":"x","BXD115":"x","BXD116":"x","BXD117":"x","BXD119":"x","BXD120":"x","BXD121":"x","BXD122":"x","BXD123":"x","BXD124":"x","BXD125":"x","BXD126":"x","BXD127":"x","BXD128":"x","BXD128a":"x","BXD130":"x","BXD131":"x","BXD132":"x","BXD133":"x","BXD134":"x","BXD135":"x","BXD136":"x","BXD137":"x","BXD138":"x","BXD139":"x","BXD141":"x","BXD142":"x","BXD144":"x","BXD145":"x","BXD146":"x","BXD147":"x","BXD148":"x","BXD149":"x","BXD150":"x","BXD151":"x","BXD152":"x","BXD153":"x","BXD154":"x","BXD155":"x","BXD156":"x","BXD157":"x","BXD160":"x","BXD161":"x","BXD162":"x","BXD165":"x","BXD168":"x","BXD169":"x","BXD170":"x","BXD171":"x","BXD172":"x","BXD173":"x","BXD174":"x","BXD175":"x","BXD176":"x","BXD177":"x","BXD178":"x","BXD180":"x","BXD181":"x","BXD183":"x","BXD184":"x","BXD186":"x","BXD187":"x","BXD188":"x","BXD189":"x","BXD190":"x","BXD191":"x","BXD192":"x","BXD193":"x","BXD194":"x","BXD195":"x","BXD196":"x","BXD197":"x","BXD198":"x","BXD199":"x","BXD200":"x","BXD201":"x","BXD202":"x","BXD203":"x","BXD204":"x","BXD205":"x","BXD206":"x","BXD207":"x","BXD208":"x","BXD209":"x","BXD210":"x","BXD211":"x","BXD212":"x","BXD213":"x","BXD214":"x","BXD215":"x","BXD216":"x","BXD217":"x","BXD218":"x","BXD219":"x","BXD220":"x"}' + +def simulated_form(corr_type: str = "sample"): + assert corr_type in ("sample", "tissue", "lit") + return { + "dataset": "HC_M2_0606_P", + "trait_id": "1435464_at", + "corr_dataset": "HC_M2_0606_P", + "corr_sample_method": "pearson", + "corr_return_results": "100", + "corr_samples_group": "samples_primary", + "sample_vals": sample_vals(), + "corr_type": corr_type, + "corr_sample_method": "pearson", + "location_type": "gene", + "corr_return_results": "20000" + } + +def profile_corrs(): + "Profile tho correlations" + profiler = cProfile.Profile() + profiler.enable() + correlation_results = compute_correlation(request.form, compute_all=True) + profiler.disable() + return profiler + +def dump_stats(profiler): + iostr = io.StringIO() + sort_by = pstats.SortKey.CUMULATIVE + ps = pstats.Stats(profiler, stream=iostr).sort_stats(sort_by) + ps.print_stats() + + cli_args = sys.argv + if len(cli_args) > 1: + with open(cli_args[1], "w+", encoding="utf-8") as output_file: + print(iostr.getvalue(), file=output_file) + + return 0 + + print(iostr.getvalue()) + return 0 + + +if __name__ == "__main__": + def main(): + "Entry point for profiler script" + return dump_stats(profile_corrs()) + + app_config() + with app.app_context(): + with app.test_request_context("/corr_compute", data=simulated_form()): + g.user_session = UserSession() + main() diff --git a/gn2/scripts/run_external.py b/gn2/scripts/run_external.py new file mode 100644 index 00000000..297d17a1 --- /dev/null +++ b/gn2/scripts/run_external.py @@ -0,0 +1,159 @@ +""" +Run jobs in external processes. +""" + +import os +import sys +import shlex +import argparse +import traceback +import subprocess +from uuid import UUID +from time import sleep +from datetime import datetime +from urllib.parse import urlparse +from tempfile import TemporaryDirectory + +# import psutil +from redis import Redis + +import gn2.jobs.jobs as jobs + +def print_help(args, parser): + print(parser.format_help()) + +def UUID4(val): + return UUID(val) + +def redis_connection(parsed_url): + return Redis.from_url( + f"redis://{parsed_url.netloc}{parsed_url.path}", decode_responses=True) + +def update_status(redis_conn: Redis, job_id: UUID, value: str): + "Update the job's status." + redis_conn.hset(jobs.job_namespace(job_id), key="status", value=value) + +def __update_stdout_stderr__( + redis_conn: Redis, job_id: UUID, bytes_read: bytes, stream: str): + job = jobs.job(redis_conn, job_id) + if job.is_nothing(): + raise jobs.NoSuchJob(job_id) + + job = job.maybe({}, lambda x: x) + redis_conn.hset( + jobs.job_namespace(job_id), key=stream, + value=(job.get(stream, "") + bytes_read.decode("utf-8"))) + +def set_stdout(redis_conn: Redis, job_id:UUID, bytes_read: bytes): + """Set the stdout value for the given job.""" + job = jobs.job(redis_conn, job_id) + if job.is_nothing(): + raise jobs.NoSuchJob(job_id) + + job = job.maybe({}, lambda x: x) + redis_conn.hset( + jobs.job_namespace(job_id), key="stdout", + value=bytes_read.decode("utf-8")) + +def update_stdout(redis_conn: Redis, job_id:UUID, bytes_read: bytes): + """Update the stdout value for the given job.""" + __update_stdout_stderr__(redis_conn, job_id, bytes_read, "stdout") + +def update_stderr(redis_conn: Redis, job_id:UUID, bytes_read: bytes): + """Update the stderr value for the given job.""" + __update_stdout_stderr__(redis_conn, job_id, bytes_read, "stderr") + +def set_meta(redis_conn: Redis, job_id: UUID, meta_key: str, meta_val: str): + job = jobs.job(redis_conn, job_id) + if job.is_nothing(): + raise jobs.NoSuchJob(job_id) + + redis_conn.hset(jobs.job_namespace(job_id), key=meta_key, value=meta_val) + +def run_job(redis_conn: Redis, job_id: UUID): + """Run the job in an external process.""" + print(f"THE ARGUMENTS TO RUN_JOB:\n\tConnection: {redis_conn}\n\tJob ID: {job_id}\n") + + the_job = jobs.job(redis_conn, job_id) + if the_job.is_nothing(): + raise jobs.NoSuchJob(job_id) + + with TemporaryDirectory() as tmpdir: + stdout_file = f"{tmpdir}/{job_id}.stdout" + stderr_file = f"{tmpdir}/{job_id}.stderr" + with open(stdout_file, "w+b") as outfl, open(stderr_file, "w+b") as errfl: + with subprocess.Popen( + jobs.command(the_job), stdout=outfl, + stderr=errfl) as process: + while process.poll() is None: + update_status(redis_conn, job_id, "running") + update_stdout(redis_conn, job_id, outfl.read1()) + sleep(1) + + update_status(redis_conn, job_id, "completed") + with open(stdout_file, "rb") as outfl, open(stderr_file, "rb") as errfl: + set_stdout(redis_conn, job_id, outfl.read()) + update_stderr(redis_conn, job_id, errfl.read()) + + os.remove(stdout_file) + os.remove(stderr_file) + + returncode = process.returncode + set_meta(redis_conn, job_id, "completion-status", + ("success" if returncode == 0 else "error")) + set_meta(redis_conn, job_id, "return-code", returncode) + return process.returncode + +def run_job_parser(parent_parser): + parser = parent_parser.add_parser( + "run-job", + help="run job with given id") + parser.add_argument( + "job_id", type=UUID4, help="A string representing a UUID4 value.") + parser.set_defaults( + run=lambda conn, args, parser: run_job(conn, args.job_id)) + +def add_subparsers(parent_parser, *subparser_fns): + sub_parsers = parent_parser.add_subparsers( + title="subcommands", description="valid subcommands", required=True) + for parser_fn in subparser_fns: + parser_fn(sub_parsers) + pass + + return parent_parser + +def parse_cli_args(): + parser = add_subparsers(argparse.ArgumentParser( + description=sys.modules[__name__].__doc__.strip()), run_job_parser) + parser.add_argument( + "--redis-uri", required=True, + help=( + "URI to use to connect to job management db." + "The URI should be of the form " + "'<scheme>://<user>:<passwd>@<host>:<port>/<path>'"), + type=urlparse) + return parser, parser.parse_args() + +def launch_manager(): + parser, args = parse_cli_args() + with redis_connection(args.redis_uri) as conn: + try: + return args.run(conn, args, parser) + except Exception as nsj: + prev_msg = ( + conn.hget(f"{jobs.JOBS_NAMESPACE}:manager", key="stderr") or "") + if bool(prev_msg): + prev_msg = f"{prev_msg}\n" + + notfoundmsg = ( + f"{prev_msg}" + f"{datetime.now().isoformat()}: {type(nsj).__name__}: {traceback.format_exc()}") + conn.hset( + f"{jobs.JOBS_NAMESPACE}:manager", + key="stderr", + value=notfoundmsg) + +if __name__ == "__main__": + def run(): + sys.exit(launch_manager()) + run() diff --git a/gn2/tests/__init__.py b/gn2/tests/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/__init__.py diff --git a/gn2/tests/integration/__init__.py b/gn2/tests/integration/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/integration/__init__.py diff --git a/gn2/tests/integration/wqflask/__init__.py b/gn2/tests/integration/wqflask/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/integration/wqflask/__init__.py diff --git a/gn2/tests/unit/__init__.py b/gn2/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/__init__.py diff --git a/gn2/tests/unit/base/__init__.py b/gn2/tests/unit/base/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/base/__init__.py diff --git a/gn2/tests/unit/base/test_data_set.py b/gn2/tests/unit/base/test_data_set.py new file mode 100644 index 00000000..9f9fb132 --- /dev/null +++ b/gn2/tests/unit/base/test_data_set.py @@ -0,0 +1,238 @@ +"""Tests for wqflask/base/data_set.py""" + +import unittest +from unittest import mock +from dataclasses import dataclass +from gn3.monads import MonadicDict + +from gn2.wqflask import app +from gn2.base.data_set import DatasetType +from gn2.base.data_set.dataset import DataSet + +GEN_MENU_JSON = """ +{ + "datasets": { + "human": { + "HLC": { + "Liver mRNA": [ + [ + "320", + "HLC_0311", + "GSE9588 Human Liver Normal (Mar11) Both Sexes" + ] + ], + "Phenotypes": [ + [ + "635", + "HLCPublish", + "HLC Published Phenotypes" + ] + ] + } + }, + "mouse": { + "BXD": { + "Genotypes": [ + [ + "600", + "BXDGeno", + "BXD Genotypes" + ] + ], + "Hippocampus mRNA": [ + [ + "112", + "HC_M2_0606_P", + "Hippocampus Consortium M430v2 (Jun06) PDNN" + ] + ], + "Phenotypes": [ + [ + "602", + "BXDPublish", + "BXD Published Phenotypes" + ] + ] + } + } + }, + "groups": { + "human": [ + [ + "HLC", + "Liver: Normal Gene Expression with Genotypes (Merck)", + "Family:None" + ] + ], + "mouse": [ + [ + "BXD", + "BXD", + "Family:None" + ] + ] + }, + "species": [ + [ + "human", + "Human" + ], + [ + "mouse", + "Mouse" + ] + ], + "types": { + "human": { + "HLC": [ + [ + "Phenotypes", + "Traits and Cofactors", + "Phenotypes" + ], + [ + "Liver mRNA", + "Liver mRNA", + "Molecular Trait Datasets" + ] + ] + }, + "mouse": { + "BXD": [ + [ + "Phenotypes", + "Traits and Cofactors", + "Phenotypes" + ], + [ + "Genotypes", + "DNA Markers and SNPs", + "Genotypes" + ], + [ + "Hippocampus mRNA", + "Hippocampus mRNA", + "Molecular Trait Datasets" + ] + ] + } + } +} +""" + +class MockPhenotypeDataset(DataSet): + def setup(self): + self.type = "Publish" + self.query_for_group = "" + self.group = "" + + + def check_confidentiality(self): + pass + + def retrieve_other_names(self): + pass + +@dataclass +class MockGroup: + name = "Group" + +class TestDataSetTypes(unittest.TestCase): + """Tests for the DataSetType class""" + + def setUp(self): + self.test_dataset = """ + { + "AD-cases-controls-MyersGeno": "Geno", + "AD-cases-controls-MyersPublish": "Publish", + "AKXDGeno": "Geno", + "AXBXAGeno": "Geno", + "AXBXAPublish": "Publish", + "Aging-Brain-UCIPublish": "Publish", + "All Phenotypes": "Publish", + "B139_K_1206_M": "ProbeSet", + "B139_K_1206_R": "ProbeSet" + } + """ + self.app_context = app.app_context() + self.app_context.push() + + def tearDown(self): + self.app_context.pop() + + def test_data_set_type(self): + """Test that DatasetType returns correctly if the Redis Instance is not empty + and the name variable exists in the dictionary + + """ + with app.app_context(): + redis_mock = mock.Mock() + cursor_mock = mock.Mock() + redis_mock.get.return_value = self.test_dataset + self.assertEqual(DatasetType(redis_mock) + ("All Phenotypes", redis_mock, cursor_mock), "Publish") + redis_mock.get.assert_called_once_with("dataset_structure") + + @mock.patch('gn2.base.data_set.datasettype.requests.get') + def test_data_set_type_with_empty_redis(self, request_mock): + """Test that DatasetType returns correctly if the Redis Instance is empty and + the name variable exists in the dictionary + + """ + with app.app_context(): + request_mock.return_value.content = GEN_MENU_JSON + redis_mock = mock.Mock() + cursor_mock = mock.Mock() + redis_mock.get.return_value = None + data_set = DatasetType(redis_mock) + self.assertEqual(data_set("BXDGeno", redis_mock, cursor_mock), + "Geno") + self.assertEqual(data_set("BXDPublish", redis_mock, cursor_mock), + "Publish") + self.assertEqual(data_set("HLC_0311", redis_mock, cursor_mock), + "ProbeSet") + + redis_mock.set.assert_called_once_with( + "dataset_structure", + ('{"HLC_0311": "ProbeSet", ' + '"HLCPublish": "Publish", ' + '"BXDGeno": "Geno", ' + '"HC_M2_0606_P": "ProbeSet", ' + '"BXDPublish": "Publish"}')) + + +class TestDatasetAccessionId(unittest.TestCase): + """Tests for the DataSetType class""" + + @mock.patch("gn2.base.data_set.dataset.query_sql") + @mock.patch("gn2.base.data_set.dataset.DatasetGroup") + def test_get_accession_id(self, mock_dataset_group, mock_query_sql): + def mock_fn(): + yield MonadicDict({"accession_id": 7}) + mock_dataset_group.return_value = MockGroup() + mock_query_sql.return_value = mock_fn() + sample_dataset = MockPhenotypeDataset( + name="BXD-LongevityPublish", + get_samplelist=False, + group_name="BXD", + redis_conn=mock.Mock() + ) + sample_dataset\ + .accession_id\ + .bind(lambda x: self.assertEqual(7, x)) + + @mock.patch("gn2.base.data_set.dataset.query_sql") + @mock.patch("gn2.base.data_set.dataset.DatasetGroup") + def test_get_accession_id_empty_return(self, mock_dataset_group, + mock_query_sql): + mock_dataset_group.return_value = MockGroup() + mock_query_sql.return_value = None + sample_dataset = MockPhenotypeDataset( + name="BXD-LongevityPublish", + get_samplelist=False, + group_name="BXD", + redis_conn=mock.Mock() + ) + sample_dataset\ + .accession_id\ + .bind(lambda x: self.assertNone(x)) diff --git a/gn2/tests/unit/base/test_general_object.py b/gn2/tests/unit/base/test_general_object.py new file mode 100644 index 00000000..1301f18b --- /dev/null +++ b/gn2/tests/unit/base/test_general_object.py @@ -0,0 +1,40 @@ +import unittest + +from gn2.base.GeneralObject import GeneralObject + + +class TestGeneralObjectTests(unittest.TestCase): + """ + Test the GeneralObject base class + """ + + def test_object_contents(self): + """Test whether base contents are stored properly""" + test_obj = GeneralObject("a", "b", "c") + self.assertEqual("abc", ''.join(test_obj.contents)) + self.assertEqual(len(test_obj), 0) + + def test_object_dict(self): + """Test whether the base class is printed properly""" + test_obj = GeneralObject("a", name="test", value=1) + self.assertEqual(str(test_obj), "name = test\nvalue = 1\n") + self.assertEqual( + repr(test_obj), "contents = ['a']\nname = test\nvalue = 1\n") + self.assertEqual(len(test_obj), 2) + self.assertEqual(test_obj["value"], 1) + test_obj["test"] = 1 + self.assertEqual(test_obj["test"], 1) + + def test_get_attribute(self): + "Test that getattr works" + test_obj = GeneralObject("a", name="test", value=1) + self.assertEqual(getattr(test_obj, "value", None), 1) + self.assertEqual(getattr(test_obj, "non-existent", None), None) + + def test_object_comparisons(self): + "Test that 2 objects of the same length are equal" + test_obj1 = GeneralObject("a", name="test", value=1) + test_obj2 = GeneralObject("b", name="test2", value=2) + test_obj3 = GeneralObject("a", name="test", x=1, y=2) + self.assertTrue(test_obj1 == test_obj2) + self.assertFalse(test_obj1 == test_obj3) diff --git a/gn2/tests/unit/base/test_mrna_assay_tissue_data.py b/gn2/tests/unit/base/test_mrna_assay_tissue_data.py new file mode 100644 index 00000000..5bc28ffa --- /dev/null +++ b/gn2/tests/unit/base/test_mrna_assay_tissue_data.py @@ -0,0 +1,81 @@ +import pytest +from gn2.base.mrna_assay_tissue_data import MrnaAssayTissueData + + +@pytest.mark.parametrize( + ('gene_symbols', 'expected_query', 'sql_fetch_all_results'), + ( + (None, + (("SELECT t.Symbol, t.GeneId, t.DataId, " + "t.Chr, t.Mb, t.description, " + "t.Probe_Target_Description " + "FROM (SELECT Symbol, " + "max(Mean) AS maxmean " + "FROM TissueProbeSetXRef WHERE " + "TissueProbeSetFreezeId=1 AND " + "Symbol != '' AND Symbol IS NOT " + "Null GROUP BY Symbol) " + "AS x INNER JOIN TissueProbeSetXRef " + "AS t ON t.Symbol = x.Symbol " + "AND t.Mean = x.maxmean"),), + (("symbol", "gene_id", + "data_id", "chr", "mb", + "description", + "probe_target_description"),)), + (["k1", "k2", "k3"], + ("SELECT t.Symbol, t.GeneId, t.DataId, " + "t.Chr, t.Mb, t.description, " + "t.Probe_Target_Description FROM (SELECT Symbol, " + "max(Mean) AS maxmean " + "FROM TissueProbeSetXRef WHERE " + "TissueProbeSetFreezeId=1 AND " + "Symbol IN (%s, %s, %s) " + "GROUP BY Symbol) AS x INNER JOIN " + "TissueProbeSetXRef AS " + "t ON t.Symbol = x.Symbol " + "AND t.Mean = x.maxmean", + ("k1", "k2", "k3")), + (("k1", "203", + "112", "xy", "20.11", + "Sample Description", + "Sample Probe Target Description"),)), + ), +) +def test_mrna_assay_tissue_data_initialisation(mocker, gene_symbols, + expected_query, + sql_fetch_all_results): + mock_conn = mocker.MagicMock() + with mock_conn.cursor() as cursor: + cursor.fetchall.return_value = sql_fetch_all_results + MrnaAssayTissueData(conn=mock_conn, gene_symbols=gene_symbols) + cursor.execute.assert_called_with(*expected_query) + + +def test_get_trait_symbol_and_tissue_values(mocker): + """Test for getting trait symbol and tissue_values""" + mock_conn = mocker.MagicMock() + with mock_conn.cursor() as cursor: + cursor.fetchall.side_effect = [ + (("k1", "203", + "112", "xy", "20.11", + "Sample Description", + "Sample Probe Target Description"),), + (("k1", "v1"), + ("k2", "v2"), + ("k3", "v3")), + ] + _m = MrnaAssayTissueData(conn=mock_conn, + gene_symbols=["k1", "k2", "k3"]) + assert _m.get_symbol_values_pairs() == { + "k1": ["v1"], + "k2": ["v2"], + "k3": ["v3"], + } + cursor.execute.assert_called_with( + "SELECT TissueProbeSetXRef.Symbol, " + "TissueProbeSetData.value FROM " + "TissueProbeSetXRef, TissueProbeSetData " + "WHERE TissueProbeSetData.Id IN (%s) " + "AND TissueProbeSetXRef.DataId = " + "TissueProbeSetData.Id", + ('112',)) diff --git a/gn2/tests/unit/base/test_species.py b/gn2/tests/unit/base/test_species.py new file mode 100644 index 00000000..d2845b25 --- /dev/null +++ b/gn2/tests/unit/base/test_species.py @@ -0,0 +1,80 @@ +"""Tests wqflask/base/species.py""" +import pytest +from gn2.base.species import TheSpecies +from gn2.base.species import IndChromosome +from gn2.base.species import Chromosomes +from collections import OrderedDict +from dataclasses import dataclass + + +@dataclass +class MockChromosome: + OrderId: int + Name: str + Length: int + + +@dataclass +class MockGroup: + name: str + + +@dataclass +class MockDataset: + group: MockGroup + + +@pytest.mark.parametrize( + ("species_name", "dataset", "expected_name", "chromosome_param"), + (("BXD", None, "BXD", 1), + (None, "Random Dataset", None, 1))) +def test_species(mocker, species_name, dataset, + expected_name, chromosome_param): + _c = mocker.patch("gn2.base.species.Chromosomes", + return_value=chromosome_param) + test_species = TheSpecies(dataset=dataset, + species_name=species_name) + _c.assert_called_with(species=species_name, + dataset=dataset) + assert test_species.name == expected_name + assert test_species.chromosomes == chromosome_param + + +@pytest.mark.parametrize( + ("name", "length", "mb_length"), + (("Test A", 10000000, 10), + ("Test B", 100, 0.0001))) +def test_create_ind_chromosome(name, length, mb_length): + _ind = IndChromosome(name=name, length=length) + assert _ind.name == name + assert _ind.length == length + assert _ind.mb_length == mb_length + + +@pytest.mark.parametrize( + ("species", "dataset", "expected_call"), + (("bxd", MockDataset(MockGroup("Random")), + ("SELECT Chr_Length.Name, Chr_Length.OrderId, Length " + "FROM Chr_Length, Species WHERE " + "Chr_Length.SpeciesId = Species.SpeciesId AND " + "Species.Name = %s " + "ORDER BY OrderId", ("Bxd",))), + (None, MockDataset(MockGroup("Random")), + ("SELECT Chr_Length.Name, Chr_Length.OrderId, " + "Length FROM Chr_Length, InbredSet WHERE " + "Chr_Length.SpeciesId = InbredSet.SpeciesId AND " + "InbredSet.Name = " + "%s ORDER BY OrderId", ("Random",))))) +def test_create_chromosomes(mocker, species, dataset, expected_call): + mock_conn = mocker.MagicMock() + with mock_conn.cursor() as cursor: + cursor.fetchall.return_value = (("1", 2, 10,), + ("2", 3, 11,), + ("4", 5, 15,),) + _c = Chromosomes(dataset=dataset, species=species) + assert _c.chromosomes(cursor) == OrderedDict([ + ("1", IndChromosome("1", 10)), + ("2", IndChromosome("2", 11)), + ("4", IndChromosome("4", 15)), + ]) + cursor.execute.assert_called_with(*expected_call) diff --git a/gn2/tests/unit/base/test_trait.py b/gn2/tests/unit/base/test_trait.py new file mode 100644 index 00000000..2adb1fdf --- /dev/null +++ b/gn2/tests/unit/base/test_trait.py @@ -0,0 +1,262 @@ +# -*- coding: utf-8 -*- +"""Tests wqflask/base/trait.py""" +import unittest +from unittest import mock + +from gn2.base.trait import GeneralTrait +from gn2.base.trait import retrieve_trait_info + + +class TestResponse: + """Mock Test Response after a request""" + @property + def content(self): + """Mock the content from Requests.get(params).content""" + return "[1, 2, 3, 4]" + + +class TestNilResponse: + """Mock Test Response after a request""" + @property + def content(self): + """Mock the content from Requests.get(params).content""" + return "{}" + + +class MockTrait(GeneralTrait): + @property + def wikidata_alias_fmt(self): + return "Mock alias" + + +class TestRetrieveTraitInfo(unittest.TestCase): + """Tests for 'retrieve_trait_info'""" + @mock.patch('gn2.base.trait.database_connection') + def test_retrieve_trait_info_with_empty_dataset(self, mock_db): + """Test that an exception is raised when dataset is empty""" + conn = mock.MagicMock() + mock_db.return_value.__enter__.return_value = conn + with self.assertRaises(ValueError): + retrieve_trait_info(trait=mock.MagicMock(), + dataset={}) + + @mock.patch('gn2.base.trait.requests.get') + @mock.patch('gn2.base.trait.g', mock.Mock()) + @mock.patch('gn2.base.trait.database_connection') + def test_retrieve_trait_info_with_empty_trait_info(self, + mock_db, + requests_mock): + """Empty trait info""" + conn = mock.MagicMock() + cursor = mock.MagicMock() + cursor.fetchone.return_value = {} + conn.cursor.return_value.__enter__.return_value = cursor + mock_db.return_value.__enter__.return_value = conn + requests_mock.return_value = TestNilResponse() + with self.assertRaises(KeyError): + retrieve_trait_info(trait=mock.MagicMock(), + dataset=mock.MagicMock()) + + @mock.patch('gn2.base.trait.requests.get') + @mock.patch('gn2.base.trait.g', mock.Mock()) + @mock.patch('gn2.base.trait.database_connection') + def test_retrieve_trait_info_with_non_empty_trait_info(self, + mock_db, + requests_mock): + """Test that attributes are set""" + mock_dataset = mock.MagicMock() + conn = mock.MagicMock() + cursor = mock.MagicMock() + cursor.fetchone.return_value = [1, 2, 3, 4] + conn.cursor.return_value.__enter__.return_value = cursor + mock_db.return_value.__enter__.return_value = conn + requests_mock.return_value = TestResponse() + type(mock_dataset).display_fields = mock.PropertyMock( + return_value=["a", "b", "c", "d"]) + test_trait = retrieve_trait_info(trait=MockTrait(dataset=mock_dataset), + dataset=mock_dataset) + self.assertEqual(test_trait.a, 1) + self.assertEqual(test_trait.b, 2) + self.assertEqual(test_trait.c, 3) + self.assertEqual(test_trait.d, 4) + + @mock.patch('gn2.base.trait.requests.get') + @mock.patch('gn2.base.trait.g', mock.Mock()) + @mock.patch('gn2.base.trait.database_connection') + def test_retrieve_trait_info_utf8_parsing(self, + mock_db, + requests_mock): + """Test that utf-8 strings are parsed correctly""" + utf_8_string = "test_string" + conn = mock.MagicMock() + mock_db.return_value.__enter__.return_value = conn + mock_dataset = mock.MagicMock() + requests_mock.return_value = TestResponse() + type(mock_dataset).display_fields = mock.PropertyMock( + return_value=["a", "b", "c", "d"]) + type(mock_dataset).type = 'Publish' + + mock_trait = MockTrait( + dataset=mock_dataset, + pre_publication_description=utf_8_string + ) + trait_attrs = { + "group_code": "test_code", + "pre_publication_description": "test_pre_pub", + "pre_publication_abbreviation": "ファイルを画é¢æ¯Žã«è¦‹ã¦è¡Œãã«ã¯ã€æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã‚’使ã„ã¾ã™ã€‚", + "post_publication_description": None, + "pubmed_id": None, + 'year': "2020", + "authors": "Jane Doe ã‹ã„ã¨", + } + for key, val in list(trait_attrs.items()): + setattr(mock_trait, key, val) + test_trait = retrieve_trait_info(trait=mock_trait, + dataset=mock_dataset) + self.assertEqual(test_trait.abbreviation, + "ファイルを画é¢æ¯Žã«è¦‹ã¦è¡Œãã«ã¯ã€æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã‚’使ã„ã¾ã™ã€‚") + self.assertEqual(test_trait.authors, + "Jane Doe ã‹ã„ã¨") + + + @unittest.skip("Too complicated") + @mock.patch('gn2.base.trait.requests.get') + @mock.patch('gn2.base.trait.database_connection') + @mock.patch('gn2.base.trait.get_resource_id') + def test_retrieve_trait_info_with_non_empty_lrs(self, + resource_id_mock, + mock_db, + requests_mock): + """Test retrieve trait info when lrs has a value""" + resource_id_mock.return_value = 1 + conn = mock.MagicMock() + mock_db.return_value.__enter__.return_value = conn + with conn.cursor() as cursor: + cursor.fetchone.side_effect = [ + # trait_info = g.db.execute(query).fetchone() + [1, 2, 3, 4], + # trait_qtl = g.db.execute(query).fetchone() + [1, 2.37, 3, 4, 5], + # trait_info = g.db.execute(query).fetchone() + [2.7333, 2.1204] + ] + requests_mock.return_value = None + + mock_dataset = mock.MagicMock() + type(mock_dataset).display_fields = mock.PropertyMock( + return_value=["a", "b", "c", "d"]) + type(mock_dataset).type = "ProbeSet" + type(mock_dataset).name = "RandomName" + + mock_trait = MockTrait( + dataset=mock_dataset, + pre_publication_description="test_string" + ) + trait_attrs = { + "description": "some description", + "probe_target_description": "some description", + "cellid": False, + "chr": 2.733, + "mb": 2.1204 + } + + for key, val in list(trait_attrs.items()): + setattr(mock_trait, key, val) + test_trait = retrieve_trait_info(trait=mock_trait, + dataset=mock_dataset, + get_qtl_info=True) + self.assertEqual(test_trait.LRS_score_repr, + "2.4") + + @unittest.skip("Too complicated") + @mock.patch('gn2.base.trait.requests.get') + @mock.patch('gn2.base.trait.g') + @mock.patch('gn2.base.trait.get_resource_id') + def test_retrieve_trait_info_with_empty_lrs_field(self, + resource_id_mock, + g_mock, + requests_mock): + """Test retrieve trait info with empty lrs field""" + resource_id_mock.return_value = 1 + g_mock.db.execute.return_value.fetchone = mock.Mock() + g_mock.db.execute.return_value.fetchone.side_effect = [ + [1, 2, 3, 4], # trait_info = g.db.execute(query).fetchone() + [1, None, 3, 4, 5], # trait_qtl = g.db.execute(query).fetchone() + [2, 3] # trait_info = g.db.execute(query).fetchone() + ] + requests_mock.return_value = None + + mock_dataset = mock.MagicMock() + type(mock_dataset).display_fields = mock.PropertyMock( + return_value=["a", "b", "c", "d"]) + type(mock_dataset).type = "ProbeSet" + type(mock_dataset).name = "RandomName" + + mock_trait = MockTrait( + dataset=mock_dataset, + pre_publication_description="test_string" + ) + trait_attrs = { + "description": "some description", + "probe_target_description": "some description", + "cellid": False, + "chr": 2.733, + "mb": 2.1204 + } + + for key, val in list(trait_attrs.items()): + setattr(mock_trait, key, val) + test_trait = retrieve_trait_info(trait=mock_trait, + dataset=mock_dataset, + get_qtl_info=True) + self.assertEqual(test_trait.LRS_score_repr, + "N/A") + self.assertEqual(test_trait.LRS_location_repr, + "Chr2: 3.000000") + + @unittest.skip("Too complicated") + @mock.patch('gn2.base.trait.requests.get') + @mock.patch('gn2.base.trait.g') + @mock.patch('gn2.base.trait.get_resource_id') + def test_retrieve_trait_info_with_empty_chr_field(self, + resource_id_mock, + g_mock, + requests_mock): + """Test retrieve trait info with empty chr field""" + resource_id_mock.return_value = 1 + g_mock.db.execute.return_value.fetchone = mock.Mock() + g_mock.db.execute.return_value.fetchone.side_effect = [ + [1, 2, 3, 4], # trait_info = g.db.execute(query).fetchone() + [1, 2, 3, 4, 5], # trait_qtl = g.db.execute(query).fetchone() + [None, 3] # trait_info = g.db.execute(query).fetchone() + ] + + requests_mock.return_value = None + + mock_dataset = mock.MagicMock() + type(mock_dataset).display_fields = mock.PropertyMock( + return_value=["a", "b", "c", "d"]) + type(mock_dataset).type = "ProbeSet" + type(mock_dataset).name = "RandomName" + + mock_trait = MockTrait( + dataset=mock_dataset, + pre_publication_description="test_string" + ) + trait_attrs = { + "description": "some description", + "probe_target_description": "some description", + "cellid": False, + "chr": 2.733, + "mb": 2.1204 + } + + for key, val in list(trait_attrs.items()): + setattr(mock_trait, key, val) + test_trait = retrieve_trait_info(trait=mock_trait, + dataset=mock_dataset, + get_qtl_info=True) + self.assertEqual(test_trait.LRS_score_repr, + "N/A") + self.assertEqual(test_trait.LRS_location_repr, + "N/A") diff --git a/gn2/tests/unit/base/test_webqtl_case_data.py b/gn2/tests/unit/base/test_webqtl_case_data.py new file mode 100644 index 00000000..250b8358 --- /dev/null +++ b/gn2/tests/unit/base/test_webqtl_case_data.py @@ -0,0 +1,40 @@ +"""Tests for wqflask/base/webqtlCaseData.py""" +import unittest + +from gn2.wqflask import app # Required because of utility.tools in webqtlCaseData.py +from gn2.base.webqtlCaseData import webqtlCaseData + + +class TestWebqtlCaseData(unittest.TestCase): + """Tests for WebqtlCaseData class""" + + def setUp(self): + self.w = webqtlCaseData(name="Test", + value=0, + variance=0.0, + num_cases=10, + name2="Test2") + + def test_webqtl_case_data_repr(self): + self.assertEqual( + repr(self.w), + "<webqtlCaseData> value=0.000 variance=0.000 ndata=10 name=Test name2=Test2" + ) + + def test_class_outlier(self): + self.assertEqual(self.w.class_outlier, "") + + def test_display_value(self): + self.assertEqual(self.w.display_value, "0.000") + self.w.value = None + self.assertEqual(self.w.display_value, "x") + + def test_display_variance(self): + self.assertEqual(self.w.display_variance, "0.000") + self.w.variance = None + self.assertEqual(self.w.display_variance, "x") + + def test_display_num_cases(self): + self.assertEqual(self.w.display_num_cases, "10") + self.w.num_cases = None + self.assertEqual(self.w.display_num_cases, "x") diff --git a/gn2/tests/unit/utility/__init__.py b/gn2/tests/unit/utility/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/utility/__init__.py diff --git a/gn2/tests/unit/utility/test_authentication_tools.py b/gn2/tests/unit/utility/test_authentication_tools.py new file mode 100644 index 00000000..90945b0f --- /dev/null +++ b/gn2/tests/unit/utility/test_authentication_tools.py @@ -0,0 +1,192 @@ +"""Tests for authentication tools""" +import unittest +from unittest import mock + +from gn2.utility.authentication_tools import check_resource_availability +from gn2.utility.authentication_tools import add_new_resource + + +class TestResponse: + """Mock Test Response after a request""" + @property + def content(self): + """Mock the content from Requests.get(params).content""" + return '["foo"]' + + +class TestUser: + """Mock user""" + @property + def user_id(self): + """Mockes user id. Used in Flask.g.user_session.user_id""" + return b"Jane" + +user_id = b"Jane" + + +class TestUserSession: + """Mock user session""" + @property + def user_session(self): + """Mock user session. Mocks Flask.g.user_session object""" + return TestUser() + + +def mock_add_resource(resource_ob, update=False): + return resource_ob + + +class TestCheckResourceAvailability(unittest.TestCase): + """Test methods related to checking the resource availability""" + @mock.patch('gn2.utility.authentication_tools.add_new_resource') + @mock.patch('gn2.utility.authentication_tools.Redis') + @mock.patch('gn2.utility.authentication_tools.g', TestUserSession()) + @mock.patch('gn2.utility.authentication_tools.get_resource_id') + def test_check_resource_availability_default_mask( + self, + resource_id_mock, + redis_mock, + add_new_resource_mock): + """Test the resource availability with default mask""" + + resource_id_mock.return_value = 1 + redis_mock.smembers.return_value = [] + test_dataset = mock.MagicMock() + type(test_dataset).type = mock.PropertyMock(return_value="Test") + add_new_resource_mock.return_value = {"default_mask": 2} + self.assertEqual(check_resource_availability(test_dataset, user_id), 2) + + @mock.patch('gn2.utility.authentication_tools.requests.get') + @mock.patch('gn2.utility.authentication_tools.add_new_resource') + @mock.patch('gn2.utility.authentication_tools.Redis') + @mock.patch('gn2.utility.authentication_tools.g', TestUserSession()) + @mock.patch('gn2.utility.authentication_tools.get_resource_id') + def test_check_resource_availability_non_default_mask( + self, + resource_id_mock, + redis_mock, + add_new_resource_mock, + requests_mock): + """Test the resource availability with non-default mask""" + resource_id_mock.return_value = 1 + redis_mock.smembers.return_value = [] + add_new_resource_mock.return_value = {"default_mask": 2} + requests_mock.return_value = TestResponse() + test_dataset = mock.MagicMock() + type(test_dataset).type = mock.PropertyMock(return_value="Test") + self.assertEqual(check_resource_availability(test_dataset, user_id), + ['foo']) + + @mock.patch('gn2.utility.authentication_tools.webqtlConfig.SUPER_PRIVILEGES', + "SUPERUSER") + @mock.patch('gn2.utility.authentication_tools.requests.get') + @mock.patch('gn2.utility.authentication_tools.add_new_resource') + @mock.patch('gn2.utility.authentication_tools.Redis') + @mock.patch('gn2.utility.authentication_tools.g', TestUserSession()) + @mock.patch('gn2.utility.authentication_tools.get_resource_id') + def test_check_resource_availability_of_super_user( + self, + resource_id_mock, + redis_mock, + add_new_resource_mock, + requests_mock): + """Test the resource availability if the user is the super user""" + resource_id_mock.return_value = 1 + redis_mock.smembers.return_value = [b"Jane"] + add_new_resource_mock.return_value = {"default_mask": 2} + requests_mock.return_value = TestResponse() + test_dataset = mock.MagicMock() + type(test_dataset).type = mock.PropertyMock(return_value="Test") + self.assertEqual(check_resource_availability(test_dataset, user_id), + "SUPERUSER") + + @mock.patch('gn2.utility.authentication_tools.webqtlConfig.DEFAULT_PRIVILEGES', + "John Doe") + def test_check_resource_availability_string_dataset(self): + """Test the resource availability if the dataset is a string""" + self.assertEqual(check_resource_availability("Test", user_id), + "John Doe") + + @mock.patch('gn2.utility.authentication_tools.webqtlConfig.DEFAULT_PRIVILEGES', + "John Doe") + def test_check_resource_availability_temp(self): + """Test the resource availability if the dataset is a string""" + test_dataset = mock.MagicMock() + type(test_dataset).type = mock.PropertyMock(return_value="Temp") + self.assertEqual(check_resource_availability(test_dataset, user_id), + "John Doe") + + +class TestAddNewResource(unittest.TestCase): + """Test cases for add_new_resource method""" + @mock.patch('gn2.utility.authentication_tools.webqtlConfig.DEFAULT_PRIVILEGES', + "John Doe") + @mock.patch('gn2.utility.authentication_tools.add_resource', mock_add_resource) + @mock.patch('gn2.utility.authentication_tools.get_group_code') + def test_add_new_resource_if_publish_datatype(self, group_code_mock): + """Test add_new_resource if dataset type is 'publish'""" + group_code_mock.return_value = "Test" + test_dataset = mock.MagicMock() + type(test_dataset).type = mock.PropertyMock(return_value="Publish") + type(test_dataset).id = mock.PropertyMock(return_value=10) + expected_value = { + "owner_id": "none", + "default_mask": "John Doe", + "group_masks": {}, + "name": "Test_None", + "data": { + "dataset": 10, + "trait": None + }, + "type": "dataset-publish" + } + self.assertEqual(add_new_resource(test_dataset), + expected_value) + + @mock.patch('gn2.utility.authentication_tools.webqtlConfig.DEFAULT_PRIVILEGES', + "John Doe") + @mock.patch('gn2.utility.authentication_tools.add_resource', mock_add_resource) + @mock.patch('gn2.utility.authentication_tools.get_group_code') + def test_add_new_resource_if_geno_datatype(self, group_code_mock): + """Test add_new_resource if dataset type is 'geno'""" + group_code_mock.return_value = "Test" + test_dataset = mock.MagicMock() + type(test_dataset).name = mock.PropertyMock(return_value="Geno") + type(test_dataset).type = mock.PropertyMock(return_value="Geno") + type(test_dataset).id = mock.PropertyMock(return_value=20) + expected_value = { + "owner_id": "none", + "default_mask": "John Doe", + "group_masks": {}, + "name": "Geno", + "data": { + "dataset": 20, + }, + "type": "dataset-geno" + } + self.assertEqual(add_new_resource(test_dataset), + expected_value) + + @mock.patch('gn2.utility.authentication_tools.webqtlConfig.DEFAULT_PRIVILEGES', + "John Doe") + @mock.patch('gn2.utility.authentication_tools.add_resource', mock_add_resource) + @mock.patch('gn2.utility.authentication_tools.get_group_code') + def test_add_new_resource_if_other_datatype(self, group_code_mock): + """Test add_new_resource if dataset type is not 'geno' or 'publish'""" + group_code_mock.return_value = "Test" + test_dataset = mock.MagicMock() + type(test_dataset).name = mock.PropertyMock(return_value="Geno") + type(test_dataset).type = mock.PropertyMock(return_value="other") + type(test_dataset).id = mock.PropertyMock(return_value=20) + expected_value = { + "owner_id": "none", + "default_mask": "John Doe", + "group_masks": {}, + "name": "Geno", + "data": { + "dataset": 20, + }, + "type": "dataset-probeset" + } + self.assertEqual(add_new_resource(test_dataset), + expected_value) diff --git a/gn2/tests/unit/utility/test_chunks.py b/gn2/tests/unit/utility/test_chunks.py new file mode 100644 index 00000000..1b5a9413 --- /dev/null +++ b/gn2/tests/unit/utility/test_chunks.py @@ -0,0 +1,20 @@ +"""Test chunking""" + +import unittest + +from gn2.utility.chunks import divide_into_chunks + + +class TestChunks(unittest.TestCase): + "Test Utility method for chunking" + + def test_divide_into_chunks(self): + "Check that a list is chunked correctly" + self.assertEqual(divide_into_chunks([1, 2, 7, 3, 22, 8, 5, 22, 333], 3), + [[1, 2, 7], [3, 22, 8], [5, 22, 333]]) + self.assertEqual(divide_into_chunks([1, 2, 7, 3, 22, 8, 5, 22, 333], 4), + [[1, 2, 7], [3, 22, 8], [5, 22, 333]]) + self.assertEqual(divide_into_chunks([1, 2, 7, 3, 22, 8, 5, 22, 333], 5), + [[1, 2], [7, 3], [22, 8], [5, 22], [333]]) + self.assertEqual(divide_into_chunks([], 5), + [[]]) diff --git a/gn2/tests/unit/utility/test_corestats.py b/gn2/tests/unit/utility/test_corestats.py new file mode 100644 index 00000000..c0eaf566 --- /dev/null +++ b/gn2/tests/unit/utility/test_corestats.py @@ -0,0 +1,55 @@ +"""Test Core Stats""" + +import unittest + +from gn2.utility.corestats import Stats + + +class TestChunks(unittest.TestCase): + "Test Utility method for chunking" + + def setUp(self): + self.stat_test = Stats((x for x in range(1, 11))) + + def test_stats_sum(self): + """ Test sequence sum """ + self.assertEqual(self.stat_test.sum(), 55) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.sum(), None) + + def test_stats_count(self): + """ Test sequence count """ + self.assertEqual(self.stat_test.count(), 10) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.count(), 0) + + def test_stats_min(self): + """ Test min value in sequence""" + self.assertEqual(self.stat_test.min(), 1) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.min(), None) + + def test_stats_max(self): + """ Test max value in sequence """ + self.assertEqual(self.stat_test.max(), 10) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.max(), None) + + def test_stats_avg(self): + """ Test avg of sequence """ + self.assertEqual(self.stat_test.avg(), 5.5) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.avg(), None) + + def test_stats_stdev(self): + """ Test standard deviation of sequence """ + self.assertEqual(self.stat_test.stdev(), 3.0276503540974917) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.stdev(), None) + + def test_stats_percentile(self): + """ Test percentile of sequence """ + self.assertEqual(self.stat_test.percentile(20), 3.0) + self.assertEqual(self.stat_test.percentile(101), None) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.percentile(20), None) diff --git a/gn2/tests/unit/utility/test_corr_result_helpers.py b/gn2/tests/unit/utility/test_corr_result_helpers.py new file mode 100644 index 00000000..59260ba6 --- /dev/null +++ b/gn2/tests/unit/utility/test_corr_result_helpers.py @@ -0,0 +1,32 @@ +""" Test correlation helper methods """ + +import unittest +from gn2.utility.corr_result_helpers import normalize_values, common_keys, normalize_values_with_samples + + +class TestCorrelationHelpers(unittest.TestCase): + """Test methods for normalising lists""" + + def test_normalize_values(self): + """Test that a list is normalised correctly""" + self.assertEqual( + normalize_values([2.3, None, None, 3.2, 4.1, 5], [ + 3.4, 7.2, 1.3, None, 6.2, 4.1]), + ([2.3, 4.1, 5], [3.4, 6.2, 4.1], 3) + ) + + def test_common_keys(self): + """Test that common keys are returned as a list""" + a = dict(BXD1=9.113, BXD2=9.825, BXD14=8.985, BXD15=9.300) + b = dict(BXD1=9.723, BXD3=9.825, BXD14=9.124, BXD16=9.300) + self.assertEqual(sorted(common_keys(a, b)), ['BXD1', 'BXD14']) + + def test_normalize_values_with_samples(self): + """Test that a sample(dict) is normalised correctly""" + self.assertEqual( + normalize_values_with_samples( + dict(BXD1=9.113, BXD2=9.825, BXD14=8.985, + BXD15=9.300, BXD20=9.300), + dict(BXD1=9.723, BXD3=9.825, BXD14=9.124, BXD16=9.300)), + (({'BXD1': 9.113, 'BXD14': 8.985}, {'BXD1': 9.723, 'BXD14': 9.124}, 2)) + ) diff --git a/gn2/tests/unit/utility/test_formatting.py b/gn2/tests/unit/utility/test_formatting.py new file mode 100644 index 00000000..aad9735d --- /dev/null +++ b/gn2/tests/unit/utility/test_formatting.py @@ -0,0 +1,33 @@ +import unittest +from gn2.utility.formatting import numify, commify + + +class TestFormatting(unittest.TestCase): + """Test formatting numbers by numifying or commifying""" + + def test_numify(self): + "Test that a number is correctly converted to a English readable string" + self.assertEqual(numify(1, 'item', 'items'), + 'one item') + self.assertEqual(numify(2, 'book'), 'two') + self.assertEqual(numify(2, 'book', 'books'), 'two books') + self.assertEqual(numify(0, 'book', 'books'), 'zero books') + self.assertEqual(numify(0), 'zero') + self.assertEqual(numify(5), 'five') + self.assertEqual(numify(14, 'book', 'books'), '14 books') + self.assertEqual(numify(999, 'book', 'books'), '999 books') + self.assertEqual(numify(1000000, 'book', 'books'), '1,000,000 books') + self.assertEqual(numify(1956), '1956') + + def test_commify(self): + "Test that commas are added correctly" + self.assertEqual(commify(1), '1') + self.assertEqual(commify(123), '123') + self.assertEqual(commify(1234), '1234') + self.assertEqual(commify(12345), '12,345') + self.assertEqual(commify(1234567890), '1,234,567,890') + self.assertEqual(commify(123.0), '123.0') + self.assertEqual(commify(1234.5), '1234.5') + self.assertEqual(commify(1234.56789), '1234.56789') + self.assertEqual(commify(123456.789), '123,456.789') + self.assertEqual(commify(None), None) diff --git a/gn2/tests/unit/utility/test_hmac.py b/gn2/tests/unit/utility/test_hmac.py new file mode 100644 index 00000000..f148ea5b --- /dev/null +++ b/gn2/tests/unit/utility/test_hmac.py @@ -0,0 +1,51 @@ +"""Test hmac utility functions""" + +import unittest +from unittest import mock + +from gn2.utility.hmac import data_hmac +from gn2.utility.hmac import url_for_hmac +from gn2.utility.hmac import hmac_creation + + +class TestHmacUtil(unittest.TestCase): + """Test Utility method for hmac creation""" + + @mock.patch("gn2.utility.hmac.app.config", {'SECRET_HMAC_CODE': "secret"}) + def test_hmac_creation(self): + """Test hmac creation with a utf-8 string""" + self.assertEqual(hmac_creation("ファイ"), "7410466338cfe109e946") + + @mock.patch("gn2.utility.hmac.app.config", + {'SECRET_HMAC_CODE': ('\x08\xdf\xfa\x93N\x80' + '\xd9\\H@\\\x9f`\x98d^' + '\xb4a;\xc6OM\x946a\xbc' + '\xfc\x80:*\xebc')}) + def test_hmac_creation_with_cookie(self): + """Test hmac creation with a cookie""" + cookie = "3f4c1dbf-5b56-4260-87d6-f35445bda37e:af4fcf5eace9e7c864ce" + uuid_, _, signature = cookie.partition(":") + self.assertEqual( + hmac_creation(uuid_), + "af4fcf5eace9e7c864ce") + + @mock.patch("gn2.utility.hmac.app.config", {'SECRET_HMAC_CODE': "secret"}) + def test_data_hmac(self): + """Test data_hmac fn with a utf-8 string""" + self.assertEqual(data_hmac("ファイ"), "ファイ:7410466338cfe109e946") + + @mock.patch("gn2.utility.hmac.app.config", {'SECRET_HMAC_CODE': "secret"}) + @mock.patch("gn2.utility.hmac.url_for") + def test_url_for_hmac_with_plain_url(self, mock_url): + """Test url_for_hmac without params""" + mock_url.return_value = "https://mock_url.com/ファイ/" + self.assertEqual(url_for_hmac("ファイ"), + "https://mock_url.com/ファイ/?hm=05bc39e659b1948f41e7") + + @mock.patch("gn2.utility.hmac.app.config", {'SECRET_HMAC_CODE': "secret"}) + @mock.patch("gn2.utility.hmac.url_for") + def test_url_for_hmac_with_param_in_url(self, mock_url): + """Test url_for_hmac with params""" + mock_url.return_value = "https://mock_url.com/?ファイ=1" + self.assertEqual(url_for_hmac("ファイ"), + "https://mock_url.com/?ファイ=1&hm=4709c1708270644aed79") diff --git a/gn2/tests/unit/utility/test_type_checking.py b/gn2/tests/unit/utility/test_type_checking.py new file mode 100644 index 00000000..e7b8e953 --- /dev/null +++ b/gn2/tests/unit/utility/test_type_checking.py @@ -0,0 +1,54 @@ +import unittest +from gn2.utility.type_checking import is_float +from gn2.utility.type_checking import is_int +from gn2.utility.type_checking import is_str +from gn2.utility.type_checking import get_float +from gn2.utility.type_checking import get_int +from gn2.utility.type_checking import get_string + + +class TestTypeChecking(unittest.TestCase): + def test_is_float(self): + floats = [2, 1.2, '3.1'] + not_floats = ["String", None, [], ()] + for flt in floats: + results = is_float(flt) + self.assertTrue(results) + for nflt in not_floats: + results = is_float(nflt) + self.assertFalse(results) + + def test_is_int(self): + int_values = [1, 1.1] + not_int_values = ["string", None, [], "1.1"] + for int_val in int_values: + results = is_int(int_val) + self.assertTrue(results) + for not_int in not_int_values: + results = is_int(not_int) + self.assertFalse(results) + + def test_is_str(self): + string_values = [1, False, [], {}, "string_value"] + falsey_values = [None] + for string_val in string_values: + results = is_str(string_val) + self.assertTrue(results) + for non_string in falsey_values: + results = is_str(non_string) + self.assertFalse(results) + + def test_get_float(self): + vars_object = {"min_value": "12"} + results = get_float(vars_object, "min_value") + self.assertEqual(results, 12.0) + + def test_get_int(self): + vars_object = {"lx_value": "1"} + results = get_int(vars_object, "lx_value") + self.assertEqual(results, 1) + + def test_get_string(self): + string_object = {"mx_value": 1} + results = get_string(string_object, "mx_value") + self.assertEqual(results, "1")
\ No newline at end of file diff --git a/gn2/tests/unit/wqflask/__init__.py b/gn2/tests/unit/wqflask/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/wqflask/__init__.py diff --git a/gn2/tests/unit/wqflask/api/__init__.py b/gn2/tests/unit/wqflask/api/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/wqflask/api/__init__.py diff --git a/gn2/tests/unit/wqflask/api/test_correlation.py b/gn2/tests/unit/wqflask/api/test_correlation.py new file mode 100644 index 00000000..2a1907af --- /dev/null +++ b/gn2/tests/unit/wqflask/api/test_correlation.py @@ -0,0 +1,175 @@ +import unittest +from unittest import mock +from gn2.wqflask import app +from collections import OrderedDict +from gn2.wqflask.api.correlation import init_corr_params +from gn2.wqflask.api.correlation import convert_to_mouse_gene_id +from gn2.wqflask.api.correlation import do_literature_correlation_for_all_traits +from gn2.wqflask.api.correlation import get_sample_r_and_p_values +from gn2.wqflask.api.correlation import calculate_results + + +class AttributeSetter: + def __init__(self, obj): + for k, v in obj.items(): + setattr(self, k, v) + + +class MockDataset(AttributeSetter): + def get_trait_data(self): + return None + + def retrieve_genes(self, id=None): + return {"TT-1": "GH-1", "TT-2": "GH-2", "TT-3": "GH-3"} + + +class TestCorrelations(unittest.TestCase): + def setUp(self): + self.app_context = app.app_context() + self.app_context.push() + + def tearDown(self): + self.app_context.pop() + + def test_init_corr_params(self): + start_vars = {"return_count": "3", "type": "T1", "method": "spearman"} + + corr_params_results = init_corr_params(start_vars=start_vars) + expected_results = {"return_count": 3, "type": "T1", "method": "spearman"} + + self.assertEqual(corr_params_results, expected_results) + + @mock.patch("gn2.wqflask.api.correlation.database_connection") + def test_convert_to_mouse_gene_id(self, mock_db): + conn = mock.MagicMock() + mock_db.return_value.__enter__.return_value = conn + with conn.cursor() as cursor: + cursor.fetchone.side_effect = [("MG-1",), ("MG-2",)] + + self.assertEqual( + convert_to_mouse_gene_id(species="Other", gene_id=""), None + ) + self.assertEqual( + convert_to_mouse_gene_id(species="mouse", gene_id="MG-4"), "MG-4" + ) + self.assertEqual( + convert_to_mouse_gene_id(species="rat", gene_id="R1"), "MG-1" + ) + self.assertEqual( + convert_to_mouse_gene_id(species="human", gene_id="H1"), "MG-2" + ) + + @mock.patch("gn2.wqflask.api.correlation.database_connection") + @mock.patch("gn2.wqflask.api.correlation.convert_to_mouse_gene_id") + def test_do_literature_correlation_for_all_traits( + self, mock_convert_to_mouse_geneid, mock_db + ): + mock_convert_to_mouse_geneid.side_effect = ["MG-1", "MG-2;", "MG-3", "MG-4"] + + trait_geneid_dict = {"TT-1": "GH-1", "TT-2": "GH-2", "TT-3": "GH-3"} + conn = mock.MagicMock() + mock_db.return_value.__enter__.return_value = conn + with conn.cursor() as cursor: + cursor.fetchone.side_effect = [("V1",), ("V2",), ("V3",)] + this_trait = AttributeSetter({"geneid": "GH-1"}) + target_dataset = AttributeSetter( + {"group": AttributeSetter({"species": "rat"})} + ) + results = do_literature_correlation_for_all_traits( + this_trait=this_trait, + target_dataset=target_dataset, + trait_geneid_dict=trait_geneid_dict, + corr_params={}, + ) + expected_results = { + "TT-1": ["GH-1", 0], + "TT-2": ["GH-2", "V1"], + "TT-3": ["GH-3", "V2"], + } + self.assertEqual(results, expected_results) + + @mock.patch("gn2.wqflask.api.correlation.corr_result_helpers.normalize_values") + def test_get_sample_r_and_p_values(self, mock_normalize): + + group = AttributeSetter( + {"samplelist": ["S1", "S2", "S3", "S4", "S5", "S6", "S7"]} + ) + target_dataset = AttributeSetter({"group": group}) + + target_vals = [3.4, 6.2, 4.1, 3.4, 1.2, 5.6] + trait_data = { + "S1": AttributeSetter({"value": 2.3}), + "S2": AttributeSetter({"value": 1.1}), + "S3": AttributeSetter({"value": 6.3}), + "S4": AttributeSetter({"value": 3.6}), + "S5": AttributeSetter({"value": 4.1}), + "S6": AttributeSetter({"value": 5.0}), + } + this_trait = AttributeSetter({"data": trait_data}) + mock_normalize.return_value = ( + [2.3, 1.1, 6.3, 3.6, 4.1, 5.0], + [3.4, 6.2, 4.1, 3.4, 1.2, 5.6], + 6, + ) + mock_normalize.side_effect = [ + ([2.3, 1.1, 6.3, 3.6, 4.1, 5.0], [3.4, 6.2, 4.1, 3.4, 1.2, 5.6], 6), + ([2.3, 1.1, 6.3, 3.6, 4.1, 5.0], [3.4, 6.2, 4.1, 3.4, 1.2, 5.6], 6), + ([2.3, 1.1, 1.4], [3.4, 6.2, 4.1], 3), + ] + + results_pearsonr = get_sample_r_and_p_values( + this_trait=this_trait, + this_dataset={}, + target_vals=target_vals, + target_dataset=target_dataset, + type="pearson", + ) + results_spearmanr = get_sample_r_and_p_values( + this_trait=this_trait, + this_dataset={}, + target_vals=target_vals, + target_dataset=target_dataset, + type="spearman", + ) + results_num_overlap = get_sample_r_and_p_values( + this_trait=this_trait, + this_dataset={}, + target_vals=target_vals, + target_dataset=target_dataset, + type="pearson", + ) + expected_pearsonr = [-0.21618688834430866, 0.680771605997119, 6] + expected_spearmanr = [-0.11595420713048969, 0.826848213385815, 6] + for i, val in enumerate(expected_pearsonr): + self.assertAlmostEqual(val, results_pearsonr[i], 4) + for i, val in enumerate(expected_spearmanr): + self.assertAlmostEqual(val, results_spearmanr[i], 4) + self.assertEqual(results_num_overlap, None) + + @mock.patch("gn2.wqflask.api.correlation.do_literature_correlation_for_all_traits") + def test_calculate_results(self, literature_correlation): + + literature_correlation.return_value = { + "TT-1": ["GH-1", 0], + "TT-2": ["GH-2", 3], + "TT-3": ["GH-3", 1], + } + + this_dataset = MockDataset({"group": AttributeSetter({"species": "rat"})}) + target_dataset = MockDataset({"group": AttributeSetter({"species": "rat"})}) + this_trait = AttributeSetter({"geneid": "GH-1"}) + corr_params = {"type": "literature"} + sorted_results = calculate_results( + this_trait=this_trait, + this_dataset=this_dataset, + target_dataset=target_dataset, + corr_params=corr_params, + ) + expected_results = { + "TT-2": ["GH-2", 3], + "TT-3": ["GH-3", 1], + "TT-1": ["GH-1", 0], + } + + self.assertTrue(isinstance(sorted_results, OrderedDict)) + self.assertEqual(dict(sorted_results), expected_results) diff --git a/gn2/tests/unit/wqflask/api/test_gen_menu.py b/gn2/tests/unit/wqflask/api/test_gen_menu.py new file mode 100644 index 00000000..2416b3f6 --- /dev/null +++ b/gn2/tests/unit/wqflask/api/test_gen_menu.py @@ -0,0 +1,423 @@ +"""Test cases for wqflask.api.gen_menu""" +import unittest +from unittest import mock + +from gn2.wqflask.api.gen_menu import gen_dropdown_json +from gn2.wqflask.api.gen_menu import get_groups +from gn2.wqflask.api.gen_menu import get_types +from gn2.wqflask.api.gen_menu import get_datasets +from gn2.wqflask.api.gen_menu import phenotypes_exist +from gn2.wqflask.api.gen_menu import genotypes_exist +from gn2.wqflask.api.gen_menu import build_datasets +from gn2.wqflask.api.gen_menu import build_types + + +class TestGenMenu(unittest.TestCase): + """Tests for the gen_menu module""" + + def setUp(self): + self.test_group = { + 'mouse': [ + ['H_T1', + 'H_T', + 'Family:DescriptionA' + ], + ['H_T2', "H_T'", 'Family:None'] + ], + 'human': [ + ['BXD', 'BXD', 'Family:None'], + ['HLC', 'Liver: Normal Gene Expression with Genotypes (Merck)', + 'Family:Test'] + ] + } + + self.test_type = { + 'mouse': { + 'H_T2': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']], + 'H_T1': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']] + }, + 'human': { + 'HLC': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']], + 'BXD': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']] + } + } + + def test_get_groups(self): + """Test that species groups are grouped correctly""" + db_mock = mock.MagicMock() + with db_mock.cursor() as cursor: + cursor.fetchall.side_effect = [ + # Mouse + (('BXD', 'BXD', None), + ('HLC', ('Liver: Normal Gene Expression ' + 'with Genotypes (Merck)'), + 'Test')), + # Human + (('H_T1', "H_T", "DescriptionA"), + ('H_T2', "H_T'", None)) + ] + self.assertEqual(get_groups([["human", "Human"], + ["mouse", "Mouse"]], + db_mock), + self.test_group) + + for name in ["mouse", "human"]: + cursor.execute.assert_any_call( + ("SELECT InbredSet.Name, InbredSet.FullName, " + "IFNULL(InbredSet.Family, 'None') " + "FROM InbredSet, Species WHERE Species.Name " + "= '{}' AND InbredSet.SpeciesId = Species.Id GROUP by " + "InbredSet.Name ORDER BY IFNULL(InbredSet.FamilyOrder, " + "InbredSet.FullName) ASC, IFNULL(InbredSet.Family, " + "InbredSet.FullName) ASC, InbredSet.FullName ASC, " + "InbredSet.MenuOrderId ASC").format(name) + ) + + def test_phenotypes_exist_called_with_correct_query(self): + """Test that phenotypes_exist is called with the correct query""" + db_mock = mock.MagicMock() + with db_mock.cursor() as cursor: + cursor.fetchone.return_value = None + phenotypes_exist("test", db_mock) + cursor.execute.assert_called_with( + "SELECT Name FROM PublishFreeze " + "WHERE PublishFreeze.Name = 'testPublish'" + ) + + def test_phenotypes_exist_with_falsy_values(self): + """Test that phenotype check returns correctly when given + a None value""" + db_mock = mock.MagicMock() + with db_mock.cursor() as cursor: + for x in [None, False, (), [], ""]: + cursor.fetchone.return_value = x + self.assertFalse(phenotypes_exist("test", db_mock)) + + def test_phenotypes_exist_with_truthy_value(self): + """Test that phenotype check returns correctly when given Truthy""" + db_mock = mock.MagicMock() + with db_mock.cursor() as conn: + with conn.cursor() as cursor: + for x in ["x", ("result"), ["result"], [1]]: + cursor.fetchone.return_value = (x) + self.assertTrue(phenotypes_exist("test", db_mock)) + + def test_genotypes_exist_called_with_correct_query(self): + """Test that genotypes_exist is called with the correct query""" + db_mock = mock.MagicMock() + with db_mock.cursor() as cursor: + cursor.fetchone.return_value = None + genotypes_exist("test", db_mock) + cursor.execute.assert_called_with( + "SELECT Name FROM GenoFreeze WHERE " + "GenoFreeze.Name = 'testGeno'" + ) + + def test_genotypes_exist_with_falsy_values(self): + """Test that genotype check returns correctly when given a None value + + """ + db_mock = mock.MagicMock() + with db_mock.cursor() as cursor: + for x in [None, False, (), [], ""]: + cursor.fetchone.return_value = x + self.assertFalse(genotypes_exist("test", db_mock)) + + def test_genotypes_exist_with_truthy_value(self): + """Test that genotype check returns correctly when given Truthy """ + db_mock = mock.MagicMock() + with db_mock.cursor() as cursor: + for x in ["x", ("result"), ["result"], [1]]: + cursor.fetchone.return_value = (x) + self.assertTrue(phenotypes_exist("test", db_mock)) + + def test_build_datasets_with_type_phenotypes(self): + """Test that correct dataset is returned for a phenotype type""" + db_mock = mock.MagicMock() + with db_mock.cursor() as cursor: + cursor.fetchall.return_value = ( + (602, "BXDPublish", "BXD Published Phenotypes"), + ) + self.assertEqual(build_datasets("Mouse", "BXD", + "Phenotypes", db_mock), + [['602', "BXDPublish", + "BXD Published Phenotypes"]]) + cursor.execute.assert_called_with( + "SELECT InfoFiles.GN_AccesionId, PublishFreeze.Name, " + + "PublishFreeze.FullName FROM InfoFiles, PublishFreeze, " + + "InbredSet WHERE InbredSet.Name = 'BXD' AND " + + "PublishFreeze.InbredSetId = InbredSet.Id AND " + + "InfoFiles.InfoPageName = PublishFreeze.Name " + + "ORDER BY PublishFreeze.CreateTime ASC" + ) + self.assertEqual(build_datasets("Mouse", "MDP", + "Phenotypes", db_mock), + [['602', "BXDPublish", + "Mouse Phenome Database"]]) + + cursor.fetchall.return_value = () + cursor.fetchone.return_value = ( + "BXDPublish", "Mouse Phenome Database" + ) + self.assertEqual(build_datasets("Mouse", "MDP", + "Phenotypes", db_mock), + [["None", "BXDPublish", + "Mouse Phenome Database"]]) + + def test_build_datasets_with_type_phenotypes_and_no_results(self): + """Test that correct dataset is returned for a phenotype type with no + results + + """ + db_mock = mock.MagicMock() + with db_mock.cursor() as cursor: + cursor.fetchall.return_value = None + cursor.fetchone.return_value = (121, + "text value") + self.assertEqual(build_datasets("Mouse", "BXD", + "Phenotypes", db_mock), + [["None", "121", + "text value"]]) + cursor.execute.assert_called_with( + "SELECT PublishFreeze.Name, PublishFreeze.FullName " + "FROM PublishFreeze, InbredSet " + "WHERE InbredSet.Name = 'BXD' AND " + "PublishFreeze.InbredSetId = InbredSet.Id " + "ORDER BY PublishFreeze.CreateTime ASC" + ) + + def test_build_datasets_with_type_genotypes(self): + """Test that correct dataset is returned for a phenotype type""" + db_mock = mock.MagicMock() + with db_mock.cursor() as cursor: + cursor.fetchone.return_value = ( + 635, "HLCPublish", "HLC Published Genotypes" + ) + self.assertEqual(build_datasets("Mouse", "HLC", + "Genotypes", db_mock), + [["635", "HLCGeno", "HLC Genotypes"]]) + cursor.execute.assert_called_with( + "SELECT InfoFiles.GN_AccesionId FROM InfoFiles, " + "GenoFreeze, InbredSet WHERE InbredSet.Name = 'HLC' AND " + "GenoFreeze.InbredSetId = InbredSet.Id AND " + "InfoFiles.InfoPageName = GenoFreeze.ShortName " + "ORDER BY GenoFreeze.CreateTime DESC" + ) + cursor.fetchone.return_value = () + self.assertEqual(build_datasets("Mouse", "HLC", + "Genotypes", db_mock), + [["None", "HLCGeno", "HLC Genotypes"]]) + + def test_build_datasets_with_type_mrna(self): + """Test that correct dataset is returned for a mRNA + expression/ Probeset""" + db_mock = mock.MagicMock() + with db_mock.cursor() as cursor: + cursor.fetchall.return_value = ( + (112, "HC_M2_0606_P", + "Hippocampus Consortium M430v2 (Jun06) PDNN"), ) + self.assertEqual(build_datasets("Mouse", + "HLC", "mRNA", db_mock), + [["112", 'HC_M2_0606_P', + "Hippocampus Consortium M430v2 (Jun06) PDNN" + ]]) + cursor.execute.assert_called_once_with( + "SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, " + "ProbeSetFreeze.FullName FROM ProbeSetFreeze, " + "ProbeFreeze, InbredSet, Tissue, Species WHERE " + "Species.Name = 'Mouse' AND Species.Id = " + "InbredSet.SpeciesId AND InbredSet.Name = 'HLC' AND " + "ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id AND " + "Tissue.Name = 'mRNA' AND ProbeFreeze.TissueId = " + "Tissue.Id AND ProbeFreeze.InbredSetId = InbredSet.Id AND " + "ProbeSetFreeze.public > 0 " + "ORDER BY -ProbeSetFreeze.OrderList DESC, " + "ProbeSetFreeze.CreateTime DESC") + + @mock.patch('gn2.wqflask.api.gen_menu.build_datasets') + def test_build_types(self, datasets_mock): + """Test that correct tissue metadata is returned""" + db_mock = mock.MagicMock() + datasets_mock.return_value = [ + ["112", 'HC_M2_0606_P', + "Hippocampus Consortium M430v2 (Jun06) PDNN"] + ] + with db_mock.cursor() as cursor: + cursor.fetchall.return_value = ( + ('Mouse Tissue'), ('Human Tissue'), ('Rat Tissue') + ) + self.assertEqual(build_types('mouse', 'random group', db_mock), + [['M', 'M', 'Molecular Traits'], + ['H', 'H', 'Molecular Traits'], + ['R', 'R', 'Molecular Traits']]) + cursor.execute.assert_called_once_with( + "SELECT DISTINCT Tissue.Name " + "FROM ProbeFreeze, ProbeSetFreeze, InbredSet, " + "Tissue, Species WHERE Species.Name = 'mouse' " + "AND Species.Id = InbredSet.SpeciesId AND " + "InbredSet.Name = 'random group' AND " + "ProbeFreeze.TissueId = Tissue.Id AND " + "ProbeFreeze.InbredSetId = InbredSet.Id AND " + "ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "ORDER BY Tissue.Name" + ) + + @mock.patch('gn2.wqflask.api.gen_menu.build_types') + @mock.patch('gn2.wqflask.api.gen_menu.genotypes_exist') + @mock.patch('gn2.wqflask.api.gen_menu.phenotypes_exist') + def test_get_types_with_existing_genotype_and_phenotypes( + self, + phenotypes_exist_mock, + genotypes_exist_mock, + build_types_mock): + """Test that build types are constructed correctly if phenotypes and genotypes + exist + + """ + phenotypes_exist_mock.return_value = True + genotypes_exist_mock.return_value = True + + expected_result = self.test_type + + build_types_mock.return_value = [ + ['M', 'M', 'Molecular Trait Datasets'] + ] + self.assertEqual(get_types(self.test_group, + mock.MagicMock()), + expected_result) + + @mock.patch('gn2.wqflask.api.gen_menu.build_types') + @mock.patch('gn2.wqflask.api.gen_menu.genotypes_exist') + @mock.patch('gn2.wqflask.api.gen_menu.phenotypes_exist') + def test_get_types_with_buildtype_and_non_existent_genotype_and_phenotypes( + self, + phenotypes_exist_mock, + genotypes_exist_mock, + build_types_mock): + """Test that build types are constructed correctly if phenotypes_exist and + genotypes_exist are false but build_type is falsy + + """ + phenotypes_exist_mock.return_value = False + genotypes_exist_mock.return_value = False + + build_types_mock.return_value = [] + self.assertEqual(get_types(self.test_group, mock.MagicMock()), + {'mouse': {}, 'human': {}}) + + @mock.patch('gn2.wqflask.api.gen_menu.build_types') + @mock.patch('gn2.wqflask.api.gen_menu.genotypes_exist') + @mock.patch('gn2.wqflask.api.gen_menu.phenotypes_exist') + def test_get_types_with_non_existent_genotype_phenotypes_and_buildtype( + self, + phenotypes_exist_mock, + genotypes_exist_mock, + build_types_mock): + """Test that build types are constructed correctly if phenotypes_exist, + genotypes_exist and build_types are truthy + + """ + phenotypes_exist_mock.return_value = False + genotypes_exist_mock.return_value = False + + build_types_mock.return_value = [ + ['M', 'M', 'Molecular Trait Datasets'] + ] + expected_result = { + 'mouse': { + 'H_T2': [['M', 'M', 'Molecular Trait Datasets']], + 'H_T1': [['M', 'M', 'Molecular Trait Datasets']]}, + 'human': { + 'HLC': [['M', 'M', 'Molecular Trait Datasets']], + 'BXD': [['M', 'M', 'Molecular Trait Datasets']]}} + self.assertEqual(get_types(self.test_group, mock.MagicMock()), + expected_result) + + @mock.patch('gn2.wqflask.api.gen_menu.build_datasets') + def test_get_datasets_with_existent_datasets(self, + build_datasets_mock): + """Test correct dataset is returned with existent build_datasets""" + build_datasets_mock.return_value = "Test" + expected_result = { + 'mouse': { + 'H_T2': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}, + 'H_T1': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}}, + 'human': {'HLC': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}, + 'BXD': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}}} + self.assertEqual(get_datasets(self.test_type, mock.MagicMock()), + expected_result) + + @mock.patch('gn2.wqflask.api.gen_menu.build_datasets') + def test_get_datasets_with_non_existent_datasets(self, + build_datasets_mock): + """Test correct dataset is returned with non-existent build_datasets""" + build_datasets_mock.return_value = None + expected_result = { + 'mouse': { + 'H_T2': {}, + 'H_T1': {}}, + 'human': {'HLC': {}, + 'BXD': {}}} + self.assertEqual(get_datasets(self.test_type, mock.MagicMock()), + expected_result) + + @mock.patch('gn2.wqflask.api.gen_menu.get_datasets') + @mock.patch('gn2.wqflask.api.gen_menu.get_types') + @mock.patch('gn2.wqflask.api.gen_menu.get_groups') + @mock.patch('gn2.wqflask.api.gen_menu.get_all_species') + def test_gen_dropdown_json(self, + species_mock, + groups_mock, + types_mock, + datasets_mock): + "Test that the correct dictionary is constructed properly" + species_mock.return_value = ("speciesA speciesB speciesC speciesD" + .split(" ")) + datasets_mock.return_value = ("datasetA datasetB datasetC datasetD" + .split(" ")) + groups_mock.return_value = ("groupA groupB groupC groupD" + .split(" ")) + types_mock.return_value = ("typeA typeB typeC typeD" + .split(" ")) + datasets_mock.return_value = ("datasetA datasetB datasetC datasetD" + .split(" ")) + + expected_result = { + 'datasets': ['datasetA', 'datasetB', 'datasetC', 'datasetD'], + 'types': ['typeA', 'typeB', 'typeC', 'typeD'], + 'groups': ['groupA', 'groupB', 'groupC', 'groupD'], + 'species': ['speciesA', 'speciesB', 'speciesC', 'speciesD']} + + self.assertEqual(gen_dropdown_json(mock.MagicMock()), expected_result) diff --git a/gn2/tests/unit/wqflask/api/test_mapping.py b/gn2/tests/unit/wqflask/api/test_mapping.py new file mode 100644 index 00000000..226e2a9b --- /dev/null +++ b/gn2/tests/unit/wqflask/api/test_mapping.py @@ -0,0 +1,113 @@ +import unittest +from unittest import mock +from gn2.wqflask.api.mapping import initialize_parameters +from gn2.wqflask.api.mapping import do_mapping_for_api + + +class AttributeSetter: + def __init__(self, obj): + for key, value in obj.items(): + setattr(self, key, value) + + +class MockGroup(AttributeSetter): + def get_marker(self): + self.markers = [] + + +class TestMapping(unittest.TestCase): + + def test_initialize_parameters(self): + expected_results = { + "format": "json", + "limit_to": False, + "mapping_method": "gemma", + "maf": 0.01, + "use_loco": True, + "num_perm": 0, + "perm_check": False, + "transform": False, + "genofile": False + } + + results = initialize_parameters( + start_vars={}, dataset={}, this_trait={}) + self.assertEqual(results, expected_results) + + start_vars = { + "format": "F1", + "limit_to": "1", + "mapping_method": "rqtl", + "control_marker": True, + "pair_scan": "true", + "interval_mapping": "true", + "use_loco": "true", + "num_perm": "14", + "transform": "qnorm", + "genofile": "BXD.8.geno" + } + + results_2 = initialize_parameters( + start_vars=start_vars, dataset={}, this_trait={}) + expected_results = { + "format": "F1", + "limit_to": 1, + "mapping_method": "gemma", + "maf": 0.01, + "use_loco": True, + "num_perm": 14, + "perm_check": "ON", + "transform": "qnorm", + "genofile": "BXD.8.geno" + } + + self.assertEqual(results_2, expected_results) + + @mock.patch("gn2.wqflask.api.mapping.rqtl_mapping.run_rqtl") + @mock.patch("gn2.wqflask.api.mapping.gemma_mapping.run_gemma") + @mock.patch("gn2.wqflask.api.mapping.initialize_parameters") + @mock.patch("gn2.wqflask.api.mapping.retrieve_sample_data") + @mock.patch("gn2.wqflask.api.mapping.create_trait") + @mock.patch("gn2.wqflask.api.mapping.data_set.create_dataset") + def test_do_mapping_for_api(self, mock_create_dataset, mock_trait, mock_retrieve_sample, mock_param, run_gemma, run_rqtl_geno): + start_vars = { + "db": "Temp", + "trait_id": "dewf3232rff2", + "format": "F1", + "mapping_method": "gemma", + "use_loco": True + + } + sampleList = ["S1", "S2", "S3", "S4"] + samplelist = ["S1", "S2", "S4"] + dataset = AttributeSetter({"group": samplelist}) + this_trait = AttributeSetter({}) + trait_data = AttributeSetter({ + "data": { + "item1": AttributeSetter({"name": "S1", "value": "S1_value"}), + "item2": AttributeSetter({"name": "S2", "value": "S2_value"}), + "item3": AttributeSetter({"name": "S3", "value": "S3_value"}), + + } + }) + trait = AttributeSetter({ + "data": trait_data + }) + + dataset.return_value = dataset + mock_trait.return_value = this_trait + + mock_retrieve_sample.return_value = trait + mock_param.return_value = { + "format": "F1", + "limit_to": False, + "mapping_method": "gemma", + "maf": 0.01, + "use_loco": "True", + "num_perm": 14, + "perm_check": "ON" + } + + run_gemma.return_value = ["results"] + results = do_mapping_for_api(start_vars=start_vars) + self.assertEqual(results, ("results", None)) diff --git a/gn2/tests/unit/wqflask/api/test_markdown_routes.py b/gn2/tests/unit/wqflask/api/test_markdown_routes.py new file mode 100644 index 00000000..ecf64a81 --- /dev/null +++ b/gn2/tests/unit/wqflask/api/test_markdown_routes.py @@ -0,0 +1,54 @@ +"""Test functions for wqflask/api/markdown.py""" + +import unittest +from unittest import mock + +from dataclasses import dataclass +from gn2.wqflask.api.markdown import render_markdown + + +@dataclass +class MockRequests404: + status_code: int = 404 + + +@dataclass +class MockRequests200: + status_code: int = 200 + content: str = b""" +# Glossary +This is some content + +## Sub-heading +This is another sub-heading""" + + +class TestMarkdownRoutesFunctions(unittest.TestCase): + """Test cases for functions in markdown""" + + @mock.patch('gn2.wqflask.api.markdown.requests.get') + def test_render_markdown_when_fetching_locally(self, requests_mock): + requests_mock.return_value = MockRequests404() + markdown_content = render_markdown("general/glossary/glossary.md") + requests_mock.assert_called_with( + "https://raw.githubusercontent.com" + "/genenetwork/gn-docs/" + "master/general/" + "glossary/glossary.md") + self.assertRegex(markdown_content, + "Content for general/glossary/glossary.md not available.") + + @mock.patch('gn2.wqflask.api.markdown.requests.get') + def test_render_markdown_when_fetching_remotely(self, requests_mock): + requests_mock.return_value = MockRequests200() + markdown_content = render_markdown("general/glossary/glossary.md") + requests_mock.assert_called_with( + "https://raw.githubusercontent.com" + "/genenetwork/gn-docs/" + "master/general/" + "glossary/glossary.md") + self.assertEqual("""<h1>Glossary</h1> +<p>This is some content</p> +<h2>Sub-heading</h2> +<p>This is another sub-heading</p>""", + markdown_content) diff --git a/gn2/tests/unit/wqflask/correlation/__init__.py b/gn2/tests/unit/wqflask/correlation/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/wqflask/correlation/__init__.py diff --git a/gn2/tests/unit/wqflask/correlation/test_correlation_functions.py b/gn2/tests/unit/wqflask/correlation/test_correlation_functions.py new file mode 100644 index 00000000..f76ef57e --- /dev/null +++ b/gn2/tests/unit/wqflask/correlation/test_correlation_functions.py @@ -0,0 +1,21 @@ +"""module contains tests for correlation functions""" + +import unittest +from unittest import mock + +from gn2.wqflask.correlation.correlation_functions import get_trait_symbol_and_tissue_values +from gn2.wqflask.correlation.correlation_functions import cal_zero_order_corr_for_tiss + + +def test_tissue_corr_computation(mocker): + """Test for cal_zero_order_corr_for_tiss""" + primary_values = [9.288, 9.313, 8.988, 9.660, 8.21] + target_values = [9.586, 8.498, 9.362, 8.820, 8.786] + _m = mocker.patch(("gn2.wqflask.correlation.correlation_functions." + "compute_corr_coeff_p_value"), + return_value=(0.51, 0.7)) + results = cal_zero_order_corr_for_tiss(primary_values, target_values) + _m.assert_called_once_with( + primary_values=primary_values, target_values=target_values, + corr_method="pearson") + assert len(results) == 3 diff --git a/gn2/tests/unit/wqflask/correlation/test_correlation_gn3.py b/gn2/tests/unit/wqflask/correlation/test_correlation_gn3.py new file mode 100644 index 00000000..432dbc95 --- /dev/null +++ b/gn2/tests/unit/wqflask/correlation/test_correlation_gn3.py @@ -0,0 +1,14 @@ +"""this module contains tests for code used in integrating to gn3 api""" +from unittest import TestCase +from gn2.base.data_set import create_dataset + +class TestCorrelation(TestCase): + + def test_create_dataset(self): + """test for creating datasets""" + + pass + def test_fetch_dataset_info(self): + """test for fetching dataset info data""" + + pass diff --git a/gn2/tests/unit/wqflask/correlation/test_show_corr_results.py b/gn2/tests/unit/wqflask/correlation/test_show_corr_results.py new file mode 100644 index 00000000..b1459dd9 --- /dev/null +++ b/gn2/tests/unit/wqflask/correlation/test_show_corr_results.py @@ -0,0 +1,42 @@ +import unittest +from unittest import mock +from gn2.wqflask.correlation.show_corr_results import get_header_fields + + +class AttributeSetter: + def __init__(self, trait_obj): + for key, value in trait_obj.items(): + setattr(self, key, value) + + +class TestShowCorrResults(unittest.TestCase): + def test_get_header_fields(self): + expected = [ + ['Index', + 'Record', + 'Symbol', + 'Description', + 'Location', + 'Mean', + 'Sample rho', + 'N', + 'Sample p(rho)', + 'Lit rho', + 'Tissue rho', + 'Tissue p(rho)', + 'Max LRS', + 'Max LRS Location', + 'Additive Effect'], + + ['Index', + 'ID', + 'Location', + 'Sample r', + 'N', + 'Sample p(r)'] + + ] + result1 = get_header_fields("ProbeSet", "spearman") + result2 = get_header_fields("Other", "Other") + self.assertEqual(result1, expected[0]) + self.assertEqual(result2, expected[1]) diff --git a/gn2/tests/unit/wqflask/marker_regression/__init__.py b/gn2/tests/unit/wqflask/marker_regression/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/wqflask/marker_regression/__init__.py diff --git a/gn2/tests/unit/wqflask/marker_regression/genotype/bimbam/file_geno.txt b/gn2/tests/unit/wqflask/marker_regression/genotype/bimbam/file_geno.txt new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/wqflask/marker_regression/genotype/bimbam/file_geno.txt diff --git a/gn2/tests/unit/wqflask/marker_regression/genotype/bimbam/file_snps.txt b/gn2/tests/unit/wqflask/marker_regression/genotype/bimbam/file_snps.txt new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/wqflask/marker_regression/genotype/bimbam/file_snps.txt diff --git a/gn2/tests/unit/wqflask/marker_regression/test_display_mapping_results.py b/gn2/tests/unit/wqflask/marker_regression/test_display_mapping_results.py new file mode 100644 index 00000000..580d79cf --- /dev/null +++ b/gn2/tests/unit/wqflask/marker_regression/test_display_mapping_results.py @@ -0,0 +1,159 @@ +import unittest + +import htmlgen as HT +from gn2.wqflask.marker_regression.display_mapping_results import ( + DisplayMappingResults, + HtmlGenWrapper +) + + +class TestDisplayMappingResults(unittest.TestCase): + """Basic Methods to test Mapping Results""" + + def test_pil_colors(self): + """Test that colors use PILLOW color format""" + self.assertEqual(DisplayMappingResults.CLICKABLE_WEBQTL_REGION_COLOR, + (245, 211, 211)) + + +class TestHtmlGenWrapper(unittest.TestCase): + """Test Wrapper around HTMLGen""" + + def test_create_image(self): + """Test HT.Image method""" + self.assertEqual( + str(HtmlGenWrapper.create_image_tag(src="test.png", + alt="random", + border="0", + width="10", + height="13", + usemap="#webqtlmap")), + ("""<img alt="random" border="0" height="13" """ + """src="test.png" usemap="#webqtlmap" """ + """width="10"/>""") + ) + + def test_create_form(self): + """Test HT.Form method""" + test_form = HtmlGenWrapper.create_form_tag( + cgi="/testing/", + enctype='multipart/form-data', + name="formName", + submit=HtmlGenWrapper.create_input_tag( + type_='hidden', name='Default_Name') + ) + test_image = HtmlGenWrapper.create_image_tag( + src="test.png", + alt="random", + border="0", + width="10", + height="13", + usemap="#webqtlmap" + ) + self.assertEqual( + str(test_form).replace("\n", ""), + ("""<form action="/testing/" enctype="multipart/form-data" """ + """method="POST" """ + """name="formName"><input name="Default_Name" """ + """type="hidden"/></form>""")) + hddn = { + 'FormID': 'showDatabase', + 'ProbeSetID': '_', + 'database': "TestGeno", + 'CellID': '_', + 'RISet': "Test", + 'incparentsf1': 'ON' + } + for key in hddn.keys(): + test_form.append( + HtmlGenWrapper.create_input_tag( + name=key, + value=hddn[key], + type_='hidden')) + test_form.append(test_image) + + self.assertEqual(str(test_form).replace("\n", ""), ( + """<form action="/testing/" enctype="multipart/form-data" """ + """method="POST" name="formName">""" + """<input name="Default_Name" type="hidden"/>""" + """<input name="FormID" type="hidden" value="showDatabase"/>""" + """<input name="ProbeSetID" type="hidden" value="_"/>""" + """<input name="database" type="hidden" value="TestGeno"/>""" + """<input name="CellID" type="hidden" value="_"/>""" + """<input name="RISet" type="hidden" value="Test"/>""" + """<input name="incparentsf1" type="hidden" value="ON"/>""" + """<img alt="random" border="0" height="13" src="test.png" """ + """usemap="#webqtlmap" width="10"/>""" + """</form>""")) + + def test_create_paragraph(self): + """Test HT.Paragraph method""" + test_p_element = HtmlGenWrapper.create_p_tag(id="smallSize") + par_text = ( + "Mapping using genotype data as " + "a trait will result in infinity LRS at one locus. " + "In order to display the result properly, all LRSs " + "higher than 100 are capped at 100." + ) + self.assertEqual( + str(test_p_element), + """<p id="smallSize"></p>""" + ) + test_p_element.append(HtmlGenWrapper.create_br_tag()) + test_p_element.append(par_text) + self.assertEqual( + str(test_p_element), + """<p id="smallSize"><br/>{}</p>""".format(par_text) + ) + + def test_create_br_tag(self): + """Test HT.BR() method""" + self.assertEqual(str(HtmlGenWrapper.create_br_tag()), + "<br/>") + + def test_create_input_tag(self): + """Test HT.Input method""" + self.assertEqual( + str(HtmlGenWrapper.create_input_tag( + type_="hidden", + name="name", + value="key", + Class="trait trait_")).replace("\n", ""), + ("""<input class="trait trait_" name="name" """ + """type="hidden" value="key"/>""")) + + def test_create_map_tag(self): + """Test HT.Map method""" + self.assertEqual(str(HtmlGenWrapper.create_map_tag( + name="WebqTLImageMap")).replace("\n", ""), + """<map name="WebqTLImageMap"></map>""") + gifmap = HtmlGenWrapper.create_map_tag(name="test") + gifmap.append(HtmlGenWrapper.create_area_tag(shape="rect", + coords='1 2 3', href='#area1')) + gifmap.append(HtmlGenWrapper.create_area_tag(shape="rect", + coords='1 2 3', href='#area2')) + self.assertEqual( + str(gifmap).replace("\n", ""), + ("""<map name="test">""" + """<area coords="1 2 3" """ + """href="#area1" shape="rect"/>""" + """<area coords="1 2 3" href="#area2" shape="rect"/>""" + """</map>""")) + + def test_create_area_tag(self): + """Test HT.Area method""" + self.assertEqual( + str(HtmlGenWrapper.create_area_tag( + shape="rect", + coords="1 2", + href="http://test.com", + title="Some Title")).replace("\n", ""), + ("""<area coords="1 2" href="http://test.com" """ + """shape="rect" title="Some Title"/>""")) + + def test_create_link_tag(self): + """Test HT.HREF method""" + self.assertEqual( + str(HtmlGenWrapper.create_link_tag( + "www.test.com", "test", target="_blank")).replace("\n", ""), + """<a href="www.test.com" target="_blank">test</a>""") diff --git a/gn2/tests/unit/wqflask/marker_regression/test_gemma_mapping.py b/gn2/tests/unit/wqflask/marker_regression/test_gemma_mapping.py new file mode 100644 index 00000000..b4d80ad7 --- /dev/null +++ b/gn2/tests/unit/wqflask/marker_regression/test_gemma_mapping.py @@ -0,0 +1,188 @@ +# test for wqflask/marker_regression/gemma_mapping.py +import os +import unittest +import random +from unittest import mock +from gn2.wqflask.marker_regression.gemma_mapping import run_gemma +from gn2.wqflask.marker_regression.gemma_mapping import gen_pheno_txt_file +from gn2.wqflask.marker_regression.gemma_mapping import gen_covariates_file +from gn2.wqflask.marker_regression.gemma_mapping import parse_loco_output + + +class AttributeSetter: + def __init__(self, obj): + for key, val in obj.items(): + setattr(self, key, val) + + +class MockGroup(AttributeSetter): + def get_samplelist(self, redis_conn): + return None + + +class TestGemmaMapping(unittest.TestCase): + + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.parse_loco_output") + def test_run_gemma_firstrun_set_false(self, mock_parse_loco): + """add tests for gemma function where first run is set to false""" + dataset = AttributeSetter( + {"group": AttributeSetter({"genofile": "genofile.geno"})}) + + output_file = "file1" + mock_parse_loco.return_value = [] + this_trait = AttributeSetter({"name": "t1"}) + + result = run_gemma(this_trait=this_trait, this_dataset=dataset, samples=[], vals=[ + ], covariates="", use_loco=True, first_run=False, output_files=output_file) + + expected_results = ([], "file1") + self.assertEqual(expected_results, result) + + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.webqtlConfig.GENERATED_IMAGE_DIR", "/home/user/img") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.GEMMAOPTS", "-debug") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.GEMMA_WRAPPER_COMMAND", "ghc") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.TEMPDIR", + os.path.join(os.path.dirname(__file__), "user/data")) + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.parse_loco_output") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.flat_files") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.gen_covariates_file") + @mock.patch("gn2.wqflask.marker_regression.run_mapping.random.choice") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.os") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.gen_pheno_txt_file") + def test_run_gemma_firstrun_set_true(self, mock_gen_pheno_txt, mock_os, mock_choice, mock_gen_covar, mock_flat_files, mock_parse_loco): + """add tests for run_gemma where first run is set to true""" + this_chromosomes = {} + for i in range(1, 5): + this_chromosomes[f'CH{i}'] = (AttributeSetter({"name": f"CH{i}"})) + chromosomes = AttributeSetter({"chromosomes": lambda cursor: this_chromosomes}) + + dataset_group = MockGroup( + {"name": "GP1", "genofile": "file_geno"}) + dataset = AttributeSetter({"group": dataset_group, "name": "dataset1_name", + "species": AttributeSetter({"chromosomes": chromosomes})}) + trait = AttributeSetter({"name": "trait1"}) + samples = [] + mock_gen_pheno_txt.return_value = None + mock_os.path.isfile.return_value = True + mock_gen_covar.return_value = None + mock_choice.return_value = "R" + mock_flat_files.return_value = os.path.join( + os.path.dirname(__file__), "genotype/bimbam") + mock_parse_loco.return_value = [] + results = run_gemma(this_trait=trait, this_dataset=dataset, samples=[ + ], vals=[], covariates="", use_loco=True) + mock_gen_pheno_txt.assert_called_once() + mock_parse_loco.assert_called_once_with( + dataset, "GP1_GWA_RRRRRR", True) + mock_os.path.isfile.assert_called_once_with( + ('/home/user/imgfile_output.assoc.txt')) + self.assertEqual(results, ([], "GP1_GWA_RRRRRR")) + + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.TEMPDIR", "/home/user/data") + def test_gen_pheno_txt_file(self): + """add tests for generating pheno txt file""" + with mock.patch("builtins.open", mock.mock_open())as mock_open: + gen_pheno_txt_file( + this_dataset=AttributeSetter({"name": "A"}), + genofile_name="", vals=[ + "x", "w", "q", "we", "R"]) + mock_open.assert_called_once_with( + '/home/user/data/gn2/PHENO_KiAEKlCvM6iGTM9Kh_TAlQ.txt', 'w') + filehandler = mock_open() + values = ["x", "w", "q", "we", "R"] + write_calls = [mock.call('NA\n'), mock.call('w\n'), mock.call( + 'q\n'), mock.call('we\n'), mock.call('R\n')] + + filehandler.write.assert_has_calls(write_calls) + + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.flat_files") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.create_trait") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.create_dataset") + def test_gen_covariates_file(self, create_dataset, create_trait, flat_files): + """add tests for generating covariates files""" + covariates = "X1:X2,Y1:Y2,M1:M3,V1:V2" + samplelist = ["X1", "X2", "X3", "X4"] + create_dataset_side_effect = [] + create_trait_side_effect = [] + + for i in range(4): + create_dataset_side_effect.append( + AttributeSetter({"name": f'name_{i}'})) + create_trait_side_effect.append( + AttributeSetter({"data": [f'data_{i}']})) + + create_dataset.side_effect = create_trait_side_effect + create_trait.side_effect = create_trait_side_effect + + group = MockGroup({"name": "group_X", "samplelist": samplelist}) + this_dataset = AttributeSetter({"group": group, "name": "dataset1_name"}) + flat_files.return_value = "Home/Genenetwork" + + with mock.patch("builtins.open", mock.mock_open())as mock_open: + gen_covariates_file(this_dataset=this_dataset, covariates=covariates, + samples=["x1", "x2", "X3"]) + + create_dataset.assert_has_calls( + [mock.call('X2'), mock.call('Y2'), mock.call('M3'), mock.call('V2')]) + mock_calls = [] + trait_names = ["X1", "Y1", "M1", "V1"] + + for i, trait in enumerate(create_trait_side_effect): + mock_calls.append( + mock.call(dataset=trait, name=trait_names[i], cellid=None)) + + create_trait.assert_has_calls(mock_calls) + + flat_files.assert_called_once_with('mapping') + mock_open.assert_called_once_with( + 'Home/Genenetwork/COVAR_npKxIOnq3azWdgYixtd9IQ.txt', 'w') + filehandler = mock_open() + filehandler.write.assert_has_calls([mock.call( + '-9\t'), mock.call('-9\t'), mock.call('-9\t'), mock.call('-9\t'), mock.call('\n')]) + + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.TEMPDIR", "/home/tmp") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.os") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.json") + def test_parse_loco_outputfile_found(self, mock_json, mock_os): + """add tests for parse loco output file found""" + mock_json.load.return_value = { + "files": [["file_name", "user", "~/file1"], + ["file_name", "user", "~/file2"]] + } + return_file = """X/Y\tM1\t28.457155\tQ\tE\tA\tMMB\t23.3\tW\t0.9\t0.85\t +chr4\tM2\t12\tQ\tE\tMMB\tR\t24\tW\t0.87\t0.5 +Y\tM4\t12\tQ\tE\tMMB\tR\t11.6\tW\t0.21\t0.7 +X\tM5\t12\tQ\tE\tMMB\tR\t21.1\tW\t0.65\t0.6""" + + return_file_2 = """chr\tother\t21322\tQ\tE\tA\tP\tMMB\tCDE\t0.5\t0.4""" + mock_os.path.isfile.return_value = True + file_to_write = """{"files":["file_1","file_2"]}""" + with mock.patch("builtins.open") as mock_open: + + handles = (mock.mock_open(read_data="gwas").return_value, mock.mock_open( + read_data=return_file).return_value, mock.mock_open(read_data=return_file_2).return_value) + mock_open.side_effect = handles + results = parse_loco_output( + this_dataset={}, gwa_output_filename=".xw/") + expected_results = [ + {'name': 'M1', 'chr': 'X/Y', 'Mb': 2.8457155e-05, 'p_value': 0.85, + 'additive': -11.65, 'lod_score': 0.07058107428570727}, + {'name': 'M2', 'chr': 4, 'Mb': 1.2e-05, 'p_value': 0.5, + 'additive': -12.0, 'lod_score': 0.3010299956639812}, + {'name': 'M4', 'chr': 'Y', 'Mb': 1.2e-05, 'p_value': 0.7, + 'additive': -5.8, 'lod_score': 0.1549019599857432}, + {'name': 'M5', 'chr': 'X', 'Mb': 1.2e-05, 'p_value': 0.6, 'additive': -10.55, 'lod_score': 0.22184874961635637}] + self.assertEqual(expected_results, results) + + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.TEMPDIR", "/home/tmp") + @mock.patch("gn2.wqflask.marker_regression.gemma_mapping.os") + def test_parse_loco_outputfile_not_found(self, mock_os): + """add tests for parse loco output where output file not found""" + + mock_os.path.isfile.return_value = False + file_to_write = """{"files":["file_1","file_2"]}""" + + with mock.patch("builtins.open", mock.mock_open(read_data=file_to_write)) as mock_open: + results = parse_loco_output( + this_dataset={}, gwa_output_filename=".xw/") + self.assertEqual(results, []) diff --git a/gn2/tests/unit/wqflask/marker_regression/test_plink_mapping.py b/gn2/tests/unit/wqflask/marker_regression/test_plink_mapping.py new file mode 100644 index 00000000..7542e15a --- /dev/null +++ b/gn2/tests/unit/wqflask/marker_regression/test_plink_mapping.py @@ -0,0 +1,86 @@ +# test for wqflask/marker_regression/plink_mapping.py +import unittest +from unittest import mock +from gn2.wqflask.marker_regression.plink_mapping import build_line_list +from gn2.wqflask.marker_regression.plink_mapping import get_samples_from_ped_file +from gn2.wqflask.marker_regression.plink_mapping import flat_files +from gn2.wqflask.marker_regression.plink_mapping import gen_pheno_txt_file_plink +from gn2.wqflask.marker_regression.plink_mapping import parse_plink_output + + +class AttributeSetter: + def __init__(self, obj): + for key, val in obj.items(): + setattr(self, key, val) + + +class TestPlinkMapping(unittest.TestCase): + + def test_build_line_list(self): + """test for building line list""" + line_1 = "this is line one test" + irregular_line = " this is an, irregular line " + exp_line1 = ["this", "is", "line", "one", "test"] + + results = build_line_list(irregular_line) + self.assertEqual(exp_line1, build_line_list(line_1)) + self.assertEqual([], build_line_list()) + self.assertEqual(["this", "is", "an,", "irregular", "line"], results) + + @mock.patch("gn2.wqflask.marker_regression.plink_mapping.flat_files") + def test_get_samples_from_ped_file(self, mock_flat_files): + """test for getting samples from ped file""" + dataset = AttributeSetter({"group": AttributeSetter({"name": "n_1"})}) + file_sample = """Expected_1\tline test +Expected_2\there + Expected_3\tthree""" + mock_flat_files.return_value = "/home/user/" + with mock.patch("builtins.open", mock.mock_open(read_data=file_sample)) as mock_open: + results = get_samples_from_ped_file(dataset) + mock_flat_files.assert_called_once_with("mapping") + mock_open.assert_called_once_with("/home/user/n_1.ped", "r") + self.assertEqual( + ["Expected_1", "Expected_2", "Expected_3"], results) + + @mock.patch("gn2.wqflask.marker_regression.plink_mapping.TMPDIR", "/home/user/data/") + @mock.patch("gn2.wqflask.marker_regression.plink_mapping.get_samples_from_ped_file") + def test_gen_pheno_txt_file_plink(self, mock_samples): + """test for getting gen_pheno txt file""" + mock_samples.return_value = ["Expected_1", "Expected_2", "Expected_3"] + + trait = AttributeSetter({"name": "TX"}) + dataset = AttributeSetter({"group": AttributeSetter({"name": "n_1"})}) + vals = ["value=K1", "value=K2", "value=K3"] + with mock.patch("builtins.open", mock.mock_open()) as mock_open: + results = gen_pheno_txt_file_plink(this_trait=trait, dataset=dataset, + vals=vals, pheno_filename="ph_file") + mock_open.assert_called_once_with( + "/home/user/data/ph_file.txt", "wb") + filehandler = mock_open() + calls_expected = [mock.call('FID\tIID\tTX\n'), + mock.call('Expected_1\tExpected_1\tK1\nExpected_2\tExpected_2\tK2\nExpected_3\tExpected_3\tK3\n')] + + filehandler.write.assert_has_calls(calls_expected) + + filehandler.close.assert_called_once() + + @mock.patch("gn2.wqflask.marker_regression.plink_mapping.TMPDIR", "/home/user/data/") + @mock.patch("gn2.wqflask.marker_regression.plink_mapping.build_line_list") + def test_parse_plink_output(self, mock_line_list): + """test for parsing plink output""" + chromosomes = [0, 34, 110, 89, 123, 23, 2] + species = AttributeSetter( + {"name": "S1", "chromosomes": AttributeSetter({"chromosomes": chromosomes})}) + + fake_file = """0 AACCAT T98.6 0.89\n2 AATA B45 0.3\n121 ACG B56.4 NA""" + + mock_line_list.side_effect = [["0", "AACCAT", "T98.6", "0.89"], [ + "2", "AATA", "B45", "0.3"], ["121", "ACG", "B56.4", "NA"]] + with mock.patch("builtins.open", mock.mock_open(read_data=fake_file)) as mock_open: + parse_results = parse_plink_output( + output_filename="P1_file", species=species) + mock_open.assert_called_once_with( + "/home/user/data/P1_file.qassoc", "rb") + expected = (2, {'AACCAT': 0.89, 'AATA': 0.3}) + + self.assertEqual(parse_results, expected) diff --git a/gn2/tests/unit/wqflask/marker_regression/test_qtlreaper_mapping.py b/gn2/tests/unit/wqflask/marker_regression/test_qtlreaper_mapping.py new file mode 100644 index 00000000..c2753141 --- /dev/null +++ b/gn2/tests/unit/wqflask/marker_regression/test_qtlreaper_mapping.py @@ -0,0 +1,24 @@ +import unittest +from unittest import mock +from gn2.wqflask.marker_regression.qtlreaper_mapping import gen_pheno_txt_file + +# issues some methods in genofile object are not defined +# modify samples should equal to vals + + +class TestQtlReaperMapping(unittest.TestCase): + @mock.patch("gn2.wqflask.marker_regression.qtlreaper_mapping.TEMPDIR", "/home/user/data") + def test_gen_pheno_txt_file(self): + vals = ["V1", "x", "V4", "V3", "x"] + samples = ["S1", "S2", "S3", "S4", "S5"] + trait_filename = "trait_file" + with mock.patch("builtins.open", mock.mock_open())as mock_open: + gen_pheno_txt_file(samples=samples, vals=vals, + trait_filename=trait_filename) + mock_open.assert_called_once_with( + "/home/user/data/gn2/trait_file.txt", "w") + filehandler = mock_open() + write_calls = [mock.call('Trait\t'), mock.call( + 'S1\tS3\tS4\n'), mock.call('T1\t'), mock.call('V1\tV4\tV3')] + + filehandler.write.assert_has_calls(write_calls) diff --git a/gn2/tests/unit/wqflask/marker_regression/test_rqtl_mapping.py b/gn2/tests/unit/wqflask/marker_regression/test_rqtl_mapping.py new file mode 100644 index 00000000..9f646fb0 --- /dev/null +++ b/gn2/tests/unit/wqflask/marker_regression/test_rqtl_mapping.py @@ -0,0 +1,43 @@ +import unittest +from unittest import mock +from dataclasses import dataclass + +from gn2.wqflask.marker_regression.rqtl_mapping import run_rqtl + +@dataclass +class MockGroup: + name: str + genofile: str + +@dataclass +class MockDataset: + group: MockGroup + +class TestRqtlMapping(unittest.TestCase): + """Tests for functions in rqtl_mapping.py""" + @mock.patch("gn2.wqflask.marker_regression.rqtl_mapping.requests.post") + @mock.patch("gn2.wqflask.marker_regression.rqtl_mapping.locate") + @mock.patch("gn2.wqflask.marker_regression.rqtl_mapping.write_phenotype_file") + def test_run_rqtl_with_perm(self, mock_write_pheno_file, mock_locate, mock_post): + """Test for run_rqtl with permutations > 0""" + dataset_group = MockGroup("GP1", "file_geno") + dataset = MockDataset(dataset_group) + + mock_write_pheno_file.return_value = "pheno_filename" + mock_locate.return_value = "geno_filename" + mock_post.return_value = mock.Mock(ok=True) + mock_post.return_value.json.return_value = {"perm_results": [], + "suggestive": 3, + "significant": 4, + "results" : []} + + results = run_rqtl(trait_name="the_trait", vals=[], samples=[], + dataset=dataset, pair_scan=False, mapping_scale="cM", model="normal", method="hk", + num_perm=5, perm_strata_list=[], do_control="false", control_marker="", + manhattan_plot=True, cofactors="") + + mock_write_pheno_file.assert_called_once() + mock_locate.assert_called_once() + mock_post.assert_called_once() + + self.assertEqual(results, ([], 3, 4, [])) diff --git a/gn2/tests/unit/wqflask/marker_regression/test_run_mapping.py b/gn2/tests/unit/wqflask/marker_regression/test_run_mapping.py new file mode 100644 index 00000000..817bf2b9 --- /dev/null +++ b/gn2/tests/unit/wqflask/marker_regression/test_run_mapping.py @@ -0,0 +1,288 @@ +import unittest +import datetime +from unittest import mock + +from gn2.wqflask.marker_regression.run_mapping import get_genofile_samplelist +from gn2.wqflask.marker_regression.run_mapping import geno_db_exists +from gn2.wqflask.marker_regression.run_mapping import write_input_for_browser +from gn2.wqflask.marker_regression.run_mapping import export_mapping_results +from gn2.wqflask.marker_regression.run_mapping import trim_markers_for_figure +from gn2.wqflask.marker_regression.run_mapping import get_perm_strata +from gn2.wqflask.marker_regression.run_mapping import get_chr_lengths + + +class AttributeSetter: + def __init__(self, obj): + for k, v in obj.items(): + setattr(self, k, v) + + +class MockGroup(AttributeSetter): + + def get_genofiles(self): + return [{"location": "~/genofiles/g1_file", "sample_list": ["S1", "S2", "S3", "S4"]}] + + +class TestRunMapping(unittest.TestCase): + def setUp(self): + + self.group = MockGroup( + {"genofile": "~/genofiles/g1_file", "name": "GP1_", "species": "Human"}) + chromosomes = { + "3": AttributeSetter({ + "name": "C1", + "length": "0.04" + }), + "4": AttributeSetter({ + "name": "C2", + "length": "0.03" + }), + "5": AttributeSetter({ + "name": "C4", + "length": "0.01" + }) + } + self.dataset = AttributeSetter( + {"fullname": "dataset_1", "group": self.group, "type": "ProbeSet"}) + + self.chromosomes = AttributeSetter({"chromosomes": lambda cur: chromosomes}) + self.trait = AttributeSetter( + {"symbol": "IGFI", "chr": "X1", "mb": 123313, "display_name": "Test Name"}) + + def tearDown(self): + self.dataset = AttributeSetter( + {"group": {"location": "~/genofiles/g1_file"}}) + + def test_get_genofile_samplelist(self): + + results_1 = get_genofile_samplelist(self.dataset) + self.assertEqual(results_1, ["S1", "S2", "S3", "S4"]) + self.group.genofile = "~/genofiles/g2_file" + result_2 = get_genofile_samplelist(self.dataset) + self.assertEqual(result_2, []) + + @mock.patch("gn2.wqflask.marker_regression.run_mapping.data_set") + def test_if_geno_db_exists(self, mock_data_set): + mock_data_set.create_dataset.side_effect = [ + AttributeSetter({}), Exception()] + results_no_error = geno_db_exists(self.dataset) + results_with_error = geno_db_exists(self.dataset) + + self.assertEqual(mock_data_set.create_dataset.call_count, 2) + self.assertEqual(results_with_error, "False") + self.assertEqual(results_no_error, "True") + + def test_trim_markers_for_figure(self): + + markers = [{ + "name": "MK1", + "chr": "C1", + "cM": "1", + "Mb": "12000", + "genotypes": [], + "dominance":"TT", + "additive":"VA", + "lod_score":0.5 + }, + { + "name": "MK2", + "chr": "C2", + "cM": "15", + "Mb": "10000", + "genotypes": [], + "lod_score":0.7 + }, + { + "name": "MK1", + "chr": "C3", + "cM": "45", + "Mb": "1", + "genotypes": [], + "dominance":"Tt", + "additive":"VE", + "lod_score":1 + }] + + marker_2 = [{ + "name": "MK1", + "chr": "C1", + "cM": "1", + "Mb": "12000", + "genotypes": [], + "dominance":"TT", + "additive":"VA", + "p_wald":4.6 + }] + results = trim_markers_for_figure(markers) + result_2 = trim_markers_for_figure(marker_2) + expected = [ + { + "name": "MK1", + "chr": "C1", + "cM": "1", + "Mb": "12000", + "genotypes": [], + "dominance":"TT", + "additive":"VA", + "lod_score":0.5 + }, + { + "name": "MK1", + "chr": "C3", + "cM": "45", + "Mb": "1", + "genotypes": [], + "dominance":"Tt", + "additive":"VE", + "lod_score":1 + } + + ] + self.assertEqual(results, expected) + self.assertEqual(result_2, marker_2) + + def test_export_mapping_results(self): + """test for exporting mapping results""" + datetime_mock = mock.Mock(wraps=datetime.datetime) + datetime_mock.now.return_value = datetime.datetime( + 2019, 9, 1, 10, 12, 12) + + markers = [{ + "name": "MK1", + "chr": "C1", + "cM": "1", + "Mb": "12000", + "genotypes": [], + "dominance":"TT", + "additive":"VA", + "lod_score":3 + }, + { + "name": "MK2", + "chr": "C2", + "cM": "15", + "Mb": "10000", + "genotypes": [], + "lod_score":7 + }, + { + "name": "MK1", + "chr": "C3", + "cM": "45", + "Mb": "1", + "genotypes": [], + "dominance":"Tt", + "additive":"VE", + "lod_score":7 + }] + + with mock.patch("builtins.open", mock.mock_open()) as mock_open: + + with mock.patch("gn2.wqflask.marker_regression.run_mapping.datetime.datetime", new=datetime_mock): + export_mapping_results(dataset=self.dataset, trait=self.trait, markers=markers, + results_path="~/results", mapping_method="gemma", mapping_scale="physic", + score_type="-logP", transform="qnorm", + covariates="Dataset1:Trait1,Dataset2:Trait2", + n_samples="100", vals_hash="") + + write_calls = [ + mock.call('Time/Date: 09/01/19 / 10:12:12\n'), + mock.call('Population: Human GP1_\n'), mock.call( + 'Data Set: dataset_1\n'), + mock.call('Trait: Test Name\n'), + mock.call('Trait Hash: \n'), + mock.call('N Samples: 100\n'), + mock.call('Mapping Tool: gemma\n'), + mock.call('Transform - Quantile Normalized\n'), + mock.call('Gene Symbol: IGFI\n'), mock.call( + 'Location: X1 @ 123313 Mb\n'), + mock.call('Cofactors (dataset - trait):\n'), + mock.call('Trait1 - Dataset1\n'), + mock.call('Trait2 - Dataset2\n'), + mock.call('\n'), mock.call('Name,Chr,'), + mock.call('Mb,-logP'), + mock.call(',Additive'), mock.call(',Dominance'), + mock.call('\n'), mock.call('MK1,C1,'), + mock.call('12000,'), mock.call('3'), + mock.call(',VA'), mock.call(',TT'), + mock.call('\n'), mock.call('MK2,C2,'), + mock.call('10000,'), mock.call('7'), + mock.call('\n'), mock.call('MK1,C3,'), + mock.call('1,'), mock.call('7'), + mock.call(',VE'), mock.call(',Tt') + ] + mock_open.assert_called_once_with("~/results", "w+") + filehandler = mock_open() + filehandler.write.assert_has_calls(write_calls) + + @mock.patch("gn2.wqflask.marker_regression.run_mapping.random.choice") + def test_write_input_for_browser(self, mock_choice): + """test for writing input for browser""" + mock_choice.side_effect = ["F", "i", "l", "e", "s", "x"] + with mock.patch("builtins.open", mock.mock_open()) as mock_open: + expected = ['GP1__Filesx_GWAS', 'GP1__Filesx_ANNOT'] + + results = write_input_for_browser( + this_dataset=self.dataset, gwas_results={}, annotations={}) + self.assertEqual(results, expected) + + def test_get_perm_strata(self): + categorical_vars = ["C1", "C2", "W1"] + used_samples = ["S1", "S2"] + sample_list = AttributeSetter({"sample_attribute_values": { + "S1": { + "c1": "c1_value", + "c2": "c2_value", + "w1": "w1_value" + }, + "S2": { + "w1": "w2_value", + "w2": "w2_value" + }, + "S3": { + + "c1": "c1_value", + "c2": "c2_value" + }, + }}) + results = get_perm_strata(this_trait={}, sample_list=sample_list, + categorical_vars=categorical_vars, used_samples=used_samples) + self.assertEqual(results, [1, 1]) + + def test_get_chr_length(self): + """test for getting chromosome length""" + cursor = mock.MagicMock() + chromosomes = AttributeSetter({"chromosomes": self.chromosomes}) + dataset = AttributeSetter({"species": chromosomes}) + results = get_chr_lengths( + mapping_scale="physic", mapping_method="reaper", dataset=dataset, qtl_results=[]) + chr_lengths = [] + for key, chromo in self.chromosomes.chromosomes(cursor).items(): + chr_lengths.append({"chr": chromo.name, "size": chromo.length}) + + self.assertEqual(chr_lengths, results) + + qtl_results = [{ + "chr": "16", + "cM": "0.2" + }, + { + "chr": "12", + "cM": "0.5" + }, + { + "chr": "18", + "cM": "0.1" + }, + { + "chr": "22", + "cM": "0.4" + }, + ] + + result_with_other_mapping_scale = get_chr_lengths( + mapping_scale="other", mapping_method="reaper", dataset=dataset, qtl_results=qtl_results) + expected_value = [{'chr': '1', 'size': '0'}, { + 'chr': '16', 'size': '500000.0'}, {'chr': '18', 'size': '400000.0'}] + + self.assertEqual(result_with_other_mapping_scale, expected_value) diff --git a/gn2/tests/unit/wqflask/show_trait/__init__.py b/gn2/tests/unit/wqflask/show_trait/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/wqflask/show_trait/__init__.py diff --git a/gn2/tests/unit/wqflask/show_trait/test_export_trait_data.py b/gn2/tests/unit/wqflask/show_trait/test_export_trait_data.py new file mode 100644 index 00000000..1ecf394b --- /dev/null +++ b/gn2/tests/unit/wqflask/show_trait/test_export_trait_data.py @@ -0,0 +1,151 @@ +import datetime +import unittest +from unittest import mock +from gn2.wqflask.show_trait.export_trait_data import dict_to_sorted_list +from gn2.wqflask.show_trait.export_trait_data import cmp_samples +from gn2.wqflask.show_trait.export_trait_data import export_sample_table +from gn2.wqflask.show_trait.export_trait_data import get_export_metadata + + +class AttributesSetter: + def __init__(self, obj): + for key, value in obj.items(): + setattr(self, key, value) + + +class TestExportTraits(unittest.TestCase): + """Test methods for exporting traits and metadata""" + + @mock.patch("gn2.wqflask.show_trait.export_trait_data.datetime") + @mock.patch("gn2.wqflask.show_trait.export_trait_data.create_trait") + @mock.patch("gn2.wqflask.show_trait.export_trait_data.data_set") + def test_get_export_metadata(self, data_mock, trait_mock, date_mock): + """test for exporting metadata with dataset.type=Publish""" + mock_dataset = AttributesSetter({"type": "Publish", + "name": "HC_M2_0606_P", + "dataset_name": "HC_M2_0606_P"}) + + mock_dataset.group = AttributesSetter({"name": "C"}) + data_mock.create_dataset.return_value = mock_dataset + + trait_data = { + "symbol": "Nr3c1", + "description_display": "nuclear receptor subfamily 3,group C, member 1 (glucocorticoid receptor); distal 3' UTR", + "title": "Trait_1 title", + + "authors": "XL_1", + "journal": "" + + } + + date_mock.datetime.now.return_value = datetime.datetime( + 2022, 8, 8, 19, 2, 31, 628813) + trait_mock.return_value = AttributesSetter(trait_data) + + results = get_export_metadata({ + "trait_id": "1460303_at", + "trait_display_name": "1460303_at", + "dataset": "HC_M2_0606_P", + "group": "BXD", + }) + + expected = [["Phenotype ID:", "1460303_at"], + ["Phenotype URL: ", "http://genenetwork.org/show_trait?trait_id=1460303_at&dataset=HC_M2_0606_P"], + ["Group: ", "C"], + ["Phenotype: ", + 'nuclear receptor subfamily 3","group C"," member 1 (glucocorticoid receptor); distal 3\' UTR'], + ["Authors: ", "XL_1"], + ["Title: ", "Trait_1 title"], + ["Journal: ", "N/A"], + ["Dataset Link: ", "http://gn1.genenetwork.org/webqtl/main.py?FormID=sharinginfo&InfoPageName=HC_M2_0606_P"], + ["Export Date: ", "August 08, 2022"], + ["Export Time: ", "19:02 GMT"]] + + self.assertEqual(results, expected) + + def test_dict_to_sortedlist(self): + """test for conversion of dict to sorted list""" + sample1 = { + "other": "exp1", + "name": "exp2" + } + sample2 = { + "se": 1, + "num_cases": 4, + "value": 6, + "name": 3 + + } + rever = { + "name": 3, + "value": 6, + "num_cases": 4, + "se": 1 + } + oneItem = { + "item1": "one" + } + + self.assertEqual(["exp2", "exp1"], dict_to_sorted_list(sample1)) + self.assertEqual([3, 6, 1, 4], dict_to_sorted_list(sample2)) + self.assertEqual([3, 6, 1, 4], dict_to_sorted_list(rever)) + self.assertEqual(["one"], dict_to_sorted_list(oneItem)) + """test that the func returns the values not the keys""" + self.assertFalse(["other", "name"] == dict_to_sorted_list(sample1)) + + def test_cmp_samples(self): + """test for comparing samples function""" + sampleA = [ + [ + ("value", "other"), + ("name", "test_name") + ] + ] + sampleB = [ + [ + ("value", "other"), + ("unknown", "test_name") + ] + ] + sampleC = [ + [("other", "value"), + ("name", "value") + ], + [ + ("name", "value"), + ("value", "name") + ], + [ + ("other", "value"), + ("name", "value" + )], + [ + ("name", "name1"), + ("se", "valuex") + ], + [( + "value", "name1"), + ("se", "valuex") + ], + [( + "other", "name1"), + ("se", "valuex" + ) + ], + [( + "name", "name_val"), + ("num_cases", "num_val") + ], + [( + "other_a", "val_a"), + ("other_b", "val" + ) + ] + ] + results = [cmp_samples(val[0], val[1]) for val in sampleA] + resultB = [cmp_samples(val[0], val[1]) for val in sampleB] + resultC = [cmp_samples(val[0], val[1]) for val in sampleC] + + self.assertEqual(1, *results) + self.assertEqual(-1, *resultB) + self.assertEqual([1, -1, 1, -1, -1, 1, -1, -1], resultC) diff --git a/gn2/tests/unit/wqflask/show_trait/test_get_max_digits.py b/gn2/tests/unit/wqflask/show_trait/test_get_max_digits.py new file mode 100644 index 00000000..45484f17 --- /dev/null +++ b/gn2/tests/unit/wqflask/show_trait/test_get_max_digits.py @@ -0,0 +1,15 @@ +import pytest +import unittest + +from gn2.wqflask.show_trait.show_trait import get_max_digits + +@unittest.skip("Too complicated") +@pytest.mark.parametrize( + "trait_vals,expected", + ((( + (0, 1345, 92, 734), + (234253, 33, 153, 5352), + (3542, 24, 135)), + [3, 5, 3]),)) +def test_get_max_digits(trait_vals, expected): + assert get_max_digits(trait_vals) == expected diff --git a/gn2/tests/unit/wqflask/snp_browser/__init__.py b/gn2/tests/unit/wqflask/snp_browser/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/wqflask/snp_browser/__init__.py diff --git a/gn2/tests/unit/wqflask/snp_browser/test_snp_browser.py b/gn2/tests/unit/wqflask/snp_browser/test_snp_browser.py new file mode 100644 index 00000000..bf0421c0 --- /dev/null +++ b/gn2/tests/unit/wqflask/snp_browser/test_snp_browser.py @@ -0,0 +1,206 @@ +import unittest +from unittest import mock +from gn2.wqflask import app +from gn2.wqflask.snp_browser.snp_browser import get_gene_id +from gn2.wqflask.snp_browser.snp_browser import get_gene_id_name_dict +from gn2.wqflask.snp_browser.snp_browser import check_if_in_gene +from gn2.wqflask.snp_browser.snp_browser import get_browser_sample_lists +from gn2.wqflask.snp_browser.snp_browser import get_header_list + + +class TestSnpBrowser(unittest.TestCase): + def setUp(self): + self.app_context = app.app_context() + self.app_context.push() + + def tearDown(self): + self.app_context.pop() + + def test_get_header_list(self): + empty_columns = { + "snp_source": "false", + "conservation_score": "true", + "gene_name": "false", + "transcript": "false", + "exon": "false", + "domain_2": "true", + "function": "false", + "function_details": "true", + } + strains = {"mouse": ["S1", "S2", "S3", "S4", "S5"], "rat": []} + expected_results = ( + [ + [ + "Index", + "SNP ID", + "Chr", + "Mb", + "Alleles", + "ConScore", + "Domain 1", + "Domain 2", + "Details", + ], + ["S1", "S2", "S3", "S4", "S5"], + ], + 5, + [ + "index", + "snp_name", + "chr", + "mb_formatted", + "alleles", + "conservation_score", + "domain_1", + "domain_2", + "function_details", + "S1", + "S2", + "S3", + "S4", + "S5", + ], + ) + + results_with_snp = get_header_list( + variant_type="SNP", + strains=strains, + species="Mouse", + empty_columns=empty_columns, + ) + results_with_indel = get_header_list( + variant_type="InDel", strains=strains, species="rat", empty_columns=[] + ) + expected_results_with_indel = ( + [ + "Index", + "ID", + "Type", + "InDel Chr", + "Mb Start", + "Mb End", + "Strand", + "Size", + "Sequence", + "Source", + ], + 0, + [ + "index", + "indel_name", + "indel_type", + "indel_chr", + "indel_mb_s", + "indel_mb_e", + "indel_strand", + "indel_size", + "indel_sequence", + "source_name", + ], + ) + + self.assertEqual(expected_results, results_with_snp) + self.assertEqual(expected_results_with_indel, results_with_indel) + + @mock.patch("gn2.wqflask.snp_browser.snp_browser.database_connection") + def test_get_gene_id(self, mock_db): + db_query_value = ( + "SELECT geneId FROM GeneList WHERE " "SpeciesId = %s AND geneSymbol = %s" + ) + conn = mock.MagicMock() + mock_db.return_value.__enter__.return_value = conn + with conn.cursor() as cursor: + cursor.fetchone.return_value = ( + ("517d729f-aa13-4413" "-a885-40a3f7ff768a"), + ) + + results = get_gene_id( + species_id="c9c0f59e-1259-4cba-91e6-831ef1a99c83", gene_name="INSR" + ) + cursor.execute.assert_called_once_with( + db_query_value, ("c9c0f59e-1259-4cba-91e6-831ef1a99c83", "INSR") + ) + self.assertEqual(results, "517d729f-aa13-4413-a885-40a3f7ff768a") + + @mock.patch("gn2.wqflask.snp_browser.snp_browser.database_connection") + def test_gene_id_name_dict(self, mock_db): + no_gene_names = [] + conn = mock.MagicMock() + mock_db.return_value.__enter__.return_value = conn + with conn.cursor() as cursor: + cursor.fetchall.side_effect = [ + [], + [ + ("fsdf43-fseferger-f22", "GH1"), + ("1sdf43-fsewferger-f22", "GH2"), + ("fwdj43-fstferger-f22", "GH3"), + ], + ] + self.assertEqual( + "", + get_gene_id_name_dict( + species_id="fregb343bui43g4", gene_name_list=no_gene_names + ), + ) + gene_name_list = ["GH1", "GH2", "GH3"] + no_results = get_gene_id_name_dict( + species_id="ret3-32rf32", gene_name_list=gene_name_list + ) + results_found = get_gene_id_name_dict( + species_id="ret3-32rf32", gene_name_list=gene_name_list + ) + expected_found = { + "GH1": "fsdf43-fseferger-f22", + "GH2": "1sdf43-fsewferger-f22", + "GH3": "fwdj43-fstferger-f22", + } + db_query_value = ( + "SELECT geneId, geneSymbol FROM GeneList WHERE " + "SpeciesId = %s AND geneSymbol in (%s, %s, %s)" + ) + cursor.execute.assert_called_with( + db_query_value, ("ret3-32rf32", "GH1", "GH2", "GH3") + ) + self.assertEqual(results_found, expected_found) + self.assertEqual(no_results, {}) + + @mock.patch("gn2.wqflask.snp_browser.snp_browser.database_connection") + def test_check_if_in_gene(self, mock_db): + conn = mock.MagicMock() + mock_db.return_value.__enter__.return_value = conn + with conn.cursor() as cursor: + cursor.fetchone.side_effect = [("fsdf-232sdf-sdf", "GHA"), ""] + results_found = check_if_in_gene( + species_id="517d729f-aa13-4413-a885-40a3f7ff768a", chr_="CH1", mb=12.09 + ) + self.assertEqual(results_found, ["fsdf-232sdf-sdf", "GHA"]) + db_query_value = ( + "SELECT geneId, geneSymbol FROM GeneList " + "WHERE SpeciesId = %s AND chromosome = %s " + "AND (txStart < %s AND txEnd > %s)" + ) + gene_not_found = check_if_in_gene( + species_id="517d729f-aa13-4413-a885-40a3f7ff768a", chr_="CH1", mb=12.09 + ) + cursor.execute.assert_has_calls( + [ + mock.call( + db_query_value, + ("517d729f-aa13-4413-a885-40a3f7ff768a", "CH1", 12.09, 12.09), + ), + mock.call( + db_query_value, + ("517d729f-aa13-4413-a885-40a3f7ff768a", "CH1", 12.09, 12.09), + ), + ] + ) + self.assertEqual(gene_not_found, "") + + @mock.patch("gn2.wqflask.snp_browser.snp_browser.database_connection") + def test_get_browser_sample_lists(self, mock_db): + conn = mock.MagicMock() + mock_db.return_value.__enter__.return_value = conn + with conn.cursor() as cursor: + cursor.execute.return_value.fetchall.return_value = [] + results = get_browser_sample_lists(species_id="12") + self.assertEqual(results, {"mouse": [], "rat": []}) diff --git a/gn2/tests/unit/wqflask/test_collect.py b/gn2/tests/unit/wqflask/test_collect.py new file mode 100644 index 00000000..6c1bafa5 --- /dev/null +++ b/gn2/tests/unit/wqflask/test_collect.py @@ -0,0 +1,75 @@ +"""Test cases for some methods in collect.py""" + +import unittest +from unittest import mock + +from flask import Flask +from gn2.wqflask.collect import process_traits + +app = Flask(__name__) + + +class MockSession: + """Helper class for mocking wqflask.collect.g.user_session.logged_in""" + + def __init__(self, is_logged_in=False): + self.is_logged_in = is_logged_in + + @property + def logged_in(self): + return self.is_logged_in + + +class MockFlaskG: + """Helper class for mocking wqflask.collect.g.user_session""" + + def __init__(self, is_logged_in=False): + self.is_logged_in = is_logged_in + + @property + def user_session(self): + if self.is_logged_in: + return MockSession(is_logged_in=True) + return MockSession() + + +class TestCollect(unittest.TestCase): + + def setUp(self): + self.app_context = app.app_context() + self.app_context.push() + + def tearDown(self): + self.app_context.pop() + + @mock.patch("gn2.wqflask.collect.g", MockFlaskG()) + def test_process_traits_with_bytestring(self): + """ + Test that the correct traits are returned when the user is logged + out and bytes are used. + """ + self.assertEqual(sorted(process_traits( + b'1452452_at:HC_M2_0606_P:163d04f7db7c9e110de6,' + b'1452447_at:HC_M2_0606_P:eeece8fceb67072debea,' + b'1451401_a_at:HC_M2_0606_P:a043d23b3b3906d8318e,' + b'1429252_at:HC_M2_0606_P:6fa378b349bc9180e8f5')), + sorted(['1429252_at:HC_M2_0606_P', + '1451401_a_at:HC_M2_0606_P', + '1452447_at:HC_M2_0606_P', + '1452452_at:HC_M2_0606_P'])) + + @mock.patch("gn2.wqflask.collect.g", MockFlaskG()) + def test_process_traits_with_normal_string(self): + """ + Test that the correct traits are returned when the user is logged + out and a normal string is used. + """ + self.assertEqual(sorted(process_traits( + '1452452_at:HC_M2_0606_P:163d04f7db7c9e110de6,' + '1452447_at:HC_M2_0606_P:eeece8fceb67072debea,' + '1451401_a_at:HC_M2_0606_P:a043d23b3b3906d8318e,' + '1429252_at:HC_M2_0606_P:6fa378b349bc9180e8f5')), + sorted(['1429252_at:HC_M2_0606_P', + '1451401_a_at:HC_M2_0606_P', + '1452447_at:HC_M2_0606_P', + '1452452_at:HC_M2_0606_P'])) diff --git a/gn2/tests/unit/wqflask/test_pbkdf2.py b/gn2/tests/unit/wqflask/test_pbkdf2.py new file mode 100644 index 00000000..ed4eff4f --- /dev/null +++ b/gn2/tests/unit/wqflask/test_pbkdf2.py @@ -0,0 +1,61 @@ +"""Test cases pbkdf2""" + +import unittest +from gn2.wqflask.pbkdf2 import pbkdf2_hex + + +class TestPbkdf2(unittest.TestCase): + def test_pbkdf2_hex(self): + """ + Test pbkdf2_hex function + """ + + for password, salt, iterations, keylen, expected_value in [ + ('password', b'salt', 1, 20, + '0c60c80f961f0e71f3a9b524af6012062fe037a6'), + ('password', b'salt', 2, 20, + 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957'), + ('password', b'salt', 4096, 20, + '4b007901b765489abead49d926f721d065a429c1'), + ('passwordPASSWORDpassword', + b'saltSALTsaltSALTsaltSALTsaltSALTsalt', + 4096, 25, + '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038'), + ('pass\x00word', b'sa\x00lt', 4096, 16, + '56fa6aa75548099dcc37d7f03425e0c3'), + ('password', b'ATHENA.MIT.EDUraeburn', 1, 16, + 'cdedb5281bb2f801565a1122b2563515'), + ('password', b'ATHENA.MIT.EDUraeburn', 1, 32, + ('cdedb5281bb2f80' + '1565a1122b256351' + '50ad1f7a04bb9f3a33' + '3ecc0e2e1f70837')), + ('password', b'ATHENA.MIT.EDUraeburn', 2, 16, + '01dbee7f4a9e243e988b62c73cda935d'), + ('password', b'ATHENA.MIT.EDUraeburn', 2, 32, + ('01dbee7f4a9e243e9' + '88b62c73cda935da05' + '378b93244ec8f48a99' + 'e61ad799d86')), + ('password', b'ATHENA.MIT.EDUraeburn', 1200, 32, + ('5c08eb61fdf71e' + '4e4ec3cf6ba1f55' + '12ba7e52ddbc5e51' + '42f708a31e2e62b1e13')), + ('X' * 64, b'pass phrase equals block size', 1200, 32, + ('139c30c0966bc32ba' + '55fdbf212530ac9c5' + 'ec59f1a452f5cc9ad' + '940fea0598ed1')), + ('X' * 65, b'pass phrase exceeds block size', 1200, 32, + ('9ccad6d468770cd' + '51b10e6a68721be6' + '11a8b4d282601db3' + 'b36be9246915ec82a')) + ]: + self.assertEqual( + pbkdf2_hex(data=password, + salt=salt, + iterations=iterations, + keylen=keylen), + expected_value) diff --git a/gn2/tests/unit/wqflask/test_resource_manager.py b/gn2/tests/unit/wqflask/test_resource_manager.py new file mode 100644 index 00000000..a4b3a0ee --- /dev/null +++ b/gn2/tests/unit/wqflask/test_resource_manager.py @@ -0,0 +1,94 @@ +"""Test cases for wqflask/resource_manager.py""" +import json +import unittest + +from unittest import mock +from gn3.authentication import get_user_membership +from gn3.authentication import get_highest_user_access_role +from gn3.authentication import DataRole +from gn3.authentication import AdminRole + + +class TestGetUserMembership(unittest.TestCase): + """Test cases for `get_user_membership`""" + + def setUp(self): + conn = mock.MagicMock() + conn.hgetall.return_value = { + '7fa95d07-0e2d-4bc5-b47c-448fdc1260b2': ( + '{"name": "editors", ' + '"admins": ["8ad942fe-490d-453e-bd37-56f252e41604", "rand"], ' + '"members": ["8ad942fe-490d-453e-bd37-56f252e41603", ' + '"rand"], ' + '"changed_timestamp": "Oct 06 2021 06:39PM", ' + '"created_timestamp": "Oct 06 2021 06:39PM"}')} + self.conn = conn + + def test_user_is_group_member_only(self): + """Test that a user is only a group member""" + self.assertEqual( + get_user_membership( + conn=self.conn, + user_id="8ad942fe-490d-453e-bd37-56f252e41603", + group_id="7fa95d07-0e2d-4bc5-b47c-448fdc1260b2"), + {"member": True, + "admin": False}) + + def test_user_is_group_admin_only(self): + """Test that a user is a group admin only""" + self.assertEqual( + get_user_membership( + conn=self.conn, + user_id="8ad942fe-490d-453e-bd37-56f252e41604", + group_id="7fa95d07-0e2d-4bc5-b47c-448fdc1260b2"), + {"member": False, + "admin": True}) + + def test_user_is_both_group_member_and_admin(self): + """Test that a user is both an admin and member of a group""" + self.assertEqual( + get_user_membership( + conn=self.conn, + user_id="rand", + group_id="7fa95d07-0e2d-4bc5-b47c-448fdc1260b2"), + {"member": True, + "admin": True}) + + +class TestCheckUserAccessRole(unittest.TestCase): + """Test cases for `get_highest_user_access_role`""" + + @mock.patch("gn2.wqflask.resource_manager.requests.get") + def test_edit_access(self, requests_mock): + """Test that the right access roles are set""" + response = mock.PropertyMock(return_value=json.dumps( + { + 'data': ['no-access', 'view', 'edit', ], + 'metadata': ['no-access', 'view', 'edit', ], + 'admin': ['not-admin', 'edit-access', ], + } + )) + type(requests_mock.return_value).content = response + self.assertEqual(get_highest_user_access_role( + resource_id="0196d92e1665091f202f", + user_id="8ad942fe-490d-453e-bd37"), + {"data": DataRole.EDIT, + "metadata": DataRole.EDIT, + "admin": AdminRole.EDIT_ACCESS}) + + @mock.patch("gn2.wqflask.resource_manager.requests.get") + def test_no_access(self, requests_mock): + response = mock.PropertyMock(return_value=json.dumps( + { + 'data': ['no-access', ], + 'metadata': ['no-access', ], + 'admin': ['not-admin', ], + } + )) + type(requests_mock.return_value).content = response + self.assertEqual(get_highest_user_access_role( + resource_id="0196d92e1665091f202f", + user_id=""), + {"data": DataRole.NO_ACCESS, + "metadata": DataRole.NO_ACCESS, + "admin": AdminRole.NOT_ADMIN}) diff --git a/gn2/tests/unit/wqflask/test_server_side.py b/gn2/tests/unit/wqflask/test_server_side.py new file mode 100644 index 00000000..b3e90eb9 --- /dev/null +++ b/gn2/tests/unit/wqflask/test_server_side.py @@ -0,0 +1,34 @@ +import unittest + +from gn2.wqflask.server_side import ServerSideTable + + +class TestServerSideTableTests(unittest.TestCase): + """ + Test the ServerSideTable class + + test table: + first, second, third + 'd', 4, 'zz' + 'b', 2, 'aa' + 'c', 1, 'ss' + """ + + def test_get_page(self): + rows_count = 3 + table_rows = [ + {'first': 'd', 'second': 4, 'third': 'zz'}, + {'first': 'b', 'second': 2, 'third': 'aa'}, + {'first': 'c', 'second': 1, 'third': 'ss'}, + ] + headers = ['first', 'second', 'third'] + request_args = {'sEcho': '1', 'iSortCol_0': '1', 'iSortingCols': '1', + 'sSortDir_0': 'asc', 'iDisplayStart': '0', 'iDisplayLength': '3'} + + test_page = ServerSideTable( + rows_count, table_rows, headers, request_args).get_page() + self.assertEqual(test_page['sEcho'], '1') + self.assertEqual(test_page['iTotalRecords'], 'nan') + self.assertEqual(test_page['iTotalDisplayRecords'], '3') + self.assertEqual(test_page['data'], [{'first': 'b', 'second': 2, 'third': 'aa'}, { + 'first': 'c', 'second': 1, 'third': 'ss'}, {'first': 'd', 'second': 4, 'third': 'zz'}]) diff --git a/gn2/tests/unit/wqflask/test_user_login.py b/gn2/tests/unit/wqflask/test_user_login.py new file mode 100644 index 00000000..84e25d45 --- /dev/null +++ b/gn2/tests/unit/wqflask/test_user_login.py @@ -0,0 +1,21 @@ +"""Test cases for some methods in login.py""" + +import unittest +from gn2.wqflask.user_login import encode_password + + +class TestUserLogin(unittest.TestCase): + def test_encode_password(self): + """ + Test encode password + """ + pass_gen_fields = { + "salt": "salt", + "hashfunc": "sha1", + "iterations": 4096, + "keylength": 20, + } + self.assertEqual( + encode_password(pass_gen_fields, + "password").get("password"), + '4b007901b765489abead49d926f721d065a429c1') diff --git a/gn2/tests/unit/wqflask/test_user_session.py b/gn2/tests/unit/wqflask/test_user_session.py new file mode 100644 index 00000000..944e5b6a --- /dev/null +++ b/gn2/tests/unit/wqflask/test_user_session.py @@ -0,0 +1,15 @@ +"""Test cases for some methods in user_session.py""" + +import unittest +from gn2.wqflask.user_session import verify_cookie + + +class TestUserSession(unittest.TestCase): + def test_verify_cookie(self): + """ + Test cookie verification + """ + self.assertEqual( + "3f4c1dbf-5b56-4260-87d6-f35445bda37e", + verify_cookie(("3f4c1dbf-5b56-4260-87d6-" + "f35445bda37e:af4fcf5eace9e7c864ce"))) diff --git a/gn2/tests/unit/wqflask/wgcna/__init__.py b/gn2/tests/unit/wqflask/wgcna/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/tests/unit/wqflask/wgcna/__init__.py diff --git a/gn2/tests/unit/wqflask/wgcna/test_wgcna.py b/gn2/tests/unit/wqflask/wgcna/test_wgcna.py new file mode 100644 index 00000000..2ed5e08f --- /dev/null +++ b/gn2/tests/unit/wqflask/wgcna/test_wgcna.py @@ -0,0 +1,50 @@ + +"""module contains for processing gn3 wgcna data""" +from unittest import TestCase + +from gn2.wqflask.wgcna.gn3_wgcna import process_wgcna_data + + +class DataProcessingTests(TestCase): + """class contains data processing tests""" + + def test_data_processing(self): + """test for parsing data for datatable""" + output = { + "input": { + "sample_names": ["BXD1", "BXD2", "BXD3", "BXD4", "BXD5", "BXD6"], + + }, + "output": { + "ModEigens": { + "MEturquoise": [ + 0.0646677768085351, + 0.137200224277058, + 0.63451113720732, + -0.544002665501479, + -0.489487590361863, + 0.197111117570427 + ], + "MEgrey": [ + 0.213, + 0.214, + 0.3141, + -0.545, + -0.423, + 0.156, + ] + }}} + + row_data = [['BXD1', 0.065, 0.213], + ['BXD2', 0.137, 0.214], + ['BXD3', 0.635, 0.314], + ['BXD4', -0.544, -0.545], + ['BXD5', -0.489, -0.423], + ['BXD6', 0.197, 0.156]] + + expected_results = { + "col_names": ["sample_names", "MEturquoise", "MEgrey"], + "mod_dataset": row_data + } + + self.assertEqual(process_wgcna_data(output), expected_results) diff --git a/gn2/tests/wqflask/show_trait/testSampleList.py b/gn2/tests/wqflask/show_trait/testSampleList.py new file mode 100644 index 00000000..1c5478bb --- /dev/null +++ b/gn2/tests/wqflask/show_trait/testSampleList.py @@ -0,0 +1,17 @@ +import unittest +import re +from unittest import mock +from gn2.wqflask.show_trait.SampleList import natural_sort + + +class TestSampleList(unittest.TestCase): + def test_natural_sort(self): + "Sort the list into natural alphanumeric order." + + characters_list = ["z", "f", "q", "s", "t", "a", "g"] + names_list = ["temp1", "publish", "Sample", "Dataset"] + sorted_list_a = natural_sort(characters_list) + sorted_list_b = natural_sort(names_list) + self.assertEqual(sorted_list_a, ["a", "f", "g", "q", "s", "t", "z"]) + self.assertEqual( + sorted_list_b, ["Dataset", "Sample", "publish", "temp1"]) diff --git a/gn2/tests/wqflask/show_trait/test_show_trait.py b/gn2/tests/wqflask/show_trait/test_show_trait.py new file mode 100644 index 00000000..51b0c82c --- /dev/null +++ b/gn2/tests/wqflask/show_trait/test_show_trait.py @@ -0,0 +1,255 @@ +"""test for wqflask/show_trait/test_show_trait.py""" +import unittest +import pytest +from unittest import mock +from gn2.wqflask.show_trait.show_trait import check_if_attr_exists +from gn2.wqflask.show_trait.show_trait import get_ncbi_summary +from gn2.wqflask.show_trait.show_trait import has_num_cases +from gn2.wqflask.show_trait.show_trait import get_table_widths +from gn2.wqflask.show_trait.show_trait import get_categorical_variables +from gn2.wqflask.show_trait.show_trait import get_trait_units +from gn2.wqflask.show_trait.show_trait import get_nearest_marker +from gn2.wqflask.show_trait.show_trait import get_genotype_scales +from gn2.wqflask.show_trait.show_trait import get_scales_from_genofile + + +class TraitObject: + def __init__(self, obj): + self.distinct_values = [] + self.id = "" + for key, value in obj.items(): + setattr(self, key, value) + self.id += str(value) + + +@pytest.mark.parametrize( + ('trait', 'id_type', 'expected'), + ( + (TraitObject({"id_type": "id"}), "id_type", True), + (TraitObject({"sample_name": ['samp1']}), "id_type", False), + (TraitObject({"sample": ""}), "sample", False), + (TraitObject({"group": None}), "group", False), + (TraitObject({}), "any", False) + ), +) +def test_check_if_attr_exists(trait, id_type, expected): + """"test check_if_attr_exists""" + assert check_if_attr_exists(trait, id_type) == expected + + +def test_get_ncbi_summary_request(mocker): + trait = TraitObject({"geneid": "id"}) + mocker.patch("gn2.wqflask.show_trait.show_trait.check_if_attr_exists", + return_value=True) + mock_get = mocker.patch( + "gn2.wqflask.show_trait.show_trait.requests.get", + return_value=TraitObject({"content": """{ + "result":{ + "id":{ + "summary":"this is a summary of the geneid" + } + } + } + """})) + assert get_ncbi_summary(trait) == "this is a summary of the geneid" + mock_get.assert_called_once_with( + "http://eutils.ncbi.nlm.nih.gov/entrez/" + "eutils/esummary.fcgi?db=gene&id=" + f"{trait.geneid}&retmode=json" + ) + mock_get.side_effect = Exception("an error occurred") + assert get_ncbi_summary(trait) == None + + +class TestTraits(unittest.TestCase): + def test_hash_num_cases_is_probeset(self): + """test for hash num_cases with dataset.type set to Probeset""" + create_dataset = TraitObject({"type": "ProbeSet"}) + create_trait = TraitObject({"dataset": create_dataset}) + self.assertFalse(has_num_cases(create_trait)) + + def test_hash_num_cases_no_probeset(self): + """test for hash num cases with dataset.type not Probeset""" + create_dataset = TraitObject({"type": "Temp"}) + construct_data = { + "nm1": TraitObject({"num_cases": False}), + "nm2": TraitObject({"num_cases": True}), + "nm3": TraitObject({"num_cases": False}) + } + construct_data2 = { + "nm1": TraitObject({"num_cases": False}), + "nm2": TraitObject({"num_cases": False}), + "nm3": TraitObject({"num_cases": False}) + } + create_trait = TraitObject( + {"dataset": create_dataset, "data": construct_data}) + create_trait2 = TraitObject( + {"dataset": create_dataset, "data": construct_data2}) + + results = has_num_cases(create_trait) + self.assertTrue(has_num_cases(create_trait)) + self.assertFalse(has_num_cases(create_trait2)) + + def test_get_table_widths(self): + """test for getting table widths""" + sample_groups = [TraitObject({'se_exists': True, "attributes": ["attr1", "attr2", "attr3"]} + ), TraitObject( + {"se_exists": False, "attributes": ["at1", "at2"] + })] + + results_with_numcase = get_table_widths(sample_groups, True) + result_no_numcase = get_table_widths(sample_groups, False) + + results_one_sample = get_table_widths( + [TraitObject({"se_exists": True, "attributes": []})], True) + expected_with_numcase = (450, 645) + expected_no_numcase = (450, 644) + expected_one_sample = (250, 381) + self.assertEqual(results_with_numcase, expected_with_numcase) + self.assertEqual(result_no_numcase, expected_no_numcase) + self.assertEqual(results_one_sample, + expected_one_sample) + + def test_get_categorical_variables_no_sample_attributes(self): + """test for getting categorical variable names with no samples""" + trait = TraitObject({}) + sample_list = TraitObject({"se_exists": True, "attributes": []}) + self.assertEqual(get_categorical_variables(trait, sample_list), []) + + def test_get_categorical_variables_with_sample_attributes(self): + """test for getting categorical variable names with no samples""" + this_trait = TraitObject({"data": { + "Gene1": TraitObject({"extra_attributes": {"ex1": "ex1value"}}), + "Gene2": TraitObject({"extra_attributes": {"ex2": "ex2value"}}), + "Gene3": TraitObject({"extra_attributes": {"ex3": "ex3value"}}) + }}) + sample_list = TraitObject({"attributes": { + "sample_attribute_1": TraitObject({"name": "ex1"}), + "sample_attribute_2": TraitObject({"name": "ex2"}), + "sample_attribute_3": TraitObject({"name": "ex3"}), + "sample_attribute_4": TraitObject({"name": "not_in_extra_attributes"}) + }}) + results = get_categorical_variables(this_trait, sample_list) + self.assertEqual( + ["ex1", "ex2", "ex3", "not_in_extra_attributes"], results) + + def test_get_trait_units(self): + """test for getting trait units""" + trait = TraitObject( + {"description_fmt": "[this is a description] another test [N/A]"}) + trait_no_unit_type = TraitObject({"description_fmt": ""}) + results = get_trait_units(trait) + results_no_unit = get_trait_units(trait_no_unit_type) + self.assertEqual(results, "this is a descriptionN/A") + self.assertEqual(results_no_unit, "value") + + @mock.patch("gn2.wqflask.show_trait.show_trait.database_connection") + def test_get_nearest_marker(self, mock_db): + """test for getting nearest marker with non-empty db""" + conn = mock.MagicMock() + mock_db.return_value.__enter__.return_value = conn + with conn.cursor() as cursor: + cursor.fetchall.return_value = [ + ["Geno1", "Geno2"], ["Geno3"]] + + trait = TraitObject({ + "locus_chr": "test_chr", + "locus_mb": "test_mb" + }) + group_name = TraitObject({"name": "group_name"}) + this_db = TraitObject({"group": group_name}) + results_with_item_db = get_nearest_marker(trait, this_db) + cursor.execute.assert_called_with( + "SELECT Geno.Name FROM Geno, GenoXRef, " + "GenoFreeze WHERE Geno.Chr = %s " + "AND GenoXRef.GenoId = Geno.Id AND " + "GenoFreeze.Id = GenoXRef.GenoFreezeId " + "AND GenoFreeze.Name = %s " + "ORDER BY ABS( Geno.Mb - %s) LIMIT 1", + ('test_chr', 'group_nameGeno', 'test_mb')) + + self.assertEqual(results_with_item_db, "Geno1") + + @mock.patch("gn2.wqflask.show_trait.show_trait.database_connection") + def test_get_nearest_marker_empty_db(self, mock_db): + """test for getting nearest marker with empty db""" + conn = mock.MagicMock() + mock_db.return_value.__enter__.return_value = conn + with conn.cursor() as cursor: + cursor.fetchall.return_value = [] + trait = TraitObject({ + "locus_chr": "test_chr", + "locus_mb": "test_mb" + }) + group_name = TraitObject({"name": "group_name"}) + this_db = TraitObject({"group": group_name}) + results_empty_db = get_nearest_marker(trait, this_db) + cursor.execute.assert_called_once() + self.assertEqual(results_empty_db, "") + + @mock.patch("gn2.wqflask.show_trait.show_trait.get_scales_from_genofile") + def test_get_genotype_scales_with_genofile_is_list(self, mock_get_scales): + """test for getting genotype scales with genofile as list """ + # where genofile is instance of list + genofiles_list = [{"filename": "file1", "location": "~/data/files/f1"}, + {"filename": "file2", "location": "~/data/files/f2"}, + {"filename": "file3", "location": "~/data/files/f3"}] + + mock_get_scales.side_effect = [[["morgan", "cM"]], + [["morgan", "cM"]], + [["physic", "Mb"]]] + + results = get_genotype_scales(genofiles_list) + expected_results = { + "~/data/files/f1": [["morgan", "cM"]], + "~/data/files/f2": [["morgan", "cM"]], + "~/data/files/f3": [["physic", "Mb"]] + } + + multiple_calls = [mock.call('~/data/files/f1'), mock.call('~/data/files/f2'), + mock.call('~/data/files/f3')] + mock_get_scales.assert_has_calls(multiple_calls) + self.assertEqual(results, expected_results) + + @mock.patch("gn2.wqflask.show_trait.show_trait.get_scales_from_genofile") + def test_genotype_scales_with_genofile_other(self, mock_get_scales): + """test for getting genotype scales with genofile as a string""" + file_location = "~/another_file_location" + mock_get_scales.return_value = [["physic", "Mb"]] + expected_results = {f"{file_location}": [["physic", "Mb"]]} + self.assertEqual(get_genotype_scales(file_location), expected_results) + mock_get_scales.assert_called_once_with(file_location) + + @mock.patch("gn2.wqflask.show_trait.show_trait.locate_ignore_error") + def test_get_scales_from_genofile_found(self, mock_ignore_location): + """"add test for get scales from genofile where file is found""" + mock_ignore_location.return_value = True + geno_file = """ + #sample line with no @scales:other\n + #sample line @scales and :separated by semicolon\n + This attempts to check whether\n + """ + + geno_file_string = "@line start with @ and has @scale:morgan" + + file_location = "~/data/file.geno" + + mock_open_geno_file = mock.mock_open(read_data=geno_file) + with mock.patch("builtins.open", mock_open_geno_file): + results = get_scales_from_genofile(file_location) + self.assertEqual(results, [["morgan", "cM"]]) + + mock_open_string = mock.mock_open(read_data=geno_file_string) + + with mock.patch("builtins.open", mock_open_string): + result2 = get_scales_from_genofile(file_location) + self.assertEqual([['morgan', 'cM']], result2) + + @mock.patch("gn2.wqflask.show_trait.show_trait.locate_ignore_error") + def test_get_scales_from_genofile_not_found(self, mock_location_ignore): + mock_location_ignore.return_value = False + + expected_results = [["physic", "Mb"]] + results = get_scales_from_genofile("~/data/file") + mock_location_ignore.assert_called_once_with("~/data/file", "genotype") + self.assertEqual(results, expected_results) diff --git a/gn2/utility/Plot.py b/gn2/utility/Plot.py new file mode 100644 index 00000000..ace954e4 --- /dev/null +++ b/gn2/utility/Plot.py @@ -0,0 +1,343 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010) +# at rwilliams@uthsc.edu and xzhou15@uthsc.edu +# +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) +# +# Created by GeneNetwork Core Team 2010/08/10 +# +# Last updated by GeneNetwork Core Team 2010/10/20 + +from PIL import ImageColor +from PIL import ImageDraw +from PIL import ImageFont + +from math import * + +import gn2.utility.corestats as corestats +from gn2.base import webqtlConfig +from gn2.utility.pillow_utils import draw_rotated_text + + +# ---- Define common colours ---- # +BLUE = ImageColor.getrgb("blue") +BLACK = ImageColor.getrgb("black") +# ---- END: Define common colours ---- # + +# ---- FONT FILES ---- # +VERDANA_FILE = "./wqflask/static/fonts/verdana.ttf" +COUR_FILE = "./wqflask/static/fonts/courbd.ttf" +TAHOMA_FILE = "./wqflask/static/fonts/tahoma.ttf" +# ---- END: FONT FILES ---- # + + +def cformat(d, rank=0): + 'custom string format' + strD = "%2.6f" % d + + if rank == 0: + while strD[-1] in ('0', '.'): + if strD[-1] == '0' and strD[-2] == '.' and len(strD) <= 4: + break + elif strD[-1] == '.': + strD = strD[:-1] + break + else: + strD = strD[:-1] + + else: + strD = strD.split(".")[0] + + if strD == '-0.0': + strD = '0.0' + return strD + + +def frange(start, end=None, inc=1.0): + "A faster range-like function that does accept float increments..." + if end == None: + end = start + 0.0 + start = 0.0 + else: + start += 0.0 # force it to be a float + count = int((end - start) / inc) + if start + count * inc != end: + # Need to adjust the count. AFAICT, it always comes up one short. + count += 1 + L = [start] * count + for i in range(1, count): + L[i] = start + i * inc + return L + + +def find_outliers(vals): + """Calculates the upper and lower bounds of a set of sample/case values + + + >>> find_outliers([3.504, 5.234, 6.123, 7.234, 3.542, 5.341, 7.852, 4.555, 12.537]) + (11.252500000000001, 0.5364999999999993) + + >>> find_outliers([9,12,15,17,31,50,7,5,6,8]) + (32.0, -8.0) + + If there are no vals, returns None for the upper and lower bounds, + which code that calls it will have to deal with. + >>> find_outliers([]) + (None, None) + + """ + + if vals: + stats = corestats.Stats(vals) + low_hinge = stats.percentile(25) + up_hinge = stats.percentile(75) + hstep = 1.5 * (up_hinge - low_hinge) + + upper_bound = up_hinge + hstep + lower_bound = low_hinge - hstep + + else: + upper_bound = None + lower_bound = None + + return upper_bound, lower_bound + +# parameter: data is either object returned by reaper permutation function (called by MarkerRegressionPage.py) +# or the first object returned by direct (pair-scan) permu function (called by DirectPlotPage.py) + + +def plotBar(canvas, data, barColor=BLUE, axesColor=BLACK, labelColor=BLACK, XLabel=None, YLabel=None, title=None, offset=(60, 20, 40, 40), zoom=1): + im_drawer = ImageDraw.Draw(canvas) + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + plotHeight = canvas.size[1] - yTopOffset - yBottomOffset + if plotHeight <= 0 or plotWidth <= 0: + return + + if len(data) < 2: + return + + max_D = max(data) + min_D = min(data) + # add by NL 06-20-2011: fix the error: when max_D is infinite, log function in detScale will go wrong + if (max_D == float('inf') or max_D > webqtlConfig.MAXLRS) and min_D < webqtlConfig.MAXLRS: + max_D = webqtlConfig.MAXLRS # maximum LRS value + + xLow, xTop, stepX = detScale(min_D, max_D) + + # reduce data + # ZS: Used to determine number of bins for permutation output + step = ceil((xTop - xLow) / 50.0) + j = xLow + dataXY = [] + Count = [] + while j <= xTop: + dataXY.append(j) + Count.append(0) + j += step + + for i, item in enumerate(data): + if (item == float('inf') or item > webqtlConfig.MAXLRS) and min_D < webqtlConfig.MAXLRS: + item = webqtlConfig.MAXLRS # maximum LRS value + j = int((item - xLow) / step) + Count[j] += 1 + + yLow, yTop, stepY = detScale(0, max(Count)) + + # draw data + xScale = plotWidth / (xTop - xLow) + yScale = plotHeight / (yTop - yLow) + barWidth = xScale * step + + for i, count in enumerate(Count): + if count: + xc = (dataXY[i] - xLow) * xScale + xLeftOffset + yc = -(count - yLow) * yScale + yTopOffset + plotHeight + im_drawer.rectangle( + xy=((xc + 2, yc), (xc + barWidth - 2, yTopOffset + plotHeight)), + outline=barColor, fill=barColor) + + # draw drawing region + im_drawer.rectangle( + xy=((xLeftOffset, yTopOffset), + (xLeftOffset + plotWidth, yTopOffset + plotHeight)) + ) + + # draw scale + scaleFont = ImageFont.truetype(font=COUR_FILE, size=11) + x = xLow + for i in range(int(stepX) + 1): + xc = xLeftOffset + (x - xLow) * xScale + im_drawer.line( + xy=((xc, yTopOffset + plotHeight), + (xc, yTopOffset + plotHeight + 5)), + fill=axesColor) + strX = cformat(d=x, rank=0) + im_drawer.text( + text=strX, + xy=(xc - im_drawer.textsize(strX, font=scaleFont)[0] / 2, + yTopOffset + plotHeight + 14), font=scaleFont) + x += (xTop - xLow) / stepX + + y = yLow + for i in range(int(stepY) + 1): + yc = yTopOffset + plotHeight - (y - yLow) * yScale + im_drawer.line( + xy=((xLeftOffset, yc), (xLeftOffset - 5, yc)), fill=axesColor) + strY = "%d" % y + im_drawer.text( + text=strY, + xy=(xLeftOffset - im_drawer.textsize(strY, + font=scaleFont)[0] - 6, yc + 5), + font=scaleFont) + y += (yTop - yLow) / stepY + + # draw label + labelFont = ImageFont.truetype(font=TAHOMA_FILE, size=17) + if XLabel: + im_drawer.text( + text=XLabel, + xy=(xLeftOffset + ( + plotWidth - im_drawer.textsize(XLabel, font=labelFont)[0]) / 2.0, + yTopOffset + plotHeight + yBottomOffset - 10), + font=labelFont, fill=labelColor) + + if YLabel: + draw_rotated_text(canvas, text=YLabel, + xy=(19, + yTopOffset + plotHeight - ( + plotHeight - im_drawer.textsize( + YLabel, font=labelFont)[0]) / 2.0), + font=labelFont, fill=labelColor, angle=90) + + labelFont = ImageFont.truetype(font=VERDANA_FILE, size=16) + if title: + im_drawer.text( + text=title, + xy=(xLeftOffset + (plotWidth - im_drawer.textsize( + title, font=labelFont)[0]) / 2.0, + 20), + font=labelFont, fill=labelColor) + +# This function determines the scale of the plot + + +def detScaleOld(min, max): + if min >= max: + return None + elif min == -1.0 and max == 1.0: + return [-1.2, 1.2, 12] + else: + a = max - min + b = floor(log10(a)) + c = pow(10.0, b) + if a < c * 5.0: + c /= 2.0 + # print a,b,c + low = c * floor(min / c) + high = c * ceil(max / c) + return [low, high, round((high - low) / c)] + + +def detScale(min=0, max=0): + + if min >= max: + return None + elif min == -1.0 and max == 1.0: + return [-1.2, 1.2, 12] + else: + a = max - min + if max != 0: + max += 0.1 * a + if min != 0: + if min > 0 and min < 0.1 * a: + min = 0.0 + else: + min -= 0.1 * a + a = max - min + b = floor(log10(a)) + c = pow(10.0, b) + low = c * floor(min / c) + high = c * ceil(max / c) + n = round((high - low) / c) + div = 2.0 + while n < 5 or n > 15: + if n < 5: + c /= div + else: + c *= div + if div == 2.0: + div = 5.0 + else: + div = 2.0 + low = c * floor(min / c) + high = c * ceil(max / c) + n = round((high - low) / c) + + return [low, high, n] + + +def bluefunc(x): + return 1.0 / (1.0 + exp(-10 * (x - 0.6))) + + +def redfunc(x): + return 1.0 / (1.0 + exp(10 * (x - 0.5))) + + +def greenfunc(x): + return 1 - pow(redfunc(x + 0.2), 2) - bluefunc(x - 0.3) + + +def colorSpectrum(n=100): + multiple = 10 + if n == 1: + return [ImageColor.getrgb("rgb(100%,0%,0%)")] + elif n == 2: + return [ImageColor.getrgb("100%,0%,0%)"), + ImageColor.getrgb("rgb(0%,0%,100%)")] + elif n == 3: + return [ImageColor.getrgb("rgb(100%,0%,0%)"), + ImageColor.getrgb("rgb(0%,100%,0%)"), + ImageColor.getrgb("rgb(0%,0%,100%)")] + N = n * multiple + out = [None] * N + for i in range(N): + x = float(i) / N + out[i] = ImageColor.getrgb("rgb({}%,{}%,{}%".format( + *[int(i * 100) for i in ( + redfunc(x), greenfunc(x), bluefunc(x))])) + out2 = [out[0]] + step = N / float(n - 1) + j = 0 + for i in range(n - 2): + j += step + out2.append(out[int(j)]) + out2.append(out[-1]) + return out2 + + +def _test(): + import doctest + doctest.testmod() + + +if __name__ == "__main__": + _test() diff --git a/gn2/utility/TDCell.py b/gn2/utility/TDCell.py new file mode 100644 index 00000000..4b0f4b1d --- /dev/null +++ b/gn2/utility/TDCell.py @@ -0,0 +1,41 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010) +# at rwilliams@uthsc.edu and xzhou15@uthsc.edu +# +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) +# +# Created by GeneNetwork Core Team 2010/08/10 +# +# Last updated by GeneNetwork Core Team 2010/10/20 + +########################################################## +# +# Table Cell Class +# +########################################################## + + +class TDCell: + def __init__(self, html="", text="", val=0.0): + self.html = html # html, for web page + self.text = text # text value, for output to a text file + self.val = val # sort by value + + def __str__(self): + return self.text diff --git a/gn2/utility/THCell.py b/gn2/utility/THCell.py new file mode 100644 index 00000000..f533dcb8 --- /dev/null +++ b/gn2/utility/THCell.py @@ -0,0 +1,42 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010) +# at rwilliams@uthsc.edu and xzhou15@uthsc.edu +# +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) +# +# Created by GeneNetwork Core Team 2010/08/10 +# +# Last updated by GeneNetwork Core Team 2010/10/20 + +########################################################## +# +# Table Header Class +# +########################################################## + + +class THCell: + def __init__(self, html="", text="", sort=1, idx=-1): + self.html = html # html, for web page + self.text = text # Column text value + self.sort = sort # 0: not sortable, 1: yes + self.idx = idx # sort by value + + def __str__(self): + return self.text diff --git a/gn2/utility/__init__.py b/gn2/utility/__init__.py new file mode 100644 index 00000000..25273fa0 --- /dev/null +++ b/gn2/utility/__init__.py @@ -0,0 +1,35 @@ +from pprint import pformat as pf + +# Todo: Move these out of __init__ + + +class Bunch: + """Like a dictionary but using object notation""" + + def __init__(self, **kw): + self.__dict__ = kw + + def __repr__(self): + return pf(self.__dict__) + + +class Struct: + '''The recursive class for building and representing objects with. + + From http://stackoverflow.com/a/6573827/1175849 + + ''' + + def __init__(self, obj): + for k, v in list(obj.items()): + if isinstance(v, dict): + setattr(self, k, Struct(v)) + else: + setattr(self, k, v) + + def __getitem__(self, val): + return self.__dict__[val] + + def __repr__(self): + return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for + (k, v) in list(self.__dict__.items()))) diff --git a/gn2/utility/after.py b/gn2/utility/after.py new file mode 100644 index 00000000..5f802d35 --- /dev/null +++ b/gn2/utility/after.py @@ -0,0 +1,15 @@ +""" +See: http://flask.pocoo.org/docs/patterns/deferredcallbacks/#deferred-callbacks + +""" + +from flask import g + +from gn2.wqflask import app + + +def after_this_request(f): + if not hasattr(g, 'after_request_callbacks'): + g.after_request_callbacks = [] + g.after_request_callbacks.append(f) + return f diff --git a/gn2/utility/authentication_tools.py b/gn2/utility/authentication_tools.py new file mode 100644 index 00000000..935f629e --- /dev/null +++ b/gn2/utility/authentication_tools.py @@ -0,0 +1,150 @@ +import json +import requests + +from flask import g +from gn2.wqflask.database import database_connection +from gn2.base import webqtlConfig + +from gn2.utility.redis_tools import (get_redis_conn, + get_resource_info, + get_resource_id, + add_resource) +from gn2.utility.tools import get_setting, GN_PROXY_URL + +Redis = get_redis_conn() + +def check_resource_availability(dataset, user_id, trait_id=None): + # At least for now assume temporary entered traits are accessible + if type(dataset) == str or dataset.type == "Temp": + return webqtlConfig.DEFAULT_PRIVILEGES + + resource_id = get_resource_id(dataset, trait_id) + + # ZS: This should never be false, but it's technically possible if + # a non-Temp dataset somehow had a type other than + # Publish/ProbeSet/Geno + if resource_id: + resource_info = get_resource_info(resource_id) + + # If resource isn't already in redis, add it with default + # privileges + if not resource_info: + resource_info = add_new_resource(dataset, trait_id) + + # Check if super-user - we should probably come up with some + # way to integrate this into the proxy + if user_id in Redis.smembers("super_users"): + return webqtlConfig.SUPER_PRIVILEGES + + response = None + the_url = f"{GN_PROXY_URL}available?resource={resource_id}&user={user_id}" + try: + response = json.loads(requests.get(the_url).content) + except: + response = resource_info['default_mask'] + + return response + + +def add_new_resource(dataset, trait_id=None): + resource_ob = { + 'owner_id': "none", # webqtlConfig.DEFAULT_OWNER_ID, + 'default_mask': webqtlConfig.DEFAULT_PRIVILEGES, + 'group_masks': {} + } + + if dataset.type == "Publish": + group_code = get_group_code(dataset) + if group_code is None: + group_code = "" + resource_ob['name'] = group_code + "_" + str(trait_id) + resource_ob['data'] = { + 'dataset': dataset.id, + 'trait': trait_id + } + resource_ob['type'] = 'dataset-publish' + elif dataset.type == "Geno": + resource_ob['name'] = dataset.name + resource_ob['data'] = { + 'dataset': dataset.id + } + resource_ob['type'] = 'dataset-geno' + else: + resource_ob['name'] = dataset.name + resource_ob['data'] = { + 'dataset': dataset.id + } + resource_ob['type'] = 'dataset-probeset' + + resource_info = add_resource(resource_ob, update=False) + + return resource_info + + +def get_group_code(dataset): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT InbredSetCode FROM InbredSet WHERE Name=%s", + (dataset.group.name,) + ) + if results := cursor.fetchone(): + return results[0] + return "" + + +def check_admin(resource_id=None): + the_url = GN_PROXY_URL + "available?resource={}&user={}".format( + resource_id, g.user_session.user_id) + try: + response = json.loads(requests.get(the_url).content)['admin'] + except: + resource_info = get_resource_info(resource_id) + response = resource_info['default_mask']['admin'] + + if type(response) is list: + if 'edit-admins' in response: + return 'edit_admins' + elif 'edit-access' in response: + return 'edit-access' + + return response + + +def check_owner(dataset=None, trait_id=None, resource_id=None): + if resource_id: + resource_info = get_resource_info(resource_id) + if g.user_session.user_id == resource_info['owner_id']: + return resource_id + else: + resource_id = get_resource_id(dataset, trait_id) + if resource_id: + resource_info = get_resource_info(resource_id) + if g.user_session.user_id == resource_info['owner_id']: + return resource_id + + return False + + +def check_owner_or_admin(dataset=None, trait_id=None, resource_id=None): + if not resource_id: + if dataset.type == "Temp": + return "not-admin" + else: + resource_id = get_resource_id(dataset, trait_id) + + try: + user_id = g.user_session.user_id.encode('utf-8') + except: + user_id = g.user_session.user_id + + if user_id in Redis.smembers("super_users"): + return "owner" + + resource_info = get_resource_info(resource_id) + if resource_info: + if user_id == resource_info['owner_id']: + return "owner" + else: + return check_admin(resource_id) + + return "not-admin" diff --git a/gn2/utility/chunks.py b/gn2/utility/chunks.py new file mode 100644 index 00000000..f6e88cbe --- /dev/null +++ b/gn2/utility/chunks.py @@ -0,0 +1,30 @@ +import math + + +def divide_into_chunks(the_list, number_chunks): + """Divides a list into approximately number_chunks smaller lists + + >>> divide_into_chunks([1, 2, 7, 3, 22, 8, 5, 22, 333], 3) + [[1, 2, 7], [3, 22, 8], [5, 22, 333]] + >>> divide_into_chunks([1, 2, 7, 3, 22, 8, 5, 22, 333], 4) + [[1, 2, 7], [3, 22, 8], [5, 22, 333]] + >>> divide_into_chunks([1, 2, 7, 3, 22, 8, 5, 22, 333], 5) + [[1, 2], [7, 3], [22, 8], [5, 22], [333]] + >>> + + """ + length = len(the_list) + + if length == 0: + return [[]] + + if length <= number_chunks: + number_chunks = length + + chunksize = int(math.ceil(length / number_chunks)) + + chunks = [] + for counter in range(0, length, chunksize): + chunks.append(the_list[counter:counter + chunksize]) + + return chunks diff --git a/gn2/utility/corestats.py b/gn2/utility/corestats.py new file mode 100644 index 00000000..da0a21db --- /dev/null +++ b/gn2/utility/corestats.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +# corestats.py (COREy STATS) +# Copyright (c) 2006-2007, Corey Goldberg (corey@goldb.org) +# +# statistical calculation class +# for processing numeric sequences +# +# license: GNU LGPL +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. + +import sys + +# ZS: Should switch to using some third party library for this; maybe scipy has an equivalent + + +class Stats: + + def __init__(self, sequence): + # sequence of numbers we will process + # convert all items to floats for numerical processing + self.sequence = [float(item) for item in sequence] + + def sum(self): + if len(self.sequence) < 1: + return None + else: + return sum(self.sequence) + + def count(self): + return len(self.sequence) + + def min(self): + if len(self.sequence) < 1: + return None + else: + return min(self.sequence) + + def max(self): + if len(self.sequence) < 1: + return None + else: + return max(self.sequence) + + def avg(self): + if len(self.sequence) < 1: + return None + else: + return sum(self.sequence) / len(self.sequence) + + def stdev(self): + if len(self.sequence) < 1: + return None + else: + avg = self.avg() + sdsq = sum([(i - avg) ** 2 for i in self.sequence]) + stdev = (sdsq / (len(self.sequence) - 1)) ** .5 + return stdev + + def percentile(self, percentile): + if len(self.sequence) < 1: + value = None + elif (percentile >= 100): + sys.stderr.write( + 'ERROR: percentile must be < 100. you supplied: %s\n' % percentile) + value = None + else: + element_idx = int(len(self.sequence) * (percentile / 100.0)) + self.sequence.sort() + value = self.sequence[element_idx] + return value + +# Sample script using this class: +# ------------------------------------------- +# #!/usr/bin/env python +# import corestats +# +# sequence = [1, 2.5, 7, 13.4, 8.0] +# stats = corestats.Stats(sequence) +# print stats.avg() +# print stats.percentile(90) +# ------------------------------------------- diff --git a/gn2/utility/corr_result_helpers.py b/gn2/utility/corr_result_helpers.py new file mode 100644 index 00000000..ea3ababf --- /dev/null +++ b/gn2/utility/corr_result_helpers.py @@ -0,0 +1,42 @@ +def normalize_values(a_values, b_values): + """ + Trim two lists of values to contain only the values they both share + + Given two lists of sample values, trim each list so that it contains + only the samples that contain a value in both lists. Also returns + the number of such samples. + + >>> normalize_values([2.3, None, None, 3.2, 4.1, 5], [3.4, 7.2, 1.3, None, 6.2, 4.1]) + ([2.3, 4.1, 5], [3.4, 6.2, 4.1], 3) + + """ + + min_length = min(len(a_values), len(b_values)) + a_new = [] + b_new = [] + for a, b in zip(a_values, b_values): + if not (a == None or b == None): + a_new.append(a) + b_new.append(b) + return a_new, b_new, len(a_new) + + +def common_keys(a_samples, b_samples): + """ + >>> a = dict(BXD1 = 9.113, BXD2 = 9.825, BXD14 = 8.985, BXD15 = 9.300) + >>> b = dict(BXD1 = 9.723, BXD3 = 9.825, BXD14 = 9.124, BXD16 = 9.300) + >>> sorted(common_keys(a, b)) + ['BXD1', 'BXD14'] + """ + return set(a_samples.keys()).intersection(set(b_samples.keys())) + + +def normalize_values_with_samples(a_samples, b_samples): + common_samples = common_keys(a_samples, b_samples) + a_new = {} + b_new = {} + for sample in common_samples: + a_new[sample] = a_samples[sample] + b_new[sample] = b_samples[sample] + + return a_new, b_new, len(a_new) diff --git a/gn2/utility/db_tools.py b/gn2/utility/db_tools.py new file mode 100644 index 00000000..07a6d9ec --- /dev/null +++ b/gn2/utility/db_tools.py @@ -0,0 +1,23 @@ +from gn2.utility.tools import get_setting +from gn2.wqflask.database import database_connection + + +def escape_(string): + with database_connection(get_setting("SQL_URI")) as conn: + return conn.escape_string(str(string)) + + +def create_in_clause(items): + """Create an in clause for mysql""" + in_clause = ', '.join("'{}'".format(x) for x in mescape(*items)) + in_clause = '( {} )'.format(in_clause) + return in_clause + + +def mescape(*items): + """Multiple escape""" + return [escape_(str(item)).decode('utf8') for item in items] + + +def escape(string_): + return escape_(string_).decode('utf8') diff --git a/gn2/utility/external.py b/gn2/utility/external.py new file mode 100644 index 00000000..805d2ffe --- /dev/null +++ b/gn2/utility/external.py @@ -0,0 +1,10 @@ +# Call external program + +import os +import sys +import subprocess + + +def shell(command): + if subprocess.call(command, shell=True) != 0: + raise Exception("ERROR: failed on " + command) diff --git a/gn2/utility/formatting.py b/gn2/utility/formatting.py new file mode 100644 index 00000000..1da3e9b7 --- /dev/null +++ b/gn2/utility/formatting.py @@ -0,0 +1,121 @@ +def numify(number, singular=None, plural=None): + """Turn a number into a word if less than 13 and optionally add a singular or plural word + + >>> numify(3) + 'three' + + >>> numify(1, 'item', 'items') + 'one item' + + >>> numify(9, 'book', 'books') + 'nine books' + + You can add capitalize to change the capitalization + >>> numify(9, 'book', 'books').capitalize() + 'Nine books' + + Or capitalize every word using title + >>> numify(9, 'book', 'books').title() + 'Nine Books' + + >>> numify(15) + '15' + + >>> numify(0) + '0' + + >>> numify(12334, 'hippopotamus', 'hippopotami') + '12,334 hippopotami' + + """ + num_repr = {0: "zero", + 1: "one", + 2: "two", + 3: "three", + 4: "four", + 5: "five", + 6: "six", + 7: "seven", + 8: "eight", + 9: "nine", + 10: "ten", + 11: "eleven", + 12: "twelve"} + + if number == 1: + word = singular + else: + word = plural + + if number in num_repr: + number = num_repr[number] + elif number > 9999: + number = commify(number) + + if word: + return "%s %s" % (number, word) + else: + return str(number) + + +def commify(n): + """Add commas to an integer n. + + See http://stackoverflow.com/questions/3909457/whats-the-easiest-way-to-add-commas-to-an-integer-in-python + But I (Sam) made some small changes based on http://www.grammarbook.com/numbers/numbers.asp + + >>> commify(1) + '1' + >>> commify(123) + '123' + >>> commify(1234) + '1234' + >>> commify(12345) + '12,345' + >>> commify(1234567890) + '1,234,567,890' + >>> commify(123.0) + '123.0' + >>> commify(1234.5) + '1234.5' + >>> commify(1234.56789) + '1234.56789' + >>> commify(123456.789) + '123,456.789' + >>> commify('%.2f' % 1234.5) + '1234.50' + >>> commify(None) + >>> + + """ + if n is None: + return None + + n = str(n) + + if len(n) <= 4: # Might as well do this early + return n + + if '.' in n: + dollars, cents = n.split('.') + else: + dollars, cents = n, None + + # Don't commify numbers less than 10000 + if len(dollars) <= 4: + return n + + r = [] + for i, c in enumerate(reversed(str(dollars))): + if i and (not (i % 3)): + r.insert(0, ',') + r.insert(0, c) + out = ''.join(r) + if cents: + out += '.' + cents + return out + + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/gn2/utility/gen_geno_ob.py b/gn2/utility/gen_geno_ob.py new file mode 100644 index 00000000..c7a1ea59 --- /dev/null +++ b/gn2/utility/gen_geno_ob.py @@ -0,0 +1,182 @@ +class genotype: + """ + Replacement for reaper.Dataset so we can remove qtlreaper use while still generating mapping output figure + """ + + def __init__(self, filename): + self.group = None + self.type = "riset" + self.prgy = [] + self.nprgy = 0 + self.mat = -1 + self.pat = 1 + self.het = 0 + self.unk = "U" + self.filler = False + self.mb_exists = False + + # ZS: This is because I'm not sure if some files switch the column that contains Mb/cM positions; might be unnecessary + self.cm_column = 2 + self.mb_column = 3 + + self.chromosomes = [] + + self.read_file(filename) + + def __iter__(self): + return iter(self.chromosomes) + + def __getitem__(self, index): + return self.chromosomes[index] + + def __len__(self): + return len(self.chromosomes) + + def read_rdata_output(self, qtl_results): + # ZS: This is necessary because R/qtl requires centimorgan marker positions, which it normally gets from the .geno file, but that doesn't exist for HET3-ITP (which only has RData), so it needs to read in the marker cM positions from the results + # ZS: Overwriting since the .geno file's contents are just placeholders + self.chromosomes = [] + + this_chr = "" # ZS: This is so it can track when the chromosome changes as it iterates through markers + chr_ob = None + for marker in qtl_results: + locus = Locus(self) + # ZS: This is really awkward but works as a temporary fix + if (str(marker['chr']) != this_chr) and this_chr != "X": + if this_chr != "": + self.chromosomes.append(chr_ob) + this_chr = str(marker['chr']) + if this_chr == "20": + this_chr = "X" + chr_ob = Chr(this_chr, self) + if 'chr' in marker: + locus.chr = str(marker['chr']) + if 'name' in marker: + locus.name = marker['name'] + if 'Mb' in marker: + locus.Mb = marker['Mb'] + if 'cM' in marker: + locus.cM = marker['cM'] + chr_ob.loci.append(locus) + + self.chromosomes.append(chr_ob) + + return self + + def read_file(self, filename): + with open(filename, 'r') as geno_file: + lines = geno_file.readlines() + + this_chr = "" # ZS: This is so it can track when the chromosome changes as it iterates through markers + chr_ob = None + for line in lines: + if line[0] == "#": + continue + elif line[0] == "@": + label = line.split(":")[0][1:] + if label == "name": + self.group = line.split(":")[1].strip() + elif label == "filler": + if line.split(":")[1].strip() == "yes": + self.filler = True + elif label == "type": + self.type = line.split(":")[1].strip() + elif label == "mat": + self.mat = line.split(":")[1].strip() + elif label == "pat": + self.pat = line.split(":")[1].strip() + elif label == "het": + self.het = line.split(":")[1].strip() + elif label == "unk": + self.unk = line.split(":")[1].strip() + else: + continue + elif line[:3] == "Chr": + header_row = line.split("\t") + if header_row[2] == "Mb": + self.mb_exists = True + self.mb_column = 2 + self.cm_column = 3 + elif header_row[3] == "Mb": + self.mb_exists = True + self.mb_column = 3 + elif header_row[2] == "cM": + self.cm_column = 2 + + if self.mb_exists: + self.prgy = header_row[4:] + else: + self.prgy = header_row[3:] + self.nprgy = len(self.prgy) + else: + if line.split("\t")[0] != this_chr: + if this_chr != "": + self.chromosomes.append(chr_ob) + this_chr = line.split("\t")[0] + chr_ob = Chr(line.split("\t")[0], self) + chr_ob.add_marker(line.split("\t")) + + self.chromosomes.append(chr_ob) + + +class Chr: + def __init__(self, name, geno_ob): + self.name = name + self.loci = [] + self.mb_exists = geno_ob.mb_exists + self.cm_column = geno_ob.cm_column + self.mb_column = geno_ob.mb_column + self.geno_ob = geno_ob + + def __iter__(self): + return iter(self.loci) + + def __getitem__(self, index): + return self.loci[index] + + def __len__(self): + return len(self.loci) + + def add_marker(self, marker_row): + self.loci.append(Locus(self.geno_ob, marker_row)) + + +class Locus: + def __init__(self, geno_ob, marker_row=None): + self.chr = None + self.name = None + self.cM = None + self.Mb = None + self.genotype = [] + if marker_row: + self.chr = marker_row[0] + self.name = marker_row[1] + try: + self.cM = float(marker_row[geno_ob.cm_column]) + except: + self.cM = float( + marker_row[geno_ob.mb_column]) if geno_ob.mb_exists else 0 + try: + self.Mb = float( + marker_row[geno_ob.mb_column]) if geno_ob.mb_exists else None + except: + self.Mb = self.cM + + geno_table = { + geno_ob.mat: -1, + geno_ob.pat: 1, + geno_ob.het: 0, + geno_ob.unk: "U" + } + + self.genotype = [] + if geno_ob.mb_exists: + start_pos = 4 + else: + start_pos = 3 + + for allele in marker_row[start_pos:]: + if allele in list(geno_table.keys()): + self.genotype.append(geno_table[allele]) + else: # ZS: Some genotype appears that isn't specified in the metadata, make it unknown + self.genotype.append("U") diff --git a/gn2/utility/genofile_parser.py b/gn2/utility/genofile_parser.py new file mode 100644 index 00000000..86d9823e --- /dev/null +++ b/gn2/utility/genofile_parser.py @@ -0,0 +1,100 @@ +# CTL analysis for GN2 +# Author / Maintainer: Danny Arends <Danny.Arends@gmail.com> + +import sys +import os +import glob +import traceback +import gzip + + +import simplejson as json + +from pprint import pformat as pf + + +class Marker: + def __init__(self): + self.name = None + self.chr = None + self.cM = None + self.Mb = None + self.genotypes = [] + + +class ConvertGenoFile: + + def __init__(self, input_file): + self.mb_exists = False + self.cm_exists = False + self.markers = [] + + self.latest_row_pos = None + self.latest_col_pos = None + + self.latest_row_value = None + self.latest_col_value = None + self.input_fh = open(input_file) + print("!!!!!!!!!!!!!!!!PARSER!!!!!!!!!!!!!!!!!!") + self.haplotype_notation = { + '@mat': "1", + '@pat': "2", + '@het': "-999", + '@unk': "-999" + } + self.configurations = {} + + def process_rows(self): + for self.latest_row_pos, row in enumerate(self.input_fh): + self.latest_row_value = row + # Take care of headers + if not row.strip(): + continue + if row.startswith('#'): + continue + if row.startswith('Chr'): + if 'Mb' in row.split(): + self.mb_exists = True + if 'cM' in row.split(): + self.cm_exists = True + skip = 2 + self.cm_exists + self.mb_exists + self.individuals = row.split()[skip:] + continue + if row.startswith('@'): + key, _separater, value = row.partition(':') + key = key.strip() + value = value.strip() + if key in self.haplotype_notation: + self.configurations[value] = self.haplotype_notation[key] + continue + if not len(self.configurations): + raise EmptyConfigurations + yield row + + def process_csv(self): + for row in self.process_rows(): + row_items = row.split("\t") + + this_marker = Marker() + this_marker.name = row_items[1] + this_marker.chr = row_items[0] + if self.cm_exists and self.mb_exists: + this_marker.cM = row_items[2] + this_marker.Mb = row_items[3] + genotypes = row_items[4:] + elif self.cm_exists: + this_marker.cM = row_items[2] + genotypes = row_items[3:] + elif self.mb_exists: + this_marker.Mb = row_items[2] + genotypes = row_items[3:] + else: + genotypes = row_items[2:] + for item_count, genotype in enumerate(genotypes): + if genotype.upper().strip() in self.configurations: + this_marker.genotypes.append( + self.configurations[genotype.upper().strip()]) + else: + print("WARNING:", genotype.upper()) + this_marker.genotypes.append("NA") + self.markers.append(this_marker.__dict__) diff --git a/gn2/utility/helper_functions.py b/gn2/utility/helper_functions.py new file mode 100644 index 00000000..fc101959 --- /dev/null +++ b/gn2/utility/helper_functions.py @@ -0,0 +1,69 @@ +from gn2.base import data_set +from gn2.base.trait import create_trait +from gn2.base.species import TheSpecies + +from gn2.utility import hmac +from gn2.utility.tools import get_setting + +from gn2.wqflask.database import database_connection + + +def get_species_dataset_trait(self, start_vars): + if "temp_trait" in list(start_vars.keys()): + if start_vars['temp_trait'] == "True": + self.dataset = data_set.create_dataset( + dataset_name="Temp", + dataset_type="Temp", + group_name=start_vars['group']) + else: + self.dataset = data_set.create_dataset(start_vars['dataset']) + else: + self.dataset = data_set.create_dataset(start_vars['dataset']) + self.species = TheSpecies(dataset=self.dataset) + self.this_trait = create_trait(dataset=self.dataset, + name=start_vars['trait_id'], + cellid=None, + get_qtl_info=True) + +def get_trait_db_obs(self, trait_db_list): + if isinstance(trait_db_list, str): + trait_db_list = trait_db_list.split(",") + + self.trait_list = [] + for trait in trait_db_list: + data, _separator, hmac_string = trait.rpartition(':') + data = data.strip() + assert hmac_string == hmac.hmac_creation(data), "Data tampering?" + trait_name, dataset_name = data.split(":")[:2] + if dataset_name == "Temp": + dataset_ob = data_set.create_dataset( + dataset_name=dataset_name, dataset_type="Temp", + group_name=trait_name.split("_")[2]) + else: + dataset_ob = data_set.create_dataset(dataset_name) + trait_ob = create_trait(dataset=dataset_ob, + name=trait_name, + cellid=None) + if trait_ob: + self.trait_list.append((trait_ob, dataset_ob)) + + +def get_species_groups(): + """Group each species into a group""" + _menu = {} + species, group_name = None, None + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT s.MenuName, i.InbredSetName FROM InbredSet i " + "INNER JOIN Species s ON s.SpeciesId = i.SpeciesId " + "ORDER BY i.SpeciesId ASC, i.Name ASC" + ) + for species, group_name in cursor.fetchall(): + if species in _menu: + if _menu.get(species): + _menu = _menu[species].append(group_name) + else: + _menu[species] = [group_name] + return [{"species": key, + "groups": value} for key, value in + list(_menu.items())] diff --git a/gn2/utility/hmac.py b/gn2/utility/hmac.py new file mode 100644 index 00000000..d7c43f73 --- /dev/null +++ b/gn2/utility/hmac.py @@ -0,0 +1,43 @@ +import hmac +import hashlib + +from flask import url_for + +from gn2.wqflask import app + + +def hmac_creation(stringy): + """Helper function to create the actual hmac""" + + secret = app.config['SECRET_HMAC_CODE'] + hmaced = hmac.new(bytearray(secret, "latin-1"), + bytearray(stringy, "utf-8"), + hashlib.sha1) + hm = hmaced.hexdigest() + # ZS: Leaving the below comment here to ask Pjotr about + # "Conventional wisdom is that you don't lose much in terms of security if you throw away up to half of the output." + # http://www.w3.org/QA/2009/07/hmac_truncation_in_xml_signatu.html + hm = hm[:20] + return hm + + +def data_hmac(stringy): + """Takes arbitrary data string and appends :hmac so we know data hasn't been tampered with""" + return stringy + ":" + hmac_creation(stringy) + + +def url_for_hmac(endpoint, **values): + """Like url_for but adds an hmac at the end to insure the url hasn't been tampered with""" + + url = url_for(endpoint, **values) + + hm = hmac_creation(url) + if '?' in url: + combiner = "&" + else: + combiner = "?" + return url + combiner + "hm=" + hm + + +app.jinja_env.globals.update(url_for_hmac=url_for_hmac, + data_hmac=data_hmac) diff --git a/gn2/utility/json/__init__.py b/gn2/utility/json/__init__.py new file mode 100644 index 00000000..b1141a34 --- /dev/null +++ b/gn2/utility/json/__init__.py @@ -0,0 +1,3 @@ +"""Local JSON utilities.""" + +from .encoders import CustomJSONEncoder diff --git a/gn2/utility/json/encoders.py b/gn2/utility/json/encoders.py new file mode 100644 index 00000000..7c5839ac --- /dev/null +++ b/gn2/utility/json/encoders.py @@ -0,0 +1,17 @@ +"""Custom JSON encoders""" +from uuid import UUID +from json import JSONEncoder + +# Do not use this `__ENCODERS__` variable outside of this module. +__ENCODERS__ = { + UUID: lambda obj: str(obj) +} + +class CustomJSONEncoder(JSONEncoder): + """Custom JSONEncoder class.""" + def default(self, obj): + """Serialise `obj` to a JSON representation.""" + obj_type = type(obj) + if obj_type in __ENCODERS__: + return __ENCODERS__[obj_type](obj) + return JSONEncoder.default(self, obj) diff --git a/gn2/utility/monads.py b/gn2/utility/monads.py new file mode 100644 index 00000000..2d708261 --- /dev/null +++ b/gn2/utility/monads.py @@ -0,0 +1,114 @@ +"""Monadic utilities + +This module is a collection of monadic utilities for use in +GeneNetwork. It includes: + +* MonadicDict - monadic version of the built-in dictionary +* MonadicDictCursor - monadic version of MySQLdb.cursors.DictCursor + that returns a MonadicDict instead of the built-in dictionary +""" + +from collections import UserDict +from functools import partial + +from MySQLdb.cursors import DictCursor +from pymonad.maybe import Just, Nothing + +class MonadicDict(UserDict): + """ + Monadic version of the built-in dictionary. + + Keys in this dictionary can be any python object, but values must + be monadic values. + + from pymonad.maybe import Just, Nothing + + Initialize by setting individual keys to monadic values. + >>> d = MonadicDict() + >>> d["foo"] = Just(1) + >>> d["bar"] = Nothing + >>> d + {'foo': 1} + + Initialize by converting a built-in dictionary object. + >>> MonadicDict({"foo": 1}) + {'foo': 1} + >>> MonadicDict({"foo": 1, "bar": None}) + {'foo': 1} + + Initialize from a built-in dictionary object with monadic values. + >>> MonadicDict({"foo": Just(1)}, convert=False) + {'foo': 1} + >>> MonadicDict({"foo": Just(1), "bar": Nothing}, convert=False) + {'foo': 1} + + Get values. For non-existent keys, Nothing is returned. Else, a + Just value is returned. + >>> d["foo"] + Just 1 + >>> d["bar"] + Nothing + + Convert MonadicDict object to a built-in dictionary object. + >>> d.data + {'foo': 1} + >>> type(d) + <class 'utility.monads.MonadicDict'> + >>> type(d.data) + <class 'dict'> + + Delete keys. Deleting non-existent keys does nothing. + >>> del d["bar"] + >>> d + {'foo': 1} + >>> del d["foo"] + >>> d + {} + """ + def __init__(self, d={}, convert=True): + """Initialize monadic dictionary. + + If convert is False, values in dictionary d must be + monadic. If convert is True, values in dictionary d are + converted to monadic values. + """ + if convert: + super().__init__({key:Just(value) for key, value in d.items() + if value is not None}) + else: + super().__init__(d) + def __getitem__(self, key): + """Get key from dictionary. + + If key exists in the dictionary, return a Just value. Else, + return Nothing. + """ + try: + return Just(self.data[key]) + except KeyError: + return Nothing + def __setitem__(self, key, value): + """Set key in dictionary. + + value must be a monadic value---either Nothing or a Just + value. If value is a Just value, set it in the dictionary. If + value is Nothing, do nothing. + """ + value.bind(partial(super().__setitem__, key)) + def __delitem__(self, key): + """Delete key from dictionary. + + If key exists in the dictionary, delete it. Else, do nothing. + """ + try: + super().__delitem__(key) + except KeyError: + pass + + +def sql_query_mdict(conn, query): + """Execute SQL query and return a generator of MonadicDict objects.""" + with conn.cursor(DictCursor) as cursor: + cursor.execute(query) + while (row := cursor.fetchone()): + yield MonadicDict(row) diff --git a/gn2/utility/pillow_utils.py b/gn2/utility/pillow_utils.py new file mode 100644 index 00000000..6e7b976d --- /dev/null +++ b/gn2/utility/pillow_utils.py @@ -0,0 +1,28 @@ +from PIL import Image, ImageColor, ImageDraw, ImageFont + +from gn2.utility.tools import TEMPDIR + +BLACK = ImageColor.getrgb("black") +WHITE = ImageColor.getrgb("white") + +# def draw_rotated_text(canvas: Image, text: str, font: ImageFont, xy: tuple, fill: ImageColor=BLACK, angle: int=-90): + + +def draw_rotated_text(canvas, text, font, xy, fill=BLACK, angle=-90): + # type: (Image, str, ImageFont, tuple, ImageColor, int) + """Utility function draw rotated text""" + tmp_img = Image.new("RGBA", font.getsize(text), color=(0, 0, 0, 0)) + draw_text = ImageDraw.Draw(tmp_img) + draw_text.text(text=text, xy=(0, 0), font=font, fill=fill) + tmp_img2 = tmp_img.rotate(angle, expand=1) + tmp_img2.save("/{0}/{1}.png".format(TEMPDIR, text), format="png") + canvas.paste(im=tmp_img2, box=tuple([int(i) for i in xy])) + +# def draw_open_polygon(canvas: Image, xy: tuple, fill: ImageColor=WHITE, outline: ImageColor=BLACK): + + +def draw_open_polygon(canvas, xy, fill=None, outline=BLACK, width=0): + # type: (Image, tuple, ImageColor, ImageColor) + draw_ctx = ImageDraw.Draw(canvas) + draw_ctx.polygon(xy, fill=fill) + draw_ctx.line(xy, fill=outline, width=width) diff --git a/gn2/utility/redis_tools.py b/gn2/utility/redis_tools.py new file mode 100644 index 00000000..b3d197a9 --- /dev/null +++ b/gn2/utility/redis_tools.py @@ -0,0 +1,285 @@ +import uuid +import simplejson as json +import datetime + +import redis # used for collections + +from gn2.utility.hmac import hmac_creation + + +def get_redis_conn(): + Redis = redis.StrictRedis(port=6379) + return Redis + + +Redis = get_redis_conn() + + +def is_redis_available(): + try: + Redis.ping() + except: + return False + return True + + +def load_json_from_redis(item_list, column_value): + if type(column_value) == str: + column_value = str.encode(column_value) + try: + return json.loads(item_list[column_value]) + except: + return None + + +def get_user_id(column_name, column_value): + user_list = Redis.hgetall("users") + key_list = [] + for key in user_list: + user_ob = json.loads(user_list[key]) + if column_name in user_ob and user_ob[column_name] == column_value: + return key + + return None + + +def get_user_by_unique_column(column_name, column_value): + item_details = None + + user_list = Redis.hgetall("users") + if column_name != "user_id": + for key in user_list: + user_ob = json.loads(user_list[key]) + if column_name in user_ob and user_ob[column_name] == column_value: + item_details = user_ob + else: + item_details = load_json_from_redis(user_list, column_value) + + return item_details + + +def set_user_attribute(user_id, column_name, column_value): + user_info = json.loads(Redis.hget("users", user_id)) + user_info[column_name] = column_value + + Redis.hset("users", user_id, json.dumps(user_info)) + + +def get_user_collections(user_id): + collections = None + collections = Redis.hget("collections", user_id) + + if collections: + return json.loads(collections) + else: + return [] + + +def save_user(user, user_id): + Redis.hset("users", user_id, json.dumps(user)) + + +def save_collections(user_id, collections_ob): + Redis.hset("collections", user_id, collections_ob) + + +def save_verification_code(user_email, code): + Redis.hset("verification_codes", code, user_email) + + +def check_verification_code(code): + email_address = None + user_details = None + email_address = Redis.hget("verification_codes", code) + + if email_address: + user_details = get_user_by_unique_column( + 'email_address', email_address) + if user_details: + return user_details + else: + return None + else: + return None + + +def get_user_groups(user_id): + # Get the groups where a user is an admin or a member and + # return lists corresponding to those two sets of groups + admin_group_ids = [] # Group IDs where user is an admin + user_group_ids = [] # Group IDs where user is a regular user + groups_list = Redis.hgetall("groups") + for group_id, group_details in groups_list.items(): + try: + _details = json.loads(group_details) + group_admins = set([this_admin if this_admin else None for this_admin in _details['admins']]) + group_members = set([this_member if this_member else None for this_member in _details['members']]) + if user_id in group_admins: + admin_group_ids.append(group_id) + elif user_id in group_members: + user_group_ids.append(group_id) + else: + continue + except: + continue + + admin_groups = [] + user_groups = [] + for the_id in admin_group_ids: + admin_groups.append(get_group_info(the_id)) + for the_id in user_group_ids: + user_groups.append(get_group_info(the_id)) + + return admin_groups, user_groups + + +def get_group_info(group_id): + group_json = Redis.hget("groups", group_id) + group_info = None + if group_json: + group_info = json.loads(group_json) + + return group_info + + +def create_group(admin_user_ids, member_user_ids=[], + group_name="Default Group Name"): + group_id = str(uuid.uuid4()) + new_group = { + "id": group_id, + "admins": admin_user_ids, + "members": member_user_ids, + "name": group_name, + "created_timestamp": datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p'), + "changed_timestamp": datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') + } + + Redis.hset("groups", group_id, json.dumps(new_group)) + + return new_group + + +def delete_group(user_id, group_id): + # ZS: If user is an admin of a group, remove it from the groups hash + group_info = get_group_info(group_id) + if user_id in group_info["admins"]: + Redis.hdel("groups", group_id) + return get_user_groups(user_id) + else: + None + + +# ZS "admins" is just to indicate whether the users should be added to +# the groups admins or regular users set +def add_users_to_group(user_id, group_id, user_emails=[], admins=False): + group_info = get_group_info(group_id) + # ZS: Just to make sure that the user is an admin for the group, + # even though they shouldn't be able to reach this point unless + # they are + if user_id in group_info["admins"]: + if admins: + group_users = set(group_info["admins"]) + else: + group_users = set(group_info["members"]) + + for email in user_emails: + user_id = get_user_id("email_address", email) + group_users.add(user_id) + + if admins: + group_info["admins"] = list(group_users) + else: + group_info["members"] = list(group_users) + + group_info["changed_timestamp"] = datetime.datetime.utcnow().strftime( + '%b %d %Y %I:%M%p') + Redis.hset("groups", group_id, json.dumps(group_info)) + return group_info + else: + return None + + +# ZS: User type is because I assume admins can remove other admins +def remove_users_from_group(user_id, + users_to_remove_ids, + group_id, + user_type="members"): + group_info = get_group_info(group_id) + + if user_id in group_info["admins"]: + users_to_remove_set = set(users_to_remove_ids) + # ZS: Make sure an admin can't remove themselves from a group, + # since I imagine we don't want groups to be able to become + # admin-less + if user_type == "admins" and user_id in users_to_remove_set: + users_to_remove_set.remove(user_id) + group_users = set(group_info[user_type]) + group_users -= users_to_remove_set + group_info[user_type] = list(group_users) + group_info["changed_timestamp"] = datetime.datetime.utcnow().strftime( + '%b %d %Y %I:%M%p') + Redis.hset("groups", group_id, json.dumps(group_info)) + + +def change_group_name(user_id, group_id, new_name): + group_info = get_group_info(group_id) + if user_id in group_info["admins"]: + group_info["name"] = new_name + Redis.hset("groups", group_id, json.dumps(group_info)) + return group_info + else: + return None + + +def get_resources(): + resource_list = Redis.hgetall("resources") + return resource_list + + +def get_resource_id(dataset, trait_id=None): + resource_id = False + if dataset.type == "Publish": + if trait_id: + resource_id = hmac_creation("{}:{}:{}".format( + 'dataset-publish', dataset.id, trait_id)) + elif dataset.type == "ProbeSet": + resource_id = hmac_creation( + "{}:{}".format('dataset-probeset', dataset.id)) + elif dataset.type == "Geno": + resource_id = hmac_creation( + "{}:{}".format('dataset-geno', dataset.id)) + + return resource_id + + +def get_resource_info(resource_id): + resource_info = Redis.hget("resources", resource_id) + if resource_info: + return json.loads(resource_info) + else: + return None + + +def add_resource(resource_info, update=True): + if 'trait' in resource_info['data']: + resource_id = hmac_creation('{}:{}:{}'.format( + str(resource_info['type']), str( + resource_info['data']['dataset']), + str(resource_info['data']['trait']))) + else: + resource_id = hmac_creation('{}:{}'.format( + str(resource_info['type']), str(resource_info['data']['dataset']))) + + if update or not Redis.hexists("resources", resource_id): + Redis.hset("resources", resource_id, json.dumps(resource_info)) + + return resource_info + + +def add_access_mask(resource_id, group_id, access_mask): + the_resource = get_resource_info(resource_id) + the_resource['group_masks'][group_id] = access_mask + + Redis.hset("resources", resource_id, json.dumps(the_resource)) + + return the_resource diff --git a/gn2/utility/startup_config.py b/gn2/utility/startup_config.py new file mode 100644 index 00000000..4f9c392a --- /dev/null +++ b/gn2/utility/startup_config.py @@ -0,0 +1,41 @@ + +from gn2.wqflask import app + +from gn2.utility.tools import WEBSERVER_MODE +from gn2.utility.tools import show_settings +from gn2.utility.tools import get_setting_int +from gn2.utility.tools import get_setting +from gn2.utility.tools import get_setting_bool + + +BLUE = '\033[94m' +GREEN = '\033[92m' +BOLD = '\033[1m' +ENDC = '\033[0m' + + +def app_config(): + app.config['SESSION_TYPE'] = app.config.get('SESSION_TYPE', 'filesystem') + if not app.config.get('SECRET_KEY'): + import os + app.config['SECRET_KEY'] = str(os.urandom(24)) + mode = WEBSERVER_MODE + if mode in ["DEV", "DEBUG"]: + app.config['TEMPLATES_AUTO_RELOAD'] = True + if mode == "DEBUG": + app.debug = True + + print("==========================================") + show_settings() + + port = get_setting_int("SERVER_PORT") + + if get_setting_bool("USE_GN_SERVER"): + print(f"GN2 API server URL is [{BLUE}GN_SERVER_URL{ENDC}]") + import requests + page = requests.get(get_setting("GN_SERVER_URL")) + if page.status_code != 200: + raise Exception("API server not found!") + print(f"GN2 is running. Visit {BLUE}" + f"[http://localhost:{str(port)}/{ENDC}]" + f"({get_setting('WEBSERVER_URL')})") diff --git a/gn2/utility/svg.py b/gn2/utility/svg.py new file mode 100644 index 00000000..912cd04c --- /dev/null +++ b/gn2/utility/svg.py @@ -0,0 +1,1179 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010) +# at rwilliams@uthsc.edu and xzhou15@uthsc.edu +# +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) +# +# Created by GeneNetwork Core Team 2010/08/10 +# +# Last updated by GeneNetwork Core Team 2010/10/20 + +#!/usr/bin/env python +# Copyright (c) 2002, Fedor Baart & Hans de Wit (Stichting Farmaceutische Kengetallen) +# All rights reserved. +## +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +## +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +## +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +## +# Neither the name of the Stichting Farmaceutische Kengetallen nor the names of +# its contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +## +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Thanks to Gerald Rosennfellner for his help and useful comments. + +import sys +import exceptions +__doc__ = """Use SVGdraw to generate your SVGdrawings. + +SVGdraw uses an object model drawing and a method toXML to create SVG graphics +by using easy to use classes and methods usualy you start by creating a drawing eg + + d=drawing() + # then you create a SVG root element + s=svg() + # then you add some elements eg a circle and add it to the svg root element + c=circle() + # you can supply attributes by using named arguments. + c=circle(fill='red',stroke='blue') + # or by updating the attributes attribute: + c.attributes['stroke-width']=1 + s.addElement(c) + # then you add the svg root element to the drawing + d.setSVG(s) + # and finaly you xmlify the drawing + d.toXml() + + +this results in the svg source of the drawing, which consists of a circle +on a white background. Its as easy as that;) +This module was created using the SVG specification of www.w3c.org and the +O'Reilly (www.oreilly.com) python books as information sources. A svg viewer +is available from www.adobe.com""" + +__version__ = "1.0" + +# there are two possibilities to generate svg: +# via a dom implementation and directly using <element>text</element> strings +# the latter is way faster (and shorter in coding) +# the former is only used in debugging svg programs +# maybe it will be removed alltogether after a while +# with the following variable you indicate whether to use the dom implementation +# Note that PyXML is required for using the dom implementation. +# It is also possible to use the standard minidom. But I didn't try that one. +# Anyway the text based approach is about 60 times faster than using the full dom implementation. +use_dom_implementation = 0 + + +if use_dom_implementation != 0: + try: + from xml.dom import implementation + from xml.dom.ext import PrettyPrint + except: + raise exceptions.ImportError( + "PyXML is required for using the dom implementation") +# The implementation is used for the creating the XML document. +# The prettyprint module is used for converting the xml document object to a xml file + +sys.setrecursionlimit = 50 +# The recursion limit is set conservative so mistakes like s=svg() s.addElement(s) +# won't eat up too much processor time. + +# the following code is pasted form xml.sax.saxutils +# it makes it possible to run the code without the xml sax package installed +# To make it possible to have <rubbish> in your text elements, it is necessary to escape the texts + + +def _escape(data, entities={}): + """Escape &, <, and > in a string of data. + + You can escape other strings of data by passing a dictionary as + the optional entities parameter. The keys and values must all be + strings; each key will be replaced with its corresponding value. + """ + # data = data.replace("&", "&") + data = data.replace("<", "<") + data = data.replace(">", ">") + for chars, entity in list(entities.items()): + data = data.replace(chars, entity) + return data + + +def _quoteattr(data, entities={}): + """Escape and quote an attribute value. + + Escape &, <, and > in a string of data, then quote it for use as + an attribute value. The \" character will be escaped as well, if + necessary. + + You can escape other strings of data by passing a dictionary as + the optional entities parameter. The keys and values must all be + strings; each key will be replaced with its corresponding value. + """ + data = _escape(data, entities) + if '"' in data: + if "'" in data: + data = '"%s"' % data.replace('"', """) + else: + data = "'%s'" % data + else: + data = '"%s"' % data + return data + + +def _xypointlist(a): + """formats a list of xy pairs""" + s = '' + for e in a: # this could be done more elegant + s += str(e)[1:-1] + ' ' + return s + + +def _viewboxlist(a): + """formats a tuple""" + s = '' + for e in a: + s += str(e) + ' ' + return s + + +def _pointlist(a): + """formats a list of numbers""" + return str(a)[1:-1] + + +class pathdata: + """class used to create a pathdata object which can be used for a path. + although most methods are pretty straightforward it might be useful to look at the SVG specification.""" + # I didn't test the methods below. + + def __init__(self, x=None, y=None): + self.path = [] + if x is not None and y is not None: + self.path.append('M ' + str(x) + ' ' + str(y)) + + def closepath(self): + """ends the path""" + self.path.append('z') + + def move(self, x, y): + """move to absolute""" + self.path.append('M ' + str(x) + ' ' + str(y)) + + def relmove(self, x, y): + """move to relative""" + self.path.append('m ' + str(x) + ' ' + str(y)) + + def line(self, x, y): + """line to absolute""" + self.path.append('L ' + str(x) + ' ' + str(y)) + + def relline(self, x, y): + """line to relative""" + self.path.append('l ' + str(x) + ' ' + str(y)) + + def hline(self, x): + """horizontal line to absolute""" + self.path.append('H' + str(x)) + + def relhline(self, x): + """horizontal line to relative""" + self.path.append('h' + str(x)) + + def vline(self, y): + """verical line to absolute""" + self.path.append('V' + str(y)) + + def relvline(self, y): + """vertical line to relative""" + self.path.append('v' + str(y)) + + def bezier(self, x1, y1, x2, y2, x, y): + """bezier with xy1 and xy2 to xy absolut""" + self.path.append('C' + str(x1) + ',' + str(y1) + ' ' + str(x2) + + ',' + str(y2) + ' ' + str(x) + ',' + str(y)) + + def relbezier(self, x1, y1, x2, y2, x, y): + """bezier with xy1 and xy2 to xy relative""" + self.path.append('c' + str(x1) + ',' + str(y1) + ' ' + str(x2) + + ',' + str(y2) + ' ' + str(x) + ',' + str(y)) + + def smbezier(self, x2, y2, x, y): + """smooth bezier with xy2 to xy absolut""" + self.path.append('S' + str(x2) + ',' + str(y2) + \ + ' ' + str(x) + ',' + str(y)) + + def relsmbezier(self, x2, y2, x, y): + """smooth bezier with xy2 to xy relative""" + self.path.append('s' + str(x2) + ',' + str(y2) + \ + ' ' + str(x) + ',' + str(y)) + + def qbezier(self, x1, y1, x, y): + """quadratic bezier with xy1 to xy absolut""" + self.path.append('Q' + str(x1) + ',' + str(y1) + \ + ' ' + str(x) + ',' + str(y)) + + def relqbezier(self, x1, y1, x, y): + """quadratic bezier with xy1 to xy relative""" + self.path.append('q' + str(x1) + ',' + str(y1) + \ + ' ' + str(x) + ',' + str(y)) + + def smqbezier(self, x, y): + """smooth quadratic bezier to xy absolut""" + self.path.append('T' + str(x) + ',' + str(y)) + + def relsmqbezier(self, x, y): + """smooth quadratic bezier to xy relative""" + self.path.append('t' + str(x) + ',' + str(y)) + + def ellarc(self, rx, ry, xrot, laf, sf, x, y): + """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag to xy absolut""" + self.path.append('A' + str(rx) + ',' + str(ry) + ' ' + str(xrot) + + ' ' + str(laf) + ' ' + str(sf) + ' ' + str(x) + ' ' + str(y)) + + def relellarc(self, rx, ry, xrot, laf, sf, x, y): + """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag to xy relative""" + self.path.append('a' + str(rx) + ',' + str(ry) + ' ' + str(xrot) + + ' ' + str(laf) + ' ' + str(sf) + ' ' + str(x) + ' ' + str(y)) + + def __repr__(self): + return ' '.join(self.path) + + +class SVGelement: + """SVGelement(type,attributes,elements,text,namespace,**args) + Creates a arbitrary svg element and is intended to be subclassed not used on its own. + This element is the base of every svg element it defines a class which resembles + a xml-element. The main advantage of this kind of implementation is that you don't + have to create a toXML method for every different graph object. Every element + consists of a type, attribute, optional subelements, optional text and an optional + namespace. Note the elements==None, if elements = None:self.elements=[] construction. + This is done because if you default to elements=[] every object has a reference + to the same empty list.""" + + def __init__(self, type='', attributes=None, elements=None, text='', namespace='', cdata=None, **args): + self.type = type + if attributes == None: + self.attributes = {} + else: + self.attributes = attributes + if elements == None: + self.elements = [] + else: + self.elements = elements + self.text = text + self.namespace = namespace + self.cdata = cdata + for arg in list(args.keys()): + arg2 = arg.replace("__", ":") + arg2 = arg2.replace("_", "-") + self.attributes[arg2] = args[arg] + + def addElement(self, SVGelement): + """adds an element to a SVGelement + + SVGelement.addElement(SVGelement) + """ + self.elements.append(SVGelement) + + def toXml(self, level, f): + f.write('\t' * level) + f.write('<' + self.type) + for attkey in list(self.attributes.keys()): + f.write(' ' + _escape(str(attkey)) + '=' + + _quoteattr(str(self.attributes[attkey]))) + if self.namespace: + f.write(' xmlns="' + _escape(str(self.namespace)) + + '" xmlns:xlink="http://www.w3.org/1999/xlink"') + if self.elements or self.text or self.cdata: + f.write('>') + if self.elements: + f.write('\n') + for element in self.elements: + element.toXml(level + 1, f) + if self.cdata: + f.write('\n' + '\t' * (level + 1) + '<![CDATA[') + for line in self.cdata.splitlines(): + f.write('\n' + '\t' * (level + 2) + line) + f.write('\n' + '\t' * (level + 1) + ']]>\n') + if self.text: + if isinstance(self.text, type('')): # If the text is only text + f.write(_escape(str(self.text))) + else: # If the text is a spannedtext class + f.write(str(self.text)) + if self.elements: + f.write('\t' * level + '</' + self.type + '>\n') + elif self.text: + f.write('</' + self.type + '>\n') + elif self.cdata: + f.write('\t' * level + '</' + self.type + '>\n') + else: + f.write('/>\n') + + +class tspan(SVGelement): + """ts=tspan(text='',**args) + + a tspan element can be used for applying formatting to a textsection + usage: + ts=tspan('this text is bold') + ts.attributes['font-weight']='bold' + st=spannedtext() + st.addtspan(ts) + t=text(3,5,st) + """ + + def __init__(self, text=None, **args): + SVGelement.__init__(self, 'tspan', **args) + if self.text != None: + self.text = text + + def __repr__(self): + s = "<tspan" + for key, value in list(self.attributes.items()): + s += ' %s="%s"' % (key, value) + s += '>' + s += self.text + s += '</tspan>' + return s + + +class tref(SVGelement): + """tr=tref(link='',**args) + + a tref element can be used for referencing text by a link to its id. + usage: + tr=tref('#linktotext') + st=spannedtext() + st.addtref(tr) + t=text(3,5,st) + """ + + def __init__(self, link, **args): + SVGelement.__init__(self, 'tref', {'xlink:href': link}, **args) + + def __repr__(self): + s = "<tref" + + for key, value in list(self.attributes.items()): + s += ' %s="%s"' % (key, value) + s += '/>' + return s + + +class spannedtext: + """st=spannedtext(textlist=[]) + + a spannedtext can be used for text which consists of text, tspan's and tref's + You can use it to add to a text element or path element. Don't add it directly + to a svg or a group element. + usage: + + ts=tspan('this text is bold') + ts.attributes['font-weight']='bold' + tr=tref('#linktotext') + tr.attributes['fill']='red' + st=spannedtext() + st.addtspan(ts) + st.addtref(tr) + st.addtext('This text is not bold') + t=text(3,5,st) + """ + + def __init__(self, textlist=None): + if textlist == None: + self.textlist = [] + else: + self.textlist = textlist + + def addtext(self, text=''): + self.textlist.append(text) + + def addtspan(self, tspan): + self.textlist.append(tspan) + + def addtref(self, tref): + self.textlist.append(tref) + + def __repr__(self): + s = "" + for element in self.textlist: + s += str(element) + return s + + +class rect(SVGelement): + """r=rect(width,height,x,y,fill,stroke,stroke_width,**args) + + a rectangle is defined by a width and height and a xy pair + """ + + def __init__(self, x=None, y=None, width=None, height=None, fill=None, stroke=None, stroke_width=None, **args): + if width == None or height == None: + raise ValueError('both height and width are required') + + SVGelement.__init__( + self, 'rect', {'width': width, 'height': height}, **args) + if x != None: + self.attributes['x'] = x + if y != None: + self.attributes['y'] = y + if fill != None: + self.attributes['fill'] = fill + if stroke != None: + self.attributes['stroke'] = stroke + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + + +class ellipse(SVGelement): + """e=ellipse(rx,ry,x,y,fill,stroke,stroke_width,**args) + + an ellipse is defined as a center and a x and y radius. + """ + + def __init__(self, cx=None, cy=None, rx=None, ry=None, fill=None, stroke=None, stroke_width=None, **args): + if rx == None or ry == None: + raise ValueError('both rx and ry are required') + + SVGelement.__init__(self, 'ellipse', {'rx': rx, 'ry': ry}, **args) + if cx != None: + self.attributes['cx'] = cx + if cy != None: + self.attributes['cy'] = cy + if fill != None: + self.attributes['fill'] = fill + if stroke != None: + self.attributes['stroke'] = stroke + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + + +class circle(SVGelement): + """c=circle(x,y,radius,fill,stroke,stroke_width,**args) + + The circle creates an element using a x, y and radius values eg + """ + + def __init__(self, cx=None, cy=None, r=None, fill=None, stroke=None, stroke_width=None, **args): + if r == None: + raise ValueError('r is required') + SVGelement.__init__(self, 'circle', {'r': r}, **args) + if cx != None: + self.attributes['cx'] = cx + if cy != None: + self.attributes['cy'] = cy + if fill != None: + self.attributes['fill'] = fill + if stroke != None: + self.attributes['stroke'] = stroke + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + + +class point(circle): + """p=point(x,y,color) + + A point is defined as a circle with a size 1 radius. It may be more efficient to use a + very small rectangle if you use many points because a circle is difficult to render. + """ + + def __init__(self, x, y, fill='black', **args): + circle.__init__(self, x, y, 1, fill, **args) + + +class line(SVGelement): + """l=line(x1,y1,x2,y2,stroke,stroke_width,**args) + + A line is defined by a begin x,y pair and an end x,y pair + """ + + def __init__(self, x1=None, y1=None, x2=None, y2=None, stroke=None, stroke_width=None, **args): + SVGelement.__init__(self, 'line', **args) + if x1 != None: + self.attributes['x1'] = x1 + if y1 != None: + self.attributes['y1'] = y1 + if x2 != None: + self.attributes['x2'] = x2 + if y2 != None: + self.attributes['y2'] = y2 + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + if stroke != None: + self.attributes['stroke'] = stroke + + +class polyline(SVGelement): + """pl=polyline([[x1,y1],[x2,y2],...],fill,stroke,stroke_width,**args) + + a polyline is defined by a list of xy pairs + """ + + def __init__(self, points, fill=None, stroke=None, stroke_width=None, **args): + SVGelement.__init__(self, 'polyline', { + 'points': _xypointlist(points)}, **args) + if fill != None: + self.attributes['fill'] = fill + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + if stroke != None: + self.attributes['stroke'] = stroke + + +class polygon(SVGelement): + """pl=polyline([[x1,y1],[x2,y2],...],fill,stroke,stroke_width,**args) + + a polygon is defined by a list of xy pairs + """ + + def __init__(self, points, fill=None, stroke=None, stroke_width=None, **args): + SVGelement.__init__( + self, 'polygon', {'points': _xypointlist(points)}, **args) + if fill != None: + self.attributes['fill'] = fill + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + if stroke != None: + self.attributes['stroke'] = stroke + + +class path(SVGelement): + """p=path(path,fill,stroke,stroke_width,**args) + + a path is defined by a path object and optional width, stroke and fillcolor + """ + + def __init__(self, pathdata, fill=None, stroke=None, stroke_width=None, id=None, **args): + SVGelement.__init__(self, 'path', {'d': str(pathdata)}, **args) + if stroke != None: + self.attributes['stroke'] = stroke + if fill != None: + self.attributes['fill'] = fill + if stroke_width != None: + self.attributes['stroke-width'] = stroke_width + if id != None: + self.attributes['id'] = id + + +class text(SVGelement): + """t=text(x,y,text,font_size,font_family,**args) + + a text element can bge used for displaying text on the screen + """ + + def __init__(self, x=None, y=None, text=None, font_size=None, font_family=None, text_anchor=None, **args): + SVGelement.__init__(self, 'text', **args) + if x != None: + self.attributes['x'] = x + if y != None: + self.attributes['y'] = y + if font_size != None: + self.attributes['font-size'] = font_size + if font_family != None: + self.attributes['font-family'] = font_family + if text != None: + self.text = text + if text_anchor != None: + self.attributes['text-anchor'] = text_anchor + + +class textpath(SVGelement): + """tp=textpath(text,link,**args) + + a textpath places a text on a path which is referenced by a link. + """ + + def __init__(self, link, text=None, **args): + SVGelement.__init__(self, 'textPath', {'xlink:href': link}, **args) + if text != None: + self.text = text + + +class pattern(SVGelement): + """p=pattern(x,y,width,height,patternUnits,**args) + + A pattern is used to fill or stroke an object using a pre-defined + graphic object which can be replicated ("tiled") at fixed intervals + in x and y to cover the areas to be painted. + """ + + def __init__(self, x=None, y=None, width=None, height=None, patternUnits=None, **args): + SVGelement.__init__(self, 'pattern', **args) + if x != None: + self.attributes['x'] = x + if y != None: + self.attributes['y'] = y + if width != None: + self.attributes['width'] = width + if height != None: + self.attributes['height'] = height + if patternUnits != None: + self.attributes['patternUnits'] = patternUnits + + +class title(SVGelement): + """t=title(text,**args) + + a title is a text element. The text is displayed in the title bar + add at least one to the root svg element + """ + + def __init__(self, text=None, **args): + SVGelement.__init__(self, 'title', **args) + if text != None: + self.text = text + + +class description(SVGelement): + """d=description(text,**args) + + a description can be added to any element and is used for a tooltip + Add this element before adding other elements. + """ + + def __init__(self, text=None, **args): + SVGelement.__init__(self, 'desc', **args) + if text != None: + self.text = text + + +class lineargradient(SVGelement): + """lg=lineargradient(x1,y1,x2,y2,id,**args) + + defines a lineargradient using two xy pairs. + stop elements van be added to define the gradient colors. + """ + + def __init__(self, x1=None, y1=None, x2=None, y2=None, id=None, **args): + SVGelement.__init__(self, 'linearGradient', **args) + if x1 != None: + self.attributes['x1'] = x1 + if y1 != None: + self.attributes['y1'] = y1 + if x2 != None: + self.attributes['x2'] = x2 + if y2 != None: + self.attributes['y2'] = y2 + if id != None: + self.attributes['id'] = id + + +class radialgradient(SVGelement): + """rg=radialgradient(cx,cy,r,fx,fy,id,**args) + + defines a radial gradient using a outer circle which are defined by a cx,cy and r and by using a focalpoint. + stop elements van be added to define the gradient colors. + """ + + def __init__(self, cx=None, cy=None, r=None, fx=None, fy=None, id=None, **args): + SVGelement.__init__(self, 'radialGradient', **args) + if cx != None: + self.attributes['cx'] = cx + if cy != None: + self.attributes['cy'] = cy + if r != None: + self.attributes['r'] = r + if fx != None: + self.attributes['fx'] = fx + if fy != None: + self.attributes['fy'] = fy + if id != None: + self.attributes['id'] = id + + +class stop(SVGelement): + """st=stop(offset,stop_color,**args) + + Puts a stop color at the specified radius + """ + + def __init__(self, offset, stop_color=None, **args): + SVGelement.__init__(self, 'stop', {'offset': offset}, **args) + if stop_color != None: + self.attributes['stop-color'] = stop_color + + +class style(SVGelement): + """st=style(type,cdata=None,**args) + + Add a CDATA element to this element for defing in line stylesheets etc.. + """ + + def __init__(self, type, cdata=None, **args): + SVGelement.__init__(self, 'style', {'type': type}, cdata=cdata, **args) + + +class image(SVGelement): + """im=image(url,width,height,x,y,**args) + + adds an image to the drawing. Supported formats are .png, .jpg and .svg. + """ + + def __init__(self, url, x=None, y=None, width=None, height=None, **args): + if width == None or height == None: + raise ValueError('both height and width are required') + SVGelement.__init__( + self, 'image', {'xlink:href': url, 'width': width, 'height': height}, **args) + if x != None: + self.attributes['x'] = x + if y != None: + self.attributes['y'] = y + + +class cursor(SVGelement): + """c=cursor(url,**args) + + defines a custom cursor for a element or a drawing + """ + + def __init__(self, url, **args): + SVGelement.__init__(self, 'cursor', {'xlink:href': url}, **args) + + +class marker(SVGelement): + """m=marker(id,viewbox,refX,refY,markerWidth,markerHeight,**args) + + defines a marker which can be used as an endpoint for a line or other pathtypes + add an element to it which should be used as a marker. + """ + + def __init__(self, id=None, viewBox=None, refx=None, refy=None, markerWidth=None, markerHeight=None, **args): + SVGelement.__init__(self, 'marker', **args) + if id != None: + self.attributes['id'] = id + if viewBox != None: + self.attributes['viewBox'] = _viewboxlist(viewBox) + if refx != None: + self.attributes['refX'] = refx + if refy != None: + self.attributes['refY'] = refy + if markerWidth != None: + self.attributes['markerWidth'] = markerWidth + if markerHeight != None: + self.attributes['markerHeight'] = markerHeight + + +class group(SVGelement): + """g=group(id,**args) + + a group is defined by an id and is used to contain elements + g.addElement(SVGelement) + """ + + def __init__(self, id=None, **args): + SVGelement.__init__(self, 'g', **args) + if id != None: + self.attributes['id'] = id + + +class symbol(SVGelement): + """sy=symbol(id,viewbox,**args) + + defines a symbol which can be used on different places in your graph using + the use element. A symbol is not rendered but you can use 'use' elements to + display it by referencing its id. + sy.addElement(SVGelement) + """ + + def __init__(self, id=None, viewBox=None, **args): + SVGelement.__init__(self, 'symbol', **args) + if id != None: + self.attributes['id'] = id + if viewBox != None: + self.attributes['viewBox'] = _viewboxlist(viewBox) + + +class defs(SVGelement): + """d=defs(**args) + + container for defining elements + """ + + def __init__(self, **args): + SVGelement.__init__(self, 'defs', **args) + + +class switch(SVGelement): + """sw=switch(**args) + + Elements added to a switch element which are "switched" by the attributes + requiredFeatures, requiredExtensions and systemLanguage. + Refer to the SVG specification for details. + """ + + def __init__(self, **args): + SVGelement.__init__(self, 'switch', **args) + + +class use(SVGelement): + """u=use(link,x,y,width,height,**args) + + references a symbol by linking to its id and its position, height and width + """ + + def __init__(self, link, x=None, y=None, width=None, height=None, **args): + SVGelement.__init__(self, 'use', {'xlink:href': link}, **args) + if x != None: + self.attributes['x'] = x + if y != None: + self.attributes['y'] = y + + if width != None: + self.attributes['width'] = width + if height != None: + self.attributes['height'] = height + + +class link(SVGelement): + """a=link(url,**args) + + a link is defined by a hyperlink. add elements which have to be linked + a.addElement(SVGelement) + """ + + def __init__(self, link='', **args): + SVGelement.__init__(self, 'a', {'xlink:href': link}, **args) + + +class view(SVGelement): + """v=view(id,**args) + + a view can be used to create a view with different attributes""" + + def __init__(self, id=None, **args): + SVGelement.__init__(self, 'view', **args) + if id != None: + self.attributes['id'] = id + + +class script(SVGelement): + """sc=script(type,type,cdata,**args) + + adds a script element which contains CDATA to the SVG drawing + + """ + + def __init__(self, type, cdata=None, **args): + SVGelement.__init__( + self, 'script', {'type': type}, cdata=cdata, **args) + + +class animate(SVGelement): + """an=animate(attribute,from,to,during,**args) + + animates an attribute. + """ + + def __init__(self, attribute, fr=None, to=None, dur=None, **args): + SVGelement.__init__( + self, 'animate', {'attributeName': attribute}, **args) + if fr != None: + self.attributes['from'] = fr + if to != None: + self.attributes['to'] = to + if dur != None: + self.attributes['dur'] = dur + + +class animateMotion(SVGelement): + """an=animateMotion(pathdata,dur,**args) + + animates a SVGelement over the given path in dur seconds + """ + + def __init__(self, pathdata, dur, **args): + SVGelement.__init__(self, 'animateMotion', **args) + if pathdata != None: + self.attributes['path'] = str(pathdata) + if dur != None: + self.attributes['dur'] = dur + + +class animateTransform(SVGelement): + """antr=animateTransform(type,from,to,dur,**args) + + transform an element from and to a value. + """ + + def __init__(self, type=None, fr=None, to=None, dur=None, **args): + SVGelement.__init__(self, 'animateTransform', { + 'attributeName': 'transform'}, **args) + # As far as I know the attributeName is always transform + if type != None: + self.attributes['type'] = type + if fr != None: + self.attributes['from'] = fr + if to != None: + self.attributes['to'] = to + if dur != None: + self.attributes['dur'] = dur + + +class animateColor(SVGelement): + """ac=animateColor(attribute,type,from,to,dur,**args) + + Animates the color of a element + """ + + def __init__(self, attribute, type=None, fr=None, to=None, dur=None, **args): + SVGelement.__init__(self, 'animateColor', { + 'attributeName': attribute}, **args) + if type != None: + self.attributes['type'] = type + if fr != None: + self.attributes['from'] = fr + if to != None: + self.attributes['to'] = to + if dur != None: + self.attributes['dur'] = dur + + +class set(SVGelement): + """st=set(attribute,to,during,**args) + + sets an attribute to a value for a + """ + + def __init__(self, attribute, to=None, dur=None, **args): + SVGelement.__init__(self, 'set', {'attributeName': attribute}, **args) + if to != None: + self.attributes['to'] = to + if dur != None: + self.attributes['dur'] = dur + + +class svg(SVGelement): + """s=svg(viewbox,width,height,**args) + + a svg or element is the root of a drawing add all elements to a svg element. + You can have different svg elements in one svg file + s.addElement(SVGelement) + + eg + d=drawing() + s=svg((0,0,100,100),'100%','100%') + c=circle(50,50,20) + s.addElement(c) + d.setSVG(s) + d.toXml() + """ + + def __init__(self, viewBox=None, width=None, height=None, **args): + SVGelement.__init__(self, 'svg', **args) + if viewBox != None: + self.attributes['viewBox'] = _viewboxlist(viewBox) + if width != None: + self.attributes['width'] = width + if height != None: + self.attributes['height'] = height + self.namespace = "http://www.w3.org/2000/svg" + + +class drawing: + """d=drawing() + + this is the actual SVG document. It needs a svg element as a root. + Use the addSVG method to set the svg to the root. Use the toXml method to write the SVG + source to the screen or to a file + d=drawing() + d.addSVG(svg) + d.toXml(optionalfilename) + """ + + def __init__(self, entity={}): + self.svg = None + self.entity = entity + + def setSVG(self, svg): + self.svg = svg + # Voeg een element toe aan de grafiek toe. + if use_dom_implementation == 0: + def toXml(self, filename='', compress=False): + import io + xml = io.StringIO() + xml.write("<?xml version='1.0' encoding='UTF-8'?>\n") + xml.write( + "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\"") + if self.entity: + xml.write(" [\n") + for item in list(self.entity.keys()): + xml.write("<!ENTITY %s \"%s\">\n" % + (item, self.entity[item])) + xml.write("]") + xml.write(">\n") + self.svg.toXml(0, xml) + if not filename: + if compress: + import gzip + f = io.StringIO() + zf = gzip.GzipFile(fileobj=f, mode='wb') + zf.write(xml.getvalue()) + zf.close() + f.seek(0) + return f.read() + else: + return xml.getvalue() + else: + if filename[-4:] == 'svgz': + import gzip + f = gzip.GzipFile(filename=filename, + mode="wb", compresslevel=9) + f.write(xml.getvalue()) + f.close() + else: + f = file(filename, 'w') + f.write(xml.getvalue()) + f.close() + + else: + def toXml(self, filename='', compress=False): + """drawing.toXml() ---->to the screen + drawing.toXml(filename)---->to the file + writes a svg drawing to the screen or to a file + compresses if filename ends with svgz or if compress is true + """ + doctype = implementation.createDocumentType( + 'svg', "-//W3C//DTD SVG 1.0//EN""", 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd ') + + global root + # root is defined global so it can be used by the appender. Its also possible to use it as an arugument but + # that is a bit messy. + root = implementation.createDocument(None, None, doctype) + # Create the xml document. + global appender + + def appender(element, elementroot): + """This recursive function appends elements to an element and sets the attributes + and type. It stops when alle elements have been appended""" + if element.namespace: + e = root.createElementNS(element.namespace, element.type) + else: + e = root.createElement(element.type) + if element.text: + textnode = root.createTextNode(element.text) + e.appendChild(textnode) + # in element.attributes is supported from python 2.2 + for attribute in list(element.attributes.keys()): + e.setAttribute(attribute, str( + element.attributes[attribute])) + if element.elements: + for el in element.elements: + e = appender(el, e) + elementroot.appendChild(e) + return elementroot + root = appender(self.svg, root) + if not filename: + import io + xml = io.StringIO() + PrettyPrint(root, xml) + if compress: + import gzip + f = io.StringIO() + zf = gzip.GzipFile(fileobj=f, mode='wb') + zf.write(xml.getvalue()) + zf.close() + f.seek(0) + return f.read() + else: + return xml.getvalue() + else: + try: + if filename[-4:] == 'svgz': + import gzip + import io + xml = io.StringIO() + PrettyPrint(root, xml) + f = gzip.GzipFile(filename=filename, + mode='wb', compresslevel=9) + f.write(xml.getvalue()) + f.close() + else: + f = open(filename, 'w') + PrettyPrint(root, f) + f.close() + except: + print(("Cannot write SVG file: " + filename)) + + def validate(self): + try: + import xml.parsers.xmlproc.xmlval + except: + raise exceptions.ImportError( + 'PyXml is required for validating SVG') + svg = self.toXml() + xv = xml.parsers.xmlproc.xmlval.XMLValidator() + try: + xv.feed(svg) + except: + raise Exception("SVG is not well formed, see messages above") + else: + print("SVG well formed") + + +if __name__ == '__main__': + + d = drawing() + s = svg((0, 0, 100, 100)) + r = rect(-100, -100, 300, 300, 'cyan') + s.addElement(r) + + t = title('SVGdraw Demo') + s.addElement(t) + g = group('animations') + e = ellipse(0, 0, 5, 2) + g.addElement(e) + c = circle(0, 0, 1, 'red') + g.addElement(c) + pd = pathdata(0, -10) + for i in range(6): + pd.relsmbezier(10, 5, 0, 10) + pd.relsmbezier(-10, 5, 0, 10) + an = animateMotion(pd, 10) + an.attributes['rotate'] = 'auto-reverse' + an.attributes['repeatCount'] = "indefinite" + g.addElement(an) + s.addElement(g) + for i in range(20, 120, 20): + u = use('#animations', i, 0) + s.addElement(u) + for i in range(0, 120, 20): + for j in range(5, 105, 10): + c = circle(i, j, 1, 'red', 'black', .5) + s.addElement(c) + d.setSVG(s) + + print((d.toXml())) diff --git a/gn2/utility/temp_data.py b/gn2/utility/temp_data.py new file mode 100644 index 00000000..07c5a318 --- /dev/null +++ b/gn2/utility/temp_data.py @@ -0,0 +1,25 @@ +from redis import Redis + +import simplejson as json + + +class TempData: + + def __init__(self, temp_uuid): + self.temp_uuid = temp_uuid + self.redis = Redis() + self.key = "tempdata:{}".format(self.temp_uuid) + + def store(self, field, value): + self.redis.hset(self.key, field, value) + self.redis.expire(self.key, 60 * 15) # Expire in 15 minutes + + def get_all(self): + return self.redis.hgetall(self.key) + + +if __name__ == "__main__": + redis = Redis() + for key in list(redis.keys()): + for field in redis.hkeys(key): + print("{}.{}={}".format(key, field, redis.hget(key, field))) diff --git a/gn2/utility/tools.py b/gn2/utility/tools.py new file mode 100644 index 00000000..7adf8e8f --- /dev/null +++ b/gn2/utility/tools.py @@ -0,0 +1,357 @@ +# Tools/paths finder resolves external paths from settings and/or environment +# variables + +import os +import sys +import json +import socket +from pathlib import Path + +from gn2.wqflask import app + +# Use the standard logger here to avoid a circular dependency +import logging +logger = logging.getLogger(__name__) + + +def app_set(command_id, value): + """Set application wide value""" + app.config.setdefault(command_id, value) + return value + + +def get_setting(command_id, guess=None): + """Resolve a setting from the environment or the global settings in + app.config, with valid_path is a function checking whether the + path points to an expected directory and returns the full path to + the binary command + + guess = os.environ.get('HOME')+'/pylmm' + valid_path(get_setting('PYLMM_PATH',guess)) + + first tries the environment variable in +id+, next gets the Flask + app setting for the same +id+ and finally does an educated + +guess+. + + In all, the environment overrides the others, next is the flask + setting, then the guess. A valid path to the binary command is + returned. If none is resolved an exception is thrown. + + Note that we do not use the system path. This is on purpose + because it will mess up controlled (reproducible) deployment. The + proper way is to either use the GNU Guix defaults as listed in + etc/default_settings.py or override them yourself by creating a + different settings.py file (or setting the environment). + + """ + def value(command): + if command: + # sys.stderr.write("Found "+command+"\n") + app_set(command_id, command) + return command + else: + return app.config.get(command_id) + + # ---- Check whether environment exists + # print("Looking for "+command_id+"\n") + command = value(os.environ.get(command_id)) + if command is None or command == "": + # ---- Check whether setting exists in app + command = value(app.config.get(command_id)) + if command is None: + command = value(guess) + if command is None or command == "": + # print command + raise Exception( + command_id + ' setting unknown or faulty (update default_settings.py?).') + # print("Set "+command_id+"="+str(command)) + return command + + +def get_setting_bool(id): + v = get_setting(id) + if v not in [0, False, 'False', 'FALSE', None]: + return True + return False + + +def get_setting_int(id): + v = get_setting(id) + if isinstance(v, str): + return int(v) + if v is None: + return 0 + return v + + +def valid_bin(bin): + if os.path.islink(bin) or valid_file(bin): + return bin + return None + + +def valid_file(fn): + if os.path.isfile(fn): + return fn + return None + + +def valid_path(dir): + if os.path.isdir(dir): + return dir + return None + + +def js_path(module=None): + """ + Find the JS module in the two paths + """ + try_gn = get_setting("JS_GN_PATH") + "/" + module + if valid_path(try_gn): + return try_gn + try_guix = get_setting("JS_GUIX_PATH") + "/" + module + if valid_path(try_guix): + return try_guix + raise "No JS path found for " + module + \ + " (if not in Guix check JS_GN_PATH)" + + +def reaper_command(guess=None): + return get_setting("REAPER_COMMAND", guess) + + +def gemma_command(guess=None): + return assert_bin(get_setting("GEMMA_COMMAND", guess)) + + +def gemma_wrapper_command(guess=None): + return assert_bin(get_setting("GEMMA_WRAPPER_COMMAND", guess)) + + +def plink_command(guess=None): + return assert_bin(get_setting("PLINK_COMMAND", guess)) + + +def flat_file_exists(subdir): + base = get_setting("GENENETWORK_FILES") + return valid_path(base + "/" + subdir) + + +def flat_files(subdir=None): + base = get_setting("GENENETWORK_FILES") + if subdir: + return assert_dir(base + "/" + subdir) + return assert_dir(base) + + +def assert_bin(fn): + if not valid_bin(fn): + raise Exception("ERROR: can not find binary " + fn) + return fn + + +def assert_dir(the_dir): + if not valid_path(the_dir): + raise FileNotFoundError(f"ERROR: can not find directory '{the_dir}'") + return the_dir + + +def assert_writable_dir(dir): + try: + fn = os.path.join(dir, "test.txt") + fh = open(fn, 'w') + fh.write("I am writing this text to the file\n") + fh.close() + os.remove(fn) + except IOError: + raise Exception('Unable to write test.txt to directory ' + dir) + return dir + + +def assert_file(fn): + if not valid_file(fn): + raise FileNotFoundError(f"Unable to find file '{fn}'") + return fn + + +def mk_dir(dir): + if not valid_path(dir): + os.makedirs(dir) + return assert_dir(dir) + + +def locate(name, subdir=None): + """ + Locate a static flat file in the GENENETWORK_FILES environment. + + This function throws an error when the file is not found. + """ + base = get_setting("GENENETWORK_FILES") + if subdir: + base = base + "/" + subdir + if valid_path(base): + lookfor = base + "/" + name + if valid_file(lookfor): + return lookfor + else: + raise Exception("Can not locate " + lookfor) + if subdir: + sys.stderr.write(subdir) + raise Exception("Can not locate " + name + " in " + base) + + +def locate_phewas(name, subdir=None): + return locate(name, '/phewas/' + subdir) + + +def locate_ignore_error(name, subdir=None): + """ + Locate a static flat file in the GENENETWORK_FILES environment. + + This function does not throw an error when the file is not found + but returns None. + """ + base = get_setting("GENENETWORK_FILES") + if subdir: + base = base + "/" + subdir + if valid_path(base): + lookfor = base + "/" + name + if valid_file(lookfor): + return lookfor + return None + + +def tempdir(): + """ + Get UNIX TMPDIR by default + """ + return valid_path(get_setting("TMPDIR", "/tmp")) + + +BLUE = '\033[94m' +GREEN = '\033[92m' +BOLD = '\033[1m' +ENDC = '\033[0m' + + +def show_settings(): + from gn2.utility.tools import LOG_LEVEL + + print(("Set global log level to " + BLUE + LOG_LEVEL + ENDC), + file=sys.stderr) + log_level = getattr(logging, LOG_LEVEL.upper()) + logging.basicConfig(level=log_level) + + logger.info(BLUE + "Mr. Mojo Risin 2" + ENDC) + keylist = list(app.config.keys()) + print("runserver.py: ****** Webserver configuration - k,v pairs from app.config ******", + file=sys.stderr) + keylist.sort() + for k in keylist: + try: + print(("%s: %s%s%s%s" % (k, BLUE, BOLD, get_setting(k), ENDC)), + file=sys.stderr) + except: + print(("%s: %s%s%s%s" % (k, GREEN, BOLD, app.config[k], ENDC)), + file=sys.stderr) + +def gn_version_repo_info(root_dir): + """retrieve the branch name and abbreviated commit hash.""" + try: + from git import Repo + repo = Repo(root_dir) + return f"{repo.head.ref.name}-{repo.head.commit.hexsha[0:9]}" + except: + return "" + +def gn_version(): + """Compute and return the version of the application.""" + hostname = socket.gethostname() + basedir = Path(__file__).absolute().parent.parent.parent + with open(Path(basedir, "etc", "VERSION"), encoding="utf8") as version_file: + version_contents = version_file.read().strip() + base_version = f"{hostname}:{basedir.name}:{version_contents}" + repo_info = gn_version_repo_info(basedir) + return f"{base_version}-{repo_info}" if bool(repo_info) else base_version + +# Cached values +GN_VERSION = gn_version() +HOME = get_setting('HOME') +SERVER_PORT = get_setting('SERVER_PORT') +WEBSERVER_MODE = get_setting('WEBSERVER_MODE') +GN2_BASE_URL = get_setting('GN2_BASE_URL') +GN2_BRANCH_URL = get_setting('GN2_BRANCH_URL') +GN_SERVER_URL = get_setting('GN_SERVER_URL') +AUTH_SERVER_URL = get_setting('AUTH_SERVER_URL') +GN_PROXY_URL = get_setting('GN_PROXY_URL') +GN3_LOCAL_URL = get_setting('GN3_LOCAL_URL') +SERVER_PORT = get_setting_int('SERVER_PORT') +SQL_URI = get_setting('SQL_URI') +LOG_LEVEL = get_setting('LOG_LEVEL') +LOG_LEVEL_DEBUG = get_setting_int('LOG_LEVEL_DEBUG') +LOG_SQL = get_setting_bool('LOG_SQL') +LOG_SQL_ALCHEMY = get_setting_bool('LOG_SQL_ALCHEMY') +LOG_BENCH = get_setting_bool('LOG_BENCH') +LOG_FORMAT = "%(message)s" # not yet in use +USE_REDIS = get_setting_bool('USE_REDIS') +REDIS_URL = get_setting('REDIS_URL') +USE_GN_SERVER = get_setting_bool('USE_GN_SERVER') + +GENENETWORK_FILES = get_setting('GENENETWORK_FILES') +JS_GUIX_PATH = get_setting('JS_GUIX_PATH') +assert_dir(JS_GUIX_PATH) +JS_GN_PATH = get_setting('JS_GN_PATH') +# assert_dir(JS_GN_PATH) + +GITHUB_CLIENT_ID = get_setting('GITHUB_CLIENT_ID') +GITHUB_CLIENT_SECRET = get_setting('GITHUB_CLIENT_SECRET') +GITHUB_AUTH_URL = "" +if GITHUB_CLIENT_ID != 'UNKNOWN' and GITHUB_CLIENT_SECRET: + GITHUB_AUTH_URL = "https://github.com/login/oauth/authorize?client_id=" + \ + GITHUB_CLIENT_ID + "&client_secret=" + GITHUB_CLIENT_SECRET + GITHUB_API_URL = get_setting('GITHUB_API_URL') + +ORCID_CLIENT_ID = get_setting('ORCID_CLIENT_ID') +ORCID_CLIENT_SECRET = get_setting('ORCID_CLIENT_SECRET') +ORCID_AUTH_URL = None +if ORCID_CLIENT_ID != 'UNKNOWN' and ORCID_CLIENT_SECRET: + ORCID_AUTH_URL = "https://orcid.org/oauth/authorize?response_type=code&scope=/authenticate&show_login=true&client_id=" + \ + ORCID_CLIENT_ID + "&client_secret=" + ORCID_CLIENT_SECRET + \ + "&redirect_uri=" + GN2_BRANCH_URL + "n/login/orcid_oauth2" + ORCID_TOKEN_URL = get_setting('ORCID_TOKEN_URL') + + +SMTP_CONNECT = get_setting('SMTP_CONNECT') +SMTP_USERNAME = get_setting('SMTP_USERNAME') +SMTP_PASSWORD = get_setting('SMTP_PASSWORD') + +REAPER_COMMAND = app_set("REAPER_COMMAND", reaper_command()) +GEMMA_COMMAND = app_set("GEMMA_COMMAND", gemma_command()) +assert(GEMMA_COMMAND is not None) +PLINK_COMMAND = app_set("PLINK_COMMAND", plink_command()) +GEMMA_WRAPPER_COMMAND = gemma_wrapper_command() +TEMPDIR = tempdir() # defaults to UNIX TMPDIR +assert_dir(TEMPDIR) + +# ---- Handle specific JS modules +JS_GUIX_PATH = get_setting("JS_GUIX_PATH") +assert_dir(JS_GUIX_PATH) +assert_dir(JS_GUIX_PATH + '/cytoscape-panzoom') + +CSS_PATH = JS_GUIX_PATH # The CSS is bundled together with the JS +# assert_dir(JS_PATH) + +JS_TWITTER_POST_FETCHER_PATH = get_setting( + "JS_TWITTER_POST_FETCHER_PATH", js_path("javascript-twitter-post-fetcher")) +assert_dir(JS_TWITTER_POST_FETCHER_PATH) +assert_file(JS_TWITTER_POST_FETCHER_PATH + "/js/twitterFetcher_min.js") + +JS_CYTOSCAPE_PATH = get_setting("JS_CYTOSCAPE_PATH", js_path("cytoscape")) +assert_dir(JS_CYTOSCAPE_PATH) +assert_file(JS_CYTOSCAPE_PATH + '/cytoscape.min.js') + +# assert_file(PHEWAS_FILES+"/auwerx/PheWAS_pval_EMMA_norm.RData") + +AUTH_SERVER_URL = get_setting("AUTH_SERVER_URL") +OAUTH2_CLIENT_ID = get_setting('OAUTH2_CLIENT_ID') +OAUTH2_CLIENT_SECRET = get_setting('OAUTH2_CLIENT_SECRET') diff --git a/gn2/utility/type_checking.py b/gn2/utility/type_checking.py new file mode 100644 index 00000000..5e98f4ae --- /dev/null +++ b/gn2/utility/type_checking.py @@ -0,0 +1,47 @@ +# Type checking functions + +def is_float(value): + try: + float(value) + return True + except: + return False + + +def is_int(value): + try: + int(value) + return True + except: + return False + + +def is_str(value): + if value is None: + return False + try: + str(value) + return True + except: + return False + + +def get_float(vars_obj, name, default=None): + if name in vars_obj: + if is_float(vars_obj[name]): + return float(vars_obj[name]) + return default + + +def get_int(vars_obj, name, default=None): + if name in vars_obj: + if is_int(vars_obj[name]): + return float(vars_obj[name]) + return default + + +def get_string(vars_obj, name, default=None): + if name in vars_obj: + if not vars_obj[name] is None and not vars_obj[name] == "": + return str(vars_obj[name]) + return default diff --git a/gn2/utility/webqtlUtil.py b/gn2/utility/webqtlUtil.py new file mode 100644 index 00000000..74a6ce4b --- /dev/null +++ b/gn2/utility/webqtlUtil.py @@ -0,0 +1,118 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010) +# at rwilliams@uthsc.edu and xzhou15@uthsc.edu +# +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) +# +# Created by GeneNetwork Core Team 2010/08/10 +# +# Last updated by GeneNetwork Core Team 2010/10/20 + +import string +import time +import re +import math +from math import * + +from gn2.base import webqtlConfig + +# NL, 07/27/2010. moved from webqtlForm.py +# Dict of Parents and F1 information, In the order of [F1, Mat, Pat] +ParInfo = { + 'BXH': ['BHF1', 'HBF1', 'C57BL/6J', 'C3H/HeJ'], + 'AKXD': ['AKF1', 'KAF1', 'AKR/J', 'DBA/2J'], + 'BXD': ['B6D2F1', 'D2B6F1', 'C57BL/6J', 'DBA/2J'], + 'C57BL-6JxC57BL-6NJF2': ['', '', 'C57BL/6J', 'C57BL/6NJ'], + 'BXD300': ['B6D2F1', 'D2B6F1', 'C57BL/6J', 'DBA/2J'], + 'B6BTBRF2': ['B6BTBRF1', 'BTBRB6F1', 'C57BL/6J', 'BTBRT<+>tf/J'], + 'BHHBF2': ['B6HF2', 'HB6F2', 'C57BL/6J', 'C3H/HeJ'], + 'BHF2': ['B6HF2', 'HB6F2', 'C57BL/6J', 'C3H/HeJ'], + 'B6D2F2': ['B6D2F1', 'D2B6F1', 'C57BL/6J', 'DBA/2J'], + 'BDF2-1999': ['B6D2F2', 'D2B6F2', 'C57BL/6J', 'DBA/2J'], + 'BDF2-2005': ['B6D2F1', 'D2B6F1', 'C57BL/6J', 'DBA/2J'], + 'CTB6F2': ['CTB6F2', 'B6CTF2', 'C57BL/6J', 'Castaneous'], + 'CXB': ['CBF1', 'BCF1', 'C57BL/6ByJ', 'BALB/cByJ'], + 'AXBXA': ['ABF1', 'BAF1', 'C57BL/6J', 'A/J'], + 'AXB': ['ABF1', 'BAF1', 'C57BL/6J', 'A/J'], + 'BXA': ['BAF1', 'ABF1', 'C57BL/6J', 'A/J'], + 'LXS': ['LSF1', 'SLF1', 'ISS', 'ILS'], + 'HXBBXH': ['SHR_BNF1', 'BN_SHRF1', 'BN-Lx/Cub', 'SHR/OlaIpcv'], + 'BayXSha': ['BayXShaF1', 'ShaXBayF1', 'Bay-0', 'Shahdara'], + 'ColXBur': ['ColXBurF1', 'BurXColF1', 'Col-0', 'Bur-0'], + 'ColXCvi': ['ColXCviF1', 'CviXColF1', 'Col-0', 'Cvi'], + 'SXM': ['SMF1', 'MSF1', 'Steptoe', 'Morex'], + 'HRDP': ['SHR_BNF1', 'BN_SHRF1', 'BN-Lx/Cub', 'SHR/OlaIpcv'] +} + +######################################### +# Accessory Functions +######################################### + + +def genRandStr(prefix="", length=8, chars=string.ascii_letters + string.digits): + from random import choice + _str = prefix[:] + for i in range(length): + _str += choice(chars) + return _str + + +def ListNotNull(lst): + '''Obsolete - Use built in function any (or all or whatever) + + + Determine if the elements in a list are all null + + ''' + for item in lst: + if item is not None: + return 1 + return None + + +def readLineCSV(line): # dcrowell July 2008 + """Parses a CSV string of text and returns a list containing each element as a string. + Used by correlationPage""" + returnList = line.split('","') + returnList[-1] = returnList[-1][:-2] + returnList[0] = returnList[0][1:] + return returnList + + +def cmpEigenValue(A, B): + try: + if A[0] > B[0]: + return -1 + elif A[0] == B[0]: + return 0 + else: + return 1 + except: + return 0 + + +def hasAccessToConfidentialPhenotypeTrait(privilege, userName, authorized_users): + access_to_confidential_phenotype_trait = 0 + if webqtlConfig.USERDICT[privilege] > webqtlConfig.USERDICT['user']: + access_to_confidential_phenotype_trait = 1 + else: + AuthorizedUsersList = [x.strip() for x in authorized_users.split(',')] + if userName in AuthorizedUsersList: + access_to_confidential_phenotype_trait = 1 + return access_to_confidential_phenotype_trait diff --git a/gn2/wqflask/.DS_Store b/gn2/wqflask/.DS_Store Binary files differnew file mode 100644 index 00000000..a119e235 --- /dev/null +++ b/gn2/wqflask/.DS_Store diff --git a/gn2/wqflask/__init__.py b/gn2/wqflask/__init__.py new file mode 100644 index 00000000..9b714868 --- /dev/null +++ b/gn2/wqflask/__init__.py @@ -0,0 +1,131 @@ +"""Entry point for flask app""" +# pylint: disable=C0413,E0611 +import os +import time +import datetime +from typing import Tuple +from pathlib import Path +from urllib.parse import urljoin, urlparse + +import redis +import jinja2 +from flask_session import Session +from authlib.integrations.requests_client import OAuth2Session +from flask import g, Flask, flash, session, url_for, redirect, current_app + + +from gn2.utility import formatting + +from gn3.authentication import DataRole, AdminRole + +from gn2.wqflask.group_manager import group_management +from gn2.wqflask.resource_manager import resource_management +from gn2.wqflask.metadata_edits import metadata_edit + +from gn2.wqflask.api.markdown import glossary_blueprint +from gn2.wqflask.api.markdown import references_blueprint +from gn2.wqflask.api.markdown import links_blueprint +from gn2.wqflask.api.markdown import policies_blueprint +from gn2.wqflask.api.markdown import environments_blueprint +from gn2.wqflask.api.markdown import facilities_blueprint +from gn2.wqflask.api.markdown import blogs_blueprint +from gn2.wqflask.api.markdown import news_blueprint +from gn2.wqflask.api.jobs import jobs as jobs_bp +from gn2.wqflask.oauth2.routes import oauth2 +from gn2.wqflask.oauth2.checks import user_logged_in +from gn2.wqflask.oauth2.collections import num_collections +from gn2.wqflask.oauth2.request_utils import user_details, authserver_authorise_uri + +from gn2.wqflask.jupyter_notebooks import jupyter_notebooks + +from gn2.wqflask.startup import ( + StartupError, + startup_errors, + check_mandatory_configs) + +app = Flask(__name__) + + +# See http://flask.pocoo.org/docs/config/#configuring-from-files +# Note no longer use the badly named WQFLASK_OVERRIDES (nyi) +default_settings_file = Path(Path(__file__).parent.parent.parent, + "etc/default_settings.py") +app.config.from_pyfile(default_settings_file) +app.config.from_envvar('GN2_SETTINGS') + +app.jinja_env.globals.update( + undefined=jinja2.StrictUndefined, + numify=formatting.numify, + logged_in=user_logged_in, + authserver_authorise_uri=authserver_authorise_uri, + user_details=user_details, + num_collections=num_collections, + datetime=datetime) + +app.config["SESSION_REDIS"] = redis.from_url(app.config["REDIS_URL"]) + +## BEGIN: SECRETS -- Should be the last of the settings to load +secrets_file = os.environ.get("GN2_SECRETS") +if secrets_file and Path(secrets_file).exists(): + app.config.from_envvar("GN2_SECRETS") +## END: SECRETS + + +# Registering blueprints +app.register_blueprint(glossary_blueprint, url_prefix="/glossary") +app.register_blueprint(references_blueprint, url_prefix="/references") +app.register_blueprint(links_blueprint, url_prefix="/links") +app.register_blueprint(policies_blueprint, url_prefix="/policies") +app.register_blueprint(environments_blueprint, url_prefix="/environments") +app.register_blueprint(facilities_blueprint, url_prefix="/facilities") +app.register_blueprint(blogs_blueprint, url_prefix="/blogs") +app.register_blueprint(news_blueprint, url_prefix="/news") +app.register_blueprint(jupyter_notebooks, url_prefix="/jupyter_notebooks") + +app.register_blueprint(resource_management, url_prefix="/resource-management") +app.register_blueprint(metadata_edit, url_prefix="/datasets/") +app.register_blueprint(group_management, url_prefix="/group-management") +app.register_blueprint(jobs_bp, url_prefix="/jobs") +app.register_blueprint(oauth2, url_prefix="/oauth2") + +from gn2.wqflask.app_errors import register_error_handlers +register_error_handlers(app) + +try: + check_mandatory_configs(app) +except StartupError as serr: + app.startup_error = serr + app.register_blueprint(startup_errors, url_prefix="/") + +server_session = Session(app) + +@app.before_request +def before_request(): + g.request_start_time = time.time() + g.request_time = lambda: "%.5fs" % (time.time() - g.request_start_time) + + token = session.get("oauth2_token", False) + if token and not bool(session.get("user_details", False)): + config = current_app.config + client = OAuth2Session( + config["OAUTH2_CLIENT_ID"], config["OAUTH2_CLIENT_SECRET"], + token=token) + resp = client.get( + urljoin(config["GN_SERVER_URL"], "oauth2/user")) + user_details = resp.json() + session["user_details"] = user_details + + if user_details.get("error") == "invalid_token": + flash(user_details["error_description"], "alert-danger") + flash("You are now logged out.", "alert-info") + session.pop("user_details", None) + session.pop("oauth2_token", None) + +@app.context_processor +def include_admin_role_class(): + return {'AdminRole': AdminRole} + + +@app.context_processor +def include_data_role_class(): + return {'DataRole': DataRole} diff --git a/gn2/wqflask/api/__init__.py b/gn2/wqflask/api/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/api/__init__.py diff --git a/gn2/wqflask/api/correlation.py b/gn2/wqflask/api/correlation.py new file mode 100644 index 00000000..090d13ac --- /dev/null +++ b/gn2/wqflask/api/correlation.py @@ -0,0 +1,244 @@ +import collections +import scipy +import numpy + +from gn2.base import data_set +from gn2.base.trait import create_trait, retrieve_sample_data +from gn2.utility import corr_result_helpers +from gn2.utility.tools import get_setting +from gn2.wqflask.correlation import correlation_functions +from gn2.wqflask.database import database_connection + +def do_correlation(start_vars): + if 'db' not in start_vars: + raise ValueError("'db' not found!") + if 'target_db' not in start_vars: + raise ValueError("'target_db' not found!") + if 'trait_id' not in start_vars: + raise ValueError("'trait_id' not found!") + + this_dataset = data_set.create_dataset(dataset_name=start_vars['db']) + target_dataset = data_set.create_dataset( + dataset_name=start_vars['target_db']) + this_trait = create_trait(dataset=this_dataset, + name=start_vars['trait_id']) + this_trait = retrieve_sample_data(this_trait, this_dataset) + + corr_params = init_corr_params(start_vars) + + corr_results = calculate_results( + this_trait, this_dataset, target_dataset, corr_params) + + final_results = [] + for _trait_counter, trait in enumerate(list(corr_results.keys())[:corr_params['return_count']]): + if corr_params['type'] == "tissue": + [sample_r, num_overlap, sample_p, symbol] = corr_results[trait] + result_dict = { + "trait": trait, + "sample_r": sample_r, + "#_strains": num_overlap, + "p_value": sample_p, + "symbol": symbol + } + elif corr_params['type'] == "literature" or corr_params['type'] == "lit": + [gene_id, sample_r] = corr_results[trait] + result_dict = { + "trait": trait, + "sample_r": sample_r, + "gene_id": gene_id + } + else: + [sample_r, sample_p, num_overlap] = corr_results[trait] + result_dict = { + "trait": trait, + "sample_r": sample_r, + "#_strains": num_overlap, + "p_value": sample_p + } + final_results.append(result_dict) + return final_results + + +def calculate_results(this_trait, this_dataset, target_dataset, corr_params): + corr_results = {} + + target_dataset.get_trait_data() + + if corr_params['type'] == "tissue": + trait_symbol_dict = this_dataset.retrieve_genes("Symbol") + corr_results = do_tissue_correlation_for_all_traits( + this_trait, trait_symbol_dict, corr_params) + sorted_results = collections.OrderedDict(sorted(list(corr_results.items()), + key=lambda t: -abs(t[1][1]))) + # ZS: Just so a user can use either "lit" or "literature" + elif corr_params['type'] == "literature" or corr_params['type'] == "lit": + trait_geneid_dict = this_dataset.retrieve_genes("GeneId") + corr_results = do_literature_correlation_for_all_traits( + this_trait, this_dataset, trait_geneid_dict, corr_params) + sorted_results = collections.OrderedDict(sorted(list(corr_results.items()), + key=lambda t: -abs(t[1][1]))) + else: + for target_trait, target_vals in list(target_dataset.trait_data.items()): + result = get_sample_r_and_p_values( + this_trait, this_dataset, target_vals, target_dataset, corr_params['type']) + if result is not None: + corr_results[target_trait] = result + + sorted_results = collections.OrderedDict( + sorted(list(corr_results.items()), key=lambda t: -abs(t[1][0]))) + + return sorted_results + + +def do_tissue_correlation_for_all_traits(this_trait, trait_symbol_dict, corr_params, tissue_dataset_id=1): + # Gets tissue expression values for the primary trait + primary_trait_tissue_vals_dict = correlation_functions.get_trait_symbol_and_tissue_values( + symbol_list=[this_trait.symbol]) + + if this_trait.symbol.lower() in primary_trait_tissue_vals_dict: + primary_trait_tissue_values = primary_trait_tissue_vals_dict[this_trait.symbol.lower( + )] + + corr_result_tissue_vals_dict = correlation_functions.get_trait_symbol_and_tissue_values( + symbol_list=list(trait_symbol_dict.values())) + + tissue_corr_data = {} + for trait, symbol in list(trait_symbol_dict.items()): + if symbol and symbol.lower() in corr_result_tissue_vals_dict: + this_trait_tissue_values = corr_result_tissue_vals_dict[symbol.lower( + )] + + result = correlation_functions.cal_zero_order_corr_for_tiss(primary_trait_tissue_values, + this_trait_tissue_values, + corr_params['method']) + + tissue_corr_data[trait] = [ + result[0], result[1], result[2], symbol] + + return tissue_corr_data + + +def do_literature_correlation_for_all_traits(this_trait, target_dataset, trait_geneid_dict, corr_params): + input_trait_mouse_gene_id = convert_to_mouse_gene_id( + target_dataset.group.species.lower(), this_trait.geneid) + + lit_corr_data = {} + for trait, gene_id in list(trait_geneid_dict.items()): + mouse_gene_id = convert_to_mouse_gene_id( + target_dataset.group.species.lower(), gene_id) + + if mouse_gene_id and str(mouse_gene_id).find(";") == -1: + result = "" + with database_connection(get_setting("SQL_URI")) as conn: + with conn.cursor() as cursor: + cursor.execute( + ("SELECT value FROM LCorrRamin3 " + "WHERE GeneId1=%s AND GeneId2=%s"), + (mouse_gene_id, + input_trait_mouse_gene_id)) + result = cursor.fetchone() + if not result: + cursor.execute( + ("SELECT value FROM LCorrRamin3 " + "WHERE GeneId2=%s AND GeneId1=%s"), + (mouse_gene_id, + input_trait_mouse_gene_id)) + result = cursor.fetchone() + if result: + lit_corr = result[0] + lit_corr_data[trait] = [gene_id, lit_corr] + else: + lit_corr_data[trait] = [gene_id, 0] + else: + lit_corr_data[trait] = [gene_id, 0] + + return lit_corr_data + + +def get_sample_r_and_p_values(this_trait, this_dataset, target_vals, target_dataset, type): + """ + Calculates the sample r (or rho) and p-value + + Given a primary trait and a target trait's sample values, + calculates either the pearson r or spearman rho and the p-value + using the corresponding scipy functions. + """ + + this_trait_vals = [] + shared_target_vals = [] + for i, sample in enumerate(target_dataset.group.samplelist): + if sample in this_trait.data: + this_sample_value = this_trait.data[sample].value + target_sample_value = target_vals[i] + this_trait_vals.append(this_sample_value) + shared_target_vals.append(target_sample_value) + + this_trait_vals, shared_target_vals, num_overlap = corr_result_helpers.normalize_values( + this_trait_vals, shared_target_vals) + + if type == 'pearson': + sample_r, sample_p = scipy.stats.pearsonr( + this_trait_vals, shared_target_vals) + else: + sample_r, sample_p = scipy.stats.spearmanr( + this_trait_vals, shared_target_vals) + + if num_overlap > 5: + if numpy.isnan(sample_r): + return None + else: + return [sample_r, sample_p, num_overlap] + + +def convert_to_mouse_gene_id(species=None, gene_id=None): + """If the species is rat or human, translate the gene_id to the mouse geneid + + If there is no input gene_id or there's no corresponding mouse gene_id, return None + + """ + if not gene_id: + return None + + mouse_gene_id = None + with database_connection(get_setting("SQL_URI")) as conn: + with conn.cursor() as cursor: + if species == 'mouse': + mouse_gene_id = gene_id + elif species == 'rat': + cursor.execute( + ("SELECT mouse FROM GeneIDXRef " + "WHERE rat=%s"), gene_id) + result = cursor.fetchone() + if result: + mouse_gene_id = result[0] + elif species == 'human': + cursor.execute( + "SELECT mouse FROM GeneIDXRef " + "WHERE human=%s", gene_id) + result = cursor.fetchone() + if result: + mouse_gene_id = result[0] + return mouse_gene_id + + +def init_corr_params(start_vars): + method = "pearson" + if 'method' in start_vars: + method = start_vars['method'] + + type = "sample" + if 'type' in start_vars: + type = start_vars['type'] + + return_count = 500 + if 'return_count' in start_vars: + assert(start_vars['return_count'].isdigit()) + return_count = int(start_vars['return_count']) + + corr_params = { + 'method': method, + 'type': type, + 'return_count': return_count + } + + return corr_params diff --git a/gn2/wqflask/api/gen_menu.py b/gn2/wqflask/api/gen_menu.py new file mode 100644 index 00000000..45d5739e --- /dev/null +++ b/gn2/wqflask/api/gen_menu.py @@ -0,0 +1,217 @@ +from gn3.db.species import get_all_species + +def gen_dropdown_json(conn): + """Generates and outputs (as json file) the data for the main dropdown menus on + the home page + """ + species = get_all_species(conn) + groups = get_groups(species, conn) + types = get_types(groups, conn) + datasets = get_datasets(types, conn) + return dict(species=species, + groups=groups, + types=types, + datasets=datasets) + + +def get_groups(species, conn): + """Build groups list""" + groups = {} + with conn.cursor() as cursor: + for species_name, _species_full_name in species: + groups[species_name] = [] + query = ("SELECT InbredSet.Name, InbredSet.FullName, " + "IFNULL(InbredSet.Family, 'None') " + "FROM InbredSet, Species WHERE Species.Name = '{}' " + "AND InbredSet.SpeciesId = Species.Id GROUP by " + "InbredSet.Name ORDER BY IFNULL(InbredSet.FamilyOrder, " + "InbredSet.FullName) ASC, IFNULL(InbredSet.Family, " + "InbredSet.FullName) ASC, InbredSet.FullName ASC, " + "InbredSet.MenuOrderId ASC").format(species_name) + cursor.execute(query) + results = cursor.fetchall() + for result in results: + family_name = "Family:" + str(result[2]) + groups[species_name].append( + [str(result[0]), str(result[1]), family_name]) + return groups + + +def get_types(groups, conn): + """Build types list""" + types = {} + + for species, group_dict in list(groups.items()): + types[species] = {} + for group_name, _group_full_name, _family_name in group_dict: + if phenotypes_exist(group_name, conn): + types[species][group_name] = [ + ("Phenotypes", "Traits and Cofactors", "Phenotypes")] + if genotypes_exist(group_name, conn): + if group_name in types[species]: + types[species][group_name] += [ + ("Genotypes", "DNA Markers and SNPs", "Genotypes")] + else: + types[species][group_name] = [ + ("Genotypes", "DNA Markers and SNPs", "Genotypes")] + if group_name in types[species]: + types_list = build_types(species, group_name, conn) + if len(types_list) > 0: + types[species][group_name] += types_list + else: + types_list = build_types(species, group_name, conn) + if len(types_list) > 0: + types[species][group_name] = types_list + else: + types[species].pop(group_name, None) + groups[species] = list( + group for group in groups[species] + if group[0] != group_name) + return types + + +def phenotypes_exist(group_name, conn): + results = [] + with conn.cursor() as cursor: + cursor.execute( + ("SELECT Name FROM PublishFreeze " + "WHERE PublishFreeze.Name = " + "'{}'").format(group_name + "Publish")) + results = cursor.fetchone() + return bool(results) + + +def genotypes_exist(group_name, conn): + with conn.cursor() as cursor: + cursor.execute( + ("SELECT Name FROM GenoFreeze " + + "WHERE GenoFreeze.Name = '{}'").format( + group_name + "Geno")) + results = cursor.fetchone() + return bool(results) + + +def build_types(species, group, conn): + """Fetches tissues + + Gets the tissues with data for this species/group + (all types except phenotype/genotype are tissues) + + """ + + query = ("SELECT DISTINCT Tissue.Name " + "FROM ProbeFreeze, ProbeSetFreeze, InbredSet, " + "Tissue, Species WHERE Species.Name = '{0}' " + "AND Species.Id = InbredSet.SpeciesId AND " + "InbredSet.Name = '{1}' AND ProbeFreeze.TissueId = " + "Tissue.Id AND ProbeFreeze.InbredSetId = InbredSet.Id " + "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "ORDER BY Tissue.Name").format(species, group) + + results = [] + with conn.cursor() as cursor: + cursor.execute(query) + for result in cursor.fetchall(): + if bool(result): + these_datasets = build_datasets(species, + group, result[0], conn) + if len(these_datasets) > 0: + results.append([str(result[0]), str(result[0]), + "Molecular Traits"]) + return results + + +def get_datasets(types, conn): + """Build datasets list""" + datasets = {} + for species, group_dict in list(types.items()): + datasets[species] = {} + for group, type_list in list(group_dict.items()): + datasets[species][group] = {} + for type_name in type_list: + these_datasets = build_datasets(species, group, + type_name[0], conn) + if bool(these_datasets): + datasets[species][group][type_name[0]] = these_datasets + + return datasets + + +def build_datasets(species, group, type_name, conn): + """Gets dataset names from database""" + dataset_text = dataset_value = None + datasets = [] + with conn.cursor() as cursor: + if type_name == "Phenotypes": + cursor.execute( + ("SELECT InfoFiles.GN_AccesionId, PublishFreeze.Name, " + "PublishFreeze.FullName FROM InfoFiles, PublishFreeze, " + "InbredSet WHERE InbredSet.Name = '{}' AND " + "PublishFreeze.InbredSetId = InbredSet.Id AND " + "InfoFiles.InfoPageName = PublishFreeze.Name " + "ORDER BY PublishFreeze.CreateTime ASC").format(group)) + results = cursor.fetchall() + if bool(results): + for result in results: + dataset_id = str(result[0]) + dataset_value = str(result[1]) + dataset_text = str(result[2]) + if group == 'MDP': + dataset_text = "Mouse Phenome Database" + + datasets.append([dataset_id, dataset_value, dataset_text]) + else: + cursor.execute( + ("SELECT PublishFreeze.Name, PublishFreeze.FullName " + "FROM PublishFreeze, InbredSet " + "WHERE InbredSet.Name = '{}' AND " + "PublishFreeze.InbredSetId = InbredSet.Id " + "ORDER BY PublishFreeze.CreateTime ASC") + .format(group)) + result = cursor.fetchone() + dataset_id = "None" + dataset_value = str(result[0]) + dataset_text = str(result[1]) + datasets.append([dataset_id, dataset_value, dataset_text]) + + elif type_name == "Genotypes": + cursor.execute( + ("SELECT InfoFiles.GN_AccesionId " + "FROM InfoFiles, GenoFreeze, InbredSet " + "WHERE InbredSet.Name = '{}' AND " + "GenoFreeze.InbredSetId = InbredSet.Id AND " + "InfoFiles.InfoPageName = GenoFreeze.ShortName " + "ORDER BY GenoFreeze.CreateTime " + "DESC").format(group)) + results = cursor.fetchone() + dataset_id = "None" + if bool(results): + dataset_id = str(results[0]) + + dataset_value = "%sGeno" % group + dataset_text = "%s Genotypes" % group + datasets.append([dataset_id, dataset_value, dataset_text]) + + else: # for mRNA expression/ProbeSet + cursor.execute( + ("SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, " + "ProbeSetFreeze.FullName FROM ProbeSetFreeze, " + "ProbeFreeze, InbredSet, Tissue, Species WHERE " + "Species.Name = '{0}' AND Species.Id = " + "InbredSet.SpeciesId AND InbredSet.Name = '{1}' " + "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "AND Tissue.Name = '{2}' AND ProbeFreeze.TissueId = " + "Tissue.Id AND ProbeFreeze.InbredSetId = InbredSet.Id " + "AND ProbeSetFreeze.public > 0 " + "ORDER BY -ProbeSetFreeze.OrderList DESC, " + "ProbeSetFreeze.CreateTime " + "DESC").format(species, group, type_name)) + results = cursor.fetchall() + datasets = [] + for dataset_info in results: + this_dataset_info = [] + for info in dataset_info: + this_dataset_info.append(str(info)) + datasets.append(this_dataset_info) + + return datasets diff --git a/gn2/wqflask/api/jobs.py b/gn2/wqflask/api/jobs.py new file mode 100644 index 00000000..7a948e1a --- /dev/null +++ b/gn2/wqflask/api/jobs.py @@ -0,0 +1,54 @@ +import uuid +from datetime import datetime + +from redis import Redis +from pymonad.io import IO +from flask import Blueprint, render_template + +from gn2.jobs.jobs import job + +jobs = Blueprint("jobs", __name__) + +@jobs.route("/debug/<uuid:job_id>") +def debug_job(job_id: uuid.UUID): + """Display job data to assist in debugging.""" + from gn2.utility.tools import REDIS_URL # Avoids circular import error + + def __stream_to_lines__(stream): + removables = ( + "Set global log level to", "runserver.py: ******", + "APPLICATION_ROOT:", "DB_", "DEBUG:", "ELASTICSEARCH_", "ENV:", + "EXPLAIN_TEMPLATE_LOADING:", "GEMMA_", "GENENETWORK_FILES", + "GITHUB_", "GN2_", "GN3_", "GN_", "HOME:", "JSONIFY_", "JS_", + "JSON_", "LOG_", "MAX_", "ORCID_", "PERMANENT_", "PLINK_", + "PREFERRED_URL_SCHEME", "PRESERVE_CONTEXT_ON_EXCEPTION", + "PROPAGATE_EXCEPTIONS", "REAPER_COMMAND", "REDIS_URL", "SECRET_", + "SECURITY_", "SEND_FILE_MAX_AGE_DEFAULT", "SERVER_", "SESSION_", + "SMTP_", "SQL_", "TEMPLATES_", "TESTING:", "TMPDIR", "TRAP_", + "USE_", "WEBSERVER_") + return tuple(filter( + lambda line: not any(line.startswith(item) for item in removables), + stream.split("\n"))) + + def __fmt_datetime(val): + return datetime.strptime(val, "%Y-%m-%dT%H:%M:%S.%f").strftime( + "%A, %d %B %Y at %H:%M:%S.%f") + + def __render_debug_page__(job): + job_details = {key.replace("-", "_"): val for key,val in job.items()} + return render_template( + "jobs/debug.html", + **{ + **job_details, + "request_received_time": __fmt_datetime( + job_details["request_received_time"]), + "stderr": __stream_to_lines__(job_details["stderr"]), + "stdout": __stream_to_lines__(job_details["stdout"]) + }) + + with Redis.from_url(REDIS_URL, decode_responses=True) as rconn: + the_job = job(rconn, job_id) + + return the_job.maybe( + render_template("jobs/no-such-job.html", job_id=job_id), + lambda job: __render_debug_page__(job)) diff --git a/gn2/wqflask/api/mapping.py b/gn2/wqflask/api/mapping.py new file mode 100644 index 00000000..1e330963 --- /dev/null +++ b/gn2/wqflask/api/mapping.py @@ -0,0 +1,186 @@ +from gn2.base import data_set +from gn2.base.trait import create_trait, retrieve_sample_data + +from gn2.wqflask.marker_regression import gemma_mapping, rqtl_mapping +from gn2.wqflask.show_trait.show_trait import normf + +def do_mapping_for_api(start_vars): + if ('db' not in start_vars) or ("trait_id" not in start_vars): + raise ValueError("Mapping: db and trait_id are not in start_vars") + + dataset = data_set.create_dataset(dataset_name=start_vars['db']) + dataset.group.get_markers() + this_trait = create_trait(dataset=dataset, name=start_vars['trait_id']) + this_trait = retrieve_sample_data(this_trait, dataset) + + samples = [] + vals = [] + + mapping_params = initialize_parameters(start_vars, dataset, this_trait) + + genofile_samplelist = [] + if mapping_params.get('genofile'): + dataset.group.genofile = mapping_params['genofile'] + genofile_samplelist = get_genofile_samplelist(dataset) + + if (len(genofile_samplelist) > 0): + samplelist = genofile_samplelist + for sample in samplelist: + in_trait_data = False + for item in this_trait.data: + if this_trait.data[item].name == sample: + value = str(this_trait.data[item].value) + samples.append(item) + vals.append(value) + in_trait_data = True + break + if not in_trait_data: + vals.append("x") + else: + samplelist = dataset.group.samplelist + for sample in samplelist: + in_trait_data = False + for item in this_trait.data: + if this_trait.data[item].name == sample: + value = str(this_trait.data[item].value) + samples.append(item) + vals.append(value) + in_trait_data = True + break + if not in_trait_data: + vals.append("x") + + if mapping_params.get('transform') == "qnorm": + vals_minus_x = [float(val) for val in vals if val != "x"] + qnorm_vals = normf(vals_minus_x) + qnorm_vals_with_x = [] + counter = 0 + for val in vals: + if val == "x": + qnorm_vals_with_x.append("x") + else: + qnorm_vals_with_x.append(qnorm_vals[counter]) + counter += 1 + + vals = qnorm_vals_with_x + + # It seems to take an empty string as default. This should probably be changed. + covariates = "" + + if mapping_params.get('mapping_method') == "gemma": + header_row = ["name", "chr", "Mb", "lod_score", "p_value"] + # gemma_mapping returns both results and the filename for LOCO, so need to only grab the former for api + if mapping_params.get('use_loco') == "True": + result_markers = gemma_mapping.run_gemma( + this_trait, dataset, samples, vals, covariates, mapping_params['use_loco'], mapping_params['maf'])[0] + else: + result_markers = gemma_mapping.run_gemma( + this_trait, dataset, samples, vals, covariates, mapping_params['use_loco'], mapping_params['maf']) + elif mapping_params.get('mapping_method') == "rqtl": + header_row = ["name", "chr", "cM", "lod_score"] + if mapping_params['num_perm'] > 0: + _sperm_output, _suggestive, _significant, result_markers = rqtl_mapping.run_rqtl(this_trait.name, vals, samples, dataset, None, "Mb", mapping_params['rqtl_model'], + mapping_params['rqtl_method'], mapping_params['num_perm'], None, + mapping_params['do_control'], mapping_params['control_marker'], + mapping_params['manhattan_plot'], None) + else: + result_markers = rqtl_mapping.run_rqtl(this_trait.name, vals, samples, dataset, None, "Mb", mapping_params['rqtl_model'], + mapping_params['rqtl_method'], mapping_params['num_perm'], None, + mapping_params['do_control'], mapping_params['control_marker'], + mapping_params['manhattan_plot'], None) + + if mapping_params.get('limit_to'): + result_markers = result_markers[:mapping_params['limit_to']] + + if mapping_params.get('format') == "csv": + output_rows = [] + output_rows.append(header_row) + for marker in result_markers: + this_row = [marker[header] for header in header_row] + output_rows.append(this_row) + + return output_rows, mapping_params['format'] + elif mapping_params['format'] == "json": + return result_markers, mapping_params['format'] + else: + return result_markers, None + + +def initialize_parameters(start_vars, dataset, this_trait): + mapping_params = {} + + mapping_params['format'] = "json" + if 'format' in start_vars: + mapping_params['format'] = start_vars['format'] + + mapping_params['limit_to'] = False + if 'limit_to' in start_vars: + if start_vars['limit_to'].isdigit(): + mapping_params['limit_to'] = int(start_vars['limit_to']) + + mapping_params['mapping_method'] = "gemma" + if 'method' in start_vars: + mapping_params['mapping_method'] = start_vars['method'] + + if mapping_params['mapping_method'] == "rqtl": + mapping_params['rqtl_method'] = "hk" + mapping_params['rqtl_model'] = "normal" + mapping_params['do_control'] = False + mapping_params['control_marker'] = "" + mapping_params['manhattan_plot'] = True + mapping_params['pair_scan'] = False + if 'rqtl_method' in start_vars: + mapping_params['rqtl_method'] = start_vars['rqtl_method'] + if 'rqtl_model' in start_vars: + mapping_params['rqtl_model'] = start_vars['rqtl_model'] + if 'control_marker' in start_vars: + mapping_params['control_marker'] = start_vars['control_marker'] + mapping_params['do_control'] = True + if 'pair_scan' in start_vars: + if start_vars['pair_scan'].lower() == "true": + mapping_params['pair_scan'] = True + + if 'interval_mapping' in start_vars: + if start_vars['interval_mapping'].lower() == "true": + mapping_params['manhattan_plot'] = False + elif 'manhattan_plot' in start_vars: + if start_vars['manhattan_plot'].lower() != "true": + mapping_params['manhattan_plot'] = False + + mapping_params['maf'] = 0.01 + if 'maf' in start_vars: + mapping_params['maf'] = start_vars['maf'] # Minor allele frequency + + mapping_params['use_loco'] = True + if 'use_loco' in start_vars: + if (start_vars['use_loco'].lower() == "false") or (start_vars['use_loco'].lower() == "no"): + mapping_params['use_loco'] = False + + mapping_params['num_perm'] = 0 + mapping_params['perm_check'] = False + if 'num_perm' in start_vars: + try: + mapping_params['num_perm'] = int(start_vars['num_perm']) + mapping_params['perm_check'] = "ON" + except: + mapping_params['perm_check'] = False + + mapping_params['transform'] = False + if 'transform' in start_vars: + mapping_params['transform'] = start_vars['transform'] + + mapping_params['genofile'] = False + if 'genofile' in start_vars: + mapping_params['genofile'] = start_vars['genofile'] + + return mapping_params + +def get_genofile_samplelist(dataset): + genofile_samplelist = [] + + genofile_json = dataset.group.get_genofiles() + for genofile in genofile_json: + if genofile['location'] == dataset.group.genofile and 'sample_list' in genofile: + genofile_samplelist = genofile['sample_list'] + + return genofile_samplelist diff --git a/gn2/wqflask/api/markdown.py b/gn2/wqflask/api/markdown.py new file mode 100644 index 00000000..580b9ac0 --- /dev/null +++ b/gn2/wqflask/api/markdown.py @@ -0,0 +1,186 @@ +"""Markdown routes + +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 # type: ignore + +from flask import send_from_directory +from flask import Blueprint +from flask import render_template + +from typing import Dict +from typing import List + +glossary_blueprint = Blueprint('glossary_blueprint', __name__) +references_blueprint = Blueprint("references_blueprint", __name__) +environments_blueprint = Blueprint("environments_blueprint", __name__) +links_blueprint = Blueprint("links_blueprint", __name__) +policies_blueprint = Blueprint("policies_blueprint", __name__) +facilities_blueprint = Blueprint("facilities_blueprint", __name__) +news_blueprint = Blueprint("news_blueprint", __name__) + +blogs_blueprint = Blueprint("blogs_blueprint", __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 (f"\nContent for {file_name} not available. " + "Please check " + "(here to see where content exists)" + "[https://github.com/genenetwork/gn-docs]. " + "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 + + +def get_blogs(user: str = "genenetwork", + repo_name: str = "gn-docs") -> dict: + + blogs: Dict[int, List] = {} + github_url = f"https://api.github.com/repos/{user}/{repo_name}/git/trees/master?recursive=1" + + repo_tree = requests.get(github_url).json()["tree"] + + for data in repo_tree: + path_name = data["path"] + if path_name.startswith("blog") and path_name.endswith(".md"): + split_path = path_name.split("/")[1:] + try: + year, title, file_name = split_path + except Exception as e: + year, file_name = split_path + title = "" + + subtitle = os.path.splitext(file_name)[0] + + blog = { + "title": title, + "subtitle": subtitle, + "full_path": path_name + } + + if year in blogs: + blogs[int(year)].append(blog) + else: + blogs[int(year)] = [blog] + + return dict(sorted(blogs.items(), key=lambda x: x[0], reverse=True)) + + +@glossary_blueprint.route('/') +def glossary(): + return render_template( + "glossary.html", + rendered_markdown=render_markdown("general/glossary/glossary.md")), 200 + + +@references_blueprint.route('/') +def references(): + return render_template( + "references.html", + rendered_markdown=render_markdown("general/references/references.md")), 200 + + +@news_blueprint.route('/') +def news(): + return render_template( + "news.html", + rendered_markdown=render_markdown("general/news/news.md")), 200 + + +@environments_blueprint.route("/") +def environments(): + + 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) + + +@environments_blueprint.route('/svg-dependency-graph') +def svg_graph(): + directory, file_name, _ = get_file_from_python_search_path( + "wqflask/dependency-graph.svg").partition("dependency-graph.svg") + return send_from_directory(directory, file_name) + + +@links_blueprint.route("/") +def links(): + return render_template( + "links.html", + rendered_markdown=render_markdown("general/links/links.md")), 200 + + +@policies_blueprint.route("/") +def policies(): + return render_template( + "policies.html", + rendered_markdown=render_markdown("general/policies/policies.md")), 200 + + +@facilities_blueprint.route("/") +def facilities(): + return render_template("facilities.html", rendered_markdown=render_markdown("general/help/facilities.md")), 200 + + +@blogs_blueprint.route("/<path:blog_path>") +def display_blog(blog_path): + return render_template("blogs.html", rendered_markdown=render_markdown(blog_path)) + + +@blogs_blueprint.route("/") +def blogs_list(): + blogs = get_blogs() + + return render_template("blogs_list.html", blogs=blogs) diff --git a/gn2/wqflask/api/router.py b/gn2/wqflask/api/router.py new file mode 100644 index 00000000..bcd08e8d --- /dev/null +++ b/gn2/wqflask/api/router.py @@ -0,0 +1,1037 @@ +# GN2 API + +import os +import io +import csv +import json +import datetime +import requests + +from zipfile import ZipFile, ZIP_DEFLATED + + +import flask +from flask import current_app +from gn2.wqflask.database import database_connection +from flask import request +from flask import make_response +from flask import send_file + +from gn2.wqflask import app + +from gn2.wqflask.api import correlation, mapping, gen_menu + +from gn2.utility.tools import flat_files, get_setting + +from gn2.wqflask.database import database_connection + + +version = "pre1" + + +@app.route("/api/v_{}/".format(version)) +def hello_world(): + return flask.jsonify({"hello": "world"}) + + +@app.route("/api/v_{}/species".format(version)) +def get_species_list(): + species_list = [] + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT SpeciesId, Name, FullName, TaxonomyId FROM Species" + ) + for species in cursor.fetchall(): + species_dict = { + "Id": species[0], + "Name": species[1], + "FullName": species[2], + "TaxonomyId": species[3] + } + species_list.append(species_dict) + return flask.jsonify(species_list) + + +@app.route("/api/v_{}/species/<path:species_name>".format(version)) +@app.route("/api/v_{}/species/<path:species_name>.<path:file_format>".format(version)) +def get_species_info(species_name, file_format="json"): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT SpeciesId, Name, FullName, TaxonomyId " + "FROM Species WHERE (Name=%s OR FullName=%s " + "OR SpeciesName=%s)", ((species_name,)*3)) + _species = cursor.fetchone() + species_dict = { + "Id": _species[0], + "Name": _species[1], + "FullName": _species[2], + "TaxonomyId": _species[3] + } + + return flask.jsonify(species_dict) + + +@app.route("/api/v_{}/groups".format(version)) +@app.route("/api/v_{}/groups/<path:species_name>".format(version)) +def get_groups_list(species_name=None): + _groups = () + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + if species_name: + cursor.execute( + "SELECT InbredSet.InbredSetId, " + "InbredSet.SpeciesId, InbredSet.InbredSetName, " + "InbredSet.Name, InbredSet.FullName, " + "InbredSet.public, IFNULL(InbredSet.MappingMethodId, " + "'None'), IFNULL(InbredSet.GeneticType, 'None') " + "FROM InbredSet, Species WHERE " + "InbredSet.SpeciesId = Species.Id AND " + "(Species.Name = %s OR Species.FullName=%s " + "OR Species.SpeciesName=%s)", ((species_name,) * 3) + ) + else: + cursor.execute( + "SELECT InbredSet.InbredSetId, " + "InbredSet.SpeciesId, InbredSet.InbredSetName, " + "InbredSet.Name, InbredSet.FullName, " + "InbredSet.public, IFNULL(InbredSet.MappingMethodId, " + "'None'), IFNULL(InbredSet.GeneticType, 'None') " + "FROM InbredSet" + ) + _groups = cursor.fetchall() + + if _groups: + groups_list = [] + for group in _groups: + group_dict = { + "Id": group[0], + "SpeciesId": group[1], + "DisplayName": group[2], + "Name": group[3], + "FullName": group[4], + "public": group[5], + "MappingMethodId": group[6], + "GeneticType": group[7] + } + groups_list.append(group_dict) + return flask.jsonify(groups_list) + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + + +@app.route("/api/v_{}/group/<path:group_name>".format(version)) +@app.route("/api/v_{}/group/<path:group_name>.<path:file_format>".format(version)) +@app.route("/api/v_{}/group/<path:species_name>/<path:group_name>".format(version)) +@app.route("/api/v_{}/group/<path:species_name>/<path:group_name>.<path:file_format>".format(version)) +def get_group_info(group_name, species_name=None, file_format="json"): + group = tuple() + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + if species_name: + cursor.execute( + "SELECT InbredSet.InbredSetId, InbredSet.SpeciesId, " + "InbredSet.InbredSetName, InbredSet.Name, " + "InbredSet.FullName, InbredSet.public, " + "IFNULL(InbredSet.MappingMethodId, 'None'), " + "IFNULL(InbredSet.GeneticType, 'None') " + "FROM InbredSet, Species WHERE " + "InbredSet.SpeciesId = Species.Id " + "AND (InbredSet.InbredSetName = %s OR " + "InbredSet.Name = %s OR InbredSet.FullName = %s) " + "AND (Species.Name = %s OR " + "Species.FullName = %s OR Species.SpeciesName = %s)", + (*(group_name,)*3, *(species_name,)*3) + ) + else: + cursor.execute( + "SELECT InbredSet.InbredSetId, InbredSet.SpeciesId, " + "InbredSet.InbredSetName, InbredSet.Name, " + "InbredSet.FullName, InbredSet.public, " + "IFNULL(InbredSet.MappingMethodId, 'None'), " + "IFNULL(InbredSet.GeneticType, 'None') " + "FROM InbredSet WHERE " + "(InbredSet.InbredSetName = %s OR " + "InbredSet.Name = %s OR " + "InbredSet.FullName = %s)", + ((group_name,)*3) + ) + group = cursor.fetchone() + + if group: + group_dict = { + "Id": group[0], + "SpeciesId": group[1], + "DisplayName": group[2], + "Name": group[3], + "FullName": group[4], + "public": group[5], + "MappingMethodId": group[6], + "GeneticType": group[7] + } + + return flask.jsonify(group_dict) + else: + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + + +@app.route("/api/v_{}/datasets/<path:group_name>".format(version)) +@app.route("/api/v_{}/datasets/<path:species_name>/<path:group_name>".format(version)) +def get_datasets_for_group(group_name, species_name=None): + _datasets = () + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + if species_name: + cursor.execute( + "SELECT ProbeSetFreeze.Id, ProbeSetFreeze.ProbeFreezeId, " + "ProbeSetFreeze.AvgID, ProbeSetFreeze.Name, " + "ProbeSetFreeze.Name2, " + "ProbeSetFreeze.FullName, ProbeSetFreeze.ShortName, " + "ProbeSetFreeze.CreateTime, ProbeSetFreeze.public, " + "ProbeSetFreeze.confidentiality, ProbeSetFreeze.DataScale " + "FROM ProbeSetFreeze, ProbeFreeze, InbredSet, Species " + "WHERE ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "AND ProbeFreeze.InbredSetId = InbredSet.Id " + "AND (InbredSet.Name = %s OR " + "InbredSet.InbredSetName = %s OR " + "InbredSet.FullName = %s) AND " + "InbredSet.SpeciesId = Species.Id AND " + "(Species.SpeciesName = %s OR " + "Species.MenuName = %s OR Species.FullName = %s);", + (*(group_name,)*3, *(species_name)*3) + ) + else: + cursor.execute( + "SELECT ProbeSetFreeze.Id, ProbeSetFreeze.ProbeFreezeId, " + "ProbeSetFreeze.AvgID, ProbeSetFreeze.Name, " + "ProbeSetFreeze.Name2, ProbeSetFreeze.FullName, " + "ProbeSetFreeze.ShortName, ProbeSetFreeze.CreateTime, " + "ProbeSetFreeze.public, ProbeSetFreeze.confidentiality, " + "ProbeSetFreeze.DataScale FROM ProbeSetFreeze, " + "ProbeFreeze, InbredSet WHERE " + "ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "AND ProbeFreeze.InbredSetId = InbredSet.Id " + "AND (InbredSet.Name = %s OR " + "InbredSet.InbredSetName = %s OR " + "InbredSet.FullName = %s)", + ((group_name,) * 3) + ) + _datasets = cursor.fetchall() + + if _datasets: + datasets_list = [] + for dataset in _datasets: + dataset_dict = { + "Id": dataset[0], + "ProbeFreezeId": dataset[1], + "AvgID": dataset[2], + "Short_Abbreviation": dataset[3], + "Long_Abbreviation": dataset[4], + "FullName": dataset[5], + "ShortName": dataset[6], + "CreateTime": dataset[7], + "public": dataset[8], + "confidentiality": dataset[9], + "DataScale": dataset[10] + } + datasets_list.append(dataset_dict) + + return flask.jsonify(datasets_list) + else: + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + + +@app.route("/api/v_{}/dataset/<path:dataset_name>".format(version)) +@app.route("/api/v_{}/dataset/<path:dataset_name>.<path:file_format>".format(version)) +@app.route("/api/v_{}/dataset/<path:group_name>/<path:dataset_name>".format(version)) +@app.route("/api/v_{}/dataset/<path:group_name>/<path:dataset_name>.<path:file_format>".format(version)) +def get_dataset_info(dataset_name, group_name=None, file_format="json"): + # ZS: First get ProbeSet (mRNA expression) datasets and then get Phenotype datasets + + # ZS: I figure I might as well return a list if there are multiple + # matches, though I don"t know if this will actually happen in + # practice + datasets_list, dataset_dict = [], {} + probeset_query = """ + SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, ProbeSetFreeze.FullName, + ProbeSetFreeze.ShortName, ProbeSetFreeze.DataScale, ProbeFreeze.TissueId, + Tissue.Name, ProbeSetFreeze.public, ProbeSetFreeze.confidentiality + FROM ProbeSetFreeze, ProbeFreeze, Tissue + """ + + where_statement = """ + WHERE ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id AND + ProbeFreeze.TissueId = Tissue.Id AND + ProbeSetFreeze.public > 0 AND + ProbeSetFreeze.confidentiality < 1 AND + """ + if dataset_name.isdigit(): + where_statement += """ + ProbeSetFreeze.Id = "{}" + """.format(dataset_name) + else: + where_statement += """ + (ProbeSetFreeze.Name = "{0}" OR ProbeSetFreeze.Name2 = "{0}" OR + ProbeSetFreeze.FullName = "{0}" OR ProbeSetFreeze.ShortName = "{0}") + """.format(dataset_name) + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute(f"{probeset_query}{where_statement}") + + if dataset := cursor.fetchone(): + dataset_dict = { + "dataset_type": "mRNA expression", + "id": dataset[0], + "name": dataset[1], + "full_name": dataset[2], + "short_name": dataset[3], + "data_scale": dataset[4], + "tissue_id": dataset[5], + "tissue": dataset[6], + "public": dataset[7], + "confidential": dataset[8] + } + + datasets_list.append(dataset_dict) + + if group_name: + cursor.execute( + "SELECT PublishXRef.Id, " + "Phenotype.Post_publication_abbreviation, " + "Phenotype.Post_publication_description, " + "Phenotype.Pre_publication_abbreviation, " + "Phenotype.Pre_publication_description, " + "Publication.PubMed_ID, Publication.Title, " + "Publication.Year FROM PublishXRef, Phenotype, " + "Publication, InbredSet, PublishFreeze WHERE " + "PublishXRef.InbredSetId = InbredSet.Id " + "AND PublishXRef.PhenotypeId = Phenotype.Id " + "AND PublishXRef.PublicationId = Publication.Id " + "AND PublishFreeze.InbredSetId = InbredSet.Id " + "AND PublishFreeze.public > 0 AND " + "PublishFreeze.confidentiality < 1 " + "AND InbredSet.Name = %s AND PublishXRef.Id = %s", + (group_name, dataset_name,) + ) + + if dataset := cursor.fetchone(): + if dataset[5]: + dataset_dict = { + "dataset_type": "phenotype", + "id": dataset[0], + "name": dataset[1], + "description": dataset[2], + "pubmed_id": dataset[5], + "title": dataset[6], + "year": dataset[7] + } + elif dataset[4]: + dataset_dict = { + "dataset_type": "phenotype", + "id": dataset[0], + "name": dataset[3], + "description": dataset[4] + } + else: + dataset_dict = { + "dataset_type": "phenotype", + "id": dataset[0] + } + + datasets_list.append(dataset_dict) + + if len(datasets_list) > 1: + return flask.jsonify(datasets_list) + elif len(datasets_list) == 1: + return flask.jsonify(dataset_dict) + else: + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + + +@app.route("/api/v_{}/traits/<path:dataset_name>".format(version), methods=("GET",)) +@app.route("/api/v_{}/traits/<path:dataset_name>.<path:file_format>".format(version), methods=("GET",)) +def fetch_traits(dataset_name, file_format="json"): + trait_ids, trait_names, data_type, dataset_id = get_dataset_trait_ids( + dataset_name, request.args) + if ("ids_only" in request.args) and (len(trait_ids) > 0): + if file_format == "json": + filename = dataset_name + "_trait_ids.json" + return flask.jsonify(trait_ids) + else: + filename = dataset_name + "_trait_ids.csv" + + si = io.StringIO() + csv_writer = csv.writer(si) + csv_writer.writerows([[trait_id] for trait_id in trait_ids]) + output = make_response(si.getvalue()) + output.headers["Content-Disposition"] = "attachment; filename=" + filename + output.headers["Content-type"] = "text/csv" + return output + elif ("names_only" in request.args) and (len(trait_ids) > 0): + if file_format == "json": + filename = dataset_name + "_trait_names.json" + return flask.jsonify(trait_names) + else: + filename = dataset_name + "_trait_names.csv" + + si = io.StringIO() + csv_writer = csv.writer(si) + csv_writer.writerows([[trait_name] for trait_name in trait_names]) + output = make_response(si.getvalue()) + output.headers["Content-Disposition"] = "attachment; filename=" + filename + output.headers["Content-type"] = "text/csv" + return output + else: + if len(trait_ids) > 0: + if data_type == "ProbeSet": + query = """ + SELECT + ProbeSet.Id, ProbeSet.Name, ProbeSet.Symbol, ProbeSet.description, ProbeSet.Chr, ProbeSet.Mb, ProbeSet.alias, + ProbeSetXRef.mean, ProbeSetXRef.se, ProbeSetXRef.Locus, ProbeSetXRef.LRS, ProbeSetXRef.pValue, ProbeSetXRef.additive, ProbeSetXRef.h2 + FROM + ProbeSet, ProbeSetXRef, ProbeSetFreeze + WHERE + ProbeSetXRef.ProbeSetFreezeId = "{0}" AND + ProbeSetXRef.ProbeSetId = ProbeSet.Id AND + ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id AND + ProbeSetFreeze.public > 0 AND + ProbeSetFreeze.confidentiality < 1 + ORDER BY + ProbeSet.Id + """ + + field_list = ["Id", "Name", "Symbol", "Description", "Chr", "Mb", + "Aliases", "Mean", "SE", "Locus", "LRS", "P-Value", "Additive", "h2"] + elif data_type == "Geno": + query = """ + SELECT + Geno.Id, Geno.Name, Geno.Marker_Name, Geno.Chr, Geno.Mb, Geno.Sequence, Geno.Source + FROM + Geno, GenoXRef, GenoFreeze + WHERE + GenoXRef.GenoFreezeId = "{0}" AND + GenoXRef.GenoId = Geno.Id AND + GenoXRef.GenoFreezeId = GenoFreeze.Id AND + GenoFreeze.public > 0 AND + GenoFreeze.confidentiality < 1 + ORDER BY + Geno.Id + """ + + field_list = ["Id", "Name", "Marker_Name", + "Chr", "Mb", "Sequence", "Source"] + else: + query = """SELECT PublishXRef.Id, + Phenotype.`Original_description`, + Publication.`Authors`, + Publication.`Year`, + Publication.`PubMed_ID`, + PublishXRef.`mean`, + PublishXRef.`LRS`, + PublishXRef.`additive`, + PublishXRef.`Locus`, + Geno.`Chr`, Geno.`Mb` + FROM Species + INNER JOIN InbredSet ON InbredSet.`SpeciesId` = Species.`Id` + INNER JOIN PublishXRef ON PublishXRef.`InbredSetId` = InbredSet.`Id` + INNER JOIN PublishFreeze ON PublishFreeze.`InbredSetId` = InbredSet.`Id` + INNER JOIN Publication ON Publication.`Id` = PublishXRef.`PublicationId` + INNER JOIN Phenotype ON Phenotype.`Id` = PublishXRef.`PhenotypeId` + LEFT JOIN Geno ON PublishXRef.Locus = Geno.Name AND Geno.SpeciesId = Species.Id + WHERE + PublishXRef.InbredSetId = {0} AND + PublishFreeze.InbredSetId = PublishXRef.InbredSetId AND + PublishFreeze.public > 0 AND + PublishFreeze.confidentiality < 1 + ORDER BY + PublishXRef.Id""" + + field_list = ["Id", "Description", "Authors", "Year", "PubMedID", "Mean", + "LRS", "Additive", "Locus", "Chr", "Mb"] + + if 'limit_to' in request.args: + limit_number = request.args['limit_to'] + query += "LIMIT " + str(limit_number) + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + if file_format == "json": + filename = dataset_name + "_traits.json" + cursor.execute(query.format(dataset_id)) + result_list = [] + for result in cursor.fetchall(): + trait_dict = {} + for i, field in enumerate(field_list): + if result[i]: + trait_dict[field] = result[i] + result_list.append(trait_dict) + return flask.jsonify(result_list) + elif file_format == "csv": + filename = dataset_name + "_traits.csv" + + results_list = [] + header_list = [] + header_list += field_list + results_list.append(header_list) + cursor.execute(query.format(dataset_id)) + for result in cursor.fetchall(): + results_list.append(result) + + si = io.StringIO() + csv_writer = csv.writer(si) + csv_writer.writerows(results_list) + output = make_response(si.getvalue()) + output.headers["Content-Disposition"] = "attachment; filename=" + filename + output.headers["Content-type"] = "text/csv" + return output + else: + return return_error( + code=400, + source=request.url_rule.rule, + title="Invalid Output Format", + details="Current formats available are JSON and CSV, with CSV as default" + ) + else: + return return_error( + code=204, + source=request.url_rule.rule, + title="No Results", + details="") + + +@app.route("/api/v_{}/sample_data/<path:dataset_name>".format(version)) +@app.route("/api/v_{}/sample_data/<path:dataset_name>.<path:file_format>".format(version)) +def all_sample_data(dataset_name, file_format="csv"): + trait_ids, trait_names, data_type, dataset_id = get_dataset_trait_ids( + dataset_name, request.args) + + if len(trait_ids) > 0: + sample_list = get_samplelist(dataset_name) + + if data_type == "ProbeSet": + query = """ + SELECT + Strain.Name, Strain.Name2, ProbeSetData.value, ProbeSetData.Id, ProbeSetSE.error + FROM + (ProbeSetData, Strain, ProbeSetXRef, ProbeSetFreeze) + LEFT JOIN ProbeSetSE ON + (ProbeSetSE.DataId = ProbeSetData.Id AND ProbeSetSE.StrainId = ProbeSetData.StrainId) + WHERE + ProbeSetXRef.ProbeSetFreezeId = "{0}" AND + ProbeSetXRef.ProbeSetId = "{1}" AND + ProbeSetXRef.DataId = ProbeSetData.Id AND + ProbeSetData.StrainId = Strain.Id AND + ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id AND + ProbeSetFreeze.public > 0 AND + ProbeSetFreeze.confidentiality < 1 + ORDER BY + Strain.Name + """ + elif data_type == "Geno": + query = """ + SELECT + Strain.Name, Strain.Name2, GenoData.value, GenoData.Id, GenoSE.error + FROM + (GenoData, Strain, GenoXRef, GenoFreeze) + LEFT JOIN GenoSE ON + (GenoSE.DataId = GenoData.Id AND GenoSE.StrainId = GenoData.StrainId) + WHERE + GenoXRef.GenoFreezeId = "{0}" AND + GenoXRef.GenoId = "{1}" AND + GenoXRef.DataId = GenoData.Id AND + GenoData.StrainId = Strain.Id AND + GenoXRef.GenoFreezeId = GenoFreeze.Id AND + GenoFreeze.public > 0 AND + GenoFreeze.confidentiality < 1 + ORDER BY + Strain.Name + """ + else: + query = """ + SELECT + Strain.Name, Strain.Name2, PublishData.value, PublishData.Id, PublishSE.error, NStrain.count + FROM + (PublishData, Strain, PublishXRef, PublishFreeze) + LEFT JOIN PublishSE ON + (PublishSE.DataId = PublishData.Id AND PublishSE.StrainId = PublishData.StrainId) + LEFT JOIN NStrain ON + (NStrain.DataId = PublishData.Id AND + NStrain.StrainId = PublishData.StrainId) + WHERE + PublishXRef.InbredSetId = "{0}" AND + PublishXRef.PhenotypeId = "{1}" AND + PublishData.Id = PublishXRef.DataId AND + PublishData.StrainId = Strain.Id AND + PublishXRef.InbredSetId = PublishFreeze.InbredSetId AND + PublishFreeze.public > 0 AND + PublishFreeze.confidentiality < 1 + ORDER BY + Strain.Name + """ + + if file_format == "csv": + filename = dataset_name + "_sample_data.csv" + + results_list = [] + header_list = [] + header_list.append("id") + header_list += sample_list + results_list.append(header_list) + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + for i, trait_id in enumerate(trait_ids): + line_list = [] + line_list.append(str(trait_names[i])) + cursor.execute(query.format(dataset_id, trait_id)) + results = cursor.fetchall() + results_dict = {} + for item in results: + results_dict[item[0]] = item[2] + for sample in sample_list: + if sample in results_dict: + line_list.append(results_dict[sample]) + else: + line_list.append("x") + results_list.append(line_list) + + results_list = list(map(list, zip(*results_list))) + + si = io.StringIO() + csv_writer = csv.writer(si) + csv_writer.writerows(results_list) + output = make_response(si.getvalue()) + output.headers["Content-Disposition"] = "attachment; filename=" + filename + output.headers["Content-type"] = "text/csv" + return output + else: + return return_error(code=415, source=request.url_rule.rule, title="Unsupported file format", details="") + else: + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + + +@app.route("/api/v_{}/sample_data/<path:dataset_name>/<path:trait_name>".format(version)) +@app.route("/api/v_{}/sample_data/<path:dataset_name>/<path:trait_name>.<path:file_format>".format(version)) +def trait_sample_data(dataset_name, trait_name, file_format="json"): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT Strain.Name, Strain.Name2, " + "ProbeSetData.value, ProbeSetData.Id, " + "ProbeSetSE.error FROM (ProbeSetData, " + "ProbeSetFreeze, Strain, ProbeSet, " + "ProbeSetXRef) LEFT JOIN ProbeSetSE ON " + "(ProbeSetSE.DataId = ProbeSetData.Id AND " + "ProbeSetSE.StrainId = ProbeSetData.StrainId) " + "WHERE ProbeSet.Name = %s AND " + "ProbeSetXRef.ProbeSetId = ProbeSet.Id " + "AND ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id " + "AND ProbeSetFreeze.Name = %s AND " + "ProbeSetXRef.DataId = ProbeSetData.Id " + "AND ProbeSetData.StrainId = Strain.Id " + "ORDER BY Strain.Name", + (trait_name, dataset_name,) + ) + + sample_data = cursor.fetchall() + if len(sample_data) > 0: + sample_list = [] + for sample in sample_data: + sample_dict = { + "sample_name": sample[0], + "sample_name_2": sample[1], + "value": sample[2], + "data_id": sample[3], + } + if sample[4]: + sample_dict["se"] = sample[4] + sample_list.append(sample_dict) + + return flask.jsonify(sample_list) + else: + if not dataset_name.isdigit(): + group_id = get_group_id(dataset_name) + if group_id: + dataset_or_group = group_id + else: + dataset_or_group = dataset_name + else: + dataset_or_group = dataset_name + + cursor.execute( + "SELECT DISTINCT Strain.Name, Strain.Name2, " + "PublishData.value, PublishData.Id, PublishSE.error, " + "NStrain.count FROM (PublishData, Strain, " + "PublishXRef, PublishFreeze) LEFT JOIN " + "PublishSE ON (PublishSE.DataId = PublishData.Id " + "AND PublishSE.StrainId = PublishData.StrainId) " + "LEFT JOIN NStrain ON " + "(NStrain.DataId = PublishData.Id AND " + "NStrain.StrainId = PublishData.StrainId) " + "WHERE PublishXRef.InbredSetId = PublishFreeze.InbredSetId " + "AND PublishData.Id = PublishXRef.DataId AND " + "PublishXRef.Id = %s AND (PublishFreeze.Id = %s " + "OR PublishFreeze.Name = %s OR " + "PublishFreeze.ShortName = %s OR " + "PublishXRef.InbredSetId = %s) AND " + "PublishData.StrainId = Strain.Id " + "ORDER BY Strain.Name", + (trait_name, *(dataset_or_group,)*4) + ) + if len(sample_data := cursor.fetchall()) > 0: + sample_list = [] + for sample in sample_data: + sample_dict = { + "sample_name": sample[0], + "sample_name_2": sample[1], + "value": sample[2], + "data_id": sample[3] + } + if sample[4]: + sample_dict["se"] = sample[4] + if sample[5]: + sample_dict["n_cases"] = sample[5] + sample_list.append(sample_dict) + + return flask.jsonify(sample_list) + else: + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + + +@app.route("/api/v_{}/trait/<path:dataset_name>/<path:trait_name>".format(version)) +@app.route("/api/v_{}/trait/<path:dataset_name>/<path:trait_name>.<path:file_format>".format(version)) +@app.route("/api/v_{}/trait_info/<path:dataset_name>/<path:trait_name>".format(version)) +@app.route("/api/v_{}/trait_info/<path:dataset_name>/<path:trait_name>.<path:file_format>".format(version)) +def get_trait_info(dataset_name, trait_name, file_format="json"): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT ProbeSet.Id, ProbeSet.Name, ProbeSet.Symbol, " + "ProbeSet.description, ProbeSet.Chr, ProbeSet.Mb, " + "ProbeSet.alias, ProbeSetXRef.mean, ProbeSetXRef.se, " + "ProbeSetXRef.Locus, ProbeSetXRef.LRS, " + "ProbeSetXRef.pValue, ProbeSetXRef.additive " + "FROM ProbeSet, ProbeSetXRef, ProbeSetFreeze " + "WHERE ProbeSet.Name = %s AND " + "ProbeSetXRef.ProbeSetId = ProbeSet.Id AND " + "ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id " + "AND ProbeSetFreeze.Name = %s", + (trait_name, dataset_name,) + ) + if trait_info := cursor.fetchone(): + trait_dict = { + "id": trait_info[0], + "name": trait_info[1], + "symbol": trait_info[2], + "description": trait_info[3], + "chr": trait_info[4], + "mb": trait_info[5], + "alias": trait_info[6], + "mean": trait_info[7], + "se": trait_info[8], + "locus": trait_info[9], + "lrs": trait_info[10], + "p_value": trait_info[11], + "additive": trait_info[12] + } + + return flask.jsonify(trait_dict) + else: + # ZS: Check if the user input the dataset_name as BXDPublish, etc (which is always going to be the group name + "Publish" + if "Publish" in dataset_name: + dataset_name = dataset_name.replace("Publish", "") + + group_id = get_group_id(dataset_name) + cursor.execute( + "SELECT PublishXRef.PhenotypeId, " + "PublishXRef.Locus, PublishXRef.LRS, " + "PublishXRef.additive FROM " + "PublishXRef WHERE " + "PublishXRef.Id = %s AND " + "PublishXRef.InbredSetId = %s", + (trait_name, group_id,) + ) + if trait_info := cursor.fetchone(): + trait_dict = { + "id": trait_info[0], + "locus": trait_info[1], + "lrs": trait_info[2], + "additive": trait_info[3] + } + + return flask.jsonify(trait_dict) + else: + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + + +@app.route("/api/v_{}/correlation".format(version), methods=("GET",)) +def get_corr_results(): + results = correlation.do_correlation(request.args) + + if len(results) > 0: + # ZS: I think flask.jsonify expects a dict/list instead of JSON + return flask.jsonify(results) + else: + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + + +@app.route("/api/v_{}/mapping".format(version), methods=("GET",)) +def get_mapping_results(): + results, format = mapping.do_mapping_for_api(request.args) + + if len(results) > 0: + if format == "csv": + filename = "mapping_" + datetime.datetime.utcnow().strftime("%b_%d_%Y_%I:%M%p") + ".csv" + + si = io.StringIO() + csv_writer = csv.writer(si) + csv_writer.writerows(results) + output = make_response(si.getvalue()) + output.headers["Content-Disposition"] = "attachment; filename=" + filename + output.headers["Content-type"] = "text/csv" + + return output + elif format == "json": + return flask.jsonify(results) + else: + return return_error(code=415, source=request.url_rule.rule, title="Unsupported Format", details="") + else: + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + + +@app.route("/api/v_{}/genotypes/view/<string:group_name>".format(version)) +def view_genotype_files(group_name): + if os.path.isfile("{0}/{1}.json".format(flat_files("genotype"), group_name)): + with open("{0}/{1}.json".format(flat_files("genotype"), group_name)) as geno_json: + return flask.jsonify(json.load(geno_json)) + + +@app.route("/api/v_{}/genotypes/<string:file_format>/<string:group_name>/<string:dataset_name>.zip".format(version)) +@app.route("/api/v_{}/genotypes/<string:file_format>/<string:group_name>/<string:dataset_name>".format(version)) +@app.route("/api/v_{}/genotypes/<string:file_format>/<string:group_name>.zip".format(version)) +@app.route("/api/v_{}/genotypes/<string:file_format>/<string:group_name>".format(version)) +@app.route("/api/v_{}/genotypes/<string:group_name>.<string:file_format>".format(version)) +def get_genotypes(group_name, file_format="csv", dataset_name=None): + limit_num = None + if 'limit_to' in request.args: + if request.args['limit_to'].isdigit(): + limit_num = int(request.args['limit_to']) + + si = io.StringIO() + if file_format == "csv" or file_format == "geno": + filename = group_name + ".geno" + + if os.path.isfile("{0}/{1}.geno".format(flat_files("genotype"), group_name)): + output_lines = [] + with open("{0}/{1}.geno".format(flat_files("genotype"), group_name)) as genofile: + i = 0 + for line in genofile: + if line[0] == "#" or line[0] == "@": + output_lines.append([line.strip()]) + else: + if limit_num and i >= limit_num: + break + output_lines.append(line.split()) + i += 1 + + csv_writer = csv.writer( + si, delimiter="\t", escapechar="\\", quoting=csv.QUOTE_NONE) + else: + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + elif file_format == "rqtl2": + memory_file = io.BytesIO() + if dataset_name: + filename = dataset_name + else: + filename = group_name + + if os.path.isfile("{0}/{1}_geno.csv".format(flat_files("genotype/rqtl2"), group_name)): + yaml_file = json.load( + open("{0}/{1}.json".format(flat_files("genotype/rqtl2"), group_name))) + yaml_file["geno"] = filename + "_geno.csv" + yaml_file["gmap"] = filename + "_gmap.csv" + yaml_file["pheno"] = filename + "_pheno.csv" + config_file = [filename + ".json", json.dumps(yaml_file)] + #config_file = [filename + ".yaml", open("{0}/{1}.yaml".format(flat_files("genotype/rqtl2"), group_name))] + geno_file = [filename + "_geno.csv", + open("{0}/{1}_geno.csv".format(flat_files("genotype/rqtl2"), group_name))] + gmap_file = [filename + "_gmap.csv", + open("{0}/{1}_gmap.csv".format(flat_files("genotype/rqtl2"), group_name))] + if dataset_name: + phenotypes = requests.get( + "http://gn2.genenetwork.org/api/v_pre1/sample_data/" + dataset_name) + else: + phenotypes = requests.get( + "http://gn2.genenetwork.org/api/v_pre1/sample_data/" + group_name + "Publish") + + with ZipFile(memory_file, 'w', compression=ZIP_DEFLATED) as zf: + zf.writestr(config_file[0], config_file[1]) + for this_file in [geno_file, gmap_file]: + zf.writestr(this_file[0], this_file[1].read()) + zf.writestr(filename + "_pheno.csv", phenotypes.content) + + memory_file.seek(0) + + return send_file(memory_file, attachment_filename=filename + ".zip", as_attachment=True) + else: + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + else: + filename = group_name + ".bimbam" + + if os.path.isfile("{0}/{1}.geno".format(flat_files("genotype"), group_name)): + output_lines = [] + with open("{0}/{1}_geno.txt".format(flat_files("genotype/bimbam"), group_name)) as genofile: + i = 0 + for line in genofile: + if limit_num and i >= limit_num: + break + output_lines.append([line.strip() + for line in line.split(",")]) + i += 1 + + csv_writer = csv.writer(si, delimiter=",") + else: + return return_error(code=204, source=request.url_rule.rule, title="No Results", details="") + + csv_writer.writerows(output_lines) + output = make_response(si.getvalue()) + output.headers["Content-Disposition"] = "attachment; filename=" + filename + output.headers["Content-type"] = "text/csv" + + return output + + +@app.route("/api/v_{}/gen_dropdown".format(version), methods=("GET",)) +def gen_dropdown_menu(): + with database_connection(get_setting("SQL_URI")) as conn: + results = gen_menu.gen_dropdown_json(conn) + + if len(results) > 0: + return flask.jsonify(results) + else: + return return_error(code=500, source=request.url_rule.rule, title="Some error occurred", details="") + + +def return_error(code, source, title, details): + json_ob = {"errors": [ + { + "status": code, + "source": {"pointer": source}, + "title": title, + "detail": details + } + ]} + + return flask.jsonify(json_ob) + + +def get_dataset_trait_ids(dataset_name, start_vars): + + if 'limit_to' in start_vars: + limit_string = "LIMIT " + str(start_vars['limit_to']) + else: + limit_string = "" + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + if "Geno" in dataset_name: + data_type = "Geno" # ZS: Need to pass back the dataset type + cursor.execute( + "SELECT GenoXRef.GenoId, Geno.Name, " + "GenoXRef.GenoFreezeId FROM Geno, " + "GenoXRef, GenoFreeze WHERE " + "Geno.Id = GenoXRef.GenoId AND " + "GenoXRef.GenoFreezeId = GenoFreeze.Id " + f"AND GenoFreeze.Name = %s {limit_string}", + (dataset_name,)) + + results = cursor.fetchall() + + trait_ids = [result[0] for result in results] + trait_names = [result[1] for result in results] + dataset_id = results[0][2] + return trait_ids, trait_names, data_type, dataset_id + + elif "Publish" in dataset_name or get_group_id(dataset_name): + data_type = "Publish" + dataset_name = dataset_name.replace("Publish", "") + dataset_id = get_group_id(dataset_name) + cursor.execute( + "SELECT PublishXRef.PhenotypeId, " + "PublishXRef.Id, InbredSet.InbredSetCode " + "FROM PublishXRef, InbredSet WHERE " + "PublishXRef.InbredSetId = %s AND " + "InbredSet.Id = PublishXRef.InbredSetId " + f"{limit_string}", + (dataset_id,) + ) + results = cursor.fetchall() + + trait_ids = [result[0] for result in results] + trait_names = [str(result[2]) + "_" + str(result[1]) + for result in results] + + return trait_ids, trait_names, data_type, dataset_id + + else: + data_type = "ProbeSet" + cursor.execute( + "SELECT ProbeSetXRef.ProbeSetId, " + "ProbeSet.Name, ProbeSetXRef.ProbeSetFreezeId " + "FROM ProbeSet, ProbeSetXRef, " + "ProbeSetFreeze WHERE " + "ProbeSet.Id = ProbeSetXRef.ProbeSetId AND " + "ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id " + f"AND ProbeSetFreeze.Name = %s {limit_string}", + (dataset_name,) + ) + results = cursor.fetchall() + trait_ids = [result[0] for result in results] + trait_names = [result[1] for result in results] + dataset_id = results[0][2] + return trait_ids, trait_names, data_type, dataset_id + + +def get_samplelist(dataset_name): + group_id = get_group_id_from_dataset(dataset_name) + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT Strain.Name FROM Strain, StrainXRef " + "WHERE StrainXRef.StrainId = Strain.Id AND " + "StrainXRef.InbredSetId = %s", + (group_id,) + ) + # sample list + return [result[0] for result in cursor.fetchall()] + + +def get_group_id_from_dataset(dataset_name): + result = () + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + if "Publish" in dataset_name: + cursor.execute( + "SELECT InbredSet.Id FROM " + "InbredSet, PublishFreeze " + "WHERE PublishFreeze.InbredSetId = InbredSet.Id " + "AND PublishFreeze.Name = %s", + (dataset_name,) + ) + elif "Geno" in dataset_name: + cursor.execute( + "SELECT InbredSet.Id FROM " + "InbredSet, GenoFreeze WHERE " + "GenoFreeze.InbredSetId = InbredSet.Id " + "AND GenoFreeze.Name = %s", + (dataset_name,) + ) + else: + cursor.execute( + "SELECT InbredSet.Id FROM " + "InbredSet, ProbeSetFreeze, " + "ProbeFreeze WHERE " + "ProbeFreeze.InbredSetId = InbredSet.Id " + "AND ProbeFreeze.Id = ProbeSetFreeze.ProbeFreezeId " + "AND ProbeSetFreeze.Name = %s", + (dataset_name,) + ) + if result := cursor.fetchone(): + return result[0] + return None + + +def get_group_id(group_name): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT InbredSet.Id FROM InbredSet " + "WHERE InbredSet.Name = %s", + (group_name,) + ) + if group_id := cursor.fetchone(): + return group_id[0] + return None diff --git a/gn2/wqflask/app_errors.py b/gn2/wqflask/app_errors.py new file mode 100644 index 00000000..c6081e08 --- /dev/null +++ b/gn2/wqflask/app_errors.py @@ -0,0 +1,31 @@ +"""Handle errors at the application's top-level""" +from flask import flash, redirect, current_app, render_template +from authlib.integrations.base_client.errors import InvalidTokenError + +from gn2.wqflask.oauth2 import session +from gn2.wqflask.decorators import AuthorisationError + +def handle_authorisation_error(exc: AuthorisationError): + """Handle AuthorisationError if not handled anywhere else.""" + current_app.logger.error(exc) + return render_template( + "authorisation_error.html", error_type=type(exc).__name__, error=exc) + +def handle_invalid_token_error(exc: InvalidTokenError): + flash("An invalid session token was detected. " + "You have been logged out of the system.", + "alert-danger") + session.clear_session_info() + return redirect("/") + +__handlers__ = { + AuthorisationError: handle_authorisation_error, + InvalidTokenError: handle_invalid_token_error +} + +def register_error_handlers(app): + """Register all error handlers.""" + for klass, handler in __handlers__.items(): + app.register_error_handler(klass, handler) + + return app diff --git a/gn2/wqflask/collect.py b/gn2/wqflask/collect.py new file mode 100644 index 00000000..21168908 --- /dev/null +++ b/gn2/wqflask/collect.py @@ -0,0 +1,396 @@ +import os +import uuid +import hashlib +import datetime +import simplejson as json +from urllib.parse import urljoin + +from flask import g +from flask import render_template +from flask import url_for +from flask import request +from flask import redirect +from flask import flash +from flask import current_app + +from gn2.wqflask import app +from gn2.utility import hmac +from gn2.utility.formatting import numify +from gn2.utility.tools import GN_SERVER_URL, TEMPDIR +from gn2.utility.redis_tools import get_redis_conn + +from gn2.base.trait import create_trait +from gn2.base.trait import retrieve_trait_info +from gn2.base.trait import jsonable +from gn2.base.data_set import create_dataset + +from gn2.wqflask.oauth2 import client +from gn2.wqflask.oauth2 import session +from gn2.wqflask.oauth2.session import session_info +from gn2.wqflask.oauth2.checks import user_logged_in +from gn2.wqflask.oauth2.request_utils import ( + process_error, with_flash_error, with_flash_success) +from gn2.wqflask.oauth2.client import ( + oauth2_get, oauth2_post, no_token_get, no_token_post) + + +Redis = get_redis_conn() + + +def process_traits(unprocessed_traits): + if isinstance(unprocessed_traits, bytes): + unprocessed_traits = unprocessed_traits.decode('utf-8').split(",") + else: # It's a string + unprocessed_traits = unprocessed_traits.split(",") + traits = set() + for trait in unprocessed_traits: + data, _separator, the_hmac = trait.rpartition(':') + data = data.strip() + if g.user_session.logged_in: + assert the_hmac == hmac.hmac_creation(data), "Data tampering?" + traits.add(str(data)) + + return tuple(traits) + + +def report_change(len_before, len_now): + new_length = len_now - len_before + if new_length: + flash("We've added {} to your collection.".format( + numify(new_length, 'new trait', 'new traits'))) + + +@app.route("/collections/store_trait_list", methods=('POST',)) +def store_traits_list(): + params = request.form + + traits = params['traits'] + hash = params['hash'] + + Redis.set(hash, traits) + + return hash + + +@app.route("/collections/add", methods=["POST"]) +def collections_add(): + anon_id = session_info()["anon_id"] + traits = request.args.get("traits", request.form.get("traits")) + the_hash = request.args.get("hash", request.form.get("hash")) + collections = g.user_session.user_collections + collections = oauth2_get("auth/user/collections/list").either( + lambda _err: tuple(), lambda colls: tuple(colls)) + no_token_get( + f"auth/user/collections/{anon_id}/list").either( + lambda _err: tuple(), lambda colls: tuple(colls)) + + def __create_new_coll_error__(error): + err = process_error(error) + flash(f"{err['error']}:{err['error_description']}", "alert-danger") + return redirect("/") + + if len(collections) < 1: + new_coll = client.post( + "auth/user/collections/new", + json={ + "anon_id": str(anon_id), + "name": "Your Default Collection", + "traits": [] + }).either(__create_new_coll_error__, lambda coll: coll) + collections = (new_coll,) + + if bool(traits): + return render_template("collections/add.html", + traits=traits, + collections=collections) + else: + return render_template("collections/add.html", + hash=the_hash, + collections=collections) + +def __compute_traits__(params): + if "hash" in params: + unprocessed_traits = Redis.get(params['hash']) or "" + Redis.delete(params['hash']) + else: + unprocessed_traits = params['traits'] + return process_traits(unprocessed_traits) + +@app.route("/collections/new", methods=["POST"]) +def collections_new(): + params = request.form + anon_id = session_info()["anon_id"] + + if "sign_in" in params: + return redirect(url_for('login')) + if "create_new" in params: + collection_name = ( + params.get("new_collection", "").strip() or + datetime.datetime.utcnow().strftime('Collection_%b_%d_%H:%M')) + request_data = { + "uri_path": "auth/user/collections/new", + "json": { + "name": collection_name, + "anon_id": str(anon_id), + "traits": __compute_traits__(params), + "hash": params.get("hash", False) + }} + if user_logged_in(): + resp = oauth2_post(**request_data) + else: + resp = no_token_post(**request_data) + #return create_new(collection_name) + def __error__(err): + error = process_error(err) + flash(f"{error['error']}: {error['error_description']}", + "alert-danger") + return redirect("/") + def __view_collection__(collection): + return redirect(url_for("view_collection", uc_id=collection["id"])) + return resp.either(__error__, __view_collection__) + elif "add_to_existing" in params: + traits = process_traits(params["traits"]) + coll_id, *_coll_name = tuple( + part.strip() for part in params["existing_collection"].split(":")) + collection_id = uuid.UUID(coll_id) + resp = redirect(url_for('view_collection', uc_id=collection_id)) + return client.post( + f"auth/user/collections/{collection_id}/traits/add", + json={ + "anon_id": str(anon_id), + "traits": traits + }).either( + with_flash_error(resp), with_flash_success(resp)) + else: + # CauseAnError + pass + + +def create_new(collection_name): + params = request.args + if "hash" in params: + unprocessed_traits = Redis.get(params['hash']) + Redis.delete(params['hash']) + else: + unprocessed_traits = params['traits'] + + traits = process_traits(unprocessed_traits) + + uc_id = g.user_session.add_collection(collection_name, traits) + + return redirect(url_for('view_collection', uc_id=uc_id)) + + +@app.route("/collections/list") +def list_collections(): + params = request.args + anon_id = session.session_info()["anon_id"] + anon_collections = no_token_get( + f"auth/user/collections/{anon_id}/list").either( + lambda err: {"anon_collections_error": process_error(err)}, + lambda colls: {"anon_collections": colls}) + + user_collections = {"collections": []} + if user_logged_in(): + user_collections = oauth2_get("auth/user/collections/list").either( + lambda err: {"user_collections_error": process_error(err)}, + lambda colls: {"collections": colls}) + + return render_template("collections/list.html", + params=params, + **user_collections, + **anon_collections) + +@app.route("/collections/handle_anonymous", methods=["POST"]) +def handle_anonymous_collections(): + """Handle any anonymous collection on logging in.""" + choice = request.form.get("anon_choice") + if choice not in ("import", "delete"): + flash("Invalid choice!", "alert-danger") + return redirect("/") + def __impdel_error__(err): + error = process_error(err) + flash(f"{error['error']}: {error['error_description']}", + "alert-danger") + return redirect("/") + def __impdel_success__(msg): + flash(f"Success: {msg['message']}", "alert-success") + return redirect("/") + return oauth2_post( + f"auth/user/collections/anonymous/{choice}", + json={ + "anon_id": str(session_info()["anon_id"]) + }).either(__impdel_error__, __impdel_success__) + +@app.route("/collections/remove", methods=('POST',)) +def remove_traits(): + params = request.form + uc_id = params['uc_id'] + traits_to_remove = process_traits(params['trait_list']) + resp = redirect(url_for("view_collection", uc_id=uc_id)) + return client.post( + f"auth/user/collections/{uc_id}/traits/remove", + json = { + "anon_id": str(session_info()["anon_id"]), + "traits": traits_to_remove + }).either(with_flash_error(resp), with_flash_success(resp)) + + +@app.route("/collections/delete", methods=('POST',)) +def delete_collection(): + def __error__(err): + error = process_error(err) + flash(f"{error['error']}: {error['error_description']}", + "alert-danger") + return redirect(url_for('list_collections')) + + def __success__(msg): + flash(msg["message"], "alert-success") + return redirect(url_for('list_collections')) + + uc_ids = [item for item in request.form.get("uc_id", "").split(":") + if bool(item)] + if len(uc_ids) > 0: + return (oauth2_post if user_logged_in() else no_token_post)( + "auth/user/collections/delete", + json = { + "anon_id": str(session_info()["anon_id"]), + "collection_ids": uc_ids + }).either( + __error__, __success__) + + flash("Nothing to delete.", "alert-info") + return redirect(url_for('list_collections')) + + +def trait_info_str(trait): + """Provide a string representation for given trait""" + def __trait_desc(trt): + if trait.dataset.type == "Geno": + return f"Marker: {trt.name}" + if hasattr(trt, "description_display"): + return trt.description_display + else: + return "N/A" + + def __symbol(trt): + return (trt.symbol or trt.abbreviation or "N/A")[:20] + + def __lrs(trt): + if trait.dataset.type == "Geno": + return 0 + else: + if trait.LRS_score_repr != "N/A": + return ( + f"{float(trait.LRS_score_repr):0.3f}" if float(trait.LRS_score_repr) > 0 + else f"{trait.LRS_score_repr}") + else: + return "N/A" + + def __lrs_location(trt): + if hasattr(trt, "LRS_location_repr"): + return trt.LRS_location_repr + else: + return "N/A" + + def __location(trt): + if hasattr(trt, "location_repr"): + return trt.location_repr + return None + + def __mean(trt): + if trait.mean: + return trt.mean + else: + return 0 + + return "{}|||{}|||{}|||{}|||{}|||{:0.3f}|||{}|||{}".format( + trait.name, trait.dataset.name, __trait_desc(trait), __symbol(trait), + __location(trait), __mean(trait), __lrs(trait), __lrs_location(trait)) + +@app.route("/collections/import", methods=('POST',)) +def import_collection(): + import_file = request.files['import_file'] + if import_file.filename != '': + file_path = os.path.join(TEMPDIR, import_file.filename) + import_file.save(file_path) + collection_csv = open(file_path, "r") + traits = [row.strip() for row in collection_csv if row[0] != "#"] + os.remove(file_path) + + return json.dumps(traits) + else: + return render_template( + "collections/list.html") + +@app.route("/collections/view") +def view_collection(): + params = request.args + + uc_id = params['uc_id'] + request_data = { + "uri_path": f"auth/user/collections/{uc_id}/view", + "json": {"anon_id": str(session_info()["anon_id"])} + } + if user_logged_in(): + coll = oauth2_post(**request_data) + else: + coll = no_token_post(**request_data) + + def __view__(uc): + traits = uc["members"] + + trait_obs = [] + json_version = [] + + for atrait in traits: + if ':' not in atrait: + continue + name, dataset_name = atrait.split(':') + if dataset_name == "Temp": + group = name.split("_")[2] + dataset = create_dataset( + dataset_name, dataset_type="Temp", group_name=group) + trait_ob = create_trait(name=name, dataset=dataset) + else: + dataset = create_dataset(dataset_name) + trait_ob = create_trait(name=name, dataset=dataset) + trait_ob = retrieve_trait_info( + trait_ob, dataset, get_qtl_info=True) + trait_obs.append(trait_ob) + + trait_json = jsonable(trait_ob) + trait_json['trait_info_str'] = trait_info_str(trait_ob) + + json_version.append(trait_json) + + collection_info = dict( + trait_obs=trait_obs, + uc=uc, + heatmap_data_url=urljoin(GN_SERVER_URL, "heatmaps/clustered")) + + if "json" in params: + return json.dumps(json_version) + else: + return render_template( + "collections/view.html", + traits_json=json_version, + trait_info_str=trait_info_str, + **collection_info) + + def __error__(err): + error = process_error(err) + flash(f"{error['error']}: {error['error_description']}", "alert-danger") + return redirect(url_for("list_collections")) + + return coll.either(__error__, __view__) + +@app.route("/collections/change_name", methods=('POST',)) +def change_collection_name(): + collection_id = request.form['collection_id'] + resp = redirect(url_for("view_collection", uc_id=collection_id)) + return client.post( + f"auth/user/collections/{collection_id}/rename", + json={ + "anon_id": str(session_info()["anon_id"]), + "new_name": request.form["new_collection_name"] + }).either(with_flash_error(resp), with_flash_success(resp)) diff --git a/gn2/wqflask/comparison_bar_chart/__init__.py b/gn2/wqflask/comparison_bar_chart/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/comparison_bar_chart/__init__.py diff --git a/gn2/wqflask/comparison_bar_chart/comparison_bar_chart.py b/gn2/wqflask/comparison_bar_chart/comparison_bar_chart.py new file mode 100644 index 00000000..3fb3cb40 --- /dev/null +++ b/gn2/wqflask/comparison_bar_chart/comparison_bar_chart.py @@ -0,0 +1,95 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Dr. Robert W. Williams at rwilliams@uthsc.edu +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) + +from pprint import pformat as pf + +from gn2.base.trait import create_trait +from gn2.base import data_set +from gn2.utility import webqtlUtil, helper_functions, corr_result_helpers +import gn2.utility.webqtlUtil # this is for parallel computing only. +from gn2.wqflask.correlation import correlation_functions + +from flask import Flask, g + + +class ComparisonBarChart: + + def __init__(self, start_vars): + trait_db_list = [trait.strip() + for trait in start_vars['trait_list'].split(',')] + + helper_functions.get_trait_db_obs(self, trait_db_list) + + self.all_sample_list = [] + self.traits = [] + self.insufficient_shared_samples = False + # ZS: Getting initial group name before verifying all traits are in the same group in the following loop + this_group = self.trait_list[0][1].group.name + for trait_db in self.trait_list: + + if trait_db[1].group.name != this_group: + self.insufficient_shared_samples = True + break + else: + this_group = trait_db[1].group.name + this_trait = trait_db[0] + self.traits.append(this_trait) + + this_sample_data = this_trait.data + + for sample in this_sample_data: + if sample not in self.all_sample_list: + self.all_sample_list.append(sample) + + if self.insufficient_shared_samples: + pass + else: + self.sample_data = [] + for trait_db in self.trait_list: + this_trait = trait_db[0] + this_sample_data = this_trait.data + + this_trait_vals = [] + for sample in self.all_sample_list: + if sample in this_sample_data: + this_trait_vals.append(this_sample_data[sample].value) + else: + this_trait_vals.append('') + self.sample_data.append(this_trait_vals) + + self.js_data = dict(traits=[trait.name for trait in self.traits], + samples=self.all_sample_list, + sample_data=self.sample_data,) + + def get_trait_db_obs(self, trait_db_list): + + self.trait_list = [] + for i, trait_db in enumerate(trait_db_list): + if i == (len(trait_db_list) - 1): + break + trait_name, dataset_name = trait_db.split(":") + #print("dataset_name:", dataset_name) + dataset_ob = data_set.create_dataset(dataset_name) + trait_ob = create_trait(dataset=dataset_ob, + name=trait_name, + cellid=None) + self.trait_list.append((trait_ob, dataset_ob)) + + #print("trait_list:", self.trait_list) diff --git a/gn2/wqflask/correlation/__init__.py b/gn2/wqflask/correlation/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/correlation/__init__.py diff --git a/gn2/wqflask/correlation/corr_scatter_plot.py b/gn2/wqflask/correlation/corr_scatter_plot.py new file mode 100644 index 00000000..59e9ac4a --- /dev/null +++ b/gn2/wqflask/correlation/corr_scatter_plot.py @@ -0,0 +1,158 @@ +import json +import math + +from redis import Redis +Redis = Redis() + +from gn2.base.trait import create_trait, retrieve_sample_data +from gn2.base import data_set, webqtlCaseData +from gn2.utility import corr_result_helpers +from gn2.wqflask.oauth2.collections import num_collections + +from scipy import stats +import numpy as np + +import logging +logger = logging.getLogger(__name__) + +class CorrScatterPlot: + """Page that displays a correlation scatterplot with a line fitted to it""" + + def __init__(self, params): + if "Temp" in params['dataset_1']: + self.dataset_1 = data_set.create_dataset( + dataset_name="Temp", dataset_type="Temp", group_name=params['dataset_1'].split("_")[1]) + else: + self.dataset_1 = data_set.create_dataset(params['dataset_1']) + if "Temp" in params['dataset_2']: + self.dataset_2 = data_set.create_dataset( + dataset_name="Temp", dataset_type="Temp", group_name=params['dataset_2'].split("_")[1]) + else: + self.dataset_2 = data_set.create_dataset(params['dataset_2']) + + self.trait_1 = create_trait( + name=params['trait_1'], dataset=self.dataset_1) + self.trait_2 = create_trait( + name=params['trait_2'], dataset=self.dataset_2) + + self.method = params['method'] + + primary_samples = self.dataset_1.group.samplelist + if self.dataset_1.group.parlist != None: + primary_samples += self.dataset_1.group.parlist + if self.dataset_1.group.f1list != None: + primary_samples += self.dataset_1.group.f1list + + if 'dataid' in params: + trait_data_dict = json.loads(Redis.get(params['dataid'])) + trait_data = {key:webqtlCaseData.webqtlCaseData(key, float(trait_data_dict[key])) for (key, value) in trait_data_dict.items() if trait_data_dict[key] != "x"} + trait_1_data = trait_data + trait_2_data = self.trait_2.data + # Check if the cached data should be used for the second trait instead + if 'cached_trait' in params: + if params['cached_trait'] == 'trait_2': + trait_2_data = trait_data + trait_1_data = self.trait_1.data + samples_1, samples_2, num_overlap = corr_result_helpers.normalize_values_with_samples( + trait_1_data, trait_2_data) + else: + samples_1, samples_2, num_overlap = corr_result_helpers.normalize_values_with_samples( + self.trait_1.data, self.trait_2.data) + + self.data = [] + self.indIDs = list(samples_1.keys()) + vals_1 = [] + for sample in list(samples_1.keys()): + vals_1.append(samples_1[sample].value) + self.data.append(vals_1) + vals_2 = [] + for sample in list(samples_2.keys()): + vals_2.append(samples_2[sample].value) + self.data.append(vals_2) + + slope, intercept, r_value, p_value, std_err = stats.linregress( + vals_1, vals_2) + + if slope < 0.001: + slope_string = '%.3E' % slope + else: + slope_string = '%.3f' % slope + + x_buffer = (max(vals_1) - min(vals_1)) * 0.1 + y_buffer = (max(vals_2) - min(vals_2)) * 0.1 + + x_range = [min(vals_1) - x_buffer, max(vals_1) + x_buffer] + y_range = [min(vals_2) - y_buffer, max(vals_2) + y_buffer] + + intercept_coords = get_intercept_coords( + slope, intercept, x_range, y_range) + + rx = stats.rankdata(vals_1) + ry = stats.rankdata(vals_2) + self.rdata = [] + self.rdata.append(rx.tolist()) + self.rdata.append(ry.tolist()) + srslope, srintercept, srr_value, srp_value, srstd_err = stats.linregress( + rx, ry) + + if srslope < 0.001: + srslope_string = '%.3E' % srslope + else: + srslope_string = '%.3f' % srslope + + x_buffer = (max(rx) - min(rx)) * 0.1 + y_buffer = (max(ry) - min(ry)) * 0.1 + + sr_range = [min(rx) - x_buffer, max(rx) + x_buffer] + + sr_intercept_coords = get_intercept_coords( + srslope, srintercept, sr_range, sr_range) + + self.collections_exist = "False" + if num_collections() > 0: + self.collections_exist = "True" + + self.js_data = dict( + data=self.data, + rdata=self.rdata, + indIDs=self.indIDs, + trait_1=self.trait_1.dataset.name + ": " + str(self.trait_1.name), + trait_2=self.trait_2.dataset.name + ": " + str(self.trait_2.name), + samples_1=samples_1, + samples_2=samples_2, + num_overlap=num_overlap, + vals_1=vals_1, + vals_2=vals_2, + x_range=x_range, + y_range=y_range, + sr_range=sr_range, + intercept_coords=intercept_coords, + sr_intercept_coords=sr_intercept_coords, + + slope=slope, + slope_string=slope_string, + intercept=intercept, + r_value=r_value, + p_value=p_value, + + srslope=srslope, + srslope_string=srslope_string, + srintercept=srintercept, + srr_value=srr_value, + srp_value=srp_value + ) + self.jsdata = self.js_data + + +def get_intercept_coords(slope, intercept, x_range, y_range): + intercept_coords = [] + + y1 = slope * x_range[0] + intercept + y2 = slope * x_range[1] + intercept + x1 = (y1 - intercept) / slope + x2 = (y2 - intercept) / slope + + intercept_coords.append([x1, y1]) + intercept_coords.append([x2, y2]) + + return intercept_coords diff --git a/gn2/wqflask/correlation/correlation_functions.py b/gn2/wqflask/correlation/correlation_functions.py new file mode 100644 index 00000000..911f6dc8 --- /dev/null +++ b/gn2/wqflask/correlation/correlation_functions.py @@ -0,0 +1,68 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010) +# at rwilliams@uthsc.edu and xzhou15@uthsc.edu +# +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) +# +# Created by GeneNetwork Core Team 2010/08/10 + + +from gn2.base.mrna_assay_tissue_data import MrnaAssayTissueData +from gn3.computations.correlations import compute_corr_coeff_p_value +from gn2.wqflask.database import database_connection +from gn2.utility.tools import get_setting + +##################################################################################### +# Input: primaryValue(list): one list of expression values of one probeSet, +# targetValue(list): one list of expression values of one probeSet, +# method(string): indicate correlation method ('pearson' or 'spearman') +# Output: corr_result(list): first item is Correlation Value, second item is tissue number, +# third item is PValue +# Function: get correlation value,Tissue quantity ,p value result by using R; +# Note : This function is special case since both primaryValue and targetValue are from +# the same dataset. So the length of these two parameters is the same. They are pairs. +# Also, in the datatable TissueProbeSetData, all Tissue values are loaded based on +# the same tissue order +##################################################################################### + + +def cal_zero_order_corr_for_tiss(primary_values, target_values, method="pearson"): + """function use calls gn3 to compute corr,p_val""" + + (corr_coeff, p_val) = compute_corr_coeff_p_value( + primary_values=primary_values, target_values=target_values, corr_method=method) + + return (corr_coeff, len(primary_values), p_val) + +######################################################################################################## +# input: cursor, symbolList (list), dataIdDict(Dict): key is symbol +# output: SymbolValuePairDict(dictionary):one dictionary of Symbol and Value Pair. +# key is symbol, value is one list of expression values of one probeSet. +# function: wrapper function for getSymbolValuePairDict function +# build gene symbol list if necessary, cut it into small lists if necessary, +# then call getSymbolValuePairDict function and merge the results. +######################################################################################################## + + +def get_trait_symbol_and_tissue_values(symbol_list=None): + with database_connection(get_setting("SQL_URI")) as conn: + tissue_data = MrnaAssayTissueData(gene_symbols=symbol_list, conn=conn) + if len(tissue_data.gene_symbols) > 0: + results = tissue_data.get_symbol_values_pairs() + return results diff --git a/gn2/wqflask/correlation/correlation_gn3_api.py b/gn2/wqflask/correlation/correlation_gn3_api.py new file mode 100644 index 00000000..76c75ec3 --- /dev/null +++ b/gn2/wqflask/correlation/correlation_gn3_api.py @@ -0,0 +1,262 @@ +"""module that calls the gn3 api's to do the correlation """ +import json +import time +from functools import wraps + +from gn2.utility.tools import SQL_URI + +from gn2.wqflask.correlation import correlation_functions +from gn2.base import data_set + +from gn2.base.trait import create_trait +from gn2.base.trait import retrieve_sample_data + +from gn3.db_utils import database_connection +from gn3.commands import run_sample_corr_cmd +from gn3.computations.correlations import map_shared_keys_to_values +from gn3.computations.correlations import compute_all_lit_correlation +from gn3.computations.correlations import compute_tissue_correlation +from gn3.computations.correlations import fast_compute_all_sample_correlation + + +def create_target_this_trait(start_vars): + """this function creates the required trait and target dataset for correlation""" + + if start_vars['dataset'] == "Temp": + this_dataset = data_set.create_dataset( + dataset_name="Temp", dataset_type="Temp", group_name=start_vars['group']) + else: + this_dataset = data_set.create_dataset( + dataset_name=start_vars['dataset']) + target_dataset = data_set.create_dataset( + dataset_name=start_vars['corr_dataset']) + this_trait = create_trait(dataset=this_dataset, + name=start_vars['trait_id']) + sample_data = () + return (this_dataset, this_trait, target_dataset, sample_data) + + +def test_process_data(this_trait, dataset, start_vars): + """test function for bxd,all and other sample data""" + + corr_samples_group = start_vars["corr_samples_group"] + + primary_samples = dataset.group.samplelist + if dataset.group.parlist != None: + primary_samples += dataset.group.parlist + if dataset.group.f1list != None: + primary_samples += dataset.group.f1list + + # If either BXD/whatever Only or All Samples, append all of that group's samplelist + if corr_samples_group != 'samples_other': + sample_data = process_samples(start_vars, primary_samples) + + # If either Non-BXD/whatever or All Samples, get all samples from this_trait.data and + # exclude the primary samples (because they would have been added in the previous + # if statement if the user selected All Samples) + if corr_samples_group != 'samples_primary': + if corr_samples_group == 'samples_other': + primary_samples = [x for x in primary_samples if x not in ( + dataset.group.parlist + dataset.group.f1list)] + sample_data = process_samples(start_vars, list( + this_trait.data.keys()), primary_samples) + + return sample_data + + +def process_samples(start_vars, sample_names=[], excluded_samples=[]): + """code to fetch correct samples""" + sample_data = {} + sample_vals_dict = json.loads(start_vars["sample_vals"]) + if sample_names: + for sample in sample_names: + if sample in sample_vals_dict and sample not in excluded_samples: + val = sample_vals_dict[sample] + if not val.strip().lower() == "x": + sample_data[str(sample)] = float(val) + + else: + for sample in sample_vals_dict.keys(): + if sample not in excluded_samples: + val = sample_vals_dict[sample] + if not val.strip().lower() == "x": + sample_data[str(sample)] = float(val) + return sample_data + + +def merge_correlation_results(correlation_results, target_correlation_results): + + corr_dict = {} + + for trait_dict in target_correlation_results: + for trait_name, values in trait_dict.items(): + + corr_dict[trait_name] = values + for trait_dict in correlation_results: + for trait_name, values in trait_dict.items(): + + if corr_dict.get(trait_name): + + trait_dict[trait_name].update(corr_dict.get(trait_name)) + + return correlation_results + + +def sample_for_trait_lists(corr_results, target_dataset, + this_trait, this_dataset, start_vars): + """interface function for correlation on top results""" + + (this_trait_data, target_dataset) = fetch_sample_data( + start_vars, this_trait, this_dataset, target_dataset) + correlation_results = run_sample_corr_cmd( + corr_method="pearson", this_trait=this_trait_data, + target_dataset=target_dataset) + + return correlation_results + + +def tissue_for_trait_lists(corr_results, this_dataset, this_trait): + """interface function for doing tissue corr_results on trait_list""" + trait_lists = dict([(list(corr_result)[0], True) + for corr_result in corr_results]) + # trait_lists = {list(corr_results)[0]: 1 for corr_result in corr_results} + traits_symbol_dict = this_dataset.retrieve_genes("Symbol") + traits_symbol_dict = dict({trait_name: symbol for ( + trait_name, symbol) in traits_symbol_dict.items() if trait_lists.get(trait_name)}) + tissue_input = get_tissue_correlation_input( + this_trait, traits_symbol_dict) + + if tissue_input is not None: + (primary_tissue_data, target_tissue_data) = tissue_input + corr_results = compute_tissue_correlation( + primary_tissue_dict=primary_tissue_data, + target_tissues_data=target_tissue_data, + corr_method="pearson") + return corr_results + + +def lit_for_trait_list(corr_results, this_dataset, this_trait): + (this_trait_geneid, geneid_dict, species) = do_lit_correlation( + this_trait, this_dataset) + + # trait_lists = {list(corr_results)[0]: 1 for corr_result in corr_results} + trait_lists = dict([(list(corr_result)[0], True) + for corr_result in corr_results]) + + geneid_dict = {trait_name: geneid for (trait_name, geneid) in geneid_dict.items() if + trait_lists.get(trait_name)} + + with database_connection(SQL_URI) as conn: + correlation_results = compute_all_lit_correlation( + conn=conn, trait_lists=list(geneid_dict.items()), + species=species, gene_id=this_trait_geneid) + + return correlation_results + + +def fetch_sample_data(start_vars, this_trait, this_dataset, target_dataset): + + corr_samples_group = start_vars["corr_samples_group"] + if corr_samples_group == "samples_primary": + sample_data = process_samples( + start_vars, this_dataset.group.samplelist) + + elif corr_samples_group == "samples_other": + sample_data = process_samples( + start_vars, excluded_samples=this_dataset.group.samplelist) + + else: + sample_data = process_samples(start_vars, + this_dataset.group.all_samples_ordered()) + + target_dataset.get_trait_data(list(sample_data.keys())) + this_trait = retrieve_sample_data(this_trait, this_dataset) + this_trait_data = { + "trait_sample_data": sample_data, + "trait_id": start_vars["trait_id"] + } + results = map_shared_keys_to_values( + target_dataset.samplelist, target_dataset.trait_data) + + return (this_trait_data, results) + + +def compute_correlation(start_vars, method="pearson", compute_all=False): + """Compute correlations using GN3 API + + Keyword arguments: + start_vars -- All input from form; includes things like the trait/dataset names + method -- Correlation method to be used (pearson, spearman, or bicor) + compute_all -- Include sample, tissue, and literature correlations (when applicable) + """ + from gn2.wqflask.correlation.rust_correlation import compute_correlation_rust + + corr_type = start_vars['corr_type'] + method = start_vars['corr_sample_method'] + corr_return_results = int(start_vars.get("corr_return_results", 100)) + return compute_correlation_rust( + start_vars, corr_type, method, corr_return_results, compute_all) + + +def compute_corr_for_top_results(start_vars, + correlation_results, + this_trait, + this_dataset, + target_dataset, + corr_type): + if corr_type != "tissue" and this_dataset.type == "ProbeSet" and target_dataset.type == "ProbeSet": + tissue_result = tissue_for_trait_lists( + correlation_results, this_dataset, this_trait) + + if tissue_result: + correlation_results = merge_correlation_results( + correlation_results, tissue_result) + + if corr_type != "lit" and this_dataset.type == "ProbeSet" and target_dataset.type == "ProbeSet": + lit_result = lit_for_trait_list( + correlation_results, this_dataset, this_trait) + + if lit_result: + correlation_results = merge_correlation_results( + correlation_results, lit_result) + + if corr_type != "sample" and this_dataset.type == "ProbeSet" and target_dataset.type == "ProbeSet": + sample_result = sample_for_trait_lists( + correlation_results, target_dataset, this_trait, this_dataset, start_vars) + if sample_result: + correlation_results = merge_correlation_results( + correlation_results, sample_result) + + return correlation_results + + +def do_lit_correlation(this_trait, this_dataset): + """function for fetching lit inputs""" + geneid_dict = this_dataset.retrieve_genes("GeneId") + species = this_dataset.group.species + if species: + species = species.lower() + trait_geneid = this_trait.geneid + return (trait_geneid, geneid_dict, species) + + +def get_tissue_correlation_input(this_trait, trait_symbol_dict): + """Gets tissue expression values for the primary trait and target tissues values""" + primary_trait_tissue_vals_dict = correlation_functions.get_trait_symbol_and_tissue_values( + symbol_list=[this_trait.symbol]) + if this_trait.symbol and this_trait.symbol.lower() in primary_trait_tissue_vals_dict: + + primary_trait_tissue_values = primary_trait_tissue_vals_dict[this_trait.symbol.lower( + )] + corr_result_tissue_vals_dict = correlation_functions.get_trait_symbol_and_tissue_values( + symbol_list=list(trait_symbol_dict.values())) + primary_tissue_data = { + "this_id": this_trait.name, + "tissue_values": primary_trait_tissue_values + + } + target_tissue_data = { + "trait_symbol_dict": trait_symbol_dict, + "symbol_tissue_vals_dict": corr_result_tissue_vals_dict + } + return (primary_tissue_data, target_tissue_data) diff --git a/gn2/wqflask/correlation/exceptions.py b/gn2/wqflask/correlation/exceptions.py new file mode 100644 index 00000000..f4e2b72b --- /dev/null +++ b/gn2/wqflask/correlation/exceptions.py @@ -0,0 +1,16 @@ +"""Correlation-Specific Exceptions""" + +class WrongCorrelationType(Exception): + """Raised when a correlation is requested for incompatible datasets.""" + + def __init__(self, trait, target_dataset, corr_method): + corr_method = { + "lit": "Literature", + "tissue": "Tissue" + }[corr_method] + message = ( + f"It is not possible to compute the '{corr_method}' correlations " + f"between trait '{trait.name}' and the data in the " + f"'{target_dataset.fullname}' dataset. " + "Please try again after selecting another type of correlation.") + super().__init__(message) diff --git a/gn2/wqflask/correlation/pre_computes.py b/gn2/wqflask/correlation/pre_computes.py new file mode 100644 index 00000000..4bd888ad --- /dev/null +++ b/gn2/wqflask/correlation/pre_computes.py @@ -0,0 +1,178 @@ +import csv +import json +import os +import hashlib +import datetime + +import lmdb +import pickle +from pathlib import Path + +from gn2.base.data_set import query_table_timestamp +from gn2.base.webqtlConfig import TEXTDIR +from gn2.base.webqtlConfig import TMPDIR + +from json.decoder import JSONDecodeError + +def cache_trait_metadata(dataset_name, data): + + + try: + with lmdb.open(os.path.join(TMPDIR,f"metadata_{dataset_name}"),map_size=20971520) as env: + with env.begin(write=True) as txn: + data_bytes = pickle.dumps(data) + txn.put(f"{dataset_name}".encode(), data_bytes) + current_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + txn.put(b"creation_date", current_date.encode()) + return "success" + + except lmdb.Error as error: + pass + +def read_trait_metadata(dataset_name): + try: + with lmdb.open(os.path.join(TMPDIR,f"metadata_{dataset_name}"), + readonly=True, lock=False) as env: + with env.begin() as txn: + db_name = txn.get(dataset_name.encode()) + return (pickle.loads(db_name) if db_name else {}) + except lmdb.Error as error: + return {} + + +def fetch_all_cached_metadata(dataset_name): + """in a gvein dataset fetch all the traits metadata""" + file_name = generate_filename(dataset_name, suffix="metadata") + + file_path = Path(TMPDIR, file_name) + + try: + with open(file_path, "r+") as file_handler: + dataset_metadata = json.load(file_handler) + + return (file_path, dataset_metadata) + + except FileNotFoundError: + pass + + except JSONDecodeError: + file_path.unlink() + + file_path.touch(exist_ok=True) + + return (file_path, {}) + + +def cache_new_traits_metadata(dataset_metadata: dict, new_traits_metadata, file_path: str): + """function to cache the new traits metadata""" + + if (dataset_metadata == {} and new_traits_metadata == {}): + return + + dataset_metadata.update(new_traits_metadata) + + with open(file_path, "w+") as file_handler: + json.dump(dataset_metadata, file_handler) + + +def generate_filename(*args, suffix="", file_ext="json"): + """given a list of args generate a unique filename""" + + string_unicode = f"{*args,}".encode() + return f"{hashlib.md5(string_unicode).hexdigest()}_{suffix}.{file_ext}" + + + + +def fetch_text_file(dataset_name, conn, text_dir=TMPDIR): + """fetch textfiles with strain vals if exists""" + + def __file_scanner__(text_dir, target_file): + for file in os.listdir(text_dir): + if file.startswith(f"ProbeSetFreezeId_{target_file}_"): + return os.path.join(text_dir, file) + + with conn.cursor() as cursor: + cursor.execute( + 'SELECT Id, FullName FROM ProbeSetFreeze WHERE Name = %s', (dataset_name,)) + results = cursor.fetchone() + if results: + try: + # checks first for recently generated textfiles if not use gn1 datamatrix + + return __file_scanner__(text_dir, results[0]) or __file_scanner__(TEXTDIR, results[0]) + + except Exception: + pass + + +def read_text_file(sample_dict, file_path): + + def __fetch_id_positions__(all_ids, target_ids): + _vals = [] + _posit = [0] # alternative for parsing + + for (idx, strain) in enumerate(all_ids, 1): + if strain in target_ids: + _vals.append(target_ids[strain]) + _posit.append(idx) + + return (_posit, _vals) + + with open(file_path) as csv_file: + csv_reader = csv.reader(csv_file, delimiter=',') + _posit, sample_vals = __fetch_id_positions__( + next(csv_reader)[1:], sample_dict) + return (sample_vals, [[line[i] for i in _posit] for line in csv_reader]) + + +def write_db_to_textfile(db_name, conn, text_dir=TMPDIR): + + def __sanitise_filename__(filename): + ttable = str.maketrans({" ": "_", "/": "_", "\\": "_"}) + return str.translate(filename, ttable) + + def __generate_file_name__(db_name): + # todo add expiry time and checker + with conn.cursor() as cursor: + cursor.execute( + 'SELECT Id, FullName FROM ProbeSetFreeze WHERE Name = %s', (db_name,)) + results = cursor.fetchone() + if (results): + return __sanitise_filename__( + f"ProbeSetFreezeId_{results[0]}_{results[1]}") + + def __parse_to_dict__(results): + ids = ["ID"] + data = {} + for (trait, strain, val) in results: + if strain not in ids: + ids.append(strain) + if trait in data: + data[trait].append(val) + else: + data[trait] = [trait, val] + return (data, ids) + + def __write_to_file__(file_path, data, col_names): + with open(file_path, 'w+', encoding='UTF8') as file_handler: + writer = csv.writer(file_handler) + writer.writerow(col_names) + writer.writerows(data.values()) + + with conn.cursor() as cursor: + cursor.execute( + "SELECT ProbeSet.Name, Strain.Name, ProbeSetData.value " + "FROM Strain LEFT JOIN ProbeSetData " + "ON Strain.Id = ProbeSetData.StrainId " + "LEFT JOIN ProbeSetXRef ON ProbeSetData.Id = ProbeSetXRef.DataId " + "LEFT JOIN ProbeSet ON ProbeSetXRef.ProbeSetId = ProbeSet.Id " + "WHERE ProbeSetXRef.ProbeSetFreezeId IN " + "(SELECT Id FROM ProbeSetFreeze WHERE Name = %s) " + "ORDER BY Strain.Name", + (db_name,)) + results = cursor.fetchall() + file_name = __generate_file_name__(db_name) + if (results and file_name): + __write_to_file__(os.path.join(text_dir, file_name), + *__parse_to_dict__(results)) diff --git a/gn2/wqflask/correlation/rust_correlation.py b/gn2/wqflask/correlation/rust_correlation.py new file mode 100644 index 00000000..a0dcbcb4 --- /dev/null +++ b/gn2/wqflask/correlation/rust_correlation.py @@ -0,0 +1,408 @@ +"""module contains integration code for rust-gn3""" +import json +from functools import reduce + +from gn2.utility.tools import SQL_URI +from gn2.utility.db_tools import mescape +from gn2.utility.db_tools import create_in_clause +from gn2.wqflask.correlation.correlation_functions\ + import get_trait_symbol_and_tissue_values +from gn2.wqflask.correlation.correlation_gn3_api import create_target_this_trait +from gn2.wqflask.correlation.correlation_gn3_api import lit_for_trait_list +from gn2.wqflask.correlation.correlation_gn3_api import do_lit_correlation +from gn2.wqflask.correlation.pre_computes import fetch_text_file +from gn2.wqflask.correlation.pre_computes import read_text_file +from gn2.wqflask.correlation.pre_computes import write_db_to_textfile +from gn2.wqflask.correlation.pre_computes import read_trait_metadata +from gn2.wqflask.correlation.pre_computes import cache_trait_metadata +from gn3.computations.correlations import compute_all_lit_correlation +from gn3.computations.rust_correlation import run_correlation +from gn3.computations.rust_correlation import get_sample_corr_data +from gn3.computations.rust_correlation import parse_tissue_corr_data +from gn3.db_utils import database_connection + +from gn2.wqflask.correlation.exceptions import WrongCorrelationType + + +def query_probes_metadata(dataset, trait_list): + """query traits metadata in bulk for probeset""" + + if not bool(trait_list) or dataset.type != "ProbeSet": + return [] + + with database_connection(SQL_URI) as conn: + with conn.cursor() as cursor: + + query = """ + SELECT ProbeSet.Name,ProbeSet.Chr,ProbeSet.Mb, + ProbeSet.Symbol,ProbeSetXRef.mean, + CONCAT_WS('; ', ProbeSet.description, ProbeSet.Probe_Target_Description) AS description, + ProbeSetXRef.additive,ProbeSetXRef.LRS,Geno.Chr, Geno.Mb + FROM ProbeSet INNER JOIN ProbeSetXRef + ON ProbeSet.Id=ProbeSetXRef.ProbeSetId + INNER JOIN Geno + ON ProbeSetXRef.Locus = Geno.Name + INNER JOIN Species + ON Geno.SpeciesId = Species.Id + WHERE ProbeSet.Name in ({}) AND + Species.Name = %s AND + ProbeSetXRef.ProbeSetFreezeId IN ( + SELECT ProbeSetFreeze.Id + FROM ProbeSetFreeze WHERE ProbeSetFreeze.Name = %s) + """.format(", ".join(["%s"] * len(trait_list))) + + cursor.execute(query, + (tuple(trait_list) + + (dataset.group.species,) + (dataset.name,)) + ) + + return cursor.fetchall() + + +def get_metadata(dataset, traits): + """Retrieve the metadata""" + def __location__(probe_chr, probe_mb): + if probe_mb: + return f"Chr{probe_chr}: {probe_mb:.6f}" + return f"Chr{probe_chr}: ???" + cached_metadata = read_trait_metadata(dataset.name) + to_fetch_metadata = list( + set(traits).difference(list(cached_metadata.keys()))) + if to_fetch_metadata: + results = {**({trait_name: { + "name": trait_name, + "view": True, + "symbol": symbol, + "dataset": dataset.name, + "dataset_name": dataset.shortname, + "mean": mean, + "description": description, + "additive": additive, + "lrs_score": f"{lrs:3.1f}" if lrs else "", + "location": __location__(probe_chr, probe_mb), + "chr": probe_chr, + "mb": probe_mb, + "lrs_location": f'Chr{chr_score}: {mb:{".6f" if mb else ""}}', + "lrs_chr": chr_score, + "lrs_mb": mb + + } for trait_name, probe_chr, probe_mb, symbol, mean, description, + additive, lrs, chr_score, mb + in query_probes_metadata(dataset, to_fetch_metadata)}), **cached_metadata} + cache_trait_metadata(dataset.name, results) + return results + return cached_metadata + + +def chunk_dataset(dataset, steps, name): + + results = [] + + query = """ + SELECT ProbeSetXRef.DataId,ProbeSet.Name + FROM ProbeSet, ProbeSetXRef, ProbeSetFreeze + WHERE ProbeSetFreeze.Name = '{}' AND + ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id AND + ProbeSetXRef.ProbeSetId = ProbeSet.Id + """.format(name) + + with database_connection(SQL_URI) as conn: + with conn.cursor() as curr: + curr.execute(query) + traits_name_dict = dict(curr.fetchall()) + + for i in range(0, len(dataset), steps): + matrix = list(dataset[i:i + steps]) + results.append([traits_name_dict[matrix[0][0]]] + [str(value) + for (trait_name, strain, value) in matrix]) + return results + + +def compute_top_n_sample(start_vars, dataset, trait_list): + """check if dataset is of type probeset""" + + if dataset.type.lower() != "probeset": + return {} + + def __fetch_sample_ids__(samples_vals, samples_group): + sample_data = get_sample_corr_data( + sample_type=samples_group, + sample_data=json.loads(samples_vals), + dataset_samples=dataset.group.all_samples_ordered()) + + with database_connection(SQL_URI) as conn: + with conn.cursor() as curr: + curr.execute( + """ + SELECT Strain.Name, Strain.Id FROM Strain, Species + WHERE Strain.Name IN {} + and Strain.SpeciesId=Species.Id + and Species.name = '{}' + """.format(create_in_clause(list(sample_data.keys())), + *mescape(dataset.group.species))) + return (sample_data, dict(curr.fetchall())) + + (sample_data, sample_ids) = __fetch_sample_ids__( + start_vars["sample_vals"], start_vars["corr_samples_group"]) + + if len(trait_list) == 0: + return {} + + with database_connection(SQL_URI) as conn: + with conn.cursor() as curr: + # fetching strain data in bulk + query = ( + "SELECT * from ProbeSetData " + f"WHERE StrainID IN ({', '.join(['%s'] * len(sample_ids))}) " + "AND Id IN (" + " SELECT ProbeSetXRef.DataId " + " FROM (ProbeSet, ProbeSetXRef, ProbeSetFreeze) " + " WHERE ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id " + " AND ProbeSetFreeze.Name = %s " + " AND ProbeSet.Name " + f" IN ({', '.join(['%s'] * len(trait_list))}) " + " AND ProbeSet.Id = ProbeSetXRef.ProbeSetId" + ")") + curr.execute( + query, + tuple(sample_ids.values()) + (dataset.name,) + tuple(trait_list)) + + corr_data = chunk_dataset( + list(curr.fetchall()), len(sample_ids.values()), dataset.name) + + return run_correlation( + corr_data, list(sample_data.values()), "pearson", ",") + + +def compute_top_n_lit(corr_results, target_dataset, this_trait) -> dict: + if not __datasets_compatible_p__(this_trait.dataset, target_dataset, "lit"): + return {} + + (this_trait_geneid, geneid_dict, species) = do_lit_correlation( + this_trait, target_dataset) + + geneid_dict = {trait_name: geneid for (trait_name, geneid) + in geneid_dict.items() if + corr_results.get(trait_name)} + with database_connection(SQL_URI) as conn: + return reduce( + lambda acc, corr: {**acc, **corr}, + compute_all_lit_correlation( + conn=conn, trait_lists=list(geneid_dict.items()), + species=species, gene_id=this_trait_geneid), + {}) + + return {} + + +def compute_top_n_tissue(target_dataset, this_trait, traits, method): + # refactor lots of rpt + if not __datasets_compatible_p__(this_trait.dataset, target_dataset, "tissue"): + return {} + + trait_symbol_dict = dict({ + trait_name: symbol + for (trait_name, symbol) + in target_dataset.retrieve_genes("Symbol").items() + if traits.get(trait_name)}) + + corr_result_tissue_vals_dict = get_trait_symbol_and_tissue_values( + symbol_list=list(trait_symbol_dict.values())) + + data = parse_tissue_corr_data(symbol_name=this_trait.symbol, + symbol_dict=get_trait_symbol_and_tissue_values( + symbol_list=[this_trait.symbol]), + dataset_symbols=trait_symbol_dict, + dataset_vals=corr_result_tissue_vals_dict) + + if data and data[0]: + return run_correlation( + data[1], data[0], method, ",", "tissue") + + return {} + + +def merge_results(dict_a: dict, dict_b: dict, dict_c: dict) -> list[dict]: + """code to merge diff corr into individual dicts + a""" + + def __merge__(trait_name, trait_corrs): + return { + trait_name: { + **trait_corrs, + **dict_b.get(trait_name, {}), + **dict_c.get(trait_name, {}) + } + } + return [__merge__(tname, tcorrs) for tname, tcorrs in dict_a.items()] + + +def __compute_sample_corr__( + start_vars: dict, corr_type: str, method: str, n_top: int, + target_trait_info: tuple): + """Compute the sample correlations""" + (this_dataset, this_trait, target_dataset, sample_data) = target_trait_info + + if this_dataset.group.f1list != None: + this_dataset.group.samplelist += this_dataset.group.f1list + + if this_dataset.group.parlist != None: + this_dataset.group.samplelist += this_dataset.group.parlist + + sample_data = get_sample_corr_data( + sample_type=start_vars["corr_samples_group"], + sample_data=json.loads(start_vars["sample_vals"]), + dataset_samples=this_dataset.group.all_samples_ordered()) + + if not bool(sample_data): + return {} + + if target_dataset.type == "ProbeSet" and start_vars.get("use_cache") == "true": + with database_connection(SQL_URI) as conn: + file_path = fetch_text_file(target_dataset.name, conn) + if file_path: + (sample_vals, target_data) = read_text_file( + sample_data, file_path) + + return run_correlation(target_data, sample_vals, + method, ",", corr_type, n_top) + + write_db_to_textfile(target_dataset.name, conn) + file_path = fetch_text_file(target_dataset.name, conn) + if file_path: + (sample_vals, target_data) = read_text_file( + sample_data, file_path) + + return run_correlation(target_data, sample_vals, + method, ",", corr_type, n_top) + + target_dataset.get_trait_data(list(sample_data.keys())) + + def __merge_key_and_values__(rows, current): + wo_nones = [value for value in current[1]] + if len(wo_nones) > 0: + return rows + [[current[0]] + wo_nones] + return rows + + target_data = reduce( + __merge_key_and_values__, target_dataset.trait_data.items(), []) + + if len(target_data) == 0: + return {} + + return run_correlation( + target_data, list(sample_data.values()), method, ",", corr_type, + n_top) + + +def __datasets_compatible_p__(trait_dataset, target_dataset, corr_method): + return not ( + corr_method in ("tissue", "Tissue r", "Literature r", "lit") + and (trait_dataset.type == "ProbeSet" and + target_dataset.type in ("Publish", "Geno"))) + + +def __compute_tissue_corr__( + start_vars: dict, corr_type: str, method: str, n_top: int, + target_trait_info: tuple): + """Compute the tissue correlations""" + (this_dataset, this_trait, target_dataset, sample_data) = target_trait_info + if not __datasets_compatible_p__(this_dataset, target_dataset, corr_type): + raise WrongCorrelationType(this_trait, target_dataset, corr_type) + + trait_symbol_dict = target_dataset.retrieve_genes("Symbol") + corr_result_tissue_vals_dict = get_trait_symbol_and_tissue_values( + symbol_list=list(trait_symbol_dict.values())) + + data = parse_tissue_corr_data( + symbol_name=this_trait.symbol, + symbol_dict=get_trait_symbol_and_tissue_values( + symbol_list=[this_trait.symbol]), + dataset_symbols=trait_symbol_dict, + dataset_vals=corr_result_tissue_vals_dict) + + if data: + return run_correlation(data[1], data[0], method, ",", "tissue") + return {} + + +def __compute_lit_corr__( + start_vars: dict, corr_type: str, method: str, n_top: int, + target_trait_info: tuple): + """Compute the literature correlations""" + (this_dataset, this_trait, target_dataset, sample_data) = target_trait_info + if not __datasets_compatible_p__(this_dataset, target_dataset, corr_type): + raise WrongCorrelationType(this_trait, target_dataset, corr_type) + + target_dataset_type = target_dataset.type + this_dataset_type = this_dataset.type + (this_trait_geneid, geneid_dict, species) = do_lit_correlation( + this_trait, target_dataset) + + with database_connection(SQL_URI) as conn: + return reduce( + lambda acc, lit: {**acc, **lit}, + compute_all_lit_correlation( + conn=conn, trait_lists=list(geneid_dict.items()), + species=species, gene_id=this_trait_geneid)[:n_top], + {}) + return {} + + +def compute_correlation_rust( + start_vars: dict, corr_type: str, method: str = "pearson", + n_top: int = 500, should_compute_all: bool = False): + """function to compute correlation""" + target_trait_info = create_target_this_trait(start_vars) + (this_dataset, this_trait, target_dataset, sample_data) = ( + target_trait_info) + if not __datasets_compatible_p__(this_dataset, target_dataset, corr_type): + raise WrongCorrelationType(this_trait, target_dataset, corr_type) + + # Replace this with `match ...` once we hit Python 3.10 + corr_type_fns = { + "sample": __compute_sample_corr__, + "tissue": __compute_tissue_corr__, + "lit": __compute_lit_corr__ + } + + results = corr_type_fns[corr_type]( + start_vars, corr_type, method, n_top, target_trait_info) + + # END: Replace this with `match ...` once we hit Python 3.10 + + top_a = top_b = {} + + if should_compute_all: + + if corr_type == "sample": + if this_dataset.type == "ProbeSet": + top_a = compute_top_n_tissue( + target_dataset, this_trait, results, method) + + top_b = compute_top_n_lit(results, target_dataset, this_trait) + else: + pass + + elif corr_type == "lit": + + # currently fails for lit + + top_a = compute_top_n_sample( + start_vars, target_dataset, list(results.keys())) + top_b = compute_top_n_tissue( + target_dataset, this_trait, results, method) + + else: + + top_a = compute_top_n_sample( + start_vars, target_dataset, list(results.keys())) + + return { + "correlation_results": merge_results( + results, top_a, top_b), + "this_trait": this_trait.name, + "target_dataset": start_vars['corr_dataset'], + "traits_metadata": get_metadata(target_dataset, list(results.keys())), + "return_results": n_top + } diff --git a/gn2/wqflask/correlation/show_corr_results.py b/gn2/wqflask/correlation/show_corr_results.py new file mode 100644 index 00000000..c8625222 --- /dev/null +++ b/gn2/wqflask/correlation/show_corr_results.py @@ -0,0 +1,406 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Dr. Robert W. Williams at rwilliams@uthsc.edu +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) + +import hashlib +import html +import json + +from gn2.base.trait import create_trait, jsonable +from gn2.base.data_set import create_dataset + +from gn2.utility import hmac +from gn2.utility.type_checking import get_float, get_int, get_string +from gn2.utility.redis_tools import get_redis_conn +Redis = get_redis_conn() + +def set_template_vars(start_vars, correlation_data): + corr_type = start_vars['corr_type'] + corr_method = start_vars['corr_sample_method'] + + if start_vars['dataset'] == "Temp": + this_dataset_ob = create_dataset( + dataset_name="Temp", dataset_type="Temp", group_name=start_vars['group']) + else: + this_dataset_ob = create_dataset(dataset_name=start_vars['dataset']) + this_trait = create_trait(dataset=this_dataset_ob, + name=start_vars['trait_id']) + + # Store trait sample data in Redis, so additive effect scatterplots can include edited values + dhash = hashlib.md5() + dhash.update(start_vars['sample_vals'].encode()) + samples_hash = dhash.hexdigest() + Redis.set(samples_hash, start_vars['sample_vals'], ex=7*24*60*60) + correlation_data['dataid'] = samples_hash + + correlation_data['this_trait'] = jsonable(this_trait, this_dataset_ob) + correlation_data['this_dataset'] = this_dataset_ob.as_monadic_dict().data + + target_dataset_ob = create_dataset(correlation_data['target_dataset']) + correlation_data['target_dataset'] = target_dataset_ob.as_monadic_dict().data + correlation_data['table_json'] = correlation_json_for_table( + start_vars, + correlation_data, + target_dataset_ob) + + if target_dataset_ob.type == "ProbeSet": + filter_cols = [7, 6] + elif target_dataset_ob.type == "Publish": + filter_cols = [8, 5] + else: + filter_cols = [4, 0] + + correlation_data['corr_method'] = corr_method + correlation_data['filter_cols'] = filter_cols + correlation_data['header_fields'] = get_header_fields( + target_dataset_ob.type, correlation_data['corr_method']) + correlation_data['formatted_corr_type'] = get_formatted_corr_type( + corr_type, corr_method) + + return correlation_data + + +def apply_filters(trait, target_trait, target_dataset, **filters): + def __p_val_filter__(p_lower, p_upper): + + return not (p_lower <= float(trait.get("corr_coefficient",0.0)) <= p_upper) + + def __min_filter__(min_expr): + if (target_dataset['type'] in ["ProbeSet", "Publish"] and target_trait['mean']): + return (min_expr != None) and (float(target_trait['mean']) < min_expr) + + return False + + def __location_filter__(location_type, location_chr, + min_location_mb, max_location_mb): + + if target_dataset["type"] in ["ProbeSet", "Geno"] and location_type == "gene": + return ( + ((location_chr!=None) and (target_trait["chr"]!=location_chr)) + or + ((min_location_mb!= None) and ( + float(target_trait['mb']) < min_location_mb) + ) + + or + ((max_location_mb != None) and + (float(target_trait['mb']) > float(max_location_mb) + )) + + ) + elif target_dataset["type"] in ["ProbeSet", "Publish"]: + + return ((location_chr!=None) and (target_trait["lrs_chr"] != location_chr) + or + ((min_location_mb != None) and ( + float(target_trait['lrs_mb']) < float(min_location_mb))) + or + ((max_location_mb != None) and ( + float(target_trait['lrs_mb']) > float(max_location_mb)) + ) + + ) + + return True + + if not target_trait: + return True + else: + # check if one of the condition is not met i.e One is True + return (__p_val_filter__( + filters.get("p_range_lower"), + filters.get("p_range_upper") + ) + or + ( + __min_filter__( + filters.get("min_expr") + ) + ) + or + __location_filter__( + filters.get("location_type"), + filters.get("location_chr"), + filters.get("min_location_mb"), + filters.get("max_location_mb") + + + ) + ) + + +def get_user_filters(start_vars): + (min_expr, p_min, p_max) = ( + get_float(start_vars, 'min_expr'), + get_float(start_vars, 'p_range_lower', -1.0), + get_float(start_vars, 'p_range_upper', 1.0) + ) + + if all(keys in start_vars for keys in ["loc_chr", + "min_loc_mb", + "max_location_mb"]): + + location_chr = get_string(start_vars, "loc_chr") + min_location_mb = get_int(start_vars, "min_loc_mb") + max_location_mb = get_int(start_vars, "max_loc_mb") + + else: + location_chr = min_location_mb = max_location_mb = None + + return { + + "min_expr": min_expr, + "p_range_lower": p_min, + "p_range_upper": p_max, + "location_chr": location_chr, + "location_type": start_vars['location_type'], + "min_location_mb": min_location_mb, + "max_location_mb": max_location_mb + + } + + +def generate_table_metadata(all_traits, dataset_metadata, dataset_obj): + + def __fetch_trait_data__(trait, dataset_obj): + target_trait_ob = create_trait(dataset=dataset_obj, + name=trait, + get_qtl_info=True) + return jsonable(target_trait_ob, dataset_obj) + + metadata = [__fetch_trait_data__(trait, dataset_obj) for + trait in (all_traits)] + + return (dataset_metadata | ({str(trait["name"]): trait for trait in metadata})) + + +def populate_table(dataset_metadata, target_dataset, this_dataset, corr_results, filters): + + def __populate_trait__(idx, trait): + + trait_name = list(trait.keys())[0] + target_trait = dataset_metadata.get(trait_name) + trait = trait[trait_name] + if not apply_filters(trait, target_trait, target_dataset, **filters): + results_dict = {} + results_dict['index'] = idx + 1 # + results_dict['trait_id'] = target_trait['name'] + results_dict['dataset'] = target_dataset['name'] + results_dict['hmac'] = hmac.data_hmac( + '{}:{}'.format(target_trait['name'], target_dataset['name'])) + results_dict['sample_r'] = f"{float(trait.get('corr_coefficient',0.0)):.3f}" + results_dict['num_overlap'] = trait.get('num_overlap', 0) + results_dict['sample_p'] = f"{float(trait.get('p_value',0)):.2e}" + if target_dataset['type'] == "ProbeSet": + results_dict['symbol'] = target_trait['symbol'] + results_dict['description'] = "N/A" + results_dict['location'] = target_trait['location'] + results_dict['mean'] = "N/A" + results_dict['additive'] = "N/A" + if target_trait['description'].strip(): + results_dict['description'] = html.escape( + target_trait['description'].strip(), quote=True) + if target_trait['mean']: + results_dict['mean'] = f"{float(target_trait['mean']):.3f}" + try: + results_dict['lod_score'] = f"{float(target_trait['lrs_score']) / 4.61:.1f}" + except: + results_dict['lod_score'] = "N/A" + results_dict['lrs_location'] = target_trait['lrs_location'] + if target_trait['additive']: + results_dict['additive'] = f"{float(target_trait['additive']):.3f}" + results_dict['lit_corr'] = "--" + results_dict['tissue_corr'] = "--" + results_dict['tissue_pvalue'] = "--" + if this_dataset['type'] == "ProbeSet": + if 'lit_corr' in trait: + results_dict['lit_corr'] = ( + f"{float(trait['lit_corr']):.3f}" + if trait["lit_corr"] else "--") + if 'tissue_corr' in trait: + results_dict['tissue_corr'] = f"{float(trait['tissue_corr']):.3f}" + results_dict['tissue_pvalue'] = f"{float(trait['tissue_p_val']):.3e}" + elif target_dataset['type'] == "Publish": + results_dict['abbreviation_display'] = "N/A" + results_dict['description'] = "N/A" + results_dict['mean'] = "N/A" + results_dict['authors_display'] = "N/A" + results_dict['additive'] = "N/A" + results_dict['pubmed_link'] = "N/A" + results_dict['pubmed_text'] = target_trait["pubmed_text"] + + if target_trait["abbreviation"]: + results_dict['abbreviation'] = target_trait['abbreviation'] + + if target_trait["description"].strip(): + results_dict['description'] = html.escape( + target_trait['description'].strip(), quote=True) + + if target_trait["mean"] != "N/A": + results_dict['mean'] = f"{float(target_trait['mean']):.3f}" + + results_dict['lrs_location'] = target_trait['lrs_location'] + + if target_trait["authors"]: + authors_list = target_trait['authors'].split(',') + results_dict['authors_display'] = ", ".join( + authors_list[:6]) + ", et al." if len(authors_list) > 6 else target_trait['authors'] + + if "pubmed_id" in target_trait: + results_dict['pubmed_link'] = target_trait['pubmed_link'] + results_dict['pubmed_text'] = target_trait['pubmed_text'] + try: + results_dict["lod_score"] = f"{float(target_trait['lrs_score']) / 4.61:.1f}" + except ValueError: + results_dict['lod_score'] = "N/A" + else: + results_dict['location'] = target_trait['location'] + + return results_dict + + return [__populate_trait__(idx, trait) + for (idx, trait) in enumerate(corr_results)] + + +def correlation_json_for_table(start_vars, correlation_data, target_dataset_ob): + """Return JSON data for use with the DataTable in the correlation result page + + Keyword arguments: + correlation_data -- Correlation results + this_trait -- Trait being correlated against a dataset, as a dict + this_dataset -- Dataset of this_trait, as a monadic dict + target_dataset_ob - Target dataset, as a Dataset ob + """ + this_dataset = correlation_data['this_dataset'] + + traits = set() + for trait in correlation_data["correlation_results"]: + traits.add(list(trait)[0]) + + dataset_metadata = generate_table_metadata(traits, + correlation_data["traits_metadata"], + target_dataset_ob) + return json.dumps([result for result in ( + populate_table(dataset_metadata=dataset_metadata, + target_dataset=target_dataset_ob.as_monadic_dict().data, + this_dataset=correlation_data['this_dataset'], + corr_results=correlation_data['correlation_results'], + filters=get_user_filters(start_vars))) if result]) + + +def get_formatted_corr_type(corr_type, corr_method): + formatted_corr_type = "" + if corr_type == "lit": + formatted_corr_type += "Literature Correlation " + elif corr_type == "tissue": + formatted_corr_type += "Tissue Correlation " + elif corr_type == "sample": + formatted_corr_type += "Genetic Correlation " + + if corr_method == "pearson": + formatted_corr_type += "(Pearson's r)" + elif corr_method == "spearman": + formatted_corr_type += "(Spearman's rho)" + elif corr_method == "bicor": + formatted_corr_type += "(Biweight r)" + + return formatted_corr_type + + +def get_header_fields(data_type, corr_method): + if data_type == "ProbeSet": + if corr_method == "spearman": + header_fields = ['Index', + 'Record', + 'Symbol', + 'Description', + 'Location', + 'Mean', + 'Sample rho', + 'N', + 'Sample p(rho)', + 'Lit rho', + 'Tissue rho', + 'Tissue p(rho)', + 'Max LRS', + 'Max LRS Location', + 'Additive Effect'] + else: + header_fields = ['Index', + 'Record', + 'Symbol', + 'Description', + 'Location', + 'Mean', + 'Sample r', + 'N', + 'Sample p(r)', + 'Lit r', + 'Tissue r', + 'Tissue p(r)', + 'Max LRS', + 'Max LRS Location', + 'Additive Effect'] + elif data_type == "Publish": + if corr_method == "spearman": + header_fields = ['Index', + 'Record', + 'Abbreviation', + 'Description', + 'Mean', + 'Authors', + 'Year', + 'Sample rho', + 'N', + 'Sample p(rho)', + 'Max LRS', + 'Max LRS Location', + 'Additive Effect'] + else: + header_fields = ['Index', + 'Record', + 'Abbreviation', + 'Description', + 'Mean', + 'Authors', + 'Year', + 'Sample r', + 'N', + 'Sample p(r)', + 'Max LRS', + 'Max LRS Location', + 'Additive Effect'] + + else: + if corr_method == "spearman": + header_fields = ['Index', + 'ID', + 'Location', + 'Sample rho', + 'N', + 'Sample p(rho)'] + else: + header_fields = ['Index', + 'ID', + 'Location', + 'Sample r', + 'N', + 'Sample p(r)'] + + return header_fields diff --git a/gn2/wqflask/correlation_matrix/__init__.py b/gn2/wqflask/correlation_matrix/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/correlation_matrix/__init__.py diff --git a/gn2/wqflask/correlation_matrix/show_corr_matrix.py b/gn2/wqflask/correlation_matrix/show_corr_matrix.py new file mode 100644 index 00000000..f7eb0b4c --- /dev/null +++ b/gn2/wqflask/correlation_matrix/show_corr_matrix.py @@ -0,0 +1,259 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Dr. Robert W. Williams at rwilliams@uthsc.edu +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) + +import datetime +import random +import string +import numpy as np +import scipy + +from gn2.base.data_set import create_dataset +from gn2.base.webqtlConfig import GENERATED_TEXT_DIR + + +from gn2.utility.helper_functions import get_trait_db_obs +from gn2.utility.corr_result_helpers import normalize_values +from gn2.utility.redis_tools import get_redis_conn + + +from gn3.computations.pca import compute_pca +from gn3.computations.pca import process_factor_loadings_tdata +from gn3.computations.pca import generate_pca_temp_traits +from gn3.computations.pca import cache_pca_dataset +from gn3.computations.pca import generate_scree_plot_data + + +class CorrelationMatrix: + + def __init__(self, start_vars): + trait_db_list = [trait.strip() + for trait in start_vars['trait_list'].split(',')] + + get_trait_db_obs(self, trait_db_list) + + self.all_sample_list = [] + self.traits = [] + self.do_PCA = True + # ZS: Getting initial group name before verifying all traits are in the same group in the following loop + this_group = self.trait_list[0][1].group.name + for trait_db in self.trait_list: + this_group = trait_db[1].group.name + this_trait = trait_db[0] + self.traits.append(this_trait) + this_sample_data = this_trait.data + + for sample in this_sample_data: + if sample not in self.all_sample_list: + self.all_sample_list.append(sample) + + self.sample_data = [] + for trait_db in self.trait_list: + this_trait = trait_db[0] + this_sample_data = this_trait.data + + this_trait_vals = [] + for sample in self.all_sample_list: + if sample in this_sample_data: + this_trait_vals.append(this_sample_data[sample].value) + else: + this_trait_vals.append('') + self.sample_data.append(this_trait_vals) + + # Shouldn't do PCA if there are more traits than observations/samples + if len(this_trait_vals) < len(self.trait_list) or len(self.trait_list) < 3: + self.do_PCA = False + + # ZS: Variable set to the lowest overlapping samples in order to notify user, or 8, whichever is lower (since 8 is when we want to display warning) + self.lowest_overlap = 8 + + self.corr_results = [] + self.pca_corr_results = [] + self.scree_data = [] + self.shared_samples_list = self.all_sample_list + for trait_db in self.trait_list: + this_trait = trait_db[0] + this_db = trait_db[1] + + this_db_samples = this_db.group.all_samples_ordered() + this_sample_data = this_trait.data + + corr_result_row = [] + pca_corr_result_row = [] + is_spearman = False # ZS: To determine if it's above or below the diagonal + for target in self.trait_list: + target_trait = target[0] + target_db = target[1] + target_samples = target_db.group.all_samples_ordered() + target_sample_data = target_trait.data + + this_trait_vals = [] + target_vals = [] + for index, sample in enumerate(target_samples): + if (sample in this_sample_data) and (sample in target_sample_data): + sample_value = this_sample_data[sample].value + target_sample_value = target_sample_data[sample].value + this_trait_vals.append(sample_value) + target_vals.append(target_sample_value) + else: + if sample in self.shared_samples_list: + self.shared_samples_list.remove(sample) + + this_trait_vals, target_vals, num_overlap = normalize_values( + this_trait_vals, target_vals) + + if num_overlap < self.lowest_overlap: + self.lowest_overlap = num_overlap + if num_overlap < 2: + corr_result_row.append([target_trait, 0, num_overlap]) + pca_corr_result_row.append(0) + else: + pearson_r, pearson_p = scipy.stats.pearsonr( + this_trait_vals, target_vals) + if is_spearman == False: + sample_r, sample_p = pearson_r, pearson_p + if sample_r > 0.999: + is_spearman = True + else: + sample_r, sample_p = scipy.stats.spearmanr( + this_trait_vals, target_vals) + + corr_result_row.append( + [target_trait, sample_r, num_overlap]) + pca_corr_result_row.append(pearson_r) + + self.corr_results.append(corr_result_row) + self.pca_corr_results.append(pca_corr_result_row) + + self.export_filename, self.export_filepath = export_corr_matrix( + self.corr_results) + + self.trait_data_array = [] + for trait_db in self.trait_list: + this_trait = trait_db[0] + this_db = trait_db[1] + this_db_samples = this_db.group.all_samples_ordered() + this_sample_data = this_trait.data + + this_trait_vals = [] + for index, sample in enumerate(this_db_samples): + if (sample in this_sample_data) and (sample in self.shared_samples_list): + sample_value = this_sample_data[sample].value + this_trait_vals.append(sample_value) + self.trait_data_array.append(this_trait_vals) + + groups = [] + for sample in self.all_sample_list: + groups.append(1) + + self.pca_works = "False" + try: + + if self.do_PCA: + self.pca_works = "True" + self.pca_trait_ids = [] + pca = self.calculate_pca() + self.loadings_array = process_factor_loadings_tdata( + factor_loadings=self.loadings, traits_num=len(self.trait_list)) + + else: + self.pca_works = "False" + except: + self.pca_works = "False" + + self.js_data = dict(traits=[trait.name for trait in self.traits], + groups=groups, + scree_data = self.scree_data, + cols=list(range(len(self.traits))), + rows=list(range(len(self.traits))), + samples=self.all_sample_list, + sample_data=self.sample_data,) + + def calculate_pca(self): + + pca = compute_pca(self.pca_corr_results) + + self.loadings = pca["components"] + self.scores = pca["scores"] + self.pca_obj = pca["pca"] + + this_group_name = self.trait_list[0][1].group.name + temp_dataset = create_dataset( + dataset_name="Temp", dataset_type="Temp", + group_name=this_group_name) + temp_dataset.group.get_samplelist(redis_conn=get_redis_conn()) + + pca_temp_traits = generate_pca_temp_traits(species=temp_dataset.group.species, group=this_group_name, + traits_data=self.trait_data_array, corr_array=self.pca_corr_results, + dataset_samples=temp_dataset.group.all_samples_ordered(), + shared_samples=self.shared_samples_list, + create_time=datetime.datetime.now().strftime("%m%d%H%M%S")) + + cache_pca_dataset(redis_conn=get_redis_conn( + ), exp_days=60 * 60 * 24 * 30, pca_trait_dict=pca_temp_traits) + + self.pca_trait_ids = list(pca_temp_traits.keys()) + + x_coord, y_coord = generate_scree_plot_data( + list(self.pca_obj.explained_variance_ratio_)) + + self.scree_data = { + "x_coord": x_coord, + "y_coord": y_coord + } + return pca + + +def export_corr_matrix(corr_results): + corr_matrix_filename = "corr_matrix_" + \ + ''.join(random.choice(string.ascii_uppercase + string.digits) + for _ in range(6)) + matrix_export_path = "{}{}.csv".format( + GENERATED_TEXT_DIR, corr_matrix_filename) + with open(matrix_export_path, "w+") as output_file: + output_file.write( + "Time/Date: " + datetime.datetime.now().strftime("%x / %X") + "\n") + output_file.write("\n") + output_file.write("Correlation ") + for i, item in enumerate(corr_results[0]): + output_file.write("Trait" + str(i + 1) + ": " + + str(item[0].dataset.name) + "::" + str(item[0].name) + "\t") + output_file.write("\n") + for i, row in enumerate(corr_results): + output_file.write("Trait" + str(i + 1) + ": " + + str(row[0][0].dataset.name) + "::" + str(row[0][0].name) + "\t") + for item in row: + output_file.write(str(item[1]) + "\t") + output_file.write("\n") + + output_file.write("\n") + output_file.write("\n") + output_file.write("N ") + for i, item in enumerate(corr_results[0]): + output_file.write("Trait" + str(i) + ": " + + str(item[0].dataset.name) + "::" + str(item[0].name) + "\t") + output_file.write("\n") + for i, row in enumerate(corr_results): + output_file.write("Trait" + str(i) + ": " + + str(row[0][0].dataset.name) + "::" + str(row[0][0].name) + "\t") + for item in row: + output_file.write(str(item[2]) + "\t") + output_file.write("\n") + + return corr_matrix_filename, matrix_export_path diff --git a/gn2/wqflask/ctl/__init__.py b/gn2/wqflask/ctl/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/ctl/__init__.py diff --git a/gn2/wqflask/ctl/ctl_analysis.py b/gn2/wqflask/ctl/ctl_analysis.py new file mode 100644 index 00000000..513c1b1c --- /dev/null +++ b/gn2/wqflask/ctl/ctl_analysis.py @@ -0,0 +1,214 @@ +# CTL analysis for GN2 +# Author / Maintainer: Danny Arends <Danny.Arends@gmail.com> +import sys +from numpy import * +import rpy2.robjects as ro # R Objects +import rpy2.rinterface as ri + +import simplejson as json + +from gn2.base.webqtlConfig import GENERATED_IMAGE_DIR +from gn2.utility import webqtlUtil # Random number for the image +from gn2.utility import genofile_parser # genofile_parser + +import base64 +import array +import csv +import itertools + +from gn2.base import data_set +from gn2.base.trait import create_trait, retrieve_sample_data + +from gn2.utility import helper_functions +from gn2.utility.tools import locate, GN2_BRANCH_URL + +from rpy2.robjects.packages import importr + + +# Get pointers to some common R functions +r_library = ro.r["library"] # Map the library function +r_options = ro.r["options"] # Map the options function +r_t = ro.r["t"] # Map the t function +r_unlist = ro.r["unlist"] # Map the unlist function +r_list = ro.r.list # Map the list function +r_png = ro.r["png"] # Map the png function for plotting +r_dev_off = ro.r["dev.off"] # Map the dev.off function +r_write_table = ro.r["write.table"] # Map the write.table function +r_data_frame = ro.r["data.frame"] # Map the write.table function +r_as_numeric = ro.r["as.numeric"] # Map the write.table function + + +class CTL: + def __init__(self): + # Load CTL - Should only be done once, since it is quite expensive + r_library("ctl") + r_options(stringsAsFactors=False) + # Map the CTLscan function + self.r_CTLscan = ro.r["CTLscan"] + # Map the CTLsignificant function + self.r_CTLsignificant = ro.r["CTLsignificant"] + # Map the ctl.lineplot function + self.r_lineplot = ro.r["ctl.lineplot"] + # Map the CTLsignificant function + self.r_plotCTLobject = ro.r["plot.CTLobject"] + self.nodes_list = [] + self.edges_list = [] + + self.gn2_url = GN2_BRANCH_URL + + def addNode(self, gt): + node_dict = {'data': {'id': str(gt.name) + ":" + str(gt.dataset.name), + 'sid': str(gt.name), + 'dataset': str(gt.dataset.name), + 'label': gt.name, + 'symbol': gt.symbol, + 'geneid': gt.geneid, + 'omim': gt.omim}} + self.nodes_list.append(node_dict) + + def addEdge(self, gtS, gtT, significant, x): + edge_data = {'id': str(gtS.symbol) + '_' + significant[1][x] + '_' + str(gtT.symbol), + 'source': str(gtS.name) + ":" + str(gtS.dataset.name), + 'target': str(gtT.name) + ":" + str(gtT.dataset.name), + 'lod': significant[3][x], + 'color': "#ff0000", + 'width': significant[3][x]} + edge_dict = {'data': edge_data} + self.edges_list.append(edge_dict) + + def run_analysis(self, requestform): + self.trait_db_list = [trait.strip() + for trait in requestform['trait_list'].split(',')] + self.trait_db_list = [x for x in self.trait_db_list if x] + strategy = requestform.get("strategy") + nperm = int(requestform.get("nperm")) + parametric = bool(requestform.get("parametric")) + significance = float(requestform.get("significance")) + + # Get the name of the .geno file belonging to the first phenotype + datasetname = self.trait_db_list[0].split(":")[1] + dataset = data_set.create_dataset(datasetname) + + genofilelocation = locate(dataset.group.name + ".geno", "genotype") + parser = genofile_parser.ConvertGenoFile(genofilelocation) + parser.process_csv() + # Create a genotype matrix + individuals = parser.individuals + markers = [] + markernames = [] + for marker in parser.markers: + markernames.append(marker["name"]) + markers.append(marker["genotypes"]) + + genotypes = list(itertools.chain(*markers)) + + rGeno = r_t(ro.r.matrix(r_unlist(genotypes), nrow=len(markernames), ncol=len( + individuals), dimnames=r_list(markernames, individuals), byrow=True)) + + # Create a phenotype matrix + traits = [] + for trait in self.trait_db_list: + if trait != "": + ts = trait.split(':') + gt = create_trait(name=ts[0], dataset_name=ts[1]) + gt = retrieve_sample_data(gt, dataset, individuals) + for ind in individuals: + if ind in list(gt.data.keys()): + traits.append(gt.data[ind].value) + else: + traits.append("-999") + + rPheno = r_t(ro.r.matrix(r_as_numeric(r_unlist(traits)), nrow=len(self.trait_db_list), ncol=len( + individuals), dimnames=r_list(self.trait_db_list, individuals), byrow=True)) + + # Use a data frame to store the objects + rPheno = r_data_frame(rPheno, check_names=False) + rGeno = r_data_frame(rGeno, check_names=False) + + # Debug: Print the genotype and phenotype files to disk + #r_write_table(rGeno, "~/outputGN/geno.csv") + #r_write_table(rPheno, "~/outputGN/pheno.csv") + + # Perform the CTL scan + res = self.r_CTLscan(rGeno, rPheno, strategy=strategy, + nperm=nperm, parametric=parametric, nthreads=6) + + # Get significant interactions + significant = self.r_CTLsignificant(res, significance=significance) + + # Create an image for output + self.results = {} + self.results['imgurl1'] = webqtlUtil.genRandStr("CTLline_") + ".png" + self.results['imgloc1'] = GENERATED_IMAGE_DIR + self.results['imgurl1'] + + self.results['ctlresult'] = significant + # Store the user specified parameters for the output page + self.results['requestform'] = requestform + + # Create the lineplot + r_png(self.results['imgloc1'], width=1000, + height=600, type='cairo-png') + self.r_lineplot(res, significance=significance) + r_dev_off() + + # We start from 2, since R starts from 1 :) + n = 2 + for trait in self.trait_db_list: + # Create the QTL like CTL plots + self.results['imgurl' + \ + str(n)] = webqtlUtil.genRandStr("CTL_") + ".png" + self.results['imgloc' + str(n)] = GENERATED_IMAGE_DIR + \ + self.results['imgurl' + str(n)] + r_png(self.results['imgloc' + str(n)], + width=1000, height=600, type='cairo-png') + self.r_plotCTLobject( + res, (n - 1), significance=significance, main='Phenotype ' + trait) + r_dev_off() + n = n + 1 + + # Flush any output from R + sys.stdout.flush() + + # Create the interactive graph for cytoscape visualization (Nodes and Edges) + if not isinstance(significant, ri.RNULLType): + for x in range(len(significant[0])): + # Source + tsS = significant[0][x].split(':') + # Target + tsT = significant[2][x].split(':') + # Retrieve Source info from the DB + gtS = create_trait(name=tsS[0], dataset_name=tsS[1]) + # Retrieve Target info from the DB + gtT = create_trait(name=tsT[0], dataset_name=tsT[1]) + self.addNode(gtS) + self.addNode(gtT) + self.addEdge(gtS, gtT, significant, x) + + # Update the trait name for the displayed table + significant[0][x] = "{} ({})".format(gtS.symbol, gtS.name) + # Update the trait name for the displayed table + significant[2][x] = "{} ({})".format(gtT.symbol, gtT.name) + + self.elements = json.dumps(self.nodes_list + self.edges_list) + + def loadImage(self, path, name): + imgfile = open(self.results[path], 'rb') + imgdata = imgfile.read() + imgB64 = base64.b64encode(imgdata) + bytesarray = array.array('B', imgB64) + self.results[name] = bytesarray + + def render_image(self, results): + self.loadImage("imgloc1", "imgdata1") + n = 2 + for trait in self.trait_db_list: + self.loadImage("imgloc" + str(n), "imgdata" + str(n)) + n = n + 1 + + def process_results(self, results): + template_vars = {} + template_vars["results"] = self.results + template_vars["elements"] = self.elements + self.render_image(self.results) + sys.stdout.flush() + return(dict(template_vars)) diff --git a/gn2/wqflask/ctl/gn3_ctl_analysis.py b/gn2/wqflask/ctl/gn3_ctl_analysis.py new file mode 100644 index 00000000..64c2ff0d --- /dev/null +++ b/gn2/wqflask/ctl/gn3_ctl_analysis.py @@ -0,0 +1,132 @@ +import requests +import itertools + +from gn2.utility import genofile_parser +from gn2.utility.tools import GN3_LOCAL_URL +from gn2.utility.tools import locate + +from gn2.base.trait import create_trait +from gn2.base.trait import retrieve_sample_data +from gn2.base import data_set + + +def process_significance_data(dataset): + col_names = ["trait", "marker", "trait_2", "LOD", "dcor"] + dataset_rows = [[] for _ in range(len(dataset["trait"]))] + for col in col_names: + for (index, col_data) in enumerate(dataset[col]): + if col in ["dcor", "LOD"]: + dataset_rows[index].append(round(float(col_data), 2)) + else: + dataset_rows[index].append(col_data) + + return { + "col_names": col_names, + "data_set_rows": dataset_rows + } + + +def parse_geno_data(dataset_group_name) -> dict: + """ + Args: + dataset_group_name: string name + + @returns : dict with keys genotypes,markernames & individuals + """ + genofile_location = locate(dataset_group_name + ".geno", "genotype") + parser = genofile_parser.ConvertGenoFile(genofile_location) + parser.process_csv() + markers = [] + markernames = [] + for marker in parser.markers: + markernames.append(marker["name"]) + markers.append(marker["genotypes"]) + + return { + + "genotypes": list(itertools.chain(*markers)), + "markernames": markernames, + "individuals": parser.individuals + + + } + + +def parse_phenotype_data(trait_list, dataset, individuals): + """ + Args: + trait_list:list contains the traits + dataset: object + individuals:a list contains the individual vals + Returns: + traits_db_List:parsed list of traits + traits: list contains trait names + individuals + + """ + + traits = [] + for trait in trait_list: + if trait != "": + ts = trait.split(':') + gt = create_trait(name=ts[0], dataset_name=ts[1]) + gt = retrieve_sample_data(gt, dataset, individuals) + for ind in individuals: + if ind in list(gt.data.keys()): + traits.append(gt.data[ind].value) + else: + traits.append("-999") + + return { + "trait_db_list": trait_list, + "traits": traits, + "individuals": individuals + } + + +def parse_form_data(form_data: dict): + + trait_db_list = [trait.strip() + for trait in form_data['trait_list'].split(',')] + + form_data["trait_db_list"] = [x for x in trait_db_list if x] + form_data["nperm"] = int(form_data["nperm"]) + form_data["significance"] = float(form_data["significance"]) + form_data["strategy"] = form_data["strategy"].capitalize() + + return form_data + + +def run_ctl(requestform): + """function to make an api call + to gn3 and run ctl""" + ctl_api = f"{GN3_LOCAL_URL}/api/ctl/run_ctl" + + form_data = parse_form_data(requestform.to_dict()) + trait_db_list = form_data["trait_db_list"] + dataset = data_set.create_dataset(trait_db_list[0].split(":")[1]) + geno_data = parse_geno_data(dataset.group.name) + pheno_data = parse_phenotype_data( + trait_db_list, dataset, geno_data["individuals"]) + + try: + + response = requests.post(ctl_api, json={ + + "genoData": geno_data, + "phenoData": pheno_data, + **form_data, + + }) + if response.status_code != 200: + return {"error": response.json()} + response = response.json()["results"] + response["significance_data"] = process_significance_data( + response["significance_data"]) + + return response + + except requests.exceptions.ConnectionError: + return { + "error": "A connection error to perform computation occurred" + } diff --git a/gn2/wqflask/database.py b/gn2/wqflask/database.py new file mode 100644 index 00000000..331ad380 --- /dev/null +++ b/gn2/wqflask/database.py @@ -0,0 +1,52 @@ +# Module to initialize sqlalchemy with flask +import os +import sys +import logging +import traceback +from typing import Tuple, Protocol, Any, Iterator +from urllib.parse import urlparse +import importlib +import contextlib + +#: type: ignore +import MySQLdb + + +class Connection(Protocol): + def cursor(self) -> Any: + ... + +def parse_db_url(sql_uri: str) -> Tuple: + """ + Parse SQL_URI env variable from an sql URI + e.g. 'mysql://user:pass@host_name/db_name' + """ + parsed_db = urlparse(sql_uri) + return ( + parsed_db.hostname, parsed_db.username, parsed_db.password, + parsed_db.path[1:], parsed_db.port) + + +@contextlib.contextmanager +def database_connection(sql_uri: str) -> Iterator[Connection]: + """Provide a context manager for opening, closing, and rolling + back - if supported - a database connection. Should an error occur, + and if the table supports transactions, the connection will be + rolled back. + + """ + host, user, passwd, db_name, port = parse_db_url(sql_uri) + connection = MySQLdb.connect( + db=db_name, user=user, passwd=passwd or '', host=host, + port=(port or 3306), autocommit=False # Required for roll-backs + ) + try: + yield connection + connection.commit() + except Exception as _exc: + logging.error("===== Query Error =====\r\n%s\r\n===== END: Query Error", + traceback.format_exc()) + connection.rollback() + raise _exc + finally: + connection.close() diff --git a/gn2/wqflask/db_info.py b/gn2/wqflask/db_info.py new file mode 100644 index 00000000..f6b94dde --- /dev/null +++ b/gn2/wqflask/db_info.py @@ -0,0 +1,115 @@ +import urllib.request +import urllib.error +import urllib.parse +import re + +from MySQLdb.cursors import DictCursor +from gn2.wqflask.database import database_connection +from gn2.utility.tools import get_setting + + +class InfoPage: + def __init__(self, start_vars): + self.info = None + self.gn_accession_id = None + if 'gn_accession_id' in start_vars: + self.gn_accession_id = start_vars['gn_accession_id'] + self.info_page_name = start_vars['info_page_name'] + + self.get_info() + self.get_datasets_list() + + def get_info(self, create=False): + query_base = ( + "SELECT InfoPageName AS info_page_name, " + "GN_AccesionId AS accession_id, " + "Species.MenuName AS menu_name, " + "Species.TaxonomyId AS taxonomy_id, " + "Tissue.Name AS tissue_name, " + "InbredSet.Name AS group_name, " + "GeneChip.GeneChipName AS gene_chip_name, " + "GeneChip.GeoPlatform AS geo_platform, " + "AvgMethod.Name AS avg_method_name, " + "Datasets.DatasetName AS dataset_name, " + "Datasets.GeoSeries AS geo_series, " + "Datasets.PublicationTitle AS publication_title, " + "DatasetStatus.DatasetStatusName AS dataset_status_name, " + "Datasets.Summary AS dataset_summary, " + "Datasets.AboutCases AS about_cases, " + "Datasets.AboutTissue AS about_tissue, " + "Datasets.AboutDataProcessing AS about_data_processing, " + "Datasets.Acknowledgment AS acknowledgement, " + "Datasets.ExperimentDesign AS experiment_design, " + "Datasets.Contributors AS contributors, " + "Datasets.Citation AS citation, " + "Datasets.Notes AS notes, " + "Investigators.FirstName AS investigator_firstname, " + "Investigators.LastName AS investigator_lastname, " + "Investigators.Address AS investigator_address, " + "Investigators.City AS investigator_city, " + "Investigators.State AS investigator_state, " + "Investigators.ZipCode AS investigator_zipcode, " + "Investigators.Country AS investigator_country, " + "Investigators.Phone AS investigator_phone, " + "Investigators.Email AS investigator_email, " + "Investigators.Url AS investigator_url, " + "Organizations.OrganizationName AS organization_name, " + "InvestigatorId AS investigator_id, " + "DatasetId AS dataset_id, " + "DatasetStatusId AS dataset_status_id, " + "Datasets.AboutPlatform AS about_platform, " + "InfoFileTitle AS info_file_title, " + "Specifics AS specifics" + "FROM InfoFiles " + "LEFT JOIN Species USING (SpeciesId) " + "LEFT JOIN Tissue USING (TissueId) " + "LEFT JOIN InbredSet USING (InbredSetId) " + "LEFT JOIN GeneChip USING (GeneChipId) " + "LEFT JOIN AvgMethod USING (AvgMethodId) " + "LEFT JOIN Datasets USING (DatasetId) " + "LEFT JOIN Investigators USING (InvestigatorId) " + "LEFT JOIN Organizations USING (OrganizationId) " + "LEFT JOIN DatasetStatus USING (DatasetStatusId) WHERE " + ) + if not all([self.gn_accession_id, self.info_page_name]): + raise ValueError('No correct parameter found') + + results = {} + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor(DictCursor) as cursor: + if self.gn_accession_id: + cursor.execute(f"{query_base}GN_AccesionId = %s", + (self.gn_accession_id,)) + elif self.info_page_name: + cursor.execute(f"{query_base}InfoPageName = %s", + (self.info_page_name,)) + if (results := cursor.fetchone()): + self.info = results + if ((not results or len(results) < 1) + and self.info_page_name and create): + return self.get_info() + if not self.gn_accession_id and self.info: + self.gn_accession_id = self.info['accession_id'] + if not self.info_page_name and self.info: + self.info_page_name = self.info['info_page_name'] + + def get_datasets_list(self): + self.filelist = [] + try: + response = urllib.request.urlopen( + "https://files.genenetwork.org/current/GN%s" % self.gn_accession_id) + data = response.read() + + matches = re.findall(r"<tr>.+?</tr>", data, re.DOTALL) + for i, match in enumerate(matches): + if i == 0: + continue + cells = re.findall(r"<td.+?>.+?</td>", match, re.DOTALL) + full_filename = re.search( + r"<a href=\"(.+?)\"", cells[1], re.DOTALL).group(1).strip() + filename = full_filename.split("/")[-1] + filesize = re.search(r">(.+?)<", cells[2]).group(1).strip() + filedate = "N/A" # ZS: Since we can't get it for now + + self.filelist.append([filename, filedate, filesize]) + except Exception as e: + pass diff --git a/gn2/wqflask/decorators.py b/gn2/wqflask/decorators.py new file mode 100644 index 00000000..4fe865c9 --- /dev/null +++ b/gn2/wqflask/decorators.py @@ -0,0 +1,137 @@ +"""This module contains gn2 decorators""" +import json +import requests +from functools import wraps +from urllib.parse import urljoin +from typing import Dict, Callable + +import redis +from flask import g, flash, request, url_for, redirect, current_app + +from gn3.authentication import AdminRole +from gn3.authentication import DataRole + +from gn2.wqflask.oauth2 import client +from gn2.wqflask.oauth2.session import session_info +from gn2.wqflask.oauth2.checks import user_logged_in +from gn2.wqflask.oauth2.request_utils import process_error + + +def login_required(pagename: str = ""): + """Use this for endpoints where login is required""" + def __build_wrap__(func): + @wraps(func) + def wrap(*args, **kwargs): + if not user_logged_in(): + msg = ("You need to be logged in to access that page." + if not bool(pagename) else + ("You need to be logged in to access the " + f"'{pagename.title()}' page.")) + flash(msg, "alert-warning") + return redirect("/") + return func(*args, **kwargs) + return wrap + return __build_wrap__ + + +def edit_access_required(f): + """Use this for endpoints where people with admin or edit privileges +are required""" + @wraps(f) + def wrap(*args, **kwargs): + resource_id: str = "" + if request.args.get("resource-id"): + resource_id = request.args.get("resource-id") + elif kwargs.get("resource_id"): + resource_id = kwargs.get("resource_id") + response: Dict = {} + try: + user_id = ((g.user_session.record.get(b"user_id") or + b"").decode("utf-8") + or g.user_session.record.get("user_id") or "") + response = json.loads( + requests.get(urljoin( + current_app.config.get("GN2_PROXY"), + ("available?resource=" + f"{resource_id}&user={user_id}"))).content) + except: + response = {} + if max([DataRole(role) for role in response.get( + "data", ["no-access"])]) < DataRole.EDIT: + return redirect(url_for("no_access_page")) + return f(*args, **kwargs) + return wrap + + +def edit_admins_access_required(f): + """Use this for endpoints where ownership of a resource is required""" + @wraps(f) + def wrap(*args, **kwargs): + resource_id: str = kwargs.get("resource_id", "") + response: Dict = {} + try: + user_id = ((g.user_session.record.get(b"user_id") or + b"").decode("utf-8") + or g.user_session.record.get("user_id") or "") + response = json.loads( + requests.get(urljoin( + current_app.config.get("GN2_PROXY"), + ("available?resource=" + f"{resource_id}&user={user_id}"))).content) + except: + response = {} + if max([AdminRole(role) for role in response.get( + "admin", ["not-admin"])]) < AdminRole.EDIT_ADMINS: + return redirect(url_for("no_access_page")) + return f(*args, **kwargs) + return wrap + +class AuthorisationError(Exception): + """Raise when there is an authorisation issue.""" + def __init__(self, description, user): + self.description = description + self.user = user + super().__init__(self, description, user) + +def required_access(access_levels: tuple[str, ...], + dataset_key: str = "dataset_name", + trait_key: str = "name") -> Callable: + def __build_access_checker__(func: Callable): + @wraps(func) + def __checker__(*args, **kwargs): + def __error__(err): + error = process_error(err) + raise AuthorisationError( + f"{error['error']}: {error['error_description']}", + session_info()["user"]) + + def __success__(priv_info): + if all(priv in priv_info[0]["privileges"] for priv in access_levels): + return func(*args, **kwargs) + missing = tuple(f"'{priv}'" for priv in access_levels + if priv not in priv_info[0]["privileges"]) + raise AuthorisationError( + f"Missing privileges: {', '.join(missing)}", + session_info()["user"]) + dataset_name = kwargs.get( + dataset_key, + request.args.get(dataset_key, request.form.get(dataset_key, ""))) + if not bool(dataset_name): + raise AuthorisationError( + "DeveloperError: Dataset name not provided. It is needed " + "for the authorisation checks.", + session_info()["user"]) + trait_name = kwargs.get( + trait_key, + request.args.get(trait_key, request.form.get(trait_key, ""))) + if not bool(trait_name): + raise AuthorisationError( + "DeveloperError: Trait name not provided. It is needed for " + "the authorisation checks.", + session_info()["user"]) + return client.post( + "auth/data/authorisation", + json={"traits": [f"{dataset_name}::{trait_name}"]}).either( + __error__, __success__) + return __checker__ + return __build_access_checker__ diff --git a/gn2/wqflask/do_search.py b/gn2/wqflask/do_search.py new file mode 100644 index 00000000..3c81783d --- /dev/null +++ b/gn2/wqflask/do_search.py @@ -0,0 +1,965 @@ +import json +import re +import requests +import string + +from gn2.wqflask.database import database_connection + +import sys + +from gn2.db import webqtlDatabaseFunction +from gn2.utility.tools import get_setting, GN2_BASE_URL + + +class DoSearch: + """Parent class containing parameters/functions used for all searches""" + + # Used to translate search phrases into classes + search_types = dict() + + def __init__(self, search_term, search_operator=None, dataset=None, search_type=None): + self.search_term = search_term + # Make sure search_operator is something we expect + assert search_operator in ( + None, "=", "<", ">", "<=", ">="), "Bad search operator" + self.search_operator = search_operator + self.dataset = dataset + self.search_type = search_type + + if self.dataset: + # Get group information for dataset and the species id + self.species_id = webqtlDatabaseFunction.retrieve_species_id( + self.dataset.group.name) + + def execute(self, query): + """Executes query and returns results""" + query = self.normalize_spaces(query) + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute(query) + return cursor.fetchall() + + def handle_wildcard(self, str): + keyword = str.strip() + keyword = keyword.replace("*", ".*") + keyword = keyword.replace("?", ".") + + return keyword + + def sescape(self, item): + """Single escape""" + from gn2.utility.tools import get_setting + with database_connection(get_setting("SQL_URI")) as conn: + escaped = conn.escape_string(str(item)).decode() + return escaped + + def mescape(self, *items): + """Multiple escape""" + from gn2.utility.tools import get_setting + escaped = [] + with database_connection(get_setting("SQL_URI")) as conn: + escaped = [conn.escape_string(str(item)).decode() for item in items] + return tuple(escaped) + + def normalize_spaces(self, stringy): + """Strips out newlines/extra spaces and replaces them with just spaces""" + step_one = " ".join(stringy.split()) + return step_one + + @classmethod + def get_search(cls, search_type): + search_type_string = search_type['dataset_type'] + if 'key' in search_type and search_type['key'] != None: + search_type_string += '_' + search_type['key'] + + if search_type_string in cls.search_types: + return cls.search_types[search_type_string] + else: + return None + + +class MrnaAssaySearch(DoSearch): + """A search within an expression dataset, including mRNA, protein, SNP, but not phenotype or metabolites""" + + DoSearch.search_types['ProbeSet'] = "MrnaAssaySearch" + + base_query = """ + SELECT DISTINCT + ProbeSetFreeze.`Name`, + ProbeSetFreeze.`FullName`, + ProbeSet.`Name`, + ProbeSet.`Symbol`, + CAST(ProbeSet.`description` AS BINARY), + CAST(ProbeSet.`Probe_Target_Description` AS BINARY), + ProbeSet.`Chr`, + ProbeSet.`Mb`, + ProbeSetXRef.`Mean`, + ProbeSetXRef.`LRS`, + ProbeSetXRef.`Locus`, + ProbeSetXRef.`pValue`, + ProbeSetXRef.`additive`, + Geno.`Chr` as geno_chr, + Geno.`Mb` as geno_mb + FROM Species + INNER JOIN InbredSet ON InbredSet.`SpeciesId`= Species.`Id` + INNER JOIN ProbeFreeze ON ProbeFreeze.`InbredSetId` = InbredSet.`Id` + INNER JOIN Tissue ON ProbeFreeze.`TissueId` = Tissue.`Id` + INNER JOIN ProbeSetFreeze ON ProbeSetFreeze.`ProbeFreezeId` = ProbeFreeze.`Id` + INNER JOIN ProbeSetXRef ON ProbeSetXRef.`ProbeSetFreezeId` = ProbeSetFreeze.`Id` + INNER JOIN ProbeSet ON ProbeSet.`Id` = ProbeSetXRef.`ProbeSetId` + LEFT JOIN Geno ON ProbeSetXRef.`Locus` = Geno.`Name` AND Geno.`SpeciesId` = Species.`Id` """ + + header_fields = ['Index', + 'Record', + 'Symbol', + 'Description', + 'Location', + 'Mean', + 'Max LRS', + 'Max LRS Location', + 'Additive Effect'] + + def get_alias_where_clause(self): + search_string = self.sescape(self.search_term[0]) + + if self.search_term[0] != "*": + match_clause = """((MATCH (ProbeSet.symbol) AGAINST ('%s' IN BOOLEAN MODE))) and """ % ( + search_string) + else: + match_clause = "" + + where_clause = (match_clause + + """ProbeSet.Id = ProbeSetXRef.ProbeSetId + and ProbeSetXRef.ProbeSetFreezeId = %s + """ % (self.sescape(str(self.dataset.id)))) + + return where_clause + + def get_where_clause(self): + search_string = self.sescape(self.search_term[0]) + + if self.search_term[0] != "*": + if re.search("\w{1,2}\-\w+|\w+\-\w{1,2}", self.search_term[0]): + search_string = f'"{search_string}*"' + + match_clause = f"""((MATCH (ProbeSet.Name, + ProbeSet.description, + ProbeSet.symbol, + alias, + GenbankId, + UniGeneId, + Probe_Target_Description) + AGAINST ('{search_string}' IN BOOLEAN MODE))) AND """ + else: + match_clause = "" + + where_clause = (match_clause + + """ProbeSet.Id = ProbeSetXRef.ProbeSetId + and ProbeSetXRef.ProbeSetFreezeId = %s + """ % (self.sescape(str(self.dataset.id)))) + + return where_clause + + def compile_final_query(self, from_clause='', where_clause=''): + """Generates the final query string""" + + from_clause = self.normalize_spaces(from_clause) + + query = (self.base_query + + """%s + WHERE %s + and ProbeSet.Id = ProbeSetXRef.ProbeSetId + and ProbeSetXRef.ProbeSetFreezeId = %s + ORDER BY ProbeSet.symbol ASC + """ % (self.sescape(from_clause), + where_clause, + self.sescape(str(self.dataset.id)))) + return query + + def run_combined(self, from_clause='', where_clause=''): + """Generates and runs a combined search of an mRNA expression dataset""" + #query = self.base_query + from_clause + " WHERE " + where_clause + + from_clause = self.normalize_spaces(from_clause) + + query = (self.base_query + + """%s + WHERE %s + and ProbeSet.Id = ProbeSetXRef.ProbeSetId + and ProbeSetXRef.ProbeSetFreezeId = %s + ORDER BY ProbeSet.symbol ASC + """ % (self.sescape(from_clause), + where_clause, + self.sescape(str(self.dataset.id)))) + + return self.execute(query) + + def run(self): + """Generates and runs a simple search of an mRNA expression dataset""" + where_clause = self.get_where_clause() + query = self.base_query + "WHERE " + where_clause + "ORDER BY ProbeSet.symbol ASC" + return self.execute(query) + + +class PhenotypeSearch(DoSearch): + """A search within a phenotype dataset""" + + DoSearch.search_types['Publish'] = "PhenotypeSearch" + + base_query = """SELECT PublishXRef.Id, + CAST(Phenotype.`Pre_publication_description` AS BINARY), + CAST(Phenotype.`Post_publication_description` AS BINARY), + Publication.`Authors`, + Publication.`Year`, + Publication.`PubMed_ID`, + PublishXRef.`mean`, + PublishXRef.`LRS`, + PublishXRef.`additive`, + PublishXRef.`Locus`, + InbredSet.`InbredSetCode`, + Geno.`Chr`, + Geno.`Mb` + FROM Species + INNER JOIN InbredSet ON InbredSet.`SpeciesId` = Species.`Id` + INNER JOIN PublishXRef ON PublishXRef.`InbredSetId` = InbredSet.`Id` + INNER JOIN PublishFreeze ON PublishFreeze.`InbredSetId` = InbredSet.`Id` + INNER JOIN Publication ON Publication.`Id` = PublishXRef.`PublicationId` + INNER JOIN Phenotype ON Phenotype.`Id` = PublishXRef.`PhenotypeId` + LEFT JOIN Geno ON PublishXRef.Locus = Geno.Name AND Geno.SpeciesId = Species.Id """ + + search_fields = ('Phenotype.Post_publication_description', + 'Phenotype.Pre_publication_description', + 'Phenotype.Pre_publication_abbreviation', + 'Phenotype.Post_publication_abbreviation', + 'Phenotype.Lab_code', + 'Publication.PubMed_ID', + 'Publication.Abstract', + 'Publication.Title', + 'Publication.Authors', + 'PublishXRef.Id') + + header_fields = ['Index', + 'Record', + 'Description', + 'Mean', + 'Authors', + 'Year', + 'Max LRS', + 'Max LRS Location', + 'Additive Effect'] + + def get_where_clause(self): + """Generate clause for WHERE portion of query""" + + # Todo: Zach will figure out exactly what both these lines mean + # and comment here + + # if "'" not in self.search_term[0]: + search_term = "%" + \ + self.handle_wildcard(self.search_term[0]) + "%" + if "_" in self.search_term[0]: + if len(self.search_term[0].split("_")[0]) == 3: + search_term = "%" + self.handle_wildcard( + self.search_term[0].split("_")[1]) + "%" + + # This adds a clause to the query that matches the search term + # against each field in the search_fields tuple + where_clause_list = [] + for field in self.search_fields: + where_clause_list.append('''%s LIKE "%s"''' % + (field, search_term)) + where_clause = "(%s) " % ' OR '.join(where_clause_list) + + return where_clause + + def compile_final_query(self, from_clause='', where_clause=''): + """Generates the final query string""" + + from_clause = self.normalize_spaces(from_clause) + + if self.search_term[0] == "*": + query = (self.base_query + + """%s + WHERE PublishXRef.InbredSetId = %s + and PublishXRef.PhenotypeId = Phenotype.Id + and PublishXRef.PublicationId = Publication.Id + and PublishFreeze.Id = %s + ORDER BY PublishXRef.Id""" % ( + from_clause, + self.sescape(str(self.dataset.group.id)), + self.sescape(str(self.dataset.id)))) + else: + query = (self.base_query + + """%s + WHERE %s + and PublishXRef.InbredSetId = %s + and PublishXRef.PhenotypeId = Phenotype.Id + and PublishXRef.PublicationId = Publication.Id + and PublishFreeze.Id = %s + ORDER BY PublishXRef.Id""" % ( + from_clause, + where_clause, + self.sescape(str(self.dataset.group.id)), + self.sescape(str(self.dataset.id)))) + + return query + + def run_combined(self, from_clause, where_clause): + """Generates and runs a combined search of an phenotype dataset""" + from_clause = self.normalize_spaces(from_clause) + + query = (self.base_query + + """%s + WHERE %s + PublishXRef.InbredSetId = %s and + PublishXRef.PhenotypeId = Phenotype.Id and + PublishXRef.PublicationId = Publication.Id and + PublishFreeze.Id = %s""" % ( + from_clause, + where_clause, + self.sescape(str(self.dataset.group.id)), + self.sescape(str(self.dataset.id)))) + + return self.execute(query) + + def run(self): + """Generates and runs a simple search of a phenotype dataset""" + + query = self.compile_final_query(where_clause=self.get_where_clause()) + + return self.execute(query) + + +class GenotypeSearch(DoSearch): + """A search within a genotype dataset""" + + DoSearch.search_types['Geno'] = "GenotypeSearch" + + base_query = """SELECT Geno.Name, + GenoFreeze.createtime as thistable, + Geno.Name as Geno_Name, + Geno.Source2 as Geno_Source2, + Geno.Chr as Geno_Chr, + Geno.Mb as Geno_Mb + FROM GenoXRef, GenoFreeze, Geno """ + + search_fields = ('Name', 'Chr') + + header_fields = ['Index', + 'Record', + 'Location'] + + def get_where_clause(self): + """Generate clause for part of the WHERE portion of query""" + + # This adds a clause to the query that matches the search term + # against each field in search_fields (above) + where_clause = [] + + if "'" not in self.search_term[0]: + self.search_term = "%" + self.search_term[0] + "%" + + for field in self.search_fields: + where_clause.append('''%s LIKE "%s"''' % ("%s.%s" % self.mescape(self.dataset.type, + field), + self.search_term)) + where_clause = "(%s) " % ' OR '.join(where_clause) + + return where_clause + + def compile_final_query(self, from_clause='', where_clause=''): + """Generates the final query string""" + + from_clause = self.normalize_spaces(from_clause) + + if self.search_term[0] == "*": + query = (self.base_query + + """WHERE Geno.Id = GenoXRef.GenoId + and GenoXRef.GenoFreezeId = GenoFreeze.Id + and GenoFreeze.Id = %s""" % (self.sescape(str(self.dataset.id)))) + else: + query = (self.base_query + + """WHERE %s + and Geno.Id = GenoXRef.GenoId + and GenoXRef.GenoFreezeId = GenoFreeze.Id + and GenoFreeze.Id = %s""" % (where_clause, + self.sescape(str(self.dataset.id)))) + + return query + + def run(self): + """Generates and runs a simple search of a genotype dataset""" + # Todo: Zach will figure out exactly what both these lines mean + # and comment here + + if self.search_term[0] == "*": + self.query = self.compile_final_query() + else: + self.query = self.compile_final_query( + where_clause=self.get_where_clause()) + + return self.execute(self.query) + + +class RifSearch(MrnaAssaySearch): + """Searches for traits with a Gene RIF entry including the search term.""" + + DoSearch.search_types['ProbeSet_RIF'] = "RifSearch" + + def get_from_clause(self): + return f" INNER JOIN GeneRIF_BASIC ON GeneRIF_BASIC.`symbol` = { self.dataset.type }.`symbol` " + + def get_where_clause(self): + where_clause = f"(MATCH (GeneRIF_BASIC.comment) AGAINST ('+{ self.search_term[0] }' IN BOOLEAN MODE)) " + + return where_clause + + def run(self): + from_clause = self.get_from_clause() + where_clause = self.get_where_clause() + + query = self.compile_final_query(from_clause, where_clause) + + return self.execute(query) + + +class WikiSearch(MrnaAssaySearch): + """Searches GeneWiki for traits other people have annotated""" + + DoSearch.search_types['ProbeSet_WIKI'] = "WikiSearch" + + def get_from_clause(self): + return ", GeneRIF " + + def get_where_clause(self): + where_clause = """%s.symbol = GeneRIF.symbol + and GeneRIF.versionId=0 and GeneRIF.display>0 + and (GeneRIF.comment LIKE '%s' or GeneRIF.initial = '%s') + """ % (self.dataset.type, + "%" + str(self.search_term[0]) + "%", + str(self.search_term[0])) + return where_clause + + def run(self): + from_clause = self.get_from_clause() + where_clause = self.get_where_clause() + + query = self.compile_final_query(from_clause, where_clause) + + return self.execute(query) + + +class GoSearch(MrnaAssaySearch): + """Searches for synapse-associated genes listed in the Gene Ontology.""" + + DoSearch.search_types['ProbeSet_GO'] = "GoSearch" + + def get_from_clause(self): + from_clause = """, db_GeneOntology.term as GOterm, + db_GeneOntology.association as GOassociation, + db_GeneOntology.gene_product as GOgene_product """ + + return from_clause + + def get_where_clause(self): + field = 'GOterm.acc' + go_id = 'GO:' + ('0000000' + self.search_term[0])[-7:] + + statements = ("""%s.symbol=GOgene_product.symbol and + GOassociation.gene_product_id=GOgene_product.id and + GOterm.id=GOassociation.term_id""" % ( + self.sescape(self.dataset.type))) + + where_clause = " %s = '%s' and %s " % (field, go_id, statements) + + return where_clause + + def run(self): + from_clause = self.get_from_clause() + where_clause = self.get_where_clause() + + query = self.compile_final_query(from_clause, where_clause) + + return self.execute(query) + +# ZS: Not sure what the best way to deal with LRS searches is + + +class LrsSearch(DoSearch): + """Searches for genes with a QTL within the given LRS values + + LRS searches can take 3 different forms: + - LRS > (or <) min/max_LRS + - LRS=(min_LRS max_LRS) + - LRS=(min_LRS max_LRS chromosome start_Mb end_Mb) + where min/max_LRS represent the range of LRS scores and start/end_Mb represent + the range in megabases on the given chromosome + + """ + + for search_key in ('LRS', 'LOD'): + DoSearch.search_types[search_key] = "LrsSearch" + + def get_from_clause(self): + converted_search_term = [] + for value in self.search_term: + try: + converted_search_term.append(float(value)) + except: + converted_search_term.append(value) + + self.search_term = converted_search_term + + from_clause = "" + + return from_clause + + def get_where_clause(self): + if self.search_operator == "=": + assert isinstance(self.search_term, (list, tuple)) + lrs_min, lrs_max = self.search_term[:2] + if self.search_type == "LOD": + lrs_min = lrs_min * 4.61 + lrs_max = lrs_max * 4.61 + + where_clause = """ %sXRef.LRS > %s and + %sXRef.LRS < %s """ % self.mescape(self.dataset.type, + min(lrs_min, + lrs_max), + self.dataset.type, + max(lrs_min, lrs_max)) + + if len(self.search_term) > 2: + try: + chr_num = int(float(self.search_term[2])) + except: + chr_num = self.search_term[2].lower().replace('chr', '') + self.search_term[2] = chr_num + + where_clause += """ and Geno.Chr = '%s' """ % (chr_num) + if len(self.search_term) == 5: + mb_low, mb_high = self.search_term[3:] + where_clause += """ and Geno.Mb > %s and + Geno.Mb < %s + """ % self.mescape(min(mb_low, mb_high), + max(mb_low, mb_high)) + + where_clause += """ and %sXRef.Locus = Geno.name and + Geno.SpeciesId = %s + """ % self.mescape(self.dataset.type, + self.species_id) + else: + # Deal with >, <, >=, and <= + lrs_val = self.search_term[0] + if self.search_type == "LOD": + lrs_val = lrs_val * 4.61 + + where_clause = """ %sXRef.LRS %s %s """ % self.mescape(self.dataset.type, + self.search_operator, + self.search_term[0]) + + return where_clause + + def run(self): + + self.from_clause = self.get_from_clause() + self.where_clause = self.get_where_clause() + + self.query = self.compile_final_query( + self.from_clause, self.where_clause) + + return self.execute(self.query) + + +class MrnaLrsSearch(LrsSearch, MrnaAssaySearch): + + for search_key in ('LRS', 'LOD'): + DoSearch.search_types['ProbeSet_' + search_key] = "MrnaLrsSearch" + + def run(self): + self.from_clause = self.get_from_clause() + self.where_clause = self.get_where_clause() + + self.query = self.compile_final_query( + from_clause=self.from_clause, where_clause=self.where_clause) + + return self.execute(self.query) + + +class PhenotypeLrsSearch(LrsSearch, PhenotypeSearch): + + for search_key in ('LRS', 'LOD'): + DoSearch.search_types['Publish_' + search_key] = "PhenotypeLrsSearch" + + def run(self): + + self.from_clause = self.get_from_clause() + self.where_clause = self.get_where_clause() + + self.query = self.compile_final_query( + from_clause=self.from_clause, where_clause=self.where_clause) + + return self.execute(self.query) + + +class CisTransLrsSearch(DoSearch): + + def get_where_clause(self, cis_trans): + self.mb_buffer = 5 # default + chromosome = None + if cis_trans == "cis": + the_operator = "<" + else: + the_operator = ">" + + if self.search_operator == "=": + if len(self.search_term) == 2 or len(self.search_term) == 3: + self.search_term = [float(value) for value in self.search_term] + if len(self.search_term) == 2: + lrs_min, lrs_max = self.search_term + #[int(value) for value in self.search_term] + elif len(self.search_term) == 3: + lrs_min, lrs_max, self.mb_buffer = self.search_term + elif len(self.search_term) == 4: + lrs_min, lrs_max, self.mb_buffer = [ + float(value) for value in self.search_term[:3]] + chromosome = self.search_term[3] + chr_str = re.match("(^c|^C)[a-z]*", chromosome) + if chr_str: + chromosome = int(chromosome.replace(chr_str.group(0), '')) + else: + SomeError + + if self.search_type == "CISLOD" or self.search_type == "TRANSLOD": + lrs_min = lrs_min * 4.61 + lrs_max = lrs_max * 4.61 + + sub_clause = """ %sXRef.LRS > %s and + %sXRef.LRS < %s and """ % ( + self.sescape(self.dataset.type), + self.sescape(str(min(lrs_min, lrs_max))), + self.sescape(self.dataset.type), + self.sescape(str(max(lrs_min, lrs_max))) + ) + else: + # Deal with >, <, >=, and <= + sub_clause = """ %sXRef.LRS %s %s and """ % ( + self.sescape(self.dataset.type), + self.sescape(self.search_operator), + self.sescape(self.search_term[0]) + ) + + if cis_trans == "cis": + where_clause = sub_clause + """ + ABS(%s.Mb-Geno.Mb) %s %s and + %sXRef.Locus = Geno.name and + Geno.SpeciesId = %s and + %s.Chr = Geno.Chr""" % ( + self.sescape(self.dataset.type), + the_operator, + self.sescape(str(self.mb_buffer)), + self.sescape(self.dataset.type), + self.sescape(str(self.species_id)), + self.sescape(self.dataset.type) + ) + else: + if chromosome: + location_clause = """ + (%s.Chr = '%s' and %s.Chr = Geno.Chr and ABS(%s.Mb-Geno.Mb) %s %s) + or (%s.Chr != Geno.Chr and Geno.Chr = '%s')""" % ( + self.sescape(self.dataset.type), + chromosome, + self.sescape( + self.dataset.type), + self.sescape( + self.dataset.type), + the_operator, + self.sescape( + str(self.mb_buffer)), + self.sescape( + self.dataset.type), + chromosome) + else: + location_clause = "(ABS(%s.Mb-Geno.Mb) %s %s and %s.Chr = Geno.Chr) or (%s.Chr != Geno.Chr)" % (self.sescape( + self.dataset.type), the_operator, self.sescape(str(self.mb_buffer)), self.sescape(self.dataset.type), self.sescape(self.dataset.type)) + where_clause = sub_clause + """ + %sXRef.Locus = Geno.name and + Geno.SpeciesId = %s and + (%s)""" % ( + self.sescape(self.dataset.type), + self.sescape(str(self.species_id)), + location_clause + ) + + return where_clause + + +class CisLrsSearch(CisTransLrsSearch, MrnaAssaySearch): + """ + Searches for genes on a particular chromosome with a cis-eQTL within the given LRS values + + A cisLRS search can take 3 forms: + - cisLRS=(min_LRS max_LRS) + - cisLRS=(min_LRS max_LRS mb_buffer) + - cisLRS>min_LRS + where min/max_LRS represent the range of LRS scores and the mb_buffer is the range around + a particular QTL where its eQTL would be considered "cis". If there is no third parameter, + mb_buffer will default to 5 megabases. + + A QTL is a cis-eQTL if a gene's expression is regulated by a QTL in roughly the same area + (where the area is determined by the mb_buffer that the user can choose). + + """ + + for search_key in ('LRS', 'LOD'): + DoSearch.search_types['ProbeSet_CIS' + search_key] = "CisLrsSearch" + + def get_where_clause(self): + return CisTransLrsSearch.get_where_clause(self, "cis") + + def run(self): + self.from_clause = self.get_from_clause() + self.where_clause = self.get_where_clause() + + self.query = self.compile_final_query( + self.from_clause, self.where_clause) + + return self.execute(self.query) + + +class TransLrsSearch(CisTransLrsSearch, MrnaAssaySearch): + """Searches for genes on a particular chromosome with a cis-eQTL within the given LRS values + + A transLRS search can take 3 forms: + - transLRS=(min_LRS max_LRS) + - transLRS=(min_LRS max_LRS mb_buffer) + - transLRS>min_LRS + where min/max_LRS represent the range of LRS scores and the mb_buffer is the range around + a particular QTL where its eQTL would be considered "cis". If there is no third parameter, + mb_buffer will default to 5 megabases. + + A QTL is a trans-eQTL if a gene's expression is regulated by a QTL in a different location/area + (where the area is determined by the mb_buffer that the user can choose). Opposite of cis-eQTL. + + """ + + for search_key in ('LRS', 'LOD'): + DoSearch.search_types['ProbeSet_TRANS' + search_key] = "TransLrsSearch" + + def get_where_clause(self): + return CisTransLrsSearch.get_where_clause(self, "trans") + + def run(self): + self.from_clause = self.get_from_clause() + self.where_clause = self.get_where_clause() + + self.query = self.compile_final_query( + self.from_clause, self.where_clause) + + return self.execute(self.query) + + +class MeanSearch(MrnaAssaySearch): + """Searches for genes expressed within an interval (log2 units) determined by the user""" + + DoSearch.search_types['ProbeSet_MEAN'] = "MeanSearch" + + def get_where_clause(self): + self.search_term = [float(value) for value in self.search_term] + + if self.search_operator == "=": + assert isinstance(self.search_term, (list, tuple)) + self.mean_min, self.mean_max = self.search_term[:2] + + where_clause = """ %sXRef.mean > %s and + %sXRef.mean < %s """ % self.mescape(self.dataset.type, + min(self.mean_min, + self.mean_max), + self.dataset.type, + max(self.mean_min, self.mean_max)) + else: + # Deal with >, <, >=, and <= + where_clause = """ %sXRef.mean %s %s """ % self.mescape(self.dataset.type, + self.search_operator, + self.search_term[0]) + + return where_clause + + def run(self): + self.where_clause = self.get_where_clause() + + self.query = self.compile_final_query(where_clause=self.where_clause) + + return self.execute(self.query) + + +class RangeSearch(MrnaAssaySearch): + """Searches for genes with a range of expression varying between two values""" + + DoSearch.search_types['ProbeSet_RANGE'] = "RangeSearch" + + def get_where_clause(self): + if self.search_operator == "=": + assert isinstance(self.search_term, (list, tuple)) + self.range_min, self.range_max = self.search_term[:2] + where_clause = """ (SELECT Pow(2, max(value) -min(value)) + FROM ProbeSetData + WHERE ProbeSetData.Id = ProbeSetXRef.dataId) > %s AND + (SELECT Pow(2, max(value) -min(value)) + FROM ProbeSetData + WHERE ProbeSetData.Id = ProbeSetXRef.dataId) < %s + """ % self.mescape(min(self.range_min, self.range_max), + max(self.range_min, self.range_max)) + else: + # Deal with >, <, >=, and <= + where_clause = """ (SELECT Pow(2, max(value) -min(value)) + FROM ProbeSetData + WHERE ProbeSetData.Id = ProbeSetXRef.dataId) > %s + """ % (self.sescape(self.search_term[0])) + return where_clause + + def run(self): + self.where_clause = self.get_where_clause() + + self.query = self.compile_final_query(where_clause=self.where_clause) + + return self.execute(self.query) + + +class PositionSearch(DoSearch): + """Searches for genes/markers located within a specified range on a specified chromosome""" + + for search_key in ('POSITION', 'POS', 'MB'): + DoSearch.search_types[search_key] = "PositionSearch" + + def get_where_clause(self): + self.search_term = [float(value) if is_number( + value) else value for value in self.search_term] + chr, self.mb_min, self.mb_max = self.search_term[:3] + self.chr = str(chr).lower() + self.get_chr() + + where_clause = """ %s.Chr = '%s' and + %s.Mb > %s and + %s.Mb < %s """ % self.mescape(self.dataset.type, + self.chr, + self.dataset.type, + min(self.mb_min, + self.mb_max), + self.dataset.type, + max(self.mb_min, self.mb_max)) + + return where_clause + + def get_chr(self): + try: + self.chr = int(float(self.chr)) + except: + self.chr = self.chr.lower().replace('chr', '') + + def run(self): + + self.get_where_clause() + self.query = self.compile_final_query(where_clause=self.where_clause) + + return self.execute(self.query) + + +class MrnaPositionSearch(PositionSearch, MrnaAssaySearch): + """Searches for genes located within a specified range on a specified chromosome""" + + for search_key in ('POSITION', 'POS', 'MB'): + DoSearch.search_types['ProbeSet_' + search_key] = "MrnaPositionSearch" + + def run(self): + + self.where_clause = self.get_where_clause() + self.query = self.compile_final_query(where_clause=self.where_clause) + + return self.execute(self.query) + + +class GenotypePositionSearch(PositionSearch, GenotypeSearch): + """Searches for genes located within a specified range on a specified chromosome""" + + for search_key in ('POSITION', 'POS', 'MB'): + DoSearch.search_types['Geno_' + search_key] = "GenotypePositionSearch" + + def run(self): + + self.where_clause = self.get_where_clause() + self.query = self.compile_final_query(where_clause=self.where_clause) + + return self.execute(self.query) + + +class PvalueSearch(MrnaAssaySearch): + """Searches for traits with a permutationed p-value between low and high""" + + DoSearch.search_types['ProbeSet_PVALUE'] = "PvalueSearch" + + def run(self): + + self.search_term = [float(value) for value in self.search_term] + + if self.search_operator == "=": + assert isinstance(self.search_term, (list, tuple)) + self.pvalue_min, self.pvalue_max = self.search_term[:2] + self.where_clause = """ %sXRef.pValue > %s and %sXRef.pValue < %s + """ % self.mescape( + self.dataset.type, + min(self.pvalue_min, self.pvalue_max), + self.dataset.type, + max(self.pvalue_min, self.pvalue_max)) + else: + # Deal with >, <, >=, and <= + self.where_clause = """ %sXRef.pValue %s %s + """ % self.mescape( + self.dataset.type, + self.search_operator, + self.search_term[0]) + + self.query = self.compile_final_query(where_clause=self.where_clause) + return self.execute(self.query) + + +class AuthorSearch(PhenotypeSearch): + """Searches for phenotype traits with specified author(s)""" + + DoSearch.search_types["Publish_NAME"] = "AuthorSearch" + + def run(self): + search_term = "%" + self.search_term[0] + "%" + self.where_clause = """ Publication.Authors LIKE "%s" and + """ % (search_term) + + self.query = self.compile_final_query(where_clause=self.where_clause) + + return self.execute(self.query) + + +def is_number(s): + try: + float(s) + return True + except ValueError: + return False + + +if __name__ == "__main__": + # Usually this will be used as a library, but call it from the command line for testing + # And it runs the code below + import sys + + from gn2.base import webqtlConfig + from gn2.base.data_set import create_dataset + from gn2.utility import webqtlUtil + from gn2.db import webqtlDatabaseFunction + + from gn2.wqflask.database import database_connection + + with database_connection(get_setting("SQL_URI")) as db_conn: + with db_conn.cursor() as cursor: + dataset_name = "HC_M2_0606_P" + dataset = create_dataset(db_conn, dataset_name) + + results = PvalueSearch(['0.005'], '<', dataset, cursor, db_conn).run() diff --git a/gn2/wqflask/docs.py b/gn2/wqflask/docs.py new file mode 100644 index 00000000..3453a3c1 --- /dev/null +++ b/gn2/wqflask/docs.py @@ -0,0 +1,42 @@ +import codecs + +from flask import g +from gn2.wqflask.database import database_connection +from gn2.utility.tools import get_setting + +class Docs: + + def __init__(self, entry, start_vars={}): + results = None + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute("SELECT Docs.title, CAST(Docs.content AS BINARY) " + "FROM Docs WHERE Docs.entry LIKE %s", (str(entry),)) + result = cursor.fetchone() + self.entry = entry + if result: + self.title = result[0] + self.content = result[1].decode("utf-8") + else: + self.title = self.entry.capitalize() + self.content = "" + self.editable = "false" + # ZS: Removing option to edit to see if text still gets vandalized + try: + if g.user_session.record['user_email_address'] == "zachary.a.sloan@gmail.com" or g.user_session.record['user_email_address'] == "labwilliams@gmail.com": + self.editable = "true" + except: + pass + + +def update_text(start_vars): + content = start_vars['ckcontent'] + content = content.replace('%', '%%').replace( + '"', '\\"').replace("'", "\\'") + try: + if g.user_session.record.get('user_email_address') in ["zachary.a.sloan@gmail.com", "labwilliams@gmail.com"]: + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + # Disable updates fully - all docs should be in markdown - please move them there, just like the Environments doc + cursor.execute("UPDATEX Docs SET content=%s WHERE entry=%s", + (content, start_vars.get("entry_type"),)) + except: + pass diff --git a/gn2/wqflask/export_traits.py b/gn2/wqflask/export_traits.py new file mode 100644 index 00000000..2d2a40cc --- /dev/null +++ b/gn2/wqflask/export_traits.py @@ -0,0 +1,191 @@ +import csv +import datetime +import io +import itertools +import re +import xlsxwriter + +from pprint import pformat as pf +from zipfile import ZipFile, ZIP_DEFLATED + +import simplejson as json + +from gn3.computations.gemma import generate_hash_of_string + +from gn2.base.trait import create_trait, retrieve_trait_info + + +def export_traits(targs, export_type): + if export_type == "collection": + return export_collection(targs) + else: + return export_traitlist(targs) + +def export_collection(targs): + table_data = json.loads(targs['export_data']) + table_rows = table_data['rows'] + + buff = io.StringIO() + writer = csv.writer(buff) + + now = datetime.datetime.now() + time_str = now.strftime('%H:%M (UTC) %m/%d/%y') + + metadata_rows = [ + ["# Collection Name: " + targs['collection_name_export']], + ["# User E-mail: " + targs['user_email_export']], + ["# Time/Date: " + time_str] + ] + + for row in metadata_rows: + writer.writerow(row) + + for trait in table_rows: + writer.writerow([trait]) + + csv_data = buff.getvalue() + buff.close() + + if 'collection_name_export' in targs: + file_name = re.sub('\s+', '_', targs['collection_name_export']) # replace whitespace with underscore + else: + file_name = generate_hash_of_string("".join(table_rows)) + + return [file_name, csv_data] + +def export_traitlist(targs): + table_data = json.loads(targs['export_data']) + table_rows = table_data['rows'] + + now = datetime.datetime.now() + time_str = now.strftime('%H:%M_%d%B%Y') + if 'file_name' in targs: + zip_file_name = targs['file_name'] + "_export_" + time_str + else: + zip_file_name = "export_" + time_str + + metadata = [] + + if 'database_name' in targs: + if targs['database_name'] != "None": + metadata.append(["Data Set: " + targs['database_name']]) + if 'accession_id' in targs: + if targs['accession_id'] != "None": + metadata.append( + ["Metadata Link: http://genenetwork.org/webqtl/main.py?FormID=sharinginfo&GN_AccessionId=" + targs['accession_id']]) + metadata.append( + ["Export Date: " + datetime.datetime.now().strftime("%B %d, %Y")]) + metadata.append( + ["Export Time: " + datetime.datetime.now().strftime("%H:%M GMT")]) + if 'search_string' in targs: + if targs['search_string'] != "None": + metadata.append(["Search Query: " + targs['search_string']]) + if 'filter_term' in 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: NIGMS (R01 GM123489, 2017-2026), 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 = [] + for trait in table_rows: + trait_name, dataset_name, _hash = trait.split(":") + trait_ob = create_trait(name=trait_name, dataset_name=dataset_name) + trait_ob = retrieve_trait_info( + trait_ob, trait_ob.dataset, get_qtl_info=True) + trait_list.append(trait_ob) + + 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) + + file_list = [] + for group in traits_by_group: + group_traits = traits_by_group[group] + samplelist = group_traits[0].dataset.group.all_samples_ordered() + if not samplelist: + continue + + buff = io.StringIO() + writer = csv.writer(buff) + csv_rows = [] + + sample_headers = [] + for sample in samplelist: + sample_headers.append(sample) + sample_headers.append(sample + "_SE") + + full_headers = table_headers + sample_headers + + for metadata_row in metadata: + writer.writerow(metadata_row) + + csv_rows.append(full_headers) + + for i, trait in enumerate(group_traits): + if getattr(trait, "symbol", None): + trait_symbol = getattr(trait, "symbol") + elif getattr(trait, "abbreviation", None): + trait_symbol = getattr(trait, "abbreviation") + 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, + trait.name, + trait_symbol, + getattr(trait, "description_display", "N/A"), + getattr(trait, "probe_target_description", "N/A"), + getattr(trait, "pubmed_id", "N/A"), + getattr(trait, "chr", "N/A"), + getattr(trait, "mb", "N/A"), + trait.alias_fmt, + getattr(trait, "geneid", "N/A"), + getattr(trait, "homologeneid", "N/A"), + getattr(trait, "unigeneid", "N/A"), + getattr(trait, "strand_probe", "N/A"), + getattr(trait, "probe_set_specificity", "N/A"), + getattr(trait, "probe_set_blat_score", "N/A"), + getattr(trait, "probe_set_blat_mb_start", "N/A"), + getattr(trait, "probe_set_blat_mb_end", "N/A"), + getattr(trait, "locus_chr", "N/A"), + getattr(trait, "locus_mb", "N/A"), + getattr(trait, "locus", "N/A"), + getattr(trait, "lrs", "N/A"), + getattr(trait, "pvalue", "N/A"), + getattr(trait, "mean", "N/A") + ] + + for sample in samplelist: + if sample in trait.data: + row_contents += [trait.data[sample].value, + trait.data[sample].variance] + else: + row_contents += ["x", "x"] + + csv_rows.append(row_contents) + + writer.writerows(csv_rows) + csv_data = buff.getvalue() + buff.close() + + file_name = group + "_traits.csv" + file_list.append([file_name, csv_data]) + + return file_list + + +def sort_traits_by_group(trait_list=[]): + traits_by_group = {} + for trait in trait_list: + if trait.dataset.group.name not in list(traits_by_group.keys()): + traits_by_group[trait.dataset.group.name] = [] + + traits_by_group[trait.dataset.group.name].append(trait) + + return traits_by_group diff --git a/gn2/wqflask/external_tools/__init__.py b/gn2/wqflask/external_tools/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/external_tools/__init__.py diff --git a/gn2/wqflask/external_tools/send_to_bnw.py b/gn2/wqflask/external_tools/send_to_bnw.py new file mode 100644 index 00000000..fdbc0d93 --- /dev/null +++ b/gn2/wqflask/external_tools/send_to_bnw.py @@ -0,0 +1,70 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Dr. Robert W. Williams at rwilliams@uthsc.edu +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) + +from gn2.base.trait import GeneralTrait +from gn2.utility import helper_functions, corr_result_helpers + + +class SendToBNW: + def __init__(self, start_vars): + trait_db_list = [trait.strip() + for trait in start_vars['trait_list'].split(',')] + helper_functions.get_trait_db_obs(self, trait_db_list) + + trait_samples_list = [] + + for trait_db in self.trait_list: + trait_1 = trait_db[0] + this_sample_data = trait_1.data + + trait1_samples = list(this_sample_data.keys()) + trait_samples_list.append(trait1_samples) + + shared_samples = list( + set(trait_samples_list[0]).intersection(*trait_samples_list)) + + self.form_value = "" # ZS: string that is passed to BNW through form + values_list = [] + for trait_db in self.trait_list: + this_trait = trait_db[0] + this_sample_data = this_trait.data + + trait_vals = [] + for sample in this_sample_data: + if sample in shared_samples: + trait_vals.append(this_sample_data[sample].value) + + values_list.append(trait_vals) + self.form_value += "_" + str(this_trait.name) + "," + + values_list = zip(*values_list) + self.form_value = self.form_value[:-1] + self.form_value += ";" + + for row in values_list: + has_none = False + for cell in row: + if not cell: + has_none = True + break + if has_none: + continue + self.form_value += ",".join(str(cell) for cell in row) + self.form_value += ";" diff --git a/gn2/wqflask/external_tools/send_to_geneweaver.py b/gn2/wqflask/external_tools/send_to_geneweaver.py new file mode 100644 index 00000000..76ff7302 --- /dev/null +++ b/gn2/wqflask/external_tools/send_to_geneweaver.py @@ -0,0 +1,113 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Dr. Robert W. Williams at rwilliams@uthsc.edu +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) +from gn2.wqflask.database import database_connection +from gn2.utility import helper_functions +from gn2.utility.tools import get_setting + + +class SendToGeneWeaver: + def __init__(self, start_vars): + trait_db_list = [trait.strip() + for trait in start_vars['trait_list'].split(',')] + helper_functions.get_trait_db_obs(self, trait_db_list) + + self.chip_name = test_chip(self.trait_list) + self.wrong_input = "False" + if self.chip_name == "mixed" or self.chip_name == "not_microarray" or '_NA' in self.chip_name: + self.wrong_input = "True" + else: + species = self.trait_list[0][1].group.species + if species == "rat": + species_name = "Rattus norvegicus" + elif species == "human": + species_name = "Homo sapiens" + elif species == "mouse": + species_name = "Mus musculus" + else: + species_name = "" + + trait_name_list = get_trait_name_list(self.trait_list) + + self.hidden_vars = { + 'client': "genenetwork", + 'species': species_name, + 'idtype': self.chip_name, + 'list': ",".join(trait_name_list), + } + + +def get_trait_name_list(trait_list): + name_list = [] + for trait_db in trait_list: + name_list.append(trait_db[0].name) + + return name_list + + +def test_chip(trait_list): + final_chip_name = "" + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + for trait_db in trait_list: + dataset = trait_db[1] + cursor.execute( + "SELECT GeneChip.GO_tree_value " + "FROM GeneChip, ProbeFreeze, ProbeSetFreeze " + "WHERE GeneChip.Id = ProbeFreeze.ChipId " + "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "AND ProbeSetFreeze.Name = %s", + (dataset.name,) + ) + + if result := cursor.fetchone: + chip_name = result[0] + if chip_name: + if chip_name != final_chip_name: + if final_chip_name: + return "mixed" + else: + final_chip_name = chip_name + else: + pass + else: + cursor.execute( + "SELECT GeneChip.Name " + "FROM GeneChip, ProbeFreeze, ProbeSetFreeze " + "WHERE GeneChip.Id = ProbeFreeze.ChipId " + "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "AND ProbeSetFreeze.Name = %s", + (dataset.name,) + ) + chip_name = f'{cursor.fetchone()[0]}_NA' + return chip_name + else: + cursor.execute( + "SELECT GeneChip.Name FROM GeneChip, " + "ProbeFreeze, ProbeSetFreeze WHERE " + "GeneChip.Id = ProbeFreeze.ChipId " + "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "AND ProbeSetFreeze.Name = %s", + (dataset.name,) + ) + if result := cursor.fetchone(): + chip_name = f'{result[0]}_NA' + return chip_name + return "not_microarray" + + return chip_name diff --git a/gn2/wqflask/external_tools/send_to_webgestalt.py b/gn2/wqflask/external_tools/send_to_webgestalt.py new file mode 100644 index 00000000..9633e560 --- /dev/null +++ b/gn2/wqflask/external_tools/send_to_webgestalt.py @@ -0,0 +1,129 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Dr. Robert W. Williams at rwilliams@uthsc.edu +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) + +from gn2.wqflask.database import database_connection + +from gn2.base.trait import retrieve_trait_info +from gn2.utility import helper_functions +from gn2.utility.tools import get_setting + + +class SendToWebGestalt: + def __init__(self, start_vars): + trait_db_list = [trait.strip() + for trait in start_vars['trait_list'].split(',')] + helper_functions.get_trait_db_obs(self, trait_db_list) + + self.chip_name = test_chip(self.trait_list) + + self.wrong_input = "False" + if self.chip_name == "mixed" or self.chip_name == "not_microarray" or '_NA' in self.chip_name: + self.wrong_input = "True" + else: + trait_name_list, gene_id_list = gen_gene_id_list(self.trait_list) + + self.target_url = "https://www.webgestalt.org/option.php" + + id_type = "entrezgene" + + self.hidden_vars = { + 'gene_list': "\n".join(gene_id_list), + 'id_type': "entrezgene", + 'ref_set': "genome", + 'enriched_database_category': "geneontology", + 'enriched_database_name': "Biological_Process", + 'sig_method': "fdr", + 'sig_value': "0.05", + 'enrich_method': "ORA", + 'fdr_method': "BH", + 'min_num': "2" + } + + species = self.trait_list[0][1].group.species + if species == "rat": + self.hidden_vars['organism'] = "rnorvegicus" + elif species == "human": + self.hidden_vars['organism'] = "hsapiens" + elif species == "mouse": + self.hidden_vars['organism'] = "mmusculus" + else: + self.hidden_vars['organism'] = "others" + + +def test_chip(trait_list): + final_chip_name = "" + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + for trait_db in trait_list: + dataset = trait_db[1] + cursor.execute( + "SELECT GeneChip.GO_tree_value " + "FROM GeneChip, ProbeFreeze, " + "ProbeSetFreeze WHERE " + "GeneChip.Id = ProbeFreeze.ChipId " + "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "AND ProbeSetFreeze.Name = %s", (dataset.name,) + ) + + if result := cursor.fetchone(): + chip_name = result[0] + if chip_name: + if chip_name != final_chip_name: + if final_chip_name: + return "mixed" + else: + final_chip_name = chip_name + else: + pass + else: + cursor.execute( + "SELECT GeneChip.Name FROM GeneChip, ProbeFreeze, " + "ProbeSetFreeze WHERE " + "GeneChip.Id = ProbeFreeze.ChipId AND " + "ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "AND ProbeSetFreeze.Name = %s", (dataset.name,) + ) + result = cursor.fetchone() + chip_name = f'{result[0]}_NA' + return chip_name + else: + cursor.execute( + "SELECT GeneChip.Name FROM GeneChip, ProbeFreeze, " + "ProbeSetFreeze WHERE GeneChip.Id = ProbeFreeze.ChipId " + "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id AND " + "ProbeSetFreeze.Name = %s", (dataset.name,) + ) + result = cursor.fetchone() + if not result: + return "not_microarray" + else: + chip_name = f'{result[0]}_NA' + return chip_name + return chip_name + + +def gen_gene_id_list(trait_list): + trait_name_list = [] + gene_id_list = [] + for trait_db in trait_list: + trait = trait_db[0] + trait_name_list.append(trait.name) + retrieve_trait_info(trait, trait.dataset) + gene_id_list.append(str(trait.geneid)) + return trait_name_list, gene_id_list diff --git a/gn2/wqflask/group_manager.py b/gn2/wqflask/group_manager.py new file mode 100644 index 00000000..b589acb6 --- /dev/null +++ b/gn2/wqflask/group_manager.py @@ -0,0 +1,157 @@ +import json +import redis +import datetime + +from flask import current_app +from flask import Blueprint +from flask import g +from flask import render_template +from flask import request +from flask import redirect +from flask import url_for +from gn3.authentication import get_groups_by_user_uid +from gn3.authentication import get_user_info_by_key +from gn3.authentication import create_group +from gn2.wqflask.decorators import login_required + +group_management = Blueprint("group_management", __name__) + + +@group_management.route("/groups") +@login_required() +def display_groups(): + groups = get_groups_by_user_uid( + user_uid=(g.user_session.record.get(b"user_id", + b"").decode("utf-8") or + g.user_session.record.get("user_id", "")), + conn=redis.from_url( + current_app.config["REDIS_URL"], + decode_responses=True)) + return render_template("admin/group_manager.html", + admin_groups=groups.get("admin"), + member_groups=groups.get("member")) + + +@group_management.route("/groups/create", methods=("GET",)) +@login_required() +def view_create_group_page(): + return render_template("admin/create_group.html") + + +@group_management.route("/groups/create", methods=("POST",)) +@login_required() +def create_new_group(): + conn = redis.from_url(current_app.config["REDIS_URL"], + decode_responses=True) + if group_name := request.form.get("group_name"): + members_uid, admins_uid = set(), set() + admins_uid.add(user_uid := ( + g.user_session.record.get( + b"user_id", + b"").decode("utf-8") or + g.user_session.record.get("user_id", ""))) + if admin_string := request.form.get("admin_emails_to_add"): + for email in admin_string.split(","): + user_info = get_user_info_by_key(key="email_address", + value=email, + conn=conn) + if user_uid := user_info.get("user_id"): + admins_uid.add(user_uid) + if member_string := request.form.get("member_emails_to_add"): + for email in member_string.split(","): + user_info = get_user_info_by_key(key="email_address", + value=email, + conn=conn) + if user_uid := user_info.get("user_id"): + members_uid.add(user_uid) + + # Create the new group: + create_group(conn=conn, + group_name=group_name, + member_user_uids=list(members_uid), + admin_user_uids=list(admins_uid)) + return redirect(url_for('group_management.display_groups')) + return redirect(url_for('group_management.create_groups')) + + +@group_management.route("/groups/delete", methods=("POST",)) +@login_required() +def delete_groups(): + conn = redis.from_url(current_app.config["REDIS_URL"], + decode_responses=True) + user_uid = (g.user_session.record.get(b"user_id", b"").decode("utf-8") or + g.user_session.record.get("user_id", "")) + current_app.logger.info(request.form.get("selected_group_ids")) + for group_uid in request.form.get("selected_group_ids", "").split(":"): + if group_info := conn.hget("groups", group_uid): + group_info = json.loads(group_info) + # A user who is an admin can delete things + if user_uid in group_info.get("admins"): + conn.hdel("groups", group_uid) + return redirect(url_for('group_management.display_groups')) + + +@group_management.route("/groups/<group_id>") +@login_required() +def view_group(group_id: str): + conn = redis.from_url(current_app.config["REDIS_URL"], + decode_responses=True) + user_uid = (g.user_session.record.get(b"user_id", b"").decode("utf-8") or + g.user_session.record.get("user_id", "")) + + resource_info = [] + for resource_uid, resource in conn.hgetall("resources").items(): + resource = json.loads(resource) + if group_id in (group_mask := resource.get("group_masks")): + __dict = {} + for val in group_mask.values(): + __dict.update(val) + __dict.update({ + "id": resource_uid, + "name": resource.get("name"), + }) + resource_info.append(__dict) + group_info = json.loads(conn.hget("groups", + group_id)) + group_info["guid"] = group_id + + return render_template( + "admin/view_group.html", + group_info=group_info, + admins=[get_user_info_by_key(key="user_id", + value=user_id, + conn=conn) + for user_id in group_info.get("admins")], + members=[get_user_info_by_key(key="user_id", + value=user_id, + conn=conn) + for user_id in group_info.get("members")], + is_admin = (True if user_uid in group_info.get("admins") else False), + resources=resource_info) + + +@group_management.route("/groups/<group_id>", methods=("POST",)) +def update_group(group_id: str): + conn = redis.from_url(current_app.config["REDIS_URL"], + decode_responses=True) + user_uid = (g.user_session.record.get(b"user_id", b"").decode("utf-8") or + g.user_session.record.get("user_id", "")) + group = json.loads(conn.hget("groups", group_id)) + timestamp = group["changed_timestamp"] + timestamp_ = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') + if user_uid in group.get("admins"): + if name := request.form.get("new_name"): + group["name"] = name + group["changed_timestamp"] = timestamp_ + if admins := request.form.get("admin_emails_to_add"): + group["admins"] = list(set(admins.split(":") + + group.get("admins"))) + group["changed_timestamp"] = timestamp_ + if members := request.form.get("member_emails_to_add"): + print(f"\n+++++\n{members}\n+++++\n") + group["members"] = list(set(members.split(":") + + group.get("members"))) + group["changed_timestamp"] = timestamp_ + conn.hset("groups", group_id, json.dumps(group)) + return redirect(url_for('group_management.view_group', + group_id=group_id)) diff --git a/gn2/wqflask/gsearch.py b/gn2/wqflask/gsearch.py new file mode 100644 index 00000000..cad6db94 --- /dev/null +++ b/gn2/wqflask/gsearch.py @@ -0,0 +1,62 @@ +from urllib.parse import urlencode, urljoin + +from pymonad.maybe import Just, Maybe +from pymonad.tools import curry +import requests + +from gn3.monads import MonadicDict +from gn2.utility.hmac import hmac_creation +from gn2.utility.tools import GN3_LOCAL_URL +from gn2.base import webqtlConfig + +# KLUDGE: Due to the lack of pagination, we hard-limit the maximum +# number of search results. +MAX_SEARCH_RESULTS = 10000 + +class GSearch: + def __init__(self, kwargs): + if ("type" not in kwargs) or ("terms" not in kwargs): + raise ValueError + self.type = kwargs["type"] + self.terms = kwargs["terms"] + + # FIXME: Handle presentation (that is, formatting strings for + # display) in the template rendering, not when retrieving + # search results. + chr_mb = curry(2, lambda chr, mb: f"Chr{chr}: {mb:.6f}") + format3f = lambda x: f"{x:.3f}" + hmac = curry(3, lambda trait_name, dataset, data_hmac: f"{trait_name}:{dataset}:{data_hmac}") + convert_lod = lambda x: x / 4.61 + self.trait_list = [] + for i, trait in enumerate(requests.get( + urljoin(GN3_LOCAL_URL, "/api/search?" + urlencode({"query": self.terms, + "type": self.type, + "per_page": MAX_SEARCH_RESULTS}))).json()): + trait = MonadicDict(trait) + trait["index"] = Just(i) + trait["location_repr"] = (Maybe.apply(chr_mb) + .to_arguments(trait.pop("chr"), trait.pop("mb"))) + trait["LRS_score_repr"] = trait.pop("lrs").map(convert_lod).map(format3f) + trait["additive"] = trait["additive"].map(format3f) + trait["mean"] = trait["mean"].map(format3f) + trait["max_lrs_text"] = (Maybe.apply(chr_mb) + .to_arguments(trait.pop("geno_chr"), trait.pop("geno_mb"))) + if self.type == "gene": + trait["hmac"] = (Maybe.apply(hmac) + .to_arguments(trait['name'], trait['dataset'], Just(hmac_creation(f"{trait['name']}:{trait['dataset']}")))) + elif self.type == "phenotype": + trait["display_name"] = trait["name"] + inbredsetcode = trait.pop("inbredsetcode") + if inbredsetcode.map(len) == Just(3): + trait["display_name"] = (Maybe.apply( + curry(2, lambda inbredsetcode, name: f"{inbredsetcode}_{name}")) + .to_arguments(inbredsetcode, trait["name"])) + + trait["hmac"] = (Maybe.apply(hmac) + .to_arguments(trait['name'], trait['dataset'], Just(hmac_creation(f"{trait['name']}:{trait['dataset']}")))) + trait["authors_display"] = (trait.pop("authors").map( + lambda authors: + ", ".join(authors[:2] + ["et al."] if len(authors) >=2 else authors))) + trait["pubmed_text"] = trait["year"].map(str) + self.trait_list.append(trait.data) + self.trait_count = len(self.trait_list) diff --git a/gn2/wqflask/heatmap/__init__.py b/gn2/wqflask/heatmap/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/heatmap/__init__.py diff --git a/gn2/wqflask/heatmap/heatmap.py b/gn2/wqflask/heatmap/heatmap.py new file mode 100644 index 00000000..c2dd55bd --- /dev/null +++ b/gn2/wqflask/heatmap/heatmap.py @@ -0,0 +1,191 @@ +import string +import os +import random +from gn2.base import species +from gn2.base import webqtlConfig +from gn2.utility import helper_functions + +from gn2.utility.tools import flat_files, REAPER_COMMAND, TEMPDIR +from redis import Redis +from flask import Flask, g + +from gn2.wqflask.database import database_connection +from gn2.utility.tools import get_setting + +Redis = Redis() + + +class Heatmap: + + def __init__(self, db_cursor, start_vars, temp_uuid): + trait_db_list = [trait.strip() + for trait in start_vars['trait_list'].split(',')] + helper_functions.get_trait_db_obs(self, trait_db_list) + + self.temp_uuid = temp_uuid + self.num_permutations = 5000 + self.dataset = self.trait_list[0][1] + + self.json_data = {} # The dictionary that will be used to create the json object that contains all the data needed to create the figure + + self.all_sample_list = [] + self.traits = [] + + chrnames = [] + self.species = species.TheSpecies(dataset=self.trait_list[0][1]) + + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as db_cursor: + for this_chr in self.species.chromosomes.chromosomes(db_cursor): + chrnames.append([self.species.chromosomes.chromosomes(db_cursor)[this_chr].name, + self.species.chromosomes.chromosomes(db_cursor)[this_chr].mb_length]) + + for trait_db in self.trait_list: + + this_trait = trait_db[0] + self.traits.append(this_trait.name) + this_sample_data = this_trait.data + + for sample in this_sample_data: + if sample not in self.all_sample_list: + self.all_sample_list.append(sample) + + self.sample_data = [] + for trait_db in self.trait_list: + this_trait = trait_db[0] + this_sample_data = this_trait.data + + this_trait_vals = [] + for sample in self.all_sample_list: + if sample in this_sample_data: + this_trait_vals.append(this_sample_data[sample].value) + else: + this_trait_vals.append('') + self.sample_data.append(this_trait_vals) + + self.gen_reaper_results() + + lodnames = [] + chr_pos = [] + pos = [] + markernames = [] + + for trait in list(self.trait_results.keys()): + lodnames.append(trait) + + self.dataset.group.get_markers() + for marker in self.dataset.group.markers.markers: + chr_pos.append(marker['chr']) + pos.append(marker['Mb']) + markernames.append(marker['name']) + + self.json_data['chrnames'] = chrnames + self.json_data['lodnames'] = lodnames + self.json_data['chr'] = chr_pos + self.json_data['pos'] = pos + self.json_data['markernames'] = markernames + + for trait in self.trait_results: + self.json_data[trait] = self.trait_results[trait] + + self.js_data = dict( + json_data=self.json_data + ) + + def gen_reaper_results(self): + self.trait_results = {} + for trait_db in self.trait_list: + self.dataset.group.get_markers() + this_trait = trait_db[0] + + genotype = self.dataset.group.read_genotype_file(use_reaper=False) + samples, values, variances, sample_aliases = this_trait.export_informative() + + if self.dataset.group.genofile != None: + genofile_name = self.dataset.group.genofile[:-5] + else: + genofile_name = self.dataset.group.name + + trimmed_samples = [] + trimmed_values = [] + for i in range(0, len(samples)): + if samples[i] in self.dataset.group.samplelist: + trimmed_samples.append(str(samples[i])) + trimmed_values.append(values[i]) + + trait_filename = str(this_trait.name) + "_" + \ + str(self.dataset.name) + "_pheno" + gen_pheno_txt_file(trimmed_samples, trimmed_values, trait_filename) + + output_filename = self.dataset.group.name + "_GWA_" + \ + ''.join(random.choice(string.ascii_uppercase + string.digits) + for _ in range(6)) + + reaper_command = REAPER_COMMAND + ' --geno {0}/{1}.geno --traits {2}/gn2/{3}.txt -n 1000 -o {4}{5}.txt'.format(flat_files('genotype'), + genofile_name, + TEMPDIR, + trait_filename, + webqtlConfig.GENERATED_IMAGE_DIR, + output_filename) + + os.system(reaper_command) + + reaper_results = parse_reaper_output(output_filename) + + lrs_values = [float(qtl['lrs_value']) for qtl in reaper_results] + + self.trait_results[this_trait.name] = [] + for qtl in reaper_results: + if qtl['additive'] > 0: + self.trait_results[this_trait.name].append( + -float(qtl['lrs_value'])) + else: + self.trait_results[this_trait.name].append( + float(qtl['lrs_value'])) + + +def gen_pheno_txt_file(samples, vals, filename): + """Generates phenotype file for GEMMA""" + + with open("{0}/gn2/{1}.txt".format(TEMPDIR, filename), "w") as outfile: + outfile.write("Trait\t") + + filtered_sample_list = [] + filtered_vals_list = [] + for i, sample in enumerate(samples): + if vals[i] != "x": + filtered_sample_list.append(sample) + filtered_vals_list.append(str(vals[i])) + + samples_string = "\t".join(filtered_sample_list) + outfile.write(samples_string + "\n") + outfile.write("T1\t") + values_string = "\t".join(filtered_vals_list) + outfile.write(values_string) + + +def parse_reaper_output(gwa_filename): + included_markers = [] + p_values = [] + marker_obs = [] + + with open("{}{}.txt".format(webqtlConfig.GENERATED_IMAGE_DIR, gwa_filename)) as output_file: + for line in output_file: + if line.startswith("ID\t"): + continue + else: + marker = {} + marker['name'] = line.split("\t")[1] + try: + marker['chr'] = int(line.split("\t")[2]) + except: + marker['chr'] = line.split("\t")[2] + marker['cM'] = float(line.split("\t")[3]) + marker['Mb'] = float(line.split("\t")[4]) + if float(line.split("\t")[7]) != 1: + marker['p_value'] = float(line.split("\t")[7]) + marker['lrs_value'] = float(line.split("\t")[5]) + marker['lod_score'] = marker['lrs_value'] / 4.61 + marker['additive'] = float(line.split("\t")[6]) + marker_obs.append(marker) + + return marker_obs diff --git a/gn2/wqflask/interval_analyst/GeneUtil.py b/gn2/wqflask/interval_analyst/GeneUtil.py new file mode 100644 index 00000000..d3b00e31 --- /dev/null +++ b/gn2/wqflask/interval_analyst/GeneUtil.py @@ -0,0 +1,155 @@ +import string + +from gn2.wqflask.database import database_connection + +from gn2.utility.tools import flat_files, get_setting + +def load_homology(chr_name, start_mb, end_mb, source_file): + homology_list = [] + with open(flat_files("homology/") + source_file) as h_file: + current_chr = 0 + for line in h_file: + line_items = line.split() + this_dict = { + "ref_chr": line_items[2][3:], + "ref_strand": line_items[4], + "ref_start": float(line_items[5])/1000000, + "ref_end": float(line_items[6])/1000000, + "query_chr": line_items[7][3:], + "query_strand": line_items[9], + "query_start": float(line_items[10])/1000000, + "query_end": float(line_items[11])/1000000 + } + + if str(this_dict["ref_chr"]) == str(chr_name) and \ + this_dict["ref_start"] < end_mb and \ + this_dict["ref_end"] > start_mb: + homology_list.append(this_dict) + + return homology_list + +def loadGenes(chrName, diffCol, startMb, endMb, species='mouse'): + assembly_map = { + "mouse": "mm10", + "rat": "rn7" + } + + def append_assembly(fetch_fields, species): + query_fields = [] + for field in fetch_fields: + if field in ['Chr', 'TxStart', 'TxEnd', 'Strand']: + query_fields.append(field + "_" + assembly_map[species]) + else: + query_fields.append(field) + + return query_fields + + + fetchFields = ['SpeciesId', 'Id', 'GeneSymbol', 'GeneDescription', 'Chr', 'TxStart', 'TxEnd', + 'Strand', 'GeneID', 'NM_ID', 'kgID', 'GenBankID', 'UnigenID', 'ProteinID', 'AlignID', + 'exonCount', 'exonStarts', 'exonEnds', 'cdsStart', 'cdsEnd'] + + # List All Species in the Gene Table + speciesDict = {} + results = [] + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute("SELECT Species.Name, GeneList081722.SpeciesId " + "FROM Species, GeneList081722 WHERE " + "GeneList081722.SpeciesId = Species.Id " + "GROUP BY GeneList081722.SpeciesId") + results = cursor.fetchall() + for item in results: + if item[0] == "rat": + speciesDict[item[0]] = (item[1], "rn7") + else: + speciesDict[item[0]] = (item[1], "mm10") + + # List current Species and other Species + speciesId, assembly = speciesDict[species] + otherSpecies = [[X, speciesDict[X][0], speciesDict[X][1]] for X in list(speciesDict.keys())] + otherSpecies.remove([species, speciesId, assembly]) + query_fields = append_assembly(fetchFields, species) + + cursor.execute(f"SELECT {', '.join(query_fields)} FROM GeneList081722 " + "WHERE SpeciesId = %s AND " + f"Chr_{assembly}" + " = %s AND " + f"((TxStart_{assembly}" + " > %s and " + f"TxStart_{assembly}" + " <= %s) " + f"OR (TxEnd_{assembly}" + " > %s and " + f"TxEnd_{assembly}" + " <= %s)) " + f"ORDER BY TxStart_{assembly}", + (speciesId, chrName, + startMb, endMb, + startMb, endMb)) + results = cursor.fetchall() + + GeneList = [] + if results: + for result in results: + newdict = {} + for j, item in enumerate(fetchFields): + newdict[item] = result[j] + # count SNPs if possible + if diffCol and species == 'mouse': + cursor.execute( + "SELECT count(*) FROM BXDSnpPosition " + "WHERE Chr = %s AND " + "Mb >= %s AND Mb < %s " + "AND StrainId1 = %s AND StrainId2 = %s", + (chrName, f"{newdict['TxStart']:2.6f}", + f"{newdict['TxEnd']:2.6f}", + diffCol[0], diffCol[1],)) + newdict["snpCount"] = cursor.fetchone()[0] + newdict["snpDensity"] = ( + newdict["snpCount"] / + (newdict["TxEnd"] - newdict["TxStart"]) / 1000.0) + else: + newdict["snpDensity"] = newdict["snpCount"] = 0 + try: + newdict['GeneLength'] = 1000.0 * \ + (newdict['TxEnd'] - newdict['TxStart']) + except: + pass + # load gene from other Species by the same name + for item in otherSpecies: + othSpec, othSpecId, othSpecAssembly = item + newdict2 = {} + query_fields = append_assembly(fetchFields, othSpec) + cursor.execute( + f"SELECT {', '.join(query_fields)} FROM GeneList081722 WHERE " + "SpeciesId = %s AND " + "geneSymbol= %s LIMIT 1", + (othSpecId, + newdict["GeneSymbol"])) + resultsOther = cursor.fetchone() + if resultsOther: + for j, item in enumerate(fetchFields): + newdict2[item] = resultsOther[j] + + # count SNPs if possible, could be a separate function + if diffCol and othSpec == 'mouse': + cursor.execute( + "SELECT count(*) FROM BXDSnpPosition " + "WHERE Chr = %s AND Mb >= %s AND " + "Mb < %s AND StrainId1 = %s " + "AND StrainId2 = %s", + (chrName, f"{newdict['TxStart']:2.6f}", + f"{newdict['TxEnd']:2.6f}", + diffCol[0], diffCol[1])) + if snp_count := cursor.fetchone(): + newdict2["snpCount"] = snp_count[0] + + newdict2["snpDensity"] = ( + newdict2["snpCount"] + / (newdict2["TxEnd"] - newdict2["TxStart"]) + / 1000.0) + else: + newdict2["snpDensity"] = newdict2["snpCount"] = 0 + try: + newdict2['GeneLength'] = ( + 1000.0 * (newdict2['TxEnd'] - newdict2['TxStart'])) + except: + pass + + newdict['%sGene' % othSpec] = newdict2 + + GeneList.append(newdict) + return GeneList diff --git a/gn2/wqflask/interval_analyst/__init__.py b/gn2/wqflask/interval_analyst/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/interval_analyst/__init__.py diff --git a/gn2/wqflask/jupyter_notebooks.py b/gn2/wqflask/jupyter_notebooks.py new file mode 100644 index 00000000..a6d06af0 --- /dev/null +++ b/gn2/wqflask/jupyter_notebooks.py @@ -0,0 +1,31 @@ +from flask import Blueprint, render_template + +jupyter_notebooks = Blueprint("jupyter_notebooks", __name__) + + +@jupyter_notebooks.route("/launcher", methods=("GET",)) +def launcher(): + links = ( + { + "main_url": "http://notebook.genenetwork.org/58965/notebooks/2020-05-08/solberg-rat-analysis.ipynb", + "notebook_name": "Quantitative Genetics Tools for Mapping Trait Variation to Mechanisms, Therapeutics, and Interventions - Webinar Series", + "src_link_url": "https://github.com/senresearch/quant-genetics-webinars", + }, + { + "main_url": "http://notebook.genenetwork.org/58163/notebooks/BXD%20Analysis.ipynb", + "notebook_name": "This shows how to model BXD mouse weight data using an AR(1) process.", + "src_link_url": "https://github.com/BonfaceKilz/tsaf-analysis-of-bxd-mouse-colonies", + }, + { + "main_url": "http://notebook.genenetwork.org/46649/notebooks/genenetwork.ipynb", + "notebook_name": "Querying the GeneNetwork API declaratively with python.", + "src_link_url": "https://github.com/jgarte/genenetwork-jupyter-notebook-example", + }, + { + "main_url": "http://notebook.genenetwork.org/37279/notebooks/genenetwork-api-using-r.ipynb", + "notebook_name": "R notebook showing how to query the GeneNetwork API.", + "src_link_url": "https://github.com/jgarte/genenetwork-api-r-jupyter-notebook", + }, + ) + + return render_template("jupyter_notebooks.html", links=links) diff --git a/gn2/wqflask/marker_regression/__init__.py b/gn2/wqflask/marker_regression/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/marker_regression/__init__.py diff --git a/gn2/wqflask/marker_regression/display_mapping_results.py b/gn2/wqflask/marker_regression/display_mapping_results.py new file mode 100644 index 00000000..b3bf3bc3 --- /dev/null +++ b/gn2/wqflask/marker_regression/display_mapping_results.py @@ -0,0 +1,3336 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010) +# at rwilliams@uthsc.edu and xzhou15@uthsc.edu +# +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) +# +# Created by GeneNetwork Core Team 2010/08/10 +# +# Last updated by Zach 12/14/2010 + +import datetime +import string +from math import * +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +from PIL import ImageColor +import os +import json + +import htmlgen as HT + +from gn2.base import webqtlConfig +from gn2.base.GeneralObject import GeneralObject +from gn2.utility import webqtlUtil +from gn2.utility import Plot +from gn2.utility.tools import get_setting +from gn2.wqflask.interval_analyst import GeneUtil +from gn2.base.webqtlConfig import GENERATED_IMAGE_DIR +from gn2.utility.pillow_utils import draw_rotated_text, draw_open_polygon +from gn2.wqflask.database import database_connection + +try: # Only import this for Python3 + from functools import reduce +except: + pass + +RED = ImageColor.getrgb("red") +BLUE = ImageColor.getrgb("blue") +GRAY = ImageColor.getrgb("gray") +GOLD = ImageColor.getrgb("gold") +BLACK = ImageColor.getrgb("black") +GREEN = ImageColor.getrgb("green") +PURPLE = ImageColor.getrgb("purple") +ORANGE = ImageColor.getrgb("orange") +YELLOW = ImageColor.getrgb("yellow") +DARKRED = ImageColor.getrgb("darkred") +DARKBLUE = ImageColor.getrgb("darkblue") +DARKGRAY = ImageColor.getrgb("darkgray") +DEEPPINK = ImageColor.getrgb("deeppink") +DARKGREEN = ImageColor.getrgb("darkgreen") +GAINSBORO = ImageColor.getrgb("gainsboro") +LIGHTBLUE = ImageColor.getrgb("lightblue") +DARKORANGE = ImageColor.getrgb("darkorange") +DARKVIOLET = ImageColor.getrgb("darkviolet") +MEDIUMPURPLE = ImageColor.getrgb("mediumpurple") +# ---- END: Define common colours ---- # + +# ZS: List of distinct colors for manhattan plot if user selects "varied" +COLOR_CODES = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", + "#000000", "#800000", "#008000", "#000080", "#808000", "#800080", + "#008080", "#808080", "#C00000", "#00C000", "#0000C0", "#C0C000", + "#C000C0", "#00C0C0", "#C0C0C0", "#400000", "#004000", "#000040"] + +DISTINCT_COLOR_LIST = [ImageColor.getrgb(color) for color in COLOR_CODES] + +# ---- FONT FILES ---- # +VERDANA_FILE = "./gn2/wqflask/static/fonts/verdana.ttf" +VERDANA_BOLD_FILE = "./gn2/wqflask/static/fonts/verdanab.ttf" +TREBUC_FILE = "./gn2/wqflask/static/fonts/trebucbd.ttf" +FNT_BS_FILE = "./gn2/wqflask/static/fonts/fnt_bs.ttf" +ARIAL_FILE = "./gn2/wqflask/static/fonts/arial.ttf" + +assert(os.path.isfile(VERDANA_FILE)) + +class HtmlGenWrapper: + """Wrapper Methods for HTML gen""" + @staticmethod + def create_image_tag(**kwargs): + image = HT.Image("", "") + for key, value in list(kwargs.items()): + image.set_attribute(key, value) + return image + + @staticmethod + def create_form_tag(**kwargs): + form = HT.Form("POST", "") # Default method is POST + + for key, value in list(kwargs.items()): + if key == "submit": + form.append(value) + continue + form.set_attribute(key.replace("cgi", "action"), str(value)) + return form + + @staticmethod + def create_p_tag(**kwargs): + paragraph = HT.Paragraph() + for key, value in list(kwargs.items()): + paragraph.set_attribute(key, value) + return paragraph + + @staticmethod + def create_br_tag(): + return HT.VoidElement("br") + + @staticmethod + def create_input_tag(**kwargs): + input_ = HT.Input() + for key, value in list(kwargs.items()): + input_.set_attribute(key.lower().replace("_", ""), value) + return input_ + + @staticmethod + def create_area_tag(**kwargs): + area = HT.VoidElement("area") + for key, value in list(kwargs.items()): + area.set_attribute(key, value) + return area + + @staticmethod + def create_link_tag(href, content, **kwargs): + link = HT.Link(href, content) + for key, value in list(kwargs.items()): + link.set_attribute(key, value) + return link + + @staticmethod + def create_map_tag(**kwargs): + map_ = HT.Element("map") + for key, value in list(kwargs.items()): + map_.set_attribute(key, value) + return map_ + + +class DisplayMappingResults: + """Inteval Mapping Plot Page""" + cMGraphInterval = 5 + GRAPH_MIN_WIDTH = 900 + GRAPH_MAX_WIDTH = 10000 # Don't set this too high + GRAPH_DEFAULT_WIDTH = 1280 + MULT_GRAPH_DEFAULT_WIDTH = 2000 + MULT_GRAPH_MIN_WIDTH = 1400 + MULT_GRAPH_DEFAULT_WIDTH = 1600 + GRAPH_DEFAULT_HEIGHT = 600 + + # Display order: + # UCSC BAND ========= + # ENSEMBL BAND -=-=-= + # ** GENES ********** + BAND_SPACING = 4 + + BAND_HEIGHT = 10 + BAND_HEIGHT = 10 + BAND_HEIGHT = 10 + + NUM_GENE_ROWS = 10 + EACH_GENE_HEIGHT = 6 # number of pixels tall, for each gene to display + EACH_GENE_ARROW_WIDTH = 5 + EACH_GENE_ARROW_SPACING = 14 + DRAW_DETAIL_MB = 4 + DRAW_UTR_LABELS_MB = 4 + + qmarkImg = HtmlGenWrapper.create_image_tag( + src='/images/qmarkBoxBlue.gif', + width="10", height="13", border="0", alt='Glossary' + ) + + # Note that "qmark.gif" is a similar, smaller, rounded-edges + # question mark. It doesn't look like the ones on the image, + # though, which is why we don't use it here. + + HELP_WINDOW_NAME = 'helpWind' + + # BEGIN HaplotypeAnalyst + NR_INDIVIDUALS = 0 + # END HaplotypeAnalyst + + ALEX_DEBUG_BOOL_PRINT_GENE_LIST = 1 + + kONE_MILLION = 1000000 + + LODFACTOR = 4.61 + + SNP_COLOR = ORANGE # Color for the SNP "seismograph" + TRANSCRIPT_LOCATION_COLOR = MEDIUMPURPLE + + BOOTSTRAP_BOX_COLOR = YELLOW + LRS_COLOR = ImageColor.getrgb("#0000FF") + SIGNIFICANT_COLOR = ImageColor.getrgb("#EBC7C7") + SUGGESTIVE_COLOR = GAINSBORO + SIGNIFICANT_WIDTH = 5 + SUGGESTIVE_WIDTH = 5 + ADDITIVE_COLOR_POSITIVE = GREEN + ADDITIVE_COLOR_NEGATIVE = ORANGE + DOMINANCE_COLOR_POSITIVE = DARKVIOLET + DOMINANCE_COLOR_NEGATIVE = RED + + # BEGIN HaplotypeAnalyst + HAPLOTYPE_POSITIVE = GREEN + HAPLOTYPE_NEGATIVE = RED + HAPLOTYPE_HETEROZYGOUS = BLUE + HAPLOTYPE_RECOMBINATION = DARKGRAY + # END HaplotypeAnalyst + + TOP_RIGHT_INFO_COLOR = BLACK + + CLICKABLE_WEBQTL_REGION_COLOR = ImageColor.getrgb("#F5D3D3") + CLICKABLE_WEBQTL_REGION_OUTLINE_COLOR = ImageColor.getrgb("#FCE9E9") + CLICKABLE_WEBQTL_TEXT_COLOR = ImageColor.getrgb("#912828") + + CLICKABLE_PHENOGEN_REGION_COLOR = ImageColor.getrgb("#A2FB94") + CLICKABLE_PHENOGEN_REGION_OUTLINE_COLOR = ImageColor.getrgb("#CEFEC7") + CLICKABLE_PHENOGEN_TEXT_COLOR = ImageColor.getrgb("#1FD504") + + CLICKABLE_UCSC_REGION_COLOR = ImageColor.getrgb("#DDDDEE") + CLICKABLE_UCSC_REGION_OUTLINE_COLOR = ImageColor.getrgb("#EDEDFF") + CLICKABLE_UCSC_TEXT_COLOR = ImageColor.getrgb("#333366") + + CLICKABLE_ENSEMBL_REGION_COLOR = ImageColor.getrgb("#EEEEDD") + CLICKABLE_ENSEMBL_REGION_OUTLINE_COLOR = ImageColor.getrgb("#FEFEEE") + CLICKABLE_ENSEMBL_TEXT_COLOR = ImageColor.getrgb("#555500") + + GRAPH_BACK_LIGHT_COLOR = ImageColor.getrgb("#FBFBFF") + GRAPH_BACK_DARK_COLOR = ImageColor.getrgb("#F1F1F9") + + HELP_PAGE_REF = '/glossary.html' + + def __init__(self, start_vars): + self.temp_uuid = start_vars['temp_uuid'] + self.hash_of_inputs = start_vars['hash_of_inputs'] + self.dataid = start_vars['dataid'] + + self.dataset = start_vars['dataset'] + self.this_trait = start_vars['this_trait'] + self.n_samples = start_vars['n_samples'] + self.species = start_vars['species'] + self.genofile_string = "" + if 'genofile_string' in start_vars: + self.genofile_string = start_vars['genofile_string'] + + self.geno_db_exists = start_vars['geno_db_exists'] + + self.first_run = True + if 'first_run' in start_vars: + self.first_run = start_vars['first_run'] + + if 'temp_trait' in start_vars and start_vars['temp_trait'] != "False": + self.temp_trait = "True" + self.group = start_vars['group'] + + # Needing for form submission when doing single chr + # mapping or remapping after changing options + self.sample_vals = start_vars['sample_vals'] + self.vals_hash= start_vars['vals_hash'] + self.sample_vals_dict = json.loads(self.sample_vals) + + self.transform = start_vars['transform'] + self.mapping_method = start_vars['mapping_method'] + self.mapping_results_path = start_vars['mapping_results_path'] + if self.mapping_method == "rqtl_geno": + self.mapmethod_rqtl_geno = start_vars['method'] + self.mapmodel_rqtl_geno = start_vars['model'] + self.pair_scan = start_vars['pair_scan'] + + self.js_data = start_vars['js_data'] + # Top markers to display in table + self.trimmed_markers = start_vars['trimmed_markers'] + + if self.dataset.group.species == "rat": + self._ucscDb = "rn6" + elif self.dataset.group.species == "mouse": + self._ucscDb = "mm10" + else: + self._ucscDb = "" + + ##################################### + # Options + ##################################### + # Mapping options + if start_vars['mapping_scale'] != "": + self.plotScale = start_vars['mapping_scale'] + else: + self.plotScale = "physic" + + self.manhattan_plot = start_vars['manhattan_plot'] + if self.manhattan_plot: + self.color_scheme = "alternating" + if 'color_scheme' in start_vars: + self.color_scheme = start_vars['color_scheme'] + if self.color_scheme == "single": + self.manhattan_single_color = ImageColor.getrgb( + "#" + start_vars['manhattan_single_color']) + + if 'permCheck' in list(start_vars.keys()): + self.permChecked = start_vars['permCheck'] + else: + self.permChecked = False + if start_vars['num_perm'] > 0: + self.nperm = int(start_vars['num_perm']) + if self.permChecked: + self.perm_output = start_vars['perm_output'] + self.suggestive = start_vars['suggestive'] + self.significant = start_vars['significant'] + else: + self.nperm = 0 + + if 'bootCheck' in list(start_vars.keys()): + self.bootChecked = start_vars['bootCheck'] + else: + self.bootChecked = False + if 'num_bootstrap' in list(start_vars.keys()): + self.nboot = int(start_vars['num_bootstrap']) + else: + self.nboot = 0 + if 'bootstrap_results' in list(start_vars.keys()): + self.bootResult = start_vars['bootstrap_results'] + else: + self.bootResult = [] + + if 'do_control' in list(start_vars.keys()): + self.doControl = start_vars['do_control'] + else: + self.doControl = "false" + if 'control_marker' in list(start_vars.keys()): + self.controlLocus = start_vars['control_marker'] + else: + self.controlLocus = "" + if 'covariates' in list(start_vars.keys()): + self.covariates = start_vars['covariates'] + if 'maf' in list(start_vars.keys()): + self.maf = start_vars['maf'] + else: + self.maf = "" + if 'output_files' in list(start_vars.keys()): + self.output_files = start_vars['output_files'] + if 'use_loco' in list(start_vars.keys()) and self.mapping_method == "gemma": + self.use_loco = start_vars['use_loco'] + + if self.mapping_method == "reaper": + if 'output_files' in start_vars: + self.output_files = ",".join( + [(the_file if the_file is not None else "") for the_file in start_vars['output_files']]) + + self.categorical_vars = "" + self.perm_strata = "" + if 'perm_strata' in list(start_vars.keys()) and 'categorical_vars' in list(start_vars.keys()): + self.categorical_vars = start_vars['categorical_vars'] + self.perm_strata = start_vars['perm_strata'] + + self.selectedChr = int(start_vars['selected_chr']) + + self.strainlist = start_vars['samples'] + + self.traitList = [] + thisTrait = start_vars['this_trait'] + self.traitList.append(thisTrait) + + ################################################################ + # Calculations QTL goes here + ################################################################ + self.multipleInterval = len(self.traitList) > 1 + self.qtlresults = start_vars['qtl_results'] + + if self.multipleInterval: + self.colorCollection = Plot.colorSpectrum(len(self.qtlresults)) + else: + self.colorCollection = [self.LRS_COLOR] + + self.dataset.group.genofile = self.genofile_string.split(":")[0] + if self.mapping_method == "reaper" and self.manhattan_plot != True: + self.genotype = self.dataset.group.read_genotype_file( + use_reaper=True) + else: + self.genotype = self.dataset.group.read_genotype_file() + + # Darwing Options + try: + if self.selectedChr > -1: + self.graphWidth = min(self.GRAPH_MAX_WIDTH, max( + self.GRAPH_MIN_WIDTH, int(start_vars['graphWidth']))) + else: + self.graphWidth = min(self.GRAPH_MAX_WIDTH, max( + self.MULT_GRAPH_MIN_WIDTH, int(start_vars['graphWidth']))) + except: + if self.selectedChr > -1: + self.graphWidth = self.GRAPH_DEFAULT_WIDTH + else: + self.graphWidth = self.MULT_GRAPH_DEFAULT_WIDTH + +# BEGIN HaplotypeAnalyst + if 'haplotypeAnalystCheck' in list(start_vars.keys()): + self.haplotypeAnalystChecked = start_vars['haplotypeAnalystCheck'] + else: + self.haplotypeAnalystChecked = False +# END HaplotypeAnalyst + + self.graphHeight = self.GRAPH_DEFAULT_HEIGHT + self.dominanceChecked = False + if 'LRSCheck' in list(start_vars.keys()): + self.LRS_LOD = start_vars['LRSCheck'] + else: + self.LRS_LOD = start_vars['score_type'] + self.intervalAnalystChecked = True + self.draw2X = False + if 'additiveCheck' in list(start_vars.keys()): + self.additiveChecked = start_vars['additiveCheck'] + else: + self.additiveChecked = False + if 'viewLegend' in list(start_vars.keys()): + self.legendChecked = start_vars['viewLegend'] + else: + self.legendChecked = False + if 'showSNP' in list(start_vars.keys()): + self.SNPChecked = start_vars['showSNP'] + else: + self.SNPChecked = False + if 'showHomology' in list(start_vars.keys()): + self.homologyChecked = start_vars['showHomology'] + else: + self.homologyChecked = "ON" + if 'showGenes' in list(start_vars.keys()): + self.geneChecked = start_vars['showGenes'] + else: + self.geneChecked = False + try: + self.startMb = float(start_vars['startMb']) + except: + self.startMb = -1 + try: + self.endMb = float(start_vars['endMb']) + except: + self.endMb = -1 + try: + self.lrsMax = float(start_vars['lrsMax']) + except: + self.lrsMax = 0 + + # Trait Infos + self.identification = "" + + ################################################################ + # Generate Chr list and Retrieve Length Information + ################################################################ + self.ChrList = [("All", -1)] + for i, indChr in enumerate(self.genotype): + if self.dataset.group.species == "mouse" and indChr.name == "20": + self.ChrList.append(("X", i)) + elif self.dataset.group.species == "rat" and indChr.name == "21": + self.ChrList.append(("X", i)) + self.ChrList.append((indChr.name, i)) + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute("SELECT Length FROM Chr_Length, InbredSet " + "WHERE Chr_Length.SpeciesId = InbredSet.SpeciesId " + "AND InbredSet.Name = %s AND Chr_Length.Name IN " + f"({', '.join(['%s' for x in self.ChrList[1:]])}) " + "ORDER BY Chr_Length.OrderId", + (self.dataset.group.name, + *[x[0] for x in self.ChrList[1:]],)) + self.ChrLengthMbList = cursor.fetchall() + + self.ChrLengthMbList = [x[0] / 1000000.0 for x in self.ChrLengthMbList] + self.ChrLengthMbSum = reduce( + lambda x, y: x + y, self.ChrLengthMbList, 0.0) + if self.ChrLengthMbList: + self.MbGraphInterval = self.ChrLengthMbSum / \ + (len(self.ChrLengthMbList) * 12) # Empirical Mb interval + else: + self.MbGraphInterval = 1 + + self.ChrLengthCMList = [] + for i, _chr in enumerate(self.genotype): + self.ChrLengthCMList.append(_chr[-1].cM - _chr[0].cM) + + self.ChrLengthCMSum = reduce( + lambda x, y: x + y, self.ChrLengthCMList, 0.0) + + if self.plotScale == 'physic': + self.GraphInterval = self.MbGraphInterval # Mb + else: + self.GraphInterval = self.cMGraphInterval # cM + + ######################### + # Get the sorting column + ######################### + RISet = self.dataset.group.name + if RISet in ('AXB', 'BXA', 'AXBXA'): + self.diffCol = ['B6J', 'A/J'] + elif RISet in ('BXD', 'BXD300', 'B6D2F2', 'BDF2-2005', 'BDF2-1999', 'BHHBF2', 'BXD-Harvested', 'BXD-Longevity', 'BXD-Micturition', 'BXD-AE', 'BXD-NIA-AD', 'B6D2RI', 'BXD-Bone', 'DOD-BXD-GWI', 'BXD-Heart-Metals', 'UTHSC-Cannabinoid'): + self.diffCol = ['B6J', 'D2J'] + elif RISet in ('CXB'): + self.diffCol = ['CBY', 'B6J'] + elif RISet in ('BXH', 'BHF2'): + self.diffCol = ['B6J', 'C3H'] + elif RISet in ('B6BTBRF2'): + self.diffCol = ['B6J', 'BTB'] + elif RISet in ('LXS'): + self.diffCol = ['ILS', 'ISS'] + else: + self.diffCol = [] + + for i, strain in enumerate(self.diffCol): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute("SELECT Id FROM Strain WHERE Symbol = %s", + (strain,)) + if result := cursor.fetchone(): + self.diffCol[i] = result[0] + + ################################################################ + # GeneCollection goes here + ################################################################ + if self.plotScale == 'physic' and self.selectedChr != -1: + #StartMb or EndMb + if self.startMb < 0 or self.endMb < 0: + self.startMb = 0 + self.endMb = self.ChrLengthMbList[self.selectedChr - 1] + + geneTable = "" + + self.geneCol = None + self.homology = None + if self.plotScale == 'physic' and self.selectedChr > -1 and (self.intervalAnalystChecked or self.geneChecked or self.homologyChecked): + # Draw the genes for this chromosome / region of this chromosome + webqtldatabase = self.dataset.name + + if self.dataset.group.species == "mouse": + if self.selectedChr == 20: + chrName = "X" + else: + chrName = self.selectedChr + self.geneCol = GeneUtil.loadGenes( + str(chrName), self.diffCol, self.startMb, self.endMb, "mouse") + self.homology = GeneUtil.load_homology(str(chrName), self.startMb, self.endMb, "mouse_to_human.csv") + + elif self.dataset.group.species == "rat": + if self.selectedChr == 21: + chrName = "X" + else: + chrName = self.selectedChr + self.geneCol = GeneUtil.loadGenes( + str(chrName), self.diffCol, self.startMb, self.endMb, "rat") + + if self.geneCol and self.intervalAnalystChecked: + ####################################################################### + #Nick use GENEID as RefGene to get Literature Correlation Informations# + #For Interval Mapping, Literature Correlation isn't useful, so skip it# + #through set GENEID is None # + ####################################################################### + + GENEID = None + + self.geneTable(self.geneCol, GENEID) + +# BEGIN HaplotypeAnalyst +# count the amount of individuals to be plotted, and increase self.graphHeight + if self.haplotypeAnalystChecked and self.selectedChr > -1: + thisTrait = self.this_trait + smd = [] + for sample in self.sample_vals_dict.keys(): + if self.sample_vals_dict[sample] != "x": + temp = GeneralObject(name=sample, value=float( + self.sample_vals_dict[sample])) + smd.append(temp) + else: + continue + samplelist = list(self.genotype.prgy) + for j, _geno in enumerate(self.genotype[0][1].genotype): + for item in smd: + if item.name == samplelist[j]: + self.NR_INDIVIDUALS = self.NR_INDIVIDUALS + 1 +# default: + self.graphHeight = self.graphHeight + 2 * \ + (self.NR_INDIVIDUALS + 10) * self.EACH_GENE_HEIGHT +# END HaplotypeAnalyst + + if self.homologyChecked and self.homology and self.geneChecked and self.geneCol: + self.graphHeight = self.graphHeight + (self.NUM_GENE_ROWS) * self.EACH_GENE_HEIGHT + if self.geneChecked and self.geneCol: + self.graphHeight = self.graphHeight + (self.NUM_GENE_ROWS) * self.EACH_GENE_HEIGHT + + + ################################################################ + # Plots goes here + ################################################################ + showLocusForm = "" + intCanvas = Image.new("RGBA", size=(self.graphWidth, self.graphHeight)) + gifmap = self.plotIntMapping( + intCanvas, startMb=self.startMb, endMb=self.endMb, showLocusForm=showLocusForm) + + self.gifmap = gifmap.__str__() + + self.filename = webqtlUtil.genRandStr("Itvl_") + intCanvas.save( + "{}.png".format( + os.path.join(webqtlConfig.GENERATED_IMAGE_DIR, self.filename)), + format='png') + intImg = HtmlGenWrapper.create_image_tag( + src="/image/{}.png".format(self.filename), + border="0", usemap='#WebQTLImageMap' + ) + + # Scales plot differently for high resolution + if self.draw2X: + intCanvasX2 = Image.new("RGBA", size=( + self.graphWidth * 2, self.graphHeight * 2)) + gifmapX2 = self.plotIntMapping( + intCanvasX2, startMb=self.startMb, endMb=self.endMb, showLocusForm=showLocusForm, zoom=2) + intCanvasX2.save( + "{}.png".format( + os.path.join(webqtlConfig.GENERATED_IMAGE_DIR, + self.filename + "X2")), + format='png') + + ################################################################ + # Outputs goes here + ################################################################ + # this form is used for opening Locus page or trait page, only available for genetic mapping + if showLocusForm: + showLocusForm = HtmlGenWrapper.create_form_tag( + cgi=os.path.join(webqtlConfig.CGIDIR, webqtlConfig.SCRIPTFILE), + enctype='multipart/form-data', + name=showLocusForm, + submit=HtmlGenWrapper.create_input_tag(type_='hidden')) + + hddn = {'FormID': 'showDatabase', 'ProbeSetID': '_', 'database': fd.RISet + \ + "Geno", 'CellID': '_', 'RISet': fd.RISet, 'incparentsf1': 'ON'} + for key in hddn.keys(): + showLocusForm.append(HtmlGenWrapper.create_input_tag( + name=key, value=hddn[key], type_='hidden')) + showLocusForm.append(intImg) + else: + showLocusForm = intImg + + if (self.permChecked and self.nperm > 0) and not (self.multipleInterval and 0 < self.nperm): + self.perm_filename = self.drawPermutationHistogram() + + ################################################################ + # footnote goes here + ################################################################ + # Small('More information about this graph is available here.') + btminfo = HtmlGenWrapper.create_p_tag(id="smallsize") + + if self.traitList and self.traitList[0].dataset and self.traitList[0].dataset.type == 'Geno': + btminfo.append(HtmlGenWrapper.create_br_tag()) + btminfo.append( + 'Mapping using genotype data as a trait will result in infinity LRS at one locus. In order to display the result properly, all LRSs higher than 100 are capped at 100.') + + def plotIntMapping(self, canvas, offset=(80, 120, 110, 100), zoom=1, startMb=None, endMb=None, showLocusForm=""): + im_drawer = ImageDraw.Draw(canvas) + # calculating margins + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + if self.multipleInterval: + yTopOffset = max(90, yTopOffset) + else: + if self.legendChecked: + yTopOffset += 10 + if self.covariates != "" and self.controlLocus and self.doControl != "false": + yTopOffset += 25 + if len(self.transform) > 0: + yTopOffset += 5 + else: + pass + + if self.plotScale != 'physic': + yBottomOffset = max(120, yBottomOffset) + fontZoom = zoom + if zoom == 2: + xLeftOffset += 20 + fontZoom = 1.5 + + xLeftOffset = int(xLeftOffset * fontZoom) + xRightOffset = int(xRightOffset * fontZoom) + yBottomOffset = int(yBottomOffset * fontZoom) + + cWidth = canvas.size[0] + cHeight = canvas.size[1] + plotWidth = cWidth - xLeftOffset - xRightOffset + plotHeight = cHeight - yTopOffset - yBottomOffset + + # Drawing Area Height + drawAreaHeight = plotHeight + if self.plotScale == 'physic' and self.selectedChr > -1: + if self.dataset.group.species == "mouse" or self.dataset.group.species == "rat": + drawAreaHeight -= 4 * self.BAND_HEIGHT + 4 * self.BAND_SPACING + 10 * zoom + else: + drawAreaHeight -= 3 * self.BAND_HEIGHT + 3 * self.BAND_SPACING + 10 * zoom + if self.homologyChecked: + drawAreaHeight -= self.NUM_GENE_ROWS * \ + self.EACH_GENE_HEIGHT + 3 * self.BAND_SPACING + if self.geneChecked: + drawAreaHeight -= self.NUM_GENE_ROWS * \ + self.EACH_GENE_HEIGHT + 3 * self.BAND_SPACING + else: + if self.selectedChr > -1: + drawAreaHeight -= 20 + else: + drawAreaHeight -= 30 + +# BEGIN HaplotypeAnalyst + if self.haplotypeAnalystChecked and self.selectedChr > -1: + drawAreaHeight -= self.EACH_GENE_HEIGHT * \ + (self.NR_INDIVIDUALS + 10) * 2 * zoom +# END HaplotypeAnalyst + + if zoom == 2: + drawAreaHeight -= 60 + + # Image map + gifmap = HtmlGenWrapper.create_map_tag(name="WebQTLImageMap") + + newoffset = (xLeftOffset, xRightOffset, yTopOffset, yBottomOffset) + # Draw the alternating-color background first and get plotXScale + plotXScale = self.drawGraphBackground( + canvas, gifmap, offset=newoffset, zoom=zoom, startMb=startMb, endMb=endMb) + + # draw bootstap + if self.bootChecked and not self.multipleInterval: + self.drawBootStrapResult(canvas, self.nboot, drawAreaHeight, plotXScale, + offset=newoffset, zoom=zoom, startMb=startMb, endMb=endMb) + + # Draw clickable region and gene band if selected + if self.plotScale == 'physic' and self.selectedChr > -1: + self.drawClickBand(canvas, gifmap, plotXScale, offset=newoffset, + zoom=zoom, startMb=startMb, endMb=endMb) + + if self.geneChecked and self.geneCol: + self.drawGeneBand(canvas, gifmap, plotXScale, offset=newoffset, + zoom=zoom, startMb=startMb, endMb=endMb) + if self.homologyChecked and self.homology: + if self.geneChecked and self.geneCol: + yTopOffset = newoffset[3] + self.NUM_GENE_ROWS * \ + self.EACH_GENE_HEIGHT + 3 * self.BAND_SPACING + 10 + self.drawHomologyBand(canvas, gifmap, plotXScale, offset=(xLeftOffset, xRightOffset, yTopOffset, yBottomOffset), + zoom=zoom, startMb=startMb, endMb=endMb) + if self.SNPChecked: + self.drawSNPTrackNew( + canvas, offset=newoffset, zoom=2 * zoom, startMb=startMb, endMb=endMb) +# BEGIN HaplotypeAnalyst + if self.haplotypeAnalystChecked: + self.drawHaplotypeBand( + canvas, gifmap, plotXScale, offset=newoffset, zoom=zoom, startMb=startMb, endMb=endMb) +# END HaplotypeAnalyst + # Draw X axis + self.drawXAxis(canvas, drawAreaHeight, gifmap, plotXScale, showLocusForm, + offset=newoffset, zoom=zoom, startMb=startMb, endMb=endMb) + # Draw QTL curve + self.drawQTL(canvas, drawAreaHeight, gifmap, plotXScale, + offset=newoffset, zoom=zoom, startMb=startMb, endMb=endMb) + + # draw legend + if self.multipleInterval: + self.drawMultiTraitName( + fd, canvas, gifmap, showLocusForm, offset=newoffset) + elif self.legendChecked: + self.drawLegendPanel(canvas, offset=newoffset, zoom=zoom) + else: + pass + + # draw position, no need to use a separate function + self.drawProbeSetPosition( + canvas, plotXScale, offset=newoffset, zoom=zoom) + + return gifmap + + def drawBootStrapResult(self, canvas, nboot, drawAreaHeight, plotXScale, offset=(40, 120, 80, 10), zoom=1, startMb=None, endMb=None): + im_drawer = ImageDraw.Draw(canvas) + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + plotHeight = canvas.size[1] - yTopOffset - yBottomOffset + yZero = canvas.size[1] - yBottomOffset + fontZoom = zoom + if zoom == 2: + fontZoom = 1.5 + + bootHeightThresh = drawAreaHeight * 3 / 4 + + # break bootstrap result into groups + BootCoord = [] + i = 0 + previous_chr = None + previous_chr_as_int = 0 + startX = xLeftOffset + + BootChrCoord = [] + if self.selectedChr == -1: # ZS: If viewing full genome/all chromosomes + for i, result in enumerate(self.qtlresults): + if result['chr'] != previous_chr: + previous_chr = result['chr'] + previous_chr_as_int += 1 + if previous_chr_as_int != 1: + BootCoord.append(BootChrCoord) + BootChrCoord = [] + startX += ( + self.ChrLengthDistList[previous_chr_as_int - 2] + self.GraphInterval) * plotXScale + if self.plotScale == 'physic': + Xc = startX + (result['Mb'] - self.startMb) * plotXScale + else: + Xc = startX + \ + (result['cM'] - self.qtlresults[0]['cM']) * plotXScale + BootChrCoord.append([Xc, self.bootResult[i]]) + else: + for i, result in enumerate(self.qtlresults): + if str(result['chr']) == str(self.ChrList[self.selectedChr][0]): + if self.plotScale == 'physic': + Xc = startX + (result['Mb'] - \ + self.startMb) * plotXScale + else: + Xc = startX + \ + (result['cM'] - self.qtlresults[0] + ['cM']) * plotXScale + BootChrCoord.append([Xc, self.bootResult[i]]) + BootCoord = [BootChrCoord] + + # reduce bootResult + if self.selectedChr > -1: + maxBootBar = 80.0 + else: + maxBootBar = 200.0 + stepBootStrap = plotWidth / maxBootBar + reducedBootCoord = [] + maxBootCount = 0 + + for BootChrCoord in BootCoord: + nBoot = len(BootChrCoord) + bootStartPixX = BootChrCoord[0][0] + bootCount = BootChrCoord[0][1] + for i in range(1, nBoot): + if BootChrCoord[i][0] - bootStartPixX < stepBootStrap: + bootCount += BootChrCoord[i][1] + continue + else: + if maxBootCount < bootCount: + maxBootCount = bootCount + # end if + reducedBootCoord.append( + [bootStartPixX, BootChrCoord[i][0], bootCount]) + bootStartPixX = BootChrCoord[i][0] + bootCount = BootChrCoord[i][1] + # end else + # end for + # add last piece + if BootChrCoord[-1][0] - bootStartPixX > stepBootStrap / 2.0: + reducedBootCoord.append( + [bootStartPixX, BootChrCoord[-1][0], bootCount]) + else: + reducedBootCoord[-1][2] += bootCount + reducedBootCoord[-1][1] = BootChrCoord[-1][0] + # end else + if maxBootCount < reducedBootCoord[-1][2]: + maxBootCount = reducedBootCoord[-1][2] + # end if + for item in reducedBootCoord: + if item[2] > 0: + if item[0] < xLeftOffset: + item[0] = xLeftOffset + if item[0] > xLeftOffset + plotWidth: + item[0] = xLeftOffset + plotWidth + if item[1] < xLeftOffset: + item[1] = xLeftOffset + if item[1] > xLeftOffset + plotWidth: + item[1] = xLeftOffset + plotWidth + if item[0] != item[1]: + im_drawer.rectangle( + xy=((item[0], yZero), + (item[1], yZero - item[2] * bootHeightThresh / maxBootCount)), + fill=self.BOOTSTRAP_BOX_COLOR, outline=BLACK) + + if maxBootCount == 0: + return + + # draw boot scale + highestPercent = (maxBootCount * 100.0) / nboot + bootScale = Plot.detScale(0, highestPercent) + bootScale = Plot.frange( + bootScale[0], bootScale[1], bootScale[1] / bootScale[2]) + bootScale = bootScale[:-1] + [highestPercent] + + bootOffset = 50 * fontZoom + bootScaleFont = ImageFont.truetype( + font=VERDANA_FILE, size=13 * fontZoom) + im_drawer.rectangle( + xy=((canvas.size[0] - bootOffset, yZero - bootHeightThresh), + (canvas.size[0] - bootOffset - 15 * zoom, yZero)), + fill=YELLOW, outline=BLACK) + im_drawer.line( + xy=((canvas.size[0] - bootOffset + 4, yZero), + (canvas.size[0] - bootOffset, yZero)), + fill=BLACK) + TEXT_Y_DISPLACEMENT = -8 + im_drawer.text(xy=(canvas.size[0] - bootOffset + 10, yZero + TEXT_Y_DISPLACEMENT), text='0%', + font=bootScaleFont, fill=BLACK) + + for item in bootScale: + if item == 0: + continue + bootY = yZero - bootHeightThresh * item / highestPercent + im_drawer.line( + xy=((canvas.size[0] - bootOffset + 4, bootY), + (canvas.size[0] - bootOffset, bootY)), + fill=BLACK) + im_drawer.text(xy=(canvas.size[0] - bootOffset + 10, bootY + TEXT_Y_DISPLACEMENT), + text='%2.1f' % item, font=bootScaleFont, fill=BLACK) + + if self.legendChecked: + if hasattr(self.traitList[0], 'chr') and hasattr(self.traitList[0], 'mb'): + startPosY = 30 + else: + startPosY = 15 + smallLabelFont = ImageFont.truetype( + font=TREBUC_FILE, size=12 * fontZoom) + leftOffset = canvas.size[0] - xRightOffset - 190 + im_drawer.rectangle( + xy=((leftOffset, startPosY - 6), + (leftOffset + 12, startPosY + 6)), + fill=YELLOW, outline=BLACK) + im_drawer.text(xy=(canvas.size[0] - xRightOffset - 170, startPosY + TEXT_Y_DISPLACEMENT), + text='Frequency of the Peak LRS', + font=smallLabelFont, fill=BLACK) + + def drawProbeSetPosition(self, canvas, plotXScale, offset=(40, 120, 80, 10), zoom=1, startMb=None, endMb=None): + im_drawer = ImageDraw.Draw(canvas) + if len(self.traitList) != 1: + return + + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + plotHeight = canvas.size[1] - yTopOffset - yBottomOffset + yZero = canvas.size[1] - yBottomOffset + fontZoom = zoom + if zoom == 2: + fontZoom = 1.5 + + try: + Chr = self.traitList[0].chr + Mb = self.traitList[0].mb + except: + return + + previous_chr = 1 + previous_chr_as_int = 0 + if self.plotScale == "physic": + this_chr = str(self.ChrList[self.selectedChr][0]) + else: + this_chr = str(self.ChrList[self.selectedChr][1] + 1) + + if self.plotScale == 'physic': + if self.selectedChr > -1: + if this_chr != Chr or Mb < self.startMb or Mb > self.endMb: + return + else: + locPixel = xLeftOffset + (Mb - self.startMb) * plotXScale + else: + locPixel = xLeftOffset + for i, _chr in enumerate(self.ChrList[1:]): + if _chr[0] != Chr: + locPixel += (self.ChrLengthDistList[i] + \ + self.GraphInterval) * plotXScale + else: + locPixel += Mb * plotXScale + break + else: + if self.selectedChr > -1: + for i, qtlresult in enumerate(self.qtlresults): + if qtlresult['chr'] != self.selectedChr: + continue + + if i == 0 and qtlresult['Mb'] >= Mb: + locPixel = -1 + break + + # the trait's position is between two traits + if i > 0 and self.qtlresults[i - 1]['Mb'] < Mb and qtlresult['Mb'] >= Mb: + locPixel = xLeftOffset + plotXScale * (self.qtlresults[i - 1]['Mb'] + (qtlresult['Mb'] - self.qtlresults[i - 1]['Mb']) * ( + Mb - self.qtlresults[i - 1]['Mb']) / (qtlresult['Mb'] - self.qtlresults[i - 1]['Mb'])) + break + + # the trait's position is on the right of the last genotype + if i == len(self.qtlresults) and Mb >= qtlresult['Mb']: + locPixel = -1 + else: + locPixel = xLeftOffset + for i, _chr in enumerate(self.ChrList): + if i < (len(self.ChrList) - 1): + if _chr != Chr: + locPixel += (self.ChrLengthDistList[i] + \ + self.GraphInterval) * plotXScale + else: + locPixel += (Mb * (_chr[-1].cM - _chr[0].cM) / \ + self.ChrLengthCMList[i]) * plotXScale + break + if locPixel >= 0 and self.plotScale == 'physic': + traitPixel = ((locPixel, yZero), (locPixel - 7, + yZero + 14), (locPixel + 7, yZero + 14)) + draw_open_polygon(canvas, xy=traitPixel, outline=BLACK, + fill=self.TRANSCRIPT_LOCATION_COLOR) + + def drawSNPTrackNew(self, canvas, offset=(40, 120, 80, 10), zoom=1, startMb=None, endMb=None): + im_drawer = ImageDraw.Draw(canvas) + if self.plotScale != 'physic' or self.selectedChr == -1 or not self.diffCol: + return + + SNP_HEIGHT_MODIFIER = 18.0 + + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + plotHeight = canvas.size[1] - yTopOffset - yBottomOffset + yZero = canvas.size[1] - yBottomOffset + fontZoom = zoom + if zoom == 2: + fontZoom = 1.5 + + drawSNPLocationY = yTopOffset + plotHeight + #chrName = self.genotype[0].name + chrName = self.ChrList[self.selectedChr][0] + + stepMb = (endMb - startMb) / plotWidth + strainId1, strainId2 = self.diffCol + SNPCounts = [] + + while startMb < endMb: + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + # snp count + cursor.execute("SELECT COUNT(*) FROM BXDSnpPosition " + "WHERE Chr = %s AND Mb >= %s AND Mb < %s AND " + "StrainId1 = %s AND StrainId2 = %s", + (chrName, f"{startMb:2.6f}", + f"{startMb + stepMb:2.6f}", strainId1, strainId2,)) + SNPCounts.append(cursor.fetchone()[0]) + startMb += stepMb + + if (len(SNPCounts) > 0): + maxCount = max(SNPCounts) + if maxCount > 0: + for i in range(xLeftOffset, xLeftOffset + plotWidth): + snpDensity = float( + SNPCounts[i - xLeftOffset] * SNP_HEIGHT_MODIFIER / maxCount) + im_drawer.line( + xy=((i, drawSNPLocationY + (snpDensity) * zoom), + (i, drawSNPLocationY - (snpDensity) * zoom)), + fill=self.SNP_COLOR, width=1) + + def drawMultiTraitName(self, fd, canvas, gifmap, showLocusForm, offset=(40, 120, 80, 10), zoom=1): + nameWidths = [] + yPaddingTop = 10 + colorFont = ImageFont.truetype(font=TREBUC_FILE, size=12) + if len(self.qtlresults) > 20 and self.selectedChr > -1: + rightShift = 20 + rightShiftStep = 60 + rectWidth = 10 + else: + rightShift = 40 + rightShiftStep = 80 + rectWidth = 15 + + for k, thisTrait in enumerate(self.traitList): + thisLRSColor = self.colorCollection[k] + kstep = k % 4 + if k != 0 and kstep == 0: + if nameWidths: + rightShiftStep = max(nameWidths[-4:]) + rectWidth + 20 + rightShift += rightShiftStep + + name = thisTrait.displayName() + nameWidth, nameHeight = im_drawer.textsize(name, font=colorFont) + nameWidths.append(nameWidth) + + im_drawer.rectangle( + xy=((rightShift, yPaddingTop + kstep * 15), + (rectWidth + rightShift, yPaddingTop + 10 + kstep * 15)), + fill=thisLRSColor, outline=BLACK) + im_drawer.text( + text=name, xy=(rectWidth + 2 + rightShift, + yPaddingTop + 10 + kstep * 15), + font=colorFont, fill=BLACK) + if thisTrait.db: + COORDS = "%d,%d,%d,%d" % (rectWidth + 2 + rightShift, yPaddingTop + kstep * \ + 15, rectWidth + 2 + rightShift + nameWidth, yPaddingTop + 10 + kstep * 15,) + HREF = "javascript:showDatabase3('%s','%s','%s','');" % ( + showLocusForm, thisTrait.db.name, thisTrait.name) + Areas = HtmlGenWrapper.create_area_tag( + shape='rect', coords=COORDS, href=HREF) + gifmap.append(Areas) # TODO + + def drawLegendPanel(self, canvas, offset=(40, 120, 80, 10), zoom=1): + im_drawer = ImageDraw.Draw(canvas) + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + plotHeight = canvas.size[1] - yTopOffset - yBottomOffset + yZero = canvas.size[1] - yBottomOffset + TEXT_Y_DISPLACEMENT = -8 + fontZoom = zoom + if zoom == 2: + fontZoom = 1.5 + + labelFont = ImageFont.truetype(font=TREBUC_FILE, size=12 * fontZoom) + startPosY = 15 + stepPosY = 12 * fontZoom + + startPosX = canvas.size[0] - xRightOffset - 415 + if hasattr(self.traitList[0], 'chr') and hasattr(self.traitList[0], 'mb'): + startPosY = 15 + nCol = 2 + smallLabelFont = ImageFont.truetype( + font=TREBUC_FILE, size=12 * fontZoom) + + leftOffset = canvas.size[0] - xRightOffset - 190 + draw_open_polygon( + canvas, + xy=( + (leftOffset + 6, startPosY - 7), + (leftOffset - 1, startPosY + 7), + (leftOffset + 13, startPosY + 7)), + outline=BLACK, fill=self.TRANSCRIPT_LOCATION_COLOR + ) + TEXT_Y_DISPLACEMENT = -8 + im_drawer.text( + text="Sequence Site", + xy=(leftOffset + 20, startPosY + TEXT_Y_DISPLACEMENT), font=smallLabelFont, + fill=self.TOP_RIGHT_INFO_COLOR) + + if self.manhattan_plot != True: + im_drawer.line( + xy=((startPosX, startPosY), (startPosX + 32, startPosY)), + fill=self.LRS_COLOR, width=2) + im_drawer.text( + text=self.LRS_LOD, xy=( + startPosX + 40, startPosY + TEXT_Y_DISPLACEMENT), + font=labelFont, fill=BLACK) + startPosY += stepPosY + + if self.additiveChecked: + im_drawer.line( + xy=((startPosX, startPosY), (startPosX + 17, startPosY)), + fill=self.ADDITIVE_COLOR_POSITIVE, width=2) + im_drawer.line( + xy=((startPosX + 18, startPosY), (startPosX + 32, startPosY)), + fill=self.ADDITIVE_COLOR_NEGATIVE, width=2) + im_drawer.text( + text='Additive Effect', xy=(startPosX + 40, startPosY + TEXT_Y_DISPLACEMENT), + font=labelFont, fill=BLACK) + startPosY += stepPosY + + if self.genotype.type == 'intercross' and self.dominanceChecked: + im_drawer.line( + xy=((startPosX, startPosY), (startPosX + 17, startPosY)), + fill=self.DOMINANCE_COLOR_POSITIVE, width=4) + im_drawer.line( + xy=((startPosX + 18, startPosY), (startPosX + 35, startPosY)), + fill=self.DOMINANCE_COLOR_NEGATIVE, width=4) + im_drawer.text( + text='Dominance Effect', xy=(startPosX + 42, startPosY + 5), + font=labelFont, fill=BLACK) + startPosY += stepPosY + + if self.haplotypeAnalystChecked: + im_drawer.line( + xy=((startPosX - 34, startPosY), (startPosX - 17, startPosY)), + fill=self.HAPLOTYPE_POSITIVE, width=4) + im_drawer.line( + xy=((startPosX - 17, startPosY), (startPosX, startPosY)), + fill=self.HAPLOTYPE_NEGATIVE, width=4) + im_drawer.line( + xy=((startPosX, startPosY), (startPosX + 17, startPosY)), + fill=self.HAPLOTYPE_HETEROZYGOUS, width=4) + im_drawer.line( + xy=((startPosX + 17, startPosY), (startPosX + 34, startPosY)), + fill=self.HAPLOTYPE_RECOMBINATION, width=4) + im_drawer.text( + text='Haplotypes (Pat, Mat, Het, Unk)', + xy=(startPosX + 41, startPosY + TEXT_Y_DISPLACEMENT), font=labelFont, fill=BLACK) + startPosY += stepPosY + + if self.permChecked and self.nperm > 0: + thisStartX = startPosX + if self.multipleInterval and not self.bootChecked: + thisStartX = canvas.size[0] - xRightOffset - 205 + im_drawer.line( + xy=((thisStartX, startPosY), (startPosX + 32, startPosY)), + fill=self.SIGNIFICANT_COLOR, width=self.SIGNIFICANT_WIDTH) + im_drawer.line( + xy=((thisStartX, startPosY + stepPosY), + (startPosX + 32, startPosY + stepPosY)), + fill=self.SUGGESTIVE_COLOR, width=self.SUGGESTIVE_WIDTH) + im_drawer.text( + text='Significant %s = %2.2f' % ( + self.LRS_LOD, self.significant), + xy=(thisStartX + 40, startPosY + TEXT_Y_DISPLACEMENT), font=labelFont, fill=BLACK) + im_drawer.text( + text='Suggestive %s = %2.2f' % (self.LRS_LOD, self.suggestive), + xy=(thisStartX + 40, startPosY + TEXT_Y_DISPLACEMENT + stepPosY), font=labelFont, + fill=BLACK) + + labelFont = ImageFont.truetype(font=VERDANA_FILE, size=12 * fontZoom) + labelColor = BLACK + + if self.dataset.type == "Publish" or self.dataset.type == "Geno": + dataset_label = self.dataset.fullname + else: + dataset_label = "%s - %s" % (self.dataset.group.name, + self.dataset.fullname) + + + self.current_datetime = datetime.datetime.now().strftime("%b %d %Y %H:%M:%S") + string1 = 'UTC Timestamp: %s' % (self.current_datetime) + string2 = 'Dataset: %s' % (dataset_label) + string3 = 'Trait Hash: %s' % (self.vals_hash) + + if self.genofile_string == "": + string4 = 'Genotype File: %s.geno' % self.dataset.group.name + else: + string4 = 'Genotype File: %s' % self.genofile_string.split(":")[1] + + string6 = '' + if self.mapping_method == "gemma" or self.mapping_method == "gemma_bimbam": + if self.use_loco == "True": + string5 = 'Using GEMMA mapping method with LOCO and ' + else: + string5 = 'Using GEMMA mapping method with ' + if self.covariates != "": + string5 += 'the cofactors below:' + cofactor_names = ", ".join( + [covar.split(":")[0] for covar in self.covariates.split(",")]) + string6 = cofactor_names + else: + string5 += 'no cofactors' + elif self.mapping_method == "rqtl_plink" or self.mapping_method == "rqtl_geno": + string5 = 'Using R/qtl mapping method with ' + if self.covariates != "": + string5 += 'the cofactors below:' + cofactor_names = ", ".join( + [covar.split(":")[0] for covar in self.covariates.split(",")]) + string6 = cofactor_names + elif self.controlLocus and self.doControl != "false": + string5 += '%s as control' % self.controlLocus + else: + string5 += 'no cofactors' + else: + string5 = 'Using Haldane mapping function with ' + if self.controlLocus and self.doControl != "false": + string5 += '%s as control' % self.controlLocus + else: + string5 += 'no control for other QTLs' + + y_constant = 10 + if self.this_trait.name: + if self.selectedChr == -1: + identification = "Mapping on All Chromosomes for " + else: + identification = "Mapping on Chromosome %s for " % ( + self.ChrList[self.selectedChr][0]) + + if self.this_trait.symbol: + identification += "Trait: %s - %s" % ( + self.this_trait.display_name, self.this_trait.symbol) + elif self.dataset.type == "Publish": + if self.this_trait.post_publication_abbreviation: + identification += "Trait: %s - %s" % ( + self.this_trait.display_name, self.this_trait.post_publication_abbreviation) + elif self.this_trait.pre_publication_abbreviation: + identification += "Trait: %s - %s" % ( + self.this_trait.display_name, self.this_trait.pre_publication_abbreviation) + else: + identification += "Trait: %s" % (self.this_trait.display_name) + else: + identification += "Trait: %s" % (self.this_trait.display_name) + identification += " with %s samples" % (self.n_samples) + + d = 4 + max( + im_drawer.textsize(identification, font=labelFont)[0], + im_drawer.textsize(string1, font=labelFont)[0], + im_drawer.textsize(string2, font=labelFont)[0], + im_drawer.textsize(string3, font=labelFont)[0], + im_drawer.textsize(string4, font=labelFont)[0]) + im_drawer.text( + text=identification, + xy=(xLeftOffset, y_constant * fontZoom), font=labelFont, + fill=labelColor) + y_constant += 15 + else: + d = 4 + max( + im_drawer.textsize(string1, font=labelFont)[0], + im_drawer.textsize(string2, font=labelFont)[0], + im_drawer.textsize(string3, font=labelFont)[0], + im_drawer.textsize(string4, font=labelFont)[0]) + + if len(self.transform) > 0: + transform_text = "Transform - " + if self.transform == "qnorm": + transform_text += "Quantile Normalized" + elif self.transform == "log2" or self.transform == "log10": + transform_text += self.transform.capitalize() + elif self.transform == "sqrt": + transform_text += "Square Root" + elif self.transform == "zscore": + transform_text += "Z-Score" + elif self.transform == "invert": + transform_text += "Invert +/-" + + im_drawer.text( + text=transform_text, xy=(xLeftOffset, y_constant * fontZoom), + font=labelFont, fill=labelColor) + y_constant += 15 + im_drawer.text( + text=string1, xy=(xLeftOffset, y_constant * fontZoom), + font=labelFont, fill=labelColor) + y_constant += 15 + im_drawer.text( + text=string2, xy=(xLeftOffset, y_constant * fontZoom), + font=labelFont, fill=labelColor) + y_constant += 15 + im_drawer.text( + text=string3, xy=(xLeftOffset, y_constant * fontZoom), + font=labelFont, fill=labelColor) + y_constant += 15 + im_drawer.text( + text=string4, xy=(xLeftOffset, y_constant * fontZoom), + font=labelFont, fill=labelColor) + y_constant += 15 + if string4 != '': + im_drawer.text( + text=string5, xy=(xLeftOffset, y_constant * fontZoom), + font=labelFont, fill=labelColor) + y_constant += 15 + if string5 != '': + im_drawer.text( + text=string6, xy=(xLeftOffset, y_constant * fontZoom), + font=labelFont, fill=labelColor) + + def drawHomologyBand(self, canvas, gifmap, plotXScale, offset=(40, 120, 80, 10), zoom=1, startMb=None, endMb=None): + im_drawer = ImageDraw.Draw(canvas) + if self.plotScale != 'physic' or self.selectedChr == -1 or not self.homology: + return + + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + plotHeight = canvas.size[1] - yTopOffset - yBottomOffset + yZero = canvas.size[1] - yBottomOffset + fontZoom = zoom + if zoom == 2: + fontZoom = 1.5 + + yPaddingTop = yTopOffset + + for index, homology_dict in enumerate(self.homology): + ref_strand = homology_dict["ref_strand"] + ref_start = homology_dict["ref_start"] + ref_end = homology_dict["ref_end"] + query_chr = homology_dict["query_chr"] + query_strand = homology_dict["query_strand"] + query_start = homology_dict["query_start"] + query_end = homology_dict["query_end"] + + geneLength = (ref_end - ref_start) * 1000.0 + tenPercentLength = geneLength * 0.0001 + + geneStartPix = xLeftOffset + \ + plotXScale * (float(ref_start) - startMb) + geneEndPix = xLeftOffset + plotXScale * \ + (float(ref_end) - startMb) # at least one pixel + + if (geneEndPix < xLeftOffset): + return # this gene is not on the screen + if (geneEndPix > xLeftOffset + plotWidth): + geneEndPix = xLeftOffset + plotWidth # clip the last in-range gene + if (geneStartPix > xLeftOffset + plotWidth): + return # we are outside the valid on-screen range, so stop drawing genes + elif (geneStartPix < xLeftOffset): + geneStartPix = xLeftOffset # clip the first in-range gene + + myColor = BLACK + + outlineColor = myColor + fillColor = myColor + + TITLE = f"hg38: Chr {query_chr} from {query_start:.3f} to {query_end:.3f} Mb" + HREF = f"http://genome.ucsc.edu/cgi-bin/hgTracks?db=hg38&position=chr{query_chr}:{int(query_start * 1000000)}-{int(query_end * 1000000)}" + + # Draw Genes + geneYLocation = yPaddingTop + \ + (index % self.NUM_GENE_ROWS) * self.EACH_GENE_HEIGHT * zoom + if self.dataset.group.species == "mouse" or self.dataset.group.species == "rat": + geneYLocation += 4 * self.BAND_HEIGHT + 4 * self.BAND_SPACING + else: + geneYLocation += 3 * self.BAND_HEIGHT + 3 * self.BAND_SPACING + + # draw the detail view + utrColor = ImageColor.getrgb("rgb(66%, 66%, 66%)") + arrowColor = ImageColor.getrgb("rgb(70%, 70%, 70%)") + + # draw the line that runs the entire length of the gene + im_drawer.line( + xy=( + (geneStartPix, geneYLocation + \ + self.EACH_GENE_HEIGHT / 2 * zoom), + (geneEndPix, geneYLocation + self.EACH_GENE_HEIGHT / 2 * zoom)), + fill=outlineColor, width=1) + + # draw the arrows + if geneEndPix - geneStartPix < 1: + genePixRange = 1 + else: + genePixRange = int(geneEndPix - geneStartPix) + for xCoord in range(0, genePixRange): + + if (xCoord % self.EACH_GENE_ARROW_SPACING == 0 and xCoord + self.EACH_GENE_ARROW_SPACING < geneEndPix - geneStartPix) or xCoord == 0: + if query_strand == "+": + im_drawer.line( + xy=((geneStartPix + xCoord, geneYLocation), + (geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, + geneYLocation + (self.EACH_GENE_HEIGHT / 2) * zoom)), + fill=arrowColor, width=1) + im_drawer.line( + xy=((geneStartPix + xCoord, + geneYLocation + self.EACH_GENE_HEIGHT * zoom), + (geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, + geneYLocation + (self.EACH_GENE_HEIGHT / 2) * zoom)), + fill=arrowColor, width=1) + else: + im_drawer.line( + xy=((geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, + geneYLocation), + (geneStartPix + xCoord, + geneYLocation + (self.EACH_GENE_HEIGHT / 2) * zoom)), + fill=arrowColor, width=1) + im_drawer.line( + xy=((geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, + geneYLocation + self.EACH_GENE_HEIGHT * zoom), + (geneStartPix + xCoord, + geneYLocation + (self.EACH_GENE_HEIGHT / 2) * zoom)), + fill=arrowColor, width=1) + + COORDS = "%d, %d, %d, %d" % ( + geneStartPix, geneYLocation, geneEndPix, (geneYLocation + self.EACH_GENE_HEIGHT)) + + gifmap.append( + HtmlGenWrapper.create_area_tag( + shape='rect', + coords=COORDS, + href=HREF, + title=TITLE, + target="_blank")) + + def drawGeneBand(self, canvas, gifmap, plotXScale, offset=(40, 120, 80, 10), zoom=1, startMb=None, endMb=None): + im_drawer = ImageDraw.Draw(canvas) + if self.plotScale != 'physic' or self.selectedChr == -1 or not self.geneCol: + return + + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + plotHeight = canvas.size[1] - yTopOffset - yBottomOffset + yZero = canvas.size[1] - yBottomOffset + fontZoom = zoom + if zoom == 2: + fontZoom = 1.5 + + yPaddingTop = yTopOffset + + for gIndex, theGO in enumerate(self.geneCol): + geneNCBILink = 'http://www.ncbi.nlm.nih.gov/gene?term=%s' + if self.dataset.group.species == "mouse": + exonStarts = [] + exonEnds = [] + txStart = theGO["TxStart"] + txEnd = theGO["TxEnd"] + geneLength = (txEnd - txStart) * 1000.0 + tenPercentLength = geneLength * 0.0001 + SNPdensity = theGO["snpCount"] / geneLength + + if theGO['exonStarts']: + exonStarts = list( + map(float, theGO['exonStarts'].split(",")[:-1])) + exonEnds = list(map(float, theGO['exonEnds'].split(",")[:-1])) + cdsStart = theGO['cdsStart'] + cdsEnd = theGO['cdsEnd'] + accession = theGO['NM_ID'] + geneSymbol = theGO["GeneSymbol"] + strand = theGO["Strand"] + exonCount = theGO["exonCount"] + + geneStartPix = xLeftOffset + \ + plotXScale * (float(txStart) - startMb) + geneEndPix = xLeftOffset + plotXScale * \ + (float(txEnd) - startMb) # at least one pixel + + if (geneEndPix < xLeftOffset): + return # this gene is not on the screen + elif (geneEndPix > xLeftOffset + plotWidth): + geneEndPix = xLeftOffset + plotWidth # clip the last in-range gene + if (geneStartPix > xLeftOffset + plotWidth): + return # we are outside the valid on-screen range, so stop drawing genes + elif (geneStartPix < xLeftOffset): + geneStartPix = xLeftOffset # clip the first in-range gene + + # color the gene based on SNP density + # found earlier, needs to be recomputed as snps are added + # always apply colors now, even if SNP Track not checked - Zach 11/24/2010 + + densities = [1.0000000000000001e-05, 0.094094033555233408, + 0.3306166377816987, 0.88246026851027781, 2.6690084029581951, 4.1, 61.0] + if SNPdensity < densities[0]: + myColor = BLACK + elif SNPdensity < densities[1]: + myColor = PURPLE + elif SNPdensity < densities[2]: + myColor = DARKBLUE + elif SNPdensity < densities[3]: + myColor = DARKGREEN + elif SNPdensity < densities[4]: + myColor = GOLD + elif SNPdensity < densities[5]: + myColor = DARKORANGE + else: + myColor = DARKRED + + outlineColor = myColor + fillColor = myColor + + TITLE = "Gene: %s (%s)\nFrom %2.3f to %2.3f Mb (%s)\nNum. exons: %d." % ( + geneSymbol, accession, float(txStart), float(txEnd), strand, exonCount) + # NL: 06-02-2011 Rob required to change this link for gene related + HREF = geneNCBILink % geneSymbol + + elif self.dataset.group.species == "rat": + exonStarts = [] + exonEnds = [] + txStart = theGO["TxStart"] + txEnd = theGO["TxEnd"] + cdsStart = theGO["TxStart"] + cdsEnd = theGO["TxEnd"] + geneSymbol = theGO["GeneSymbol"] + strand = theGO["Strand"] + exonCount = 0 + + geneStartPix = xLeftOffset + \ + plotXScale * (float(txStart) - startMb) + geneEndPix = xLeftOffset + plotXScale * \ + (float(txEnd) - startMb) # at least one pixel + + if (geneEndPix < xLeftOffset): + return # this gene is not on the screen + elif (geneEndPix > xLeftOffset + plotWidth): + geneEndPix = xLeftOffset + plotWidth # clip the last in-range gene + if (geneStartPix > xLeftOffset + plotWidth): + return # we are outside the valid on-screen range, so stop drawing genes + elif (geneStartPix < xLeftOffset): + geneStartPix = xLeftOffset # clip the first in-range gene + + outlineColor = DARKBLUE + fillColor = DARKBLUE + TITLE = "Gene: %s\nFrom %2.3f to %2.3f Mb (%s)" % ( + geneSymbol, float(txStart), float(txEnd), strand) + # NL: 06-02-2011 Rob required to change this link for gene related + HREF = geneNCBILink % geneSymbol + else: + outlineColor = ORANGE + fillColor = ORANGE + TITLE = "Gene: %s" % geneSymbol + + # Draw Genes + geneYLocation = yPaddingTop + \ + (gIndex % self.NUM_GENE_ROWS) * self.EACH_GENE_HEIGHT * zoom + if self.dataset.group.species == "mouse" or self.dataset.group.species == "rat": + geneYLocation += 4 * self.BAND_HEIGHT + 4 * self.BAND_SPACING + else: + geneYLocation += 3 * self.BAND_HEIGHT + 3 * self.BAND_SPACING + + # draw the detail view + if self.endMb - self.startMb <= self.DRAW_DETAIL_MB and geneEndPix - geneStartPix > self.EACH_GENE_ARROW_SPACING * 3: + utrColor = ImageColor.getrgb("rgb(66%, 66%, 66%)") + arrowColor = ImageColor.getrgb("rgb(70%, 70%, 70%)") + + # draw the line that runs the entire length of the gene + im_drawer.line( + xy=( + (geneStartPix, geneYLocation + \ + self.EACH_GENE_HEIGHT / 2 * zoom), + (geneEndPix, geneYLocation + self.EACH_GENE_HEIGHT / 2 * zoom)), + fill=outlineColor, width=1) + + # draw the arrows + if geneEndPix - geneStartPix < 1: + genePixRange = 1 + else: + genePixRange = int(geneEndPix - geneStartPix) + for xCoord in range(0, genePixRange): + + if (xCoord % self.EACH_GENE_ARROW_SPACING == 0 and xCoord + self.EACH_GENE_ARROW_SPACING < geneEndPix - geneStartPix) or xCoord == 0: + if strand == "+": + im_drawer.line( + xy=((geneStartPix + xCoord, geneYLocation), + (geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, + geneYLocation + (self.EACH_GENE_HEIGHT / 2) * zoom)), + fill=arrowColor, width=1) + im_drawer.line( + xy=((geneStartPix + xCoord, + geneYLocation + self.EACH_GENE_HEIGHT * zoom), + (geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, + geneYLocation + (self.EACH_GENE_HEIGHT / 2) * zoom)), + fill=arrowColor, width=1) + else: + im_drawer.line( + xy=((geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, + geneYLocation), + (geneStartPix + xCoord, + geneYLocation + (self.EACH_GENE_HEIGHT / 2) * zoom)), + fill=arrowColor, width=1) + im_drawer.line( + xy=((geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, + geneYLocation + self.EACH_GENE_HEIGHT * zoom), + (geneStartPix + xCoord, + geneYLocation + (self.EACH_GENE_HEIGHT / 2) * zoom)), + fill=arrowColor, width=1) + + # draw the blocks for the exon regions + for i in range(0, len(exonStarts)): + exonStartPix = ( + exonStarts[i] - startMb) * plotXScale + xLeftOffset + exonEndPix = (exonEnds[i] - startMb) * \ + plotXScale + xLeftOffset + if (exonStartPix < xLeftOffset): + exonStartPix = xLeftOffset + if (exonEndPix < xLeftOffset): + exonEndPix = xLeftOffset + if (exonEndPix > xLeftOffset + plotWidth): + exonEndPix = xLeftOffset + plotWidth + if (exonStartPix > xLeftOffset + plotWidth): + exonStartPix = xLeftOffset + plotWidth + im_drawer.rectangle( + xy=((exonStartPix, geneYLocation), + (exonEndPix, (geneYLocation + self.EACH_GENE_HEIGHT * zoom))), + outline=outlineColor, fill=fillColor) + + # draw gray blocks for 3' and 5' UTR blocks + if cdsStart and cdsEnd: + utrStartPix = (txStart - startMb) * \ + plotXScale + xLeftOffset + utrEndPix = (cdsStart - startMb) * plotXScale + xLeftOffset + if (utrStartPix < xLeftOffset): + utrStartPix = xLeftOffset + if (utrEndPix < xLeftOffset): + utrEndPix = xLeftOffset + if (utrEndPix > xLeftOffset + plotWidth): + utrEndPix = xLeftOffset + plotWidth + if (utrStartPix > xLeftOffset + plotWidth): + utrStartPix = xLeftOffset + plotWidth + + if self.endMb - self.startMb <= self.DRAW_UTR_LABELS_MB: + if strand == "-": + labelText = "3'" + else: + labelText = "5'" + im_drawer.text( + text=labelText, + xy=(utrStartPix - 9, geneYLocation + \ + self.EACH_GENE_HEIGHT), + font=ImageFont.truetype(font=ARIAL_FILE, size=2)) + + # the second UTR region + + utrStartPix = (cdsEnd - startMb) * plotXScale + xLeftOffset + utrEndPix = (txEnd - startMb) * plotXScale + xLeftOffset + if (utrStartPix < xLeftOffset): + utrStartPix = xLeftOffset + if (utrEndPix < xLeftOffset): + utrEndPix = xLeftOffset + if (utrEndPix > xLeftOffset + plotWidth): + utrEndPix = xLeftOffset + plotWidth + if (utrStartPix > xLeftOffset + plotWidth): + utrStartPix = xLeftOffset + plotWidth + + if self.endMb - self.startMb <= self.DRAW_UTR_LABELS_MB: + if strand == "-": + labelText = "5'" + else: + labelText = "3'" + im_drawer.text( + text=labelText, + xy=(utrEndPix + 2, geneYLocation + \ + self.EACH_GENE_HEIGHT), + font=ImageFont.truetype(font=ARIAL_FILE, size=2)) + + # draw the genes as rectangles + else: + im_drawer.rectangle( + xy=((geneStartPix, geneYLocation), + (geneEndPix, (geneYLocation + self.EACH_GENE_HEIGHT * zoom))), + outline=outlineColor, fill=fillColor) + + COORDS = "%d, %d, %d, %d" % ( + geneStartPix, geneYLocation, geneEndPix, (geneYLocation + self.EACH_GENE_HEIGHT)) + # NL: 06-02-2011 Rob required to display NCBI info in a new window + gifmap.append( + HtmlGenWrapper.create_area_tag( + shape='rect', + coords=COORDS, + href=HREF, + title=TITLE, + target="_blank")) + +# BEGIN HaplotypeAnalyst + def drawHaplotypeBand(self, canvas, gifmap, plotXScale, offset=(40, 120, 80, 10), zoom=1, startMb=None, endMb=None): + if self.plotScale != 'physic' or self.selectedChr == -1 or not self.geneCol: + return + + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + + yPaddingTop = yTopOffset + + thisTrait = self.this_trait + + samplelist = list(self.genotype.prgy) + + smd = [] + for sample in self.sample_vals_dict.keys(): + if self.sample_vals_dict[sample] != "x" and sample in samplelist: + temp = GeneralObject(name=sample, value=float( + self.sample_vals_dict[sample])) + smd.append(temp) + else: + continue + + smd.sort(key=lambda A: A.value) + smd.reverse() + + oldgeneEndPix = -1 + # Initializing plotRight, error before + plotRight = xRightOffset + + im_drawer = ImageDraw.Draw(canvas) + +# find out PlotRight + for _chr in self.genotype: + if _chr.name == self.ChrList[self.selectedChr][0]: + for i, _locus in enumerate(_chr): + txStart = _chr[i].Mb + txEnd = _chr[i].Mb + + geneStartPix = xLeftOffset + plotXScale * \ + (float(txStart) - startMb) - 0 + geneEndPix = xLeftOffset + plotXScale * \ + (float(txEnd) - startMb) - 0 + + drawit = 1 + if (geneStartPix < xLeftOffset): + drawit = 0 + if (geneStartPix > xLeftOffset + plotWidth): + drawit = 0 + + if drawit == 1: + if _chr[i].name != " - ": + plotRight = geneEndPix + 4 + +# end find out PlotRight + + firstGene = 1 + lastGene = 0 + + # Sets the length to the length of the strain list. Beforehand, "oldgeno = self.genotype[0][i].genotype" + # was the only place it was initialized, which worked as long as the very start (startMb = None/0) wasn't being mapped. + # Now there should always be some value set for "oldgeno" - Zach 12/14/2010 + oldgeno = [None] * len(self.strainlist) + + for i, _chr in enumerate(self.genotype): + if _chr.name == self.ChrList[self.selectedChr][0]: + for j, _locus in enumerate(_chr): + txStart = _chr[j].Mb + txEnd = _chr[j].Mb + + geneStartPix = xLeftOffset + plotXScale * \ + (float(txStart) - startMb) - 0 + geneEndPix = xLeftOffset + plotXScale * \ + (float(txEnd) - startMb) + 0 + + if oldgeneEndPix >= xLeftOffset: + drawStart = oldgeneEndPix + 4 + else: + drawStart = xLeftOffset + 3 + + drawEnd = plotRight - 9 + + drawit = 1 + + if (geneStartPix < xLeftOffset): + if firstGene == 1: + drawit = 1 + else: + drawit = 0 + + elif (geneStartPix > (xLeftOffset + plotWidth - 3)): + if lastGene == 0: + drawit = 1 + drawEnd = xLeftOffset + plotWidth - 6 + lastGene = 1 + else: + break + + else: + firstGene = 0 + drawit = 1 + + if drawit == 1: + myColor = DARKBLUE + outlineColor = myColor + fillColor = myColor + + maxind = 0 + + # Draw Genes + + geneYLocation = yPaddingTop + self.NUM_GENE_ROWS * \ + (self.EACH_GENE_HEIGHT) * zoom + if self.dataset.group.species == "mouse" or self.dataset.group.species == "rat": + geneYLocation += 4 * self.BAND_HEIGHT + 4 * self.BAND_SPACING + else: + geneYLocation += 3 * self.BAND_HEIGHT + 3 * self.BAND_SPACING + + if _chr[j].name != " - ": + + if (firstGene == 1) and (lastGene != 1): + oldgeneEndPix = drawStart = xLeftOffset + oldgeno = _chr[j].genotype + continue + + for k, _geno in enumerate(_chr[j].genotype): + plotbxd = 0 + if samplelist[k] in [item.name for item in smd]: + plotbxd = 1 + + if (plotbxd == 1): + ind = 0 + if samplelist[k] in [item.name for item in smd]: + ind = [item.name for item in smd].index( + samplelist[k]) + + maxind = max(ind, maxind) + + # lines + if (oldgeno[k] == -1 and _geno == -1): + mylineColor = self.HAPLOTYPE_NEGATIVE + elif (oldgeno[k] == 1 and _geno == 1): + mylineColor = self.HAPLOTYPE_POSITIVE + elif (oldgeno[k] == 0 and _geno == 0): + mylineColor = self.HAPLOTYPE_HETEROZYGOUS + else: + mylineColor = self.HAPLOTYPE_RECOMBINATION # XZ: Unknown + + im_drawer.line( + xy=((drawStart, + geneYLocation + 7 + 2 * ind * self.EACH_GENE_HEIGHT * zoom), + (drawEnd, + geneYLocation + 7 + 2 * ind * self.EACH_GENE_HEIGHT * zoom)), + fill=mylineColor, width=zoom * (self.EACH_GENE_HEIGHT + 2)) + + fillColor = BLACK + outlineColor = BLACK + if lastGene == 0: + im_drawer.rectangle( + xy=((geneStartPix, + geneYLocation + 2 * ind * self.EACH_GENE_HEIGHT * zoom), + (geneEndPix, + geneYLocation + 2 * ind * self.EACH_GENE_HEIGHT + 2 * self.EACH_GENE_HEIGHT * zoom)), + outline=outlineColor, fill=fillColor) + + COORDS = "%d, %d, %d, %d" % ( + geneStartPix, geneYLocation + ind * self.EACH_GENE_HEIGHT, geneEndPix + 1, (geneYLocation + ind * self.EACH_GENE_HEIGHT)) + TITLE = "Strain: %s, marker (%s) \n Position %2.3f Mb." % ( + samplelist[k], _chr[j].name, float(txStart)) + HREF = '' + gifmap.append( + HtmlGenWrapper.create_area_tag( + shape='rect', + coords=COORDS, + href=HREF, + title=TITLE)) + + # if there are no more markers in a chromosome, the plotRight value calculated above will be before the plotWidth + # resulting in some empty space on the right side of the plot area. This draws an "unknown" bar from plotRight to the edge. + if (plotRight < (xLeftOffset + plotWidth - 3)) and (lastGene == 0): + drawEnd = xLeftOffset + plotWidth - 6 + mylineColor = self.HAPLOTYPE_RECOMBINATION + im_drawer.line( + xy=((plotRight, + geneYLocation + 7 + 2 * ind * self.EACH_GENE_HEIGHT * zoom), + (drawEnd, + geneYLocation + 7 + 2 * ind * self.EACH_GENE_HEIGHT * zoom)), + fill=mylineColor, width=zoom * (self.EACH_GENE_HEIGHT + 2)) + + if lastGene == 0: + draw_rotated_text( + canvas, text="%s" % (_chr[j].name), + font=ImageFont.truetype(font=VERDANA_FILE, + size=12), + xy=(geneStartPix, + geneYLocation + 17 + 2 * maxind * self.EACH_GENE_HEIGHT * zoom), + fill=BLACK, angle=-90) + + oldgeneEndPix = geneEndPix + oldgeno = _chr[j].genotype + firstGene = 0 + else: + lastGene = 0 + + for i, _chr in enumerate(self.genotype): + if _chr.name == self.ChrList[self.selectedChr][0]: + for j, _geno in enumerate(_chr[1].genotype): + + plotbxd = 0 + if samplelist[j] in [item.name for item in smd]: + plotbxd = 1 + + if (plotbxd == 1): + ind = [item.name for item in smd].index( + samplelist[j]) - 1 + expr = smd[ind+1].value + + # Place where font is hardcoded + im_drawer.text( + text="%s" % (samplelist[j]), + xy=((xLeftOffset + plotWidth + 10), + geneYLocation + 11 + 2 * ind * self.EACH_GENE_HEIGHT * zoom), + font=ImageFont.truetype( + font=VERDANA_FILE, size=12), + fill=BLACK) + im_drawer.text( + text="%2.2f" % (expr), + xy=((xLeftOffset + plotWidth + 60), + geneYLocation + 11 + 2 * ind * self.EACH_GENE_HEIGHT * zoom), + font=ImageFont.truetype( + font=VERDANA_FILE, size=12), + fill=BLACK) + +# END HaplotypeAnalyst + + def drawClickBand(self, canvas, gifmap, plotXScale, offset=(40, 120, 80, 10), zoom=1, startMb=None, endMb=None): + im_drawer = ImageDraw.Draw(canvas) + if self.plotScale != 'physic' or self.selectedChr == -1: + return + + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + plotHeight = canvas.size[1] - yTopOffset - yBottomOffset + yZero = canvas.size[1] - yBottomOffset + fontZoom = zoom + if zoom == 2: + fontZoom = 1.5 + + # only draw this many clickable regions (if you set it higher, you get more precision in clicking, + # but it makes the HTML huge, and takes forever to render the page in the first place) + # Draw the bands that you can click on to go to UCSC / Ensembl + MAX_CLICKABLE_REGION_DIVISIONS = 100 + clickableRegionLabelFont = ImageFont.truetype( + font=VERDANA_FILE, size=9) + pixelStep = max( + 5, int(float(plotWidth) / MAX_CLICKABLE_REGION_DIVISIONS)) + # pixelStep: every N pixels, we make a new clickable area for the user to go to that area of the genome. + + numBasesCurrentlyOnScreen = self.kONE_MILLION * \ + abs(startMb - endMb) # Number of bases on screen now + flankingWidthInBases = int( + min((float(numBasesCurrentlyOnScreen) / 2.0), (5 * self.kONE_MILLION))) + webqtlZoomWidth = numBasesCurrentlyOnScreen / 16.0 + # Flanking width should be such that we either zoom in to a 10 million base region, or we show the clicked region at the same scale as we are currently seeing. + + currentChromosome = self.genotype[0].name + i = 0 + + paddingTop = yTopOffset + phenogenPaddingTop = paddingTop + \ + (self.BAND_HEIGHT + self.BAND_SPACING) + if self.dataset.group.species == "mouse" or self.dataset.group.species == "rat": + ucscPaddingTop = paddingTop + 2 * \ + (self.BAND_HEIGHT + self.BAND_SPACING) + ensemblPaddingTop = paddingTop + 3 * \ + (self.BAND_HEIGHT + self.BAND_SPACING) + else: + ucscPaddingTop = paddingTop + \ + (self.BAND_HEIGHT + self.BAND_SPACING) + ensemblPaddingTop = paddingTop + 2 * \ + (self.BAND_HEIGHT + self.BAND_SPACING) + + if zoom == 1: + for pixel in range(xLeftOffset, xLeftOffset + plotWidth, pixelStep): + + calBase = self.kONE_MILLION * \ + (startMb + (endMb - startMb) * \ + (pixel - xLeftOffset - 0.0) / plotWidth) + + xBrowse1 = pixel + xBrowse2 = min(xLeftOffset + plotWidth, + (pixel + pixelStep - 1)) + + WEBQTL_COORDS = "%d, %d, %d, %d" % ( + xBrowse1, paddingTop, xBrowse2, (paddingTop + self.BAND_HEIGHT)) + WEBQTL_HREF = "javascript:rangeView('%s', %f, %f)" % (self.selectedChr - 1, max( + 0, (calBase - webqtlZoomWidth)) / 1000000.0, (calBase + webqtlZoomWidth) / 1000000.0) + + WEBQTL_TITLE = "Click to view this section of the genome in WebQTL" + gifmap.append( + HtmlGenWrapper.create_area_tag( + shape='rect', + coords=WEBQTL_COORDS, + href=WEBQTL_HREF, + title=WEBQTL_TITLE)) + im_drawer.rectangle( + xy=((xBrowse1, paddingTop), + (xBrowse2, (paddingTop + self.BAND_HEIGHT))), + outline=self.CLICKABLE_WEBQTL_REGION_COLOR, + fill=self.CLICKABLE_WEBQTL_REGION_COLOR) + im_drawer.line( + xy=((xBrowse1, paddingTop), (xBrowse1, + (paddingTop + self.BAND_HEIGHT))), + fill=self.CLICKABLE_WEBQTL_REGION_OUTLINE_COLOR) + + if self.dataset.group.species == "mouse" or self.dataset.group.species == "rat": + PHENOGEN_COORDS = "%d, %d, %d, %d" % ( + xBrowse1, phenogenPaddingTop, xBrowse2, (phenogenPaddingTop + self.BAND_HEIGHT)) + if self.dataset.group.species == "mouse": + PHENOGEN_HREF = "https://phenogen.org/gene.jsp?speciesCB=Mm&auto=Y&geneTxt=chr%s:%d-%d&genomeVer=mm10" % ( + self.selectedChr, max(0, calBase - flankingWidthInBases), calBase + flankingWidthInBases) + else: + PHENOGEN_HREF = "https://phenogen.org/gene.jsp?speciesCB=Mm&auto=Y&geneTxt=chr%s:%d-%d&genomeVer=mm10" % ( + self.selectedChr, max(0, calBase - flankingWidthInBases), calBase + flankingWidthInBases) + PHENOGEN_TITLE = "Click to view this section of the genome in PhenoGen" + gifmap.append( + HtmlGenWrapper.create_area_tag( + shape='rect', + coords=PHENOGEN_COORDS, + href=PHENOGEN_HREF, + title=PHENOGEN_TITLE)) + im_drawer.rectangle( + xy=((xBrowse1, phenogenPaddingTop), + (xBrowse2, (phenogenPaddingTop + self.BAND_HEIGHT))), + outline=self.CLICKABLE_PHENOGEN_REGION_COLOR, + fill=self.CLICKABLE_PHENOGEN_REGION_COLOR) + im_drawer.line( + xy=((xBrowse1, phenogenPaddingTop), (xBrowse1, + (phenogenPaddingTop + self.BAND_HEIGHT))), + fill=self.CLICKABLE_PHENOGEN_REGION_OUTLINE_COLOR) + + UCSC_COORDS = "%d, %d, %d, %d" % ( + xBrowse1, ucscPaddingTop, xBrowse2, (ucscPaddingTop + self.BAND_HEIGHT)) + if self.dataset.group.species == "mouse": + UCSC_HREF = "http://genome.ucsc.edu/cgi-bin/hgTracks?db=%s&position=chr%s:%d-%d&hgt.customText=%s/snp/chr%s" % ( + self._ucscDb, self.selectedChr, max(0, calBase - flankingWidthInBases), calBase + flankingWidthInBases, webqtlConfig.PORTADDR, self.selectedChr) + else: + UCSC_HREF = "http://genome.ucsc.edu/cgi-bin/hgTracks?db=%s&position=chr%s:%d-%d" % ( + self._ucscDb, self.selectedChr, max(0, calBase - flankingWidthInBases), calBase + flankingWidthInBases) + UCSC_TITLE = "Click to view this section of the genome in the UCSC Genome Browser" + gifmap.append( + HtmlGenWrapper.create_area_tag( + shape='rect', + coords=UCSC_COORDS, + href=UCSC_HREF, + title=UCSC_TITLE)) + im_drawer.rectangle( + xy=((xBrowse1, ucscPaddingTop), + (xBrowse2, (ucscPaddingTop + self.BAND_HEIGHT))), + outline=self.CLICKABLE_UCSC_REGION_COLOR, + fill=self.CLICKABLE_UCSC_REGION_COLOR) + im_drawer.line( + xy=((xBrowse1, ucscPaddingTop), + (xBrowse1, (ucscPaddingTop + self.BAND_HEIGHT))), + fill=self.CLICKABLE_UCSC_REGION_OUTLINE_COLOR) + + ENSEMBL_COORDS = "%d, %d, %d, %d" % ( + xBrowse1, ensemblPaddingTop, xBrowse2, (ensemblPaddingTop + self.BAND_HEIGHT)) + if self.dataset.group.species == "mouse": + ENSEMBL_HREF = "http://www.ensembl.org/Mus_musculus/contigview?highlight=&chr=%s&vc_start=%d&vc_end=%d&x=35&y=12" % ( + self.selectedChr, max(0, calBase - flankingWidthInBases), calBase + flankingWidthInBases) + else: + ENSEMBL_HREF = "http://www.ensembl.org/Rattus_norvegicus/contigview?chr=%s&start=%d&end=%d" % ( + self.selectedChr, max(0, calBase - flankingWidthInBases), calBase + flankingWidthInBases) + ENSEMBL_TITLE = "Click to view this section of the genome in the Ensembl Genome Browser" + gifmap.append(HtmlGenWrapper.create_area_tag( + shape='rect', + coords=ENSEMBL_COORDS, + href=ENSEMBL_HREF, + title=ENSEMBL_TITLE)) + im_drawer.rectangle( + xy=((xBrowse1, ensemblPaddingTop), + (xBrowse2, (ensemblPaddingTop + self.BAND_HEIGHT))), + outline=self.CLICKABLE_ENSEMBL_REGION_COLOR, + fill=self.CLICKABLE_ENSEMBL_REGION_COLOR) + im_drawer.line( + xy=((xBrowse1, ensemblPaddingTop), + (xBrowse1, (ensemblPaddingTop + self.BAND_HEIGHT))), + fill=self.CLICKABLE_ENSEMBL_REGION_OUTLINE_COLOR) + # end for + + im_drawer.text( + text="Click to view the corresponding section of the genome in an 8x expanded WebQTL map", + xy=((xLeftOffset + 10), paddingTop), # + self.BAND_HEIGHT/2), + font=clickableRegionLabelFont, + fill=self.CLICKABLE_WEBQTL_TEXT_COLOR) + if self.dataset.group.species == "mouse" or self.dataset.group.species == "rat": + im_drawer.text( + text="Click to view the corresponding section of the genome in PhenoGen", + # + self.BAND_HEIGHT/2), + xy=((xLeftOffset + 10), phenogenPaddingTop), + font=clickableRegionLabelFont, fill=self.CLICKABLE_PHENOGEN_TEXT_COLOR) + im_drawer.text( + text="Click to view the corresponding section of the genome in the UCSC Genome Browser", + # + self.BAND_HEIGHT/2), + xy=((xLeftOffset + 10), ucscPaddingTop), + font=clickableRegionLabelFont, fill=self.CLICKABLE_UCSC_TEXT_COLOR) + im_drawer.text( + text="Click to view the corresponding section of the genome in the Ensembl Genome Browser", + # + self.BAND_HEIGHT/2), + xy=((xLeftOffset + 10), ensemblPaddingTop), + font=clickableRegionLabelFont, fill=self.CLICKABLE_ENSEMBL_TEXT_COLOR) + + # draw the gray text + chrFont = ImageFont.truetype( + font=VERDANA_BOLD_FILE, size=26 * zoom) + chrX = xLeftOffset + plotWidth - 2 - im_drawer.textsize( + "Chr %s" % self.ChrList[self.selectedChr][0], font=chrFont)[0] + im_drawer.text( + text="Chr %s" % self.ChrList[self.selectedChr][0], + xy=(chrX, paddingTop), font=chrFont, fill=GRAY) + # end of drawBrowserClickableRegions + else: + # draw the gray text + chrFont = ImageFont.truetype(font=VERDANA_FILE, size=26 * zoom) + chrX = xLeftOffset + (plotWidth - im_drawer.textsize( + "Chr %s" % currentChromosome, font=chrFont)[0]) / 2 + im_drawer.text( + text="Chr %s" % currentChromosome, xy=(chrX, 32), font=chrFont, + fill=GRAY) + # end of drawBrowserClickableRegions + pass + + def drawXAxis(self, canvas, drawAreaHeight, gifmap, plotXScale, showLocusForm, offset=(40, 120, 80, 10), zoom=1, startMb=None, endMb=None): + im_drawer = ImageDraw.Draw(canvas) + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + plotHeight = canvas.size[1] - yTopOffset - yBottomOffset + yZero = canvas.size[1] - yBottomOffset + fontZoom = zoom + if zoom == 2: + fontZoom = 1.5 + + # Parameters + NUM_MINOR_TICKS = 5 # Number of minor ticks between major ticks + X_MAJOR_TICK_THICKNESS = 3 + X_MINOR_TICK_THICKNESS = 1 + X_AXIS_THICKNESS = 1 * zoom + + # ======= Alex: Draw the X-axis labels (megabase location) + MBLabelFont = ImageFont.truetype(font=VERDANA_FILE, size=15 * zoom) + xMajorTickHeight = 10 * zoom # How high the tick extends below the axis + xMinorTickHeight = 5 * zoom + xAxisTickMarkColor = BLACK + xAxisLabelColor = BLACK + fontHeight = 12 * fontZoom # How tall the font that we're using is + spacingFromLabelToAxis = 10 + + if self.plotScale == 'physic': + strYLoc = yZero + MBLabelFont.font.height / 2 + # Physical single chromosome view + if self.selectedChr > -1: + XScale = Plot.detScale(startMb, endMb) + XStart, XEnd, XStep = XScale + if XStep < 8: + XStep *= 2 + spacingAmtX = spacingAmt = (XEnd - XStart) / XStep + + j = 0 + while abs(spacingAmtX - int(spacingAmtX)) >= spacingAmtX / 100.0 and j < 6: + j += 1 + spacingAmtX *= 10 + + formatStr = '%%2.%df' % j + + for counter, _Mb in enumerate(Plot.frange(XStart, XEnd, spacingAmt / NUM_MINOR_TICKS)): + if _Mb < startMb or _Mb > endMb: + continue + Xc = xLeftOffset + plotXScale * (_Mb - startMb) + if counter % NUM_MINOR_TICKS == 0: # Draw a MAJOR mark, not just a minor tick mark + im_drawer.line(xy=((Xc, yZero), + (Xc, yZero + xMajorTickHeight)), + fill=xAxisTickMarkColor, + width=X_MAJOR_TICK_THICKNESS) # Draw the MAJOR tick mark + # What Mbase location to put on the label + labelStr = str(formatStr % _Mb) + strWidth, strHeight = im_drawer.textsize( + labelStr, font=MBLabelFont) + drawStringXc = (Xc - (strWidth / 2.0)) + im_drawer.text(xy=(drawStringXc, strYLoc), + text=labelStr, font=MBLabelFont, + fill=xAxisLabelColor) + else: + im_drawer.line(xy=((Xc, yZero), + (Xc, yZero + xMinorTickHeight)), + fill=xAxisTickMarkColor, + width=X_MINOR_TICK_THICKNESS) # Draw the MINOR tick mark + + # Physical genome wide view + else: + distScale = 0 + startPosX = xLeftOffset + for i, distLen in enumerate(self.ChrLengthDistList): + if distScale == 0: # universal scale in whole genome mapping + if distLen > 75: + distScale = 25 + elif distLen > 30: + distScale = 10 + else: + distScale = 5 + for j, tickdists in enumerate(range(distScale, int(ceil(distLen)), distScale)): + im_drawer.line( + xy=((startPosX + tickdists * plotXScale, yZero), + (startPosX + tickdists * plotXScale, yZero + 7)), + fill=BLACK, width=1 * zoom) + if j % 2 == 0: + draw_rotated_text( + canvas, text=str(tickdists), font=MBLabelFont, + xy=(startPosX + tickdists * plotXScale, + yZero + 10 * zoom), fill=BLACK, angle=270) + startPosX += (self.ChrLengthDistList[i] + \ + self.GraphInterval) * plotXScale + + megabaseLabelFont = ImageFont.truetype( + font=VERDANA_FILE, size=int(18 * zoom * 1.5)) + im_drawer.text( + text="Megabases", + xy=( + xLeftOffset + (plotWidth - im_drawer.textsize( + "Megabases", font=megabaseLabelFont)[0]) / 2, + strYLoc + MBLabelFont.font.height + 10 * (zoom % 2)), + font=megabaseLabelFont, fill=BLACK) + pass + else: + strYLoc = yZero + spacingFromLabelToAxis + MBLabelFont.font.height / 2 + ChrAInfo = [] + preLpos = -1 + distinctCount = 0.0 + + if self.selectedChr == -1: # ZS: If viewing full genome/all chromosomes + for i, _chr in enumerate(self.genotype): + thisChr = [] + Locus0CM = _chr[0].cM + nLoci = len(_chr) + if nLoci <= 8: + for _locus in _chr: + if _locus.name != ' - ': + if _locus.cM != preLpos: + distinctCount += 1 + preLpos = _locus.cM + thisChr.append( + [_locus.name, _locus.cM - Locus0CM]) + else: + for j in (0, round(nLoci / 4), round(nLoci / 2), round(nLoci * 3 / 4), -1): + while _chr[j].name == ' - ': + j += 1 + if _chr[j].cM != preLpos: + distinctCount += 1 + preLpos = _chr[j].cM + thisChr.append( + [_chr[j].name, _chr[j].cM - Locus0CM]) + ChrAInfo.append(thisChr) + else: + for i, _chr in enumerate(self.genotype): + if _chr.name == self.ChrList[self.selectedChr][0]: + thisChr = [] + Locus0CM = _chr[0].cM + for _locus in _chr: + if _locus.name != ' - ': + if _locus.cM != preLpos: + distinctCount += 1 + preLpos = _locus.cM + thisChr.append( + [_locus.name, _locus.cM - Locus0CM]) + ChrAInfo.append(thisChr) + + stepA = (plotWidth + 0.0) / distinctCount + + LRectWidth = 10 + LRectHeight = 3 + offsetA = -stepA + lineColor = LIGHTBLUE + startPosX = xLeftOffset + + for j, ChrInfo in enumerate(ChrAInfo): + preLpos = -1 + for i, item in enumerate(ChrInfo): + Lname, Lpos = item + if Lpos != preLpos: + offsetA += stepA + differ = 1 + else: + differ = 0 + preLpos = Lpos + Lpos *= plotXScale + if self.selectedChr > -1: + Zorder = i % 5 + else: + Zorder = 0 + if differ: + im_drawer.line( + xy=((startPosX + Lpos, yZero), (xLeftOffset + offsetA,\ + yZero + 25)), + fill=lineColor) + im_drawer.line( + xy=((xLeftOffset + offsetA, yZero + 25), (xLeftOffset + offsetA,\ + yZero + 40 + Zorder * (LRectWidth + 3))), + fill=lineColor) + rectColor = ORANGE + else: + im_drawer.line( + xy=((xLeftOffset + offsetA, yZero + 40 + Zorder * (LRectWidth + 3) - 3), (\ + xLeftOffset + offsetA, yZero + 40 + Zorder * (LRectWidth + 3))), + fill=lineColor) + rectColor = DEEPPINK + im_drawer.rectangle( + xy=((xLeftOffset + offsetA, yZero + 40 + Zorder * (LRectWidth + 3)), + (xLeftOffset + offsetA - LRectHeight, + yZero + 40 + Zorder * (LRectWidth + 3) + LRectWidth)), + outline=rectColor, fill=rectColor, width=0) + COORDS = "%d,%d,%d,%d" % (xLeftOffset + offsetA - LRectHeight, yZero + 40 + Zorder * (LRectWidth + 3),\ + xLeftOffset + offsetA, yZero + 40 + Zorder * (LRectWidth + 3) + LRectWidth) + HREF = "/show_trait?trait_id=%s&dataset=%s" % ( + Lname, self.dataset.group.name + "Geno") + #HREF="javascript:showDatabase3('%s','%s','%s','');" % (showLocusForm,fd.RISet+"Geno", Lname) + Areas = HtmlGenWrapper.create_area_tag( + shape='rect', + coords=COORDS, + href=HREF, + target="_blank", + title="Locus : {}".format(Lname)) + gifmap.append(Areas) + # piddle bug + if j == 0: + im_drawer.line( + xy=((startPosX, yZero), (startPosX, yZero + 40)), + fill=lineColor) + startPosX += (self.ChrLengthDistList[j] + \ + self.GraphInterval) * plotXScale + + centimorganLabelFont = ImageFont.truetype( + font=VERDANA_FILE, size=int(18 * zoom * 1.5)) + im_drawer.text( + text="Centimorgans", + xy=(xLeftOffset + (plotWidth - im_drawer.textsize( + "Centimorgans", font=centimorganLabelFont)[0]) / 2, + strYLoc + MBLabelFont.font.height + 10 * (zoom % 2)), + font=centimorganLabelFont, fill=BLACK) + + im_drawer.line(xy=((xLeftOffset, yZero), (xLeftOffset + plotWidth, yZero)), + fill=BLACK, width=X_AXIS_THICKNESS) # Draw the X axis itself + + def drawQTL(self, canvas, drawAreaHeight, gifmap, plotXScale, offset=(40, 120, 80, 10), zoom=1, startMb=None, endMb=None): + im_drawer = ImageDraw.Draw(canvas) + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + plotHeight = canvas.size[1] - yTopOffset - yBottomOffset + fontZoom = zoom + if zoom == 2: + fontZoom = 1.5 + + INTERCROSS = (self.genotype.type == "intercross") + + # draw the LRS scale + # We first determine whether or not we are using a sliding scale. + # If so, we need to compute the maximum LRS value to determine where the max y-value should be, and call this LRS_LOD_Max. + # LRSTop is then defined to be above the LRS_LOD_Max by enough to add one additional LRSScale increment. + # if we are using a set-scale, then we set LRSTop to be the user's value, and LRS_LOD_Max doesn't matter. + + # ZS: This is a mess, but I don't know a better way to account for different mapping methods returning results in different formats + the option to change between LRS and LOD + if self.lrsMax <= 0: # sliding scale + if "lrs_value" in self.qtlresults[0]: + LRS_LOD_Max = max([result['lrs_value'] + for result in self.qtlresults]) + if self.LRS_LOD == "LOD" or self.LRS_LOD == "-logP": + LRS_LOD_Max = LRS_LOD_Max / self.LODFACTOR + if self.permChecked and self.nperm > 0 and not self.multipleInterval: + self.significant = min( + self.significant / self.LODFACTOR, webqtlConfig.MAXLRS) + self.suggestive = min( + self.suggestive / self.LODFACTOR, webqtlConfig.MAXLRS) + else: + if self.permChecked and self.nperm > 0 and not self.multipleInterval: + self.significant = min( + self.significant, webqtlConfig.MAXLRS) + self.suggestive = min( + self.suggestive, webqtlConfig.MAXLRS) + else: + pass + else: + LRS_LOD_Max = max([result['lod_score'] + for result in self.qtlresults]) + 1 + if self.LRS_LOD == "LRS": + LRS_LOD_Max = LRS_LOD_Max * self.LODFACTOR + if self.permChecked and self.nperm > 0 and not self.multipleInterval: + self.significant = min( + self.significant * self.LODFACTOR, webqtlConfig.MAXLRS) + self.suggestive = min( + self.suggestive * self.LODFACTOR, webqtlConfig.MAXLRS) + else: + if self.permChecked and self.nperm > 0 and not self.multipleInterval: + self.significant = min( + self.significant, webqtlConfig.MAXLRS) + self.suggestive = min( + self.suggestive, webqtlConfig.MAXLRS) + else: + pass + + if self.permChecked and self.nperm > 0 and not self.multipleInterval: + LRS_LOD_Max = max(self.significant, LRS_LOD_Max) + + # genotype trait will give infinite LRS + LRS_LOD_Max = min(LRS_LOD_Max, webqtlConfig.MAXLRS) + else: + LRS_LOD_Max = self.lrsMax + + # ZS: Needed to pass to genome browser + js_data = json.loads(self.js_data) + if self.LRS_LOD == "LRS": + js_data['max_score'] = LRS_LOD_Max / 4.61 + else: + js_data['max_score'] = LRS_LOD_Max + self.js_data = json.dumps(js_data) + + LRSScaleFont = ImageFont.truetype(font=VERDANA_FILE, size=16 * zoom) + LRSLODFont = ImageFont.truetype( + font=VERDANA_FILE, size=int(18 * zoom * 1.5)) + + yZero = yTopOffset + plotHeight + LRSHeightThresh = drawAreaHeight + AdditiveHeightThresh = drawAreaHeight / 2 + DominanceHeightThresh = drawAreaHeight / 2 + + if LRS_LOD_Max > 100: + LRSScale = 20.0 + elif LRS_LOD_Max > 20: + LRSScale = 5.0 + elif LRS_LOD_Max > 7.5: + LRSScale = 2.5 + else: + LRSScale = 1.0 + + LRSAxisList = Plot.frange(LRSScale, LRS_LOD_Max, LRSScale) + + # ZS: Convert to int if all axis values are whole numbers + all_int = True + for item in LRSAxisList: + if isinstance(item, int): + continue + else: + all_int = False + break + # TODO(PIL): Convert from PIL + # if all_int: + # max_lrs_width = canvas.stringWidth("%d" % LRS_LOD_Max, font=LRSScaleFont) + 40 + # else: + # max_lrs_width = canvas.stringWidth("%2.1f" % LRS_LOD_Max, font=LRSScaleFont) + 30 + + # draw the "LRS" or "LOD" string to the left of the axis + LRSScaleFont = ImageFont.truetype(font=VERDANA_FILE, size=16 * zoom) + LRSLODFont = ImageFont.truetype( + font=VERDANA_FILE, size=int(18 * zoom * 1.5)) + yZero = yTopOffset + plotHeight + + # TEXT_X_DISPLACEMENT = -20 + #TEXT_Y_DISPLACEMENT = -215 + if all_int: + TEXT_X_DISPLACEMENT = -12 + else: + TEXT_X_DISPLACEMENT = -30 + if self.LRS_LOD == "-logP": + TEXT_Y_DISPLACEMENT = -242 + else: + TEXT_Y_DISPLACEMENT = -210 + draw_rotated_text( + canvas, text=self.LRS_LOD, font=LRSLODFont, + xy=(xLeftOffset - im_drawer.textsize( + "999.99", font=LRSScaleFont)[0] - 15 * (zoom - 1) + TEXT_X_DISPLACEMENT, + yZero + TEXT_Y_DISPLACEMENT - 300 * (zoom - 1)), + fill=BLACK, angle=90) + + for item in LRSAxisList: + if LRS_LOD_Max == 0.0: + LRS_LOD_Max = 0.000001 + yTopOffset + 30 * (zoom - 1) + yLRS = yZero - (item / LRS_LOD_Max) * LRSHeightThresh + im_drawer.line(xy=((xLeftOffset, yLRS), (xLeftOffset - 4, yLRS)), + fill=self.LRS_COLOR, width=1 * zoom) + if all_int: + scaleStr = "%d" % item + else: + scaleStr = "%2.1f" % item + # Draw the LRS/LOD Y axis label + TEXT_Y_DISPLACEMENT = -10 + im_drawer.text( + text=scaleStr, + xy=(xLeftOffset - 4 - im_drawer.textsize(scaleStr, font=LRSScaleFont)[0] - 5, + yLRS + TEXT_Y_DISPLACEMENT), + font=LRSScaleFont, fill=self.LRS_COLOR) + + if self.permChecked and self.nperm > 0 and not self.multipleInterval: + significantY = yZero - self.significant * LRSHeightThresh / LRS_LOD_Max + suggestiveY = yZero - self.suggestive * LRSHeightThresh / LRS_LOD_Max + # significantY = yZero - self.significant*LRSHeightThresh/LRSAxisList[-1] + # suggestiveY = yZero - self.suggestive*LRSHeightThresh/LRSAxisList[-1] + startPosX = xLeftOffset + + # "Significant" and "Suggestive" Drawing Routine + # ======= Draw the thick lines for "Significant" and "Suggestive" ===== (crowell: I tried to make the SNPs draw over these lines, but piddle wouldn't have it...) + + # ZS: I don't know if what I did here with this inner function is clever or overly complicated, but it's the only way I could think of to avoid duplicating the code inside this function + def add_suggestive_significant_lines_and_legend(start_pos_x, chr_length_dist): + rightEdge = xLeftOffset + plotWidth + im_drawer.line( + xy=((start_pos_x + self.SUGGESTIVE_WIDTH / 1.5, suggestiveY), + (rightEdge, suggestiveY)), + fill=self.SUGGESTIVE_COLOR, width=self.SUGGESTIVE_WIDTH * zoom + # ,clipX=(xLeftOffset, xLeftOffset + plotWidth-2) + ) + im_drawer.line( + xy=((start_pos_x + self.SUGGESTIVE_WIDTH / 1.5, significantY), + (rightEdge, significantY)), + fill=self.SIGNIFICANT_COLOR, + width=self.SIGNIFICANT_WIDTH * zoom + # , clipX=(xLeftOffset, xLeftOffset + plotWidth-2) + ) + sugg_coords = "%d, %d, %d, %d" % ( + start_pos_x, suggestiveY - 2, rightEdge + 2 * zoom, suggestiveY + 2) + sig_coords = "%d, %d, %d, %d" % ( + start_pos_x, significantY - 2, rightEdge + 2 * zoom, significantY + 2) + + if self.LRS_LOD == 'LRS': + sugg_title = "Suggestive LRS = %0.2f" % self.suggestive + sig_title = "Significant LRS = %0.2f" % self.significant + else: + sugg_title = "Suggestive LOD = %0.2f" % ( + self.suggestive / 4.61) + sig_title = "Significant LOD = %0.2f" % ( + self.significant / 4.61) + Areas1 = HtmlGenWrapper.create_area_tag( + shape='rect', + coords=sugg_coords, + title=sugg_title) + Areas2 = HtmlGenWrapper.create_area_tag( + shape='rect', + coords=sig_coords, + title=sig_title) + gifmap.append(Areas1) + gifmap.append(Areas2) + + start_pos_x += (chr_length_dist + \ + self.GraphInterval) * plotXScale + return start_pos_x + + for i, _chr in enumerate(self.genotype): + if self.selectedChr != -1: + if _chr.name == self.ChrList[self.selectedChr][0]: + startPosX = add_suggestive_significant_lines_and_legend( + startPosX, self.ChrLengthDistList[0]) + break + else: + continue + else: + startPosX = add_suggestive_significant_lines_and_legend( + startPosX, self.ChrLengthDistList[i]) + + if self.multipleInterval: + lrsEdgeWidth = 1 + else: + if self.additiveChecked: + additiveMax = max([abs(X['additive']) + for X in self.qtlresults]) + lrsEdgeWidth = 3 + + if zoom == 2: + lrsEdgeWidth = 2 * lrsEdgeWidth + + LRSCoordXY = [] + AdditiveCoordXY = [] + DominanceCoordXY = [] + + symbolFont = ImageFont.truetype( + font=FNT_BS_FILE, size=5) # ZS: For Manhattan Plot + + previous_chr = 1 + previous_chr_as_int = 0 + lineWidth = 1 + oldStartPosX = 0 + startPosX = xLeftOffset + for i, qtlresult in enumerate(self.qtlresults): + m = 0 + thisLRSColor = self.colorCollection[0] + if qtlresult['chr'] != previous_chr and self.selectedChr == -1: + if self.manhattan_plot != True and len(LRSCoordXY) > 1: + draw_open_polygon(canvas, xy=LRSCoordXY, + outline=thisLRSColor, width=lrsEdgeWidth) + + if not self.multipleInterval and self.additiveChecked: + plusColor = self.ADDITIVE_COLOR_POSITIVE + minusColor = self.ADDITIVE_COLOR_NEGATIVE + for k, aPoint in enumerate(AdditiveCoordXY): + if k > 0: + Xc0, Yc0 = AdditiveCoordXY[k - 1] + Xc, Yc = aPoint + if (Yc0 - yZero) * (Yc - yZero) < 0: + if Xc == Xc0: # genotype , locus distance is 0 + Xcm = Xc + else: + Xcm = (yZero - Yc0) / \ + ((Yc - Yc0) / (Xc - Xc0)) + Xc0 + if Yc0 < yZero: + im_drawer.line( + xy=((Xc0, Yc0), (Xcm, yZero)), + fill=plusColor, width=lineWidth + ) + im_drawer.line( + xy=((Xcm, yZero), + (Xc, yZero - (Yc - yZero))), + fill=minusColor, width=lineWidth + ) + else: + im_drawer.line( + xy=((Xc0, yZero - (Yc0 - yZero)), + (Xcm, yZero)), + fill=minusColor, width=lineWidth + ) + im_drawer.line( + xy=((Xcm, yZero), (Xc, Yc)), + fill=plusColor, width=lineWidth + ) + elif (Yc0 - yZero) * (Yc - yZero) > 0: + if Yc < yZero: + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), + fill=plusColor, + width=lineWidth + ) + else: + im_drawer.line( + xy=((Xc0, yZero - (Yc0 - yZero)), + (Xc, yZero - (Yc - yZero))), + fill=minusColor, width=lineWidth + ) + else: + minYc = min(Yc - yZero, Yc0 - yZero) + if minYc < 0: + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), + fill=plusColor, width=lineWidth + ) + else: + im_drawer.line( + xy=((Xc0, yZero - (Yc0 - yZero)), + (Xc, yZero - (Yc - yZero))), + fill=minusColor, width=lineWidth + ) + + LRSCoordXY = [] + AdditiveCoordXY = [] + previous_chr = qtlresult['chr'] + previous_chr_as_int += 1 + newStartPosX = ( + self.ChrLengthDistList[previous_chr_as_int - 1] + self.GraphInterval) * plotXScale + if newStartPosX != oldStartPosX: + startPosX += newStartPosX + oldStartPosX = newStartPosX + + # This is because the chromosome value stored in qtlresult['chr'] can be (for example) either X or 20 depending upon the mapping method/scale used + this_chr = str(self.ChrList[self.selectedChr][0]) + if self.plotScale != "physic": + this_chr = str(self.ChrList[self.selectedChr][1] + 1) + + if self.selectedChr == -1 or str(qtlresult['chr']) == this_chr: + if self.plotScale != "physic" and self.mapping_method == "reaper" and not self.manhattan_plot: + start_cm = self.genotype[self.selectedChr - 1][0].cM + Xc = startPosX + (qtlresult['cM'] - start_cm) * plotXScale + if hasattr(self.genotype, "filler"): + if self.genotype.filler: + if self.selectedChr != -1: + Xc = startPosX + \ + (qtlresult['Mb'] - start_cm) * plotXScale + else: + Xc = startPosX + ((qtlresult['Mb'] - start_cm - startMb) * plotXScale) * ( + ((qtlresult['Mb'] - start_cm - startMb) * plotXScale) / ((qtlresult['Mb'] - start_cm - startMb + self.GraphInterval) * plotXScale)) + else: + if self.selectedChr != -1 and qtlresult['Mb'] > endMb: + Xc = startPosX + endMb * plotXScale + else: + if qtlresult['Mb'] - startMb < 0: + continue + Xc = startPosX + (qtlresult['Mb'] - startMb) * plotXScale + + # updated by NL 06-18-2011: + # fix the over limit LRS graph issue since genotype trait may give infinite LRS; + # for any lrs is over than 460(LRS max in this system), it will be reset to 460 + + yLRS = yZero - (item / LRS_LOD_Max) * LRSHeightThresh + + if 'lrs_value' in qtlresult: + if self.LRS_LOD == "LOD" or self.LRS_LOD == "-logP": + if qtlresult['lrs_value'] > 460 or qtlresult['lrs_value'] == 'inf': + Yc = yZero - webqtlConfig.MAXLRS * \ + LRSHeightThresh / \ + (LRS_LOD_Max * self.LODFACTOR) + else: + Yc = yZero - \ + qtlresult['lrs_value'] * LRSHeightThresh / \ + (LRS_LOD_Max * self.LODFACTOR) + else: + if qtlresult['lrs_value'] > 460 or qtlresult['lrs_value'] == 'inf': + Yc = yZero - webqtlConfig.MAXLRS * LRSHeightThresh / LRS_LOD_Max + else: + Yc = yZero - \ + qtlresult['lrs_value'] * \ + LRSHeightThresh / LRS_LOD_Max + else: + if qtlresult['lod_score'] > 100 or qtlresult['lod_score'] == 'inf': + Yc = yZero - webqtlConfig.MAXLRS * LRSHeightThresh / LRS_LOD_Max + else: + if self.LRS_LOD == "LRS": + Yc = yZero - \ + qtlresult['lod_score'] * self.LODFACTOR * \ + LRSHeightThresh / LRS_LOD_Max + else: + Yc = yZero - \ + qtlresult['lod_score'] * \ + LRSHeightThresh / LRS_LOD_Max + + if self.manhattan_plot == True: + if self.color_scheme == "single": + point_color = self.manhattan_single_color + elif self.color_scheme == "varied": + point_color = DISTINCT_COLOR_LIST[previous_chr_as_int] + else: + if self.selectedChr == -1 and (previous_chr_as_int % 2 == 1): + point_color = RED + else: + point_color = BLUE + + im_drawer.text( + text="5", + xy=( + Xc - im_drawer.textsize("5", + font=symbolFont)[0] / 2 + 1, + Yc - 4), + fill=point_color, font=symbolFont) + else: + LRSCoordXY.append((Xc, Yc)) + + if not self.multipleInterval and self.additiveChecked: + if additiveMax == 0.0: + additiveMax = 0.000001 + Yc = yZero - qtlresult['additive'] * \ + AdditiveHeightThresh / additiveMax + AdditiveCoordXY.append((Xc, Yc)) + + if self.selectedChr != -1 and qtlresult['Mb'] > endMb and endMb != -1: + break + m += 1 + + if self.manhattan_plot != True and len(LRSCoordXY) > 1: + draw_open_polygon(canvas, xy=LRSCoordXY, outline=thisLRSColor, + width=lrsEdgeWidth) + + if not self.multipleInterval and self.additiveChecked: + plusColor = self.ADDITIVE_COLOR_POSITIVE + minusColor = self.ADDITIVE_COLOR_NEGATIVE + for k, aPoint in enumerate(AdditiveCoordXY): + if k > 0: + Xc0, Yc0 = AdditiveCoordXY[k - 1] + Xc, Yc = aPoint + if (Yc0 - yZero) * (Yc - yZero) < 0: + if Xc == Xc0: # genotype , locus distance is 0 + Xcm = Xc + else: + Xcm = (yZero - Yc0) / \ + ((Yc - Yc0) / (Xc - Xc0)) + Xc0 + if Yc0 < yZero: + im_drawer.line( + xy=((Xc0, Yc0), (Xcm, yZero)), + fill=plusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + im_drawer.line( + xy=((Xcm, yZero), (Xc, yZero - (Yc - yZero))), + fill=minusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + else: + im_drawer.line( + xy=((Xc0, yZero - (Yc0 - yZero)), + (Xcm, yZero)), + fill=minusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + im_drawer.line( + xy=((Xcm, yZero), (Xc, Yc)), + fill=plusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + elif (Yc0 - yZero) * (Yc - yZero) > 0: + if Yc < yZero: + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), fill=plusColor, + width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + else: + im_drawer.line( + xy=((Xc0, yZero - (Yc0 - yZero)), + (Xc, yZero - (Yc - yZero))), + fill=minusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + else: + minYc = min(Yc - yZero, Yc0 - yZero) + if minYc < 0: + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), + fill=plusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + else: + im_drawer.line( + xy=((Xc0, yZero - (Yc0 - yZero)), + (Xc, yZero - (Yc - yZero))), + fill=minusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + + if not self.multipleInterval and INTERCROSS and self.dominanceChecked: + plusColor = self.DOMINANCE_COLOR_POSITIVE + minusColor = self.DOMINANCE_COLOR_NEGATIVE + for k, aPoint in enumerate(DominanceCoordXY): + if k > 0: + Xc0, Yc0 = DominanceCoordXY[k - 1] + Xc, Yc = aPoint + if (Yc0 - yZero) * (Yc - yZero) < 0: + if Xc == Xc0: # genotype , locus distance is 0 + Xcm = Xc + else: + Xcm = (yZero - Yc0) / \ + ((Yc - Yc0) / (Xc - Xc0)) + Xc0 + if Yc0 < yZero: + im_drawer.line( + xy=((Xc0, Yc0), (Xcm, yZero)), + fill=plusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + im_drawer.line( + xy=((Xcm, yZero), (Xc, yZero - (Yc - yZero))), + fill=minusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + else: + im_drawer.line( + xy=((Xc0, yZero - (Yc0 - yZero)), (Xcm, yZero)), + fill=minusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + im_drawer.line( + xy=((Xcm, yZero), (Xc, Yc)), + fill=plusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + elif (Yc0 - yZero) * (Yc - yZero) > 0: + if Yc < yZero: + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), + fill=plusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + else: + im_drawer.line( + xy=((Xc0, yZero - (Yc0 - yZero)), + (Xc, yZero - (Yc - yZero))), + fill=minusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + else: + minYc = min(Yc - yZero, Yc0 - yZero) + if minYc < 0: + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), + fill=plusColor, width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + else: + im_drawer.line( + xy=((Xc0, yZero - (Yc0 - yZero)), + (Xc, yZero - (Yc - yZero))), fill=minusColor, + width=lineWidth + # , clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + + # draw additive scale + if not self.multipleInterval and self.additiveChecked: + additiveScaleFont = ImageFont.truetype( + font=VERDANA_FILE, size=16 * zoom) + additiveScale = Plot.detScaleOld(0, additiveMax) + additiveStep = (additiveScale[1] - \ + additiveScale[0]) / additiveScale[2] + additiveAxisList = Plot.frange(0, additiveScale[1], additiveStep) + addPlotScale = AdditiveHeightThresh / additiveMax + TEXT_Y_DISPLACEMENT = -8 + + additiveAxisList.append(additiveScale[1]) + for item in additiveAxisList: + additiveY = yZero - item * addPlotScale + im_drawer.line( + xy=((xLeftOffset + plotWidth, additiveY), + (xLeftOffset + 4 + plotWidth, additiveY)), + fill=self.ADDITIVE_COLOR_POSITIVE, width=1 * zoom) + scaleStr = "%2.3f" % item + im_drawer.text( + text=scaleStr, + xy=(xLeftOffset + plotWidth + 6, + additiveY + TEXT_Y_DISPLACEMENT), + font=additiveScaleFont, fill=self.ADDITIVE_COLOR_POSITIVE) + + im_drawer.line( + xy=((xLeftOffset + plotWidth, additiveY), + (xLeftOffset + plotWidth, yZero)), + fill=self.ADDITIVE_COLOR_POSITIVE, width=1 * zoom) + + im_drawer.line( + xy=((xLeftOffset, yZero), (xLeftOffset, yTopOffset + 30 * (zoom - 1))), + fill=self.LRS_COLOR, width=1 * zoom) # the blue line running up the y axis + + def drawGraphBackground(self, canvas, gifmap, offset=(80, 120, 80, 50), zoom=1, startMb=None, endMb=None): + # conditions + # multiple Chromosome view + # single Chromosome Physical + # single Chromosome Genetic + im_drawer = ImageDraw.Draw(canvas) + xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset + plotWidth = canvas.size[0] - xLeftOffset - xRightOffset + plotHeight = canvas.size[1] - yTopOffset - yBottomOffset + yBottom = yTopOffset + plotHeight + fontZoom = zoom + if zoom == 2: + fontZoom = 1.5 + yTopOffset += 30 + + # calculate plot scale + if self.plotScale != 'physic': + self.ChrLengthDistList = self.ChrLengthCMList + drawRegionDistance = self.ChrLengthCMSum + else: + self.ChrLengthDistList = self.ChrLengthMbList + drawRegionDistance = self.ChrLengthMbSum + + if self.selectedChr > -1: # single chromosome view + spacingAmt = plotWidth / 13.5 + i = 0 + for startPix in Plot.frange(xLeftOffset, xLeftOffset + plotWidth, spacingAmt): + if (i % 2 == 0): + theBackColor = self.GRAPH_BACK_DARK_COLOR + else: + theBackColor = self.GRAPH_BACK_LIGHT_COLOR + i += 1 + im_drawer.rectangle( + [(startPix, yTopOffset), + (min(startPix + spacingAmt, xLeftOffset + plotWidth), yBottom)], + outline=theBackColor, fill=theBackColor) + + drawRegionDistance = self.ChrLengthDistList[self.ChrList[self.selectedChr][1]] + self.ChrLengthDistList = [drawRegionDistance] + if self.plotScale == 'physic': + plotXScale = plotWidth / (endMb - startMb) + else: + plotXScale = plotWidth / drawRegionDistance + + else: # multiple chromosome view + plotXScale = plotWidth / \ + ((len(self.genotype) - 1) * self.GraphInterval + drawRegionDistance) + + startPosX = xLeftOffset + if fontZoom == 1.5: + chrFontZoom = 2 + else: + chrFontZoom = 1 + chrLabelFont = ImageFont.truetype( + font=VERDANA_FILE, size=24 * chrFontZoom) + + for i, _chr in enumerate(self.genotype): + if (i % 2 == 0): + theBackColor = self.GRAPH_BACK_DARK_COLOR + else: + theBackColor = self.GRAPH_BACK_LIGHT_COLOR + + # draw the shaded boxes and the sig/sug thick lines + im_drawer.rectangle( + ((startPosX, yTopOffset), + (startPosX + self.ChrLengthDistList[i] * plotXScale, yBottom)), + outline=GAINSBORO, + fill=theBackColor) + + chrNameWidth, chrNameHeight = im_drawer.textsize( + _chr.name, font=chrLabelFont) + chrStartPix = startPosX + \ + (self.ChrLengthDistList[i] * plotXScale - chrNameWidth) / 2 + chrEndPix = startPosX + \ + (self.ChrLengthDistList[i] * plotXScale + chrNameWidth) / 2 + + TEXT_Y_DISPLACEMENT = 0 + im_drawer.text(xy=(chrStartPix, yTopOffset + TEXT_Y_DISPLACEMENT), + text=_chr.name, font=chrLabelFont, fill=BLACK) + COORDS = "%d,%d,%d,%d" % ( + chrStartPix, yTopOffset, chrEndPix, yTopOffset + 20) + + # add by NL 09-03-2010 + HREF = "javascript:chrView(%d,%s);" % (i, self.ChrLengthMbList) + Areas = HtmlGenWrapper.create_area_tag( + shape='rect', + coords=COORDS, + href=HREF) + gifmap.append(Areas) + startPosX += (self.ChrLengthDistList[i] + \ + self.GraphInterval) * plotXScale + + return plotXScale + + def drawPermutationHistogram(self): + ######################################### + # Permutation Graph + ######################################### + myCanvas = Image.new("RGBA", size=(500, 300)) + if 'lod_score' in self.qtlresults[0] and self.LRS_LOD == "LRS": + perm_output = [value * 4.61 for value in self.perm_output] + elif 'lod_score' not in self.qtlresults[0] and self.LRS_LOD == "LOD": + perm_output = [value / 4.61 for value in self.perm_output] + else: + perm_output = self.perm_output + + filename = webqtlUtil.genRandStr("Reg_") + Plot.plotBar(myCanvas, perm_output, XLabel=self.LRS_LOD, + YLabel='Frequency', title=' Histogram of Permutation Test') + myCanvas.save("{}.gif".format(GENERATED_IMAGE_DIR + filename), + format='gif') + + return filename + + def geneTable(self, geneCol, refGene=None): + if self.dataset.group.species == 'mouse' or self.dataset.group.species == 'rat': + self.gene_table_header = self.getGeneTableHeaderList(refGene=None) + self.gene_table_body = self.getGeneTableBody(geneCol, refGene=None) + else: + self.gene_table_header = None + self.gene_table_body = None + + def getGeneTableHeaderList(self, refGene=None): + + gene_table_header_list = [] + if self.dataset.group.species == "mouse": + if refGene: + gene_table_header_list = ["Index", + "Symbol", + "Mb Start", + "Length (Kb)", + "SNP Count", + "SNP Density", + "Avg Expr", + "Human Chr", + "Mb Start (hg19)", + "Literature Correlation", + "Gene Description"] + else: + gene_table_header_list = ["", + "Index", + "Symbol", + "Mb Start", + "Length (Kb)", + "SNP Count", + "SNP Density", + "Avg Expr", + "Human Chr", + "Mb Start (hg19)", + "Gene Description"] + elif self.dataset.group.species == "rat": + gene_table_header_list = ["", + "Index", + "Symbol", + "Mb Start", + "Length (Kb)", + "Avg Expr", + "Mouse Chr", + "Mb Start (mm9)", + "Human Chr", + "Mb Start (hg19)", + "Gene Description"] + + return gene_table_header_list + + def getGeneTableBody(self, geneCol, refGene=None): + gene_table_body = [] + + tableIterationsCnt = 0 + if self.dataset.group.species == "mouse": + for gIndex, theGO in enumerate(geneCol): + + tableIterationsCnt = tableIterationsCnt + 1 + + this_row = [] # container for the cells of each row + selectCheck = HtmlGenWrapper.create_input_tag( + type_="checkbox", + name="selectCheck", + value=theGO["GeneSymbol"], + Class="checkbox trait_checkbox") # checkbox for each row + + geneLength = (theGO["TxEnd"] - theGO["TxStart"]) * 1000.0 + tenPercentLength = geneLength * 0.0001 + txStart = theGO["TxStart"] + txEnd = theGO["TxEnd"] + theGO["snpDensity"] = theGO["snpCount"] / geneLength + if self.ALEX_DEBUG_BOOL_PRINT_GENE_LIST: + geneIdString = 'http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?db=gene&cmd=Retrieve&dopt=Graphics&list_uids=%s' % theGO[ + "GeneID"] + + if theGO["snpCount"]: + snpString = HT.Link( + (f"http://genenetwork.org/webqtl/main.py?FormID=snpBrowser&" + f"chr={theGO['Chr']}&" + f"start={theGO['TxStart']}&" + f"end={theGO['TxEnd']}&" + f"geneName={theGO['GeneSymbol']}&" + f"s1={self.diffCol[0]}&s2=%d"), + str(theGO["snpCount"]) # The text to display + ) + snpString.set_blank_target() + snpString.set_attribute("class", "normalsize") + else: + snpString = 0 + + mouseStartString = "http://genome.ucsc.edu/cgi-bin/hgTracks?clade=vertebrate&org=Mouse&db=mm10&position=chr" + \ + theGO["Chr"] + "%3A" + str(int(theGO["TxStart"] * 1000000.0)) + "-" + str( + int(theGO["TxEnd"] * 1000000.0)) + "&pix=620&Submit=submit" + + # the chromosomes for human 1 are 1qXX.XX + if 'humanGene' in theGO: + if theGO['humanGene']["TxStart"] == '': + humanStartDisplay = "" + else: + humanStartDisplay = "%0.6f" % theGO['humanGene']["TxStart"] + + humanChr = theGO['humanGene']["Chr"] + humanTxStart = theGO['humanGene']["TxStart"] + + humanStartString = "http://genome.ucsc.edu/cgi-bin/hgTracks?clade=vertebrate&org=Human&db=hg17&position=chr%s:%d-%d" % ( + humanChr, int(1000000 * theGO['humanGene']["TxStart"]), int(1000000 * theGO['humanGene']["TxEnd"])) + else: + humanStartString = humanChr = humanStartDisplay = "--" + + geneDescription = theGO["GeneDescription"] + if len(geneDescription) > 70: + geneDescription = geneDescription[:70] + "..." + + if theGO["snpDensity"] < 0.000001: + snpDensityStr = "0" + else: + snpDensityStr = "%0.6f" % theGO["snpDensity"] + + avgExpr = [] # theGO["avgExprVal"] + if avgExpr in ([], None): + avgExpr = "--" + else: + avgExpr = "%0.6f" % avgExpr + + # If we have a referenceGene then we will show the Literature Correlation + if theGO["Chr"] == "X": + chr_as_int = 19 + else: + chr_as_int = int(theGO["Chr"]) - 1 + if refGene: + literatureCorrelationString = str(self.getLiteratureCorrelation( + self.cursor, refGene, theGO['GeneID']) or "N/A") + + this_row = [selectCheck.__str__(), + str(tableIterationsCnt), + str(HtmlGenWrapper.create_link_tag( + geneIdString, + theGO["GeneSymbol"], + target="_blank") + ), + str(HtmlGenWrapper.create_link_tag( + mouseStartString, + "{:.6f}".format(txStart), + target="_blank") + ), + str(HtmlGenWrapper.create_link_tag( + "javascript:rangeView('{}', {:f}, {:f})".format( + str(chr_as_int), + txStart - tenPercentLength, + txEnd + tenPercentLength), + "{:.3f}".format(geneLength))), + snpString, + snpDensityStr, + avgExpr, + humanChr, + str(HtmlGenWrapper.create_link_tag( + humanStartString, + humanStartDisplay, + target="_blank")), + literatureCorrelationString, + geneDescription] + else: + this_row = [selectCheck.__str__(), + str(tableIterationsCnt), + str(HtmlGenWrapper.create_link_tag( + geneIdString, theGO["GeneSymbol"], + target="_blank")), + str(HtmlGenWrapper.create_link_tag( + mouseStartString, + "{:.6f}".format(txStart), + target="_blank")), + str(HtmlGenWrapper.create_link_tag( + "javascript:rangeView('{}', {:f}, {:f})".format( + str(chr_as_int), + txStart - tenPercentLength, + txEnd + tenPercentLength), + "{:.3f}".format(geneLength))), + snpString, + snpDensityStr, + avgExpr, + humanChr, + str(HtmlGenWrapper.create_link_tag( + humanStartString, + humanStartDisplay, + target="_blank")), + geneDescription] + + gene_table_body.append(this_row) + + elif self.dataset.group.species == 'rat': + for gIndex, theGO in enumerate(geneCol): + this_row = [] # container for the cells of each row + selectCheck = str(HtmlGenWrapper.create_input_tag( + type_="checkbox", + name="selectCheck", + Class="checkbox trait_checkbox")) # checkbox for each row + + if theGO["GeneID"] != "": + geneSymbolNCBI = str(HtmlGenWrapper.create_link_tag( + "http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?db=gene&cmd=Retrieve&dopt=Graphics&list_uids={}".format( + theGO["GeneID"]), + theGO["GeneSymbol"], + Class="normalsize", + target="_blank")) + else: + geneSymbolNCBI = theGO["GeneSymbol"] + + if theGO["Chr"] == "X": + chr_as_int = 20 + else: + chr_as_int = int(theGO["Chr"]) - 1 + + geneLength = (float(theGO["TxEnd"]) - float(theGO["TxStart"])) + geneLengthURL = "javascript:rangeView('%s', %f, %f)" % (theGO["Chr"], float( + theGO["TxStart"]) - (geneLength * 0.1), float(theGO["TxEnd"]) + (geneLength * 0.1)) + + avgExprVal = [] + if avgExprVal != "" and avgExprVal: + avgExprVal = "%0.5f" % float(avgExprVal) + else: + avgExprVal = "" + + # Mouse Gene + if theGO['mouseGene']: + mouseChr = theGO['mouseGene']["Chr"] + mouseTxStart = "%0.6f" % theGO['mouseGene']["TxStart"] + else: + mouseChr = mouseTxStart = "" + + # the chromosomes for human 1 are 1qXX.XX + if 'humanGene' in theGO: + humanChr = theGO['humanGene']["Chr"] + humanTxStart = "%0.6f" % theGO['humanGene']["TxStart"] + else: + humanChr = humanTxStart = "" + + geneDesc = theGO["GeneDescription"] + if geneDesc == "---": + geneDesc = "" + + this_row = [selectCheck.__str__(), + str(gIndex + 1), + geneSymbolNCBI, + "%0.6f" % theGO["TxStart"], + str(HtmlGenWrapper.create_link_tag( + geneLengthURL, + "{:.3f}".format(geneLength * 1000.0))), + avgExprVal, + mouseChr, + mouseTxStart, + humanChr, + humanTxStart, + geneDesc] + + gene_table_body.append(this_row) + + return gene_table_body + + def getLiteratureCorrelation(cursor, geneId1=None, geneId2=None): + if not geneId1 or not geneId2: + return None + if geneId1 == geneId2: + return 1.0 + geneId1 = str(geneId1) + geneId2 = str(geneId2) + lCorr = None + try: + query = 'SELECT Value FROM LCorrRamin3 WHERE GeneId1 = %s and GeneId2 = %s' + for x, y in [(geneId1, geneId2), (geneId2, geneId1)]: + cursor.execute(query, (x, y)) + lCorr = cursor.fetchone() + if lCorr: + lCorr = lCorr[0] + break + except: + raise # lCorr = None + return lCorr diff --git a/gn2/wqflask/marker_regression/exceptions.py b/gn2/wqflask/marker_regression/exceptions.py new file mode 100644 index 00000000..8c7e822b --- /dev/null +++ b/gn2/wqflask/marker_regression/exceptions.py @@ -0,0 +1,13 @@ +"""Mapping Exception classes.""" + +class NoMappingResultsError(Exception): + "Exception to raise if no results are computed." + + def __init__(self, trait, dataset, mapping_method): + self.trait = trait + self.dataset = dataset + self.mapping_method = mapping_method + self.message = ( + f"The mapping of trait '{trait}' from dataset '{dataset}' using " + f"the '{mapping_method}' mapping method returned no results.") + super().__init__(self.message, trait, mapping_method) diff --git a/gn2/wqflask/marker_regression/gemma_mapping.py b/gn2/wqflask/marker_regression/gemma_mapping.py new file mode 100644 index 00000000..d8851486 --- /dev/null +++ b/gn2/wqflask/marker_regression/gemma_mapping.py @@ -0,0 +1,245 @@ +import os +import math +import string +import random +import json + +from gn2.base import webqtlConfig +from gn2.base.trait import create_trait +from gn2.base.data_set import create_dataset +from gn2.utility.redis_tools import get_redis_conn +from gn2.utility.tools import flat_files, assert_file +from gn2.utility.tools import GEMMA_WRAPPER_COMMAND +from gn2.utility.tools import TEMPDIR +from gn2.utility.tools import WEBSERVER_MODE +from gn2.utility.tools import get_setting +from gn2.wqflask.database import database_connection +from gn3.computations.gemma import generate_hash_of_string + + +GEMMAOPTS = "-debug" +if WEBSERVER_MODE == 'PROD': + GEMMAOPTS = "-no-check" + + +def generate_random_n_string(n): + return ''.join(random.choice(string.ascii_uppercase + string.digits) + for _ in range(n)) + + +def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco, + maf=0.01, first_run=True, output_files=None): + """Generates p-values for each marker using GEMMA""" + + if this_dataset.group.genofile is not None: + genofile_name = this_dataset.group.genofile[:-5] + else: + genofile_name = this_dataset.group.name + + if first_run: + pheno_filename = gen_pheno_txt_file(this_dataset, genofile_name, vals) + + if not os.path.isfile(f"{webqtlConfig.GENERATED_IMAGE_DIR}" + f"{genofile_name}_output.assoc.txt"): + open((f"{webqtlConfig.GENERATED_IMAGE_DIR}" + f"{genofile_name}_output.assoc.txt"), + "w+") + + k_output_filename = (f"{this_dataset.group.name}_K_" + f"{generate_random_n_string(6)}") + gwa_output_filename = (f"{this_dataset.group.name}_GWA_" + f"{generate_random_n_string(6)}") + + + this_chromosomes_name = [] + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as db_cursor: + for this_chr in this_dataset.species.chromosomes.chromosomes(db_cursor): + this_chromosomes_name.append(this_dataset.species.chromosomes.chromosomes(db_cursor)[this_chr].name) + + chr_list_string = ",".join(this_chromosomes_name) + if covariates != "": + covar_filename = gen_covariates_file(this_dataset, covariates, samples) + if str(use_loco).lower() == "true": + bimbam_dir = flat_files('genotype/bimbam') + geno_filepath = assert_file( + f"{bimbam_dir}/{genofile_name}_geno.txt") + pheno_filepath = f"{TEMPDIR}/gn2/{pheno_filename}.txt" + snps_filepath = assert_file( + f"{bimbam_dir}/{genofile_name}_snps.txt") + k_json_output_filepath = f"{TEMPDIR}/gn2/{k_output_filename}.json" + generate_k_command = (f"{GEMMA_WRAPPER_COMMAND} --json --loco " + f"{chr_list_string} -- {GEMMAOPTS} " + f"-g {geno_filepath} -p " + f"{pheno_filepath} -a " + f"{snps_filepath} -gk > " + f"{k_json_output_filepath}") + os.system(generate_k_command) + + gemma_command = (f"{GEMMA_WRAPPER_COMMAND} --json --loco " + f"--input {k_json_output_filepath} " + f"-- {GEMMAOPTS} " + f"-g {geno_filepath} " + f"-p {pheno_filepath} ") + if covariates != "": + gemma_command += (f"-c {flat_files('mapping')}/" + f"{covar_filename}.txt " + f"-a {flat_files('genotype/bimbam')}/" + f"{genofile_name}_snps.txt " + f"-lmm 9 -maf {maf} > {TEMPDIR}/gn2/" + f"{gwa_output_filename}.json") + else: + gemma_command += (f"-a {flat_files('genotype/bimbam')}/" + f"{genofile_name}_snps.txt -lmm 9 -maf " + f"{maf} > " + f"{TEMPDIR}/gn2/{gwa_output_filename}.json") + + else: + generate_k_command = (f"{GEMMA_WRAPPER_COMMAND} --json -- " + f"{GEMMAOPTS} " + f" -g {flat_files('genotype/bimbam')}/" + f"{genofile_name}_geno.txt -p " + f"{TEMPDIR}/gn2/{pheno_filename}.txt -a " + f"{flat_files('genotype/bimbam')}/" + f"{genofile_name}_snps.txt -gk > " + f"{TEMPDIR}/gn2/{k_output_filename}.json") + + os.system(generate_k_command) + + gemma_command = (f"{GEMMA_WRAPPER_COMMAND} --json --input " + f"{TEMPDIR}/gn2/{k_output_filename}.json -- " + f"{GEMMAOPTS} " + f"-a {flat_files('genotype/bimbam')}/" + f"{genofile_name}_snps.txt " + f"-lmm 9 -g {flat_files('genotype/bimbam')}/" + f"{genofile_name}_geno.txt -p " + f"{TEMPDIR}/gn2/{pheno_filename}.txt ") + + if covariates != "": + gemma_command += (f" -c {flat_files('mapping')}/" + f"{covar_filename}.txt > " + f"{TEMPDIR}/gn2/{gwa_output_filename}.json") + else: + gemma_command += f" > {TEMPDIR}/gn2/{gwa_output_filename}.json" + + os.system(gemma_command) + else: + gwa_output_filename = output_files + + if use_loco == "True": + marker_obs = parse_loco_output(this_dataset, gwa_output_filename) + return marker_obs, gwa_output_filename + else: + marker_obs = parse_loco_output( + this_dataset, gwa_output_filename, use_loco) + return marker_obs, gwa_output_filename + + +def gen_pheno_txt_file(this_dataset, genofile_name, vals): + """Generates phenotype file for GEMMA""" + + filename = "PHENO_" + generate_hash_of_string(this_dataset.name + str(vals)).replace("/", "_") + + with open(f"{TEMPDIR}/gn2/{filename}.txt", "w") as outfile: + for value in vals: + if value == "x": + outfile.write("NA\n") + else: + outfile.write(value + "\n") + + return filename + + +def gen_covariates_file(this_dataset, covariates, samples): + covariate_list = covariates.split(",") + covariate_data_object = [] + for covariate in covariate_list: + this_covariate_data = [] + trait_name = covariate.split(":")[0] + dataset_name = covariate.split(":")[1] + if dataset_name == "Temp": + temp_group = trait_name.split("_")[2] + dataset_ob = create_dataset( + dataset_name="Temp", dataset_type="Temp", group_name=temp_group) + else: + dataset_ob = create_dataset(covariate.split(":")[1]) + trait_ob = create_trait(dataset=dataset_ob, + name=trait_name, + cellid=None) + this_dataset.group.get_samplelist(redis_conn=get_redis_conn()) + trait_samples = this_dataset.group.samplelist + trait_sample_data = trait_ob.data + for index, sample in enumerate(trait_samples): + if sample in samples: + if sample in trait_sample_data: + sample_value = trait_sample_data[sample].value + this_covariate_data.append(sample_value) + else: + this_covariate_data.append("-9") + covariate_data_object.append(this_covariate_data) + + filename = "COVAR_" + generate_hash_of_string(this_dataset.name + str(covariate_data_object)).replace("/", "_") + + with open((f"{flat_files('mapping')}/" + f"{filename}.txt"), + "w") as outfile: + for i in range(len(covariate_data_object[0])): + for this_covariate in covariate_data_object: + outfile.write(str(this_covariate[i]) + "\t") + outfile.write("\n") + + return filename + + +def parse_loco_output(this_dataset, gwa_output_filename, loco="True"): + + output_filename = f"{TEMPDIR}/gn2/{gwa_output_filename}.json" + if os.stat(output_filename).st_size == 0: + return {} + + output_filelist = [] + with open(output_filename) as data_file: + data = json.load(data_file) + + files = data['files'] + for file in files: + output_filelist.append(file[2]) + + included_markers = [] + p_values = [] + marker_obs = [] + previous_chr = 0 + + for this_file in output_filelist: + if not os.path.isfile(this_file): + break + with open(this_file) as output_file: + for line in output_file: + if line.startswith("chr\t"): + continue + else: + marker = {} + marker['name'] = line.split("\t")[1] + if line.split("\t")[0] != "X" and line.split("\t")[0] != "X/Y" and line.split("\t")[0] != "Y" and line.split("\t")[0] != "M": + if "chr" in line.split("\t")[0]: + marker['chr'] = int(line.split("\t")[0][3:]) + else: + marker['chr'] = int(line.split("\t")[0]) + if marker['chr'] > int(previous_chr): + previous_chr = marker['chr'] + elif marker['chr'] < int(previous_chr): + break + else: + marker['chr'] = line.split("\t")[0] + marker['Mb'] = float(line.split("\t")[2]) / 1000000 + marker['p_value'] = float(line.split("\t")[10]) + marker['additive'] = -(float(line.split("\t")[7])/2) + if math.isnan(marker['p_value']) or (marker['p_value'] <= 0): + marker['lod_score'] = marker['p_value'] = 0 + else: + marker['lod_score'] = -math.log10(marker['p_value']) + marker_obs.append(marker) + + included_markers.append(line.split("\t")[1]) + p_values.append(float(line.split("\t")[9])) + + return marker_obs diff --git a/gn2/wqflask/marker_regression/plink_mapping.py b/gn2/wqflask/marker_regression/plink_mapping.py new file mode 100644 index 00000000..7427717a --- /dev/null +++ b/gn2/wqflask/marker_regression/plink_mapping.py @@ -0,0 +1,167 @@ +import string +import os + +from gn2.base.webqtlConfig import TMPDIR +from gn2.utility import webqtlUtil +from gn2.utility.tools import flat_files, PLINK_COMMAND + + +def run_plink(this_trait, dataset, species, vals, maf): + plink_output_filename = webqtlUtil.genRandStr( + f"{dataset.group.name}_{this_trait.name}_") + gen_pheno_txt_file(dataset, vals) + + plink_command = f"{PLINK_COMMAND} --noweb --bfile {flat_files('mapping')}/{dataset.group.name} --no-pheno --no-fid --no-parents --no-sex --maf {maf} --out { TMPDIR}{plink_output_filename} --assoc " + + os.system(plink_command) + + count, p_values = parse_plink_output(plink_output_filename, species) + + dataset.group.markers.add_pvalues(p_values) + + return dataset.group.markers.markers + + +def gen_pheno_txt_file(this_dataset, vals): + """Generates phenotype file for GEMMA/PLINK""" + + current_file_data = [] + with open(f"{flat_files('mapping')}/{this_dataset.group.name}.fam", "r") as outfile: + for i, line in enumerate(outfile): + split_line = line.split() + current_file_data.append(split_line) + + with open(f"{flat_files('mapping')}/{this_dataset.group.name}.fam", "w") as outfile: + for i, line in enumerate(current_file_data): + if vals[i] == "x": + this_val = -9 + else: + this_val = vals[i] + outfile.write("0 " + line[1] + " " + line[2] + " " + \ + line[3] + " " + line[4] + " " + str(this_val) + "\n") + + +def gen_pheno_txt_file_plink(this_trait, dataset, vals, pheno_filename=''): + ped_sample_list = get_samples_from_ped_file(dataset) + output_file = open(f"{TMPDIR}{pheno_filename}.txt", "wb") + header = f"FID\tIID\t{this_trait.name}\n" + output_file.write(header) + + new_value_list = [] + + # if valueDict does not include some strain, value will be set to -9999 as missing value + for i, sample in enumerate(ped_sample_list): + try: + value = vals[i] + value = str(value).replace('value=', '') + value = value.strip() + except: + value = -9999 + + new_value_list.append(value) + + new_line = '' + for i, sample in enumerate(ped_sample_list): + j = i + 1 + value = new_value_list[i] + new_line += f"{sample}\t{sample}\t{value}\n" + + if j % 1000 == 0: + output_file.write(newLine) + new_line = '' + + if new_line: + output_file.write(new_line) + + output_file.close() + +# get strain name from ped file in order + + +def get_samples_from_ped_file(dataset): + ped_file = open(f"{flat_files('mapping')}{dataset.group.name}.ped", "r") + line = ped_file.readline() + sample_list = [] + + while line: + lineList = line.strip().split('\t') + lineList = [item.strip() for item in lineList] + + sample_name = lineList[0] + sample_list.append(sample_name) + + line = ped_file.readline() + + return sample_list + + +def parse_plink_output(output_filename, species): + plink_results = {} + + threshold_p_value = 1 + + result_fp = open(f"{TMPDIR}{output_filename}.qassoc", "rb") + + line = result_fp.readline() + + value_list = [] # initialize value list, this list will include snp, bp and pvalue info + p_value_dict = {} + count = 0 + + while line: + # convert line from str to list + line_list = build_line_list(line=line) + + # only keep the records whose chromosome name is in db + if int(line_list[0]) in species.chromosomes.chromosomes and line_list[-1] and line_list[-1].strip() != 'NA': + + chr_name = species.chromosomes.chromosomes[int(line_list[0])] + snp = line_list[1] + BP = line_list[2] + p_value = float(line_list[-1]) + if threshold_p_value >= 0 and threshold_p_value <= 1: + if p_value < threshold_p_value: + p_value_dict[snp] = float(p_value) + + if chr_name in plink_results: + value_list = plink_results[chr_name] + + # pvalue range is [0,1] + if threshold_p_value >= 0 and threshold_p_value <= 1: + if p_value < threshold_p_value: + value_list.append((snp, BP, p_value)) + count += 1 + + plink_results[chr_name] = value_list + value_list = [] + else: + if threshold_p_value >= 0 and threshold_p_value <= 1: + if p_value < threshold_p_value: + value_list.append((snp, BP, p_value)) + count += 1 + + if value_list: + plink_results[chr_name] = value_list + + value_list = [] + + line = result_fp.readline() + else: + line = result_fp.readline() + + return count, p_value_dict + +###################################################### +# input: line: str,one line read from file +# function: convert line from str to list; +# output: lineList list +####################################################### + + +def build_line_list(line=""): + # irregular number of whitespaces between columns + line_list = line.strip().split(' ') + line_list = [item for item in line_list if item != ''] + line_list = [item.strip() for item in line_list] + + return line_list diff --git a/gn2/wqflask/marker_regression/qtlreaper_mapping.py b/gn2/wqflask/marker_regression/qtlreaper_mapping.py new file mode 100644 index 00000000..2c9ca1b2 --- /dev/null +++ b/gn2/wqflask/marker_regression/qtlreaper_mapping.py @@ -0,0 +1,193 @@ +import os +import math +import string +import random +import json +import re + +from gn2.base import webqtlConfig +from gn2.base.trait import GeneralTrait +from gn2.base.data_set import create_dataset +from gn2.utility.tools import flat_files, REAPER_COMMAND, TEMPDIR + + +def run_reaper(this_trait, this_dataset, samples, vals, json_data, num_perm, boot_check, num_bootstrap, do_control, control_marker, manhattan_plot, first_run=True, output_files=None): + """Generates p-values for each marker using qtlreaper""" + + if first_run: + if this_dataset.group.genofile != None: + genofile_name = this_dataset.group.genofile[:-5] + else: + genofile_name = this_dataset.group.name + + trait_filename = f"{str(this_trait.name)}_{str(this_dataset.name)}_pheno" + gen_pheno_txt_file(samples, vals, trait_filename) + + output_filename = (f"{this_dataset.group.name}_GWA_" + + ''.join(random.choice(string.ascii_uppercase + string.digits) + for _ in range(6)) + ) + bootstrap_filename = None + permu_filename = None + + opt_list = [] + if boot_check and num_bootstrap > 0: + bootstrap_filename = (f"{this_dataset.group.name}_BOOTSTRAP_" + + ''.join(random.choice(string.ascii_uppercase + string.digits) + for _ in range(6)) + ) + + opt_list.append("-b") + opt_list.append(f"--n_bootstrap {str(num_bootstrap)}") + opt_list.append( + f"--bootstrap_output {webqtlConfig.GENERATED_IMAGE_DIR}{bootstrap_filename}.txt") + if num_perm > 0: + permu_filename = ("{this_dataset.group.name}_PERM_" + + ''.join(random.choice(string.ascii_uppercase + + string.digits) for _ in range(6)) + ) + opt_list.append("-n " + str(num_perm)) + opt_list.append( + "--permu_output " + webqtlConfig.GENERATED_IMAGE_DIR + permu_filename + ".txt") + if control_marker != "" and do_control == "true": + opt_list.append("-c " + control_marker) + if manhattan_plot != True: + opt_list.append("--interval 1") + + reaper_command = (REAPER_COMMAND + + ' --geno {0}/{1}.geno --traits {2}/gn2/{3}.txt {4} -o {5}{6}.txt'.format(flat_files('genotype'), + + genofile_name, + TEMPDIR, + trait_filename, + " ".join( + opt_list), + webqtlConfig.GENERATED_IMAGE_DIR, + output_filename)) + os.system(reaper_command) + else: + output_filename, permu_filename, bootstrap_filename = output_files + + marker_obs, permu_vals, bootstrap_vals = parse_reaper_output( + output_filename, permu_filename, bootstrap_filename) + + suggestive = 0 + significant = 0 + if len(permu_vals) > 0: + suggestive = permu_vals[int(num_perm * 0.37 - 1)] + significant = permu_vals[int(num_perm * 0.95 - 1)] + + return (marker_obs, permu_vals, suggestive, significant, bootstrap_vals, + [output_filename, permu_filename, bootstrap_filename]) + + +def gen_pheno_txt_file(samples, vals, trait_filename): + """Generates phenotype file for GEMMA""" + + with open(f"{TEMPDIR}/gn2/{trait_filename}.txt", "w") as outfile: + outfile.write("Trait\t") + + filtered_sample_list = [] + filtered_vals_list = [] + for i, sample in enumerate(samples): + if vals[i] != "x": + filtered_sample_list.append(sample) + filtered_vals_list.append(vals[i]) + + samples_string = "\t".join(filtered_sample_list) + outfile.write(samples_string + "\n") + outfile.write("T1\t") + values_string = "\t".join(filtered_vals_list) + outfile.write(values_string) + + +def parse_reaper_output(gwa_filename, permu_filename, bootstrap_filename): + included_markers = [] + p_values = [] + marker_obs = [] + + only_cm = False + only_mb = False + + with open(f"{webqtlConfig.GENERATED_IMAGE_DIR}{gwa_filename}.txt") as output_file: + for line in output_file: + if line.startswith("ID\t"): + if len(line.split("\t")) < 8: + if 'cM' in line.split("\t"): + only_cm = True + else: + only_mb = True + continue + else: + marker = {} + marker['name'] = line.split("\t")[1] + try: + marker['chr'] = int(line.split("\t")[2]) + except: + marker['chr'] = line.split("\t")[2] + if only_cm or only_mb: + if only_cm: + marker['cM'] = float(line.split("\t")[3]) + else: + if float(line.split("\t")[3]) > 1000: + marker['Mb'] = float(line.split("\t")[3]) / 1000000 + else: + marker['Mb'] = float(line.split("\t")[3]) + if float(line.split("\t")[6]) != 1: + marker['p_value'] = float(line.split("\t")[6]) + marker['lrs_value'] = float(line.split("\t")[4]) + marker['lod_score'] = marker['lrs_value'] / 4.61 + marker['additive'] = float(line.split("\t")[5]) + else: + marker['cM'] = float(line.split("\t")[3]) + if float(line.split("\t")[4]) > 1000: + marker['Mb'] = float(line.split("\t")[4]) / 1000000 + else: + marker['Mb'] = float(line.split("\t")[4]) + if float(line.split("\t")[7]) != 1: + marker['p_value'] = float(line.split("\t")[7]) + marker['lrs_value'] = float(line.split("\t")[5]) + marker['lod_score'] = marker['lrs_value'] / 4.61 + marker['additive'] = float(line.split("\t")[6]) + marker_obs.append(marker) + + # ZS: Results have to be reordered because the new reaper returns results sorted alphabetically by chr for some reason, resulting in chr 1 being followed by 10, etc + sorted_indices = natural_sort(marker_obs) + + permu_vals = [] + if permu_filename: + with open(f"{webqtlConfig.GENERATED_IMAGE_DIR}{permu_filename}.txt") as permu_file: + for line in permu_file: + permu_vals.append(float(line)) + + bootstrap_vals = [] + if bootstrap_filename: + with open(f"{webqtlConfig.GENERATED_IMAGE_DIR}{bootstrap_filename}.txt") as bootstrap_file: + for line in bootstrap_file: + bootstrap_vals.append(int(line)) + + marker_obs = [marker_obs[i] for i in sorted_indices] + if len(bootstrap_vals) > 0: + bootstrap_vals = [bootstrap_vals[i] for i in sorted_indices] + + return marker_obs, permu_vals, bootstrap_vals + + +def natural_sort(marker_list): + """ + Function to naturally sort numbers + strings, adopted from user Mark Byers here: https://stackoverflow.com/questions/4836710/does-python-have-a-built-in-function-for-string-natural-sort + Changed to return indices instead of values, though, since the same reordering needs to be applied to bootstrap results + """ + + def convert(text): + if text.isdigit(): + return int(text) + else: + if text != "M": + return text.lower() + else: + return "z" + + alphanum_key = lambda key: [convert(c) for c in re.split( + '([0-9]+)', str(marker_list[key]['chr']))] + return sorted(list(range(len(marker_list))), key=alphanum_key) diff --git a/gn2/wqflask/marker_regression/rqtl_mapping.py b/gn2/wqflask/marker_regression/rqtl_mapping.py new file mode 100644 index 00000000..b7739228 --- /dev/null +++ b/gn2/wqflask/marker_regression/rqtl_mapping.py @@ -0,0 +1,167 @@ +import csv +import hashlib +import io +import json +import requests +import shutil +from typing import Dict +from typing import List +from typing import Optional +from typing import TextIO + +import numpy as np + +from gn2.base.webqtlConfig import TMPDIR +from gn2.base.trait import create_trait +from gn2.utility.redis_tools import get_redis_conn +from gn2.utility.tools import locate, get_setting, GN3_LOCAL_URL +from gn2.wqflask.database import database_connection + + +def run_rqtl(trait_name, vals, samples, dataset, pair_scan, mapping_scale, model, method, num_perm, perm_strata_list, do_control, control_marker, manhattan_plot, cofactors): + """Run R/qtl by making a request to the GN3 endpoint and reading in the output file(s)""" + + pheno_file = write_phenotype_file(trait_name, samples, vals, dataset, cofactors, perm_strata_list) + if dataset.group.genofile: + geno_file = locate(dataset.group.genofile, "genotype") + else: + geno_file = locate(dataset.group.name + ".geno", "genotype") + + post_data = { + "pheno_file": pheno_file, + "geno_file": geno_file, + "model": model, + "method": method, + "nperm": num_perm, + "scale": mapping_scale + } + + if pair_scan: + post_data["pairscan"] = True + + if cofactors: + covarstruct_file = write_covarstruct_file(cofactors) + post_data["covarstruct"] = covarstruct_file + + if do_control == "true" and control_marker: + post_data["control"] = control_marker + + if not manhattan_plot and not pair_scan: + post_data["interval"] = True + if cofactors: + post_data["addcovar"] = True + + if perm_strata_list: + post_data["pstrata"] = True + + rqtl_output = requests.post(GN3_LOCAL_URL + "api/rqtl/compute", data=post_data).json() + if num_perm > 0: + return rqtl_output['perm_results'], rqtl_output['suggestive'], rqtl_output['significant'], rqtl_output['results'] + else: + return rqtl_output['results'] + + +def get_hash_of_textio(the_file: TextIO) -> str: + """Given a StringIO, return the hash of its contents""" + + the_file.seek(0) + hash_of_file = hashlib.md5(the_file.read().encode()).hexdigest() + hash_of_file = hash_of_file.replace("/", "_") # Replace / with _ to prevent issue with filenames being translated to directories + + return hash_of_file + + +def write_covarstruct_file(cofactors: str) -> str: + """ + Given list of cofactors (as comma-delimited string), write + a comma-delimited file where the first column consists of cofactor names + and the second column indicates whether they're numerical or categorical + """ + trait_datatype_json = None + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute("SELECT value FROM TraitMetadata WHERE type='trait_data_type'") + trait_datatype_json = json.loads(cursor.fetchone()[0]) + + covar_struct_file = io.StringIO() + writer = csv.writer(covar_struct_file, delimiter="\t", quoting = csv.QUOTE_NONE) + for cofactor in cofactors.split(","): + datatype = trait_datatype_json[cofactor] if cofactor in trait_datatype_json else "numerical" + cofactor_name = cofactor.split(":")[0] + writer.writerow([cofactor_name, datatype]) + + hash_of_file = get_hash_of_textio(covar_struct_file) + file_path = TMPDIR + hash_of_file + ".csv" + + with open(file_path, "w") as fd: + covar_struct_file.seek(0) + shutil.copyfileobj(covar_struct_file, fd) + + return file_path + + +def write_phenotype_file(trait_name: str, + samples: List[str], + vals: List, + dataset_ob, + cofactors: Optional[str] = None, + perm_strata_list: Optional[List] = None) -> TextIO: + """Given trait name, sample list, value list, dataset ob, and optional string + representing cofactors, return the file's full path/name + + """ + cofactor_data = cofactors_to_dict(cofactors, dataset_ob, samples) + + pheno_file = io.StringIO() + writer = csv.writer(pheno_file, delimiter="\t", quoting=csv.QUOTE_NONE) + + header_row = ["Samples", trait_name] + header_row += [cofactor for cofactor in cofactor_data] + if perm_strata_list: + header_row.append("Strata") + + writer.writerow(header_row) + for i, sample in enumerate(samples): + this_row = [sample] + if vals[i] != "x": + this_row.append(str(round(float(vals[i]), 3))) + else: + this_row.append("NA") + for cofactor in cofactor_data: + this_row.append(cofactor_data[cofactor][i]) + if perm_strata_list: + this_row.append(perm_strata_list[i]) + writer.writerow(this_row) + + hash_of_file = get_hash_of_textio(pheno_file) + file_path = TMPDIR + hash_of_file + ".csv" + + with open(file_path, "w") as fd: + pheno_file.seek(0) + shutil.copyfileobj(pheno_file, fd) + + return file_path + + +def cofactors_to_dict(cofactors: str, dataset_ob, samples) -> Dict: + """Given a string of cofactors, the trait being mapped's dataset ob, + and list of samples, return cofactor data as a Dict + + """ + cofactor_dict = {} + if cofactors: + dataset_ob.group.get_samplelist(redis_conn=get_redis_conn()) + sample_list = dataset_ob.group.samplelist + for cofactor in cofactors.split(","): + cofactor_name, cofactor_dataset = cofactor.split(":") + if cofactor_dataset == dataset_ob.name: + cofactor_dict[cofactor_name] = [] + trait_ob = create_trait(dataset=dataset_ob, + name=cofactor_name) + sample_data = trait_ob.data + for index, sample in enumerate(samples): + if sample in sample_data: + sample_value = str(round(float(sample_data[sample].value), 3)) + cofactor_dict[cofactor_name].append(sample_value) + else: + cofactor_dict[cofactor_name].append("NA") + return cofactor_dict diff --git a/gn2/wqflask/marker_regression/run_mapping.py b/gn2/wqflask/marker_regression/run_mapping.py new file mode 100644 index 00000000..7d2d40f7 --- /dev/null +++ b/gn2/wqflask/marker_regression/run_mapping.py @@ -0,0 +1,743 @@ +from gn2.base.trait import GeneralTrait +from gn2.base import data_set # import create_dataset + +from pprint import pformat as pf + +import string +import math +from decimal import Decimal +import random +import sys +import datetime +import os +import collections +import uuid + +import numpy as np + +import pickle as pickle +import itertools + +import simplejson as json + +from redis import Redis +Redis = Redis() + +from flask import Flask, g + +from gn2.base.trait import GeneralTrait +from gn2.base import data_set +from gn2.base import species +from gn2.base import webqtlConfig +from gn2.utility import webqtlUtil, helper_functions, hmac, Plot, Bunch, temp_data +from gn2.utility.redis_tools import get_redis_conn +from gn2.wqflask.database import database_connection +from gn2.wqflask.marker_regression import gemma_mapping, rqtl_mapping, qtlreaper_mapping, plink_mapping +from gn2.wqflask.show_trait.SampleList import SampleList + +from gn2.utility.tools import locate, get_setting, locate_ignore_error, GEMMA_COMMAND, PLINK_COMMAND, TEMPDIR +from gn2.utility.external import shell +from gn2.base.webqtlConfig import TMPDIR, GENERATED_TEXT_DIR + +Redis = get_redis_conn() + +class RunMapping: + + def __init__(self, start_vars, temp_uuid): + helper_functions.get_species_dataset_trait(self, start_vars) + + # needed to pass temp_uuid to gn1 mapping code (marker_regression_gn1.py) + self.temp_uuid = temp_uuid + + # ZS: Needed to zoom in or remap temp traits like PCA traits + if "temp_trait" in start_vars and start_vars['temp_trait'] != "False": + self.temp_trait = "True" + self.group = self.dataset.group.name + + self.hash_of_inputs = start_vars['hash_of_inputs'] + self.dataid = start_vars['dataid'] + + self.json_data = {} + self.json_data['lodnames'] = ['lod.hk'] + + # ZS: Sometimes a group may have a genofile that only includes a subset of samples + genofile_samplelist = [] + if 'genofile' in start_vars: + if start_vars['genofile'] != "": + self.genofile_string = start_vars['genofile'] + self.dataset.group.genofile = self.genofile_string.split(":")[ + 0] + genofile_samplelist = get_genofile_samplelist(self.dataset) + + all_samples_ordered = self.dataset.group.all_samples_ordered() + + self.vals = [] + self.samples = [] + self.sample_vals = start_vars['sample_vals'] + self.vals_hash = start_vars['vals_hash'] + sample_val_dict = json.loads(self.sample_vals) + samples = sample_val_dict.keys() + if (len(genofile_samplelist) != 0): + for sample in genofile_samplelist: + self.samples.append(sample) + if sample in samples: + self.vals.append(sample_val_dict[sample]) + else: + self.vals.append("x") + else: + for sample in self.dataset.group.samplelist: + if sample in samples: + self.vals.append(sample_val_dict[sample]) + self.samples.append(sample) + + if 'n_samples' in start_vars: + self.n_samples = start_vars['n_samples'] + else: + self.n_samples = len([val for val in self.vals if val != "x"]) + + # ZS: Check if genotypes exist in the DB in order to create links for markers + + self.geno_db_exists = geno_db_exists(self.dataset) + + self.mapping_method = start_vars['method'] + if "results_path" in start_vars: + self.mapping_results_path = start_vars['results_path'] + else: + mapping_results_filename = "_".join([self.dataset.group.name, self.mapping_method, self.vals_hash]).replace("/", "_") + self.mapping_results_path = "{}{}.csv".format( + webqtlConfig.GENERATED_IMAGE_DIR, mapping_results_filename) + + self.pair_scan = False + self.manhattan_plot = False + if 'manhattan_plot' in start_vars: + if start_vars['manhattan_plot'].lower() != "false": + self.color_scheme = "alternating" + if "color_scheme" in start_vars: + self.color_scheme = start_vars['color_scheme'] + if self.color_scheme == "single": + self.manhattan_single_color = start_vars['manhattan_single_color'] + self.manhattan_plot = True + + self.maf = start_vars['maf'] # Minor allele frequency + if "use_loco" in start_vars: + self.use_loco = start_vars['use_loco'] + else: + self.use_loco = None + self.suggestive = "" + self.significant = "" + if 'transform' in start_vars: + self.transform = start_vars['transform'] + else: + self.transform = "" + self.score_type = "LRS" # ZS: LRS or LOD + self.mapping_scale = "physic" + if "mapping_scale" in start_vars: + self.mapping_scale = start_vars['mapping_scale'] + self.num_perm = 0 + self.perm_output = [] + self.bootstrap_results = [] + self.covariates = start_vars['covariates'] if "covariates" in start_vars else "" + self.categorical_vars = [] + + # ZS: This is passed to GN1 code for single chr mapping + self.selected_chr = -1 + if "selected_chr" in start_vars: + # ZS: Needs to be -1 if showing full map; there's probably a better way to fix this + if int(start_vars['selected_chr']) != -1: + self.selected_chr = int(start_vars['selected_chr']) + 1 + else: + self.selected_chr = int(start_vars['selected_chr']) + if "startMb" in start_vars: + self.startMb = start_vars['startMb'] + if "endMb" in start_vars: + self.endMb = start_vars['endMb'] + if "graphWidth" in start_vars: + self.graphWidth = start_vars['graphWidth'] + if "lrsMax" in start_vars: + self.lrsMax = start_vars['lrsMax'] + if "haplotypeAnalystCheck" in start_vars: + self.haplotypeAnalystCheck = start_vars['haplotypeAnalystCheck'] + if "startMb" in start_vars: # This is to ensure showGenes, Legend, etc are checked the first time you open the mapping page, since startMb will only not be set during the first load + if "permCheck" in start_vars: + self.permCheck = "ON" + else: + self.permCheck = False + self.num_perm = int(start_vars['num_perm']) + + self.LRSCheck = start_vars['LRSCheck'] + + if "showSNP" in start_vars: + self.showSNP = start_vars['showSNP'] + else: + self.showSNP = False + + if "showHomology" in start_vars: + self.showHomology = start_vars['showHomology'] + else: + self.showHomology = False + + if "showGenes" in start_vars: + self.showGenes = start_vars['showGenes'] + else: + self.showGenes = False + + if "viewLegend" in start_vars: + self.viewLegend = start_vars['viewLegend'] + else: + self.viewLegend = False + else: + try: + if int(start_vars['num_perm']) > 0: + self.num_perm = int(start_vars['num_perm']) + except: + self.num_perm = 0 + + if self.num_perm > 0: + self.permCheck = "ON" + else: + self.permCheck = False + self.showSNP = "ON" + self.showGenes = "ON" + self.viewLegend = "ON" + + # self.dataset.group.get_markers() + if self.mapping_method == "gemma": + self.first_run = True + self.output_files = None + if 'output_files' in start_vars: + self.output_files = start_vars['output_files'] + # ZS: check if first run so existing result files can be used if it isn't (for example zooming on a chromosome, etc) + if 'first_run' in start_vars: + self.first_run = False + self.score_type = "-logP" + self.manhattan_plot = True + if self.use_loco == "True": + marker_obs, self.output_files = gemma_mapping.run_gemma( + self.this_trait, self.dataset, self.samples, self.vals, self.covariates, self.use_loco, self.maf, self.first_run, self.output_files) + else: + marker_obs, self.output_files = gemma_mapping.run_gemma( + self.this_trait, self.dataset, self.samples, self.vals, self.covariates, self.use_loco, self.maf, self.first_run, self.output_files) + results = marker_obs + elif self.mapping_method == "rqtl_plink": + results = self.run_rqtl_plink() + elif self.mapping_method == "rqtl_geno": + self.perm_strata = [] + if "perm_strata" in start_vars and "categorical_vars" in start_vars: + self.categorical_vars = start_vars["categorical_vars"].split( + ",") + if len(self.categorical_vars) and start_vars["perm_strata"] == "True": + primary_samples = SampleList(dataset=self.dataset, + sample_names=self.samples, + this_trait=self.this_trait) + + self.perm_strata = get_perm_strata( + self.this_trait, primary_samples, self.categorical_vars, self.samples) + self.score_type = "-logP" + self.control_marker = start_vars['control_marker'] + self.do_control = start_vars['do_control'] + if 'mapmethod_rqtl' in start_vars: + self.method = start_vars['mapmethod_rqtl'] + else: + self.method = "em" + self.model = start_vars['mapmodel_rqtl'] + self.pair_scan = False + if start_vars['pair_scan'] == "true": + self.pair_scan = True + if self.permCheck and self.num_perm > 0: + self.perm_output, self.suggestive, self.significant, results = rqtl_mapping.run_rqtl( + self.this_trait.name, self.vals, self.samples, self.dataset, self.pair_scan, self.mapping_scale, self.model, self.method, self.num_perm, self.perm_strata, self.do_control, self.control_marker, self.manhattan_plot, self.covariates) + else: + results = rqtl_mapping.run_rqtl(self.this_trait.name, self.vals, self.samples, self.dataset, self.pair_scan, self.mapping_scale, self.model, self.method, + self.num_perm, self.perm_strata, self.do_control, self.control_marker, self.manhattan_plot, self.covariates) + elif self.mapping_method == "reaper": + if "startMb" in start_vars: # ZS: Check if first time page loaded, so it can default to ON + if "additiveCheck" in start_vars: + self.additiveCheck = start_vars['additiveCheck'] + else: + self.additiveCheck = False + + if "bootCheck" in start_vars: + self.bootCheck = "ON" + else: + self.bootCheck = False + self.num_bootstrap = int(start_vars['num_bootstrap']) + else: + self.additiveCheck = "ON" + try: + if int(start_vars['num_bootstrap']) > 0: + self.bootCheck = "ON" + self.num_bootstrap = int(start_vars['num_bootstrap']) + else: + self.bootCheck = False + self.num_bootstrap = 0 + except: + self.bootCheck = False + self.num_bootstrap = 0 + + self.control_marker = start_vars['control_marker'] + self.do_control = start_vars['do_control'] + + self.first_run = True + self.output_files = None + # ZS: check if first run so existing result files can be used if it isn't (for example zooming on a chromosome, etc) + if 'first_run' in start_vars: + self.first_run = False + if 'output_files' in start_vars: + self.output_files = start_vars['output_files'].split( + ",") + + results, self.perm_output, self.suggestive, self.significant, self.bootstrap_results, self.output_files = qtlreaper_mapping.run_reaper(self.this_trait, + self.dataset, + self.samples, + self.vals, + self.json_data, + self.num_perm, + self.bootCheck, + self.num_bootstrap, + self.do_control, + self.control_marker, + self.manhattan_plot, + self.first_run, + self.output_files) + elif self.mapping_method == "plink": + self.score_type = "-logP" + self.manhattan_plot = True + results = plink_mapping.run_plink( + self.this_trait, self.dataset, self.species, self.vals, self.maf) + #results = self.run_plink() + + self.no_results = False + if len(results) == 0: + self.no_results = True + else: + if self.pair_scan == True: + self.figure_data = results[0] + self.table_data = results[1] + else: + self.qtl_results = [] + self.results_for_browser = [] + self.annotations_for_browser = [] + highest_chr = 1 # This is needed in order to convert the highest chr to X/Y + for marker in results: + marker['hmac'] = hmac.data_hmac('{}:{}'.format(marker['name'], self.dataset.group.name + "Geno")) + if 'Mb' in marker: + this_ps = marker['Mb'] * 1000000 + else: + this_ps = marker['cM'] * 1000000 + + browser_marker = dict( + chr=str(marker['chr']), + rs=marker['name'], + ps=this_ps, + url="/show_trait?trait_id=" + \ + marker['name'] + "&dataset=" + \ + self.dataset.group.name + "Geno" + ) + + if self.geno_db_exists == "True": + annot_marker = dict( + name=str(marker['name']), + chr=str(marker['chr']), + rs=marker['name'], + pos=this_ps, + url="/show_trait?trait_id=" + \ + marker['name'] + "&dataset=" + \ + self.dataset.group.name + "Geno" + ) + else: + annot_marker = dict( + name=str(marker['name']), + chr=str(marker['chr']), + rs=marker['name'], + pos=this_ps + ) + + if 'lrs_value' in marker and marker['lrs_value'] > 0: + browser_marker['p_wald'] = 10**- \ + (marker['lrs_value'] / 4.61) + elif 'lod_score' in marker and marker['lod_score'] > 0: + browser_marker['p_wald'] = 10**-(marker['lod_score']) + else: + browser_marker['p_wald'] = 0 + + self.results_for_browser.append(browser_marker) + self.annotations_for_browser.append(annot_marker) + if str(marker['chr']) > '0' or str(marker['chr']) == "X" or str(marker['chr']) == "X/Y": + if str(marker['chr']) > str(highest_chr) or str(marker['chr']) == "X" or str(marker['chr']) == "X/Y": + highest_chr = marker['chr'] + if ('lod_score' in marker.keys()) or ('lrs_value' in marker.keys()): + if 'Mb' in marker.keys(): + marker['display_pos'] = "Chr" + \ + str(marker['chr']) + ": " + \ + "{:.6f}".format(marker['Mb']) + elif 'cM' in marker.keys(): + marker['display_pos'] = "Chr" + \ + str(marker['chr']) + ": " + \ + "{:.3f}".format(marker['cM']) + else: + marker['display_pos'] = "N/A" + self.qtl_results.append(marker) + + total_markers = len(self.qtl_results) + export_mapping_results(self.dataset, self.this_trait, self.qtl_results, self.mapping_results_path, + self.mapping_method, self.mapping_scale, self.score_type, + self.transform, self.covariates, self.n_samples, self.vals_hash) + + if len(self.qtl_results) > 30000: + self.qtl_results = trim_markers_for_figure( + self.qtl_results) + self.results_for_browser = trim_markers_for_figure( + self.results_for_browser) + filtered_annotations = [] + for marker in self.results_for_browser: + for annot_marker in self.annotations_for_browser: + if annot_marker['rs'] == marker['rs']: + filtered_annotations.append(annot_marker) + break + self.annotations_for_browser = filtered_annotations + browser_files = write_input_for_browser( + self.dataset, self.results_for_browser, self.annotations_for_browser) + else: + browser_files = write_input_for_browser( + self.dataset, self.results_for_browser, self.annotations_for_browser) + + self.trimmed_markers = trim_markers_for_table(results) + + chr_lengths = get_chr_lengths( + self.mapping_scale, self.mapping_method, self.dataset, self.qtl_results) + + # ZS: For zooming into genome browser, need to pass chromosome name instead of number + if self.dataset.group.species == "mouse": + if self.selected_chr == 20: + this_chr = "X" + else: + this_chr = str(self.selected_chr) + elif self.dataset.group.species == "rat": + if self.selected_chr == 21: + this_chr = "X" + else: + this_chr = str(self.selected_chr) + else: + if self.selected_chr == 22: + this_chr = "X" + elif self.selected_chr == 23: + this_chr = "Y" + else: + this_chr = str(self.selected_chr) + + if self.mapping_method != "gemma": + if self.score_type == "LRS": + significant_for_browser = self.significant / 4.61 + else: + significant_for_browser = self.significant + + self.js_data = dict( + #result_score_type = self.score_type, + #this_trait = self.this_trait.name, + #data_set = self.dataset.name, + #maf = self.maf, + #manhattan_plot = self.manhattan_plot, + #mapping_scale = self.mapping_scale, + #chromosomes = chromosome_mb_lengths, + #qtl_results = self.qtl_results, + categorical_vars=self.categorical_vars, + chr_lengths=chr_lengths, + num_perm=self.num_perm, + perm_results=self.perm_output, + significant=significant_for_browser, + browser_files=browser_files, + selected_chr=this_chr, + total_markers=total_markers + ) + else: + self.js_data = dict( + chr_lengths=chr_lengths, + browser_files=browser_files, + selected_chr=this_chr, + total_markers=total_markers + ) + + def run_rqtl_plink(self): + # os.chdir("") never do this inside a webserver!! + + output_filename = webqtlUtil.genRandStr("%s_%s_" % ( + self.dataset.group.name, self.this_trait.name)) + + plink_mapping.gen_pheno_txt_file_plink( + self.this_trait, self.dataset, self.vals, pheno_filename=output_filename) + + rqtl_command = './plink --noweb --ped %s.ped --no-fid --no-parents --no-sex --no-pheno --map %s.map --pheno %s/%s.txt --pheno-name %s --maf %s --missing-phenotype -9999 --out %s%s --assoc ' % ( + self.dataset.group.name, self.dataset.group.name, TMPDIR, plink_output_filename, self.this_trait.name, self.maf, TMPDIR, plink_output_filename) + + os.system(rqtl_command) + + count, p_values = self.parse_rqtl_output(plink_output_filename) + + def identify_empty_samples(self): + no_val_samples = [] + for sample_count, val in enumerate(self.vals): + if val == "x": + no_val_samples.append(sample_count) + return no_val_samples + + def trim_genotypes(self, genotype_data, no_value_samples): + trimmed_genotype_data = [] + for marker in genotype_data: + new_genotypes = [] + for item_count, genotype in enumerate(marker): + if item_count in no_value_samples: + continue + try: + genotype = float(genotype) + except ValueError: + genotype = np.nan + pass + new_genotypes.append(genotype) + trimmed_genotype_data.append(new_genotypes) + return trimmed_genotype_data + + +def export_mapping_results(dataset, trait, markers, results_path, mapping_method, mapping_scale, score_type, transform, covariates, n_samples, vals_hash): + if mapping_scale == "physic": + scale_string = "Mb" + else: + scale_string = "cM" + with open(results_path, "w+") as output_file: + output_file.write( + "Time/Date: " + datetime.datetime.now().strftime("%x / %X") + "\n") + output_file.write( + "Population: " + dataset.group.species.title() + " " + dataset.group.name + "\n") + output_file.write("Data Set: " + dataset.fullname + "\n") + output_file.write("Trait: " + trait.display_name + "\n") + output_file.write("Trait Hash: " + vals_hash + "\n") + output_file.write("N Samples: " + str(n_samples) + "\n") + output_file.write("Mapping Tool: " + str(mapping_method) + "\n") + if len(transform) > 0: + transform_text = "Transform - " + if transform == "qnorm": + transform_text += "Quantile Normalized" + elif transform == "log2" or transform == "log10": + transform_text += transform.capitalize() + elif transform == "sqrt": + transform_text += "Square Root" + elif transform == "zscore": + transform_text += "Z-Score" + elif transform == "invert": + transform_text += "Invert +/-" + else: + transform_text = "" + output_file.write(transform_text + "\n") + if dataset.type == "ProbeSet": + if trait.symbol: + output_file.write("Gene Symbol: " + trait.symbol + "\n") + output_file.write("Location: " + str(trait.chr) + \ + " @ " + str(trait.mb) + " Mb\n") + if len(covariates) > 0: + output_file.write("Cofactors (dataset - trait):\n") + for covariate in covariates.split(","): + trait_name = covariate.split(":")[0] + dataset_name = covariate.split(":")[1] + output_file.write(dataset_name + " - " + trait_name + "\n") + output_file.write("\n") + output_file.write("Name,Chr,") + if score_type.lower() == "-logP": + score_type = "-logP" + output_file.write(scale_string + "," + score_type) + if "additive" in list(markers[0].keys()): + output_file.write(",Additive") + if "dominance" in list(markers[0].keys()): + output_file.write(",Dominance") + output_file.write("\n") + for i, marker in enumerate(markers): + output_file.write(marker['name'] + "," + str(marker['chr']) + ",") + output_file.write(str(marker[scale_string]) + ",") + if score_type == "-logP": + output_file.write(str(marker['lod_score'])) + else: + output_file.write(str(marker['lrs_value'])) + if "additive" in list(marker.keys()): + output_file.write("," + str(marker['additive'])) + if "dominance" in list(marker.keys()): + output_file.write("," + str(marker['dominance'])) + if i < (len(markers) - 1): + output_file.write("\n") + + +def trim_markers_for_figure(markers): + if 'p_wald' in list(markers[0].keys()): + score_type = 'p_wald' + elif 'lod_score' in list(markers[0].keys()): + score_type = 'lod_score' + else: + score_type = 'lrs_value' + + filtered_markers = [] + low_counter = 0 + med_counter = 0 + high_counter = 0 + for marker in markers: + if score_type == 'p_wald': + if marker[score_type] > 0.1: + if low_counter % 20 == 0: + filtered_markers.append(marker) + low_counter += 1 + elif 0.1 >= marker[score_type] > 0.01: + if med_counter % 10 == 0: + filtered_markers.append(marker) + med_counter += 1 + elif 0.01 >= marker[score_type] > 0.001: + if high_counter % 2 == 0: + filtered_markers.append(marker) + high_counter += 1 + else: + filtered_markers.append(marker) + elif score_type == 'lod_score': + if marker[score_type] < 1: + if low_counter % 20 == 0: + filtered_markers.append(marker) + low_counter += 1 + elif 1 <= marker[score_type] < 2: + if med_counter % 10 == 0: + filtered_markers.append(marker) + med_counter += 1 + elif 2 <= marker[score_type] <= 3: + if high_counter % 2 == 0: + filtered_markers.append(marker) + high_counter += 1 + else: + filtered_markers.append(marker) + else: + if marker[score_type] < 4.61: + if low_counter % 20 == 0: + filtered_markers.append(marker) + low_counter += 1 + elif 4.61 <= marker[score_type] < (2 * 4.61): + if med_counter % 10 == 0: + filtered_markers.append(marker) + med_counter += 1 + elif (2 * 4.61) <= marker[score_type] <= (3 * 4.61): + if high_counter % 2 == 0: + filtered_markers.append(marker) + high_counter += 1 + else: + filtered_markers.append(marker) + return filtered_markers + + +def trim_markers_for_table(markers): + if 'lod_score' in list(markers[0].keys()): + sorted_markers = sorted( + markers, key=lambda k: k['lod_score'], reverse=True) + else: + sorted_markers = sorted( + markers, key=lambda k: k['lrs_value'], reverse=True) + + #ZS: So we end up with a list of just 2000 markers + if len(sorted_markers) >= 25000: + trimmed_sorted_markers = sorted_markers[:25000] + return trimmed_sorted_markers + else: + return sorted_markers + + +def write_input_for_browser(this_dataset, gwas_results, annotations): + file_base = this_dataset.group.name + "_" + \ + ''.join(random.choice(string.ascii_uppercase + string.digits) + for _ in range(6)) + gwas_filename = file_base + "_GWAS" + annot_filename = file_base + "_ANNOT" + gwas_path = "{}/gn2/".format(TEMPDIR) + gwas_filename + annot_path = "{}/gn2/".format(TEMPDIR) + annot_filename + + with open(gwas_path + ".json", "w") as gwas_file, open(annot_path + ".json", "w") as annot_file: + gwas_file.write(json.dumps(gwas_results)) + annot_file.write(json.dumps(annotations)) + + return [gwas_filename, annot_filename] + + +def geno_db_exists(this_dataset): + geno_db_name = this_dataset.group.name + "Geno" + try: + geno_db = data_set.create_dataset( + dataset_name=geno_db_name, get_samplelist=False) + return "True" + except: + return "False" + + +def get_chr_lengths(mapping_scale, mapping_method, dataset, qtl_results): + chr_lengths = [] + if mapping_scale == "physic": + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as db_cursor: + for i, the_chr in enumerate(dataset.species.chromosomes.chromosomes(db_cursor)): + this_chr = { + "chr": dataset.species.chromosomes.chromosomes(db_cursor)[the_chr].name, + "size": str(dataset.species.chromosomes.chromosomes(db_cursor)[the_chr].length) + } + chr_lengths.append(this_chr) + else: + this_chr = 1 + highest_pos = 0 + for i, result in enumerate(qtl_results): + chr_as_num = 0 + try: + chr_as_num = int(result['chr']) + except: + chr_as_num = 20 + if chr_as_num > this_chr or i == (len(qtl_results) - 1): + if i == (len(qtl_results) - 1): + if mapping_method == "reaper": + highest_pos = float(result['cM']) * 1000000 + else: + highest_pos = float(result['Mb']) * 1000000 + chr_lengths.append( + {"chr": str(this_chr), "size": str(highest_pos)}) + else: + chr_lengths.append( + {"chr": str(this_chr), "size": str(highest_pos)}) + this_chr = chr_as_num + else: + if mapping_method == "reaper": + if float(result['cM']) > highest_pos: + highest_pos = float(result['cM']) * 1000000 + else: + if float(result['Mb']) > highest_pos: + highest_pos = float(result['Mb']) * 1000000 + + return chr_lengths + + +def get_genofile_samplelist(dataset): + genofile_samplelist = [] + + genofile_json = dataset.group.get_genofiles() + for genofile in genofile_json: + if genofile['location'] == dataset.group.genofile and 'sample_list' in genofile: + genofile_samplelist = genofile['sample_list'] + + return genofile_samplelist + + +def get_perm_strata(this_trait, sample_list, categorical_vars, used_samples): + perm_strata_strings = [] + for sample in used_samples: + if sample in list(sample_list.sample_attribute_values.keys()): + combined_string = "" + for var in categorical_vars: + if var in sample_list.sample_attribute_values[sample]: + combined_string += str( + sample_list.sample_attribute_values[sample][var]) + else: + combined_string += "NA" + else: + combined_string = "NA" + + perm_strata_strings.append(combined_string) + + d = dict([(y, x + 1) + for x, y in enumerate(sorted(set(perm_strata_strings)))]) + list_to_numbers = [d[x] for x in perm_strata_strings] + perm_strata = list_to_numbers + + return perm_strata diff --git a/gn2/wqflask/metadata_edits.py b/gn2/wqflask/metadata_edits.py new file mode 100644 index 00000000..b9514b35 --- /dev/null +++ b/gn2/wqflask/metadata_edits.py @@ -0,0 +1,973 @@ +import re +import datetime +import json +import os +from pathlib import Path +from functools import reduce + +from collections import namedtuple +from itertools import groupby +from typing import Dict, Optional + +import difflib +import redis + +from flask import Blueprint +from flask import Response +from flask import current_app +from flask import flash +from flask import g +from flask import redirect +from flask import render_template +from flask import request +from flask import url_for + +from gn2.utility.json import CustomJSONEncoder + +from gn2.wqflask.database import database_connection +from gn2.wqflask.decorators import login_required +from gn2.wqflask.decorators import required_access +from gn2.wqflask.decorators import edit_admins_access_required + +from gn2.wqflask.oauth2 import client +from gn2.wqflask.oauth2 import session +from gn2.wqflask.oauth2.request_utils import flash_error, process_error + +from gn3.authentication import AdminRole +from gn3.authentication import get_highest_user_access_role +from gn3.csvcmp import create_dirs_if_not_exists +from gn3.csvcmp import csv_diff +from gn3.csvcmp import extract_invalid_csv_headers +from gn3.csvcmp import remove_insignificant_edits +from gn3.db import diff_from_dict +from gn3.db.datasets import ( + retrieve_sample_list, + retrieve_mrna_group_name, + retrieve_phenotype_group_name) +from gn3.db.metadata_audit import ( + create_metadata_audit, + fetch_probeset_metadata_audit_by_trait_name, + fetch_phenotype_metadata_audit_by_dataset_id) +from gn3.db.probesets import ( + update_probeset as _update_probeset, + fetch_probeset_metadata_by_name) +from gn3.db.phenotypes import ( + fetch_trait, + fetch_metadata, + update_publication, + update_cross_reference, + fetch_publication_by_id, + fetch_publication_by_pubmed_id, + update_phenotype as _update_phenotype) +from gn3.db.sample_data import ( + delete_sample_data, + insert_sample_data, + update_sample_data, + get_pheno_sample_data, + get_pheno_csv_sample_data, + get_mrna_sample_data, + get_mrna_csv_sample_data) + + +metadata_edit = Blueprint("metadata_edit", __name__) + +def _get_diffs(diff_dir: str, redis_conn: redis.Redis): + """Get all the diff details.""" + def __get_file_metadata(file_name: str) -> Dict: + author, resource_id, time_stamp, *_ = file_name.split(".") + try: + author = json.loads(redis_conn.hget("users", author)).get( + "full_name" + ) + except (AttributeError, TypeError): + author = author + return { + "resource_id": resource_id, + "file_name": file_name, + "author": author, + "time_stamp": time_stamp + } + + def __get_diff__(diff_dir: str, diff_file_name: str) -> dict: + """Get the contents of the diff at `filepath`.""" + with open(Path(diff_dir, diff_file_name), encoding="utf8") as dfile: + return json.loads(dfile.read().strip()) + + return tuple({ + "filepath": Path(diff_dir, dname).absolute(), + "meta": __get_file_metadata(file_name=dname), + "diff": __get_diff__(diff_dir, dname) + } for dname in os.listdir(diff_dir)) + + +def edit_phenotype(conn, name, dataset_id): + publish_xref = fetch_trait(conn, dataset_id=dataset_id, trait_name=name) + return { + "publish_xref": publish_xref, + "phenotype": fetch_metadata(conn, publish_xref["phenotype_id"]), + "publication": fetch_publication_by_id(conn, publish_xref["publication_id"]) + } + + +@metadata_edit.route("/<dataset_id>/traits/<name>") +@login_required(pagename="phenotype edit") +@required_access( + ("group:resource:view-resource", "group:resource:edit-resource")) +def display_phenotype_metadata(dataset_id: str, name: str): + from gn2.utility.tools import get_setting + with database_connection(get_setting("SQL_URI")) as conn: + _d = edit_phenotype(conn=conn, name=name, dataset_id=dataset_id) + + group_name = retrieve_phenotype_group_name(conn, dataset_id) + sample_list = retrieve_sample_list(group_name) + sample_data = [] + if len(sample_list) < 2000: + sample_data = get_pheno_sample_data(conn, name, _d["publish_xref"]["phenotype_id"]) + + return render_template( + "edit_phenotype.html", + sample_list = sample_list, + sample_data = sample_data, + publish_xref=_d.get("publish_xref"), + phenotype=_d.get("phenotype"), + publication=_d.get("publication"), + dataset_id=dataset_id, + name=name, + resource_id=request.args.get("resource-id"), + version=get_setting("GN_VERSION"), + dataset_name=request.args["dataset_name"]) + + +@metadata_edit.route("/traits/<name>") +@required_access( + ("group:resource:view-resource", "group:resource:edit-resource")) +def display_probeset_metadata(name: str): + from gn2.utility.tools import get_setting + with database_connection(get_setting("SQL_URI")) as conn: + _d = {"probeset": fetch_probeset_metadata_by_name(conn, name)} + + dataset_name=request.args["dataset_name"] + group_name = retrieve_mrna_group_name(conn, _d["probeset"]["id_"], dataset_name) + sample_list = retrieve_sample_list(group_name) + sample_data = get_mrna_sample_data(conn, _d["probeset"]["id_"], dataset_name) + + return render_template( + "edit_probeset.html", + diff=_d.get("diff"), + probeset=_d.get("probeset"), + probeset_id=_d["probeset"]["id_"], + name=name, + resource_id=request.args.get("resource-id"), + version=get_setting("GN_VERSION"), + dataset_name=request.args["dataset_name"], + sample_list=sample_list, + sample_data=sample_data + ) + + +@metadata_edit.route("/<dataset_id>/traits/<name>", methods=("POST",)) +@login_required(pagename="phenotype update") +@required_access( + ("group:resource:view-resource", "group:resource:edit-resource")) +def update_phenotype(dataset_id: str, name: str): + from gn2.utility.tools import get_setting + data_ = request.form.to_dict() + TMPDIR = current_app.config.get("TMPDIR") + author = session.session_info()["user"]["user_id"] + phenotype_id = str(data_.get("phenotype-id")) + if not (file_ := request.files.get("file")) and data_.get('edited') == "false": + flash("No sample-data has been uploaded", "warning") + else: + create_dirs_if_not_exists( + [ + SAMPLE_DATADIR := os.path.join(TMPDIR, "sample-data"), + DIFF_DATADIR := os.path.join(SAMPLE_DATADIR, "diffs"), + UPLOAD_DATADIR := os.path.join(SAMPLE_DATADIR, "updated"), + ] + ) + + current_time = str(datetime.datetime.now().isoformat()) + _file_name = ( + f"{author}.{request.args.get('resource-id')}." f"{current_time}" + ) + diff_data = {} + with database_connection(get_setting("SQL_URI")) as conn: + group_name = retrieve_phenotype_group_name(conn, dataset_id) + sample_list = retrieve_sample_list(group_name) + headers = ["Strain Name", "Value", "SE", "Count"] + base_csv = get_pheno_csv_sample_data( + conn=conn, + trait_name=name, + group_id=dataset_id, + sample_list=sample_list, + ) + if not (file_) and data_.get('edited') == "true": + delta_csv = create_delta_csv(base_csv, data_, sample_list) + diff_data = remove_insignificant_edits( + diff_data=csv_diff( + base_csv=base_csv, + delta_csv=delta_csv, + tmp_dir=TMPDIR, + ), + epsilon=0.001, + ) + else: + diff_data = remove_insignificant_edits( + diff_data=csv_diff( + base_csv=base_csv, + delta_csv=(delta_csv := file_.read().decode()), + tmp_dir=TMPDIR, + ), + epsilon=0.001, + ) + + invalid_headers = extract_invalid_csv_headers( + allowed_headers=headers, csv_text=delta_csv + ) + if invalid_headers: + flash( + "You have invalid headers: " + f"""{', '.join(invalid_headers)}. Valid headers """ + f"""are: {', '.join(headers)}""", + "warning", + ) + return redirect( + f"/datasets/{dataset_id}/traits/{name}" + f"?resource-id={request.args.get('resource-id')}" + f"&dataset_name={request.args['dataset_name']}" + ) + # Edge case where the csv file has not been edited! + if not any(diff_data.values()): + flash( + "You have not modified the csv file you downloaded!", "warning" + ) + return redirect( + f"/datasets/{dataset_id}/traits/{name}" + f"?resource-id={request.args.get('resource-id')}" + f"&dataset_name={request.args['dataset_name']}" + ) + + with open( + os.path.join(UPLOAD_DATADIR, f"{_file_name}.csv"), "w" + ) as f_: + f_.write(base_csv) + with open( + os.path.join(UPLOAD_DATADIR, f"{_file_name}.delta.csv"), "w" + ) as f_: + f_.write(delta_csv) + + with open(os.path.join(DIFF_DATADIR, f"{_file_name}.json"), "w") as f: + diff_data.update( + { + "trait_name": str(name), + "phenotype_id": str(phenotype_id), + "dataset_id": dataset_id, + "dataset_name": request.args["dataset_name"], + "resource_id": request.args.get("resource-id"), + "author": author, + "timestamp": ( + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ), + } + ) + f.write(json.dumps(diff_data, cls=CustomJSONEncoder)) + url = url_for("metadata_edit.list_diffs") + flash(f"Sample-data has been successfully uploaded. \ +View the diffs <a href='{url}' target='_blank'>here</a>", "success") + # Run updates: + phenotype_ = { + "pre_pub_description": data_.get("pre-pub-desc"), + "post_pub_description": data_.get("post-pub-desc"), + "original_description": data_.get("orig-desc"), + "units": data_.get("units"), + "pre_pub_abbreviation": data_.get("pre-pub-abbrev"), + "post_pub_abbreviation": data_.get("post-pub-abbrev"), + "lab_code": data_.get("labcode"), + "submitter": data_.get("submitter"), + "owner": data_.get("owner"), + "authorized_users": data_.get("authorized-users"), + } + updated_phenotypes = "" + with database_connection(get_setting("SQL_URI")) as conn: + updated_phenotypes = _update_phenotype( + conn, {"id_": data_["phenotype-id"], **{ + key: value for key,value in phenotype_.items() + if value is not None}}) + diff_data = {} + if updated_phenotypes: + diff_data.update( + { + "Phenotype": diff_from_dict( + old={ + k: data_.get(f"old_{k}") + for k, v in phenotype_.items() + if v is not None + }, + new=phenotype_, + ) + } + ) + def __parse_int__(val) -> Optional[int]: + """Safe parser for integers""" + try: + return int(val, base=10) + except ValueError as _verr: + return None + except TypeError as _terr: + # trying to convert None + return None + publication_ = { + key: val for key, val in { + "pubmed_id": __parse_int__(data_.get("pubmed-id")), + "abstract": data_.get("abstract"), + "authors": data_.get("authors"), + "title": data_.get("title"), + "journal": data_.get("journal"), + "volume": data_.get("volume"), + "pages": data_.get("pages"), + "month": data_.get("month"), + "year": data_.get("year"), + }.items() if val is not None + } + updated_publications = "" + with database_connection(get_setting("SQL_URI")) as conn: + existing_publication = (# fetch publication + data_.get("pubmed-id") and # only if `pubmed-id` exists + fetch_publication_by_pubmed_id(conn, data_["pubmed-id"])) + + if existing_publication: + update_cross_reference(conn, + dataset_id, + name, + {"publication_id": existing_publication['id_']}) + else: + updated_publications = update_publication( + conn, {"id_": data_["old_id_"], **publication_}) + conn.commit() + + if updated_publications: + diff_data.update( + { + "Publication": diff_from_dict( + old={ + k: data_.get(f"old_{k}") + for k, v in publication_.items() + if v is not None + }, + new=publication_, + ) + } + ) + if diff_data: + diff_data.update( + { + "phenotype_id": str(phenotype_id), + "dataset_id": dataset_id, + "trait_name": name, + "resource_id": request.args.get("resource-id"), + "author": author, + "timestamp": ( + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ), + } + ) + with database_connection(get_setting("SQL_URI")) as conn: + create_metadata_audit(conn, { + "dataset_id": dataset_id, + "editor": author, + "json_data": json.dumps(diff_data, cls=CustomJSONEncoder)}) + flash(f"Diff-data: \n{diff_data}\nhas been uploaded", "success") + return redirect( + f"/datasets/{dataset_id}/traits/{name}" + f"?resource-id={request.args.get('resource-id')}" + f"&dataset_name={request.args['dataset_name']}" + ) + + +@metadata_edit.route("/traits/<name>", methods=("POST",)) +@required_access( + ("group:resource:view-resource", "group:resource:edit-resource"), + dataset_key="dataset_id", trait_key="name") +def update_probeset(name: str): + from gn2.utility.tools import get_setting + data_ = request.form.to_dict() + TMPDIR = current_app.config.get("TMPDIR") + author = session.session_info()["user"]["user_id"] + probeset_id=str(data_.get("id")) + trait_name = str(data_.get("probeset_name")) + dataset_name = str(data_.get("dataset_name")) + + if not (file_ := request.files.get("file")) and data_.get('edited') == "false": + flash("No sample-data has been uploaded", "warning") + else: + create_dirs_if_not_exists( + [ + SAMPLE_DATADIR := os.path.join(TMPDIR, "sample-data"), + DIFF_DATADIR := os.path.join(SAMPLE_DATADIR, "diffs"), + UPLOAD_DATADIR := os.path.join(SAMPLE_DATADIR, "updated"), + ] + ) + + current_time = str(datetime.datetime.now().isoformat()) + _file_name = ( + f"{author}.{request.args.get('resource-id')}." f"{current_time}" + ) + diff_data = {} + with database_connection(get_setting("SQL_URI")) as conn: + group_name = retrieve_mrna_group_name(conn, probeset_id, dataset_name) + sample_list = retrieve_sample_list(group_name) + headers = ["Strain Name", "Value", "SE", "Count"] + + base_csv = get_mrna_csv_sample_data( + conn=conn, + probeset_id=probeset_id, + dataset_name=dataset_name, + sample_list=retrieve_sample_list(group_name) + ) + if not (file_) and data_.get('edited') == "true": + delta_csv = create_delta_csv(base_csv, data_, sample_list) + diff_data = remove_insignificant_edits( + diff_data=csv_diff( + base_csv=base_csv, + delta_csv=delta_csv, + tmp_dir=TMPDIR, + ), + epsilon=0.001, + ) + else: + diff_data = remove_insignificant_edits( + diff_data=csv_diff( + base_csv=base_csv, + delta_csv=(delta_csv := file_.read().decode()), + tmp_dir=TMPDIR, + ), + epsilon=0.001, + ) + + invalid_headers = extract_invalid_csv_headers( + allowed_headers=headers, csv_text=delta_csv + ) + if invalid_headers: + flash( + "You have invalid headers: " + f"""{', '.join(invalid_headers)}. Valid headers """ + f"""are: {', '.join(headers)}""", + "warning", + ) + return redirect( + f"/datasets/{dataset_id}/traits/{name}" + f"?resource-id={request.args.get('resource-id')}" + f"&dataset_name={request.args['dataset_name']}" + ) + # Edge case where the csv file has not been edited! + if not any(diff_data.values()): + flash( + "You have not modified the csv file you downloaded!", "warning" + ) + return redirect( + f"/datasets/{dataset_id}/traits/{name}" + f"?resource-id={request.args.get('resource-id')}" + f"&dataset_name={request.args['dataset_name']}" + ) + + with open( + os.path.join(UPLOAD_DATADIR, f"{_file_name}.csv"), "w" + ) as f_: + f_.write(base_csv) + with open( + os.path.join(UPLOAD_DATADIR, f"{_file_name}.delta.csv"), "w" + ) as f_: + f_.write(delta_csv) + + with open(os.path.join(DIFF_DATADIR, f"{_file_name}.json"), "w") as f: + diff_data.update( + { + "trait_name": str(trait_name), + "probeset_id": str(probeset_id), + "dataset_name": dataset_name, + "resource_id": request.args.get("resource-id"), + "author": author, + "timestamp": ( + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ), + } + ) + f.write(json.dumps(diff_data, cls=CustomJSONEncoder)) + url = url_for("metadata_edit.list_diffs") + flash(f"Sample-data has been successfully uploaded. \ +View the diffs <a href='{url}' target='_blank'>here</a>", "success") + with database_connection(get_setting("SQL_URI")) as conn: + data_ = request.form.to_dict() + probeset_ = { + "id_": data_.get("id"), + "symbol": data_.get("symbol"), + "description": data_.get("description"), + "probe_target_description": data_.get("probe_target_description"), + "chr_": data_.get("chr"), + "mb": data_.get("mb"), + "alias": data_.get("alias"), + "geneid": data_.get("geneid"), + "homologeneid": data_.get("homologeneid"), + "unigeneid": data_.get("unigeneid"), + "omim": data_.get("OMIM"), + "refseq_transcriptid": data_.get("refseq_transcriptid"), + "blatseq": data_.get("blatseq"), + "targetseq": data_.get("targetseq"), + "strand_probe": data_.get("Strand_Probe"), + "probe_set_target_region": data_.get("probe_set_target_region"), + "probe_set_specificity": data_.get("probe_set_specificity"), + "probe_set_blat_score": data_.get("probe_set_blat_score"), + "probe_set_blat_mb_start": data_.get("probe_set_blat_mb_start"), + "probe_set_blat_mb_end": data_.get("probe_set_blat_mb_end"), + "probe_set_strand": data_.get("probe_set_strand"), + "probe_set_note_by_rw": data_.get("probe_set_note_by_rw"), + "flag": data_.get("flag"), + } + diff_data = {} + author = ( + (g.user_session.record.get(b"user_id") or b"").decode("utf-8") + or g.user_session.record.get("user_id") + or "" + ) + + updated_probesets = "" + updated_probesets = _update_probeset( + conn, probeset_id, {"id_": data_["id"], **{ + key: value for key,value in probeset_.items() + if value is not None}}) + + if updated_probesets: + diff_data.update( + { + "Probeset": diff_from_dict( + old={ + k: data_.get(f"old_{k}") + for k, v in probeset_.items() + if v is not None + }, + new=probeset_, + ) + } + ) + if diff_data: + diff_data.update({"probeset_name": data_.get("probeset_name")}) + diff_data.update({"author": author}) + diff_data.update({"resource_id": request.args.get("resource-id")}) + diff_data.update( + { + "timestamp": datetime.datetime.now().strftime( + "%Y-%m-%d %H:%M:%S" + ) + } + ) + create_metadata_audit(conn, { + "dataset_id": data_["id"], + "editor": author, + "json_data": json.dumps(diff_data, cls=CustomJSONEncoder)}) + edited_values = {k: v for (k, v) in diff_data['Probeset'].items() + if k not in {"id_", "timestamp", "author"}} + changes = [] + for k in edited_values.keys(): + changes.append(f"<b><span data-message-id='{k}'></span></b>") + message = f"You successfully updated the following entries \ + at {diff_data['timestamp']}: {', '.join(changes)}" + flash(f"You successfully edited: {message}", "success") + else: + flash("No edits were made!", "warning") + return redirect( + f"/datasets/traits/{name}" + f"?resource-id={request.args.get('resource-id')}" + f"&dataset_name={request.args['dataset_id']}" + ) + + +@metadata_edit.route("/pheno/<name>/group/<group_id>/csv") +@login_required() +def get_pheno_sample_data_as_csv(name: int, group_id: int): + from gn2.utility.tools import get_setting + with database_connection(get_setting("SQL_URI")) as conn: + group_name = retrieve_phenotype_group_name(conn, group_id) + return Response( + get_pheno_csv_sample_data( + conn=conn, + trait_name=name, + group_id=group_id, + sample_list=retrieve_sample_list(group_name) + ), + mimetype="text/csv", + headers={ + "Content-disposition": f"attachment; \ +filename=sample-data-{group_name}-{name}.csv" + }, + ) + +@metadata_edit.route("/mrna/<probeset_id>/dataset/<dataset_name>/csv") +@login_required() +def get_mrna_sample_data_as_csv(probeset_id: int, dataset_name: str): + from gn2.utility.tools import get_setting + + with database_connection(get_setting("SQL_URI")) as conn: + csv_data = get_mrna_csv_sample_data( + conn=conn, + probeset_id=str(probeset_id), + dataset_name=str(dataset_name), + sample_list=retrieve_sample_list( + retrieve_mrna_group_name(conn, probeset_id, dataset_name)) + ) + return Response( + get_mrna_csv_sample_data( + conn=conn, + probeset_id=str(probeset_id), + dataset_name=str(dataset_name), + sample_list=retrieve_sample_list( + retrieve_mrna_group_name(conn, probeset_id, dataset_name)) + ), + mimetype="text/csv", + headers={ + "Content-disposition": f"attachment; \ +filename=sample-data-{probeset_id}.csv" + }, + ) + + +@metadata_edit.route("/diffs") +@login_required(pagename="Sample Data Diffs") +def list_diffs(): + files = _get_diffs( + diff_dir=f"{current_app.config.get('TMPDIR')}/sample-data/diffs", + redis_conn=redis.from_url(current_app.config["REDIS_URL"], + decode_responses=True)) + + def __filter_authorised__(diffs, auth_details): + """Retain only those diffs that the current user has edit access to.""" + return list({ + diff["filepath"]: diff for diff in diffs + for auth in auth_details + if (diff["diff"]["dataset_name"] == auth["dataset_name"] + and + diff["diff"]["trait_name"] == auth["trait_name"]) }.values()) + + def __organise_diffs__(acc, item): + if item["filepath"].name.endswith(".rejected"): + return {**acc, "rejected": acc["rejected"] + [item]} + if item["filepath"].name.endswith(".approved"): + return {**acc, "approved": acc["approved"] + [item]} + return {**acc, "waiting": acc["waiting"] + [item]} + + accessible_diffs = client.post( + "auth/data/authorisation", + json={ + "traits": [ + f"{meta['diff']['dataset_name']}::{meta['diff']['trait_name']}" + for meta in files + ] + } + ).map( + lambda lst: [ + auth_item for auth_item in lst + if (("group:resource:edit-resource" in auth_item["privileges"]) + or + ("system:resources:edit-all" in auth_item["privileges"]))] + ).map( + lambda alst: __filter_authorised__(files, alst) + ).map(lambda diffs: reduce(__organise_diffs__, + diffs, + {"approved": [], "rejected": [], "waiting": []})) + + def __handle_error__(error): + flash_error(process_error(error)) + return render_template( + "display_files.html", approved=[], rejected=[], waiting=[]) + + def __success__(org_diffs): + return render_template( + "display_files.html", + approved=sorted( + org_diffs["approved"], + reverse=True, + key=lambda d: d["meta"]["time_stamp"]), + rejected=sorted( + org_diffs["rejected"], + reverse=True, + key=lambda d: d["meta"]["time_stamp"]), + waiting=sorted( + org_diffs["waiting"], + reverse=True, + key=lambda d: d["meta"]["time_stamp"])) + + return accessible_diffs.either(__handle_error__, __success__) + + +@metadata_edit.route("/diffs/<name>") +@login_required(pagename="diff display") +def show_diff(name): + TMPDIR = current_app.config.get("TMPDIR") + with open( + os.path.join(f"{TMPDIR}/sample-data/diffs", name), "r" + ) as myfile: + content = myfile.read() + content = json.loads(content) + for data in content.get("Modifications"): + data["Diff"] = "\n".join( + difflib.ndiff([data.get("Original")], [data.get("Current")]) + ) + return render_template("display_diffs.html", diff=content) + +@metadata_edit.route("/<dataset_id>/traits/<name>/history") +@metadata_edit.route("/probeset/<name>") +def show_history(dataset_id: str = "", name: str = ""): + from gn2.utility.tools import get_setting + diff_data_ = None + with database_connection(get_setting("SQL_URI")) as conn: + json_data = None + if dataset_id: # This is a published phenotype + json_data = fetch_phenotype_metadata_audit_by_dataset_id( + conn, dataset_id) + else: # This is a probeset + json_data = fetch_probeset_metadata_audit_by_trait_name( + conn, name) + Edit = namedtuple("Edit", ["field", "old", "new", "diff"]) + Diff = namedtuple("Diff", ["author", "diff", "timestamp"]) + diff_data = [] + for data in json_data: + json_ = data["json_data"] + timestamp = json_.get("timestamp") + author = json_.get("author") + for key, value in json_.items(): + if isinstance(value, dict): + for field, data_ in value.items(): + diff_data.append( + Diff( + author=author, + diff=Edit( + field, + data_.get("old") or "", + data_.get("new") or "", + "\n".join(difflib.ndiff( + [str(data_.get("old")) or ""], + [str(data_.get("new")) or ""], + ))), + timestamp=timestamp)) + + if len(diff_data) > 0: + diff_data_ = groupby( + (diff for diff in diff_data if ( + diff.diff.diff.startswith("-") or + diff.diff.diff.startswith("+"))), + lambda x: x.timestamp) + return render_template( + "edit_history.html", + diff={key: set(val) for key,val in diff_data_}, + version=get_setting("GN_VERSION"), + ) + +def __authorised_p__(dataset_name, trait_name): + """Check whether the user is authorised to edit the trait.""" + def __error__(error): + flash_error(process_error(error)) + return False + + def __success__(auth_details): + key = f"{dataset_name}::{trait_name}" + dets = auth_details.get(key) + if not bool(dets): + return False + return (("group:resource:edit-resource" in dets["privileges"]) + or + ("system:resources:edit-all" in dets["privileges"])) + + return client.post( + "auth/data/authorisation", + json={"traits": [f"{dataset_name}::{trait_name}"]} + ).map( + lambda adets: { + f"{dets['dataset_name']}::{dets['trait_name']}": dets + for dets in adets + } + ).either(__error__, __success__) + +@metadata_edit.route("<resource_id>/diffs/<file_name>/reject") +@login_required(pagename="sample data rejection") +@required_access( + ("group:resource:view-resource", "group:resource:edit-resource"), + trait_key="trait_name") +def reject_data(resource_id: str, file_name: str): + diffs_page = redirect(url_for("metadata_edit.list_diffs")) + TMPDIR = current_app.config.get("TMPDIR") + sampledir = Path(TMPDIR, "sample-data/diffs") + samplefile = Path(sampledir, file_name) + + if not samplefile.exists(): + flash("No such diffs file!", "alert-danger") + return diffs_page + + with open(samplefile, "r") as sfile: + sample_data = json.loads(sfile.read()) + if not __authorised_p__(sample_data["dataset_name"], + sample_data["trait_name"]): + flash("You are not authorised to edit that trait." + "alert-danger") + return diffs_page + + samplefile.rename(Path(sampledir, f"{file_name}.rejected")) + flash(f"{file_name} has been rejected!", "alert-success") + return diffs_page + +@metadata_edit.route("<resource_id>/diffs/<file_name>/approve") +@login_required(pagename="Sample Data Approval") +@required_access( + ("group:resource:view-resource", "group:resource:edit-resource"), + trait_key="trait_name") +def approve_data(resource_id: str, file_name: str): + from gn2.utility.tools import get_setting + sample_data = {file_name: str} + TMPDIR = current_app.config.get("TMPDIR") + diffpath = Path(TMPDIR, "sample-data/diffs", file_name) + if not diffpath.exists(): + flash(f"Could not find diff with the name '{diffpath.name}'", + "alert-danger") + return redirect(url_for("metadata_edit.list_diffs")) + + n_deletions = 0 + n_insertions = 0 + with (open(diffpath, "r") as myfile, + database_connection(get_setting("SQL_URI")) as conn): + sample_data = json.load(myfile) + + if not __authorised_p__(sample_data["dataset_name"], + sample_data["trait_name"]): + flash("You are not authorised to edit that trait.", "alert-danger") + return redirect(url_for("metadata_edit.list_diffs")) + + # Define the trait_info that is passed into the update functions, by data type + if sample_data.get("probeset_id"): # if trait is ProbeSet + trait_info = { + 'probeset_id': int(sample_data.get("probeset_id")), + 'dataset_name': sample_data.get("dataset_name") + } + else: # if trait is Publish + trait_info = { + 'trait_name': sample_data.get("trait_name"), + 'phenotype_id': int(sample_data.get("phenotype_id")) + } + + for modification in ( + modifications := [d for d in sample_data.get("Modifications")]): + if modification.get("Current"): + update_sample_data( + conn=conn, + original_data=modification.get("Original"), + updated_data=modification.get("Current"), + csv_header=sample_data.get( + "Columns", "Strain Name,Value,SE,Count" + ), + trait_info=trait_info + ) + + # Deletions + for data in [d for d in sample_data.get("Deletions")]: + __deletions = delete_sample_data( + conn=conn, + data=data, + csv_header=sample_data.get( + "Columns", "Strain Name,Value,SE,Count" + ), + trait_info=trait_info + ) + if __deletions: + n_deletions += 1 + # Remove any data that already exists from sample_data deletes + else: + sample_data.get("Deletions").remove(data) + + ## Insertions + for data in [d for d in sample_data.get("Additions")]: + + __insertions = insert_sample_data( + conn=conn, + data=data, + csv_header=sample_data.get( + "Columns", "Strain Name,Value,SE,Count" + ), + trait_info=trait_info + ) + if __insertions: + n_insertions += 1 + else: + sample_data.get("Additions").remove(data) + if any( + [ + sample_data.get("Additions"), + sample_data.get("Modifications"), + sample_data.get("Deletions"), + ] + ): + with database_connection(get_setting("SQL_URI")) as conn: + if sample_data.get("dataset_id"): # if phenotype + create_metadata_audit(conn, { + "dataset_id": sample_data.get("dataset_id"), + "editor": sample_data.get("author"), + "json_data": json.dumps(sample_data, cls=CustomJSONEncoder) + }) + else: + create_metadata_audit(conn, { + "dataset_id": sample_data.get("probeset_id"), + "editor": sample_data.get("author"), + "json_data": json.dumps(sample_data, cls=CustomJSONEncoder) + }) + # Once data is approved, rename it! + os.rename( + os.path.join(f"{TMPDIR}/sample-data/diffs", file_name), + os.path.join( + f"{TMPDIR}/sample-data/diffs", f"{file_name}.approved" + ), + ) + if n_deletions: + flash(f"# Deletions: {n_deletions}", "success") + if n_insertions: + flash(f"# Additions: {len(n_insertions)}", "success") + if len(modifications): + flash(f"# Modifications: {len(modifications)}", "success") + else: # Edge case where you need to automatically reject the file + os.rename( + os.path.join(f"{TMPDIR}/sample-data/diffs", file_name), + os.path.join( + f"{TMPDIR}/sample-data/diffs", f"{file_name}.rejected" + ), + ) + flash( + ( + "Automatically rejecting this file since no " + "changes could be applied." + ), + "warning", + ) + return redirect(url_for("metadata_edit.list_diffs")) + +def is_a_number(value: str): + """Check whether the string is a number""" + return bool(re.search(r"^[0-9]+\.*[0-9]*$", value)) + +def create_delta_csv(base_csv, form_data, sample_list): + base_csv_lines = base_csv.split("\n") + delta_csv_lines = [base_csv_lines[0]] + + for line in base_csv_lines[1:]: + sample = {} + sample['name'], sample['value'], sample['error'], sample['n_cases'] = line.split(",") + for key in form_data: + if sample['name'] in key: + new_line_items = [sample['name']] + for field in ["value", "error", "n_cases"]: + the_value = form_data.get(f"{field}:{sample['name']}") + if the_value: + if is_a_number(the_value) or the_value.lower() == "x": + new_line_items.append(the_value) + continue + new_line_items.append(sample[field]) + delta_csv_lines.append(",".join(new_line_items)) + break + else: + delta_csv_lines.append(line) + + return "\n".join(delta_csv_lines) diff --git a/gn2/wqflask/network_graph/__init__.py b/gn2/wqflask/network_graph/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/network_graph/__init__.py diff --git a/gn2/wqflask/network_graph/network_graph.py b/gn2/wqflask/network_graph/network_graph.py new file mode 100644 index 00000000..d60d50ff --- /dev/null +++ b/gn2/wqflask/network_graph/network_graph.py @@ -0,0 +1,188 @@ +# Copyright (C) University of Tennessee Health Science Center, Memphis, TN. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# This program is available from Source Forge: at GeneNetwork Project +# (sourceforge.net/projects/genenetwork/). +# +# Contact Dr. Robert W. Williams at rwilliams@uthsc.edu +# +# +# This module is used by GeneNetwork project (www.genenetwork.org) + +import scipy +import simplejson as json + +from gn2.base.trait import create_trait +from gn2.base import data_set +from gn2.utility import helper_functions +from gn2.utility import corr_result_helpers +from gn2.utility.tools import GN2_BRANCH_URL + + +class NetworkGraph: + + def __init__(self, start_vars): + trait_db_list = [trait.strip() + for trait in start_vars['trait_list'].split(',')] + + helper_functions.get_trait_db_obs(self, trait_db_list) + + self.all_sample_list = [] + self.traits = [] + for trait_db in self.trait_list: + this_trait = trait_db[0] + self.traits.append(this_trait) + this_sample_data = this_trait.data + + for sample in this_sample_data: + if sample not in self.all_sample_list: + self.all_sample_list.append(sample) + + self.sample_data = [] + for trait_db in self.trait_list: + this_trait = trait_db[0] + this_sample_data = this_trait.data + + this_trait_vals = [] + for sample in self.all_sample_list: + if sample in this_sample_data: + this_trait_vals.append(this_sample_data[sample].value) + else: + this_trait_vals.append('') + self.sample_data.append(this_trait_vals) + + # ZS: Variable set to the lowest overlapping samples in order to notify user, or 8, whichever is lower (since 8 is when we want to display warning) + self.lowest_overlap = 8 + + self.nodes_list = [] + self.edges_list = [] + for trait_db in self.trait_list: + this_trait = trait_db[0] + this_db = trait_db[1] + + this_db_samples = this_db.group.all_samples_ordered() + this_sample_data = this_trait.data + + corr_result_row = [] + is_spearman = False # ZS: To determine if it's above or below the diagonal + + max_corr = 0 # ZS: Used to determine whether node should be hidden when correlation coefficient slider is used + + for target in self.trait_list: + target_trait = target[0] + target_db = target[1] + + if str(this_trait) == str(target_trait) and str(this_db) == str(target_db): + continue + + target_samples = target_db.group.all_samples_ordered() + + target_sample_data = target_trait.data + + this_trait_vals = [] + target_vals = [] + for index, sample in enumerate(target_samples): + + if (sample in this_sample_data) and (sample in target_sample_data): + sample_value = this_sample_data[sample].value + target_sample_value = target_sample_data[sample].value + this_trait_vals.append(sample_value) + target_vals.append(target_sample_value) + + this_trait_vals, target_vals, num_overlap = corr_result_helpers.normalize_values( + this_trait_vals, target_vals) + + if num_overlap < self.lowest_overlap: + self.lowest_overlap = num_overlap + if num_overlap < 2: + continue + else: + pearson_r, pearson_p = scipy.stats.pearsonr( + this_trait_vals, target_vals) + if is_spearman == False: + sample_r, sample_p = pearson_r, pearson_p + if sample_r == 1: + continue + else: + sample_r, sample_p = scipy.stats.spearmanr( + this_trait_vals, target_vals) + + if -1 <= sample_r < -0.7: + color = "#0000ff" + width = 3 + elif -0.7 <= sample_r < -0.5: + color = "#00ff00" + width = 2 + elif -0.5 <= sample_r < 0: + color = "#000000" + width = 0.5 + elif 0 <= sample_r < 0.5: + color = "#ffc0cb" + width = 0.5 + elif 0.5 <= sample_r < 0.7: + color = "#ffa500" + width = 2 + elif 0.7 <= sample_r <= 1: + color = "#ff0000" + width = 3 + else: + color = "#000000" + width = 0 + + if abs(sample_r) > max_corr: + max_corr = abs(sample_r) + + edge_data = {'id': f"{str(this_trait.name)}:{str(this_trait.dataset.name)}" + '_to_' + f"{str(target_trait.name)}:{str(target_trait.dataset.name)}", + 'source': str(this_trait.name) + ":" + str(this_trait.dataset.name), + 'target': str(target_trait.name) + ":" + str(target_trait.dataset.name), + 'correlation': round(sample_r, 3), + 'abs_corr': abs(round(sample_r, 3)), + 'p_value': round(sample_p, 3), + 'overlap': num_overlap, + 'color': color, + 'width': width} + + edge_dict = {'data': edge_data} + + self.edges_list.append(edge_dict) + + if trait_db[1].type == "ProbeSet": + node_dict = {'data': {'id': str(this_trait.name) + ":" + str(this_trait.dataset.name), + 'label': this_trait.symbol, + 'symbol': this_trait.symbol, + 'geneid': this_trait.geneid, + 'omim': this_trait.omim, + 'max_corr': max_corr}} + elif trait_db[1].type == "Publish": + node_dict = {'data': {'id': str(this_trait.name) + ":" + str(this_trait.dataset.name), + 'label': this_trait.name, + 'max_corr': max_corr}} + else: + node_dict = {'data': {'id': str(this_trait.name) + ":" + str(this_trait.dataset.name), + 'label': this_trait.name, + 'max_corr': max_corr}} + self.nodes_list.append(node_dict) + + self.elements = json.dumps(self.nodes_list + self.edges_list) + self.gn2_url = GN2_BRANCH_URL + + groups = [] + for sample in self.all_sample_list: + groups.append(1) + + self.js_data = dict(traits=[trait.name for trait in self.traits], + groups=groups, + cols=list(range(len(self.traits))), + rows=list(range(len(self.traits))), + samples=self.all_sample_list, + sample_data=self.sample_data, + elements=self.elements,) diff --git a/gn2/wqflask/oauth2/__init__.py b/gn2/wqflask/oauth2/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/oauth2/__init__.py diff --git a/gn2/wqflask/oauth2/checks.py b/gn2/wqflask/oauth2/checks.py new file mode 100644 index 00000000..5d90f986 --- /dev/null +++ b/gn2/wqflask/oauth2/checks.py @@ -0,0 +1,49 @@ +"""Various checkers for OAuth2""" +from functools import wraps +from urllib.parse import urljoin + +from authlib.integrations.requests_client import OAuth2Session +from flask import ( + flash, request, url_for, redirect, current_app, session as flask_session) + +from . import session + +def user_logged_in(): + """Check whether the user has logged in.""" + suser = session.session_info()["user"] + if suser["logged_in"]: + if session.expired(): + session.clear_session_info() + return False + return suser["token"].is_right() + return False + +def require_oauth2(func): + """Decorator for ensuring user is logged in.""" + @wraps(func) + def __token_valid__(*args, **kwargs): + """Check that the user is logged in and their token is valid.""" + config = current_app.config + def __clear_session__(_no_token): + session.clear_session_info() + flask_session.pop("oauth2_token", None) + flask_session.pop("user_details", None) + flash("You need to be logged in.", "alert-warning") + return redirect("/") + + def __with_token__(token): + from gn2.utility.tools import ( + AUTH_SERVER_URL, OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET) + client = OAuth2Session( + OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, token=token) + resp = client.get( + urljoin(AUTH_SERVER_URL, "auth/user/")) + user_details = resp.json() + if not user_details.get("error", False): + return func(*args, **kwargs) + + return clear_session_info(token) + + return session.user_token().either(__clear_session__, __with_token__) + + return __token_valid__ diff --git a/gn2/wqflask/oauth2/client.py b/gn2/wqflask/oauth2/client.py new file mode 100644 index 00000000..c6a3110b --- /dev/null +++ b/gn2/wqflask/oauth2/client.py @@ -0,0 +1,124 @@ +"""Common oauth2 client utilities.""" +import json +import requests +from typing import Any, Optional +from urllib.parse import urljoin + +from flask import jsonify, current_app as app +from pymonad.maybe import Just, Maybe, Nothing +from pymonad.either import Left, Right, Either +from authlib.integrations.requests_client import OAuth2Session + +from gn2.wqflask.oauth2 import session +from gn2.wqflask.oauth2.checks import user_logged_in + +SCOPE = ("profile group role resource register-client user masquerade " + "introspect migrate-data") + +def oauth2_client(): + def __client__(token) -> OAuth2Session: + from gn2.utility.tools import ( + AUTH_SERVER_URL, OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET) + return OAuth2Session( + OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, + scope=SCOPE, token_endpoint_auth_method="client_secret_post", + token=token) + return session.user_token().either( + lambda _notok: __client__(None), + lambda token: __client__(token)) + +def __no_token__(_err) -> Left: + """Handle situation where request is attempted with no token.""" + resp = requests.models.Response() + resp._content = json.dumps({ + "error": "AuthenticationError", + "error-description": ("You need to authenticate to access requested " + "information.")}).encode("utf-8") + resp.status_code = 400 + return Left(resp) + +def oauth2_get(uri_path: str, data: dict = {}, **kwargs) -> Either: + def __get__(token) -> Either: + from gn2.utility.tools import ( + AUTH_SERVER_URL, OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET) + client = OAuth2Session( + OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, + token=token, scope=SCOPE) + resp = client.get( + urljoin(AUTH_SERVER_URL, uri_path), + data=data, + **kwargs) + if resp.status_code == 200: + return Right(resp.json()) + + return Left(resp) + + return session.user_token().either(__no_token__, __get__) + +def oauth2_post( + uri_path: str, data: Optional[dict] = None, json: Optional[dict] = None, + **kwargs) -> Either: + def __post__(token) -> Either: + from gn2.utility.tools import ( + AUTH_SERVER_URL, OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET) + client = OAuth2Session( + OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, + token=token, scope=SCOPE) + resp = client.post( + urljoin(AUTH_SERVER_URL, uri_path), data=data, json=json, + **kwargs) + if resp.status_code == 200: + return Right(resp.json()) + + return Left(resp) + + return session.user_token().either(__no_token__, __post__) + +def no_token_get(uri_path: str, **kwargs) -> Either: + from gn2.utility.tools import AUTH_SERVER_URL + resp = requests.get(urljoin(AUTH_SERVER_URL, uri_path), **kwargs) + if resp.status_code == 200: + return Right(resp.json()) + return Left(resp) + +def no_token_post(uri_path: str, **kwargs) -> Either: + from gn2.utility.tools import ( + AUTH_SERVER_URL, OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET) + data = kwargs.get("data", {}) + the_json = kwargs.get("json", {}) + request_data = { + **data, + **the_json, + "client_id": OAUTH2_CLIENT_ID, + "client_secret": OAUTH2_CLIENT_SECRET + } + new_kwargs = { + **{ + key: value for key, value in kwargs.items() + if key not in ("data", "json") + }, + ("data" if bool(data) else "json"): request_data + } + resp = requests.post(urljoin(AUTH_SERVER_URL, uri_path), + **new_kwargs) + if resp.status_code == 200: + return Right(resp.json()) + return Left(resp) + +def post(uri_path: str, **kwargs) -> Either: + """ + Generic function to do POST requests, that checks whether or not the user is + logged in and selects the appropriate function/method to run. + """ + if user_logged_in(): + return oauth2_post(uri_path, **kwargs) + return no_token_post(uri_path, **kwargs) + +def get(uri_path: str, **kwargs) -> Either: + """ + Generic function to do GET requests, that checks whether or not the user is + logged in and selects the appropriate function/method to run. + """ + if user_logged_in(): + return oauth2_get(uri_path, **kwargs) + return no_token_get(uri_path, **kwargs) diff --git a/gn2/wqflask/oauth2/collections.py b/gn2/wqflask/oauth2/collections.py new file mode 100644 index 00000000..63bf206e --- /dev/null +++ b/gn2/wqflask/oauth2/collections.py @@ -0,0 +1,16 @@ +"""Functions for collections.""" +from .session import session_info +from .checks import user_logged_in +from .client import oauth2_get, no_token_get + +def num_collections() -> int: + """Compute the number of collections available for the current session.""" + anon_id = session_info()["anon_id"] + all_collections = no_token_get( + f"auth/user/collections/{anon_id}/list").either( + lambda _err: [], lambda colls: colls) + if user_logged_in(): + all_collections = all_collections + oauth2_get( + "auth/user/collections/list").either( + lambda _err: [], lambda colls: colls) + return len(all_collections) diff --git a/gn2/wqflask/oauth2/data.py b/gn2/wqflask/oauth2/data.py new file mode 100644 index 00000000..a1dfdf95 --- /dev/null +++ b/gn2/wqflask/oauth2/data.py @@ -0,0 +1,319 @@ +"""Handle linking data to groups.""" +import sys +import json +import uuid +from datetime import datetime +from urllib.parse import urljoin + +from redis import Redis +from flask import ( + flash, request, jsonify, url_for, redirect, Response, Blueprint, + current_app as app) + +from gn2.wqflask.oauth2.request_utils import with_flash_error + +from gn2.jobs import jobs +from .ui import render_ui +from .request_utils import process_error +from .client import oauth2_get, oauth2_post + +data = Blueprint("data", __name__) + +def __search_mrna__(query, template, **kwargs): + from gn2.utility.tools import AUTH_SERVER_URL + species_name = kwargs["species_name"] + search_uri = urljoin(AUTH_SERVER_URL, "auth/data/search") + datasets = oauth2_get( + "auth/data/search", + json = { + "query": query, + "dataset_type": "mrna", + "species_name": species_name, + "selected": __selected_datasets__() + }).either( + lambda err: {"datasets_error": process_error(err)}, + lambda datasets: {"datasets": datasets}) + return render_ui(template, search_uri=search_uri, **datasets, **kwargs) + +def __selected_datasets__(): + if bool(request.json): + return request.json.get( + "selected", + request.args.get("selected", + request.form.get("selected", []))) + return request.args.get("selected", + request.form.get("selected", [])) + +def __search_genotypes__(query, template, **kwargs): + from gn2.utility.tools import AUTH_SERVER_URL + species_name = kwargs["species_name"] + search_uri = urljoin(AUTH_SERVER_URL, "auth/data/search") + datasets = oauth2_get( + "auth/data/search", + json = { + "query": query, + "dataset_type": "genotype", + "species_name": species_name, + "selected": __selected_datasets__() + }).either( + lambda err: {"datasets_error": process_error(err)}, + lambda datasets: {"datasets": datasets}) + return render_ui(template, search_uri=search_uri, **datasets, **kwargs) + +def __search_phenotypes__(query, template, **kwargs): + from gn2.utility.tools import GN_SERVER_URL, AUTH_SERVER_URL + page = int(request.args.get("page", 1)) + per_page = int(request.args.get("per_page", 50)) + selected_traits = request.form.getlist("selected_traits") + def __search_success__(search_results): + job_id = uuid.UUID(search_results["job_id"]) + return render_ui( + template, traits=[], per_page=per_page, query=query, + selected_traits=selected_traits, search_results=search_results, + search_endpoint=urljoin( + AUTH_SERVER_URL, "auth/data/search"), + gn_server_url = AUTH_SERVER_URL, + results_endpoint=urljoin( + AUTH_SERVER_URL, + f"auth/data/search/phenotype/{job_id}"), + **kwargs) + return oauth2_get("auth/data/search", json={ + "dataset_type": "phenotype", + "species_name": kwargs["species_name"], + "per_page": per_page, + "page": page, + "gn3_server_uri": GN_SERVER_URL + }).either( + with_flash_error(redirect(url_for('oauth2.data.list_data'))), + __search_success__) + +@data.route("/genotype/search", methods=["POST"]) +def json_search_genotypes() -> Response: + def __handle_error__(err): + error = process_error(err) + return jsonify(error), error["status_code"] + + return oauth2_get( + "auth/data/search", + json = { + "query": request.json["query"], + "dataset_type": "genotype", + "species_name": request.json["species_name"], + "selected": __selected_datasets__() + }).either( + __handle_error__, + lambda datasets: jsonify(datasets)) + +@data.route("/mrna/search", methods=["POST"]) +def json_search_mrna() -> Response: + def __handle_error__(err): + error = process_error(err) + return jsonify(error), error["status_code"] + + return oauth2_get( + "auth/data/search", + json = { + "query": request.json["query"], + "dataset_type": "mrna", + "species_name": request.json["species_name"], + "selected": __selected_datasets__() + }).either( + __handle_error__, + lambda datasets: jsonify(datasets)) + +@data.route("/phenotype/search", methods=["POST"]) +def json_search_phenotypes() -> Response: + """Search for phenotypes.""" + from gn2.utility.tools import AUTH_SERVER_URL + form = request.json + def __handle_error__(err): + error = process_error(err) + return jsonify(error), error["status_code"] + + return oauth2_get( + "auth/data/search", + json={ + "dataset_type": "phenotype", + "species_name": form["species_name"], + "query": form.get("query", ""), + "per_page": int(form.get("per_page", 50)), + "page": int(form.get("page", 1)), + "auth_server_uri": AUTH_SERVER_URL, + "selected_traits": form.get("selected_traits", []) + }).either(__handle_error__, jsonify) + +@data.route("/<string:species_name>/<string:dataset_type>/list", + methods=["GET", "POST"]) +def list_data_by_species_and_dataset( + species_name: str, dataset_type: str) -> Response: + templates = { + "mrna": "oauth2/data-list-mrna.html", + "genotype": "oauth2/data-list-genotype.html", + "phenotype": "oauth2/data-list-phenotype.html" + } + search_fns = { + "mrna": __search_mrna__, + "genotype": __search_genotypes__, + "phenotype": __search_phenotypes__ + } + groups = oauth2_get("auth/group/list").either( + lambda err: {"groups_error": process_error(err)}, + lambda grps: {"groups": grps}) + query = request.args.get("query", "") + return search_fns[dataset_type]( + query, templates[dataset_type], **groups, species_name=species_name, + dataset_type=dataset_type) + +@data.route("/list", methods=["GET", "POST"]) +def list_data(): + """List ungrouped data.""" + def __render__(**kwargs): + roles = kwargs.get("roles", []) + user_privileges = tuple( + privilege["privilege_id"] for role in roles + for privilege in role["privileges"]) + return render_ui( + "oauth2/data-list.html", + groups=kwargs.get("groups", []), + data_items=kwargs.get("data_items", []), + user_privileges=user_privileges, + **{key:val for key,val in kwargs.items() + if key not in ("groups", "data_items", "user_privileges")}) + + groups = oauth2_get("auth/group/list").either( + lambda err: {"groups_error": process_error(err)}, + lambda grp: {"groups": grp}) + roles = oauth2_get("auth/system/roles").either( + lambda err: {"roles_error": process_error(err)}, + lambda roles: {"roles": roles}) + species = oauth2_get("auth/data/species").either( + lambda err: {"species_error": process_error(err)}, + lambda species: {"species": species}) + + if request.method == "GET": + return __render__(**{**groups, **roles, **species}) + + species_name = request.form["species_name"] + dataset_type = request.form["dataset_type"] + if dataset_type not in ("mrna", "genotype", "phenotype"): + flash("InvalidDatasetType: An invalid dataset type was provided", + "alert-danger") + return __render__(**{**groups, **roles, **species}) + + return redirect(url_for( + "oauth2.data.list_data_by_species_and_dataset", + species_name=species_name, dataset_type=dataset_type)) + +@data.route("/link", methods=["POST"]) +def link_data(): + """Link the selected data to a specific group.""" + def __error__(err, form_data): + error = process_error(err) + flash(f"{error['error']}: {error['error_description']}", "alert-danger") + return redirect(url_for("oauth2.data.list_data", **form_data), code=307) + def __success__(success, form_data): + flash("Data successfully linked!", "alert-success") + return redirect(url_for("oauth2.data.list_data", **form_data), code=307) + + form = request.form + try: + keys = ("dataset_type", "group_id") + assert all(item in form for item in keys) + assert all(bool(form[item]) for item in keys) + state_data = { + "dataset_type": form["dataset_type"], + "offset": form.get("offset", 0)} + dataset_ids = form.getlist("dataset_ids") + if len(dataset_ids) == 0: + flash("You must select at least one item to link", "alert-danger") + return redirect(url_for( + "oauth2.data.list_data", **state_data)) + return oauth2_post( + "auth/group/data/link", + data={ + "dataset_type": form["dataset_type"], + "dataset_ids": dataset_ids, + "group_id": form["group_id"] + }).either(lambda err: __error__(err, state_data), + lambda success: __success__(success, state_data)) + except AssertionError as aserr: + flash("You must provide all the expected data.", "alert-danger") + return redirect(url_for("oauth2.data.list_data")) + +@data.route("/link/genotype", methods=["POST"]) +def link_genotype_data(): + """Link genotype data to a group.""" + form = request.form + link_source_url = redirect(url_for("oauth2.data.list_data")) + if bool(form.get("species_name")): + link_source_url = redirect(url_for( + "oauth2.data.list_data_by_species_and_dataset", + species_name=form["species_name"], dataset_type="genotype")) + + def __link_error__(err): + flash(f"{err['error']}: {err['error_description']}", "alert-danger") + return link_source_url + + def __link_success__(success): + flash(success["description"], "alert-success") + return link_source_url + + return oauth2_post("auth/data/link/genotype", json={ + "species_name": form.get("species_name"), + "group_id": form.get("group_id"), + "selected": tuple(json.loads(dataset) for dataset + in form.getlist("selected")) + }).either(lambda err: __link_error__(process_error(err)), __link_success__) + + +@data.route("/link/mrna", methods=["POST"]) +def link_mrna_data(): + """Link mrna data to a group.""" + form = request.form + link_source_url = redirect(url_for("oauth2.data.list_data")) + if bool(form.get("species_name")): + link_source_url = redirect(url_for( + "oauth2.data.list_data_by_species_and_dataset", + species_name=form["species_name"], dataset_type="mrna")) + + def __link_error__(err): + error = process_error(err) + flash(f"{err['error']}: {err['error_description']}", "alert-danger") + return link_source_url + + def __link_success__(success): + flash(success["description"], "alert-success") + return link_source_url + + return oauth2_post("auth/data/link/mrna", json={ + "species_name": form.get("species_name"), + "group_id": form.get("group_id"), + "selected": tuple(json.loads(dataset) for dataset + in form.getlist("selected")) + }).either(lambda err: __link_error__(process_error(err)), __link_success__) + +@data.route("/link/phenotype", methods=["POST"]) +def link_phenotype_data(): + """Link phenotype data to a group.""" + form = request.form + link_source_url = redirect(url_for("oauth2.data.list_data")) + if bool(form.get("species_name")): + link_source_url = redirect(url_for( + "oauth2.data.list_data_by_species_and_dataset", + species_name=form["species_name"], dataset_type="phenotype")) + + def __link_error__(err): + error = process_error(err) + flash(f"{error['error']}: {error['error_description']}", "alert-danger") + return link_source_url + + def __link_success__(success): + flash(success["description"], "alert-success") + return link_source_url + + return oauth2_post("auth/data/link/phenotype", json={ + "species_name": form.get("species_name"), + "group_id": form.get("group_id"), + "selected": tuple( + json.loads(trait) for trait in form.getlist("selected"))}).either( + __link_error__, __link_success__) diff --git a/gn2/wqflask/oauth2/groups.py b/gn2/wqflask/oauth2/groups.py new file mode 100644 index 00000000..fd5ab7eb --- /dev/null +++ b/gn2/wqflask/oauth2/groups.py @@ -0,0 +1,210 @@ +import uuid +import datetime +from functools import partial + +from flask import ( + flash, session, request, url_for, redirect, Response, Blueprint) + +from .ui import render_ui +from .checks import require_oauth2 +from .client import oauth2_get, oauth2_post +from .request_utils import ( + user_details, handle_error, process_error, handle_success, + raise_unimplemented) + +groups = Blueprint("group", __name__) + +@groups.route("/", methods=["GET"]) +def user_group(): + """Get the user's group.""" + def __get_join_requests__(group, users): + return oauth2_get("auth/group/requests/join/list").either( + lambda error: render_ui( + "oauth2/group.html", group=group, users=users, + group_join_requests_error=process_error(error)), + lambda gjr: render_ui( + "oauth2/group.html", group=group, users=users, + group_join_requests=gjr)) + def __success__(group): + return oauth2_get(f"auth/group/members/{group['group_id']}").either( + lambda error: render_ui( + "oauth2/group.html", group=group, + user_error=process_error(error)), + partial(__get_join_requests__, group)) + + def __group_error__(err): + return render_ui( + "oauth2/group.html", group_error=process_error(err)) + + return oauth2_get("auth/user/group").either( + __group_error__, __success__) + +@groups.route("/create", methods=["POST"]) +@require_oauth2 +def create_group(): + def __setup_group__(response): + session["user_details"]["group"] = response + + resp = oauth2_post("auth/group/create", data=dict(request.form)) + return resp.either( + handle_error("oauth2.group.join_or_create"), + handle_success( + "Created group", "oauth2.user.user_profile", + response_handlers=[__setup_group__])) + +@groups.route("/join-or-create", methods=["GET"]) +@require_oauth2 +def join_or_create(): + usr_dets = user_details() + if bool(usr_dets["group"]): + flash("You are already a member of a group.", "alert-info") + return redirect(url_for("oauth2.user.user_profile")) + def __group_error__(err): + return render_ui( + "oauth2/group_join_or_create.html", groups=[], + groups_error=process_error(err)) + def __group_success__(groups): + return oauth2_get("auth/user/group/join-request").either( + __gjr_error__, partial(__gjr_success__, groups=groups)) + def __gjr_error__(err): + return render_ui( + "oauth2/group_join_or_create.html", groups=[], + gjr_error=process_error(err)) + def __gjr_success__(gjr, groups): + return render_ui( + "oauth2/group_join_or_create.html", groups=groups, + group_join_request=gjr) + return oauth2_get("auth/group/list").either( + __group_error__, __group_success__) + +@groups.route("/delete/<uuid:group_id>", methods=["GET", "POST"]) +@require_oauth2 +def delete_group(group_id): + """Delete the user's group.""" + return "WOULD DELETE GROUP." + +@groups.route("/edit/<uuid:group_id>", methods=["GET", "POST"]) +@require_oauth2 +def edit_group(group_id): + """Edit the user's group.""" + return "WOULD EDIT GROUP." + +@groups.route("/list-join-requests", methods=["GET"]) +@require_oauth2 +def list_join_requests() -> Response: + def __ts_to_dt_str__(timestamp): + return datetime.datetime.fromtimestamp(timestamp).isoformat() + def __fail__(error): + return render_ui( + "oauth2/join-requests.html", error=process_error(error), + requests=[]) + def __success__(requests): + return render_ui( + "oauth2/join-requests.html", error=False, requests=requests, + datetime_string=__ts_to_dt_str__) + return oauth2_get("auth/group/requests/join/list").either( + __fail__, __success__) + +@groups.route("/accept-join-requests", methods=["POST"]) +@require_oauth2 +def accept_join_request(): + def __fail__(error): + err=process_error() + flash("{}", "alert-danger") + return redirect(url_for("oauth2.group.list_join_requests")) + def __success__(requests): + flash("Request was accepted successfully.", "alert-success") + return redirect(url_for("oauth2.group.list_join_requests")) + return oauth2_post( + "auth/group/requests/join/accept", + data=request.form).either( + handle_error("oauth2.group.list_join_requests"), + __success__) + +@groups.route("/reject-join-requests", methods=["POST"]) +@require_oauth2 +def reject_join_request(): + def __fail__(error): + err=process_error() + flash(f"{err['error']}: {err['error_description']}", "alert-danger") + return redirect(url_for("oauth2.group.list_join_requests")) + def __success__(requests): + flash("Request was rejected successfully.", "alert-success") + return redirect(url_for("oauth2.group.list_join_requests")) + return oauth2_post( + "auth/group/requests/join/reject", + data=request.form).either( + handle_error("oauth2.group.list_join_requests"), + __success__) + +@groups.route("/role/<uuid:group_role_id>", methods=["GET"]) +@require_oauth2 +def group_role(group_role_id: uuid.UUID): + """View the details of a particular role.""" + def __render_error(**kwargs): + return render_ui("oauth2/view-group-role.html", **kwargs) + + def __gprivs_success__(role, group_privileges): + return render_ui( + "oauth2/view-group-role.html", group_role=role, + group_privileges=tuple( + priv for priv in group_privileges + if priv not in role["role"]["privileges"])) + + def __role_success__(role): + return oauth2_get("auth/group/privileges").either( + lambda err: __render_error__( + group_role=group_role, + group_privileges_error=process_error(err)), + lambda privileges: __gprivs_success__(role, privileges)) + + return oauth2_get(f"auth/group/role/{group_role_id}").either( + lambda err: __render_error__(group_role_error=process_error(err)), + __role_success__) + +def add_delete_privilege_to_role( + group_role_id: uuid.UUID, direction: str) -> Response: + """Add/delete a privilege to/from a role depending on `direction`.""" + assert direction in ("ADD", "DELETE") + def __render__(): + return redirect(url_for( + "oauth2.group.group_role", group_role_id=group_role_id)) + + def __error__(error): + err = process_error(error) + flash(f"{err['error']}: {err['error_description']}", "alert-danger") + return __render__() + + def __success__(success): + flash(success["description"], "alert-success") + return __render__() + try: + form = request.form + privilege_id = form.get("privilege_id") + assert bool(privilege_id), "Privilege to add must be provided" + uris = { + "ADD": f"auth/group/role/{group_role_id}/privilege/add", + "DELETE": f"auth/group/role/{group_role_id}/privilege/delete" + } + return oauth2_post( + uris[direction], + data={ + "group_role_id": group_role_id, + "privilege_id": privilege_id + }).either(__error__, __success__) + except AssertionError as aerr: + flash(aerr.args[0], "alert-danger") + return redirect(url_for( + "oauth2.group.group_role", group_role_id=group_role_id)) + +@groups.route("/role/<uuid:group_role_id>/privilege/add", methods=["POST"]) +@require_oauth2 +def add_privilege_to_role(group_role_id: uuid.UUID): + """Add a privilege to a group role.""" + return add_delete_privilege_to_role(group_role_id, "ADD") + +@groups.route("/role/<uuid:group_role_id>/privilege/delete", methods=["POST"]) +@require_oauth2 +def delete_privilege_from_role(group_role_id: uuid.UUID): + """Delete a privilege from a group role.""" + return add_delete_privilege_to_role(group_role_id, "DELETE") diff --git a/gn2/wqflask/oauth2/request_utils.py b/gn2/wqflask/oauth2/request_utils.py new file mode 100644 index 00000000..ade0923b --- /dev/null +++ b/gn2/wqflask/oauth2/request_utils.py @@ -0,0 +1,99 @@ +"""General request utilities""" +from typing import Optional, Callable +from urllib.parse import urljoin, urlparse + +import simplejson +from flask import ( + flash, request, url_for, redirect, Response, render_template, + current_app as app) + +from .client import SCOPE, oauth2_get + +def authserver_authorise_uri(): + from gn2.utility.tools import AUTH_SERVER_URL, OAUTH2_CLIENT_ID + req_baseurl = urlparse(request.base_url, scheme=request.scheme) + host_uri = f"{req_baseurl.scheme}://{req_baseurl.netloc}/" + return urljoin( + AUTH_SERVER_URL, + "auth/authorise?response_type=code" + f"&client_id={OAUTH2_CLIENT_ID}" + f"&redirect_uri={urljoin(host_uri, 'oauth2/code')}") + +def raise_unimplemented(): + raise Exception("NOT IMPLEMENTED") + +def user_details(): + return oauth2_get("auth/user/").either( + lambda err: {}, + lambda usr_dets: usr_dets) + +def process_error(error: Response, + message: str=("Requested endpoint was not found on the API " + "server.") + ) -> dict: + if error.status_code in range(400, 500): + try: + err = error.json() + msg = err.get("error_description", f"{error.reason}") + except simplejson.errors.JSONDecodeError as _jde: + msg = message + return { + "error": error.reason, + "error_message": msg, + "error_description": msg, + "status_code": error.status_code + } + return {**error.json(), "status_code": error.status_code} + +def request_error(response): + app.logger.error(f"{response}: {response.url} [{response.status_code}]") + return render_template("oauth2/request_error.html", response=response) + +def handle_error(redirect_uri: Optional[str] = None, **kwargs): + def __handler__(error): + error_json = process_error(error)# error.json() + msg = error_json.get( + "error_message", error_json.get( + "error_description", "undefined error")) + flash(f"{error_json['error']}: {msg}.", + "alert-danger") + if "response_handlers" in kwargs: + for handler in kwargs["response_handlers"]: + handler(response) + if redirect: + return redirect(url_for(redirect_uri, **kwargs)) + + return __handler__ + +def handle_success( + success_msg: str, redirect_uri: Optional[str] = None, **kwargs): + def __handler__(response): + flash(f"Success: {success_msg}.", "alert-success") + if "response_handlers" in kwargs: + for handler in kwargs["response_handlers"]: + handler(response) + if redirect: + return redirect(url_for(redirect_uri, **kwargs)) + + return __handler__ + +def flash_error(error): + flash(f"{error['error']}: {error['error_description']}", "alert-danger") + +def flash_success(success): + flash(f"{success['description']}", "alert-success") + +def with_flash_error(response) -> Callable: + def __err__(err) -> Response: + error = process_error(err) + flash(f"{error['status_code']} {error['error']}: " + f"{error['error_description']}", + "alert-danger") + return response + return __err__ + +def with_flash_success(response) -> Callable: + def __succ__(msg) -> Response: + flash(f"Success: {msg['message']}", "alert-success") + return response + return __succ__ diff --git a/gn2/wqflask/oauth2/resources.py b/gn2/wqflask/oauth2/resources.py new file mode 100644 index 00000000..7d20b859 --- /dev/null +++ b/gn2/wqflask/oauth2/resources.py @@ -0,0 +1,294 @@ +import uuid + +from flask import ( + flash, request, jsonify, url_for, redirect, Response, Blueprint) + +from . import client +from .ui import render_ui +from .checks import require_oauth2 +from .client import oauth2_get, oauth2_post +from .request_utils import ( + flash_error, flash_success, request_error, process_error) + +resources = Blueprint("resource", __name__) + +@resources.route("/", methods=["GET"]) +@require_oauth2 +def user_resources(): + """List the resources the user has access to.""" + def __success__(resources): + return render_ui("oauth2/resources.html", resources=resources) + + return oauth2_get("auth/user/resources").either( + request_error, __success__) + +@resources.route("/create", methods=["GET", "POST"]) +@require_oauth2 +def create_resource(): + """Create a new resource.""" + def __render_template__(categories=[], error=None): + return render_ui( + "oauth2/create-resource.html", + resource_categories=categories, + resource_category_error=error, + resource_name=request.args.get("resource_name"), + resource_category=request.args.get("resource_category")) + + if request.method == "GET": + return oauth2_get("auth/resource/categories").either( + lambda error: __render_template__(error=process_error( + error, "Could not retrieve resource categories")), + lambda cats: __render_template__(categories=cats)) + + def __perr__(error): + err = process_error(error) + flash(f"{err['error']}: {err['error_description']}", "alert-danger") + return redirect(url_for( + "oauth2.resource.create_resource", + resource_name=request.form.get("resource_name"), + resource_category=request.form.get("resource_category"))) + def __psuc__(succ): + flash("Resource created successfully", "alert-success") + return redirect(url_for("oauth2.resource.user_resources")) + return oauth2_post( + "auth/resource/create", data=request.form).either( + __perr__, __psuc__) + +def __compute_page__(submit, current_page): + if submit == "next": + return current_page + 1 + return (current_page - 1) or 1 + +@resources.route("/view/<uuid:resource_id>", methods=["GET"]) +@require_oauth2 +def view_resource(resource_id: uuid.UUID): + """View the given resource.""" + page = __compute_page__(request.args.get("submit"), + int(request.args.get("page", "1"), base=10)) + count_per_page = int(request.args.get("count_per_page", "100"), base=10) + def __users_success__( + resource, unlinked_data, users_n_roles, this_user, group_roles, + users): + return render_ui( + "oauth2/view-resource.html", resource=resource, + unlinked_data=unlinked_data, users_n_roles=users_n_roles, + this_user=this_user, group_roles=group_roles, users=users, + page=page, count_per_page=count_per_page) + + def __group_roles_success__( + resource, unlinked_data, users_n_roles, this_user, group_roles): + return oauth2_get("auth/user/list").either( + lambda err: render_ui( + "oauth2/view-resource.html", resource=resource, + unlinked_data=unlinked_data, users_n_roles=users_n_roles, + this_user=this_user, group_roles=group_roles, + users_error=process_error(err)), + lambda users: __users_success__( + resource, unlinked_data, users_n_roles, this_user, group_roles, + users)) + + def __this_user_success__(resource, unlinked_data, users_n_roles, this_user): + return oauth2_get("auth/group/roles").either( + lambda err: render_ui( + "oauth2/view-resources.html", resource=resource, + unlinked_data=unlinked_data, users_n_roles=users_n_roles, + this_user=this_user, group_roles_error=process_error(err)), + lambda groles: __group_roles_success__( + resource, unlinked_data, users_n_roles, this_user, groles)) + + def __users_n_roles_success__(resource, unlinked_data, users_n_roles): + return oauth2_get("auth/user/").either( + lambda err: render_ui( + "oauth2/view-resources.html", + this_user_error=process_error(err)), + lambda usr_dets: __this_user_success__( + resource, unlinked_data, users_n_roles, usr_dets)) + + def __unlinked_success__(resource, unlinked_data): + return oauth2_get(f"auth/resource/{resource_id}/user/list").either( + lambda err: render_ui( + "oauth2/view-resource.html", + resource=resource, + unlinked_data=unlinked_data, + users_n_roles_error=process_error(err), + page=page, + count_per_page=count_per_page), + lambda users_n_roles: __users_n_roles_success__( + resource, unlinked_data, users_n_roles)) + + def __resource_success__(resource): + dataset_type = resource["resource_category"]["resource_category_key"] + return oauth2_get(f"auth/group/{dataset_type}/unlinked-data").either( + lambda err: render_ui( + "oauth2/view-resource.html", resource=resource, + unlinked_error=process_error(err)), + lambda unlinked: __unlinked_success__(resource, unlinked)) + + def __fetch_resource_data__(resource): + """Fetch the resource's data.""" + return client.get( + f"auth/resource/view/{resource['resource_id']}/data?page={page}" + f"&count_per_page={count_per_page}").either( + lambda err: { + **resource, "resource_data_error": process_error(err) + }, + lambda resdata: {**resource, "resource_data": resdata}) + + return oauth2_get(f"auth/resource/view/{resource_id}").map( + __fetch_resource_data__).either( + lambda err: render_ui( + "oauth2/view-resource.html", + resource=None, resource_error=process_error(err)), + __resource_success__) + +@resources.route("/data/link", methods=["POST"]) +@require_oauth2 +def link_data_to_resource(): + """Link group data to a resource""" + form = request.form + try: + assert "resource_id" in form, "Resource ID not provided." + assert "data_link_id" in form, "Data Link ID not provided." + assert "dataset_type" in form, "Dataset type not specified" + assert form["dataset_type"].lower() in ( + "mrna", "genotype", "phenotype"), "Invalid dataset type provided." + resource_id = form["resource_id"] + + def __error__(error): + err = process_error(error) + flash(f"{err['error']}: {err['error_description']}", "alert-danger") + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=resource_id)) + + def __success__(success): + flash(f"Data linked to resource successfully", "alert-success") + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=resource_id)) + return oauth2_post("auth/resource/data/link", data=dict(form)).either( + __error__, + __success__) + except AssertionError as aserr: + flash(aserr.args[0], "alert-danger") + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=form["resource_id"])) + +@resources.route("/data/unlink", methods=["POST"]) +@require_oauth2 +def unlink_data_from_resource(): + """Unlink group data from a resource""" + form = request.form + try: + assert "resource_id" in form, "Resource ID not provided." + assert "data_link_id" in form, "Data Link ID not provided." + resource_id = form["resource_id"] + + def __error__(error): + err = process_error(error) + flash(f"{err['error']}: {err['error_description']}", "alert-danger") + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=resource_id)) + + def __success__(success): + flash(f"Data unlinked from resource successfully", "alert-success") + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=resource_id)) + return oauth2_post( + "auth/resource/data/unlink", data=dict(form)).either( + __error__, __success__) + except AssertionError as aserr: + flash(aserr.args[0], "alert-danger") + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=form["resource_id"])) + +@resources.route("<uuid:resource_id>/user/assign", methods=["POST"]) +@require_oauth2 +def assign_role(resource_id: uuid.UUID) -> Response: + form = request.form + group_role_id = form.get("group_role_id", "") + user_email = form.get("user_email", "") + try: + assert bool(group_role_id), "The role must be provided." + assert bool(user_email), "The user email must be provided." + + def __assign_error__(error): + err = process_error(error) + flash(f"{err['error']}: {err['error_description']}", "alert-danger") + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=resource_id)) + + def __assign_success__(success): + flash(success["description"], "alert-success") + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=resource_id)) + + return oauth2_post( + f"auth/resource/{resource_id}/user/assign", + data={ + "group_role_id": group_role_id, + "user_email": user_email + }).either(__assign_error__, __assign_success__) + except AssertionError as aserr: + flash(aserr.args[0], "alert-danger") + return redirect(url_for("oauth2.resources.view_resource", resource_id=resource_id)) + +@resources.route("<uuid:resource_id>/user/unassign", methods=["POST"]) +@require_oauth2 +def unassign_role(resource_id: uuid.UUID) -> Response: + form = request.form + group_role_id = form.get("group_role_id", "") + user_id = form.get("user_id", "") + try: + assert bool(group_role_id), "The role must be provided." + assert bool(user_id), "The user id must be provided." + + def __unassign_error__(error): + err = process_error(error) + flash(f"{err['error']}: {err['error_description']}", "alert-danger") + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=resource_id)) + + def __unassign_success__(success): + flash(success["description"], "alert-success") + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=resource_id)) + + return oauth2_post( + f"auth/resource/{resource_id}/user/unassign", + data={ + "group_role_id": group_role_id, + "user_id": user_id + }).either(__unassign_error__, __unassign_success__) + except AssertionError as aserr: + flash(aserr.args[0], "alert-danger") + return redirect(url_for("oauth2.resources.view_resource", resource_id=resource_id)) + +@resources.route("/toggle/<uuid:resource_id>", methods=["POST"]) +@require_oauth2 +def toggle_public(resource_id: uuid.UUID): + """Toggle the given resource's public status.""" + def __handle_error__(err): + flash_error(process_error(err)) + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=resource_id)) + + def __handle_success__(success): + flash_success(success) + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=resource_id)) + + return oauth2_post( + f"auth/resource/{resource_id}/toggle-public", data={}).either( + lambda err: __handle_error__(err), + lambda suc: __handle_success__(suc)) + +@resources.route("/edit/<uuid:resource_id>", methods=["GET"]) +@require_oauth2 +def edit_resource(resource_id: uuid.UUID): + """Edit the given resource.""" + return "WOULD Edit THE GIVEN RESOURCE'S DETAILS" + +@resources.route("/delete/<uuid:resource_id>", methods=["GET"]) +@require_oauth2 +def delete_resource(resource_id: uuid.UUID): + """Delete the given resource.""" + return "WOULD DELETE THE GIVEN RESOURCE" diff --git a/gn2/wqflask/oauth2/roles.py b/gn2/wqflask/oauth2/roles.py new file mode 100644 index 00000000..2fe35f9b --- /dev/null +++ b/gn2/wqflask/oauth2/roles.py @@ -0,0 +1,99 @@ +"""Handle role endpoints""" +import uuid + +from flask import flash, request, url_for, redirect, Blueprint + +from .ui import render_ui +from .checks import require_oauth2 +from .client import oauth2_get, oauth2_post +from .request_utils import request_error, process_error + +roles = Blueprint("role", __name__) + +@roles.route("/user", methods=["GET"]) +@require_oauth2 +def user_roles(): + def __grerror__(roles, user_privileges, error): + return render_ui( + "oauth2/list_roles.html", roles=roles, + user_privileges=user_privileges, + group_roles_error=process_error(error)) + + def __grsuccess__(roles, user_privileges, group_roles): + return render_ui( + "oauth2/list_roles.html", roles=roles, + user_privileges=user_privileges, group_roles=group_roles) + + def __role_success__(roles): + uprivs = tuple( + privilege["privilege_id"] for role in roles + for privilege in role["privileges"]) + return oauth2_get("auth/group/roles").either( + lambda err: __grerror__(roles, uprivs, err), + lambda groles: __grsuccess__(roles, uprivs, groles)) + + return oauth2_get("auth/system/roles").either( + request_error, __role_success__) + +@roles.route("/role/<uuid:role_id>", methods=["GET"]) +@require_oauth2 +def role(role_id: uuid.UUID): + def __success__(the_role): + return render_ui("oauth2/role.html", + role=the_role[0], + resource_id=uuid.UUID(the_role[1])) + + return oauth2_get(f"auth/role/view/{role_id}").either( + request_error, __success__) + +@roles.route("/create", methods=["GET", "POST"]) +@require_oauth2 +def create_role(): + """Create a new role.""" + def __roles_error__(error): + return render_ui( + "oauth2/create-role.html", roles_error=process_error(error)) + + def __gprivs_error__(roles, error): + return render_ui( + "oauth2/create-role.html", roles=roles, + group_privileges_error=process_error(error)) + + def __success__(roles, gprivs): + uprivs = tuple( + privilege["privilege_id"] for role in roles + for privilege in role["privileges"]) + return render_ui( + "oauth2/create-role.html", roles=roles, user_privileges=uprivs, + group_privileges=gprivs, + prev_role_name=request.args.get("role_name")) + + def __fetch_gprivs__(roles): + return oauth2_get("auth/group/privileges").either( + lambda err: __gprivs_error__(roles, err), + lambda gprivs: __success__(roles, gprivs)) + + if request.method == "GET": + return oauth2_get("auth/user/roles").either( + __roles_error__, __fetch_gprivs__) + + form = request.form + role_name = form.get("role_name") + privileges = form.getlist("privileges[]") + if len(privileges) == 0: + flash("You must assign at least one privilege to the role", + "alert-danger") + return redirect(url_for( + "oauth2.role.create_role", role_name=role_name)) + def __create_error__(error): + err = process_error(error) + flash(f"{err['error']}: {err['error_description']}", + "alert-danger") + return redirect(url_for("oauth2.role.create_role")) + def __create_success__(*args): + flash("Role created successfully.", "alert-success") + return redirect(url_for("oauth2.role.user_roles")) + return oauth2_post( + "auth/group/role/create",data={ + "role_name": role_name, "privileges[]": privileges}).either( + __create_error__,__create_success__) diff --git a/gn2/wqflask/oauth2/routes.py b/gn2/wqflask/oauth2/routes.py new file mode 100644 index 00000000..4c4b877b --- /dev/null +++ b/gn2/wqflask/oauth2/routes.py @@ -0,0 +1,18 @@ +"""Routes for the OAuth2 auth system in GN3""" +from flask import Blueprint + +from .data import data +from .users import users +from .roles import roles +from .groups import groups +from .toplevel import toplevel +from .resources import resources + +oauth2 = Blueprint("oauth2", __name__, template_folder="templates/oauth2") + +oauth2.register_blueprint(toplevel, url_prefix="/") +oauth2.register_blueprint(data, url_prefix="/data") +oauth2.register_blueprint(users, url_prefix="/user") +oauth2.register_blueprint(roles, url_prefix="/role") +oauth2.register_blueprint(groups, url_prefix="/group") +oauth2.register_blueprint(resources, url_prefix="/resource") diff --git a/gn2/wqflask/oauth2/session.py b/gn2/wqflask/oauth2/session.py new file mode 100644 index 00000000..2ef534e2 --- /dev/null +++ b/gn2/wqflask/oauth2/session.py @@ -0,0 +1,111 @@ +"""Deal with user sessions""" +from uuid import UUID, uuid4 +from datetime import datetime +from typing import Any, Optional, TypedDict + +from flask import request, session +from pymonad.either import Left, Right, Either + +class UserDetails(TypedDict): + """Session information relating specifically to the user.""" + user_id: UUID + name: str + email: str + token: Either + logged_in: bool + +class SessionInfo(TypedDict): + """All Session information we save.""" + session_id: UUID + user: UserDetails + anon_id: UUID + user_agent: str + ip_addr: str + masquerade: Optional[UserDetails] + +__SESSION_KEY__ = "GN::2::session_info" # Do not use this outside this module!! + +def clear_session_info(): + """Clears the session.""" + session.pop(__SESSION_KEY__) + +def save_session_info(sess_info: SessionInfo) -> SessionInfo: + """Save `session_info`.""" + # TODO: if it is an existing session, verify that certain important security + # bits have not changed before saving. + # old_session_info = session.get(__SESSION_KEY__) + # if bool(old_session_info): + # if old_session_info["user_agent"] == request.headers.get("User-Agent"): + # session[__SESSION_KEY__] = sess_info + # return sess_info + # # request session verification + # return verify_session(sess_info) + # New session + session[__SESSION_KEY__] = sess_info + return sess_info + +def session_info() -> SessionInfo: + """Retrieve the session information""" + anon_id = uuid4() + return save_session_info( + session.get(__SESSION_KEY__, { + "session_id": uuid4(), + "user": { + "user_id": anon_id, + "name": "Anonymous User", + "email": "anon@ymous.user", + "token": Left("INVALID-TOKEN"), + "logged_in": False + }, + "anon_id": anon_id, + "user_agent": request.headers.get("User-Agent"), + "ip_addr": request.environ.get("HTTP_X_FORWARDED_FOR", + request.remote_addr), + "masquerading": None + })) + +def expired(): + the_session = session_info() + def __expired__(token): + return datetime.now() > datetime.fromtimestamp(token["expires_at"]) + return the_session["user"]["token"].either( + lambda left: False, + __expired__) + +def set_user_token(token: str) -> SessionInfo: + """Set the user's token.""" + info = session_info() + return save_session_info({ + **info, "user": {**info["user"], "token": Right(token)}}) + +def set_user_details(userdets: UserDetails) -> SessionInfo: + """Set the user details information""" + return save_session_info({**session_info(), "user": userdets}) + +def user_token() -> Either: + """Retrieve the user token.""" + return session_info()["user"]["token"] + +def set_masquerading(masq_info): + """Save the masquerading user information.""" + orig_user = session_info()["user"] + return save_session_info({ + **session_info(), + "user": { + "user_id": UUID(masq_info["masquerade_as"]["user"]["user_id"]), + "name": masq_info["masquerade_as"]["user"]["name"], + "email": masq_info["masquerade_as"]["user"]["email"], + "token": Right(masq_info["masquerade_as"]["token"]), + "logged_in": True + }, + "masquerading": orig_user + }) + +def unset_masquerading(): + """Restore the original session.""" + the_session = session_info() + return save_session_info({ + **the_session, + "user": the_session["masquerading"], + "masquerading": None + }) diff --git a/gn2/wqflask/oauth2/toplevel.py b/gn2/wqflask/oauth2/toplevel.py new file mode 100644 index 00000000..65f60067 --- /dev/null +++ b/gn2/wqflask/oauth2/toplevel.py @@ -0,0 +1,57 @@ +"""Authentication endpoints.""" +from uuid import UUID +from urllib.parse import urljoin, urlparse, urlunparse +from flask import ( + flash, request, Blueprint, url_for, redirect, render_template, + current_app as app) + +from . import session +from .client import SCOPE, no_token_post +from .checks import require_oauth2, user_logged_in +from .request_utils import user_details, process_error + +toplevel = Blueprint("toplevel", __name__) + +@toplevel.route("/register-client", methods=["GET", "POST"]) +@require_oauth2 +def register_client(): + """Register an OAuth2 client.""" + return "USER IS LOGGED IN AND SUCCESSFULLY ACCESSED THIS ENDPOINT!" + +@toplevel.route("/code", methods=["GET"]) +def authorisation_code(): + """Use authorisation code to get token.""" + def __error__(error): + flash(f"{error['error']}: {error['error_description']}", + "alert-danger") + return redirect("/") + + def __success__(token): + session.set_user_token(token) + udets = user_details() + session.set_user_details({ + "user_id": UUID(udets["user_id"]), + "name": udets["name"], + "email": udets["email"], + "token": session.user_token(), + "logged_in": True + }) + return redirect("/") + + code = request.args.get("code", "") + if bool(code): + base_url = urlparse(request.base_url, scheme=request.scheme) + request_data = { + "grant_type": "authorization_code", + "code": code, + "scope": SCOPE, + "redirect_uri": urljoin( + urlunparse(base_url), + url_for("oauth2.toplevel.authorisation_code")), + "client_id": app.config["OAUTH2_CLIENT_ID"] + } + return no_token_post( + "auth/token", data=request_data).either( + lambda err: __error__(process_error(err)), __success__) + flash("AuthorisationError: No code was provided.", "alert-danger") + return redirect("/") diff --git a/gn2/wqflask/oauth2/ui.py b/gn2/wqflask/oauth2/ui.py new file mode 100644 index 00000000..b706cdd9 --- /dev/null +++ b/gn2/wqflask/oauth2/ui.py @@ -0,0 +1,23 @@ +"""UI utilities""" +from flask import session, render_template + +from .client import oauth2_get +from .checks import user_logged_in +from .request_utils import process_error + +def render_ui(templatepath: str, **kwargs): + """Handle repetitive UI rendering stuff.""" + roles = kwargs.get("roles", tuple()) # Get roles if already provided + if user_logged_in() and not bool(roles): # If not, try fetching them + roles_results = oauth2_get("auth/system/roles").either( + lambda err: {"roles_error": process_error(err)}, + lambda roles: {"roles": roles}) + kwargs = {**kwargs, **roles_results} + roles = kwargs.get("roles", tuple()) + user_privileges = tuple( + privilege["privilege_id"] for role in roles + for privilege in role["privileges"]) + kwargs = { + **kwargs, "roles": roles, "user_privileges": user_privileges + } + return render_template(templatepath, **kwargs) diff --git a/gn2/wqflask/oauth2/users.py b/gn2/wqflask/oauth2/users.py new file mode 100644 index 00000000..12d07d0c --- /dev/null +++ b/gn2/wqflask/oauth2/users.py @@ -0,0 +1,190 @@ +import requests +from uuid import UUID +from urllib.parse import urljoin + +from authlib.integrations.base_client.errors import OAuthError +from flask import ( + flash, request, url_for, redirect, Response, Blueprint, + current_app as app) + +from . import client +from . import session +from .ui import render_ui +from .checks import require_oauth2, user_logged_in +from .client import oauth2_get, oauth2_post, oauth2_client +from .request_utils import ( + user_details, request_error, process_error, with_flash_error) + +users = Blueprint("user", __name__) + +@users.route("/profile", methods=["GET"]) +@require_oauth2 +def user_profile(): + __id__ = lambda the_val: the_val + usr_dets = user_details() + def __render__(usr_dets, roles=[], **kwargs): + return render_ui( + "oauth2/view-user.html", user_details=usr_dets, roles=roles, + user_privileges = tuple( + privilege["privilege_id"] for role in roles + for privilege in role["privileges"]), + **kwargs) + + def __roles_success__(roles): + if bool(usr_dets.get("group")): + return __render__(usr_dets, roles) + return oauth2_get("auth/user/group/join-request").either( + lambda err: __render__( + user_details, group_join_error=process_error(err)), + lambda gjr: __render__(usr_dets, roles=roles, group_join_request=gjr)) + + return oauth2_get("auth/system/roles").either( + lambda err: __render__(usr_dets, role_error=process_error(err)), + __roles_success__) + +@users.route("/request-add-to-group", methods=["POST"]) +@require_oauth2 +def request_add_to_group() -> Response: + """Request to be added to a group.""" + form = request.form + group_id = form["group"] + + def __error__(error): + err = process_error(error) + flash(f"{err['error']}: {err['error_message']}", "alert-danger") + return redirect(url_for("oauth2.user.user_profile")) + + def __success__(response): + flash(f"{response['message']} (Response ID: {response['request_id']})", + "alert-success") + return redirect(url_for("oauth2.user.user_profile")) + + return oauth2_post(f"auth/group/requests/join/{group_id}", + data=form).either(__error__, __success__) + +@users.route("/login", methods=["GET", "POST"]) +def login(): + """Route to allow users to sign up.""" + from gn2.utility.tools import AUTH_SERVER_URL + next_endpoint=request.args.get("next", False) + + if request.method == "POST": + form = request.form + client = oauth2_client() + try: + token = client.fetch_token( + urljoin(AUTH_SERVER_URL, "auth/token"), + username=form.get("email_address"), + password=form.get("password"), + grant_type="password") + session.set_user_token(token) + udets = user_details() + session.set_user_details({ + "user_id": UUID(udets["user_id"]), + "name": udets["name"], + "email": udets["email"], + "token": session.user_token(), + "logged_in": True + }) + except OAuthError as _oaerr: + flash(_oaerr.args[0], "alert-danger") + return render_ui( + "oauth2/login.html", next_endpoint=next_endpoint, + email=form.get("email_address")) + + if user_logged_in(): + if next_endpoint: + return redirect(url_for(next_endpoint)) + return redirect("/") + + return render_ui("oauth2/login.html", next_endpoint=next_endpoint) + +@users.route("/logout", methods=["GET", "POST"]) +def logout(): + from gn2.utility.tools import AUTH_SERVER_URL + if user_logged_in(): + resp = oauth2_client().revoke_token( + urljoin(AUTH_SERVER_URL, "auth/revoke")) + the_session = session.session_info() + if not bool(the_session["masquerading"]): + # Normal session - clear and go back. + session.clear_session_info() + flash("Successfully logged out.", "alert-success") + return redirect("/") + # Restore masquerading session + session.unset_masquerading() + flash( + "Successfully logged out as user " + f"{the_session['user']['name']} ({the_session['user']['email']}) " + "and restored session for user " + f"{the_session['masquerading']['name']} " + f"({the_session['masquerading']['email']})", + "alert-success") + return redirect("/") + +@users.route("/register", methods=["GET", "POST"]) +def register_user(): + from gn2.utility.tools import AUTH_SERVER_URL + if user_logged_in(): + next_endpoint=request.args.get("next", "/") + flash(("You cannot register a new user while logged in. " + "Please logout to register a new user."), + "alert-danger") + return redirect(next_endpoint) + + if request.method == "GET": + return render_ui("oauth2/register_user.html") + + form = request.form + response = requests.post( + urljoin(AUTH_SERVER_URL, "auth/user/register"), + data = { + "user_name": form.get("user_name"), + "email": form.get("email_address"), + "password": form.get("password"), + "confirm_password": form.get("confirm_password")}) + results = response.json() + if "error" in results: + error_messages = tuple( + f"{results['error']}: {msg.strip()}" + for msg in results.get("error_description").split("::")) + for message in error_messages: + flash(message, "alert-danger") + return redirect(url_for("oauth2.user.register_user")) + + flash("Registration successful! Please login to continue.", "alert-success") + return redirect(url_for("oauth2.user.login")) + +@users.route("/masquerade", methods=["GET", "POST"]) +def masquerade(): + """Masquerade as a particular user.""" + if request.method == "GET": + this_user = session.session_info()["user"] + return client.get("auth/user/list").either( + lambda err: render_ui( + "oauth2/masquerade.html", users_error=process_error(err)), + lambda usrs: render_ui( + "oauth2/masquerade.html", users=tuple( + usr for usr in usrs + if UUID(usr["user_id"]) != this_user["user_id"]))) + + def __masq_success__(masq_details): + session.set_masquerading(masq_details) + flash( + f"User {masq_details['original']['user']['name']} " + f"({masq_details['original']['user']['email']}) is now " + "successfully masquerading as the user " + f"User {masq_details['masquerade_as']['user']['name']} " + f"({masq_details['masquerade_as']['user']['email']}) is now ", + "alert-success") + return redirect("/") + form = request.form + masquerade_as = form.get("masquerade_as").strip() + if not(bool(masquerade_as)): + flash("You must provide a user to masquerade as.", "alert-danger") + return redirect(url_for("oauth2.user.masquerade")) + return client.post( + "auth/user/masquerade/", + json={"masquerade_as": request.form.get("masquerade_as")}).either( + with_flash_error(redirect(url_for("oauth2.user.masquerade"))), + __masq_success__) diff --git a/gn2/wqflask/parser.py b/gn2/wqflask/parser.py new file mode 100644 index 00000000..ddf48d90 --- /dev/null +++ b/gn2/wqflask/parser.py @@ -0,0 +1,91 @@ +""" +Parses search terms input by user + +Searches take two primary forms: +- search term by itself (ex. "shh" or "brain") +- key / separator / value(s) (ex. "LRS=(9 99 Chr4 122 155)" or "GO:342533") + +In the example of "LRS=(9 99 Chr4 122 155)", the key is "LRS", the separator is "=" and the value +is everything within the parentheses. + +Both "=" and ":" can be used as separators; in the future, it would also be good to allow no +separator at all (ex. "cisLRS(9 999 10)") + +Both square brackets and parentheses can be used interchangeably. Both can also be used to +encapsulate a single value; "cisLRS=[9 999 10)" would +be acceptable.] + +""" + +import re + +from pprint import pformat as pf + + +def parse(pstring): + """ + + returned item search_term is always a list, even if only one element + """ + pstring = re.split(r"""(?:(\w+\s*=\s*[\('"\[][^)'"]*[\)\]'"]) | # LRS=(1 2 3), cisLRS=[4 5 6], etc + (\w+\s*[=:\>\<][\w\*]+) | # wiki=bar, GO:foobar, etc + (".*?") | ('.*?') | # terms in quotes, i.e. "brain weight" + ([\w\*\?\-]+)) # shh, brain, etc """, pstring, + flags=re.VERBOSE) + + pstring = [item.strip() for item in pstring if item and item.strip()] + + items = [] + + separators = [re.escape(x) for x in ("<=", ">=", ":", "=", "<", ">")] + separators = '(%s)' % ("|".join(separators)) + + for item in pstring: + splat = re.split(separators, item) + + # splat is an array of 1 if no match, otherwise more than 1 + if len(splat) > 1: + key, separator, value = splat + if '(' in value or '[' in value: + assert value.startswith(("(", "[")), "Invalid token" + assert value.endswith((")", "]")), "Invalid token" + value = value[1:-1] # Get rid of the parenthesis + values = re.split(r"""\s+|,""", value) + value = [value.strip() for value in values if value.strip()] + else: + value = [value] + # : is a synonym for = + if separator == ":": + separator = "=" + + term = dict(key=key, + separator=separator, + search_term=value) + else: + if (item[0] == "\"" and item[-1] == "\"") or (item[0] == "'" and item[-1] == "'"): + item = item[1:-1] + term = dict(key=None, + separator=None, + search_term=[item]) + + items.append(term) + return(items) + + +if __name__ == '__main__': + parse("foo=[3 2 1]") + parse("WIKI=ho*") + parse("LRS>9") + parse("LRS>=18") + parse("NAME='rw williams'") + parse('NAME="rw williams"') + parse("foo <= 2") + parse("cisLRS<20") + parse("foo=[3 2 1)") + parse("foo=(3 2 1)") + parse("shh") + parse("shh grep") + parse("LRS=(9 99 Chr4 122 155) cisLRS=(9 999 10)") + parse("sal1 LRS=(9 99 Chr4 122 155) sal2 cisLRS=(9 999 10)") + parse("sal1 sal3 LRS=(9 99 Chr4 122 155) wiki=bar sal2 go:foobar cisLRS=(9 999 10)") + parse("sal1 LRS=(9 99 Chr4 122 155) wiki=bar sal2 go:foobar cisLRS=(9, 999, 10)") diff --git a/gn2/wqflask/partial_correlations_views.py b/gn2/wqflask/partial_correlations_views.py new file mode 100644 index 00000000..44b0fba1 --- /dev/null +++ b/gn2/wqflask/partial_correlations_views.py @@ -0,0 +1,372 @@ +import json +import math +import requests +from functools import reduce +from typing import Union, Tuple +from urllib.parse import urljoin + +from flask import ( + flash, + request, + url_for, + redirect, + current_app, + render_template) + +from gn2.wqflask import app +from gn2.utility.tools import get_setting, GN_SERVER_URL +from gn2.wqflask.database import database_connection +from gn3.db.partial_correlations import traits_info + +def publish_target_databases(conn, groups, threshold): + query = ( + "SELECT PublishFreeze.FullName,PublishFreeze.Name " + "FROM PublishFreeze, InbredSet " + "WHERE PublishFreeze.InbredSetId = InbredSet.Id " + f"AND InbredSet.Name IN ({', '.join(['%s'] * len(groups))}) " + "AND PublishFreeze.public > %s") + with conn.cursor() as cursor: + cursor.execute(query, tuple(groups) + (threshold,)) + res = cursor.fetchall() + if res: + return tuple( + dict(zip(("description", "value"), row)) for row in res) + + return tuple() + +def geno_target_databases(conn, groups, threshold): + query = ( + "SELECT GenoFreeze.FullName,GenoFreeze.Name " + "FROM GenoFreeze, InbredSet " + "WHERE GenoFreeze.InbredSetId = InbredSet.Id " + f"AND InbredSet.Name IN ({', '.join(['%s'] * len(groups))}) " + "AND GenoFreeze.public > %s") + with conn.cursor() as cursor: + cursor.execute(query, tuple(groups) + (threshold,)) + res = cursor.fetchall() + if res: + return tuple( + dict(zip(("description", "value"), row)) for row in res) + + return tuple() + +def probeset_target_databases(conn, groups, threshold): + query1 = "SELECT Id, Name FROM Tissue order by Name" + with conn.cursor() as cursor: + cursor.execute(query1) + tissue_res = cursor.fetchall() + if tissue_res: + tissue_ids = tuple(row[0] for row in tissue_res) + groups_clauses = ["InbredSet.Name like %s"] * len(groups) + query2 = ( + "SELECT ProbeFreeze.TissueId, ProbeSetFreeze.FullName, " + "ProbeSetFreeze.Name " + "FROM ProbeSetFreeze, ProbeFreeze, InbredSet " + "WHERE ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "AND ProbeFreeze.TissueId IN " + f"({', '.join(['%s'] * len(tissue_ids))}) " + "AND ProbeSetFreeze.public > %s " + "AND ProbeFreeze.InbredSetId = InbredSet.Id " + f"AND ({' OR '.join(groups_clauses)}) " + "ORDER BY ProbeSetFreeze.CreateTime desc, ProbeSetFreeze.AvgId") + cursor.execute(query2, tissue_ids + (threshold,) + tuple(groups)) + db_res = cursor.fetchall() + if db_res: + databases = tuple( + dict(zip(("tissue_id", "description", "value"), row)) + for row in db_res) + return tuple( + {tissue_name: tuple( + { + "value": item["value"], + "description": item["description"] + } for item in databases + if item["tissue_id"] == tissue_id)} + for tissue_id, tissue_name in tissue_res) + + return tuple() + +def target_databases(conn, traits, threshold): + """ + Retrieves the names of possible target databases from the database. + """ + trait_info = traits_info( + conn, threshold, + tuple(f"{trait['dataset']}::{trait['trait_name']}" for trait in traits)) + groups = tuple(set(row["db"]["group"] for row in trait_info)) + return ( + publish_target_databases(conn, groups, threshold) + + geno_target_databases(conn, groups, threshold) + + probeset_target_databases(conn, groups, threshold)) + +def primary_error(args): + if len(args["primary_trait"]) == 0 or len(args["primary_trait"]) > 1: + return { + **args, + "errors": (args.get("errors", tuple()) + + ("You must provide one, and only one primary trait",))} + return args + +def controls_error(args): + if len(args["control_traits"]) == 0 or len(args["control_traits"]) > 3: + return { + **args, + "errors": ( + args.get("errors", tuple()) + + (("You must provide at least one control trait, and a maximum " + "of three control traits"),))} + return args + +def target_traits_error(args, with_target_traits): + target_traits_present = ( + (args.get("target_traits") is not None) and + (len(args["target_traits"]) > 0)) + if with_target_traits and not target_traits_present: + return { + **args, + "errors": ( + args.get("errors", tuple()) + + (("You must provide at least one target trait"),))} + return args + +def target_db_error(args, with_target_db: bool): + if with_target_db and not args["target_db"]: + return { + **args, + "errors": ( + args.get("errors", tuple()) + + ("The target database must be provided",))} + return args + +def method_error(args): + methods = ( + "pearson's r", "spearman's rho", + "genetic correlation, pearson's r", + "genetic correlation, spearman's rho", + "sgo literature correlation", + "tissue correlation, pearson's r", + "tissue correlation, spearman's rho") + if not args["method"] or args["method"].lower() not in methods: + return { + **args, + "errors": ( + args.get("errors", tuple()) + + ("Invalid correlation method provided",))} + return args + +def criteria_error(args): + try: + int(args.get("criteria", "invalid")) + return args + except ValueError: + return { + **args, + "errors": ( + args.get("errors", tuple()) + + ("Invalid return number provided",))} + +def errors(args, with_target_db: bool): + return { + **criteria_error( + method_error( + target_traits_error( + target_db_error( + controls_error(primary_error(args)), + with_target_db), + not with_target_db))), + "with_target_db": with_target_db + } + +def __classify_args(acc, item): + if item[1].startswith("primary_"): + return { + **acc, + "primary_trait": (acc.get("primary_trait", tuple()) + (item,))} + if item[1].startswith("controls_"): + return {**acc, "control_traits": (acc.get("control_traits", tuple()) + (item,))} + if item[1].startswith("targets_"): + return {**acc, "target_traits": (acc.get("target_traits", tuple()) + (item,))} + if item[0] == "target_db": + return {**acc, "target_db": item[1]} + if item[0] == "method": + return {**acc, "method": item[1]} + if item[0] == "criteria": + return {**acc, "criteria": item[1]} + return acc + +def __build_args(raw_form, traits): + args = reduce(__classify_args, raw_form.items(), {}) + return { + **args, + "primary_trait": [ + item for item in traits if item["trait_name"] in + (name[1][8:] for name in args["primary_trait"])], + "control_traits": [ + item for item in traits if item["trait_name"] in + (name[1][9:] for name in args["control_traits"])], + "target_traits": [ + item for item in traits if item["trait_name"] in + (name[1][8:] for name in args.get("target_traits", tuple()))] + } + +def parse_trait(trait_str): + return dict(zip( + ("trait_name", "dataset", "description", "symbol", "location", "mean", + "lrs", "lrs_location"), + trait_str.strip().split("|||"))) + +def response_error_message(response): + error_messages = { + 404: ("We could not connect to the API server at this time. " + "Try again later."), + 500: ("The API server experienced a problem. We will be working on a " + "fix. Please try again later.") + } + return error_messages.get( + response.status_code, + "General API server error!!") + +def render_error(error_message, command_id = None): + return render_template( + "partial_correlations/pcorrs_error.html", + message = error_message, + command_id = command_id) + +def __format_number(num): + if num is None or math.isnan(num): + return "" + if abs(num) <= 1.04E-4: + return f"{num:.2e}" + return f"{num:.5f}" + +def handle_200_response(response): + if response.get("queued", False): + return redirect( + url_for( + "poll_partial_correlation_results", + command_id=response["results"]), + code=303) + if response["status"] == "success": + return render_template( + "partial_correlations/pcorrs_results_with_target_traits.html", + primary = response["results"]["results"]["primary_trait"], + controls = response["results"]["results"]["control_traits"], + pcorrs = sorted( + response["results"]["results"]["correlations"], + key = lambda item: item["partial_corr_p_value"]), + method = response["results"]["results"]["method"], + enumerate = enumerate, + format_number = __format_number) + return render_error(response["results"]) + +def handle_response(response): + if response.status_code != 200: + return render_template( + "partial_correlations/pcorrs_error.html", + message = response_error_message(response)) + return handle_200_response(response.json()) + +@app.route("/partial_correlations", methods=["POST"]) +def partial_correlations(): + form = request.form + traits = tuple( + parse_trait(trait) for trait in + form.get("trait_list").split(";;;")) + + submit = form.get("submit") + + if submit in ("with_target_pearsons", "with_target_spearmans"): + method = "pearsons" if "pearsons" in submit else "spearmans" + args = { + **errors(__build_args(form, traits), with_target_db=False), + "method": method + } + if len(args.get("errors", [])) == 0: + post_data = { + **args, + "primary_trait": args["primary_trait"][0], + "with_target_db": args["with_target_db"] + } + return handle_response(requests.post( + url=urljoin(GN_SERVER_URL, "correlation/partial"), + json=post_data)) + + for error in args["errors"]: + flash(error, "alert-danger") + + if submit == "Run Partial Correlations": + args = errors(__build_args(form, traits), with_target_db=True) + if len(args.get("errors", [])) == 0: + post_data = { + **args, + "primary_trait": args["primary_trait"][0], + "with_target_db": args["with_target_db"] + } + return handle_response(requests.post( + url=urljoin(GN_SERVER_URL, "correlation/partial"), + json=post_data)) + + for error in args["errors"]: + flash(error, "alert-danger") + + with database_connection(get_setting("SQL_URI")) as conn: + target_dbs = target_databases(conn, traits, threshold=0) + return render_template( + "partial_correlations/pcorrs_select_operations.html", + trait_list_str=form.get("trait_list"), + traits=traits, + target_dbs=target_dbs) + +def process_pcorrs_command_output(result): + if result["status"] == "success": + + if result["results"]["dataset_type"] == "NOT SET YET": + return render_template( + "partial_correlations/pcorrs_results_with_target_traits.html", + primary = result["results"]["primary_trait"], + controls = result["results"]["control_traits"], + pcorrs = sorted( + result["results"]["correlations"], + key = lambda item: item["partial_corr_p_value"]), + method = result["results"]["method"], + enumerate = enumerate, + format_number = __format_number) + + return render_template( + "partial_correlations/pcorrs_results_presentation.html", + primary=result["results"]["primary_trait"], + controls=result["results"]["control_traits"], + correlations=result["results"]["correlations"], + dataset_type=result["results"]["dataset_type"], + method=result["results"]["method"], + enumerate = enumerate, + format_number=__format_number) + if result["status"] == "error": + return render_error( + f"({result['error_type']}: {result['message']})") + +@app.route("/partial_correlations/<command_id>", methods=["GET"]) +def poll_partial_correlation_results(command_id): + response = requests.get( + url=urljoin(GN_SERVER_URL, f"async_commands/state/{command_id}")) + + if response.status_code == 200: + data = response.json() + raw_result = data["result"] + result = {"status": "computing"} + if raw_result: + result = json.loads(raw_result) + if result["status"].lower() in ("error", "exception"): + return render_error( + "We messed up, and the computation failed due to a system " + "error.", + command_id) + if data["status"] == "success": + return process_pcorrs_command_output(json.loads(data["result"])) + return render_template( + "partial_correlations/pcorrs_poll_results.html", + command_id = command_id) + return render_error( + "We messed up, and the computation failed due to a system " + "error.", + command_id) diff --git a/gn2/wqflask/pbkdf2.py b/gn2/wqflask/pbkdf2.py new file mode 100644 index 00000000..1a965fc5 --- /dev/null +++ b/gn2/wqflask/pbkdf2.py @@ -0,0 +1,22 @@ +import hashlib + +from werkzeug.security import safe_str_cmp as ssc + +# Replace this because it just wraps around Python3's internal +# functions. Added this during migration. + + +def pbkdf2_hex(data, salt, iterations=1000, keylen=24, hashfunc="sha1"): + """Wrapper function of python's hashlib.pbkdf2_hmac. + """ + + dk = hashlib.pbkdf2_hmac(hashfunc, + bytes(data, "utf-8"), # password + salt, + iterations, + keylen) + return dk.hex() + + +def safe_str_cmp(a, b): + return ssc(a, b) diff --git a/gn2/wqflask/requests.py b/gn2/wqflask/requests.py new file mode 100644 index 00000000..43c8001f --- /dev/null +++ b/gn2/wqflask/requests.py @@ -0,0 +1,16 @@ +"""requests but with monads""" +import requests +from pymonad.either import Left, Right, Either + +def __wrap_response__(resp) -> Either: + if resp.status_code == 200: + return Right(resp) + return Left(resp) + +def get(url, params=None, **kwargs) -> Either: + """Wrap requests get method with Either monad""" + return __wrap_response__(requests.get(url, params=params, **kwargs)) + +def post(url, data=None, json=None, **kwargs) -> Either: + """Wrap requests post method with Either monad""" + return __wrap_response__(requests.post(url, data=data, json=json, **kwargs)) diff --git a/gn2/wqflask/resource_manager.py b/gn2/wqflask/resource_manager.py new file mode 100644 index 00000000..b0da6d6f --- /dev/null +++ b/gn2/wqflask/resource_manager.py @@ -0,0 +1,169 @@ +import json +import redis +import requests + +from flask import Blueprint +from flask import current_app +from flask import flash +from flask import g +from flask import redirect +from flask import render_template +from flask import request +from flask import url_for + +from gn3.authentication import AdminRole +from gn3.authentication import DataRole +from gn3.authentication import get_user_membership +from gn3.authentication import get_highest_user_access_role + +from typing import Dict, Tuple +from urllib.parse import urljoin + + +from gn2.wqflask.decorators import edit_access_required +from gn2.wqflask.decorators import edit_admins_access_required +from gn2.wqflask.decorators import login_required + + +resource_management = Blueprint('resource_management', __name__) + + +def add_extra_resource_metadata(conn: redis.Redis, + resource_id: str, + resource: Dict) -> Dict: + """If resource['owner_id'] exists, add metadata about that user. Also, +if the resource contains group masks, add the group name into the +resource dict. Note that resource['owner_id'] and the group masks are +unique identifiers so they aren't human readable names. + + Args: + - conn: A redis connection with the responses decoded. + - resource_id: The unique identifier of the resource. + - resource: A dict containing details(metadata) about a + given resource. + + Returns: + An embellished dictionary with its resource id; the human + readable names of the group masks; and the owner id if it was set. + + """ + resource["resource_id"] = resource_id + + # Embellish the resource information with owner details if the + # owner is set + if (owner_id := resource.get("owner_id", "none").lower()) == "none": + resource["owner_id"] = None + resource["owner_details"] = None + else: + user_details = json.loads(conn.hget("users", owner_id)) + resource["owner_details"] = { + "email_address": user_details.get("email_address"), + "full_name": user_details.get("full_name"), + "organization": user_details.get("organization"), + } + + # Embellish the resources information with the group name if the + # group masks are present + if groups := resource.get('group_masks', {}): + for group_id in groups.keys(): + resource['group_masks'][group_id]["group_name"] = ( + json.loads(conn.hget("groups", group_id)).get('name')) + return resource + + +@resource_management.route("/resources/<resource_id>") +@login_required() +def view_resource(resource_id: str): + user_id = (g.user_session.record.get(b"user_id", + b"").decode("utf-8") or + g.user_session.record.get("user_id", "")) + redis_conn = redis.from_url( + current_app.config["REDIS_URL"], + decode_responses=True) + # Abort early if the resource can't be found + if not (resource := redis_conn.hget("resources", resource_id)): + return f"Resource: {resource_id} Not Found!", 401 + + return render_template( + "admin/manage_resource.html", + resource_info=(add_extra_resource_metadata( + conn=redis_conn, + resource_id=resource_id, + resource=json.loads(resource))), + access_role=get_highest_user_access_role( + resource_id=resource_id, + user_id=user_id, + gn_proxy_url=current_app.config.get("GN2_PROXY"))) + + +@resource_management.route("/resources/<resource_id>/make-public", + methods=('POST',)) +@edit_access_required +@login_required() +def update_resource_publicity(resource_id: str): + redis_conn = redis.from_url( + current_app.config["REDIS_URL"], + decode_responses=True) + resource_info = json.loads(redis_conn.hget("resources", resource_id)) + + if (is_open_to_public := request + .form + .to_dict() + .get("open_to_public")) == "True": + resource_info['default_mask'] = { + 'data': DataRole.VIEW.value, + 'admin': AdminRole.NOT_ADMIN.value, + 'metadata': DataRole.VIEW.value, + } + elif is_open_to_public == "False": + resource_info['default_mask'] = { + 'data': DataRole.NO_ACCESS.value, + 'admin': AdminRole.NOT_ADMIN.value, + 'metadata': DataRole.NO_ACCESS.value, + } + redis_conn.hset("resources", resource_id, json.dumps(resource_info)) + return redirect(url_for("resource_management.view_resource", + resource_id=resource_id)) + + +@resource_management.route("/resources/<resource_id>/change-owner") +@edit_admins_access_required +@login_required() +def view_resource_owner(resource_id: str): + return render_template( + "admin/change_resource_owner.html", + resource_id=resource_id) + + +@resource_management.route("/resources/<resource_id>/change-owner", + methods=('POST',)) +@edit_admins_access_required +@login_required() +def change_owner(resource_id: str): + if user_id := request.form.get("new_owner"): + redis_conn = redis.from_url( + current_app.config["REDIS_URL"], + decode_responses=True) + resource = json.loads(redis_conn.hget("resources", resource_id)) + resource["owner_id"] = user_id + redis_conn.hset("resources", resource_id, json.dumps(resource)) + flash("The resource's owner has been changed.", "alert-info") + return redirect(url_for("resource_management.view_resource", + resource_id=resource_id)) + + +@resource_management.route("<resource_id>/users/search", methods=('POST',)) +@edit_admins_access_required +@login_required() +def search_user(resource_id: str): + results = {} + for user in (users := redis.from_url( + current_app.config["REDIS_URL"], + decode_responses=True).hgetall("users")): + user = json.loads(users[user]) + for q in (request.form.get("user_name"), + request.form.get("user_email")): + if q and (q in user.get("email_address", "") or + q in user.get("full_name", "")): + results[user.get("user_id", "")] = user + return json.dumps(tuple(results.values())) diff --git a/gn2/wqflask/search_results.py b/gn2/wqflask/search_results.py new file mode 100644 index 00000000..b0f08463 --- /dev/null +++ b/gn2/wqflask/search_results.py @@ -0,0 +1,433 @@ +import uuid +from math import * +import requests +import unicodedata +import re + +import json + +from flask import g + +from gn2.base.data_set import create_dataset +from gn2.base.webqtlConfig import PUBMEDLINK_URL +from gn2.wqflask import parser +from gn2.wqflask import do_search + +from gn2.wqflask.database import database_connection + +from gn2.utility import hmac +from gn2.utility.authentication_tools import check_resource_availability +from gn2.utility.tools import get_setting, GN2_BASE_URL +from gn2.utility.type_checking import is_str + + +class SearchResultPage: + #maxReturn = 3000 + + def __init__(self, kw): + """ + This class gets invoked after hitting submit on the main menu (in + views.py). + """ + + ########################################### + # Names and IDs of group / F2 set + ########################################### + + self.uc_id = uuid.uuid4() + self.go_term = None + + if kw['search_terms_or']: + self.and_or = "or" + self.search_terms = kw['search_terms_or'] + else: + self.and_or = "and" + self.search_terms = kw['search_terms_and'] + search = self.search_terms + self.original_search_string = self.search_terms + # check for dodgy search terms + rx = re.compile( + r'.*\W(href|http|sql|select|update)\W.*', re.IGNORECASE) + if rx.match(search): + self.search_term_exists = False + return + else: + self.search_term_exists = True + + self.results = [] + max_result_count = 100000 # max number of results to display + type = kw.get('type') + if type == "Phenotypes": # split datatype on type field + max_result_count = 50000 + dataset_type = "Publish" + elif type == "Genotypes": + dataset_type = "Geno" + else: + dataset_type = "ProbeSet" # ProbeSet is default + + assert(is_str(kw.get('dataset'))) + self.dataset = create_dataset(kw['dataset'], dataset_type) + + # I don't like using try/except, but it seems like the easiest way to account for all possible bad searches here + try: + self.search() + except: + self.search_term_exists = False + + self.too_many_results = False + if self.search_term_exists: + if len(self.results) > max_result_count: + self.trait_list = [] + self.too_many_results = True + else: + self.gen_search_result() + + def gen_search_result(self): + """ + Get the info displayed in the search result table from the set of results computed in + the "search" function + + """ + trait_list = [] + json_trait_list = [] + + # result_set represents the results for each search term; a search of + # "shh grin2b" would have two sets of results, one for each term + + if self.dataset.type == "ProbeSet": + self.header_data_names = ['index', 'display_name', 'symbol', 'description', 'location', 'mean', 'lrs_score', 'lrs_location', 'additive'] + elif self.dataset.type == "Publish": + self.header_data_names = ['index', 'display_name', 'description', 'mean', 'authors', 'pubmed_text', 'lrs_score', 'lrs_location', 'additive'] + elif self.dataset.type == "Geno": + self.header_data_names = ['index', 'display_name', 'location'] + + for index, result in enumerate(self.results): + if not result: + continue + + trait_dict = {} + trait_dict['index'] = index + 1 + + trait_dict['dataset'] = self.dataset.name + if self.dataset.type == "ProbeSet": + trait_dict['display_name'] = result[2] + trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(trait_dict['display_name'], trait_dict['dataset'])) + trait_dict['symbol'] = "N/A" if result[3] is None else result[3].strip() + description_text = "" + if result[4] is not None and str(result[4]) != "": + description_text = unicodedata.normalize("NFKD", result[4].decode('latin1')) + + target_string = result[5].decode('utf-8') if result[5] else "" + description_display = description_text if target_string is None or str(target_string) == "" else description_text + "; " + str(target_string).strip() + trait_dict['description'] = description_display + + trait_dict['location'] = "N/A" + if (result[6] is not None) and (result[6] != "") and (result[6] != "Un") and (result[7] is not None) and (result[7] != 0): + trait_dict['location'] = f"Chr{result[6]}: {float(result[7]):.6f}" + + trait_dict['mean'] = "N/A" if result[8] is None or result[8] == "" else f"{result[8]:.3f}" + trait_dict['additive'] = "N/A" if result[12] is None or result[12] == "" else f"{result[12]:.3f}" + trait_dict['lod_score'] = "N/A" if result[9] is None or result[9] == "" else f"{float(result[9]) / 4.61:.1f}" + trait_dict['lrs_location'] = "N/A" if result[13] is None or result[13] == "" or result[14] is None else f"Chr{result[13]}: {float(result[14]):.6f}" + elif self.dataset.type == "Geno": + trait_dict['display_name'] = str(result[0]) + trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(trait_dict['display_name'], trait_dict['dataset'])) + trait_dict['location'] = "N/A" + if (result[4] != "NULL" and result[4] != "") and (result[5] != 0): + trait_dict['location'] = f"Chr{result[4]}: {float(result[5]):.6f}" + elif self.dataset.type == "Publish": + # Check permissions on a trait-by-trait basis for phenotype traits + trait_dict['name'] = trait_dict['display_name'] = str(result[0]) + trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(trait_dict['name'], trait_dict['dataset'])) + permissions = check_resource_availability( + self.dataset, g.user_session.user_id, trait_dict['display_name']) + if not any(x in permissions['data'] for x in ["view", "edit"]): + continue + + if result[10]: + trait_dict['display_name'] = str(result[10]) + "_" + str(result[0]) + trait_dict['description'] = "N/A" + trait_dict['pubmed_id'] = "N/A" + trait_dict['pubmed_link'] = "N/A" + trait_dict['pubmed_text'] = "N/A" + trait_dict['mean'] = "N/A" + trait_dict['additive'] = "N/A" + pre_pub_description = "N/A" if result[1] is None else result[1].strip() + post_pub_description = "N/A" if result[2] is None else result[2].strip() + if result[5] != "NULL" and result[5] != None: + trait_dict['pubmed_id'] = result[5] + trait_dict['pubmed_link'] = PUBMEDLINK_URL % trait_dict['pubmed_id'] + trait_dict['description'] = post_pub_description + else: + trait_dict['description'] = pre_pub_description + + if result[4].isdigit(): + trait_dict['pubmed_text'] = result[4] + + trait_dict['authors'] = result[3] + trait_dict['authors_display'] = trait_dict['authors'] + author_list = trait_dict['authors'].split(",") + if len(author_list) >= 2: + trait_dict['authors_display'] = (",").join(author_list[:2]) + ", et al." + + if result[6] != "" and result[6] != None: + trait_dict['mean'] = f"{result[6]:.3f}" + + try: + trait_dict['lod_score'] = f"{float(result[7]) / 4.61:.1f}" + except: + trait_dict['lod_score'] = "N/A" + + try: + trait_dict['lrs_location'] = f"Chr{result[11]}: {float(result[12]):.6f}" + except: + trait_dict['lrs_location'] = "N/A" + + trait_dict['additive'] = "N/A" if not result[8] else f"{result[8]:.3f}" + + trait_dict['trait_info_str'] = trait_info_str(trait_dict, self.dataset.type) + + # Convert any bytes in dict to a normal utf-8 string + for key in trait_dict.keys(): + if isinstance(trait_dict[key], bytes): + try: + trait_dict[key] = trait_dict[key].decode('utf-8') + except UnicodeDecodeError: + trait_dict[key] = trait_dict[key].decode('latin-1') + + trait_list.append(trait_dict) + + if self.results: + self.max_widths = {} + for i, trait in enumerate(trait_list): + for key in trait.keys(): + if key == "authors": + authors_string = ",".join(str(trait[key]).split(",")[:2]) + ", et al." + self.max_widths[key] = max(len(authors_string), self.max_widths[key]) if key in self.max_widths else len(str(authors_string)) + elif key == "symbol": + self.max_widths[key] = len(trait[key]) + if len(trait[key]) > 20: + self.max_widths[key] = 20 + else: + self.max_widths[key] = max(len(str(trait[key])), self.max_widths[key]) if key in self.max_widths else len(str(trait[key])) + + self.wide_columns_exist = False + if self.dataset.type == "Publish": + if (self.max_widths['display_name'] > 25 or self.max_widths['description'] > 100 or self.max_widths['authors']> 80): + self.wide_columns_exist = True + if self.dataset.type == "ProbeSet": + if (self.max_widths['display_name'] > 25 or self.max_widths['symbol'] > 25 or self.max_widths['description'] > 100): + self.wide_columns_exist = True + + + self.trait_list = trait_list + + def search(self): + """ + This function sets up the actual search query in the form of a SQL statement and executes + + """ + self.search_terms = parser.parse(self.search_terms) + + combined_from_clause = "" + combined_where_clause = "" + # The same table can't be referenced twice in the from clause + previous_from_clauses = [] + + for i, a_search in enumerate(self.search_terms): + if a_search['key'] == "GO": + self.go_term = a_search['search_term'][0] + gene_list = get_GO_symbols(a_search) + self.search_terms += gene_list + continue + else: + the_search = self.get_search_ob(a_search) + if the_search != None: + if a_search['key'] == None and self.dataset.type == "ProbeSet": + alias_terms = get_alias_terms(a_search['search_term'][0], self.dataset.group.species) + alias_where_clauses = [] + for alias_search in alias_terms: + alias_search_ob = self.get_search_ob(alias_search) + if alias_search_ob != None: + get_from_clause = getattr( + alias_search_ob, "get_from_clause", None) + if callable(get_from_clause): + from_clause = alias_search_ob.get_from_clause() + if from_clause in previous_from_clauses: + pass + else: + previous_from_clauses.append(from_clause) + combined_from_clause += from_clause + where_clause = alias_search_ob.get_alias_where_clause() + alias_where_clauses.append(where_clause) + + get_from_clause = getattr( + the_search, "get_from_clause", None) + if callable(get_from_clause): + from_clause = the_search.get_from_clause() + if from_clause in previous_from_clauses: + pass + else: + previous_from_clauses.append(from_clause) + combined_from_clause += from_clause + + where_clause = the_search.get_where_clause() + alias_where_clauses.append(where_clause) + + combined_where_clause += "(" + " OR ".join(alias_where_clauses) + ")" + if (i + 1) < len(self.search_terms): + if self.and_or == "and": + combined_where_clause += "AND" + else: + combined_where_clause += "OR" + else: + get_from_clause = getattr( + the_search, "get_from_clause", None) + if callable(get_from_clause): + from_clause = the_search.get_from_clause() + if from_clause in previous_from_clauses: + pass + else: + previous_from_clauses.append(from_clause) + combined_from_clause += from_clause + + where_clause = the_search.get_where_clause() + combined_where_clause += "(" + where_clause + ")" + if (i + 1) < len(self.search_terms): + if self.and_or == "and": + combined_where_clause += "AND" + else: + combined_where_clause += "OR" + else: + self.search_term_exists = False + + if self.search_term_exists: + combined_where_clause = "(" + combined_where_clause + ")" + final_query = the_search.compile_final_query( + combined_from_clause, combined_where_clause) + + results = the_search.execute(final_query) + self.results.extend(results) + + if self.search_term_exists: + if the_search != None: + self.header_fields = the_search.header_fields + + def get_search_ob(self, a_search): + search_term = a_search['search_term'] + search_operator = a_search['separator'] + search_type = {} + search_type['dataset_type'] = self.dataset.type + if a_search['key']: + search_type['key'] = a_search['key'].upper() + else: + search_type['key'] = None + + search_ob = do_search.DoSearch.get_search(search_type) + if search_ob: + search_class = getattr(do_search, search_ob) + the_search = search_class(search_term, + search_operator, + self.dataset, + search_type['key'] + ) + return the_search + else: + return None + +def trait_info_str(trait, dataset_type): + """Provide a string representation for given trait""" + def __trait_desc(trt): + if dataset_type == "Geno": + return f"Marker: {trait['display_name']}" + return trait['description'] or "N/A" + + def __symbol(trt): + if dataset_type == "ProbeSet": + return (trait['symbol'] or "N/A")[:20] + + def __lrs(trt): + if dataset_type == "Geno": + return 0 + else: + if trait['lod_score'] != "N/A": + return ( + f"{float(trait['lod_score']):0.3f}" if float(trait['lod_score']) > 0 + else f"{trait['lod_score']}") + else: + return "N/A" + + def __lrs_location(trt): + if 'lrs_location' in trait: + return trait['lrs_location'] + else: + return "N/A" + + def __location(trt): + if 'location' in trait: + return trait['location'] + else: + return None + + def __mean(trt): + if 'mean' in trait: + return trait['mean'] + else: + return 0 + + return "{}|||{}|||{}|||{}|||{}|||{}|||{}|||{}".format( + trait['display_name'], trait['dataset'], __trait_desc(trait), __symbol(trait), + __location(trait), __mean(trait), __lrs(trait), __lrs_location(trait)) + +def get_GO_symbols(a_search): + gene_list = None + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute("SELECT genes FROM GORef WHERE goterm=%s", + (f"{a_search['key']}:{a_search['search_term'][0]}",)) + gene_list = cursor.fetchone()[0].strip().split() + + new_terms = [] + for gene in gene_list: + new_terms.append(dict(key=None, separator=None, search_term=[gene])) + + return new_terms + + +def insert_newlines(string, every=64): + """ This is because it is seemingly impossible to change the width of the description column, so I'm just manually adding line breaks """ + lines = [] + for i in range(0, len(string), every): + lines.append(string[i:i + every]) + return '\n'.join(lines) + + +def get_alias_terms(symbol, species): + if species == "mouse": + symbol_string = symbol.capitalize() + elif species == "human": + symbol_string = symbol.upper() + else: + return [] + + filtered_aliases = [] + response = requests.get( + GN2_BASE_URL + "/gn3/gene/aliases/" + symbol_string) + if response: + alias_list = json.loads(response.content) + + seen = set() + for item in alias_list: + if item in seen: + continue + else: + filtered_aliases.append(item) + seen.add(item) + + alias_terms = [] + for alias in filtered_aliases: + the_search_term = {'key': None, + 'search_term': [alias], + 'separator': None} + alias_terms.append(the_search_term) + + return alias_terms diff --git a/gn2/wqflask/send_mail.py b/gn2/wqflask/send_mail.py new file mode 100644 index 00000000..299c866a --- /dev/null +++ b/gn2/wqflask/send_mail.py @@ -0,0 +1,51 @@ +import datetime +import time + +import simplejson as json + +from redis import StrictRedis +Redis = StrictRedis() + +import mailer + + +def timestamp(): + ts = datetime.datetime.utcnow() + return ts.isoformat() + + +def main(): + while True: + print("I'm alive!") + + # Set something so we know it's running (or at least been running recently) + Redis.setex("send_mail:ping", 300, time.time()) + + msg = Redis.blpop("mail_queue", 30) + + if msg: + # Queue name is the first element, we want the second, which is the actual message + msg = msg[1] + + print("\n\nGot a msg in queue at {}: {}".format(timestamp(), msg)) + # Todo: Truncate mail_processed when it gets to long + Redis.rpush("mail_processed", msg) + process_message(msg) + + +def process_message(msg): + msg = json.loads(msg) + + message = mailer.Message() + message.From = msg['From'] + message.To = msg['To'] + message.Subject = msg['Subject'] + message.Body = msg['Body'] + + sender = mailer.Mailer('localhost') + sender.send(message) + print("Sent message at {}: {}\n".format(timestamp(), msg)) + + +if __name__ == '__main__': + main() diff --git a/gn2/wqflask/server_side.py b/gn2/wqflask/server_side.py new file mode 100644 index 00000000..e661c407 --- /dev/null +++ b/gn2/wqflask/server_side.py @@ -0,0 +1,90 @@ +# handles server side table processing + + +class ServerSideTable: + """ + This class is used to do server-side processing + on the DataTables table such as paginating, sorting, + filtering(not implemented) etc. This takes the load off + the client-side and reduces the size of data interchanged. + + Usage: + ServerSideTable(rows_count, table_rows, header_data_names, request_values) + where, + `rows_count` as number of rows in the table, + `table_rows` as data rows of the table, + `header_data_names` as headers names of the table. + `request_values` must have request arguments values + including the DataTables server-side processing arguments. + + Have a look at snp_browser_table() function in + wqflask/wqflask/views.py for reference use. + """ + + def __init__(self, rows_count, table_rows, header_data_names, request_values): + self.request_values = request_values + self.sEcho = self.request_values['sEcho'] + + self.rows_count = rows_count + self.table_rows = table_rows + self.header_data_names = header_data_names + + self.sort_rows() + self.paginate_rows() + + def sort_rows(self): + """ + Sorts the rows taking in to account the column (or columns) that the + user has selected. + """ + def is_reverse(str_direction): + """ Maps the 'desc' and 'asc' words to True or False. """ + return True if str_direction == 'desc' else False + + if (self.request_values['iSortCol_0'] != "") and (int(self.request_values['iSortingCols']) > 0): + for i in range(0, int(self.request_values['iSortingCols'])): + column_number = int(self.request_values['iSortCol_' + str(i)]) + column_name = self.header_data_names[column_number - 1] + sort_direction = self.request_values['sSortDir_' + str(i)] + self.table_rows = sorted(self.table_rows, + key=lambda x: x[column_name], + reverse=is_reverse(sort_direction)) + + def paginate_rows(self): + """ + Selects a subset of the filtered and sorted data based on if the table + has pagination, the current page and the size of each page. + """ + def requires_pagination(): + """ Check if the table is going to be paginated """ + if self.request_values['iDisplayStart'] != "": + if int(self.request_values['iDisplayLength']) != -1: + return True + return False + + if not requires_pagination(): + return + + start = int(self.request_values['iDisplayStart']) + length = int(self.request_values['iDisplayLength']) + + # if search returns only one page + if len(self.table_rows) <= length: + # display only one page + self.table_rows = self.table_rows[start:] + else: + limit = -len(self.table_rows) + start + length + if limit < 0: + # display pagination + self.table_rows = self.table_rows[start:limit] + else: + # display last page of pagination + self.table_rows = self.table_rows[start:] + + def get_page(self): + output = {} + output['sEcho'] = str(self.sEcho) + output['iTotalRecords'] = str(float('Nan')) + output['iTotalDisplayRecords'] = str(self.rows_count) + output['data'] = self.table_rows + return output diff --git a/gn2/wqflask/show_trait/SampleList.py b/gn2/wqflask/show_trait/SampleList.py new file mode 100644 index 00000000..64fc8fe6 --- /dev/null +++ b/gn2/wqflask/show_trait/SampleList.py @@ -0,0 +1,223 @@ +import re +import itertools + +from gn2.wqflask.database import database_connection +from gn2.base import webqtlCaseData, webqtlConfig +from pprint import pformat as pf + +from gn2.utility import Plot +from gn2.utility import Bunch +from gn2.utility.tools import get_setting + +class SampleList: + def __init__(self, + dataset, + sample_names, + this_trait, + sample_group_type="primary", + header="Samples"): + + self.dataset = dataset + self.this_trait = this_trait + self.sample_group_type = sample_group_type # primary or other + self.header = header + + self.sample_list = [] # The actual list + self.sample_attribute_values = {} + + self.get_attributes() + + if self.this_trait and self.dataset: + self.get_extra_attribute_values() + + for counter, sample_name in enumerate(sample_names, 1): + sample_name = sample_name.replace("_2nd_", "") + + # self.this_trait will be a list if it is a Temp trait + if isinstance(self.this_trait, list): + sample = webqtlCaseData.webqtlCaseData(name=sample_name) + if counter <= len(self.this_trait): + if isinstance(self.this_trait[counter - 1], (bytes, bytearray)): + if (self.this_trait[counter - 1].decode("utf-8").lower() != 'x'): + sample = webqtlCaseData.webqtlCaseData( + name=sample_name, + value=float(self.this_trait[counter - 1])) + else: + if (self.this_trait[counter - 1].lower() != 'x'): + sample = webqtlCaseData.webqtlCaseData( + name=sample_name, + value=float(self.this_trait[counter - 1])) + else: + # If there's no value for the sample/strain, + # create the sample object (so samples with no value + # are still displayed in the table) + try: + sample = self.this_trait.data[sample_name] + except KeyError: + sample = webqtlCaseData.webqtlCaseData(name=sample_name) + + sample.extra_info = {} + if (self.dataset.group.name == 'AXBXA' + and sample_name in ('AXB18/19/20', 'AXB13/14', 'BXA8/17')): + sample.extra_info['url'] = "/mouseCross.html#AXB/BXA" + sample.extra_info['css_class'] = "fs12" + + sample.this_id = str(counter) + + # For extra attribute columns; currently only used by + # several datasets + if self.sample_attribute_values: + sample.extra_attributes = self.sample_attribute_values.get( + sample_name, {}) + + # Add a url so RRID case attributes can be displayed as links + if '36' in sample.extra_attributes: + rrid_string = str(sample.extra_attributes['36']) + if self.dataset.group.species == "mouse": + if len(rrid_string.split(":")) > 1: + the_rrid = rrid_string.split(":")[1] + sample.extra_attributes['36'] = [ + rrid_string] + sample.extra_attributes['36'].append( + webqtlConfig.RRID_MOUSE_URL % the_rrid) + elif self.dataset.group.species == "rat": + if len(rrid_string): + the_rrid = rrid_string.split("_")[1] + sample.extra_attributes['36'] = [ + rrid_string] + sample.extra_attributes['36'].append( + webqtlConfig.RRID_RAT_URL % the_rrid) + + self.sample_list.append(sample) + + self.se_exists = any(sample.variance for sample in self.sample_list) + self.num_cases_exists = False + if (any(sample.num_cases for sample in self.sample_list) and + any((sample.num_cases and sample.num_cases != "1") for sample in self.sample_list)): + self.num_cases_exists = True + + first_attr_col = self.get_first_attr_col() + for sample in self.sample_list: + sample.first_attr_col = first_attr_col + + self.do_outliers() + + def __repr__(self): + return "<SampleList> --> %s" % (pf(self.__dict__)) + + def do_outliers(self): + values = [sample.value for sample in self.sample_list + if sample.value is not None] + upper_bound, lower_bound = Plot.find_outliers(values) + + for sample in self.sample_list: + if sample.value: + if upper_bound and sample.value > upper_bound: + sample.outlier = True + elif lower_bound and sample.value < lower_bound: + sample.outlier = True + else: + sample.outlier = False + + def get_attributes(self): + """Finds which extra attributes apply to this dataset""" + + # Get attribute names and distinct values for each attribute + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT DISTINCT CaseAttribute.CaseAttributeId, " + "CaseAttribute.Name, CaseAttribute.Description, " + "CaseAttributeXRefNew.Value FROM " + "CaseAttribute, CaseAttributeXRefNew WHERE " + "CaseAttributeXRefNew.CaseAttributeId = CaseAttribute.CaseAttributeId " + "AND CaseAttributeXRefNew.InbredSetId = %s " + "ORDER BY CaseAttribute.CaseAttributeId", (str(self.dataset.group.id),) + ) + + self.attributes = {} + for attr, values in itertools.groupby( + cursor.fetchall(), lambda row: (row[0], row[1], row[2]) + ): + key, name, description = attr + self.attributes[key] = Bunch() + self.attributes[key].id = key + self.attributes[key].name = name + self.attributes[key].description = description + self.attributes[key].distinct_values = [ + item[3] for item in values] + self.attributes[key].distinct_values = natural_sort( + self.attributes[key].distinct_values) + all_numbers = True + for value in self.attributes[key].distinct_values: + try: + val_as_float = float(value) + except: + all_numbers = False + break + + if all_numbers: + self.attributes[key].alignment = "right" + else: + self.attributes[key].alignment = "left" + + def get_extra_attribute_values(self): + if self.attributes: + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT Strain.Name AS SampleName, " + "CaseAttributeId AS Id, " + "CaseAttributeXRefNew.Value FROM Strain, " + "StrainXRef, InbredSet, CaseAttributeXRefNew " + "WHERE StrainXRef.StrainId = Strain.Id " + "AND InbredSet.Id = StrainXRef.InbredSetId " + "AND CaseAttributeXRefNew.StrainId = Strain.Id " + "AND InbredSet.Id = CaseAttributeXRefNew.InbredSetId " + "AND CaseAttributeXRefNew.InbredSetId = %s " + "ORDER BY SampleName", (self.dataset.group.id,) + ) + + for sample_name, items in itertools.groupby( + cursor.fetchall(), lambda row: row[0] + ): + attribute_values = {} + # Make a list of attr IDs without values (that have values for other samples) + valueless_attr_ids = [self.attributes[key].id for key in self.attributes.keys()] + for item in items: + sample_name, _id, value = item + valueless_attr_ids.remove(_id) + attribute_value = value + + # If it's an int, turn it into one for sorting + # (for example, 101 would be lower than 80 if + # they're strings instead of ints) + try: + attribute_value = int(attribute_value) + except ValueError: + pass + + attribute_values[str(_id)] = attribute_value + for attr_id in valueless_attr_ids: + attribute_values[str(attr_id)] = "" + + self.sample_attribute_values[sample_name] = attribute_values + + def get_first_attr_col(self): + first_attr_col = 4 + if self.se_exists: + first_attr_col += 2 + if self.num_cases_exists: + first_attr_col += 1 + + return first_attr_col + + +def natural_sort(a_list, key=lambda s: s): + """ + Sort the list into natural alphanumeric order. + """ + def get_alphanum_key_func(key): + def convert(text): return int(text) if text.isdigit() else text + return lambda s: [convert(c) for c in re.split('([0-9]+)', key(s))] + sort_key = get_alphanum_key_func(key) + sorted_list = sorted(a_list, key=sort_key) + return sorted_list diff --git a/gn2/wqflask/show_trait/__init__.py b/gn2/wqflask/show_trait/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/show_trait/__init__.py diff --git a/gn2/wqflask/show_trait/export_trait_data.py b/gn2/wqflask/show_trait/export_trait_data.py new file mode 100644 index 00000000..06c1c502 --- /dev/null +++ b/gn2/wqflask/show_trait/export_trait_data.py @@ -0,0 +1,113 @@ +import datetime +import simplejson as json + +from pprint import pformat as pf +from functools import cmp_to_key +from gn2.base.trait import create_trait +from gn2.base import data_set + + +def export_sample_table(targs): + + sample_data = json.loads(targs['export_data']) + trait_name = targs['trait_display_name'] + + meta_data = get_export_metadata(targs) + + final_sample_data = meta_data + + column_headers = ["Index", "Name", "Value"] + attr_pos = 2 + if any(sample["se"] for sample in sample_data['primary_samples']): + column_headers.append("SE") + attr_pos = 3 + if any(sample["num_cases"] for sample in sample_data['primary_samples']): + column_headers.append("N") + attr_pos = 4 + + for key in sample_data["primary_samples"][0].keys(): + if key not in ["name", "value", "se", "num_cases"]: + column_headers.append(key) + + final_sample_data.append(column_headers) + for sample_group in ['primary_samples', 'other_samples']: + for i, row in enumerate(sample_data[sample_group]): + sorted_row = [i + 1] + dict_to_sorted_list(row)[:attr_pos] + for attr in sample_data['attributes']: + sorted_row.append(row[attr]) + final_sample_data.append(sorted_row) + + return trait_name, final_sample_data + + +def get_export_metadata(trait_metadata): + + trait_id, display_name, dataset_name, group_name = trait_metadata['trait_id'], trait_metadata['trait_display_name'], trait_metadata['dataset'], trait_metadata['group'] + + dataset = data_set.create_dataset(dataset_name, group_name=group_name) + this_trait = create_trait(dataset=dataset, + name=trait_id, + cellid=None, + get_qtl_info=False) + + metadata = [] + if dataset.type == "Publish": + metadata.append(["Phenotype ID:", display_name]) + metadata.append(["Phenotype URL: ", "http://genenetwork.org/show_trait?trait_id=" + \ + trait_id + "&dataset=" + dataset_name]) + metadata.append(["Group: ", dataset.group.name]) + metadata.append( + ["Phenotype: ", this_trait.description_display.replace(",", "\",\"")]) + metadata.append( + ["Authors: ", (this_trait.authors if this_trait.authors else "N/A")]) + metadata.append( + ["Title: ", (this_trait.title if this_trait.title else "N/A")]) + metadata.append( + ["Journal: ", (this_trait.journal if this_trait.journal else "N/A")]) + + metadata.append( + ["Dataset Link: ", "http://gn1.genenetwork.org/webqtl/main.py?FormID=sharinginfo&InfoPageName=" + dataset.name]) + else: + metadata.append(["Record ID: ", trait_id]) + metadata.append(["Trait URL: ", "http://genenetwork.org/show_trait?trait_id=" + \ + trait_id + "&dataset=" + dataset_name]) + if this_trait.symbol: + metadata.append(["Symbol: ", this_trait.symbol]) + metadata.append(["Dataset: ", dataset.name]) + metadata.append(["Group: ", dataset.group.name]) + metadata.append( + ["Export Date: ", datetime.datetime.now().strftime("%B %d, %Y")]) + metadata.append( + ["Export Time: ", datetime.datetime.now().strftime("%H:%M GMT")]) + + + return metadata + + +def dict_to_sorted_list(dictionary): + sorted_list = [item for item in list(dictionary.items())] + sorted_list = sorted(sorted_list, key=cmp_to_key(cmp_samples)) + sorted_values = [item[1] for item in sorted_list] + return sorted_values + + +def cmp_samples(a, b): + if b[0] == 'name': + return 1 + elif b[0] == 'value': + if a[0] == 'name': + return -1 + else: + return 1 + elif b[0] == 'se': + if a[0] == 'name' or a[0] == 'value': + return -1 + else: + return 1 + elif b[0] == 'num_cases': + if a[0] == 'name' or a[0] == 'value' or a[0] == 'se': + return -1 + else: + return 1 + else: + return -1 diff --git a/gn2/wqflask/show_trait/show_trait.py b/gn2/wqflask/show_trait/show_trait.py new file mode 100644 index 00000000..64d9be3b --- /dev/null +++ b/gn2/wqflask/show_trait/show_trait.py @@ -0,0 +1,859 @@ +from typing import Dict + +import string +import datetime +import uuid +import requests +import json as json + +from collections import OrderedDict + +import numpy as np +import scipy.stats as ss + +from gn2.wqflask.database import database_connection + +from gn2.base import webqtlConfig +from gn2.wqflask.show_trait.SampleList import SampleList +from gn2.base.trait import create_trait +from gn2.base import data_set +from gn2.utility import helper_functions +from gn2.utility.tools import get_setting, locate_ignore_error +from gn2.utility.tools import GN_PROXY_URL +from gn2.utility.redis_tools import get_redis_conn, get_resource_id + +from gn3.authentication import get_highest_user_access_role + +Redis = get_redis_conn() +ONE_YEAR = 60 * 60 * 24 * 365 + + +############################################### +# +# Todo: Put in security to ensure that user has permission to access +# confidential data sets And add i.p.limiting as necessary +# +############################################## + + +class ShowTrait: + def __init__(self, db_cursor, user_id, kw): + if 'trait_id' in kw and kw['dataset'] != "Temp": + self.temp_trait = False + self.trait_id = kw['trait_id'] + helper_functions.get_species_dataset_trait(self, kw) + self.resource_id = get_resource_id(self.dataset, + self.trait_id) + elif 'group' in kw: + self.temp_trait = True + self.trait_id = "Temp_" + kw['species'] + "_" + kw['group'] + \ + "_" + datetime.datetime.now().strftime("%m%d%H%M%S") + self.temp_species = kw['species'] + self.temp_group = kw['group'] + self.dataset = data_set.create_dataset( + dataset_name="Temp", dataset_type="Temp", group_name=self.temp_group) + + # Put values in Redis so they can be looked up later if + # added to a collection + Redis.set(self.trait_id, kw['trait_paste'], ex=ONE_YEAR) + self.trait_vals = kw['trait_paste'].split() + self.this_trait = create_trait(dataset=self.dataset, + name=self.trait_id, + cellid=None) + else: + self.temp_trait = True + self.trait_id = kw['trait_id'] + self.temp_species = self.trait_id.split("_")[1] + self.temp_group = self.trait_id.split("_")[2] + self.dataset = data_set.create_dataset( + dataset_name="Temp", dataset_type="Temp", group_name=self.temp_group) + self.this_trait = create_trait(dataset=self.dataset, + name=self.trait_id, + cellid=None) + self.trait_vals = Redis.get(self.trait_id).split() + + # Get verify/rna-seq link URLs + try: + blatsequence = self.this_trait.blatseq if self.dataset.type == "ProbeSet" else self.this_trait.sequence + if not blatsequence: + # XZ, 06/03/2009: ProbeSet name is not unique among platforms. We should use ProbeSet Id instead. + seqs = () + if self.dataset.type == "ProbeSet": + db_cursor.execute( + "SELECT Probe.Sequence, Probe.Name " + "FROM Probe, ProbeSet, ProbeSetFreeze, " + "ProbeSetXRef WHERE " + "ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id " + "AND ProbeSetXRef.ProbeSetId = ProbeSet.Id AND " + "ProbeSetFreeze.Name = %s AND " + "ProbeSet.Name = %s AND " + "Probe.ProbeSetId = ProbeSet.Id ORDER " + "BY Probe.SerialOrder", + (self.this_trait.dataset.name, self.this_trait.name,) + ) + else: + db_cursor.execute( + "SELECT Geno.Sequence " + "FROM Geno, GenoXRef, GenoFreeze " + "WHERE Geno.Name = %s AND " + "Geno.Id = GenoXRef.GenoId AND " + "GenoXRef.GenoFreezeId = GenoFreeze.Id AND " + "GenoFreeze.Name = %s", + (self.this_trait.name, self.this_trait.dataset.name) + ) + seqs = db_cursor.fetchall() + if not seqs: + raise ValueError + else: + blatsequence = '' + for seqt in seqs: + if int(seqt[1][-1]) % 2 == 1: + blatsequence += string.strip(seqt[0]) + + # --------Hongqiang add this part in order to not only blat ProbeSet, but also blat Probe + blatsequence = '%3E' + self.this_trait.name + '%0A' + blatsequence + '%0A' + + # XZ, 06/03/2009: ProbeSet name is not unique among platforms. We should use ProbeSet Id instead. + seqs = () + db_cursor.execute( + "SELECT Probe.Sequence, Probe.Name " + "FROM Probe, ProbeSet, ProbeSetFreeze, " + "ProbeSetXRef WHERE " + "ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id " + "AND ProbeSetXRef.ProbeSetId = ProbeSet.Id AND " + "ProbeSetFreeze.Name = %s AND ProbeSet.Name = %s " + "AND Probe.ProbeSetId = ProbeSet.Id " + "ORDER BY Probe.SerialOrder", + (self.this_trait.dataset.name, self.this_trait.name,) + ) + seqs = db_cursor.fetchall() + for seqt in seqs: + if int(seqt[1][-1]) % 2 == 1: + blatsequence += '%3EProbe_' + \ + seqt[1].strip() + '%0A' + seqt[0].strip() + '%0A' + + if self.dataset.group.species == "rat": + self.UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ( + 'rat', 'rn7', blatsequence) + self.UTHSC_BLAT_URL = "" + elif self.dataset.group.species == "mouse": + self.UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ( + 'mouse', 'mm10', blatsequence) + self.UTHSC_BLAT_URL = webqtlConfig.UTHSC_BLAT % ( + 'mouse', 'mm10', blatsequence) + elif self.dataset.group.species == "human": + self.UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ( + 'human', 'hg38', blatsequence) + self.UTHSC_BLAT_URL = "" + else: + self.UCSC_BLAT_URL = "" + self.UTHSC_BLAT_URL = "" + except: + self.UCSC_BLAT_URL = "" + self.UTHSC_BLAT_URL = "" + + if self.dataset.type == "ProbeSet": + self.show_probes = "True" + + trait_units = get_trait_units(self.this_trait) + self.get_external_links() + self.build_correlation_tools() + + self.ncbi_summary = get_ncbi_summary(self.this_trait) + + # Get nearest marker for composite mapping + if not self.temp_trait: + if check_if_attr_exists(self.this_trait, 'locus_chr') and self.dataset.type != "Geno" and self.dataset.type != "Publish": + self.nearest_marker = get_nearest_marker( + self.this_trait, self.dataset) + else: + self.nearest_marker = "" + + self.make_sample_lists() + + trait_vals_by_group = [] + for sample_type in self.sample_groups: + trait_vals_by_group.append(get_trait_vals(sample_type.sample_list)) + + self.max_digits_by_group, self.no_decimal_place = get_max_digits(trait_vals_by_group) + + self.qnorm_vals = quantile_normalize_vals(self.sample_groups, trait_vals_by_group) + self.z_scores = get_z_scores(self.sample_groups, trait_vals_by_group) + + self.temp_uuid = uuid.uuid4() + + self.sample_group_types = OrderedDict() + if len(self.sample_groups) > 1: + self.sample_group_types['samples_primary'] = self.dataset.group.name + self.sample_group_types['samples_other'] = "Other" + self.sample_group_types['samples_all'] = "All" + else: + self.sample_group_types['samples_primary'] = self.dataset.group.name + sample_lists = [group.sample_list for group in self.sample_groups] + + self.categorical_var_list = [] + self.numerical_var_list = [] + if not self.temp_trait: + # ZS: Only using first samplelist, since I think mapping only uses those samples + self.categorical_var_list = get_categorical_variables( + self.this_trait, self.sample_groups[0]) + self.numerical_var_list = get_numerical_variables( + self.this_trait, self.sample_groups[0]) + + # ZS: Get list of chromosomes to select for mapping + self.chr_list = [["All", -1]] + for i, this_chr in enumerate(self.dataset.species.chromosomes.chromosomes(db_cursor)): + self.chr_list.append( + [self.dataset.species.chromosomes.chromosomes(db_cursor)[this_chr].name, i]) + + self.genofiles = self.dataset.group.get_genofiles() + study_samplelist_json = self.dataset.group.get_study_samplelists() + self.study_samplelists = [study["title"] for study in study_samplelist_json] + + # ZS: No need to grab scales from .geno file unless it's using + # a mapping method that reads .geno files + if "QTLReaper" or "R/qtl" in dataset.group.mapping_names: + if self.genofiles: + self.scales_in_geno = get_genotype_scales(self.genofiles) + else: + self.scales_in_geno = get_genotype_scales( + self.dataset.group.name + ".geno") + else: + self.scales_in_geno = {} + + self.has_num_cases = has_num_cases(self.this_trait) + + # ZS: Needed to know whether to display bar chart + get max + # sample name length in order to set table column width + self.num_values = 0 + # ZS: So it knows whether to display the Binary R/qtl mapping + # method, which doesn't work unless all values are 0 or 1 + self.binary = "true" + # ZS: Since we don't want to show log2 transform option for + # situations where it doesn't make sense + self.negative_vals_exist = "false" + max_samplename_width = 1 + for group in self.sample_groups: + for sample in group.sample_list: + if len(sample.name) > max_samplename_width: + max_samplename_width = len(sample.name) + if sample.display_value != "x": + self.num_values += 1 + if sample.display_value != 0 or sample.display_value != 1: + self.binary = "false" + if sample.value < 0: + self.negative_vals_exist = "true" + + # ZS: Check whether any attributes have few enough distinct + # values to show the "Block samples by group" option + self.categorical_attr_exists = "false" + for attribute in self.sample_groups[0].attributes: + if len(self.sample_groups[0].attributes[attribute].distinct_values) <= 10: + self.categorical_attr_exists = "true" + break + + sample_column_width = max_samplename_width * 8 + + self.stats_table_width, self.trait_table_width = get_table_widths( + self.sample_groups, sample_column_width, self.has_num_cases) + + if self.num_values >= 5000: + self.maf = 0.01 + else: + self.maf = 0.05 + + trait_symbol = None + short_description = None + if not self.temp_trait: + if self.this_trait.symbol: + trait_symbol = self.this_trait.symbol + short_description = trait_symbol + + elif hasattr(self.this_trait, 'post_publication_abbreviation'): + short_description = self.this_trait.post_publication_abbreviation + + elif hasattr(self.this_trait, 'pre_publication_abbreviation'): + short_description = self.this_trait.pre_publication_abbreviation + + # Todo: Add back in the ones we actually need from below, as we discover we need them + hddn = OrderedDict() + + if self.dataset.group.allsamples: + hddn['allsamples'] = ','.join(self.dataset.group.allsamples) + hddn['primary_samples'] = ','.join(self.primary_sample_names) + hddn['trait_id'] = self.trait_id + hddn['trait_display_name'] = self.this_trait.display_name + hddn['dataset'] = self.dataset.name + hddn['temp_trait'] = False + if self.temp_trait: + hddn['temp_trait'] = True + hddn['group'] = self.temp_group + hddn['species'] = self.temp_species + else: + hddn['group'] = self.dataset.group.name + hddn['species'] = self.dataset.group.species + hddn['use_outliers'] = False + hddn['method'] = "gemma" + hddn['mapmethod_rqtl'] = "hk" + hddn['mapmodel_rqtl'] = "normal" + hddn['pair_scan'] = "" + hddn['selected_chr'] = -1 + hddn['mapping_display_all'] = True + hddn['suggestive'] = 0 + hddn['study_samplelists'] = json.dumps(study_samplelist_json) + hddn['num_perm'] = 0 + hddn['categorical_vars'] = "" + if self.categorical_var_list: + hddn['categorical_vars'] = ",".join(self.categorical_var_list) + hddn['manhattan_plot'] = "" + hddn['control_marker'] = "" + if not self.temp_trait: + if hasattr(self.this_trait, 'locus_chr') and self.this_trait.locus_chr != "" and self.dataset.type != "Geno" and self.dataset.type != "Publish": + hddn['control_marker'] = self.nearest_marker + hddn['do_control'] = False + hddn['maf'] = 0.05 + hddn['mapping_scale'] = "physic" + hddn['compare_traits'] = [] + hddn['export_data'] = "" + hddn['export_format'] = "excel" + if len(self.scales_in_geno) < 2 and bool(self.scales_in_geno): + hddn['mapping_scale'] = self.scales_in_geno[list( + self.scales_in_geno.keys())[0]][0][0] + + # We'll need access to this_trait and hddn in the Jinja2 + # Template, so we put it inside self + self.hddn = hddn + + js_data = dict(trait_id=self.trait_id, + trait_symbol=trait_symbol, + max_digits = self.max_digits_by_group, + no_decimal_place = self.no_decimal_place, + short_description=short_description, + unit_type=trait_units, + dataset_type=self.dataset.type, + species=self.dataset.group.species, + scales_in_geno=self.scales_in_geno, + data_scale=self.dataset.data_scale, + sample_group_types=self.sample_group_types, + sample_lists=sample_lists, + se_exists=self.sample_groups[0].se_exists, + has_num_cases=self.has_num_cases, + attributes=self.sample_groups[0].attributes, + categorical_attr_exists=self.categorical_attr_exists, + categorical_vars=",".join(self.categorical_var_list), + num_values=self.num_values, + qnorm_values=self.qnorm_vals, + zscore_values=self.z_scores, + sample_column_width=sample_column_width, + temp_uuid=self.temp_uuid) + self.js_data = js_data + + def get_external_links(self): + # ZS: There's some weirdness here because some fields don't + # exist while others are empty strings + self.pubmed_link = webqtlConfig.PUBMEDLINK_URL % self.this_trait.pubmed_id if check_if_attr_exists( + self.this_trait, 'pubmed_id') else None + self.ncbi_gene_link = webqtlConfig.NCBI_LOCUSID % self.this_trait.geneid if check_if_attr_exists( + self.this_trait, 'geneid') else None + self.omim_link = webqtlConfig.OMIM_ID % self.this_trait.omim if check_if_attr_exists( + self.this_trait, 'omim') else None + self.homologene_link = webqtlConfig.HOMOLOGENE_ID % self.this_trait.homologeneid if check_if_attr_exists( + self.this_trait, 'homologeneid') else None + + self.genbank_link = None + if check_if_attr_exists(self.this_trait, 'genbankid'): + genbank_id = '|'.join(self.this_trait.genbankid.split('|')[0:10]) + if genbank_id[-1] == '|': + genbank_id = genbank_id[0:-1] + self.genbank_link = webqtlConfig.GENBANK_ID % genbank_id + + self.uniprot_link = None + if check_if_attr_exists(self.this_trait, 'uniprotid'): + self.uniprot_link = webqtlConfig.UNIPROT_URL % self.this_trait.uniprotid + + self.genotation_link = self.rgd_link = self.phenogen_link = self.gtex_link = self.genebridge_link = self.ucsc_blat_link = self.biogps_link = self.protein_atlas_link = None + self.string_link = self.panther_link = self.aba_link = self.ebi_gwas_link = self.wiki_pi_link = self.genemania_link = self.ensembl_link = None + if self.this_trait.symbol: + self.genotation_link = webqtlConfig.GENOTATION_URL % self.this_trait.symbol + self.gtex_link = webqtlConfig.GTEX_URL % self.this_trait.symbol + self.string_link = webqtlConfig.STRING_URL % self.this_trait.symbol + self.panther_link = webqtlConfig.PANTHER_URL % self.this_trait.symbol + self.ebi_gwas_link = webqtlConfig.EBIGWAS_URL % self.this_trait.symbol + self.protein_atlas_link = webqtlConfig.PROTEIN_ATLAS_URL % self.this_trait.symbol + + if self.dataset.group.species == "mouse" or self.dataset.group.species == "human": + self.rgd_link = webqtlConfig.RGD_URL % ( + self.this_trait.symbol, self.dataset.group.species.capitalize()) + if self.dataset.group.species == "mouse": + self.genemania_link = webqtlConfig.GENEMANIA_URL % ( + "mus-musculus", self.this_trait.symbol) + else: + self.genemania_link = webqtlConfig.GENEMANIA_URL % ( + "homo-sapiens", self.this_trait.symbol) + + if self.dataset.group.species == "mouse": + self.aba_link = webqtlConfig.ABA_URL % self.this_trait.symbol + results = () + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT chromosome, txStart, txEnd FROM " + "GeneList WHERE geneSymbol = %s", + (self.this_trait.symbol,) + ) + results = cursor.fetchone() + if results: + chr, transcript_start, transcript_end = results + else: + chr = transcript_start = transcript_end = None + + if chr and transcript_start and transcript_end and self.this_trait.refseq_transcriptid: + transcript_start = int(transcript_start * 1000000) + transcript_end = int(transcript_end * 1000000) + self.ucsc_blat_link = webqtlConfig.UCSC_REFSEQ % ( + 'mm10', self.this_trait.refseq_transcriptid, chr, transcript_start, transcript_end) + + if self.dataset.group.species == "rat": + self.rgd_link = webqtlConfig.RGD_URL % ( + self.this_trait.symbol, self.dataset.group.species.capitalize()) + self.phenogen_link = webqtlConfig.PHENOGEN_URL % ( + self.this_trait.symbol) + self.genemania_link = webqtlConfig.GENEMANIA_URL % ( + "rattus-norvegicus", self.this_trait.symbol) + + results = () + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT kgID, chromosome, txStart, txEnd " + "FROM GeneList_rn33 WHERE geneSymbol = %s", + (self.this_trait.symbol,) + ) + if results := cursor.fetchone(): + kgId, chr, transcript_start, transcript_end = results + else: + kgId = chr = transcript_start = transcript_end = None + + if chr and transcript_start and transcript_end and kgId: + # Convert to bases from megabases + transcript_start = int(transcript_start * 1000000) + transcript_end = int(transcript_end * 1000000) + self.ucsc_blat_link = webqtlConfig.UCSC_REFSEQ % ( + 'rn7', kgId, chr, transcript_start, transcript_end) + + if self.this_trait.geneid and (self.dataset.group.species == "mouse" or self.dataset.group.species == "rat" or self.dataset.group.species == "human"): + self.biogps_link = webqtlConfig.BIOGPS_URL % ( + self.dataset.group.species, self.this_trait.geneid) + self.gemma_link = webqtlConfig.GEMMA_URL % self.this_trait.geneid + + if self.dataset.group.species == "human": + self.aba_link = webqtlConfig.ABA_URL % self.this_trait.geneid + + def build_correlation_tools(self): + if self.temp_trait == True: + this_group = self.temp_group + else: + this_group = self.dataset.group.name + + # We're checking a string here! + assert isinstance(this_group, str), "We need a string type thing here" + if this_group[:3] == 'BXD' and this_group not in webqtlConfig.BXD_GROUP_EXCEPTIONS: + this_group = 'BXD' + + if this_group: + if self.temp_trait == True: + dataset_menu = data_set.datasets(this_group) + else: + dataset_menu = data_set.datasets( + this_group, self.dataset.group) + dataset_menu_selected = None + if len(dataset_menu): + if self.dataset: + dataset_menu_selected = self.dataset.name + + return_results_menu = (100, 200, 500, 1000, + 2000, 5000, 10000, 15000, 20000) + return_results_menu_selected = 500 + + self.corr_tools = dict(dataset_menu=dataset_menu, + dataset_menu_selected=dataset_menu_selected, + return_results_menu=return_results_menu, + return_results_menu_selected=return_results_menu_selected,) + + def make_sample_lists(self): + + all_samples_ordered = self.dataset.group.all_samples_ordered() + + parent_f1_samples = [] + if self.dataset.group.parlist and self.dataset.group.f1list: + parent_f1_samples = self.dataset.group.parlist + self.dataset.group.f1list + + primary_sample_names = list(all_samples_ordered) + + if not self.temp_trait: + other_sample_names = [] + + for sample in list(self.this_trait.data.keys()): + if (self.this_trait.data[sample].name2 != self.this_trait.data[sample].name): + if ((self.this_trait.data[sample].name2 in primary_sample_names) + and (self.this_trait.data[sample].name not in primary_sample_names)): + primary_sample_names.append( + self.this_trait.data[sample].name) + primary_sample_names.remove( + self.this_trait.data[sample].name2) + + all_samples_set = set(all_samples_ordered) + if sample not in all_samples_set: + all_samples_ordered.append(sample) + other_sample_names.append(sample) + + # CFW is here because the .geno file doesn't properly + # contain its full list of samples. This should probably + # be fixed. + if self.dataset.group.species == "human" or (set(primary_sample_names) == set(parent_f1_samples)) or self.dataset.group.name == "CFW": + primary_sample_names += other_sample_names + other_sample_names = [] + + if other_sample_names: + primary_header = "%s Only" % (self.dataset.group.name) + else: + primary_header = "Samples" + + primary_samples = SampleList(dataset=self.dataset, + sample_names=primary_sample_names, + this_trait=self.this_trait, + sample_group_type='primary', + header=primary_header) + + # if other_sample_names and self.dataset.group.species != + # "human" and self.dataset.group.name != "CFW": + if len(other_sample_names) > 0: + other_sample_names.sort() # Sort other samples + other_samples = SampleList(dataset=self.dataset, + sample_names=other_sample_names, + this_trait=self.this_trait, + sample_group_type='other', + header="Other") + + self.sample_groups = (primary_samples, other_samples) + else: + self.sample_groups = (primary_samples,) + else: + primary_samples = SampleList(dataset=self.dataset, + sample_names=primary_sample_names, + this_trait=self.trait_vals, + sample_group_type='primary', + header="%s Only" % (self.dataset.group.name)) + self.sample_groups = (primary_samples,) + self.primary_sample_names = primary_sample_names + self.dataset.group.allsamples = all_samples_ordered + + +def get_trait_vals(sample_list): + trait_vals = [] + for sample in sample_list: + try: + trait_vals.append(float(sample.value)) + except: + continue + + return trait_vals + +def get_max_digits(trait_vals): + def __max_digits__(these_vals): + if not bool(these_vals): + return None + return len(str(max(these_vals))) - 1 + + return [__max_digits__(val) for val in trait_vals], all([val.is_integer() for val in sum(trait_vals, [])]) + +def normf(trait_vals): + ranked_vals = ss.rankdata(trait_vals) + p_list = [] + for i, val in enumerate(trait_vals): + p_list.append(((i + 1) - 0.5) / len(trait_vals)) + + z = ss.norm.ppf(p_list) + + normed_vals = [] + for rank in ranked_vals: + normed_vals.append("%0.3f" % z[int(rank) - 1]) + + return normed_vals + +def quantile_normalize_vals(sample_groups, trait_vals): + qnorm_by_group = [] + for i, sample_type in enumerate(sample_groups): + qnorm_vals = normf(trait_vals[i]) + qnorm_vals_with_x = [] + counter = 0 + for sample in sample_type.sample_list: + if sample.display_value == "x": + qnorm_vals_with_x.append("x") + else: + qnorm_vals_with_x.append(qnorm_vals[counter]) + counter += 1 + + qnorm_by_group.append(qnorm_vals_with_x) + + return qnorm_by_group + + +def get_z_scores(sample_groups, trait_vals): + zscore_by_group = [] + for i, sample_type in enumerate(sample_groups): + zscores = ss.mstats.zscore(np.array(trait_vals[i])).tolist() + zscores_with_x = [] + counter = 0 + for sample in sample_type.sample_list: + if sample.display_value == "x": + zscores_with_x.append("x") + else: + zscores_with_x.append("%0.3f" % zscores[counter]) + counter += 1 + + zscore_by_group.append(zscores_with_x) + + return zscore_by_group + + +def get_nearest_marker(this_trait, this_db): + this_chr = this_trait.locus_chr + this_mb = this_trait.locus_mb + # One option is to take flanking markers, another is to take the + # two (or one) closest + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute( + "SELECT Geno.Name FROM Geno, GenoXRef, " + "GenoFreeze WHERE Geno.Chr = %s AND " + "GenoXRef.GenoId = Geno.Id AND " + "GenoFreeze.Id = GenoXRef.GenoFreezeId " + "AND GenoFreeze.Name = %s ORDER BY " + "ABS( Geno.Mb - %s) LIMIT 1", + (this_chr, f"{this_db.group.name}Geno", this_mb,) + ) + if result := cursor.fetchall(): + return result[0][0] + return "" + + +def get_table_widths(sample_groups, sample_column_width, has_num_cases=False): + stats_table_width = 250 + if len(sample_groups) > 1: + stats_table_width = 450 + + trait_table_width = 300 + sample_column_width + if sample_groups[0].se_exists: + trait_table_width += 80 + if has_num_cases: + trait_table_width += 80 + trait_table_width += len(sample_groups[0].attributes) * 88 + + return stats_table_width, trait_table_width + + +def has_num_cases(this_trait): + has_n = False + if this_trait.dataset.type != "ProbeSet" and this_trait.dataset.type != "Geno": + for name, sample in list(this_trait.data.items()): + if sample.num_cases and sample.num_cases != "1": + has_n = True + break + + return has_n + + +def get_trait_units(this_trait): + unit_type = "" + inside_brackets = False + if this_trait.description_fmt: + if ("[" in this_trait.description_fmt) and ("]" in this_trait.description_fmt): + for i in this_trait.description_fmt: + if inside_brackets: + if i != "]": + unit_type += i + else: + inside_brackets = False + if i == "[": + inside_brackets = True + + if unit_type == "": + unit_type = "value" + + return unit_type + + +def check_if_attr_exists(the_trait, id_type): + if hasattr(the_trait, id_type): + if getattr(the_trait, id_type) == None or getattr(the_trait, id_type) == "": + return False + else: + return True + else: + return False + + +def get_ncbi_summary(this_trait): + if check_if_attr_exists(this_trait, 'geneid'): + # ZS: Need to switch this try/except to something that checks + # the output later + try: + response = requests.get( + "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=gene&id=%s&retmode=json" % this_trait.geneid) + summary = json.loads(response.content)[ + 'result'][this_trait.geneid]['summary'] + return summary + except: + return None + else: + return None + + +def get_categorical_variables(this_trait, sample_list) -> list: + categorical_var_list = [] + + if len(sample_list.attributes) > 0: + for attribute in sample_list.attributes: + if len(sample_list.attributes[attribute].distinct_values) < 10: + categorical_var_list.append(str(sample_list.attributes[attribute].id)) + + return categorical_var_list + +def get_numerical_variables(this_trait, sample_list) -> list: + numerical_var_list = [] + + if len(sample_list.attributes) > 0: + for attribute in sample_list.attributes: + all_numeric = True + all_none = True + for attr_val in sample_list.attributes[attribute].distinct_values: + if not attr_val: + continue + try: + val_as_float = float(attr_val) + all_none = False + except: + all_numeric = False + break + if all_numeric and not all_none: + numerical_var_list.append(sample_list.attributes[attribute].name) + + return numerical_var_list + +def get_genotype_scales(genofiles): + geno_scales = {} + if isinstance(genofiles, list): + for the_file in genofiles: + file_location = the_file['location'] + geno_scales[file_location] = get_scales_from_genofile( + file_location) + else: + geno_scales[genofiles] = get_scales_from_genofile(genofiles) + + return geno_scales + + +def get_scales_from_genofile(file_location): + geno_path = locate_ignore_error(file_location, 'genotype') + # ZS: This is just to allow the code to run when + if not geno_path: + return [["physic", "Mb"]] + cm_and_mb_cols_exist = True + cm_column = None + mb_column = None + with open(geno_path, "r") as geno_fh: + for i, line in enumerate(geno_fh): + if line[0] == "#" or line[0] == "@": + # ZS: If the scale is made explicit in the metadata, + # use that + if "@scale" in line: + scale = line.split(":")[1].strip() + if scale == "morgan": + return [["morgan", "cM"]] + else: + return [["physic", "Mb"]] + else: + continue + if line[:3] == "Chr": + first_marker_line = i + 1 + if line.split("\t")[2].strip() == "cM": + cm_column = 2 + elif line.split("\t")[3].strip() == "cM": + cm_column = 3 + if line.split("\t")[2].strip() == "Mb": + mb_column = 2 + elif line.split("\t")[3].strip() == "Mb": + mb_column = 3 + break + + # ZS: This attempts to check whether the cM and Mb columns are + # 'real', since some .geno files have one column be a copy of + # the other column, or have one column that is all 0s + cm_all_zero = True + mb_all_zero = True + cm_mb_all_equal = True + for i, line in enumerate(geno_fh): + # ZS: I'm assuming there won't be more than 10 markers + # where the position is listed as 0 + if first_marker_line <= i < first_marker_line + 10: + if cm_column: + cm_val = line.split("\t")[cm_column].strip() + if cm_val != "0": + cm_all_zero = False + if mb_column: + mb_val = line.split("\t")[mb_column].strip() + if mb_val != "0": + mb_all_zero = False + if cm_column and mb_column: + if cm_val != mb_val: + cm_mb_all_equal = False + else: + if i > first_marker_line + 10: + break + + # ZS: This assumes that both won't be all zero, since if that's + # the case mapping shouldn't be an option to begin with + if mb_all_zero: + return [["morgan", "cM"]] + elif cm_mb_all_equal: + return [["physic", "Mb"]] + elif cm_and_mb_cols_exist: + return [["physic", "Mb"], ["morgan", "cM"]] + else: + return [["physic", "Mb"]] + + + +def get_diff_of_vals(new_vals: Dict, trait_id: str) -> Dict: + """ Get the diff between current sample values and the values in the DB + + Given a dict of the changed values and the trait/dataset ID, return a Dict + with keys corresponding to each sample with a changed value and a value + that is a dict with keys for the old_value and new_value + + """ + + trait_name = trait_id.split(":")[0] + dataset_name = trait_id.split(":")[1] + trait_ob = create_trait(name=trait_name, dataset_name=dataset_name) + + old_vals = {sample : trait_ob.data[sample].value for sample in trait_ob.data} + + shared_samples = set.union(set(new_vals.keys()), set(old_vals.keys())) + + diff_dict = {} + for sample in shared_samples: + try: + new_val = round(float(new_vals[sample]), 3) + except: + new_val = "x" + try: + old_val = round(float(old_vals[sample]), 3) + except: + old_val = "x" + + if new_val != old_val: + diff_dict[sample] = { + "new_val": new_val, + "old_val": old_val + } + + return diff_dict diff --git a/gn2/wqflask/snp_browser/__init__.py b/gn2/wqflask/snp_browser/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/snp_browser/__init__.py diff --git a/gn2/wqflask/snp_browser/snp_browser.py b/gn2/wqflask/snp_browser/snp_browser.py new file mode 100644 index 00000000..2d17f82b --- /dev/null +++ b/gn2/wqflask/snp_browser/snp_browser.py @@ -0,0 +1,934 @@ +import string +from PIL import (Image) + +from gn2.base import species +from gn2.base import webqtlConfig + +from gn2.wqflask.database import database_connection + +from gn2.utility.tools import get_setting + + +class SnpBrowser: + + def __init__(self, db_cursor, start_vars): + self.strain_lists = get_browser_sample_lists() + self.initialize_parameters(db_cursor, start_vars) + + if self.first_run == "false": + self.filtered_results = self.get_browser_results() + self.table_rows = self.get_table_rows() + self.rows_count = len(self.table_rows) + + del self.filtered_results + + if 'sEcho' not in start_vars: + self.table_rows = [] + + if self.limit_strains == "true": + self.header_fields, self.empty_field_count, self.header_data_names = get_header_list( + variant_type=self.variant_type, strains=self.chosen_strains, empty_columns=self.empty_columns) + else: + self.header_fields, self.empty_field_count, self.header_data_names = get_header_list( + variant_type=self.variant_type, strains=self.strain_lists, species=self.species_name, empty_columns=self.empty_columns) + + def initialize_parameters(self, db_cursor, start_vars): + if 'first_run' in start_vars: + self.first_run = "false" + else: + self.first_run = "true" + self.allele_list = [] + + self.variant_type = "SNP" + if 'variant' in start_vars: + self.variant_type = start_vars['variant'] + + self.species_name = "Mouse" + self.species_id = 1 + if 'species' in start_vars: + self.species_name = start_vars['species'] + if self.species_name.capitalize() == "Rat": + self.species_id = 2 + + self.mouse_chr_list = [] + self.rat_chr_list = [] + mouse_species_ob = species.TheSpecies(species_name="Mouse") + for key in mouse_species_ob.chromosomes.chromosomes(db_cursor): + self.mouse_chr_list.append( + mouse_species_ob.chromosomes.chromosomes(db_cursor)[key].name) + rat_species_ob = species.TheSpecies(species_name="Rat") + for key in rat_species_ob.chromosomes.chromosomes(db_cursor): + self.rat_chr_list.append( + rat_species_ob.chromosomes.chromosomes(db_cursor)[key].name) + + if self.species_id == 1: + self.this_chr_list = self.mouse_chr_list + else: + self.this_chr_list = self.rat_chr_list + + if self.first_run == "true": + self.chr = "19" + self.start_mb = 30.1 + self.end_mb = 30.12 + else: + if 'gene_name' in start_vars: + if start_vars['gene_name'] != "": + self.gene_name = start_vars['gene_name'] + else: + self.gene_name = "" + self.chr = start_vars['chr'] + try: + self.start_mb = float(start_vars['start_mb']) + self.end_mb = float(start_vars['end_mb']) + except: + self.start_mb = 0.0 + self.end_mb = 0.0 + else: + try: + self.chr = start_vars['chr'] + self.start_mb = float(start_vars['start_mb']) + self.end_mb = float(start_vars['end_mb']) + except: + self.chr = "1" + self.start_mb = 0.0 + self.end_mb = 0.0 + + self.limit_strains = "true" + if self.first_run == "false": + if 'limit_strains' not in start_vars: + self.limit_strains = "false" + else: + if start_vars['limit_strains'] == "false": + self.limit_strains = "false" + + self.chosen_strains_mouse = ["C57BL/6J", + "DBA/2J", + "A/J", + "129S1/SvImJ", + "NOD/ShiLtJ", + "NZO/HlLtJ", + "WSB/EiJ", + "PWK/PhJ", + "CAST/EiJ"] + self.chosen_strains_rat = ["BN", "F344", "WLI", "WMI"] + if 'chosen_strains_mouse' in start_vars: + self.chosen_strains_mouse = start_vars['chosen_strains_mouse'].split( + ",") + if 'chosen_strains_rat' in start_vars: + self.chosen_strains_rat = start_vars['chosen_strains_rat'].split( + ",") + + if self.species_id == 1: + self.chosen_strains = self.chosen_strains_mouse + else: + self.chosen_strains = self.chosen_strains_rat + + self.domain = "All" + if 'domain' in start_vars: + self.domain = start_vars['domain'] + self.function = "All" + if 'function' in start_vars: + self.function = start_vars['function'] + self.source = "All" + if 'source' in start_vars: + self.source = start_vars['source'] + self.criteria = ">=" + if 'criteria' in start_vars: + self.criteria = start_vars['criteria'] + self.score = 0.0 + if 'score' in start_vars: + self.score = start_vars['score'] + + self.redundant = "false" + if self.first_run == "false" and 'redundant' in start_vars: + self.redundant = "true" + self.diff_alleles = "true" + if self.first_run == "false": + if 'diff_alleles' not in start_vars: + self.diff_alleles = "false" + else: + if start_vars['diff_alleles'] == "false": + self.diff_alleles = "false" + + def get_browser_results(self): + self.snp_list = None + __query = "" + __vars = None + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + if self.gene_name != "": + if self.species_id != 0: + __query = ("SELECT geneSymbol, chromosome, txStart, " + "txEnd FROM GeneList WHERE SpeciesId = %s " + "AND geneSymbol = %s") + __vars = (self.species_id, self.gene_name,) + else: + __query = ("SELECT geneSymbol, chromosome, txStart, " + "txEnd FROM GeneList WHERE geneSymbol = %s") + __vars = (self.gene_name,) + cursor.execute(__query, __vars) + + if result := cursor.fetchone(): + self.gene_name, self.chr, self.start_mb, self.end_mb = result + else: + if self.variant_type in ["SNP", "InDel"]: + result_snp = None + __vars = (self.gene_name,) + if self.variant_type == "SNP": + if self.gene_name[:2] == "rs": + __query = ("SELECT Id, Chromosome, Position, " + "Position+0.000001 FROM SnpAll " + "WHERE Rs = %s") + else: + if self.species_id != 0: + __query = ( + "SELECT Id, Chromosome, Position, " + "Position+0.000001 FROM SnpAll WHERE " + "SpeciesId = %s AND SnpName = %s") + __vars = (self.species_id, self.gene_name,) + else: + __query = ( + "SELECT Id, Chromosome, Position, " + "Position+0.000001 FROM SnpAll " + "WHERE SnpName = %s") + cursor.execute(__query, __vars) + result_snp = cursor.fetchall() + else: # variant_type == InDel + if self.gene_name[0] == "I": + if self.species_id != 0: + __query = ( + "SELECT Id, Chromosome, Mb_start, " + "Mb_end FROM IndelAll WHERE " + "SpeciesId = %s AND Name = %s") + __vars = (self.species_id, self.gene_name,) + else: + __query = ( + "SELECT Id, Chromosome, Mb_start, " + "Mb_end FROM IndelAll WHERE Name = %s",) + __vars = (self.gene_name,) + cursor.execute(__query, __vars) + result_snp = cursor.fetchall() + if result_snp: + self.snp_list = [item[0] for item in result_snp] + self.chr = result_snp[0][1] + self.start_mb = result_snp[0][2] + self.end_mb = result_snp[0][3] + else: + return [] + + if self.variant_type == "SNP": + __vars = (self.species_id, self.chr, + f"{self.start_mb:.6f}", + f"{self.end_mb:.6f}",) + if self.species_id == 1: # Mouse + __query = ("SELECT a.*, b.* FROM SnpAll a, SnpPattern b " + "WHERE a.SpeciesId = %s AND a.Chromosome = %s " + "AND a.Position >= %s AND a.Position < %s " + "AND a.Id = b.SnpId ORDER BY a.Position") + elif self.species_id == 2: # Rat + __query = ( + "SELECT a.*, b.* FROM SnpAll a, RatSnpPattern b " + "WHERE a.SpeciesId = %s AND a.Chromosome = %s " + "AND a.Position >= %s AND a.Position < %s " + "AND a.Id = b.SnpId ORDER BY a.Position") + + elif self.variant_type == "InDel": + if self.species_id != 0: + __query = ( + "SELECT DISTINCT a.Name, a.Chromosome, a.SourceId, " + "a.Mb_start, a.Mb_end, a.Strand, a.Type, a.Size, " + "a.InDelSequence, b.Name FROM IndelAll a, " + "SnpSource b WHERE a.SpeciesId = %s AND " + "a.Chromosome = %s AND a.Mb_start >= %s " + "AND a.Mb_start < %s AND b.Id = a.SourceId " + "ORDER BY a.Mb_start") + __vars = (self.species_id, + self.chr, f"{self.start_mb:2.6f}", + f"{self.end_mb+0.0010:2.6f}",) + cursor.execute(__query, __vars) + else: + __query = ( + "SELECT DISTINCT a.Name, a.Chromosome, a.SourceId, " + "a.Mb_start, a.Mb_end, a.Strand, a.Type, a.Size, " + "a.InDelSequence, b.Name FROM IndelAll a, " + "SnpSource b WHERE a.Chromosome = %s AND " + "a.Mb_start >= %s AND a.Mb_start < %s " + "AND b.Id = a.SourceId ORDER BY a.Mb_start") + __vars = (self.chr, f"{self.start_mb+0.0010:2.6f}", + f"{self.end_mb+0.0010:2.6f}",) + cursor.execute(__query, __vars) + return self.filter_results(cursor.fetchall()) + + def filter_results(self, results): + filtered_results = [] + strain_index_list = [] # ZS: List of positions of selected strains in strain list + last_mb = -1 + + if self.limit_strains == "true" and len(self.chosen_strains) > 0: + for item in self.chosen_strains: + index = self.strain_lists[self.species_name.lower()].index( + item) + strain_index_list.append(index) + + for seq, result in enumerate(results): + result = list(result) + + if self.variant_type == "SNP": + display_strains = [] + snp_id, species_id, snp_name, rs, chr, mb, mb_2016, alleles, snp_source, conservation_score = result[ + :10] + effect_list = result[10:28] + if self.species_id == 1: + self.allele_list = result[30:] + elif self.species_id == 2: + self.allele_list = result[31:] + + if self.limit_strains == "true" and len(self.chosen_strains) > 0: + for index in strain_index_list: + if self.species_id == 1: + display_strains.append(result[29 + index]) + elif self.species_id == 2: + display_strains.append(result[31 + index]) + self.allele_list = display_strains + + effect_info_dict = get_effect_info(effect_list) + coding_domain_list = ['Start Gained', 'Start Lost', + 'Stop Gained', 'Stop Lost', 'Nonsynonymous', 'Synonymous'] + intron_domain_list = ['Splice Site', 'Nonsplice Site'] + + for key in effect_info_dict: + if key in coding_domain_list: + domain = ['Exon', 'Coding'] + elif key in ['3\' UTR', '5\' UTR']: + domain = ['Exon', key] + elif key == "Unknown Effect In Exon": + domain = ['Exon', ''] + elif key in intron_domain_list: + domain = ['Intron', key] + else: + domain = [key, ''] + + if 'Intergenic' in domain: + if self.gene_name != "": + gene_id = get_gene_id( + self.species_id, self.gene_name) + gene = [gene_id, self.gene_name] + else: + gene = check_if_in_gene(species_id, chr, mb) + transcript = exon = function = function_details = '' + if self.redundant == "false" or last_mb != mb: # filter redundant + if self.include_record(domain, function, snp_source, conservation_score): + info_list = [snp_name, rs, chr, mb, alleles, gene, transcript, exon, domain, + function, function_details, snp_source, conservation_score, snp_id] + info_list.extend(self.allele_list) + filtered_results.append(info_list) + last_mb = mb + else: + gene_list, transcript_list, exon_list, function_list, function_details_list = effect_info_dict[ + key] + for index, item in enumerate(gene_list): + gene = item + transcript = transcript_list[index] + if exon_list: + exon = exon_list[index] + else: + exon = "" + + if function_list: + function = function_list[index] + if function == "Unknown Effect In Exon": + function = "Unknown" + else: + function = "" + + if function_details_list: + function_details = "Biotype: " + \ + function_details_list[index] + else: + function_details = "" + + if self.redundant == "false" or last_mb != mb: + if self.include_record(domain, function, snp_source, conservation_score): + info_list = [snp_name, rs, chr, mb, alleles, gene, transcript, exon, domain, + function, function_details, snp_source, conservation_score, snp_id] + info_list.extend(self.allele_list) + filtered_results.append(info_list) + last_mb = mb + + elif self.variant_type == "InDel": + # The order of variables is important; this applies to anything from the variant table as indel + indel_name, indel_chr, source_id, indel_mb_start, indel_mb_end, indel_strand, indel_type, indel_size, indel_sequence, source_name = result + + indel_type = indel_type.title() + if self.redundant == "false" or last_mb != indel_mb_start: + gene = "No Gene" + domain = conservation_score = snp_id = snp_name = rs = flank_3 = flank_5 = ncbi = function = "" + if self.include_record(domain, function, source_name, conservation_score): + filtered_results.append([indel_name, indel_chr, indel_mb_start, indel_mb_end, + indel_strand, indel_type, indel_size, indel_sequence, source_name]) + last_mb = indel_mb_start + + else: + filtered_results.append(result) + + return filtered_results + + def get_table_rows(self): + """ Take results and put them into the order and format necessary for the tables rows """ + + if self.variant_type == "SNP": + gene_name_list = [] + for item in self.filtered_results: + if item[5] and item[5] != "": + gene_name = item[5][1] + # eliminate duplicate gene_name + if gene_name and (gene_name not in gene_name_list): + gene_name_list.append(gene_name) + if len(gene_name_list) > 0: + gene_id_name_dict = get_gene_id_name_dict( + self.species_id, gene_name_list) + + # ZS: list of booleans representing which columns are entirely empty, so they aren't displayed on the page; only including ones that are sometimes empty (since there's always a location, etc) + self.empty_columns = { + "snp_source": "false", + "conservation_score": "false", + "gene_name": "false", + "transcript": "false", + "exon": "false", + "domain_2": "false", + "function": "false", + "function_details": "false" + } + + the_rows = [] + for i, result in enumerate(self.filtered_results): + this_row = {} + if self.variant_type == "SNP": + snp_name, rs, chr, mb, alleles, gene, transcript, exon, domain, function, function_details, snp_source, conservation_score, snp_id = result[ + :14] + allele_value_list = result[14:] + if rs: + snp_url = webqtlConfig.DBSNP % (rs) + snp_name = rs + else: + rs = "" + start_bp = int(mb * 1000000 - 100) + end_bp = int(mb * 1000000 + 100) + position_info = "chr%s:%d-%d" % (chr, start_bp, end_bp) + if self.species_id == 2: + snp_url = webqtlConfig.GENOMEBROWSER_URL % ( + "rn6", position_info) + else: + snp_url = webqtlConfig.GENOMEBROWSER_URL % ( + "mm10", position_info) + + mb = float(mb) + mb_formatted = "%2.6f" % mb + + if snp_source == "Sanger/UCLA": + source_url_1 = "http://www.sanger.ac.uk/resources/mouse/genomes/" + source_url_2 = "http://mouse.cs.ucla.edu/mousehapmap/beta/wellcome.html" + source_urls = [source_url_1, source_url_2] + self.empty_columns['snp_source'] = "true" + else: + source_urls = [] + + if not conservation_score: + conservation_score = "" + else: + self.empty_columns['conservation_score'] = "true" + + if gene: + gene_name = gene[1] + # if gene_name has related gene_id, use gene_id for NCBI search + if (gene_name in gene_id_name_dict) and (gene_id_name_dict[gene_name] != None and gene_id_name_dict[gene_name] != ""): + gene_id = gene_id_name_dict[gene[1]] + gene_link = webqtlConfig.NCBI_LOCUSID % gene_id + else: + gene_link = "http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?CMD=search&DB=gene&term=%s" % gene_name + + self.empty_columns['gene_name'] = "true" + else: + gene_name = "" + gene_link = "" + + if transcript: + transcript_link = webqtlConfig.ENSEMBLETRANSCRIPT_URL % ( + transcript) + self.empty_columns['transcript'] = "true" + else: + transcript_link = "" + + if exon: + exon = exon[1] # exon[0] is exon_id, exon[1] is exon_rank + self.empty_columns['exon'] = "true" + else: + exon = "" + + if domain: + domain_1 = domain[0] + domain_2 = domain[1] + if domain_1 == "Intergenic" and gene != "": + domain_1 = gene_name + else: + if domain_1 == "Exon": + domain_1 = domain_1 + " " + exon + + if domain_2 != "": + self.empty_columns['domain_2'] = "true" + + if function: + self.empty_columns['function'] = "true" + + function_list = [] + if function_details: + function_list = function_details.strip().split(",") + function_list = [item.strip() for item in function_list] + function_list[0] = function_list[0].title() + function_details = ", ".join( + item for item in function_list) + function_details = function_details.replace("_", " ") + function_details = function_details.replace("/", " -> ") + if function_details == "Biotype: Protein Coding": + function_details = function_details + ", Coding Region Unknown" + + self.empty_columns['function_details'] = "true" + + #[snp_href, chr, mb_formatted, alleles, snp_source_cell, conservation_score, gene_name_cell, transcript_href, exon, domain_1, domain_2, function, function_details] + + base_color_dict = {"A": "#C33232", "C": "#1569C7", "T": "#CFCF32", "G": "#32C332", + "t": "#FF6", "c": "#5CB3FF", "a": "#F66", "g": "#CF9", ":": "#FFFFFF", "-": "#FFFFFF", "?": "#FFFFFF"} + + the_bases = [] + for j, item in enumerate(allele_value_list): + if item and isinstance(item, str): + this_base = [str(item), base_color_dict[item]] + else: + this_base = "" + + the_bases.append(this_base) + + this_row = { + "index": i + 1, + "rs": str(rs), + "snp_url": str(snp_url), + "snp_name": str(snp_name), + "chr": str(chr), + "mb_formatted": mb_formatted, + "alleles": str(alleles), + "snp_source": str(snp_source), + "source_urls": source_urls, + "conservation_score": str(conservation_score), + "gene_name": str(gene_name), + "gene_link": str(gene_link), + "transcript": str(transcript), + "transcript_link": str(transcript_link), + "exon": str(exon), + "domain_1": str(domain_1), + "domain_2": str(domain_2), + "function": str(function), + "function_details": str(function_details), + "allele_value_list": the_bases + } + + elif self.variant_type == "InDel": + indel_name, indel_chr, indel_mb_s, indel_mb_e, indel_strand, indel_type, indel_size, indel_sequence, source_name = result + this_row = { + "index": i, + "indel_name": str(indel_name), + "indel_chr": str(indel_chr), + "indel_mb_s": str(indel_mb_s), + "indel_mb_e": str(indel_mb_e), + "indel_strand": str(indel_strand), + "indel_type": str(indel_type), + "indel_size": str(indel_size), + "indel_sequence": str(indel_sequence), + "source_name": str(source_name) + } + #this_row = [indel_name, indel_chr, indel_mb_s, indel_mb_e, indel_strand, indel_type, indel_size, indel_sequence, source_name] + + the_rows.append(this_row) + + return the_rows + + def include_record(self, domain, function, snp_source, conservation_score): + """ Decide whether to add this record """ + + domain_satisfied = True + function_satisfied = True + different_alleles_satisfied = True + source_satisfied = True + + if domain: + if len(domain) == 0: + if self.domain != "All": + domain_satisfied = False + else: + domain_satisfied = False + if domain[0].startswith(self.domain) or domain[1].startswith(self.domain) or self.domain == "All": + domain_satisfied = True + else: + if self.domain != "All": + domain_satisfied = False + + if snp_source: + if len(snp_source) == 0: + if self.source != "All": + source_satisfied = False + else: + source_satisfied = False + if snp_source.startswith(self.source) or self.source == "All": + source_satisfied = True + else: + if self.source != "All": + source_satisfied = False + + if function: + if len(function) == 0: + if self.function != "All": + function_satisfied = False + else: + function_satisfied = False + if self.function != "All": + if function.startswith(self.function): + function_satisfied = True + else: + function_satisfied = True + else: + if self.function != "All": + function_satisfied = False + + if conservation_score: + score_as_float = float(conservation_score) + try: + input_score_float = float(self.score) # the user-input score + except: + input_score_float = 0.0 + + if self.criteria == ">=": + if score_as_float >= input_score_float: + score_satisfied = True + else: + score_satisfied = False + elif self.criteria == "==": + if score_as_float == input_score_float: + score_satisfied = True + else: + score_satisfied = False + elif self.criteria == "<=": + if score_as_float <= input_score_float: + score_satisfied = True + else: + score_satisfied = False + else: + try: + if float(self.score) > 0: + score_satisfied = False + else: + score_satisfied = True + except: + score_satisfied = True + + if self.variant_type == "SNP" and self.diff_alleles == "true": + this_allele_list = [] + + for item in self.allele_list: + if item and isinstance(item, str) and (item.lower() not in this_allele_list) and (item != "-"): + this_allele_list.append(item.lower()) + + total_allele_count = len(this_allele_list) + if total_allele_count <= 1: + different_alleles_satisfied = False + else: + different_alleles_satisfied = True + else: + different_alleles_satisfied = True + + return domain_satisfied and function_satisfied and source_satisfied and score_satisfied and different_alleles_satisfied + + def snp_density_map(self, query, results): + + canvas_width = 900 + canvas_height = 200 + snp_canvas = Image.new("RGBA", size=(canvas_width, canvas_height)) + left_offset, right_offset, top_offset, bottom_offset = (30, 30, 40, 50) + plot_width = canvas_width - left_offset - right_offset + plot_height = canvas_height - top_offset - bottom_offset + y_zero = top_offset + plot_height / 2 + + x_scale = plot_width / (self.end_mb - self.start_mb) + + # draw clickable image map at some point + n_click = 80.0 + click_step = plot_width / n_click + click_mb_step = (self.end_mb - self.start_mb) / n_click + + +def get_browser_sample_lists(species_id=1): + strain_lists = {} + mouse_strain_list = [] + rat_strain_list = [] + with database_connection(get_setting("SQL_URI")) as conn: + with conn.cursor() as cursor: + cursor.execute("SHOW COLUMNS FROM SnpPattern") + _mouse_snp_pattern = cursor.fetchall() + cursor.execute("SHOW COLUMNS FROM RatSnpPattern") + _rats_snp_pattern = cursor.fetchall() + for result in _mouse_snp_pattern[1:]: + mouse_strain_list.append(result[0]) + for result in _rats_snp_pattern[2:]: + rat_strain_list.append(result[0]) + strain_lists['mouse'] = mouse_strain_list + strain_lists['rat'] = rat_strain_list + return strain_lists + + +def get_header_list(variant_type, strains, species=None, empty_columns=None): + if species == "Mouse": + strain_list = strains['mouse'] + elif species == "Rat": + strain_list = strains['rat'] + else: + strain_list = strains + + empty_field_count = 0 # ZS: This is an awkward way of letting the javascript know the index where the allele value columns start; there's probably a better way of doing this + + header_fields = [] + header_data_names = [] + if variant_type == "SNP": + header_fields.append(['Index', 'SNP ID', 'Chr', 'Mb', 'Alleles', 'Source', 'ConScore', + 'Gene', 'Transcript', 'Exon', 'Domain 1', 'Domain 2', 'Function', 'Details']) + header_data_names = ['index', 'snp_name', 'chr', 'mb_formatted', 'alleles', 'snp_source', 'conservation_score', + 'gene_name', 'transcript', 'exon', 'domain_1', 'domain_2', 'function', 'function_details'] + + header_fields.append(strain_list) + header_data_names += strain_list + + if empty_columns != None: + if empty_columns['snp_source'] == "false": + empty_field_count += 1 + header_fields[0].remove('Source') + if empty_columns['conservation_score'] == "false": + empty_field_count += 1 + header_fields[0].remove('ConScore') + if empty_columns['gene_name'] == "false": + empty_field_count += 1 + header_fields[0].remove('Gene') + if empty_columns['transcript'] == "false": + empty_field_count += 1 + header_fields[0].remove('Transcript') + if empty_columns['exon'] == "false": + empty_field_count += 1 + header_fields[0].remove('Exon') + if empty_columns['domain_2'] == "false": + empty_field_count += 1 + header_fields[0].remove('Domain 2') + if empty_columns['function'] == "false": + empty_field_count += 1 + header_fields[0].remove('Function') + if empty_columns['function_details'] == "false": + empty_field_count += 1 + header_fields[0].remove('Details') + + for col in empty_columns.keys(): + if empty_columns[col] == "false": + header_data_names.remove(col) + + elif variant_type == "InDel": + header_fields = ['Index', 'ID', 'Type', 'InDel Chr', + 'Mb Start', 'Mb End', 'Strand', 'Size', 'Sequence', 'Source'] + header_data_names = ['index', 'indel_name', 'indel_type', 'indel_chr', 'indel_mb_s', + 'indel_mb_e', 'indel_strand', 'indel_size', 'indel_sequence', 'source_name'] + + return header_fields, empty_field_count, header_data_names + + +def get_effect_details_by_category(effect_name=None, effect_value=None): + gene_list = [] + transcript_list = [] + exon_list = [] + function_list = [] + function_detail_list = [] + tmp_list = [] + + gene_group_list = ['Upstream', 'Downstream', + 'Splice Site', 'Nonsplice Site', '3\' UTR'] + biotype_group_list = ['Unknown Effect In Exon', 'Start Gained', + 'Start Lost', 'Stop Gained', 'Stop Lost', 'Nonsynonymous', 'Synonymous'] + new_codon_group_list = ['Start Gained'] + codon_effect_group_list = [ + 'Start Lost', 'Stop Gained', 'Stop Lost', 'Nonsynonymous', 'Synonymous'] + + effect_detail_list = effect_value.strip().split('|') + effect_detail_list = [item.strip() for item in effect_detail_list] + + for index, item in enumerate(effect_detail_list): + item_list = item.strip().split(',') + item_list = [item.strip() for item in item_list] + + gene_id = item_list[0] + gene_name = item_list[1] + gene_list.append([gene_id, gene_name]) + transcript_list.append(item_list[2]) + + if effect_name not in gene_group_list: + exon_id = item_list[3] + exon_rank = item_list[4] + exon_list.append([exon_id, exon_rank]) + + if effect_name in biotype_group_list: + biotype = item_list[5] + function_list.append(effect_name) + + if effect_name in new_codon_group_list: + new_codon = item_list[6] + tmp_list = [biotype, new_codon] + function_detail_list.append(", ".join(tmp_list)) + elif effect_name in codon_effect_group_list: + old_new_AA = item_list[6] + old_new_codon = item_list[7] + codon_num = item_list[8] + tmp_list = [biotype, old_new_AA, old_new_codon, codon_num] + function_detail_list.append(", ".join(tmp_list)) + else: + function_detail_list.append(biotype) + + return [gene_list, transcript_list, exon_list, function_list, function_detail_list] + + +def get_effect_info(effect_list): + domain = "" + effect_detail_list = [] + effect_info_dict = {} + + prime3_utr, prime5_utr, upstream, downstream, intron, nonsplice_site, splice_site, intergenic = effect_list[ + :8] + exon, non_synonymous_coding, synonymous_coding, start_gained, start_lost, stop_gained, stop_lost, unknown_effect_in_exon = effect_list[ + 8:16] + + if intergenic: + domain = "Intergenic" + effect_info_dict[domain] = "" + else: + # if not exon, get gene list/transcript list info + if upstream: + domain = "Upstream" + effect_detail_list = get_effect_details_by_category( + effect_name='Upstream', effect_value=upstream) + effect_info_dict[domain] = effect_detail_list + if downstream: + domain = "Downstream" + effect_detail_list = get_effect_details_by_category( + effect_name='Downstream', effect_value=downstream) + effect_info_dict[domain] = effect_detail_list + if intron: + if splice_site: + domain = "Splice Site" + effect_detail_list = get_effect_details_by_category( + effect_name='Splice Site', effect_value=splice_site) + effect_info_dict[domain] = effect_detail_list + if nonsplice_site: + domain = "Nonsplice Site" + effect_detail_list = get_effect_details_by_category( + effect_name='Nonsplice Site', effect_value=nonsplice_site) + effect_info_dict[domain] = effect_detail_list + # get gene, transcript_list, and exon info + if prime3_utr: + domain = "3\' UTR" + effect_detail_list = get_effect_details_by_category( + effect_name='3\' UTR', effect_value=prime3_utr) + effect_info_dict[domain] = effect_detail_list + if prime5_utr: + domain = "5\' UTR" + effect_detail_list = get_effect_details_by_category( + effect_name='5\' UTR', effect_value=prime5_utr) + effect_info_dict[domain] = effect_detail_list + + if start_gained: + domain = "Start Gained" + effect_detail_list = get_effect_details_by_category( + effect_name='Start Gained', effect_value=start_gained) + effect_info_dict[domain] = effect_detail_list + if unknown_effect_in_exon: + domain = "Unknown Effect In Exon" + effect_detail_list = get_effect_details_by_category( + effect_name='Unknown Effect In Exon', effect_value=unknown_effect_in_exon) + effect_info_dict[domain] = effect_detail_list + if start_lost: + domain = "Start Lost" + effect_detail_list = get_effect_details_by_category( + effect_name='Start Lost', effect_value=start_lost) + effect_info_dict[domain] = effect_detail_list + if stop_gained: + domain = "Stop Gained" + effect_detail_list = get_effect_details_by_category( + effect_name='Stop Gained', effect_value=stop_gained) + effect_info_dict[domain] = effect_detail_list + if stop_lost: + domain = "Stop Lost" + effect_detail_list = get_effect_details_by_category( + effect_name='Stop Lost', effect_value=stop_lost) + effect_info_dict[domain] = effect_detail_list + + if non_synonymous_coding: + domain = "Nonsynonymous" + effect_detail_list = get_effect_details_by_category( + effect_name='Nonsynonymous', effect_value=non_synonymous_coding) + effect_info_dict[domain] = effect_detail_list + if synonymous_coding: + domain = "Synonymous" + effect_detail_list = get_effect_details_by_category( + effect_name='Synonymous', effect_value=synonymous_coding) + effect_info_dict[domain] = effect_detail_list + + return effect_info_dict + + +def get_gene_id(species_id, gene_name): + query = ("SELECT geneId FROM GeneList WHERE " + "SpeciesId = %s AND geneSymbol = %s") + + with database_connection(get_setting("SQL_URI")) as conn: + with conn.cursor() as cursor: + cursor.execute(query, (species_id, gene_name)) + if (result := cursor.fetchone()): + return result[0] + return "" + + +def get_gene_id_name_dict(species_id, gene_name_list): + gene_id_name_dict = {} + if len(gene_name_list) == 0: + return "" + query = ("SELECT geneId, geneSymbol FROM " + "GeneList WHERE SpeciesId = %s AND " + f"geneSymbol in ({', '.join(['%s'] * len(gene_name_list))})") + with database_connection(get_setting("SQL_URI")) as conn: + with conn.cursor() as cursor: + cursor.execute(query, (species_id, *gene_name_list)) + results = cursor.fetchall() + if results: + for item in results: + gene_id_name_dict[item[1]] = item[0] + return gene_id_name_dict + + +def check_if_in_gene(species_id, chr_, mb): + with database_connection(get_setting("SQL_URI")) as conn: + with conn.cursor() as cursor: + if species_id != 0: # ZS: Check if this is necessary + cursor.execute( + "SELECT geneId, geneSymbol " + "FROM GeneList WHERE " + "SpeciesId = %s AND chromosome = %s " + "AND (txStart < %s AND txEnd > %s)", + (species_id, chr_, mb, mb)) + else: + cursor.execute( + "SELECT geneId,geneSymbol " + "FROM GeneList WHERE " + "chromosome = %s AND " + "(txStart < %s AND txEnd > %s)", + (chr_, mb, mb)) + if (result := cursor.fetchone()): + return [result[0], result[1]] + return "" diff --git a/gn2/wqflask/startup.py b/gn2/wqflask/startup.py new file mode 100644 index 00000000..069755fe --- /dev/null +++ b/gn2/wqflask/startup.py @@ -0,0 +1,42 @@ +"""Checks to do before the application is started.""" +from typing import Tuple + +from flask import Blueprint, current_app, render_template + +class StartupError(Exception): + """Base class for Application Check Errors.""" + +class MissingConfigurationError(StartupError): + """Raised in case of a missing required setting.""" + + def __init__(self, missing=Tuple[str, ...]): + """Initialise the MissingConfigurationError object.""" + super().__init__("At least one required configuration is missing.") + self.missing = missing + +startup_errors = Blueprint("app_check_errors", __name__) +__MANDATORY_CONFIGURATIONS__ = ( + "REDIS_URL", # URI to Redis server + "SQL_URI", # URI to MariaDB server + "GN_SERVER_URL", # REST API Server + "AUTH_SERVER_URL" # Auth(entic/oris)ation Server +) + +def check_mandatory_configs(app): + """Check that all mandatory configuration settings are defined.""" + missing = tuple( + setting for setting in __MANDATORY_CONFIGURATIONS__ + if (setting not in app.config + or app.config.get(setting) is None + or app.config.get(setting).strip() == "")) + if len(missing) > 0: + print(missing) + raise MissingConfigurationError(missing) + +@startup_errors.route("/") +def error_index(): + """Display errors experienced at application startup""" + return render_template( + "startup_errors.html", + error_type = type(current_app.startup_error).__name__, + error_value = current_app.startup_error) diff --git a/gn2/wqflask/static/Congenic.png b/gn2/wqflask/static/Congenic.png Binary files differnew file mode 100644 index 00000000..8cd489a4 --- /dev/null +++ b/gn2/wqflask/static/Congenic.png diff --git a/gn2/wqflask/static/fonts/README b/gn2/wqflask/static/fonts/README new file mode 100644 index 00000000..75a3e444 --- /dev/null +++ b/gn2/wqflask/static/fonts/README @@ -0,0 +1 @@ +These fonts are used by pillow for 'interval mapping' diff --git a/gn2/wqflask/static/fonts/arial.ttf b/gn2/wqflask/static/fonts/arial.ttf Binary files differnew file mode 100644 index 00000000..bf0d4a95 --- /dev/null +++ b/gn2/wqflask/static/fonts/arial.ttf diff --git a/gn2/wqflask/static/fonts/courbd.ttf b/gn2/wqflask/static/fonts/courbd.ttf Binary files differnew file mode 100644 index 00000000..c8081467 --- /dev/null +++ b/gn2/wqflask/static/fonts/courbd.ttf diff --git a/gn2/wqflask/static/fonts/fnt_bs.ttf b/gn2/wqflask/static/fonts/fnt_bs.ttf Binary files differnew file mode 100644 index 00000000..712c38cf --- /dev/null +++ b/gn2/wqflask/static/fonts/fnt_bs.ttf diff --git a/gn2/wqflask/static/fonts/tahoma.ttf b/gn2/wqflask/static/fonts/tahoma.ttf Binary files differnew file mode 100644 index 00000000..bb314be3 --- /dev/null +++ b/gn2/wqflask/static/fonts/tahoma.ttf diff --git a/gn2/wqflask/static/fonts/trebucbd.ttf b/gn2/wqflask/static/fonts/trebucbd.ttf Binary files differnew file mode 100644 index 00000000..1ab1ae0a --- /dev/null +++ b/gn2/wqflask/static/fonts/trebucbd.ttf diff --git a/gn2/wqflask/static/fonts/verdana.ttf b/gn2/wqflask/static/fonts/verdana.ttf Binary files differnew file mode 100644 index 00000000..754a9b7b --- /dev/null +++ b/gn2/wqflask/static/fonts/verdana.ttf diff --git a/gn2/wqflask/static/fonts/verdanab.ttf b/gn2/wqflask/static/fonts/verdanab.ttf Binary files differnew file mode 100644 index 00000000..1a99258f --- /dev/null +++ b/gn2/wqflask/static/fonts/verdanab.ttf diff --git a/gn2/wqflask/static/gif/89.gif b/gn2/wqflask/static/gif/89.gif Binary files differnew file mode 100644 index 00000000..e9b3279d --- /dev/null +++ b/gn2/wqflask/static/gif/89.gif diff --git a/gn2/wqflask/static/gif/error/Wild-Type-Mouse.gif b/gn2/wqflask/static/gif/error/Wild-Type-Mouse.gif Binary files differnew file mode 100644 index 00000000..2c68b5ee --- /dev/null +++ b/gn2/wqflask/static/gif/error/Wild-Type-Mouse.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-aliens-29.gif b/gn2/wqflask/static/gif/error/animated-gifs-aliens-29.gif Binary files differnew file mode 100644 index 00000000..e9d38277 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-aliens-29.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-angels-04.gif b/gn2/wqflask/static/gif/error/animated-gifs-angels-04.gif Binary files differnew file mode 100644 index 00000000..94e11847 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-angels-04.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-cats-016.gif b/gn2/wqflask/static/gif/error/animated-gifs-cats-016.gif Binary files differnew file mode 100644 index 00000000..7e6ec9a3 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-cats-016.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-cats-031.gif b/gn2/wqflask/static/gif/error/animated-gifs-cats-031.gif Binary files differnew file mode 100644 index 00000000..af7ef655 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-cats-031.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-cell-phones-03.gif b/gn2/wqflask/static/gif/error/animated-gifs-cell-phones-03.gif Binary files differnew file mode 100644 index 00000000..89c79ddf --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-cell-phones-03.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-cell-phones-16.gif b/gn2/wqflask/static/gif/error/animated-gifs-cell-phones-16.gif Binary files differnew file mode 100644 index 00000000..7530d180 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-cell-phones-16.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-13.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-13.gif Binary files differnew file mode 100644 index 00000000..afb05c62 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-computers-13.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-28.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-28.gif Binary files differnew file mode 100644 index 00000000..f5b4a563 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-computers-28.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-32.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-32.gif Binary files differnew file mode 100644 index 00000000..7258e594 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-computers-32.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-42.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-42.gif Binary files differnew file mode 100644 index 00000000..ed1f8722 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-computers-42.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-60.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-60.gif Binary files differnew file mode 100644 index 00000000..f58d69f1 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-computers-60.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-64.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-64.gif Binary files differnew file mode 100644 index 00000000..5d5b4fdf --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-computers-64.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-65.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-65.gif Binary files differnew file mode 100644 index 00000000..b4b10845 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-computers-65.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-72.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-72.gif Binary files differnew file mode 100644 index 00000000..e60cb4fe --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-computers-72.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-74.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-74.gif Binary files differnew file mode 100644 index 00000000..bd7b72f3 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-computers-74.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-75.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-75.gif Binary files differnew file mode 100644 index 00000000..916d6b33 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-computers-75.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-construction-sites-038.gif b/gn2/wqflask/static/gif/error/animated-gifs-construction-sites-038.gif Binary files differnew file mode 100644 index 00000000..0ec782c4 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-construction-sites-038.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-dogs-04.gif b/gn2/wqflask/static/gif/error/animated-gifs-dogs-04.gif Binary files differnew file mode 100644 index 00000000..9515c18a --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-dogs-04.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-dogs-14.gif b/gn2/wqflask/static/gif/error/animated-gifs-dogs-14.gif Binary files differnew file mode 100644 index 00000000..f1e2e1f5 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-dogs-14.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-dogs-18.gif b/gn2/wqflask/static/gif/error/animated-gifs-dogs-18.gif Binary files differnew file mode 100644 index 00000000..572849d5 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-dogs-18.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-dogs-47.gif b/gn2/wqflask/static/gif/error/animated-gifs-dogs-47.gif Binary files differnew file mode 100644 index 00000000..d808c9ee --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-dogs-47.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-dogs-50.gif b/gn2/wqflask/static/gif/error/animated-gifs-dogs-50.gif Binary files differnew file mode 100644 index 00000000..9865ee45 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-dogs-50.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-lava-lamps-01.gif b/gn2/wqflask/static/gif/error/animated-gifs-lava-lamps-01.gif Binary files differnew file mode 100644 index 00000000..ee9c113d --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-lava-lamps-01.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-mice-02.gif b/gn2/wqflask/static/gif/error/animated-gifs-mice-02.gif Binary files differnew file mode 100644 index 00000000..5ca2ee5c --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-mice-02.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-mice-09.gif b/gn2/wqflask/static/gif/error/animated-gifs-mice-09.gif Binary files differnew file mode 100644 index 00000000..7cb361e4 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-mice-09.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-mice-24.gif b/gn2/wqflask/static/gif/error/animated-gifs-mice-24.gif Binary files differnew file mode 100644 index 00000000..96a26450 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-mice-24.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-smileys-063.gif b/gn2/wqflask/static/gif/error/animated-gifs-smileys-063.gif Binary files differnew file mode 100644 index 00000000..62de166c --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-smileys-063.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-smileys-068.gif b/gn2/wqflask/static/gif/error/animated-gifs-smileys-068.gif Binary files differnew file mode 100644 index 00000000..3550e978 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-smileys-068.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-smileys-134.gif b/gn2/wqflask/static/gif/error/animated-gifs-smileys-134.gif Binary files differnew file mode 100644 index 00000000..954ab614 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-smileys-134.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-smileys-211.gif b/gn2/wqflask/static/gif/error/animated-gifs-smileys-211.gif Binary files differnew file mode 100644 index 00000000..596174d7 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-smileys-211.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-smileys-234.gif b/gn2/wqflask/static/gif/error/animated-gifs-smileys-234.gif Binary files differnew file mode 100644 index 00000000..5aba636b --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-smileys-234.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-001.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-001.gif Binary files differnew file mode 100644 index 00000000..7896ff1f --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-001.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-002.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-002.gif Binary files differnew file mode 100644 index 00000000..89da6441 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-002.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-005.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-005.gif Binary files differnew file mode 100644 index 00000000..b7887630 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-005.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-012.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-012.gif Binary files differnew file mode 100644 index 00000000..f6697d02 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-012.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-056.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-056.gif Binary files differnew file mode 100644 index 00000000..2b2496a4 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-056.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-059.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-059.gif Binary files differnew file mode 100644 index 00000000..f2188656 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-059.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-060.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-060.gif Binary files differnew file mode 100644 index 00000000..aa8f7bd3 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-060.gif diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-069.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-069.gif Binary files differnew file mode 100644 index 00000000..473212e4 --- /dev/null +++ b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-069.gif diff --git a/gn2/wqflask/static/gif/error/m001.gif b/gn2/wqflask/static/gif/error/m001.gif Binary files differnew file mode 100644 index 00000000..81c8ba26 --- /dev/null +++ b/gn2/wqflask/static/gif/error/m001.gif diff --git a/gn2/wqflask/static/gif/error/m002.gif b/gn2/wqflask/static/gif/error/m002.gif Binary files differnew file mode 100644 index 00000000..7232c58b --- /dev/null +++ b/gn2/wqflask/static/gif/error/m002.gif diff --git a/gn2/wqflask/static/gif/error/m003.gif b/gn2/wqflask/static/gif/error/m003.gif Binary files differnew file mode 100644 index 00000000..2384ceb6 --- /dev/null +++ b/gn2/wqflask/static/gif/error/m003.gif diff --git a/gn2/wqflask/static/gif/error/m004.gif b/gn2/wqflask/static/gif/error/m004.gif Binary files differnew file mode 100644 index 00000000..d77c708d --- /dev/null +++ b/gn2/wqflask/static/gif/error/m004.gif diff --git a/gn2/wqflask/static/gif/error/m005.gif b/gn2/wqflask/static/gif/error/m005.gif Binary files differnew file mode 100644 index 00000000..1b2de7ec --- /dev/null +++ b/gn2/wqflask/static/gif/error/m005.gif diff --git a/gn2/wqflask/static/gif/error/m006.gif b/gn2/wqflask/static/gif/error/m006.gif Binary files differnew file mode 100644 index 00000000..f354a4cc --- /dev/null +++ b/gn2/wqflask/static/gif/error/m006.gif diff --git a/gn2/wqflask/static/gif/error/m007.gif b/gn2/wqflask/static/gif/error/m007.gif Binary files differnew file mode 100644 index 00000000..ba2eeb37 --- /dev/null +++ b/gn2/wqflask/static/gif/error/m007.gif diff --git a/gn2/wqflask/static/gif/error/m008.gif b/gn2/wqflask/static/gif/error/m008.gif Binary files differnew file mode 100644 index 00000000..4cdec5cb --- /dev/null +++ b/gn2/wqflask/static/gif/error/m008.gif diff --git a/gn2/wqflask/static/gif/error/mouse-wheel.gif b/gn2/wqflask/static/gif/error/mouse-wheel.gif Binary files differnew file mode 100644 index 00000000..164220e7 --- /dev/null +++ b/gn2/wqflask/static/gif/error/mouse-wheel.gif diff --git a/gn2/wqflask/static/gif/loader.gif b/gn2/wqflask/static/gif/loader.gif Binary files differnew file mode 100644 index 00000000..c2146b77 --- /dev/null +++ b/gn2/wqflask/static/gif/loader.gif diff --git a/gn2/wqflask/static/gif/waitAnima2.gif b/gn2/wqflask/static/gif/waitAnima2.gif Binary files differnew file mode 100644 index 00000000..50aff7f2 --- /dev/null +++ b/gn2/wqflask/static/gif/waitAnima2.gif diff --git a/gn2/wqflask/static/images/Belknap_Fig1_1998.png b/gn2/wqflask/static/images/Belknap_Fig1_1998.png Binary files differnew file mode 100644 index 00000000..46305fa1 --- /dev/null +++ b/gn2/wqflask/static/images/Belknap_Fig1_1998.png diff --git a/gn2/wqflask/static/images/Chrna1vsMyf6.gif b/gn2/wqflask/static/images/Chrna1vsMyf6.gif Binary files differnew file mode 100644 index 00000000..881a08e8 --- /dev/null +++ b/gn2/wqflask/static/images/Chrna1vsMyf6.gif diff --git a/gn2/wqflask/static/images/Congenic.png b/gn2/wqflask/static/images/Congenic.png Binary files differnew file mode 100644 index 00000000..8cd489a4 --- /dev/null +++ b/gn2/wqflask/static/images/Congenic.png diff --git a/gn2/wqflask/static/images/Normal_Plot.gif b/gn2/wqflask/static/images/Normal_Plot.gif Binary files differnew file mode 100644 index 00000000..dc239f8e --- /dev/null +++ b/gn2/wqflask/static/images/Normal_Plot.gif diff --git a/gn2/wqflask/static/images/SilverFig3_2.png b/gn2/wqflask/static/images/SilverFig3_2.png Binary files differnew file mode 100644 index 00000000..5b4b2c70 --- /dev/null +++ b/gn2/wqflask/static/images/SilverFig3_2.png diff --git a/gn2/wqflask/static/images/SilverFig3_6.png b/gn2/wqflask/static/images/SilverFig3_6.png Binary files differnew file mode 100644 index 00000000..5b91d991 --- /dev/null +++ b/gn2/wqflask/static/images/SilverFig3_6.png diff --git a/gn2/wqflask/static/images/Winsorize1.png b/gn2/wqflask/static/images/Winsorize1.png Binary files differnew file mode 100644 index 00000000..f3a65f29 --- /dev/null +++ b/gn2/wqflask/static/images/Winsorize1.png diff --git a/gn2/wqflask/static/images/Winsorize3.png b/gn2/wqflask/static/images/Winsorize3.png Binary files differnew file mode 100644 index 00000000..a9ed95d6 --- /dev/null +++ b/gn2/wqflask/static/images/Winsorize3.png diff --git a/gn2/wqflask/static/images/edit.png b/gn2/wqflask/static/images/edit.png Binary files differnew file mode 100644 index 00000000..571b08cd --- /dev/null +++ b/gn2/wqflask/static/images/edit.png diff --git a/gn2/wqflask/static/new/css/autocomplete.css b/gn2/wqflask/static/new/css/autocomplete.css new file mode 100644 index 00000000..1501e280 --- /dev/null +++ b/gn2/wqflask/static/new/css/autocomplete.css @@ -0,0 +1,85 @@ +.autocomplete { + /*the container must be positioned relative:*/ + position: relative; + display: inline-block; + + +} + +input.autocomplete { + border: 1px solid transparent; + background-color: #f1f1f1; + padding: 10px; + font-size: 16px; +} + +input[type=text].autocomplete { + background-color: #f1f1f1; + width: 100%; +} + +input[type=submit].autocomplete { + background-color: DodgerBlue; + color: #fff; +} + +.autocomplete-items { + position: absolute; + border: 1px solid #d4d4d4; + border-bottom: none; + border-top: none; + z-index: 99; + /*position the autocomplete items to be the same width as the container:*/ + top: 100%; + left: 0; + right: 0; + border:1px solid black; + border-top:none; + box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; + +} + +.autocomplete-items div { + padding: 10px; + cursor: pointer; + background-color: #fff; + border-bottom: 1px dotted #d4d4d4; +} + +.autocomplete-items div:hover { + /*when hovering an item:*/ + background-color: #e9e9e9; +} + +.autocomplete-active { + /*when navigating through the items using the arrow keys:*/ + background-color: DodgerBlue !important; + color: #ffffff; +} + +.recent-search-title { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; +} + +.recent-search-title * { + -webkit-box-flex: 1 1 auto; + -moz-box-flex: 1 1 auto; + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; +} + + +.recent-search-title input[type="button"] { + border: none; + background: none; + cursor: pointer; + margin: 0; + padding: 0; + color: blue; + +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/bar_chart.css b/gn2/wqflask/static/new/css/bar_chart.css new file mode 100644 index 00000000..20730c2f --- /dev/null +++ b/gn2/wqflask/static/new/css/bar_chart.css @@ -0,0 +1,23 @@ +.barchart .axis path, +.barchart .axis line { + fill: none; + stroke: #000; + shape-rendering: crispEdges; +} + +.bar { + fill: steelblue; + shape-rendering: crispEdges; +} + +#legend-left, #legend-right { + font-size: 20px; +} + +.nvd3 .nv-group { + fill-opacity: .9 !important; +} + +.nvtooltip table td.legend-color-guide div { + display: none; +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/bootstrap-custom.css b/gn2/wqflask/static/new/css/bootstrap-custom.css new file mode 100644 index 00000000..007dcc44 --- /dev/null +++ b/gn2/wqflask/static/new/css/bootstrap-custom.css @@ -0,0 +1,7560 @@ +/*! + * Bootstrap v3.3.0 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +body { + margin: 0; +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} + +audio:not([controls]) { + display: none; + height: 0; +} + +[hidden], +template { + display: none; +} + +a { + background-color: transparent; +} + +a:active, +a:hover { + outline: 0; +} + +abbr[title] { + border-bottom: 1px dotted; +} + +b, +strong { + font-weight: bold; +} + +dfn { + font-style: italic; +} + +h1 { + margin: .67em 0; + font-size: 2em; +} + +mark { + color: #000; + background: #ff0; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sup { + top: -.5em; +} + +sub { + bottom: -.25em; +} + +img { + border: 0; +} + +svg:not(:root) { + overflow: hidden; +} + +figure { + margin: 1em 40px; +} + +hr { + height: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +pre { + overflow: auto; +} + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; +} + +button { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} + +button[disabled], +html input[disabled] { + cursor: default; +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} + +input { + line-height: normal; +} + +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} + +legend { + padding: 0; + border: 0; +} + +textarea { + overflow: auto; +} + +optgroup { + font-weight: bold; +} + +table { + border-spacing: 0; + border-collapse: collapse; +} + +td, +th { + padding: 0; +} + +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " ("attr(href) ")"; + } + + abbr[title]:after { + content: " ("attr(title) ")"; + } + + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + + page-break-inside: avoid; + } + + thead { + display: table-header-group; + } + + tr, + img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } + + select { + background: #fff !important; + } + + .navbar { + display: none; + } + + .btn>.caret, + .dropup>.btn>.caret { + border-top-color: #000 !important; + } + + .label { + border: 1px solid #000; + } + + .table { + border-collapse: collapse !important; + } + + .table td, + .table th { + background-color: #fff !important; + } + + .table-bordered th, + .table-bordered td { + border: 1px solid #000 !important; + } +} + +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} + +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.glyphicon-asterisk:before { + content: "\2a"; +} + +.glyphicon-plus:before { + content: "\2b"; +} + +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} + +.glyphicon-minus:before { + content: "\2212"; +} + +.glyphicon-cloud:before { + content: "\2601"; +} + +.glyphicon-envelope:before { + content: "\2709"; +} + +.glyphicon-pencil:before { + content: "\270f"; +} + +.glyphicon-glass:before { + content: "\e001"; +} + +.glyphicon-music:before { + content: "\e002"; +} + +.glyphicon-search:before { + content: "\e003"; +} + +.glyphicon-heart:before { + content: "\e005"; +} + +.glyphicon-star:before { + content: "\e006"; +} + +.glyphicon-star-empty:before { + content: "\e007"; +} + +.glyphicon-user:before { + content: "\e008"; +} + +.glyphicon-film:before { + content: "\e009"; +} + +.glyphicon-th-large:before { + content: "\e010"; +} + +.glyphicon-th:before { + content: "\e011"; +} + +.glyphicon-th-list:before { + content: "\e012"; +} + +.glyphicon-ok:before { + content: "\e013"; +} + +.glyphicon-remove:before { + content: "\e014"; +} + +.glyphicon-zoom-in:before { + content: "\e015"; +} + +.glyphicon-zoom-out:before { + content: "\e016"; +} + +.glyphicon-off:before { + content: "\e017"; +} + +.glyphicon-signal:before { + content: "\e018"; +} + +.glyphicon-cog:before { + content: "\e019"; +} + +.glyphicon-trash:before { + content: "\e020"; +} + +.glyphicon-home:before { + content: "\e021"; +} + +.glyphicon-file:before { + content: "\e022"; +} + +.glyphicon-time:before { + content: "\e023"; +} + +.glyphicon-road:before { + content: "\e024"; +} + +.glyphicon-download-alt:before { + content: "\e025"; +} + +.glyphicon-download:before { + content: "\e026"; +} + +.glyphicon-upload:before { + content: "\e027"; +} + +.glyphicon-inbox:before { + content: "\e028"; +} + +.glyphicon-play-circle:before { + content: "\e029"; +} + +.glyphicon-repeat:before { + content: "\e030"; +} + +.glyphicon-refresh:before { + content: "\e031"; +} + +.glyphicon-list-alt:before { + content: "\e032"; +} + +.glyphicon-lock:before { + content: "\e033"; +} + +.glyphicon-flag:before { + content: "\e034"; +} + +.glyphicon-headphones:before { + content: "\e035"; +} + +.glyphicon-volume-off:before { + content: "\e036"; +} + +.glyphicon-volume-down:before { + content: "\e037"; +} + +.glyphicon-volume-up:before { + content: "\e038"; +} + +.glyphicon-qrcode:before { + content: "\e039"; +} + +.glyphicon-barcode:before { + content: "\e040"; +} + +.glyphicon-tag:before { + content: "\e041"; +} + +.glyphicon-tags:before { + content: "\e042"; +} + +.glyphicon-book:before { + content: "\e043"; +} + +.glyphicon-bookmark:before { + content: "\e044"; +} + +.glyphicon-print:before { + content: "\e045"; +} + +.glyphicon-camera:before { + content: "\e046"; +} + +.glyphicon-font:before { + content: "\e047"; +} + +.glyphicon-bold:before { + content: "\e048"; +} + +.glyphicon-italic:before { + content: "\e049"; +} + +.glyphicon-text-height:before { + content: "\e050"; +} + +.glyphicon-text-width:before { + content: "\e051"; +} + +.glyphicon-align-left:before { + content: "\e052"; +} + +.glyphicon-align-center:before { + content: "\e053"; +} + +.glyphicon-align-right:before { + content: "\e054"; +} + +.glyphicon-align-justify:before { + content: "\e055"; +} + +.glyphicon-list:before { + content: "\e056"; +} + +.glyphicon-indent-left:before { + content: "\e057"; +} + +.glyphicon-indent-right:before { + content: "\e058"; +} + +.glyphicon-facetime-video:before { + content: "\e059"; +} + +.glyphicon-picture:before { + content: "\e060"; +} + +.glyphicon-map-marker:before { + content: "\e062"; +} + +.glyphicon-adjust:before { + content: "\e063"; +} + +.glyphicon-tint:before { + content: "\e064"; +} + +.glyphicon-edit:before { + content: "\e065"; +} + +.glyphicon-share:before { + content: "\e066"; +} + +.glyphicon-check:before { + content: "\e067"; +} + +.glyphicon-move:before { + content: "\e068"; +} + +.glyphicon-step-backward:before { + content: "\e069"; +} + +.glyphicon-fast-backward:before { + content: "\e070"; +} + +.glyphicon-backward:before { + content: "\e071"; +} + +.glyphicon-play:before { + content: "\e072"; +} + +.glyphicon-pause:before { + content: "\e073"; +} + +.glyphicon-stop:before { + content: "\e074"; +} + +.glyphicon-forward:before { + content: "\e075"; +} + +.glyphicon-fast-forward:before { + content: "\e076"; +} + +.glyphicon-step-forward:before { + content: "\e077"; +} + +.glyphicon-eject:before { + content: "\e078"; +} + +.glyphicon-chevron-left:before { + content: "\e079"; +} + +.glyphicon-chevron-right:before { + content: "\e080"; +} + +.glyphicon-plus-sign:before { + content: "\e081"; +} + +.glyphicon-minus-sign:before { + content: "\e082"; +} + +.glyphicon-remove-sign:before { + content: "\e083"; +} + +.glyphicon-ok-sign:before { + content: "\e084"; +} + +.glyphicon-question-sign:before { + content: "\e085"; +} + +.glyphicon-info-sign:before { + content: "\e086"; +} + +.glyphicon-screenshot:before { + content: "\e087"; +} + +.glyphicon-remove-circle:before { + content: "\e088"; +} + +.glyphicon-ok-circle:before { + content: "\e089"; +} + +.glyphicon-ban-circle:before { + content: "\e090"; +} + +.glyphicon-arrow-left:before { + content: "\e091"; +} + +.glyphicon-arrow-right:before { + content: "\e092"; +} + +.glyphicon-arrow-up:before { + content: "\e093"; +} + +.glyphicon-arrow-down:before { + content: "\e094"; +} + +.glyphicon-share-alt:before { + content: "\e095"; +} + +.glyphicon-resize-full:before { + content: "\e096"; +} + +.glyphicon-resize-small:before { + content: "\e097"; +} + +.glyphicon-exclamation-sign:before { + content: "\e101"; +} + +.glyphicon-gift:before { + content: "\e102"; +} + +.glyphicon-leaf:before { + content: "\e103"; +} + +.glyphicon-fire:before { + content: "\e104"; +} + +.glyphicon-eye-open:before { + content: "\e105"; +} + +.glyphicon-eye-close:before { + content: "\e106"; +} + +.glyphicon-warning-sign:before { + content: "\e107"; +} + +.glyphicon-plane:before { + content: "\e108"; +} + +.glyphicon-calendar:before { + content: "\e109"; +} + +.glyphicon-random:before { + content: "\e110"; +} + +.glyphicon-comment:before { + content: "\e111"; +} + +.glyphicon-magnet:before { + content: "\e112"; +} + +.glyphicon-chevron-up:before { + content: "\e113"; +} + +.glyphicon-chevron-down:before { + content: "\e114"; +} + +.glyphicon-retweet:before { + content: "\e115"; +} + +.glyphicon-shopping-cart:before { + content: "\e116"; +} + +.glyphicon-folder-close:before { + content: "\e117"; +} + +.glyphicon-folder-open:before { + content: "\e118"; +} + +.glyphicon-resize-vertical:before { + content: "\e119"; +} + +.glyphicon-resize-horizontal:before { + content: "\e120"; +} + +.glyphicon-hdd:before { + content: "\e121"; +} + +.glyphicon-bullhorn:before { + content: "\e122"; +} + +.glyphicon-bell:before { + content: "\e123"; +} + +.glyphicon-certificate:before { + content: "\e124"; +} + +.glyphicon-thumbs-up:before { + content: "\e125"; +} + +.glyphicon-thumbs-down:before { + content: "\e126"; +} + +.glyphicon-hand-right:before { + content: "\e127"; +} + +.glyphicon-hand-left:before { + content: "\e128"; +} + +.glyphicon-hand-up:before { + content: "\e129"; +} + +.glyphicon-hand-down:before { + content: "\e130"; +} + +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} + +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} + +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} + +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} + +.glyphicon-globe:before { + content: "\e135"; +} + +.glyphicon-wrench:before { + content: "\e136"; +} + +.glyphicon-tasks:before { + content: "\e137"; +} + +.glyphicon-filter:before { + content: "\e138"; +} + +.glyphicon-briefcase:before { + content: "\e139"; +} + +.glyphicon-fullscreen:before { + content: "\e140"; +} + +.glyphicon-dashboard:before { + content: "\e141"; +} + +.glyphicon-paperclip:before { + content: "\e142"; +} + +.glyphicon-heart-empty:before { + content: "\e143"; +} + +.glyphicon-link:before { + content: "\e144"; +} + +.glyphicon-phone:before { + content: "\e145"; +} + +.glyphicon-pushpin:before { + content: "\e146"; +} + +.glyphicon-usd:before { + content: "\e148"; +} + +.glyphicon-gbp:before { + content: "\e149"; +} + +.glyphicon-sort:before { + content: "\e150"; +} + +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} + +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} + +.glyphicon-sort-by-order:before { + content: "\e153"; +} + +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} + +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} + +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} + +.glyphicon-unchecked:before { + content: "\e157"; +} + +.glyphicon-expand:before { + content: "\e158"; +} + +.glyphicon-collapse-down:before { + content: "\e159"; +} + +.glyphicon-collapse-up:before { + content: "\e160"; +} + +.glyphicon-log-in:before { + content: "\e161"; +} + +.glyphicon-flash:before { + content: "\e162"; +} + +.glyphicon-log-out:before { + content: "\e163"; +} + +.glyphicon-new-window:before { + content: "\e164"; +} + +.glyphicon-record:before { + content: "\e165"; +} + +.glyphicon-save:before { + content: "\e166"; +} + +.glyphicon-open:before { + content: "\e167"; +} + +.glyphicon-saved:before { + content: "\e168"; +} + +.glyphicon-import:before { + content: "\e169"; +} + +.glyphicon-export:before { + content: "\e170"; +} + +.glyphicon-send:before { + content: "\e171"; +} + +.glyphicon-floppy-disk:before { + content: "\e172"; +} + +.glyphicon-floppy-saved:before { + content: "\e173"; +} + +.glyphicon-floppy-remove:before { + content: "\e174"; +} + +.glyphicon-floppy-save:before { + content: "\e175"; +} + +.glyphicon-floppy-open:before { + content: "\e176"; +} + +.glyphicon-credit-card:before { + content: "\e177"; +} + +.glyphicon-transfer:before { + content: "\e178"; +} + +.glyphicon-cutlery:before { + content: "\e179"; +} + +.glyphicon-header:before { + content: "\e180"; +} + +.glyphicon-compressed:before { + content: "\e181"; +} + +.glyphicon-earphone:before { + content: "\e182"; +} + +.glyphicon-phone-alt:before { + content: "\e183"; +} + +.glyphicon-tower:before { + content: "\e184"; +} + +.glyphicon-stats:before { + content: "\e185"; +} + +.glyphicon-sd-video:before { + content: "\e186"; +} + +.glyphicon-hd-video:before { + content: "\e187"; +} + +.glyphicon-subtitles:before { + content: "\e188"; +} + +.glyphicon-sound-stereo:before { + content: "\e189"; +} + +.glyphicon-sound-dolby:before { + content: "\e190"; +} + +.glyphicon-sound-5-1:before { + content: "\e191"; +} + +.glyphicon-sound-6-1:before { + content: "\e192"; +} + +.glyphicon-sound-7-1:before { + content: "\e193"; +} + +.glyphicon-copyright-mark:before { + content: "\e194"; +} + +.glyphicon-registration-mark:before { + content: "\e195"; +} + +.glyphicon-cloud-download:before { + content: "\e197"; +} + +.glyphicon-cloud-upload:before { + content: "\e198"; +} + +.glyphicon-tree-conifer:before { + content: "\e199"; +} + +.glyphicon-tree-deciduous:before { + content: "\e200"; +} + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-size: 10px; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #000; + background-color: #fff; +} + +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +a { + color: #3071a9; + text-decoration: none; +} + +a:hover, +a:focus { + color: #2a6496; + text-decoration: underline; +} + +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +figure { + margin: 0; +} + +img { + vertical-align: middle; +} + +.img-responsive, +.thumbnail>img, +.thumbnail a>img, +.carousel-inner>.item>img, +.carousel-inner>.item>a>img { + display: block; + max-width: 100%; + height: auto; +} + +.img-rounded { + border-radius: 6px; +} + +.img-thumbnail { + display: inline-block; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} + +.img-circle { + border-radius: 50%; +} + +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} + +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} + +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #777; +} + +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 10px; + margin-bottom: 10px; +} + +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} + +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} + +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} + +h1, +.h1 { + font-size: 30px; +} + +h2, +.h2 { + font-size: 24px; +} + +h3, +.h3 { + font-size: 18px; +} + +h4, +.h4 { + font-size: 14px; +} + +h5, +.h5 { + font-size: 12px; +} + +h6, +.h6 { + font-size: 10px; +} + +p { + margin: 0 0 10px; +} + +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} + +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} + +small, +.small { + font-size: 85%; +} + +mark, +.mark { + padding: .2em; + background-color: #fcf8e3; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.text-center { + text-align: center; +} + +.text-justify { + text-align: justify; +} + +.text-nowrap { + white-space: nowrap; +} + +.text-lowercase { + text-transform: lowercase; +} + +.text-uppercase { + text-transform: uppercase; +} + +.text-capitalize { + text-transform: capitalize; +} + +.text-muted { + color: #777; +} + +.text-primary { + color: #428bca; +} + +a.text-primary:hover { + color: #3071a9; +} + +.text-success { + color: #3c763d; +} + +a.text-success:hover { + color: #2b542c; +} + +.text-info { + color: #31708f; +} + +a.text-info:hover { + color: #245269; +} + +.text-warning { + color: #8a6d3b; +} + +a.text-warning:hover { + color: #66512c; +} + +.text-danger { + color: #a94442; +} + +a.text-danger:hover { + color: #843534; +} + +.bg-primary { + color: #fff; + background-color: #428bca; +} + +a.bg-primary:hover { + background-color: #3071a9; +} + +.bg-success { + background-color: #dff0d8; +} + +a.bg-success:hover { + background-color: #c1e2b3; +} + +.bg-info { + background-color: #d9edf7; +} + +a.bg-info:hover { + background-color: #afd9ee; +} + +.bg-warning { + background-color: #fcf8e3; +} + +a.bg-warning:hover { + background-color: #f7ecb5; +} + +.bg-danger { + background-color: #f2dede; +} + +a.bg-danger:hover { + background-color: #e4b9b9; +} + +.page-header { + padding-bottom: 9px; + margin: 10px 0 10px; + border-bottom: 1px solid #eee; +} + +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} + +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} + +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; +} + +.list-inline>li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} + +dl { + margin-top: 0; + margin-bottom: 20px; +} + +dt, +dd { + line-height: 1.42857143; +} + +dt { + font-weight: bold; +} + +dd { + margin-left: 0; +} + +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + + .dl-horizontal dd { + margin-left: 180px; + } +} + +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777; +} + +.initialism { + font-size: 90%; + text-transform: uppercase; +} + +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +} + +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} + +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777; +} + +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} + +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0; +} + +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} + +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} + +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} + +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} + +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} + +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +} + +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + -webkit-box-shadow: none; + box-shadow: none; +} + +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} + +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} + +.container { + padding-right: 15px; + padding-left: 15px; +} + +/* +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +}*/ + +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +.row { + margin-right: -15px; + margin-left: -15px; +} + +.col-xs-1, +.col-sm-1, +.col-md-1, +.col-lg-1, +.col-xs-2, +.col-sm-2, +.col-md-2, +.col-lg-2, +.col-xs-3, +.col-sm-3, +.col-md-3, +.col-lg-3, +.col-xs-4, +.col-sm-4, +.col-md-4, +.col-lg-4, +.col-xs-5, +.col-sm-5, +.col-md-5, +.col-lg-5, +.col-xs-6, +.col-sm-6, +.col-md-6, +.col-lg-6, +.col-xs-7, +.col-sm-7, +.col-md-7, +.col-lg-7, +.col-xs-8, +.col-sm-8, +.col-md-8, +.col-lg-8, +.col-xs-9, +.col-sm-9, +.col-md-9, +.col-lg-9, +.col-xs-10, +.col-sm-10, +.col-md-10, +.col-lg-10, +.col-xs-11, +.col-sm-11, +.col-md-11, +.col-lg-11, +.col-xs-12, +.col-sm-12, +.col-md-12, +.col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} + +.col-xs-1, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-10, +.col-xs-11, +.col-xs-12 { + float: left; +} + +.col-xs-12 { + width: 100%; +} + +.col-xs-11 { + width: 91.66666667%; +} + +.col-xs-10 { + width: 83.33333333%; +} + +.col-xs-9 { + width: 75%; +} + +.col-xs-8 { + width: 66.66666667%; +} + +.col-xs-7 { + width: 58.33333333%; +} + +.col-xs-6 { + width: 50%; +} + +.col-xs-5 { + width: 41.66666667%; +} + +.col-xs-4 { + width: 33.33333333%; +} + +.col-xs-3 { + width: 25%; +} + +.col-xs-2 { + width: 16.66666667%; +} + +.col-xs-1 { + width: 8.33333333%; +} + +.col-xs-pull-12 { + right: 100%; +} + +.col-xs-pull-11 { + right: 91.66666667%; +} + +.col-xs-pull-10 { + right: 83.33333333%; +} + +.col-xs-pull-9 { + right: 75%; +} + +.col-xs-pull-8 { + right: 66.66666667%; +} + +.col-xs-pull-7 { + right: 58.33333333%; +} + +.col-xs-pull-6 { + right: 50%; +} + +.col-xs-pull-5 { + right: 41.66666667%; +} + +.col-xs-pull-4 { + right: 33.33333333%; +} + +.col-xs-pull-3 { + right: 25%; +} + +.col-xs-pull-2 { + right: 16.66666667%; +} + +.col-xs-pull-1 { + right: 8.33333333%; +} + +.col-xs-pull-0 { + right: auto; +} + +.col-xs-push-12 { + left: 100%; +} + +.col-xs-push-11 { + left: 91.66666667%; +} + +.col-xs-push-10 { + left: 83.33333333%; +} + +.col-xs-push-9 { + left: 75%; +} + +.col-xs-push-8 { + left: 66.66666667%; +} + +.col-xs-push-7 { + left: 58.33333333%; +} + +.col-xs-push-6 { + left: 50%; +} + +.col-xs-push-5 { + left: 41.66666667%; +} + +.col-xs-push-4 { + left: 33.33333333%; +} + +.col-xs-push-3 { + left: 25%; +} + +.col-xs-push-2 { + left: 16.66666667%; +} + +.col-xs-push-1 { + left: 8.33333333%; +} + +.col-xs-push-0 { + left: auto; +} + +.col-xs-offset-12 { + margin-left: 100%; +} + +.col-xs-offset-11 { + margin-left: 91.66666667%; +} + +.col-xs-offset-10 { + margin-left: 83.33333333%; +} + +.col-xs-offset-9 { + margin-left: 75%; +} + +.col-xs-offset-8 { + margin-left: 66.66666667%; +} + +.col-xs-offset-7 { + margin-left: 58.33333333%; +} + +.col-xs-offset-6 { + margin-left: 50%; +} + +.col-xs-offset-5 { + margin-left: 41.66666667%; +} + +.col-xs-offset-4 { + margin-left: 33.33333333%; +} + +.col-xs-offset-3 { + margin-left: 25%; +} + +.col-xs-offset-2 { + margin-left: 16.66666667%; +} + +.col-xs-offset-1 { + margin-left: 8.33333333%; +} + +.col-xs-offset-0 { + margin-left: 0; +} + +/* +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } +} +*/ + +table { + background-color: transparent; +} + +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777; + text-align: left; +} + +th { + text-align: left; +} + +.table { + //width: 100%; + //max-width: 100%; + margin-bottom: 20px; +} + +.table>thead>tr>th, +.table>tbody>tr>th, +.table>tfoot>tr>th, +.table>thead>tr>td, +.table>tbody>tr>td, +.table>tfoot>tr>td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} + +.table>thead>tr>th { + vertical-align: middle; + border-bottom: 2px solid #ddd; +} + +.table>caption+thead>tr:first-child>th, +.table>colgroup+thead>tr:first-child>th, +.table>thead:first-child>tr:first-child>th, +.table>caption+thead>tr:first-child>td, +.table>colgroup+thead>tr:first-child>td, +.table>thead:first-child>tr:first-child>td { + border-top: 0; +} + +.table>tbody+tbody { + border-top: 2px solid #ddd; +} + +.table .table { + background-color: #fff; +} + +.table-condensed>thead>tr>th, +.table-condensed>tbody>tr>th, +.table-condensed>tfoot>tr>th, +.table-condensed>thead>tr>td, +.table-condensed>tbody>tr>td, +.table-condensed>tfoot>tr>td { + padding: 5px; +} + +.table-bordered { + border: 1px solid #000; +} + +.table-bordered>thead>tr>th, +.table-bordered>tbody>tr>th, +.table-bordered>tfoot>tr>th, +.table-bordered>thead>tr>td, +.table-bordered>tbody>tr>td, +.table-bordered>tfoot>tr>td { + border: 1px solid #000; +} + +.table-bordered>thead>tr>th, +.table-bordered>thead>tr>td { + border-bottom-width: 2px; +} + +.table-striped>tbody>tr:nth-child(odd) { + background-color: #f9f9f9; +} + +.table-hover>tbody>tr:hover { + background-color: #f5f5f5; +} + +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} + +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} + +.table>thead>tr>td.active, +.table>tbody>tr>td.active, +.table>tfoot>tr>td.active, +.table>thead>tr>th.active, +.table>tbody>tr>th.active, +.table>tfoot>tr>th.active, +.table>thead>tr.active>td, +.table>tbody>tr.active>td, +.table>tfoot>tr.active>td, +.table>thead>tr.active>th, +.table>tbody>tr.active>th, +.table>tfoot>tr.active>th { + background-color: #f5f5f5; +} + +.table-hover>tbody>tr>td.active:hover, +.table-hover>tbody>tr>th.active:hover, +.table-hover>tbody>tr.active:hover>td, +.table-hover>tbody>tr:hover>.active, +.table-hover>tbody>tr.active:hover>th { + background-color: #e8e8e8; +} + +.table>thead>tr>td.success, +.table>tbody>tr>td.success, +.table>tfoot>tr>td.success, +.table>thead>tr>th.success, +.table>tbody>tr>th.success, +.table>tfoot>tr>th.success, +.table>thead>tr.success>td, +.table>tbody>tr.success>td, +.table>tfoot>tr.success>td, +.table>thead>tr.success>th, +.table>tbody>tr.success>th, +.table>tfoot>tr.success>th { + background-color: #dff0d8; +} + +.table-hover>tbody>tr>td.success:hover, +.table-hover>tbody>tr>th.success:hover, +.table-hover>tbody>tr.success:hover>td, +.table-hover>tbody>tr:hover>.success, +.table-hover>tbody>tr.success:hover>th { + background-color: #d0e9c6; +} + +.table>thead>tr>td.info, +.table>tbody>tr>td.info, +.table>tfoot>tr>td.info, +.table>thead>tr>th.info, +.table>tbody>tr>th.info, +.table>tfoot>tr>th.info, +.table>thead>tr.info>td, +.table>tbody>tr.info>td, +.table>tfoot>tr.info>td, +.table>thead>tr.info>th, +.table>tbody>tr.info>th, +.table>tfoot>tr.info>th { + background-color: #d9edf7; +} + +.table-hover>tbody>tr>td.info:hover, +.table-hover>tbody>tr>th.info:hover, +.table-hover>tbody>tr.info:hover>td, +.table-hover>tbody>tr:hover>.info, +.table-hover>tbody>tr.info:hover>th { + background-color: #c4e3f3; +} + +.table>thead>tr>td.warning, +.table>tbody>tr>td.warning, +.table>tfoot>tr>td.warning, +.table>thead>tr>th.warning, +.table>tbody>tr>th.warning, +.table>tfoot>tr>th.warning, +.table>thead>tr.warning>td, +.table>tbody>tr.warning>td, +.table>tfoot>tr.warning>td, +.table>thead>tr.warning>th, +.table>tbody>tr.warning>th, +.table>tfoot>tr.warning>th { + background-color: #fcf8e3; +} + +.table-hover>tbody>tr>td.warning:hover, +.table-hover>tbody>tr>th.warning:hover, +.table-hover>tbody>tr.warning:hover>td, +.table-hover>tbody>tr:hover>.warning, +.table-hover>tbody>tr.warning:hover>th { + background-color: #faf2cc; +} + +.table>thead>tr>td.danger, +.table>tbody>tr>td.danger, +.table>tfoot>tr>td.danger, +.table>thead>tr>th.danger, +.table>tbody>tr>th.danger, +.table>tfoot>tr>th.danger, +.table>thead>tr.danger>td, +.table>tbody>tr.danger>td, +.table>tfoot>tr.danger>td, +.table>thead>tr.danger>th, +.table>tbody>tr.danger>th, +.table>tfoot>tr.danger>th { + background-color: #f2dede; +} + +.table-hover>tbody>tr>td.danger:hover, +.table-hover>tbody>tr>th.danger:hover, +.table-hover>tbody>tr.danger:hover>td, +.table-hover>tbody>tr:hover>.danger, +.table-hover>tbody>tr.danger:hover>th { + background-color: #ebcccc; +} + +.table-responsive { + min-height: .01%; + overflow-x: auto; +} + +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + + .table-responsive>.table { + margin-bottom: 0; + } + + .table-responsive>.table>thead>tr>th, + .table-responsive>.table>tbody>tr>th, + .table-responsive>.table>tfoot>tr>th, + .table-responsive>.table>thead>tr>td, + .table-responsive>.table>tbody>tr>td, + .table-responsive>.table>tfoot>tr>td { + white-space: nowrap; + } + + .table-responsive>.table-bordered { + border: 0; + } + + .table-responsive>.table-bordered>thead>tr>th:first-child, + .table-responsive>.table-bordered>tbody>tr>th:first-child, + .table-responsive>.table-bordered>tfoot>tr>th:first-child, + .table-responsive>.table-bordered>thead>tr>td:first-child, + .table-responsive>.table-bordered>tbody>tr>td:first-child, + .table-responsive>.table-bordered>tfoot>tr>td:first-child { + border-left: 0; + } + + .table-responsive>.table-bordered>thead>tr>th:last-child, + .table-responsive>.table-bordered>tbody>tr>th:last-child, + .table-responsive>.table-bordered>tfoot>tr>th:last-child, + .table-responsive>.table-bordered>thead>tr>td:last-child, + .table-responsive>.table-bordered>tbody>tr>td:last-child, + .table-responsive>.table-bordered>tfoot>tr>td:last-child { + border-right: 0; + } + + .table-responsive>.table-bordered>tbody>tr:last-child>th, + .table-responsive>.table-bordered>tfoot>tr:last-child>th, + .table-responsive>.table-bordered>tbody>tr:last-child>td, + .table-responsive>.table-bordered>tfoot>tr:last-child>td { + border-bottom: 0; + } +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} + +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} + +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} + +input[type="file"] { + display: block; +} + +input[type="range"] { + display: block; + width: 100%; +} + +select[multiple], +select[size] { + height: auto; +} + +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555; +} + +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #000; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} + +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); +} + +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} + +.form-control:-ms-input-placeholder { + color: #999; +} + +.form-control::-webkit-input-placeholder { + color: #999; +} + +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eee; + opacity: 1; +} + +textarea.form-control { + height: auto; +} + +input[type="search"] { + -webkit-appearance: none; +} + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + line-height: 34px; + line-height: 1.42857143 \0; +} + +input[type="date"].input-sm, +input[type="time"].input-sm, +input[type="datetime-local"].input-sm, +input[type="month"].input-sm { + line-height: 30px; + line-height: 1.5 \0; +} + +input[type="date"].input-lg, +input[type="time"].input-lg, +input[type="datetime-local"].input-lg, +input[type="month"].input-lg { + line-height: 46px; + line-height: 1.33 \0; +} + +_:-ms-fullscreen, +:root input[type="date"], +_:-ms-fullscreen, +:root input[type="time"], +_:-ms-fullscreen, +:root input[type="datetime-local"], +_:-ms-fullscreen, +:root input[type="month"] { + line-height: 1.42857143; +} + +_:-ms-fullscreen.input-sm, +:root input[type="date"].input-sm, +_:-ms-fullscreen.input-sm, +:root input[type="time"].input-sm, +_:-ms-fullscreen.input-sm, +:root input[type="datetime-local"].input-sm, +_:-ms-fullscreen.input-sm, +:root input[type="month"].input-sm { + line-height: 1.5; +} + +_:-ms-fullscreen.input-lg, +:root input[type="date"].input-lg, +_:-ms-fullscreen.input-lg, +:root input[type="time"].input-lg, +_:-ms-fullscreen.input-lg, +:root input[type="datetime-local"].input-lg, +_:-ms-fullscreen.input-lg, +:root input[type="month"].input-lg { + line-height: 1.33; +} + +.form-group { + margin-bottom: 15px; +} + +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} + +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} + +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px; +} + +.radio+.radio, +.checkbox+.checkbox { + margin-top: -5px; +} + +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} + +.radio-inline+.radio-inline, +.checkbox-inline+.checkbox-inline { + margin-top: 0; + margin-left: 10px; +} + +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} + +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} + +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} + +.form-control-static { + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; +} + +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-right: 0; + padding-left: 0; +} + +.input-sm, +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +select.input-sm, +select.form-group-sm .form-control { + height: 30px; + line-height: 30px; +} + +textarea.input-sm, +textarea.form-group-sm .form-control, +select[multiple].input-sm, +select[multiple].form-group-sm .form-control { + height: auto; +} + +.input-lg, +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +select.input-lg, +select.form-group-lg .form-control { + height: 46px; + line-height: 46px; +} + +textarea.input-lg, +textarea.form-group-lg .form-control, +select[multiple].input-lg, +select[multiple].form-group-lg .form-control { + height: auto; +} + +.has-feedback { + position: relative; +} + +.has-feedback .form-control { + padding-right: 42.5px; +} + +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none; +} + +.input-lg+.form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} + +.input-sm+.form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} + +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #3c763d; +} + +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} + +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; +} + +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; +} + +.has-success .form-control-feedback { + color: #3c763d; +} + +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #8a6d3b; +} + +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} + +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; +} + +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b; +} + +.has-warning .form-control-feedback { + color: #8a6d3b; +} + +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442; +} + +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} + +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; +} + +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442; +} + +.has-error .form-control-feedback { + color: #a94442; +} + +.has-feedback label~.form-control-feedback { + top: 25px; +} + +.has-feedback label.sr-only~.form-control-feedback { + top: 0; +} + +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} + +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + + .form-inline .form-control-static { + display: inline-block; + } + + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + + .form-inline .input-group>.form-control { + width: 100%; + } + + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} + +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} + +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} + +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} + +.form-horizontal .control-label { + padding-top: 7px; + margin-bottom: 0; + text-align: right; +} + +.form-horizontal .control-label.text-left{ + text-align: left; +} + +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} + +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 14.3px; + } +} + +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + } +} + +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} + +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +.btn:hover, +.btn:focus, +.btn.focus { + color: #333; + text-decoration: none; +} + +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} + +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + pointer-events: none; + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; +} + +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} + +.btn-default:hover, +.btn-default:focus, +.btn-default.focus, +.btn-default:active, +.btn-default.active, +.open>.dropdown-toggle.btn-default { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} + +.btn-default:active, +.btn-default.active, +.open>.dropdown-toggle.btn-default { + background-image: none; +} + +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #fff; + border-color: #ccc; +} + +.btn-default .badge { + color: #fff; + background-color: #333; +} + +.btn-primary { + color: #fff; + background-color: #369; + border-color: #357ebd; +} + +.btn-primary:hover, +.btn-primary:focus, +.btn-primary.focus, +.btn-primary:active, +.btn-primary.active, +.open>.dropdown-toggle.btn-primary { + color: #fff; + background-color: #3071a9; + border-color: #285e8e; +} + +.btn-primary:active, +.btn-primary.active, +.open>.dropdown-toggle.btn-primary { + background-image: none; +} + +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #428bca; + border-color: #357ebd; +} + +.btn-primary .badge { + color: #428bca; + background-color: #fff; +} + +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} + +.btn-success:hover, +.btn-success:focus, +.btn-success.focus, +.btn-success:active, +.btn-success.active, +.open>.dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + border-color: #398439; +} + +.btn-success:active, +.btn-success.active, +.open>.dropdown-toggle.btn-success { + background-image: none; +} + +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; +} + +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} + +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} + +.btn-info:hover, +.btn-info:focus, +.btn-info.focus, +.btn-info:active, +.btn-info.active, +.open>.dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} + +.btn-info:active, +.btn-info.active, +.open>.dropdown-toggle.btn-info { + background-image: none; +} + +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; +} + +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} + +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} + +.btn-warning:hover, +.btn-warning:focus, +.btn-warning.focus, +.btn-warning:active, +.btn-warning.active, +.open>.dropdown-toggle.btn-warning { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} + +.btn-warning:active, +.btn-warning.active, +.open>.dropdown-toggle.btn-warning { + background-image: none; +} + +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; +} + +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} + +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} + +.btn-danger:hover, +.btn-danger:focus, +.btn-danger.focus, +.btn-danger:active, +.btn-danger.active, +.open>.dropdown-toggle.btn-danger { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} + +.btn-danger:active, +.btn-danger.active, +.open>.dropdown-toggle.btn-danger { + background-image: none; +} + +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a; +} + +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} + +.btn-link { + font-weight: normal; + color: #428bca; + border-radius: 0; +} + +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} + +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} + +.btn-link:hover, +.btn-link:focus { + color: #2a6496; + text-decoration: underline; + background-color: transparent; +} + +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777; + text-decoration: none; +} + +.btn-lg, +.btn-group-lg>.btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +.btn-sm, +.btn-group-sm>.btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.btn-xs, +.btn-group-xs>.btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.btn-block { + display: block; + width: 100%; +} + +.btn-block+.btn-block { + margin-top: 5px; +} + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} + +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + -o-transition: opacity .15s linear; + transition: opacity .15s linear; +} + +.fade.in { + opacity: 1; +} + +.collapse { + display: none; + visibility: hidden; +} + +.collapse.in { + display: block; + visibility: visible; +} + +tr.collapse.in { + display: table-row; +} + +tbody.collapse.in { + display: table-row-group; +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; + -webkit-transition-duration: .35s; + -o-transition-duration: .35s; + transition-duration: .35s; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; +} + +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} + +.dropdown { + position: relative; +} + +.dropdown-toggle:focus { + outline: 0; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} + +.dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} + +.dropdown-menu>li>a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} + +.dropdown-menu>li>a:hover, +.dropdown-menu>li>a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} + +.dropdown-menu>.active>a, +.dropdown-menu>.active>a:hover, +.dropdown-menu>.active>a:focus { + color: #fff; + text-decoration: none; + background-color: #428bca; + outline: 0; +} + +.dropdown-menu>.disabled>a, +.dropdown-menu>.disabled>a:hover, +.dropdown-menu>.disabled>a:focus { + color: #777; +} + +.dropdown-menu>.disabled>a:hover, +.dropdown-menu>.disabled>a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.open>.dropdown-menu { + display: block; +} + +.open>a { + outline: 0; +} + +.dropdown-menu-right { + right: 0; + left: auto; +} + +.dropdown-menu-left { + right: auto; + left: 0; +} + +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777; + white-space: nowrap; +} + +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} + +.pull-right>.dropdown-menu { + right: 0; + left: auto; +} + +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px solid; +} + +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; +} + +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} + +.btn-group>.btn, +.btn-group-vertical>.btn { + position: relative; + float: left; +} + +.btn-group>.btn:hover, +.btn-group-vertical>.btn:hover, +.btn-group>.btn:focus, +.btn-group-vertical>.btn:focus, +.btn-group>.btn:active, +.btn-group-vertical>.btn:active, +.btn-group>.btn.active, +.btn-group-vertical>.btn.active { + z-index: 2; +} + +.btn-group>.btn:focus, +.btn-group-vertical>.btn:focus { + outline: 0; +} + +.btn-group .btn+.btn, +.btn-group .btn+.btn-group, +.btn-group .btn-group+.btn, +.btn-group .btn-group+.btn-group { + margin-left: -1px; +} + +.btn-toolbar { + margin-left: -5px; +} + +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} + +.btn-toolbar>.btn, +.btn-toolbar>.btn-group, +.btn-toolbar>.input-group { + margin-left: 5px; +} + +.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +.btn-group>.btn:first-child { + margin-left: 0; +} + +.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group>.btn:last-child:not(:first-child), +.btn-group>.dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group>.btn-group { + float: left; +} + +.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn { + border-radius: 0; +} + +.btn-group>.btn-group:first-child>.btn:last-child, +.btn-group>.btn-group:first-child>.dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group>.btn-group:last-child>.btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + +.btn-group>.btn+.dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} + +.btn-group>.btn-lg+.dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} + +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} + +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} + +.btn .caret { + margin-left: 0; +} + +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} + +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} + +.btn-group-vertical>.btn, +.btn-group-vertical>.btn-group, +.btn-group-vertical>.btn-group>.btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} + +.btn-group-vertical>.btn-group>.btn { + float: none; +} + +.btn-group-vertical>.btn+.btn, +.btn-group-vertical>.btn+.btn-group, +.btn-group-vertical>.btn-group+.btn, +.btn-group-vertical>.btn-group+.btn-group { + margin-top: -1px; + margin-left: 0; +} + +.btn-group-vertical>.btn:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.btn-group-vertical>.btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical>.btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 4px; +} + +.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn { + border-radius: 0; +} + +.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child, +.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} + +.btn-group-justified>.btn, +.btn-group-justified>.btn-group { + display: table-cell; + float: none; + width: 1%; +} + +.btn-group-justified>.btn-group .btn { + width: 100%; +} + +.btn-group-justified>.btn-group .dropdown-menu { + left: auto; +} + +[data-toggle="buttons"]>.btn input[type="radio"], +[data-toggle="buttons"]>.btn-group>.btn input[type="radio"], +[data-toggle="buttons"]>.btn input[type="checkbox"], +[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} + +.input-group { + position: relative; + display: table; + border-collapse: separate; +} + +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} + +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} + +.input-group-lg>.form-control, +.input-group-lg>.input-group-addon, +.input-group-lg>.input-group-btn>.btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +select.input-group-lg>.form-control, +select.input-group-lg>.input-group-addon, +select.input-group-lg>.input-group-btn>.btn { + height: 46px; + line-height: 46px; +} + +textarea.input-group-lg>.form-control, +textarea.input-group-lg>.input-group-addon, +textarea.input-group-lg>.input-group-btn>.btn, +select[multiple].input-group-lg>.form-control, +select[multiple].input-group-lg>.input-group-addon, +select[multiple].input-group-lg>.input-group-btn>.btn { + height: auto; +} + +.input-group-sm>.form-control, +.input-group-sm>.input-group-addon, +.input-group-sm>.input-group-btn>.btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +select.input-group-sm>.form-control, +select.input-group-sm>.input-group-addon, +select.input-group-sm>.input-group-btn>.btn { + height: 30px; + line-height: 30px; +} + +textarea.input-group-sm>.form-control, +textarea.input-group-sm>.input-group-addon, +textarea.input-group-sm>.input-group-btn>.btn, +select[multiple].input-group-sm>.form-control, +select[multiple].input-group-sm>.input-group-addon, +select[multiple].input-group-sm>.input-group-btn>.btn { + height: auto; +} + +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} + +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} + +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 4px; +} + +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} + +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} + +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} + +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child>.btn, +.input-group-btn:first-child>.btn-group>.btn, +.input-group-btn:first-child>.dropdown-toggle, +.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child>.btn-group:not(:last-child)>.btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group-addon:first-child { + border-right: 0; +} + +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child>.btn, +.input-group-btn:last-child>.btn-group>.btn, +.input-group-btn:last-child>.dropdown-toggle, +.input-group-btn:first-child>.btn:not(:first-child), +.input-group-btn:first-child>.btn-group:not(:first-child)>.btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.input-group-addon:last-child { + border-left: 0; +} + +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} + +.input-group-btn>.btn { + position: relative; +} + +.input-group-btn>.btn+.btn { + margin-left: -1px; +} + +.input-group-btn>.btn:hover, +.input-group-btn>.btn:focus, +.input-group-btn>.btn:active { + z-index: 2; +} + +.input-group-btn:first-child>.btn, +.input-group-btn:first-child>.btn-group { + margin-right: -1px; +} + +.input-group-btn:last-child>.btn, +.input-group-btn:last-child>.btn-group { + margin-left: -1px; +} + +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav>li { + margin-right: 10px; + position: relative; + display: block; +} + +.nav>li>a { + position: relative; + display: block; + padding: 10px 15px; +} + +.nav>li>a:hover, +.nav>li>a:focus { + text-decoration: none; + background-color: #eee; +} + +.nav>li.disabled>a { + color: #777; +} + +.nav>li.disabled>a:hover, +.nav>li.disabled>a:focus { + color: #777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} + +.nav .open>a, +.nav .open>a:hover, +.nav .open>a:focus { + background-color: #eee; + border-color: #428bca; +} + +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} + +.nav>li>a>img { + max-width: none; +} + +.nav-tabs { + border-bottom: 1px solid #ddd; +} + +.nav-tabs>li { + float: left; + margin-bottom: -1px; +} + +.nav-tabs>li>a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} + +.nav-tabs>li>a:hover { + border-color: #eee #eee #ddd; +} + +.nav-tabs>li.active>a, +.nav-tabs>li.active>a:hover, +.nav-tabs>li.active>a:focus { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} + +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} + +.nav-tabs.nav-justified>li { + float: none; +} + +.nav-tabs.nav-justified>li>a { + margin-bottom: 5px; + text-align: center; +} + +.nav-tabs.nav-justified>.dropdown .dropdown-menu { + top: auto; + left: auto; +} + +.nav-tabs.nav-justified>li { + display: table-cell; + width: 1%; +} + +.nav-tabs.nav-justified>li>a { + margin-bottom: 0; +} + +.nav-tabs.nav-justified>li>a { + margin-right: 0; + border-radius: 4px; +} + +.nav-tabs.nav-justified>.active>a, +.nav-tabs.nav-justified>.active>a:hover, +.nav-tabs.nav-justified>.active>a:focus { + border: 1px solid #ddd; +} + +.nav-tabs.nav-justified>li>a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; +} + +.nav-tabs.nav-justified>.active>a, +.nav-tabs.nav-justified>.active>a:hover, +.nav-tabs.nav-justified>.active>a:focus { + border-bottom-color: #fff; +} + +.nav-pills>li { + float: left; +} + +.nav-pills>li>a { + border-radius: 4px; +} + +.nav-pills>li+li { + margin-left: 2px; +} + +.nav-pills>li.active>a, +.nav-pills>li.active>a:hover, +.nav-pills>li.active>a:focus { + color: #fff; + background-color: #3071a9; + /* Tab cell background color */ +} + +.nav-stacked>li { + float: none; +} + +.nav-stacked>li+li { + margin-top: 2px; + margin-left: 0; +} + +.nav-justified { + width: 100%; +} + +.nav-justified>li { + float: none; +} + +.nav-justified>li>a { + margin-bottom: 5px; + text-align: center; +} + +.nav-justified>.dropdown .dropdown-menu { + top: auto; + left: auto; +} + +.nav-justified>li { + display: table-cell; + width: 1%; +} + +.nav-justified>li>a { + margin-bottom: 0; +} + +.nav-tabs-justified { + border-bottom: 0; +} + +.nav-tabs-justified>li>a { + margin-right: 0; + border-radius: 4px; +} + +.nav-tabs-justified>.active>a, +.nav-tabs-justified>.active>a:hover, +.nav-tabs-justified>.active>a:focus { + border: 1px solid #ddd; +} + +.nav-tabs-justified>li>a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; +} + +.nav-tabs-justified>.active>a, +.nav-tabs-justified>.active>a:hover, +.nav-tabs-justified>.active>a:focus { + border-bottom-color: #fff; +} + +.tab-content>.tab-pane { + display: none; + visibility: hidden; +} + +.tab-content>.active { + display: block; + visibility: visible; +} + +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.navbar { + position: relative; + min-height: 30px; + border: 1px solid transparent; +} + +/* +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +*/ + +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} + +.navbar-collapse.in { + overflow-y: auto; +} + +/* +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + visibility: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +*/ + +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} + +@media (max-device-width: 480px) and (orientation: landscape) { + + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} + +.container>.navbar-header, +.container-fluid>.navbar-header, +.container>.navbar-collapse, +.container-fluid>.navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} + +@media (min-width: 768px) { + + .container>.navbar-header, + .container-fluid>.navbar-header, + .container>.navbar-collapse, + .container-fluid>.navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} + +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} + +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} + +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} + +@media (min-width: 768px) { + + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} + +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} + +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} + +.navbar-brand { + float: left; + height: 30px; + padding: 6px 15px; + font-size: 15px; + line-height: 18px; +} + +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} + +.navbar-brand>img { + display: block; +} + +@media (min-width: 768px) { + + .navbar>.container .navbar-brand, + .navbar>.container-fluid .navbar-brand { + margin-left: -15px; + } +} + +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} + +.navbar-toggle:focus { + outline: 0; +} + +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} + +.navbar-toggle .icon-bar+.icon-bar { + margin-top: 4px; +} + +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} + +.navbar-nav { + margin: 7.5px -15px; +} + +.navbar-nav>li>a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} + +.navbar-nav>li, +.navbar-nav { + float: left !important; +} + +.navbar-nav.navbar-right:last-child { + margin-right: -15px !important; +} + +.navbar-right { + float: right !important; +} + +/* +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} + + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } + +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +*/ + +.navbar-nav>li>.dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} + +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} + +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} + +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} + +.navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; +} + + +.navbar-left { + float: left !important; +} + +.navbar-right { + float: right !important; + margin-right: -15px; +} + +.navbar-right~.navbar-right { + margin-right: 0; +} + +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} + +.navbar-default .navbar-brand { + color: #777; +} + +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} + +.navbar-default .navbar-text { + color: #777; +} + +.navbar-default .navbar-nav>li>a { + color: #777; +} + +.navbar-default .navbar-nav>li>a:hover, +.navbar-default .navbar-nav>li>a:focus { + color: #333; + background-color: transparent; +} + +.navbar-default .navbar-nav>.active>a, +.navbar-default .navbar-nav>.active>a:hover, +.navbar-default .navbar-nav>.active>a:focus { + color: #555; + background-color: #e7e7e7; +} + +.navbar-default .navbar-nav>.disabled>a, +.navbar-default .navbar-nav>.disabled>a:hover, +.navbar-default .navbar-nav>.disabled>a:focus { + color: #ccc; + background-color: transparent; +} + +.navbar-default .navbar-toggle { + border-color: #ddd; +} + +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd; +} + +.navbar-default .navbar-toggle .icon-bar { + background-color: #888; +} + +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} + +.navbar-default .navbar-nav>.open>a, +.navbar-default .navbar-nav>.open>a:hover, +.navbar-default .navbar-nav>.open>a:focus { + color: #555; + background-color: #e7e7e7; +} + +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu>li>a { + color: #777; + } + + .navbar-default .navbar-nav .open .dropdown-menu>li>a:hover, + .navbar-default .navbar-nav .open .dropdown-menu>li>a:focus { + color: #333; + background-color: transparent; + } + + .navbar-default .navbar-nav .open .dropdown-menu>.active>a, + .navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover, + .navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus { + color: #555; + background-color: #e7e7e7; + } + + .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a, + .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover, + .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus { + color: #ccc; + background-color: transparent; + } +} + +.navbar-default .navbar-link { + color: #777; +} + +.navbar-default .navbar-link:hover { + color: #333; +} + +.navbar-default .btn-link { + color: #777; +} + +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333; +} + +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} + +.navbar-inverse { + background-color: #336699; + border-color: #080808; +} + +.navbar-inverse .navbar-brand { + color: #ffffff; +} + +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #ffffff; + background-color: transparent; +} + +.navbar-inverse .navbar-text { + color: #ffffff; +} + +.navbar-inverse .navbar-nav>li>a { + color: #ffffff; +} + +.navbar-inverse .navbar-nav>li>a:hover, +.navbar-inverse .navbar-nav>li>a:focus { + color: #ffffff; + background-color: transparent; +} + +.navbar-inverse .navbar-nav>.active>a, +.navbar-inverse .navbar-nav>.active>a:hover, +.navbar-inverse .navbar-nav>.active>a:focus { + color: #fff; + background-color: #080808; +} + +.navbar-inverse .navbar-nav>.disabled>a, +.navbar-inverse .navbar-nav>.disabled>a:hover, +.navbar-inverse .navbar-nav>.disabled>a:focus { + color: #444; + background-color: transparent; +} + +.navbar-inverse .navbar-toggle { + border-color: #333; +} + +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} + +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} + +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} + +.navbar-inverse .navbar-nav>.open>a, +.navbar-inverse .navbar-nav>.open>a:hover, +.navbar-inverse .navbar-nav>.open>a:focus { + color: #fff; + background-color: #080808; +} + +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header { + border-color: #080808; + } + + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>li>a { + color: #9d9d9d; + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus { + color: #fff; + background-color: transparent; + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a, + .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus { + color: #fff; + background-color: #080808; + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a, + .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus { + color: #444; + background-color: transparent; + } +} + +.navbar-inverse .navbar-link { + color: #9d9d9d; +} + +.navbar-inverse .navbar-link:hover { + color: #fff; +} + +.navbar-inverse .btn-link { + color: #9d9d9d; +} + +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} + +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; +} + +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} + +.breadcrumb>li { + display: inline-block; +} + +.breadcrumb>li+li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} + +.breadcrumb>.active { + color: #777; +} + +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} + +.pagination>li { + display: inline; +} + +.pagination>li>a, +.pagination>li>span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #428bca; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} + +.pagination>li:first-child>a, +.pagination>li:first-child>span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +.pagination>li:last-child>a, +.pagination>li:last-child>span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.pagination>li>a:hover, +.pagination>li>span:hover, +.pagination>li>a:focus, +.pagination>li>span:focus { + color: #2a6496; + background-color: #eee; + border-color: #ddd; +} + +.pagination>.active>a, +.pagination>.active>span, +.pagination>.active>a:hover, +.pagination>.active>span:hover, +.pagination>.active>a:focus, +.pagination>.active>span:focus { + z-index: 2; + color: #fff; + cursor: default; + background-color: #3071a9; + border-color: #428bca; +} + +.pagination>.disabled>span, +.pagination>.disabled>span:hover, +.pagination>.disabled>span:focus, +.pagination>.disabled>a, +.pagination>.disabled>a:hover, +.pagination>.disabled>a:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} + +.pagination-lg>li>a, +.pagination-lg>li>span { + padding: 10px 16px; + font-size: 18px; +} + +.pagination-lg>li:first-child>a, +.pagination-lg>li:first-child>span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} + +.pagination-lg>li:last-child>a, +.pagination-lg>li:last-child>span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} + +.pagination-sm>li>a, +.pagination-sm>li>span { + padding: 5px 10px; + font-size: 12px; +} + +.pagination-sm>li:first-child>a, +.pagination-sm>li:first-child>span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.pagination-sm>li:last-child>a, +.pagination-sm>li:last-child>span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} + +.pager li { + display: inline; +} + +.pager li>a, +.pager li>span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; +} + +.pager li>a:hover, +.pager li>a:focus { + text-decoration: none; + background-color: #eee; +} + +.pager .next>a, +.pager .next>span { + float: right; +} + +.pager .previous>a, +.pager .previous>span { + float: left; +} + +.pager .disabled>a, +.pager .disabled>a:hover, +.pager .disabled>a:focus, +.pager .disabled>span { + color: #777; + cursor: not-allowed; + background-color: #fff; +} + +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} + +a.label:hover, +a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} + +.label:empty { + display: none; +} + +.btn .label { + position: relative; + top: -1px; +} + +.label-default { + background-color: #777; +} + +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} + +.label-primary { + background-color: #428bca; +} + +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #3071a9; +} + +.label-success { + background-color: #5cb85c; +} + +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} + +.label-info { + background-color: #5bc0de; +} + +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} + +.label-warning { + background-color: #f0ad4e; +} + +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} + +.label-danger { + background-color: #d9534f; +} + +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} + +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + background-color: #777; + border-radius: 10px; +} + +.badge:empty { + display: none; +} + +.btn .badge { + position: relative; + top: -1px; +} + +.btn-xs .badge { + top: 0; + padding: 1px 5px; +} + +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} + +a.list-group-item.active>.badge, +.nav-pills>.active>a>.badge { + color: #3071a9; + background-color: #fff; +} + +.nav-pills>li>a>.badge { + margin-left: 3px; +} + +.jumbotron { + margin-bottom: 10px; + color: inherit; + background-color: #eee; +} + +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} + +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} + +.jumbotron>hr { + border-top-color: #d5d5d5; +} + +.container .jumbotron, +.container-fluid .jumbotron { + border-radius: 6px; +} + +.jumbotron .container { + max-width: 100%; +} + +/*@media screen and (min-width: 768px) { + .jumbotron { + padding: 48px 0; + } + .container .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +}*/ + +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: border .2s ease-in-out; + -o-transition: border .2s ease-in-out; + transition: border .2s ease-in-out; +} + +.thumbnail>img, +.thumbnail a>img { + margin-right: auto; + margin-left: auto; +} + +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #428bca; +} + +.thumbnail .caption { + padding: 9px; + color: #333; +} + +.alert { + padding: 10px; + margin-bottom: 5px; + border: 1px solid transparent; + border-radius: 4px; +} + +.alert h4 { + margin-top: 0; + color: inherit; +} + +.alert .alert-link { + font-weight: bold; +} + +.alert>p, +.alert>ul { + margin-bottom: 0; +} + +.alert>p+p { + margin-top: 5px; +} + +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} + +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} + +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.alert-success hr { + border-top-color: #c9e2b3; +} + +.alert-success .alert-link { + color: #2b542c; +} + +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.alert-info hr { + border-top-color: #a6e1ec; +} + +.alert-info .alert-link { + color: #245269; +} + +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} + +.alert-warning hr { + border-top-color: #f7e1b5; +} + +.alert-warning .alert-link { + color: #66512c; +} + +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} + +.alert-danger hr { + border-top-color: #e4b9c0; +} + +.alert-danger .alert-link { + color: #843534; +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + + to { + background-position: 0 0; + } +} + +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + + to { + background-position: 0 0; + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + + to { + background-position: 0 0; + } +} + +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); +} + +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #428bca; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + -o-transition: width .6s ease; + transition: width .6s ease; +} + +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} + +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} + +.progress-bar-success { + background-color: #5cb85c; +} + +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} + +.progress-bar-info { + background-color: #5bc0de; +} + +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} + +.progress-bar-warning { + background-color: #f0ad4e; +} + +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} + +.progress-bar-danger { + background-color: #d9534f; +} + +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} + +.media { + margin-top: 15px; +} + +.media:first-child { + margin-top: 0; +} + +.media-right, +.media>.pull-right { + padding-left: 10px; +} + +.media-left, +.media>.pull-left { + padding-right: 10px; +} + +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} + +.media-middle { + vertical-align: middle; +} + +.media-bottom { + vertical-align: bottom; +} + +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} + +.media-list { + padding-left: 0; + list-style: none; +} + +.list-group { + padding-left: 0; + margin-bottom: 20px; +} + +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} + +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} + +.list-group-item>.badge { + float: right; +} + +.list-group-item>.badge+.badge { + margin-right: 5px; +} + +a.list-group-item { + color: #555; +} + +a.list-group-item .list-group-item-heading { + color: #333; +} + +a.list-group-item:hover, +a.list-group-item:focus { + color: #555; + text-decoration: none; + background-color: #f5f5f5; +} + +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777; + cursor: not-allowed; + background-color: #eee; +} + +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} + +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777; +} + +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #428bca; + border-color: #428bca; +} + +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading>small, +.list-group-item.active:hover .list-group-item-heading>small, +.list-group-item.active:focus .list-group-item-heading>small, +.list-group-item.active .list-group-item-heading>.small, +.list-group-item.active:hover .list-group-item-heading>.small, +.list-group-item.active:focus .list-group-item-heading>.small { + color: inherit; +} + +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #e1edf7; +} + +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} + +a.list-group-item-success { + color: #3c763d; +} + +a.list-group-item-success .list-group-item-heading { + color: inherit; +} + +a.list-group-item-success:hover, +a.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} + +a.list-group-item-success.active, +a.list-group-item-success.active:hover, +a.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} + +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} + +a.list-group-item-info { + color: #31708f; +} + +a.list-group-item-info .list-group-item-heading { + color: inherit; +} + +a.list-group-item-info:hover, +a.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} + +a.list-group-item-info.active, +a.list-group-item-info.active:hover, +a.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} + +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} + +a.list-group-item-warning { + color: #8a6d3b; +} + +a.list-group-item-warning .list-group-item-heading { + color: inherit; +} + +a.list-group-item-warning:hover, +a.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} + +a.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} + +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} + +a.list-group-item-danger { + color: #a94442; +} + +a.list-group-item-danger .list-group-item-heading { + color: inherit; +} + +a.list-group-item-danger:hover, +a.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} + +a.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} + +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} + +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} + +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} + +.panel-body { + padding: 15px; +} + +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + cursor: pointer; +} + +.panel-heading>.dropdown .dropdown-toggle { + color: inherit; +} + +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} + +.panel-title>a { + color: inherit; +} + +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} + +.panel>.list-group, +.panel>.panel-collapse>.list-group { + margin-bottom: 0; +} + +.panel>.list-group .list-group-item, +.panel>.panel-collapse>.list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} + +.panel>.list-group:first-child .list-group-item:first-child, +.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} + +.panel>.list-group:last-child .list-group-item:last-child, +.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} + +.panel-heading+.list-group .list-group-item:first-child { + border-top-width: 0; +} + +.list-group+.panel-footer { + border-top-width: 0; +} + +.panel>.table, +.panel>.table-responsive>.table, +.panel>.panel-collapse>.table { + margin-bottom: 0; +} + +.panel>.table caption, +.panel>.table-responsive>.table caption, +.panel>.panel-collapse>.table caption { + padding-right: 15px; + padding-left: 15px; +} + +.panel>.table:first-child, +.panel>.table-responsive:first-child>.table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} + +.panel>.table:first-child>thead:first-child>tr:first-child, +.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child, +.panel>.table:first-child>tbody:first-child>tr:first-child, +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} + +.panel>.table:first-child>thead:first-child>tr:first-child td:first-child, +.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child, +.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child, +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child, +.panel>.table:first-child>thead:first-child>tr:first-child th:first-child, +.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child, +.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child, +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child { + border-top-left-radius: 3px; +} + +.panel>.table:first-child>thead:first-child>tr:first-child td:last-child, +.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child, +.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child, +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child, +.panel>.table:first-child>thead:first-child>tr:first-child th:last-child, +.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child, +.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child, +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child { + border-top-right-radius: 3px; +} + +.panel>.table:last-child, +.panel>.table-responsive:last-child>.table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} + +.panel>.table:last-child>tbody:last-child>tr:last-child, +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child, +.panel>.table:last-child>tfoot:last-child>tr:last-child, +.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} + +.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child, +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child, +.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child, +.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child, +.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child, +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child, +.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child, +.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} + +.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child, +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child, +.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child, +.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child, +.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child, +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child, +.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child, +.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} + +.panel>.panel-body+.table, +.panel>.panel-body+.table-responsive, +.panel>.table+.panel-body, +.panel>.table-responsive+.panel-body { + border-top: 1px solid #ddd; +} + +.panel>.table>tbody:first-child>tr:first-child th, +.panel>.table>tbody:first-child>tr:first-child td { + border-top: 0; +} + +.panel>.table-bordered, +.panel>.table-responsive>.table-bordered { + border: 0; +} + +.panel>.table-bordered>thead>tr>th:first-child, +.panel>.table-responsive>.table-bordered>thead>tr>th:first-child, +.panel>.table-bordered>tbody>tr>th:first-child, +.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child, +.panel>.table-bordered>tfoot>tr>th:first-child, +.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child, +.panel>.table-bordered>thead>tr>td:first-child, +.panel>.table-responsive>.table-bordered>thead>tr>td:first-child, +.panel>.table-bordered>tbody>tr>td:first-child, +.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child, +.panel>.table-bordered>tfoot>tr>td:first-child, +.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child { + border-left: 0; +} + +.panel>.table-bordered>thead>tr>th:last-child, +.panel>.table-responsive>.table-bordered>thead>tr>th:last-child, +.panel>.table-bordered>tbody>tr>th:last-child, +.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child, +.panel>.table-bordered>tfoot>tr>th:last-child, +.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child, +.panel>.table-bordered>thead>tr>td:last-child, +.panel>.table-responsive>.table-bordered>thead>tr>td:last-child, +.panel>.table-bordered>tbody>tr>td:last-child, +.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child, +.panel>.table-bordered>tfoot>tr>td:last-child, +.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child { + border-right: 0; +} + +.panel>.table-bordered>thead>tr:first-child>td, +.panel>.table-responsive>.table-bordered>thead>tr:first-child>td, +.panel>.table-bordered>tbody>tr:first-child>td, +.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td, +.panel>.table-bordered>thead>tr:first-child>th, +.panel>.table-responsive>.table-bordered>thead>tr:first-child>th, +.panel>.table-bordered>tbody>tr:first-child>th, +.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th { + border-bottom: 0; +} + +.panel>.table-bordered>tbody>tr:last-child>td, +.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td, +.panel>.table-bordered>tfoot>tr:last-child>td, +.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td, +.panel>.table-bordered>tbody>tr:last-child>th, +.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th, +.panel>.table-bordered>tfoot>tr:last-child>th, +.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th { + border-bottom: 0; +} + +.panel>.table-responsive { + margin-bottom: 0; + border: 0; +} + +.panel-group { + margin-bottom: 20px; +} + +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} + +.panel-group .panel+.panel { + margin-top: 5px; +} + +.panel-group .panel-heading { + border-bottom: 0; +} + +.panel-group .panel-heading+.panel-collapse>.panel-body, +.panel-group .panel-heading+.panel-collapse>.list-group { + border-top: 1px solid #ddd; +} + +.panel-group .panel-footer { + border-top: 0; +} + +.panel-group .panel-footer+.panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} + +.panel-default { + border-color: #ddd; +} + +.panel-default>.panel-heading { + color: #333; + background-color: #f5f5f5; + border-color: #ddd; +} + +.panel-default>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #ddd; +} + +.panel-default>.panel-heading .badge { + color: #f5f5f5; + background-color: #333; +} + +.panel-default>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #ddd; +} + +.panel-primary { + border-color: #428bca; +} + +.panel-primary>.panel-heading { + color: #fff; + background-color: #428bca; + border-color: #428bca; +} + +.panel-primary>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #428bca; +} + +.panel-primary>.panel-heading .badge { + color: #428bca; + background-color: #fff; +} + +.panel-primary>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #428bca; +} + +.panel-success { + border-color: #d6e9c6; +} + +.panel-success>.panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.panel-success>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #d6e9c6; +} + +.panel-success>.panel-heading .badge { + color: #dff0d8; + background-color: #3c763d; +} + +.panel-success>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #d6e9c6; +} + +.panel-info { + border-color: #bce8f1; +} + +.panel-info>.panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.panel-info>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #bce8f1; +} + +.panel-info>.panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} + +.panel-info>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #bce8f1; +} + +.panel-warning { + border-color: #faebcc; +} + +.panel-warning>.panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} + +.panel-warning>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #faebcc; +} + +.panel-warning>.panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b; +} + +.panel-warning>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #faebcc; +} + +.panel-danger { + border-color: #ebccd1; +} + +.panel-danger>.panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} + +.panel-danger>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #ebccd1; +} + +.panel-danger>.panel-heading .badge { + color: #f2dede; + background-color: #a94442; +} + +.panel-danger>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #ebccd1; +} + +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} + +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} + +.embed-responsive.embed-responsive-16by9 { + padding-bottom: 56.25%; +} + +.embed-responsive.embed-responsive-4by3 { + padding-bottom: 75%; +} + +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} + +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15); +} + +.well-lg { + padding: 24px; + border-radius: 6px; +} + +.well-sm { + padding: 9px; + border-radius: 3px; +} + +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2; +} + +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} + +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} + +.modal-open { + overflow: hidden; +} + +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + display: none; + overflow: hidden; + -webkit-overflow-scrolling: touch; + outline: 0; +} + +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); +} + +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} + +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} + +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} + +.modal-content { + position: relative; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: 0; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5); +} + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: #000; +} + +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} + +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: .5; +} + +.modal-header { + min-height: 16.42857143px; + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} + +.modal-header .close { + margin-top: -2px; +} + +.modal-title { + margin: 0; + line-height: 1.42857143; +} + +.modal-body { + position: relative; + padding: 15px; +} + +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} + +.modal-footer .btn+.btn { + margin-bottom: 0; + margin-left: 5px; +} + +.modal-footer .btn-group .btn+.btn { + margin-left: -1px; +} + +.modal-footer .btn-block+.btn-block { + margin-left: 0; +} + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + } + + .modal-sm { + width: 300px; + } +} + +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} + +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-size: 12px; + line-height: 1.4; + visibility: visible; + filter: alpha(opacity=0); + opacity: 0; +} + +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9; +} + +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} + +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} + +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} + +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} + +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + text-decoration: none; + background-color: #000; + border-radius: 4px; +} + +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} + +.tooltip.top-left .tooltip-arrow { + bottom: 0; + left: 5px; + border-width: 5px 5px 0; + border-top-color: #000; +} + +.tooltip.top-right .tooltip-arrow { + right: 5px; + bottom: 0; + border-width: 5px 5px 0; + border-top-color: #000; +} + +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} + +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} + +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} + +.tooltip.bottom-left .tooltip-arrow { + top: 0; + left: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} + +.tooltip.bottom-right .tooltip-arrow { + top: 0; + right: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); +} + +.popover.top { + margin-top: -10px; +} + +.popover.right { + margin-left: 10px; +} + +.popover.bottom { + margin-top: 10px; +} + +.popover.left { + margin-left: -10px; +} + +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} + +.popover-content { + padding: 9px 14px; +} + +.popover>.arrow, +.popover>.arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.popover>.arrow { + border-width: 11px; +} + +.popover>.arrow:after { + content: ""; + border-width: 10px; +} + +.popover.top>.arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} + +.popover.top>.arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} + +.popover.right>.arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, .25); + border-left-width: 0; +} + +.popover.right>.arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} + +.popover.bottom>.arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} + +.popover.bottom>.arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} + +.popover.left>.arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, .25); +} + +.popover.left>.arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} + +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-inner>.item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; + transition: .6s ease-in-out left; +} + +.carousel-inner>.item>img, +.carousel-inner>.item>a>img { + line-height: 1; +} + +@media all and (transform-3d), +(-webkit-transform-3d) { + .carousel-inner>.item { + -webkit-transition: -webkit-transform .6s ease-in-out; + -o-transition: -o-transform .6s ease-in-out; + transition: transform .6s ease-in-out; + + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000; + perspective: 1000; + } + + .carousel-inner>.item.next, + .carousel-inner>.item.active.right { + left: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + + .carousel-inner>.item.prev, + .carousel-inner>.item.active.left { + left: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + + .carousel-inner>.item.next.left, + .carousel-inner>.item.prev.right, + .carousel-inner>.item.active { + left: 0; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.carousel-inner>.active, +.carousel-inner>.next, +.carousel-inner>.prev { + display: block; +} + +.carousel-inner>.active { + left: 0; +} + +.carousel-inner>.next, +.carousel-inner>.prev { + position: absolute; + top: 0; + width: 100%; +} + +.carousel-inner>.next { + left: 100%; +} + +.carousel-inner>.prev { + left: -100%; +} + +.carousel-inner>.next.left, +.carousel-inner>.prev.right { + left: 0; +} + +.carousel-inner>.active.left { + left: -100%; +} + +.carousel-inner>.active.right { + left: 100%; +} + +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); + filter: alpha(opacity=50); + opacity: .5; +} + +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} + +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} + +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: 0; + opacity: .9; +} + +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; +} + +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} + +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} + +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + font-family: serif; +} + +.carousel-control .icon-prev:before { + content: '\2039'; +} + +.carousel-control .icon-next:before { + content: '\203a'; +} + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} + +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} + +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); +} + +.carousel-caption .btn { + text-shadow: none; +} + +/* +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -15px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -15px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +*/ +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical>.btn-group:before, +.btn-group-vertical>.btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} + +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical>.btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-footer:after { + clear: both; +} + +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} + +.pull-right { + float: right !important; +} + +.pull-left { + float: left !important; +} + +.hide { + display: none !important; +} + +.show { + display: block !important; +} + +.invisible { + visibility: hidden; +} + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.hidden { + display: none !important; + visibility: hidden !important; +} + +.affix { + position: fixed; +} + +@-ms-viewport { + width: device-width; +} + +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} + +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} + +/* +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +*/ +.visible-print-block { + display: none !important; +} + +@media print { + .visible-print-block { + display: block !important; + } +} + +.visible-print-inline { + display: none !important; +} + +@media print { + .visible-print-inline { + display: inline !important; + } +} + +.visible-print-inline-block { + display: none !important; +} + +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} + +@media print { + .hidden-print { + display: none !important; + } +} diff --git a/gn2/wqflask/static/new/css/box_plot.css b/gn2/wqflask/static/new/css/box_plot.css new file mode 100644 index 00000000..4c743b33 --- /dev/null +++ b/gn2/wqflask/static/new/css/box_plot.css @@ -0,0 +1,20 @@ +.box {
+ font: 10px sans-serif;
+}
+
+.box line,
+.box rect,
+.box circle {
+ fill: #fff;
+ stroke: #000;
+ stroke-width: 1.5px;
+}
+
+.box .center {
+ stroke-dasharray: 3,3;
+}
+
+.box .outlier {
+ fill: none;
+ stroke: #ccc;
+}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/broken_links.css b/gn2/wqflask/static/new/css/broken_links.css new file mode 100644 index 00000000..676f32d9 --- /dev/null +++ b/gn2/wqflask/static/new/css/broken_links.css @@ -0,0 +1,5 @@ + +.broken_link{ + color:red; + text-decoration: underline; +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/colorbox.css b/gn2/wqflask/static/new/css/colorbox.css new file mode 100644 index 00000000..8b9fb388 --- /dev/null +++ b/gn2/wqflask/static/new/css/colorbox.css @@ -0,0 +1,238 @@ +/* + Colorbox Core Style: + The following CSS is consistent between example themes and should not be altered. +*/ +#colorbox, +#cboxOverlay, +#cboxWrapper { + position: absolute; + top: 0; + left: 0; + z-index: 9999; + overflow: hidden; +} + +#cboxOverlay { + position: fixed; + width: 100%; + height: 100%; +} + +#cboxMiddleLeft, +#cboxBottomLeft { + clear: left; +} + +#cboxContent { + position: relative; +} + +#cboxLoadedContent { + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +#cboxTitle { + margin: 0; +} + +#cboxLoadingOverlay, +#cboxLoadingGraphic { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +#cboxPrevious, +#cboxNext, +#cboxClose, +#cboxSlideshow { + cursor: pointer; +} + +.cboxPhoto { + float: left; + margin: auto; + border: 0; + display: block; + max-width: none; + -ms-interpolation-mode: bicubic; +} + +.cboxIframe { + width: 100%; + height: 100%; + display: block; + border: 0; +} + +#colorbox, +#cboxContent, +#cboxLoadedContent { + box-sizing: content-box; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; +} + +/* + User Style: + Change the following styles to modify the appearance of Colorbox. They are + ordered & tabbed in a way that represents the nesting of the generated HTML. +*/ +#cboxOverlay { + background: #fff; +} + +#colorbox { + outline: 0; +} + +#cboxTopLeft { + width: 25px; + height: 25px; + background: url(images/border1.png) no-repeat 0 0; +} + +#cboxTopCenter { + height: 25px; + background: url(images/border1.png) repeat-x 0 -50px; +} + +#cboxTopRight { + width: 25px; + height: 25px; + background: url(images/border1.png) no-repeat -25px 0; +} + +#cboxBottomLeft { + width: 25px; + height: 25px; + background: url(images/border1.png) no-repeat 0 -25px; +} + +#cboxBottomCenter { + height: 25px; + background: url(images/border1.png) repeat-x 0 -75px; +} + +#cboxBottomRight { + width: 25px; + height: 25px; + background: url(images/border1.png) no-repeat -25px -25px; +} + +#cboxMiddleLeft { + width: 25px; + background: url(images/border2.png) repeat-y 0 0; +} + +#cboxMiddleRight { + width: 25px; + background: url(images/border2.png) repeat-y -25px 0; +} + +#cboxContent { + background: #fff; + overflow: hidden; +} + +.cboxIframe { + background: #fff; +} + +#cboxError { + padding: 50px; + border: 1px solid #ccc; +} + +#cboxLoadedContent { + margin-bottom: 20px; +} + +#cboxTitle { + position: absolute; + bottom: 0px; + left: 0; + text-align: center; + width: 100%; + color: #999; +} + +#cboxCurrent { + position: absolute; + bottom: 0px; + left: 100px; + color: #999; +} + +#cboxLoadingOverlay { + background: #fff url(images/loading.gif) no-repeat 5px 5px; +} + +/* these elements are buttons, and may need to have additional styles reset to avoid unwanted base styles */ +#cboxPrevious, +#cboxNext, +#cboxSlideshow, +#cboxClose { + border: 0; + padding: 0; + margin: 0; + overflow: visible; + width: auto; + background: none; +} + +/* avoid outlines on :active (mouseclick), but preserve outlines on :focus (tabbed navigating) */ +#cboxPrevious:active, +#cboxNext:active, +#cboxSlideshow:active, +#cboxClose:active { + outline: 0; +} + +#cboxSlideshow { + position: absolute; + bottom: 0px; + right: 42px; + color: #444; +} + +#cboxPrevious { + position: absolute; + bottom: 0px; + left: 0; + color: #444; +} + +#cboxNext { + position: absolute; + bottom: 0px; + left: 63px; + color: #444; +} + +#cboxClose { + position: absolute; + bottom: 0; + right: 0; + display: block; + color: #444; +} + +/* + The following fixes a problem where IE7 and IE8 replace a PNG's alpha transparency with a black fill + when an alpha filter (opacity change) is set on the element or ancestor element. This style is not applied to or needed in IE9. + See: http://jacklmoore.com/notes/ie-transparency-problems/ +*/ +.cboxIE #cboxTopLeft, +.cboxIE #cboxTopCenter, +.cboxIE #cboxTopRight, +.cboxIE #cboxBottomLeft, +.cboxIE #cboxBottomCenter, +.cboxIE #cboxBottomRight, +.cboxIE #cboxMiddleLeft, +.cboxIE #cboxMiddleRight { + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#00FFFFFF, endColorstr=#00FFFFFF); +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/corr_matrix.css b/gn2/wqflask/static/new/css/corr_matrix.css new file mode 100644 index 00000000..cd2b0a80 --- /dev/null +++ b/gn2/wqflask/static/new/css/corr_matrix.css @@ -0,0 +1,30 @@ +.vertical_label { + -webkit-transform: rotate(-90deg); + -moz-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + -o-transform: rotate(-90deg); + transform: rotate(-90deg); + color: #000 + font-size: 22px; + height: 50px; + width: 100px; + float: left; +} + +.chart { + +} + +.main text { + font: 10px sans-serif; +} + +.axis line, .axis path { + shape-rendering: crispEdges; + stroke: black; + fill: none; +} + +circle { + fill: steelblue; +} diff --git a/gn2/wqflask/static/new/css/corr_scatter_plot.css b/gn2/wqflask/static/new/css/corr_scatter_plot.css new file mode 100644 index 00000000..a2ebb252 --- /dev/null +++ b/gn2/wqflask/static/new/css/corr_scatter_plot.css @@ -0,0 +1,41 @@ +.nvd3 .nv-axis.nv-x text { + font-size: 16px; + font-weight: normal; + fill: black; +} + +.nvd3 .nv-axis.nv-y text { + font-size: 16px; + font-weight: normal; + fill: black; +} + +.nv-x .nv-axis g path.domain { + stroke: black; + stroke-width: 2; +} + +.nv-y .nv-axis g path.domain { + stroke: black; + stroke-width: 2; +} + +.nvd3 .nv-axis.nv-x path.domain { + stroke-opacity: 1; +} + +.nvd3 .nv-axis.nv-y path.domain { + stroke-opacity: 1; +} + +line.nv-regLine { + stroke: red; + stroke-width: 1; +} + +.nv-axisMin-x, +.nv-axisMax-x, +.nv-axisMin-y, +.nv-axisMax-y { + display: none; +} diff --git a/gn2/wqflask/static/new/css/corr_scatter_plot2.css b/gn2/wqflask/static/new/css/corr_scatter_plot2.css new file mode 100644 index 00000000..a2ebb252 --- /dev/null +++ b/gn2/wqflask/static/new/css/corr_scatter_plot2.css @@ -0,0 +1,41 @@ +.nvd3 .nv-axis.nv-x text { + font-size: 16px; + font-weight: normal; + fill: black; +} + +.nvd3 .nv-axis.nv-y text { + font-size: 16px; + font-weight: normal; + fill: black; +} + +.nv-x .nv-axis g path.domain { + stroke: black; + stroke-width: 2; +} + +.nv-y .nv-axis g path.domain { + stroke: black; + stroke-width: 2; +} + +.nvd3 .nv-axis.nv-x path.domain { + stroke-opacity: 1; +} + +.nvd3 .nv-axis.nv-y path.domain { + stroke-opacity: 1; +} + +line.nv-regLine { + stroke: red; + stroke-width: 1; +} + +.nv-axisMin-x, +.nv-axisMax-x, +.nv-axisMin-y, +.nv-axisMax-y { + display: none; +} diff --git a/gn2/wqflask/static/new/css/d3-tip.min.css b/gn2/wqflask/static/new/css/d3-tip.min.css new file mode 100644 index 00000000..3d253caf --- /dev/null +++ b/gn2/wqflask/static/new/css/d3-tip.min.css @@ -0,0 +1 @@ +.d3-tip{line-height:1;font-weight:bold;padding:12px;background:rgba(0,0,0,0.8);color:#fff;border-radius:2px;pointer-events:none}.d3-tip:after{box-sizing:border-box;display:inline;font-size:10px;width:100%;line-height:1;color:rgba(0,0,0,0.8);position:absolute;pointer-events:none}.d3-tip.n:after{content:"\25BC";margin:-1px 0 0 0;top:100%;left:0;text-align:center}.d3-tip.e:after{content:"\25C0";margin:-4px 0 0 0;top:50%;left:-8px}.d3-tip.s:after{content:"\25B2";margin:0 0 1px 0;top:-8px;left:0;text-align:center}.d3-tip.w:after{content:"\25B6";margin:-4px 0 0 -1px;top:50%;left:100%}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/d3panels.min.css b/gn2/wqflask/static/new/css/d3panels.min.css new file mode 100644 index 00000000..7db7b91f --- /dev/null +++ b/gn2/wqflask/static/new/css/d3panels.min.css @@ -0,0 +1 @@ +svg.qtlcharts,svg.d3panels{font-family:sans-serif;font-size:11pt}svg.qtlcharts .title text,svg.d3panels .title text{dominant-baseline:middle;fill:#0074d9;text-anchor:middle}svg.qtlcharts .y.axis text,svg.d3panels .y.axis text{dominant-baseline:middle;text-anchor:end}svg.qtlcharts .y.axis text.title,svg.d3panels .y.axis text.title{text-anchor:middle;fill:slateblue}svg.qtlcharts .x.axis text,svg.d3panels .x.axis text{dominant-baseline:hanging;text-anchor:middle}svg.qtlcharts .x.axis text.title,svg.d3panels .x.axis text.title{fill:slateblue}svg.qtlcharts line.axis.grid,svg.d3panels line.axis.grid{fill:none;stroke-width:1;pointer-events:none}svg.qtlcharts line.x.axis.grid,svg.d3panels line.x.axis.grid{stroke:#c8c8c8;stroke-width:3}svg.qtlcharts line.y.axis.grid,svg.d3panels line.y.axis.grid{stroke:white}svg.qtlcharts .extent,svg.d3panels .extent{fill:#cac;opacity:.3}svg.qtlcharts circle.selected,svg.d3panels circle.selected{fill:hotpink;opacity:1}svg.qtlcharts a,svg.d3panels a{color:#08c;text-decoration:none}svg.qtlcharts a:hover,svg.d3panels a:hover{color:#333;text-decoration:underline}svg.qtlcharts text.crosstab,svg.d3panels text.crosstab{dominant-baseline:middle;text-anchor:end}svg.qtlcharts text.crosstabtitle,svg.d3panels text.crosstabtitle{dominant-baseline:middle;text-anchor:center}.caption{font-family:Sans-serif;font-size:11pt;margin-left:60px;width:600px}div.d3panels-tooltip{position:absolute;text-align:center;width:max-content;height:min-content;padding:5px;font-size:16px;font-family:sans-serif;font-weight:bold;background:darkslateblue;color:white;text-align:center;border:0;border-radius:3px;pointer-events:none;z-index:999}div.d3panels-tooltip-tri{position:absolute;text-align:center;width:32px;height:32px;padding:0;font-size:16px;font-family:sans-serif;font-weight:bold;background:#ffffff00;color:darkslateblue;text-align:center;border:0;pointer-events:none;z-index:998}div.error p{font-family:sans-serif;font-size:16pt;font-weight:bold;padding:5px}div.qtlcharts{font-family:sans-serif;font-size:12pt}div.searchbox{font-family:Sans-serif;font-size:12pt;margin-left:60px}div.searchbox.inactive input{color:#888} diff --git a/gn2/wqflask/static/new/css/docs.css b/gn2/wqflask/static/new/css/docs.css new file mode 100644 index 00000000..665559e6 --- /dev/null +++ b/gn2/wqflask/static/new/css/docs.css @@ -0,0 +1,1080 @@ +/* Add additional stylesheets below +-------------------------------------------------- */ +/* + Bootstrap's documentation styles + Special styles for presenting Bootstrap's documentation and examples +*/ + + + +/* Body and structure +-------------------------------------------------- */ + +body { + position: relative; + padding-top: 0px; +} + +/* Code in headings */ +h3 code { + font-size: 14px; + font-weight: normal; +} + + + +/* Tweak navbar brand link to be super sleek +-------------------------------------------------- */ +/* +body > .navbar { + font-size: 12px; + font-weight: bold; +} +*/ + +/* Change the docs' brand */ + +body>.navbar .navbar-brand { + padding-right: 20px; + padding-left: 20px; + margin-left: 20px; + float: left; + font-weight: bold; + color: #ffffff; + text-shadow: 0 1px 0 rgba(255, 255, 255, .1), 0 0 30px rgba(255, 255, 255, .125); + -webkit-transition: all .2s linear; + -moz-transition: all .2s linear; + transition: all .2s linear; +} + +body>.navbar .brand:hover { + text-decoration: none; + text-shadow: 0 1px 0 rgba(255, 255, 255, .1), 0 0 30px rgba(255, 255, 255, .4); +} + + +/* Sections +-------------------------------------------------- */ + +/* padding for in-page bookmarks and fixed navbar */ +section { + padding-top: 0px; +} + +section>.page-header, +section>.lead { + color: #5a5a5a; +} + +section>ul li { + margin-bottom: 5px; +} + +/* Separators (hr) */ +.bs-docs-separator { + margin: 40px 0 39px; +} + +/* Faded out hr */ +hr.soften { + height: 1px; + margin: 70px 0; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0), rgba(0, 0, 0, .1), rgba(0, 0, 0, 0)); + background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0), rgba(0, 0, 0, .1), rgba(0, 0, 0, 0)); + background-image: -ms-linear-gradient(left, rgba(0, 0, 0, 0), rgba(0, 0, 0, .1), rgba(0, 0, 0, 0)); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0), rgba(0, 0, 0, .1), rgba(0, 0, 0, 0)); + border: 0; +} + + + +/* Jumbotrons +-------------------------------------------------- */ + +/* Base class +------------------------- */ +.jumbotron { + position: relative; + padding: 0px 0; + color: black; + text-align: left; + text-shadow: 0 1px 3px rgba(0, 0, 0, .4), 0 0 30px rgba(0, 0, 0, .075); + background: #d5d5d5; + /* Old browsers */ + +} + +.jumbotron h1 { + font-size: 60px; + font-weight: bold; + letter-spacing: -1px; + line-height: 1; +} + +.jumbotron p { + font-size: 20px; + font-weight: 300; + line-height: 20px; + margin-bottom: 10px; +} + +/* Link styles (used on .masthead-links as well) */ +.jumbotron a { + color: #336699; + color: rgba(255, 255, 255, .5); + -webkit-transition: all .2s ease-in-out; + -moz-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} + +.jumbotron a:hover { + color: #336699; + text-shadow: 0 0 10px rgba(255, 255, 255, .25); +} + +/* Download button */ +.masthead .btn { + padding: 14px 24px; + font-size: 24px; + font-weight: 200; + color: #fff; + /* redeclare to override the `.jumbotron a` */ + border: 0; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 5px rgba(0, 0, 0, .25); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 5px rgba(0, 0, 0, .25); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 5px rgba(0, 0, 0, .25); + -webkit-transition: none; + -moz-transition: none; + transition: none; +} + +.masthead .btn:hover { + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 5px rgba(0, 0, 0, .25); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 5px rgba(0, 0, 0, .25); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 5px rgba(0, 0, 0, .25); +} + +.masthead .btn:active { + -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, .1), 0 1px 0 rgba(255, 255, 255, .1); + -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, .1), 0 1px 0 rgba(255, 255, 255, .1); +} + + +/* Pattern overlay +------------------------- */ +.jumbotron .container { + position: relative; + z-index: 2; +} + +.jumbotron:after { + content: ''; + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + /*background: url(../img/bs-docs-masthead-pattern.png) repeat center center;*/ + opacity: .4; +} + +/* Masthead (docs home) +------------------------- */ +.masthead { + padding: 70px 0 80px; + margin-bottom: 0; + color: #fff; +} + +.masthead h1 { + font-size: 120px; + line-height: 1; + letter-spacing: -2px; +} + +.masthead p { + font-size: 40px; + font-weight: 200; + line-height: 1.25; +} + +/* Textual links in masthead */ +.masthead-links { + margin: 0; + list-style: none; +} + +.masthead-links li { + display: inline; + padding: 0 10px; + color: rgba(255, 255, 255, .25); +} + +/* Social proof buttons from GitHub & Twitter */ +.bs-docs-social { + padding: 15px 0; + text-align: center; + background-color: #f5f5f5; + border-top: 1px solid #fff; + border-bottom: 1px solid #ddd; +} + +/* Quick links on Home */ +.bs-docs-social-buttons { + margin-left: 0; + margin-bottom: 0; + padding-left: 0; + list-style: none; +} + +.bs-docs-social-buttons li { + display: inline-block; + padding: 5px 8px; + line-height: 1; + *display: inline; + *zoom: 1; +} + +/* Subhead (other pages) +------------------------- */ +.subhead { + text-align: left; + border-bottom: 1px solid #ddd; +} + +.subhead h1 { + font-size: 30px; +} + +.subhead p { + margin-bottom: 10px; +} + +.subhead .navbar { + display: none; +} + + + +/* Marketing section of Overview +-------------------------------------------------- */ + +.marketing { + text-align: center; + color: #5a5a5a; +} + +.marketing h1 { + margin: 60px 0 10px; + font-size: 60px; + font-weight: 200; + line-height: 1; + letter-spacing: -1px; +} + +.marketing h2 { + font-weight: 200; + margin-bottom: 5px; +} + +.marketing p { + font-size: 16px; + line-height: 1.5; +} + +.marketing .marketing-byline { + margin-bottom: 40px; + font-size: 20px; + font-weight: 300; + line-height: 25px; + color: #999; +} + +.marketing img { + display: block; + margin: 0 auto 30px; +} + + + +/* Footer +-------------------------------------------------- */ + +.footer { + padding: 70px 0; + margin-top: 70px; + border-top: 1px solid #e5e5e5; + background-color: #f5f5f5; +} + +.footer p { + margin-bottom: 0; + color: #777; +} + +.footer-links { + margin: 10px 0; +} + +.footer-links li { + display: inline; + margin-right: 10px; +} + + + +/* Special grid styles +-------------------------------------------------- */ + +.show-grid { + margin-top: 10px; + margin-bottom: 20px; +} + +.show-grid [class*="span"] { + background-color: #eee; + text-align: center; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + min-height: 40px; + line-height: 40px; +} + +.show-grid:hover [class*="span"] { + background: #ddd; +} + +.show-grid .show-grid { + margin-top: 0; + margin-bottom: 0; +} + +.show-grid .show-grid [class*="span"] { + background-color: #ccc; +} + + + +/* Mini layout previews +-------------------------------------------------- */ +.mini-layout { + border: 1px solid #ddd; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); + box-shadow: 0 1px 2px rgba(0, 0, 0, .075); +} + +.mini-layout, +.mini-layout .mini-layout-body, +.mini-layout.fluid .mini-layout-sidebar { + height: 300px; +} + +.mini-layout { + margin-bottom: 20px; + padding: 9px; +} + +.mini-layout div { + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +.mini-layout .mini-layout-body { + background-color: #dceaf4; + margin: 0 auto; + width: 70%; +} + +.mini-layout.fluid .mini-layout-sidebar, +.mini-layout.fluid .mini-layout-header, +.mini-layout.fluid .mini-layout-body { + float: left; +} + +.mini-layout.fluid .mini-layout-sidebar { + background-color: #bbd8e9; + width: 20%; +} + +.mini-layout.fluid .mini-layout-body { + width: 77.5%; + margin-left: 2.5%; +} + + + +/* Download page +-------------------------------------------------- */ + +.download .page-header { + margin-top: 36px; +} + +.page-header .toggle-all { + margin-top: 5px; +} + +/* Space out h3s when following a section */ +.download h3 { + margin-bottom: 5px; +} + +.download-builder input+h3, +.download-builder .checkbox+h3 { + margin-top: 9px; +} + +/* Fields for variables */ +.download-builder input[type=text] { + margin-bottom: 9px; + font-family: Menlo, Monaco, "Courier New", monospace; + font-size: 12px; + color: #d14; +} + +.download-builder input[type=text]:focus { + background-color: #fff; +} + +/* Custom, larger checkbox labels */ +.download .checkbox { + padding: 6px 10px 6px 25px; + font-size: 13px; + line-height: 18px; + color: #555; + background-color: #f9f9f9; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + cursor: pointer; +} + +.download .checkbox:hover { + color: #333; + background-color: #f5f5f5; +} + +.download .checkbox small { + font-size: 12px; + color: #777; +} + +/* Variables section */ +#variables label { + margin-bottom: 0; +} + +/* Giant download button */ +.download-btn { + margin: 36px 0 108px; +} + +#download p, +#download h4 { + max-width: 50%; + margin: 0 auto; + color: #999; + text-align: center; +} + +#download h4 { + margin-bottom: 0; +} + +#download p { + margin-bottom: 18px; +} + +.download-btn .btn { + display: block; + width: auto; + padding: 19px 24px; + margin-bottom: 27px; + font-size: 30px; + line-height: 1; + text-align: center; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + + + +/* Misc +-------------------------------------------------- */ + +/* Make tables spaced out a bit more */ +h2+table, +h3+table, +h4+table, +h2+.row { + margin-top: 5px; +} + +/* Example sites showcase */ +.example-sites { + xmargin-left: 20px; +} + +.example-sites img { + max-width: 100%; + margin: 0 auto; +} + +.scrollspy-example { + height: 200px; + overflow: auto; + position: relative; +} + + +/* Fake the :focus state to demo it */ +.focused { + border-color: rgba(82, 168, 236, .8); + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .1), 0 0 8px rgba(82, 168, 236, .6); + -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .1), 0 0 8px rgba(82, 168, 236, .6); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, .1), 0 0 8px rgba(82, 168, 236, .6); + outline: 0; +} + +/* For input sizes, make them display block */ +.docs-input-sizes select, +.docs-input-sizes input[type=text] { + display: block; + margin-bottom: 9px; +} + +/* Icons +------------------------- */ +.the-icons { + margin-left: 0; + list-style: none; +} + +.the-icons li { + float: left; + width: 25%; + line-height: 25px; +} + +.the-icons i:hover { + background-color: rgba(255, 0, 0, .25); +} + +/* Example page +------------------------- */ +.bootstrap-examples p { + font-size: 13px; + line-height: 18px; +} + +.bootstrap-examples .thumbnail { + margin-bottom: 9px; + background-color: #fff; +} + + + +/* Bootstrap code examples +-------------------------------------------------- */ + +/* Base class */ +.bs-docs-example { + position: relative; + margin: 15px 0; + padding: 39px 19px 14px; + *padding-top: 19px; + background-color: #fff; + border: 1px solid #ddd; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + + +/* Remove spacing between an example and it's code */ +.bs-docs-example+.prettyprint { + margin-top: -20px; + padding-top: 15px; +} + +/* Tweak examples +------------------------- */ +.bs-docs-example>p:last-child { + margin-bottom: 0; +} + +.bs-docs-example .table, +.bs-docs-example .progress, +.bs-docs-example .well, +.bs-docs-example .alert, +.bs-docs-example .hero-unit, +.bs-docs-example .pagination, +.bs-docs-example .navbar, +.bs-docs-example>.nav, +.bs-docs-example blockquote { + margin-bottom: 5px; +} + +.bs-docs-example .pagination { + margin-top: 0; +} + +.bs-navbar-top-example, +.bs-navbar-bottom-example { + z-index: 1; + padding: 0; + height: 90px; + overflow: hidden; + /* cut the drop shadows off */ +} + +.bs-navbar-top-example .navbar-fixed-top, +.bs-navbar-bottom-example .navbar-fixed-bottom { + margin-left: 0; + margin-right: 0; +} + +.bs-navbar-top-example { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} + +.bs-navbar-top-example:after { + top: auto; + bottom: -1px; + -webkit-border-radius: 0 4px 0 4px; + -moz-border-radius: 0 4px 0 4px; + border-radius: 0 4px 0 4px; +} + +.bs-navbar-bottom-example { + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} + +.bs-navbar-bottom-example .navbar { + margin-bottom: 0; +} + +form.bs-docs-example { + padding-bottom: 19px; +} + +/* Images */ +.bs-docs-example-images img { + margin: 10px; + display: inline-block; +} + +/* Tooltips */ +.bs-docs-tooltip-examples { + text-align: center; + margin: 0 0 10px; + list-style: none; +} + +.bs-docs-tooltip-examples li { + display: inline; + padding: 0 10px; +} + +/* Popovers */ +.bs-docs-example-popover { + padding-bottom: 24px; + background-color: #f9f9f9; +} + +.bs-docs-example-popover .popover { + position: relative; + display: block; + float: left; + width: 260px; + margin: 20px; +} + + + +/* Responsive docs +-------------------------------------------------- */ + +/* Utility classes table +------------------------- */ +.responsive-utilities th small { + display: block; + font-weight: normal; + color: #999; +} + +.responsive-utilities tbody th { + font-weight: normal; +} + +.responsive-utilities td { + text-align: center; +} + +.responsive-utilities td.is-visible { + color: #468847; + background-color: #dff0d8 !important; +} + +.responsive-utilities td.is-hidden { + color: #ccc; + background-color: #f9f9f9 !important; +} + +/* Responsive tests +------------------------- */ +.responsive-utilities-test { + margin-top: 5px; + margin-left: 0; + list-style: none; + overflow: hidden; + /* clear floats */ +} + +.responsive-utilities-test li { + position: relative; + float: left; + width: 25%; + height: 43px; + font-size: 14px; + font-weight: bold; + line-height: 43px; + color: #999; + text-align: center; + border: 1px solid #ddd; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.responsive-utilities-test li+li { + margin-left: 10px; +} + +.responsive-utilities-test span { + position: absolute; + top: -1px; + left: -1px; + right: -1px; + bottom: -1px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.responsive-utilities-test span { + color: #468847; + background-color: #dff0d8; + border: 1px solid #d6e9c6; +} + + + +/* Sidenav for Docs +-------------------------------------------------- */ + +.bs-docs-sidenav { + width: 228px; + margin: 30px 0 0; + padding: 0; + background-color: #fff; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, .065); + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, .065); + box-shadow: 0 1px 4px rgba(0, 0, 0, .065); +} + +.bs-docs-sidenav>li>a { + display: block; + *width: 190px; + margin: 0 0 -1px; + padding: 8px 14px; + border: 1px solid #e5e5e5; +} + +.bs-docs-sidenav>li:first-child>a { + -webkit-border-radius: 6px 6px 0 0; + -moz-border-radius: 6px 6px 0 0; + border-radius: 6px 6px 0 0; +} + +.bs-docs-sidenav>li:last-child>a { + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; +} + +.bs-docs-sidenav>.active>a { + position: relative; + z-index: 2; + padding: 9px 15px; + border: 0; + text-shadow: 0 1px 0 rgba(0, 0, 0, .15); + -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, .1), inset -1px 0 0 rgba(0, 0, 0, .1); + -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, .1), inset -1px 0 0 rgba(0, 0, 0, .1); + box-shadow: inset 1px 0 0 rgba(0, 0, 0, .1), inset -1px 0 0 rgba(0, 0, 0, .1); +} + +/* Chevrons */ +.bs-docs-sidenav .icon-chevron-right { + float: right; + margin-top: 2px; + margin-right: -6px; + opacity: .25; +} + +.bs-docs-sidenav>li>a:hover { + background-color: #f5f5f5; +} + +.bs-docs-sidenav a:hover .icon-chevron-right { + opacity: .5; +} + +.bs-docs-sidenav .active .icon-chevron-right, +.bs-docs-sidenav .active a:hover .icon-chevron-right { + background-image: url(../img/glyphicons-halflings-white.png); + opacity: 1; +} + +.bs-docs-sidenav.affix { + top: 40px; +} + +.bs-docs-sidenav.affix-bottom { + position: absolute; + top: auto; + bottom: 270px; +} + + + + +/* Responsive +-------------------------------------------------- */ + +/* Desktop large +------------------------- */ +@media (min-width: 1200px) { + .bs-docs-container { + max-width: 970px; + } + + .bs-docs-sidenav { + width: 258px; + } +} + +/* Desktop +------------------------- */ +@media (max-width: 980px) { + + /* Unfloat brand */ + body>.navbar-fixed-top .brand { + float: left; + margin-left: 0; + padding-left: 10px; + padding-right: 10px; + } + + /* Inline-block quick links for more spacing */ + .quick-links li { + display: inline-block; + margin: 5px; + } + + /* When affixed, space properly */ + .bs-docs-sidenav { + top: 0; + margin-top: 30px; + margin-right: 0; + } +} + +/* Tablet to desktop +------------------------- */ +@media (min-width: 768px) and (max-width: 980px) { + + /* Remove any padding from the body */ + body { + padding-top: 0; + } + + /* Widen masthead and social buttons to fill body padding */ + .jumbotron { + margin-top: -20px; + /* Offset bottom margin on .navbar */ + } + + /* Adjust sidenav width */ + .bs-docs-sidenav { + width: 166px; + margin-top: 20px; + } + + .bs-docs-sidenav.affix { + top: 0; + } +} + +/* Tablet +------------------------- */ +@media (max-width: 767px) { + + /* Remove any padding from the body */ + body { + padding-top: 0; + } + + /* Widen masthead and social buttons to fill body padding */ + .jumbotron { + padding: 40px 20px; + margin-top: -20px; + /* Offset bottom margin on .navbar */ + margin-right: -20px; + margin-left: -20px; + } + + .masthead h1 { + font-size: 90px; + } + + .masthead p, + .masthead .btn { + font-size: 24px; + } + + .marketing .span4 { + margin-bottom: 40px; + } + + .bs-docs-social { + margin: 0 -20px; + } + + /* Space out the show-grid examples */ + .show-grid [class*="span"] { + margin-bottom: 5px; + } + + /* Sidenav */ + .bs-docs-sidenav { + width: auto; + margin-bottom: 20px; + } + + .bs-docs-sidenav.affix { + position: static; + width: auto; + top: 0; + } + + /* Unfloat the back to top link in footer */ + .footer { + margin-left: -20px; + margin-right: -20px; + padding-left: 20px; + padding-right: 20px; + } + + .footer p { + margin-bottom: 9px; + } +} + +/* Landscape phones +------------------------- */ +@media (max-width: 480px) { + + /* Remove padding above jumbotron */ + body { + padding-top: 0; + } + + /* Change up some type stuff */ + h2 small { + display: block; + } + + /* Downsize the jumbotrons */ + .jumbotron h1 { + font-size: 40px; + } + + .jumbotron p, + .jumbotron .btn { + font-size: 20px; + } + + .jumbotron .btn { + display: block; + margin: 0 auto; + } + + /* center align subhead text like the masthead */ + .subhead h1, + .subhead p { + text-align: left; + } + + /* Marketing on home */ + .marketing h1 { + font-size: 40px; + } + + /* center example sites */ + .example-sites { + margin-left: 0; + } + + .example-sites>li { + float: none; + display: block; + max-width: 280px; + margin: 0 auto 18px; + text-align: center; + } + + .example-sites .thumbnail>img { + max-width: 270px; + } + + /* Do our best to make tables work in narrow viewports */ + table code { + white-space: normal; + word-wrap: break-word; + word-break: break-all; + } + + /* Modal example */ + .modal-example .modal { + position: relative; + top: auto; + right: auto; + bottom: auto; + left: auto; + } + + /* Unfloat the back to top in footer to prevent odd text wrapping */ + .footer .pull-right { + float: none; + } +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/index_page.css b/gn2/wqflask/static/new/css/index_page.css new file mode 100644 index 00000000..34b3cebf --- /dev/null +++ b/gn2/wqflask/static/new/css/index_page.css @@ -0,0 +1,4 @@ +.info-button {
+ border: #A9A9A9;
+ background-color: #D3D3D3;
+}
diff --git a/gn2/wqflask/static/new/css/jupyter_notebooks.css b/gn2/wqflask/static/new/css/jupyter_notebooks.css new file mode 100644 index 00000000..db972a17 --- /dev/null +++ b/gn2/wqflask/static/new/css/jupyter_notebooks.css @@ -0,0 +1,16 @@ +.jupyter-links { + padding: 1.5em; +} + +.jupyter-links:nth-of-type(2n) { + background: #EEEEEE; +} + +.jupyter-links .main-link { + font-size: larger; + display: block; +} + +.jupyter-links .src-link { + font-size: smaller; +} diff --git a/gn2/wqflask/static/new/css/main.css b/gn2/wqflask/static/new/css/main.css new file mode 100644 index 00000000..d5fb8a61 --- /dev/null +++ b/gn2/wqflask/static/new/css/main.css @@ -0,0 +1,40 @@ +@media (max-width: 10px) { + .navbar-header { + float: none; + } + .navbar-toggle { + display: block; + } + .navbar-collapse { + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.1); + } + .navbar-collapse.collapse { + display: none!important; + } + .navbar-nav { + float: none!important; + margin: 7.5px -15px; + } + .navbar-nav>li { + float: none; + } + .navbar-nav>li>a { + padding-top: 10px; + padding-bottom: 10px; + } + .navbar-text { + float: none; + margin: 15px 0; + } + .navbar-collapse.collapse.in { + display: block!important; + } + .collapsing { + overflow: hidden!important; + } +} + +.checkbox { + min-height: 20px; +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/markdown.css b/gn2/wqflask/static/new/css/markdown.css new file mode 100644 index 00000000..859fe7fc --- /dev/null +++ b/gn2/wqflask/static/new/css/markdown.css @@ -0,0 +1,108 @@ +#markdown { + padding: 20px; +} + +#markdown h2, +#markdown h3, +#markdown h4, +#markdown h5 { + font-weight: bold; +} + +#markdown img { + display: block; + margin-right: auto; + margin-left: auto; +} + +.github-btn-container { + margin: 20px 10px; + display: flex; + width: 90vw; + justify-content: flex-end; +} + +.github-btn { + display: flex; + justify-content: center; + border: 3px solid #357ebd; + background: lightgrey; + padding: 3px 4px; + width: 200px; + border-radius: 16px; +} + +.github-btn a { + align-self: center; + font-weight: bold; + color: #357ebd; +} + +.github-btn a img { + height: 40px; + width: 40px; + padding-left:5px; +} + + +.container { + width: 80vw; + margin: auto; + max-width: 80vw; + +} + +.container p { + font-size: 17px; + word-spacing: 0.2em; +} + +.graph-legend h1 { + text-align: center; +} + +.graph-legend, +#guix-graph, +#guix-svg-graph{ + width: 90%; + margin: 10px auto; +} + +#guix-graph { + border: solid 2px black; +} + +#guix-svg-graph img { + width: 100%; +} + +#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; + } +} diff --git a/gn2/wqflask/static/new/css/marker_regression.css b/gn2/wqflask/static/new/css/marker_regression.css new file mode 100644 index 00000000..9f56b63d --- /dev/null +++ b/gn2/wqflask/static/new/css/marker_regression.css @@ -0,0 +1,76 @@ +.chr_manhattan_plot .y_axis path, +.chr_manhattan_plot .y_axis line { + fill: none; + stroke: black; + shape-rendering: crispEdges; +} +.chr_manhattan_plot .y_axis text { + font-family: sans-serif; + font-size: 14px; +} + +.chr_manhattan_plot .x_axis path, +.chr_manhattan_plot .x_axis line { + fill: none; + stroke: black; + shape-rendering: crispEdges; +} + +.chr_manhattan_plot .x_axis text { + text-anchor: end; + font-family: sans-serif; + font-size: 10px; +} + +rect.pane { + cursor: move; + fill: none; + pointer-events: all; +} + +/*rect { + stroke: WhiteSmoke; + fill: lightgrey; +}*/ +/*rect { + stroke: WhiteSmoke; + fill: Azure; +}*/ + +tr .outlier { + background-color: #ffff99; +} + +table.dataTable thead th{ + border-right: 1px solid white; + color: white; + background-color: #369; +} + +table.dataTable thead .sorting_asc { + background-image: url("/js/DataTables/images/sort_asc_disabled.png"); +} +table.dataTable thead .sorting_desc { + background-image: url("/js/DataTables/images/sort_desc_disabled.png"); +} + +table.dataTable thead th { + padding: 4px 18px 4px 10px; +} + +table.dataTable tbody td { + padding: 4px 20px 2px 10px; +} + +table.dataTable tbody tr.selected { + background-color: #ffee99; +} + +table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { + border-top: 1px solid #ccc; + border-right: 1px solid #ccc; +} +table.dataTable.cell-border tbody tr th:first-child, +table.dataTable.cell-border tbody tr td:first-child { + border-left: 1px solid #ccc; +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/mytooltip.css b/gn2/wqflask/static/new/css/mytooltip.css new file mode 100644 index 00000000..bc44c488 --- /dev/null +++ b/gn2/wqflask/static/new/css/mytooltip.css @@ -0,0 +1,16 @@ +.mytooltiptext { + visibility: hidden; + width: inherit; + background-color: #E5E5AF; + color: #595959; + text-align: left; + padding: 5px 0; + border-radius: 6px; + + position: absolute; + z-index: 1; +} + +.mytooltip:hover .mytooltiptext { + visibility: visible; +} diff --git a/gn2/wqflask/static/new/css/network_graph.css b/gn2/wqflask/static/new/css/network_graph.css new file mode 100644 index 00000000..1a0bafeb --- /dev/null +++ b/gn2/wqflask/static/new/css/network_graph.css @@ -0,0 +1,37 @@ +#content h2, h3, h4, h5, h6 { + color: #545454; + margin-bottom: 1em; + border-bottom: dashed 1px #dfdfdf; + padding-bottom: 0.3em; +} + +#content table td { + padding: 0.5em; + /* border-right: solid 1px #fff; */ +} + +#secondaryContent { + position: relative; + float: left; + width: 18.5em; + background: #fff url('/static/new/images/a1.gif') top right repeat-y; +} + + +input[type="button" i], input[type="submit" i], input[type="reset" i], input[type="file" i]::-webkit-file-upload-button, button { + align-items: flex-start; + text-align: center; + cursor: default; + color: buttontext; + border-image-source: initial; + border-image-slice: initial; + border-image-width: initial; + border-image-outset: initial; + border-image-repeat: initial; + background-color: buttonface; + box-sizing: border-box; + padding: 2px 6px 3px; + border-width: 2px; + border-style: outset; + border-color: buttonface; +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/non-responsive.css b/gn2/wqflask/static/new/css/non-responsive.css new file mode 100644 index 00000000..a4bcddd2 --- /dev/null +++ b/gn2/wqflask/static/new/css/non-responsive.css @@ -0,0 +1,114 @@ +/* Template-specific stuff + * + * Customizations just for the template; these are not necessary for anything + * with disabling the responsiveness. + */ + +/* Account for fixed navbar */ +body { + //min-width: 1200px; + padding-top: 70px; + padding-bottom: 30px; +} + +/* Finesse the page header spacing */ +.page-header { + margin-bottom: 10px; +} + +.page-header .lead { + margin-bottom: 10px; +} + + +/* Non-responsive overrides + * + * Utilitze the following CSS to disable the responsive-ness of the container, + * grid system, and navbar. + */ + +/* Reset the container */ +.container { + width: 100%; + max-width: none !important; +} + + +.container .navbar-header, +.container .navbar-collapse { + margin-right: 0; + margin-left: 0; +} + +/* Always float the navbar header */ +.navbar-header { + float: left; +} + +/* Undo the collapsing navbar */ +.navbar-collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; +} + +.navbar-toggle { + display: none; +} + +.navbar-collapse { + border-top: 0; +} + +/* Always apply the floated nav */ +.navbar-nav { + float: left; + margin: 0; +} + +.navbar-nav>li { + float: left; +} + +.navbar-nav>li>a { + padding: 5px; +} + +/* Redeclare since we override the float above */ +.navbar-nav.navbar-right { + float: right; +} + +/* Undo custom dropdowns */ +.navbar .navbar-nav .open .dropdown-menu { + position: absolute; + float: left; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-width: 0 1px 1px; + border-radius: 0 0 4px 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} + +.navbar-default .navbar-nav .open .dropdown-menu>li>a { + color: #333; +} + +.navbar .navbar-nav .open .dropdown-menu>li>a:hover, +.navbar .navbar-nav .open .dropdown-menu>li>a:focus, +.navbar .navbar-nav .open .dropdown-menu>.active>a, +.navbar .navbar-nav .open .dropdown-menu>.active>a:hover, +.navbar .navbar-nav .open .dropdown-menu>.active>a:focus { + color: #fff !important; + background-color: #3071a9 !important; +} + +.navbar .navbar-nav .open .dropdown-menu>.disabled>a, +.navbar .navbar-nav .open .dropdown-menu>.disabled>a:hover, +.navbar .navbar-nav .open .dropdown-menu>.disabled>a:focus { + color: #999 !important; + background-color: transparent !important; +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/pair_scan.css b/gn2/wqflask/static/new/css/pair_scan.css new file mode 100644 index 00000000..90cec529 --- /dev/null +++ b/gn2/wqflask/static/new/css/pair_scan.css @@ -0,0 +1,6 @@ +/* Unset CSS for tooltip since it will inherit from the bootstrap tooltip class CSS otherwise */
+.tooltip{all:unset;}
+
+.pairscan-container {
+ width: 1000px;
+}
diff --git a/gn2/wqflask/static/new/css/panelutil.css b/gn2/wqflask/static/new/css/panelutil.css new file mode 100644 index 00000000..ccd7eb01 --- /dev/null +++ b/gn2/wqflask/static/new/css/panelutil.css @@ -0,0 +1,91 @@ +
+/* figures will always be within a div with class="qtlcharts" */
+div.qtlcharts {
+ font-family: sans-serif;
+ font-size: 11pt;
+}
+
+div.qtlcharts .title text {
+ dominant-baseline: middle;
+ fill: blue;
+ text-anchor: middle;
+}
+
+div.qtlcharts .y.axis text {
+ dominant-baseline: middle;
+ text-anchor: end;
+}
+
+div.qtlcharts .y.axis text.title {
+ text-anchor: middle;
+ fill: slateblue;
+}
+
+div.qtlcharts .x.axis text {
+ dominant-baseline: hanging;
+ text-anchor: middle;
+}
+
+div.qtlcharts .x.axis text.title {
+ fill: slateblue;
+}
+
+/*div.qtlcharts line.axis.grid {
+ fill: none;
+ stroke-width: 1;
+ pointer-events: none;
+}
+
+div.qtlcharts line.x.axis.grid {
+ stroke: rgb(200, 200, 200);
+ stroke-width: 3;
+}
+
+div.qtlcharts line.y.axis.grid {
+ stroke: white;
+}*/
+
+div.qtlcharts .extent {
+ fill: #cac;
+ opacity: 0.3;
+}
+
+div.qtlcharts circle.selected {
+ fill: hotpink;
+ opacity: 1;
+}
+
+div.qtlcharts a {
+ color: #08c;
+ text-decoration: none;
+}
+
+div.qtlcharts a:hover {
+ color: #333;
+ text-decoration: underline;
+}
+
+
+/* figure captions */
+.caption {
+ font-family: Sans-serif;
+ font-size: 11pt;
+ margin-left: 60px;
+ width: 600px;
+}
+
+
+/* d3 tip for tool tips */
+.d3-tip {
+ background: darkslateblue;
+ color: #fff;
+ stroke: none;
+ font-weight: bold;
+ font-size: 16px;
+ font-family: sans-serif;
+ padding: 5px;
+}
+
+.d3-tip.e:after {
+ color: darkslateblue;
+}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/parsley.css b/gn2/wqflask/static/new/css/parsley.css new file mode 100644 index 00000000..7d244579 --- /dev/null +++ b/gn2/wqflask/static/new/css/parsley.css @@ -0,0 +1,20 @@ +/* Adapted from parsleyjs.org/documentation.html#parsleyclasses */ + +input.parsley-success, textarea.parsley-success { + color: #468847 !important; + background-color: #DFF0D8 !important; + border: 1px solid #D6E9C6 !important; +} +input.parsley-error, textarea.parsley-error { + color: #B94A48 !important; + background-color: #F2DEDE !important; + border: 1px solid #EED3D7 !important; +} +ul.parsley-error-list { + font-size: 11px; + margin: 2px; + list-style-type:none; +} +ul.parsley-error-list li { + line-height: 11px; +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/partial_correlations.css b/gn2/wqflask/static/new/css/partial_correlations.css new file mode 100644 index 00000000..84a0877f --- /dev/null +++ b/gn2/wqflask/static/new/css/partial_correlations.css @@ -0,0 +1,109 @@ +#partial-correlations-form { + width: 100%; + display: grid; + grid-column-gap: 1em; +} + +#main-form { + grid-column-start: 1; + grid-column-end: 2; + text-align: left; +} + +#form-display-area { + grid-column-start: 2; + grid-column-end: 3; +} + +#part-corr-success { + grid-column-start: 1; + grid-column-end: 3; +} + +td, th { + border: 1px solid; + text-align: left; + padding: 0.2em 0.5em 0.2em 0.7em; +} + +tr:nth-of-type(2n) { + background: #F9F9F9; +} + +.with-trait { + margin-left: 0.7em; + position: relative; + display: grid; + width: 100%; + grid-column-gap: 1em; + grid-template-columns: 1em 1fr; + text-align: left; +} + +.with-trait:nth-of-type(2n) { + background: #E5E5FF; +} + +.with-trait .selector-element { + grid-column: 1 / 2; + grid-row: 1 / 1; +} + +.with-trait:first-of-type { + padding-top: 2.5em; +} + +.with-trait:first-of-type label *:before{ + position: absolute; + top: 0px; + + text-transform: capitalize; + font-weight: bolder; + content: attr(data-title); + background: #336699; /*#FFCCCC;*/ + color: #FFFFFF; + padding: 0.5em; +} + +.with-trait .label-element { + display: grid; + grid-column-gap: 0.5em; + grid-template-columns: 4fr 2fr 2fr 9fr 2fr 1fr 2fr; + grid-column: 2 / 2; + grid-row: 1 / 1; +} + +.with-trait .label-element .trait-dataset { + grid-column: 1; + grid-row: 1 / 1; +} + +.with-trait .label-element .trait-name { + grid-column: 2; + grid-row: 1 / 1; +} + +.with-trait .label-element .trait-symbol { + grid-column: 3; + grid-row: 1 / 1; +} + +.with-trait .label-element .trait-description { + grid-column: 4; + grid-row: 1 / 1; +} + +.with-trait .label-element .trait-location { + grid-column: 5; + grid-row: 1 / 1; +} + +.with-trait .label-element .trait-mean-expr { + grid-column: 6; + grid-row: 1 / 1; +} + +.with-trait .label-element .trait-max-lrs { + grid-column: 7; + grid-row: 1 / 1; +} diff --git a/gn2/wqflask/static/new/css/prob_plot.css b/gn2/wqflask/static/new/css/prob_plot.css new file mode 100644 index 00000000..2a1f8f53 --- /dev/null +++ b/gn2/wqflask/static/new/css/prob_plot.css @@ -0,0 +1,3 @@ +#prob_plot_title { + text-align: center; +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/scatter-matrix.css b/gn2/wqflask/static/new/css/scatter-matrix.css new file mode 100644 index 00000000..58150b26 --- /dev/null +++ b/gn2/wqflask/static/new/css/scatter-matrix.css @@ -0,0 +1,40 @@ +#scatter-matrix { font-size: 14px; } + +#scatter-matrix .axis { shape-rendering: crispEdges; } +#scatter-matrix .axis line { stroke: #ddd; stroke-width: 1px; } +#scatter-matrix .axis path { display: none; } +#scatter-matrix .axis text { font-size: 0.8em; } + +rect.extent { fill: #000; fill-opacity: .125; stroke: #fff; } +rect.frame { fill: #fff; fill-opacity: .7; stroke: #aaa; } + +circle { fill: #ccc; fill-opacity: .5; } + +.legend circle { fill-opacity: 1; } +.legend text { font-size: 18px; font-style: oblique; } + +.cell text { pointer-events: none; } + +.color-0 { fill: #800; } +.color-1 { fill: #080; } +.color-2 { fill: #008; } +.color-3 { fill: #440; } +.color-4 { fill: #044; } +.color-5 { fill: #404; } +.color-6 { fill: #400; } +.color-7 { fill: #040; } +.color-8 { fill: #004; } +.color-9 { fill: #cc0; } +.color-10 { fill: #0cc; } +.color-11 { fill: #c0c; } +.color-12 { fill: #880; } +.color-13 { fill: #088; } +.color-14 { fill: #808; } +.color-15 { fill: #c00; } +.color-16 { fill: #0c0; } +.color-17 { fill: #00c; } + +.scatter-matrix-filter-control ul { list-style: none; padding-left: 10px; } +.scatter-matrix-variable-control ul { list-style: none; padding-left: 10px; } +.scatter-matrix-drill-control ul { list-style: none; padding-left: 10px; } + diff --git a/gn2/wqflask/static/new/css/show_trait.css b/gn2/wqflask/static/new/css/show_trait.css new file mode 100644 index 00000000..577bb5dd --- /dev/null +++ b/gn2/wqflask/static/new/css/show_trait.css @@ -0,0 +1,311 @@ +tr .outlier { + background-color: #ffff99; +} + +table.dataTable tbody tr.selected { + background-color: #ffee99; +} + +table.metadata { + table-layout: fixed; +} + +table.metadata td:nth-child(1) { + width: 10%; +} + +table summary b { + cursor: pointer; + text-decoration: underline; +} + +table details[open] { + width: 80%; +} + +#bar_chart_container { + overflow-x:scroll; +} + +div.sample_group { + overflow: auto; # needed because it contains float dataTable wrapper +} + +.js-plotly-plot .plotly .modebar { + left: 100px; +} + +table.dataTable thead th, table.dataTable tfoot th{ + border-right: 1px solid white; + color: white; + background-color: #369; +} + +table.dataTable thead .sorting_asc { + background-image: url("/js/DataTables/images/sort_asc_disabled.png"); +} +table.dataTable thead .sorting_desc { + background-image: url("/js/DataTables/images/sort_desc_disabled.png"); +} + +table.dataTable thead th, table.dataTable tfoot { + padding: 4px 18px 4px 10px; +} + +/* Header for a column with a numeric value that should be right-aligned */ +table.dataTable thead th div.numeric_header { + text-align: right; +} + +table.dataTable tbody td { + padding: 2px 15px 0px 10px; +} + +table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { + border-top: 1px solid #ccc; + border-right: 1px solid #ccc; +} +table.dataTable.cell-border tbody tr th:first-child, +table.dataTable.cell-border tbody tr td:first-child { + border-left: 1px solid #ccc; +} + +.checkbox { + min-height: 20px; +} + +.trait_value_input { + text-align: right; +} + +.glyphicon { + position: relative; + top: 2px; +} + +table.dataTable tbody td.column_name-Checkbox { + padding: 1px 0px 0px 9px; /* The left padding here is because text-align:center does not seem to be working properly. No idea why this is. */ + text-align: center; + vertical-align: middle; +} + +table.dataTable tbody td.column_name-Index { + padding: 2px 4px 0px 2px; + text-align: right; +} + +/* the column with +/- in it that appears in the table when there's an SE column */ +table.dataTable tbody td.column_name-PlusMinus { + padding-left: 2px; + padding-right: 2px; + text-align: center; +} + +table.dataTable tbody td.column_name-Value, table.dataTable tbody td.column_name-SE, table.dataTable tbody td.column_name-num_cases { + text-align: right; +} + +div.btn-toolbar { + margin-bottom:15px; +} + +/* div containing the form options for each segment of the trait page - correlations, statistics, mapping, etc */ +div.section-form-div { + padding: 20px; +} + +table.left-float { + float: left; +} + +div.scatterplot-btn-div { + margin-bottom:40px; +} + +.min-expr-field { + width: 70px; +} + +div.p-range-slider { + width: 200px; + margin-left: 15px; +} + +span.p-range-lower { + margin-top: 5px; + font: 400 12px Arial; + color: #888; + display: inline-block; +} + +span.p-range-upper { + font: 400 12px Arial; + color: #888; + display: inline-block; + margin: 5px 0px 0px 170px; +} + +input.corr-location { + width: 50px; + display: inline; +} + +div.block-div { + margin-bottom: 10px; +} + +div.block-div-2 { + margin-top:10px; + margin-bottom:10px; +} + +div.normalize-div { + margin-top:10px; +} + +div.main { + display: block; +} + +div.options { + float: left; + width: 600px; + padding-top: 0px; +} + +div.descriptions { + float: left; + width: 500px; + min-width: 20%; + margin-bottom: 0px; + padding-left: 15px; +} + +div.covar-options { + padding-top: 7px; +} + +.control-label { + text-align: right; +} + +.chr-select, .maf-select, .scale-select, .reaper-ver-select { + width: 80px; +} + +span.covar-text { + font-size: 13px; + font-weight: 400; +} + +div.select-covar-div { + margin-bottom: 10px; +} + +.select-covar-button { + width: 80px; + padding-right: 10px; +} + +.selected-covariates { + overflow-y: scroll; + resize: none; + width: 400px; +} + +.cofactor-input { + width: 160px; + display: inline-block; +} + +.no-control-radio { + margin-left: 10px; +} + +.map-method-text { + margin-top: 20px; +} + +.rqtl-description { + padding-top: 40px; + display: none; +} + +div.sample-table-container { + padding-bottom: 10px; +} + +div.sample-table-search-container { + display: inline; + height: 40px; +} + +input.sample-table-search { + width: 200px; + display: inline; + float: left; + vertical-align: top; +} + +div.sample-table-export-container { + padding-left: 10px; + display: inline; +} + +div.export-code-container { + padding-top: 20px; + width: 600px; + display: none; +} + +.export-code-field { + padding-top: 0px; + padding-bottom: 0px; + height: 150px; +} + +table.sample-table { + float: left; +} + +input.trait-value-input { + text-align: right; +} + +div.inline-div { + display: inline; +} + +/* div.colorbox_border { + border: 1px solid grey; +} */ +div#cboxContent { + /* box-shadow: + 0 2.8px 2.2px rgba(0, 0, 0, 0.034), + 0 6.7px 5.3px rgba(0, 0, 0, 0.048), + 0 12.5px 10px rgba(0, 0, 0, 0.06), + 0 22.3px 17.9px rgba(0, 0, 0, 0.072), + 0 41.8px 33.4px rgba(0, 0, 0, 0.086), + 0 100px 80px rgba(0, 0, 0, 0.12) */ + + padding: 10px 10px 5px 10px; + + -moz-box-shadow: 3px 3px 5px #535353; + -webkit-box-shadow: 3px 3px 5px #535353; + box-shadow: 3px 3px 5px #535353; + + -moz-border-radius: 6px 6px 6px 6px; + -webkit-border-radius: 6px; + border-radius: 6px 6px 6px 6px; + + /* border: 2px solid grey; */ +} + +#cboxClose { + margin-right: 5px; + margin-bottom: 2px; +} + +div.container { + min-width:750px; +} diff --git a/gn2/wqflask/static/new/css/snp_browser.css b/gn2/wqflask/static/new/css/snp_browser.css new file mode 100644 index 00000000..a7942d2a --- /dev/null +++ b/gn2/wqflask/static/new/css/snp_browser.css @@ -0,0 +1,58 @@ +.form_group { + margin-bottom 5px; +} + +table.dataTable thead th { + vertical-align: bottom; +} + +table.dataTable tbody tr.selected { + background-color: #ffee99; +} + +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc, +table.dataTable thead .sorting_asc_disabled, +table.dataTable thead .sorting_desc_disabled { + background-repeat: no-repeat; + background-position: bottom right; +} + +table.dataTable thead th{ + border-right: 1px solid white; + color: white; + background-color: #369; +} + +table.dataTable tbody td { + padding: 4px 20px 2px 10px; +} + +td.A_allele_color { + background-color: #C33232 +} +td.C_allele_color { + background-color: #1569C7 +} +td.T_allele_color { + background-color: #CFCF32 +} +td.G_allele_color { + background-color: #32C332 +} +td.t_allele_color { + background-color: #FF6 +} +td.c_allele_color { + background-color: #5CB3FF +} +td.a_allele_color { + background-color: #F66 +} +td.g_allele_color { + background-color: #CF9 +} +td.default_allele_color { + background-color: #FFFFFF +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/css/trait_list.css b/gn2/wqflask/static/new/css/trait_list.css new file mode 100644 index 00000000..cc5a9eac --- /dev/null +++ b/gn2/wqflask/static/new/css/trait_list.css @@ -0,0 +1,53 @@ +div.tool-button-container { + min-width: 1050px; +} + +div.collection-table-options { + min-width: 1100px; +} + +div.show-hide-container { + margin-bottom: 5px; + margin-top: 10px; +} + +button.active { + background: #BEBEBE; + -webkit-box-shadow: inset 0px 0px 5px #c1c1c1; + -moz-box-shadow: inset 0px 0px 5px #c1c1c1; + box-shadow: inset 0px 0px 5px #c1c1c1; + outline: none; +} + +div.dts { + display:block !important +} + +div.dts div.dts_loading { + z-index:1 +} + +div.dts div.dts_label { + position:absolute; + right:10px; + background:rgba(0,0,0,0.8); + color:white; + box-shadow:3px 3px 10px rgba(0,0,0,0.5); + text-align:right; + border-radius:3px; + padding:0.4em; + z-index:2; + display:none +} + +div.dts div.dataTables_scrollBody { + background:repeating-linear-gradient(45deg, #edeeff, #edeeff 10px, #fff 10px, #fff 20px) +} + +div.dts div.dataTables_scrollBody table { + z-index:2 +} + +div.dts div.dataTables_paginate,div.dts div.dataTables_length{ + display:none +} diff --git a/gn2/wqflask/static/new/css/typeahead-bootstrap.css b/gn2/wqflask/static/new/css/typeahead-bootstrap.css new file mode 100644 index 00000000..87dd4b5d --- /dev/null +++ b/gn2/wqflask/static/new/css/typeahead-bootstrap.css @@ -0,0 +1,94 @@ +span.twitter-typeahead .tt-menu, +span.twitter-typeahead .tt-dropdown-menu { + cursor: pointer; + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + font-size: 14px; + text-align: left; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; +} +span.twitter-typeahead .tt-suggestion { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333333; + white-space: nowrap; +} +span.twitter-typeahead .tt-suggestion.tt-cursor, +span.twitter-typeahead .tt-suggestion:hover, +span.twitter-typeahead .tt-suggestion:focus { + color: #ffffff; + text-decoration: none; + outline: 0; + background-color: #337ab7; +} +.input-group.input-group-lg span.twitter-typeahead .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.input-group.input-group-sm span.twitter-typeahead .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +span.twitter-typeahead { + width: 100%; +} +.input-group span.twitter-typeahead { + display: block !important; + height: 34px; +} +.input-group span.twitter-typeahead .tt-menu, +.input-group span.twitter-typeahead .tt-dropdown-menu { + top: 32px !important; +} +.input-group span.twitter-typeahead:not(:first-child):not(:last-child) .form-control { + border-radius: 0; +} +.input-group span.twitter-typeahead:first-child .form-control { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group span.twitter-typeahead:last-child .form-control { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.input-group.input-group-sm span.twitter-typeahead { + height: 30px; +} +.input-group.input-group-sm span.twitter-typeahead .tt-menu, +.input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu { + top: 30px !important; +} +.input-group.input-group-lg span.twitter-typeahead { + height: 46px; +} +.input-group.input-group-lg span.twitter-typeahead .tt-menu, +.input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu { + top: 46px !important; +}
\ No newline at end of file diff --git a/gn2/wqflask/static/new/images/CITGLogo.png b/gn2/wqflask/static/new/images/CITGLogo.png Binary files differnew file mode 100644 index 00000000..ae99fedb --- /dev/null +++ b/gn2/wqflask/static/new/images/CITGLogo.png diff --git a/gn2/wqflask/static/new/images/Nif.png b/gn2/wqflask/static/new/images/Nif.png Binary files differnew file mode 100644 index 00000000..e975a921 --- /dev/null +++ b/gn2/wqflask/static/new/images/Nif.png diff --git a/gn2/wqflask/static/new/images/PythonLogo.png b/gn2/wqflask/static/new/images/PythonLogo.png Binary files differnew file mode 100644 index 00000000..b76dafba --- /dev/null +++ b/gn2/wqflask/static/new/images/PythonLogo.png diff --git a/gn2/wqflask/static/new/images/a1.gif b/gn2/wqflask/static/new/images/a1.gif Binary files differnew file mode 100644 index 00000000..283921a0 --- /dev/null +++ b/gn2/wqflask/static/new/images/a1.gif diff --git a/gn2/wqflask/static/new/images/arrowdown.gif b/gn2/wqflask/static/new/images/arrowdown.gif Binary files differnew file mode 100644 index 00000000..577e5476 --- /dev/null +++ b/gn2/wqflask/static/new/images/arrowdown.gif diff --git a/gn2/wqflask/static/new/images/edit.gif b/gn2/wqflask/static/new/images/edit.gif Binary files differnew file mode 100644 index 00000000..45b314ee --- /dev/null +++ b/gn2/wqflask/static/new/images/edit.gif diff --git a/gn2/wqflask/static/new/images/ipad_icon3.png b/gn2/wqflask/static/new/images/ipad_icon3.png Binary files differnew file mode 100644 index 00000000..97060e97 --- /dev/null +++ b/gn2/wqflask/static/new/images/ipad_icon3.png diff --git a/gn2/wqflask/static/new/images/question_mark.jpg b/gn2/wqflask/static/new/images/question_mark.jpg Binary files differnew file mode 100644 index 00000000..82df7e81 --- /dev/null +++ b/gn2/wqflask/static/new/images/question_mark.jpg diff --git a/gn2/wqflask/static/new/images/step1.gif b/gn2/wqflask/static/new/images/step1.gif Binary files differnew file mode 100644 index 00000000..42d2bc7d --- /dev/null +++ b/gn2/wqflask/static/new/images/step1.gif diff --git a/gn2/wqflask/static/new/images/step2.gif b/gn2/wqflask/static/new/images/step2.gif Binary files differnew file mode 100644 index 00000000..b3dca656 --- /dev/null +++ b/gn2/wqflask/static/new/images/step2.gif diff --git a/gn2/wqflask/static/new/images/step3.gif b/gn2/wqflask/static/new/images/step3.gif Binary files differnew file mode 100644 index 00000000..0e0c7ea2 --- /dev/null +++ b/gn2/wqflask/static/new/images/step3.gif diff --git a/gn2/wqflask/static/new/javascript/auth/search.js b/gn2/wqflask/static/new/javascript/auth/search.js new file mode 100644 index 00000000..d094cebf --- /dev/null +++ b/gn2/wqflask/static/new/javascript/auth/search.js @@ -0,0 +1,172 @@ +class InvalidCSSIDSelector extends Error { + constructor(message) { + super(message); + this.name = "InvalidCSSIDSelector"; + } +} + +class InvalidDataAttributeName extends Error { + constructor(message) { + super(message); + this.name = "InvalidDataAttributeName"; + } +} + +/** + * CSSIDSelector: A CSS ID Selector + * @param {String} A CSS selector of the form '#...' + */ +class CSSIDSelector { + constructor(selector) { + if(!selector.startsWith("#")) { + throw new InvalidCSSIDSelector( + "Expected the CSS selector to begin with a `#` character."); + } + let id_str = selector.slice(1, selector.length); + if(document.getElementById(id_str) == null) { + throw new InvalidCSSIDSelector( + "Element with ID '" + id_str + "' does not exist."); + } + this.selector = selector; + } +} + +/** + * TableDataSource: A type to represent a table's data source + * @param {String} A CSS selector for an ID + * @param {String} A `data-*` attribute name + */ +class TableDataSource { + constructor(table_id, data_attribute_name, checkbox_creation_function) { + this.table_id = new CSSIDSelector(table_id); + let data = document.querySelector( + table_id).getAttribute(data_attribute_name); + if(data == null) { + throw new InvalidDataAttributeName( + "data-* attribute '" + data_attribute_name + "' does not exist " + + "for table with ID '" + table_id.slice(1, table_id.length) + + "'."); + } else { + this.data_attribute_name = data_attribute_name; + } + this.checkbox_creation_function = checkbox_creation_function; + } +} + +/** + * Render the table + * @param {String} The selector for the table's ID + * @param {String} The name of the data-* attribute holding the table's data + * @param {Function} The function to call to generate the appropriate checkbox + */ +function render_table(table_data_source) { + table_id = table_data_source.table_id.selector; + data_attr_name = table_data_source.data_attribute_name; + $(table_id + " tbody tr").remove(); + table_data = JSON.parse($(table_id).attr(data_attr_name)).sort((d1, d2) => { + return (d1.dataset_name > d2.dataset_name ? 1 : ( + d1.dataset_name < d2.dataset_name ? -1 : 0)) + }); + if(table_data.length < 1) { + row = $("<tr>") + cell = $('<td colspan="100%" align="center">'); + cell.append( + $('<span class="glyphicon glyphicon-info-sign text-info">')); + cell.append(" "); + cell.append("No genotype datasets remaining."); + row.append(cell); + $(table_id + " tbody").append(row); + } + table_data.forEach(function(dataset) { + row = $("<tr>") + row.append(table_data_source.checkbox_creation_function(dataset)); + row.append(table_cell(dataset.InbredSetName)); + row.append(table_cell(dataset.dataset_name)); + row.append(table_cell(dataset.dataset_fullname)); + row.append(table_cell(dataset.dataset_shortname)); + $(table_id + " tbody").append(row); + }); +} + +function in_array(items, filter_fn) { + return items.filter(filter_fn).length > 0; +} + +function remove_from_table_data(dataset, table_data_source, filter_fn) { + let table_id = table_data_source.table_id.selector; + let data_attr_name = table_data_source.data_attribute_name; + without_dataset = JSON.parse($(table_id).attr(data_attr_name)).filter( + filter_fn); + $(table_id).attr(data_attr_name, JSON.stringify(without_dataset)); +} + +function add_to_table_data(dataset, table_data_source, filter_fn) { + let table_id = table_data_source.table_id.selector; + let data_attr_name = table_data_source.data_attribute_name; + table_data = JSON.parse($(table_id).attr(data_attr_name)); + if(!in_array(table_data, filter_fn)) { + table_data.push(dataset); + } + $(table_id).attr(data_attr_name, JSON.stringify(Array.from(table_data))); +} + +/** + * Switch the dataset/trait from search table to selection table and vice versa + * @param {Object} A dataset/trait object + * @param {TableDataSource} The source table for the dataset/trait + * @param {TableDataSource} The destination table for the dataset/trait + */ +function select_deselect(item, source, destination, filter_fn, render_fn=render_table) { + dest_selector = destination.table_id.selector + dest_data = JSON.parse( + $(dest_selector).attr(destination.data_attribute_name)); + add_to_table_data(item, destination, filter_fn); // Add to destination table + remove_from_table_data(item, source, (arg) => {return !filter_fn(arg)}); // Remove from source table + /***** BEGIN: Re-render tables *****/ + render_fn(destination); + render_fn(source); + /***** END: Re-render tables *****/ +} + +function debounce(func, delay=500) { + var timeout; + return function search(event) { + clearTimeout(timeout); + timeout = setTimeout(func, delay); + }; +} + +/** + * Build a checkbox + * @param {Dataset Object} A JSON.stringify-able object + * @param {String} The name to assign the checkbox + */ +function build_checkbox(data_object, checkbox_name, checkbox_aux_classes="", checked=false) { + cell = $("<td>"); + check = $( + '<input type="checkbox" class="checkbox" ' + + 'name="' + checkbox_name + '">'); + check.val(JSON.stringify(data_object)); + check.prop("checked", checked); + auxilliary_classes = checkbox_aux_classes.trim(); + if(Boolean(auxilliary_classes)) { + check.attr("class", + check.attr("class") + " " + auxilliary_classes.trim()); + } + cell.append(check); + return cell; +} + +function link_checkbox(dataset) { + return build_checkbox(dataset, "selected", "checkbox-selected", true); +} + +function search_checkbox(dataset) { + return build_checkbox(dataset, "search_datasets", "checkbox-search"); +} + +function table_cell(value) { + cell = $("<td>"); + cell.html(value); + return cell; +} diff --git a/gn2/wqflask/static/new/javascript/auth/search_genotypes.js b/gn2/wqflask/static/new/javascript/auth/search_genotypes.js new file mode 100644 index 00000000..d1b8ed9e --- /dev/null +++ b/gn2/wqflask/static/new/javascript/auth/search_genotypes.js @@ -0,0 +1,95 @@ +function toggle_link_button() { + num_groups = $("#frm-link-genotypes select option").length - 1; + num_selected = JSON.parse( + $("#tbl-link-genotypes").attr("data-selected-datasets")).length; + if(num_groups > 0 && num_selected > 0) { + $("#frm-link-genotypes input[type='submit']").prop("disabled", false); + } else { + $("#frm-link-genotypes input[type='submit']").prop("disabled", true); + } +} + +function search_genotypes() { + query = document.getElementById("txt-query").value; + selected = JSON.parse(document.getElementById( + "tbl-link-genotypes").getAttribute("data-selected-datasets")); + species_name = document.getElementById("txt-species-name").value + search_endpoint = "/auth/data/genotype/search" + search_table = new TableDataSource( + "#tbl-genotypes", "data-datasets", search_checkbox); + $.ajax( + search_endpoint, + { + "method": "POST", + "contentType": "application/json; charset=utf-8", + "dataType": "json", + "data": JSON.stringify({ + "query": query, + "selected": selected, + "dataset_type": "genotype", + "species_name": species_name}), + "error": function(jqXHR, textStatus, errorThrown) { + data = jqXHR.responseJSON + elt = document.getElementById("search-error").setAttribute( + "style", "display: block;"); + document.getElementById("search-error-text").innerHTML = ( + data.error + " (" + data.status_code + "): " + + data.error_description); + document.getElementById("tbl-genotypes").setAttribute( + "data-datasets", JSON.stringify([])); + render_table(search_table); + }, + "success": function(data, textStatus, jqXHR) { + document.getElementById("search-error").setAttribute( + "style", "display: none;"); + document.getElementById("tbl-genotypes").setAttribute( + "data-datasets", JSON.stringify(data)); + render_table(search_table); + } + }); +} + +/** + * Return function to check whether `dataset` is in array of `datasets`. + * @param {GenotypeDataset} A genotype dataset. + * @param {Array} An array of genotype datasets. + */ +function make_filter(trait) { + return (dst) => { + return (dst.SpeciesId == dataset.SpeciesId && + dst.InbredSetId == dataset.InbredSetId && + dst.GenoFreezeId == dataset.GenoFreezeId); + }; +} + +$(document).ready(function() { + let search_table = new TableDataSource( + "#tbl-genotypes", "data-datasets", search_checkbox); + let link_table = new TableDataSource( + "#tbl-link-genotypes", "data-selected-datasets", link_checkbox); + + $("#frm-search-traits").submit(function(event) { + event.preventDefault(); + return false; + }); + + $("#txt-query").keyup(debounce(search_genotypes)); + + $("#tbl-genotypes").on("change", ".checkbox-search", function(event) { + if(this.checked) { + dataset = JSON.parse(this.value); + select_deselect( + dataset, search_table, link_table, make_filter(dataset)); + toggle_link_button(); + } + }); + + $("#tbl-link-genotypes").on("change", ".checkbox-selected", function(event) { + if(!this.checked) { + dataset = JSON.parse(this.value); + select_deselect( + dataset, link_table, search_table, make_filter(dataset)); + toggle_link_button(); + } + }); +}); diff --git a/gn2/wqflask/static/new/javascript/auth/search_mrna.js b/gn2/wqflask/static/new/javascript/auth/search_mrna.js new file mode 100644 index 00000000..3eca4ed2 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/auth/search_mrna.js @@ -0,0 +1,97 @@ +function toggle_link_button() { + num_groups = $("#frm-link select option").length - 1; + num_selected = JSON.parse( + $("#tbl-link").attr("data-datasets")).length; + if(num_groups > 0 && num_selected > 0) { + $("#frm-link input[type='submit']").prop("disabled", false); + } else { + $("#frm-link input[type='submit']").prop("disabled", true); + } +} + +function search_mrna() { + query = document.getElementById("txt-query").value; + selected = JSON.parse(document.getElementById( + "tbl-link").getAttribute("data-datasets")); + species_name = document.getElementById("txt-species-name").value + search_endpoint = "/auth/data/mrna/search" + search_table = new TableDataSource( + "#tbl-search", "data-datasets", search_checkbox); + $.ajax( + search_endpoint, + { + "method": "POST", + "contentType": "application/json; charset=utf-8", + "dataType": "json", + "data": JSON.stringify({ + "query": query, + "selected": selected, + "dataset_type": "mrna", + "species_name": species_name}), + "error": function(jqXHR, textStatus, errorThrown) { + error_data = jqXHR.responseJSON + console.debug("ERROR_DATA:", error_data); + elt = document.getElementById("search-error").setAttribute( + "style", "display: block;"); + document.getElementById("search-error-text").innerHTML = ( + error_data.error + " (" + error_data.status_code + "): " + + error_data.error_description); + document.getElementById("tbl-search").setAttribute( + "data-datasets", JSON.stringify([])); + render_table(search_table); + }, + "success": function(data, textStatus, jqXHR) { + document.getElementById("search-error").setAttribute( + "style", "display: none;"); + document.getElementById("tbl-search").setAttribute( + "data-datasets", JSON.stringify(data)); + render_table(search_table); + } + }); +} + +/** + * Make function to check whether `dataset` is in array of `datasets`. + * @param {mRNADataset} A mrna dataset. + * @param {Array} An array of mrna datasets. + */ +function make_filter(dataset) { + return (dst) => { + return (dst.SpeciesId == dataset.SpeciesId && + dst.InbredSetId == dataset.InbredSetId && + dst.ProbeFreezeId == dataset.ProbeFreezeId && + dst.ProbeSetFreezeId == dataset.ProbeSetFreezeId); + }; +} + +$(document).ready(function() { + let search_table = new TableDataSource( + "#tbl-search", "data-datasets", search_checkbox); + let link_table = new TableDataSource( + "#tbl-link", "data-datasets", link_checkbox); + + $("#frm-search").submit(function(event) { + event.preventDefault(); + return false; + }); + + $("#txt-query").keyup(debounce(search_mrna)); + + $("#tbl-search").on("change", ".checkbox-search", function(event) { + if(this.checked) { + dataset = JSON.parse(this.value); + select_deselect( + dataset, search_table, link_table, make_filter(dataset)); + toggle_link_button(); + } + }); + + $("#tbl-link").on("change", ".checkbox-selected", function(event) { + if(!this.checked) { + dataset = JSON.parse(this.value); + select_deselect( + dataset, link_table, search_table, make_filter(dataset)); + toggle_link_button(); + } + }); +}); diff --git a/gn2/wqflask/static/new/javascript/auth/search_phenotypes.js b/gn2/wqflask/static/new/javascript/auth/search_phenotypes.js new file mode 100644 index 00000000..99ecb16e --- /dev/null +++ b/gn2/wqflask/static/new/javascript/auth/search_phenotypes.js @@ -0,0 +1,188 @@ +/** + * Global variables: Bad idea - figure out how to pass them down a call stack. + */ +search_table = new TableDataSource( + "#tbl-phenotypes", "data-traits", (trait) => { + return build_checkbox(trait, "search_traits", "checkbox-search"); + }); +link_table = new TableDataSource( + "#tbl-link-phenotypes", "data-traits", (trait) => { + return build_checkbox( + trait, "selected", "checkbox-selected", checked=true); + }); + +/** + * Toggle the state for the "Link Traits" button + */ +function toggle_link_button() { + num_groups = $("#frm-link-phenotypes select option").length - 1; + num_selected = JSON.parse( + $("#tbl-link-phenotypes").attr("data-traits")).length; + if(num_groups > 0 && num_selected > 0) { + $("#frm-link-phenotypes input[type='submit']").prop("disabled", false); + } else { + $("#frm-link-phenotypes input[type='submit']").prop("disabled", true); + } +} + +/** + * Default error function: print out debug messages + */ +function default_error_fn(jqXHR, textStatus, errorThrown) { + console.debug("XHR:", jqXHR); + console.debug("STATUS:", textStatus); + console.debug("ERROR:", errorThrown); +} + +/** + * Render the table(s) for the phenotype traits + * @param {TableDataSource} The table to render + */ +function render_pheno_table(table_data_source) { + table_id = table_data_source.table_id.selector; + data_attr_name = table_data_source.data_attribute_name; + $(table_id + " tbody tr").remove(); + table_data = JSON.parse($(table_id).attr(data_attr_name)).sort((t1, t2) => { + return (t1.name > t2.name ? 1 : (t1.name < t2.name ? -1 : 0)) + }); + if(table_data.length < 1) { + row = $("<tr>") + cell = $('<td colspan="100%" align="center">'); + cell.append( + $('<span class="glyphicon glyphicon-info-sign text-info">')); + cell.append(" "); + cell.append("No phenotype traits to select from."); + row.append(cell); + $(table_id + " tbody").append(row); + } + table_data.forEach(function(trait) { + row = $("<tr>") + row.append(table_data_source.checkbox_creation_function(trait)); + row.append(table_cell(trait.name)); + row.append(table_cell(trait.group)); + row.append(table_cell(trait.dataset)); + row.append(table_cell(trait.dataset_fullname)); + row.append(table_cell(trait.description)); + row.append(table_cell(trait.authors.join(", "))); + row.append(table_cell( + '<a href="' + trait.pubmed_link + + '" title="Pubmed link for trait ' + trait.name + '.">' + + trait.year + "</a>")); + row.append(table_cell("Chr:" + trait.geno_chr + "@" + trait.geno_mb)); + row.append(table_cell(trait.lrs)); + row.append(table_cell(trait.additive)); + $(table_id + " tbody").append(row); + }); +} + +function display_search_results(data, textStatus, jqXHR) { + if(data.status == "queued" || data.status == "started") { + setTimeout(() => { + fetch_search_results(data.job_id, display_search_results); + }, 250); + return; + } + if(data.status == "completed") { + $("#tbl-phenotypes").attr( + "data-traits", JSON.stringify(data.search_results)); + // Remove this reference to global variable + render_pheno_table(search_table); + } + $("#txt-search").prop("disabled", false); +} + +/** + * Fetch the search results + * @param {UUID}: The job id to fetch data for + */ +function fetch_search_results(job_id, success, error=default_error_fn) { + host = $("#frm-search-traits").attr("data-gn-server-url"); + endpoint = host + "auth/data/search/phenotype/" + job_id + $("#txt-search").prop("disabled", true); + $.ajax( + endpoint, + { + "method": "GET", + "contentType": "application/json; charset=utf-8", + "dataType": "json", + "error": error, + "success": success + } + ); +} + +function search_phenotypes() { + query = document.getElementById("txt-query").value; + selected = JSON.parse(document.getElementById( + "tbl-link-phenotypes").getAttribute("data-traits")); + species_name = document.getElementById("txt-species-name").value + per_page = document.getElementById("txt-per-page").value + search_table = new TableDataSource( + "#tbl-phenotypes", "data-traits", search_checkbox); + endpoint = "/auth/data/phenotype/search" + $.ajax( + endpoint, + { + "method": "POST", + "contentType": "application/json; charset=utf-8", + "dataType": "json", + "data": JSON.stringify({ + "query": query, + "species_name": species_name, + "dataset_type": "phenotype", + "per_page": per_page, + "selected_traits": selected + }), + "error": default_error_fn, + "success": (data, textStatus, jqXHR) => { + fetch_search_results(data.job_id, display_search_results); + } + }); +} + +/** + * Return a function to check whether `trait` is in array of `traits`. + * @param {PhenotypeTrait} A phenotype trait. + * @param {Array} An array of phenotype traits. + */ +function make_filter(trait) { + return (trt) => { + return (trt.species == trait.species && + trt.group == trait.group && + trt.dataset == trait.dataset && + trt.name == trait.name); + }; +} + +$(document).ready(function() { + $("#frm-search-traits").submit(event => { + event.preventDefault(); + return false; + }); + + $("#txt-query").keyup(debounce(search_phenotypes)); + + $("#tbl-link-phenotypes").on("change", ".checkbox-selected", function(event) { + if(!this.checked) { + trait = JSON.parse(this.value); + select_deselect(trait, link_table, search_table, + make_filter(trait), render_pheno_table); + toggle_link_button(); + } + }); + + $("#tbl-phenotypes").on("change", ".checkbox-search", function(event) { + if(this.checked) { + trait = JSON.parse(this.value) + select_deselect(trait, search_table, link_table, + make_filter(trait), render_pheno_table); + toggle_link_button(); + } + }); + + setTimeout(() => { + fetch_search_results( + $("#tbl-phenotypes").attr("data-initial-job-id"), + display_search_results); + }, 500); +}); diff --git a/gn2/wqflask/static/new/javascript/auto_hide_column.js b/gn2/wqflask/static/new/javascript/auto_hide_column.js new file mode 100644 index 00000000..1a4dc039 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/auto_hide_column.js @@ -0,0 +1,21 @@ + function filterDatatable(datatable){ + let invalidColumns=[] + let columnCount=datatable.columns().header().length; + let numberOfRows=datatable.rows().count(); + for (let col=0; col<columnCount; col++){ + colObj = datatable.column(col).nodes().to$(); + allNAs = true; + for (let i=0;i<numberOfRows;i++){ + cellContent = colObj[i].childNodes[0].data + if (cellContent != "N/A" && cellContent != ""){ + allNAs = false; + break; + } + } + if (allNAs){ + invalidColumns.push(col) + } + } + return datatable.columns(invalidColumns).visible(false); + + }
\ No newline at end of file diff --git a/gn2/wqflask/static/new/javascript/bar_chart.js b/gn2/wqflask/static/new/javascript/bar_chart.js new file mode 100644 index 00000000..d8540580 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/bar_chart.js @@ -0,0 +1,498 @@ +// Generated by CoffeeScript 1.9.2 +(function() { + var Bar_Chart, root, + bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + hasProp = {}.hasOwnProperty; + + root = typeof exports !== "undefined" && exports !== null ? exports : this; + + Bar_Chart = (function() { + function Bar_Chart(sample_lists) { + this.add_legend_discrete = bind(this.add_legend_discrete, this); + this.add_legend_continuous = bind(this.add_legend_continuous, this); + this.remove_legend = bind(this.remove_legend, this); + this.add_legend = bind(this.add_legend, this); + var key, l1, l1_names, l2, l3, longest_sample_name_len, s, sample, x; + this.sample_lists = {}; + l1 = this.sample_lists['samples_primary'] = sample_lists[0] || []; + l2 = this.sample_lists['samples_other'] = sample_lists[1] || []; + l1_names = (function() { + var j, len, results; + results = []; + for (j = 0, len = l1.length; j < len; j++) { + x = l1[j]; + results.push(x.name); + } + return results; + })(); + l3 = l1.concat((function() { + var j, len, ref, results; + results = []; + for (j = 0, len = l2.length; j < len; j++) { + x = l2[j]; + if (ref = x.name, indexOf.call(l1_names, ref) < 0) { + results.push(x); + } + } + return results; + })()); + this.sample_lists['samples_all'] = l3; + longest_sample_name_len = d3.max((function() { + var j, len, results; + results = []; + for (j = 0, len = l3.length; j < len; j++) { + sample = l3[j]; + results.push(sample.name.length); + } + return results; + })()); + this.margin = { + top: 20, + right: 20, + bottom: longest_sample_name_len * 6, + left: 40 + }; + this.attributes = (function() { + var results; + results = []; + for (key in sample_lists[0][0]["extra_attributes"]) { + results.push(key); + } + return results; + })(); + this.sample_attr_vals = (function() { + var j, len, ref, results; + ref = this.sample_lists['samples_all']; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + s = ref[j]; + if (s.value !== null) { + results.push(this.extra(s)); + } + } + return results; + }).call(this); + this.get_distinct_attr_vals(); + this.get_attr_color_dict(this.distinct_attr_vals); + this.attribute_name = "None"; + this.sort_by = "name"; + this.chart = null; + this.select_attribute_box = $("#color_attribute"); + d3.select("#color_attribute").on("change", (function(_this) { + return function() { + _this.attribute_name = _this.select_attribute_box.val(); + return _this.rebuild_bar_graph(); + }; + })(this)); + $(".sort_by_value").on("click", (function(_this) { + return function() { + console.log("sorting by value"); + _this.sort_by = "value"; + return _this.rebuild_bar_graph(); + }; + })(this)); + $(".sort_by_name").on("click", (function(_this) { + return function() { + console.log("sorting by name"); + _this.sort_by = "name"; + return _this.rebuild_bar_graph(); + }; + })(this)); + d3.select("#color_by_trait").on("click", (function(_this) { + return function() { + return _this.open_trait_selection(); + }; + })(this)); + } + + Bar_Chart.prototype.value = function(sample) { + return this.value_dict[sample.name].value; + }; + + Bar_Chart.prototype.variance = function(sample) { + return this.value_dict[sample.name].variance; + }; + + Bar_Chart.prototype.extra = function(sample) { + var attr_vals, attribute, j, len, ref; + attr_vals = {}; + ref = this.attributes; + for (j = 0, len = ref.length; j < len; j++) { + attribute = ref[j]; + attr_vals[attribute] = sample["extra_attributes"][attribute]; + } + return attr_vals; + }; + + Bar_Chart.prototype.redraw = function(samples_dict, selected_group) { + var x; + this.value_dict = samples_dict[selected_group]; + this.raw_data = (function() { + var j, len, ref, results; + ref = this.sample_lists[selected_group]; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + x = ref[j]; + if (x.name in this.value_dict && this.value(x) !== null) { + results.push(x); + } + } + return results; + }).call(this); + return this.rebuild_bar_graph(); + }; + + Bar_Chart.prototype.rebuild_bar_graph = function() { + var container, h, raw_data; + raw_data = this.raw_data.slice(); + if (this.sort_by === 'value') { + raw_data = raw_data.sort((function(_this) { + return function(x, y) { + return _this.value(x) - _this.value(y); + }; + })(this)); + } + console.log("raw_data: ", raw_data); + h = 600; + container = $("#bar_chart_container"); + container.height(h + this.margin.top + this.margin.bottom); + if (this.chart === null) { + this.chart = nv.models.multiBarChart().height(h).errorBarColor((function(_this) { + return function() { + return 'red'; + }; + })(this)).reduceXTicks(false).staggerLabels(false).showControls(false).showLegend(false); + this.chart.multibar.dispatch.on('elementMouseover.tooltip', (function(_this) { + return function(evt) { + var k, ref, v; + evt.value = _this.chart.x()(evt.data); + evt['series'] = [ + { + key: 'Value', + value: evt.data.y, + color: evt.color + } + ]; + if (evt.data.yErr) { + evt['series'].push({ + key: 'SE', + value: evt.data.yErr + }); + } + if (evt.data.attr) { + ref = evt.data.attr; + for (k in ref) { + v = ref[k]; + evt['series'].push({ + key: k, + value: v + }); + } + } + return _this.chart.tooltip.data(evt).hidden(false); + }; + })(this)); + this.chart.tooltip.valueFormatter(function(d, i) { + return d; + }); + } + return nv.addGraph((function(_this) { + return function() { + var d, s, values; + _this.remove_legend(); + values = (function() { + var j, len, results; + results = []; + for (j = 0, len = raw_data.length; j < len; j++) { + s = raw_data[j]; + results.push({ + x: s.name, + y: this.value(s), + yErr: this.variance(s) || 0, + attr: s.extra_attributes + }); + } + return results; + }).call(_this); + if (_this.attribute_name !== "None") { + _this.color_dict = _this.attr_color_dict[_this.attribute_name]; + _this.chart.barColor(function(d) { + return _this.color_dict[d.attr[_this.attribute_name]]; + }); + _this.add_legend(); + } else if (typeof _this.trait_color_dict !== 'undefined') { + _this.color_dict = _this.trait_color_dict; + _this.chart.barColor(function(d) { + return _this.color_dict[d['x']]; + }); + } else { + _this.chart.barColor(function() { + return 'steelblue'; + }); + } + _this.chart.width(raw_data.length * 20); + //User should be able to change Y domain, but should still have good default + _this.chart.yDomain([ + 0.95 * _.min((function() { // ZS: Decreasing this constant decreases the min Y axis value + var j, len, results; + results = []; + for (j = 0, len = values.length; j < len; j++) { + d = values[j]; + results.push(d.y - 0.5 * d.yErr); //ZS: the 0.5 was originally 1.5 + } + return results; + })()), 1.05 * _.max((function() { // ZS: Decreasing this constant decreases the max Y axis value + var j, len, results; + results = []; + for (j = 0, len = values.length; j < len; j++) { + d = values[j]; + results.push(d.y + 0.5 * d.yErr); // //ZS: the 0.5 was originally 1.5 + } + return results; + })()) + ]); + console.log("values: ", values); + + decimal_exists = "False"; + for(i=0; i < values.length; i++){ + if (values[i]['y'] % 1 != 0){ + decimal_exists = "True"; + break; + } + } + if (decimal_exists == "False"){ + _this.chart.yAxis.tickFormat(d3.format('d')) + } + d3.select("#bar_chart_container svg").datum([ + { + values: values + } + ]).style('width', raw_data.length * 20 + 'px').transition().duration(1000).call(_this.chart); + d3.select("#bar_chart_container .nv-x").selectAll('.tick text').style("font-size", "12px").style("text-anchor", "end").attr("dx", "-.8em").attr("dy", "-.3em").attr("transform", function(d) { + return "rotate(-90)"; + }); + return _this.chart; + }; + })(this)); + }; + + Bar_Chart.prototype.get_attr_color_dict = function(vals) { + var color, color_range, discrete, distinct_vals, i, j, key, l, len, len1, results, this_color_dict, value; + this.attr_color_dict = {}; + this.is_discrete = {}; + this.minimum_values = {}; + this.maximum_values = {}; + console.log("vals:", vals); + results = []; + for (key in vals) { + if (!hasProp.call(vals, key)) continue; + distinct_vals = vals[key]; + this.min_val = d3.min(distinct_vals); + this.max_val = d3.max(distinct_vals); + this_color_dict = {}; + discrete = distinct_vals.length < 10; + if (discrete) { + color = d3.scale.category10(); + for (i = j = 0, len = distinct_vals.length; j < len; i = ++j) { + value = distinct_vals[i]; + this_color_dict[value] = color(i); + } + } else { + console.log("distinct_values:", distinct_vals); + if (_.every(distinct_vals, (function(_this) { + return function(d) { + if (isNaN(d)) { + return false; + } else { + return true; + } + }; + })(this))) { + color_range = d3.scale.linear().domain([this.min_val, this.max_val]).range([0, 255]); + for (i = l = 0, len1 = distinct_vals.length; l < len1; i = ++l) { + value = distinct_vals[i]; + console.log("color_range(value):", parseInt(color_range(value))); + this_color_dict[value] = d3.rgb(parseInt(color_range(value)), 0, 0); + } + } + } + this.attr_color_dict[key] = this_color_dict; + this.is_discrete[key] = discrete; + this.minimum_values[key] = this.min_val; + results.push(this.maximum_values[key] = this.max_val); + } + return results; + }; + + Bar_Chart.prototype.get_distinct_attr_vals = function() { + var attribute, j, len, ref, results, sample; + this.distinct_attr_vals = {}; + ref = this.sample_attr_vals; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + sample = ref[j]; + results.push((function() { + var ref1, results1; + results1 = []; + for (attribute in sample) { + if (!this.distinct_attr_vals[attribute]) { + this.distinct_attr_vals[attribute] = []; + } + if (ref1 = sample[attribute], indexOf.call(this.distinct_attr_vals[attribute], ref1) < 0) { + results1.push(this.distinct_attr_vals[attribute].push(sample[attribute])); + } else { + results1.push(void 0); + } + } + return results1; + }).call(this)); + } + return results; + }; + + Bar_Chart.prototype.add_legend = function() { + if (this.is_discrete[this.attribute_name]) { + return this.add_legend_discrete(); + } else { + return this.add_legend_continuous(); + } + }; + + Bar_Chart.prototype.remove_legend = function() { + $(".legend").remove(); + return $("#legend-left,#legend-right,#legend-colors").empty(); + }; + + Bar_Chart.prototype.add_legend_continuous = function() { + var svg_html; + $('#legend-left').html(this.minimum_values[this.attribute_name]); + $('#legend-right').html(this.maximum_values[this.attribute_name]); + svg_html = '<svg height="15" width="120"> <rect x="0" width="20" height="15" style="fill: rgb(0, 0, 0);"></rect> <rect x="20" width="20" height="15" style="fill: rgb(50, 0, 0);"></rect> <rect x="40" width="20" height="15" style="fill: rgb(100, 0, 0);"></rect> <rect x="60" width="20" height="15" style="fill: rgb(150, 0, 0);"></rect> <rect x="80" width="20" height="15" style="fill: rgb(200, 0, 0);"></rect> <rect x="100" width="20" height="15" style="fill: rgb(255, 0, 0);"></rect> </svg>'; + console.log("svg_html:", svg_html); + return $('#legend-colors').html(svg_html); + }; + + Bar_Chart.prototype.add_legend_discrete = function() { + var legend_span; + legend_span = d3.select('#bar_chart_legend').append('div').style('word-wrap', 'break-word').attr('class', 'legend').selectAll('span').data(this.distinct_attr_vals[this.attribute_name]).enter().append('span').style({ + 'word-wrap': 'normal', + 'display': 'inline-block' + }); + legend_span.append('span').style("background-color", (function(_this) { + return function(d) { + return _this.attr_color_dict[_this.attribute_name][d]; + }; + })(this)).style({ + 'display': 'inline-block', + 'width': '15px', + 'height': '15px', + 'margin': '0px 5px 0px 15px' + }); + return legend_span.append('span').text((function(_this) { + return function(d) { + return d; + }; + })(this)).style('font-size', '20px'); + }; + + Bar_Chart.prototype.open_trait_selection = function() { + return $('#collections_holder').load('/collections/list?color_by_trait #collections_list', (function(_this) { + return function() { + $.colorbox({ + inline: true, + href: "#collections_holder" + }); + return $('a.collection_name').attr('onClick', 'return false'); + }; + })(this)); + }; + + Bar_Chart.prototype.color_by_trait = function(trait_sample_data) { + var distinct_values, trimmed_samples; + console.log("BXD1:", trait_sample_data["BXD1"]); + console.log("trait_sample_data:", trait_sample_data); + trimmed_samples = this.trim_values(trait_sample_data); + distinct_values = {}; + distinct_values["collection_trait"] = this.get_distinct_values(trimmed_samples); + this.trait_color_dict = this.get_trait_color_dict(trimmed_samples, distinct_values); + console.log("TRAIT_COLOR_DICT:", this.trait_color_dict); + return this.rebuild_bar_graph(); + //return console.log("SAMPLES:", this.samples); + }; + + Bar_Chart.prototype.trim_values = function(trait_sample_data) { + var j, len, ref, sample, trimmed_samples; + trimmed_samples = {}; + ref = this.sample_lists['samples_all']; + for (j = 0, len = ref.length; j < len; j++) { + sample = ref[j]['name']; + if (sample in trait_sample_data) { + trimmed_samples[sample] = trait_sample_data[sample]; + } + } + console.log("trimmed_samples:", trimmed_samples); + return trimmed_samples; + }; + + Bar_Chart.prototype.get_distinct_values = function(samples) { + var distinct_values; + distinct_values = _.uniq(_.values(samples)); + console.log("distinct_values:", distinct_values); + return distinct_values; + }; + + Bar_Chart.prototype.get_trait_color_dict = function(samples, vals) { + var color, color_range, distinct_vals, i, j, k, key, len, len1, results, sample, this_color_dict, value; + trait_color_dict = {}; + console.log("vals:", vals); + for (key in vals) { + if (!hasProp.call(vals, key)) continue; + distinct_vals = vals[key]; + this_color_dict = {}; + this.min_val = d3.min(distinct_vals); + this.max_val = d3.max(distinct_vals); + if (distinct_vals.length < 10) { + color = d3.scale.category10(); + for (i = j = 0, len = distinct_vals.length; j < len; i = ++j) { + value = distinct_vals[i]; + this_color_dict[value] = color(i); + } + } else { + console.log("distinct_values:", distinct_vals); + if (_.every(distinct_vals, (function(_this) { + return function(d) { + if (isNaN(d)) { + return false; + } else { + return true; + } + }; + })(this))) { + color_range = d3.scale.linear().domain([d3.min(distinct_vals), d3.max(distinct_vals)]).range([0, 255]); + for (i = k = 0, len1 = distinct_vals.length; k < len1; i = ++k) { + value = distinct_vals[i]; + //console.log("color_range(value):", parseInt(color_range(value))); + this_color_dict[value] = d3.rgb(parseInt(color_range(value)), 0, 0); + } + } + } + } + results = []; + console.log("SAMPLES:", samples) + for (sample in samples) { + if (!hasProp.call(samples, sample)) continue; + value = samples[sample]; + trait_color_dict[sample] = this_color_dict[value]; + //results.push(this.trait_color_dict[sample] = this_color_dict[value]); + } + return trait_color_dict; + }; + + return Bar_Chart; + + })(); + + root.Bar_Chart = Bar_Chart; + +}).call(this); diff --git a/gn2/wqflask/static/new/javascript/box.js b/gn2/wqflask/static/new/javascript/box.js new file mode 100644 index 00000000..aae80f05 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/box.js @@ -0,0 +1,307 @@ +//Taken from http://bl.ocks.org/mbostock/4061502
+
+(function() {
+
+// Inspired by http://informationandvisualization.de/blog/box-plot
+d3.box = function() {
+ var width = 1,
+ height = 1,
+ duration = 0,
+ domain = null,
+ value = Number,
+ whiskers = boxWhiskers,
+ quartiles = boxQuartiles,
+ tickFormat = null;
+
+ // For each small multiple…
+ function box(g) {
+ g.each(function(d, i) {
+ console.log("d:", d)
+ console.log("domain:", domain)
+ d = d.map(value).sort(d3.ascending);
+ var g = d3.select(this),
+ n = d.length,
+ min = d[0],
+ max = d[n - 1];
+
+ // Compute quartiles. Must return exactly 3 elements.
+ var quartileData = d.quartiles = quartiles(d);
+
+ // Compute whiskers. Must return exactly 2 elements, or null.
+ var whiskerIndices = whiskers && whiskers.call(this, d, i),
+ whiskerData = whiskerIndices && whiskerIndices.map(function(i) { return d[i]; });
+
+ // Compute outliers. If no whiskers are specified, all data are "outliers".
+ // We compute the outliers as indices, so that we can join across transitions!
+ var outlierIndices = whiskerIndices
+ ? d3.range(0, whiskerIndices[0]).concat(d3.range(whiskerIndices[1] + 1, n))
+ : d3.range(n);
+
+ // Compute the new x-scale.
+ var x1 = d3.scale.linear()
+ .domain(domain && domain.call(this, d, i) || [min, max])
+ .range([height, 0]);
+
+ // Retrieve the old x-scale, if this is an update.
+ var x0 = this.__chart__ || d3.scale.linear()
+ .domain([0, Infinity])
+ .range(x1.range());
+
+ // Stash the new scale.
+ this.__chart__ = x1;
+
+ // Note: the box, median, and box tick elements are fixed in number,
+ // so we only have to handle enter and update. In contrast, the outliers
+ // and other elements are variable, so we need to exit them! Variable
+ // elements also fade in and out.
+
+ // Update center line: the vertical line spanning the whiskers.
+ var center = g.selectAll("line.center")
+ .data(whiskerData ? [whiskerData] : []);
+
+ center.enter().insert("line", "rect")
+ .attr("class", "center")
+ .attr("x1", width / 2)
+ .attr("y1", function(d) { return x0(d[0]); })
+ .attr("x2", width / 2)
+ .attr("y2", function(d) { return x0(d[1]); })
+ .style("opacity", 1e-6)
+ .transition()
+ .duration(duration)
+ .style("opacity", 1)
+ .attr("y1", function(d) { return x1(d[0]); })
+ .attr("y2", function(d) { return x1(d[1]); });
+
+ center.transition()
+ .duration(duration)
+ .style("opacity", 1)
+ .attr("y1", function(d) { return x1(d[0]); })
+ .attr("y2", function(d) { return x1(d[1]); });
+
+ center.exit().transition()
+ .duration(duration)
+ .style("opacity", 1e-6)
+ .attr("y1", function(d) { return x1(d[0]); })
+ .attr("y2", function(d) { return x1(d[1]); })
+ .remove();
+
+ // Update innerquartile box.
+ var box = g.selectAll("rect.box")
+ .data([quartileData]);
+
+ box.enter().append("rect")
+ .attr("class", "box")
+ .attr("x", 0)
+ .attr("y", function(d) { return x0(d[2]); })
+ .attr("width", width)
+ .attr("height", function(d) { return x0(d[0]) - x0(d[2]); })
+ .transition()
+ .duration(duration)
+ .attr("y", function(d) { return x1(d[2]); })
+ .attr("height", function(d) { return x1(d[0]) - x1(d[2]); });
+
+ box.transition()
+ .duration(duration)
+ .attr("y", function(d) { return x1(d[2]); })
+ .attr("height", function(d) { return x1(d[0]) - x1(d[2]); });
+
+ // Update median line.
+ var medianLine = g.selectAll("line.median")
+ .data([quartileData[1]]);
+
+ medianLine.enter().append("line")
+ .attr("class", "median")
+ .attr("x1", 0)
+ .attr("y1", x0)
+ .attr("x2", width)
+ .attr("y2", x0)
+ .transition()
+ .duration(duration)
+ .attr("y1", x1)
+ .attr("y2", x1);
+
+ medianLine.transition()
+ .duration(duration)
+ .attr("y1", x1)
+ .attr("y2", x1);
+
+ // Update whiskers.
+ var whisker = g.selectAll("line.whisker")
+ .data(whiskerData || []);
+
+ whisker.enter().insert("line", "circle, text")
+ .attr("class", "whisker")
+ .attr("x1", 0)
+ .attr("y1", x0)
+ .attr("x2", width)
+ .attr("y2", x0)
+ .style("opacity", 1e-6)
+ .transition()
+ .duration(duration)
+ .attr("y1", x1)
+ .attr("y2", x1)
+ .style("opacity", 1);
+
+ whisker.transition()
+ .duration(duration)
+ .attr("y1", x1)
+ .attr("y2", x1)
+ .style("opacity", 1);
+
+ whisker.exit().transition()
+ .duration(duration)
+ .attr("y1", x1)
+ .attr("y2", x1)
+ .style("opacity", 1e-6)
+ .remove();
+
+ // Update outliers.
+ var outlier = g.selectAll("circle.outlier")
+ .data(outlierIndices, Number);
+
+ outlier.enter().insert("circle", "text")
+ .attr("class", "outlier")
+ .attr("r", 5)
+ .attr("cx", width / 2)
+ .attr("cy", function(i) { return x0(d[i]); })
+ .style("opacity", 1e-6)
+ .transition()
+ .duration(duration)
+ .attr("cy", function(i) { return x1(d[i]); })
+ .style("opacity", 1);
+
+ outlier.transition()
+ .duration(duration)
+ .attr("cy", function(i) { return x1(d[i]); })
+ .style("opacity", 1);
+
+ outlier.exit().transition()
+ .duration(duration)
+ .attr("cy", function(i) { return x1(d[i]); })
+ .style("opacity", 1e-6)
+ .remove();
+
+ // Compute the tick format.
+ var format = tickFormat || x1.tickFormat(8);
+
+ // Update box ticks.
+ var boxTick = g.selectAll("text.box")
+ .data(quartileData);
+
+ console.log("quartileData:", quartileData);
+
+ boxTick.enter().append("text")
+ .attr("class", "box")
+ .attr("dy", ".3em")
+ .attr("dx", function(d, i) { return i & 1 ? 6 : -6 })
+ .attr("x", function(d, i) { return i & 1 ? width : 0 })
+ .attr("y", x0)
+ .attr("text-anchor", function(d, i) { return i & 1 ? "start" : "end"; })
+ .text(format)
+ .transition()
+ .duration(duration)
+ .attr("y", x1);
+
+ boxTick.transition()
+ .duration(duration)
+ .text(format)
+ .attr("y", x1);
+
+ // Update whisker ticks. These are handled separately from the box
+ // ticks because they may or may not exist, and we want don't want
+ // to join box ticks pre-transition with whisker ticks post-.
+ var whiskerTick = g.selectAll("text.whisker")
+ .data(whiskerData || []);
+
+ whiskerTick.enter().append("text")
+ .attr("class", "whisker")
+ .attr("dy", ".3em")
+ .attr("dx", 6)
+ .attr("x", width)
+ .attr("y", x0)
+ .text(format)
+ .style("opacity", 1e-6)
+ .transition()
+ .duration(duration)
+ .attr("y", x1)
+ .style("opacity", 1);
+
+ whiskerTick.transition()
+ .duration(duration)
+ .text(format)
+ .attr("y", x1)
+ .style("opacity", 1);
+
+ whiskerTick.exit().transition()
+ .duration(duration)
+ .attr("y", x1)
+ .style("opacity", 1e-6)
+ .remove();
+ });
+ d3.timer.flush();
+ }
+
+ box.width = function(x) {
+ if (!arguments.length) return width;
+ width = x;
+ return box;
+ };
+
+ box.height = function(x) {
+ if (!arguments.length) return height;
+ height = x;
+ return box;
+ };
+
+ box.tickFormat = function(x) {
+ if (!arguments.length) return tickFormat;
+ tickFormat = x;
+ return box;
+ };
+
+ box.duration = function(x) {
+ if (!arguments.length) return duration;
+ duration = x;
+ return box;
+ };
+
+ box.domain = function(x) {
+ if (!arguments.length) return domain;
+ domain = x == null ? x : d3.functor(x);
+ return box;
+ };
+
+ box.value = function(x) {
+ if (!arguments.length) return value;
+ value = x;
+ return box;
+ };
+
+ box.whiskers = function(x) {
+ if (!arguments.length) return whiskers;
+ whiskers = x;
+ return box;
+ };
+
+ box.quartiles = function(x) {
+ if (!arguments.length) return quartiles;
+ quartiles = x;
+ return box;
+ };
+
+ return box;
+};
+
+function boxWhiskers(d) {
+ return [0, d.length - 1];
+}
+
+function boxQuartiles(d) {
+ return [
+ d3.quantile(d, .25),
+ d3.quantile(d, .5),
+ d3.quantile(d, .75)
+ ];
+}
+
+})();
\ No newline at end of file diff --git a/gn2/wqflask/static/new/javascript/box_plot.js b/gn2/wqflask/static/new/javascript/box_plot.js new file mode 100644 index 00000000..566a8eb8 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/box_plot.js @@ -0,0 +1,80 @@ +// Generated by CoffeeScript 1.8.0 +var Box_Plot, root; + +root = typeof exports !== "undefined" && exports !== null ? exports : this; + +Box_Plot = (function() { + function Box_Plot(sample_list, sample_group) { + this.sample_list = sample_list; + this.sample_group = sample_group; + this.get_samples(); + this.margin = { + top: 10, + right: 50, + bottom: 20, + left: 50 + }; + this.plot_width = 200 - this.margin.left - this.margin.right; + this.plot_height = 500 - this.margin.top - this.margin.bottom; + this.min = d3.min(this.sample_vals); + this.max = d3.max(this.sample_vals); + this.svg = this.create_svg(); + this.enter_data(); + } + + Box_Plot.prototype.get_samples = function() { + var sample; + return this.sample_vals = (function() { + var _i, _len, _ref, _results; + _ref = this.sample_list; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + sample = _ref[_i]; + if (sample.value !== null) { + _results.push(sample.value); + } + } + return _results; + }).call(this); + }; + + Box_Plot.prototype.create_svg = function() { + var svg; + svg = d3.box().whiskers(this.inter_quartile_range(1.5)).width(this.plot_width - 30).height(this.plot_height - 30).domain([this.min, this.max]); + return svg; + }; + + Box_Plot.prototype.enter_data = function() { + return d3.select("#box_plot").selectAll("svg").data([this.sample_vals]).enter().append("svg:svg").attr("class", "box").attr("width", this.plot_width).attr("height", this.plot_height).append("svg:g").call(this.svg); + }; + + Box_Plot.prototype.inter_quartile_range = function(k) { + return (function(_this) { + return function(d, i) { + var inter_quartile_range, j, q1, q3; + console.log("iqr d:", d); + q1 = d.quartiles[0]; + q3 = d.quartiles[2]; + inter_quartile_range = (q3 - q1) * k; + console.log("iqr:", inter_quartile_range); + i = 0; + j = d.length; + console.log("d[-1]:", d[1]); + console.log("q1 - iqr:", q1 - inter_quartile_range); + while (d[i] < q1 - inter_quartile_range) { + i++; + } + while (d[j] > q3 + inter_quartile_range) { + j--; + } + console.log("[i, j]", [i, j]); + return [i, j]; + }; + })(this); + }; + + return Box_Plot; + +})(); + +root.Box_Plot = Box_Plot; diff --git a/gn2/wqflask/static/new/javascript/chr_lod_chart.js b/gn2/wqflask/static/new/javascript/chr_lod_chart.js new file mode 100644 index 00000000..c6cbd01b --- /dev/null +++ b/gn2/wqflask/static/new/javascript/chr_lod_chart.js @@ -0,0 +1,297 @@ +// Generated by CoffeeScript 1.9.2 +var Chr_Lod_Chart; + +Chr_Lod_Chart = (function() { + function Chr_Lod_Chart(plot_height, plot_width, chr, manhattanPlot, mappingScale) { + this.plot_height = plot_height; + this.plot_width = plot_width; + this.chr = chr; + this.manhattanPlot = manhattanPlot; + this.mappingScale = mappingScale; + this.qtl_results = js_data.qtl_results; + console.log("qtl_results are:", this.qtl_results); + console.log("chr is:", this.chr); + this.get_max_chr(); + this.filter_qtl_results(); + console.log("filtered results:", this.these_results); + this.get_qtl_count(); + this.x_coords = []; + this.y_coords = []; + this.marker_names = []; + console.time('Create coordinates'); + this.create_coordinates(); + console.log("@x_coords: ", this.x_coords); + console.log("@y_coords: ", this.y_coords); + console.timeEnd('Create coordinates'); + this.x_buffer = this.plot_width / 30; + this.y_buffer = this.plot_height / 20; + this.x_max = d3.max(this.x_coords); + this.y_max = d3.max(this.y_coords) * 1.2; + this.y_threshold = this.get_lod_threshold(); + this.svg = this.create_svg(); + this.plot_coordinates = _.zip(this.x_coords, this.y_coords, this.marker_names); + console.log("coordinates:", this.plot_coordinates); + this.plot_height -= this.y_buffer; + this.create_scales(); + console.time('Create graph'); + this.create_graph(); + console.timeEnd('Create graph'); + } + + Chr_Lod_Chart.prototype.get_max_chr = function() { + var key, results; + this.max_chr = 0; + results = []; + for (key in js_data.chromosomes) { + console.log("key is:", key); + if (parseInt(key) > this.max_chr) { + results.push(this.max_chr = parseInt(key)); + } else { + results.push(void 0); + } + } + return results; + }; + + Chr_Lod_Chart.prototype.filter_qtl_results = function() { + var i, len, ref, result, results, this_chr; + this.these_results = []; + this_chr = 100; + ref = this.qtl_results; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + result = ref[i]; + if (result.chr === "X") { + this_chr = this.max_chr; + } else { + this_chr = result.chr; + } + if (this_chr > parseInt(this.chr[0])) { + break; + } + if (parseInt(this_chr) === parseInt(this.chr[0])) { + results.push(this.these_results.push(result)); + } else { + results.push(void 0); + } + } + return results; + }; + + Chr_Lod_Chart.prototype.get_qtl_count = function() { + var high_qtl_count, i, len, ref, result; + high_qtl_count = 0; + ref = this.these_results; + for (i = 0, len = ref.length; i < len; i++) { + result = ref[i]; + if (result.lod_score > 1) { + high_qtl_count += 1; + } + } + console.log("high_qtl_count:", high_qtl_count); + return this.y_axis_filter = 2; + }; + + Chr_Lod_Chart.prototype.create_coordinates = function() { + var i, len, ref, result, results; + ref = this.these_results; + console.log("THESE_RESULTS:", ref) + results = []; + for (i = 0, len = ref.length; i < len; i++) { + result = ref[i]; + this.x_coords.push(parseFloat(result.Mb)); + if (js_data.result_score_type == "LOD") { + this.y_coords.push(result.lod_score); + } + else { + console.log("LRS VALUE:", result['lrs_value']) + this.y_coords.push(result['lrs_value']); + } + results.push(this.marker_names.push(result.name)); + } + return results; + }; + + Chr_Lod_Chart.prototype.create_svg = function() { + var svg; + svg = d3.select("#topchart").append("svg").attr("class", "chr_manhattan_plot").attr("width", this.plot_width + this.x_buffer).attr("height", this.plot_height + this.y_buffer).append("g"); + return svg; + }; + + Chr_Lod_Chart.prototype.create_scales = function() { + if (this.mappingScale == "morgan") { + max_pos = 0 + for (i = 0, len = this.these_results.length; i < len; i++) { + marker = this.these_results[i] + if (parseFloat(marker['Mb']) > max_pos){ + max_pos = parseFloat(marker.Mb) + } + } + this.x_scale = d3.scale.linear().domain([0, max_pos]).range([this.x_buffer, this.plot_width]); + } + else { + this.x_scale = d3.scale.linear().domain([0, this.chr[1]]).range([this.x_buffer, this.plot_width]); + } + return this.y_scale = d3.scale.linear().domain([0, this.y_max]).range([this.plot_height, this.y_buffer]); + }; + + Chr_Lod_Chart.prototype.get_lod_threshold = function() { + if (this.y_max / 2 > 2) { + return this.y_max / 2; + } else { + return 2; + } + }; + + Chr_Lod_Chart.prototype.create_graph = function() { + this.add_border(); + this.add_x_axis(); + this.add_y_axis(); + this.add_title(); + this.add_back_button(); + if (this.manhattanPlot) { + return this.add_plot_points(); + } else { + return this.add_path(); + } + }; + + Chr_Lod_Chart.prototype.add_border = function() { + var border_coords; + border_coords = [[this.y_buffer, this.plot_height, this.x_buffer, this.x_buffer], [this.y_buffer, this.plot_height, this.plot_width, this.plot_width], [this.y_buffer, this.y_buffer, this.x_buffer, this.plot_width], [this.plot_height, this.plot_height, this.x_buffer, this.plot_width]]; + return this.svg.selectAll("line").data(border_coords).enter().append("line").attr("y1", (function(_this) { + return function(d) { + return d[0]; + }; + })(this)).attr("y2", (function(_this) { + return function(d) { + return d[1]; + }; + })(this)).attr("x1", (function(_this) { + return function(d) { + return d[2]; + }; + })(this)).attr("x2", (function(_this) { + return function(d) { + return d[3]; + }; + })(this)).style("stroke", "#000"); + }; + + Chr_Lod_Chart.prototype.add_x_axis = function() { + this.xAxis = d3.svg.axis().scale(this.x_scale).orient("bottom").ticks(20); + this.xAxis.tickFormat((function(_this) { + return function(d) { + d3.format("d"); + return d; + }; + })(this)); + return this.svg.append("g").attr("class", "x_axis").attr("transform", "translate(0," + this.plot_height + ")").call(this.xAxis).selectAll("text").attr("text-anchor", "right").attr("font-size", "12px").attr("dx", "-1.6em").attr("transform", (function(_this) { + return function(d) { + return "translate(-12,0) rotate(-90)"; + }; + })(this)); + }; + + Chr_Lod_Chart.prototype.add_y_axis = function() { + this.yAxis = d3.svg.axis().scale(this.y_scale).orient("left").ticks(5); + return this.svg.append("g").attr("class", "y_axis").attr("transform", "translate(" + this.x_buffer + ",0)").call(this.yAxis); + }; + + Chr_Lod_Chart.prototype.add_title = function() { + return this.svg.append("text").attr("class", "title").text("Chr " + this.chr[0]).attr("x", (function(_this) { + return function(d) { + return (_this.plot_width + _this.x_buffer) / 2; + }; + })(this)).attr("y", this.y_buffer + 20).attr("dx", "0em").attr("text-anchor", "middle").attr("font-family", "sans-serif").attr("font-size", "18px").attr("fill", "black"); + }; + + Chr_Lod_Chart.prototype.add_back_button = function() { + return $("#return_to_full_view").show().click((function(_this) { + return function() { + return _this.return_to_full_view(); + }; + })(this)); + }; + + Chr_Lod_Chart.prototype.add_path = function() { + var line_function, line_graph; + line_function = d3.svg.line().x((function(_this) { + return function(d) { + return _this.x_scale(d[0]); + }; + })(this)).y((function(_this) { + return function(d) { + return _this.y_scale(d[1]); + }; + })(this)).interpolate("linear"); + return line_graph = this.svg.append("path").attr("d", line_function(this.plot_coordinates)).attr("stroke", "blue").attr("stroke-width", 1).attr("fill", "none"); + }; + + Chr_Lod_Chart.prototype.add_plot_points = function() { + return this.plot_point = this.svg.selectAll("circle").data(this.plot_coordinates).enter().append("circle").attr("cx", (function(_this) { + return function(d) { + return _this.x_scale(d[0]); + }; + })(this)).attr("cy", (function(_this) { + return function(d) { + return _this.y_scale(d[1]); + }; + })(this)).attr("r", (function(_this) { + return function(d) { + return 2; + }; + })(this)).attr("fill", (function(_this) { + return function(d) { + return "black"; + }; + })(this)).attr("stroke", "black").attr("stroke-width", "1").attr("id", (function(_this) { + return function(d) { + return "point_" + String(d[2]); + }; + })(this)).classed("circle", true).on("mouseover", (function(_this) { + return function(d) { + var this_id; + console.log("d3.event is:", d3.event); + console.log("d is:", d); + this_id = "point_" + String(d[2]); + return d3.select("#" + this_id).classed("d3_highlight", true).attr("r", 5).attr("stroke", "none").attr("fill", "blue").call(_this.show_marker_in_table(d)); + }; + })(this)).on("mouseout", (function(_this) { + return function(d) { + var this_id; + this_id = "point_" + String(d[2]); + return d3.select("#" + this_id).classed("d3_highlight", false).attr("r", function(d) { + return 2; + }).attr("fill", function(d) { + return "black"; + }).attr("stroke", "black").attr("stroke-width", "1"); + }; + })(this)).append("svg:title").text((function(_this) { + return function(d) { + return d[2]; + }; + })(this)); + }; + + Chr_Lod_Chart.prototype.return_to_full_view = function() { + $("#return_to_full_view").hide(); + $('#topchart').remove(); + $('#chart_container').append('<div class="qtlcharts" id="topchart"></div>'); + return create_lod_chart(); + }; + + Chr_Lod_Chart.prototype.show_marker_in_table = function(marker_info) { + var marker_name; + console.log("in show_marker_in_table"); + + /* Searches for the select marker in the results table below */ + if (marker_info) { + marker_name = marker_info[2]; + return $("#qtl_results_filter").find("input:first").val(marker_name).change(); + } + }; + + return Chr_Lod_Chart; + +})(); diff --git a/gn2/wqflask/static/new/javascript/chr_manhattan_plot.js b/gn2/wqflask/static/new/javascript/chr_manhattan_plot.js new file mode 100644 index 00000000..c661edc7 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/chr_manhattan_plot.js @@ -0,0 +1,273 @@ +// Generated by CoffeeScript 1.8.0 +var Chr_Manhattan_Plot; + +Chr_Manhattan_Plot = (function() { + function Chr_Manhattan_Plot(plot_height, plot_width, chr, manhattanPlot) { + this.plot_height = plot_height; + this.plot_width = plot_width; + this.chr = chr; + this.qtl_results = js_data.qtl_results; + console.log("qtl_results are:", this.qtl_results); + console.log("chr is:", this.chr); + this.get_max_chr(); + this.filter_qtl_results(); + console.log("filtered results:", this.these_results); + this.get_qtl_count(); + this.x_coords = []; + this.y_coords = []; + this.marker_names = []; + console.time('Create coordinates'); + this.create_coordinates(); + console.log("@x_coords: ", this.x_coords); + console.log("@y_coords: ", this.y_coords); + console.timeEnd('Create coordinates'); + this.x_buffer = this.plot_width / 30; + this.y_buffer = this.plot_height / 20; + this.x_max = d3.max(this.x_coords); + this.y_max = d3.max(this.y_coords) * 1.2; + this.y_threshold = this.get_lod_threshold(); + this.svg = this.create_svg(); + this.plot_coordinates = _.zip(this.x_coords, this.y_coords, this.marker_names); + console.log("coordinates:", this.plot_coordinates); + this.plot_height -= this.y_buffer; + this.create_scales(); + console.time('Create graph'); + this.create_graph(); + console.timeEnd('Create graph'); + } + + Chr_Manhattan_Plot.prototype.get_max_chr = function() { + var key, _results; + this.max_chr = 0; + _results = []; + for (key in js_data.chromosomes) { + console.log("key is:", key); + if (parseInt(key) > this.max_chr) { + _results.push(this.max_chr = parseInt(key)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + Chr_Manhattan_Plot.prototype.filter_qtl_results = function() { + var result, this_chr, _i, _len, _ref, _results; + this.these_results = []; + this_chr = 100; + _ref = this.qtl_results; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + result = _ref[_i]; + if (result.chr === "X") { + this_chr = this.max_chr; + } else { + this_chr = result.chr; + } + console.log("this_chr is:", this_chr); + console.log("@chr[0] is:", parseInt(this.chr[0])); + if (this_chr > parseInt(this.chr[0])) { + break; + } + if (parseInt(this_chr) === parseInt(this.chr[0])) { + _results.push(this.these_results.push(result)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + Chr_Manhattan_Plot.prototype.get_qtl_count = function() { + var high_qtl_count, result, _i, _len, _ref; + high_qtl_count = 0; + _ref = this.these_results; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + result = _ref[_i]; + if (result.lod_score > 1) { + high_qtl_count += 1; + } + } + console.log("high_qtl_count:", high_qtl_count); + return this.y_axis_filter = 2; + }; + + Chr_Manhattan_Plot.prototype.create_coordinates = function() { + var result, _i, _len, _ref, _results; + _ref = this.these_results; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + result = _ref[_i]; + this.x_coords.push(parseFloat(result.Mb)); + this.y_coords.push(result.lod_score); + _results.push(this.marker_names.push(result.name)); + } + return _results; + }; + + Chr_Manhattan_Plot.prototype.create_svg = function() { + var svg; + svg = d3.select("#topchart").append("svg").attr("class", "chr_manhattan_plot").attr("width", this.plot_width + this.x_buffer).attr("height", this.plot_height + this.y_buffer).append("g"); + return svg; + }; + + Chr_Manhattan_Plot.prototype.create_scales = function() { + this.x_scale = d3.scale.linear().domain([0, this.chr[1]]).range([this.x_buffer, this.plot_width]); + return this.y_scale = d3.scale.linear().domain([0, this.y_max]).range([this.plot_height, this.y_buffer]); + }; + + Chr_Manhattan_Plot.prototype.get_lod_threshold = function() { + if (this.y_max / 2 > 2) { + return this.y_max / 2; + } else { + return 2; + } + }; + + Chr_Manhattan_Plot.prototype.create_graph = function() { + this.add_border(); + this.add_x_axis(); + this.add_y_axis(); + this.add_title(); + this.add_back_button(); + if (manhattanPlot) { + return this.add_plot_points(); + } else { + return this.add_path(); + } + }; + + Chr_Manhattan_Plot.prototype.add_border = function() { + var border_coords; + border_coords = [[this.y_buffer, this.plot_height, this.x_buffer, this.x_buffer], [this.y_buffer, this.plot_height, this.plot_width, this.plot_width], [this.y_buffer, this.y_buffer, this.x_buffer, this.plot_width], [this.plot_height, this.plot_height, this.x_buffer, this.plot_width]]; + return this.svg.selectAll("line").data(border_coords).enter().append("line").attr("y1", (function(_this) { + return function(d) { + return d[0]; + }; + })(this)).attr("y2", (function(_this) { + return function(d) { + return d[1]; + }; + })(this)).attr("x1", (function(_this) { + return function(d) { + return d[2]; + }; + })(this)).attr("x2", (function(_this) { + return function(d) { + return d[3]; + }; + })(this)).style("stroke", "#000"); + }; + + Chr_Manhattan_Plot.prototype.add_x_axis = function() { + this.xAxis = d3.svg.axis().scale(this.x_scale).orient("bottom").ticks(20); + this.xAxis.tickFormat((function(_this) { + return function(d) { + d3.format("d"); + return d; + }; + })(this)); + return this.svg.append("g").attr("class", "x_axis").attr("transform", "translate(0," + this.plot_height + ")").call(this.xAxis).selectAll("text").attr("text-anchor", "right").attr("font-size", "12px").attr("dx", "-1.6em").attr("transform", (function(_this) { + return function(d) { + return "translate(-12,0) rotate(-90)"; + }; + })(this)); + }; + + Chr_Manhattan_Plot.prototype.add_y_axis = function() { + this.yAxis = d3.svg.axis().scale(this.y_scale).orient("left").ticks(5); + return this.svg.append("g").attr("class", "y_axis").attr("transform", "translate(" + this.x_buffer + ",0)").call(this.yAxis); + }; + + Chr_Manhattan_Plot.prototype.add_title = function() { + return this.svg.append("text").attr("class", "title").text("Chr " + this.chr[0]).attr("x", (function(_this) { + return function(d) { + return (_this.plot_width + _this.x_buffer) / 2; + }; + })(this)).attr("y", this.y_buffer + 20).attr("dx", "0em").attr("text-anchor", "middle").attr("font-family", "sans-serif").attr("font-size", "18px").attr("fill", "black"); + }; + + Chr_Manhattan_Plot.prototype.add_back_button = function() { + return this.svg.append("text").attr("class", "back").text("Return to full view").attr("x", this.x_buffer * 2).attr("y", this.y_buffer / 2).attr("dx", "0em").attr("text-anchor", "middle").attr("font-family", "sans-serif").attr("font-size", "18px").attr("cursor", "pointer").attr("fill", "black").on("click", this.return_to_full_view); + }; + + Chr_Manhattan_Plot.prototype.add_path = function() { + var line_function, line_graph; + line_function = d3.svg.line().x((function(_this) { + return function(d) { + return _this.x_scale(d[0]); + }; + })(this)).y((function(_this) { + return function(d) { + return _this.y_scale(d[1]); + }; + })(this)).interpolate("linear"); + return line_graph = this.svg.append("path").attr("d", line_function(this.plot_coordinates)).attr("stroke", "blue").attr("stroke-width", 1).attr("fill", "none"); + }; + + Chr_Manhattan_Plot.prototype.add_plot_points = function() { + return this.plot_point = this.svg.selectAll("circle").data(this.plot_coordinates).enter().append("circle").attr("cx", (function(_this) { + return function(d) { + return _this.x_scale(d[0]); + }; + })(this)).attr("cy", (function(_this) { + return function(d) { + return _this.y_scale(d[1]); + }; + })(this)).attr("r", (function(_this) { + return function(d) { + return 2; + }; + })(this)).attr("fill", (function(_this) { + return function(d) { + return "black"; + }; + })(this)).attr("stroke", "black").attr("stroke-width", "1").attr("id", (function(_this) { + return function(d) { + return "point_" + String(d[2]); + }; + })(this)).classed("circle", true).on("mouseover", (function(_this) { + return function(d) { + var this_id; + console.log("d3.event is:", d3.event); + console.log("d is:", d); + this_id = "point_" + String(d[2]); + return d3.select("#" + this_id).classed("d3_highlight", true).attr("r", 5).attr("stroke", "none").attr("fill", "blue").call(_this.show_marker_in_table(d)); + }; + })(this)).on("mouseout", (function(_this) { + return function(d) { + var this_id; + this_id = "point_" + String(d[2]); + return d3.select("#" + this_id).classed("d3_highlight", false).attr("r", function(d) { + return 2; + }).attr("fill", function(d) { + return "black"; + }).attr("stroke", "black").attr("stroke-width", "1"); + }; + })(this)).append("svg:title").text((function(_this) { + return function(d) { + return d[2]; + }; + })(this)); + }; + + Chr_Manhattan_Plot.prototype.return_to_full_view = function() { + $('#topchart').remove(); + $('#chart_container').append('<div class="qtlcharts" id="topchart"></div>'); + return create_manhattan_plot(); + }; + + Chr_Manhattan_Plot.prototype.show_marker_in_table = function(marker_info) { + var marker_name; + console.log("in show_marker_in_table"); + + /* Searches for the select marker in the results table below */ + if (marker_info) { + marker_name = marker_info[2]; + return $("#qtl_results_filter").find("input:first").val(marker_name).change(); + } + }; + + return Chr_Manhattan_Plot; + +})(); diff --git a/gn2/wqflask/static/new/javascript/colorbrewer.js b/gn2/wqflask/static/new/javascript/colorbrewer.js new file mode 100644 index 00000000..2efa9632 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/colorbrewer.js @@ -0,0 +1,302 @@ +// This product includes color specifications and designs developed by Cynthia Brewer (http://colorbrewer.org/).
+var colorbrewer = {YlGn: {
+3: ["#f7fcb9","#addd8e","#31a354"],
+4: ["#ffffcc","#c2e699","#78c679","#238443"],
+5: ["#ffffcc","#c2e699","#78c679","#31a354","#006837"],
+6: ["#ffffcc","#d9f0a3","#addd8e","#78c679","#31a354","#006837"],
+7: ["#ffffcc","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#005a32"],
+8: ["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#005a32"],
+9: ["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#006837","#004529"]
+},YlGnBu: {
+3: ["#edf8b1","#7fcdbb","#2c7fb8"],
+4: ["#ffffcc","#a1dab4","#41b6c4","#225ea8"],
+5: ["#ffffcc","#a1dab4","#41b6c4","#2c7fb8","#253494"],
+6: ["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#2c7fb8","#253494"],
+7: ["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"],
+8: ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"],
+9: ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"]
+},GnBu: {
+3: ["#e0f3db","#a8ddb5","#43a2ca"],
+4: ["#f0f9e8","#bae4bc","#7bccc4","#2b8cbe"],
+5: ["#f0f9e8","#bae4bc","#7bccc4","#43a2ca","#0868ac"],
+6: ["#f0f9e8","#ccebc5","#a8ddb5","#7bccc4","#43a2ca","#0868ac"],
+7: ["#f0f9e8","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#08589e"],
+8: ["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#08589e"],
+9: ["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081"]
+},BuGn: {
+3: ["#e5f5f9","#99d8c9","#2ca25f"],
+4: ["#edf8fb","#b2e2e2","#66c2a4","#238b45"],
+5: ["#edf8fb","#b2e2e2","#66c2a4","#2ca25f","#006d2c"],
+6: ["#edf8fb","#ccece6","#99d8c9","#66c2a4","#2ca25f","#006d2c"],
+7: ["#edf8fb","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#005824"],
+8: ["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#005824"],
+9: ["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#006d2c","#00441b"]
+},PuBuGn: {
+3: ["#ece2f0","#a6bddb","#1c9099"],
+4: ["#f6eff7","#bdc9e1","#67a9cf","#02818a"],
+5: ["#f6eff7","#bdc9e1","#67a9cf","#1c9099","#016c59"],
+6: ["#f6eff7","#d0d1e6","#a6bddb","#67a9cf","#1c9099","#016c59"],
+7: ["#f6eff7","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016450"],
+8: ["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016450"],
+9: ["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016c59","#014636"]
+},PuBu: {
+3: ["#ece7f2","#a6bddb","#2b8cbe"],
+4: ["#f1eef6","#bdc9e1","#74a9cf","#0570b0"],
+5: ["#f1eef6","#bdc9e1","#74a9cf","#2b8cbe","#045a8d"],
+6: ["#f1eef6","#d0d1e6","#a6bddb","#74a9cf","#2b8cbe","#045a8d"],
+7: ["#f1eef6","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#034e7b"],
+8: ["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#034e7b"],
+9: ["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#045a8d","#023858"]
+},BuPu: {
+3: ["#e0ecf4","#9ebcda","#8856a7"],
+4: ["#edf8fb","#b3cde3","#8c96c6","#88419d"],
+5: ["#edf8fb","#b3cde3","#8c96c6","#8856a7","#810f7c"],
+6: ["#edf8fb","#bfd3e6","#9ebcda","#8c96c6","#8856a7","#810f7c"],
+7: ["#edf8fb","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#6e016b"],
+8: ["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#6e016b"],
+9: ["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#810f7c","#4d004b"]
+},RdPu: {
+3: ["#fde0dd","#fa9fb5","#c51b8a"],
+4: ["#feebe2","#fbb4b9","#f768a1","#ae017e"],
+5: ["#feebe2","#fbb4b9","#f768a1","#c51b8a","#7a0177"],
+6: ["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#c51b8a","#7a0177"],
+7: ["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177"],
+8: ["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177"],
+9: ["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177","#49006a"]
+},PuRd: {
+3: ["#e7e1ef","#c994c7","#dd1c77"],
+4: ["#f1eef6","#d7b5d8","#df65b0","#ce1256"],
+5: ["#f1eef6","#d7b5d8","#df65b0","#dd1c77","#980043"],
+6: ["#f1eef6","#d4b9da","#c994c7","#df65b0","#dd1c77","#980043"],
+7: ["#f1eef6","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#91003f"],
+8: ["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#91003f"],
+9: ["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#980043","#67001f"]
+},OrRd: {
+3: ["#fee8c8","#fdbb84","#e34a33"],
+4: ["#fef0d9","#fdcc8a","#fc8d59","#d7301f"],
+5: ["#fef0d9","#fdcc8a","#fc8d59","#e34a33","#b30000"],
+6: ["#fef0d9","#fdd49e","#fdbb84","#fc8d59","#e34a33","#b30000"],
+7: ["#fef0d9","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#990000"],
+8: ["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#990000"],
+9: ["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#b30000","#7f0000"]
+},YlOrRd: {
+3: ["#ffeda0","#feb24c","#f03b20"],
+4: ["#ffffb2","#fecc5c","#fd8d3c","#e31a1c"],
+5: ["#ffffb2","#fecc5c","#fd8d3c","#f03b20","#bd0026"],
+6: ["#ffffb2","#fed976","#feb24c","#fd8d3c","#f03b20","#bd0026"],
+7: ["#ffffb2","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#b10026"],
+8: ["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#b10026"],
+9: ["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026","#800026"]
+},YlOrBr: {
+3: ["#fff7bc","#fec44f","#d95f0e"],
+4: ["#ffffd4","#fed98e","#fe9929","#cc4c02"],
+5: ["#ffffd4","#fed98e","#fe9929","#d95f0e","#993404"],
+6: ["#ffffd4","#fee391","#fec44f","#fe9929","#d95f0e","#993404"],
+7: ["#ffffd4","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#8c2d04"],
+8: ["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#8c2d04"],
+9: ["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#993404","#662506"]
+},Purples: {
+3: ["#efedf5","#bcbddc","#756bb1"],
+4: ["#f2f0f7","#cbc9e2","#9e9ac8","#6a51a3"],
+5: ["#f2f0f7","#cbc9e2","#9e9ac8","#756bb1","#54278f"],
+6: ["#f2f0f7","#dadaeb","#bcbddc","#9e9ac8","#756bb1","#54278f"],
+7: ["#f2f0f7","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#4a1486"],
+8: ["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#4a1486"],
+9: ["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#54278f","#3f007d"]
+},Blues: {
+3: ["#deebf7","#9ecae1","#3182bd"],
+4: ["#eff3ff","#bdd7e7","#6baed6","#2171b5"],
+5: ["#eff3ff","#bdd7e7","#6baed6","#3182bd","#08519c"],
+6: ["#eff3ff","#c6dbef","#9ecae1","#6baed6","#3182bd","#08519c"],
+7: ["#eff3ff","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#084594"],
+8: ["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#084594"],
+9: ["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#08519c","#08306b"]
+},Greens: {
+3: ["#e5f5e0","#a1d99b","#31a354"],
+4: ["#edf8e9","#bae4b3","#74c476","#238b45"],
+5: ["#edf8e9","#bae4b3","#74c476","#31a354","#006d2c"],
+6: ["#edf8e9","#c7e9c0","#a1d99b","#74c476","#31a354","#006d2c"],
+7: ["#edf8e9","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#005a32"],
+8: ["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#005a32"],
+9: ["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#006d2c","#00441b"]
+},Oranges: {
+3: ["#fee6ce","#fdae6b","#e6550d"],
+4: ["#feedde","#fdbe85","#fd8d3c","#d94701"],
+5: ["#feedde","#fdbe85","#fd8d3c","#e6550d","#a63603"],
+6: ["#feedde","#fdd0a2","#fdae6b","#fd8d3c","#e6550d","#a63603"],
+7: ["#feedde","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#8c2d04"],
+8: ["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#8c2d04"],
+9: ["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#a63603","#7f2704"]
+},Reds: {
+3: ["#fee0d2","#fc9272","#de2d26"],
+4: ["#fee5d9","#fcae91","#fb6a4a","#cb181d"],
+5: ["#fee5d9","#fcae91","#fb6a4a","#de2d26","#a50f15"],
+6: ["#fee5d9","#fcbba1","#fc9272","#fb6a4a","#de2d26","#a50f15"],
+7: ["#fee5d9","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#99000d"],
+8: ["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#99000d"],
+9: ["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15","#67000d"]
+},Greys: {
+3: ["#f0f0f0","#bdbdbd","#636363"],
+4: ["#f7f7f7","#cccccc","#969696","#525252"],
+5: ["#f7f7f7","#cccccc","#969696","#636363","#252525"],
+6: ["#f7f7f7","#d9d9d9","#bdbdbd","#969696","#636363","#252525"],
+7: ["#f7f7f7","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525"],
+8: ["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525"],
+9: ["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525","#000000"]
+},PuOr: {
+3: ["#f1a340","#f7f7f7","#998ec3"],
+4: ["#e66101","#fdb863","#b2abd2","#5e3c99"],
+5: ["#e66101","#fdb863","#f7f7f7","#b2abd2","#5e3c99"],
+6: ["#b35806","#f1a340","#fee0b6","#d8daeb","#998ec3","#542788"],
+7: ["#b35806","#f1a340","#fee0b6","#f7f7f7","#d8daeb","#998ec3","#542788"],
+8: ["#b35806","#e08214","#fdb863","#fee0b6","#d8daeb","#b2abd2","#8073ac","#542788"],
+9: ["#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788"],
+10: ["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"],
+11: ["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"]
+},BrBG: {
+3: ["#d8b365","#f5f5f5","#5ab4ac"],
+4: ["#a6611a","#dfc27d","#80cdc1","#018571"],
+5: ["#a6611a","#dfc27d","#f5f5f5","#80cdc1","#018571"],
+6: ["#8c510a","#d8b365","#f6e8c3","#c7eae5","#5ab4ac","#01665e"],
+7: ["#8c510a","#d8b365","#f6e8c3","#f5f5f5","#c7eae5","#5ab4ac","#01665e"],
+8: ["#8c510a","#bf812d","#dfc27d","#f6e8c3","#c7eae5","#80cdc1","#35978f","#01665e"],
+9: ["#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e"],
+10: ["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"],
+11: ["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"]
+},PRGn: {
+3: ["#af8dc3","#f7f7f7","#7fbf7b"],
+4: ["#7b3294","#c2a5cf","#a6dba0","#008837"],
+5: ["#7b3294","#c2a5cf","#f7f7f7","#a6dba0","#008837"],
+6: ["#762a83","#af8dc3","#e7d4e8","#d9f0d3","#7fbf7b","#1b7837"],
+7: ["#762a83","#af8dc3","#e7d4e8","#f7f7f7","#d9f0d3","#7fbf7b","#1b7837"],
+8: ["#762a83","#9970ab","#c2a5cf","#e7d4e8","#d9f0d3","#a6dba0","#5aae61","#1b7837"],
+9: ["#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837"],
+10: ["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"],
+11: ["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"]
+},PiYG: {
+3: ["#e9a3c9","#f7f7f7","#a1d76a"],
+4: ["#d01c8b","#f1b6da","#b8e186","#4dac26"],
+5: ["#d01c8b","#f1b6da","#f7f7f7","#b8e186","#4dac26"],
+6: ["#c51b7d","#e9a3c9","#fde0ef","#e6f5d0","#a1d76a","#4d9221"],
+7: ["#c51b7d","#e9a3c9","#fde0ef","#f7f7f7","#e6f5d0","#a1d76a","#4d9221"],
+8: ["#c51b7d","#de77ae","#f1b6da","#fde0ef","#e6f5d0","#b8e186","#7fbc41","#4d9221"],
+9: ["#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221"],
+10: ["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"],
+11: ["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"]
+},RdBu: {
+3: ["#ef8a62","#f7f7f7","#67a9cf"],
+4: ["#ca0020","#f4a582","#92c5de","#0571b0"],
+5: ["#ca0020","#f4a582","#f7f7f7","#92c5de","#0571b0"],
+6: ["#b2182b","#ef8a62","#fddbc7","#d1e5f0","#67a9cf","#2166ac"],
+7: ["#b2182b","#ef8a62","#fddbc7","#f7f7f7","#d1e5f0","#67a9cf","#2166ac"],
+8: ["#b2182b","#d6604d","#f4a582","#fddbc7","#d1e5f0","#92c5de","#4393c3","#2166ac"],
+9: ["#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac"],
+10: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"],
+11: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"]
+},RdGy: {
+3: ["#ef8a62","#ffffff","#999999"],
+4: ["#ca0020","#f4a582","#bababa","#404040"],
+5: ["#ca0020","#f4a582","#ffffff","#bababa","#404040"],
+6: ["#b2182b","#ef8a62","#fddbc7","#e0e0e0","#999999","#4d4d4d"],
+7: ["#b2182b","#ef8a62","#fddbc7","#ffffff","#e0e0e0","#999999","#4d4d4d"],
+8: ["#b2182b","#d6604d","#f4a582","#fddbc7","#e0e0e0","#bababa","#878787","#4d4d4d"],
+9: ["#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d"],
+10: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"],
+11: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"]
+},RdYlBu: {
+3: ["#fc8d59","#ffffbf","#91bfdb"],
+4: ["#d7191c","#fdae61","#abd9e9","#2c7bb6"],
+5: ["#d7191c","#fdae61","#ffffbf","#abd9e9","#2c7bb6"],
+6: ["#d73027","#fc8d59","#fee090","#e0f3f8","#91bfdb","#4575b4"],
+7: ["#d73027","#fc8d59","#fee090","#ffffbf","#e0f3f8","#91bfdb","#4575b4"],
+8: ["#d73027","#f46d43","#fdae61","#fee090","#e0f3f8","#abd9e9","#74add1","#4575b4"],
+9: ["#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4"],
+10: ["#a50026","#d73027","#f46d43","#fdae61","#fee090","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"],
+11: ["#a50026","#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"]
+},Spectral: {
+3: ["#fc8d59","#ffffbf","#99d594"],
+4: ["#d7191c","#fdae61","#abdda4","#2b83ba"],
+5: ["#d7191c","#fdae61","#ffffbf","#abdda4","#2b83ba"],
+6: ["#d53e4f","#fc8d59","#fee08b","#e6f598","#99d594","#3288bd"],
+7: ["#d53e4f","#fc8d59","#fee08b","#ffffbf","#e6f598","#99d594","#3288bd"],
+8: ["#d53e4f","#f46d43","#fdae61","#fee08b","#e6f598","#abdda4","#66c2a5","#3288bd"],
+9: ["#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd"],
+10: ["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"],
+11: ["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"]
+},RdYlGn: {
+3: ["#fc8d59","#ffffbf","#91cf60"],
+4: ["#d7191c","#fdae61","#a6d96a","#1a9641"],
+5: ["#d7191c","#fdae61","#ffffbf","#a6d96a","#1a9641"],
+6: ["#d73027","#fc8d59","#fee08b","#d9ef8b","#91cf60","#1a9850"],
+7: ["#d73027","#fc8d59","#fee08b","#ffffbf","#d9ef8b","#91cf60","#1a9850"],
+8: ["#d73027","#f46d43","#fdae61","#fee08b","#d9ef8b","#a6d96a","#66bd63","#1a9850"],
+9: ["#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850"],
+10: ["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"],
+11: ["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"]
+},Accent: {
+3: ["#7fc97f","#beaed4","#fdc086"],
+4: ["#7fc97f","#beaed4","#fdc086","#ffff99"],
+5: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0"],
+6: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f"],
+7: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17"],
+8: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17","#666666"]
+},Dark2: {
+3: ["#1b9e77","#d95f02","#7570b3"],
+4: ["#1b9e77","#d95f02","#7570b3","#e7298a"],
+5: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e"],
+6: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02"],
+7: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d"],
+8: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d","#666666"]
+},Paired: {
+3: ["#a6cee3","#1f78b4","#b2df8a"],
+4: ["#a6cee3","#1f78b4","#b2df8a","#33a02c"],
+5: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99"],
+6: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c"],
+7: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f"],
+8: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00"],
+9: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6"],
+10: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a"],
+11: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99"],
+12: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99","#b15928"]
+},Pastel1: {
+3: ["#fbb4ae","#b3cde3","#ccebc5"],
+4: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4"],
+5: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6"],
+6: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc"],
+7: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd"],
+8: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec"],
+9: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"]
+},Pastel2: {
+3: ["#b3e2cd","#fdcdac","#cbd5e8"],
+4: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4"],
+5: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9"],
+6: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae"],
+7: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc"],
+8: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc","#cccccc"]
+},Set1: {
+3: ["#e41a1c","#377eb8","#4daf4a"],
+4: ["#e41a1c","#377eb8","#4daf4a","#984ea3"],
+5: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00"],
+6: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33"],
+7: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628"],
+8: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf"],
+9: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf","#999999"]
+},Set2: {
+3: ["#66c2a5","#fc8d62","#8da0cb"],
+4: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3"],
+5: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854"],
+6: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f"],
+7: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494"],
+8: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494","#b3b3b3"]
+},Set3: {
+3: ["#8dd3c7","#ffffb3","#bebada"],
+4: ["#8dd3c7","#ffffb3","#bebada","#fb8072"],
+5: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3"],
+6: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462"],
+7: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69"],
+8: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5"],
+9: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9"],
+10: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd"],
+11: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5"],
+12: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5","#ffed6f"]
+}};
diff --git a/gn2/wqflask/static/new/javascript/compare_traits_scatterplot.js b/gn2/wqflask/static/new/javascript/compare_traits_scatterplot.js new file mode 100644 index 00000000..5c0ef16c --- /dev/null +++ b/gn2/wqflask/static/new/javascript/compare_traits_scatterplot.js @@ -0,0 +1,121 @@ +// Generated by CoffeeScript 1.8.0 +var root; + +root = typeof exports !== "undefined" && exports !== null ? exports : this; + +root.create_scatterplot = function(json_ids, json_data) { + var data, h, halfh, halfw, indID, margin, mychart, totalh, totalw, w; + console.log("TESTING2"); + h = 400; + w = 500; + margin = { + left: 60, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + halfh = h + margin.top + margin.bottom; + totalh = halfh * 2; + halfw = w + margin.left + margin.right; + totalw = halfw * 2; + mychart = scatterplot().xvar(0).yvar(1).xlab("X").ylab("Y").height(h).width(w).margin(margin); + data = json_data; + indID = json_ids; + d3.select("div#comparison_scatterplot").datum({ + data: data, + indID: indID + }).call(mychart); + return mychart.pointsSelect().on("mouseover", function(d) { + return d3.select(this).attr("r", mychart.pointsize() * 3); + }).on("mouseout", function(d) { + return d3.select(this).attr("r", mychart.pointsize()); + }); +}; + +root.create_scatterplots = function(trait_names, json_ids, json_data) { + var brush, brushend, brushmove, brushstart, chart, data, h, halfh, halfw, i, indID, margin, mychart, num_traits, svg, totalh, totalw, w, xscale, xshift, xvar, yscale, yshift, yvar, _i, _j, _k, _ref, _ref1, _ref2, _results; + console.log("json_data:", json_data); + console.log("trait_names:", trait_names); + num_traits = json_data.length; + console.log("num_traits:", num_traits); + h = 300; + w = 400; + margin = { + left: 60, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + halfh = h + margin.top + margin.bottom; + totalh = halfh * (num_traits - 1); + halfw = w + margin.left + margin.right; + totalw = halfw; + xvar = []; + yvar = []; + xshift = []; + yshift = []; + for (i = _i = 0, _ref = num_traits - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) { + xvar.push(i); + yvar.push(0); + xshift.push(0); + yshift.push(halfh * i); + } + console.log("xvar:", xvar); + console.log("yvar:", yvar); + svg = d3.select("div#comparison_scatterplot").append("svg").attr("height", totalh).attr("width", totalw); + mychart = []; + chart = []; + for (i = _j = 1, _ref1 = num_traits - 1; 1 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 1 <= _ref1 ? ++_j : --_j) { + mychart[i - 1] = scatterplot().xvar(xvar[i]).yvar(yvar[i]).nxticks(6).height(h).width(w).margin(margin).pointsize(4).xlab("" + trait_names[i - 1]).ylab("" + trait_names[0]).title("" + trait_names[0] + " vs. " + trait_names[i - 1]); + data = json_data; + indID = json_ids; + chart[i - 1] = svg.append("g").attr("id", "chart" + (i - 1)).attr("transform", "translate(" + xshift[i] + "," + yshift[i - 1] + ")"); + chart[i - 1].datum({ + data: data, + indID: indID + }).call(mychart[i - 1]); + } + brush = []; + brushstart = function(i) { + return function() { + var j, _k, _ref2; + for (j = _k = 0, _ref2 = num_traits - 2; 0 <= _ref2 ? _k <= _ref2 : _k >= _ref2; j = 0 <= _ref2 ? ++_k : --_k) { + if (j !== i) { + chart[j].call(brush[j].clear()); + } + } + return svg.selectAll("circle").attr("opacity", 0.6).classed("selected", false); + }; + }; + brushmove = function(i) { + return function() { + var e; + svg.selectAll("circle").classed("selected", false); + e = brush[i].extent(); + return chart[i].selectAll("circle").classed("selected", function(d, j) { + var circ, cx, cy, selected; + circ = d3.select(this); + cx = circ.attr("cx"); + cy = circ.attr("cy"); + selected = e[0][0] <= cx && cx <= e[1][0] && e[0][1] <= cy && cy <= e[1][1]; + if (selected) { + svg.selectAll("circle.pt" + j).classed("selected", true); + } + return selected; + }); + }; + }; + brushend = function() { + return svg.selectAll("circle").attr("opacity", 1); + }; + xscale = d3.scale.linear().domain([margin.left, margin.left + w]).range([margin.left, margin.left + w]); + yscale = d3.scale.linear().domain([margin.top, margin.top + h]).range([margin.top, margin.top + h]); + _results = []; + for (i = _k = 0, _ref2 = num_traits - 2; 0 <= _ref2 ? _k <= _ref2 : _k >= _ref2; i = 0 <= _ref2 ? ++_k : --_k) { + brush[i] = d3.svg.brush().x(xscale).y(yscale).on("brushstart", brushstart(i)).on("brush", brushmove(i)).on("brushend", brushend); + _results.push(chart[i].call(brush[i])); + } + return _results; +}; diff --git a/gn2/wqflask/static/new/javascript/comparison_bar_chart.js b/gn2/wqflask/static/new/javascript/comparison_bar_chart.js new file mode 100644 index 00000000..5e73807c --- /dev/null +++ b/gn2/wqflask/static/new/javascript/comparison_bar_chart.js @@ -0,0 +1,25 @@ +generate_traces = function() { + traces = []; + for (i = 0, _len = js_data.traits.length; i < _len; i++) { + this_trace = { + x: js_data.samples, + y: js_data.sample_data[i], + name: js_data.traits[i], + type: 'bar', + bargap: 20 + } + + traces.push(this_trace) + } + + return traces +} + +create_bar_chart = function() { + var data = generate_traces() + var layout = {barmode: 'group', bargap: 5}; + + Plotly.newPlot('comp_bar_chart', data, layout); +} + +create_bar_chart();
\ No newline at end of file diff --git a/gn2/wqflask/static/new/javascript/corr_matrix.js b/gn2/wqflask/static/new/javascript/corr_matrix.js new file mode 100644 index 00000000..ad0fc8b8 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/corr_matrix.js @@ -0,0 +1,159 @@ +// Generated by CoffeeScript 1.8.0 +var iplotCorr, root; + +root = typeof exports !== "undefined" && exports !== null ? exports : this; + +iplotCorr = function(data, chartOpts) { + var cells, chartdivid, colorScale, corXscale, corYscale, corZscale, corcolors, corr, corr_tip, corrplot, cortitle, drawScatter, height, i, j, margin, nGroup, ncorrX, ncorrY, nind, nvar, pixel_height, pixel_width, rectcolor, scat_tip, scatcolors, scatterplot, scattitle, svg, totalh, totalw, width, zlim, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9; + height = (_ref = chartOpts != null ? chartOpts.height : void 0) != null ? _ref : 450; + width = (_ref1 = chartOpts != null ? chartOpts.width : void 0) != null ? _ref1 : height; + margin = (_ref2 = chartOpts != null ? chartOpts.margin : void 0) != null ? _ref2 : { + left: 70, + top: 40, + right: 5, + bottom: 70, + inner: 5 + }; + corcolors = (_ref3 = chartOpts != null ? chartOpts.corcolors : void 0) != null ? _ref3 : ["darkslateblue", "white", "crimson"]; + zlim = (_ref4 = chartOpts != null ? chartOpts.zlim : void 0) != null ? _ref4 : [-1, 0, 1]; + rectcolor = (_ref5 = chartOpts != null ? chartOpts.rectcolor : void 0) != null ? _ref5 : d3.rgb(230, 230, 230); + cortitle = (_ref6 = chartOpts != null ? chartOpts.cortitle : void 0) != null ? _ref6 : ""; + scattitle = (_ref7 = chartOpts != null ? chartOpts.scattitle : void 0) != null ? _ref7 : ""; + scatcolors = (_ref8 = chartOpts != null ? chartOpts.scatcolors : void 0) != null ? _ref8 : null; + chartdivid = (_ref9 = chartOpts != null ? chartOpts.chartdivid : void 0) != null ? _ref9 : 'chart'; + totalh = height + margin.top + margin.bottom; + totalw = (width + margin.left + margin.right) * 2; + svg = d3.select("div#" + chartdivid).append("svg").attr("height", totalh).attr("width", totalw); + corrplot = svg.append("g").attr("id", "corplot").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + scatterplot = svg.append("g").attr("id", "scatterplot").attr("transform", "translate(" + (margin.left * 2 + margin.right + width) + "," + margin.top + ")"); + nind = data.indID.length; + nvar = data["var"].length; + ncorrX = data.cols.length; + ncorrY = data.rows.length; + corXscale = d3.scale.ordinal().domain(d3.range(ncorrX)).rangeBands([0, width]); + corYscale = d3.scale.ordinal().domain(d3.range(ncorrY)).rangeBands([0, height]); + corZscale = d3.scale.linear().domain(zlim).range(corcolors); + pixel_width = corXscale(1) - corXscale(0); + pixel_height = corYscale(0) - corYscale(1); + corr = []; + for (i in data.corr) { + for (j in data.corr[i]) { + corr.push({ + row: i, + col: j, + value: data.corr[i][j] + }); + } + corrplot.append("text").attr("class", "corrlabel_y").attr("y", corYscale(i) - pixel_height / 2).attr("x", -margin.left * 0.1).text(data["var"][data.rows[i]]).attr("dominant-baseline", "middle").attr("text-anchor", "end"); + //corrplot.append("text").attr("class", "corrlabel_x").attr("x", corXscale(i) + pixel_width / 2).attr("y", height + margin.bottom * 0.2).text(data["var"][data.cols[i]]).attr("dominant-baseline", "middle").attr("text-anchor", "middle"); + + } + scatterplot.append("rect").attr("height", height).attr("width", width).attr("fill", rectcolor).attr("stroke", "black").attr("stroke-width", 1).attr("pointer-events", "none"); + corr_tip = d3.tip().attr('class', 'd3-tip').html(function(d) { + return d3.format(".2f")(d.value); + }).direction('e').offset([0, 10]); + corrplot.call(corr_tip); + + cells = corrplot.selectAll("empty").data(corr).enter().append("rect").attr("class", "cell").attr("x", function(d) { + return corXscale(d.col); + }).attr("y", function(d) { + return corYscale(d.row); + }).attr("width", corXscale.rangeBand()).attr("height", corYscale.rangeBand()).attr("fill", function(d) { + return corZscale(d.value); + }).attr("stroke", "none").attr("stroke-width", 2).on("mouseover", function(d) { + d3.select(this).attr("stroke", "black"); + corr_tip.show(d); + //corrplot.append("text").attr("class", "corrlabel").attr("x", corXscale(d.col) + pixel_width / 2).attr("y", height + margin.bottom * 0.2).text(data["var"][data.cols[d.col]]).attr("dominant-baseline", "middle").attr("text-anchor", "middle"); + //return corrplot.append("text").attr("class", "corrlabel_x").attr("y", corYscale(d.row) + pixel_height / 2).attr("x", -margin.left * 0.1).text(data["var"][data.rows[d.row]]).attr("dominant-baseline", "middle").attr("text-anchor", "end"); + return corrplot.append("text").attr("class", "corrlabel_x").attr("x", corXscale(d.col) + pixel_width / 2).attr("y", height + margin.bottom * 0.2).text(data["var"][data.cols[d.col]]).attr("dominant-baseline", "middle").attr("text-anchor", "middle"); + }).on("mouseout", function(d) { + corr_tip.hide(d); + d3.selectAll("text.corrlabel_x").remove(); + return d3.select(this).attr("stroke", "none"); + }).on("click", function(d) { + return drawScatter(d.col, d.row); + }); + nGroup = d3.max(data.group); + if (!(scatcolors != null) || scatcolors.length < nGroup) { + if (nGroup === 1) { + scatcolors = [d3.rgb(150, 150, 150)]; + } else if (nGroup <= 3) { + scatcolors = ["crimson", "green", "darkslateblue"]; + } else { + if (nGroup <= 10) { + colorScale = d3.scale.category10(); + } else { + colorScale = d3.scale.category20(); + } + scatcolors = (function() { + var _results; + _results = []; + for (i in d3.range(nGroup)) { + _results.push(colorScale(i)); + } + return _results; + })(); + } + } + scat_tip = d3.tip().attr('class', 'd3-tip').html(function(d, i) { + return data.indID[i]; + }).direction('e').offset([0, 10]); + scatterplot.call(scat_tip); + drawScatter = function(i, j) { + var xScale, xticks, yScale, yticks; + d3.selectAll("circle.points").remove(); + d3.selectAll("text.axes").remove(); + d3.selectAll("line.axes").remove(); + console.log("data.dat:", data.dat); + console.log("data.cols:", data.cols); + xScale = d3.scale.linear().domain(d3.extent(data.dat[data.cols[i]])).range([margin.inner, width - margin.inner]); + yScale = d3.scale.linear().domain(d3.extent(data.dat[data.rows[j]])).range([height - margin.inner, margin.inner]); + scatterplot.append("text").attr("id", "xaxis").attr("class", "axes").attr("x", width / 2).attr("y", height + margin.bottom * 0.7).text(data["var"][data.cols[i]]).attr("dominant-baseline", "middle").attr("text-anchor", "middle").attr("fill", "slateblue"); + scatterplot.append("text").attr("id", "yaxis").attr("class", "axes").attr("x", -margin.left * 0.8).attr("y", height / 2).text(data["var"][data.rows[j]]).attr("dominant-baseline", "middle").attr("text-anchor", "middle").attr("transform", "rotate(270," + (-margin.left * 0.8) + "," + (height / 2) + ")").attr("fill", "slateblue"); + xticks = xScale.ticks(5); + yticks = yScale.ticks(5); + scatterplot.selectAll("empty").data(xticks).enter().append("text").attr("class", "axes").text(function(d) { + return formatAxis(xticks)(d); + }).attr("x", function(d) { + return xScale(d); + }).attr("y", height + margin.bottom * 0.3).attr("dominant-baseline", "middle").attr("text-anchor", "middle"); + scatterplot.selectAll("empty").data(yticks).enter().append("text").attr("class", "axes").text(function(d) { + return formatAxis(yticks)(d); + }).attr("x", -margin.left * 0.1).attr("y", function(d) { + return yScale(d); + }).attr("dominant-baseline", "middle").attr("text-anchor", "end"); + scatterplot.selectAll("empty").data(xticks).enter().append("line").attr("class", "axes").attr("x1", function(d) { + return xScale(d); + }).attr("x2", function(d) { + return xScale(d); + }).attr("y1", 0).attr("y2", height).attr("stroke", "white").attr("stroke-width", 1); + scatterplot.selectAll("empty").data(yticks).enter().append("line").attr("class", "axes").attr("y1", function(d) { + return yScale(d); + }).attr("y2", function(d) { + return yScale(d); + }).attr("x1", 0).attr("x2", width).attr("stroke", "white").attr("stroke-width", 1); + return scatterplot.selectAll("empty").data(d3.range(nind)).enter().append("circle").attr("class", "points").attr("cx", function(d) { + return xScale(data.dat[data.cols[i]][d]); + }).attr("cy", function(d) { + return yScale(data.dat[data.rows[j]][d]); + }).attr("r", function(d) { + var x, y; + x = data.dat[data.cols[i]][d]; + y = data.dat[data.rows[j]][d]; + if ((x != "") && (y != "")) { + return 3; + } else { + return null; + } + }).attr("stroke", "black").attr("stroke-width", 1).attr("fill", function(d) { + return scatcolors[data.group[d] - 1]; + }).on("mouseover", scat_tip.show).on("mouseout", scat_tip.hide); + }; + corrplot.append("rect").attr("height", height).attr("width", width).attr("fill", "none").attr("stroke", "black").attr("stroke-width", 1).attr("pointer-events", "none"); + scatterplot.append("rect").attr("height", height).attr("width", width).attr("fill", "none").attr("stroke", "black").attr("stroke-width", 1).attr("pointer-events", "none"); + corrplot.append("text").text(cortitle).attr("id", "corrtitle").attr("x", width / 2).attr("y", -margin.top / 2).attr("dominant-baseline", "middle").attr("text-anchor", "middle"); + scatterplot.append("text").text(scattitle).attr("id", "scattitle").attr("x", width / 2).attr("y", -margin.top / 2).attr("dominant-baseline", "middle").attr("text-anchor", "middle"); + return d3.select("div#caption").style("opacity", 1); +}; + +root.corr_matrix = iplotCorr; diff --git a/gn2/wqflask/static/new/javascript/corr_scatter_plot.js b/gn2/wqflask/static/new/javascript/corr_scatter_plot.js new file mode 100644 index 00000000..553423cf --- /dev/null +++ b/gn2/wqflask/static/new/javascript/corr_scatter_plot.js @@ -0,0 +1,73 @@ +// Generated by CoffeeScript 1.8.0 +var Scatter_Plot, root; + +root = typeof exports !== "undefined" && exports !== null ? exports : this; + +Scatter_Plot = (function() { + function Scatter_Plot() { + var chart, data, g, height, i, main, margin, maxx, maxy, minx, miny, sample1, sample2, samplename, samples_1, samples_2, text, width, x, xAxis, y, yAxis; + data = new Array(); + samples_1 = js_data.samples_1; + samples_2 = js_data.samples_2; + i = 0; + for (samplename in samples_1) { + sample1 = samples_1[samplename]; + sample2 = samples_2[samplename]; + data[i++] = [sample1.value, sample2.value]; + } + margin = { + top: 100, + right: 15, + bottom: 60, + left: 60 + }; + width = js_data.width - margin.left - margin.right; + height = js_data.height - margin.top - margin.bottom; + minx = d3.min(data, function(d) { + return d[0]; + }) * 0.95; + maxx = d3.max(data, function(d) { + return d[0]; + }) * 1.05; + miny = d3.min(data, function(d) { + return d[1]; + }) * 0.95; + maxy = d3.max(data, function(d) { + return d[1]; + }) * 1.05; + x = d3.scale.linear().domain([minx, maxx]).range([0, width]); + y = d3.scale.linear().domain([miny, maxy]).range([height, 0]); + chart = d3.select("#scatter_plot").append("svg:svg").attr("width", width + margin.right + margin.left).attr("height", height + margin.top + margin.bottom).attr("class", "chart"); + main = chart.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("width", width).attr("height", height).attr("class", "main"); + xAxis = d3.svg.axis().scale(x).orient("bottom"); + main.append("g").attr("transform", "translate(0," + height + ")").attr("class", "main axis date").call(xAxis); + yAxis = d3.svg.axis().scale(y).orient("left"); + main.append("g").attr("transform", "translate(0,0)").attr("class", "main axis date").call(yAxis); + g = main.append("svg:g"); + g.selectAll("scatter-dots").data(data).enter().append("svg:circle").attr("cx", function(d) { + return x(d[0]); + }).attr("cy", function(d) { + return y(d[1]); + }).attr("fill", js_data.circle_color).attr("r", js_data.circle_radius); + main.append("line").attr("x1", x(minx)).attr("y1", y(js_data.slope * minx + js_data.intercept)).attr("x2", x(maxx * 0.995)).attr("y2", y(js_data.slope * maxx * 0.995 + js_data.intercept)).style("stroke", js_data.line_color).style("stroke-width", js_data.line_width); + chart.append("text").attr("x", width / 2).attr("y", margin.top / 2 - 25).text("Sample Correlation Scatterplot"); + text = ""; + text += "N=" + js_data.num_overlap; + chart.append("text").attr("x", margin.left).attr("y", margin.top / 2 - 5).text(text); + text = ""; + text += "r=" + js_data.r_value + "\t"; + text += "p(r)=" + js_data.p_value; + chart.append("text").attr("x", margin.left).attr("y", margin.top / 2 + 15).text(text); + text = ""; + text += "slope=" + js_data.slope + "\t"; + text += "intercept=" + js_data.intercept; + chart.append("text").attr("x", margin.left).attr("y", margin.top / 2 + 35).text(text); + chart.append("text").attr("x", width / 2).attr("y", height + margin.top + 35).text(js_data.trait_1); + chart.append("text").attr("x", 20).attr("y", height / 2 + margin.top + 30).attr("transform", "rotate(-90 20," + (height / 2 + margin.top + 30) + ")").text(js_data.trait_2); + } + + return Scatter_Plot; + +})(); + +root.Scatter_Plot = Scatter_Plot; diff --git a/gn2/wqflask/static/new/javascript/create_corr_matrix.js b/gn2/wqflask/static/new/javascript/create_corr_matrix.js new file mode 100644 index 00000000..c0c39fbc --- /dev/null +++ b/gn2/wqflask/static/new/javascript/create_corr_matrix.js @@ -0,0 +1,94 @@ +var neg_color_scale = chroma.scale(['#91bfdb', '#ffffff']).domain([-1, -0.4]); +var pos_color_scale = chroma.scale(['#ffffff', '#fc8d59']).domain([0.4, 1]) +$('.corr_cell').each( function () { + corr_value = parseFloat($(this).find('span.corr_value').text()) + if (corr_value >= 0.5){ + $(this).css('background-color', pos_color_scale(parseFloat(corr_value))._rgb) + } + else if (corr_value <= -0.5) { + $(this).css('background-color', neg_color_scale(parseFloat(corr_value))._rgb) + } + else { + $(this).css('background-color', 'white') + } +}); + +$('#short_labels').click( function (){ + if ($('.short_check').css("display") == "none"){ + $('.short_check').css("display", "inline-block") + } else { + $('.short_check').css("display", "none") + } + $('.shortName').each( function() { + if ($(this).css("display") == "none"){ + $(this).css("display", "block"); + } + else { + $(this).css("display", "none"); + } + }); +}); + +$('#long_labels').click( function (){ + if ($('.long_check').css("display") == "none"){ + $('.long_check').css("display", "inline-block") + } else { + $('.long_check').css("display", "none") + } + $('.verboseName').each( function() { + if ($(this).css("display") == "none"){ + $(this).css("display", "block"); + } + else { + $(this).css("display", "none"); + } + }); +}); + +select_all = function() { + $(".trait_checkbox").each(function() { + $(this).prop('checked', true); + }); +}; + +deselect_all = function() { + $(".trait_checkbox").each(function() { + $(this).prop('checked', false); + }); +}; + +change_buttons = function() { + num_checked = $('.trait_checkbox:checked').length; + if (num_checked === 0) { + $("#add").prop("disabled", true); + } else { + $("#add").prop("disabled", false); + } +}; + +add = function() { + var traits; + traits = $("input[name=pca_trait]:checked").map(function() { + return $(this).val(); + }).get(); + + var traits_hash = md5(traits.toString()); + + $.ajax({ + type: "POST", + url: "/collections/store_trait_list", + data: { + hash: traits_hash, + traits: traits.toString() + } + }); + + return $.colorbox({ + href: "/collections/add?hash=" + traits_hash + }); +} + +$("#select_all").click(select_all); +$("#deselect_all").click(deselect_all); +$("#add").click(add); +$(".btn, .trait_checkbox").click(change_buttons); diff --git a/gn2/wqflask/static/new/javascript/create_datatable.js b/gn2/wqflask/static/new/javascript/create_datatable.js new file mode 100644 index 00000000..541dfdf5 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/create_datatable.js @@ -0,0 +1,117 @@ +create_table = function(tableId="trait_table", tableData = [], columnDefs = [], customSettings = {}) { + + loadDataTable(tableId=tableId, tableData=tableData, customSettings, firstRun=true) + + var widthChange = 0; // For storing the change in width so overall table width can be adjusted by that amount + function loadDataTable(tableId, tableData, customSettings, firstRun=false){ + if (!firstRun){ + columnDefs = setUserColumnsDefWidths(tableId, columnDefs); + } + + tableSettings = { + "drawCallback": function( settings ) { + $('#' + tableId + ' 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") && event.target.tagName.toLowerCase() !== 'a'){ + $(this).removeClass("selected") + } else if (event.target.tagName.toLowerCase() !== 'a') { + $(this).addClass("selected") + } + change_buttons() + }); + }, + "columns": columnDefs, + "sDom": "iti", + "destroy": true, + "autoWidth": false, + "bSortClasses": false, + "scrollY": "100vh", + "scrollX": "100%", + "scrollCollapse": true, + "scroller": true, + "iDisplayLength": -1, + "initComplete": function (settings) { + // Add JQueryUI resizable functionality to each th in the ScrollHead table + $('#' + tableId + '_wrapper .dataTables_scrollHead thead th').resizable({ + handles: "e", + alsoResize: '#' + tableId + '_wrapper .dataTables_scrollHead table', //Not essential but makes the resizing smoother + resize: function( event, ui ) { + widthChange = ui.size.width - ui.originalSize.width; + }, + stop: function () { + saveColumnSettings(tableId, theTable); + loadDataTable(tableId, tableData, customSettings, firstRun=false); + } + }); + } + } + + if (tableData.length > 0){ + tableSettings["data"] = tableData + } + + // Replace default settings with custom settings or add custom settings if not already set in default settings + $.each(customSettings, function(key, value) { + tableSettings[key] = value + }); + + if (!firstRun){ + $('#' + tableId + '_container').css("width", String($('#' + tableId).width() + widthChange + 17) + "px"); // Change the container width by the change in width of the adjusted column, so the overall table size adjusts properly + + let checkedRows = getCheckedRows(tableId); + theTable = $('#' + tableId).DataTable(tableSettings); + if (checkedRows.length > 0){ + recheckRows(theTable, checkedRows); + } + } else { + theTable = $('#' + tableId).DataTable(tableSettings); + theTable.draw(); + $('#' + tableId + '_container').css("width", String($('#' + tableId).width() + 17) + "px"); + theTable.columns.adjust().draw(); + } + } + + theTable.on( 'order.dt search.dt draw.dt', function () { + theTable.column(1, {search:'applied', order:'applied'}).nodes().each( function (cell, i) { + cell.innerHTML = i+1; + } ); + } ).draw(); + + window.addEventListener('resize', function(){ + theTable.columns.adjust(); + }); + + $('#' + tableId + '_searchbox').on( 'keyup', function () { + theTable.search($(this).val()).draw(); + } ); + + $('.toggle-vis').on('click', function (e) { + e.preventDefault(); + + function toggleColumn(column) { + // Toggle column visibility + column.visible( ! column.visible() ); + if (column.visible()){ + $(this).removeClass("active"); + } else { + $(this).addClass("active"); + } + } + + // Get the column API object + var targetCols = $(this).attr('data-column').split(",") + for (let i = 0; i < targetCols.length; i++){ + var column = theTable.column( targetCols[i] ); + toggleColumn(column); + } + } ); + + $('#redraw').on('click', function (e) { + e.preventDefault(); + trait_table.columns().visible( true ); + $('.toggle-vis.active').removeClass('active'); + }); +} diff --git a/gn2/wqflask/static/new/javascript/create_heatmap.js b/gn2/wqflask/static/new/javascript/create_heatmap.js new file mode 100644 index 00000000..f3ae2a46 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/create_heatmap.js @@ -0,0 +1,14 @@ +// Generated by CoffeeScript 1.8.0 +var create_heatmap; + +create_heatmap = function() { + var data, h, mychart, w; + h = 500; + w = 1200; + mychart = lodheatmap().height(h).width(w); + data = js_data.json_data; + console.log("data:", data); + return d3.select("div#chart").datum(data).call(mychart); +}; + +create_heatmap(); diff --git a/gn2/wqflask/static/new/javascript/create_lodchart.js b/gn2/wqflask/static/new/javascript/create_lodchart.js new file mode 100644 index 00000000..778eed3a --- /dev/null +++ b/gn2/wqflask/static/new/javascript/create_lodchart.js @@ -0,0 +1,50 @@ +//var create_lod_chart; + +create_lod_chart = function() { + var additive, chrrect, data, h, halfh, margin, mychart, totalh, totalw, w; + h = 500; + w = 1200; + margin = { + left: 60, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + halfh = h + margin.top + margin.bottom; + totalh = halfh * 2; + totalw = w + margin.left + margin.right; + if ('additive' in js_data) { + additive = js_data.additive; + } else { + additive = false; + } + console.log("js_data:", js_data); + mychart = lodchart().lodvarname("lod.hk").height(h).width(w).margin(margin).ylab(js_data.result_score_type + " score").manhattanPlot(js_data.manhattan_plot); + data = js_data.json_data; + d3.select("div#topchart").datum(data).call(mychart); + chrrect = mychart.chrSelect(); + chrrect.on("mouseover", function() { + return d3.select(this).attr("fill", "#E9CFEC"); + }).on("mouseout", function(d, i) { + return d3.select(this).attr("fill", function() { + if (i % 2) { + return "#F1F1F9"; + } + return "#FBFBFF"; + }); + }); + return mychart.markerSelect().on("click", function(d) { + var r; + r = d3.select(this).attr("r"); + return d3.select(this).transition().duration(500).attr("r", r * 3).transition().duration(500).attr("r", r); + }); +}; + +create_lod_chart() + +/* +$(function() { + return root.create_lod_chart = create_lod_chart; +}); +*/
\ No newline at end of file diff --git a/gn2/wqflask/static/new/javascript/create_manhattan_plot.js b/gn2/wqflask/static/new/javascript/create_manhattan_plot.js new file mode 100644 index 00000000..30af484d --- /dev/null +++ b/gn2/wqflask/static/new/javascript/create_manhattan_plot.js @@ -0,0 +1,68 @@ +// Generated by CoffeeScript 1.8.0 +var create_manhattan_plot; + +create_manhattan_plot = function() { + var chrrect, data, h, halfh, margin, mychart, totalh, totalw, w; + h = 500; + w = 1200; + margin = { + left: 60, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + halfh = h + margin.top + margin.bottom; + totalh = halfh * 2; + totalw = w + margin.left + margin.right; + console.log("js_data:", js_data); + mychart = lodchart().lodvarname("lod.hk").height(h).width(w).margin(margin).ylab("LOD score").manhattanPlot(js_data.manhattan_plot); + data = js_data.json_data; + d3.select("div#topchart").datum(data).call(mychart); + chrrect = mychart.chrSelect(); + chrrect.on("mouseover", function() { + return d3.select(this).attr("fill", "#E9CFEC"); + }).on("mouseout", function(d, i) { + return d3.select(this).attr("fill", function() { + if (i % 2) { + return "#F1F1F9"; + } + return "#FBFBFF"; + }); + }); + return mychart.markerSelect().on("click", function(d) { + var r; + r = d3.select(this).attr("r"); + return d3.select(this).transition().duration(500).attr("r", r * 3).transition().duration(500).attr("r", r); + }); +}; + +create_manhattan_plot(); + +$("#export").click((function(_this) { + return function() { + var filename, form, svg, svg_xml; + svg = $("#topchart").find("svg")[0]; + svg_xml = (new XMLSerializer).serializeToString(svg); + console.log("svg_xml:", svg_xml); + filename = "manhattan_plot_" + js_data.this_trait; + form = $("#exportform"); + form.find("#data").val(svg_xml); + form.find("#filename").val(filename); + return form.submit(); + }; +})(this)); + +$("#export_pdf").click((function(_this) { + return function() { + var filename, form, svg, svg_xml; + svg = $("#topchart").find("svg")[0]; + svg_xml = (new XMLSerializer).serializeToString(svg); + console.log("svg_xml:", svg_xml); + filename = "manhattan_plot_" + js_data.this_trait; + form = $("#exportpdfform"); + form.find("#data").val(svg_xml); + form.find("#filename").val(filename); + return form.submit(); + }; +})(this)); diff --git a/gn2/wqflask/static/new/javascript/ctl_graph.js b/gn2/wqflask/static/new/javascript/ctl_graph.js new file mode 100644 index 00000000..bd950592 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/ctl_graph.js @@ -0,0 +1,193 @@ +window.onload=function() { + // id of Cytoscape Web container div + //var div_id = "cytoscapeweb"; + + var cy = cytoscape({ + container: $('#cytoscapeweb'), // container to render in + + elements: elements_list, + + style: [ // the stylesheet for the graph + { + selector: 'node', + style: { + 'background-color': '#666', + 'label': 'data(symbol)', + 'font-size': 10 + } + }, + + { + selector: 'edge', + style: { + 'width': 'data(width)', + 'line-color': 'data(color)', + 'target-arrow-color': '#ccc', + 'target-arrow-shape': 'none', + 'font-size': 8, + 'curve-style': 'bezier' + } + } + ], + + zoom: 12, + layout: { name: 'circle', + fit: true, // whether to fit the viewport to the graph + padding: 30 // the padding on fit + //idealEdgeLength: function( edge ){ return edge.data['correlation']*10; }, + }, + + + zoomingEnabled: true, + userZoomingEnabled: true, + panningEnabled: true, + userPanningEnabled: true, + boxSelectionEnabled: false, + selectionType: 'single', + + // rendering options: + styleEnabled: true + }); + + var eles = cy.$() // var containing all elements, so elements can be restored after being removed + + var defaults = { + zoomFactor: 0.05, // zoom factor per zoom tick + zoomDelay: 45, // how many ms between zoom ticks + minZoom: 0.1, // min zoom level + maxZoom: 10, // max zoom level + fitPadding: 30, // padding when fitting + panSpeed: 10, // how many ms in between pan ticks + panDistance: 10, // max pan distance per tick + panDragAreaSize: 75, // the length of the pan drag box in which the vector for panning is calculated (bigger = finer control of pan speed and direction) + panMinPercentSpeed: 0.25, // the slowest speed we can pan by (as a percent of panSpeed) + panInactiveArea: 8, // radius of inactive area in pan drag box + panIndicatorMinOpacity: 0.5, // min opacity of pan indicator (the draggable nib); scales from this to 1.0 + zoomOnly: false, // a minimal version of the ui only with zooming (useful on systems with bad mousewheel resolution) + fitSelector: undefined, // selector of elements to fit + animateOnFit: function(){ // whether to animate on fit + return false; + }, + fitAnimationDuration: 1000, // duration of animation on fit + + // icon class names + sliderHandleIcon: 'fa fa-minus', + zoomInIcon: 'fa fa-plus', + zoomOutIcon: 'fa fa-minus', + resetIcon: 'fa fa-expand' + }; + + cy.panzoom( defaults ); + + function create_qtips(cy){ + cy.nodes().qtip({ + content: function(){ + gn_link = '<b>'+'<a href="' + gn2_url + '/show_trait?trait_id=' + this.data().sid + '&dataset=' + this.data().dataset + '" >'+this.data().id +'</a>'+'</b><br>' + ncbi_link = '<a href="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?db=gene&cmd=Retrieve&dopt=Graphics&list_uids=' + this.data().geneid + '" >NCBI<a>'+'<br>' + omim_link = '<a href="http://www.ncbi.nlm.nih.gov/omim/' + this.data().omim + '" >OMIM<a>'+'<br>' + qtip_content = gn_link + ncbi_link + omim_link + return qtip_content + }, + position: { + my: 'top center', + at: 'bottom center' + }, + style: { + classes: 'qtip-bootstrap', + tip: { + width: 16, + height: 8 + } + } + }); + + cy.edges().qtip({ + content: function(){ + edge_ID = '<b>Edge: ' + this.data().id + '</b><br>' + lod_score = 'LOD: ' + this.data().lod + '<br>' + return edge_ID + lod_score + }, + position: { + my: 'top center', + at: 'bottom center' + }, + style: { + classes: 'qtip-bootstrap', + tip: { + width: 16, + height: 8 + } + } + }); + } + + create_qtips(cy) + + $('#slide').change(function() { + eles.restore() + + console.log(eles) + + // nodes_to_restore = eles.filter("node[max_corr >= " + $(this).val() + "], edge[correlation >= " + $(this).val() + "][correlation <= -" + $(this).val() + "]") + // nodes_to_restore.restore() + + // edges_to_restore = eles.filter("edge[correlation >= " + $(this).val() + "][correlation <= -" + $(this).val() + "]") + // edges_to_restore.restore() + + //cy.$("node[max_corr >= " + $(this).val() + "]").restore(); + //cy.$("edge[correlation >= " + $(this).val() + "][correlation <= -" + $(this).val() + "]").restore(); + + cy.$("node[max_corr < " + $(this).val() + "]").remove(); + cy.$("edge[correlation < " + $(this).val() + "][correlation > -" + $(this).val() + "]").remove(); + + cy.layout({ name: $('select[name=layout_select]').val(), + fit: true, // whether to fit the viewport to the graph + padding: 25 // the padding on fit + }); + + }); + + $('#reset_graph').click(function() { + eles.restore() + $('#slide').val(0) + cy.layout({ name: $('select[name=layout_select]').val(), + fit: true, // whether to fit the viewport to the graph + padding: 25 // the padding on fit + }); + }); + + $('select[name=focus_select]').change(function() { + focus_trait = $(this).val() + + eles.restore() + cy.$('edge[source != "' + focus_trait + '"][target != "' + focus_trait + '"]').remove() + + cy.layout({ name: $('select[name=layout_select]').val(), + fit: true, // whether to fit the viewport to the graph + padding: 25 // the padding on fit + }); + }); + + $('select[name=layout_select]').change(function() { + layout_type = $(this).val() + console.log("LAYOUT:", layout_type) + cy.layout({ name: layout_type, + fit: true, // whether to fit the viewport to the graph + padding: 25 // the padding on fit + }); + }); + + $("a#image_link").click(function(e) { + var pngData = cy.png(); + + $(this).attr('href', pngData); + $(this).attr('download', 'network_graph.png'); + + console.log("TESTING:", image_link) + + }); + + +}; + + diff --git a/gn2/wqflask/static/new/javascript/curvechart.js b/gn2/wqflask/static/new/javascript/curvechart.js new file mode 100644 index 00000000..48bf6bf3 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/curvechart.js @@ -0,0 +1,353 @@ +// Generated by CoffeeScript 1.8.0 +var curvechart; + +curvechart = function() { + var axispos, chart, commonX, curvesSelect, height, margin, nxticks, nyticks, rectcolor, rotate_ylab, strokecolor, strokecolorhilit, strokewidth, strokewidthhilit, title, titlepos, width, xlab, xlim, xscale, xticks, ylab, ylim, yscale, yticks; + width = 800; + height = 500; + margin = { + left: 60, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + axispos = { + xtitle: 25, + ytitle: 30, + xlabel: 5, + ylabel: 5 + }; + titlepos = 20; + xlim = null; + ylim = null; + nxticks = 5; + xticks = null; + nyticks = 5; + yticks = null; + rectcolor = "#e6e6e6"; + strokecolor = null; + strokecolorhilit = null; + strokewidth = 2; + strokewidthhilit = 2; + title = ""; + xlab = "X"; + ylab = "Y"; + rotate_ylab = null; + yscale = d3.scale.linear(); + xscale = d3.scale.linear(); + curvesSelect = null; + commonX = true; + chart = function(selection) { + return selection.each(function(data) { + var curve, curves, g, gEnter, group, i, indID, indtip, j, lastpoint, ngroup, points, pointsg, svg, titlegrp, tmp, v, xaxis, xrange, xs, yaxis, yrange, ys, _i, _j, _len, _ref, _ref1, _ref2, _ref3, _results; + indID = (_ref = data != null ? data.indID : void 0) != null ? _ref : null; + indID = indID != null ? indID : (function() { + _results = []; + for (var _i = 1, _ref1 = data.data.length; 1 <= _ref1 ? _i <= _ref1 : _i >= _ref1; 1 <= _ref1 ? _i++ : _i--){ _results.push(_i); } + return _results; + }).apply(this); + group = (_ref2 = data != null ? data.group : void 0) != null ? _ref2 : (function() { + var _results1; + _results1 = []; + for (i in data.data) { + _results1.push(1); + } + return _results1; + })(); + ngroup = d3.max(group); + group = (function() { + var _j, _len, _results1; + _results1 = []; + for (_j = 0, _len = group.length; _j < _len; _j++) { + g = group[_j]; + _results1.push(g - 1); + } + return _results1; + })(); + strokecolor = strokecolor != null ? strokecolor : selectGroupColors(ngroup, "pastel"); + strokecolor = expand2vector(strokecolor, ngroup); + strokecolorhilit = strokecolorhilit != null ? strokecolorhilit : selectGroupColors(ngroup, "dark"); + strokecolorhilit = expand2vector(strokecolorhilit, ngroup); + if (commonX) { + data = (function() { + var _results1; + _results1 = []; + for (i in data.data) { + _results1.push({ + x: data.x, + y: data.data[i] + }); + } + return _results1; + })(); + } else { + data = data.data; + } + xlim = xlim != null ? xlim : d3.extent(pullVarAsArray(data, "x")); + ylim = ylim != null ? ylim : d3.extent(pullVarAsArray(data, "y")); + for (i in data) { + tmp = data[i]; + data[i] = []; + for (j in tmp.x) { + if (!((tmp.x[j] == null) || (tmp.y[j] == null))) { + data[i].push({ + x: tmp.x[j], + y: tmp.y[j] + }); + } + } + } + svg = d3.select(this).selectAll("svg").data([data]); + gEnter = svg.enter().append("svg").append("g"); + svg.attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom); + g = svg.select("g"); + g.append("rect").attr("x", margin.left).attr("y", margin.top).attr("height", height).attr("width", width).attr("fill", rectcolor).attr("stroke", "none"); + xrange = [margin.left + margin.inner, margin.left + width - margin.inner]; + yrange = [margin.top + height - margin.inner, margin.top + margin.inner]; + xscale.domain(xlim).range(xrange); + yscale.domain(ylim).range(yrange); + xs = d3.scale.linear().domain(xlim).range(xrange); + ys = d3.scale.linear().domain(ylim).range(yrange); + yticks = yticks != null ? yticks : ys.ticks(nyticks); + xticks = xticks != null ? xticks : xs.ticks(nxticks); + titlegrp = g.append("g").attr("class", "title").append("text").attr("x", margin.left + width / 2).attr("y", margin.top - titlepos).text(title); + xaxis = g.append("g").attr("class", "x axis"); + xaxis.selectAll("empty").data(xticks).enter().append("line").attr("x1", function(d) { + return xscale(d); + }).attr("x2", function(d) { + return xscale(d); + }).attr("y1", margin.top).attr("y2", margin.top + height).attr("fill", "none").attr("stroke", "white").attr("stroke-width", 1).style("pointer-events", "none"); + xaxis.selectAll("empty").data(xticks).enter().append("text").attr("x", function(d) { + return xscale(d); + }).attr("y", margin.top + height + axispos.xlabel).text(function(d) { + return formatAxis(xticks)(d); + }); + xaxis.append("text").attr("class", "title").attr("x", margin.left + width / 2).attr("y", margin.top + height + axispos.xtitle).text(xlab); + rotate_ylab = rotate_ylab != null ? rotate_ylab : ylab.length > 1; + yaxis = g.append("g").attr("class", "y axis"); + yaxis.selectAll("empty").data(yticks).enter().append("line").attr("y1", function(d) { + return yscale(d); + }).attr("y2", function(d) { + return yscale(d); + }).attr("x1", margin.left).attr("x2", margin.left + width).attr("fill", "none").attr("stroke", "white").attr("stroke-width", 1).style("pointer-events", "none"); + yaxis.selectAll("empty").data(yticks).enter().append("text").attr("y", function(d) { + return yscale(d); + }).attr("x", margin.left - axispos.ylabel).text(function(d) { + return formatAxis(yticks)(d); + }); + yaxis.append("text").attr("class", "title").attr("y", margin.top + height / 2).attr("x", margin.left - axispos.ytitle).text(ylab).attr("transform", rotate_ylab ? "rotate(270," + (margin.left - axispos.ytitle) + "," + (margin.top + height / 2) + ")" : ""); + indtip = d3.tip().attr('class', 'd3-tip').html(function(d) { + return indID[d]; + }).direction('e').offset([0, 10]); + svg.call(indtip); + curve = d3.svg.line().x(function(d) { + return xscale(d.x); + }).y(function(d) { + return yscale(d.y); + }); + curves = g.append("g").attr("id", "curves"); + curvesSelect = curves.selectAll("empty").data(d3.range(data.length)).enter().append("path").datum(function(d) { + return data[d]; + }).attr("d", curve).attr("class", function(d, i) { + return "path" + i; + }).attr("fill", "none").attr("stroke", function(d, i) { + return strokecolor[group[i]]; + }).attr("stroke-width", strokewidth).on("mouseover.panel", function(d, i) { + var circle; + d3.select(this).attr("stroke", strokecolorhilit[group[i]]).moveToFront(); + circle = d3.select("circle#hiddenpoint" + i); + return indtip.show(i, circle.node()); + }).on("mouseout.panel", function(d, i) { + d3.select(this).attr("stroke", strokecolor[group[i]]).moveToBack(); + return indtip.hide(); + }); + lastpoint = (function() { + var _results1; + _results1 = []; + for (i in data) { + _results1.push({ + x: null, + y: null + }); + } + return _results1; + })(); + for (i in data) { + _ref3 = data[i]; + for (_j = 0, _len = _ref3.length; _j < _len; _j++) { + v = _ref3[_j]; + if ((v.x != null) && (v.y != null)) { + lastpoint[i] = v; + } + } + } + pointsg = g.append("g").attr("id", "invisiblepoints"); + points = pointsg.selectAll("empty").data(lastpoint).enter().append("circle").attr("id", function(d, i) { + return "hiddenpoint" + i; + }).attr("cx", function(d) { + return xscale(d.x); + }).attr("cy", function(d) { + return yscale(d.y); + }).attr("r", 1).attr("opacity", 0); + return g.append("rect").attr("x", margin.left).attr("y", margin.top).attr("height", height).attr("width", width).attr("fill", "none").attr("stroke", "black").attr("stroke-width", "none"); + }); + }; + chart.width = function(value) { + if (!arguments.length) { + return width; + } + width = value; + return chart; + }; + chart.height = function(value) { + if (!arguments.length) { + return height; + } + height = value; + return chart; + }; + chart.margin = function(value) { + if (!arguments.length) { + return margin; + } + margin = value; + return chart; + }; + chart.axispos = function(value) { + if (!arguments.length) { + return axispos; + } + axispos = value; + return chart; + }; + chart.titlepos = function(value) { + if (!arguments.length) { + return titlepos; + } + titlepos; + return chart; + }; + chart.xlim = function(value) { + if (!arguments.length) { + return xlim; + } + xlim = value; + return chart; + }; + chart.nxticks = function(value) { + if (!arguments.length) { + return nxticks; + } + nxticks = value; + return chart; + }; + chart.xticks = function(value) { + if (!arguments.length) { + return xticks; + } + xticks = value; + return chart; + }; + chart.ylim = function(value) { + if (!arguments.length) { + return ylim; + } + ylim = value; + return chart; + }; + chart.nyticks = function(value) { + if (!arguments.length) { + return nyticks; + } + nyticks = value; + return chart; + }; + chart.yticks = function(value) { + if (!arguments.length) { + return yticks; + } + yticks = value; + return chart; + }; + chart.rectcolor = function(value) { + if (!arguments.length) { + return rectcolor; + } + rectcolor = value; + return chart; + }; + chart.strokecolor = function(value) { + if (!arguments.length) { + return strokecolor; + } + strokecolor = value; + return chart; + }; + chart.strokecolorhilit = function(value) { + if (!arguments.length) { + return strokecolorhilit; + } + strokecolorhilit = value; + return chart; + }; + chart.strokewidth = function(value) { + if (!arguments.length) { + return strokewidth; + } + strokewidth = value; + return chart; + }; + chart.strokewidthhilit = function(value) { + if (!arguments.length) { + return strokewidthhilit; + } + strokewidthhilit = value; + return chart; + }; + chart.commonX = function(value) { + if (!arguments.length) { + return commonX; + } + commonX = value; + return chart; + }; + chart.title = function(value) { + if (!arguments.length) { + return title; + } + title = value; + return chart; + }; + chart.xlab = function(value) { + if (!arguments.length) { + return xlab; + } + xlab = value; + return chart; + }; + chart.ylab = function(value) { + if (!arguments.length) { + return ylab; + } + ylab = value; + return chart; + }; + chart.rotate_ylab = function(value) { + if (!arguments.length) { + return rotate_ylab; + } + rotate_ylab = value; + return chart; + }; + chart.yscale = function() { + return yscale; + }; + chart.xscale = function() { + return xscale; + }; + chart.curvesSelect = function() { + return curvesSelect; + }; + return chart; +}; diff --git a/gn2/wqflask/static/new/javascript/d3panels.min.js b/gn2/wqflask/static/new/javascript/d3panels.min.js new file mode 100644 index 00000000..dfc10643 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/d3panels.min.js @@ -0,0 +1 @@ +!function(){var d3panels={version:"1.7.1"};"use strict";d3panels.formatAxis=function(d){var extra_digits=arguments.length>1&&arguments[1]!==undefined?arguments[1]:0;var gap,ndig;if(d[0]!=null){gap=d[1]-d[0]}else if(d.length>2){gap=d[2]-d[1]}else{gap=d[1]}ndig=Math.floor(d3panels.log10(Math.abs(gap)));if(ndig>0){ndig=0}ndig=Math.abs(ndig)+extra_digits;return function(val){if(val!=null&&val!=="NA"){return d3.format("."+ndig+"f")(val)}return"NA"}};d3panels.unique=function(x){var k,len,output,v;output={};for(k=0,len=x.length;k<len;k++){v=x[k];if(v!=null){output[v]=v}}output=function(){var results;results=[];for(v in output){results.push(output[v])}return results}();return d3panels.forceAsArray(output)};d3panels.pullVarAsArray=function(data,variable){var i,results;results=[];for(i in data){results.push(data[i][variable])}return results};d3panels.reorgByChr=function(uniquechr,chr,vector){var c,i,k,len,result;if(chr.length!==vector.length){d3panels.displayError("reorgByChr: chr.length ("+chr.length+") != vector.length ("+vector.length+")")}result={};for(i=k=0,len=uniquechr.length;k<len;i=++k){c=uniquechr[i];result[c]=function(){var results;results=[];for(i in vector){if(chr[i]===c){results.push(vector[i])}}return results}()}return result};d3panels.reorgLodData=function(data){var i;data.posByChr=d3panels.reorgByChr(data.chrname,data.chr,data.pos);data.lodByChr=d3panels.reorgByChr(data.chrname,data.chr,data.lod);if(data.poslabel!=null){data.poslabelByChr=d3panels.reorgByChr(data.chrname,data.chr,data.poslabel)}if(data.marker!=null){data.markerinfo=function(){var results;results=[];for(i in data.marker){if(data.marker[i]!==""){results.push({name:data.marker[i],chr:data.chr[i],pos:data.pos[i],lod:data.lod[i]})}}return results}()}return data};d3panels.calc_chrscales=function(plot_width,left_margin,gap,chr,start,end){var reverse=arguments.length>6&&arguments[6]!==undefined?arguments[6]:false;var chr_end_pixels,chr_length,chr_start_pixels,domain,i,k,n_chr,range,ref,right,tot_chr_length,tot_pixels,xscale;n_chr=chr.length;chr_length=function(){var results;results=[];for(i in end){results.push(end[i]-start[i])}return results}();tot_chr_length=chr_length.reduce(function(t,s){return t+s});tot_pixels=plot_width-gap*n_chr;chr_start_pixels=[left_margin+gap/2];chr_end_pixels=[left_margin+gap/2+tot_pixels/tot_chr_length*chr_length[0]];for(i=k=1,ref=n_chr-1;1<=ref?k<=ref:k>=ref;i=1<=ref?++k:--k){chr_start_pixels.push(chr_end_pixels[i-1]+gap);chr_end_pixels.push(chr_start_pixels[i]+tot_pixels/tot_chr_length*chr_length[i])}right=plot_width+left_margin*2;xscale={};for(i in chr){domain=[start[i],end[i]];range=[chr_start_pixels[i],chr_end_pixels[i]];if(reverse){domain.reverse();range=[right-range[1],right-range[0]]}xscale[chr[i]]=d3.scaleLinear().domain(domain).range(range)}return xscale};d3panels.selectGroupColors=function(ngroup,palette){var cat20,pastel1,pastel20,set1;if(ngroup===0){return[]}set1=["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf","#999999"];pastel1=["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"];cat20=["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];pastel20=["#8fc7f4","#fed7f8","#ffbf8e","#fffbb8","#8ce08c","#d8ffca","#f68788","#ffd8d6","#d4a7fd","#f5f0f5","#cc968b","#f4dcd4","#f3b7f2","#f7f6f2","#bfbfbf","#f7f7f7","#fcfd82","#fbfbcd","#87feff","#defaf5"];if(palette==="dark"){if(ngroup===1){return["slateblue"]}if(ngroup===2){return["MediumVioletRed","slateblue"]}if(ngroup===3){return["MediumVioletRed","MediumSeaGreen","slateblue"]}if(ngroup<=9){return set1.slice(0,ngroup)}return cat20.slice(0,ngroup)}else{if(ngroup===1){return["#bebebe"]}if(ngroup===2){return["lightpink","lightblue"]}if(ngroup<=9){return pastel1.slice(0,ngroup)}return pastel20.slice(0,ngroup)}};d3panels.expand2vector=function(input,n){var i;if(input==null){return input}if(Array.isArray(input)&&input.length>=n){return input}if(!Array.isArray(input)){input=[input]}if(input.length>1&&n>1){input=function(){var results;results=[];for(i in d3.range(n)){results.push(input[i%input.length])}return results}()}if(input.length===1&&n>1){input=function(){var results;results=[];for(i in d3.range(n)){results.push(input[0])}return results}()}return input};d3panels.median=function(x){var n,xv;if(x==null){return null}x=function(){var k,len,results;results=[];for(k=0,len=x.length;k<len;k++){xv=x[k];if(xv!=null){results.push(xv)}}return results}();n=x.length;if(!(n>0)){return null}x.sort(function(a,b){return a-b});if(n%2===1){return x[(n-1)/2]}return(x[n/2]+x[n/2-1])/2};d3panels.pad_vector=function(x){var pad=arguments.length>1&&arguments[1]!==undefined?arguments[1]:null;if(pad==null){return[x[0]-(x[1]-x[0])].concat(x).concat([x[x.length-1]+(x[x.length-1]-x[x.length-2])])}return[x[0]-pad].concat(x).concat(x[x.length-1]+pad)};d3panels.calc_midpoints=function(x){var i,k,ref,results;results=[];for(i=k=0,ref=x.length-2;0<=ref?k<=ref:k>=ref;i=0<=ref?++k:--k){results.push((x[i]+x[i+1])/2)}return results};d3panels.calc_cell_rect=function(cells,xmid,ymid){var bottom,cell,k,left,len,results,right,top;results=[];for(k=0,len=cells.length;k<len;k++){cell=cells[k];left=xmid[cell.xindex];right=xmid[1+cell.xindex];top=ymid[cell.yindex];bottom=ymid[1+cell.yindex];cell.left=d3.min([left,right]);cell.width=Math.abs(right-left);cell.top=d3.min([top,bottom]);results.push(cell.height=Math.abs(bottom-top))}return results};d3panels.calc_chrcell_rect=function(cells,xmid,ymid){var bottom,cell,k,left,len,results,right,top;results=[];for(k=0,len=cells.length;k<len;k++){cell=cells[k];left=xmid[cell.chr][cell.posindex];right=xmid[cell.chr][1+cell.posindex];top=ymid[cell.lodindex];bottom=ymid[1+cell.lodindex];cell.left=d3.min([left,right]);cell.width=Math.abs(right-left);cell.top=d3.min([top,bottom]);results.push(cell.height=Math.abs(bottom-top))}return results};d3panels.calc_2dchrcell_rect=function(cells,xmid,ymid){var bottom,cell,k,left,len,results,right,top;results=[];for(k=0,len=cells.length;k<len;k++){cell=cells[k];left=xmid[cell.chrx][cell.xindexByChr];right=xmid[cell.chrx][1+cell.xindexByChr];top=ymid[cell.chry][cell.yindexByChr];bottom=ymid[cell.chry][1+cell.yindexByChr];cell.left=d3.min([left,right]);cell.width=Math.abs(right-left);cell.top=d3.min([top,bottom]);results.push(cell.height=Math.abs(bottom-top))}return results};d3panels.maxdiff=function(x){var d,i,k,ref,result;if(x.length<2){return null}result=x[1]-x[0];if(x.length<3){return result}for(i=k=2,ref=x.length;2<=ref?k<ref:k>ref;i=2<=ref?++k:--k){d=x[i]-x[i-1];if(d>result){result=d}}return result};d3panels.matrixMin=function(mat){var i,j,result;result=mat[0][0];for(i in mat){for(j in mat[i]){if(!(result!=null)||result>mat[i][j]&&mat[i][j]!=null){result=mat[i][j]}}}return result};d3panels.matrixMax=function(mat){var i,j,result;result=mat[0][0];for(i in mat){for(j in mat[i]){if(!(result!=null)||result<mat[i][j]&&mat[i][j]!=null){result=mat[i][j]}}}return result};d3panels.matrixMaxAbs=function(mat){var i,j,result;result=Math.abs(mat[0][0]);for(i in mat){for(j in mat[i]){if(!(result!=null)||result<Math.abs(mat[i][j])&&mat[i][j]!=null){result=Math.abs(mat[i][j])}}}return result};d3panels.matrixExtent=function(mat){return[d3panels.matrixMin(mat),d3panels.matrixMax(mat)]};d3panels.forceAsArray=function(x){if(x==null){return x}if(Array.isArray(x)){return x}return[x]};d3panels.missing2null=function(vec){var missingvalues=arguments.length>1&&arguments[1]!==undefined?arguments[1]:["NA",""];return vec.map(function(value){if(missingvalues.indexOf(value)>-1){return null}else{return value}})};d3panels.displayError=function(message){var divid=arguments.length>1&&arguments[1]!==undefined?arguments[1]:null;var div;div="div.error";if(divid!=null){div+="#"+divid}if(d3.select(div).empty()){d3.select("body").insert("div",":first-child").attr("class","error")}return d3.select(div).append("p").text(message)};d3panels.sumArray=function(vec){var x;vec=function(){var k,len,results;results=[];for(k=0,len=vec.length;k<len;k++){x=vec[k];if(x!=null){results.push(x)}}return results}();if(!(vec.length>0)){return null}return vec.reduce(function(a,b){return a*1+b*1})};d3panels.calc_crosstab=function(data){var col,cs,i,k,l,ncol,nrow,ref,ref1,result,row,rs;nrow=data.ycat.length;ncol=data.xcat.length;result=function(){var k,ref,results;results=[];for(row=k=0,ref=nrow;0<=ref?k<=ref:k>=ref;row=0<=ref?++k:--k){results.push(function(){var l,ref1,results1;results1=[];for(col=l=0,ref1=ncol;0<=ref1?l<=ref1:l>=ref1;col=0<=ref1?++l:--l){results1.push(0)}return results1}())}return results}();for(i in data.x){result[data.y[i]][data.x[i]]+=1}rs=d3panels.rowSums(result);cs=d3panels.colSums(result);for(i=k=0,ref=ncol;0<=ref?k<ref:k>ref;i=0<=ref?++k:--k){result[nrow][i]=cs[i]}for(i=l=0,ref1=nrow;0<=ref1?l<ref1:l>ref1;i=0<=ref1?++l:--l){result[i][ncol]=rs[i]}result[nrow][ncol]=d3panels.sumArray(rs);return result};d3panels.rowSums=function(mat){var k,len,results,x;results=[];for(k=0,len=mat.length;k<len;k++){x=mat[k];results.push(d3panels.sumArray(x))}return results};d3panels.transpose=function(mat){var i,j,k,ref,results;results=[];for(j=k=0,ref=mat[0].length;0<=ref?k<ref:k>ref;j=0<=ref?++k:--k){results.push(function(){var l,ref1,results1;results1=[];for(i=l=0,ref1=mat.length;0<=ref1?l<ref1:l>ref1;i=0<=ref1?++l:--l){results1.push(mat[i][j])}return results1}())}return results};d3panels.colSums=function(mat){return d3panels.rowSums(d3panels.transpose(mat))};d3panels.log2=function(x){if(x==null){return x}return Math.log(x)/Math.log(2)};d3panels.log10=function(x){if(x==null){return x}return Math.log(x)/Math.log(10)};d3panels.abs=function(x){if(x==null){return x}return Math.abs(x)};d3panels.mean_by_group=function(g,y){var i,means,n;means={};n={};for(i in g){if(n[g[i]]!=null){if(y[i]!=null){means[g[i]]+=y[i]}if(y[i]!=null){n[g[i]]+=1}}else{if(y[i]!=null){means[g[i]]=y[i]}if(y[i]!=null){n[g[i]]=1}}}for(i in means){means[i]/=n[i]}return means};d3panels.sd_by_group=function(g,y){var dev,i,means,n,sds;means=d3panels.mean_by_group(g,y);sds={};n={};for(i in g){dev=y[i]-means[g[i]];if(n[g[i]]!=null){if(y[i]!=null){sds[g[i]]+=dev*dev}if(y[i]!=null){n[g[i]]+=1}}else{if(y[i]!=null){sds[g[i]]=dev*dev}if(y[i]!=null){n[g[i]]=1}}}for(i in sds){sds[i]=n[i]<2?null:Math.sqrt(sds[i]/(n[i]-1))}return sds};d3panels.count_groups=function(g,y){var i,n;n={};for(i in g){if(n[g[i]]!=null){if(y[i]!=null){n[g[i]]+=1}}else{if(y[i]!=null){n[g[i]]=1}}}return n};d3panels.ci_by_group=function(g,y){var m=arguments.length>2&&arguments[2]!==undefined?arguments[2]:2;var ci,dev,i,means,n,sds;means=d3panels.mean_by_group(g,y);sds={};n={};for(i in g){dev=y[i]-means[g[i]];if(n[g[i]]!=null){if(y[i]!=null){sds[g[i]]+=dev*dev}if(y[i]!=null){n[g[i]]+=1}}else{if(y[i]!=null){sds[g[i]]=dev*dev}if(y[i]!=null){n[g[i]]=1}}}for(i in sds){sds[i]=n[i]<2?null:Math.sqrt(sds[i]/(n[i]-1))}ci={};for(i in means){ci[i]={mean:means[i],low:n[i]>0?means[i]-m*sds[i]/Math.sqrt(n[i]):means[i],high:n[i]>0?means[i]+m*sds[i]/Math.sqrt(n[i]):means[i]}}return ci};d3panels.pad_ylim=function(ylim){var p=arguments.length>1&&arguments[1]!==undefined?arguments[1]:.025;var d;d=ylim[1]-ylim[0];return[ylim[0]-d*p,ylim[1]+d*p]};d3panels.add_chrname_start_end=function(data){var c,i,k,l,len,len1,ref,ref1,these_pos;if(data.chrname==null){data.chrname=d3panels.unique(data.chr)}data.chrname=d3panels.forceAsArray(data.chrname);if(data.chrstart==null){data.chrstart=[];ref=data.chrname;for(k=0,len=ref.length;k<len;k++){c=ref[k];these_pos=function(){var results;results=[];for(i in data.chr){if(data.chr[i]===c){results.push(data.pos[i])}}return results}();data.chrstart.push(d3.min(these_pos))}}if(data.chrend==null){data.chrend=[];ref1=data.chrname;for(l=0,len1=ref1.length;l<len1;l++){c=ref1[l];these_pos=function(){var results;results=[];for(i in data.chr){if(data.chr[i]===c){results.push(data.pos[i])}}return results}();data.chrend.push(d3.max(these_pos))}}data.start=d3panels.forceAsArray(data.start);data.end=d3panels.forceAsArray(data.end);return data};d3panels.calc_breaks=function(number,low,high){var d,i,results;if(low>=high){d3panels.displayError("calc_breaks: should have low < high");if(low>high){var _ref=[high,low];low=_ref[0];high=_ref[1]}if(low===high){low-=.5;high+=.5}}if(number<2){d3panels.displayError("calc_breaks: number should be >= 2");number=2}d=(high-low)/(number-1);results=[];for(i in d3.range(number)){results.push(low+d*i)}return results};d3panels.calc_freq=function(values,breaks){var return_counts=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;var br,i,k,len,n,ref,result,v,z;v=values.slice(0);v.sort(function(a,b){return+a-b});br=breaks.slice(0);br.sort(function(a,b){return+a-b});br[0]-=1e-6;br[br.length-1]+=1e-6;result=function(){var k,len,ref,results;ref=d3.range(br.length-1);results=[];for(k=0,len=ref.length;k<len;k++){i=ref[k];results.push(0)}return results}();n=v.length;v=function(){var k,len,results;results=[];for(k=0,len=v.length;k<len;k++){z=v[k];if(z>br[0]&&z<br[br.length-1]){results.push(z)}}return results}();if(v.length<n){d3panels.displayError("calc_freq: values out of range of breaks")}n=v.length;ref=d3.range(br.length-1);for(k=0,len=ref.length;k<len;k++){i=ref[k];result[i]=function(){var l,len1,results;results=[];for(l=0,len1=v.length;l<len1;l++){z=v[l];if(z>=br[i]&&z<br[i+1]){results.push(z)}}return results}().length;if(!return_counts){result[i]/=n*(br[i+1]-br[i])}}return result};d3panels.calc_hist_path=function(freq,breaks){var i,result;if(freq.length!==breaks.length-1){d3panels.displayError("freq.length ("+freq.length+") should be breaks.length - 1 ("+(breaks.length-1)+")")}result=[{x:breaks[0],y:0}];for(i in freq){result.push({x:breaks[i],y:freq[i]});result.push({x:breaks[+i+1],y:freq[i]})}result.push({x:breaks[breaks.length-1],y:0});return result};d3panels.index_of_nearest=function(d,vec){var abs_diff;abs_diff=vec.map(function(val){return Math.abs(val-d)});return abs_diff.indexOf(d3.min(abs_diff))};d3panels.check_listarg_v_default=function(arg,defaults){var key;for(key in defaults){if(arg[key]==null){arg[key]=defaults[key]}}return arg};d3panels.object_position=function(object){var obj,x,y;obj=object.node().getBoundingClientRect();x=obj.left/2+obj.right/2;y=obj.top/2+obj.bottom/2;return{x:x,y:y}};"use strict";d3panels.lod2dheatmap=function(chartOpts){var cellSelect,cells,celltip,chart,chrGap,colors,equalCells,height,hilitcolor,margin,nullcolor,oneAtTop,ref,ref1,ref10,ref11,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,svg,tipclass,width,xscale,yscale,zlim,zscale,zthresh;if(chartOpts==null){chartOpts={}}width=(ref=chartOpts!=null?chartOpts.width:void 0)!=null?ref:800;height=(ref1=chartOpts!=null?chartOpts.height:void 0)!=null?ref1:800;margin=(ref2=chartOpts!=null?chartOpts.margin:void 0)!=null?ref2:{left:60,top:40,right:40,bottom:60};chrGap=(ref3=chartOpts!=null?chartOpts.chrGap:void 0)!=null?ref3:6;equalCells=(ref4=chartOpts!=null?chartOpts.equalCells:void 0)!=null?ref4:false;oneAtTop=(ref5=chartOpts!=null?chartOpts.oneAtTop:void 0)!=null?ref5:false;colors=(ref6=chartOpts!=null?chartOpts.colors:void 0)!=null?ref6:["slateblue","white","crimson"];nullcolor=(ref7=chartOpts!=null?chartOpts.nullcolor:void 0)!=null?ref7:"#e6e6e6";zlim=(ref8=chartOpts!=null?chartOpts.zlim:void 0)!=null?ref8:null;zthresh=(ref9=chartOpts!=null?chartOpts.zthresh:void 0)!=null?ref9:null;hilitcolor=(ref10=chartOpts!=null?chartOpts.hilitcolor:void 0)!=null?ref10:"black";tipclass=(ref11=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref11:"tooltip";xscale=null;yscale=null;zscale=null;celltip=null;cells=null;svg=null;cellSelect=null;chart=function chart(selection,data){var cellg,chr,i,indexWithinChr,j,k,l,len,len1,len2,m,myframe,n_pos,posByChr,ref12,ref13,ref14,tooltipfunc,x,xmid_scaled,y,ymid_scaled,zmax,zmin;margin=d3panels.check_listarg_v_default(margin,{left:60,top:40,right:40,bottom:60});if(data.chr==null){d3panels.displayError("lod2dheatmap: data.chr is missing")}if(data.pos==null){d3panels.displayError("lod2dheatmap: data.pos is missing")}if(data.lod==null){d3panels.displayError("lod2dheatmap: data.lod is missing")}n_pos=data.chr.length;if(data.pos.length!==n_pos){d3panels.displayError("lod2dheatmap: data.pos.length ("+data.pos.length+") != data.chr.length ("+n_pos+")")}if(data.lod.length!==n_pos){d3panels.displayError("lod2dheatmap: data.lod.length ("+data.lod.length+") != data.chr.length ("+n_pos+")")}for(i in data.lod){if(data.lod[i].length!==n_pos){d3panels.displayError("lod2dheatmap: data.lod["+i+"].length ("+data.lod[i].length+") != data.chr.length ("+n_pos+")")}}if(data.poslabel!=null){if(data.poslabel.length!==n_pos){d3panels.displayError("lod2dheatmap: data.poslabel.length ("+data.poslabel.length+") != data.chr.length ("+n_pos+")")}}else{data.poslabel=function(){var results;results=[];for(i in data.chr){results.push(data.chr[i]+"@"+d3panels.formatAxis(data.pos)(data.pos[i]))}return results}()}if(data.chrname==null){data.chrname=d3panels.unique(data.chr)}data.chrname=d3panels.forceAsArray(data.chrname);if(equalCells){data.pos=[];ref12=data.chrname;for(k=0,len=ref12.length;k<len;k++){chr=ref12[k];data.pos=data.pos.concat(function(){var results;results=[];for(i in data.chr){if(data.chr[i]===chr){results.push(+i)}}return results}())}}data=d3panels.add_chrname_start_end(data);if(equalCells){chrGap=(width-margin.left-margin.right-2*data.chrname.length)/data.chr.length+2}chartOpts.chrGap=chrGap;chartOpts.width=width;chartOpts.height=height;chartOpts.margin=margin;myframe=d3panels.chr2dpanelframe(chartOpts);myframe(selection,{chr:data.chrname,start:data.chrstart,end:data.chrend});svg=myframe.svg();xscale=myframe.xscale();yscale=myframe.yscale();posByChr=d3panels.reorgByChr(data.chrname,data.chr,data.pos);xmid_scaled={};ymid_scaled={};ref13=data.chrname;for(l=0,len1=ref13.length;l<len1;l++){chr=ref13[l];xmid_scaled[chr]=d3panels.calc_midpoints(d3panels.pad_vector(function(){var len2,m,ref14,results;ref14=posByChr[chr];results=[];for(m=0,len2=ref14.length;m<len2;m++){x=ref14[m];results.push(xscale[chr](x))}return results}(),chrGap-2));ymid_scaled[chr]=d3panels.calc_midpoints(d3panels.pad_vector(function(){var len2,m,ref14,results;ref14=posByChr[chr];results=[];for(m=0,len2=ref14.length;m<len2;m++){y=ref14[m];results.push(yscale[chr](y))}return results}(),oneAtTop?chrGap-2:2-chrGap))}zmin=d3panels.matrixMin(data.lod);zmax=d3panels.matrixMaxAbs(data.lod);zlim=zlim!=null?zlim:[-zmax,0,zmax];if(zlim.length!==colors.length){d3panels.displayError("lod2dheatmap: zlim.length ("+zlim.length+") != colors.length ("+colors.length+")")}zscale=d3.scaleLinear().domain(zlim).range(colors);zthresh=zthresh!=null?zthresh:zmin-1;indexWithinChr=[];ref14=data.chrname;for(m=0,len2=ref14.length;m<len2;m++){chr=ref14[m];indexWithinChr=indexWithinChr.concat(function(){var results;results=[];for(i in posByChr[chr]){results.push(+i)}return results}())}cells=[];for(i in data.chr){for(j in data.chr){if(Math.abs(data.lod[i][j])>=zthresh){cells.push({lod:data.lod[i][j],chrx:data.chr[i],chry:data.chr[j],poslabelx:data.poslabel[i],poslabely:data.poslabel[j],xindex:i,yindex:j,xindexByChr:indexWithinChr[i],yindexByChr:indexWithinChr[j]})}}}d3panels.calc_2dchrcell_rect(cells,xmid_scaled,ymid_scaled);cellg=svg.append("g").attr("id","cells");cellSelect=cellg.selectAll("empty").data(cells).enter().append("rect").attr("x",function(d){return d.left}).attr("y",function(d){return d.top}).attr("width",function(d){return d.width}).attr("height",function(d){return d.height}).attr("class",function(d,i){return"cell"+i}).attr("fill",function(d){if(d.lod!=null){return zscale(d.lod)}else{return nullcolor}}).attr("stroke","none").attr("stroke-width","1").on("mouseover",function(d){return d3.select(this).attr("stroke",hilitcolor).raise()}).on("mouseout",function(){return d3.select(this).attr("stroke","none")});tooltipfunc=function tooltipfunc(d){var z;z=d3.format(".2f")(Math.abs(d.lod));return"("+d.poslabelx+","+d.poslabely+") → "+z};return celltip=d3panels.tooltip_create(d3.select("body"),cellg.selectAll("rect"),{tipclass:tipclass},tooltipfunc)};chart.xscale=function(){return xscale};chart.yscale=function(){return yscale};chart.zscale=function(){return zscale};chart.cells=function(){return cellSelect};chart.celltip=function(){return celltip};chart.svg=function(){return svg};chart.remove=function(){svg.remove();d3panels.tooltip_destroy(celltip);return null};return chart};"use strict";d3panels.panelframe=function(chartOpts){var axispos,box,boxcolor,boxwidth,chart,height,margin,nxticks,nyticks,plot_height,plot_width,rectcolor,ref,ref1,ref10,ref11,ref12,ref13,ref14,ref15,ref16,ref17,ref18,ref19,ref2,ref20,ref21,ref22,ref23,ref24,ref25,ref26,ref3,ref4,ref5,ref6,ref7,ref8,ref9,rotate_ylab,svg,title,titlepos,v_over_h,width,xNA,xNA_size,xlab,xlabels,xlim,xlineOpts,xlines,xscale,xscale_wnull,xticklab,xticks,yNA,yNA_size,ylab,ylabels,ylim,ylineOpts,ylines,yscale,yscale_wnull,yticklab,yticks;if(chartOpts==null){chartOpts={}}width=(ref=chartOpts!=null?chartOpts.width:void 0)!=null?ref:800;height=(ref1=chartOpts!=null?chartOpts.height:void 0)!=null?ref1:500;margin=(ref2=chartOpts!=null?chartOpts.margin:void 0)!=null?ref2:{left:60,top:40,right:40,bottom:40,inner:3};axispos=(ref3=chartOpts!=null?chartOpts.axispos:void 0)!=null?ref3:{xtitle:25,ytitle:45,xlabel:5,ylabel:5};titlepos=(ref4=chartOpts!=null?chartOpts.titlepos:void 0)!=null?ref4:20;title=(ref5=chartOpts!=null?chartOpts.title:void 0)!=null?ref5:"";xlab=(ref6=chartOpts!=null?chartOpts.xlab:void 0)!=null?ref6:"X";ylab=(ref7=chartOpts!=null?chartOpts.ylab:void 0)!=null?ref7:"Y";rotate_ylab=(ref8=chartOpts!=null?chartOpts.rotate_ylab:void 0)!=null?ref8:null;xNA=(ref9=chartOpts!=null?chartOpts.xNA:void 0)!=null?ref9:false;yNA=(ref10=chartOpts!=null?chartOpts.yNA:void 0)!=null?ref10:false;xNA_size=(ref11=chartOpts!=null?chartOpts.xNA_size:void 0)!=null?ref11:{width:20,gap:10};yNA_size=(ref12=chartOpts!=null?chartOpts.yNA_size:void 0)!=null?ref12:{width:20,gap:10};xlim=(ref13=chartOpts!=null?chartOpts.xlim:void 0)!=null?ref13:[0,1];ylim=(ref14=chartOpts!=null?chartOpts.ylim:void 0)!=null?ref14:[0,1];nxticks=(ref15=chartOpts!=null?chartOpts.nxticks:void 0)!=null?ref15:5;xticks=(ref16=chartOpts!=null?chartOpts.xticks:void 0)!=null?ref16:null;xticklab=(ref17=chartOpts!=null?chartOpts.xticklab:void 0)!=null?ref17:null;nyticks=(ref18=chartOpts!=null?chartOpts.nyticks:void 0)!=null?ref18:5;yticks=(ref19=chartOpts!=null?chartOpts.yticks:void 0)!=null?ref19:null;yticklab=(ref20=chartOpts!=null?chartOpts.yticklab:void 0)!=null?ref20:null;rectcolor=(ref21=chartOpts!=null?chartOpts.rectcolor:void 0)!=null?ref21:"#e6e6e6";boxcolor=(ref22=chartOpts!=null?chartOpts.boxcolor:void 0)!=null?ref22:"black";boxwidth=(ref23=chartOpts!=null?chartOpts.boxwidth:void 0)!=null?ref23:2;xlineOpts=(ref24=chartOpts!=null?chartOpts.xlineOpts:void 0)!=null?ref24:{color:"white",width:2};ylineOpts=(ref25=chartOpts!=null?chartOpts.ylineOpts:void 0)!=null?ref25:{color:"white",width:2};v_over_h=(ref26=chartOpts!=null?chartOpts.v_over_h:void 0)!=null?ref26:false;xscale=null;yscale=null;xNA=xNA;yNA=yNA;xlines=null;ylines=null;xlabels=null;ylabels=null;plot_width=null;plot_height=null;box=null;svg=null;xscale_wnull=null;yscale_wnull=null;chart=function chart(selection){var boxes,boxes2include,d,g,i,inner_height,inner_width,xNA_xpos,xaxis,xrange,yNA_ypos,yaxis,ylabpos_x,ylabpos_y,yrange;margin=d3panels.check_listarg_v_default(margin,{left:60,top:40,right:40,bottom:40,inner:3});axispos=d3panels.check_listarg_v_default(axispos,{xtitle:25,ytitle:45,xlabel:5,ylabel:5});xNA_size=d3panels.check_listarg_v_default(xNA_size,{width:20,gap:10});yNA_size=d3panels.check_listarg_v_default(yNA_size,{width:20,gap:10});xlineOpts=d3panels.check_listarg_v_default(xlineOpts,{color:"white",width:2});ylineOpts=d3panels.check_listarg_v_default(ylineOpts,{color:"white",width:2});svg=selection.append("svg");svg.attr("width",width).attr("height",height).attr("class","d3panels");g=svg.append("g").attr("id","frame");if(!xNA){xNA_size={width:0,gap:0}}if(!yNA){yNA_size={width:0,gap:0}}plot_width=width-(margin.left+margin.right);plot_height=height-(margin.top+margin.bottom);inner_width=width-(margin.right+margin.left+xNA_size.width+xNA_size.gap);inner_height=height-(margin.top+margin.bottom+yNA_size.width+yNA_size.gap);boxes={left:[margin.left+xNA_size.width+xNA_size.gap,margin.left,margin.left,margin.left+xNA_size.width+xNA_size.gap],width:[inner_width,xNA_size.width,xNA_size.width,inner_width],top:[margin.top,margin.top,height-(margin.bottom+yNA_size.width),height-(margin.bottom+yNA_size.width)],height:[inner_height,inner_height,yNA_size.width,yNA_size.width]};xNA_xpos=xNA?margin.left+xNA_size.width/2:-5e4;yNA_ypos=yNA?height-margin.bottom-yNA_size.width/2:-5e4;xrange=[boxes.left[0],boxes.left[0]+boxes.width[0]];yrange=[boxes.top[0]+boxes.height[0],boxes.top[0]];for(i in boxes.left){if(boxes.width[i]>0&&boxes.height[i]>0){g.append("rect").attr("x",boxes.left[i]).attr("y",boxes.top[i]).attr("height",boxes.height[i]).attr("width",boxes.width[i]).attr("fill",rectcolor).attr("stroke","none").attr("shape-rendering","crispEdges")}}g.append("g").attr("class","title").append("text").text(title).attr("x",(width-margin.left-margin.right)/2+margin.left).attr("y",titlepos);rotate_ylab=rotate_ylab!=null?rotate_ylab:ylab.length>1;if(v_over_h){yaxis=g.append("g").attr("class","y axis");xaxis=g.append("g").attr("class","x axis")}else{xaxis=g.append("g").attr("class","x axis");yaxis=g.append("g").attr("class","y axis")}xaxis.append("text").attr("class","title").text(xlab).attr("x",(width-margin.left-margin.right)/2+margin.left).attr("y",plot_height+margin.top+axispos.xtitle);ylabpos_y=(height-margin.top-margin.bottom)/2+margin.top;ylabpos_x=margin.left-axispos.ytitle;yaxis.append("text").attr("class","title").text(ylab).attr("y",ylabpos_y).attr("x",ylabpos_x).attr("transform",rotate_ylab?"rotate(270,"+ylabpos_x+","+ylabpos_y+")":"");xscale=d3.scaleLinear().domain(xlim).range([xrange[0]+margin.inner,xrange[1]-margin.inner]);yscale=d3.scaleLinear().domain(ylim).range([yrange[0]-margin.inner,yrange[1]+margin.inner]);xscale_wnull=function xscale_wnull(val){if(val==null){return xNA_xpos}return xscale(val)};yscale_wnull=function yscale_wnull(val){if(val==null){return yNA_ypos}return yscale(val)};xticks=xticks!=null?xticks:xscale.ticks(nxticks);if(xticklab!=null&&xticklab.length!==xticks.length){d3panels.displayError("panelframe: xticklab.length ("+xticklab.length+") != xticks.length ("+xticks.length+")")}if(!(xticklab!=null&&xticklab.length===xticks.length)){xticklab=function(){var j,len,results;results=[];for(j=0,len=xticks.length;j<len;j++){d=xticks[j];results.push(d3panels.formatAxis(xticks)(d))}return results}()}if(xNA){xticks=[null].concat(xticks)}if(xNA){xticklab=["NA"].concat(xticklab)}yticks=yticks!=null?yticks:yscale.ticks(nyticks);if(yticklab!=null&&yticklab.length!==yticks.length){d3panels.displayError("panelframe: yticklab.length ("+yticklab.length+") != yticks.length ("+yticks.length+")")}if(!(yticklab!=null&&yticklab.length===yticks.length)){yticklab=function(){var j,len,results;results=[];for(j=0,len=yticks.length;j<len;j++){d=yticks[j];results.push(d3panels.formatAxis(yticks)(d))}return results}()}if(yNA){yticks=[null].concat(yticks)}if(yNA){yticklab=["NA"].concat(yticklab)}ylines=yaxis.append("g").attr("id","ylines").selectAll("empty").data(yticks.concat(yticks)).enter().append("line").attr("y1",function(d){return yscale_wnull(d)}).attr("y2",function(d){return yscale_wnull(d)}).attr("x1",function(d,i){if(i<yticks.length){return xrange[0]}return margin.left}).attr("x2",function(d,i){if(i<yticks.length){return xrange[1]}return margin.left+xNA_size.width}).attr("fill","none").attr("stroke",ylineOpts.color).attr("stroke-width",ylineOpts.width).attr("shape-rendering","crispEdges").style("pointer-events","none");xlines=xaxis.append("g").attr("id","xlines").selectAll("empty").data(xticks.concat(xticks)).enter().append("line").attr("x1",function(d){return xscale_wnull(d)}).attr("x2",function(d){return xscale_wnull(d)}).attr("y1",function(d,i){if(i<xticks.length){return yrange[0]}return height-margin.bottom}).attr("y2",function(d,i){if(i<xticks.length){return yrange[1]}return height-margin.bottom-yNA_size.width}).attr("fill","none").attr("stroke",xlineOpts.color).attr("stroke-width",xlineOpts.width).attr("shape-rendering","crispEdges").style("pointer-events","none");xlabels=xaxis.append("g").attr("id","xlabels").selectAll("empty").data(xticklab).enter().append("text").attr("x",function(d,i){return xscale_wnull(xticks[i])}).attr("y",height-margin.bottom+axispos.xlabel).text(function(d){return d});ylabels=yaxis.append("g").attr("id","ylabels").selectAll("empty").data(yticklab).enter().append("text").attr("y",function(d,i){return yscale_wnull(yticks[i])}).attr("x",margin.left-axispos.ylabel).text(function(d){return d});boxes2include=function(){var results;results=[];for(i in boxes.left){if(boxes.width[i]>0&&boxes.height[i]>0){results.push(i)}}return results}();box=svg.append("g").attr("id","box");return box.selectAll("empty").data(boxes2include).enter().append("rect").attr("x",function(i){return boxes.left[i]}).attr("y",function(i){return boxes.top[i]}).attr("height",function(i){return boxes.height[i]}).attr("width",function(i){return boxes.width[i]}).attr("fill","none").attr("stroke",boxcolor).attr("stroke-width",boxwidth).attr("shape-rendering","crispEdges")};chart.xscale=function(){return xscale_wnull};chart.yscale=function(){return yscale_wnull};chart.xNA=function(){return xNA};chart.yNA=function(){return yNA};chart.xlines=function(){return xlines};chart.ylines=function(){return ylines};chart.xlabels=function(){return xlabels};chart.ylabels=function(){return ylabels};chart.plot_width=function(){return plot_width};chart.plot_height=function(){return plot_height};chart.width=function(){return width};chart.height=function(){return height};chart.margin=function(){return margin};chart.box=function(){return box};chart.svg=function(){return svg};chart.remove=function(){svg.remove();return null};return chart};"use strict";d3panels.chrpanelframe=function(chartOpts){var altrectcolor,axispos,box,boxcolor,boxwidth,chart,chrGap,chrSelect,chrlinecolor,chrlines,chrlinewidth,height,horizontal,margin,nyticks,rectcolor,ref,ref1,ref10,ref11,ref12,ref13,ref14,ref15,ref16,ref17,ref18,ref19,ref2,ref20,ref21,ref22,ref3,ref4,ref5,ref6,ref7,ref8,ref9,rotate_ylab,svg,title,titlepos,width,xlab,xlabels,xlineOpts,xscale,ylab,ylabels,ylim,ylineOpts,yscale,yticklab,yticks;if(chartOpts==null){chartOpts={}}width=(ref=chartOpts!=null?chartOpts.width:void 0)!=null?ref:800;height=(ref1=chartOpts!=null?chartOpts.height:void 0)!=null?ref1:500;margin=(ref2=chartOpts!=null?chartOpts.margin:void 0)!=null?ref2:{left:60,top:40,right:40,bottom:40};axispos=(ref3=chartOpts!=null?chartOpts.axispos:void 0)!=null?ref3:{xtitle:25,ytitle:45,xlabel:5,ylabel:5};titlepos=(ref4=chartOpts!=null?chartOpts.titlepos:void 0)!=null?ref4:20;title=(ref5=chartOpts!=null?chartOpts.title:void 0)!=null?ref5:"";xlab=(ref6=chartOpts!=null?chartOpts.xlab:void 0)!=null?ref6:null;ylab=(ref7=chartOpts!=null?chartOpts.ylab:void 0)!=null?ref7:"LOD score";rotate_ylab=(ref8=chartOpts!=null?chartOpts.rotate_ylab:void 0)!=null?ref8:null;ylim=(ref9=chartOpts!=null?chartOpts.ylim:void 0)!=null?ref9:[0,1];nyticks=(ref10=chartOpts!=null?chartOpts.nyticks:void 0)!=null?ref10:5;yticks=(ref11=chartOpts!=null?chartOpts.yticks:void 0)!=null?ref11:null;yticklab=(ref12=chartOpts!=null?chartOpts.yticklab:void 0)!=null?ref12:null;rectcolor=(ref13=chartOpts!=null?chartOpts.rectcolor:void 0)!=null?ref13:"#e6e6e6";altrectcolor=(ref14=chartOpts!=null?chartOpts.altrectcolor:void 0)!=null?ref14:"#d4d4d4";chrlinecolor=(ref15=chartOpts!=null?chartOpts.chrlinecolor:void 0)!=null?ref15:"";chrlinewidth=(ref16=chartOpts!=null?chartOpts.chrlinewidth:void 0)!=null?ref16:2;boxcolor=(ref17=chartOpts!=null?chartOpts.boxcolor:void 0)!=null?ref17:"black";boxwidth=(ref18=chartOpts!=null?chartOpts.boxwidth:void 0)!=null?ref18:2;xlineOpts=(ref19=chartOpts!=null?chartOpts.xlineOpts:void 0)!=null?ref19:{color:"#d4d4d4",width:2};ylineOpts=(ref20=chartOpts!=null?chartOpts.ylineOpts:void 0)!=null?ref20:{color:"white",width:2};chrGap=(ref21=chartOpts!=null?chartOpts.chrGap:void 0)!=null?ref21:6;horizontal=(ref22=chartOpts.horizontal)!=null?ref22:false;xscale=null;yscale=null;xlabels=null;ylabels=null;chrSelect=null;chrlines=null;box=null;svg=null;chart=function chart(selection,data){var c,d,g,plot_height,plot_width,thechr,xaxis,xlines,xticks,yaxis,ylabpos_x,ylabpos_y,ylines;margin=d3panels.check_listarg_v_default(margin,{left:60,top:40,right:40,bottom:60});axispos=d3panels.check_listarg_v_default(axispos,{xtitle:25,ytitle:45,xlabel:5,ylabel:5});xlineOpts=d3panels.check_listarg_v_default(xlineOpts,{color:"white",width:2});ylineOpts=d3panels.check_listarg_v_default(ylineOpts,{color:"white",width:2});if(data.chr==null){d3panels.displayError("chrpanelframe: data.chr is missing")}if(data.end==null){d3panels.displayError("chrpanelframe: data.end is missing")}if(xlab==null){xlab=data.chr.length===1?"Position":"Chromosome"}svg=selection.append("svg");svg.attr("width",width).attr("height",height).attr("class","d3panels");g=svg.append("g").attr("id","frame");plot_width=width-(margin.left+margin.right);plot_height=height-(margin.top+margin.bottom);if(!(data!=null?data.start:void 0)){data.start=function(){var j,len,ref23,results;ref23=data.chr;results=[];for(j=0,len=ref23.length;j<len;j++){c=ref23[j];results.push(0)}return results}()}if(data.chr.length!==data.start.length){d3panels.displayError("chrpanelframe: data.start.length ("+data.start.length+") != data.chr.length ("+data.chr.length+")")}if(data.chr.length!==data.end.length){d3panels.displayError("chrpanelframe: data.end.length ("+data.end.length+") != data.chr.length ("+data.chr.length+")")}if(horizontal){xscale=d3panels.calc_chrscales(plot_height,margin.top,chrGap,data.chr,data.start,data.end);yscale=d3.scaleLinear().domain(ylim.reverse()).range([plot_width+margin.left,margin.left])}else{xscale=d3panels.calc_chrscales(plot_width,margin.left,chrGap,data.chr,data.start,data.end);yscale=d3.scaleLinear().domain(ylim).range([plot_height+margin.top,margin.top])}g.append("rect").attr("x",margin.left).attr("width",plot_width).attr("y",margin.top).attr("height",plot_height).attr("fill",rectcolor).attr("shape-rendering","crispEdges");if(altrectcolor!==""){chrSelect=g.append("g").selectAll("empty").data(data.chr).enter().append("rect").attr("x",function(d,i){if(horizontal){return margin.left}return xscale[d](data.start[i])-chrGap/2}).attr("width",function(d,i){if(horizontal){return plot_width}return xscale[d](data.end[i])-xscale[d](data.start[i])+chrGap}).attr("y",function(d,i){if(horizontal){return xscale[d](data.start[i])-chrGap/2}return margin.top}).attr("height",function(d,i){if(horizontal){return xscale[d](data.end[i])-xscale[d](data.start[i])+chrGap}return plot_height}).attr("fill",function(d,i){if(i%2===0){return rectcolor}return altrectcolor}).attr("shape-rendering","crispEdges")}g.append("g").attr("class","title").append("text").text(title).attr("x",(width-margin.left-margin.right)/2+margin.left).attr("y",titlepos);if(horizontal){rotate_ylab=rotate_ylab!=null?rotate_ylab:xlab.length>1}else{rotate_ylab=rotate_ylab!=null?rotate_ylab:ylab.length>1}xaxis=g.append("g").attr("class",function(){if(horizontal){return"y axis"}return"x axis"});yaxis=g.append("g").attr("class",function(){if(horizontal){return"x axis"}return"y axis"});xaxis.append("text").attr("class","title").text(function(){if(horizontal){return ylab}return xlab}).attr("x",(width-margin.left-margin.right)/2+margin.left).attr("y",plot_height+margin.top+axispos.xtitle);ylabpos_y=(height-margin.top-margin.bottom)/2+margin.top;ylabpos_x=margin.left-axispos.ytitle;yaxis.append("text").attr("class","title").text(function(){if(horizontal){return xlab}return ylab}).attr("y",ylabpos_y).attr("x",ylabpos_x).attr("transform",rotate_ylab?"rotate(270,"+ylabpos_x+","+ylabpos_y+")":"");if(data.chr.length>1){xlabels=xaxis.append("g").attr("id","xlabels").selectAll("empty").data(data.chr).enter().append("text").attr("x",function(d,i){if(horizontal){return margin.left-axispos.ylabel}return(xscale[d](data.start[i])+xscale[d](data.end[i]))/2}).attr("y",function(d,i){if(horizontal){return(xscale[d](data.start[i])+xscale[d](data.end[i]))/2}return height-margin.bottom+axispos.xlabel}).text(function(d){return d})}else{thechr=data.chr[0];xticks=xscale[thechr].ticks(5);xlabels=xaxis.append("g").attr("id","xlabels").selectAll("empty").data(xticks).enter().append("text").attr("x",function(d){if(horizontal){return margin.left-axispos.ylabel}return xscale[thechr](d)}).attr("y",function(d,i){if(horizontal){return xscale[thechr](d)}return height-margin.bottom+axispos.xlabel}).text(function(d){return d});xlines=xaxis.append("g").attr("id","xlines").selectAll("empty").data(xticks).enter().append("line").attr("x1",function(d){if(horizontal){return margin.left}return xscale[thechr](d)}).attr("x2",function(d){if(horizontal){return margin.left+plot_width}return xscale[thechr](d)}).attr("y1",function(d,i){if(horizontal){return xscale[thechr](d)}return margin.top}).attr("y2",function(d,i){if(horizontal){return xscale[thechr](d)}return plot_height+margin.top}).attr("fill","none").attr("stroke",xlineOpts.color).attr("stroke-width",xlineOpts.width).attr("shape-rendering","crispEdges").style("pointer-events","none")}yticks=yticks!=null?yticks:yscale.ticks(nyticks);if(yticklab!=null&&yticklab.length!==yticks.length){displayError("chrpanelframe: yticklab.length ("+yticklab.length+") != yticks.length ("+yticks.length+")")}if(!(yticklab!=null&&yticklab.length===yticks.length)){yticklab=function(){var j,len,results;results=[];for(j=0,len=yticks.length;j<len;j++){d=yticks[j];results.push(d3panels.formatAxis(yticks)(d))}return results}()}ylines=yaxis.append("g").attr("id","ylines").selectAll("empty").data(yticks.concat(yticks)).enter().append("line").attr("y1",function(d){if(horizontal){return margin.top}return yscale(d)}).attr("y2",function(d){if(horizontal){return margin.top+plot_height}return yscale(d)}).attr("x1",function(d,i){if(horizontal){return yscale(d)}return margin.left}).attr("x2",function(d,i){if(horizontal){return yscale(d)}return plot_width+margin.left}).attr("fill","none").attr("stroke",ylineOpts.color).attr("stroke-width",ylineOpts.width).attr("shape-rendering","crispEdges").style("pointer-events","none");ylabels=yaxis.append("g").attr("id","ylabels").selectAll("empty").data(yticklab).enter().append("text").attr("y",function(d,i){if(horizontal){return height-margin.bottom+axispos.xlabel}return yscale(yticks[i])}).attr("x",function(d,i){if(horizontal){return yscale(yticks[i])}return margin.left-axispos.ylabel}).text(function(d){return d});if(chrlinecolor!==""&&data.chr.length>1){chrlines=svg.append("g").attr("id","chrlines");chrlines.selectAll("empty").data(data.chr.slice(0,+(data.chr.length-2)+1||9e9)).enter().append("line").attr("x1",function(d,i){if(horizontal){return margin.left}return xscale[d](data.end[i])+chrGap/2}).attr("x2",function(d,i){if(horizontal){return margin.left+plot_width}return xscale[d](data.end[i])+chrGap/2}).attr("y1",function(d,i){if(horizontal){return xscale[d](data.end[i])+chrGap/2}return margin.top}).attr("y2",function(d,i){if(horizontal){return xscale[d](data.end[i])+chrGap/2}return margin.top+plot_height}).attr("stroke",chrlinecolor).attr("stroke-width",chrlinewidth).attr("shape-rendering","crispEdges")}return box=svg.append("rect").attr("class","box").attr("x",margin.left).attr("y",margin.top).attr("height",plot_height).attr("width",plot_width).attr("fill","none").attr("stroke",boxcolor).attr("stroke-width",boxwidth).attr("shape-rendering","crispEdges")};chart.xscale=function(){return xscale};chart.yscale=function(){return yscale};chart.xlabels=function(){return xlabels};chart.ylabels=function(){return ylabels};chart.chrSelect=function(){return chrSelect};chart.chrlines=function(){return chrlines};chart.plot_width=function(){return plot_width};chart.plot_height=function(){return plot_height};chart.width=function(){return width};chart.height=function(){return height};chart.margin=function(){return margin};chart.box=function(){return box};chart.svg=function(){return svg};chart.remove=function(){svg.remove();return null};return chart};"use strict";d3panels.chr2dpanelframe=function(chartOpts){var altrectcolor,axispos,box,boxcolor,boxwidth,chart,chrGap,chrSelect,chrlinecolor,chrlines,chrlinewidth,height,margin,oneAtTop,rectcolor,ref,ref1,ref10,ref11,ref12,ref13,ref14,ref15,ref16,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,rotate_ylab,svg,title,titlepos,width,xlab,xlabels,xscale,ylab,ylabels,yscale;if(chartOpts==null){chartOpts={}}width=(ref=chartOpts!=null?chartOpts.width:void 0)!=null?ref:800;height=(ref1=chartOpts!=null?chartOpts.height:void 0)!=null?ref1:800;margin=(ref2=chartOpts!=null?chartOpts.margin:void 0)!=null?ref2:{left:60,top:40,right:40,bottom:60};axispos=(ref3=chartOpts!=null?chartOpts.axispos:void 0)!=null?ref3:{xtitle:25,ytitle:45,xlabel:5,ylabel:5};titlepos=(ref4=chartOpts!=null?chartOpts.titlepos:void 0)!=null?ref4:20;title=(ref5=chartOpts!=null?chartOpts.title:void 0)!=null?ref5:"";xlab=(ref6=chartOpts!=null?chartOpts.xlab:void 0)!=null?ref6:"Chromosome";ylab=(ref7=chartOpts!=null?chartOpts.ylab:void 0)!=null?ref7:"Chromosome";rotate_ylab=(ref8=chartOpts!=null?chartOpts.rotate_ylab:void 0)!=null?ref8:null;rectcolor=(ref9=chartOpts!=null?chartOpts.rectcolor:void 0)!=null?ref9:"#e6e6e6";altrectcolor=(ref10=chartOpts!=null?chartOpts.altrectcolor:void 0)!=null?ref10:"#d4d4d4";chrlinecolor=(ref11=chartOpts!=null?chartOpts.chrlinecolor:void 0)!=null?ref11:"";chrlinewidth=(ref12=chartOpts!=null?chartOpts.chrlinewidth:void 0)!=null?ref12:2;boxcolor=(ref13=chartOpts!=null?chartOpts.boxcolor:void 0)!=null?ref13:"black";boxwidth=(ref14=chartOpts!=null?chartOpts.boxwidth:void 0)!=null?ref14:2;chrGap=(ref15=chartOpts!=null?chartOpts.chrGap:void 0)!=null?ref15:6;oneAtTop=(ref16=chartOpts!=null?chartOpts.oneAtTop:void 0)!=null?ref16:false;xscale=null;yscale=null;xlabels=null;ylabels=null;chrSelect=null;chrlines=null;box=null;svg=null;chart=function chart(selection,data){var c,chrRect,chrx,chry,g,j,k,len,len1,plot_height,plot_width,ref17,ref18,x,xaxis,y,yaxis,ylabpos_x,ylabpos_y;margin=d3panels.check_listarg_v_default(margin,{left:60,top:40,right:40,bottom:60});axispos=d3panels.check_listarg_v_default(axispos,{xtitle:25,ytitle:45,xlabel:5,ylabel:5});if(data.chr==null){d3panels.displayError("chr2dpanelframe: data.chr is missing")}if(data.end==null){d3panels.displayError("chr2dpanelframe: data.end is missing")}svg=selection.append("svg");svg.attr("width",width).attr("height",height).attr("class","d3panels");g=svg.append("g").attr("id","frame");plot_width=width-(margin.left+margin.right);plot_height=height-(margin.top+margin.bottom);if(!(data!=null?data.start:void 0)){data.start=function(){var j,len,ref17,results;ref17=data.chr;results=[];for(j=0,len=ref17.length;j<len;j++){c=ref17[j];results.push(0)}return results}()}if(data.chr.length!==data.start.length){d3panels.displayError("chr2dpanelframe: data.start.length ("+data.start.length+") != data.chr.length ("+data.chr.length+")")}if(data.chr.length!==data.end.length){d3panels.displayError("chr2dpanelframe: data.end.length ("+data.end.length+") != data.chr.length ("+data.chr.length+")")}xscale=d3panels.calc_chrscales(plot_width,margin.left,chrGap,data.chr,data.start,data.end);yscale=d3panels.calc_chrscales(plot_height,margin.top,chrGap,data.chr,data.start,data.end,!oneAtTop);g.append("rect").attr("x",margin.left).attr("width",plot_width).attr("y",margin.top).attr("height",plot_height).attr("fill",rectcolor).attr("shape-rendering","crispEdges");chrRect=[];ref17=data.chr;for(x=j=0,len=ref17.length;j<len;x=++j){chrx=ref17[x];ref18=data.chr;for(y=k=0,len1=ref18.length;k<len1;y=++k){chry=ref18[y];chrRect.push({chrx:chrx,xi:x,chry:chry,yi:y,odd:(x+y)%2})}}if(altrectcolor!==""){chrSelect=g.append("g").selectAll("empty").data(chrRect).enter().append("rect").attr("x",function(d){return xscale[d.chrx](data.start[d.xi])-chrGap/2}).attr("width",function(d){return xscale[d.chrx](data.end[d.xi])-xscale[d.chrx](data.start[d.xi])+chrGap}).attr("y",function(d){if(oneAtTop){return yscale[d.chry](data.start[d.yi])-chrGap/2}return yscale[d.chry](data.end[d.yi])-chrGap/2}).attr("height",function(d){if(oneAtTop){return yscale[d.chry](data.end[d.yi])-yscale[d.chry](data.start[d.yi])+chrGap}return yscale[d.chry](data.start[d.yi])-yscale[d.chry](data.end[d.yi])+chrGap}).attr("fill",function(d,i){if(!d.odd){return rectcolor}return altrectcolor}).attr("shape-rendering","crispEdges")}g.append("g").attr("class","title").append("text").text(title).attr("x",(width-margin.left-margin.right)/2+margin.left).attr("y",titlepos);rotate_ylab=rotate_ylab!=null?rotate_ylab:ylab.length>1;xaxis=g.append("g").attr("class","x axis");yaxis=g.append("g").attr("class","y axis");xaxis.append("text").attr("class","title").text(function(){return xlab}).attr("x",(width-margin.left-margin.right)/2+margin.left).attr("y",plot_height+margin.top+axispos.xtitle);ylabpos_y=(height-margin.top-margin.bottom)/2+margin.top;ylabpos_x=margin.left-axispos.ytitle;yaxis.append("text").attr("class","title").text(function(){return ylab}).attr("y",ylabpos_y).attr("x",ylabpos_x).attr("transform",rotate_ylab?"rotate(270,"+ylabpos_x+","+ylabpos_y+")":"");xlabels=xaxis.append("g").attr("id","xlabels").selectAll("empty").data(data.chr).enter().append("text").attr("x",function(d,i){return(xscale[d](data.start[i])+xscale[d](data.end[i]))/2}).attr("y",height-margin.bottom+axispos.xlabel).text(function(d){return d});ylabels=yaxis.append("g").attr("id","ylabels").selectAll("empty").data(data.chr).enter().append("text").attr("y",function(d,i){return(yscale[d](data.start[i])+yscale[d](data.end[i]))/2}).attr("x",margin.left-axispos.ylabel).text(function(d){return d});if(chrlinecolor!==""&&data.chr.length>1){chrlines=svg.append("g").attr("id","chrlines");chrlines.selectAll("empty").data(data.chr.slice(0,+(data.chr.length-2)+1||9e9)).enter().append("line").attr("x1",function(d,i){return xscale[d](data.end[i])+chrGap/2}).attr("x2",function(d,i){return xscale[d](data.end[i])+chrGap/2}).attr("y1",margin.top).attr("y2",margin.top+plot_height).attr("stroke",chrlinecolor).attr("stroke-width",chrlinewidth).attr("shape-rendering","crispEdges");chrlines.selectAll("empty").data(data.chr.slice(0,+(data.chr.length-2)+1||9e9)).enter().append("line").attr("y1",function(d,i){if(oneAtTop){return yscale[d](data.end[i])+chrGap/2}return yscale[d](data.end[i])-chrGap/2}).attr("y2",function(d,i){if(oneAtTop){return yscale[d](data.end[i])+chrGap/2}return yscale[d](data.end[i])-chrGap/2}).attr("x1",margin.left).attr("x2",margin.left+plot_width).attr("stroke",chrlinecolor).attr("stroke-width",chrlinewidth).attr("shape-rendering","crispEdges")}return box=svg.append("rect").attr("class","box").attr("x",margin.left).attr("y",margin.top).attr("height",plot_height).attr("width",plot_width).attr("fill","none").attr("stroke",boxcolor).attr("stroke-width",boxwidth).attr("shape-rendering","crispEdges")};chart.xscale=function(){return xscale};chart.yscale=function(){return yscale};chart.xlabels=function(){return xlabels};chart.ylabels=function(){return ylabels};chart.chrSelect=function(){return chrSelect};chart.chrlines=function(){return chrlines};chart.plot_width=function(){return plot_width};chart.plot_height=function(){return plot_height};chart.width=function(){return width};chart.height=function(){return height};chart.margin=function(){return margin};chart.box=function(){return box};chart.svg=function(){return svg};chart.remove=function(){svg.remove();return null};return chart};"use strict";d3panels.cichart=function(chartOpts){var chart,horizontal,ref,ref1,ref10,ref11,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,segcolor,segments,segstrokewidth,segwidth,svg,tip,tipclass,v_over_h,vertsegcolor,xcatlabels,xlab,xlineOpts,xscale,ylab,ylim,yscale;if(chartOpts==null){chartOpts={}}xcatlabels=(ref=chartOpts!=null?chartOpts.xcatlabels:void 0)!=null?ref:null;segwidth=(ref1=chartOpts!=null?chartOpts.segwidth:void 0)!=null?ref1:.4;segcolor=(ref2=chartOpts!=null?chartOpts.segcolor:void 0)!=null?ref2:"slateblue";segstrokewidth=(ref3=chartOpts!=null?chartOpts.segstrokewidth:void 0)!=null?ref3:"3";vertsegcolor=(ref4=chartOpts!=null?chartOpts.vertsegcolor:void 0)!=null?ref4:"slateblue";xlab=(ref5=chartOpts!=null?chartOpts.xlab:void 0)!=null?ref5:"Group";ylab=(ref6=chartOpts!=null?chartOpts.ylab:void 0)!=null?ref6:"Response";ylim=(ref7=chartOpts!=null?chartOpts.ylim:void 0)!=null?ref7:null;xlineOpts=(ref8=chartOpts!=null?chartOpts.xlineOpts:void 0)!=null?ref8:{color:"#CDCDCD",width:5};horizontal=(ref9=chartOpts!=null?chartOpts.horizontal:void 0)!=null?ref9:false;v_over_h=(ref10=chartOpts!=null?chartOpts.v_over_h:void 0)!=null?ref10:horizontal;tipclass=(ref11=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref11:"tooltip";xscale=null;yscale=null;segments=null;tip=null;svg=null;chart=function chart(selection,data){var direction,high,i,low,mean,myframe,ncat,segmentGroup,tipfunc,xlim,xticks,xval,yval;xlineOpts=d3panels.check_listarg_v_default(xlineOpts,{color:"#CDCDCD",width:5});if(data.mean==null){d3panels.displayError("cichart: data.mean is missing")}if(data.low==null){d3panels.displayError("cichart: data.low is missing")}if(data.high==null){d3panels.displayError("cichart: data.high is missing")}mean=data.mean;low=data.low;high=data.high;ncat=mean.length;if(ncat!==low.length){d3panels.displayError("cichart: low.length ["+low.length+"] != mean.length ["+ncat+"]")}if(ncat!==high.length){d3panels.displayError("cichart: high.length ["+high.length+"] != mean.length ["+ncat+"]")}xticks=function(){var results;results=[];for(i in mean){results.push(+i+1)}return results}();xcatlabels=xcatlabels!=null?xcatlabels:xticks;if(xcatlabels.length!==mean.length){d3panels.displayError("cichart: xcatlabels.length ["+xcatlabels.length+"] != mean.length ["+ncat+"]")}ylim=ylim!=null?ylim:d3panels.pad_ylim(d3.extent(low.concat(high)));xlim=[.5,mean.length+.5];segcolor=d3panels.expand2vector(d3panels.forceAsArray(segcolor),mean.length);vertsegcolor=d3panels.expand2vector(d3panels.forceAsArray(vertsegcolor),mean.length);if(horizontal){chartOpts.ylim=xlim.reverse();chartOpts.xlim=ylim;chartOpts.xlab=ylab;chartOpts.ylab=xlab;chartOpts.xlineOpts=chartOpts.ylineOpts;chartOpts.ylineOpts=xlineOpts;chartOpts.yNA=chartOpts.xNA;chartOpts.xNA=chartOpts.yNA;chartOpts.yticks=xticks;chartOpts.yticklab=xcatlabels;chartOpts.v_over_h=v_over_h}else{chartOpts.ylim=ylim;chartOpts.xlim=xlim;chartOpts.xlab=xlab;chartOpts.ylab=ylab;chartOpts.ylineOpts=chartOpts.ylineOpts;chartOpts.xlineOpts=xlineOpts;chartOpts.xticks=xticks;chartOpts.xticklab=xcatlabels;chartOpts.v_over_h=v_over_h}myframe=d3panels.panelframe(chartOpts);myframe(selection);svg=myframe.svg();xscale=myframe.xscale();yscale=myframe.yscale();segmentGroup=svg.append("g").attr("id","segments");segments=segmentGroup.selectAll("empty").data(low).enter().append("line").attr("x1",function(d,i){if(!horizontal){return xscale(i+1)}return xscale(d)}).attr("x2",function(d,i){if(!horizontal){return xscale(i+1)}return xscale(high[i])}).attr("y1",function(d,i){if(!horizontal){return yscale(d)}return yscale(i+1)}).attr("y2",function(d,i){if(!horizontal){return yscale(high[i])}return yscale(i+1)}).attr("fill","none").attr("stroke",function(d,i){return vertsegcolor[i]}).attr("stroke-width",segstrokewidth).attr("shape-rendering","crispEdges");yval=mean.concat(low,high);xval=function(){var results;results=[];for(i in yval){results.push(+(i%ncat)+1)}return results}();segments=segmentGroup.selectAll("empty").data(yval).enter().append("line").attr("x1",function(d,i){if(horizontal){return xscale(d)}else{if(i<ncat){return xscale(xval[i]-segwidth/2)}return xscale(xval[i]-segwidth/3)}}).attr("x2",function(d,i){if(horizontal){return xscale(d)}else{if(i<ncat){return xscale(xval[i]+segwidth/2)}return xscale(xval[i]+segwidth/3)}}).attr("y1",function(d,i){if(horizontal){if(i<ncat){return yscale(xval[i]-segwidth/2)}return yscale(xval[i]-segwidth/3)}else{return yscale(d)}}).attr("y2",function(d,i){if(horizontal){if(i<ncat){return yscale(xval[i]+segwidth/2)}return yscale(xval[i]+segwidth/3)}else{return yscale(d)}}).attr("fill","none").attr("stroke",function(d,i){return segcolor[i%mean.length]}).attr("stroke-width",segstrokewidth).attr("shape-rendering","crispEdges");direction=horizontal?"north":"east";tipfunc=function tipfunc(d,i){var f,index;index=i%mean.length;f=d3panels.formatAxis([low[index],mean[index]],1);return f(mean[index])+" ("+f(low[index])+" - "+f(high[index])+")"};tip=d3panels.tooltip_create(d3.select("body"),segments,{direction:direction,tipclass:tipclass},tipfunc);return myframe.box().raise()};chart.yscale=function(){return yscale};chart.xscale=function(){return xscale};chart.segments=function(){return segments};chart.tip=function(){return tip};chart.svg=function(){return svg};chart.remove=function(){svg.remove();d3panels.tooltip_destroy(tip);return null};return chart};"use strict";d3panels.crosstab=function(chartOpts){var bordercolor,cellPad,chart,colrect,fontsize,height,hilitcolor,margin,rectcolor,ref,ref1,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,rowrect,svg,title,titlepos,width;if(chartOpts==null){chartOpts={}}width=(ref=chartOpts!=null?chartOpts.width:void 0)!=null?ref:600;height=(ref1=chartOpts!=null?chartOpts.height:void 0)!=null?ref1:300;margin=(ref2=chartOpts!=null?chartOpts.margin:void 0)!=null?ref2:{left:60,top:80,right:40,bottom:20};cellPad=(ref3=chartOpts!=null?chartOpts.cellPad:void 0)!=null?ref3:null;titlepos=(ref4=chartOpts!=null?chartOpts.titlepos:void 0)!=null?ref4:50;title=(ref5=chartOpts!=null?chartOpts.title:void 0)!=null?ref5:"";fontsize=(ref6=chartOpts!=null?chartOpts.fontsize:void 0)!=null?ref6:null;rectcolor=(ref7=chartOpts!=null?chartOpts.rectcolor:void 0)!=null?ref7:"#e6e6e6";hilitcolor=(ref8=chartOpts!=null?chartOpts.hilitcolor:void 0)!=null?ref8:"#e9cfec";bordercolor=(ref9=chartOpts!=null?chartOpts.bordercolor:void 0)!=null?ref9:"black";rowrect=null;colrect=null;svg=null;chart=function chart(selection,data){var borders,cell,cellHeight,cellWidth,cells,collab,denom,i,j,k,l,n,ncol,nrow,plot_height,plot_width,rect,ref10,ref11,ref12,ref13,ref14,ref15,ref16,ref17,rowlab,tab,titles,values,xscale,xv,yscale,yv;margin=d3panels.check_listarg_v_default(margin,{left:60,top:40,right:40,bottom:60});if(data.x==null){d3panels.displayError("crosstab: data.x is missing")}if(data.y==null){d3panels.displayError("crosstab: data.y is missing")}n=data.x.length;if(data.y.length!==n){d3panels.displayError("crosstab: data.x.length ["+data.x.length+"] != data.y.length ["+data.y.length+"]")}data.xcat=(ref10=data!=null?data.xcat:void 0)!=null?ref10:function(){var k,len,ref11,results;ref11=d3.range(d3.max(data.x));results=[];for(k=0,len=ref11.length;k<len;k++){xv=ref11[k];results.push(xv+1)}return results}();data.ycat=(ref11=data!=null?data.ycat:void 0)!=null?ref11:function(){var k,len,ref12,results;ref12=d3.range(d3.max(data.y));results=[];for(k=0,len=ref12.length;k<len;k++){yv=ref12[k];results.push(yv+1)}return results}();ncol=data.xcat.length;if(d3.max(data.x)>ncol||d3.min(data.x)<=0){d3panels.displayError("crosstab: data.x should be in range 1-"+ncol+" [was "+d3.min(data.x)+" - "+d3.max(data.x)+"]")}nrow=data.ycat.length;if(d3.max(data.y)>nrow||d3.min(data.y)<=0){d3panels.displayError("crosstab: data.y should be in range 1-"+nrow+" [was "+d3.min(data.y)+" - "+d3.max(data.y)+"]")}data.x=function(){var k,len,ref12,results;ref12=data.x;results=[];for(k=0,len=ref12.length;k<len;k++){xv=ref12[k];results.push(xv-1)}return results}();data.y=function(){var k,len,ref12,results;ref12=data.y;results=[];for(k=0,len=ref12.length;k<len;k++){yv=ref12[k];results.push(yv-1)}return results}();tab=d3panels.calc_crosstab(data);data.xlabel=(ref12=data!=null?data.xlabel:void 0)!=null?ref12:"";data.ylabel=(ref13=data!=null?data.ylabel:void 0)!=null?ref13:"";cells=[];for(i=k=0,ref14=nrow;0<=ref14?k<=ref14:k>=ref14;i=0<=ref14?++k:--k){for(j=l=0,ref15=ncol;0<=ref15?l<=ref15:l>=ref15;j=0<=ref15?++l:--l){cell={value:tab[i][j],row:i,col:j,shaded:false,rowpercent:"",colpercent:""};if(i<nrow-1&&(j<ncol-1||j===ncol)){cell.shaded=true}if(j<ncol-1&&(i<nrow-1||i===nrow)){cell.shaded=true}if(i<nrow-1){denom=tab[nrow][j]-tab[nrow-1][j];cell.colpercent=denom>0?Math.round(100*tab[i][j]/denom)+"%":"—"}else if(i===nrow-1){denom=tab[nrow][j];cell.colpercent=denom>0?"("+Math.round(100*tab[i][j]/denom)+"%)":"—"}else{cell.colpercent=cell.value}if(j<ncol-1){denom=tab[i][ncol]-tab[i][ncol-1];cell.rowpercent=denom>0?Math.round(100*tab[i][j]/denom)+"%":"—"}else if(j===ncol-1){denom=tab[i][ncol];cell.rowpercent=denom>0?"("+Math.round(100*tab[i][j]/denom)+"%)":"—"}else{cell.rowpercent=cell.value}cells.push(cell)}}plot_width=width-margin.left-margin.right;plot_height=height-margin.top-margin.bottom;cellWidth=plot_width/(ncol+2);cellHeight=plot_height/(nrow+2);fontsize=fontsize!=null?fontsize:cellHeight*.5;cellPad=cellPad!=null?cellPad:cellWidth*.1;xscale=d3.scaleBand().domain(function(){var results=[];for(var m=0,ref16=ncol+1;0<=ref16?m<=ref16:m>=ref16;0<=ref16?m++:m--){results.push(m)}return results}.apply(this)).range([margin.left,width-margin.right]);yscale=d3.scaleBand().domain(function(){var results=[];for(var m=0,ref17=nrow+1;0<=ref17?m<=ref17:m>=ref17;0<=ref17?m++:m--){results.push(m)}return results}.apply(this)).range([margin.top,height-margin.bottom]);svg=selection.append("svg").attr("width",width).attr("height",height).attr("class","d3panels");rect=svg.append("g").attr("id","value_rect");rect.selectAll("empty").data(cells).enter().append("rect").attr("x",function(d){return xscale(d.col+1)}).attr("y",function(d){return yscale(d.row+1)}).attr("width",cellWidth).attr("height",cellHeight).attr("fill",function(d){if(d.shaded){return rectcolor}else{return"none"}}).attr("stroke",function(d){if(d.shaded){return rectcolor}else{return"none"}}).attr("stroke-width",0).style("pointer-events","none").attr("shape-rendering","crispEdges");values=svg.append("g").attr("id","values");values.selectAll("empty").data(cells).enter().append("text").attr("x",function(d){return xscale(d.col+1)+cellWidth-cellPad}).attr("y",function(d){return yscale(d.row+1)+cellHeight/2}).text(function(d){return d.value}).attr("class",function(d){return"crosstab row"+d.row+" col"+d.col}).style("font-size",fontsize).style("pointer-events","none");colrect=svg.append("g").attr("id","colrect");colrect.selectAll("empty").data(data.xcat.concat("Total")).enter().append("rect").attr("x",function(d,i){return xscale(i+1)}).attr("y",yscale(0)).attr("width",cellWidth).attr("height",cellHeight).attr("fill","white").attr("stroke","white").attr("shape-rendering","crispEdges").on("mouseover",function(d,i){d3.select(this).attr("fill",hilitcolor).attr("stroke",hilitcolor);return values.selectAll(".col"+i).text(function(d){return d.colpercent})}).on("mouseout",function(d,i){d3.select(this).attr("fill","white").attr("stroke","white");return values.selectAll("text.col"+i).text(function(d){return d.value})});collab=svg.append("g").attr("id","collab");collab.selectAll("empty").data(data.xcat.concat("Total")).enter().append("text").attr("x",function(d,i){return xscale(i+1)+cellWidth-cellPad}).attr("y",yscale(0)+cellHeight/2).text(function(d){return d}).attr("class","crosstab").style("font-size",fontsize).style("pointer-events","none");rowrect=svg.append("g").attr("id","rowrect");rowrect.selectAll("empty").data(data.ycat.concat("Total")).enter().append("rect").attr("x",xscale(0)).attr("y",function(d,i){return yscale(i+1)}).attr("width",cellWidth).attr("height",cellHeight).attr("fill","white").attr("stroke","white").attr("shape-rendering","crispEdges").on("mouseover",function(d,i){d3.select(this).attr("fill",hilitcolor).attr("stroke",hilitcolor);return values.selectAll(".row"+i).text(function(d){return d.rowpercent})}).on("mouseout",function(d,i){d3.select(this).attr("fill","white").attr("stroke","white");return values.selectAll(".row"+i).text(function(d){return d.value})});rowlab=svg.append("g").attr("id","rowlab");rowlab.selectAll("empty").data(data.ycat.concat("Total")).enter().append("text").attr("x",xscale(0)+cellWidth-cellPad).attr("y",function(d,i){return yscale(i+1)+cellHeight/2}).text(function(d){return d}).attr("class","crosstab").style("font-size",fontsize).style("pointer-events","none");borders=svg.append("g").attr("id","borders");borders.append("rect").attr("x",xscale(1)).attr("y",yscale(1)).attr("width",cellWidth*ncol).attr("height",cellHeight*nrow).attr("fill","none").attr("stroke",bordercolor).attr("stroke-width",2).style("pointer-events","none").attr("shape-rendering","crispEdges");borders.append("rect").attr("x",xscale(ncol+1)).attr("y",yscale(nrow+1)).attr("width",cellWidth).attr("height",cellHeight).attr("fill","none").attr("stroke",bordercolor).attr("stroke-width",2).style("pointer-events","none").attr("shape-rendering","crispEdges");titles=svg.append("g").attr("id","titles");titles.append("text").attr("class","crosstabtitle").attr("x",margin.left+(ncol+1)*cellWidth/2).attr("y",margin.top-cellHeight/2).text(data.xlabel).style("font-size",fontsize).style("font-weight","bold");titles.append("text").attr("class","crosstab").attr("x",xscale(0)+cellWidth-cellPad).attr("y",yscale(0)+cellHeight/2).text(data.ylabel).style("font-size",fontsize).style("font-weight","bold");return titles.append("text").attr("class","crosstabtitle").attr("x",margin.left+(width-margin.left-margin.right)/2).attr("y",margin.top-titlepos).text(title).style("font-size",fontsize)};chart.rowrect=function(){return rowrect};chart.colrect=function(){return colrect};chart.svg=function(){return svg};chart.remove=function(){svg.remove();return null};return chart};"use strict";d3panels.curvechart=function(chartOpts){var chart,curves,indtip,linecolor,linecolorhilit,linewidth,linewidthhilit,ref,ref1,ref2,ref3,ref4,ref5,ref6,svg,tipclass,xlim,xscale,ylim,yscale;if(chartOpts==null){chartOpts={}}xlim=(ref=chartOpts!=null?chartOpts.xlim:void 0)!=null?ref:null;ylim=(ref1=chartOpts!=null?chartOpts.ylim:void 0)!=null?ref1:null;linecolor=(ref2=chartOpts!=null?chartOpts.linecolor:void 0)!=null?ref2:null;linecolorhilit=(ref3=chartOpts!=null?chartOpts.linecolorhilit:void 0)!=null?ref3:null;linewidth=(ref4=chartOpts!=null?chartOpts.linewidth:void 0)!=null?ref4:2;linewidthhilit=(ref5=chartOpts!=null?chartOpts.linewidthhilit:void 0)!=null?ref5:2;tipclass=(ref6=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref6:"tooltip";xscale=null;yscale=null;curves=null;indtip=null;svg=null;chart=function chart(selection,data){var add_curves,i,j,myframe,n_ind,ref7,x,y;if(data.x==null){d3panels.displayError("curvechart: data.x is missing")}if(data.y==null){d3panels.displayError("curvechart: data.y is missing")}x=data.x;y=data.y;n_ind=y.length;if(x.length===1&&y.length>1){for(j=i=2,ref7=n_ind;2<=ref7?i<=ref7:i>=ref7;j=2<=ref7?++i:--i){x.push(x[0])}}if(x.length!==n_ind){d3panels.displayError("curvechart: data.x.length ("+x.length+") != data.y.length ("+n_ind+")")}xlim=xlim!=null?xlim:d3panels.matrixExtent(x);ylim=ylim!=null?ylim:d3panels.matrixExtent(y);chartOpts.xlim=xlim;chartOpts.ylim=ylim;chartOpts.xNA=false;chartOpts.yNA=false;myframe=d3panels.panelframe(chartOpts);myframe(selection);svg=myframe.svg();xscale=myframe.xscale();yscale=myframe.yscale();add_curves=d3panels.add_curves({linecolor:linecolor,linecolorhilit:linecolorhilit,linewidth:linewidth,linewidthhilit:linewidthhilit,tipclass:tipclass});add_curves(myframe,data);curves=add_curves.curves();indtip=add_curves.indtip();return myframe.box().raise()};chart.xscale=function(){return xscale};chart.yscale=function(){return yscale};chart.curves=function(){return curves};chart.indtip=function(){return indtip};chart.svg=function(){return svg};chart.remove=function(){svg.remove();d3panels.tooltip_destroy(indtip);return null};return chart};"use strict";var indexOf=[].indexOf;d3panels.dotchart=function(chartOpts){var chart,horizontal,indtip,jitter,pointcolor,points,pointsize,pointstroke,ref,ref1,ref10,ref11,ref12,ref13,ref14,ref15,ref16,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,svg,tipclass,v_over_h,xNA,xNA_size,xcategories,xcatlabels,xlab,xlineOpts,xscale,yNA,yNA_size,ylab,ylim,yscale;if(chartOpts==null){chartOpts={}}xcategories=(ref=chartOpts!=null?chartOpts.xcategories:void 0)!=null?ref:null;xcatlabels=(ref1=chartOpts!=null?chartOpts.xcatlabels:void 0)!=null?ref1:null;xNA=(ref2=chartOpts!=null?chartOpts.xNA:void 0)!=null?ref2:{handle:true,force:false};yNA=(ref3=chartOpts!=null?chartOpts.yNA:void 0)!=null?ref3:{handle:true,force:false};xNA_size=(ref4=chartOpts!=null?chartOpts.xNA_size:void 0)!=null?ref4:{width:20,gap:10};yNA_size=(ref5=chartOpts!=null?chartOpts.yNA_size:void 0)!=null?ref5:{width:20,gap:10};ylim=(ref6=chartOpts!=null?chartOpts.ylim:void 0)!=null?ref6:null;xlab=(ref7=chartOpts!=null?chartOpts.xlab:void 0)!=null?ref7:"Group";ylab=(ref8=chartOpts!=null?chartOpts.ylab:void 0)!=null?ref8:"Response";xlineOpts=(ref9=chartOpts!=null?chartOpts.xlineOpts:void 0)!=null?ref9:{color:"#cdcdcd",width:5};pointcolor=(ref10=chartOpts!=null?chartOpts.pointcolor:void 0)!=null?ref10:null;pointstroke=(ref11=chartOpts!=null?chartOpts.pointstroke:void 0)!=null?ref11:"black";pointsize=(ref12=chartOpts!=null?chartOpts.pointsize:void 0)!=null?ref12:3;jitter=(ref13=chartOpts!=null?chartOpts.jitter:void 0)!=null?ref13:"beeswarm";tipclass=(ref14=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref14:"tooltip";horizontal=(ref15=chartOpts!=null?chartOpts.horizontal:void 0)!=null?ref15:false;v_over_h=(ref16=chartOpts!=null?chartOpts.v_over_h:void 0)!=null?ref16:horizontal;xscale=null;yscale=null;xNA=xNA;yNA=yNA;points=null;indtip=null;svg=null;chart=function chart(selection,data){var force,g,group,i,indID,jitter_width,myframe,ngroup,pointGroup,ref17,ref18,ref19,ref20,scaledPoints,u,x,xlim,xv,y;xNA=d3panels.check_listarg_v_default(xNA,{handle:true,force:false});yNA=d3panels.check_listarg_v_default(yNA,{handle:true,force:false});xNA_size=d3panels.check_listarg_v_default(xNA,{width:20,gap:10});yNA_size=d3panels.check_listarg_v_default(yNA,{width:20,gap:10});if(data.x==null){d3panels.displayError("dotchart: data.x is missing")}if(data.y==null){d3panels.displayError("dotchart: data.y is missing")}x=d3panels.missing2null(data.x);y=d3panels.missing2null(data.y);indID=(ref17=data!=null?data.indID:void 0)!=null?ref17:function(){var results=[];for(var j=1,ref18=x.length;1<=ref18?j<=ref18:j>=ref18;1<=ref18?j++:j--){results.push(j)}return results}.apply(this);if(x.length!==y.length){d3panels.displayError("dotchart: length(x) ["+x.length+"] != length(y) ["+y.length+"]")}if(indID.length!==x.length){d3panels.displayError("dotchart: length(indID) ["+indID.length+"] != length(x) ["+x.length+"]")}group=(ref19=data!=null?data.group:void 0)!=null?ref19:function(){var j,len,results;results=[];for(j=0,len=x.length;j<len;j++){i=x[j];results.push(1)}return results}();group=d3panels.expand2vector(group,x.length);ngroup=d3.max(group);group=function(){var j,len,results;results=[];for(j=0,len=group.length;j<len;j++){g=group[j];results.push(g!=null?g-1:g)}return results}();if(d3panels.sumArray(function(){var j,len,results;results=[];for(j=0,len=group.length;j<len;j++){g=group[j];results.push(g<0||g>ngroup-1)}return results}())>0){d3panels.displayError("dotchart: group values out of range");console.log("ngroup: "+ngroup);console.log("distinct groups: "+d3panels.unique(group))}if(group.length!==x.length){d3panels.displayError("dotchart: group.length ("+group.length+") != x.length ("+x.length+")")}pointcolor=pointcolor!=null?pointcolor:d3panels.selectGroupColors(ngroup,"dark");pointcolor=d3panels.expand2vector(pointcolor,ngroup);if(pointcolor.length<ngroup){d3panels.displayError("add_points: pointcolor.length ("+pointcolor.length+") < ngroup ("+ngroup+")")}xcategories=xcategories!=null?xcategories:d3panels.unique(x);xcategories=d3panels.forceAsArray(xcategories);xcatlabels=xcatlabels!=null?xcatlabels:xcategories;xcatlabels=d3panels.forceAsArray(xcatlabels);if(xcatlabels.length!==xcategories.length){d3panels.displayError("dotchart: xcatlabels.length ["+xcatlabels.length+"] != xcategories.length ["+xcategories.length+"]")}if(d3panels.sumArray(function(){var j,len,results;results=[];for(j=0,len=x.length;j<len;j++){xv=x[j];results.push(xv!=null&&!(indexOf.call(xcategories,xv)>=0))}return results}())>0){d3panels.displayError("dotchart: Some x values not in xcategories");console.log("xcategories:");console.log(xcategories);console.log("x:");console.log(x);for(i in x){if(x[i]!=null&&!(ref20=x[i],indexOf.call(xcategories,ref20)>=0)){x[i]=null}}}ylim=ylim!=null?ylim:d3panels.pad_ylim(d3.extent(y));xlim=[d3.min(xcategories)-.5,d3.max(xcategories)+.5];xNA.handle=xNA.force||xNA.handle&&!x.every(function(v){return v!=null});yNA.handle=yNA.force||yNA.handle&&!y.every(function(v){return v!=null});if(horizontal){chartOpts.ylim=xlim.reverse();chartOpts.xlim=ylim;chartOpts.xlab=ylab;chartOpts.ylab=xlab;chartOpts.xlineOpts=chartOpts.ylineOpts;chartOpts.ylineOpts=xlineOpts;chartOpts.yNA=xNA.handle;chartOpts.xNA=yNA.handle;chartOpts.xNA_size=yNA_size;chartOpts.yNA_size=xNA_size;chartOpts.yticks=xcategories;chartOpts.yticklab=xcatlabels;chartOpts.v_over_h=v_over_h}else{chartOpts.ylim=ylim;chartOpts.xlim=xlim;chartOpts.xlab=xlab;chartOpts.ylab=ylab;chartOpts.ylineOpts=chartOpts.ylineOpts;chartOpts.xlineOpts=xlineOpts;chartOpts.xNA=xNA.handle;chartOpts.yNA=yNA.handle;chartOpts.xNA_size=xNA_size;chartOpts.yNA_size=yNA_size;chartOpts.xticks=xcategories;chartOpts.xticklab=xcatlabels;chartOpts.v_over_h=v_over_h}myframe=d3panels.panelframe(chartOpts);myframe(selection);svg=myframe.svg();xscale=myframe.xscale();yscale=myframe.yscale();if(horizontal){scaledPoints=function(){var results;results=[];for(i in x){results.push({x:xscale(y[i]),y:yscale(x[i])})}return results}()}else{scaledPoints=function(){var results;results=[];for(i in x){results.push({x:xscale(x[i]),y:yscale(y[i])})}return results}()}pointGroup=svg.append("g").attr("id","points");points=pointGroup.selectAll("empty").data(scaledPoints).enter().append("circle").attr("class",function(d,i){return"pt"+i}).attr("r",pointsize).attr("fill",function(d,i){return pointcolor[group[i]]}).attr("stroke",pointstroke).attr("stroke-width","1").attr("cx",function(d){return d.x}).attr("cy",function(d){return d.y});indtip=d3panels.tooltip_create(d3.select("body"),points,{tipclass:tipclass},function(d,i){return indID[i]});if(jitter==="random"){jitter_width=.2;u=function(){var results;results=[];for(i in scaledPoints){results.push((Math.random()-.5)*jitter_width)}return results}();if(horizontal){points.attr("cy",function(d,i){if(x[i]!=null){return yscale(x[i]+u[i])}return yscale(x[i])+u[i]/jitter_width*xNA_size.width/2})}else{points.attr("cx",function(d,i){if(x[i]!=null){return xscale(x[i]+u[i])}return xscale(x[i])+u[i]/jitter_width*xNA_size.width/2})}}else if(jitter==="beeswarm"){if(horizontal){d3.range(scaledPoints.length).map(function(i){return scaledPoints[i].fx=scaledPoints[i].x});force=d3.forceSimulation(scaledPoints).force("y",d3.forceY(function(d){return d.y})).force("collide",d3.forceCollide(pointsize*1.1)).stop()}else{d3.range(scaledPoints.length).map(function(i){return scaledPoints[i].fy=scaledPoints[i].y});force=d3.forceSimulation(scaledPoints).force("x",d3.forceX(function(d){return d.x})).force("collide",d3.forceCollide(pointsize*1.1)).stop()}(function(){var results=[];for(var j=0;j<=30;j++){results.push(j)}return results}).apply(this).map(function(d){force.tick();return points.attr("cx",function(d){return d.x}).attr("cy",function(d){return d.y})})}else if(jitter!=="none"){d3panels.displayError('dotchart: jitter should be "beeswarm", "random", or "none"')}return myframe.box().raise()};chart.xscale=function(){return xscale};chart.yscale=function(){return yscale};chart.xNA=function(){return xNA.handle};chart.yNA=function(){return yNA.handle};chart.points=function(){return points};chart.indtip=function(){return indtip};chart.svg=function(){return svg};chart.remove=function(){svg.remove();d3panels.tooltip_destroy(indtip);return null};return chart};"use strict";d3panels.heatmap=function(chartOpts){var cellSelect,cells,celltip,chart,colors,hilitcolor,margin,nullcolor,ref,ref1,ref2,ref3,ref4,ref5,ref6,ref7,ref8,svg,tipclass,xlim,xscale,ylim,yscale,zlim,zscale,zthresh;if(chartOpts==null){chartOpts={}}margin=(ref=chartOpts!=null?chartOpts.margin:void 0)!=null?ref:{left:60,top:40,right:40,bottom:40,inner:0};xlim=(ref1=chartOpts!=null?chartOpts.xlim:void 0)!=null?ref1:null;ylim=(ref2=chartOpts!=null?chartOpts.ylim:void 0)!=null?ref2:null;nullcolor=(ref3=chartOpts!=null?chartOpts.nullcolor:void 0)!=null?ref3:"#e6e6e6";colors=(ref4=chartOpts!=null?chartOpts.colors:void 0)!=null?ref4:["slateblue","white","crimson"];zlim=(ref5=chartOpts!=null?chartOpts.zlim:void 0)!=null?ref5:null;zthresh=(ref6=chartOpts!=null?chartOpts.zthresh:void 0)!=null?ref6:null;hilitcolor=(ref7=chartOpts!=null?chartOpts.hilitcolor:void 0)!=null?ref7:"black";tipclass=(ref8=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref8:"tooltip";xscale=null;yscale=null;zscale=null;cells=null;celltip=null;svg=null;cellSelect=null;chart=function chart(selection,data){var cell,cellrect,i,j,myframe,nx,ny,ref10,ref9,tooltipfunc,xlabels,xmid,xmid_scaled,xv,ylabels,ymid,ymid_scaled,yv,zmax,zmin;margin=d3panels.check_listarg_v_default(margin,{left:60,top:40,right:40,bottom:40,inner:0});if(!(data.x!=null||data.xcat!=null)){d3panels.displayError("heatmap: data.x is missing")}if(!(data.y!=null||data.ycat!=null)){d3panels.displayError("heatmap: data.y is missing")}if(data.z==null){d3panels.displayError("heatmap: data.z is missing")}if(data.xcat!=null){data.x=function(){var results;results=[];for(i in data.xcat){results.push(+i)}return results}();xlim=xlim!=null?xlim:[-.5,data.x.length-.5];chartOpts.xticks=data.x;chartOpts.xlineOpts={color:"none",width:0};chartOpts.xlab=(ref9=chartOpts!=null?chartOpts.xlab:void 0)!=null?ref9:""}if(data.ycat!=null){data.y=function(){var results;results=[];for(i in data.ycat){results.push(+i)}return results}();ylim=ylim!=null?ylim:[-.5,data.x.length-.5];chartOpts.yticks=data.y;chartOpts.ylineOpts={color:"none",width:0};chartOpts.ylab=(ref10=chartOpts!=null?chartOpts.ylab:void 0)!=null?ref10:""}nx=data.x.length;ny=data.y.length;if(data.z.length!==nx){d3panels.displayError("heatmap: data.x.length ("+nx+") != data.z.length ("+data.z.length+")")}for(i in data.z){if(data.z[i].length!==ny){d3panels.displayError("heatmap: data.y.length ("+ny+") != data.z["+i+"].length ("+data.z[i].length+")")}}cells=[];for(i in data.z){for(j in data.z[i]){cells.push({x:data.x[i],y:data.y[j],z:data.z[i][j],xindex:+i,yindex:+j})}}xmid=d3panels.calc_midpoints(d3panels.pad_vector(data.x));ymid=d3panels.calc_midpoints(d3panels.pad_vector(data.y));xlim=xlim!=null?xlim:d3.extent(xmid);ylim=ylim!=null?ylim:d3.extent(ymid);zmin=d3panels.matrixMin(data.z);zmax=d3panels.matrixMaxAbs(data.z);zlim=zlim!=null?zlim:[-zmax,0,zmax];if(zlim.length!==colors.length){d3panels.displayError("heatmap: zlim.length ("+zlim.length+") != colors.length ("+colors.length+")")}zscale=d3.scaleLinear().domain(zlim).range(colors);zthresh=zthresh!=null?zthresh:zmin-1;cells=function(){var k,len,results;results=[];for(k=0,len=cells.length;k<len;k++){cell=cells[k];if(Math.abs(cell.z)>=zthresh){results.push(cell)}}return results}();chartOpts.margin=margin;chartOpts.xlim=xlim;chartOpts.ylim=ylim;chartOpts.xNA=false;chartOpts.yNA=false;myframe=d3panels.panelframe(chartOpts);myframe(selection);svg=myframe.svg();xscale=myframe.xscale();yscale=myframe.yscale();xlabels=myframe.xlabels();ylabels=myframe.ylabels();xmid_scaled=function(){var k,len,results;results=[];for(k=0,len=xmid.length;k<len;k++){xv=xmid[k];results.push(xscale(xv))}return results}();ymid_scaled=function(){var k,len,results;results=[];for(k=0,len=ymid.length;k<len;k++){yv=ymid[k];results.push(yscale(yv))}return results}();d3panels.calc_cell_rect(cells,xmid_scaled,ymid_scaled);cellrect=svg.append("g").attr("id","cells");cellSelect=cellrect.selectAll("empty").data(cells).enter().append("rect").attr("x",function(d){return d.left}).attr("y",function(d){return d.top}).attr("width",function(d){return d.width}).attr("height",function(d){return d.height}).attr("class",function(d,i){return"cell"+i}).attr("fill",function(d){if(d.z!=null){return zscale(d.z)}else{return nullcolor}}).attr("stroke","none").attr("stroke-width","1").attr("shape-rendering","crispEdges").on("mouseover",function(d){d3.select(this).attr("stroke",hilitcolor).raise();if(data.xcat!=null){svg.select("text#xlab"+d.x).attr("opacity",1)}if(data.ycat!=null){return svg.select("text#ylab"+d.y).attr("opacity",1)}}).on("mouseout",function(d){d3.select(this).attr("stroke","none");if(data.xcat!=null){svg.select("text#xlab"+d.x).attr("opacity",0)}if(data.ycat!=null){return svg.select("text#ylab"+d.y).attr("opacity",0)}});tooltipfunc=function tooltipfunc(d,i){var x,y,z;x=d3panels.formatAxis(data.x)(d.x);y=d3panels.formatAxis(data.y)(d.y);z=d3panels.formatAxis([0,zmax/100])(d.z);if(data.xcat!=null&&data.ycat!=null){return""+z}if(data.xcat!=null){return"("+y+") → "+z}if(data.ycat!=null){return"("+x+") → "+z}return"("+x+", "+y+") → "+z};celltip=d3panels.tooltip_create(d3.select("body"),cellrect.selectAll("rect"),{tipclass:tipclass},tooltipfunc);if(data.xcat!=null){xlabels.text(function(d,i){return data.xcat[i]}).attr("opacity",0).attr("id",function(d,i){return"xlab"+data.x[i]})}if(data.ycat!=null){ylabels.text(function(d,i){return data.ycat[i]}).attr("opacity",0).attr("id",function(d,i){return"ylab"+data.y[i]})}return myframe.box().raise()};chart.xscale=function(){return xscale};chart.yscale=function(){return yscale};chart.zscale=function(){return zscale};chart.cells=function(){return cellSelect};chart.celltip=function(){return celltip};chart.svg=function(){return svg};chart.remove=function(){svg.remove();d3panels.tooltip_destroy(celltip);return null};return chart};"use strict";d3panels.lodchart=function(chartOpts){var chart,chrSelect,linecolor,linewidth,markerSelect,markertip,pointcolor,pointsize,pointstroke,ref,ref1,ref2,ref3,ref4,ref5,ref6,_svg,tipclass,_xscale,ylim,_yscale;if(chartOpts==null){chartOpts={}}linecolor=(ref=chartOpts!=null?chartOpts.linecolor:void 0)!=null?ref:"darkslateblue";linewidth=(ref1=chartOpts!=null?chartOpts.linewidth:void 0)!=null?ref1:2;pointcolor=(ref2=chartOpts!=null?chartOpts.pointcolor:void 0)!=null?ref2:"#e9cfec";pointsize=(ref3=chartOpts!=null?chartOpts.pointsize:void 0)!=null?ref3:0;pointstroke=(ref4=chartOpts!=null?chartOpts.pointstroke:void 0)!=null?ref4:"black";ylim=(ref5=chartOpts!=null?chartOpts.ylim:void 0)!=null?ref5:null;tipclass=(ref6=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref6:"tooltip";_xscale=null;_yscale=null;chrSelect=null;markerSelect=null;markertip=null;_svg=null;chart=function chart(selection,data){var add2chart,i,myframe,self_chart;if(data.chr==null){d3panels.displayError("lodchart: data.chr is missing")}if(data.pos==null){d3panels.displayError("lodchart: data.pos is missing")}if(data.lod==null){d3panels.displayError("lodchart: data.lod is missing")}if(data.marker==null){d3panels.displayError("lodchart: data.marker is missing")}if(data.pos.length!==data.chr.length){d3panels.displayError("lodchart: data.pos.length ("+data.pos.length+") != data.chr.length ("+data.chr.length+")")}if(data.lod.length!==data.chr.length){d3panels.displayError("lodchart: data.lod.length ("+data.lod.length+") != data.chr.length ("+data.chr.length+")")}if(data.marker==null){data.marker=[function(){var results;results=[];for(i in data.chr){results.push("")}return results}()]}if(data.marker.length!==data.chr.length){d3panels.displayError("lodchart: data.marker.length ("+data.marker.length+") != data.chr.length ("+data.chr.length+")")}data=d3panels.add_chrname_start_end(data);data=d3panels.reorgLodData(data);chartOpts.ylim=ylim!=null?ylim:[0,d3.max(data.lod)*1.05];myframe=d3panels.chrpanelframe(chartOpts);myframe(selection,{chr:data.chrname,start:data.chrstart,end:data.chrend});_svg=myframe.svg();_xscale=myframe.xscale();_yscale=myframe.yscale();chrSelect=myframe.chrSelect();self_chart={svg:function svg(){return _svg},xscale:function xscale(){return _xscale},yscale:function yscale(){return _yscale}};add2chart=d3panels.add_lodcurve(chartOpts);add2chart(self_chart,data);markerSelect=add2chart.markerSelect();markertip=add2chart.markertip();return myframe.box().raise()};chart.xscale=function(){return _xscale};chart.yscale=function(){return _yscale};chart.chrSelect=function(){return chrSelect};chart.markerSelect=function(){return markerSelect};chart.markertip=function(){return markertip};chart.svg=function(){return _svg};chart.remove=function(){_svg.remove();d3panels.tooltip_destroy(markertip);return null};return chart};"use strict";d3panels.add_lodcurve=function(chartOpts){var chart,g,horizontal,linecolor,linedash,linewidth,markerSelect,markertip,pointcolor,pointsize,pointstroke,ref,ref1,ref2,ref3,ref4,ref5,ref6,ref7,tipclass;if(chartOpts==null){chartOpts={}}linecolor=(ref=chartOpts!=null?chartOpts.linecolor:void 0)!=null?ref:"darkslateblue";linewidth=(ref1=chartOpts!=null?chartOpts.linewidth:void 0)!=null?ref1:2;linedash=(ref2=chartOpts!=null?chartOpts.linedash:void 0)!=null?ref2:"";pointcolor=(ref3=chartOpts!=null?chartOpts.pointcolor:void 0)!=null?ref3:"#e9cfec";pointsize=(ref4=chartOpts!=null?chartOpts.pointsize:void 0)!=null?ref4:0;pointstroke=(ref5=chartOpts!=null?chartOpts.pointstroke:void 0)!=null?ref5:"black";tipclass=(ref6=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref6:"tooltip";horizontal=(ref7=chartOpts!=null?chartOpts.horizontal:void 0)!=null?ref7:false;markerSelect=null;markertip=null;g=null;chart=function chart(prevchart,data){var bigpointsize,chr,curves,hiddenpoints,i,j,len,lodcurve,markerpoints,ref8,svg,xscale,yscale;if(data.chr==null){d3panels.displayError("add_lodcurve: data.chr is missing")}if(data.pos==null){d3panels.displayError("add_lodcurve: data.pos is missing")}if(data.lod==null){d3panels.displayError("add_lodcurve: data.lod is missing")}if(data.marker==null){d3panels.displayError("add_lodcurve: data.marker is missing")}if(data.pos.length!==data.chr.length){d3panels.displayError("add_lodcurve: data.pos.length ("+data.pos.length+") != data.chr.length ("+data.chr.length+")")}if(data.lod.length!==data.chr.length){d3panels.displayError("add_lodcurve: data.lod.length ("+data.lod.length+") != data.chr.length ("+data.chr.length+")")}if(data.marker==null){data.marker=[function(){var results;results=[];for(i in data.chr){results.push("")}return results}()]}if(data.marker.length!==data.chr.length){d3panels.displayError("add_lodcurve: data.marker.length ("+data.lod.length+") != data.chr.length ("+data.chr.length+")")}data=d3panels.add_chrname_start_end(data);if(!(data.posByChr!=null&&data.lodByChr!=null&&data.markerinfo!=null)){data=d3panels.reorgLodData(data)}svg=prevchart.svg();g=svg.append("g").attr("id","lod_curve");xscale=prevchart.xscale();yscale=prevchart.yscale();lodcurve=function lodcurve(chr){return d3.line().x(function(d,i){if(horizontal){return yscale(data.lodByChr[chr][i])}return xscale[chr](d)}).y(function(d,i){if(horizontal){return xscale[chr](d)}return yscale(data.lodByChr[chr][i])})};if(linewidth>0){curves=g.append("g").attr("id","curves");ref8=data.chrname;for(j=0,len=ref8.length;j<len;j++){chr=ref8[j];curves.append("path").datum(data.posByChr[chr]).attr("d",lodcurve(chr)).attr("stroke",linecolor).attr("fill","none").attr("stroke-width",linewidth).attr("stroke-dasharray",linedash).style("pointer-events","none")}}if(pointsize>0){markerpoints=g.append("g").attr("id","markerpoints_visible");markerpoints.selectAll("empty").data(data.markerinfo).enter().append("circle").attr("cx",function(d){if(horizontal){return yscale(d.lod)}return xscale[d.chr](d.pos)}).attr("cy",function(d){if(horizontal){return xscale[d.chr](d.pos)}return yscale(d.lod)}).attr("r",function(d){if(d.lod!=null){return pointsize}else{return null}}).attr("fill",pointcolor).attr("stroke",pointstroke).attr("pointer-events","hidden")}hiddenpoints=g.append("g").attr("id","markerpoints_hidden");bigpointsize=d3.max([2*pointsize,3]);markerSelect=hiddenpoints.selectAll("empty").data(data.markerinfo).enter().append("circle").attr("cx",function(d){if(horizontal){return yscale(d.lod)}return xscale[d.chr](d.pos)}).attr("cy",function(d){if(horizontal){return xscale[d.chr](d.pos)}return yscale(d.lod)}).attr("id",function(d){return d.name}).attr("r",function(d){if(d.lod!=null){return bigpointsize}else{return null}}).attr("opacity",0).attr("fill",pointcolor).attr("stroke",pointstroke).attr("stroke-width","1").on("mouseover",function(d){return d3.select(this).attr("opacity",1)}).on("mouseout",function(){return d3.select(this).attr("opacity",0)});return markertip=d3panels.tooltip_create(d3.select("body"),markerSelect,{tipclass:tipclass},function(d,i){return[d.name," LOD = "+d3.format(".2f")(d.lod)]})};chart.markerSelect=function(){return markerSelect};chart.markertip=function(){return markertip};chart.remove=function(){g.remove();d3panels.tooltip_destroy(markertip);return null};return chart};"use strict";d3panels.add_curves=function(chartOpts){var chart,curveGroup,curves,indtip,linecolor,linecolorhilit,linewidth,linewidthhilit,ref,ref1,ref2,ref3,ref4,tipclass;if(chartOpts==null){chartOpts={}}linecolor=(ref=chartOpts!=null?chartOpts.linecolor:void 0)!=null?ref:null;linecolorhilit=(ref1=chartOpts!=null?chartOpts.linecolorhilit:void 0)!=null?ref1:null;linewidth=(ref2=chartOpts!=null?chartOpts.linewidth:void 0)!=null?ref2:2;linewidthhilit=(ref3=chartOpts!=null?chartOpts.linewidthhilit:void 0)!=null?ref3:2;tipclass=(ref4=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref4:"tooltip";curves=null;indtip=null;curveGroup=null;chart=function chart(prevchart,data){var curvefunc,dataByPoint,g,group,i,indID,j,k,n_ind,ngroup,ref5,ref6,ref7,svg,x,xscale,y,yscale;if(data.x==null){d3panels.displayError("add_curves: data.x is missing")}if(data.y==null){d3panels.displayError("add_curves: data.y is missing")}x=function(){var k,len,ref5,results;ref5=data.x;results=[];for(k=0,len=ref5.length;k<len;k++){x=ref5[k];results.push(d3panels.missing2null(x))}return results}();y=function(){var k,len,ref5,results;ref5=data.y;results=[];for(k=0,len=ref5.length;k<len;k++){y=ref5[k];results.push(d3panels.missing2null(y))}return results}();n_ind=y.length;if(x.length===1&&y.length>1){for(j=k=2,ref5=n_ind;2<=ref5?k<=ref5:k>=ref5;j=2<=ref5?++k:--k){x.push(x[0])}}if(x.length!==n_ind){d3panels.displayError("add_curves: data.x.length ("+x.length+") != data.y.length ("+n_ind+")")}indID=(ref6=data!=null?data.indID:void 0)!=null?ref6:function(){var results=[];for(var l=1;1<=n_ind?l<=n_ind:l>=n_ind;1<=n_ind?l++:l--){results.push(l)}return results}.apply(this);if(indID.length!==n_ind){d3panels.displayError("add_curves: data.indID.length ("+indID.length+") != data.y.length ("+n_ind+")")}group=(ref7=data!=null?data.group:void 0)!=null?ref7:function(){var results;results=[];for(i in y){results.push(1)}return results}();group=d3panels.expand2vector(group,n_ind);ngroup=d3.max(group);group=function(){var l,len,results;results=[];for(l=0,len=group.length;l<len;l++){g=group[l];results.push(g!=null?g-1:g)}return results}();if(d3panels.sumArray(function(){var l,len,results;results=[];for(l=0,len=group.length;l<len;l++){g=group[l];results.push(g<0||g>ngroup-1)}return results}())>0){d3panels.displayError("add_curves: group values out of range");console.log("distinct groups: "+d3panels.unique(group))}if(group.length!==n_ind){d3panels.displayError("add_curves: data.group.length ("+group.length+") != data.y.length ("+n_ind+")")}for(i in y){if(x[i].length!==y[i].length){d3panels.displayError("add_curves: length(x) ("+x[i].length+") != length(y) ("+y[i].length+") for individual "+indID[i]+" (index "+(i+1)+")")}}linecolor=linecolor!=null?linecolor:d3panels.selectGroupColors(ngroup,"pastel");linecolor=d3panels.expand2vector(linecolor,ngroup);linecolorhilit=linecolorhilit!=null?linecolorhilit:d3panels.selectGroupColors(ngroup,"dark");linecolorhilit=d3panels.expand2vector(linecolorhilit,ngroup);svg=prevchart.svg();xscale=prevchart.xscale();yscale=prevchart.yscale();dataByPoint=[];for(i in y){dataByPoint.push(function(){var results;results=[];for(j in y[i]){if(x[i][j]!=null&&y[i][j]!=null){results.push({x:x[i][j],y:y[i][j]})}}return results}())}curvefunc=d3.line().x(function(d){return xscale(d.x)}).y(function(d){return yscale(d.y)});curveGroup=svg.append("g").attr("id","curves");curves=curveGroup.selectAll("empty").data(d3.range(n_ind)).enter().append("path").datum(function(d){return dataByPoint[d]}).attr("d",curvefunc).attr("class",function(d,i){return"path"+i}).attr("fill","none").attr("stroke",function(d,i){return linecolor[group[i]]}).attr("stroke-width",linewidth).on("mouseover.panel",function(d,i){return d3.select(this).attr("stroke",linecolorhilit[group[i]]).attr("stroke-width",linewidthhilit).raise()}).on("mouseout.panel",function(d,i){return d3.select(this).attr("stroke",linecolor[group[i]]).attr("stroke-width",linewidth)});indtip=d3panels.tooltip_create(d3.select("body"),curves,{tipclass:tipclass},function(d,i){return indID[i]});return prevchart.box().raise()};chart.curves=function(){return curves};chart.indtip=function(){return indtip};chart.remove=function(){curveGroup.remove();d3panels.tooltip_destroy(indtip);return null};return chart};"use strict";d3panels.add_points=function(chartOpts){var chart,indtip,jitter,pointGroup,pointcolor,points,pointsize,pointstroke,ref,ref1,ref2,ref3,ref4,tipclass;if(chartOpts==null){chartOpts={}}pointcolor=(ref=chartOpts!=null?chartOpts.pointcolor:void 0)!=null?ref:null;pointsize=(ref1=chartOpts!=null?chartOpts.pointsize:void 0)!=null?ref1:3;pointstroke=(ref2=chartOpts!=null?chartOpts.pointstroke:void 0)!=null?ref2:"black";jitter=(ref3=chartOpts!=null?chartOpts.jitter:void 0)!=null?ref3:"beeswarm";tipclass=(ref4=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref4:"tooltip";points=null;indtip=null;pointGroup=null;chart=function chart(prevchart,data){var force,g,group,i,indID,ngroup,ref5,ref6,ref7,scaledPoints,svg,ux,uy,x,xscale,xwid,y,yscale,ywid;if(data.x==null){d3panels.displayError("add_points: data.x is missing")}if(data.y==null){d3panels.displayError("add_points: data.y is missing")}x=d3panels.missing2null(data.x);y=d3panels.missing2null(data.y);if(x.length!==y.length){d3panels.displayError("add_points: x.length ("+x.length+") != y.length ("+y.length+")")}indID=(ref5=data!=null?data.indID:void 0)!=null?ref5:null;indID=indID!=null?indID:function(){var results=[];for(var j=1,ref6=x.length;1<=ref6?j<=ref6:j>=ref6;1<=ref6?j++:j--){results.push(j)}return results}.apply(this);if(indID.length!==x.length){d3panels.displayError("add_points: indID.length ("+indID.length+") != x.length ("+x.length+")")}group=(ref7=data!=null?data.group:void 0)!=null?ref7:function(){var j,len,results;results=[];for(j=0,len=x.length;j<len;j++){i=x[j];results.push(1)}return results}();group=d3panels.expand2vector(group,x.length);ngroup=d3.max(group);group=function(){var j,len,results;results=[];for(j=0,len=group.length;j<len;j++){g=group[j];results.push(g!=null?g-1:g)}return results}();if(d3panels.sumArray(function(){var j,len,results;results=[];for(j=0,len=group.length;j<len;j++){g=group[j];results.push(g<0||g>ngroup-1)}return results}())>0){d3panels.displayError("add_points: group values out of range");console.log("ngroup: "+ngroup);console.log("distinct groups: "+d3panels.unique(group))}if(group.length!==x.length){d3panels.displayError("add_points: group.length ("+group.length+") != x.length ("+x.length+")")}pointcolor=pointcolor!=null?pointcolor:d3panels.selectGroupColors(ngroup,"dark");pointcolor=d3panels.expand2vector(pointcolor,ngroup);if(pointcolor.length<ngroup){d3panels.displayError("add_points: pointcolor.length ("+pointcolor.length+") < ngroup ("+ngroup+")")}svg=prevchart.svg();xscale=prevchart.xscale();yscale=prevchart.yscale();pointGroup=svg.append("g").attr("id","points");points=pointGroup.selectAll("empty").data(d3.range(x.length)).enter().append("circle").attr("cx",function(d,i){return xscale(x[i])}).attr("cy",function(d,i){return yscale(y[i])}).attr("class",function(d,i){return"pt"+i}).attr("r",pointsize).attr("fill",function(d,i){return pointcolor[group[i]]}).attr("stroke",pointstroke).attr("stroke-width","1");indtip=d3panels.tooltip_create(d3.select("body"),points,{tipclass:tipclass},function(d,i){return indID[i]});if(prevchart.xNA()||prevchart.yNA()){if(jitter==="random"){xwid=20-pointsize-2;xwid=xwid<=2?2:xwid;ywid=20-pointsize-2;ywid=ywid<=2?2:ywid;ux=function(){var results;results=[];for(i in x){results.push((Math.random()-.5)*xwid)}return results}();uy=function(){var results;results=[];for(i in x){results.push((Math.random()-.5)*ywid)}return results}();points.attr("cx",function(d,i){if(x[i]!=null){return xscale(x[i])}return xscale(x[i])+ux[i]}).attr("cy",function(d,i){if(y[i]!=null){return yscale(y[i])}return yscale(y[i])+uy[i]})}else if(jitter==="beeswarm"){scaledPoints=function(){var results;results=[];for(i in x){results.push({x:xscale(x[i]),y:yscale(y[i]),xnull:x[i]==null,ynull:y[i]==null})}return results}();d3.range(scaledPoints.length).map(function(i){if(!scaledPoints[i].xnull){scaledPoints[i].fx=scaledPoints[i].x}if(!scaledPoints[i].ynull){return scaledPoints[i].fy=scaledPoints[i].y}});force=d3.forceSimulation(scaledPoints).force("x",d3.forceX(function(d){return d.x})).force("y",d3.forceY(function(d){return d.y})).force("collide",d3.forceCollide(pointsize*1.1)).stop();(function(){var results=[];for(var j=0;j<=30;j++){results.push(j)}return results}).apply(this).map(function(d){force.tick();return points.attr("cx",function(d,i){return scaledPoints[i].x}).attr("cy",function(d,i){return scaledPoints[i].y})})}else if(jitter!=="none"){d3panels.displayError('add_points: jitter should be "beeswarm", "random", or "none"')}}return prevchart.box().raise()};chart.points=function(){return points};chart.indtip=function(){return indtip};chart.remove=function(){pointGroup.remove();d3panels.tooltip_destroy(indtip);return null};return chart};"use strict";d3panels.lodheatmap=function(chartOpts){var cellSelect,cells,celltip,chart,chrGap,colors,equalCells,height,hilitcolor,horizontal,margin,nullcolor,ref,ref1,ref10,ref11,ref12,ref13,ref14,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,svg,tipclass,width,xlab,xscale,ylab,ylim,yscale,zlim,zscale,zthresh;if(chartOpts==null){chartOpts={}}width=(ref=chartOpts!=null?chartOpts.width:void 0)!=null?ref:800;height=(ref1=chartOpts!=null?chartOpts.height:void 0)!=null?ref1:500;margin=(ref2=chartOpts!=null?chartOpts.margin:void 0)!=null?ref2:{left:60,top:40,right:40,bottom:40};colors=(ref3=chartOpts!=null?chartOpts.colors:void 0)!=null?ref3:["slateblue","white","crimson"];nullcolor=(ref4=chartOpts!=null?chartOpts.nullcolor:void 0)!=null?ref4:"#e6e6e6";xlab=(ref5=chartOpts!=null?chartOpts.xlab:void 0)!=null?ref5:null;ylab=(ref6=chartOpts!=null?chartOpts.ylab:void 0)!=null?ref6:"";ylim=(ref7=chartOpts!=null?chartOpts.ylim:void 0)!=null?ref7:null;zlim=(ref8=chartOpts!=null?chartOpts.zlim:void 0)!=null?ref8:null;zthresh=(ref9=chartOpts!=null?chartOpts.zthresh:void 0)!=null?ref9:null;horizontal=(ref10=chartOpts!=null?chartOpts.horizontal:void 0)!=null?ref10:false;hilitcolor=(ref11=chartOpts!=null?chartOpts.hilitcolor:void 0)!=null?ref11:"black";chrGap=(ref12=chartOpts!=null?chartOpts.chrGap:void 0)!=null?ref12:6;equalCells=(ref13=chartOpts!=null?chartOpts.equalCells:void 0)!=null?ref13:false;tipclass=(ref14=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref14:"tooltip";xscale=null;yscale=null;zscale=null;cells=null;celltip=null;svg=null;cellSelect=null;chart=function chart(selection,data){var cellg,celltipfunc,chr,direction,i,j,k,l,len,len1,len2,len3,len4,lod,m,myframe,n,n_lod,n_pos,o,pos,ref15,ref16,ref17,ref18,ref19,x,xmid_scaled,y,ylabels,ymid,ymid_scaled,yv,zmax,zmin;margin=d3panels.check_listarg_v_default(margin,{left:60,top:40,right:40,bottom:40});if(data.chr==null){d3panels.displayError("lodheatmap: data.chr is missing")}if(data.pos==null){d3panels.displayError("lodheatmap: data.pos is missing")}if(data.lod==null){d3panels.displayError("lodheatmap: data.lod is missing")}if(data.ycat!=null){data.y=function(){var results;results=[];for(i in data.ycat){results.push(i+1)}return results}()}if(data.y==null){data.ycat=function(){var results;results=[];for(i in data.lod[0]){results.push(i+1)}return results}();data.y=function(){var results;results=[];for(i in data.lod[0]){results.push(i+1)}return results}()}data.y=function(){var k,len,ref15,results;ref15=data.y;results=[];for(k=0,len=ref15.length;k<len;k++){yv=ref15[k];results.push(+yv)}return results}();n_pos=data.chr.length;n_lod=data.y.length;if(n_pos!==data.pos.length){d3panels.displayError("lodheatmap: data.pos.length ("+data.pos.length+") != data.chr.length ("+n_pos+")")}if(n_pos!==data.lod.length){d3panels.displayError("lodheatmap: data.lod.length ("+data.lod.length+") != data.chr.length ("+n_pos+")")}for(i in data.lod){if(data.lod[i].length!==data.y.length){d3panels.displayError("lodheatmap: data.lod["+i+"].length ("+data.lod[i].length+") != data.y.length ("+n_lod+")")}}if(data.poslabel!=null){if(data.poslabel.length!==n_pos){d3panels.displayError("lodheatmap: data.poslabel.length ("+data.poslabel.length+") != data.chr.length ("+n_pos+")")}}else{data.poslabel=function(){var results;results=[];for(i in data.chr){results.push(data.chr[i]+"@"+d3panels.formatAxis(data.pos)(data.pos[i]))}return results}()}if(data.chrname==null){data.chrname=d3panels.unique(data.chr)}data.chrname=d3panels.forceAsArray(data.chrname);if(equalCells){data.pos=[];ref15=data.chrname;for(k=0,len=ref15.length;k<len;k++){chr=ref15[k];data.pos=data.pos.concat(function(){var results;results=[];for(i in data.chr){if(data.chr[i]===chr){results.push(+i)}}return results}())}}data=d3panels.add_chrname_start_end(data);if(equalCells){chrGap=(width-margin.left-margin.right-2*data.chrname.length)/data.chr.length+2}data=d3panels.reorgLodData(data);ymid=d3panels.calc_midpoints(d3panels.pad_vector(data.y));chartOpts.ylim=ylim!=null?ylim:d3.extent(ymid);chartOpts.horizontal=horizontal;chartOpts.xlab=xlab;chartOpts.ylab=ylab;chartOpts.chrGap=chrGap;chartOpts.width=width;chartOpts.height=height;chartOpts.margin=margin;if(data.ycat!=null){chartOpts.yticks=data.y;chartOpts.yticklab=data.ycat}myframe=d3panels.chrpanelframe(chartOpts);myframe(selection,{chr:data.chrname,start:data.chrstart,end:data.chrend});svg=myframe.svg();xscale=myframe.xscale();yscale=myframe.yscale();ylabels=myframe.ylabels();ymid_scaled=function(){var l,len1,results;results=[];for(l=0,len1=ymid.length;l<len1;l++){y=ymid[l];results.push(yscale(y))}return results}();xmid_scaled={};ref16=data.chrname;for(l=0,len1=ref16.length;l<len1;l++){chr=ref16[l];xmid_scaled[chr]=d3panels.calc_midpoints(d3panels.pad_vector(function(){var len2,m,ref17,results;ref17=data.posByChr[chr];results=[];for(m=0,len2=ref17.length;m<len2;m++){x=ref17[m];results.push(xscale[chr](x))}return results}(),(chrGap-2)/2))}zmin=d3panels.matrixMin(data.lod);zmax=d3panels.matrixMaxAbs(data.lod);zlim=zlim!=null?zlim:[-zmax,0,zmax];if(zlim.length!==colors.length){d3panels.displayError("lodheatmap: zlim.length ("+zlim.length+") != colors.length ("+colors.length+")")}zscale=d3.scaleLinear().domain(zlim).range(colors);zthresh=zthresh!=null?zthresh:zmin-1;cells=[];ref17=data.chrname;for(m=0,len2=ref17.length;m<len2;m++){chr=ref17[m];ref18=data.posByChr[chr];for(i=n=0,len3=ref18.length;n<len3;i=++n){pos=ref18[i];ref19=data.lodByChr[chr][i];for(j=o=0,len4=ref19.length;o<len4;j=++o){lod=ref19[j];if(Math.abs(lod)>=zthresh){cells.push({lod:lod,chr:chr,pos:pos,poslabel:data.poslabelByChr[chr][i],posindex:+i,lodindex:+j})}}}}d3panels.calc_chrcell_rect(cells,xmid_scaled,ymid_scaled);cellg=svg.append("g").attr("id","cells");cellSelect=cellg.selectAll("empty").data(cells).enter().append("rect").attr("x",function(d){if(horizontal){return d.top}return d.left}).attr("y",function(d){if(horizontal){return d.left}return d.top}).attr("width",function(d){if(horizontal){return d.height}return d.width}).attr("height",function(d){if(horizontal){return d.width}return d.height}).attr("class",function(d,i){return"cell"+i}).attr("fill",function(d){if(d.lod!=null){return zscale(d.lod)}else{return nullcolor}}).attr("stroke","none").attr("stroke-width","1").attr("shape-rendering","crispEdges").on("mouseover",function(d){d3.select(this).attr("stroke",hilitcolor).raise();if(data.ycat!=null){return svg.select("text#ylab"+d.lodindex).attr("opacity",1)}}).on("mouseout",function(d){d3.select(this).attr("stroke","none");if(data.ycat!=null){return svg.select("text#ylab"+d.lodindex).attr("opacity",0)}});if(data.ycat!=null){svg.selectAll("g#ylines").remove();ylabels.attr("opacity",0).attr("id",function(d,i){return"ylab"+i})}celltipfunc=function celltipfunc(d){var lodlabel,z;z=d3.format(".2f")(Math.abs(d.lod));lodlabel=data.ycat!=null?data.ycat[d.lodindex]:d3panels.formatAxis(data.y)(data.y[d.lodindex]);if(horizontal){return lodlabel+", "+d.poslabel+" → "+z}return d.poslabel+", "+lodlabel+" → "+z};direction=horizontal?"north":"east";celltip=d3panels.tooltip_create(d3.select("body"),cellg.selectAll("rect"),{direction:direction,tipclass:tipclass},celltipfunc);return myframe.box().raise()};chart.xscale=function(){return xscale};chart.yscale=function(){return yscale};chart.zscale=function(){return yscale};chart.cells=function(){return cellSelect};chart.celltip=function(){return celltip};chart.svg=function(){return svg};chart.remove=function(){svg.remove();d3panels.tooltip_destroy(celltip);return null};return chart};"use strict";d3panels.mapchart=function(chartOpts){var chart,horizontal,linecolor,linecolorhilit,linewidth,markerSelect,martip,ref,ref1,ref10,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,shiftStart,svg,tickwidth,tipclass,v_over_h,xlab,xlineOpts,xscale,ylab,yscale;if(chartOpts==null){chartOpts={}}tickwidth=(ref=chartOpts!=null?chartOpts.tickwidth:void 0)!=null?ref:10;linecolor=(ref1=chartOpts!=null?chartOpts.linecolor:void 0)!=null?ref1:"slateblue";linecolorhilit=(ref2=chartOpts!=null?chartOpts.linecolorhilit:void 0)!=null?ref2:"Orchid";linewidth=(ref3=chartOpts!=null?chartOpts.linewidth:void 0)!=null?ref3:3;xlab=(ref4=chartOpts!=null?chartOpts.xlab:void 0)!=null?ref4:"Chromosome";ylab=(ref5=chartOpts!=null?chartOpts.ylab:void 0)!=null?ref5:"Position (cM)";xlineOpts=(ref6=chartOpts!=null?chartOpts.xlineOpts:void 0)!=null?ref6:{color:"#cdcdcd",width:5};horizontal=(ref7=chartOpts!=null?chartOpts.horizontal:void 0)!=null?ref7:false;v_over_h=(ref8=chartOpts!=null?chartOpts.v_over_h:void 0)!=null?ref8:horizontal;shiftStart=(ref9=chartOpts!=null?chartOpts.shiftStart:void 0)!=null?ref9:false;tipclass=(ref10=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref10:"tooltip";xscale=null;yscale=null;markerSelect=null;martip=null;svg=null;chart=function chart(selection,data){var chr,chrscale,direction,extentByChr,i,j,k,l,len,len1,markerpos,markers,minpos,myframe,n_chr,n_pos,pos,ref11,ref12,these_index,these_pos,tipfunc,x,xlim,xticklab,xticks,ylim;xlineOpts=d3panels.check_listarg_v_default(xlineOpts,{color:"#cdcdcd",width:5});if(data.chr==null){d3panels.displayError("mapchart: data.chr is missing")}if(data.pos==null){d3panels.displayError("mapchart: data.pos is missing")}if(data.marker==null){d3panels.displayError("mapchart: data.marker is missing")}n_pos=data.pos.length;if(data.chr.length!==n_pos){d3panels.displayError("mapchart: data.chr.length ("+data.chr.length+") != data.pos.length ("+n_pos+")")}if(data.marker.length!==n_pos){d3panels.displayError("mapchart: data.marker.length ("+data.marker.length+") != data.pos.length ("+n_pos+")")}if(data.chrname==null){data.chrname=d3panels.unique(data.chr)}data.chrname=d3panels.forceAsArray(data.chrname);data.adjpos=data.pos.slice(0);if(shiftStart){ref11=data.chrname;for(k=0,len=ref11.length;k<len;k++){chr=ref11[k];these_pos=function(){var results;results=[];for(i in data.pos){if(data.chr[i]===chr){results.push(data.pos[i])}}return results}();these_index=function(){var results;results=[];for(i in data.pos){if(data.chr[i]===chr){results.push(i)}}return results}();minpos=d3.min(these_pos);these_pos=function(){var l,len1,results;results=[];for(l=0,len1=these_pos.length;l<len1;l++){x=these_pos[l];results.push(x-minpos)}return results}();for(j in these_pos){data.adjpos[these_index[j]]=these_pos[j]}}}extentByChr={};ref12=data.chrname;for(l=0,len1=ref12.length;l<len1;l++){chr=ref12[l];pos=function(){var results;results=[];for(i in data.adjpos){if(data.chr[i]===chr){results.push(data.adjpos[i])}}return results}();extentByChr[chr]=d3.extent(pos)}ylim=ylim!=null?ylim:d3.extent(data.adjpos);n_chr=data.chrname.length;xlim=[.5,n_chr+.5];xticks=function(){var len2,m,ref13,results;ref13=d3.range(n_chr);results=[];for(m=0,len2=ref13.length;m<len2;m++){i=ref13[m];results.push(i+1)}return results}();xticklab=data.chrname;chartOpts.xNA=false;chartOpts.yNA=false;if(horizontal){chartOpts.xlim=ylim;chartOpts.ylim=xlim.reverse();chartOpts.xlineOpts=chartOpts.ylineOpts;chartOpts.ylineOpts=xlineOpts;chartOpts.xlab=ylab;chartOpts.ylab=xlab;chartOpts.xticks=chartOpts.yticks;chartOpts.yticks=xticks;chartOpts.nxticks=chartOpts.nyticks;chartOpts.xticklab=chartOpts.yticklab;chartOpts.yticklab=xticklab;chartOpts.v_over_h=v_over_h}else{chartOpts.xlim=xlim;chartOpts.ylim=ylim.reverse();chartOpts.xlineOpts=xlineOpts;chartOpts.xlab=xlab;chartOpts.ylab=ylab;chartOpts.xticks=xticks;chartOpts.xticklab=xticklab;chartOpts.v_over_h=v_over_h}myframe=d3panels.panelframe(chartOpts);myframe(selection);svg=myframe.svg();xscale=myframe.xscale();yscale=myframe.yscale();chrscale=function chrscale(chr){var chrpos;chrpos=data.chrname.indexOf(chr)+1;if(!horizontal){return xscale(chrpos)}return yscale(chrpos)};svg.append("g").attr("id","chromosomes").selectAll("empty").data(data.chrname).enter().append("line").attr("x1",function(d){if(horizontal){return xscale(extentByChr[d][0])}return chrscale(d)}).attr("x2",function(d){if(horizontal){return xscale(extentByChr[d][1])}return chrscale(d)}).attr("y1",function(d){if(horizontal){return chrscale(d)}return yscale(extentByChr[d][0])}).attr("y2",function(d){if(horizontal){return chrscale(d)}return yscale(extentByChr[d][1])}).attr("fill","none").attr("stroke",linecolor).attr("stroke-width",linewidth).attr("shape-rendering","crispEdges").style("pointer-events","none");markerpos={};for(i in data.marker){markerpos[data.marker[i]]=d3.format(".1f")(data.pos[i])}markers=svg.append("g").attr("id","points");markerSelect=markers.selectAll("empty").data(data.marker).enter().append("line").attr("x1",function(d,i){if(horizontal){return xscale(data.adjpos[i])}return chrscale(data.chr[i])-tickwidth}).attr("x2",function(d,i){if(horizontal){return xscale(data.adjpos[i])}return chrscale(data.chr[i])+tickwidth}).attr("y1",function(d,i){if(horizontal){return chrscale(data.chr[i])-tickwidth}return yscale(data.adjpos[i])}).attr("y2",function(d,i){if(horizontal){return chrscale(data.chr[i])+tickwidth}return yscale(data.adjpos[i])}).attr("id",function(d){return d}).attr("fill","none").attr("stroke",linecolor).attr("stroke-width",linewidth).attr("shape-rendering","crispEdges").on("mouseover.paneltip",function(d){return d3.select(this).attr("stroke",linecolorhilit)}).on("mouseout.paneltip",function(){return d3.select(this).attr("stroke",linecolor)});tipfunc=function tipfunc(d,i){return d+" ("+markerpos[d]+")"};direction=horizontal?"north":"east";martip=d3panels.tooltip_create(d3.select("body"),markers.selectAll("line"),{direction:direction,tipclass:tipclass},tipfunc);return myframe.box().raise()};chart.xscale=function(){return xscale};chart.yscale=function(){return yscale};chart.markerSelect=function(){return markerSelect};chart.martip=function(){return martip};chart.svg=function(){return svg};chart.remove=function(){svg.remove();d3panels.tooltip_destroy(martip);return null};return chart};"use strict";d3panels.scatterplot=function(chartOpts){var chart,indtip,jitter,pointcolor,points,pointsize,pointstroke,ref,ref1,ref10,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,svg,tipclass,xNA,xNA_size,xlim,xscale,yNA,yNA_size,ylim,yscale;if(chartOpts==null){chartOpts={}}xNA=(ref=chartOpts!=null?chartOpts.xNA:void 0)!=null?ref:{handle:true,force:false};yNA=(ref1=chartOpts!=null?chartOpts.yNA:void 0)!=null?ref1:{handle:true,force:false};xNA_size=(ref2=chartOpts!=null?chartOpts.xNA_size:void 0)!=null?ref2:{width:20,gap:10};yNA_size=(ref3=chartOpts!=null?chartOpts.yNA_size:void 0)!=null?ref3:{width:20,gap:10};xlim=(ref4=chartOpts!=null?chartOpts.xlim:void 0)!=null?ref4:null;ylim=(ref5=chartOpts!=null?chartOpts.ylim:void 0)!=null?ref5:null;pointcolor=(ref6=chartOpts!=null?chartOpts.pointcolor:void 0)!=null?ref6:null;pointstroke=(ref7=chartOpts!=null?chartOpts.pointstroke:void 0)!=null?ref7:"black";pointsize=(ref8=chartOpts!=null?chartOpts.pointsize:void 0)!=null?ref8:3;jitter=(ref9=chartOpts!=null?chartOpts.jitter:void 0)!=null?ref9:"beeswarm";tipclass=(ref10=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref10:"tooltip";xscale=null;yscale=null;xNA=xNA;yNA=yNA;points=null;indtip=null;svg=null;chart=function chart(selection,data){var addpts,myframe,x,y;xNA=d3panels.check_listarg_v_default(xNA,{handle:true,force:false});yNA=d3panels.check_listarg_v_default(yNA,{handle:true,force:false});xNA_size=d3panels.check_listarg_v_default(xNA,{width:20,gap:10});yNA_size=d3panels.check_listarg_v_default(yNA,{width:20,gap:10});if(data.x==null){d3panels.displayError("scatterplot: data.x is missing")}if(data.y==null){d3panels.displayError("scatterplot: data.y is missing")}x=d3panels.missing2null(data.x);y=d3panels.missing2null(data.y);if(x.length!==y.length){d3panels.displayError("scatterplot: x.length ("+x.length+") != y.length ("+y.length+")")}xNA.handle=xNA.force||xNA.handle&&!x.every(function(v){return v!=null});yNA.handle=yNA.force||yNA.handle&&!y.every(function(v){return v!=null});xlim=xlim!=null?xlim:d3panels.pad_ylim(d3.extent(x));ylim=ylim!=null?ylim:d3panels.pad_ylim(d3.extent(y));chartOpts.xlim=xlim;chartOpts.ylim=ylim;chartOpts.xNA=xNA.handle;chartOpts.yNA=yNA.handle;chartOpts.xNA_size=xNA_size;chartOpts.yNA_size=yNA_size;myframe=d3panels.panelframe(chartOpts);myframe(selection);svg=myframe.svg();xscale=myframe.xscale();yscale=myframe.yscale();addpts=d3panels.add_points({pointcolor:pointcolor,pointstroke:pointstroke,pointsize:pointsize,jitter:jitter,tipclass:tipclass});addpts(myframe,{x:x,y:y,indID:data.indID,group:data.group});points=addpts.points();return indtip=addpts.indtip()};chart.xscale=function(){return xscale};chart.yscale=function(){return yscale};chart.xNA=function(){return xNA.handle};chart.yNA=function(){return yNA.handle};chart.points=function(){return points};chart.indtip=function(){return indtip};chart.svg=function(){return svg};chart.remove=function(){svg.remove();d3panels.tooltip_destroy(indtip);return null};return chart};"use strict";d3panels.trichart=function(chartOpts){var boxcolor,boxwidth,chart,gridcolor,gridlines,gridwidth,height,indtip,labelpos,labels,margin,pointcolor,points,pointsize,pointstroke,pscale,rectcolor,ref,ref1,ref10,ref11,ref12,ref13,ref14,ref15,ref16,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,svg,tipclass,title,titlepos,width,xscale,yscale;if(chartOpts==null){chartOpts={}}width=(ref=chartOpts!=null?chartOpts.width:void 0)!=null?ref:600;height=(ref1=chartOpts!=null?chartOpts.height:void 0)!=null?ref1:520;margin=(ref2=chartOpts!=null?chartOpts.margin:void 0)!=null?ref2:{left:60,top:40,right:60,bottom:10};labelpos=(ref3=chartOpts!=null?chartOpts.labelpos:void 0)!=null?ref3:10;titlepos=(ref4=chartOpts!=null?chartOpts.titlepos:void 0)!=null?ref4:20;title=(ref5=chartOpts!=null?chartOpts.title:void 0)!=null?ref5:"";labels=(ref6=chartOpts!=null?chartOpts.labels:void 0)!=null?ref6:["(1,0,0)","(0,1,0)","(0,0,1)"];rectcolor=(ref7=chartOpts!=null?chartOpts.rectcolor:void 0)!=null?ref7:"#e6e6e6";boxcolor=(ref8=chartOpts!=null?chartOpts.boxcolor:void 0)!=null?ref8:"black";boxwidth=(ref9=chartOpts!=null?chartOpts.boxwidth:void 0)!=null?ref9:2;pointcolor=(ref10=chartOpts!=null?chartOpts.pointcolor:void 0)!=null?ref10:null;pointstroke=(ref11=chartOpts!=null?chartOpts.pointstroke:void 0)!=null?ref11:"black";pointsize=(ref12=chartOpts!=null?chartOpts.pointsize:void 0)!=null?ref12:3;gridlines=(ref13=chartOpts!=null?chartOpts.gridlines:void 0)!=null?ref13:0;gridcolor=(ref14=chartOpts!=null?chartOpts.gridcolor:void 0)!=null?ref14:"white";gridwidth=(ref15=chartOpts!=null?chartOpts.gridwidth:void 0)!=null?ref15:1;tipclass=(ref16=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref16:"tooltip";xscale=null;yscale=null;pscale=null;points=null;indtip=null;svg=null;chart=function chart(selection,data){var d,first,flag_length_not_3,flag_out_of_range,flag_sum_not_1,frame,framefunc,g,gr,group,i,indID,indices,j,len,n,ngroup,p,p1,p2,p3,p4,plot_height,plot_width,ref17,ref18,second,sum,v,vertices,vv,xlim,xy,ylim;margin=d3panels.check_listarg_v_default(margin,{left:60,top:40,right:60,bottom:10});if(data.p==null){d3panels.displayError("trichart: data.p is missing")}p=function(){var j,len,ref17,results;ref17=data.p;results=[];for(j=0,len=ref17.length;j<len;j++){v=ref17[j];results.push(d3panels.missing2null(v))}return results}();flag_length_not_3=false;flag_sum_not_1=false;flag_out_of_range=false;for(j=0,len=p.length;j<len;j++){v=p[j];if(v.length!==3){flag_length_not_3=true}sum=d3panels.sumArray(v);if(d3panels.abs(sum-1)>1e-6){flag_sum_not_1}if(d3panels.sumArray(function(){var k,len1,results;results=[];for(k=0,len1=v.length;k<len1;k++){vv=v[k];results.push(vv<0||vv>1)}return results}())>0){flag_out_of_range=true}}if(flag_length_not_3){d3panels.displayError("trichart: points not all of length 3")}if(flag_sum_not_1){d3panels.displayError("trichart: points not all summing to 1")}if(flag_out_of_range){d3panels.displayError("trichart: points not all in [0,1]")}n=p.length;indID=(ref17=data!=null?data.indID:void 0)!=null?ref17:function(){var results;results=[];for(i in p){results.push(+i+1)}return results}();if(indID.length!==n){d3panels.displayError("trichart: data.indID.length ("+indID.length+") != data.p.length ("+n+")")}group=(ref18=data!=null?data.group:void 0)!=null?ref18:function(){var results;results=[];for(i in p){results.push(1)}return results}();group=d3panels.expand2vector(group,n);ngroup=d3.max(group);group=function(){var k,len1,results;results=[];for(k=0,len1=group.length;k<len1;k++){g=group[k];results.push(g!=null?g-1:g)}return results}();if(d3panels.sumArray(function(){var k,len1,results;results=[];for(k=0,len1=group.length;k<len1;k++){g=group[k];results.push(g<0||g>ngroup-1)}return results}())>0){d3panels.displayError("add_points: group values out of range");console.log("ngroup: "+ngroup);console.log("distinct groups: "+d3panels.unique(group))}if(group.length!==n){d3panels.displayError("trichart: data.group.length ("+group.length+") != data.p.length ("+n+")")}pointcolor=pointcolor!=null?pointcolor:d3panels.selectGroupColors(ngroup,"dark");pointcolor=d3panels.expand2vector(pointcolor,ngroup);if(pointcolor.length<ngroup){d3panels.displayError("add_points: pointcolor.length ("+pointcolor.length+") < ngroup ("+ngroup+")")}xlim=[0,2/Math.sqrt(3)];ylim=[0,1];plot_height=height-margin.top-margin.bottom;plot_width=width-margin.left-margin.right;if(plot_height>plot_width/xlim[1]){d=plot_height-plot_width/xlim[1];margin.top+=d/2;margin.bottom+=d/2;plot_height-=d}else{d=plot_width-plot_height*xlim[1];margin.left+=d/2;margin.right+=d/2;plot_width-=d}xscale=d3.scaleLinear().domain(xlim).range([margin.left,margin.left+plot_width]);yscale=d3.scaleLinear().domain(ylim).range([plot_height+margin.top,margin.top]);pscale=function pscale(p){sum=d3panels.sumArray(p);return{x:xscale((p[0]*2+p[1])/Math.sqrt(3)/sum),y:yscale(p[1]/sum)}};xy=function(){var k,len1,results;results=[];for(k=0,len1=p.length;k<len1;k++){v=p[k];results.push({x:(v[0]*2+v[1])/Math.sqrt(3),y:v[1]})}return results}();svg=selection.append("svg");svg.attr("width",width).attr("height",height).attr("class","d3panels");frame=svg.append("g").attr("id","frame");vertices=[{x:xlim[0],y:ylim[0]},{x:xlim[1]/2,y:ylim[1]},{x:xlim[1],y:ylim[0]}];framefunc=d3.line().x(function(d){return xscale(vertices[d].x)}).y(function(d){return yscale(vertices[d].y)});indices=function(){var results;results=[];for(i in vertices){results.push(+i)}return results}().concat(0);frame.append("path").datum(indices).attr("d",framefunc).attr("fill",rectcolor).attr("stroke",boxcolor).attr("stroke-width",boxwidth);if(gridlines>0){gr=function(){var results=[];for(var k=1;1<=gridlines?k<=gridlines:k>=gridlines;1<=gridlines?k++:k--){results.push(k)}return results}.apply(this).map(function(i){return i/(gridlines+1)});p1=gr.map(function(x){return[x,0,1-x]});p2=gr.map(function(x){return[x,1-x,0]});p3=gr.map(function(x){return[0,1-x,x]});p4=gr.map(function(x){return[1-x,0,x]});first=p1.concat(p2).concat(p3);second=p2.concat(p3).concat(p4);g=frame.append("g").attr("class","gridlines").selectAll("empty").data(first).enter().append("line").attr("x1",function(d){return pscale(d).x}).attr("y1",function(d){return pscale(d).y}).attr("x2",function(d,i){return pscale(second[i]).x}).attr("y2",function(d,i){return pscale(second[i]).y}).attr("stroke",gridcolor).attr("stroke-width",gridwidth).attr("shape-rendering","crispEdges").style("pointer-events","none").attr("fill","none");frame.append("path").datum(indices).attr("d",framefunc).attr("fill","none").attr("stroke",boxcolor).attr("stroke-width",boxwidth)}frame.append("g").attr("class","title").append("text").text(title).attr("x",plot_width/2+margin.left).attr("y",margin.top-titlepos);frame.append("g").attr("id","labels").selectAll("empty").data(vertices).enter().append("text").attr("x",function(d,i){return xscale(d.x)+[-1,+1,+1][i]*labelpos}).attr("y",function(d){return yscale(d.y)}).style("dominant-baseline","middle").style("text-anchor",function(d,i){return["end","start","start"][i]}).text(function(d,i){return labels[i]});points=svg.append("g").attr("id","points").selectAll("empty").data(p).enter().append("circle").attr("r",pointsize).attr("cx",function(d){return pscale(d).x}).attr("cy",function(d){return pscale(d).y}).attr("fill",function(d,i){return pointcolor[group[i]]}).attr("stroke",pointstroke).attr("stroke-width",1);return indtip=d3panels.tooltip_create(d3.select("body"),points,{tipclass:tipclass},function(d,i){return indID[i]})};chart.xscale=function(){return xscale};chart.yscale=function(){return yscale};chart.pscale=function(){return pscale};chart.points=function(){return points};chart.indtip=function(){return indtip};chart.svg=function(){return svg};chart.remove=function(){svg.remove();d3panels.tooltip_destroy(indtip);return null};return chart};"use strict";d3panels.histchart=function(chartOpts){var chart,curves,density,indtip,linecolor,linecolorhilit,linewidth,linewidthhilit,ref,ref1,ref10,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,rotate_ylab,svg,tipclass,xlab,xlim,xscale,ylab,ylim,yscale;if(chartOpts==null){chartOpts={}}xlim=(ref=chartOpts!=null?chartOpts.xlim:void 0)!=null?ref:null;ylim=(ref1=chartOpts!=null?chartOpts.ylim:void 0)!=null?ref1:null;xlab=(ref2=chartOpts!=null?chartOpts.xlab:void 0)!=null?ref2:"";ylab=(ref3=chartOpts!=null?chartOpts.ylab:void 0)!=null?ref3:"";rotate_ylab=(ref4=chartOpts!=null?chartOpts.rotate_ylab:void 0)!=null?ref4:null;linecolor=(ref5=chartOpts!=null?chartOpts.linecolor:void 0)!=null?ref5:null;linecolorhilit=(ref6=chartOpts!=null?chartOpts.linecolorhilit:void 0)!=null?ref6:null;linewidth=(ref7=chartOpts!=null?chartOpts.linewidth:void 0)!=null?ref7:2;linewidthhilit=(ref8=chartOpts!=null?chartOpts.linewidthhilit:void 0)!=null?ref8:2;density=(ref9=chartOpts!=null?chartOpts.density:void 0)!=null?ref9:true;tipclass=(ref10=chartOpts!=null?chartOpts.tipclass:void 0)!=null?ref10:"tooltip";xscale=null;yscale=null;curves=null;indtip=null;svg=null;chart=function chart(selection,data){var add_curves,breaks,brlim,d,f,freq,i,j,k,l,len,len1,maxn,maxpos,myframe,n_ind,p,path_data,path_x,path_y,pt,ref11,x,xv,xx;if(data.x==null){d3panels.displayError("histchart: data.x is missing")}x=data.x;data.group=function(){var results;results=[];for(i in x){results.push(+i+1)}return results}();n_ind=x.length;if(data.breaks==null){maxn=d3.max(function(){var k,len,results;results=[];for(k=0,len=x.length;k<len;k++){xv=x[k];results.push(xv.length)}return results}());data.breaks=[2*Math.ceil(Math.sqrt(maxn)+1)]}breaks=d3panels.forceAsArray(data.breaks);xlim=xlim!=null?xlim:d3panels.matrixExtent(x);if(breaks.length===1){breaks=d3panels.calc_breaks(breaks[0],xlim[0],xlim[1])}brlim=[d3.min(breaks)-1e-6,d3.max(breaks)+1e-6];x=function(){var k,len,results;results=[];for(k=0,len=x.length;k<len;k++){xx=x[k];results.push(function(){var l,len1,results1;results1=[];for(l=0,len1=xx.length;l<len1;l++){xv=xx[l];if(xv>=brlim[0]&xv<=brlim[1]){results1.push(xv)}}return results1}())}return results}();freq=function(){var k,len,results;results=[];for(k=0,len=x.length;k<len;k++){xv=x[k];results.push(d3panels.calc_freq(xv,breaks,!density))}return results}();maxpos=[];for(k=0,len=freq.length;k<len;k++){f=freq[k];pt={x:breaks[1],y:f[0]};ref11=d3.range(f.length);for(l=0,len1=ref11.length;l<len1;l++){j=ref11[l];if(f[j]>pt.y){pt.y=f[j];pt.x=breaks[j+1]}}maxpos.push(pt)}ylim=ylim!=null?ylim:[0,d3panels.matrixMax(freq)*1.05];chartOpts.xlim=xlim;chartOpts.ylim=ylim;chartOpts.xNA=false;chartOpts.yNA=false;chartOpts.xlab=xlab;chartOpts.ylab=ylab;chartOpts.rotate_ylab=rotate_ylab;myframe=d3panels.panelframe(chartOpts);myframe(selection);svg=myframe.svg();xscale=myframe.xscale();yscale=myframe.yscale();path_data=function(){var len2,m,results;results=[];for(m=0,len2=freq.length;m<len2;m++){f=freq[m];results.push(d3panels.calc_hist_path(f,breaks))}return results}();path_x=function(){var len2,m,results;results=[];for(m=0,len2=path_data.length;m<len2;m++){p=path_data[m];results.push(function(){var len3,n,results1;results1=[];for(n=0,len3=p.length;n<len3;n++){d=p[n];results1.push(d.x)}return results1}())}return results}();path_y=function(){var len2,m,results;results=[];for(m=0,len2=path_data.length;m<len2;m++){p=path_data[m];results.push(function(){var len3,n,results1;results1=[];for(n=0,len3=p.length;n<len3;n++){d=p[n];results1.push(d.y)}return results1}())}return results}();add_curves=d3panels.add_curves({linecolor:linecolor,linecolorhilit:linecolorhilit,linewidth:linewidth,linewidthhilit:linewidthhilit,tipclass:tipclass});add_curves(myframe,{x:path_x,y:path_y,indID:data.indID,group:data.group});curves=add_curves.curves();indtip=add_curves.indtip();return myframe.box().raise()};chart.xscale=function(){return xscale};chart.yscale=function(){return yscale};chart.curves=function(){return curves};chart.indtip=function(){return indtip};chart.svg=function(){return svg};chart.remove=function(){svg.remove();d3panels.tooltip_dstroy(indtip);return null};return chart};"use strict";d3panels.slider=function(chartOpts){var buttoncolor,buttondotcolor,buttondotsize,buttonround,buttonsize,buttonstroke,_chart,height,margin,nticks,rectcolor,rectheight,ref,ref1,ref10,ref11,ref12,ref13,ref14,ref15,ref16,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,slider_svg,stopindex,textsize,tickgap,tickheight,ticks,ticks_at_stops,value,width;if(chartOpts==null){chartOpts={}}width=(ref=chartOpts!=null?chartOpts.width:void 0)!=null?ref:800;height=(ref1=chartOpts!=null?chartOpts.height:void 0)!=null?ref1:80;margin=(ref2=chartOpts!=null?chartOpts.margin:void 0)!=null?ref2:{left:25,right:25,inner:0,top:40,bottom:40};rectheight=(ref3=chartOpts!=null?chartOpts.rectheight:void 0)!=null?ref3:10;rectcolor=(ref4=chartOpts!=null?chartOpts.rectcolor:void 0)!=null?ref4:"#ccc";buttonsize=(ref5=chartOpts!=null?chartOpts.buttonsize:void 0)!=null?ref5:rectheight*2;buttoncolor=(ref6=chartOpts!=null?chartOpts.buttoncolor:void 0)!=null?ref6:"#eee";buttonstroke=(ref7=chartOpts!=null?chartOpts.buttonstroke:void 0)!=null?ref7:"black";buttonround=(ref8=chartOpts!=null?chartOpts.buttonround:void 0)!=null?ref8:buttonsize*.2;buttondotcolor=(ref9=chartOpts!=null?chartOpts.buttondotcolor:void 0)!=null?ref9:"slateblue";buttondotsize=(ref10=chartOpts!=null?chartOpts.buttondotsize:void 0)!=null?ref10:buttonsize/4;tickheight=(ref11=chartOpts!=null?chartOpts.tickheight:void 0)!=null?ref11:10;tickgap=(ref12=chartOpts!=null?chartOpts.tickgap:void 0)!=null?ref12:tickheight/2;textsize=(ref13=chartOpts!=null?chartOpts.textsize:void 0)!=null?ref13:14;nticks=(ref14=chartOpts!=null?chartOpts.nticks:void 0)!=null?ref14:5;ticks=(ref15=chartOpts!=null?chartOpts.ticks:void 0)!=null?ref15:null;ticks_at_stops=(ref16=chartOpts!=null?chartOpts.ticks_at_stops:void 0)!=null?ref16:true;value=0;stopindex=0;slider_svg=null;_chart=function chart(selection,callback,range,stops,initial_value){var button,clamp_pixels,dragged,end_drag,xcscale,xscale;margin=d3panels.check_listarg_v_default(margin,{left:25,right:25,inner:0,top:40,bottom:40});margin.left+=margin.inner;margin.right+=margin.inner;if(range==null){range=[margin.left,width-margin.right]}if(margin.top!=null&&margin.top+margin.bottom>0){margin.top=height*margin.top/(margin.top+margin.bottom)}else{margin.top=height/2}if(initial_value!=null){value=initial_value;if(value<range[0]){value=range[0]}if(value>range[1]){value=range[1]}if(stops!=null){stopindex=d3panels.index_of_nearest(value,stops)}if(stops!=null){value=stops[stopindex]}}else{if(stops!=null){stopindex=Math.floor(Math.random()*stops.length);value=stops[stopindex]}else{value=(range[1]-range[0])*Math.random()+range[0]}}slider_svg=selection.insert("svg").attr("height",height).attr("width",width);xcscale=d3.scaleLinear().range([margin.left,width-margin.right]).domain(range).clamp(true);xscale=function xscale(d){if(stops!=null){return xcscale(stops[d3panels.index_of_nearest(d,stops)])}return xcscale(d)};clamp_pixels=function clamp_pixels(pixels,interval){if(pixels<interval[0]){return interval[0]}if(pixels>interval[1]){return interval[1]}return pixels};slider_svg.insert("rect").attr("x",margin.left).attr("y",margin.top-rectheight/2).attr("rx",rectheight*.3).attr("ry",rectheight*.3).attr("width",width-margin.left-margin.right).attr("height",rectheight).attr("fill",rectcolor);if(ticks==null){ticks=xcscale.ticks(nticks)}slider_svg.selectAll("empty").data(ticks).enter().insert("line").attr("x1",function(d){return xcscale(d)}).attr("x2",function(d){return xcscale(d)}).attr("y1",margin.top+rectheight/2+tickgap).attr("y2",margin.top+rectheight/2+tickgap+tickheight).attr("stroke","black").attr("shape-rendering","crispEdges");slider_svg.selectAll("empty").data(ticks).enter().insert("text").attr("x",function(d){return xcscale(d)}).attr("y",margin.top+rectheight/2+tickgap*2+tickheight).text(function(d){return d}).style("font-size",textsize).style("dominant-baseline","hanging").style("text-anchor","middle").style("pointer-events","none").style("-webkit-user-select","none").style("-moz-user-select","none").style("-ms-user-select","none");if(stops!=null&&ticks_at_stops){slider_svg.selectAll("empty").data(stops).enter().insert("line").attr("x1",function(d){return xcscale(d)}).attr("x2",function(d){return xcscale(d)}).attr("y1",margin.top-rectheight/2-tickgap).attr("y2",margin.top-rectheight/2-tickgap-tickheight).attr("stroke","black").attr("shape-rendering","crispEdges")}button=slider_svg.insert("g").attr("id","button").attr("transform","translate("+xscale(value)+",0)");button.insert("rect").attr("x",-buttonsize/2).attr("y",margin.top-buttonsize/2).attr("height",buttonsize).attr("width",buttonsize).attr("rx",buttonround).attr("ry",buttonround).attr("stroke",buttonstroke).attr("stroke-width",2).attr("fill",buttoncolor);button.insert("circle").attr("cx",0).attr("cy",margin.top).attr("r",buttondotsize).attr("fill",buttondotcolor);dragged=function dragged(d){var clamped_pixels,pixel_value;pixel_value=d3.event.x;clamped_pixels=clamp_pixels(pixel_value,[margin.left,width-margin.right]);value=xcscale.invert(clamped_pixels);d3.select(this).attr("transform","translate("+xcscale(value)+",0)");if(stops!=null){stopindex=d3panels.index_of_nearest(value,stops);value=stops[stopindex]}if(callback!=null){return callback(_chart)}};end_drag=function end_drag(d){var clamped_pixels,pixel_value;pixel_value=d3.event.x;clamped_pixels=clamp_pixels(pixel_value,[margin.left,width-margin.right]);value=xcscale.invert(clamped_pixels);if(stops!=null){stopindex=d3panels.index_of_nearest(value,stops);value=stops[stopindex]}if(callback!=null){callback(_chart)}return d3.select(this).attr("transform","translate("+xcscale(value)+",0)")};button.call(d3.drag().on("drag",dragged).on("end",end_drag));if(callback!=null){return callback(_chart)}};_chart.value=function(){return value};_chart.stopindex=function(){return stopindex};_chart.remove=function(){return slider_svg.remove()};return _chart};"use strict";d3panels.double_slider=function(chartOpts){var buttoncolor,buttondotcolor,buttondotsize,buttonround,buttonsize,buttonstroke,_chart,height,margin,nticks,rectcolor,rectheight,ref,ref1,ref10,ref11,ref12,ref13,ref14,ref15,ref16,ref2,ref3,ref4,ref5,ref6,ref7,ref8,ref9,slider_svg,stopindex,textsize,tickgap,tickheight,ticks,ticks_at_stops,value,width;if(chartOpts==null){chartOpts={}}width=(ref=chartOpts!=null?chartOpts.width:void 0)!=null?ref:800;height=(ref1=chartOpts!=null?chartOpts.height:void 0)!=null?ref1:80;margin=(ref2=chartOpts!=null?chartOpts.margin:void 0)!=null?ref2:{left:25,right:25,inner:0,top:40,bottom:40};rectheight=(ref3=chartOpts!=null?chartOpts.rectheight:void 0)!=null?ref3:10;rectcolor=(ref4=chartOpts!=null?chartOpts.rectcolor:void 0)!=null?ref4:"#ccc";buttonsize=(ref5=chartOpts!=null?chartOpts.buttonsize:void 0)!=null?ref5:rectheight*2;buttoncolor=(ref6=chartOpts!=null?chartOpts.buttoncolor:void 0)!=null?ref6:"#eee";buttonstroke=(ref7=chartOpts!=null?chartOpts.buttonstroke:void 0)!=null?ref7:"black";buttonround=(ref8=chartOpts!=null?chartOpts.buttonround:void 0)!=null?ref8:buttonsize*.2;buttondotcolor=(ref9=chartOpts!=null?chartOpts.buttondotcolor:void 0)!=null?ref9:["slateblue","orchid"];buttondotsize=(ref10=chartOpts!=null?chartOpts.buttondotsize:void 0)!=null?ref10:buttonsize/4;tickheight=(ref11=chartOpts!=null?chartOpts.tickheight:void 0)!=null?ref11:10;tickgap=(ref12=chartOpts!=null?chartOpts.tickgap:void 0)!=null?ref12:tickheight/2;textsize=(ref13=chartOpts!=null?chartOpts.textsize:void 0)!=null?ref13:14;nticks=(ref14=chartOpts!=null?chartOpts.nticks:void 0)!=null?ref14:5;ticks=(ref15=chartOpts!=null?chartOpts.ticks:void 0)!=null?ref15:null;ticks_at_stops=(ref16=chartOpts!=null?chartOpts.ticks_at_stops:void 0)!=null?ref16:true;value=[0,0];stopindex=[0,0];slider_svg=null;if(!Array.isArray(buttoncolor)){buttoncolor=[buttoncolor,buttoncolor]}if(!Array.isArray(buttonstroke)){buttonstroke=[buttonstroke,buttonstroke]}if(!Array.isArray(buttondotcolor)){buttondotcolor=[buttondotcolor,buttondotcolor]}_chart=function chart(selection,callback1,callback2,range,stops,initial_value){var buttons,callbacks,clamp_pixels,dragged,end_drag,start_drag,xcscale,xscale;margin=d3panels.check_listarg_v_default(margin,{left:25,right:25,inner:0,top:40,bottom:40});callbacks=[callback1,callback2];margin.left+=margin.inner;margin.right+=margin.inner;if(range==null){range=[margin.left,width-margin.right]}if(margin.top!=null&&margin.top+margin.bottom>0){margin.top=height*margin.top/(margin.top+margin.bottom)}else{margin.top=height/2}if(initial_value!=null){value=initial_value.map(function(d){if(d<range[0]){return range[0]}if(d>range[1]){return range[1]}return d});if(stops!=null){stopindex=value.map(function(d){return d3panels.index_of_nearest(d,stops)})}if(stops!=null){value=stopindex.map(function(i){return stops[i]})}}else{if(stops!=null){stopindex=[0,1].map(function(i){return Math.floor(Math.random()*stops.length)});value=stopindex.map(function(i){return stops[i]})}else{value=[0,1].map(function(i){return(range[1]-range[0])*Math.random()+range[0]})}}slider_svg=selection.insert("svg").attr("height",height).attr("width",width);xcscale=d3.scaleLinear().range([margin.left,width-margin.right]).domain(range).clamp(true);xscale=function xscale(d){if(stops!=null){return xcscale(stops[d3panels.index_of_nearest(d,stops)])}return xcscale(d)};clamp_pixels=function clamp_pixels(pixels,interval){if(pixels<interval[0]){return interval[0]}if(pixels>interval[1]){return interval[1]}return pixels};slider_svg.insert("rect").attr("x",margin.left).attr("y",margin.top-rectheight/2).attr("rx",rectheight*.3).attr("ry",rectheight*.3).attr("width",width-margin.left-margin.right).attr("height",rectheight).attr("fill",rectcolor);if(ticks==null){ticks=xcscale.ticks(nticks)}slider_svg.selectAll("empty").data(ticks).enter().insert("line").attr("x1",function(d){return xcscale(d)}).attr("x2",function(d){return xcscale(d)}).attr("y1",margin.top+rectheight/2+tickgap).attr("y2",margin.top+rectheight/2+tickgap+tickheight).attr("stroke","black").attr("shape-rendering","crispEdges");slider_svg.selectAll("empty").data(ticks).enter().insert("text").attr("x",function(d){return xcscale(d)}).attr("y",margin.top+rectheight/2+tickgap*2+tickheight).text(function(d){return d}).style("font-size",textsize).style("dominant-baseline","hanging").style("text-anchor","middle").style("pointer-events","none").style("-webkit-user-select","none").style("-moz-user-select","none").style("-ms-user-select","none");if(stops!=null&&ticks_at_stops){slider_svg.selectAll("empty").data(stops).enter().insert("line").attr("x1",function(d){return xcscale(d)}).attr("x2",function(d){return xcscale(d)}).attr("y1",margin.top-rectheight/2-tickgap).attr("y2",margin.top-rectheight/2-tickgap-tickheight).attr("stroke","black").attr("shape-rendering","crispEdges")}buttons=[0,1].map(function(i){return slider_svg.insert("g").attr("id","button"+(i+1)).attr("transform",function(d){return"translate("+xscale(value[i])+",0)"})});[0,1].map(function(i){buttons[i].insert("rect").attr("x",-buttonsize/2).attr("y",margin.top-buttonsize/2).attr("height",buttonsize).attr("width",buttonsize).attr("rx",buttonround).attr("ry",buttonround).attr("stroke",buttonstroke[i]).attr("stroke-width",2).attr("fill",buttoncolor[i]);return buttons[i].insert("circle").attr("cx",0).attr("cy",margin.top).attr("r",buttondotsize).attr("fill",buttondotcolor[i])});start_drag=function start_drag(i){return function(d){return buttons[i].raise()}};dragged=function dragged(i){return function(d){var clamped_pixels,pixel_value;pixel_value=d3.event.x;clamped_pixels=clamp_pixels(pixel_value,[margin.left,width-margin.right]);value[i]=xcscale.invert(clamped_pixels);d3.select(this).attr("transform","translate("+xcscale(value[i])+",0)");if(stops!=null){stopindex[i]=d3panels.index_of_nearest(value[i],stops);value[i]=stops[stopindex[i]]}if(callbacks[i]!=null){return callbacks[i](_chart)}}};end_drag=function end_drag(i){return function(d){var clamped_pixels,pixel_value;pixel_value=d3.event.x;clamped_pixels=clamp_pixels(pixel_value,[margin.left,width-margin.right]);value[i]=xcscale.invert(clamped_pixels);if(stops!=null){stopindex[i]=d3panels.index_of_nearest(value[i],stops);value[i]=stops[stopindex[i]]}if(callbacks[i]!=null){callbacks[i](_chart)}return d3.select(this).attr("transform","translate("+xcscale(value[i])+",0)")}};return[0,1].map(function(i){buttons[i].call(d3.drag().on("start",start_drag(i)).on("drag",dragged(i)).on("end",end_drag(i)));if(callbacks[i]!=null){return callbacks[i](_chart)}})};_chart.value=function(){return value};_chart.stopindex=function(){return stopindex};_chart.remove=function(){return slider_svg.remove()};return _chart};"use strict";d3panels.tooltip_create=function(selection,objects,options,tooltip_func){var direction,fill,fontcolor,fontsize,in_duration,out_duration,pad,ref,ref1,ref2,ref3,ref4,tipclass,tipdiv,tipgroup,triChar,tridiv;tipclass=(ref=options!=null?options.tipclass:void 0)!=null?ref:"d3panels-tooltip";direction=(ref1=options!=null?options.direction:void 0)!=null?ref1:"east";out_duration=(ref2=options!=null?options.out_duration:void 0)!=null?ref2:1e3;in_duration=(ref3=options!=null?options.in_duration:void 0)!=null?ref3:0;pad=(ref4=options!=null?options.pad:void 0)!=null?ref4:8;fill=options!=null?options.fill:void 0;fontcolor=options!=null?options.fontcolor:void 0;fontsize=options!=null?options.fontsize:void 0;tipgroup=selection.append("g").attr("class","d3panels-tooltip "+tipclass).style("opacity",0).datum({direction:direction,pad:pad});tipdiv=tipgroup.append("div").attr("class","d3panels-tooltip "+tipclass);if(direction==="east"){triChar="â—€"}else if(direction==="west"){triChar="â–¶"}else if(direction==="north"){triChar="â–¼"}else if(direction==="south"){triChar="â–²"}else{d3panels.displayError("tooltip_create: invalid direction ("+direction+")")}tridiv=tipgroup.append("div").attr("class","d3panels-tooltip-tri "+tipclass).html(triChar);if(fill!=null){tipdiv.style("background",fill)}if(fontcolor!=null){tipdiv.style("color",fontcolor)}if(fontsize!=null){tipdiv.style("font-size",fontsize+"px")}if(fill!=null){tridiv.style("color",fill)}if(fontsize!=null){tridiv.style("font-size",fontsize+"px")}objects.on("mouseover."+tipclass,function(d,i){var mouseX,mouseY;mouseX=d3.event.pageX*1;mouseY=d3.event.pageY*1;tipdiv.html(tooltip_func(d,i));d3panels.tooltip_move(tipgroup,mouseX,mouseY);return d3panels.tooltip_show(tipgroup,in_duration)});objects.on("mouseout."+tipclass,function(d){return d3panels.tooltip_hide(tipgroup,out_duration)});return tipgroup};d3panels.tooltip_move=function(tipgroup,x,y){var direction,divpad,fontsize,pad,posX,posY,shiftX,shiftY,tipbox_height,tipbox_width,tipdiv,triX,triY,tridiv;tipdiv=tipgroup.select("div.d3panels-tooltip");tridiv=tipgroup.select("div.d3panels-tooltip-tri");tipbox_height=tipdiv.node().getBoundingClientRect().height*1;tipbox_width=tipdiv.node().getBoundingClientRect().width*1;fontsize=tridiv.style("font-size").replace("px","")*1;pad=tipgroup.datum().pad*1+fontsize;direction=tipgroup.datum().direction;shiftX=shiftY=0;if(direction==="east"){posX=x+pad;posY=y-tipbox_height/2;divpad=tipdiv.style("padding-left").replace("px","")*1;shiftX=-fontsize-divpad;shiftY=tipbox_height/2-fontsize/2}else if(direction==="west"){posX=x-tipbox_width*1-pad;posY=y-tipbox_height/2;divpad=tipdiv.style("padding-right").replace("px","")*1;shiftX=tipbox_width-fontsize+divpad;shiftY=tipbox_height/2-fontsize/2}else if(direction==="north"){posX=x-tipbox_width/2;posY=y-tipbox_height-pad;divpad=tipdiv.style("padding-bottom").replace("px","")*1;shiftX=tipbox_width/2-fontsize;shiftY=tipbox_height+divpad/2-fontsize/2}else if(direction==="south"){posX=x-tipbox_width/2;posY=y+pad;divpad=tipdiv.style("padding-top").replace("px","")*1;shiftX=+tipbox_width/2-fontsize;shiftY=-fontsize}tipdiv.style("left",posX+"px").style("top",posY+"px");triX=posX+shiftX;triY=posY+shiftY;return tridiv.style("left",triX+"px").style("top",triY+"px").style("width",tipbox_width).style("height",tipbox_height)};d3panels.tooltip_text=function(tipgroup,text){return tipgroup.select("div.d3panels-tooltip").html(text)};d3panels.tooltip_show=function(tipgroup,duration){return tipgroup.transition().duration(duration).style("opacity",1)};d3panels.tooltip_hide=function(tipgroup,duration){return tipgroup.transition().duration(duration).style("opacity",0)};d3panels.tooltip_destroy=function(tipgroup){return tipgroup.remove()};if(typeof define==="function"&&define.amd)this.d3panels=d3panels,define(d3panels);else if(typeof module==="object"&&module.exports)module.exports=d3panels;else this.d3panels=d3panels}();
\ No newline at end of file diff --git a/gn2/wqflask/static/new/javascript/dataset_select_menu_orig.js b/gn2/wqflask/static/new/javascript/dataset_select_menu_orig.js new file mode 100644 index 00000000..25a703e6 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/dataset_select_menu_orig.js @@ -0,0 +1,330 @@ +var apply_default, check_search_term, dataset_info, group_info, make_default, open_window, populate_dataset, populate_group, populate_species, populate_type, process_json, redo_dropdown; +process_json = function(data) { + window.jdata = data; + populate_species(); + if ($('#type').length > 0) { //This is to determine if it's the index page or the submit_trait page (which only has species and group selection and no make default option) + return apply_default(); + } +}; + +range = function(size, startAt=0) { + return [...Array(size).keys()].map(idx => idx + startAt); +}; + +indicate_error = function (jqXHR, textStatus, errorThrown) { + errorElement = document.createElement("span"); + errorElement.setAttribute("class", "alert-danger"); + errorText = document.createTextNode( + "There was an error retrieving and setting the menu. Try again later."); + errorElement.appendChild(errorText); + if (document.getElementById("search")){ + form = document.getElementById("search").getElementsByTagName("form")[0]; + form.prepend(errorElement); + disable_element = function(select) { + select.setAttribute("disabled", "disabled"); + }; + Array.from(form.getElementsByTagName("select")).forEach(disable_element); + Array.from(form.getElementsByTagName("textarea")).forEach(disable_element); + } +}; + +defaultStatusCodeFunctions = range(200, 400).reduce( + function(acc, scode) { + acc[scode] = indicate_error; + return acc; + }, {}); + +if (typeof gn_server_url === 'undefined'){ + gn_server_url = $("#search form").attr("data-gn_server_url") +} + +$.ajax(gn_server_url +'/menu/generate/json', { + dataType: 'json', + success: process_json, + error: indicate_error, + statusCode: { + ...defaultStatusCodeFunctions, + } +}); + +populate_species = function() { + var species_list; + species_list = this.jdata.species; + redo_dropdown($('#species'), species_list); + return populate_group(); +}; +window.populate_species = populate_species; + +populate_group = function() { + var group_list, species; + species = $('#species').val(); + group_list = this.jdata.groups[species]; + for (_i = 0, _len = group_list.length; _i < (_len - 1); _i++) { + if (group_list[_i][0] == "BXD300"){ + group_list.splice(_i, 1) + } + } + redo_dropdown($('#group'), group_list); + if ($('#type').length > 0) { //This is to determine if it's the index page or the submit_trait page (which only has species and group selection and no make default option) + return populate_type(); + } +}; +window.populate_group = populate_group; + +populate_type = function() { + var group, species, type_list; + species = $('#species').val(); + group = $('#group').val(); + type_list = this.jdata.types[species][group]; + redo_dropdown($('#type'), type_list); + return populate_dataset(); +}; +window.populate_type = populate_type; + +populate_dataset = function() { + var dataset_list, group, species, type; + species = $('#species').val(); + group = $('#group').val(); + type = $('#type').val(); + dataset_list = this.jdata.datasets[species][group][type]; + return redo_dropdown($('#dataset'), dataset_list); +}; +window.populate_dataset = populate_dataset; + +redo_dropdown = function(dropdown, items) { + var item, _i, _len, _results; + dropdown.empty(); + _results = []; + + if (dropdown.attr('id') == "species"){ + species_family_list = []; + for (_i = 0, _len = items.length; _i < _len; _i++) { + item = items[_i]; + species_family = item[2].toString() + species_family_list.push([item[0], item[1], species_family]) + } + + current_family = "" + this_opt_group = null + for (_i = 0, _len = species_family_list.length; _i < _len; _i++) { + item = species_family_list[_i]; + if (item[2] != "None" && current_family == ""){ + current_family = item[2] + this_opt_group = $("<optgroup label=\"" + item[2] + "\">") + this_opt_group.append($("<option />").val(item[0]).text(item[1])); + } else if (current_family != "" && item[2] == current_family){ + this_opt_group.append($("<option />").val(item[0]).text(item[1])); + if (_i == species_family_list.length - 1){ + _results.push(dropdown.append(this_opt_group)) + } + } else if (current_family != "" && item[2] != current_family && item[2] != "None"){ + current_family = item[2] + _results.push(dropdown.append(this_opt_group)) + this_opt_group = $("<optgroup label=\"" + current_family + "\">") + this_opt_group.append($("<option />").val(item[0]).text(item[1])); + if (_i == species_family_list.length - 1){ + _results.push(dropdown.append(this_opt_group)) + } + } else if (current_family != "" && this_opt_group != null && item[2] == "None"){ + _results.push(dropdown.append(this_opt_group)) + current_family = "" + _results.push(dropdown.append($("<option />").val(item[0]).text(item[1]))); + } else { + _results.push(dropdown.append($("<option />").val(item[0]).text(item[1]))); + } + } + } else if (dropdown.attr('id') == "group"){ + group_family_list = []; + for (_i = 0, _len = items.length; _i < _len; _i++) { + item = items[_i]; + group_family = item[2].toString().split(":")[1] + group_family_list.push([item[0], item[1], group_family]) + } + + current_family = "" + this_opt_group = null + for (_i = 0, _len = group_family_list.length; _i < _len; _i++) { + item = group_family_list[_i]; + if (item[2] != "None" && current_family == ""){ + current_family = item[2] + this_opt_group = $("<optgroup label=\"" + item[2] + "\">") + this_opt_group.append($("<option />").val(item[0]).text(item[1])); + } else if (current_family != "" && item[2] == current_family){ + this_opt_group.append($("<option />").val(item[0]).text(item[1])); + if (_i == group_family_list.length - 1){ + _results.push(dropdown.append(this_opt_group)) + } + } else if (current_family != "" && item[2] != current_family && item[2] != "None"){ + current_family = item[2] + _results.push(dropdown.append(this_opt_group)) + this_opt_group = $("<optgroup label=\"" + current_family + "\">") + this_opt_group.append($("<option />").val(item[0]).text(item[1])); + if (_i == group_family_list.length - 1){ + _results.push(dropdown.append(this_opt_group)) + } + } else if (current_family != "" && this_opt_group != null && item[2] == "None"){ + _results.push(dropdown.append(this_opt_group)) + current_family = "" + _results.push(dropdown.append($("<option />").val(item[0]).text(item[1]))); + } else { + _results.push(dropdown.append($("<option />").val(item[0]).text(item[1]))); + } + } + } else if (dropdown.attr('id') == "type"){ + type_family_list = []; + for (_i = 0, _len = items.length; _i < _len; _i++) { + item = items[_i]; + type_family_list.push([item[0], item[1], item[2]]) + } + + current_family = "" + this_opt_group = null + for (_i = 0, _len = type_family_list.length; _i < _len; _i++) { + item = type_family_list[_i]; + if (item[2] != "None" && current_family == ""){ + current_family = item[2] + this_opt_group = $("<optgroup label=\"" + item[2] + "\">") + this_opt_group.append($("<option />").val(item[0]).text(item[1])); + if (_i == type_family_list.length - 1){ + _results.push(dropdown.append(this_opt_group)) + } + } else if (current_family != "" && item[2] == current_family){ + this_opt_group.append($("<option />").val(item[0]).text(item[1])); + if (_i == type_family_list.length - 1){ + _results.push(dropdown.append(this_opt_group)) + } + } else if (current_family != "" && item[2] != current_family && item[2] != "None"){ + current_family = item[2] + _results.push(dropdown.append(this_opt_group)) + this_opt_group = $("<optgroup label=\"" + current_family + "\">") + this_opt_group.append($("<option />").val(item[0]).text(item[1])); + if (_i == type_family_list.length - 1){ + _results.push(dropdown.append(this_opt_group)) + } + } else { + _results.push(dropdown.append(this_opt_group)) + current_family = "" + _results.push(dropdown.append($("<option />").val(item[0]).text(item[1]))); + } + } + } else { + for (_i = 0, _len = items.length; _i < _len; _i++) { + item = items[_i]; + if (item.length > 2){ + _results.push(dropdown.append($("<option data-id=\""+item[0]+"\" />").val(item[1]).text(item[2]))); + } else { + _results.push(dropdown.append($("<option />").val(item[0]).text(item[1]))); + } + } + } + + return _results; +}; + +$('#species').change((function(_this) { + return function() { + return populate_group(); + }; +})(this)); + +$('#group').change((function(_this) { + return function() { + if ($('#type').length > 0) { //This is to determine if it's the index page or the submit_trait page (which only has species and group selection and no make default option) + return populate_type(); + } + else { + return false + } + }; +})(this)); + +$('#type').change((function(_this) { + return function() { + return populate_dataset(); + }; +})(this)); + +open_window = function(url, name) { + var options; + options = "menubar=yes,toolbar=yes,titlebar=yes,location=yes,resizable=yes,status=yes,scrollbars=yes,directories=yes,width=900"; + return open(url, name, options).focus(); +}; + +group_info = function() { + var group, species, url; + species = $('#species').val(); + group = $('#group').val(); + url = "http://gn1.genenetwork.org/" + species + "Cross.html#" + group; + return open_window(url, "_blank"); +}; +$('#group_info').click(group_info); + +dataset_info = function() { + var dataset, url; + accession_id = $('#dataset option:selected').data("id"); + if (accession_id != "None") { + url = "https://info.genenetwork.org/infofile/source.php?GN_AccesionId=" + accession_id + } else { + species = $('#species').val(); + group = $('#group').val(); + url = "https://info.genenetwork.org/species/source.php?SpeciesName=" + species + "&InbredSetName=" + group + } + return open_window(url, "_blank"); +}; +$('#dataset_info').click(dataset_info); + +make_default = function() { + var holder, item, jholder, _i, _len, _ref; + alert("The current settings are now your default.") + holder = {}; + _ref = ['species', 'group', 'type', 'dataset']; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + item = _ref[_i]; + holder[item] = $("#" + item).val(); + } + jholder = JSON.stringify(holder); + return $.cookie('search_defaults', jholder, { + expires: 365 + }); +}; + +apply_default = function() { + var defaults, item, populate_function, _i, _len, _ref, _results; + defaults = $.cookie('search_defaults'); + if (defaults) { + defaults = $.parseJSON(defaults); + } else { + defaults = { + species: "mouse", + group: "BXD", + type: "Hippocampus mRNA", + dataset: "HC_M2_0606_P" + }; + } + + _ref = [['species', 'group'], ['group', 'type'], ['type', 'dataset'], ['dataset', null]]; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + item = _ref[_i]; + $("#" + item[0]).val(defaults[item[0]]); + if (item[1]) { + populate_function = "populate_" + item[1]; + _results.push(window[populate_function]()); + } else { + _results.push(void 0); + } + } + return _results; +}; + +check_search_term = function() { + var or_search_term, and_search_term; + or_search_term = $('#or_search').val(); + and_search_term = $('#and_search').val(); + if (or_search_term === "" && and_search_term === "") { + alert("Please enter one or more search terms or search equations."); + return false; + } +}; + +$("#make_default").click(make_default); diff --git a/gn2/wqflask/static/new/javascript/draw_corr_scatterplot.js b/gn2/wqflask/static/new/javascript/draw_corr_scatterplot.js new file mode 100644 index 00000000..f883c0d9 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/draw_corr_scatterplot.js @@ -0,0 +1,913 @@ +var chart; +var srchart; + +x_val_range = js_data.x_range[1] - js_data.x_range[0] +y_val_range = js_data.y_range[1] - js_data.y_range[0] + +if (x_val_range >= 2 && x_val_range < 9){ + x_tick_digits = '.1f' +} else if (x_val_range >= 0.8 && x_val_range < 2) { + x_tick_digits = '.2f' +} else if (x_val_range < 0.8) { + x_tick_digits = '.3f' +} else { + x_tick_digits = 'f' +} + +if (y_val_range >= 2 && y_val_range < 8){ + y_tick_digits = '.1f' +} else if (y_val_range >= 0.8 && y_val_range < 2) { + y_tick_digits = '.2f' +} else if (y_val_range < 0.8) { + y_tick_digits = '.3f' +} else { + y_tick_digits = 'f' +} + +console.log("y_digits:", y_tick_digits) + +var layout = { + height: 700, + width: 800, + margin: { + l: 70, + r: 30, + t: 90, + b: 50 + }, + xaxis: { + range: [js_data.x_range[0], js_data.x_range[1]], + title: js_data.trait_1, + zeroline: false, + visible: true, + linecolor: 'black', + linewidth: 1, + ticklen: 4, + tickformat: x_tick_digits + }, + yaxis: { + range: [js_data.y_range[0], js_data.y_range[1]], + title: js_data.trait_2, + zeroline: false, + visible: true, + linecolor: 'black', + linewidth: 1, + ticklen: 4, + tickformat: y_tick_digits, + automargin: true + }, + hovermode: "closest", + showlegend: false, + annotations:[{ + xref: 'paper', + yref: 'paper', + x: 1, + xanchor: 'right', + y: 1.05, + yanchor: 'top', + text: '<i>r</i> = ' + js_data.r_value.toFixed(3) + ', <i>p</i> = ' + js_data.p_value.toExponential(3) + ', <i>n</i> = ' + js_data.num_overlap, + showarrow: false, + font: { + size: 14 + }, + } + ] +} + +var sr_layout = { + height: 700, + width: 800, + margin: { + l: 60, + r: 30, + t: 80, + b: 50 + }, + xaxis: { + range: [js_data.sr_range[0], js_data.sr_range[1]], + title: js_data.trait_1, + zeroline: false, + visible: true, + linecolor: 'black', + linewidth: 1, + }, + yaxis: { + range: [js_data.sr_range[0], js_data.sr_range[1]], + title: js_data.trait_2, + zeroline: false, + visible: true, + linecolor: 'black', + linewidth: 1, + }, + hovermode: "closest", + showlegend: false, + annotations:[{ + xref: 'paper', + yref: 'paper', + x: 1, + xanchor: 'right', + y: 1.05, + yanchor: 'top', + text: '<i>rho</i> = ' + js_data.srr_value.toFixed(3) + ', <i>P</i> = ' + js_data.srp_value.toExponential(3) + ', <i>n</i> = ' + js_data.num_overlap, + showarrow: false, + font: { + size: 14 + }, + } +] +} + +var modebar_options = { + modeBarButtonsToAdd:[{ + name: 'Export as SVG', + icon: Plotly.Icons.camera, + click: function(gd) { + Plotly.downloadImage(gd, {format: 'svg'}) + } + }, + { + name: 'Export as JPEG', + icon: Plotly.Icons.disk, + click: function(gd) { + Plotly.downloadImage(gd, {format: 'jpeg'}) + } + }], + showEditInChartStudio: true, + plotlyServerURL: "https://chart-studio.plotly.com", + modeBarButtonsToRemove:['toImage', 'sendDataToCloud', 'hoverClosest', 'hoverCompare', 'hoverClosestCartesian', 'hoverCompareCartesian', 'lasso2d', 'toggleSpikelines'], + displaylogo: false +} + +cofactor1_dict = {} +ranked_cofactor1_dict = {} +//cofactor1_values = [] +//ranked_cofactor1_values = [] +cofactor2_dict = {} +ranked_cofactor2_dict = {} +//cofactor2_values = [] +//ranked_cofactor2_values = [] +cofactor3_dict = {} +ranked_cofactor3_dict = {} + +function drawg() { + x_values = [] + y_values = [] + sample_names = [] + for (j = 0; j < js_data.data[0].length; j++) { + x_values.push(js_data.data[0][j]) + y_values.push(js_data.data[1][j]) + sample_names.push(js_data.indIDs[j]) + } + + var trace1 = { + x: x_values, + y: y_values, + mode: 'markers', + text: sample_names, + hoverinfo: "text+x+y" + } + + var trace2 = { + x: [js_data.intercept_coords[0][0], js_data.intercept_coords[1][0]], + y: [js_data.intercept_coords[0][1], js_data.intercept_coords[1][1]], + mode: 'lines', + line: { + color: 'rgb(250, 60, 73)' + } + } + + Plotly.newPlot('scatterplot2', [trace2, trace1], layout, modebar_options) + +} + +function srdrawg() { + x_values = [] + y_values = [] + sample_names = [] + for (j = 0; j < js_data.rdata[0].length; j++) { + x_values.push(js_data.rdata[0][j]) + y_values.push(js_data.rdata[1][j]) + sample_names.push(js_data.indIDs[j]) + } + + var trace1 = { + x: x_values, + y: y_values, + mode: 'markers', + text: sample_names, + hoverinfo: "text+x+y" + } + + Plotly.newPlot('srscatterplot2', [trace1], sr_layout, modebar_options) +} + +function getdata() { + var data = []; + data.push({ + values: [], + slope: js_data.slope, + intercept: js_data.intercept + }); + + sizemin = 8; + sizemax = 30; + + samples1 = []; + samples2 = []; + samples3 = []; + + if ($('input[name=cofactor1_vals]').val()){ + vals1 = [] + val_sample_dict = {} + val_sample_pairs = $('input[name=cofactor1_vals]').val().split(",") + for (i=0; i < val_sample_pairs.length; i++) { + samples1.push(val_sample_pairs[i].split(":")[0]) + vals1.push(parseFloat(val_sample_pairs[i].split(":")[1])) + val_sample_dict[val_sample_pairs[i].split(":")[0]] = parseFloat(val_sample_pairs[i].split(":")[1]) + } + datamin1 = d3.min(vals1); + datamax1 = d3.max(vals1); + + cofactor1_dict = val_sample_dict + cofactor1_values = vals1 + } + + if ($('input[name=cofactor2_vals]').val()){ + vals2 = []; + val_sample_dict = {} + val_sample_pairs = $('input[name=cofactor2_vals]').val().split(",") + for (i=0; i < val_sample_pairs.length; i++) { + samples2.push(val_sample_pairs[i].split(":")[0]) + vals2.push(parseFloat(val_sample_pairs[i].split(":")[1])) + val_sample_dict[val_sample_pairs[i].split(":")[0]] = val_sample_pairs[i].split(":")[1] + } + datamin2 = d3.min(vals2); + datamax2 = d3.max(vals2); + + cofactor2_dict = val_sample_dict + cofactor2_values = vals2 + } + + if ($('input[name=cofactor3_vals]').val()){ + vals3 = []; + val_sample_dict = {} + val_sample_pairs = $('input[name=cofactor3_vals]').val().split(",") + for (i=0; i < val_sample_pairs.length; i++) { + samples3.push(val_sample_pairs[i].split(":")[0]) + vals3.push(parseFloat(val_sample_pairs[i].split(":")[1])) + val_sample_dict[val_sample_pairs[i].split(":")[0]] = val_sample_pairs[i].split(":")[1] + } + + datamin3 = d3.min(vals3); + datamax3 = d3.max(vals3); + + cofactor3_dict = val_sample_dict + cofactor3_values = vals3 + } + + x_values = [] + y_values = [] + sample_names = [] + sizes = [] + + size_cofactor_vals = [] + if ($('#cofactor1_type option:selected').val() == "size" && $('input[name=cofactor1_vals]').val()){ + size_cofactor_vals = cofactor1_values + cofactor_samples = samples1 + datamin = datamin1 + datamax = datamax1 + } else if ($('#cofactor2_type option:selected').val() == "size" && $('input[name=cofactor2_vals]').val()) { + size_cofactor_vals = cofactor2_values + cofactor_samples = samples2 + datamin = datamin2 + datamax = datamax2 + } else if ($('#cofactor3_type option:selected').val() == "size" && $('input[name=cofactor3_vals]').val()) { + size_cofactor_vals = cofactor3_values + cofactor_samples = samples3 + datamin = datamin3 + datamax = datamax3 + } + + unique_vals = [] + symbol_cofactor_vals = [] + if ($('#cofactor1_type option:selected').val() == "symbol" && $('input[name=cofactor1_vals]').val()){ + symbol_cofactor_vals = cofactor1_values + cofactor_samples = samples1 + } else if ($('#cofactor2_type option:selected').val() == "symbol" && $('input[name=cofactor2_vals]').val()) { + symbol_cofactor_vals = cofactor2_values + cofactor_samples = samples2 + } else if ($('#cofactor3_type option:selected').val() == "symbol" && $('input[name=cofactor3_vals]').val()) { + symbol_cofactor_vals = cofactor3_values + cofactor_samples = samples3 + } + + symbol_list = [] + if (symbol_cofactor_vals.length > 0) { + unique_vals = [...new Set(symbol_cofactor_vals)] + for (i=0; i<symbol_cofactor_vals.length; i++){ + val_pos = unique_vals.indexOf(symbol_cofactor_vals[i]) + if (val_pos != "-1") { + symbol_list.push(val_pos) + } else { + symbol_list.push(0) + } + } + } + + //This is needed to calculate samples shared by cofactors + cofactor_samples_array = [] + if (samples1.length > 0){ + cofactor_samples_array.push(samples1) + } + if (samples2.length > 0){ + cofactor_samples_array.push(samples2) + } + if (samples3.length > 0){ + cofactor_samples_array.push(samples3) + } + if (cofactor_samples_array.length > 0){ + shared_samples = _.intersection.apply(_, cofactor_samples_array) + } else { + shared_samples = js_data.indIDs + } + + for (j = 0; j < js_data.data[0].length; j++) { + + if (shared_samples.indexOf(js_data.indIDs[j]) == -1) { + continue + } + + sizev = 10; + datav = 0; + if (size_cofactor_vals.length > 0){ + if (cofactor_samples.indexOf(js_data.indIDs[j])) { + datav = size_cofactor_vals[j] + sizev = map1to2(datamin, datamax, sizemin, sizemax, datav); + } + } + + x_values.push(js_data.data[0][j]) + y_values.push(js_data.data[1][j]) + sample_names.push(js_data.indIDs[j]) + sizes.push(sizev) + + data[0].values.push({ + type: "normal", + x: js_data.data[0][j], + y: js_data.data[1][j], + name: js_data.indIDs[j], + size: sizev, + v3: datav + }); + } + + point_text = [] + for (j = 0; j < sample_names.length; j++) { + this_text = "" + this_text += sample_names[j] + if (sample_names[j] in cofactor1_dict){ + this_text += "<br>Cofactor 1: " + cofactor1_dict[sample_names[j]] + } + if (sample_names[j] in cofactor2_dict){ + this_text += "<br>Cofactor 2: " + cofactor2_dict[sample_names[j]] + } + if (sample_names[j] in cofactor3_dict){ + this_text += "<br>Cofactor 3: " + cofactor3_dict[sample_names[j]] + } + point_text.push(this_text) + } + + if (symbol_list.length > 0) { + var trace1 = { + x: x_values, + y: y_values, + mode: 'markers', + text: point_text, + hoverinfo: "text+x+y", + marker: { + color: 'rgb(66, 66, 245)', + symbol: symbol_list, + size: sizes + } + } + } else { + var trace1 = { + x: x_values, + y: y_values, + mode: 'markers', + text: point_text, + hoverinfo: "text+x+y", + marker: { + color: 'rgb(66, 66, 245)', + size: sizes + } + } + } + + var trace2 = { + x: [js_data.intercept_coords[0][0], js_data.intercept_coords[1][0]], + y: [js_data.intercept_coords[0][1], js_data.intercept_coords[1][1]], + mode: 'lines', + line: { + color: 'rgb(250, 60, 73)' + } + } + + return [trace2, trace1]; +} + +function map1to2 (min1, max1, min2, max2, v1) { + v2 = (v1 - min1) * (max2 - min2) / (max1 - min1) + min2; + return v2; +} + +function srgetdata() { + var data = []; + data.push({ + values: [], + slope: js_data.srslope, + intercept: js_data.srintercept + }); + + sizemin = 8; + sizemax = 30; + + ranked_cofactor_vals = "" + + samples1 = []; + samples2 = []; + samples3 = []; + + if ($('input[name=ranked_cofactor1_vals]').val()){ + vals1 = [] + val_sample_dict = {} + val_sample_pairs = $('input[name=ranked_cofactor1_vals]').val().split(",") + for (i=0; i < val_sample_pairs.length; i++) { + samples1.push(val_sample_pairs[i].split(":")[0]) + vals1.push(parseFloat(val_sample_pairs[i].split(":")[1])) + val_sample_dict[val_sample_pairs[i].split(":")[0]] = parseFloat(val_sample_pairs[i].split(":")[1]) + } + datamin1 = d3.min(vals1); + datamax1 = d3.max(vals1); + + ranked_cofactor1_dict = val_sample_dict + ranked_cofactor1_values = vals1 + } + + if ($('input[name=ranked_cofactor2_vals]').val()){ + vals2 = []; + val_sample_dict = {} + val_sample_pairs = $('input[name=ranked_cofactor2_vals]').val().split(",") + for (i=0; i < val_sample_pairs.length; i++) { + samples2.push(val_sample_pairs[i].split(":")[0]) + vals2.push(parseFloat(val_sample_pairs[i].split(":")[1])) + val_sample_dict[val_sample_pairs[i].split(":")[0]] = val_sample_pairs[i].split(":")[1] + } + datamin2 = d3.min(vals2); + datamax2 = d3.max(vals2); + + ranked_cofactor2_dict = val_sample_dict + ranked_cofactor2_values = vals2 + } + + if ($('input[name=ranked_cofactor3_vals]').val()){ + vals3 = []; + val_sample_dict = {} + val_sample_pairs = $('input[name=ranked_cofactor3_vals]').val().split(",") + for (i=0; i < val_sample_pairs.length; i++) { + samples3.push(val_sample_pairs[i].split(":")[0]) + vals3.push(parseFloat(val_sample_pairs[i].split(":")[1])) + val_sample_dict[val_sample_pairs[i].split(":")[0]] = val_sample_pairs[i].split(":")[1] + } + + datamin3 = d3.min(vals3); + datamax3 = d3.max(vals3); + + ranked_cofactor3_dict = val_sample_dict + ranked_cofactor3_values = vals3 + } + + x_values = [] + y_values = [] + sample_names = [] + sizes = [] + + if ($('#cofactor1_type option:selected').val() == "size" && $('input[name=ranked_cofactor1_vals]').val()){ + size_cofactor_vals = ranked_cofactor1_values + cofactor_samples = samples1 + datamin = datamin1 + datamax = datamax1 + } else if ($('#cofactor2_type option:selected').val() == "size" && $('input[name=ranked_cofactor2_vals]').val()) { + size_cofactor_vals = ranked_cofactor2_values + cofactor_samples = samples2 + datamin = datamin2 + datamax = datamax2 + } else if ($('#cofactor3_type option:selected').val() == "size" && $('input[name=ranked_cofactor3_vals]').val()) { + size_cofactor_vals = ranked_cofactor3_values + cofactor_samples = samples3 + datamin = datamin3 + datamax = datamax3 + } + + unique_vals = [] + symbol_cofactor_vals = [] + if ($('#cofactor1_type option:selected').val() == "symbol" && $('input[name=ranked_cofactor1_vals]').val()){ + symbol_cofactor_vals = cofactor1_values + cofactor_samples = samples1 + } else if ($('#cofactor2_type option:selected').val() == "symbol" && $('input[name=ranked_cofactor2_vals]').val()) { + symbol_cofactor_vals = cofactor2_values + cofactor_samples = samples2 + } else if ($('#cofactor3_type option:selected').val() == "symbol" && $('input[name=ranked_cofactor3_vals]').val()) { + symbol_cofactor_vals = cofactor3_values + cofactor_samples = samples3 + } + + symbol_list = [] + if (symbol_cofactor_vals.length > 0) { + unique_vals = [...new Set(symbol_cofactor_vals)] + for (i=0; i<symbol_cofactor_vals.length; i++){ + val_pos = unique_vals.indexOf(symbol_cofactor_vals[i]) + if (val_pos != "-1") { + symbol_list.push(val_pos) + } else { + symbol_list.push(0) + } + } + } + + //This is needed to calculate samples shared by cofactors + cofactor_samples_array = [] + if (samples1.length > 0){ + cofactor_samples_array.push(samples1) + } + if (samples2.length > 0){ + cofactor_samples_array.push(samples2) + } + if (samples3.length > 0){ + cofactor_samples_array.push(samples3) + } + if (cofactor_samples_array.length > 0){ + shared_samples = _.intersection.apply(_, cofactor_samples_array) + } else { + shared_samples = js_data.indIDs + } + + for (j = 0; j < js_data.rdata[0].length; j++) { + + if (shared_samples.indexOf(js_data.indIDs[j]) == -1) { + continue + } + + if (size_cofactor_vals.length > 0){ + if (cofactor_samples.indexOf(js_data.indIDs[j])) { + datav = size_cofactor_vals[j] + sizev = map1to2(datamin, datamax, sizemin, sizemax, datav); + } + } else { + sizev = 10; + } + + x_values.push(js_data.rdata[0][j]) + y_values.push(js_data.rdata[1][j]) + sample_names.push(js_data.indIDs[j]) + sizes.push(sizev) + + data[0].values.push({ + type: "ranked", + x: js_data.rdata[0][j], + y: js_data.rdata[1][j], + name: js_data.indIDs[j], + size: sizev, + }); + } + + point_text = [] + for (j = 0; j < sample_names.length; j++) { + this_text = "" + this_text += sample_names[j] + if (sample_names[j] in ranked_cofactor1_dict){ + this_text += "<br>Cofactor 1: " + ranked_cofactor1_dict[sample_names[j]] + } + if (sample_names[j] in ranked_cofactor2_dict){ + this_text += "<br>Cofactor 2: " + ranked_cofactor2_dict[sample_names[j]] + } + if (sample_names[j] in cofactor3_dict){ + this_text += "<br>Cofactor 3: " + ranked_cofactor3_dict[sample_names[j]] + } + point_text.push(this_text) + } + + var trace1 = { + x: x_values, + y: y_values, + mode: 'markers', + text: point_text, + hoverinfo: "text+x+y", + marker: { + color: 'rgb(66, 66, 245)', + symbol: symbol_list, + size: sizes + } + } + + var trace2 = { + x: [js_data.sr_intercept_coords[0][0], js_data.sr_intercept_coords[1][0]], + y: [js_data.sr_intercept_coords[0][1], js_data.sr_intercept_coords[1][1]], + mode: 'lines', + line: { + color: 'rgb(250, 60, 73)' + } + } + + return [trace2, trace1]; +} + +function chartupdatewh() { + var width = $("#width").val(); + var height = $("#height").val(); + + width_height_update = { + height: height, + width: width + } + + Plotly.newPlot('scatterplot2', getdata(), layout, modebar_options) + Plotly.relayout('scatterplot2', width_height_update) + + Plotly.newPlot('srscatterplot2', srgetdata(), sr_layout, modebar_options) + Plotly.relayout('srscatterplot2', width_height_update) +} + +function colorer(d) { + if ($('#cofactor1_type option:selected').val() == "color"){ + datamin = d3.min(cofactor1_values); + datamax = d3.max(cofactor1_values); + } else if ($('#cofactor2_type option:selected').val() == "color"){ + datamin = d3.min(cofactor2_values); + datamax = d3.max(cofactor2_values); + } else { + datamin = d3.min(cofactor3_values); + datamax = d3.max(cofactor3_values); + } + colormin = $("#cocolorfrom").val(); + colormax = $("#cocolorto").val(); + + compute = d3.interpolate("#"+colormin, "#"+colormax); + linear = d3.scale.linear().domain([datamin, datamax]).range([0,1]); + + this_sample = d.tx.split("<br>")[0] + + if ($('#cofactor1_type option:selected').val() == "color"){ + c= compute(linear(cofactor1_dict[this_sample])); + } else if ($('#cofactor2_type option:selected').val() == "color"){ + c= compute(linear(cofactor2_dict[this_sample])); + } else { + c= compute(linear(cofactor3_dict[this_sample])); + } + + return c; +} + +function ranked_colorer(d) { + if ($('#cofactor1_type option:selected').val() == "color"){ + datamin = d3.min(ranked_cofactor1_values); + datamax = d3.max(ranked_cofactor1_values); + } else if ($('#cofactor2_type option:selected').val() == "color"){ + datamin = d3.min(ranked_cofactor2_values); + datamax = d3.max(ranked_cofactor2_values); + } else { + datamin = d3.min(ranked_cofactor3_values); + datamax = d3.max(ranked_cofactor3_values); + } + colormin = $("#cocolorfrom").val(); + colormax = $("#cocolorto").val(); + + compute = d3.interpolate("#"+colormin, "#"+colormax); + linear = d3.scale.linear().domain([datamin, datamax]).range([0,1]); + + this_sample = d.tx.split("<br>")[0] + + if ($('#cofactor1_type option:selected').val() == "color"){ + c= compute(linear(ranked_cofactor1_dict[this_sample])); + } else if ($('#cofactor2_type option:selected').val() == "color"){ + c= compute(linear(ranked_cofactor2_dict[this_sample])); + } else { + c= compute(linear(ranked_cofactor3_dict[this_sample])); + } + + return c; +} + +function chartupdatedata() { + var size = $("#marksize").val(); + var shape = $("#markshape").val(); + + var pearson_title_update = { + title: "Pearson Correlation Scatterplot" + } + var spearman_title_update = { + title: "Spearman Rank Correlation Scatterplot" + } + + Plotly.newPlot('scatterplot2', getdata(), layout, modebar_options) + Plotly.relayout('scatterplot2', pearson_title_update) + Plotly.newPlot('srscatterplot2', srgetdata(), sr_layout, modebar_options) + Plotly.relayout('srscatterplot2', spearman_title_update) + + if ($('#cofactor1_type option:selected').val() == "color"){ + $('#cofactor_color_selector').css("display", "inline") + if ($('input[name=cofactor1_vals]').val()){ + d3.select('#scatterplot2 svg').selectAll('.point') + .style({ + 'stroke': colorer, + 'fill': colorer + }); + d3.select('#srscatterplot2 svg').selectAll('.point') + .style({ + 'stroke': ranked_colorer, + 'fill': ranked_colorer + }); + } + } else if ($('#cofactor2_type option:selected').val() == "color"){ + $('#cofactor_color_selector').css("display", "inline") + if ($('input[name=cofactor2_vals]').val()){ + d3.select('#scatterplot2 svg').selectAll('.point') + .style({ + 'stroke': colorer, + 'fill': colorer + }); + d3.select('#srscatterplot2 svg').selectAll('.point') + .style({ + 'stroke': ranked_colorer, + 'fill': ranked_colorer + }); + } + } else { + $('#cofactor_color_selector').css("display", "inline") + if ($('input[name=cofactor3_vals]').val()){ + d3.select('#scatterplot2 svg').selectAll('.point') + .style({ + 'stroke': colorer, + 'fill': colorer + }); + d3.select('#srscatterplot2 svg').selectAll('.point') + .style({ + 'stroke': ranked_colorer, + 'fill': ranked_colorer + }); + } + } +} + +drawg(); +srdrawg(); + +$(".chartupdatewh").change(function () { + chartupdatewh(); +}); + +$(".chartupdatedata").change(function () { + chartupdatedata(); +}); + +$("#cofactor1_type").change(function () { + + the_types = ["color", "size", "symbol"] + + cofactor1_type = $(this).val() + cofactor2_type = $("#cofactor2_type option:selected").val() + cofactor3_type = $("#cofactor3_type option:selected").val() + + if (cofactor2_type == cofactor1_type){ + for (i=0; i<3; i++){ + if (the_types[i] != cofactor1_type && the_types[i] != cofactor3_type) { + $("#cofactor2_type").val(the_types[i]); + } + } + } + else if (cofactor3_type == cofactor1_type){ + for (i=0; i<3; i++){ + if (the_types[i] != cofactor1_type && the_types[i] != cofactor2_type) { + $("#cofactor3_type").val(the_types[i]); + } + } + } + + chartupdatedata(); +}); + +$("#cofactor2_type").change(function () { + + the_types = ["color", "size", "symbol"] + + cofactor2_type = $(this).val() + cofactor1_type = $("#cofactor1_type option:selected").val() + cofactor3_type = $("#cofactor3_type option:selected").val() + + if (cofactor1_type == cofactor2_type){ + for (i=0; i<3; i++){ + if (the_types[i] != cofactor2_type && the_types[i] != cofactor3_type){ + $("#cofactor1_type").val(the_types[i]); + } + } + } + else if (cofactor3_type == cofactor2_type){ + for (i=0; i<3; i++){ + if (the_types[i] != cofactor2_type && the_types[i] != cofactor1_type){ + $("#cofactor3_type").val(the_types[i]); + } + } + } + + chartupdatedata(); +}); + +$("#cofactor3_type").change(function () { + + the_types = ["color", "size", "symbol"] + + cofactor3_type = $(this).val() + cofactor1_type = $("#cofactor1_type option:selected").val() + cofactor2_type = $("#cofactor2_type option:selected").val() + + if (cofactor1_type == cofactor3_type){ + for (i=0; i<3; i++){ + if (the_types[i] != cofactor2_type && the_types[i] != cofactor3_type){ + $("#cofactor1_type").val(the_types[i]); + } + } + } + else if (cofactor2_type == cofactor3_type){ + for (i=0; i<3; i++){ + if (the_types[i] != cofactor2_type && the_types[i] != cofactor1_type){ + $("#cofactor3_type").val(the_types[i]); + } + } + } +}); + +open_covariate_selection = function() { + return $('#collections_holder').load('/collections/list #collections_list', (function(_this) { + return function() { + $.colorbox({ + inline: true, + href: "#collections_holder", + onComplete: function(){ + $.getScript("/static/new/javascript/get_traits_from_collection.js"); + } + }); + return $('a.collection_name').attr('onClick', 'return false'); + }; + })(this)); +}; + +remove_cofactors = function() { + $('input[name=cofactor1_vals]').val(""); + $('input[name=ranked_cofactor1_vals]').val(""); + $('input[name=cofactor2_vals]').val(""); + $('input[name=ranked_cofactor2_vals]').val(""); + $('input[name=cofactor3_vals]').val(""); + $('input[name=ranked_cofactor3_vals]').val(""); + + $('#select_cofactor1').text("Select Cofactor 1"); + $('#cofactor2_button').css("display", "none"); + $('#cofactor3_button').css("display", "none"); + + $('#cofactor_color_select').css("display", "none"); + + $('#cofactor1_info_container').css("display", "none"); + $('#cofactor2_info_container').css("display", "none"); + $('#cofactor3_info_container').css("display", "none"); + + chartupdatedata(); +}; + +$(document).ready(function(){ + chartupdatedata(); + + $('#select_cofactor1').click(function () { + $('input[name=selecting_which_cofactor]').val("1"); + open_covariate_selection(); + }); + + $('#select_cofactor2').click(function () { + $('input[name=selecting_which_cofactor]').val("2"); + open_covariate_selection(); + }); + + $('#select_cofactor3').click(function () { + $('input[name=selecting_which_cofactor]').val("3"); + open_covariate_selection(); + }); + + $('#remove_cofactors').click(function () { + remove_cofactors(); + }); + + $('#invert_axes').click(function () { + var [dataset_1, trait_1] = js_data.trait_2.split(": ") + var [dataset_2, trait_2] = js_data.trait_1.split(": ") + window.location.replace("/corr_scatter_plot?method=pearson&dataset_1=" + dataset_1 + "&dataset_2=" + dataset_2 + "&trait_1=" + trait_1 + "&trait_2=" + trait_2) + }); + +}); diff --git a/gn2/wqflask/static/new/javascript/draw_probability_plot.js b/gn2/wqflask/static/new/javascript/draw_probability_plot.js new file mode 100644 index 00000000..1b944d4f --- /dev/null +++ b/gn2/wqflask/static/new/javascript/draw_probability_plot.js @@ -0,0 +1,136 @@ +// Generated by CoffeeScript 1.9.2 +(function() { + var get_z_scores, redraw_prob_plot, root; + + root = typeof exports !== "undefined" && exports !== null ? exports : this; + + get_z_scores = function(n) { + var i, j, osm_uniform, ref, x; + osm_uniform = new Array(n); + osm_uniform[n - 1] = Math.pow(0.5, 1.0 / n); + osm_uniform[0] = 1 - osm_uniform[n - 1]; + for (i = j = 1, ref = n - 2; 1 <= ref ? j <= ref : j >= ref; i = 1 <= ref ? ++j : --j) { + osm_uniform[i] = (i + 1 - 0.3175) / (n + 0.365); + } + return (function() { + var k, len, results; + results = []; + for (k = 0, len = osm_uniform.length; k < len; k++) { + x = osm_uniform[k]; + results.push(jStat.normal.inv(x, 0, 1)); + } + return results; + })(); + }; + + redraw_prob_plot = function(samples, sample_group) { + var container, h, margin, totalh, totalw, w; + h = 600; + w = 500; + margin = { + left: 60, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + totalh = h + margin.top + margin.bottom; + totalw = w + margin.left + margin.right; + container = $("#prob_plot_container"); + container.width(totalw); + container.height(totalh); + return nv.addGraph((function(_this) { + return function() { + var W, all_samples, chart, data, intercept, make_data, names, pvalue, pvalue_str, slope, sorted_names, sorted_values, sw_result, test_str, x, z_scores; + chart = nv.models.scatterChart().width(w).height(h).showLegend(true).color(d3.scale.category10().range()); + chart.pointRange([50, 50]); + chart.legend.updateState(false); + chart.xAxis.axisLabel("Expected Z score").axisLabelDistance(20).tickFormat(d3.format('.02f')); + chart.tooltipContent(function(obj) { + return '<b style="font-size: 20px">' + obj.point.name + '</b>'; + }); + all_samples = samples[sample_group]; + names = (function() { + var j, len, ref, results; + ref = _.keys(all_samples); + results = []; + for (j = 0, len = ref.length; j < len; j++) { + x = ref[j]; + if (all_samples[x] !== null) { + results.push(x); + } + } + return results; + })(); + sorted_names = names.sort(function(x, y) { + return all_samples[x].value - all_samples[y].value; + }); + max_decimals = 0 + sorted_values = (function() { + var j, len, results; + results = []; + for (j = 0, len = sorted_names.length; j < len; j++) { + x = sorted_names[j]; + results.push(all_samples[x].value); + if (all_samples[x].value.countDecimals() > max_decimals) { + max_decimals = all_samples[x].value.countDecimals()-1 + } + } + return results; + })(); + //ZS: 0.1 indicates buffer, increase to increase buffer + y_domain = [sorted_values[0] - (sorted_values.slice(-1)[0] - sorted_values[0])*0.1, sorted_values.slice(-1)[0] + (sorted_values.slice(-1)[0] - sorted_values[0])*0.1] + chart.yDomain(y_domain) + chart.yAxis.axisLabel("Trait value").axisLabelDistance(10).tickFormat(d3.format('.0'+max_decimals.toString()+'f')); + sw_result = ShapiroWilkW(sorted_values); + W = sw_result.w.toFixed(3); + pvalue = sw_result.p.toFixed(3); + pvalue_str = pvalue > 0.05 ? pvalue.toString() : "<span style='color:red'>" + pvalue + "</span>"; + test_str = "Shapiro-Wilk test statistic is " + W + " (p = " + pvalue_str + ")"; + z_scores = get_z_scores(sorted_values.length); + //ZS: 0.1 indicates buffer, increase to increase buffer + x_domain = [z_scores[0] - (z_scores.slice(-1)[0] - z_scores[0])*0.1, z_scores.slice(-1)[0] + (z_scores.slice(-1)[0] - z_scores[0])*0.1] + chart.xDomain(x_domain) + slope = jStat.stdev(sorted_values); + intercept = jStat.mean(sorted_values); + make_data = function(group_name) { + var sample, value, z_score; + return { + key: js_data.sample_group_types[group_name], + slope: slope, + intercept: intercept, + values: (function() { + var j, len, ref, ref1, results; + ref = _.zip(get_z_scores(sorted_values.length), sorted_values, sorted_names); + results = []; + for (j = 0, len = ref.length; j < len; j++) { + ref1 = ref[j], z_score = ref1[0], value = ref1[1], sample = ref1[2]; + if (sample in samples[group_name]) { + results.push({ + x: z_score, + y: value, + name: sample + }); + } + } + return results; + })() + }; + }; + data = [make_data('samples_primary'), make_data('samples_other')]; + d3.select("#prob_plot_container svg").datum(data).call(chart); + if (js_data.trait_symbol != null) { + $("#prob_plot_title").html("<h3>" + js_data.trait_symbol + ": " + js_data.trait_id + "</h3>"); + } else { + $("#prob_plot_title").html("<h3>" + js_data.trait_id + "</h3>"); + } + $("#shapiro_wilk_text").html(test_str) + $("#prob_plot_container .nv-legendWrap").toggle(sample_group === "samples_all"); + return chart; + }; + })(this)); + }; + + root.redraw_prob_plot_impl = redraw_prob_plot; + +}).call(this); diff --git a/gn2/wqflask/static/new/javascript/get_covariates_from_collection.js b/gn2/wqflask/static/new/javascript/get_covariates_from_collection.js new file mode 100644 index 00000000..ba8fa8b0 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/get_covariates_from_collection.js @@ -0,0 +1,248 @@ +// Generated by CoffeeScript 1.8.0 +var add_trait_data, assemble_into_json, back_to_collections, collection_click, collection_list, color_by_trait, create_trait_data_csv, get_this_trait_vals, get_trait_data, process_traits, selected_traits, submit_click, this_trait_data, trait_click, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +this_trait_data = null; + +selected_traits = {}; + +$('#collections_list').attr("style", "width: 100%;"); +$('#trait_table').dataTable( { + "drawCallback": function( settings ) { + $('#trait_table tr').click(function(event) { + if (event.target.type !== 'checkbox') { + $(':checkbox', this).trigger('click'); + } + }); + }, + "columns": [ + { "type": "natural", "width": "3%" }, + { "type": "natural", "width": "8%" }, + { "type": "natural", "width": "20%" }, + { "type": "natural", "width": "25%" }, + { "type": "natural", "width": "25%" }, + { "type": "natural", "width": "15%" } + ], + "columnDefs": [ { + "targets": 0, + "orderable": false + } ], + "order": [[1, "asc" ]], + "sDom": "RZtr", + "iDisplayLength": -1, + "autoWidth": true, + "bDeferRender": true, + "bSortClasses": false, + "paging": false, + "orderClasses": true +} ); + +if ( ! $.fn.DataTable.isDataTable( '#collection_table' ) ) { + $('#collection_table').dataTable( { + "createdRow": function ( row, data, index ) { + if ($('td', row).eq(2).text().length > 40) { + $('td', row).eq(2).text($('td', row).eq(2).text().substring(0, 40)); + $('td', row).eq(2).text($('td', row).eq(2).text() + '...') + } + if ($('td', row).eq(4).text().length > 50) { + $('td', row).eq(4).text($('td', row).eq(4).text().substring(0, 50)); + $('td', row).eq(4).text($('td', row).eq(4).text() + '...') + } + }, + "columnDefs": [ { + "targets": 0, + "orderable": false + } ], + "order": [[1, "asc" ]], + "sDom": "ZRtr", + "iDisplayLength": -1, + "autoWidth": true, + "bSortClasses": false, + "paging": false, + "orderClasses": true + } ); +} + +collection_click = function() { + var this_collection_url; + this_collection_url = $(this).find('.collection_name').prop("href"); + this_collection_url += "&json"; + collection_list = $("#collections_holder").html(); + return $.ajax({ + dataType: "json", + url: this_collection_url, + success: process_traits + }); +}; + +submit_click = function() { + var covariates_string = ""; + var covariates_as_set = new Set(); + $(".selected-covariates:first option").each(function() { + if ($(this).val() != ""){ + covariates_as_set.add($(this).val() + "," + $(this).text()); + } + }); + $('#collections_holder').find('input[type=checkbox]:checked').each(function() { + var this_dataset, this_trait; + this_trait = $(this).parents('tr').find('.trait').text(); + this_trait_display = $(this).parents('tr').find('.trait').data("display_name"); + this_description = $(this).parents('tr').find('.description').text(); + this_dataset = $(this).parents('tr').find('.dataset').data("dataset"); + this_covariate_display_string = this_trait_display + if (this_covariate_display_string.length > 50) { + this_covariate_display_string = this_covariate_display_string.substring(0, 45) + "..." + } + covariates_as_set.add(this_trait + ":" + this_dataset + "," + this_covariate_display_string) + }); + + covariates_as_list = Array.from(covariates_as_set) + + // Removed the starting "No covariates selected" option before adding options for each covariate + if (covariates_as_list.length > 0){ + $(".selected-covariates option[value='']").each(function() { + $(this).remove(); + }); + } + + $(".selected-covariates option").each(function() { + $(this).remove(); + }); + + covariate_list_for_form = [] + $.each(covariates_as_list, function (index, value) { + option_value = value.split(",")[0] + option_text = value.split(",")[1] + $(".selected-covariates").append($("<option/>", { + value: option_value, + text: option_text + })) + covariate_list_for_form.push(option_value) + }); + + $("input[name=covariates]").val(covariate_list_for_form.join(",")); + + cofactor_count = $(".selected-covariates:first option").length; + if (cofactor_count > 10){ + $(".selected-covariates").attr("size", 10); + } else { + $(".selected-covariates").attr("size", cofactor_count); + } + + return $.colorbox.close(); +}; + +trait_click = function() { + var dataset, this_trait_url, trait; + trait = $(this).parent().find('.trait').text(); + dataset = $(this).parent().find('.dataset').data("dataset"); + $("input[name=covariates]").val(trait + ":" + dataset) + $(".selected-covariates").text(trait) + return $.colorbox.close(); +}; + +add_trait_data = function(trait_data, textStatus, jqXHR) { + var trait_name, trait_sample_data; + trait_name = trait_data[0]; + trait_sample_data = trait_data[1]; + selected_traits[trait_name] = trait_sample_data; + return console.log("selected_traits:", selected_traits); +}; + +get_trait_data = function(trait_data, textStatus, jqXHR) { + var sample, samples, this_trait_vals, trait_sample_data, vals, _i, _len; + trait_sample_data = trait_data[1]; + samples = $('input[name=allsamples]').val().split(" "); + vals = []; + for (_i = 0, _len = samples.length; _i < _len; _i++) { + sample = samples[_i]; + if (__indexOf.call(Object.keys(trait_sample_data), sample) >= 0) { + vals.push(parseFloat(trait_sample_data[sample])); + } else { + vals.push(null); + } + } + if ($('input[name=samples]').length < 1) { + $('#hidden_inputs').append('<input type="hidden" name="samples" value="[' + samples.toString() + ']" />'); + } + $('#hidden_inputs').append('<input type="hidden" name="vals" value="[' + vals.toString() + ']" />'); + this_trait_vals = get_this_trait_vals(samples); + return color_by_trait(trait_sample_data); +}; + +get_this_trait_vals = function(samples) { + var sample, this_trait_vals, this_val, this_vals_json, _i, _len; + this_trait_vals = []; + for (_i = 0, _len = samples.length; _i < _len; _i++) { + sample = samples[_i]; + this_val = parseFloat($("input[name='value:" + sample + "']").val()); + if (!isNaN(this_val)) { + this_trait_vals.push(this_val); + } else { + this_trait_vals.push(null); + } + } + this_vals_json = '[' + this_trait_vals.toString() + ']'; + return this_trait_vals; +}; + +assemble_into_json = function(this_trait_vals) { + var json_data, json_ids, num_traits, samples; + num_traits = $('input[name=vals]').length; + samples = $('input[name=samples]').val(); + json_ids = samples; + json_data = '[' + this_trait_vals; + $('input[name=vals]').each((function(_this) { + return function(index, element) { + return json_data += ',' + $(element).val(); + }; + })(this)); + json_data += ']'; + return [json_ids, json_data]; +}; + +color_by_trait = function(trait_sample_data, textStatus, jqXHR) { + console.log('in color_by_trait:', trait_sample_data); + return root.bar_chart.color_by_trait(trait_sample_data); +}; + +process_traits = function(trait_data, textStatus, jqXHR) { + var the_html, trait, _i, _len; + the_html = "<button id='back_to_collections' class='btn btn-inverse btn-small'>"; + the_html += "<i class='icon-white icon-arrow-left'></i> Back </button>"; + the_html += " <button id='submit_cofactors' class='btn btn-primary btn-small submit'> Submit </button>"; + the_html += "<table id='collection_table' style='padding-top: 10px;' class='table table-hover'>"; + the_html += "<thead><tr><th></th><th>Record</th><th>Data Set</th><th>Description</th></tr></thead>"; + the_html += "<tbody>"; + for (_i = 0, _len = trait_data.length; _i < _len; _i++) { + trait = trait_data[_i]; + the_html += "<tr class='trait_line'>"; + the_html += "<td class='select_trait'><input type='checkbox' name='selectCheck' class='checkbox edit_sample_checkbox'></td>"; + if ("abbreviation" in trait) { + the_html += "<td class='trait' data-display_name='" + trait.name + " - " + trait.abbreviation + "'>" + trait.name + "</td>"; + } else if ("symbol" in trait) { + the_html += "<td class='trait' data-display_name='" + trait.name + " - " + trait.symbol + "'>" + trait.name + "</td>"; + } else { + the_html += "<td class='trait' data-display_name='" + trait.name + "'>" + trait.name + "</td>"; + } + the_html += "<td class='dataset' data-dataset='" + trait.dataset + "'>" + trait.dataset_name + "</td>"; + the_html += "<td class='description'>" + trait.description + "</td>"; + } + the_html += "</tbody>"; + the_html += "</table>"; + the_html += "<script type='text/javascript' src='/static/new/javascript/get_covariates_from_collection.js'></script>" + $("#collections_holder").html(the_html); + return $('#collections_holder').colorbox.resize(); +}; + +back_to_collections = function() { + console.log("collection_list:", collection_list); + $("#collections_holder").html(collection_list); + $(document).on("click", ".collection_line", collection_click); + return $('#collections_holder').colorbox.resize(); +}; + +$(".collection_line").on("click", collection_click); +$(".submit").on("click", submit_click); +$(".trait").on("click", trait_click); +$("#back_to_collections").on("click", back_to_collections); diff --git a/gn2/wqflask/static/new/javascript/get_traits_from_collection.js b/gn2/wqflask/static/new/javascript/get_traits_from_collection.js new file mode 100644 index 00000000..c115f3b0 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/get_traits_from_collection.js @@ -0,0 +1,414 @@ +// Generated by CoffeeScript 1.8.0 +var add_trait_data, assemble_into_json, back_to_collections, collection_click, collection_list, color_by_trait, create_trait_data_csv, get_this_trait_vals, get_trait_data, process_traits, selected_traits, submit_click, this_trait_data, trait_click, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +this_trait_data = null; + +selected_traits = {}; + +$('#collections_list').attr("style", "width: 100%;"); +$('#trait_table').dataTable( { + "drawCallback": function( settings ) { + $('#trait_table tr').click(function(event) { + if (event.target.type !== 'checkbox') { + $(':checkbox', this).trigger('click'); + } + }); + }, + "columns": [ + { "type": "natural", "width": "3%" }, + { "type": "natural", "width": "8%" }, + { "type": "natural", "width": "20%" }, + { "type": "natural", "width": "25%" }, + { "type": "natural", "width": "25%" }, + { "type": "natural", "width": "15%" } + ], + "columnDefs": [ { + "targets": 0, + "orderable": false + } ], + "order": [[1, "asc" ]], + "sDom": "RZtr", + "iDisplayLength": -1, + "autoWidth": true, + "bDeferRender": true, + "bSortClasses": false, + "paging": false, + "orderClasses": true +} ); + +if ( ! $.fn.DataTable.isDataTable( '#collection_table' ) ) { + $('#collection_table').dataTable( { + "createdRow": function ( row, data, index ) { + if ($('td', row).eq(2).text().length > 40) { + $('td', row).eq(2).text($('td', row).eq(2).text().substring(0, 40)); + $('td', row).eq(2).text($('td', row).eq(2).text() + '...') + } + if ($('td', row).eq(4).text().length > 50) { + $('td', row).eq(4).text($('td', row).eq(4).text().substring(0, 50)); + $('td', row).eq(4).text($('td', row).eq(4).text() + '...') + } + }, + "columnDefs": [ { + "targets": 0, + "orderable": false + } ], + "order": [[1, "asc" ]], + "sDom": "ZRtr", + "iDisplayLength": -1, + "autoWidth": true, + "bSortClasses": false, + "paging": false, + "orderClasses": true + } ); +} + +collection_click = function() { + var this_collection_url; + + this_collection_url = $(this).find('.collection_name').prop("href"); + this_collection_url += "&json"; + collection_list = $("#collections_holder").html(); + return $.ajax({ + dataType: "json", + url: this_collection_url, + success: process_traits + }); +}; + +submit_click = function() { + var all_vals, sample, samples, scatter_matrix, this_trait_vals, trait, trait_names, trait_vals_csv, traits, _i, _j, _len, _len1, _ref; + selected_traits = {}; + traits = []; + $('#collections_holder').find('input[type=checkbox]:checked').each(function() { + var this_dataset, this_trait, this_trait_url; + this_trait = $(this).parents('tr').find('.trait').text(); + this_dataset = $(this).parents('tr').find('.dataset').text(); + this_trait_url = "/trait/get_sample_data?trait=" + this_trait + "&dataset=" + this_dataset; + return $.ajax({ + dataType: "json", + url: this_trait_url, + async: false, + success: add_trait_data + }); + }); + trait_names = []; + samples = $('input[name=allsamples]').val().split(" "); + all_vals = []; + this_trait_vals = get_this_trait_vals(samples); + all_vals.push(this_trait_vals); + _ref = Object.keys(selected_traits); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + trait = _ref[_i]; + trait_names.push(trait); + this_trait_vals = []; + for (_j = 0, _len1 = samples.length; _j < _len1; _j++) { + sample = samples[_j]; + if (__indexOf.call(Object.keys(selected_traits[trait]), sample) >= 0) { + this_trait_vals.push(parseFloat(selected_traits[trait][sample])); + } else { + this_trait_vals.push(null); + } + } + all_vals.push(this_trait_vals); + } + trait_vals_csv = create_trait_data_csv(selected_traits); + scatter_matrix = new ScatterMatrix(trait_vals_csv); + scatter_matrix.render(); + return $.colorbox.close(); +}; + +create_trait_data_csv = function(selected_traits) { + var all_vals, index, sample, sample_vals, samples, this_trait_vals, trait, trait_names, trait_vals_csv, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref; + trait_names = []; + trait_names.push($('input[name=trait_id]').val()); + samples = $('input[name=allsamples]').val().split(" "); + all_vals = []; + this_trait_vals = get_this_trait_vals(samples); + all_vals.push(this_trait_vals); + _ref = Object.keys(selected_traits); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + trait = _ref[_i]; + trait_names.push(trait); + this_trait_vals = []; + for (_j = 0, _len1 = samples.length; _j < _len1; _j++) { + sample = samples[_j]; + if (__indexOf.call(Object.keys(selected_traits[trait]), sample) >= 0) { + this_trait_vals.push(parseFloat(selected_traits[trait][sample])); + } else { + this_trait_vals.push(null); + } + } + all_vals.push(this_trait_vals); + } + + trait_vals_csv = trait_names.join(","); + trait_vals_csv += "\n"; + for (index = _k = 0, _len2 = samples.length; _k < _len2; index = ++_k) { + sample = samples[index]; + if (all_vals[0][index] === null) { + continue; + } + sample_vals = []; + for (_l = 0, _len3 = all_vals.length; _l < _len3; _l++) { + trait = all_vals[_l]; + sample_vals.push(trait[index]); + } + trait_vals_csv += sample_vals.join(","); + trait_vals_csv += "\n"; + } + return trait_vals_csv; +}; + +trait_click = function() { + var dataset, this_trait_url, trait; + + trait = $(this).parent().find('.trait').text(); + dataset = $(this).parent().find('.dataset').text(); + this_trait_url = "/trait/get_sample_data?trait=" + trait + "&dataset=" + dataset; + $.ajax({ + dataType: "json", + url: this_trait_url, + success: get_trait_data + }); + return $.colorbox.close(); +}; + +trait_row_click = function() { + var dataset, this_trait_url, trait; + trait = $(this).find('.trait').text(); + dataset = $(this).find('.dataset').data("dataset"); + this_trait_url = "/trait/get_sample_data?trait=" + trait + "&dataset=" + dataset; + $.ajax({ + dataType: "json", + url: this_trait_url, + success: get_trait_data + }); + return $.colorbox.close(); +}; + +add_trait_data = function(trait_data, textStatus, jqXHR) { + var trait_name, trait_sample_data; + trait_name = trait_data[0]; + trait_sample_data = trait_data[1]; + selected_traits[trait_name] = trait_sample_data; + return console.log("selected_traits:", selected_traits); +}; + +populate_cofactor_info = function(trait_info) { + if ($('input[name=selecting_which_cofactor]').val() == "1"){ + $('#cofactor1_trait_link').attr("href", trait_info['url']) + if (trait_info['type'] == "ProbeSet"){ + $('#cofactor1_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['tissue'] + " " + trait_info['db'] + ": " + trait_info['name']) + $('#cofactor1_description').text("[" + trait_info['symbol'] + " on " + trait_info['location'] + " Mb]\n" + trait_info['description']) + } else if (trait_info['type'] == "Publish") { + $('#cofactor1_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['db'] + ": " + trait_info['name']) + if ('pubmed_link' in trait_info) { + $('#cofactor1_description').html('<a href=\"' + trait_info['pubmed_link'] + '\">PubMed: ' + trait_info['pubmed_text'] + '</a><br>' + trait_info['description']) + } else { + $('#cofactor1_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['db'] + ": " + trait_info['name']) + $('#cofactor1_description').text("[" + trait_info['name'] + " on " + trait_info['location'] + " Mb]\n" + trait_info['description']) + } + } else { + $('#cofactor1_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['db'] + ": " + trait_info['name']) + $('#cofactor1_description').text("[" + trait_info['name'] + " on " + trait_info['location'] + " Mb]\n") + } + $('#select_cofactor1').text("Change Cofactor 1"); + $('#cofactor1_info_container').css("display", "inline"); + $('#cofactor2_button').css("display", "inline-block"); + } else if ($('input[name=selecting_which_cofactor]').val() == "2"){ + $('#cofactor2_trait_link').attr("href", trait_info['url']) + if (trait_info['type'] == "ProbeSet"){ + $('#cofactor2_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['tissue'] + " " + trait_info['db'] + ": " + trait_info['name']) + $('#cofactor2_description').text("[" + trait_info['symbol'] + " on " + trait_info['location'] + " Mb]\n" + trait_info['description']) + } else if (trait_info['type'] == "Publish") { + $('#cofactor2_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['db'] + ": " + trait_info['name']) + if ('pubmed_link' in trait_info) { + $('#cofactor2_description').html('<a href=\"' + trait_info['pubmed_link'] + '\">PubMed: ' + trait_info['pubmed_text'] + '</a><br>' + trait_info['description']) + } else { + $('#cofactor2_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['db'] + ": " + trait_info['name']) + $('#cofactor2_description').text("[" + trait_info['name'] + " on " + trait_info['location'] + " Mb]\n" + trait_info['description']) + } + } else { + $('#cofactor2_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['db'] + ": " + trait_info['name']) + $('#cofactor2_description').text("[" + trait_info['name'] + " on " + trait_info['location'] + " Mb]\n") + } + $('#select_cofactor2').text("Change Cofactor 2"); + $('#cofactor2_info_container').css("display", "inline"); + $('#cofactor3_button').css("display", "inline-block"); + } else { + $('#cofactor3_trait_link').attr("href", trait_info['url']) + if (trait_info['type'] == "ProbeSet"){ + $('#cofactor3_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['tissue'] + " " + trait_info['db'] + ": " + trait_info['name']) + $('#cofactor3_description').text("[" + trait_info['symbol'] + " on " + trait_info['location'] + " Mb]\n" + trait_info['description']) + } else if (trait_info['type'] == "Publish") { + $('#cofactor3_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['db'] + ": " + trait_info['name']) + if ('pubmed_link' in trait_info) { + $('#cofactor3_description').html('<a href=\"' + trait_info['pubmed_link'] + '\">PubMed: ' + trait_info['pubmed_text'] + '</a><br>' + trait_info['description']) + } else { + $('#cofactor3_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['db'] + ": " + trait_info['name']) + $('#cofactor3_description').text("[" + trait_info['name'] + " on " + trait_info['location'] + " Mb]\n" + trait_info['description']) + } + } else { + $('#cofactor3_trait_link').text(trait_info['species'] + " " + trait_info['group'] + " " + trait_info['db'] + ": " + trait_info['name']) + $('#cofactor3_description').text("[" + trait_info['name'] + " on " + trait_info['location'] + " Mb]\n") + } + $('#select_cofactor3').text("Change Cofactor 3"); + $('#cofactor3_info_container').css("display", "inline"); + } +} + +get_trait_data = function(trait_data, textStatus, jqXHR) { + var sample, samples, this_trait_vals, trait_sample_data, vals, _i, _len; + trait_sample_data = trait_data[1]; + if ( $('input[name=allsamples]').length ) { + samples = $('input[name=allsamples]').val().split(" "); + } else { + samples = js_data.indIDs + } + sample_vals = []; + vals = []; + for (_i = 0, _len = samples.length; _i < _len; _i++) { + sample = samples[_i]; + if (sample in trait_sample_data) { + sample_vals.push(sample + ":" + parseFloat(trait_sample_data[sample])) + vals.push(parseFloat(trait_sample_data[sample])) + } else { + sample_vals.push(null) + vals.push(null) + } + } + if ( $('input[name=allsamples]').length ) { + if ($('input[name=samples]').length < 1) { + $('#hidden_inputs').append('<input type="hidden" name="samples" value="[' + samples.toString() + ']" />'); + } + $('#hidden_inputs').append('<input type="hidden" name="vals" value="[' + vals.toString() + ']" />'); + this_trait_vals = get_this_trait_vals(samples); + return color_by_trait(trait_sample_data); + } else{ + sorted = vals.slice().sort(function(a,b){return a-b}) + ranks = vals.slice().map(function(v){ return sorted.indexOf(v)+1 }); + sample_ranks = [] + for (_i = 0; _i < samples.length; _i++){ + if (samples[_i] in trait_sample_data){ + sample_ranks.push(samples[_i] + ":" + ranks[_i]) + } else { + sample_ranks.push(null) + } + } + + if ($('input[name=selecting_which_cofactor]').val() == "1"){ + if ($('#cofactor1_type option:selected').val() == "symbol") { + unique_vals = [...new Set(vals)] + if (unique_vals.length > 45) { + alert("If displaying cofactor as symbol, please choose a trait with 45 or fewer distinct sample values."); + return false; + } + } + $('input[name=cofactor1_vals]').val(sample_vals) + $('input[name=ranked_cofactor1_vals]').val(sample_ranks) + } else if ($('input[name=selecting_which_cofactor]').val() == "2"){ + if ($('#cofactor2_type option:selected').val() == "symbol") { + unique_vals = [...new Set(vals)] + if (unique_vals.length > 45) { + alert("If displaying cofactor as symbol, please choose a trait with 45 or fewer distinct sample values."); + return false; + } + } + $('input[name=cofactor2_vals]').val(sample_vals) + $('input[name=ranked_cofactor2_vals]').val(sample_ranks) + } else{ + if ($('#cofactor3_type option:selected').val() == "symbol") { + unique_vals = [...new Set(vals)] + if (unique_vals.length > 45) { + alert("If displaying cofactor as symbol, please choose a trait with 45 or fewer distinct sample values."); + return false; + } + } + $('input[name=cofactor3_vals]').val(sample_vals) + $('input[name=ranked_cofactor3_vals]').val(sample_ranks) + } + populate_cofactor_info(trait_data[0]) + chartupdatedata(); + return false + } +}; + +get_this_trait_vals = function(samples) { + var sample, this_trait_vals, this_val, this_vals_json, _i, _len; + this_trait_vals = []; + for (_i = 0, _len = samples.length; _i < _len; _i++) { + sample = samples[_i]; + this_val = parseFloat($("input[name='value:" + sample + "']").val()); + if (!isNaN(this_val)) { + this_trait_vals.push(this_val); + } else { + this_trait_vals.push(null); + } + } + this_vals_json = '[' + this_trait_vals.toString() + ']'; + return this_trait_vals; +}; + +assemble_into_json = function(this_trait_vals) { + var json_data, json_ids, num_traits, samples; + num_traits = $('input[name=vals]').length; + samples = $('input[name=samples]').val(); + json_ids = samples; + json_data = '[' + this_trait_vals; + $('input[name=vals]').each((function(_this) { + return function(index, element) { + return json_data += ',' + $(element).val(); + }; + })(this)); + json_data += ']'; + return [json_ids, json_data]; +}; + +color_by_trait = function(trait_sample_data, textStatus, jqXHR) { + return root.bar_chart.color_by_trait(trait_sample_data); +}; + +process_traits = function(trait_data, textStatus, jqXHR) { + var the_html, trait, _i, _len; + the_html = "<button id='back_to_collections' class='btn btn-inverse btn-small'>"; + the_html += "<i class='icon-white icon-arrow-left'></i> Back </button>"; + the_html += " <button id='submit_cofactors' class='btn btn-primary btn-small submit'> Submit </button>"; + the_html += "<table id='collection_table' style='padding-top: 10px;' class='table table-hover'>"; + the_html += "<thead><tr><th></th><th>Record</th><th>Data Set</th><th>Description</th></tr></thead>"; + the_html += "<tbody>"; + for (_i = 0, _len = trait_data.length; _i < _len; _i++) { + trait = trait_data[_i]; + the_html += "<tr class='trait_line'>"; + the_html += "<td class='select_trait'><input type='checkbox' name='selectCheck' class='checkbox edit_sample_checkbox'></td>"; + if ("abbreviation" in trait) { + the_html += "<td class='trait' data-display_name='" + trait.name + " - " + trait.abbreviation + "'>" + trait.name + "</td>"; + } else if ("symbol" in trait) { + the_html += "<td class='trait' data-display_name='" + trait.name + " - " + trait.symbol + "'>" + trait.name + "</td>"; + } else { + the_html += "<td class='trait' data-display_name='" + trait.name + "'>" + trait.name + "</td>"; + } + the_html += "<td class='dataset' data-dataset='" + trait.dataset + "'>" + trait.dataset_name + "</td>"; + the_html += "<td class='description'>" + trait.description + "</td>"; + } + the_html += "</tbody>"; + the_html += "</table>"; + the_html += "<script type='text/javascript' src='/static/new/javascript/get_traits_from_collection.js'></script>" + $("#collections_holder").html(the_html); + return $('#collections_holder').colorbox.resize(); +}; + +back_to_collections = function() { + $("#collections_holder").html(collection_list); + $(document).on("click", ".collection_line", collection_click); + return $('#collections_holder').colorbox.resize(); +}; + +$(".collection_line").on("click", collection_click); +$("#submit_cofactors").on("click", submit_click); +if ($('#scatterplot2').length){ + $(".trait_line").on("click", trait_row_click); +} else { + $(".trait").on("click", trait_click); +} +$("#back_to_collections").on("click", back_to_collections); diff --git a/gn2/wqflask/static/new/javascript/group_manager.js b/gn2/wqflask/static/new/javascript/group_manager.js new file mode 100644 index 00000000..cd56133a --- /dev/null +++ b/gn2/wqflask/static/new/javascript/group_manager.js @@ -0,0 +1,37 @@ +$('#add_to_admins').click(function() { + add_emails('admin') +}) + +$('#add_to_members').click(function() { + add_emails('member') +}) + +$('#clear_admins').click(function(){ + clear_emails('admin') +}) + +$('#clear_members').click(function(){ + clear_emails('member') +}) + + +function add_emails(user_type){ + let email_address = $('input[name=user_email]').val(); + let email_list_string = $('input[name=' + user_type + '_emails_to_add]').val().trim() + if (email_list_string == ""){ + let email_set = new Set(); + } else { + let email_set = new Set(email_list_string.split(",")) + } + email_set.add(email_address) + + $('input[name=' + user_type + '_emails_to_add]').val(Array.from(email_set).join(',')) + + let emails_display_string = Array.from(email_set).join('\n') + $('.added_' + user_type + 's').val(emails_display_string) +} + +function clear_emails(user_type){ + $('input[name=' + user_type + '_emails_to_add]').val("") + $('.added_' + user_type + 's').val("") +} diff --git a/gn2/wqflask/static/new/javascript/histogram.js b/gn2/wqflask/static/new/javascript/histogram.js new file mode 100644 index 00000000..f71080e8 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/histogram.js @@ -0,0 +1,134 @@ +// Generated by CoffeeScript 1.9.2 +(function() { + var Histogram, root; + + root = typeof exports !== "undefined" && exports !== null ? exports : this; + + Histogram = (function() { + function Histogram(sample_list1, sample_group) { + this.sample_list = sample_list1; + this.sample_group = sample_group; + this.sort_by = "name"; + this.format_count = d3.format(",.0f"); + this.margin = { + top: 10, + right: 30, + bottom: 30, + left: 30 + }; + this.plot_width = 960 - this.margin.left - this.margin.right; + this.plot_height = 500 - this.margin.top - this.margin.bottom; + this.x_buffer = this.plot_width / 20; + this.y_buffer = this.plot_height / 20; + this.plot_height -= this.y_buffer; + this.get_sample_vals(this.sample_list); + this.redraw(this.sample_vals); + } + + Histogram.prototype.redraw = function(sample_vals) { + this.sample_vals = sample_vals; + this.y_min = d3.min(this.sample_vals); + this.y_max = d3.max(this.sample_vals) * 1.1; + this.create_x_scale(); + this.get_histogram_data(); + this.create_y_scale(); + $("#histogram").empty(); + this.svg = this.create_svg(); + return this.create_graph(); + }; + + Histogram.prototype.get_sample_vals = function(sample_list) { + var sample; + return this.sample_vals = (function() { + var i, len, results; + results = []; + for (i = 0, len = sample_list.length; i < len; i++) { + sample = sample_list[i]; + if (sample.value !== null) { + results.push(sample.value); + } + } + return results; + })(); + }; + + Histogram.prototype.create_svg = function() { + var svg; + svg = d3.select("#histogram").append("svg").attr("class", "histogram").attr("width", this.plot_width + this.margin.left + this.margin.right).attr("height", this.plot_height + this.margin.top + this.margin.bottom).append("g").attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")"); + return svg; + }; + + Histogram.prototype.create_x_scale = function() { + var x0; + console.log("min/max:", d3.min(this.sample_vals) + "," + d3.max(this.sample_vals)); + x0 = Math.max(-d3.min(this.sample_vals), d3.max(this.sample_vals)); + return this.x_scale = d3.scale.linear().domain([d3.min(this.sample_vals), d3.max(this.sample_vals)]).range([0, this.plot_width]).nice(); + }; + + Histogram.prototype.get_histogram_data = function() { + var n_bins; + console.log("sample_vals:", this.sample_vals); + n_bins = 2*Math.sqrt(this.sample_vals.length); //Was originally just the square root, but increased to 2*; ideally would be a GUI for changing this + this.histogram_data = d3.layout.histogram().bins(this.x_scale.ticks(n_bins))(this.sample_vals); + return console.log("histogram_data:", this.histogram_data[0]); + }; + + Histogram.prototype.create_y_scale = function() { + return this.y_scale = d3.scale.linear().domain([ + 0, d3.max(this.histogram_data, (function(_this) { + return function(d) { + return d.y; + }; + })(this)) + ]).range([this.plot_height, 0]); + }; + + Histogram.prototype.create_graph = function() { + this.add_x_axis(); + this.add_y_axis(); + return this.add_bars(); + }; + + Histogram.prototype.add_x_axis = function() { + var x_axis; + x_axis = d3.svg.axis().scale(this.x_scale).orient("bottom"); + return this.svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + this.plot_height + ")").call(x_axis); + }; + + Histogram.prototype.add_y_axis = function() { + var yAxis; + yAxis = d3.svg.axis().scale(this.y_scale).orient("left").ticks(5); + return this.svg.append("g").attr("class", "y axis").call(yAxis).append("text").attr("transform", "rotate(-90)").attr("y", 6).attr("dy", ".71em").style("text-anchor", "end"); + }; + + Histogram.prototype.add_bars = function() { + var bar, rect_width; + bar = this.svg.selectAll(".bar").data(this.histogram_data).enter().append("g").attr("class", "bar").attr("transform", (function(_this) { + return function(d) { + return "translate(" + _this.x_scale(d.x) + "," + _this.y_scale(d.y) + ")"; + }; + })(this)); + rect_width = this.x_scale(this.histogram_data[0].x + this.histogram_data[0].dx) - this.x_scale(this.histogram_data[0].x); + bar.append("rect").attr("x", 1).attr("width", rect_width - 1).attr("height", (function(_this) { + return function(d) { + return _this.plot_height - _this.y_scale(d.y); + }; + })(this)); + return bar.append("text").attr("dy", ".75em").attr("y", 6).attr("x", rect_width / 2).attr("text-anchor", "middle").style("fill", "#fff").text((function(_this) { + return function(d) { + var bar_height; + bar_height = _this.plot_height - _this.y_scale(d.y); + if (bar_height > 20) { + return _this.format_count(d.y); + } + }; + })(this)); + }; + + return Histogram; + + })(); + + root.Histogram = Histogram; + +}).call(this); diff --git a/gn2/wqflask/static/new/javascript/init_genome_browser.js b/gn2/wqflask/static/new/javascript/init_genome_browser.js new file mode 100644 index 00000000..508f5bf2 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/init_genome_browser.js @@ -0,0 +1,82 @@ +snps_filename = "/browser_input?filename=" + js_data.browser_files[0] +annot_filename = "/browser_input?filename=" + js_data.browser_files[1] + +localUrls = +{ + snps: snps_filename, + annotations: annot_filename +}; + +var coordinateSystem = js_data.chr_lengths + +var vscaleWidth = 90.0; +var legendWidth = 150.0; + +if ('significant' in js_data) { + var significant_score = parseFloat(js_data.significant) +} else { + var significant_score = js_data.max_score * 0.75 +} +var score = { min: 0.0, max: js_data.max_score, sig: significant_score }; +var gwasPadding = { top: 35.0, + bottom: 35.0, + left: vscaleWidth, + right: legendWidth }; +var gwasHeight = 500.0; +var config = +{ score: score, + urls: localUrls, + tracks: { + gwas: { + trackHeight: gwasHeight, + padding: gwasPadding, + snps: { + radius: 3.75, + lineWidth: 1.0, + color: { outline: "#FFFFFF", + fill: "#00008B" }, + pixelOffset: {x: 0.0, y: 0.0} + }, + annotations: { + urls: { + url: "GeneNetwork" + }, + radius: 5.5, + outline: "#000000", + snpColor: "#0074D9", + geneColor: "#FF4136" + }, + score: score, + legend: { + fontSize: 14, + hPad: 0.1, + vPad: 0.2 + }, + vscale: { + color: "#000000", + hPad: 0.125, + numSteps: 3, + fonts: { labelSize: 18, scaleSize: 16 } + }, + }, + }, + chrs: { + chrBG1: "#FFFFFF", + chrBG2: "#EEEEEE", + chrLabels: { fontSize: 16 }, + }, + // initialChrs: { left: "1", right: "5" } + coordinateSystem: coordinateSystem, +}; + +if (js_data.selected_chr != -1) { + config['initialChrs'] = {left: js_data.selected_chr, right: js_data.selected_chr} +} + +$('#browser_tab').click(function() { + if ($('#gwas').length == 0){ + GenomeBrowser.main(config)(); + } +}); + +document.getElementById("controls").style.visibility = "visible";
\ No newline at end of file diff --git a/gn2/wqflask/static/new/javascript/initialize_show_trait_tables.js b/gn2/wqflask/static/new/javascript/initialize_show_trait_tables.js new file mode 100644 index 00000000..44076c17 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/initialize_show_trait_tables.js @@ -0,0 +1,246 @@ +// This file initializes the tables for the show_trait page + +// This variable is just created to get the column position of the first case attribute (if case attributes exist), since it's needed to set the row classes in createdRow for the DataTable +var attributeStartPos = 3; +if (js_data.se_exists) { + attributeStartPos += 2; +} +if (js_data.has_num_cases === true) { + attributeStartPos += 1; +} + +buildColumns = function() { + let columnList = [ + { + 'data': null, + 'orderDataType': "dom-checkbox", + 'searchable' : false, + 'targets': 0, + 'width': "25px", + 'render': function() { + return '<input type="checkbox" name="searchResult" class="checkbox edit_sample_checkbox" value="">' + } + }, + { + 'title': "ID", + 'type': "natural", + 'searchable' : false, + 'targets': 1, + 'width': "35px", + 'data': "this_id" + }, + { + 'title': "Sample", + 'type': "natural", + 'data': null, + 'targets': 2, + 'width': "60px", + 'render': function(data) { + return '<span class="edit_sample_sample_name">' + data.name + '</span>' + } + }, + { + 'title': "<div style='text-align: right;'>Value</div>", + 'orderDataType': "dom-input", + 'type': "cust-txt", + 'data': null, + 'targets': 3, + 'width': "60px", + 'render': function(data) { + if (data.value == null) { + return '<input type="text" data-value="x" data-qnorm="x" data-zscore="x" name="value:' + data.name + '" style="text-align: right;" class="trait_value_input edit_sample_value" value="x" size=' + js_data.max_digits[0] + '>' + } else { + if (js_data.no_decimal_place == false) { + return '<input type="text" data-value="' + data.value.toFixed(3) + '" data-qnorm="' + js_data['qnorm_values'][0][parseInt(data.this_id) - 1] + '" data-zscore="' + js_data['zscore_values'][0][parseInt(data.this_id) - 1] + '" name="value:' + data.name + '" class="trait_value_input edit_sample_value" value="' + data.value.toFixed(3) + '" size=' + js_data.max_digits[0] + '>' + } else { + return '<input type="text" data-value="' + data.value + '" data-qnorm="' + js_data['qnorm_values'][0][parseInt(data.this_id) - 1] + '" data-zscore="' + js_data['zscore_values'][0][parseInt(data.this_id) - 1] + '" name="value:' + data.name + '" class="trait_value_input edit_sample_value" value="' + data.value + '" size=' + js_data.max_digits[0] + '>' + } + } + } + } + ]; + + attrStart = 4 + if (js_data.se_exists) { + attrStart += 2 + columnList.push( + { + 'bSortable': false, + 'type': "natural", + 'data': null, + 'targets': 4, + 'searchable' : false, + 'width': "25px", + 'render': function() { + return '±' + } + }, + { + 'title': "<div style='text-align: right;'>SE</div>", + 'orderDataType': "dom-input", + 'type': "cust-txt", + 'data': null, + 'targets': 5, + 'width': "60px", + 'render': function(data) { + if (data.variance == null) { + return '<input type="text" data-value="x" data-qnorm="x" data-zscore="x" name="value:' + data.name + '" class="trait_value_input edit_sample_se" value="x" size=6>' + } else { + return '<input type="text" data-value="' + data.variance.toFixed(3) + '" data-qnorm="x" data-zscore="x" name="value:' + data.name + '" class="trait_value_input edit_sample_se" value="' + data.variance.toFixed(3) + '" size=6>' + } + } + } + ); + + if (js_data.has_num_cases === true) { + attrStart += 1 + columnList.push( + { + 'title': "<div style='text-align: right;'>N</div>", + 'orderDataType': "dom-input", + 'type': "cust-txt", + 'data': null, + 'targets': 6, + 'width': "60px", + 'render': function(data) { + if (data.num_cases == null || data.num_cases == undefined) { + return '<input type="text" data-value="x" data-qnorm="x" data-zscore="x" name="value:' + data.name + '" class="trait_value_input edit_sample_num_cases" value="x" size=4 maxlength=4>' + } else { + return '<input type="text" data-value="' + data.num_cases + '" data-qnorm="x" data-zscore="x" name="value:' + data.name + '" class="trait_value_input edit_sample_num_cases" value="' + data.num_cases + '" size=4 maxlength=4>' + } + } + } + ); + } + } + else { + if (js_data.has_num_cases === true) { + attrStart += 1 + columnList.push( + { + 'title': "<div style='text-align: right;'>N</div>", + 'orderDataType': "dom-input", + 'type': "cust-txt", + 'data': null, + 'targets': 4, + 'width': "60px", + 'render': function(data) { + if (data.num_cases == null || data.num_cases == undefined) { + return '<input type="text" data-value="x" data-qnorm="x" data-zscore="x" name="value:' + data.name + '" class="trait_value_input edit_sample_num_cases" value="x" size=4 maxlength=4>' + } else { + return '<input type="text" data-value="' + data.num_cases + '" data-qnorm="x" data-zscore="x" name="value:' + data.name + '" class="trait_value_input edit_sample_num_cases" value="' + data.num_cases + '" size=4 maxlength=4>' + } + } + } + ); + } + } + + attrKeys = Object.keys(js_data.attributes).sort((a, b) => (js_data.attributes[a].id > js_data.attributes[b].id) ? 1 : -1) + for (i = 0; i < attrKeys.length; i++){ + columnList.push( + { + 'title': "<div title='" + js_data.attributes[attrKeys[i]].description + "' style='text-align: " + js_data.attributes[attrKeys[i]].alignment + "'>" + js_data.attributes[attrKeys[i]].name + "</div>", + 'type': "natural", + 'data': null, + 'targets': attrStart + i, + 'render': function(data, type, row, meta) { + attr_name = Object.keys(data.extra_attributes).sort((a, b) => (parseInt(a) > parseInt(b)) ? 1 : -1)[meta.col - data.first_attr_col] + + if (attr_name != null && attr_name != undefined){ + if (Array.isArray(data.extra_attributes[attr_name])){ + return '<a href="' + data.extra_attributes[attr_name][1] + '">' + data.extra_attributes[attr_name][0] + '</a>' + } else { + return data.extra_attributes[attr_name] + } + } else { + return "" + } + } + } + ) + } + return columnList +} + +columnDefs = buildColumns(); + +tableIds = ["samples_primary"] +if (js_data.sample_lists.length > 1) { + tableIds.push("samples_other") +} + +for (var i = 0; i < tableIds.length; i++) { + tableId = tableIds[i] + + if (tableId == "samples_primary"){ + tableType = "Primary" + } else { + tableType = "Other" + } + + tableSettings = { + "drawCallback": function( settings ) { + $('#' + tableId + ' 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") && event.target.tagName.toLowerCase() !== 'a'){ + $(this).removeClass("selected") + } else if (event.target.tagName.toLowerCase() !== 'a') { + $(this).addClass("selected") + } + }); + }, + 'createdRow': function ( row, data, index ) { + $(row).attr('id', tableType + "_" + data.this_id) + $(row).addClass("value_se"); + if (data.outlier) { + $(row).addClass("outlier"); + $(row).attr("style", "background-color: orange;"); + } + $('td', row).eq(1).addClass("column_name-Index") + $('td', row).eq(2).addClass("column_name-Sample") + $('td', row).eq(3).addClass("column_name-Value") + if (js_data.se_exists) { + $('td', row).eq(5).addClass("column_name-SE") + if (js_data.has_num_cases === true) { + $('td', row).eq(6).addClass("column_name-num_cases") + } else { + if (js_data.has_num_cases === true) { + $('td', row).eq(4).addClass("column_name-num_cases") + } + } + } else { + if (js_data.has_num_cases === true) { + $('td', row).eq(4).addClass("column_name-num_cases") + } + } + + for (j=0; j < attrKeys.length; j++) { + $('td', row).eq(attributeStartPos + j + 1).addClass("column_name-" + js_data.attributes[attrKeys[j]].name) + $('td', row).eq(attributeStartPos + j + 1).attr("style", "text-align: " + js_data.attributes[attrKeys[j]].alignment + "; padding-top: 2px; padding-bottom: 0px;") + } + } + } + + create_table(tableId, js_data['sample_lists'][i], columnDefs, tableSettings); + + // Enable mapping compute buttons and replace their text only after table has loaded + // This is because submitting the form prior to the table loading causes an error + $('button.submit_special').html('<span class="glyphicon glyphicon-play-circle"></span> Compute'); + $('button.submit_special').prop('disabled', false); +} + +primary_table = $('#samples_primary').DataTable(); +$('#primary_searchbox').on( 'keyup', function () { + primary_table.search($(this).val()).draw(); +} ); + +if ($('#samples_other').length) { + other_table = $('#samples_other').DataTable(); + $('#other_searchbox').on( 'keyup', function () { + other_table.search($(this).val()).draw(); + } ); +} diff --git a/gn2/wqflask/static/new/javascript/iplotMScanone_noeff.js b/gn2/wqflask/static/new/javascript/iplotMScanone_noeff.js new file mode 100644 index 00000000..5ecf46e1 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/iplotMScanone_noeff.js @@ -0,0 +1,114 @@ +// Generated by CoffeeScript 1.8.0 +var iplotMScanone_noeff, lod_data; + +iplotMScanone_noeff = function(lod_data, times, chartOpts) { + var axispos, chartdivid, chrGap, colors, darkrect, extra_digits, g_heatmap, g_lodchart, hbot, htop, i, lightrect, linecolor, linewidth, lod4curves, lod_labels, lod_ylab, lodchart_curves, lodcolumn, lodcurve, margin, mylodchart, mylodheatmap, nullcolor, nxticks, plotLodCurve, pos, svg, titlepos, totalh, totalw, wleft, wright, x, xticks, y, zlim, zthresh, _ref, _ref1, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _ref19, _ref2, _ref20, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9; + wleft = (_ref = chartOpts != null ? chartOpts.wleft : void 0) != null ? _ref : 1000; + wright = (_ref1 = chartOpts != null ? chartOpts.wright : void 0) != null ? _ref1 : 500; + htop = (_ref2 = chartOpts != null ? chartOpts.htop : void 0) != null ? _ref2 : 350; + hbot = (_ref3 = chartOpts != null ? chartOpts.hbot : void 0) != null ? _ref3 : 350; + margin = (_ref4 = chartOpts != null ? chartOpts.margin : void 0) != null ? _ref4 : { + left: 100, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + axispos = (_ref5 = chartOpts != null ? chartOpts.axispos : void 0) != null ? _ref5 : { + xtitle: 25, + ytitle: 30, + xlabel: 5, + ylabel: 5 + }; + titlepos = (_ref6 = chartOpts != null ? chartOpts.titlepos : void 0) != null ? _ref6 : 20; + chrGap = (_ref7 = chartOpts != null ? chartOpts.chrGap : void 0) != null ? _ref7 : 8; + darkrect = (_ref8 = chartOpts != null ? chartOpts.darkrect : void 0) != null ? _ref8 : "#C8C8C8"; + lightrect = (_ref9 = chartOpts != null ? chartOpts.lightrect : void 0) != null ? _ref9 : "#E6E6E6"; + nullcolor = (_ref10 = chartOpts != null ? chartOpts.nullcolor : void 0) != null ? _ref10 : "#E6E6E6"; + colors = (_ref11 = chartOpts != null ? chartOpts.colors : void 0) != null ? _ref11 : ["slateblue", "white", "crimson"]; + zlim = (_ref12 = chartOpts != null ? chartOpts.zlim : void 0) != null ? _ref12 : null; + zthresh = (_ref13 = chartOpts != null ? chartOpts.zthresh : void 0) != null ? _ref13 : null; + lod_ylab = (_ref14 = chartOpts != null ? chartOpts.lod_ylab : void 0) != null ? _ref14 : ""; + linecolor = (_ref15 = chartOpts != null ? chartOpts.linecolor : void 0) != null ? _ref15 : "darkslateblue"; + linewidth = (_ref16 = chartOpts != null ? chartOpts.linewidth : void 0) != null ? _ref16 : 2; + nxticks = (_ref17 = chartOpts != null ? chartOpts.nxticks : void 0) != null ? _ref17 : 5; + xticks = (_ref18 = chartOpts != null ? chartOpts.xticks : void 0) != null ? _ref18 : null; + lod_labels = (_ref19 = chartOpts != null ? chartOpts.lod_labels : void 0) != null ? _ref19 : null; + chartdivid = (_ref20 = chartOpts != null ? chartOpts.chartdivid : void 0) != null ? _ref20 : 'chart'; + totalh = htop + hbot + 2 * (margin.top + margin.bottom); + totalw = wleft + wright + 2 * (margin.left + margin.right); + if (lod_labels == null) { + lod_labels = times != null ? (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = times.length; _i < _len; _i++) { + x = times[_i]; + _results.push(formatAxis(times, extra_digits = 1)(x)); + } + return _results; + })() : lod_data.lodnames; + } + mylodheatmap = lodheatmap().height(htop).width(wleft).margin(margin).axispos(axispos); + svg = d3.select("div#" + chartdivid).append("svg").attr("height", totalh).attr("width", totalw); + g_heatmap = svg.append("g").attr("id", "heatmap").datum(lod_data).call(mylodheatmap); + mylodchart = lodchart().height(hbot).width(wleft).margin(margin).axispos(axispos).titlepos(titlepos).chrGap(chrGap).linecolor("none").pad4heatmap(true).darkrect(darkrect).lightrect(lightrect).ylim([0, d3.max(mylodheatmap.zlim())]).pointsAtMarkers(false); + g_lodchart = svg.append("g").attr("transform", "translate(0," + (htop + margin.top + margin.bottom) + ")").attr("id", "lodchart").datum(lod_data).call(mylodchart); + lodcurve = function(chr, lodcolumn) { + return d3.svg.line().x(function(d) { + return mylodchart.xscale()[chr](d); + }).y(function(d, i) { + return mylodchart.yscale()(Math.abs(lod_data.lodByChr[chr][i][lodcolumn])); + }); + }; + lodchart_curves = null; + plotLodCurve = function(lodcolumn) { + var chr, _i, _len, _ref21, _results; + g_lodchart.selectAll("g#curves").remove(); + lodchart_curves = g_lodchart.append("g").attr("id", "lodcurves"); + _ref21 = lod_data.chrnames; + _results = []; + for (_i = 0, _len = _ref21.length; _i < _len; _i++) { + chr = _ref21[_i]; + _results.push(lodchart_curves.append("path").datum(lod_data.posByChr[chr[0]]).attr("d", lodcurve(chr[0], lodcolumn)).attr("stroke", linecolor).attr("fill", "none").attr("stroke-width", linewidth).style("pointer-events", "none")); + } + return _results; + }; + lod4curves = { + data: [] + }; + for (pos in lod_data.pos) { + y = (function() { + var _i, _len, _ref21, _results; + _ref21 = lod_data.lodnames; + _results = []; + for (_i = 0, _len = _ref21.length; _i < _len; _i++) { + lodcolumn = _ref21[_i]; + _results.push(Math.abs(lod_data[lodcolumn][pos])); + } + return _results; + })(); + x = (function() { + var _results; + _results = []; + for (i in lod_data.lodnames) { + _results.push(+i); + } + return _results; + })(); + lod4curves.data.push({ + x: x, + y: y + }); + } + return mylodheatmap.cellSelect().on("mouseover", function(d) { + var p; + plotLodCurve(d.lodindex); + g_lodchart.select("g.title text").text("" + lod_labels[d.lodindex]); + return p = d3.format(".1f")(d.pos); + }).on("mouseout", function(d) { + lodchart_curves.remove(); + return g_lodchart.select("g.title text").text(""); + }); +}; + +iplotMScanone_noeff(lod_data = js_data.json_data); diff --git a/gn2/wqflask/static/new/javascript/loadings_plot.js b/gn2/wqflask/static/new/javascript/loadings_plot.js new file mode 100644 index 00000000..c44288c0 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/loadings_plot.js @@ -0,0 +1,109 @@ +var margin = {top: 20, right: 70, bottom: 60, left: 60} + , width = 960 - margin.left - margin.right + , height = 500 - margin.top - margin.bottom; + +var x = d3.scale.linear() + .domain([d3.min(loadings, function(d) { return d[0]; }) + 0.1*d3.min(loadings, function(d) { return d[0]; }), d3.max(loadings, function(d) { return d[0]; })]) + .range([ 0, width ]); + +var y = d3.scale.linear() + .domain([d3.min(loadings, function(d) { return d[1]; }) + 0.1*d3.min(loadings, function(d) { return d[1]; }), d3.max(loadings, function(d) { return d[1]; })]) + .range([ height, 0 ]); + +var chart = d3.select('#loadings_plot') + .append('svg:svg') + .attr('width', width + margin.right + margin.left) + .attr('height', height + margin.top + margin.bottom) + .attr('class', 'chart') + +var main = chart.append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + .attr('width', width) + .attr('height', height) + .attr('class', 'main') + +// draw the x axis +var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom'); + +main.append('g') + .attr('transform', 'translate(0,' + height + ')') + .attr('class', 'x axis') + .call(xAxis); + +chart.append("text") + .attr("class", "x label") + .attr("text-anchor", "end") + .attr("x", 550) + .attr("y", 480) + .style("font-size", 14) + .style("fill", "blue") + .text("Factor (1)"); + +chart.append("text") + .attr("class", "y label") + .attr("text-anchor", "end") + .attr("x", -200) + .attr("y", 5) + .attr("dy", ".75em") + .attr("transform", "rotate(-90)") + .style("font-size", 14) + .style("fill", "blue") + .text("Factor (2)"); + +// draw the y axis +var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + +main.append('g') + .attr('transform', 'translate(0,0)') + .attr('class', 'y axis') + .call(yAxis); + +chart.select('.x.axis') + .selectAll("text") + .style("font-size","14px"); + +chart.select('.y.axis') + .selectAll("text") + .style("font-size","14px"); + +var g = main.append("svg:g"); + +g.selectAll("scatter-dots") + .data(loadings) + .enter().append("svg:circle") + .attr("cx", function (d,i) { return x(d[0]); } ) + .attr("cy", function (d) { return y(d[1]); } ) + .attr("r", 4) + .style("fill", "blue"); + +traits_and_loadings = [] +for (i = 0; i < js_data.traits.length; i++) { + this_trait_loadings = [] + this_trait_loadings[0] = js_data.traits[i] + this_trait_loadings[1] = [loadings[i][0], loadings[i][1]] + traits_and_loadings[i] = this_trait_loadings +} + +g.selectAll("scatter-dots") + .data(traits_and_loadings) + .enter().append("text") + .attr("x", function(d, i) { return x(d[1][0]); }) + .attr("y", function(d) { return y(d[1][1]); }) + .text(function(d) { return d[0]; }) + .style("font-size", 12) + .style("fill", "blue"); + +g.selectAll("scatter-lines") + .data(loadings) + .enter().append("svg:line") + .attr("x1", x(0)) + .attr("y1", y(0)) + .attr("x2", function (d,i) {return x(d[0]); } ) + .attr("y2", function (d) { return y(d[1]); } ) + .attr("stroke-width", 1) + .attr("stroke", "red"); +
\ No newline at end of file diff --git a/gn2/wqflask/static/new/javascript/lod_chart.js b/gn2/wqflask/static/new/javascript/lod_chart.js new file mode 100644 index 00000000..014bf59b --- /dev/null +++ b/gn2/wqflask/static/new/javascript/lod_chart.js @@ -0,0 +1,473 @@ +// Generated by CoffeeScript 1.9.2 +var lodchart; + +lodchart = function() { + var additive, additive_ylab, additive_ylim, additive_yscale, additive_yticks, additivelinecolor, axispos, chart, chrGap, chrSelect, darkrect, height, lightrect, linewidth, lodcurve, lodlinecolor, lodvarname, manhattanPlot, mappingScale, margin, markerSelect, nyticks, pad4heatmap, pointcolor, pointsAtMarkers, pointsize, pointstroke, rotate_ylab, significantcolor, suggestivecolor, title, titlepos, width, xlab, xscale, ylab, ylim, yscale, yticks; + width = 800; + height = 500; + margin = { + left: 60, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + axispos = { + xtitle: 25, + ytitle: 30, + xlabel: 5, + ylabel: 5 + }; + titlepos = 20; + manhattanPlot = false; + ylim = null; + additive_ylim = null; + nyticks = 5; + yticks = null; + additive_yticks = null; + chrGap = 8; + darkrect = "#F1F1F9"; + lightrect = "#FBFBFF"; + lodlinecolor = "darkslateblue"; + additivelinecolor_plus = "red"; + additivelinecolor_negative = "green"; + linewidth = 2; + suggestivecolor = "gainsboro"; + significantcolor = "#EBC7C7"; + pointcolor = "#E9CFEC"; + pointsize = 0; + pointstroke = "black"; + title = ""; + xlab = "Chromosome"; + ylab = "LRS score"; + additive_ylab = "Additive Effect"; + rotate_ylab = null; + yscale = d3.scale.linear(); + additive_yscale = d3.scale.linear(); + xscale = null; + pad4heatmap = false; + lodcurve = null; + lodvarname = null; + markerSelect = null; + chrSelect = null; + pointsAtMarkers = true; + chart = function(selection) { + return selection.each(function(data) { + var additive_yaxis, additivecurve, chr, curves, g, gEnter, hiddenpoints, j, k, len, len1, lodvarnum, markerpoints, markertip, redraw_plot, ref, ref1, rotate_additive_ylab, suggestive_bar, svg, titlegrp, x, xaxis, yaxis; + if (manhattanPlot === true) { + pointcolor = "darkslateblue"; + pointsize = 2; + } + lodvarname = lodvarname != null ? lodvarname : data.lodnames[0]; + data[lodvarname] = (function() { + var j, len, ref, results; + ref = data[lodvarname]; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + x = ref[j]; + results.push(Math.abs(x)); + } + return results; + })(); + ylim = ylim != null ? ylim : [0, d3.max(data[lodvarname])]; + + if ('additive' in data) { + data['additive'] = (function() { + var j, len, ref, results; + ref = data['additive']; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + x = ref[j]; + results.push(x); + } + return results; + })(); + additive_ylim = additive_ylim != null ? additive_ylim : [0, d3.max(data['additive'])]; + } + lodvarnum = data.lodnames.indexOf(lodvarname); + svg = d3.select(this).selectAll("svg").data([data]); + gEnter = svg.enter().append("svg").append("g"); + svg.attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom); + g = svg.select("g"); + g.append("rect").attr("x", margin.left).attr("y", margin.top).attr("height", height).attr("width", width).attr("fill", darkrect).attr("stroke", "none"); + yscale.domain(ylim).range([height + margin.top, margin.top + margin.inner]); + yticks = yticks != null ? yticks : yscale.ticks(nyticks); + if ('additive' in data) { + additive_yscale.domain(additive_ylim).range([height + margin.top, margin.top + margin.inner + height / 2]); + additive_yticks = additive_yticks != null ? additive_yticks : additive_yscale.ticks(nyticks); + } + reorgLodData(data, lodvarname); + data = chrscales(data, width, chrGap, margin.left, pad4heatmap, mappingScale); + xscale = data.xscale; + chrSelect = g.append("g").attr("class", "chrRect").selectAll("empty").data(data.chrnames).enter().append("rect").attr("id", function(d) { + return "chrrect" + d[0]; + }).attr("x", function(d, i) { + if (i === 0 && pad4heatmap) { + return data.chrStart[i]; + } + return data.chrStart[i] - chrGap / 2; + }).attr("width", function(d, i) { + if ((i === 0 || i + 1 === data.chrnames.length) && pad4heatmap) { + return data.chrEnd[i] - data.chrStart[i] + chrGap / 2; + } + return data.chrEnd[i] - data.chrStart[i] + chrGap; + }).attr("y", margin.top).attr("height", height).attr("fill", function(d, i) { + if (i % 2) { + return darkrect; + } + return lightrect; + }).attr("stroke", "none").on("click", function(d) { + console.log("d is:", d); + return redraw_plot(d); + }); + xaxis = g.append("g").attr("class", "x axis"); + xaxis.selectAll("empty").data(data.chrnames).enter().append("text").text(function(d) { + return d[0]; + }).attr("x", function(d, i) { + return (data.chrStart[i] + data.chrEnd[i]) / 2; + }).attr("y", margin.top + height + axispos.xlabel).attr("dominant-baseline", "hanging").attr("text-anchor", "middle").attr("cursor", "pointer").on("click", function(d) { + return redraw_plot(d); + }); + xaxis.append("text").attr("class", "title").attr("y", margin.top + height + axispos.xtitle).attr("x", margin.left + width / 2).attr("fill", "slateblue").text(xlab); + redraw_plot = function(chr_ob) { + var chr_plot; + $('#topchart').remove(); + $('#chart_container').append('<div class="qtlcharts" id="topchart"></div>'); + return chr_plot = new Chr_Lod_Chart(600, 1200, chr_ob, manhattanPlot, mappingScale); + }; + rotate_ylab = rotate_ylab != null ? rotate_ylab : ylab.length > 1; + yaxis = g.append("g").attr("class", "y axis"); + yaxis.selectAll("empty").data(yticks).enter().append("line").attr("y1", function(d) { + return yscale(d); + }).attr("y2", function(d) { + return yscale(d); + }).attr("x1", margin.left).attr("x2", margin.left + 7).attr("fill", "none").attr("stroke", "white").attr("stroke-width", 1).style("pointer-events", "none"); + yaxis.selectAll("empty").data(yticks).enter().append("text").attr("y", function(d) { + return yscale(d); + }).attr("x", margin.left - axispos.ylabel).attr("fill", "blue").attr("dominant-baseline", "middle").attr("text-anchor", "end").text(function(d) { + return formatAxis(yticks)(d); + }); + yaxis.append("text").attr("class", "title").attr("y", margin.top + height / 2).attr("x", margin.left - axispos.ytitle).text(ylab).attr("transform", rotate_ylab ? "rotate(270," + (margin.left - axispos.ytitle) + "," + (margin.top + height / 2) + ")" : "").attr("text-anchor", "middle").attr("fill", "slateblue"); + if ('additive' in data) { + rotate_additive_ylab = rotate_additive_ylab != null ? rotate_additive_ylab : additive_ylab.length > 1; + additive_yaxis = g.append("g").attr("class", "y axis"); + additive_yaxis.selectAll("empty").data(additive_yticks).enter().append("line").attr("y1", function(d) { + return additive_yscale(d); + }).attr("y2", function(d) { + return additive_yscale(d); + }).attr("x1", margin.left + width).attr("x2", margin.left + width - 7).attr("fill", "none").attr("stroke", "white").attr("stroke-width", 1).style("pointer-events", "none"); + additive_yaxis.selectAll("empty").data(additive_yticks).enter().append("text").attr("y", function(d) { + return additive_yscale(d); + }).attr("x", function(d) { + return margin.left + width + axispos.ylabel + 20; + }).attr("fill", "green").attr("dominant-baseline", "middle").attr("text-anchor", "end").text(function(d) { + return formatAxis(additive_yticks)(d); + }); + additive_yaxis.append("text").attr("class", "title").attr("y", margin.top + 1.5 * height).attr("x", margin.left + width + axispos.ytitle).text(additive_ylab).attr("transform", rotate_additive_ylab ? "rotate(270," + (margin.left + width + axispos.ytitle) + ", " + (margin.top + height * 1.5) + ")" : "").attr("text-anchor", "middle").attr("fill", "green"); + } + if ('suggestive' in data) { + suggestive_bar = g.append("g").attr("class", "suggestive"); + suggestive_bar.selectAll("empty").data([data.suggestive]).enter().append("line").attr("y1", function(d) { + return yscale(d); + }).attr("y2", function(d) { + return yscale(d); + }).attr("x1", margin.left).attr("x2", margin.left + width).attr("fill", "none").attr("stroke", suggestivecolor).attr("stroke-width", 5).style("pointer-events", "none"); + suggestive_bar = g.append("g").attr("class", "significant"); + suggestive_bar.selectAll("empty").data([data.significant]).enter().append("line").attr("y1", function(d) { + return yscale(d); + }).attr("y2", function(d) { + return yscale(d); + }).attr("x1", margin.left).attr("x2", margin.left + width).attr("fill", "none").attr("stroke", significantcolor).attr("stroke-width", 5).style("pointer-events", "none"); + } + if (manhattanPlot === false) { + lodcurve = function(chr, lodcolumn) { + return d3.svg.line().x(function(d) { + return xscale[chr](d); + }).y(function(d, i) { + return yscale(data.lodByChr[chr][i][lodcolumn]); + }); + }; + if ('additive' in data) { + additivecurve = function(chr, lodcolumn) { + if (data.additiveByChr[chr][0] < 0) { + pos_neg = "negative" + } + else { + pos_neg = "positive" + } + return [pos_neg, d3.svg.line().x(function(d) { + return xscale[chr](d); + }).y(function(d, i) { + return additive_yscale(Math.abs(data.additiveByChr[chr][i])); + })]; + }; + } + curves = g.append("g").attr("id", "curves"); + ref = data.chrnames; + for (j = 0, len = ref.length; j < len; j++) { + chr = ref[j]; + if (chr.indexOf(data['chr'])) { + curves.append("path").datum(data.posByChr[chr[0]]).attr("d", lodcurve(chr[0], lodvarnum)).attr("stroke", lodlinecolor).attr("fill", "none").attr("stroke-width", linewidth).style("pointer-events", "none"); + } + } + if ('additive' in data) { + ref1 = data.chrnames; + for (k = 0, len1 = ref1.length; k < len1; k++) { + chr = ref1[k]; + if (chr.indexOf(data['chr'])) { + if (additivecurve(chr[0], lodvarnum)[0] == "negative") { + curves.append("path").datum(data.posByChr[chr[0]]).attr("d", additivecurve(chr[0], lodvarnum)[1]).attr("stroke", additivelinecolor_negative).attr("fill", "none").attr("stroke-width", 1).style("pointer-events", "none"); + } + else { + curves.append("path").datum(data.posByChr[chr[0]]).attr("d", additivecurve(chr[0], lodvarnum)[1]).attr("stroke", additivelinecolor_plus).attr("fill", "none").attr("stroke-width", 1).style("pointer-events", "none"); + } + } + } + } + } + console.log("before pointsize"); + if (pointsize > 0) { + console.log("pointsize > 0 !!!"); + } + markerpoints = g.append("g").attr("id", "markerpoints_visible"); + markerpoints.selectAll("empty").data(data.markers).enter().append("circle").attr("cx", function(d) { + return xscale[d.chr](d.pos); + }).attr("cy", function(d) { + return yscale(d.lod); + }).attr("r", pointsize).attr("fill", pointcolor).attr("stroke", pointstroke).attr("pointer-events", "hidden"); + titlegrp = g.append("g").attr("class", "title").append("text").attr("x", margin.left + width / 2).attr("y", margin.top - titlepos).text(title); + g.append("rect").attr("x", margin.left).attr("y", margin.top).attr("height", height).attr("width", function() { + if (pad4heatmap) { + return data.chrEnd.slice(-1)[0] - margin.left; + } + return data.chrEnd.slice(-1)[0] - margin.left + chrGap / 2; + }).attr("fill", "none").attr("stroke", "black").attr("stroke-width", "none"); + if (pointsAtMarkers) { + hiddenpoints = g.append("g").attr("id", "markerpoints_hidden"); + markertip = d3.tip().attr('class', 'd3-tip').html(function(d) { + return [d.name, " LRS = " + (d3.format('.2f')(d.lod))]; + }).direction("e").offset([0, 10]); + svg.call(markertip); + return markerSelect = hiddenpoints.selectAll("empty").data(data.markers).enter().append("circle").attr("cx", function(d) { + return xscale[d.chr](d.pos); + }).attr("cy", function(d) { + return yscale(d.lod); + }).attr("id", function(d) { + return d.name; + }).attr("r", d3.max([pointsize * 2, 3])).attr("opacity", 0).attr("fill", pointcolor).attr("stroke", pointstroke).attr("stroke-width", "1").on("mouseover.paneltip", function(d) { + d3.select(this).attr("opacity", 1); + return markertip.show(d); + }).on("mouseout.paneltip", function() { + return d3.select(this).attr("opacity", 0).call(markertip.hide); + }); + } + }); + }; + chart.width = function(value) { + if (!arguments.length) { + return width; + } + width = value; + return chart; + }; + chart.height = function(value) { + if (!arguments.length) { + return height; + } + height = value; + return chart; + }; + chart.margin = function(value) { + if (!arguments.length) { + return margin; + } + margin = value; + return chart; + }; + chart.titlepos = function(value) { + if (!arguments.length) { + return titlepos; + } + titlepos; + return chart; + }; + chart.axispos = function(value) { + if (!arguments.length) { + return axispos; + } + axispos = value; + return chart; + }; + chart.manhattanPlot = function(value) { + if (!arguments.length) { + return manhattanPlot; + } + manhattanPlot = value; + return chart; + }; + chart.mappingScale = function(value) { + if (!arguments.length) { + return mappingScale; + } + mappingScale = value; + return chart; + }; + chart.ylim = function(value) { + if (!arguments.length) { + return ylim; + } + ylim = value; + return chart; + }; + chart.additive_ylim = function(value) { + if (!arguments.length) { + return additive_ylim; + } + additive_ylim = value; + return chart; + }; + chart.nyticks = function(value) { + if (!arguments.length) { + return nyticks; + } + nyticks = value; + return chart; + }; + chart.yticks = function(value) { + if (!arguments.length) { + return yticks; + } + yticks = value; + return chart; + }; + chart.chrGap = function(value) { + if (!arguments.length) { + return chrGap; + } + chrGap = value; + return chart; + }; + chart.darkrect = function(value) { + if (!arguments.length) { + return darkrect; + } + darkrect = value; + return chart; + }; + chart.lightrect = function(value) { + if (!arguments.length) { + return lightrect; + } + lightrect = value; + return chart; + }; + chart.linecolor = function(value) { + var linecolor; + if (!arguments.length) { + return linecolor; + } + linecolor = value; + return chart; + }; + chart.linewidth = function(value) { + if (!arguments.length) { + return linewidth; + } + linewidth = value; + return chart; + }; + chart.pointcolor = function(value) { + if (!arguments.length) { + return pointcolor; + } + pointcolor = value; + return chart; + }; + chart.pointsize = function(value) { + if (!arguments.length) { + return pointsize; + } + pointsize = value; + return chart; + }; + chart.pointstroke = function(value) { + if (!arguments.length) { + return pointstroke; + } + pointstroke = value; + return chart; + }; + chart.title = function(value) { + if (!arguments.length) { + return title; + } + title = value; + return chart; + }; + chart.xlab = function(value) { + if (!arguments.length) { + return xlab; + } + xlab = value; + return chart; + }; + chart.ylab = function(value) { + if (!arguments.length) { + return ylab; + } + ylab = value; + return chart; + }; + chart.rotate_ylab = function(value) { + if (!arguments.length) { + return rotate_ylab; + } + rotate_ylab = value; + return chart; + }; + chart.lodvarname = function(value) { + if (!arguments.length) { + return lodvarname; + } + lodvarname = value; + return chart; + }; + chart.pad4heatmap = function(value) { + if (!arguments.length) { + return pad4heatmap; + } + pad4heatmap = value; + return chart; + }; + chart.pointsAtMarkers = function(value) { + if (!arguments.length) { + return pointsAtMarkers; + } + pointsAtMarkers = value; + return chart; + }; + chart.yscale = function() { + return yscale; + }; + chart.additive_yscale = function() { + return additive_yscale; + }; + chart.xscale = function() { + return xscale; + }; + if (manhattanPlot === false) { + chart.lodcurve = function() { + return lodcurve; + }; + } + chart.additivecurve = function() { + return additivecurve; + }; + chart.markerSelect = function() { + return markerSelect; + }; + chart.chrSelect = function() { + return chrSelect; + }; + return chart; +};
\ No newline at end of file diff --git a/gn2/wqflask/static/new/javascript/lodheatmap.js b/gn2/wqflask/static/new/javascript/lodheatmap.js new file mode 100644 index 00000000..b82c95ad --- /dev/null +++ b/gn2/wqflask/static/new/javascript/lodheatmap.js @@ -0,0 +1,277 @@ +// Generated by CoffeeScript 1.8.0 +var lodheatmap; + +lodheatmap = function() { + var axispos, cellSelect, chart, chrGap, colors, height, margin, rectcolor, rotate_ylab, title, titlepos, width, xlab, xscale, ylab, yscale, zlim, zscale, zthresh; + width = 1200; + height = 600; + margin = { + left: 100, + top: 40, + right: 40, + bottom: 40 + }; + axispos = { + xtitle: 25, + ytitle: 30, + xlabel: 5, + ylabel: 5 + }; + chrGap = 8; + titlepos = 20; + rectcolor = d3.rgb(230, 230, 230); + colors = ["slateblue", "white", "red"]; + title = ""; + xlab = "Chromosome"; + ylab = ""; + rotate_ylab = null; + zlim = null; + zthresh = null; + xscale = d3.scale.linear(); + yscale = d3.scale.linear(); + zscale = d3.scale.linear(); + cellSelect = null; + chart = function(selection) { + return selection.each(function(data) { + var cells, celltip, chr, chr_ob, extent, g, gEnter, i, j, lod, lodcol, nlod, pos, rectHeight, svg, titlegrp, xLR, xaxis, yaxis, zmax, zmin, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref, _ref1, _ref2, _ref3, _ref4; + data = reorgLodData(data); + data = chrscales(data, width, chrGap, margin.left, true); + xscale = data.xscale; + nlod = data.lodnames.length; + yscale.domain([-0.5, nlod - 0.5]).range([margin.top + height, margin.top]); + rectHeight = yscale(0) - yscale(1); + xLR = {}; + _ref = data.chrnames; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + chr = _ref[_i]; + if (data.posByChr[chr[0]].length > 0){ + xLR[chr[0]] = getLeftRight(data.posByChr[chr[0]]); + } + } + zmin = 0; + zmax = 0; + _ref1 = data.lodnames; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + lodcol = _ref1[_j]; + extent = d3.extent(data[lodcol]); + if (extent[0] < zmin) { + zmin = extent[0]; + } + if (extent[1] > zmax) { + zmax = extent[1]; + } + } + if (-zmin > zmax) { + zmax = -zmin; + } + zlim = zlim != null ? zlim : [-zmax, 0, zmax]; + if (zlim.length !== colors.length) { + console.log("zlim.length (" + zlim.length + ") != colors.length (" + colors.length + ")"); + } + zscale.domain(zlim).range(colors); + zthresh = zthresh != null ? zthresh : zmin - 1; + data.cells = []; + _ref2 = data.chrnames; + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + chr_ob = _ref2[_k]; + chr = chr_ob[0]; + _ref3 = data.posByChr[chr]; + for (i = _l = 0, _len3 = _ref3.length; _l < _len3; i = ++_l) { + pos = _ref3[i]; + _ref4 = data.lodByChr[chr][i]; + for (j = _m = 0, _len4 = _ref4.length; _m < _len4; j = ++_m) { + lod = _ref4[j]; + if (lod >= zthresh || lod <= -zthresh) { + data.cells.push({ + z: lod, + left: (xscale[chr](pos) + xscale[chr](xLR[chr][pos].left)) / 2, + right: (xscale[chr](pos) + xscale[chr](xLR[chr][pos].right)) / 2, + lodindex: j, + chr: chr, + pos: pos + }); + } + } + } + } + svg = d3.select(this).selectAll("svg").data([data]); + gEnter = svg.enter().append("svg").append("g"); + svg.attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom); + g = svg.select("g"); + g.append("g").attr("id", "boxes").selectAll("empty").data(data.chrnames).enter().append("rect").attr("id", function(d) { + return "box" + d[0]; + }).attr("x", function(d, i) { + return data.chrStart[i]; + }).attr("y", function(d) { + return margin.top; + }).attr("height", height).attr("width", function(d, i) { + return data.chrEnd[i] - data.chrStart[i]; + }).attr("fill", rectcolor).attr("stroke", "none"); + titlegrp = g.append("g").attr("class", "title").append("text").attr("x", margin.left + width / 2).attr("y", margin.top - titlepos).text(title); + xaxis = g.append("g").attr("class", "x axis"); + xaxis.selectAll("empty").data(data.chrnames).enter().append("text").attr("x", function(d, i) { + return (data.chrStart[i] + data.chrEnd[i]) / 2; + }).attr("y", margin.top + height + axispos.xlabel).text(function(d) { + return d[0]; + }); + xaxis.append("text").attr("class", "title").attr("x", margin.left + width / 2).attr("y", margin.top + height + axispos.xtitle).text(xlab); + rotate_ylab = rotate_ylab != null ? rotate_ylab : ylab.length > 1; + yaxis = g.append("g").attr("class", "y axis"); + yaxis.append("text").attr("class", "title").attr("y", margin.top + height / 2).attr("x", margin.left - axispos.ytitle).text(ylab).attr("transform", rotate_ylab ? "rotate(270," + (margin.left - axispos.ytitle) + "," + (margin.top + height / 2) + ")" : ""); + yaxis.selectAll("empty").data(data.lodnames).enter().append("text").attr("id", function(d, i) { + return "yaxis" + i; + }).attr("y", function(d, i) { + return yscale(i); + }).attr("x", margin.left - axispos.ylabel).text(function(d) { + return d; + }).attr("opacity", 0); + celltip = d3.tip().attr('class', 'd3-tip').html(function(d) { + var p, z; + z = d3.format(".2f")(Math.abs(d.z)); + p = d3.format(".1f")(d.pos); + return "" + d.chr + "@" + p + " → " + z; + }).direction('e').offset([0, 10]); + svg.call(celltip); + cells = g.append("g").attr("id", "cells"); + cellSelect = cells.selectAll("empty").data(data.cells).enter().append("rect").attr("x", function(d) { + return d.left; + }).attr("y", function(d) { + return yscale(d.lodindex) - rectHeight / 2; + }).attr("width", function(d) { + return d.right - d.left; + }).attr("height", rectHeight).attr("class", function(d, i) { + return "cell" + i; + }).attr("fill", function(d) { + return zscale(d.z); + }).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, this); + }).on("mouseout.paneltip", function(d) { + yaxis.select("text#yaxis" + d.lodindex).attr("opacity", 0); + d3.select(this).attr("stroke", "none"); + return celltip.hide(); + }); + return g.append("g").attr("id", "boxes").selectAll("empty").data(data.chrnames).enter().append("rect").attr("id", function(d) { + return "box" + d; + }).attr("x", function(d, i) { + return data.chrStart[i]; + }).attr("y", function(d) { + return margin.top; + }).attr("height", height).attr("width", function(d, i) { + return data.chrEnd[i] - data.chrStart[i]; + }).attr("fill", "none").attr("stroke", "black").attr("stroke-width", "none"); + }); + }; + chart.width = function(value) { + if (!arguments.length) { + return width; + } + width = value; + return chart; + }; + chart.height = function(value) { + if (!arguments.length) { + return height; + } + height = value; + return chart; + }; + chart.margin = function(value) { + if (!arguments.length) { + return margin; + } + margin = value; + return chart; + }; + chart.axispos = function(value) { + if (!arguments.length) { + return axispos; + } + axispos = value; + return chart; + }; + chart.titlepos = function(value) { + if (!arguments.length) { + return titlepos; + } + titlepos; + return chart; + }; + chart.rectcolor = function(value) { + if (!arguments.length) { + return rectcolor; + } + rectcolor = value; + return chart; + }; + chart.colors = function(value) { + if (!arguments.length) { + return colors; + } + colors = value; + return chart; + }; + chart.title = function(value) { + if (!arguments.length) { + return title; + } + title = value; + return chart; + }; + chart.xlab = function(value) { + if (!arguments.length) { + return xlab; + } + xlab = value; + return chart; + }; + chart.ylab = function(value) { + if (!arguments.length) { + return ylab; + } + ylab = value; + return chart; + }; + chart.rotate_ylab = function(value) { + if (!arguments.length) { + return rotate_ylab; + } + rotate_ylab = value; + return chart; + }; + chart.zthresh = function(value) { + if (!arguments.length) { + return zthresh; + } + zthresh = value; + return chart; + }; + chart.zlim = function(value) { + if (!arguments.length) { + return zlim; + } + zlim = value; + return chart; + }; + chart.chrGap = function(value) { + if (!arguments.length) { + return chrGap; + } + chrGap = value; + return chart; + }; + chart.xscale = function() { + return xscale; + }; + chart.yscale = function() { + return yscale; + }; + chart.zscale = function() { + return zscale; + }; + chart.cellSelect = function() { + return cellSelect; + }; + return chart; +}; diff --git a/gn2/wqflask/static/new/javascript/login.js b/gn2/wqflask/static/new/javascript/login.js new file mode 100644 index 00000000..2fe9ba3c --- /dev/null +++ b/gn2/wqflask/static/new/javascript/login.js @@ -0,0 +1,41 @@ +// Generated by CoffeeScript 1.8.0 +$(function() { + var form_success, modalize, submit_form; + modalize = function(event) { + event.preventDefault(); + console.log("in modal_replace:", $(this).attr("href")); + return $.colorbox({ + open: true, + href: this.href, + onComplete: function() { + return $(".focused").focus(); + } + }); + }; + $(document).one("click", ".modalize", modalize); + console.log("Modalized click!!!"); + form_success = function(data) { + return $.colorbox({ + open: true, + html: data, + onComplete: function() { + return $("form").on("submit", submit_form); + } + }); + }; + submit_form = function(event) { + var data, submit_to; + event.preventDefault(); + submit_to = $(this).attr('action'); + data = $(this).serialize(); + console.log("submit_to is:", submit_to); + return $.ajax({ + type: "POST", + url: submit_to, + data: data, + dataType: "html", + success: form_success + }); + }; + return $("#colorbox form").on("submit", submit_form); +}); diff --git a/gn2/wqflask/static/new/javascript/network_graph.js b/gn2/wqflask/static/new/javascript/network_graph.js new file mode 100644 index 00000000..480443ee --- /dev/null +++ b/gn2/wqflask/static/new/javascript/network_graph.js @@ -0,0 +1,259 @@ +var default_style = [ // the stylesheet for the graph + { + selector: 'node', + style: { + 'background-color': '#666', + 'label': 'data(label)', + 'font-size': 10 + } + }, + + { + selector: 'edge', + style: { + 'width': 'data(width)', + 'line-color': 'data(color)', + 'target-arrow-color': '#ccc', + 'target-arrow-shape': 'triangle', + 'font-size': 8 + } + } + ] + +var default_layout = { name: 'circle', + fit: true, // whether to fit the viewport to the graph + padding: 30 // the padding on fit + //idealEdgeLength: function( edge ){ return edge.data['correlation']*10; }, + } + +window.onload=function() { + // id of Cytoscape Web container div + //var div_id = "cytoscapeweb"; + + var cy = cytoscape({ + container: $('#cytoscapeweb'), // container to render in + + elements: elements_list, + + style: default_style, + + zoom: 12, + layout: default_layout, + + zoomingEnabled: true, + userZoomingEnabled: true, + panningEnabled: true, + userPanningEnabled: true, + boxSelectionEnabled: false, + selectionType: 'single', + + // rendering options: + styleEnabled: true + }); + + var eles = cy.$() // var containing all elements, so elements can be restored after being removed + + var defaults = { + zoomFactor: 0.05, // zoom factor per zoom tick + zoomDelay: 45, // how many ms between zoom ticks + minZoom: 0.1, // min zoom level + maxZoom: 10, // max zoom level + fitPadding: 30, // padding when fitting + panSpeed: 10, // how many ms in between pan ticks + panDistance: 10, // max pan distance per tick + panDragAreaSize: 75, // the length of the pan drag box in which the vector for panning is calculated (bigger = finer control of pan speed and direction) + panMinPercentSpeed: 0.25, // the slowest speed we can pan by (as a percent of panSpeed) + panInactiveArea: 8, // radius of inactive area in pan drag box + panIndicatorMinOpacity: 0.5, // min opacity of pan indicator (the draggable nib); scales from this to 1.0 + zoomOnly: false, // a minimal version of the ui only with zooming (useful on systems with bad mousewheel resolution) + fitSelector: undefined, // selector of elements to fit + animateOnFit: function(){ // whether to animate on fit + return false; + }, + fitAnimationDuration: 1000, // duration of animation on fit + + // icon class names + sliderHandleIcon: 'fa fa-minus', + zoomInIcon: 'fa fa-plus', + zoomOutIcon: 'fa fa-minus', + resetIcon: 'fa fa-expand' + }; + + cy.panzoom( defaults ); + + function create_qtips(cy){ + cy.nodes().qtip({ + content: function(){ + qtip_content = '' + gn_link = '<b>'+'<a href="' + gn2_url + '/show_trait?trait_id=' + this.data().id.split(":")[0] + '&dataset=' + this.data().id.split(":")[1] + '" >'+this.data().id +'</a>'+'</b><br>' + qtip_content += gn_link + if (typeof(this.data().geneid) !== 'undefined'){ + ncbi_link = '<a href="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?db=gene&cmd=Retrieve&dopt=Graphics&list_uids=' + this.data().geneid + '" >NCBI<a>'+'<br>' + qtip_content += ncbi_link + } + if (typeof(this.data().omim) !== 'undefined'){ + omim_link = '<a href="http://www.ncbi.nlm.nih.gov/omim/' + this.data().omim + '" >OMIM<a>'+'<br>' + qtip_content += omim_link + } + return qtip_content + }, + position: { + my: 'top center', + at: 'bottom center' + }, + style: { + classes: 'qtip-bootstrap', + tip: { + width: 16, + height: 8 + } + } + }); + + cy.edges().qtip({ + content: function(){ + correlation_line = '<b>Sample r: ' + this.data().correlation + '</b><br>' + p_value_line = 'Sample p(r): ' + this.data().p_value + '<br>' + overlap_line = 'Overlap: ' + this.data().overlap + '<br>' + scatter_plot = '<a href="' + gn2_url + '/corr_scatter_plot?method=pearson&dataset_1=' + this.data().source.split(":")[1] + '&dataset_2=' + this.data().target.split(":")[1] + '&trait_1=' + this.data().source.split(":")[0] + '&trait_2=' + this.data().target.split(":")[0] + '" >View Scatterplot</a>' + return correlation_line + p_value_line + overlap_line + scatter_plot + }, + position: { + my: 'top center', + at: 'bottom center' + }, + style: { + classes: 'qtip-bootstrap', + tip: { + width: 16, + height: 8 + } + } + }); + } + + create_qtips(cy) + + $('#neg_slide').change(function() { + eles.restore() + + pos_slide_val = $('#pos_slide').val(); + cy.$("node[max_corr > " + $(this).val() + "][max_corr < " + pos_slide_val + "]").remove(); + cy.$("edge[correlation > " + $(this).val() + "][correlation < " + pos_slide_val + "]").remove(); + + cy.layout({ name: $('select[name=layout_select]').val(), + fit: true, // whether to fit the viewport to the graph + padding: 25 // the padding on fit + }).run(); + + }); + $('#pos_slide').change(function() { + eles.restore() + + neg_slide_val = $('#neg_slide').val(); + cy.$("node[max_corr > " + neg_slide_val +"][max_corr < " + $(this).val() + "]").remove(); + cy.$("edge[correlation > " + neg_slide_val +"][correlation < " + $(this).val() + "]").remove(); + + cy.layout({ name: $('select[name=layout_select]').val(), + fit: true, // whether to fit the viewport to the graph + padding: 25 // the padding on fit + }).run(); + + }); + + $('#reset_graph').click(function() { + eles.restore() + $('#pos_slide').val(0) + $('#neg_slide').val(0) + cy.layout({ name: $('select[name=layout_select]').val(), + fit: true, // whether to fit the viewport to the graph + padding: 25 // the padding on fit + }).run(); + }); + + $('select[name=focus_select]').change(function() { + focus_trait = $(this).val() + + eles.restore() + cy.$('edge[source != "' + focus_trait + '"][target != "' + focus_trait + '"]').remove() + + cy.layout({ name: $('select[name=layout_select]').val(), + fit: true, // whether to fit the viewport to the graph + padding: 25 // the padding on fit + }).run(); + }); + + $('select[name=layout_select]').change(function() { + layout_type = $(this).val() + cy.layout({ name: layout_type, + fit: true, // whether to fit the viewport to the graph + padding: 25 // the padding on fit + }).run(); + }); + + $('select[name=font_size]').change(function() { + font_size = $(this).val() + + new_style = default_style + new_style[0]['style']['font-size'] = parseInt(font_size) + cy.style().fromJson(new_style).update() + }); + $('select[name=edge_width]').change(function() { + //eles.restore() + + //ZS: This is needed, or else it alters the original object + orig_elements = JSON.parse(JSON.stringify(elements_list)); + + width_multiplier = $(this).val() + updated_elements = [] + for (i=0; i < orig_elements.length; i++){ + this_element = orig_elements[i] + if ('source' in this_element['data']) { + orig_width = this_element['data']['width'] + this_element['data']['width'] = orig_width * width_multiplier + } + updated_elements.push(this_element) + } + cy.remove(eles) + cy.add(updated_elements) + cy.layout({ name: $('select[name=layout_select]').val(), + fit: true, // whether to fit the viewport to the graph + padding: 25 // the padding on fit + }).run(); + }); + + $('select[name=edge_width]').change(function() { + //eles.restore() + + //ZS: This is needed, or else it alters the original object + orig_elements = JSON.parse(JSON.stringify(elements_list)); + + width_multiplier = $(this).val() + updated_elements = [] + for (i=0; i < orig_elements.length; i++){ + this_element = orig_elements[i] + if ('source' in this_element['data']) { + orig_width = this_element['data']['width'] + this_element['data']['width'] = orig_width * width_multiplier + } + updated_elements.push(this_element) + } + cy.remove(eles) + cy.add(updated_elements) + cy.layout({ name: $('select[name=layout_select]').val(), + fit: true, // whether to fit the viewport to the graph + padding: 25 // the padding on fit + }).run(); + }); + + $("a#image_link").click(function(e) { + var pngData = cy.png(); + + $(this).attr('href', pngData); + $(this).attr('download', 'network_graph.png'); + }); + + +}; + + diff --git a/gn2/wqflask/static/new/javascript/panelutil.js b/gn2/wqflask/static/new/javascript/panelutil.js new file mode 100644 index 00000000..ea55a7cf --- /dev/null +++ b/gn2/wqflask/static/new/javascript/panelutil.js @@ -0,0 +1,462 @@ +// Generated by CoffeeScript 1.8.0 +var abs, calc_crosstab, chrscales, colSums, displayError, expand2vector, forceAsArray, formatAxis, getLeftRight, log10, log2, matrixExtent, matrixMax, matrixMaxAbs, matrixMin, maxdiff, median, missing2null, pullVarAsArray, reorgLodData, rowSums, selectGroupColors, sumArray, transpose, unique; + +formatAxis = function(d, extra_digits) { + var ndig; + if (extra_digits == null) { + extra_digits = 0; + } + d = d[1] - d[0]; + ndig = Math.floor(Math.log(d % 10) / Math.log(10)); + if (ndig > 0) { + ndig = 0; + } + ndig = Math.abs(ndig) + extra_digits; + return d3.format("." + ndig + "f"); +}; + +unique = function(x) { + var output, v, _i, _len, _results; + output = {}; + for (_i = 0, _len = x.length; _i < _len; _i++) { + v = x[_i]; + if (v) { + output[v] = v; + } + } + _results = []; + for (v in output) { + _results.push(output[v]); + } + return _results; +}; + +pullVarAsArray = function(data, variable) { + var i, v; + v = []; + for (i in data) { + v = v.concat(data[i][variable]); + } + return v; +}; + +reorgLodData = function(data, lodvarname) { + var chr, i, j, lodcolumn, lodval, marker, pos, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2; + if (lodvarname == null) { + lodvarname = null; + } + data.posByChr = {}; + data.lodByChr = {}; + if ('additive' in data){ + data.additiveByChr = {}; + } + _ref = data.chrnames; + for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { + chr = _ref[i]; + data.posByChr[chr[0]] = []; + data.lodByChr[chr[0]] = []; + if ('additive' in data){ + data.additiveByChr[chr[0]] = []; + } + _ref1 = data.pos; + + for (j = _j = 0, _len1 = _ref1.length; _j < _len1; j = ++_j) { + pos = _ref1[j]; + if (data.chr[j].toString() === chr[0]) { + data.posByChr[chr[0]].push(pos); + if (!Array.isArray(data.lodnames)) { + data.lodnames = [data.lodnames]; + } + lodval = (function() { + var _k, _len2, _ref2, _results; + _ref2 = data.lodnames; + _results = []; + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + lodcolumn = _ref2[_k]; + _results.push(data[lodcolumn][j]); + } + return _results; + })(); + data.lodByChr[chr[0]].push(lodval); + + if ('additive' in data){ + addval = data['additive'][j] + data.additiveByChr[chr[0]].push(addval); + } + + } + } + } + if (lodvarname != null) { + data.markers = []; + _ref2 = data.markernames; + for (i = _k = 0, _len2 = _ref2.length; _k < _len2; i = ++_k) { + marker = _ref2[i]; + if (marker !== "") { + data.markers.push({ + name: marker, + chr: data.chr[i], + pos: data.pos[i], + lod: data[lodvarname][i] + }); + } + } + } + return data; +}; + +chrscales = function(data, width, chrGap, leftMargin, pad4heatmap, mappingScale) { + var L, chr, chrEnd, chrLength, chrStart, cur, d, i, maxd, rng, totalChrLength, w, _i, _j, _len, _len1, _ref, _ref1; + chrStart = []; + chrEnd = []; + chrLength = []; + totalChrLength = 0; + maxd = 0; + _ref = data.chrnames; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + chr = _ref[_i]; + d = maxdiff(data.posByChr[chr[0]]); + if (d > maxd) { + maxd = d; + } + //rng = d3.extent(data.posByChr[chr[0]]); + //chrStart.push(rng[0]); + //chrEnd.push(rng[1]); + //L = rng[1] - rng[0]; + chrStart.push(0); + chrEnd.push(chr[1]); + L = chr[1] + chrLength.push(L); + totalChrLength += L; + } + if (pad4heatmap) { + data.recwidth = maxd; + chrStart = chrStart.map(function(x) { + return x - maxd / 2; + }); + chrEnd = chrEnd.map(function(x) { + return x + maxd / 2; + }); + chrLength = chrLength.map(function(x) { + return x + maxd; + }); + totalChrLength += chrLength.length * maxd; + } + data.chrStart = []; + data.chrEnd = []; + cur = leftMargin; + if (!pad4heatmap) { + cur += chrGap / 2; + } + data.xscale = {}; + _ref1 = data.chrnames; + for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) { + chr = _ref1[i]; + data.chrStart.push(cur); + w = Math.round((width - chrGap * (data.chrnames.length - pad4heatmap)) / totalChrLength * chrLength[i]); + data.chrEnd.push(cur + w); + cur = data.chrEnd[i] + chrGap; + + if (mappingScale == "morgan") { + max_pos = d3.max(data.posByChr[chr[0]]) + data.xscale[chr[0]] = d3.scale.linear().domain([chrStart[i], max_pos]).range([data.chrStart[i], data.chrEnd[i]]); + } + else { + data.xscale[chr[0]] = d3.scale.linear().domain([chrStart[i], chrEnd[i]]).range([data.chrStart[i], data.chrEnd[i]]); + } + } + return data; +}; + +selectGroupColors = function(ngroup, palette) { + if (ngroup === 0) { + return []; + } + if (palette === "dark") { + if (ngroup === 1) { + return ["slateblue"]; + } + if (ngroup === 2) { + return ["MediumVioletRed", "slateblue"]; + } + if (ngroup <= 9) { + return colorbrewer.Set1[ngroup]; + } + return d3.scale.category20().range().slice(0, ngroup); + } else { + if (ngroup === 1) { + return ["#bebebe"]; + } + if (ngroup === 2) { + return ["lightpink", "lightblue"]; + } + if (ngroup <= 9) { + return colorbrewer.Pastel1[ngroup]; + } + return ["#8fc7f4", "#fed7f8", "#ffbf8e", "#fffbb8", "#8ce08c", "#d8ffca", "#f68788", "#ffd8d6", "#d4a7fd", "#f5f0f5", "#cc968b", "#f4dcd4", "#f3b7f2", "#f7f6f2", "#bfbfbf", "#f7f7f7", "#fcfd82", "#fbfbcd", "#87feff", "#defaf5"].slice(0, ngroup); + } +}; + +expand2vector = function(input, n) { + var i; + if (input == null) { + return input; + } + if (Array.isArray(input) && input.length >= n) { + return input; + } + if (!Array.isArray(input)) { + input = [input]; + } + if (input.length === 1 && n > 1) { + input = (function() { + var _results; + _results = []; + for (i in d3.range(n)) { + _results.push(input[0]); + } + return _results; + })(); + } + return input; +}; + +median = function(x) { + var n; + if (x == null) { + return null; + } + n = x.length; + x.sort(function(a, b) { + return a - b; + }); + if (n % 2 === 1) { + return x[(n - 1) / 2]; + } + return (x[n / 2] + x[(n / 2) - 1]) / 2; +}; + +getLeftRight = function(x) { + var i, n, result, v, xdif, _i, _j, _k, _len, _ref; + n = x.length; + x.sort(function(a, b) { + return a - b; + }); + xdif = []; + result = {}; + for (_i = 0, _len = x.length; _i < _len; _i++) { + v = x[_i]; + result[v] = {}; + } + for (i = _j = 1; 1 <= n ? _j < n : _j > n; i = 1 <= n ? ++_j : --_j) { + xdif.push(x[i] - x[i - 1]); + result[x[i]].left = x[i - 1]; + } + for (i = _k = 0, _ref = n - 1; 0 <= _ref ? _k < _ref : _k > _ref; i = 0 <= _ref ? ++_k : --_k) { + result[x[i]].right = x[i + 1]; + } + xdif = median(xdif); + result.mediandiff = xdif; + result[x[0]].left = x[0] - xdif; + result[x[n - 1]].right = x[n - 1] + xdif; + result.extent = [x[0] - xdif / 2, x[n - 1] + xdif / 2]; + return result; +}; + +maxdiff = function(x) { + var d, i, result, _i, _ref; + if (x.length < 2) { + return null; + } + result = x[1] - x[0]; + if (x.length < 3) { + return result; + } + for (i = _i = 2, _ref = x.length; 2 <= _ref ? _i < _ref : _i > _ref; i = 2 <= _ref ? ++_i : --_i) { + d = x[i] - x[i - 1]; + if (d > result) { + result = d; + } + } + return result; +}; + +matrixMin = function(mat) { + var i, j, result; + result = mat[0][0]; + for (i in mat) { + for (j in mat[i]) { + if (result > mat[i][j]) { + result = mat[i][j]; + } + } + } + return result; +}; + +matrixMax = function(mat) { + var i, j, result; + result = mat[0][0]; + for (i in mat) { + for (j in mat[i]) { + if (result < mat[i][j]) { + result = mat[i][j]; + } + } + } + return result; +}; + +matrixMaxAbs = function(mat) { + var i, j, result; + result = Math.abs(mat[0][0]); + for (i in mat) { + for (j in mat[i]) { + if (result < mat[i][j]) { + result = Math.abs(mat[i][j]); + } + } + } + return result; +}; + +matrixExtent = function(mat) { + return [matrixMin(mat), matrixMax(mat)]; +}; + +d3.selection.prototype.moveToFront = function() { + return this.each(function() { + return this.parentNode.appendChild(this); + }); +}; + +d3.selection.prototype.moveToBack = function() { + return this.each(function() { + var firstChild; + firstChild = this.parentNode.firstchild; + if (firstChild) { + return this.parentNode.insertBefore(this, firstChild); + } + }); +}; + +forceAsArray = function(x) { + if (x == null) { + return x; + } + if (Array.isArray(x)) { + return x; + } + return [x]; +}; + +missing2null = function(vec, missingvalues) { + if (missingvalues == null) { + missingvalues = ['NA', '']; + } + return vec.map(function(value) { + if (missingvalues.indexOf(value) > -1) { + return null; + } else { + return value; + } + }); +}; + +displayError = function(message) { + if (d3.select("div.error").empty()) { + d3.select("body").insert("div", ":first-child").attr("class", "error"); + } + return d3.select("div.error").append("p").text(message); +}; + +sumArray = function(vec) { + return vec.reduce(function(a, b) { + return a + b; + }); +}; + +calc_crosstab = function(data) { + var col, cs, i, ncol, nrow, result, row, rs, _i, _j; + nrow = data.ycat.length; + ncol = data.xcat.length; + result = (function() { + var _i, _results; + _results = []; + for (row = _i = 0; 0 <= nrow ? _i <= nrow : _i >= nrow; row = 0 <= nrow ? ++_i : --_i) { + _results.push((function() { + var _j, _results1; + _results1 = []; + for (col = _j = 0; 0 <= ncol ? _j <= ncol : _j >= ncol; col = 0 <= ncol ? ++_j : --_j) { + _results1.push(0); + } + return _results1; + })()); + } + return _results; + })(); + for (i in data.x) { + result[data.y[i]][data.x[i]] += 1; + } + rs = rowSums(result); + cs = colSums(result); + for (i = _i = 0; 0 <= ncol ? _i < ncol : _i > ncol; i = 0 <= ncol ? ++_i : --_i) { + result[nrow][i] = cs[i]; + } + for (i = _j = 0; 0 <= nrow ? _j < nrow : _j > nrow; i = 0 <= nrow ? ++_j : --_j) { + result[i][ncol] = rs[i]; + } + result[nrow][ncol] = sumArray(rs); + return result; +}; + +rowSums = function(mat) { + var x, _i, _len, _results; + _results = []; + for (_i = 0, _len = mat.length; _i < _len; _i++) { + x = mat[_i]; + _results.push(sumArray(x)); + } + return _results; +}; + +transpose = function(mat) { + var i, j, _i, _ref, _results; + _results = []; + for (j = _i = 0, _ref = mat[0].length; 0 <= _ref ? _i < _ref : _i > _ref; j = 0 <= _ref ? ++_i : --_i) { + _results.push((function() { + var _j, _ref1, _results1; + _results1 = []; + for (i = _j = 0, _ref1 = mat.length; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) { + _results1.push(mat[i][j]); + } + return _results1; + })()); + } + return _results; +}; + +colSums = function(mat) { + return rowSums(transpose(mat)); +}; + +log2 = function(x) { + if (x == null) { + return x; + } + return Math.log(x) / Math.log(2.0); +}; + +log10 = function(x) { + if (x == null) { + return x; + } + return Math.log(x) / Math.log(10.0); +}; + +abs = function(x) { + if (x == null) { + return x; + } + return Math.abs(x); +};
\ No newline at end of file diff --git a/gn2/wqflask/static/new/javascript/partial_correlations.js b/gn2/wqflask/static/new/javascript/partial_correlations.js new file mode 100644 index 00000000..5de1204c --- /dev/null +++ b/gn2/wqflask/static/new/javascript/partial_correlations.js @@ -0,0 +1,26 @@ +function selected_traits() { + traits = $("#trait_table input:checked").map(function() { + return $(this).attr("data-trait-info"); + }).get(); + if (traits.length == 0){ + num_traits = $("#trait_table input").length + if (num_traits <= 100){ + traits = $("#trait_table input").map(function() { + return $(this).attr("data-trait-info"); + }).get(); + } + } + return traits +} + +$("#partial-correlations").on("click", function() { + // Submit the form to the `partial_correlations` endpoint + url = $(this).data("url") + traits = selected_traits(); + $("#trait_list").val(traits.reduce(function(acc, str) { + return acc.concat(";;;".concat(str)); + })); + $("input[name=tool_used]").val("Partial Correlation") + $("input[name=form_url]").val(url) + return submit_special(url) +}) diff --git a/gn2/wqflask/static/new/javascript/password_strength.js b/gn2/wqflask/static/new/javascript/password_strength.js new file mode 100644 index 00000000..a8a45f7d --- /dev/null +++ b/gn2/wqflask/static/new/javascript/password_strength.js @@ -0,0 +1,57 @@ +// Generated by CoffeeScript 1.8.0 + +$(function() { + var word_score; + $("#password").keyup(function() { + var crack_time, display, passtext, result, word; + passtext = $(this).val(); + result = zxcvbn(passtext); + if (passtext.length < 6) { + let error_message = `<strong style="color:red;">the password must have a length greater than six characters</strong>` + $("#password_strength").html(error_message); + return $("#password_alert").fadeIn(); + } else { + word = word_score(result.score); + crack_time = result.crack_times_display.online_throttling_100_per_hour; + if (crack_time === "instant") { + crack_time = "a second"; + } + display = `This is ${word} password.` + $("#password_strength").html(display); + return $("#password_alert").fadeIn(); + } + }); + return word_score = function(num_score) { + num_score = parseInt(num_score); + console.log("num_score is:", num_score); + + let passwordFeedback = { + 0:{ + color:"#cc1818", + feedback:"a weak" + }, + + 1:{ + color:"#cc1818", + feedback:"a bad" + }, + + 2:{ + color:"#f59105", + feedback:"a mediocre" + }, + + 3:{ + color:"#44ba34", + feedback:"a strong" + }, + 4:{ + color:"green", + feedback:"a stronger" + } + } + + let mappingResult = `<strong style="color:${passwordFeedback[num_score].color};">${passwordFeedback[num_score].feedback}</strong>` + return mappingResult; + }; +}); diff --git a/gn2/wqflask/static/new/javascript/plotly_probability_plot.js b/gn2/wqflask/static/new/javascript/plotly_probability_plot.js new file mode 100644 index 00000000..bc1e021b --- /dev/null +++ b/gn2/wqflask/static/new/javascript/plotly_probability_plot.js @@ -0,0 +1,308 @@ +// Generated by CoffeeScript 1.9.2 +(function() { + var get_z_scores, redraw_prob_plot, root; + + root = typeof exports !== "undefined" && exports !== null ? exports : this; + + get_z_scores = function(n) { + var i, j, osm_uniform, ref, x; + osm_uniform = new Array(n); + osm_uniform[n - 1] = Math.pow(0.5, 1.0 / n); + osm_uniform[0] = 1 - osm_uniform[n - 1]; + for (i = j = 1, ref = n - 2; 1 <= ref ? j <= ref : j >= ref; i = 1 <= ref ? ++j : --j) { + osm_uniform[i] = (i + 1 - 0.3175) / (n + 0.365); + } + return (function() { + var k, len, results; + results = []; + for (k = 0, len = osm_uniform.length; k < len; k++) { + x = osm_uniform[k]; + results.push(jStat.normal.inv(x, 0, 1)); + } + return results; + })(); + }; + + redraw_prob_plot = function(samples, sample_group) { + var container, h, margin, totalh, totalw, w; + h = 550; + w = 600; + margin = { + left: 60, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + totalh = h + margin.top + margin.bottom; + totalw = w + margin.left + margin.right; + container = $("#prob_plot_container"); + container.width(totalw); + container.height(totalh); + var W, all_samples, chart, data, intercept, make_data, names, pvalue, pvalue_str, slope, sorted_names, sorted_values, sw_result, test_str, x, z_scores; + all_samples = samples[sample_group]; + names = (function() { + var j, len, ref, results; + ref = _.keys(all_samples); + results = []; + for (j = 0, len = ref.length; j < len; j++) { + x = ref[j]; + if (all_samples[x] !== null) { + results.push(x); + } + } + return results; + })(); + sorted_names = names.sort(function(x, y) { + return all_samples[x].value - all_samples[y].value; + }); + max_decimals = 0 + sorted_values = (function() { + var j, len, results; + results = []; + for (j = 0, len = sorted_names.length; j < len; j++) { + x = sorted_names[j]; + results.push(all_samples[x].value); + if (all_samples[x].value.countDecimals() > max_decimals) { + max_decimals = all_samples[x].value.countDecimals()-1 + } + } + return results; + })(); + //ZS: 0.1 indicates buffer, increase to increase buffer + y_domain = [sorted_values[0] - (sorted_values.slice(-1)[0] - sorted_values[0])*0.1, sorted_values.slice(-1)[0] + (sorted_values.slice(-1)[0] - sorted_values[0])*0.1] + //sw_result = ShapiroWilkW(sorted_values); + //W = sw_result.w.toFixed(3); + //pvalue = sw_result.p.toFixed(3); + //pvalue_str = pvalue > 0.05 ? pvalue.toString() : "<span style='color:red'>" + pvalue + "</span>"; + //test_str = "Shapiro-Wilk test statistic is " + W + " (p = " + pvalue_str + ")"; + z_scores = get_z_scores(sorted_values.length); + //ZS: 0.1 indicates buffer, increase to increase buffer + x_domain = [z_scores[0] - (z_scores.slice(-1)[0] - z_scores[0])*0.1, z_scores.slice(-1)[0] + (z_scores.slice(-1)[0] - z_scores[0])*0.1] + slope = jStat.stdev(sorted_values); + intercept = jStat.mean(sorted_values); + make_data = function(group_name) { + var sample, value, z_score; + return { + key: js_data.sample_group_types[group_name], + slope: slope, + intercept: intercept, + values: (function() { + var j, len, ref, ref1, results; + ref = _.zip(get_z_scores(sorted_values.length), sorted_values, sorted_names); + results = []; + for (j = 0, len = ref.length; j < len; j++) { + ref1 = ref[j], z_score = ref1[0], value = ref1[1], sample = ref1[2]; + if (sample in samples[group_name]) { + results.push({ + x: z_score, + y: value, + name: sample + }); + } + } + return results; + })() + }; + }; + data = [make_data('samples_primary'), make_data('samples_other'), make_data('samples_all')]; + x_values = {} + y_values = {} + point_names = {} + for (i = 0; i < 3; i++){ + these_x_values = [] + these_y_values = [] + these_names = [] + for (j = 0; j < data[i].values.length; j++){ + these_x_values.push(data[i].values[j].x) + these_y_values.push(data[i].values[j].y) + these_names.push(data[i].values[j].name) + } + if (i == 0){ + x_values['samples_primary'] = these_x_values + y_values['samples_primary'] = these_y_values + point_names['samples_primary'] = these_names + } else if (i == 1) { + x_values['samples_other'] = these_x_values + y_values['samples_other'] = these_y_values + point_names['samples_other'] = these_names + } else { + x_values['samples_all'] = these_x_values + y_values['samples_all'] = these_y_values + point_names['samples_all'] = these_names + } + } + + intercept_line = {} + + if (sample_group == "samples_primary"){ + first_x = Math.floor(x_values['samples_primary'][0]) + first_x = first_x - first_x*0.1 + last_x = Math.ceil(x_values['samples_primary'][x_values['samples_primary'].length - 1]) + last_x = last_x + last_x*0.1 + first_value = data[0].intercept + data[0].slope * first_x + last_value = data[0].intercept + data[0].slope * last_x + intercept_line['samples_primary'] = [[first_x, last_x], [first_value, last_value]] + } else if (sample_group == "samples_other") { + first_x = Math.floor(x_values['samples_other'][0]) + first_x = first_x - first_x*0.1 + last_x = Math.ceil(x_values['samples_other'][x_values['samples_other'].length - 1]) + last_x = last_x + last_x*0.1 + first_value = data[1].intercept + data[1].slope * first_x + last_value = data[1].intercept + data[1].slope * last_x + intercept_line['samples_other'] = [[first_x, last_x], [first_value, last_value]] + } else { + first_x = Math.floor(x_values['samples_all'][0]) + first_x = first_x - first_x*0.1 + last_x = Math.ceil(x_values['samples_all'][x_values['samples_all'].length - 1]) + first_value = data[2].intercept + data[2].slope * first_x + last_x = last_x + last_x*0.1 + last_value = data[2].intercept + data[2].slope * last_x + intercept_line['samples_all'] = [[first_x, last_x], [first_value, last_value]] + } + + val_range = Math.max(...y_values['samples_all']) - Math.min(...y_values['samples_all']) + if (val_range < 4){ + tick_digits = '.1f' + } else if (val_range < 0.4) { + tick_digits = '.2f' + } else { + tick_digits = 'f' + } + + var layout = { + title: { + x: 0, + y: 10, + xanchor: 'left', + text: "<b>Trait " + js_data.trait_id + ": " + js_data.short_description + "</b>", + }, + margin: { + l: 100, + r: 30, + t: 100, + b: 60 + }, + legend: { + x: 0.05, + y: 0.9, + xanchor: 'left' + }, + xaxis: { + title: "<b>normal quantiles</b>", + range: [first_x, last_x], + zeroline: false, + visible: true, + linecolor: 'black', + linewidth: 1, + titlefont: { + family: "arial", + size: 16 + }, + ticklen: 4, + tickfont: { + size: 16 + } + }, + yaxis: { + zeroline: false, + visible: true, + linecolor: 'black', + linewidth: 1, + title: "<b>" + js_data.unit_type + "</b>", + titlefont: { + family: "arial", + size: 16 + }, + ticklen: 4, + tickfont: { + size: 16 + }, + tickformat: tick_digits, + automargin: true + }, + width: 600, + height: 600, + hovermode: "closest", + dragmode: false + } + + var primary_trace = { + x: x_values['samples_primary'], + y: y_values['samples_primary'], + mode: 'markers', + type: 'scatter', + name: 'Samples', + text: point_names['samples_primary'], + marker: { + color: 'blue', + width: 6 + } + } + if ("samples_other" in js_data.sample_group_types) { + var other_trace = { + x: x_values['samples_other'], + y: y_values['samples_other'], + mode: 'markers', + type: 'scatter', + name: js_data.sample_group_types['samples_other'], + text: point_names['samples_other'], + marker: { + color: 'blue', + width: 6 + } + } + } + + if (sample_group == "samples_primary"){ + var primary_intercept_trace = { + x: intercept_line['samples_primary'][0], + y: intercept_line['samples_primary'][1], + mode: 'lines', + type: 'scatter', + name: 'Normal Function', + line: { + color: 'black', + width: 1 + } + } + } else if (sample_group == "samples_other"){ + var other_intercept_trace = { + x: intercept_line['samples_other'][0], + y: intercept_line['samples_other'][1], + mode: 'lines', + type: 'scatter', + name: 'Normal Function', + line: { + color: 'black', + width: 1 + } + } + } else { + var all_intercept_trace = { + x: intercept_line['samples_all'][0], + y: intercept_line['samples_all'][1], + mode: 'lines', + type: 'scatter', + name: 'Normal Function', + line: { + color: 'black', + width: 1 + } + } + } + + if (sample_group == "samples_primary"){ + var data = [primary_intercept_trace, primary_trace] + } else if (sample_group == "samples_other"){ + var data = [other_intercept_trace, other_trace] + } else { + var data = [all_intercept_trace, primary_trace, other_trace] + } + + Plotly.newPlot('prob_plot_div', data, layout, root.modebar_options) + }; + + root.redraw_prob_plot_impl = redraw_prob_plot; + +}).call(this); diff --git a/gn2/wqflask/static/new/javascript/scatter-matrix.js b/gn2/wqflask/static/new/javascript/scatter-matrix.js new file mode 100644 index 00000000..31cb384b --- /dev/null +++ b/gn2/wqflask/static/new/javascript/scatter-matrix.js @@ -0,0 +1,551 @@ +// Heavily influenced by Mike Bostock's Scatter Matrix example +// http://mbostock.github.io/d3/talk/20111116/iris-splom.html +// + +/* +ScatterMatrix = function(url) { + this.__url = url; + this.__data = undefined; + this.__cell_size = 140; +}; +*/ + +ScatterMatrix = function(csv_string) { + this.__csv_string = csv_string; + this.__data = undefined; + this.__cell_size = 140; +}; + +ScatterMatrix.prototype.cellSize = function(n) { + this.__cell_size = n; + return this; +}; + +/* +ScatterMatrix.prototype.onData = function(cb) { + if (this.__data) { cb(); return; } + var self = this; + d3.csv(self.__url, function(data) { + self.__data = data; + cb(); + }); +}; +*/ + +ScatterMatrix.prototype.onData = function(cb) { + if (this.__data) { cb(); return; } + var self = this; + console.log("self.csv_string:", self.__csv_string) + + data = d3.csv.parse(self.__csv_string); + self.__data = data; + cb(); + +/* + d3.csv.parseRows(self.__csv_string, function(data) { + self.__data = data; + cb(); + }); +*/ + +}; + +ScatterMatrix.prototype.render = function () { + var self = this; + + var container = d3.select('#scatterplot_container').append('div') + .attr('class', 'scatter-matrix-container'); + var control = container.append('div') + .attr('class', 'scatter-matrix-control') + .style({'float':'left', 'margin-right':'50px'}) + var svg = container.append('div') + .attr('class', 'scatter-matrix-svg') + .style({'float':'left'}) + .html('<em>Loading data...</em>'); + + this.onData(function() { + var data = self.__data; + + // Fetch data and get all string variables + var string_variables = [undefined]; + var numeric_variables = []; + var numeric_variable_values = {}; + + for (k in data[0]) { + if (isNaN(+data[0][k])) { string_variables.push(k); } + else { numeric_variables.push(k); numeric_variable_values[k] = []; } + } + + console.log("data:", data) + + data.forEach(function(d) { + for (var j in numeric_variables) { + var k = numeric_variables[j]; + var value = d[k]; + if (numeric_variable_values[k].indexOf(value) < 0) { + numeric_variable_values[k].push(value); + } + } + }); + + var size_control = control.append('div').attr('class', 'scatter-matrix-size-control'); + var color_control = control.append('div').attr('class', 'scatter-matrix-color-control'); + var filter_control = control.append('div').attr('class', 'scatter-matrix-filter-control'); + var variable_control = control.append('div').attr('class', 'scatter-matrix-variable-control'); + var drill_control = control.append('div').attr('class', 'scatter-matrix-drill-control'); + + // shared control states + var to_include = []; + var color_variable = undefined; + var selected_colors = undefined; + for (var j in numeric_variables) { + var v = numeric_variables[j]; + to_include.push(v); + } + var drill_variables = []; + + function set_filter(variable) { + filter_control.selectAll('*').remove(); + if (variable) { + // Get unique values for this variable + var values = []; + data.forEach(function(d) { + var v = d[variable]; + if (values.indexOf(v) < 0) { values.push(v); } + }); + + selected_colors = []; + for (var j in values) { + var v = values[j]; + selected_colors.push(v); + } + + var filter_li = + filter_control + .append('p').text('Filter by '+variable+': ') + .append('ul') + .selectAll('li') + .data(values) + .enter().append('li'); + + filter_li.append('input') + .attr('type', 'checkbox') + .attr('checked', 'checked') + .on('click', function(d, i) { + var new_selected_colors = []; + for (var j in selected_colors) { + var v = selected_colors[j]; + if (v !== d || this.checked) { new_selected_colors.push(v); } + } + if (this.checked) { new_selected_colors.push(d); } + selected_colors = new_selected_colors; + self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); + }); + filter_li.append('label') + .html(function(d) { return d; }); + } + } + + size_a = size_control.append('p').text('Change cell size: '); + size_a.append('a') + .attr('href', '#') + .html('-') + .on('click', function() { + self.__cell_size *= 0.75; + self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); + }); + size_a.append('span').html(' '); + size_a.append('a') + .attr('href', '#') + .html('+') + .on('click', function() { + self.__cell_size *= 1.25; + self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); + }); + + color_control.append('p').text('Select a variable to color:') + color_control + .append('ul') + .selectAll('li') + .data(string_variables) + .enter().append('li') + .append('a') + .attr('href', '#') + .text(function(d) { return d ? d : 'None'; }) + .on('click', function(d, i) { + color_variable = d; + selected_colors = undefined; + self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); + set_filter(d); + }); + + var variable_li = + variable_control + .append('p').text('Include variables: ') + .append('ul') + .selectAll('li') + .data(numeric_variables) + .enter().append('li'); + + variable_li.append('input') + .attr('type', 'checkbox') + .attr('checked', 'checked') + .on('click', function(d, i) { + var new_to_include = []; + for (var j in to_include) { + var v = to_include[j]; + if (v !== d || this.checked) { new_to_include.push(v); } + } + if (this.checked) { new_to_include.push(d); } + to_include = new_to_include; + self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); + }); + variable_li.append('label') + .html(function(d) { return d; }); + + drill_li = + drill_control + .append('p').text('Drill and Expand: ') + .append('ul') + .selectAll('li') + .data(numeric_variables) + .enter().append('li'); + + drill_li.append('input') + .attr('type', 'checkbox') + .on('click', function(d, i) { + var new_drill_variables = []; + for (var j in drill_variables) { + var v = drill_variables[j]; + if (v !== d || this.checked) { new_drill_variables.push(v); } + } + if (this.checked) { new_drill_variables.push(d); } + drill_variables = new_drill_variables; + self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); + }); + drill_li.append('label') + .html(function(d) { return d+' ('+numeric_variable_values[d].length+')'; }); + + self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); + }); +}; + +ScatterMatrix.prototype.__draw = + function(cell_size, container_el, color_variable, selected_colors, to_include, drill_variables) { + var self = this; + this.onData(function() { + var data = self.__data; + + if (color_variable && selected_colors) { + data = []; + self.__data.forEach(function(d) { + if (selected_colors.indexOf(d[color_variable]) >= 0) { data.push(d); } + }); + } + + container_el.selectAll('*').remove(); + + // If no data, don't do anything + if (data.length == 0) { return; } + + // Parse headers from first row of data + var numeric_variables = []; + for (k in data[0]) { + if (!isNaN(+data[0][k]) && to_include.indexOf(k) >= 0) { numeric_variables.push(k); } + } + numeric_variables.sort(); + + // Get values of the string variable + var colors = []; + if (color_variable) { + // Using self.__data, instead of data, so our css classes are consistent when + // we filter by value. + self.__data.forEach(function(d) { + var s = d[color_variable]; + if (colors.indexOf(s) < 0) { colors.push(s); } + }); + } + + function color_class(d) { + var c = d; + if (color_variable && d[color_variable]) { c = d[color_variable]; } + return colors.length > 0 ? 'color-'+colors.indexOf(c) : 'color-2'; + } + + // Size parameters + var size = cell_size, padding = 10, + axis_width = 20, axis_height = 15, legend_width = 200, label_height = 15; + + // Get x and y scales for each numeric variable + var x = {}, y = {}; + numeric_variables.forEach(function(trait) { + // Coerce values to numbers. + data.forEach(function(d) { d[trait] = +d[trait]; }); + + var value = function(d) { return d[trait]; }, + domain = [d3.min(data, value), d3.max(data, value)], + range_x = [padding / 2, size - padding / 2], + range_y = [padding / 2, size - padding / 2]; + + x[trait] = d3.scale.linear().domain(domain).range(range_x); + y[trait] = d3.scale.linear().domain(domain).range(range_y.reverse()); + }); + + // When drilling, user select one or more variables. The first drilled + // variable becomes the x-axis variable for all columns, and each column + // contains only data points that match specific values for each of the + // drilled variables other than the first. + + var drill_values = []; + var drill_degrees = [] + drill_variables.forEach(function(variable) { + // Skip first one, since that's just the x axis + if (drill_values.length == 0) { + drill_values.push([]); + drill_degrees.push(1); + } + else { + var values = []; + data.forEach(function(d) { + var v = d[variable]; + if (v !== undefined && values.indexOf(v) < 0) { values.push(v); } + }); + values.sort(); + drill_values.push(values); + drill_degrees.push(values.length); + } + }); + var total_columns = 1; + drill_degrees.forEach(function(d) { total_columns *= d; }); + + // Pick out stuff to draw on horizontal and vertical dimensions + + if (drill_variables.length > 0) { + // First drill is now the x-axis variable for all columns + x_variables = []; + for (var i=0; i<total_columns; i++) { + x_variables.push(drill_variables[0]); + } + } + else { + x_variables = numeric_variables.slice(0); + } + + if (drill_variables.length > 0) { + // Don't draw any of the "drilled" variables in vertical dimension + y_variables = []; + numeric_variables.forEach(function(variable) { + if (drill_variables.indexOf(variable) < 0) { y_variables.push(variable); } + }); + } + else { + y_variables = numeric_variables.slice(0); + } + + var filter_descriptions = 0; + if (drill_variables.length > 1) { + filter_descriptions = drill_variables.length-1; + } + + // Axes + var x_axis = d3.svg.axis(); + var y_axis = d3.svg.axis(); + var intf = d3.format('d'); + var fltf = d3.format('.f'); + var scif = d3.format('e'); + + x_axis.ticks(5) + .tickSize(size * y_variables.length) + .tickFormat(function(d) { + if (Math.abs(+d) > 10000 || (Math.abs(d) < 0.001 && Math.abs(d) != 0)) { return scif(d); } + if (parseInt(d) == +d) { return intf(d); } + return fltf(d); + }); + + y_axis.ticks(5) + .tickSize(size * x_variables.length) + .tickFormat(function(d) { + if (Math.abs(+d) > 10000 || (Math.abs(d) < 0.001 && Math.abs(d) != 0)) { return scif(d); } + if (parseInt(d) == +d) { return intf(d); } + return fltf(d); + }); + + // Brush - for highlighting regions of data + var brush = d3.svg.brush() + .on("brushstart", brushstart) + .on("brush", brush) + .on("brushend", brushend); + + // Root panel + var svg = container_el.append("svg:svg") + .attr("width", label_height + size * x_variables.length + axis_width + padding + legend_width) + .attr("height", size * y_variables.length + axis_height + label_height + label_height*filter_descriptions) + .append("svg:g") + .attr("transform", "translate("+label_height+",0)"); + + // Push legend to the side + var legend = svg.selectAll("g.legend") + .data(colors) + .enter().append("svg:g") + .attr("class", "legend") + .attr("transform", function(d, i) { + return "translate(" + (label_height + size * x_variables.length + padding) + "," + (i*20+10) + ")"; + }); + + legend.append("svg:circle") + .attr("class", function(d, i) { return color_class(d); }) + .attr("r", 3); + + legend.append("svg:text") + .attr("x", 12) + .attr("dy", ".31em") + .text(function(d) { return d; }); + + // Draw X-axis + svg.selectAll("g.x.axis") + .data(x_variables) + .enter().append("svg:g") + .attr("class", "x axis") + .attr("transform", function(d, i) { return "translate(" + i * size + ",0)"; }) + .each(function(d) { d3.select(this).call(x_axis.scale(x[d]).orient("bottom")); }); + + // Draw Y-axis + svg.selectAll("g.y.axis") + .data(y_variables) + .enter().append("svg:g") + .attr("class", "y axis") + .attr("transform", function(d, i) { return "translate(0," + i * size + ")"; }) + .each(function(d) { d3.select(this).call(y_axis.scale(y[d]).orient("right")); }); + + // Draw scatter plot + var cell = svg.selectAll("g.cell") + .data(cross(x_variables, y_variables)) + .enter().append("svg:g") + .attr("class", "cell") + .attr("transform", function(d) { return "translate(" + d.i * size + "," + d.j * size + ")"; }) + .each(plot); + + // Add titles for y variables + cell.filter(function(d) { return d.i == 0; }).append("svg:text") + .attr("x", padding-size) + .attr("y", -label_height) + .attr("dy", ".71em") + .attr("transform", function(d) { return "rotate(-90)"; }) + .text(function(d) { return d.y; }); + + function plot(p) { + // console.log(p); + + var data_to_draw = data; + + // If drilling, compute what values of the drill variables correspond to + // this column. + // + var filter = {}; + if (drill_variables.length > 1) { + var column = p.i; + + var cap = 1; + for (var i=drill_variables.length-1; i > 0; i--) { + var var_name = drill_variables[i]; + var var_value = undefined; + + if (i == drill_variables.length-1) { + // for the last drill variable, we index by % + var_value = drill_values[i][column % drill_degrees[i]]; + } + else { + // otherwise divide by capacity of subsequent variables to get value array index + var_value = drill_values[i][parseInt(column/cap)]; + } + + filter[var_name] = var_value; + cap *= drill_degrees[i]; + } + + data_to_draw = []; + data.forEach(function(d) { + var pass = true; + for (k in filter) { if (d[k] != filter[k]) { pass = false; break; } } + if (pass === true) { data_to_draw.push(d); } + }); + } + + var cell = d3.select(this); + + // Frame + cell.append("svg:rect") + .attr("class", "frame") + .attr("x", padding / 2) + .attr("y", padding / 2) + .attr("width", size - padding) + .attr("height", size - padding); + + // Scatter plot dots + cell.selectAll("circle") + .data(data_to_draw) + .enter().append("svg:circle") + .attr("class", function(d) { return color_class(d); }) + .attr("cx", function(d) { return x[p.x](d[p.x]); }) + .attr("cy", function(d) { return y[p.y](d[p.y]); }) + .attr("r", 5); + + // Add titles for x variables and drill variable values + if (p.j == y_variables.length-1) { + cell.append("svg:text") + .attr("x", padding) + .attr("y", size+axis_height) + .attr("dy", ".71em") + .text(function(d) { return d.x; }); + + if (drill_variables.length > 1) { + var i = 0; + for (k in filter) { + i += 1; + cell.append("svg:text") + .attr("x", padding) + .attr("y", size+axis_height+label_height*i) + .attr("dy", ".71em") + .text(function(d) { return filter[k]+': '+k; }); + } + } + } + + // Brush + cell.call(brush.x(x[p.x]).y(y[p.y])); + } + + // Clear the previously-active brush, if any + function brushstart(p) { + if (brush.data !== p) { + cell.call(brush.clear()); + brush.x(x[p.x]).y(y[p.y]).data = p; + } + } + + // Highlight selected circles + function brush(p) { + var e = brush.extent(); + svg.selectAll(".cell circle").attr("class", function(d) { + return e[0][0] <= d[p.x] && d[p.x] <= e[1][0] + && e[0][1] <= d[p.y] && d[p.y] <= e[1][1] + ? color_class(d) : null; + }); + } + + // If brush is empty, select all circles + function brushend() { + if (brush.empty()) svg.selectAll(".cell circle").attr("class", function(d) { + return color_class(d); + }); + } + + function cross(a, b) { + var c = [], n = a.length, m = b.length, i, j; + for (i = -1; ++i < n;) for (j = -1; ++j < m;) c.push({x: a[i], i: i, y: b[j], j: j}); + return c; + } + }); + +}; + diff --git a/gn2/wqflask/static/new/javascript/scatterplot.js b/gn2/wqflask/static/new/javascript/scatterplot.js new file mode 100644 index 00000000..3fea0503 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/scatterplot.js @@ -0,0 +1,414 @@ +// Generated by CoffeeScript 1.8.0 +var root, scatterplot; + +root = typeof exports !== "undefined" && exports !== null ? exports : this; + +scatterplot = function() { + var axispos, chart, dataByInd, height, margin, nxticks, nyticks, pointcolor, pointsSelect, pointsize, pointstroke, rectcolor, rotate_ylab, title, titlepos, width, xNA, xlab, xlim, xscale, xticks, xvar, yNA, ylab, ylim, yscale, yticks, yvar; + width = 800; + height = 600; + margin = { + left: 60, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + axispos = { + xtitle: 25, + ytitle: 45, + xlabel: 5, + ylabel: 5 + }; + titlepos = 20; + xNA = { + handle: true, + force: false, + width: 15, + gap: 10 + }; + yNA = { + handle: true, + force: false, + width: 15, + gap: 10 + }; + xlim = null; + ylim = null; + nxticks = 5; + xticks = null; + nyticks = 5; + yticks = null; + rectcolor = d3.rgb(230, 230, 230); + pointcolor = null; + pointstroke = "black"; + pointsize = 3; + title = "Correlation Scatterplot"; + xlab = "X"; + ylab = "Y"; + rotate_ylab = null; + yscale = d3.scale.linear(); + xscale = d3.scale.linear(); + xvar = 0; + yvar = 1; + pointsSelect = null; + dataByInd = false; + chart = function(selection) { + return selection.each(function(data) { + var g, gEnter, group, i, indID, indtip, maxx, minx, na_value, ngroup, panelheight, paneloffset, panelwidth, points, svg, titlegrp, x, xaxis, xrange, xs, y, yaxis, yrange, ys, _i, _ref, _ref1, _ref2, _results; + if (dataByInd) { + x = data.data.map(function(d) { + return d[xvar]; + }); + y = data.data.map(function(d) { + return d[yvar]; + }); + } else { + x = data.data[xvar]; + y = data.data[yvar]; + } + + indID = (_ref = data != null ? data.indID : void 0) != null ? _ref : null; + indID = indID != null ? indID : (function() { + _results = []; + for (var _i = 1, _ref1 = x.length; 1 <= _ref1 ? _i <= _ref1 : _i >= _ref1; 1 <= _ref1 ? _i++ : _i--){ _results.push(_i); } + return _results; + }).apply(this); + + group = (_ref2 = data != null ? data.group : void 0) != null ? _ref2 : (function() { + var _j, _len, _results1; + _results1 = []; + for (_j = 0, _len = x.length; _j < _len; _j++) { + i = x[_j]; + _results1.push(1); + } + return _results1; + })(); + ngroup = d3.max(group); + group = (function() { + var _j, _len, _results1; + _results1 = []; + for (_j = 0, _len = group.length; _j < _len; _j++) { + g = group[_j]; + _results1.push(g - 1); + } + return _results1; + })(); + pointcolor = pointcolor != null ? pointcolor : selectGroupColors(ngroup, "dark"); + pointcolor = expand2vector(pointcolor, ngroup); + if (x.every(function(v) { + return (v != null) && !xNA.force; + })) { + xNA.handle = false; + } + if (y.every(function(v) { + return (v != null) && !yNA.force; + })) { + yNA.handle = false; + } + if (xNA.handle) { + paneloffset = xNA.width + xNA.gap; + panelwidth = width - paneloffset; + } else { + paneloffset = 0; + panelwidth = width; + } + if (yNA.handle) { + panelheight = height - (yNA.width + yNA.gap); + } else { + panelheight = height; + } + xlim = xlim != null ? xlim : d3.extent(x); + ylim = ylim != null ? ylim : d3.extent(y); + na_value = d3.min(x.concat(y)) - 100; + svg = d3.select(this).selectAll("svg").data([data]); + gEnter = svg.enter().append("svg").append("g"); + svg.attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom); + g = svg.select("g"); + g.append("rect").attr("x", paneloffset + margin.left).attr("y", margin.top).attr("height", panelheight).attr("width", panelwidth).attr("fill", rectcolor).attr("stroke", "none"); + if (xNA.handle) { + g.append("rect").attr("x", margin.left).attr("y", margin.top).attr("height", panelheight).attr("width", xNA.width).attr("fill", rectcolor).attr("stroke", "none"); + } + if (xNA.handle && yNA.handle) { + g.append("rect").attr("x", margin.left).attr("y", margin.top + height - yNA.width).attr("height", yNA.width).attr("width", xNA.width).attr("fill", rectcolor).attr("stroke", "none"); + } + if (yNA.handle) { + g.append("rect").attr("x", margin.left + paneloffset).attr("y", margin.top + height - yNA.width).attr("height", yNA.width).attr("width", panelwidth).attr("fill", rectcolor).attr("stroke", "none"); + } + xrange = [margin.left + paneloffset + margin.inner, margin.left + paneloffset + panelwidth - margin.inner]; + yrange = [margin.top + panelheight - margin.inner, margin.top + margin.inner]; + xscale.domain(xlim).range(xrange); + yscale.domain(ylim).range(yrange); + xs = d3.scale.linear().domain(xlim).range(xrange); + ys = d3.scale.linear().domain(ylim).range(yrange); + if (xNA.handle) { + xscale.domain([na_value].concat(xlim)).range([margin.left + xNA.width / 2].concat(xrange)); + x = x.map(function(e) { + if (e != null) { + return e; + } else { + return na_value; + } + }); + } + if (yNA.handle) { + yscale.domain([na_value].concat(ylim)).range([height + margin.top - yNA.width / 2].concat(yrange)); + y = y.map(function(e) { + if (e != null) { + return e; + } else { + return na_value; + } + }); + } + minx = xlim[0]; + maxx = xlim[1]; + yticks = yticks != null ? yticks : ys.ticks(nyticks); + xticks = xticks != null ? xticks : xs.ticks(nxticks); + titlegrp = g.append("g").attr("class", "title").append("text").attr("x", margin.left + width / 2).attr("y", margin.top - titlepos).style("fill", "black").style("font-size", "24px").text(title); + xaxis = g.append("g").attr("class", "x axis"); + xaxis.selectAll("empty").data(xticks).enter().append("line").attr("x1", function(d) { + return xscale(d); + }).attr("x2", function(d) { + return xscale(d); + }).attr("y1", margin.top).attr("y2", margin.top + height).attr("fill", "none").attr("stroke", "white").attr("stroke-width", 1).style("pointer-events", "none"); + xaxis.selectAll("empty").data(xticks).enter().append("text").attr("x", function(d) { + return xscale(d); + }).attr("y", margin.top + height + axispos.xlabel).text(function(d) { + return formatAxis(xticks)(d); + }); + xaxis.append("text").attr("class", "title").attr("x", margin.left + width / 2).attr("y", margin.top + height + axispos.xtitle).style("fill", "black").text(xlab); + if (xNA.handle) { + xaxis.append("text").attr("x", margin.left + xNA.width / 2).attr("y", margin.top + height + axispos.xlabel).text("N/A"); + } + rotate_ylab = rotate_ylab != null ? rotate_ylab : ylab.length > 1; + yaxis = g.append("g").attr("class", "y axis"); + yaxis.selectAll("empty").data(yticks).enter().append("line").attr("y1", function(d) { + return yscale(d); + }).attr("y2", function(d) { + return yscale(d); + }).attr("x1", margin.left).attr("x2", margin.left + width).attr("fill", "none").attr("stroke", "white").attr("stroke-width", 1).style("pointer-events", "none"); + yaxis.selectAll("empty").data(yticks).enter().append("text").attr("y", function(d) { + return yscale(d); + }).attr("x", margin.left - axispos.ylabel).text(function(d) { + return formatAxis(yticks)(d); + }); + yaxis.append("text").attr("class", "title").attr("y", margin.top + height / 2).attr("x", margin.left - axispos.ytitle).style("fill", "black").text(ylab).attr("transform", rotate_ylab ? "rotate(270," + (margin.left - axispos.ytitle) + "," + (margin.top + height / 2) + ")" : ""); + if (yNA.handle) { + yaxis.append("text").attr("x", margin.left - axispos.ylabel).attr("y", margin.top + height - yNA.width / 2).text("N/A"); + } + indtip = d3.tip().attr('class', 'd3-tip').html(function(d, i) { + return indID[i]; + }).direction('e').offset([0, 10]); + svg.call(indtip); + if (js_data.slope && js_data.intercept) { + g.append("line").attr("x1", xscale(minx) - margin.inner).attr('y1', yscale(js_data.slope * minx + js_data.intercept)).attr("x2", xscale(maxx * 1) + margin.inner).attr("y2", yscale(slope * maxx * 1 + intercept)).style("stroke", "black").style("stroke-width", 2); + } + points = g.append("g").attr("id", "points"); + pointsSelect = points.selectAll("empty").data(d3.range(x.length)).enter().append("circle").attr("cx", function(d, i) { + return xscale(x[i]); + }).attr("cy", function(d, i) { + return yscale(y[i]); + }).attr("class", function(d, i) { + return "pt" + i; + }).attr("r", pointsize).attr("fill", function(d, i) { + return pointcolor[group[i]]; + }).attr("stroke", pointstroke).attr("stroke-width", "1").attr("opacity", function(d, i) { + if (((x[i] != null) || xNA.handle) && ((y[i] != null) || yNA.handle)) { + return 1; + } + return 0; + }).on("mouseover.paneltip", indtip.show).on("mouseout.paneltip", indtip.hide); + g.append("rect").attr("x", margin.left + paneloffset).attr("y", margin.top).attr("height", panelheight).attr("width", panelwidth).attr("fill", "none").attr("stroke", "black").attr("stroke-width", "none"); + if (xNA.handle) { + g.append("rect").attr("x", margin.left).attr("y", margin.top).attr("height", panelheight).attr("width", xNA.width).attr("fill", "none").attr("stroke", "black").attr("stroke-width", "none"); + } + if (xNA.handle && yNA.handle) { + g.append("rect").attr("x", margin.left).attr("y", margin.top + height - yNA.width).attr("height", yNA.width).attr("width", xNA.width).attr("fill", "none").attr("stroke", "black").attr("stroke-width", "none"); + } + if (yNA.handle) { + return g.append("rect").attr("x", margin.left + paneloffset).attr("y", margin.top + height - yNA.width).attr("height", yNA.width).attr("width", panelwidth).attr("fill", "none").attr("stroke", "black").attr("stroke-width", "none"); + } + }); + }; + chart.width = function(value) { + if (!arguments.length) { + return width; + } + width = value; + return chart; + }; + chart.height = function(value) { + if (!arguments.length) { + return height; + } + height = value; + return chart; + }; + chart.margin = function(value) { + if (!arguments.length) { + return margin; + } + margin = value; + return chart; + }; + chart.axispos = function(value) { + if (!arguments.length) { + return axispos; + } + axispos = value; + return chart; + }; + chart.titlepos = function(value) { + if (!arguments.length) { + return titlepos; + } + titlepos; + return chart; + }; + chart.xlim = function(value) { + if (!arguments.length) { + return xlim; + } + xlim = value; + return chart; + }; + chart.nxticks = function(value) { + if (!arguments.length) { + return nxticks; + } + nxticks = value; + return chart; + }; + chart.xticks = function(value) { + if (!arguments.length) { + return xticks; + } + xticks = value; + return chart; + }; + chart.ylim = function(value) { + if (!arguments.length) { + return ylim; + } + ylim = value; + return chart; + }; + chart.nyticks = function(value) { + if (!arguments.length) { + return nyticks; + } + nyticks = value; + return chart; + }; + chart.yticks = function(value) { + if (!arguments.length) { + return yticks; + } + yticks = value; + return chart; + }; + chart.rectcolor = function(value) { + if (!arguments.length) { + return rectcolor; + } + rectcolor = value; + return chart; + }; + chart.pointcolor = function(value) { + if (!arguments.length) { + return pointcolor; + } + pointcolor = value; + return chart; + }; + chart.pointsize = function(value) { + if (!arguments.length) { + return pointsize; + } + pointsize = value; + return chart; + }; + chart.pointstroke = function(value) { + if (!arguments.length) { + return pointstroke; + } + pointstroke = value; + return chart; + }; + chart.dataByInd = function(value) { + if (!arguments.length) { + return dataByInd; + } + dataByInd = value; + return chart; + }; + chart.title = function(value) { + if (!arguments.length) { + return title; + } + title = value; + return chart; + }; + chart.xlab = function(value) { + if (!arguments.length) { + return xlab; + } + xlab = value; + return chart; + }; + chart.ylab = function(value) { + if (!arguments.length) { + return ylab; + } + ylab = value; + return chart; + }; + chart.rotate_ylab = function(value) { + if (!arguments.length) { + return rotate_ylab; + } + rotate_ylab = value; + return chart; + }; + chart.xvar = function(value) { + if (!arguments.length) { + return xvar; + } + xvar = value; + return chart; + }; + chart.yvar = function(value) { + if (!arguments.length) { + return yvar; + } + yvar = value; + return chart; + }; + chart.xNA = function(value) { + if (!arguments.length) { + return xNA; + } + xNA = value; + return chart; + }; + chart.yNA = function(value) { + if (!arguments.length) { + return yNA; + } + yNA = value; + return chart; + }; + chart.yscale = function() { + return yscale; + }; + chart.xscale = function() { + return xscale; + }; + chart.pointsSelect = function() { + return pointsSelect; + }; + return chart; +}; + +root.scatterplot = scatterplot; diff --git a/gn2/wqflask/static/new/javascript/search_autocomplete.js b/gn2/wqflask/static/new/javascript/search_autocomplete.js new file mode 100644 index 00000000..10c22c95 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/search_autocomplete.js @@ -0,0 +1,173 @@ + //replace with gn search hints + + + + + + function autocomplete(inp, arr) { + /*the autocomplete function takes two arguments, + the text field element and an array of possible autocompleted values:*/ + var currentFocus; + /*execute a function when someone writes in the text field:*/ + + inp.addEventListener("focus", function(e) { + + + var a, b, i, val = this.value; + closeAllLists(); + currentFocus = -1; + a = document.createElement("DIV"); + a.setAttribute("id", this.id + "autocomplete-list"); + a.setAttribute("class", "autocomplete-items"); + this.parentNode.appendChild(a); + let historySearch = retrieveSearchHistory().splice(0, 5) + let text_title = document.createElement("DIV") + text_title.innerHTML = "<strong>RECENT SEARCH</strong><input id='clear_all' type='button' value='clear_all'>"; + text_title.setAttribute("class", "recent-search-title") + text_title.setAttribute('disabled', true); + + a.appendChild(text_title); + for (i = 0; i < historySearch.length; i++) { + b = document.createElement("DIV"); + b.innerHTML = "<span>" + historySearch[i].substr(0, val.length) + "</span>"; + b.style.color = "blue"; + b.innerHTML += historySearch[i].substr(val.length); + b.innerHTML += "<input type='hidden' value='" + historySearch[i] + "'>"; + b.addEventListener("click", function(e) { + inp.value = this.getElementsByTagName("input")[0].value; + closeAllLists(); + }); + a.appendChild(b); + + } + document.getElementById("clear_all").addEventListener("click",(event)=>{ + + + deleteSearchHistory() + }) + + }); + + + + inp.addEventListener("input", function(e) { + var a, b, i, val = this.value; + /*close any already open lists of autocompleted values*/ + closeAllLists(); + if (!val) { return false; } + currentFocus = -1; + /*create a DIV element that will contain the items (values):*/ + a = document.createElement("DIV"); + a.setAttribute("id", this.id + "autocomplete-list"); + a.setAttribute("class", "autocomplete-items"); + /*append the DIV element as a child of the autocomplete container:*/ + this.parentNode.appendChild(a); + /*for each item in the array...*/ + for (i = 0; i < arr.length; i++) { + /*check if the item starts with the same letters as the text field value:*/ + if (arr[i].substr(0, val.length).toUpperCase() == val.toUpperCase()) { + /*create a DIV element for each matching element:*/ + b = document.createElement("DIV"); + /*make the matching letters bold:*/ + b.innerHTML = "<strong>" + arr[i].substr(0, val.length) + "</strong>"; + b.innerHTML += arr[i].substr(val.length); + /*insert a input field that will hold the current array item's value:*/ + b.innerHTML += "<input type='hidden' value='" + arr[i] + "'>"; + /*execute a function when someone clicks on the item value (DIV element):*/ + b.addEventListener("click", function(e) { + /*insert the value for the autocomplete text field:*/ + inp.value = this.getElementsByTagName("input")[0].value; + /*close the list of autocompleted values, + (or any other open lists of autocompleted values:*/ + closeAllLists(); + }); + a.appendChild(b); + } + } + }); + /*execute a function presses a key on the keyboard:*/ + inp.addEventListener("keydown", function(e) { + var x = document.getElementById(this.id + "autocomplete-list"); + if (x) x = x.getElementsByTagName("div"); + if (e.keyCode == 40) { + /*If the arrow DOWN key is pressed, + increase the currentFocus variable:*/ + currentFocus++; + /*and and make the current item more visible:*/ + addActive(x); + } else if (e.keyCode == 38) { //up + /*If the arrow UP key is pressed, + decrease the currentFocus variable:*/ + currentFocus--; + /*and and make the current item more visible:*/ + addActive(x); + } else if (e.keyCode == 13) { + /*If the ENTER key is pressed, prevent the form from being submitted,*/ + e.preventDefault(); + if (currentFocus > -1) { + /*and simulate a click on the "active" item:*/ + if (x) x[currentFocus].click(); + } + } + }); + + function addActive(x) { + /*a function to classify an item as "active":*/ + if (!x) return false; + /*start by removing the "active" class on all items:*/ + removeActive(x); + if (currentFocus >= x.length) currentFocus = 0; + if (currentFocus < 0) currentFocus = (x.length - 1); + /*add class "autocomplete-active":*/ + x[currentFocus].classList.add("autocomplete-active"); + } + + function removeActive(x) { + /*a function to remove the "active" class from all autocomplete items:*/ + for (var i = 0; i < x.length; i++) { + x[i].classList.remove("autocomplete-active"); + } + } + + function closeAllLists(elmnt) { + /*close all autocomplete lists in the document, + except the one passed as an argument:*/ + var x = document.getElementsByClassName("autocomplete-items"); + for (var i = 0; i < x.length; i++) { + if (elmnt != x[i] && elmnt != inp) { + x[i].parentNode.removeChild(x[i]); + } + } + } + + + + /*execute a function when someone clicks in the document:*/ + document.addEventListener("click", function(e) { + closeAllLists(e.target); + }) + + } + + function retrieveSearchHistory() { + let results = localStorage.getItem("gn_search_history") + + return results ? JSON.parse(results) : [] + } + + + + + + function saveBeforeSubmit(new_search) { + + let search = retrieveSearchHistory() + search.unshift(new_search) + + localStorage.setItem("gn_search_history", JSON.stringify([...new Set(search)].splice(0,8))) + + } + + function deleteSearchHistory(){ + localStorage.setItem("gn_search_history", []) + }
\ No newline at end of file diff --git a/gn2/wqflask/static/new/javascript/search_results.js b/gn2/wqflask/static/new/javascript/search_results.js new file mode 100644 index 00000000..c263ef49 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/search_results.js @@ -0,0 +1,386 @@ +change_buttons = function(check_node = 0) { + 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[check_node].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() { + let selectAll, deselectAll, invert; + + selectAll = function() { + 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(); + }; + + deselectAll = function() { + 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() { + 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(){ + if ($(this).val() != ""){ + $('#filter_term').val($(this).val()); + } else { + $('#filter_term').val("None"); + } + $('#trait_table').DataTable().search($(this).val()).draw(); + }); + + /** + * parseIndexString takes a string consisting of integers, + * hyphens, and/or commas to indicate range(s) of indices + * to select a rows and returns the corresponding set of indices + * For example - "1, 5-10, 15" would return a set of 8 rows + * @return {Set} The list of indices as a Set + */ + parseIndexString = function(idx_string) { + index_list = []; + + _ref = idx_string.split(","); + for (_i = 0; _i < _ref.length; _i++) { + index_set = _ref[_i]; + if (!/^ *([0-9]+$) *| *([0-9]+ *- *[0-9]+$) *|(^$)$/.test(index_set)) { + $('#select_samples_invalid').show(); + break + } else { + $('#select_samples_invalid').hide(); + } + if (index_set.indexOf('-') !== -1) { + start_index = parseInt(index_set.split("-")[0]); + end_index = parseInt(index_set.split("-")[1]); + + // If start index is higher than end index (for example is the string "10-5" exists) swap values so it'll be interpreted as "5-10" instead + if (start_index > end_index) { + [start_index, end_index] = [end_index, start_index] + } + + for (index = start_index; index <= end_index; index++) { + index_list.push(index); + } + } else { + index = parseInt(index_set); + index_list.push(index); + } + } + return new Set(index_list) + } + + filterByIndex = function() { + indexString = $('#select_top').val() + indexSet = parseIndexString(indexString) + + tableApi = $('#trait_table').DataTable(); + checkNodes = tableApi.column(0).nodes().to$(); + checkNodes.each(function(index) { + if (indexSet.has(index + 1)){ + $(this)[0].childNodes[0].checked = true + } + }) + + checkRows = tableApi.rows().nodes().to$(); + checkRows.each(function(index) { + if (indexSet.has(index + 1)){ + $(this)[0].classList.add("selected"); + } + }) + } + + $(window).keydown(function(event){ + if((event.keyCode == 13)) { + event.preventDefault(); + return false; + } + }); + + $('#select_top').keyup(function(event){ + if (event.keyCode === 13) { + filterByIndex() + } + }); + + $('#select_top').blur(function() { + filterByIndex() + }); + + addToCollection = function() { + var traits; + table_api = $('#trait_table').DataTable(); + check_nodes = table_api.column(0).nodes().to$(); + traits = Array.from(check_nodes.map(function() { + if ($(this)[0].childNodes[0].checked){ + return $(this)[0].childNodes[0].value + } + })) + + var traits_hash = md5(traits.toString()); + + $.ajax({ + type: "POST", + url: "/collections/store_trait_list", + data: { + hash: traits_hash, + traits: traits.toString() + } + }); + + return $.colorbox({ + href: "/collections/add", + data: { + "traits": traits.toString(), + "hash": traits_hash + } + }); + + }; + + submitBnw = function() { + trait_data = submitTraits("trait_table", "submit_bnw") + } + + exportTraits = function() { + trait_data = submitTraits("trait_table", "export_traits_csv") + }; + + exportCollection = function() { + trait_data = submitTraits("trait_table", "export_collection") + } + + fetchTraits = function(table_name){ + trait_table = $('#'+table_name); + table_dict = {}; + + headers = []; + trait_table.find('th').each(function () { + if ($(this).data('export')){ + headers.push($(this).data('export')) + } + }); + table_dict['headers'] = headers; + + selected_rows = []; + all_rows = []; // 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) + } + } + + 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); + } + + submitTraits = function(table_name, destination) { + fetchTraits(table_name); + $('#export_form').attr('action', '/' + destination); + $('#export_form').submit(); + }; + + getTraitsFromTable = function(){ + traits = $("#trait_table input:checked").map(function() { + return $(this).val(); + }).get(); + if (traits.length == 0){ + num_traits = $("#trait_table input").length + if (num_traits <= 100){ + traits = $("#trait_table input").map(function() { + return $(this).val(); + }).get(); + } + } + return traits + } + + $("#corr_matrix").on("click", function() { + traits = getTraitsFromTable() + $("#trait_list").val(traits) + $("input[name=tool_used]").val("Correlation Matrix") + $("input[name=form_url]").val($(this).data("url")) + return submit_special("/loading") + }); + $("#network_graph").on("click", function() { + traits = getTraitsFromTable() + $("#trait_list").val(traits) + $("input[name=tool_used]").val("Network Graph") + $("input[name=form_url]").val($(this).data("url")) + return submit_special("/loading") + }); + $("#wgcna_setup").on("click", function() { + traits = getTraitsFromTable() + $("#trait_list").val(traits) + $("input[name=tool_used]").val("WGCNA Setup") + $("input[name=form_url]").val($(this).data("url")) + return submit_special("/loading") + }); + $("#ctl_setup").on("click", function() { + traits = getTraitsFromTable() + $("#trait_list").val(traits) + $("input[name=tool_used]").val("CTL Setup") + $("input[name=form_url]").val($(this).data("url")) + return submit_special("/loading") + }); + $("#heatmap").on("click", function() { + traits = getTraitsFromTable() + $("#trait_list").val(traits) + $("input[name=tool_used]").val("Heatmap") + $("input[name=form_url]").val($(this).data("url")) + return submit_special("/loading") + }); + $("#comp_bar_chart").on("click", function() { + traits = getTraitsFromTable() + $("#trait_list").val(traits) + $("input[name=tool_used]").val("Comparison Bar Chart") + $("input[name=form_url]").val($(this).data("url")) + return submit_special("/loading") + }); + + $("#send_to_webgestalt, #send_to_bnw, #send_to_geneweaver").on("click", function() { + traits = getTraitsFromTable() + $("#trait_list").val(traits) + url = $(this).data("url") + return submit_special(url) + }); + + + $("#select_all").click(selectAll); + $("#deselect_all").click(deselectAll); + $("#invert").click(invert); + $("#add").click(addToCollection); + $("#submit_bnw").click(submitBnw); + $("#export_traits").click(exportTraits); + $("#export_collection").click(exportCollection); + + let naturalAsc = $.fn.dataTableExt.oSort["natural-ci-asc"] + let naturalDesc = $.fn.dataTableExt.oSort["natural-ci-desc"] + + let na_equivalent_vals = ["N/A", "--", ""]; //ZS: Since there are multiple values that should be treated the same as N/A + + function extractInnerText(the_string){ + var span = document.createElement('span'); + span.innerHTML = the_string; + return span.textContent || span.innerText; + } + + function sortNAs(a, b, sort_function){ + if ( na_equivalent_vals.includes(a) && na_equivalent_vals.includes(b)) { + return 0; + } + if (na_equivalent_vals.includes(a)){ + return 1 + } + if (na_equivalent_vals.includes(b)) { + return -1; + } + return sort_function(a, b) + } + + $.extend( $.fn.dataTableExt.oSort, { + "natural-minus-na-asc": function (a, b) { + return sortNAs(extractInnerText(a), extractInnerText(b), naturalAsc) + }, + "natural-minus-na-desc": function (a, b) { + return sortNAs(extractInnerText(a), extractInnerText(b), naturalDesc) + } + }); + + $.fn.dataTable.ext.order['dom-checkbox'] = function ( settings, col ) + { + return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { + return $('input', td).prop('checked') ? '1' : '0'; + } ); + }; + + $.fn.dataTable.ext.order['dom-inner-text'] = function ( settings, col ) + { + return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { + return $(td).text(); + } ); + } + + applyDefault = function() { + let default_collection_id = $.cookie('default_collection'); + if (default_collection_id) { + let the_option = $('[name=existing_collection] option').filter(function() { + return ($(this).text().split(":")[0] == default_collection_id); + }) + the_option.prop('selected', true); + } + } + applyDefault(); + +}); diff --git a/gn2/wqflask/static/new/javascript/show_trait.js b/gn2/wqflask/static/new/javascript/show_trait.js new file mode 100644 index 00000000..c5214947 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/show_trait.js @@ -0,0 +1,1677 @@ +var statTableRows, isNumber, + __hasProp = {}.hasOwnProperty, + __slice = [].slice; + +isNumber = function(o) { + return !isNaN((o - 0) && o !== null); +}; + +statTableRows = [ + { + vn: "n_of_samples", + pretty: "N of Samples", + digits: 0 + }, { + vn: "mean", + pretty: "Mean", + digits: 3 + }, { + vn: "median", + pretty: "Median", + digits: 3 + }, { + vn: "std_error", + pretty: "Standard Error (SE)", + digits: 3 + }, { + vn: "std_dev", + pretty: "Standard Deviation (SD)", + digits: 3 + }, { + vn: "min", + pretty: "Minimum", + digits: 3 + }, { + vn: "max", + pretty: "Maximum", + digits: 3 + } +] + +if (js_data.dataset_type == "ProbeSet"){ + if (js_data.data_scale == "linear_positive" || js_data.data_scale == "log2") { + statTableRows.push({ + vn: "range", + pretty: "Range (log2)", + digits: 3 + }) + } else { + statTableRows.push({ + vn: "range", + pretty: "Range", + digits: 3 + }) + } +} else { + statTableRows.push({ + vn: "range", + pretty: "Range", + digits: 3 + }) +} + +statTableRows.push( + { + vn: "range_fold", + pretty: "Range (fold)", + digits: 3 + }, { + vn: "interquartile", + pretty: "<font color='black'>Interquartile Range</font>", + url: "http://www.genenetwork.org/glossary.html#Interquartile", + digits: 3 + }, { + vn: "skewness", + pretty: "Skewness", + url: "https://en.wikipedia.org/wiki/Skewness", + digits: 3 + }, { + vn: "kurtosis", + pretty: "Excess Kurtosis", + url: "https://en.wikipedia.org/wiki/Kurtosis", + digits: 3 + } +); + +toggleDescription = function() { + if ($('.truncDesc').is(':visible')) { + $('.truncDesc').hide(); + $('.fullDesc').show(); + } else { + $('.truncDesc').show(); + $('.fullDesc').hide(); + } +} + +add = function() { + var trait; + trait = $("input[name=trait_hmac]").val(); + return $.colorbox({ + href: "/collections/add", + data: { + "traits": trait + } + }); +}; +$('#add_to_collection').click(add); + +sampleLists = js_data.sample_lists; +sampleGroupTypes = js_data.sample_group_types; + +$(".select_covariates").click(function () { + openCovariateSelection(); +}); + +$(".remove_covariates").click(function () { + $(".selected-covariates option:selected").each(function() { + this_val = $(this).val(); + $(".selected-covariates option").each(function(){ + if ($(this).val() == this_val){ + $(this).remove(); + } + }) + cofactor_count = $(".selected-covariates:first option").length + if (cofactor_count > 2 && cofactor_count < 11){ + $(".selected-covariates").each(function() { + $(this).attr("size", $(".selected-covariates:first option").length) + }); + } else if (cofactor_count > 10) { + $(".selected-covariates").each(function() { + $(this).attr("size", 10) + }); + } else { + $(".selected-covariates").each(function() { + $(this).attr("size", 2) + }); + } + if (cofactor_count == 0){ + $(".selected-covariates").each(function() { + $(this).append($("<option/>", { + value: "", + text: "No covariates selected" + })) + }) + } + }); + + covariates_list = []; + $(".selected-covariates:first option").each(function() { + covariates_list.push($(this).val()); + }) + $("input[name=covariates]").val(covariates_list.join(",")) +}); + +$(".remove_all_covariates").click(function() { + $(".selected-covariates option").each(function() { + $(this).remove(); + }); + $(".selected-covariates").attr("size", 2) + $("input[name=covariates]").val(""); +}) + +openTraitSelection = function() { + return $('#collections_holder').load('/collections/list?color_by_trait #collections_list', (function(_this) { + return function() { + $.colorbox({ + inline: true, + href: "#collections_holder", + onComplete: function(){ + $.getScript("/static/new/javascript/get_traits_from_collection.js"); + } + }); + return $('a.collection_name').attr('onClick', 'return false'); + }; + })(this)); +}; +openCovariateSelection = function() { + return $('#collections_holder').load('/collections/list #collections_list', (function(_this) { + return function() { + $.colorbox({ + inline: true, + href: "#collections_holder", + width: "1000px", + height: "700px", + onComplete: function(){ + $.getScript("/static/new/javascript/get_covariates_from_collection.js"); + } + }); + return $('a.collection_name').attr('onClick', 'return false'); + }; + })(this)); +}; +hideTabs = function(start) { + var x, _i, _results; + _results = []; + for (x = _i = start; start <= 10 ? _i <= 10 : _i >= 10; x = start <= 10 ? ++_i : --_i) { + _results.push($("#stats_tabs" + x).hide()); + } + return _results; +}; +statsMdpChange = function() { + var selected; + selected = $(this).val(); + hideTabs(0); + return $("#stats_tabs" + selected).show(); +}; +changeStatsValue = function(sample_sets, category, value_type, decimal_places, effects) { + var current_value, id, in_box, the_value, title_value; + id = "#" + processId(category, value_type); + in_box = $(id).html; + current_value = parseFloat($(in_box)).toFixed(decimal_places); + the_value = sample_sets[category][value_type](); + if (decimal_places > 0) { + title_value = the_value.toFixed(decimal_places * 2); + the_value = the_value.toFixed(decimal_places); + } else { + title_value = null; + } + if (the_value !== current_value) { + if (effects) { + $(id).html(the_value).effect("highlight"); + } else { + $(id).html(the_value); + } + } + if (title_value) { + return $(id).attr('title', title_value); + } +}; +updateStatValues = function(sample_sets) { + var category, row, show_effects, _i, _len, _ref, _results; + show_effects = $(".tab-pane.active").attr("id") === "stats_tab"; + _ref = ['samples_primary', 'samples_other', 'samples_all']; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + category = _ref[_i]; + _results.push((function() { + var _j, _len1, _results1; + _results1 = []; + for (_j = 0, _len1 = statTableRows.length; _j < _len1; _j++) { + row = statTableRows[_j]; + _results1.push(changeStatsValue(sample_sets, category, row.vn, row.digits, show_effects)); + } + return _results1; + })()); + } + return _results; +}; + +updateHistogram_width = function() { + num_bins = $('#histogram').find('g.trace.bars').find('g.point').length + + if (num_bins < 10) { + width_update = { + width: 400 + } + + Plotly.relayout('histogram', width_update) + } +} + +updateHistogram = function() { + var x; + var _i, _len, _ref, data; + _ref = _.values(root.selected_samples[root.stats_group]); + var trait_vals = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + x = _ref[_i]; + trait_vals.push(x.value); + } + root.histogram_data[0]['x'] = trait_vals + + if ($('input[name="transform"]').val() != ""){ + root.histogram_layout['xaxis']['title'] = "<b>" + js_data.unit_type + " (" + $('input[name="transform"]').val() + ")</b>" + } else { + root.histogram_layout['xaxis']['title'] = "<b>" + js_data.unit_type + "</b>" + } + + Plotly.newPlot('histogram', root.histogram_data, root.histogram_layout, root.modebar_options); + updateHistogram_width() +}; + +updateBarChart = function() { + var x; + var _i, _len, _ref, data; + _ref = _.values(root.selected_samples[root.stats_group]); + names_and_values = [] + for (i = 0; i < _ref.length; i++){ + _ref[i]["name"] = Object.keys(root.selected_samples[root.stats_group])[i] + } + trait_vals = []; + trait_vars = []; + trait_samples = []; + + function sortFunction(a, b) { + if (a.value === b.value) { + return 0; + } + else { + return (a.value < b.value) ? -1 : 1; + } + } + + if (root.bar_sort == "value") { + _ref.sort(sortFunction) + } + + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + x = _ref[_i]; + trait_samples.push(x.name) + trait_vals.push(x.value); + if (x["variance"] != undefined) { + trait_vars.push(x.variance); + } else{ + trait_vars.push(null) + } + } + + new_chart_range = getBarRange(trait_vals, trait_vars) + + root.bar_layout['yaxis']['range'] = new_chart_range + + if ($('input[name="transform"]').val() != ""){ + root.bar_layout['yaxis']['title'] = "<b>" + js_data.unit_type + " (" + $('input[name="transform"]').val() + ")</b>" + } else { + root.bar_layout['yaxis']['title'] = "<b>" + js_data.unit_type + "</b>" + } + + root.bar_data[0]['y'] = trait_vals + root.bar_data[0]['error_y'] = { + type: 'data', + array: trait_vars, + visible: root.errors_exist + } + root.bar_data[0]['x'] = trait_samples + + if (trait_vals.length < 256) { + Plotly.newPlot('bar_chart', root.bar_data, root.bar_layout, root.modebar_options); + } +}; + +updateViolinPlot = function() { + var y_value_list = [] + if (sampleLists.length > 1) { + i = 0; + for (var sample_group in root.selected_samples){ + var trait_sample_data = _.values(root.selected_samples[sample_group]) + var trait_vals = []; + for (j = 0, len = trait_sample_data.length; j < len; j++) { + this_sample_data = trait_sample_data[j]; + trait_vals.push(this_sample_data.value); + } + root.violin_data[i]['y'] = trait_vals + i++; + } + } else { + var trait_sample_data = _.values(root.selected_samples['samples_all']) + var trait_vals = []; + for (j = 0, len = trait_sample_data.length; j < len; j++) { + this_sample_data = trait_sample_data[j]; + trait_vals.push(this_sample_data.value); + } + root.violin_data[0]['y'] = trait_vals + } + + if ($('input[name="transform"]').val() != ""){ + root.violin_layout['yaxis']['title'] = "<b>" + js_data.unit_type + " (" + $('input[name="transform"]').val() + ")</b>" + } else { + root.violin_layout['yaxis']['title'] = "<b>" + js_data.unit_type + "</b>" + } + + Plotly.newPlot('violin_plot', root.violin_data, root.violin_layout, root.modebar_options) +} + + +updateProbPlot = function() { + return root.redraw_prob_plot_impl(root.selected_samples, root.prob_plot_group); +}; + +makeTable = function() { + var header, key, row, row_line, table, the_id, the_rows, value, _i, _len, _ref, _ref1; + if (js_data.trait_symbol != null) { + header = "<thead><tr><th style=\"color: white; background-color: #369; text-align: center;\" colspan=\"100%\">Trait " + js_data.trait_id + " - " + js_data.trait_symbol + "</th></tr><tr><th style=\"text-align: right; padding-left: 5px;\">Statistic</th>"; + } else if (js_data.dataset_type == "Geno"){ + header = "<thead><tr><th style=\"color: white; background-color: #369; text-align: center;\" colspan=\"100%\">Marker " + js_data.trait_id + "</th></tr><tr><th style=\"text-align: right; padding-left: 5px;\">Statistic</th>"; + } else { + header = "<thead><tr><th style=\"color: white; background-color: #369; text-align: center;\" colspan=\"100%\">Trait " + js_data.trait_id + ": " + js_data.short_description + "</th></tr><tr><th style=\"text-align: right; padding-left: 5px;\">Statistic</th>"; + } + _ref = js_data.sample_group_types; + for (key in _ref) { + if (!__hasProp.call(_ref, key)) continue; + value = _ref[key]; + the_id = processId("column", key); + if (Object.keys(_ref).length > 1) { + header += "<th id=\"" + the_id + "\" style=\"text-align: right; padding-left: 5px;\">" + value + "</th>"; + } else { + header += "<th id=\"" + the_id + "\" style=\"text-align: right; padding-left: 5px;\">Value</th>"; + } + } + + header += "</thead>"; + the_rows = "<tbody>"; + for (_i = 0, _len = statTableRows.length; _i < _len; _i++) { + row = statTableRows[_i]; + if ((row.vn == "range_fold") && js_data.dataset_type == "Publish"){ + continue; + } + row_line = "<tr>"; + if (row.url != null) { + row_line += "<td id=\"" + row.vn + "\" align=\"right\"><a href=\"" + row.url + "\" style=\"color: #0000EE;\">" + row.pretty + "</a></td>"; + } else { + row_line += "<td id=\"" + row.vn + "\" align=\"right\">" + row.pretty + "</td>"; + } + _ref1 = js_data.sample_group_types; + for (key in _ref1) { + if (!__hasProp.call(_ref1, key)) continue; + value = _ref1[key]; + the_id = processId(key, row.vn); + row_line += "<td id=\"" + the_id + "\" align=\"right\">N/A</td>"; + } + row_line += "</tr>"; + the_rows += row_line; + } + the_rows += "</tbody>"; + table = header + the_rows; + return $("#stats_table").append(table); +}; +processId = function() { + var processed, value, values, _i, _len; + values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + + /* Make an id or a class valid javascript by, for example, eliminating spaces */ + processed = ""; + for (_i = 0, _len = values.length; _i < _len; _i++) { + value = values[_i]; + value = value.replace(" ", "_"); + if (processed.length) { + processed += "-"; + } + processed += value; + } + return processed; +}; + +fetchSampleValues = function() { + // This is meant to fetch all sample values using DataTables API, since they can't all be submitted with the form when using Scroller (and this should also be faster) + sample_val_dict = {}; + + table = 'samples_primary'; + if ($('#' + table).length){ + tableApi = $('#' + table).DataTable(); + val_nodes = tableApi.column(3).nodes().to$(); + for (_j = 0; _j < val_nodes.length; _j++){ + sample_name = val_nodes[_j].childNodes[0].name.split(":")[1] + sample_val = val_nodes[_j].childNodes[0].value + sample_val_dict[sample_name] = sample_val + } + } + + return sample_val_dict; +} + +editDataChange = function() { + var already_seen, sample_sets, table, tables, _i, _j, _len; + already_seen = {}; + sample_sets = { + samples_primary: new Stats([]), + samples_other: new Stats([]), + samples_all: new Stats([]) + }; + root.selected_samples = { + samples_primary: {}, + samples_other: {}, + samples_all: {} + }; + + tables = ['samples_primary', 'samples_other']; + for (_i = 0, _len = tables.length; _i < _len; _i++) { + table = tables[_i]; + if ($('#' + table).length){ + tableApi = $('#' + table).DataTable(); + sample_vals = []; + name_nodes = tableApi.column(2).nodes().to$(); + val_nodes = tableApi.column(3).nodes().to$(); + var_nodes = tableApi.column(5).nodes().to$(); + for (_j = 0; _j < val_nodes.length; _j++){ + sample_val = val_nodes[_j].childNodes[0].value + sample_name = $.trim(name_nodes[_j].childNodes[0].textContent) + if (isNumber(sample_val) && sample_val !== "") { + sample_val = parseFloat(sample_val); + sample_sets[table].add_value(sample_val); + try { + sample_var = var_nodes[_j].childNodes[0].value + if (isNumber(sample_var)) { + sample_var = parseFloat(sample_var) + } else { + sample_var = null; + } + } catch { + sample_var = null; + } + sample_dict = { + value: sample_val, + variance: sample_var + } + root.selected_samples[table][sample_name] = sample_dict; + if (!(sample_name in already_seen)) { + sample_sets['samples_all'].add_value(sample_val); + root.selected_samples['samples_all'][sample_name] = sample_dict; + already_seen[sample_name] = true; + } + } + } + } + + } + + updateStatValues(sample_sets); + + if ($('#histogram').hasClass('js-plotly-plot')){ + updateHistogram(); + } + if ($('#bar_chart').hasClass('js-plotly-plot')){ + updateBarChart(); + } + if ($('#violin_plot').hasClass('js-plotly-plot')){ + updateViolinPlot(); + } + if ($('#prob_plot_div').hasClass('js-plotly-plot')){ + return updateProbPlot(); + } +}; + +showHideOutliers = function() { + var label; + label = $('#showHideOutliers').val(); + if (label === "Hide Outliers") { + return $('#showHideOutliers').val("Show Outliers"); + } else if (label === "Show Outliers") { + $('#showHideOutliers').val("Hide Outliers"); + return console.log("Should be now Hide Outliers"); + } +}; + +onCorrMethodChange = function() { + var corr_method; + corr_method = $('select[name=corr_type]').val(); + $('.correlation_desc').hide(); + $('#' + corr_method + "_r_desc").show().effect("highlight"); + if (corr_method === "lit") { + return $("#corr_sample_method").hide(); + } else { + return $("#corr_sample_method").show(); + } +}; +$('select[name=corr_type]').change(onCorrMethodChange); + +on_dataset_change = function() { + let dataset_type = $('select[name=corr_dataset] option:selected').data('type'); + let location_type = $('select[name=location_type] option:selected').val(); + + if (dataset_type == "mrna_assay"){ + $('#min_expr_filter').show(); + $('select[name=location_type] option:disabled').prop('disabled', false) + } + else if (dataset_type == "pheno"){ + $('#min_expr_filter').show(); + $('select[name=location_type]>option:eq(0)').prop('disabled', true).attr('selected', false); + $('select[name=location_type]>option:eq(1)').prop('disabled', false).attr('selected', true); + } + else { + $('#min_expr_filter').hide(); + $('select[name=location_type]>option:eq(0)').prop('disabled', false).attr('selected', true); + $('select[name=location_type]>option:eq(1)').prop('disabled', true).attr('selected', false); + } +} + +$('select[name=corr_dataset]').change(on_dataset_change); +$('select[name=location_type]').change(on_dataset_change); + +submit_special = function(url) { + $("input[name=sample_vals]").val(JSON.stringify(fetchSampleValues())) + $("#trait_data_form").attr("action", url); + + $("#trait_data_form").submit(); +}; + +var corrInputList = ['sample_vals', 'corr_type', 'primary_samples', 'trait_id', 'dataset', 'group', 'tool_used', 'form_url', 'corr_sample_method', 'corr_samples_group', 'corr_dataset', 'min_expr', + 'corr_return_results', 'location_type', 'loc_chr', 'min_loc_mb', 'max_loc_mb', 'p_range_lower', 'p_range_upper',"use_cache"] + +$(".test_corr_compute").on("click", (function(_this) { + return function() { + $('input[name=tool_used]').val("Correlation"); + $('input[name=form_url]').val("/test_corr_compute"); + $('input[name=wanted_inputs]').val(corrInputList.join(",")); + + + url = "/loading"; + return submit_special(url); + }; +})(this)); + +$(".corr_compute").on("click", (function(_this) { + + return function() { + + + $('input[name=tool_used]').val("Correlation"); + $('input[name=form_url]').val("/corr_compute"); + $('input[name=wanted_inputs]').val(corrInputList.join(",")); + + + $('input[name=use_cache]').val($('#use_cache').is(":checked") ? "true": "false"); + + url = "/loading"; + return submit_special(url); + }; +})(this)); + +createValueDropdown = function(value) { + return "<option val=" + value + ">" + value + "</option>"; +}; + +populateSampleAttributesValuesDropdown = function() { + var attribute_info, key, sample_attributes, selected_attribute, value, _i, _len, _ref, _ref1, _results; + $('#attribute_values').empty(); + sample_attributes = []; + + var attributesAsList = Object.keys(js_data.attributes).map(function(key) { + return [key, js_data.attributes[key].id]; + }); + + attributesAsList.sort(function(first, second) { + if (second[1] > first[1]){ + return -1 + } + if (first[1] > second[1]){ + return 1 + } + return 0 + }); + + for (i=0; i < attributesAsList.length; i++) { + attribute_info = js_data.attributes[attributesAsList[i][1]] + sample_attributes.push(attribute_info.distinct_values); + } + + selected_attribute = $('#exclude_column').val() + _ref1 = sample_attributes[selected_attribute - 1]; + _results = []; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + value = _ref1[_i]; + if (value != ""){ + _results.push($(createValueDropdown(value)).appendTo($('#attribute_values'))); + } + } + return _results; +}; + +if (js_data.categorical_attr_exists == "true"){ + populateSampleAttributesValuesDropdown(); +} + +$('#exclude_column').change(populateSampleAttributesValuesDropdown); +blockByAttributeValue = function() { + var attribute_name, cell_class, exclude_by_value; + + let exclude_group = $('#exclude_by_attr_group').val(); + let exclude_column = $('#exclude_column').val(); + + if (exclude_group === "other") { + var tableApi = $('#samples_other').DataTable(); + } else { + var tableApi = $('#samples_primary').DataTable(); + } + + exclude_by_value = $('#attribute_values').val(); + + let val_nodes = tableApi.column(3).nodes().to$(); + let exclude_val_nodes = tableApi.column(attributeStartPos + parseInt(exclude_column)).nodes().to$(); + + for (i = 0; i < exclude_val_nodes.length; i++) { + if (exclude_val_nodes[i].hasChildNodes()) { + let this_col_value = exclude_val_nodes[i].childNodes[0].data; + let this_val_node = val_nodes[i].childNodes[0]; + + if (this_col_value == exclude_by_value){ + this_val_node.value = "x"; + } + } + } + + editDataChange(); +}; +$('#exclude_by_attr').click(blockByAttributeValue); + +blockByIndex = function() { + var end_index, error, index, index_list, index_set, index_string, start_index, _i, _j, _k, _len, _len1, _ref; + index_string = $('#remove_samples_field').val(); + index_list = []; + _ref = index_string.split(","); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + index_set = _ref[_i]; + if (index_set.indexOf('-') !== -1) { + try { + start_index = parseInt(index_set.split("-")[0]); + end_index = parseInt(index_set.split("-")[1]); + for (index = _j = start_index; start_index <= end_index ? _j <= end_index : _j >= end_index; index = start_index <= end_index ? ++_j : --_j) { + index_list.push(index); + } + } catch (_error) { + error = _error; + alert("Syntax error"); + } + } else { + index = parseInt(index_set); + index_list.push(index); + } + } + + let block_group = $('#block_group').val(); + if (block_group === "other") { + tableApi = $('#samples_other').DataTable(); + } else { + tableApi = $('#samples_primary').DataTable(); + } + val_nodes = tableApi.column(3).nodes().to$(); + for (_k = 0, _len1 = index_list.length; _k < _len1; _k++) { + index = index_list[_k]; + val_nodes[index - 1].childNodes[0].value = "x"; + } +}; + +filter_by_study = function() { + let this_study = $('#filter_study').val(); + + let study_sample_data = JSON.parse($('input[name=study_samplelists]').val()) + let filter_samples = study_sample_data[parseInt(this_study)]['samples'] + + if ($('#filter_study_group').length){ + let block_group = $('#filter_study_group').val(); + if (block_group === "other") { + tableApi = $('#samples_other').DataTable(); + } else { + tableApi = $('#samples_primary').DataTable(); + } + } + + let sample_nodes = tableApi.column(2).nodes().to$(); + let val_nodes = tableApi.column(3).nodes().to$(); + for (i = 0; i < sample_nodes.length; i++) { + this_sample = sample_nodes[i].childNodes[0].innerText; + if (!filter_samples.includes(this_sample)){ + val_nodes[i].childNodes[0].value = "x"; + } + } +} + +filter_by_value = function() { + let filter_logic = $('#filter_logic').val(); + let filter_column = $('#filter_column').val(); + let filter_value = $('#filter_value').val(); + let block_group = $('#filter_group').val(); + + if (block_group === "other") { + var tableApi = $('#samples_other').DataTable(); + } else { + var tableApi = $('#samples_primary').DataTable(); + } + + let val_nodes = tableApi.column(3).nodes().to$(); + if (filter_column == "value"){ + var filter_val_nodes = tableApi.column(3).nodes().to$(); + } + else if (filter_column == "stderr"){ + var filter_val_nodes = tableApi.column(5).nodes().to$(); + } + else if (!isNaN(filter_column)){ + var filter_val_nodes = tableApi.column(attributeStartPos + parseInt(filter_column)).nodes().to$(); + } + else { + return false + } + + for (i = 0; i < filter_val_nodes.length; i++) { + if (filter_column == "value" || filter_column == "stderr"){ + var this_col_value = filter_val_nodes[i].childNodes[0].value; + } else { + if (filter_val_nodes[i].childNodes[0] !== undefined){ + var this_col_value = filter_val_nodes[i].innerText; + } else { + continue + } + } + let this_val_node = val_nodes[i].childNodes[0]; + + if(!isNaN(this_col_value) && !isNaN(filter_value)) { + if (filter_logic == "greater_than"){ + if (parseFloat(this_col_value) <= parseFloat(filter_value)){ + this_val_node.value = "x"; + } + } + else if (filter_logic == "less_than"){ + if (parseFloat(this_col_value) >= parseFloat(filter_value)){ + this_val_node.value = "x"; + } + } + else if (filter_logic == "greater_or_equal"){ + if (parseFloat(this_col_value) < parseFloat(filter_value)){ + this_val_node.value = "x"; + } + } + else if (filter_logic == "less_or_equal"){ + if (parseFloat(this_col_value) > parseFloat(filter_value)){ + this_val_node.value = "x"; + } + } + } + } +}; + +hideNoValue_filter = function( settings, data, dataIndex ) { + this_value = tableApi.column(3).nodes().to$()[dataIndex].childNodes[0].value; + if (this_value == "x"){ + return false + } else { + return true + } +} + +hideNoValue = function() { + tables = ['samples_primary', 'samples_other']; + filter_active = $(this).data("active"); + for (_i = 0, _len = tables.length; _i < _len; _i++) { + table = tables[_i]; + if ($('#' + table).length) { + tableApi = $('#' + table).DataTable(); + if (filter_active == "true"){ + $(this).val("Hide No Value") + tableApi.draw(); + $(this).data("active", "false"); + } else { + $(this).val("Show No Value") + $.fn.dataTable.ext.search.push(hideNoValue_filter); + tableApi.search(); + tableApi.draw(); + $.fn.dataTable.ext.search.splice($.fn.dataTable.ext.search.indexOf(hideNoValue_filter, 1)); + $(this).data("active", "true"); + } + } + } +}; +$('#hideNoValue').click(hideNoValue); + +blockOutliers = function() { + return $('.outlier').each((function(_this) { + return function(_index, element) { + return $(element).find('.trait-value-input').val('x'); + }; + })(this)); +}; +$('#blockOutliers').click(blockOutliers); + +resetSamplesTable = function() { + $('input[name="transform"]').val(""); + $('span[name="transform_text"]').text("") + $('#hideNoValue').val("Hide No Value") + tables = ['samples_primary', 'samples_other']; + for (_i = 0, _len = tables.length; _i < _len; _i++) { + table = tables[_i]; + if ($('#' + table).length) { + tableApi = $('#' + table).DataTable(); + val_nodes = tableApi.column(3).nodes().to$(); + for (i = 0; i < val_nodes.length; i++) { + this_node = val_nodes[i].childNodes[0]; + this_node.value = this_node.attributes["data-value"].value; + } + if (js_data.se_exists){ + se_nodes = tableApi.column(5).nodes().to$(); + for (i = 0; i < val_nodes.length; i++) { + this_node = val_nodes[i].childNodes[0]; + this_node.value = this_node.attributes["data-value"].value; + } + } + tableApi.draw(); + } + } +}; +$('.reset').click(function() { + $('.selected').each(function() { + $(this).removeClass('selected'); + $(this).find('.edit_sample_checkbox').prop("checked", false); + }) + resetSamplesTable(); + $('input[name="transform"]').val(""); + editDataChange(); +}); + +checkForZeroToOneVals = function() { + tables = ['samples_primary', 'samples_other']; + for (_i = 0, _len = tables.length; _i < _len; _i++) { + table = tables[_i]; + if ($('#' + table).length) { + tableApi = $('#' + table).DataTable(); + val_nodes = tableApi.column(3).nodes().to$(); + for (i = 0; i < val_nodes.length; i++) { + this_node = val_nodes[i].childNodes[0]; + if(!isNaN(this_node.value)) { + if (0 <= this_node.value && this_node.value < 1){ + return true + } + } + } + } + } + return false +} + +log2Data = function(this_node) { + current_value = this_node.value; + original_value = this_node.attributes['data-value'].value; + if(!isNaN(current_value) && !isNaN(original_value)) { + if (zeroToOneValsExist){ + original_value = parseFloat(original_value) + 1; + } + this_node.value = Math.log2(original_value).toFixed(3); + } +}; + +log10Data = function() { + current_value = this_node.value; + original_value = this_node.attributes['data-value'].value; + if(!isNaN(current_value) && !isNaN(original_value)) { + if (zeroToOneValsExist){ + original_value = parseFloat(original_value) + 1; + } + this_node.value = Math.log10(original_value).toFixed(3); + } +}; + +sqrtData = function() { + current_value = this_node.value; + original_value = this_node.attributes['data-value'].value; + if(!isNaN(current_value) && !isNaN(original_value)) { + if (zeroToOneValsExist){ + original_value = parseFloat(original_value) + 1; + } + this_node.value = Math.sqrt(original_value).toFixed(3); + } +}; + +invertData = function() { + current_value = this_node.value; + if(!isNaN(current_value)) { + this_node.value = parseFloat(-(current_value)).toFixed(3); + } +}; + +qnormData = function() { + current_value = this_node.value; + qnorm_value = this_node.attributes['data-qnorm'].value; + if(!isNaN(current_value)) { + this_node.value = qnorm_value; + } +}; + +zScoreData = function() { + current_value = this_node.value; + zscore_value = this_node.attributes['data-zscore'].value; + if(!isNaN(current_value)) { + this_node.value = zscore_value; + } +}; + +doTransform = function(transform_type) { + tables = ['samples_primary', 'samples_other']; + for (_i = 0, _len = tables.length; _i < _len; _i++) { + table = tables[_i]; + if ($('#' + table).length) { + tableApi = $('#' + table).DataTable(); + val_nodes = tableApi.column(3).nodes().to$(); + for (i = 0; i < val_nodes.length; i++) { + this_node = val_nodes[i].childNodes[0] + if (transform_type == "log2"){ + log2Data(this_node) + } + if (transform_type == "log10"){ + log10Data(this_node) + } + if (transform_type == "sqrt"){ + sqrtData(this_node) + } + if (transform_type == "invert"){ + invertData(this_node) + } + if (transform_type == "qnorm"){ + qnormData(this_node) + } + if (transform_type == "zscore"){ + zScoreData(this_node) + } + } + } + } +} + +normalizeData = function() { + if ($('#norm_method option:selected').val() == 'log2' || $('#norm_method option:selected').val() == 'log10'){ + if ($('input[name="transform"]').val() != "log2" && $('#norm_method option:selected').val() == 'log2') { + doTransform("log2") + $('input[name="transform"]').val("log2") + $('span[name="transform_text"]').text(" - log2 Transformed") + } else { + if ($('input[name="transform"]').val() != "log10" && $('#norm_method option:selected').val() == 'log10'){ + doTransform("log10") + $('input[name="transform"]').val("log10") + $('span[name="transform_text"]').text(" - log10 Transformed") + } + } + } + else if ($('#norm_method option:selected').val() == 'sqrt'){ + if ($('input[name="transform"]').val() != "sqrt") { + doTransform("sqrt") + $('input[name="transform"]').val("sqrt") + $('span[name="transform_text"]').text(" - Square Root Transformed") + } + } + else if ($('#norm_method option:selected').val() == 'invert'){ + doTransform("invert") + $('input[name="transform"]').val("inverted") + if ($('span[name="transform_text"]:eq(0)').text() != ""){ + current_text = $('span[name="transform_text"]:eq(0)').text(); + $('span[name="transform_text"]').text(current_text + " and Inverted"); + } else { + $('span[name="transform_text"]').text(" - Inverted") + } + } + else if ($('#norm_method option:selected').val() == 'qnorm'){ + if ($('input[name="transform"]').val() != "qnorm") { + doTransform("qnorm") + $('input[name="transform"]').val("qnorm") + $('span[name="transform_text"]').text(" - Quantile Normalized") + } + } + else if ($('#norm_method option:selected').val() == 'zscore'){ + if ($('input[name="transform"]').val() != "zscore") { + doTransform("zscore") + $('input[name="transform"]').val("zscore") + $('span[name="transform_text"]').text(" - Z-Scores") + } + } +} + +zeroToOneValsExist = checkForZeroToOneVals(); + +showTransformWarning = function() { + transform_type = $('#norm_method option:selected').val() + if (transform_type == "log2" || transform_type == "log10"){ + if (zeroToOneValsExist){ + $('#transform_alert').css("display", "block") + } + } else { + $('#transform_alert').css("display", "none") + } +} + +$('#norm_method').change(function(){ + showTransformWarning() +}); +$('#normalize').hover(function(){ + showTransformWarning() +}); + +$('#normalize').click(normalizeData) + +switchQNormData = function() { + return $('.trait-value-input').each((function(_this) { + return function(_index, element) { + transform_val = $(element).data('transform') + if (transform_val != "") { + $(element).val(transform_val.toFixed(3)); + } + return transform_val + }; + })(this)); +}; +$('#qnorm').click(switchQNormData); + +getSampleTableData = function(tableName, attributesAsList, includeNAs=false) { + var samples = []; + + if ($('#' + tableName).length){ + tableApi = $('#' + tableName).DataTable(); + attrCol = 4 + + nameNodes = tableApi.column(2).nodes().to$(); + valNodes = tableApi.column(3).nodes().to$(); + if (js_data.se_exists){ + varNodes = tableApi.column(5).nodes().to$(); + attrCol = 6 + if (js_data.has_num_cases) { + nNodes = tableApi.column(6).nodes().to$(); + attrCol = 7 + } + } else { + if (js_data.has_num_cases){ + nNodes = tableApi.column(4).nodes().to$(); + attrCol = 5 + } + } + + attributeNodes = [] + for (_i = 0; _i < attributesAsList.length; _i++){ + attributeNodes.push(tableApi.column(attrCol + _i).nodes().to$()) + } + + checkedRows = getCheckedRows(tableName) + + for (_j = 0; _j < valNodes.length; _j++){ + if (!checkedRows.includes(_j) && checkedRows.length > 0) { + continue + } + sampleVal = valNodes[_j].childNodes[0].value + sampleName = $.trim(nameNodes[_j].childNodes[0].textContent) + if (isNumber(sampleVal) && sampleVal !== "") { + sampleVal = parseFloat(sampleVal); + } else { + sampleVal = 'x' + } + if (typeof varNodes == 'undefined'){ + sampleVar = null; + } else { + sampleVar = varNodes[_j].childNodes[0].value; + if (isNumber(sampleVar)) { + sampleVar = parseFloat(sampleVar); + } else { + sampleVar = 'x'; + } + } + if (typeof nNodes == 'undefined'){ + sampleN = null; + } else { + sampleN = nNodes[_j].childNodes[0].value; + if (isNumber(sampleN)) { + sampleN = parseInt(sampleN); + } else { + sampleN = 'x'; + } + } + + rowDict = { + name: sampleName, + value: sampleVal, + se: sampleVar, + num_cases: sampleN + } + + for (_k = 0; _k < attributeNodes.length; _k++){ + rowDict[attributesAsList[_k]] = attributeNodes[_k][_j].textContent; + } + if (includeNAs || sampleVal != 'x') { + samples.push(rowDict) + } + } + } + + return samples; +}; +exportSampleTableData = function() { + var format, json_sample_data, sample_data; + + var attributesAsList = Object.keys(js_data.attributes).map(function(key) { + return js_data.attributes[key].name; + }); + + sample_data = {}; + sample_data.primary_samples = getSampleTableData('samples_primary', attributesAsList, true); + sample_data.other_samples = getSampleTableData('samples_other', attributesAsList, true); + sample_data.attributes = attributesAsList; + json_sample_data = JSON.stringify(sample_data); + $('input[name=export_data]').val(json_sample_data); + format = $('input[name=export_format]').val(); + if (format === "excel") { + $('#trait_data_form').attr('action', '/export_trait_excel'); + } else { + $('#trait_data_form').attr('action', '/export_trait_csv'); + } + return $('#trait_data_form').submit(); +}; + +$('.export_format').change(function() { + if (this.value == "csv"){ + $('#export_code').css("display", "block") + } else{ + $('#export_code').css("display", "none") + } + $('input[name=export_format]').val( this.value ); + $('.export_format').val( this.value ); +}); + +$('.export').click(exportSampleTableData); +$('#blockOutliers').click(blockOutliers); +_.mixin(_.str.exports()); + +getSampleVals = function(sample_list) { + var sample; + return this.sample_vals = (function() { + var i, len, results; + results = []; + for (i = 0, len = sample_list.length; i < len; i++) { + sample = sample_list[i]; + if (sample.value !== null) { + results.push(sample.value); + } + } + return results; + })(); +}; + +getSampleErrors = function(sample_list) { + var sample; + return this.sample_vals = (function() { + var i, len, results; + variance_exists = false; + results = []; + for (i = 0, len = sample_list.length; i < len; i++) { + sample = sample_list[i]; + if (sample.variance !== null) { + results.push(sample.variance); + variance_exists = true; + } + } + return [results, variance_exists]; + })(); +}; + +getSampleNames = function(sample_list) { + var sample; + return this.sampleNames = (function() { + var i, len, results; + results = []; + for (i = 0, len = sample_list.length; i < len; i++) { + sample = sample_list[i]; + if (sample.value !== null) { + results.push(sample.name); + } + } + return results; + })(); +}; + +getBarBottomMargin = function(sample_list){ + bottomMargin = 80 + maxLength = 0 + sampleNames = getSampleNames(sample_list) + for (i=0; i < sampleNames.length; i++){ + if (sampleNames[i].length > maxLength) { + maxLength = sampleNames[i].length + } + } + + if (maxLength > 6){ + bottomMargin += 11*(maxLength - 6) + } + + return bottomMargin; +} + +root.stats_group = 'samples_primary'; + +if (Object.keys(js_data.sample_group_types).length > 1) { + fullSampleLists = [sampleLists[0], sampleLists[1], sampleLists[0].concat(sampleLists[1])] + sampleGroupList = [js_data.sample_group_types['samples_primary'], js_data.sample_group_types['samples_other'], js_data.sample_group_types['samples_all']] +} else { + fullSampleLists = [sampleLists[0]] + sampleGroupList = [js_data.sample_group_types['samples_primary']] +} + +// Define Plotly Options (for the options bar at the top of each figure) + +root.modebar_options = { + displayModeBar: true, + modeBarButtonsToAdd:[{ + name: 'Export as SVG', + icon: Plotly.Icons.disk, + click: function(gd) { + Plotly.downloadImage(gd, {format: 'svg'}) + } + }, + { + name: 'Export as JPEG', + icon: Plotly.Icons.camera, + click: function(gd) { + Plotly.downloadImage(gd, {format: 'jpeg'}) + } + }], + showEditInChartStudio: true, + plotlyServerURL: "https://chart-studio.plotly.com", + modeBarButtonsToRemove:['zoom2d', 'pan2d', 'toImage', 'hoverClosest', 'hoverCompare', 'hoverClosestCartesian', 'hoverCompareCartesian', 'lasso2d', 'toggleSpikelines', 'resetScale2d'], + displaylogo: false + //modeBarButtons:['toImage2', 'zoom2d', 'pan2d', 'select2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d'], +} + +// Bar Chart + +root.errors_exist = getSampleErrors(sampleLists[0])[1] +var barTrace = { + x: getSampleNames(sampleLists[0]), + y: getSampleVals(sampleLists[0]), + error_y: { + type: 'data', + array: getSampleErrors(sampleLists[0])[0], + visible: root.errors_exist + }, + type: 'bar' +} + +root.bar_data = [barTrace] + +getBarRange = function(sample_vals, sampleErrors = null){ + positiveErrorVals = [] + negativeErrorVals = [] + for (i = 0;i < sample_vals.length; i++){ + if (sampleErrors[i] != undefined) { + positiveErrorVals.push(sample_vals[i] + sampleErrors[i]) + negativeErrorVals.push(sample_vals[i] - sampleErrors[i]) + } else { + positiveErrorVals.push(sample_vals[i]) + negativeErrorVals.push(sample_vals[i]) + } + } + + minYVal = Math.min(...negativeErrorVals) + maxYVal = Math.max(...positiveErrorVals) + + if (minYVal == 0) { + rangeTop = maxYVal + Math.abs(maxYVal)*0.1 + rangeBottom = 0; + } else { + rangeTop = maxYVal + Math.abs(maxYVal)*0.1 + rangeBottom = minYVal - Math.abs(minYVal)*0.1 + if (minYVal > 0) { + rangeBottom = minYVal - 0.1*Math.abs(minYVal) + } else if (minYVal < 0) { + rangeBottom = minYVal + 0.1*minYVal + } else { + rangeBottom = 0 + } + } + + return [rangeBottom, rangeTop] +} + +root.chart_range = getBarRange(getSampleVals(sampleLists[0]), getSampleErrors(sampleLists[0])[0]) +valRange = root.chart_range[1] - root.chart_range[0] + +if (valRange < 0.05){ + tickDigits = '.3f' + leftMargin = 80 +} else if (valRange < 0.5) { + tickDigits = '.2f' + leftMargin = 70 +} else if (valRange < 5){ + tickDigits = '.1f' + leftMargin = 60 +} else { + tickDigits = 'f' + leftMargin = 55 +} + +if (js_data.num_values < 256) { + barChartWidth = 25 * getSampleVals(sampleLists[0]).length + + // Set bottom margin dependent on longest sample name length, since those can get long + bottomMargin = getBarBottomMargin(sampleLists[0]) + + root.bar_layout = { + title: { + x: 0, + y: 10, + xanchor: 'left', + text: "<b>Trait " + js_data.trait_id + ": " + js_data.short_description + "</b>", + }, + xaxis: { + type: 'category', + titlefont: { + size: 16 + }, + showline: true, + ticklen: 4, + tickfont: { + size: 16 + }, + }, + yaxis: { + title: "<b>" + js_data.unit_type + "</b>", + range: root.chart_range, + titlefont: { + size: 16 + }, + showline: true, + ticklen: 4, + tickfont: { + size: 16 + }, + tickformat: tickDigits, + fixedrange: true + }, + width: barChartWidth, + height: 600, + margin: { + l: leftMargin, + r: 30, + t: 100, + b: bottomMargin + }, + dragmode: false + }; + + $('.bar_chart_tab').click(function() { + updateBarChart(); + }); +} + +total_sample_count = 0 +for (i = 0, i < sampleLists.length; i++;) { + total_sample_count += getSampleVals(sampleLists[i]).length +} + +// Histogram +var hist_trace = { + x: getSampleVals(sampleLists[0]), + type: 'histogram' +}; +root.histogram_data = [hist_trace]; +root.histogram_layout = { + bargap: 0.05, + title: { + x: 0, + y: 10, + xanchor: 'left', + text: "<b> Trait " + js_data.trait_id + ": " + js_data.short_description + "</b>", + }, + xaxis: { + autorange: true, + title: js_data.unit_type, + titlefont: { + family: "arial", + size: 16 + }, + ticklen: 4, + tickfont: { + size: 16 + } + }, + yaxis: { + autorange: true, + title: "<b>count</b>", + titlefont: { + family: "arial", + size: 16 + }, + showline: true, + ticklen: 4, + tickfont: { + size: 16 + }, + automargin: true, + fixedrange: true + }, + width: 500, + height: 600, + margin: { + l: 100, + r: 30, + t: 100, + b: 50 + } +}; + +$('.histogram_tab').click(function() { + updateHistogram(); + updateHistogram_width(); +}); + +$('.histogram_samples_group').val(root.stats_group); +$('.histogram_samples_group').change(function() { + root.stats_group = $(this).val(); + return updateHistogram(); +}); + +// Violin Plot + +root.violin_layout = { + title: "<b>Trait " + js_data.trait_id + ": " + js_data.short_description + "</b>", + xaxis: { + showline: true, + titlefont: { + family: "arial", + size: 16 + }, + tickfont: { + family: "arial", + size: 16 + } + }, + yaxis: { + title: { + text: "<b>"+js_data.unit_type+"</b>" + }, + titlefont: { + family: "arial", + size: 16 + }, + autorange: true, + showline: true, + ticklen: 4, + tickfont: { + size: 16 + }, + tickformat: tickDigits, + zeroline: false, + automargin: true + }, + margin: { + l: 100, + r: 30, + t: 100, + b: 80 + }, + dragmode: false, + showlegend: false +}; + +if (fullSampleLists.length > 1) { + root.violin_layout['width'] = 600; + root.violin_layout['height'] = 500; + var trace1 = { + y: getSampleVals(fullSampleLists[2]), + type: 'violin', + points: 'none', + box: { + visible: true + }, + line: { + color: 'blue', + }, + meanline: { + visible: true + }, + name: "<b>" + sampleGroupList[2] + "</b>", + x0: "<b>" + sampleGroupList[2] + "</b>" + } + var trace2 = { + y: getSampleVals(fullSampleLists[1]), + type: 'violin', + points: 'none', + box: { + visible: true + }, + line: { + color: 'red', + }, + meanline: { + visible: true + }, + name: "<b>" + sampleGroupList[1] + "</b>", + x0: "<b>" + sampleGroupList[1] + "</b>" + } + var trace3 = { + y: getSampleVals(fullSampleLists[0]), + type: 'violin', + points: 'none', + box: { + visible: true + }, + line: { + color: 'green', + }, + meanline: { + visible: true + }, + name: "<b>" + sampleGroupList[0] + "</b>", + x0: "<b>" + sampleGroupList[0] + "</b>" + } + root.violin_data = [trace1, trace2, trace3] +} else { + root.violin_layout['width'] = 320; + root.violin_layout['height'] = 400; + root.violin_data = [ + { + y: getSampleVals(fullSampleLists[0]), + type: 'violin', + points: 'none', + box: { + visible: true + }, + meanline: { + visible: true + }, + name: "<b>Trait " + js_data.trait_id + "</b>", + x0: "<b>density</b>" + } + ] +} + +$('.violin_plot_tab').click(function() { + updateViolinPlot(); +}); + +if (getSampleVals(sampleLists[0]).length < 256) { + $('.bar_chart_samples_group').change(function() { + root.stats_group = $(this).val(); + return updateBarChart(); + }); + root.bar_sort = "name" +} +$('.sort_by_name').click(function() { + root.bar_sort = "name" + return updateBarChart(); +}); +$('.sort_by_value').click(function() { + root.bar_sort = "value" + return updateBarChart(); +}); + +root.prob_plot_group = 'samples_primary'; +$('.prob_plot_samples_group').val(root.prob_plot_group); +$('.prob_plot_tab').click(function() { + return updateProbPlot(); +}); +$('.prob_plot_samples_group').change(function() { + root.prob_plot_group = $(this).val(); + return updateProbPlot(); +}); + +function isEmpty( el ){ + return !$.trim(el.html()) +} + +$('.stats_panel').click(function() { + if (isEmpty($('#stats_table'))){ + makeTable(); + editDataChange(); + } else { + editDataChange(); + } +}); + +$('#block_by_index').click(function(){ + blockByIndex(); + editDataChange(); +}); + +$('#filter_by_study').click(function(){ + filter_by_study(); + editDataChange(); +}) + +$('#filter_by_value').click(function(){ + filter_by_value(); + editDataChange(); +}) + +$('.edit_sample_value').change(function() { + editDataChange(); +}); + +$('#exclude_group').click(editDataChange); +$('#blockOutliers').click(editDataChange); +$('#reset').click(editDataChange); +$('#qnorm').click(editDataChange); +$('#normalize').click(editDataChange); + +Number.prototype.countDecimals = function () { + if(Math.floor(this.valueOf()) === this.valueOf()) return 0; + return this.toString().split(".")[1].length || 0; +} diff --git a/gn2/wqflask/static/new/javascript/show_trait_mapping_tools.js b/gn2/wqflask/static/new/javascript/show_trait_mapping_tools.js new file mode 100644 index 00000000..a4e6dafc --- /dev/null +++ b/gn2/wqflask/static/new/javascript/show_trait_mapping_tools.js @@ -0,0 +1,329 @@ +var block_outliers, composite_mapping_fields, do_ajax_post, get_progress, mapping_method_fields, open_mapping_results, outlier_text, showalert, submit_special, toggle_enable_disable, update_time_remaining; + +update_time_remaining = function(percent_complete) { + var minutes_remaining, now, period, total_seconds_remaining; + now = new Date(); + period = now.getTime() - root.start_time; + console.log("period is:", period); + if (period > 8000) { + total_seconds_remaining = (period / percent_complete * (100 - percent_complete)) / 1000; + minutes_remaining = Math.round(total_seconds_remaining / 60); + if (minutes_remaining < 3) { + return $('#time_remaining').text(Math.round(total_seconds_remaining) + " seconds remaining"); + } else { + return $('#time_remaining').text(minutes_remaining + " minutes remaining"); + } + } +}; + +get_progress = function() { + var params, params_str, temp_uuid, url; + console.log("temp_uuid:", $("#temp_uuid").val()); + temp_uuid = $("#temp_uuid").val(); + params = { + key: temp_uuid + }; + params_str = $.param(params); + url = "/get_temp_data?" + params_str; + console.log("url:", url); + $.ajax({ + type: "GET", + url: url, + success: (function(_this) { + return function(progress_data) { + var percent_complete; + percent_complete = progress_data['percent_complete']; + console.log("in get_progress data:", progress_data); + $('#marker_regression_progress').css("width", percent_complete + "%"); + if (root.start_time) { + if (!isNaN(percent_complete)) { + return update_time_remaining(percent_complete); + } + } else { + return root.start_time = new Date().getTime(); + } + }; + })(this) + }); + return false; +}; + +block_outliers = function() { + return $('.outlier').each((function(_this) { + return function(_index, element) { + return $(element).find('.trait_value_input').val('x'); + }; + })(this)); +}; + +do_ajax_post = function(url, form_data) { + $.ajax({ + type: "POST", + url: url, + data: form_data, + error: (function(_this) { + return function(xhr, ajaxOptions, thrownError) { + alert("Sorry, an error occurred"); + console.log(xhr); + clearInterval(_this.my_timer); + $('#progress_bar_container').modal('hide'); + $('#static_progress_bar_container').modal('hide'); + return $("body").html("We got an error."); + }; + })(this), + success: (function(_this) { + return function(data) { + clearInterval(_this.my_timer); + $('#progress_bar_container').modal('hide'); + $('#static_progress_bar_container').modal('hide'); + return open_mapping_results(data); + }; + })(this) + }); + console.log("settingInterval"); + this.my_timer = setInterval(get_progress, 1000); + return false; +}; + +open_mapping_results = function(data) { + return $.colorbox({ + html: data, + href: "#mapping_results_holder", + height: "90%", + width: "90%", + onComplete: (function(_this) { + return function() { + var filename, getSvgXml; + root.create_lod_chart(); + filename = "lod_chart_" + js_data.this_trait; + getSvgXml = function() { + var svg; + svg = $("#topchart").find("svg")[0]; + return (new XMLSerializer).serializeToString(svg); + }; + $("#exportform > #export").click(function() { + var form, svg_xml; + svg_xml = getSvgXml(); + form = $("#exportform"); + form.find("#data").val(svg_xml); + form.find("#filename").val(filename); + return form.submit(); + }); + return $("#exportpdfform > #export_pdf").click(function() { + var form, svg_xml; + svg_xml = getSvgXml(); + form = $("#exportpdfform"); + form.find("#data").val(svg_xml); + form.find("#filename").val(filename); + return form.submit(); + }); + }; + })(this) + }); +}; + +outlier_text = "One or more outliers exist in this data set. Please review values before mapping. Including outliers when mapping may lead to misleading results."; +showalert = function(message, alerttype) { + return $('#outlier_alert_placeholder').append('<div id="mapping_alert" class="alert ' + alerttype + '"><a class="close" data-dismiss="alert">�</a><span>' + message + '</span></div>'); +}; + +$('#suggestive').hide(); + +$('input[name=display_all]').change((function(_this) { + return function() { + if ($('input[name=display_all]:checked').val() === "False") { + return $('#suggestive').show(); + } else { + return $('#suggestive').hide(); + } + }; +})(this)); + +// This is a list of inputs to be passed to the loading page, since not all inputs on the trait page are relevant to mapping +var mapping_input_list = ['temp_uuid', 'trait_id', 'dataset', 'tool_used', 'form_url', 'method', + 'transform', 'trimmed_markers', 'selected_chr', 'chromosomes', 'mapping_scale', + 'sample_vals', 'vals_hash', 'score_type', 'suggestive', 'significant', + 'num_perm', 'permCheck', 'perm_output', 'perm_strata', 'categorical_vars', + 'num_bootstrap', 'bootCheck', 'bootstrap_results', 'LRSCheck', 'covariates', + 'maf', 'use_loco', 'manhattan_plot', 'control_marker', 'do_control', + 'genofile', 'pair_scan', 'startMb', 'endMb', 'graphWidth', 'lrsMax', + 'additiveCheck', 'showSNP', 'showGenes', 'viewLegend', 'haplotypeAnalystCheck', + 'mapmethod_rqtl', 'mapmodel_rqtl', 'temp_trait', 'group', 'species', + 'reaper_version', 'primary_samples'] + +$(".rqtl-geno-tab, #rqtl_geno_compute").on("click", (function(_this) { + return function() { + if ($(this).hasClass('active') || $(this).attr('id') == "rqtl_geno_compute"){ + var form_data, url; + url = "/loading"; + $('input[name=method]').val("rqtl_geno"); + $('input[name=pair_scan]').val("false"); + $('input[name=selected_chr]').val($('#chr_rqtl_geno').val()); + $('input[name=mapping_scale]').val($('#scale_rqtl_geno').val()); + $('input[name=genofile]').val($('#genofile_rqtl_geno').val()); + $('input[name=mapmodel_rqtl]').val($('#mapmodel_rqtl_geno').val()); + $('input[name=mapmethod_rqtl]').val($('#mapmethod_rqtl_geno').val()); + $('input[name=num_perm]').val($('input[name=num_perm_rqtl_geno]').val()); + $('input[name=categorical_vars]').val(js_data.categorical_vars) + $('input[name=manhattan_plot]').val($('input[name=manhattan_plot_rqtl]:checked').val()); + $('input[name=control_marker]').val($('input[name=control_rqtl_geno]').val()); + $('input[name=do_control]').val($('input[name=do_control_rqtl]:checked').val()); + $('input[name=tool_used]').val("Mapping"); + $('input[name=form_url]').val("/run_mapping"); + $('input[name=wanted_inputs]').val(mapping_input_list.join(",")); + return submit_special(url); + } else { + return true + } + }; +})(this)); + +$(".rqtl-pair-tab, #rqtl_pair_compute").on("click", (function(_this) { + return function() { + if ($(this).hasClass('active') || $(this).attr('id') == "rqtl_pair_compute"){ + var form_data, url; + url = "/loading"; + $('input[name=method]').val("rqtl_geno"); + $('input[name=pair_scan]').val("true"); + $('input[name=genofile]').val($('#genofile_rqtl_pair').val()); + $('input[name=mapmodel_rqtl]').val($('#mapmodel_rqtl_pair').val()); + $('input[name=mapmethod_rqtl]').val($('#mapmethod_rqtl_pair').val()); + $('input[name=num_perm]').val($('input[name=num_perm_rqtl_pair]').val()); + $('input[name=categorical_vars]').val(js_data.categorical_vars) + $('input[name=control_marker]').val($('input[name=control_rqtl_pair]').val()); + $('input[name=do_control]').val($('input[name=do_control_rqtl_pair]:checked').val()); + $('input[name=tool_used]').val("Mapping"); + $('input[name=form_url]').val("/run_mapping"); + $('input[name=wanted_inputs]').val(mapping_input_list.join(",")); + return submit_special(url); + } else { + return true + } + }; +})(this)); + +$(".gemma-tab, #gemma_compute").on("click", (function(_this) { + return function() { + if ($(this).hasClass('active') || $(this).attr('id') == "gemma_compute"){ + var form_data, url; + url = "/loading"; + $('input[name=method]').val("gemma"); + $('input[name=mapping_scale]').val('physic'); + $('input[name=selected_chr]').val($('#chr_gemma').val()); + $('input[name=num_perm]').val(0); + $('input[name=genofile]').val($('#genofile_gemma').val()); + $('input[name=maf]').val($('input[name=maf_gemma]').val()); + $('input[name=tool_used]').val("Mapping"); + $('input[name=form_url]').val("/run_mapping"); + $('input[name=wanted_inputs]').val(mapping_input_list.join(",")); + return submit_special(url); + } else { + return true + } + }; +})(this)); + +$(".reaper-tab, #interval_mapping_compute").on("click", (function(_this) { + return function() { + if ($(this).hasClass('active') || $(this).attr('id') == "interval_mapping_compute"){ + var form_data, url; + console.log("In interval mapping"); + url = "/loading"; + $('input[name=method]').val("reaper"); + $('input[name=selected_chr]').val($('#chr_reaper').val()); + $('input[name=mapping_scale]').val($('#scale_reaper').val()); + $('input[name=genofile]').val($('#genofile_reaper').val()); + $('input[name=num_perm]').val($('input[name=num_perm_reaper]').val()); + $('input[name=control_marker]').val($('input[name=control_reaper]').val()); + $('input[name=do_control]').val($('input[name=do_control_reaper]:checked').val()); + $('input[name=manhattan_plot]').val($('input[name=manhattan_plot_reaper]:checked').val()); + $('input[name=mapping_display_all]').val($('input[name=display_all_reaper]')); + $('input[name=suggestive]').val($('input[name=suggestive_reaper]')); + $('input[name=tool_used]').val("Mapping"); + $('input[name=form_url]').val("/run_mapping"); + $('input[name=wanted_inputs]').val(mapping_input_list.join(",")); + return submit_special(url); + } else { + return true + } + }; +})(this)); + +$("#interval_mapping_compute, #gemma_compute, rqtl_geno_compute").on("mouseover", (function(_this) { + return function() { + if ($(".outlier").length && $(".outlier-alert").length < 1) { + return showalert(outlier_text, "alert-success outlier-alert"); + } + }; +})(this)); + +composite_mapping_fields = function() { + return $(".composite_fields").toggle(); +}; + +mapping_method_fields = function() { + return $(".mapping_method_fields").toggle(); +}; + +$("#use_composite_choice").change(composite_mapping_fields); + +$("#mapping_method_choice").change(mapping_method_fields); + +$("#mapmodel_rqtl_geno,#mapmodel_rqtl_pair").change(function() { + if ($(this).val() == "np"){ + $("#mapmethod_rqtl_geno").attr('disabled', 'disabled'); + $("#mapmethod_rqtl_geno").css('background-color', '#CCC'); + $("#missing_geno,#missing_geno_pair").attr('disabled', 'disabled'); + $("#missing_geno,#missing_geno_pair").css('background-color', '#CCC'); + } else { + $("#mapmethod_rqtl_geno").removeAttr('disabled'); + $("#mapmethod_rqtl_geno").css('background-color', '#FFF'); + $("#missing_geno,#missing_geno_pair").removeAttr('disabled'); + $("#missing_geno,#missing_geno_pair").css('background-color', '#FFF'); + } +}); + +$("#mapmethod_rqtl_geno,#mapmethod_rqtl_pair").change(function() { + if ($(this).val() == "mr"){ + $("#missing_geno_div,#missing_geno_pair_div").css('display', 'block'); + } else { + $("#missing_geno_div,#missing_geno_pair_div").css('display', 'none'); + } +}); + +$("li.mapping-tab").click(function() { + if ($(this).hasClass("rqtl")){ + $(".rqtl_description").css("display", "block"); + } else { + $(".rqtl_description").css("display", "none"); + } +}); + +toggle_enable_disable = function(elem) { + return $(elem).prop("disabled", !$(elem).prop("disabled")); +}; + +$("#choose_closet_control").change(function() { + return toggle_enable_disable("#control_locus"); +}); + +$("#display_all_lrs").change(function() { + return toggle_enable_disable("#suggestive_lrs"); +}); + +$('#genofile_rqtl_geno').change(function() { + geno_location = $(this).children("option:selected").val().split(":")[0] + $('#scale_rqtl_geno').empty() + the_scales = js_data.scales_in_geno[geno_location] + for (var i = 0; i < the_scales.length; i++){ + $('#scale_rqtl_geno').append($("<option></option>").attr("value", the_scales[i][0]).text(the_scales[i][1])); + } +}); +$('#genofile_reaper').change(function() { + geno_location = $(this).children("option:selected").val().split(":")[0] + $('#scale_reaper').empty() + the_scales = js_data.scales_in_geno[geno_location] + for (var i = 0; i < the_scales.length; i++){ + $('#scale_reaper').append($("<option></option>").attr("value", the_scales[i][0]).text(the_scales[i][1])); + } +}); diff --git a/gn2/wqflask/static/new/javascript/stats.js b/gn2/wqflask/static/new/javascript/stats.js new file mode 100644 index 00000000..6c443ab3 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/stats.js @@ -0,0 +1,176 @@ +// Generated by CoffeeScript 1.8.0 +var Stats, bxd_only; + +Stats = (function() { + function Stats(the_values) { + this.the_values = the_values; + } + + Stats.prototype.add_value = function(value) { + return this.the_values.push(value); + }; + + Stats.prototype.n_of_samples = function() { + return this.the_values.length; + }; + + Stats.prototype.sum = function() { + var total, value, _i, _len, _ref; + total = 0; + _ref = this.the_values; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + value = _ref[_i]; + total += value; + } + return total; + }; + + Stats.prototype.mean = function() { + return this.sum() / this.n_of_samples(); + }; + + Stats.prototype.median = function() { + var is_odd, median_position, the_values_sorted; + is_odd = this.the_values.length % 2; + median_position = Math.floor(this.the_values.length / 2); + the_values_sorted = this.the_values.sort(function(a, b) { + return a - b; + }); + if (is_odd) { + return the_values_sorted[median_position]; + } else { + return (the_values_sorted[median_position] + the_values_sorted[median_position - 1]) / 2; + } + }; + + Stats.prototype.std_dev = function() { + var step_a, step_b, sum, value, _i, _len, _ref; + sum = 0; + _ref = this.the_values; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + value = _ref[_i]; + step_a = Math.pow(value - this.mean(), 2); + sum += step_a; + } + step_b = sum / this.the_values.length; + return Math.sqrt(step_b); + }; + + Stats.prototype.std_error = function() { + return this.std_dev() / Math.sqrt(this.n_of_samples()); + }; + + Stats.prototype.min = function() { + return Math.min.apply(Math, this.the_values); + }; + + Stats.prototype.max = function() { + return Math.max.apply(Math, this.the_values); + }; + + Stats.prototype.range = function() { + if (js_data.dataset_type == "ProbeSet"){ + if (js_data.data_scale == "linear_positive"){ + return Math.log2(this.max()) - Math.log2(this.min()); + } else { + return this.max() - this.min() + } + } else { + return this.max() - this.min() + } + }; + + Stats.prototype.range_fold = function() { + if (js_data.dataset_type == "ProbeSet"){ + return Math.pow(2, this.range()); + } else { + return this.range() + } + }; + + Stats.prototype.interquartile = function() { + var iq, length, q1, q3; + length = this.the_values.length; + if (js_data.dataset_type == "ProbeSet" && js_data.data_scale == "linear_positive") { + q1 = Math.log2(this.the_values[Math.floor(length * .25)]); + q3 = Math.log2(this.the_values[Math.floor(length * .75)]); + } else { + q1 = this.the_values[Math.floor(length * .25)]; + q3 = this.the_values[Math.floor(length * .75)]; + } + iq = q3 - q1; + if (js_data.dataset_type == "ProbeSet") { + return Math.pow(2, iq); + } else { + return iq; + } + }; + + Stats.prototype.skewness = function() { + var len = this.the_values.length, + delta = 0, + delta_n = 0, + term1 = 0, + N = 0, + mean = 0, + M2 = 0, + M3 = 0, + g; + + for ( var i = 0; i < len; i++ ) { + N += 1; + + delta = this.the_values[ i ] - mean; + delta_n = delta / N; + + term1 = delta * delta_n * (N-1); + + M3 += term1*delta_n*(N-2) - 3*delta_n*M2; + M2 += term1; + mean += delta_n; + } + // Calculate the population skewness: + g = Math.sqrt( N )*M3 / Math.pow( M2, 3/2 ); + + // Return the corrected sample skewness: + return Math.sqrt( N*(N-1))*g / (N-2); + }; + + Stats.prototype.kurtosis = function() { + var len = this.the_values.length, + delta = 0, + delta_n = 0, + delta_n2 = 0, + term1 = 0, + N = 0, + mean = 0, + M2 = 0, + M3 = 0, + M4 = 0, + g; + + for ( var i = 0; i < len; i++ ) { + N += 1; + + delta = this.the_values[ i ] - mean; + delta_n = delta / N; + delta_n2 = delta_n * delta_n; + + term1 = delta * delta_n * (N-1); + + M4 += term1*delta_n2*(N*N - 3*N + 3) + 6*delta_n2*M2 - 4*delta_n*M3; + M3 += term1*delta_n*(N-2) - 3*delta_n*M2; + M2 += term1; + mean += delta_n; + } + // Calculate the population excess kurtosis: + g = N*M4 / (M2*M2) - 3; + //Return the corrected sample excess kurtosis: + return (N-1) / ( (N-2)*(N-3) ) * ( (N+1)*g + 6 ); + }; + + return Stats; + +})(); + +window.Stats = Stats; diff --git a/gn2/wqflask/static/new/javascript/table_functions.js b/gn2/wqflask/static/new/javascript/table_functions.js new file mode 100644 index 00000000..62888cd9 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/table_functions.js @@ -0,0 +1,90 @@ +recheckRows = function(theTable, checkedRows){ + // This is meant to recheck checkboxes after columns are resized + checkCells = theTable.column(0).nodes().to$(); + for (let i = 0; i < checkCells.length; i++) { + if (checkedRows.includes(i)){ + checkCells[i].childNodes[0].checked = true; + } + } + + checkRows = traitTable.rows().nodes(); + for (let i =0; i < checkRows.length; i++) { + if (checkedRows.includes(i)){ + checkRows[i].classList.add("selected") + } + } +} + +getCheckedRows = function(tableId){ + let checkedRows = [] + $("#" + tableId + " input.checkbox").each(function(index){ + if ($(this).prop("checked") == true){ + checkedRows.push(index); + } + }); + + return checkedRows +} + +function setUserColumnsDefWidths(tableId, columnDefs) { + var userColumnDef; + + // Get the settings for this table from localStorage + var userColumnDefs = JSON.parse(localStorage.getItem(tableId)) || []; + + if (userColumnDefs.length === 0 ) return; + + columnDefs.forEach( function(columnDef) { + // Check if there is a width specified for this column + userColumnDef = userColumnDefs.find( function(column) { + return column.targets === columnDef.targets; + }); + + // If there is, set the width of this columnDef in px + if ( userColumnDef ) { + + columnDef.sWidth = userColumnDef.width + 'px'; + columnDef.width = userColumnDef.width + 'px'; + + $('.toggle-vis').each(function(){ + if ($(this).attr('data-column') == columnDef.targets){ + if ($(this).hasClass("active")){ + columnDef.bVisible = false + } else { + columnDef.bVisible = true + } + } + }) + } + }); + + return columnDefs +} + +function saveColumnSettings(tableId, traitTable) { + var userColumnDefs = JSON.parse(localStorage.getItem(tableId)) || []; + var width, header, existingSetting; + + traitTable.columns().every( function ( targets ) { + // Check if there is a setting for this column in localStorage + existingSetting = userColumnDefs.findIndex( function(column) { return column.targets === targets;}); + + // Get the width of this column + header = this.header(); + width = $(header).width(); + + if ( existingSetting !== -1 ) { + // Update the width + userColumnDefs[existingSetting].width = width; + } else { + // Add the width for this column + userColumnDefs.push({ + targets: targets, + width: width, + }); + } + }); + + // Save (or update) the settings in localStorage + localStorage.setItem(tableId, JSON.stringify(userColumnDefs)); +} diff --git a/gn2/wqflask/static/new/javascript/thank_you.js b/gn2/wqflask/static/new/javascript/thank_you.js new file mode 100644 index 00000000..deb68211 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/thank_you.js @@ -0,0 +1,6 @@ +// Generated by CoffeeScript 1.8.0 +$(function() { + console.log("Starting transform"); + $('#login_out').text('Sign out').attr('href', '/logout').removeClass('modalize'); + return console.log("Transformed to sign out I hope"); +}); diff --git a/gn2/wqflask/static/new/javascript/typeahead_rn6.json b/gn2/wqflask/static/new/javascript/typeahead_rn6.json new file mode 100644 index 00000000..1889d8a2 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/typeahead_rn6.json @@ -0,0 +1 @@ +var rat_genes = ["A2ml1","A3galt2","A1cf","AA926063","A2m","A4gnt","A4galt","A1bg","Aadacl4","Aadac","Aacs","Aadat","Aaas","Aadacl2","Aadacl3","Aagab","Aak1","Aaed1","Aanat","Aamdc","Aard","Aasdh","Aar2","Aasdhppt","Aass","Aars","Aarsd1","Aars2","Abca1","Aatk","Abca14","Abca13","Abca15","Aatf","Aamp","Abca12","Abca16","Abca3","Abat","Abca17","Abca5","Abca8","Abca2","Abca9","Abca4","Abca8a","Abcb1a","Abcb11","Abcb10","Abca6","Abca7","Abcb1b","Abcb5","Abcb4","Abcb7","Abcb8","Abcb6","Abcc1","Abcb9","Abcc3","Abcc10","Abcc12","Abcc4","Abcc6","Abcc2","Abcc5","Abcd3","Abcd4","Abcc9","Abcd1","Abcf1","Abce1","Abcf3","Abcg2","Abcg1","Abcg3l1","Abcf2","Abcg4","Abcg5","Abcg3l3","Abcg3","Abcg3l4","Abcg3l2","Abcd2","Abhd11os","Abhd10","Abcg8","Abcc8","Abhd12","Abhd12b","Abhd1","Abhd13","Abhd14a","Abhd15","Abhd11","Abhd16b","Abhd17a","Abhd17b","Abhd16a","Abhd14b","Abhd2","Abhd3","Abhd4","Abhd17c","Abhd5","Abhd8","Abhd6","Abi2","Abi3","Abhd18","Ablim1","Abl2","Abl1","Abi1","Ablim3","Ablim2","Abo2","Abo3","Abra","Abr","Abi3bp","Abracl","Abraxas1","Abt1","Abtb1","Ac1576","Acaa1b","Abraxas2","Abo","Acacb","Abtb2","Acad11","Acad8","Acad9","Acaa2","Acadl","Acadm","Acadsb","Acads","Acadvl","Acaca","Acap1","Acap3","Acat1","Acat2","Acad10","Acaa1a","Acap2","Acat2l1","Acbd4","Acbd5","Acbd7","Acbd6","Accs","Acbd3","Acd","Acan","Acer1","Acer2","Accsl","Ace2","Ackr1","Ache","Ackr2","Ackr3","Acin1","Ackr4","Acnat1","Acmsd","Acly","Acnat2","Acer3","Ace3","Ace","Acot1","Aco1","Acot13","Aco2","Acot12","Acot4","Acot2","Acot6","Acot5","Acot11","Acot8","Acot9","Acod1","Acot3","Acox2","Acot7","Acox1","Acox3","Acp2","Acp1","Acp4","Acp6","Acp5","Acp7","Acrv1","Acrbp","Acsbg1","Acr","Acpp","Acsbg2","Acoxl","Acsf2","Acsf3","Acsl3","Acsl5","Acsl1","Acsl4","Acsl6","Acsm1","Acsm4","Acss1","Acsm2","Acp1-ps1","Acsm5","Acss2","Acsm3","Acss3","Actg1","Acta1","Actc1","Actg2","Actbl2","Actb","Acta2","Actl7a","Actl6a","Actl6b","Actl7b","Actl9","Actl9b","Actl11","Actn3","Actn2","Actl10","Actn1","Actr10","Actn4","Actr1a","Actr1b","Actr3b","Actr2","Actr5","Actr6","Actr3","Actr8","Actrt1","Actrt2","Actrt3","Acvr1b","Acvr1c","Acvr2a","Acvr2b","Acvrl1","Acy3","Acy1","Acyp1","Acyp2-ps1","Acyp2","Adal","Ada","Adad1","Adad2","Adam11","Adam10","Acvr1","Adam12","Adam18","Adam15","Adam19","Adam17","Adam1a","Adam20","Adam2","Adam25","Adam26a","Adam23","Adam24","Adam22","Adam32","Adam34l","Adam33","Adam34","Adam3a","Adam21","Adam4l1","Adam4","Adam6","Adam5","Adam28","Adam30","Adam7","Adamdec1","Adam9","Adam8","Adamts12","Adamts1","Adamts10","Adamts15","Adamts13","Adamts14","Adamts16","Adamts17","Adamts18","Adamts19","Adamts2","Adamts20","Adamts3","Adamts4","Adamts8","Adamts6","Adamts5","Adamts7","Adamts9","Adamtsl1","Adamtsl2","Adamtsl3","Adamtsl5","Adamtsl4","Adap2","Adap1","Adar","Adat1","Adarb2","Adat2","Adarb1","Adck1","Adck2","Adck5","Adcy1","Adcy2","Adcy10","Adcy3","Adcy4","Adcy5","Adcy6","Adcy7","Adcy9","Adcy8","Adat3","Adcyap1r1","Adcyap1","Add1","Add2","Adgra1","Adgb","Add3","Adgra2","Adgra3","Adgrb1","Adgrb2","Adgrd1","Adgrb3","Adgre4","Adgre1","Adgrf1","Adgre5","Adgrf2","Adgrf3","Adgrf4","Adgrg1","Adgrg3","Adgrf5","Adgrg2","Adgrg4","Adgrg7","Adgrg5","Adgrg6","Adgrl1","Adgrl4","Adgrl2","Adgrv1","Adgrl3","Adh4","Adh1","Adh6","Adh6a","Adh7","Adhfe1","Adi1","Adh5","Adipor1","Adipor2","Adm2","Adig","Adk","Adnp","Adm","Ado","Adipoq","Adnp2","Adora2b","Adora3","Adpgk","Adprh","Adprhl2","Adora1","Adprhl1","Adora2a","Adprm","Adra1b","Adra1d","Adra2a","Adra2c","Adra2b","Adra1a","Adrb1","Adrm1","Adtrp","Adsl","Adss","Adssl1","Adrb3","Adrb2","Aebp1","Aebp2","Aen","Afap1l1","Afap1","Afap1l2","Afdn","Aff1","Aff2","Afg1l","Aff3","Aff4","Afg3l1","Aff1-as1","Afg3l2","Afmid","Aftph","Afm","Afp","Aga","Agap1","Agap2","Agap3","Agbl1","Agbl3","Agbl2","Agbl4","Agbl5","Agfg1-ps1","Agfg1","Agfg2","Aggf1","Agk","Agl","Agmat","Aes","Agmo","Ago1","Ago3","Ago2","Ager","Ago4","Agpat2","Agpat1","Agpat3","Agpat4","Agpat5","Agr2","Agps","Agr3","Agrp","Agtpbp1","Agrn","Agtrap","Agtr1b","Agt","Agtr2","Agxt","Agxt2","Ahctf1","Ahcyl1","Ahcy","Ahdc1","Ahcyl2","Ahnak2","Ahnak","Agtr1a","Ahi1","Ahrr","Ahsa2","Ahsa1","Ahsp","Ahr","Aicda","Aif1l","Aida","Aifm2","Ahsg","Aifm1","Aifm3","Aig1","Aim2","Aimp1","Aipl1","Aip","Aimp2","Aire","Ajap1","Ajm1","Ajuba","Ak3","Ak1","Ak2","Aif1","Ak5","Ak4","Ak6","Ak9","Ak8","Ak7","Akap10","Akap1","Akap11","Akap14","Akap17a","Akap12","Akap13","Akap17b","Akap2","Akap3","Akain1","Akap4","Akap5","Akap7","Akap8","Akap6","Akap8l","Akirin1","Akip1","Akap9","Akirin2","Akna","Aknad1","Akr1a1","Akp3","Akr1b1-ps1","Akr1b1-ps2","Akr1b1-ps3","Akr1b7","Akr1b10","Akr1b1","Akr1b8","Akr1c1","Akr1c12","Akr1c12l1","Akr1c13","Akr1c19","Akr1c14","Akr1c2","Akr1cl","Akr1d1","Akr1c3","Akr1e2","Akr7a2","Akr7a3","Akt1s1","Aktip","Akt3","Akt2","Alas1","Alad","Alas2","Alcam","Aldh16a1","Aldh1a1","Aldh1a2","Aldh1a3","Akt1","Aldh1a7","Alb","Aldh1b1","Aldh1l1","Aldh1l2","Aldh18a1","Aldh3b2","Aldh3b1","Aldh3a1","Aldh5a1","Aldh8a1","Aldh7a1","Aldh6a1","Aldh3a2","Aldh9a1","Aldh2","Aldoart2","Alg1","Aldoc","Aldob","Aldoa","Alg10","Alg11","Alg12","Alg14","Alg2","Alg13","Alg3","Alg5","Alg8","Alg6","Alg9","Alk","Alkal1","Alkbh1","Alkbh2","Alkal2","Aldh4a1","Alkbh3","Alkbh4","Alkbh5","Alkbh6","Alkbh7","Alkbh8","Allc","Alms1","Alox12b","Alox12e","Alox12","Alox15b","Alox15","Aloxe3","Alox5ap","Alox5","Alpi","Alpk1","Alpk2","Alpk3","Alppl2","Alpp","Alpl","Als2","Als2cr12","Als2cl","Alx3","Alx4","Alyref","Amacr","Ambn","Ambp","Ambra1","Amd1-ps1","Amd1-ps2","Amd1","Amd1-ps3","Amdhd1","Amer1","Amdhd2","Amer2","Amer3","Amelx","Alx1","Amfr","Amh","Amhr2","Amigo3","Amigo1","Amigo2","Ammecr1","Ammecr1l","Amn1","Amotl1","Amot","Amotl2","Amn","Ampd1","Ampd3","Ampd2","Amtn","Amph","Amt","Amy2-ps1","Amy2-ps2","Amy1a","Amy2a3","Amz1","Amz2","Anapc10","Anapc11","Anapc13","Anapc1","Anapc15","Anapc2","Anapc16","Anapc4","Anapc7","Anapc5","Andpro","Ang2","Ang","Angel1","Angel2","Angpt4","Angptl1","Angptl2","Angptl3","Angptl4","Angpt2","Angptl6","Angptl7","Ank1","Ank2","Ankar","Ankdd1a","Ankdd1b","Angptl8","Angpt1","Ank3","Ankfn1","Ankef1","Ankfy1","Ankh","Ankib1","Ankhd1","Ankk1","Ankle1","Ankmy1","Ankle2","Ankmy2","Ankra2","Ankrd10","Ankrd11","Ankrd1","Ankrd13a","Ankrd12","Ankrd13b","Ankrd13d","Ankrd13c","Ankrd16","Ankrd22","Ankrd2","Ankrd23","Ankrd24","Ankrd17","Ankrd26","Ankrd27","Ankrd28","Ankrd30a","Ankrd31","Ankrd33b","Ankrd34a","Ankrd29","Ankrd34c","Ankrd35","Ankrd36","Ankrd37","Ankrd39","Ankrd40","Ankrd42","Ankrd44","Ankrd45","Ankrd33","Ankrd46","Ankrd34b","Ankrd49","Ankrd50","Ankrd53","Ankrd52","Ankrd54","Ankrd6","Ankrd60","Ankrd55","Ankrd63","Ankrd7","Ankrd66","Ankrd9","Ankrd65","Anks1a","Anks3","Anks4b","Anks1b","Ankrd61","Ankub1","Ankzf1","Anlnl1","Anln","Ano10","Ano1","Ano3","Ano2","Ano4","Anks6","Ano6","Ano8","Ano7","Ano5","Ano9","Anp32a","Anp32e","Anp32b","Anos1","Antxr1","Antxrl","Antxr2","Anxa10","Anxa11","Anxa2-ps1","Anxa13","Anxa1","Anpep","Anxa3","Anxa2","Anxa4","Anxa6","Anxa8","Anxa5","Anxa7","Aoc2-ps1","Anxa9","Aoah","Aoc1","Aox2","Aoc3","Aox1","Ap1ar","Aox3","Ap1b1","Ap1g1","Aox4","Ap1g2","Ap1m2","Ap1m1","Ap1s1","Ap1s2","Ap1s3","Ap2a1","Ap2a2","Ap2s1","Ap2b1","Ap3b1","Ap3b2","Ap3d1","Ap3m1","Ap3m2","Ap3s1","Ap3s2","Ap4b1","Ap4e1","Ap4m1","Ap5b1","Ap4s1","Ap2m1","Ap5m1","Ap5s1","Ap5z1","Apba3","Apba1","Apba2","Apaf1","Apbb1ip","Apbb3","Apbb2","Apbb1","Apcdd1","Apbh","Apc2","Apcdd1l","Apeg3","Apcs","Apeh","Apc","Apex2l1","Apex2","Aph1a","Apex1","Aph1b","Api5","Apip","Aplf","Aplnr","Apln","Aplp1","Apmap","Aplp2","Apoa2","Apoa4","Apoa5","Apobec1","Apoa1","Apobec2","Apobec3b","Apobec4","Apobr","Apob","Apoc1","Apoc4","Apoc2","Apoc3","Apod","Apof","Apol11a","Apol2","Apoh","Apol3","Apol9a","Apold1","Apom","Apon","Apoo","Apool","Apopt1","Apoe","Appl1","Appbp2","Appl2","Aptr","Aprt","Aptx","App","Aqp11","Aqp12a","Aqp1","Aqp3","Aqp5","Aqp2","Aqp6","Aqp7","Aqp8","Aqr","Aqp9","Aqp4","Araf","Arap2","Arap1","Arcn1","Arap3","Areg","Ar","Arc","Arf2","Arel1","Arf5","Arf1","Arf4","Arf3","Arf6","Arfgap1","Arfgap2","Arfgap3","Arfgef1","Arfgef2","Arfgef3","Arfip1","Arfip2","Arfrp1","Arg2","Arglu1","Arhgap11a","Arg1","Arhgap1","Arhgap10","Arhgap12","Arhgap20","Arhgap18","Arhgap19","Arhgap17","Arhgap22","Arhgap21","Arhgap23","Arhgap25","Arhgap24","Arhgap27","Arhgap15","Arhgap28","Arhgap26","Arhgap30","Arhgap29","Arhgap31","Arhgap32","Arhgap36","Arhgap39","Arhgap40","Arhgap35","Arhgap4","Arhgap33","Arhgap42","Arhgap44","Arhgap6","Arhgap45","Arhgap5","Arhgap8","Arhgap9","Arhgdib","Arhgdia","Arhgdig","Arhgef10l","Arhgef10","Arhgef1","Arhgef12","Arhgef11","Arhgef15","Arhgef16","Arhgef17","Arhgef18","Arhgef19","Arhgef2","Arhgef25","Arhgef26","Arhgef28","Arhgef3","Arhgef33","Arhgef38","Arhgef37","Arhgef4","Arhgef39","Arhgef5","Arhgef40","Arhgef6","Arhgef9","Arhgef7","Arid1a","Arid2","Arid3b","Arid3a","Arid4a","Arid1b","Arid4b","Arid5b","Arid5a","Arih1","Arih2","Arid3c","Arl10","Arl1","Arl11","Arl13a","Arl14","Arl13b","Arl14ep","Arl14epl","Arl16","Arl15","Arih2os","Arl2","Arl2bp","Arl4a","Arl4c","Arl3","Arl4d","Arl5a","Arl5c","Arl5b","Arl6","Arl2-ps1","Arl6ip1","Arl6ip5","Arl6ip4","Arl6ip6","Arl8a","Arl8b","Arl9","Armc12","Armc10","Armc1","Armc2","Armc3","Armc4","Armc5","Armc6","Armc7","Armc9","Armc8","Armcx1","Armcx2","Armcx4","Armcx3","Armcx6","Armt1","Arnt2","Arnt","Arpc1a","Arntl2","Arntl","Arpc1b","Armcx5","Arpc2","Arpc3","Arpc5","Arpc4","Arpc5l-ps1","Arpc5l","Arpin","Arpp19","Arrb2-ps","Arrdc2","Arr3","Arrdc1","Arpp21","Arrb1","Arrdc3","Arrb2","Arrdc5","Arrdc4","Arsa","Arse","Arsb","Arsi","Arsg","Arsj","Arsk","Art1","Art4","Art3","Art5","Artn","Arv1","Arvcf","Arxes2","Arx","As3mt","Asah1","Asah2","Asap2","Asap1","Asap3","Art2b","Asb10","Asb1","Asb11","Asb12","Asb14","Asb13","Asb15","Asb16","Asb17","Asb18","Asb4","Asb2","Asb3","Asb6","Asb5","Asb7","Asb8","Asb9","Ascc2","Ascc3","Ascc1","Ascl3","Ascl2","Ascl4","Ascl5","Ascl1","Asf1b","Asf1a","Asgr1","Ash2l","Asgr2","Ash1l","Asic4","Asic1","Asic3","Asic2","Asic5","Asip","Asmt","Asna1","Asmtl","Asl","Aspdh","Asnsd1","Asns","Aspa","Aspg","Asphd1","Asphd2","Asprv1","Aspnl1","Aspm","Aspn","Aspscr1","Asph","Asrgl1","Aste1","Astl","Astn1","Ass1","Asxl1","Astn2","Asxl2","Asxl3","Asz1","Atad2","Atad1","Atad2b","Atad5","Atad3a","Atat1","Atcay","Ate1","Atf1","Atf2","Atf5","Atf3","Atf4","Atf6","Atf6b","Atf7","Atf7ip","Atf7ip2","Atg10","Atg101","Atg12","Atg13","Atg14","Atg16l2","Atg16l1","Atg2a","Atg2b","Atg4a","Atg3","Atg4b","Atg4c","Atg4d","Atg5","Atg9a","Atg7","Atg9b","Atl3","Atl1","Atic","Atl2","Atmin","Atoh1","Atn1","Atoh7","Atoh8","Atm","Atp10a","Atox1","Atp10d","Atp10b","Atp11a","Atp11b","Atp11c","Atp12a","Atp13a1","Atp13a2","Atp13a3","Atp13a5","Atp13a4","Atp1a4","Atp1a3","Atp1a2","Atp1a1","Atp1b2","Atp1b4","Atp1b1","Atp1b3","Atp23","Atp2a1","Atp2a3","Atp2b1","Atp2b3","Atp2a2","Atp2b2","Atp2b4","Atp2c1","Atp2c2","Atp4a","Atp4b","Atp5a1","Atp5c1","Atp5d","Atp5b","Atp5e","Atp5f1","Atp5h-ps1","Atp5g1","Atp5hl1","Atp5g2","Atp5g3","Atp5h","Atp5i","Atp5j","Atp5l","Atp5j2","Atp5s","Atp5o","Atp6ap1","Atp6ap1l","Atp6ap2","Atp6v0b","Atp6v0a1","Atp6v0a4","Atp6v0c","Atp6v0a2","Atp6v0d2","Atp6v0d1","Atp6v0e1","Atp6v0e2","Atp6v1b1","Atp6v1a","Atp6v1c1","Atp6v1b2","Atp6v1d","Atp6v1c2","Atp6v1e2","Atp6v1e1","Atp6v1f","Atp6v1g1","Atp6v1g2","Atp6v1g3","Atp6v1h","Atp7b","Atp8a1","Atp8a2","Atp8b1","Atp7a","Atp8b2","Atp8b3","Atp8b4","Atp9a","Atp8b5p","Atp9b","Atpaf1","Atpaf2","Atraid","Atpif1","Atr","Atrn","Atrnl1","Atxn1l","Atxn10","Atrx","Atxn1","Atxn3","Atxn2","Atxn2l","Atxn7","Atxn7l3","Atxn7l2","Atxn7l1","Atrip","Aup1","Aunip","Auh","Aurkaip1","Aurka","Aurkc","Auts2l1","Aurkb","Auts2l","Atxn7l3b","Auts2","Avgr1","Aven","Avil","Avl9","Avpi1","Awat1","Avpr1b","Axdnd1","Avpr1a","Avpr2","Awat2","Avp","Axin1","Axin2","Azi2","Axl","Azgp1","Azin1","Azin2","B3galnt1","B3galnt2","B3galt2","B3galt1","B3galt4","B3galt5","B3galt6","B2m","B3gat1","B3gat2","B3gat3","B3glct","B3gnt4","B3gnt5","B3gnt2","B3gnt3","B3gnt6","B3gnt8","B3gnt9","B3gnt7","B4galnt2","B3gntl1","B4galnt3","B4galnt1","B4galnt4","B4galt2","B4galt3","B4galt4","B4galt5","B4galt1","B4galt7","B4galt6","B4gat1","B9d2","Baalc","B9d1","Babam1","Baat","Babam2","Bace2","Bace1","Bach1","Bach2","Bag2","Bag3","Bag1","Bag4","Bag5","Bag5l1","Bahcc1","Bag6","Bahd1","Baiap2l1","Bad","Baiap3","Baiap2l2","Bambi","Banf1","Bak1","Banf2","Banp","Bank1","Bap1","Bard1","Barhl1","Barx1","Barhl2","Barx2","Baiap2","Batf","Basp1","Batf2","Batf3","Baz2a","Baz1a","Baz1b","Bbc3","Bbof1","Baz2b","Bbox1","Bbs10","Bbs12","Bbs1","Bbs2","Bax","Bbs4","Bbs5","Bbip1","Bbs7","Bbs9","Bc1","Bbx","Bcam","Bcap29","Bcar3","Bcap31","Bcas2","Bcar1","Bcan","Bcas1","Bcas3","Bccip","Bcdin3d","Bcat2","Bcat1","Bckdhb","Bckdha","Bche","Bckdk","Bcl10","Bcl11b","Bcl2a1","Bcl11a","Bcl2l1-ps1","Bcl2l10","Bcl2l13","Bcl2l15","Bcl2l12","Bcl2l14","Bcl2l1","Bcl2l11","Bcl3","Bcl2l2","Bcl6","Bcl6b","Bcl7a","Bcl7b","Bcl7c","Bcl9","Bcl9l","Bclaf3","Bco1","Bclaf1","Bco2","Bcor","Bcorl1","Bcr","Bcs1l","Bdh2","Bdh1","Bdkrb2","Bean1","Bdkrb1","Bdp1","Becn2","Bend2","Bend3","Begain","Bend4","Becn1","Bend5","Bcl2","Bend6","Bend7","Best2","Best1","Best4","Best3","Bet1","Bex1","Bex2","Bet1l","Bex4-ps1","Bex4","Bfar","Bfsp2","Bfsp1","Bgn","Bhlha9","Bglap","Bhlhb9","Bhlha15","Bhlhe22","Bdnf","Bex3","Bhlhe23","Bhlhe40","Bhlhe41","Bhmt","Bhmt2","Bicd1","Bicc1","Bicd2","Bicdl1","Bicdl2","Bicra","Bhmg1","Bicral","Bid","Bik","Bin2","Bin2a","Bin3","Bin1","Birc3","Birc2","Birc6","Birc7","Birc5","Bivm","Blcap","Bles03","Blk","Blmh","Blm","Blnk","Bloc1s1","Bloc1s2","Bloc1s3","Bloc1s4","Bloc1s5","Blvrb","Bloc1s6","Blzf1","Blvra","Bmf","Bmi1","Bmp10","Bmp1","Bmp2k","Bmp15","Bmp3","Bmp5","Bmp2","Bmp8a","Bmp6","Bmp8b","Bmp7","Bmper","Bmp4","Bmpr1a","Bmt2","Bmpr1b","Bms1","Bmx","Bnc1","Bmpr2","Bmyc","Bnc2","Bnip1","Bnip2","Bnip3-ps1","Bnip3l-ps1","Bnip3","Bnipl","Bod1","Bod1l1","Boc","Bola1","Bok","Bola2","Bola2-ps1","Bola2-ps2","Bola3","Boll","Bop1","Borcs6","Borcs5","Bora","Bnip3l","Borcs7","Borcs8","Bphl","Bpgm","Bpi","Bpifa1","Bpifa3","Bpifa5","Bpifa6","Bpifb2","Bpifb1","Bpifb3","Bpifb4","Bpifa2f","Bpifb5","Bpifb6","Bpnt1-ps1","Bpifc","Bpifa2","Bpnt1","Bptf","Brap","Braf","Brcc3-ps1","Brat1","Brcc3","Brca2","Brd1","Brca1","Brd3","Brd2","Brd4","Brd7","Brd9","Brdt","Brd8","Brf1","Brf2","Bri3","Bri3bp","Bricd5","Brinp1","Brinp2","Brinp3","Brk1","Brms1","Brix1","Brms1l","Brox","Brpf1","Brs3","Brpf3","Brsk2","Brsk1","Brwd3","Brwd1","Bscl2","Bsdc1","Brip1","Bsph1","Bsnd","Bsph2","Bsn","Bsg","Bst1","Bst2","Bspry","Bsx","Btbd1","Btaf1","Btbd10","Btbd11","Btbd18","Btbd16","Btbd17","Btbd19","Btbd2","Btbd3","Btbd6","Btbd7","Btbd8","Btc","Btbd9","Btd","Btf3","Btf3l4","Btg1","Btg3","Btg4","Btk","Btla","Btn1a1","Btn2a2","Btn3a2","Btnl10","Btnl2","Btnl5","Btnl3","Btg2","Btnl6-ps1","Btnl8","Btnl7","Btnl9","Bud13","Bub1","Bub3","Bub1b","Btrc","Bves","Bud23","Bud31","Bysl","Bzw2","Bzw1","C10H17orf102","C17H10orf113","C18H10orf95","C17h6orf52","C19H16orf47","C1d","C10H5orf58","C12H7orf61","C1d-ps1","C1galt1c1","C1H10orf76","C10H10orf95","C1galt1","C1H19orf84","C1H9orf66","C1qa","C1qb","C1ql1","C1ql2","C1qc","C1qbp","C1ql3","C1ql4","C1qtnf1","C1qtnf2","C1qtnf12","C1qtnf4","C1qtnf3","C1qtnf5","C1qtnf6","C1qtnf7","C1qtnf9","C1rl","C1r","C1s","C2cd2","C2","C2cd2l","C2cd3","C2cd4a","C2cd4b","C2cd4c","C2cd4d","C2H5orf64","C2cd6","C2cd5","C3ar1","C4bpb","C4bpa","C4a","C4b","C3","C5ar2","C5","C7H12orf80","C5ar1","C6","C7","C8a","C8b","C8g","Ca5b","C9","Ca5a","Caap1","Cab39l","Cab39","Cabcoco1","Cables2","Cabin1","Cables1","Cabp2","Cabp1","Cabp4","Cabp5","Cabp7","Cabs1","Cabyr","Cacfd1","Cachd1","Cacna1b","Cacna1e","Cacna1a","Cacna1f","Cacna1g","Cacna1c","Cacna1d","Cacna1h","Cacna1i","Cacna2d2","Cacna2d4","Cacna1s","Cacna2d3","Cacnb1","Cacna2d1","Cacnb2","Cacnb3","Cacng1","Cacnb4","Cacng5","Cacng2","Cacng3","Cacng4","Cacng6","Cacng7","Cactin","Cacng8","Cacul1","Cacybp","Cadm1","Cad","Cadm2","Cadm3","Cadm4","Cahm","Cadps","Cage1","Cadps2","Calb2","Calb1","Calcb","Calcoco2","Calcoco1","Calcr","Calhm1","Calca","Calcrl","Cald1","Calhm2","Calhm3","Calhm4","Calm-ps1","Calm-ps2","Calhm5","Calhm6","Calml3","Calml5","Calml4","Calm3","Calm2","Caln1","Calm1","Calr3","Calr4","Calr","Caly","Calu","Camk1","Camk1d","Camk1g","Camk2n1","Camk2n2","Camk2g","Camk2b","Camk2a","Camkk1","Camk2d","Camk4","Camkmt","Camkk2","Camp","Camkv","Camlg","Camsap1","Camsap2","Camsap3","Camta2","Camta1","Cand1","Cand2","Cant1","Cap2","Cap1","Canx","Capg","Capn10","Capn12","Capn11","Capn1","Capn13","Capn15","Capn2","Capn5","Capn7","Capn3","Capn8","Capn9","Capns1","Capns1-ps1","Capns2","Caprin2","Caprin1","Capsl","Caps2","Capza2","Capza1","Capza3","Capzb","Car1","Car10","Car11","Capn6","Car13","Car13-ps1","Car12","Car15","Car14","Car7","Car6","Car3","Car2","Car8","Car4","Card10","Car9","Card14","Card19","Card11","Card6","Card9","Carf","Carhsp1","Carmn","Carm1","Carnmt1","Carns1","Carmil2","Carmil1","Carmil3","Cars","Cars2","Casd1","Casc3","Casc1","Casc4","Cartpt","Cask","Casp14","Casp16","Caskin2","Caskin1","Casp12","Casp1","Casp2","Casp4","Casp7","Casp8ap2","Casp6","Casp8","Casq1","Casq2","Casp9","Cass4","Casp3","Castor2","Castor1","Casr","Cast","Catsper1","Catip","Casz1","Catsper3","Catsper2","Catsper4","Catspere","Catsperd","Catsperg","Catsperz","Cat","Cav2","Cavin1","Cavin2","Cavin3","Catsperb","Cavin4","CB741658","Cbarp","Cbfa2t2","Cbfa2t3","Cbfb","Cblb","Cav1","Cblc","Cbll1","Cbln1","Cb707485","Cav3","Cbl","Cbln2","Cbln4","Cbln3","Cbr3","Cbr4","Cbr1","Cbwd1","Cbs","Cbx1","Cbx2","Cbx3","Cbx4","Cbx5","Cbx6","Cby3","Cbx8","Cbx7","Cby1","Cc2d1a","Cc2d1b","Cc2d2b","Ccbe1","Ccar1","Cc2d2a","Ccdc102a","Ccar2","Ccdc103","Ccdc105","Ccdc106","Ccdc110","Ccdc107","Ccdc112","Ccdc113","Ccdc114","Ccdc115","Ccdc116","Ccdc117","Ccdc121","Ccdc12","Ccdc120","Ccdc122","Ccdc124","Ccdc125","Ccdc126","Ccdc127","Ccdc13","Ccdc129","Ccdc130","Ccdc134","Ccdc136","Ccdc137","Ccdc138","Ccdc144b","Ccdc14","Ccdc142","Ccdc141","Ccdc146","Ccdc148","Ccdc149","Ccdc15","Ccdc150","Ccdc151","Ccdc152","Ccdc153","Ccdc154","Ccdc157","Ccdc155","Ccdc162","Ccdc160","Ccdc158","Ccdc159","Ccdc166","Ccdc163","Ccdc167","Ccdc169","Ccdc17","Ccdc170","Ccdc171","Ccdc174","Ccdc172","Ccdc173","Ccdc168","Ccdc175","Ccdc179","Ccdc177","Ccdc18","Ccdc178","Ccdc181","Ccdc182","Ccdc180","Ccdc183","Ccdc185","Ccdc184","Ccdc187","Ccdc186","Ccdc188","Ccdc189","Ccdc190","Ccdc196","Ccdc22","Ccdc24","Ccdc191","Ccdc25","Ccdc27","Ccdc28a","Ccdc28b","Ccdc3","Ccdc30","Ccdc33","Ccdc32","Ccdc34","Ccdc36","Ccdc38","Ccdc198","Ccdc39","Ccdc40","Ccdc42","Ccdc43","Ccdc47","Ccdc50","Ccdc54","Ccdc51","Ccdc57","Ccdc58","Ccdc59","Ccdc6","Ccdc61","Ccdc60","Ccdc62","Ccdc65","Ccdc63","Ccdc68","Ccdc69","Ccdc70","Ccdc7","Ccdc66","Ccdc71","Ccdc71l","Ccdc74a","Ccdc73","Ccdc77","Ccdc78","Ccdc8","Ccdc80","Ccdc81","Ccdc82","Ccdc83","Ccdc84","Ccdc85b","Ccdc85a","Ccdc85c","Ccdc87","Ccdc86","Ccdc88a","Ccdc88c","Ccdc88b","Ccdc89","Ccdc9","Ccdc90b","Ccdc91","Ccdc92","Ccdc92b","Ccdc96","Ccdc94","Ccdc93","Ccdc9b","Ccdc97","Ccer2","Ccer1","Cchcr1","Ccin","Cck","Ccl1","Ccl12","Cckbr","Cckar","Ccl17","Ccl11","Ccl19","Ccl21","Ccl22","Ccl25","Ccl24","Ccl26","Ccl27","Ccl28","Ccl4","Ccl6","Ccl7","Ccl9","Ccl20","Ccl3","Ccl5","Ccm2","Ccm2l","Ccna1","Ccnb1ip1","Ccnb2","Ccna2","Ccnb3","Ccnb1","Ccnc","Ccnd2","Ccnd3","Ccndbp1","Ccne2","Ccne1","Ccnf","Ccnf-ps1","Ccl2","Ccng2","Ccng1","Ccnh","Ccni","Ccnj","Ccnk","Ccnjl","Ccnl1","Ccnl2","Ccno","Ccnyl1","Ccny","Ccnt2","Ccnd1","Ccp110","Ccp6l1","Ccnt1","Ccpg1","Ccpg1os","Ccr10","Ccr1l1","Ccr1","Ccr4","Ccr3","Ccr2","Ccr7","Ccr6","Ccr8","Ccr9","Ccrl2","Ccs","Ccr5","Ccsap","Cct2-ps1","Ccser2","Ccser1","Cct2","Cct3-ps2","Cct3-ps1","Cct3-ps3","Cct3","Cct3-ps4","Cct5-ps2","Cct4","Cct6a-ps1","Cct5","Cct6a-ps11","Cct6a-ps12","Cct6a-ps10","Cct6a","Cct6a-ps14","Cct6a-ps15","Cct6a-ps2","Cct6a-ps3","Cct6a-ps5","Cct6a-ps4","Cct6a-ps6","Cct6a-ps7","Cct6a-ps8","Cct6a-ps9","Cct7-ps1","Cct7-ps2","Cct7-ps3","Cct6b","Cct7","Cct8-ps1","Cct8-ps2","Cct8","Cct8l1","Ccz1b","Cd109","Cd101","Cd160","Cd151","Cd163","Cd164l2","Cd164","Cd14","Cd180","Cd177","Cd19","Cd1d1","Cd2","Cd200r1","Cd207","Cd200r1l","Cd209a","Cd200","Cd209c","Cd209d","Cd209e","Cd209f","Cd22","Cd226","Cd24","Cd247","Cd244","Cd248","Cd27","Cd274","Cd276","Cd2bp2","Cd28","Cd300a","Cd2ap","Cd300c","Cd300e","Cd300c2","Cd300lb","Cd300ld","Cd300le","Cd300lg","Cd300lf","Cd302","Cd320","Cd33","Cd34","Cd37","Cd3e-ps1","Cd38","Cd3d","Cd3eap","Cd3e","Cd3g","Cd36","Cd4","Cd48","Cd47","Cd44","Cd5","Cd46","Cd52","Cd53","Cd5l","Cd40lg","Cd55","Cd59","Cd6","Cd40","Cd63","Cd68","Cd7","Cd70","Cd72","Cd79al","Cd69","Cd74","Cd79b","Cd80","Cd83","Cd84","Cd81","Cd82","Cd79a","Cd8a","Cd8b","Cd86","Cd99","Cd93","Cd96","Cd9","Cd99l2","Cda","Cdadc1","Cdan1","Cdc123","Cdc14a","Cdc14b","Cdc16","Cdc20b","Cdc20","Cdc23","Cdc26","Cdc27","Cdc34","Cdc37l1","Cdc37","Cdc40","Cdc42bpa","Cdc25b","Cdc42","Cdc42bpb","Cdc42bpg","Cdc42ep1","Cdc42ep2","Cdc42ep4","Cdc42ep3","Cdc42ep5","Cdc42se1","Cdc42se2","Cdc45","Cdc6","Cdc7","Cdc5l","Cdc73","Cdca2","Cdca3","Cdca4","Cdca7","Cdca5","Cdca7l","Cdca8","Cdcp1","Cdc25c","Cdcp2","Cdh10","Cdh11","Cdh14","Cdh15","Cdh12","Cdh1","Cdh16","Cdh13","Cdh17","Cdh19","Cdh18","Cdh20","Cdh22","Cdh23","Cdh2","Cdh24","Cdh26","Cdh3","Cdh6","Cdh4","Cdh7","Cdh5","Cdc25a","Cdh8","Cdh9","Cdhr3","Cdhr1","Cdhr4","Cdhr2","Cdip1","Cdhr5","Cdipt","Cdk10","Cdk13","Cdk11b","Cdk15","Cdk12","Cdk14","Cdk16","Cdk17","Cdk18","Cdk19","Cdk2ap1","Cdk20","Cdk2ap1-ps1","Cdk2","Cdk2ap1-ps2","Cdk2ap1-ps3","Cdk2ap1-ps5","Cdk2ap1-ps4","Cdk2ap1-ps6","Cdk2ap1-ps7","Cdk2ap1-ps8","Cdk2ap2","Cdk5r2","Cdk5r1","Cdk5rap1","Cdk4","Cdk5","Cdk5rap3","Cdk6","Cdk8","Cdk7","Cdk9","Cdkal1","Cdkl1","Cdkl4","Cdkl2","Cdkl3","Cdk5rap2","Cdkn1c","Cdkn2aip","Cdkn1a","Cdkn1b","Cdkn2aipnl","Cdk1","Cdkn2a","Cdkn2d","Cdkn2c","Cdkn3","Cdnf","Cdkn2b","Cdkl5","Cdo1","Cdon","Cdpf1","Cdrt4","Cdr2l","Cdr2","Cds2","Cds1","Cdsn","Cdt1","Cdx1","Cdr1","Cdx2","Cdx4","Cdv3","Cdyl","Cdyl2","Ceacam11","Ceacam12","Ceacam16","Ceacam18","Ceacam19","Ceacam1","Ceacam20","Ceacam3","Ceacam6","Ceacam4","Ceacam9","Cebpd","Cebpe","Cebpg","Cebpa","Cebpb","Cebpz","Cecr2","Cel","Cela1","Cela2a","Cela3b","Celf3","Celf1","Celf4","Celf2","Celf5","Celf6","Celsr1","Celsr2","Cend1","Cenpb","Cemip","Cenpa","Celsr3","Cenpc","Cenpe","Cenph","Cenpf","Cenpk","Cenpj","Cenpi","Cenpm","Cenpl","Cenpo","Cenpp","Cenps","Cenpq","Cenpn","Cenpv","Cenpu","Cenpt","Cenpx","Cenpw","Cep112","Cep104","Cep120","Cep128","Cep126","Cep131","Cep135","Cep152","Cep170b","Cep170","Cep162","Cep164","Cep19","Cep192","Cep250","Cep295nl","Cep44","Cep350","Cep290","Cep295","Cep41","Cep55","Cep57","Cep68","Cep57l1","Cep72","Cep63","Cep70","Cep76","Cep78","Cep83","Cep83os","Cep85l","Cep89","Cep95","Cep85","Cep97","Cept1","Cer1","Cercam","Cerk","Cers1","Cerkl","Cers2","Cers4","Cers3","Cers5","Cers6","Ces1a","Ces1d","Ces2","Ces2a","Ces1c","Ces1e","Ces2c","Ces2e","Ces1f","Ces2i","Ces3a","Ces2g","Ces2j","Ces4a","Ces2h","Ces5a","Cesl1","Cetn1","Cetn4","Cetn2","Cfap100","Cetn3","Cfap157","Cfap126","Cfap161","Cfap20","Cfap206","Cfap221","Cfap43","Cfap36","Cfap45","Cfap44","Cfap46","Cfap47","Cfap53","Cfap54","Cfap52","Cfap57","Cfap58","Cfap61","Cfap65","Cfap73","Cfap58l1","Cfap70","Cfap74","Cfap77","Cfap97","Cfap99","Cfc1","Cfap69","Cfdp1","Cfd","Cfhr2","Cfhr1","Cfb","Cfh","Cfi","Cfl2","Cfp","Cflar","Cga","Cfl1","Cgas","Cggbp1","Cgm4","Cgn","Cgnl1","Cftr","Cgref1","Cgrrf1","Ch25h","Chac1","Chac2","Chad","Chaf1a","Chadl","Champ1","Chaf1b","Chchd1","Chchd10","Chat","Chchd2","Chchd3","Chchd4","Chchd5","Chchd6","Chchd7","Chd1","Chd1l","Chd2","Chd3","Chd4","Chd5","Chd6","Chd8","Chd9","Chdh","Cherp","Chek1","Chfr","Chek2","Chga","Chgb","Chi3l1","Chi3l4","Chi3l3","Chd7","Chic1","Chic2","Chid1","Chit1","Chkb","Chl1","Chka","Chml","Chm","Chmp1a","Chmp1b","Chmp3","Chmp2b","Chmp2a","Chmp4bl1","Chmp4b","Chmp4c","Chmp5","Chmp6","Chmp7","Chn3","Chn1","Chn2","Chia","Chodl","Chordc1","Chp2","Chpf","Chp1","Chpf2","Chpt1","Chrac1","Chrdl2","Chrdl1","Chrd","Chrm2","Chrm4","Chrm1","Chrm3","Chrm5","Chrna1","Chrna10","Chrna2","Chrna3","Chrna5","Chrna6","Chrna4","Chrna9","Chrnb1","Chrnb3","Chrna7","Chrnd","Chrnb2","Chrnb4","Chrng","Chst1","Chrne","Chst10","Chst11","Chst12","Chst13","Chst14","Chst2","Chst15","Chst3","Chst5","Chst4","Chst8","Chst7","Chst9","Chsy1","Chsy3","Chsy3l","Chtf18","Chtop","Chtopl1","Churc1","Chtf8","Chuk","Ciapin1","Ciao1","Ciart","Cib3","Cib2","Cib1","Cib4","Cidea","Cideb","Cic","Cidec","Cilp","Cilp2","Cinp","Cip2a","Cipc","Ciita","Cir1","Cisd2","Cisd1","Cirbp","Cistr","Cisd3","Cish","Cited1","Cited4","Cit","Ckap2l","Ckap2","Ciz1","Cited2","Ckap4","Cklf","Ckap5","Ckmt2","Cks1l","Cks1b","Ckm","Ckb","Ckmt1","Cks2","Clba1","Clasrp","Clasp2","Clca2","Clasp1","Clca1","Clca4","Clca4l","Clca5","Clcf1","Clcc1","Clcn1","Clcn2","Clcn3","Clcn4","Clcn6","Clcn5","Clcn7","Clcnka","Clcnkb","Cldn1","Cldn10","Cldn11","Cldn14","Cldn12","Cldn15","Cldn16","Cldn17","Cldn19","Cldn18","Cldn2","Cldn20","Cldn22","Cldn25","Cldn24","Cldn23","Cldn34a","Cldn3","Cldn34c4","Cldn34d","Cldn34b","Cldn34e","Cldn4","Cldn8","Cldn6","Cldn7","Cldn5","Cldn9","Cldnd1","Cldnd2","Clec10a","Clec11a","Clec12a","Clec12b","Clec14a","Clec16a","Clec18a","Clec19a","Clec1b","Clec1a","Clec2d","Clec2d2","Clec2dl1","Clec2h","Clec2e","Clec2l","Clec2g","Clec3a","Clec3b","Clec4a1","Clec20a","Clec4a2","Clec4a","Clec4a3","Clec4b2","Clec4d","Clec4e","Clec4g","Clec4f","Clec5a","Clec4m","Clec6a-ps1","Clec9a","Clhc1","Clec7a","Clgn","Clic2","Clic3","Clic4","Clic1","Clic6","Clic5","Clint1","Clip2","Clip1","Clip3","Clk1","Clip4","Clk3","Clk2","Clk4","Clmn","Clmp","Cln5","Cln3","Cln8","Clnk","Clns1a","Clp1","Clock","Clpb","Clpp","Clps","Clpsl2","Clptm1","Clrn2","Clptm1l","Clrn1","Clpx","Clrn3","Clspn","Cln6","Clstn1","Clstn3","Clstn2","Cltb","Clta","Cluap1","Cltc","Cluh","Clul1","Clvs2","Clvs1","Clybl","Clu","Cma1","Cmahp","Cmas","Cmbl","Cmc1","Cmc2","Cmklr1","Cmip","Cmpk2","Cmpk1","Cmtm1","Cmtm2a","Cmss1","Cmtm3","Cmtm4","Cmtm5","Cmtm7","Cmtm6","Cmtr2","Cmtm8","Cmtr1","Cmya5","Cnbd2","Cnbd1","Cnep1r1","Cnbp","Cndp2","Cndp1","Cnfn","Cnga1","Cnga2","Cnga3","Cnih1","Cnga4","Cnih2","Cngb3","Cngb1","Cnih3","Cnih4","Cnksr1","Cnmd","Cnksr3","Cnksr2","Cnn2","Cnn1","Cnn3","Cnnm1","Cnnm2","Cnnm3","Cnnm4","Cnot10","Cnot11","Cnot1","Cnot3","Cnot2","Cnot4","Cnot6","Cnot6l","Cnot7","Cnot8","Cnot9","Cnpy1","Cnpy3","Cnppd1","Cnpy2","Cnp","Cnpy4","Cnst","Cnrip1","Cntd1","Cntfr","Cnr2","Cntln","Cntf","Cnr1","Cntn2","Cntn1","Cntn3","Cntn5","Cntn6","Cntn4","Cntnap1","Cntnap3","Cntnap4","Cntnap5a","Cntnap2","Cntnap5c","Cntrl","Cntnap5b","Coa3","Cntrob","Coa4","Coa5","Coa6","Coa7","Coasy","Cobl","Cobll1","Coch","Cog1","Cog3","Cog2","Cog4","Cog6","Cog5","Cog7","Cog8","Coil","Col10a1","Col11a1","Col13a1","Col11a2","Col15a1","Col14a1","Col12a1","Col16a1","Col17a1","Col18a1","Col19a1","Col20a1","Col22a1","Col24a1","Col23a1","Col1a2","Col25a1","Col26a1","Col28a1","Col27a1","Col1a1","Col3a1","Col4a1","Col2a1","Col4a2","Col4a3bp","Col4a3","Col4a4","Col4a5","Col4a6","Col5a2","Col5a1","Col5a3","Col6a1","Col6a2","Col6a4","Col6a5","Col6a6","Col8a1","Col8a2","Col7a1","Col9a1","Col9a2","Colca2","Colec10","Col9a3","Colec11","Colgalt1","Colec12","Col6a3","Colgalt2","Commd1","Colq","Commd2","Commd10","Commd4","Commd3","Commd5","Commd7","Commd8","Commd6","Commd9","Comtd1","Comp","Copb2","Comt","Copa","Copb1","Cope","Coprs","Copg2","Copg1","Cops3","Cops4","Cops2","Cops5","Cops6","Cops7a","Cops7b","Cops8","Copz2","Coq10a","Cops9","Copz1","Coq10b","Coq4","Coq2","Coq3","Coq5","Coq6","Coq8b","Coq7","Coq8a","Coq9","Corin","Coro1a","Coro1c","Coro1b","Coro2a","Coro2b","Cort","Coro7","Coro6","Cotl1","Cox11","Cox10","Cox14","Cox15","Cox17","Cox19","Cox18","Cox20","Cox4i2","Cox5a","Cox4i1","Cox6a1","Cox6a2","Cox5b","Cox6b1-ps1","Cox6b1","Cox6b2","Cox6c-ps1","Cox6c","Cox7a1","Cox7a2","Cox7a2l","Cox7a2l2","Cox7b2","Cox7b","Cox16","Cox8a","Cox8b","Cox8c","Cpa1","Cpa2","Cpa3","Cpa4","Cpa5","Cpa6","Cpb1","Cpamd8","Cpb2","Cpd","Cox7c","Cp","Cpe","Cpeb1","Cpeb2","Cpg1","Cped1","Cpeb3","Cpeb4","Cphx","Cplx1","Cplx3","Cplx2","Cplx4","Cpn2","Cpm","Cpne1","Cpn1","Cpne3","Cpne2","Cpne4","Cpne6","Cpne5","Cpne7","Cpne8","Cpne9","Cpo","Cpped1","Cpox","Cpq","Cpsf1","Cpsf2","Cpsf3","Cpsf4","Cps1","Cpsf4l","Cpsf6","Cpsf7","Cpt1c","Cpt2","Cptp","Cpt1a","Cpvl","Cpt1b","Cpxcr1","Cpxm1","Cpxm2","Cpz","Crabp1","Cr1l","Cracr2a","Cr2","Crabp2","Cracr2b","Cradd","Cramp1","Crb2","Crb3","Crb1","Crat","Crbn","Crct1","Crcp","Creb3","Creb3l2","Creb3l1","Creb3l4","Creb3l3","Creb5","Crebl2","Crebzf","Creg2","Creg1","Crebrf","Crebbp","Creld1","Creb1","Creld2","Crhbp","Crim1","Crem","Crip1","Crh","Crip2","Crhr2","Crip3","Cripak","Cript","Crisp1","Crisp2","Crhr1","Crisp3","Crispld1","Crispld2","Crk","Crkl","Crlf2","Crlf1","Crlf3","Crmp1","Crls1","Crnde","Crnkl1-ps1","Crnkl1","Crnn","Crocc","Crot","Crocc2","Crtac1","Crtam","Crtap","Crtapl1","Crtc1","Crxos1","Crtc2","Crtc3","Crx","Cry1","Cry2","Cryaa","Cryba2","Cryba4","Cryba1","Crybb1","Cryab","Crybb2","Crp","Crybb3","Crybg1","Crybg3","Crybg2","Cryga","Crygb","Crygc","Crygd","Crygf","Cryge","Crygn","Crygs","Cryl1","Crym","Cryz","Cryzl1","Csap1","Cs","Csad","Csdc2","Cse1l","Csde1","Csf2ra","Csf1r","Csf2rb","Csf2","Csf1","Csf3r","Csgalnact1","Csf3","Csgalnact2","Csk","Csmd1","Csn1s2a","Csn1s2b","Csmd2","Csn1s1","Csmd3","Csn2","Csn3","Csnk1g3","Csnk1e","Csnk1g1","Csnk1a1","Csnk1g2","Csnk1d","Csnk2a1","Csnka2ip","Csnk2a2","Csprs","Cspg5","Cspp1","Cspg4","Csnk2b","Csrnp1","Csrnp2","Csrnp3","Cst11","Csrp1","Cst12","Csrp2","Csrp3","Cst13","Cst5","Cst6","Cst8","Cst9l","Cst7","Csta","Cst3","Cstb","Cstl1","Cstf3","Cstf2t","Cstf2","Cstf1","Ct45a9","Ct55","Ctag2","Ctbs","Ctbp2","Ctc1","Ctbp1","Ctcf","Ctcfl","Ctdnep1","Ct47b1","Ctdsp2","Ctdp1","Ctdsp1","Ctdspl","Ctdspl2","Ctf2","Ctf1","Cthrc1","Ctif","Ctla2a","Cth","Ctnna1","Ctla4","Ctgf","Ctnnal1","Ctnna2","Ctnnbip1","Ctnna3","Ctnnbl1","Ctnnd2","Ctps1","Ctnnd1","Ctps2","Ctr9","Ctnnb1","Ctrb1","Ctrc","Cts8","Cts8l1","Ctrl","Cts7","Ctns","Ctsa","Ctsf","Ctse","Ctsg","Ctsc","Ctsd","Ctsh","Ctsb","Ctsj","Ctso","Ctsll3","Ctsm","Ctsk","Ctsq","Ctsr","Ctsql2","Ctsl","Ctsw","Ctss","Ctsz","Cttnbp2","Cttnbp2nl","Ctu1","Cttn","Ctu2","Ctxn3","Ctxn1","Cuedc1","Cuedc2","Ctxn2","Cubn","Cul2","Cul1","Cul4a","Cul3","Cul4b","Cul5","Cul7","Cutc","Cuta","Cul9","Cux2","Cwc15","Cuzd1","Cux1","Cwf19l1","Cwc25","Cwc22","Cwc27","Cwf19l2","Cwh43","Cx3cl1","Cxadrl1","Cxadr","Cx3cr1","Cxcl11","Cxcl1","Cxcl10","Cxcl13","Cxcl14","Cxcl16","Cxcl17","Cxcl12","Cxcl3","Cxcl2","Cxcl9","Cxcl6","Cxcr3","Cxcr6","Cxcr5","CXHXorf65","Cxcr4","Cxx1a","Cxcr1","Cxxc1","Cyb561","Cxxc5","Cyb561d1","Cxxc4","Cyb561a3","Cxcr2","Cyb5d1","Cyb561d2","Cyb5d2","Cyb5b","Cyb5r1","Cyb5a","Cyb5r2","Cyb5r3","Cyb5rl","Cybrd1","Cyc1-ps1","Cyb5r4","Cyc1","Cyba","Cybb","Cyct","Cycs","Cyfip2","Cygb","Cylc2","Cylc1","Cyhr1","Cyld-ps1","Cym","Cyld","Cyp11b1","Cyfip1","Cyp11b3","Cyp11b2","Cyp20a1","Cyp1a2","Cyp1b1","Cyp21a1","Cyp19a1","Cyp1a1","Cyp21a1-ps","Cyp26a1","Cyp24a1","Cyp26b1","Cyp26c1","Cyp27a1","Cyp27b1","Cyp2ab1","Cyp2a2","Cyp2ac1","Cyp2a1","Cyp2a3","Cyp2b13","Cyp2b12","Cyp2b1","Cyp2b15","Cyp2b21","Cyp2b2","Cyp2b31","Cyp17a1","Cyp2b3","Cyp2c11","Cyp2c12","Cyp2c22","Cyp2c13","Cyp2c23","Cyp2c24","Cyp2c77-ps","Cyp2c79","Cyp2c80","Cyp11a1","Cyp2c7","Cyp2c6v1","Cyp2d2","Cyp2d1","Cyp2d3","Cyp2d5","Cyp2g1","Cyp2f4","Cyp2d4","Cyp2j13","Cyp2e1","Cyp2j10","Cyp2j16","Cyp2j5-ps","Cyp2j3","Cyp2r1","Cyp2j4","Cyp2s1","Cyp2t1","Cyp2u1","Cyp2w1","Cyp39a1","Cyp3a62","Cyp3a18","Cyp3a2","Cyp3a71-ps","Cyp3a23/3a1","Cyp3a73","Cyp3a85-ps","Cyp46a1","Cyp4a34-ps","Cyp3a9","Cyp4a3","Cyp4a2","Cyp4b1","Cyp4a8","Cyp4a1","Cyp4f18","Cyp4f17","Cyp4f1","Cyp4f37","Cyp4f40","Cyp4f39","Cyp4f4","Cyp4f5","Cyp4x1","Cyp4f6","Cyp4v3","Cyp51a1-ps1","Cyp51","Cyp7a1","Cyp7b1","Cyp8b1","Cys1","Cysltr2","Cysrt1","Cyr61","Cystm1","Cyss","Cyth2","Cyth1","Cyth3","Cyth4","Cytip","Cytl1","Cyyr1","Da2-19","D2hgdh","Daam2","Daam1","Dab1","Cysltr1","Dab2","Dab2ip","Dach2","Dach1","Dact1","Dact2","Dact3","Dagla","Dalrd3","Dad1","Daglb","Dand5","Dag1","Dap","Dao","Dap3","Dapk2","Dapk1","Dapl1","Dapk3","Dapp1","Dars2","Daw1","Dars","Dazap2","Dazap1","Daxx","Dazl","Dbf4b","Dbf4","Dbil5","Dbh","Dbndd1","Dbndd2","Dbi","Dbn1","Dbnl","Dbp","Dancr","Dbr1","Dbt","Dbx1","Dbx2","Dcaf10","Dcaf1","Dcaf12","Dcaf11","Dcaf12l2","Dcaf12l1","Dcaf15","Dcaf13","Dcaf17","Dcaf4","Dcaf5","Dcaf8l1","Dcaf7","Dcaf8","Dcaf6","Dcakd","Dcbld1","Dcbld2","Dcdc1","Dcdc2b","Dcdc2","Dcdc5","Dchs2","Dcc","Dcdc2c","Dchs1","Dck","Dclre1b","Dclk3","Dclre1a","Dclre1c","Dclk2","Dcm5","Dclk1","Dcn","Dcp1b","Dcp2","Dcst1","Dcp1a","Dcstamp","Dcps","Dcst2","Dct","Dctn3l1","Dctn3","Dctd","Dctn5","Dctn2","Dctn4","Dctn1","Dctn6","Dctpp1","Dcun1d2","Dcun1d1","Dcun1d3","Dcun1d4","Dcun1d5","Dda1","Dcxr","Dcx","Ddah1","Ddah2","Ddb2","Ddb1","Ddhd1","Ddi1","Ddc","Ddhd2","Ddi2","Ddias","Ddit4","Ddit4l2","Ddit4l","Ddit3","Ddn","Ddo","Ddost","Ddrgk1","Ddt","Ddr2","Ddx1","Ddr1","Ddx10","Ddx11","Ddx17","Ddx18","Ddx20","Ddx19b","Ddx19a","Ddx23","Ddx24","Ddx21","Ddx25","Ddx3","Ddx27","Ddx28","Ddx31","Ddx39a","Ddx3y","Ddx39b","Ddx3x","Ddx4","Ddx41","Ddx42","Ddx43","Ddx46","Ddx47","Ddx49","Ddx51","Ddx50","Ddx5","Ddx54","Ddx52","Ddx55","Ddx56","Ddx58","Ddx59","Ddx60","Dear","Ddx6","Deaf1","Decr1","Dedd","Dedd2","Decr2","Def6","Defa10","Def8","Defa11","Defa24","Defa6","Defa7","Defa5","Defa9","Defa8","Defal1","Defb1","Defb10","Defb13","Defb12","Defb11","Defb16-ps","Defb14","Defb15","Defb17","Defb18","Defb19","Defb2","Defb20","Defb21","Defb23","Defb22","Defb24","Defb25","Defb26","Defb28","Defb27","Defb3","Defb29","Defb30","Defb36","Defb33","Defb37","Defb38","Defb39","Defb41","Defb40","Defb4","Defb43","Defb42","Defb44","Defb49","Defb5","Defb50","Defb51","Defb52","Defb9","Degs1","Degs2","Dek","Dennd1c","Dennd1b","Dennd1a","Dennd2a","Dennd2c","Dennd2d","Dennd3","Dennd4b","Dennd4c","Dennd4a","Dennd5a","Dennd5b","Dennd6a","Dennd6b","Denr","Depdc1","Depdc1b","Depdc7","Deptor","Depdc5","Dera","Derl1","Derl3","Det1","Desi1","Desi2","Des","Derl2","Deup1","Dexi","Dffa","Dffb","Dgat2l6","Dgat2","Dgat1","Dgcr2","Dgcr6","Dgcr8","Dgka","Dgke","Dgkb","Dgkd","Dgkg","Dgkh","Dgkk","Dgki","Dgkq","Dglucy","Dguok","Dgkz","Dhcr24","Dhdds","Dhdh","Dhcr7","Dhfr","Dhrs1","Dhh","Dhps","Dhodh","Dhrs11","Dhrs13","Dhrs2","Dhrs3","Dhrs7","Dhrs7c","Dhrs4","Dhrs7b","Dhrs7l1","Dhrs9","Dhrsx","Dhtkd1","Dhx15","Dhx29","Dhx16","Dhx32","Dhx33","Dhx34","Dhx30","Dhx35","Dhx37","Dhx57","Dhx36","Dhx38","Dhx58","Dhx40","Dhx8","Diablo","Dhx9","Diaph2","Diaph3","Diaph1","Dido1","Dicer1","Diexf","Dio3os","Dimt1","Dio1","Dio3","Dio2","Dip2a","Dip2b","Dip2c","Diras1","Diras2","Diras3","Dirc2","Dis3","Dis3l","Dis3l2","Disp1","Disc1","Disp2","Disp3","Dixdc1","Dkc1","Dkk2","Dkk1","Dkk4","Dkkl1","Dkk3","Dlat","Dleu7","Dlec1","Dld","Dlc1","Dlg3","Dlg1","Dlg2","Dlg5","Dlgap3","Dlgap2","Dlgap1","Dlgap4","Dlg4","Dlgap5","Dlk1","Dlk2","Dll3","Dll1","Dll4","Dlx1","Dlx3","Dlx2","Dlx4","Dlst","Dlx6","Dlx5","Dmac1","Dmac2","Dmbx1","Dmc1","Dmap1","Dmbt1","Dmkn","Dmgdh","Dmp1","Dmrt2","Dmrt1","Dmpk","Dmrt3","Dmrta1","Dmd","Dmrta2","Dmrtc1b","Dmrtb1","Dmrtc1a","Dmrtc1c1","Dmrtc2","Dna2","Dmtf1","Dmwd","Dmtn","Dmxl1","Dnaaf2","Dmxl2","Dnaaf1","Dnaaf3","Dnaaf5","Dnah10","Dnaaf4","Dnah14","Dnah1","Dnah11","Dnah12","Dnah3","Dnah17","Dnah5","Dnah6","Dnah2","Dnah8","Dnah7","Dnai1","Dnah9","Dnai2","Dnaja1","Dnaja2","Dnaja3","Dnaja4","Dnajb1","Dnajb11","Dnajb12","Dnajb13","Dnajb14","Dnajb3","Dnajb4","Dnajb2","Dnajb5","Dnajb7","Dnajb8","Dnajb6","Dnajb9","Dnajc1","Dnajc11","Dnajc10","Dnajc12","Dnajc15","Dnajc14","Dnajc13","Dnajc16","Dnajc17","Dnajc18","Dnajc19","Dnajc2","Dnajc22","Dnajc21","Dnajc24","Dnajc25","Dnajc28","Dnajc27","Dnajc30","Dnajc3","Dnajc4","Dnajc5b","Dnajc5","Dnajc5g","Dnajc7","Dnajc6","Dnajc9","Dnajc8","Dnal4","Dnal1","Dnali1","Dnase1","Dnase1l2","Dnase1l1","Dnase2","Dnase1l3","Dnd1","Dnase2b","Dner","Dnhd1","Dnlz","Dnmbp","Dnm1","Dnm3","Dnm2","Dnm1l","Dnmt3b-ps1","Dnmt3b","Dnmt3b-ps2","Dnpep","Dnmt3l","Dnmt1","Dnmt3a","Dnph1","Dnttip1","Dntt","Doc2a","Doc2g","Doc2b","Dock1","Dock10","Dock2-ps1","Dock2","Dnttip2","Dock11","Dock4","Dock3","Dock5","Dock6","Dohh","Dock7","Dock8","Dok1","Dok2","Dock9","Dok3","Dok4","Dok5","Dok6","Dok7","Dolk","Dolpp1","Donson","Dopey1","Dopey2","Doxl1","Dot1l","Dpagt1","Doxl2","Dpcd","Dpcr1","Dpep1","Dpep2","Dpep3","Dpf1","Dph1","Dph2","Dpf2","Dpf3","Dph3","Dph5","Dph6","Dph7","Dpm2","Dpm1","Dpm3-ps1","Dpm3","Dpp3l","Dpp3","Dpp7","Dpp9","Dpp10","Dpp8","Dppa1","Dpp4","Dpp6","Dppa1-ps1","Dppa3-ps2","Dppa3-ps1","Dppa3l1","Dppa3","Dppa4","Dppa5","Dpy19l1","Dpt","Dpy19l2","Dpy19l4","Dpy19l3","Dpy30","Dpys","Dpysl3","Dpysl2","Dpysl5","Dpysl4","Dqx1","Dr1","Dram1","Dram2","Draxin","Drap1","Drc3","Drc1","Drc7","Dpyd","Drg2","Drg1","Drgx","Drd3","Drd5","Drd4","Drd1","Drd2","Dsc1","Dsc3","Drosha","Dsc2","Drp2","Dscam","Dscaml1","Dscc1","Dscr3","Dsel","Dse","Dsg1","Dsg2","Dsg3","Dsg4","Dsn1","Dstnl1","Dsp","Dstn","Dspp","Dstyk","Dtd1","Dtd2","Dst","Dthd1","Dtl","Dtna","Dtnb","Dtnbp1","Dtwd1","Dtwd2","Dtx2-ps1","Dtx1","Dtx2","Dtx3","Dtx3l","Dtx4","Dtymk","Duoxa1","Duox1","Duox2","Duoxa2","Dupd1","Dus1l","Dus2","Dus4l","Dus3l","Dusp10","Dusp11","Dusp12","Dusp1","Dusp13","Dusp14l1","Dusp15","Dusp14","Dusp16","Dusp18","Dusp19","Dusp2","Dusp21","Dusp23","Dusp22","Dusp27","Dusp28","Dusp26","Dusp3","Dusp4","Dusp7","Dusp5","Dusp8","Dusp9","Dusp6","Dut-ps","Dut","Duxbl1","Dvl2","Dvl3","Dydc1","Dxo","Dydc2","Dvl1","Dym","Dynap","Dync1li1","Dync1li2","Dync1i1","Dync1i2","Dync1h1","Dync2li1","Dynll1","Dync2h1","Dynlrb1","Dynlrb2","Dynll2","Dynlt3","Dynlt1","Dyrk1b","Dyrk1a","Dyrk2","Dyrk3","Dyrk4","Dytn","Dzank1","Dysf","Dzip1-ps1","Dzip1","Dzip1l","E230034O05Rik","Dzip3","E2f2","E2f3","E2f1","E2f4","E2f5","E2f7","E2f8","E4f1","Eaf1","Eaf2","Eapp","Ear1","Ears2","Ears2l1","Ebag9","Ebf2","Ebf4","E2f6","Ebf1","Ebf3","Ebi3","Ebna1bp2","Ebpl","Ebp","Ece2","Ecd","Ecel1","Ech1","Echdc1","Ece1","Echdc2","Echdc3","Echs1","Eci1","Ecm1","Eci3","Eci2","Ecm2","Ecscr","Ecsit","Ect2l","Ect2","Edar","Eda","Eda2r","Edaradd","Edc3","Edc4","Eddm3b","Edem1","Edem3","Edem2","Edf1","Edil3","Edn3","Edn2","Edrf1","Eea1","Eed","Ednra","Eef1a2","Ednrb","Eef1akmt3","Eef1akmt2","Eef1a1","Eef1akmt1","Eef1b2","Edn1","Eef1e1","Eef1d","Eef1g","Eef2kmt","Eef2k","Eefsec","Eepd1","Eef2","Ef1","Efcab1","Efcab10","Efcab11","Efcab12","Efcab14","Efcab13","Efcab3","Efcab2","Efcab5","Efcab7","Efcab8","Efcab6","Efcab9","Efcc1","Efemp2","Efemp1","Efhb","Efhc1","Efhc2","Efhd1","Efhd2","Efl1","Efna2","Efna1","Efna3","Efna4","Efna5","Efnb2","Efnb1","Efnb3","Efr3a","Efr3b","Efs","Egfem1","Eftud2","Egfl6","Egfl7","Egf","Egfl8","Egflam","Egln2","Egln1","Egln3","Egr3","Egr2","Egr4","Ehbp1","Ehbp1l1","Ehd1","Egfr","Ehd2","Ehd3","Egr1","Ehf","Ehd4","Ehhadh","Ehmt1","Eid2","Ei24","Eid1","Eid2b","Eid3","Ehmt2","Eif1","Eif1ad","Eif1a","Eif1b","Eif1ax","Eif2a","Eif2ak1","Eif2b1","Eif2ak3","Eif2b2","Eif2ak4","Eif2b3","Eif2b4","Eif2ak2","Eif2b5","Eif2d-ps1","Eif2d","Eif2s2","Eif2s3y","Eif2s3","Eif2s1","Eif3a","Eif3b","Eif3c","Eif3d","Eif3e","Eif3f","Eif3el1","Eif3g","Eif3h","Eif3i-ps1","Eif3i","Eif3j","Eif3l","Eif3m","Eif3k","Eif4a1","Eif4b","Eif4a3","Eif4e1b","Eif4a2","Eif4e3","Eif4e","Eif4e2","Eif4ebp2","Eif4enif1","Eif4ebp1","Eif4g2-ps1","Eif4ebp3","Eif4g2-ps2","Eif4g1","Eif4g2","Eif4h","Eif4g3","Eif5a2","Eif5","Eif5b-ps1","Eif5a","Eif5b","Eif6","Eif6-ps1","Elac1","Eipr1","Elac2","Elane","Elavl1","Elavl3","Elavl4","Elavl2","Elf1","Elf3","Elf2","Elf4","Elfn2","Elf5","Elfn1","Elk1","Elk3","Elk4","Ell","Ell2","Ell3","Elmo1","Elmo2","Elmo3","Elmod2","Elmod1","Elmod3","Elmsan1","Eloa","Elob","Elof1","Eln","Elovl1","Elovl2","Eloc","Elovl3","Elovl4","Elovl5","Elovl7","Elovl6","Elp3","Elp2","Elp1","Elp4","Elp5","Elp6","Emb","Emc2","Emc1","Emc10","Emc3","Emc4","Emc6","Emc8","Emc7","Emc9","Emcn","Eme1","Emd","Eme2","Emg1","Emid1","Emilin3","Emilin1","Emilin2","Eml1","Eml2","Eml3","Eml4","Eml5","Emp1","Emp2","Emp3","Eml6","Emx1","Emsy","Emx2","En1","En2","Enam","Enah","Enc1","Endod1","Endog","Endou","Endov","Engase","Enkur","Enkd1","Eno1-ps1","Eng","Eno1","Eno4","Eno3","Eno2","Enoph1","Enox1","Enox2","Enpep","Enpp4","Enpp5","Enpp2","Enpp3","Enpp1","Enpp6","Enthd1","Enpp7","Ensa","Enho","Entpd2","Entpd1","Entpd3","Entpd4","Entpd6","Entpd5","Entpd7","Entpd8","Eny2","Eomes","Eogt","Epb41l1","Ep300","Ep400","Epas1","Epb41l4a","Epb41l2","Epb41","Epb41l3","Epc2l1","Epb41l5","Epc2","Epc1","Epb42","Epb41l4b","Epcam","Epdr1","Epgn","Epg5","Epha10","Epha1","Epha2","Epha3","Epha4","Epha5","Epha6","Epha8","Epha7","Ephb1","Ephb3","Ephb2","Ephb4","Ephb6","Ephx3","Ephx4","Ephx2","Epm2aip1","Epm2a","Epn1","Epn3","Epn2","Epop","Eppin","Eppk1","Epor","Epo","Eps15","Eprs","Ephx1","Eps15l1","Eps8","Eps8l1","Eps8l3","Eps8l2","Epx","Epsti1","Epyc","Eqtn","Eral1","Eras","Erap1","Erbin","Erc1","Erbb3","Erbb4","Erbb2","Erc2","Ercc4l1","Ercc4","Ercc3","Ercc5","Ercc6l","Ercc6l2","Ercc8","Ereg","Ercc2","Erf","Erfe","Ercc1","Erg28","Ergic1","Ercc6","Ergic2","Erg","Ergic3","Erh","Eri1","Eri2","Erich1","Erich3","Erich2","Eri3","Erich4","Erich5","Erich6b","Erich6","Erlec1","Erlin1","Erlin2","Ermap","Ermard","Ermn","Ermp1","Ern2","Ern1","Ero1b","Ero1a","Erp27","Erp44","Erp29","Ervfrd-1","Errfi1","Esco2-ps1","Esam","Esco2-ps2","Esco2","Esco1","Esd","Esm1","Espnl","Esf1","Espl1","Espn","Esrp1","Esrp2","Esrrb","Esrra","Ess2","Esx1","Esrrg","Esyt1","Esyt2","Esyt3","Etaa1","Esr2","Esr1","Etaa1l1","Etf1","Etfb","Etfa","Etfbkmt","Etfrf1","Ethe1","Etfdh","Etnk1","Etl4","Etnk2","Etnppl","Etv2","Ets2","Ets1","Etv1","Etv3","Etv3l","Etv5","Etv4","Eva1a","Eva1b","Etv6","Eva1c","Evc","Evc2","Evi2a","Evi5l","Evi5","Evl","Evpl","Evx1","Evx2","Evi2b","Ewsr1","Exd1","Exd2","Exoc1l","Exo1","Exo5","Exoc1","Exoc3l2","Exoc2","Exoc3","Exoc3l1","Exoc3l4","Exoc5","Exoc4","Exoc6","Exog","Exoc8","Exoc7","Exoc6b","Exosc1","Exosc2","Exosc10","Exosc3","Exosc6","Exosc5","Exosc7","Exosc4","Exosc8","Exph5","Exosc9","Ext2","Extl1","Ext1","Extl3","Extl2","Eya1","Eya2","Eya3","Eya4","Ezh1","F10","Ezr","Ezh2","F11","F11r","F12","F13b","F13a1","F2rl2","F2rl3","F2r","F2rl1","F2","F5","F8a1","F3","F7","Fa2h","F8","F9","Faah","Faap20","Faap100","Faap24","Fabp12","Fabp3","Fabp1","Fabp2","Fabp5","Fabp4","Fabp7","Fabp9","Fabp6","Fads1","Fads2","Fadd","Fads2l1","Fads6","Fads3","Faf1","Faf2","Fahd1","Fah","Fahd2a","Fam102a","Faim","Faim2","Fam102b","Fam103a1","Fam105a","Fam104a","Fam107a","Fam104b","Fam107b","Fam109a","Fam109b","Fam110a","Fam110d","Fam110b","Fam110c","Fam111a","Fam114a1l1","Fam114a1","Fam114a2","Fam115c","Fam115e","Fam117a","Fam117b","Fam118a","Fam118b","Fam120b","Fam120a","FAM120C","Fam122a","Fam122b","Fam122c","Fam124b","Fam124a","Fam126a","Fam126b","Fam129a","Fam129b","Fam131a","Fam129c","Fam133a","Fam131b","Fam131c","Fam133b","Fam135b","Fam135a","Fam136a","Fam13a","Fam13c","Fam149a","Fam13b","Fam149b1","Fam151b","Fam151a","Fam156b","Fam155b","Fam155a","Fam160a1","Fam160a2","Fam160b1","Fam160b2","Fam161b","Fam161a","Fam162a","Fam163a","Fam163b","Fam166b","Fam166a","Fam167a","Fam167b","Fam169a","Fam168a","Fam168b","Fam169b","Fam170a","Fam170b","Fam171a1","Fam173a","Fam171a2","Fam171b","Fam173b","Fam174a","Fam172a","Fam174b","Fam177a1","Fam177b","Fam180b","Fam180a","Fam178b","Fam181a","Fam181b","Fam183b","Fam184a","Fam184b","Fam185a","Fam186b","Fam187b","FAM187A","Fam188a","Fam188b","Fam188b2","Fam189a1","Fam189a2","Fam18b-ps1","Fam189b","Fam192a","Fam193b","Fam193a","Fam196b","Fam198a","Fam199x","Fam198b","Fam19a1","Fam19a3","Fam19a2","Fam19a4","Fam19a5","Fam204a","Fam205a","Fam205c","Fam206a","Fam196a","Fam207a","Fam208a","Fam209a","Fam208b","Fam20a","Fam20b","Fam210a","Fam212a","Fam20c","Fam210b","Fam212b","Fam213a","Fam213b","Fam216b","Fam214a","Fam217a","Fam214b","Fam216a","Fam217b","Fam219a","Fam219b","Fam220a","Fam222a","Fam221a","Fam221b","Fam222b","Fam227a","Fam227b","Fam228a","Fam229a","Fam234a","Fam229b","Fam228b","Fam234b","Fam237a","Fam241a","Fam241b","Fam24a","Fam25a","Fam32a","Fam3a","Fam35a","Fam3b","Fam3c","Fam3d","Fam43a","Fam43b","Fam45a","Fam46b","Fam46a","Fam46c","Fam47a","Fam47e","Fam46d","Fam48b1","Fam49a","Fam50a","Fam49b","Fam50b","Fam53a","Fam53b","Fam53c","Fam57a","FAM58A-ps1","Fam57b","Fam58b","Fam69a","Fam69c","Fam71a","Fam69b","Fam71b","Fam71d","Fam71e1","Fam71e2","Fam71f1","Fam71f2","Fam76a","Fam72a","Fam76b","Fam78a","Fam78b","Fam81a","Fam83a","Fam81b","Fam83c","Fam83b","Fam83d","Fam83e","Fam83f","Fam83g","Fam83h","Fam84b","Fam89a","Fam84a","Fam89b","Fam8a1","Fam90a1-ps1","Fam90a1","Fam92a","Fam92b","Fam91a1","Fam96b","Fam98a","Fam96a","Fam98b","Fam9b","Fam98c","Fam9c","Fan1","Fancb","Fanca","Fancd2os","Fancc","Fance","Fancd2","Fancf","Fancg","Fanci","Fancm","Fancl","Fank1","Fap","Far2","Far1","Farp1","Farsa","Fars2","Farp2","Farsb","Fastk","Fasn","Fastkd1","Fastkd2","Fastkd5","Fastkd3","Fas","Fat1","Fat2","Fat4","Fat3","Faxc","Fau","Faxdc2","Fblim1","Fbl","Fbll1","Faslg","Fbf1","Fbln2","Fbln1","Fbln7","Fbln5","Fbp2","Fbn2","Fbrs","Fbp1","Fbrsl1","Fbxl12","Fbn1","Fbxl14","Fbxl13","Fbxl15","Fbxl16","Fbxl17","Fbxl18","Fbxl19","Fbxl2","Fbxl20","Fbxl21","Fbxl22","Fbxl3","Fbxl5","Fbxl4","Fbxl6","Fbxl7","Fbxl8","Fbxo10","Fbxo11","Fbxo15","Fbxo16","Fbxo17","Fbxo18","Fbxo2","Fbxo22","Fbxo21","Fbxo25","Fbxo24","Fbxo27","Fbxo28","Fbxo3","Fbxo30","Fbxo31","Fbxo33","Fbxo32","Fbxo34","Fbxo36","Fbxo38","Fbxo4","Fbxo39","Fbxo40","Fbxo43","Fbxo41","Fbxo42","Fbxo44","Fbxo45","Fbxo47","Fbxo46","Fbxo48","Fbxo5","Fbxo6","Fbxo7","Fbxo8","Fbxw10","Fbxo9","Fbxw12","Fbxw17","Fbxw11","Fbxw2","Fbxw4","Fbxw5","Fbxw8","Fcamr","Fbxw9","Fbxw7","Fcar","Fcer1a","Fcgbp","Fcer1g","Fcf1","Fcgbpl1","Fcer2","Fcgr1a","Fcgr2a","Fcho1","Fcho2","Fcgr2b","Fcgrt","Fcgr3a","Fchsd1","Fchsd2","Fcmr","Fcnb","Fcna","Fcrl5","Fcrl2","Fcrl1","Fcrl6","Fcrla","Fcrlb","Fdcsp","Fdxacb1","Fdx1l","Fdx1","Fdft1","Fdps","Fdxr","Fem1a","Fendrr","Fem1b","Fem1c","Fech","Fen1","Fer1l4","Ferd3l","Fer","Fermt1","Fer1l6","Fer1l5","Fermt3","Fermt2","Fes","Fev","Fetub","Fezf1","Fezf2","Fez1","Fez2","Ffar1","Ffar2","Ffar3","Ffar4","Fgd2","Fgd3","Fgd1","Fgb","Fga","Fgd4","Fgd5","Fgd6","Fgf11","Fgf12","Fgf10","Fgf1","Fgf13","Fgf14","Fgf16","Fgf17","Fgf18","Fgf19","Fgf20","Fgf22","Fgf21","Fgf3","Fgf23","Fgf4","Fgf5","Fgf6","Fgf7","Fgf2","Fgfr1-ps1","Fgfbp1","Fgfbp3","Fgf8","Fgf9","Fgfr1op2","Fgfr1op","Fgfr1","Fgfrl1","Fgfr3","Fgfr4","Fgg","Fgl1","Fgfr2","Fggy","Fgl2","Fh","Fgr","Fhdc1","Fhad1","Fhit","Fhl1","Fhl3","Fhl4","Fhl2","Fhl5","Fibcd1","Fhod1","Fhod3","Fibcd1l1","Fibin","Fibp","Ficd","Figla","Fign","Fig4","Fignl2","Fignl1","Filip1","Filip1l","Fip1l1","Fitm1","Fitm2","Fjx1","Fiz1","Fis1","Fkbp10","Fkbp11","Fkbp14","Fkbp15","Fkbp3","Fkbp1b","Fkbp2","Fkbp1a","Fkbp4","Fkbp6","Fkbp5","Fkbp7","Fkbp9","Fkbp8","Fkbpl","Fkrp","Flad1","Flg","Flg2","Fktn","Flcn","Flii","Fli1","Flnc","Flna","Flrt1","Flnb","Flot1","Flrt3","Flrt2","Flot2","Flvcr1","Flt3lg","Flywch2","Flywch1","Flvcr2","Flt3","Flt4","Fmc1","Flt1","Fmn2","Fmn1","Fmnl2","Fmnl1","Fmnl3","Fmo1","Fmo13","Fmo2","Fmo3","Fmo4","Fmo6","Fmo5","Fmo9","Fmod","Fmr1nb","Fn3k","Fn3krp","Fmr1","Fnbp1","Fnbp4","Fnbp1l","Fnd3c2","Fndc1","Fndc10","Fndc11","Fn1","Fndc3b","Fndc3c1","Fndc3a","Fndc5","Fndc7","Fndc8","Fndc9","Fnip1","Fnip2","Fnta","Fntb","Folr2","Fopnl","Focad","Folh1","Folr1","Fosl1","Foxb1","Fosl2","Fosb","Foxb2","Fos","Foxd1","Foxc2","Foxc1","Foxd2","Foxa3","Foxd3","Foxa2","Foxa1","Foxd4","Foxe1","Foxe3","Foxf2","Foxf1","Foxi1","Foxh1","Foxg1","Foxi2","Foxi3","Foxj1","Foxj2","Foxk1","Foxj3","Foxl1","Foxk2","Foxl2","Foxm1","Foxn1","Foxn2","Foxn4","Foxn3","Foxo6","Foxo4","Foxo1","Foxo3","Foxp4","Foxp3","Foxq1","Foxr1","Foxred1","Foxr2","Foxred2","Foxs1","Fpgs","Fpgt","Fpr-rs3","Fpr-rs4","Fpr-rs6","Fpr1","Fpr2l","Fpr3","Fpr2","Fra10ac1","Foxp2","Foxp1","Frat1","Frat2","Fras1","Frg1l1","Frem2","Frem3","Frem1","Frg1","Frg2","Frmd1","Frk","Frmd4a","Frmd3","Frmd6","Frmd4b","Frmd5","Frmd7","Frmd8","Frmpd3","Frmpd2","Frmpd1","Frrs1l","Frrs1","Frmpd4","Frs3","Frs2","Fsbp","Fry","Frzb","Fscb","Fryl","Fscn1","Fscn2","Fscn3","Fsd1","Fsd1l","Fsd2","Fsip2","Fshb","Fsip1","Fsip2-ps1","Fshr","Fst","Fstl1","Fstl3","Fstl4","Fthl17c","Fstl5","Ftcd","Fthl17e","Ftl1l1","Ftmt","Fto","Ftsj1","Ftl1","Ftsj3","Fth1","Ftx","Fubp1","Fubp3","Fuca2","Fuk","Fundc1","Fundc2","Fuom","Fuca1","Fut1","Fus","Fut10","Fut11","Fut7","Fut4","Fut2","Fut9","Fut8","Fxc1-ps1","Fuz","Fv1","Fxr1","Fxn","Furin","Fxr2","Fxyd3","Fxyd4","Fxyd1","Fxyd5","Fxyd2","Fxyd7","Fxyd6","Fyb2","Fyco1","Fyb1","Fzd10","Fyttd1","Fzd1","Fzd2","Fyn","Fzd3","Fzd5","Fzd6","Fzd7","Fzd4","Fzd8","Fzd9","Fzr1","G0s2","G2e3","G4","G3bp1","G3bp2","G6pc2","G6pc","G7e-ps1","G6pc3","G8","Gab1","Gaa","Gab2","G6pd","Gabarap","Gabarapl1","Gabarapl2","Gabpa","Gabpb1","Gabpb1l","Gabbr2","Gabpb2","Gabbr1","Gabra2","Gabra1","Gabra4","Gabra3","Gabra6","Gabra5","Gabrb1","Gabrb2","Gabrb3","Gabrd","Gabre","Gabrg1","Gabrg3","Gabrg2","Gabrp","Gabrq","Gabrr1","Gabrr3","Gabrr2","Gad2","Gadd45b","Gadd45a","Gad1","Gadd45g","Gadl1","Gadd45gip1","Gak","Gal3st2","Gal3st3","Gal3st1","Gal3st4","Gal","Galc","Gale","Galk1","Galm","Galk2","Galns","Galnt1","Galnt10","Galnt11","Galnt12","Galnt15","Galnt14","Galnt16","Galnt13","Galnt17","Galnt18","Galnt3","Galnt2","Galnt4","Galnt5","Galnt6","Galnt7","Galnt9","Galntl5","Galp","Galntl6","Galr1","Galr3","Galr2","Gan","Gamt","Galt","Ganab","Ganc","Gapdh-ps1","Gapdh-ps2","Gapt","Gap43","Gapvd1","Gapdhs","Gar1","Garem1","Garem2","Gas1","Garnl3","Gapdh","Gars","Gart","Gas2","Gas2l1","Gas2l2","Gas5","Gas2l3","Gas8","Gast","Gas7","Gas6","Gata1","Gata2","Gata3","Gata5","Gatad1","Gatad2a","Gata6","Gata4","Gatad2b","Gatb","Gatc","Gatd1","Gba2","Gatm","Gba3","Gbe1","Gba","Gbgt1","Gbp1","Gbf1","Gbp3","Gbp2","Gbp4","Gbx1","Gbp6","Gbp5","Gbx2","Gca","Gcat","Gcc1","Gc","Gcc2","Gcdh","Gcfc2","Gcg","Gchfr","Gch1","Gcgr","Gckr","Gclm","Gclc","Gck","Gcm1","Gcm2","Gcn1l1","Gcnt1","Gcnt3","Gcnt2","Gcnt4","Gcnt6","Gcnt7","Gcsam","Gcsh","Gdap1l1","Gda","Gdap1","Gdap2","Gde1","Gdf1","Gdf10","Gdf11","Gdf15","Gdf2","Gdf3","Gdf5","Gdf6","Gdf7","Gdf9","Gdpd1","Gdi1","Gdpd3","Gdpd2","Gdi2","Gdpd4","Gdpd5","Gdpgp1","Gem","Gdnf","Gemin6","Gemin4","Gemin2","Gemin5","Gemin7","Gemin7l1","Gemin8","Gen1","Get4","Gfer","Gfi1","Gfi1b","Gfap","Gfm2","Gfm1","Gfod1","Gfod2","Gfpt1","Gfpt2","Gfra1","Gfy","Gfral","Gfra3","Gfra2","Gfra4","Gga1","Gga2","Ggact","Gga3","Ggn","Ggct","Ggh","Ggcx","Ggnbp1","Ggnbp2","Ggps1","Ggt6","Ggt5","Ggt7","Ggt1","Ggta1","Ggta1l1","Ghdc","Ghitm","Gh1","Ghrhr","Ghr","Ghrh","Ghsr","Gid4","Gid8","Ghrl","Gif","Gimap4","Gigyf1","Gigyf2","Gimap1","Gimap6","Gimap5","Gimap7","Gimd1","Gimap9","Gimap8","Gin1","Ginm1","Gins1","Gins2","Gins3","Gins4","Giot1","Gip","Gipc2","Gipc3","Gipc1","Gipr","Git1","Git2","Gja10","Gja3","Gja4","Gja6","Gja5","Gja8","Gjb1","Gjb4","Gjb5","Gjb2","Gjb6","Gjc1","Gjc2","Gjc3","Gja1","Gjd3","Gjd2","Gje1","Gjb3","Gjd4","Gk2","Gk5","Gk","Gkn1","Gkap1","Gkn3","Gkn2","Gla","Glb1l","Glb1l2","Glb1l3","Glcci1","Glce","Gldn","Gldc","Gle1-ps1","Gle1","Glg1","Gli1","Glb1","Gli2","Gli4","Glipr1","Glipr1l1","Gli3","Glipr1l2","Glis1","Glipr2","Glis3","Glis2","Glmp","Glmn","Glo1","Glod4","Glod5","Glp2r","Glp1r","Glra1","Glra2","Glra4","Glra3","Glrb","Glrx2","Glrx","Glrx5","Glt1d1","Glt6d1","Glrx3","Gls2","Gls","Glt8d1","Glt8d2","Gltp","Gltpd2","Glyatl1","Glyat","Glul","Glud1","Glyatl2","Glyctk","Glycam1","Glyatl3","Gm2a","Glyr1","Gm5471","Gmcl1l","Gmcl1","Gmds","Gmeb1","Gmeb2","Gmfg","Gmfb","Gmip","Gml","Gmnc","Gmppb","Gmnn","Gmppa","Gmpr","Gmpr2","Gna12","Gmps","Gna11","Gna14","Gna13","Gna15","Gnai1","Gnai3","Gnal","Gnai2","Gnao1","Gnat1","Gnaq","Gnat3","Gnat2","Gnaz","Gnas","Gnb1l","Gnb1","Gnb3","Gnb2","Gnb4","Gnb5","Gne","Gng11","Gng10","Gng13","Gng14","Gng12","Gng2","Gng3","Gng4","Gng7","Gng5","Gngt1","Gngt2","Gng8","Gnl1","Gnl3","Gnl2","Gnl3l","Gnpda1","Gnmt","Gnpda2","Gnpat","Gnpnat1","Gnptab","Gnptg","Golga1","Gns","Gnrhr","Gnrh1","Golga2","Golga3","Golga5","Golga4","Golga7b","Golga7","Golgb1","Golm1","Golim4","Golph3","Golph3l","Golt1a","Golt1b","Gon7","Gon4l","Gopc","Gorab","Gorasp2","Gorasp1","Gosr1","Gosr2","Got1l1","Got1","Gp1ba","Gp1bb","Got2","Gp2","Gp6","Gp5","Gp9","Gpalpp1","Gpa33","Gpaa1","Gpank1","Gpat2","Gpat3","Gpam","Gpat4","Gpatch11","Gpatch1","Gpatch2","Gpatch3","Gpatch2l","Gpatch4","Gpbar1","Gpatch8","Gpbp1l2","Gpbp1l1","Gpbp1","Gpc2","Gpc1","Gpc4","Gpc3","Gpc5","Gpcpd1","Gpd1l","Gpc6","Gpd1","Gpd2","Gpha2","Gphb5","Gper1","Gpihbp1","Gpkow","Gpi","Gphn","Gpld1","Gpn1","Gpm6b","Gpm6a","Gpn2","Gpn3","Gpr1","Gpr112l","Gpr101","Gpnmb","Gpr107","Gpr108","Gpr119","Gpr12","Gpr137","Gpr135","Gpr132","Gpr137b","Gpr141","Gpr137c","Gpr139","Gpr142","Gpr146","Gpr143","Gpr149","Gpr15","Gpr151","Gpr152","Gpr150","Gpr153","Gpr157","Gpr156","Gpr155","Gpr158","Gpr160","Gpr161","Gpr162","Gpr165","Gpr17","Gpr171","Gpr173","Gpr174","Gpr176","Gpr18","Gpr179","Gpr180","Gpr20","Gpr183","Gpr19","Gpr182","Gpr21","Gpr25","Gpr22","Gpr26","Gpr32","Gpr31","Gpr27","Gpr3","Gpr33","Gpr34","Gpr35","Gpr37l1","Gpr37","Gpr4","Gpr39","Gpr45","Gpr52","Gpr50","Gpr55","Gpr6","Gpr61","Gpr62","Gpr63","Gpr65","Gpr68","Gpr75","Gpr82","Gpr83","Gpr84","Gpr87","Gpr85","Gpr88","Gprasp1","Gpr89b","Gprasp2","Gprc5a","Gprc5b","Gprc5d","Gprc5c","Gprin1","Gprc6a","Gprin2","Gprin3","Gps2","Gps1","Gpsm2","Gpsm3","Gpsm1","Gpt","Gpt2","Gpx2-ps1","Gpx2-ps2","Gpx2","Gpx4-ps1","Gpx4-ps2","Gpx3","Gpx4-ps3","Gpx4","Gpx1","Gpx5","Gpx7","Gpx6","Gpx8","Gramd1a","Gramd1c","Gramd2","Gramd1b","Gramd3","Grap","Gramd4","Grap2","Grasp","Grb10","Grb14","Grb7","Greb1l","Greb1","Grcc10","Grb2","Grhl1","Grem1","Grem2","Grhl2","Grhl3","Grhpr","Grid1","Gria3","Gria4","Grid2ip","Grifin","Gria1","Grid2","Gria2","Grik3","Grik4","Grik1","Grik5","Grik2","Grin3a","Grin2c","Grin2d","Grin3b","Grina","Grin2a","Grin2b","Grin1","Grip2","Gripap1","Grk1","Grip1","Grk4","Grk3","Grk2","Grlf1-ps1","Grk5","Grk6","Grm2","Grm4","Grm3","Grm1","Grm6","Grm5","Grm7","Grm8","Grn","Grpel1","Grp","Grpel2","Grpr","Grsf1","Grwd1","Grxcr1","Grxcr2","Grtp1","Gsc","Gsap","Gsc2","Gsdmc","Gsdma","Gsdmd","Gsdme","Gsg1","Gsg1l2","Gse1","Gsg1l","Gskip","Gsk3a","Gspt2","Gspt1","Gsn","Gss","Gsr","Gsta2","Gsta5","Gsta1","Gsta3","Gsta4","Gstcd","Gsk3b","Gstk1","Gstm3","Gstm3l","Gstm4","Gstm5","Gstm6","Gstm2","Gstm6l","Gstm7","Gstp-ps1","Gsto1","Gsto2","Gstm1","Gstt3","Gstt2","Gstt4","Gsx1","Gstt1","Gstz1","Gstp1","Gsx2","Gtdc1","Gtf2a1","Gtf2a1l","Gtf2a2","Gtf2e1","Gtf2b","Gtf2e2","Gtf2f1","Gtf2f2","Gtf2h1","Gtf2h2","Gtf2h3","Gtf2h4","Gtf2h5","Gtf2i","Gtf2ird2","Gtf3a","Gtf3c1","Gtf2ird1","Gtf3c2","Gtf3c4","Gtf3c3","Gtf3c5","Gtf3c6","Gtpbp1","Gtpbp10","Gtpbp2","Gtpbp3","Gtpbp6","Gtpbp8","Gtpbp4","Gtse1","Gtsf1","Gtsf1l","Guca1a","Guca1b","Guca2a","Gucd1","Guca2b","Gucy1a2","Gucy1a3","Gucy2d","Gucy1b2","Gucy2c","Gucy1b3","Gucy2e","Gucy2f","Gucy2g","Guk1","Guf1","Gulo","Gulp1","Gvin1","Gusb","Gvinp1","Gxylt1","Gxylt2","Gykl1","Gyg1","Gypa","Gypc","Gys1","Gys2","Gzf1","Gzma","Gzmbl1","Gzmbl3","Gzmbl2","Gzmb","Gzmf","Gzmc","Gzmk","Gzmm","Gzmn","H1fnt","H1foo","H1f0","H19","H2afb3","H1fx","H2afj","H2afv","H2afy","H2afy2","H2afx","H2afz","H3f3c","H3f3a","H3f3b","H6pd","Habp4","Habp2","Haao","Hacd1","Hacd2","Hacd3","Hacd4","Hace1","Hacl1","Hadh","Hadhb","Hadha","Hagh","Haghl","Hal","Hand1","Hand2","Hao1","Hamp","Hao2","Hap1","Hapln1","Hapln2","Hapln3","Harbi1","Hapln4","Hars","Hars2","Has1","Has2","Has3","Haspin","Hat1","Haus2","Haus1","Haus3","Haus4","Haus5","Haus8-ps1","Haus7","Haus8","Hax1","Havcr2","Havcr1","Hba-a2","Hba-a3","Hba-a1","Hbb-b1","Hbe2","Hbe1","Hbegf","Hbq1","Hbg1","Hbq1b","Hbp1","Hbz","Hbs1l","Hbb","Hcar1","Hcar2","Hccs","Hcfc1r1","Hcfc1","Hcfc2","Hcls1","Hck","Hcn3","Hcn2","Hcn1","Hcn4","Hcst","Hcrtr2","Hcrtr1","Hcrt","Hdac11","Hdac10","Hdac1l","Hdac1","Hdac2","Hdac3","Hdac4","Hdac5","Hdac7","Hdac6","Hdac8","Hddc2","Hddc3","Hdc","Hdac9","Hdgfl1","Hdgf","Hdgfl3","Hdgfl2","Hdhd3","Hdhd2","Hdhd5","Hdx","Heatr1l1","Hdlbp","Heatr3","Heatr4","Heatr5a","Heatr5b","Heatr6","Heatr9","Hebp2","Hebp1","Heatr1","Heca","Hectd3","Hectd2","Hecw2","Hectd1","Hecw1","Hectd4","Helb","Heg1","Hells","Helq","Helz2","Helt","Helz","Hemgn","Hemk1","Henmt1","Hepacam","Hepacam2","Hephl1","Heph","Herc3","Herc1","Herc6","Herc2","Herc4","Herpud2","Herpud1","Hes2","Hes3","Hes5","Hes1","Hes6","Hes7","Hesx1","Hexa","Hexim1","Hexdc","Hexb","Hey1","Hexim2","Hey2","Heyl","Hfe2","Hgd","Hfm1","Hfe","Hgh1","Hgfac","Hgs","Hgsnat","Hhat","Hhex","Hhatl","Hgf","Hhipl1","Hhip","Hhipl2","Hhla2","Hiatl3","Hic1","Hibadh","Hibch","Hid1","Hic2","Higd1c","Hif1an","Higd1a","Higd1b","Hif3a","Higd2a","Hiat1-ps1","Higd2al1","Hilpda","Hils1","Hikeshi","Hint1","Hinfp","Hint1-ps1","Hint2","Hint3","Hipk2","Hip1r","Hipk1","Hip1","Hipk4","Hipk3","Hira","Hirip3","Hist1h1a","Hist1h1b","Hist1h1d","Hist1h1c","Hist1h1e","Hist1h1t","Hist1h2ac","Hist1h2af","Hist1h2aa","Hist1h2ah","Hist1h2ai","Hist1h2ail1","Hif1a","Hist1h2ak","hist1h2ail2","Hist1h2an","Hist1h2ba","Hist1h2bcl1","Hist1h2bd","Hist1h2ao","Hist1h2bg","Hist1h2bh","Hist1h2bk","Hist1h2bl","Hist1h2bo","Hist1h2bq","Hist1h3b","Hist1h3a","Hist1h4a","Hist2h2aa2","Hist1h4m","Hist1h4b","Hist2h2aa3","Hist2h2bb","Hist2h2ab","Hist2h2ac","Hist2h2be","Hist2h3c2","Hist2h4a","Hist2h4","Hist3h2ba","Hist3h2a","Hist3h2bb","Hist3h3","Hist4h4","Hivep1","Hivep3","Hjurp","Hivep2","Hk1","Hk2","Hk3","Hkdc1","Hlcs","Hlf","Hltf","Hlx","Hm13","Hmbox1","Hmbs","Hmces","Hmcn1","Hmcn2","Hmg1l1","Hmg20a","Hmg20b","Hmga1","Hmga2","Hmgb1","Hmgb1-ps1","Hmgb1-ps2","Hmgb1-ps3","Hmgb1-ps4","Hmgb2","Hmgb2l1","Hmgb3","Hmgb4","Hmgcl","Hmgcll1","Hmgcr","Hmgcs1","Hmgcs2","Hmgn1","Hmgn2","Hmgn3","Hmgn4","Hmgn5","Hmgn5b","Hmgxb3","Hmgxb4","Hmmr","Hmox1","Hmox2","Hmox2-ps1","Hmx1","Hmx2","Hmx3","Hnf1a","Hnf1b","Hnf4a","Hnf4g","Hnmt","Hnrnpa0","Hnrnpa1","Hnrnpa1-ps1","Hnrnpa2b1","Hnrnpa3","Hnrnpa3-ps1","Hnrnpab","Hnrnpc","Hnrnpd","Hnrnpdl","Hnrnpf","Hnrnph1","Hnrnph2","Hnrnph3","Hnrnpk","Hnrnpl","Hnrnpll","Hnrnpm","Hnrnpr","Hnrnpu","Hnrnpul1","Hnrnpul2","Hoga1","Homer1","Homer2","Homer3","Homez","Hook1","Hook2","Hook3","Hopx","Hormad1","Hormad2","Hotairm1","Hoxa1","Hoxa10","Hoxa11","Hoxa11-as","Hoxa13","Hoxa2","Hoxa3","Hoxa4","Hoxa5","Hoxa6","Hoxa7","Hoxa9","Hoxaas3","Hoxb1","Hoxb13","Hoxb2","Hoxb3","Hoxb4","Hoxb5","Hoxb5os","Hoxb6","Hoxb7","Hoxb8","Hoxb9","Hoxc10","Hoxc11","Hoxc12","Hoxc13","Hoxc4","Hoxc5","Hoxc6","Hoxc8","Hoxc9","Hoxd1","Hoxd10","Hoxd11","Hoxd12","Hoxd13","Hoxd3","Hoxd4","Hoxd8","Hoxd9","Hp","Hp1bp3","Hpca","Hpcal1","Hpcal4","Hpd","Hpdl","Hpf1","Hpgd","Hpgds","Hpn","Hprt1","Hps1","Hps3","Hps4","Hps5","Hps6","Hpse","Hpse2","Hpx","Hr","Hras","Hrasls","Hrasls5","Hrc","Hrct1","Hrg","Hrh1","Hrh2","Hrh3","Hrh4","Hrk","Hrnr","Hs1bp3","Hs2st1","Hs3st1","Hs3st2","Hs3st3a1","Hs3st3b1","Hs3st4","Hs3st5","Hs3st6","Hs6st1","Hs6st2","Hs6st3","Hsbp1","Hsbp1l1","Hsc70-ps1","Hsc70-ps2","Hscb","Hsd11b1","Hsd11b2","Hsd17b1","Hsd17b10","Hsd17b11","Hsd17b12","Hsd17b13","Hsd17b14","Hsd17b2","Hsd17b3","Hsd17b4","Hsd17b6","Hsd17b7","Hsd17b8","Hsd3b1","Hsd3b2","Hsd3b3","Hsd3b5","Hsd3b7","Hsdl1","Hsdl2","Hsf1","Hsf2","Hsf2bp","Hsf4","Hsf5","Hsfy2","Hsh2d","Hsp90aa1","Hsp90ab1","Hsp90b1","Hspa12a","Hspa12b","Hspa13","Hspa14","Hspa1a","Hspa1b","Hspa1l","Hspa2","Hspa4","Hspa4l","Hspa5","Hspa8","Hspa8-ps1","Hspa9","Hspb1","Hspb11","Hspb2","Hspb3","Hspb6","Hspb7","Hspb8","Hspb9","Hspbap1","Hspbp1","Hspd1","Hspd1-ps1","Hspd1-ps10","Hspd1-ps11","Hspd1-ps12","Hspd1-ps13","Hspd1-ps14","Hspd1-ps15","Hspd1-ps16","Hspd1-ps17","Hspd1-ps18","Hspd1-ps19","Hspd1-ps2","Hspd1-ps20","Hspd1-ps21","Hspd1-ps22","Hspd1-ps23","Hspd1-ps25","Hspd1-ps26","Hspd1-ps27","Hspd1-ps28","Hspd1-ps29","Hspd1-ps3","Hspd1-ps31","Hspd1-ps32","Hspd1-ps4","Hspd1-ps5","Hspd1-ps6","Hspd1-ps7","Hspd1-ps8","Hspd1-ps9","Hspe1","Hspg2","Hsph1","Htatip2","Htatsf1","Htr1a","Htr1b","Htr1d","Htr1f","Htr2a","Htr2b","Htr2c","Htr3a","Htr3b","Htr4","Htr5a","Htr5b","Htr6","Htr7","Htra1","Htra2","Htra3","Htra4","Htt","Hunk","Hus1","Hus1b","Huwe1","Hvcn1","Hyal1","Hyal2","Hyal3","Hyal4","Hyal5","Hyal6","Hydin","Hyi","Hykk","Hyls1","Hyou1","Hypk","Hypm","Iah1","Iapp","Iars","Iars2","Iba57","Ibsp","Ibtk","Ica1","Ica1l","Icam1","Icam2","Icam4","Icam5","Ice1","Ice2","Ick","Icmt","Icos","Icoslg","Id1","Id2","Id3","Id4","Ide","Idh1","Idh2","Idh3a","Idh3B","Idh3g","Idi1","Idi2","Idnk","Ido1","Ido2","Ids","Idua","Ier2","Ier3","Ier3ip1","Ier5","Ier5l","Iffo1","Iffo2","Ifi203-ps1","Ifi27","Ifi27l2b","Ifi30","Ifi35","Ifi44","Ifi44l","Ifi47","Ifih1","Ifit1","Ifit1bl","Ifit2","Ifit3","Ifitm1","Ifitm10","Ifitm2","Ifitm3","Ifitm5","Ifitm6","Ifitm7","Ifna1","Ifna11","Ifna16l1","Ifna2","Ifna4","Ifna5","Ifnar1","Ifnar2","Ifnb1","Ifne","Ifng","Ifngr1","Ifngr2","Ifnk","Ifnl1","Ifnl3","Ifnlr1","Ifrd1","Ifrd2","Ift122","Ift140","Ift172","Ift20","Ift22","Ift27","Ift43","Ift46","Ift52","Ift57","Ift74","Ift80","Ift81","Ift88","Igbp1","Igbp1b","Igdcc3","Igdcc4","Igf1","Igf1r","Igf2","Igf2bp1","Igf2bp2","Igf2bp3","Igf2r","Igfals","Igfbp1","Igfbp2","Igfbp3","Igfbp4","Igfbp5","Igfbp6","Igfbp7","Igfbpl1","Igfl3","Igflr1","Igfn1","IgG-2a","Igh-6","Ighe","Ighg1","Ighmbp2","Ighv","Igip","Igkc","Igkv28","Igl","Igll1","Iglon5","Igsf1","Igsf10","Igsf11","Igsf21","Igsf22","Igsf23","Igsf3","Igsf5","Igsf6","Igsf7","Igsf8","Igsf9","Igsf9b","Igtp","Ihh","Ik","Ikbip","Ikbkb","Ikbke","Ikbkg","Ikzf1","Ikzf2","Ikzf3","Ikzf4","Ikzf5","Il10","Il10ra","Il10rb","Il11","Il11ra1","Il12a","Il12b","Il12rb1","Il12rb2","Il13","Il13ra1","Il13ra2","Il15","Il15ra","Il16","Il17a","Il17b","Il17c","Il17d","Il17f","Il17ra","Il17rb","Il17rc","Il17rd","Il17re","Il18","Il18bp","Il18r1","Il18rap","Il19","Il1a","Il1b","Il1f10","Il1r1","Il1r2","Il1rap","Il1rapl1","Il1rapl2","Il1rl1","Il1rl2","Il1rn","Il2","Il20","Il20ra","Il20rb","Il21","Il21r","Il22","Il22ra1","Il22ra2","Il23a","Il23r","Il24","Il25","Il27","Il27ra","Il2ra","Il2rb","Il2rg","Il3","Il31","Il31ra","Il33","Il34","Il36a","Il36b","Il36g","Il36rn","Il3ra","Il4","Il4i1","Il4r","Il5","Il5ra","Il6","Il6r","Il6st","Il7","Il7r","Il9","Il9r","Ildr1","Ildr2","Ilf2","Ilf3","Ilk","Ilkap","Ilvbl","Immp1l","Immp2l","Immt","Imp3","Imp4","Impa1","Impa2","Impact","Impad1","Impdh1","Impdh2","Impg1","Impg2","Ina","Inafm1","Inafm2","Inca1","Incenp","Inf2","Ing1","Ing2","Ing3","Ing4","Ing5","Inha","Inhba","Inhbb","Inhbc","Inhbe","Inip","Inmt","Ino80","Ino80b","Ino80c","Ino80d","Ino80e","Inpp1","Inpp4a","Inpp4b","Inpp5a","Inpp5b","Inpp5d","Inpp5e","Inpp5f","Inpp5j","Inpp5k","Inppl1","Ins1","Ins2","Insc","Insig1","Insig2","Insl3","Insl5","Insl6","Insm1","Insm2","Insr","Insrr","Ints1","Ints10","Ints11","Ints12","Ints13","Ints14","Ints2","Ints3","Ints4","Ints5","Ints6","Ints6l","Ints7","Ints8","Ints9","Intu","Invs","Ip6k1","Ip6k2","Ip6k3","Ipcef1","Ipmk","Ipo11","Ipo13","Ipo4","Ipo5","Ipo7","Ipo9","Ipp","Ippk","Iqank1","Iqca1","Iqca1l","Iqcb1","Iqcc","Iqcd","Iqce","Iqcf1","Iqcf3","Iqcf5","Iqcf6","Iqcg","Iqch","Iqck","Iqgap1","Iqgap2","Iqgap3","Iqsec1","Iqsec2","Iqsec3","Iqub","Irak1","Irak1bp1","Irak2","Irak3","Irak4","Ireb2","Irf1","Irf2","Irf2bp1","Irf2bp2","Irf2bpl","Irf3","Irf4","Irf5","Irf6","Irf7","Irf8","Irf9","Irgc","Irgm","Irgm2","Irgq","Irs1","Irs2","Irs3","Irs4","Irx1","Irx2","Irx3","Irx4","Irx5","Irx6","Isca1","Isca1-ps1","Isca2","Isca2-ps1","Iscu","Isg15","Isg20","Isg20l2","Isl1","Isl2","Islr","Islr2","Ism1","Ism2","Isoc1","Isoc2b","Ispd","Ist1","Isx","Isy1","Isyna1","Itch","Itfg1","Itfg2","Itga1","Itga10","Itga11","Itga2","Itga2b","Itga3","Itga4","Itga5","Itga6","Itga7","Itga8","Itga9","Itgad","Itgae","Itgal","Itgam","Itgav","Itgax","Itgb1","Itgb1bp1","Itgb1bp2","Itgb2","Itgb3","Itgb3bp","Itgb4","Itgb5","Itgb6","Itgb7","Itgb8","Itgbl1","Itih1","Itih2","Itih3","Itih4","Itih6","Itm2a","Itk","Itln1","Itm2c","Itm2b","Itpka","Itpkc","Itpk1","Itpa","Itpkb","Itpr2","Itpripl2","Itpripl1","Itpr1","Ivd","Itsn2","Itsn1","Itpr3","Ivl","Ivns1abp","Iws1","Iyd","Izumo2","Izumo1r","Izumo3","Izumo1","Izumo4","Jade2","Jade1","Jade3","Jagn1","Jag2","Jag1","Jak1","Jakmip2","Jakmip1","Jak3","Jam2","Jakmip3","Jam3","Jaml","Jak2","Jazf1","Jarid2","Jcad","Jchain","Jdp2","Jhy","Jkamp","Jmjd4","Jmjd1c","Jmjd6","Jmjd8","Jmy","Josd1","Josd2","Jph1","Jmjd7","Jph3","Jph2","Jph4","Jpt1","Jpx","Jpt2","Jrk","Jrkl","Jsrp1","Jtb","Junb","Ka11","Jund","Jup","Kank2","Kank1","Jun","Kank4","Kank3","Kalrn","Kansl1","Kansl1l","Kantr","Kansl2","Kansl3","Kap","Kat14","Kars","Kat2b","Kat2a","Kat6b","Kat7","Kat5","Kat6a","Kat8","Katna1","Katnal1","Katnal2","Katnbl1","Katnb1","Kazald1","Kazn","Kb15","Kb23","Kbtbd12","Kbtbd11","Kbtbd13","Kbtbd2","Kbtbd3","Kbtbd4","Kbtbd6","Kbtbd7","Kbtbd8","Kcmf1","Kcna10","Kcna1","Kcna3","Kcna2","Kcna4","Kcna5","Kcna6","Kcna7","Kcnab1","Kcnab2","Kcnab3","Kcnb2","Kcnc1","Kcnb1","Kcnc3","Kcnc2","Kcnd1","Kcnc4","Kcnd2","Kcne2","Kcne3","Kcne4","Kcne1","Kcnd3","Kcnf1","Kcne5","Kcng1","Kcng3","Kcng4","Kcng2","Kcnh4","Kcnh3","Kcnh2","Kcnh1","Kcnh5","Kcnh6","Kcnh7","Kcnh8","Kcnip1","Kcnip3","Kcnip2","Kcnip4","Kcnj10","Kcnj1","Kcnj13","Kcnj12","Kcnj14","Kcnj16","Kcnj15","Kcnj11","Kcnj2","Kcnj4","Kcnj3","Kcnj5","Kcnj9","Kcnk1","Kcnj8","Kcnj6","Kcnk12","Kcnk10","Kcnk13","Kcnk15","Kcnk16","Kcnk18","Kcnk3","Kcnk4","Kcnk2","Kcnk7","Kcnk5","Kcnk6","Kcnk9","Kcnmb3","Kcnmb1","Kcnmb2","Kcnmb4","Kcnn1","Kcnn2","Kcnma1","Kcnn3","Kcnq4","Kcnrg","Kcnn4","Kcnq3","Kcnq2","Kcnq5","Kcnq1","Kcns1","Kcns2","Kcns3","Kcnv1","Kcnv2","Kcnt1","Kcnt2","Kcnu1","Kcp","Kctd10","Kctd11","Kctd1","Kctd12","Kctd14","Kctd13","Kctd15","Kctd16","Kctd2","Kctd19","Kctd17","Kctd20","Kctd18","Kctd21","Kctd4","Kctd3","Kctd5","Kctd7","Kctd6","Kctd8","Kctd9","Kdelc1","Kdelr1","Kdelc2","Kdf1","Kdelr3","Kdelr2","Kdm1b","Kdm1a","Kdm2a","Kdm2b","Kdm3a","Kdm3b","Kdm4a","Kdm4b","Kdm4dl2","Kdm4e","Kdm4d","Kdm4c","Kdm5a","Kdm7a","Kdm5c","Kdm5d","Kdm5b","Kdm6a","Kdm8","Kdm6b","Kdsr","Kel","Khdc1","Kera","Khdc1b","Khdc3","Keap1","Khdc4","Khps1a","Khdrbs2","Khnyn","Khdrbs3","Khk","Kdr","Khdrbs1","Kiaa1671","Khsrp","Kiaa0408L","Kif11","Kif12","Kif13a","Kidins220","Kif13b","Kif14","Kif15","Kif16b","Kif17","Kif18a","Kif18b","Kif19","Kif1a","Kif1bp","Kif1c","Kif20a","Kif1b","Kif20b","Kif21b","Kif21a","Kif22","Kif23","Kif24","Kif26a","Kif26b","Kif27","Kif28p","Kif2a","Kif2b","Kif3b","Kif2c","Kif3c","Kif4b","Kif4a","Kif3a","Kif5b","Kif5a","Kif6","Kif7","Kif5c","Kif9","Kifap3","Kifc1","Kin","Kir3dl1","Kifc2","Kirrel2","Kifc3","Kirrel1","Kirrel3","Kiss1","Kiz","Kiss1r","Klb","Kitlg","Kl","Klc1","Klc2","Kit","Klc3","Klf1","Klc4","Klf10","Klf11","Klf13","Klf12","Klf14","Klf17","Klf15","Klf16","Klf2","Klf5-ps1","Klf3","Klf4","Klf5","Klf5-ps2","Klf7","Klf6","Klf8","Klf9","Klhdc1","Klhdc2","Klhdc10","Klhdc3","Klhdc4","Klhdc7a","Klhdc7b","Klhdc8b","Klhdc8a","Klhdc9","Klhl1","Klhl11","Klhl10","Klhl12","Klhl14","Klhl13","Klhl15","Klhl17","Klhl18","Klhl2","Klhl20","Klhl21","Klhl22","Klhl23","Klhl24","Klhl25","Klhl28","Klhl26","Klhl29","Klhl3","Klhl31","Klhl33","Klhl32","Klhl30","Klhl34","Klhl35","Klhl36","Klhl4","Klhl40","Klhl38","Klhl42","Klhl41","Klhl5","Klhl6","Klhl7","Klhl9","Klhl8","Klk11","Klk12","Klk10","Klk13","Klk1","Klk14","Klk15","Klk1c10","Klk1c4","Klk1b3","Klk1c3","Klk1c12","Klk1c2","Klk1c6","Klk1c8","Klk5","Klk5l","Klk4","Klk1c9","Klk6","Klk7","Klk9","Klk8","Klra2","Klra1","Klkb1","Klra22","Klra5","Klrb1","Klrb1a","Klrb1b","Klrc2","Klrc1","Klrb1c","Klrc3","Klrd1","Klln","Klre1","Klrg1","Klrh1","Klri1","Klrg2","Klri2","Kmo","Kmt2a","Kmt2c","Kmt2e","Kmt2b","Kmt5b","Kmt5a","Kmt2d","Kndc1","Kncn","Kmt5c","Klrk1","Kng2l1","Kng1","Knl1","Knop1","Kng2","Knstrn","Kntc1","Kpna1","Kpna3","Kpna4","Kpna2","Kpna5","Kpna6","Kpna7","Kprp","Kpnb1","Kptn","Kremen1","Krba1","Kremen2","Kras","Krcc1","Krr1","Kri1","Krit1","Krt1","Krt10","Krt12","Krt15","Krt16","Krt18","Krt13","Krt17","Krt2","Krt19","Krt14","Krt20","Krt222","Krt23","Krt24","Krt27","Krt28","Krt26","Krt25","Krt31","Krt32","Krt33a","Krt33b","Krt35","Krt34","Krt39","Krt36","Krt42","Krt40","Krt4","Krt71","Krt72","Krt5","Krt7","Krt73","Krt76","Krt75","Krt77","Krt79","Krt80","Krt78","Krt81","Krt8","Krt82","Krt83","Krt84","Krt85","Krtap1-1","Krt9","Krt86","Krtap1-3","Krtap1-5","Krtap11-1","Krtap12-2","Krtap13-1","Krtap13-2","Krtap15-1","Krtap14","Krtap14l","Krtap16-1","Krtap16-5","Krtap17-1","Krtap19-5","Krtap20l3","Krtap2-1","Krtap2-4l","Krtap2-4","Krtap24-1","Krtap26-1","Krtap22-2","Krtap27-1","Krtap3-1","Krtap3-3","Krtap4-7","Krtap31-1","Krtap5-1","Krtap3-2","Krtap4-3","Krtap7-1","Krtap8-1","Krtap29-1","Krtcap2","Krtdap","Krtap9-1","Krtcap3","Ksr1","Kti12","Kxd1","Ksr2","Kyat3","Ky","Kyat1","Ktn1","Kynu","L1td1","L2hgdh","L3hypdh","L3mbtl2","L3mbtl4","L1cam","L3mbtl1","L3mbtl3","Lacc1","Lactb","Lactb2","Lad1-ps1","Lad1","Lair1","Lage3","Lag3","Lalba","Lama2","Lama3","Lama1","Lama4","Lamb3","Lamb1","Lamb2","Lama5","Lamc1","Lamc3","Lamc2","Lamp1","Lamp3","Lamp2","Lamp5","Lamtor1","Lamtor2","Lamtor4","Lamtor3","Lamtor5","Lancl3","Lao1","Lancl1","Lancl2","Lap3","Laptm4a","Laptm4b","Laptm5","Large2","Larp1","Large1","Larp1b","Larp4","Larp6","Larp4b","Larp7","Las1l","Lars","Lasp1","Lars2","Lat","Lats1","Lat2","Lats2","Lax1","Lbh","Layn","Lbp","Lbr","Lbx2","Lbx1","Lce1c","Lca5l","Lca5","Lce1d","Lcat","Lce1f","Lce1m","Lce1l","Lce3d","Lce3e","Lce6a","Lclat1","Lcmt1","Lcmt2","Lcn1","Lcn10","Lck","Lcn11","Lcn12","Lcn15l1","Lcn3","Lcn4","Lcn6","Lcn5","Lcn8","Lcn2","Lcn9","Lcor","Lcorl","Lctl","Lcp2","Lcp1","Lct","Ldah","Ldb1","Ldb2","Ldhal6b","Ldhd","Ldhb","Ldhc","Ldha","Ldlrad1","Ldlr","Ldlrad2","Leap2","Ldb3","Ldlrad3","Ldoc1","Ldlrad4","Ldlrap1","Lect2","Lelp1","Lemd1","Lefty1","Lekr1","Lefty2","Lemd2","Lef1","Lemd3","Lenep","Leng1","Leng8","Leo1","Leprotl1","Leprot","Letm2","Letm1","Leng9","Letmd1","Lepr","Lexm","Lfng","Lgals2","Lgals12","Lgals1","Lep","Lgals3bp","Lgals4","Lgals5","Lgals7","Lgals3","Lgals8","Lgalsl","Lgals9","Lgi1","Lgi2","Lgi3","Lgi4","Lgmn","Lgr4","Lgr6","Lgr5","Lgsn","Lhb","Lhfpl1","Lhfpl3","Lhfpl2","Lhfpl5","Lhfpl4","Lhcgr","Lhfpl6","Lhx1","Lhpp","Lhx2","Lhx4","Lhx5","Lhx3","Lhx6","Lhx8","Lhx9","Lias","Lif","Lifr","Lig1","Lig3","Lig4","Lilra5","Lilrb1","Lilrb2","Lilrb3","Lilrb4","Lilrb3a","Lilra3","Lilrc2","Lilrb3l","Lim2","Lima1","Lime1","Limd1","Limd2","Limch1","Limk1","Limk2","Lims1","Lin28c","Lin28b","Lims2","Lin28a","Lin37","Lin52","Lin54","Lin7b","Linc00176","Lin7a","Lin7c","Lin9","Linc00514","Linc01158","Linc-rbe","Lingo3","Lingo2","Lingo1","Lingo4","Lins1","Lipa","Lipe","Lipf","Liph","Lipi","Lipg","Lipk","Lipc","Lipm","Lipogenin","Lipn","Lipt1","Lipo1","Lipt2","Lix1","Litaf","Lix1l","Lkaaear1","Llgl1","Llph","Llgl2","Lman1","Lman1l","Lman2","Lman2l","Lmbr1","Lmbrd2","Lmbr1l","Lmbrd1","Lmcd1","Lmln","Lmf1","Lmf2","Lmnb2","Lmntd2","Lmnb1","Lmo1","Lmna","Lmo2","Lmntd1","Lmo3","Lmod1","Lmod2","Lmod3","Lmo4","Lmtk3","Lmtk2","Lmx1a","Lmo7","Lnc-hc","Lnc001","Lnc004","Lnc012","Lnc056","Lnc016","Lmx1b","Lnc081","Lnc134","Lnc215","Lnp1","Lnpk","Lnx2","Lonrf1","Lonp1","Lonrf2","Lonp2","Lor","Lonrf3","Loxhd1","Lox","Loxl1","Loxl2","Loxl3","Loxl4","Lpal2","Lpar2","Lpar1","Lpar4","Lpar3","Lpar5","Lpar6","Lpcat1","Lpcat2","Lpcat2b","Lpcat3","Lpin1","Lpcat4","Lpgat1","Lpin2","Lpin3","Lpo","Lppos","Lpl","Lpxn","Lpp","Lrap","Lrat","Lrba","Lrch1","Lrch2","Lrch3","Lrcol1","Lre3","Lrch4","Lrfn1","Lrfn2","Lrfn3","Lrfn4","Lrg1","Lrfn5","Lrif1","Lrguk","Lrig1","Lrig2","Lrig3","Lrit1","Lrit2","Lrit3","Lrmp","Lrmda","Lrp10","Lrp12","Lrp11","Lrp1","Lrp1b","Lrp2bp","Lrp3","Lrp4","Lrp2","Lrp6","Lrpap1","Lrp8","Lrp5","Lrpprc","Lrr1","Lrrc1","Lrrc10b","Lrrc10","Lrrc14","Lrrc14b","Lrrc15","Lrrc17","Lrrc19","Lrrc18","Lrrc2","Lrrc20","Lrrc23","Lrrc24","Lrrc25","Lrrc26","Lrrc27","Lrrc28","Lrrc29","Lrrc3","Lrrc30","Lrrc31","Lrrc32","Lrrc34","Lrrc37a","Lrrc36","Lrrc38","Lrrc39","Lrrc3c","Lrrc3b","Lrrc4","Lrrc41","Lrrc42","Lrrc40","Lrrc43","Lrrc45","Lrrc46","Lrrc47","Lrrc49","Lrrc4b","Lrrc51","Lrrc52","Lrrc4c","Lrrc55","Lrrc56","Lrrc57","Lrrc58","Lrrc59","Lrrc6","Lrrc61","Lrrc66","Lrrc63","Lrrc69","Lrrc72","Lrrc71","Lrrc7","Lrrc73","Lrrc74b","Lrrc74a","Lrrc75a","Lrrc75b","Lrrc8b","Lrrc8a","Lrrc8d","Lrrc8c","Lrrc9","Lrrc8e","Lrrd1","Lrrcc1","Lrrfip1","Lrriq1","Lrrfip2","Lrriq3","Lrriq4","Lrrk1","Lrrn1","Lrrn2","Lrrk2","Lrrn4","Lrrn3","Lrrn4cl","LRRTM1","Lrrtm2","Lrrtm3","Lrrtm4","Lrtm1","Lrsam1","Lrtm2","Lrtomt","Lrwd1","Lsamp","Lsg1","Lsm1","Lsm10","Lsm11","Lsm12","Lsm14a","Lsm2","Lsm3","Lsm14b","Lsm4","Lsm5","Lsm7","Lsm6","Lsm8","Lsmem2","Lsmem1","Lsp1","Lsr","Lst1","Lss","Lta4h","Ltb","Ltb4r","Lta","Ltb4r2","Ltbp3","Ltbp2","Ltbp1","Ltbp4","Ltbr","Ltc4s","Ltf","Ltk","Ltv1","Ltn1","Luc7l","Luc7l2","Luc7l3","Lurap1","Lurap1l","Lum","Luzp4","Luzp2","Luzp1","Lvrn","Lxn","Ly49i2","Ly49i3","Ly49i4","Ly49i5","Ly49i9","Ly49i7","Ly49s3","Ly49s4","Ly49s5","Ly49s6","Ly49s7","Ly49si1","Ly6al","Ly49si2","Ly49si3","Ly6c","Ly6d","Ly6g5c","Ly6g5b","Ly6e","Ly6g6c","Ly6g6d","Ly6g6f","Ly6g6e","Ly6h","Ly6l","Ly6k","Ly6i","Ly75","Ly86","Ly96","Ly9","Lyar","Lyg1","Lyg2","Lyc2","Lyl1","Lynx1","Lypd1","Lyn","Lypd2","Lypd3","Lypd4","Lypd5","Lypd6","Lypd6b","Lypd8","Lypla1","Lypla2","Lyplal1","Lyrm1","Lyrm2","Lyrm4","Lyrm7","Lyrm9","Lysmd1","Lysmd2","Lysmd3","Lysmd4","Lyst","Lyve1","Lyzl1","Lyz2","Lyzl4","Lyzl6","Lzic","Lztfl1","Lztr1","Lzts1","Lzts2","Lzts3","M1ap","Maats1","M6pr","Mab21l1","Mab21l2","Macc1","Mab21l3","Macrod1","Macrod2","Mad1l1","Macf1","Mad2l1","Mad2l1bp","Mad2l2","Madcam1","Madd","Maea","Mael","Maf","Maf1","Mafa","Maff","Mafb","Mafg","Mafk","Magea10","Magea11","Magea4","Mag","Magea8","Magea9","Magea9-ps1","Mageb1","Mageb18","mageb1l1","Mageb16","Mageb2","Mageb3","Mageb6","Mageb4","Mageb5","Mageb7","Magebl1","Magec2","Maged1","Maged2","Magee1","Magee2","Magel2","Mageh1","Magix","Magi1","Magi2","Magi3","Magmas-ps1","Magoh","Magohb","Magt1","Maip1","Majin","Mak16","Mak","Mal","Mall","Mal2","Malrd1","Malsu1","Malt1","Mamdc2","Maml1","Mamdc4","Maml2","Maml3","Mamstr","Man1a2","Man1b1","Man1a1","Man1c1","Man2a1","Man2a2","Man2b1","Man2b2","Man2c1","Manbal","Manba","Manea","Maneal","Mansc1","Manf","Mansc4","Maoa","Map10","Maob","Map1a","Map1lc3b2","Map1b","Map1lc3a","Map1lc3b","Map1s","Map2k2","Map2k1","Map2","Map2k3","Map2k4","Map2k6","Map2k7","Map2k5","Map3k10","Map3k1","Map3k11","Map3k12","Map3k13","Map3k15","Map3k14","Map3k19","Map3k21","Map3k2","Map3k20","Map3k3","Map3k4","Map3k6","Map3k5","Map3k7","Map3k7cl","Map3k8","Map3k9","Map4","Map4k1","Map4k2","Map4k3","Map6d1","Map4k5","Map6","Map4k4","Map7","Map7d1","Map7d2","Map7d3","Map9","Mapk11","Mapk10","Mapk12","Mapk13","Mapk15","Mapk1ip1","Mapk1","Mapk1ip1l","Mapk4","Mapk6","Mapk14","Mapk7","Mapk8ip1","Mapk8ip2","Mapk8","Mapk3","Mapk8ip3","Mapkap1","Mapk9","Mapkapk2","Mapkapk3","Mapkapk5","Mapkbp1","Mapre1","Mapre2","Mapre3","1-Mar","1-Mar","2-Mar","10-Mar","11-Mar","2-Mar","Mapt","4-Mar","3-Mar","5-Mar","6-Mar","7-Mar","8-Mar","9-Mar","Marcksl1","Marcks","Marco","Marf1","Mark1","Mark2","Mark3","Mark4","Mars","Mars2","Marveld1","Marveld2","Marveld3","Mas1l","Mas1","Masp1","Masp2","MAST1","Mast2","Mast4","Mast3","Mastl","Mat1a","Mat2b","Mat2a","Matk","Matn1","Matn3","Matn2","Matn4","Matr3-ps1","Matr3-ps2","Matr3","Mau2","Mavs","Max","Max-ps1","Maz","Mb21d2","Mb","Mbd1","Mbd2","Mbd3l1","Mbd3l2","Mbd3","mbd3l2l","Mbd4","Mbd5","Mbd6","Mbip","Mblac2","Mblac1","Mbl1","Mbl2","Mbnl2","Mbnl1","Mbnl3","Mboat1","Mboat2","Mboat4","Mboat7","Mboat7l1","Mbtd1","Mbtps1","Mbtps2","Mbp","Mc1r","Mc2r","Mc3r","Mc5r","Mc4r","Mcart1","Mcam","Mcat","Mcc","Mccc2","Mccc1","Mcee","Mcemp1","Mcf2","Mcfd2","Mcf2l","Mchr1","Mcidas","Mcm10","Mcl1","Mcm2","Mcm3","Mcm4","Mcm3ap","Mcm5","Mcm6","Mcm8","Mcm7","Mcm9","Mcmdc1","Mcmbp","Mcmdc2","Mcoln1","Mcoln2","Mcoln3","Mcph1","Mcpt1","Mcpt10","Mcpt1l1","Mcpt1l2","Mcpt1l3","Mcpt1l4","Mcpt2","Mcpt3","Mcpt4l1","Mcpt4","Mcpt8l2","Mcpt8","Mcpt9","Mcrip1","Mcpt8l3","Mcrip2","Mcrs1","Mctp1","Mcts1","Mctp2","Mcts2","Mcub","Mcur1","Mcu","Mdc1","Mdfic","Mdfi","Mdga1","Mdga2","Mdh1","Mdh2","Mdh1b","Mdk","Mdm1","Mdm4","Mdn1","Mdp1","Mdm2","Me2","Me1","Mea1","Meaf6","Me3","Mecom","Mecr","Med10","Med1","Mecp2","Med11","Med12","Med13","Med12l","Med13l","Med14-ps1","Med14","Med15","Med16","Med18","Med17","Med19","Med20","Med21","Med22","Med24","Med23","Med25","Med26","Med27","Med28","Med29","Med30","Med31","Med6","Med4","Med7","Med8","Med9","Medag","Mef2b","Mef2a","Mef2d","Mef2c","Mefv","Megf10","Megf6","Megf9","Megf11","Megf8","Mei1","Meikin","Mei4","Meig1","Meiob","Meioc","Meis1","Meis2","Meis3","Melk","Meltf","Memo1","Meox1","Men1","Meox2","Mep1a","Mep1b","Mepce","Mepe","Mesd","Mesp1","Mertk","Mesp2","Mest","Metap1","Metap1d","Metap2","Met","Metrn","Metrnl","Mettl1","Mettl13","Mettl11b","Mettl15","Mettl16","Mettl14","Mettl17","Mettl18","Mettl21c","Mettl21a","Mettl21cl1","Mettl21ep","Mettl22","Mettl23","Mettl24","Mettl25","Mettl27","Mettl26","Mettl2b","Mettl4","Mettl3","Mettl5","Mettl6","Mettl7a","Mettl9-ps1","Mettl7b","Mettl8","Mettl9","Mex3a","Mex3b","Mex3c","Mex3d","Mfap2","Mfap1a","Mfap3l","Mfap3","Mfap4","Mfap5","Mfhas1","Mff","Mfge8","Mfng","Mfsd1","Mfrp","Mfn1","Mfn2","Mfsd10","Mfsd11","Mfsd12","Mfsd14a","Mfsd13a","Mfsd14b","Mfsd2a","Mfsd2b","Mfsd4a","Mfsd3","Mfsd4b","Mfsd6l","Mfsd5","Mfsd6","Mfsd7","Mfsd8","Mfsd9","Mgam","Mga","Mgarp","Mgat1","Mgat2","Mgat3","Mgat4b","Mgat4a","Mgat4d","Mgat4e","Mgat4c","Mgat5b","MGC105567","Mgat5","MGC105649","MGC108823","MGC109340","MGC112692","MGC114246","MGC114483","MGC114492","MGC114499","MGC116121","MGC93861","MGC116197","MGC116202","MGC94207","MGC94199","MGC95208","MGC94891","MGC95210","Mgea5","Mgme1","Mgll","Mgp","Mgrn1","Mgst1","Mgmt","Mgst2","Mgst3","Mia","Miat","Mia3","Mia2","Mib1","Mib2","Mical1","Mical2","Micalcl","Mical3","Micall1","Micall2","Micb","Micu2","Micu1","Micu3","Mid1ip1","Mid2","Mid1","Midn","Mief1","Mief2","Mien1","Mier2","Mier1","Mier3","Mif4gd","Miga1","Miga2","Miip","Milr1","Mill1","Mif","Mindy1","Mindy2","Minos1","Minpp1","Mink1","Mios","Miox","Mipol1","Mipep","Mip","Mir1","Mir100","Mir101-2","Mir103a1","Mir101a","Mir103a2","Mir105","Mir107","Mir106a","Mir106b","Mir10a","Mir10b","Mir1188","Mir1193","Mir1199","Mir1224","Mir122","Mir124-1","Mir124-2","Mir124-3","Mir1249","Mir1247","Mir125a","Mir125b1","Mir125b2","Mir126a","Mir126b","Mir127","Mir128-2","Mir128-1","Mir129-1","Mir129-2","Mir1297","Mir1306","Mir1298","Mir130b","Mir130a","Mir132","Mir133a1","Mir133b","Mir134","Mir135a","Mir135b","Mir136","Mir137","Mir138-2","Mir138-1","Mir139","Mir140","Mir141","Mir142","Mir144","Mir143","Mir145","Mir146a","Mir146b","Mir148a","Mir147","Mir148b","Mir149","Mir151","Mir150","Mir151b","Mir152","Mir154","Mir153","Mir155","Mir155hg","Mir15b","Mir16","Mir17","Mir181a-1","Mir181a2","Mir181b1","Mir181b2","Mir181c","Mir181d","Mir182","Mir183","Mir184","Mir1843b","Mir185","Mir186","Mir187","Mir188","Mir1896","Mir18a","Mir190b","Mir190","Mir191","Mir1912","Mir192","Mir193","Mir193a","Mir193b","Mir1949","Mir194-2","Mir194-1","Mir195","Mir1956","Mir196a","Mir196b","Mir196c","Mir1b","Mir19a","Mir199a2","Mir19b1","Mir19b2","Mir200a","Mir200b","Mir200c","Mir201","Mir202","Mir203","Mir204","Mir207","Mir206","Mir205","Mir208a","Mir208b","Mir20a","Mir20b","Mir211","Mir212","Mir210","Mir21","Mir215","Mir216a","Mir214","Mir218-2","Mir216b","Mir217","Mir218-1","Mir219-2","Mir219a1","Mir22","Mir221","Mir222","Mir224","Mir223","Mir23a","Mir23b","Mir24-2","Mir25","Mir24-1","Mir26b","Mir27a","Mir26a","Mir27b","Mir28","Mir290","Mir291a","Mir291b","Mir292","Mir294","Mir293","Mir295-1","Mir295-2","Mir2964","Mir296","Mir299","Mir297","Mir298","Mir2985","Mir299b","Mir29a","Mir29b1","Mir301a","Mir29b2","Mir29c","Mir301b","Mir300","Mir3064","Mir3065","Mir3074","Mir3072","Mir3075","Mir3084a","Mir3084c","Mir3099","Mir3085","Mir30a","Mir30b","Mir30c1","Mir30c2","Mir31","Mir30d","Mir30e","Mir3102","Mir32","Mir323","Mir3120","Mir320a","Mir324","Mir325","Mir326","Mir322","Mir327","Mir328b","Mir328","Mir329","Mir330","Mir33","Mir331","Mir336","Mir337","Mir338","Mir339","Mir341","Mir342","Mir343","Mir344-1","Mir344a-2","Mir344b-1","Mir344g","Mir344i","Mir345","Mir346","Mir349","Mir3473","Mir34a","Mir34b","Mir351","Mir350","Mir34c","Mir3541","Mir3542","Mir3543","Mir3544","Mir3545","Mir3546","Mir3547","Mir3548","Mir3550","Mir3551","Mir3552","Mir3553","Mir3554","Mir3556b","Mir3555","Mir3557","Mir3556a","Mir3558","Mir3559","Mir3560","Mir3561","Mir3565","Mir3562","Mir3564","Mir3566","Mir3568","Mir3569","Mir3570","Mir3571","Mir3572","Mir3576","Mir3573","Mir3575","Mir3574","Mir3577","Mir3578","Mir3579","Mir3580","Mir3581","Mir3582","Mir3583","Mir3584","Mir3585","Mir3587","Mir3588","Mir3589","Mir3590","Mir3591","Mir3592","Mir3593","Mir3594","Mir3595","Mir3596a","Mir3596b","Mir3596c","Mir3597-1","Mir3597-2","Mir3597-3","Mir361","Mir362","Mir363","Mir365b","Mir369","Mir370","Mir374b","Mir376a","Mir375","Mir376b","Mir376c","Mir377","Mir378","Mir379","Mir378b","Mir380","Mir381","Mir382","Mir383","Mir384","Mir410","Mir411","Mir409","Mir412","Mir421","Mir423","Mir425","Mir429","Mir433","Mir431","Mir434","Mir448","Mir449a","Mir449c","Mir450a1","Mir455","Mir463","Mir466c","Mir465","Mir451a","Mir466d","Mir471","Mir483","Mir484","Mir485","Mir488","Mir489","Mir487b","Mir490","Mir493","Mir494","Mir495","Mir496","Mir497","Mir499a","Mir501","Mir500","Mir503","Mir504","Mir505","Mir511","Mir539","Mir5132","Mir509","Mir532","Mir540","Mir541","Mir542","Mir544","Mir543","Mir551b","Mir547","Mir568","Mir582","Mir592","Mir598","Mir615","Mir6215","Mir6216","Mir628","Mir6314","Mir6315","Mir6316","Mir6318","Mir6320","Mir6322","Mir632","Mir6321","Mir6323","Mir6324","Mir6325","Mir6326","Mir6327","Mir6328","Mir6329","Mir6330","Mir6332","Mir6331","Mir6333","Mir6334","Mir652","Mir664-1","Mir653","Mir665","Mir666","Mir664-2","Mir667","Mir668","Mir672","Mir671","Mir673","Mir676","Mir674","Mir675","Mir678","Mir702","Mir741","Mir708","Mir711","Mir742","Mir743a","Mir743b","Mir7578","Mir758","Mir759","Mir761","Mir760","Mir762","Mir770","Mir764","Mir7a1","Mir7a2","Mir802","Mir7b","Mir871","Mir872","Mir874","Mir873","Mir875","Mir876","Mir878","Mir879","Mir877","Mir883","Mir880","Mir881","Mir9-1","Mir9-2","Mir9-3","Mir92a1","Mir92b","Mir92a2","Mir935","Mir93","Mir96","Mir98","Mir99a","Mir99b","Mirlet7a-2","Mirlet7a1","Mirlet7bhg","Mirlet7b","Mirlet7c1","Mirlet7c2","Mirlet7e","Mirlet7f-1","Mirlet7f1","Mirlet7d","Mirlet7f2","Mirlet7g","Mirlet7i","Mis12","Misp","Misp3","Mis18a","Mis18bp1","Mitd1","Mitf","Mixl1","Mk1","Mkl2","Mkks","Mki67","Mkl1","Mkln1","Mknk2","Mknk1","Mkrn1","Mkrn2os","Mkrn2","Mkrn3","Mkx","Mks1","Mlana","Mlc1","Mlec","Mlf1","Mlf2","Mlkl","Mlh1","Mlh3","Mllt1","Mlip","Mllt10","Mllt11","Mllt3","Mln","Mllt6","Mlnr","Mlph","Mlst8","Mlxip","Mlx","Mlxipl","Mlycd","Mmaa","Mmab","Mmachc","Mmd","Mmadhc","Mmd2","Mmgt1","Mmgt2","Mmel1","Mme","Mmp10","Mmp11","Mmp12","Mmp15","Mmp1","Mmp16","Mmp14","Mmp13","Mmp1b","Mmp17","Mmp19","Mmp20","Mmp21","Mmp25","Mmp23","Mmp27","Mmp28","Mmp24","Mmp8","Mmrn1","Mmp7","Mms19","Mmrn2","Mmp3","Mn1","Mnda","Mms22l","Mnd1","Mmp2","Mnat1","Mns1","Moap1-ps1","Mnt","Mnx1","Moap1","Mob1b","Mob2","Mob1a","Mob3a","Mob3c","Mob3b","Mob4","Mocos","Mocs1","Mobp","Mocs3","Mogat1","Mocs2","Mog","Mmp9","Mogat2","Mogat3","Mogs","Mok","Mon1a","Mon1b","Morc2b","Morc2","Morc1","Mon2","Morc3","Morc4","Morf4l1","Morn2","Morn1","Morf4l2","Morn3","Morn5","Morn4","Mos","Mospd1","Mospd4","Mospd3","Mospd2","Mov10","Moxd1","Mov10l1","Moxd2","Mpc1l","Mpdu1","Mpc1","Mpc2","Mpeg1","Mpdz","Mphosph6-ps1","Mpg","Mphosph10","Mphosph6","Mphosph9","Mphosph8","Mpi","Mpig6b","Mplkip","Mpnd","Mpp1","Mpl","Mpo","Mpp2","Mpp3","Mpp4","Mpp5","Mpp6","Mppe1","Mpp7","Mpped1","Mpped2","Mptx1","Mpst","Mpv17","Mpv17l","Mprip","Mpv17l2","Mpz","Mpzl1","Mpzl2","Mpzl3","Mrap","Mr1","Mrap2","Mras","Mrc1","Mrc2","Mreg","Mrfap1","Mrgprb13","Mrgbp","Mre11a","Mrgprb3","Mrgprb4","Mrgprc","Mrgprb5","Mrgprd","Mrgpre","Mrgprf","Mrgprx1","Mrgprg","Mrgprx2l","Mrgprx2","Mrgprx3","Mrgprx4","Mrln","Mri1","Mrm2","Mrm1","Mrm3","Mrnip","Mro","Mroh2a","Mroh2b","Mroh5","Mroh1","Mroh4","Mroh6","Mroh7","Mroh9","Mroh8","Mrpl1","Mrpl10","Mrpl12","mrpl11","Mrpl13","Mrpl14","Mrpl15","Mrpl16","Mrpl17","Mrpl18","Mrpl19","Mrpl2","Mrpl20","Mrpl21","Mrpl22","Mrpl23","Mrpl27","mrpl24","Mrpl28","Mrpl3","Mrpl32","Mrpl30","Mrpl33","Mrpl34","Mrpl35","Mrpl36","Mrpl38","Mrpl37","Mrpl39","Mrpl4","Mrpl42","Mrpl41","Mrpl40","Mrpl43","Mrpl44","Mrpl45","Mrpl46","Mrpl47","Mrpl48","Mrpl50","Mrpl49","Mrpl51","Mrpl52","Mrpl53","Mrpl54","Mrpl55","Mrpl57","Mrpl58","Mrps10","mrpl9","Mrps11","Mrps12","Mrps14","Mrps15","Mrps16","Mrps17-ps1","Mrps17","Mrps18a","Mrps18b","Mrps18c","Mrps2","Mrps21l","Mrps21","Mrps22","Mrps23","Mrps25","Mrps26","Mrps24","Mrps27","Mrps30","Mrps28","Mrps31","Mrps33","Mrps34","Mrps36","Mrps35","Mrps6","Mrps7","Mrps5","Mrrf","Mrps9","Mrs2","Mrto4","Mrvi1","Ms4a10","Ms4a1","Ms4a12","Ms4a13-ps1","Ms4a14","Ms4a18","Ms4a15","Ms4a2","Ms4a3","Ms4a4a","Ms4a4c","Ms4a5","Ms4a6b","Ms4a6a","Ms4a6bl","Ms4a6c","Ms4a7","Ms4a6e","Ms4a8","Msantd2","Msantd1","Msantd3","Msantd4","Msc","Msgn1","Msh3","Msh4","Msi1","Msh5","Msh2","Msh6","Msl1","Msi2","Msl2","Msl3l2","Msl3","Mslnl","Msmb","Msmp","Msln","Msmo1","Msr1","Msrb1","Msn","Msrb2","Msrb3","Mss51","Msra","Mst1","Msto1","Msx3","Mst1r","Msx1","Mstn","Msx2","Mt-atp6","Mt-atp8","Mt-co1","Mt-co2","Mt-co3","Mt-nd2","Mt-nd4","Mt-cyb","Mt-nd3","Mt-nd1","mt-Ta","mt-Tc","mt-Td","mt-Te","Mt-nd6","Mt-nd4l","mt-Tf","Mt-nd5","mt-Th","mt-Tg","mt-Ti","mt-Tk","mt-Tl1","mt-Tl2","mt-Tm","mt-Tn","mt-Tp","mt-Tq","mt-Tr","mt-Ts1","mt-Ts2","mt-Tv","mt-Tt","mt-Tw","mt-Ty","Mt1f","Mt1m","Mt1-ps2","Mt1-ps1","Mt1","Mt2A","Mt3","Mt4","Mta1","Mta2","Mta3","Mtap","Mtbp","Mtch1","Mtch2","Mtcl1","Mtcp1","Mterf1","Mterf2","Mtdh","Mterf4","Mterf3","Mtf1","Mtf2","Mtfmt","Mtfp1","Mtfr1-ps1","Mtfr1","Mtfr1l","Mtg1","Mtfr2","Mtg2","Mthfd1","Mthfd1l","Mthfd2","Mthfd2l","Mthfsd","Mtif3","Mtif2","Mthfs","Mtm1","Mtmr1","Mtmr10","Mtmr11","Mtmr12","Mthfr","Mtmr14","Mtmr2","Mtmr3","Mtmr6","Mtmr4","Mtmr9","Mtmr7","Mto1","Mtnr1b","Mtnr1a","Mtpap","Mtrf1","Mtpn","Mtrf1l","Mtr","Mtss1l","Mtrr","Mtss1","Mttp","Mtor","Mturn","Mtx1","Mtus2","Mtus1","Mtx2","Mtx3","Muc15","Muc13","Muc16","Muc19","Muc19l1","Muc1","Muc20","Muc3","Muc2","Muc5b","Muc5ac","Muc4","Mucl1","Muc6","Mul1","Mum1l1","Mum1","Mug2","Mug1","Mus81","Mup4","Mup5","Mustn1","Musk","Mutyh","Mvb12a","Mut","Mvb12b","Mvd","Mvp","Mvk","Mxd1","Mx1","Mx2","Mxd3","Mxd4","Mxra7","Myadml2","Mxi1","Mxra8","Myb","Mybbp1a","Myadm","Mybl1","Mybl2","Mybpc2","Mybpc3","Mybpc1","Mybphl","Mycbp","Mybph","Mycbp2","Mycbpap","Mycl","Myct1","Mycn","Mycs","Myd88","Mydgf","Myef2","Myf5","Myf6","Myg1","Myh1","Myh13","Myc","Myh10","Myh11","Myh2","Myh15","Myh14","Myh3","Myh7b","Myh6","Myh4","Myh8","Myh9l1","Myh7","Myh9","Myl10","Myl1","Myl12a","Myl2","Myl3","Myl12b","Myl4","Myl6b","Myl6","Myl6l","Myl7","Myl9","Mylip","Mylk3","Mylk2","Mylk4","Mylk","Mymk","Mylpf","Mynn","Myo15b","Myo10","Myo15a","Myo18a","Myo16","Myo19","Myo18b","Myo1a","Myo1b","Myo1c","Myo1e","Myo1f","Myo1h","Myo1d","Myo1g","Myo3b","Myo3a","Myo5c","Myo5a","Myo5b","Myo7b","Myo6","Myo7a","Myo9a","Myo9b","Myoc","Myocd","Myod1","Myof","Myom2","Myom1","Myog","Myom3","Myorg","Myoz3","Myoz2","Myot","Myoz1","Mypn","Mypop","Myrf","Myrfl","Myrip","Myt1","Mysm1","Myt1l","Mzb1","Mzt1","Myzap","Mzf1","N4bp1","N4bp2l1","Mzt2b","N4bp2","N5","N4bp2l2","N4bp3","N6amt1","Naa10","Naa11","Naa15","Naa16","Naa20","Naa30","Naa25","Naa35","Naa38","Naa40","Naa50","Naa60","Naaa","Naaladl1","Naalad2","Naaladl2","Nab1","Nab2","Nabp1","Nabp2","Nacad","Naca","Nacc1","Nacc2","Nadk","Nadk2","Nadsyn1","Nae1","Naga","Nagk","Naf1","Naglt1","Nagpa","Naglu","Naif1","Naip5","Nags","Nalcn","Naip6","Nanos1","Nanos2","Nampt","Nanog","Nanos3","Nanp","Nans","Nap1l2","Nap1l1","Nap1l3","Nap1l4","Nap1l5","Napb","Napa","Napepld","Napg","Naprt","Napsa","Narf","Nars","Narfl","Nars2","Nasp","Nat14","Nat10","Nat6","Nat8","Nat3","Nat1","Nat8b","Nat2","Nat8f1","Nat8f2","Nat8f3","Nat8f4","Nat8f5","Nat8l","Nat9","Natd1","Nav1","Naxd","Nav3","Nav2","Naxe","Nbas","Nbea","Nbeal1","Nbl1","Ncald","Nbeal2","Nbn","Nbr1","Ncam2","Ncapd2","Ncan","Ncaph","Ncapg","Ncapd3","Ncam1","Ncapg2","Ncaph2","Ncbp2","Ncbp3","Ncbp1","Nccrp1","Ncdn","Nceh1","Ncf2","Ncf1","Ncf4","Nck1","Nck2","Nckap1","Nckap1l","Nckap5","Nckap5l","Nckipsd","Ncl-ps1","Ncmap","Ncln","Ncl","Ncoa4","Ncoa5","Ncoa2","Ncoa3","Ncoa1","Ncoa7","Ncoa6","Ncr2","Ncr3lg1","Ncr1","Ncr3","Ncor1","Ncor2","Ncstn","Ndc1","Ncs1","Ndc80","Ndfip1","Nde1","Ndel1","Ndfip2","Ndnf","Ndn","Ndor1","Ndp","Ndrg1","Ndrg3","Ndrg2","Ndst3","Ndst2","Ndst1","Ndrg4","Ndst4","Ndufa1","Ndufa10l1","Ndufa13-ps1","Ndufa11","Ndufa10","Ndufa12","Ndufa13","Ndufa2","Ndufa3","Ndufa4l2","Ndufa7l","Ndufa4","Ndufa6","Ndufa5","Ndufa7","Ndufa8","Ndufaf1","Ndufa9","Ndufaf2","Ndufab1","Ndufaf4","Ndufaf5","Ndufaf3","Ndufaf6","Ndufb1","Ndufaf8","Ndufaf7","Ndufb10","Ndufb1l1","Ndufb11","Ndufb2","Ndufb3","Ndufb4l1","Ndufb4-ps1","Ndufb4","Ndufb6","Ndufb5","Ndufb7","Ndufb8","Ndufb9","Ndufc1","Ndufc2","Ndufs2","Ndufs1","Ndufs3","Ndufs4","Ndufs5-ps1","Ndufs5","Ndufs6","Ndufs7","Ndufs8","Ndufv2","Ndufv1","Ndufv3-ps1","Ndufv3","Nebl","Necab1","Neb","Necab2","Necab3","Necap2","Necap1","Nectin1","Nectin3","Nectin2","Nedd1","Nectin4","Nedd8","Nedd4l","Nedd4","Nedd9","Negr1","Nefh","Neil1","Nefm","Neil2","Nefl","Neil3","Nek1","Nek11","Nek2","Nek10","Nek2l1","Nek3","Nek4","Nek5","Nek6","Nek7","Nelfa","Nek8","Nek9","Nelfcd","Nelfb","Nelfe","Nemp1","Nemf","Nemp2","Nenf","Nell1","Nepn","Neo1","Nell2","Nerg-ps11","Nerg-ps1","Nepro","Nerg-ps12","Nerg-ps10","Nerg-ps13","Nerg-ps14","Nerg-ps15","Nerg-ps2","Nerg-ps4","Nerg-ps6","Nerg-ps5","Nerg-ps3","Nerg-ps7","Nerg-ps8","Nerg-ps9","Net1","Neto2","Neto1","Neu1","Neu4","Neu3","Nes","Neu2","Neurl1b","Neurl2","Neurl1","Neurl3","Neurod2","Neurl4","Neurod4","Neurod1","Neurod6","Nexmif","Nexn","Nf1x","Nfam1","Nf2","Nfat5","Nf1","Nfasc","Nfatc2ip","Nfatc1","Nfatc2","Nfatc3","Nfe2","Nfe2l1","Nfe2l3","Nfatc4","Nfia","Nfic","Nfib","Nfil3","Nfkb2","Nfe2l2","Nfkbib","Nfkbia","Nfkbie","Nfkbil1","Nfkbid","Nfkbiz","Nfrkb","Nfkb1","Nfs1","Nfu1","Nfx1","Nfxl1","Nfya","Nfyb","Nfyc","Ngdn","Ngef","Ngb","Ngly1","Ngp","Ngrn","Nhlh2","Nhlh1","Nhej1","Nhlrc1","Nhlrc2","Ngf","Ngfr","Nhlrc3","Nhlrc4","Nhp2","Nhsl2","Nhs","Nhsl1","Nicn1","Nif3l1","Nid1","Nid2","Nifk","Nim1k","Ninj1","Nin","Ninj2","Nipa1","Nip7","Ninl","Nipa2","Nipal1","Nipal4","Nipal2","Nipal3","Nipsnap1","Nipsnap2","Nipsnap3a","Nipsnap3b","Nipbl","Nit1","Nisch","Nit2","Nkain1","Nkain2","Nkain3","Nkain4","Nkap","Nkapl","Nkapd1","Nkd1","Nkg7","Nkd2","Nkiras1","Nkpd1","Nkiras2","Nkr-p1c","Nkrf","Nktr","Nkx1-1","Nkx1-2","Nkx2-3","Nkx2-4","Nkx2-2","Nkx2-6","Nkx2-8","Nkx2-5","Nkx2-1","Nkx3-1","Nkx3-2","Nkx6-2","Nkx6-1","Nkx6-3","Nle1","Nlgn2","Nlk","Nlgn3","Nlgn1","Nln","Nlrc3","Nlrc5","Nlrc4","Nlrp10","Nlrp14","Nlrp12","Nlrp1b","Nlrp2","Nlrp1a","Nlrp4","Nlrp4b","Nlrp3","Nlrp4a","Nlrp4f","Nlrp5","Nlrp6","Nlrx1","Nlrp9","Nmbr","Nmb","Nmd3","Nme2-ps1","Nme1","Nme5","Nme3","Nme2","Nme4","Nme6","Nme7","Nme9","Nme8","Nmi","Nmnat1","Nmnat2","Nmral1","Nmnat3","Nmrk1","Nmrk2","NMS","Nmt1","Nmt2","Nmur1","Nmur2","Nmu","Nnmt","Nnat","Noa1","Nnt","Nob1","Nobox","Noc2l","Noc3l","Noc4l","Noct","Nod1","Nodal","Nol11","Nol10","Nol12","Nod2","Nol3","Nol4","Nol4l","Nol7","Nol6","Nol8","Nol9","Nom1","Nolc1","Nomo1","Nono","Nono-ps1","Nop14","Nop16","Nop10","Nop2","Nop53","Nop56","Nop58","Nop9","Nos1ap","Nosip","Nostrin","Notch2","Nog","Notch1","Nos1","Notch3","Notch4","Noto","Notum","Nos3","Nova2","Nov","Nova1","Nox3","Nox1","Nos2","Nox4","Noxa1","Noxred1","Noxo1","Np4","Npas1","Npas3","Npas2","Npb","Npas4","Npbwr1","Npat","Npc1","Npc1l1","Npdc1","Npc2","Npepl1","Npepo","Npff","Npepps","Npffr1","Npffr2","Nphp3","Nphp1","Npl","Nphp4","Nphs2","Nphs1","Nploc4","Npm2","Npm3","Npnt","Npm1","Nppc","Npr2","Nppb","Npr1","Npr3","Nprl2","Nppa","Nprl3","Nps","Nptx1","Npsr1","Nptxr","Nptx2","Nptn","Npvf","Npw","Npy4r","Npy2r","Npy1r","Nqo2","Nr0b1","Npy5r","Nqo1","Nr0b2","Nr1d1","Nr1d2","Nr1h2","Nr1h3","Nr1h4","Nr1h5","Nr1i2","Nr2c1","Npy","Nr2c2ap","Nr1i3","Nr2e1","Nr2e3","Nr2f1","Nr2c2","Nr2f6","Nr2f2","Nr3c2","Nr4a1","Nr4a2","Nr4a3","Nr5a1","Nr6a1","Nr5a2","Nr3c1","Nradd","Nras-ps1","Nrarp","Nrap","Nrbf2","Nras","Nrbp2","Nrbp1","Nrde2","Nrep","Nrdc","Nrg2","Nrf1","Nrcam","Nrg3","Nrg4","Nrgn","Nrip1","Nrip2","Nrip3","Nrk","Nrm","Nrl","Nrg1","Nrn1","Nrros","Nrp2","Nrsn1","Nrp1","Nrsn2","Nrtn","Nrxn1","Ns5atp4","Nsa2","Nsd1","Nrxn2","Nrxn3","Nsd2","Nrn1l","Nsdhl","Nsd3","Nsg2","Nsg1","Nsl1","Nsfl1c","Nsf","Nsmaf","Nsmce1","Nsmce3","Nsmce4a","Nsmce2","Nsrp1","Nsun2","Nsmf","Nsun3","Nsun7","Nsun5","Nsun4","Nsun6","Nt5c","Nt5c1a","Nt5c1b","Nt5c2","Nt5dc1","Nt5c3b","Nt5dc3","Nt5dc2","Nt5c3a","Nt5m","Nt5e","Ntan1","Nthl1","Ntmt1","Ntm","Ntf4","Ntn3","Ntf3","Ntn1","Ntn4","Ntn5","Ntpcr","Ntng2","Ntng1","Ntsr2","Nts","Nuak1","Ntsr1","Nuak2","Ntrk3","Ntrk1","Nubpl","Nubp2","Nub1","Nubp1","Nucb1","Ntrk2","Nucb2","Nucks1","Nudcd3","Nudcd2","Nudcd1","Nudc","Nudt1","Nudt10","Nudt11","Nudt12","Nudt13","Nudt15","Nudt14","Nudt16","Nudt17","Nudt16l1","Nudt19","Nudt18","Nudt2","Nudt3","Nudt22","Nudt21","Nudt4","Nudt5","Nudt6","Nudt8","Nudt7","Nuf2","Nufip1","Nudt9","Nufip2","Nuggc","Numa1","Numb","Numbl","Nup107","Nup133","Nup160","Nup153","Nup155","Nup188","Nup205","Nup210l","Nup214","Nup37","Nup210","Nup35","Nup43","Nup50","Nup54","Nup85","Nup62cl","Nup62","Nup88","Nup93","Nup58","Nupl2","Nupr1l1","Nupr1","Nup98","Nupr2","Nus1","Nusap1","Nutf2","Nutf2-ps1","Nutm1","Nwd1","Nutm2f","Nvl","Nwd2","Nxf2","Nxf1","Nxf5","Nxf3","Nxnl1","Nxf7","Nxn","Nxnl2","Nxpe2","Nxpe1","Nxpe3","Nxpe4","Nxph2","Nxpe5l1","Nxpe5","Nxph1","Nxph3","Nxph4","Nxt1","Nxt2","Nynrin","Nyap1","Nyap2","Nyx","Oacyl","Oaf","Oas1d","Oas1a","Oard1","Oas1b","Oas1e","Oas1g","Oas1h","Oas1f","Oas1i","Oas2","Oas1k","Oas3","Oasl","Oasl2","Oaz1-ps","Oat","Oaz1","Obox1","Oaz2","Oaz3","Obox2","Obox5","Obp2a","Obp2b","Obp1f","Oc90","Obsl1","Obscn","Obp3","Oca2","Ociad2","Ocel1","Ociad1","Ocstamp","Ocm2","Ocrl","Ocln","Odam","Odf1","Odc1","Odf2","Odf3","Odf2l","Odf3b","Odf3l1","Odf3l2","Odf4","Ofcc1","Odr4","Ogfod1","Ofd1","Ogdhl","Ogfod2","Ogfod3","Ogdh","Ogfrl1","Ogfr","Ogn","Oip5","Oit3","Ogg1","Ola1","Ogt","Olah","Olfm1","Olfm2","Olfm3","Olfm4","Olfml1","Olfml2a","Olfml3","Olfml2b","Olfr1330-ps1","Olfr1055","Olfr873","Olfr94-ps1","Olig1","Olig3","Olig2","Olr1","Olr1000","Olr1005-ps","Olr100-ps","Olr1004-ps","Olr10","Olr1002","Olr1008-ps","Olr1007","Olr1010-ps","Olr1006","Olr1001-ps","Olr1003-ps","Olr1011-ps","Olr1014","Olr1015-ps","Olr1012","Olr1018-ps","Olr1019-ps","Olr102-ps","Olr1020","Olr1009-ps","Olr1017-ps","Olr1013-ps","Olr101","Olr1021-ps","Olr1022","Olr1016","Olr1023-ps","Olr1025-ps","Olr1027-ps","Olr1026-ps","Olr1024","Olr1028-ps","Olr1029","Olr103","Olr1030-ps","Olr1031-ps","Olr1033-ps","Olr1032-ps","Olr1034-ps","Olr1035-ps","Olr1037-ps","Olr1036-ps","Olr1038-ps","Olr1039-ps","Olr1041-ps","Olr1040-ps","Olr104","Olr1042-ps","Olr1043-ps","Olr1044-ps","Olr1048-ps","Olr1045-ps","Olr1047-ps","Olr1046-ps","Olr1049","Olr105","Olr1050-ps","Olr1051","Olr1052","Olr1053-ps","Olr1054-ps","Olr1056-ps","Olr1055","Olr1057","Olr1058","Olr1059","Olr106","Olr1062-ps","Olr1060","Olr1061","Olr1063","Olr1064","Olr1065","Olr1066-ps","Olr1067","Olr1069","Olr1068","Olr107","Olr1071","Olr1070","Olr1072","Olr1074-ps","Olr1073","Olr1075","Olr1076","Olr1077","Olr1078","Olr1079","Olr1080-ps","Olr108","Olr1081","Olr1083","Olr1082","Olr1084","Olr1085","Olr1086","Olr1088","Olr1087","Olr1089-ps","Olr109","Olr1090","Olr1091","Olr1093","Olr1092","Olr1094-ps","Olr1095","Olr1096","Olr1097-ps","Olr1099-ps","Olr1098-ps","Olr110","Olr11","Olr1100-ps","Olr1102","Olr1105","Olr1104","Olr1106","Olr1107","Olr1109-ps","Olr111","Olr1108","Olr1110-ps","Olr1111","Olr1112-ps","Olr1113-ps","Olr1114-ps","Olr1116-ps","Olr1115","Olr1117","Olr1118","Olr112","Olr1120-ps","Olr1119","Olr1121","Olr1122","Olr1124","Olr1125","Olr1126","Olr1127-ps","Olr1128","Olr1129","Olr113","Olr1130","Olr1131-ps","Olr1132","Olr1133-ps","Olr1134-ps","Olr1136-ps","Olr1135","Olr1137","Olr1139","Olr1138","Olr1140-ps","Olr114","Olr1141-ps","Olr1143","Olr1142","Olr1144","Olr1145","Olr1146","Olr1147","Olr1148","Olr1149","Olr115","Olr1150-ps","Olr1152-ps","Olr1151","Olr1153-ps","Olr1154-ps","Olr1156","Olr1155","Olr1157-ps","Olr1158","Olr116-ps","Olr1161-ps","Olr1159","Olr1160","Olr1163","Olr1162","Olr1164","Olr1165","Olr1166","Olr1167-ps","Olr1168-ps","Olr1169","Olr117-ps","Olr1170-ps","Olr1171","Olr1172","Olr1175-ps","Olr1176-ps","Olr1174","Olr1178-ps","Olr1177","Olr1179","Olr118-ps","Olr1181-ps","Olr1182-ps","Olr1184-ps","Olr1183-ps","Olr1185","Olr1187-ps","Olr1186","Olr119","Olr1191","Olr1192","Olr1193","Olr1194","Olr1195","Olr1197","Olr1196","Olr1198","Olr1199","Olr12","Olr120","Olr1200","Olr1201","Olr1203","Olr1202","Olr1205-ps","Olr1204","Olr1206","Olr1207-ps","Olr1208-ps","Olr1209-ps","Olr1210-ps","Olr121","Olr1212-ps","Olr1211-ps","Olr1215-ps","Olr1213","Olr1214","Olr1217","Olr1218","Olr1219","Olr122","Olr1220","Olr1222","Olr1223","Olr1224-ps","Olr1225","Olr1226","Olr1227","Olr1228","Olr1229","Olr1232","Olr1230","Olr1231","Olr1233","Olr1234","Olr1235","Olr1236","Olr1237","Olr1238","Olr124","Olr1239","Olr1240","Olr1241","Olr1242","Olr1243","Olr1244","Olr1245","Olr1246","Olr1247","Olr1248","Olr1249","Olr125","Olr1250","Olr1251","Olr1252","Olr1253","Olr1254","Olr1255-ps","Olr1256","Olr1257","Olr1258","Olr1258-ps","Olr126","Olr1259","Olr1260","Olr1261","Olr1262","Olr1263-ps","Olr1264","Olr1267-ps","Olr1268-ps","Olr1265","Olr1269-ps","Olr1266","Olr1270-ps","Olr127","Olr1272-ps","Olr1273","Olr1275","Olr1276-ps","Olr1271","Olr1274","Olr1277-ps","Olr1278","Olr1279","Olr1281-ps","Olr1282-ps","Olr128","Olr1280","Olr1284-ps","Olr1283","Olr1285","Olr1287-ps","Olr1286","Olr1289-ps","Olr129","Olr1288","Olr1290-ps","Olr1291","Olr1292","Olr1293","Olr1296-ps","Olr1294","Olr1299-ps","Olr1298-ps","Olr1295","Olr1297","Olr13","Olr1300-ps","Olr130","Olr1301","Olr1302","Olr1303","Olr1304","Olr1305","Olr1306","Olr1307","Olr1308","Olr1310-ps","Olr131","Olr1309","Olr1311","Olr1312-ps","Olr1313","Olr1314","Olr1317-ps","Olr1318","Olr1315","Olr1316","Olr132","Olr1319","Olr1324-ps","Olr1320","Olr1325","Olr1328","Olr1326","Olr1323","Olr1321","Olr1327","Olr133-ps","Olr1329","Olr1333-ps","Olr1332","Olr1330","Olr1331","Olr1334","Olr1335","Olr1337","Olr1338","Olr134-ps","Olr1339","Olr1341","Olr1340","Olr1342-ps","Olr1343","Olr1345","Olr1344","Olr1348-ps","Olr1346","Olr1349","Olr1347","Olr135","Olr1350","Olr1351","Olr1352","Olr1357-ps","Olr1354-ps","Olr1353","Olr1355","Olr1356","Olr1358","Olr1359-ps","Olr1360-ps","Olr136","Olr1363-ps","Olr1362","Olr1361","Olr1364","Olr1365","Olr1367-ps","Olr1366","Olr1368","Olr1369","Olr1371-ps","Olr137","Olr1370","Olr1372","Olr1373","Olr1374","Olr1377-ps","Olr1376","Olr1375","Olr1379-ps","Olr1378","Olr138-ps","Olr1380","Olr1381","Olr1383","Olr1384","Olr1382","Olr1385","Olr1386","Olr1387","Olr1388","Olr1389","Olr1390-ps","Olr139","Olr1391","Olr1392","Olr1393","Olr1394","Olr1395","Olr1396","Olr1397","Olr1398","Olr1399","Olr14","Olr1400","Olr140","Olr1402-ps","Olr1401","Olr1403-ps","Olr1404","Olr1405","Olr1406","Olr1407","Olr1408","Olr1409","Olr1411","Olr1410","Olr141","Olr1412-ps","Olr1414","Olr1413","Olr1415","Olr1416","Olr1419-ps","Olr1417","Olr1418","Olr142","Olr1420-ps","Olr1421","Olr1422","Olr1424","Olr1427-ps","Olr1423","Olr1425","Olr1428","Olr1429-ps","Olr1430-ps","Olr143","Olr1433","Olr1431","Olr1434","Olr1432","Olr1435","Olr1436","Olr1438-ps","Olr1437","Olr144","Olr1439","Olr1440","Olr1444-ps","Olr1442","Olr1443","Olr1446-ps","Olr1445","Olr1447-ps","Olr1448","Olr1451","Olr1449","Olr1450","Olr145","Olr1452","Olr1453","Olr1454","Olr1455","Olr1456","Olr1457","Olr146-ps","Olr1458","Olr1459","Olr1460","Olr1461","Olr1462","Olr1464-ps","Olr1467","Olr1466","Olr1463","Olr1468","Olr147-ps","Olr1469","Olr1470","Olr1472","Olr1471","Olr1473-ps","Olr1475","Olr1474","Olr1476-ps","Olr1477-ps","Olr1479","Olr1480-ps","Olr148","Olr1481","Olr1483-ps","Olr1482","Olr1485","Olr1484-ps","Olr1487-ps","Olr1489-ps","Olr1486","Olr1488","Olr1490","Olr149","Olr1491","Olr1492","Olr1495-ps","Olr1493","Olr1497-ps","Olr1496","Olr1498","Olr1499","Olr15-ps","Olr150","Olr1500","Olr1503-ps","Olr1501","Olr1505","Olr1504","Olr1507","Olr1508-ps","Olr1509","Olr151-ps","Olr1510-ps","Olr1511","Olr1512","Olr1513","Olr1514","Olr1515","Olr1516","Olr1518-ps","Olr1517","Olr1519","Olr152","Olr1520","Olr1521","Olr1522","Olr1524-ps","Olr1525","Olr1523","Olr1526-ps","Olr1527-ps","Olr1528","Olr1529","Olr153","Olr1531","Olr1530","Olr1532","Olr1533","Olr1534-ps","Olr1535","Olr1536","Olr1537","Olr1538","Olr1540","Olr1539","Olr1541","Olr154","Olr1542","Olr1543","Olr1544-ps","Olr1545","Olr1546","Olr1548","Olr1547","Olr1549","Olr155","Olr1550-ps","Olr1551","Olr1552-ps","Olr1554-ps","Olr1553","Olr1555","Olr1556-ps","Olr1557","Olr1558","Olr156","Olr1559","Olr1560","Olr1563","Olr1562","Olr1561","Olr1564","Olr1566","Olr1565","Olr1567","Olr157","Olr1570","Olr1568","Olr1569","Olr1571","Olr1572","Olr1573-ps","Olr1575-ps","Olr1574-ps","Olr1577-ps","Olr1578-ps","Olr1576","Olr1580-ps","Olr158","Olr1579","Olr1581","Olr1583","Olr1582","Olr1584","Olr1585","Olr1586-ps","Olr1588","Olr1587","Olr1589","Olr1590","Olr159","Olr1591","Olr1592","Olr1593","Olr1594-ps","Olr1595","Olr1598","Olr1599-ps","Olr1597","Olr1596","Olr16","Olr1600","Olr160","Olr1601","Olr1604-ps","Olr1603-ps","Olr1602","Olr1605","Olr1606","Olr1607","Olr1608","Olr1609","Olr161","Olr1610","Olr1612","Olr1611","Olr1614","Olr1615","Olr1616","Olr1618-ps","Olr1617","Olr1619","Olr1620","Olr162","Olr1621","Olr1623-ps","Olr1622","Olr1624","Olr1629","Olr1626","Olr1625","Olr1627","Olr1630","Olr163","Olr1631","Olr1632","Olr1633","Olr1634-ps","Olr1637","Olr1635","Olr1638","Olr1639","Olr164","Olr1640","Olr1641","Olr1642","Olr1643","Olr1644","Olr1645","Olr1646","Olr1647-ps","Olr1648-ps","Olr1649-ps","Olr1650-ps","Olr165","Olr1651-ps","Olr1652","Olr1656-ps","Olr1653","Olr1655-ps","Olr1654","Olr1659-ps","Olr1657","Olr1658","Olr166-ps","Olr1665-ps","Olr1664","Olr1660","Olr1662","Olr1666","Olr1667","Olr1669-ps","Olr1668","Olr1672-ps","Olr1671","Olr1670","Olr167","Olr1674-ps","Olr1676-ps","Olr1675","Olr1673","Olr1680","Olr1679","Olr168","Olr1678","Olr1681","Olr1682","Olr1684","Olr1683","Olr1685-ps","Olr1686","Olr1687","Olr1688","Olr1689","Olr1690","Olr1692","Olr1691","Olr1693","Olr1694","Olr1695","Olr1697","Olr1696","Olr1698-ps","Olr1699","Olr17","Olr170","Olr1703-ps","Olr1701","Olr1700","Olr1702","Olr1704","Olr1706-ps","Olr1705","Olr1707","Olr1709","Olr1708","Olr171","Olr1710","Olr1711-ps","Olr1712-ps","Olr1713-ps","Olr1714","Olr1717-ps","Olr1715-ps","Olr1716-ps","Olr1719-ps","Olr1718","Olr172","Olr1720","Olr1721-ps","Olr1723-ps","Olr1722","Olr1724","Olr1725-ps","Olr1726","Olr1728-ps","Olr1729","Olr173-ps","Olr1731","Olr1732-ps","Olr1733","Olr1730","Olr1734","Olr1735","Olr1736","Olr1737","Olr1741-ps","Olr1738","Olr174","Olr1739","Olr1740-ps","Olr1742","Olr1743","Olr1744","Olr1747-ps","Olr1746","Olr1749","Olr1748","Olr175","Olr1750","Olr1751","Olr1752-ps","Olr1753-ps","Olr1754-ps","Olr1756-ps","Olr1755-ps","Olr1757-ps","Olr1758-ps","Olr1759-ps","Olr1760-ps","Olr176","Olr1761-ps","Olr1762-ps","Olr1763-ps","Olr1764-ps","Olr1765","Olr1766","Olr1767","Olr1768","Olr1769-ps","Olr1770-ps","Olr177-ps","Olr1771-ps","Olr1772-ps","Olr1773-ps","Olr1774-ps","Olr1775-ps","Olr1776-ps","Olr1777-ps","Olr1778-ps","Olr1779-ps","Olr1780-ps","Olr178","Olr1781-ps","Olr1783-ps","Olr1782-ps","Olr1785-ps","Olr1784-ps","Olr1786-ps","Olr1787-ps","Olr1788-ps","Olr1790-ps","Olr1789-ps","Olr1791-ps","Olr1792-ps","Olr1793-ps","Olr179","Olr1795-ps","Olr1794-ps","Olr1796-ps","Olr1797-ps","Olr1798-ps","Olr1799-ps","Olr18-ps","Olr180","Olr1800-ps","Olr1802-ps","Olr1801-ps","Olr1803-ps","Olr1804-ps","Olr1805-ps","Olr1806-ps","Olr1807-ps","Olr1809-ps","Olr1808-ps","Olr1810-ps","Olr181","Olr1811-ps","Olr1812-ps","Olr1814-ps","Olr1813-ps","Olr1815-ps","Olr1816-ps","Olr1817-ps","Olr1818-ps","Olr1819-ps","Olr182-ps","Olr1820-ps","Olr1821-ps","Olr1822-ps","Olr1823-ps","Olr1824-ps","Olr1825-ps","Olr1826-ps","Olr1827-ps","Olr1828-ps","Olr1829-ps","Olr1830-ps","Olr183","Olr1831-ps","Olr1832-ps","Olr1833-ps","Olr1835-ps","Olr1834-ps","Olr1836-ps","Olr1837-ps","Olr1838-ps","Olr1839-ps","Olr1840-ps","Olr184","Olr1841-ps","Olr1842-ps","Olr1843-ps","Olr1844-ps","Olr1845","Olr1846-ps","Olr1849-ps","Olr1847-ps","Olr1848-ps","Olr1850-ps","Olr185","Olr1851-ps","Olr1852-ps","Olr1853-ps","Olr1855-ps","Olr1854-ps","Olr1856-ps","Olr1857-ps","Olr1858-ps","Olr1859-ps","Olr1860-ps","Olr186","Olr1861-ps","Olr1862-ps","Olr1863-ps","Olr1864-ps","Olr1865-ps","Olr1866-ps","Olr1868","Olr1869","Olr1870","Olr1867","Olr187-ps","Olr1872","Olr1873","Olr1874","Olr1876","Olr1875","Olr1878","Olr1877","Olr188","Olr19","Olr189","Olr190","Olr191-ps","Olr192","Olr193","Olr195-ps","Olr194","Olr196","Olr197","Olr198","Olr2-ps","Olr199","Olr1l","Olr20","Olr200","Olr201","Olr202","Olr203","Olr205","Olr204","Olr206","Olr207-ps","Olr208","Olr21-ps","Olr209","Olr210","Olr212-ps","Olr211","Olr214","Olr213","Olr215","Olr216-ps","Olr217","Olr218","Olr22-ps","Olr220","Olr219","Olr221","Olr222","Olr223","Olr225-ps","Olr224","Olr228-ps","Olr226","Olr227","Olr229","Olr230","Olr23","Olr231","Olr232","Olr236-ps","Olr233","Olr235","Olr234","Olr238-ps","Olr237","Olr239","Olr24","Olr243-ps","Olr241","Olr240","Olr242","Olr245","Olr244","Olr246","Olr247","Olr248-ps","Olr25","Olr250","Olr251","Olr254-ps","Olr252","Olr255","Olr256-ps","Olr257","Olr258-ps","Olr26-ps","Olr259","Olr260","Olr261-ps","Olr262","Olr265-ps","Olr263","Olr264","Olr266-ps","Olr267","Olr268","Olr269-ps","Olr27","Olr270","Olr271","Olr273-ps","Olr272","Olr274-ps","Olr276","Olr278","Olr279","Olr28-ps","Olr280-ps","Olr281","Olr282","Olr283","Olr284-ps","Olr285","Olr286","Olr287","Olr288","Olr29","Olr289","Olr290-ps","Olr293-ps","Olr292","Olr294-ps","Olr294","Olr295","Olr296-ps","Olr297","Olr298","Olr299","Olr3","Olr30","Olr301-ps","Olr300","Olr302","Olr303","Olr304","Olr305","Olr306","Olr307","Olr308","Olr309","Olr31-ps","Olr310","Olr312","Olr311","Olr313","Olr314-ps","Olr315","Olr317-ps","Olr318","Olr319","Olr32","Olr320-ps","Olr321","Olr322","Olr323","Olr324","Olr325","Olr326","Olr327","Olr328-ps","Olr329","Olr33-ps","Olr330","Olr331","Olr332","Olr333-ps","Olr334","Olr335-ps","Olr336","Olr337","Olr338","Olr339","Olr34","Olr340","Olr342-ps","Olr341","Olr343","Olr344","Olr346","Olr347","Olr348","Olr349","Olr35","Olr350-ps","Olr351-ps","Olr352","Olr353-ps","Olr354","Olr355-ps","Olr356-ps","Olr357","Olr359-ps","Olr358","Olr36","Olr360","Olr361","Olr362-ps","Olr366-ps","Olr363","Olr365","Olr367","Olr368-ps","Olr369-ps","Olr370-ps","Olr37","Olr371","Olr372","Olr373","Olr374","Olr375","Olr376","Olr377","Olr378","Olr38-ps","Olr379","Olr380","Olr381-ps","Olr382","Olr383","Olr384","Olr385","Olr386","Olr389-ps","Olr387","Olr391-ps","Olr39","Olr390","Olr393-ps","Olr394-ps","Olr392","Olr395","Olr396","Olr397","Olr398","Olr399","Olr4","Olr40","Olr400","Olr401","Olr402","Olr403","Olr404","Olr405-ps","Olr406","Olr407","Olr408","Olr409","Olr41","Olr410","Olr411","Olr412-ps","Olr413","Olr414","Olr415","Olr416","Olr417","Olr418","Olr42-ps","Olr419","Olr420","Olr421","Olr422","Olr423","Olr424","Olr425","Olr427","Olr429","Olr428","Olr43","Olr433-ps","Olr431-ps","Olr434","Olr435-ps","Olr436","Olr437","Olr437-ps","Olr438-ps","Olr439","Olr44","Olr440","Olr441","Olr442","Olr443","Olr444","Olr445","Olr446","Olr447","Olr449-ps","Olr448","Olr45","Olr452-ps","Olr450","Olr453","Olr454","Olr455","Olr456","Olr457-ps","Olr458","Olr459","Olr46","Olr460-ps","Olr461","Olr462","Olr463","Olr464","Olr465","Olr466","Olr467-ps","Olr468","Olr469","Olr47","Olr470","Olr471","Olr472","Olr473","Olr475","Olr476","Olr477","Olr478-ps","Olr479","Olr48","Olr480","Olr481","Olr482","Olr483","Olr484","Olr485","Olr487-ps","Olr486","Olr488","Olr489-ps","Olr49","Olr490","Olr491","Olr492-ps","Olr493","Olr494-ps","Olr495","Olr496","Olr497-ps","Olr498-ps","Olr5","Olr499","Olr50","Olr501-ps","Olr500","Olr503-ps","Olr502","Olr505","Olr506-ps","Olr507","Olr508","Olr509-ps","Olr510","Olr51","Olr511-ps","Olr512-ps","Olr513","Olr514","Olr515","Olr516","Olr517","Olr518","Olr519","Olr520","Olr521-ps","Olr523-ps","Olr524-ps","Olr522","Olr525-ps","Olr526","Olr527","Olr528","Olr529","Olr53","Olr530","Olr531","Olr532","Olr533","Olr534-ps","Olr536","Olr535","Olr537","Olr538-ps","Olr54-ps","Olr539","Olr540","Olr541","Olr542","Olr543-ps","Olr544","Olr545","Olr547-ps","Olr546","Olr548-ps","Olr549-ps","Olr55-ps","Olr551","Olr550","Olr552","Olr553-ps","Olr554","Olr555","Olr556","Olr558","Olr557","Olr559","Olr56","Olr560","Olr561","Olr562","Olr563","Olr564-ps","Olr565-ps","Olr567","Olr566","Olr568-ps","Olr569","Olr57","Olr570-ps","Olr571-ps","Olr573-ps","Olr572-ps","Olr574-ps","Olr576","Olr575","Olr577","Olr579-ps","Olr578","Olr58-ps","Olr580-ps","Olr581","Olr582","Olr583","Olr584","Olr585-ps","Olr586","Olr587","Olr588","Olr589-ps","Olr590","Olr591-ps","Olr59","Olr593-ps","Olr592","Olr594","Olr596","Olr595","Olr597","Olr598","Olr6","Olr600-ps","Olr60","Olr601","Olr603-ps","Olr602","Olr604","Olr605-ps","Olr606","Olr607","Olr608","Olr609","Olr61","Olr610","Olr611","Olr612-ps","Olr613","Olr614","Olr616-ps","Olr617-ps","Olr619","Olr62","Olr620-ps","Olr621","Olr622-ps","Olr624","Olr623","Olr625","Olr626-ps","Olr627-ps","Olr628-ps","Olr629","Olr63","Olr630","Olr632-ps","Olr631","Olr633","Olr634","Olr635","Olr636","Olr638-ps","Olr637","Olr639","Olr640","Olr641","Olr642-ps","Olr643-ps","Olr644-ps","Olr645-ps","Olr646","Olr647-ps","Olr648","Olr649","Olr650","Olr651","Olr653","Olr652","Olr654","Olr655","Olr657","Olr658","Olr66-ps","Olr659","Olr660","Olr661","Olr662","Olr663","Olr664","Olr665","Olr667-ps","Olr666","Olr668","Olr67","Olr669","Olr670","Olr671","Olr672","Olr673","Olr674","Olr676-ps","Olr675","Olr678","Olr677","Olr679","Olr680-ps","Olr68","Olr681","Olr682-ps","Olr683-ps","Olr684","Olr685-ps","Olr686","Olr687","Olr688-ps","Olr689","Olr69","Olr690","Olr691","Olr692-ps","Olr693","Olr694","Olr695","Olr696","Olr697","Olr698","Olr699-ps","Olr700-ps","Olr7","Olr70","Olr701","Olr703","Olr702","Olr704","Olr705","Olr706-ps","Olr707","Olr708-ps","Olr709","Olr71-ps","Olr710","Olr711","Olr712","Olr713","Olr714","Olr715","Olr716","Olr717","Olr718","Olr719-ps","Olr72","Olr720","Olr721","Olr722","Olr723-ps","Olr724","Olr726","Olr725","Olr727","Olr728","Olr729","Olr73-ps","Olr730-ps","Olr732-ps","Olr731","Olr734","Olr733","Olr735","Olr738-ps","Olr736","Olr737","Olr739-ps","Olr740-ps","Olr74","Olr741","Olr743-ps","Olr742","Olr744","Olr746-ps","Olr745","Olr747","Olr748","Olr749","Olr75","Olr750","Olr752","Olr753","Olr755-ps","Olr754","Olr756","Olr758","Olr757","Olr76-ps","Olr762-ps","Olr760","Olr764-ps","Olr763","Olr765","Olr766","Olr767","Olr768","Olr769","Olr77","Olr770","Olr771","Olr772","Olr773","Olr775","Olr774","Olr776","Olr777","Olr778","Olr779","Olr78","Olr780","Olr781","Olr782","Olr783","Olr784","Olr785","Olr786","Olr787-ps","Olr788","Olr789","Olr79","Olr790","Olr791","Olr792","Olr794-ps","Olr795","Olr798-ps","Olr796","Olr799","Olr80","Olr8","Olr801","Olr802","Olr803","Olr804","Olr805-ps","Olr806","Olr807","Olr808","Olr809","Olr81","Olr810","Olr811","Olr812","Olr813","Olr814-ps","Olr815-ps","Olr816","Olr818","Olr819","Olr82","Olr820","Olr821","Olr822-ps","Olr823","Olr824","Olr825","Olr826","Olr827","Olr829","Olr83","Olr828","Olr830","Olr831","Olr832","Olr834","Olr833","Olr835-ps","Olr836","Olr837","Olr838","Olr839","Olr84","Olr840","Olr843-ps","Olr841","Olr846-ps","Olr845","Olr844","Olr848","Olr847","Olr85","Olr850","Olr851","Olr852","Olr853","Olr854","Olr855","Olr856","Olr857","Olr858","Olr859","Olr86","Olr860","Olr862","Olr865","Olr867","Olr868","Olr866","Olr870-ps","Olr87","Olr869","Olr872-ps","Olr871-ps","Olr873-ps","Olr874-ps","Olr876","Olr875","Olr877","Olr878","Olr879","Olr88","Olr880","Olr881","Olr882-ps","Olr883","Olr884-ps","Olr885-ps","Olr886","Olr888-ps","Olr887-ps","Olr89","Olr889","Olr890-ps","Olr891-ps","Olr893-ps","Olr892","Olr894","Olr895-ps","Olr896","Olr897-ps","Olr899-ps","Olr898","Olr9-ps","Olr90-ps","Olr900-ps","Olr901","Olr902-ps","Olr904-ps","Olr903","Olr905","Olr906","Olr907","Olr908","Olr909-ps","Olr91","Olr912-ps","Olr910","Olr913-ps","Olr914-ps","Olr916","Olr918-ps","Olr919-ps","Olr917","Olr920","Olr92","Olr921","Olr923-ps","Olr922","Olr924-ps","Olr925-ps","Olr926-ps","Olr927","Olr928-ps","Olr929-ps","Olr930-ps","Olr93","Olr931","Olr932","Olr934-ps","Olr933-ps","Olr935-ps","Olr936","Olr937","Olr938-ps","Olr939-ps","Olr94","Olr940-ps","Olr941-ps","Olr942-ps","Olr943","Olr944-ps","Olr945-ps","Olr946-ps","Olr947","Olr948-ps","Olr949-ps","Olr95","Olr951","Olr950","Olr952","Olr953-ps","Olr954","Olr955-ps","Olr958-ps","Olr956","Olr957-ps","Olr959","Olr96","Olr960","Olr961-ps","Olr963-ps","Olr962","Olr964","Olr966-ps","Olr965-ps","Olr967-ps","Olr968-ps","Olr969-ps","Olr97","Olr970-ps","Olr971-ps","Olr972-ps","Olr973-ps","Olr975-ps","Olr974-ps","Olr976-ps","Olr977-ps","Olr978-ps","Olr979-ps","Olr98","Olr980-ps","Olr981-ps","Olr982","Olr983-ps","Olr985-ps","Olr984","Olr986-ps","Olr987","Olr988-ps","Olr989-ps","Olr99-ps","Olr990","Olr991","Olr992-ps","Olr993-ps","Olr994-ps","Olr995","Olr997-ps","Olr996","Olr998-ps","Olr999-ps","Oma1","Omd","Omg","Omp","Onecut1","Onecut2","Onecut3","Ooep","Oog3","Oog1","Oosp1","Oosp2","Opa3","Opalin","Opa1","Opcml","Oplah","Ophn1","Opn1mw","Opn1sw","Opn3","Opn4","Opn5","Oprd1","Oprk1","Oprl1","Oprpn","Optc","Or10ad1","Oprm1","Or51t1","Optn","Or7e24","Orai2","Orai1","Orai3","Oraov1","Orc1","Orc2","Orc3","Orc4","Orc5","Orc6","Orm1","Ormdl1","Ormdl2","Ormdl3","Os9","Osbp","Osbp2","Osbpl11","Osbpl10","Osbpl1a","Osbpl2","Osbpl3","Osbpl5","Osbpl6","Osbpl7","Osbpl8","Osbpl9","Oscar","Oscp1","Oser1","Osgep","Osgepl1","Osgin1","Osm","Osgin2","Osmr","Osr1","Osr2","Ost4","Ostc","Ostf1","Ostn","Ostm1","Otc","Otoa","Otof","Otogl","Otog","Otol1","Otop1","Otop2","Otop3","Otor","Otos","Otp","Otub1","Otub2","Otud1","Otud3","Otud4","Otud5","Otud6a","Otud6b","Otud7a","Otud7b","Otulin","Otx1","Otx2","Ovca2","Ovch2","Ovol2","Ovol1","Ovol3","Oxa1l","Oxct2a","Oxct1","Oxct2b","Oxgr1","Oxld1","Oxnad1","Oxsm","Oxr1","Oxsr1","P22k15","Oxt","Oxtr","P2rx1","P2rx3","P2rx2","P2rx4","P2rx5","P2rx6","P2ry10","P2ry1","P2rx7","P2ry13","P2ry12","P2ry14","P2ry2","P2ry4","P2ry6","P3h3","P3h2","P3h1","P3h4","P4ha1","P4ha2","P4ha3","P4hb","P4htm","p53-ps","Pabpc1-ps1","Pa2g4","Pabpc1","Pabpc1l2a","Pabpc1l","Pabpc2","Pabpc4l","Pabpc4","Pabpc5","Pabpc6","Pabpn1l","Pabpn1","Pacrg","Pacrgl","Pacs1","Pacs2","Pacsin1","Pacsin2","Pacsin3","Padi1","Padi3","Padi2","Padi6","Padi4","Paf1","Pafah1b3","Pafah1b2","Pafah1b1","Pag1","Pafah2","Pagr1","Paip1","Pah","Paics","Paip2","Paip2b","Paip2l1","Pak1ip1","Pak2","Pak1","Pak3","Pak4","Pak6","Pak7","Palb2","Pald1","Palld","Palldl1","Palm","Palm2","Palm3","Palmd","Pam16","Pamr1","Pam","Pan3","Pan2","Pank1","Pank3","Pank2","Pank4","Panx2","Panx1","Panx3","Paox","Papd4","Papd5","Papd7","Papln","Papolb","Papola","Papolg","Pappa1","Papss1","Papss2","Paqr3","Paqr4","Paqr5","Paqr6","Paqr7","Paqr8","Paqr9","Pard3b","Pard3","Pard6a","Pard6b","Pard6g","Parg","Parl","Park7","Parm1","Parn","Pappa2","Parp10","Parp11","Parp12","Parp16","Parp14","Parp1","Parp2","Parp3","Parp4","Parp6","Parp9","Parp8","Pars2","Parpbp","Parva","Parvg","Parvb","Pasd1","Pate-f","Pask","Pate3","Pate1","Pate2","Pate4","Patl2","Patl1","Patj","Patz1","Pax1","Pawr","Pax2","Pax3","Pax5","Pax4","Pax7","Pax9","Paxbp1","Pax8","Pax6","Paxip1","Pbdc1","Paxx","Pbld2","Pbk","Pbp-ps","Pbld1","Pbp2","Pbsn","Pbx1","Pbx3","Pbx2","Pbrm1","Pbxip1","Pbx4","Pcbd2","Pcbd1","Pcbp1","Pc","Pcbp2","Pcbp4","Pcbp3","Pccb","Pcdh1","Pcca","Pcdh10","Pcdh11x","Pcdh12","Pcdh15","Pcdh17","Pcdh18","Pcdh19","Pcdh20","Pcdh8","Pcdh9","Pcdha1","Pcdh7","Pcdha10","Pcdha2","Pcdha13","Pcdha11","Pcdha12","Pcdha3","Pcdha5","Pcdha7","Pcdha6","Pcdha8","Pcdha4","Pcdha9","Pcdhac1","Pcdhb1","Pcdhac2","Pcdhb10","Pcdhb11","Pcdhb14","Pcdhb12","Pcdhb15","Pcdhb16","Pcdhb17","Pcdhb18","Pcdhb2","Pcdhb19","Pcdhb20","Pcdhb21","Pcdhb2l","Pcdhb22","Pcdhb3","Pcdhb4","Pcdhb5","Pcdhb6","Pcdhb7","Pcdhb8","Pcdhb9","Pcdhga1","Pcdhga10","Pcdhga11","Pcdhga12","Pcdhga2","Pcdhga3","Pcdhga4","Pcdhga5","Pcdhga6","Pcdhga8","Pcdhgb2","Pcdhga7","Pcdhgb4","Pcdhga9","Pcdhgb6","Pcdhgb5","Pcdhgb8","Pcdhgb7","Pcdhgc3","Pcdhgc5","Pced1a","Pcgf1","Pcf11","Pced1b","Pcgf2","Pcgf3","Pcid2","Pcgf6","Pcgf5","Pcif1","Pclaf","Pck2","Pck1","Pclo","Pcm1","Pcmt1","Pcmtd1","Pcmtd2","Pcnp","Pcnt","Pcnx1","Pcna","Pcnx2","Pcnx3","Pcnx4","Pcolce","PCOLCE2","Pcp2","Pcp4l1","Pcp4","Pcsk1n","Pcsk1","Pcsk2","Pcsk4","Pcsk5","Pcsk6","Pcsk7","Pctp","Pcsk9","Pcyox1","Pcyox1l","Pcyt1b","Pcyt1a","Pdap1","Pcyt2","Pdc","Pdcd10","Pdcd1lg2","Pdcd1","Pdcd2","Pdcd11","Pdcd2l","Pdcd5","Pdcd6","Pdcd4","Pdcd6ip","Pdcd7","Pdcl2","Pdcl","Pdcl3","Pde10a","Pde12","Pde11a","Pde1a","Pde1b","Pde1c","Pde2a","Pde3b","Pde3a","Pde4a","Pde4b","Pde4c","Pde4dip","Pde5a","Pde6a","Pde4d","Pde6c","Pde6b","Pde6d","Pde6g","Pde6h","Pde7a","Pde7b","Pde8a","Pde8b","Pdf","Pde9a","Pdgfa","Pdgfc","Pdgfd","Pdgfb","Pdgfrl","Pdgfra","Pdha1l1","Pdha1","Pdha2","Pdgfrb","Pdhb","Pdia2","Pdhx","Pdia5","Pdia3","Pdia4","Pdik1l","Pdia6","Pdilt","Pdk1","Pdk3","Pdk2","Pdk4","Pdlim1","Pdlim2","Pdlim3","Pdlim4","Pdlim5","Pdlim7","Pdp1","Pdp2","Pdpk1","Pdpr","Pdrg1","Pdpn","Pds5a","Pdss1","Pds5b","Pdss2","Pdxdc1","Pdxp","Pdxk","Pdx1","Pdyn","Pdzd11","Pdzd3","Pdzd2","Pdzd4","Pdzd7","Pdzd8","Pdzd9","Pdzk1","Pdzk1ip1","Pdzrn4","Pdzrn3","Pea15","Pebp1-ps1","Peak1","Pear1","Pebp4","Pebp1","Pef1","Pecr","Peg10","Peg12","Pecam1","Peg3","Peli1-ps1","Peli1","Peli2","Peli3","Pelo","Pelp1","Pemt","Pepd","Penk","Per1","Perm1","Per3","Perp","Per2","Pes1","Pet100","Pex10","Pex11a","Pex1","Pex11g","Pex11b","Pex12","Pex13","Pex14","Pex16","Pex26","Pex19","Pex2","Pex3","Pex5","Pex6","Pex5l","Pex7","Pf4","Pfdn1","Pfdn4","Pfdn2","Pfas","Pfdn5","Pfdn6","Pfkfb1","Pfkfb3","Pfkfb4","Pfkfb2","Pfkl","Pfkm","Pfn2","Pfkp","Pfn3","Pfn1","Pfpl","Pfn4","Pga5","Pgam1","Pgam2","Pgap1","Pgam5","Pgbd1","Pgap3","Pgap2","Pgbd2","Pgbd5","Pgc","Pgghg","Pgd","Pggt1b","Pgf","Pgk2","Pgls","Pgk1","Pglyrp1","Pglyrp2","Pglyrp3","Pglyrp3b","Pglyrp4","Pgm1","Pgm2","Pgm2l1","Pgm5","Pgm3","Pgpep1","Pgp","Pgpep1l","Pgr15l","Pgy2","Pgs1","Pgrmc2","Pgr","Pgrmc1","Pgy4","Phactr4","Phactr3","Phactr1","Phactr2","Phb-ps1","Phax","Phc1","Phb","Phb2","Phc2","Phc3","Phf10","Phf1","Phex","Phf11","Phf11b","Phf13","Phf12","Phf19","Phf14","Phf2","Phf20","Phf20l1","Phf21b","Phf23","Phf21a","Phf24","Phf3","Phf5a","Phf6","Phf7","Phf8","Phgr1","Phgdh","Phip","Phka1","Phka2","Phkb","Phkg1","Phkg2","Phlda1","Phlda2","Phlda3","Phldb2","Phldb1","Phldb3","Phlpp2","Phospho1","Phlpp1","Phospho2","Phox2a","Phox2b","Phpt1","Phtf1","Phrf1","Phtf2","Phyh","Phyhd1","Phyhip","Phyhipl","Pi15","Phykpl","Pi16","Pi4k2a","Pi4k2b","Pi4kb","Pianp","Pi4ka","Pias1","Pias2","Pias3","Pibf1","Pias4","Picalm","Pid1","Pidd1","Pick1","Piezo1","Pifo","Pif1","Piga","Piezo2","Pigb","Pigbos1","Pigf","Pigc","Pigh","Pigg","Pigk","Pigm","Pigl","Pigo","Pign","Pigp","Pigq","Pigs","Pigr","Pigt","Pigu","Pigv","Pigw","Pigx","Pigy","Pigz","Pih1d2","Pih1d1","Pih1d3","Pik3ap1","Pik3c2a","Pik3c2b","Pik3c2g","Pik3c3","Pik3ca","Pik3cb","Pik3cd","Pik3ip1","Pik3cg","Pik3r2","Pik3r3","Pik3r4","Pik3r5","Pik3r1","Pik3r6","Pilra","Pilrb","Pikfyve","Pim2","Pim1","Pim3","Pimreg","Pin4","Pin1","Pinlyp","Pinx1","Pink1","Pip","Pip4k2b","Pip4k2a","Pip4k2c","Pip4p1","Pip4p2","Pip5k1a","Pip5kl1","Pip5k1b","Pip5k1c","Pipox","Pira2","Pir","Pirt","Pirb","Pithd1","Pisd","Pitpna","Pitpnb","Pitpnc1","Pitpnm1","Pitpnm2","Pitpnm3","Pitrm1","Pitx1","Pitx3","Pitx2","Piwil1","Piwil2","Piwil4","Pja1","Pja2","Pjvk","Pkd1l2","Pkd1l1","Pkd1","Pkd1l3","Pkd2l1","Pkd2","Pkd2l2","Pkdrej","Pkdcc","Pkhd1","Pkia","Pkhd1l1","Pkig","Pkib","Pkmyt1","Pklr","Pkm","Pkn1","Pkn2","Pknox1","Pkn3","Pknox2","Pkp1","Pkp2","Pkp3","Pla1a","Pkp4","Pla2g10","Pla2g12a","Pla2g12b","Pla2g15","Pla2g16","Pla2g1b","Pla2g2c","Pla2g2a","Pla2g2e","Pla2g2d","Pla2g2f","Pla2g3","Pla2g4b","Pla2g4cl1","Pla2g4c","Pla2g4a","Pla2g4d","Pla2g4f","Pla2g4e","Pla2g5","Pla2g7","Pla2r1","Plaa","Plac1","Pla2g6","Plac8","Plac8l1","Plac9","Plag1","Plagl1","Plagl2","Plb1","Plaur","Plbd1","Plat","Plau","Plbd2","Plcb3","Plcb2","Plcb1","Plcb4","Plcd1","Plcd3","Plcd4","Plce1","Plch1","Plcg2","Plch2","Plcg1","Plcxd1","Plcl1","Plcl2","Plcxd3","Plcxd2","Pld3","Plcz1","Pld4","Pld2","Pld6","Pld1","Pld5","Plek","Plek2","Plekha2","Plekha1","Plec","Plekha3","Plekha4","Plekha6","Plekha7","Plekha5","Plekha8","Plekhb1","Plekhb2","Plekhd1","Plekhf1","Plekhf2","Plekhg1","Plekhg2","Plekhg3","Plekhg4","Plekhg7","Plekhg5","Plekhg6","Plekhh1","Plekhh3","Plekhh2","Plekhm1","Plekhj1","Plekhm2","Plekhm3","Plekhn1","Plekho2","Plekho1","Plet1","Plekhs1","Plgrkt","Plin1","Plin3","Plin2","Plin4","Plg","Plin5","Plk3","Plk2","Plk4","Plk1","Plk5","Pllp","Plod2","Plod1","Pln","Plod3","Plp2","Plp1","Plpbp","Plpp1","Plpp2","Plpp3","Plpp4","Plpp5","Plpp6","Plpp7","Plppr1","Plppr2","Plppr5","Plppr3","Plppr4","Plrg1","Pls1","Plscr2","Pls3","Plscr1","Plscr4","Plscr3","Plscr5","Pltp","Plvap","Plxdc1","Plxdc2","Plxna1","Plxna2","Plxna4","Plxna3","Plxnb1","Plxnb2","Plxnc1","Plxnb3","Plxnd1","Pm20d1","Pm20d2","Pmaip1","Pmel","Pmch","Pmepa1","Pmf1","Pmfbp1","Pmm2","Pmm1","Pml","Pmp2","Pmp22","Pmpca","Pmpcb","Pms1","Pms2","Pmvk","Pnck","Pnisr","Pnldc1","Pnkp","Pnkd","Pnlip","Pnliprp1","Pnma1","Pnma2","Pnliprp2","Pnma3","Pnma5","Pnma8a","Pnma8b","Pno1-ps1","Pnmt","Pnn","Pno1","Pnoc","Pnp","Pnpla1","Pnpla2","Pnpla3","Pnpla4","Pnpla5","Pnpla6","Pnpla8","Pnpla7","Pnpo","Pnrc1","Pnpt1","Pnrc2","Poc1a","Poc1b","Podn","Poc5","Podnl1","Podxl2","Podxl","Pof1b","Pofut2","Pofut1","Pogk","Poglut1","Pogz","Pola2","Pola1","Polb","Pold1","Pold2","Pold3","Pold4","Poldip2","Poldip3","Pole2","Pole4","Pole","Pole3","Polh","Polg2","Poli","Polg","Polk","Poll","Polm","Poln","Polq","Polr1a","Polr1b","Polr1d","Polr1c","Polr1e","Polr2a","Polr2b","Polr2c","Polr2d","Polr2e","Polr2h-ps1","Polr2f","Polr2g","Polr2h","Polr2i","Polr2j","Polr2l","Polr2l-ps1","Polr2k","Polr3b","Polr2m","Polr3d","Polr3a","Polr3c","Polr3d-ps1","Polr3f","Polr3e","Polr3g","Polr3gl","Polr3h","Pom121l12","Polrmt","Polr3k","Pom121","Pom121l2","Pomgnt2","Pomgnt1","Pomk","Pomp","Pomc","Pomt2","Pomt1","Pon2","Pon3","Pon1","Pop1","Pop4","Pop5","Pop7","Popdc3","Popdc2","Porcn","Por","Postn","Pot1b","Pot1","Potec","Potef","Poteh","Poteg","Potem","Pou2af1","Pou1f1","Pou2f1","Pou2f3","Pou2f2","Pou3f1","Pou3f2","Pou3f3","Pou3f4","Pou4f1","Pou4f3","Pou4f2","Pou5f2","Pou6f2","Pou5f1","Pou6f1","Pp2d1","Ppa2","Ppa1","Ppan","Ppard","Ppargc1b","Ppara","Ppbp","Ppat","Ppcdc","Ppargc1a","Ppcs","Pparg","Ppdpf","Ppef1","Ppef2","Ppfia1","Ppfia2","Ppfia3","Ppfia4","Pphln1","Ppfibp1","Ppfibp2","Ppial4d","Ppia","Ppial4g","Ppic","Ppid-ps1","Ppib","Ppid","Ppidl1","Ppie","Ppig","Ppif","Ppih","Ppil1","Ppil2","Ppil4","Ppil3","Ppil6","Ppip5k1","Ppip5k2","Ppl","Ppm1a","Ppm1b","Ppm1d","Ppm1e","Ppm1g","Ppm1f","Ppm1j","Ppm1h","Ppm1m","Ppm1k","Ppm1l","Ppm1n","Ppme1","Ppp1cb-ps","Ppox","Ppp1ca","Ppp1cb","Ppp1r11","Ppp1r10","Ppp1cc","Ppp1r12b","Ppp1r12a","Ppp1r13b","Ppp1r12c","Ppp1r13l","Ppp1r14a","Ppp1r14b","Ppp1r14c","Ppp1r14d","Ppp1r15a","Ppp1r15b","Ppp1r16a","Ppp1r17","Ppp1r16b","Ppp1r18","Ppp1r1a","Ppp1r1c","Ppp1r1b","Ppp1r2","Ppp1r2-ps1","Ppp1r26","Ppp1r21","Ppp1r27","Ppp1r32","Ppp1r35","Ppp1r36","Ppp1r37","Ppp1r3a","Ppp1r3b","Ppp1r3c","Ppp1r3d","Ppp1r3f","Ppp1r3e","Ppp1r3g","Ppp1r42","Ppp1r8","Ppp1r7","Ppp1r9a","Ppp1r9b","Ppp2cb","Ppp2r1a","Ppp2ca","Ppp2r1b","Ppp2r2c","Ppp2r2a","Ppp2r2b","Ppp2r2d","Ppp2r3a","Ppp2r3b","Ppp2r3c","Ppp2r5a","Ppp2r5b","Ppp2r5c","Ppp2r5d","Ppp2r5e","Ppp3cb","Ppp3ca","Ppp3cc","Ppp3r1","Ppp3r2","Ppp4c","Ppp4r1","Ppp4r3c","Ppp4r3a","Ppp4r2","Ppp4r3b","Ppp4r4","Ppp6r1","Ppp6c","Ppp6r2","Ppp5c","Ppp6r3","Pprc1","Ppt1","Ppt2","Pptc7","Ppy","Ppwd1","Pqbp1","Pqlc1","Pqlc2","Pradc1","Pqlc3","Praf2","Prag1","Pram1","Pramef12","Prame","Pramef17","Pramef20","pramef20l","Pramef25","Pramef5","Pramef6","Pramef27","Pramef8","Pramel","Pramel7","Pramel3","Pramel6","Prap1","Prb1","Prb3","Prc1","Prcd","Prcc","Prcp","Prdm1","Prdm11","Prdm12","Prdm10","Prdm13","Prdm14","Prdm15","Prdm16","Prdm2","Prdm4","Prdm5","Prdm8","Prdm6","Prdm9","Prdx1l1","Prdx1","Prdx2","Prdx3","Prdx4","Prdx5","Prdx6","Prelid3a","Prelid1","Preb","Prelid2","Prelid3b","Prep","Prepl","Prelp","Prex1","Prex2","Prg3","Prg2","Prf1","Prg4","Prh1","Prickle2","Prickle1","Prickle4","Prickle3","Prim1","Prima1","Primpol","Prim2","Prkaa1","Prkab1","Prkab2","Prkaa2","Prkaca","Prkacb","Prkag2","Prkag3","Prkag1","Prkar1a-ps1","Prkar1a","Prkar1b","Prkar2a","Prkar2b","Prkcb","Prkca","Prkcg","Prkce","Prkcd","Prkcsh","Prkch","Prkci","Prkcq","Prkd2","Prkd1","Prkd3","Prkcz","Prkdc","Prkg2","Prkg1","Prkra","Prkrip1","Prkx","Prkn","Prl2a1","Prl2b1","Prl2c1","Prl3a1","Prl3b1","Prl3d1","Prl3c1","Prl","Prl3d2","Prl4a1","Prl5a1","Prl3d4","Prl5a2","Prl6a1","Prl7a3","Prl7a4","Prl7d1","Prl7b1","Prl8a3","Prl8a2","Prl8a4","Prl8a7","Prl8a5","Prl8a9","Prlh","Prlhr","Prm2","Prm1","Prm3","Prlr","Prmt2","Prmt1","Prmt3","Prmt5","Prmt6","Prmt7","Prmt8","Prmt9","Prnd","Prob1","Proca1","Prnp","Procr","Prodh1","Proc","Prodh2","Prok1","Prokr1","Prok2","Prokr2","Prom2","Prop1","Prorsd1","Prom1","Proser2","Pros1","Proser1","Proser3","Prox1","Prox2","Prp2","Prp15","Proz","Prp2l1","Prpf18","Prpf31","Prpf3","Prpf19","Prpf38a","Prpf38b","Prpf39","Prpf4","Prpf40a","Prpf40b","Prpf4b","Prpf6","Prpf8","Prpmp5","Prps1l1","Prph","Prps1","Prph2","Prps2","Prpsap1","Prpsap2","Prr11","Prr13","Prr14","Prr12","Prr15","Prr14l","Prr15l","Prr16","Prr18","Prr20e","Prr21","Prr19","Prr23d2","Prr23a","Prr22","Prr29","Prr27","Prr30","Prr32","Prr34","Prr33","Prr3","Prr35","Prr36","Prr7","Prr4","Prr5","Prr5l","Prr9","Prrc1","Prrc2a","Prrc2c","Prrc2b","Prrg1","Prrg2","Prrg3","Prrg4","Prrt1b","Prrt1","Prrt3","Prrt2","Prrt4","Prrx2","Prrx1","Prss1","Prss16","Prss12","Prss21","Prss2","Prss23","Prss27","Prss22","Prss29","Prss3","Prss30","Prss32","Prss33","Prss34","Prss35","Prss36","Prss37","Prss38","Prss39","Prss3b","Prss42","Prss40","Prss41","Prss44","Prss45","Prss46","Prss47","Prss48","Prss50","Prss53","Prss54","Prss55","Prss56","Prss57","Prss58","Prtfdc1","Prss8","Prtn3","Prune1","Prtg","Prune2","Prx","Psapl1","Psca","Psat1","Psap","Psd2","Psd","Psd4","Psd3","Psenen","Psg16","Psg29","Psg19","Psen2","Psen1","Psgb1","Pskh1","Psma1","Psip1","Psma2","Psma3l","Psma3","Psma4","Psma5","Psma6","Psma8","Psma7","Psmb1","Psmb11","Psmb10","Psmb2","Psmb3","Psmb4","Psmb5","Psmb6","Psmb7","Psmb8","Psmc1","Psmc2","Psmb9","Psmc3ip","Psmc3","Psmc4","Psmc6","Psmd10","Psmc5","Psmd1","Psmd11","Psmd12","Psmd13","Psmd14","Psmd2","Psmd3","Psmd4","Psmd5","Psmd6","Psmd7","Psmd8","Psme1-ps1","Psmd9","Psme1","Psme2","Psmf1","Psme3","Psmg1","Psme4","Psmg3","Psmg2","Psmg4","Psors1c2","Pspc1","Pspn","Psph","Psrc1","Pstpip1","Psx1","Pstk","Pstpip2","Ptar1","Ptbp2","Ptafr","Ptcd1","Ptbp1","Ptcd2","Ptbp3","Ptcd3","Ptchd1","Ptchd3","Ptch2","Ptchd4","Ptcra","Ptch1","Ptdss1","Ptdss2","Pter","Ptf1a","Ptgdr","Ptgdr2","Ptgdrl","Ptger1","Ptgds","Ptger2","Ptges2","Pten","Ptges","Ptger4","Ptger3","Ptges3l","Ptges3","Ptges3l1","Ptgir","Ptgfrn","Ptgfr","Ptgr1","Ptgr2","Ptgis","Pth2","Ptgs1","Pth2r","Pth1r","Pthlh","Pth","Ptk6","Ptk7","Ptma","Ptk2","Ptms","Ptn","Ptk2b","Ptov1","Ptp4a2","Ptgs2","Ptp4a1","Ptp4a3","Ptpa","Ptpdc1","Ptpmt1","Ptpn12","Ptpn1","Ptpn13","Ptpn14","Ptpn18","Ptpn11","Ptpn2","Ptpn20","Ptpn21","Ptpn23","Ptpn22","Ptpn3","Ptpn4","Ptpn5","Ptpn6","Ptpn7","Ptpn9","Ptpra","Ptprcap","Ptprb","Ptpre","Ptprc","Ptprd","Ptprf","Ptprg","Ptprh","Ptprj","Ptprk","Ptprm","Ptprn","Ptprn2","Ptpro","Ptprq","Ptprr","Ptprs","Ptprt","Ptpru","Ptprv","Ptprz1","Ptrh1","Ptrh2","Ptrhd1","Pts","Pttg1","Pttg1ip","Ptx3","Ptx4","Pudp","Puf60","Pum1","Pum2","Pum3","Pura","Purb","Purg","Pus1","Pus10","Pus3","Pus7","Pus7l","Pusl1","Pvalb","PVR","Pvrig","Pvt1","Pwp1","Pwp2","Pwwp2a","Pwwp2b","Pxdc1","Pxdn","Pxk","Pxmp2","Pxmp4","Pxn","Pxt1","Pxylp1","Pycard","Pycr1","Pycr2","Pycr3","Pygb","Pygl","Pygm","Pygo1","Pygo2","Pym1","Pyroxd1","Pyroxd2","Pyurf","Pyy","Pzp","Qars","Qdpr","Qk","Qki","Qpct","Qpctl","Qprt","Qrfp","Qrfpr","Qrich1","Qrich2","Qrsl1","Qser1","Qsox1","Qsox2","Qtrt1","Qtrt2","R3hcc1","R3hcc1l","R3hdm1","R3hdm2","R3hdm4","R3hdml","Rab1-ps1","Rab10","Rab11a","Rab11b","Rab11fip1","Rab11fip2","Rab11fip3","Rab11fip4","Rab11fip5","Rab12","Rab13","Rab14","Rab15","Rab17","Rab18","Rab19","Rab1a","Rab1b","Rab1b-ps1","Rab20","Rab21","Rab22a","Rab23","Rab24","Rab25","Rab26","Rab27a","Rab27b","Rab28","Rab29","Rab2a","Rab2b","Rab30","Rab31","Rab32","Rab33a","Rab33b","Rab34","Rab35","Rab36","Rab37","Rab38","Rab39a","Rab3a","Rab3b","Rab3c","Rab3d","Rab3gap1","Rab3gap2","Rab3il1","Rab3ip","Rab40b","Rab40c","Rab42","Rab43","Rab44","Rab4a","Rab4b","Rab5a","Rab5al1","Rab5b","Rab5c","Rab6a","Rab6b","Rab7a","Rab7b","Rab8a","Rab8b","Rab9a","Rab9b","Rabac1","Rabep1","Rabep2","Rabepk","Rabgap1","Rabgap1l","Rabgef1","Rabggta","Rabggta-ps1","Rabggtb","Rabif","Rabl2","Rabl3","Rabl6","Rac1","Rac2","Rac3","Racgap1","Rack1","Rad1","Rad17","Rad18","Rad21","Rad21l1","Rad23a","Rad23b","Rad50","Rad51","Rad51ap1","Rad51ap2","Rad51b","Rad51c","Rad51d","Rad52","Rad54b","Rad54l","Rad54l2","Rad9a","Rad9b","Radil","Rae1","Raet1c","Raet1d","Raet1e","Raet1l","Raf1","Rag1","Rag2","Rai1","Rai14","Rai2","Rala","Ralb","Ralbp1","Ralgapa1","Ralgapa2","Ralgapb","Ralgds","Ralgps1","Ralgps2","Raly","Ralyl","Ramp1","Ramp2","Ramp3","Ran","Ranbp1","Ranbp10","Ranbp17","Ranbp2","Ranbp3","Ranbp3l","Ranbp6","Ranbp9","Rangap1","Rangrf","Rap1a","Rap1b","Rap1gap","Rap1gap2","Rap1gds1","Rap2a","Rap2b","Rap2c","Rapgef1","Rapgef2","Rapgef3","Rapgef4","Rapgef5","Rapgef6","Rapgefl1","Raph1","Rapsn","Rara","Rarb","Rarg","Rarres1","Rarres2","Rars","Rars2","Rasa1","Rasa2","Rasa3","Rasa4","Rasal1","Rasal2","Rasal3","Rasd1","Rasd2","Rasef","Rasgef1a","Rasgef1b","Rasgef1c","Rasgrf1","Rasgrf2","Rasgrp1","Rasgrp2","Rasgrp3","Rasgrp4","Rasip1","Rasl10a","Rasl10b","Rasl11a","Rasl11b","Rasl12","Rasl2-9","Rassf1","Rassf10","Rassf2","Rassf3","Rassf4","Rassf5","Rassf6","Rassf7","Rassf8","Rassf9","RatNP-3b","Raver1","Raver2","Rax","Rb1","Rb1cc1","Rbak","Rbbp4","Rbbp5","Rbbp6","Rbbp7","Rbbp8","Rbbp8nl","Rbbp9","Rbck1","Rbfa","Rbfox1","Rbfox2","Rbfox3","Rbks","Rbl1","Rbl2","Rbm10","Rbm11","Rbm12","Rbm12b","Rbm14","Rbm15","Rbm15b","Rbm17","Rbm18","Rbm19","Rbm20","Rbm22","Rbm24","Rbm25","Rbm25l1","Rbm26","Rbm27","Rbm28","Rbm3","Rbm31y","Rbm33","Rbm34","Rbm38","Rbm39","Rbm4","Rbm41","Rbm42","Rbm43","Rbm44","Rbm45","Rbm46","Rbm47","Rbm48","Rbm4b","Rbm5","Rbm6","Rbm7","Rbm8a","Rbms1","Rbms2","Rbms3","Rbmx","Rbmx2","Rbmxl1","Rbmxl1b","Rbmxl2","Rbmy1j","Rbp1","Rbp2","Rbp3","Rbp4","Rbp7","Rbpj","Rbpjl","Rbpjl2","Rbpms","Rbpms2","Rbsn","Rbx1","Rc3h1","Rc3h2","Rcan1","Rcan2","Rcan3","Rcbtb1","Rcbtb2","Rcc1","Rcc1l","Rcc2","Rccd1","Rce1","Rchy1","Rcl1","Rcn1","Rcn2","Rcn3","Rcor1","Rcor2","Rcor2l1","Rcor3","Rcrg1-ps1","Rcrg1-ps10","Rcrg1-ps11","Rcrg1-ps12","Rcrg1-ps13","Rcrg1-ps14","Rcrg1-ps15","Rcrg1-ps16","Rcrg1-ps17","Rcrg1-ps18","Rcrg1-ps19","Rcrg1-ps2","Rcrg1-ps20","Rcrg1-ps21","Rcrg1-ps22","Rcrg1-ps23","Rcrg1-ps24","Rcrg1-ps25","Rcrg1-ps26","Rcrg1-ps27","Rcrg1-ps28","Rcrg1-ps29","Rcrg1-ps3","Rcrg1-ps30","Rcrg1-ps31","Rcrg1-ps32","Rcrg1-ps33","Rcrg1-ps34","Rcrg1-ps35","Rcrg1-ps36","Rcrg1-ps37","Rcrg1-ps38","Rcrg1-ps39","Rcrg1-ps4","Rcrg1-ps40","Rcrg1-ps5","Rcrg1-ps6","Rcrg1-ps7","Rcrg1-ps8","Rcrg1-ps9","Rcrg2-ps1","Rcrg2-ps2","Rcrg2-ps3","Rcrg2-ps4","Rcrg2-ps5","Rcrg2-ps6","Rcsd1","Rcvrn","Rd3","Rd3l","Rdh10","Rdh11","Rdh12","Rdh13","Rdh14","Rdh16","Rdh5","Rdh7","Rdh8","Rdm1","Rdx","Rec114","Rec8","Reck","Recql","Recql4","Recql5","Reep1","Reep2","Reep3","Reep4","Reep5","Reep6","Reg1a","Reg3a","Reg3b","Reg3g","Reg4","Rel","Rela","Relb","Rell1","Rell2","Reln","Relt","Rem1","Rem2","Ren","Renbp","Rep15","Repin1","Reps1","Reps2","Rer1","Rere","Rerg","Rergl","Resp18","Rest","Ret","Retn","Retnla","Retnlb","Retnlg","Retreg1","Retreg2","Retreg3","Retsat","Rev1","Rev3l","Rex1bd","Rex2","Rexo1","Rexo1l1-ps1","Rexo2","Rexo4","Rexo5","Rfc1","Rfc2","Rfc3","Rfc4","Rfc5","Rfesd","Rffl","Rfk","Rflna","Rflnb","Rfng","Rfpl4a","Rfpl4b","Rft1","Rftn1","Rftn2","Rfwd2","Rfwd3","Rfx1","Rfx2","Rfx3","Rfx4","Rfx5","Rfx6","Rfx7","Rfx8","Rfxank","Rfxap","Rfxapl1","Rgcc","RGD1302996","RGD1303003","RGD1304554","RGD1304567","RGD1304587","RGD1304622","RGD1304624","RGD1304694","RGD1304728","RGD1304745","RGD1304770","RGD1304810","RGD1304870","RGD1304884","RGD1304963","RGD1305014","RGD1305089","RGD1305110","RGD1305178","RGD1305184","RGD1305207","RGD1305298","RGD1305347","RGD1305350","RGD1305455","RGD1305464","RGD1305645","RGD1305704","RGD1305713","RGD1305733","RGD1305807","RGD1305938","RGD1306063","RGD1306072","RGD1306148","RGD1306186","RGD1306195","RGD1306227","RGD1306233","RGD1306271","RGD1306282","RGD1306441","RGD1306474","RGD1306484","RGD1306502","RGD1306519","RGD1306556","RGD1306704","RGD1306746","RGD1306750","RGD1306782","RGD1306941","RGD1306954","RGD1306995","RGD1307100","RGD1307182","RGD1307235","RGD1307443","RGD1307461","RGD1307537","RGD1307554","RGD1307595","RGD1307603","RGD1307621","RGD1307752","RGD1307782","RGD1307916","RGD1307929","RGD1307947","RGD1308005","RGD1308065","RGD1308106","RGD1308117","RGD1308134","RGD1308143","RGD1308147","RGD1308195","RGD1308428","RGD1308430","RGD1308544","RGD1308564","RGD1308601","RGD1308706","RGD1308742","RGD1308750","RGD1308751","RGD1308775","RGD1308878","RGD1309028","RGD1309036","RGD1309049","RGD1309079","RGD1309104","RGD1309106","RGD1309110","RGD1309139","RGD1309170","RGD1309291","RGD1309310","RGD1309350","RGD1309362","RGD1309489","RGD1309534","RGD1309540","RGD1309594","RGD1309621","RGD1309651","RGD1309730","RGD1309748","RGD1309779","RGD1309808","RGD1309870","RGD1309998","RGD1310081","RGD1310110","RGD1310127","RGD1310166","RGD1310209","RGD1310212","RGD1310352","RGD1310429","RGD1310495","RGD1310507","RGD1310553","RGD1310587","RGD1310602","RGD1310717","RGD1310819","RGD1310852","RGD1310935","RGD1310951","RGD1311084","RGD1311164","RGD1311251","RGD1311300","RGD1311318","RGD1311343","RGD1311345","RGD1311447","RGD1311517","RGD1311575","RGD1311595","RGD1311703","RGD1311739","RGD1311744","RGD1311745","RGD1311847","RGD1311892","RGD1311899","RGD1311933","RGD1311946","RGD1312005","RGD1359108","RGD1359127","RGD1359158","RGD1359290","RGD1359334","RGD1359449","RGD1359508","RGD1359634","RGD1559424","RGD1559441","RGD1559458","RGD1559459","RGD1559460","RGD1559461","RGD1559464","RGD1559482","RGD1559499","RGD1559508","RGD1559512","RGD1559513","RGD1559516","RGD1559519","RGD1559532","RGD1559534","RGD1559536","RGD1559545","RGD1559573","RGD1559574","RGD1559575","RGD1559578","RGD1559579","RGD1559588","RGD1559600","RGD1559602","RGD1559607","RGD1559622","RGD1559629","RGD1559639","RGD1559644","RGD1559647","RGD1559654","RGD1559662","RGD1559667","RGD1559669","RGD1559677","RGD1559679","RGD1559683","RGD1559707","RGD1559708","RGD1559710","RGD1559714","RGD1559724","RGD1559726","RGD1559731","RGD1559743","RGD1559747","RGD1559752","RGD1559769","RGD1559772","RGD1559781","RGD1559786","RGD1559795","RGD1559804","RGD1559806","RGD1559808","RGD1559810","RGD1559821","RGD1559833","RGD1559839","RGD1559854","RGD1559859","RGD1559877","RGD1559887","RGD1559890","RGD1559891","RGD1559892","RGD1559896","RGD1559902","RGD1559903","RGD1559908","RGD1559909","RGD1559912","RGD1559916","RGD1559921","RGD1559924","RGD1559935","RGD1559938","RGD1559940","RGD1559948","RGD1559950","RGD1559951","RGD1559955","RGD1559960","RGD1559962","RGD1559965","RGD1559967","RGD1559970","RGD1559972","RGD1559982","RGD1559995","RGD1559999","RGD1560010","RGD1560015","RGD1560017","RGD1560028","RGD1560034","RGD1560065","RGD1560069","RGD1560073","RGD1560076","RGD1560088","RGD1560096","RGD1560099","RGD1560105","RGD1560108","RGD1560109","RGD1560110","RGD1560112","RGD1560119","RGD1560124","RGD1560146","RGD1560162","RGD1560163","RGD1560171","RGD1560180","RGD1560186","RGD1560203","RGD1560207","RGD1560208","RGD1560212","RGD1560225","RGD1560227","RGD1560231","RGD1560234","RGD1560242","RGD1560262","RGD1560263","RGD1560281","RGD1560288","RGD1560289","RGD1560291","RGD1560302","RGD1560303","RGD1560314","RGD1560324","RGD1560325","RGD1560330","RGD1560333","RGD1560337","RGD1560341","RGD1560348","RGD1560349","RGD1560350","RGD1560353","RGD1560357","RGD1560382","RGD1560394","RGD1560398","RGD1560402","RGD1560408","RGD1560412","RGD1560429","RGD1560431","RGD1560436","RGD1560444","RGD1560455","RGD1560462","RGD1560464","RGD1560474","RGD1560482","RGD1560484","RGD1560492","RGD1560510","RGD1560512","RGD1560513","RGD1560523","RGD1560527","RGD1560533","RGD1560539","RGD1560544","RGD1560554","RGD1560559","RGD1560568","RGD1560585","RGD1560590","RGD1560608","RGD1560617","RGD1560623","RGD1560633","RGD1560640","RGD1560648","RGD1560661","RGD1560683","RGD1560687","RGD1560689","RGD1560697","RGD1560700","RGD1560703","RGD1560718","RGD1560723","RGD1560729","RGD1560730","RGD1560738","RGD1560771","RGD1560775","RGD1560784","RGD1560789","RGD1560795","RGD1560797","RGD1560801","RGD1560806","RGD1560813","RGD1560815","RGD1560821","RGD1560824","RGD1560826","RGD1560831","RGD1560842","RGD1560844","RGD1560854","RGD1560857","RGD1560860","RGD1560872","RGD1560883","RGD1560892","RGD1560901","RGD1560917","RGD1560925","RGD1560927","RGD1560931","RGD1560936","RGD1560954","RGD1560958","RGD1560961","RGD1560975","RGD1560979","RGD1560983","RGD1560986","RGD1560987","RGD1561017","RGD1561024","RGD1561034","RGD1561050","RGD1561060","RGD1561079","RGD1561102","RGD1561106","RGD1561111","RGD1561113","RGD1561114","RGD1561118","RGD1561134","RGD1561137","RGD1561143","RGD1561145","RGD1561148","RGD1561149","RGD1561150","RGD1561151","RGD1561154","RGD1561157","RGD1561161","RGD1561185","RGD1561192","RGD1561195","RGD1561206","RGD1561212","RGD1561215","RGD1561219","RGD1561224","RGD1561226","RGD1561230","RGD1561231","RGD1561232","RGD1561236","RGD1561246","RGD1561251","RGD1561252","RGD1561276","RGD1561277","RGD1561286","RGD1561288","RGD1561305","RGD1561306","RGD1561310","RGD1561317","RGD1561318","RGD1561323","RGD1561327","RGD1561333","RGD1561341","RGD1561381","RGD1561382","RGD1561395","RGD1561410","RGD1561413","RGD1561415","RGD1561426","RGD1561430","RGD1561440","RGD1561442","RGD1561445","RGD1561463","RGD1561444","RGD1561453","RGD1561465","RGD1561497","RGD1561481","RGD1561508","RGD1561520","RGD1561517","RGD1561525","RGD1561551","RGD1561552","RGD1561558","RGD1561572","RGD1561557","RGD1561582","RGD1561560","RGD1561575","RGD1561585","RGD1561589","RGD1561590","RGD1561594","RGD1561627","RGD1561620","RGD1561618","RGD1561635","RGD1561636","RGD1561658","RGD1561661","RGD1561689","RGD1561648","RGD1561671","RGD1561662","RGD1561684","RGD1561694","RGD1561667","RGD1561709","RGD1561704","RGD1561722","RGD1561715","RGD1561699","RGD1561729","RGD1561736","RGD1561754","RGD1561738","RGD1561766","RGD1561755","RGD1561767","RGD1561730","RGD1561771","RGD1561777","RGD1561780","RGD1561787","RGD1561788","RGD1561803","RGD1561795","RGD1561808","RGD1561796","RGD1561812","RGD1561821","RGD1561827","RGD1561843","RGD1561847","RGD1561853","RGD1561860","RGD1561849","RGD1561871","RGD1561877","RGD1561870","RGD1561886","RGD1561890","RGD1561897","RGD1561908","RGD1561906","RGD1561911","RGD1561919","RGD1561916","RGD1561935","RGD1561944","RGD1561958","RGD1561986","RGD1561977","RGD1561987","RGD1562020","RGD1562011","RGD1562024","RGD1562033","RGD1562029","RGD1562035","RGD1561998","RGD1562036","RGD1562055","RGD1562039","RGD1562066","RGD1562067","RGD1562068","RGD1562073","RGD1562080","RGD1562088","RGD1562090","RGD1562104","RGD1562118","RGD1562134","RGD1562107","RGD1562114","RGD1562139","RGD1562136","RGD1562140","RGD1562143","RGD1562146","RGD1562152","RGD1562156","RGD1562171","RGD1562178","RGD1562187","RGD1562196","RGD1562218","RGD1562259","RGD1562229","RGD1562266","RGD1562265","RGD1562281","RGD1562272","RGD1562289","RGD1562299","RGD1562290","RGD1562306","RGD1562355","RGD1562310","RGD1562339","RGD1562319","RGD1562387","RGD1562381","RGD1562394","RGD1562378","RGD1562399","RGD1562400","RGD1562402","RGD1562392","RGD1562404","RGD1562415","RGD1562420","RGD1562431","RGD1562423","RGD1562451","RGD1562433","RGD1562461","RGD1562458","RGD1562460","RGD1562462","RGD1562465","RGD1562485","RGD1562494","RGD1562492","RGD1562507","RGD1562508","RGD1562517","RGD1562521","RGD1562515","RGD1562524","RGD1562525","RGD1562545","RGD1562542","RGD1562532","RGD1562558","RGD1562569","RGD1562577","RGD1562598","RGD1562603","RGD1562613","RGD1562614","RGD1562636","RGD1562608","RGD1562618","RGD1562625","RGD1562641","RGD1562638","RGD1562650","RGD1562652","RGD1562690","RGD1562667","RGD1562683","RGD1562700","RGD1562699","RGD1562660","RGD1562704","RGD1562725","RGD1562739","RGD1562753","RGD1562726","RGD1562747","RGD1562755","RGD1562771","RGD1562758","RGD1562774","RGD1562775","RGD1562781","RGD1562776","RGD1562794","RGD1562801","RGD1562811","RGD1562818","RGD1562819","RGD1562820","RGD1562833","RGD1562844","RGD1562835","RGD1562851","RGD1562847","RGD1562863","RGD1562871","RGD1562890","RGD1562885","RGD1562914","RGD1562917","RGD1562936","RGD1562937","RGD1562932","RGD1562960","RGD1562980","RGD1562992","RGD1563029","RGD1562987","RGD1563034","RGD1563036","RGD1563055","RGD1563049","RGD1563057","RGD1563056","RGD1563060","RGD1563066","RGD1563097","RGD1563072","RGD1563100","RGD1563099","RGD1563124","RGD1563104","RGD1563134","RGD1563135","RGD1563145","RGD1563136","RGD1563157","RGD1563150","RGD1563159","RGD1563200","RGD1563217","RGD1563222","RGD1563232","RGD1563242","RGD1563234","RGD1563231","RGD1563270","RGD1563285","RGD1563263","RGD1563294","RGD1563295","RGD1563300","RGD1563301","RGD1563302","RGD1563314","RGD1563322","RGD1563330","RGD1563307","RGD1563323","RGD1563340","RGD1563334","RGD1563346","RGD1563351","RGD1563352","RGD1563349","RGD1563356","RGD1563375","RGD1563365","RGD1563354","RGD1563378","RGD1563392","RGD1563410","RGD1563402","RGD1563412","RGD1563405","RGD1563400","RGD1563445","RGD1563441","RGD1563451","RGD1563459","RGD1563486","RGD1563483","RGD1563482","RGD1563489","RGD1563492","RGD1563496","RGD1563502","RGD1563503","RGD1563527","RGD1563535","RGD1563537","RGD1563545","RGD1563546","RGD1563551","RGD1563554","RGD1563563","RGD1563570","RGD1563581","RGD1563584","RGD1563562","RGD1563578","RGD1563591","RGD1563590","RGD1563601","RGD1563604","RGD1563606","RGD1563613","RGD1563636","RGD1563620","RGD1563656","RGD1563667","RGD1563668","RGD1563679","RGD1563709","RGD1563690","RGD1563705","RGD1563680","RGD1563692","RGD1563725","RGD1563716","RGD1563743","RGD1563738","RGD1563739","RGD1563714","RGD1563747","RGD1563749","RGD1563780","RGD1563757","RGD1563812","RGD1563835","RGD1563834","RGD1563818","RGD1563815","RGD1563847","RGD1563850","RGD1563861","RGD1563903","RGD1563894","RGD1563905","RGD1563931","RGD1563888","RGD1563917","RGD1563937","RGD1563947","RGD1563956","RGD1563943","RGD1563941","RGD1563958","RGD1563963","RGD1563962","RGD1563991","RGD1563978","RGD1564021","RGD1564026","RGD1563986","RGD1564031","RGD1564040","RGD1564053","RGD1564062","RGD1564095","RGD1564100","RGD1564086","RGD1564097","RGD1564125","RGD1564126","RGD1564131","RGD1564133","RGD1564129","RGD1564138","RGD1564159","RGD1564148","RGD1564149","RGD1564162","RGD1564166","RGD1564172","RGD1564167","RGD1564177","RGD1564179","RGD1564193","RGD1564171","RGD1564225","RGD1564236","RGD1564240","RGD1564243","RGD1564277","RGD1564268","RGD1564275","RGD1564247","RGD1564278","RGD1564284","RGD1564292","RGD1564301","RGD1564318","RGD1564306","RGD1564308","RGD1564313","RGD1564320","RGD1564319","RGD1564343","RGD1564325","RGD1564324","RGD1564366","RGD1564347","RGD1564378","RGD1564382","RGD1564386","RGD1564390","RGD1564380","RGD1564392","RGD1564405","RGD1564416","RGD1564400","RGD1564409","RGD1564433","RGD1564425","RGD1564447","RGD1564477","RGD1564463","RGD1564492","RGD1564482","RGD1564480","RGD1564512","RGD1564513","RGD1564515","RGD1564536","RGD1564534","RGD1564571","RGD1564574","RGD1564548","RGD1564541","RGD1564578","RGD1564580","RGD1564581","RGD1564583","RGD1564585","RGD1564587","RGD1564597","RGD1564606","RGD1564599","RGD1564613","RGD1564627","RGD1564617","RGD1564614","RGD1564637","RGD1564645","RGD1564649","RGD1564651","RGD1564650","RGD1564657","RGD1564664","RGD1564665","RGD1564698","RGD1564696","RGD1564699","RGD1564702","RGD1564712","RGD1564730","RGD1564748","RGD1564744","RGD1564781","RGD1564764","RGD1564783","RGD1564770","RGD1564786","RGD1564795","RGD1564798","RGD1564800","RGD1564814","RGD1564801","RGD1564807","RGD1564804","RGD1564836","RGD1564827","RGD1564839","RGD1564845","RGD1564849","RGD1564855","RGD1564854","RGD1564865","RGD1564866","RGD1564883","RGD1564885","RGD1564915","RGD1564897","RGD1564899","RGD1564887","RGD1564933","RGD1564920","RGD1564937","RGD1564941","RGD1564956","RGD1564963","RGD1564958","RGD1564972","RGD1564955","RGD1564974","RGD1564981","RGD1564999","RGD1565032","RGD1565010","RGD1565017","RGD1565046","RGD1565047","RGD1565033","RGD1565048","RGD1565054","RGD1565056","RGD1565058","RGD1565063","RGD1565057","RGD1565066","RGD1565059","RGD1565083","RGD1565086","RGD1565071","RGD1565088","RGD1565102","RGD1565107","RGD1565117","RGD1565119","RGD1565131","RGD1565129","RGD1565143","RGD1565158","RGD1565170","RGD1565166","RGD1565183","RGD1565212","RGD1565245","RGD1565258","RGD1565260","RGD1565272","RGD1565222","RGD1565286","RGD1565288","RGD1565291","RGD1565297","RGD1565299","RGD1565301","RGD1565317","RGD1565329","RGD1565331","RGD1565323","RGD1565332","RGD1565338","RGD1565346","RGD1565356","RGD1565361","RGD1565365","RGD1565355","RGD1565367","RGD1565369","RGD1565395","RGD1565372","RGD1565400","RGD1565403","RGD1565410","RGD1565413","RGD1565422","RGD1565415","RGD1565435","RGD1565429","RGD1565438","RGD1565430","RGD1565478","RGD1565459","RGD1565472","RGD1565462","RGD1565495","RGD1565498","RGD1565533","RGD1565534","RGD1565541","RGD1565548","RGD1565560","RGD1565565","RGD1565566","RGD1565569","RGD1565573","RGD1565598","RGD1565590","RGD1565588","RGD1565600","RGD1565607","RGD1565611","RGD1565627","RGD1565634","RGD1565617","RGD1565622","RGD1565648","RGD1565616","RGD1565641","RGD1565651","RGD1565653","RGD1565657","RGD1565655","RGD1565661","RGD1565660","RGD1565664","RGD1565665","RGD1565679","RGD1565689","RGD1565685","RGD1565732","RGD1565693","RGD1565695","RGD1565725","RGD1565735","RGD1565752","RGD1565762","RGD1565753","RGD1565767","RGD1565779","RGD1565766","RGD1565784","RGD1565785","RGD1565798","RGD1565806","RGD1565822","RGD1565829","RGD1565844","RGD1565862","RGD1565894","RGD1565866","RGD1565900","RGD1565902","RGD1565912","RGD1565904","RGD1565939","RGD1565945","RGD1565956","RGD1565965","RGD1565959","RGD1565977","RGD1565987","RGD1565988","RGD1565989","RGD1565996","RGD1566007","RGD1566033","RGD1566006","RGD1566008","RGD1566035","RGD1566037","RGD1566048","RGD1566059","RGD1566060","RGD1566061","RGD1566067","RGD1566078","RGD1566093","RGD1566085","RGD1566100","RGD1566099","RGD1566129","RGD1566135","RGD1566136","RGD1566134","RGD1566138","RGD1566137","RGD1566184","RGD1566189","RGD1566159","RGD1566197","RGD1566198","RGD1566212","RGD1566217","RGD1566225","RGD1566227","RGD1566237","RGD1566226","RGD1566241","RGD1566244","RGD1566247","RGD1566248","RGD1566257","RGD1566258","RGD1566251","RGD1566262","RGD1566271","RGD1566273","RGD1566265","RGD1566284","RGD1566289","RGD1566292","RGD1566300","RGD1566324","RGD1566303","RGD1566331","RGD1566325","RGD1566337","RGD1566344","RGD1566347","RGD1566353","RGD1566354","RGD1566355","RGD1566368","RGD1566359","RGD1566369","RGD1566373","RGD1566383","RGD1566387","RGD1566386","RGD1566409","RGD1566401","RGD1584023","RGD1597339","RGD2301395","RGD2320734","RGD1624210","RGD9310068","RGD621098","RGD735029","RGD735065","Rgl1","Rgl2","Rgmb","Rgl3","Rgma","Rgp1","Rgr","Rgs1","Rgn","Rgs11","Rgs13","Rgs10","Rgs16","Rgs12","Rgs14","Rgs17","Rgs18","Rgs19","Rgs20","Rgs2","Rgs21","Rgs22","Rgs3","Rgs5","Rgs4","Rgs6","Rgs7bp","Rgs8","Rgs7","Rgs9bp","Rgs9","Rgsl2h","Rgsl1","Rhag","Rhbdd1","Rhbdd2","Rhbdf1","Rhbdd3","Rhbdf2","Rhbdl1","Rhbdl2","Rhbdl3","Rhbg","Rhcg","Rhd","Rhebl1","Rheb","Rhno1","Rho","Rhob","Rhobtb1","Rhobtb2","Rhobtb3","Rhod","Rhoc","Rhof","Rhoh","Rhog","Rhoa","Rhoq","Rhoj","Rhot2","Rhou","Rhot1","Rhox11","Rhox12","Rhov","Rhox13","Rhox2","Rhox3","Rhox4g","Rhox5","Rhox7","Rhox8","Rhox9","Rhoxf10","Rhpn1","Rhpn2","Ribc1","Ribc2","Ric1","Ric3","Ric8b","Ric8a","Rictor","Rida","Riiad1","Rif1","Rilp","Rilpl1","Rilpl2","Rimbp2","Rimbp3","Rimkla","Rimklb","Rims3","Rims4","Rin1","Rims1","Rin3","Rims2","Ring1","Rin2","Rinl","Rint1","Riok1","Riok2","Riox1","Riok3","Riox2","Ripk2","Ripk3","Ripk1","Ripk4","Ripor3","Ripor2","Ripply1","Ripor1","Ripply2","Ripply3","Rit1","Rita1","Rit2","Rlbp1","Rlf","Rlim","Rln1","Rln3","Rmdn1","Rmdn2","Rmi2","Rmdn3","Rmi1","Rmrp","Rmnd1","Rmnd5a","Rmnd5b","Rn18s","Rn28s","Rmt1","Rn5-8s","Rn7sl1","Rn5s","Rnase-ps","Rnase1","Rnase11","Rnase13","Rnase10","Rnase12","Rnase17","Rnase1l1","Rnase1l2","Rnase2","Rnase9","Rnase3","Rnase6","Rnaseh1","Rnase4","Rnaseh2b","Rnaseh2a","Rnaseh2c","Rnasek","Rnaset2","Rnd1","Rnasel","Rnd2","Rnf10","Rnd3","Rnf11","Rnf103","Rnf113a","Rnf112","Rnf111","Rnf113a1","Rnf113a2","Rnf114","Rnf11l2","Rnf115","Rnf11l1","Rnf122","Rnf121","Rnf125","Rnf123","Rnf126","Rnf130","Rnf13","Rnf128","Rnf133","Rnf135","Rnf138","Rnf139","rnf141","Rnf144a","Rnf14","Rnf144b","Rnf145","Rnf146","Rnf148","Rnf149","Rnf150","Rnf151","Rnf157","Rnf152","Rnf165","Rnf166","Rnf167","Rnf169","Rnf168","Rnf17","Rnf170","Rnf180","Rnf181","Rnf182","Rnf186","Rnf185","Rnf183","Rnf187","Rnf19b","Rnf19a","Rnf20","Rnf2","Rnf207","Rnf208","Rnf212","Rnf213","Rnf215","Rnf214","Rnf216","Rnf217","Rnf219","Rnf220","Rnf223","Rnf222","Rnf225","Rnf224","Rnf24","Rnf26","Rnf25","Rnf32","Rnf31","Rnf34","Rnf39","Rnf38","Rnf4","Rnf40","Rnf41","Rnf43","Rnf5","Rnf44","Rnf6","Rnf7","Rnf8","Rnft2","Rnft1","Rngtt","Rnmt","Rnh1","Rnls","Rnpc3","Rnpepl1","Rnps1","Rnr3_mapped","Rnpep","Robld3-ps1","Robo3","Robo4","Robo2","Robo1","Rogdi","Rom1","Rock2","Romo1","Rock1","Ropn1","Ropn1l","Ror1","Ror2","Rora","Rorc","Rorb","Rp1l1","Rp1","Rp2","Ros1","Rp9","Rpa1","Rpa3","Rpain","Rpap1","Rpa2","Rpap2","Rpap3","Rpe","Rpf2","Rpgrip1","Rpf1","Rpe65","Rpgr","Rpgrip1l","Rph3a","Rph3al","Rpia","Rpl10l","Rpl10a","Rpl10","Rpl11","Rpl12-ps1","Rpl12","Rpl13-ps1","Rpl13","Rpl17l1","Rpl13a","Rpl17-ps1","Rpl14","Rpl15","Rpl17","Rpl18","Rpl18a","Rpl21-ps2","Rpl21-ps1","Rpl21-ps3","Rpl19","Rpl22-ps","Rpl21","Rpl22","Rpl22l1","Rpl22l2","Rpl24","Rpl23","Rpl23a","Rpl26-ps1","Rpl26-ps2","Rpl26-ps3","Rpl26","Rpl27-l1","Rpl27-ps1","Rpl27","Rpl27a","Rpl29-ps1","Rpl29-ps2","Rpl28","Rpl29","Rpl29-ps3","Rpl30l1","Rpl31l1","Rpl31l2","Rpl3","Rpl31l3","Rpl31","Rpl30","Rpl31l4","Rpl32-ps1","Rpl32-ps2","Rpl32-ps3","Rpl32-ps4","Rpl34-ps1","Rpl34l1","Rpl32","Rpl34","Rpl35","Rpl36a-ps1","Rpl35al1","Rpl36","Rpl36a-ps2","Rpl36a-ps3","Rpl36a","Rpl35a","Rpl36a-ps4","Rpl37-ps1","Rpl37a-ps2","Rpl37a-ps3","Rpl36al","Rpl37a","Rpl37a-ps1","Rpl37","Rpl38","Rpl38-ps1","Rpl38-ps2","Rpl38-ps3","Rpl39-ps","Rpl39l","Rpl3l","Rpl39","Rpl41-ps1","Rpl41","Rpl5l1","Rpl4","Rpl6-ps1","Rpl5","Rpl6","Rpl7l1","Rpl7","Rpl7a","Rpl8","Rplp0l1","Rpl9","Rplp2","Rpn1","Rplp1","Rpn2","Rpp14","Rplp0","Rpp21","Rpp25","Rpp30","Rpp25l","Rpp38","Rpp40","Rprd1a","Rprd2","Rprd1b","Rprm","Rps10l1","Rprml","Rps12l2","Rps12l3","Rps10","Rps11","Rps12","Rps13","Rps15-ps1","Rps15-ps2","Rps15a-ps1","Rps15","Rps15al1","Rps14","Rps15al2","Rps15a","Rps15al4","Rps17l","Rps17","Rps16","Rps18l1","Rps18","Rps19bp1","Rps19","Rps19l1","Rps2-ps1","Rps2-ps2","Rps2-ps3","Rps2-ps4","Rps2-ps5","Rps2-ps6","Rps2","Rps2-ps7","Rps21-ps1","Rps20","Rps25-ps2","Rps25-ps1","Rps21","Rps23","Rps25","Rps24","Rps26","Rps27a-ps10","Rps27a-ps11","Rps27a-ps1","Rps27a-ps13","Rps27a-ps14","Rps27","Rps27a","Rps27a-ps15","Rps27a-ps16","Rps27a-ps17","Rps27a-ps18","Rps27a-ps19","Rps27a-ps20","Rps27a-ps2","Rps27a-ps21","Rps27a-ps22","Rps27a-ps23","Rps27a-ps25","Rps27a-ps26","Rps27a-ps24","Rps27a-ps27","Rps27a-ps28","Rps27a-ps29","Rps27a-ps3","Rps27a-ps30","Rps27a-ps4","Rps27a-ps5","Rps27a-ps6","Rps27a-ps7","Rps27a-ps8","Rps27a-ps9","Rps27l","Rps28-ps1","Rps28","Rps4x-ps1","Rps3","Rps29","Rps4x-ps3","Rps4x-ps2","Rps3a","Rps4x","Rps4x-ps4","Rps4x-ps5","Rps4x-ps6","Rps4x-ps7","Rps4x-ps8","Rps4x-ps9","Rps4y2","Rps5","Rps6","Rps6ka2","Rps6ka1","Rps6ka4","Rps6ka3","Rps6ka6","Rps6ka5","Rps6kl1","Rps7","Rps6kb2","Rps6kc1","Rps8-ps1","Rps6kb1","Rps8","Rps9","Rptn","Rps9l1","Rpusd1","Rpusd2","Rptor","Rpusd3","Rpsa","Rpusd4","Rraga","Rrad","RragB","Rragc","Rragd","Rras2","Rras","Rrbp1","Rrh","Rrlt","Rreb1","Rrm1-ps1","Rrm1","Rrm2b","Rrn3","Rrm2","Rrnad1","Rrp12","Rrp1","Rrp15","Rrp36","Rrp1b","Rrp7a","Rrp8","Rrs1","RSA-14-44","Rrp9","Rs1","Rsad1","Rsad2","Rsbn1l","Rsbn1","Rsf1","Rsg1","Rsl1","Rsl1d1","Rsl1d1l1","Rsl24d1","Rsph1","Rsph10b","Rsph14","Rsph3","Rsph4a","Rsph6a","Rsph9","Rspo1","Rspo2","Rspo4","Rspo3","Rsrc1","Rspry1","Rsrc2","Rsrp1","Rsu1","RT1-A","RT1-B","RT1-A2","RT1-A3","RT1-A1","RT1-CE1","RT1-CE10","RT1-CE11","RT1-CE12","RT1-CE14","RT1-CE13","RT1-Ba","RT1-CE14-ps1","RT1-Bb","RT1-CE15","RT1-CE16","RT1-CE2","RT1-CE3","RT1-CE6","RT1-CE4","RT1-CE8-ps1","RT1-CE5","RT1-CE9-ps1","RT1-CE7","RT1-Cl","RT1-Db2","RT1-DOa","RT1-DMa","RT1-Da","RT1-DMb","RT1-DOb","RT1-Hb-ps1","RT1-M1-1-ps","RT1-L3","RT1-EC2","RT1-Ha","RT1-Db1","RT1-M1-3-ps","RT1-M1-2","RT1-M10-2-ps","RT1-M10-3-ps","RT1-M1-5","RT1-M1-4","RT1-M10-1","RT1-M10-4-ps","RT1-M2","RT1-M3-3-ps","RT1-M3-2-ps","RT1-M3-1","RT1-M7-ps","RT1-M6-2","RT1-M4","RT1-M6-1","RT1-M5","RT1-M8-ps","RT1-N1","RT1-O2-ps","RT1-O3-ps","RT1-P1-ps1","RT1-O1","RT1-N2","RT1-N3","RT1-P2-ps1","RT1-T24-2","RT1-S2","RT1-T18","RT1-T24-1","RT1-V1-ps1","RT1-T24-3","RT1-S3","RT1-T24-4","Rt1.a-4","RT1-V2-ps1","Rtbdn","Rtcb","Rtca","Rtf1","Rtel1","Rtfdc1","Rtkn2","Rtkn","Rtl1","Rtl3","Rtl5","Rtl8a","Rtl4","Rtl6","Rtl8b","Rtl9","Rtn2","Rtn1","Rtn3","Rtn4ip1","Rtn4rl1","Rtn4rl2","Rtn4r","Rtp1","Rtn4","Rtp2","Rtp3","Rtp4","Rtraf","Rttn","Rubcnl","Rubcn","Rufy1","Rufy2","Rufy4","Rundc1","Rufy3","Rundc3a","Rundc3b","Runx1t1","Runx1","Runx3","Runx2","Rup2","Rusc1","Rusc2","Ruvbl1","Ruvbl2","Rwdd1","Rwdd2a","Rwdd2b","Rwdd3","Rwdd4","Rxfp1","Rxfp2","Rxfp3","Rxfp4-ps1","Rxra","Rxrb","Rybp","Rxrg","Ryk","S100a10","S100a1","Ryr1","Ryr3","S100a11","S100a14","S100a13","S100a16","S100a3","Ryr2","S100a4","S100a5","S100a6","S100a7a","S100a7l2","S100a8","S100a9","S100pbp","S100vp","S100g","S100z","S100b","S1pr4","S1pr2","S1pr1","S1pr3","S1pr5","Sacm2l-ps1","Saal1","Sac3d1","Saa4","Sacm1l","Sacm2l-ps2","Sacm2l-ps5","Sacm2l-ps4","Sacm2l-ps3","Sacm2l-ps6","Sacm2l-ps7","Safb2","Sacs","Sae1","Safb","Sall1","Sag","Samd1","Sall3","Sall2","Samd10","Samd11","Samd12","Samd13","Samd14","Samd15","Samd3","Sall4","Samd5","Samd7","Samd4b","Samd4a","Samd8","Samd9","Samd9l","Samm50","Samhd1","Samt2","Samsn1","Samt4","Samt3","Sap130","Sap18","Sap25","Sap30","Sap30bp","Sap30l","Sapcd2","Sapcd1","Sar1b","Saraf","Sar1a","Sarm1","Sarnp","Sardh","Sars","Sars2","Sart1","Sash1","Sart3","Sash3","Sass6","Sat1","Sat2","Satb1","Satl1","Satb2","Sav1","Saxo2","Saysd1","Saxo1","Saxol1","Sbds","Sbk1","Sbk2","Sbk3","Sbf1","Sbf2","Sbno2","Sbno1","Sbsn","Sbp","Sbspon","Sc5d","Scaf1","Scaf11","Scaf4","Scaf8","Scai","Scamp1","Scamp2","Scamp4","Scamp3","Scand1","Scand3-ps1","Scamp5","Scand3-ps2","Scand3-ps3","Scand3-ps4","Scara3","Scaper","Scap","Scara5","Scarf1","Scarb2","Scarf2","Scarna15","Scarb1","Scarna3","Scart1","Sccpdh","Scd","Scd2","Scd4","Scel","Scfd2","Scfd1","Scg3","Scg2","Scg5","Scgb1b30","Scgb1b24","Scgb1c1","Scgb1d2","Scgb1a1","Scgb1d4","Scgb2a1","Scgb2b2","Scgb2a2","Scgb2b24","Scgb3a1","Scgb3a2","Scgn","Scimp","Schip1","Scin","Scml1","Scmh1","Scly","Sclt1","Scml2","Scml4","Scn11a","Scn2b","Scn10a","Scn1b","Scn1a","Scn2a","Scn3b","Scn3a","Scn4b","Scn7a","Scn4a","Scn8a","Scnm1","Scn5a","Scn9a","Sco1","Scoc","Scnn1a","Scp2d1","Scnn1b","Scnn1g","Scpep1","Scp2","Scrg1","Scrn1","Scrn2","Scrt1","Scrn3","Scrt2","Scrib","Sct","Sctr","Scx","Scube2","Scube1","Scube3","Scyl1","Scyl2","Scyl3","Sdad1","Sdc2","Sdc3","Sdc1","Sdccag1-ps1","Sdcbp2","Sdc4","Sdcbp","Sdccag3","Sde2","Sdf2","Sdccag8","Sdf2l1","Sdhaf1","Sdf4","Sdha","Sdhaf3","Sdhaf2","Sdhaf4","Sdhc","Sdhb","Sdhd","Sdk1","Sdr16c6","Sdr16c5","Sdk2","Sdr39u1","Sdr42e1","Sdr42e2","Sdr9c7","Sdsl","Sec1","Sebox","Sds","Sec11a","Sec11c","Sec13","Sec14l1","Sec14l5","Sec14l4","Sec14l3","Sec14l2","Sec16a","Sec16b","Sec22a","Sec22c","Sec22b","Sec23a","Sec23b","Sec23ip","Sec24a","Sec24b","Sec24c","Sec61-ps","Sec24d","Sec31b","Sec31a","Sec61a1","Sec61a2","Sec61b","Sec61g","Sec61g-ps1","Sec61gl","Sec62","Secisbp2","Sec63","Sectm1a","Secisbp2l","Seh1l","Sectm1b","Sel1l","Sel1l2","Sel1l3","Selenoh","Selenbp1","Selenof","Sele","Selenoi","Selenok-ps1","Selenok","Selenok-ps2","Selenok-ps3","Selenok-ps4","Selenok-ps6","Selenok-ps5","Selenon","Selenom","Selenoo","Selenop","Selenos","Selenot-ps1","Selenot","Selenov","Selenow-ps1","Selenow","Selplg","Sem1","Sell","Sema3b","Sema3c","Sema3a","Sema3d","Sema3e","Sema3g","Sema3f","Sema4a","Sema4b","Sema4c","Sema4d","Selp","Sema4f","Sema6a","Sema4g","Sema5b","Sema5a","Sema6b","Sema6c","Sema6d","Sema7a","Senp17","Senp1","Senp18","Semg1","Senp2","Senp3","Senp5","Sephs2","Senp6","Senp7","Senp8","Sephs1","Sepsecs","1-Sep","10-Sep","12-Sep","14-Sep","11-Sep","3-Sep","2-Sep","4-Sep","5-Sep","6-Sep","7-Sep","Serac1","8-Sep","9-Sep","Serf1","Serbp1","Serf2","Sergef","Serhl2","Serinc2","Serinc1","Serinc3","Serinc4","Serinc5","Serp2","Serp1","Serpina11","Serpina12","Serpina10","Serpina16","Serpina1f","Serpina1","Serpina3c","Serpina3m","Serpina4","Serpina5","Serpina9","Serpina3n","Serpina6","Serpina7","Serpinb10","Serpinb11","Serpinb12","Serpinb13","Serpinb1b","Serpinb2","Serpinb1a","Serpinb3","Serpinb3a","Serpinb5","Serpinb6a","Serpinb6b","Serpinb6e","Serpinb8","Serpinb7","Serpinb9d","Serpinb9","Serpind1","Serpinc1","Serpine3","Serpine2","Serpinf2","Serping1","Serpinf1","Serpinh1","Sert1","Serpini1","Serpini2","Sertad1","Sertad2","Serpine1","Sertad3","Sertm1","Sertad4","Sesn1","Sesn3","Sesn2","Sestd1","Setbp1","Set","Setd1b","Setd1a","Setd3","Setd4","Setd2","Setd6","Setd5","Setd7","Setdb1","Setdb2","Setsip","Setmar","Setx","Sez6","Sez6l2","Sez6l","Sf1","Sf3a2","Sf3a1","Sf3a3","Sf3b4","Sf3b1","Sf3b2","Sf3b3","Sf3b5","Sf3b6","Sfi1","Sfmbt1","Sfmbt2","Sfr1","Sfn","Sfpq","Sfrp2","Sfrp1","Sfrp4","Sfrp5","Sft2d3","Sft2d2","Sfswap","Sft2d1","Sfta2","Sfxn2","Sfxn1","Sfxn3","Sfxn4","Sftpc","Sftpa1","Sftpb","Sftpd","Sfxn5","Sgca","Sgcb","Sgce","Sgcd","Sgcg","Sgcz","Sgf29","Sgk494","Sgk2","Sgk3","Sgip1","Sgo1","Sgms2","Sgk1","Sgms1","Sgo2","Sgpp1","Sgsh","Sgpl1","Sgpp2","Sgsm1","Sgsm2","Sgsm3","Sgta","Sgtb","Sh2b3","Sh2b1","Sh2b2","Sh2d1b2","Sh2d1a","Sh2d1b","Sh2d3a","Sh2d2a","Sh2d4b","Sh2d6","Sh2d3c","Sh2d4a","Sh2d5","Sh2d7","Sh3bgr","Sh3bgrl","Sh3bgrl2","Sh3bgrl3","Sh3bp2","Sh3bp1","Sh3bp4","Sh3bp5","Sh3bp5l","Sh3d19","Sh3gl1","Sh3d21","Sh3gl2","Sh3gl3","Sh3glb1","Sh3glb2","Sh3pxd2a","Sh3kbp1","Sh3pxd2b","Sh3rf1","Sh3rf2","Sh3rf3","Sh3tc1","Sh3tc2","Sh3yl1","Shb","Sharpin","Shank1","Shank3","Shbg","Shc2","Shank2","Shc1","Shc3","Shc4","Shcbp1","Shcbp1l","She","Shf","Shd","Shisa2","Shisa6","Shisa3","Shisa7","Shisa4","Shisa8","Shisa5","Shisa9","Shisal2b","Shisal2a","Shisal1","Shkbp1","Shh","Shmt1","Shmt2","Shoc2","Shpk","Shox2","Shq1","Shroom1","Shprh","Shroom2","Shroom3","Shroom4","Shtn1","Siah2","Siae","Si","Siah1","Siah3","Sidt1","Sidt2","Sigirr","Siglec15","Siglec10","Siglec8","Siglec1","Siglech","Siglec5","Siglecl1","Sik2","Sike1","Sik3","Sigmar1","Sik1","Sil1","Sim2","Sim1","Simc1","Sinhcaf","Sipa1l1","Sipa1","Sin3b","Sipa1l2","Sin3a","Sipa1l3","Sirpb2","Sirpb2l1","Sirpd","Sirpa","Sirt2","Sirt4","Sirt3","Sirt5","Sirt7","Sit1","Sirt6","Siva1","Six2","Six1","Six4","Six3","Six5","Six6","Six6os1","Ska1","Ska2","Sirt1","Ska3","Skap2","Skida1","Skap1","Ski","Skil","Skint10","Skint1","Skint4","Skint8","Skiv2l","Skor1","Skiv2l2","Skor2","Sla","Skp1","Skp2","Sla2","Slain1","Slain2","Slamf1","Slamf6","Slamf7","Slamf8","Slamf9","Slbp","Slc10a3","Slc10a1","Slc10a2","Slc10a4","Slc10a5","Slc10a6","Slc10a7","Slc11a1","Slc12a2","Slc12a1","Slc12a4","Slc12a3","Slc12a6","Slc11a2","Slc12a5","Slc12a9","Slc12a7","Slc13a1","Slc12a8","Slc13a4","Slc13a2","Slc13a3","Slc13a5","Slc14a1","Slc15a3","Slc15a4","Slc15a2","Slc15a5","Slc15a1","Slc14a2","Slc16a1","Slc16a14","Slc16a11","Slc16a12","Slc16a10","Slc16a13","Slc16a2","Slc16a3","Slc16a4","Slc16a9","Slc16a5","Slc16a6","Slc16a8","Slc17a1","Slc16a7","Slc17a2","Slc17a4","Slc17a3","Slc17a5","Slc17a9","Slc17a6","Slc17a8","Slc17a7","Slc18a1","Slc18a3","Slc18b1","Slc18a2","Slc19a3","Slc19a2","Slc19a1","Slc1a4","Slc1a1","Slc1a5","Slc1a6","Slc1a3","Slc1a2","Slc1a7","Slc20a1","Slc20a2","Slc22a14","Slc22a12","Slc21a4","Slc22a16","Slc22a15","Slc22a13","Slc22a1","Slc22a17","Slc22a20","Slc22a18","Slc22a22","Slc22a23","Slc22a24","Slc22a25","Slc22a2","Slc22a7-ps1","Slc22a3","Slc22a4","Slc22a7","Slc22a6","Slc22a5","Slc23a1","Slc22a8","Slc23a3","Slc24a1","Slc23a2","Slc24a3","Slc24a2","Slc24a5","Slc24a4","Slc25a1","Slc25a10","Slc25a11","Slc25a12","Slc25a13","Slc25a14","Slc25a15","Slc25a17","Slc25a16","Slc25a18","Slc25a2","Slc25a19","Slc25a20","Slc25a21","Slc25a22","Slc25a23","Slc25a24","Slc25a26","Slc25a25","Slc25a27","Slc25a28","Slc25a30","Slc25a29","Slc25a32","Slc25a3","Slc25a31","Slc25a33","Slc25a34","Slc25a35","Slc25a36l1","Slc25a36","Slc25a38","Slc25a37","Slc25a39","Slc25a4","Slc25a40","Slc25a41","Slc25a43","Slc25a42","Slc25a44","Slc25a45","Slc25a46","Slc25a48","Slc25a47","Slc25a5","Slc25a53","Slc25a52","Slc25a51","Slc25a54","Slc25a6","Slc26a10","Slc26a1","Slc26a11","Slc26a3","Slc26a2","Slc26a6","Slc26a4","Slc26a5","Slc26a7","Slc26a8","Slc26a9","Slc27a2","Slc27a3","Slc27a1","Slc27a4","Slc27a5","Slc27a6","Slc28a3","Slc28a1","Slc28a2","Slc29a1","Slc29a3","Slc29a4","Slc29a2","Slc2a10","Slc2a12","Slc2a1","Slc2a13","Slc2a5","Slc2a6","Slc2a3","Slc2a2","Slc2a7","Slc2a8","Slc2a9","Slc2a4","Slc30a1","Slc30a10","Slc30a2","Slc30a4","Slc30a3","Slc30a5","Slc30a7","Slc30a8","Slc30a6","Slc30a9","Slc31a2","Slc31a1","Slc33a1","Slc32a1","Slc35a1","Slc34a2","Slc34a3","Slc35a3","Slc35a2","Slc35a5","Slc35a4","Slc34a1","Slc35b1","Slc35b2","Slc35b3","Slc35b4","Slc35c1","Slc35d1","Slc35d2","Slc35c2","Slc35d3","Slc35e1","Slc35e3","Slc35e2b","Slc35e4","Slc35f1","Slc35f3","Slc35f2","Slc35f4","Slc35f5","Slc35f6","Slc35g1","Slc35g2","Slc35g3","Slc36a1","Slc36a2","Slc36a4","Slc36a3","Slc37a1","Slc37a2","Slc37a3","Slc37a4","Slc38a11","Slc38a10","Slc38a1","Slc38a2","Slc38a3","Slc38a5","Slc38a4","Slc38a7","Slc38a6","Slc38a8","Slc38a9","Slc39a1","Slc39a10","Slc39a12","Slc39a11","Slc39a2","Slc39a13","Slc39a14","Slc39a3","Slc39a4l","Slc39a4","Slc39a5","Slc39a6","Slc39a7","Slc39a8","Slc39a9","Slc3a1","Slc3a2","Slc41a1","Slc41a2","Slc41a3","Slc40a1","Slc43a1","Slc43a2","Slc43a3","Slc44a1","Slc44a3","Slc44a2","Slc45a1","Slc44a4","Slc44a5","Slc45a2","Slc45a3","Slc45a4","Slc46a2","Slc46a1","Slc47a2","Slc46a3","Slc48a1","Slc47a1","Slc4a1","Slc4a11","Slc4a1ap","Slc4a10","Slc4a5","Slc4a3","Slc4a2","Slc4a4","Slc50a1","Slc4a7","Slc4a9","Slc4a8","Slc51a","Slc51b","Slc52a2","Slc52a3","Slc5a12","Slc5a10","Slc5a11","Slc5a3","Slc5a1","Slc5a2","Slc5a4","Slc5a4b","Slc5a8","Slc5a6","Slc5a5","Slc5a9","Slc5a7","Slc6a11","Slc6a1","Slc6a12","Slc6a16","Slc6a14","Slc6a13","Slc6a15","Slc6a17","Slc6a18","Slc6a19","Slc6a2","Slc6a20","Slc6a5","Slc6a6","Slc6a7","Slc6a3","Slc6a8","Slc6a4","Slc6a9","Slc7a12","Slc7a10","Slc7a11","Slc7a13","Slc7a1","Slc7a14","Slc7a15","Slc7a4","Slc7a3","Slc7a6os","Slc7a6","Slc7a5","Slc7a2","Slc7a7","Slc7a8","Slc7a9","Slc8a2","Slc8b1","Slc8a3","Slc9a2","Slc9a1","Slc9a3r2","Slc8a1","Slc9a3r1","Slc9a4","Slc9a6","Slc9a3","Slc9a5","Slc9a7","Slc9a8","Slc9b1","Slc9b2","Slc9a9","Slc9c1","Slc9c2","Slco1a1","Slco1a2","Slco1a4","Slco1a6","Slco1b2","Slco1c1","Slco2a1","Slco2b1","Slco3a1","Slco4a1","Slco4c1","Slco6c1","Slco6b1","Slco5a1","Slco6d1","Slf1","Slfn1","Slf2","Slfn13","Slfn14","Slfn3","Slfn2","Slfn4","Slfn5","Slfnl1","Slirp","Slitrk1","Slit1","Slitrk3","Slitrk2","Slitrk4","Slit3","Slitrk5","Slit2","Slitrk6","Slk","Sln","Slpil2","Slpil3","Slpi","Slmap","Sltm","Slurp1","Slu7","Slx1b","Slx4","Slx4ip","Smad1","Smad2","Smad5","Smad3","Smad6","Smad9","Smad7","Smad4","Smagp","Smap1","Smap2","Smarca1","Smarca2","Smarcad1","Smarca5","Smarcal1","Smarcb1","Smarca4","Smarcc1","Smarcd1","Smarcc2","Smarce1l","Smarcd3","Smarcd2","Smc1b","Smarce1","Smc1a","Smc2","Smc3","Smc4","Smc6","Smc5","Smco1","Smco2","Smchd1","Smco3","Smcp","Smcr8","Smdt1","Smco4","Smg5","Smg1","Smim1","Smg6","Smgc","Smg8","Smg7","Smg9","Smim10","Smim10l1","Smim11","Smim12","Smim13","Smim14","Smim15","Smim17","Smim18","Smim19","Smim2","Smim20","Smim22","Smim23","Smim24","Smim26","Smim29","Smim3","Smim31","Smim34a","Smim4","Smim35","Smim5","Smim6","Smim7","Smkr1","Smlr1","Smim8","Smndc1","Smoc1","Smn1","Smok2a","Smoc2","Smo","Smox","Smpd1","Smpd4","Smpd2","Smpd3","Smpd5","Smpdl3a","Smpdl3b","Smptb","Smpx","Smr3a","Sms-ps1","Sms","Smr3b","Smtn","Smtnl1","Smtnl2","Smu1","Smug1","Smurf1","Smurf2","Smyd1","Smyd4","Smyd2","Smyd3","Smyd5","Snai1","Snai2","Snai3","Snap47","Snap23","Snap29","Snapc1","Snapc3","Snap91","Snapc2","Snapc4","Snapc5","Snap25","Snapin","Sncaip","Sncg","Sncb","Snd1","Snf8","Snhg11","Sned1","Snhg4","Snca","Snora19","Snip1","Snn","Snora58","Snph","Snrk","Snrnp200","Snrnp25","Snrnp40","Snrnp35","Snrnp27","Snrnp48","Snrpa","Snrnp70","Snrpc-ps1","Snrpa1","Snrpb2","Snrpc","Snrpb","Snrpd1","Snrpd2","Snrpd2l","Snrpel","Snrpel1","Snrpd3","Snrpep2","Snrpf","Snrpg-ps1","Snrpg","Snrpgl2","Snrpe","Snta1","Sntb1","Snrpn","Sntb2","Sntg1","Sntg2","Sntn","Snu13","Snupn","Snurf","Snw1","Snx1","Snx10","Snx11","Snx12","Snx13","Snx16","Snx15","Snx14","Snx17","Snx18","Snx19","Snx20","Snx2","Snx21","Snx22","Snx24","Snx25","Snx27","Snx3","Snx30","Snx29","Snx31","Snx33","Snx32","Snx4","Snx5","Snx7","Snx8","Snx9","Snx6","Soat1","Sobp","Soat2","Socs4","Socs2","Socs5","Socs1","Socs3","Socs6","Socs7","Soga1","Soga3","Sod3","Sohlh1","Sohlh2","Son","Sod2","Sod1","Sorbs1","Sorbs3","Sorbs2","Sorcs1","Sorcs2","Sorcs3","Sorl1","Sord","Sort1","Sostdc1","Sost","Sowaha","Sos2","Sos1","Sowahb","Sox1","Sowahc","Sowahd","Sox12","Sox11","Sox13","Sox14","Sox10","Sox15","Sox21","Sox17","Sox18","Sox3","Sox30","Sox4","Sox2","Sox7","Sox5","Sox8","Sox6","Sp100","Sp110","Sox9","Sp140","Sp1","Sp2","Sp4","Sp3","Sp5","Sp6","Sp8","Spaca1","Sp7","Sp9","Spa17","Spaca3","Spaca4","Spaca5","Spaca6","Spaca7","Spaca9","Spag1","Spag11a","Spag11b","Spag11bl","Spag17","Spag16","Spag4","Spag6","Spag7","Spag5","Spag6l","Spag8","Spam1","Sparcl1","Spag9","Spart","Sparc","Spast","Spata1","Spata13","Spata16","Spata17","Spata18","Spata19","Spata2","Spata20","Spata21","Spata22","Spata24","Spata25","Spata2L","Spata3","Spata31d1b","Spata31a5","Spata31d1d","Spata31d3","Spata31e1","Spata32","Spata33","Spata4","Spata45","Spata46","Spata5l1","Spata5","Spata6l","Spata6","Spata9","Spata7","Spatc1","Spatc1l","Spats1","Spats2","Spc24","Spats2l","Spc25","Spcs1","Spcs2","Spcs3","Spdef","Spdl1","Spdya","Spdye4","Specc1","Specc1l","Spef1","Spef2","Spem1","Speg","Spen","Spert","Spesp1","Spetex-2A","Spetex-2B","Spetex-2C","Spetex-2D","Spetex-2E","Spetex-2F","Spetex-2G","Spetex-2H","Spg11","Spg21","Spg7","Sphk2","Sphk1","Sphkap","Spi1","Spib","Spic","Spice1","Spidr","Spin1","Spin3","Spin2a","Spin4","Spink10","Spink1","Spink13","Spink14","Spink2","Spink1l","Spink4","Spink5","Spink6","Spink7","Spink8","Spink9","Spint1","Spint2","Spint4","Spint3","Spint5p","Spire1","Spire2","Spn","Spns1","Spns2","Spns3","Spocd1","Spo11","Spock1","Spock2","Spock3","Spon1","Spon2","Spop","Spopl","Spout1","Spp1","Spp2","Sppl2a","Sppl2b","Sppl2c","Sppl3","Spr","Spred1","Spred2","Spred3","Sprn","Sprr1a","Sprr1b","Sprr2d","Sprr3","Sprr4","Sprtn","Spry1","Spry2","Spry3","Spry4","Spryd4","Spryd3","Spryd7","Spsb1","Spsb2","Spsb3","Spsb4","Spt1","Spta1","Sptan1","Sptb","Sptbn1","Sptbn2","Sptbn5","Sptbn4","Sptlc1","Sptlc2","Sptssa","Sptlc3","Sptssb","Spty2d1","Spz1","Spx","Sqor","Sqle","Sra1","Sqstm1","Srarp","Srbd1","Srcap","Src","Srcin1","Srd5a2","Srd5a1","Srd5a3","Srebf1","Srebf2","Srek1","Srek1ip1","Srfbp1","Srf","Srgap1","Srgap2","Srgap3","Srgn","Sri","Srl","Srm","Srms","Srp14","Srp19","Srp54a","Srp68","Srp72-ps1","Srp72","Srp9","Srpk1","Srpk2","Srpk3","Srpra","Srprb","Srpx","Srpx2","Srrd","Srr","Srrm1","Srrm2","Srrm4","Srrm3","Srrm5","Srrt","Srsf10","Srsf1","Srsf12","Srsf11","Srsf3","Srsf2","Srsf3-ps1","Srsf4","Srsf5","Srsf6","Srsf7","Srsf8","Srsf9","Srxn1","Ss18","Sry","Ss18l1","Ss18l2","Ssbp1","Ssb","Ssbp2","Ssbp3","Ssbp4","Ssc4d","Ssc5d","Ssfa2","Ssh1","Ssh2","Ssmem1","Ssh3","Ssna1","Sspn","Ssr1","Sspo","Ssr2","Ssr3","Ssr4","Ssrp1","Sssca1","Sst","Sstr1","Sstr3","Sstr2","Sstr4","Sstr5","Ssty1","Ssu72","Ssuh2","Ssx1","Ssx2","Ssx2ip","St13","St14","St18","St3gal1","St3gal2","St3gal3","St3gal4","St3gal5","St3gal6","St5","St6gal1","St6gal2","St6galnac1","St6galnac2","St6galnac3","St6galnac4","St6galnac5","St6galnac6","St7l","ST7","St8sia2","St8sia1","St8sia3","St8sia4","St8sia6","St8sia5","Stab1","Stab2","Stac","Stac2","Stac3","Stag1","Stag2","Stag3","Stam","Stam2","Stambp","Stambpl1","Stap1","Stap2","Stard10","Stard13","Stard3","Stard3nl","Star","Stard4","Stard5","Stard6","Stard7","Stard8","Stard9","Stat2","Stat1","Stat4","Stat3","Stat5a","Stat5b","Stat6","Stath","Stau1","Stau2","Stbd1","Stc1","Stc2","Steap1","Steap2","Steap3","Steap4","Stfa2","Stfa2l1","Stfa2l2","Stfa3","Stfa3l1","Stil","Stim1","Stim2","Stip1","Stk10","Stk11ip","Stk11","Stk16","Stk17b","Stk19-ps","Stk19","Stk25","Stk24","Stk26","Stk3","Stk31","Stk32a","Stk32c","Stk32b","Stk35","Stk33","Stk36","Stk38","Stk38l","Stk39","Stk4","Stk40","Stkld1","Stmn1","Stmn2","Stmn3","Stmnd1","Stmn4","Stn1","Stom","Stoml1","Stoml2","Stoml3","Ston1","Ston2","Stox1","Stox2","Stpg1","Stpg3","Stpg2","Stpg4","Stra6","Stra8","Strada","Stradb","Strap","Strbp","Strc","Strip1","Strip2","Strn","Strn3","Strn4","Sts","Stt3a","Stt3b","Stum","Stub1","Stx11","Stx12","Stx16","Stx17","Stx19","Stx18","Stx1b","Stx1a","Stx2","Stx3","Stx4","Stx5","Stx6","Stx7","Stx8","Stxbp2","Stxbp1","Stxbp3","Stxbp4","Stxbp5","Stxbp5l","Stxbp6","Styk1","Styx","Styxl1","Styxl2","Sub1","Sucla2","Suclg1","Suclg2","Sucnr1","Suco","Suds3","Sufu","Sugct","Sugp1","Sugp2","Sugt1","Sulf1","Sulf2","Sult1a1","Sult1b1","Sult1c2a","Sult1c2","Sult1d1","Sult1c3","Sult1e1","Sult2a1","Sult2a2","Sult2a6","Sult2b1","Sult4a1","Sult5a1","Sult6b1","Sumf1","Sumf2","Sumo1","Sumo2","Sumo3","Sumo4","Sun1","Sun2","Sun3","Sun5","Suox","Supt16h","Supt3h","Supt20h","Supt4h1","Supt5h","Supt7l","Supt6h","Supv3l1","Surf1","Surf2","Surf4","Surf6","Susd1","Susd2","Susd3","Susd4","Susd5","Suv39h1","Susd6","Suv39h1l1","Suv39h2","Suz12","Sv2a","Sv2b","Sv2c","Sval1","Sval2","Svbp","Svep1","Svil","Svip","Svop","Svopl","Svs1","Svs3a","Svs3b","Svs4","Svs5","Svs6","Swap70","Swi5","Swsap1","Swt1","Syap1","Sybu","Syce1","Syce1l","Syce2","Syce3","Sycn","Sycp1","Sycp2","Sycp2l","Sycp3","Syde1","Syde2","Syf2","Sympk","Syk","Syn2","Syn1","Synb","Syn3","Sync","Syncrip","Syndig1","Syndig1l","Syne1","Syne2","Syne3","Syne4","Syngr1","Syngap1","Syngr2","Syngr3","Syngr4","Synj1","Synj2","Synj2bp","Synm","Synpo","Synpo2","Synpo2l","Synpr","Synrg","Sypl1","Syp","Sypl2","Sys1","Syt10","Syt11","Syt1","Syt12","Syt13","Syt14","Syt15","Syt16","Syt17","Syt2","Syt3","Syt5","Syt4","Syt6","Syt7","Syt8","Syt9","Sytl1","Sytl2","Sytl3","Sytl4","Sytl5","Syvn1","Szrd1","T","Szt2","T2","Taar1","Taar2","Taar3","Taar4","Taar5","Taar6","Taar7a","Taar7b","Taar7c","Taar7d","Taar7e","Taar7f-ps","Taar7g","Taar7h","Taar7i-ps","Taar8a","Taar8b","Taar8c","Taar9","Tab1","Tab2","Tab3","Tac3","Tac1","Tac4","Tacc1","Tacc2","Tacc3","Taco1","Tacr2","Tacr1","Tacr3","Tacstd2","Tada1","Tada2a","Tada2b","Tada3lb","Tada3","Taf1","Taf10","Taf11","Taf12","Taf13","Taf15","Taf1a","Taf1b","Taf1c","Taf1d","Taf2","Taf4","Taf3","Taf4b","Taf5","Taf5l","Taf6","Taf6l","Taf7","Taf7l","Taf7l-ps1","Taf8","Taf9","Taf9-ps","Tagap","Taf9b","Tagln","Tagln2","Tagln3","Tal2","Tal1","Taldo1","Tamm41","Tanc1","Tanc2","Tango2","Tango6","Tank","Taok1","Taok2","Taok3","Tap1","Tap2","Tapbpl","Tapbp","Tarbp1","Tapt1","Tarbp2","Tarm1","Tardbp","Tars","Tars2","Tarsl2","Tas1r1","Tas1r2","Tas1r3","Tas2r102","Tas2r103","Tas2r104","Tas2r105","Tas2r106","Tas2r107","Tas2r108","Tas2r109","Tas2r110","Tas2r113","Tas2r114","Tas2r116","Tas2r117","Tas2r118","Tas2r119","Tas2r120","Tas2r121","Tas2r123","Tas2r124","Tas2r125","Tas2r126","Tas2r129","Tas2r13","Tas2r130","Tas2r134","Tas2r136","Tas2r135","Tas2r137","Tas2r138","Tas2r139","Tas2r140","Tas2r143","Tas2r144","Tas2r145","Tas2r7l","Tasp1","Tatdn1","Tat","Tatdn2","Tatdn3","Tax1bp3","Tax1bp1","Tbata","Taz","Tbc1d1","Tbc1d10a","Tbc1d10b","Tbc1d10c","Tbc1d12","Tbc1d13","Tbc1d14","Tbc1d15","Tbc1d16","Tbc1d17","Tbc1d19","Tbc1d2","Tbc1d20","Tbc1d21","Tbc1d22a","Tbc1d22b","Tbc1d23","Tbc1d24","Tbc1d25","Tbc1d2b","Tbc1d30","Tbc1d31","Tbc1d32","Tbc1d4","Tbc1d5","Tbc1d7","Tbc1d8","Tbc1d8b","Tbc1d9","Tbca","Tbc1d9b","Tbcb","Tbcc","Tbccd1","Tbcd","Tbce","Tbcel","Tbck","Tbk1","Tbkbp1","Tbl1x","Tbl1xr1","Tbl2","Tbl3","Tbp","Tbpl1","Tbpl2","Tbr1","Tbrg1","Tbrg4","Tbx10","Tbx1","Tbx15","Tbx18","Tbx19","Tbx2","Tbx20","Tbx21","Tbx22","Tbx3","Tbx4","Tbx6","Tbx5","Tbxa2r","Tbxas1","Tc2n","Tcaf1","Tcaf2","Tcam1","Tcaim","Tcap","Tcea1","Tcea2","Tcea3","Tceal1","Tceal3","Tceal5","Tceal6","Tceal7","Tceal8","Tceal9","Tceanc","Tceanc2","Tcerg1","Tcerg1l","Tcf15","Tcf12","Tcf19","Tcf20","Tcf21","Tcf23","Tcf24","Tcf25","Tcf3","Tcf4","Tcf7","Tcf7l1","Tcf7l2","Tcfl5","Tchh","Tchhl1","Tchp","Tcirg1","Tcl1a","Tcn2","Tcof1","Tcp1-ps1","Tcp1","Tcp10b","Tcp11","Tcp11l1","Tcp11l2","Tcp11x2","Tcra-v22.1","Tcrb","Tcta","Tcte3","Tcte1","Tctex1d1","Tctex1d2","Tctex1d4","Tctn1","Tctn2","Tctn3","Tdg","Tdg-ps1","Tdgf1","Tdh","Tdo2","Tdp1","Tdp2","Tdpoz1","Tdrd12","Tdrd1","Tdrd15","Tdrd3","Tdrd6","Tdrd5","Tdrd7","Tdrd9","Tdrkh","Tdrp","Tead1","Tead2","Tead3","Tead4","Tec","Tecpr1","Tecpr2","Tecr","Tecrl","Tecta","Tectb","Tedc1","Teddm1","Tef","Tefm","Tekt1","Tek","Tekt2","Tekt3","Tekt4","Tekt5","Telo2","Ten1","Tenm1","Tenm2","Tenm3","Tenm4","Tep1","Tepp","Tepsin","Terb1","Terb2","Terc","Terf1","Terf2","Terf2ip","Tes","Tert","Tesb","Tesc","Tescl","Tesk1","Tesk2","Tesl","Tesmin","Testin","Tet1","Tet2","Tet3","Tex10","Tex101","Tex11","Tex12","Tex13a","Tex13b","Tex13c","Tex14","Tex15","Tex16-ps1","Tex19.1","Tex19.2","Tex2","Tex21","Tex22","Tex26","Tex261","Tex264","Tex28","Tex29","Tex30","Tex33","Tex35","Tex36","Tex37","Tex38","Tex43","Tex44","Tex45","Tex47","Tex49","Tex51","Tex52","Tex9","Tfam","Tf","Tfap2a","Tfap2b","Tfap2c","Tfap2d","Tfap2e","Tfap4","Tfb1m","Tfb2m","Tfcp2","Tfcp2l1","Tfdp1","Tfdp2","Tfe3","Tfeb","Tfec","Tff1","Tff2","Tff3","Tfg","Tfip11","Tfpi","Tfpi2","Tfpt","Tfr2","Tfrc","Tg","tGap1","Tgds","Tgfa","Tgfb1i1","Tgfb2","Tgfb1","Tgfb3","Tgfbi","Tgfbr1","Tgfbr2","Tgfbr3","Tgfbr3l","Tgfbrap1","Tgif1","Tgif2-ps1","Tgif2","Tgif2lx2","Tgm1","Tgm2","Tgm3","Tgm4","Tgm5","Tgm7","Tgm6","Tgs1","Tgoln2","Thada","Thap1","Th","Thap11","Thap12","Thap2","Thap3","Thap4","Thap6","Thap7","Thap8","Thbs1","Thbd","Thbs3","Thbs2","Theg","Thbs4","Thegl","Them4","Them5","Them6","Themis","Themis2","Thg1l","Thnsl1","Thoc1","Thnsl2","Thoc3","Thoc2","Thoc5","Thoc6","Thoc7","Thop1","Thpol1","Thpo","Thrap3","Thra","Thrb","Thrsp","Thsd1","Thsd4","Thsd7a","Thsd7b","Thtpa","Thumpd1","Thumpd2","Thumpd3","Thumpd3-as1","Thyn1","Thy1","Tia1","Tial1","Tiam1","Tiam2","Ticam1","Ticam2","Ticrr","Tie1","Tifa","Tifab","Tigar","Tigd2","Tigd3","Tigd4","Tigd5","Tigit","Timd2","Timd4","Timeless","Timm10","Timm13","Timm10b","Timm17al1","Timm17a","Timm17b","Timm21","Timm22","Timm23","Timm23b","Timm29","Timm44","Timm50","Timm8a1","Timm8a2","Timm8b","Timm9","Timmdc1","Timp1","Timp2","Timp4","Timp3","Tinag","Tinagl1","Tincr","Tinf2","Tiparp","Tipin-ps1","Tipin","Tipinl1","Tiprl","Tirap","Tjap1","Tjp2","Tjp1","Tjp3","Tk1","Tk2","Tkfc","Tkt","Tktl1","Tktl2","Tlcd1","Tlcd2","Tldc1","Tldc2","Tle1","Tle2","Tle3","Tle4","Tle6","Tlk1","Tlk2","Tll1","Tll2","Tln1","Tln2","Tlnrd1","Tlr1","Tlr10","Tlr11","Tlr12","Tlr13","Tlr3","Tlr2","Tlr4","Tlr5","Tlr6","Tlr7","Tlr8","Tlx1","Tlr9","Tlx2","Tlx3","Tm2d1","Tm2d3","Tm2d2","Tm4sf19","Tm4sf1","Tm4sf20","Tm4sf4","Tm4sf5","Tm6sf1","Tm6sf2","Tm7sf2","Tm7sf3","Tm9sf1","Tm9sf2","Tm9sf3","Tm9sf4","Tma16","Tma7","Tmbim1","Tmbim4","Tmbim6","Tmbim7","Tmc1","Tmc2","Tmc3","Tmc4","Tmc4b","Tmc5","Tmc6","Tmc7","Tmc8","Tmcc1","Tmcc2","Tmco1","Tmcc3","Tmco2","Tmco3","Tmco4","Tmco5a","Tmco5b","Tmco6","Tmed1","Tmed10","Tmed11","Tmed2","Tmed3","Tmed4","Tmed5","Tmed6","Tmed7","Tmed8","Tmed9","Tmeff1","Tmeff2","Tmem100","Tmem101","Tmem102","Tmem105","Tmem104","Tmem106a","Tmem106b","Tmem106c","Tmem107","Tmem108","Tmem109","Tmem11","Tmem110","Tmem114","Tmem115","Tmem116","Tmem117","Tmem119","Tmem120a","Tmem120b","Tmem121","Tmem121b","Tmem123","Tmem125","Tmem126a","Tmem126b","Tmem127","Tmem128","Tmem129","Tmem130","Tmem131","Tmem132a","Tmem132b","Tmem132c","Tmem132e","Tmem132d","Tmem134","Tmem135","Tmem136","Tmem138","Tmem139","Tmem140","Tmem141","Tmem144","Tmem143","Tmem145","Tmem147","Tmem14a","Tmem14c","Tmem150a","Tmem150b","Tmem150c","Tmem151a","Tmem151b","Tmem154","Tmem156","Tmem158","Tmem159","Tmem160","Tmem161a","Tmem161b","Tmem163","Tmem164","Tmem165","Tmem167a","Tmem167b","Tmem168","Tmem169","Tmem17","Tmem170a","Tmem170b","Tmem171","Tmem173","Tmem174","Tmem175","Tmem176a","Tmem176b","Tmem177","Tmem178a","Tmem178b","Tmem179","Tmem179b","Tmem18","Tmem181","Tmem182","Tmem183a","Tmem184a","Tmem184b","Tmem184c","Tmem185a","Tmem185b","Tmem186","Tmem189","Tmem19","Tmem190","Tmem191c","Tmem192","Tmem196","Tmem198","Tmem198b","Tmem199","Tmem2","Tmem200a","Tmem200b","Tmem200c","Tmem201","Tmem202","Tmem203","Tmem204","Tmem205","Tmem206","Tmem207","Tmem208","Tmem209","Tmem210","Tmem211","Tmem212","Tmem213","Tmem214","Tmem215","Tmem216","Tmem217","Tmem218","Tmem219","Tmem220","Tmem221","Tmem222","Tmem223","Tmem225","Tmem229a","Tmem229b","Tmem230","Tmem231","Tmem232","Tmem233","Tmem234","Tmem235","Tmem236","Tmem238","Tmem237","Tmem239","Tmem240","Tmem241","Tmem242","Tmem243","Tmem245","Tmem246","Tmem247","Tmem249","Tmem248","Tmem25","Tmem250","Tmem251","Tmem252","Tmem253","Tmem254","Tmem255a","Tmem255b","Tmem256","Tmem258","Tmem258b","Tmem259","Tmem26","Tmem262","Tmem260","Tmem265","Tmem263","Tmem266","Tmem267","Tmem268","Tmem269","Tmem270","Tmem27","Tmem30b","Tmem30a","Tmem30c","Tmem33","Tmem35a","Tmem35b","Tmem37","Tmem38a","Tmem38b","Tmem39a","Tmem39b","Tmem40","Tmem41a","Tmem41b","Tmem42","Tmem43","Tmem44","Tmem45a","Tmem45al","Tmem45b","Tmem47","Tmem5","Tmem50a","Tmem50b","Tmem51","Tmem52","Tmem52b","Tmem53","Tmem54","Tmem56","Tmem57","Tmem59","Tmem59l","Tmem60","Tmem61","Tmem62","Tmem63a","Tmem63b","Tmem63c","Tmem64","Tmem65","Tmem67","Tmem68","Tmem69","Tmem70","Tmem71","Tmem72","Tmem74","Tmem74b","Tmem74bos","Tmem79","Tmem80","Tmem81","Tmem82","Tmem86a","Tmem86b","Tmem87a","Tmem87b","Tmem88","Tmem88b","Tmem89","Tmem8a","Tmem8b","Tmem9","Tmem91","Tmem92","Tmem95","Tmem97","Tmem98","Tmem9b","Tmf1","Tmie","Tmigd1","Tmigd3","Tmlhe","Tmod1","Tmod2","Tmod3","Tmod4","Tmpo","Tmppe","Tmprss11a","Tmprss11b","Tmprss11c","Tmprss11d","Tmprss11e","Tmprss11f","Tmprss11g","Tmprss12","Tmprss13","Tmprss15","Tmprss2","Tmprss3","Tmprss4","Tmprss5","Tmprss6","Tmprss7","Tmprss9","Tmsb10","Tmsb15b2","Tmtc1","Tmsb4x","Tmtc3","Tmtc2","Tmub1","Tmtc4","Tmx1","Tmub2","Tmx2","Tmx3","Tmx4","Tnc","Tnfaip1","Tnfaip2","Tnfaip3","Tnfaip6","Tnfaip8","Tnfaip8l1","Tnfaip8l2","Tnf","Tnfaip8l3","Tnfrsf10b","Tnfrsf11a","Tnfrsf11b","Tnfrsf12a","Tnfrsf13b","Tnfrsf13c","Tnfrsf14","Tnfrsf17","Tnfrsf18","Tnfrsf19","Tnfrsf1b","Tnfrsf1a","Tnfrsf21","Tnfrsf22","Tnfrsf25","Tnfrsf26","Tnfrsf4","Tnfrsf8","Tnfrsf9","Tnfsf10","Tnfsf11","Tnfsf12","Tnfsf13","Tnfsf13b","Tnfsf14","Tnfsf15","Tnfsf18","Tnfsf4","Tnfsf8","Tnfsf9","Tnik","Tnip1","Tnip2","Tnip3","Tnk1","Tnk2","Tnks","Tnks1bp1","Tnks2","Tnmd","Tnn","Tnnc1","Tnnc2","Tnni1","Tnni2","Tnni3","Tnni3k","Tnnt1","Tnnt2","Tnnt3","Tnp1","Tnp2","Tnpo1","Tnpo2","Tnpo3","Tnrc18","Tnr","Tnrc6a","Tnrc6b","Tnrc6c","Tns1","Tns2","Tns3","Tns4","Tnxa-ps1","Tnxb","Tob1","Tob2","Toe1","Togaram1","Togaram2","Tollip","Tom1","Tom1l1","Tom1l2","Tomm20l","Tomm20","Tomm22","Tomm34","Tomm40l","Tomm40","Tomm5","Tomm6","Tomm7","Tomm70","Tonsl","Top1","Top1mt","Top2b","Top2a","Top3a","Top3b","Topaz1","Topbp1","Topors","Tor1a","Tor1aip1","Tor1aip2","Tor1b","Tor2a","Tor3a","Tor4a","Tore","Tox","Tox2","Tox3","Tox4","Tp53bp2","Tp53bp1","Tp53i11","Tp53i3","Tp53i13","Tp53inp2","Tp53rk","Tp53tg5","Tp53inp1","Tpbgl","Tpbpa","Tpbg","Tp63","Tp73","Tpc1808","Tpcr12","Tpcn1","Tpcn2","Tpd52","Tpd52l1","Tp53","Tpgs1","Tpd52l3","Tpd52l2","Tpgs2","Tph2","Tph1","Tpi1","Tpk1","Tpm3_v1","Tpm2","Tpm4","Tpmt","Tpm1","Tpm3","Tpo","Tpp2","Tppp","Tpp1","Tppp2","Tppp3","Tprg1","Tpra1","Tprg1l","Tpr","Tprn","Tprkb","Tpsb2","Tpsg1","Tpsab1","Tpst1","Tpst2","Tpte2","Tpx2","Tra2a","Tpt1","Tra2b","Trabd","Trabd2b","Tradd","Traf1","Traf3ip1","Traf2","Traf3","Traf3ip2","Traf3ip3","Traf5","Traf4","Traf6","Traf7","Trafd1","Traip","Trak1","Tram1l1","Trak2","Tram1","Tram2","Trank1","Trap1","Trap1a","Trappc1","Trappc10","Trappc12","Trappc11","Trappc2","Trappc13","Trappc2b","Trappc2l","Trappc3","Trappc3l","Trappc5","Trappc4","Trappc6a","Trappc6b","Trappc8","Trat1","Trappc9","Trav14s2","Trav12-3","Trav22","Treh","Trem1","Trdmt1","Trem2","Trdn","Trem3","Treml1","Treml4","Treml2","Trex1","Trerf1","Trex2","Trg","Triap1","Trhde","Trhr","Trib1","Trh","Trib2","Tril","Trim10","Trib3","Trim13","Trim11","Trim14","Trim15-ps1","Trim15","Trim16","Trim17","Trim2","Trim21","Trim24","Trim23","Trim25","Trim29","Trim26","Trim27","Trim3","Trim28","Trim30","Trim31","Trim30c","Trim34","Trim32","Trim33","Trim35","Trim36","Trim38","Trim39-ps","Trim39","Trim40","Trim43a","Trim37","Trim42","Trim41","Trim44","Trim45","Trim47","Trim46","Trim50","Trim5","Trim52","Trim54","Trim58","Trim55","Trim60","Trim59","Trim6","Trim62","Trim65","Trim66","Trim63","Trim67","Trim68","Trim7","Trim69","Trim8","Trim72","Trim71","Trim80","Triml1","Triml2","Trim9","Triobp","Trip10","Trip11","Trio","Trip13","Trip6","Trip12","Trip4","Triqk","Trir","Trit1","Trmo","Trmt1","Trmt10a","Trmt10b","Trmt10c","Trmt11","Trmt112","Trmt12","Trmt13","Trmt1l","Trmt2b","Trmt2a","Trmt5","Trmt44","Trmt6","Trmt61a","Trnaa-agc1","Trmu","Trnaa-agc10","Trnaa-agc11","Trnaa-agc13","Trnaa-agc12","Trnaa-agc15","Trnaa-agc14","Trnaa-agc16","Trnaa-agc17","Trnaa-agc19","Trnaa-agc18","Trnaa-agc2","Trnaa-agc20","Trnaa-agc3","Trnaa-agc4","Trnaa-agc5","Trnaa-agc6","Trnaa-agc7","Trnaa-agc8","Trnaa-agc9","Trnaa-cgc1","Trnaa-cgc2","Trnaa-cgc3","Trnaa-cgc4","Trnaa-ggc1","Trnaa-ugc1","Trnaa-ugc2","Trnaa-ugc3","Trnaa-ugc","Trnaa-ugc4","Trnaa-ugc5","Trnaa-ugc6","Trnaa-ugc7","Trnaa-ugc8","Trnaa-ugc9","Trnac-gca1","Trnac-gca10","Trnac-gca11","Trnac-gca12","Trnac-gca13","Trnac-gca","Trnac-gca14","Trnac-gca15","Trnac-gca16","Trnac-gca17","Trnac-gca18","Trnac-gca19","Trnac-gca2","Trnac-gca20","Trnac-gca21","Trnac-gca22","Trnac-gca23","Trnac-gca24","Trnac-gca25","Trnac-gca26","Trnac-gca27","Trnac-gca28","Trnac-gca29","Trnac-gca3","Trnac-gca30","Trnac-gca31","Trnac-gca32","Trnac-gca33","Trnac-gca34","Trnac-gca35","Trnac-gca36","Trnac-gca37","Trnac-gca38","Trnac-gca39","Trnac-gca4","Trnac-gca5","Trnac-gca6","Trnac-gca7","Trnac-gca8","Trnac-gca9","Trnad-guc1","Trnad-guc10","Trnad-guc11","Trnad-guc13","Trnad-guc12","Trnad-guc14","Trnad-guc2","Trnad-guc3","Trnad-guc4","Trnad-guc5","Trnad-guc6","Trnad-guc7","Trnad-guc8","Trnad-guc9","Trnae-cuc1","Trnae-cuc2","Trnae-cuc3","Trnae-cuc4","Trnae-cuc5","Trnae-cuc6","Trnae-cuc7","Trnae-cuc8","Trnae-cuc9","Trnae-uuc1","Trnae-uuc10","Trnae-uuc2","Trnae-uuc3","Trnae-uuc4","Trnae-uuc5","Trnae-uuc6","Trnae-uuc7","Trnae-uuc8","Trnae-uuc9","Trnaf-gaa1","Trnaf-gaa2","Trnaf-gaa3","Trnaf-gaa4","Trnaf-gaa5","Trnaf-gaa6","Trnaf-gaa7","Trnaf-gaa8","Trnag-ccc1","Trnag-ccc3","Trnag-ccc2","Trnag-ccc4","Trnag-ccc5","Trnag-gcc1","Trnag-gcc10","Trnag-gcc11","Trnag-gcc2","Trnag-gcc3","Trnag-gcc4","Trnag-gcc5","Trnag-gcc6","Trnag-gcc7","Trnag-gcc8","Trnag-gcc9","Trnag-ucc","Trnag-ucc1","Trnag-ucc2","Trnag-ucc3","Trnag-ucc4","Trnag-ucc5","Trnag-ucc6","Trnag-ucc7","Trnag-ucc8","Trnag-ucc9","Trnah-gug10","Trnah-gug1","Trnah-gug11","Trnah-gug2","Trnah-gug3","Trnah-gug4","Trnah-gug5","Trnah-gug6","Trnah-gug7","Trnah-gug8","Trnah-gug9","Trnai-aau1","Trnai-aau10","Trnai-aau11","Trnai-aau2","Trnai-aau3","Trnai-aau4","Trnai-aau5","Trnai-aau6","Trnai-aau7","Trnai-aau8","Trnai-aau9","Trnai-uau1","Trnai-uau2","Trnai-uau3","Trnak-cuu1","Trnak-cuu","Trnak-cuu10","Trnak-cuu11","Trnak-cuu12","Trnak-cuu13","Trnak-cuu14","Trnak-cuu15","Trnak-cuu16","Trnak-cuu17","Trnak-cuu18","Trnak-cuu2","Trnak-cuu3","Trnak-cuu4","Trnak-cuu5","Trnak-cuu6","Trnak-cuu7","Trnak-cuu8","Trnak-cuu9","Trnak-uuu","Trnak-uuu1","Trnak-uuu2","Trnak-uuu3","Trnak-uuu4","Trnak-uuu5","Trnak-uuu6","Trnak-uuu7","Trnak-uuu8","Trnak-uuu9","Trnal-aag1","Trnal-aag2","Trnal-aag3","Trnal-aag4","Trnal-aag5","Trnal-aag6","Trnal-caa2","Trnal-caa1","Trnal-caa3","Trnal-cag1","Trnal-cag10","Trnal-cag2","Trnal-cag3","Trnal-cag4","Trnal-cag5","Trnal-cag6","Trnal-cag7","Trnal-cag9","Trnal-cag8","Trnal-uaa1","Trnal-uaa2","Trnal-uag1","Trnal-uag2","Trnal-uag3","Trnam-cau1","Trnam-cau10","Trnam-cau11","Trnam-cau12","Trnam-cau13","Trnam-cau2","Trnam-cau3","Trnam-cau4","Trnam-cau5","Trnam-cau6","Trnam-cau7","Trnam-cau8","Trnam-cau9","Trnan-guu","Trnan-guu1","Trnan-guu10","Trnan-guu11","Trnan-guu12","Trnan-guu13","Trnan-guu2","Trnan-guu3","Trnan-guu4","Trnan-guu5","Trnan-guu6","Trnan-guu7","Trnan-guu8","Trnan-guu9","Trnan1","Trnap-agg","Trnap-agg1","Trnap-agg10","Trnap-agg11","Trnap-agg13","Trnap-agg12","Trnap-agg14","Trnap-agg15","Trnap-agg16","Trnap-agg17","Trnap-agg18","Trnap-agg2","Trnap-agg3","Trnap-agg4","Trnap-agg5","Trnap-agg6","Trnap-agg7","Trnap-agg8","Trnap-agg9","Trnap-cgg1","Trnap-cgg2","Trnap-cgg3","Trnap-ugg","Trnap-ugg1","Trnap-ugg2","Trnap-ugg4","Trnap-ugg3","Trnap-ugg5","Trnap-ugg6","Trnaq-cug1","Trnaq-cug","Trnaq-cug10","Trnaq-cug2","Trnaq-cug3","Trnaq-cug4","Trnaq-cug5","Trnaq-cug6","Trnaq-cug7","Trnaq-cug8","Trnaq-cug9","Trnaq-uug1","Trnaq-uug2","Trnaq-uug3","Trnaq-uug4","Trnaq-uug5","Trnar-acg1","Trnar-acg2","Trnar-acg3","Trnar-acg4","Trnar-acg5","Trnar-acg6","Trnar-ccg1","Trnar-ccg2","Trnar-ccg3","Trnar-ccu","Trnar-ccu1","Trnar-ccu2","Trnar-ccu3","Trnar-ccu4","Trnar-ccu5","Trnar-ccu6","Trnar-ccu7","Trnar-ccu8","Trnar-ccu9","Trnar-ucg1","Trnar-ucg2","Trnar-ucg3","Trnar-ucg4","Trnar-ucg5","Trnar-ucu1","Trnar-ucu2","Trnar-ucu3","Trnar-ucu4","Trnar-ucu5","Trnar-ucu6","Trnar-ucu7","Trnas-aga1","Trnas-aga10","Trnas-aga11","Trnas-aga2","Trnas-aga3","Trnas-aga4","Trnas-aga5","Trnas-aga6","Trnas-aga7","Trnas-aga8","Trnas-aga9","Trnas-cga1","Trnas-cga2","Trnas-cga3","Trnas-gcu","Trnas-gcu1","Trnas-gcu10","Trnas-gcu11","Trnas-gcu2","Trnas-gcu3","Trnas-gcu4","Trnas-gcu5","Trnas-gcu6","Trnas-gcu7","Trnas-gcu8","Trnas-gcu9","Trnas-uga1","Trnas-uga2","Trnas-uga3","Trnas-uga4","Trnat-agu","Trnat-agu1","Trnat-agu2","Trnat-agu3","Trnat-agu4","Trnat-agu5","Trnat-agu6","Trnat-agu7","Trnat-agu8","Trnat-cgu1","Trnat-cgu2","Trnat-cgu3","Trnat-cgu4","Trnat-cgu5","Trnat-ugu1","Trnat-ugu2","Trnat-ugu3","Trnat-ugu4","Trnat-ugu5","Trnav-aac1","Trnau1ap","Trnav-aac2","Trnav-aac3","Trnav-aac4","Trnav-aac5","Trnav-aac6","Trnav-aac7","Trnav-cac1","Trnav-cac2","Trnav-cac3","Trnav-cac4","Trnav-cac5","Trnav-cac6","Trnav-cac7","Trnav-cac8","Trnav-uac1","Trnav-uac2","Trnav-uac3","Trnav-uac4","Trnaw-cca1","Trnaw-cca2","Trnaw-cca3","Trnaw-cca4","Trnaw-cca5","Trnaw-cca6","Trnaw-cca7","Trnaw-cca8","Trnay-gua1","Trnay-gua2","Trnay-gua3","Trnay-gua4","Trnay-gua5","Trnay-gua6","Trnay-gua7","Trnay-gua8","Trnp1","Trnt1","Trnt1-ps1","Tro","Troap","Trove2","Trpa1","Trpc1","Trpc2","Trpc3","Trpc4ap","Trpc4","Trpc5os","Trpc5","Trpc7","Trpc6","Trpm1","Trpm2","Trpm3","Trpm4","Trpm5","Trpm6","Trpm7","Trpm8","Trps1","Trpt1","Trpv2","Trpv3","Trpv1","Trpv4","Trpv5","Trrap","Trpv6","Trub2","Trub1","Trub2-ps1","Trub2-ps2","Try10","Try5","Tryx5","Tsacc","Tsc1","Tsc2","Tsc22d1","Tsc22d2","Tsc22d3","Tsc22d4","Tsen15","Tsen2","Tsen34l1","Tsen34","Tsen54","Tsfm","Tsga10","Tsg101","Tsga10ip","Tsga13","Tshb","Tshr","Tshz1","Tshz2","Tshz3","Tsks","Tsku","Tslp","Tsn","Tsnax","Tsnaxip1","Tspan10","Tspan1","Tspan11","Tspan12","Tspan13","Tspan14","Tspan15","Tspan17","Tspan18","Tspan2","Tspan3","Tspan31","Tspan32","Tspan33","Tspan4","Tspan5","Tspan6","Tspan7","Tspan8","Tspan9","Tspear","Tspo","Tspo2","Tspy1","Tspoap1","Tspy26","Tspyl1","Tspyl2","Tspyl4","Tspyl5","Tsr1","Tsr2","Tsr3","Tssc4","Tssk1b","Tssk2","Tssk3","Tssk4","Tssk5","Tssk6","Tst","Tsta3","Tstd1","Tstd2","Tstd3","Tsx","Ttbk1","Ttbk2","Ttc1","Ttc12","Ttc13","Ttc14","Ttc16","Ttc17","Ttc19","Ttc21a","Ttc21b","Ttc22","Ttc23","Ttc23l","Ttc24","Ttc25","Ttc26","Ttc27","Ttc28","Ttc29","Ttc30a","Ttc3","Ttc30a1","Ttc30b","Ttc32","Ttc33","Ttc34","Ttc36","Ttc38","Ttc37","Ttc39b","Ttc39a","Ttc39d","Ttc39c","Ttc5","Ttc4","Ttc6","Ttc7a","Ttc7b","Ttc9","Ttc8","Ttc9b","Ttc9c","Ttf1","Ttf2","Tti1","Tti2","Ttl","Ttk","Ttll1","Ttll10","Ttll11","Ttll12","Ttll2","Ttll13","Ttll4","Ttll3","Ttll6","Ttll5","Ttll7","Ttll8","Ttll9","Ttn","Ttpa","Ttpal","Ttyh1","Ttr","Ttyh2","Ttyh3","Tub","Tuba1b","Tuba1a","Tuba1c","Tuba3a","Tuba3b","Tuba4a","Tuba8","Tubal3","Tubb1","Tubb2a","Tubb2b","Tubb3","Tubb4a","Tubb4b","Tubb5","Tubd1","Tubb6","Tube1","Tubg1","Tubg2","Tubgcp2","Tubgcp3","Tubgcp4","Tubgcp5","Tubgcp6","Tufm","Tuft1","Tug1","Tulp1","Tulp2","Tulp3","Tulp4","Tusc2","Tusc3","Tusc5","Tut1","Tvp23a","Tvp23b","Twf1","Twf2","Twf2-ps1","Twist1","Twist2","Twistnb","Twnk","Twsg1","Txlna","Txk","Txlnb","Txlng","Txn2","Txn1","Txndc11","Txndc12","Txndc15","Txndc16","Txndc17","Txndc2","Txndc5","Txndc8","Txndc9","Txnip","Txnl1","Txnl4a","Txnl4b","Txnrd1","Txnrd2","Txnrd3","Tyk2","Tymp","Tyms","Tyr","Tyro3","Tyrobp","Tyrp1","Tysnd1","Tyw1","Tyw3","Tyw5","U2af1","U2af1l4","U2af2","U2surp","Uaca","Uap1","Uap1l1","Uap1l2","Uba1","Uba1y","Uba2-ps1","Uba2","Uba3","Uba52","Uba5","Uba6","Uba7","Ubac1","Ubac2","Ubald1","Ubald2","Ubap1","Ubap1l","Ubap2","Ubap2l","Ubash3a","Ubash3b","Ubbp4","Ubb","Ubd","Ubc","Ube2a","Ube2b","Ube2c","Ube2d1","Ube2d2","Ube2d3","Ube2d4","Ube2d4l1","Ube2e1","Ube2e2","Ube2e3","Ube2f","Ube2g1","Ube2g2","Ube2h","Ube2i","Ube2j1","Ube2j2","Ube2k","Ube2l3","Ube2l6","Ube2m","Ube2n","Ube2o","Ube2q1","Ube2q2","Ube2q2l","Ube2ql1","Ube2s","Ube2r2","Ube2t","Ube2u","Ube2v1","Ube2v2","Ube2w","Ube2z","Ube3a","Ube3b","Ube3c","Ube3d","Ube4a","Ube4b","Ubfd1","Ubiad1","Ubl3","Ubl4b","Ubl4a","Ubl5","Ubl7","Ublcp1","Ubn1","Ubn2","Ubox5","Ubp1","Ubqln1","Ubqln2","Ubqln3","Ubqln4","Ubqlnl","Ubr1","Ubr2","Ubr3","Ubr5","Ubr4","Ubr7","Ubtd1","Ubtd2","Ubtfl1","Ubtf","Ubxn10","Ubxn1","Ubxn2a","Ubxn11","Ubxn2b","Ubxn4","Ubxn7","Ubxn6","Ubxn8","Uchl1","Uchl3","Uchl3-ps1","Uck1","Uchl5","Uck2","Uckl1","Ucma","Ucn","Ucn2","Ucn3","Ucp1","Ucp2","Ucp3","Uevld","Ufc1","Ufd1","Ufl1","Ufm1","Ufsp1","Ufsp2","Ugcg","Uggt1","Ugdh","Uggt2","Ugp2","Ugt1a2","Ugt1a1","Ugt1a4-ps","Ugt1a3","Ugt1a5","Ugt1a6","Ugt1a7c","Ugt1a8","Ugt1a9","Ugt1a9-ps","Ugt2a1","Ugt2a3","Ugt2b10","Ugt2b","Ugt2b17","Ugt2b15","Ugt2b35","Ugt2b37","Ugt2b7","Ugt3a2","Ugt8","Uhmk1","Uhrf1","Uhrf1bp1","Uhrf1bp1l","Uhrf2","Uimc1","Ulk1","Ulk2","Ulk3","Ulk4","Umodl","Umod","Umodl1","Umps","Unc119","Unc119b","Unc13a","Unc13b","Unc13c","Unc13d","Unc45a","Unc45b","Unc50","Unc5a","Unc5b","Unc5cl","Unc5c","Unc5d","Unc79","Unc80","Unc93a","Unc93b1","Uncx","Ung","Unk","Unkl","Uox","Upb1","Upf1","Upf2","Upf3a","Upf3b","Upk1a","Upk1b","Upk2","Upk3a","Upk3b","Upk3bl1","Upp2","Upp1","Uprt","Uqcc1","Uqcc2","Uqcc3","Uqcr10","Uqcr11","Uqcrb-ps1","Uqcrb","Uqcrc1","Uqcrc2","Uqcrfs1","Uqcrh","Urad","Uqcrq","Urb1","Urb2","Urgcp","Uri1","Urm1","Uroc1","Urod","Uros","Usb1","Use1","Usf1","Usf2","Usf3","Ush1c","Ush1g","Ushbp1","Usmg5","Ush2a","Uso1","Usp1","Usp10","Usp11","Usp12","Usp13","Usp14","Usp15","Usp16","Usp17l5","Usp18","Usp19","Usp2","Usp20","Usp21","Usp22","Usp24","Usp25","Usp26","Usp27x","Usp28","Usp29","Usp3","Usp30","Usp31","Usp32","Usp33","Usp34","Usp35","Usp36","Usp37","Usp38","Usp39","Usp4","Usp40","Usp42","Usp43","Usp44","Usp45","Usp46","Usp47","Usp48","Usp49","Usp5","Usp50","Usp51","Usp53","Usp54","Usp6nl","Usp7","Usp8","Usp9y","Usp9x","Uspl1","Ust","UST4r","Utf1","Ust5r","Utp11","Utp14a","Utp15","Utp18","Utp20","Utp23","Utp3","Utp4","Utp6","Uts2","Utrn","Uts2b","Uts2r","Uty","Uvrag","Uvssa","Uxt","Uxs1","V1ra14","Vac14","Vamp1","Vamp3","Vamp2","Vamp4","Vamp5","Vamp7","Vamp8","Vangl1","Vangl2","Vapa","Vapb","Vars","Vars2","Vash1","Vash2","Vasn","Vasp","Vat1","Vat1l","Vav1","Vav2","Vav3","Vax1","Vax2","Vbp1","Vcan","Vcam1","Vcl","Vcp","Vcpip1","Vcpkmt","Vcsa2","Vdac1","Vdac2","Vdac3","Vdr","Vegfb","Vegfa","Vegfc","Vegfd","Vegp2","Veph1","Vezf1","Vezt","Vgf","Vgll1","Vgll2","Vgll3","Vgll4","Vhl","Vhll","Vil1","Vill","Vim","Vip","Vipas39","Vipr1","Vipr2","Virma","Vit","Vkorc1","Vkorc1l1","Vma21","Vldlr","Vmac","Vmo1","Vmp1","Vnn1","Vnn3","Vof16","Vom1r-ps1","Vom1r-ps10","Vom1r-ps100","Vom1r-ps101","Vom1r-ps102","Vom1r-ps103","Vom1r-ps104","Vom1r-ps105","Vom1r-ps106","Vom1r-ps107","Vom1r-ps108","Vom1r-ps11","Vom1r-ps110","Vom1r-ps111","Vom1r-ps112","Vom1r-ps12","Vom1r-ps13","Vom1r-ps14","Vom1r-ps15","Vom1r-ps16","Vom1r-ps17","Vom1r-ps18","Vom1r-ps19","Vom1r-ps2","Vom1r-ps20","Vom1r-ps21","Vom1r-ps22","Vom1r-ps23","Vom1r-ps24","Vom1r-ps25","Vom1r-ps26","Vom1r-ps27","Vom1r-ps28","Vom1r-ps29","Vom1r-ps3","Vom1r-ps30","Vom1r-ps31","Vom1r-ps32","Vom1r-ps33","Vom1r-ps34","Vom1r-ps35","Vom1r-ps36","Vom1r-ps37","Vom1r-ps38","Vom1r-ps4","Vom1r-ps40","Vom1r-ps41","Vom1r-ps43","Vom1r-ps42","Vom1r-ps44","Vom1r-ps45","Vom1r-ps46","Vom1r-ps47","Vom1r-ps48","Vom1r-ps49","Vom1r-ps5","Vom1r-ps50","Vom1r-ps51","Vom1r-ps52","Vom1r-ps53","Vom1r-ps54","Vom1r-ps55","Vom1r-ps56","Vom1r-ps57","Vom1r-ps58","Vom1r-ps59","Vom1r-ps6","Vom1r-ps60","Vom1r-ps61","Vom1r-ps62","Vom1r-ps63","Vom1r-ps64","Vom1r-ps65","Vom1r-ps66","Vom1r-ps68","Vom1r-ps67","Vom1r-ps69","Vom1r-ps7","Vom1r-ps70","Vom1r-ps71","Vom1r-ps72","Vom1r-ps73","Vom1r-ps74","Vom1r-ps75","Vom1r-ps76","Vom1r-ps77","Vom1r-ps78","Vom1r-ps79","Vom1r-ps8","Vom1r-ps80","Vom1r-ps81","Vom1r-ps82","Vom1r-ps83","Vom1r-ps84","Vom1r-ps85","Vom1r-ps86","Vom1r-ps87","Vom1r-ps88","Vom1r-ps89","Vom1r-ps9","Vom1r-ps90","Vom1r-ps92","Vom1r-ps91","Vom1r-ps93","Vom1r-ps94","Vom1r-ps95","Vom1r-ps96","Vom1r-ps97","Vom1r-ps98","Vom1r-ps99","Vom1r1","Vom1r10","Vom1r100","Vom1r101","Vom1r102","Vom1r103","Vom1r104","Vom1r105","Vom1r106","Vom1r107","Vom1r108","Vom1r109","Vom1r11","Vom1r110","Vom1r111","Vom1r12","Vom1r13","Vom1r14","Vom1r15","Vom1r16","Vom1r17","Vom1r19","Vom1r2","Vom1r20","Vom1r21","Vom1r22","Vom1r23","Vom1r24","Vom1r25","Vom1r26","Vom1r27","Vom1r28","Vom1r3","Vom1r29","Vom1r30","Vom1r31","Vom1r32","Vom1r33","Vom1r34","Vom1r35","Vom1r36","Vom1r37","Vom1r38","Vom1r39","Vom1r4","Vom1r40","Vom1r41","Vom1r42","Vom1r43","Vom1r44","Vom1r45","Vom1r46","Vom1r47","Vom1r48","Vom1r49","Vom1r5","Vom1r50","Vom1r51","Vom1r52","Vom1r53","Vom1r54","Vom1r55","Vom1r56","Vom1r58","Vom1r57","Vom1r59","Vom1r6","Vom1r60","Vom1r61","Vom1r62","Vom1r63","Vom1r64","Vom1r65","Vom1r66","Vom1r67","Vom1r68","Vom1r69","Vom1r7","Vom1r70","Vom1r71","Vom1r72","Vom1r73","Vom1r74","Vom1r75","Vom1r76","Vom1r77","Vom1r78","Vom1r79","Vom1r8","Vom1r80","Vom1r81","Vom1r82","Vom1r83","Vom1r84","Vom1r85","Vom1r86","Vom1r87","Vom1r88","Vom1r89","Vom1r9","Vom1r92","Vom1r90","Vom1r93","Vom1r94","Vom1r95","Vom1r96","Vom1r97","Vom1r98","Vom2r-ps1","Vom1r99","Vom2r-ps10","Vom2r-ps100","Vom2r-ps101","Vom2r-ps103","Vom2r-ps104","Vom2r-ps105","Vom2r-ps106","Vom2r-ps107","Vom2r-ps108","Vom2r-ps109","Vom2r-ps110","Vom2r-ps11","Vom2r-ps111","Vom2r-ps112","Vom2r-ps113","Vom2r-ps114","Vom2r-ps115","Vom2r-ps116","Vom2r-ps117","Vom2r-ps118","Vom2r-ps119","Vom2r-ps12","Vom2r-ps120","Vom2r-ps121","Vom2r-ps122","Vom2r-ps123","Vom2r-ps124","Vom2r-ps125","Vom2r-ps126","Vom2r-ps127","Vom2r-ps128","Vom2r-ps129","Vom2r-ps13","Vom2r-ps130","Vom2r-ps131","Vom2r-ps132","Vom2r-ps133","Vom2r-ps136","Vom2r-ps135","Vom2r-ps137","Vom2r-ps138","Vom2r-ps14","Vom2r-ps140","Vom2r-ps141","Vom2r-ps142","Vom2r-ps15","Vom2r-ps16","Vom2r-ps17","Vom2r-ps18","Vom2r-ps19","Vom2r-ps20","Vom2r-ps22","Vom2r-ps23","Vom2r-ps24","Vom2r-ps25","Vom2r-ps26","Vom2r-ps28","Vom2r-ps27","Vom2r-ps3","Vom2r-ps30","Vom2r-ps31","Vom2r-ps32","Vom2r-ps33","Vom2r-ps34","Vom2r-ps35","Vom2r-ps36","Vom2r-ps37","Vom2r-ps38","Vom2r-ps39","Vom2r-ps4","Vom2r-ps40","Vom2r-ps41","Vom2r-ps42","Vom2r-ps43","Vom2r-ps44","Vom2r-ps46","Vom2r-ps45","Vom2r-ps47","Vom2r-ps48","Vom2r-ps49","Vom2r-ps5","Vom2r-ps50","Vom2r-ps51","Vom2r-ps52","Vom2r-ps53","Vom2r-ps54","Vom2r-ps55","Vom2r-ps56","Vom2r-ps57","Vom2r-ps59","Vom2r-ps61","Vom2r-ps60","Vom2r-ps62","Vom2r-ps63","Vom2r-ps64","Vom2r-ps65","Vom2r-ps66","Vom2r-ps67","Vom2r-ps69","Vom2r-ps7","Vom2r-ps70","Vom2r-ps71","Vom2r-ps72","Vom2r-ps74","Vom2r-ps75","Vom2r-ps76","Vom2r-ps77","Vom2r-ps78","Vom2r-ps79","Vom2r-ps8","Vom2r-ps82","Vom2r-ps84","Vom2r-ps83","Vom2r-ps85","Vom2r-ps86","Vom2r-ps87","Vom2r-ps88","Vom2r-ps89","Vom2r-ps9","Vom2r-ps90","Vom2r-ps91","Vom2r-ps92","Vom2r-ps93","Vom2r-ps94","Vom2r-ps95","Vom2r-ps96","Vom2r-ps97","Vom2r-ps98","Vom2r-ps99","Vom2r1","Vom2r10","Vom2r11","Vom2r12","Vom2r13","Vom2r15","Vom2r16","Vom2r17","Vom2r18","Vom2r19","Vom2r2","Vom2r21","Vom2r22","Vom2r23","Vom2r24","Vom2r25","Vom2r26","Vom2r27","Vom2r28","Vom2r29","Vom2r3","Vom2r30","Vom2r31","Vom2r32","Vom2r34","Vom2r33","Vom2r35","Vom2r36","Vom2r38","Vom2r37","Vom2r39","Vom2r4","Vom2r40","Vom2r41","Vom2r42","Vom2r43","Vom2r44","Vom2r45","Vom2r46","Vom2r47","Vom2r48","Vom2r49","Vom2r5","Vom2r50","Vom2r51","Vom2r52","Vom2r53","Vom2r54","Vom2r55","Vom2r56","Vom2r57","Vom2r58","Vom2r59","Vom2r6","Vom2r60","Vom2r62","Vom2r61","Vom2r63","Vom2r64","Vom2r65","Vom2r66","Vom2r67","Vom2r68","Vom2r69","Vom2r7","Vom2r70","Vom2r71","Vom2r72","Vom2r73","Vom2r75","Vom2r76","Vom2r77","Vom2r78","Vom2r79","Vom2r8","Vom2r80","Vom2r81","Vom2r9","Vopp1","Vpreb2","Vpreb1","Vpreb3","Vps11","Vps13b","Vps13a","Vps13c","Vps13d","Vps16","Vps18","Vps25","Vps26a","Vps26b","Vps28","Vps29","Vps33a","Vps33b","Vps35","Vps36","Vps37a","Vps37b","Vps37c","Vps37d","Vps39","Vps41","Vps45","Vps4a","Vps4b","Vps50","Vps51","Vps52","Vps53","Vps54","Vps54-ps1","Vps72","Vps8","Vps9d1","Vrk1","Vrk2","Vrk3","Vrtn","Vsig1","Vsig10","Vsig10l2","Vsig10l","Vsig2","Vsig4","Vsig8","Vsir","Vstm1","Vsnl1","Vstm2a","Vstm2b","Vstm2l","Vstm4","Vstm5","Vsx1","Vsx2","Vta1","Vtcn1","Vti1a","Vti1b","Vtn","Vwa1","Vwa2","Vwa3a","Vwa3b","Vwa5b1","Vwa5a","Vwa7","Vwa5b2","Vwc2","Vwa8","Vwc2l","Vwce","Vwde","Wac","Vwf","Wap","Wapl","Wars","Wars2","Was","Wasf1","Wasf2","Wasf3","Washc1","Washc2c","Washc3","Washc4","Washc5","Wbp1","Wasl","Wbp11","Wbp11l1","Wbp1l","Wbp2","Wbp2nl","Wbp4","Wdcp","Wdfy2","Wdfy1","Wdfy3","Wdfy4","Wdhd1","Wdpcp","Wdr1","Wdr11","Wdr12","Wdr13","Wdr17","Wdr18","Wdr19","Wdr20","Wdr24","Wdr25","Wdr26","Wdr27","Wdr3","Wdr33","Wdr31","Wdr34","Wdr35","Wdr36","Wdr37","Wdr38","Wdr4","Wdr41","Wdr43","Wdr44","Wdr45","Wdr45b","Wdr46","Wdr47","Wdr48","Wdr49","Wdr53","Wdr5","Wdr54","Wdr55","Wdr5b","Wdr59","Wdr6","Wdr60","Wdr61","Wdr62","Wdr63","Wdr64","Wdr65-ps1","Wdr66","Wdr7","Wdr70","Wdr72","Wdr73","Wdr74","Wdr75","Wdr76","Wdr78","Wdr81","Wdr77","Wdr82","Wdr83","Wdr83os","Wdr87","Wdr86","Wdr88","Wdr89","Wdr90","Wdr91","Wdr92","Wdr93","Wdr95","Wdr97","Wdr98","Wdsub1","Wdyhv1","Wdtc1","Wee1","Wee2","Wfdc10a","Wfdc11","Wfdc1","Wfdc13","Wfdc12","Wfdc15a","Wfdc15b","Wfdc16","Wfdc2","Wfdc18","Wfdc21","Wfdc3","Wfdc5","Wfdc6b","Wfdc6a","Wfdc9","Wfdc8","Wfikkn1","Wfikkn2","Wfs1","Whamm","Whrn","Wif1","Wipf1","Wipf2","Wipf3","Wipi1","Wipi2","Wisp1","Wisp2","Wisp3","Wiz","Wls","Wnk1","Wnk2","Wnk3","Wnk4","Wnt10b","Wnt10a","Wnt1","Wnt11","Wnt16","Wnt2b","Wnt2","Wnt3","Wnt3a","Wnt4","Wnt5a","Wnt5b","Wnt6","Wnt7a","Wnt7b","Wnt8b","Wnt8a","Wnt9a","Wnt9b","Wrap73","Wrap53","Wrb","Wrnip1","Wsb1","Wrn","Wsb2","Wscd2","Wscd1","Wt1","Wtip","Wtap","Wwc1","Wwc2","Wwc3","Wwox","Wwp1","Wwp2","Wwtr1","Xab2","Xaf1","Xcl1","Xbp1","Xcr1","Xirp1","Xiap","Xirp2","Xdh","Xkr4","Xk","Xkr5","Xkr7","Xkr8","Xkr9","Xkr6","Xkrx","Xlr3a","Xlr4a","Xpa","Xpnpep1","Xpc","Xpnpep2","Xpnpep3","Xpo4","Xpo1","Xpo5","Xpo6","Xpot","Xpo7","Xpr1","Xrcc2","Xrcc1","Xrcc3","Xrcc4","Xrcc5","Xrn1","Xrcc6","Xrn2","Xxylt1","Xrra1","Xylb","Xylt1","Xylt2","Yae1d1","Yaf2","Yars2","Yars","Yap1","Ybey","Ybx1-ps1","Ybx1-ps2","Ybx1-ps4","Ybx1-ps3","Ybx1","Ybx1-ps5","Ybx1-ps6","Ybx2","Ydjc","Ybx3","Yeats2","Yeats4","Yif1a","Yes1","Yif1b","Yipf1","Yipf2","Yipf4","Yipf3","Yipf5","Yipf6","Yipf7","Yjefn3","Ykt6","Ylpm1","Yme1l1","Yod1","Ypel1","Ypel3","Ypel2","Ypel4","Ypel5","Yrdc","Ythdc2","Ythdc1","Ythdf1","Ythdf2","Ythdf3","Ywhab","Ywhae","Ywhag","Ywhah","Ywhaq","Yy2","Yy1","Ywhaz","Zan","Zadh2","Zar1","Zap70","Zar1l","Zbed2","Zbbx","Zbed3","Zbed4","Zbed6","Zbed5","Zbp1","Zbtb1","Zbtb11","Zbtb10","Zbtb11os1","Zbtb12","Zbtb18","Zbtb17","Zbtb2","Zbtb16","Zbtb21","Zbtb20","Zbtb22","Zbtb24","Zbtb25","Zbtb26","Zbtb3","Zbtb32","Zbtb33","Zbtb34","Zbtb37","Zbtb38","Zbtb39","Zbtb4","Zbtb41","Zbtb40","Zbtb42","Zbtb43","Zbtb45","Zbtb44","Zbtb46","Zbtb47","Zbtb48","Zbtb49","Zbtb5","Zbtb6","Zbtb7b","Zbtb7a","Zbtb7c","Zbtb8a","Zbtb8b","Zbtb8os","Zbtb9","Zc2hc1a","Zc2hc1b","Zc2hc1c","Zc3h10","Zc3h11a","Zc3h12a","Zc3h12b","Zc3h12c","Zc3h12d","Zc3h13","Zc3h14","Zc3h18","Zc3h15","Zc3h3","Zc3h6","Zc3h4","Zc3h7a","Zc3h7b","Zc3h8","Zc3hav1l","Zc3hav1","Zc3hc1","Zc4h2","Zcchc10","Zcchc12","Zcchc11","Zcchc13","Zcchc14","Zcchc18","Zcchc17","Zcchc2","Zcchc24","Zcchc4","Zcchc3","Zcchc6","Zcchc8","Zcchc7","Zcchc9","Zcrb1","Zcwpw1","Zcwpw2","Zdbf2","Zdhhc11","Zdhhc1","Zdhhc12","Zdhhc13","Zdhhc14","Zdhhc15","Zdhhc16","Zdhhc17","Zdhhc18","Zdhhc19","Zdhhc2","Zdhhc21","Zdhhc20","Zdhhc22","Zdhhc23","Zdhhc24","Zdhhc25","Zdhhc3","Zdhhc4","Zdhhc5","Zdhhc6","Zdhhc7","Zdhhc9","Zdhhc8","Zeb1","Zeb2","Zeb2os","Zer1","Zfand1","Zfand2a","Zfand2b","Zfand3","Zfand5","Zfand6","Zfand4","Zfat","Zfc3h1","Zfhx2","Zfhx3","Zfhx4","Zfp1","Zfp105","Zfp108","Zfp106","Zfp11","Zfp110","Zfp113","Zfp111","Zfp112","Zfp12","Zfp13","Zfp133","Zfp131","Zfp136","Zfp141","Zfp14","Zfp143","Zfp142","Zfp146","Zfp148","Zfp157","Zfp161","Zfp169","Zfp17","Zfp174","Zfp18","Zfp180","Zfp182","Zfp184","Zfp185","Zfp189","Zfp2","Zfp202","Zfp207","Zfp212","Zfp213","Zfp217","Zfp219","Zfp24","Zfp236","Zfp248","Zfp251","Zfp26","Zfp263","Zfp260","Zfp266","Zfp27","Zfp267","Zfp275","Zfp277","Zfp276","Zfp28","Zfp28-ps1","Zfp280b","Zfp281","Zfp280d","Zfp280c","Zfp282","Zfp287","Zfp286a","Zfp292","Zfp296","Zfp3","Zfp30","Zfp300","Zfp316","Zfp318","Zfp317","Zfp319","Zfp322a","Zfp324","Zfp326","Zfp330","Zfp329","Zfp334","Zfp335","Zfp341","Zfp347","Zfp346","Zfp352","Zfp35","Zfp353","Zfp354b","Zfp354a","Zfp354c","Zfp358","Zfp362","Zfp36","Zfp366","Zfp365","Zfp367","Zfp36l3","Zfp36l2","Zfp36l1","Zfp383","Zfp37","Zfp382","Zfp384","Zfp385c","Zfp385a","Zfp385b","Zfp385d","Zfp386","Zfp39","Zfp394","Zfp397","Zfp398","Zfp395","Zfp40","Zfp407","Zfp41","Zfp410","Zfp414","Zfp42","Zfp420","Zfp418","Zfp422","Zfp423","Zfp428","Zfp426","Zfp42l","Zfp438","Zfp445","Zfp444","Zfp446","Zfp446-ps1","Zfp449","Zfp451","Zfp455","Zfp458","Zfp46","Zfp462","Zfp467","Zfp469","Zfp472","Zfp473","Zfp474","Zfp483","Zfp488","Zfp494","Zfp496","Zfp503","Zfp507","Zfp51","Zfp512b","Zfp512","Zfp511","Zfp513","Zfp518b","Zfp516","Zfp518a","Zfp52","Zfp524","Zfp523","Zfp521","Zfp526","Zfp53","Zfp532","Zfp536","Zfp54","Zfp541","Zfp558","Zfp551","Zfp560","Zfp563","Zfp569","Zfp566","Zfp57","Zfp574","Zfp575","Zfp579","Zfp580","Zfp59","Zfp583","Zfp593","Zfp592","Zfp598","Zfp597","Zfp600","Zfp605","Zfp606","Zfp608","Zfp609","Zfp61","Zfp612","Zfp617","Zfp618","Zfp623","Zfp62","Zfp622","Zfp628","Zfp629","Zfp637","Zfp638","Zfp639","Zfp646","Zfp64","Zfp641","Zfp647","Zfp648","Zfp644","Zfp652","Zfp653","Zfp654","Zfp664","Zfp663","Zfp655","Zfp668","Zfp667","Zfp672","Zfp683","Zfp68","Zfp688","Zfp687","Zfp69","Zfp691","Zfp689","Zfp697","Zfp692","Zfp704","Zfp703","Zfp7","Zfp706","Zfp707","Zfp707l1","Zfp709","Zfp709l1","Zfp719","Zfp711","Zfp710","Zfp717","Zfp74","Zfp748","Zfp758","Zfp768","Zfp764","Zfp764l1","Zfp746","Zfp763","Zfp770","Zfp773-ps1","Zfp771","Zfp772","Zfp78","Zfp775","Zfp777","Zfp780b","Zfp780b-ps1","Zfp786","Zfp788","Zfp787","Zfp791","Zfp800","Zfp804a","Zfp799","Zfp804b","Zfp821","Zfp819","Zfp82","Zfp827","Zfp830","Zfp839","Zfp831","Zfp853","Zfp865","Zfp862","Zfp846","Zfp84","Zfp866","Zfp867","Zfp868","Zfp87","Zfp869","Zfp874b","Zfp870","Zfp879","Zfp9","Zfp90","Zfp939","Zfp92","Zfp93","Zfp940-ps1","Zfp91","Zfp945","Zfp94","Zfp948-ps1","Zfp952","Zfp951","Zfp949","Zfp954","Zfp956","Zfp955a","Zfp961","Zfp958","Zfp964","Zfpl1","Zfpm2","Zfpm1","Zfr2","Zfr","Zfyve1","Zfx","Zfyve16","Zfyve19","Zfyve21","Zfyve26","Zfyve28","Zfyve27","Zg16","Zfyve9","Zglp1","Zg16b","Zgpat","Zgrf1","Zhx1","Zhx2","Zic1","Zhx3","Zic3","Zic5","Zic2","Zic4","Zik1","Zim1","Zkscan1","Zkscan2","Zkscan5","Zkscan4","Zkscan3","Zkscan7","Zmat1","Zkscan8","Zmat2","Zmat5","Zmat3","Zmat4","Zmiz1","Zmiz2","Zmpste24","Zmym1","Zmym2","Zmym3","Zmym5","Zmym4","Zmym6","Zmynd10","Znf146","Zmynd12","Zmynd15","Zmynd11","Zmynd19","Znf235","Znf354b","Znf408","Znf442","Znf454","Zmynd8","Znf48","Znf658","Znf474","Znf660","Znf7","Znf750","Znf740","Znf768","Znhit1","Znhit2","Znfx1","Znhit3","Znhit6","Znrd1-ps1","Znrd1as","Znrd1","Znrd1as1","Znrf2","Znrf3","Znrf1","Znrf4","Zp1","Zp2","Zp3","Zp3r","Zp4","Zpbp","Zpld1","Zpbp2","Zpr1","Zranb1","Zrsr1","Zscan10","Zrsr2","Zranb2","Zranb3","Zscan12","Zscan18","Zscan2","Zscan22","Zscan29","Zscan20","Zscan26","Zscan21","Zscan25","Zscan30","Zscan5b","Zscan4f","Zswim1","Zswim3","Zswim2","Zswim4","Zswim5","Zswim6","Zswim7","Zswim9","Zwilch","Zufsp","Zswim8","Zw10","Zxdb","Zwint","Zxdc","Zyg11a","Zyx","Zyg11b","Zzz3","Zzef1"]
\ No newline at end of file diff --git a/gn2/wqflask/static/new/javascript/validation.js b/gn2/wqflask/static/new/javascript/validation.js new file mode 100644 index 00000000..2cacacfa --- /dev/null +++ b/gn2/wqflask/static/new/javascript/validation.js @@ -0,0 +1,42 @@ +// Generated by CoffeeScript 1.8.0 +$(function() { + var remove_samples_is_valid, validate_remove_samples; + remove_samples_is_valid = function(input) { + var new_splats, pattern, splat, splats, _i, _len; + if (_.trim(input).length === 0) { + return true; + } + splats = input.split(","); + new_splats = (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = splats.length; _i < _len; _i++) { + input = splats[_i]; + _results.push(_.trim(input)); + } + return _results; + })(); + pattern = /^\d+\s*(?:-\s*\d+)?\s*$/; + for (_i = 0, _len = new_splats.length; _i < _len; _i++) { + splat = new_splats[_i]; + if (!splat.match(pattern)) { + return false; + } + } + return true; + }; + validate_remove_samples = function() { + + /* + Check if input for the remove samples function is valid and notify the user if not + */ + var input; + input = $('#remove_samples_field').val(); + if (remove_samples_is_valid(input)) { + return $('#remove_samples_invalid').hide(); + } else { + return $('#remove_samples_invalid').show(); + } + }; + return $('#remove_samples_field').change(validate_remove_samples); +}); diff --git a/gn2/wqflask/templates/admin/change_resource_owner.html b/gn2/wqflask/templates/admin/change_resource_owner.html new file mode 100644 index 00000000..7fd84387 --- /dev/null +++ b/gn2/wqflask/templates/admin/change_resource_owner.html @@ -0,0 +1,114 @@ +{% extends "base.html" %} +{% block title %}Resource Manager{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{% endblock %} +{% block content %} +<!-- Start of body --> + <div class="container"> + <div class="page-header"> + <h1>Search for user to assign ownership to:</h1> + </div> + <form id="change_owner_form" action="/resource-management/resources/{{ resource_id }}/change-owner" method="POST"> + <div style="min-width: 600px; max-width: 800px;"> + <fieldset> + <div class="form-horizontal" style="width: 900px;"> + <div style="margin-bottom: 30px;"> + <h2>Search for user by either name or e-mail:</h2> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label for="user_name" class="col-xs-3" style="float: left; font-size: 18px;">User's Name:</label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <input name="user_name" type="text" value=""> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label for="user_email" class="col-xs-3" style="float: left; font-size: 18px;">User's E-mail:</label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <input name="user_email" type="text" value=""> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label class="col-xs-3" style="float: left; font-size: 18px;"></label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <button type="button" id="find_users" class="btn btn-primary">Search</button> + <button style="margin-left: 20px; display: none;" type="submit" id="submit_new_owner" class="btn btn-success">Assign Ownership to Selected User</button> + </div> + </div> + </div> + </fieldset> + <hr> + <div id="user_results"> + </div> + </div> + </form> + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + + <script language="javascript"> + $('#find_users').click(function() { + $.ajax({ + method: "POST", + url: "/resource-management/{{ resource_id }}/users/search", + data: { + user_name: $('input[name=user_name]').val(), + user_email: $('input[name=user_email]').val() + }, + success: populate_users + }); + }) + + populate_users = function(json_user_list){ + let user_list = JSON.parse(json_user_list) + let searchResultsHtml = "" + if (user_list.length > 0){ + searchResultsHtml += "<table id='users_table' style='padding-top: 10px; width: 100%;' class='table-hover table-striped cell-border'>"; + searchResultsHtml += "<thead><tr><th></th><th>Index</th><th>Name</th><th>E-mail Address</th><th>Organization</th></tr></thead>"; + searchResultsHtml += "<tbody>"; + for (_i = 0, _len = user_list.length; _i < _len; _i++) { + this_user = user_list[_i] + searchResultsHtml += "<tr>"; + searchResultsHtml += "<td align='center' class='select_user'><input type='radio' name='new_owner' value='" + this_user.user_id + "'></td>"; + searchResultsHtml += "<td>" + (_i + 1).toString() + "</td>" + if ("full_name" in this_user) { + searchResultsHtml += "<td>" + this_user.full_name + "</td>"; + } else { + searchResultsHtml += "<td>N/A</td>" + } + if ("email_address" in this_user) { + searchResultsHtml += "<td>" + this_user.email_address + "</td>"; + } else { + searchResultsHtml += "<td>N/A</td>" + } + if ("organization" in this_user) { + searchResultsHtml += "<td>" + this_user.organization + "</td>"; + } else { + searchResultsHtml += "<td>N/A</td>" + } + searchResultsHtml += "</tr>" + } + searchResultsHtml += "</tbody>"; + searchResultsHtml += "</table>"; + } else { + searchResultsHtml = "<span>No users were found matching the entered criteria.</span>" + } + + $('#user_results').html(searchResultsHtml) + if (user_list.length > 0){ + $('#users_table').dataTable({ + 'order': [[1, "asc" ]], + 'sDom': 'tr' + }); + $('input[name=select_user]:eq(0)').prop("checked", true) + $('#submit_new_owner').css("display", "inline-block") + } + } + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/admin/create_group.html b/gn2/wqflask/templates/admin/create_group.html new file mode 100644 index 00000000..b1d214ea --- /dev/null +++ b/gn2/wqflask/templates/admin/create_group.html @@ -0,0 +1,84 @@ +{% extends "base.html" %} +{% block title %}Group Manager{% endblock %} +{% block content %} +<!-- Start of body --> + <div class="container"> + <div class="page-header"> + <h1>Create Group</h1> + </div> + <form action="{{ url_for('group_management.create_new_group') }}" + method="POST"> + <input type="hidden" name="admin_emails_to_add" value=""> + <input type="hidden" name="member_emails_to_add" value=""> + <fieldset> + <div class="form-horizontal" style="width: 900px; margin-bottom: 50px;"> + <div class="form-group" style="padding-left: 20px;"> + <label for="group_name" class="col-xs-3" style="float: left; font-size: 18px;">Group Name:</label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <div class="col-xs-12"> + <input name="group_name" type="text" style="width:100%;"></input> + </div> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label for="user_email" class="col-xs-3" style="float: left; font-size: 18px;">Add User Email:</label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <div class="col-xs-12"> + <input name="user_email" type="text" style="width:100%;"></input> + </div> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label class="col-xs-3"></label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <div class="col-xs-6"> + <button type="button" id="add_to_admins" class="btn btn-default">Add to Admins</button> + </div> + <div class="col-xs-6"> + <button type="button" id="add_to_members" class="btn btn-default">Add to Members</button> + </div> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label class="col-xs-3" style="font-size: 18px;">Members to be added:</label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <div class="col-xs-6"> + <textarea rows="8" cols="60" readonly placeholder="No users added" style="overflow-y: scroll; resize: none; width: 200px;" class="added_admins"></textarea> + </div> + <div class="col-xs-6"> + <textarea rows="8" cols="60" readonly placeholder="No users added" style="overflow-y: scroll; resize: none; width: 200px;" class="added_members"></textarea> + </div> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label class="col-xs-3"></label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <div class="col-xs-6"> + <button type="button" id="clear_admins" class="btn btn-default">Clear</button> + </div> + <div class="col-xs-6"> + <button type="button" id="clear_members" class="btn btn-default">Clear</button> + </div> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label for="create_group" class="col-xs-3" style="float: left; font-size: 18px;"></label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <div class="col-xs-6"> + <button type="submit" id="create_group" class="btn btn-primary">Create Group</button> + </div> + </div> + </div> + </div> + </fieldset> + </form> + </div> + +<!-- End of body --> +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/group_manager.js"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> +{% endblock %} diff --git a/gn2/wqflask/templates/admin/group_manager.html b/gn2/wqflask/templates/admin/group_manager.html new file mode 100644 index 00000000..eedfe138 --- /dev/null +++ b/gn2/wqflask/templates/admin/group_manager.html @@ -0,0 +1,147 @@ +{% extends "base.html" %} +{% block title %}Group Manager{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css') }}" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{% endblock %} +{% block content %} +<!-- Start of body --> + <div class="container"> + <div class="page-header"> + <h1>Manage Groups</h1> + {% if admin_groups|length != 0 or member_groups|length != 0 %} + <div style="display: inline;"> + <a href="{{ url_for('group_management.view_create_group_page') }}" target="_blank"> + <button type="button" class="btn btn-primary"> + Create Group + </button> + </a> + <button type="button" id="remove_groups" class="btn btn-primary" + data-url="{{ url_for('group_management.delete_groups') }}"> + Remove Selected Groups + </button> + </div> + {% endif %} + </div> + <form id="groups_form" action="/groups/manage" method="POST"> + <input type="hidden" name="selected_group_ids" value=""> + <div style="min-width: 800px; max-width: 1000px;"> + {% if admin_groups|length == 0 and member_groups|length == 0 %} + <h4>You currently aren't a member or admin of any groups.</h4> + <br> + <a href="{{ url_for('group_management.view_create_group_page') }}" target="_blank"> + <button type="button" class="btn btn-primary"> + Create Group + </button> + </a> + {% else %} + <div style="margin-top: 20px;"><h2>Admin Groups</h2></div> + <hr> + {% if admin_groups|length == 0 %} + <h4>You currently aren't the administrator of any groups.</h4> + {% else %} + <table id="admin_groups" class="table-hover table-striped cell-border" style="float: left;"> + <thead> + <tr> + <th></th> + <th>Index</th> + <th>Name</th> + <th># Members</th> + <th>Created</th> + <th>Last Changed</th> + <th>Group ID</th> + </tr> + </thead> + <tbody> + {% for group in admin_groups %} + <tr> + <td><input type="checkbox" name="group_id" value="{{ group.id }}"></td> + <td align="right">{{ loop.index }}</td> + {% set group_url = url_for('group_management.view_group', group_id=group.uuid) %} + <td><a href="{{ group_url }}">{{ group.name }}</a></td> + <td align="right">{{ group.admins|length + group.members|length }}</td> + <td>{{ group.created_timestamp }}</td> + <td>{{ group.changed_timestamp }}</td> + <td>{{ group.uuid }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} + </div> + <hr> + <div style="min-width: 800px; max-width: 1000px;"> + <div><h2>User Groups</h2></div> + <hr> + {% if member_groups|length == 0 %} + <h4>You currently aren't a member of any groups.</h4> + {% else %} + <table id="member_groups" class="table-hover table-striped cell-border" style="float: left;"> + <thead> + <tr> + <th></th> + <th>Index</th> + <th>Name</th> + <th># Members</th> + <th>Created</th> + <th>Last Changed</th> + </tr> + </thead> + <tbody> + {% for group in member_groups %} + <tr> + <td><input type="checkbox" name="read" value="{{ group.id }}"></td> + <td>{{ loop.index }}</td> + {% set group_url = url_for('group_management.view_group', group_id=group.uuid) %} + <td><a href="{{ group_url }}">{{ group.name }}</a></td> + <td>{{ group.admins|length + group.members|length }}</td> + <td>{{ group.created_timestamp }}</td> + <td>{{ group.changed_timestamp }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} + {% endif %} + </div> + </form> + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + + <script type="text/javascript" charset="utf-8"> + $(document).ready( function () { + {% if admin_groups|length != 0 %} + $('#admin_groups').dataTable({ + 'sDom': 'tr' + }); + {% endif %} + {% if member_groups|length != 0 %} + $('#member_groups').dataTable({ + 'sDom': 'tr' + }); + {% endif %} + submit_special = function(url) { + $("#groups_form").attr("action", url); + return $("#groups_form").submit(); + }; + + $("#remove_groups").on("click", function() { + url = $(this).data("url") + groups = [] + $("input[name=group_id]:checked").each(function() { + groups.push($(this).val()); + }); + groups_string = groups.join(":") + $("input[name=selected_group_ids]").val(groups_string) + return submit_special(url) + }); + }); + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/admin/ind_user_manager.html b/gn2/wqflask/templates/admin/ind_user_manager.html new file mode 100644 index 00000000..b821e5d5 --- /dev/null +++ b/gn2/wqflask/templates/admin/ind_user_manager.html @@ -0,0 +1,111 @@ +{% extends "base.html" %} +{% block title %}User Manager{% endblock %} +{% block content %} +<!-- Start of body --> + + <div class="container"> + <div class="page-header"> + <h1 title="{{ user.id }}">{{ user.email_address }}</h1> + + <span class="badge">{{ numify(user.login_count, "login", "logins").capitalize() }}</span> + + {% if user.active %} + <span class="label label-success">Active</span> + {% else %} + <span class="label label-warning">Inactive</span> + {% endif %} + </div> + + {{ flash_me() }} + + <table class="table table-hover"> +<!-- <thead> + <tr> + <th>Field</th> + <th>Value</th> + </tr> + </thead>--> + + <tr> + <td>Name</td> + <td>{{ user.full_name }}</td> + </tr> + + <tr> + <td>Organization</td> + <td>{{ user.organization }}</td> + </tr> + + + <tr> + <td>Confirmed</td> + + {% if user.confirmed_at %} + <td>{{ timeago(user.confirmed_at + "Z") }}</td> + {% else %} + <td><span class="label label-warning">Unconfirmed</span></td> + {% endif %} + </tr> + + <tr> + <td>Superuser</td> + + {% if user.superuser %} + <td>Made a superuser {{ timeago(user.superuser_info['timestamp'] + "Z") }} by + {{ user.crowner.name_and_org }}. + </td> + {% else %} + <td> + <span> + <a class="btn btn-danger btn-small" href={{"/manage/make_superuser?user_id={}".format(user.id)}}> + Make Superuser + </a> + </span> + </td> + {% endif %} + </tr> + + + <tr> + <td>Most recent login</td> + {% if user.most_recent_login %} + <td>{{ timeago(user.most_recent_login.timestamp.isoformat() + "Z") }} from {{ user.most_recent_login.ip_address }}</td> + {% else %} + <td><span class="label label-warning">Never</span></td> + {% endif %} + </tr> + +<!-- <tr> + <td>Last login</td> + <td>{{ user.last_login_at }} from {{ user.last_login_ip }}</td> + </tr> +--> + <!-- <tr> + <td>Number of logins</td> + <td>{{ user.login_count }}</td> + </tr>--> + <tr> + <td colspan="2"> + <a class="btn btn-danger btn-small" href={{"/manage/assume_identity?user_id={}".format(user.id)}}> + Become this user for debugging + </a> + </td> + </tr> + + </table> + + + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/timeago.min.js') }}"></script> + <script> + $('body').timeago(); + </script> + +{% endblock %} diff --git a/gn2/wqflask/templates/admin/manage_resource.html b/gn2/wqflask/templates/admin/manage_resource.html new file mode 100644 index 00000000..63ec17c0 --- /dev/null +++ b/gn2/wqflask/templates/admin/manage_resource.html @@ -0,0 +1,124 @@ +{% extends "base.html" %} +{% block title %}Resource Manager{% endblock %} +{% block content %} +<!-- Start of body --> +<div class="container"> + <section> + {{ flash_me() }} + {% set DATA_ACCESS = access_role.get('data') %} + {% set METADATA_ACCESS = access_role.get('metadata') %} + {% set ADMIN_STATUS = access_role.get('admin') %} + {% set ADMIN_STATUS = access_role.get('admin') %} + <h1>Resource Manager</h1> + {% if resource_info.get('owner_id') %} + {% set user_details = resource_info.get('owner_details') %} + <h3> + Current Owner: {{ user_details.get('full_name') }} + </h3> + {% if user_details.get('organization') %} + <h3> + Organization: {{ user_details.get('organization')}} + </h3> + {% endif %} + {% endif %} + {% if DATA_ACCESS > DataRole.VIEW and ADMIN_STATUS > AdminRole.NOT_ADMIN %} + <a class="btn btn-danger" target="_blank" + href="/resource-management/resources/{{ resource_info.get('resource_id') }}/change-owner"> + Change Owner + </a> + {% endif %} + </section> + + <section class="container" style="margin-top: 2em;"> + <form class="container-fluid" action="/resource-management/resources/{{ resource_info.get('resource_id') }}/make-public" method="POST"> + <input type="hidden" name="resource_id" value="{{ resource_info.get('resource_id') }}"> + <div> + <fieldset> + <div class="form-horizontal" style="width: 900px; margin-bottom: 50px;"> + <div class="form-group" style="padding-left: 20px;"> + <label for="group_name" class="col-xs-3" style="float: left; font-size: 18px;">Resource Name:</label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + {{ resource_info.get('name') }} + </div> + </div> + {% if DATA_ACCESS > DataRole.VIEW and ADMIN_STATUS > AdminRole.NOT_ADMIN %} + {% set is_open_to_public = DataRole(resource_info.get('default_mask').get('data')) > DataRole.NO_ACCESS %} + <div class="form-group" style="padding-left: 20px;"> + <label for="user_email" class="col-xs-3" style="float: left; font-size: 18px;">Open to Public:</label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <label class="radio-inline"> + <input type="radio" name="open_to_public" value="True" {{ 'checked' if is_open_to_public }}> + Yes + </label> + <label class="radio-inline"> + <input type="radio" name="open_to_public" value="False" {{ 'checked' if not is_open_to_public }}> + No + </label> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label class="col-xs-3" style="float: left; font-size: 18px;"></label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <button id="save_changes" class="btn btn-primary" data-url="/resource-management/resources/change_default_privileges">Save Changes</button> + </div> + </div> + {% endif %} + </div> + </fieldset> + </div> + {% if ADMIN_STATUS > AdminRole.NOT_ADMIN %} + <div style="min-width: 600px; max-width: 800px;"> + <hr> + <button id="add_group_to_resource" class="btn btn-primary" style="margin-bottom: 30px;" data-url="/resources/add_group">Add Group</button> + <br> + {% if resource_info.get('group_masks', [])|length > 0 %} + <h2>Current Group Permissions</h2> + <hr> + <table id="groups_table" class="table table-hover table-striped cell-border"> + <thead> + <tr> + <th>Id</th> + <th>Name</th> + <th>Data</th> + <th>Metadata</th> + </tr> + </thead> + <tbody> + {% for key, value in resource_info.get('group_masks').items() %} + <tr> + <td>{{ key }}</td> + <td>{{ value.group_name}}</td> + <td>{{ value.data }}</td> + <td>{{ value.metadata }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% else %} + <h3>No groups are currently added to this resource.</h3> + {% endif %} + </div> + {% endif %} + </form> + </section> + + <!-- End of body --> + + {% endblock %} + {% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + + <script type="text/javascript" charset="utf-8"> + $('#add_group_to_resource, #save_changes, #change_owner').click(function(){ + url = $(this).data("url"); + $('#manage_resource').attr("action", url) + $('#manage_resource').submit() + }) + + {% if group_masks|length > 0 %} + $('#groups_table').dataTable({ + 'sDom': 'tr', + }); + {% endif %} + </script> + {% endblock %} diff --git a/gn2/wqflask/templates/admin/manage_user.html b/gn2/wqflask/templates/admin/manage_user.html new file mode 100644 index 00000000..3ef90b90 --- /dev/null +++ b/gn2/wqflask/templates/admin/manage_user.html @@ -0,0 +1,79 @@ +{% extends "base.html" %}
+{% block title %}View and Edit Group{% endblock %}
+{% block css %}
+ <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" />
+ <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css') }}" />
+ <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" />
+{% endblock %}
+{% block content %}
+<!-- Start of body -->
+ <div class="container">
+ {% if 'full_name' in user_details %}
+ <div class="page-header">
+ <h1>User details for: {{ user_details.full_name }}</h1>
+ </div>
+ {% endif %}
+ <form id="user_form" action="/user/manage" method="POST">
+ <div class="row">
+ <div id="user_info_div" class="col-xs-8" style="margin-right: 30px; min-width: 800px; max-width: 1000px;">
+ <div class="form-horizontal">
+ <div class="form-group">
+ <label for="email_address" style="text-align: left;" class="col-xs-2">Email Address:</label>
+ <div style="margin-left: 20px; text-align: left;" class="col-xs-2 controls">
+ <span id="email_address">{% if 'email_address' in user_details %}{{ user_details.email_address }}{% else %}N/A{% endif %}</span>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="full_name" style="text-align: left;" class="col-xs-2">User Name:</label>
+ <div style="margin-left: 20px; text-align: left;" class="col-xs-2 controls">
+ <span id="full_name" class="old_user_attribute">{% if 'full_name' in user_details %}{{ user_details.full_name }}{% else %}N/A{% endif %}</span>
+ <input type="text" name="new_full_name" style="display: none; width: 500px;" class="form-control new_user_attribute" placeholder="{% if 'full_name' in user_details %}{{ user_details.full_name }}{% else %}N/A{% endif %}">
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="organization" style="text-align: left;" class="col-xs-2">Organization:</label>
+ <div style="margin-left: 20px; text-align: left;" class="col-xs-2 controls">
+ <span id="organization" class="old_user_attribute">{% if 'organization' in user_details %}{{ user_details.organization }}{% else %}N/A{% endif %}</span>
+ <input type="text" name="new_organization" style="display: none; width: 500px;" class="form-control new_user_attribute" placeholder="{% if 'organization' in user_details %}{{ user_details.organization }}{% else %}N/A{% endif %}">
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="change_user_details" style="text-align: left;" class="col-xs-2"></label>
+ <div style="margin-left: 20px; text-align: left;" class="col-xs-2 controls">
+ <input type="button" id="change_user_details" value="Change Details">
+ <input type="button" id="save_changes" value="Save Changes" style="display: none;">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </form>
+ </div>
+
+<!-- End of body -->
+
+{% endblock %}
+
+{% block js %}
+ <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script>
+
+ <script type="text/javascript" charset="utf-8">
+ $(document).ready( function () {
+ $('#change_user_details').click(function(){
+ $('.new_user_attribute').css("display", "inline-block");
+ $('.old_user_attribute').css("display", "none");
+ $('#change_user_details').css("display", "none");
+ $('#save_changes').css("display", "inline-block");
+ });
+
+ $('#save_changes').click(function(){
+ $('.new_user_attribute').each(function(){
+ if ($(this).val() == ""){
+ $(this).val($(this).attr("placeholder"))
+ }
+ });
+ $('#user_form').submit();
+ });
+ });
+ </script>
+{% endblock %}
diff --git a/gn2/wqflask/templates/admin/search_for_groups.html b/gn2/wqflask/templates/admin/search_for_groups.html new file mode 100644 index 00000000..0e1ec720 --- /dev/null +++ b/gn2/wqflask/templates/admin/search_for_groups.html @@ -0,0 +1,134 @@ +{% extends "base.html" %} +{% block title %}Resource Manager{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{% endblock %} +{% block content %} +<!-- Start of body --> + <div class="container"> + <div class="page-header"> + <h1>Find Groups</h1> + </div> + <form id="add_group" action="/resources/add_group" method="POST"> + <input type="hidden" name="resource_id" value="{{ resource_id }}"> + <div style="min-width: 600px; max-width: 800px;"> + <fieldset> + <div class="form-horizontal" style="width: 900px;"> + <div style="margin-bottom: 30px;"> + <h2>Search by:</h2> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label for="group_name" class="col-xs-3" style="float: left; font-size: 18px;">Group ID:</label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <input name="group_id" type="text" value=""> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label for="group_name" class="col-xs-3" style="float: left; font-size: 18px;">Group Name:</label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <input name="group_name" type="text" value=""> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label for="user_name" class="col-xs-3" style="float: left; font-size: 18px;">User Name:</label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <input name="user_name" type="text" value=""> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label for="user_email" class="col-xs-3" style="float: left; font-size: 18px;">User E-mail:</label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <input name="user_email" type="text" value=""> + </div> + </div> + <div class="form-group" style="padding-left: 20px;"> + <label class="col-xs-3" style="float: left; font-size: 18px;"></label> + <div class="controls input-append col-xs-9" style="display: flex; padding-left: 20px; float: left;"> + <button type="button" id="find_groups" class="btn btn-primary">Search</button> + <button style="margin-left: 20px; display: none;" type="submit" id="submit_group" class="btn btn-success">Add Privileges for Selected Group</button> + </div> + </div> + </div> + </fieldset> + <hr> + <div id="group_results"> + </div> + </div> + </form> + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/group_manager.js"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + + <script type="text/javascript" charset="utf-8"> + + $('#find_groups').click(function() { + $.ajax({ + method: "POST", + url: "/search_for_groups", + data: { + group_id: $('input[name=group_id]').val(), + group_name: $('input[name=group_name]').val(), + user_name: $('input[name=user_name]').val(), + user_email: $('input[name=user_email]').val() + }, + success: populate_groups + }); + }) + + populate_groups = function(json_group_list){ + console.log(json_group_list) + var group_list = JSON.parse(json_group_list) + + var the_html = "" + if (group_list.length > 0){ + the_html += "<table id='groups_table' style='padding-top: 10px; width: 100%;' class='table-hover table-striped cell-border'>"; + the_html += "<thead><tr><th></th><th>Index</th><th>Name</th><th># Admins</th><th># Members</th></tr></thead>"; + the_html += "<tbody>"; + for (_i = 0, _len = group_list.length; _i < _len; _i++) { + this_group = group_list[_i] + the_html += "<tr>"; + the_html += "<td align='center' class='select_group'><input type='radio' name='selected_group' value='" + this_group.id + "'></td>"; + the_html += "<td>" + (_i + 1).toString() + "</td>" + if ("name" in this_group) { + the_html += "<td>" + this_group.name + "</td>"; + } else { + the_html += "<td>N/A</td>" + } + if ("admins" in this_group) { + the_html += "<td>" + this_group.admins.length + "</td>"; + } else { + the_html += "<td>0</td>" + } + if ("members" in this_group) { + the_html += "<td>" + this_group.members.length + "</td>"; + } else { + the_html += "<td>0</td>" + } + the_html += "</tr>" + } + the_html += "</tbody>"; + the_html += "</table>"; + } else { + the_html = "<span>No groups were found matching the entered criteria.</span>" + } + + $('#group_results').html(the_html) + if (group_list.length > 0){ + $('#groups_table').dataTable({ + 'order': [[1, "asc" ]], + 'sDom': 'tr' + }); + $('input[name=selected_group]:eq(0)').prop("checked", true) + $('#submit_group').css("display", "inline-block") + } + } + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/admin/set_group_privileges.html b/gn2/wqflask/templates/admin/set_group_privileges.html new file mode 100644 index 00000000..04842453 --- /dev/null +++ b/gn2/wqflask/templates/admin/set_group_privileges.html @@ -0,0 +1,102 @@ +{% extends "base.html" %} +{% block title %}Set Group Privileges{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css') }}" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{% endblock %} +{% block content %} +<!-- Start of body --> + <div class="container"> + <h1>Group Privileges</h1> + <br> + <form id="set_group_privileges" action="/resources/add_group" method="POST"> + <input type="hidden" name="resource_id" value="{{ resource_id }}"> + <input type="hidden" name="group_id" value="{{ group_id }}"> + <div style="min-width: 600px; max-width: 800px;"> + <button type="submit" class="btn btn-primary" style="margin-bottom: 10px;">Add Group</button> + <hr> + <h2>Data and Metadata Privileges</h2> + <table id="data_privileges_table" class="table-hover table-striped cell-border" style="float: left;"> + <thead> + <tr> + <th></th> + <th>No-Access</th> + <th>View</th> + <th>Edit</th> + </tr> + </thead> + <tbody> + <tr> + <td>Data:</td> + {% if 'data' in default_privileges %} + <td align="center" style="padding: 0px;"><input type="radio" name="data_privilege" VALUE="no-access" {% if default_privileges.data == "no-access" %}checked{% endif %}></td> + <td align="center" style="padding: 0px;"><input type="radio" name="data_privilege" VALUE="view" {% if default_privileges.data == "view" %}checked{% endif %}></td> + <td align="center" style="padding: 0px;"><input type="radio" name="data_privilege" VALUE="edit" {% if default_privileges.data == "edit" %}checked{% endif %}></td> + {% else %} + <td align="center" style="padding: 0px;"><input type="radio" name="data_privilege" VALUE="no-access" checked></td> + <td align="center" style="padding: 0px;"><input type="radio" name="data_privilege" VALUE="view"></td> + <td align="center" style="padding: 0px;"><input type="radio" name="data_privilege" VALUE="edit"></td> + {% endif %} + </tr> + <tr> + <td>Metadata:</td> + {% if 'metadata' in default_privileges %} + <td align="center" style="padding: 0px;"><input type="radio" name="metadata_privilege" VALUE="no-access" {% if default_privileges.metadata == "no-access" %}checked{% endif %}></td> + <td align="center" style="padding: 0px;"><input type="radio" name="metadata_privilege" VALUE="view" {% if default_privileges.metadata == "view" %}checked{% endif %}></td> + <td align="center" style="padding: 0px;"><input type="radio" name="metadata_privilege" VALUE="edit" {% if default_privileges.metadata[-1] == "edit" %}checked{% endif %}></td> + {% else %} + <td align="center" style="padding: 0px;"><input type="radio" name="metadata_privilege" VALUE="no-access" checked></td> + <td align="center" style="padding: 0px;"><input type="radio" name="metadata_privilege" VALUE="view"></td> + <td align="center" style="padding: 0px;"><input type="radio" name="metadata_privilege" VALUE="edit"></td> + {% endif %} + </tr> + </tbody> + </table> + <hr> + <h2>Admin Privileges</h2> + <table id="admin_privileges_table" class="table-hover table-striped cell-border" style="float: left;"> + <thead> + <tr> + <th></th> + <th>Not Admin</th> + <th>Edit Access</th> + <th>Edit Admins</th> + </tr> + </thead> + <tbody> + <tr> + <td>Admin:</td> + {% if 'admin' in default_privileges %} + <td align="center" style="padding: 0px;"><input type="radio" name="admin_privilege" VALUE="not-admin" {% if default_privileges.admin == "not-admin" %}checked{% endif %}></td> + <td align="center" style="padding: 0px;"><input type="radio" name="admin_privilege" VALUE="edit-access" {% if default_privileges.admin == "edit-access" %}checked{% endif %}></td> + <td align="center" style="padding: 0px;"><input type="radio" name="admin_privilege" VALUE="edit-admins" {% if default_privileges.admin == "edit-admins" %}checked{% endif %}></td> + {% else %} + <td align="center" style="padding: 0px;"><input type="radio" name="admin_privilege" VALUE="not-admin" checked></td> + <td align="center" style="padding: 0px;"><input type="radio" name="admin_privilege" VALUE="edit-access"></td> + <td align="center" style="padding: 0px;"><input type="radio" name="admin_privilege" VALUE="edit-admins"></td> + {% endif %} + </tr> + </tbody> + </table> + </div> + </form> + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + <script> + $('#data_privileges_table').dataTable({ + 'sDom': 'tr', + 'bSort': false + }); + $('#admin_privileges_table').dataTable({ + 'sDom': 'tr', + 'bSort': false + }); + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/admin/user_manager.html b/gn2/wqflask/templates/admin/user_manager.html new file mode 100644 index 00000000..2b6c1b2b --- /dev/null +++ b/gn2/wqflask/templates/admin/user_manager.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} +{% block title %}User Manager{% endblock %} +{% block content %} +<!-- Start of body --> + {{ header("List of users", "" )}} + + + <div class="container"> + <div class="page-header"> + <h1>User Manager</h1> + </div> + + <table class="table table-hover"> + <thead> + <tr> + <th>Email</th> + <th>Organization</th> + <th>Active</th> + <th>Confirmed</th> + <th>Superuser</th> + </tr> + </thead> + {% for user in users %} + <tr> + <td title="{{ user.id }}"> + <a href="{{ url_for('manage_user', user_id=user.id) }}">{{ user.email_address }}</a> + </td> + <td>{{ user.organization }}</td> + <td>{{ 'Yes' if user.active else 'No' }}</td> + <td title="{{ user.confirmed }}">{{ 'True' if user.confirmed else 'False' }}</td> + <td title="{{ user.superuser }}">{{ 'True' if user.superuser else 'False' }}</td> + </tr> + {% endfor %} + </table> + + + </div> + +<!-- End of body --> + +{% endblock %} diff --git a/gn2/wqflask/templates/admin/view_group.html b/gn2/wqflask/templates/admin/view_group.html new file mode 100644 index 00000000..c88ce0e7 --- /dev/null +++ b/gn2/wqflask/templates/admin/view_group.html @@ -0,0 +1,270 @@ +{% extends "base.html" %} +{% block title %}View and Edit Group{% endblock %} +{% block css %} +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css') }}" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{% endblock %} +{% block content %} +<!-- Start of body --> +{% set GROUP_URL = url_for('group_management.view_group', group_id=group_info.guid) %} +{% set UPDATE_GROUP_URL = url_for('group_management.update_group', group_id=group_info.guid) %} +<div class="container"> + <div class="page-header"> + <h1> + <span id="group_name">Name: {{ group_info.name }}</span> + <input type="text" name="new_group_name" style="font-size: 20px; display: none; width: 500px;" class="form-control" placeholder="{{ group_info.name }}"> + {% if is_admin %} + <button class="btn btn-default" style="display: inline;" id="change_group_name">Change Group Name</button> + {% endif %} + </h1> + {% if is_admin %} + <div style="display: inline;"> + <button type="button" id="remove_users" class="btn btn-danger" data-url="/groups/remove_users">Remove Selected Users from Group</button> + </div> + {% endif %} + </div> + <form id="group_form" action="{{ UPDATE_GROUP_URL }}" method="POST"> + <input type="hidden" name="group_id" value="{{ group_info.id }}"> + <input type="hidden" name="selected_admin_ids" value=""> + <input type="hidden" name="selected_member_ids" value=""> + <div class="row"> + <div id="groups_div" class="col-xs-6" style="margin-right: 30px; min-width: 600px; max-width: 800px;"> + <div> + <div style="margin-top: 20px;"><h2>Admins</h2></div> + <hr> + <table id="group_admins" class="table-hover table-striped cell-border" style="float: left;"> + <thead> + <tr> + <th></th> + <th>Index</th> + <th>Name</th> + <th>Email Address</th> + <th>Organization</th> + {% if is_admin %} + <th>UID</th> + {% endif %} + </tr> + </thead> + <tbody> + {% for admin in admins %} + <tr> + <td style="text-align: center; padding: 0px 10px 2px 10px;"><input type="checkbox" name="admin_id" value="{{ admin.user_id }}"></td> + <td align="right">{{ loop.index }}</td> + <td>{% if 'full_name' in admin %}{{ admin.full_name }}{% elif 'name' in admin %}{{ admin.name }}{% else %}N/A{% endif %}</td> + <td>{% if 'email_address' in admin %}{{ admin.email_address }}{% else %}N/A{% endif %}</td> + <td>{% if 'organization' in admin %}{{ admin.organization }}{% else %}N/A{% endif %}</td> + {% if is_admin %} + <td>{{admin.user_id}}</td> + {% endif %} + </tr> + {% endfor %} + </tbody> + </table> + {% if is_admin %} + <div style="margin-top: 20px;"> + <span>E-mail of user to add to admins (multiple e-mails can be added separated by commas):</span> + <input type="text" size="60" name="admin_emails_to_add" placeholder="Enter E-mail(s)" value=""> + </div> + <div style="margin-bottom: 30px; margin-top: 20px;"> + <button type="button" id="add_admins" class="btn btn-primary" data-usertype="admin" data-url="{{ UPDATE_GROUP_URL }}">Add Admin(s)</button> + </div> + {% endif %} + </div> + <hr> + <div> + {% if members|length > 0 %} + <div><h2>Members</h2></div> + <hr> + <table id="group_members" class="table-hover table-striped cell-border" style="float: left;"> + <thead> + <tr> + <th></th> + <th>Index</th> + <th>Name</th> + <th>Email Address</th> + <th>Organization</th> + {% if is_admin %} + <th>UID</th> + {% endif %} + </tr> + </thead> + <tbody> + {% for member in members %} + <tr> + + <td style="text-align: center; padding: 0px 10px 2px 10px;"> + {% if is_admin %} + <input type="checkbox" name="member_id" value="{{ member.user_id }}"> + {% endif %} + </td> + <td align="right">{{ loop.index }}</td> + <td>{% if 'full_name' in member %}{{ member.full_name }}{% elif 'name' in admin %}{{ admin.name }}{% else %}N/A{% endif %}</td> + <td>{% if 'email_address' in member %}{{ member.email_address }}{% else %}N/A{% endif %}</td> + <td>{% if 'organization' in member %}{{ member.organization }}{% else %}N/A{% endif %}</td> + {% if is_admin %} + <td>{{ member }}</td> + {% endif %} + + </tr> + {% endfor %} + </tbody> + </table> + {% if is_admin %} + <div style="margin-top: 20px;"> + <span>E-mail of user to add to members (multiple e-mails can be added separated by commas):</span> + <input type="text" size="60" name="member_emails_to_add" placeholder="Enter E-mail(s)" value=""> + </div> + <div style="margin-bottom: 30px; margin-top: 20px;"> + <button type="button" id="add_members" class="btn btn-primary" data-usertype="member" data-url="{{ GROUP_URL }}">Add Member(s)</button> + </div> + {% endif %} + {% else %} + There are currently no members in this group. + {% if is_admin %} + <div style="margin-top: 20px;"> + <span>E-mail of user to add to members (multiple e-mails can be added separated by commas):</span> + <input type="text" size="60" name="member_emails_to_add" placeholder="Enter E-mail(s)" value=""> + </div> + <div style="margin-bottom: 30px; margin-top: 20px;"> + <button type="button" id="add_members" class="btn btn-primary" data-usertype="member" data-url="{{ GROUP_URL }}">Add Member(s)</button> + </div> + {% endif %} + {% endif %} + </div> + </div> + <div id="resources_div" class="col-xs-6" style="border-left: 1px solid #eee; margin-right: 30px; min-width: 600px; max-width: 800px;"> + <div style="margin-top: 20px;"><h2>Resources</h2></div> + <hr> + {% if resources|length > 0 %} + <table id="resources" class="table-hover table-striped cell-border" style="float: left;"> + <thead> + <tr> + <th>Index</th> + <th>Name</th> + <th>Data</th> + <th>Metadata</th> + <th>Admin</th> + </tr> + </thead> + <tbody> + {% for resource in resources %} + <tr> + <td align="right">{{ loop.index }}</td> + <td>{% if 'name' in resource %}<a href="/resources/manage?resource_id={{ resource.id }}">{{ resource.name }}</a>{% else %}N/A{% endif %}</td> + <td>{% if 'data' in resource %}{{ resource.data }}{% else %}N/A{% endif %}</td> + <td>{% if 'metadata' in resource %}{{ resource.metadata }}{% else %}N/A{% endif %}</td> + <td>{% if 'admin' in resource %}{{ resource.admin }}{% else %}N/A{% endif %}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% else %} + There are currently no resources associated with this group. + {% endif %} + </div> + </div> + </form> + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + + <script type="text/javascript" charset="utf-8"> + $(document).ready( function () { + $('#group_admins').dataTable({ + 'order': [[1, "asc" ]], + 'columns': [ + { "type": "natural", "width": "25px"}, + { "type": "natural", "width": "30px" }, + { "type": "natural", "width": "150px" }, + { "type": "natural" }, + { "type": "natural" } + ], + 'sDom': 'tr' + }); + {% if members|length > 0 %} + $('#group_members').dataTable({ + 'order': [[1, "asc" ]], + 'columns': [ + { "type": "natural", "width": "25px"}, + { "type": "natural", "width": "30px" }, + { "type": "natural", "width": "150px" }, + { "type": "natural" }, + { "type": "natural" } + ], + 'sDom': 'tr' + }); + {% endif %} + {% if resources|length > 0 %} + $('#resources').dataTable({ + 'order': [[0, "asc" ]], + 'columns': [ + { "type": "natural", "width": "30px" }, + { "type": "natural", "width": "150px" }, + { "type": "natural" }, + { "type": "natural" }, + { "type": "natural" } + ], + 'sDom': 'tr' + }); + {% endif %} + + $('#resources_div').css('height', $('#groups_div').css('height')) + + submit_special = function(url) { + $("#group_form").attr("action", url); + return $("#group_form").submit(); + }; + + $("#remove_users").on("click", function() { + url = $(this).data("url"); + admins = []; + $("input[name=admin_id]:checked").each(function() { + admins.push($(this).val()); + }); + admins_string = admins.join(":") + $("input[name=selected_admin_ids]").val(admins_string) + + members = []; + $("input[name=member_id]:checked").each(function() { + members.push($(this).val()); + }); + members_string = members.join(":") + $("input[name=selected_member_ids]").val(members_string) + return submit_special(url) + }); + + $("#add_admins, #add_members").on("click", function() { + url = $(this).data("url"); + console.log(url) + return submit_special(url) + }); + + $("#change_group_name").on("click", function() { + if ($('input[name=new_group_name]').css('display') == 'none') { + $('input[name=new_group_name]').css('display', 'inline'); + $('#group_name').css('display', 'none'); + } else { + new_name = $('input[name=new_group_name]').val() + $.ajax({ + type: "POST", + url: "{{ GROUP_URL }} ", + data: { + group_id: $('input[name=group_id]').val(), + new_name: new_name + } + }); + $('input[name=new_group_name]').css('display', 'none'); + $('input[name=group_name]').val(new_name); + $('#group_name').text(new_name) + $('#group_name').css('display', 'inline'); + } + }); + }); + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/authorisation_error.html b/gn2/wqflask/templates/authorisation_error.html new file mode 100644 index 00000000..3dce8b52 --- /dev/null +++ b/gn2/wqflask/templates/authorisation_error.html @@ -0,0 +1,19 @@ +{%extends "base.html"%} +{%block title%}{{error_type}}: ...{%endblock%} +{%block content%} +<div class="container"> + <div class="page-header"> + <h3>Access Error: {{error_type}}</h3> + </div> + <p> + <span class="glyphicon glyphicon-exclamation-sign text-danger"></span> + <strong>{{error_type}}:</strong> + <small class="text-danger">{{error.description}}</small> + </p> + <p> + Please contact the data's owner or GN administrators if you believe you + should have access to these data. + </p> +</div> + +{%endblock%} diff --git a/gn2/wqflask/templates/base.html b/gn2/wqflask/templates/base.html new file mode 100644 index 00000000..984cf92a --- /dev/null +++ b/gn2/wqflask/templates/base.html @@ -0,0 +1,397 @@ +{% from "base_macro.html" import header, flash_me, timeago %} +<!DOCTYPE HTML> +<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> + +<head> + <meta charset="utf-8"> + <title>{% block title %}{% endblock %} GeneNetwork 2</title> + <meta name="description" content=""> + <meta name="author" content=""> + <script type="text/javascript"> + var pageLoadStart = Date.now(); + </script> + <link rel="icon" type="image/png" sizes="64x64" href="/static/new/images/CITGLogo.png"> + <link rel="apple-touch-icon" type="image/png" sizes="64x64" href="/static/new/images/CITGLogo.png"> + <link REL="stylesheet" TYPE="text/css" href="{{ url_for('css', filename='bootstrap/css/bootstrap.css') }}" /> + <link REL="stylesheet" TYPE="text/css" href="/static/new/css/bootstrap-custom.css" /> + <link REL="stylesheet" TYPE="text/css" href="/static/new/css/non-responsive.css" /> + <link REL="stylesheet" TYPE="text/css" href="/static/new/css/docs.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/colorbox.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/parsley.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/broken_links.css" /> + <link rel="stylesheet" href="/static/new/css/autocomplete.css" /> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> + + {% block css %} + {% endblock %} + <style> + .form-rounded { + border-radius: 1rem; + } + table.dataTable thead .sorting_asc { + background-image: url({{ url_for("js", filename="DataTables/images/sort_asc_disabled.png") }}); + } + table.dataTable thead .sorting_desc { + background-image: url({{ url_for("js", filename="DataTables/images/sort_desc_disabled.png") }}); + } + + + .global_search_input{ + padding:9px 8px; + text-decoration: none; + border: none; + + border-radius: 5px; + } + + + .global_search_input:focus{ + outline: none; +} + + + +.btn-stc { + padding:9px 8px; + + border-left:none; + + border-radius:0 40px 40px 0; + + cursor: pointer; + + height: 40px; + width: 64px; + margin:0; + border:1px solid #d3d3d3; + background-color: white; + + position: absolute; + + top:0; + left: 100%; + + right: 0; + + border-left: none; + + + + + + +} + + +</style> +</head> + +<body style="width: 100%"> + <!-- Navbar ================================================== --> + <div class="navbar navbar-inverse navbar-static-top pull-left" role="navigation" style="width: 100%; min-width: 850px; white-space: nowrap;"> + <div class="container-fluid" style="width: 100%;"> + <!-- Collect the nav links, forms, and other content for toggling --> + <div> + <ul class="nav navbar-nav"> + <li class="" style="margin-right: 20px;"> + <a href="/" style="font-weight: bold;">GeneNetwork</a> + </li> + <li class=""> + <a href="/help" class="dropdow-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Intro <span class="caret"></a> + <ul class="dropdown-menu"> + <li><a href="/intro">Intro</a></li> + <li><a href="/submit_trait">Submit Trait</a></li> + <li><a href="http://genenetwork.org/webqtl/main.py?FormID=batSubmit">Batch Submission</a></li> + </ul> + </li> + <li class=""> + <a href="/help" class="dropdow-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Help <span class="caret"></a> + <ul class="dropdown-menu"> + <li><a href="{{ url_for('references_blueprint.references') }}">References</a></li> + <li><a href="/tutorials">Webinars, Tutorials/Primers</a></li> + <li><a href="{{ url_for('blogs_blueprint.blogs_list') }}">Blogs</a></li> + <li><a href="{{ url_for('glossary_blueprint.glossary') }}">Glossary of Term</a></li> + <li><a href="http://gn1.genenetwork.org/faq.html">FAQ</a></li> + <li><a href="{{ url_for('policies_blueprint.policies') }}">Policies</a></li> + <li><a href="{{ url_for('links_blueprint.links') }}">Links</a></li> + <li><a href="{{ url_for('facilities_blueprint.facilities') }}">Facilities</a></li> + <li><a href="{{ url_for('environments_blueprint.environments') }}">Environments</a></li> + <li><a href="/news">GN1 News</a></li> + </ul> + </li> + <li class=""> + <a href="/help" class="dropdow-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Tools <span class="caret"></a> + <ul class="dropdown-menu"> + <li><a href="/snp_browser">Variant Browser</a></li> + <li><a href="http://bnw.genenetwork.org/sourcecodes/home.php">Bayesian Network Webserver</a></li> + <li><a href="https://systems-genetics.org/">Systems Genetics PheWAS</a></li> + <li><a href="http://ucscbrowser.genenetwork.org/">Genome Browser</a></li> + <li><a href="http://power.genenetwork.org">BXD Power Calculator</a></li> + <li><a href="{{url_for('jupyter_notebooks.launcher')}}">Jupyter Notebooks</a></li> + <li><a href="https://files.genenetwork.org/current/">Public Datasets</a></li> + </ul> + </li> + {% if g.user_session %} + <li class=""> + <a href="/collections/list">Collections + <span class="badge badge-info">{{num_collections()}}</span> + </a> + </li> + <li class=""> + <a href="/repositories" class="dropdow-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Source Code <span class="caret"></a> + <ul class="dropdown-menu"> + <li><a href="https://github.com/genenetwork/genenetwork2">GN2 Source Code</a></li> + <li><a href="https://github.com/genenetwork/genenetwork">GN1 Source Code</a></li> + <li><a href="https://github.com/genenetwork/sysmaintenance">System Maintenance Code</a></li> + <li><a href="https://github.com/genenetwork/genenetwork2/blob/testing/doc/API_readme.md">REST API Documentation</a></li> + </ul> + </li> + {% if g.user_session.logged_in %} + <li class=""> + <a href="/edit_account_settings" class="dropdow-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{% if g.user_session.user_email %}{{ g.user_session.user_email }}{% else %}User Account Settings{% endif %}<span class="caret"></a> + <ul class="dropdown-menu"> + <li><a id="manage_user" title="User Options" href="/user/manage">User Options</a></li> + <li><a id="manage_groups" title="Manage Groups" href="/group-management/groups">Manage Groups</a></li> + </ul> + </li> + {% endif %} + <li class=""> + {%if logged_in()%} + {%if user_details is mapping%} + {%set user_dets = user_details%} + {%else%} + {%set user_dets = user_details()%} + {%endif%} + <a id="login_out" + title="Signed in as {{user_dets.name}}({{user_dets.email}})" + href="{{url_for('oauth2.user.logout')}}">Sign out</a> + {%else%} + <a id="login_in" href="{{authserver_authorise_uri()}}">Sign in</a> + {%endif%} + </li> + {% endif %} + + {%if not logged_in()%} + <li class=""> + <a id="user:register" title="Register a new user account." + href="{{url_for('oauth2.user.register_user')}}"> + Register + </a> + </li> + {%endif%} + + {%if logged_in()%} + <li class=""> + <a id="user:profile" title="User's profile page." + href="{{url_for('oauth2.user.user_profile')}}"> + Profile + </a> + </li> + {%endif%} + <!-- + <li style="margin-left: 20px;"> + <a href="http://gn2-staging.genenetwork.org" style="font-weight: bold;" >Use Staging Branch</a> + </li> + --> + </ul> + </div> + </div> + </div> + <div class="container-fluid" style="width: 100%; min-width: 650px; position: relative;background-color: #d5d5d5; height: 100px;"> + <form id="gnqna_search_home" method="POST" action="/gnqna" style="display:none;width: 100%;"> + + <div > + <input id="gnqna_search_home_input" style="width:45vw" type="text" autocomplete="off" +required placeholder="Ask More Questions or Topics (E.g Genes) " value='' name="querygnqa"> + </div> + </form> + <form method="get" action="/gsearch" id="globalsearchform"> + <div class="form-group"> + <div class="controls"> + <select name="type"> + <option value="gene">Genes / Molecules</option> + <option value="phenotype" {% if type=="phenotype" %}selected{% endif %}>Phenotypes</option> + </select> + <div class="col-8 autocomplete" style="margin-left: 30px;margin-right: 10px;"> + <input autocomplete="off" class="global_search_input" id="term" style="width:45vw" type="text" required placeholder="Enter you search term here (ex: cytochrome AND P450)" name="terms"> + + <button type="submit" class="btn-stc" style="position: absolute; background-color: #336699"><i class="fa fa-search" title="Search " style="color:white;"></i></button> + </div> + + + + <!-- todo fix text overlap for this;;responsiveness--> + + <span style="padding: 5px;margin-left: 65px;" id="gnqna_home"> + <a href="/gnqna">GNQNA Search</a> + </span> + + <span style="padding: 5px;margin-left: 65px;" > + <a style="text-decoration: none" target="_blank" href="https://issues.genenetwork.org/topics/xapian/xapian-search-queries"> + <i style="text-align: center;color:#336699;;" class="fa fa-question-circle fa-2x" title="see more search hints" aria-hidden="true"></i> + </a> + + </span> + + </div> + </div> + </form> + + </div> + {% block content %} + {% endblock %} + <footer class="footer"> + <div class="row" style="margin: 10px; width: 100%; min-width: 1200px;"> + <div class="col-xs-6"> + <p>Web services initiated January, 1994 as + <a href="http://www.ncbi.nlm.nih.gov/pubmed?term=8043953"> + The Portable Dictionary of the Mouse Genome + </a>; June 15, 2001 as <a href="https://www.ncbi.nlm.nih.gov/pubmed/15043217">WebQTL</a>; and Jan 5, 2005 as GeneNetwork. + </p> + <p> + This site is currently operated by + <a href="mailto:rwilliams@uthsc.edu">Rob Williams</a>, + <a href="https://thebird.nl/">Pjotr Prins</a>, + <a href="http://www.senresearch.org">Saunak Sen</a>, + <a href="mailto:zachary.a.sloan@gmail.com">Zachary Sloan</a>, + <a href="mailto:acenteno@uthsc.edu">Arthur Centeno</a>, + and <a href="mailto:bonfacemunyoki@gmail.com">Bonface Munyoki</a>. + </p> + <p>Design and code by Pjotr Prins, Shelby Solomon, Zach Sloan, Arthur Centeno, Christan Fischer, Bonface Munyoki, Danny Arends, Arun Isaac, Alex Mwangi, Fred Muriithi, Sam Ockman, Lei Yan, Xiaodong Zhou, Christian Fernandez, + Ning Liu, Rudi Alberts, Elissa Chesler, Sujoy Roy, Evan G. Williams, Alexander G. Williams, Kenneth Manly, Jintao Wang, Robert W. Williams, and + <a href="/credits">colleagues</a>.</p> + <br /> + <p>GeneNetwork support from:</p> + <ul> + <li> + <a href="http://citg.uthsc.edu"> + The UT Center for Integrative and Translational Genomics + </a> + </li> + <li> + <a href="https://www.nigms.nih.gov/">NIGMS</a> + Systems Genetics and Precision Medicine Project (R01 GM123489, 2017-2026) + </li> + <li> + <a href="https://www.nsf.gov/awardsearch/showAward?AWD_ID=2118709">NSF</a> + Panorama: Integrated Rack-Scale Acceleration for Computational Pangenomics + (PPoSS 2118709, 2021-2016) + </li> + <li> + <a href="https://www.drugabuse.gov/">NIDA</a> + NIDA Core Center of Excellence in Transcriptomics, Systems Genetics, and the Addictome (P30 DA044223, 2017-2022) + </li> + <li> + <a href="http://www.nia.nih.gov/">NIA</a> + Translational Systems Genetics of Mitochondria, Metabolism, and Aging (R01AG043930, 2013-2018) + </li> + <li> + <a href="https://www.ohsu.edu/iniastress-consortium">NIAAA</a> + Integrative Neuroscience Initiative on Alcoholism (U01 AA016662, U01 AA013499, U24 AA013513, U01 AA014425, 2006-2017) + </li> + <li> + <a href="http://www.drugabuse.gov/">NIDA</a>, <a href="http://www.nimh.nih.gov/">NIMH</a>, and <a href="http://www.niaaa.nih.gov/">NIAAA</a> + (P20-DA 21131, 2001-2012) + </li> + <li> + NCI <a href="http://emice.nci.nih.gov/">MMHCC</a> (U01CA105417), NCRR, <span class="broken_link test" href="http://www.birncommunity.org/">BIRN</span>, (U24 RR021760) + </li> + </UL> + <p>Published in + <a href="http://joss.theoj.org/papers/10.21105/joss.00025"><img src="https://camo.githubusercontent.com/846b750f582ae8f1d0b4f7e8fee78bed705c88ba/687474703a2f2f6a6f73732e7468656f6a2e6f72672f7061706572732f31302e32313130352f6a6f73732e30303032352f7374617475732e737667" alt="JOSS" data-canonical-src="http://joss.theoj.org/papers/10.21105/joss.00025/status.svg" style="max-width:100%;"></a> + </p> + <p> + Development and source code on <a href="https://github.com/genenetwork/">github</a> with <a href="https://issues.genenetwork.org/">issue tracker</a> and <a href="https://github.com/genenetwork/genenetwork2/blob/master/README.md">documentation</a>. + {% if version: %} + <p><small>GeneNetwork {{ version }}</small></p> + {% endif %} + <p> It took the server {{ g.request_time() }} seconds to process this page.</p> + </div> + <div class="col-xs-2"> + <a href="http://www.python.org/" target="_blank"> + <img src="/static/new/images/PythonLogo.png" alt="Python Powered" border="0"> + </a> + <a href="http://www.neuinfo.org" target="_blank"> + <img src="/static/new/images/Nif.png" alt="Registered with Nif" border="0"> + </a> + </div> + </div> + </footer> + <!--http://stackoverflow.com/questions/14045515/how-can-i-reuse-one-bootstrap-modal-div--> + <!-- Modal --> + <div id="utility" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> + <div class="modal-body"> + <p>.</p> + </div> + </div> + <script src="{{ url_for('js', filename='jquery/jquery.min.js') }}" type="text/javascript"></script> + <script src="{{ url_for('js', filename='bootstrap/js/bootstrap.min.js') }}" type="text/javascript"></script> + <script src="/static/new/javascript/search_autocomplete.js"></script> + <script> + //http://stackoverflow.com/questions/11521763/bootstrap-scrollspy-not-working + var $window = $(window) + $('.bs-docs-sidenav').affix({ + offset: { + top: function() { return $window.width() <= 980 ? 290 : 210 }, + bottom: 270 + } + }) + </script> + <script type="text/javascript"> + $(document).ready(function() { + + const urlParams = new URLSearchParams(window.location.search) + let term = urlParams.get("terms") + + //should web scrap + var global_search_hint = [ + "cytochrome", + "cytochrome AND P450", + "cytochrome NEAR P450", + "cytochrome -P450", + "cytochrome NOT P450", + "species:human", + "group:BXD", + "Hs:chr4:9930021 species:mouse", + "Hs:chr4:9130000..9980000 species:mouse", + "mean:5..7", + "mean:7..", + "Hs:chr4:9930021", + "Hs:chr4:9930021 species:mouse", + "Hs:chr4:9130000..9980000 species:mouse", + "bx*", + "*", + ] + autocomplete(document.getElementById("term"), global_search_hint); + $("#term").keyup(function(event) { + if (event.keyCode === 13) { + event.preventDefault(); + $('#globalsearchform').attr('action', "/gsearch").submit(); + if ($("#term").val().trim() != "") { + saveBeforeSubmit($("#term").val().trim()) + $("#globalsearchform")[0].submit(); + } + + } + }) + + }); + </script> + <script src="{{ url_for('js', filename='jquery-cookie/jquery.cookie.js') }}" type="text/javascript"></script> + <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='colorbox/jquery.colorbox-min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/parsley.min.js') }}"></script> + {% block js %} + {% endblock %} + <script type="text/javascript"> + document.addEventListener('DOMContentLoaded', function() { + let timeToLoad = document.createElement("p"); + timeToLoad.innerHTML = "It took your browser " + ((Date.now() - pageLoadStart) / 1000) + " second(s) to render this page"; + document.querySelector("footer .row .col-xs-6").appendChild(timeToLoad); + + + }); + </script> +</body> + +</html> diff --git a/gn2/wqflask/templates/base_macro.html b/gn2/wqflask/templates/base_macro.html new file mode 100644 index 00000000..7fcb6fe7 --- /dev/null +++ b/gn2/wqflask/templates/base_macro.html @@ -0,0 +1,28 @@ +{% macro flash_me() -%} + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + <div class="container"> + {% for category, message in messages %} + <div class="alert {{ category }}" role="alert">{{ message }}</div> + {% endfor %} + </div> + {% endif %} + {% endwith %} +{% endmacro %} + +{% macro header(main, second) %} + <header class="jumbotron subhead" id="overview" > + <div class="container"> + <h1>{{ main }}</h1> + <p class="lead"> + {{ second }} + </p> + </div> + </header> + + {{ flash_me() }} +{% endmacro %} + +{% macro timeago(timestamp) %} +<time class="timeago" datetime="{{ timestamp }}">{{ timestamp }}</time> +{% endmacro %} diff --git a/gn2/wqflask/templates/blogs.html b/gn2/wqflask/templates/blogs.html new file mode 100644 index 00000000..314eb733 --- /dev/null +++ b/gn2/wqflask/templates/blogs.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% block title %}Blogs {% endblock %} +{% block css %} +<link rel="stylesheet" type="text/css" href="/static/new/css/markdown.css" /> +{% endblock %} +{% block content %} +<div class="github-btn-container"> +</div> +<div id="markdown" class="container"> + {{ rendered_markdown|safe }} +</div> +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/blogs_list.html b/gn2/wqflask/templates/blogs_list.html new file mode 100644 index 00000000..6bad4628 --- /dev/null +++ b/gn2/wqflask/templates/blogs_list.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} +{% block title %}Blogs {% endblock %} +{% block css %} +<style type="text/css"> +.container { + height: 100vh; +} + +.blog_year { + font-weight: bold; + font-size: 48px; + padding: 12px 10px; +} + +.blog_title { + padding: 10px 15px; + +} + + + +.blog_title a { + font-size: 1.3em; + padding-left: 10px; + letter-spacing: 0.07em; + text-decoration: underline; +} +</style> +<link rel="stylesheet" type="text/css" href="/static/new/css/markdown.css" /> +{% endblock %} +{% block content %} +<div id="markdown" class="container"> + <div> + {% for year, year_blogs in blogs.items() %} + <div class="blog_year"> + <h3>{{year}}</h3> + </div> + {%for blog in year_blogs%} + <div> + <div class="blog_title"> + <ul> + <li> + <a href="{{ url_for('blogs_blueprint.display_blog',blog_path = blog.full_path)}}">{{blog['subtitle']}}</a> + </li> + </ul> + </div> + </div> + {% endfor %} + {%endfor%} + </div> +</div> +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/bnw_page.html b/gn2/wqflask/templates/bnw_page.html new file mode 100644 index 00000000..317b4bd7 --- /dev/null +++ b/gn2/wqflask/templates/bnw_page.html @@ -0,0 +1,7 @@ +<title>Opening BNW</title> +<form method="post" action="https://bnw.genenetwork.org/sourcecodes/bn_genenet.php" name="bnwform" id="bnwform"> + <input type="hidden" name="My_Genenet" value="{{ form_value }}"> +</form> +<script type="text/javascript"> + document.bnwform.submit(); +</script> diff --git a/gn2/wqflask/templates/case_attributes.html b/gn2/wqflask/templates/case_attributes.html new file mode 100644 index 00000000..d1669761 --- /dev/null +++ b/gn2/wqflask/templates/case_attributes.html @@ -0,0 +1,415 @@ +{% extends "base.html" %} +{% block title %}Trait Submission{% endblock %} + +{% block css %} +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> +<style> + .reject, .approve { + margin-bottom: 5px; + } +.case-attribute { + width: 10em; + } + .table-title { + padding-bottom: 10px; + margin: 0 0 10px; + } + + .table-title h2 { + margin: 6px 0 0; + font-size: 22px; + } + .table-title .add-new { + float: right; + height: 30px; + font-weight: bold; + font-size: 12px; + text-shadow: none; + min-width: 100px; + border-radius: 50px; + line-height: 13px; + } + .table-title .add-new i { + margin-right: 4px; + } + table.table { + table-layout: fixed; + width: 70%; + } + table.table tr th, table.table tr td { + border-color: #e9e9e9; + } + table.table th i { + font-size: 13px; + margin: 0 5px; + cursor: pointer; + } + table.table th:last-child { + width: 100px; + } + table.table td a { + cursor: pointer; + display: inline-block; + margin: 0 5px; + min-width: 24px; + } + table.table td a.add { + color: #27C46B; + } + table.table td a.edit { + color: #FFC107; + } + table.table td a.delete { + color: #E34724; + } + table.table td i { + font-size: 19px; + } + table.table td a.add i { + font-size: 24px; + margin-right: -1px; + position: relative; + top: 3px; + } + table.table .form-control { + height: 32px; + line-height: 32px; + box-shadow: none; + border-radius: 2px; + } + table.table .form-control.error { + border-color: #f50000; + } + table.table td .add { + display: none; + } +</style> +{% endblock %} + +{% block content %} +<!-- Start of body --> +<div class="container"> + <div class="table-wrapper"> + <div class="table-title"> + <div class="row"> + <div class="col-sm-8"> + <h2>Case Attributes Reference Table</h2> + </div> + </div> + </div> + <table class="table table-bordered table-responsive"> + <colgroup> + <col class="case-attribute"> + <col> + <col> + </colgroup> + <thead> + <th scope="col">Case Attribute</th> + <th scope="col">Description</th> + <th scope="col">Actions</th> + </thead> + <tbody> + {% for id_, name, description in case_attributes %} + <tr data-id="{{id_}}"> + <td class="name" data-original-value="{{ name }}">{{ name }}</td> + <td class="description" data-original-value="{{ description }}">{{ description }}</td> + <td> + <a class="add" title="Add" data-toggle="tooltip"><i><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></i></a> + <a class="edit" title="Edit" data-toggle="tooltip"><i><span class="glyphicon glyphicon-edit" aria-hidden="true"></span></i></a> + <a class="delete" title="Delete" data-toggle="tooltip"><i><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></i></a> + </td> + </tr> + {% endfor %} + <tr id="addNew"> + <td></td> + <td></td> + <td><button type="button" class="btn btn-info add-new"><i class="fa fa-plus"></i> Add New</button></td> + </tr> + </tbody> + </table> + + {% if modifications or inserts or deletions %} + <h2>Please Review These Changes</h2> + {% endif %} + {% if modifications %} + <h3>Modify Existing Case Attributes</h3> + <table class="table table-responsive table-hover table-striped cell-border" id="table-modifications"> + <thead> + <th scope="col">Author</th> + <th scope="col">Diff</th> + <th scope="col">Action</th> + </thead> + <tbody> + {% for data in modifications %} + <tr> + <td>{{ data.get('author') }}</td> + <td> + {% if data.get("name")%} + <b>Name:</b><br/> + <small>Original: </small>{{ data["name"].get("Original") }}<br/> + <small>Current: </small>{{ data["name"].get("Current") }} <br/> + <small>Diff:</small><br/> + <pre>{{data["name"].get("Diff")}}</pre> + {% endif %} + {% if data.get("description")%} + <b>Description:</b><br/> + <small>Original: </small>{{ data["description"].get("Original") }}<br/> + <small>Current: </small>{{ data["description"].get("Current") }} <br/> + <small>Diff:</small><br/> + <pre>{{data["description"].get("Diff")}}</pre> + {% endif %} + </td> + <td> + <button data-id="{{ data.get('id') }}" type="button" + class="btn btn-default reject"> + Reject + </button> + <button data-id="{{ data.get('id') }}" type="button" + class="btn btn-success approve"> + Approve + </button> + </td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} + {% if deletions %} + <h3>Delete Existing Case Attributes</h3> + <table class="table table-responsive table-hover table-striped cell-border" id="table-modifications"> + <colgroup> + <col class="case-attribute"> + <col> + <col> + <col> + </colgroup> + <thead> + <th scope="col">Case Attribute</th> + <th scope="col">Author</th> + <th scope="col">Description</th> + <th scope="col">Action</th> + </thead> + <tbody> + {% for data in deletions %} + <tr> + <td>{{data.get("name")}}</td> + <td>{{ data.get('author') }}</td> + <td>{{data.get("description")}}</td> + <td> + <button data-id="{{data.get('id')}}" type="button" + class="btn btn-default reject"> + Reject + </button> + <button data-id="{{data.get('id')}}" type="button" class="btn btn-success approve"> + Approve + </button> + </td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} + + {% if inserts %} + <h3>Insert New Case Attributes</h3> + <table class="table table-responsive table-hover table-striped cell-border" id="table-modifications"> + <colgroup> + <col class="case-attribute"> + <col> + <col> + <col> + </colgroup> + <thead> + <th scope="col">Case Attribute</th> + <th scope="col">Author</th> + <th scope="col">Description</th> + <th scope="col">Action</th> + </thead> + <tbody> + {% for data in inserts %} + <tr> + <td>{{ data.get("name") }}</td> + <td>{{ data.get("author")}}</td> + <td>{{ data.get("description") }} </td> + <td> + <button data-id="{{ data.get('id') }}" type="button" + class="btn btn-default reject"> + Reject + </button> + <button data-id="{{ data.get('id') }}" type="button" class="btn btn-success approve"> + Approve + </button> + </td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} + </div> + {%endblock%} + + {% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script> + <script language="javascript" type="text/javascript"> + gn_server_url = "{{ gn_server_url }}"; + + $(document).ready( function() { + let actions = $("table td:last-child").html(); + // Append table with add row form on add new button click + $(".add-new").click(function(){ + $(this).attr("disabled", "disabled"); + let index = $("table tbody tr:nth-last-child(2)").index(); + let row = '<tr>' + + '<td class="name"><input type="text" class="form-control"></td>' + + '<td class="description"><input type="text" class="form-control"></td>' + + '<td>' + actions + '</td>' + + '</tr>'; + $(row).insertBefore("#addNew") + $("table tbody tr").eq(index + 1).find(".add, .edit").toggle(); + }); + // Add row on add button click + $(document).on("click", ".add", function(){ + let empty = false; + let input = $(this).parents("tr").find('input[type="text"]'); + input.each(function(){ + if(!$(this).val()){ + $(this).addClass("error"); + empty = true; + } else{ + $(this).removeClass("error"); + } + }); + $(this).parents("tr").find(".error").first().focus(); + if(!empty){ + input.each(function(){ + $(this).parent("td").html($(this).val()); + }); + $(this).parents("tr").find(".add, .edit").toggle(); + $(".add-new").removeAttr("disabled"); + } + else { + return; + } + + let nameNode = $(this).parents("tr").find(".name"); + let descNode = $(this).parents("tr").find(".description"); + let name = nameNode.html(); + let desc = descNode.html(); + let nodeId = $(this).parents("tr").data("id"); + let originalName = nameNode.data("original-value"); + let originalDesc = descNode.data("original-value"); + let diff = {}; + if (nodeId) { + if (name !== originalName) { + diff["Modification"] = { + name: { + Original: originalName, + Current: name, + }}; + } + if (desc !== originalDesc) { + let desc_ = { + Original: originalDesc, + Current: desc, + } + if (Object.keys(diff).length == 0) { + diff["Modification"] = { + description: desc_ + } + } + else { + diff["Modification"].description = desc_; + } + } + if (!Object.keys(diff).length == 0) { + diff["id"] = nodeId; + } + } else { + if (name) { + diff["Insert"] = { + name: name, + } + } + if (desc) { + if (Object.keys(diff).length == 0) { + diff["Insert"] = { + description: desc, + } + } + else { + diff["Insert"].description = desc; + } + } + } + if(diff) { + $.ajax({ + type: "POST", + data: {"data": JSON.stringify(diff)}, + url: '{{ url_for("metadata_edit.update_case_attributes") }}', + /* contentType: "application/json", */ + success: function(data, status, xhr) { location.reload() } + } + ) + } + }); + // Edit row on edit button click + $(document).on("click", ".edit", function(){ + $(this).parents("tr").find("td:not(:last-child)").each(function(){ + $(this).html('<input type="text" class="form-control" value="' + $(this).text() + '">'); + }); + $(this).parents("tr").find(".add, .edit").toggle(); + $(".add-new").attr("disabled", "disabled"); + }); + + // Delete row on delete button click + $(document).on("click", ".delete", function(){ + $(this).parents("tr").remove(); + $(".add-new").removeAttr("disabled"); + let node = $(this).parents("tr") + + let diff = {}; + let nodeId = $(this).parents("tr").data("id"); + diff["Deletion"] = { + name: node.find(".name").data("original-value"), + description: node.find(".description").data("original-value"), + id: node.data("id")} + if (nodeId){ + $.ajax({ + type: "POST", + data: {"data": JSON.stringify(diff)}, + url: '{{ url_for("metadata_edit.update_case_attributes") }}', + success: function(data, status, xhr) { + location.reload(); + } + })} + }); + }); + + $(".reject").click(function(){ + let id = $(this).data("id"); + if (Number.isInteger(id)) { + $.ajax({ + type: "POST", + data: {"id": id}, + url: '{{ url_for("metadata_edit.reject_case_attribute_data") }}', + success: function(data, status, xhr) { + location.reload(); + } + }) + } + }); + $(".approve").click(function(){ + let id = $(this).data("id"); + if (Number.isInteger(id)) { + $.ajax({ + type: "POST", + data: {"id": id}, + url: '{{ url_for("metadata_edit.approve_case_attribute_data") }}', + success: function(data, status, xhr) { + location.reload(); + } + }) + } + }); + </script> + {% endblock %} diff --git a/gn2/wqflask/templates/collections/add.html b/gn2/wqflask/templates/collections/add.html new file mode 100644 index 00000000..478c80fb --- /dev/null +++ b/gn2/wqflask/templates/collections/add.html @@ -0,0 +1,86 @@ +<div id="myModal"> + <div class="modal-header"> + <h2>Define or Add to Collection</h2> + <p>You have two choices: Name a new collection + or add to an existing collection.</p> + </div> + <div class="modal-body" style="margin-left: 20px;"> + <form action="/collections/new" + method="POST" + target="_blank" + data-validate="parsley" + id="add_form" + class="form-inline"> + {% if traits is defined %} + <input type="hidden" name="traits" value="{{ traits }}" /> + {% else %} + <input type="hidden" name="hash" value="{{ hash }}" /> + {% endif %} + {% if collections|length > 0 %} + <fieldset> + <legend>1. Add to an existing collection</legend> + <div style="margin-left: 20px;"> + <select name="existing_collection" class="form-control" style="width: 80%;"> + {% for col in collections %} + {% if loop.index == 1 %} + <option value="{{ col.id }}:{{ col.name }}" selected>{{ col.name }}</option> + {% else %} + <option value="{{ col.id }}:{{ col.name }}">{{ col.name }}</option> + {% endif %} + {% endfor %} + </select> + <input type="button" style="display: inline;" id="make_default" value="Make Default"> + <br><br> + <button type="submit" name="add_to_existing" class="btn btn-primary">Add</button> + </div> + </fieldset> + {% endif %} + <hr /> + <fieldset> + <legend>{% if collections|length > 0 %}2. {% else %}{% endif %}Create a new collection</legend> + <div style="margin-left: 20px;"> + <input type="text" name="new_collection" placeholder=" Name of new collection..." + data-trigger="change" data-minlength="5" data-maxlength="50" style="width: 100%"> + <button type="submit" name="create_new" class="btn btn-primary" style="margin-top: 20px;">Create collection</button> + {% if uc is not defined %} + <span class="help-block">This collection will be saved to your computer for a year (or until you clear your cache).</span> + {% endif %} + </div> + </fieldset> + </form> + </div> +</div> + +<script> + $('#add_form').parsley(); + $('#add_form').on('submit', function(){ + parent.jQuery.colorbox.close(); + }); + + make_default = function() { + alert("The current collection is now your default collection.") + let uc_id = $('[name=existing_collection] option:selected').val().split(":")[0] + $.cookie('default_collection', uc_id, { + expires: 365, + path: '/' + }); + + let default_collection_id = $.cookie('default_collection'); + }; + + $("#make_default").on("click", function(){ + make_default(); + }); + + apply_default = function() { + let default_collection_id = $.cookie('default_collection'); + if (default_collection_id) { + let the_option = $('[name=existing_collection] option').filter(function() { + return ($(this).val().split(":")[0] == default_collection_id); + }) + the_option.prop('selected', true); + } + } + + apply_default(); +</script> diff --git a/gn2/wqflask/templates/collections/add_anonymous.html b/gn2/wqflask/templates/collections/add_anonymous.html new file mode 100644 index 00000000..2eb7525f --- /dev/null +++ b/gn2/wqflask/templates/collections/add_anonymous.html @@ -0,0 +1,21 @@ +<div id="myModal"> + <div class="modal-header"> + <h3>Add to collection</h3> + <br /> + <div> + <p>We can add this to a temporary collection for you.</p> + <p>For the most features you'll want to sign in or create an account.</p> + </div> + </div> + <div class="modal-body"> + <form action="/collections/new" data-validate="parsley" id="add_form"> + <input type="hidden" name="traits" value="{{ traits }}" /> + <button type="submit" name="anonymous_add" class="btn btn-large btn-block btn-primary">Continue without signing in</button> + <button type="submit" name="sign_in" class="btn btn-large btn-block">Sign in or create an account</button> + </form> + </div> +</div> + +<script> + $('#add_form').parsley(); +</script> diff --git a/gn2/wqflask/templates/collections/list.html b/gn2/wqflask/templates/collections/list.html new file mode 100644 index 00000000..c553717f --- /dev/null +++ b/gn2/wqflask/templates/collections/list.html @@ -0,0 +1,189 @@ +{% extends "base.html" %} +{%from "oauth2/display_error.html" import display_error%} +{% block title %}Your Collections{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('js', filename='DataTablesExtensions/buttonsBootstrap/css/buttons.bootstrap.css') }}" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{% endblock %} +{% block content %} +<!-- Start of body --> + <div class="container"> + {{flash_me()}} + + {% if g.user_session.logged_in %} + <h1>Collections owned by {% if g.user_session.user_name %}{{ g.user_session.user_name }}{% else %}{{ g.user_session.user_email }} {% endif %}</h1> + {% else %} + <h1>Your Collections</h1> + {% endif %} + + <div> + <button class="btn btn-default" id="select_all"><span class="glyphicon glyphicon-ok"></span> Select All</button> + <button class="btn btn-default" id="deselect_all"><span class="glyphicon glyphicon-remove"></span> Deselect All</button> + <button class="btn btn-default" id="invert"><span class="glyphicon glyphicon-resize-vertical"></span> Invert</button> + <button class="btn btn-danger" id="remove_collections" data-url="/collections/delete">Remove Collections</button> + <input type="text" id="searchbox" class="form-control" style="width: 200px; display: inline;" placeholder="Search This Table For ..."> + <input type="text" id="select_top" class="form-control" style="width: 200px; display: inline;" placeholder="Select Top ..."> + <br> + <form id="collections_form" action="javascript:void(0);" method="post" enctype="multipart/form-data"> + <input type="hidden" name="uc_id" id="uc_id" value="" /> + <div style="margin-top: 10px; display: inline-block;"><button class="btn btn-default" id="import_collection" data-url="/collections/import"> Import Collection</button> <input type="file" name="import_file" id="import_file" size="20" style="display: inline-block;"></input></div> + </form> + </div> + <br> + <div id="collections_list" style="width:50%; margin-top: 10px; margin-bottom: 10px;"> + {%if anon_collections_error is defined%} + {{display_error("Anonymous Collections", anon_collections_error)}} + {%endif%} + {%if anon_collections | length > 0%} + <table class="table-hover table-striped cell-border" id='anon_trait_table' + style="float: left;"> + <caption>Anonymous Collections</caption> + <thead> + <tr> + <th></th> + <th>Index</th> + <th>Name</th> + <th>Created</th> + <th>Last Changed</th> + <th># Records</th> + </tr> + </thead> + + <tbody> + {% for uc in anon_collections %} + <tr class="collection_line"> + <td align="center" style="padding: 0px;"><INPUT TYPE="checkbox" NAME="collection" class="checkbox trait_checkbox" VALUE="{{ uc.id }}"></td> + <td align="right">{{ loop.index }} + <td><a class="collection_name" href="{{ url_for('view_collection', uc_id=uc.id) }}">{{ uc.name }}</a></td> + <td>{{ uc.created_timestamp }}</td> + <td>{{ uc.changed_timestamp }}</td> + <td align="right">{{ uc.num_members }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {%endif%} + {% if collections|length > 0 %} + {% if (anon_collections | length > 0) and (collections | length > 0) %} + <hr /> + {%endif%} + <table class="table-hover table-striped cell-border" id='trait_table' style="float: left;"> + <caption>User Collections</caption> + <thead> + <tr> + <th></th> + <th>Index</th> + <th>Name</th> + <th>Created</th> + <th>Last Changed</th> + <th># Records</th> + </tr> + </thead> + + <tbody> + {% for uc in collections %} + <tr class="collection_line"> + <td align="center" style="padding: 0px;"><INPUT TYPE="checkbox" NAME="collection" class="checkbox trait_checkbox" VALUE="{{ uc.id }}"></td> + <td align="right">{{ loop.index }} + <td><a class="collection_name" href="{{ url_for('view_collection', uc_id=uc.id) }}">{{ uc.name }}</a></td> + <td>{{ uc.created_timestamp }}</td> + <td>{{ uc.changed_timestamp }}</td> + <td align="right">{{ uc.num_members }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% else %} + You have no collections yet. + {% endif %} + </div> + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/timeago.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/md5.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='jszip/jszip.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/natural.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/buttons/js/dataTables.buttons.min.js') }}"></script> + <script type="text/javascript" src="/static/new/javascript/search_results.js"></script> + <script type="text/javascript" src="/static/new/javascript/table_functions.js"></script> + <script type="text/javascript" src="/static/new/javascript/create_datatable.js"></script> + <script> + $(document).ready( function () { + tableId = "trait_table"; + + columnDefs = [ + { "type": "natural", "width": "3%", "targets": 0, "orderable": false}, + { "type": "natural", "width": "8%", "targets": 1}, + { "type": "natural", "width": "20%", "targets": 2}, + { "type": "natural", "width": "25%", "targets": 3}, + { "type": "natural", "width": "25%", "targets": 4}, + { "type": "natural", "width": "15%", "targets": 5} + ]; + + create_table("anon_trait_table", [], columnDefs) + create_table(tableId, [], columnDefs) + + submit_special = function(url) { + $("#collections_form").attr("action", url); + return $("#collections_form").submit(); + }; + + add_collection = function(trait_data, textStatus, jqXHR) { + var traits_hash = md5(trait_data.toString()); + + $.ajax({ + type: "POST", + url: "/collections/store_trait_list", + data: { + hash: traits_hash, + traits: trait_data.toString() + } + }); + + return $.colorbox({ + href: "/collections/add?hash=" + traits_hash + }); + } + + $("#import_collection").on("click", function() { + var fd = new FormData(); + var files = $('#import_file')[0].files; + if(files.length > 0){ + fd.append('import_file', files[0]) + $.ajax({ + type: "POST", + url: "/collections/import", + data: fd, + dataType: "json", + contentType: false, + processData: false, + success: add_collection + }); + } else { + alert("No file selected") + } + }); + + + $("#remove_collections").on("click", function() { + url = $(this).data("url") + collections = [] + $(".trait_checkbox:checked").each(function() { + collections.push($(this).val()); + }); + collections_string = collections.join(":") + $("input[name=uc_id]").val(collections_string) + return submit_special(url) + }); + + }); + + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/collections/not_logged_in.html b/gn2/wqflask/templates/collections/not_logged_in.html new file mode 100644 index 00000000..49b0e07d --- /dev/null +++ b/gn2/wqflask/templates/collections/not_logged_in.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% block title %}Your Collections{% endblock %} +{% block content %} +<!-- Start of body --> + {{ header("Not logged in") }} + + + <div id="collections_holder" class="container"> + <div class="page-header"> + <h1>Please log in in order to use this feature.</h1> + </div> + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/timeago.min.js') }}"></script> + <script> + $('body').timeago(); + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/collections/remove.html b/gn2/wqflask/templates/collections/remove.html new file mode 100644 index 00000000..faee4f78 --- /dev/null +++ b/gn2/wqflask/templates/collections/remove.html @@ -0,0 +1,48 @@ +<div id="myModal"> + <div class="modal-header"> + <h3>Add to collection</h3> + <p>You have three choices: Use your default collection, create a new named collection, + or add the traits to an existing collection.</p> + </div> + <div class="modal-body"> + <form action="/collections/new" data-validate="parsley" id="add_form"> + <fieldset> + <legend>Use your default collection</legend> + <span class="help-block">Choose this if you're in a hurry or don't plan on using the collection again.</span> + <span class="help-block"><em></em>If you are unsure this is probably the option you want.</em></span> + <button type="submit" name="Default" class="btn">Continue</button> + </fieldset> + <hr /> + + + <input type="hidden" name="traits" value="{{ traits }}" /> + <fieldset> + <legend>Or create a new named collection</legend> + <label>New collection name</label> + <input type="text" name="new_collection" placeholder="Name of new collection..." + data-trigger="change" data-minlength="5" data-maxlength="50"> + <span class="help-block">Type the name of the new collection.</span> + <button type="submit" name="create_new" class="btn">Create and add traits</button> + </fieldset> + + <hr /> + <fieldset> + <legend>Or add to an existing collection</legend> + <label>Existing collection name</label> + + <select name="existing_collection" class="form-control"> + {% for col in user_collections %} + <option value="{{ col.id }}">{{ col.name }}</option> + {% endfor %} + </select> + <br /> + + <button type="submit" name="add_to_existing" class="btn">Add to existing collection</button> + </fieldset> + </form> + </div> +</div> + +<script> + $('#add_form').parsley(); +</script> diff --git a/gn2/wqflask/templates/collections/view.html b/gn2/wqflask/templates/collections/view.html new file mode 100644 index 00000000..c850e163 --- /dev/null +++ b/gn2/wqflask/templates/collections/view.html @@ -0,0 +1,483 @@ +{% extends "base.html" %} +{% block title %}View Collection{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('js', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css') }}"> + <link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet" type="text/css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/trait_list.css" /> +{% endblock %} +{% block content %} +<!-- Start of body --> + + <div class="container"> + {{flash_me()}} + <h1> + <span id="collection_name">{{ uc.name }}</span> + <form id="frm-change-collection-name" + method="POST" + action="{{url_for('change_collection_name')}}" + style="display:inline;"> + <input type="hidden" name="collection_id" value="{{uc.id}}" /> + <input type="text" + name="new_collection_name" + style="font-size: 20px; display: none; width: 500px;" + class="form-control" + placeholder="{{ uc.name }}" /> + </form> + <button class="btn btn-default" style="display: inline;" id="change_collection_name">Change Collection Name</button> + <button class="btn btn-default" style="display: inline;" id="make_default">Make Default</button> + </h1> + <h3>This collection has {{ '{}'.format(numify(trait_obs|count, "record", "records")) }}</h3> + + <div class="tool-button-container"> + <form id="collection_form" action="/loading" method="post"> + <input type="hidden" name="uc_id" id="uc_id" value="{{ uc.id }}" /> + <input type="hidden" name="collection_name" id="collection_name" value="{{ uc.name }}" /> + <input type="hidden" name="tool_used" value="" /> + <input type="hidden" name="form_url" value="" /> + <input type="hidden" name="trait_list" id="trait_list" value= " + {% for this_trait in trait_obs %} + {{ this_trait.name }}:{{ this_trait.dataset.name }}:{{ data_hmac('{}:{}'.format(this_trait.name, this_trait.dataset.name)) }}, + {% endfor %}" > + + {% include 'tool_buttons.html' %} + + </form> + </div> + <div style="display: flex;"> + <form id="heatmaps_form"> + <button id="clustered-heatmap" + class="btn btn-primary" + data-url="{{heatmap_data_url}}" + title="Generate heatmap from this collection" style="margin-top: 10px; margin-bottom: 10px;"> + Generate Heatmap + </button> + <br> + <div id="heatmap-options" style="display: none;"> + <div style="margin-bottom: 10px;"> + <b>Heatmap Orientation: </b> + <br> + Vertical + <input id="heatmap-orient-vertical" + type="radio" + name="vertical" + value="true" checked="checked"/> + Horizontal + <input id="heatmap-orient-horizontal" + type="radio" + name="vertical" + value="false" /> + </div> + <div style="margin-bottom: 10px;"> + <button id="clear-heatmap" + class="btn btn-danger" + title="Clear Heatmap"> + Clear Heatmap + </button> + </div> + </div> + </form> + + </div> + + <div> + <div id="clustered-heatmap-image-area"></div> + <br /> + <div class="collection-table-options"> + <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" 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"> + <input type="hidden" name="collection_name_export" value="{{ uc.name }}"> + <input type="hidden" name="user_email_export" value="{% if g.user_session.user_email %}{{ g.user_session.user_email }}{% else %}N/A{% endif %}"> + <button class="btn btn-default" id="export_traits">Download</button> + <button class="btn btn-default" id="export_collection">Export Collection</button> + <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" type="button" disabled><i class="icon-minus-sign"></i> Remove</button> + </form> + </div> + <div style="margin-top: 10px; margin-bottom: 5px;" class="show-hide-container"> + <b>Show/Hide Columns: </b> + <button class="toggle-vis" data-column="1">Index</button> + <button class="toggle-vis" data-column="2">Dataset</button> + <button class="toggle-vis" data-column="3">Record</button> + <button class="toggle-vis" data-column="4">Symbol</button> + <button class="toggle-vis" data-column="5">Description</button> + <button class="toggle-vis" data-column="6">Location</button> + <button class="toggle-vis" data-column="7">Mean</button> + <button class="toggle-vis" data-column="8">Peak -logP</button> + <button class="toggle-vis" data-column="9">Peak Location</button> + <button class="toggle-vis" data-column="10">Effect Size</button> + </div> + <div id="trait_table_container" style="width: 2000px; min-width: 1500px;"> + <table class="table-hover table-striped cell-border" id='trait_table' style="float: left;"> + <tbody> + <td colspan="100%" align="center"><br><b><font size="15">Loading...</font></b><br></td> + </tbody> + </table> + </div> + <br /> + </div> + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='jszip/jszip.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/md5.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/natural.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/buttons/js/dataTables.buttons.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/buttons/js/buttons.colVis.min.js') }}"></script> + <script type="text/javascript" src="/static/new/javascript/search_results.js"></script> + <script type="text/javascript" src="/static/new/javascript/table_functions.js"></script> + <script type="text/javascript" src="/static/new/javascript/create_datatable.js"></script> + + <script type="text/javascript" src="{{ url_for('js', filename='plotly/plotly.min.js') }}"></script> + + <script type="text/javascript" + src="/static/new/javascript/partial_correlations.js"></script> + <script type='text/javascript'> + var traitsJson = {{ traits_json|safe }}; + </script> + <script language="javascript" type="text/javascript"> + $(document).ready( function () { + + tableId = "trait_table" + + columnDefs = [ + { + 'data': null, + 'width': "5px", + 'orderDataType': "dom-checkbox", + 'targets': 0, + 'render': function(data) { + return '<input type="checkbox" name="searchResult" class="checkbox trait_checkbox" value="' + data.hmac + '" data-trait-info="' + data.trait_info_str + '">' + } + }, + { + 'title': "Index", + 'type': "natural", + 'width': "35px", + 'searchable': false, + 'orderable': false, + 'targets': 1, + 'render': function(data, type, row, meta) { + return meta.row + } + }, + { + 'title': "Dataset", + 'type': "natural", + 'targets': 2, + 'data': "dataset" + }, + { + 'title': "Record", + 'type': "natural-minus-na", + 'width': "120px", + 'targets': 3, + 'data': null, + 'render': function(data) { + return '<a target="_blank" href="/show_trait?trait_id=' + data.name + '&dataset=' + data.dataset + '">' + data.display_name + '</a>' + } + }, + { + 'title': "Symbol", + 'type': "natural", + 'targets': 4, + 'data': null, + 'render': function(data) { + if (Object.hasOwn(data, 'symbol')){ + return data.symbol + } else if (Object.hasOwn(data, 'name')){ + return data.name + } else { + return "N/A" + } + } + }, + { + 'title': "Description", + 'type': "natural", + 'targets': 5, + 'data': null, + 'render': function(data) { + if (Object.hasOwn(data, 'description')){ + try { + return decodeURIComponent(escape(data.description)) + } catch(err){ + return escape(data.description) + } + } else if (Object.hasOwn(data, 'location')){ + return data.location + } else if (Object.hasOwn(data, 'name')){ + return data.name + } else { + return "N/A" + } + } + }, + { + 'title': "<div style='text-align: right;'>Location</div>", + 'type': "natural-minus-na", + 'width': "125px", + 'targets': 6, + 'data': null, + 'render': function(data) { + if (Object.hasOwn(data, 'location')){ + return data.location + } else { + return "N/A" + } + } + }, + { + 'title': "<div style='text-align: right;'>Mean</div>", + 'type': "natural-minus-na", + 'width': "60px", + 'data': null, + 'targets': 7, + 'orderSequence': [ "desc", "asc"], + 'render': function(data) { + if (Object.hasOwn(data, 'mean')){ + if (data.mean != 'N/A'){ + return data.mean.toFixed(3) + } else { + return "N/A" + } + } else { + return "N/A" + } + } + }, + { + 'title': "<div style='text-align: right; padding-right: 10px;'>Peak</div> <div style='text-align: right;'>-logP <a href=\"{{ url_for('glossary_blueprint.glossary') }}#LRS\" target=\"_blank\" style=\"color: white;\"><sup style='color: #FF0000;'><i>?</i></sup></a></div>", + 'type': "natural-minus-na", + 'data': null, + 'width': "60px", + 'targets': 8, + 'orderSequence': [ "desc", "asc"], + 'render': function(data) { + if (Object.hasOwn(data, 'lrs_score')){ + return (data.lrs_score / 4.61).toFixed(3) + } else { + return "N/A" + } + } + }, + { + 'title': "<div style='text-align: right;'>Peak Location</div>", + 'type': "natural-minus-na", + 'data': null, + 'width': "125px", + 'targets': 9, + 'render': function(data) { + if (Object.hasOwn(data, 'lrs_location')){ + return data.lrs_location + } else { + return "N/A" + } + } + }, + { + 'title': "<div style='text-align: right; padding-right: 10px;'>Effect</div> <div style='text-align: right;'>Size <a href=\"{{ url_for('glossary_blueprint.glossary') }}#A\" target=\"_blank\" style=\"color: white;\"><sup style='color: #FF0000;'><i>?</i></sup></a></div>", + 'type': "natural-minus-na", + 'data': null, + 'width': "85px", + 'targets': 10, + 'orderSequence': [ "desc", "asc"], + 'render': function(data) { + if (Object.hasOwn(data, 'additive')){ + if (data.additive != "") { + return data.additive.toFixed(3) + } else { + return "N/A" + } + } else { + return "N/A" + } + } + } + ] + + tableSettings = { + "createdRow": function ( row, data, index ) { + $('td', row).eq(0).attr("style", "text-align: center; padding: 0px 10px 2px 10px;"); + $('td', row).eq(1).attr("align", "right"); + $('td', row).eq(1).attr('data-export', index+1); + $('td', row).eq(2).attr('data-export', $('td', row).eq(2).text()); + $('td', row).eq(3).attr('title', $('td', row).eq(3).text()); + $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); + $('td', row).eq(4).attr('title', $('td', row).eq(4).text()); + $('td', row).eq(4).attr('data-export', $('td', row).eq(4).text()); + $('td', row).eq(5).attr('data-export', $('td', row).eq(5).text()); + if ($('td', row).eq(5).text().length > 500) { + $('td', row).eq(5).text($('td', row).eq(5).text().substring(0, 500)); + $('td', row).eq(5).text($('td', row).eq(5).text() + '...') + } + $('td', row).slice(6,11).attr("align", "right"); + $('td', row).eq(6).attr('data-export', $('td', row).eq(6).text()); + $('td', row).eq(7).attr('data-export', $('td', row).eq(7).text()); + $('td', row).eq(8).attr('data-export', $('td', row).eq(8).text()); + $('td', row).eq(9).attr('data-export', $('td', row).eq(9).text()); + $('td', row).eq(10).attr('data-export', $('td', row).eq(9).text()); + }, + "order": [[1, "asc" ]], + {% if traits_json|length > 20 %} + "scroller": true + {% else %} + "scroller": false + {% endif %} + } + + create_table(tableId, traitsJson, columnDefs, tableSettings) + + submit_special = function(url) { + $("#collection_form").attr("action", url); + $("#collection_form").attr('target', '_blank').submit(); + return false; + }; + + $("#delete").on("click", function() { + url = $(this).data("url") + $("#collection_form").attr("action", url); + return $("#collection_form").removeAttr('target').submit(); + }); + + $("#remove").on("click", function() { + url = $(this).data("url") + traits = $("#trait_table input:checked").map(function() { + return $(this).val(); + }).get(); + $("#trait_list").val(traits) + $("#collection_form").attr("action", url); + return $("#collection_form").removeAttr('target').submit(); + }); + + $("#change_collection_name").on("click", function() { + if ($('input[name=new_collection_name]').css('display') == 'none') { + $('input[name=new_collection_name]').css('display', 'inline'); + $('#collection_name').css('display', 'none'); + } else { + new_name = $('input[name=new_collection_name]').val(); + $('#frm-change-collection-name').submit(); + console.debug("We really should not get here, but, whatever..."); + $('input[name=new_collection_name]').css('display', 'none'); + $('input[name=collection_name]').val(new_name); + $('#collection_name').text(new_name) + $('#collection_name').css('display', 'inline'); + } + }); + + make_default = function() { + alert("The current collection is now your default collection.") + let uc_id = $('#uc_id').val(); + $.cookie('default_collection', uc_id, { + expires: 365, + path: '/' + }); + + let default_collection_id = $.cookie('default_collection'); + }; + + $("#make_default").on("click", function(){ + make_default(); + }); + + $("#heatmaps_form").submit(function(e) { + e.preventDefault(); + }); + + function clear_heatmap_area() { + area = document.getElementById("clustered-heatmap-image-area"); + area.querySelectorAll("*").forEach(function(child) { + child.remove(); + }); + } + + function generate_progress_indicator() { + count = 0; + default_message = "Computing" + return function() { + message = default_message; + if(count >= 10) { + count = 0; + } + for(i = 0; i < count; i++) { + message = message + " ."; + } + clear_heatmap_area(); + $("#clustered-heatmap-image-area").append( + '<div class="alert alert-info"' + + ' style="font-weigh: bold; font-size: 150%;">' + + message + '</div>'); + count = count + 1; + }; + } + + function display_clustered_heatmap(heatmap_data) { + clear_heatmap_area(); + image_area = document.getElementById("clustered-heatmap-image-area") + Plotly.newPlot(image_area, heatmap_data) + } + + function process_clustered_heatmap_error(xhr, status, error) { + clear_heatmap_area() + $("#clustered-heatmap-image-area").append( + $( + '<div class="alert alert-danger">ERROR: ' + + xhr.responseJSON.message + + '</div>')); + } + + $("#clustered-heatmap").on("click", function() { + clear_heatmap_area(); + $("#heatmap-options").show(); + intv = window.setInterval(generate_progress_indicator(), 300); + vert_element = document.getElementById("heatmap-orient-vertical"); + vert_true = vert_element == null ? false : vert_element.checked; + heatmap_url = $(this).attr("data-url") + traits = $(".trait_checkbox:checked").map(function() { + return this.value + }).get(); + $.ajax({ + type: "POST", + url: heatmap_url, + contentType: "application/json", + data: JSON.stringify({ + "traits_names": traits, + "vertical": vert_true + }), + dataType: "JSON", + success: function(data, status, xhr) { + window.clearInterval(intv); + display_clustered_heatmap(data); + }, + error: function(xhr, status, error) { + window.clearInterval(intv); + process_clustered_heatmap_error(xhr, status, error); + } + }); + }); + + $("#clear-heatmap").on("click", function() { + clear_heatmap_area(); + $("#heatmap-options").hide(); + }); + + }); + </script> + + +{% endblock %} + diff --git a/gn2/wqflask/templates/collections/view_anonymous.html b/gn2/wqflask/templates/collections/view_anonymous.html new file mode 100644 index 00000000..56323e10 --- /dev/null +++ b/gn2/wqflask/templates/collections/view_anonymous.html @@ -0,0 +1,143 @@ +{% extends "base.html" %} +{% block title %}View Collection{% endblock %} +{% block content %} +<!-- Start of body --> + {% if uc %} + {{ header(uc.name, + 'This collection has {}.'.format(numify(trait_obs|count, "record", "records"))) }} + {% else %} + {{ header('Your Collection', + 'This collection has {}.'.format(numify(trait_obs|count, "record", "records"))) }} + {% endif %} + <div class="container"> + <div class="page-header"> + <h1>Your Collection</h1> + {% if uc %} + <h2>{{ uc.name }}</h2> + {% endif %} + + <div class="form-group"> + <form action="/collections/delete" method="post"> + {% if uc %} + <input type="hidden" name="uc_id" id="uc_id" value="{{ uc.id }}" /> + {% endif %} + <div class="col-xs-3 controls"> + <input type="submit" class="btn btn-danger" value="Delete this collection" /> + </div> + </form> + <form action="/corr_matrix" method="post"> + {% if uc %} + <input type="hidden" name="uc_id" id="uc_id" value="{{ uc.id }}" /> + {% endif %} + <input type="hidden" name="trait_list" id="trait_list" value= " + {% for this_trait in trait_obs %} + {{ this_trait.name }}:{{ this_trait.dataset.name }}, + {% endfor %}" > + <div class="col-xs-2 controls"> + <input type="submit" class="btn btn-primary" value="Correlation Matrix" /> + </div> + </form> + <form action="/heatmap" method="post"> + {% if uc %} + <input type="hidden" name="uc_id" id="uc_id" value="{{ uc.id }}" /> + {% endif %} + <input type="hidden" name="trait_list" id="trait_list" value= " + {% for this_trait in trait_obs %} + {{ this_trait.name }}:{{ this_trait.dataset.name }}, + {% endfor %}" > + <div class="col-xs-2 controls"> + <input type="submit" class="btn btn-primary" value="Heatmap" /> + </div> + </form> + </div> + + <!-- + <form action="/corr_matrix" method="post"> + {% if uc %} + <input type="hidden" name="uc_id" id="uc_id" value="{{ uc.id }}" /> + {% endif %} + <input type="hidden" name="trait_list" id="trait_list" value= " + {% for this_trait in trait_obs %} + {{ this_trait.name }}:{{ this_trait.dataset.name }}, + {% endfor %}" > + <input type="submit" + class="btn btn-small" + value="Correlation Matrix" /> + </form> + <form action="/heatmap" method="post"> + {% if uc %} + <input type="hidden" name="uc_id" id="uc_id" value="{{ uc.id }}" /> + {% endif %} + <input type="hidden" name="trait_list" id="trait_list" value= " + {% for this_trait in trait_obs %} + {{ this_trait.name }}:{{ this_trait.dataset.name }}, + {% endfor %}" > + <input type="submit" + class="btn btn-small" + value="Heatmap" /> + </form> + --> + </div> + + + + <div class="bs-docs-example"> + <table class="table table-hover" id='trait_table'> + <thead> + <tr> + <th></th> + <th>Record</th> + <th>Description</th> + <th>Location</th> + <th>Mean</th> + <th>Max LRS</th> + <th>Max LRS Location</th> + </tr> + </thead> + + <tbody> + {% for this_trait in trait_obs %} + <TR id="trait:{{ this_trait.name }}:{{ this_trait.dataset.name }}"> + <TD> + <INPUT TYPE="checkbox" NAME="searchResult" class="checkbox trait_checkbox" + VALUE="{{ data_hmac('{}:{}'.format(this_trait.name, this_trait.dataset.name)) }}"> + </TD> + <TD> + <a href="{{ url_for('show_trait_page', + trait_id = this_trait.name, + dataset = this_trait.dataset.name + )}}"> + {{ this_trait.name }} + </a> + </TD> + + <TD>{{ this_trait.description_display }}</TD> + <TD>{{ this_trait.location_repr }}</TD> + <TD>{{ this_trait.mean }}</TD> + <TD>{{ this_trait.LRS_score_repr }}</TD> + <TD>{{ this_trait.LRS_location_repr }}</TD> + + </TR> + {% endfor %} + </tbody> + + </table> + + <br /> + + <button class="btn" id="select_all"><i class="icon-ok"></i> Select All</button> + <button class="btn" id="deselect_all"><i class="icon-remove"></i> Deselect All</button> + <button class="btn" id="invert"><i class="icon-resize-vertical"></i> Invert</button> + <button class="btn" id="add" disabled="disabled"><i class="icon-plus-sign"></i> Add Record to Other Collection</button> + <button class="btn" id="remove" disabled="disabled"><i class="icon-minus-sign"></i> Remove Record</button> + <button class="btn btn-primary pull-right"><i class="icon-download icon-white"></i> Download Table</button> + </div> + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script type="text/javascript" src="/static/new/javascript/search_results.js"></script> +{% endblock %} diff --git a/gn2/wqflask/templates/comparison_bar_chart.html b/gn2/wqflask/templates/comparison_bar_chart.html new file mode 100644 index 00000000..d77e0515 --- /dev/null +++ b/gn2/wqflask/templates/comparison_bar_chart.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} +{% block title %}Comparison Bar Chart{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='d3-tip/d3-tip.css') }}" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/panelutil.css" /> +{% endblock %} +{% block content %} <!-- Start of body --> + <div class="container"> + <h1>Comparison Bar Chart</h1> + <hr style="height: 1px; background-color: #A9A9A9;"> + <div> + <h3> + The following is a grouped bar chart with the sample values for each selected trait. + </h3> + </div> + <div id="chart_container"> + <div id="comp_bar_chart"></div> + </div> + + </div> + + <!-- End of body --> + +{% endblock %} + +{% block js %} + <script> + js_data = {{ js_data | safe }} + </script> + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3js/d3.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3-tip/d3-tip.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/panelutil.js"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> + <script type="text/javascript" src="{{ url_for('js', filename='plotly/plotly.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/comparison_bar_chart.js"></script> + +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/corr_scatterplot.html b/gn2/wqflask/templates/corr_scatterplot.html new file mode 100644 index 00000000..554471be --- /dev/null +++ b/gn2/wqflask/templates/corr_scatterplot.html @@ -0,0 +1,364 @@ +{% extends "base.html" %} + +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/panelutil.css" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='d3-tip/d3-tip.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='nvd3/nv.d3.min.css') }}"> + <link rel="stylesheet" type="text/css" href="/static/new/css/trait_list.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/corr_scatter_plot.css" /> +{% endblock %} + +{% block content %} + +<div class="container-fluid"> + + <input type="hidden" name="cofactor1_vals"></input> + <input type="hidden" name="ranked_cofactor1_vals"></input> + <input type="hidden" name="cofactor2_vals"></input> + <input type="hidden" name="ranked_cofactor2_vals"></input> + <input type="hidden" name="cofactor3_vals"></input> + <input type="hidden" name="ranked_cofactor3_vals"></input> + <input type="hidden" name="selecting_which_cofactor"></input> + + <h1>Correlation Scatterplot</h1> + <hr style="height: 1px; background-color: #A9A9A9;"> + + <table> + <tr> + <td style="vertical-align: middle;">Width <input class="chartupdatewh" id="width" type="text" value="800" style="width: 44px; height: 22px;"> px </td> + <td style="vertical-align: middle; padding-left: 5px;">Height <input class="chartupdatewh" id="height" type="text" value="700" style="width: 44px; height: 22px;"> px</td> + <td style="vertical-align: middle; padding-left: 5px;"><button type="button" class="btn btn-default" id="invert_axes"> + Invert Axes + </button></td> + </tr> + </table> + <hr style="height: 1px; background-color: #A9A9A9;"> + {% if collections_exist == "True" %} + <div> + <div style="margin-bottom: 10px;"> + You can select up to three traits from a saved trait collection to use as cofactors in the scatterplots, with each trait corresponding to point color, size, or symbol. + For symbol, traits must have no more than 4 distinct values. + </div> + <div style="display: inline-block;"> + <div id="cofactor1_button" style="display: inline-block;"> + <button type="button" class="btn btn-default" id="select_cofactor1"> + Select Cofactor 1 + </button> + <select id="cofactor1_type"> + <option value="color" selected>Color</option> + <option value="size">Size</option> + <option value="symbol">Symbol</option> + </select> + </div> + <div id="cofactor2_button" style="margin-left: 10px; display: none;"> + <button type="button" class="btn btn-default" id="select_cofactor2"> + Add a Second Cofactor? + </button> + <select id="cofactor2_type"> + <option value="color">Color</option> + <option value="size" selected>Size</option> + <option value="symbol">Symbol</option> + </select> + </div> + <div id="cofactor3_button" style="margin-left: 10px; display: none;"> + <button type="button" class="btn btn-default" id="select_cofactor3"> + Add a Third Cofactor? + </button> + <select id="cofactor3_type"> + <option value="color">Color</option> + <option value="size">Size</option> + <option value="symbol" selected>Symbol</option> + </select> + </div> + <div id="remove_cofactors_button" style="margin-left: 20px; display: inline-block;"> + <button type="button" class="btn btn-danger" id="remove_cofactors"> + Remove Cofactors + </button> + </div> + </div> + </div> + <div id="collections_holder_wrapper" style="display:none;"> + <div id="collections_holder"></div> + </div> + + <div id="cofactor_color_selector" style="margin-bottom: 10px; display:none;"> + <hr> + <b>Cofactor Color Range:</b> + <input class="chartupdatedata" name="color2" type="hidden" id="cocolorfrom" value="#0000FF"> + <button class="chartupdatedata jscolor {valueElement: 'cocolorfrom'}">Low</button> + <input class="chartupdatedata" name="color1" type="hidden" id="cocolorto" value="#FF0000"> + <button class="chartupdatedata jscolor {valueElement: 'cocolorto'}">High</button> + <hr> + </div> + + <div id="cofactor1_info_container" style="margin-left: 0px; display: none;"> + <div> + <b>Cofactor 1</b>: <a id="cofactor1_trait_link" href="#"></a> + </div> + <div id="cofactor1_description"></div> + <hr> + </div> + + <div id="cofactor2_info_container" style="margin-left: 0px; display: none;"> + <div> + <b>Cofactor 2</b>: <a id="cofactor2_trait_link" href="#"></a> + </div> + <div id="cofactor2_description"></div> + <hr> + </div> + + <div id="cofactor3_info_container" style="margin-left: 0px; display: none;"> + <div> + <b>Cofactor 3</b>: <a id="cofactor3_trait_link" href="#"></a> + </div> + <div id="cofactor3_description"></div> + <hr> + </div> + + {% else %} + <div style="margin-bottom: 10px;">No collections currently exist. Please create a collection first if you wish to include cofactors in the scatterplots.</div> + <hr> + {% endif %} + + <ul class="nav nav-tabs"> + <li {% if method == 'pearson' %}class="active"{% endif %}> + <a href="#tp1" data-toggle="tab">Pearson</a> + </li> + <li {% if method == 'spearman' %}class="active"{% endif %}> + <a href="#tp2" data-toggle="tab">Spearman Rank</a> + </li> + </ul> + + <div class="tab-content" style="min-width: 800px;"> + + <div class="tab-pane {% if method == 'pearson' %}active{% endif %}" id="tp1"> + <br> + <div id="scatterplot2"></div> + <br> + <div style="min-width: 700px; overflow: hidden;"> + <div style="margin-left: 50px; min-width: 300px;"> + {% if trait_1.dataset.type == "ProbeSet" %} + <div> + X axis: + <a href="{{url_for('show_trait_page', trait_id = trait_1.name, dataset = trait_1.dataset.name)}}"> + {{trait_1.dataset.group.species + " " + trait_1.dataset.group.name + " " + trait_1.dataset.tissue + " " + trait_1.dataset.name + ": " + trait_1.name|string}} + </a> + </div> + <div> + [{{trait_1.symbol}} on {{trait_1.location_repr}} Mb] + {{trait_1.description_display}} + </div> + {% elif trait_1.dataset.type == "Publish" %} + <div> + X axis: + <a href="{{url_for('show_trait_page', trait_id = trait_1.name, dataset = trait_1.dataset.name)}}"> + {{trait_1.dataset.group.species + " " + trait_1.dataset.group.name + " " + trait_1.dataset.name + ": " + trait_1.name|string}} + </a> + </div> + <div> + <a href="{{trait_1.pubmed_link}}">PubMed: {{trait_1.pubmed_text}}</a> + {{trait_1.description_display}} + </div> + {% elif trait_1.dataset.type == "Geno" %} + <div> + X axis: + <a href="{{url_for('show_trait_page', trait_id = trait_1.name, dataset = trait_1.dataset.name)}}"> + {{trait_1.dataset.group.species + " " + trait_1.dataset.group.name + " " + trait_1.dataset.name + ": " + trait_1.name|string}} + </a> + </div> + <div> + Location: {{trait_1.location_repr}} Mb + </div> + {% endif %} + + <br/> + + {% if trait_2.dataset.type == "ProbeSet" %} + <div> + Y axis: + <a href="{{url_for('show_trait_page', trait_id = trait_2.name, dataset = trait_2.dataset.name)}}"> + {{trait_2.dataset.group.species + " " + trait_2.dataset.group.name + " " + trait_2.dataset.tissue + " " + trait_2.dataset.name + ": " + trait_2.name|string}} + </a> + </div> + <div> + [{{trait_2.symbol}} on {{trait_2.location_repr}} Mb] + {{trait_2.description_display}} + </div> + {% elif trait_2.dataset.type == "Publish" %} + <div> + Y axis: + <a href="{{url_for('show_trait_page', trait_id = trait_2.name, dataset = trait_2.dataset.name)}}"> + {{trait_2.dataset.group.species + " " + trait_2.dataset.group.name + " " + trait_2.dataset.name + ": " + trait_2.name|string}} + </a> + </div> + <div> + <a href="{{trait_2.pubmed_link}}">PubMed: {{trait_2.pubmed_text}}</a> + {{trait_2.description_display}} + </div> + {% elif trait_2.dataset.type == "Geno" %} + <div> + Y axis: + <a href="{{url_for('show_trait_page', trait_id = trait_2.name, dataset = trait_2.dataset.name)}}"> + {{trait_2.dataset.group.species + " " + trait_2.dataset.group.name + " " + trait_2.dataset.name + ": " + trait_2.name|string}} + </a> + </div> + <div> + Location: {{trait_2.location_repr}} Mb + </div> + {% endif %} + </div> + <div style="float: left; margin-top:30px;"> + <table class="table table-hover table-striped table-bordered" style="width: 80%; margin-left: 60px; text-align: right;"> + <thead> + <tr><th style="text-align: right;">Statistic</th><th style="text-align: right;">Value</th></tr> + </thead> + <tbody> + <tr> + <td>Number</td> + <td>{{jsdata.num_overlap}}</td> + </tr> + <tr> + <td>Slope</td> + <td>{{ jsdata.slope_string }}</td> + </tr> + <tr> + <td>Intercept</td> + <td>{{'%0.3f' % jsdata.intercept}}</td> + </tr> + <tr> + <td>r value</td> + <td>{{'%0.3f' % jsdata.r_value}}</td> + </tr> + <tr> + <td>P value</td> + <td>{% if jsdata.p_value < 0.001 %}{{'%0.3e' % jsdata.p_value}}{% else %}{{'%0.3f' % jsdata.p_value}}{% endif %}</td> + </tr> + <tr> + <td style="text-align: left;" colspan="2"> + Regression Line + <br> + y = {{ jsdata.slope_string }} * x {% if jsdata.intercept < 0 %}- {{'%0.3f' % (jsdata.intercept * -1)}}{% else %}+ {{'%0.3f' % jsdata.intercept}}{% endif %} + </td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + + <div class="tab-pane {% if method == 'spearman' %}active{% endif %}" id="tp2"> + <br> + <div id="srscatterplot2"></div> + <br> + <div class="row" style="min-width: 700px; overflow: hidden;"> + <div style="margin-left: 50px; min-width: 300px;"> + {% if trait_1.dataset.type == "ProbeSet" %} + <div> + X axis: + <a href="{{url_for('show_trait_page', trait_id = trait_1.name, dataset = trait_1.dataset.name)}}"> + {{trait_1.dataset.group.species + " " + trait_1.dataset.group.name + " " + trait_1.dataset.tissue + " " + trait_1.dataset.name + ": " + trait_1.name|string}} + </a> + </div> + <div> + [{{trait_1.symbol}} on {{trait_1.location_repr}} Mb] + {{trait_1.description_display}} + </div> + {% elif trait_1.dataset.type == "Publish" %} + <div> + X axis: + <a href="{{url_for('show_trait_page', trait_id = trait_1.name, dataset = trait_1.dataset.name)}}"> + {{trait_1.dataset.group.species + " " + trait_1.dataset.group.name + " " + trait_1.dataset.name + ": " + trait_1.name|string}} + </a> + </div> + <div> + <a href="{{trait_1.pubmed_link}}">PubMed: {{trait_1.pubmed_text}}</a> + {{trait_1.description_display}} + </div> + {% endif %} + + <br/> + + {% if trait_2.dataset.type == "ProbeSet" %} + <div> + Y axis: + <a href="{{url_for('show_trait_page', trait_id = trait_2.name, dataset = trait_2.dataset.name)}}"> + {{trait_2.dataset.group.species + " " + trait_2.dataset.group.name + " " + trait_2.dataset.tissue + " " + trait_2.dataset.name + ": " + trait_2.name|string}} + </a> + </div> + <div> + [{{trait_2.symbol}} on {{trait_2.location_repr}} Mb] + {{trait_2.description_display}} + </div> + {% elif trait_2.dataset.type == "Publish" %} + <div> + Y axis: + <a href="{{url_for('show_trait_page', trait_id = trait_2.name, dataset = trait_2.dataset.name)}}"> + {{trait_2.dataset.group.species + " " + trait_2.dataset.group.name + " " + trait_2.dataset.name + ": " + trait_2.name|string}} + </a> + </div> + <div> + <a href="{{trait_2.pubmed_link}}">PubMed: {{trait_2.pubmed_text}}</a> + {{trait_2.description_display}} + </div> + {% endif %} + </div> + <div style="float: left; margin-top: 30px;"> + <table class="table table-hover table-striped table-bordered" style="width: 80%; margin-left: 60px; text-align: right;"> + <thead> + <tr><th style="text-align: right;">Statistic</th><th style="text-align: right;">Value</th></tr> + </thead> + <tbody> + <tr> + <td>Number</td> + <td>{{jsdata.num_overlap}}</td> + </tr> + <tr> + <td>Slope</td> + <td>{{ jsdata.srslope_string }}</td> + </tr> + <tr> + <td>Intercept</td> + <td>{{'%0.3f' % jsdata.srintercept}}</td> + </tr> + <tr> + <td>r value</td> + <td>{{'%0.3f' % jsdata.srr_value}}</td> + </tr> + <tr> + <td>P value</td> + <td>{% if jsdata.srp_value < 0.001 %}{{'%0.3e' % jsdata.srp_value}}{% else %}{{'%0.3f' % jsdata.srp_value}}{% endif %}</td> + </tr> + <tr> + <td style="text-align: left;" colspan="2"> + Regression Line + <br> + y = {{ jsdata.srslope_string }} * x {% if jsdata.srintercept < 0 %}- {{'%0.3f' % (jsdata.srintercept * -1)}}{% else %}+ {{'%0.3f' % jsdata.srintercept}}{% endif %} + </td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + +</div> + +{% endblock %} + +{% block js %} + <script> + js_data = {{ js_data | safe }}; + </script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3js/d3.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3-tip/d3-tip.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='jscolor/jscolor.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/panelutil.js"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/natural.js') }}"></script> + <!--<script language="javascript" type="text/javascript" src="/static/new/javascript/get_traits_from_collection.js"></script>--> + <script type="text/javascript" src="{{ url_for('js', filename='plotly/plotly.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/draw_corr_scatterplot.js"></script> +{% endblock %} diff --git a/gn2/wqflask/templates/correlation_error_page.html b/gn2/wqflask/templates/correlation_error_page.html new file mode 100644 index 00000000..7d11daf0 --- /dev/null +++ b/gn2/wqflask/templates/correlation_error_page.html @@ -0,0 +1,23 @@ +{%extends "base.html"%} +{%block title%}Correlation Results{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> +<link rel="stylesheet" type="text/css" href="{{ url_for('js', filename='DataTablesExtensions/buttonsBootstrap/css/buttons.bootstrap.css') }}" /> +<link rel="stylesheet" type="text/css" href="{{ url_for('js', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css') }}" /> +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='fontawesome/css/all.min.css') }}"/> +<link rel="stylesheet" type="text/css" href="/static/new/css/trait_list.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{%endblock%} + +{%block content%} +<div class="container"> + <h3 style="color: red;">{{error["error-type"]}}</h3> + <p>{{error["error-message"]}}</p> + <p style="background-color: black; color: green;"> + {%for line in error["stderr-output"]%} + {{line}}<br /> + {%endfor%} + </p> +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/correlation_matrix.html b/gn2/wqflask/templates/correlation_matrix.html new file mode 100644 index 00000000..17fd66fa --- /dev/null +++ b/gn2/wqflask/templates/correlation_matrix.html @@ -0,0 +1,203 @@ +{% extends "base.html" %} +{% block title %}Correlation Matrix{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/corr_matrix.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/panelutil.css" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='d3-tip/d3-tip.css') }}" /> +{% endblock %} +{% block content %} + +<div class="container" width="100%"> +<h1>Correlation Matrix</h1> +<div style="width: 100%; max-width: 850px;">Select any cell in the matrix to generate a <b>scatter plot.</b><br><b>Lower left</b> cells list Pearson product-moment correlations; <b>upper right</b> cells list Spearman rank order correlations. Each cell also contains the <b><i>n</i> of cases</b> in parenthesis. Values ranging from 0.4 to 1.0 range from orange to white, while values ranging from –0.4 to –1.0 range from dark blue to white.</div> +<hr style="height: 1px; background-color: #A9A9A9;"> +{% if lowest_overlap < 8 %} +<div style="margin-bottom: 10px;"><i><font style="color: red;">Caution</font>: This matrix of correlations contains some cells with small sample sizes of fewer than 8.</i></div> +{% endif %} +<table class="matrix" border="1" cellpadding="5" cellspacing="1" width="100%"> + <tbody> + <tr> + <td style="background-color: #369;" ></td> + <td align="center" colspan="{{traits|length + 2 }}" style="font-weight: Bold; border: 1px solid #000000; padding: 3px; color: #fff; background-color: #369;">Spearman Rank Correlation (rho)</td> + </tr> + <tr> + <td align="center" rowspan="{{traits|length + 2 }}" width="10" style="font-weight: Bold; border: 1px solid #000000; padding: 3px; color: #fff; background-color: #369;">P e a r s o n r</td> + <td></td> + <td width="300" align="center" style="white-space: nowrap"><div style="display: inline-block; margin: 4px;"><button class="btn btn-default" id="short_labels" style="display: inline-block"><span style="display: none;" class="short_check glyphicon glyphicon-ok"></span> Short Labels</button> <button class="btn btn-default" id="long_labels" style="display: inline-block;"><span style="display: none;" class="long_check glyphicon glyphicon-ok"></span> Long Labels</button></div></td> + {% for trait in traits %} + <td align="center" style="padding: 5px;"> + <a href="/show_trait?trait_id={{ trait.name }}&dataset={{ trait.dataset.name }}"><font style="font-size: 14px;"><b>{{ loop.index }}</b></font></a> + </td> + {% endfor %} + </tr> + {% for trait in traits %} + {% set outer_loop = loop.index %} + <tr> + <td align="center"><input type="checkbox" class="checkbox" style="margin-left: 3px; margin-right: 1px;"></td> + <td align="right" style="padding-right: 4px;" > + <a href="/show_trait?trait_id={{ trait.name }}&dataset={{ trait.dataset.name }}"><font style="font-size: 14px; font-style: Bold;"><b>{{ loop.index }}: {{ trait.dataset.name }} {{ trait.name }}</b></font></a> + <div class="shortName">{% if trait.dataset.type == "ProbeSet" %}Gene Symbol: {{ trait.symbol }}{% elif trait.dataset.type == "Publish" %}Trait Symbol: {{ trait.post_publication_abbreviation }}{% elif trait.dataset.type == "Geno" %}Genotype{% endif %} </div> + <div class="verboseName" style="display: none;"> + {% if trait.dataset.type == "ProbeSet" %} + <div>{{ trait.symbol }} on Chr {{ trait.chr }} @ {{ trait.mb }} Mb</div><div>{{ trait.description }}</div><div>{{ trait.probe_target_description }}</div> + {% elif trait.dataset.type == "Publish" %} + <div>PMID: <a href="http://www.ncbi.nlm.nih.gov/pubmed/?term={{ trait.pubmed_id }}">{{ trait.pubmed_id }}</a>, Record ID {{ trait.name }}</div><div>Phenotype: {{ trait.description_display }}</div> + {% elif trait.dataset.type == "Geno" %} + <div>Locus {{ trait.name }}</div><div>Chr {{ trait.chr }} @ {{ trait.mb }} Mb</div> + {% endif %} + </div> + </td> + {% for result in corr_results[loop.index-1] %} + {% if result[0].name == trait.name and result[0].dataset == trait.dataset %} + <td nowrap="ON" align="center" bgcolor="#cccccc" style="padding: 3px; line-height: 1.1;"><a href="/show_trait?trait_id={{ trait.name }}&dataset={{ trait.dataset.name }}"><font style="font-size: 12px; color: #3071a9; font-weight: bold;" ><em>n</em><br>{{ result[2] }}</font></a></td> + {% else %} + {% if result[1] == 0 %} + <td nowrap="ON" align="middle" bgcolor="#eeeeee" style="padding: 3px; line-height: 1.1;">N/A</td> + {% else %} + <td nowrap="ON" align="middle" class="corr_cell" style="padding: 3px; line-height: 1.1;"><a href="/corr_scatter_plot?method={% if loop.index > outer_loop %}spearman{% else %}pearson{% endif %}&dataset_1={% if trait.dataset.name == 'Temp' %}Temp_{{ trait.dataset.group.name }}{% else %}{{ trait.dataset.name }}{% endif %}&dataset_2={% if result[0].dataset.name == 'Temp' %}Temp_{{ result[0].dataset.group.name }}{% else %}{{ result[0].dataset.name }}{% endif %}&trait_1={{ trait.name }}&trait_2={{ result[0].name }}"><span class="corr_value" style="font-size: 14px; color: #3071a9; font-weight: bold;">{{ '%0.2f' % result[1] }}</span><br><span class="corr_value" style="font-size: 12px; color: #3071a9; font-weight: bold;">({{ result[2] }})</span></a></td> + {% endif %} + {% endif %} + {% endfor %} + </tr> + {% endfor %} + </tbody> +</table> +<br> +<form method="post" target="_blank" action="/export_corr_matrix" id="matrix_export_form"> + <input type="hidden" name="export_filepath" value="{{ export_filepath }}"> + <input type="hidden" name="export_filename" value="{{ export_filename }}"> + <button class="btn btn-default" id="export">Download <span class="glyphicon glyphicon-download"></span></button> +</form> +<br> + +{% if pca_works == "True" %} +<div> + {% include 'pca_scree_plot.html' %} +</div> + +<h2>PCA Traits</h2> +<div style="margin-bottom: 20px; overflow:hidden; width: 450px;"> +<table class="table-hover table-striped cell-border pca_table" id='trait_table' style="float: left;"> + <colgroup> + <col span="1" style="width: 30px;"> + <col span="1" style="width: 50px;"> + <col span="1"> + </colgroup> + <thead> + <tr> + <th></th> + <th>Index</th> + <th>PCA Trait</th> + </tr> + </tr> + </thead> + <tbody> + {% for this_trait_id in pca_trait_ids %} + <tr> + <td align="center" style="padding: 0px;"><input type="checkbox" class="trait_checkbox" name="pca_trait" value="{{ data_hmac('{}:{}'.format(pca_trait_ids[loop.index - 1], "Temp")) }}"></td> + <td align="right">{{ loop.index }}</td> + <td><a href="{{ url_for('show_trait_page', trait_id = pca_trait_ids[loop.index - 1], dataset = "Temp") }}">{{ pca_trait_ids[loop.index - 1] }}</a></td> + </tr> + {% endfor %} + </tbody> +</table> +<br> +<button class="btn btn-default" id="select_all"><span class="glyphicon glyphicon-ok"></span> Select All</button> +<button class="btn btn-default" id="deselect_all"><span class="glyphicon glyphicon-remove"></span> Deselect All</button> +<button class="btn btn-success" id="add" disabled><span class="glyphicon glyphicon-plus-sign"></span> Add</button> +</div> +<h2>Factor Loadings Plot</h2> +<div id="loadings_plot" style="margin-top: 20px; margin-bottom: 20px; width: 980px; border-style: solid; border-width: 1px;"></div> +<h2>Factor Loadings Table</h2> +<table class="table-hover table-striped cell-border loadings_table" id='trait_table' style="float: left;"> + <thead> + <tr> + <th>Trait</th> + <th style="text-align: right;" >Factor 1</th> + <th style="text-align: right;" >Factor 2</th> + {% if trait_list|length > 2 %}<th style="text-align: right;">Factor 3</th>{% endif %} + </tr> + </thead> + <tbody> + {% for row in loadings_array %} + {% set row_counter = loop.index-1 %} + <tr> + <td> + <a title="{% if traits[loop.index-1].dataset.type == "ProbeSet" %}{{ traits[loop.index-1].symbol }}{% elif traits[loop.index-1].dataset.type == "Publish" %}{{ traits[loop.index-1].post_publication_abbreviation }}{% endif %}" href="{{ url_for('show_trait_page', trait_id = traits[loop.index-1].name, dataset = traits[loop.index-1].dataset.name) }}"> + {{ traits[loop.index-1].name }} + </a> + </td> + {% for column in row %} + <td><span style="float: right;">{{ '%0.3f' % loadings_array[row_counter][loop.index-1]|float }}</span></td> + {% endfor %} + </tr> + {% endfor %} + + </tbody> +</table> +</div> +{% endif %} +<div id="myModal"></div> +{% endblock %} + +{% block js %} + + <script> + js_data = {{ js_data | safe }} + loadings = {{ loadings_array | safe }} + </script> + + <script type="text/javascript" src="{{ url_for('js', filename='d3js/d3.min.js') }}"></script> + <script type="text/javascript" src="{{ url_for('js', filename='d3-tip/d3-tip.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/md5.min.js') }}"></script> + <script type="text/javascript" src="/static/new/javascript/panelutil.js"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='chroma/chroma.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/loadings_plot.js"></script> + <script type="text/javascript" src="/static/new/javascript/create_corr_matrix.js"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + <script type="text/javascript" src="/static/new/javascript/search_results.js"></script> + + <script> + $('.pca_table').dataTable( { + "columnDefs": [ { + "targets": 0, + "orderable": false + } ], + "order": [[1, "asc" ]], + "sDom": "t", + "iDisplayLength": -1, + "autoWidth": true, + "bDeferRender": true, + "bSortClasses": false, + "paging": false, + "orderClasses": true + } ); + + $('.loadings_table').dataTable( { + "columnDefs": [ { + "targets": 0, + "orderable": false + } ], + "order": [[1, "asc" ]], + "sDom": "t", + "iDisplayLength": -1, + "autoWidth": true, + "bDeferRender": true, + "bSortClasses": false, + "paging": false, + "orderClasses": true + } ); + + export_corr_matrix = function() { + $('#matrix_export_form').attr('action', '/export_corr_matrix'); + return $('#matrix_export_form').submit(); + } + + $('#export').click(export_corr_matrix); + + </script> + +{% endblock %} diff --git a/gn2/wqflask/templates/correlation_page.html b/gn2/wqflask/templates/correlation_page.html new file mode 100644 index 00000000..d3ee32f3 --- /dev/null +++ b/gn2/wqflask/templates/correlation_page.html @@ -0,0 +1,550 @@ +{% extends "base.html" %} +{% block title %}Correlation Results{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('js', filename='DataTablesExtensions/buttonsBootstrap/css/buttons.bootstrap.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('js', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css') }}"> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='fontawesome/css/all.min.css') }}"/> + <link rel="stylesheet" type="text/css" href="/static/new/css/trait_list.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{% endblock %} +{% block content %} + <div class="container"> + <div class="page-header"> + <h1>Correlation Table</h1> + <h2>Trait: {{ this_trait.name }} + + <hr style="height: 1px; background-color: #A9A9A9;"> + </div> + <div style="max-width: 100%;"> + <p>Values of record {{ this_trait.name }} in the <a href="http://genenetwork.org/webqtl/main.py?FormID=sharinginfo&{% if this_dataset.accession_id %}GN_AccessionId={{ this_dataset.accession_id }}{% else %}InfoPageName={{ this_dataset.name }}{% endif %}">{{ this_dataset.fullname }}</a> + dataset were compared to all records in the <a href="http://genenetwork.org/webqtl/main.py?FormID=sharinginfo&{% if target_dataset.accession_id %}GN_AccessionId={{ target_dataset.accession_id }}{% else %}InfoPageName={{ target_dataset.name }}{% endif %}">{{ target_dataset.fullname }}</a> + dataset. The top {{ return_results }} correlations ranked by the {{ formatted_corr_type }} are displayed. + You can resort this list by clicking the headers. Select the Record ID to open the trait data + and analysis page. + </p> + </div> + <div class="tool-button-container"> + <form id="correlation_form" target="_blank" action="/corr_matrix" method="post"> + <input type="hidden" name="tool_used" value="" /> + <input type="hidden" name="form_url" value="" /> + <input type="hidden" name="trait_list" id="trait_list" value= " + {% for this_trait in trait_list %} + {{ this_trait }}:{{ this_dataset.name }}, + {% endfor %}" > + {% include 'tool_buttons.html' %} + </form> + </div> + <br /> + <div style="min-width: 900px;"> + <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=""> + <input type="hidden" name="file_name" id="file_name" value="{{ this_trait.name }}_{{ this_dataset.name }}_correlation"> + <input type="text" id="searchbox" class="form-control" style="width: 200px; display: inline;" placeholder="Search Table For ..."> + <input type="text" id="select_top" class="form-control" style="width: 200px; display: inline;" placeholder="Select Rows (1-5, 11)"> + <button class="btn btn-default" id="deselect_all" type="button"><span class="glyphicon glyphicon-remove"></span> Deselect</button> + <button id="redraw" class="btn btn-default" type="button">Reset Columns</button> + </form> + <br /> + <div id="export_options" style="float: left;"></div> + <br /> + <div style="float: left; clear: left; margin-top: 10px; margin-bottom: 20px;"> + <button id="more_options" class="btn btn-primary">More Options...</button> + </div> + <br /> + <br /> + <div id="filter_options" style="display: none; float: left; clear: left;"> + <span style="border: 1px dashed #999999; padding: 8px; background-color: #ddf; font-size: 12px;"> + <button id="select_traits" class="btn btn-primary" style="font-size: 12px; padding: 2px 3px;">Select Traits</button> with r > + <input type="text" name="r_greater_select" value="-1.0" size="6" maxlength="10"> + <select id="r_and_or" size="1"> + <option value="and" selected>AND</option> + <option value="or">OR</option> + </select> + r < + <input type="text" name="r_less_select" value="1.0" size="6" maxlength="10">, with mean > + <input type="text" name="mean_greater_select" value="0" size="6" maxlength="10"> + <select id="mean_and_or" size="1"> + <option value="and" selected>AND</option> + <option value="or">OR</option> + </select> + mean < + <input type="text" name="mean_less_select" value="100" size="6" maxlength="10"> + </span> + <br /> + <br /> + </div> + </div> + <div class="show-hide-container" style="float: left; clear: left;"> + <b>Show/Hide Columns:</b> + <br> + {% if target_dataset.type == 'ProbeSet' %} + <button class="toggle-vis" data-column="3">Symbol</button> + <button class="toggle-vis" data-column="4">Description</button> + <button class="toggle-vis" data-column="5">Location</button> + <button class="toggle-vis" data-column="6">Mean</button> + <button class="toggle-vis" data-column="7">Peak -logP</button> + <button class="toggle-vis" data-column="8">Peak Location</button> + <button class="toggle-vis" data-column="9">Effect Size</button> + {% elif target_dataset.type == 'Publish' %} + <button class="toggle-vis" data-column="3">Abbreviation</button> + <button class="toggle-vis" data-column="4">Description</button> + <button class="toggle-vis" data-column="5">Mean</button> + <button class="toggle-vis" data-column="6">Authors</button> + <button class="toggle-vis" data-column="7">Year</button> + <button class="toggle-vis" data-column="8">Sample {% if corr_method == 'pearson' %}r{% else %}rho{% endif %}</button> + <button class="toggle-vis" data-column="9">N</button> + <button class="toggle-vis" data-column="10">Sample p({% if corr_method == 'pearson' %}r{% else %}rho{% endif %})</button> + <button class="toggle-vis" data-column="11">Peak -logP</button> + <button class="toggle-vis" data-column="12">Peak Location</button> + <button class="toggle-vis" data-column="13">Effect Size</button> + {% else %} + <button class="toggle-vis" data-column="3">Location</button> + <button class="toggle-vis" data-column="4">Sample {% if corr_method == 'pearson' %}r{% else %}rho{% endif %}</button> + <button class="toggle-vis" data-column="5">N</button> + <button class="toggle-vis" data-column="6">Sample p({% if corr_method == 'pearson' %}r{% else %}rho{% endif %})</button> + {% endif %} + </div> + <div style="width: 90%; {% if target_dataset.type == "ProbeSet" %}min-width: 1700px;{% elif target_dataset.type == "Publish" %}min-width: 1600px;{% else %}width: 650px;{% endif %}"> + <table id="trait_table" class="table-hover table-striped cell-border" style="float: left;"> + <thead> + <tr> + <th></th> + {% for header in header_fields %} + <th {% if header != "" %}data-export="{{ header }}"{% endif %}>{{header}}</th> + {% endfor %} + </tr> + </thead> + + <tbody> + <td colspan="100%" align="center"><br><b><font size="15">Loading...</font></b><br></td> + </tbody> + </table> + </div> + </div> +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/md5.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='jszip/jszip.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/buttons/js/dataTables.buttons.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/buttons/js/buttons.html5.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/natural.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='fontawesome/js/all.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script> + <script type="text/javascript" src="/static/new/javascript/search_results.js"></script> + <script type="text/javascript" src="/static/new/javascript/table_functions.js"></script> + <script type="text/javascript" src="/static/new/javascript/create_datatable.js"></script> + + + <script type="text/javascript" charset="utf-8"> + var tableJson = {{ table_json | safe }} + </script> + + <script type="text/javascript" charset="utf-8"> + $.fn.dataTable.ext.order['dom-innertext'] = function (settings, col) { + return this.api().column(col, { order: 'index' }).nodes().map(function (td, i) { + return Math.abs(parseFloat($('a', td).text())); + }); + } + + $.fn.dataTableExt.oSort['numeric-html-asc'] = function(a,b) { + a = Math.abs(parseFloat(a)); + b = Math.abs(parseFloat(b)); + return ((a < b) ? -1 : ((a > b) ? 1 : 0)); + }; + + $.fn.dataTableExt.oSort['numeric-html-desc'] = function(a,b) { + a = Math.abs(parseFloat(a)); + b = Math.abs(parseFloat(b)); + return ((a < b) ? 1 : ((a > b) ? -1 : 0)); + }; + + $.fn.dataTableExt.oSort['scientific-asc'] = function ( a, b ) { + var x = parseFloat(a); + var y = parseFloat(b); + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + }; + + $.fn.dataTableExt.oSort['scientific-desc'] = function ( a, b ) { + var x = parseFloat(a); + var y = parseFloat(b); + return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + }; + + $.fn.dataTable.ext.search.push( function( settings, data, dataIndex ) { + var r_column = {{ filter_cols[0] }}; + var r_greater = parseFloat($('input[name=r_greater_select]').val()) + var r_less = parseFloat($('input[name=r_less_select]').val()); + var r_and_or = $('#r_and_or').val(); + + var mean_column = {{ filter_cols[1] }}; + var mean_greater = parseFloat($('input[name=mean_greater_select]').val()); + var mean_less = parseFloat($('input[name=mean_less_select]').val()); + var mean_and_or = $('#mean_and_or').val(); + + if (r_and_or == "and" && mean_and_or == "and"){ + if ( (data[r_column] >= r_greater && data[r_column] <= r_less) && {% if filter_cols[1] != 0 %}(data[mean_column] > mean_greater && data[mean_column] < mean_less){% else %} true{% endif %} ){ + return true + } + else { + return false + } + } else if (r_and_or == "and" && mean_and_or == "or"){ + if ( (data[r_column] >= r_greater && data[r_column] <= r_less) && {% if filter_cols[1] != 0 %}(data[mean_column] >= mean_greater || data[mean_column] <= mean_less){% else %} true{% endif %} ){ + return true + } else { + return false + } + } else if (r_and_or == "or" && mean_and_or == "and") { + if ( (data[r_column] >= r_greater || data[r_column] <= r_less) && {% if filter_cols[1] != 0 %}(data[mean_column] >= mean_greater && data[mean_column] <= mean_less){% else %} true{% endif %} ){ + return true + } else { + return false + } + } else { + if ( (data[r_column] >= r_greater || data[r_column] <= r_less) && {% if filter_cols[1] != 0 %}(data[mean_column] >= mean_greater || data[mean_column] <= mean_less){% else %} true{% endif %} ){ + return true + } else { + return false + } + } + return true + }); + + $(document).ready( function () { + + tableId = "trait_table"; + + {% if corr_method == 'pearson' %} + rOrRho = "r" + corr_method = "pearson" + {% else %} + rOrRho = "rho" + corr_method = "spearman" + {% endif %} + + columnDefs = [ + { + 'data': null, + 'width': "25px", + 'orderDataType': "dom-checkbox", + 'orderSequence': [ "desc", "asc"], + 'render': function(data) { + return '<input type="checkbox" name="searchResult" class="checkbox trait_checkbox" value="' + data.hmac + '">' + } + }, + { + 'title': "Index", + 'type': "natural", + 'width': "30px", + 'data': "index" + }, + { + 'title': "Record", + 'type': "natural-minus-na", + 'data': null, + 'width': "60px", + 'render': function(data) { + return '<a target="_blank" href="/show_trait?trait_id=' + data.trait_id + '&dataset=' + data.dataset + '">' + data.trait_id + '</a>' + } + }{% if target_dataset.type == 'ProbeSet' %}, + { + 'title': "Symbol", + 'type': "natural", + 'width': "120px", + 'data': "symbol" + }, + { + 'title': "Description", + 'type': "natural", + 'data': null, + 'render': function(data) { + try { + return decodeURIComponent(escape(data.description)) + } catch(err){ + return escape(data.description) + } + } + }, + { + 'title': "Location", + 'type': "natural-minus-na", + 'width': "125px", + 'data': "location" + }, + { + 'title': "Mean", + 'type': "natural-minus-na", + 'width': "40px", + 'data': "mean", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Sample " + rOrRho, + 'type': "natural-minus-na", + 'width': "40px", + 'data': null, + 'orderSequence': [ "desc", "asc"], + 'render': function(data) { + if (data.sample_r != "N/A") { + return "<a target\"_blank\" href=\"corr_scatter_plot?method=" + corr_method + "&dataid={{ dataid }}&dataset_1={% if this_dataset.name == 'Temp' %}Temp_{{ this_dataset.group }}{% else %}{{ this_dataset.name }}{% endif %}&dataset_2=" + data.dataset + "&trait_1={{ this_trait.name }}&trait_2=" + data.trait_id + "\">" + data.sample_r + "</a>" + } else { + return data.sample_r + } + } + }, + { + 'title': "N", + 'type': "natural-minus-na", + 'width': "40px", + 'data': "num_overlap", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Sample p(" + rOrRho + ")", + 'type': "scientific", + 'width': "65px", + 'data': "sample_p", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Lit " + rOrRho, + 'type': "natural-minus-na", + 'width': "40px", + 'data': "lit_corr", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Tissue " + rOrRho, + 'type': "natural-minus-na", + 'width': "40px", + 'data': "tissue_corr", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Tissue p(" + rOrRho + ")", + 'type': "natural-minus-na", + 'width': "40px", + 'data': "tissue_pvalue", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "<div style='text-align: right; padding-right: 10px;'>Peak</div> <div style='text-align: right;'>-logP <a href=\"{{ url_for('glossary_blueprint.glossary') }}#LRS\" target=\"_blank\" style=\"color: white;\"><sup style='color: #FF0000;'><i>?</i></sup></a></div>", + 'type': "natural-minus-na", + 'data': "lod_score", + 'width': "60px", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Peak Location", + 'type': "natural-minus-na", + 'width': "125px", + 'data': "lrs_location" + }, + { + 'title': "Effect Size<a href=\"http://gn1.genenetwork.org/glossary.html#A\" target=\"_blank\" style=\"color: white;\"> <i class=\"fa fa-info-circle\" aria-hidden=\"true\"></i></a>", + 'type': "natural-minus-na", + 'data': "additive", + 'width': "85px", + 'orderSequence': [ "desc", "asc"] + }{% elif target_dataset.type == 'Publish' %}, + { + 'title': "Abbreviation", + 'type': "natural", + 'data': null, + 'render': function(data) { + try { + return decodeURIComponent(escape(data.abbreviation_display)) + } catch(err){ + return data.abbreviation_display + } + } + }, + { + 'title': "Description", + 'type': "natural", + 'data': null, + 'render': function(data) { + try { + return decodeURIComponent(escape(data.description)) + } catch(err){ + return data.description + } + } + }, + { + 'title': "Mean", + 'type': "natural-minus-na", + 'width': "40px", + 'data': "mean", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Authors", + 'type': "natural", + 'width': "400px", + 'data': null, + 'render': function(data) { + try { + return decodeURIComponent(escape(data.authors_display)) + } catch(err){ + return data.authors_display + } + } + }, + { + 'title': "Year", + 'type': "natural-minus-na", + 'data': null, + 'width': "80px", + 'render': function(data) { + if (data.pubmed_link != "N/A"){ + return '<a href="' + data.pubmed_link + '">' + data.pubmed_text + '</a>' + } else { + return data.pubmed_text + } + }, + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Sample " + rOrRho, + 'type': "natural-minus-na", + 'width': "40px", + 'data': null, + 'orderSequence': [ "desc", "asc"], + 'render': function(data) { + if (data.sample_r != "N/A") { + return "<a target\"_blank\" href=\"corr_scatter_plot?method=" + corr_method + "&dataset_1={% if this_dataset.name== 'Temp' %}Temp_{{ this_dataset.group }}{% else %}{{ this_dataset.name }}{% endif %}&dataset_2=" + data.dataset + "&trait_1={{ this_trait.name }}&trait_2=" + data.trait_id + "\">" + data.sample_r + "</a>" + } else { + return data.sample_r + } + } + }, + { + 'title': "N", + 'type': "natural-minus-na", + 'width': "40px", + 'data': "num_overlap", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Sample p(" + rOrRho + ")", + 'type': "scientific", + 'width': "65px", + 'data': "sample_p", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "<div style='text-align: right; padding-right: 10px;'>Peak</div> <div style='text-align: right;'>-logP <a href=\"{{ url_for('glossary_blueprint.glossary') }}#LRS\" target=\"_blank\" style=\"color: white;\"><sup style='color: #FF0000;'><i>?</i></sup></a></div>", + 'type': "natural-minus-na", + 'data': "lod_score", + 'width': "60px", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Peak Location", + 'type': "natural-minus-na", + 'width': "160px", + 'data': "lrs_location" + }, + { + 'title': "Effect Size<a href=\"http://gn1.genenetwork.org/glossary.html#A\" target=\"_blank\" style=\"color: white;\"> <i class=\"fa fa-info-circle\" aria-hidden=\"true\"></i></a>", + 'type': "natural-minus-na", + 'data': "additive", + 'width': "85px", + 'orderSequence': [ "desc", "asc"] + }{% elif target_dataset.type == 'Geno' %}, + { + 'title': "Location", + 'type': "natural-minus-na", + 'width': "120px", + 'data': "location" + }, + { + 'title': "Sample " + rOrRho, + 'type': "natural-minus-na", + 'width': "40px", + 'data': null, + 'orderSequence': [ "desc", "asc"], + 'render': function(data) { + if (data.sample_r != "N/A") { + return "<a target\"_blank\" href=\"corr_scatter_plot?method=" + corr_method + "&dataset_1={% if this_dataset.name == 'Temp' %}Temp_{{ this_dataset.group }}{% else %}{{ this_dataset.name }}{% endif %}&dataset_2=" + data.dataset + "&trait_1={{ this_trait.name }}&trait_2=" + data.trait_id + "\">" + data.sample_r + "</a>" + } else { + return data.sample_r + } + } + }, + { + 'title': "N", + 'type': "natural-minus-na", + 'width': "40px", + 'data': "num_overlap", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Sample p(" + rOrRho + ")", + 'type': "scientific", + 'width': "65px", + 'data': "sample_p", + 'orderSequence': [ "desc", "asc"] + }{% endif %} + ] + + tableSettings = { + "buttons": [ + { + extend: 'csvHtml5', + text: 'Download <span class="glyphicon glyphicon-download"></span>', + className: 'btn btn-default', + exportOptions: { + columns: 'th:not(:first-child)' + } + } + ], + {% if table_json|length > 2000 %} + "scroller": false, + {% endif %} + {% if target_dataset.type == 'Geno' %} + "order": [[6, "asc" ]], + {% elif target_dataset.type == 'Publish' %} + "order": [[10, "asc" ]], + {% else %} + "order": [[9, "asc" ]], + {% endif %} + } + + create_table(tableId, tableJson, columnDefs, tableSettings) + + trait_table = $('#trait_table').DataTable(); + trait_table.buttons().container().appendTo('#export_options') + + $('.buttons-csv').removeClass('dt-button') + + submit_special = function(url) { + $("#correlation_form").attr("action", url); + return $("#correlation_form").submit(); + }; + + $("#delete").on("click", function() { + url = $(this).data("url") + return submit_special(url) + }); + + $("#more_options").click(function() { + $("div#filter_options").toggle(); + }); + + $("#select_traits").click(function() { + trait_table.draw(); + }); + }); + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/credits.html b/gn2/wqflask/templates/credits.html new file mode 100644 index 00000000..aab1dfb1 --- /dev/null +++ b/gn2/wqflask/templates/credits.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} +{% block title %}Credit{% endblock %} +{% block content %} +<Table width= "100%" cellSpacing=0 cellPadding=5 style="margin: 10px;"><TR> +<!-- Body Start from Here --> +<TD valign="top" height="200" width="100%"> + <P class="title"><H2>Web site design and coding</H2></P> + <UL> +<LI> <A HREF="mailto:rwilliams@uthsc.edu">Robert W Williams</a> +<LI>Kenneth Manly (design and QTL mapping, 1995-2007) +<LI>Jintao Wang (lead programmer, 2001–2006) +<LI><a href="http://www.nervenet.org/main/people.html">Arthur G. Centeno (IT Analyst III, 2001–present)</a> +<LI><a href="http://www.nervenet.org/main/people.html">Zachary Sloan (IT Analyst III, 2009–present)</a> +<LI>Frederick Muriithi (programmer, 2017-present)</LI> +<LI>Bonface Munyoki (programmer, 2020-present)</LI> +<LI>Alexander Kabua (programmer, 2020-present)</LI> +<LI>Arun Isaac (programmer, 2021-present)</LI> +<LI><a href="http://www.nervenet.org/main/people.html">Lei Yan (systems and web services interface 2008-2018)</a> +<LI><a href="http://www.nervenet.org/main/people.html">Xusheng Wang (data analysis, 2008-2012)</a> +<LI><a href="http://www.nervenet.org/main/people.html">Xiaodong Zhou (lead programmer 2009–2011)</a> +<LI>Ning Liu (programmer 2008–2009)</LI> +<LI>Zhaohui Sun (SNP browser, programmer 2007) +<LI>Yanhua Qu (data entry, 2005-2008) +<LI>Stephen Pitts (programmer) +<LI>Hongqiang Li (lead programmer, 2007-2009) +<LI><A HREF="http://www.jax.org/news/archives/2009/chesler.html">Elissa Chesler</A> (design of QTL Heat Map, Compare Correlations, 2004-2006) +<LI>Kevitt Adler (systems, 2006-2008) +<LI>Robert Crowell (SNP Browser, 2006-2007) +<LI>David Crowell (partial correlations, 2008-2009) +<LI>Evan G. Williams (SNP and Variant Browser, data entry, 2004-2006) +<LI>Alex G Williams (QTL Maps GUI, 2003-2006) + </UL> + <P class="title"><H2>Published and Unpublished Phenotype Data</H2></P> + <UL> + <LI><A HREF="http://www.nervenet.org/people/lulu_cv.html">Lu Lu</A> + <LI> <A HREF="http://www.jax.org/news/archives/2009/chesler.html">Elissa J. Chesler</A> + <LI><span class="broken-link" href="http://www.ohsu.edu/som-BehNeuro/Faculty/Crabbe.html">John C Crabbe</span>, OHSU + <LI><span class="broken-link" href="http://www.ohsu.edu/som-BehNeuro/Faculty/Belknap.html">John K Belknap</span>, OHSU + <LI>Mary-Kathleen Sullivan + <LI>Emily English + <LI>Byron Jones + <LI>Ryan McNieve + <LI>Nathan Copeland + </UL> + <P class="title"><H2>Genotype / Genomic Data</H2></P> + <UL> + <LI> <A HREF="http://www.nervenet.org/people/lulu_cv.html">Lu Lu</A> + <LI><a href="http://www.nervenet.org/people/Gu_cv.html">Jing Gu</a> + <LI>Shuhua Qi + <LI>John Hogenesch + <LI>Timothy Wiltshire + <LI><a href="http://www.nervenet.org/people/Yanhua_cv.html">Yanhua Qu</a> + </UL> + <P></P> +</TD> +</TR></TABLE> + +{% endblock %} diff --git a/gn2/wqflask/templates/ctl_results.html b/gn2/wqflask/templates/ctl_results.html new file mode 100644 index 00000000..1c31b499 --- /dev/null +++ b/gn2/wqflask/templates/ctl_results.html @@ -0,0 +1,77 @@ +{% extends "base.html" %} +{% block css %} + <link rel="stylesheet" type="text/css" href="/static/new/css/network_graph.css" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='cytoscape-panzoom/cytoscape.js-panzoom.css') }}"> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='qtip2/jquery.qtip.min.css') }}"> + <style> + /* The Cytoscape Web container must have its dimensions set. */ + html, body { height: 100%; width: 100%; padding: 0; margin: 0; } + #cytoscapeweb { width: 70%; height: 70%; } + </style> +{% endblock %} + +{% block title %}CTL results{% endblock %} + +{% block content %} <!-- Start of body --> +<div class="container"> + <h1>CTL Results</h1> + {{(request.form['trait_list'].split(',')|length)}} phenotypes as input<br> + + <!-- + <a href="/tmp/{{ results['imgurl1'] }}"> + <img alt="Embedded Image" src="data:image/png;base64, + {% for elem in results['imgdata1'] -%} + {% print("%c"|format(elem)) %} + {%- endfor %} + " /></a> --> + + <h3>CTL/QTL Plots for individual traits</h3> + {% for r in range(2, (request.form['trait_list'].split(',')|length +1)) %} + <a href="/tmp/{{ results['imgurl' + r|string] }}"> + <img width=100 height=100 alt="Embedded Image" src="data:image/png;base64, + {% for elem in results['imgdata' + r|string] -%} + {% print("%c"|format(elem)) %} + {%- endfor %} + " /></a> + {% endfor %} + <h3>Tabular results</h3> + <table width="80%"> + <tr><th>trait</th><th>marker</th><th>trait</th><th>LOD</th><th>dCor</th></tr> + significant CTL:<br> + {% for r in range(results['ctlresult'][0]|length) %} + <tr> + {% for c in range(results['ctlresult']|length) %} + <td> + {% if c > 2 %} + {{results['ctlresult'][c][r]|float|round(2)}} + {% else %} + {{results['ctlresult'][c][r]}} + {% endif %} + </td> + {% endfor %} + </tr> + {% endfor %} + </table> + <h3>Network Figure</h3> + <div id="cytoscapeweb" class="col-xs-9" style="min-height:700px !important;"></div> +</div> +{% endblock %} + +{% block js %} + + <script> + elements_list = {{ elements | safe }} + gn2_url = "{{ gn2_url | safe }}" + </script> + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='qtip2/jquery.qtip.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='cytoscape/cytoscape.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='cytoscape-panzoom/cytoscape-panzoom.js') }}"></script> + <!-- should be using cytoscape-popper for tips, see docs --> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='cytoscape-qtip/cytoscape-qtip.js') }}"></script> + + <script language="javascript" type="text/javascript" src="/static/new/javascript/ctl_graph.js"></script> + +{% endblock %} diff --git a/gn2/wqflask/templates/ctl_setup.html b/gn2/wqflask/templates/ctl_setup.html new file mode 100644 index 00000000..f5b0baf8 --- /dev/null +++ b/gn2/wqflask/templates/ctl_setup.html @@ -0,0 +1,70 @@ +{% extends "base.html" %} +{% block title %}CTL analysis{% endblock %} +{% block content %} +<!-- Start of body --> +<div class="container"> + {% if request.form['trait_list'].split(",")|length < 2 %} <div class="alert alert-danger" role="alert"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> + <span class="sr-only">Error:</span> + <h2>Too few traits as input</h2> + Please make sure you select enough traits to perform CTL. Your collection needs to contain at least 2 different traits. You provided {{request.form['trait_list'].split(',')|length}} traits as input. +</div> +{% else %} +<h1>CTL analysis</h1> +CTL analysis is published as open source software, if you are using this method in your publications, please cite:<br><br> +Arends D, Li Y, Brockmann GA, Jansen RC, Williams RW, Prins P<br> +Correlation trait locus (CTL) mapping: Phenotype network inference subject to genotype.<br> +The Journal of Open Source Software (2016)<br> +Published in <a href="http://joss.theoj.org/papers/10.21105/joss.00087"><img src="http://joss.theoj.org/papers/10.21105/joss.00087/status.svg"></a> +<br><br> +<form class="col-md-8" action="/ctl_results" method="post" class="form-horizontal" id="ctl_form"> + <input type="hidden" name="trait_list" id="trait_list" value="{{request.form['trait_list']}}"> + <div class="form-group row"> + <label for="Strategy" class="col-md-3 col-form-label col-form-label-sm">Strategy</label> + <div class="col-md-9"> + <select class="form-control col-md-9" name="strategy" id="strategy"> + <option value="Exact" selected="selected">Exact</option> + <option value="Full">Full</option> + <option value="Pairwise">Pairwise</option> + </select> + </div> + </div> + <div class="form-group row"> + <label for="corType" class="col-md-3 col-form-label col-form-label-sm">Perform parametric analysis</label> + <div class="col-md-9"> + <select class="form-control col-md-9" name="parametric" id="parametric"> + <option value="True" selected="selected">True</option> + <option value="False">False</option> + </select> + </div> + </div> + <div class="form-group row"> + <label for="Permutations" class="col-md-3 col-form-label col-form-label-sm">Number of permutation <span style="color:red;">(Used when strategy is Full or Pairwise)</span></label> + <div class="col-md-9"> + <select class="form-control" name="nperm" id="nperm"> + <option value="100">100</option> + <option value="1000" selected="selected">1000</option> + <option value="10000">10000</option> + </select> + </div> + </div> + <div class="form-group row"> + <label for="Significance" class="col-md-3 col-form-label col-form-label-sm"> Significance level</label> + <div class="col-md-9"> + <select class="form-control" name="significance" id="significance"> + <option value="0.1">0.1</option> + <option value="0.05" selected="selected">0.05</option> + <option value="0.001">0.001</option> + </select> + </div> + </div> + <div class="form-group"> + <div class="text-center"> + <input type="submit" class="btn btn-primary" value="Run CTL using these settings" /> + </div> + </div> +</form> +{% endif %} +</div> +{% endblock %} + diff --git a/gn2/wqflask/templates/data_sharing.html b/gn2/wqflask/templates/data_sharing.html new file mode 100644 index 00000000..cca498ec --- /dev/null +++ b/gn2/wqflask/templates/data_sharing.html @@ -0,0 +1,262 @@ +{% extends "base.html" %} +{% block title %}Data Sharing{% endblock %} +{% block content %} + + <!-- Start of body --> + <TR> + <TD bgColor=#eeeeee class="solidBorder"> + <Table width= "100%" cellSpacing=0 cellPadding=5> + <TR> + <td> +<a href="/webqtl/main.py?FormID=sharingListDataset">List of DataSets</a><br> +<H1 class="title" id="parent-fieldname-title">{{ info.DB_Name }} +<a href="/webqtl/main.py?FormID=sharinginfoedit&GN_AccessionId={{ info.GN_AccesionId }}"><img src="/images/modify.gif" alt="modify this page" border="0" valign="middle"></a> +<span style="color:red;"></span> +</H1> +<table border="0" width="100%"> +<tr> +<td valign="top" width="50%"> +<TABLE cellSpacing=0 cellPadding=5 width=100% border=0> + <TR><td><b>GN Accession:</b> {{ info.GN_AccesionId }}</TD></tr> + <TR><TD><b>GEO Series:</b> {{ info.GEO_Series }}</TD></TR> + <TR><TD><b>Title:</b> {{ info.Title }}</TD></TR> + <TR><TD><b>Organism:</b> <a href=http://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?mode=Info&id={{ info.Organism_Id }}>{{ info.Organism }}</a></TD></tr> + <tr><TD><b>Group:</b> {{ info.InbredSet }}</TD></TR> + <TR><TD><b>Tissue:</b> {{ info.Tissue }}</TD></tr> + <tr><TD><b>Dataset Status:</b> {{ info.Status }}</TD></tr> + <TR><TD><b>Platforms:</b> {{ info.Platforms }}</TD></TR> + <TR><TD><b>Normalization:</b> {{ info.Normalization }}</TD></TR> + <TR><TD><!--Code below to Show hide Contact information --> + <a href="#" onclick="colapse('answer1')">See Contact Information</a><br> + <span id="answer1" style="display: none; return: false;"> + {{ info.Contact_Name }}<br> + {{ info.Organization_Name }}<br> + {{ info.Department }}<br> + {{ info.Street }}<br> + {{ info.City }}, {{ info.State }} {{ info.ZIP }} {{ info.Country }}<br> + Tel. {{ info.Phone }}<br> + {{ info.Emails }}<br> + <a href="{{ info.URL }}">{{ info.URL }}</a> + </span><!--Code above to Show hide Contact information --></TD></TR> +</TABLE> +</td> +<td valign="top" width="50%"> +<table border="0" width="100%"> +<tr> + <td bgcolor="#dce4e1"><b>Download datasets and supplementary data files</b></td> +</tr> +<tr> + <td>{{ htmlfilelist|safe }}</td> +</tr> +</table> +</td> +</tr> +</table> +<HR> +<p> +<table width="100%" border="0" cellpadding="5" cellspacing="0"> +<tr><td><span style="font-size:115%;font-weight:bold;">Summary:</span></td></tr> + <tr><td>{{ info.Summary | safe}}<br><br></td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">About the cases used to generate this set of data:</span></td></tr> + <tr><td>{{ info.About_Cases | safe}}</td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">About the tissue used to generate this set of data:</span></td></tr> + <tr><td>{{ info.About_Tissue | safe }}</td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">About downloading this data set:</span></td></tr> + <tr><td> <P>All data links (right-most column above) will be made active as sooon as the global analysis of these data by the Consortium has been accepted for publication. Please see text on <A HREF="http://www.genenetwork.org/dataSharing.html" target="_empty" class="normalsize">Data Sharing Policies</A>, and <A HREF="http://www.genenetwork.org/conditionsofUse.html" target="_empty" class="normalsize">Conditions and Limitations</A>, and <A HREF="http://www.genenetwork.org/statusandContact.html" target="_empty" class="normalsize">Contacts</A>. Following publication, download a summary text file or Excel file of the PDNN probe set data. Contact RW Williams regarding data access problems. +</P><br><br></td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">About the array platform:</span></td></tr> + <tr><td> <P><B>Affymetrix Mouse Genome 430 2.0 array: </B>The <A HREF="http://www.affymetrix.com/support/technical/byproduct.affx?product=moe430-20" target="_blank" class="normalsize">430v2</A> array consists of 992936 useful 25-nucleotide probes that estimate the expression of approximately 39,000 transcripts and the majority of known genes and expressed sequence tags. The array sequences were selected late in 2002 using Unigene Build 107 by Affymetrix. The UTHSC group has recently reannotated all probe sets on this array, producing more accurate data on probe and probe set targets. All probes were aligned to the most recent assembly of the Mouse Genome (Build 34, mm6) using Jim Kent's BLAT program. Many of the probe sets have been manually curated by Jing Gu and Rob Williams. </P><br><br></td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">About data values and data processing:</span></td></tr> + <tr><td> <A HREF="http://www.biomedcentral.com/1471-2105/6/65" target="_empty" class="normalsize">Harshlight</A> was used to examine the image quality of the array (CEL files). Bad areas (bubbles, scratches, blemishes) of arrays were masked. + +<P>First pass data quality control: Affymetrix GCOS provides useful array quality control data including: +<OL> +<LI>The scale factor used to normalize mean probe intensity. This averaged 3.3 for the 179 arrays that passed and 6.2 for arrays that were excluded. The scale factor is not a particular critical parameter. +<LI>The average background level. Values averaged 54.8 units for the data sets that passed and 55.8 for data sets that were excluded. This factor is not important for quality control. +<LI>The percentage of probe sets that are associated with good signal ("present" calls). This averaged 50% for the 179 data sets that passed and 42% for those that failed. Values for passing data sets extended from 43% to 55%. This is a particularly important criterion. +<LI>The 3':5' signal ratios of actin and Gapdh. Values for passing data sets averaged 1.5 for actin and 1.0 for Gapdh. Values for excluded data sets averaged 12.9 for actin and 9.6 for Gapdh. This is a highly discriminative QC criterion, although one must keep in mind that only two transcripts are being tested. Sequence variation among strains (particularly wild derivative strains such as CAST/Ei) may affect these ratios. +</OL> + +<P>The second step in our post-processing QC involves a count of the number of probe sets in each array that are more than 2 standard deviations (z score units) from the mean across the entire 206 array data sets. This was the most important criterion used to eliminate "bad" data sets. All 206 arrays were processed togther using standard RMA and PDNN methods. The count and percentage of probe sets in each array that were beyond the 2 z theshold was computed. Using the RMA transform the average percentage of probe sets beyond the 2 z threshold for the 179 arrays that finally passed of QC procedure was 1.76% (median of 1.18%). In contrast the 2 z percentage was more than 10-fold higher (mean of 22.4% and median 20.2%) for those arrays that were excluded. This method is not very sensitive to the transformation method that is used. Using the PDNN transform, the average percent of probe sets exceeding was 1.31% for good arrays and was 22.6% for those that were excluded. In our opinion, this 2 z criterion is the most useful criterion for the final decision of whether or not to include arrays, although again, allowances need to be made for wild strains that one expects to be different from the majority of conventional inbred strains. For example, if a data set has excellent characteristics on all of the Affymetrix GCOS metrics listed above, but generates a high 2 z percentage, then one would include the sample if one can verify that there are no problems in sample and data set identification. + +<P>The entire procedure can be reapplied once the initial outlier data sets have been eliminated to detect any remaining outlier data sets. + + +<P><span class="broken_link" HREF="http://www.datadesk.com/products/data_analysis/datadesk/" target="_empty" class="normalsize">DataDesk</span> was used to examine the statistical quality of the probe level (CEL) data after step 5 below. DataDesk allows the rapid detection of subsets of probes that are particularly sensitive to still unknown factors in array processing. Arrays can then be categorized at the probe level into "reaction classes." A reaction class is a group of arrays for which the expression of essentially all probes are colinear over the full range of log2 values. A single but large group of arrays (n = 32) processed in essentially the identical manner by a single operator can produce arrays belonging to as many as four different reaction classes. Reaction classes are NOT related to strain, age, sex, treatment, or any known biological parameter (technical replicates can belong to different reaction classes). We do not yet understand the technical origins of reaction classes. The number of probes that contribute to the definition of reaction classes is quite small (<10% of all probes). We have categorized all arrays in this data set into one of 5 reaction classes. These have then been treated as if they were separate batches. Probes in these data type "batches" have been aligned to a common mean as described below. + +<P><B>Probe (cell) level data from the CEL file: </B>These CEL values produced by <span href="http://www.affymetrix.com/support/technical/product_updates/gcos_download.affx" target="_blank" class="normalsize broken_link">GCOS</span> are 75% quantiles from a set of 91 pixel values per cell. +<OL> + +<LI>We added an offset of 1.0 unit to each cell signal to ensure that all values could be logged without generating negative values. We then computed the log base 2 of each cell. + +<LI>We performed a quantile normalization of the log base 2 values for all arrays using the same initial steps used by the RMA transform. + +<LI>We computed the Z scores for each cell value. + +<LI>We multiplied all Z scores by 2. + +<LI>We added 8 to the value of all Z scores. The consequence of this simple set of transformations is to produce a set of Z scores that have a mean of 8, a variance of 4, and a standard deviation of 2. The advantage of this modified Z score is that a two-fold difference in expression level (probe brightness level) corresponds approximately to a 1 unit difference. + +<LI>Finally, we computed the arithmetic mean of the values for the set of microarrays for each strain. Technical replicates were averaged before computing the mean for independent biological samples. Note, that we have not (yet) corrected for variance introduced by differences in sex or any interaction terms. We have not corrected for background beyond the background correction implemented by Affymetrix in generating the CEL file. We eventually hope to add statistical controls and adjustments for some of these variables. +</OL> +<P><B>Probe set data from the CHP file: </B>The expression values were generated using PDNN. The same simple steps described above were also applied to these values. Every microarray data set therefore has a mean expression of 8 with a standard deviation of 2. A 1 unit difference represents roughly a two-fold difference in expression level. Expression levels below 5 are usually close to background noise levels. </Blockquote> + + +<P>Probe level QC: Log2 probe data of all arrays were inspected in DataDesk before and after quantile normalization. Inspection involved examining scatterplots of pairs of arrays for signal homogeneity (i.e., high correlation and linearity of the bivariate plots) and looking at all pairs of correlation coefficients. XY plots of probe expression and signal variance were also examined. Probe level array data sets were organized into reaction groups. Arrays with probe data that were not homogeneous when compared to other arrays were flagged. + +<P>Probe set level QC: The final normalized individual array data were evaluated for outliers. This involved counting the number of times that the probe set value for a particular array was beyond two standard deviations of the mean. This outlier analysis was carried out using the PDNN, RMA and MAS5 transforms and outliers across different levels of expression. Arrays that were associated with an average of more than 8% outlier probe sets across all transforms and at all expression levels were eliminated. In contrast, most other arrays generated fewer than 5% outliers. + + +<P>Validation of strains and sex of each array data set: A subset of probes and probe sets with a Mendelian pattern of inheritance were used to construct a expression correlation matrix for all arrays and the ideal Mendelian expectation for each strain constructed from the genotypes. There should naturally be a very high correlation in the expression patterns of transcripts with Mendelian phenotypes within each strain, as well as with the genotype strain distribution pattern of markers for the strain. + +<P>Sex of the samples was validated using sex-specific probe sets such as <I>Xist</I> and <I>Dby</I>.<br><br></td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">Data source acknowledgment:</span></td></tr> + <tr><td> <P>Data were generated with funds provided by a variety of public and private source to members of the Consortium. All of us thank Muriel Davisson, Cathy Lutz, and colleagues at the Jackson Laboratory for making it possible for us to add all of the CXB strains, and one or more samples from KK/HIJ, WSB/Ei, NZO/HILtJ, LG/J, CAST/Ei, PWD/PhJ, and PWK/PhJ to this study. We thank Yan Cui at UTHSC for allowing us to use his Linux cluster to align all M430 2.0 probes and probe sets to the mouse genome. We thank Hui-Chen Hsu and John Mountz for providing us BXD tissue samples, as well as many strains of BXD stock. We thanks Douglas Matthews (UMem in Table 1) and John Boughter (JBo in Table 1) for sharing BXD stock with us. Members of the Hippocampus Consortium thank the following sources for financial support of this effort: + +<UL> +<LI>David C. Airey, Ph.D. <!--$5,000 contribution --> +<BR>Grant Support: Vanderbilt Institute for Integratie Genomics +<BR>Department of Pharmacology +<BR>david.airey at vanderbilt.edu + +<LI>Lu Lu, M.D. <!--Tissue acquisition, RNA processing, experimental design--> +<BR>Grant Support: NIH U01AA13499, U24AA13513 + +<LI><span HREF="http://www.salk.edu/faculty/faculty/details.php?id=23" target="_empty" class="broken_link normalsize">Fred H. Gage, Ph.D.</span> <!--$10,000 contribution --> +<BR>Grant Support: Lookout Foundation + +<LI>Dan Goldowitz, Ph.D. <!--$30,000 contribution --> +<BR>Grant Support: NIAAA INIA AA013503 +<BR>University of Tennessee Health Science Center +<BR>Dept. Anatomy and Neurobiology +<BR>email: dgold@nb.utmem.edu + +<LI>Shirlean Goodwin, Ph.D. <!--All array processing--> +<BR>Grant Support: NIAAA INIA U01AA013515 + +<LI><span HREF="http://www.bccn-berlin.de/ResearchGroups/Kempermann" class="broken_link normalsize">Gerd Kempermann, M.D.</span> <!--$30,000 contribution --> +<BR>Grant Support: The <A HREF="http://www.volkswagen-stiftung.de/" target="_empty" class="normalsize">Volkswagen Foundation</A> Grant on Permissive and Persistent Factors in Neurogenesis in the Adult Central Nervous System +<BR>Humboldt-Universitat Berlin +<BR>Universitatsklinikum Charite +<BR>email: gerd.kempermann at mdc-berlin.de + +<LI>Kenneth F. Manly, Ph.D. <!--Data handling in The GeneNetwork--> +<BR>Grant Support: NIH P20MH062009 and U01CA105417 + +<LI>Richard S. Nowakowski, Ph.D. <!--$10,000 contribution--> +<BR>Grant Support: R01 NS049445-01 + +<LI>Glenn D. Rosen, Ph.D. <!--Tissue and dissections--> +<BR>Grant Support: NIH P20 + +<LI>Leonard C. Schalkwyk, Ph.D. <!--$5,000 contribution --> +<BR>Grant Support: MRC Career Establishment Grant G0000170 +<BR>Social, Genetic and Developmental Psychiatry +<BR>Institute of Psychiatry,Kings College London +<BR>PO82, De Crespigny Park London SE5 8AF +<BR>L.Schalkwyk@iop.kcl.ac.uk + +<LI>Guus Smit, Ph.D. <!--$6,000 contribution --> +<BR>Dutch NeuroBsik Mouse Phenomics Consortium +<BR>Center for Neurogenomics & Cognitive Research +<BR>Vrije Universiteit Amsterdam, The Netherlands +<BR>e-mail: guus.smit at falw.vu.nl +<BR>Grant Support: BSIK 03053 + +<LI>Thomas Sutter, Ph.D. <!--All array handling and ~$20,000 for array chemistry --> +<BR>Grant Support: INIA U01 AA13515 and the W. Harry Feinstone Center for Genome Research + +<LI>Stephen Whatley, Ph.D. <!--$5,000 contribution --> +<BR>Grant Support: XXXX + +<LI>Robert W. Williams, Ph.D. <!--Consortium director, design, error checking, metadata, and GeneNetwork--> +<BR>Grant Support: NIH U01AA013499, P20MH062009, U01AA013499, U01AA013513 +</UL> +</P><br><br></td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">Experiment Type:</span></td></tr> + <tr><td> <P>Pooled RNA samples (usually one pool of male hippocampii and one pool of female hippocampii) were prepared using standard protocols. Samples were processed using a total of 206 Affymetrix GeneChip Mouse Expression 430 2.0 short oligomer arrays (MOE430 2.0 or M430v2; see GEO platform ID <A HREF="http://www.ncbi.nlm.nih.gov/projects/geo/query/acc.cgi?acc=GPL1261" target="_empty" class="normalsize">GPL1261</A>), of which 201 passed quality control and error checking. This particular data set was processed using the <span href="http://odin.mdacc.tmc.edu/~zhangli/PerfectMatch/" target="_blank" class="broken_link normalsize">PDNN</span> protocol. To simplify comparisons among transforms, PDNN values of each array were adjusted to an average of 8 units and a standard deviation of 2 units. +<br><br></td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">Overall Design:</span></td></tr> + <tr><td> <P>Pooled RNA samples (usually one pool of male hippocampii and one pool of female hippocampii) were prepared using standard protocols. Samples were processed using a total of 206 Affymetrix GeneChip Mouse Expression 430 2.0 short oligomer arrays (MOE430 2.0 or M430v2; see GEO platform ID <A HREF="http://www.ncbi.nlm.nih.gov/projects/geo/query/acc.cgi?acc=GPL1261" target="_empty" class="normalsize">GPL1261</A>), of which 201 passed quality control and error checking. This particular data set was processed using the <span href="http://odin.mdacc.tmc.edu/~zhangli/PerfectMatch/" target="_blank" class="broken_link normalsize">PDNN</span> protocol. To simplify comparisons among transforms, PDNN values of each array were adjusted to an average of 8 units and a standard deviation of 2 units. +<br><br></td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">Contributor:</span></td></tr> + <tr><td> <UL> +<LI>David C. Airey, Ph.D. <!--$5,000 contribution --> +<BR>Grant Support: Vanderbilt Institute for Integratie Genomics +<BR>Department of Pharmacology +<BR>david.airey at vanderbilt.edu + +<LI>Lu Lu, M.D. <!--Tissue acquisition, RNA processing, experimental design--> +<BR>Grant Support: NIH U01AA13499, U24AA13513 + +<LI><span HREF="http://www.salk.edu/faculty/faculty/details.php?id=23" target="_empty" class="broken_link normalsize">Fred H. Gage, Ph.D.</span> <!--$10,000 contribution --> +<BR>Grant Support: Lookout Foundation + +<LI>Dan Goldowitz, Ph.D. <!--$30,000 contribution --> +<BR>Grant Support: NIAAA INIA AA013503 +<BR>University of Tennessee Health Science Center +<BR>Dept. Anatomy and Neurobiology +<BR>email: dgold@nb.utmem.edu + +<LI>Shirlean Goodwin, Ph.D. <!--All array processing--> +<BR>Grant Support: NIAAA INIA U01AA013515 + +<LI><span HREF="http://www.bccn-berlin.de/ResearchGroups/Kempermann" class="broken_link normalsize">Gerd Kempermann, M.D.</span> <!--$30,000 contribution --> +<BR>Grant Support: The <A HREF="http://www.volkswagen-stiftung.de/" target="_empty" class="normalsize">Volkswagen Foundation</A> Grant on Permissive and Persistent Factors in Neurogenesis in the Adult Central Nervous System +<BR>Humboldt-Universitat Berlin +<BR>Universitatsklinikum Charite +<BR>email: gerd.kempermann at mdc-berlin.de + +<LI>Kenneth F. Manly, Ph.D. <!--Data handling in The GeneNetwork--> +<BR>Grant Support: NIH P20MH062009 and U01CA105417 + +<LI>Richard S. Nowakowski, Ph.D. <!--$10,000 contribution--> +<BR>Grant Support: R01 NS049445-01 + +<LI>Glenn D. Rosen, Ph.D. <!--Tissue and dissections--> +<BR>Grant Support: NIH P20 + +<LI>Leonard C. Schalkwyk, Ph.D. <!--$5,000 contribution --> +<BR>Grant Support: MRC Career Establishment Grant G0000170 +<BR>Social, Genetic and Developmental Psychiatry +<BR>Institute of Psychiatry,Kings College London +<BR>PO82, De Crespigny Park London SE5 8AF +<BR>L.Schalkwyk@iop.kcl.ac.uk + +<LI>Guus Smit, Ph.D. <!--$6,000 contribution --> +<BR>Dutch NeuroBsik Mouse Phenomics Consortium +<BR>Center for Neurogenomics & Cognitive Research +<BR>Vrije Universiteit Amsterdam, The Netherlands +<BR>e-mail: guus.smit at falw.vu.nl +<BR>Grant Support: BSIK 03053 + +<LI>Thomas Sutter, Ph.D. <!--All array handling and ~$20,000 for array chemistry --> +<BR>Grant Support: INIA U01 AA13515 and the W. Harry Feinstone Center for Genome Research + +<LI>Stephen Whatley, Ph.D. <!--$5,000 contribution --> +<BR>Grant Support: XXXX + +<LI>Robert W. Williams, Ph.D. <!--Consortium director, design, error checking, metadata, and GeneNetwork--> +<BR>Grant Support: NIH U01AA013499, P20MH062009, U01AA013499, U01AA013513 +</UL><br><br></td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">Citation:</span></td></tr> + <tr><td> +<P>Please cite: Overall RW, Kempermann G, Peirce J, Lu L, Goldowitz D, Gage FH, Goodwin S, Smit AB, Airey DC, Rosen GD, Schalkwyk LC, Sutter TR, Nowakowski RS, Whatley S, Williams RW (<span class="broken_link" href="http://frontiersin.org/neurogenomics/paper/pending/0/815/" target="_blank" class="normalsize">2009</span>) Genetics of the hippocampal transcriptome in mice: a systematic survey and online neurogenomic resource. Front. Neurogen. 1:3 <span href="http://frontiersin.org/neurogenomics/paper/pending/0/815/" target="_blank" class="broken_link smallsize"><I>Full Text HTML</I></A> doi:10.3389/neuro.15.003.2009 + +<br><br></td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">Submission Date:</span></td></tr> + <tr><td> 01 Jul. 2009<br><br></td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">Laboratory:</span></td></tr> + <tr><td> Williams and Lu Labs<br><br></td></tr> +<tr><td><span style="font-size:115%;font-weight:bold;">Samples:</span></td></tr> + <tr><td> None<br><br></td></tr> +</table> +</p> +</td> + + </TR> + </TABLE> + </TD> + </TR> + <!-- End of body --> +{% endblock %} diff --git a/gn2/wqflask/templates/dataset.html b/gn2/wqflask/templates/dataset.html new file mode 100644 index 00000000..2e22be17 --- /dev/null +++ b/gn2/wqflask/templates/dataset.html @@ -0,0 +1,107 @@ +{% extends "base.html" %} + +{% block css %} +<style type="text/css"> + .page-header, .dataset-content { + padding-left: 10%; + line-height: 1.375; + } + + .dataset-content blockquote { + font-size: 13px; + } + .page-header h1 { + font-size: 1.8em; + line-height: 1.375; + } + .panel-about { + background: #F8F8F8; + max-width: 35em; + margin: 10px; + } + .panel-metadata { + display: inline-block; + width: fit-content; + height: fit-content; + padding: 0; + {% if dataset.description or dataset.specificity or dataset.experimentDesignInfo or + dataset.caseInfo or dataset.tissue or + dataset.platform or dataset.processingInfo or + dataset.notes or dataset.references or dataset.acknowledgment or dataset.contributors + %} + float: right; + {% endif %} + } + + .panel-metadata dt { + color: green; + } + + .panel-metadata dt::after { + content: ":"; + } + + .search { + width: 50%; + margin: 1em auto; + } + .has-search .form-control-feedback { + right: initial; + left: 0; + color: #ccc; + } + + .has-search .form-control { + padding-right: 12px; + padding-left: 34px; + } + + .search { + transform: scale(1.5, 1.5); + } + .search input { + min-width: 17em; + } + .dataset-search { + padding: 0 17%; + } +</style> +{% endblock %} + +{% block title %}Dataset: {{ name }}{% endblock %} +{% block content %} + +{% if dataset %} + +{% include 'metadata/dataset.html' %} + +{% else %} +<div class="container dataset-search"> + <p class="lead">We appreciate your interest, but unfortunately, we don't have any additional information available for: <strong>{{ name }}</strong>. If you have other inquiries or need assistance with something else, please don't hesitate to get in touch with us. <b><i>In the meantime you can explore other datasets here:</i></b></p>. + + <!-- Actual search box --> + <div class="search"> + <form class="form-group has-feedback has-search" + hx-post="/datasets/search" + hx-target="#search-results"> + <span class="glyphicon glyphicon-search form-control-feedback"></span> + <input class="form-control" + type="search" + name="search" placeholder="Start your dataset search here"> + </form> + </div> + + <div id="search-results"></div> + +</div> + + +{% endif %} + +{% endblock %} + +{% block js %} +<script language="javascript" + type="text/javascript" + src="{{ url_for('js', filename='htmx.min.js') }}"></script> +{% endblock %} diff --git a/gn2/wqflask/templates/display_diffs.html b/gn2/wqflask/templates/display_diffs.html new file mode 100644 index 00000000..ce50c1b4 --- /dev/null +++ b/gn2/wqflask/templates/display_diffs.html @@ -0,0 +1,95 @@ +{% extends "base.html" %} +{% block title %}Trait Submission{% endblock %} + +{% block css %} +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> +{% endblock %} + +{% block content %} +<!-- Start of body --> +<div class="container"> + {% set additions = diff.get("Additions") %} + {% set modifications = diff.get("Modifications") %} + {% set deletions = diff.get("Deletions") %} + {% set header = diff.get("Columns", "Strain Name,Value,SE,Count") %} + {% if additions %} + <h2>Additions Data:</h2> + <div class="row"> + <div class="col-md-8"> + <table class="table-responsive table-hover table-striped cell-border" id="table-additions"> + <thead> + <th scope="col">Added Data ({{ header }})</th> + </thead> + <tbody> + {% for data in additions %} + <tr> + <td>{{ data }}</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + {% endif %} + + {% if modifications %} + <h2>Modified Data:</h2> + + <div class="row"> + <div class="col-md-8"> + <table class="table-responsive table-hover table-striped cell-border" id="table-modifications"> + <thead> + <th scope="col">Original</th> + <th scope="col">Current</th> + <th scope="col">Diff ({{ header }})</th> + </thead> + <tbody> + {% for data in modifications %} + <tr> + <td>{{ data.get("Original") }}</td> + <td>{{ data.get("Current") }}</td> + <td><pre>{{data.get("Diff")}}</pre></td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + {% endif %} + + {% if deletions %} + <h2>Deleted Data:</h2> + <div class="row"> + <div class="col-md-8"> + <table class="table-responsive table-hover table-striped cell-border" id="table-deletions"> + <thead> + <th scope="col">Deleted Data</</th> + </thead> + <tbody> + {% for data in deletions %} + <tr> + <td>{{ data }}</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + {% endif %} + +</div> +{%endblock%} + +{% block js %} +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> +<script language="javascript" type="text/javascript"> + gn_server_url = "{{ gn_server_url }}"; + + $(document).ready( function() { + $('#table-additions').dataTable(); + $('#table-modifications').dataTable(); + $('#table-deletions').dataTable(); + }); +</script> +{% endblock %} diff --git a/gn2/wqflask/templates/display_files.html b/gn2/wqflask/templates/display_files.html new file mode 100644 index 00000000..d0e8cc33 --- /dev/null +++ b/gn2/wqflask/templates/display_files.html @@ -0,0 +1,131 @@ +{% extends "base.html" %} +{% block title %}Trait Submission{% endblock %} + +{% block css %} +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> +{% endblock %} + +{% block content %} +<!-- Start of body --> +{% with messages = get_flashed_messages(with_categories=true) %} +{% if messages %} +<div class="container-fluid bg-{{ category }}"> + {% for category, message in messages %} + <div class="alert {{category}}" role="alert">{{ message }}</div> + {% endfor %} +</div> +{% endif %} +{% endwith %} + +<div class="container"> + {%if (not waiting) and (not approved) and (not rejected)%} + <div class="row" style="text-align: left; padding: 5em 0 0 5em;"> + <span class="glyphicon glyphicon-info-sign text-info"></span> + <strong>There are no diffs to act on.</strong> + </div> + {%endif%} + {% if waiting %} + <h2>Files for approval:</h2> + <div class="row"> + <div class="col-md-7"> + <table class="table table-hover table-striped cell-border"> + <thead> + <th scope="col">Resource Id</</th> + <th scope="col">Author</th> + <th scope="col">TimeStamp</th> + <th scope="col"></th> + <th scope="col"></th> + </thead> + <tbody> + {% for data in waiting %} + <tr> + {% set file_url = url_for('metadata_edit.show_diff', name=data.filepath.name) %} + <td><a href="{{ file_url }}" target="_blank">{{ data.meta.get("resource_id") }}</a></td> + <td>{{ data.meta.get("author")}}</td> + <td>{{ data.meta.get("time_stamp")}}</td> + {% set reject_url = url_for('metadata_edit.reject_data', resource_id=data.meta.get('resource_id'), file_name=data.filepath.name, dataset_name=data.diff.dataset_name, trait_name=data.diff.trait_name) %} + {% set approve_url = url_for('metadata_edit.approve_data', resource_id=data.meta.get('resource_id'), file_name=data.filepath.name, dataset_name=data.diff.dataset_name, trait_name=data.diff.trait_name) %} + <td> + <button type="button" + class="btn btn-secondary btn-sm"> + <a href="{{ reject_url }}">Reject</a> + </button> + </td> + <td> + <button type="button" + class="btn btn-warning btn-sm"> + <a href="{{ approve_url }}">Approve</a> + </button> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + {% endif %} + + {% if approved %} + <h2>Approved Data:</h2> + <div class="row"> + <div class="col-md-8"> + <table class="table-responsive table-hover table-striped cell-border" id="table-approved"> + <thead> + <th scope="col">Resource Id</</th> + <th scope="col">Author</th> + <th scope="col">TimeStamp</th> + </thead> + <tbody> + {% for data in approved %} + <tr> + {% set file_url = url_for('metadata_edit.show_diff', name=data.filepath.name) %} + <td><a href="{{ file_url }}" target="_blank">{{ data.meta.get("resource_id") }}</a></td> + <td>{{ data.meta.get("author")}}</td> + <td>{{ data.meta.get("time_stamp")}}</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + {% endif %} + + {% if rejected %} + <h2>Rejected Files:</h2> + <div class="row"> + <div class="col-md-8"> + <table class="table-responsive table-hover table-striped cell-border" id="table-rejected"> + <thead> + <th scope="col">Resource Id</</th> + <th scope="col">Author</th> + <th scope="col">TimeStamp</th> + </thead> + <tbody> + {% for data in rejected %} + <tr> + {% set file_url = url_for('metadata_edit.show_diff', name=data.filepath.name) %} + <td><a href="{{ file_url }}" target="_blank">{{ data.meta.get("resource_id") }}</a></td> + <td>{{ data.meta.get("author")}}</td> + <td>{{ data.meta.get("time_stamp")}}</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + {% endif %} +</div> +{%endblock%} + +{% block js %} +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> +<script language="javascript" type="text/javascript"> + gn_server_url = "{{ gn_server_url }}"; + + $(document).ready( function() { + $('#table-approved').dataTable(); + $('#table-rejected').dataTable(); + }); +</script> +{% endblock %} diff --git a/gn2/wqflask/templates/docedit.html b/gn2/wqflask/templates/docedit.html new file mode 100644 index 00000000..50bb96c0 --- /dev/null +++ b/gn2/wqflask/templates/docedit.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% block title %}Edit: {{title}}{% endblock %} + +{% block content %} +<div class="container"> + <h3>Edit: {{title}}</h3> + <form id="update_text" action="/update_text" method="post"> + <input type="hidden" name="entry_type" value="{{ entry }}"> + <input type="hidden" name="title" value="{{ title }}"> + {% if editable is defined %} + <input type="hidden" name="edit" value="{{ editable }}"> + {% endif %} + <button class="submit_changes" style="margin-bottom: 20px;">Submit Changes</button> + <textarea name="ckcontent" id="ckcontent"> + {{content|safe}} + </textarea> + <button class="submit_changes" style="margin-top: 20px;">Submit Changes</button> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='ckeditor/ckeditor.js') }}"></script> + <script type="text/javascript"> + CKEDITOR.replace('ckcontent', { + height: '650px', + }); + + $('.submit_changes').click(function() { + $('#update_text').submit(); + }) + </script> + </form> +</div> +{% endblock %} diff --git a/gn2/wqflask/templates/docs.html b/gn2/wqflask/templates/docs.html new file mode 100644 index 00000000..1e5a7aef --- /dev/null +++ b/gn2/wqflask/templates/docs.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block title %}{{title}}{% endblock %} + +{% block content %} +<div class="container"> + <h3>{{title}}</h3> + <div style="text-align: right;"> + {% if editable == "true" %} + <a href="docedit?entry={{entry}}&edit=true"> + <img style="width: 16px;" src="/static/new/images/edit.gif"> + </a> + {% endif %} + </div> + {{content|safe}} +</div> +{% endblock %} diff --git a/gn2/wqflask/templates/edit_case_attributes.html b/gn2/wqflask/templates/edit_case_attributes.html new file mode 100644 index 00000000..3c97b992 --- /dev/null +++ b/gn2/wqflask/templates/edit_case_attributes.html @@ -0,0 +1,104 @@ +{%extends "base.html"%} +{%block title%}Edit Case Attributes{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" + href="/css/DataTables/css/jquery.dataTables.css" /> +<link rel="stylesheet" type="text/css" + href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + +<style> + .table-fixed-head {overflow-y: auto; height: 32em;} + .table-fixed-head thead th {position: sticky; top: 0;} +</style> +{%endblock%} + +{%block content%} +<div class="container"> + <h1>{{inbredset_group.InbredSetName}}: Edit Case-Attributes</h1> + + {{flash_me()}} + + <h3>Instructions</h3> + <ul> + <li> + The table is scrollable. Scroll to find the strain(s) you want to edit. + </li> + <li>Change value(s) to edit them in the database.</li> + <li>Delete value(s) to delete them from the database.</li> + <li>Click "Submit" to submit all the changes you have made</li> + <li> + Click "Reset" to undo <strong>ALL</strong> the changes you have made and + start over. + </li> + </ul> + + <a href="{{url_for('list_case_attribute_diffs', inbredset_id=inbredset_id)}}" + title="List out diffs awaiting review" + class="btn btn-info">View Diffs</a> + + <form method="POST" action="{{url_for('edit_case_attributes', inbredset_id=inbredset_id)}}"> + <div class="form-group" style="text-align: center; padding: 1em 0 0 0;"> + <input type="submit" value="Submit" class="btn btn-primary" /> + <input type="reset" value="Reset" class="btn btn-warning" /> + </div> + + <div class="table-fixed-head"> + <table class="table-hover table-striped cell-border dataTable no-footer"> + <thead> + <tr> + <th>Sample/Strain</th> + {%for caname in case_attribute_names%} + <th>{{caname.Name}}</th> + {%endfor%} + </tr> + </thead> + <tbody> + {%for strain in strains%} + <tr> + <div class="form-group"> + <td>{{strain.Name}}</td> + {%for attr in case_attribute_names%} + {%if case_attribute_values.get(strain.Name)%} + <td> + <input type="text" + value="{{case_attribute_values[strain.Name]['case-attributes'].get(attr.Name, '')}}" + name="new:{{strain.Name}}:{{attr.Name}}" + class="form-control" /> + </td> + {%else%} + <td> + <input type="text" + value="" + name="new:{{strain.Name}}:{{attr.Name}}" + class="form-control" /> + </td> + {%endif%} + {%endfor%} + </div> + </tr> + {%else%} + <tr> + <td colspan="{{case_attribute_names | length + 1}}"> + No samples/strains for this InbredSet group. + </td> + </tr> + {%endfor%} + </tbody> + </table> + </div> + + <div class="form-group" style="text-align: center; padding: 1em 0 0 0;"> + <input type="submit" value="Submit" class="btn btn-primary" /> + <input type="reset" value="Reset" class="btn btn-warning" /> + </div> + </form> +</div> +{%endblock%} + +{%block js%} +<script language="javascript" + type="text/javascript" + src="{{url_for('js', filename='DataTables/js/jquery.js')}}"></script> +{%endblock%} diff --git a/gn2/wqflask/templates/edit_history.html b/gn2/wqflask/templates/edit_history.html new file mode 100644 index 00000000..876ab085 --- /dev/null +++ b/gn2/wqflask/templates/edit_history.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} +{% block title %}Trait Submission{% endblock %} + +{% block css %} +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> +{% endblock %} + +{% block content %} +<!-- Start of body --> +<div class="container"> + + <h1>History</h1> + {% if diff %} + <div class="row"> + <table id="history" class="table-responsive table-hover table-striped cell-border"> + <thead> + <th>Timestamp</th> + <th>Editor</th> + <th>Field</th> + <th>Diff</th> + </thead> + <tbody> + {%set ns = namespace(display_ts=True)%} + {%for ts, item in diff.items()%} + {%set ns.display_ts = True%} + {%for the_diff in item%} + <tr> + {%if ns.display_ts%} + <td rowspan={{item | length}}>{{ts}}</td> + {%set ns.display_ts = False%} + {%endif%} + <td>{{the_diff.author}}</td> + <td>{{the_diff.diff.field}}</td> + <td><pre>{{the_diff.diff.diff}}</pre></td> + </tr> + {%endfor%} + {%endfor%} + </tbody> + </table> + </div> + {% endif %} + +</div> +{%endblock%} + +{% block js %} +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> +<script language="javascript" type="text/javascript"> + gn_server_url = "{{ gn_server_url }}"; + + $(document).ready( function() { + $('#history').dataTable(); + }); +</script> +{% endblock %} diff --git a/gn2/wqflask/templates/edit_phenotype.html b/gn2/wqflask/templates/edit_phenotype.html new file mode 100644 index 00000000..99efa46c --- /dev/null +++ b/gn2/wqflask/templates/edit_phenotype.html @@ -0,0 +1,279 @@ +{% extends "base.html" %} +{% block title %}Trait Submission{% endblock %} +{% block content %} +<!-- Start of body --> +{% with messages = get_flashed_messages(with_categories=true) %} +{% if messages %} +{% for category, message in messages %} +<div class="container-fluid bg-{{ category }}"> + <p>{{ message|safe }}</p> +</div> +{% endfor %} +{% endif %} +{% endwith %} +<div class="container"> + <div class="page-header text-left"> + <h1>Trait Metadata and Data Editing Form: {{ name }}</h1> + <small><a href="{{url_for('metadata_edit.show_history', dataset_id=dataset_id, name=name)}}" target="_blank">[View History]</a></small> + </div> + + <form id="edit-form" class="form-horizontal" method="post" action="/datasets/{{dataset_id}}/traits/{{publish_xref.id_}}?resource-id={{resource_id}}&dataset_name={{dataset_name}}" enctype='multipart/form-data'> + <div class="form-group"> + <div class="controls left-block col-sm-8 col-lg-8" style="width: max-content;"> + <input name="inbred-set-id" class="changed" type="hidden" value="{{ publish_xref.inbred_set_id }}"/> + <input name="phenotype-id" class="changed" type="hidden" value="{{ publish_xref.phenotype_id }}"/> + <input name="comments" class="changed" type="hidden" value="{{ publish_xref.comments }}"/> + <input name="edited" class="changed" type="hidden" value="false"/> + </div> + </div> + <div> + <h2>Edit Sample Data</h2> + <p> + Download a spreadsheet of sample values to edit in Excel (or a similar program) and then upload the edited file + </p> + <div> + <a href="/datasets/pheno/{{ name }}/group/{{ dataset_id }}/csv?resource-id={{ resource_id }}" class="btn btn-link"> + Click to Download Sample Data + </a> + </div> + <div class="form-group"> + <input type = "file" class="col-md-4 control-label text-left" name = "file" /> + </div> + <input type="submit" style="width: 125px; margin-right: 25px;" class="btn btn-success form-control changed" value="Submit Change"> + </div> + <hr> + <div> + <h2>Edit Metadata</h2> + <div class="form-group"> + <label for="pubmed-id" class="col-sm-3 col-lg-2 control-label text-left">PubMed ID</label> + <!-- Do not enter PubMed_ID if this trait has not been Published. + If the PubMed_ID you entered is alreday stored in our + database, all the following fields except Postpublication + Description will be ignored. Do not enter any non-digit + character in this field. --> + <div class="col-sm-7 col-lg-8"> + <input type="text" name="pubmed-id" class="form-control" + value="{{publication.pubmed_id |default('', true)}}"> + <input name="old_id_" class="changed" type="hidden" value="{{ publication.id_ |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="pre-pub-desc" class="col-sm-3 col-lg-2 control-label text-left">Prepublication Description</label> + <div class="col-sm-7 col-lg-8"> + <textarea name="pre-pub-desc" class="form-control" rows="4">{{ phenotype.pre_pub_description |default('', true) }}</textarea> + <input name="old_pre_pub_description" class="changed" type="hidden" value="{{ phenotype.pre_pub_description |default('', true) }}"/> + </div> + <!-- If the PubMed ID is entered, the Postpublication Description + will be shown to all users. If there is no PubMed ID, and the + Prepublication Description is entered, only you and + authorized users can see the Postpublication Description --> + </div> + <div class="form-group"> + <label for="post-pub-desc" class="col-sm-3 col-lg-2 control-label text-left">Postpublication Description</label> + <div class="col-sm-7 col-lg-8"> + <textarea name="post-pub-desc" class="form-control" rows="4">{{ phenotype.post_pub_description |default('', true) }}</textarea> + <input name="old_post_pub_description" class="changed" type="hidden" value="{{ phenotype.post_pub_description |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="orig-desc" class="col-sm-3 col-lg-2 control-label text-left">Original Description</label> + <div class="col-sm-7 col-lg-8"> + <textarea name="orig-desc" class="form-control" rows="4">{{ phenotype.original_description |default('', true) }}</textarea> + <input name="old_original_description" class="changed" type="hidden" value="{{ phenotype.original_description |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="txt:units" class="col-sm-3 col-lg-2 control-label text-left">Units</label> + <div class="col-sm-7 col-lg-8"> + <input id="txt:units" type="text" name="units" + class="form-control" + value="{{phenotype.units |default('', true)}}" /> + <input name="old_units" class="changed" type="hidden" value="{{ phenotype.units |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="pre-pub-abbrev" class="col-sm-3 col-lg-2 control-label text-left"> + Prepublication Abbreviation + </label> + <div class="col-sm-7 col-lg-8"> + <input id="pre-pub-abbrev" name="pre-pub-abbrev" + class="form-control" + value="{{phenotype.pre_pub_abbreviation |default('', true)}}" /> + <input name="old_pre_pub_abbreviation" class="changed" type="hidden" value="{{ phenotype.pre_pub_abbreviation |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="post-pub-abbrev" class="col-sm-3 col-lg-2 control-label text-left"> + Postpublication Abbreviation + </label> + <div class="col-sm-7 col-lg-8"> + <input type="text" id="post-pub-abbrex" name="post-pub-abbrev" + class="form-control" + value="{{phenotype.post_pub_abbreviation |default('', true)}}" /> + <input name="old_post_pub_abbreviation" class="changed" type="hidden" value="{{ phenotype.post_pub_abbreviation |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="labcode" class="col-sm-3 col-lg-2 control-label text-left"> + Lab Code + </label> + <div class="col-sm-7 col-lg-8"> + <input type="text" id="labcode" name="labcode" + class="form-control" + value="{{phenotype.lab_code |default('', true) }}" /> + <input name="old_lab_code" class="changed" type="hidden" value="{{ phenotype.lab_code |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="submitter" class="col-sm-3 col-lg-2 control-label text-left"> + Submitter + </label> + <div class="col-sm-7 col-lg-8"> + <input type="text" id="submitter" name="submitter" + class="form-control" + value="{{phenotype.submitter |default('', true)}}" /> + <input name="old_submitter" class="changed" type="hidden" value="{{ phenotype.submitter |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="authorized-users" class="col-sm-3 col-lg-2 control-label text-left"> + Authorized Users + </label> + <div class="col-sm-7 col-lg-8"> + <input type="text" id="authorized-users" name="authorized-users" + class="form-control" + value="{{phenotype.authorized_users |default('', true)}}" /> + <input name="old_authorized_users" class="changed" type="hidden" value="{{ phenotype.authorized_users |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="authors" class="col-sm-3 col-lg-2 control-label text-left">Authors</label> + <div class="col-sm-7 col-lg-8"> + <textarea name="authors" class="form-control" rows="3" placeholder="Example: Roy S, Ingels J, Bohl CJ, McCarty M, Lu L, Mulligan MK, Mozhui K, Centeno A, Williams EG, Auwerx J, Williams RW">{{ publication.authors |default('', true) }}</textarea> + <input name="old_authors" class="changed" type="hidden" value="{{ publication.authors |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="year" class="col-sm-3 col-lg-2 control-label text-left">Year</label> + <div class="col-sm-7 col-lg-8"> + <input type="number" name="year" class="form-control" + min="1000" + value="{{publication.year |default(datetime.datetime.now().year) }}" /> + <input name="old_year" class="changed" type="hidden" value="{{ publication.year |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="month" class="col-sm-3 col-lg-2 control-label text-left">Month</label> + <div class="col-sm-7 col-lg-8"> + <select id="month" name="month" class="form-control"> + {%set selected_month =(publication.month or datetime.datetime.now().strftime("%b"))%} + {%for smonth, lmonth in (("Jan", "January"),("Feb", "February"),("Mar", "March"),("Apr", "April"),("May", "May"),("Jun", "Jun"),("Jul", "July"),("Aug", "August"),("Sep", "September"),("Oct", "October"),("Nov", "November"),("Dec", "December"))%} + <option value="{{smonth}}" + {%if smonth == selected_month%} + selected="selected" + {%endif%}>{{lmonth}}</option> + {%endfor%} + </select> + <input name="old_month" class="changed" type="hidden" value="{{ publication.month |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="txt:title" class="col-sm-3 col-lg-2 control-label text-left">Title</label> + <div class="col-sm-7 col-lg-8"> + <input type="text" id="txt:title" name="title" + class="form-control" + value="{{publication.title |default('', true)}}" /> + <input name="old_title" class="changed" type="hidden" value="{{ publication.title |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="abstract" class="col-sm-3 col-lg-2 control-label text-left">Abstract</label> + <div class="col-sm-7 col-lg-8"> + <textarea name="abstract" class="form-control" rows="6">{{ publication.abstract |default('', true) }}</textarea> + <input name="old_abstract" class="changed" type="hidden" value="{{ publication.abstract |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="volume" class="col-sm-3 col-lg-2 control-label text-left">Volume</label> + <div class="col-sm-7 col-lg-8"> + <input type="text" id="volume" name="volume" + class="form-control" + value="{{publication.volume |default('', true)}}" /> + <input name="old_volume" class="changed" type="hidden" value="{{ publication.volume |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="journal" class="col-sm-3 col-lg-2 control-label text-left">Journal</label> + <div class="col-sm-7 col-lg-8"> + <input type="text" id="journal" name="journal" + class="form-control" + value="{{publication.journal |default('', true)}}" /> + <input name="old_journal" class="changed" type="hidden" value="{{ publication.journal_ |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="pages" class="col-sm-3 col-lg-2 control-label text-left">Pages</label> + <div class="col-sm-7 col-lg-8"> + <input type="text" id="pages" name="pages" + class="form-control" + value="{{publication.pages |default('', true)}}" /> + <input name="old_pages" class="changed" type="hidden" value="{{ publication.pages |default('', true) }}"/> + </div> + </div> + <input type="submit" style="width: 125px; margin-right: 25px;" class="btn btn-success form-control changed" value="Submit Change"> + </div> + {% if sample_list|length < 2000 %} + <div style="padding-top: 20px;"> + <p class="text-info" style="padding-left: 5em;"> + <strong>Type "x" to delete a value.</strong> + </p> + <table style="width: 500px;" class="table table-hover table-striped table-bordered left-float"> + <thead> + <th></th> + <th>ID</th> + <th>Sample</th> + <th>Value</th> + <th></th> + <th>SE</th> + <th>N</th> + </thead> + <tbody> + {% for sample in sample_list %} + <tr> + <td><input type="checkbox"></td> + <td>{{ loop.index }}</td> + <td>{{ sample }}</td> + <td><input type="text" name="value:{{ sample }}" class="table_input" value="{% if sample_data.get(sample) %}{{ '%0.3f' | format(sample_data.get(sample).value | float) }}{% else %}x{% endif %}" size=4 maxlength=6></input></td> + <td>±</td> + <td><input type="text" name="error:{{ sample }}" class="table_input" value="{% if sample_data.get(sample).error %}{{ '%0.3f' | format(sample_data.get(sample).error | float) }}{% else %}x{% endif %}" size=4 maxlength=5></input></td> + <td><input type="text" name="n_cases:{{ sample }}" class="table_input" value="{% if sample_data.get(sample).n_cases %}{{ sample_data.get(sample).n_cases }}{% else %}x{% endif %}" size=3 maxlength=3></input></td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + {% endif %} + + </form> +</div> + +{%endblock%} + +{% block js %} +<script> + gn_server_url = "{{ gn_server_url }}"; + function MarkAsChanged(){ + $(this).addClass("changed"); + } + $(":input").blur(MarkAsChanged).change(MarkAsChanged); + + $("input[type=submit]").click(function(){ + $(":input:not(.changed)").attr("disabled", "disabled"); + }); + + // This is an awkward way to detect changes to table data, so it doesn't process it otherwise + $(".table_input").change(function(){ + $("input[name=edited]").val("true"); + }); +</script> +{% endblock %} diff --git a/gn2/wqflask/templates/edit_probeset.html b/gn2/wqflask/templates/edit_probeset.html new file mode 100644 index 00000000..88e97837 --- /dev/null +++ b/gn2/wqflask/templates/edit_probeset.html @@ -0,0 +1,282 @@ +{% extends "base.html" %} +{% block title %}Trait Submission{% endblock %} +{% block content %} +<!-- Start of body --> +<div class="container"> + {% with messages = get_flashed_messages(category_filter=["warning"]) %} + {% if messages %} + {% for message in messages %} + <div class="alert alert-warning alert-dismissible" role="alert"> + <button class="close" type="button" data-dismiss="alert" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + {{ message }} + </div> + {% endfor %} + {% endif %} + {% endwith %} + + {% with messages = get_flashed_messages(category_filter=["success"]) %} + {% if messages %} + {% for message in messages %} + <div class="alert alert-success alert-dismissible" role="alert"> + <button class="close" type="button" data-dismiss="alert" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + {{ message|safe }} + </div> + {% endfor %} + {% endif %} + {% endwith %} + <div class="page-header text-left"> + <h1>Trait Metadata and Data Editing Form: {{ name }}</h1> + <small><a href="{{url_for('metadata_edit.show_history', dataset_id=dataset_id, name=name)}}" target="_blank">[View History]</a></small> + </div> + <form id="edit-form" class="form-horizontal" method="post" action="/datasets/traits/{{name}}?resource-id={{resource_id}}&dataset_id={{dataset_name}}" enctype='multipart/form-data'> + <div class="form-group"> + <div class="controls left-block col-sm-8 col-lg-8" style="width: max-content;"> + <input name="id" class="changed" type="hidden" value="{{ probeset.id_ }}"/> + <input name="old_id_" class="changed" type="hidden" value="{{ probeset.id_ }}"/> + <input name="probeset_name" class="changed" type="hidden" value="{{ probeset.name }}"/> + <input name="dataset_name" class="changed" type="hidden" value="{{ dataset_name }}"/> + <input name="edited" class="changed" type="hidden" value="false"/> + </div> + </div> + <div> + <h2>Edit Sample Data</h2> + <p> + Download a spreadsheet of sample values to edit in Excel (or a similar program) and then upload the edited file + </p> + <div> + <a href="/datasets/mrna/{{ probeset_id }}/dataset/{{ dataset_name }}/csv?resource-id={{ resource_id }}" class="btn btn-link"> + Click to Download Sample Data + </a> + </div> + <div class="form-group"> + <input type = "file" class="col-md-4 control-label text-left" name = "file" /> + </div> + <input type="submit" style="width: 125px; margin-right: 25px;" class="btn btn-success form-control changed" value="Submit Change"> + </div> + <hr> + <div> + <h2>Edit Metadata</h2> + <div class="form-group"> + <label for="symbol" class="col-sm-3 col-lg-2 control-label text-left">Symbol</label> + <div class="col-sm-4"> + <textarea name="symbol" class="form-control" rows="1">{{ probeset.symbol |default('', true) }}</textarea> + <input name="old_symbol" class="changed" type="hidden" value="{{ probeset.symbol |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="description" class="col-sm-3 col-lg-2 control-label text-left">Description</label> + <div class="col-sm-4"> + <textarea name="description" class="form-control" rows="3">{{ probeset.description |default('', true) }}</textarea> + <input name="old_description" class="changed" type="hidden" value="{{ probeset.description |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="probe_target_description" class="col-sm-3 col-lg-2 control-label text-left">Probe Target Description</label> + <div class="col-sm-4"> + <textarea name="probe_target_description" class="form-control" rows="4">{{ probeset.probe_target_description |default('', true) }}</textarea> + <input name="old_probe_target_description" class="changed" type="hidden" value="{{ probeset.probe_target_description |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="chr" class="col-sm-3 col-lg-2 control-label text-left">Chr</label> + <div class="col-sm-4"> + <textarea name="chr" class="form-control" rows="1">{{ probeset.chr_ |default('', true) }}</textarea> + <input name="old_chr_" class="changed" type="hidden" value="{{ probeset.chr_ |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="mb" class="col-sm-3 col-lg-2 control-label text-left">Mb</label> + <div class="col-sm-4"> + <textarea name="mb" class="form-control" rows="1">{{ probeset.mb |default('', true) }}</textarea> + <input name="old_mb" class="changed" type="hidden" value="{{ probeset.mb |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="alias" class="col-sm-3 col-lg-2 control-label text-left"> + Alias: + </label> + <div class="col-sm-4"> + <textarea name="alias" class="form-control" rows="1">{{ probeset.alias |default('', true) }}</textarea> + <input name="old_alias" class="changed" type="hidden" value="{{ probeset.alias |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="geneid" class="col-sm-3 col-lg-2 control-label text-left"> + Gene Id: + </label> + <div class="col-sm-4"> + <textarea name="geneid" class="form-control" rows="1">{{ probeset.geneid |default('', true) }}</textarea> + <input name="old_geneid" class="changed" type="hidden" value="{{ probeset.geneid |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="homologeneid" class="col-sm-3 col-lg-2 control-label text-left"> + Homologene Id: + </label> + <div class="col-sm-4"> + <textarea name="homologeneid" class="form-control" rows="1">{{ probeset.homologeneid |default('', true) }}</textarea> + <input name="old_homologeneid" class="changed" type="hidden" value="{{ probeset.homologeneid |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="unigeneid" class="col-sm-3 col-lg-2 control-label text-left"> + Unigene Id: + </label> + <div class="col-sm-4"> + <textarea name="unigeneid" class="form-control" rows="1">{{ probeset.unigeneid |default('', true) }}</textarea> + <input name="old_unigeneid" class="changed" type="hidden" value="{{ probeset.unigeneid |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="omim" class="col-sm-3 col-lg-2 control-label text-left">OMIM</label> + <div class="col-sm-4"> + <textarea name="omim" class="form-control" rows="1">{{ probeset.omim |default('', true) }}</textarea> + <input name="old_omim" class="changed" type="hidden" value="{{ probeset.omim |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="refseq_transcriptid" class="col-sm-3 col-lg-2 control-label text-left"> + Refseq TranscriptId: + </label> + <div class="col-sm-4"> + <textarea name="refseq_transcriptid" class="form-control" rows="1">{{ probeset.refseq_transcriptid |default('', true) }}</textarea> + <input name="old_refseq_transcriptid" class="changed" type="hidden" value="{{ probeset.refseq_transcriptid |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="blatseq" class="col-sm-3 col-lg-2 control-label text-left">BlatSeq</label> + <div class="col-sm-8"> + <textarea name="blatseq" class="form-control" rows="6">{{ probeset.blatseq |default('', true) }}</textarea> + <input name="old_blatseq" class="changed" type="hidden" value="{{ probeset.blatseq |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="targetseq" class="col-sm-3 col-lg-2 control-label text-left">TargetSeq</label> + <div class="col-sm-8"> + <textarea name="targetseq" class="form-control" rows="6">{{ probeset.targetseq |default('', true) }}</textarea> + <input name="old_targetseq" class="changed" type="hidden" value="{{ probeset.targetseq |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="strand_probe" class="col-sm-3 col-lg-2 control-label text-left">Strand Probe</label> + <div class="col-sm-2"> + <textarea name="strand_probe" class="form-control" rows="1">{{ probeset.strand_probe |default('', true) }}</textarea> + <input name="old_strand_probe" class="changed" type="hidden" value="{{ probeset.strand_probe |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="probe_set_target_region" class="col-sm-3 col-lg-2 control-label text-left">Probe Set Target Region</label> + <div class="col-sm-8"> + <textarea name="probe_set_target_region" class="form-control" rows="1">{{ probeset.probe_set_target_region |default('', true) }}</textarea> + <input name="old_probe_set_target_region" class="changed" type="hidden" value="{{ probeset.probe_set_target_region_ |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="probe_set_specificity" class="col-sm-3 col-lg-2 control-label text-left">Probeset Specificity</label> + <div class="col-sm-8"> + <textarea name="probe_set_specificity" class="form-control" rows="1">{{ probeset.probe_set_specificity |default('', true) }}</textarea> + <input name="old_probe_set_specificity" class="changed" type="hidden" value="{{ probeset.probe_set_specificity |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="probe_set_blat_score" class="col-sm-3 col-lg-2 control-label text-left">Probeset Blat Score</label> + <div class="col-sm-8"> + <textarea name="probe_set_blat_score" class="form-control" rows="1">{{ probeset.probe_set_blat_score |default('', true) }}</textarea> + <input name="old_probe_set_blat_score" class="changed" type="hidden" value="{{ probeset.probe_set_blat_score |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="probe_set_blat_mb_start" class="col-sm-3 col-lg-2 control-label text-left"> + Probeset Blat Mb Start</label> + <div class="col-sm-8"> + <textarea name="probe_set_blat_mb_start" class="form-control" rows="1">{{ probeset.probe_set_blat_mb_start |default('', true) }}</textarea> + <input name="old_probe_set_blat_mb_start" class="changed" type="hidden" value="{{ probeset.probe_set_blat_mb_start |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="probe_set_blat_mb_end" class="col-sm-3 col-lg-2 control-label text-left">Probeset Blat Mb End</label> + <div class="col-sm-8"> + <textarea name="probe_set_blat_mb_end" class="form-control" rows="1">{{ probeset.probe_set_blat_mb_end |default('', true) }}</textarea> + <input name="old_probe_set_blat_mb_end" class="changed" type="hidden" value="{{ probeset.probe_set_blat_mb_end |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="probe_set_strand" class="col-sm-3 col-lg-2 control-label text-left">Probeset Strand</label> + <div class="col-sm-8"> + <textarea name="probe_set_strand" class="form-control" rows="6">{{ probeset.probe_set_strand |default('', true) }}</textarea> + <input name="old_probe_set_strand" class="changed" type="hidden" value="{{ probeset.probe_set_strand |default('', true) }}"/> + </div> + </div> + <div class="form-group"> + <label for="probe_set_note_by_rw" class="col-sm-3 col-lg-2 control-label text-left">Probeset Strand</label> + <div class="col-sm-8"> + <textarea name="probe_set_note_by_rw" class="form-control" rows="6">{{ probeset.probe_set_note_by_rw |default('', true) }}</textarea> + <input name="old_probe_set_note_by_rw" class="changed" type="hidden" value="{{ probeset.probe_set_note_by_rw |default('', true) }}"/> + </div> + </div> + <input type="submit" style="width: 125px; margin-right: 25px;" class="btn btn-success form-control col-xs-2 changed" value="Submit Change"> + </div> + <div style="padding-top: 20px;"> + <p class="text-info" style="padding-left: 5em;"> + <strong>Type "x" to delete a value.</strong> + </p> + <table style="width: 500px;" class="table table-hover table-striped table-bordered left-float"> + <thead> + <th></th> + <th>ID</th> + <th>Sample</th> + <th>Value</th> + <th></th> + <th>SE</th> + <th>N</th> + </thead> + <tbody> + {% for sample in sample_list %} + <tr> + <td><input type="checkbox"></td> + <td>{{ loop.index }}</td> + <td>{{ sample }}</td> + <td><input type="text" name="value:{{ sample }}" class="table_input" value="{% if sample_data.get(sample) %}{{ '%0.3f' | format(sample_data.get(sample).value | float) }}{% else %}x{% endif %}" size=4 maxlength=6></input></td> + <td>±</td> + <td><input type="text" name="error:{{ sample }}" class="table_input" value="{% if sample_data.get(sample).error %}{{ '%0.3f' | format(sample_data.get(sample).error | float) }}{% else %}x{% endif %}" size=4 maxlength=5></input></td> + <td><input type="text" name="n_cases:{{ sample }}" class="table_input" value="{% if sample_data.get(sample).n_cases %}{{ sample_data.get(sample).n_cases }}{% else %}x{% endif %}" size=3 maxlength=3></input></td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + + </form> +</div> + +{%endblock%} + +{% block js %} +<script> + gn_server_url = "{{ gn_server_url }}"; + spans = document.querySelectorAll("[data-message-id]") + spans.forEach((span) => { + span.innerHTML = $("[for='" + span.getAttribute("data-message-id") + "']").text(); + }); + $("[data-message-id]").lead + + function MarkAsChanged(){ + $(this).addClass("changed"); + } + $(":input").blur(MarkAsChanged).change(MarkAsChanged); + + $("input[type=submit]").click(function(){ + $(":input:not(.changed)").attr("disabled", "disabled"); + }); + + // This is an awkward way to detect changes to table data, so it doesn't process it otherwise + $(".table_input").change(function(){ + $("input[name=edited]").val("true"); + }); + +</script> +{% endblock %} diff --git a/gn2/wqflask/templates/email/forgot_password.txt b/gn2/wqflask/templates/email/forgot_password.txt new file mode 100644 index 00000000..e7d1389b --- /dev/null +++ b/gn2/wqflask/templates/email/forgot_password.txt @@ -0,0 +1,5 @@ +Sorry to hear you lost your GeneNetwork password. + +To reset your password please click the following link, or cut and paste it into your browser window: + +{{ url_for_hmac("password_reset", code = verification_code, _external=True )}} diff --git a/gn2/wqflask/templates/empty_collection.html b/gn2/wqflask/templates/empty_collection.html new file mode 100644 index 00000000..d1b779ef --- /dev/null +++ b/gn2/wqflask/templates/empty_collection.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% block title %}{{ tool }}{% endblock %} +{% block content %} +<!-- Start of body --> + {{ header("Error") }} + + <div class="container"> + <input type="hidden" name="uc_id" id="uc_id" value="{{ uc_id }}"> + <p>You must select at least two traits to use the {{ tool }}.</p> + </div> + + +<!-- End of body --> + +{% endblock %} diff --git a/gn2/wqflask/templates/environment.html b/gn2/wqflask/templates/environment.html new file mode 100644 index 00000000..89e805ce --- /dev/null +++ b/gn2/wqflask/templates/environment.html @@ -0,0 +1,160 @@ +{% extends "base.html" %} + +{% block title %}Glossary{% endblock %} + +{% block css %} +<link rel="stylesheet" type="text/css" href="/static/new/css/markdown.css" /> +{% endblock %} + +{% block content %} + +<div id="markdown" class="container"> + + <div class="cls-table-style">{{ rendered_markdown|safe }} </div> +</div> + +{% 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> + +<!-- Display the svg graph --> + +<div id="guix-svg-graph"> + <h1>The dependency graph is shown below</h1> + + <p>To explore this image SVG you may want to open it in new browser page and zoom in. Or use an SVG viewing application.</p> + + <img alt="Dependency graph of the tools needed to build python3-genenetwork2" src="{{url_for('environments_blueprint.svg_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; }); + + // 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 %} + +{% endblock %} diff --git a/gn2/wqflask/templates/error.html b/gn2/wqflask/templates/error.html new file mode 100644 index 00000000..2f1e06fa --- /dev/null +++ b/gn2/wqflask/templates/error.html @@ -0,0 +1,61 @@ +{% extends "base.html" %} +{% block title %}Error: {{message}}{% endblock %} +{% block content %} +<!-- Start of body --> + +<div class="col-md-8"> +<div class="form-group has-error"> + <div class="control-label" for="inputError1"> + + <img src="/static/gif/error/{{ error_image }}"> + + <h1>ERROR</h1> + + <p> + This error is not what we wanted to see. Unfortunately errors + are part of all software systems and we need to resolve this + together. + </p> + <p> + <b>It is important to report this ERROR so we can fix it for everyone</b>. + </p> + + <p> + Report to the GeneNetwork team by recording the steps you take + to reproduce this ERROR. Next to those steps, copy-paste below + stack trace, either as + a <a href="https://github.com/genenetwork/genenetwork2/issues/new">new + issue</a> or E-mail this full page to one of the developers + directly. + </p> + </div> + + <p> + (GeneNetwork error: {{message[:128]}}) + </p> + + <pre> + GeneNetwork {{ version }} {{ stack[0] }} + {{ message }} (error) + {{ stack[-3] }} + {{ stack[-2] }} + </pre> + + <p> + To check if this already a known issue, search the + <a href="https://github.com/genenetwork/genenetwork2/issues">issue + tracker</a>. + </p> + + <a href="#Stack" class="btn btn-default" data-toggle="collapse">Toggle full stack trace</a> + <div id="Stack" class="collapse"> + <pre> + GeneNetwork {{ version }} {% for line in stack %} {{ line }} + {% endfor %} + </pre> + </div> +</div> +</div> + + +{% endblock %} diff --git a/gn2/wqflask/templates/facilities.html b/gn2/wqflask/templates/facilities.html new file mode 100644 index 00000000..56b127f9 --- /dev/null +++ b/gn2/wqflask/templates/facilities.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% block title %}Facilities{% endblock %} + +{% block css %} +<link rel="stylesheet" type="text/css" href="/static/new/css/markdown.css" /> +{% endblock %} + +{% block content %} + + <div class="github-btn-container"> + <div class="github-btn"> + <a href="https://github.com/genenetwork/gn-docs/blob/master/general/help/facilities.md"> + Edit Text + <img src="/static/images/edit.png"> + </a> + </div> +</div> +<div id="markdown" class="container"> + {{ rendered_markdown|safe }} + +</div> + +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/generif.html b/gn2/wqflask/templates/generif.html new file mode 100644 index 00000000..ac815b43 --- /dev/null +++ b/gn2/wqflask/templates/generif.html @@ -0,0 +1,101 @@ +{% extends "base.html" %} + +{% block title %} +GeneWiki Entry for {{ symbol }} +{% endblock %} + +{% block css %} +<style> + + .badge { + vertical-align: top; + background-color: #336699; + } + + .list-group { + counter-reset: gnentries; + } + + summary::before { + counter-increment: gnentries; + content: counter(gnentries) "." " "; + } + + summary:hover { + cursor: zoom-in; + } +</style> + +{% endblock %} +{% block content %} + + +<div class="container"> + <h1 class="page-header">GeneWiki For {{ symbol }}</h1> + <p class="well"><strong>GeneWiki</strong> enables you to enrich the annotation of genes and transcripts.</p> + + <h3> + <strong>GeneNetwork</strong> + <span class="badge"> + {{ entries.gn_entries|length if entries.gn_entries[0] else 0 }} + </span>: + </h3> + {% if entries.gn_entries[0] %} + <ul class="list-group"> + {% for entry in entries.gn_entries %} + <li class="list-group-item"> + <details> + <summary> + {{ entry["entry"]["value"] }} + {% if entry.get("weburl") %} + <sup><small><a href="{{ entry.weburl.value }}" target="_blank"><span class="glyphicon glyphicon-globe" aria-hidden="true"></span> web</a></small></sup> + {% endif %} + </summary> + <dl class="dl-horizontal"> + <dt>Author:</dt> + <dd>{{ entry["author"]["value"] }}</dd> + + {% if entry.get("geneCategory") %} + <dt>Category:</dt> + <dd>{{ entry["geneCategory"]["value"]}}</dd> + {% endif %} + + <dt>Add Time:</dt> + <dd>{{ entry["created"]["value"]}}</dd> + </dl> + </details> + </li> + {% endfor %} + </ul> + + {% else %} + + <p class="well"><u>There are no GeneNetwork entries for <b>{{ symbol }}.</b></u></p> + + {% endif %} + + <h3> + <strong>GeneRIF from NCBI</strong> + <span class="badge"> + {{ entries.ncbi_entries|length if entries.ncbi_entries[0] else 0 }} + </span>: + </h3> + {% if entries.ncbi_entries[0] %} + <ol> + {% for entry in entries.ncbi_entries %} + <li> + {{ entry.entry.value }} + (<a href="{{ entry['generif']['value'] }}" target="_blank">{{ entry["speciesBinomialName"]["value"] }}</a>) + {% if entry.PubMedId.value != "" %} + {% set pmids = entry.PubMedId.value.split(",") %} + (PubMed: {% for id in pmids %} <a href="http://rdf.ncbi.nlm.nih.gov/pubmed/{{ id }}" target="_blank">{{ id }}</a>{% endfor %}) + <sup><small><em>{{ entry.createdOn.value }}</em></small></sup> + {% endif %} + </li> + {% endfor %} + </ol> + {% else %} + <p class="well"><u>There are no NCBI entries for <b>{{ symbol }}.</b></u></p> + {% endif %} +</div> +{% endblock %} diff --git a/gn2/wqflask/templates/geneweaver_page.html b/gn2/wqflask/templates/geneweaver_page.html new file mode 100644 index 00000000..7687cb6a --- /dev/null +++ b/gn2/wqflask/templates/geneweaver_page.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% block title %}{% if wrong_input == "True" %}WebGestalt Error{% else %}Opening WebGestalt{% endif %}{% endblock %} +{% block content %} + {% if wrong_input == "True" %} + <div class="container"> + <h1>Error</h1> + <hr style="height: 1px; background-color: #A9A9A9;"> + {% if chip_name == "mixed" %} + <h3>Sorry, the analysis was interrupted because your selections from GeneNetwork apparently include data from more than one array platform (i.e., Affymetrix U74A and M430 2.0). Most WebGestalt analyses assume that you are using a single array type and compute statistical values on the basis of that particular array. Please reselect traits from a signle platform and submit again.</h3> + {% elif chip_name == "not_microarray" %} + <h3>You need to select at least one microarray trait to submit.</hr> + {% elif '_NA' in chip_name %} + <h3>Sorry, the analysis was interrupted because your selections from GeneNetwork apparently include data from platform {{ chip_name }} which is unknown by GeneWeaver. Please reselect traits and submit again.</h3> + {% else %} + <h3>Sorry, an error occurred while submitting your traits to GeneWeaver.</h3> + {% endif %} + </div> + {% else %} + <div class="container"> + <h3>Opening GeneWeaver...</h3> + </div> + <form method="post" action="http://ontologicaldiscovery.org/index.php?action=manage&cmd=importGeneSet" name="formODE"> + {% for key in hidden_vars %} + <input type="hidden" name="{{ key }}" value="{{ hidden_vars[key] }}"> + {% endfor %} + </form> + {% endif %} +{% endblock %} +{% block js %} +{% if wrong_input == "False" %} +<script type="text/javascript"> + setTimeout('document.formODE.submit()', 1000); +</script> +{% endif %} +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/genotype.html b/gn2/wqflask/templates/genotype.html new file mode 100644 index 00000000..fc5b1ad7 --- /dev/null +++ b/gn2/wqflask/templates/genotype.html @@ -0,0 +1,87 @@ +{% extends "base.html" %} + +{% block css %} +<style type="text/css"> + .page-header { + text-underline-offset: 0.5rem; + padding: 1em; + } +</style> +{% endblock %} + +{% block title %}Genotype: {{ name }}{% endblock %} + +{% block content %} + +<h1 class="page-header"> + {% if metadata.name %} + <u>Genotype: {{ metadata.name }}</u> + {% else %} + {{ name }} + {% endif %} +</h1> + +<div class="container"> + <table class="table"> + {% if metadata.datasetName %} + <tr> + <td><b>Dataset: </b></td> + <td> + <a href="{{ metadata.genotypeOfDataset }}" target="_blank">{{ metadata.datasetName }}</a> + </td> + </tr> + {% endif %} + + {% if metadata.inbredSetName %} + <tr> + <td><b>Group</b></td> + <td>{{ metadata.inbredSetName }}</td> + </tr> + {% endif %} + + <tr> + <td><b>Species</b></td> + <td>{{ metadata.species or "N/A"}}</td> + </tr> + <tr> + <td><b>Location</b></td> + <td>Chr {{ metadata.ch }} @ {{ metadata.mb }} mb </td> + </tr> + {% if metadata.cM %} + <tr> + <td><b>cM</b></td> + <td>{{ metadata.cM }}</td> + </tr> + {% endif %} + + {% if metadata.mb %} + <tr> + <td><b>mb</b></td> + <td>{{ metadata.mb }}</td> + </tr> + {% endif %} + + {% if metadata.sequence %} + <tr> + <td><b>Sequence</b></td> + <td>metadata.sequence</td> + </tr> + {% endif %} + + {% if metadata.source %} + <tr> + <td><b>Source</b></td> + <td>{{ metadata.source}}</td> + </tr> + {% endif %} + + {% if metadata.markerName %} + <tr> + <td><b>Marker Name</b></td> + <td>{{ metadata.markerName}}</td> + </tr> + {% endif %} + </table> +</div> + +{% endblock %} diff --git a/gn2/wqflask/templates/glossary.html b/gn2/wqflask/templates/glossary.html new file mode 100644 index 00000000..aaee7c5a --- /dev/null +++ b/gn2/wqflask/templates/glossary.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block title %}Glossary{% endblock %} + +{% block css %} +<link rel="stylesheet" type="text/css" href="/static/new/css/markdown.css" /> +{% endblock %} + +{% block content %} + +<div class="github-btn-container"> + <div class="github-btn"> + <a href="https://github.com/genenetwork/gn-docs/blob/master/general/glossary/glossary.md"> + Edit Text + <img src="/static/images/edit.png"> + </a> + </div> +</div> +<div id="markdown" class="container"> + + {{ rendered_markdown|safe }} +</div> +{% endblock %} diff --git a/gn2/wqflask/templates/gn3_ctl_results.html b/gn2/wqflask/templates/gn3_ctl_results.html new file mode 100644 index 00000000..c42707f6 --- /dev/null +++ b/gn2/wqflask/templates/gn3_ctl_results.html @@ -0,0 +1,101 @@ +{% extends "base.html" %} +{% block title %}Ctl results{% endblock %} +{% block content %} +<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> +<link REL="stylesheet" TYPE="text/css" href="{{ url_for('css', filename='bootstrap/css/bootstrap.css') }}" /> +<link rel="stylesheet" href="https://cdn.datatables.net/1.11.3/css/jquery.dataTables.min.css"> +<style type="text/css"> +.carousel-control-next, +.carousel-control-prev + +/*, .carousel-indicators */ + { + filter: invert(100%); +} +</style> +<div style="margin-top:10px"> + +{% if error %} + <div> + <h4 style="text-align:center;color:red">{{error}}</h4> + </div> + +{% else %} + <div> + <div> + <div style="text-align: center;"> + <h3>CTL/QTL Plots for Individual Traits</h3> + <h4> {{ctl_plots|length}} phenotypes as input</h4> + </div> + <div id="carouselExampleControls" class="carousel slide" data-interval="false"> + <div class="carousel-inner"> + {% for ctl_plot in ctl_plots %} + <div class="item{% if loop.index == 1 %} active{% endif %}"> + <img style="width:1000px;height: 600px;" class="center-block" src="data:image/jpeg;base64,{{ ctl_plot | safe }}" alt="First slide"> + </div> + {% endfor %} + <a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-slide="prev"> + <span class="carousel-control-prev-icon" aria-hidden="true"></span> + <span class="sr-only">Previous</span> + </a> + <a class="carousel-control-next" href="#carouselExampleControls" role="button" data-slide="next"> + <span class="carousel-control-next-icon" aria-hidden="true"></span> + <span class="sr-only">Next</span> + </a> + </div> + </div> + </div> + <div> + <div style="text-align:center;"> + <h2>Ctl line plot</h2> + <h4>Plot the CTL for genome-wide CTL on all traits (the output of CTLscan).</h4> + </div> + <div class="row"> + <div class="col-8"> + <img style="width:100%;height: 600px;" class="center-block" src="data:image/jpeg;base64,{{ image_data | safe }}"> + </div> + <div class="col-4"> + <ol style="height: 100%;display:flex;flex-direction: column;align-items: center;justify-content: center;"> + {% for trait in phenotypes %} + {% set trait_data = trait.split(':') %} + <li><a href="/show_trait?trait_id={{trait_data[0]}}&dataset={{trait_data[1]}}">{{trait_data[0]}}</a></li> + {% endfor %} + </ol> + </div> + </div> + </div> + <h2 style="text-align:center">Significant CTL </h2> + <table id="significance" width="80vw"></table> + <div style="text-align: center;margin-top: 20px;"> + <h3 style="text-align:center;">Network figure</h3> + <div style="margin-top: 20px;"> + <p>Use tools like cytoscape to visualize the network</p> + <a href="/ctl_network_files/{{network_file_name}}/sif" class="btn btn-secondary btn-lg mx-2">Download Sif file</a> + <a href="/ctl_network_files/{{network_file_name}}/nodes" class="btn btn-secondary btn-lg mx-2">Download Node file</a> + </div> + </div> +</div> + +{% endif %} +</div> + +<!-- <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script> --> +<script src="{{ url_for('js', filename='bootstrap/js/bootstrap.min.js') }}" type="text/javascript"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script> +<script type="text/javascript"> +let { data_set_rows, col_names } = {{ significance_data | safe }} + + +$('#significance').DataTable({ + data: data_set_rows, + columns: col_names.map((name) => { + return { + title: name + } + }) +}); +</script> +{% endblock %} + diff --git a/gn2/wqflask/templates/gn3_wgcna_results.html b/gn2/wqflask/templates/gn3_wgcna_results.html new file mode 100644 index 00000000..8a31bf28 --- /dev/null +++ b/gn2/wqflask/templates/gn3_wgcna_results.html @@ -0,0 +1,192 @@ +{% extends "base.html" %} +{% block title %}WCGNA results{% endblock %} +{% block content %} +<!-- Start of body --> + +<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.5/xterm.min.css" integrity="sha512-iLYuqv+v/P4u9erpk+KM83Ioe/l7SEmr7wB6g+Kg1qmEit8EShDKnKtLHlv2QXUp7GGJhmqDI+1PhJYLTsfb8w==" crossorigin="anonymous" referrerpolicy="no-referrer" /> + +<link rel="stylesheet" href="https://cdn.datatables.net/1.11.3/css/jquery.dataTables.min.css"> + + +<style type="text/css"> + + +.container { + min-height: 100vh; + width: 100vw; + padding: 20px; + +} + +.grid_container { + + + width: 80vw; + margin: auto; + padding: 20px; + + + display: grid; + grid-template-columns: repeat(7, 1fr); + /*grid-gap: 5px;*/ + border: 1px solid black; + grid-column-gap: 20px; + +} + +.control_sft_column { + text-align: center; +} + +.grid_container div:not(:last-child) { + border-right: 1px solid #000; +} + +.grid_container .control_sft_column h3 { + font-weight: bold; + font-size: 18px; +} + +.control_net_colors { + + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + text-align: center; +} + + +.control_mod_eigens { + display: grid; + grid-template-columns: repeat(2, 200px); +} + +.control-image{ + display: block; + margin-left: auto; + margin-right: auto; + width: 80vw; +} +</style> +<div class="container"> + {% if error!='null' %} + <h4 style="text-align: center;">{{error}}</h4> + + {% else %} + <div> + <div> + <h2 style="text-align:center;">Parameters used</h2> + + <table id="summary" class="display" width="40vw" ></table> + + </div> + <div > + <h2 style="text-align:center">Soft Thresholds </h2> + <div class="grid_container"> + + {% for key, value in results["data"]["output"]["soft_threshold"].items()%} + <div class="control_sft_column"> + <h3>{{key}}</h3> + {% for val in value %} + <p>{{val|round(3)}}</p> + {% endfor %} + </div> + {% endfor %} + </div> + </div> + + <div> + + {% if image["image_generated"] %} + <div > + <img class="control-image" src="data:image/jpeg;base64,{{ image['image_data']| safe }}"> + </div> + + {% endif %} +<!-- <div > + <img class="control-image" src="data:image/jpeg;base64,{{ results['data']['output']['image_data2']| safe }}"> + </div> --> + </div> + + <div> + <h2 style="text-align:center;"> Module eigen genes </h2> + <table id="eigens" class="display" width="80vw"></table> + </div> + + <div> + <h2 style="text-align:center;">Phenotype modules </h2> + + <table id="phenos" class="display" width="40vw" ></table> + </div> + </div> + +{% endif %} + +</div> + +{% endblock %} + +{% block js %} + +<script src="https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.5/xterm.min.js"></script> + +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script> + + +<script type="text/javascript"> + + +let phenoModules = {{results|safe}}["data"]["output"]["net_colors"] +let phenotypes = Object.keys(phenoModules) +let phenoMods = Object.values(phenoModules) + +let {col_names,mod_dataset} = {{data|safe}} +const summary = { + modules_found:phenoMods.length, + ...{{parameters|safe}} + +} + + $("#summary").DataTable( { + + data: Object.keys(summary).map((key)=>{ + return [key,summary[key]] + }), + columns: [ { + title: "parameter", + + }, + + { + title:" param_values" + } + ] + }) + + + $('#eigens').DataTable( { + data: mod_dataset, + columns: col_names.map((name)=>{ + return { + title:name + } + }) + } ); + $('#phenos').DataTable( { + data:phenotypes.map((phenoName,idx)=>{ + return [phenoName,phenoMods[idx]] + }), + columns: [{ + title:"Phenotypes" + }, + { + title: "Modules" + }] + } ); + + +</script> +{% endblock %} diff --git a/gn2/wqflask/templates/gnqa.html b/gn2/wqflask/templates/gnqa.html new file mode 100644 index 00000000..46cc1e90 --- /dev/null +++ b/gn2/wqflask/templates/gnqa.html @@ -0,0 +1,84 @@ +{% extends "base.html" %} +{% block title %}GNQNA{% endblock %} + +{% block content %} <!-- Start of body --> + + + <style> + + + + #gnqna_search { + padding:15px 10px; + border-radius: 15px; + border:1px solid blue; + } + + #gnqna_search{ + font-weight: bold; + font-weight: 18px; + } + + #main-search-controller{ + display: flex; + justify-content: center; + align-items: center; +} + + #loader { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + background: rgba(0,0,0,0.75) url("/static/gif/loader.gif") no-repeat center center; + z-index: 10000; +} + + +</style> + + +<div style="height: 100vh;width: 100vw; "> + +<div id="loader"></div> +<div id="main-search-controller" style="margin: 20px;width: 100%;height: 100%;"> +<div id="gnqna_controller"> + <form method="POST" action="/gnqna" id="gnqna_form"> + <input id="gnqna_search" style="width:45vw" type="text" autocomplete="off" +required placeholder="Ask a Question or Topic e.g ( Genes) " value='' name="querygnqa"> + +</form> +</div> + +</div> + +<div> + +</div> + + +<script src="{{ url_for('js', filename='jquery/jquery.min.js') }}" type="text/javascript"></script> + + <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script> +<script type="text/javascript"> + + document.addEventListener('DOMContentLoaded', function() { + $("#gnqna_search").keypress(function(event) { + if (event.keyCode === 13) { + $('#gnqna_form').submit(); + let spinner = $("#loader") + spinner.show() + } + }) + + + }); + + +</script> +<!-- Bootstrap JS and Popper.js --> +{% endblock %} + diff --git a/gn2/wqflask/templates/gnqa_answer.html b/gn2/wqflask/templates/gnqa_answer.html new file mode 100644 index 00000000..99242b29 --- /dev/null +++ b/gn2/wqflask/templates/gnqa_answer.html @@ -0,0 +1,174 @@ +{% extends "base.html" %} +{% block title %}GNQNA{% endblock %} + +{% block content %} <!-- Start of body --> + + + <style> + + + + #gnqna_search { + padding:15px 10px; + border-radius: 15px; + border:1px solid blue; + } + + #gnqna_search{ + font-weight: bold; + font-weight: 18px; + } + + #main-search-controller{ + display: flex; + justify-content: center; + align-items: center; +} + + +</style> + +{% block css %} +<link rel="stylesheet" type="text/css" href="/static/new/css/markdown.css" /> +{% endblock %} + + +<style type="text/css"> + + #gnqa_ref { + height: 90vh; + overflow: hidden; + overflow-y: scroll; + } + + + #gnqa_ref_item{ + border-bottom: 1px solid lightgrey; + padding:12px; + } + + + #gnqa_query { + font-size: 24px; + font-family: serif; + color: #3071a9; + font-style: italic; + letter-spacing: 0.3em; + text-transform: capitalize; + + text-decoration: underline; + } + + #gnqa_query::first-letter { + font-size: 30px; + font-style: italic; + + } + + #gnqa_answer { + text-transform: capitalize; + } + + #gnqa_answer::first-letter{ + font-weight: bold; + + + } + #gnqna_search_home{ + display: flex; + justify-content: center; + align-items: center; + } + + + #gnqna_search_home input{ + padding:7px 5px; + border-radius: 10px; + border:1px solid blue; + } + + #gnqna_search_home input{ + font-weight: bold; + } + + .container{ + / box-shadow: rgb(38, 57, 77) 0px 20px 30px -10px; */ + } + + #loader { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + background: rgba(0,0,0,0.75) url("/static/gif/loader.gif") no-repeat center center; + z-index: 10000; +} + + + +</style> + +<div style="height: 90vh;width: 100vw; margin-bottom: 10px;"> + + <div id="loader"> + + </div> +<div style="margin:10px; border:1px solid #ccc;padding: 15px;"> + <div > + <h1 id="gnqa_query" style="font-weight: bolder">{{query}}<h1> + </div> + + <div style="width:40vw;overflow-wrap: normal; margin-top: 5px;"> + <p style="word-spacing: 0.7em;" id="gnqa_answer">{{answer}} <p> + </div> +</div> + +<div> + + + + <div class="container" style="overflow-y: hidden;"> + <h4> <a href="#">References</a></h4> + {% if references %} + <ul id="gnqa_ref"> + {% for reference in references %} + <li id="gnqa_ref_item"> + <h2 style="font-weight:bolder;">{{ reference.bibInfo }} </h2> + <p>{{reference.comboTxt}}</p> + </li> + {% endfor %} + </ul> + {% else %} + <p>No references available.</p> + {% endif %} + </div> +</div> +</div> +<script type="text/javascript"> + + document.addEventListener('DOMContentLoaded', function() { + $('#globalsearchform').hide() + $('#gnqna_search_home').show() + $('footer').hide() + $("#gnqna_search_home_input").keypress(function(event) { + if (event.keyCode === 13) { + console.log("clicked this button") + $('#gnqna_search_home').submit(); + let spinner = $("#loader") + spinner.show(); + + } + }) + }); + +</script> + +<!-- Bootstrap JS and Popper.js --> + + + +{% endblock %} + diff --git a/gn2/wqflask/templates/gsearch_gene.html b/gn2/wqflask/templates/gsearch_gene.html new file mode 100644 index 00000000..6bc92377 --- /dev/null +++ b/gn2/wqflask/templates/gsearch_gene.html @@ -0,0 +1,267 @@ +{% extends "base.html" %} +{% block title %}Search Results{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet" type="text/css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{% endblock %} +{% block content %} +<!-- Start of body --> + + <div class="container"> + + <h3>GN searched for the term(s) <b>"{{ terms }}"</b> in 754 datasets and 39,765,944 traits across 10 species<br/> + and found <b>{{ trait_count }}</b> results that match your query.<br/> + You can filter these results by adding key words in the fields below<br/> + and you can also sort results on most columns.</h3> + <p>To study a record, click on its Record ID below.<br />Check records below and click Add button to add to selection.</p> + + <div> + <br /> + <button class="btn btn-default" id="select_all"><span class="glyphicon glyphicon-ok"></span> Select All</button> + <button class="btn btn-default" id="deselect_all"><span class="glyphicon glyphicon-remove"></span> Deselect All</button> + <button class="btn btn-default" id="invert"><span class="glyphicon glyphicon-resize-vertical"></span> Invert</button> + <button class="btn btn-success" id="add" disabled ><span class="glyphicon glyphicon-plus-sign"></span> Add</button> + <input type="text" id="searchbox" class="form-control" style="width: 180px; display: inline;" placeholder="Search This Table For ..."> + <input type="text" id="select_top" class="form-control" style="width: 120px; display: inline;" placeholder="Select Top ..."> + <form id="export_form" method="POST" action="/export_traits_csv" style="display: inline;"> + <input type="hidden" name="headers" id="headers" value="{% for field in header_fields %}{{ field }},{% endfor %}"> + <input type="hidden" name="database_name" id="database_name" value="None"> + <input type="hidden" name="export_data" id="export_data" value=""> + <button class="btn btn-default" id="export_traits">Download</button> + </form> + <br /> + <br /> + <div id="table_container" style="width: 2000px;"> + <table id="trait_table" class="table-hover table-striped cell-border" style="float: left;"> + <tbody> + <td colspan="100%" align="center"><br><b><font size="15">Loading...</font></b><br></td> + </tbody> + </table> + </div> + </div> + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/md5.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='jszip/jszip.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/natural.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/colReorder/js/dataTables.colReorder.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/colResize/dataTables.colResize.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/search_results.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/table_functions.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/create_datatable.js"></script> + + <script type='text/javascript'> + var getParams = function(url) { + let parser = document.createElement('a'); + parser.href = url; + let params = parser.search.substring(1); + if(params.length > 0) { + return ('?'+params); + } + return params; + }; + </script> + + <script type='text/javascript'> + var traitsJson = {{ trait_list|safe }}; + </script> + + <script type="text/javascript" charset="utf-8"> + $(document).ready( function () { + var tableId = "trait_table"; + + columnDefs = [ + { + 'orderDataType': "dom-checkbox", + 'width': "5px", + 'data': null, + 'targets': 0, + 'render': function(data) { + return '<input type="checkbox" name="searchResult" class="trait_checkbox checkbox" value="' + data.hmac + '">' + } + }, + { + 'title': "Index", + 'type': "natural", + 'width': "30px", + 'targets': 1, + 'data': "index", + 'defaultContent': "N/A" + }, + { + 'title': "Record", + 'type': "natural", + 'orderDataType': "dom-inner-text", + 'width': "60px", + 'data': null, + 'targets': 2, + 'render': function(data) { + return '<a target="_blank" href="/show_trait?trait_id=' + data.name + '&dataset=' + data.dataset + '">' + data.name + '</a>' + } + }, + { + 'title': "Species", + 'type': "natural", + 'width': "60px", + 'targets': 3, + 'data': "species", + 'defaultContent': "N/A" + }, + { + 'title': "Group", + 'type': "natural", + 'width': "150px", + 'targets': 4, + 'data': "group", + 'defaultContent': "N/A" + }, + { + 'title': "Tissue", + 'type': "natural", + 'width': "150px", + 'targets': 5, + 'data': "tissue", + 'defaultContent': "N/A" + }, + { + 'title': "Dataset", + 'type': "natural", + 'targets': 6, + 'width': "320px", + 'data': "dataset_fullname", + 'defaultContent': "N/A" + }, + { + 'title': "Symbol", + 'type': "natural", + 'width': "60px", + 'targets': 7, + 'data': "symbol", + 'defaultContent': "N/A" + }, + { + 'title': "Description", + 'type': "natural", + 'data': null, + 'width': "120px", + 'targets': 8, + 'render': function(data) { + try { + return decodeURIComponent(escape(data.description)) + } catch(err) { + return escape(data.description) + } + } + }, + { + 'title': "Location", + 'type': "natural-minus-na", + 'width': "125px", + 'targets': 9, + 'data': "location_repr", + 'defaultContent': "N/A" + }, + { + 'title': "Mean", + 'type': "natural-minus-na", + 'orderSequence': [ "desc", "asc"], + 'width': "30px", + 'targets': 10, + 'data': null, + 'defaultContent': "N/A", + 'render': function(data) { + if (data.mean > 100) { + return Math.log2(data.mean).toFixed(3) + } else { + return data.mean + } + } + }, + { + 'title': "<div style='text-align: right; padding-right: 10px;'>Peak</div> <div style='text-align: right;'>-logP <a href=\"{{ url_for('glossary_blueprint.glossary') }}#LRS\" target=\"_blank\" style=\"color: white;\"><sup>?</sup></a></div>", + 'type': "natural-minus-na", + 'width': "60px", + 'targets': 11, + 'data': "LRS_score_repr", + 'defaultContent': "N/A", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Peak Location", + 'type': "natural-minus-na", + 'width': "125px", + 'targets': 12, + 'data': "max_lrs_text", + 'defaultContent': "N/A" + }, + { + 'title': "Additive<br>Effect<a href=\"{{ url_for('glossary_blueprint.glossary') }}#A\" target=\"_blank\" style=\"color: white;\"><sup>?</sup></a>", + 'type': "natural-minus-na", + 'width': "50px", + 'targets': 13, + 'data': "additive", + 'defaultContent': "N/A", + 'orderSequence': [ "desc", "asc"] + } + ] + + tableSettings = { + 'createdRow': function ( row, data, index ) { + $('td', row).eq(0).attr("style", "text-align: center; padding: 0px 10px 2px 10px;"); + $('td', row).eq(1).attr("align", "right"); + $('td', row).eq(4).attr('title', $('td', row).eq(4).text()); + if ($('td', row).eq(4).text().length > 30) { + $('td', row).eq(4).text($('td', row).eq(4).text().substring(0, 30)); + $('td', row).eq(4).text($('td', row).eq(4).text() + '...') + } + $('td', row).eq(5).attr('title', $('td', row).eq(5).text()); + if ($('td', row).eq(5).text().length > 35) { + $('td', row).eq(5).text($('td', row).eq(5).text().substring(0, 35)); + $('td', row).eq(5).text($('td', row).eq(5).text() + '...') + } + $('td', row).eq(6).attr('title', $('td', row).eq(6).text()); + if ($('td', row).eq(6).text().length > 60) { + $('td', row).eq(6).text($('td', row).eq(6).text().substring(0, 60)); + $('td', row).eq(6).text($('td', row).eq(6).text() + '...') + } + $('td', row).eq(8).attr('title', $('td', row).eq(8).text()); + if ($('td', row).eq(8).text().length > 60) { + $('td', row).eq(8).text($('td', row).eq(8).text().substring(0, 60)); + $('td', row).eq(8).text($('td', row).eq(8).text() + '...') + } + $('td', row).slice(10,14).attr("align", "right"); + $('td', row).eq(1).attr('data-export', $('td', row).eq(1).text()); + $('td', row).eq(2).attr('data-export', $('td', row).eq(2).text()); + $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); + $('td', row).eq(4).attr('data-export', $('td', row).eq(4).text()); + $('td', row).eq(5).attr('data-export', $('td', row).eq(5).text()); + $('td', row).eq(6).attr('data-export', $('td', row).eq(6).text()); + $('td', row).eq(7).attr('data-export', $('td', row).eq(7).text()); + $('td', row).eq(8).attr('data-export', $('td', row).eq(8).text()); + $('td', row).eq(9).attr('data-export', $('td', row).eq(9).text()); + $('td', row).eq(10).attr('data-export', $('td', row).eq(10).text()); + $('td', row).eq(11).attr('data-export', $('td', row).eq(11).text()); + $('td', row).eq(12).attr('data-export', $('td', row).eq(12).text()); + $('td', row).eq(13).attr('data-export', $('td', row).eq(13).text()); + }, + {% if trait_count <= 20 %} + "scroller": false + {% else %} + "scroller": true + {% endif %} + } + + create_table(tableId, traitsJson, columnDefs, tableSettings); + + }); + + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/gsearch_pheno.html b/gn2/wqflask/templates/gsearch_pheno.html new file mode 100644 index 00000000..a1fef2c8 --- /dev/null +++ b/gn2/wqflask/templates/gsearch_pheno.html @@ -0,0 +1,238 @@ +{% extends "base.html" %} +{% block title %}Search Results{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet" type="text/css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{% endblock %} +{% block content %} +<!-- Start of body --> + + <div class="container"> + + <h3>GN searched for the term(s) <b>"{{ terms }}"</b> in 51 datasets and 13763 traits across 10 species<br/> + and found <b>{{ trait_count }}</b> results that match your query.<br/> + You can filter these results by adding key words in the fields below<br/> + and you can also sort results on most columns.</h3> + <p>To study a record, click on its ID below.<br />Check records below and click Add button to add to selection.</p> + + <div> + <br /> + <button class="btn btn-default" id="select_all"><span class="glyphicon glyphicon-ok"></span> Select All</button> + <button class="btn btn-default" id="deselect_all"><span class="glyphicon glyphicon-remove"></span> Deselect All</button> + <button class="btn btn-default" id="invert"><span class="glyphicon glyphicon-resize-vertical"></span> Invert</button> + <button class="btn btn-success" id="add" disabled ><span class="glyphicon glyphicon-plus-sign"></span> Add</button> + <input type="text" id="searchbox" class="form-control" style="width: 180px; display: inline;" placeholder="Search This Table For ..."> + <input type="text" id="select_top" class="form-control" style="width: 120px; display: inline;" placeholder="Select Top ..."> + <form id="export_form" method="POST" action="/export_traits_csv" style="display: inline;"> + <input type="hidden" name="headers" id="headers" value="{% for field in header_fields %}{{ field }},{% endfor %}"> + <input type="hidden" name="database_name" id="database_name" value="None"> + <input type="hidden" name="export_data" id="export_data" value=""> + <button class="btn btn-default" id="export_traits">Download CSV</button> + </form> + <br /> + <br /> + <div id="table_container" style="width: 2000px;"> + <table id="trait_table" class="table-hover table-striped cell-border" style="float: left;"> + <tbody> + <td colspan="100%" align="center"><br><b><font size="15">Loading...</font></b><br></td> + </tbody> + </table> + </div> + </div> + </div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/md5.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='jszip/jszip.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/natural.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/colReorder/js/dataTables.colReorder.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/colResize/dataTables.colResize.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/search_results.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/table_functions.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/create_datatable.js"></script> + + <script type='text/javascript'> + var getParams = function(url) { + let parser = document.createElement('a'); + parser.href = url; + let params = parser.search.substring(1); + if(params.length > 0) { + return ('?'+params); + } + return params; + }; + </script> + + <script type='text/javascript'> + var traitsJson = {{ trait_list|safe }}; + </script> + + <script type="text/javascript" charset="utf-8"> + $(document).ready( function () { + var tableId = "trait_table"; + + columnDefs = [ + { + 'data': null, + 'orderDataType': "dom-checkbox", + 'width': "10px", + 'targets': 0, + 'render': function(data) { + return '<input type="checkbox" name="searchResult" class="trait_checkbox checkbox" value="' + data.hmac + '">' + } + }, + { + 'title': "Index", + 'type': "natural", + 'width': "30px", + 'targets': 1, + 'data': "index", + 'defaultContent': "N/A" + }, + { + 'title': "Species", + 'type': "natural", + 'width': "60px", + 'targets': 2, + 'data': "species", + 'defaultContent': "N/A" + }, + { + 'title': "Group", + 'type': "natural", + 'width': "100px", + 'targets': 3, + 'data': "group", + 'defaultContent': "N/A" + }, + { + 'title': "Record", + 'type': "natural", + 'data': null, + 'width': "60px", + 'targets': 4, + 'orderDataType': "dom-inner-text", + 'render': function(data) { + return '<a target="_blank" href="/show_trait?trait_id=' + data.name + '&dataset=' + data.dataset + '">' + data.display_name + '</a>' + } + }, + { + 'title': "Description", + 'type': "natural", + 'width': "500px", + 'targets': 5, + 'data': null, + 'render': function(data) { + try { + return decodeURIComponent(escape(data.description)) + } catch(err) { + return data.description + } + } + }, + { + 'title': "Mean", + 'type': "natural-minus-na", + 'width': "30px", + 'targets': 6, + 'data': "mean", + 'defaultContent': "N/A" + }, + { + 'title': "Authors", + 'type': "natural", + 'width': "300px", + 'targets': 7, + 'data': "authors_display", + 'defaultContent': "N/A" + }, + { + 'title': "Year", + 'type': "natural-minus-na", + 'data': null, + 'orderDataType': "dom-inner-text", + 'width': "25px", + 'targets': 8, + 'render': function(data) { + if ("pubmed_id" in data){ + return '<a href="' + data.pubmed_link + '">' + data.pubmed_text + '</a>' + } else { + return data.pubmed_text + } + }, + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "<div style='text-align: right; padding-right: 10px;'>Peak</div> <div style='text-align: right;'>-logP <a href=\"{{ url_for('glossary_blueprint.glossary') }}#LRS\" target=\"_blank\" style=\"color: white;\"><sup>?</sup></a></div>", + 'type': "natural-minus-na", + 'data': "LRS_score_repr", + 'defaultContent': "N/A", + 'width': "60px", + 'targets': 9, + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Peak Location", + 'type': "natural-minus-na", + 'width': "125px", + 'targets': 10, + 'data': "max_lrs_text", + 'defaultContent': "N/A" + }, + { + 'title': "Additive Effect<a href=\"{{ url_for('glossary_blueprint.glossary') }}#A\" target=\"_blank\" style=\"color: white;\"><sup>?</sup></a>", + 'type': "natural-minus-na", + 'data': "additive", + 'defaultContent': "N/A", + 'width': "60px", + 'targets': 11, + 'orderSequence': [ "desc", "asc"] + } + ] + + tableSettings = { + "createdRow": function ( row, data, index ) { + $('td', row).eq(0).attr("style", "text-align: center; padding: 4px 10px 2px 10px;"); + $('td', row).eq(1).attr("align", "right"); + $('td', row).eq(5).attr('title', $('td', row).eq(5).text()); + if ($('td', row).eq(5).text().length > 150) { + $('td', row).eq(5).text($('td', row).eq(5).text().substring(0, 150)); + $('td', row).eq(5).text($('td', row).eq(5).text() + '...') + } + $('td', row).eq(6).attr('title', $('td', row).eq(6).text()); + if ($('td', row).eq(6).text().length > 150) { + $('td', row).eq(6).text($('td', row).eq(6).text().substring(0, 150)); + $('td', row).eq(6).text($('td', row).eq(6).text() + '...') + } + $('td', row).eq(6).attr("align", "right"); + $('td', row).slice(8,11).attr("align", "right"); + $('td', row).eq(1).attr('data-export', $('td', row).eq(1).text()); + $('td', row).eq(2).attr('data-export', $('td', row).eq(2).text()); + $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); + $('td', row).eq(4).attr('data-export', $('td', row).eq(4).text()); + $('td', row).eq(5).attr('data-export', $('td', row).eq(5).text()); + $('td', row).eq(6).attr('data-export', $('td', row).eq(6).text()); + $('td', row).eq(7).attr('data-export', $('td', row).eq(7).text()); + $('td', row).eq(8).attr('data-export', $('td', row).eq(8).text()); + $('td', row).eq(9).attr('data-export', $('td', row).eq(9).text()); + $('td', row).eq(10).attr('data-export', $('td', row).eq(10).text()); + }, + {% if trait_count <= 20 %} + "scroller": false + {% else %} + "scroller": true + {% endif %} + } + + create_table(tableId, traitsJson, columnDefs, tableSettings); + + }); + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/heatmap.html b/gn2/wqflask/templates/heatmap.html new file mode 100644 index 00000000..92754266 --- /dev/null +++ b/gn2/wqflask/templates/heatmap.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} +{% block title %}Heatmap{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='d3-tip/d3-tip.css') }}" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/panelutil.css" /> +{% endblock %} +{% block content %} <!-- Start of body --> + <div class="container"> + <h1>Heatmap</h1> + <hr style="height: 1px; background-color: #A9A9A9;"> + <div> + <h3> + The following heatmap is a work in progress. The heatmap for each trait runs horizontally (as opposed to vertically in GeneNetwork 1), + and hovering over a given trait's heatmap track will display its corresponding QTL chart below. White on the heatmap corresponds with a + low positive or negative z-score (darker when closer to 0), while light blue and red correspond to high negative and positive z-scores respectively. + </h3> + </div> + <div id="chart_container"> + <div class="qtlcharts" id="chart"> + + </div> + </div> + + </div> + + <!-- End of body --> + +{% endblock %} + +{% block js %} + <script> + js_data = {{ js_data | safe }} + </script> + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3js/d3.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3-tip/d3-tip.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/panelutil.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/lodheatmap.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/lod_chart.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/curvechart.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/iplotMScanone_noeff.js"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> + +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/index_page.html b/gn2/wqflask/templates/index_page.html new file mode 100755 index 00000000..7b5a1d16 --- /dev/null +++ b/gn2/wqflask/templates/index_page.html @@ -0,0 +1,397 @@ +{% extends "base.html" %} +{% block title %}GeneNetwork{% endblock %} +{% block css %} +<!-- UIkit CSS --> +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.7.4/dist/css/uikit.min.css" /> +<link rel="stylesheet" href="/static/new/css/index_page.css" /> +<link rel="stylesheet" href="/static/new/css/autocomplete.css"/> + +<!-- UIkit JS --> +<script src="https://cdn.jsdelivr.net/npm/uikit@3.7.4/dist/js/uikit.min.js"></script> +<script src="https://cdn.jsdelivr.net/npm/uikit@3.7.4/dist/js/uikit-icons.min.js"></script> +<script type="module" src="https://esm.sh/emfed"></script> +<link rel="stylesheet" type="text/css" + href="https://cdn.jsdelivr.net/gh/sampsyo/emfed@1/toots.css"> + +<style TYPE="text/css"> + p.interact { display: none; } + + + .media { + padding-bottom:10px; + border-bottom: 1px solid #c8ccc9; + } + + .media img { + + width: 95%; + height: 100%; + border-radius: 5px; + /*transform: scale(1.1); image small?*/ + border:1px solid #c8ccc9; + } + + .toots { + margin:0 auto; + max-width: 500px; + height: 160px; + position: relative; + border-radius:10px; + background-color: #F9F9F9; + + } + + h2 { + margin-bottom: 0px; + } + + ul { + margin-top: 0px; + } + + .toot { + padding-top:8px; + background:#f9f9f9; + color:#000; + + } +</style> +{% endblock %} +{% block content %} +<!-- Start of body --> + <div class="container-fluid" style="min-width: 1210px;"> + + {{ flash_me() }} + + {%if anon_collections | length > 0%} + <div class="panel panel-warning"> + <div class="panel-heading"> + <h3 class="panel-title">Import Anonymous Collections</h3> + </div> + <div class="panel-body"> + <p> + There {%if anon_collections | length > 1%}are{%else%}is{%endif%} + {{anon_collections | length}} anonymous + collection{%if anon_collections | length > 1%}s{%endif%} + associated with your current session. What do you wish to do? + </p> + <p class="text-danger" style="font-weight: bold;"> + <small> + If you choose to ignore this, the anonymous collections will be + eventually deleted and lost. + </small> + </p> + <form action="{{url_for('handle_anonymous_collections')}}" + method="POST"> + <div class="form-group"> + <input type="radio" id="rdo-import" value="import" + name="anon_choice" /> + <label for="rdo-import">Import</label> + <input type="radio" id="rdo-delete" value="delete" + name="anon_choice" /> + <label for="rdo-delete">Delete</label> + </div> + + <input type="submit" class="btn btn-warning" value="Submit" /> + </form> + </div> + </div> + {%endif%} + + <div class="row" style="width: 100%;"> + + <div class="col-xs-4" style="margin-right:50px; min-width: 530px; max-width: 550px;"> + <section id="search"> + <div class="page-header"> + <h2>Select and Search</h2> + </div> + <form method="get" action="/search" target="_blank" id="searchform" name="SEARCHFORM", + data-gn_server_url="{{gn_server_url}}"> + <fieldset> + <div style="padding-left: 20px; padding-right: 20px;" class="form-horizontal"> + + <div class="form-group"> + <label for="species" class="col-xs-1 control-label" style="width: 65px !important;">Species:</label> + <div class="col-xs-10 controls input-append" style="display: flex; padding-left: 20px;"> + <div class="col-9"> + <select name="species" id="species" class="form-control" style="width: 340px !important;"><option>Loading...</option></select> + </div> + </div> + </div> + + <div class="form-group"> + <label for="group" class="col-xs-1 control-label" style="width: 65px !important;">Group:</label> + <div class="col-xs-10 controls input-append" style="display: flex; padding-left: 20px;"> + <div class="col-9"> + <select name="group" id="group" class="form-control" style="width: 340px !important;"><option>Loading...</option></select> + <i class="icon-question-sign"></i> + </div> + <div class="col-3" style="margin-left: 10px;"> + <button type="button" id="group_info" class="btn form-control info-button"><span class="glyphicon glyphicon-flag"></span> Info</button> + </div> + </div> + </div> + + <div class="form-group"> + <label for="tissue" class="col-xs-1 control-label" style="width: 65px !important;">Type:</label> + <div class="col-xs-10 controls input-append" style="display: flex; padding-left: 20px;"> + <div class="col-9"> + <select name="type" id="type" class="form-control" style="width: 340px !important;"><option>Loading...</option></select> + </div> + <div class="col-3" style="margin-left: 10px;"> + <button type="button" id="dataset_info" class="btn form-control info-button"><span class="glyphicon glyphicon-flag"></span> Info</button> + </div> + </div> + </div> + + <div class="form-group"> + <label for="dataset" class="col-xs-1 control-label" style="width: 65px !important;">Dataset:</label> + <div class="col-xs-10 controls" style="display: flex; padding-left: 20px;"> + <div class="col-9"> + <select name="dataset" id="dataset" class="form-control" style="max-width: 550px; width: 450px !important;"><option>Loading...</option></select> + <i class="icon-question-sign"></i> + </div> + </div> + </div> + <!-- GET ANY SEARCH --> + + <div class="form-group"> + <label for="or_search" class="col-xs-1 control-label" style="padding-left: 0px; padding-right: 0px; width: 65px !important;">Get Any:</label> + <div class="col-xs-10 controls" style="padding-left: 20px;"> + <div class="col-8 autocomplete"> + <textarea id="myInput" onkeyup="pressed(event)" name="search_terms_or" rows="1" class="form-control search-query" style="resize: vertical; max-width: 550px; width: 450px !important;" id="or_search"></textarea> + </div> + </div> + </div> + + <!-- GET ANY HELP --> + <div class="form-group"> + <label for="btsearch" class="col-xs-1 control-label" style="width: 65px !important;"></label> + <div class="col-xs-10 controls" style="padding-left: 20px;"> + <div class="col-12 controls"> + Enter terms, genes, ID numbers in the <b>Search</b> field.<br> + Use <b>*</b> or <b>?</b> wildcards (Cyp*a?, synap*).<br> + Use <b>quotes</b> for terms such as <i>"tyrosine kinase"</i>. + + + <div><strong><a style="text-decoration: none;" href="https://issues.genenetwork.org/topics/xapian-search-queries" target=â€_blank†>see more hints</a></strong></div> + </div> + </div> + </div> + + <div class="form-group"> + <label for="and_search" class="col-xs-1 control-label" style="padding-left: 0px; padding-right: 0px; width: 65px !important;">Combined:</label> + <div class="col-xs-10 controls" style="padding-left: 20px;"> + <div class="col-8 autocomplete"> + <textarea id="myInput" onkeyup="pressed(event)" name="search_terms_and" rows="1" class="form-control search-query" style="resize: vertical; max-width: 550px; width: 450px !important;" id="and_search"></textarea> + </div> + </div> + </div> + + <div class="form-group"> + <label for="btsearch" class="col-xs-1 control-label" style="width: 65px !important;"></label> + <div class="col-xs-10 controls" style="display: flex; padding-left: 20px;"> + <div class="col-2 controls"> + <button id="btsearch" class="btn btn-primary form-control"><span class="glyphicon glyphicon-search"></span> Search</button> + </div> + <div class="col-2 align-self-end controls" style="padding-left: 20px;"> + <button type="button" id="make_default" class="btn form-control info-button"><span class="glyphicon glyphicon-pushpin"></span> Lock Menu</button> + </div> + </div> + </div> + + <!-- SEARCH, MAKE DEFAULT --> + <div class="form-group"> + </div> + + <input type="hidden" name="FormID" value="searchResult" class="form-control"> + </div> + </fieldset> + </form> + </section> + <section id="advanced"> + <div class="page-header"> + <h2>Advanced Commands</h2> + </div> + + <p>You can also use advanced commands. Copy these simple examples into the Get Any field for single term searches and Combined for searches with multiple terms:</p> + + <ul> + <li><b>POSITION=(chr1 25 30)</b> finds genes, markers, or transcripts on + chromosome 1 between 25 and 30 Mb.</li> + + <li><b>MEAN=(15 16)</b> in the <b>Combined</b> field finds + highly expressed genes (15 to 16 log2 units)</li> + + <li><b>RANGE=(1.5 2.5)</b> in the <b>Any</b> field finds traits with values with a specified fold-range (minimum = 1). + Useful for finding "housekeeping genes" <b>(1.0 1.2)</b> or highly variable molecular assays <b>(10 100)</b>.</li> + + <li><b>LRS=(15 1000)</b> or <b>LOD=(2 8)</b> finds all traits with peak LRS or LOD scores between lower and upper limits.</li> + + <li><b>LRS=(9 999 Chr4 122 155)</b> finds all traits on Chr 4 from 122 and 155 Mb with LRS scores between 9 and 999.</li> + + <li><b>cisLRS=(15 1000 5)</b> or <b>cisLOD=(2 8 5)</b> finds all cis eQTLs with peak LRS or LOD scores between lower and upper limits, + with an <b>inclusion</b> zone of 5 Mb around the parent gene.</li> + + <li><b>transLRS=(15 1000 5)</b> or <b>transLOD=(2 8 5)</b> finds all trans eQTLs with peak LRS or LOD scores between lower and upper limits, + with an <b>exclusion</b> zone of 5 Mb around the parent gene. You can also add a fourth term specifying which chromosome you want the transLRS to be on + (for example transLRS=(15 1000 5 7) would find all trans eQTLs with peak LRS on chromosome 7 that is also a trans eQTL with exclusionary zone of 5Mb).</li> + + <li><b>POSITION=(Chr4 122 130) cisLRS=(9 999 10)</b> + finds all traits on Chr 4 from 122 and 155 Mb with cisLRS scores + between 9 and 999 and an inclusion zone of 10 Mb.</li> + + <li><b>RIF=mitochondrial</b> searches RNA databases for <a href="https://en.wikipedia.org/wiki/GeneRIF"> + GeneRIF</a> links.</li> + + <li><b>WIKI=nicotine</b> searches <a href="http://gn1.genenetwork.org/webqtl/main.py?FormID=geneWiki"> + GeneWiki</a> for genes that you or other users have annotated + with the word <i>nicotine</i>.</li> + + <li><b>GO:0045202</b> searches for synapse-associated genes listed in the + <a href="http://amigo.geneontology.org/amigo/medial_search?q=GO%3A0045202"> + Gene Ontology</a>.</li> + + <li><b>RIF=diabetes LRS=(9 999 Chr2 100 105) transLRS=(9 999 10)</b> + finds diabetes-associated transcripts with peak <a href="{{ url_for('glossary_blueprint.glossary') }}#E"> + trans eQTLs</a> on Chr 2 between 100 and 105 Mb with LRS + scores between 9 and 999.</li> + </ul> + </section> + </div> + + <div class="col-xs-4" style="width: 600px !important;"> + <section id="tutorials"> + <div class="page-header"> + <h2>Tutorials</h2> + </div> + <div class="uk-grid-match uk-child-width-1-3@m" uk-grid> + <div> + <strong><a class="uk-link-text" href="/tutorials">Webinars & Courses</a></strong><br> + In-person courses, live webinars and webinar recordings<br> + <a href="/tutorials" class="uk-icon-link" uk-icon="laptop" ratio="2"></a> + </div> + <div> + <strong><a class="uk-link-text" href="/tutorials">Tutorials</a></strong><br> + Tutorials: Training materials in HTML, PDF and video formats<br> + <a href="/tutorials" class="uk-icon-link" uk-icon="file-text" ratio="2"></a> + </div> + <div> + <strong><a class="uk-link-text" href="/tutorials">Documentation</a></strong><br> + Online manuals, handbooks, fact sheets and FAQs<br> + <a href="/tutorials" class="uk-icon-link" uk-icon="album" ratio="2"></a> + </div> + </div> + </section> + <section id="news-section"> + <div class="page-header"> + <h2>News</h2> + </div> + <div id="mastadon" style="height: 405px; overflow: scroll; overflow-x: hidden;"> + <a class="mastodon-feed" target="_blank" + href="https://genomic.social/@genenetwork" + data-toot-limit="5" + >Genenetwork mastadon</a> + + </div> + <div align="right"> + + <a href="https://genomic.social/@genenetwork">more news items...</a> + </div> + </section> + <section id="websites"> + <div class="page-header"> + <h2>Links</h2> + </div> + <ul> + <li>Github</li> + <ul> + <li><a href="https://github.com/genenetwork/genenetwork2">GN2 Source Code</a></li> + <li><a href="https://github.com/genenetwork/genenetwork3">GN3 Source Code</a></li> + <li><a href="https://github.com/genenetwork/genenetwork">GN1 Source Code</a></li> + <li><a href="https://github.com/genenetwork/sysmaintenance">System Maintenance Code</a></li> + </ul> + </ul> + <ul> + <li>GeneNetwork v2</li> + <ul> + <li><a href="https://genenetwork.org/">Main website</a> at UTHSC</li> + </ul> + </ul> + <ul> + <li>GeneNetwork v1</li> + <ul> + <li><a href="http://gn1.genenetwork.org/">Main website</a> at UTHSC</li> + </ul> + </ul> + <ul> + <li>Affiliates</li> + <ul> + <li><b><a href="http://gn1.genenetwork.org">GeneNetwork 1</a> at UTHSC</b></li> + <li><a href="https://systems-genetics.org/">Systems Genetics</a> at EPFL</li> + <li><a href="http://bnw.genenetwork.org/">Bayesian Network Web Server</a> at UTHSC</li> + <li><a href="https://www.geneweaver.org/">GeneWeaver</a></li> + <li><a href="https://phenogen.org/">PhenoGen</a> at University of Colorado</li> + <li><a href="http://www.webgestalt.org/">WebGestalt</a> at Baylor</li> + </ul> + </ul> + <ul> + <li>GeneNetwork hosted portals</li> + <ul> + <li><a href="https://www.opar.io/">Omics Portal For Addiction Research</a></li> + <li><a href="https://msk.genenetwork.org/">GN4MSK musculoskeletal</a></li> + <li><a href="https://genecup.org/">GeneCup: Mining PubMed for Gene Relationships using Custom Ontologies</a></li> + </ul> + </ul> + </section> + </div> + </div> + </div> + +{%endblock%} + +{% block js %} + + <script src="/static/new/javascript/dataset_select_menu_orig.js"></script> + + + <script type="text/javascript"> + +$(document).on('submit', '#searchform', function(event){ + + event.preventDefault() + let user_searches = []; + $(".search-query").each(function() { + this_search = $(this).val().trim(); + if (this_search != ""){ + user_searches.push(this_search); + } + }); + + for (i=0; i<user_searches.length; ++i){ + saveBeforeSubmit(user_searches[i]) + } + $( "#searchform" )[0].submit(); + +}); + </script> + + <script> + function pressed(e) { + // Has the enter key been pressed? + if ( (window.event ? event.keyCode : e.which) == 13) { + e.preventDefault(); + // If enter key has been pressed and the search fields are non-empty + // manually submit the <form> + + if( event.target.value.trim() != "" ) { + saveBeforeSubmit(event.target.value.trim()) + $("#searchform")[0].submit(); + + } + + } + } + + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/info_page.html b/gn2/wqflask/templates/info_page.html new file mode 100644 index 00000000..91d34573 --- /dev/null +++ b/gn2/wqflask/templates/info_page.html @@ -0,0 +1,92 @@ +{% extends "base.html" %}
+{% block title %}Policies{% endblock %}
+{% block content %}
+
+<h1 id="parent-fieldname-title">Data Set Group: {{ info.dataset_name }}
+<!--<a href="/infoshare/manager/member-studies-edit.html?DatasetId=%s"><img src="/images/modify.gif" alt="modify this page" border="0" valign="middle"></a>-->
+<span style="color:red;">{{ info.info_page_name }}</span>
+</h1>
+<table border="0" width="100%">
+<tr>
+<td valign="top" width="50%">
+<table name="info_table" cellSpacing=0 cellPadding=5 width=100% border=0>
+ <tr><td><b>Data Set:</b> {{ info.info_file_title }} <!--<a href="/infoshare/manager/member-infofile-edit.html?GN_AccesionId=%s"><img src="/images/modify.gif" alt="modify this page" border="0" valign="middle"></a>--></td></tr>
+ <tr><td><b>GN Accession:</b> GN{{ gn_accession_id }}</td></tr>
+ <tr><td><b>GEO Series:</b> <a href="http://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc={{ info.geo_series }}" target="_blank">{{ info.geo_series }}</a></td></tr>
+ <tr><td><b>Title:</b> {{ info.publication_title }}</td></tr>
+ <tr><td><b>Organism:</b> <a href="http://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?mode=Info&id={{ info.taxonomy_id }}" target="_blank">{{ info.menu_name }}</a></td></tr>
+ <tr><td><b>Group:</b> {{ info.group_name }}</td></tr>
+ <tr><td><b>Tissue:</b> {{ info.tissue_name }}</td></tr>
+ <tr><td><b>Dataset Status:</b> {{ info.dataset_status_name }}</td></tr>
+ <tr><td><b>Platforms:</b> <a href="http://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc={{ info.geo_platform }}" target="_blank">{{ info.gene_chip_name }}</a></td></tr>
+ <tr><td><b>Normalization:</b> {{ info.avg_method_name }}</td></tr>
+</table>
+</td>
+<td valign="top" width="50%">
+<table border="0" width="100%">
+ <tr>
+ <td><b>Contact Information</b></td>
+ </tr>
+ <tr>
+ <td>
+ {{ info.investigator_first_name }} {{ info.inveestigator_last_name }}<br>
+ {{ info.organization_name }} <br>
+ {{ info.investigator_address }}<br>
+ {{ info.investigator_city }}, {{ info.investigator_state }} {{ info.investigator_zipcode }} {{ info.investigator_country }}<br>
+ Tel. {{ info.investigator_phone }}<br>
+ {{ info.investigator_email }}<br>
+ <a href="{{ info.investigator_url }}" target="_blank">Website</a>
+ </td>
+ </tr>
+
+<tr>
+ <td><b>Download datasets and supplementary data files</b></td>
+</tr>
+<tr>
+ <td>
+ <ul style="line-height: 160%">
+ {% for file in filelist %}
+ <li><a href="https://files.genenetwork.org/current/GN{{ gn_accession_id }}/{{ file[0] }}">{{ file[0] }} ({{ file[2] }})</a></li>
+ {% endfor %}
+ </ul>
+ </td>
+</tr>
+
+<tr><td>
+</td></tr>
+
+</table>
+</td>
+</tr>
+</table>
+<HR>
+<p>
+<table name="info_table" width="100%" border="0" cellpadding="5" cellspacing="0">
+<tr><td><span style="font-size:115%%;font-weight:bold;">Specifics of this Data Set:</span></td></tr>
+ <tr><td> {{ info.specifics|safe }}<br><br></td></tr>
+<tr><td><span style="font-size:115%%;font-weight:bold;">Summary:</span></td></tr>
+ <tr><td> {{ info.dataset_summary|safe }}<br><br></td></tr>
+<tr><td><span style="font-size:115%; font-weight:bold;">About the cases used to generate this set of data:</span></td></tr>
+ <tr><td> {{ info.about_cases|safe }}<br><br></td></tr>
+<tr><td><span style="font-size:115%; font-weight:bold;">About the tissue used to generate this set of data:</span></td></tr>
+ <tr><td> {{ info.about_tissue|safe }}<br><br></td></tr>
+ <tr><td><span style="font-size:115%; font-weight:bold;">About the array platform:</span></td></tr>
+ <tr><td> {{ info.about_platform|safe }}<br><br></td></tr>
+<tr><td><span style="font-size:115%; font-weight:bold;">About data values and data processing:</span></td></tr>
+ <tr><td> {{ info.about_data_processing|safe }}<br><br></td></tr>
+<tr><td><span style="font-size:115%; font-weight:bold;">Notes:</span></td></tr>
+ <tr><td> {{ info.notes|safe }}<br><br></td></tr>
+<tr><td><span style="font-size:115%; font-weight:bold;">Experiment Type:</span></td></tr>
+ <tr><td> {{ info.experiment_design|safe }}<br><br></td></tr>
+<tr><td><span style="font-size:115%; font-weight:bold;">Contributor:</span></td></tr>
+ <tr><td> {{ info.contributors|safe }}<br><br></td></tr>
+<tr><td><span style="font-size:115%; font-weight:bold;">Citation:</span></td></tr>
+ <tr><td> {{ info.citation|safe }}<br><br></td></tr>
+<tr><td><span style="font-size:115%; font-weight:bold;">Data source acknowledgment:</span></td></tr>
+ <tr><td> {{ info.acknowledgement|safe }}<br><br></td></tr>
+<tr><td><span style="font-size:115%; font-weight:bold;">Study Id:</span></td></tr>
+ <tr><td> {{ info.dataset_id }}<br><br></td></tr>
+</table>
+</p>
+
+{% endblock %}
diff --git a/gn2/wqflask/templates/jobs/debug.html b/gn2/wqflask/templates/jobs/debug.html new file mode 100644 index 00000000..828ab1cc --- /dev/null +++ b/gn2/wqflask/templates/jobs/debug.html @@ -0,0 +1,42 @@ +{%extends "base.html"%} +{%block title%}Debug Job{% endblock%} +{%block css%} +{%endblock%} + +{%block content%} +<h1>Debug Job</h1> + +The following show details for job "{{job_id}}" to assist in debugging. + +<h2>Metadata</h2> + +<ul> + <li><strong>Job ID:</strong> {{job_id}}</li> + <li><strong>Command:</strong> <code>{{command}}</code></li> + <li><strong>Received:</strong> {{request_received_time}}</li> + <li><strong>Return Code:</strong> {{return_code}}</li> + <li><strong>Completion Status:</strong> {{completion_status}}</li> + <li><strong>Status:</strong> {{status}}</li> +</ul> + +<h2>STDERR</h2> + +<div style="background-color: black; color: red;"> + {%for line in stderr:%} + <p>{{line}}</p> + {%endfor%} +</div> + +<h2>STDOUT</h2> + +<div style="background-color: black; color: green;"> + {%for line in stdout:%} + <p>{{line}}</p> + {%endfor%} +</div> + + +{%endblock%} + +{%block js%} +{%endblock%} diff --git a/gn2/wqflask/templates/jobs/no-such-job.html b/gn2/wqflask/templates/jobs/no-such-job.html new file mode 100644 index 00000000..6fe7d014 --- /dev/null +++ b/gn2/wqflask/templates/jobs/no-such-job.html @@ -0,0 +1,13 @@ +{%extends "base.html"%} +{%block title%}No Such Job{% endblock%} +{%block css%} +{%endblock%} + +{%block content%} +<h1>No Such Job</h1> + +<p>The job with id <strong>{{job_id | string}}</strong> does not exist</p> +{%endblock%} + +{%block js%} +{%endblock%} diff --git a/gn2/wqflask/templates/jupyter_notebooks.html b/gn2/wqflask/templates/jupyter_notebooks.html new file mode 100644 index 00000000..afc95a15 --- /dev/null +++ b/gn2/wqflask/templates/jupyter_notebooks.html @@ -0,0 +1,28 @@ +{%extends "base.html"%} + +{%block title%} +Jupyter Notebooks +{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" href="/static/new/css/jupyter_notebooks.css" /> +{%endblock%} + +{%block content%} + +<div class="container"> + <h1>Current Notebooks</h1> + + {%for item in links:%} + <div class="jupyter-links"> + <a href="{{item.get('main_url')}}" + title="Access running notebook for '{{item.get('notebook_name')}}' on GeneNetwork" + class="main-link">{{item.get("notebook_name")}}</a> + <a href="{{item.get('src_link_url')}}" + title="Link to the notebook repository for {{item.get('notebook_name', '_')}}" + class="src-link">View Source</a> + </div> + {%endfor%} +</div> + +{%endblock%} diff --git a/gn2/wqflask/templates/links.html b/gn2/wqflask/templates/links.html new file mode 100644 index 00000000..6e91adae --- /dev/null +++ b/gn2/wqflask/templates/links.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% block title %}Links{% endblock %} + +{% block css %} +<link rel="stylesheet" type="text/css" href="/static/new/css/markdown.css" /> +{% endblock %} + +{% block content %} + +<div class="github-btn-container"> + <div class="github-btn "> + <a href="https://github.com/genenetwork/gn-docs/blob/master/general/links/links.md"> + Edit Text + <img src="/static/images/edit.png"> + </a> + </div> +</div> + +<div id="markdown" class="container"> + {{ rendered_markdown|safe }} + +</div> +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/list_case_attribute_diffs.html b/gn2/wqflask/templates/list_case_attribute_diffs.html new file mode 100644 index 00000000..f5c7482f --- /dev/null +++ b/gn2/wqflask/templates/list_case_attribute_diffs.html @@ -0,0 +1,59 @@ +{%extends "base.html"%} +{%block title%}List Case Attribute Diffs{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" + href="/css/DataTables/css/jquery.dataTables.css" /> +<link rel="stylesheet" type="text/css" + href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + +<style> + .table-fixed-head {overflow-y: auto; height: 32em;} + .table-fixed-head thead th {position: sticky; top: 0;} +</style> +{%endblock%} + +{%block content%} +<div class="container"> + <h1>List Diffs</h1> + + {{flash_me()}} + + <table class="table-hover table-striped cell-border dataTable no-footer"> + <thead> + <tr> + <th>Edit on</th> + <th>Filename</th> + </tr> + </thead> + + <tbody> + {%for diff in diffs%} + <tr> + <td>{{diff.time_stamp}}</td> + <td> + <a href="{{url_for('view_diff', inbredset_id=diff.json_diff_data.inbredset_id, diff_id=diff.id)}}" + title="View the diff"> + {{diff.filename}} + </a> + </td> + </tr> + {%else%} + <tr> + <td colspan="5" class="text-info" style="text-align: center;line-height: 2em;"> + <span class="glyphicon glyphicon-exclamation-sign"> + </span> + There are no diffs pending review + </td> + </tr> + {%endfor%} + </tbody> + </table> +{%endblock%} + +{%block js%} +<script language="javascript" + type="text/javascript" + src="{{url_for('js', filename='DataTables/js/jquery.js')}}"></script> +{%endblock%} diff --git a/gn2/wqflask/templates/list_case_attribute_diffs_error.html b/gn2/wqflask/templates/list_case_attribute_diffs_error.html new file mode 100644 index 00000000..6ca70984 --- /dev/null +++ b/gn2/wqflask/templates/list_case_attribute_diffs_error.html @@ -0,0 +1,37 @@ +{%extends "base.html"%} +{%block title%}List Case Attribute Diffs{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" + href="/css/DataTables/css/jquery.dataTables.css" /> +<link rel="stylesheet" type="text/css" + href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + +<style> + .table-fixed-head {overflow-y: auto; height: 32em;} + .table-fixed-head thead th {position: sticky; top: 0;} +</style> +{%endblock%} + +{%block content%} +<div class="container"> + <h1>List Diffs</h1> + + {{flash_me()}} + + {%set the_error = error.json()%} + + <p class="text-danger"> + <span class="glyphicon glyphicon-exclamation-sign"> + </span> + <strong>{{error.status_code}}: {{the_error["error"]}}</strong> + {{the_error.error_description}} + </p> +{%endblock%} + +{%block js%} +<script language="javascript" + type="text/javascript" + src="{{url_for('js', filename='DataTables/js/jquery.js')}}"></script> +{%endblock%} diff --git a/gn2/wqflask/templates/loading.html b/gn2/wqflask/templates/loading.html new file mode 100644 index 00000000..a6d9ae5e --- /dev/null +++ b/gn2/wqflask/templates/loading.html @@ -0,0 +1,133 @@ +<title>Loading {{ start_vars.tool_used }} Results</title> +<link REL="stylesheet" TYPE="text/css" href="{{ url_for('css', filename='bootstrap/css/bootstrap.css') }}" /> +<link REL="stylesheet" TYPE="text/css" href="/static/new/css/bootstrap-custom.css" /> +<form method="post" action="" name="loading_form" id="loading_form" class="form-horizontal"> + {% for key, value in start_vars.items() %} + <input type="hidden" name="{{ key }}" value="{{ value }}"> + {% endfor %} + <div class="container"> + <div> + <div style="min-height: 80vh; display: flex; align-items: center; text-align: left;"> + <div style="margin-bottom: 5px; left: 50%; margin-right: -50%; top: 50%; transform: translate(-50%, -50%); position: absolute;"> + {% if start_vars.tool_used == "Mapping" %} + <h1>Computing the Maps</h1> + <br> + <b>Time Elapsed:</b> <span class="timer"></span> + <br> + <b>Trait Metadata</b> + <br> + species = <b><i>{{ start_vars.species[0] | upper }}{{ start_vars.species[1:] }}</i></b> + <br> + group = <b><i>{{ start_vars.group[0] | upper }}{{ start_vars.group[1:] }}</i></b> + <br> + trait identifier = <b><i>{{ start_vars.trait_name }}</i></b> + <br> + n of sample = <b><i>{{ start_vars.n_samples }}</i></b> + {% if start_vars.transform != "" %} + <br> + transformation = <b><i>{{ start_vars.transform }}</i></b> + {% endif %} + <br> + hash of sample values = <b><i>{{ start_vars.vals_hash }}</i></b> + <br><br> + <b>Mapping Metadata</b> + <br> + mapping method = <b><i>{% if start_vars.method == "gemma" %}GEMMA {% if start_vars.use_loco == "True" %}using LOCO {% endif %}{% else %}{{ start_vars.method }}{% endif %}</i></b> + {% if start_vars.maf != "" and start_vars.method != "reaper" %} + <br> + minor allele frequency lower limit = <b><i>{{ start_vars.maf }}</i></b> + {% endif %} + <br> + {% if start_vars.covariates != "" and start_vars.method != "reaper" %} + {% set covariate_list = start_vars.covariates.split(",") %} + cofactors = <b><i>{% for covariate in covariate_list %}{% set this_covariate = covariate.split(":")[0] %}{{ this_covariate }}{% if not loop.last %}, {% endif %}{% endfor %}</i></b> + {% else %} + cofactors = <b><i>None</i></b> + {% endif %} + {% if start_vars.control_marker != "" and start_vars.do_control == "true" and start_vars.method != "gemma" %} + <br> + marker covariate = <b><i>{{ start_vars.control_marker }}</i></b> + {% endif %} + <br> + {% if start_vars.genofile != "" %} + {% set genofile_desc = start_vars.genofile.split(":")[1] %} + genotype file = <b><i>{{ genofile_desc }}</i></b> + {% else %} + genotype file = <b><i>{{ start_vars.group[0] | upper }}{{ start_vars.group[1:] }}.geno</i></b> + {% endif %} + {% if start_vars.num_perm | int > 0 and start_vars.method != "gemma" %} + <br> + n of permutations = <b><i>{{ start_vars.num_perm }}</i></b> + {% endif %} + {% if num_bootstrap in start_vars %} + {% if start_vars.num_bootstrap | int > 0 and start_vars.method == "reaper" %} + <br> + n of bootstrap = <b><i>{{ start_vars.num_bootstrap }}</i></b> + {% endif %} + {% endif %} + {% else %} + <h1> {{ start_vars.tool_used }} Computation in progress ...</h1> + {% endif %} + <br><br> + <div style="text-align: center;"> + <img align="center" src="/static/gif/waitAnima2.gif"> + </div> + {% if start_vars.vals_diff|length != 0 and start_vars.transform == "" %} + <br><br> + <button id="show_full_diff">Show Full Diff</button> + <br> + <div id="diff_table_container" style="display: none; height:200px; overflow:auto;"> + <table class="table table-hover"> + <thead> + <th>Sample</th> + <th>New Value</th> + <th>Old Value</th> + </thead> + <tbody> + {% for sample in start_vars.vals_diff %} + <tr> + <td>{{ sample }}</td> + <td>{{ start_vars.vals_diff[sample].new_val }}</td> + <td>{{ start_vars.vals_diff[sample].old_val }}</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + {% endif %} + </div> + </div> + </div> + </div> +</form> +<script src="{{ url_for('js', filename='jquery/jquery.min.js') }}" type="text/javascript"></script> +<script src="{{ url_for('js', filename='bootstrap/js/bootstrap.min.js') }}" type="text/javascript"></script> +<script type="text/javascript"> +$('#show_full_diff').click(function() { + if ($('#diff_table_container').is(':visible')){ + $('#diff_table_container').hide(); + } else { + $('#diff_table_container').show(); + } +}) + +var start = new Date; + +setInterval(function() { + minutes = Math.floor((new Date - start) / 1000 / 60) + seconds = Math.round(((new Date - start) / 1000) % 60) + if (seconds < 10 && minutes >= 1){ + seconds_text = "0" + seconds.toString() + } else { + seconds_text = seconds.toString() + } + if (minutes < 1) { + $('.timer').text(seconds_text + " seconds"); + } else { + $('.timer').text(minutes.toString() + ":" + seconds_text); + } +}, 100); + +$("#loading_form").attr("action", "{{ start_vars.form_url }}"); +setTimeout(function(){ $("#loading_form").submit()}, 350); +</script> diff --git a/gn2/wqflask/templates/loading_corrs.html b/gn2/wqflask/templates/loading_corrs.html new file mode 100644 index 00000000..8abd5464 --- /dev/null +++ b/gn2/wqflask/templates/loading_corrs.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> + + <head> + <title>Loading Correlation Results</title> + + <meta charset="utf-8" /> + <meta http-equiv="refresh" content="5"> + + <link rel="stylesheet" type="text/css" + href="{{url_for('css', filename='bootstrap/css/bootstrap.css')}}" /> + <link rel="stylesheet" type="text/css" + href="/static/new/css/bootstrap-custom.css" /> + </head> + + <body> + <div style="margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);"> + <h1> Correlation Computation in progress ...</h1> + <div style="text-align: center;"> + <img align="center" src="/static/gif/waitAnima2.gif"> + </div> + </div> + + <script src="{{ url_for('js', filename='jquery/jquery.min.js') }}" type="text/javascript"></script> + <script src="{{ url_for('js', filename='bootstrap/js/bootstrap.min.js') }}" type="text/javascript"></script> + </body> + +</html> diff --git a/gn2/wqflask/templates/mapping_error.html b/gn2/wqflask/templates/mapping_error.html new file mode 100644 index 00000000..8364af3c --- /dev/null +++ b/gn2/wqflask/templates/mapping_error.html @@ -0,0 +1,36 @@ +{%extends "base.html"%} +{%block titl%}Error{%endblock%} +{%block content%} +<!-- Start of body --> +{{ header("An error occurred during mapping") }} + +<div class="container"> + <h3> + {%if error:%} + <p> + The following error was raised<br /><br /> + <dl> + <dt>Error message</dt> + <dd>{{error.args[0]}}</dd> + <dt>Error Type</dt> + <dd>{{error_type}}</dd> + </dl> + </p> + <p> + Please contact Zach Sloan (zachary.a.sloan@gmail.com) or Arthur Centeno + (acenteno@gmail.com) about the error. + </p> + {%else:%} + <p>There is likely an issue with the genotype file associated with this group/RISet. Please contact Zach Sloan (zachary.a.sloan@gmail.com) or Arthur Centeno (acenteno@gmail.com) about the data set in question.</p> + </h3> + <br> + <h3> + <p>Try mapping using interval mapping instead; some genotype files with many columns of NAs have issues with GEMMA or R/qtl.</p> + {%endif%} + </h3> +</div> + + +<!-- End of body --> + +{%endblock%} diff --git a/gn2/wqflask/templates/mapping_results.html b/gn2/wqflask/templates/mapping_results.html new file mode 100644 index 00000000..0e084ba7 --- /dev/null +++ b/gn2/wqflask/templates/mapping_results.html @@ -0,0 +1,698 @@ +{% extends "base.html" %} +{% block title %}Mapping Results{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + + <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous"> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='purescript-genome-browser/css/purescript-genetics-browser.css') }}" /> + + <link rel="stylesheet" type="text/css" href="/static/new/css/marker_regression.css" /> + <link rel="stylesheet" type="text/css" href="static/new/css/show_trait.css" /> + +{% endblock %} +{% from "base_macro.html" import header %} +{% block content %} + <div class="container"> + <form method="post" target="_blank" action="/run_mapping" name="marker_regression" id="marker_regression_form"> + <input type="hidden" name="temp_uuid" value="{{ temp_uuid }}"> + {% if temp_trait is defined %} + <input type="hidden" name="temp_trait" value="{{ temp_trait }}"> + {% endif %} + <input type="hidden" name="inputs_hash" value="{{ hash_of_inputs }}"> + <input type="hidden" name="group" value="{{ dataset.group.name }}"> + <input type="hidden" name="species" value="{{ dataset.group.species }}"> + <input type="hidden" name="trait_id" value="{{ this_trait.name }}"> + <input type="hidden" name="dataset" value="{{ dataset.name }}"> + <input type="hidden" name="genofile" value="{{ genofile_string }}"> + <input type="hidden" name="geno_db_exists" value="{{ geno_db_exists }}"> + <input type="hidden" name="first_run" value="{{ first_run }}"> + {% if output_files is defined %} + <input type="hidden" name="output_files" value="{{ output_files }}"> + {% endif %} + {% if reaper_version is defined %} + <input type="hidden" name="reaper_version" value="{{ reaper_version }}"> + {% endif %} + <input type="hidden" name="results_path" value="{{ mapping_results_path }}"> + <input type="hidden" name="method" value="{{ mapping_method }}"> + <input type="hidden" name="sample_vals" value="{{ sample_vals }}"> + <input type="hidden" name="vals_hash" value="{{ vals_hash }}"> + <input type="hidden" name="n_samples" value="{{ n_samples }}"> + <input type="hidden" name="maf" value="{{ maf }}"> + <input type="hidden" name="use_loco" value="{{ use_loco }}"> + <input type="hidden" name="selected_chr" value="{{ selectedChr }}"> + <input type="hidden" name="mapping_scale" value="{{ plotScale }}"> + <input type="hidden" name="manhattan_plot" value="{{ manhattan_plot }}"> + {% if manhattan_plot == True %} + <input type="hidden" name="color_scheme" value="alternating"> + {% endif %} + <input type="hidden" name="num_perm" value="{{ nperm }}"> + <input type="hidden" name="perm_info" value=""> + {% if categorical_vars is defined %} + <input type="hidden" name="categorical_vars" value="{{ categorical_vars|join(',') }}"> + {% endif %} + {% if perm_strata is defined %} + <input type="hidden" name="perm_strata" value="True"> + {% endif %} + <input type="hidden" name="num_bootstrap" value="{{ nboot }}"> + <input type="hidden" name="do_control" value="{{ doControl }}"> + <input type="hidden" name="control_marker" value="{{ controlLocus }}"> + <input type="hidden" name="covariates" value="{{ covariates }}"> + <input type="hidden" name="mapmethod_rqtl_geno" value="{{ mapmethod_rqtl_geno }}"> + <input type="hidden" name="mapmodel_rqtl_geno" value="{{ mapmodel_rqtl_geno }}"> + <input type="hidden" name="pair_scan" value="{{ pair_scan }}"> + <input type="hidden" name="transform" value="{{ transform }}"> + <input type="hidden" name="tool_used" value="Mapping"> + <input type="hidden" name="wanted_inputs" value=""> + <input type="hidden" name="form_url" value="/run_mapping"> + + <div class="container" style="width: 1200px; float: left;"> + <div class="col-xs-4" style="word-wrap: normal;"> + <h2>Map Viewer: Whole Genome</h2><br> + <b>Population:</b> {{ dataset.group.species|capitalize }} {{ dataset.group.name }}<br> + <b>Database:</b> {{ dataset.fullname }}<br> + {% if dataset.type == "ProbeSet" %}<b>Trait ID:</b>{% else %}<b>Record ID:</b>{% endif %} <a href="/show_trait?trait_id={{ this_trait.name }}&dataset={{ dataset.name }}">{{ this_trait.display_name }}</a><br> + <b>Trait Hash: </b> {{ vals_hash }}<br> + {% if dataset.type == "ProbeSet" %} + <b>Gene Symbol:</b> <i>{{ this_trait.symbol }}</i><br> + <b>Location:</b> Chr {{ this_trait.chr }} @ {{ this_trait.mb }} Mb<br> + {% endif %} + {% if genofile_string != "" %} + <b>Genotypes:</b> {{ genofile_string.split(":")[1] }}<br> + {% endif %} + <b>Current Date/Time:</b> {{ current_datetime }}<br> + <br> + <button class="btn btn-default export_mapping_results"><span class="glyphicon glyphicon-download-alt"></span>Map Data</button> + <br><br> + <div class="input-group"> + <input type="text" class="form-control" name="mappingLink" size="75"></input> + <div class="input-group-btn"> + <button class="btn btn-default share-results" type="button">Copy and Share</button> + </div> + </div> + </div> + <div id="gn1_map_options" class="col-xs-6" style="outline: 3px double #AAAAAA; padding: 10px; margin: 10px;"> + <div class="col-xs-8" style="padding: 0px;"> + <table> + <tr> + <td><b>Chr: </b></td> + <td style="padding: 5px;"> + <select name="chromosomes" size="1"> + {% for chr in ChrList %} + <option value="{{ chr[1] }}" {% if (chr[1] + 1) == selectedChr %}selected{% endif %}>{{ chr[0] }}</option> + {% endfor %} + </select> + <button type="button" class="btn btn-primary" style="padding-bottom: 2px; padding-top: 2px;" onclick="javascript:remap();">Remap</button> + </td> + </tr> + <tr> + <td ><b>View: </b></td> + <td style="padding: 5px;"> + <input type="text" name="startMb" size="7" value="{% if startMb != -1 %}{{ startMb }}{% endif %}"> to <input type="text" name="endMb" size="7" value="{% if endMb != -1 %}{{ endMb }}{% endif %}"> + </td> + </tr> + <tr> + <td><b>Units: </b></td> + <td style="padding: 5px;"> + <label class="radio-inline"> + <input type="radio" name="LRSCheck" value="LRS" {% if LRS_LOD == "LRS" %}checked{% endif %}>LRS + </label> + <label class="radio-inline"> + <input type="radio" name="LRSCheck" value="{% if LRS_LOD == "-logP" %}-logP{% else %}LOD{% endif %}" {% if LRS_LOD == "LOD" or LRS_LOD == "-logP" %}checked{% endif %}>{% if LRS_LOD == "-logP" %}-logP{% else %}LOD{% endif %} + </label> + <a href="https://genenetwork.org/glossary.html#LOD" target="_blank"> + <sup style="color:#f00"> ?</sup> + </a> + </td> + </tr> + <tr> + <td></td> + <td style="padding: 5px;"> + <input type="text" name="lrsMax" value="{{ '%0.1f' | format(lrsMax|float) }}" size="3"> <span style="font-size: 12px;">units on the y-axis (0 for default)</span> + </td> + </tr> + <tr> + <td><b>Width: </b></td> + <td style="padding: 5px;"> + <input type="text" name="graphWidth" value="{% if graphWidth is defined %}{{ graphWidth }}{% else %}1600{% endif %}" size="5"><span style="font-size: 12px;"> pixels (minimum=900)</span> + </td> + </tr> + </table> + {% if manhattan_plot == True and selectedChr == -1 %} + <table style="margin-top: 10px;"> + <tr> + <td> + <b>Manhattan Plot Color Scheme: </b> + </td> + <td> + <select id="color_scheme"> + <option value="alternating" {% if color_scheme == "alternating" %}selected{% endif %}>Alternating</option> + <option value="varied" {% if color_scheme == "varied" %}selected{% endif %}>Varied by Chr</option> + <option value="single" {% if color_scheme == "single" %}selected{% endif %}>Single Color</option> + </select> + </td> + <td> + <input name="manhattan_single_color" type="hidden" id="point_color" value={% if manhattan_single_color %}{{ manhattan_single_color }}{% else %}"#D9D9D9"{% endif %}> + <button style="display: none; margin-left: 5px;" id="point_color_picker" class="jscolor {valueElement: 'point_color'}">Choose Color</button> + </td> + </tr> + </table> + {% endif %} + </div> + <div class="col-xs-4" style="padding: 0px;"> + {% if (mapping_method == "reaper" or mapping_method == "rqtl_geno") and nperm > 0 %} + <input type="checkbox" name="permCheck" class="checkbox" style="display: inline; margin-top: 0px;" {% if permChecked|upper == "ON" %}value="ON" checked{% endif %}> <span style="font-size: 12px;">Permutation Test + <a href="http://genenetwork.org/glossary.html#Permutation" target="_blank"> + <sup style="color:#f00"> ?</sup> + </a> + <br> + {% endif %} + {% if mapping_method == "reaper" and nboot > 0 %} + <input type="checkbox" name="bootCheck" class="checkbox" style="display: inline; margin-top: 0px;" {% if bootChecked|upper == "ON" %}value="ON" checked{% endif %}> <span style="font-size: 12px;">Bootstrap Test + <a href="http://genenetwork.org/glossary.html#bootstrap" target="_blank"> + <sup style="color:#f00"> ?</sup> + </a> + <br> + {% endif %} + {% if mapping_method == "reaper" %} + <input type="checkbox" name="additiveCheck" class="checkbox" style="display: inline; margin-top: 0px;" {% if additiveChecked|upper == "ON" %}value="ON" checked{% endif %}> <span style="font-size: 12px;">Allele Effects + <a href="http://genenetwork.org/glossary.html#additive" target="_blank"> + <sup style="color:#f00"> ?</sup> + </a> + <br> + {% endif %} + <input type="checkbox" name="showSNP" class="checkbox" style="display: inline; margin-top: 0px;" {% if SNPChecked|upper == "ON" %}value="ON" checked{% endif %}> <span style="font-size: 12px;">SNP Track </span> + <a href="http://genenetwork.org/glossary.html#snpSeismograph" target="_blank"> + <sup style="color:#f00"> ?</sup> + </a> + <span style="color:red;">*</span> + <br> + <input type="checkbox" name="showGenes" class="checkbox" style="display: inline; margin-top: 0px;" {% if geneChecked|upper == "ON" %}value="ON" checked{% endif %}> <span style="font-size: 12px;">Gene Track </span> <span style="color:red;">*</span><br> + {% if plotScale != "morgan" %} + <input type="checkbox" name="haplotypeAnalystCheck" class="checkbox" style="display: inline; margin-top: 0px;" {% if haplotypeAnalystChecked|upper == "ON" %}value="ON" checked{% endif %}> <span style="font-size: 12px;">Haplotype Tracks </span> <span style="color:red;">*</span><br> + {% endif %} + <input type="checkbox" name="viewLegend" class="checkbox" style="display: inline; margin-top: 0px;" {% if legendChecked|upper == "ON" %}value="ON" checked{% endif %}> <span style="font-size: 12px;">Legend </span><br> + <input type="checkbox" name="showHomology" class="checkbox" style="display: inline; margin-top: 0px;" {% if homologyChecked|upper == "ON" %}value="ON" checked{% endif %}> <span style="font-size: 12px;">Human Chromosomes <a href="http://hgdownload.cse.ucsc.edu/goldenpath/hg38/liftOver/"><sup style="color:#f00" title="Human Syntenic Chromosomes taken from HG38 (Human Genome 38)"> ?</sup></a></span> + <br> + <span style="color:red;">*</span> <span style="font-size: 12px;">only apply to single chromosome physical mapping</span> + </div> + </div> + </div> + + <div class="tabbable" style="width: 100%; overflow-x: auto; margin: 10px; float: left;"> + <ul class="nav nav-tabs"> + <li id="gn1_map_tab"> + <a href="#gn1_map" data-toggle="tab" aria-expanded="true">GN1 Map</a> + </li> + {% if mapping_method == "gemma" or mapping_method == "reaper" %} + <li id="browser_tab"> + <a href="#browser_holder" data-toggle="tab" aria-expanded="true">Genome Browser</a> + </li> + {% endif %} + </ul> + <div class="tab-content"> + <div class="tab-pane active" id="gn1_map"> + <div class="qtlcharts"> + {{ gifmap|safe }} + <img src="/generated/{{ filename }}.png" usemap="#WebQTLImageMap"> + {% if additiveChecked|upper == "ON" %} + <br> + <span style="white-space: nowrap;">A positive additive coefficient (green line) indicates that {{ dataset.group.parlist[1] }} alleles increase trait values. In contrast, a negative additive coefficient (orange line) indicates that {{ dataset.group.parlist[0] }} alleles increase trait values.</span> + {% endif %} + </div> + </div> + {% if mapping_method == "gemma" or mapping_method == "reaper" %} + <div class="tab-pane" id="browser_holder" style="height: 600px;"> + <div id="browser" style="margin-right: 20px; width: 90%;"> + <div id="controls"> + <button id="scrollLeft" type="button" > + <i class="fas fa-arrow-left"></i> + </button> + <button id="scrollRight" type="button" > + <i class="fas fa-arrow-right"></i> + </button> + <button id="zoomOut" type="button" > + <i class="fas fa-search-minus"></i> + </button> + <button id="zoomIn" type="button" > + <i class="fas fa-search-plus"></i> + </button> + <button id="reset" type="button" >Reset</button> + </div> + <div id="infoBox"></div> + </div> + </div> + {% endif %} + </div> + </div> + <div style="float: left;"> + {% if nperm > 0 and permChecked == "ON" %} + <br><br> + <div id="perm_histogram" class="barchart"></div> + <br><br> + Total of {{ nperm }} permutations <a href="javascript:;" id="download_perm" target="_blank" >Download Permutation Results</a> + <br> + {% endif %} + </div> + + </form> + {% if selectedChr == -1 %} + <div class="container" style="padding-left: 30px; margin-top: 50px; position: relative; float: left;"> + <h2>Mapping Statistics</h2> + <br /> + <button class="btn btn-default" id="select_all"><span class="glyphicon glyphicon-ok"></span> Select All</button> + <button class="btn btn-default" id="deselect_all"><span class="glyphicon glyphicon-remove"></span> Deselect All</button> + <button class="btn btn-default" id="invert"><span class="glyphicon glyphicon-resize-vertical"></span> Invert</button> + {% if geno_db_exists == "True" %}<button class="btn btn-success" id="add" disabled><span class="glyphicon glyphicon-plus-sign"></span> Add</button>{% endif %} + <button class="btn btn-default export_mapping_results" ><span class="glyphicon glyphicon-download"></span> Download</span></button> + <br /> + <br /> + <div id="trait_table_container" style="width:{% if 'additive' in trimmed_markers[0] %}600{% else %}550{% endif %}px;"> + <table class="table-hover table-striped cell-border no-footer" id='trait_table' style="float: left;"> + <tbody> + <td colspan="100%" align="center"><br><b><font size="15">Loading...</font></b><br></td> + </tbody> + </table> + </div> + </div> + {% elif selectedChr != -1 and plotScale =="physic" and (dataset.group.species == 'mouse' or dataset.group.species == 'rat') %} + <div style="width: 100%;"> + <h2>Interval Analyst</h2> + <div id="table_container"> + <table id="trait_table" class="table-hover table-striped cell-border dataTable" style="float: left; width:100%;"> + <thead> + <tr> + {% for header in gene_table_header %} + <th>{{ header|safe }}</th> + {% endfor %} + </tr> + </thead> + <tbody> + {% for row in gene_table_body %} + <tr> + {% for n in range(row|length) %} + {% if n == 0 %} + <td align="center" style="padding: 1px 0px 1px 0px;">{{ row[n]|safe }}</td> + {% else %} + <td>{{ row[n]|safe }}</td> + {% endif %} + {% endfor %} + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + {% endif %} + </div> + + <!-- End of body --> + +{% endblock %} + +{% block js %} + + <script type="text/javascript" src="{{ url_for('js', filename='d3js/d3.min.js') }}"></script> + <script type="text/javascript" src="{{ url_for('js', filename='js_alt/md5.min.js') }}"></script> + <script type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> + <script type="text/javascript" src="{{ url_for('js', filename='underscore-string/underscore.string.min.js') }}"></script> + <script type="text/javascript" src="{{ url_for('js', filename='d3-tip/d3-tip.js') }}"></script> + <script type="text/javascript" src="{{ url_for('js', filename='plotly/plotly.min.js') }}"></script> + {% if manhattan_plot == True and selectedChr == -1 %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='jscolor/jscolor.js') }}"></script> + + {% endif %} + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/buttons/js/dataTables.buttons.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script> + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/scientific.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/natural.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='purescript-genome-browser/js/purescript-genetics-browser.js') }}"></script> + + <script> + var js_data = {{ js_data | safe }}; + var markersJson = {{ trimmed_markers|safe }}; + </script> + + <script language="javascript" type="text/javascript" src="/static/new/javascript/search_results.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/table_functions.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/create_datatable.js"></script> + {% if mapping_method == "gemma" or mapping_method == "reaper" %} + <script language="javascript" type="text/javascript" src="/static/new/javascript/init_genome_browser.js"></script> + {% endif %} + + <script type="text/javascript" charset="utf-8"> + $(document).ready( function () { + {% if selectedChr == -1 %} + tableId = "trait_table" + + columnDefs = [ + { + 'data': null, + 'width': "5px", + 'orderDataType': "dom-checkbox", + 'targets': 0, + 'render': function(data, type, row, meta) { + return '<input type="checkbox" name="selectCheck" class="checkbox trait_checkbox" value="'+ data.hmac + '">' + } + }, + { + 'title': "Row", + 'type': "natural", + 'width': "35px", + 'searchable': false, + 'orderable': false, + 'targets': 1, + 'render': function(data, type, row, meta) { + return meta.row + } + }, + { + 'title': "Marker", + 'type': "natural", + 'width': "20%", + 'targets': 2, + {% if geno_db_exists == "True" %} + 'data': null, + 'render': function(data, type, row, meta) { + return '<a target="_blank" href="/show_trait?trait_id=' + data.name + '&dataset={{ dataset.group.name }}Geno">' + data.name + '</a>' + } + {% else %} + 'data': "name" + {% endif %} + }, + { + 'title': "<div style='text-align: right;'>{{ LRS_LOD }}</div>", + 'type': "natural", + 'width': "60px", + 'targets': 3, + 'orderSequence': [ "desc", "asc"], + 'data': null, + 'render': function(data, type, row, meta) { + {% if (LRS_LOD == "LOD") or (LRS_LOD == "-logP") %} + if ('lod_score' in data){ + return String(parseFloat(data.lod_score).toFixed(2)) + } else { + return String((parseFloat(data.lrs_value) / 4.61).toFixed(2)) + } + {% else %} + if ('lod_score' in data){ + return String((parseFloat(data.lod_score) * 4.61).toFixed(2)) + } else { + return String(parseFloat(data.lrs_value).toFixed(2)) + } + {% endif %} + } + }, + { + 'title': "<div style='text-align: right;'>Position ({% if plotScale == 'physic' %}Mb{% else %}cM{% endif %})</div>", + 'type': 'natural', + 'targets': 4, + 'data': "display_pos" + }, + {% if 'additive' in trimmed_markers[0] %} + { + 'title': "<div style='text-align: right;'>Add Eff</div>", + 'type': "natural", + 'targets': 5, + 'data': null, + 'render': function(data, type, row, meta) { + {% if geno_db_exists == "True" %} + return '<a target"_blank" href="corr_scatter_plot?method=pearson&dataset_1={{ dataset.group.name }}Geno&dataset_2={{ dataset.name }}&trait_1=' + data.name + '&trait_2={{ this_trait.name }}&cached_trait=trait_2&dataid={{ dataid }}">' + String(parseFloat(data.additive).toFixed(3)) + '</a>' + {% else %} + return String(parseFloat(data.additive).toFixed(3)) + {% endif %} + } + }, + {% endif %} + {% if ('dominance' in trimmed_markers[0]) and (dataset.group.genetic_type != "riset") %} + { + 'title': "<div style='text-align: right;'>Add Eff</div>", + 'type': "natural", + 'targets': {% if ('additive' in trimmed_markers[0]) %}6{% else %}5{% endif %}, + 'data': null, + 'render': function(data, type, row, meta) { + return String(parseFloat(data.dominance).toFixed(2)) + } + } + {% endif %} + ] + + tableSettings = { + "createdRow": function ( row, data, index ) { + $('td', row).eq(0).attr("style", "text-align: center; padding: 0px 10px 2px 10px;"); + $('td', row).eq(1).attr("align", "right"); + $('td', row).eq(1).attr('data-export', index+1); + $('td', row).eq(2).attr('data-export', $('td', row).eq(2).text()); + $('td', row).eq(3).attr("align", "right"); + $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); + $('td', row).eq(4).attr("align", "right"); + $('td', row).eq(4).attr('data-export', $('td', row).eq(4).text()); + {% if 'additive' in trimmed_markers[0] %} + $('td', row).eq(5).attr("align", "right"); + $('td', row).eq(5).attr('data-export', $('td', row).eq(5).text()); + {% endif %} + {% if 'dominance' in trimmed_markers[0] %} + $('td', row).eq(6).attr("align", "right"); + $('td', row).eq(6).attr('data-export', $('td', row).eq(6).text()); + {% endif %} + }, + "language": { + "info": "Showing from _START_ to _END_ of " + js_data.total_markers + " records", + }, + "order": [[1, "asc" ]], + "scrollY": "1000px", + "scroller": true + } + + create_table(tableId, markersJson, columnDefs, tableSettings) + + {% elif selectedChr != -1 and plotScale =="physic" and (dataset.group.species == 'mouse' or dataset.group.species == 'rat') %} + $('#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") && event.target.tagName.toLowerCase() !== 'a'){ + $(this).removeClass("selected") + } else if (event.target.tagName.toLowerCase() !== 'a') { + $(this).addClass("selected") + } + }); + }, + "createdRow": function ( row, data, index ) { + $('td', row).eq(1).attr("align", "right"); + $('td', row).eq(3).attr("align", "right"); + $('td', row).eq(4).attr("align", "right"); + $('td', row).eq(5).attr("align", "right"); + $('td', row).eq(6).attr("align", "right"); + $('td', row).eq(7).attr("align", "right"); + $('td', row).eq(8).attr("align", "center"); + $('td', row).eq(9).attr("align", "right"); + }, + "columns": [ + { "orderDataType": "dom-checkbox" }, + { "type": "natural"}, + { "type": "natural" , "orderDataType": "dom-inner-text" }, + { "type": "natural" , "orderDataType": "dom-inner-text" }, + { "type": "natural" , "orderDataType": "dom-inner-text" }, + { "type": "natural" , "orderDataType": "dom-inner-text" }, + { "type": "natural" }, + { "type": "natural-minus-na" }, + { "type": "natural-minus-na" }, + { "type": "natural-minus-na" , "orderDataType": "dom-inner-text" }, + { "type": "natural" } + ], + "columnDefs": [ { + "targets": 0, + "sortable": false + }], + "order": [[3, "asc" ]], + "sDom": "RZtir", + "iDisplayLength": -1, + "autoWidth": false, + "deferRender": true, + "bSortClasses": false, + "scrollCollapse": false, + "paging": false + } ); + {% endif %} + + $('#vector_map_tab').click(function(){ + $('div#gn1_map_options').hide(); + }); + + $('#gn1_map_tab').click(function(){ + $('div#gn1_map_options').show(); + }); + + }); + + var mapping_input_list = ['temp_uuid', 'trait_id', 'dataset', 'tool_used', 'form_url', 'method', 'transform', 'trimmed_markers', 'selected_chr', 'chromosomes', 'mapping_scale', 'sample_vals', + 'score_type', 'suggestive', 'significant', 'num_perm', 'permCheck', 'perm_output', 'perm_strata', 'categorical_vars', 'num_bootstrap', 'bootCheck', 'bootstrap_results', + 'LRSCheck', 'covariates', 'maf', 'use_loco', 'manhattan_plot', 'color_scheme', 'manhattan_single_color', 'control_marker', 'do_control', 'genofile', + 'pair_scan', 'startMb', 'endMb', 'graphWidth', 'lrsMax', 'additiveCheck', 'showSNP', 'showHomology', 'showGenes', 'viewLegend', 'haplotypeAnalystCheck', + 'mapmethod_rqtl_geno', 'mapmodel_rqtl_geno', 'temp_trait', 'group', 'species', 'reaper_version', 'primary_samples', 'n_samples'] + + $('input[name=wanted_inputs]').val(mapping_input_list.join(",")); + + chrView = function(this_chr, chr_mb_list) { + $('input[name=selected_chr]').val(this_chr) + $('input[name=chr_mb_list]').val(chr_mb_list) + + $('#marker_regression_form').attr('action', '/loading'); + $('#marker_regression_form').submit(); + }; + + rangeView = function(this_chr, start_mb, end_mb) { + $('input[name=selected_chr]').val(this_chr) + $('input[name=startMb]').val(start_mb) + $('input[name=endMb]').val(end_mb) + //$('input[name=mb_range]').val(start_mb + "," + end_mb) + + $('#marker_regression_form').attr('action', '/loading'); + $('#marker_regression_form').submit(); + }; + + remap = function() { + $('input[name=selected_chr]').val($('select[name=chromosomes]').val()); + $('input[name=color_scheme]').val($('select#color_scheme').val()); + $('#marker_regression_form').attr('action', '/loading'); + return $('#marker_regression_form').submit(); + }; + + {% if manhattan_plot == True and selectedChr == -1 %} + $('#color_scheme').change(function(){ + if ($(this).val() == "single"){ + $('#point_color_picker').show(); + } else { + $('#point_color_picker').hide(); + } + }); + {% endif %} + + {% if mapping_method != "gemma" and mapping_method != "plink" and nperm > 0 and permChecked == "ON" %} + $('#download_perm').click(function(){ + perm_info_dict = { + perm_data: js_data.perm_results, + num_perm: "{{ nperm }}", + trait_name: "{{ this_trait.display_name }}", + trait_description: `{{ this_trait.description_display }}`, + cofactors: "{{ covariates }}", + n_samples: {{ n_samples }}, + n_genotypes: {{ qtl_results|length }}, + {% if genofile_string is defined %} + genofile: "{{ genofile_string }}", + {% else %} + genofile: "", + {% endif %} + units_linkage: "{{ LRS_LOD }}", + strat_cofactors: js_data.categorical_vars + } + json_perm_data = JSON.stringify(perm_info_dict); + + $('input[name=perm_info]').val(json_perm_data); + $('#marker_regression_form').attr('action', '/export_perm_data'); + return $('#marker_regression_form').submit(); + }); + + modebar_options = { + modeBarButtonsToAdd:[{ + name: 'Export as SVG', + icon: Plotly.Icons.disk, + click: function(gd) { + Plotly.downloadImage(gd, {format: 'svg'}) + } + }], + modeBarButtonsToRemove:['toImage', 'sendDataToCloud', 'hoverClosest', 'hoverCompare', 'hoverClosestCartesian', 'hoverCompareCartesian', 'lasso2d', 'toggleSpikelines'], + displaylogo: false + //modeBarButtons:['toImage2', 'zoom2d', 'pan2d', 'select2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d'], + } + + perm_data = js_data.perm_results + var hist_trace = { + x: perm_data, + type: 'histogram' + }; + histogram_data = [hist_trace]; + histogram_layout = { + bargap: 0.05, + title: "<b>Histogram of Permutation Test</b>", + xaxis: { + autorange: true, + title: "<b>{{ LRS_LOD }}</b>", + titlefont: { + family: "arial", + size: 20 + }, + ticklen: 4, + tickfont: { + size: 16 + } + }, + yaxis: { + autorange: true, + title: "<b>Count</b>", + titlefont: { + family: "arial", + size: 20 + }, + showline: true, + ticklen: 4, + tickfont: { + size: 16 + }, + automargin: true + }, + width: 500, + height: 300, + margin: { + l: 70, + r: 30, + t: 100, + b: 50 + } + }; + + Plotly.newPlot('perm_histogram', histogram_data, histogram_layout, modebar_options); + {% endif %} + + export_mapping_results = function() { + $('#marker_regression_form').attr('action', '/export_mapping_results'); + return $('#marker_regression_form').submit(); + } + + $('.export_mapping_results').click(export_mapping_results); + + $('#browser_tab').click(function() { + $('#gn1_map_options').css("display", "none") + }) + $('#gn1_map_tab').click(function() { + $('#gn1_map_options').css("display", "block") + }) + + hash = $('input[name=inputs_hash]').val(); + mappingLink = window.location.origin + "/run_mapping/"+ hash; + $('input[name=mappingLink]').width(mappingLink.length*0.95 + "ch") + $('input[name=mappingLink]').val(mappingLink); + $('.share-results').click(function() { + $.ajax({ + method: "POST", + url: "/cache_mapping_inputs", + data: { + inputs_hash: hash + }, + success: function() { + document.querySelector("input[name='mappingLink']").select(); + document.execCommand('copy'); + } + }); + }) + + </script> + +{% endblock %} + diff --git a/gn2/wqflask/templates/marker_regression.html b/gn2/wqflask/templates/marker_regression.html new file mode 100644 index 00000000..b633f815 --- /dev/null +++ b/gn2/wqflask/templates/marker_regression.html @@ -0,0 +1,119 @@ +{% from "base_macro.html" import header %} +{% block content %} + {{ header("Mapping", + '{}: {}'.format(this_trait.name, this_trait.description_fmt)) }} + + <div class="container"> + <div> + <h2> + Whole Genome Mapping + </h2> + <form style ='float: left; padding: 5px;' id="exportform" action="export" method="post"> + <input type="hidden" id="data" name="data" value=""> + <input type="hidden" id="filename" name="filename" value=""> + <input type="submit" id="export" value="Download SVG"> + </form> + <form style ='float: left; padding: 5px;' id="exportpdfform" action="export_pdf" method="post"> + <input type="hidden" id="data" name="data" value=""> + <input type="hidden" id="filename" name="filename" value=""> + <input type="submit" id="export_pdf" value="Download PDF"> + </form> + <button id="return_to_full_view" class="btn" style="display:none">Return to full view</button> + </div> + <div id="chart_container"> + <div class="qtlcharts" id="topchart"> + + </div> + </div> + <div style="width:60%;"> + <h2> + Results + </h2> + <table id="qtl_results" class="table table-hover table-striped"> + <thead> + <tr> + <th></th> + <th>Index</th> + <th>{{ score_type }}</th> + <th>Chr</th> + {% if mapping_scale == "centimorgan" %} + <th>cM</th> + {% else %} + <th>Mb</th> + {% endif %} + <th>Locus</th> + </tr> + </thead> + <tbody> + {% for marker in qtl_results %} + {% if (score_type == "LOD" and marker.lod_score > cutoff) or + (score_type == "LRS" and marker.lrs_value > cutoff) %} + <tr> + <td> + <input type="checkbox" name="selectCheck" + class="checkbox edit_sample_checkbox" + value="{{ marker.name }}" checked="checked"> + </td> + <Td align="right">{{ loop.index }}</Td> + {% if score_type == "LOD" %} + <td>{{ '%0.2f' | format(marker.lod_score|float) }}</td> + {% else %} + <td>{{ '%0.2f' | format(marker.lrs_value|float) }}</td> + {% endif %} + <td>{{marker.chr}}</td> + <td>{{ '%0.6f' | format(marker.Mb|float) }}</td> + <td> + {{ marker.name }} + <!--<a href="{{ url_for('show_trait_page', + trait_id = marker.name, + dataset = dataset.name + )}}"> + {{ marker.name }} + </a>--> + </td> + </tr> + {% endif %} + {% endfor %} + </tbody> + </table> + </div> + </div> + + <!-- End of body --> + +{% endblock %} + +{% block js %} + <script> + js_data = {{ js_data | safe }} + </script> + + <script type="text/javascript" charset="utf-8"> + $(document).ready( function () { + console.time("Creating table"); + $('#qtl_results').dataTable( { + "columns": [ + { "type": "natural", "bSortable": false }, + { "type": "natural" }, + { "type": "natural" }, + { "type": "natural" }, + { "type": "natural" }, + { "type": "natural" } + ], + "buttons": [ + 'csv' + ], + "sDom": "RZBtir", + "iDisplayLength": -1, + "autoWidth": true, + "bDeferRender": true, + "bSortClasses": false, + "scrollY": "700px", + "scrollCollapse": true, + "paging": false + } ); + console.timeEnd("Creating table"); + + }); + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/metadata/dataset.html b/gn2/wqflask/templates/metadata/dataset.html new file mode 100644 index 00000000..06df6a68 --- /dev/null +++ b/gn2/wqflask/templates/metadata/dataset.html @@ -0,0 +1,157 @@ +<header class="page-header text-justify"> + <h1> + {% if dataset.title or dataset.label or dataset.altLabel %} + {{ dataset.title or dataset.label or dataset.altLabel }} + {% if dataset.title != dataset.altLabel and dataset.label != dataset.altLabel %} + <br/> + <small>({{ dataset.altLabel }})</small> + {% endif %} + {% else %} + {{ name }} + {% endif %} + </h1> +</header> + +<div class="container dataset-content"> + <div class="panel-about panel panel-info panel-metadata text-muted"> + <div class="panel-heading"> + <strong> + <span class="glyphicon glyphicon-info-sign aria-hidden=true"></span> + Details + </strong> + {% if dataset.accessionId %} + <small> + <a href="https://gn1.genenetwork.org/webqtl/main.py?FormID=sharinginfo&GN_AccessionId={{ dataset.accessionId.split('GN')[-1] }}" target="_blank">(GN1 Link)</a> + </small> + {% endif %} + </div> + <div class="panel-body"> + <dl class="dl-horizontal"> + {% if dataset.label != dataset.altLabel and dataset.label != dataset.title %} + <dt>Name</dt> + <dd> {{ dataset.label }} </dd> + {% endif %} + {% if dataset.created %} + <dt>Created</dt> + <dd>{{ dataset.created }}</dd> + {% endif %} + {% if dataset.inbredSet %} + <dt>Group</dt> + <dd>{{ dataset.inbredSet.label}}</dd> + {% endif %} + + {% if dataset.accessionId %} + <dt>Accession Id</dt> + <dd>{{ dataset.accessionId}}</dd> + {% endif %} + + {% if dataset.hasGeoSeriesId %} + <dt>GEO Series</dt> + <dd> + <a href="{{ dataset.geoSeriesId }}" target="_blank">{{ dataset.geoSeriesId.split("=")[-1] }}</a> + </dd> + {% endif %} + + {% if dataset.tissue %} + <dt>Tissue</dt> + <dd>{{ dataset.tissue.label }}</dd> + {% endif %} + + {% if dataset.platform %} + <dt>Platforms</dt> + <dd> + {{ dataset.platform.label }} + [<a href="{{ dataset.platform.id}}" target="_blank"> + {{ dataset.platform.prefLabel}} + </a>] + </dd> + {% endif %} + + {% if dataset.normalization %} + <dt>Normalization</dt> + <dd>{{ dataset.normalization.label }}</dd> + {% endif %} + + {% if dataset.contactPoint %} + <dt>Investigator</dt> + <dd> + {% if dataset.contactPoint.contactWebUrl %} + <a href="{{ dataset.contactPoint.contactWebUrl }}" target="_blank"> + {{ dataset.contactPoint.contactName }} + </a> + {% else %} + {{ dataset.contactPoint.contactName }} + {% endif %} + </dd> + {% endif %} + + {% if dataset.organization %} + <dt>Organization</dt> + <dd>{{ dataset.organization }}</dd> + {% endif %} + </dl> + </div> + </div> + + <div class="container row dataset-metadata"> + {% if dataset.description %} + <div>{{ dataset.description|safe }}</div> + {% endif %} + + {% if dataset.specificity %} + <h2><strong>Specifics of this Dataset</strong></h2> + <div>{{ dataset.specificity }}</div> + {% endif %} + + {% if dataset.experimentDesignInfo %} + <h3><strong>Experiment Design</strong></h3> + <div>{{ dataset.experimentDesignInfo|safe }}</div> + {% endif %} + + {% if dataset.caseInfo %} + <h3><strong>About the Cases Used to Generate this Dataset:</strong></h3> + <div>{{ dataset.caseInfo|safe }}</div> + {% endif %} + + {% if dataset.tissue %} + <h3><strong>About the Tissue + {%if dataset.tissue and dataset.tissue.label %}({{ dataset.tissue.label }}) + {% endif %}Used to Generate this Dataset</strong></h3> + <div>{{ dataset.tissue.tissueInfo|safe }}</div> + {% endif %} + + {% if dataset.platform %} + <h3 title="{{ dataset.platform.label }}"><strong>About the Array Platform</strong></h3> + <div>{{ dataset.platform.platformInfo|safe }}</div> + {% endif %} + + {% if dataset.processingInfo %} + <h3><strong>About Data Values and Data Processing</strong></h3> + <div>{{ dataset.processingInfo|safe }}</div> + {% endif %} + + {% if dataset.notes %} + <h3><strong>Notes</strong></h3> + <div>{{ dataset.notes|safe }}</div> + {% endif %} + + {% if dataset.citation|trim %} + <h3><strong>Citation</strong></h3> + <div>{{ dataset.citation|safe}}</div> + {% endif %} + + {% if dataset.acknowledgement|trim %} + <h3><strong>Acknowledgment</strong></h3> + <div>{{ dataset.acknowledgement|safe}}</div> + {% endif %} + + {% if dataset.contributors %} + <h3><strong>Contributors</strong></h3> + <div>{{ dataset.contributors|safe }}</div> + {% endif %} + </div> + +</div> + +</div> + diff --git a/gn2/wqflask/templates/network_graph.html b/gn2/wqflask/templates/network_graph.html new file mode 100644 index 00000000..cff69ac8 --- /dev/null +++ b/gn2/wqflask/templates/network_graph.html @@ -0,0 +1,152 @@ +{% extends "base.html" %} +{% block css %} + <link rel="stylesheet" type="text/css" href="/static/new/css/network_graph.css" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='cytoscape-panzoom/cytoscape.js-panzoom.css') }}"> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='qtip2/jquery.qtip.min.css') }}"> + <style> + /* The Cytoscape Web container must have its dimensions set. */ + html, body { height: 100%; width: 100%; padding: 0; margin: 0; } + #cytoscapeweb { width: 70%; height: 70%; } + </style> +{% endblock %} +{% block content %} + <div class="container-fluid"> + <h1>Network Graph</h1> + <hr style="height: 1px; background-color: #A9A9A9;"> + <div class="row" > + <div id="content"> + <div id="secondaryContent" class="col-xs-3"> + <h3 style="margin-top:0px; margin-bottom: 5px;"> Visualization Options</h3> + <table border="0"> + <tbody> + <tr> + <td> + <button id="reset_graph">Reset Graph</button> + </td> + </tr> + <tr> + <td> + Focus Trait<sup title="Only show edges connected to the specified node" style="color:#f00"> ?</sup> + </td> + </tr> + <tr> + <td> + <select name="focus_select"> + <option disabled selected value>Select Trait</option> + {% for trait in traits %} + {% if trait.symbol == None %} + <option value="{{ trait.name }}:{{ trait.dataset.name }}">{{ trait.name }}</option> + {% else %} + <option value="{{ trait.name }}:{{ trait.dataset.name }}">{{ trait.symbol }} ({{ trait.name }})</option> + {% endif %} + {% endfor %} + </select> + </td> + </tr> + <tr> + <td colspan="1"> + Correlation Coefficient<sup title="Filter edges to only show correlations less than the negative value specified with the slider and greater than the positive value. For example, moving the slider half way will display correlations less than -0.5 and greater than 0.5" style="color:#f00"> ?</sup> + </td> + </tr> + <tr> + <td colspan="1"> + <div style="text-align: center;"> + <div style="float: left;"><font size="2"><b>-1</b></font></div> + <font size="2"><b>0</b></font> + <div style="float: right;"><font size="2"><b>1</b></font></div> + </div> + <!-- + <font size="2"><b>-1 + 0 + 1</b></font><br> + --> + <input type="range" id="neg_slide" min="-1" max="0" value="0" step="0.001" list="corr_range" style="display: inline; width: 49%"> + <input type="range" id="pos_slide" min="0" max="1" value="0" step="0.001" list="corr_range" style="display: inline; width: 49%"> + </td> + </tr> + <tr> + <td> + Layouts: + <select name="layout_select"> + <option value="circle">Circle</option> + <option value="concentric">Concentric</option> + <option value="cose">Cose</option> + <option value="grid">Grid</option> + <option value="breadthfirst">Breadthfirst</option> + <option value="random">Random</option> + </select> + </td> + </tr> + <tr> + <td> + Node Font Size: + <select name="font_size"> + <option value="6">6</option> + <option value="7">7</option> + <option value="8">8</option> + <option value="9">9</option> + <option value="10" selected>10</option> + <option value="11">11</option> + <option value="12">12</option> + <option value="13">13</option> + <option value="14">14</option> + <option value="15">15</option> + <option value="16">16</option> + <option value="17">17</option> + <option value="18">18</option> + </select> + </td> + <td> + </tr> + <tr> + <td> + Edge Width: + <select name="edge_width"> + <option value="1" selected>1x</option> + <option value="2">2x</option> + <option value="3">3x</option> + <option value="4">4x</option> + </select> + </td> + <td> + </tr> + </tbody> + </table> + <h3 style="margin-bottom: 5px;"> Download</h3> + <table> + <tbody> + <tr> + <td> + <a id="image_link" href="javascript:void(0)"> + <button style="width:100px;height:25px;">Save Image</button> + </a> + </td> + </tr> + </tbody> + </table> + </div> + <div id="cytoscapeweb" class="col-xs-9" style="height:700px !important; border-style: solid; border-width: 1px; border-color: grey;"></div> + </div> + </div> + </div> + + +{% endblock %} + +{% block js %} + + <script> + elements_list = {{ elements | safe }} + gn2_url = "{{ gn2_url | safe }}" + </script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='qtip2/jquery.qtip.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='cytoscape/cytoscape.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='cytoscape-panzoom/cytoscape-panzoom.js') }}"></script> + <!-- should be using cytoscape-popper for tips, see docs --> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='cytoscape-qtip/cytoscape-qtip.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/network_graph.js"></script> + +{% endblock %} diff --git a/gn2/wqflask/templates/new_security/forgot_password.html b/gn2/wqflask/templates/new_security/forgot_password.html new file mode 100644 index 00000000..60a221da --- /dev/null +++ b/gn2/wqflask/templates/new_security/forgot_password.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} +{% block title %}Forgot Password{% endblock %} +{% block content %} + + <div class="container"> + <div class="page-header"> + <h1>Password Reset</h1> + </div> + + <div class="security_box"> + + <h4>Enter your email address and we'll send you a link to reset your password</h4> + + <form class="form-horizontal" action="/n/forgot_password_submit" + method="POST" name="login_user_form"> + <fieldset> + <div class="control-group"> + <div class="controls"> + <input id="email_address" class="focused" name="email_address" type="text" value=""> + </div> + </div> + + + <div class="control-group"> + <div class="controls" style="margin-top: 10px;"> + <input id="next" name="next" type="hidden" value=""> + + <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Send link"> + </div> + </div> + + </fieldset> + + <br /> + + <div class="well"> + <h5>Has your email address changed?</h5> + + If you no longer use the email address connected to your account, you can contact us for assistance. + + </div> + + </form> + </div> + </div> + </div> + + {% endblock %} + +{% block js %} + +{% endblock %} diff --git a/gn2/wqflask/templates/new_security/forgot_password_step2.html b/gn2/wqflask/templates/new_security/forgot_password_step2.html new file mode 100644 index 00000000..1835fd4c --- /dev/null +++ b/gn2/wqflask/templates/new_security/forgot_password_step2.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% block title %}Register{% endblock %} +{% block content %} + + {{ header("Password Reset", "Check your email.") }} + + + <div class="container"> + <div class="page-header"> + <h3>One last step</h3> + </div> + + <p>You will receive an email with the subject <strong>{{ subject }}</strong>.</p> + + <p>You must click the link in the email to reset the password.</p> + + <p>If you don't see the email, check your spam folder.</p> + </div> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script> + <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script> +{% endblock %} diff --git a/gn2/wqflask/templates/new_security/login_user.html b/gn2/wqflask/templates/new_security/login_user.html new file mode 100644 index 00000000..b8cdf6ef --- /dev/null +++ b/gn2/wqflask/templates/new_security/login_user.html @@ -0,0 +1,119 @@ +{% extends "base.html" %} +{% block title %}Register{% endblock %} +{% block content %} + + <div class="container" style="min-width: 1250px;"> + + {{ flash_me() }} + + <h3>Sign in here.</h3> + + {% if redis_is_available: %} + <form class="form-horizontal" action="/n/login" method="POST" name="login_user_form" id="loginUserForm"> + <input name="anon_id" type="hidden" value="{{ g.user_session.user_id }}"> + <fieldset> + <div class="form-group"> + <label style="text-align:left;" class="col-xs-1 control-label" for="email_address">Email Address</label> + <div style="margin-left:20px;" class="col-xs-4"> + <input id="email_address" class="focused" name="email_address" type="text" value="" size="50"> + </div> + </div> + + <div class="form-group"> + <label style="text-align:left;" class="col-xs-1 control-label" for="password">Password</label> + <div style="margin-left:20px;" class="col-xs-4 controls"> + <input id="password" class="focused" name="password" type="password" value="" size="50"> + <br /> + <br /> + <a href="/n/forgot_password">Forgot your password?</a><br/> + </div> + </div> + + <div class="form-group"> + <label class="col-xs-1 control-label" for="remember"></label> + <div style="margin-left:20px;" class="col-xs-4 controls"> + <input id="remember" name="remember" type="checkbox" value="y"> <b>Remember me</b><br> + <input id="import_collections" name="import_collections" type="checkbox" value="y"> <b>Import existing collections</b> + </div> + </div> + + <div class="form-group"> + <label class="col-xs-1 control-label" for="submit"></label> + <div style="margin-left:20px;" class="col-xs-4 controls"> + <input id="next" name="next" type="hidden" value=""> + <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Sign in"> + </div> + </div> + </fieldset> + + <div class="security_box"> + + <h4>Don't have an account?</h4> + + {% if redis_is_available: %} + <a href="/n/register" class="btn btn-primary modalize">Create a new account</a> + {% else %} + <div class="alert alert-warning"> + <p>You cannot create an account at this moment.<br /> + Please try again later.</p> + </div> + {% endif %} + + <hr /> + <h4>Login with external services</h4> + + {% if external_login: %} + <div> + {% if external_login["github"]: %} + <a href="{{external_login['github']}}" title="Login with GitHub" class="btn btn-info btn-group">Login with Github</a> + {% else %} + <p>Github login is not available right now</p> + {% endif %} + + {% if external_login["orcid"]: %} + <a href="{{external_login['orcid']}}" title="Login with ORCID" class="btn btn-info btn-group">Login with ORCID</a> + {% else %} + <p>ORCID login is not available right now</p> + {% endif %} + </div> + {% else: %} + <div class="alert alert-warning"> + <p>Sorry, you cannot login with Github or ORCID at this time.</p> + </div> + {% endif %} + + </form> + {% else: %} + <div class="alert alert-warning"> + <p>You cannot login at this moment using your GeneNetwork account (the authentication service is down).<br /> + Please try again later.</p> + </div> + {% endif %} + {% if not external_login: %} + <hr> + <div class="alert alert-warning"> + Note: it is safe to use GeneNetwork without a login. Login is only required for keeping track of + collections and getting access to some types of restricted data. + </div> + {% endif %} + </div> + </div> + + {% endblock %} + +{% block css %} +<style type="text/css"> +input.error{ + border:1px solid #FF0000 !important; +} + +label.error,div.error{ + font-weight:normal; + color:#FF0000 !important; +} +</style> +{% endblock %} + +{% block js %} + +{% endblock %} diff --git a/gn2/wqflask/templates/new_security/not_authenticated.html b/gn2/wqflask/templates/new_security/not_authenticated.html new file mode 100644 index 00000000..ea688346 --- /dev/null +++ b/gn2/wqflask/templates/new_security/not_authenticated.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% block title %}Authentication Needed{% endblock %} +{% block content %} + <div class="container"> + <div class="page-header"> + <h3>You lack the permissions to view these data.</h3> + </div> + <p>Please contact the data's owner or GN administrators if you believe you should have access to these data.</p> + </div> + +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/new_security/password_reset.html b/gn2/wqflask/templates/new_security/password_reset.html new file mode 100644 index 00000000..e21f075c --- /dev/null +++ b/gn2/wqflask/templates/new_security/password_reset.html @@ -0,0 +1,78 @@ +{% extends "base.html" %} +{% block title %}Register{% endblock %} +{% block content %} + + {{ header("Password Reset", "Create a new password.") }} + + + <div class="container"> + <div class="page-header"> + <h1>Password reset</h1> + </div> + + <div class="security_box"> + + + <h4>Enter your new password</h4> + + + {% if errors %} + <div class="alert alert-error"> + <strong>Please note:</strong> + <ul> + {% for error in errors %} + <li>{{error}}</li> + {% endfor %} + </ul> + </div> + {% endif %} + + <form class="form-horizontal" action="/n/password_reset_step2" data-validate="parsley" + method="POST" name="login_user_form"> + + <fieldset> + + <input class="hidden" name="user_encode" value="{{ user_encode }}"> + + + <div class="control-group"> + <label class="control-label" for="password">Password</label> + <div class="controls"> + <input id="password" name="password" type="password" value="" + data-trigger="change" data-required="true" data-minlength="6"> + </div> + </div> + + <div class="control-group" style="display: none" id="password_alert"> + <div class="controls""> + <span id="password_strength" class="alert"></span> + </div> + </div> + + <div class="control-group"> + <label class="control-label" for="password_confirm">Confirm Password</label> + <div class="controls"> + <input id="password" name="password_confirm" type="password" value="" + data-trigger="change" data-required="true" data-equalto="#password"> + </div> + </div> + + <div class="control-group"> + <div class="controls""> + <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Reset password"> + </div> + </div> + + </fieldset> + + </form> + </div> + </div> + +{% endblock %} + +{% block js %} + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script> + <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script> +{% endblock %} diff --git a/gn2/wqflask/templates/new_security/register_user.html b/gn2/wqflask/templates/new_security/register_user.html new file mode 100644 index 00000000..c2895517 --- /dev/null +++ b/gn2/wqflask/templates/new_security/register_user.html @@ -0,0 +1,105 @@ +{% extends "base.html" %} +{% block title %}Register{% endblock %} +{% block content %} + + {{ header("Register", "It's fast and easy to make an account.") }} + + + <div class="container"> + <div class="page-header"> + <h1>Registration</h1> + </div> + + <div class="security_box"> + <h4>Already have an account?</h4> + + + <a href="/n/login" + class="btn btn-info modalize">Sign in using existing account</a> + + + <hr /> + + <h4>Don't have an account?</h4> + + <h5>Register here</h5> + + {% if errors %} + <div class="alert alert-error"> + <strong>Please note:</strong> + <ul> + {% for error in errors %} + <li>{{error}}</li> + {% endfor %} + </ul> + </div> + {% endif %} + + <form class="form-horizontal" action="/n/register" data-validate="parsley" + method="POST" name="login_user_form"> + <fieldset> + + <div class="control-group"> + <label class="control-label" for="email_address">Email Address</label> + <div class="controls"> + <input id="email_address" name="email_address" class="focused" type="text" size="50" value="{{values.email_address}}" + data-trigger="change" data-required="true" data-type="email" data-maxlength="50"> + </div> + </div> + + <div class="control-group"> + <label class="control-label" for="full_name">Full Name</label> + <div class="controls"> + <input id="full_name" name="full_name" type="text" size="50" value="{{values.full_name}}" + data-trigger="change" data-required="true" data-minlength="5" data-maxlength="50"> + </div> + </div> + + <div class="control-group"> + <label class="control-label" for="organization">Organization</label> + <div class="controls"> + <input id="organization" name="organization" type="text" size="50" value="{{values.organization}}" data-minlength="3" data-maxlength="50"> + </div> + </div> + + <div class="control-group"> + <label class="control-label" for="password">Password</label> + <div class="controls"> + <input id="password" name="password" type="password" size="50" value="" + data-trigger="change" data-required="true" data-minlength="6"> + </div> + </div> + + <div class="control-group" style="display: none" id="password_alert"> + <div class="controls""> + <span id="password_strength" class="alert"></span> + </div> + </div> + + <div class="control-group"> + <label class="control-label" for="password_confirm">Confirm Password</label> + <div class="controls"> + <input id="password" name="password_confirm" type="password" size="50" value="" + data-trigger="change" data-required="true" data-equalto="#password"> + </div> + </div> + + <div class="control-group"> + <div class="controls" style="margin-top: 10px;"> + <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Create account"> + </div> + </div> + + </fieldset> + + </form> + </div> + </div> + +{% endblock %} + +{% block js %} + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script> + <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script> +{% endblock %} diff --git a/gn2/wqflask/templates/new_security/registered.html b/gn2/wqflask/templates/new_security/registered.html new file mode 100644 index 00000000..29889a97 --- /dev/null +++ b/gn2/wqflask/templates/new_security/registered.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} +{% block title %}Register{% endblock %} +{% block content %} + {{ header("Almost Done", "Thanks for registering") }} + + <div class="container"> + <div class="page-header"> + <h3>One last step</h3> + </div> + + <p>You will receive an email with the subject <strong>{{ subject }}</strong>.</p> + + <p>You must click the link in the email to complete registration.</p> + + <p>If you don't see the email, check your spam folder.</p> + </div> + +{% endblock %} + +{% block js %} + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script> + <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script> +{% endblock %} diff --git a/gn2/wqflask/templates/new_security/thank_you.html b/gn2/wqflask/templates/new_security/thank_you.html new file mode 100644 index 00000000..d4f5e574 --- /dev/null +++ b/gn2/wqflask/templates/new_security/thank_you.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% block title %}Register{% endblock %} +{% block content %} + {{ header("Thank you", "Thanks for verifying") }} + + <div class="container"> + <div class="page-header"> + <h3>You are done registering</h3> + </div> + + <p>Enjoy using the site.</p> + + <br /> + + <a class="btn btn-primary" href="{{ url_for("index_page") }}">Go to the homepage</a> + </div> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script> + <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script> +{% endblock %} diff --git a/gn2/wqflask/templates/new_security/verification_still_needed.html b/gn2/wqflask/templates/new_security/verification_still_needed.html new file mode 100644 index 00000000..1f91fd8d --- /dev/null +++ b/gn2/wqflask/templates/new_security/verification_still_needed.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} +{% block title %}Verification{% endblock %} +{% block content %} + {{ header("Verification", "You still need to verify") }} + + <div class="container"> + <div class="page-header"> + <h3>Verification of e-mail address</h3> + </div> + <h4>You still need to verify your e-mail address before you can sign in.</h4> + + <p>We've resent the verificaiton email. + + <p>Please check for an email with the subject <strong>{{ subject }}</strong>.</p> + + <p>You must click the link in the email to complete registration.</p> + + <p>If you don't see the email, check your spam folder.</p> + </div> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script> + <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script> +{% endblock %} diff --git a/gn2/wqflask/templates/news.html b/gn2/wqflask/templates/news.html new file mode 100644 index 00000000..a615564b --- /dev/null +++ b/gn2/wqflask/templates/news.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% block title %}News{% endblock %} + +{% block css %} +<link rel="stylesheet" type="text/css" href="/static/new/css/markdown.css" /> +{% endblock %} + +{% block content %} + + <div class="github-btn-container"> + <div class="github-btn"> + <a href="https://github.com/genenetwork/gn-docs/blob/master/general/news/news.md"> + Edit Text + <img src="/static/images/edit.png"> + </a> + </div> +</div> +<div id="markdown" class="container"> + {{ rendered_markdown|safe }} + +</div> + +{% endblock %} diff --git a/gn2/wqflask/templates/oauth2/create-resource.html b/gn2/wqflask/templates/oauth2/create-resource.html new file mode 100644 index 00000000..479f4152 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/create-resource.html @@ -0,0 +1,89 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} + +{%block title%}Create Resource{%endblock%} +{%block css%} +<link rel="stylesheet" type="text/css" href="/static/new/css/mytooltip.css" /> +{%endblock%} + +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("resources", user_privileges)}} + + {{flash_me()}} + + <div class="container-fluid"> + <div class="row"> + {%if resource_category_error%} + <p> + <span class="glyphicon glyphicon-exclamation-sign text-danger"></span> + + <span class="text-danger">{{resource_category_error.error}}</span>: + {{resource_category_error.error_message}} + </p> + {%else%} + <form method="POST" + action="{{url_for('oauth2.resource.create_resource')}}"> + + <fieldset> + <legend>Resource Category</legend> + <div class="form-group"> + {%for category in resource_categories%} + <div class="radio mytooltip"> + <label for="rdo:resource_category:{{category.resource_category_id}}" + class="form-label" + style="text-transform: capitalize;"> + <input type="radio" name="resource_category" required="required" + id="rdo:resource_category:{{category.resource_category_id}}" + value="{{category.resource_category_id}}" + {%if resource_category is defined%} + {%if category.resource_category_id == resource_category%} + checked="checked" + {%endif%} + {%endif%} /> + {{category.resource_category_key}} + </label> + <span class="mytooltiptext"> + {{category.resource_category_description}} + </span> + </div> + {%endfor%} + </div> + </fieldset> + + <fieldset> + <legend>Basic Resource Information</legend> + <div class="form-group mytooltip"> + <label for="resource_name" class="form-label">Name</label> + <input type="text" name="resource_name" class="form-control" + {%if resource_name is defined and resource_name is not none%} + value="{{resource_name}}" + {%endif%} + required="required" /> + <span class="mytooltiptext"> + The resource name, e.g. the experiment name. + </span> + </div> + </fieldset> + + <fieldset> + <legend>Access Control</legend> + <div class="form-group mytooltip"> + <label for="chk-public">Publicly Viewable?</label> + <input type="checkbox" name="public" id="chk-public" + checked="checked" /> + <span class="mytooltiptext"> + Select whether data in this resource will be publicly viewable. + </span> + </div> + </fieldset> + + <input class="btn btn-primary" type="submit" value="Create" /> + + </form> + {%endif%} + </div> + </div> + +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/create-role.html b/gn2/wqflask/templates/oauth2/create-role.html new file mode 100644 index 00000000..f2bff7b4 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/create-role.html @@ -0,0 +1,46 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} +{%block title%}View User{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("roles", user_privileges)}} + <h3>Create Role</h3> + + {{flash_me()}} + + {%if group_privileges_error is defined%} + {{display_error("Group Privileges", group_privileges_error)}} + {%else%} + {%if "group:role:create-role" in user_privileges%} + <form method="POST" action="{{url_for('oauth2.role.create_role')}}"> + <legend>Create Group Role</legend> + <div class="form-group"> + <label for="role_name" class="form-label">Name</label> + <input type="text" id="role_name" name="role_name" required="required" + class="form-control" + {%if prev_role_name is defined and prev_role_name is not none%} + value="{{prev_role_name}}" + {%endif%} /> + </div> + <label class="form-label">Privileges</label> + {%for priv in group_privileges%} + <div class="checkbox"> + <label for="chk:{{priv.privilege_id}}"> + <input type="checkbox" id="chk:{{priv.privilege_id}}" + name="privileges[]" value={{priv.privilege_id}} /> + <span style="text-transform: capitalize;"> + {{priv.privilege_description}} + </span> ({{priv.privilege_id}}) + </label> + </div> + {%endfor%} + + <input type="submit" class="btn btn-primary" value="Create" /> + </form> + {%else%} + {{display_error("Privilege", {"error":"PrivilegeError", "error_description": "You do not have sufficient privileges to create a new role."})}} + {%endif%} + {%endif%} +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/data-list-genotype.html b/gn2/wqflask/templates/oauth2/data-list-genotype.html new file mode 100644 index 00000000..c780a583 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/data-list-genotype.html @@ -0,0 +1,166 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} + +{%block title%}Link Data: Genotype{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" + href="/css/DataTables/css/jquery.dataTables.css" /> +<link rel="stylesheet" type="text/css" + href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{%endblock%} + +{%block content%} +<div class="container" style="width: 98%;"> + {{profile_nav("data", user_privileges)}} + + {{flash_me()}} + + <div class="row"> + <noscript>This page needs javascript to work correctly</noscript> + </div> + + <div class="row"> + <form id="frm-link-genotypes" method="POST" + action="{{url_for('oauth2.data.link_genotype_data')}}"> + <legend>Link Genotype Datasets to Group</legend> + + <input type="hidden" name="species_name" value="{{species_name}}" /> + + <div class="form-group"> + <label for="select-group">Group</label> + <select id="select-group" name="group_id" required="required" + class="form-control"> + <option value="">Select group</option> + {%for group in groups%} + <option value="{{group.group_id}}">{{group.group_name}}</option> + {%endfor%} + </select> + </div> + + <div class="form-group"> + <table id="tbl-link-genotypes" + class="table-hover table-striped cell-border dataTable no-footer" + data-selected-datasets='{{selected_datasets | list | tojson}}'> + <thead> + <tr> + <th>Deselect</th> + <th>Group</th> + <th>Dataset Name</th> + <th>Dataset FullName</th> + <th>Dataset ShortName</th> + </tr> + </thead> + <tbody> + {%for dataset in selected_datasets%} + <tr> + <td> + <input type="checkbox" class="checkbox checkbox-selected" + name="selected" + value='{{dataset | tojson}}' /> + </td> + <td>{{dataset.dataset_name}}</td> + <td>{{dataset.dataset_fullname}}</td> + <td>{{dataset.dataset_shortname}}</td> + </tr> + {%else%} + <tr> + <td colspan="100%" align="center"> + <span class="glyphicon glyphicon-info-sign text-info"></span> +   + No datasets selected for linking. + </td> + </tr> + {%endfor%} + </tbody> + </table> + </div> + + <div class="form-group text-center"> + <input type="submit" value="Link Selected" + class="btn btn-primary" + style="border-top: 0.3em;" + {%if groups | length <= 0 or selected_datasets | length <= 0%} + disabled="disabled" + {%endif%} /> + </div> + </form> + </div> + + <div class="row"> + <span id="search-messages" class="alert-danger" style="display:none"></span> + <form id="frm-search" + action="{{search_uri}}" + method="POST"> + <legend>Search: Genotype</legend> + <input type="hidden" value="{{species_name}}" name="species" + id="txt-species-name" /> + <input type="hidden" value="{{dataset_type}}" name="dataset_type" + id="txt-dataset-type" /> + <input type="hidden" value="{{per_page}}" name="per_page" + id="txt-per-page" /> + + <div class="form-group"> + <label for="txt-query">Dataset Search String</label> + <input type="text" id="txt-query" name="query" class="form-control" + value="{{query}}"/> + </div> + </form> + </div> + + <div class="row"> + <div id="search-error" class="text-danger" style="display: none;"> + <span class="glyphicon glyphicon-exclamation-sign"></span> +   + <span id="search-error-text"></span> + </div> + <table id="tbl-genotypes" + class="table-hover table-striped cell-border dataTable no-footer" + data-datasets='{{datasets | list | tojson}}'> + <thead> + <tr> + <th>Select</th> + <th>Group</th> + <th>Dataset Name</th> + <th>Dataset FullName</th> + <th>Dataset ShortName</th> + </tr> + </thead> + <tbody> + {%for dataset in datasets:%} + <tr> + <td> + <input type="checkbox" class="checkbox checkbox-search" + name="search_datasets" + value='{{dataset | tojson}}' /> + </td> + <td>{{dataset.InbredSetName}}</td> + <td>{{dataset.dataset_name}}</td> + <td>{{dataset.dataset_fullname}}</td> + <td>{{dataset.dataset_shortname}}</td> + </tr> + {%else%} + <tr> + <td colspan="100%" align="center"> + <span class="glyphicon glyphicon-info-sign text-info"></span> +   + No datasets available for selection. + </td> + </tr> + {%endfor%} + </tbody> + </table> + </div> + + +</div> +{%endblock%} + +{%block js%} +<script src="/static/new/javascript/auth/search.js" + language="javascript" type="text/javascript"></script> +<script src="/static/new/javascript/auth/search_genotypes.js" + language="javascript" type="text/javascript"></script> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/data-list-mrna.html b/gn2/wqflask/templates/oauth2/data-list-mrna.html new file mode 100644 index 00000000..0e163235 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/data-list-mrna.html @@ -0,0 +1,168 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} + +{%block title%}Link Data: Genotype{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" + href="/css/DataTables/css/jquery.dataTables.css" /> +<link rel="stylesheet" type="text/css" + href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{%endblock%} + +{%block content%} +<div class="container" style="width: 98%;"> + {{profile_nav("data", user_privileges)}} + + {{flash_me()}} + + <div class="row"> + <noscript>This page needs javascript to work correctly</noscript> + </div> + + <div class="row"> + <form id="frm-link" method="POST" + action="{{url_for('oauth2.data.link_mrna_data')}}"> + <legend>Link mRNA Assay Datasets to Group</legend> + + <input type="hidden" name="species_name" value="{{species_name}}" /> + + <div class="form-group"> + <label for="select-group">Group</label> + <select id="select-group" name="group_id" required="required" + class="form-control"> + <option value="">Select group</option> + {%for group in groups%} + <option value="{{group.group_id}}">{{group.group_name}}</option> + {%endfor%} + </select> + </div> + + <div class="form-group"> + <table id="tbl-link" + class="table-hover table-striped cell-border dataTable no-footer" + data-datasets='{{selected_datasets | list | tojson}}'> + <thead> + <tr> + <th>Deselect</th> + <th>Group</th> + <th>Dataset Name</th> + <th>Dataset FullName</th> + <th>Dataset ShortName</th> + </tr> + </thead> + <tbody> + {%for dataset in selected_datasets%} + <tr> + <td> + <input type="checkbox" class="checkbox checkbox-selected" + name="selected" + value='{{dataset | tojson}}' /> + </td> + <td>{{dataset.dataset_name}}</td> + <td>{{dataset.dataset_fullname}}</td> + <td>{{dataset.dataset_shortname}}</td> + </tr> + {%else%} + <tr> + <td colspan="100%" align="center"> + <span class="glyphicon glyphicon-info-sign text-info"></span> +   + No datasets selected for linking. + </td> + </tr> + {%endfor%} + </tbody> + </table> + </div> + + <div class="form-group text-center"> + <input type="submit" value="Link Selected" + class="btn btn-primary" + style="border-top: 0.3em;" + {%if groups | length <= 0 or selected_datasets | length <= 0%} + disabled="disabled" + {%endif%} /> + </div> + </form> + </div> + + <div class="row"> + <span id="search-messages" class="alert-danger" style="display:none"></span> + <form id="frm-search" + action="{{search_uri}}" + method="POST"> + <legend>Search: mRNA Assay</legend> + <input type="hidden" value="{{species_name}}" name="species" + id="txt-species-name" /> + <input type="hidden" value="{{dataset_type}}" name="dataset_type" + id="txt-dataset-type" /> + <input type="hidden" value="{{per_page}}" name="per_page" + id="txt-per-page" /> + + <div class="form-group"> + <label for="txt-query">Dataset Search String</label> + <input type="text" id="txt-query" name="query" class="form-control" + value="{{query}}"/> + </div> + </form> + </div> + + <div class="row"> + <div id="search-error" class="text-danger" style="display: none;"> + <span class="glyphicon glyphicon-exclamation-sign"></span> +   + <span id="search-error-text"></span> + </div> + <table id="tbl-search" + class="table-hover table-striped cell-border dataTable no-footer" + data-datasets='{{datasets | list | tojson}}'> + <thead> + <tr> + <th>Select</th> + <th>Group</th> + <th>Study Name</th> + <th>Dataset Name</th> + <th>Dataset FullName</th> + <th>Dataset ShortName</th> + </tr> + </thead> + <tbody> + {%for dataset in datasets:%} + <tr> + <td> + <input type="checkbox" class="checkbox checkbox-search" + name="search_datasets" + value='{{dataset | tojson}}' /> + </td> + <td>{{dataset.InbredSetName}}</td> + <td>{{dataset.StudyName}}</td> + <td>{{dataset.dataset_name}}</td> + <td>{{dataset.dataset_fullname}}</td> + <td>{{dataset.dataset_shortname}}</td> + </tr> + {%else%} + <tr> + <td colspan="100%" align="center"> + <span class="glyphicon glyphicon-info-sign text-info"></span> +   + No datasets available for selection. + </td> + </tr> + {%endfor%} + </tbody> + </table> + </div> + + +</div> +{%endblock%} + +{%block js%} +<script src="/static/new/javascript/auth/search.js" + language="javascript" type="text/javascript"></script> +<script src="/static/new/javascript/auth/search_mrna.js" + language="javascript" type="text/javascript"></script> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/data-list-phenotype.html b/gn2/wqflask/templates/oauth2/data-list-phenotype.html new file mode 100644 index 00000000..8c79c0d6 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/data-list-phenotype.html @@ -0,0 +1,209 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} + +{%block title%}Link Data: Phenotype{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" + href="/css/DataTables/css/jquery.dataTables.css" /> +<link rel="stylesheet" type="text/css" + href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{%endblock%} + +{%block content%} + +<div class="container" style="min-width: 1250px;"> + {{profile_nav("data", user_privileges)}} + + {{flash_me()}} + + <div class="row"> + <noscript>This page needs javascript to work correctly</noscript> + </div> + + <div class="row"> + <form id="frm-link-phenotypes" + action="{{url_for('oauth2.data.link_phenotype_data')}}" + method="POST"> + <input type="hidden" value="{{species_name}}" name="species_name" /> + <legend style="text-transform: capitalize;"> + {{dataset_type}}: Link Traits to Group + </legend> + + <div class="form-group"> + <label for="select-group">Group</label> + <select id="select-group" name="group_id" required="required" + class="form-control"> + <option value="">Select group</option> + {%for group in groups%} + <option value="{{group.group_id}}">{{group.group_name}}</option> + {%endfor%} + </select> + </div> + + <table id="tbl-link-phenotypes" + class="table-hover table-striped cell-border dataTable no-footer" + data-traits="[]"> + <thead> + <tr> + <th>Link</th> + <th>Name</th> + <th>Group</th> + <th>Dataset</th> + <th>Dataset Fullname</th> + <th>Description</th> + <th>Authors</th> + <th>Year</th> + <th>Location</th> + <th>LRS</th> + <th>Additive</th> + </tr> + </thead> + <tbody> + {%for trait in selected%} + <tr> + <th> + <input type="checkbox" class="checkbox checkbox-search" + name="search_traits" value="{{trait | tojson}}" /> + </th> + <th>{{trait.name}}</th> + <th>{{trait.group}}</th> + <th>{{trait.dataset}}</th> + <th>{{trait.dataset_fullname}}</th> + <th>{{trait.description}}</th> + <th>{{trait.authors | join(" ")}}</th> + <th> + <a href="{{trait.pubmed_linj}}" title="Pubmed link for trait"> + {{trait.year}} + </a> + </th> + <th>CHR{{trait.geno_chr}}@{{trait.geno_mb}}</th> + <th>{{trait.lrs}}</th> + <th>{{trait.additive}}</th> + </tr> + {%else%} + <tr> + <td colspan="100%" align="center" style="text-align: center;"> + <br/><b><font size="4"> + <span class="glyphicon glyphicon-info-sign text-info"></span> + + There are no phenotype traits to link to the user group. + </font></b><br /> + </td> + </tr> + {%endfor%} + </tbody> + </table> + + <div class="form-group text-center"> + <input type="submit" value="Link Selected" + class="btn btn-primary" + style="border-top: 0.3em;" + {%if groups | length <= 0 or traits | length <= 0%} + disabled="disabled" + {%endif%} /> + </div> + </form> + </div> + + <div class="row"> + <span id="search-messages" class="alert-danger" style="display:none"></span> + <form id="frm-search-traits" + action="#" + method="POST" + data-gn-server-url="{{gn_server_url}}"> + {%if dataset_type == "mrna"%} + <legend>mRNA: Search</legend> + {%else%} + <legend style="text-transform: capitalize;"> + {{dataset_type}}: Search + </legend> + {%endif%} + <input type="hidden" value="{{species_name}}" name="species" + id="txt-species-name" /> + <input type="hidden" value="{{dataset_type}}" name="dataset_type" + id="txt-dataset-type" /> + <input type="hidden" value="{{per_page}}" name="per_page" + id="txt-per-page" /> + + <div class="form-group"> + <label for="txt-query">Search</label> + <div class="input-group"> + <span class="input-group-addon">species:{{species_name}} AND </span> + <input type="text" id="txt-query" name="query" class="form-control" + value="{{query}}"/> + </div> + </div> + </form> + </div> + + <div class="row"> + <table id="tbl-phenotypes" + class="table-hover table-striped cell-border dataTable no-footer" + data-traits="{{traits | tojson}}" + data-initial-job-id="{{search_results.job_id}}" + data-initial-command-id="{{search_results.command_id}}"> + <thead> + <tr> + <th>Select</th> + <th>Name</th> + <th>Group</th> + <th>Dataset</th> + <th>Dataset Fullname</th> + <th>Description</th> + <th>Authors</th> + <th>Year</th> + <th>Location</th> + <th>LRS</th> + <th>Additive</th> + </tr> + </thead> + <tbody> + {%for trait in traits%} + <tr> + <th> + <input type="checkbox" class="checkbox checkbox-search" + name="search_traits" value="{{trait | tojson}}" /> + </th> + <th>{{trait.name}}</th> + <th>{{trait.group}}</th> + <th>{{trait.dataset}}</th> + <th>{{trait.dataset_fullname}}</th> + <th>{{trait.description}}</th> + <th>{{trait.authors | join(" ")}}</th> + <th> + <a href="{{trait.pubmed_linj}}" title="Pubmed link for trait"> + {{trait.year}} + </a> + </th> + <th>CHR{{trait.geno_chr}}@{{trait.geno_mb}}</th> + <th>{{trait.lrs}}</th> + <th>{{trait.additive}}</th> + </tr> + {%else%} + <tr> + <td colspan="100%" align="center" style="text-align: center;"> + <br/><b><font size="4"> + <span class="glyphicon glyphicon-info-sign text-info"></span> + + There are no phenotype traits to select from. + </font></b><br /> + </td> + </tr> + {%endfor%} + </tbody> + </table> + </div> + +</div> + +{%endblock%} + +{%block js%} +<script language="javascript" type="text/javascript" + src="/static/new/javascript/auth/search.js"></script> +<script language="javascript" type="text/javascript" + src="/static/new/javascript/auth/search_phenotypes.js"></script> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/data-list.html b/gn2/wqflask/templates/oauth2/data-list.html new file mode 100644 index 00000000..8a8f6694 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/data-list.html @@ -0,0 +1,53 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} +{%block title%}Link Data{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("data", user_privileges)}} + + {{flash_me()}} + + <div class="row"> + <form id="frm-select-datatype" + action="{{url_for('oauth2.data.list_data')}}" + method="POST"> + <legend>Search</legend> + {%if species_error is defined%} + {{display_error("Species", species_error)}} + {%elif species | length == 0%} + <span class="glyphicon glyphicon-info-sign text-danger"> + </span> + + <strong class="text-danger">No list of species to select from</strong> + {%else%} + <div class="form-group"> + <label for="select-species">Species</label> + <select id="select-species" name="species_name" required="required" + class="form-control"> + <option value="">Select Species</option> + {%for spc in species%} + <option value="{{spc.Name}}"> + {{spc.MenuName}} ({{spc.FullName}}) + </option> + {%endfor%} + </select> + </div> + + <div class="form-group"> + <label for="select-dataset-type">Dataset/Trait Type</label> + <select id="select-dataset-type" name="dataset_type" + required="required" class="form-control"> + <option value="">Select dataset type</option> + <option value="mrna">mRNA Assay (ProbeSet) Dataset</option> + <option value="genotype">Genotype Dataset</option> + <option value="phenotype">Phenotype (Publish) Dataset</option> + </select> + </div> + + <input type="submit" class="btn btn-primary" value="Search" /> + {%endif%} + </form> + </div> +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/display_error.html b/gn2/wqflask/templates/oauth2/display_error.html new file mode 100644 index 00000000..9abe02c4 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/display_error.html @@ -0,0 +1,10 @@ +{%macro display_error(title, the_error)%} + +<strong>{{title}}</strong>: +<span class="glyphicon glyphicon-exclamation-sign text-danger"> +</span> + +<strong class="text-danger">{{the_error.error}}:</strong> +{{the_error.error_description}} + +{%endmacro%} diff --git a/gn2/wqflask/templates/oauth2/group.html b/gn2/wqflask/templates/oauth2/group.html new file mode 100644 index 00000000..f4c29d18 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/group.html @@ -0,0 +1,114 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} +{%block title%}View User{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("group", user_privileges)}} + + {{flash_me()}} + + {%if group_error is defined%} + <div class="row" style="text-align:center;line-height:5em;"> + {{display_error("Group", group_error)}} + </div> + {%else%} + <div class="container-fluid"> + <div class="row"> + {%if group_join_requests_error is defined %} + {{display_error("Join Requests", group_join_requests_error)}} + {%else%} + <a href="{{url_for('oauth2.group.list_join_requests')}}" + class="btn btn-info"> + Requests ({{group_join_requests | count}}) + </a> + {%endif%} + </div> + <div class="row"> + <table class="table"> + <caption>Group Information</caption> + <thead> + <tr> + <th>Name</th> + <th colspan="2" style="text-align: center;">Metadata</th> + <th colspan="2" style="text-align: center;">Actions</th> + </tr> + </thead> + + <tbody> + <tr> + <td rowspan="{{group.group_metadata.items() | count + 1}}"> + {{group.group_name}} + </td> + <td><strong>Key</strong></td> + <td><strong>Value</strong></td> + <td rowspan="{{group.group_metadata.items() | count + 1}}" + style="text-align: center;"> + <a href="{{url_for('oauth2.group.edit_group', group_id=group.group_id)}}" + class="btn btn-info" title="Edit group information">Edit</a> + </td> + <td rowspan="{{group.group_metadata.items() | count + 1}}" + style="text-align: center;"> + <a href="{{url_for('oauth2.group.edit_group', group_id=group.group_id)}}" + class="btn btn-danger" title="Delete this group">Delete</a> + </td> + </tr> + + {%for key,val in group.group_metadata.items()%} + <tr> + <td>{{key.split("_") | map("capitalize") | join(" ")}}</td> + <td>{{val}}</td> + </tr> + {%endfor%} + + </tbody> + </table> + </div> + </div> + + <div class="container-fluid"> + + <table class="table"> + <caption>Group Users</caption> + <thead> + <tr> + <th>Name</th> + <th>Email</th> + <th>Actions</th> + </tr> + </thead> + + <tbody> + {%for user in users%} + <tr> + <td>{{user.name}}</td> + <td>{{user.email}}</td> + <td> + <a href="url_for('oauth2.group.remove_user', user_id=user.user_id)" + title="Remove this user from being a member of this group." + class="btn btn-danger">Remove</a> + </td> + </tr> + {%else%} + <tr> + <td colspan="3"> + {%if user_error is defined%} + <span class="glyphicon glyphicon-exclamation-sign text-danger"> + </span> + + <strong class="text-danger">{{user_error.error}}</strong> + {{user_error.error_description}} + {%else%} + No users found for this group + {%endif%} + </td> + </tr> + {%endfor%} + </tbody> + </table> + + </div> + {%endif%} + +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/group_join_or_create.html b/gn2/wqflask/templates/oauth2/group_join_or_create.html new file mode 100644 index 00000000..8255d2f8 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/group_join_or_create.html @@ -0,0 +1,99 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%block title%}Join or Create Group{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" href="/static/new/css/mytooltip.css" /> +{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("group", user_privileges)}} + + <h3>Join or Create Group</h3> + + {{flash_me()}} + + {%if group_join_request is defined and group_join_request.exists %} + <p> + <span class="glyphicon glyphicon-info-sign text-warning"></span> + + <span class="text-info">You have an active request to join a group.</span> + </p> + + <p class="explainer"> + You cannot create a group, or request to join a new group until your + currently active request has been either accepted or rejected. + </p> + {%else%} + <p>You can</p> + + {%if groups | length > 0 %} + <div class="explainer"> + <p> + For most users, this is the preffered choice. You request access to an + existing group, and the group leader will chose whether or not to add you to + their group.</p> + + <p>You can only be a member of a single group.</p> + </div> + + <form action="{{url_for('oauth2.user.request_add_to_group')}}" + method="POST"> + <legend>Request to be added to group</legend> + <div class="form-group"> + <label class="control-label" for="group">Group</label> + <select class="form-control" id="group" required="required" name="group"> + <option value="">Select a group</option> + {%for group in groups%} + <option value="{{group.group_id}}">{{group.group_name}}</option> + {%endfor%} + </select> + </div> + <div class="form-group"> + <input type="submit" value="Request Access" class="btn btn-primary" /> + </div> + </form> + + <p>or</p> + {%else%} + <p> + <span class="glyphicon glyphicon-warning-sign text-warning"></span> + + <span class="text-warning">There an currently no groups to join.</span> + </p> + {%endif%} + + <div class="explainer"> + <p> + Creating a new group automatically makes you that group's administrator. + </p> + + <p>You can only be a member of a single group.</p> + </div> + + <form action="{{url_for('oauth2.group.create_group')}}" + method="POST"> + <legend>Create a new group</legend> + <div class="form-group mytooltip"> + <label class="control-label" for="group_name">Group Name</label> + <input type="text" class="form-control" id="group_name" + name="group_name"required="required" /> + <span class="mytooltiptext"> + Name of the group. + </span> + </div> + <div class="form-group mytooltip"> + <label class="control-label" for="group_desc">Group Description</label> + <textarea class="form-control" id="group_description" + name="group_description"></textarea> + <span class="mytooltiptext"> + A description to help identify the purpose/goal of the group + </span> + </div> + <div class="form-group"> + <input type="submit" value="Create Group" class="btn btn-primary" /> + </div> + </form> + {%endif%} +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/join-requests.html b/gn2/wqflask/templates/oauth2/join-requests.html new file mode 100644 index 00000000..833b4e93 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/join-requests.html @@ -0,0 +1,73 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} +{%block title%}View User{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("group", user_privileges)}} + + {{flash_me()}} + + <div class="container-fluid"> + <div class="row"> + <table class="table"> + <caption>Join Requests</caption> + <thead> + <tr> + <th>Name</th> + <th>Email</th> + <th>Request Date/Time</th> + <th>Status</th> + <th>Message</th> + <th colspan="2" style="text-align: center;">Actions</th> + </tr> + </thead> + + <tbody> + {%for request in requests%} + <tr> + <td>{{request.name}}</td> + <td>{{request.email}}</td> + <td>{{datetime_string(request.timestamp)}}</td> + <td>{{request.status}}</td> + <td>{{request.message}}</td> + <td> + <form method="POST" + action="{{url_for('oauth2.group.accept_join_request')}}"> + <input type="hidden" name="request_id" + value="{{request.request_id}}" /> + <input type="submit" class="btn btn-primary" value="Accept" + {%if request.status != "PENDING"%} + disabled="disabled" + {%endif%} /> + </form> + </td> + <td> + <form method="POST" + action="{{url_for('oauth2.group.reject_join_request')}}"> + <input type="hidden" name="request_id" + value="{{request.request_id}}" /> + <input type="submit" class="btn btn-danger" value="Reject" + {%if request.status != "PENDING"%} + disabled="disabled" + {%endif%} /> + </form> + </td> + </tr> + {%else%} + <tr> + <td colspan="3"> + {%if error is defined %} + {{display_error("Join Requests", error)}} + {%else%} + No one has requested to join your group yet. + {%endif%} + </td> + </tr> + {%endfor%} + </tbody> + </table> + </div> + </div> +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/list_roles.html b/gn2/wqflask/templates/oauth2/list_roles.html new file mode 100644 index 00000000..a4061fca --- /dev/null +++ b/gn2/wqflask/templates/oauth2/list_roles.html @@ -0,0 +1,80 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} +{%block title%}View User{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("roles", user_privileges)}} + <h3>Roles</h3> + + {{flash_me()}} + + <div class="container-fluid"> + <div class="row"> + <h3>Your System-Level Roles</h3> + <ul> + {%for role in roles %} + <li> + <a href="{{url_for('oauth2.role.role', role_id=role.role_id)}}" + title="Link to role {{role.role_name}}">{{role.role_name}}</a> + </li> + {%else%} + <li> + <span class="glyphicon glyphicon-warning-sign"></span> + <span class="text-warning">No roles attached to this user</span> + </li> + {%endfor%} + </ul> + </div> + + <div class="row"> + <h3>Group-Wide Roles</h3> + + {%if "group:role:create-role" in user_privileges%} + <a href="{{url_for('oauth2.role.create_role')}}" + title="Link to create a new group role" + class="btn btn-info">New Group Role</a> + {%endif%} + + {%if group_roles_error is defined%} + {{display_error("Group Roles", group_role_error)}} + {%else%} + <table class="table"> + <caption>Group Roles</caption> + <thead> + <tr> + <th>Role Name</th> + <th colspan="100%">Actions</th> + </tr> + </thead> + <tbody> + {%for grole in group_roles%} + <tr> + <td>{{grole.role.role_name}}</td> + <td> + <a href="{{url_for('oauth2.group.group_role', group_role_id=grole.group_role_id)}}" + title="Link to role {{grole.role.role_name}}" + class="btn btn-info"> + View + </a> + </td> + </tr> + {%else%} + <tr> + <td colspan="3"> + <span class="glyphicon glyphicon-exclamation-sign text-info"> + </span> + + <span class="text-info">No group roles found</span> + </td> + </tr> + {%endfor%} + </tbody> + </table> + {%endif%} + </div> + + </div> + +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/login.html b/gn2/wqflask/templates/oauth2/login.html new file mode 100644 index 00000000..eaa1a192 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/login.html @@ -0,0 +1,47 @@ +{%extends "base.html"%} +{%block title%}Login{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + <h3>Sign in here.</h3> + + <form class="form-horizontal" + {%if next_endpoint%} + action="{{url_for('oauth2.user.login', next=next_endpoint)}}" + {%else%} + action="{{url_for('oauth2.user.login')}}" + {%endif%} + method="POST" id="oauth2-login-form"> + <fieldset> + <legend>Sign in with Genenetwork</legend> + {{flash_me()}} + <div class="form-group"> + <label class="col-xs-1 control-label" for="email_address" + style="text-align:left;">Email</label> + <div style="margin-left:20px;" class="col-xs-4"> + <input id="email_address" name="email_address" type="email" + {%if email%}value="{{email}}"{%endif%} + placeholder="your@email.address" size="50" + class="form-control" /> + </div> + </div> + + <div class="form-group"> + <label class="col-xs-1 control-label" for="password" + style="text-align:left;">Password</label> + <div style="margin-left:20px;" class="col-xs-4"> + <input id="password" name="password" type="password" + size="50" class="form-control" + placeholder="your password" /> + </div> + </div> + + <div class="form-group"> + <div style="margin-left:20px;" class="col-xs-4 controls"> + <input type="submit" class="btn btn-primary form-control" id="submit" name="submit" + value="Sign in" /> + </div> + </div> + </fieldset> + </form> +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/masquerade.html b/gn2/wqflask/templates/oauth2/masquerade.html new file mode 100644 index 00000000..48ec6cee --- /dev/null +++ b/gn2/wqflask/templates/oauth2/masquerade.html @@ -0,0 +1,39 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} +{%block title%}Masquerade As{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("masquerade", user_privileges)}} + + {{flash_me()}} + + {%if users_error is defined%} + {{display_error("Users", users_error)}} + {%else%} + <div class="container-fluid"> + <div class="row"> + <form method="POST" + action="{{url_for('oauth2.user.masquerade')}}"> + <legend>Masquerade As</legend> + <div class="form-group"> + <label for="select-masquerade" class="form-label"> + Masquerade as + </label> + <select id="select-masquerade" name="masquerade_as" + required="required" class="form-control"> + <option value="">Select User</option> + {%for user in users%} + <option value="{{user.user_id}}">{{user.name}} ({{user.email}})</option> + {%endfor%} + </select> + </div> + <div class="form-group"> + <input type="submit" class="btn btn-primary" value="Masquerade" /> + </div> + </form> + </div> + </div> + {%endif%} +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/profile_nav.html b/gn2/wqflask/templates/oauth2/profile_nav.html new file mode 100644 index 00000000..aa752905 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/profile_nav.html @@ -0,0 +1,64 @@ +{%macro profile_nav(calling_page, user_sys_privileges)%} + +<ul class="nav nav-pills"> + + <li role="presentation" + {%if calling_page == "dashboard"%} + class="active" + {%endif%}> + <a href="{{url_for('oauth2.user.user_profile')}}">Dashboard</a> + </li> + + <li role="presentation" + {%if calling_page == "group"%} + class="active" + {%endif%}> + <a href="{{url_for('oauth2.group.user_group')}}">Group</a> + </li> + + <li role="presentation" + {%if calling_page == "roles"%} + class="active" + {%endif%}> + <a href="{{url_for('oauth2.role.user_roles')}}">Roles</a> + </li> + + <li role="presentation" + {%if calling_page == "resources"%} + class="active" + {%endif%}> + <a href="{{url_for('oauth2.resource.user_resources')}}">Resources</a> + </li> + + {%if "system:data:link-to-group" in user_sys_privileges %} + <li role="presentation" + {%if calling_page == "data"%} + class="active" + {%endif%}> + <a href="{{url_for('oauth2.data.list_data')}}">Link Data</a> + </li> + {%endif%} + + {%if "system:user:masquerade" in user_sys_privileges %} + <li role="presentation" + {%if calling_page == "masquerade"%} + class="active" + {%endif%}> + <a href="{{url_for('oauth2.user.masquerade')}}" + title="Masquerade as another user"> + Masquerade As + </a> + </li> + {%endif%} + + <li role="presentation"> + {%if logged_in():%} + <a href="{{url_for('oauth2.user.logout')}}">Logout</a> + {%else%} + <a href="{{url_for('oauth2.user.login')}}">Login</a> + {%endif%} + </li> + +</ul> + +{%endmacro%} diff --git a/gn2/wqflask/templates/oauth2/register_user.html b/gn2/wqflask/templates/oauth2/register_user.html new file mode 100644 index 00000000..27ccbd30 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/register_user.html @@ -0,0 +1,62 @@ +{%extends "base.html"%} +{%block title%}Register New User{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + <h3>Register User</h3> + + <form class="form-horizontal" + action="{{url_for('oauth2.user.register_user')}}" + method="POST" id="oauth2-register-user-form"> + <fieldset> + <legend>Register User</legend> + {{flash_me()}} + <div class="form-group"> + <label class="col-xs-1 control-label" for="name" + style="text-align:left;">Name</label> + <div style="margin-left:20px;" class="col-xs-4"> + <input id="user_name" name="user_name" type="text" + placeholder="Your Name" size="50" + class="form-control" required="required" /> + </div> + </div> + + <div class="form-group"> + <label class="col-xs-1 control-label" for="email_address" + style="text-align:left;">Email</label> + <div style="margin-left:20px;" class="col-xs-4"> + <input id="email_address" name="email_address" type="email" + placeholder="your@email.address" size="50" + class="form-control" required="required" /> + </div> + </div> + + <div class="form-group"> + <label class="col-xs-1 control-label" for="password" + style="text-align:left;">Password</label> + <div style="margin-left:20px;" class="col-xs-4"> + <input id="password" name="password" type="password" + size="50" class="form-control" + placeholder="your password" required="required" /> + </div> + </div> + + <div class="form-group"> + <label class="col-xs-1 control-label" for="confirm_password" + style="text-align:left;">Confirm Password</label> + <div style="margin-left:20px;" class="col-xs-4"> + <input id="confirm_password" name="confirm_password" type="password" + size="50" class="form-control" required="required" + placeholder="your password, again" /> + </div> + </div> + + <div class="form-group"> + <div style="margin-left:20px;" class="col-xs-4 controls"> + <input type="submit" class="btn btn-primary form-control" id="submit" name="submit" + value="Register" /> + </div> + </div> + </fieldset> + </form> +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/request_error.html b/gn2/wqflask/templates/oauth2/request_error.html new file mode 100644 index 00000000..e6ed5fff --- /dev/null +++ b/gn2/wqflask/templates/oauth2/request_error.html @@ -0,0 +1,32 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%block title%}View User{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("error", user_privileges)}} + <h3>ERROR</h3> + + {{flash_me()}} + + <div class="container-fluid"> + + <div class="row"> + <dl> + <dt>Error code</dt> + <dd>{{response.status}}[{{response.status_code}}]</dd> + + <dt>URI</dt> + <dd>{{response.url}}</dd> + + <dt>Content Type</dt> + <dd>{{response.content_type or "-"}}</dd> + + <dt>{{response.content}}</dt> + <dd>{{response.content}}</dd> + </dl> + </div> + + </div> + +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/resources.html b/gn2/wqflask/templates/oauth2/resources.html new file mode 100644 index 00000000..c52043db --- /dev/null +++ b/gn2/wqflask/templates/oauth2/resources.html @@ -0,0 +1,58 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%block title%}View User{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("resources", user_privileges)}} + <h3>Resources</h3> + + {{flash_me()}} + + <div class="container-fluid"> + <div class="row"> + <a href="{{url_for('oauth2.resource.create_resource')}}" + class="btn btn-info" title="Create a new resource"> + Create New Resource + </a> + </div> + + <div class="row"> + <table class="table"> + <caption>Resources</caption> + <thead> + <tr> + <th>Name</th> + <th>Category</th> + </tr> + </thead> + <tbody> + {%for resource in resources %} + <tr> + <td> + <a href="{{url_for( + 'oauth2.resource.view_resource', + resource_id=resource.resource_id)}}" + title="View resource {{resource.resource_name}}"> + {{resource.resource_name}} + </a> + </td> + <td>{{resource.resource_category.resource_category_key}}</td> + </tr> + {%else%} + <tr> + <td colspan="3"> + <span class="glyphicon glyphicon-warning-sign"></span> + <span class="text-warning"> + The user has no access to any resource. + </span> + </td> + </tr> + {%endfor%} + </tbody> + </table> + </div> + + </div> + +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/role.html b/gn2/wqflask/templates/oauth2/role.html new file mode 100644 index 00000000..c33c93ee --- /dev/null +++ b/gn2/wqflask/templates/oauth2/role.html @@ -0,0 +1,56 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%block title%}View User{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("roles", user_privileges)}} + <h3>Role: {{role.role_name}}</h3> + + {{flash_me()}} + + <div class="container-fluid"> + <div class="row"> + <div class="panel panel-info"> + <div class="panel-heading"> + <strong>{{role.role_name}}</strong> + </div> + <div class="panel-body"> + <table class="table"> + <thead> + <tr><th>privilege id</th><th>description</th></tr> + </thead> + <tbody> + {%for privilege in role.privileges:%} + <tr> + <td>{{privilege.privilege_id}}</td> + <td>{{privilege.privilege_description}}</td> + </tr> + {%else%} + <tr> + <td> + <span class="glyphicon glyphicon-warning-sign text-warning"></span> + + </td> + <td> + <span class="text-warning">No privileges found for this role.</span> + </td> + </tr> + {%endfor%} + </tbody> + </table> + </div> + <div class="panel-footer"> + <p> + This role acts on the resource with ID: + <a href="{{url_for('oauth2.resource.view_resource', resource_id=resource_id)}}"> + {{resource_id}} + </a> + </p> + </div> + </div> + </div> + + </div> + +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/view-group-role.html b/gn2/wqflask/templates/oauth2/view-group-role.html new file mode 100644 index 00000000..5da023bf --- /dev/null +++ b/gn2/wqflask/templates/oauth2/view-group-role.html @@ -0,0 +1,102 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} +{%block title%}View User{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("roles", user_privileges)}} + <h3>View Group Role</h3> + + {{flash_me()}} + + <div class="container-fluid"> + <div class="row"> + <h3>Role Details</h3> + {%if group_role_error is defined%} + {{display_error("Group Role", group_role_error)}} + {%else%} + <table class="table"> + <caption>Details for '{{group_role.role.role_name}}' Role</caption> + <thead> + <tr> + <th>Privilege</th> + <th>Description</th> + <th>Action</th> + </tr> + </thead> + <tbody> + {%for privilege in group_role.role.privileges%} + <tr> + <td>{{privilege.privilege_id}}</td> + <td>{{privilege.privilege_description}}</td> + <td> + <form action="{{url_for( + 'oauth2.group.delete_privilege_from_role', + group_role_id=group_role.group_role_id)}}" + method="POST"> + <input type="hidden" name="privilege_id" + value="{{privilege.privilege_id}}" /> + <input type="submit" class="btn btn-danger" + value="Remove" + {%if not group_role.role.user_editable%} + disabled="disabled" + {%endif%} /> + </form> + </td> + </tr> + {%endfor%} + </tbody> + </table> + {%endif%} + </div> + + <div class="row"> + <h3>Other Privileges</h3> + <table class="table"> + <caption>Other Privileges not Assigned to this Role</caption> + <thead> + <tr> + <th>Privilege</th> + <th>Description</th> + <th>Action</th> + </tr> + </thead> + + <tbody> + {%for priv in group_privileges%} + <tr> + <td>{{priv.privilege_id}}</td> + <td>{{priv.privilege_description}}</td> + <td> + <form action="{{url_for( + 'oauth2.group.add_privilege_to_role', + group_role_id=group_role.group_role_id)}}" + method="POST"> + <input type="hidden" name="privilege_id" + value="{{priv.privilege_id}}" /> + <input type="submit" class="btn btn-warning" + value="Add to Role" + {%if not group_role.role.user_editable%} + disabled="disabled" + {%endif%} /> + </form> + </td> + </tr> + {%else%} + <tr> + <td colspan="3"> + <span class="glyphicon glyphicon-info-sign text-info"> + </span> + + <span class="text-info">All privileges assigned!</span> + </td> + </tr> + {%endfor%} + </tbody> + </table> + </div> + + </div> + +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/view-resource.html b/gn2/wqflask/templates/oauth2/view-resource.html new file mode 100644 index 00000000..275fcb24 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/view-resource.html @@ -0,0 +1,352 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} +{%block title%}View User{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("resources", user_privileges)}} + <h3>Resources</h3> + + {{flash_me()}} + + <div class="container-fluid"> + + {%if resource_error is defined %} + {{display_error("Resource", resource_error)}} + {%else%} + <div class="row"> + <h3>Resource Details</h3> + <table class="table"> + <caption>Resource: {{resource.resource_name}}</caption> + <thead> + <tr> + <th>Name</th> + <th>Category</th> + <th colspan="3" style="text-align: center;">Actions</th> + </tr> + </thead> + + <tbody> + <tr> + <td>{{resource.resource_name}}</td> + <td>{{resource.resource_category.resource_category_description}}</td> + <td> + <form method="POST" + action="{{url_for( + 'oauth2.resource.toggle_public', + resource_id=resource.resource_id)}}"> + + <div class="input-group"> + {%if resource.public%} + <input type="submit" value="Make Private" + class="btn btn-success" /> + {%else%} + <input type="submit" value="Make Public" + class="btn btn-danger" /> + {%endif%} + </div> + </form> + </td> + <td> + <a href="{{url_for( + 'oauth2.resource.edit_resource', + resource_id=resource.resource_id)}}" + title="Edit resource" + class="btn btn-warning">Edit</a> + </td> + <td> + <a href="{{url_for( + 'oauth2.resource.delete_resource', + resource_id=resource.resource_id)}}" + title="Edit resource" + class="btn btn-danger">Delete</a> + </td> + </tr> + </tbody> + </table> + </div> + + <div class="row"> + <h3>Resource Data</h3> + <table class="table"> + <caption>Resource Data</caption> + <thead> + <tr> + {%if resource.resource_category.resource_category_key == "phenotype"%} + <th>Trait</th> + <th>Description</th> + <th>Year</th> + {%endif%} + <th>Dataset Name</th> + <th>Full Name</th> + <th>Actions</th> + </tr> + </thead> + + <tbody> + {%for data_item in resource.resource_data:%} + <tr> + {%if resource.resource_category.resource_category_key == "phenotype"%} + <td> + <a href="/show_trait?trait_id={{data_item.PublishXRefId}}&dataset={{data_item.dataset_name}}" + title="Trait Data and Analysis for {{data_item.PublishXRefId}}" + target="_blank"> + {{data_item.PublishXRefId}} + </a> + </td> + <td>{{data_item.description}}</td> + <td> + {%if data_item.PubMed_ID%} + <a href="https://pubmed.ncbi.nlm.nih.gov/{{data_item.PubMed_ID}}/" + title="{{data_item.Title}}" target="_blank"> + {{data_item.Year}} + </a> + {%else%} + {{data_item.Year}} + {%endif%} + </td> + {%endif%} + <td> + <a href="https://gn1.genenetwork.org/webqtl/main.py?FormID=sharinginfo&GN_AccessionId={{data_item.accession_id}}&InfoPageName={{data_item.dataset_name}}" + title="Link to information on dataset '{{data_item.dataset_fullname}}'" + target="_blank"> + {{data_item.dataset_name}} + </a> + </td> + <td>{{data_item.dataset_fullname}}</td> + <td> + <form action="{{url_for('oauth2.resource.unlink_data_from_resource')}}" + method="POST"> + <input type="hidden" name="resource_id" + value="{{resource.resource_id}}" /> + <input type="hidden" name="data_link_id" + value="{{data_item.data_link_id}}" /> + <input type="submit" value="Unlink" class="btn btn-danger" /> + </form> + </td> + </tr> + {%else%} + <tr> + <td colspan="2"> + <span class="glyphicon glyphicon-info-sign text-danger"> + </span> + + <strong class="text-info">No linked data.</strong> + </td> + </tr> + {%endfor%} + </tbody> + </table> + <form action="{{url_for('oauth2.resource.view_resource', resource_id=resource.resource_id)}}" + method="GET" + style="width:100%;text-align:center;"> + <input type="hidden" name="page" value="{{page}}" /> + <input type="hidden" name="count_per_page" value="{{count_per_page}}" /> + + <input type="submit" name="submit" value="prev" class="btn btn-info" + {%if page == 1 %}disabled="disabled"{%endif%} /> + <input type="submit" name="submit" value="next" class="btn btn-info" + {%if resource.resource_data | length < count_per_page %} + disabled="disabled" + {%endif%} /> + </form> + </div> + + <div class="row"> + <h3>Unlinked Data</h3> + <table class="table"> + <caption>Link Data</caption> + <thead> + <tr> + {%if resource.resource_category.resource_category_key == "phenotype"%} + <th>Trait</th> + <th>Description</th> + <th>Year</th> + {%endif%} + <th>Dataset Name</th> + <th>Dataset FullName</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + {%if unlinked_error is defined%} + {{display_error("Unlinked Data Error", unlinked_error)}} + {%else%} + {%for data_item in unlinked_data:%} + <tr> + {%if resource.resource_category.resource_category_key == "phenotype"%} + <td> + <a href="/show_trait?trait_id={{data_item.PublishXRefId}}&dataset={{data_item.dataset_name}}" + title="Trait Data and Analysis for {{data_item.PublishXRefId}}" + target="_blank"> + {{data_item.PublishXRefId}} + </a> + </td> + <td>{{data_item.description}}</td> + <td> + {%if data_item.PubMed_ID%} + <a href="https://pubmed.ncbi.nlm.nih.gov/{{data_item.PubMed_ID}}/" + title="{{data_item.Title}}" target="_blank"> + {{data_item.Year}} + </a> + {%else%} + {{data_item.Year}} + {%endif%} + </td> + {%endif%} + <td> + <a href="https://gn1.genenetwork.org/webqtl/main.py?FormID=sharinginfo&GN_AccessionId={{data_item.accession_id}}&InfoPageName={{data_item.dataset_name}}" + title="Dataset Group: {{data_item.dataset_name}}" + target="_blank"> + {{data_item.dataset_name}} + </a> + </td> + <td>{{data_item.dataset_fullname}}</td> + <td> + <form method="POST" + action="{{url_for('oauth2.resource.link_data_to_resource')}}"> + <input type="hidden" name="resource_id" + value="{{resource.resource_id}}" /> + <input type="hidden" name="data_link_id" + value="{{data_item.data_link_id}}" /> + <input type="hidden" name="dataset_type" + value="{{resource.resource_category.resource_category_key | lower}}" /> + <input type="submit" value="Link" class="btn btn-info" + {%if resource.resource_category.resource_category_description == "mRNA Dataset" and resource.resource_data | count != 0%} + disabled="disabled" + {%endif%} /> + </form> + </td> + </tr> + {%else%} + <span class="glyphicon glyphicon-info-sign text-info"> + </span> + + <strong class="text-info">No data to link.</strong> + {%endfor%} + {%endif%} + </tbody> + </table> + </div> + + <div class="row"> + <h3>User Roles</h3> + {%if users_n_roles_error is defined%} + {{display_error("Users and Roles", users_n_roles_error)}} + {%else%} + <table class="table"> + <caption>User Roles</caption> + <thead> + <tr> + <th>User Email</th> + <th>User Name</th> + <th>User Group</th> + <th colspan="2">Assigned Roles</th> + </tr> + </thead> + <tbody> + {%for user_row in users_n_roles%} + <tr> + <td rowspan="{{user_row.roles | length + 1}}">{{user_row.user.email}}</td> + <td rowspan="{{user_row.roles | length + 1}}">{{user_row.user.name}}</td> + <td rowspan="{{user_row.roles | length + 1}}"> + {{user_row.user_group.group_name}}</td> + <th>Role</th> + <th>Action</th> + </tr> + {%for grole in user_row.roles%} + <tr> + <td> + <a href="{{url_for( + 'oauth2.role.role', + role_id=grole.role_id)}}" + title="Details for '{{grole.role_name}}' role"> + {{grole.role_name}} + </a> + </td> + <td> + <form action="{{url_for('oauth2.resource.unassign_role', + resource_id=resource.resource_id)}}" + method="POST"> + <input type="hidden" name="user_id" + value="{{user_row.user.user_id}}" /> + <input type="hidden" name="group_role_id" + value="{{grole.group_role_id}}"> + <input type="submit" + value="Unassign" + class="btn btn-danger" + {%if user_row.user.user_id==this_user.user_id%} + disabled="disabled" + {%endif%}> + </form> + </td> + </tr> + {%endfor%} + {%else%} + <tr> + <td colspan="5"> + <span class="glyphicon glyphicon-info-sign text-info"> + </span> + + <span class="text-info"> + There are no users assigned any role for this resource. + </span> + </td> + </tr> + {%endfor%} + </tbody> + </table> + {%endif%} + </div> + + <div class="row"> + <h3>Assign</h3> + {%if group_roles_error is defined%} + {{display_error("Group Roles", group_roles_error)}} + {%elif users_error is defined%} + {{display_error("Users", users_error)}} + {%else%} + <form action="{{url_for( + 'oauth2.resource.assign_role', + resource_id=resource.resource_id)}}" + method="POST" autocomplete="off"> + <input type="hidden" name="resource_id" value="{{resource_id}}" /> + <div class="form-group"> + <label for="group_role_id" class="form-label">Role</label> + <select class="form-control" name="group_role_id" + id="group_role_id" required="required"> + <option value="">Select role</option> + {%for grole in group_roles%} + <option value="{{grole.group_role_id}}"> + {{grole.role.role_name}} + </option> + {%endfor%} + </select> + </div> + <div class="form-group"> + <label for="user-email" class="form-label">User Email</label> + <input list="users-list" name="user_email" class="form-control" + {%if users | length == 0%} + disabled="disabled" + {%endif%} + required="required" /> + <datalist id="users-list"> + {%for user in users%} + <option value="{{user.email}}">{{user.email}} - {{user.name}}</option> + {%endfor%} + </datalist> + </div> + + <input type="submit" class="btn btn-primary" value="Assign" + {%if users | length == 0%} + disabled="disabled" + {%endif%} /> + </form> + {%endif%} + </div> + {%endif%} + + </div> + +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/view-user.html b/gn2/wqflask/templates/oauth2/view-user.html new file mode 100644 index 00000000..34526b14 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/view-user.html @@ -0,0 +1,48 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%block title%}View User{%endblock%} +{%block content%} +<div class="container" style="min-width: 1250px;"> + {{profile_nav("dashboard", user_privileges)}} + <h3>View User</h3> + + {{flash_me()}} + + <div class="container-fluid"> + <div class="row"> + {%if user_details%} + <p><strong>Name</strong>: {{user_details.name}}</p> + <p><strong>E-Mail</strong>: {{user_details.email}}</p> + {%if user_details.group%} + <p><strong>Group</strong>:{{user_details.group.group_name}}</p> + {%else%} + <p> + <span class="glyphicon glyphicon-warning-sign text-warning"></span> + + <span class="text-warning">User is not a member of a group.</span> + </p> + + {%if group_join_request is defined and group_join_request.exists %} + <p> + <span class="glyphicon glyphicon-info-sign text-warning"></span> + + <span class="text-info">You have an active join request to a group.</span> + </p> + {%else%} + <p><a href="{{url_for('oauth2.group.join_or_create')}}" + class="btn btn-primary" + title="Join an existing group, or create your own group"> + Join or Create group + </a></p> + {%endif%} + + {%endif%} + {%else%} + <p class="text-warning">No details found.</p> + {%endif%} + </div> + + </div> + +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/pair_scan_results.html b/gn2/wqflask/templates/pair_scan_results.html new file mode 100644 index 00000000..dbd90bc7 --- /dev/null +++ b/gn2/wqflask/templates/pair_scan_results.html @@ -0,0 +1,114 @@ +{% extends "base.html" %} +{% block title %}Pair Scan{% endblock %} +{% block css %} +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='d3-tip/d3-tip.css') }}" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/d3panels.min.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/pair_scan.css" /> +{% endblock %} + +{% block content %} <!-- Start of body --> + +{{ header("Mapping", + '{}: {}'.format(this_trait.name, this_trait.description_fmt)) }} + +<div id="main_div" class="container"> + <div> + <h2> + Pair Scan + </h2> + </div> + <div class="qtlcharts" id="chart_container"> + <div id="pairscan_chart"></div> + </div> + <div class="pairscan-container"> + <h2> + Results + </h2> + <table cellpadding="0" cellspacing="0" border="0" id="pair_scan_results" class="table table-hover table-striped table-bordered"> + <thead> + <tr> + <th colspan="3">Interval 1</th> + <th rowspan="3">LOD</th> + <th colspan="3">Interval 2</th> + </tr> + <tr> + <th rowspan="2">Position</th> + <th colspan="2">Flanking Markers</th> + <th rowspan="2">Position</th> + <th colspan="2">Flanking Markers</th> + </tr> + <tr> + <th>Proximal</th> + <th>Distal</th> + <th>Proximal</th> + <th>Distal</th> + </tr> + </thead> + <tbody> + {% for row in table_data %} + <tr> + <td>{{ row.pos1 }}</td> + <td>{{ row.proximal1 }}</td> + <td>{{ row.distal1 }}</td> + <td>{{ row.lod }}</td> + <td>{{ row.pos2 }}</td> + <td>{{ row.proximal2 }}</td> + <td>{{ row.distal2 }}</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + +{% endblock %} + +{% block js %} + +<script> + var figure_data = {{ figure_data | safe }} +</script> + +<script src="https://d3js.org/d3.v7.min.js"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/scientific.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script> +<script language="javascript" type="text/javascript" src="/static/new/javascript/d3panels.min.js"></script> + +<script type="text/javascript"> + +var data, mychart; + +mychart = d3panels.lod2dheatmap({ + equalCells: true +}); + +mychart(d3.select('div#pairscan_chart'), figure_data); + +table_conf = { + "columns":[ + { "width": "165px" }, + { "width": "130px" }, + { "width": "130px" }, + { "width": "50px" }, + { "width": "165px" }, + { "width": "130px" }, + { "width": "130px" }, + ], + "sDom": "itir", + "autoWidth": false, + "bSortClasses": false, + "order": [[3, "desc" ]], + "scrollY": "100vh", + "scroller": true, + "scrollCollapse": true + } + +trait_table = $('#pair_scan_results').DataTable(table_conf); + +</script> + +{% endblock %} diff --git a/gn2/wqflask/templates/partial_correlations/pcorrs_error.html b/gn2/wqflask/templates/partial_correlations/pcorrs_error.html new file mode 100644 index 00000000..8d6c4bbe --- /dev/null +++ b/gn2/wqflask/templates/partial_correlations/pcorrs_error.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} +{% block title %}Error: {{message}}{% endblock %} +{% block content %} +<!-- Start of body --> + +<div class="container"> + <div class="col-md-8"> + <div class="form-group has-error"> + <div class="control-label" for="inputError1"> + + <img src="/static/gif/error/{{ error_image }}"> + + <h1>ERROR</h1> + + <p> + This error is not what we wanted to see. Unfortunately errors + are part of all software systems and we need to resolve this + together. + </p> + <p> + <b>It is important to report this ERROR so we can fix it for everyone</b>. + </p> + + <p> + Report to the GeneNetwork team by recording the steps you take + to reproduce this ERROR. Next to those steps, copy-paste below + stack trace, either as + a <a href="https://github.com/genenetwork/genenetwork2/issues/new">new + issue</a> or E-mail this full page to one of the developers + directly. + </p> + </div> + + <p> + GeneNetwork error:<br /> + {{message}} + </p> + + {%if command_id %} + <p> + Please provide the following information to help with + troubleshooting:<br /> + <strong>Command ID</strong>: <em>{{command_id}}</em> + </p> + {%endif%} + + <p> + To check if this already a known issue, search the + <a href="https://github.com/genenetwork/genenetwork2/issues">issue + tracker</a>. + </p> + + <a href="#Stack" class="btn btn-default" data-toggle="collapse">Toggle full stack trace</a> + <div id="Stack" class="collapse"> + <pre> + GeneNetwork {{ version }} {% for line in stack %} {{ line }} + {% endfor %} + </pre> + </div> + </div> + </div> +</div> + + +{% endblock %} diff --git a/gn2/wqflask/templates/partial_correlations/pcorrs_poll_results.html b/gn2/wqflask/templates/partial_correlations/pcorrs_poll_results.html new file mode 100644 index 00000000..38577c32 --- /dev/null +++ b/gn2/wqflask/templates/partial_correlations/pcorrs_poll_results.html @@ -0,0 +1,19 @@ +{%extends "base.html"%} + +{%block title%}Partial Correlations:{%endblock%} + +{%block css%} +<meta http-equiv="refresh" + content="5;URL=/partial_correlations/{{command_id}}"> +{%endblock%} + +{%block content%} + +<div class="container"> + <center> + <h1>Computing partial correlations...</h1> + <img src="/static/gif/waitAnima2.gif" + alt="Image indicating computation of partial correlations is ongoing" /> + </center> +</div> +{%endblock%} diff --git a/gn2/wqflask/templates/partial_correlations/pcorrs_results_presentation.html b/gn2/wqflask/templates/partial_correlations/pcorrs_results_presentation.html new file mode 100644 index 00000000..dac02397 --- /dev/null +++ b/gn2/wqflask/templates/partial_correlations/pcorrs_results_presentation.html @@ -0,0 +1,261 @@ +{%extends "base.html"%} + +{%block title%}Partial Correlations:{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" href="/static/new/css/partial_correlations.css" /> +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/trait_list.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +{%endblock%} + +{%block content%} +<div class="container"> + <p> + <strong>Primary Trait</strong><br /><br /> + <a href="{{url_for( + 'show_trait_page', + trait_id=primary['trait_name'], + dataset=primary['dataset_name'])}}" + title="Link to trait data for trait {{primary['trait_name']}}"> + {{primary["dataset_type"]}}/{{primary["trait_name"]}} + [{{primary["symbol"] }} on Chr {{primary["chr"]}} @ {{primary["mb"]}}]: + {{primary["description"]}} + </a> --- FROM: {{primary["dataset_name"]}} + </p> + <p><strong>Control Traits</strong><br /><br /> + {%for trait in controls:%} + <a href="{{url_for( + 'show_trait_page', + trait_id=trait['trait_name'], + dataset=trait['dataset_name'])}}" + title="Link to trait data for trait {{trait['trait_name']}}"> + {{trait["dataset_type"]}}/{{trait["trait_name"]}} + [{{trait["symbol"] }} on Chr {{trait["chr"]}} @ {{trait["mb"]}}]: + {{trait["description"]}} + </a> --- FROM: {{trait["dataset_name"]}}<br /> + {%endfor%} + </p> + + <div id="partial-correlation-results"> + {%if dataset_type == "Publish":%} + <table id="part-corr-results-publish" + class="table-hover table-striped cell-border dataTable" + style="float: left;"> + <thead> + <tr> + <th> + </th> + <th>Index</th> + <th>Record</th> + <th>Phenotype</th> + <th>Authors</th> + <th>Year</th> + <th>N</th> + <th>Partial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}</th> + <th>p(partial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})</th> + <th>{%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}</th> + <th>p({%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})</th> + <th>delta {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}</th> + </tr> + </thead> + + <tbody> + {%for idx, trait in enumerate(correlations, start=1):%} + <tr class="results-row"> + <td> + <input type="checkbox" name="chk_{{trait['trait_name']}}" + value="{{trait['trait_fullname']}}" /> + </td> + <td data-column-heading="Index">{{idx}}</td> + <td data-column-heading="Record"> + <a href="{{url_for( + 'show_trait_page', + trait_id=trait['trait_name'], + dataset=trait['dataset_name'])}}" + title="Link to trait data for trait {{trait['trait_name']}}"> + {{trait["trait_name"]}} + </a> + </td> + <td data-column-heading="Phenotype"> + {{trait["post_publication_description"]}}</td> + <td data-column-heading="Authors">{{trait["authors"]}}</td> + <td data-column-heading="Year">{{trait["year"]}}</td> + <td data-column-heading="N">{{trait["noverlap"]}}</td> + <td data-column-heading="Partial {%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%}"> + {{format_number(trait.get("partial_corr"))}} + </td> + <td data-column-heading="p(partial {%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%})"> + {{format_number(trait.get("partial_corr_p_value"))}} + </td> + <td data-column-heading="{%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%}"> + {{format_number(trait.get("corr"))}} + </td> + <td data-column-heading="p({%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%})"> + {{format_number(trait.get("corr_p_value"))}} + </td> + <td data-column-heading="delta {%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%}"> + {{format_number(trait.get("delta"))}} + </td> + </tr> + {%endfor%} + </tbody> + </table> + {%endif%} + + {%if dataset_type == "Geno":%} + <table id="part-corr-results-geno" + class="table-hover table-striped cell-border dataTable" + style="float: left;"> + <thead> + <tr> + <th></th> + <th>Index</th> + <th>Locus</th> + <th>Chr</th> + <th>Megabase</th> + <th>N</th> + <th>Partial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}</th> + <th>p(partial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})</th> + <th>{%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}</th> + <th>p({%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})</th> + <th>delta {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}</th> + </tr> + </thead> + + <tbody> + {%for idx, trait in enumerate(correlations, start=1):%} + <tr class="results-row"> + <td> + <input type="checkbox" name="chk_{{trait['trait_name']}}" + value="{{trait['trait_fullname']}}" /> + </td> + <td data-column-heading="Index">{{idx}}</td> + <td data-column-heading="Locus"> + <a href="{{url_for( + 'show_trait_page', + trait_id=trait['trait_name'], + dataset=trait['dataset_name'])}}" + title="Link to trait data for trait {{trait['trait_name']}}"> + {{trait["trait_name"]}} + </a> + </td> + <td data-column-heading="Chr">{{trait["chr"]}}</td> + <td data-column-heading="Megabase">{{trait["mb"]}}</td> + <td data-column-heading="N">{{trait["noverlap"]}}</td> + <td data-column-heading="Partial {%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%}"> + {{format_number(trait.get("partial_corr"))}} + </td> + <td data-column-heading="p(partial {%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%})"> + {{format_number(trait.get("partial_corr_p_value"))}} + </td> + <td data-column-heading="{%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%}"> + {{format_number(trait.get("corr"))}} + </td> + <td data-column-heading="p({%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%})"> + {{format_number(trait.get("corr_p_value"))}} + </td> + <td data-column-heading="delta {%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%}"> + {{format_number(trait.get("delta"))}} + </td> + </tr> + {%endfor%} + </tbody> + </table> + {%endif%} + + {%if dataset_type == "ProbeSet":%} + <table id="part-corr-results-probeset" + class="table-hover table-striped cell-border dataTable" + style="float: left;"> + <thead> + <tr> + <th></th> + <th>Index</th> + <th>Record</th> + <th>Gene ID</th> + <th>Homologene ID</th> + <th>Symbol</th> + <th>Description</th> + <th>Chr</th> + <th>Megabase</th> + <th>Mean Expr</th> + <th>N</th> + <th>Sample Partial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}</th> + <th>Sample p(partial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})</th> + <th>Sample {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}</th> + <th>Sample p({%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})</th> + <th>delta {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}</th> + <th>Lit Corr</th> + <th>Tissue {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}</th> + <th>Tissue p({%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})</th> + </tr> + </thead> + + <tbody> + {%for idx, trait in enumerate(correlations, start=1):%} + <tr class="results-row"> + <td> + <input type="checkbox" name="chk_{{trait['trait_name']}}" + value="{{trait['trait_fullname']}}" /> + </td> + <td data-column-heading="Index">{{idx}}</td> + <td data-column-heading="Record"> + <a href="{{url_for( + 'show_trait_page', + trait_id=trait['trait_name'], + dataset=trait['dataset_name'])}}" + title="Link to trait data for trait {{trait['trait_name']}}"> + {{trait["trait_name"]}} + </a> + </td> + <td data-column-heading="Gene ID">{{trait["geneid"]}}</td> + <td data-column-heading="Homologene ID">{{trait["homologeneid"]}}</td> + <td data-column-heading="Symbol">{{trait["symbol"]}}</td> + <td data-column-heading="Description">{{trait["description"]}}</td> + <td data-column-heading="Chr">{{trait["chr"]}}</td> + <td data-column-heading="Megabase">{{trait["mb"]}}</td> + <td data-column-heading="Mean Expr">{{trait["mean_expr"]}}</td> + <td data-column-heading="N">{{trait["noverlap"]}}</td> + <td data-column-heading="Sample Partial {%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%}"> + {{format_number(trait.get("partial_corr"))}} + </td> + <td data-column-heading="Sample p(partial {%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%})"> + {{format_number(trait.get("partial_corr_p_value"))}} + </td> + <td data-column-heading="Sample {%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%}"> + {{format_number(trait.get("corr"))}} + </td> + <td data-column-heading="Sample p({%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%})"> + {{format_number(trait.get("corr_p_value"))}} + </td> + <td data-column-heading="delta {%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%}"> + {{format_number(trait.get("delta"))}} + </td> + <td data-column-heading="Lit Corr"> + {{format_number(trait.get("l_corr"))}} + </td> + <td data-column-heading="Tissue {%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%}"> + {{format_number(trait.get("tissue_corr"))}} + </td> + <td data-column-heading="Tissue p({%if 'spearman' in (method | lower):%}rho{%else:%}r{%endif%})"> + {{format_number(trait.get("tissue_p_value"))}} + </td> + </tr> + {%endfor%} + </tbody> + </table> + {%endif%} + + </div> +</div> +{%endblock%} + +{%block js%} +{%if step == "select-corr-method":%} +<script type="text/javascript" + src="/static/new/javascript/partial_correlations.js"></script> +<script language="javascript" type="text/javascript" + src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> +{%endif%} +{%endblock%} diff --git a/gn2/wqflask/templates/partial_correlations/pcorrs_results_with_target_traits.html b/gn2/wqflask/templates/partial_correlations/pcorrs_results_with_target_traits.html new file mode 100644 index 00000000..c1ef6001 --- /dev/null +++ b/gn2/wqflask/templates/partial_correlations/pcorrs_results_with_target_traits.html @@ -0,0 +1,115 @@ +{%extends "base.html"%} + +{%block title%}Partial Correlations:{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" + href="{{url_for('css', filename='DataTables/css/jquery.dataTables.css')}}" /> +<link rel="stylesheet" type="text/css" + href="{{url_for('js', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css')}}"> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/trait_list.css" /> +<link rel="stylesheet" type="text/css" + href="/static/new/css/partial_correlations.css" /> +{%endblock%} + +{%block content%} +<div class="container"> + <p> + <strong>Primary Trait</strong><br /><br /> + <a href="{{url_for( + 'show_trait_page', + trait_id=primary['trait_name'], + dataset=primary['dataset_name'])}}" + title="Link to trait data for trait {{primary['trait_name']}}"> + {{primary["dataset_type"]}}/{{primary["trait_name"]}} + [{{primary["symbol"] }} on Chr {{primary["chr"]}} @ {{primary["mb"]}}]: + {{primary["description"]}} + </a> --- FROM: {{primary["dataset_name"]}} + </p> + <p><strong>Control Traits</strong><br /><br /> + {%for trait in controls:%} + <a href="{{url_for( + 'show_trait_page', + trait_id=trait['trait_name'], + dataset=trait['dataset_name'])}}" + title="Link to trait data for trait {{trait['trait_name']}}"> + {{trait["dataset_type"]}}/{{trait["trait_name"]}} + [{{trait["symbol"] }} on Chr {{trait["chr"]}} @ {{trait["mb"]}}]: + {{trait["description"]}} + </a> --- FROM: {{trait["dataset_name"]}}<br /> + {%endfor%} + </p> + + <table id="part-corr-results-publish" + class="table-hover table-striped cell-border dataTable"> + <thead> + <tr> + <th>_</th> + <th>Index</th> + <th>Database</th> + <th>Record</th> + <th>Symbol</th> + <th>Description</th> + <th>N</th> + {%if method == "spearmans":%} + <th>Partial rho</th> + <th>p(partial rho)</th> + <th>rho</th> + <th>p(rho)</th> + <th>delta rho</th> + {%else:%} + <th>Partial r</th> + <th>p(partial r)</th> + <th>r</th> + <th>p(r)</th> + <th>delta r</th> + {%endif%} + </tr> + </thead> + + <tbody> + {%for idx, trait in enumerate(pcorrs, start=1):%} + <tr> + <td> + <input type="checkbox" name="chk_{{trait['trait_name']}}" + value="{{trait['trait_fullname']}}"> + </td> + <td>{{idx}}</td> + <td>{{trait["dataset_name"]}}</td> + <td> + <a href="{{url_for( + 'show_trait_page', + trait_id=trait['trait_name'], + dataset=trait['dataset_name'])}}"> + {{trait["trait_name"]}} + </a> + </td> + <td>{{trait["symbol"]}}</td> + <td>{{trait["description"]}}</td> + <td>{{trait["noverlap"]}}</td> + <td>{{format_number(trait["partial_corr"])}}</td> + <td>{{format_number(trait["partial_corr_p_value"])}}</td> + <td>{{format_number(trait["corr"])}}</td> + <td>{{format_number(trait["corr_p_value"])}}</td> + <td>{{format_number(trait["delta"])}}</td> + </tr> + {%else:%} + <tr> + <td colspan="12"> + No correlations were computed + </td> + </tr> + {%endfor%} + </tbody> + </table> + +</div> +{%endblock%} + +{%block js%} +<!-- + <script type="text/javascript" + src="/static/new/javascript/partial_correlations.js"></script> +--> +{%endblock%} diff --git a/gn2/wqflask/templates/partial_correlations/pcorrs_select_operations.html b/gn2/wqflask/templates/partial_correlations/pcorrs_select_operations.html new file mode 100644 index 00000000..fe7f8cd4 --- /dev/null +++ b/gn2/wqflask/templates/partial_correlations/pcorrs_select_operations.html @@ -0,0 +1,167 @@ +{%extends "base.html"%} + +{%block title%}Partial Correlations:{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" + href="{{url_for('css', filename='DataTables/css/jquery.dataTables.css')}}" /> +<link rel="stylesheet" type="text/css" + href="{{url_for('js', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css')}}"> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/trait_list.css" /> +<link rel="stylesheet" type="text/css" + href="/static/new/css/partial_correlations.css" /> +{%endblock%} + +{%block content%} +<div class="container"> + <form id="pcorrs-form" + method="POST" + action="{{url_for('partial_correlations')}}"> + {%with messages = get_flashed_messages(with_categories=true)%} + {%if messages:%} + <ul class=flashes> + {%for category, message in messages:%} + <li class="{{category}}">{{message}}</li> + {%endfor%} + </ul> + {%endif%} + {%endwith%} + + <input type="hidden" value="{{trait_list_str}}" name="trait_list"> + <h1>Partial Correlation</h1> + <div>Please select one primary trait, one to three control traits, and at least one target trait.</div> + <br /> + <table id="pcorrs_traits_table" + class="table-hover table-striped cell-border dataTable" + role="grid"> + <thead> + <tr> + <th>Primary (X)</th> + <th>Controls (Z)</th> + <th>Targets (Y)</th> + <th>Ignored</th> + <th>Dataset</th> + <th>Trait ID</th> + <th>Symbol</th> + <th>Description</th> + <th>Location</th> + <th>Mean</th> + <th>Max LRS</th> + <th>Max LRS Location Chr and Mb</th> + </tr> + </thead> + + <tbody> + {%for trait in traits:%} + <tr> + <td> + <input type="radio" name="trait_{{trait['trait_name']}}" + id="trait_{{trait['trait_name']}}" + value="primary_{{trait['trait_name']}}" /> + </td> + <td> + <input type="radio" name="trait_{{trait['trait_name']}}" + id="trait_{{trait['trait_name']}}" + value="controls_{{trait['trait_name']}}" /> + </td> + <td> + <input type="radio" name="trait_{{trait['trait_name']}}" + id="trait_{{trait['trait_name']}}" + value="targets_{{trait['trait_name']}}" checked="checked" /> + </td> + <td> + <input type="radio" name="trait_{{trait['trait_name']}}" + id="trait_{{trait['trait_name']}}" + value="ignored_{{trait['trait_name']}}" /> + </td> + <td>{{trait.get("dataset", "_")}} + <td>{{trait.get("trait_name", "_")}}</td> + <td>{{trait.get("symbol", "_")}}</td> + <td>{{trait.get("description", "_")}}</td> + <td>{{trait.get("location", "_")}}</td> + <td>{{trait.get("mean", "_")}}</td> + <td>{{trait.get("lrs", "_")}}</td> + <td>{{trait.get("lrs_location", "_")}}</td> + </tr> + {%endfor%} + </tbody> + </table> + + <br /> + <p>Compute partial correlations for target selected above:</p> + <button type="submit" class="btn btn-primary" name="submit" + value="with_target_pearsons"> + Pearson's r + </button> + <button type="submit" class="btn btn-primary" name="submit" + value="with_target_spearmans"> + Spearman's rho + </button> + + <hr /> + + <p style="color: red; font-weight: bold;">OR</p> + <p>Compute partial correlation for each trait in the database below:</p> + + <div class="form-group"> + <label for="corr-method-input" class="form-label">Compute</label> + <select id="corr-method-input" required="required" name="method" + class="form-control"> + <option value="Pearson's r">Pearson's r</option> + <option value="Spearman's rho">Spearman's rho</option> + </select> + </div> + + <div class="form-group"> + <label for="target-db-input" class="form-label">Choose Database</label> + <select id="target-db-input" required="required" name="target_db" + class="form-control"> + {%if target_dbs:%} + {%for item in target_dbs:%} + {%if "description" in item.keys():%} + <option value="{{item['value']}}">{{item['description']}}</option> + {%else:%} + {%for group, opts in item.items()%} + {%if opts | length > 0:%} + <optgroup label="{{group}} ------"> + {%for item2 in opts:%} + <option value="{{item2['value']}}">{{item2['description']}}</option> + {%endfor%} + </optgroup> + {%endif%} + {%endfor%} + {%endif%} + {%endfor%} + {%endif%} + </select> + </div> + + <div class="form-group"> + <label for="criteria-input" class="form-label">Return</label> + <select id="criteria-input" required="required" name="criteria" size="1" + class="form-control"> + <option value="100">top 100</option> + <option value="200">top 200</option> + <option value="500" selected="selected">top 500</option> + <option value="1000">top 1000</option> + <option value="2000">top 2000</option> + <option value="5000">top 5000</option> + <option value="10000">top 10000</option> + <option value="15000">top 15000</option> + <option value="20000">top 20000</option> + </select> + </div> + + <button type="submit" class="btn btn-primary" name="submit" + value="Run Partial Correlations"> + Run Partial Correlations + </button> + </form> +</div> +{%endblock%} + +{%block js%} +<script type="text/javascript" + src="/static/new/javascript/partial_correlations.js"></script> +{%endblock%} diff --git a/gn2/wqflask/templates/pca_scree_plot.html b/gn2/wqflask/templates/pca_scree_plot.html new file mode 100644 index 00000000..74eb2c15 --- /dev/null +++ b/gn2/wqflask/templates/pca_scree_plot.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title></title> +</head> + +<body> + <div> + <h2>Scree Plot</h2> + <div style="padding-bottom: 10px;">Review more on <a href="https://en.wikipedia.org/wiki/Scree_plot">scree plots</a>.</div> + <div id="scree_plot" style="width:700px;height:600px;"></div> + </div> +</body> +<script type="text/javascript" src="{{ url_for('js', filename='plotly/plotly.min.js') }}"></script> +<script type="text/javascript"> +js_data = {{ js_data | safe }} + +let { x_coord, y_coord } = js_data["scree_data"] + + +const layout = { + yaxis: { + title: { + text: "% of Variance", + font: { + "size": 18, + "color": "" + + } + } + }, + + xaxis: { + title: { + text: "Principal Components", + font: { + "size": 18, + "color": "" + + } + } + }, + +} + +const data = [{ + x: x_coord, + y: y_coord, + marker: { + + color: 'rgb(17, 157, 255)', + size: 5, + line: { + color: 'rgb(255, 0, 0)', + width: 3 + } + + } +}] + + +let custom_configs = (filename, download_format, modebar = true) => { + + return { + displayModeBar: modebar, + scrollZoom: false, + toImageButtonOptions: { + filename, + format:download_format, + height: 600, + width: 700, + scale: 1 + } + } + +} + +Plotly.newPlot(document.getElementById("scree_plot"), data, layout, + custom_configs(file_name = "scree_plot", download_format = "svg")); +</script> + +</html> diff --git a/gn2/wqflask/templates/phenotype.html b/gn2/wqflask/templates/phenotype.html new file mode 100644 index 00000000..4f4fba6e --- /dev/null +++ b/gn2/wqflask/templates/phenotype.html @@ -0,0 +1,136 @@ +{% extends "base.html" %} + +{% block css %} +<style type="text/css"> + .page-header { + padding: 1em; + } +</style> +{% endblock %} + +{% block title %}Phenotype: {{ name }}{% endblock %} + +{% block content %} + +{% set published_p = "http://rdf.ncbi.nlm.nih.gov/pubmed" in metadata.references.id %} + +<h1 class="page-header"> + Phenotype: {{ metadata.traitName }} ({{ metadata.abbreviation }}) +</h1> + +<div class="container"> + <table class="table"> + <tr> + <td><b>Species</b></td> + <td>{{ metadata.species or "N/A" }}</td> + </tr> + <tr> + <td><b>Group</b></td> + <td>{{ metadata.group or "N/A" }}</td> + </tr> + + <tr> + <td><b>Phenotype</b></td> + <td>{{ metadata.description or "N/A"}}</td> + </tr> + + {% if metadata.creator %} + <tr> + <td><b>Authors</b></td> + <td> + {% if metadata.creator is iterable %} + {{ metadata.creator |join(", ") }} + {% for creator in metadata.creator %} + {{ creator }} + {% endfor %} + {% else %} + metadata.creator + {% endif %} + {% endif %} + </td> + </tr> + {% if metadata.references.id %} + <tr> + <td> + <b>Publication</b> + {% if published_p == False %} + <sup><small>(unpublished)</small></sup> + {% endif %} + </td> + <td> + <i> + {% if metadata.references.title %} + {{ metadata.references.title }}. + {% endif %} + </i> + {% if metadata.references.creator %} + {{ ', '.join(metadata.references.creator) }}. + {% endif %} + {{ metadata.references.year }} + {{ metadata.references.month }} + {% if metadata.references.volume and metadata.references.page %} + {{ metadata.references.volume }}:{{ metadata.references.page }} + {% endif %} + + <sup> + <a href="{{ metadata.references.id }}" target="_blank"><small> + {% if published_p %} + PubMed + {% else %} + GN RDF Page + {% endif %} + <sup> <span class="glyphicon glyphicon-new-window"></span></sup></small></a> + </sup> + </td> + </tr> + {% endif %} + + <tr> + <td><b>Database</b></td> + <td> + {% for database in metadata.dataset %} + {% set dataset_url = url_for('get_dataset', name=database.identifier)%} + <a href="{{ dataset_url }}" target="blank">{{ database.prefLabel }}</a> <br/> + {% endfor %} + </td> + </tr> + + <tr> + <td><b>Mean</b></td> + <td>{{ metadata.mean or "N/A"}}</td> + </tr> + + <tr> + <td><b>Peak -logP</b></td> + <td>{{ metadata.lodScore or "N/A"}}</td> + </tr> + + <tr> + <td><b>Effect Size</b></td> + <td>{{ metadata.additive or "N/A"}}</td> + </tr> + {% if metadata.locus %} + <tr> + <td><b>Peak Location</b></td> + <td>Chr{{ metadata.locus.chromosome }}: {{ metadata.locus.mb }}</td> + </tr> + {% endif %} + {% if metadata.references.id %} + <tr> + <td><b>Resource Links</b></td> + <td> + <a href="{{ metadata.references.id }}" target="_blank"> + {% if published_p %} + PubMed + {% else %} + GN RDF Page + {% endif %} + </a> + </td> + </tr> + {% endif %} + </table> +</div> + + +{% endblock %} diff --git a/gn2/wqflask/templates/policies.html b/gn2/wqflask/templates/policies.html new file mode 100644 index 00000000..e36c9e08 --- /dev/null +++ b/gn2/wqflask/templates/policies.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block title %}Links{% endblock %} + +{% block css %} +<link rel="stylesheet" type="text/css" href="/static/new/css/markdown.css" /> +{% endblock %} + +{% block content %} + + <div class="github-btn-container"> + <div class="github-btn "> + <a href="https://github.com/genenetwork/gn-docs/blob/master/general/policies/policies.md"> + Edit Text + <img src="/static/images/edit.png"> + </a> + </div> +</div> +<div id="markdown" class="container"> + {{ rendered_markdown|safe }} + +</div> +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/publication.html b/gn2/wqflask/templates/publication.html new file mode 100644 index 00000000..556d184f --- /dev/null +++ b/gn2/wqflask/templates/publication.html @@ -0,0 +1,62 @@ +{% extends "base.html" %} + +{% block css %} +<style type="text/css"> + .page-header { + text-underline-offset: 0.5rem; + padding: 1em; + } + + .panel-metadata { + display: inline-block; + width: fit-content; + height: fit-content; + padding: 0; + } + + .panel-metadata dt { + color: green; + } + + .panel-metadata dt::after { + content: ":"; + } + +</style> +{% endblock %} + +{% block title %}Title: {{metadata.title}}{% endblock %} + +{% block content %} + +<header class="page-header text-justify"> + <h1> + {% if metadata.title %} + <u><a href="{{ metadata.id }}">{{ metadata.title}}</a></u> + {% else %} + {{ name }} + {% endif %} + </h1> +</header> + +<div class="container"> + {% if metadata == {} %} + <p class="lead">We appreciate your interest, but unfortunately, we don't have any additional information available for: <strong>{{ name }}</strong>. If you have any other questions or need assistance with something else, please feel free to reach out to us.</p> + {% else %} + <div class="panel-about panel panel-info panel-metadata text-muted{{ float_p }}"> + <div class="panel-heading"><strong><span class="glyphicon glyphicon-info-sign aria-hidden=true"></span> Details</strong> </div> + <div class="panel-body"> + <dl class="dl-horizontal"> + <dt>Abstract</dt> <dd>{{ metadata.abstract or "N/A" }}</dd> + <dt>Journal</dt> <dd>{{ metadata.journal or "N/A" }}</dd> + <dt>Month</dt> <dd>{{ metadata.month or "N/A" }}</dd> + <dt>Page</dt> <dd>{{ metadata.pages or "N/A" }}</dd> + <dt>Year</dt> <dd>{{ metadata.year or "N/A" }}</dd> + </dl> + </div> + </div> + {% endif %} + +</div> + +{% endblock %} diff --git a/gn2/wqflask/templates/references.html b/gn2/wqflask/templates/references.html new file mode 100644 index 00000000..04e60361 --- /dev/null +++ b/gn2/wqflask/templates/references.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% block title %}Reference{% endblock %} +{% block css %} +<link rel="stylesheet" type="text/css" href="/static/new/css/markdown.css" /> +{% endblock %} +{% block content %} +<div class="github-btn-container"> + <div class="github-btn"> + <a href="https://github.com/genenetwork/gn-docs/blob/master/general/references/references.md"> + Edit Text + <img src="/static/images/edit.png"> + </a> + </div> +</div> +<div id="markdown" class="container"> + {{ rendered_markdown|safe }} +</div> + +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/search_autocomplete.html b/gn2/wqflask/templates/search_autocomplete.html new file mode 100644 index 00000000..b8d0514e --- /dev/null +++ b/gn2/wqflask/templates/search_autocomplete.html @@ -0,0 +1,249 @@ +<<!DOCTYPE html> + <html> + + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <title></title> + <link rel="stylesheet" href=""> + </head> + <style type="text/css"> + * { + box-sizing: border-box; + } + + body { + font: 16px Arial; + } + + .autocomplete { + /*the container must be positioned relative:*/ + position: relative; + display: inline-block; + } + + input { + border: 1px solid transparent; + background-color: #f1f1f1; + padding: 10px; + font-size: 16px; + } + + input[type=text] { + background-color: #f1f1f1; + width: 100%; + } + + input[type=submit] { + background-color: DodgerBlue; + color: #fff; + } + + + .search-item, + .autocomplete-items { + position: absolute; + border: 1px solid #d4d4d4; + border-bottom: none; + border-top: none; + z-index: 99; + /*position the autocomplete items to be the same width as the container:*/ + top: 100%; + left: 0; + right: 0; + } + + + .search-item div { + + background-color: red; + + } + + .autocomplete-items div { + padding: 10px; + cursor: pointer; + background-color: #fff; + border-bottom: 1px solid #d4d4d4; + } + + .autocomplete-items div:hover { + /*when hovering an item:*/ + background-color: #e9e9e9; + } + + .autocomplete-active { + /*when navigating through the items using the arrow keys:*/ + background-color: DodgerBlue !important; + color: #ffffff; + } + </style> + + <body> + <div> + <h2>hello there</h2> + <form autocomplete="off" action="/action_page.php"> + <div class="autocomplete" style="width:300px;"> + <input id="myInput" type="text" name="myCountry" placeholder="search for a country"> + </div> + <input type="submit"> + </form> + </div> + <script type="text/javascript"> + //replace with gn search hints + var trial_hints = ["Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Anguilla", "Antigua & Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia & Herzegovina", "Botswana", "Brazil", "British Virgin Islands", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central Arfrican Republic", "Chad", "Chile", "China", "Colombia", "Congo", "Cook Islands", "Costa Rica", "Cote D Ivoire", "Croatia", "Cuba", "Curacao", "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands", "Faroe Islands", "Fiji", "Finland", "France", "French Polynesia", "French West Indies", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guam", "Guatemala", "Guernsey"] + + + let x = document.getElementById("myInput"); + + x.addEventListener("focus", function() { + + //recentSearches(searches) + }); + + + function recentSearches(arr) { + + console.log("calling this funciton") + + let x = document.getElementById("myInput"); + + x.addEventListener("focus", function(e) { + + + a = document.createElement("DIV") + + a.setAttribute("id", "searchitem_x"); + + a.setAttribute("class", "autocomplete-items"); + + + + this.parentNode.appendChild(a) + + for (i = 0; i < arr.length; i++) { + + console.log(arr[i]) + b = document.createElement("DIV") + b.innerHTML = "<strong>" + arr[i] + "</strong>"; + b.innerHTML += "<input type='hidden' value='" + arr[i] + "'>"; + + b.addEventListener("click", function(e) { + x.value = b.innerHTML += "<input type='hidden' value='" + arr[i] + "'>"; + + }) + + a.appendChild(b) + + + + + } + }) + } + + function autocomplete(inp, arr) { + /*the autocomplete function takes two arguments, + the text field element and an array of possible autocompleted values:*/ + var currentFocus; + /*execute a function when someone writes in the text field:*/ + inp.addEventListener("input", function(e) { + var a, b, i, val = this.value; + /*close any already open lists of autocompleted values*/ + closeAllLists(); + if (!val) { return false; } + currentFocus = -1; + /*create a DIV element that will contain the items (values):*/ + a = document.createElement("DIV"); + a.setAttribute("id", this.id + "autocomplete-list"); + a.setAttribute("class", "autocomplete-items"); + /*append the DIV element as a child of the autocomplete container:*/ + this.parentNode.appendChild(a); + /*for each item in the array...*/ + for (i = 0; i < arr.length; i++) { + /*check if the item starts with the same letters as the text field value:*/ + if (arr[i].substr(0, val.length).toUpperCase() == val.toUpperCase()) { + /*create a DIV element for each matching element:*/ + b = document.createElement("DIV"); + /*make the matching letters bold:*/ + b.innerHTML = "<strong>" + arr[i].substr(0, val.length) + "</strong>"; + b.innerHTML += arr[i].substr(val.length); + /*insert a input field that will hold the current array item's value:*/ + b.innerHTML += "<input type='hidden' value='" + arr[i] + "'>"; + /*execute a function when someone clicks on the item value (DIV element):*/ + b.addEventListener("click", function(e) { + /*insert the value for the autocomplete text field:*/ + inp.value = this.getElementsByTagName("input")[0].value; + /*close the list of autocompleted values, + (or any other open lists of autocompleted values:*/ + closeAllLists(); + }); + a.appendChild(b); + } + } + }); + /*execute a function presses a key on the keyboard:*/ + inp.addEventListener("keydown", function(e) { + var x = document.getElementById(this.id + "autocomplete-list"); + if (x) x = x.getElementsByTagName("div"); + if (e.keyCode == 40) { + /*If the arrow DOWN key is pressed, + increase the currentFocus variable:*/ + currentFocus++; + /*and and make the current item more visible:*/ + addActive(x); + } else if (e.keyCode == 38) { //up + /*If the arrow UP key is pressed, + decrease the currentFocus variable:*/ + currentFocus--; + /*and and make the current item more visible:*/ + addActive(x); + } else if (e.keyCode == 13) { + /*If the ENTER key is pressed, prevent the form from being submitted,*/ + e.preventDefault(); + if (currentFocus > -1) { + /*and simulate a click on the "active" item:*/ + if (x) x[currentFocus].click(); + } + } + }); + + function addActive(x) { + /*a function to classify an item as "active":*/ + if (!x) return false; + /*start by removing the "active" class on all items:*/ + removeActive(x); + if (currentFocus >= x.length) currentFocus = 0; + if (currentFocus < 0) currentFocus = (x.length - 1); + /*add class "autocomplete-active":*/ + x[currentFocus].classList.add("autocomplete-active"); + } + + function removeActive(x) { + /*a function to remove the "active" class from all autocomplete items:*/ + for (var i = 0; i < x.length; i++) { + x[i].classList.remove("autocomplete-active"); + } + } + + function closeAllLists(elmnt) { + /*close all autocomplete lists in the document, + except the one passed as an argument:*/ + var x = document.getElementsByClassName("autocomplete-items"); + for (var i = 0; i < x.length; i++) { + if (elmnt != x[i] && elmnt != inp) { + x[i].parentNode.removeChild(x[i]); + } + } + } + /*execute a function when someone clicks in the document:*/ + document.addEventListener("click", function(e) { + closeAllLists(e.target); + }); + } + + autocomplete(document.getElementById("myInput"), trial_hints); + </script> + </body> + + </html>
\ No newline at end of file diff --git a/gn2/wqflask/templates/search_error.html b/gn2/wqflask/templates/search_error.html new file mode 100644 index 00000000..df8d9dff --- /dev/null +++ b/gn2/wqflask/templates/search_error.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} +{% block title %}Search Results{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> +{% endblock %} +{% block content %} +<!-- Start of body --> + {{ header("Error") }} + + <div class="container"> + <input type="hidden" name="uc_id" id="uc_id" value="{{ uc_id }}"> + <p>You entered at least one incorrect search command, or your search term was not applicable to the dataset type.</p> + <p><i>Some search terms may not be applicable to Phenotype or Genotype datasets (RIF, etc)</i></p> + </div> + + <div id="myModal"></div> + +<!-- End of body --> + +{% endblock %} diff --git a/gn2/wqflask/templates/search_history.html b/gn2/wqflask/templates/search_history.html new file mode 100644 index 00000000..11586c0a --- /dev/null +++ b/gn2/wqflask/templates/search_history.html @@ -0,0 +1,297 @@ +<<!DOCTYPE html> + <html> + + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <title></title> + <link rel="stylesheet" href=""> + </head> + <style type="text/css"> + .autocomplete { + /*the container must be positioned relative:*/ + position: relative; + display: inline-block; + + /*width: 30vw; */ + } + + .search_box { + + background: #Ececec; + + width: 50vw; + /*padding:15px 20px; */ + border-radius: 50%; + border: 1px solid grey; + /* text-align: center; */ + + font-size: 18px; + + flex-grow: 2; + border: none; + border-radius: 50px; + + height: 40px; + + padding: 2px 5px; + + padding-left: 40px; + + + + + } + + .search_box:focus { + border: 1px solid grey; + } + + + + .hint-items { + + position: absolute; + + border: 1px solid #d4d4d4; + + z-index: 99; + /*position the autocomplete items to be the same width as the container:*/ + top: 100%; + left: 0; + right: 0; + + background: white; + + padding: 10px; + + border-radius: 40px; + + padding: 10px 20px; + + /*border-bottom: none; */ + + + } + + .search_box_hint { + position: absolute; + + border: 1px solid #d4d4d4; + border-bottom: none; + border-top: none; + z-index: 99; + /*position the autocomplete items to be the same width as the container:*/ + top: 100%; + left: 0; + right: 0; + + background: red; + + padding: 10px; + + border-radius: 40px; + + + } + + + .hint_area { + height: px; + + width: 50vw; + + border-radius: 40px; + background: red; + } + + + + .search_box_hint_input { + display: block; + + color: white; + + width: 100%; + + padding: 10px 20px; + + /*border-radius:20px;*/ + border: none; + + color: #000; + + font-weight: bold; + + font-size: 18px; + + } + + .search_box_hint_input { + border: none; + background-color: transparent; + + text-align: inherit; + + color: #101691; + } + + .search_box_hint_input:focus, + .search_box_hint_input:active { + box-shadow: none !important; + -moz-box-shadow: none !important; + -webkit-box-shadow: none !important; + outline: none !important; + } + + .search_box_hint_input:hover { + background-color: #dedad9; + } + </style> + + <body> + <div> + <div> + <h3> + this is the title + </h3> + </div> + <form id="form_1"> + <div class="autocomplete" id="autocompleteForm"> + <input class="search_box" id="search_box_input" type="text" name="" placeholder="search value" autocomplete="off" value=""> + </div> + </form> + </div> + <script type="text/javascript"> + let cache_countries; + + const search = document.getElementById("search_box_input"); + + const form = document.getElementById("autocompleteForm"); + + search.addEventListener("focus", function(event) { + + console.log("hello there") + + + a = document.createElement("DIV") + + a.setAttribute("class", "hint-items"); + + + this.parentNode.appendChild(a) + + cache_countries = retrieveSearchHistory() + + + for (let i = 0; i < cache_countries.length; i++) { + + console.log(i) + var textfieldSelector = document.createElement("input"); + + textfieldSelector.type = "button"; + + textfieldSelector.value = `${cache_countries[i]}`; + + //textfieldSelector.classList.add("search_box_hint"); + + textfieldSelector.classList.add("search_box_hint_input"); + + textfieldSelector.addEventListener("click", function(event) { + + console.log("clicked this country") + + search.value = cache_countries[i] // remove child element + + let parent = document.getElementById("search_box_input").parentNode + + parent.removeChild(this.parentNode) + + + }) + + a.appendChild(textfieldSelector); + } + + document.addEventListener('click', (event) => { + if (!event.target.matches('#autocompleteForm, #autocompleteForm *')) { + + + const collection = document.getElementsByClassName("hint-items"); + + console.log(collection) + + if (collection.length > 0) { + let parent = document.getElementById("search_box_input").parentNode + + parent.removeChild(collection[0]) + } else { + + //ju + } + + } + }) + + }) + + formElement = document.getElementById("form_1"); + + function setPlaceHolderValue() { + document.getElementById("search_box_input"). + } + + function onFocusHandler(event) { + + } + + function searchHistoryClick(event) { + + } + + function removeSearchHintOnclick(element) { + + parentELement = document.createElement("DIV") + parentELement.setAttribute("class", "hint-items"); + element.parentNode.appendChild(a) + searchHistory = retrieveSearchHistory() + } + + function saveBeforeSubmit(form) { + + form.addEventListener("keydown", (event) => { + + if (event.keyCode === 13) { + event.preventDefault() + if (search.value) { + dumpSearch(search.value) + form.submit() + } + return; + } + }) + + } + saveBeforeSubmit(formElement) + + function retrieveSearchHistory() { + let results = JSON.parse(localStorage.getItem("gn_search_history")) + return results ? results : [] + } + + function dumpSearch(search_item) { + + let currentSearch = retrieveSearchHistory() + currentSearch.unshift(search_item) + + console.log(currentSearch) + + localStorage.setItem("gn_search_history", JSON.stringify(currentSearch.slice(0, 5))); + + return currentSearch + + } + </script> + </body> + + </html>
\ No newline at end of file diff --git a/gn2/wqflask/templates/search_result_page.html b/gn2/wqflask/templates/search_result_page.html new file mode 100644 index 00000000..cade198a --- /dev/null +++ b/gn2/wqflask/templates/search_result_page.html @@ -0,0 +1,453 @@ +{% extends "base.html" %} +{% block title %}Search Results{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='fontawesome/css/font-awesome.min.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('js', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css') }}"> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='fontawesome/css/all.min.css') }}"/> + <link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet" type="text/css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + <link rel="stylesheet" type="text/css" href="static/new/css/trait_list.css" /> +{% endblock %} +{% block content %} +<!-- Start of body --> + <div style="margin-left: 20px;"> + <input type="hidden" name="uc_id" id="uc_id" value="{{ uc_id }}"> + + <div style="padding-top: 10px; padding-bottom: 10px; font-size: 16px;"> + <!-- Need to customize text more for other types of searches --> + + <p>We searched <a href="http://genenetwork.org/webqtl/main.py?FormID=sharinginfo&{% if dataset.accession_id %}GN_AccessionId={{dataset.accession_id }}{% else %}InfoPageName={{ dataset.name }}{% endif %}">{{ dataset.fullname }}</a> + <br> + to find all records + {% if go_term is not none %} + with <span style="font-family: Courier New"><b>Gene Ontology ID</b></span> <strong>GO:{{ go_term }}</strong>. + {% else %} + {% for word in search_terms %} + {% if word.key|lower == "rif" %} + with <span style="font-family: Courier New"><b>GeneRIF</b></span> containing <strong>{{ word.search_term[0] }}</strong>{% if loop.last %}{% else %} and {% endif %} + {% elif word.key|lower == "go" %} + with <span style="font-family: Courier New"><b>Gene Ontology ID</b></span> <strong>{{ word.search_term[0] }}</strong>{% if loop.last %}{% else %} and {% endif %} + {% elif word.key|lower == "wiki" %} + with <span style="font-family: Courier New"><b>GeneWiki</b></span> containing <strong>{{ word.search_term[0] }}</strong>{% if loop.last %}{% else %} and {% endif %} + {% elif word.key|lower == "mean" %} + with <span style="font-family: Courier New"><b>mean</b></span> between <strong>{{ word.search_term[0] }}</strong> and <strong>{{ word.search_term[1] }}</strong>{% if loop.last %}{% else %} and {% endif %} + {% elif word.key|lower == "range" %} + with <span style="font-family: Courier New"><b>RANGE</b></span> between <strong>{{ word.search_term[0] }}</strong> and <strong>{{ word.search_term[1] }}</strong>{% if loop.last %}{% else %} and {% endif %} + {% elif word.key|lower == "lrs" or word.key|lower == "lod" or word.key|lower == "translrs" or word.key|lower == "cislrs" or word.key|lower == "translod" or word.key|lower == "cislod" %} + {% if word.search_term|length == 1 %} + with {% if word.key|lower == "translrs" %}trans{% elif word.key|lower == "cislrs" %}cis{% endif %}LRS {% if word.separator == ">" %} greater than {% elif word.separator == "<" %} less than {% elif word.separator == ">=" %} greater than or equal to {% elif word.separator == "<=" %} less than or equal to {% endif %} <strong>{{ word.search_term[0] }}</strong>{% if loop.last %}{% else %} and {% endif %} + {% elif word.search_term|length == 2 %} + with <span style="font-family: Courier New"><b>{{ word.key|upper }}</b></span> between <strong>{{ word.search_term[0] }}</strong> and <strong>{{ word.search_term[1] }}</strong>{% if loop.last %}{% else %} and {% endif %} + {% elif word.search_term|length == 3 %} + with <span style="font-family: Courier New"><b>{{ word.key|upper }}</b></span> between <strong>{{ word.search_term[0] }}</strong> and <strong>{{ word.search_term[1] }}</strong> on chromosome <strong>{{ word.search_term[2] }}</strong>{% if loop.last %}{% else %} and {% endif %} + {% elif word.search_term|length == 4 %} + with <span style="font-family: Courier New"><b>{{ word.key|upper }}</b></span> between <strong>{{ word.search_term[0] }}</strong> and <strong>{{ word.search_term[1] }}</strong> on chromosome <strong>{{ word.search_term[3] }}</strong> with an exclusion zone of <strong>{{ word.search_term[2] }}</strong> Mb + {% elif word.search_term|length == 5 %} + with <span style="font-family: Courier New"><b>{{ word.key|upper }}</b></span> between <strong>{{ word.search_term[0] }}</strong> and <strong>{{ word.search_term[1] }}</strong> on chromosome <strong>{{ word.search_term[2] }}</strong> between <strong>{{ word.search_term[3] }}</strong> and <strong>{{ word.search_term[4] }}</strong> Mb{% if loop.last %}{% else %} and {% endif %} + {% endif %} + {% elif word.key|lower == "position" or word.key|lower == "mb" %} + with <u>target genes</u> on chromosome <strong>{% if (word.search_term[0]|lower).split('chr')|length > 1 %}{{ (word.search_term[0]|lower).split('chr')[1] }}{% else %}{{ word.search_term[0] }}{% endif %}</strong> between <strong>{{ word.search_term[1] }}</strong> and <strong>{{ word.search_term[2] }}</strong> Mb{% if loop.last %}{% else %} and {% endif %} + {% else %} + {% if word.search_term[0] == "*" %} in the dataset.{% else %}{% if loop.first %}that match: {% endif %}<b>{{ word.search_term[0] }}</b>{% if loop.last %}{% else %} and {% endif %}{% endif %} + {% endif %} + {% endfor %} + {% endif %} + <br> + {% if results|count > 0 %} + <b>{{ results|count }}</b> record{% if results|count > 1 %}s{% else %}{% endif %} found + {% else %} + No (<b>0</b>) records found for this search. Modify your search, check target dataset, or use <b>Search All</b> above. + {% endif %} + </p> + + {% if results|count > 0 %} + {% if go_term is not none %} + <p><b>The associated genes include:</b><br><br>{% for word in search_terms %}{{ word.search_term[0] }}{% endfor %}</p> + {% endif %} + + </div> + {% if too_many_results %} + <p>Your search generated over 50000 results. Please modify your search to generate 50000 or fewer matches.</p> + {% else %} + <div style="min-width: 1050px;"> + <form id="trait_submission_form" target="_blank" action="/corr_matrix" method="post"> + <input type="hidden" name="tool_used" value="" /> + <input type="hidden" name="form_url" value="" /> + <input type="hidden" name="trait_list" id="trait_list" value= " + {% for this_trait in trait_list %} + {{ this_trait.name }}:{{ this_trait.dataset }}, + {% endfor %}" > + + {% include 'tool_buttons.html' %} + + </form> + </div> + + <div> + <br /> + <form id="export_form" method="POST" action="/export_traits_csv" style="display: inline;"> + <div style="min-width: 950px;"> + <input type="hidden" name="headers" id="headers" value="{% for field in header_fields %}{{ field }},{% endfor %}"> + <input type="hidden" name="search_string" id="search_string" value="{{ original_search_string }}"> + <input type="hidden" name="database_name" id="database_name" value="{{ dataset.fullname }}"> + <input type="hidden" name="file_name" id="file_name" value="search_results"> + <input type="hidden" name="filter_term" id="filter_term" value="None"> + {% if dataset.accession_id %} + <input type="hidden" name="accession_id" id="accession_id" value="{{ dataset.accession_id }}"> + {% endif %} + <input type="hidden" name="export_data" id="export_data" value=""> + <input type="text" id="searchbox" class="form-control" style="width: 200px; display: inline;" placeholder="Search For..."> + <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="select_all" type="button"><span class="glyphicon glyphicon-ok"></span> Select All</button> + <input type="text" id="select_top" class="form-control" style="width: 200px; display: inline;" placeholder="Select Rows (1-5, 11)"> + <button class="btn btn-default" id="export_traits"><span class="glyphicon glyphicon-download-alt"></span> Download</button> + <button class="btn btn-default" id="invert" type="button"><span class="glyphicon glyphicon-adjust"></span> Invert</button> + <button class="btn btn-default" id="deselect_all" type="button"><span class="glyphicon glyphicon-remove"></span> Deselect</button> + </div> + <div id="select_samples_invalid" class="alert alert-error" style="display:none;"> + Please check that your syntax includes only a combination of integers, dashes, and commas of a format + similar to <strong>1,5,10</strong> or <strong>2, 5-10, 15</strong>, etc. + </div> + </form> + {% if dataset.type != 'Geno' %} + <div class="show-hide-container"> + <b>Show/Hide Columns: </b> + {% if dataset.type == 'ProbeSet' %} + <button class="toggle-vis" data-column="3">Symbol</button> + <button class="toggle-vis" data-column="4">Description</button> + <button class="toggle-vis" data-column="5">Location</button> + <button class="toggle-vis" data-column="6">Mean</button> + <button class="toggle-vis" data-column="7">Peak -logP</button> + <button class="toggle-vis" data-column="8">Peak Location</button> + <button class="toggle-vis" data-column="9">Effect Size</button> + {% elif dataset.type == 'Publish' %} + <button class="toggle-vis" data-column="3">Description</button> + <button class="toggle-vis" data-column="4">Mean</button> + <button class="toggle-vis" data-column="5">Authors</button> + <button class="toggle-vis" data-column="6">Year</button> + <button class="toggle-vis" data-column="7">Peak -logP</button> + <button class="toggle-vis" data-column="8">Peak Location</button> + <button class="toggle-vis" data-column="9">Effect Size</button> + {% endif %} + </div> + {% endif %} + <div id="trait_table_container" style="{% if dataset.type == 'Geno' %}width: 450px;{% else %}width: 90%; min-width: 1250px;{% endif %}"> + <table class="table-hover table-striped cell-border" id='trait_table' style="float: left;"> + <tbody> + <td colspan="100%" align="center"><br><b><font size="15">Loading...</font></b><br></td> + </tbody> + </table> + </div> + </div> + {% endif %} + {% else %} + <br> + <button type="button" onclick="window.location.href='/'">Return To Search</button> + {% endif %} + </div> + <div id="myModal"></div> + +<!-- End of body --> + +{% endblock %} + +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/md5.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='jszip/jszip.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/natural.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/buttons/js/dataTables.buttons.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/buttons/js/buttons.colVis.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='fontawesome/js/all.min.js') }}"></script> + + <script language="javascript" type="text/javascript" src="/static/new/javascript/search_results.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/table_functions.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/create_datatable.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/partial_correlations.js"></script> + + <script type='text/javascript'> + var traitsJson = {{ trait_list|safe }}; + </script> + + <script type="text/javascript" charset="utf-8"> + $(document).ready( function () { + + var getParams = function(url) { + let parser = document.createElement('a'); + parser.href = url; + let params = parser.search.substring(1); + if(params.length > 0) { + return ('?'+params); + } + return params; + }; + + {% if results|count > 0 and not too_many_results %} + var tableId = "trait_table"; + + var widthChange = 0; //ZS: For storing the change in width so overall table width can be adjusted by that amount + + columnDefs = [ + { + 'data': null, + 'width': "5px", + 'orderDataType': "dom-checkbox", + 'targets': 0, + 'render': function(data) { + return '<input type="checkbox" name="searchResult" class="checkbox trait_checkbox" value="' + data.hmac + '" data-trait-info="' + data.trait_info_str + '">' + } + }, + { + 'title': "Index", + 'type': "natural", + 'width': "35px", + "searchable": false, + "orderable": false, + 'targets': 1, + 'data': "index" + } + {% if dataset.type == 'ProbeSet' %}, + { + 'title': "Record", + 'type': "natural-minus-na", + 'data': null, + 'width': "{{ max_widths.display_name * 8 }}px", + 'targets': 2, + 'render': function(data) { + return '<a target="_blank" href="/show_trait?trait_id=' + data.display_name + '&dataset=' + data.dataset + '">' + data.display_name + '</a>' + } + }, + { + 'title': "Symbol", + 'type': "natural", + 'width': "{{ max_widths.symbol * 8 }}px", + 'targets': 3, + 'data': "symbol" + }, + { + 'title': "Description", + 'type': "natural", + 'data': null, + 'targets': 4, + 'render': function(data) { + description = data.description + if (data.description.length > 200) { + description = data.description.slice(0, 200) + '...' + } + try { + return decodeURIComponent(escape(description)) + } catch(err){ + return escape(description) + } + } + }, + { + 'title': "<div style='text-align: right;'>Location</div>", + 'type': "natural-minus-na", + 'width': "130px", + 'targets': 5, + 'data': "location" + }, + { + 'title': "<div style='text-align: right;'>Mean</div>", + 'type': "natural-minus-na", + 'width': "40px", + 'data': "mean", + 'targets': 6, + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "<div style='text-align: right; padding-right: 10px;'>Peak</div> <div style='text-align: right;'>-logP <a href=\"{{ url_for('glossary_blueprint.glossary') }}#LRS\" target=\"_blank\" style=\"color: white;\"><sup style='color: #FF0000;'><i>?</i></sup></a></div>", + 'type': "natural-minus-na", + 'data': "lod_score", + 'width': "60px", + 'targets': 7, + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "<div style='text-align: right;'>Peak Location</div>", + 'type': "natural-minus-na", + 'width': "130px", + 'targets': 8, + 'data': "lrs_location" + }, + { + 'title': "<div style='text-align: right; padding-right: 10px;'>Effect</div> <div style='text-align: right;'>Size <a href=\"{{ url_for('glossary_blueprint.glossary') }}#A\" target=\"_blank\" style=\"color: white;\"><sup style='color: #FF0000;'><i>?</i></sup></a></div>", + 'type': "natural-minus-na", + 'data': "additive", + 'width': "65px", + 'targets': 9, + 'orderSequence': [ "desc", "asc"] + }{% elif dataset.type == 'Publish' %}, + { + 'title': "Record", + 'type': "natural-minus-na", + 'width': "{{ max_widths.display_name * 9 }}px", + 'data': null, + 'targets': 2, + 'render': function(data) { + return '<a target="_blank" href="/show_trait?trait_id=' + data.name + '&dataset=' + data.dataset + '">' + data.display_name + '</a>' + } + }, + { + 'title': "Description", + 'type': "natural", + 'data': null, + 'targets': 3, + 'render': function(data) { + description = data.description + if (data.description.length > 200) { + description = data.description.slice(0, 200) + '...' + } + try { + return decodeURIComponent(escape(description)) + } catch(err){ + return escape(description) + } + } + }, + { + 'title': "<div style='text-align: right;'>Mean</div>", + 'type': "natural-minus-na", + 'width': "60px", + 'data': "mean", + 'targets': 4, + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "Authors", + 'type': "natural", + {% if (max_widths.authors * 5) < 500 %} + 'width': "{{ max_widths.authors * 5 }}px", + {% else %} + 'width': "500px", + {% endif %} + 'data': "authors_display", + 'targets': 5 + }, + { + 'title': "<div style='text-align: right;'>Year</div>", + 'type': "natural-minus-na", + 'data': null, + 'width': "50px", + 'targets': 6, + 'render': function(data) { + if (data.pubmed_id != "N/A"){ + return '<a href="' + data.pubmed_link + '">' + data.pubmed_text + '</a>' + } else { + return data.pubmed_text + } + }, + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "<div style='text-align: right; padding-right: 10px;'>Peak</div> <div style='text-align: right;'>-logP <a href=\"{{ url_for('glossary_blueprint.glossary') }}#LRS\" target=\"_blank\" style=\"color: white;\"><sup style='color: #FF0000;'><i>?</i></sup></a></div>", + 'type': "natural-minus-na", + 'data': "lod_score", + 'targets': 7, + 'width': "60px", + 'orderSequence': [ "desc", "asc"] + }, + { + 'title': "<div style='text-align: right;'>Peak Location</div>", + 'type': "natural-minus-na", + 'width': "125px", + 'targets': 8, + 'data': "lrs_location" + }, + { + 'title': "<div style='text-align: right; padding-right: 10px;'>Effect</div> <div style='text-align: right;'>Size <a href=\"{{ url_for('glossary_blueprint.glossary') }}#A\" target=\"_blank\" style=\"color: white;\"><sup style='color: #FF0000;'><i>?</i></sup></a></div>", + 'type': "natural-minus-na", + 'width': "60px", + 'data': "additive", + 'targets': 9, + 'orderSequence': [ "desc", "asc"] + }{% elif dataset.type == 'Geno' %}, + { + 'title': "Record", + 'type': "natural-minus-na", + 'width': "{{ max_widths.display_name * 9 }}px", + 'data': null, + 'targets': 2, + 'render': function(data) { + return '<a target="_blank" href="/show_trait?trait_id=' + data.display_name + '&dataset=' + data.dataset + '">' + data.display_name + '</a>' + } + }, + { + 'title': "<div style='text-align: right;'>Location</div>", + 'type': "natural-minus-na", + 'width': "125px", + 'targets': 2, + 'data': "location" + }{% endif %} + ]; + + tableSettings = { + "createdRow": function ( row, data, index ) { + $('td', row).eq(0).attr("style", "text-align: center; padding: 0px 10px 2px 10px;"); + $('td', row).eq(1).attr("align", "right"); + $('td', row).eq(1).attr('data-export', index+1); + $('td', row).eq(2).attr('data-export', $('td', row).eq(2).text()); + {% if dataset.type == 'ProbeSet' %} + $('td', row).eq(3).attr('title', $('td', row).eq(3).text()); + $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); + if ($('td', row).eq(3).text().length > 20) { + $('td', row).eq(3).text($('td', row).eq(3).text().substring(0, 20)); + $('td', row).eq(3).text($('td', row).eq(3).text() + '...') + } + $('td', row).eq(4).attr('title', $('td', row).eq(4).text()); + $('td', row).eq(4).attr('data-export', $('td', row).eq(4).text()); + if ($('td', row).eq(4).text().length > 500) { + $('td', row).eq(4).text($('td', row).eq(4).text().substring(0, 500)); + $('td', row).eq(4).text($('td', row).eq(4).text() + '...') + } + $('td', row).slice(5,10).attr("align", "right"); + $('td', row).eq(5).attr('data-export', $('td', row).eq(5).text()); + $('td', row).eq(6).attr('data-export', $('td', row).eq(6).text()); + $('td', row).eq(7).attr('data-export', $('td', row).eq(7).text()); + $('td', row).eq(8).attr('data-export', $('td', row).eq(8).text()); + $('td', row).eq(9).attr('data-export', $('td', row).eq(9).text()); + {% elif dataset.type == 'Publish' %} + $('td', row).eq(3).attr('title', $('td', row).eq(3).text()); + $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); + if ($('td', row).eq(3).text().length > 500) { + $('td', row).eq(3).text($('td', row).eq(3).text().substring(0, 500)); + $('td', row).eq(3).text($('td', row).eq(3).text() + '...') + } + $('td', row).eq(4).attr('title', $('td', row).eq(4).text()); + $('td', row).eq(4).attr('data-export', $('td', row).eq(4).text()); + $('td', row).eq(4).attr('align', 'right'); + $('td', row).slice(6,10).attr("align", "right"); + $('td', row).eq(5).attr('data-export', $('td', row).eq(5).text()); + $('td', row).eq(6).attr('data-export', $('td', row).eq(6).text()); + $('td', row).eq(7).attr('data-export', $('td', row).eq(7).text()); + $('td', row).eq(8).attr('data-export', $('td', row).eq(8).text()); + $('td', row).eq(9).attr('data-export', $('td', row).eq(8).text()); + {% elif dataset.type == 'Geno' %} + $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); + {% endif %} + }, + "order": [[1, "asc" ]], + "autoWidth": true, + {% if trait_list|length > 10 %} + "scrollY": "1000px", + "scroller": true, + "scrollCollapse":true + {% else %} + "scroller": false + {% endif %} + } + + create_table(tableId, traitsJson, columnDefs, tableSettings) + {% endif %} + + submit_special = function(url) { + $("#trait_submission_form").attr("action", url); + return $("#trait_submission_form").submit(); + }; + + }); + </script> +{% endblock %} diff --git a/gn2/wqflask/templates/set_group_privileges.html b/gn2/wqflask/templates/set_group_privileges.html new file mode 100644 index 00000000..a0a53292 --- /dev/null +++ b/gn2/wqflask/templates/set_group_privileges.html @@ -0,0 +1,77 @@ +{% extends "base.html" %}
+{% block title %}Set Group Privileges{% endblock %}
+{% block css %}
+ <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" />
+ <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css') }}" />
+ <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" />
+{% endblock %}
+{% block content %}
+<!-- Start of body -->
+ <div class="container">
+ <h1>Group Privileges</h1>
+ <br>
+ <form id="set_group_privileges">
+ <input type="hidden" name="resource_id" value="{{ resource_id }}">
+ <div style="min-width: 600px; max-width: 800px;">
+ <button type="submit" class="btn btn-primary" style="margin-bottom: 40px;">Add Group</button>
+ <hr>
+ <h2>Data and Metadata Privileges</h2>
+ <table id="data_privileges_table" class="table-hover table-striped cell-border" style="float: left;">
+ <thead>
+ <tr>
+ <th></th>
+ <th>No-Access</th>
+ <th>View</th>
+ <th>Edit</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Data:</td>
+ <td align="center" style="padding: 0px;"><input type="radio" name="data_privilege" VALUE="no-access" checked></td>
+ <td align="center" style="padding: 0px;"><input type="radio" name="data_privilege" VALUE="view"></td>
+ <td align="center" style="padding: 0px;"><input type="radio" name="data_privilege" VALUE="edit"></td>
+ </tr>
+ <tr>
+ <td>Metadata:</td>
+ <td align="center" style="padding: 0px;"><input type="radio" name="metadata_privilege" VALUE="no-access" checked></td>
+ <td align="center" style="padding: 0px;"><input type="radio" name="metadata_privilege" VALUE="view"></td>
+ <td align="center" style="padding: 0px;"><input type="radio" name="metadata_privilege" VALUE="edit"></td>
+ </tr>
+ </tbody>
+ </table>
+ <hr>
+ <h2>Admin Privileges</h2>
+ <table id="admin_privileges_table" class="table-hover table-striped cell-border" style="float: left;">
+ <thead>
+ <tr>
+ <th></th>
+ <th>Not Admin</th>
+ <th>Edit Access</th>
+ <th>Edit Admins</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Admin:</td>
+ <td align="center" style="padding: 0px;"><input type="radio" name="admin_privilege" VALUE="not-admin" checked></td>
+ <td align="center" style="padding: 0px;"><input type="radio" name="admin_privilege" VALUE="edit-access"></td>
+ <td align="center" style="padding: 0px;"><input type="radio" name="admin_privilege" VALUE="edit-admins"></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </form>
+ </div>
+
+<!-- End of body -->
+
+{% endblock %}
+
+{% block js %}
+ <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script>
+ <script>
+ $('#data_privileges_table').dataTable();
+ $('#admin_privileges_table').dataTable();
+ </script>
+{% endblock %}
diff --git a/gn2/wqflask/templates/show_image.html b/gn2/wqflask/templates/show_image.html new file mode 100644 index 00000000..521f5414 --- /dev/null +++ b/gn2/wqflask/templates/show_image.html @@ -0,0 +1,5 @@ +<img alt="Embedded Image" src="data:image/png;base64, +{% for elem in img_base64 -%} + {% print("%c"|format(elem)) %} +{%- endfor %} +" />
\ No newline at end of file diff --git a/gn2/wqflask/templates/show_trait.html b/gn2/wqflask/templates/show_trait.html new file mode 100644 index 00000000..dd054ffc --- /dev/null +++ b/gn2/wqflask/templates/show_trait.html @@ -0,0 +1,276 @@ +{% extends "base.html" %} +{%from "oauth2/display_error.html" import display_error%} + +{% block title %}Trait Data and Analysis{% endblock %} + +{% block css %} + <link rel="stylesheet" type="text/css" href="/static/new/css/bar_chart.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/box_plot.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/prob_plot.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/scatter-matrix.css" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='d3-tip/d3-tip.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='nouislider/nouislider.min.css') }}" /> + <link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet" type="text/css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/trait_list.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + +{% endblock %} + +{% block content %} <!-- Start of body --> + + {{flash_me()}} + {%if "group:resource:view-resource" in trait_privileges or "system:resource:public-read" in trait_privileges%} + <div class="container"> + <h2>Trait Data and Analysis for <b>{{ this_trait.display_name }}</b></h2> + {% if this_trait.dataset.type != 'Publish' %} + <h3> + {% set trait_description = this_trait.description_fmt[0]|upper + this_trait.description_fmt[1:]|safe %} + {% if trait_description|length < 100 %} + {{ trait_description }} + {% else %} + <span class="truncDesc" style="display: block;">{{ trait_description[:99] }}... (<a onclick="toggleDescription()" href="#">Show More</a>)</span><span class="fullDesc" style="display: none;">{{ trait_description }} (<a onclick="toggleDescription()" href="#">Show Less</a>)</span> + {% endif %} + </h3> + {% endif %} + + <form method="post" action="" target="_blank" name="trait_page" id="trait_data_form" class="form-horizontal"> + <div id="hidden_inputs"> + <input type="hidden" name="trait_hmac" value="{{ data_hmac('{}:{}'.format(this_trait.name, dataset.name)) }}"> + {% for key in hddn %} + <input type="hidden" name="{{ key }}" value="{{ hddn[key] }}"> + {% endfor %} + </div> + + <input type="hidden" name="temp_uuid" id="temp_uuid" value="{{ temp_uuid }}"> + <input type="hidden" name="tool_used" value=""> + <input type="hidden" name="form_url" value=""> + <input type="hidden" name="wanted_inputs" value=""> + <input type="hidden" name="genofile" value=""> + <input type="hidden" name="covariates" value=""> + <input type="hidden" name="transform" value=""> + <input type="hidden" name="sample_vals" value=""> + + <div class="container"> + <div class="panel-group" id="accordion"> + <div class="panel panel-default"> + <div class="panel-heading" data-toggle="collapse" data-parent="#accordion" data-target="#collapseOne" aria-expanded="true"> + <h3 class="panel-title"> + <span class="glyphicon glyphicon-chevron-down"></span> Details and Links + </h3> + </div> + <div id="collapseOne" class="panel-collapse collapse" aria-expanded="true"> + <div class="panel-body"> + {% include 'show_trait_details.html' %} + </div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading stats_panel" data-toggle="collapse" data-parent="#accordion" data-target="#collapseTwo"> + <h3 class="panel-title"> + <span class="glyphicon glyphicon-chevron-down"></span> Statistics + </h3> + </div> + <div id="collapseTwo" class="panel-collapse collapse in"> + <div class="panel-body"> + {% include 'show_trait_statistics.html' %} + </div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading" data-toggle="collapse" data-parent="#accordion" data-target="#collapseThree"> + <h3 class="panel-title"> + <span class="glyphicon glyphicon-chevron-down"></span> Transform and Filter Data + </h3> + </div> + <div id="collapseThree" class="panel-collapse collapse in"> + <div class="panel-body"> + {% include 'show_trait_transform_and_filter.html' %} + </div> + <div id="transform_alert_placeholder"><div id="transform_alert" style="display: none;"class="alert alert-success outlier-alert"><a href="#" class="close" data-dismiss="alert">�</a><span>Because there are some values between 0 and 1, log2 and log10 transforms will add 1 to each value.</span></div></div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading" data-toggle="collapse" data-parent="#accordion" data-target="#collapseFour"> + <h3 class="panel-title"> + <span class="glyphicon glyphicon-chevron-down"></span> Calculate Correlations + </h3> + </div> + <div id="collapseFour" class="panel-collapse collapse in"> + <div class="panel-body"> + {% include 'show_trait_calculate_correlations.html' %} + </div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading" data-toggle="collapse" data-parent="#accordion" data-target="#collapseFive"> + <h3 class="panel-title"> + <span class="glyphicon glyphicon-chevron-down"></span> Mapping Tools + </h3> + </div> + <div id="collapseFive" class="panel-collapse collapse in"> + <div class="panel-body"> + {% include 'show_trait_mapping_tools.html' %} + </div> + <div id="outlier_alert_placeholder"></div> + </div> + </div> + <div class="panel panel-default" {%if (trait_table_width|int > 1100)%}style="min-width: {{trait_table_width|int + 30}}px;"{% endif %}> + <div class="panel-heading" data-toggle="collapse" data-parent="#accordion" data-target="#collapseSix" aria-expanded="true"> + <h3 class="panel-title"> + <span class="glyphicon glyphicon-chevron-up"></span> Review and Edit Data + </h3> + </div> + <div id="collapseSix" class="panel-collapse collapse" aria-expanded="true"> + <div class="panel-body"> + {% include 'show_trait_edit_data.html' %} + </div> + </div> + </div> + </div> + {% include 'show_trait_progress_bar.html' %} + </div> + </form> + </div> + {%else%} + {%if user.name == "Anonymous User"%} + {{display_error("Access Denied", {"error": "AuthorisationError", "error_description": "This trait is not accessible for the general public yet. Please log in."})}} + {%else%} + {{display_error("Access Denied", {"error": "AuthorisationError", "error_description": "The user '" + user.name + "', does not currently possess the appropriate privileges to view this trait. If you know the owner of this trait, please request that they grant you access, or wait until it is made public."})}} + {%endif%} + {%endif%} + + <!-- End of body --> + +{% endblock %} + +{% block js %} + <script> + js_data = {{ js_data | safe }} + $('.collapse').collapse() + </script> + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3js/d3.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='underscore-string/underscore.string.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3-tip/d3-tip.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/jstat.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='shapiro-wilk/shapiro-wilk.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='plotly/plotly.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/colorbrewer.js"></script> + + <script language="javascript" type="text/javascript" src="/static/new/javascript/stats.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/scatter-matrix.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/plotly_probability_plot.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/compare_traits_scatterplot.js"></script> + + + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/scientific.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/natural.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='nouislider/nouislider.js') }}"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/table_functions.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/create_datatable.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/initialize_show_trait_tables.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/show_trait_mapping_tools.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/show_trait.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/validation.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/get_covariates_from_collection.js"></script> + + <script type="text/javascript" charset="utf-8"> + + $.fn.dataTable.ext.order['dom-checkbox'] = function ( settings, col ) + { + return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { + return $('input', td).prop('checked') ? '1' : '0'; + } ); + }; + + function getValue(x) { + if (x == 'x') { + return "x"; + } + else { + return parseFloat(x); + } + } + + $.fn.dataTable.ext.order['dom-input'] = function (settings, col) { + return this.api().column(col, { order: 'index' }).nodes().map(function (td, i) { + return $('input', td).val(); + }); + } + + $.fn.dataTableExt.oSort['cust-txt-asc'] = function (a, b) { + var x = getValue(a); + var y = getValue(b); + + if (x == 'x' || x === '') { + return 1; + } + else if (y == 'x' || y === '') { + return -1; + } + else { + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + } + }; + + $.fn.dataTableExt.oSort['cust-txt-desc'] = function (a, b) { + var x = getValue(a); + var y = getValue(b); + return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + }; + + $(document).ready( function () { + $('.panel-heading').click(function () { + if ($(this).hasClass('collapsed')){ + $(this).find('.glyphicon-chevron-down').removeClass('glyphicon-chevron-down').addClass('glyphicon-chevron-up'); + } + else { + $(this).find('.glyphicon-chevron-up').removeClass('glyphicon-chevron-up').addClass('glyphicon-chevron-down'); + } + }); + + $('#samples_primary, #samples_other').find("tr.outlier").css('background-color', 'orange') + + $('.edit_sample_checkbox:checkbox').change(function() { + 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') + } + } + }); + + var slider = document.getElementById('p_range_slider'); + noUiSlider.create(slider, { + start: [-1.0, 1.0], + range: { + 'min': [-1.0], + 'max': [1.0] + } + }); + + var slider_display = [ + document.getElementById('p_range_lower'), + document.getElementById('p_range_upper') + ]; + var slider_values = [ + $('input[name=p_range_lower]'), + $('input[name=p_range_upper]') + ]; + + slider.noUiSlider.on('update', function( values, handle ) { + slider_display[handle].innerHTML = values[handle]; + slider_values[handle].val(values[handle]); + }); + }); + </script> + +{% endblock %} diff --git a/gn2/wqflask/templates/show_trait_calculate_correlations.html b/gn2/wqflask/templates/show_trait_calculate_correlations.html new file mode 100644 index 00000000..22fe6142 --- /dev/null +++ b/gn2/wqflask/templates/show_trait_calculate_correlations.html @@ -0,0 +1,165 @@ +<div class="main"> + <div class="col-xs-3 options"> + <div class="form-horizontal section-form-div"> + + <div class="form-group"> + <label for="corr_type" class="col-xs-2 control-label">Method</label> + <div class="col-xs-3 controls"> + <select name="corr_type" class="form-control"> + <option value="sample">Sample r</option> + {% if dataset.type == 'ProbeSet' %} + <option value="lit">Literature r</option> + <option value="tissue">Tissue r</option> + {% endif %} + </select> + </div> + </div> + + <div class="form-group"> + <label for="corr_dataset" class="col-xs-2 control-label">Database</label> + <div class="col-xs-10 controls"> + <select name="corr_dataset" class="form-control"> + {% for tissue in corr_tools.dataset_menu %} + {% if tissue.tissue %} + <optgroup label="{{ tissue.tissue }} ------"> + {% endif %} + {% for dataset in tissue.datasets %} + <option data-type="{% if tissue.tissue %}mrna_assay{% elif dataset[1][-4:] == 'Geno' %}geno{% else %}pheno{% endif %}" value="{{ dataset[1] }}" + {% if corr_tools.dataset_menu_selected == dataset[1] %} + selected + {% endif %}> + {{ dataset[0] }} + </option> + {% endfor %} + {% if tissue.tissue %} + </optgroup> + {% endif %} + {% endfor %} + </select> + </div> + </div> + + <div class="form-group"> + <label for="corr_return_results" class="col-xs-2 control-label">Limit to</label> + <div class="col-xs-4 controls"> + <select name="corr_return_results" class="form-control"> + {% for return_result in corr_tools.return_results_menu %} + <option value="{{ return_result }}" + {% if corr_tools.return_results_menu_selected == return_result %} + selected + {% endif %}> + Top {{ return_result }} + </option> + {% endfor %} + </select> + </div> + </div> + + <div class="form-group"> + <label for="corr_samples_group" class="col-xs-2 control-label">Samples</label> + <div class="col-xs-4 controls"> + <select name="corr_samples_group" class="form-control"> + {% for group, pretty_group in sample_group_types.items() %} + <option value="{{ group }}">{{ pretty_group }}</option> + {% endfor %} + </select> + </div> + </div> + + <div id="corr_sample_method" class="form-group"> + <label for="corr_sample_method" class="col-xs-2 control-label">Type</label> + <div class="col-xs-4 controls"> + <select name="corr_sample_method" class="form-control"> + <option value="pearson">Pearson</option> + <option value="spearman">Spearman Rank</option> + </select> + </div> + </div> + <div id="min_expr_filter" class="form-group" style="display: {% if dataset.type != 'Geno' %}block{% else %}none{% endif %};"> + <label class="col-xs-2 control-label">Min Expr</label> + <div class="col-xs-4 controls"> + <input name="min_expr" value="" type="text" class="form-control min-expr-field"> + </div> + </div> + <div class="form-group"> + <label for="location_type" class="col-xs-2 control-label">Location Type</label> + <div class="col-xs-4 controls"> + <select name="location_type" class="form-control"> + <option value="gene" {% if dataset.type == 'Publish' %}disabled{% endif %}>Gene</option> + <option value="highest_lod">Highest LOD</option> + </select> + </div> + </div> + <div id="location_filter" class="form-group"> + <label class="col-xs-2 control-label">Location</label> + <div class="col-xs-10 controls"> + <span> + Chr: <input name="loc_chr" value="" type="text" class="form-control corr-location"> + Mb: <input name="min_loc_mb" value="" type="text" class="form-control corr-location"> to <input name="max_loc_mb" value="" type="text" class="form-control corr-location"> + </span> + <br> + </div> + </div> + <div class="form-group"> + <label class="col-xs-2 control-label">Range</label> + <div class="col-xs-5 controls"> + <input name="p_range_lower" value="" type="hidden"> + <input name="p_range_upper" value="" type="hidden"> + <span class="inline-div"> + <div id="p_range_slider" class="p-range-slider"></div> + <span id="p_range_lower" class="p-range-lower"></span> + <span id="p_range_upper" class="p-range-upper"></span> + </span> + </div> + </div> + + + <div class="form-group"> + <label class="col-xs-2 control-label" >Use Cache</label> + <div class="col-xs-5 controls"> + <input id="use_cache" name="use_cache" type="checkbox" checked> + </div> + </div> + + <div class="form-group"> + <label for="corr_sample_method" class="col-xs-2 control-label"></label> + <div class="col-xs-3 controls"> + <input type="button" class="btn corr_compute submit_special btn-success" data-url="/corr_compute" title="Compute Correlation" value="Compute"> + </div> + </div> + </div> + </div> + <div class="descriptions"> + <div class="section-form-div"> + <dl> + <dt class="map-method-text">Sample Correlation</dt> + <dd>The <a href="http://genenetwork.org/correlationAnnotation.html#genetic_r">Sample Correlation</a> + is computed + between trait data and any + other traits in the sample database selected above. Use + <a href="{{ url_for('glossary_blueprint.glossary') }}#Correlations">Spearman + Rank</a> + when the sample size is small (<20) or when there are influential outliers.</dd> + <dt class="map-method-text">Literature Correlation</dt> + <dd>The <a href="http://genenetwork.org/correlationAnnotation.html#literatureCorr">Literature Correlation</a> + (Lit r) between + this gene and all other genes is computed<br> + using the <span class="broken_link" href="https://grits.eecs.utk.edu/sgo/sgo.html"> + Semantic Gene Organizer</span> + and human, rat, and mouse data from PubMed. + Values are ranked by Lit r, but Sample r and Tissue r are also displayed.<br> + <a href="http://genenetwork.org/glossary.html#Literature">More on using Lit r</a></dd> + <dt class="map-method-text">Tissue Correlation</dt> + <dd>The <a href="http://genenetwork.org/webqtl/main.py?FormID=tissueCorrelation">Tissue Correlation</a> + (Tissue r) + estimates the similarity of expression of two genes + or transcripts across different cells, tissues, or organs + (<a href="http://genenetwork.org/correlationAnnotation.html#tissueCorr">glossary</a>). + Tissue correlations + are generated by analyzing expression in multiple samples usually taken from single cases.<br> + <strong>Pearson</strong> and <strong>Spearman Rank</strong> correlations have been + computed for all pairs of genes using data from mouse samples.<br></dd> + </dl> + </div> + </div> + </div> diff --git a/gn2/wqflask/templates/show_trait_details.html b/gn2/wqflask/templates/show_trait_details.html new file mode 100644 index 00000000..9c12393d --- /dev/null +++ b/gn2/wqflask/templates/show_trait_details.html @@ -0,0 +1,255 @@ +<table class="table"> + <tr> + <td><b>Group</b></td> + <td>{{ this_trait.dataset.group.species[0]|upper }}{{ this_trait.dataset.group.species[1:] }}: {{ this_trait.dataset.group.name }} group</td> + </tr> + {% if this_trait.dataset.type == 'Publish' %} + <tr> + <td><b>Phenotype</b></td> + <td><div>{{ this_trait.description_fmt }}</div></td> + </tr> + <tr> + <td><b>Authors</b></td> + <td><div>{{ this_trait.authors }}</div></td> + </tr> + <tr> + <td><b>Title</b></td> + <td><div>{{ this_trait.title }}</div></td> + </tr> + <tr> + <td><b>Journal</b></td> + <td>{{ this_trait.journal }} ({% if this_trait.pubmed_id %}<a href="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=PubMed&list_uids={{ this_trait.pubmed_id }}&dop=Abstract" target="_blank" title="PubMed">{{ this_trait.year }}</a>{% else %}{{ this_trait.year }}{% endif %})</td> + </tr> + {% elif this_trait.dataset.type == 'ProbeSet' %} + <tr> + <td><b>Tissue</b></td> + <td>{{ this_trait.dataset.tissue }}</td> + </tr> + {% endif %} + {% if this_trait.dataset.type == 'ProbeSet' %} + {% if this_trait.symbol != None %} + <tr> + <td><b>Gene Symbol</b></td> + <td>{{ this_trait.symbol }}</td> + </tr> + {% endif %} + <tr> + <td><b>Aliases</b></td> + <td>Wikidata: {{ this_trait.wikidata_alias_fmt|replace(",",";") }}</td> + </tr> + {% if this_trait.alias_fmt != "Not Available" %} + <tr> + <td></td> + <td>GeneNetwork: {{ this_trait.alias_fmt|replace(",",";") }}</td> + </tr> + {% endif %} + {% endif %} + {% if this_trait.dataset.type != 'Publish' %} + <tr> + <td><b>Location</b></td> + <td>{{ this_trait.location_fmt }}</td> + </tr> + {% endif %} + {% if ncbi_summary != None and ncbi_summary != "" %} + <tr> + <td><b>Summary</b></td> + <td>{{ ncbi_summary }}</td> + </tr> + {% endif %} + <tr> + <td><b>Database</b></td> + <td> + <a href="http://gn1.genenetwork.org/webqtl/main.py?FormID=sharinginfo&InfoPageName={{ dataset.name }}" target="_blank"> + {{ dataset.fullname }} + </a> + <br/> + <a href="{{ url_for('get_dataset', name=dataset.name) }}" target="_blank"> + GN2 Link: {{ dataset.fullname }} + </a> + </td> + </tr> + {% if this_trait.probe_set_specificity %} + <tr> + <td><b>Target Score</b></td> + <td> + <a href="http://gn1.genenetwork.org/blatInfo.html" target="_blank" title="Values higher than 2 for the specificity are good"> + BLAT Specificity + </a>: + {{ "%0.3f" | format(this_trait.probe_set_specificity|float) }} + + {% if this_trait.probe_set_blat_score %} + Score: {{ "%0.3f" | format(this_trait.probe_set_blat_score|float) }} + {% endif %} + </td> + </tr> + {% endif %} + {% if this_trait.pubmed_id or this_trait.geneid or this_trait.omim or this_trait.symbol %} + <tr> + <td><b>Resource Links</b></td> + <td> + {% if pubmed_link %} + <a href="{{ pubmed_link }}" target="_blank" title="PubMed"> + PubMed + </a> + {% endif %} + {% if ncbi_gene_link %} + <a href="{{ ncbi_gene_link }}" target="_blank" title="Info from NCBI Entrez Gene"> + Gene + </a> + + {% endif %} + {% if omim_link %} + <a href="{{ omim_link }}" target="_blank" title="Summary from On Mendelion Inheritance in Man"> + OMIM + </a> + + {% endif %} + {% if genemania_link %} + <a href="{{ genemania_link }}" target="_blank" title="GeneMANIA"> + GeneMANIA + </a> + + {% endif %} + {% if protein_atlas_link %} + <a href="{{ protein_atlas_link }}" target="_blank" title="Human Protein Atlas"> + Protein Atlas + </a> + + {% endif %} + {% if open_targets_link %} + <a href="{{ open_targets_link }}" target="_blank" title="Open Targets"> + Open Targets + </a> + + {% endif %} + {% if homologene_link %} + <a href="{{ homologene_link }}" target="_blank" title="Find similar genes in other species"> + HomoloGene + </a> + + {% endif %} + {% if this_trait.symbol %} + <!-- + <a href="{{ genotation_link }}" target="_blank" title="Related descriptive, genomic, clinical, functional and drug-therapy information"> + Genotation + </a> + + --> + {% if rgd_link %} + <a href="{{ rgd_link }}" target="_blank"> + Rat Genome DB + </a> + + {% endif %} + <a href="{{ gtex_link }}" target="_blank" title="GTEx Portal"> + GTEx Portal + </a> + + {% if phenogen_link %} + <a href="{{ phenogen_link }}" target="_blank"> + PhenoGen + </a> + + {% endif %} + {% if genebridge_link %} + <a href="{{ genebridge_link }}" target="_blank"> + GeneBridge + </a> + {% endif %} + {% endif %} + <br> + {% if ucsc_blat_link %} + <a href="{{ ucsc_blat_link }}" target="_blank" title="Info from UCSC Genome Browser"> + UCSC + </a> + + {% endif %} + {% if biogps_link %} + <a href="{{ biogps_link }}" target="_blank" title="Expression across many tissues and cell types"> + BioGPS + </a> + + {% endif %} + {% if string_link %} + <a href="{{ string_link }}" target="_blank" title="Protein interactions: known and inferred"> + STRING + </a> + + {% endif %} + {% if panther_link %} + <a href="{{ panther_link }}" target="_blank" title="Gene and protein data resources from Celera-ABI"> + PANTHER + </a> + + {% endif %} + {% if gemma_link %} + <a href="{{ gemma_link }}" target="_blank" title="Meta-analysis of gene expression data"> + Gemma + </a> + + {% endif %} + {% if aba_link %} + <a href="{{ aba_link }}" target="_blank" title="Allen Brain Atlas"> + ABA + </a> + + {% endif %} + {% if ebi_gwas_link %} + <a href="{{ ebi_gwas_link }}" target="_blank" title="EBI GWAS"> + EBI GWAS + </a> + + {% endif %} + {% if wiki_pi_link %} + <a href="{{ wiki_pi_link }}" target="_blank" title="Wiki-Pi"> + Wiki-Pi + </a> + + {% endif %} + {% if uniprot_link %} + <a href="{{ uniprot_link }}" target="_blank" title="UniProt"> + UniProt + </a> + + {% endif %} + </td> + </tr> + {% endif %} +</table> + +<div class="btn-toolbar"> + <div class="btn-group"> + <button type="button" id="add_to_collection" class="btn btn-success" title="Add to Collection">Add</button> + {% if this_trait.dataset.type == 'ProbeSet' or this_trait.dataset.type == 'Geno' %} + {% if this_trait.symbol != None %} + <button type="button" class="btn btn-default" title="Find similar expression data" onclick="window.open('http://gn1.genenetwork.org/webqtl/main.py?cmd=sch&gene={{ this_trait.symbol }}&alias=1&species={{ dataset.group.species }}', '_blank')">Find</button> + {% endif %} + {% if UCSC_BLAT_URL != "" %} + <button type="button" class="btn btn-default" title="Check probe locations at UCSC" onclick="window.open('{{ UCSC_BLAT_URL }}', '_blank')">Verify</button> + {% endif %} + {% if this_trait.symbol != None %} + <button type="button" class="btn btn-default" title="Write or review comments about this gene" onclick="window.open('http://gn1.genenetwork.org/webqtl/main.py?FormID=geneWiki&symbol={{ this_trait.symbol }}', '_blank')">(GN1) GeneWiki</button> + {% if dataset.group.species == "mouse" or dataset.group.species == "rat" %} + <button type="button" class="btn btn-default" title="View SNPs and Indels" onclick="window.open('/snp_browser?first_run=true&species={{ dataset.group.species }}&gene_name={{ this_trait.symbol }}&limit_strains=on', '_blank')">SNPs</button> + {% endif %} + {% endif %} + {% if show_probes == "True" %} + <button type="button" class="btn btn-default" title="Check sequence of probes" onclick="window.open('http://gn1.genenetwork.org/webqtl/main.py?FormID=showProbeInfo&database={{ this_trait.dataset.name }}&ProbeSetID={{ this_trait.name }}&CellID={{ this_trait.cellid }}&RISet={{ dataset.group.name }}&incparentsf1=ON', '_blank')">Probes</button> + {% endif %} + {% endif %} + <button type="button" id="view_in_gn1" class="btn btn-primary" title="View Trait in GN1" onclick="window.open('http://gn1.genenetwork.org/webqtl/main.py?cmd=show&db={{ this_trait.dataset.name }}&probeset={{ this_trait.name }}', '_blank')">Go to GN1</button> + {% if this_trait.dataset.type == 'Publish' %} + <button type="button" id="edit_resource" class="btn btn-success" title="Edit Resource" onclick="window.open('/datasets/{{ this_trait.dataset.group.id }}/traits/{{ this_trait.name }}?resource-id={{ resource_id }}&dataset_name={{this_trait.dataset.name}}', '_blank')">Edit</button> + {% endif %} + + {% if this_trait.dataset.type == 'ProbeSet' %} + <button type="button" id="edit_resource" class="btn btn-success" title="Edit Resource" onclick="window.open('/datasets/traits/{{ this_trait.name }}?resource-id={{ resource_id }}&dataset_name={{this_trait.dataset.name}}', '_blank')">Edit</button> + {% endif %} + </div> + {%if "group:resource:edit-resource" in trait_privileges%} + <div class="btn-group"> + <button type="button" id="edit_resource" class="btn btn-success" title="Edit Privileges" onclick="window.open('{{url_for('oauth2.resource.view_resource', resource_id=resource_id)}}', '_blank')">Edit Privileges</button> + </div> + {% endif %} +</div> + diff --git a/gn2/wqflask/templates/show_trait_edit_data.html b/gn2/wqflask/templates/show_trait_edit_data.html new file mode 100644 index 00000000..91cbdb6e --- /dev/null +++ b/gn2/wqflask/templates/show_trait_edit_data.html @@ -0,0 +1,75 @@ +<div> + {% for sample_type in sample_groups %} + <div class="sample-table-container"> + {% if loop.index == 1 and (sample_groups[0].se_exists or has_num_cases or sample_groups[0].attributes|length > 0) %} + <b>Show/Hide Columns:</b> + <br> + {% if sample_groups[0].se_exists %} + <button class="toggle-vis" data-column="4,5">SE</button> + {% if has_num_cases %} + <button class="toggle-vis" data-column="6">N</button> + {% set attr_start_pos = 7 %} + {% else %} + {% set attr_start_pos = 6 %} + {% endif %} + {% else %} + {% if has_num_cases %} + <button class="toggle-vis" data-column="4">N</button> + {% set attr_start_pos = 5 %} + {% else %} + {% set attr_start_pos = 4 %} + {% endif %} + {% endif %} + {% if sample_groups[0].attributes %} + {% for attribute in sample_groups[0].attributes %} + <button class="toggle-vis" data-column="{{ loop.index + attr_start_pos - 1 }}">{{ sample_groups[0].attributes[attribute].name }}</button> + {% endfor %} + {% endif %} + <br> + <br> + {% endif %} + <div class="sample-table-search-container"> + <input type="text" id="{{ sample_type.sample_group_type }}_searchbox" class="form-control sample-table-search" placeholder="Search This Table For ..."> + </div> + <div class="sample-table-export-container"> + <button class="btn btn-default export"><span class="glyphicon glyphicon-download-alt"></span> Export</button> + <select class="select optional span2 export_format"> + <option value="excel">Excel</option> + <option value="csv">CSV</option> + </select> + <button type="button" class="btn btn-success reset"><span class="glyphicon glyphicon-repeat"></span> Reset</button> + </div> + <div id="export_code" class="export-code-container"> + <pre class="export-code_field"> + <code> + # read into R + trait <- read.csv("{{ this_trait.display_name}}.csv", header = TRUE, comment.char = "#") + + # read into python + import pandas as pd + trait = pd.read_csv("{{ this_trait.display_name}}.csv", header = 0, comment = "#") + </code> + </pre> + </div> + </div> + <a href="{{url_for('edit_case_attributes', inbredset_id=dataset.group.id)}}" + title="Edit case attributes for group." + target="_blank" + class="btn btn-info">Edit CaseAttributes</a> + {% set outer_loop = loop %} + <div class="sample_group"> + <div style="position: relative;"> + <div class="inline-div"><h3 style="float: left;">{{ sample_type.header }}<span name="transform_text"></span></h3></div> + </div> + <div id="{{ sample_type.sample_group_type }}_container" style="width: {{ trait_table_width }}px;"> + <table class="table-hover table-striped cell-border" id="samples_{{ sample_type.sample_group_type }}"> + <tbody> + <td colspan="100%" align="center"><br><b><font size="15">Loading...</font></b><br></td> + </tbody> + </table> + </div> + </div> + <br> + {% endfor %} + <input type="hidden" name="Default_Name"> +</div> diff --git a/gn2/wqflask/templates/show_trait_error.html b/gn2/wqflask/templates/show_trait_error.html new file mode 100644 index 00000000..c924d1f1 --- /dev/null +++ b/gn2/wqflask/templates/show_trait_error.html @@ -0,0 +1,20 @@ +{%extends "base.html"%} +{%block title%}Trait Data and Analysis{%endblock%} +{%block css%} +<link rel="stylesheet" type="text/css" href="/static/new/css/bar_chart.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/box_plot.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/prob_plot.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/scatter-matrix.css" /> +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='d3-tip/d3-tip.css') }}" /> +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='nouislider/nouislider.min.css') }}" /> +<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet" type="text/css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/trait_list.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + +{%endblock%} +{%block content%} <!-- Start of body --> +<div class="container"> + {{flash_me()}} +</div> +{%endblock%} <!-- End of body --> diff --git a/gn2/wqflask/templates/show_trait_mapping_tools.html b/gn2/wqflask/templates/show_trait_mapping_tools.html new file mode 100755 index 00000000..f1ed8922 --- /dev/null +++ b/gn2/wqflask/templates/show_trait_mapping_tools.html @@ -0,0 +1,436 @@ +<div class="main"> + {% if dataset.group.mapping_names|length > 0 %} + <div class="options"> + <div class="tabbable"> <!-- Only required for left/right tabs --> + + <ul class="nav nav-pills"> + {% for mapping_method in dataset.group.mapping_names %} + {% if mapping_method == "GEMMA" %} + <li class="gemma-tab mapping-tab {% if dataset.group.mapping_id == '1' or dataset.group.mapping_id == '2' %}active{% endif %}"> + <a href="#gemma" data-toggle="tab">GEMMA</a> + </li> + {% elif mapping_method == "R/qtl" %} + <li class="rqtl-geno-tab mapping-tab {% if dataset.group.mapping_id == '3' %}active{% endif %}"> + <a href="#rqtl_geno" data-toggle="tab">R/qtl</a> + </li> + <li class="rqtl-pair-tab mapping-tab"> + <a href="#rqtl_pair" data-toggle="tab">Pair Scan</a> + </li> + {% elif mapping_method == "QTLReaper" %} + <li class="reaper-tab mapping-tab"> + <a href="#interval_mapping" data-toggle="tab">Haley-Knott Regression</a> + </li> + {% endif %} + {% endfor %} + </ul> + + <div class="tab-content"> + {% for mapping_method in dataset.group.mapping_names %} + {% if mapping_method == "GEMMA" %} + <div class="tab-pane {% if dataset.group.mapping_id == '1' or dataset.group.mapping_id == '2' %}active{% endif %}" id="gemma"> + <div class="form-horizontal section-form-div"> + <div class="mapping_method_fields form-group"> + <label for="chr_select" class="col-xs-3 control-label">Chromosome</label> + <div class="col-xs-2 controls"> + <select id="chr_gemma" class="form-control chr-select"> + {% for item in chr_list %} + <option value="{{ item[1] }}">{{ item[0] }}</option> + {% endfor %} + </select> + </div> + </div> + {% if genofiles and genofiles|length>0 %} + <div class="mapping_method_fields form-group"> + <label for="genofiles" class="col-xs-3 control-label">Genotypes</label> + <div class="col-xs-6 controls"> + <select id="genofile_gemma" class="form-control"> + {% for item in genofiles %} + <option value="{{item['location']}}:{{item['title']}}">{{item['title']}}</option> + {% endfor %} + </select> + </div> + </div> + {% endif %} + <div class="mapping_method_fields form-group"> + <label for="maf_gemma" class="col-xs-3 control-label">Minor Allele ≥</label> + <div class="col-xs-4 controls"> + <input name="maf_gemma" value="{{ maf }}" type="text" class="form-control maf-select"> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label class="col-xs-3 control-label">Use LOCO</label> + <div class="col-xs-6 controls"> + <label class="radio-inline"> + <input type="radio" name="use_loco" value="True" checked=""> + Yes + </label> + <label class="radio-inline"> + <input type="radio" name="use_loco" value="False"> + No + </label> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label class="col-xs-3 control-label">Covariates<br><span class="covar-text">Select covariate(s) from a collection</span></label> + <div class="col-xs-8 covar-options"> + <div class="select-covar-div"> + <button type="button" class="btn btn-success select-covar-button select_covariates">Select</button> + <button type="button" class="btn btn-default select-covar-button remove_covariates">Remove</button> + <button type="button" class="btn btn-danger select-covar-button remove_all_covariates">Clear</button> + </div> + <select size="2" name="selected_covariates_gemma" class="form-control selected-covariates" multiple> + <option value="">No covariates selected</option> + </select> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label class="col-xs-3 control-label"></label> + <div class="col-xs-6"> + <button id="gemma_compute" type="button" class="btn submit_special btn-success" title="Compute Marker Regression" value="Compute" disabled>Loading...</button> + </div> + </div> + </div> + </div> + {% elif mapping_method == "QTLReaper" %} + <div class="tab-pane" id="interval_mapping"> + <div class="form-horizontal section-form-div"> + <div class="mapping_method_fields form-group"> + <label for="chr_select" class="col-xs-3 control-label">Chromosome</label> + <div class="col-xs-2 controls"> + <select id="chr_reaper" class="form-control chr-select"> + {% for item in chr_list %} + <option value="{{ item[1] }}">{{ item[0] }}</option> + {% endfor %} + </select> + </div> + </div> + {% if genofiles and genofiles|length>0 %} + <div class="mapping_method_fields form-group"> + <label for="scale_select" class="col-xs-3 control-label">Map Scale</label> + <div class="col-xs-2 controls"> + <select id="scale_reaper" class="form-control scale-select"> + {% for item in scales_in_geno[genofiles[0]['location']] %} + <option value="{{ item[0] }}">{{ item[1] }}</option> + {% endfor %} + </select> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label for="genofiles" class="col-xs-3 control-label">Genotypes</label> + <div class="col-xs-6 controls"> + <select id="genofile_reaper" class="form-control"> + {% for item in genofiles %} + <option value="{{item['location']}}:{{item['title']}}">{{item['title']}}</option> + {% endfor %} + </select> + </div> + </div> + {% else %} + <div class="mapping_method_fields form-group"> + <label for="scale_select" class="col-xs-3 control-label">Map Scale</label> + <div class="col-xs-2 controls"> + <select id="scale_reaper" class="form-control scale-select"> + {% for item in scales_in_geno[dataset.group.name + ".geno"] %} + <option value="{{ item[0] }}">{{ item[1] }}</option> + {% endfor %} + </select> + </div> + </div> + {% endif %} + <div class="mapping_method_fields form-group"> + <label for="mapping_permutations" class="col-xs-3 control-label">Permutations</label> + <div class="col-xs-4 controls"> + <input name="num_perm_reaper" value="1000" type="text" class="form-control"> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label for="mapping_bootstraps" class="col-xs-3 control-label">Bootstraps</label> + <div class="col-xs-4 controls"> + <input name="num_bootstrap" value="0" type="text" class="form-control"> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label for="control_for" class="col-xs-3 control-label">Control for<br>Cofactors</label> + <div class="col-xs-6 controls"> + <input name="control_reaper" value="{% if dataset.type == 'ProbeSet' and this_trait.locus_chr != '' %}{{ nearest_marker }}{% endif %}" type="text" class="form-control cofactor-input" /> + <br> + <label class="radio-inline"> + <input type="radio" name="do_control_reaper" value="true"> + Yes + </label> + <label class="radio-inline"> + <input type="radio" name="do_control_reaper" value="false" checked=""> + No + </label> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label class="col-xs-3 control-label">Interval Map</label> + <div class="col-xs-6 controls"> + <label class="radio-inline"> + <input type="radio" name="manhattan_plot_reaper" value="false" checked=""> + Yes + </label> + <label class="radio-inline"> + <input type="radio" name="manhattan_plot_reaper" value="true"> + No + </label> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label class="col-xs-3 control-label"></label> + <div class="col-xs-6"> + <button id="interval_mapping_compute" type="button" class="btn submit_special btn-success" title="Compute Interval Mapping" value="Compute" disabled>Loading...</button> + </div> + </div> + </div> + </div> + {% elif mapping_method == "R/qtl" %} + <div class="tab-pane {% if dataset.group.mapping_id == '3' %}active{% endif %}" id="rqtl_geno"> + <div class="form-horizontal section-form-div"> + <div class="mapping_method_fields form-group"> + <label for="chr_select" class="col-xs-3 control-label">Chromosome</label> + <div class="col-xs-2 controls"> + <select id="chr_rqtl_geno" class="form-control chr-select"> + {% for item in chr_list %} + <option value="{{ item[1] }}">{{ item[0] }}</option> + {% endfor %} + </select> + </div> + </div> + {% if genofiles and genofiles|length > 0 %} + <div class="mapping_method_fields form-group"> + <label for="scale_select" class="col-xs-3 control-label">Map Scale</label> + <div class="col-xs-2 controls"> + <select id="scale_rqtl_geno" class="form-control scale-select"> + {% for item in scales_in_geno[genofiles[0]['location']] %} + <option value="{{ item[0] }}">{{ item[1] }}</option> + {% endfor %} + </select> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label for="genofiles" class="col-xs-3 control-label">Genotypes</label> + <div class="col-xs-6 controls"> + <select id="genofile_rqtl_geno" class="form-control"> + {% for item in genofiles %} + <option value="{{item['location']}}:{{item['title']}}">{{item['title']}}</option> + {% endfor %} + </select> + </div> + </div> + {% else %} + <div class="mapping_method_fields form-group"> + <label for="scale_select" class="col-xs-3 control-label">Map Scale</label> + <div class="col-xs-2 controls"> + <select id="scale_rqtl_geno" class="form-control scale-select"> + {% for item in scales_in_geno[dataset.group.name + ".geno"] %} + <option value="{{ item[0] }}">{{ item[1] }}</option> + {% endfor %} + </select> + </div> + </div> + {% endif %} + <div class="mapping_method_fields form-group"> + <label for="mapping_permutations" class="col-xs-3 control-label">Permutations</label> + <div class="col-xs-4 controls"> + <input name="num_perm_rqtl_geno" value="200" type="text" class="form-control"> + </div> + </div> + {% if sample_groups[0].attributes|length > 0 %} + <div class="mapping_method_fields form-group"> + <label class="col-xs-3 control-label">Stratified</label> + <div class="col-xs-6 controls"> + <label class="radio-inline"> + <input type="radio" name="perm_strata" value="True" checked=""> + Yes + </label> + <label class="radio-inline"> + <input type="radio" name="perm_strata" value="False" > + No + </label> + </div> + </div> + {% endif %} + <div class="mapping_method_fields form-group"> + <label for="mapmodel_rqtl_geno" class="col-xs-3 control-label">Model</label> + <div class="col-xs-4 controls"> + <select id="mapmodel_rqtl_geno" name="mapmodel_rqtl_geno" class="form-control"> + <option value="normal">Normal</option> + {% if binary == "true" %}<option value="binary">Binary</option>{% endif %} + <!--<option value="2part">2-part</option>--> + <option value="np">Non-parametric</option> + </select> + </div> + </div> + + <div class="mapping_method_fields form-group"> + <label for="mapmethod_rqtl_geno" class="col-xs-3 control-label">Method</label> + <div class="col-xs-6 controls"> + <select id="mapmethod_rqtl_geno" name="mapmethod_rqtl_geno" class="form-control"> + <option value="hk" selected>Haley-Knott</option> + <option value="ehk">Extended Haley-Knott</option> + <option value="mr">Marker Regression</option> + <option value="em">Expectation-Maximization</option> + <option value="imp">Imputation</option> + </select> + </div> + </div> + <div id="missing_geno_div" class="mapping_method_fields form-group" style="display: none;"> + <label for="missing_genotypes" class="col-xs-3 control-label"></label> + <div class="col-xs-6 controls"> + <select id="missing_genotype" name="missing_genotypes" class="form-control"> + <option value="mr">Remove Samples w/o Genotypes</option> + <option value="mr-imp">Single Imputation</option> + <option value="mr-argmax">Imputation w/ Viterbi Algorithm</option> + </select> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label class="col-xs-3 control-label">Manhattan<br>Plot</label> + <div class="col-xs-6 controls"> + <label class="radio-inline"> + <input type="radio" name="manhattan_plot_rqtl" value="True"> + Yes + </label> + <label class="radio-inline"> + <input type="radio" name="manhattan_plot_rqtl" value="False" checked=""> + No + </label> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label class="col-xs-3 control-label">Covariates<br><span class="covar-text">Select covariate(s) from a collection</span></label> + <div class="col-xs-8 covar-options"> + <div class="select-covar-div"> + <button type="button" class="btn btn-success select-covar-button select_covariates">Select</button> + <button type="button" class="btn btn-default select-covar-button remove_covariates">Remove</button> + <button type="button" class="btn btn-danger select-covar-button remove_all_covariates">Clear</button> + </div> + <select size="2" name="selected_covariates_rqtl" class="form-control selected-covariates" multiple> + <option value="">No covariates selected</option> + </select> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label class="col-xs-3 control-label"></label> + <div class="col-xs-6 controls"> + <button id="rqtl_geno_compute" type="button" class="btn submit_special btn-success" title="Compute Marker Regression" value="Compute" disabled>Loading...</button> + </div> + </div> + </div> + </div> + <div class="tab-pane" id="rqtl_pair"> + <div class="form-horizontal section-form-div"> + {% if genofiles and genofiles|length > 0 %} + <div class="mapping_method_fields form-group"> + <label for="genofiles" class="col-xs-3 control-label">Genotypes</label> + <div class="col-xs-6 controls"> + <select id="genofile_rqtl_pair" class="form-control"> + {% for item in genofiles %} + <option value="{{item['location']}}:{{item['title']}}">{{item['title']}}</option> + {% endfor %} + </select> + </div> + </div> + {% endif %} + <div class="mapping_method_fields form-group"> + <label for="control_for" class="col-xs-3 control-label">Control for</label> + <div class="col-xs-6 controls"> + <input name="control_rqtl_pair" value="{% if dataset.type == 'ProbeSet' and this_trait.locus_chr != '' %}{{ nearest_marker }}{% endif %}" type="text" class="form-control cofactor-input" /> + <label class="radio-inline"> + <input type="radio" name="do_control_rqtl" value="true"> + Yes + </label> + <label class="radio-inline"> + <input type="radio" name="do_control_rqtl" value="false" checked=""> + No + </label> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label for="mapmodel_rqtl_pair" class="col-xs-3 control-label">Model</label> + <div class="col-xs-4 controls"> + <select id="mapmodel_rqtl_pair" name="mapmodel_rqtl_pair" class="form-control"> + <option value="normal">Normal</option> + {% if binary == "true" %}<option value="binary">Binary</option>{% endif %} + <!--<option value="2part">2-part</option>--> + <option value="np">Non-parametric</option> + </select> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label for="mapmethod_rqtl_pair" class="col-xs-3 control-label">Method</label> + <div class="col-xs-6 controls"> + <select id="mapmethod_rqtl_pair" name="mapmethod_rqtl_pair" class="form-control"> + <option value="hk" selected>Haley-Knott</option> + <option value="ehk">Extended Haley-Knott</option> + <option value="mr">Marker Regression</option> + <option value="em">Expectation-Maximization</option> + <option value="imp">Imputation</option> + </select> + </div> + </div> + <div id="missing_geno_pair_div" class="mapping_method_fields form-group" style="display: none;"> + <label for="missing_genotypes_pair" class="col-xs-3 control-label"></label> + <div class="col-xs-6 controls"> + <select id="missing_genotype_pair" name="missing_genotypes" class="form-control"> + <option value="mr">Remove Samples w/o Genotypes</option> + <option value="mr-imp">Single Imputation</option> + <option value="mr-argmax">Imputation w/ Viterbi Algorithm</option> + </select> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label class="col-xs-3 control-label">Covariates<br><span class="covar-text">Select covariate(s) from a collection</span></label> + <div class="col-xs-8 covar-options"> + <div class="select-covar-div"> + <button type="button" class="btn btn-default select-covar-button select_covariates">Select</button> + <button type="button" class="btn btn-default select-covar-button remove_covariates">Remove</button> + </div> + <textarea rows="3" cols="50" readonly placeholder="No covariates selected" class="selected-covariates"></textarea> + </div> + </div> + <div class="mapping_method_fields form-group"> + <label class="col-xs-3 control-label"></label> + <div class="col-xs-6 controls"> + <button id="rqtl_pair_compute" type="button" class="btn submit_special btn-success" data-url="/marker_regression" title="Compute Pair Scan" value="Compute" disabled>Loading...</button> + </div> + </div> + </div> + </div> + {% endif %} + {% endfor %} + </div> + </div> + </div> + <div class="descriptions"> + <div class="section-form-div"> + <dl> + {% for mapping_method in dataset.group.mapping_names %} + {% if mapping_method == "GEMMA" %} + <dt>GEMMA</dt> + <dd>GEMMA maps with correction for kinship using a linear mixed model and can include covariates such as sex and age. Defaults include a minor allele frequency of 0.05 and the leave-one-chromosome-out method (<a href="https://www.ncbi.nlm.nih.gov/pubmed/24531419">PMID: 2453419</a>, and <a href="https://github.com/genetics-statistics/GEMMA"> GitHub code</a>).</dd> + {% elif mapping_method == "R/qtl" %} + <dt class="map-method-text">R/qtl (version 1.44.9)</dt> + <dd><a href="https://www.ncbi.nlm.nih.gov/pubmed/12724300">R/qtl</a> maps using several models and uniquely support 4-way intercrosses such as the "Aging Mouse Lifespan Studies" (NIA UM-HET3). We will add support for R/qtl2 (<a href="https://www.ncbi.nlm.nih.gov/pubmed/30591514">PMID: 30591514</a>) in 2023—a version that handles complex populations with admixture and many haplotypes.</dd> + <dt class="map-method-text">Pair Scan (R/qtl v 1.44.9)</dt> + <dd>The Pair Scan mapping tool performs a search for joint effects of two separate loci that may influence a trait. This search typically requires large sample sizes. Pair Scans can included covariates such as age and sex. For more on this function by K. Broman and colleagues see www.rdocumentation.org/packages/qtl/versions/1.60/topics/scantwo</dd> + {% elif mapping_method == "QTLReaper" %} + <dt class="map-method-text">Haley-Knott Regression</dt> + <dd>HK regression (QTL Reaper) is a fast mapping method with permutation that works well with F2 intercrosses and backcrosses (<a href="https://www.ncbi.nlm.nih.gov/pubmed/16718932">PMID 16718932</a>), but is not recommended for admixed populations, advanced intercrosses, or strain families such as the BXDs (<a href="https://github.com/pjotrp/QTLReaper">QTL Reaper code</a>).</dd> + {% endif %} + {% endfor %} + </dl> + <div class="rqtl-description"> + More information on R/qtl mapping models and methods can be found <a href="http://www.rqtl.org/tutorials/rqtltour.pdf">here</a>. + </div> + </div> + </div> + <div id="mapping_result_holder_wrapper" style="display:none;"> + <div id="mapping_result_holder"></div> + </div> + {% else %} + Mapping options are disabled for data not matched with genotypes. + {% endif %} +</div> diff --git a/gn2/wqflask/templates/show_trait_progress_bar.html b/gn2/wqflask/templates/show_trait_progress_bar.html new file mode 100644 index 00000000..f9a34070 --- /dev/null +++ b/gn2/wqflask/templates/show_trait_progress_bar.html @@ -0,0 +1,35 @@ +<div id="progress_bar_container" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="progress_bar" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h3 id="progress_bar">Loading...</h3> + </div> + <div class="modal-body"> + <div class="progress"> + <div id="marker_regression_progress" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:0%;"> + </div> + </div> + <div id="time_remaining"> + </div> + </div> + </div> + </div> +</div> + +<div id="static_progress_bar_container" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="progress_bar" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h3 id="progress_bar">Loading... (Estimated time ~10-15m)</h3> + </div> + <div class="modal-body"> + <div class="progress progress-striped active"> + <div id="marker_regression_progress" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:100%;"> + </div> + </div> + <div id="time_remaining"> + </div> + </div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/gn2/wqflask/templates/show_trait_statistics.html b/gn2/wqflask/templates/show_trait_statistics.html new file mode 100644 index 00000000..9ee0de5c --- /dev/null +++ b/gn2/wqflask/templates/show_trait_statistics.html @@ -0,0 +1,106 @@ +<div> + <div class="tabbable"> <!-- Only required for left/right tabs --> + <ul class="nav nav-pills"> + <li class="active"> + <a href="#stats_tab" data-toggle="tab">Basic Statistics</a> + </li> + <li> + <a href="#histogram_tab" class="histogram_tab" data-toggle="tab">Histogram</a> + </li> + {% if num_values < 256 %} + <li> + <a href="#bar_chart_tab" class="bar_chart_tab" data-toggle="tab">Bar Chart</a> + </li> + {% endif %} + <li> + <a href="#probability_plot" class="prob_plot_tab" data-toggle="tab">Probability Plot</a> + </li> + <li> + <a href="#violin_plot_tab" class="violin_plot_tab" data-toggle="tab">Violin Plot</a> + </li> + </ul> + <div class="tab-content"> + <div class="tab-pane active" id="stats_tab"> + <div class="form-horizontal section-form-div"> + <table id="stats_table" style="width: {{ stats_table_width }}px;" class="table table-hover table-striped table-bordered left-float"></table> + </div> + </div> + <div class="tab-pane" id="histogram_tab"> + <div class="form-horizontal section-form-div"> + {% if sample_groups|length != 1 %} + Select Group: + <select class="histogram_samples_group"> + {% for group, pretty_group in sample_group_types.items() %} + <option value="{{ group }}">{{ pretty_group }}</option> + {% endfor %} + </select> + <br><br> + {% endif %} + <div id="histogram_container"> + <div id="histogram" class="barchart"></div> + </div> + </div> + </div> + {% if num_values < 256 %} + <div class="tab-pane" id="bar_chart_tab"> + <div class="form-horizontal section-form-div"> + {% if sample_groups|length != 1 %} + Select Group: + <select class="bar_chart_samples_group"> + {% for group, pretty_group in sample_group_types.items() %} + <option value="{{ group }}">{{ pretty_group }}</option> + {% endfor %} + </select> + {% endif %} + + <div id="update_bar_chart" class="btn-group"> + <button type="button" class="btn btn-default sort_by_name" value="name"> + <i class="icon-resize-horizontal"></i> Sort By Name + </button> + <button type="button" class="btn btn-default sort_by_value" value="value"> + <i class="icon-signal"></i> Sort By Value + </button> + </div> + <div id="bar_chart_container"> + <div id="bar_chart"></div> + </div> + </div> + </div> + {% endif %} + <div class="tab-pane" id="probability_plot"> + <div class="form-horizontal section-form-div"> + {% if sample_groups|length != 1 %} + Select Group: + <select class="prob_plot_samples_group"> + {% for group, pretty_group in sample_group_types.items() %} + <option value="{{ group }}">{{ pretty_group }}</option> + {% endfor %} + </select> + <br> + <br> + {% endif %} + + <div id="prob_plot_container"> + <div id="prob_plot_div"></div> + </div> + <div id="shapiro_wilk_text"></div> + <br> + <div> + More about <a href="http://en.wikipedia.org/wiki/Normal_probability_plot">Normal Probability Plots</a> and more + about interpreting these plots from the <a href="http://genenetwork.org/glossary.html#normal_probability">glossary</a> + </div> + </div> + </div> + <div class="tab-pane" id="violin_plot_tab"> + <div class="form-horizontal section-form-div"> + <div id="violin_plot_container"> + <div id="violin_plot"></div> + </div> + </div> + </div> + </div> + </div> + <div id="collections_holder_wrapper" style="display:none;"> + <div id="collections_holder"></div> + </div> +</div> diff --git a/gn2/wqflask/templates/show_trait_transform_and_filter.html b/gn2/wqflask/templates/show_trait_transform_and_filter.html new file mode 100644 index 00000000..0706f64d --- /dev/null +++ b/gn2/wqflask/templates/show_trait_transform_and_filter.html @@ -0,0 +1,140 @@ +<div> + <div class="form-horizontal"> + <p>Edit or delete values in the Trait Data boxes, and use the + <strong>Reset</strong> option as + needed. + </p> + <div id="blockMenuSpan" class="input-append block-div"> + <label for="remove_samples_field">Block samples by index:</label> + <input type="text" id="remove_samples_field" placeholder="Example: 3, 5-10, 12"> + <select id="block_group" size="1"> + <option value="primary"> + {{ sample_group_types['samples_primary'] }} + </option> + <option value="other"> + {{ sample_group_types['samples_other'] }} + </option> + </select> + <input type="button" id="block_by_index" class="btn btn-danger" value="Block"> + </div> + <div id="remove_samples_invalid" class="alert alert-error" style="display:none;"> + Please check that your input is formatted correctly, e.g. <strong>3, 5-10, 12</strong> + </div> + {% if categorical_attr_exists == "true" %} + <div class="input-append block-div-2"> + <label for="exclude_column">Block samples by group:</label> + <select id="exclude_column" size=1> + {% for attribute in sample_groups[0].attributes %} + {% if sample_groups[0].attributes[attribute].distinct_values|length <= 500 and sample_groups[0].attributes[attribute].distinct_values|length > 1 %} + <option value="{{ loop.index }}"> + {{ sample_groups[0].attributes[attribute].name }} + </option> + {% endif %} + {% endfor %} + </select> + <select id="attribute_values" size=1> + </select> + <select id="exclude_by_attr_group" size="1"> + <option value="primary"> + {{ sample_group_types['samples_primary'] }} + </option> + <option value="other"> + {{ sample_group_types['samples_other'] }} + </option> + </select> + <input type="button" id="exclude_by_attr" class="btn btn-danger" value="Block"> + </div> + {% endif %} + {% if study_samplelists|length > 0 %} + <div id="filterMenuSpan" class="input-append block-div-2"> + <label for="filter_study_select">Filter samples by study: </label> + <select id="filter_study"> + {% for study in study_samplelists %} + <option value="{{ loop.index - 1 }}">{{ study }}</option> + {% endfor %} + </select> + {% if sample_groups|length != 1 %} + <select id="filter_study_group" size="1"> + <option value="primary"> + {{ sample_group_types['samples_primary'] }} + </option> + <option value="other"> + {{ sample_group_types['samples_other'] }} + </option> + </select> + {% endif %} + <input type="button" id="filter_by_study" class="btn btn-danger" value="Filter"> + </div> + {% endif %} + <div id="filterMenuSpan" class="input-append block-div-2"> + <label for="filter_samples_field">Filter samples by {% if (numerical_var_list|length == 0) and (not js_data.se_exists) %}value{% endif %} </label> + {% if (numerical_var_list|length > 0) or js_data.se_exists %} + <select id="filter_column"> + <option value="value">Value</option> + {% if js_data.se_exists %} + <option value="stderr">SE</option> + {% endif %} + {% for attribute in sample_groups[0].attributes %} + {% if sample_groups[0].attributes[attribute].name in numerical_var_list %} + <option value="{{ loop.index }}"> + {{ sample_groups[0].attributes[attribute].name }} + </option> + {% endif %} + {% endfor %} + </select> + {% endif %} + <select id="filter_logic" size="1"> + <option value="greater_than">></option> + <option value="less_than"><</option> + <option value="greater_or_equal">≥</option> + <option value="less_or_equal">≤</option> + </select> + <input type="text" id="filter_value" placeholder="Example: 3, 10, 15"> + <select id="filter_group" size="1"> + <option value="primary"> + {{ sample_group_types['samples_primary'] }} + </option> + <option value="other"> + {{ sample_group_types['samples_other'] }} + </option> + </select> + <input type="button" id="filter_by_value" class="btn btn-danger" value="Filter"> + </div> + <div> + <input data-active="false" type="button" id="hide_no_value" class="btn btn-default" value="Hide No Value"> + <input type="button" id="block_outliers" class="btn btn-default" value="Block Outliers"> + <button type="button" class="btn btn-success reset"><span class="glyphicon glyphicon-repeat"></span> Reset</button> + <span class="input-append"> + <button class="btn btn-default export"><span class="glyphicon glyphicon-download-alt"></span> Export</button> + <select class="select optional span2 export_format"> + <option value="excel">Excel</option> + <option value="csv">CSV</option> + </select> + </span> + <br> + <div class="normalize-div"> + <input type="button" id="normalize" class="btn btn-success" value="Normalize"> + <select id="norm_method" class="select optional span2"> + {% if negative_vals_exist == "false" %} + <option value="log2">Log2</option> + <option value="log10">Log10</option> + <option value="sqrt">Square Root</option> + {% endif %} + <option value="zscore">Z-score</option> + <option value="qnorm">Quantile</option> + <option value="invert">Invert +/-</option> + </select> + </div> + </div> + </div> + <br> + <div> + <p>Outliers highlighted in + <strong style="background-color:orange;">orange</strong> + can be hidden using + the <strong>Hide Outliers</strong> button. + </p> + + <p>Samples with no value (x) can be hidden by clicking<strong>Hide No Value</strong> button.</p> + </div> +</div> diff --git a/gn2/wqflask/templates/snp_browser.html b/gn2/wqflask/templates/snp_browser.html new file mode 100644 index 00000000..b9aea570 --- /dev/null +++ b/gn2/wqflask/templates/snp_browser.html @@ -0,0 +1,582 @@ +{% extends "base.html" %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('js', filename='DataTablesExtensions/buttonsBootstrap/css/buttons.bootstrap.css') }}" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/typeahead-bootstrap.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/snp_browser.css" /> +{% endblock %} +{% block content %} + + <div class="container-fluid"> + <h2>Variant Browser <a class="btn btn-primary" href="http://genenetwork.org/snpbrowser.html" role="button">Info</a></h2> + <hr style="height: 1px; background-color: #A9A9A9;"> + <div class="container" style="border-style: double; position: relative; width: 950px; padding-top: 10px; padding-right: 40px;"> + <form id="snp_browser_form" method="get" action="/snp_browser"> + <input type="hidden" name="first_run" value="{{ first_run }}"> + <input type="hidden" name="chosen_strains_mouse" value="{{ chosen_strains_mouse|join(",") }}"> + <input type="hidden" name="chosen_strains_rat" value="{{ chosen_strains_rat|join(",") }}"> + <div class="col-xs-4" style="width: 260px; padding-left: 30px; padding-right: 0px;"> + <div class="form-group row" style="margin-bottom: 5px;"> + <label for="snp_or_indel" style="text-align: right;" class="col-xs-4 col-form-label"><b>Type:</b></label> + <div class="col-xs-8"> + <select name="variant"> + <option value="SNP" {% if variant_type == "SNP" %}selected{% endif %}>SNP</option> + <option value="InDel" {% if variant_type == "InDel" %}selected{% endif %}>InDel</option> + </select> + </div> + </div> + <div class="form-group row" style="margin-bottom: 5px;"> + <label for="species" style="text-align: right;" class="col-xs-4 col-form-label"><b>Species:</b></label> + <div class="col-xs-8"> + <select id="species_select" name="species"> + <option value="Mouse" {% if species_name == "Mouse" %}selected{% endif %}>Mouse</option> + <option value="Rat" {% if species_name == "Rat" %}selected{% endif %}>Rat</option> + <option value="Human" disabled>Human</option> + </select> + </div> + </div> + <div class="form-group row" style="margin-bottom: 5px;"> + <label for="gene_or_id" style="text-align: right;" class="col-xs-4 col-form-label"><b>Gene or ID:</b></label> + <div class="col-xs-8"> + <input type="text" name="gene_name" size="12" value="{{ gene_name }}"> + </div> + </div> + <div class="form-group row"> + <div style="text-align: center;"><b><font color="red">Or select</font></b></div> + </div> + <div class="form-group row" style="margin-bottom: 5px;"> + <label for="chr" style="text-align: right;" class="col-xs-4 col-form-label"><b>Chr:</b></label> + <div class="col-xs-8"> + <select id="chr_select" name="chr"> + {% for item in this_chr_list %} + <option value="{{ item }}" {% if item == chr %}selected{% endif %}>{{ item }}</option> + {% endfor %} + </select> + </div> + </div> + <div class="form-group row" style="margin-bottom: 5px;"> + <label for="start_mb" style="text-align: right;" class="col-xs-4 col-form-label"><b>Mb:</b></label> + <div class="col-xs-8"> + <input type="text" name="start_mb" size="10" value="{{ start_mb }}"> + </div> + </div> + <div class="form-group row"> + <label for="end_mb" style="text-align: right;" class="col-xs-4 col-form-label">to</label> + <div class="col-xs-8"> + <input type="text" name="end_mb" size="10" value="{{ end_mb }}"> + </div> + </div> + <hr> + <div class="form-group row"> + <label class="col-xs-4 col-form-label"></label> + <div class="col-xs-8"> + <button class="btn btn-primary" type="submit">Search</button> + </div> + </div> + </div> + <div class="col-xs-4" style="width: 310px; padding-left: 0px; padding-right: 20px;"> + <div class="form-group row" style="margin-bottom: 10px;"> + <label for="strains" style="text-align: right;" class="col-xs-4 col-form-label"><b>Strains:</b></label> + <div class="col-xs-8"> + <select id="strain_select" name="strains" style="width: 70%;"> + {% if species_name == "Mouse" %} + {% for strain in strain_lists['mouse'] %} + <option value="{{ strain }}" {% if loop.index == 1 %}selected{% endif %}>{{ strain }}</option> + {% endfor %} + {% elif species_name == "Rat" %} + {% for strain in strain_lists['rat'] %} + <option value="{{ strain }}" {% if loop.index == 1 %}selected{% endif %}>{{ strain }}</option> + {% endfor %} + {% endif %} + </select> + <div style="float: right; line-height: 20px;"> + <input class="btn btn-primary" type="button" name="add_strain" value="Add" style="vertical-align: middle;"> + </div> + </div> + </div> + <div class="form-group row"> + <label for="chosen_strains_select" style="text-align: right;" class="col-xs-4 col-form-label"><b><font color="red">Limit to:</font></b> <input type="checkbox" name="limit_strains" {% if limit_strains == "true" %}checked{% endif %} size="100"></label> + <div class="col-xs-8"> + <select id="chosen_strains_select" size="11" style="width: 70%;"> + {% for strain in chosen_strains %} + <option value="{{ strain }}">{{ strain }}</option> + {% endfor %} + </select> + <div style="float: right; line-height: 189px;"> + <input class="btn btn-primary" type="button" name="remove_strain" value="Cut" style="vertical-align: middle;"> + </div> + </div> + </div> + <div class="form-group row"> + <label class="col-xs-4 col-form-label"></label> + <div class="col-xs-8" style="margin-top: 65px;"> + <input class="btn btn-primary" type="button" name="export_csv" value="Export to CSV"> + </div> + </div> + </div> + <div class="col-xs-4" style="width: 310px; padding-left: 20px;"> + <div class="form-group row" style="margin-bottom: 5px;"> + <label for="domain" style="text-align: right;" class="col-xs-4 col-form-label"><b>Domain:</b></label> + <div class="col-xs-8"> + <select name="domain" size="4"> + <option value="All" {% if domain == "All" %}selected{% endif %}>All</option> + <option value="Exon" {% if domain == "Exon" %}selected{% endif %}>Exon</option> + <option value="5' UTR" {% if domain == "5' UTR" %}selected{% endif %}> 5' UTR</option> + <option value="Coding" {% if domain == "Coding" %}selected{% endif %}> Coding Region</option> + <option value="3' UTR" {% if domain == "3' UTR" %}selected{% endif %}> 3' UTR</option> + <option value="Intron" {% if domain == "Intron" %}selected{% endif %}>Intron</option> + <option value="Splice Site" {% if domain == "Splice Site" %}selected{% endif %}> Splice Site</option> + <option value="Nonsplice Site" {% if domain == "Nonsplice Site" %}selected{% endif %}> Nonsplice Site</option> + <option value="Upstream" {% if domain == "Upstream" %}selected{% endif %}>Upstream</option> + <option value="Downstream" {% if domain == "Downstream" %}selected{% endif %}>Downstream</option> + <option value="Intergenic" {% if domain == "Intergenic" %}selected{% endif %}>Intergenic</option> + </select> + </div> + </div> + <div class="form-group row" style="margin-bottom: 5px;"> + <label for="function" style="text-align: right;" class="col-xs-4 col-form-label"><b>Function:</b></label> + <div class="col-xs-8"> + <select name="function" size="3"> + <option value="All" {% if function == "All" %}selected{% endif %}>All</option> + <option value="Nonsynonymous" {% if function == "Nonsynonymous" %}selected{% endif %}>Nonsynonymous</option> + <option value="Synonymous" {% if function == "Synonymous" %}selected{% endif %}>Synonymous</option> + <option value="Start Gained" {% if function == "Start Gained" %}selected{% endif %}>Start Gained</option> + <option value="Start Lost" {% if function == "Start Lost" %}selected{% endif %}>Start Lost</option> + <option value="Stop Gained" {% if function == "Stop Gained" %}selected{% endif %}>Stop Gained</option> + <option value="Stop Lost" {% if function == "Stop Lost" %}selected{% endif %}>Stop Lost</option> + </select> + </div> + </div> + <div class="form-group row" style="margin-bottom: 5px;"> + <label for="source" style="text-align: right;" class="col-xs-4 col-form-label"><b>Source:</b></label> + <div class="col-xs-8"> + <select name="source"> + <option value="All" {% if source == "All" %}selected{% endif %}>All</option> + <option value="None" {% if source == "None" %}selected{% endif %}>None</option> + <option value="dbSNP" {% if source == "dbSNP" %}selected{% endif %}>dbSNP</option> + <option value="dbSNP (release 149)" {% if source == "dbSNP (release 149)" %}selected{% endif %}>dbSNP (release 149)</option> + <option value="Sanger/UCLA" {% if source == "Sanger/UCLA" %}selected{% endif %}>Sanger/UCLA</option> + <option value="UTHSC_CITG" {% if source == "UTHSC_CITG" %}selected{% endif %}>UTHSC_CITG</option> + </select> + </div> + </div> + <div class="form-group row"> + <label for="criteria" style="text-align: right;" class="col-xs-4 col-form-label"><b>ConScore:</b></label> + <div class="col-xs-8"> + <select name="criteria" size="1"> + <option value=">=" {% if criteria == ">=" %}selected{% endif %}>>=</option> + <option value="==" {% if criteria == "==" %}selected{% endif %}>=</option> + <option value="<=" {% if criteria == "<=" %}selected{% endif %}><=</option> + </select> + <input type="text" name="score" value="{{ score }}" size="5"> + </div> + </div> + <div class="form-group row" style="margin-bottom: 5px;"> + <label style="text-align: right;" class="col-xs-4 col-form-label"><input type="checkbox" name="redundant" {% if redundant == "true" %}checked{% endif %}></label> + <div class="col-xs-8"> + Non-redundant SNP Only + </div> + </div> + <div class="form-group row"> + <label style="text-align: right;" class="col-xs-4 col-form-label"><input type="checkbox" name="diff_alleles" {% if diff_alleles == "true" %}checked{% endif %}></label> + <div class="col-xs-8"> + Different Alleles Only + </div> + </div> + </div> + </form> + </div> + + <div style="margin-top: 20px;"> + {% if table_rows is defined %} + <table class="dataTable cell-border nowrap" id="results_table" style="float: left;"> + <thead> + <tr> + <th></th> + {% if header_fields|length == 2 %} + {% for header in header_fields[0] %} + <th data-export="{{ header }}" name="{{ header }}">{{ header }}</th> + {% endfor %} + {% for strain in header_fields[1] %} + <th data-export="{{ strain }}" name="{{ strain }}" style="align: center; text-align: center; line-height: 12px;">{% for letter in strain|reverse %}<div style="transform: rotate(270deg);">{{ letter }}</div>{% endfor %}</th> + {% endfor %} + {% else %} + {% for header in header_fields %} + <th data-export="{{ header }}" name="{{ header }}">{{ header }}</th> + {% endfor %} + {% endif %} + </tr> + </thead> + <tbody> + <td colspan="100%" align="center"><br><b><font size="15">Loading...</font></b><br></td> + </tbody> + </table> + {% endif %} + </div> + </div> + +{% endblock %} +{% block js %} + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/natural.js') }}"></script> + <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='typeahead/typeahead.bundle.js') }}"></script> + + <script language="javascript" type="text/javascript" src="/static/new/javascript/typeahead_rn6.json"></script> + + <script type='text/javascript'> + var empty_columns = {{ empty_columns|safe }}; + + var remain_field_count = 15 - {{ empty_field_count|safe }}; + var total_field_count = 15 - {{ empty_field_count|safe }} + {{ allele_list|safe|length }}; + </script> + + <script language="javascript"> + + var getParams = function(url) { + let parser = document.createElement('a'); + parser.href = url; + let params = parser.search.substring(1); + if(params.length > 0) { + return ('?'+params); + } + return params; + }; + + var substringMatcher = function(strs) { + return function findMatches(q, cb) { + var matches, substringRegex; + + // an array that will be populated with substring matches + matches = []; + + // regex used to determine if a string contains the substring `q` + substrRegex = new RegExp(q, 'i'); + + // iterate through the pool of strings and for any string that + // contains the substring `q`, add it to the `matches` array + $.each(strs, function(i, str) { + if (substrRegex.test(str)) { + matches.push(str); + } + }); + + cb(matches); + }; + }; + + $('input[name=gene_name]').typeahead({ + minLength: 2, + hint: true, + highlight: true + }, + { + name: 'rn6-genes', + source: substringMatcher(rat_genes) + }); + + {% if table_rows is defined %} + $("#results_table").DataTable( { + {% if variant_type == "SNP" %} + 'columns': [ + { + 'data': null, + 'className': 'dt-body-center', + 'orderable': false, + 'render': function(data, type, row, meta) { + return '<input type="checkbox" class="checkbox" id="variant_checkbox" onchange="onVarinatCheck(this)" name="trait_check">' + } + }, { + 'data': 'index', + 'className': 'dt-body-right' + }, { + 'data': null, + 'render': function(data, type, row, meta) { + if (data.rs != "") { + return '<b><a href="' + data.snp_url + '">' + data.snp_name + '</a></b>' + } else { + return '<a href="' + data.snp_url + '">' + data.snp_name + '</a>' + } + } + }, { + 'data': 'chr', + 'className': 'dt-body-center' + }, { + 'data': 'mb_formatted', + 'className': 'dt-body-right' + }, { + 'data': 'alleles' + }, {% if empty_columns['snp_source'] == "true" %}{ + 'data': null, + 'render': function(data, type, row, meta) { + if (data.snp_source == "Sanger/UCLA") { + return '<a href="' + data.source_urls[0] + '">Sanger</a>, <a href="' + data.source_urls[1] + '">UCLA</a>' + } else { + return data.snp_source + } + } + }, {% endif %} {% if empty_columns['conservation_score'] == "true" %}{ + 'data': 'conservation_score', + 'className': 'dt-body-right' + }, {% endif %} {% if empty_columns['gene_name'] == "true" %}{ + 'data': null, + 'render': function(data, type, row, meta) { + if (data.gene_name != "") { + return '<i>' + data.gene_name + '</i>, <a href="' + data.gene_link + '">NCBI</a>' + } else { + return data.gene_name + } + } + }, {% endif %} {% if empty_columns['transcript'] == "true" %}{ + 'data': null, + 'render': function(data, type, row, meta) { + if (data.transcript != "") { + return '<a href="' + data.transcript_link + '">' + data.transcript + '</a>' + } else { + return data.transcript + } + } + }, {% endif %} {% if empty_columns['exon'] == "true" %}{ + 'data': 'exon' + }, {% endif %}{ + 'data': 'domain_1' + }, {% if empty_columns['domain_2'] == "true" %}{ + 'data': 'domain_2' + }, {% endif %} {% if empty_columns['function'] == "true" %}{ + 'data': 'function' + }, {% endif %} {% if empty_columns['function_details'] == "true" %}{ + 'data': 'function_details' + }, {% endif %} {% for item in allele_list %} { + 'data': null, + 'orderable': false, + 'className': 'dt-body-center', + 'render': function(data, type, row, meta) { + if (typeof data.allele_value_list[{{ loop.index - 1 }}][0] !== "undefined") { + return data.allele_value_list[{{ loop.index - 1 }}][0] + } else { + return '' + } + } + }{% if loop.index < allele_list|length %},{% endif %}{% endfor %} + ], + 'createdRow': function(row, data, dataIndex) { + for (i = remain_field_count; i < total_field_count; i++) { + var this_allele = $('td', row).eq(i).text(); + switch (this_allele) { + case "A": + $('td', row).eq(i).addClass('A_allele_color'); + break; + case "C": + $('td', row).eq(i).addClass('C_allele_color'); + break; + case "T": + $('td', row).eq(i).addClass('T_allele_color'); + break; + case "G": + $('td', row).eq(i).addClass('G_allele_color'); + break; + case "t": + $('td', row).eq(i).addClass('t_allele_color'); + break; + case "c": + $('td', row).eq(i).addClass('c_allele_color'); + break; + case "a": + $('td', row).eq(i).addClass('a_allele_color'); + break; + case "g": + $('td', row).eq(i).addClass('g_allele_color'); + break; + default: + $('td', row).eq(i).addClass('default_allele_color'); + } + } + }, + {% else %} + 'columns': [ + { + 'data': null, + 'render': function(data, type, row, meta) { + return '<input type="checkbox" class="checkbox" id="variant_checkbox" onchange="onVarinatCheck(this)" name="trait_check">' + } + }, { + 'data': 'index', + 'className': 'dt-body-right' + }, { + 'data': 'indel_name' + }, { + 'data': 'indel_type' + }, { + 'data': 'indel_chr', + 'className': 'dt-body-center' + }, { + 'data': 'indel_mb_s', + 'className': 'dt-body-right' + }, { + 'data': 'indel_mb_e', + 'className': 'dt-body-right' + }, { + 'data': 'indel_strand' + }, { + 'data': 'indel_size', + 'className': 'dt-body-right' + }, { + 'data': 'indel_sequence' + }, { + 'data': 'source_name' + } + ], + {% endif %} + 'order': [[1, "asc" ]], + 'sDom': "rtip", + 'iDisplayLength': 100, + 'bServerSide': true, + 'sAjaxSource': '/snp_browser_table'+getParams(window.location.href), + 'infoCallback': function(settings, start, end, max, total, pre) { + return "Showing " + start + " to " + (start + this.api().data().length - 1) + " of " + total + " entries"; + } + }); + {% endif %} + + function onVarinatCheck(checkboxElem) { + if (checkboxElem.checked) { + if (!checkboxElem.parentElement.parentElement.classList.contains('selected')) { + checkboxElem.parentElement.parentElement.classList.add('selected') + } + } + else { + if (checkboxElem.parentElement.parentElement.classList.contains('selected')) { + checkboxElem.parentElement.parentElement.classList.remove('selected') + } + } + } + + $("#species_select").change(function() { + this_species = $(this).val(); + $("#strain_select").empty() + $("#chosen_strains_select").empty() + $("#chr_select").empty() + + if (this_species == "Mouse") { + {% for strain in strain_lists["mouse"] %} + var option = $('<option></option>').attr("value", "{{ strain }}").text("{{ strain }}"); + $("select[name=strains]").append(option); + {% endfor %} + + {% for chr in mouse_chr_list %} + var option = $('<option></option>').attr("value", "{{ chr }}").text("{{ chr }}"); + $("select[name=chr]").append(option); + {% endfor %} + + chosen_strains = $("input[name=chosen_strains_mouse]").val().split(",") + } else if (this_species == "Rat") { + {% for strain in strain_lists["rat"] %} + var option = $('<option></option>').attr("value", "{{ strain }}").text("{{ strain }}"); + $("select[name=strains]").append(option); + {% endfor %} + + {% for chr in rat_chr_list %} + var option = $('<option></option>').attr("value", "{{ chr }}").text("{{ chr }}"); + $("select[name=chr]").append(option); + {% endfor %} + + chosen_strains = $("input[name=chosen_strains_rat]").val().split(",") + } + + for (i=0; i < chosen_strains.length; i++) { + var option = $('<option></option>').attr("value", chosen_strains[i]).text(chosen_strains[i]); + $("#chosen_strains_select").append(option) + } + }); + + $("input[name=add_strain]").click(function() { + var selected_strain = $("select[name=strains] option:selected").val(); + + var current_species = $("#species_select").val(); + if (current_species == "Mouse") { + stored_strains = $("input[name=chosen_strains_mouse]").val().split(",") + if (!(stored_strains.includes(selected_strain))){ + stored_strains.push(selected_strain) + $("input[name=chosen_strains_mouse]").val(stored_strains.join(",")) + $("#chosen_strains_select").append("<option value='" + selected_strain + "'>" + selected_strain + "</option>"); + } + } else if (current_species == "Rat") { + stored_strains = $("input[name=chosen_strains_rat]").val().split(",") + if (!(stored_strains.includes(selected_strain))){ + stored_strains.push(selected_strain) + $("input[name=chosen_strains_rat]").val(stored_strains.join(",")) + $("#chosen_strains_select").append("<option value='" + selected_strain + "'>" + selected_strain + "</option>"); + } + } + }); + + $("input[name=remove_strain]").click(function() { + var selected_strain = $("#chosen_strains_select option:selected").val(); + $("#chosen_strains_select option[value='" + selected_strain + "']").remove(); + + var current_species = $("#species_select").val(); + + if (current_species == "Mouse") { + stored_strains = $("input[name=chosen_strains_mouse]").val().split(",") + for (i=0; i < stored_strains.length; i++) { + if (stored_strains[i] == selected_strain) { + stored_strains.splice(i, 1); + break; + } + } + $("input[name=chosen_strains_mouse]").val(stored_strains.join(",")) + } else if (current_species == "Rat") { + stored_strains = $("input[name=chosen_strains_rat]").val().split(",") + for (i=0; i < stored_strains.length; i++) { + if (stored_strains[i] == selected_strain) { + stored_strains.splice(i, 1); + break; + } + } + $("input[name=chosen_strains_rat]").val(stored_strains.join(",")) + } + }); + + $("#snp_browser_form").submit(function() { + var strain_list = []; + $("#chosen_strains_select option").each(function() { + strain_list.push($(this).val()); + }); + $("input[name=chosen_strains]").val(strain_list.join(",")); + }); + + + + $("input[name=export_csv]").click(function() { + var csv = []; + var rows = document.querySelectorAll("table tr"); + + var headers = []; + var col_header = rows[0].querySelectorAll("th"); + for(let i = 1; i < col_header.length; i++) { + headers.push(col_header[i].getAttribute("name")); + } + csv.push(headers.join(",")); + + for (let i = 1; i < rows.length; i++) { + var row = [], cols = rows[i].querySelectorAll("td"); + var checkBox = rows[i].querySelector("input"); + + if(checkBox.checked == true) { + for (let j = 1; j < cols.length; j++) + row.push(cols[j].innerText); + + csv.push(row.join(",")); + } + } + + var csvFile = new Blob([csv.join("\n")], {type: "text/csv"}); + var downloadLink = document.createElement("a"); + downloadLink.download = "variant_data.csv"; + downloadLink.href = window.URL.createObjectURL(csvFile); + downloadLink.style.display = "none"; + document.body.appendChild(downloadLink); + downloadLink.click(); + }); + </script> +{% endblock %} + diff --git a/gn2/wqflask/templates/startup_errors.html b/gn2/wqflask/templates/startup_errors.html new file mode 100644 index 00000000..82d85572 --- /dev/null +++ b/gn2/wqflask/templates/startup_errors.html @@ -0,0 +1,20 @@ +{%extends "base.html"%} +{%block title%}Startup Error{%endblock%} +{%block content %} +{%if error_type == "MissingConfigurationError"%} + +<div class="container"> + <h1>Startup Error</h1> + + <p> + The application could not start due to the missing configuration settings + below: + <ul> + {%for setting in error_value.missing%} + <li class="text-danger"><strong>{{setting}}</strong></li> + {%endfor%} + </ul> + </p> +</div> +{%endif%} +{%endblock%} diff --git a/gn2/wqflask/templates/submit_trait.html b/gn2/wqflask/templates/submit_trait.html new file mode 100644 index 00000000..10ddf69f --- /dev/null +++ b/gn2/wqflask/templates/submit_trait.html @@ -0,0 +1,111 @@ +{% extends "base.html" %} +{% block title %}Trait Submission{% endblock %} +{% block content %} +<!-- Start of body --> + <form method="post" action="/show_temp_trait"> + <div class="container-fluid"> + + {{ flash_me() }} + + <div class="row" style="width: 1400px !important;"> + <div class="col-xs-3"> + <section id="description"> + <div> + <h2 style="color: #5a5a5a;">Introduction</h2> + <hr> + <p>The trait values that you enter are statistically compared with verified genotypes collected at a set of microsatellite markers in each RI set. The markers are drawn from a set of over 750, but for each set redundant markers have been removed, preferentially retaining those that are most informative.</p> + <p>These error-checked RI mapping data match theoretical expectations for RI strain sets. The cumulative adjusted length of the RI maps are approximately 1400 cM, a value that matches those of both MIT maps and Chromosome Committee Report maps. See our <a target="_blank" href="http://www.nervenet.org/papers/BXN.html">full description</a> of the genetic data collected as part of the WebQTL project.</p> + </div> + </section> + <br> + <section id="description"> + <div> + <h2 style="color: #5a5a5a;">About Your Data</h2> + <hr> + <p>You can open a separate window giving the number of strains for each data set and sample data.</p> + <p>None of your submitted data is copied or stored by this system except during the actual processing of your submission. By the time the reply page displays in your browser, your submission has been cleared from this system.</p> + </div> + </section> + </div> + <div style="padding-left:20px" class="col-xs-6" style="width: 600px !important;"> + <section id="submission_form"> + <div class="form-group"> + <h2 style="color: #5a5a5a;">Trait Submission Form</h2> + <hr> + <div style="margin-bottom: 150px;" class="form-horizontal"> + <h3>1. Choose Species and Group:</h3> + <br> + <div class="col-xs-2" style="min-height: 15vh; display: flex; align-items: center;"> + <img src="/static/new/images/step1.gif"> + </div> + <div class="col-xs-10"> + <div class="form-group"> + <label for="species" class="col-xs-2 control-label">Species: </label> + <div class="col-xs-4 controls"> + <select name="species" id="species" class="form-control span3" style="width: 280px !important;"></select> + </div> + </div> + <div class="form-group"> + <label for="group" class="col-xs-2 control-label">Group: </label> + <div class="col-xs-4 controls"> + <select name="group" id="group" class="form-control span3" style="width: 280px !important;"></select> + </div> + </div> + </div> + </div> + <div style="padding-bottom: 50px; margin-bottom:400px" class="form-horizontal"> + <h3>2. Enter Trait Data:</h3> + <h4 style="color:red;">File uploading isn't enabled yet, but is coming soon.</h4> + <br> + <div class="col-xs-2" style="min-height: 50vh; max-height: 100vh; display: flex; align-items: center;"> + <img src="/static/new/images/step2.gif"> + </div> + <div class="col-xs-10"> + <div class="form-group" style="padding-left: 15px;"> + <p> + <b>Paste or Type Multiple Values:</b> You can enter data by pasting a series of numbers representing trait values into this area. + The values can be on one line separated by spaces or tabs, or they can be on separate lines. Include one value for each individual + or line. Use an "x" for missing values. If you have chosen a set of inbred strains, then your data will be displayed in a form in + which you can confirm and/or edit. If you enter a file name in the previous section, + any data that you paste here will be ignored. Check <a href="http://gn1.genenetwork.org/RIsample.html">sample data</a> for the correct format. + </p> + <textarea name="trait_paste" rows="6" cols="70"></textarea> + </div> + </div> + <div class="controls" style="display:block; margin-left: 40%; margin-right: 20%;"> + <input type="submit" style="width: 110px; margin-right: 25px;" class="btn btn-primary form-control col-xs-2" value="Submit Trait"> + <input type="reset" style="width: 110px;" class="btn btn-primary form-control col-xs-2" value="Reset"> + </div> + </div> + <div style="padding-bottom: 50px;" class="form-horizontal"> + <h3>3. Enable Use of Trait Variance:</h3> + <div class="col-xs-2" style=""display: flex; align-items: center;"> + <img src="/static/new/images/step3.gif"> + </div> + <div class="col-xs-10"> + <div class="form-group" style="padding-left: 15px;"> + <p> + <b>Name Your Trait:</b> <span style="color:red;">(optional)</span> + </p> + <textarea name="trait_name" rows="1" cols="30"></textarea> + </div> + </div> + <div class="controls" style="display:block; margin-left: 40%; margin-right: 20%;"> + <input type="submit" style="width: 110px; margin-right: 25px;" class="btn btn-primary form-control col-xs-2" value="Submit Trait"> + <input type="reset" style="width: 110px;" class="btn btn-primary form-control col-xs-2" value="Reset"> + </div> + </div> + </section> + </div> + </div> + </div> + </form> + +{%endblock%} + +{% block js %} + <script> + gn_server_url = "{{ gn_server_url }}"; + </script> + <script src="/static/new/javascript/dataset_select_menu_orig.js"></script> +{% endblock %} diff --git a/gn2/wqflask/templates/test_correlation_page.html b/gn2/wqflask/templates/test_correlation_page.html new file mode 100644 index 00000000..991773a2 --- /dev/null +++ b/gn2/wqflask/templates/test_correlation_page.html @@ -0,0 +1,159 @@ +{% extends "base.html" %} +{% block title %}Correlation Results{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('js', filename='DataTablesExtensions/buttonsBootstrap/css/buttons.bootstrap.css') }}" /> + <link rel="stylesheet" type="text/css" href="{{ url_for('js', filename='DataTablesExtensions/buttonStyles/css/buttons.dataTables.min.css') }}"> + <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"> + <link rel="stylesheet" type="text/css" href="/static/new/css/trait_list.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + + <style type="text/css"> + .td-styles{ + height: 40px; + text-align: center; + } + .trait_col { + font-weight:bolder; + text-align: center; + color:#036ffc; + /*font-size: 1.1em;*/ + } + table th { + font-weight: bolder; + text-transform: uppercase; + } + .correlation-title { + padding:25px 10px; + } + .correlation-title h3 span { + font-weight: bolder; + } + .header-toggle-vis { + padding:10px 5px; + } + .header-toggle-vis button { + border-radius: 5px; + + } + </style> +{% endblock %} + +{% block content %} + +<div class="correlation-title"> + <h3>Correlation Results for <span>{{target_dataset}}</span> against <span><a href="">{{this_trait}}</a></span> for the top <span>{{return_results}}</span> Results</h3> +</div> +<div class="header-toggle-vis"> + <h4 style="font-weight: bolder;padding: 5px 3px;">Toggle Columns</h4> + <button class="toggle-vis" data-column="1">Index</button> + <button class="toggle-vis" data-column="2">Trait Name</button> + <button class="toggle-vis" data-column="3">Sample r</button> + <button class="toggle-vis" data-column="4">Sample P(r)</button> + <button class="toggle-vis" data-column="5">Num overlap</button> +</div> + <table id="example" class="display" width="100%"> + <thead> + <tr > + <th></th> + <th>index</th> + <th>trait_name</th> + <th>Sample r</th> + <th>Sample r(p)</th> + <th>N</th> + <th>Tissue r</th> + <th>Tissue r(p)</th> + <th>Lit r</th> + </tr> + </thead> + </table> + +{% endblock %} + +{% block js %} +<script type="text/javascript" src="{{ url_for('js', filename='js_alt/md5.min.js') }}"></script> +<script type="text/javascript" src="/static/new/javascript/search_results.js"></script> + +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='jszip/jszip.min.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/buttons/js/dataTables.buttons.min.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/buttons/js/buttons.html5.min.js') }}"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/natural.js') }}"></script> +<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/js/all.min.js"></script> +<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script> +<script type="text/javascript"> + let correlationResults = {{correlation_results|safe}} + // document.querySelector(".content").innerHTML =correlationResults + // parse the data + let counter = 0; + let corr_type = "tissue"; + correlationResults =correlationResults.map((trait_object)=>{ + let trait_name = Object.keys(trait_object)[0] + + let new_dict = { + "index":counter, + "trait_name":trait_name, + ...trait_object[trait_name] + } + counter++; + return new_dict; + }) + +console.log(correlationResults) + +</script> + +<script type="text/javascript"> + $(document).ready(function() { + let table = $('#example').DataTable( { + "data": correlationResults, + "columns": [ + {"data":corr_type=="sample"?null:"fd","width":"25px"}, + { "data": "index","width":"120px","title":"Index" }, + { "data": "trait_name","title":"TraitName"}, + { "data": "corr_coefficient","defaultContent": "--"}, + { "data": "p_value","defaultContent":"--"}, + { "data": "num_overlap","defaultContent":"--"}, + {"data":"tissue_corr","defaultContent":"--","title":"Tissue r"}, + {"data":"tissue_p_val","defaultContent":"--","title":"Tissue r(p)"}, + {"data":"lit_corr","defaultContent":"--","title":"Lit rho"} + ], + "columnDefs": [ + { + targets:0, + data:null, + defaultContent: '', + orderable: false, + className: 'select-checkbox', + "render":(data,type,row)=>{ + return `<input type="checkbox" class="checkbox trait_checkbox" value="other">` + } + + }, + {className:"trait_col",targets:2}, + {className: "td-styles", targets: "_all"}, + { + "targets":2, + "render":(data,type,row)=>{ + // should use a dynamic dataset name + let urlLink = `/show_trait?trait_id=${data}&dataset=HC_M2_0606_P` + let traitLink = `<a href=${urlLink}>${data}</a>` + return traitLink + }, + } + + ] + } ); + + $(":button.toggle-vis").on("click",function(e){ + e.preventDefault() + let column = table.column($(this).attr("data-column")); + column.visible(!column.visible()) + console.log($(this).attr("data-column")) + }) +} ); +</script> + +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/tool_buttons.html b/gn2/wqflask/templates/tool_buttons.html new file mode 100644 index 00000000..c6d1476c --- /dev/null +++ b/gn2/wqflask/templates/tool_buttons.html @@ -0,0 +1,38 @@ +<button id="corr_matrix" class="btn btn-primary submit_special" data-url="/corr_matrix" title="Correlation Matrix" > + Correlations +</button> + +<button id="network_graph" class="btn btn-primary submit_special" data-url="/network_graph" title="Network Graph" > + Networks +</button> + +<button id="send_to_webgestalt" class="btn btn-primary submit_special" data-url="/webgestalt_page" title="WebGestalt" > + WebGestalt +</button> + +<button id="send_to_geneweaver" class="btn btn-primary submit_special" data-url="/geneweaver_page" title="GeneWeaver" > + GeneWeaver +</button> + +<button id="send_to_bnw" class="btn btn-primary submit_special" data-url="/bnw_page" title="Bayesian network software for causal modeling and reasoning, with an intuitive interface to incorporate biological knowledge and a complete pipeline from data to model to prediction" > + BNW +</button> + +<button id="wgcna_setup" class="btn btn-primary submit_special" data-url="/wgcna_setup" title="WGCNA Analysis" > + WGCNA +</button> + +<button id="ctl_setup" class="btn btn-primary submit_special" data-url="/ctl_setup" title="CTL Analysis" > + CTL Maps +</button> + +<button id="heatmap" class="btn btn-primary submit_special" data-url="/heatmap" title="Heatmap" > + MultiMap +</button> + +<button id="partial-correlations" + class="btn btn-primary submit_special" + data-url="{{url_for('partial_correlations')}}" + title="Run partial correlations with the selected traits"> + Partial Correlations +</button> diff --git a/gn2/wqflask/templates/tutorials.html b/gn2/wqflask/templates/tutorials.html new file mode 100644 index 00000000..74c84726 --- /dev/null +++ b/gn2/wqflask/templates/tutorials.html @@ -0,0 +1,256 @@ +{% extends "base.html" %} +{% block title %}Tutorials/Primers{% endblock %} +{% block content %} + +<head> + <title>GeneNetwork Webinar Series, Tutorials and Short Video Tours</title> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> +<!-- <link rel="stylesheet" href="uikit-3/css/uikit.min.css" />--> +<!-- <script src="uikit-3/js/uikit.min.js"></script>--> +<!-- <script src="uikit-3/js/uikit-icons.min.js"></script>--> + <!-- UIkit CSS --> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.5.4/dist/css/uikit.min.css" /> + + <!-- UIkit JS --> + <script src="https://cdn.jsdelivr.net/npm/uikit@3.5.4/dist/js/uikit.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/uikit@3.5.4/dist/js/uikit-icons.min.js"></script> + + <!-- DataTables--> + <!-- <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/dt/jq-3.3.1/dt-1.10.21/cr-1.5.2/datatables.min.css"/> --> + <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.22/css/jquery.dataTables.min.css"/> + + <script type="text/javascript" src="https://cdn.datatables.net/v/dt/jq-3.3.1/dt-1.10.21/cr-1.5.2/datatables.min.js"></script> + <script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.js"></script> + <script type="text/javascript" src="https://cdn.datatables.net/1.10.22/js/jquery.dataTables.min.js"></script> + +</head> +<body> +<div class="uk-margin-small uk-card uk-card-default uk-card-body"> + <!-- <div class="row">--> + <!-- <div class="col-lg-12"> + <p>Primary Sponsor</p> + <img class="img-responsive" src="images/illumina_logo.jpg" alt=""> <h2 class="page-header">Program (Preliminary)</h2> + </div>--> + <div class="col-lg-12"> + + <ul id="myTab" class="nav nav-tabs nav-justified"> + <li class="active"><a href="#service-one" data-toggle="tab"><i class="fa fa-file-text-o"></i>Webinars</a> + </li> + <li class=""><a href="#service-two" data-toggle="tab"><i class="fa fa-file-text-o"></i>Short Video Tours</a> + </li> + <li class=""><a href="#service-three" data-toggle="tab"><i class="fa fa-file-text-o"></i>Tutorials</a> + </li> + <li class=""><a href="#service-four" data-toggle="tab"><i class="fa fa-file-text-o"></i>Documentation</a> + </li> + </ul> +<p></p> + <div id="myTabContent" class="tab-content"> + <div class="tab-pane fade active in" id="service-one"> + <table id="myTable" class="display"> + <thead> + <tr> + <th>Title/Description</th> + <th>Presentation</th> + </tr> + </thead> + <tbody> + <tr> + <td><p><h3>Introduction to Quantitative Trait Loci (QTL) Analysis</h3> + <p>Goals of this webinar (trait variance to QTL):</p> + <ul> + <li>Define quantitative trait locus (QTL)</li> + <li>Explain how genome scans can help find QTL</li> + </ul> + <p>Presented by:<br> +Dr. Saunak Sen<br> +Professor and Chief of Biostatistics<br> +Department of Preventative Medicine<br> +University of Tennessee Health Science Center +</p> +<p><a href="https://github.com/OSGA-OPAR/quant-genetics-webinars/blob/master/2020-05-08/README.md">Link to course material</a> +</p></td> + <td> + <iframe width="560" height="315" src="https://www.youtube.com/embed/leY3kPmnLaI" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td> + </tr> + <tr> + <td><p><h3>Mapping Addiction and Behavioral Traits and Getting at Causal Gene Variants with GeneNetwork</h3> + <p>Goals of this webinar (QTL to gene variant):</p> + <ul> + <li>Demonstrate mapping a quantitative trait using GeneNetwork (GN)</li> + <li>Explore GN tools to identify genes and genetics variants related to a QTL</li> + </ul> + <p>Presented by:<br> +Dr. Rob Williams<br> +Professor and Chair<br> +Department of Genetics, Genomics, and Informatics<br> +University of Tennessee Health Science Center +</p><p><a href="https://github.com/OSGA-OPAR/quant-genetics-webinars/blob/master/2020-05-22/README.md">Link to course material</a> +</p></td> + <td><iframe width="560" height="315" src="https://www.youtube.com/embed/LwpXzLHX9aM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td> + </tr> + + <!--NEW WEBINAR STARTS HERE--> + <tr> + <td><p><h3>Data structure, disease risk, GXE, and causal modeling</h3> + + <p>Human disease is mainly due to complex interactions between genetic and environmental factors (GXE). We need to acquire the right "smart" data types—coherent and multiplicative data—required to make accurate predictions about risk and outcome for n = 1 individuals—a daunting task. We have developed large families of fully sequenced mice that mirror the genetic complexity of humans. We are using these Reference Populations to generate multiplicatively useful data and to build and test causal quantitative models of disease mechanisms with a special focus on diseases of aging, addiction, and neurological and psychiatric disease. + +<p>Speaker Bio: Robert (Rob) W. Williams received a BA in neuroscience from UC Santa Cruz (1975) and a Ph.D. in system physiology at UC Davis with Leo M. Chalupa (1983). He did postdoctoral work in developmental neurobiology at Yale School of Medicine with Pasko Rakic where he developed novel stereological methods to estimate cell populations in brain. In 2013 Williams established the Department of Genetics, Genomics and Informatics at UTHSC. He holds the UT Oak Ridge National Laboratory Governor’s Chair in Computational Genomics. Williams is director of the Complex Trait Community (www.complextrait.org) and editor-in-chief of Frontiers in Neurogenomics. One of Williams’ more notable contributions is in the field of systems neurogenetics and experimental precision medicine. He and his research collaborators have built GeneNetwork (www.genenetwork.org), an online resource of data and analysis code that is used as a platform for experimental precision medicine.</p> + + <p>Presented by:<br> +Dr. Rob Williams<br> +Professor and Chair<br> +Department of Genetics, Genomics, and Informatics<br> +University of Tennessee Health Science Center +</p> +<!--<p>There is no fee associated with this webinar, but users are asked to register to receive the Zoom link and password. +Registration: <a href="https://bit.ly/osga_2020-11-20">https://bit.ly/osga_2020-11-20</a>--> +<!--<p>This webinar series is sponsored by the NIDA Center of Excellence in Omics, Systems Genetics, and the Addictome (P30 DA044223). --> +</td> + <!--<td><strong>After the presentation, the recording will be made available here.</strong></td>--> + <td><iframe width="560" height="315" src="https://www.youtube.com/embed/4ZhnXU8gV44" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + </tr> + <!--WEBINAR ENDS HERE--> + + </tbody> + </table> + +<script> +$('#myTable').dataTable( { + "lengthMenu": [ 50, 75, 100 ] +} ); +</script> + </div> + <div class="tab-pane fade" id="service-two"> + <table id="myTable2" class="display"> + <thead> + <tr> + <th>Title/Description</th> + <th>Presentation</th> + </tr> + </thead> + <tbody> + + <!--NEW WEBINAR STARTS HERE--> + <tr> + <td><p><h3>Introduction to Gene Network</h3> + <p><i>Please note that this tutorial is based on GeneNetwork v1</i> + +<p>GeneNetwork is a group of linked data sets and tools used to study complex networks of genes, molecules, and higher order gene function and phenotypes. GeneNetwork combines more than 25 years of legacy data generated by hundreds of scientists together with sequence data (SNPs) and massive transcriptome data sets (expression genetic or eQTL data sets). The quantitative trait locus (QTL) mapping module that is built into GN is optimized for fast on-line analysis of traits that are controlled by combinations of gene variants and environmental factors. GeneNetwork can be used to study humans, mice (BXD, AXB, LXS, etc.), rats (HXB), Drosophila, and plant species (barley and Arabidopsis). Most of these population data sets are linked with dense genetic maps (genotypes) that can be used to locate the genetic modifiers that cause differences in expression and phenotypes, including disease susceptibility. + +<p>Users are welcome to enter their own private data directly into GeneNetwork to exploit the full range of analytic tools and to map modulators in a powerful environment. This combination of data and fast analytic functions enable users to study relations between sequence variants, molecular networks, and function.</p> + + <p>Presented by:<br> +Dr. Rob Williams<br> +Professor and Chair<br> +Department of Genetics, Genomics, and Informatics<br> +University of Tennessee Health Science Center +</p> +<!--<p>There is no fee associated with this webinar, but users are asked to register to receive the Zoom link and password. +Registration: <a href="https://bit.ly/osga_2020-11-20">https://bit.ly/osga_2020-11-20</a>--> +<!--<p>This webinar series is sponsored by the NIDA Center of Excellence in Omics, Systems Genetics, and the Addictome (P30 DA044223). --> +</td> + <!--<td><strong>After the presentation, the recording will be made available here.</strong></td>--> + <td> + <iframe width="560" height="315" src="https://www.youtube.com/embed/B3g_0q-ldJ8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + </tr> + <!--WEBINAR ENDS HERE--> + + <!--NEW WEBINAR STARTS HERE--> + <tr> + <td><p><h3>How to search in GeneNetwork</h3><br>Presented by Rob Williams University of Tennessee Health Science Center</td> + <!--<td><p><h3>How to search in GeneNetwork</h3> +</td>--> + <td> + <iframe width="560" height="315" src="https://www.youtube.com/embed/5exnkka5Tso" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> </td> + </tr> + <!--WEBINAR ENDS HERE--> + + <!--NEW WEBINAR STARTS HERE--> + <tr> + <td><p><h3>GeneNetwork.org: genetic analysis for all neuroscientists</h3><br>Presented by David G. Ashbrook Assistant Professor University of Tennessee Health Science Center +</td> + <td> + <iframe width="560" height="315" src="https://www.youtube.com/embed/JmlVLki09Q8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td> + </tr> + <!--WEBINAR ENDS HERE--> + </tbody> + </table> + +<script> +$('#myTable2').dataTable( { + "lengthMenu": [ 50, 75, 100 ] +} ); +</script> + </div> + <div class="tab-pane fade" id="service-three"> + <table id="myTable3" class="display"> + <thead> + <tr> + <th>Title</th> + <th>Speaker</th> + <th>Video link</th> + </tr> + </thead> + <tbody> + <tr><td>Diallel Crosses, Artificial Intelligence, and Mouse Models of Alzheimer’s Disease</td> + <td>David G. Ashbrook<br>Assistant Professor<br>University of Tennessee Health Science Center</td> + <td><a href="https://www.youtube.com/watch?v=HKfYc8CJwqM">YouTube link</a></td> + </tr> + + + </tbody> + </table> + <script> +$('#myTable3').dataTable( { + "lengthMenu": [ 50, 75, 100 ] +} ); +</script> + </div> + + <div class="tab-pane fade" id="service-four"> + <table id="myTable4" class="display"> + <thead> + <tr> + <th>Title</th> + </tr> + </thead> + <tbody> + <tr><td><a href="https://www.biorxiv.org/content/10.1101/2020.12.23.424047v1">GeneNetwork: a continuously updated tool for systems genetics analyses</a></td></tr> + <tr><td><a href="https://doi.org/10.3390/genes13040614">New Insights on Gene by Environmental Effects of Drugs of Abuse in Animal Models Using GeneNetwork</a></td> + <tr><td><a href="https://www.opar.io/pdf/Rat_HRDP_Brain_Proteomics_Wang_WIlliams_08Oct2021.pdf">A Primer on Brain Proteomics and protein-QTL Analysis for Substance Use Disorders</a></td></tr> + + </tbody> + </table> + <script> +$('#myTable4').dataTable( { + "lengthMenu": [ 50, 75, 100 ] +} ); +</script> + </div> + </div> + + </div> + </div> + +</div> + + <hr> + + + + </div> + <!-- /.container --> + + <!-- jQuery --> + <script src="js/jquery.js"></script> + + <!-- Bootstrap Core JavaScript --> + <script src="js/bootstrap.min.js"></script> + +</body> + + +{% endblock %} + diff --git a/gn2/wqflask/templates/view_case_attribute_diff.html b/gn2/wqflask/templates/view_case_attribute_diff.html new file mode 100644 index 00000000..0b5c95f1 --- /dev/null +++ b/gn2/wqflask/templates/view_case_attribute_diff.html @@ -0,0 +1,117 @@ +{%extends "base.html"%} +{%block title%}View Case Attribute Diff{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" + href="/css/DataTables/css/jquery.dataTables.css" /> +<link rel="stylesheet" type="text/css" + href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + +<style> + .table-fixed-head {overflow-y: auto; height: 32em;} + .table-fixed-head thead th {position: sticky; top: 0;} + .diff-row { + display: grid; + grid-template-columns: 1rem 9rem; + column-gap: 0.5em; + padding: 0.5em; + background:#CCCCCC; + border-color:#FFFFFF; + border-style:solid; + border-radius: 10px; + } + .diff-indicator { + grid-column-start: 1; + grid-column-end: 2; + } + .diff-original { + grid-column-start: 2; + grid-column-end: 3; + } + .diff-current { + grid-column-start: 2; + grid-column-end: 3; + } + .diff-addition {color: green; font-weight: bold;} + .diff-deletion {color: red; font-weight: bold;} + form input[type="submit"] { + text-transform: capitalize; + } +</style> +{%endblock%} + +{%block content%} +<div class="container"> + <h1>View Diff</h1> + + {{flash_me()}} + + <div id="diff-display" class="panel panel-primary"> + <div class="panel-heading"> + <h3 class="panel-title">Changes</h3> + </div> + <div class="panel-body"> + {%set the_diff = diff.json_diff_data.diff%} + {%if the_diff.Additions | length %} + <h4>Additions</h4> + <div class="diff-row"> + <span class="diff-indicator"></span> + <span class="diff-original"></span> + <span class="diff-indicator diff-addition">+</span> + <span class="diff-current diff-addition">{{item.Current}}</span> + </div> + {%endif%} + {%if the_diff.Modifications | length %} + <h4>Modifications</h4> + {%for item in the_diff.Modifications%} + <div class="diff-row"> + <span class="diff-indicator diff-deletion">-</span> + <span class="diff-original diff-deletion">{{item.Original}}</span> + <span class="diff-indicator diff-addition">+</span> + <span class="diff-current diff-addition">{{item.Current}}</span> + </div> + {%endfor%} + {%endif%} + {%if the_diff.Deletions | length %} + <h4>Deletions</h4> + <div class="diff-row"> + <span class="diff-indicator diff-addition">+</span> + <span class="diff-original diff-addition">{{item.Original}}</span> + <span class="diff-indicator diff-deletion">-</span> + <span class="diff-current diff-deletion">{{item.Current}}</span> + </div> + {%endif%} + </div> + <div class="panel-footer"> + <p>Edited by: {{diff.json_diff_data.user_id}}</p> + </div> + </div> + + + + <form method="POST" action="{{url_for('approve_reject_diff')}}"> + <input type="hidden" + name="diff_id" + value="{{diff.id}}" /> + <input type="hidden" + name="diff_data" + value='{{diff.json_diff_data | tojson}}' /> + <input type="submit" + name="action" + title="Approve the changes" + class="btn btn-warning" + value="approve" /> + <input type="submit" + name="action" + title="Reject the changes" + class="btn btn-danger" + value="reject" /> + </form> +{%endblock%} + +{%block js%} +<script language="javascript" + type="text/javascript" + src="{{url_for('js', filename='DataTables/js/jquery.js')}}"></script> +{%endblock%} diff --git a/gn2/wqflask/templates/view_case_attribute_diff_error.html b/gn2/wqflask/templates/view_case_attribute_diff_error.html new file mode 100644 index 00000000..a10f7ab9 --- /dev/null +++ b/gn2/wqflask/templates/view_case_attribute_diff_error.html @@ -0,0 +1,35 @@ +{%extends "base.html"%} +{%block title%}View Case Attribute Diff{%endblock%} + +{%block css%} +<link rel="stylesheet" type="text/css" + href="/css/DataTables/css/jquery.dataTables.css" /> +<link rel="stylesheet" type="text/css" + href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" /> +<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> + +<style> + .table-fixed-head {overflow-y: auto; height: 32em;} + .table-fixed-head thead th {position: sticky; top: 0;} +</style> +{%endblock%} + +{%block content%} +<div class="container"> + <h1>View Diff</h1> + + {{flash_me()}} + + <p class="text-danger"> + <span class="glyphicon glyphicon-exclamation-sign"> + </span> + <strong>{{error.status_code}}: {{error["error"]}}</strong> + {{error.error_description}} + </p> +{%endblock%} + +{%block js%} +<script language="javascript" + type="text/javascript" + src="{{url_for('js', filename='DataTables/js/jquery.js')}}"></script> +{%endblock%} diff --git a/gn2/wqflask/templates/webgestalt_page.html b/gn2/wqflask/templates/webgestalt_page.html new file mode 100644 index 00000000..759e0251 --- /dev/null +++ b/gn2/wqflask/templates/webgestalt_page.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% block title %}{% if wrong_input == "True" %}WebGestalt Error{% else %}Opening WebGestalt{% endif %}{% endblock %} +{% block content %} + {% if wrong_input == "True" %} + {{ header("Error") }} + + <div class="container"> + {% if chip_name == "mixed" %} + <h3>Sorry, the analysis was interrupted because your selections from GeneNetwork apparently include data from more than one array platform (i.e., Affymetrix U74A and M430 2.0). Most WebGestalt analyses assume that you are using a single array type and compute statistical values on the basis of that particular array. Please reselect traits from a signle platform and submit again.</h3> + {% elif chip_name == "not_microarray" %} + <h3>You need to select at least one microarray trait to submit.</hr> + {% elif '_NA' in chip_name %} + <h3>Sorry, the analysis was interrupted because your selections from GeneNetwork apparently include data from platform {{ chip_name }} which is unknown by WebGestalt. Please reselect traits and submit again.</h3> + {% else %} + <h3>Sorry, an error occurred while submitting your traits to WebGestalt.</h3> + {% endif %} + </div> + {% else %} + <div class="container"> + <h3>Opening WebGestalt...</h3> + </div> + <form method="post" action="https://www.webgestalt.org/option.php" name="WebGestalt"> + {% for key in hidden_vars %} + <input type="hidden" name="{{ key }}" value="{{ hidden_vars[key] }}"> + {% endfor %} + </form> + {% endif %} +{% endblock %} +{% block js %} +{% if wrong_input == "False" %} +<script type="text/javascript"> + setTimeout('document.WebGestalt.submit()', 1000); +</script> +{% endif %} +{% endblock %} diff --git a/gn2/wqflask/templates/wgcna_results.html b/gn2/wqflask/templates/wgcna_results.html new file mode 100644 index 00000000..0dc030b1 --- /dev/null +++ b/gn2/wqflask/templates/wgcna_results.html @@ -0,0 +1,76 @@ +{% extends "base.html" %} +{% block title %}WCGNA results{% endblock %} + +{% block content %} <!-- Start of body --> + <div class="container"> + <h1>WGCNA Results</h1> + Analysis found {{results['nmod']}} modules when scanning {{results['nphe']}} phenotypes, measured on {{results['nstr']}} strains.<br> + Additional parameters settings: + <ul> + <li>Soft thresholds checked = {{results['requestform']['SoftThresholds']}}</li> + <li>Power used for this analysis = {{results['Power']}}</li> + <li>TomType = {{results['requestform']['TOMtype']}}</li> + <li>Minimum module size = {{results['requestform']['MinModuleSize'] }}</li> + <li>mergeCutHeight = {{results['requestform']['mergeCutHeight'] }}</li> + </ul> + + <h3>Soft threshold table</h3> + <table width="80%"> + <tr><th>Power</th><th>SFT.R.sq</th><th>slope</th><th>truncated.R.sq</th><th>mean.k</th><th>median.k</th><th>max.k</th><th>Analysis</th></tr> + {% for r in range(powers[0][0]|length) %} + {% if powers[0][1][r] > 0.85 %} + <tr style="color: #00ff00;"> + {% elif powers[0][1][r] > 0.75 %} + <tr style="color: #aaaa00;"> + {% else %} + <tr style="color: #ff0000;"> + {% endif %} + {% for c in range(powers[0]|length) %} + <td>{{powers[0][c][r]|round(3)}}</td> + {% endfor %} + <td style="color: #000000;"> + {% if powers[0][1][r] > 0.75 %} + <input type="submit" value="Redo use power = {{powers[0][0][r]}}" /></td> + {% endif %} + </tr> + {% endfor %} + </table> + <h3>WGCNA module plot</h3> + <a href="/tmp/{{ results['imgurl'] }}"> + <img alt="Embedded Image" src="data:image/png;base64, + {% for elem in results['imgdata'] -%} + {% print("%c"|format(elem)) %} + {%- endfor %} + " /></a> + + + <h3>Phenotype / Module table</h3> + <table width="80%"> + <tr><th>Phenotype</th><th>Module</th></tr> + {% for r in range(results['nphe']) %} + <tr> + <td>{{results['phenotypes'][r][0]}}</td> + <td>{{results['network'][0][r]}}</td> + </tr> + {% endfor %} + </table> + + <h3>Module eigen genes</h3> + <table width="80%"> + <tr><th>Phenotype</th> + {% for m in range(results['nmod']) %} + <th><input type="submit" value="Add module {{m}} to collection" /></th> + {% endfor %} + </tr> + {% for r in range(results['nstr']) %} + <tr> + <td>{{results['strains'][r][0]}}</td> + {% for m in range(results['nmod']) %} + <td>{{results['network'][2][m][r]}}</td> + {% endfor %} + </tr> + {% endfor %} + </table> + </div> +{% endblock %} + diff --git a/gn2/wqflask/templates/wgcna_setup.html b/gn2/wqflask/templates/wgcna_setup.html new file mode 100644 index 00000000..d7acd5f2 --- /dev/null +++ b/gn2/wqflask/templates/wgcna_setup.html @@ -0,0 +1,142 @@ +{% extends "base.html" %} +{% block title %}WCGNA analysis{% endblock %} +{% block content %} +<!-- Start of body --> +<style type="text/css"> + +#terminal { + margin-top: 10px; +} + +</style> + +<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='xterm/xterm.min.css') }}" /> + +<div class="container"> + <div class="col-md-5"> + <h1 class="mx-3 my-2 "> WGCNA analysis parameters</h1> + {% if request.form['trait_list'].split(",")|length < 4 %} <div class="alert alert-danger" role="alert"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> + <span class="sr-only">Error:</span> + <h2>Too few phenotypes as input</h2> + Please make sure you select enough phenotypes / genes to perform WGCNA. Your collection needs to contain at least 4 different phenotypes. You provided {{request.form['trait_list'].split(',')|length}} phenotypes as input. + </div> + {% else %} + <form class="col-md-12" action="/wgcna_results" method="post" class="form-horizontal" id="wgcna_form"> + <input type="hidden" name="trait_list" id="trait_list" value="{{request.form['trait_list']}}"> + <div class="form-group row "> + <label for="SoftThresholds" class="col-md-3 col-form-label col-form-label-sm">Soft threshhold</label> + <div class="col-md-9"> + <input type="text" class="form-control form-control-md" value="1,2,3,4,5,6,7,8,9" id="SoftThresholds" name="SoftThresholds"> + </div> + </div> + <div class="form-group row "> + <label for="MinModuleSize" class="col-md-3 col-form-label col-form-label-sm">Minimum module size:</label> + <div class="col-md-9"> + <input type="text" class="form-control form-control-md" id="MinModuleSize" value="30" name="MinModuleSize"> + </div> + </div> + + <div class="form-group row"> + <label for="TOMtype" class="col-md-3 col-form-label col-form-label-sm">TOMtype:</label> + <div class="col-md-9"> + <select class="form-control" id="TOMtype" name="TOMtype"> + <option value="unsigned">unsigned</option> + <option value="signed">signed</option> + </select> + </div> + + </div> + <div class="form-group row "> + <label for="mergeCutHeight" class="col-md-3 col-form-label col-form-label-sm">mergeCutHeight:</label> + <div class="col-md-9"> + <input type="text" class="form-control form-control-md" id="mergeCutHeight" value="0.25" name="mergeCutHeight"> + </div> + </div> + + <div class="form-group row"> + <label for="corType" class="col-md-3 col-form-label col-form-label-sm">corType:</label> + <div class="col-md-9"> + <select class="form-control col-md-9" id="corType" name="corType"> + <option value="pearson">pearson</option> + <option value="bicor">bicor</option> + </select> + </div> + + </div> + <div class="form-group"> + <div class="text-center"> + <input type="submit" class="btn btn-primary" value="Run WGCNA using these settings" /> + </div> + </div> + + + + </form> + {% endif %} +</div> +<div class="col-md-7"> + <div id="terminal" class="mt-2"> + </div> +</div> +</div> + +<script src="{{ url_for('js', filename='xterm/xterm.min.js') }}" type="text/javascript"></script> +<script src="{{ url_for('js', filename='jquery/jquery.min.js') }}" type="text/javascript"></script> +<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.min.js"></script> + +<script> +document.addEventListener('DOMContentLoaded', function() { +let term = new Terminal({ + cursorBlink: true, + lineHeight: 1.3, + scrollback: true, + macOptionIsMeta: true +}); + +let termDebugs = { + general: "Computation process to be displayed here....", + success: "Computation in process ......", + fail: "Too few phenotypes as input must be >=4" +} + +const fitAddon = new FitAddon.FitAddon() +term.loadAddon(fitAddon) + +term.open(document.getElementById('terminal')); +term.setOption('theme', { + background: '#300a24' +}); +term.writeln(termDebugs.general) + +wgcnaForm = document.querySelector("#wgcna_form") + +fitAddon.fit() +term.onData((data) => { + term.write(data) +}) + + +if (wgcnaForm) { +} else { + term.writeln(termDebugs.fail) +} + +$(document).on('submit', '#wgcna_form', function(e) { + term.writeln(termDebugs.success) + + e.preventDefault(); + var form = $(this); + $.ajax({ + type: 'POST', + url: '/wgcna_results', + data: form.serialize(), + success: function(data) { + document.write(data) + } + }) +}) +}) + +</script> +{% endblock %}
\ No newline at end of file diff --git a/gn2/wqflask/templates/with-trait-items.html b/gn2/wqflask/templates/with-trait-items.html new file mode 100644 index 00000000..66d6fd22 --- /dev/null +++ b/gn2/wqflask/templates/with-trait-items.html @@ -0,0 +1,18 @@ +{%for trait in traits_list:%} +<div class="with-trait"> + <input type="{%if step=='select-primary':%}radio{%else:%}checkbox{%endif%}" + name="{%if step=='select-primary':%}primary_trait{%else:%}control_traits[]{%endif%}" + value="{{trait['name']}}:::{{trait['dataset']}}:::{{trait['symbol']}}:::{{trait['description']}}:::{{trait['location']}}:::{{trait['mean_expr']}}:::{{trait['max_lrs']}}:::{{trait['data_hmac']}}" + id="trait_{{trait['data_hmac']}}" + class="selector-element" /> + <label for="trait_{{trait['data_hmac']}}" class="label-element"> + <span class="trait-dataset" data-title="dataset">{{trait["dataset"]}}</span> + <span class="trait-name" data-title="name">{{trait["name"]}}</span> + <span class="trait-symbol" data-title="symbol">{{trait["symbol"]}}</span> + <span class="trait-description" data-title="description">{{trait["description"]}}</span> + <span class="trait-locatin" data-title="location">{{trait["location"]}}</span> + <span class="trait-mean-expr" data-title="mean">{{"%0.3f" % trait["mean_expr"]|float}}</span> + <span class="trait-max-lrs" data-title="max LRS">{{trait["max_lrs"]}}</span> + </label> +</div> +{%endfor%} diff --git a/gn2/wqflask/update_search_results.py b/gn2/wqflask/update_search_results.py new file mode 100644 index 00000000..e6d2b0ca --- /dev/null +++ b/gn2/wqflask/update_search_results.py @@ -0,0 +1,102 @@ +import json + +from gn2.base.data_set import create_dataset +from gn2.base.trait import GeneralTrait +from gn2.db import webqtlDatabaseFunction +from gn2.wqflask.database import database_connection +from gn2.utility.tools import get_setting + + +class GSearch: + + def __init__(self, kw): + self.type = kw['type'] + self.terms = kw['terms'] + #self.row_range = kw['row_range'] + if self.type == "gene": + results = None + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + cursor.execute(""" +SELECT Species.`Name` AS species_name, InbredSet.`Name` AS inbredset_name, +Tissue.`Name` AS tissue_name, ProbeSetFreeze.Name AS probesetfreeze_name, +ProbeSet.Name AS probeset_name, ProbeSet.Symbol AS probeset_symbol, +ProbeSet.`description` AS probeset_description, ProbeSet.Chr AS chr, ProbeSet.Mb AS mb, +ProbeSetXRef.Mean AS mean, ProbeSetXRef.LRS AS lrs, ProbeSetXRef.`Locus` AS locus, +ProbeSetXRef.`pValue` AS pvalue, ProbeSetXRef.`additive` AS additive +FROM Species, InbredSet, ProbeSetXRef, ProbeSet, ProbeFreeze, ProbeSetFreeze, Tissue +WHERE InbredSet.`SpeciesId`=Species.`Id` AND ProbeFreeze.InbredSetId=InbredSet.`Id` +AND ProbeFreeze.`TissueId`=Tissue.`Id` AND ProbeSetFreeze.ProbeFreezeId=ProbeFreeze.Id +AND ( MATCH (ProbeSet.Name,ProbeSet.description,ProbeSet.symbol,alias,GenbankId, UniGeneId, +Probe_Target_Description) AGAINST (%s IN BOOLEAN MODE) ) +AND ProbeSet.Id = ProbeSetXRef.ProbeSetId AND ProbeSetXRef.ProbeSetFreezeId=ProbeSetFreeze.Id +AND ProbeSetFreeze.public > 0 ORDER BY species_name, inbredset_name, tissue_name, +probesetfreeze_name, probeset_name LIMIT 6000""", + (self.terms,)) + results = cursor.fetchall() + self.trait_list = [] + for line in results: + dataset = create_dataset( + line[3], "ProbeSet", get_samplelist=False) + trait_id = line[4] + this_trait = GeneralTrait( + dataset=dataset, name=trait_id, get_qtl_info=True, get_sample_info=False) + self.trait_list.append(this_trait) + + elif self.type == "phenotype": + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + results = None + cursor.execute(""" +SELECT Species.`Name`, InbredSet.`Name`, PublishFreeze.`Name`, PublishXRef.`Id`, +Phenotype.`Post_publication_description`, Publication.`Authors`, Publication.`Year`, +PublishXRef.`LRS`, PublishXRef.`Locus`, PublishXRef.`additive` FROM Species, InbredSet, +PublishFreeze, PublishXRef, Phenotype, Publication WHERE PublishXRef.`InbredSetId`=InbredSet.`Id` +AND PublishFreeze.`InbredSetId`=InbredSet.`Id` AND InbredSet.`SpeciesId`=Species.`Id` +AND PublishXRef.`PhenotypeId`=Phenotype.`Id` AND PublishXRef.`PublicationId`=Publication.`Id` +AND (Phenotype.Post_publication_description REGEXP "[[:<:]]%s[[:>:]]" +OR Phenotype.Pre_publication_description REGEXP "[[:<:]]%s[[:>:]]" +OR Phenotype.Pre_publication_abbreviation REGEXP "[[:<:]]%s[[:>:]]" +OR Phenotype.Post_publication_abbreviation REGEXP "[[:<:]]%s[[:>:]]" +OR Phenotype.Lab_code REGEXP "[[:<:]]%s[[:>:]]" +OR Publication.PubMed_ID REGEXP "[[:<:]]%s[[:>:]]" +OR Publication.Abstract REGEXP "[[:<:]]%s[[:>:]]" +OR Publication.Title REGEXP "[[:<:]]%s[[:>:]]" +OR Publication.Authors REGEXP "[[:<:]]%s[[:>:]]" +OR PublishXRef.Id REGEXP "[[:<:]]%s[[:>:]]") +ORDER BY Species.`Name`, InbredSet.`Name`, PublishXRef.`Id` LIMIT 6000""", + ((self.terms, ) * 10)) + results = cursor.fetchall() + self.trait_list = [] + for line in results: + dataset = create_dataset(line[2], "Publish") + trait_id = line[3] + this_trait = GeneralTrait( + dataset=dataset, name=trait_id, get_qtl_info=True, get_sample_info=False) + self.trait_list.append(this_trait) + + self.results = self.convert_to_json() + + def convert_to_json(self): + json_dict = {} + #json_dict['draw'] = self.draw, + json_dict['recordsTotal'] = len(self.trait_list), + json_dict['data'] = [] + + for i, trait in enumerate(self.trait_list): + trait_row = {"checkbox": "<INPUT TYPE=\"checkbox\" NAME=\"searchResult\" class=\"checkbox trait_checkbox\" style=\"transform: scale(1.5);\" VALUE=\"{}:{}\">".format(trait.name, trait.dataset.name), + "index": i + 1, + "species": trait.dataset.group.species, + "group": trait.dataset.group.name, + "tissue": trait.dataset.tissue, + "dataset": trait.dataset.fullname, + "record": "<a href=\"/show_trait?trait_id=" + trait.name + "&dataset=" + trait.dataset.name + "\" target=\"_blank\">" + trait.name + "</a>", + "symbol": trait.symbol, + "description": trait.description_display, + "location": trait.location_repr, + "mean": trait.mean, + "max_lrs": trait.LRS_score_repr, + "max_lrs_location": trait.LRS_location_repr, + "additive_effect": trait.additive} + json_dict['data'].append(trait_row) + + json_results = json.dumps(json_dict) + return json_results diff --git a/gn2/wqflask/user_login.py b/gn2/wqflask/user_login.py new file mode 100644 index 00000000..55af2081 --- /dev/null +++ b/gn2/wqflask/user_login.py @@ -0,0 +1,519 @@ +import os +import hashlib +import datetime +import time +import uuid +import hmac +import base64 +import requests + +import simplejson as json + +from flask import (Flask, g, render_template, url_for, request, make_response, + redirect, flash, abort) + +from gn2.wqflask import app +from gn2.wqflask import pbkdf2 +from gn2.wqflask.user_session import UserSession + +from gn2.utility import hmac +from gn2.utility.redis_tools import is_redis_available, get_redis_conn, get_user_id, get_user_by_unique_column, set_user_attribute, save_user, save_verification_code, check_verification_code, get_user_collections, save_collections +Redis = get_redis_conn() + +from smtplib import SMTP +from gn2.utility.tools import SMTP_CONNECT, SMTP_USERNAME, SMTP_PASSWORD, LOG_SQL_ALCHEMY, GN2_BRANCH_URL + +THREE_DAYS = 60 * 60 * 24 * 3 + + +def timestamp(): + return datetime.datetime.utcnow().isoformat() + + +def basic_info(): + return dict(timestamp=timestamp(), + ip_address=request.remote_addr, + user_agent=request.headers.get('User-Agent')) + + +def encode_password(pass_gen_fields, unencrypted_password): + if isinstance(pass_gen_fields['salt'], bytes): + salt = pass_gen_fields['salt'] + else: + salt = bytes(pass_gen_fields['salt'], "utf-8") + encrypted_password = pbkdf2.pbkdf2_hex(str(unencrypted_password), + salt, + pass_gen_fields['iterations'], + pass_gen_fields['keylength'], + pass_gen_fields['hashfunc']) + + pass_gen_fields.pop("unencrypted_password", None) + pass_gen_fields["password"] = encrypted_password + + return pass_gen_fields + + +def set_password(password): + pass_gen_fields = { + "unencrypted_password": password, + "algorithm": "pbkdf2", + "hashfunc": "sha256", + "salt": base64.b64encode(os.urandom(32)), + "iterations": 100000, + "keylength": 32, + "created_timestamp": timestamp() + } + + assert len(password) >= 6, "Password shouldn't be shorter than 6 characters" + + encoded_password = encode_password( + pass_gen_fields, pass_gen_fields['unencrypted_password']) + + return encoded_password + + +def get_signed_session_id(user): + session_id = str(uuid.uuid4()) + + session_id_signature = hmac.hmac_creation(session_id) + session_id_signed = session_id + ":" + session_id_signature + + # ZS: Need to check if this is ever actually used or exists + if 'user_id' not in user: + user['user_id'] = str(uuid.uuid4()) + save_user(user, user['user_id']) + + if 'github_id' in user: + session = dict(login_time=time.time(), + user_type="github", + user_id=user['user_id'], + github_id=user['github_id'], + user_name=user['name'], + user_url=user['user_url']) + elif 'orcid' in user: + session = dict(login_time=time.time(), + user_type="orcid", + user_id=user['user_id'], + github_id=user['orcid'], + user_name=user['name'], + user_url=user['user_url']) + else: + session = dict(login_time=time.time(), + user_type="gn2", + user_id=user['user_id'], + user_name=user['full_name'], + user_email_address=user['email_address']) + + key = UserSession.user_cookie_name + ":" + session_id + Redis.hmset(key, session) + Redis.expire(key, THREE_DAYS) + + return session_id_signed + + +def send_email(toaddr, msg, fromaddr="no-reply@genenetwork.org"): + """Send an E-mail through SMTP_CONNECT host. If SMTP_USERNAME is not + 'UNKNOWN' TLS is used + + """ + if SMTP_USERNAME == 'UNKNOWN': + server = SMTP(SMTP_CONNECT) + server.sendmail(fromaddr, toaddr, msg) + else: + server = SMTP(SMTP_CONNECT) + server.starttls() + server.login(SMTP_USERNAME, SMTP_PASSWORD) + server.sendmail(fromaddr, toaddr, msg) + server.quit() + + +def send_verification_email(user_details, template_name="email/user_verification.txt", key_prefix="verification_code", subject="GeneNetwork e-mail verification"): + verification_code = str(uuid.uuid4()) + key = key_prefix + ":" + verification_code + + data = json.dumps(dict(id=user_details['user_id'], timestamp=timestamp())) + + Redis.set(key, data) + Redis.expire(key, THREE_DAYS) + + recipient = user_details['email_address'] + body = render_template(template_name, verification_code=verification_code) + send_email(recipient, subject, body) + return {"recipient": recipient, "subject": subject, "body": body} + + +def send_invitation_email(user_email, temp_password, template_name="email/user_invitation.txt", subject="You've been added to a GeneNetwork user group"): + recipient = user_email + body = render_template(template_name, temp_password) + send_email(recipient, subject, body) + return {"recipient": recipient, "subject": subject, "body": body} + + +@app.route("/manage/verify_email") +def verify_email(): + if 'code' in request.args: + user_details = check_verification_code(request.args['code']) + if user_details: + # As long as they have access to the email account + # We might as well log them in + session_id_signed = get_signed_session_id(user_details) + flash("Thank you for logging in {}.".format( + user_details['full_name']), "alert-success") + response = make_response(redirect( + url_for('index_page', import_collections=import_col, anon_id=anon_id))) + response.set_cookie(UserSession.user_cookie_name, + session_id_signed, max_age=None) + return response + else: + flash( + "Invalid code: Password reset code does not exist or might have expired!", "error") + + +@app.route("/n/login", methods=('GET', 'POST')) +def login(): + params = request.form if request.form else request.args + if not params: # ZS: If coming to page for first time + from gn2.utility.tools import GITHUB_AUTH_URL, GITHUB_CLIENT_ID, ORCID_AUTH_URL, ORCID_CLIENT_ID + external_login = {} + if GITHUB_AUTH_URL and GITHUB_CLIENT_ID != 'UNKNOWN': + external_login["github"] = GITHUB_AUTH_URL + if ORCID_AUTH_URL and ORCID_CLIENT_ID != 'UNKNOWN': + external_login["orcid"] = ORCID_AUTH_URL + return render_template("new_security/login_user.html", external_login=external_login, redis_is_available=is_redis_available()) + else: # ZS: After clicking sign-in + if 'type' in params and 'uid' in params: + user_details = get_user_by_unique_column("user_id", params['uid']) + if user_details: + session_id_signed = get_signed_session_id(user_details) + if 'name' in user_details and user_details['name'] != "None": + display_id = user_details['name'] + elif 'github_id' in user_details: + display_id = user_details['github_id'] + elif 'orcid' in user_details: + display_id = user_details['orcid'] + else: + display_id = "" + flash("Thank you for logging in {}.".format( + display_id), "alert-success") + response = make_response(redirect(url_for('index_page'))) + response.set_cookie( + UserSession.user_cookie_name, session_id_signed, max_age=None) + else: + flash("Something went unexpectedly wrong.", "alert-danger") + response = make_response(redirect(url_for('index_page'))) + return response + else: + user_details = get_user_by_unique_column( + "email_address", params['email_address']) + password_match = False + if user_details: + submitted_password = params['password'] + pwfields = user_details['password'] + if isinstance(pwfields, str): + pwfields = json.loads(pwfields) + encrypted_pass_fields = encode_password( + pwfields, submitted_password) + password_match = pbkdf2.safe_str_cmp( + encrypted_pass_fields['password'], pwfields['password']) + + else: # Invalid e-mail + flash("Invalid e-mail address. Please try again.", "alert-danger") + response = make_response(redirect(url_for('login'))) + + return response + if password_match: # If password correct + if user_details['confirmed']: # If account confirmed + import_col = "false" + anon_id = "" + if 'import_collections' in params: + import_col = "true" + anon_id = params['anon_id'] + + session_id_signed = get_signed_session_id(user_details) + flash("Thank you for logging in {}.".format( + user_details['full_name']), "alert-success") + response = make_response(redirect( + url_for('index_page', import_collections=import_col, anon_id=anon_id))) + response.set_cookie( + UserSession.user_cookie_name, session_id_signed, max_age=None) + return response + else: + email_ob = send_verification_email( + user_details, template_name="email/user_verification.txt") + return render_template("newsecurity/verification_still_needed.html", subject=email_ob['subject']) + else: # Incorrect password + # ZS: It previously seemed to store that there was an incorrect log-in attempt here, but it did so in the MySQL DB so this might need to be reproduced with Redis + flash("Invalid password. Please try again.", "alert-danger") + response = make_response(redirect(url_for('login'))) + + return response + + +@app.route("/n/login/github_oauth2", methods=('GET', 'POST')) +def github_oauth2(): + from gn2.utility.tools import GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GITHUB_AUTH_URL + code = request.args.get("code") + data = { + "client_id": GITHUB_CLIENT_ID, + "client_secret": GITHUB_CLIENT_SECRET, + "code": code + } + + result = requests.post( + "https://github.com/login/oauth/access_token", json=data) + result_dict = {arr[0]: arr[1] + for arr in [tok.split("=") for tok in result.text.split("&")]} + + github_user = get_github_user_details(result_dict["access_token"]) + + user_details = get_user_by_unique_column("github_id", github_user["id"]) + if user_details == None: + user_details = { + "user_id": str(uuid.uuid4()), + "name": github_user["name"].encode("utf-8") if github_user["name"] else "None", + "github_id": github_user["id"], + "user_url": github_user["html_url"].encode("utf-8"), + "login_type": "github", + "organization": "", + "active": 1, + "confirmed": 1 + } + save_user(user_details, user_details["user_id"]) + + url = "/n/login?type=github&uid=" + user_details["user_id"] + return redirect(url) + + +def get_github_user_details(access_token): + from gn2.utility.tools import GITHUB_API_URL + result = requests.get(GITHUB_API_URL, headers={ + 'Authorization': 'token ' + access_token}).content + + return json.loads(result) + + +@app.route("/n/login/orcid_oauth2", methods=('GET', 'POST')) +def orcid_oauth2(): + from uuid import uuid4 + from gn2.utility.tools import ORCID_CLIENT_ID, ORCID_CLIENT_SECRET, ORCID_TOKEN_URL, ORCID_AUTH_URL + code = request.args.get("code") + error = request.args.get("error") + url = "/n/login" + if code: + data = { + "client_id": ORCID_CLIENT_ID, + "client_secret": ORCID_CLIENT_SECRET, + "grant_type": "authorization_code", + "redirect_uri": GN2_BRANCH_URL + "n/login/orcid_oauth2", + "code": code + } + + result = requests.post(ORCID_TOKEN_URL, data=data) + result_dict = json.loads(result.text.encode("utf-8")) + + user_details = get_user_by_unique_column("orcid", result_dict["orcid"]) + if user_details == None: + user_details = { + "user_id": str(uuid4()), + "name": result_dict["name"], + "orcid": result_dict["orcid"], + "user_url": "%s/%s" % ("/".join(ORCID_AUTH_URL.split("/")[:-2]), result_dict["orcid"]), + "login_type": "orcid", + "organization": "", + "active": 1, + "confirmed": 1 + } + save_user(user_details, user_details["user_id"]) + + url = "/n/login?type=orcid&uid=" + user_details["user_id"] + else: + flash("There was an error getting code from ORCID") + return redirect(url) + + +def get_github_user_details(access_token): + from gn2.utility.tools import GITHUB_API_URL + result = requests.get(GITHUB_API_URL, headers={ + 'Authorization': 'token ' + access_token}).content + + return json.loads(result) + + +@app.route("/n/logout") +def logout(): + UserSession().delete_session() + flash("You are now logged out. We hope you come back soon!") + response = make_response(redirect(url_for('index_page'))) + # Delete the cookie + response.set_cookie(UserSession.user_cookie_name, '', expires=0) + return response + + +@app.route("/n/forgot_password", methods=['GET']) +def forgot_password(): + """Entry point for forgotten password""" + print("ARGS: ", request.args) + errors = {"no-email": request.args.get("no-email")} + print("ERRORS: ", errors) + return render_template("new_security/forgot_password.html", errors=errors) + + +def send_forgot_password_email(verification_email): + from email.mime.multipart import MIMEMultipart + from email.mime.text import MIMEText + + template_name = "email/forgot_password.txt" + key_prefix = "forgot_password_code" + subject = "GeneNetwork password reset" + fromaddr = "no-reply@genenetwork.org" + + verification_code = str(uuid.uuid4()) + key = key_prefix + ":" + verification_code + + data = { + "verification_code": verification_code, + "email_address": verification_email, + "timestamp": timestamp() + } + + save_verification_code(verification_email, verification_code) + + body = render_template(template_name, verification_code=verification_code) + + msg = MIMEMultipart() + msg["To"] = verification_email + msg["Subject"] = subject + msg["From"] = fromaddr + msg.attach(MIMEText(body, "plain")) + + send_email(verification_email, msg.as_string()) + + return subject + + +@app.route("/n/forgot_password_submit", methods=('POST',)) +def forgot_password_submit(): + """When a forgotten password form is submitted we get here""" + params = request.form + email_address = params['email_address'] + next_page = None + if email_address != "": + user_details = get_user_by_unique_column( + "email_address", email_address) + if user_details: + email_subject = send_forgot_password_email( + user_details["email_address"]) + return render_template("new_security/forgot_password_step2.html", + subject=email_subject) + else: + flash("The e-mail entered is not associated with an account.", + "alert-danger") + return redirect(url_for("forgot_password")) + + else: + flash("You MUST provide an email", "alert-danger") + return redirect(url_for("forgot_password")) + + +@app.route("/n/password_reset", methods=['GET']) +def password_reset(): + """Entry point after user clicks link in E-mail""" + verification_code = request.args.get('code') + hmac = request.args.get('hm') + + if verification_code: + user_details = check_verification_code(verification_code) + if user_details: + return render_template( + "new_security/password_reset.html", user_encode=user_details["email_address"]) + else: + flash( + "Invalid code: Password reset code does not exist or might have expired!", "error") + return redirect(url_for("login")) + else: + return redirect(url_for("login")) + + +@app.route("/n/password_reset_step2", methods=('POST',)) +def password_reset_step2(): + """Handle confirmation E-mail for password reset""" + errors = [] + user_email = request.form['user_encode'] + user_id = get_user_id("email_address", user_email) + + password = request.form['password'] + encoded_password = set_password(password) + + set_user_attribute(user_id, "password", encoded_password) + + flash("Password changed successfully. You can now sign in.", "alert-info") + return redirect(url_for('login')) + + +def register_user(params): + thank_you_mode = False + errors = [] + user_details = {} + + user_details['email_address'] = params.get( + 'email_address', '').encode("utf-8").strip() + if not (5 <= len(user_details['email_address']) <= 50): + errors.append( + 'Email Address needs to be between 5 and 50 characters.') + else: + email_exists = get_user_by_unique_column( + "email_address", user_details['email_address']) + if email_exists: + errors.append('User already exists with that email') + + user_details['full_name'] = params.get( + 'full_name', '').encode("utf-8").strip() + if not (5 <= len(user_details['full_name']) <= 50): + errors.append('Full Name needs to be between 5 and 50 characters.') + + user_details['organization'] = params.get( + 'organization', '').encode("utf-8").strip() + if user_details['organization'] and not (5 <= len(user_details['organization']) <= 50): + errors.append( + 'Organization needs to be empty or between 5 and 50 characters.') + + password = str(params.get('password', '')) + if not (6 <= len(password)): + errors.append('Password needs to be at least 6 characters.') + + if params.get('password_confirm') != password: + errors.append("Passwords don't match.") + + user_details['password'] = set_password(password) + user_details['user_id'] = str(uuid.uuid4()) + user_details['confirmed'] = 1 + + user_details['registration_info'] = basic_info() + + if len(errors) == 0: + save_user(user_details, user_details['user_id']) + + return errors + + +@app.route("/n/register", methods=('GET', 'POST')) +def register(): + errors = [] + + params = request.form if request.form else request.args + params = params.to_dict(flat=True) + + if params: + errors = register_user(params) + + if len(errors) == 0: + flash( + "Registration successful. You may login with your new account", "alert-info") + return redirect(url_for("login")) + + return render_template("new_security/register_user.html", values=params, errors=errors) + + +@app.errorhandler(401) +def unauthorized(error): + return redirect(url_for('login')) diff --git a/gn2/wqflask/user_session.py b/gn2/wqflask/user_session.py new file mode 100644 index 00000000..af4e8cb4 --- /dev/null +++ b/gn2/wqflask/user_session.py @@ -0,0 +1,330 @@ +import datetime +import time +import uuid + +import simplejson as json + +from flask import (Flask, g, render_template, url_for, request, make_response, + redirect, flash, abort) + +from gn2.wqflask import app +from gn2.utility import hmac + +from gn2.utility.redis_tools import get_redis_conn, get_user_id, get_user_by_unique_column, set_user_attribute, get_user_collections, save_collections +Redis = get_redis_conn() + + +THREE_DAYS = 60 * 60 * 24 * 3 +THIRTY_DAYS = 60 * 60 * 24 * 30 + + +@app.before_request +def get_user_session(): + g.user_session = UserSession() + # I think this should solve the issue of deleting the cookie and redirecting to the home page when a user's session has expired + if not g.user_session: + response = make_response(redirect(url_for('login'))) + response.set_cookie('session_id_v2', '', expires=0) + return response + + +@app.after_request +def set_user_session(response): + if hasattr(g, 'user_session'): + if not request.cookies.get(g.user_session.cookie_name): + response.set_cookie(g.user_session.cookie_name, + g.user_session.cookie) + else: + response.set_cookie('session_id_v2', '', expires=0) + return response + + +def verify_cookie(cookie): + the_uuid, separator, the_signature = cookie.partition(':') + assert len(the_uuid) == 36, "Is session_id a uuid?" + assert separator == ":", "Expected a : here" + assert the_signature == hmac.hmac_creation( + the_uuid), "Uh-oh, someone tampering with the cookie?" + return the_uuid + + +def create_signed_cookie(): + the_uuid = str(uuid.uuid4()) + signature = hmac.hmac_creation(the_uuid) + uuid_signed = the_uuid + ":" + signature + return the_uuid, uuid_signed + + +@app.route("/user/manage", methods=('GET', 'POST')) +def manage_user(): + params = request.form if request.form else request.args + if 'new_full_name' in params: + set_user_attribute(g.user_session.user_id, + 'full_name', params['new_full_name']) + if 'new_organization' in params: + set_user_attribute(g.user_session.user_id, + 'organization', params['new_organization']) + + user_details = get_user_by_unique_column("user_id", g.user_session.user_id) + + return render_template("admin/manage_user.html", user_details=user_details) + + +class UserSession: + """Logged in user handling""" + + user_cookie_name = 'session_id_v2' + anon_cookie_name = 'anon_user_v1' + + def __init__(self): + user_cookie = request.cookies.get(self.user_cookie_name) + if not user_cookie: + self.logged_in = False + anon_cookie = request.cookies.get(self.anon_cookie_name) + self.cookie_name = self.anon_cookie_name + if anon_cookie: + self.cookie = anon_cookie + session_id = verify_cookie(self.cookie) + else: + session_id, self.cookie = create_signed_cookie() + else: + self.cookie_name = self.user_cookie_name + self.cookie = user_cookie + session_id = verify_cookie(self.cookie) + + self.redis_key = self.cookie_name + ":" + session_id + self.session_id = session_id + self.record = Redis.hgetall(self.redis_key) + + # ZS: If user correctly logged in but their session expired + # ZS: Need to test this by setting the time-out to be really short or something + if not self.record or self.record == []: + if user_cookie: + self.logged_in = False + self.record = dict(login_time=time.time(), + user_type="anon", + user_id=str(uuid.uuid4())) + Redis.hmset(self.redis_key, self.record) + Redis.expire(self.redis_key, THIRTY_DAYS) + + # Grrr...this won't work because of the way flask handles cookies + # Delete the cookie + flash( + "Due to inactivity your session has expired. If you'd like please login again.") + return None + else: + self.record = dict(login_time=time.time(), + user_type="anon", + user_id=str(uuid.uuid4())) + Redis.hmset(self.redis_key, self.record) + Redis.expire(self.redis_key, THIRTY_DAYS) + else: + if user_cookie: + self.logged_in = True + self.user_details = get_user_by_unique_column("user_id", self.user_id) + if not self.user_details: + self.logged_in = False + return None + + if user_cookie: + session_time = THREE_DAYS + else: + session_time = THIRTY_DAYS + + if Redis.ttl(self.redis_key) < session_time: + # (Almost) everytime the user does something we extend the session_id in Redis... + Redis.expire(self.redis_key, session_time) + + @property + def user_id(self): + """Shortcut to the user_id""" + if b'user_id' not in self.record: + self.record[b'user_id'] = str(uuid.uuid4()) + + try: + return self.record[b'user_id'].decode("utf-8") + except: + return self.record[b'user_id'] + + @property + def user_email(self): + """Shortcut to the user email address""" + + if self.logged_in and 'email_address' in self.user_details: + return self.user_details['email_address'] + else: + return None + + @property + def redis_user_id(self): + """User id from Redis (need to check if this is the same as the id stored in self.records)""" + + # This part is a bit weird. Some accounts used to not have saved user ids, and in the process of testing I think I created some duplicate accounts for myself. + # Accounts should automatically generate user_ids if they don't already have one now, so this might not be necessary for anything other than my account's collections + + if 'user_email_address' in self.record: + user_email = self.record['user_email_address'] + + # Get user's collections if they exist + user_id = None + user_id = get_user_id("email_address", user_email) + elif 'user_id' in self.record: + user_id = self.record['user_id'] + elif 'github_id' in self.record: + user_github_id = self.record['github_id'] + user_id = None + user_id = get_user_id("github_id", user_github_id) + else: # Anonymous user + return None + + return user_id + + @property + def user_name(self): + """Shortcut to the user_name""" + if 'user_name' in self.record: + return self.record['user_name'] + else: + return '' + + @property + def user_collections(self): + """List of user's collections""" + + # Get user's collections if they exist + collections = get_user_collections(self.user_id) + collections = [item for item in collections if item['name'] != "Your Default Collection"] + \ + [item for item in collections if item['name'] + == "Your Default Collection"] # Ensure Default Collection is last in list + return collections + + @property + def num_collections(self): + """Number of user's collections""" + + return len([item for item in self.user_collections if item['num_members'] > 0]) + + def add_collection(self, collection_name, traits): + """Add collection into Redis""" + + collection_dict = {'id': str(uuid.uuid4()), + 'name': collection_name, + 'created_timestamp': datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p'), + 'changed_timestamp': datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p'), + 'num_members': len(traits), + 'members': list(traits)} + + current_collections = self.user_collections + current_collections.append(collection_dict) + self.update_collections(current_collections) + + return collection_dict['id'] + + def change_collection_name(self, collection_id, new_name): + updated_collections = [] + for collection in self.user_collections: + updated_collection = collection + if collection['id'] == collection_id: + updated_collection['name'] = new_name + updated_collections.append(collection) + + self.update_collections(updated_collections) + return new_name + + def delete_collection(self, collection_id): + """Remove collection with given ID""" + + updated_collections = [] + for collection in self.user_collections: + if collection['id'] == collection_id: + continue + else: + updated_collections.append(collection) + + self.update_collections(updated_collections) + + return collection['name'] + + def add_traits_to_collection(self, collection_id, traits_to_add): + """Add specified traits to a collection""" + + this_collection = self.get_collection_by_id(collection_id) + + updated_collection = this_collection + current_members_minus_new = [ + member for member in this_collection['members'] if member not in traits_to_add] + updated_traits = traits_to_add + current_members_minus_new + + updated_collection['members'] = updated_traits + updated_collection['num_members'] = len(updated_traits) + updated_collection['changed_timestamp'] = datetime.datetime.utcnow().strftime( + '%b %d %Y %I:%M%p') + + updated_collections = [] + for collection in self.user_collections: + if collection['id'] == collection_id: + updated_collections.append(updated_collection) + else: + updated_collections.append(collection) + + self.update_collections(updated_collections) + + def remove_traits_from_collection(self, collection_id, traits_to_remove): + """Remove specified traits from a collection""" + + this_collection = self.get_collection_by_id(collection_id) + + updated_collection = this_collection + updated_traits = [] + for trait in this_collection['members']: + if trait in traits_to_remove: + continue + else: + updated_traits.append(trait) + + updated_collection['members'] = updated_traits + updated_collection['num_members'] = len(updated_traits) + updated_collection['changed_timestamp'] = datetime.datetime.utcnow().strftime( + '%b %d %Y %I:%M%p') + + updated_collections = [] + for collection in self.user_collections: + if collection['id'] == collection_id: + updated_collections.append(updated_collection) + else: + updated_collections.append(collection) + + self.update_collections(updated_collections) + + return updated_traits + + def get_collection_by_id(self, collection_id): + for collection in self.user_collections: + if collection['id'] == collection_id: + return collection + + def get_collection_by_name(self, collection_name): + for collection in self.user_collections: + if collection['name'] == collection_name: + return collection + + return None + + def update_collections(self, updated_collections): + collection_body = json.dumps(updated_collections) + + save_collections(self.user_id, collection_body) + + def import_traits_to_user(self, anon_id): + collections = get_user_collections(anon_id) + for collection in collections: + collection_exists = self.get_collection_by_name(collection['name']) + if collection_exists: + continue + else: + self.add_collection(collection['name'], collection['members']) + + def delete_session(self): + # And more importantly delete the redis record + Redis.delete(self.redis_key) + self.logged_in = False diff --git a/gn2/wqflask/views.py b/gn2/wqflask/views.py new file mode 100644 index 00000000..4f636ebc --- /dev/null +++ b/gn2/wqflask/views.py @@ -0,0 +1,1328 @@ +"""Main routing table for GN2""" +import array +import base64 +import csv +import datetime +import flask +import hashlib +import io # Todo: Use cStringIO? + +import json +import numpy as np +import os +import pickle as pickle +import random +import requests +import sys +import traceback +import uuid +import xlsxwriter + +from functools import reduce + +from zipfile import ZipFile +from zipfile import ZIP_DEFLATED + +from uuid import UUID + +from urllib.parse import urljoin + +from gn2.wqflask import app + +from gn3.computations.gemma import generate_hash_of_string +from flask import current_app +from flask import g +from flask import Response +from flask import request +from flask import make_response +from flask import render_template +from flask import send_from_directory +from flask import redirect +from flask import send_file +from flask import url_for +from flask import flash +from flask import session + +# Some of these (like collect) might contain endpoints, so they're still used. +# Blueprints should probably be used instead. +from gn2.wqflask import collect +from gn2.wqflask import search_results +from gn2.wqflask import server_side +from gn2.base.data_set import create_dataset # Used by YAML in marker_regression +from gn2.wqflask.show_trait import show_trait +from gn2.wqflask.show_trait import export_trait_data +from gn2.wqflask.show_trait.show_trait import get_diff_of_vals +from gn2.wqflask.heatmap import heatmap +from gn2.wqflask.external_tools import send_to_bnw +from gn2.wqflask.external_tools import send_to_webgestalt +from gn2.wqflask.external_tools import send_to_geneweaver +from gn2.wqflask.comparison_bar_chart import comparison_bar_chart +from gn2.wqflask.marker_regression import run_mapping +from gn2.wqflask.marker_regression.exceptions import NoMappingResultsError +from gn2.wqflask.marker_regression import display_mapping_results +from gn2.wqflask.network_graph import network_graph +from gn2.wqflask.correlation.show_corr_results import set_template_vars +from gn2.wqflask.correlation.correlation_gn3_api import compute_correlation +from gn2.wqflask.correlation.rust_correlation import compute_correlation_rust +from gn2.wqflask.correlation_matrix import show_corr_matrix +from gn2.wqflask.correlation import corr_scatter_plot +from gn2.wqflask.correlation.exceptions import WrongCorrelationType +from gn2.wqflask.ctl.gn3_ctl_analysis import run_ctl + +from gn2.wqflask.wgcna.gn3_wgcna import run_wgcna +from gn2.wqflask.snp_browser import snp_browser +from gn2.wqflask.search_results import SearchResultPage +from gn2.wqflask.export_traits import export_traits +from gn2.wqflask.gsearch import GSearch +from gn2.wqflask.update_search_results import GSearch as UpdateGSearch +from gn2.wqflask.docs import Docs, update_text +from gn2.wqflask.decorators import edit_access_required +from gn2.wqflask.db_info import InfoPage + +from gn2.wqflask.oauth2 import client +from gn2.wqflask.oauth2.client import no_token_get +from gn2.wqflask.oauth2.request_utils import with_flash_error, with_flash_success + +from gn2.utility import temp_data +from gn2.utility.tools import get_setting +from gn2.utility.tools import TEMPDIR +from gn2.utility.tools import USE_REDIS +from gn2.utility.tools import REDIS_URL +from gn2.utility.tools import GN_SERVER_URL +from gn2.utility.tools import GN3_LOCAL_URL +from gn2.utility.tools import GN_VERSION +from gn2.utility.tools import JS_TWITTER_POST_FETCHER_PATH +from gn2.utility.tools import JS_GUIX_PATH +from gn2.utility.helper_functions import get_species_groups +from gn2.utility.redis_tools import get_redis_conn + +import gn2.utility.hmac as hmac + +from gn2.base.webqtlConfig import TMPDIR +from gn2.base.webqtlConfig import GENERATED_IMAGE_DIR + +from gn2.wqflask.database import database_connection + +import gn2.jobs.jobs as jobs + +from gn2.wqflask.oauth2.session import session_info +from gn2.wqflask.oauth2.checks import user_logged_in + +from gn2.wqflask import requests as monad_requests + +Redis = get_redis_conn() + + +@app.errorhandler(Exception) +def handle_generic_exceptions(e): + import werkzeug + err_msg = str(e) + now = datetime.datetime.utcnow() + time_str = now.strftime('%l:%M%p UTC %b %d, %Y') + # get the stack trace and send it to the logger + exc_type, exc_value, exc_traceback = sys.exc_info() + formatted_lines = (f"{request.url} ({time_str}) \n" + f"{traceback.format_exc()}") + _message_templates = { + werkzeug.exceptions.NotFound: ("404: Not Found: " + f"{time_str}: {request.url}"), + werkzeug.exceptions.BadRequest: ("400: Bad Request: " + f"{time_str}: {request.url}"), + werkzeug.exceptions.RequestTimeout: ("408: Request Timeout: " + f"{time_str}: {request.url}")} + # Default to the lengthy stack trace! + app.logger.error(_message_templates.get(exc_type, + formatted_lines)) + # Handle random animations + # Use a cookie to have one animation on refresh + animation = request.cookies.get(err_msg[:32]) + if not animation: + animation = random.choice([fn for fn in os.listdir( + "./wqflask/static/gif/error") if fn.endswith(".gif")]) + + resp = make_response(render_template("error.html", message=err_msg, + stack={formatted_lines}, + error_image=animation, + version=GN_VERSION)) + resp.set_cookie(err_msg[:32], animation) + return resp + + +@app.route("/authentication_needed") +def no_access_page(): + return render_template("new_security/not_authenticated.html") + + +@app.route("/") +def index_page(): + anon_id = session_info()["anon_id"] + def __render__(colls): + return render_template("index_page.html", version=GN_VERSION, + gn_server_url=GN_SERVER_URL, + anon_collections=( + colls if user_logged_in() else []), + anon_id=anon_id) + + return no_token_get( + f"auth/user/collections/{anon_id}/list").either( + lambda err: __render__([]), + __render__) + + +@app.route("/tmp/<img_path>") +def tmp_page(img_path): + initial_start_vars = request.form + imgfile = open(GENERATED_IMAGE_DIR + img_path, 'rb') + imgdata = imgfile.read() + imgB64 = base64.b64encode(imgdata) + bytesarray = array.array('B', imgB64) + return render_template("show_image.html", + img_base64=bytesarray) + + +@app.route("/js/<path:filename>") +def js(filename): + js_path = JS_GUIX_PATH + name = filename + if 'js_alt/' in filename: + js_path = js_path.replace('genenetwork2/javascript', 'javascript') + name = name.replace('js_alt/', '') + return send_from_directory(js_path, name) + + +@app.route("/css/<path:filename>") +def css(filename): + js_path = JS_GUIX_PATH + name = filename + if 'js_alt/' in filename: + js_path = js_path.replace('genenetwork2/javascript', 'javascript') + name = name.replace('js_alt/', '') + return send_from_directory(js_path, name) + + +@app.route("/twitter/<path:filename>") +def twitter(filename): + return send_from_directory(JS_TWITTER_POST_FETCHER_PATH, filename) + + +@app.route("/search", methods=('GET',)) +def search_page(): + result = None + if USE_REDIS: + key = "search_results:v1:" + \ + json.dumps(request.args, sort_keys=True) + result = Redis.get(key) + if result: + result = pickle.loads(result) + result = SearchResultPage(request.args).__dict__ + valid_search = result['search_term_exists'] + if USE_REDIS and valid_search: + # Redis.set(key, pickle.dumps(result, pickle.HIGHEST_PROTOCOL)) + Redis.expire(key, 60 * 60) + + if valid_search: + return render_template("search_result_page.html", **result) + else: + return render_template("search_error.html") + + +@app.route("/search_table", methods=('GET',)) +def search_page_table(): + the_search = search_results.SearchResultPage(request.args) + current_page = server_side.ServerSideTable( + len(the_search.trait_list), + the_search.trait_list, + the_search.header_data_names, + request.args, + ).get_page() + + return flask.jsonify(current_page) + + +@app.route("/gsearch", methods=('GET',)) +def gsearchact(): + result = GSearch(request.args).__dict__ + type = request.args['type'] + if type == "gene": + return render_template("gsearch_gene.html", **result) + elif type == "phenotype": + return render_template("gsearch_pheno.html", **result) + + +@app.route("/gsearch_table", methods=('GET',)) +def gsearchtable(): + gsearch_table_data = GSearch(request.args) + current_page = server_side.ServerSideTable( + gsearch_table_data.trait_count, + gsearch_table_data.trait_list, + gsearch_table_data.header_data_names, + request.args, + ).get_page() + + return flask.jsonify(current_page) + + + +@app.route("/gnqna",methods =["POST","GET"]) +def gnqna(): + if request.method == "POST": + try: + def __error__(resp): + return resp.json() + def __success__(resp): + + return render_template("gnqa_answer.html",**resp.json()) + return monad_requests.post( + urljoin(GN_SERVER_URL, + "llm/gnqna"), + json=dict(request.form), + ).then( + lambda resp: resp + ).either( + __error__, __success__) + except Exception as error: + return flask.jsonify({"error":str(error)}) + return render_template("gnqa.html") + + +@app.route("/gsearch_updating", methods=('POST',)) +def gsearch_updating(): + result = UpdateGSearch(request.args).__dict__ + return result['results'] + + +@app.route("/docedit") +def docedit(): + try: + if g.user_session.record['user_email_address'] == "zachary.a.sloan@gmail.com" or g.user_session.record['user_email_address'] == "labwilliams@gmail.com": + doc = Docs(request.args['entry'], request.args) + return render_template("docedit.html", **doc.__dict__) + else: + return "You shouldn't be here!" + except: + return "You shouldn't be here!" + + +@app.route('/generated/<filename>') +def generated_file(filename): + return send_from_directory(GENERATED_IMAGE_DIR, filename) + + +@app.route("/help") +def help(): + doc = Docs("help", request.args) + return render_template("docs.html", **doc.__dict__) + + +@app.route("/wgcna_setup", methods=('POST',)) +def wcgna_setup(): + # We are going to get additional user input for the analysis + # Display them using the template + return render_template("wgcna_setup.html", **request.form) + + +@app.route("/wgcna_results", methods=('POST',)) +def wcgna_results(): + """call the gn3 api to get wgcna response data""" + results = run_wgcna(dict(request.form)) + return render_template("gn3_wgcna_results.html", **results) + + +@app.route("/ctl_setup", methods=('POST',)) +def ctl_setup(): + # We are going to get additional user input for the analysis + # Display them using the template + return render_template("ctl_setup.html", **request.form) + + +@app.route("/ctl_results", methods=["POST"]) +def ctl_results(): + ctl_results = run_ctl(request.form) + return render_template("gn3_ctl_results.html", **ctl_results) + + +@app.route("/ctl_network_files/<file_name>/<file_type>") +def fetch_network_files(file_name, file_type): + file_path = f"{file_name}.{file_type}" + + file_path = os.path.join("/tmp/", file_path) + + return send_file(file_path) + + +@app.route("/intro") +def intro(): + doc = Docs("intro", request.args) + return render_template("docs.html", **doc.__dict__) + + +@app.route("/tutorials") +def tutorials(): + return render_template("tutorials.html") + + +@app.route("/credits") +def credits(): + return render_template("credits.html") + + +@app.route("/update_text", methods=('POST',)) +def update_page(): + update_text(request.form) + doc = Docs(request.form['entry_type'], request.form) + return render_template("docs.html", **doc.__dict__) + + +@app.route("/submit_trait") +def submit_trait_form(): + species_and_groups = get_species_groups() + return render_template( + "submit_trait.html", + species_and_groups=species_and_groups, + gn_server_url=GN_SERVER_URL, + version=GN_VERSION) + + +@app.route("/create_temp_trait", methods=('POST',)) +def create_temp_trait(): + doc = Docs("links") + return render_template("links.html", **doc.__dict__) + + +@app.route('/export_trait_excel', methods=('POST',)) +def export_trait_excel(): + """Excel file consisting of the sample data from the trait data and analysis page""" + trait_name, sample_data = export_trait_data.export_sample_table( + request.form) + app.logger.info(request.url) + buff = io.BytesIO() + workbook = xlsxwriter.Workbook(buff, {'in_memory': True}) + worksheet = workbook.add_worksheet() + for i, row in enumerate(sample_data): + for j, column in enumerate(row): + worksheet.write(i, j, row[j]) + workbook.close() + excel_data = buff.getvalue() + buff.close() + + return Response(excel_data, + mimetype='application/vnd.ms-excel', + headers={"Content-Disposition": "attachment;filename=" + trait_name + ".xlsx"}) + + +@app.route('/export_trait_csv', methods=('POST',)) +def export_trait_csv(): + """CSV file consisting of the sample data from the trait data and analysis page""" + trait_name, sample_data = export_trait_data.export_sample_table( + request.form) + + buff = io.StringIO() + writer = csv.writer(buff) + for row in sample_data: + writer.writerow(row) + csv_data = buff.getvalue() + buff.close() + + return Response(csv_data, + mimetype='text/csv', + headers={"Content-Disposition": "attachment;filename=" + trait_name + ".csv"}) + + +@app.route('/export_traits_csv', methods=('POST',)) +def export_traits_csv(): + """CSV file consisting of the traits from the search result page""" + file_list = export_traits(request.form, "metadata") + + if len(file_list) > 1: + now = datetime.datetime.now() + time_str = now.strftime('%H:%M_%d%B%Y') + filename = "export_{}".format(time_str) + memory_file = io.BytesIO() + with ZipFile(memory_file, mode='w', compression=ZIP_DEFLATED) as zf: + for the_file in file_list: + zf.writestr(the_file[0], the_file[1]) + + memory_file.seek(0) + + return send_file(memory_file, attachment_filename=filename + ".zip", as_attachment=True) + else: + return Response(file_list[0][1], + mimetype='text/csv', + headers={"Content-Disposition": "attachment;filename=" + file_list[0][0]}) + + +@app.route('/export_collection', methods=('POST',)) +def export_collection_csv(): + """CSV file consisting of trait list so collections can be exported/shared""" + out_file = export_traits(request.form, "collection") + return Response(out_file[1], + mimetype='text/csv', + headers={"Content-Disposition": "attachment;filename=" + out_file[0] + ".csv"}) + + +@app.route('/export_perm_data', methods=('POST',)) +def export_perm_data(): + """CSV file consisting of the permutation data for the mapping results""" + perm_info = json.loads(request.form['perm_info']) + + now = datetime.datetime.now() + time_str = now.strftime('%H:%M_%d%B%Y') + + file_name = "Permutation_" + \ + perm_info['num_perm'] + "_" + perm_info['trait_name'] + "_" + time_str + + the_rows = [ + ["#Permutation Test"], + ["#File_name: " + file_name], + ["#Metadata: From GeneNetwork.org"], + ["#Trait_ID: " + perm_info['trait_name']], + ["#Trait_description: " + perm_info['trait_description']], + ["#N_permutations: " + str(perm_info['num_perm'])], + ["#Cofactors: " + perm_info['cofactors']], + ["#N_cases: " + str(perm_info['n_samples'])], + ["#N_genotypes: " + str(perm_info['n_genotypes'])], + ["#Genotype_file: " + perm_info['genofile']], + ["#Units_linkage: " + perm_info['units_linkage']], + ["#Permutation_stratified_by: " + + ", ".join([str(cofactor) for cofactor in perm_info['strat_cofactors']])], + ["#RESULTS_1: Suggestive LRS(p=0.63) = " + + str(np.percentile(np.array(perm_info['perm_data']), 67))], + ["#RESULTS_2: Significant LRS(p=0.05) = " + str( + np.percentile(np.array(perm_info['perm_data']), 95))], + ["#RESULTS_3: Highly Significant LRS(p=0.01) = " + str( + np.percentile(np.array(perm_info['perm_data']), 99))], + ["#Comment: Results sorted from low to high peak linkage"] + ] + + buff = io.StringIO() + writer = csv.writer(buff) + writer.writerows(the_rows) + for item in perm_info['perm_data']: + writer.writerow([item]) + csv_data = buff.getvalue() + buff.close() + + return Response(csv_data, + mimetype='text/csv', + headers={"Content-Disposition": "attachment;filename=" + file_name + ".csv"}) + + +@app.route("/show_temp_trait", methods=('POST',)) +def show_temp_trait_page(): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + user_id = ((g.user_session.record.get(b"user_id") or b"").decode("utf-8") + or g.user_session.record.get("user_id") or "") + template_vars = show_trait.ShowTrait(cursor, + user_id=user_id, + kw=request.form) + template_vars.js_data = json.dumps(template_vars.js_data, + default=json_default_handler, + indent=" ") + return redirect(url_for("show_trait_page", dataset=template_vars.dataset.name, trait_id=template_vars.trait_id)) + + +@app.route("/show_trait") +def show_trait_page(): + def __show_trait__(privileges_data): + assert len(privileges_data) == 1 + privileges_data = privileges_data[0] + trait_privileges = tuple(item for item in privileges_data["privileges"]) + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + + user_id = ((g.user_session.record.get(b"user_id") or b"").decode("utf-8") + or g.user_session.record.get("user_id") or "") + template_vars = show_trait.ShowTrait(cursor, + user_id=user_id, + kw=request.args) + template_vars.js_data = json.dumps(template_vars.js_data, + default=json_default_handler, + indent=" ") + return render_template( + "show_trait.html", + **{ + **template_vars.__dict__, + "user": privileges_data["user"], + "trait_privileges": trait_privileges, + "resource_id": privileges_data["resource_id"] + }) + dataset = request.args["dataset"] + trait_id = request.args["trait_id"] + + return client.post( + "auth/data/authorisation", + json={ + "traits": [f"{dataset}::{trait_id}"] + }).either(with_flash_error(render_template("show_trait_error.html")), + __show_trait__) + + +@app.route("/heatmap", methods=('POST',)) +def heatmap_page(): + start_vars = request.form + temp_uuid = uuid.uuid4() + + traits = [trait.strip() for trait in start_vars['trait_list'].split(',')] + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + if traits[0] != "": + version = "v5" + key = "heatmap:{}:".format( + version) + json.dumps(start_vars, sort_keys=True) + result = Redis.get(key) + + if result: + result = pickle.loads(result) + + else: + template_vars = heatmap.Heatmap(cursor, request.form, temp_uuid) + template_vars.js_data = json.dumps(template_vars.js_data, + default=json_default_handler, + indent=" ") + + result = template_vars.__dict__ + + pickled_result = pickle.dumps(result, pickle.HIGHEST_PROTOCOL) + Redis.set(key, pickled_result) + Redis.expire(key, 60 * 60) + rendered_template = render_template("heatmap.html", **result) + + else: + rendered_template = render_template( + "empty_collection.html", **{'tool': 'Heatmap'}) + + return rendered_template + + +@app.route("/bnw_page", methods=('POST',)) +def bnw_page(): + start_vars = request.form + + traits = [trait.strip() for trait in start_vars['trait_list'].split(',')] + if traits[0] != "": + template_vars = send_to_bnw.SendToBNW(request.form) + + result = template_vars.__dict__ + rendered_template = render_template("bnw_page.html", **result) + else: + rendered_template = render_template( + "empty_collection.html", **{'tool': 'BNW'}) + + return rendered_template + + +@app.route("/webgestalt_page", methods=('POST',)) +def webgestalt_page(): + start_vars = request.form + + traits = [trait.strip() for trait in start_vars['trait_list'].split(',')] + if traits[0] != "": + template_vars = send_to_webgestalt.SendToWebGestalt(request.form) + + result = template_vars.__dict__ + rendered_template = render_template("webgestalt_page.html", **result) + else: + rendered_template = render_template( + "empty_collection.html", **{'tool': 'WebGestalt'}) + + return rendered_template + + +@app.route("/geneweaver_page", methods=('POST',)) +def geneweaver_page(): + start_vars = request.form + + traits = [trait.strip() for trait in start_vars['trait_list'].split(',')] + if traits[0] != "": + template_vars = send_to_geneweaver.SendToGeneWeaver(request.form) + + result = template_vars.__dict__ + rendered_template = render_template("geneweaver_page.html", **result) + else: + rendered_template = render_template( + "empty_collection.html", **{'tool': 'GeneWeaver'}) + + return rendered_template + + +@app.route("/comparison_bar_chart", methods=('POST',)) +def comp_bar_chart_page(): + start_vars = request.form + + traits = [trait.strip() for trait in start_vars['trait_list'].split(',')] + if traits[0] != "": + template_vars = comparison_bar_chart.ComparisonBarChart(request.form) + template_vars.js_data = json.dumps(template_vars.js_data, + default=json_default_handler, + indent=" ") + + result = template_vars.__dict__ + rendered_template = render_template( + "comparison_bar_chart.html", **result) + else: + rendered_template = render_template( + "empty_collection.html", **{'tool': 'Comparison Bar Chart'}) + + return rendered_template + + +@app.route("/mapping_results_container") +def mapping_results_container_page(): + return render_template("mapping_results_container.html") + + +@app.route("/loading", methods=('POST',)) +def loading_page(): + initial_start_vars = request.form + start_vars_container = {} + n_samples = 0 # ZS: So it can be displayed on loading page + if 'wanted_inputs' in initial_start_vars: + wanted = initial_start_vars['wanted_inputs'].split(",") + start_vars = {} + for key, value in list(initial_start_vars.items()): + if key in wanted: + start_vars[key] = value + + sample_vals_dict = json.loads(start_vars['sample_vals']) + if 'n_samples' in start_vars: + n_samples = int(start_vars['n_samples']) + else: + if 'group' in start_vars: + dataset = create_dataset( + start_vars['dataset'], group_name=start_vars['group']) + else: + dataset = create_dataset(start_vars['dataset']) + start_vars['trait_name'] = start_vars['trait_id'] + if dataset.type == "Publish": + start_vars['trait_name'] = f"{dataset.group.code}_{start_vars['trait_name']}" + samples = dataset.group.samplelist + if 'genofile' in start_vars: + if start_vars['genofile'] != "": + genofile_string = start_vars['genofile'] + dataset.group.genofile = genofile_string.split(":")[0] + genofile_samples = run_mapping.get_genofile_samplelist( + dataset) + if len(genofile_samples) > 1: + samples = genofile_samples + + for sample in samples: + if sample in sample_vals_dict: + if sample_vals_dict[sample] != "x": + n_samples += 1 + + start_vars['n_samples'] = n_samples + start_vars['vals_hash'] = generate_hash_of_string( + str(sample_vals_dict)) + if start_vars['dataset'] != "Temp": # Currently can't get diff for temp traits + start_vars['vals_diff'] = get_diff_of_vals(sample_vals_dict, str( + start_vars['trait_id'] + ":" + str(start_vars['dataset']))) + + start_vars['wanted_inputs'] = initial_start_vars['wanted_inputs'] + + start_vars_container['start_vars'] = start_vars + else: + start_vars_container['start_vars'] = initial_start_vars + + rendered_template = render_template("loading.html", **start_vars_container) + + return rendered_template + + +@app.route("/run_mapping", methods=('POST',)) +@app.route("/run_mapping/<path:hash_of_inputs>") +def mapping_results_page(hash_of_inputs=None): + if hash_of_inputs: + initial_start_vars = json.loads(Redis.get(hash_of_inputs)) + initial_start_vars['hash_of_inputs'] = hash_of_inputs + else: + initial_start_vars = request.form + + # Get hash of inputs (as JSON) for sharing results + inputs_json = json.dumps(initial_start_vars, sort_keys=True) + dhash = hashlib.md5() + dhash.update(inputs_json.encode()) + hash_of_inputs = dhash.hexdigest() + + # Just store for one hour on initial load; will be stored for longer if user clicks Share + Redis.set(hash_of_inputs, inputs_json, ex=60*60*24*30) + + temp_uuid = initial_start_vars['temp_uuid'] + wanted = ( + 'trait_id', + 'dataset', + 'group', + 'species', + 'samples', + 'vals', + 'sample_vals', + 'vals_hash', + 'first_run', + 'output_files', + 'geno_db_exists', + 'method', + 'mapping_results_path', + 'trimmed_markers', + 'selected_chr', + 'chromosomes', + 'mapping_scale', + 'plotScale', + 'score_type', + 'suggestive', + 'significant', + 'num_perm', + 'permCheck', + 'perm_strata', + 'categorical_vars', + 'perm_output', + 'num_bootstrap', + 'bootCheck', + 'bootstrap_results', + 'LRSCheck', + 'covariates', + 'maf', + 'use_loco', + 'manhattan_plot', + 'color_scheme', + 'manhattan_single_color', + 'control_marker', + 'do_control', + 'genofile', + 'genofile_string', + 'pair_scan', + 'startMb', + 'endMb', + 'graphWidth', + 'lrsMax', + 'additiveCheck', + 'showSNP', + 'showHomology', + 'showGenes', + 'viewLegend', + 'haplotypeAnalystCheck', + 'mapmethod_rqtl', + 'mapmodel_rqtl', + 'temp_trait', + 'n_samples', + 'transform', + 'hash_of_inputs', + 'dataid' + ) + start_vars = {} + for key, value in list(initial_start_vars.items()): + if key in wanted: + start_vars[key] = value + + start_vars['hash_of_inputs'] = hash_of_inputs + + # Store trait sample data in Redis, so additive effect scatterplots can include edited values + dhash = hashlib.md5() + dhash.update(start_vars['sample_vals'].encode()) + samples_hash = dhash.hexdigest() + Redis.set(samples_hash, start_vars['sample_vals'], ex=7*24*60*60) + start_vars['dataid'] = samples_hash + + version = "v3" + key = "mapping_results:{}:".format( + version) + json.dumps(start_vars, sort_keys=True) + result = None # Just for testing + + if result: + result = pickle.loads(result) + else: + template_vars = run_mapping.RunMapping(start_vars, temp_uuid) + if template_vars.no_results: + raise NoMappingResultsError( + start_vars["trait_id"], start_vars["dataset"], start_vars["method"]) + + if not template_vars.pair_scan: + template_vars.js_data = json.dumps(template_vars.js_data, + default=json_default_handler, + indent=" ") + + result = template_vars.__dict__ + + if result['pair_scan']: + rendered_template = render_template( + "pair_scan_results.html", **result) + else: + gn1_template_vars = display_mapping_results.DisplayMappingResults( + result).__dict__ + + rendered_template = render_template( + "mapping_results.html", **gn1_template_vars) + + return rendered_template + +@app.route("/cache_mapping_inputs", methods=('POST',)) +def cache_mapping_inputs(): + cache_id = request.form.get("inputs_hash") + inputs_json = Redis.get(cache_id) + Redis.set(cache_id, inputs_json) + + return "Success" + +@app.route("/export_mapping_results", methods=('POST',)) +def export_mapping_results(): + file_path = request.form.get("results_path") + results_csv = open(file_path, "r").read() + response = Response(results_csv, + mimetype='text/csv', + headers={"Content-Disposition": "attachment;filename=" + os.path.basename(file_path)}) + + return response + + +@app.route("/export_corr_matrix", methods=('POST',)) +def export_corr_matrix(): + file_path = request.form.get("export_filepath") + file_name = request.form.get("export_filename") + results_csv = open(file_path, "r").read() + response = Response(results_csv, + mimetype='text/csv', + headers={"Content-Disposition": "attachment;filename=" + file_name + ".csv"}) + + return response + + +@app.route("/export", methods=('POST',)) +def export(): + svg_xml = request.form.get("data", "Invalid data") + filename = request.form.get("filename", "manhattan_plot_snp") + response = Response(svg_xml, mimetype="image/svg+xml") + response.headers["Content-Disposition"] = "attachment; filename=%s" % filename + return response + + +@app.route("/export_pdf", methods=('POST',)) +def export_pdf(): + import cairosvg + svg_xml = request.form.get("data", "Invalid data") + filename = request.form.get("filename", "interval_map_pdf") + pdf_file = cairosvg.svg2pdf(bytestring=svg_xml) + response = Response(pdf_file, mimetype="application/pdf") + response.headers["Content-Disposition"] = "attachment; filename=%s" % filename + return response + + +@app.route("/network_graph", methods=('POST',)) +def network_graph_page(): + start_vars = request.form + traits = [trait.strip() for trait in start_vars['trait_list'].split(',')] + if traits[0] != "": + template_vars = network_graph.NetworkGraph(start_vars) + template_vars.js_data = json.dumps(template_vars.js_data, + default=json_default_handler, + indent=" ") + + return render_template("network_graph.html", **template_vars.__dict__) + else: + return render_template("empty_collection.html", **{'tool': 'Network Graph'}) + +def __handle_correlation_error__(exc): + return render_template( + "correlation_error_page.html", + error = { + "error-type": { + "WrongCorrelationType": "Wrong Correlation Type" + }[type(exc).__name__], + "error-message": exc.args[0] + }) + +@app.route("/corr_compute", methods=('POST', 'GET')) +def corr_compute_page(): + with Redis.from_url(REDIS_URL, decode_responses=True) as rconn: + if request.method == "POST": + request_received = datetime.datetime.utcnow() + filename=hmac.hmac_creation(f"request_form_{request_received.isoformat()}") + filepath = f"{TMPDIR}{filename}" + with open(filepath, "wb") as pfile: + pickle.dump(request.form, pfile, protocol=pickle.HIGHEST_PROTOCOL) + job_id = jobs.queue( + rconn, { + "command": [ + sys.executable, "-m", "scripts.corr_compute", filepath, + g.user_session.user_id], + "request_received_time": request_received.isoformat(), + "status": "queued" + }) + jobs.run(job_id, REDIS_URL) + + return redirect(url_for("corr_compute_page", job_id=str(job_id))) + + job = jobs.job( + rconn, UUID(request.args.get("job_id"))).maybe( + {}, lambda the_job: the_job) + + if jobs.completed_successfully(job): + output = json.loads(job.get("stdout", "{}")) + return render_template("correlation_page.html", **output) + + if jobs.completed_erroneously(job): + try: + error_output = { + "error-type": "ComputeError", + "error-message": "There was an error computing the correlations", + **json.loads(job.get("stdout") or "{}"), + "stderr-output": job.get("stderr", "").split("\n") + } + return render_template( + "correlation_error_page.html", error=error_output) + except json.decoder.JSONDecodeError as jde: + raise Exception(f"STDOUT: {job.get('stdout')}") from jde + + return render_template("loading_corrs.html") + + +@app.route("/corr_matrix", methods=('POST',)) +def corr_matrix_page(): + start_vars = request.form + traits = [trait.strip() for trait in start_vars['trait_list'].split(',')] + if len(traits) > 1: + template_vars = show_corr_matrix.CorrelationMatrix(start_vars) + template_vars.js_data = json.dumps(template_vars.js_data, + default=json_default_handler, + indent=" ") + + return render_template("correlation_matrix.html", **template_vars.__dict__) + else: + return render_template("empty_collection.html", **{'tool': 'Correlation Matrix'}) + + +@app.route("/corr_scatter_plot") +def corr_scatter_plot_page(): + template_vars = corr_scatter_plot.CorrScatterPlot(request.args) + template_vars.js_data = json.dumps(template_vars.js_data, + default=json_default_handler, + indent=" ") + return render_template("corr_scatterplot.html", **template_vars.__dict__) + + +@app.route("/snp_browser", methods=('GET',)) +def snp_browser_page(): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + template_vars = snp_browser.SnpBrowser(cursor, request.args) + return render_template("snp_browser.html", **template_vars.__dict__) + + +@app.route("/db_info", methods=('GET',)) +def db_info_page(): + template_vars = InfoPage(request.args) + + return render_template("info_page.html", **template_vars.__dict__) + + +@app.route("/snp_browser_table", methods=('GET',)) +def snp_browser_table(): + with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor: + snp_table_data = snp_browser.SnpBrowser(cursor, request.args) + current_page = server_side.ServerSideTable( + snp_table_data.rows_count, + snp_table_data.table_rows, + snp_table_data.header_data_names, + request.args, + ).get_page() + + return flask.jsonify(current_page) + + +@app.route("/tutorial/WebQTLTour", methods=('GET',)) +def tutorial_page(): + # ZS: Currently just links to GN1 + return redirect("http://gn1.genenetwork.org/tutorial/WebQTLTour/") + + +@app.route("/tutorial/security", methods=('GET',)) +def security_tutorial_page(): + # ZS: Currently just links to GN1 + return render_template("admin/security_help.html") + + +@app.route("/submit_bnw", methods=('POST',)) +def submit_bnw(): + return render_template("empty_collection.html", **{'tool': 'Correlation Matrix'}) + +# Take this out or secure it before putting into production + + +@app.route("/get_temp_data") +def get_temp_data(): + temp_uuid = request.args['key'] + return flask.jsonify(temp_data.TempData(temp_uuid).get_all()) + + +@app.route("/browser_input", methods=('GET',)) +def browser_inputs(): + """ Returns JSON from tmp directory for the purescript genome browser""" + + filename = request.args['filename'] + + with open("{}/gn2/".format(TEMPDIR) + filename + ".json", "r") as the_file: + file_contents = json.load(the_file) + + return flask.jsonify(file_contents) + + +def json_default_handler(obj): + """Based on http://stackoverflow.com/a/2680060/1175849""" + # Handle datestamps + if hasattr(obj, 'isoformat'): + return obj.isoformat() + # Handle integer keys for dictionaries + elif isinstance(obj, int) or isinstance(obj, uuid.UUID): + return str(obj) + # Handle custom objects + if hasattr(obj, '__dict__'): + return obj.__dict__ + else: + raise TypeError('Object of type %s with value of %s is not JSON serializable' % ( + type(obj), repr(obj))) + + +@app.route("/admin/data-sample/diffs/") +@edit_access_required +def display_diffs_admin(): + TMPDIR = current_app.config.get("TMPDIR") + DIFF_DIR = f"{TMPDIR}/sample-data/diffs" + files = [] + if os.path.exists(DIFF_DIR): + files = os.listdir(DIFF_DIR) + files = filter(lambda x: not(x.endswith((".approved", ".rejected"))), + files) + return render_template("display_files_admin.html", + files=files) + + +@app.route("/user/data-sample/diffs/") +def display_diffs_users(): + TMPDIR = current_app.config.get("TMPDIR") + DIFF_DIR = f"{TMPDIR}/sample-data/diffs" + files = [] + author = g.user_session.record.get(b'user_name').decode("utf-8") + if os.path.exists(DIFF_DIR): + files = os.listdir(DIFF_DIR) + files = filter(lambda x: not(x.endswith((".approved", ".rejected"))) + and author in x, + files) + return render_template("display_files_user.html", + files=files) + + +@app.route("/genewiki/<symbol>") +def display_generif_page(symbol): + """Fetch GeneRIF metadata from GN3 and display it""" + entries = requests.get( + urljoin( + GN3_LOCAL_URL, + f"/api/metadata/genewiki/{symbol}" + ) + ).json() + return render_template( + "generif.html", + symbol=symbol, + entries=entries + ) + + +@app.route("/datasets/<name>", methods=('GET',)) +def get_dataset(name): + metadata = requests.get( + urljoin( + GN3_LOCAL_URL, + f"/api/metadata/datasets/{name}") + ).json() + return render_template( + "dataset.html", + name=name, + dataset=metadata + ) + +@app.route("/datasets/search", methods=('POST',)) +def search_for_dataset(): + search = request.form.get('search') + return render_template( + "metadata/dataset_search_results.html", + results=search + ) + +@app.route("/publications/<name>", methods=('GET',)) +def get_publication(name): + metadata = requests.get( + urljoin( + GN3_LOCAL_URL, + f"/api/metadata/publications/{name}") + ).json() + return render_template( + "publication.html", + metadata=metadata, + ) + + +@app.route("/phenotypes/<name>", methods=('GET',)) +@app.route("/phenotypes/<group>/<name>", methods=('GET',)) +def get_phenotype(name, group=None): + if group: + name = f"{group}_{name}" + metadata = requests.get( + urljoin( + GN3_LOCAL_URL, + f"/api/metadata/phenotypes/{name}") + ).json() + return render_template( + "phenotype.html", + metadata=metadata, + ) + + +@app.route("/genotypes/<name>", methods=('GET',)) +def get_genotype(name): + metadata = requests.get( + urljoin( + GN3_LOCAL_URL, + f"/api/metadata/genotypes/{name}") + ).json() + return render_template( + "genotype.html", + metadata=metadata, + ) + +@app.route("/case-attribute/<int:inbredset_id>/edit", methods=["GET", "POST"]) +def edit_case_attributes(inbredset_id: int) -> Response: + """ + Edit the case-attributes for InbredSet group identified by `inbredset_id`. + """ + if request.method == "POST": + form = request.form + def __process_data__(acc, item): + _new, strain, calabel = tuple(val.strip() for val in item[0].split(":")) + old_row = acc.get(strain, {}) + return { + **acc, + strain: { + **old_row, "case-attributes": { + **old_row.get("case-attributes", {}), + calabel: item[1] + } + } + } + + edit_case_attributes_page = redirect(url_for( + "edit_case_attributes", inbredset_id=inbredset_id)) + token = session_info()["user"]["token"].either( + lambda err: err, lambda tok: tok["access_token"]) + def flash_success(resp): + def __succ__(remote_resp): + flash(f"Success: {remote_resp.json()['message']}", "alert-success") + return resp + return __succ__ + return monad_requests.post( + urljoin( + current_app.config["GN_SERVER_URL"], + f"/api/case-attribute/{inbredset_id}/edit"), + json={ + "edit-data": reduce(__process_data__, form.items(), {}) + }, + headers={ + "Authorization": f"Bearer {token}"}).either( + with_flash_error(edit_case_attributes_page), + flash_success(edit_case_attributes_page)) + + def __fetch_strains__(inbredset_group): + return monad_requests.get(urljoin( + current_app.config["GN_SERVER_URL"], + f"/api/case-attribute/{inbredset_id}/strains")).then( + lambda resp: {**inbredset_group, "strains": resp.json()}) + + def __fetch_names__(strains): + return monad_requests.get(urljoin( + current_app.config["GN_SERVER_URL"], + f"/api/case-attribute/{inbredset_id}/names")).then( + lambda resp: {**strains, "case_attribute_names": resp.json()}) + + def __fetch_values__(canames): + return monad_requests.get(urljoin( + current_app.config["GN_SERVER_URL"], + f"/api/case-attribute/{inbredset_id}/values")).then( + lambda resp: {**canames, "case_attribute_values": { + value["StrainName"]: value for value in resp.json()}}) + + return monad_requests.get(urljoin( + current_app.config["GN_SERVER_URL"], + f"/api/case-attribute/{inbredset_id}")).then( + lambda resp: {"inbredset_group": resp.json()}).then( + __fetch_strains__).then(__fetch_names__).then( + __fetch_values__).either( + lambda err: err, ## TODO: Handle error better + lambda values: render_template( + "edit_case_attributes.html", inbredset_id=inbredset_id, **values)) + +@app.route("/case-attribute/<int:inbredset_id>/list-diffs", methods=["GET"]) +def list_case_attribute_diffs(inbredset_id: int) -> Response: + """List any diffs awaiting review.""" + return monad_requests.get(urljoin( + current_app.config["GN_SERVER_URL"], + f"/api/case-attribute/{inbredset_id}/diff/list")).then( + lambda resp: resp.json()).either( + lambda err: render_template( + "list_case_attribute_diffs_error.html", + inbredset_id=inbredset_id, + error=err), + lambda diffs: render_template( + "list_case_attribute_diffs.html", + inbredset_id=inbredset_id, + diffs=diffs)) + +@app.route("/case-attribute/<int:inbredset_id>/diff/<int:diff_id>/view", methods=["GET"]) +def view_diff(inbredset_id:int, diff_id: int) -> Response: + """View the pending diff.""" + token = session_info()["user"]["token"].either( + lambda err: err, lambda tok: tok["access_token"]) + return monad_requests.get( + urljoin(current_app.config["GN_SERVER_URL"], + f"/api/case-attribute/{inbredset_id}/diff/{diff_id}/view"), + headers={"Authorization": f"Bearer {token}"}).then( + lambda resp: resp.json()).either( + lambda err: render_template( + "view_case_attribute_diff_error.html", error=err.json()), + lambda diff: render_template( + "view_case_attribute_diff.html", diff=diff)) + +@app.route("/case-attribute/diff/approve-reject", methods=["POST"]) +def approve_reject_diff() -> Response: + """Approve/Reject the diff.""" + try: + form = request.form + action = form["action"] + assert action in ("approve", "reject") + diff_data = json.loads(form["diff_data"]) + diff_data = { + **diff_data, + "created": datetime.datetime.fromisoformat(diff_data["created"])} + inbredset_id = diff_data["inbredset_id"] + filename = ( + f"{inbredset_id}:::{diff_data['user_id']}:::" + f"{diff_data['created'].isoformat()}.json") + + list_diffs_page = url_for("list_case_attribute_diffs", + inbredset_id=inbredset_id) + token = session_info()["user"]["token"].either( + lambda err: err, lambda tok: tok["access_token"]) + def __error__(resp): + error = resp.json() + flash((f"{resp.status_code} {error['error']}: " + f"{error['error_description']}"), + "alert-danger") + return redirect(list_diffs_page) + def __success__(results): + flash(results["message"], "alert-success") + return redirect(list_diffs_page) + return monad_requests.post( + urljoin(current_app.config["GN_SERVER_URL"], + f"/api/case-attribute/{action}/{filename}"), + headers={"Authorization": f"Bearer {token}"}).then( + lambda resp: resp.json() + ).either( + __error__, __success__) + except AssertionError as _ae: + flash("Invalid action! Expected either 'approve' or 'reject'.", + "alert-danger") + return redirect(url_for("view_diff", + inbredset_id=inbredset_id, + diff_id=form["diff_id"])) diff --git a/gn2/wqflask/wgcna/__init__.py b/gn2/wqflask/wgcna/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/gn2/wqflask/wgcna/__init__.py diff --git a/gn2/wqflask/wgcna/gn3_wgcna.py b/gn2/wqflask/wgcna/gn3_wgcna.py new file mode 100644 index 00000000..2cae4f18 --- /dev/null +++ b/gn2/wqflask/wgcna/gn3_wgcna.py @@ -0,0 +1,118 @@ +"""module contains code to consume gn3-wgcna api +and process data to be rendered by datatables +""" + +import requests +from types import SimpleNamespace + +from gn2.utility.helper_functions import get_trait_db_obs +from gn2.utility.tools import GN3_LOCAL_URL + + +def fetch_trait_data(requestform): + """fetch trait data""" + db_obj = SimpleNamespace() + get_trait_db_obs(db_obj, + [trait.strip() + for trait in requestform['trait_list'].split(',')]) + + return process_dataset(db_obj.trait_list) + + +def process_dataset(trait_list): + """process datasets and strains""" + + input_data = {} + traits = [] + strains = [] + + for trait in trait_list: + traits.append(trait[0].name) + + input_data[trait[0].name] = {} + for strain in trait[0].data: + strains.append(strain) + input_data[trait[0].name][strain] = trait[0].data[strain].value + + return { + "input": input_data, + "trait_names": traits, + "sample_names": strains + } + + +def process_wgcna_data(response): + """function for processing modeigene genes + for create row data for datataba""" + mod_eigens = response["output"]["ModEigens"] + + sample_names = response["input"]["sample_names"] + + mod_dataset = [[sample] for sample in sample_names] + + for _, mod_values in mod_eigens.items(): + for (index, _sample) in enumerate(sample_names): + mod_dataset[index].append(round(mod_values[index], 3)) + + return { + "col_names": ["sample_names", *mod_eigens.keys()], + "mod_dataset": mod_dataset + } + + +def process_image(response): + """function to process image check if byte string is empty""" + image_data = response["output"]["image_data"] + return ({ + "image_generated": True, + "image_data": image_data + } if image_data else { + "image_generated": False + }) + + +def run_wgcna(form_data): + """function to run wgcna""" + + wgcna_api = f"{GN3_LOCAL_URL}/api/wgcna/run_wgcna" + + trait_dataset = fetch_trait_data(form_data) + form_data["minModuleSize"] = int(form_data["MinModuleSize"]) + + form_data["SoftThresholds"] = [int(threshold.strip()) + for threshold in form_data['SoftThresholds'].rstrip().split(",")] + + try: + + unique_strains = list(set(trait_dataset["sample_names"])) + + response = requests.post(wgcna_api, json={ + "sample_names": unique_strains, + "trait_names": trait_dataset["trait_names"], + "trait_sample_data": list(trait_dataset["input"].values()), + **form_data + + } + ) + + status_code = response.status_code + response = response.json() + + parameters = { + "nstrains": len(unique_strains), + "nphe": len(trait_dataset["trait_names"]), + **{key: val for key, val in form_data.items() if key not in ["trait_list"]} + } + + return {"error": response} if status_code != 200 else { + "error": 'null', + "parameters": parameters, + "results": response, + "data": process_wgcna_data(response["data"]), + "image": process_image(response["data"]) + } + + except requests.exceptions.ConnectionError: + return { + "error": "A connection error to perform computation occurred" + } diff --git a/gn2/wqflask/wgcna/wgcna_analysis.py b/gn2/wqflask/wgcna/wgcna_analysis.py new file mode 100644 index 00000000..f982c021 --- /dev/null +++ b/gn2/wqflask/wgcna/wgcna_analysis.py @@ -0,0 +1,189 @@ +""" +WGCNA analysis for GN2 + +Author / Maintainer: Danny Arends <Danny.Arends@gmail.com> +""" +import base64 +import sys +import rpy2.robjects as ro # R Objects +import rpy2.rinterface as ri + +from array import array as arr +from numpy import * +from gn2.base.webqtlConfig import GENERATED_IMAGE_DIR +from rpy2.robjects.packages import importr + +from gn2.utility import webqtlUtil # Random number for the image +from gn2.utility import helper_functions + +utils = importr("utils") + +# Get pointers to some common R functions +r_library = ro.r["library"] # Map the library function +r_options = ro.r["options"] # Map the options function +r_read_csv = ro.r["read.csv"] # Map the read.csv function +r_dim = ro.r["dim"] # Map the dim function +r_c = ro.r["c"] # Map the c function +r_cat = ro.r["cat"] # Map the cat function +r_paste = ro.r["paste"] # Map the paste function +r_unlist = ro.r["unlist"] # Map the unlist function +r_unique = ro.r["unique"] # Map the unique function +r_length = ro.r["length"] # Map the length function +r_unlist = ro.r["unlist"] # Map the unlist function +r_list = ro.r.list # Map the list function +r_matrix = ro.r.matrix # Map the matrix function +r_seq = ro.r["seq"] # Map the seq function +r_table = ro.r["table"] # Map the table function +r_names = ro.r["names"] # Map the names function +r_sink = ro.r["sink"] # Map the sink function +r_is_NA = ro.r["is.na"] # Map the is.na function +r_file = ro.r["file"] # Map the file function +r_png = ro.r["png"] # Map the png function for plotting +r_dev_off = ro.r["dev.off"] # Map the dev.off function + + +class WGCNA: + def __init__(self): + # To log output from stdout/stderr to a file add `r_sink(log)` + print("Initialization of WGCNA") + + # Load WGCNA - Should only be done once, since it is quite expensive + r_library("WGCNA") + r_options(stringsAsFactors=False) + print("Initialization of WGCNA done, package loaded in R session") + # Map the enableWGCNAThreads function + self.r_enableWGCNAThreads = ro.r["enableWGCNAThreads"] + # Map the pickSoftThreshold function + self.r_pickSoftThreshold = ro.r["pickSoftThreshold"] + # Map the blockwiseModules function + self.r_blockwiseModules = ro.r["blockwiseModules"] + # Map the labels2colors function + self.r_labels2colors = ro.r["labels2colors"] + # Map the plotDendroAndColors function + self.r_plotDendroAndColors = ro.r["plotDendroAndColors"] + print("Obtained pointers to WGCNA functions") + + def run_analysis(self, requestform): + print("Starting WGCNA analysis on dataset") + # Enable multi threading + self.r_enableWGCNAThreads() + self.trait_db_list = [trait.strip() + for trait in requestform['trait_list'].split(',')] + print(("Retrieved phenotype data from database", + requestform['trait_list'])) + helper_functions.get_trait_db_obs(self, self.trait_db_list) + + # self.input contains the phenotype values we need to send to R + self.input = {} + # All the strains we have data for (contains duplicates) + strains = [] + # All the traits we have data for (should not contain duplicates) + traits = [] + for trait in self.trait_list: + traits.append(trait[0].name) + self.input[trait[0].name] = {} + for strain in trait[0].data: + strains.append(strain) + self.input[trait[0].name][strain] = trait[0].data[strain].value + + # Transfer the load data from python to R + # Unique strains in R vector + uStrainsR = r_unique(ro.Vector(strains)) + uTraitsR = r_unique(ro.Vector(traits)) # Unique traits in R vector + + r_cat("The number of unique strains:", r_length(uStrainsR), "\n") + r_cat("The number of unique traits:", r_length(uTraitsR), "\n") + + # rM is the datamatrix holding all the data in + # R /rows = strains columns = traits + rM = ro.r.matrix(ri.NA_Real, nrow=r_length(uStrainsR), ncol=r_length( + uTraitsR), dimnames=r_list(uStrainsR, uTraitsR)) + for t in uTraitsR: + # R uses vectors every single element is a vector + trait = t[0] + for s in uStrainsR: + # R uses vectors every single element is a vector + strain = s[0] + rM.rx[strain, trait] = self.input[trait].get( + strain) # Update the matrix location + sys.stdout.flush() + + self.results = {} + # Number of phenotypes/traits + self.results['nphe'] = r_length(uTraitsR)[0] + self.results['nstr'] = r_length( + uStrainsR)[0] # Number of strains + self.results['phenotypes'] = uTraitsR # Traits used + # Strains used in the analysis + self.results['strains'] = uStrainsR + # Store the user specified parameters for the output page + self.results['requestform'] = requestform + + # Calculate soft threshold if the user specified the + # SoftThreshold variable + if requestform.get('SoftThresholds') is not None: + powers = [int(threshold.strip()) + for threshold in requestform['SoftThresholds'].rstrip().split(",")] + rpow = r_unlist(r_c(powers)) + print(("SoftThresholds: {} == {}".format(powers, rpow))) + self.sft = self.r_pickSoftThreshold( + rM, powerVector=rpow, verbose=5) + + print(("PowerEstimate: {}".format(self.sft[0]))) + self.results['PowerEstimate'] = self.sft[0] + if self.sft[0][0] is ri.NA_Integer: + print("No power is suitable for the analysis, just use 1") + # No power could be estimated + self.results['Power'] = 1 + else: + # Use the estimated power + self.results['Power'] = self.sft[0][0] + else: + # The user clicked a button, so no soft threshold selection + # Use the power value the user gives + self.results['Power'] = requestform.get('Power') + + # Create the block wise modules using WGCNA + network = self.r_blockwiseModules( + rM, + power=self.results['Power'], + TOMType=requestform['TOMtype'], + minModuleSize=requestform['MinModuleSize'], + verbose=3) + + # Save the network for the GUI + self.results['network'] = network + + # How many modules and how many gene per module ? + print(("WGCNA found {} modules".format(r_table(network[1])))) + self.results['nmod'] = r_length(r_table(network[1]))[0] + + # The iconic WCGNA plot of the modules in the hanging tree + self.results['imgurl'] = webqtlUtil.genRandStr("WGCNAoutput_") + ".png" + self.results['imgloc'] = GENERATED_IMAGE_DIR + self.results['imgurl'] + r_png(self.results['imgloc'], width=1000, height=600, type='cairo-png') + mergedColors = self.r_labels2colors(network[1]) + self.r_plotDendroAndColors(network[5][0], mergedColors, + "Module colors", dendroLabels=False, + hang=0.03, addGuide=True, guideHang=0.05) + r_dev_off() + sys.stdout.flush() + + def render_image(self, results): + print(("pre-loading imgage results:", self.results['imgloc'])) + imgfile = open(self.results['imgloc'], 'rb') + imgdata = imgfile.read() + imgB64 = base64.b64encode(imgdata) + bytesarray = arr('B', imgB64) + self.results['imgdata'] = bytesarray + + def process_results(self, results): + print("Processing WGCNA output") + template_vars = {} + template_vars["input"] = self.input + # Results from the soft threshold analysis + template_vars["powers"] = self.sft[1:] + template_vars["results"] = self.results + self.render_image(results) + sys.stdout.flush() + return(dict(template_vars)) diff --git a/gn2/wsgi.py b/gn2/wsgi.py new file mode 100644 index 00000000..94cc605d --- /dev/null +++ b/gn2/wsgi.py @@ -0,0 +1,4 @@ +from gn2.run_gunicorn import app as application # expect application as a name + +if __name__ == "__main__": + application.run() |