diff --git a/.gitmodules b/.gitmodules index 05f46a77aa..1e36ca9171 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,3 +37,6 @@ [submodule "anvio/data/interactive/lib/JavaScript-MD5"] path = anvio/data/interactive/lib/JavaScript-MD5 url = https://github.com/blueimp/JavaScript-MD5.git +[submodule "anvio/data/interactive/lib/fabric.js"] + path = anvio/data/interactive/lib/fabric + url = https://github.com/fabricjs/fabric.js.git diff --git a/anvio/__init__.py b/anvio/__init__.py index 7665eabfbe..fe7a55d76b 100644 --- a/anvio/__init__.py +++ b/anvio/__init__.py @@ -166,6 +166,12 @@ def TABULATE(table, header, numalign="right", max_width=0): 'required': True, 'help': "Anvi'o structure database."} ), + 'genome-view-db': ( + ['-E', '--genome-view-db'], + {'metavar': "GENOME_VIEW_DB", + 'required': True, + 'help': "Anvi'o genome view database."} + ), 'only-if-structure': ( ['--only-if-structure'], {'default': False, @@ -3563,7 +3569,8 @@ def set_version(): t.genomes_storage_vesion, \ t.structure_db_version, \ t.metabolic_modules_db_version, \ - t.trnaseq_db_version + t.trnaseq_db_version, \ + t.genome_view_db_version def get_version_tuples(): @@ -3576,7 +3583,8 @@ def get_version_tuples(): ("Genome data storage version", __genomes_storage_version__), ("Structure DB version", __structure__version__), ("KEGG Modules DB version", __kegg_modules_version__), - ("tRNA-seq DB version", __trnaseq__version__)] + ("tRNA-seq DB version", __trnaseq__version__), + ("Genome view DB version", __genome_view_db__version__)] def print_version(): @@ -3588,7 +3596,8 @@ def print_version(): run.info("Auxiliary data storage", __auxiliary_data_version__) run.info("Structure database", __structure__version__) run.info("Metabolic modules database", __kegg_modules_version__) - run.info("tRNA-seq database", __trnaseq__version__, nl_after=1) + run.info("tRNA-seq database", __trnaseq__version__) + run.info("Genome view database", __genome_view_db__version__, nl_after=1) __version__, \ @@ -3601,7 +3610,8 @@ def print_version(): __genomes_storage_version__ , \ __structure__version__, \ __kegg_modules_version__, \ -__trnaseq__version__ = set_version() +__trnaseq__version__, \ +__genome_view_db__version__ = set_version() if '-v' in sys.argv or '--version' in sys.argv: diff --git a/anvio/bottleroutes.py b/anvio/bottleroutes.py index 75ee0c4f7d..d976840966 100644 --- a/anvio/bottleroutes.py +++ b/anvio/bottleroutes.py @@ -188,6 +188,8 @@ def register_routes(self): self.route('/data/reroot_tree', callback=self.reroot_tree, method='POST') self.route('/data/save_tree', callback=self.save_tree, method='POST') self.route('/data/check_homogeneity_info', callback=self.check_homogeneity_info, method='POST') + self.route('/data/get_genome_view_data', callback=self.get_genome_view_data, method='POST') + self.route('/data/get_genome_view_adl', callback=self.get_genome_view_continuous_data_layers, method='POST') self.route('/data/search_items', callback=self.search_items_by_name, method='POST') self.route('/data/get_taxonomy', callback=self.get_taxonomy, method='POST') self.route('/data/get_functions_for_gene_clusters', callback=self.get_functions_for_gene_clusters, method='POST') @@ -264,6 +266,8 @@ def redirect_to_app(self): homepage = 'metabolism.html' elif self.interactive.mode == 'inspect': redirect('/app/charts.html?id=%s&show_snvs=true&rand=%s' % (self.interactive.inspect_split_name, self.random_hash(8))) + elif self.interactive.mode == 'genome-view': + homepage = 'genomeview.html' redirect('/app/%s?rand=%s' % (homepage, self.random_hash(8))) @@ -469,9 +473,13 @@ def save_state(self, state_name): def get_state(self, state_name): if state_name in self.interactive.states_table.states: + state = self.interactive.states_table.states[state_name] state_dict = json.loads(state['content']) + if self.interactive.mode == 'genome-view': + return json.dumps({'content' : state_dict}) + if self.interactive.mode == 'structure': return json.dumps({'content': state['content']}) else: @@ -1357,6 +1365,24 @@ def get_initial_data(self): return json.dumps(self.interactive.get_initial_data()) + def get_genome_view_data(self): + try: + return json.dumps({'genomes': self.interactive.genomes, + 'gene_associations': self.interactive.gene_associations}) + except Exception as e: + return json.dumps({'error': f"Something went wrong at the backend :( Here is the error message: '{e}'"}) + + + def get_genome_view_continuous_data_layers(self): + """populate continuous data layers, and send them back to the frontend""" + + try: + self.interactive.populate_genome_continuous_data_layers() + return json.dumps(self.interactive.continuous_data_layers) + except Exception as e: + return json.dumps({'error': f"Something went wrong at the backend :( Here is the error message: '{e}'"}) + + def get_column_info(self): gene_callers_id = int(request.forms.get('gene_callers_id')) engine = request.forms.get('engine') @@ -1477,7 +1503,7 @@ def get_functions_for_gene_clusters(self): message = (f"At least one of the gene clusters in your list (e.g., {gene_cluster_name}) is missing in " f"the functions summary dict :/") return json.dumps({'status': 1, 'message': message}) - + d[gene_cluster_name] = self.interactive.gene_clusters_functions_summary_dict[gene_cluster_name] return json.dumps({'functions': d, 'sources': list(self.interactive.gene_clusters_function_sources)}) diff --git a/anvio/data/interactive/css/genomeview.css b/anvio/data/interactive/css/genomeview.css new file mode 100644 index 0000000000..50d070c489 --- /dev/null +++ b/anvio/data/interactive/css/genomeview.css @@ -0,0 +1,275 @@ +.container { + width: 95%; + align-content: center; + margin: 0 auto 20px auto; + background: rgba(255, 255, 255, 0.5); + box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2); + padding: 20px 30px; + touch-action: none; +} + +svg { + font-family: 'Lato', Arial, serif; + font-size: 10px; + font-weight: 700; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.8); +} + +g.scale g.brush rect.background { + fill: rgba(0, 0, 0, .1); +} + +g.scale g.axis path { + stroke-opacity: 0; +} + +g.scale g.axis line { + stroke-opacity: .5; +} + +.axis path, +.axis line { + fill: none; + stroke: #aaa; + shape-rendering: crispEdges; +} + +.brush .extent { + stroke: black; + fill-opacity: .125; + shape-rendering: crispEdges; +} + +g.scale rect.background { + fill: rgb(200, 200, 255); + visibility: visible !important; + pointer-events: all; +} + +#tooltip-body { + background-color: white; + opacity: 100%; + border-style: solid; + border-width: 3px; + padding: 10px; +} + +#deepdive-tooltip-body { + background-color: white; + opacity: 100%; + border-style: solid; + border-width: 3px; + padding: 10px; +} + + +#toggle-panel-settings { + position: fixed; + height: 100px; + width: 15px; + background-color: #EEEEEE; + border: solid 1px #DDDDDD; + top: calc(40%); + border-radius: 10px 0px 0px 10px; + cursor: pointer; + font-size: 10px; + padding-top: 14px; + right: 0px; + z-index: 999; +} + +#toggle-panel-query { + position: fixed; + height: 100px; + width: 15px; + background-color: #EEEEEE; + border: solid 1px #DDDDDD; + top: calc(55%); + border-radius: 10px 0px 0px 10px; + cursor: pointer; + font-size: 10px; + padding-top: 14px; + right: 0px; + z-index: 999; +} + +#toggle-panel-mouseover { + position: fixed; + height: 100px; + width: 15px; + background-color: #EEEEEE; + border: solid 1px #DDDDDD; + top: calc(70%); + border-radius: 10px 0px 0px 10px; + cursor: pointer; + font-size: 10px; + padding-top: 14px; + right: 0px; + z-index: 999; +} + +#settings-panel { + display: none; + top: 0px; + right: 0px; + position: fixed; + height: 100%; + background: url('../images/fractal.jpg') center center scroll; + border-left: 1px solid #D3D3D3; + opacity: 0.97; + width: 530px; + padding: 20px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + overflow-y: scroll; + z-index: 999; + color: black; +} + +#query-panel { + display: none; + top: 0px; + right: 0px; + position: fixed; + height: 100%; + background: url('../images/fractal.jpg') center center scroll; + border-left: 1px solid #D3D3D3; + opacity: 0.97; + width: 530px; + padding: 20px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + overflow-y: scroll; + z-index: 999; + color: black; +} + +#mouseover-panel { + display: none; + top: 0px; + right: 0px; + position: fixed; + height: 100%; + background: url('../images/fractal.jpg') center center scroll; + border-left: 1px solid #D3D3D3; + opacity: 0.97; + width: 530px; + padding: 20px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + overflow-y: scroll; + z-index: 999; + color: black; +} + +.settings-header { + font-variant: small-caps; + font-size: 1.2em; + border-bottom: 1px #969696 solid; + display: block; + margin-bottom: 5px; + margin-top: 12px; +} + +.settings-section-info { + background: #e7f3381a; + padding: 3px 6px 5px 10px; + border: 1px dashed #a9a9a9; + border-radius: 9px; + font-size: 12px; +} + +#settings-section-info-SNV-warning { + display: none; + color: red; +} + +#settings-panel code { + padding: 1.4px 2px; + font-size: 90%; + color: #c7254e; + background-color: #f3f3f3; + border-radius: 2px; +} + +.settings-info-line { + text-align: center; + border: 1px dashed #c5c5c5; + background: #f3f3f3; + width: 40%; + margin: auto; + border-radius: 7px; + font-style: italic; +} + +.toggle-panel-settings-pos { + right: 530px !important; +} + +.toggle-panel-query-pos { + right: 530px !important; +} + +.toggle-panel-mouseover-pos { + right: 530px !important; +} + +#settings-panel .btn { + margin-bottom: 5px !important; +} + +.sidebar-footer { + position: fixed; + background-color: white; + width: 420px; + bottom: -0px; + border-top: 1px solid #D3D3D3; + border-right: 1px solid #D3D3D3; +} + +.sidebar-footer>.btn { + width: 50%; +} + +body { + font-family: 'Lato', Arial, serif; + font-weight: 300; + font-size: 14px; + -webkit-font-smoothing: antialiased; +} + +a { + color: #722; + text-decoration: none; +} + +#labelAndMainContainer { + display: flex; + flex-direction: row; +} + +#labelAndDropdownContainer { + border-style: solid; + border-width: 1; + padding: 15px; + margin: 5px; +} + +#scaleContainer { + border-style: solid; + border-width: 3px; + background: white; + padding: 15px; + margin: 5px; + position: fixed; + bottom: 25px; + background-color: rgba(255, 255, 255, 0.8); +} + +#tabular-modal-body { + overflow-y: scroll; +} + +.modal-multiselect-col { + display: flex; + flex-direction: column; + width: 40%; + margin: 15px; +} \ No newline at end of file diff --git a/anvio/data/interactive/genomeview.html b/anvio/data/interactive/genomeview.html new file mode 100644 index 0000000000..54646f937b --- /dev/null +++ b/anvio/data/interactive/genomeview.html @@ -0,0 +1,652 @@ + + + + + Genome View + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
Loading... +
+
+
+
+ +
+
+
+ +
+ + + + + + +

