From 204a308be0f741726b9a620d88fbc22b22124c81 Mon Sep 17 00:00:00 2001 From: Arun Isaac Date: Fri, 29 Dec 2023 18:55:37 +0000 Subject: Namespace all modules under gn2. We move all modules under a gn2 directory. This is important for "correct" packaging and deployment as a Guix service. --- gn2/wqflask/.DS_Store | Bin 0 -> 6148 bytes gn2/wqflask/__init__.py | 131 + gn2/wqflask/api/__init__.py | 0 gn2/wqflask/api/correlation.py | 244 + gn2/wqflask/api/gen_menu.py | 217 + gn2/wqflask/api/jobs.py | 54 + gn2/wqflask/api/mapping.py | 186 + gn2/wqflask/api/markdown.py | 186 + gn2/wqflask/api/router.py | 1037 +++ gn2/wqflask/app_errors.py | 31 + gn2/wqflask/collect.py | 396 + gn2/wqflask/comparison_bar_chart/__init__.py | 0 .../comparison_bar_chart/comparison_bar_chart.py | 95 + gn2/wqflask/correlation/__init__.py | 0 gn2/wqflask/correlation/corr_scatter_plot.py | 158 + gn2/wqflask/correlation/correlation_functions.py | 68 + gn2/wqflask/correlation/correlation_gn3_api.py | 262 + gn2/wqflask/correlation/exceptions.py | 16 + gn2/wqflask/correlation/pre_computes.py | 178 + gn2/wqflask/correlation/rust_correlation.py | 408 ++ gn2/wqflask/correlation/show_corr_results.py | 406 ++ gn2/wqflask/correlation_matrix/__init__.py | 0 gn2/wqflask/correlation_matrix/show_corr_matrix.py | 259 + gn2/wqflask/ctl/__init__.py | 0 gn2/wqflask/ctl/ctl_analysis.py | 214 + gn2/wqflask/ctl/gn3_ctl_analysis.py | 132 + gn2/wqflask/database.py | 52 + gn2/wqflask/db_info.py | 115 + gn2/wqflask/decorators.py | 137 + gn2/wqflask/do_search.py | 965 +++ gn2/wqflask/docs.py | 42 + gn2/wqflask/export_traits.py | 191 + gn2/wqflask/external_tools/__init__.py | 0 gn2/wqflask/external_tools/send_to_bnw.py | 70 + gn2/wqflask/external_tools/send_to_geneweaver.py | 113 + gn2/wqflask/external_tools/send_to_webgestalt.py | 129 + gn2/wqflask/group_manager.py | 157 + gn2/wqflask/gsearch.py | 62 + gn2/wqflask/heatmap/__init__.py | 0 gn2/wqflask/heatmap/heatmap.py | 191 + gn2/wqflask/interval_analyst/GeneUtil.py | 155 + gn2/wqflask/interval_analyst/__init__.py | 0 gn2/wqflask/jupyter_notebooks.py | 31 + gn2/wqflask/marker_regression/__init__.py | 0 .../marker_regression/display_mapping_results.py | 3336 +++++++++ gn2/wqflask/marker_regression/exceptions.py | 13 + gn2/wqflask/marker_regression/gemma_mapping.py | 245 + gn2/wqflask/marker_regression/plink_mapping.py | 167 + gn2/wqflask/marker_regression/qtlreaper_mapping.py | 193 + gn2/wqflask/marker_regression/rqtl_mapping.py | 167 + gn2/wqflask/marker_regression/run_mapping.py | 743 ++ gn2/wqflask/metadata_edits.py | 973 +++ gn2/wqflask/network_graph/__init__.py | 0 gn2/wqflask/network_graph/network_graph.py | 188 + gn2/wqflask/oauth2/__init__.py | 0 gn2/wqflask/oauth2/checks.py | 49 + gn2/wqflask/oauth2/client.py | 124 + gn2/wqflask/oauth2/collections.py | 16 + gn2/wqflask/oauth2/data.py | 319 + gn2/wqflask/oauth2/groups.py | 210 + gn2/wqflask/oauth2/request_utils.py | 99 + gn2/wqflask/oauth2/resources.py | 294 + gn2/wqflask/oauth2/roles.py | 99 + gn2/wqflask/oauth2/routes.py | 18 + gn2/wqflask/oauth2/session.py | 111 + gn2/wqflask/oauth2/toplevel.py | 57 + gn2/wqflask/oauth2/ui.py | 23 + gn2/wqflask/oauth2/users.py | 190 + gn2/wqflask/parser.py | 91 + gn2/wqflask/partial_correlations_views.py | 372 + gn2/wqflask/pbkdf2.py | 22 + gn2/wqflask/requests.py | 16 + gn2/wqflask/resource_manager.py | 169 + gn2/wqflask/search_results.py | 433 ++ gn2/wqflask/send_mail.py | 51 + gn2/wqflask/server_side.py | 90 + gn2/wqflask/show_trait/SampleList.py | 223 + gn2/wqflask/show_trait/__init__.py | 0 gn2/wqflask/show_trait/export_trait_data.py | 113 + gn2/wqflask/show_trait/show_trait.py | 859 +++ gn2/wqflask/snp_browser/__init__.py | 0 gn2/wqflask/snp_browser/snp_browser.py | 934 +++ gn2/wqflask/startup.py | 42 + gn2/wqflask/static/Congenic.png | Bin 0 -> 56578 bytes gn2/wqflask/static/fonts/README | 1 + gn2/wqflask/static/fonts/arial.ttf | Bin 0 -> 151232 bytes gn2/wqflask/static/fonts/courbd.ttf | Bin 0 -> 181388 bytes gn2/wqflask/static/fonts/fnt_bs.ttf | Bin 0 -> 20988 bytes gn2/wqflask/static/fonts/tahoma.ttf | Bin 0 -> 249012 bytes gn2/wqflask/static/fonts/trebucbd.ttf | Bin 0 -> 123828 bytes gn2/wqflask/static/fonts/verdana.ttf | Bin 0 -> 139640 bytes gn2/wqflask/static/fonts/verdanab.ttf | Bin 0 -> 156340 bytes gn2/wqflask/static/gif/89.gif | Bin 0 -> 27183 bytes gn2/wqflask/static/gif/error/Wild-Type-Mouse.gif | Bin 0 -> 37328 bytes .../static/gif/error/animated-gifs-aliens-29.gif | Bin 0 -> 14088 bytes .../static/gif/error/animated-gifs-angels-04.gif | Bin 0 -> 12155 bytes .../static/gif/error/animated-gifs-cats-016.gif | Bin 0 -> 10388 bytes .../static/gif/error/animated-gifs-cats-031.gif | Bin 0 -> 6937 bytes .../gif/error/animated-gifs-cell-phones-03.gif | Bin 0 -> 14902 bytes .../gif/error/animated-gifs-cell-phones-16.gif | Bin 0 -> 7307 bytes .../gif/error/animated-gifs-computers-13.gif | Bin 0 -> 7616 bytes .../gif/error/animated-gifs-computers-28.gif | Bin 0 -> 7566 bytes .../gif/error/animated-gifs-computers-32.gif | Bin 0 -> 2970 bytes .../gif/error/animated-gifs-computers-42.gif | Bin 0 -> 9391 bytes .../gif/error/animated-gifs-computers-60.gif | Bin 0 -> 35873 bytes .../gif/error/animated-gifs-computers-64.gif | Bin 0 -> 20728 bytes .../gif/error/animated-gifs-computers-65.gif | Bin 0 -> 26358 bytes .../gif/error/animated-gifs-computers-72.gif | Bin 0 -> 16361 bytes .../gif/error/animated-gifs-computers-74.gif | Bin 0 -> 21087 bytes .../gif/error/animated-gifs-computers-75.gif | Bin 0 -> 98317 bytes .../error/animated-gifs-construction-sites-038.gif | Bin 0 -> 949 bytes .../static/gif/error/animated-gifs-dogs-04.gif | Bin 0 -> 9450 bytes .../static/gif/error/animated-gifs-dogs-14.gif | Bin 0 -> 2532 bytes .../static/gif/error/animated-gifs-dogs-18.gif | Bin 0 -> 3596 bytes .../static/gif/error/animated-gifs-dogs-47.gif | Bin 0 -> 13920 bytes .../static/gif/error/animated-gifs-dogs-50.gif | Bin 0 -> 7297 bytes .../gif/error/animated-gifs-lava-lamps-01.gif | Bin 0 -> 27122 bytes .../static/gif/error/animated-gifs-mice-02.gif | Bin 0 -> 73345 bytes .../static/gif/error/animated-gifs-mice-09.gif | Bin 0 -> 27822 bytes .../static/gif/error/animated-gifs-mice-24.gif | Bin 0 -> 10566 bytes .../static/gif/error/animated-gifs-smileys-063.gif | Bin 0 -> 2230 bytes .../static/gif/error/animated-gifs-smileys-068.gif | Bin 0 -> 11751 bytes .../static/gif/error/animated-gifs-smileys-134.gif | Bin 0 -> 21431 bytes .../static/gif/error/animated-gifs-smileys-211.gif | Bin 0 -> 11209 bytes .../static/gif/error/animated-gifs-smileys-234.gif | Bin 0 -> 8677 bytes .../gif/error/animated-gifs-stickmen-001.gif | Bin 0 -> 6897 bytes .../gif/error/animated-gifs-stickmen-002.gif | Bin 0 -> 14098 bytes .../gif/error/animated-gifs-stickmen-005.gif | Bin 0 -> 13264 bytes .../gif/error/animated-gifs-stickmen-012.gif | Bin 0 -> 12141 bytes .../gif/error/animated-gifs-stickmen-056.gif | Bin 0 -> 5946 bytes .../gif/error/animated-gifs-stickmen-059.gif | Bin 0 -> 16427 bytes .../gif/error/animated-gifs-stickmen-060.gif | Bin 0 -> 14434 bytes .../gif/error/animated-gifs-stickmen-069.gif | Bin 0 -> 7668 bytes gn2/wqflask/static/gif/error/m001.gif | Bin 0 -> 273531 bytes gn2/wqflask/static/gif/error/m002.gif | Bin 0 -> 1799777 bytes gn2/wqflask/static/gif/error/m003.gif | Bin 0 -> 2238947 bytes gn2/wqflask/static/gif/error/m004.gif | Bin 0 -> 2090274 bytes gn2/wqflask/static/gif/error/m005.gif | Bin 0 -> 947565 bytes gn2/wqflask/static/gif/error/m006.gif | Bin 0 -> 121116 bytes gn2/wqflask/static/gif/error/m007.gif | Bin 0 -> 41670 bytes gn2/wqflask/static/gif/error/m008.gif | Bin 0 -> 732023 bytes gn2/wqflask/static/gif/error/mouse-wheel.gif | Bin 0 -> 2001764 bytes gn2/wqflask/static/gif/waitAnima2.gif | Bin 0 -> 54013 bytes gn2/wqflask/static/images/Belknap_Fig1_1998.png | Bin 0 -> 117246 bytes gn2/wqflask/static/images/Chrna1vsMyf6.gif | Bin 0 -> 59529 bytes gn2/wqflask/static/images/Congenic.png | Bin 0 -> 56578 bytes gn2/wqflask/static/images/Normal_Plot.gif | Bin 0 -> 47289 bytes gn2/wqflask/static/images/SilverFig3_2.png | Bin 0 -> 61570 bytes gn2/wqflask/static/images/SilverFig3_6.png | Bin 0 -> 22017 bytes gn2/wqflask/static/images/Winsorize1.png | Bin 0 -> 15117 bytes gn2/wqflask/static/images/Winsorize3.png | Bin 0 -> 17317 bytes gn2/wqflask/static/images/edit.png | Bin 0 -> 2452 bytes gn2/wqflask/static/new/css/autocomplete.css | 85 + gn2/wqflask/static/new/css/bar_chart.css | 23 + gn2/wqflask/static/new/css/bootstrap-custom.css | 7560 ++++++++++++++++++++ gn2/wqflask/static/new/css/box_plot.css | 20 + gn2/wqflask/static/new/css/broken_links.css | 5 + gn2/wqflask/static/new/css/colorbox.css | 238 + gn2/wqflask/static/new/css/corr_matrix.css | 30 + gn2/wqflask/static/new/css/corr_scatter_plot.css | 41 + gn2/wqflask/static/new/css/corr_scatter_plot2.css | 41 + gn2/wqflask/static/new/css/d3-tip.min.css | 1 + gn2/wqflask/static/new/css/d3panels.min.css | 1 + gn2/wqflask/static/new/css/docs.css | 1080 +++ gn2/wqflask/static/new/css/index_page.css | 4 + gn2/wqflask/static/new/css/jupyter_notebooks.css | 16 + gn2/wqflask/static/new/css/main.css | 40 + gn2/wqflask/static/new/css/markdown.css | 108 + gn2/wqflask/static/new/css/marker_regression.css | 76 + gn2/wqflask/static/new/css/mytooltip.css | 16 + gn2/wqflask/static/new/css/network_graph.css | 37 + gn2/wqflask/static/new/css/non-responsive.css | 114 + gn2/wqflask/static/new/css/pair_scan.css | 6 + gn2/wqflask/static/new/css/panelutil.css | 91 + gn2/wqflask/static/new/css/parsley.css | 20 + .../static/new/css/partial_correlations.css | 109 + gn2/wqflask/static/new/css/prob_plot.css | 3 + gn2/wqflask/static/new/css/scatter-matrix.css | 40 + gn2/wqflask/static/new/css/show_trait.css | 311 + gn2/wqflask/static/new/css/snp_browser.css | 58 + gn2/wqflask/static/new/css/trait_list.css | 53 + gn2/wqflask/static/new/css/typeahead-bootstrap.css | 94 + gn2/wqflask/static/new/images/CITGLogo.png | Bin 0 -> 11962 bytes gn2/wqflask/static/new/images/Nif.png | Bin 0 -> 4444 bytes gn2/wqflask/static/new/images/PythonLogo.png | Bin 0 -> 7685 bytes gn2/wqflask/static/new/images/a1.gif | Bin 0 -> 430 bytes gn2/wqflask/static/new/images/arrowdown.gif | Bin 0 -> 844 bytes gn2/wqflask/static/new/images/edit.gif | Bin 0 -> 157 bytes gn2/wqflask/static/new/images/ipad_icon3.png | Bin 0 -> 6728 bytes gn2/wqflask/static/new/images/question_mark.jpg | Bin 0 -> 47158 bytes gn2/wqflask/static/new/images/step1.gif | Bin 0 -> 758 bytes gn2/wqflask/static/new/images/step2.gif | Bin 0 -> 826 bytes gn2/wqflask/static/new/images/step3.gif | Bin 0 -> 818 bytes gn2/wqflask/static/new/javascript/auth/search.js | 172 + .../static/new/javascript/auth/search_genotypes.js | 95 + .../static/new/javascript/auth/search_mrna.js | 97 + .../new/javascript/auth/search_phenotypes.js | 188 + .../static/new/javascript/auto_hide_column.js | 21 + gn2/wqflask/static/new/javascript/bar_chart.js | 498 ++ gn2/wqflask/static/new/javascript/box.js | 307 + gn2/wqflask/static/new/javascript/box_plot.js | 80 + gn2/wqflask/static/new/javascript/chr_lod_chart.js | 297 + .../static/new/javascript/chr_manhattan_plot.js | 273 + gn2/wqflask/static/new/javascript/colorbrewer.js | 302 + .../new/javascript/compare_traits_scatterplot.js | 121 + .../static/new/javascript/comparison_bar_chart.js | 25 + gn2/wqflask/static/new/javascript/corr_matrix.js | 159 + .../static/new/javascript/corr_scatter_plot.js | 73 + .../static/new/javascript/create_corr_matrix.js | 94 + .../static/new/javascript/create_datatable.js | 117 + .../static/new/javascript/create_heatmap.js | 14 + .../static/new/javascript/create_lodchart.js | 50 + .../static/new/javascript/create_manhattan_plot.js | 68 + gn2/wqflask/static/new/javascript/ctl_graph.js | 193 + gn2/wqflask/static/new/javascript/curvechart.js | 353 + gn2/wqflask/static/new/javascript/d3panels.min.js | 1 + .../new/javascript/dataset_select_menu_orig.js | 330 + .../static/new/javascript/draw_corr_scatterplot.js | 913 +++ .../static/new/javascript/draw_probability_plot.js | 136 + .../javascript/get_covariates_from_collection.js | 248 + .../new/javascript/get_traits_from_collection.js | 414 ++ gn2/wqflask/static/new/javascript/group_manager.js | 37 + gn2/wqflask/static/new/javascript/histogram.js | 134 + .../static/new/javascript/init_genome_browser.js | 82 + .../new/javascript/initialize_show_trait_tables.js | 246 + .../static/new/javascript/iplotMScanone_noeff.js | 114 + gn2/wqflask/static/new/javascript/loadings_plot.js | 109 + gn2/wqflask/static/new/javascript/lod_chart.js | 473 ++ gn2/wqflask/static/new/javascript/lodheatmap.js | 277 + gn2/wqflask/static/new/javascript/login.js | 41 + gn2/wqflask/static/new/javascript/network_graph.js | 259 + gn2/wqflask/static/new/javascript/panelutil.js | 462 ++ .../static/new/javascript/partial_correlations.js | 26 + .../static/new/javascript/password_strength.js | 57 + .../new/javascript/plotly_probability_plot.js | 308 + .../static/new/javascript/scatter-matrix.js | 551 ++ gn2/wqflask/static/new/javascript/scatterplot.js | 414 ++ .../static/new/javascript/search_autocomplete.js | 173 + .../static/new/javascript/search_results.js | 386 + gn2/wqflask/static/new/javascript/show_trait.js | 1677 +++++ .../new/javascript/show_trait_mapping_tools.js | 329 + gn2/wqflask/static/new/javascript/stats.js | 176 + .../static/new/javascript/table_functions.js | 90 + gn2/wqflask/static/new/javascript/thank_you.js | 6 + .../static/new/javascript/typeahead_rn6.json | 1 + gn2/wqflask/static/new/javascript/validation.js | 42 + .../templates/admin/change_resource_owner.html | 114 + gn2/wqflask/templates/admin/create_group.html | 84 + gn2/wqflask/templates/admin/group_manager.html | 147 + gn2/wqflask/templates/admin/ind_user_manager.html | 111 + gn2/wqflask/templates/admin/manage_resource.html | 124 + gn2/wqflask/templates/admin/manage_user.html | 79 + gn2/wqflask/templates/admin/search_for_groups.html | 134 + .../templates/admin/set_group_privileges.html | 102 + gn2/wqflask/templates/admin/user_manager.html | 41 + gn2/wqflask/templates/admin/view_group.html | 270 + gn2/wqflask/templates/authorisation_error.html | 19 + gn2/wqflask/templates/base.html | 397 + gn2/wqflask/templates/base_macro.html | 28 + gn2/wqflask/templates/blogs.html | 12 + gn2/wqflask/templates/blogs_list.html | 52 + gn2/wqflask/templates/bnw_page.html | 7 + gn2/wqflask/templates/case_attributes.html | 415 ++ gn2/wqflask/templates/collections/add.html | 86 + .../templates/collections/add_anonymous.html | 21 + gn2/wqflask/templates/collections/list.html | 189 + .../templates/collections/not_logged_in.html | 23 + gn2/wqflask/templates/collections/remove.html | 48 + gn2/wqflask/templates/collections/view.html | 483 ++ .../templates/collections/view_anonymous.html | 143 + gn2/wqflask/templates/comparison_bar_chart.html | 38 + gn2/wqflask/templates/corr_scatterplot.html | 364 + gn2/wqflask/templates/correlation_error_page.html | 23 + gn2/wqflask/templates/correlation_matrix.html | 203 + gn2/wqflask/templates/correlation_page.html | 550 ++ gn2/wqflask/templates/credits.html | 58 + gn2/wqflask/templates/ctl_results.html | 77 + gn2/wqflask/templates/ctl_setup.html | 70 + gn2/wqflask/templates/data_sharing.html | 262 + gn2/wqflask/templates/dataset.html | 107 + gn2/wqflask/templates/display_diffs.html | 95 + gn2/wqflask/templates/display_files.html | 131 + gn2/wqflask/templates/docedit.html | 31 + gn2/wqflask/templates/docs.html | 17 + gn2/wqflask/templates/edit_case_attributes.html | 104 + gn2/wqflask/templates/edit_history.html | 56 + gn2/wqflask/templates/edit_phenotype.html | 279 + gn2/wqflask/templates/edit_probeset.html | 282 + gn2/wqflask/templates/email/forgot_password.txt | 5 + gn2/wqflask/templates/empty_collection.html | 15 + gn2/wqflask/templates/environment.html | 160 + gn2/wqflask/templates/error.html | 61 + gn2/wqflask/templates/facilities.html | 24 + gn2/wqflask/templates/generif.html | 101 + gn2/wqflask/templates/geneweaver_page.html | 35 + gn2/wqflask/templates/genotype.html | 87 + gn2/wqflask/templates/glossary.html | 23 + gn2/wqflask/templates/gn3_ctl_results.html | 101 + gn2/wqflask/templates/gn3_wgcna_results.html | 192 + gn2/wqflask/templates/gnqa.html | 68 + gn2/wqflask/templates/gnqa_answer.html | 158 + gn2/wqflask/templates/gsearch_gene.html | 267 + gn2/wqflask/templates/gsearch_pheno.html | 238 + gn2/wqflask/templates/heatmap.html | 44 + gn2/wqflask/templates/index_page.html | 397 + gn2/wqflask/templates/info_page.html | 92 + gn2/wqflask/templates/jobs/debug.html | 42 + gn2/wqflask/templates/jobs/no-such-job.html | 13 + gn2/wqflask/templates/jupyter_notebooks.html | 28 + gn2/wqflask/templates/links.html | 24 + .../templates/list_case_attribute_diffs.html | 59 + .../templates/list_case_attribute_diffs_error.html | 37 + gn2/wqflask/templates/loading.html | 133 + gn2/wqflask/templates/loading_corrs.html | 28 + gn2/wqflask/templates/mapping_error.html | 36 + gn2/wqflask/templates/mapping_results.html | 698 ++ gn2/wqflask/templates/marker_regression.html | 119 + gn2/wqflask/templates/metadata/dataset.html | 157 + gn2/wqflask/templates/network_graph.html | 152 + .../templates/new_security/forgot_password.html | 52 + .../new_security/forgot_password_step2.html | 25 + gn2/wqflask/templates/new_security/login_user.html | 119 + .../templates/new_security/not_authenticated.html | 11 + .../templates/new_security/password_reset.html | 78 + .../templates/new_security/register_user.html | 105 + gn2/wqflask/templates/new_security/registered.html | 24 + gn2/wqflask/templates/new_security/thank_you.html | 23 + .../new_security/verification_still_needed.html | 26 + gn2/wqflask/templates/news.html | 24 + gn2/wqflask/templates/oauth2/create-resource.html | 89 + gn2/wqflask/templates/oauth2/create-role.html | 46 + .../templates/oauth2/data-list-genotype.html | 166 + gn2/wqflask/templates/oauth2/data-list-mrna.html | 168 + .../templates/oauth2/data-list-phenotype.html | 209 + gn2/wqflask/templates/oauth2/data-list.html | 53 + gn2/wqflask/templates/oauth2/display_error.html | 10 + gn2/wqflask/templates/oauth2/group.html | 114 + .../templates/oauth2/group_join_or_create.html | 99 + gn2/wqflask/templates/oauth2/join-requests.html | 73 + gn2/wqflask/templates/oauth2/list_roles.html | 80 + gn2/wqflask/templates/oauth2/login.html | 47 + gn2/wqflask/templates/oauth2/masquerade.html | 39 + gn2/wqflask/templates/oauth2/profile_nav.html | 64 + gn2/wqflask/templates/oauth2/register_user.html | 62 + gn2/wqflask/templates/oauth2/request_error.html | 32 + gn2/wqflask/templates/oauth2/resources.html | 58 + gn2/wqflask/templates/oauth2/role.html | 56 + gn2/wqflask/templates/oauth2/view-group-role.html | 102 + gn2/wqflask/templates/oauth2/view-resource.html | 352 + gn2/wqflask/templates/oauth2/view-user.html | 48 + gn2/wqflask/templates/pair_scan_results.html | 114 + .../partial_correlations/pcorrs_error.html | 65 + .../partial_correlations/pcorrs_poll_results.html | 19 + .../pcorrs_results_presentation.html | 261 + .../pcorrs_results_with_target_traits.html | 115 + .../pcorrs_select_operations.html | 167 + gn2/wqflask/templates/pca_scree_plot.html | 85 + gn2/wqflask/templates/phenotype.html | 136 + gn2/wqflask/templates/policies.html | 23 + gn2/wqflask/templates/publication.html | 62 + gn2/wqflask/templates/references.html | 19 + gn2/wqflask/templates/search_autocomplete.html | 249 + gn2/wqflask/templates/search_error.html | 20 + gn2/wqflask/templates/search_history.html | 297 + gn2/wqflask/templates/search_result_page.html | 453 ++ gn2/wqflask/templates/set_group_privileges.html | 77 + gn2/wqflask/templates/show_image.html | 5 + gn2/wqflask/templates/show_trait.html | 276 + .../show_trait_calculate_correlations.html | 165 + gn2/wqflask/templates/show_trait_details.html | 255 + gn2/wqflask/templates/show_trait_edit_data.html | 75 + gn2/wqflask/templates/show_trait_error.html | 20 + .../templates/show_trait_mapping_tools.html | 436 ++ gn2/wqflask/templates/show_trait_progress_bar.html | 35 + gn2/wqflask/templates/show_trait_statistics.html | 106 + .../templates/show_trait_transform_and_filter.html | 140 + gn2/wqflask/templates/snp_browser.html | 582 ++ gn2/wqflask/templates/startup_errors.html | 20 + gn2/wqflask/templates/submit_trait.html | 111 + gn2/wqflask/templates/test_correlation_page.html | 159 + gn2/wqflask/templates/tool_buttons.html | 38 + gn2/wqflask/templates/tutorials.html | 256 + .../templates/view_case_attribute_diff.html | 117 + .../templates/view_case_attribute_diff_error.html | 35 + gn2/wqflask/templates/webgestalt_page.html | 35 + gn2/wqflask/templates/wgcna_results.html | 76 + gn2/wqflask/templates/wgcna_setup.html | 142 + gn2/wqflask/templates/with-trait-items.html | 18 + gn2/wqflask/update_search_results.py | 102 + gn2/wqflask/user_login.py | 519 ++ gn2/wqflask/user_session.py | 330 + gn2/wqflask/views.py | 1328 ++++ gn2/wqflask/wgcna/__init__.py | 0 gn2/wqflask/wgcna/gn3_wgcna.py | 118 + gn2/wqflask/wgcna/wgcna_analysis.py | 189 + 395 files changed, 60342 insertions(+) create mode 100644 gn2/wqflask/.DS_Store create mode 100644 gn2/wqflask/__init__.py create mode 100644 gn2/wqflask/api/__init__.py create mode 100644 gn2/wqflask/api/correlation.py create mode 100644 gn2/wqflask/api/gen_menu.py create mode 100644 gn2/wqflask/api/jobs.py create mode 100644 gn2/wqflask/api/mapping.py create mode 100644 gn2/wqflask/api/markdown.py create mode 100644 gn2/wqflask/api/router.py create mode 100644 gn2/wqflask/app_errors.py create mode 100644 gn2/wqflask/collect.py create mode 100644 gn2/wqflask/comparison_bar_chart/__init__.py create mode 100644 gn2/wqflask/comparison_bar_chart/comparison_bar_chart.py create mode 100644 gn2/wqflask/correlation/__init__.py create mode 100644 gn2/wqflask/correlation/corr_scatter_plot.py create mode 100644 gn2/wqflask/correlation/correlation_functions.py create mode 100644 gn2/wqflask/correlation/correlation_gn3_api.py create mode 100644 gn2/wqflask/correlation/exceptions.py create mode 100644 gn2/wqflask/correlation/pre_computes.py create mode 100644 gn2/wqflask/correlation/rust_correlation.py create mode 100644 gn2/wqflask/correlation/show_corr_results.py create mode 100644 gn2/wqflask/correlation_matrix/__init__.py create mode 100644 gn2/wqflask/correlation_matrix/show_corr_matrix.py create mode 100644 gn2/wqflask/ctl/__init__.py create mode 100644 gn2/wqflask/ctl/ctl_analysis.py create mode 100644 gn2/wqflask/ctl/gn3_ctl_analysis.py create mode 100644 gn2/wqflask/database.py create mode 100644 gn2/wqflask/db_info.py create mode 100644 gn2/wqflask/decorators.py create mode 100644 gn2/wqflask/do_search.py create mode 100644 gn2/wqflask/docs.py create mode 100644 gn2/wqflask/export_traits.py create mode 100644 gn2/wqflask/external_tools/__init__.py create mode 100644 gn2/wqflask/external_tools/send_to_bnw.py create mode 100644 gn2/wqflask/external_tools/send_to_geneweaver.py create mode 100644 gn2/wqflask/external_tools/send_to_webgestalt.py create mode 100644 gn2/wqflask/group_manager.py create mode 100644 gn2/wqflask/gsearch.py create mode 100644 gn2/wqflask/heatmap/__init__.py create mode 100644 gn2/wqflask/heatmap/heatmap.py create mode 100644 gn2/wqflask/interval_analyst/GeneUtil.py create mode 100644 gn2/wqflask/interval_analyst/__init__.py create mode 100644 gn2/wqflask/jupyter_notebooks.py create mode 100644 gn2/wqflask/marker_regression/__init__.py create mode 100644 gn2/wqflask/marker_regression/display_mapping_results.py create mode 100644 gn2/wqflask/marker_regression/exceptions.py create mode 100644 gn2/wqflask/marker_regression/gemma_mapping.py create mode 100644 gn2/wqflask/marker_regression/plink_mapping.py create mode 100644 gn2/wqflask/marker_regression/qtlreaper_mapping.py create mode 100644 gn2/wqflask/marker_regression/rqtl_mapping.py create mode 100644 gn2/wqflask/marker_regression/run_mapping.py create mode 100644 gn2/wqflask/metadata_edits.py create mode 100644 gn2/wqflask/network_graph/__init__.py create mode 100644 gn2/wqflask/network_graph/network_graph.py create mode 100644 gn2/wqflask/oauth2/__init__.py create mode 100644 gn2/wqflask/oauth2/checks.py create mode 100644 gn2/wqflask/oauth2/client.py create mode 100644 gn2/wqflask/oauth2/collections.py create mode 100644 gn2/wqflask/oauth2/data.py create mode 100644 gn2/wqflask/oauth2/groups.py create mode 100644 gn2/wqflask/oauth2/request_utils.py create mode 100644 gn2/wqflask/oauth2/resources.py create mode 100644 gn2/wqflask/oauth2/roles.py create mode 100644 gn2/wqflask/oauth2/routes.py create mode 100644 gn2/wqflask/oauth2/session.py create mode 100644 gn2/wqflask/oauth2/toplevel.py create mode 100644 gn2/wqflask/oauth2/ui.py create mode 100644 gn2/wqflask/oauth2/users.py create mode 100644 gn2/wqflask/parser.py create mode 100644 gn2/wqflask/partial_correlations_views.py create mode 100644 gn2/wqflask/pbkdf2.py create mode 100644 gn2/wqflask/requests.py create mode 100644 gn2/wqflask/resource_manager.py create mode 100644 gn2/wqflask/search_results.py create mode 100644 gn2/wqflask/send_mail.py create mode 100644 gn2/wqflask/server_side.py create mode 100644 gn2/wqflask/show_trait/SampleList.py create mode 100644 gn2/wqflask/show_trait/__init__.py create mode 100644 gn2/wqflask/show_trait/export_trait_data.py create mode 100644 gn2/wqflask/show_trait/show_trait.py create mode 100644 gn2/wqflask/snp_browser/__init__.py create mode 100644 gn2/wqflask/snp_browser/snp_browser.py create mode 100644 gn2/wqflask/startup.py create mode 100644 gn2/wqflask/static/Congenic.png create mode 100644 gn2/wqflask/static/fonts/README create mode 100644 gn2/wqflask/static/fonts/arial.ttf create mode 100644 gn2/wqflask/static/fonts/courbd.ttf create mode 100644 gn2/wqflask/static/fonts/fnt_bs.ttf create mode 100644 gn2/wqflask/static/fonts/tahoma.ttf create mode 100644 gn2/wqflask/static/fonts/trebucbd.ttf create mode 100644 gn2/wqflask/static/fonts/verdana.ttf create mode 100644 gn2/wqflask/static/fonts/verdanab.ttf create mode 100644 gn2/wqflask/static/gif/89.gif create mode 100644 gn2/wqflask/static/gif/error/Wild-Type-Mouse.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-aliens-29.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-angels-04.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-cats-016.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-cats-031.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-cell-phones-03.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-cell-phones-16.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-computers-13.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-computers-28.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-computers-32.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-computers-42.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-computers-60.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-computers-64.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-computers-65.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-computers-72.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-computers-74.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-computers-75.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-construction-sites-038.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-dogs-04.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-dogs-14.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-dogs-18.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-dogs-47.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-dogs-50.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-lava-lamps-01.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-mice-02.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-mice-09.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-mice-24.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-smileys-063.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-smileys-068.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-smileys-134.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-smileys-211.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-smileys-234.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-stickmen-001.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-stickmen-002.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-stickmen-005.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-stickmen-012.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-stickmen-056.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-stickmen-059.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-stickmen-060.gif create mode 100644 gn2/wqflask/static/gif/error/animated-gifs-stickmen-069.gif create mode 100644 gn2/wqflask/static/gif/error/m001.gif create mode 100644 gn2/wqflask/static/gif/error/m002.gif create mode 100644 gn2/wqflask/static/gif/error/m003.gif create mode 100644 gn2/wqflask/static/gif/error/m004.gif create mode 100644 gn2/wqflask/static/gif/error/m005.gif create mode 100644 gn2/wqflask/static/gif/error/m006.gif create mode 100644 gn2/wqflask/static/gif/error/m007.gif create mode 100644 gn2/wqflask/static/gif/error/m008.gif create mode 100644 gn2/wqflask/static/gif/error/mouse-wheel.gif create mode 100644 gn2/wqflask/static/gif/waitAnima2.gif create mode 100644 gn2/wqflask/static/images/Belknap_Fig1_1998.png create mode 100644 gn2/wqflask/static/images/Chrna1vsMyf6.gif create mode 100644 gn2/wqflask/static/images/Congenic.png create mode 100644 gn2/wqflask/static/images/Normal_Plot.gif create mode 100644 gn2/wqflask/static/images/SilverFig3_2.png create mode 100644 gn2/wqflask/static/images/SilverFig3_6.png create mode 100644 gn2/wqflask/static/images/Winsorize1.png create mode 100644 gn2/wqflask/static/images/Winsorize3.png create mode 100644 gn2/wqflask/static/images/edit.png create mode 100644 gn2/wqflask/static/new/css/autocomplete.css create mode 100644 gn2/wqflask/static/new/css/bar_chart.css create mode 100644 gn2/wqflask/static/new/css/bootstrap-custom.css create mode 100644 gn2/wqflask/static/new/css/box_plot.css create mode 100644 gn2/wqflask/static/new/css/broken_links.css create mode 100644 gn2/wqflask/static/new/css/colorbox.css create mode 100644 gn2/wqflask/static/new/css/corr_matrix.css create mode 100644 gn2/wqflask/static/new/css/corr_scatter_plot.css create mode 100644 gn2/wqflask/static/new/css/corr_scatter_plot2.css create mode 100644 gn2/wqflask/static/new/css/d3-tip.min.css create mode 100644 gn2/wqflask/static/new/css/d3panels.min.css create mode 100644 gn2/wqflask/static/new/css/docs.css create mode 100644 gn2/wqflask/static/new/css/index_page.css create mode 100644 gn2/wqflask/static/new/css/jupyter_notebooks.css create mode 100644 gn2/wqflask/static/new/css/main.css create mode 100644 gn2/wqflask/static/new/css/markdown.css create mode 100644 gn2/wqflask/static/new/css/marker_regression.css create mode 100644 gn2/wqflask/static/new/css/mytooltip.css create mode 100644 gn2/wqflask/static/new/css/network_graph.css create mode 100644 gn2/wqflask/static/new/css/non-responsive.css create mode 100644 gn2/wqflask/static/new/css/pair_scan.css create mode 100644 gn2/wqflask/static/new/css/panelutil.css create mode 100644 gn2/wqflask/static/new/css/parsley.css create mode 100644 gn2/wqflask/static/new/css/partial_correlations.css create mode 100644 gn2/wqflask/static/new/css/prob_plot.css create mode 100644 gn2/wqflask/static/new/css/scatter-matrix.css create mode 100644 gn2/wqflask/static/new/css/show_trait.css create mode 100644 gn2/wqflask/static/new/css/snp_browser.css create mode 100644 gn2/wqflask/static/new/css/trait_list.css create mode 100644 gn2/wqflask/static/new/css/typeahead-bootstrap.css create mode 100644 gn2/wqflask/static/new/images/CITGLogo.png create mode 100644 gn2/wqflask/static/new/images/Nif.png create mode 100644 gn2/wqflask/static/new/images/PythonLogo.png create mode 100644 gn2/wqflask/static/new/images/a1.gif create mode 100644 gn2/wqflask/static/new/images/arrowdown.gif create mode 100644 gn2/wqflask/static/new/images/edit.gif create mode 100644 gn2/wqflask/static/new/images/ipad_icon3.png create mode 100644 gn2/wqflask/static/new/images/question_mark.jpg create mode 100644 gn2/wqflask/static/new/images/step1.gif create mode 100644 gn2/wqflask/static/new/images/step2.gif create mode 100644 gn2/wqflask/static/new/images/step3.gif create mode 100644 gn2/wqflask/static/new/javascript/auth/search.js create mode 100644 gn2/wqflask/static/new/javascript/auth/search_genotypes.js create mode 100644 gn2/wqflask/static/new/javascript/auth/search_mrna.js create mode 100644 gn2/wqflask/static/new/javascript/auth/search_phenotypes.js create mode 100644 gn2/wqflask/static/new/javascript/auto_hide_column.js create mode 100644 gn2/wqflask/static/new/javascript/bar_chart.js create mode 100644 gn2/wqflask/static/new/javascript/box.js create mode 100644 gn2/wqflask/static/new/javascript/box_plot.js create mode 100644 gn2/wqflask/static/new/javascript/chr_lod_chart.js create mode 100644 gn2/wqflask/static/new/javascript/chr_manhattan_plot.js create mode 100644 gn2/wqflask/static/new/javascript/colorbrewer.js create mode 100644 gn2/wqflask/static/new/javascript/compare_traits_scatterplot.js create mode 100644 gn2/wqflask/static/new/javascript/comparison_bar_chart.js create mode 100644 gn2/wqflask/static/new/javascript/corr_matrix.js create mode 100644 gn2/wqflask/static/new/javascript/corr_scatter_plot.js create mode 100644 gn2/wqflask/static/new/javascript/create_corr_matrix.js create mode 100644 gn2/wqflask/static/new/javascript/create_datatable.js create mode 100644 gn2/wqflask/static/new/javascript/create_heatmap.js create mode 100644 gn2/wqflask/static/new/javascript/create_lodchart.js create mode 100644 gn2/wqflask/static/new/javascript/create_manhattan_plot.js create mode 100644 gn2/wqflask/static/new/javascript/ctl_graph.js create mode 100644 gn2/wqflask/static/new/javascript/curvechart.js create mode 100644 gn2/wqflask/static/new/javascript/d3panels.min.js create mode 100644 gn2/wqflask/static/new/javascript/dataset_select_menu_orig.js create mode 100644 gn2/wqflask/static/new/javascript/draw_corr_scatterplot.js create mode 100644 gn2/wqflask/static/new/javascript/draw_probability_plot.js create mode 100644 gn2/wqflask/static/new/javascript/get_covariates_from_collection.js create mode 100644 gn2/wqflask/static/new/javascript/get_traits_from_collection.js create mode 100644 gn2/wqflask/static/new/javascript/group_manager.js create mode 100644 gn2/wqflask/static/new/javascript/histogram.js create mode 100644 gn2/wqflask/static/new/javascript/init_genome_browser.js create mode 100644 gn2/wqflask/static/new/javascript/initialize_show_trait_tables.js create mode 100644 gn2/wqflask/static/new/javascript/iplotMScanone_noeff.js create mode 100644 gn2/wqflask/static/new/javascript/loadings_plot.js create mode 100644 gn2/wqflask/static/new/javascript/lod_chart.js create mode 100644 gn2/wqflask/static/new/javascript/lodheatmap.js create mode 100644 gn2/wqflask/static/new/javascript/login.js create mode 100644 gn2/wqflask/static/new/javascript/network_graph.js create mode 100644 gn2/wqflask/static/new/javascript/panelutil.js create mode 100644 gn2/wqflask/static/new/javascript/partial_correlations.js create mode 100644 gn2/wqflask/static/new/javascript/password_strength.js create mode 100644 gn2/wqflask/static/new/javascript/plotly_probability_plot.js create mode 100644 gn2/wqflask/static/new/javascript/scatter-matrix.js create mode 100644 gn2/wqflask/static/new/javascript/scatterplot.js create mode 100644 gn2/wqflask/static/new/javascript/search_autocomplete.js create mode 100644 gn2/wqflask/static/new/javascript/search_results.js create mode 100644 gn2/wqflask/static/new/javascript/show_trait.js create mode 100644 gn2/wqflask/static/new/javascript/show_trait_mapping_tools.js create mode 100644 gn2/wqflask/static/new/javascript/stats.js create mode 100644 gn2/wqflask/static/new/javascript/table_functions.js create mode 100644 gn2/wqflask/static/new/javascript/thank_you.js create mode 100644 gn2/wqflask/static/new/javascript/typeahead_rn6.json create mode 100644 gn2/wqflask/static/new/javascript/validation.js create mode 100644 gn2/wqflask/templates/admin/change_resource_owner.html create mode 100644 gn2/wqflask/templates/admin/create_group.html create mode 100644 gn2/wqflask/templates/admin/group_manager.html create mode 100644 gn2/wqflask/templates/admin/ind_user_manager.html create mode 100644 gn2/wqflask/templates/admin/manage_resource.html create mode 100644 gn2/wqflask/templates/admin/manage_user.html create mode 100644 gn2/wqflask/templates/admin/search_for_groups.html create mode 100644 gn2/wqflask/templates/admin/set_group_privileges.html create mode 100644 gn2/wqflask/templates/admin/user_manager.html create mode 100644 gn2/wqflask/templates/admin/view_group.html create mode 100644 gn2/wqflask/templates/authorisation_error.html create mode 100644 gn2/wqflask/templates/base.html create mode 100644 gn2/wqflask/templates/base_macro.html create mode 100644 gn2/wqflask/templates/blogs.html create mode 100644 gn2/wqflask/templates/blogs_list.html create mode 100644 gn2/wqflask/templates/bnw_page.html create mode 100644 gn2/wqflask/templates/case_attributes.html create mode 100644 gn2/wqflask/templates/collections/add.html create mode 100644 gn2/wqflask/templates/collections/add_anonymous.html create mode 100644 gn2/wqflask/templates/collections/list.html create mode 100644 gn2/wqflask/templates/collections/not_logged_in.html create mode 100644 gn2/wqflask/templates/collections/remove.html create mode 100644 gn2/wqflask/templates/collections/view.html create mode 100644 gn2/wqflask/templates/collections/view_anonymous.html create mode 100644 gn2/wqflask/templates/comparison_bar_chart.html create mode 100644 gn2/wqflask/templates/corr_scatterplot.html create mode 100644 gn2/wqflask/templates/correlation_error_page.html create mode 100644 gn2/wqflask/templates/correlation_matrix.html create mode 100644 gn2/wqflask/templates/correlation_page.html create mode 100644 gn2/wqflask/templates/credits.html create mode 100644 gn2/wqflask/templates/ctl_results.html create mode 100644 gn2/wqflask/templates/ctl_setup.html create mode 100644 gn2/wqflask/templates/data_sharing.html create mode 100644 gn2/wqflask/templates/dataset.html create mode 100644 gn2/wqflask/templates/display_diffs.html create mode 100644 gn2/wqflask/templates/display_files.html create mode 100644 gn2/wqflask/templates/docedit.html create mode 100644 gn2/wqflask/templates/docs.html create mode 100644 gn2/wqflask/templates/edit_case_attributes.html create mode 100644 gn2/wqflask/templates/edit_history.html create mode 100644 gn2/wqflask/templates/edit_phenotype.html create mode 100644 gn2/wqflask/templates/edit_probeset.html create mode 100644 gn2/wqflask/templates/email/forgot_password.txt create mode 100644 gn2/wqflask/templates/empty_collection.html create mode 100644 gn2/wqflask/templates/environment.html create mode 100644 gn2/wqflask/templates/error.html create mode 100644 gn2/wqflask/templates/facilities.html create mode 100644 gn2/wqflask/templates/generif.html create mode 100644 gn2/wqflask/templates/geneweaver_page.html create mode 100644 gn2/wqflask/templates/genotype.html create mode 100644 gn2/wqflask/templates/glossary.html create mode 100644 gn2/wqflask/templates/gn3_ctl_results.html create mode 100644 gn2/wqflask/templates/gn3_wgcna_results.html create mode 100644 gn2/wqflask/templates/gnqa.html create mode 100644 gn2/wqflask/templates/gnqa_answer.html create mode 100644 gn2/wqflask/templates/gsearch_gene.html create mode 100644 gn2/wqflask/templates/gsearch_pheno.html create mode 100644 gn2/wqflask/templates/heatmap.html create mode 100755 gn2/wqflask/templates/index_page.html create mode 100644 gn2/wqflask/templates/info_page.html create mode 100644 gn2/wqflask/templates/jobs/debug.html create mode 100644 gn2/wqflask/templates/jobs/no-such-job.html create mode 100644 gn2/wqflask/templates/jupyter_notebooks.html create mode 100644 gn2/wqflask/templates/links.html create mode 100644 gn2/wqflask/templates/list_case_attribute_diffs.html create mode 100644 gn2/wqflask/templates/list_case_attribute_diffs_error.html create mode 100644 gn2/wqflask/templates/loading.html create mode 100644 gn2/wqflask/templates/loading_corrs.html create mode 100644 gn2/wqflask/templates/mapping_error.html create mode 100644 gn2/wqflask/templates/mapping_results.html create mode 100644 gn2/wqflask/templates/marker_regression.html create mode 100644 gn2/wqflask/templates/metadata/dataset.html create mode 100644 gn2/wqflask/templates/network_graph.html create mode 100644 gn2/wqflask/templates/new_security/forgot_password.html create mode 100644 gn2/wqflask/templates/new_security/forgot_password_step2.html create mode 100644 gn2/wqflask/templates/new_security/login_user.html create mode 100644 gn2/wqflask/templates/new_security/not_authenticated.html create mode 100644 gn2/wqflask/templates/new_security/password_reset.html create mode 100644 gn2/wqflask/templates/new_security/register_user.html create mode 100644 gn2/wqflask/templates/new_security/registered.html create mode 100644 gn2/wqflask/templates/new_security/thank_you.html create mode 100644 gn2/wqflask/templates/new_security/verification_still_needed.html create mode 100644 gn2/wqflask/templates/news.html create mode 100644 gn2/wqflask/templates/oauth2/create-resource.html create mode 100644 gn2/wqflask/templates/oauth2/create-role.html create mode 100644 gn2/wqflask/templates/oauth2/data-list-genotype.html create mode 100644 gn2/wqflask/templates/oauth2/data-list-mrna.html create mode 100644 gn2/wqflask/templates/oauth2/data-list-phenotype.html create mode 100644 gn2/wqflask/templates/oauth2/data-list.html create mode 100644 gn2/wqflask/templates/oauth2/display_error.html create mode 100644 gn2/wqflask/templates/oauth2/group.html create mode 100644 gn2/wqflask/templates/oauth2/group_join_or_create.html create mode 100644 gn2/wqflask/templates/oauth2/join-requests.html create mode 100644 gn2/wqflask/templates/oauth2/list_roles.html create mode 100644 gn2/wqflask/templates/oauth2/login.html create mode 100644 gn2/wqflask/templates/oauth2/masquerade.html create mode 100644 gn2/wqflask/templates/oauth2/profile_nav.html create mode 100644 gn2/wqflask/templates/oauth2/register_user.html create mode 100644 gn2/wqflask/templates/oauth2/request_error.html create mode 100644 gn2/wqflask/templates/oauth2/resources.html create mode 100644 gn2/wqflask/templates/oauth2/role.html create mode 100644 gn2/wqflask/templates/oauth2/view-group-role.html create mode 100644 gn2/wqflask/templates/oauth2/view-resource.html create mode 100644 gn2/wqflask/templates/oauth2/view-user.html create mode 100644 gn2/wqflask/templates/pair_scan_results.html create mode 100644 gn2/wqflask/templates/partial_correlations/pcorrs_error.html create mode 100644 gn2/wqflask/templates/partial_correlations/pcorrs_poll_results.html create mode 100644 gn2/wqflask/templates/partial_correlations/pcorrs_results_presentation.html create mode 100644 gn2/wqflask/templates/partial_correlations/pcorrs_results_with_target_traits.html create mode 100644 gn2/wqflask/templates/partial_correlations/pcorrs_select_operations.html create mode 100644 gn2/wqflask/templates/pca_scree_plot.html create mode 100644 gn2/wqflask/templates/phenotype.html create mode 100644 gn2/wqflask/templates/policies.html create mode 100644 gn2/wqflask/templates/publication.html create mode 100644 gn2/wqflask/templates/references.html create mode 100644 gn2/wqflask/templates/search_autocomplete.html create mode 100644 gn2/wqflask/templates/search_error.html create mode 100644 gn2/wqflask/templates/search_history.html create mode 100644 gn2/wqflask/templates/search_result_page.html create mode 100644 gn2/wqflask/templates/set_group_privileges.html create mode 100644 gn2/wqflask/templates/show_image.html create mode 100644 gn2/wqflask/templates/show_trait.html create mode 100644 gn2/wqflask/templates/show_trait_calculate_correlations.html create mode 100644 gn2/wqflask/templates/show_trait_details.html create mode 100644 gn2/wqflask/templates/show_trait_edit_data.html create mode 100644 gn2/wqflask/templates/show_trait_error.html create mode 100755 gn2/wqflask/templates/show_trait_mapping_tools.html create mode 100644 gn2/wqflask/templates/show_trait_progress_bar.html create mode 100644 gn2/wqflask/templates/show_trait_statistics.html create mode 100644 gn2/wqflask/templates/show_trait_transform_and_filter.html create mode 100644 gn2/wqflask/templates/snp_browser.html create mode 100644 gn2/wqflask/templates/startup_errors.html create mode 100644 gn2/wqflask/templates/submit_trait.html create mode 100644 gn2/wqflask/templates/test_correlation_page.html create mode 100644 gn2/wqflask/templates/tool_buttons.html create mode 100644 gn2/wqflask/templates/tutorials.html create mode 100644 gn2/wqflask/templates/view_case_attribute_diff.html create mode 100644 gn2/wqflask/templates/view_case_attribute_diff_error.html create mode 100644 gn2/wqflask/templates/webgestalt_page.html create mode 100644 gn2/wqflask/templates/wgcna_results.html create mode 100644 gn2/wqflask/templates/wgcna_setup.html create mode 100644 gn2/wqflask/templates/with-trait-items.html create mode 100644 gn2/wqflask/update_search_results.py create mode 100644 gn2/wqflask/user_login.py create mode 100644 gn2/wqflask/user_session.py create mode 100644 gn2/wqflask/views.py create mode 100644 gn2/wqflask/wgcna/__init__.py create mode 100644 gn2/wqflask/wgcna/gn3_wgcna.py create mode 100644 gn2/wqflask/wgcna/wgcna_analysis.py (limited to 'gn2/wqflask') diff --git a/gn2/wqflask/.DS_Store b/gn2/wqflask/.DS_Store new file mode 100644 index 00000000..a119e235 Binary files /dev/null and b/gn2/wqflask/.DS_Store differ 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 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/") +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("/") +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/".format(version)) +@app.route("/api/v_{}/species/.".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/".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/".format(version)) +@app.route("/api/v_{}/group/.".format(version)) +@app.route("/api/v_{}/group//".format(version)) +@app.route("/api/v_{}/group//.".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/".format(version)) +@app.route("/api/v_{}/datasets//".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/".format(version)) +@app.route("/api/v_{}/dataset/.".format(version)) +@app.route("/api/v_{}/dataset//".format(version)) +@app.route("/api/v_{}/dataset//.".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/".format(version), methods=("GET",)) +@app.route("/api/v_{}/traits/.".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/".format(version)) +@app.route("/api/v_{}/sample_data/.".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//".format(version)) +@app.route("/api/v_{}/sample_data//.".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//".format(version)) +@app.route("/api/v_{}/trait//.".format(version)) +@app.route("/api/v_{}/trait_info//".format(version)) +@app.route("/api/v_{}/trait_info//.".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/".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///.zip".format(version)) +@app.route("/api/v_{}/genotypes///".format(version)) +@app.route("/api/v_{}/genotypes//.zip".format(version)) +@app.route("/api/v_{}/genotypes//".format(version)) +@app.route("/api/v_{}/genotypes/.".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 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 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 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 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 +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".+?", data, re.DOTALL) + for i, match in enumerate(matches): + if i == 0: + continue + cells = re.findall(r".+?", match, re.DOTALL) + full_filename = 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 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/") +@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/", 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 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 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 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("//traits/") +@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/") +@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("//traits/", 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 here", "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/", 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 here", "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"") + 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//group//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//dataset//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/") +@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("//traits//history") +@metadata_edit.route("/probeset/") +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("/diffs//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("/diffs//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 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 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("///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/", methods=["GET", "POST"]) +@require_oauth2 +def delete_group(group_id): + """Delete the user's group.""" + return "WOULD DELETE GROUP." + +@groups.route("/edit/", 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/", 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//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//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/", 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("/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("/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/", 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/", 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/", 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/", 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/", 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/") +@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//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//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//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("/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 " --> %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 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 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 new file mode 100644 index 00000000..8cd489a4 Binary files /dev/null and b/gn2/wqflask/static/Congenic.png differ 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 new file mode 100644 index 00000000..bf0d4a95 Binary files /dev/null and b/gn2/wqflask/static/fonts/arial.ttf differ diff --git a/gn2/wqflask/static/fonts/courbd.ttf b/gn2/wqflask/static/fonts/courbd.ttf new file mode 100644 index 00000000..c8081467 Binary files /dev/null and b/gn2/wqflask/static/fonts/courbd.ttf differ diff --git a/gn2/wqflask/static/fonts/fnt_bs.ttf b/gn2/wqflask/static/fonts/fnt_bs.ttf new file mode 100644 index 00000000..712c38cf Binary files /dev/null and b/gn2/wqflask/static/fonts/fnt_bs.ttf differ diff --git a/gn2/wqflask/static/fonts/tahoma.ttf b/gn2/wqflask/static/fonts/tahoma.ttf new file mode 100644 index 00000000..bb314be3 Binary files /dev/null and b/gn2/wqflask/static/fonts/tahoma.ttf differ diff --git a/gn2/wqflask/static/fonts/trebucbd.ttf b/gn2/wqflask/static/fonts/trebucbd.ttf new file mode 100644 index 00000000..1ab1ae0a Binary files /dev/null and b/gn2/wqflask/static/fonts/trebucbd.ttf differ diff --git a/gn2/wqflask/static/fonts/verdana.ttf b/gn2/wqflask/static/fonts/verdana.ttf new file mode 100644 index 00000000..754a9b7b Binary files /dev/null and b/gn2/wqflask/static/fonts/verdana.ttf differ diff --git a/gn2/wqflask/static/fonts/verdanab.ttf b/gn2/wqflask/static/fonts/verdanab.ttf new file mode 100644 index 00000000..1a99258f Binary files /dev/null and b/gn2/wqflask/static/fonts/verdanab.ttf differ diff --git a/gn2/wqflask/static/gif/89.gif b/gn2/wqflask/static/gif/89.gif new file mode 100644 index 00000000..e9b3279d Binary files /dev/null and b/gn2/wqflask/static/gif/89.gif differ diff --git a/gn2/wqflask/static/gif/error/Wild-Type-Mouse.gif b/gn2/wqflask/static/gif/error/Wild-Type-Mouse.gif new file mode 100644 index 00000000..2c68b5ee Binary files /dev/null and b/gn2/wqflask/static/gif/error/Wild-Type-Mouse.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-aliens-29.gif b/gn2/wqflask/static/gif/error/animated-gifs-aliens-29.gif new file mode 100644 index 00000000..e9d38277 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-aliens-29.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-angels-04.gif b/gn2/wqflask/static/gif/error/animated-gifs-angels-04.gif new file mode 100644 index 00000000..94e11847 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-angels-04.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-cats-016.gif b/gn2/wqflask/static/gif/error/animated-gifs-cats-016.gif new file mode 100644 index 00000000..7e6ec9a3 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-cats-016.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-cats-031.gif b/gn2/wqflask/static/gif/error/animated-gifs-cats-031.gif new file mode 100644 index 00000000..af7ef655 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-cats-031.gif differ 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 new file mode 100644 index 00000000..89c79ddf Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-cell-phones-03.gif differ 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 new file mode 100644 index 00000000..7530d180 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-cell-phones-16.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-13.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-13.gif new file mode 100644 index 00000000..afb05c62 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-computers-13.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-28.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-28.gif new file mode 100644 index 00000000..f5b4a563 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-computers-28.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-32.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-32.gif new file mode 100644 index 00000000..7258e594 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-computers-32.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-42.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-42.gif new file mode 100644 index 00000000..ed1f8722 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-computers-42.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-60.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-60.gif new file mode 100644 index 00000000..f58d69f1 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-computers-60.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-64.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-64.gif new file mode 100644 index 00000000..5d5b4fdf Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-computers-64.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-65.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-65.gif new file mode 100644 index 00000000..b4b10845 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-computers-65.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-72.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-72.gif new file mode 100644 index 00000000..e60cb4fe Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-computers-72.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-74.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-74.gif new file mode 100644 index 00000000..bd7b72f3 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-computers-74.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-computers-75.gif b/gn2/wqflask/static/gif/error/animated-gifs-computers-75.gif new file mode 100644 index 00000000..916d6b33 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-computers-75.gif differ 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 new file mode 100644 index 00000000..0ec782c4 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-construction-sites-038.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-dogs-04.gif b/gn2/wqflask/static/gif/error/animated-gifs-dogs-04.gif new file mode 100644 index 00000000..9515c18a Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-dogs-04.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-dogs-14.gif b/gn2/wqflask/static/gif/error/animated-gifs-dogs-14.gif new file mode 100644 index 00000000..f1e2e1f5 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-dogs-14.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-dogs-18.gif b/gn2/wqflask/static/gif/error/animated-gifs-dogs-18.gif new file mode 100644 index 00000000..572849d5 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-dogs-18.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-dogs-47.gif b/gn2/wqflask/static/gif/error/animated-gifs-dogs-47.gif new file mode 100644 index 00000000..d808c9ee Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-dogs-47.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-dogs-50.gif b/gn2/wqflask/static/gif/error/animated-gifs-dogs-50.gif new file mode 100644 index 00000000..9865ee45 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-dogs-50.gif differ 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 new file mode 100644 index 00000000..ee9c113d Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-lava-lamps-01.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-mice-02.gif b/gn2/wqflask/static/gif/error/animated-gifs-mice-02.gif new file mode 100644 index 00000000..5ca2ee5c Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-mice-02.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-mice-09.gif b/gn2/wqflask/static/gif/error/animated-gifs-mice-09.gif new file mode 100644 index 00000000..7cb361e4 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-mice-09.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-mice-24.gif b/gn2/wqflask/static/gif/error/animated-gifs-mice-24.gif new file mode 100644 index 00000000..96a26450 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-mice-24.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-smileys-063.gif b/gn2/wqflask/static/gif/error/animated-gifs-smileys-063.gif new file mode 100644 index 00000000..62de166c Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-smileys-063.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-smileys-068.gif b/gn2/wqflask/static/gif/error/animated-gifs-smileys-068.gif new file mode 100644 index 00000000..3550e978 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-smileys-068.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-smileys-134.gif b/gn2/wqflask/static/gif/error/animated-gifs-smileys-134.gif new file mode 100644 index 00000000..954ab614 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-smileys-134.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-smileys-211.gif b/gn2/wqflask/static/gif/error/animated-gifs-smileys-211.gif new file mode 100644 index 00000000..596174d7 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-smileys-211.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-smileys-234.gif b/gn2/wqflask/static/gif/error/animated-gifs-smileys-234.gif new file mode 100644 index 00000000..5aba636b Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-smileys-234.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-001.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-001.gif new file mode 100644 index 00000000..7896ff1f Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-001.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-002.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-002.gif new file mode 100644 index 00000000..89da6441 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-002.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-005.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-005.gif new file mode 100644 index 00000000..b7887630 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-005.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-012.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-012.gif new file mode 100644 index 00000000..f6697d02 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-012.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-056.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-056.gif new file mode 100644 index 00000000..2b2496a4 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-056.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-059.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-059.gif new file mode 100644 index 00000000..f2188656 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-059.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-060.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-060.gif new file mode 100644 index 00000000..aa8f7bd3 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-060.gif differ diff --git a/gn2/wqflask/static/gif/error/animated-gifs-stickmen-069.gif b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-069.gif new file mode 100644 index 00000000..473212e4 Binary files /dev/null and b/gn2/wqflask/static/gif/error/animated-gifs-stickmen-069.gif differ diff --git a/gn2/wqflask/static/gif/error/m001.gif b/gn2/wqflask/static/gif/error/m001.gif new file mode 100644 index 00000000..81c8ba26 Binary files /dev/null and b/gn2/wqflask/static/gif/error/m001.gif differ diff --git a/gn2/wqflask/static/gif/error/m002.gif b/gn2/wqflask/static/gif/error/m002.gif new file mode 100644 index 00000000..7232c58b Binary files /dev/null and b/gn2/wqflask/static/gif/error/m002.gif differ diff --git a/gn2/wqflask/static/gif/error/m003.gif b/gn2/wqflask/static/gif/error/m003.gif new file mode 100644 index 00000000..2384ceb6 Binary files /dev/null and b/gn2/wqflask/static/gif/error/m003.gif differ diff --git a/gn2/wqflask/static/gif/error/m004.gif b/gn2/wqflask/static/gif/error/m004.gif new file mode 100644 index 00000000..d77c708d Binary files /dev/null and b/gn2/wqflask/static/gif/error/m004.gif differ diff --git a/gn2/wqflask/static/gif/error/m005.gif b/gn2/wqflask/static/gif/error/m005.gif new file mode 100644 index 00000000..1b2de7ec Binary files /dev/null and b/gn2/wqflask/static/gif/error/m005.gif differ diff --git a/gn2/wqflask/static/gif/error/m006.gif b/gn2/wqflask/static/gif/error/m006.gif new file mode 100644 index 00000000..f354a4cc Binary files /dev/null and b/gn2/wqflask/static/gif/error/m006.gif differ diff --git a/gn2/wqflask/static/gif/error/m007.gif b/gn2/wqflask/static/gif/error/m007.gif new file mode 100644 index 00000000..ba2eeb37 Binary files /dev/null and b/gn2/wqflask/static/gif/error/m007.gif differ diff --git a/gn2/wqflask/static/gif/error/m008.gif b/gn2/wqflask/static/gif/error/m008.gif new file mode 100644 index 00000000..4cdec5cb Binary files /dev/null and b/gn2/wqflask/static/gif/error/m008.gif differ diff --git a/gn2/wqflask/static/gif/error/mouse-wheel.gif b/gn2/wqflask/static/gif/error/mouse-wheel.gif new file mode 100644 index 00000000..164220e7 Binary files /dev/null and b/gn2/wqflask/static/gif/error/mouse-wheel.gif differ diff --git a/gn2/wqflask/static/gif/waitAnima2.gif b/gn2/wqflask/static/gif/waitAnima2.gif new file mode 100644 index 00000000..50aff7f2 Binary files /dev/null and b/gn2/wqflask/static/gif/waitAnima2.gif differ diff --git a/gn2/wqflask/static/images/Belknap_Fig1_1998.png b/gn2/wqflask/static/images/Belknap_Fig1_1998.png new file mode 100644 index 00000000..46305fa1 Binary files /dev/null and b/gn2/wqflask/static/images/Belknap_Fig1_1998.png differ diff --git a/gn2/wqflask/static/images/Chrna1vsMyf6.gif b/gn2/wqflask/static/images/Chrna1vsMyf6.gif new file mode 100644 index 00000000..881a08e8 Binary files /dev/null and b/gn2/wqflask/static/images/Chrna1vsMyf6.gif differ diff --git a/gn2/wqflask/static/images/Congenic.png b/gn2/wqflask/static/images/Congenic.png new file mode 100644 index 00000000..8cd489a4 Binary files /dev/null and b/gn2/wqflask/static/images/Congenic.png differ diff --git a/gn2/wqflask/static/images/Normal_Plot.gif b/gn2/wqflask/static/images/Normal_Plot.gif new file mode 100644 index 00000000..dc239f8e Binary files /dev/null and b/gn2/wqflask/static/images/Normal_Plot.gif differ diff --git a/gn2/wqflask/static/images/SilverFig3_2.png b/gn2/wqflask/static/images/SilverFig3_2.png new file mode 100644 index 00000000..5b4b2c70 Binary files /dev/null and b/gn2/wqflask/static/images/SilverFig3_2.png differ diff --git a/gn2/wqflask/static/images/SilverFig3_6.png b/gn2/wqflask/static/images/SilverFig3_6.png new file mode 100644 index 00000000..5b91d991 Binary files /dev/null and b/gn2/wqflask/static/images/SilverFig3_6.png differ diff --git a/gn2/wqflask/static/images/Winsorize1.png b/gn2/wqflask/static/images/Winsorize1.png new file mode 100644 index 00000000..f3a65f29 Binary files /dev/null and b/gn2/wqflask/static/images/Winsorize1.png differ diff --git a/gn2/wqflask/static/images/Winsorize3.png b/gn2/wqflask/static/images/Winsorize3.png new file mode 100644 index 00000000..a9ed95d6 Binary files /dev/null and b/gn2/wqflask/static/images/Winsorize3.png differ diff --git a/gn2/wqflask/static/images/edit.png b/gn2/wqflask/static/images/edit.png new file mode 100644 index 00000000..571b08cd Binary files /dev/null and b/gn2/wqflask/static/images/edit.png differ 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 new file mode 100644 index 00000000..ae99fedb Binary files /dev/null and b/gn2/wqflask/static/new/images/CITGLogo.png differ diff --git a/gn2/wqflask/static/new/images/Nif.png b/gn2/wqflask/static/new/images/Nif.png new file mode 100644 index 00000000..e975a921 Binary files /dev/null and b/gn2/wqflask/static/new/images/Nif.png differ diff --git a/gn2/wqflask/static/new/images/PythonLogo.png b/gn2/wqflask/static/new/images/PythonLogo.png new file mode 100644 index 00000000..b76dafba Binary files /dev/null and b/gn2/wqflask/static/new/images/PythonLogo.png differ diff --git a/gn2/wqflask/static/new/images/a1.gif b/gn2/wqflask/static/new/images/a1.gif new file mode 100644 index 00000000..283921a0 Binary files /dev/null and b/gn2/wqflask/static/new/images/a1.gif differ diff --git a/gn2/wqflask/static/new/images/arrowdown.gif b/gn2/wqflask/static/new/images/arrowdown.gif new file mode 100644 index 00000000..577e5476 Binary files /dev/null and b/gn2/wqflask/static/new/images/arrowdown.gif differ diff --git a/gn2/wqflask/static/new/images/edit.gif b/gn2/wqflask/static/new/images/edit.gif new file mode 100644 index 00000000..45b314ee Binary files /dev/null and b/gn2/wqflask/static/new/images/edit.gif differ diff --git a/gn2/wqflask/static/new/images/ipad_icon3.png b/gn2/wqflask/static/new/images/ipad_icon3.png new file mode 100644 index 00000000..97060e97 Binary files /dev/null and b/gn2/wqflask/static/new/images/ipad_icon3.png differ diff --git a/gn2/wqflask/static/new/images/question_mark.jpg b/gn2/wqflask/static/new/images/question_mark.jpg new file mode 100644 index 00000000..82df7e81 Binary files /dev/null and b/gn2/wqflask/static/new/images/question_mark.jpg differ diff --git a/gn2/wqflask/static/new/images/step1.gif b/gn2/wqflask/static/new/images/step1.gif new file mode 100644 index 00000000..42d2bc7d Binary files /dev/null and b/gn2/wqflask/static/new/images/step1.gif differ diff --git a/gn2/wqflask/static/new/images/step2.gif b/gn2/wqflask/static/new/images/step2.gif new file mode 100644 index 00000000..b3dca656 Binary files /dev/null and b/gn2/wqflask/static/new/images/step2.gif differ diff --git a/gn2/wqflask/static/new/images/step3.gif b/gn2/wqflask/static/new/images/step3.gif new file mode 100644 index 00000000..0e0c7ea2 Binary files /dev/null and b/gn2/wqflask/static/new/images/step3.gif differ 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 = $("") + cell = $(''); + cell.append( + $('')); + cell.append(" "); + cell.append("No genotype datasets remaining."); + row.append(cell); + $(table_id + " tbody").append(row); + } + table_data.forEach(function(dataset) { + row = $("") + 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 = $(""); + check = $( + ''); + 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 = $(""); + 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 = $("") + cell = $(''); + cell.append( + $('')); + cell.append(" "); + cell.append("No phenotype traits to select from."); + row.append(cell); + $(table_id + " tbody").append(row); + } + table_data.forEach(function(trait) { + row = $("") + 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( + '' + + trait.year + "")); + 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 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('
'); + 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('
'); + 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 = ''+''+this.data().id +''+'
' + ncbi_link = 'NCBI'+'
' + omim_link = '
OMIM'+'
' + 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 = 'Edge: ' + this.data().id + '
' + lod_score = 'LOD: ' + this.data().lod + '
' + 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;k6&&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;k0)){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;kref;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)||result1&&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;k0)){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?kref;i=0<=ref?++k:--k){result[nrow][i]=cs[i]}for(i=l=0,ref1=nrow;0<=ref1?lref1;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;kref;j=0<=ref?++k:--k){results.push(function(){var l,ref1,results1;results1=[];for(i=l=0,ref1=mat.length;0<=ref1?lref1;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=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;kbr[0]&&z=br[i]&&z=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;j0&&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;j1}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;j1){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;j1;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(incol||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=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(i0?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(j0?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;jngroup-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=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=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;k0){curves=g.append("g").attr("id","curves");ref8=data.chrname;for(j=0,len=ref8.length;j0){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;k1){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;lngroup-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;jngroup-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=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;k1e-6){flag_sum_not_1}if(d3panels.sumArray(function(){var k,len1,results;results=[];for(k=0,len1=v.length;k1)}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;kngroup-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.lengthplot_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;k0){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=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;kpt.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;m0){margin.top=height*margin.top/(margin.top+margin.bottom)}else{margin.top=height/2}if(initial_value!=null){value=initial_value;if(valuerange[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(pixelsinterval[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(drange[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(pixelsinterval[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 = $("") + this_opt_group.append($("") + this_opt_group.append($("") + this_opt_group.append($("") + this_opt_group.append($("") + this_opt_group.append($("") + this_opt_group.append($("
PubMed: ' + trait_info['pubmed_text'] + '
' + 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('PubMed: ' + trait_info['pubmed_text'] + '
' + 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('PubMed: ' + trait_info['pubmed_text'] + '
' + 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(''); + } + $('#hidden_inputs').append(''); + 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 = ""; + the_html += " "; + the_html += ""; + the_html += ""; + the_html += ""; + for (_i = 0, _len = trait_data.length; _i < _len; _i++) { + trait = trait_data[_i]; + the_html += ""; + the_html += ""; + if ("abbreviation" in trait) { + the_html += ""; + } else if ("symbol" in trait) { + the_html += ""; + } else { + the_html += ""; + } + the_html += ""; + the_html += ""; + } + the_html += ""; + the_html += "
RecordData SetDescription
" + trait.name + "" + trait.name + "" + trait.name + "" + trait.dataset_name + "" + trait.description + "
"; + the_html += "" + $("#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 '' + } + }, + { + '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 '' + data.name + '' + } + }, + { + 'title': "
Value
", + 'orderDataType': "dom-input", + 'type': "cust-txt", + 'data': null, + 'targets': 3, + 'width': "60px", + 'render': function(data) { + if (data.value == null) { + return '' + } else { + if (js_data.no_decimal_place == false) { + return '' + } else { + return '' + } + } + } + } + ]; + + 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': "
SE
", + 'orderDataType': "dom-input", + 'type': "cust-txt", + 'data': null, + 'targets': 5, + 'width': "60px", + 'render': function(data) { + if (data.variance == null) { + return '' + } else { + return '' + } + } + } + ); + + if (js_data.has_num_cases === true) { + attrStart += 1 + columnList.push( + { + 'title': "
N
", + '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 '' + } else { + return '' + } + } + } + ); + } + } + else { + if (js_data.has_num_cases === true) { + attrStart += 1 + columnList.push( + { + 'title': "
N
", + '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 '' + } else { + return '' + } + } + } + ); + } + } + + 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': "
" + js_data.attributes[attrKeys[i]].name + "
", + '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 '' + data.extra_attributes[attr_name][0] + '' + } 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(' 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('
'); + 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 = ''+''+this.data().id +''+'
' + qtip_content += gn_link + if (typeof(this.data().geneid) !== 'undefined'){ + ncbi_link = 'NCBI'+'
' + qtip_content += ncbi_link + } + if (typeof(this.data().omim) !== 'undefined'){ + omim_link = '
OMIM'+'
' + 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 = 'Sample r: ' + this.data().correlation + '
' + p_value_line = 'Sample p(r): ' + this.data().p_value + '
' + overlap_line = 'Overlap: ' + this.data().overlap + '
' + scatter_plot = '
View Scatterplot' + 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 = `the password must have a length greater than six characters` + $("#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 = `${passwordFeedback[num_score].feedback}` + 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() : "" + pvalue + ""; + //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: "Trait " + js_data.trait_id + ": " + js_data.short_description + "", + }, + margin: { + l: 100, + r: 30, + t: 100, + b: 60 + }, + legend: { + x: 0.05, + y: 0.9, + xanchor: 'left' + }, + xaxis: { + title: "normal quantiles", + 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: "" + js_data.unit_type + "", + 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('Loading data...'); + + 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 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 = "RECENT SEARCH"; + 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 = "" + historySearch[i].substr(0, val.length) + ""; + b.style.color = "blue"; + b.innerHTML += historySearch[i].substr(val.length); + b.innerHTML += ""; + 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 = "" + arr[i].substr(0, val.length) + ""; + b.innerHTML += arr[i].substr(val.length); + /*insert a input field that will hold the current array item's value:*/ + b.innerHTML += ""; + /*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: "Interquartile Range", + 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($(""; +}; + +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: "Trait " + js_data.trait_id + ": " + js_data.short_description + "", + }, + xaxis: { + type: 'category', + titlefont: { + size: 16 + }, + showline: true, + ticklen: 4, + tickfont: { + size: 16 + }, + }, + yaxis: { + title: "" + js_data.unit_type + "", + 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: " Trait " + js_data.trait_id + ": " + js_data.short_description + "", + }, + xaxis: { + autorange: true, + title: js_data.unit_type, + titlefont: { + family: "arial", + size: 16 + }, + ticklen: 4, + tickfont: { + size: 16 + } + }, + yaxis: { + autorange: true, + title: "count", + 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: "Trait " + js_data.trait_id + ": " + js_data.short_description + "", + xaxis: { + showline: true, + titlefont: { + family: "arial", + size: 16 + }, + tickfont: { + family: "arial", + size: 16 + } + }, + yaxis: { + title: { + text: ""+js_data.unit_type+"" + }, + 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: "" + sampleGroupList[2] + "", + x0: "" + sampleGroupList[2] + "" + } + var trace2 = { + y: getSampleVals(fullSampleLists[1]), + type: 'violin', + points: 'none', + box: { + visible: true + }, + line: { + color: 'red', + }, + meanline: { + visible: true + }, + name: "" + sampleGroupList[1] + "", + x0: "" + sampleGroupList[1] + "" + } + var trace3 = { + y: getSampleVals(fullSampleLists[0]), + type: 'violin', + points: 'none', + box: { + visible: true + }, + line: { + color: 'green', + }, + meanline: { + visible: true + }, + name: "" + sampleGroupList[0] + "", + x0: "" + sampleGroupList[0] + "" + } + 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: "Trait " + js_data.trait_id + "", + x0: "density" + } + ] +} + +$('.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('
�' + message + '
'); +}; + +$('#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($("").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($("").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 %} + + +{% endblock %} +{% block content %} + +
+ +
+
+
+
+
+

Search for user by either name or e-mail:

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+
+ + + +{% endblock %} + +{% block js %} + + + +{% 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 %} + +
+ +
+ + +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+ + +{% endblock %} + +{% block js %} + + + +{% 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 %} + + + +{% endblock %} +{% block content %} + +
+ +
+ +
+ {% if admin_groups|length == 0 and member_groups|length == 0 %} +

You currently aren't a member or admin of any groups.

+
+ + + + {% else %} +

Admin Groups

+
+ {% if admin_groups|length == 0 %} +

You currently aren't the administrator of any groups.

+ {% else %} + + + + + + + + + + + + + + {% for group in admin_groups %} + + + + {% set group_url = url_for('group_management.view_group', group_id=group.uuid) %} + + + + + + + {% endfor %} + +
IndexName# MembersCreatedLast ChangedGroup ID
{{ loop.index }}{{ group.name }}{{ group.admins|length + group.members|length }}{{ group.created_timestamp }}{{ group.changed_timestamp }}{{ group.uuid }}
+ {% endif %} +
+
+
+

User Groups

+
+ {% if member_groups|length == 0 %} +

You currently aren't a member of any groups.

+ {% else %} + + + + + + + + + + + + + {% for group in member_groups %} + + + + {% set group_url = url_for('group_management.view_group', group_id=group.uuid) %} + + + + + + {% endfor %} + +
IndexName# MembersCreatedLast Changed
{{ loop.index }}{{ group.name }}{{ group.admins|length + group.members|length }}{{ group.created_timestamp }}{{ group.changed_timestamp }}
+ {% endif %} + {% endif %} +
+
+
+ + + +{% endblock %} + +{% block js %} + + + +{% 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 %} + + +
+ + + {{ flash_me() }} + + + + + + + + + + + + + + + + + + + {% if user.confirmed_at %} + + {% else %} + + {% endif %} + + + + + + {% if user.superuser %} + + {% else %} + + {% endif %} + + + + + + {% if user.most_recent_login %} + + {% else %} + + {% endif %} + + + + + + + + +
Name{{ user.full_name }}
Organization{{ user.organization }}
Confirmed{{ timeago(user.confirmed_at + "Z") }}Unconfirmed
SuperuserMade a superuser {{ timeago(user.superuser_info['timestamp'] + "Z") }} by + {{ user.crowner.name_and_org }}. + + + + Make Superuser + + +
Most recent login{{ timeago(user.most_recent_login.timestamp.isoformat() + "Z") }} from {{ user.most_recent_login.ip_address }}Never
+ + Become this user for debugging + +
+ + +
+ + + +{% endblock %} + +{% block js %} + + + + +{% 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 %} + +
+
+ {{ 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') %} +

Resource Manager

+ {% if resource_info.get('owner_id') %} + {% set user_details = resource_info.get('owner_details') %} +

+ Current Owner: {{ user_details.get('full_name') }} +

+ {% if user_details.get('organization') %} +

+ Organization: {{ user_details.get('organization')}} +

+ {% endif %} + {% endif %} + {% if DATA_ACCESS > DataRole.VIEW and ADMIN_STATUS > AdminRole.NOT_ADMIN %} + + Change Owner + + {% endif %} +
+ +
+
+ +
+
+
+
+ +
+ {{ resource_info.get('name') }} +
+
+ {% 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 %} +
+ +
+ + +
+
+
+ +
+ +
+
+ {% endif %} +
+
+
+ {% if ADMIN_STATUS > AdminRole.NOT_ADMIN %} +
+
+ +
+ {% if resource_info.get('group_masks', [])|length > 0 %} +

Current Group Permissions

+
+ + + + + + + + + + + {% for key, value in resource_info.get('group_masks').items() %} + + + + + + + {% endfor %} + +
IdNameDataMetadata
{{ key }}{{ value.group_name}}{{ value.data }}{{ value.metadata }}
+ {% else %} +

No groups are currently added to this resource.

+ {% endif %} +
+ {% endif %} +
+
+ + + + {% endblock %} + {% block js %} + + + + {% 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 %} + + + +{% endblock %} +{% block content %} + +
+ {% if 'full_name' in user_details %} + + {% endif %} +
+
+
+
+
+ +
+ {% if 'email_address' in user_details %}{{ user_details.email_address }}{% else %}N/A{% endif %} +
+
+
+ +
+ {% if 'full_name' in user_details %}{{ user_details.full_name }}{% else %}N/A{% endif %} + +
+
+
+ +
+ {% if 'organization' in user_details %}{{ user_details.organization }}{% else %}N/A{% endif %} + +
+
+
+ +
+ + +
+
+
+
+
+
+
+ + + +{% endblock %} + +{% block js %} + + + +{% 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 %} + + +{% endblock %} +{% block content %} + +
+ +
+ +
+
+
+
+

Search by:

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+
+ + + +{% endblock %} + +{% block js %} + + + + + +{% 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 %} + + + +{% endblock %} +{% block content %} + +
+

Group Privileges

+
+
+ + +
+ +
+

Data and Metadata Privileges

+ + + + + + + + + + + + + {% if 'data' in default_privileges %} + + + + {% else %} + + + + {% endif %} + + + + {% if 'metadata' in default_privileges %} + + + + {% else %} + + + + {% endif %} + + +
No-AccessViewEdit
Data:
Metadata:
+
+

Admin Privileges

+ + + + + + + + + + + + + {% if 'admin' in default_privileges %} + + + + {% else %} + + + + {% endif %} + + +
Not AdminEdit AccessEdit Admins
Admin:
+
+
+
+ + + +{% endblock %} + +{% block js %} + + +{% 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 %} + + {{ header("List of users", "" )}} + + +
+ + + + + + + + + + + + + {% for user in users %} + + + + + + + + {% endfor %} +
EmailOrganizationActiveConfirmedSuperuser
+ {{ user.email_address }} + {{ user.organization }}{{ 'Yes' if user.active else 'No' }}{{ 'True' if user.confirmed else 'False' }}{{ 'True' if user.superuser else 'False' }}
+ + +
+ + + +{% 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 %} + + + +{% endblock %} +{% block content %} + +{% 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) %} +
+ +
+ + + +
+
+
+

Admins

+
+ + + + + + + + + {% if is_admin %} + + {% endif %} + + + + {% for admin in admins %} + + + + + + + {% if is_admin %} + + {% endif %} + + {% endfor %} + +
IndexNameEmail AddressOrganizationUID
{{ loop.index }}{% if 'full_name' in admin %}{{ admin.full_name }}{% elif 'name' in admin %}{{ admin.name }}{% else %}N/A{% endif %}{% if 'email_address' in admin %}{{ admin.email_address }}{% else %}N/A{% endif %}{% if 'organization' in admin %}{{ admin.organization }}{% else %}N/A{% endif %}{{admin.user_id}}
+ {% if is_admin %} +
+ E-mail of user to add to admins (multiple e-mails can be added separated by commas): + +
+
+ +
+ {% endif %} +
+
+
+ {% if members|length > 0 %} +

Members

+
+ + + + + + + + + {% if is_admin %} + + {% endif %} + + + + {% for member in members %} + + + + + + + + {% if is_admin %} + + {% endif %} + + + {% endfor %} + +
IndexNameEmail AddressOrganizationUID
+ {% if is_admin %} + + {% endif %} + {{ loop.index }}{% if 'full_name' in member %}{{ member.full_name }}{% elif 'name' in admin %}{{ admin.name }}{% else %}N/A{% endif %}{% if 'email_address' in member %}{{ member.email_address }}{% else %}N/A{% endif %}{% if 'organization' in member %}{{ member.organization }}{% else %}N/A{% endif %}{{ member }}
+ {% if is_admin %} +
+ E-mail of user to add to members (multiple e-mails can be added separated by commas): + +
+
+ +
+ {% endif %} + {% else %} + There are currently no members in this group. + {% if is_admin %} +
+ E-mail of user to add to members (multiple e-mails can be added separated by commas): + +
+
+ +
+ {% endif %} + {% endif %} +
+
+
+

Resources

+
+ {% if resources|length > 0 %} + + + + + + + + + + + + {% for resource in resources %} + + + + + + + + {% endfor %} + +
IndexNameDataMetadataAdmin
{{ loop.index }}{% if 'name' in resource %}{{ resource.name }}{% else %}N/A{% endif %}{% if 'data' in resource %}{{ resource.data }}{% else %}N/A{% endif %}{% if 'metadata' in resource %}{{ resource.metadata }}{% else %}N/A{% endif %}{% if 'admin' in resource %}{{ resource.admin }}{% else %}N/A{% endif %}
+ {% else %} + There are currently no resources associated with this group. + {% endif %} +
+
+
+
+ + + +{% endblock %} + +{% block js %} + + + +{% 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%} +
+ +

+ + {{error_type}}:  + {{error.description}} +

+

+ Please contact the data's owner or GN administrators if you believe you + should have access to these data. +

+
+ +{%endblock%} diff --git a/gn2/wqflask/templates/base.html b/gn2/wqflask/templates/base.html new file mode 100644 index 00000000..e0fc9e63 --- /dev/null +++ b/gn2/wqflask/templates/base.html @@ -0,0 +1,397 @@ +{% from "base_macro.html" import header, flash_me, timeago %} + + + + + + {% block title %}{% endblock %} GeneNetwork 2 + + + + + + + + + + + + + + + + {% block css %} + {% endblock %} + + + + + + +
+ +
+
+
+ +
+ + + +
+ + + + + + + GNQNA Search + + + + + + + + + +
+
+
+ +
+ {% block content %} + {% endblock %} +
+
+
+

Web services initiated January, 1994 as + + The Portable Dictionary of the Mouse Genome + ; June 15, 2001 as WebQTL; and Jan 5, 2005 as GeneNetwork. +

+

+ This site is currently operated by + Rob Williams, + Pjotr Prins, + Saunak Sen, + Zachary Sloan, + Arthur Centeno, + and Bonface Munyoki. +

+

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 + colleagues.

+
+

GeneNetwork support from:

+
    +
  • + + The UT Center for Integrative and Translational Genomics + +
  • +
  • + NIGMS + Systems Genetics and Precision Medicine Project (R01 GM123489, 2017-2026) +
  • +
  • + NSF + Panorama: Integrated Rack-Scale Acceleration for Computational Pangenomics + (PPoSS 2118709, 2021-2016) +
  • +
  • + NIDA + NIDA Core Center of Excellence in Transcriptomics, Systems Genetics, and the Addictome (P30 DA044223, 2017-2022) +
  • +
  • + NIA + Translational Systems Genetics of Mitochondria, Metabolism, and Aging (R01AG043930, 2013-2018) +
  • +
  • + NIAAA + Integrative Neuroscience Initiative on Alcoholism (U01 AA016662, U01 AA013499, U24 AA013513, U01 AA014425, 2006-2017) +
  • +
  • + NIDA, NIMH, and NIAAA + (P20-DA 21131, 2001-2012) +
  • +
  • + NCI MMHCC (U01CA105417), NCRR, BIRN, (U24 RR021760) +
  • +
+

Published in + JOSS +

+

+ Development and source code on github with issue tracker and documentation. + {% if version: %} +

GeneNetwork {{ version }}

+ {% endif %} +

It took the server {{ g.request_time() }} seconds to process this page.

+
+ +
+
+ + + + + + + + + + + + + {% block js %} + {% endblock %} + + + + 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 %} +
+ {% for category, message in messages %} + + {% endfor %} +
+ {% endif %} + {% endwith %} +{% endmacro %} + +{% macro header(main, second) %} +
+
+

{{ main }}

+

+ {{ second }} +

+
+
+ + {{ flash_me() }} +{% endmacro %} + +{% macro timeago(timestamp) %} + +{% 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 %} + +{% endblock %} +{% block content %} +
+
+
+ {{ rendered_markdown|safe }} +
+{% 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 %} + + +{% endblock %} +{% block content %} +
+
+ {% for year, year_blogs in blogs.items() %} +
+

{{year}}

+
+ {%for blog in year_blogs%} +
+
+ +
+
+ {% endfor %} + {%endfor%} +
+
+{% 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 @@ +Opening BNW +
+ +
+ 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 %} + + +{% endblock %} + +{% block content %} + +
+
+
+
+
+

Case Attributes Reference Table

+
+
+
+ + + + + + + + + + + + + {% for id_, name, description in case_attributes %} + + + + + + {% endfor %} + + + + + + +
Case AttributeDescriptionActions
{{ name }}{{ description }} + + + +
+ + {% if modifications or inserts or deletions %} +

Please Review These Changes

+ {% endif %} + {% if modifications %} +

Modify Existing Case Attributes

+ + + + + + + + {% for data in modifications %} + + + + + + {% endfor %} + +
AuthorDiffAction
{{ data.get('author') }} + {% if data.get("name")%} + Name:
+ Original: {{ data["name"].get("Original") }}
+ Current: {{ data["name"].get("Current") }}
+ Diff:
+
{{data["name"].get("Diff")}}
+ {% endif %} + {% if data.get("description")%} + Description:
+ Original: {{ data["description"].get("Original") }}
+ Current: {{ data["description"].get("Current") }}
+ Diff:
+
{{data["description"].get("Diff")}}
+ {% endif %} +
+ + +
+ {% endif %} + {% if deletions %} +

Delete Existing Case Attributes

+ + + + + + + + + + + + + + + {% for data in deletions %} + + + + + + + {% endfor %} + +
Case AttributeAuthorDescriptionAction
{{data.get("name")}}{{ data.get('author') }}{{data.get("description")}} + + +
+ {% endif %} + + {% if inserts %} +

Insert New Case Attributes

+ + + + + + + + + + + + + + + {% for data in inserts %} + + + + + + + {% endfor %} + +
Case AttributeAuthorDescriptionAction
{{ data.get("name") }}{{ data.get("author")}}{{ data.get("description") }} + + +
+ {% endif %} +
+ {%endblock%} + + {% block js %} + + + {% 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 @@ +
+ + +
+ + 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 @@ +
+ + +
+ + 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 %} + + + +{% endblock %} +{% block content %} + +
+ {{flash_me()}} + + {% if g.user_session.logged_in %} +

Collections owned by {% if g.user_session.user_name %}{{ g.user_session.user_name }}{% else %}{{ g.user_session.user_email }} {% endif %}

+ {% else %} +

Your Collections

+ {% endif %} + +
+ + + + + + +
+
+ +
+
+
+
+
+ {%if anon_collections_error is defined%} + {{display_error("Anonymous Collections", anon_collections_error)}} + {%endif%} + {%if anon_collections | length > 0%} + + + + + + + + + + + + + + + {% for uc in anon_collections %} + + + + + + + + {% endfor %} + +
Anonymous Collections
IndexNameCreatedLast Changed# Records
{{ loop.index }} + {{ uc.name }}{{ uc.created_timestamp }}{{ uc.changed_timestamp }}{{ uc.num_members }}
+ {%endif%} + {% if collections|length > 0 %} + {% if (anon_collections | length > 0) and (collections | length > 0) %} +
+ {%endif%} + + + + + + + + + + + + + + + {% for uc in collections %} + + + + + + + + {% endfor %} + +
User Collections
IndexNameCreatedLast Changed# Records
{{ loop.index }} + {{ uc.name }}{{ uc.created_timestamp }}{{ uc.changed_timestamp }}{{ uc.num_members }}
+ {% else %} + You have no collections yet. + {% endif %} +
+
+ + + +{% endblock %} + +{% block js %} + + + + + + + + + + +{% 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 %} + + {{ header("Not logged in") }} + + +
+ +
+ + + +{% endblock %} + +{% block js %} + + +{% 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 @@ +
+ + +
+ + 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 %} + + + + + +{% endblock %} +{% block content %} + + +
+ {{flash_me()}} +

+ {{ uc.name }} +
+ + +
+ + +

+

This collection has {{ '{}'.format(numify(trait_obs|count, "record", "records")) }}

+ +
+
+ + + + + + + {% include 'tool_buttons.html' %} + +
+
+
+
+ +
+ +
+   +
+ +
+
+
+
+
+ + + + + + + + + + + + + + +
+
+
+ Show/Hide Columns:   + + + + + + + + + + +
+
+ + + + +

Loading...
+
+
+
+
+ + + +{% endblock %} + +{% block js %} + + + + + + + + + + + + + + + + + + +{% 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 %} + + {% 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 %} +
+ + + + +
+ + + + + + + + + + + + + + + {% for this_trait in trait_obs %} + + + + + + + + + + + + {% endfor %} + + +
RecordDescriptionLocationMeanMax LRSMax LRS Location
+ + + + {{ this_trait.name }} + + {{ this_trait.description_display }}{{ this_trait.location_repr }}{{ this_trait.mean }}{{ this_trait.LRS_score_repr }}{{ this_trait.LRS_location_repr }}
+ +
+ + + + + + + +
+
+ + + +{% endblock %} + +{% block js %} + +{% 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 %} + + +{% endblock %} +{% block content %} +
+

Comparison Bar Chart

+
+
+

+ The following is a grouped bar chart with the sample values for each selected trait. +

+
+
+
+
+ +
+ + + +{% endblock %} + +{% block js %} + + + + + + + + + +{% 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 %} + + + + + + + +{% endblock %} + +{% block content %} + +
+ + + + + + + + + +

Correlation Scatterplot

+
+ + + + + + + +
Width px Height px
+
+ {% if collections_exist == "True" %} +
+
+ 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. +
+
+
+ + +
+ + +
+ +
+
+
+ + + + + + + + + + + {% else %} +
No collections currently exist. Please create a collection first if you wish to include cofactors in the scatterplots.
+
+ {% endif %} + + + +
+ +
+
+
+
+
+
+ {% if trait_1.dataset.type == "ProbeSet" %} + +
+ [{{trait_1.symbol}} on {{trait_1.location_repr}} Mb] + {{trait_1.description_display}} +
+ {% elif trait_1.dataset.type == "Publish" %} + +
+ PubMed: {{trait_1.pubmed_text}} + {{trait_1.description_display}} +
+ {% elif trait_1.dataset.type == "Geno" %} + +
+ Location: {{trait_1.location_repr}} Mb +
+ {% endif %} + +
+ + {% if trait_2.dataset.type == "ProbeSet" %} + +
+ [{{trait_2.symbol}} on {{trait_2.location_repr}} Mb] + {{trait_2.description_display}} +
+ {% elif trait_2.dataset.type == "Publish" %} + +
+ PubMed: {{trait_2.pubmed_text}} + {{trait_2.description_display}} +
+ {% elif trait_2.dataset.type == "Geno" %} + +
+ Location: {{trait_2.location_repr}} Mb +
+ {% endif %} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StatisticValue
Number{{jsdata.num_overlap}}
Slope{{ jsdata.slope_string }}
Intercept{{'%0.3f' % jsdata.intercept}}
r value{{'%0.3f' % jsdata.r_value}}
P value{% if jsdata.p_value < 0.001 %}{{'%0.3e' % jsdata.p_value}}{% else %}{{'%0.3f' % jsdata.p_value}}{% endif %}
+ Regression Line +
+ y = {{ jsdata.slope_string }} * x {% if jsdata.intercept < 0 %}- {{'%0.3f' % (jsdata.intercept * -1)}}{% else %}+ {{'%0.3f' % jsdata.intercept}}{% endif %} +
+
+
+
+ +
+
+
+
+
+
+ {% if trait_1.dataset.type == "ProbeSet" %} + +
+ [{{trait_1.symbol}} on {{trait_1.location_repr}} Mb] + {{trait_1.description_display}} +
+ {% elif trait_1.dataset.type == "Publish" %} + +
+ PubMed: {{trait_1.pubmed_text}} + {{trait_1.description_display}} +
+ {% endif %} + +
+ + {% if trait_2.dataset.type == "ProbeSet" %} + +
+ [{{trait_2.symbol}} on {{trait_2.location_repr}} Mb] + {{trait_2.description_display}} +
+ {% elif trait_2.dataset.type == "Publish" %} + +
+ PubMed: {{trait_2.pubmed_text}} + {{trait_2.description_display}} +
+ {% endif %} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StatisticValue
Number{{jsdata.num_overlap}}
Slope{{ jsdata.srslope_string }}
Intercept{{'%0.3f' % jsdata.srintercept}}
r value{{'%0.3f' % jsdata.srr_value}}
P value{% if jsdata.srp_value < 0.001 %}{{'%0.3e' % jsdata.srp_value}}{% else %}{{'%0.3f' % jsdata.srp_value}}{% endif %}
+ Regression Line +
+ y = {{ jsdata.srslope_string }} * x {% if jsdata.srintercept < 0 %}- {{'%0.3f' % (jsdata.srintercept * -1)}}{% else %}+ {{'%0.3f' % jsdata.srintercept}}{% endif %} +
+
+
+
+ +
+ +{% endblock %} + +{% block js %} + + + + + + + + + + + +{% 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%} + + + + + + +{%endblock%} + +{%block content%} +
+

{{error["error-type"]}}

+

{{error["error-message"]}}

+

+ {%for line in error["stderr-output"]%} + {{line}}
+ {%endfor%} +

+
+{%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 %} + + + + + +{% endblock %} +{% block content %} + +
+

Correlation Matrix

+
Select any cell in the matrix to generate a scatter plot.
Lower left cells list Pearson product-moment correlations; upper right cells list Spearman rank order correlations. Each cell also contains the n of cases 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.
+
+{% if lowest_overlap < 8 %} +
Caution: This matrix of correlations contains some cells with small sample sizes of fewer than 8.
+{% endif %} + + + + + + + + + + + {% for trait in traits %} + + {% endfor %} + + {% for trait in traits %} + {% set outer_loop = loop.index %} + + + + {% for result in corr_results[loop.index-1] %} + {% if result[0].name == trait.name and result[0].dataset == trait.dataset %} + + {% else %} + {% if result[1] == 0 %} + + {% else %} + + {% endif %} + {% endif %} + {% endfor %} + + {% endfor %} + +
Spearman Rank Correlation (rho)
P e a r s o n     r
  
+ {{ loop.index }} +
+ {{ loop.index }}: {{ trait.dataset.name }}  {{ trait.name }} +
{% 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 %}
+ +
n
{{ result[2] }}
N/A{{ '%0.2f' % result[1] }}
({{ result[2] }})
+
+
+ + + +
+
+ +{% if pca_works == "True" %} +
+ {% include 'pca_scree_plot.html' %} +
+ +

PCA Traits

+
+ + + + + + + + + + + + + + + + {% for this_trait_id in pca_trait_ids %} + + + + + + {% endfor %} + +
IndexPCA Trait
{{ loop.index }}{{ pca_trait_ids[loop.index - 1] }}
+
+ + + +
+

Factor Loadings Plot

+
+

Factor Loadings Table

+ + + + + + + {% if trait_list|length > 2 %}{% endif %} + + + + {% for row in loadings_array %} + {% set row_counter = loop.index-1 %} + + + {% for column in row %} + + {% endfor %} + + {% endfor %} + + +
TraitFactor 1Factor 2Factor 3
+ + {{ traits[loop.index-1].name }} + + {{ '%0.3f' % loadings_array[row_counter][loop.index-1]|float }}
+
+{% endif %} +
+{% endblock %} + +{% block js %} + + + + + + + + + + + + + + + + +{% 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 %} + + + + + + +{% endblock %} +{% block content %} +
+ +
+

Values of record {{ this_trait.name }} in the {{ this_dataset.fullname }} + dataset were compared to all records in the {{ target_dataset.fullname }} + 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. +

+
+
+
+ + + + {% include 'tool_buttons.html' %} +
+
+
+
+
+ + + + + + + + + + +
+
+
+
+
+ +
+
+
+ +
+
+ Show/Hide Columns: +
+ {% if target_dataset.type == 'ProbeSet' %} + + + + + + + + {% elif target_dataset.type == 'Publish' %} + + + + + + + + + + + + {% else %} + + + + + {% endif %} +
+
+ + + + + {% for header in header_fields %} + + {% endfor %} + + + + + + +
{{header}}

Loading...
+
+
+{% endblock %} + +{% block js %} + + + + + + + + + + + + + + + + + + +{% 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 %} + + + +
+

Web site design and coding

+ +

Published and Unpublished Phenotype Data

+
    +
  • Lu Lu +
  • Elissa J. Chesler +
  • John C Crabbe, OHSU +
  • John K Belknap, OHSU +
  • Mary-Kathleen Sullivan +
  • Emily English +
  • Byron Jones +
  • Ryan McNieve +
  • Nathan Copeland +
+

Genotype / Genomic Data

+ +

+
+ +{% 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 %} + + + + +{% endblock %} + +{% block title %}CTL results{% endblock %} + +{% block content %} +
+

CTL Results

+ {{(request.form['trait_list'].split(',')|length)}} phenotypes as input
+ + + +

CTL/QTL Plots for individual traits

+ {% for r in range(2, (request.form['trait_list'].split(',')|length +1)) %} + + Embedded Image + {% endfor %} +

Tabular results

+ + + significant CTL:
+ {% for r in range(results['ctlresult'][0]|length) %} + + {% for c in range(results['ctlresult']|length) %} + + {% endfor %} + + {% endfor %} +
traitmarkertraitLODdCor
+ {% if c > 2 %} + {{results['ctlresult'][c][r]|float|round(2)}} + {% else %} + {{results['ctlresult'][c][r]}} + {% endif %} +
+

Network Figure

+
+
+{% endblock %} + +{% block js %} + + + + + + + + + + + + + +{% 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 %} + +
+ {% if request.form['trait_list'].split(",")|length < 2 %} +{% else %} +

CTL analysis

+CTL analysis is published as open source software, if you are using this method in your publications, please cite:

+Arends D, Li Y, Brockmann GA, Jansen RC, Williams RW, Prins P
+Correlation trait locus (CTL) mapping: Phenotype network inference subject to genotype.
+The Journal of Open Source Software (2016)
+Published in +

+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+{% endif %} +
+{% 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 %} + + + + + + + + + +
+List of DataSets
+

{{ info.DB_Name }} +modify this page + +

+ + + + + +
+ + + + + + + + + + + +
GN Accession: {{ info.GN_AccesionId }}
GEO Series: {{ info.GEO_Series }}
Title: {{ info.Title }}
Organism: {{ info.Organism }}
Group: {{ info.InbredSet }}
Tissue: {{ info.Tissue }}
Dataset Status: {{ info.Status }}
Platforms: {{ info.Platforms }}
Normalization: {{ info.Normalization }}
+ See Contact Information
+
+
+ + + + + + + +
Download datasets and supplementary data files
{{ htmlfilelist|safe }}
+
+
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Summary:
{{ info.Summary | safe}}

About the cases used to generate this set of data:
{{ info.About_Cases | safe}}
About the tissue used to generate this set of data:
{{ info.About_Tissue | safe }}
About downloading this data set:

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 Data Sharing Policies, and Conditions and Limitations, and Contacts. Following publication, download a summary text file or Excel file of the PDNN probe set data. Contact RW Williams regarding data access problems. +



About the array platform:

Affymetrix Mouse Genome 430 2.0 array: The 430v2 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.



About data values and data processing:
Harshlight was used to examine the image quality of the array (CEL files). Bad areas (bubbles, scratches, blemishes) of arrays were masked. + +

First pass data quality control: Affymetrix GCOS provides useful array quality control data including: +

    +
  1. 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. +
  2. 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. +
  3. 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. +
  4. 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. +
+ +

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. + +

The entire procedure can be reapplied once the initial outlier data sets have been eliminated to detect any remaining outlier data sets. + + +

DataDesk 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. + +

Probe (cell) level data from the CEL file: These CEL values produced by GCOS are 75% quantiles from a set of 91 pixel values per cell. +

    + +
  1. 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. + +
  2. We performed a quantile normalization of the log base 2 values for all arrays using the same initial steps used by the RMA transform. + +
  3. We computed the Z scores for each cell value. + +
  4. We multiplied all Z scores by 2. + +
  5. 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. + +
  6. 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. +
+

Probe set data from the CHP file: 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. + + +

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. + +

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. + + +

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. + +

Sex of the samples was validated using sex-specific probe sets such as Xist and Dby.

Data source acknowledgment:

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: + +

    +
  • David C. Airey, Ph.D. +
    Grant Support: Vanderbilt Institute for Integratie Genomics +
    Department of Pharmacology +
    david.airey at vanderbilt.edu + +
  • Lu Lu, M.D. +
    Grant Support: NIH U01AA13499, U24AA13513 + +
  • Fred H. Gage, Ph.D. +
    Grant Support: Lookout Foundation + +
  • Dan Goldowitz, Ph.D. +
    Grant Support: NIAAA INIA AA013503 +
    University of Tennessee Health Science Center +
    Dept. Anatomy and Neurobiology +
    email: dgold@nb.utmem.edu + +
  • Shirlean Goodwin, Ph.D. +
    Grant Support: NIAAA INIA U01AA013515 + +
  • Gerd Kempermann, M.D. +
    Grant Support: The Volkswagen Foundation Grant on Permissive and Persistent Factors in Neurogenesis in the Adult Central Nervous System +
    Humboldt-Universitat Berlin +
    Universitatsklinikum Charite +
    email: gerd.kempermann at mdc-berlin.de + +
  • Kenneth F. Manly, Ph.D. +
    Grant Support: NIH P20MH062009 and U01CA105417 + +
  • Richard S. Nowakowski, Ph.D. +
    Grant Support: R01 NS049445-01 + +
  • Glenn D. Rosen, Ph.D. +
    Grant Support: NIH P20 + +
  • Leonard C. Schalkwyk, Ph.D. +
    Grant Support: MRC Career Establishment Grant G0000170 +
    Social, Genetic and Developmental Psychiatry +
    Institute of Psychiatry,Kings College London +
    PO82, De Crespigny Park London SE5 8AF +
    L.Schalkwyk@iop.kcl.ac.uk + +
  • Guus Smit, Ph.D. +
    Dutch NeuroBsik Mouse Phenomics Consortium +
    Center for Neurogenomics & Cognitive Research +
    Vrije Universiteit Amsterdam, The Netherlands +
    e-mail: guus.smit at falw.vu.nl +
    Grant Support: BSIK 03053 + +
  • Thomas Sutter, Ph.D. +
    Grant Support: INIA U01 AA13515 and the W. Harry Feinstone Center for Genome Research + +
  • Stephen Whatley, Ph.D. +
    Grant Support: XXXX + +
  • Robert W. Williams, Ph.D. +
    Grant Support: NIH U01AA013499, P20MH062009, U01AA013499, U01AA013513 +
+



Experiment Type:

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 GPL1261), of which 201 passed quality control and error checking. This particular data set was processed using the PDNN 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. +

Overall Design:

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 GPL1261), of which 201 passed quality control and error checking. This particular data set was processed using the PDNN 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. +

Contributor:
    +
  • David C. Airey, Ph.D. +
    Grant Support: Vanderbilt Institute for Integratie Genomics +
    Department of Pharmacology +
    david.airey at vanderbilt.edu + +
  • Lu Lu, M.D. +
    Grant Support: NIH U01AA13499, U24AA13513 + +
  • Fred H. Gage, Ph.D. +
    Grant Support: Lookout Foundation + +
  • Dan Goldowitz, Ph.D. +
    Grant Support: NIAAA INIA AA013503 +
    University of Tennessee Health Science Center +
    Dept. Anatomy and Neurobiology +
    email: dgold@nb.utmem.edu + +
  • Shirlean Goodwin, Ph.D. +
    Grant Support: NIAAA INIA U01AA013515 + +
  • Gerd Kempermann, M.D. +
    Grant Support: The Volkswagen Foundation Grant on Permissive and Persistent Factors in Neurogenesis in the Adult Central Nervous System +
    Humboldt-Universitat Berlin +
    Universitatsklinikum Charite +
    email: gerd.kempermann at mdc-berlin.de + +
  • Kenneth F. Manly, Ph.D. +
    Grant Support: NIH P20MH062009 and U01CA105417 + +
  • Richard S. Nowakowski, Ph.D. +
    Grant Support: R01 NS049445-01 + +
  • Glenn D. Rosen, Ph.D. +
    Grant Support: NIH P20 + +
  • Leonard C. Schalkwyk, Ph.D. +
    Grant Support: MRC Career Establishment Grant G0000170 +
    Social, Genetic and Developmental Psychiatry +
    Institute of Psychiatry,Kings College London +
    PO82, De Crespigny Park London SE5 8AF +
    L.Schalkwyk@iop.kcl.ac.uk + +
  • Guus Smit, Ph.D. +
    Dutch NeuroBsik Mouse Phenomics Consortium +
    Center for Neurogenomics & Cognitive Research +
    Vrije Universiteit Amsterdam, The Netherlands +
    e-mail: guus.smit at falw.vu.nl +
    Grant Support: BSIK 03053 + +
  • Thomas Sutter, Ph.D. +
    Grant Support: INIA U01 AA13515 and the W. Harry Feinstone Center for Genome Research + +
  • Stephen Whatley, Ph.D. +
    Grant Support: XXXX + +
  • Robert W. Williams, Ph.D. +
    Grant Support: NIH U01AA013499, P20MH062009, U01AA013499, U01AA013513 +


Citation:
+

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 (2009) Genetics of the hippocampal transcriptome in mice: a systematic survey and online neurogenomic resource. Front. Neurogen. 1:3 Full Text HTML doi:10.3389/neuro.15.003.2009 + +

Submission Date:
01 Jul. 2009

Laboratory:
Williams and Lu Labs

Samples:
None

+

+
+ + + +{% 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 %} + +{% endblock %} + +{% block title %}Dataset: {{ name }}{% endblock %} +{% block content %} + +{% if dataset %} + +{% include 'metadata/dataset.html' %} + +{% else %} + + + +{% endif %} + +{% endblock %} + +{% block js %} + +{% 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 %} + +{% endblock %} + +{% block content %} + +
+ {% 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 %} +

Additions Data:

+
+
+ + + + + + {% for data in additions %} + + + + {% endfor %} + +
Added Data ({{ header }})
{{ data }}
+
+
+ {% endif %} + + {% if modifications %} +

Modified Data:

+ +
+
+ + + + + + + + {% for data in modifications %} + + + + + + {% endfor %} + +
OriginalCurrentDiff ({{ header }})
{{ data.get("Original") }}{{ data.get("Current") }}
{{data.get("Diff")}}
+
+
+ {% endif %} + + {% if deletions %} +

Deleted Data:

+
+
+ + + + + {% for data in deletions %} + + + + {% endfor %} + +
Deleted Data +
{{ data }}
+
+
+ {% endif %} + +
+{%endblock%} + +{% block js %} + + + +{% 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 %} + +{% endblock %} + +{% block content %} + +{% with messages = get_flashed_messages(with_categories=true) %} +{% if messages %} +
+ {% for category, message in messages %} + + {% endfor %} +
+{% endif %} +{% endwith %} + +
+ {%if (not waiting) and (not approved) and (not rejected)%} +
+ + There are no diffs to act on. +
+ {%endif%} + {% if waiting %} +

Files for approval:

+
+
+ + + + + + + + + {% for data in waiting %} + + {% set file_url = url_for('metadata_edit.show_diff', name=data.filepath.name) %} + + + + {% 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) %} + + + + {% endfor %} + +
Resource Id + AuthorTimeStamp
{{ data.meta.get("resource_id") }}{{ data.meta.get("author")}}{{ data.meta.get("time_stamp")}} + + + +
+
+
+ {% endif %} + + {% if approved %} +

Approved Data:

+
+
+ + + + + + + {% for data in approved %} + + {% set file_url = url_for('metadata_edit.show_diff', name=data.filepath.name) %} + + + + + {% endfor %} + +
Resource Id + AuthorTimeStamp
{{ data.meta.get("resource_id") }}{{ data.meta.get("author")}}{{ data.meta.get("time_stamp")}}
+
+
+ {% endif %} + + {% if rejected %} +

Rejected Files:

+
+
+ + + + + + + {% for data in rejected %} + + {% set file_url = url_for('metadata_edit.show_diff', name=data.filepath.name) %} + + + + + {% endfor %} + +
Resource Id + AuthorTimeStamp
{{ data.meta.get("resource_id") }}{{ data.meta.get("author")}}{{ data.meta.get("time_stamp")}}
+
+
+ {% endif %} +
+{%endblock%} + +{% block js %} + + + +{% 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 %} +
+

Edit: {{title}}

+
+ + + {% if editable is defined %} + + {% endif %} + + + + + +
+
+{% 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 %} +
+

{{title}}

+
+ {% if editable == "true" %} + + + + {% endif %} +
+ {{content|safe}} +
+{% 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%} + + + + + +{%endblock%} + +{%block content%} +
+

{{inbredset_group.InbredSetName}}: Edit Case-Attributes

+ + {{flash_me()}} + +

Instructions

+
    +
  • + The table is scrollable. Scroll to find the strain(s) you want to edit. +
  • +
  • Change value(s) to edit them in the database.
  • +
  • Delete value(s) to delete them from the database.
  • +
  • Click "Submit" to submit all the changes you have made
  • +
  • + Click "Reset" to undo ALL the changes you have made and + start over. +
  • +
+ + View Diffs + +
+
+ + +
+ +
+ + + + + {%for caname in case_attribute_names%} + + {%endfor%} + + + + {%for strain in strains%} + +
+
+ {%for attr in case_attribute_names%} + {%if case_attribute_values.get(strain.Name)%} + + {%else%} + + {%endif%} + {%endfor%} + + + {%else%} + + + + {%endfor%} + + +
+ +
+ + +
+
+
+{%endblock%} + +{%block js%} + +{%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 %} + +{% endblock %} + +{% block content %} + +
+ +

History

+ {% if diff %} +
+ + + + + + + + + {%set ns = namespace(display_ts=True)%} + {%for ts, item in diff.items()%} + {%set ns.display_ts = True%} + {%for the_diff in item%} + + {%if ns.display_ts%} + + {%set ns.display_ts = False%} + {%endif%} + + + + + {%endfor%} + {%endfor%} + +
TimestampEditorFieldDiff
{{ts}}{{the_diff.author}}{{the_diff.diff.field}}
{{the_diff.diff.diff}}
+
+ {% endif %} + +
+{%endblock%} + +{% block js %} + + + +{% 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 %} + +{% with messages = get_flashed_messages(with_categories=true) %} +{% if messages %} +{% for category, message in messages %} +
+

{{ message|safe }}

+
+{% endfor %} +{% endif %} +{% endwith %} +
+ + +
+
+
+ + + + +
+
+
+

Edit Sample Data

+

+ Download a spreadsheet of sample values to edit in Excel (or a similar program) and then upload the edited file +

+ +
+ +
+ +
+
+
+

Edit Metadata

+
+ + +
+ + +
+
+
+ +
+ + +
+ +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+ +
+ {% if sample_list|length < 2000 %} +
+

+ Type "x" to delete a value. +

+ + + + + + + + + + + + {% for sample in sample_list %} + + + + + + + + + + {% endfor %} + +
IDSampleValueSEN
{{ loop.index }}{{ sample }}±
+
+ {% endif %} + +
+
+ +{%endblock%} + +{% block js %} + +{% 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 %} + +
+ {% with messages = get_flashed_messages(category_filter=["warning"]) %} + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} + + {% with messages = get_flashed_messages(category_filter=["success"]) %} + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} + +
+
+
+ + + + + +
+
+
+

Edit Sample Data

+

+ Download a spreadsheet of sample values to edit in Excel (or a similar program) and then upload the edited file +

+ +
+ +
+ +
+
+
+

Edit Metadata

+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+ +
+
+

+ Type "x" to delete a value. +

+ + + + + + + + + + + + {% for sample in sample_list %} + + + + + + + + + + {% endfor %} + +
IDSampleValueSEN
{{ loop.index }}{{ sample }}±
+
+ +
+
+ +{%endblock%} + +{% block js %} + +{% 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 %} + + {{ header("Error") }} + +
+ +

You must select at least two traits to use the {{ tool }}.

+
+ + + + +{% 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 %} + +{% endblock %} + +{% block content %} + +
+ +
{{ rendered_markdown|safe }}
+
+ +{% if svg_data %} + +
+

Chord dependency Graph of Genenetwork2

+ Graph generated from genenetwork.scm. You can zoom in and out within the bounding box. +
+ +
+ + + +
+

The dependency graph is shown below

+ +

To explore this image SVG you may want to open it in new browser page and zoom in. Or use an SVG viewing application.

+ + Dependency graph of the tools needed to build python3-genenetwork2 +
+{% endif %} + +{% endblock %} + +{% block js %} + +{% if svg_data %} + + +{% 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 %} + + +
+
+
+ + + +

ERROR

+ +

+ This error is not what we wanted to see. Unfortunately errors + are part of all software systems and we need to resolve this + together. +

+

+ It is important to report this ERROR so we can fix it for everyone. +

+ +

+ 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 new + issue or E-mail this full page to one of the developers + directly. +

+
+ +

+ (GeneNetwork error: {{message[:128]}}) +

+ +
+    GeneNetwork {{ version }} {{ stack[0] }}
+    {{ message }} (error)
+    {{ stack[-3] }}
+    {{ stack[-2] }}
+  
+ +

+ To check if this already a known issue, search the + issue + tracker. +

+ + Toggle full stack trace +
+
+      GeneNetwork {{ version }} {% for line in stack %} {{ line }}
+      {% endfor %}
+    
+
+
+
+ + +{% 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 %} + +{% endblock %} + +{% block content %} + + +
+ {{ rendered_markdown|safe }} + +
+ +{% 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 %} + + +{% endblock %} +{% block content %} + + +
+

GeneWiki For {{ symbol }}

+

GeneWiki enables you to enrich the annotation of genes and transcripts.

+ +

+ GeneNetwork + + {{ entries.gn_entries|length if entries.gn_entries[0] else 0 }} + : +

+ {% if entries.gn_entries[0] %} +
    + {% for entry in entries.gn_entries %} +
  • +
    + + {{ entry["entry"]["value"] }} + {% if entry.get("weburl") %} + web + {% endif %} + +
    +
    Author:
    +
    {{ entry["author"]["value"] }}
    + + {% if entry.get("geneCategory") %} +
    Category:
    +
    {{ entry["geneCategory"]["value"]}}
    + {% endif %} + +
    Add Time:
    +
    {{ entry["created"]["value"]}}
    +
    +
    +
  • + {% endfor %} +
+ + {% else %} + +

There are no GeneNetwork entries for {{ symbol }}.

+ + {% endif %} + +

+ GeneRIF from NCBI + + {{ entries.ncbi_entries|length if entries.ncbi_entries[0] else 0 }} + : +

+ {% if entries.ncbi_entries[0] %} +
    + {% for entry in entries.ncbi_entries %} +
  1. + {{ entry.entry.value }} + ({{ entry["speciesBinomialName"]["value"] }}) + {% if entry.PubMedId.value != "" %} + {% set pmids = entry.PubMedId.value.split(",") %} + (PubMed: {% for id in pmids %} {{ id }}{% endfor %}) + {{ entry.createdOn.value }} + {% endif %} +
  2. + {% endfor %} +
+ {% else %} +

There are no NCBI entries for {{ symbol }}.

+ {% endif %} +
+{% 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" %} +
+

Error

+
+ {% if chip_name == "mixed" %} +

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.

+ {% elif chip_name == "not_microarray" %} +

You need to select at least one microarray trait to submit. + {% elif '_NA' in chip_name %} +

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.

+ {% else %} +

Sorry, an error occurred while submitting your traits to GeneWeaver.

+ {% endif %} +
+ {% else %} +
+

Opening GeneWeaver...

+
+
+ {% for key in hidden_vars %} + + {% endfor %} +
+ {% endif %} +{% endblock %} +{% block js %} +{% if wrong_input == "False" %} + +{% 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 %} + +{% endblock %} + +{% block title %}Genotype: {{ name }}{% endblock %} + +{% block content %} + +

+ {% if metadata.name %} + Genotype: {{ metadata.name }} + {% else %} + {{ name }} + {% endif %} +

+ +
+ + {% if metadata.datasetName %} + + + + + {% endif %} + + {% if metadata.inbredSetName %} + + + + + {% endif %} + + + + + + + + + + {% if metadata.cM %} + + + + + {% endif %} + + {% if metadata.mb %} + + + + + {% endif %} + + {% if metadata.sequence %} + + + + + {% endif %} + + {% if metadata.source %} + + + + + {% endif %} + + {% if metadata.markerName %} + + + + + {% endif %} +
Dataset: + {{ metadata.datasetName }} +
Group{{ metadata.inbredSetName }}
Species{{ metadata.species or "N/A"}}
LocationChr {{ metadata.ch }} @ {{ metadata.mb }} mb
cM{{ metadata.cM }}
mb{{ metadata.mb }}
Sequencemetadata.sequence
Source{{ metadata.source}}
Marker Name{{ metadata.markerName}}
+
+ +{% 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 %} + +{% endblock %} + +{% block content %} + + +
+ + {{ rendered_markdown|safe }} +
+{% 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 %} + + + + +
+ +{% if error %} +
+

{{error}}

+
+ +{% else %} +
+
+
+

CTL/QTL Plots for Individual Traits

+

{{ctl_plots|length}} phenotypes as input

+
+ +
+
+
+

Ctl line plot

+

Plot the CTL for genome-wide CTL on all traits (the output of CTLscan).

+
+
+
+ +
+
+
    + {% for trait in phenotypes %} + {% set trait_data = trait.split(':') %} +
  1. {{trait_data[0]}}
  2. + {% endfor %} +
+
+
+
+

Significant CTL

+
+
+

Network figure

+
+

Use tools like cytoscape to visualize the network

+ Download Sif file + Download Node file +
+
+
+ +{% endif %} +
+ + + + + + + +{% 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 %} + + + + + + + + +
+ {% if error!='null' %} +

{{error}}

+ + {% else %} +
+
+

Parameters used

+ +
+ +
+
+

Soft Thresholds

+
+ + {% for key, value in results["data"]["output"]["soft_threshold"].items()%} +
+

{{key}}

+ {% for val in value %} +

{{val|round(3)}}

+ {% endfor %} +
+ {% endfor %} +
+
+ +
+ + {% if image["image_generated"] %} +
+ +
+ + {% endif %} + +
+ +
+

Module eigen genes

+
+
+ +
+

Phenotype modules

+ +
+
+
+ +{% endif %} + +
+ +{% endblock %} + +{% block js %} + + + + + + + + + +{% endblock %} diff --git a/gn2/wqflask/templates/gnqa.html b/gn2/wqflask/templates/gnqa.html new file mode 100644 index 00000000..bed2627d --- /dev/null +++ b/gn2/wqflask/templates/gnqa.html @@ -0,0 +1,68 @@ +{% extends "base.html" %} +{% block title %}GNQNA{% endblock %} + +{% block content %} + + + + + +
+
+
+
+ + +
+
+ +
+ +
+ +
+ + + + + + + +{% endblock %} + diff --git a/gn2/wqflask/templates/gnqa_answer.html b/gn2/wqflask/templates/gnqa_answer.html new file mode 100644 index 00000000..4fb4b268 --- /dev/null +++ b/gn2/wqflask/templates/gnqa_answer.html @@ -0,0 +1,158 @@ +{% extends "base.html" %} +{% block title %}GNQNA{% endblock %} + +{% block content %} + + + + +{% block css %} + +{% endblock %} + + + + +
+
+
+

{{query}}

+

+ +
+

{{answer}}

+

+
+ +
+ + + +
+

References

+ {% if references %} +
    + {% for reference in references %} +
  • +

    {{ reference.bibInfo }}

    +

    {{reference.comboTxt}}

    +
  • + {% endfor %} +
+ {% else %} +

No references available.

+ {% endif %} +
+
+
+ + + + + + +{% 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 %} + + + +{% endblock %} +{% block content %} + + +
+ +

GN searched for the term(s) "{{ terms }}" in 754 datasets and 39,765,944 traits across 10 species
+ and found {{ trait_count }} results that match your query.
+ You can filter these results by adding key words in the fields below
+ and you can also sort results on most columns.

+

To study a record, click on its Record ID below.
Check records below and click Add button to add to selection.

+ +
+
+ + + + + + +
+ + + + +
+
+
+
+ + + + +

Loading...
+
+
+
+ + + +{% endblock %} + +{% block js %} + + + + + + + + + + + + + + + + +{% 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 %} + + + +{% endblock %} +{% block content %} + + +
+ +

GN searched for the term(s) "{{ terms }}" in 51 datasets and 13763 traits across 10 species
+ and found {{ trait_count }} results that match your query.
+ You can filter these results by adding key words in the fields below
+ and you can also sort results on most columns.

+

To study a record, click on its ID below.
Check records below and click Add button to add to selection.

+ +
+
+ + + + + + +
+ + + + +
+
+
+
+ + + + +

Loading...
+
+
+
+ + + +{% endblock %} + +{% block js %} + + + + + + + + + + + + + + + + +{% 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 %} + + +{% endblock %} +{% block content %} +
+

Heatmap

+
+
+

+ 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. +

+
+
+
+ +
+
+ +
+ + + +{% endblock %} + +{% block js %} + + + + + + + + + + + +{% 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 %} + + + + + + + + + + + + +{% endblock %} +{% block content %} + +
+ + {{ flash_me() }} + + {%if anon_collections | length > 0%} +
+
+

Import Anonymous Collections

+
+
+

+ 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? +

+

+ + If you choose to ignore this, the anonymous collections will be + eventually deleted and lost. + +

+
+
+ + + + +
+ + +
+
+
+ {%endif%} + +
+ +
+ +
+ + +

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:

+ +
    +
  • POSITION=(chr1 25 30) finds genes, markers, or transcripts on + chromosome 1 between 25 and 30 Mb.
  • + +
  • MEAN=(15 16) in the Combined field finds + highly expressed genes (15 to 16 log2 units)
  • + +
  • RANGE=(1.5 2.5) in the Any field finds traits with values with a specified fold-range (minimum = 1). + Useful for finding "housekeeping genes" (1.0 1.2) or highly variable molecular assays (10 100).
  • + +
  • LRS=(15 1000) or LOD=(2 8) finds all traits with peak LRS or LOD scores between lower and upper limits.
  • + +
  • LRS=(9 999 Chr4 122 155) finds all traits on Chr 4 from 122 and 155 Mb with LRS scores between 9 and 999.
  • + +
  • cisLRS=(15 1000 5) or cisLOD=(2 8 5) finds all cis eQTLs with peak LRS or LOD scores between lower and upper limits, + with an inclusion zone of 5 Mb around the parent gene.
  • + +
  • transLRS=(15 1000 5) or transLOD=(2 8 5) finds all trans eQTLs with peak LRS or LOD scores between lower and upper limits, + with an exclusion 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).
  • + +
  • POSITION=(Chr4 122 130) cisLRS=(9 999 10) + 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.
  • + +
  • RIF=mitochondrial searches RNA databases for + GeneRIF links.
  • + +
  • WIKI=nicotine searches + GeneWiki for genes that you or other users have annotated + with the word nicotine.
  • + +
  • GO:0045202 searches for synapse-associated genes listed in the + + Gene Ontology.
  • + +
  • RIF=diabetes LRS=(9 999 Chr2 100 105) transLRS=(9 999 10) + finds diabetes-associated transcripts with peak + trans eQTLs on Chr 2 between 100 and 105 Mb with LRS + scores between 9 and 999.
  • +
+
+
+ +
+
+ +
+
+ Webinars & Courses
+ In-person courses, live webinars and webinar recordings
+ +
+
+ Tutorials
+ Tutorials: Training materials in HTML, PDF and video formats
+ +
+
+ Documentation
+ Online manuals, handbooks, fact sheets and FAQs
+ +
+
+
+
+ + + +
+
+ + + + + + +
+
+
+
+ +{%endblock%} + +{% block js %} + + + + + + + +{% 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 %} + +

Data Set Group: {{ info.dataset_name }} + +{{ info.info_page_name }} +

+ + + + + +
+ + + + + + + + + + + +
Data Set: {{ info.info_file_title }}
GN Accession: GN{{ gn_accession_id }}
GEO Series: {{ info.geo_series }}
Title: {{ info.publication_title }}
Organism: {{ info.menu_name }}
Group: {{ info.group_name }}
Tissue: {{ info.tissue_name }}
Dataset Status: {{ info.dataset_status_name }}
Platforms: {{ info.gene_chip_name }}
Normalization: {{ info.avg_method_name }}
+
+ + + + + + + + + + + + + + + + + +
Contact Information
+ {{ info.investigator_first_name }} {{ info.inveestigator_last_name }}
+ {{ info.organization_name }}
+ {{ info.investigator_address }}
+ {{ info.investigator_city }}, {{ info.investigator_state }} {{ info.investigator_zipcode }} {{ info.investigator_country }}
+ Tel. {{ info.investigator_phone }}
+ {{ info.investigator_email }}
+ Website +
Download datasets and supplementary data files
+ +
+
+
+
+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Specifics of this Data Set:
{{ info.specifics|safe }}

Summary:
{{ info.dataset_summary|safe }}

About the cases used to generate this set of data:
{{ info.about_cases|safe }}

About the tissue used to generate this set of data:
{{ info.about_tissue|safe }}

About the array platform:
{{ info.about_platform|safe }}

About data values and data processing:
{{ info.about_data_processing|safe }}

Notes:
{{ info.notes|safe }}

Experiment Type:
{{ info.experiment_design|safe }}

Contributor:
{{ info.contributors|safe }}

Citation:
{{ info.citation|safe }}

Data source acknowledgment:
{{ info.acknowledgement|safe }}

Study Id:
{{ info.dataset_id }}

+

+ +{% 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%} +

Debug Job

+ +The following show details for job "{{job_id}}" to assist in debugging. + +

Metadata

+ +
    +
  • Job ID: {{job_id}}
  • +
  • Command: {{command}}
  • +
  • Received: {{request_received_time}}
  • +
  • Return Code: {{return_code}}
  • +
  • Completion Status: {{completion_status}}
  • +
  • Status: {{status}}
  • +
+ +

STDERR

+ +
+ {%for line in stderr:%} +

{{line}}

+ {%endfor%} +
+ +

STDOUT

+ +
+ {%for line in stdout:%} +

{{line}}

+ {%endfor%} +
+ + +{%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%} +

No Such Job

+ +

The job with id {{job_id | string}} does not exist

+{%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%} + +{%endblock%} + +{%block content%} + +
+

Current Notebooks

+ + {%for item in links:%} + + {%endfor%} +
+ +{%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 %} + +{% endblock %} + +{% block content %} + + + +
+ {{ rendered_markdown|safe }} + +
+{% 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%} + + + + + +{%endblock%} + +{%block content%} +
+

List Diffs

+ + {{flash_me()}} + + + + + + + + + + + {%for diff in diffs%} + + + + + {%else%} + + + + {%endfor%} + + +{%endblock%} + +{%block js%} + +{%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%} + + + + + +{%endblock%} + +{%block content%} +
+

List Diffs

+ + {{flash_me()}} + + {%set the_error = error.json()%} + +

+ + + {{error.status_code}}: {{the_error["error"]}} + {{the_error.error_description}} +

+{%endblock%} + +{%block js%} + +{%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 @@ +Loading {{ start_vars.tool_used }} Results + + +
+ {% for key, value in start_vars.items() %} + + {% endfor %} +
+
+
+
+ {% if start_vars.tool_used == "Mapping" %} +

Computing the Maps

+
+ Time Elapsed: +
+ Trait Metadata +
+ species = {{ start_vars.species[0] | upper }}{{ start_vars.species[1:] }} +
+ group = {{ start_vars.group[0] | upper }}{{ start_vars.group[1:] }} +
+ trait identifier = {{ start_vars.trait_name }} +
+ n of sample = {{ start_vars.n_samples }} + {% if start_vars.transform != "" %} +
+ transformation = {{ start_vars.transform }} + {% endif %} +
+ hash of sample values = {{ start_vars.vals_hash }} +

+ Mapping Metadata +
+ mapping method = {% if start_vars.method == "gemma" %}GEMMA {% if start_vars.use_loco == "True" %}using LOCO {% endif %}{% else %}{{ start_vars.method }}{% endif %} + {% if start_vars.maf != "" and start_vars.method != "reaper" %} +
+ minor allele frequency lower limit = {{ start_vars.maf }} + {% endif %} +
+ {% if start_vars.covariates != "" and start_vars.method != "reaper" %} + {% set covariate_list = start_vars.covariates.split(",") %} + cofactors = {% for covariate in covariate_list %}{% set this_covariate = covariate.split(":")[0] %}{{ this_covariate }}{% if not loop.last %}, {% endif %}{% endfor %} + {% else %} + cofactors = None + {% endif %} + {% if start_vars.control_marker != "" and start_vars.do_control == "true" and start_vars.method != "gemma" %} +
+ marker covariate = {{ start_vars.control_marker }} + {% endif %} +
+ {% if start_vars.genofile != "" %} + {% set genofile_desc = start_vars.genofile.split(":")[1] %} + genotype file = {{ genofile_desc }} + {% else %} + genotype file = {{ start_vars.group[0] | upper }}{{ start_vars.group[1:] }}.geno + {% endif %} + {% if start_vars.num_perm | int > 0 and start_vars.method != "gemma" %} +
+ n of permutations = {{ start_vars.num_perm }} + {% endif %} + {% if num_bootstrap in start_vars %} + {% if start_vars.num_bootstrap | int > 0 and start_vars.method == "reaper" %} +
+ n of bootstrap = {{ start_vars.num_bootstrap }} + {% endif %} + {% endif %} + {% else %} +

 {{ start_vars.tool_used }} Computation in progress ...

+ {% endif %} +

+
+ +
+ {% if start_vars.vals_diff|length != 0 and start_vars.transform == "" %} +

+ +
+ + {% endif %} +
+
+
+
+
+ + + 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 @@ + + + + + Loading Correlation Results + + + + + + + + + +
+

 Correlation Computation in progress ...

+
+ +
+
+ + + + + + 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%} + +{{ header("An error occurred during mapping") }} + +
+

+ {%if error:%} +

+ The following error was raised

+

+
Error message
+
{{error.args[0]}}
+
Error Type
+
{{error_type}}
+
+

+

+ Please contact Zach Sloan (zachary.a.sloan@gmail.com) or Arthur Centeno + (acenteno@gmail.com) about the error. +

+ {%else:%} +

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.

+

+
+

+

Try mapping using interval mapping instead; some genotype files with many columns of NAs have issues with GEMMA or R/qtl.

+ {%endif%} +

+
+ + + + +{%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 %} + + + + + + + + +{% endblock %} +{% from "base_macro.html" import header %} +{% block content %} +
+
+ + {% if temp_trait is defined %} + + {% endif %} + + + + + + + + + {% if output_files is defined %} + + {% endif %} + {% if reaper_version is defined %} + + {% endif %} + + + + + + + + + + + {% if manhattan_plot == True %} + + {% endif %} + + + {% if categorical_vars is defined %} + + {% endif %} + {% if perm_strata is defined %} + + {% endif %} + + + + + + + + + + + + +
+
+

Map Viewer: Whole Genome


+ Population: {{ dataset.group.species|capitalize }} {{ dataset.group.name }}
+ Database: {{ dataset.fullname }}
+ {% if dataset.type == "ProbeSet" %}Trait ID:{% else %}Record ID:{% endif %} {{ this_trait.display_name }}
+ Trait Hash: {{ vals_hash }}
+ {% if dataset.type == "ProbeSet" %} + Gene Symbol: {{ this_trait.symbol }}
+ Location: Chr {{ this_trait.chr }} @ {{ this_trait.mb }} Mb
+ {% endif %} + {% if genofile_string != "" %} + Genotypes: {{ genofile_string.split(":")[1] }}
+ {% endif %} + Current Date/Time: {{ current_datetime }}
+
+ +

+
+ +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
Chr:  +   + +
View:  + to +
Units:  + + + + ? + +
+ units on the y-axis (0 for default) +
Width:  + pixels (minimum=900) +
+ {% if manhattan_plot == True and selectedChr == -1 %} + + + + + + +
+ Manhattan Plot Color Scheme:  + + + + + +
+ {% endif %} +
+
+ {% if (mapping_method == "reaper" or mapping_method == "rqtl_geno") and nperm > 0 %} + Permutation Test + + ? + +
+ {% endif %} + {% if mapping_method == "reaper" and nboot > 0 %} + Bootstrap Test + + ? + +
+ {% endif %} + {% if mapping_method == "reaper" %} + Allele Effects + + ? + +
+ {% endif %} + SNP Track + + ? + + * +
+ Gene Track *
+ {% if plotScale != "morgan" %} + Haplotype Tracks *
+ {% endif %} + Legend
+ Human Chromosomes ? +
+ * only apply to single chromosome physical mapping +
+
+
+ +
+ +
+
+
+ {{ gifmap|safe }} + + {% if additiveChecked|upper == "ON" %} +
+ 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. + {% endif %} +
+
+ {% if mapping_method == "gemma" or mapping_method == "reaper" %} +
+
+
+ + + + + +
+
+
+
+ {% endif %} +
+
+
+ {% if nperm > 0 and permChecked == "ON" %} +

+
+

+ Total of {{ nperm }} permutations  Download Permutation Results +
+ {% endif %} +
+ +
+ {% if selectedChr == -1 %} +
+

Mapping Statistics

+
+ + + + {% if geno_db_exists == "True" %}{% endif %} + +
+
+
+ + + + + +
+
+ {% elif selectedChr != -1 and plotScale =="physic" and (dataset.group.species == 'mouse' or dataset.group.species == 'rat') %} +
+

Interval Analyst

+
+ + + + {% for header in gene_table_header %} + + {% endfor %} + + + + {% for row in gene_table_body %} + + {% for n in range(row|length) %} + {% if n == 0 %} + + {% else %} + + {% endif %} + {% endfor %} + + {% endfor %} + +
{{ header|safe }}
{{ row[n]|safe }}{{ row[n]|safe }}
+
+
+ {% endif %} +
+ + + +{% endblock %} + +{% block js %} + + + + + + + + {% if manhattan_plot == True and selectedChr == -1 %} + + + {% endif %} + + + + + + + + + + + + + + + {% if mapping_method == "gemma" or mapping_method == "reaper" %} + + {% endif %} + + + +{% 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)) }} + +
+
+

+ Whole Genome Mapping +

+
+ + + +
+
+ + + +
+ +
+
+
+ +
+
+
+

+ Results +

+ + + + + + + + {% if mapping_scale == "centimorgan" %} + + {% else %} + + {% endif %} + + + + + {% for marker in qtl_results %} + {% if (score_type == "LOD" and marker.lod_score > cutoff) or + (score_type == "LRS" and marker.lrs_value > cutoff) %} + + + + {% if score_type == "LOD" %} + + {% else %} + + {% endif %} + + + + + {% endif %} + {% endfor %} + +
Index{{ score_type }}ChrcMMbLocus
+ + {{ loop.index }}{{ '%0.2f' | format(marker.lod_score|float) }}{{ '%0.2f' | format(marker.lrs_value|float) }}{{marker.chr}}{{ '%0.6f' | format(marker.Mb|float) }} + {{ marker.name }} + +
+
+
+ + + +{% endblock %} + +{% block js %} + + + +{% 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 @@ + + +
+ + + + +
+ +
+ 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 %} + + + + +{% endblock %} +{% block content %} +
+

Network Graph

+
+
+
+
+

Visualization Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ Focus Trait ? +
+ +
+ Correlation Coefficient ? +
+
+
-1
+ 0 +
1
+
+ + + +
+ Layouts: + +
+ Node Font Size: + + +
+ Edge Width: + + +
+

Download

+ + + + + + +
+ + + +
+
+
+
+
+
+ + +{% endblock %} + +{% block js %} + + + + + + + + + + + + +{% 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 %} + +
+ + +
+ +

Enter your email address and we'll send you a link to reset your password

+ +
+
+
+
+ +
+
+ + +
+
+ + + +
+
+ +
+ +
+ +
+
Has your email address changed?
+ + If you no longer use the email address connected to your account, you can contact us for assistance. + +
+ +
+
+
+
+ + {% 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.") }} + + +
+ + +

You will receive an email with the subject {{ subject }}.

+ +

You must click the link in the email to reset the password.

+ +

If you don't see the email, check your spam folder.

+
+ +{% endblock %} + +{% block js %} + + +{% 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 %} + +
+ + {{ flash_me() }} + +

Sign in here.

+ + {% if redis_is_available: %} +
+ +
+
+ +
+ +
+
+ +
+ + +
+ +
+ +
+ Remember me
+ Import existing collections +
+
+ +
+ +
+ + +
+
+
+ +
+ +

Don't have an account?

+ + {% if redis_is_available: %} + Create a new account + {% else %} +
+

You cannot create an account at this moment.
+ Please try again later.

+
+ {% endif %} + +
+

Login with external services

+ + {% if external_login: %} +
+ {% if external_login["github"]: %} + Login with Github + {% else %} +

Github login is not available right now

+ {% endif %} + + {% if external_login["orcid"]: %} + Login with ORCID + {% else %} +

ORCID login is not available right now

+ {% endif %} +
+ {% else: %} +
+

Sorry, you cannot login with Github or ORCID at this time.

+
+ {% endif %} + + + {% else: %} +
+

You cannot login at this moment using your GeneNetwork account (the authentication service is down).
+ Please try again later.

+
+ {% endif %} + {% if not external_login: %} +
+
+ 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. +
+ {% endif %} +
+
+ + {% endblock %} + +{% block css %} + +{% 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 %} +
+ +

Please contact the data's owner or GN administrators if you believe you should have access to these data.

+
+ +{% 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.") }} + + +
+ + +
+ + +

Enter your new password

+ + + {% if errors %} +
+ Please note: +
    + {% for error in errors %} +
  • {{error}}
  • + {% endfor %} +
+
+ {% endif %} + +
+ +
+ + + + +
+ +
+ +
+
+ + + +
+ +
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+ +{% endblock %} + +{% block js %} + + + +{% 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.") }} + + +
+ + +
+

Already have an account?

+ + + Sign in using existing account + + +
+ +

Don't have an account?

+ +
Register here
+ + {% if errors %} +
+ Please note: +
    + {% for error in errors %} +
  • {{error}}
  • + {% endfor %} +
+
+ {% endif %} + +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ + + +
+ +
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+ +{% endblock %} + +{% block js %} + + + +{% 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") }} + +
+ + +

You will receive an email with the subject {{ subject }}.

+ +

You must click the link in the email to complete registration.

+ +

If you don't see the email, check your spam folder.

+
+ +{% endblock %} + +{% block js %} + + + +{% 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") }} + +
+ + +

Enjoy using the site.

+ +
+ + Go to the homepage +
+ +{% endblock %} + +{% block js %} + + +{% 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") }} + +
+ +

You still need to verify your e-mail address before you can sign in.

+ +

We've resent the verificaiton email. + +

Please check for an email with the subject {{ subject }}.

+ +

You must click the link in the email to complete registration.

+ +

If you don't see the email, check your spam folder.

+
+ +{% endblock %} + +{% block js %} + + +{% 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 %} + +{% endblock %} + +{% block content %} + + +
+ {{ rendered_markdown|safe }} + +
+ +{% 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%} + +{%endblock%} + +{%block content%} +
+ {{profile_nav("resources", user_privileges)}} + + {{flash_me()}} + +
+
+ {%if resource_category_error%} +

+ +   + {{resource_category_error.error}}: + {{resource_category_error.error_message}} +

+ {%else%} +
+ +
+ Resource Category +
+ {%for category in resource_categories%} +
+ + + {{category.resource_category_description}} + +
+ {%endfor%} +
+
+ +
+ Basic Resource Information +
+ + + + The resource name, e.g. the experiment name. + +
+
+ +
+ Access Control +
+ + + + Select whether data in this resource will be publicly viewable. + +
+
+ + + +
+ {%endif%} +
+
+ +
+{%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%} +
+ {{profile_nav("roles", user_privileges)}} +

Create Role

+ + {{flash_me()}} + + {%if group_privileges_error is defined%} + {{display_error("Group Privileges", group_privileges_error)}} + {%else%} + {%if "group:role:create-role" in user_privileges%} +
+ Create Group Role +
+ + +
+ + {%for priv in group_privileges%} +
+ +
+ {%endfor%} + + +
+ {%else%} + {{display_error("Privilege", {"error":"PrivilegeError", "error_description": "You do not have sufficient privileges to create a new role."})}} + {%endif%} + {%endif%} +
+{%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%} + + + +{%endblock%} + +{%block content%} +
+ {{profile_nav("data", user_privileges)}} + + {{flash_me()}} + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + {%for dataset in datasets:%} + + + + + + + + {%else%} + + + + {%endfor%} + + +
+ + +
+{%endblock%} + +{%block js%} + + +{%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%} + + + +{%endblock%} + +{%block content%} +
+ {{profile_nav("data", user_privileges)}} + + {{flash_me()}} + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + {%for dataset in datasets:%} + + + + + + + + + {%else%} + + + + {%endfor%} + + +
+ + +
+{%endblock%} + +{%block js%} + + +{%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%} + + + +{%endblock%} + +{%block content%} + +
+ {{profile_nav("data", user_privileges)}} + + {{flash_me()}} + +
+ +
+ +
+ +
+ +
+ +
+ {%if dataset_type == "mrna"%} + mRNA: Search + {%else%} + + {{dataset_type}}: Search + + {%endif%} + + + + +
+ +
+ species:{{species_name}} AND + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + {%for trait in traits%} + + + + + + + + + + + + + + {%else%} + + + + {%endfor%} + + +
+ +
+ +{%endblock%} + +{%block js%} + + +{%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%} +
+ {{profile_nav("data", user_privileges)}} + + {{flash_me()}} + +
+
+ Search + {%if species_error is defined%} + {{display_error("Species", species_error)}} + {%elif species | length == 0%} + + +   + No list of species to select from + {%else%} +
+ + +
+ +
+ + +
+ + + {%endif%} +
+
+
+{%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)%} + +{{title}}: + + +  +{{the_error.error}}: +{{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%} +
+ {{profile_nav("group", user_privileges)}} + + {{flash_me()}} + + {%if group_error is defined%} +
+ {{display_error("Group", group_error)}} +
+ {%else%} +
+
+ {%if group_join_requests_error is defined %} + {{display_error("Join Requests", group_join_requests_error)}} + {%else%} + + Requests ({{group_join_requests | count}}) + + {%endif%} +
+
+ + + + + + + + + + + + + + + + + + + + {%for key,val in group.group_metadata.items()%} + + + + + {%endfor%} + + +
Group Information
NameMetadataActions
+ {{group.group_name}} + KeyValue + Edit + + Delete +
{{key.split("_") | map("capitalize") | join(" ")}}{{val}}
+
+
+ +
+ + + + + + + + + + + + + {%for user in users%} + + + + + + {%else%} + + + + {%endfor%} + +
Group Users
NameEmailActions
{{user.name}}{{user.email}} + Remove +
+ {%if user_error is defined%} + + +   + {{user_error.error}} + {{user_error.error_description}} + {%else%} + No users found for this group + {%endif%} +
+ +
+ {%endif%} + +
+{%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%} + +{%endblock%} +{%block content%} +
+ {{profile_nav("group", user_privileges)}} + +

Join or Create Group

+ + {{flash_me()}} + + {%if group_join_request is defined and group_join_request.exists %} +

+ +   + You have an active request to join a group. +

+ +

+ You cannot create a group, or request to join a new group until your + currently active request has been either accepted or rejected. +

+ {%else%} +

You can

+ + {%if groups | length > 0 %} +
+

+ 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.

+ +

You can only be a member of a single group.

+
+ +
+ Request to be added to group +
+ + +
+
+ +
+
+ +

or

+ {%else%} +

+ +   + There an currently no groups to join. +

+ {%endif%} + +
+

+ Creating a new group automatically makes you that group's administrator. +

+ +

You can only be a member of a single group.

+
+ +
+ Create a new group +
+ + + + Name of the group. + +
+
+ + + + A description to help identify the purpose/goal of the group + +
+
+ +
+
+ {%endif%} +
+{%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%} +
+ {{profile_nav("group", user_privileges)}} + + {{flash_me()}} + +
+
+ + + + + + + + + + + + + + + {%for request in requests%} + + + + + + + + + + {%else%} + + + + {%endfor%} + +
Join Requests
NameEmailRequest Date/TimeStatusMessageActions
{{request.name}}{{request.email}}{{datetime_string(request.timestamp)}}{{request.status}}{{request.message}} +
+ + +
+
+
+ + +
+
+ {%if error is defined %} + {{display_error("Join Requests", error)}} + {%else%} + No one has requested to join your group yet. + {%endif%} +
+
+
+
+{%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%} +
+ {{profile_nav("roles", user_privileges)}} +

Roles

+ + {{flash_me()}} + +
+
+

Your System-Level Roles

+
    + {%for role in roles %} +
  • + {{role.role_name}} +
  • + {%else%} +
  • +   + No roles attached to this user +
  • + {%endfor%} +
+
+ +
+

Group-Wide Roles

+ + {%if "group:role:create-role" in user_privileges%} + New Group Role + {%endif%} + + {%if group_roles_error is defined%} + {{display_error("Group Roles", group_role_error)}} + {%else%} + + + + + + + + + + {%for grole in group_roles%} + + + + + {%else%} + + + + {%endfor%} + +
Group Roles
Role NameActions
{{grole.role.role_name}} + + View + +
+ + +   + No group roles found +
+ {%endif%} +
+ +
+ +
+{%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%} +
+

Sign in here.

+ +
+
+ Sign in with Genenetwork + {{flash_me()}} +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+
+
+{%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%} +
+ {{profile_nav("masquerade", user_privileges)}} + + {{flash_me()}} + + {%if users_error is defined%} + {{display_error("Users", users_error)}} + {%else%} +
+
+
+ Masquerade As +
+ + +
+
+ +
+
+
+
+ {%endif%} +
+{%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)%} + + + +{%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%} +
+

Register User

+ +
+
+ Register User + {{flash_me()}} +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+
+
+{%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%} +
+ {{profile_nav("error", user_privileges)}} +

ERROR

+ + {{flash_me()}} + +
+ +
+
+
Error code
+
{{response.status}}[{{response.status_code}}]
+ +
URI
+
{{response.url}}
+ +
Content Type
+
{{response.content_type or "-"}}
+ +
{{response.content}}
+
{{response.content}}
+
+
+ +
+ +
+{%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%} +
+ {{profile_nav("resources", user_privileges)}} +

Resources

+ + {{flash_me()}} + +
+ + +
+ + + + + + + + + + {%for resource in resources %} + + + + + {%else%} + + + + {%endfor%} + +
Resources
NameCategory
+ + {{resource.resource_name}} + + {{resource.resource_category.resource_category_key}}
+   + + The user has no access to any resource. + +
+
+ +
+ +
+{%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%} +
+ {{profile_nav("roles", user_privileges)}} +

Role: {{role.role_name}}

+ + {{flash_me()}} + +
+
+
+
+ {{role.role_name}} +
+
+ + + + + + {%for privilege in role.privileges:%} + + + + + {%else%} + + + + + {%endfor%} + +
privilege iddescription
{{privilege.privilege_id}}{{privilege.privilege_description}}
+ +   + + No privileges found for this role. +
+
+ +
+
+ +
+ +
+{%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%} +
+ {{profile_nav("roles", user_privileges)}} +

View Group Role

+ + {{flash_me()}} + +
+
+

Role Details

+ {%if group_role_error is defined%} + {{display_error("Group Role", group_role_error)}} + {%else%} + + + + + + + + + + + {%for privilege in group_role.role.privileges%} + + + + + + {%endfor%} + +
Details for '{{group_role.role.role_name}}' Role
PrivilegeDescriptionAction
{{privilege.privilege_id}}{{privilege.privilege_description}} +
+ + +
+
+ {%endif%} +
+ +
+

Other Privileges

+ + + + + + + + + + + + {%for priv in group_privileges%} + + + + + + {%else%} + + + + {%endfor%} + +
Other Privileges not Assigned to this Role
PrivilegeDescriptionAction
{{priv.privilege_id}}{{priv.privilege_description}} +
+ + +
+
+ + +   + All privileges assigned! +
+
+ +
+ +
+{%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%} +
+ {{profile_nav("resources", user_privileges)}} +

Resources

+ + {{flash_me()}} + +
+ + {%if resource_error is defined %} + {{display_error("Resource", resource_error)}} + {%else%} +
+

Resource Details

+ + + + + + + + + + + + + + + + + + + +
Resource: {{resource.resource_name}}
NameCategoryActions
{{resource.resource_name}}{{resource.resource_category.resource_category_description}} +
+ +
+ {%if resource.public%} + + {%else%} + + {%endif%} +
+
+
+ Edit + + Delete +
+
+ +
+

Resource Data

+ + + + + {%if resource.resource_category.resource_category_key == "phenotype"%} + + + + {%endif%} + + + + + + + + {%for data_item in resource.resource_data:%} + + {%if resource.resource_category.resource_category_key == "phenotype"%} + + + + {%endif%} + + + + + {%else%} + + + + {%endfor%} + +
Resource Data
TraitDescriptionYearDataset NameFull NameActions
+ + {{data_item.PublishXRefId}} + + {{data_item.description}} + {%if data_item.PubMed_ID%} + + {{data_item.Year}} + + {%else%} + {{data_item.Year}} + {%endif%} + + + {{data_item.dataset_name}} + + {{data_item.dataset_fullname}} +
+ + + +
+
+ + +   + No linked data. +
+
+ + + + + +
+
+ +
+

Unlinked Data

+ + + + + {%if resource.resource_category.resource_category_key == "phenotype"%} + + + + {%endif%} + + + + + + + {%if unlinked_error is defined%} + {{display_error("Unlinked Data Error", unlinked_error)}} + {%else%} + {%for data_item in unlinked_data:%} + + {%if resource.resource_category.resource_category_key == "phenotype"%} + + + + {%endif%} + + + + + {%else%} + + +   + No data to link. + {%endfor%} + {%endif%} + +
Link Data
TraitDescriptionYearDataset NameDataset FullNameActions
+ + {{data_item.PublishXRefId}} + + {{data_item.description}} + {%if data_item.PubMed_ID%} + + {{data_item.Year}} + + {%else%} + {{data_item.Year}} + {%endif%} + + + {{data_item.dataset_name}} + + {{data_item.dataset_fullname}} +
+ + + + +
+
+
+ +
+

User Roles

+ {%if users_n_roles_error is defined%} + {{display_error("Users and Roles", users_n_roles_error)}} + {%else%} + + + + + + + + + + + + {%for user_row in users_n_roles%} + + + + + + + + {%for grole in user_row.roles%} + + + + + {%endfor%} + {%else%} + + + + {%endfor%} + +
User Roles
User EmailUser NameUser GroupAssigned Roles
{{user_row.user.email}}{{user_row.user.name}} + {{user_row.user_group.group_name}}RoleAction
+ + {{grole.role_name}} + + +
+ + + +
+
+ + +   + + There are no users assigned any role for this resource. + +
+ {%endif%} +
+ +
+

Assign

+ {%if group_roles_error is defined%} + {{display_error("Group Roles", group_roles_error)}} + {%elif users_error is defined%} + {{display_error("Users", users_error)}} + {%else%} +
+ +
+ + +
+
+ + + + {%for user in users%} + + {%endfor%} + +
+ + +
+ {%endif%} +
+ {%endif%} + +
+ +
+{%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%} +
+ {{profile_nav("dashboard", user_privileges)}} +

View User

+ + {{flash_me()}} + +
+
+ {%if user_details%} +

Name: {{user_details.name}}

+

E-Mail: {{user_details.email}}

+ {%if user_details.group%} +

Group:{{user_details.group.group_name}}

+ {%else%} +

+ +   + User is not a member of a group. +

+ + {%if group_join_request is defined and group_join_request.exists %} +

+ +   + You have an active join request to a group. +

+ {%else%} +

+ Join or Create group +

+ {%endif%} + + {%endif%} + {%else%} +

No details found.

+ {%endif%} +
+ +
+ +
+{%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 %} + + + + + +{% endblock %} + +{% block content %} + +{{ header("Mapping", + '{}: {}'.format(this_trait.name, this_trait.description_fmt)) }} + +
+
+

+ Pair Scan +

+
+
+
+
+
+

+ Results +

+ + + + + + + + + + + + + + + + + + + + + + {% for row in table_data %} + + + + + + + + + + {% endfor %} + +
Interval 1LODInterval 2
PositionFlanking MarkersPositionFlanking Markers
ProximalDistalProximalDistal
{{ row.pos1 }}{{ row.proximal1 }}{{ row.distal1 }}{{ row.lod }}{{ row.pos2 }}{{ row.proximal2 }}{{ row.distal2 }}
+
+
+ +{% endblock %} + +{% block js %} + + + + + + + + + + + + +{% 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 %} + + +
+
+
+
+ + + +

ERROR

+ +

+ This error is not what we wanted to see. Unfortunately errors + are part of all software systems and we need to resolve this + together. +

+

+ It is important to report this ERROR so we can fix it for everyone. +

+ +

+ 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 new + issue or E-mail this full page to one of the developers + directly. +

+
+ +

+ GeneNetwork error:
+ {{message}} +

+ + {%if command_id %} +

+ Please provide the following information to help with + troubleshooting:
+ Command ID: {{command_id}} +

+ {%endif%} + +

+ To check if this already a known issue, search the + issue + tracker. +

+ + Toggle full stack trace +
+
+	  GeneNetwork {{ version }} {% for line in stack %} {{ line }}
+	  {% endfor %}
+	
+
+
+
+
+ + +{% 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%} + +{%endblock%} + +{%block content%} + +
+
+

Computing partial correlations...

+ Image indicating computation of partial correlations is ongoing +
+
+{%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%} + + + + +{%endblock%} + +{%block content%} +
+

+ Primary Trait

+ + {{primary["dataset_type"]}}/{{primary["trait_name"]}} + [{{primary["symbol"] }} on Chr {{primary["chr"]}} @ {{primary["mb"]}}]: + {{primary["description"]}} + --- FROM: {{primary["dataset_name"]}} +

+

Control Traits

+ {%for trait in controls:%} + + {{trait["dataset_type"]}}/{{trait["trait_name"]}} + [{{trait["symbol"] }} on Chr {{trait["chr"]}} @ {{trait["mb"]}}]: + {{trait["description"]}} + --- FROM: {{trait["dataset_name"]}}
+ {%endfor%} +

+ +
+ {%if dataset_type == "Publish":%} + + + + + + + + + + + + + + + + + + + + {%for idx, trait in enumerate(correlations, start=1):%} + + + + + + + + + + + + + + + {%endfor%} + +
+ IndexRecordPhenotypeAuthorsYearNPartial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}p(partial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}){%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}p({%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})delta {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}
+ + {{idx}} + + {{trait["trait_name"]}} + + + {{trait["post_publication_description"]}}{{trait["authors"]}}{{trait["year"]}}{{trait["noverlap"]}} + {{format_number(trait.get("partial_corr"))}} + + {{format_number(trait.get("partial_corr_p_value"))}} + + {{format_number(trait.get("corr"))}} + + {{format_number(trait.get("corr_p_value"))}} + + {{format_number(trait.get("delta"))}} +
+ {%endif%} + + {%if dataset_type == "Geno":%} + + + + + + + + + + + + + + + + + + + {%for idx, trait in enumerate(correlations, start=1):%} + + + + + + + + + + + + + + {%endfor%} + +
IndexLocusChrMegabaseNPartial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}p(partial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}){%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}p({%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})delta {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}
+ + {{idx}} + + {{trait["trait_name"]}} + + {{trait["chr"]}}{{trait["mb"]}}{{trait["noverlap"]}} + {{format_number(trait.get("partial_corr"))}} + + {{format_number(trait.get("partial_corr_p_value"))}} + + {{format_number(trait.get("corr"))}} + + {{format_number(trait.get("corr_p_value"))}} + + {{format_number(trait.get("delta"))}} +
+ {%endif%} + + {%if dataset_type == "ProbeSet":%} + + + + + + + + + + + + + + + + + + + + + + + + + + + {%for idx, trait in enumerate(correlations, start=1):%} + + + + + + + + + + + + + + + + + + + + + + {%endfor%} + +
IndexRecordGene IDHomologene IDSymbolDescriptionChrMegabaseMean ExprNSample Partial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}Sample p(partial {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})Sample {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}Sample p({%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})delta {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}Lit CorrTissue {%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%}Tissue p({%if "spearman" in (method | lower):%}rho{%else:%}r{%endif%})
+ + {{idx}} + + {{trait["trait_name"]}} + + {{trait["geneid"]}}{{trait["homologeneid"]}}{{trait["symbol"]}}{{trait["description"]}}{{trait["chr"]}}{{trait["mb"]}}{{trait["mean_expr"]}}{{trait["noverlap"]}} + {{format_number(trait.get("partial_corr"))}} + + {{format_number(trait.get("partial_corr_p_value"))}} + + {{format_number(trait.get("corr"))}} + + {{format_number(trait.get("corr_p_value"))}} + + {{format_number(trait.get("delta"))}} + + {{format_number(trait.get("l_corr"))}} + + {{format_number(trait.get("tissue_corr"))}} + + {{format_number(trait.get("tissue_p_value"))}} +
+ {%endif%} + +
+
+{%endblock%} + +{%block js%} +{%if step == "select-corr-method":%} + + +{%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%} + + + + + +{%endblock%} + +{%block content%} +
+

+ Primary Trait

+ + {{primary["dataset_type"]}}/{{primary["trait_name"]}} + [{{primary["symbol"] }} on Chr {{primary["chr"]}} @ {{primary["mb"]}}]: + {{primary["description"]}} + --- FROM: {{primary["dataset_name"]}} +

+

Control Traits

+ {%for trait in controls:%} + + {{trait["dataset_type"]}}/{{trait["trait_name"]}} + [{{trait["symbol"] }} on Chr {{trait["chr"]}} @ {{trait["mb"]}}]: + {{trait["description"]}} + --- FROM: {{trait["dataset_name"]}}
+ {%endfor%} +

+ + + + + + + + + + + + {%if method == "spearmans":%} + + + + + + {%else:%} + + + + + + {%endif%} + + + + + {%for idx, trait in enumerate(pcorrs, start=1):%} + + + + + + + + + + + + + + + {%else:%} + + + + {%endfor%} + +
_IndexDatabaseRecordSymbolDescriptionNPartial rhop(partial rho)rhop(rho)delta rhoPartial rp(partial r)rp(r)delta r
+ + {{idx}}{{trait["dataset_name"]}} + + {{trait["trait_name"]}} + + {{trait["symbol"]}}{{trait["description"]}}{{trait["noverlap"]}}{{format_number(trait["partial_corr"])}}{{format_number(trait["partial_corr_p_value"])}}{{format_number(trait["corr"])}}{{format_number(trait["corr_p_value"])}}{{format_number(trait["delta"])}}
+ No correlations were computed +
+ +
+{%endblock%} + +{%block js%} + +{%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%} + + + + + +{%endblock%} + +{%block content%} +
+
+ {%with messages = get_flashed_messages(with_categories=true)%} + {%if messages:%} +
    + {%for category, message in messages:%} +
  • {{message}}
  • + {%endfor%} +
+ {%endif%} + {%endwith%} + + +

Partial Correlation

+
Please select one primary trait, one to three control traits, and at least one target trait.
+
+ + + + + + + + + + + + + + + + + + + + {%for trait in traits:%} + + + + + + + + + + + + + + {%endfor%} + +
Primary (X)Controls (Z)Targets (Y)IgnoredDatasetTrait IDSymbolDescriptionLocationMeanMax LRSMax LRS Location Chr and Mb
+ + + + + + + + {{trait.get("dataset", "_")}} + {{trait.get("trait_name", "_")}}{{trait.get("symbol", "_")}}{{trait.get("description", "_")}}{{trait.get("location", "_")}}{{trait.get("mean", "_")}}{{trait.get("lrs", "_")}}{{trait.get("lrs_location", "_")}}
+ +
+

Compute partial correlations for target selected above:

+ + + +
+ +

OR

+

Compute partial correlation for each trait in the database below:

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+{%endblock%} + +{%block js%} + +{%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 @@ + + + + + + + + + + +
+

Scree Plot

+
Review more on scree plots.
+
+
+ + + + + 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 %} + +{% endblock %} + +{% block title %}Phenotype: {{ name }}{% endblock %} + +{% block content %} + +{% set published_p = "http://rdf.ncbi.nlm.nih.gov/pubmed" in metadata.references.id %} + +

+ Phenotype: {{ metadata.traitName }} ({{ metadata.abbreviation }}) +

+ +
+ + + + + + + + + + + + + + + + {% if metadata.creator %} + + + + + {% if metadata.references.id %} + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + {% if metadata.locus %} + + + + + {% endif %} + {% if metadata.references.id %} + + + + + {% endif %} +
Species{{ metadata.species or "N/A" }}
Group{{ metadata.group or "N/A" }}
Phenotype{{ metadata.description or "N/A"}}
Authors + {% if metadata.creator is iterable %} + {{ metadata.creator |join(", ") }} + {% for creator in metadata.creator %} + {{ creator }} + {% endfor %} + {% else %} + metadata.creator + {% endif %} + {% endif %} +
+ Publication + {% if published_p == False %} + (unpublished) + {% endif %} + + + {% if metadata.references.title %} + {{ metadata.references.title }}. + {% endif %} + + {% 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 %} + + + + {% if published_p %} + PubMed + {% else %} + GN RDF Page + {% endif %} + + +
Database + {% for database in metadata.dataset %} + {% set dataset_url = url_for('get_dataset', name=database.identifier)%} + {{ database.prefLabel }}
+ {% endfor %} +
Mean{{ metadata.mean or "N/A"}}
Peak -logP{{ metadata.lodScore or "N/A"}}
Effect Size{{ metadata.additive or "N/A"}}
Peak LocationChr{{ metadata.locus.chromosome }}: {{ metadata.locus.mb }}
Resource Links + + {% if published_p %} + PubMed + {% else %} + GN RDF Page + {% endif %} + +
+
+ + +{% 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 %} + +{% endblock %} + +{% block content %} + + +
+ {{ rendered_markdown|safe }} + +
+{% 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 %} + +{% endblock %} + +{% block title %}Title: {{metadata.title}}{% endblock %} + +{% block content %} + + + +
+ {% if metadata == {} %} +

We appreciate your interest, but unfortunately, we don't have any additional information available for: {{ name }}. If you have any other questions or need assistance with something else, please feel free to reach out to us.

+ {% else %} + + {% endif %} + +
+ +{% 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 %} + +{% endblock %} +{% block content %} + +
+ {{ rendered_markdown|safe }} +
+ +{% 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 @@ +< + + + + + + + + + + + +
+

hello there

+
+
+ +
+ +
+
+ + + + \ 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 %} + +{% endblock %} +{% block content %} + + {{ header("Error") }} + +
+ +

You entered at least one incorrect search command, or your search term was not applicable to the dataset type.

+

Some search terms may not be applicable to Phenotype or Genotype datasets (RIF, etc)

+
+ +
+ + + +{% 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 @@ +< + + + + + + + + + + + +
+
+

+ this is the title +

+
+
+
+ +
+
+
+ + + + \ 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 %} + + + + + + + +{% endblock %} +{% block content %} + +
+ + +
+ + +

We searched {{ dataset.fullname }} +
+ to find all records + {% if go_term is not none %} + with Gene Ontology ID GO:{{ go_term }}. + {% else %} + {% for word in search_terms %} + {% if word.key|lower == "rif" %} + with GeneRIF containing {{ word.search_term[0] }}{% if loop.last %}{% else %} and {% endif %} + {% elif word.key|lower == "go" %} + with Gene Ontology ID {{ word.search_term[0] }}{% if loop.last %}{% else %} and {% endif %} + {% elif word.key|lower == "wiki" %} + with GeneWiki containing {{ word.search_term[0] }}{% if loop.last %}{% else %} and {% endif %} + {% elif word.key|lower == "mean" %} + with mean between {{ word.search_term[0] }} and {{ word.search_term[1] }}{% if loop.last %}{% else %} and {% endif %} + {% elif word.key|lower == "range" %} + with RANGE between {{ word.search_term[0] }} and {{ word.search_term[1] }}{% 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 %} {{ word.search_term[0] }}{% if loop.last %}{% else %} and {% endif %} + {% elif word.search_term|length == 2 %} + with {{ word.key|upper }} between {{ word.search_term[0] }} and {{ word.search_term[1] }}{% if loop.last %}{% else %} and {% endif %} + {% elif word.search_term|length == 3 %} + with {{ word.key|upper }} between {{ word.search_term[0] }} and {{ word.search_term[1] }} on chromosome {{ word.search_term[2] }}{% if loop.last %}{% else %} and {% endif %} + {% elif word.search_term|length == 4 %} + with {{ word.key|upper }} between {{ word.search_term[0] }} and {{ word.search_term[1] }} on chromosome {{ word.search_term[3] }} with an exclusion zone of {{ word.search_term[2] }} Mb + {% elif word.search_term|length == 5 %} + with {{ word.key|upper }} between {{ word.search_term[0] }} and {{ word.search_term[1] }} on chromosome {{ word.search_term[2] }} between {{ word.search_term[3] }} and {{ word.search_term[4] }} Mb{% if loop.last %}{% else %} and {% endif %} + {% endif %} + {% elif word.key|lower == "position" or word.key|lower == "mb" %} + with target genes on chromosome {% if (word.search_term[0]|lower).split('chr')|length > 1 %}{{ (word.search_term[0]|lower).split('chr')[1] }}{% else %}{{ word.search_term[0] }}{% endif %} between {{ word.search_term[1] }} and {{ word.search_term[2] }} Mb{% if loop.last %}{% else %} and {% endif %} + {% else %} + {% if word.search_term[0] == "*" %} in the dataset.{% else %}{% if loop.first %}that match: {% endif %}{{ word.search_term[0] }}{% if loop.last %}{% else %} and {% endif %}{% endif %} + {% endif %} + {% endfor %} + {% endif %} +
+ {% if results|count > 0 %} + {{ results|count }} record{% if results|count > 1 %}s{% else %}{% endif %} found + {% else %} + No (0) records found for this search. Modify your search, check target dataset, or use Search All above. + {% endif %} +

+ + {% if results|count > 0 %} + {% if go_term is not none %} +

The associated genes include:

{% for word in search_terms %}{{ word.search_term[0] }}{% endfor %}

+ {% endif %} + +
+ {% if too_many_results %} +

Your search generated over 50000 results. Please modify your search to generate 50000 or fewer matches.

+ {% else %} +
+
+ + + + + {% include 'tool_buttons.html' %} + +
+
+ +
+
+
+
+ + + + + + {% if dataset.accession_id %} + + {% endif %} + + + + + + + + +
+ +
+ {% if dataset.type != 'Geno' %} +
+ Show/Hide Columns:   + {% if dataset.type == 'ProbeSet' %} + + + + + + + + {% elif dataset.type == 'Publish' %} + + + + + + + + {% endif %} +
+ {% endif %} +
+ + + + +

Loading...
+
+
+ {% endif %} + {% else %} +
+ + {% endif %} +
+
+ + + +{% endblock %} + +{% block js %} + + + + + + + + + + + + + + + + + +{% 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 %} + + + +{% endblock %} +{% block content %} + +
+

Group Privileges

+
+
+ +
+ +
+

Data and Metadata Privileges

+ + + + + + + + + + + + + + + + + + + + + + + +
No-AccessViewEdit
Data:
Metadata:
+
+

Admin Privileges

+ + + + + + + + + + + + + + + + + +
Not AdminEdit AccessEdit Admins
Admin:
+
+
+
+ + + +{% endblock %} + +{% block js %} + + +{% 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 @@ +Embedded Image \ 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 %} + + + + + + + + + + + +{% endblock %} + +{% block content %} + + {{flash_me()}} + {%if "group:resource:view-resource" in trait_privileges or "system:resource:public-read" in trait_privileges%} +
+

Trait Data and Analysis for {{ this_trait.display_name }}

+ {% if this_trait.dataset.type != 'Publish' %} +

+ {% set trait_description = this_trait.description_fmt[0]|upper + this_trait.description_fmt[1:]|safe %} + {% if trait_description|length < 100 %} + {{ trait_description }} + {% else %} + {{ trait_description[:99] }}... (Show More) + {% endif %} +

+ {% endif %} + +
+
+ + {% for key in hddn %} + + {% endfor %} +
+ + + + + + + + + + +
+
+
+
+

+ Details and Links +

+
+
+
+ {% include 'show_trait_details.html' %} +
+
+
+
+
+

+ Statistics +

+
+
+
+ {% include 'show_trait_statistics.html' %} +
+
+
+
+
+

+ Transform and Filter Data +

+
+
+
+ {% include 'show_trait_transform_and_filter.html' %} +
+
+
+
+
+
+

+ Calculate Correlations +

+
+
+
+ {% include 'show_trait_calculate_correlations.html' %} +
+
+
+
+
+

+ Mapping Tools +

+
+
+
+ {% include 'show_trait_mapping_tools.html' %} +
+
+
+
+
1100)%}style="min-width: {{trait_table_width|int + 30}}px;"{% endif %}> +
+

+ Review and Edit Data +

+
+
+
+ {% include 'show_trait_edit_data.html' %} +
+
+
+
+ {% include 'show_trait_progress_bar.html' %} +
+
+
+ {%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%} + + + +{% endblock %} + +{% block js %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% 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 @@ +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + Chr:     + Mb:  to  + +
+
+
+
+ +
+ + + +
+ + +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ +
+
+
+
+
+
+
+
Sample Correlation
+
The Sample Correlation + is computed + between trait data and any + other traits in the sample database selected above. Use + Spearman + Rank + when the sample size is small (<20) or when there are influential outliers.
+
Literature Correlation
+
The Literature Correlation + (Lit r) between + this gene and all other genes is computed
+ using the + Semantic Gene Organizer + and human, rat, and mouse data from PubMed. + Values are ranked by Lit r, but Sample r and Tissue r are also displayed.
+ More on using Lit r
+
Tissue Correlation
+
The Tissue Correlation + (Tissue r) + estimates the similarity of expression of two genes + or transcripts across different cells, tissues, or organs + (glossary). + Tissue correlations + are generated by analyzing expression in multiple samples usually taken from single cases.
+ Pearson and Spearman Rank correlations have been + computed for all pairs of genes using data from mouse samples.
+
+
+
+
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 @@ + + + + + + {% if this_trait.dataset.type == 'Publish' %} + + + + + + + + + + + + + + + + + {% elif this_trait.dataset.type == 'ProbeSet' %} + + + + + {% endif %} + {% if this_trait.dataset.type == 'ProbeSet' %} + {% if this_trait.symbol != None %} + + + + + {% endif %} + + + + + {% if this_trait.alias_fmt != "Not Available" %} + + + + + {% endif %} + {% endif %} + {% if this_trait.dataset.type != 'Publish' %} + + + + + {% endif %} + {% if ncbi_summary != None and ncbi_summary != "" %} + + + + + {% endif %} + + + + + {% if this_trait.probe_set_specificity %} + + + + + {% endif %} + {% if this_trait.pubmed_id or this_trait.geneid or this_trait.omim or this_trait.symbol %} + + + + + {% endif %} +
Group{{ this_trait.dataset.group.species[0]|upper }}{{ this_trait.dataset.group.species[1:] }}: {{ this_trait.dataset.group.name }} group
Phenotype
{{ this_trait.description_fmt }}
Authors
{{ this_trait.authors }}
Title
{{ this_trait.title }}
Journal{{ this_trait.journal }} ({% if this_trait.pubmed_id %}{{ this_trait.year }}{% else %}{{ this_trait.year }}{% endif %})
Tissue{{ this_trait.dataset.tissue }}
Gene Symbol{{ this_trait.symbol }}
AliasesWikidata: {{ this_trait.wikidata_alias_fmt|replace(",",";") }}
GeneNetwork: {{ this_trait.alias_fmt|replace(",",";") }}
Location{{ this_trait.location_fmt }}
Summary{{ ncbi_summary }}
Database + + {{ dataset.fullname }} + +
+ + GN2 Link: {{ dataset.fullname }} + +
Target Score + + BLAT Specificity + : + {{ "%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 %} +
Resource Links + {% if pubmed_link %} + + PubMed + + {% endif %} + {% if ncbi_gene_link %} + + Gene + +    + {% endif %} + {% if omim_link %} + + OMIM + +    + {% endif %} + {% if genemania_link %} + + GeneMANIA + +    + {% endif %} + {% if protein_atlas_link %} + + Protein Atlas + +    + {% endif %} + {% if open_targets_link %} + + Open Targets + +    + {% endif %} + {% if homologene_link %} + + HomoloGene + +    + {% endif %} + {% if this_trait.symbol %} + + {% if rgd_link %} + + Rat Genome DB + +    + {% endif %} + + GTEx Portal + +    + {% if phenogen_link %} + + PhenoGen + +    + {% endif %} + {% if genebridge_link %} + + GeneBridge + + {% endif %} + {% endif %} +
+ {% if ucsc_blat_link %} + + UCSC + +    + {% endif %} + {% if biogps_link %} + + BioGPS + +    + {% endif %} + {% if string_link %} + + STRING + +    + {% endif %} + {% if panther_link %} + + PANTHER + +    + {% endif %} + {% if gemma_link %} + + Gemma + +    + {% endif %} + {% if aba_link %} + + ABA + +    + {% endif %} + {% if ebi_gwas_link %} + + EBI GWAS + +    + {% endif %} + {% if wiki_pi_link %} + + Wiki-Pi + +    + {% endif %} + {% if uniprot_link %} + + UniProt + +    + {% endif %} +
+ +
+
+ + {% if this_trait.dataset.type == 'ProbeSet' or this_trait.dataset.type == 'Geno' %} + {% if this_trait.symbol != None %} + + {% endif %} + {% if UCSC_BLAT_URL != "" %} + + {% endif %} + {% if this_trait.symbol != None %} + + {% if dataset.group.species == "mouse" or dataset.group.species == "rat" %} + + {% endif %} + {% endif %} + {% if show_probes == "True" %} + + {% endif %} + {% endif %} + + {% if this_trait.dataset.type == 'Publish' %} + + {% endif %} + + {% if this_trait.dataset.type == 'ProbeSet' %} + + {% endif %} +
+ {%if "group:resource:edit-resource" in trait_privileges%} +
+ +
+ {% endif %} +
+ 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 @@ +
+ {% for sample_type in sample_groups %} +
+ {% if loop.index == 1 and (sample_groups[0].se_exists or has_num_cases or sample_groups[0].attributes|length > 0) %} + Show/Hide Columns: +
+ {% if sample_groups[0].se_exists %} + + {% if has_num_cases %} + + {% set attr_start_pos = 7 %} + {% else %} + {% set attr_start_pos = 6 %} + {% endif %} + {% else %} + {% if has_num_cases %} + + {% 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 %} + + {% endfor %} + {% endif %} +
+
+ {% endif %} +
+ +
+
+ + + +
+
+
+            
+  # 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 = "#")
+            
+          
+
+
+ Edit CaseAttributes + {% set outer_loop = loop %} +
+
+

{{ sample_type.header }}

+
+
+ + + + +

Loading...
+
+
+
+ {% endfor %} + +
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%} + + + + + + + + + + + +{%endblock%} +{%block content%} +
+ {{flash_me()}} +
+{%endblock%} 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 @@ +
+ {% if dataset.group.mapping_names|length > 0 %} +
+
+ + + +
+ {% for mapping_method in dataset.group.mapping_names %} + {% if mapping_method == "GEMMA" %} +
+
+
+ +
+ +
+
+ {% if genofiles and genofiles|length>0 %} +
+ +
+ +
+
+ {% endif %} +
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+
+ + + +
+ +
+
+
+ +
+ +
+
+
+
+ {% elif mapping_method == "QTLReaper" %} +
+
+
+ +
+ +
+
+ {% if genofiles and genofiles|length>0 %} +
+ +
+ +
+
+
+ +
+ +
+
+ {% else %} +
+ +
+ +
+
+ {% endif %} +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+
+ {% elif mapping_method == "R/qtl" %} +
+
+
+ +
+ +
+
+ {% if genofiles and genofiles|length > 0 %} +
+ +
+ +
+
+
+ +
+ +
+
+ {% else %} +
+ +
+ +
+
+ {% endif %} +
+ +
+ +
+
+ {% if sample_groups[0].attributes|length > 0 %} +
+ +
+ + +
+
+ {% endif %} +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + +
+
+
+ +
+
+ + + +
+ +
+
+
+ +
+ +
+
+
+
+
+
+ {% if genofiles and genofiles|length > 0 %} +
+ +
+ +
+
+ {% endif %} +
+ +
+ + + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+ +
+
+ + +
+ +
+
+
+ +
+ +
+
+
+
+ {% endif %} + {% endfor %} +
+
+
+
+
+
+ {% for mapping_method in dataset.group.mapping_names %} + {% if mapping_method == "GEMMA" %} +
GEMMA
+
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 (PMID: 2453419, and GitHub code).
+ {% elif mapping_method == "R/qtl" %} +
R/qtl (version 1.44.9)
+
R/qtl 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 (PMID: 30591514) in 2023—a version that handles complex populations with admixture and many haplotypes.
+
Pair Scan (R/qtl v 1.44.9)
+
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
+ {% elif mapping_method == "QTLReaper" %} +
Haley-Knott Regression
+
HK regression (QTL Reaper) is a fast mapping method with permutation that works well with F2 intercrosses and backcrosses (PMID 16718932), but is not recommended for admixed populations, advanced intercrosses, or strain families such as the BXDs (QTL Reaper code).
+ {% endif %} + {% endfor %} +
+
+ More information on R/qtl mapping models and methods can be found here. +
+
+
+ + {% else %} + Mapping options are disabled for data not matched with genotypes. + {% endif %} +
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 @@ + + + \ 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 @@ +
+
+ +
+
+
+
+
+
+
+
+ {% if sample_groups|length != 1 %} + Select Group: + +

+ {% endif %} +
+
+
+
+
+ {% if num_values < 256 %} +
+
+ {% if sample_groups|length != 1 %} + Select Group: + + {% endif %} + +
+ + +
+
+
+
+
+
+ {% endif %} +
+
+ {% if sample_groups|length != 1 %} + Select Group: + +
+
+ {% endif %} + +
+
+
+
+
+
+ More about Normal Probability Plots and more + about interpreting these plots from the glossary +
+
+
+
+
+
+
+
+
+
+
+
+ +
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 @@ +
+
+

Edit or delete values in the Trait Data boxes, and use the + Reset option as + needed. +

+
+ + + + +
+ + {% if categorical_attr_exists == "true" %} +
+ + + + + +
+ {% endif %} + {% if study_samplelists|length > 0 %} +
+ + + {% if sample_groups|length != 1 %} + + {% endif %} + +
+ {% endif %} +
+ + {% if (numerical_var_list|length > 0) or js_data.se_exists %} + + {% endif %} + + + + +
+
+ + + + + + + +
+
+ + +
+
+
+
+
+

Outliers highlighted in + orange + can be hidden using + the Hide Outliers button. +

+ +

Samples with no value (x) can be hidden by clickingHide No Value button.

+
+
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 %} + + + + +{% endblock %} +{% block content %} + +
+

Variant Browser Info

+
+
+
+ + + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
Or select
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ Non-redundant SNP Only +
+
+
+ +
+ Different Alleles Only +
+
+
+ +
+ +
+ {% if table_rows is defined %} + + + + + {% if header_fields|length == 2 %} + {% for header in header_fields[0] %} + + {% endfor %} + {% for strain in header_fields[1] %} + + {% endfor %} + {% else %} + {% for header in header_fields %} + + {% endfor %} + {% endif %} + + + + + +
{{ header }}{% for letter in strain|reverse %}
{{ letter }}
{% endfor %}
{{ header }}

Loading...
+ {% endif %} +
+
+ +{% endblock %} +{% block js %} + + + + + + + + + +{% 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"%} + +
+

Startup Error

+ +

+ The application could not start due to the missing configuration settings + below: +

    + {%for setting in error_value.missing%} +
  • {{setting}}
  • + {%endfor%} +
+

+
+{%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 %} + +
+
+ + {{ flash_me() }} + +
+
+
+
+

Introduction

+
+

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.

+

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 full description of the genetic data collected as part of the WebQTL project.

+
+
+
+
+
+

About Your Data

+
+

You can open a separate window giving the number of strains for each data set and sample data.

+

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.

+
+
+
+
+
+
+

Trait Submission Form

+
+
+

1. Choose Species and Group:

+
+
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+

2. Enter Trait Data:

+

File uploading isn't enabled yet, but is coming soon.

+
+
+ +
+
+
+

+ Paste or Type Multiple Values: 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 sample data for the correct format. +

+ +
+
+
+ + +
+
+
+

3. Enable Use of Trait Variance:

+
+ +
+
+
+

+ Name Your Trait: (optional) +

+ +
+
+
+ + +
+
+
+
+
+
+
+ +{%endblock%} + +{% block js %} + + +{% 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 %} + + + + + + + + +{% endblock %} + +{% block content %} + +
+

Correlation Results for {{target_dataset}} against {{this_trait}} for the top {{return_results}} Results

+
+
+

Toggle Columns

+ + + + + +
+ + + + + + + + + + + + + + +
indextrait_nameSample rSample r(p)NTissue rTissue r(p)Lit r
+ +{% endblock %} + +{% block js %} + + + + + + + + + + + + + + + + +{% 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 @@ + + + + + + + + + + + + + + + + + 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 %} + + + GeneNetwork Webinar Series, Tutorials and Short Video Tours + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Title/DescriptionPresentation

Introduction to Quantitative Trait Loci (QTL) Analysis

+

Goals of this webinar (trait variance to QTL):

+
    +
  • Define quantitative trait locus (QTL)
  • +
  • Explain how genome scans can help find QTL
  • +
+

Presented by:
+Dr. Saunak Sen
+Professor and Chief of Biostatistics
+Department of Preventative Medicine
+University of Tennessee Health Science Center +

+

Link to course material +

+

Mapping Addiction and Behavioral Traits and Getting at Causal Gene Variants with GeneNetwork

+

Goals of this webinar (QTL to gene variant):

+
    +
  • Demonstrate mapping a quantitative trait using GeneNetwork (GN)
  • +
  • Explore GN tools to identify genes and genetics variants related to a QTL
  • +
+

Presented by:
+Dr. Rob Williams
+Professor and Chair
+Department of Genetics, Genomics, and Informatics
+University of Tennessee Health Science Center +

Link to course material +

Data structure, disease risk, GXE, and causal modeling

+ +

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. + +

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.

+ +

Presented by:
+Dr. Rob Williams
+Professor and Chair
+Department of Genetics, Genomics, and Informatics
+University of Tennessee Health Science Center +

+ + +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Title/DescriptionPresentation

Introduction to Gene Network

+

Please note that this tutorial is based on GeneNetwork v1 + +

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. + +

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.

+ +

Presented by:
+Dr. Rob Williams
+Professor and Chair
+Department of Genetics, Genomics, and Informatics
+University of Tennessee Health Science Center +

+ + +
+ +

How to search in GeneNetwork


Presented by Rob Williams University of Tennessee Health Science Center
+

GeneNetwork.org: genetic analysis for all neuroscientists


Presented by David G. Ashbrook Assistant Professor University of Tennessee Health Science Center +
+
+ + +
+
+ + + + + + + + + + + + + + + + +
TitleSpeakerVideo link
Diallel Crosses, Artificial Intelligence, and Mouse Models of Alzheimer’s DiseaseDavid G. Ashbrook
Assistant Professor
University of Tennessee Health Science Center
YouTube link
+ +
+ + +
+ +
+
+ +
+ +
+ + + +
+ + + + + + + + + + + +{% 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%} + + + + + +{%endblock%} + +{%block content%} +
+

View Diff

+ + {{flash_me()}} + +
+
+

Changes

+
+
+ {%set the_diff = diff.json_diff_data.diff%} + {%if the_diff.Additions | length %} +

Additions

+
+ + + + + {{item.Current}} +
+ {%endif%} + {%if the_diff.Modifications | length %} +

Modifications

+ {%for item in the_diff.Modifications%} +
+ - + {{item.Original}} + + + {{item.Current}} +
+ {%endfor%} + {%endif%} + {%if the_diff.Deletions | length %} +

Deletions

+
+ + + {{item.Original}} + - + {{item.Current}} +
+ {%endif%} +
+ +
+ + + +
+ + + + +
+{%endblock%} + +{%block js%} + +{%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%} + + + + + +{%endblock%} + +{%block content%} +
+

View Diff

+ + {{flash_me()}} + +

+ + + {{error.status_code}}: {{error["error"]}} + {{error.error_description}} +

+{%endblock%} + +{%block js%} + +{%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") }} + +
+ {% if chip_name == "mixed" %} +

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.

+ {% elif chip_name == "not_microarray" %} +

You need to select at least one microarray trait to submit. + {% elif '_NA' in chip_name %} +

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.

+ {% else %} +

Sorry, an error occurred while submitting your traits to WebGestalt.

+ {% endif %} +
+ {% else %} +
+

Opening WebGestalt...

+
+
+ {% for key in hidden_vars %} + + {% endfor %} +
+ {% endif %} +{% endblock %} +{% block js %} +{% if wrong_input == "False" %} + +{% 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 %} +
+

WGCNA Results

+ Analysis found {{results['nmod']}} modules when scanning {{results['nphe']}} phenotypes, measured on {{results['nstr']}} strains.
+ Additional parameters settings: +
    +
  • Soft thresholds checked = {{results['requestform']['SoftThresholds']}}
  • +
  • Power used for this analysis = {{results['Power']}}
  • +
  • TomType = {{results['requestform']['TOMtype']}}
  • +
  • Minimum module size = {{results['requestform']['MinModuleSize'] }}
  • +
  • mergeCutHeight = {{results['requestform']['mergeCutHeight'] }}
  • +
+ +

Soft threshold table

+ + + {% for r in range(powers[0][0]|length) %} + {% if powers[0][1][r] > 0.85 %} + + {% elif powers[0][1][r] > 0.75 %} + + {% else %} + + {% endif %} + {% for c in range(powers[0]|length) %} + + {% endfor %} + + {% endif %} + + {% endfor %} +
PowerSFT.R.sqslopetruncated.R.sqmean.kmedian.kmax.kAnalysis
{{powers[0][c][r]|round(3)}} + {% if powers[0][1][r] > 0.75 %} +
+

WGCNA module plot

+ + Embedded Image + + +

Phenotype / Module table

+ + + {% for r in range(results['nphe']) %} + + + + + {% endfor %} +
PhenotypeModule
{{results['phenotypes'][r][0]}}{{results['network'][0][r]}}
+ +

Module eigen genes

+ + + {% for m in range(results['nmod']) %} + + {% endfor %} + + {% for r in range(results['nstr']) %} + + + {% for m in range(results['nmod']) %} + + {% endfor %} + + {% endfor %} +
Phenotype
{{results['strains'][r][0]}}{{results['network'][2][m][r]}}
+
+{% 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 %} + + + + + +
+
+

WGCNA analysis parameters

+ {% if request.form['trait_list'].split(",")|length < 4 %} + {% else %} +
+ +
+ +
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ +
+ +
+
+
+ +
+
+ + + +
+ {% endif %} +
+
+
+
+
+
+ + + + + + +{% 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:%} +
+ + +
+{%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": "".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": "" + trait.name + "", + "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/") +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/") +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/") +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/") +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/') +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//") +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/") +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/") +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/", 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/", 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/", methods=('GET',)) +@app.route("/phenotypes//", 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/", 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//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//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//diff//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 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 +""" +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)) -- cgit v1.2.3