+
+
+ +
+ + + + + +
+ SETTINGS +
+
+ + +
+ +
+ QUERY +
+
+ Search! + + + + + + + + + + + + +
+ + + +
Category: + +
Function Name: +
+ + + +

+
+ + + + + + + + + + + +
Gene IDGenomeStartStopAction
+ Bookmarks + + + + + + + + + + + + +
+
+
+
+ +
+ MOUSE +
+
+

Gene Call

+ + + + + + + + + + + + +
IDSourceLengthDirectionStartStopCall type
+

Annotations

+ + + + + + + + +
SourceAccessionAnnotation
+
+ + + + + + + + diff --git a/anvio/data/interactive/js/animations.js b/anvio/data/interactive/js/animations.js index 16b99afbf3..e771bc2a12 100644 --- a/anvio/data/interactive/js/animations.js +++ b/anvio/data/interactive/js/animations.js @@ -1,3 +1,21 @@ +/** + * Functions for panel animations. + * + * Authors: Özcan Esen + * + * Copyright 2015-2021, The anvi'o project (http://anvio.org) + * + * Anvi'o is a free software. You can redistribute this program + * and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * You should have received a copy of the GNU General Public License + * along with anvi'o. If not, see . + * + * @license GPL-3.0+ + */ + var ANIMATIONS_ENABLED = true; var SLIDE_INTERVAL = 4; var SLIDE_STEP_SIZE = 15; @@ -11,7 +29,7 @@ function toggleLeftPanel() { is_left_panel_sliding = true; if ($('#panel-left').is(':visible')) { - var animation_frame = function(){ + var animation_frame = function(){ if (ANIMATIONS_ENABLED && $('#panel-left')[0].getBoundingClientRect().right > 0) { $('#panel-left').css('left', parseInt($('#panel-left').css('left')) - SLIDE_STEP_SIZE); $('#toggle-panel-left').css('left', $('#sidebar')[0].getBoundingClientRect().right + 'px'); @@ -28,7 +46,7 @@ function toggleLeftPanel() { animation_frame(); } else { $('#panel-left').show(); - var animation_frame = function(){ + var animation_frame = function(){ if (ANIMATIONS_ENABLED && $('#panel-left')[0].getBoundingClientRect().left < 0) { $('#panel-left').css('left', parseInt($('#panel-left').css('left')) + SLIDE_STEP_SIZE); $('#toggle-panel-left').css('left', $('#sidebar')[0].getBoundingClientRect().right + 'px'); @@ -50,7 +68,7 @@ function toggleRightPanel(name) { ['#mouse_hover_panel', '#description-panel', '#news-panel'].forEach(function(right_panel) { if (right_panel == name) return; - + $(right_panel).hide(); }); diff --git a/anvio/data/interactive/js/area-zoom.js b/anvio/data/interactive/js/area-zoom.js index 24b97881a1..40d1805e97 100644 --- a/anvio/data/interactive/js/area-zoom.js +++ b/anvio/data/interactive/js/area-zoom.js @@ -1,3 +1,22 @@ +/** + * Zooming in an out. + * + * Authors: Özcan Esen + * + * Copyright 2015-2021, The anvi'o project (http://anvio.org) + * + * Anvi'o is a free software. You can redistribute this program + * and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * You should have received a copy of the GNU General Public License + * along with anvi'o. If not, see . + * + * @license GPL-3.0+ + */ + + var mouse_event_origin_x = 0; var mouse_event_origin_y = 0; @@ -9,8 +28,8 @@ function initialize_area_zoom() { var viewport = document.getElementById('svg'); viewport.addEventListener('mousedown', - function(event) { - dragging = false; + function(event) { + dragging = false; document.activeElement.blur(); mouse_event_origin_x = event.clientX; @@ -28,11 +47,11 @@ function initialize_area_zoom() { } }); - viewport.addEventListener('mousemove', + viewport.addEventListener('mousemove', function(event) { if (Math.abs(mouse_event_origin_x - event.clientX) + Math.abs(mouse_event_origin_y - event.clientY) > 2) { - dragging = true; + dragging = true; } if (event.shiftKey && drawing_zoom) @@ -56,25 +75,25 @@ function initialize_area_zoom() { } }); - viewport.addEventListener('mouseup', + viewport.addEventListener('mouseup', function() { if (drawing_zoom) { var zoom_rect = document.getElementById('divzoom').getBoundingClientRect(); - + if (zoom_rect.width > 2 && zoom_rect.height > 2) { var _dx = (parseInt("0" + $('#svg').position().left) + (VIEWER_WIDTH / 2)) - (zoom_rect.left + zoom_rect.width / 2); var _dy = (parseInt("0" + $('#svg').position().top) + (VIEWER_HEIGHT / 2)) - (zoom_rect.top + zoom_rect.height / 2); - pan(_dx,_dy); + pan(_dx,_dy); zoom(Math.min(VIEWER_WIDTH / zoom_rect.width, VIEWER_HEIGHT / zoom_rect.height)); } } clearTextSelection(); - drawing_zoom=false; - zoomBox = {}; - $('#divzoom').hide(); + drawing_zoom=false; + zoomBox = {}; + $('#divzoom').hide(); }); } diff --git a/anvio/data/interactive/js/bin.js b/anvio/data/interactive/js/bin.js index 207401be0d..63793ac59e 100644 --- a/anvio/data/interactive/js/bin.js +++ b/anvio/data/interactive/js/bin.js @@ -1,11 +1,11 @@ /** * Draw bins, bin labels stuff. * - * Author: Özcan Esen - * Credits: A. Murat Eren - * Copyright 2017, The anvio Project + * Authors: Özcan Esen + * A. Murat Eren * - * This file is part of anvi'o (). + * + * Copyright 2015-2021, The anvi'o project (http://anvio.org) * * Anvi'o is a free software. You can redistribute this program * and/or modify it under the terms of the GNU General Public diff --git a/anvio/data/interactive/js/charts.js b/anvio/data/interactive/js/charts.js index b1947117be..fd37cbf0ee 100644 --- a/anvio/data/interactive/js/charts.js +++ b/anvio/data/interactive/js/charts.js @@ -1,11 +1,14 @@ /** * Javascript library to visualize anvi'o charts * - * Author: A. Murat Eren - * Credits: Özcan Esen, Gökmen Göksel, Tobias Paczian. - * Copyright 2015, The anvio Project + * Authors: A. Murat Eren + * Ozcan Esen + * Isaac Fink + * Matthew Klein + * Gökmen Göksel + * Tobias Paczian * - * This file is part of anvi'o (). + * Copyright 2015-2021, The anvi'o project (http://anvio.org) * * Anvi'o is a free software. You can redistribute this program * and/or modify it under the terms of the GNU General Public @@ -51,10 +54,8 @@ var gene_offset_y = 0; var select_boxes = {}; var curr_height; var show_cags_in_split = true; - -var mcags; - -var cog_annotated = false, kegg_annotated = false; +var thresh_count_gene_colors = 1; +var order_gene_colors_by_count = true; function loadAll() { info("Initiated"); @@ -218,21 +219,6 @@ function loadAll() { info("Checking for gene functional annotations"); geneParser = new GeneParser(genes); - geneParser["data"].forEach(function(gene) { - if(gene.functions != null) { - if(gene.functions.hasOwnProperty("COG20_CATEGORY")) { - gene.functions["COG_CATEGORY"] = gene.functions["COG20_CATEGORY"]; - gene.functions["COG_FUNCTION"] = gene.functions["COG20_FUNCTION"]; - } else if(gene.functions.hasOwnProperty("COG14_CATEGORY")) { - gene.functions["COG_CATEGORY"] = gene.functions["COG14_CATEGORY"]; - gene.functions["COG_FUNCTION"] = gene.functions["COG14_FUNCTION"]; - } - - if(gene.functions.hasOwnProperty("COG_CATEGORY")) cog_annotated = true; - if(gene.functions.hasOwnProperty("KEGG_Class")) kegg_annotated = true; - if(cog_annotated && kegg_annotated) return; - } - }); if(!state['highlight-genes']) state['highlight-genes'] = {}; state['large-indel'] = 10; @@ -279,25 +265,14 @@ function loadAll() { state['source-colors'] = default_source_colors; } generateFunctionColorTable(state['source-colors'], "Source", highlight_genes=state['highlight-genes'], show_cags_in_split); - mcags = Object.keys(default_source_colors); - if(cog_annotated) { - $('#gene_color_order').append($('