From c5984b883c319c2468885ec1a46a54f1e01d40f8 Mon Sep 17 00:00:00 2001 From: mrkylesmith Date: Wed, 28 Feb 2024 11:38:33 -0800 Subject: [PATCH] Adding experimental buttons for getting sampled donor/acceptor subtrees for viewing on usherbio --- backend/backend.py | 30 +- rivet-frontend.py | 36 +- static/styles.css | 18 +- static/utils.js | 4 +- static/variant_tracks.js | 2641 +++++++++++++++++++------------------- static/viz.js | 756 ++++++----- 6 files changed, 1718 insertions(+), 1767 deletions(-) diff --git a/backend/backend.py b/backend/backend.py index b84f00c..eefd250 100644 --- a/backend/backend.py +++ b/backend/backend.py @@ -76,21 +76,31 @@ def get_gene_annotations(features): return gene_region_data -def get_sampled_desc(results_file, node_id): +def get_sampled_desc(results_file, node_id, _type): """ """ + NODE_ID_COL = 0 + SAMPLED_DESC_COL = -3 + if _type == "Donor": + NODE_ID_COL = 1 + SAMPLED_DESC_COL = -2 + elif _type == "Acceptor": + NODE_ID_COL = 2 + SAMPLED_DESC_COL = -1 BUF_SIZE = 1048576 # Read 1MB at a time f = open(results_file, 'r', buffering=BUF_SIZE) d = {} # Skip over column headers (assuming one) header = f.readline() + # Check to make sure results file contains sampled descendants field - if header.strip().split('\t')[-1] != "Sampled Descendants": + if header.strip().split('\t')[-3] != "Sampled Descendants": return None + for line in f: splitline = line.strip().split('\t') - sampled_desc_list = list(filter(None,splitline[-1].split(','))) - if node_id == splitline[0]: + sampled_desc_list = list(filter(None,splitline[SAMPLED_DESC_COL].split(','))) + if node_id == splitline[NODE_ID_COL]: return sampled_desc_list # Error, node_id not found in results file return None @@ -258,7 +268,7 @@ def build_counts_histogram(results_file, month_seq_counts_filename): joined_date = "_".join([year, month]) if joined_date not in month_bins.keys(): #TODO: Temporary fix, update data statistics data past 2023-02 - if year == "2023" and int(month) > 2: + if year == "2023" and int(month) > 2 or year == "2024": continue print("[ERROR] CHECK Formatting: {}".format(joined_date)) print("Recomb Node id: {}, with date: {}, in this file: {} is producing this error".format(recomb_id, recomb_date, results_file)) @@ -1086,3 +1096,13 @@ def search_by_sample(recomb_node_set, desc_file, desc_lookup_table, substr): if pattern in desc_string.lower(): recomb_nodes.add(node_id) return list(recomb_nodes) + +def get_aa_mutations(db_file, table, node_id): + """ + """ + import duckdb + con = duckdb.connect(database=db_file, read_only=True) + mutations = con.sql("select * from {} where column0 = '{}'".format(table, node_id)).fetchall() + con.close() + # Return aa_mutations list and and nt_mutations list for the given node_id + return [x[2] for x in mutations], [x[1] for x in mutations] diff --git a/rivet-frontend.py b/rivet-frontend.py index 351b0ea..dbdb94f 100644 --- a/rivet-frontend.py +++ b/rivet-frontend.py @@ -66,11 +66,12 @@ def get_usher_link(): #TODO: Handle for local version # return jsonify(None) node_id = content["node"] + _type = content["type"] else: # Initialize default values from input TSV node_id = init_data["recomb_id"] - samples = backend.get_sampled_desc(results_file, node_id) + samples = backend.get_sampled_desc(results_file, node_id, _type) if not samples: print("[Error] {} not found in results file.".format(node_id)) samples = ["England/PHEP-YYGTYSK/2023|2023-03-18", @@ -238,14 +239,12 @@ def get_aa_mutations(): content = request.get_json() recomb_node_id = content["recomb_node_id"] tree = content["tree"] + db_file = app.config.get('db_file') + aa_tables = app.config.get('aa_tables') if tree == "public": - translation_data = app.config.get('translation_data') - aa_mutations = translation_data[recomb_node_id]["aa_mutations"] - nt_mutations = translation_data[recomb_node_id]["nt_mutations"] + aa_mutations, nt_mutations = backend.get_aa_mutations(db_file, aa_tables[0], recomb_node_id) else: - translation_data = app.config.get('full_tree_translation_data') - aa_mutations = translation_data[recomb_node_id]["aa_mutations"] - nt_mutations = translation_data[recomb_node_id]["nt_mutations"] + aa_mutations, nt_mutations = backend.get_aa_mutations(db_file, aa_tables[1], recomb_node_id) return jsonify({"aa": aa_mutations, "nt": nt_mutations}) @app.route("/get_all_descendants", methods=["POST", "GET"]) @@ -488,14 +487,13 @@ def table(): parser.add_argument("-r", "--recombinant_results", required=True, type=str, help="Give input recombination results file") parser.add_argument("-d", "--descendants_file", required=False, type=str, help="File containing descendants (up to 10k) for each node in VCF") parser.add_argument("-c", "--config", required=True, type=str, help="Configuration file for defining custom color schema for visualizations.") - parser.add_argument("-t", "--translate", required=False, type=str, help="matUtils translate file for visualizing amino acid information.") parser.add_argument("-a", "--analysis", required=False, type=str, help="Extra data files with counts of new genomes sequenced per month. Not for general use.") args = parser.parse_args() # Load and parse config file config = backend.parse_config(args.config) print("Loading RIVET for MAT date: ", config["date"]) - + color_schema = config #color_schema = None #if args.config != None: @@ -504,7 +502,7 @@ def table(): # print("Config file not provided, using default RIVET settings.") # color_schema = backend.default_color_schema() #TODO: Check extensions for input files for correct file format (ie. genbank file) - + # Load recombination results file and get initial data results_files = args.recombinant_results.split(",") recomb_results = results_files[0] @@ -563,7 +561,6 @@ def table(): app.config['month_seq_counts'] = month_seq_counts app.config['recomb_counts'] = recomb_counts app.config['relative_recombinants'] = relative_recombinants - full_tree_month_case_counts,full_tree_month_seq_counts,full_tree_recomb_counts,full_tree_relative_recombinants, = None,None,None,None if len(results_files) > 1: @@ -598,20 +595,6 @@ def table(): full_tree_recomb_node_set = set([cell[1] for cell in full_tree_table]) full_tree_desc_position_table, full_tree_sample_counts = backend.preprocess_desc_file(full_tree_desc_file, full_tree_recomb_node_set) - # Amino acid translation (if provided) - translation_data, full_tree_translation_data = None, None - if args.translate: - translation_files = args.translate.split(",") - translation_file = translation_files[0] - print("Loading provided amino acid translation file/s: ", translation_file) - translation_data = backend.parse_translation_files(translation_file, recomb_node_set) - if len(translation_files) > 1: - full_tree_translation_file = translation_files[1] - print("Loading provided amino acid translation file/s: ", full_tree_translation_file) - full_tree_translation_data = backend.parse_translation_files(full_tree_translation_file, full_tree_recomb_node_set) - - app.config['translation_data'] = translation_data - app.config['full_tree_translation_data'] = full_tree_translation_data # App parameter for public tree recombination results app.config['info_sites'] = info_sites @@ -653,6 +636,9 @@ def table(): app.config['genome_size'] = genome_size app.config['genomic_range'] = genomic_range app.config['gene_region_data'] = gene_region_data + # Name of persistent database file + app.config['db_file'] = config['db_file'] + app.config['aa_tables'] = (config['aa_public'], config['aa_full']) tock = time.perf_counter() print(f"Time elapsed: {tock-tick:.2f} seconds") diff --git a/static/styles.css b/static/styles.css index 69a10dd..38d7f29 100644 --- a/static/styles.css +++ b/static/styles.css @@ -77,19 +77,31 @@ div#off_canvas_right_body { #view_usher { position:absolute; - bottom: 165; + bottom: 200; + left: 10; +} + +#view_usher_donor { + position:absolute; + bottom: 155; + left: 10; +} + +#view_usher_acceptor { + position:absolute; + bottom: 110; left: 10; } #show_mutations { position:absolute; - bottom: 115; + bottom: 65; left: 10; } #view_tree { position:absolute; - bottom: 65; + bottom: 20; left: 10; } diff --git a/static/utils.js b/static/utils.js index 9563de3..b55d831 100644 --- a/static/utils.js +++ b/static/utils.js @@ -86,7 +86,7 @@ function usher_button_info_display(tooltip) { 'px'); } -function view_usher_tree(d) { +function view_usher_tree(d, node_type) { let tree_selected = 'public'; let full_table_select = document.getElementById('full_table'); // Check if public or full table is selected @@ -98,7 +98,7 @@ function view_usher_tree(d) { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify( - {node: d['NODE_IDS']['Recomb'], tree: tree_selected}) + {node: d['NODE_IDS'][node_type], tree: tree_selected, type: node_type }) }).then(res => { res.json().then(data => { // Open usher.bio link in new tab diff --git a/static/variant_tracks.js b/static/variant_tracks.js index ce0bab8..b8f1551 100644 --- a/static/variant_tracks.js +++ b/static/variant_tracks.js @@ -12,616 +12,598 @@ const coordinate_outer_buffer = 150; const coordinate_outermost_buffer = 30; function add_small_trio_track(track_svg, y_position, square_dims, d) { - // New y position to update and return for next track added above - var new_y = y_position; - - var color = '#c6c6c6'; - - var donor_snps = []; - var recomb_snps = []; - var acceptor_snps = []; - var reference_snps = []; - - for (const [key, value] of Object.entries(d['SNPS'])) { - donor_snps.push(value['Donor']); - recomb_snps.push(value['Recomb']); - acceptor_snps.push(value['Acceptor']); - reference_snps.push(value['Reference']); - } - - var small_track_width = (reference_snps.length * square_dims) + - ((reference_snps.length - 1) * buffer_btw_bases); - - // Add donor track - track_svg.append('rect') - .attr('x', track_x_position) - .attr('y', new_y - buffer_btw_tracks) - .attr('width', small_track_width) - .attr('height', square_dims) - .attr('fill', color); - - // Add snps to donor track - add_bases_to_track( - track_svg, donor_snps, track_x_position, new_y - buffer_btw_tracks, - square_dims, false, reference_snps, d); - - // Add label to donor track - var donor_label_text = 'Donor'; - var label_node_id = d['NODE_IDS']['Donor']; - add_track_label( - track_svg, new_y - buffer_btw_tracks, square_dims, donor_label_text, - label_node_id, d); - - // Reposition y - new_y -= (square_dims + buffer_btw_tracks); - - // Add recombinant track - track_svg.append('rect') - .attr('x', track_x_position) - .attr('y', new_y - buffer_btw_tracks) - .attr('width', small_track_width) - .attr('height', square_dims) - .attr('fill', color); - - // Add snps to recomb track - add_bases_to_track( - track_svg, recomb_snps, track_x_position, new_y - buffer_btw_tracks, - square_dims, false, reference_snps, d); - - var recomb_label_text = 'Recombinant'; - var label_node_id = d['NODE_IDS']['Recomb']; - add_track_label( - track_svg, new_y - buffer_btw_tracks, square_dims, - recomb_label_text, label_node_id, d); - - new_y -= (square_dims + buffer_btw_tracks); - - // Add acceptor track - track_svg.append('rect') - .attr('x', track_x_position) - .attr('y', new_y - buffer_btw_tracks) - .attr('width', small_track_width) - .attr('height', square_dims) - .attr('fill', color); - - // Add snps to acceptor track - add_bases_to_track( - track_svg, acceptor_snps, track_x_position, - new_y - buffer_btw_tracks, square_dims, false, reference_snps, d); - - var acceptor_label_text = 'Acceptor'; - var label_node_id = d['NODE_IDS']['Acceptor']; - add_track_label( - track_svg, new_y - buffer_btw_tracks, square_dims, - acceptor_label_text, label_node_id, d); - - var snp_positions = []; - for (const [key, value] of Object.entries(d['SNPS'])) { - snp_positions.push(key); - } - - // Set domain for top position tick mark axis - var x = d3.scaleBand().domain(snp_positions).range([ - track_x_position, small_track_width + track_x_position - ]); - - track_svg.append('g') - .attr('class', 'TopAxis') - .data([snp_positions]) - .attr( - 'transform', - //'translate(0,570)') - // Vertical position - `translate(0,${new_y - buffer_btw_tracks - 5})`) - .call(d3.axisTop(x)) - .selectAll('text') - .style('text-anchor', 'start') - .attr('dx', '1em') - .attr('dy', '.2em') - .style('font-size', '15px') - .attr('transform', 'rotate(-65)') - // TODO: Add mouseover highlighting - .on('mouseover', function(d) { - d3.select(this).attr('fill', '#ff0000'); - }); - - // Update y position to inform next track - new_y -= (square_dims + buffer_btw_tracks); - - return [track_svg, new_y]; + // New y position to update and return for next track added above + var new_y = y_position; + + var color = '#c6c6c6'; + + var donor_snps = []; + var recomb_snps = []; + var acceptor_snps = []; + var reference_snps = []; + + for (const [key, value] of Object.entries(d['SNPS'])) { + donor_snps.push(value['Donor']); + recomb_snps.push(value['Recomb']); + acceptor_snps.push(value['Acceptor']); + reference_snps.push(value['Reference']); + } + + var small_track_width = (reference_snps.length * square_dims) + + ((reference_snps.length - 1) * buffer_btw_bases); + + // Add donor track + track_svg.append('rect') + .attr('x', track_x_position) + .attr('y', new_y - buffer_btw_tracks) + .attr('width', small_track_width) + .attr('height', square_dims) + .attr('fill', color); + + // Add snps to donor track + add_bases_to_track( + track_svg, donor_snps, track_x_position, new_y - buffer_btw_tracks, + square_dims, false, reference_snps, d); + + // Add label to donor track + var donor_label_text = 'Donor'; + var label_node_id = d['NODE_IDS']['Donor']; + add_track_label( + track_svg, new_y - buffer_btw_tracks, square_dims, donor_label_text, + label_node_id, d); + + // Reposition y + new_y -= (square_dims + buffer_btw_tracks); + + // Add recombinant track + track_svg.append('rect') + .attr('x', track_x_position) + .attr('y', new_y - buffer_btw_tracks) + .attr('width', small_track_width) + .attr('height', square_dims) + .attr('fill', color); + + // Add snps to recomb track + add_bases_to_track( + track_svg, recomb_snps, track_x_position, new_y - buffer_btw_tracks, + square_dims, false, reference_snps, d); + + var recomb_label_text = 'Recombinant'; + var label_node_id = d['NODE_IDS']['Recomb']; + add_track_label( + track_svg, new_y - buffer_btw_tracks, square_dims, recomb_label_text, + label_node_id, d); + + new_y -= (square_dims + buffer_btw_tracks); + + // Add acceptor track + track_svg.append('rect') + .attr('x', track_x_position) + .attr('y', new_y - buffer_btw_tracks) + .attr('width', small_track_width) + .attr('height', square_dims) + .attr('fill', color); + + // Add snps to acceptor track + add_bases_to_track( + track_svg, acceptor_snps, track_x_position, new_y - buffer_btw_tracks, + square_dims, false, reference_snps, d); + + var acceptor_label_text = 'Acceptor'; + var label_node_id = d['NODE_IDS']['Acceptor']; + add_track_label( + track_svg, new_y - buffer_btw_tracks, square_dims, acceptor_label_text, + label_node_id, d); + + var snp_positions = []; + for (const [key, value] of Object.entries(d['SNPS'])) { + snp_positions.push(key); + } + + // Set domain for top position tick mark axis + var x = d3.scaleBand().domain(snp_positions).range([ + track_x_position, small_track_width + track_x_position + ]); + + track_svg.append('g') + .attr('class', 'TopAxis') + .data([snp_positions]) + .attr( + 'transform', + //'translate(0,570)') + // Vertical position + `translate(0,${new_y - buffer_btw_tracks - 5})`) + .call(d3.axisTop(x)) + .selectAll('text') + .style('text-anchor', 'start') + .attr('dx', '1em') + .attr('dy', '.2em') + .style('font-size', '15px') + .attr('transform', 'rotate(-65)') + // TODO: Add mouseover highlighting + .on('mouseover', function(d) { + d3.select(this).attr('fill', '#ff0000'); + }); + + // Update y position to inform next track + new_y -= (square_dims + buffer_btw_tracks); + + return [track_svg, new_y]; } function add_small_reference_track( track_svg, polygon_buffer, y_position, square_dims, reference_snps, snp_positions, d) { - // New y position to update - var new_y = y_position; - - var small_track_width = (reference_snps.length * square_dims) + - ((reference_snps.length - 1) * buffer_btw_bases); - - // Grey color for reference track - var color = d['COLOR']['reference_track']; - - track_svg.append('rect') - .attr('x', track_x_position) - .attr('y', new_y - polygon_buffer - buffer_btw_tracks) - .attr('width', small_track_width) - // Autoscale track height based on even paritioning - // of square dimensions - .attr('height', square_dims) - .attr('fill', color); - track_svg.append('text') - .attr('x', 0) - .attr('y', track_x_position + square_dims) - .style('font-size', '20px'); - - var y_pos = new_y - polygon_buffer - buffer_btw_tracks; - - add_bases_to_track( - track_svg, reference_snps, track_x_position, y_pos, square_dims, - true, d); - - // Draw polygons connecting genomic coordinates with snp - // columns - var top_line = 0; - var left_square = 0; - var right_square = 0; - - // Add label to left side of reference track - var ref_label_text = 'Reference'; - var label_node_id = 'None'; - add_track_label( - track_svg, new_y - polygon_buffer - buffer_btw_tracks, square_dims, - ref_label_text, label_node_id, d); - - return [ - track_svg, - new_y - buffer_btw_tracks - polygon_buffer - square_dims - ]; + // New y position to update + var new_y = y_position; + + var small_track_width = (reference_snps.length * square_dims) + + ((reference_snps.length - 1) * buffer_btw_bases); + + // Grey color for reference track + var color = d['COLOR']['reference_track']; + + track_svg.append('rect') + .attr('x', track_x_position) + .attr('y', new_y - polygon_buffer - buffer_btw_tracks) + .attr('width', small_track_width) + // Autoscale track height based on even paritioning + // of square dimensions + .attr('height', square_dims) + .attr('fill', color); + track_svg.append('text') + .attr('x', 0) + .attr('y', track_x_position + square_dims) + .style('font-size', '20px'); + + var y_pos = new_y - polygon_buffer - buffer_btw_tracks; + + add_bases_to_track( + track_svg, reference_snps, track_x_position, y_pos, square_dims, true, d); + + // Draw polygons connecting genomic coordinates with snp + // columns + var top_line = 0; + var left_square = 0; + var right_square = 0; + + // Add label to left side of reference track + var ref_label_text = 'Reference'; + var label_node_id = 'None'; + add_track_label( + track_svg, new_y - polygon_buffer - buffer_btw_tracks, square_dims, + ref_label_text, label_node_id, d); + + return [track_svg, new_y - buffer_btw_tracks - polygon_buffer - square_dims]; } function add_small_coordinate_track(track_svg, y_position, data, square_dims) { - // New y position to update and return as new track is - // added above previous one - var new_y = y_position; - - // Get all genomic positions - var snp_positions = []; - - for (const [key, value] of Object.entries(data['SNPS'])) { - snp_positions.push(parseInt(key)); - } - // Small track half the size of regular track - var small_track_width = track_width / 1.5; - - // Add reference at bottom of border - track_svg.append('rect') - .attr('x', track_x_position) - .attr('y', border_height - coordinate_outer_buffer) - .attr('width', small_track_width) - .attr('height', coordinate_track_height) - //.attr('fill', '#b2b2b2'); - .attr('fill', '#dadada'); - - var zero = 0; - var x_axis_height = - border_height - coordinate_outer_buffer + coordinate_track_height; - var x_axis_pos = [zero.toString(), x_axis_height.toString()].join(','); - - var x = d3.scaleLinear().domain([0, data['GENOME_SIZE']]).range([ - track_x_position, small_track_width + track_x_position - ]); - - track_svg.append('g') - .attr( - 'transform', - // TODO: Parameterize this translation - 'translate(0,600)') - .call(d3.axisBottom(x).tickValues(data['GENOMIC_RANGE'])); - - // Create x-axis label for coordinate track - track_svg.append('text') - .attr('class', 'x label') - .attr('text-anchor', 'end') - //.attr('x', small_track_width / 1.5 + track_x_position) - .attr('x', 280) - .attr('y', border_height - 60) - .attr('font-weight', 700) - .style('font-size', '20px') - .text('Genomic Coordinate'); - - var breakpoint_data = data['BREAKPOINTS']; - - track_svg.append('rect') - .data([breakpoint_data]) - .attr( - 'width', - function(d) { - try { - return ( - x(d['breakpoint1'].end) - - x(d['breakpoint1'].xpos)) - } catch (error) { - return 0; - } - }) - .attr('height', coordinate_track_height) - .attr( - 'x', - function(d) { - try { - return x(d['breakpoint1'].xpos) - } catch (error) { - return 0; - } - }) - .attr('y', border_height - coordinate_outer_buffer) - //.attr('fill', '#b2b2b2') - .attr('fill', '#dadada') - - //.transition() - // Delay of the transition - //.delay(1000) - //.duration(3000) - .attr('fill', data['COLOR']['breakpoint_intervals']); - - track_svg.append('rect') - .data([breakpoint_data]) - .attr( - 'width', - function(d) { - try { - return ( - x(d['breakpoint2'].end) - - x(d['breakpoint2'].xpos)) - } catch (error) { - return 0 - } - }) - .attr('height', coordinate_track_height) - .attr( - 'x', - function(d) { - try { - return x(d['breakpoint2'].xpos) - } catch (error) { - return 0; - } - }) - .attr('y', border_height - coordinate_outer_buffer) - //.attr('fill', '#b2b2b2') - .attr('fill', '#dadada') - - //.transition() - //.delay(1000) - //.duration(3000) - .attr('fill', data['COLOR']['breakpoint_intervals']); - - // Drawing polygons - const dot = track_svg.append('g') - .selectAll('dot') - .data(snp_positions) - .enter() - .append('circle') - .attr('cx', (d) => x(d)) - .attr('cy', border_height - coordinate_outer_buffer) - .attr('r', 0.1) - .style('fill', 'darkblue') - - var lines = track_svg.selectAll('lines') - .data(snp_positions) - .enter() - .append('line') - .attr('class', 'lines') - .attr('x1', (d) => x(d)) - .attr('x2', (d) => x(d)) - .attr('y1', border_height - coordinate_outer_buffer) - .attr( - 'y2', - border_height - coordinate_outer_buffer + - coordinate_track_height) - .attr('stroke-width', '1.0') - //.attr('stroke', '#b2b2b2') - .attr('stroke', '#dadada') - - //.transition() - // Added a delay so the lines would appear in sequential - // increasing order - //.duration(function(d, i) { - // var delay = i * 200; - // return delay; - //}) - //.duration(1000) - // Fade in line posiiton color - // Speed slightly faster than matching polygons. - .attr('stroke', '#dd760b'); - - - // Get informative site positions - var info_sites = data['INFO_SITES']; - - var num = 0; - var circle = - track_svg.selectAll('polygon') - .data(snp_positions) - .enter() - .append('polygon') - .attr( - 'points', - function(d, i) { - var x_offset = x(d); - let left_y = - border_height - coordinate_outer_buffer; - let left = [ - x_offset.toString(), left_y.toString() - ].join(','); - - let top_y = border_height - - coordinate_outer_buffer - polygon_buffer + - square_dims - buffer_btw_tracks; - // (square_dims*i) gives offset for - // num square - let top_x = track_x_position + - ((i * square_dims) + (i * buffer_btw_tracks)); - let _top = - [top_x.toString(), top_y.toString()].join(','); - - let right_y = border_height - - coordinate_outer_buffer - polygon_buffer + - square_dims - buffer_btw_tracks; - let right_x = track_x_position + - (i * square_dims + (i * buffer_btw_tracks)) + - square_dims; - let right = [ - right_x.toString(), right_y.toString() - ].join(','); - - // Join all points - // together in formatted - // points string - polygon_points = [left, _top, right].join(' '); - - - return polygon_points; - }) - .attr('fill', '#FFFFFF') - - //.transition() - //.duration(function(d, i) { - // var delay = i * 200; - // return delay; - //}) - - .attr('fill', function(d, i) { - var color; - if (d.toString() in info_sites) { - var match = info_sites[d]; - color = - determine_informative(match, data['COLOR']); - } else { - // Otherwise not an informative site, - // don't highlight - color = data['COLOR']['non_informative_site']; - } - return color; - }); - - var region_sizes = [232, 13205, 8095]; - var regions = [1, 233, 13436, 21531]; - - var color = '#474747'; - var region_names = ['ORF1a', 'ORF1b', 'S', '3a', 'E', 'M', 'N']; - var region_colors = [ - '#add8e6', '#ffb1b1', '#ffffe0', '#ffdc9d', '#bcf5bc', - '#ffceff', '#ffa500', '#1e90ff' - ]; - var region_widths = [13203, 8095, 3767, 858, 230, 800, 1000, 1268]; - var starting_x_coord = - [232, 13203, 8095, 3767, 858, 230, 800, 1000, 1268]; - - // TODO: Extract this region data from json passed from - // backend processing of gene annotation files - var region_data = { - 'ORF1a': {xpos: 274, end: 13409, color: '#333333'}, - 'ORF1b': {xpos: 13409, end: 21531, color: '#333333'}, - 'S': {xpos: 21531, end: 25268, color: '#333333'}, - '3a': {xpos: 25268, end: 26126, color: '#333333'}, - 'E': {xpos: 26126, end: 26471, color: '#333333'}, - 'M': {xpos: 26471, end: 28471, color: '#333333'}, - 'N': {xpos: 28471, end: 29903, color: '#333333'} - }; - - draw_genomic_region(track_svg, region_names, region_data, x); - - return [track_svg, border_height - coordinate_outer_buffer]; + // New y position to update and return as new track is + // added above previous one + var new_y = y_position; + + // Get all genomic positions + var snp_positions = []; + + for (const [key, value] of Object.entries(data['SNPS'])) { + snp_positions.push(parseInt(key)); + } + // Small track half the size of regular track + var small_track_width = track_width / 1.5; + + // Add reference at bottom of border + track_svg.append('rect') + .attr('x', track_x_position) + .attr('y', border_height - coordinate_outer_buffer) + .attr('width', small_track_width) + .attr('height', coordinate_track_height) + //.attr('fill', '#b2b2b2'); + .attr('fill', '#dadada'); + + var zero = 0; + var x_axis_height = + border_height - coordinate_outer_buffer + coordinate_track_height; + var x_axis_pos = [zero.toString(), x_axis_height.toString()].join(','); + + var x = d3.scaleLinear().domain([0, data['GENOME_SIZE']]).range([ + track_x_position, small_track_width + track_x_position + ]); + + track_svg.append('g') + .attr( + 'transform', + // TODO: Parameterize this translation + 'translate(0,600)') + .call(d3.axisBottom(x).tickValues(data['GENOMIC_RANGE'])); + + // Create x-axis label for coordinate track + track_svg.append('text') + .attr('class', 'x label') + .attr('text-anchor', 'end') + //.attr('x', small_track_width / 1.5 + track_x_position) + .attr('x', 280) + .attr('y', border_height - 60) + .attr('font-weight', 700) + .style('font-size', '20px') + .text('Genomic Coordinate'); + + var breakpoint_data = data['BREAKPOINTS']; + + track_svg.append('rect') + .data([breakpoint_data]) + .attr( + 'width', + function(d) { + try { + return (x(d['breakpoint1'].end) - x(d['breakpoint1'].xpos)) + } catch (error) { + return 0; + } + }) + .attr('height', coordinate_track_height) + .attr( + 'x', + function(d) { + try { + return x(d['breakpoint1'].xpos) + } catch (error) { + return 0; + } + }) + .attr('y', border_height - coordinate_outer_buffer) + //.attr('fill', '#b2b2b2') + .attr('fill', '#dadada') + + //.transition() + // Delay of the transition + //.delay(1000) + //.duration(3000) + .attr('fill', data['COLOR']['breakpoint_intervals']); + + track_svg.append('rect') + .data([breakpoint_data]) + .attr( + 'width', + function(d) { + try { + return (x(d['breakpoint2'].end) - x(d['breakpoint2'].xpos)) + } catch (error) { + return 0 + } + }) + .attr('height', coordinate_track_height) + .attr( + 'x', + function(d) { + try { + return x(d['breakpoint2'].xpos) + } catch (error) { + return 0; + } + }) + .attr('y', border_height - coordinate_outer_buffer) + //.attr('fill', '#b2b2b2') + .attr('fill', '#dadada') + + //.transition() + //.delay(1000) + //.duration(3000) + .attr('fill', data['COLOR']['breakpoint_intervals']); + + // Drawing polygons + const dot = track_svg.append('g') + .selectAll('dot') + .data(snp_positions) + .enter() + .append('circle') + .attr('cx', (d) => x(d)) + .attr('cy', border_height - coordinate_outer_buffer) + .attr('r', 0.1) + .style('fill', 'darkblue') + + var lines = + track_svg.selectAll('lines') + .data(snp_positions) + .enter() + .append('line') + .attr('class', 'lines') + .attr('x1', (d) => x(d)) + .attr('x2', (d) => x(d)) + .attr('y1', border_height - coordinate_outer_buffer) + .attr( + 'y2', + border_height - coordinate_outer_buffer + coordinate_track_height) + .attr('stroke-width', '1.0') + //.attr('stroke', '#b2b2b2') + .attr('stroke', '#dadada') + + //.transition() + // Added a delay so the lines would appear in sequential + // increasing order + //.duration(function(d, i) { + // var delay = i * 200; + // return delay; + //}) + //.duration(1000) + // Fade in line posiiton color + // Speed slightly faster than matching polygons. + .attr('stroke', '#dd760b'); + + + // Get informative site positions + var info_sites = data['INFO_SITES']; + + var num = 0; + var circle = + track_svg.selectAll('polygon') + .data(snp_positions) + .enter() + .append('polygon') + .attr( + 'points', + function(d, i) { + var x_offset = x(d); + let left_y = border_height - coordinate_outer_buffer; + let left = [x_offset.toString(), left_y.toString()].join(','); + + let top_y = border_height - coordinate_outer_buffer - + polygon_buffer + square_dims - buffer_btw_tracks; + // (square_dims*i) gives offset for + // num square + let top_x = track_x_position + + ((i * square_dims) + (i * buffer_btw_tracks)); + let _top = [top_x.toString(), top_y.toString()].join(','); + + let right_y = border_height - coordinate_outer_buffer - + polygon_buffer + square_dims - buffer_btw_tracks; + let right_x = track_x_position + + (i * square_dims + (i * buffer_btw_tracks)) + square_dims; + let right = [right_x.toString(), right_y.toString()].join(','); + + // Join all points + // together in formatted + // points string + polygon_points = [left, _top, right].join(' '); + + + return polygon_points; + }) + .attr('fill', '#FFFFFF') + + //.transition() + //.duration(function(d, i) { + // var delay = i * 200; + // return delay; + //}) + + .attr('fill', function(d, i) { + var color; + if (d.toString() in info_sites) { + var match = info_sites[d]; + color = determine_informative(match, data['COLOR']); + } else { + // Otherwise not an informative site, + // don't highlight + color = data['COLOR']['non_informative_site']; + } + return color; + }); + + var region_sizes = [232, 13205, 8095]; + var regions = [1, 233, 13436, 21531]; + + var color = '#474747'; + var region_names = ['ORF1a', 'ORF1b', 'S', '3a', 'E', 'M', 'N']; + var region_colors = [ + '#add8e6', '#ffb1b1', '#ffffe0', '#ffdc9d', '#bcf5bc', '#ffceff', '#ffa500', + '#1e90ff' + ]; + var region_widths = [13203, 8095, 3767, 858, 230, 800, 1000, 1268]; + var starting_x_coord = [232, 13203, 8095, 3767, 858, 230, 800, 1000, 1268]; + + // TODO: Extract this region data from json passed from + // backend processing of gene annotation files + var region_data = { + 'ORF1a': {xpos: 274, end: 13409, color: '#333333'}, + 'ORF1b': {xpos: 13409, end: 21531, color: '#333333'}, + 'S': {xpos: 21531, end: 25268, color: '#333333'}, + '3a': {xpos: 25268, end: 26126, color: '#333333'}, + 'E': {xpos: 26126, end: 26471, color: '#333333'}, + 'M': {xpos: 26471, end: 28471, color: '#333333'}, + 'N': {xpos: 28471, end: 29903, color: '#333333'} + }; + + draw_genomic_region(track_svg, region_names, region_data, x); + + return [track_svg, border_height - coordinate_outer_buffer]; } function handle_zero_snps(svg) { - svg.append('text') - .attr('x', border_width / 2) - .attr('y', border_height / 2) - .attr('text-anchor', 'middle') - .attr('font-size', 60) - .style('font-weight', 'bold') - .style('fill', 'red') - .text('No SNVs to display for the selected recombinant.'); + svg.append('text') + .attr('x', border_width / 2) + .attr('y', border_height / 2) + .attr('text-anchor', 'middle') + .attr('font-size', 60) + .style('font-weight', 'bold') + .style('fill', 'red') + .text('No SNVs to display for the selected recombinant.'); } function hide_aa_labels(track_svg) { - // Hide amino acid track, restore SNV view title - d3.select('#plot_title').style('opacity', 1); - d3.select('#aa_track_title').remove(); - d3.selectAll('#AALabels').remove(); - d3.selectAll('.TopAxisAA').remove(); + // Hide amino acid track, restore SNV view title + d3.select('#plot_title').style('opacity', 1); + d3.select('#aa_track_title').remove(); + d3.selectAll('#AALabels').remove(); + d3.selectAll('.TopAxisAA').remove(); } function add_aa_labels( track_svg, y_position, snp_positions, square_dims, num_snps, d, nt_positions, aa_mutations) { - // Hide plot title - d3.select('#plot_title').style('opacity', 0); - // Add title to left of AA track - let label_x_offset = 180; - track_svg.append('text') - .attr('id', 'aa_track_title') - .attr('x', label_x_offset + 100) - .attr('y', 210) - .attr('text-anchor', 'start') - .attr('text-anchor', 'end') - .attr('dominant-baseline', 'central') - .attr('font-weight', 400) - .style('font-size', '15px') - .text('Coding Amino Acid Mutations'); - - var acceptor_snps = []; - for (const [key, value] of Object.entries(d['SNPS'])) { - acceptor_snps.push(value['Acceptor']); + + // Hide plot title + d3.select('#plot_title').style('opacity', 0); + // Add title to left of AA track + let label_x_offset = 180; + track_svg.append('text') + .attr('id', 'aa_track_title') + .attr('x', label_x_offset + 100) + .attr('y', 210) + .attr('text-anchor', 'start') + .attr('text-anchor', 'end') + .attr('dominant-baseline', 'central') + .attr('font-weight', 400) + .style('font-size', '15px') + .text('Coding Amino Acid Mutations'); + + var acceptor_snps = []; + for (const [key, value] of Object.entries(d['SNPS'])) { + acceptor_snps.push(value['Acceptor']); + } + var new_y = y_position; + + var num_snps = acceptor_snps.length; + var label_width = + (num_snps * square_dims + (num_snps - 1) * buffer_btw_bases) + + track_x_position; + + var x = d3.scaleBand().domain(snp_positions).range([ + track_x_position, label_width + ]); + + // Get informative site positions + var info_sites = d['INFO_SITES']; + var colors = d['COLOR']; + track_svg.append('g') + .attr('class', 'TopAxisAA') + .data([snp_positions]) + .attr('transform', `translate(0,${new_y - buffer_btw_tracks - 5})`) + .call(d3.axisTop(x)) + .selectAll('text') + .style('text-anchor', 'start') + .attr('dx', '1em') + .attr('dy', '.2em') + .style('font-size', '15px') + .attr('transform', 'rotate(-65)') + .attr('id', 'AALabels') + .style( + 'fill', + function(d, i) { + var color; + if (d.toString() in info_sites) { + var match = info_sites[d]; + color = determine_informative(match, colors); + } else { + // Otherwise not an informative site, don't + // highlight + color = colors['non_informative_site']; + } + return color; + }) + .style('opacity', function(d, i) { + + if (!nt_positions.includes(d.toString())) { + return '0'; + } else { + let index = nt_positions.indexOf(d.toString()); + let aa_mut = aa_mutations[index].toString(); + d3.select(this).text(aa_mut); + return '1'; } - var new_y = y_position; - - var num_snps = acceptor_snps.length; - var label_width = - (num_snps * square_dims + (num_snps - 1) * buffer_btw_bases) + - track_x_position; - - var x = d3.scaleBand().domain(snp_positions).range([ - track_x_position, label_width - ]); - - // Get informative site positions - var info_sites = d['INFO_SITES']; - var colors = d['COLOR']; - track_svg.append('g') - .attr('class', 'TopAxisAA') - .data([snp_positions]) - .attr('transform', `translate(0,${new_y - buffer_btw_tracks - 5})`) - .call(d3.axisTop(x)) - .selectAll('text') - .style('text-anchor', 'start') - .attr('dx', '1em') - .attr('dy', '.2em') - .style('font-size', '15px') - .attr('transform', 'rotate(-65)') - .attr('id', 'AALabels') - .style( - 'fill', - function(d, i) { - var color; - if (d.toString() in info_sites) { - var match = info_sites[d]; - color = determine_informative(match, colors); - } else { - // Otherwise not an informative site, don't - // highlight - color = colors['non_informative_site']; - } - return color; - }) - .style('opacity', function(d, i) { - if (!nt_positions.includes(d.toString())) { - return '0'; - } else { - var index = nt_positions.indexOf(d.toString()); - d3.select(this).text( - aa_mutations[index].toString()); - return '1'; - } - }); - track_svg.selectAll('.TopAxisAA path').style('stroke', '#FFFFFF'); + }); + track_svg.selectAll('.TopAxisAA path').style('stroke', '#FFFFFF'); } function add_column_label( track_svg, label_y_position, x_pos, square_dims, label_text) { - // Add label above each column - track_svg.append('rect') - .attr('x', x_pos) - .attr('y', label_y_position) - .attr('width', square_dims) - .attr('height', square_dims * 2) - // Background rect white, so only text label shows - //.attr('fill', '#D3D3D3'); - .attr('fill', '#D3D3D3'); - - // Add text position label above each track - track_svg.append('text') - .attr('x', x_pos + (square_dims / 2)) - .attr('y', (label_y_position + (square_dims / 2))) - .text(label_text) - .style('text-anchor', 'end') - .attr('fill', '#c45f00') - .attr('dominant-baseline', 'central') - .attr('font-weight', 700) - .style('font-size', '20px') - .attr('dx', '-.8em') - .attr('dy', '.15em') - .attr('transform', function(d) { - return 'rotate(-90)' - }); + // Add label above each column + track_svg.append('rect') + .attr('x', x_pos) + .attr('y', label_y_position) + .attr('width', square_dims) + .attr('height', square_dims * 2) + // Background rect white, so only text label shows + //.attr('fill', '#D3D3D3'); + .attr('fill', '#D3D3D3'); + + // Add text position label above each track + track_svg.append('text') + .attr('x', x_pos + (square_dims / 2)) + .attr('y', (label_y_position + (square_dims / 2))) + .text(label_text) + .style('text-anchor', 'end') + .attr('fill', '#c45f00') + .attr('dominant-baseline', 'central') + .attr('font-weight', 700) + .style('font-size', '20px') + .attr('dx', '-.8em') + .attr('dy', '.15em') + .attr('transform', function(d) { + return 'rotate(-90)' + }); } function add_column_position_labels( track_svg, y_position, snp_positions, square_dims, num_snps, d) { - // Move y_position past top track - y_position -= (square_dims + buffer_btw_bases) + // Move y_position past top track + y_position -= (square_dims + buffer_btw_bases) - // Starting x_pos on lefthand side of visual - var x_pos = track_x_position; - for (pos of snp_positions) { - add_column_label( - track_svg, y_position, x_pos, square_dims, pos.toString()); + // Starting x_pos on lefthand side of visual + var x_pos = track_x_position; + for (pos of snp_positions) { + add_column_label(track_svg, y_position, x_pos, square_dims, pos.toString()); - // Update x_pos - x_pos += (square_dims + buffer_btw_bases); - } + // Update x_pos + x_pos += (square_dims + buffer_btw_bases); + } } function init_coordinate_track(track_svg, y_position, data) { - // New y position to update and return as new track is - // added above previous one - var new_y = y_position; - const init_track_x_position = 100; - - track_svg - .append('text') - //.attr('x', 1000) - .attr('x', 800) - .attr('y', 300) - .attr('text-anchor', 'middle') - .attr('font-size', 28) - .style('font-weight', 'bold') - .text( - 'Select a table entry below to view the single-nucleotide variation in the recombinant and its parents'); - - // Add initial genomic track - track_svg.append('rect') - .attr('x', init_track_x_position) - //.attr('x', track_x_position) - .attr('y', border_height - coordinate_outer_buffer) - .attr('width', track_width) - .attr('height', coordinate_track_height) - //.attr('fill', '#b2b2b2'); - .attr('fill', '#dadada'); - - var zero = 0; - var x_axis_height = - border_height - coordinate_outer_buffer + coordinate_track_height; - var x_axis_pos = [zero.toString(), x_axis_height.toString()].join(','); - var x = d3.scaleLinear().domain([0, data['GENOME_SIZE']]).range([ - // track_x_position, track_width + track_x_position - init_track_x_position, track_width + init_track_x_position - ]); - track_svg.append('g') - .attr('transform', 'translate(0,600)') - .call(d3.axisBottom(x).tickValues(data['GENOMIC_RANGE'])); - - // Create x-axis label for coordinate track - /* + // New y position to update and return as new track is + // added above previous one + var new_y = y_position; + const init_track_x_position = 100; + + track_svg + .append('text') + //.attr('x', 1000) + .attr('x', 800) + .attr('y', 300) + .attr('text-anchor', 'middle') + .attr('font-size', 28) + .style('font-weight', 'bold') + .text( + 'Select a table entry below to view the single-nucleotide variation in the recombinant and its parents'); + + // Add initial genomic track + track_svg.append('rect') + .attr('x', init_track_x_position) + //.attr('x', track_x_position) + .attr('y', border_height - coordinate_outer_buffer) + .attr('width', track_width) + .attr('height', coordinate_track_height) + //.attr('fill', '#b2b2b2'); + .attr('fill', '#dadada'); + + var zero = 0; + var x_axis_height = + border_height - coordinate_outer_buffer + coordinate_track_height; + var x_axis_pos = [zero.toString(), x_axis_height.toString()].join(','); + var x = d3.scaleLinear().domain([0, data['GENOME_SIZE']]).range([ + // track_x_position, track_width + track_x_position + init_track_x_position, track_width + init_track_x_position + ]); + track_svg.append('g') + .attr('transform', 'translate(0,600)') + .call(d3.axisBottom(x).tickValues(data['GENOMIC_RANGE'])); + + // Create x-axis label for coordinate track + /* track_svg.append('text') .attr('class', 'x label') .attr('text-anchor', 'end') @@ -632,838 +614,815 @@ track_svg.append('text') .text('Genomic Coordinate'); */ - let region_names = Object.keys(data['REGION_DATA']); - let region_data = data['REGION_DATA']; + let region_names = Object.keys(data['REGION_DATA']); + let region_data = data['REGION_DATA']; - draw_genomic_region(track_svg, region_names, region_data, x); + draw_genomic_region(track_svg, region_names, region_data, x); - return [track_svg, border_height - coordinate_outer_buffer]; + return [track_svg, border_height - coordinate_outer_buffer]; } function append_interval(test, breakpoint_data, breakpoint, x) { - test.append('rect') - .attr( - 'width', - function(d) { - return (x(d[breakpoint].end) - x(d[breakpoint].xpos)) - }) - .attr('height', coordinate_track_height) - .attr( - 'x', - function(d) { - return x(d[breakpoint].xpos) - }) - .attr('y', border_height - coordinate_outer_buffer) - //.attr('fill', '#b2b2b2') - .attr('fill', '#dadada') - .transition() - .duration(10) - .attr('fill', data['COLOR']['breakpoint_intervals']); + test.append('rect') + .attr( + 'width', + function(d) { + return (x(d[breakpoint].end) - x(d[breakpoint].xpos)) + }) + .attr('height', coordinate_track_height) + .attr( + 'x', + function(d) { + return x(d[breakpoint].xpos) + }) + .attr('y', border_height - coordinate_outer_buffer) + //.attr('fill', '#b2b2b2') + .attr('fill', '#dadada') + .transition() + .duration(10) + .attr('fill', data['COLOR']['breakpoint_intervals']); } function add_breakpoint_internals(track_svg, data) { - track_svg - .append('rect') - // X-position = breakpoint interval start - .attr('x', track_x_position + 100) - // Same as coordinate track - .attr('y', border_height - coordinate_outer_buffer) - // width = breakpoint interval end - .attr('width', 50) - // Same as coordinate track - .attr('height', coordinate_track_height) - //.attr('fill', '#b2b2b2') - .attr('fill', '#dadada') - .transition() - .duration(30) - .attr('fill', data['COLOR']['breakpoint_intervals']); + track_svg + .append('rect') + // X-position = breakpoint interval start + .attr('x', track_x_position + 100) + // Same as coordinate track + .attr('y', border_height - coordinate_outer_buffer) + // width = breakpoint interval end + .attr('width', 50) + // Same as coordinate track + .attr('height', coordinate_track_height) + //.attr('fill', '#b2b2b2') + .attr('fill', '#dadada') + .transition() + .duration(30) + .attr('fill', data['COLOR']['breakpoint_intervals']); } function add_track_label( track_svg, label_y_position, square_dims, label_text, label_node_id, d) { - let label_halfway = square_dims / 2; - let label_width = 100; - // Left side track label buffer distance from each track - let label_x_offset = 180; - - if (d != null) { - // Add label to left of reference track - track_svg.append('rect') - .attr('x', label_x_offset) - .attr('y', label_y_position) - .attr('width', label_width) - // The track height scales based on even paritioning of - // square dimensions - .attr('height', square_dims) - // Background rect white, so only text label shows - .attr('fill', '#ffffff'); - - // Add text labels to left of each track - track_svg.append('text') - .attr('x', label_x_offset + label_width) - .attr('y', (label_y_position + (square_dims / 2))) - .text(label_text) - .attr('id', label_text) - .attr('text-anchor', 'end') - .attr('dominant-baseline', 'central') - .attr('font-weight', 700) - .style('font-size', '16px') - .on('click', - function(d) { - // Fetch and display the descendants for the - // selected recombinant node id - display_descendants(label_node_id); - d3.select('#' + label_text) - }) - .on('mouseover', - function(d) { - d3.select(this) - .text(label_node_id) - .transition() - .delay(10); - - d3.select(this) - .style('fill', '#4169E1') - .attr('stroke-width', '1') - .style('opacity', 3); - }) - .on('mouseleave', function(d) { - d3.select(this) - .text(label_text) - .style('fill', 'black') - .style('opacity', 1) - .transition() - .delay(10) - }); - } - if (label_text == 'Acceptor' && - document.querySelector('#download_all_descendants') - .style.visibility != 'hidden') { - // Add text instuctions above tracks - track_svg.append('text') - .attr('x', label_x_offset + label_width) - .attr('y', (label_y_position + (square_dims / 2)) - 50) - .text('Click below to view descendants') - // TODO: Uniquely label text that will not be included in - // SVG download, only for browser interactivity - //.attr('id', label_text) - .attr('id', 'click_desc_label') - .attr('text-anchor', 'end') - .attr('dominant-baseline', 'central') - .attr('font-weight', 400) - .style('font-size', '8px'); - } + let label_halfway = square_dims / 2; + let label_width = 100; + // Left side track label buffer distance from each track + let label_x_offset = 180; + + if (d != null) { + // Add label to left of reference track + track_svg.append('rect') + .attr('x', label_x_offset) + .attr('y', label_y_position) + .attr('width', label_width) + // The track height scales based on even paritioning of + // square dimensions + .attr('height', square_dims) + // Background rect white, so only text label shows + .attr('fill', '#ffffff'); + + // Add text labels to left of each track + track_svg.append('text') + .attr('x', label_x_offset + label_width) + .attr('y', (label_y_position + (square_dims / 2))) + .text(label_text) + .attr('id', label_text) + .attr('text-anchor', 'end') + .attr('dominant-baseline', 'central') + .attr('font-weight', 700) + .style('font-size', '16px') + .on('click', + function(d) { + // Fetch and display the descendants for the + // selected recombinant node id + display_descendants(label_node_id); + d3.select('#' + label_text) + }) + .on('mouseover', + function(d) { + d3.select(this).text(label_node_id).transition().delay(10); + + d3.select(this) + .style('fill', '#4169E1') + .attr('stroke-width', '1') + .style('opacity', 3); + }) + .on('mouseleave', function(d) { + d3.select(this) + .text(label_text) + .style('fill', 'black') + .style('opacity', 1) + .transition() + .delay(10) + }); + } + if (label_text == 'Acceptor' && + document.querySelector('#download_all_descendants').style.visibility != + 'hidden') { + // Add text instuctions above tracks + track_svg.append('text') + .attr('x', label_x_offset + label_width) + .attr('y', (label_y_position + (square_dims / 2)) - 50) + .text('Click below to view descendants') + // TODO: Uniquely label text that will not be included in + // SVG download, only for browser interactivity + //.attr('id', label_text) + .attr('id', 'click_desc_label') + .attr('text-anchor', 'end') + .attr('dominant-baseline', 'central') + .attr('font-weight', 400) + .style('font-size', '8px'); + } } // TODO: Remove, not in use function format_polygon_points(square_dims) { - let left_y = border_height - coordinate_outer_buffer; - let left = [track_x_position.toString(), left_y.toString()].join(','); + let left_y = border_height - coordinate_outer_buffer; + let left = [track_x_position.toString(), left_y.toString()].join(','); - let top_y = border_height - coordinate_outer_buffer - polygon_buffer + - square_dims - buffer_btw_tracks; - let _top = [track_x_position.toString(), top_y.toString()].join(','); + let top_y = border_height - coordinate_outer_buffer - polygon_buffer + + square_dims - buffer_btw_tracks; + let _top = [track_x_position.toString(), top_y.toString()].join(','); - let right_y = border_height - coordinate_outer_buffer - polygon_buffer + - square_dims - buffer_btw_tracks; - let right_x = track_x_position + square_dims; - let right = [right_x.toString(), right_y.toString()].join(','); + let right_y = border_height - coordinate_outer_buffer - polygon_buffer + + square_dims - buffer_btw_tracks; + let right_x = track_x_position + square_dims; + let right = [right_x.toString(), right_y.toString()].join(','); - // Join all points together in formatted points string - polygon_points = [left, _top, right].join(' '); + // Join all points together in formatted points string + polygon_points = [left, _top, right].join(' '); - return polygon_points; + return polygon_points; } function append_g(g, region_data, region, x) { - const genomic_region_dims = 30; - - g.append('rect') - // Width mapped to genomic region coordinates - .attr( - 'width', - function(d) { - return (x(d[region].end) - x(d[region].xpos)) - }) - .attr('height', genomic_region_dims) - // X-coordinate as a function of genomic region - // (mapping to 1-30k) - .attr( - 'x', - function(d) { - return x(d[region].xpos) - }) - .attr( - 'y', - border_height - coordinate_outermost_buffer - - genomic_region_dims) - .attr('fill', region_data[region].color) - .on('mouseover', - function(d) { - d3.select(this) - .attr('fill', '#4169E1') - .style('stroke', 'white') - .attr('stroke-width', '3'); - }) - .on('mouseleave', - function(d) { - d3.select(this) - .attr('fill', region_data[region].color) - .attr('stroke', 'white') - .attr('stroke-width', '1') - }) - .style('stroke', 'white') - .attr('stroke-width', '1') - - .on('click', function(d) { - // TODO: Finish select SNVs falling in specific region - // feature - console.log('CLICKED ON REGION: ', region); - }); - - // Add text label to genomic region - // TODO: Fix mouseover text issue - g.append('text') - .attr( - 'x', - function(d) { - return ( - x(d[region].xpos) + - ((x(d[region].end) - x(d[region].xpos)) / 2)) - }) - .attr( - 'y', - border_height - coordinate_outermost_buffer - - (genomic_region_dims / 2)) - .text(function(d) { - let length = d[region].end - d[region].xpos; - let region_dim = x(d[region].end) - x(d[region].xpos); - if (region_dim < 30 && region.length != 1) { - return; - } - return region; - }) - - // Center text char in square - .attr('text-anchor', 'middle') - .attr('dominant-baseline', 'central') - .attr('fill', 'white') - .style('font-size', '10px'); + const genomic_region_dims = 30; + + g.append('rect') + // Width mapped to genomic region coordinates + .attr( + 'width', + function(d) { + return (x(d[region].end) - x(d[region].xpos)) + }) + .attr('height', genomic_region_dims) + // X-coordinate as a function of genomic region + // (mapping to 1-30k) + .attr( + 'x', + function(d) { + return x(d[region].xpos) + }) + .attr( + 'y', + border_height - coordinate_outermost_buffer - genomic_region_dims) + .attr('fill', region_data[region].color) + .on('mouseover', + function(d) { + d3.select(this) + .attr('fill', '#4169E1') + .style('stroke', 'white') + .attr('stroke-width', '3'); + }) + .on('mouseleave', + function(d) { + d3.select(this) + .attr('fill', region_data[region].color) + .attr('stroke', 'white') + .attr('stroke-width', '1') + }) + .style('stroke', 'white') + .attr('stroke-width', '1') + + .on('click', function(d) { + // TODO: Finish select SNVs falling in specific region + // feature + console.log('CLICKED ON REGION: ', region); + }); + + // Add text label to genomic region + // TODO: Fix mouseover text issue + g.append('text') + .attr( + 'x', + function(d) { + return ( + x(d[region].xpos) + + ((x(d[region].end) - x(d[region].xpos)) / 2)) + }) + .attr( + 'y', + border_height - coordinate_outermost_buffer - + (genomic_region_dims / 2)) + .text(function(d) { + let length = d[region].end - d[region].xpos; + let region_dim = x(d[region].end) - x(d[region].xpos); + if (region_dim < 30 && region.length != 1) { + return; + } + return region; + }) + + // Center text char in square + .attr('text-anchor', 'middle') + .attr('dominant-baseline', 'central') + .attr('fill', 'white') + .style('font-size', '10px'); } function draw_genomic_region(track_svg, region_names, region_data, x) { - // Add genomic region rect - var g = track_svg.selectAll('.rect') - .data([region_data]) - .enter() - .append('g') - .classed('rect', true) - - for (region of region_names) { - append_g(g, region_data, region, x); - } + // Add genomic region rect + var g = track_svg.selectAll('.rect') + .data([region_data]) + .enter() + .append('g') + .classed('rect', true) + + for (region of region_names) { + append_g(g, region_data, region, x); + } } function draw_polypons( track_svg, square_dims, top_line, left_square, right_square, snp_positions) { - let left_y = border_height - coordinate_outer_buffer; - let left = [track_x_position.toString(), left_y.toString()].join(','); + let left_y = border_height - coordinate_outer_buffer; + let left = [track_x_position.toString(), left_y.toString()].join(','); - let top_y = border_height - coordinate_outer_buffer - polygon_buffer + - square_dims - buffer_btw_tracks; - let _top = [track_x_position.toString(), top_y.toString()].join(','); + let top_y = border_height - coordinate_outer_buffer - polygon_buffer + + square_dims - buffer_btw_tracks; + let _top = [track_x_position.toString(), top_y.toString()].join(','); - let right_y = border_height - coordinate_outer_buffer - polygon_buffer + - square_dims - buffer_btw_tracks; - let right_x = track_x_position + square_dims; + let right_y = border_height - coordinate_outer_buffer - polygon_buffer + + square_dims - buffer_btw_tracks; + let right_x = track_x_position + square_dims; - let right = [right_x.toString(), right_y.toString()].join(','); + let right = [right_x.toString(), right_y.toString()].join(','); - polygon_points = [left, _top, right].join(' '); + polygon_points = [left, _top, right].join(' '); - var circle = track_svg.append('polygon') - .attr('points', polygon_points) - .attr('fill', 'green'); + var circle = track_svg.append('polygon') + .attr('points', polygon_points) + .attr('fill', 'green'); } function add_bases_to_track( svg, snps, starting_x_pos, y_pos, square_dims, is_reference, reference_snps = [], d) { - var position_data = []; - var colors; - - // Get reference and trio snps at each position - if (d != null) { - for (const [key, value] of Object.entries(d['SNPS'])) { - position_data.push(parseInt(key)); - } - var Tooltip = d3.select('#tracks') - .append('div') - .style('opacity', 0) - .attr('class', 'tooltip') - .style('background-color', 'white') - .style('border', 'solid') - .style('border-width', '2px') - .style('border-radius', '5px') - - var color_base_mapping = { - 'G': d['COLOR']['g'], - 'C': d['COLOR']['c'], - 'T': d['COLOR']['t'], - 'A': d['COLOR']['a'] - }; - - var base; - var color; - var num_snps = snps.length; - var x_pos = starting_x_pos; - i = 0 - for (base of snps) { - if (is_reference == true) { - // Fill reference bases with dark grey squares - color = d['COLOR']['reference_track']; - } else { - // Check if reference_snps parameter passed, if - // so assume reference_snps.length = snps.length - // (also guaranteed by backend data - // preprossessing) - if (!(reference_snps.length == 0)) { - // If alternate allele, color according - // to base type - if (base != reference_snps[i]) { - color = - color_base_mapping[base]; - } - // Otherwise if base matches reference, - // color base same as reference - else { - color = - d['COLOR']['base_matching_reference']; - } - } - } - - // Move to next base in reference sequence - ++i; - - let halfway = square_dims / 2; - - // Append square base inside specific svg track - var bases_rect = svg.append('rect') - .data(position_data) - .attr('class', 'bases') - .attr('x', x_pos) - .attr('y', y_pos) - .attr('height', square_dims) - .attr('width', square_dims) - .attr('fill', color) - .each(function(d, i) {}); - - /* TODO: Add interactivity to each SNV position - .on('mouseover', - function(d) { - d3.selectAll('.bases') - .style('opacity', - '0.2') .filter(function() { return !this.classList - .contains( - 'active') - }) - d3.select(this) - .style('stroke', - '#4169E1') .attr('stroke-width', '2') - .style('opacity', - '1') - }) - .on('mousemove', - function(d) { - // TODO - }) - .on('mouseleave', - function(d) { - Tooltip.style('opacity', - 0) d3.select(this).style('stroke', 'none') - d3.selectAll('.bases').style( - 'opacity', '1') - }) - .on('click', function(d, i) { - // TODO - console.log(d); - console.log(i); - }); - */ - - // Add base text inside the square - svg.append('text') - .attr('x', x_pos + halfway) - .attr('y', y_pos + halfway) - .text(base) - // Center text char in square - .attr('text-anchor', 'middle') - .attr('dominant-baseline', 'central') - .style('fill', 'white') - .style('font-size', '20px'); - - // Increment x_pos by width of square to place next - // square - x_pos += (square_dims + buffer_btw_bases); - } + var position_data = []; + var colors; + + // Get reference and trio snps at each position + if (d != null) { + for (const [key, value] of Object.entries(d['SNPS'])) { + position_data.push(parseInt(key)); + } + var Tooltip = d3.select('#tracks') + .append('div') + .style('opacity', 0) + .attr('class', 'tooltip') + .style('background-color', 'white') + .style('border', 'solid') + .style('border-width', '2px') + .style('border-radius', '5px') + + var color_base_mapping = { + 'G': d['COLOR']['g'], + 'C': d['COLOR']['c'], + 'T': d['COLOR']['t'], + 'A': d['COLOR']['a'] + }; + + var base; + var color; + var num_snps = snps.length; + var x_pos = starting_x_pos; + i = 0 + for (base of snps) { + if (is_reference == true) { + // Fill reference bases with dark grey squares + color = d['COLOR']['reference_track']; + } else { + // Check if reference_snps parameter passed, if + // so assume reference_snps.length = snps.length + // (also guaranteed by backend data + // preprossessing) + if (!(reference_snps.length == 0)) { + // If alternate allele, color according + // to base type + if (base != reference_snps[i]) { + color = color_base_mapping[base]; + } + // Otherwise if base matches reference, + // color base same as reference + else { + color = d['COLOR']['base_matching_reference']; + } } + } + + // Move to next base in reference sequence + ++i; + + let halfway = square_dims / 2; + + // Append square base inside specific svg track + var bases_rect = svg.append('rect') + .data(position_data) + .attr('class', 'bases') + .attr('x', x_pos) + .attr('y', y_pos) + .attr('height', square_dims) + .attr('width', square_dims) + .attr('fill', color) + .each(function(d, i) {}); + + /* TODO: Add interactivity to each SNV position + .on('mouseover', + function(d) { + d3.selectAll('.bases') + .style('opacity', + '0.2') .filter(function() { return !this.classList + .contains( + 'active') + }) + d3.select(this) + .style('stroke', + '#4169E1') .attr('stroke-width', '2') + .style('opacity', + '1') + }) + .on('mousemove', + function(d) { + // TODO + }) + .on('mouseleave', + function(d) { + Tooltip.style('opacity', + 0) d3.select(this).style('stroke', 'none') + d3.selectAll('.bases').style( + 'opacity', '1') + }) + .on('click', function(d, i) { + // TODO + console.log(d); + console.log(i); + }); + */ + + // Add base text inside the square + svg.append('text') + .attr('x', x_pos + halfway) + .attr('y', y_pos + halfway) + .text(base) + // Center text char in square + .attr('text-anchor', 'middle') + .attr('dominant-baseline', 'central') + .style('fill', 'white') + .style('font-size', '20px'); + + // Increment x_pos by width of square to place next + // square + x_pos += (square_dims + buffer_btw_bases); + } + } } // TODO: Old, remove function draw_coordinate_line(track_svg, x_coordinate) { - track_svg.append('line') - .attr('x1', track_x_position + x_coordinate) - .attr('x2', track_x_position + x_coordinate) - .attr('y1', border_height - coordinate_outer_buffer) - .attr( - 'y2', - border_height - coordinate_outer_buffer + - coordinate_track_height) - .attr('stroke', '#dd760b'); + track_svg.append('line') + .attr('x1', track_x_position + x_coordinate) + .attr('x2', track_x_position + x_coordinate) + .attr('y1', border_height - coordinate_outer_buffer) + .attr( + 'y2', + border_height - coordinate_outer_buffer + coordinate_track_height) + .attr('stroke', '#dd760b'); } function add_coordinate_track( track_svg, y_position, data, square_dims, container_width) { - // New y position to update, and return - var new_y = y_position; - - // Get all genomic positions - var snp_positions = []; - var scale_width = container_width - (track_x_position * 2); - - for (const [key, value] of Object.entries(data['SNPS'])) { - snp_positions.push(parseInt(key)); - } - // Add reference at bottom of border - track_svg.append('rect') - .attr('x', track_x_position) - .attr('y', border_height - coordinate_outer_buffer) - .attr('width', scale_width) - .attr('height', coordinate_track_height) - //.attr('fill', '#b2b2b2'); - .attr('fill', '#dadada'); - - var zero = 0; - var x_axis_height = - border_height - coordinate_outer_buffer + coordinate_track_height; - var x_axis_pos = [zero.toString(), x_axis_height.toString()].join(','); - - var x = d3.scaleLinear().domain([0, data['GENOME_SIZE']]).rangeRound([ - track_x_position, (container_width - track_x_position) - ]); - - // Draw axis - // TODO: Make vertical position a parameter - track_svg.append('g') - .attr( - 'transform', - 'translate(0,600)') // vertical position - .call(d3.axisBottom(x).tickValues(data['GENOMIC_RANGE'])); - - // Create x-axis label for coordinate track - track_svg.append('text') - .attr('class', 'x label') - .attr('text-anchor', 'end') - .attr('x', 280) - .attr('y', border_height - 90) - .attr('font-weight', 700) - .style('font-size', '20px') - .text('Genomic Coordinate'); - - // Create x-axis label for gene region track - track_svg.append('text') - .attr('class', 'x label') - .attr('text-anchor', 'end') - .attr('x', 280) - .attr('y', border_height - 35) - .attr('font-weight', 700) - .style('font-size', '20px') - .text('Gene Annotations'); - - var breakpoint_data = data['BREAKPOINTS']; - - /* Do not display breakpoint intervals on coordinate track for now - track_svg.append('rect') - .data([breakpoint_data]) - .attr( - 'width', - function(d) { - try { - return ( - x(d['breakpoint1'].end) - - x(d['breakpoint1'].xpos)) - } catch (error) { - return 0 - } - }) - .attr('height', coordinate_track_height) - .attr( - 'x', - function(d) { - try { - return x(d['breakpoint1'].xpos) - } catch (error) { - return 0 - } - }) - .attr('y', border_height - coordinate_outer_buffer) - //.attr('fill', '#b2b2b2') - .attr('fill', '#dadada') - - //.transition() - // Delay of the transition - //.delay(1000) - //.duration(3000) - .attr('fill', data['COLOR']['breakpoint_intervals']); - - track_svg.append('rect') - .data([breakpoint_data]) - .attr( - 'width', - function(d) { - try { - return ( - x(d['breakpoint2'].end) - - x(d['breakpoint2'].xpos)) - } catch (error) { - return 0 - } - }) - .attr('height', coordinate_track_height) - .attr( - 'x', - function(d) { - try { - return x(d['breakpoint2'].xpos) - } catch (error) { - return 0 - } - }) - .attr('y', border_height - coordinate_outer_buffer) - //.attr('fill', '#b2b2b2') - .attr('fill', '#dadada') - - //.transition() - //.delay(1000) - //.duration(3000) - - .attr('fill', data['COLOR']['breakpoint_intervals']); - */ - - // Drawing polygons - const dot = track_svg.append('g') - .selectAll('dot') - .data(snp_positions) - .enter() - .append('circle') - .attr('cx', (d) => x(d)) - .attr('cy', border_height - coordinate_outer_buffer) - .attr('r', 0.1) - .style('fill', 'darkblue') - - var lines = - track_svg.selectAll('lines') - .data(snp_positions) - .enter() - .append('line') - .attr('class', 'lines') - .attr('x1', (d) => x(d)) - .attr('x2', (d) => x(d)) - .attr('y1', border_height - coordinate_outer_buffer) - .attr( - 'y2', - border_height - coordinate_outer_buffer + - coordinate_track_height) - .attr('stroke-width', '1.0') - // Starting color same as coordinate track - //.attr('stroke', '#b2b2b2') - .attr('stroke', '#dadada') - - //.transition() - // Added a delay so the lines would appear in sequential - // increasing order - //.duration(function(d, i) { - // var delay = i * 200; - // return delay; - //}) - - // Fade in line posiiton color - // Speed slightly faster than matching polygons. - .attr('stroke', '#dd760b'); // Orange line for snp positions - - // Get informative site positions - var info_sites = data['INFO_SITES']; - - var num = 0; - var circle = - track_svg.selectAll('polygon') - .data(snp_positions) - .enter() - .append('polygon') - .attr( - 'points', - function(d, i) { - var x_offset = x(d); - let left_y = - border_height - coordinate_outer_buffer; - let left = [ - x_offset.toString(), left_y.toString() - ].join(','); - - let top_y = border_height - - coordinate_outer_buffer - polygon_buffer + - square_dims - buffer_btw_tracks; - // (square_dims*i) gives offset for - // num square - let top_x = track_x_position + - ((i * square_dims) + (i * buffer_btw_tracks)); - let _top = - [top_x.toString(), top_y.toString()].join(','); - - let right_y = border_height - - coordinate_outer_buffer - polygon_buffer + - square_dims - buffer_btw_tracks; - let right_x = track_x_position + - (i * square_dims + (i * buffer_btw_tracks)) + - square_dims; - let right = [ - right_x.toString(), right_y.toString() - ].join(','); - - // Join all points - // together in formatted - // points string - polygon_points = [left, _top, right].join(' '); - - - return polygon_points; - }) - .attr('fill', '#FFFFFF') - - //.transition() - //.duration(function(d, i) { - // var delay = i * 200; - // return delay; - //}) - - .attr('fill', function(d, i) { - var color; - if (d.toString() in info_sites) { - var match = info_sites[d]; - color = - determine_informative(match, data['COLOR']); - } else { - // Otherwise not an informative site, - // don't highlight - color = data['COLOR']['non_informative_site']; - } - return color; - }); - - let region_names = Object.keys(data['REGION_DATA']); - let region_data = data['REGION_DATA']; - draw_genomic_region(track_svg, region_names, region_data, x); - - // Display color legend left of visualization, below track labels - display_legend(track_svg, data['COLOR']); - - return [track_svg, border_height - coordinate_outer_buffer]; + // New y position to update, and return + var new_y = y_position; + + // Get all genomic positions + var snp_positions = []; + var scale_width = container_width - (track_x_position * 2); + + for (const [key, value] of Object.entries(data['SNPS'])) { + snp_positions.push(parseInt(key)); + } + // Add reference at bottom of border + track_svg.append('rect') + .attr('x', track_x_position) + .attr('y', border_height - coordinate_outer_buffer) + .attr('width', scale_width) + .attr('height', coordinate_track_height) + //.attr('fill', '#b2b2b2'); + .attr('fill', '#dadada'); + + var zero = 0; + var x_axis_height = + border_height - coordinate_outer_buffer + coordinate_track_height; + var x_axis_pos = [zero.toString(), x_axis_height.toString()].join(','); + + var x = d3.scaleLinear().domain([0, data['GENOME_SIZE']]).rangeRound([ + track_x_position, (container_width - track_x_position) + ]); + + // Draw axis + // TODO: Make vertical position a parameter + track_svg.append('g') + .attr( + 'transform', + 'translate(0,600)') // vertical position + .call(d3.axisBottom(x).tickValues(data['GENOMIC_RANGE'])); + + // Create x-axis label for coordinate track + track_svg.append('text') + .attr('class', 'x label') + .attr('text-anchor', 'end') + .attr('x', 280) + .attr('y', border_height - 90) + .attr('font-weight', 700) + .style('font-size', '20px') + .text('Genomic Coordinate'); + + // Create x-axis label for gene region track + track_svg.append('text') + .attr('class', 'x label') + .attr('text-anchor', 'end') + .attr('x', 280) + .attr('y', border_height - 35) + .attr('font-weight', 700) + .style('font-size', '20px') + .text('Gene Annotations'); + + var breakpoint_data = data['BREAKPOINTS']; + + /* Do not display breakpoint intervals on coordinate track for now + track_svg.append('rect') + .data([breakpoint_data]) + .attr( + 'width', + function(d) { + try { + return ( + x(d['breakpoint1'].end) - + x(d['breakpoint1'].xpos)) + } catch (error) { + return 0 + } + }) + .attr('height', coordinate_track_height) + .attr( + 'x', + function(d) { + try { + return x(d['breakpoint1'].xpos) + } catch (error) { + return 0 + } + }) + .attr('y', border_height - coordinate_outer_buffer) + //.attr('fill', '#b2b2b2') + .attr('fill', '#dadada') + + //.transition() + // Delay of the transition + //.delay(1000) + //.duration(3000) + .attr('fill', data['COLOR']['breakpoint_intervals']); + + track_svg.append('rect') + .data([breakpoint_data]) + .attr( + 'width', + function(d) { + try { + return ( + x(d['breakpoint2'].end) - + x(d['breakpoint2'].xpos)) + } catch (error) { + return 0 + } + }) + .attr('height', coordinate_track_height) + .attr( + 'x', + function(d) { + try { + return x(d['breakpoint2'].xpos) + } catch (error) { + return 0 + } + }) + .attr('y', border_height - coordinate_outer_buffer) + //.attr('fill', '#b2b2b2') + .attr('fill', '#dadada') + + //.transition() + //.delay(1000) + //.duration(3000) + + .attr('fill', data['COLOR']['breakpoint_intervals']); + */ + + // Drawing polygons + const dot = track_svg.append('g') + .selectAll('dot') + .data(snp_positions) + .enter() + .append('circle') + .attr('cx', (d) => x(d)) + .attr('cy', border_height - coordinate_outer_buffer) + .attr('r', 0.1) + .style('fill', 'darkblue') + + var lines = + track_svg.selectAll('lines') + .data(snp_positions) + .enter() + .append('line') + .attr('class', 'lines') + .attr('x1', (d) => x(d)) + .attr('x2', (d) => x(d)) + .attr('y1', border_height - coordinate_outer_buffer) + .attr( + 'y2', + border_height - coordinate_outer_buffer + coordinate_track_height) + .attr('stroke-width', '1.0') + // Starting color same as coordinate track + //.attr('stroke', '#b2b2b2') + .attr('stroke', '#dadada') + + //.transition() + // Added a delay so the lines would appear in sequential + // increasing order + //.duration(function(d, i) { + // var delay = i * 200; + // return delay; + //}) + + // Fade in line posiiton color + // Speed slightly faster than matching polygons. + .attr('stroke', '#dd760b'); // Orange line for snp positions + + // Get informative site positions + var info_sites = data['INFO_SITES']; + + var num = 0; + var circle = + track_svg.selectAll('polygon') + .data(snp_positions) + .enter() + .append('polygon') + .attr( + 'points', + function(d, i) { + var x_offset = x(d); + let left_y = border_height - coordinate_outer_buffer; + let left = [x_offset.toString(), left_y.toString()].join(','); + + let top_y = border_height - coordinate_outer_buffer - + polygon_buffer + square_dims - buffer_btw_tracks; + // (square_dims*i) gives offset for + // num square + let top_x = track_x_position + + ((i * square_dims) + (i * buffer_btw_tracks)); + let _top = [top_x.toString(), top_y.toString()].join(','); + + let right_y = border_height - coordinate_outer_buffer - + polygon_buffer + square_dims - buffer_btw_tracks; + let right_x = track_x_position + + (i * square_dims + (i * buffer_btw_tracks)) + square_dims; + let right = [right_x.toString(), right_y.toString()].join(','); + + // Join all points + // together in formatted + // points string + polygon_points = [left, _top, right].join(' '); + + + return polygon_points; + }) + .attr('fill', '#FFFFFF') + + //.transition() + //.duration(function(d, i) { + // var delay = i * 200; + // return delay; + //}) + + .attr('fill', function(d, i) { + var color; + if (d.toString() in info_sites) { + var match = info_sites[d]; + color = determine_informative(match, data['COLOR']); + } else { + // Otherwise not an informative site, + // don't highlight + color = data['COLOR']['non_informative_site']; + } + return color; + }); + + let region_names = Object.keys(data['REGION_DATA']); + let region_data = data['REGION_DATA']; + draw_genomic_region(track_svg, region_names, region_data, x); + + // Display color legend left of visualization, below track labels + display_legend(track_svg, data['COLOR']); + + return [track_svg, border_height - coordinate_outer_buffer]; } function add_reference_track( track_svg, polygon_buffer, y_position, square_dims, reference_snps, snp_positions, d, container_width) { - // New y position to update and return - var new_y = y_position; - var color = d['COLOR']['reference_track']; - - var scale_width = container_width - (track_x_position * 2); - - track_svg.append('rect') - .attr('x', track_x_position) - .attr('y', new_y - polygon_buffer - buffer_btw_tracks) - .attr('width', scale_width) - // The track height scales based on even paritioning of - // square dimensions - .attr('height', square_dims) - .attr('fill', '#FFFFFF'); - - track_svg.append('text') - .attr('x', 0) - .attr('y', track_x_position + square_dims) - .style('font-size', '20px'); - - var y_pos = new_y - polygon_buffer - buffer_btw_tracks; - - add_bases_to_track( - track_svg, reference_snps, track_x_position, y_pos, square_dims, - true, reference_snps, d); - - var top_line = 0; - var left_square = 0; - var right_square = 0; - - // Add label to left side of reference track - var ref_label_text = 'Reference'; - var label_node_id = 'None'; - add_track_label( - track_svg, new_y - polygon_buffer - buffer_btw_tracks, square_dims, - ref_label_text, label_node_id, d); - - return [ - track_svg, - new_y - buffer_btw_tracks - polygon_buffer - square_dims - ]; + // New y position to update and return + var new_y = y_position; + var color = d['COLOR']['reference_track']; + + var scale_width = container_width - (track_x_position * 2); + + track_svg.append('rect') + .attr('x', track_x_position) + .attr('y', new_y - polygon_buffer - buffer_btw_tracks) + .attr('width', scale_width) + // The track height scales based on even paritioning of + // square dimensions + .attr('height', square_dims) + .attr('fill', '#FFFFFF'); + + track_svg.append('text') + .attr('x', 0) + .attr('y', track_x_position + square_dims) + .style('font-size', '20px'); + + var y_pos = new_y - polygon_buffer - buffer_btw_tracks; + + add_bases_to_track( + track_svg, reference_snps, track_x_position, y_pos, square_dims, true, + reference_snps, d); + + var top_line = 0; + var left_square = 0; + var right_square = 0; + + // Add label to left side of reference track + var ref_label_text = 'Reference'; + var label_node_id = 'None'; + add_track_label( + track_svg, new_y - polygon_buffer - buffer_btw_tracks, square_dims, + ref_label_text, label_node_id, d); + + return [track_svg, new_y - buffer_btw_tracks - polygon_buffer - square_dims]; } // TODO: Redundant, create templated function function add_trio_track( track_svg, y_position, square_dims, d, container_width) { - // New y position to update and return - var new_y = y_position; - var color = '#c6c6c6'; - var donor_snps = []; - var recomb_snps = []; - var acceptor_snps = []; - var reference_snps = []; - - var scale_width = container_width - (track_x_position * 2); - - for (const [key, value] of Object.entries(d['SNPS'])) { - donor_snps.push(value['Donor']); - recomb_snps.push(value['Recomb']); - acceptor_snps.push(value['Acceptor']); - reference_snps.push(value['Reference']); - } - - // Add donor track - track_svg.append('rect') - .attr('x', track_x_position) - .attr('y', new_y - buffer_btw_tracks) - .attr('width', scale_width) - .attr('height', square_dims) - .attr('fill', '#FFFFFF'); - - // Add snps to donor track - add_bases_to_track( - track_svg, donor_snps, track_x_position, new_y - buffer_btw_tracks, - square_dims, false, reference_snps, d); - - // Add label to donor track - var donor_label_text = 'Donor'; - var label_node_id = d['NODE_IDS']['Donor']; - add_track_label( - track_svg, new_y - buffer_btw_tracks, square_dims, donor_label_text, - label_node_id, d); - - new_y -= (square_dims + buffer_btw_tracks); - - // Add recombinant track - track_svg.append('rect') - .attr('x', track_x_position) - .attr('y', new_y - buffer_btw_tracks) - .attr('width', track_width) - .attr('height', square_dims) - .attr('fill', '#FFFFFF'); - - // Add snps to recomb track - add_bases_to_track( - track_svg, recomb_snps, track_x_position, new_y - buffer_btw_tracks, - square_dims, false, reference_snps, d); - - var recomb_label_text = 'Recombinant'; - var label_node_id = d['NODE_IDS']['Recomb']; - add_track_label( - track_svg, new_y - buffer_btw_tracks, square_dims, - recomb_label_text, label_node_id, d); - - new_y -= (square_dims + buffer_btw_tracks); - - // Add acceptor track - track_svg.append('rect') - .attr('x', track_x_position) - .attr('y', new_y - buffer_btw_tracks) - .attr('width', track_width) - .attr('height', square_dims) - .attr('fill', '#FFFFFF'); - - // Add snps to acceptor track - add_bases_to_track( - track_svg, acceptor_snps, track_x_position, - new_y - buffer_btw_tracks, square_dims, false, reference_snps, d); - - - var acceptor_label_text = 'Acceptor'; - var label_node_id = d['NODE_IDS']['Acceptor']; - add_track_label( - track_svg, new_y - buffer_btw_tracks, square_dims, - acceptor_label_text, label_node_id, d); - - // TODO: Redundant - var snp_positions = []; - for (const [key, value] of Object.entries(d['SNPS'])) { - snp_positions.push(key); + // New y position to update and return + var new_y = y_position; + var color = '#c6c6c6'; + var donor_snps = []; + var recomb_snps = []; + var acceptor_snps = []; + var reference_snps = []; + + var scale_width = container_width - (track_x_position * 2); + + for (const [key, value] of Object.entries(d['SNPS'])) { + donor_snps.push(value['Donor']); + recomb_snps.push(value['Recomb']); + acceptor_snps.push(value['Acceptor']); + reference_snps.push(value['Reference']); + } + + // Add donor track + track_svg.append('rect') + .attr('x', track_x_position) + .attr('y', new_y - buffer_btw_tracks) + .attr('width', scale_width) + .attr('height', square_dims) + .attr('fill', '#FFFFFF'); + + // Add snps to donor track + add_bases_to_track( + track_svg, donor_snps, track_x_position, new_y - buffer_btw_tracks, + square_dims, false, reference_snps, d); + + // Add label to donor track + var donor_label_text = 'Donor'; + var label_node_id = d['NODE_IDS']['Donor']; + add_track_label( + track_svg, new_y - buffer_btw_tracks, square_dims, donor_label_text, + label_node_id, d); + + new_y -= (square_dims + buffer_btw_tracks); + + // Add recombinant track + track_svg.append('rect') + .attr('x', track_x_position) + .attr('y', new_y - buffer_btw_tracks) + .attr('width', track_width) + .attr('height', square_dims) + .attr('fill', '#FFFFFF'); + + // Add snps to recomb track + add_bases_to_track( + track_svg, recomb_snps, track_x_position, new_y - buffer_btw_tracks, + square_dims, false, reference_snps, d); + + var recomb_label_text = 'Recombinant'; + var label_node_id = d['NODE_IDS']['Recomb']; + add_track_label( + track_svg, new_y - buffer_btw_tracks, square_dims, recomb_label_text, + label_node_id, d); + + new_y -= (square_dims + buffer_btw_tracks); + + // Add acceptor track + track_svg.append('rect') + .attr('x', track_x_position) + .attr('y', new_y - buffer_btw_tracks) + .attr('width', track_width) + .attr('height', square_dims) + .attr('fill', '#FFFFFF'); + + // Add snps to acceptor track + add_bases_to_track( + track_svg, acceptor_snps, track_x_position, new_y - buffer_btw_tracks, + square_dims, false, reference_snps, d); + + + var acceptor_label_text = 'Acceptor'; + var label_node_id = d['NODE_IDS']['Acceptor']; + add_track_label( + track_svg, new_y - buffer_btw_tracks, square_dims, acceptor_label_text, + label_node_id, d); + + // TODO: Redundant + var snp_positions = []; + for (const [key, value] of Object.entries(d['SNPS'])) { + snp_positions.push(key); + } + + var num_snps = acceptor_snps.length; + var label_width = + (num_snps * square_dims + (num_snps - 1) * buffer_btw_bases) + + track_x_position; + + var x = d3.scaleBand().domain(snp_positions).range([ + track_x_position, label_width + ]); + + var info_sites = d['INFO_SITES']; + var colors = d['COLOR']; + + track_svg.append('g') + .attr('class', 'TopAxis') + .data([snp_positions]) + .attr('transform', `translate(0,${new_y - buffer_btw_tracks - 5})`) + .call(d3.axisTop(x)) + .selectAll('text') + .style('text-anchor', 'start') + .attr('dx', '1em') + .attr('dy', '.2em') + .style('font-size', '15px') + .attr('transform', 'rotate(-65)') + .style('fill', function(d, i) { + var color; + if (d.toString() in info_sites) { + var match = info_sites[d]; + color = determine_informative(match, colors); + } else { + // Otherwise not an informative site, don't + // highlight + color = colors['non_informative_site']; } - - var num_snps = acceptor_snps.length; - var label_width = - (num_snps * square_dims + (num_snps - 1) * buffer_btw_bases) + - track_x_position; - - var x = d3.scaleBand().domain(snp_positions).range([ - track_x_position, label_width - ]); - - var info_sites = d['INFO_SITES']; - var colors = d['COLOR']; - - track_svg.append('g') - .attr('class', 'TopAxis') - .data([snp_positions]) - .attr('transform', `translate(0,${new_y - buffer_btw_tracks - 5})`) - .call(d3.axisTop(x)) - .selectAll('text') - .style('text-anchor', 'start') - .attr('dx', '1em') - .attr('dy', '.2em') - .style('font-size', '15px') - .attr('transform', 'rotate(-65)') - .style('fill', function(d, i) { - var color; - if (d.toString() in info_sites) { - var match = info_sites[d]; - color = determine_informative(match, colors); - } else { - // Otherwise not an informative site, don't - // highlight - color = colors['non_informative_site']; - } - return color; - }); - track_svg.selectAll('.topAxis path').style('stroke', '#FFFFFF'); - /* TODO: Mouseover/click to column position labels - .on('mouseover', - function(d) { - d3.select(this).attr('fill', '#ff0000'); - }) - */ - - // Set new y position (to add further tracks above if needed) - new_y -= (square_dims + buffer_btw_tracks); - - return [track_svg, new_y]; + return color; + }); + track_svg.selectAll('.topAxis path').style('stroke', '#FFFFFF'); + /* TODO: Mouseover/click to column position labels + .on('mouseover', + function(d) { + d3.select(this).attr('fill', '#ff0000'); + }) + */ + + // Set new y position (to add further tracks above if needed) + new_y -= (square_dims + buffer_btw_tracks); + + return [track_svg, new_y]; } function add_coordinate_axis(svg, margin, width, height) { - // Create the scale - var x = d3.scaleLinear().domain([0, 29903]).range([ - track_x_position, track_width + track_x_position - ]); - - svg.append('g') - .attr('transform', 'translate(0,270)') - .call(d3.axisBottom(x)); + // Create the scale + var x = d3.scaleLinear().domain([0, 29903]).range([ + track_x_position, track_width + track_x_position + ]); + + svg.append('g').attr('transform', 'translate(0,270)').call(d3.axisBottom(x)); } diff --git a/static/viz.js b/static/viz.js index 10fbde2..861031a 100644 --- a/static/viz.js +++ b/static/viz.js @@ -1,396 +1,370 @@ function graph() { - var svg; - var div; - var legend; - - // Set constants for tracks - var border_color = 'black'; - const buffer_btw_tracks = 5; - const buffer_btw_bases = 5; - const outer_buffer = 50; // Enough to be larger than height of track - const label_halfway = 20; - const polygon_buffer = 150; - const trio_block_buffer = 25; - const track_height = 25; - const track_width = 1750; - const track_x_position = 300; - const track_y_position = border_height; // Plus some offset upwards - const num_tracks = 0; - - const margin = {top: 100, right: 100, bottom: 100, left: 100}, - width = track_width - height = border_height - outer_buffer; - - function track(d) { - // Clear previous track visualization elements - div.selectAll('svg').remove(); - - var y_position = border_height; - var snp_positions = []; - var reference_snps = []; - - // Get reference and trio snps at each position - for (const [key, value] of Object.entries(d['SNPS'])) { - snp_positions.push(parseInt(key)); - reference_snps.push(value['Reference']); - } - var num_snps = reference_snps.length; - var fixed_square_dims = 30; - var medium_track_width = track_width; - var square_dims; - var container_width; - - // Error handle zero SNPS - if (num_snps == 0) { - handle_zero_snps(div); - return; - } - // Less than 20 snps, scale down medium track size, make base - // sizes slightly larger - if (num_snps < 20) { - container_width = medium_track_width; - square_dims = 40; - } - // Between 20-40 snps: Keep container width and base size fixed - if (num_snps >= 20 && num_snps <= 40) { - square_dims = 30; - container_width = medium_track_width; - } - // >40 snps: container width becomes variable, - // snp base sizes fixed - if (num_snps > 40) { - container_width = (buffer_btw_bases * (num_snps - 1)) + - (num_snps * fixed_square_dims) + track_x_position; - square_dims = fixed_square_dims; - } - - // Variable svg container width based on - // number of snps - var container = div.append('svg') - .attr('id', 'inner_SVG') - .attr('width', container_width) - .attr('height', 700); - - // Add title to SNP visualization plot - container.append('text') - .attr('id', 'plot_title') - .attr('x', 300) - .attr('y', 120) - .attr('text-anchor', 'start') - .style('font-size', '30px') - .text( - 'Single-nucleotide variation in the recombinant and its parents'); - - document.getElementById('download_svg').style.visibility = - 'visible'; - // document.getElementById('copy_svg').style.visibility = - //'visible'; - document.getElementById('next_button').style.visibility = - 'visible'; - document.getElementById('previous_button').style.visibility = - 'visible'; - - // Disable overview side panel for local rivet for now - if (d['COLOR']['environment'] == 'production') { - let tree = tree_selected(); - - // Summary side panel generated for each selected row - const overview = document.getElementById('summary'); - // Clear any previous text elements from overview div - overview.textContent = ''; - - // Append overview header - var overview_header = document.createElement('h3'); - const header_text = document.createTextNode('Overview'); - overview_header.setAttribute('id', 'overview_header'); - overview_header.appendChild(header_text); - overview.appendChild(overview_header); - - // Get data from table for each of these fields - fetch('/get_overview', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify( - {'id': d['ID'], 'tree_type': tree}) - }).then(res => { - res.json().then(data => { - append_text( - overview, - 'Recombinant Node ID: ' + - d['NODE_IDS']['Recomb']); - append_text( - overview, - 'Current Recombinant Lineage Designation: ' + - data['overview'] - ['recomb_lineage']); - append_text( - overview, - 'Recombinant Origin Date: ' + - data['overview'] - ['recomb_date']); - append_text( - overview, - 'Recombinant Between: ' + - data['overview'] - ['donor_lineage'] + - ' and ' + - data['overview'] - ['acceptor_lineage']); - append_text( - overview, - 'Number Sequences: ' + - data['overview']['num_desc'] - .toLocaleString()); - - // Adding linebreaks for additional - // buttons - var linebreak = - document.createElement('br'); - linebreak.style.lineHeight = '14'; - overview.appendChild(linebreak); - }); - }); - - // Append "More info" button in overview section - var overview_info_button = - document.createElement('button'); - overview_info_button.setAttribute( - 'class', 'btn btn-outline-primary'); - overview_info_button.setAttribute('id', 'info'); - const info_btn_text = - document.createTextNode('More Info'); - overview_info_button.appendChild(info_btn_text); - overview.appendChild(overview_info_button); - - // Toggle offcanvas right - // Handle next/prev result button input - if (overview_info_button) { - var overview_info = - overview_info_button.onclick = function() { - var myOffcanvas = - document.getElementById( - 'off_canvas_right') - var bsOffcanvas = - new bootstrap.Offcanvas( - myOffcanvas); - bsOffcanvas.show(); - get_detailed_overview( - overview, d, tree); - } - } - - // Append "View UShER" button in overview section - var overview_usher_button = - document.createElement('button'); - overview_usher_button.setAttribute( - 'class', 'btn btn-outline-primary'); - overview_usher_button.setAttribute('id', 'view_usher'); - const usher_btn_text = - document.createTextNode('View UShER Tree'); - overview_usher_button.appendChild(usher_btn_text); - overview.appendChild(overview_usher_button); - - const tooltip = - d3.select('#view_usher') - .append('div') - .style('position', 'absolute') - .style('visibility', 'hidden') - .style('padding', '15px') - .style('background', 'rgba(0,0,0,0.6)') - .style('border-radius', '5px') - .style('color', 'white'); - - if (overview_usher_button) { - overview_usher_button.onclick = function() { - view_usher_tree(d); - }; - } - - /* + var svg; + var div; + var legend; + + // Set constants for tracks + var border_color = 'black'; + const buffer_btw_tracks = 5; + const buffer_btw_bases = 5; + const outer_buffer = 50; // Enough to be larger than height of track + const label_halfway = 20; + const polygon_buffer = 150; + const trio_block_buffer = 25; + const track_height = 25; + const track_width = 1750; + const track_x_position = 300; + const track_y_position = border_height; // Plus some offset upwards + const num_tracks = 0; + const right_side_buffer = 10; // Extra space on right for amino acid view + + const margin = {top: 100, right: 100, bottom: 100, left: 100}, + width = track_width + height = border_height - outer_buffer; + + function track(d) { + // Clear previous track visualization elements + div.selectAll('svg').remove(); + + var y_position = border_height; + var snp_positions = []; + var reference_snps = []; + + // Get reference and trio snps at each position + for (const [key, value] of Object.entries(d['SNPS'])) { + snp_positions.push(parseInt(key)); + reference_snps.push(value['Reference']); + } + var num_snps = reference_snps.length; + var fixed_square_dims = 30; + var medium_track_width = track_width; + var square_dims; + var container_width; + + // Error handle zero SNPS + if (num_snps == 0) { + handle_zero_snps(div); + return; + } + // Less than 20 snps, scale down medium track size, make base + // sizes slightly larger + if (num_snps < 20) { + container_width = medium_track_width; + square_dims = 40; + } + // Between 20-40 snps: Keep container width and base size fixed + if (num_snps >= 20 && num_snps <= 40) { + square_dims = 30; + container_width = medium_track_width; + } + // >40 snps: container width becomes variable, + // snp base sizes fixed + if (num_snps > 40) { + container_width = (buffer_btw_bases * (num_snps - 1)) + + (num_snps * fixed_square_dims) + track_x_position + right_side_buffer; + square_dims = fixed_square_dims; + } + + // Variable svg container width based on + // number of snps + var container = div.append('svg') + .attr('id', 'inner_SVG') + .attr('width', container_width) + .attr('height', 700); + + // Add title to SNP visualization plot + container.append('text') + .attr('id', 'plot_title') + .attr('x', 300) + .attr('y', 120) + .attr('text-anchor', 'start') + .style('font-size', '30px') + .text('Single-nucleotide variation in the recombinant and its parents'); + + document.getElementById('download_svg').style.visibility = 'visible'; + // document.getElementById('copy_svg').style.visibility = + //'visible'; + document.getElementById('next_button').style.visibility = 'visible'; + document.getElementById('previous_button').style.visibility = 'visible'; + + // Disable overview side panel for local rivet for now + if (d['COLOR']['environment'] == 'production') { + let tree = tree_selected(); + + // Summary side panel generated for each selected row + const overview = document.getElementById('summary'); + // Clear any previous text elements from overview div + overview.textContent = ''; + + // Append overview header + var overview_header = document.createElement('h3'); + const header_text = document.createTextNode('Overview'); + overview_header.setAttribute('id', 'overview_header'); + overview_header.appendChild(header_text); + overview.appendChild(overview_header); + + // Get data from table for each of these fields + fetch('/get_overview', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({'id': d['ID'], 'tree_type': tree}) + }).then(res => { + res.json().then(data => { + append_text( + overview, 'Recombinant Node ID: ' + d['NODE_IDS']['Recomb']); + append_text( + overview, + 'Current Recombinant Lineage Designation: ' + + data['overview']['recomb_lineage']); + append_text( + overview, + 'Recombinant Origin Date: ' + data['overview']['recomb_date']); + append_text( + overview, + 'Recombinant Between: ' + data['overview']['donor_lineage'] + + ' and ' + data['overview']['acceptor_lineage']); + append_text( + overview, + 'Number Sequences: ' + + data['overview']['num_desc'].toLocaleString()); + + // Adding linebreaks for additional + // buttons + var linebreak = document.createElement('br'); + linebreak.style.lineHeight = '14'; + overview.appendChild(linebreak); + }); + }); + + // Append "More info" button in overview section + var overview_info_button = document.createElement('button'); + overview_info_button.setAttribute('class', 'btn btn-outline-primary'); + overview_info_button.setAttribute('id', 'info'); + const info_btn_text = document.createTextNode('More Info'); + overview_info_button.appendChild(info_btn_text); + overview.appendChild(overview_info_button); + + // Toggle offcanvas right + // Handle next/prev result button input + if (overview_info_button) { + var overview_info = overview_info_button.onclick = function() { + var myOffcanvas = document.getElementById('off_canvas_right') + var bsOffcanvas = new bootstrap.Offcanvas(myOffcanvas); + bsOffcanvas.show(); + get_detailed_overview(overview, d, tree); + } + } + + // Append "View UShER" button in overview section + let overview_usher_button = document.createElement('button'); + overview_usher_button.setAttribute('class', 'btn btn-outline-primary'); + overview_usher_button.setAttribute('id', 'view_usher'); + const usher_btn_text = document.createTextNode('View UShER Tree (Recomb)'); + overview_usher_button.appendChild(usher_btn_text); + overview.appendChild(overview_usher_button); + + //TODO: Experimental, donor/acceptor sampling, refactor + let overview_usher_button_donor = document.createElement('button'); + overview_usher_button_donor.setAttribute('class', 'btn btn-outline-primary'); + overview_usher_button_donor.setAttribute('id', 'view_usher_donor'); + const usher_btn_text_donor = document.createTextNode('View UShER Tree (Donor)'); + overview_usher_button_donor.appendChild(usher_btn_text_donor); + overview.appendChild(overview_usher_button_donor); + + let overview_usher_button_acceptor = document.createElement('button'); + overview_usher_button_acceptor.setAttribute('class', 'btn btn-outline-primary'); + overview_usher_button_acceptor.setAttribute('id', 'view_usher_acceptor'); + const usher_btn_text_acceptor = document.createTextNode('View UShER Tree (Acceptor)'); + overview_usher_button_acceptor.appendChild(usher_btn_text_acceptor); + overview.appendChild(overview_usher_button_acceptor); + + + const tooltip = d3.select('#view_usher') + .append('div') + .style('position', 'absolute') + .style('visibility', 'hidden') + .style('padding', '15px') + .style('background', 'rgba(0,0,0,0.6)') + .style('border-radius', '5px') + .style('color', 'white'); + + if (overview_usher_button) { + overview_usher_button.onclick = function() { + view_usher_tree(d, "Recomb"); + }; + } + if (overview_usher_button_donor) { + overview_usher_button_donor.onclick = function() { + view_usher_tree(d, "Donor"); + }; + } + if (overview_usher_button_acceptor) { + overview_usher_button_acceptor.onclick = function() { + view_usher_tree(d, "Acceptor"); + }; + } + + /* overview_usher_button.addEventListener( - 'mouseover', function() { - usher_button_info_display(tooltip); - }, false); +'mouseover', function() { +usher_button_info_display(tooltip); +}, false); overview_usher_button.addEventListener( - 'mouseout', function() { - usher_button_info_hide(tooltip); - }, false); - */ - - - let private_table_select = - document.getElementById('full_table'); - if (private_table_select.hidden) { - // Append "Tree View" button in overview section - var overview_tree_button = - document.createElement('button'); - overview_tree_button.setAttribute( - 'class', 'btn btn-outline-primary'); - overview_tree_button.setAttribute( - 'id', 'view_tree'); - const tree_btn_text = document.createTextNode( - 'View Taxonium Tree'); - overview_tree_button.appendChild(tree_btn_text); - overview.appendChild(overview_tree_button); - - var view_tree_button = - document.querySelector('#view_tree'); - if (view_tree_button) { - view_tree_button.onclick = function() { - fetch('/get_tree_view', { - method: 'POST', - headers: { - 'Content-Type': - 'application/json' - }, - body: JSON.stringify( - d['NODE_IDS']) - }).then(res => { - res.json().then(data => { - window.open( - data['url'], - '_blank'); - }); - }); - } - } - } - - // Append "Show Defining Mutations - let overview_mutations_button = - document.createElement('button'); - overview_mutations_button.setAttribute( - 'class', 'btn btn-outline-primary'); - overview_mutations_button.setAttribute( - 'id', 'show_mutations'); - const mutations_btn_text = document.createTextNode( - 'Show Amino Acid Mutations'); - overview_mutations_button.appendChild( - mutations_btn_text); - overview.appendChild(overview_mutations_button); - - if (overview_mutations_button) { - overview_mutations_button.onclick = function() { - let active; - // Update button status - if ($('#show_mutations') - .hasClass('active')) { - active = false; - $('#show_mutations') - .removeClass('active'); - } else { - active = true; - $('#show_mutations') - .addClass('active'); - } - if (active) { - let nt_data = - get_amino_acid_mutations( - d['NODE_IDS']['Recomb'], - container, y_position, - snp_positions, - square_dims, num_snps, - d); - } else { - // Hide amino acid track labels - hide_aa_labels(container); - } - }; - } - /* - // TODO: Visualize QC - metrics button var overview_view_qc = - document.createElement('button'); - overview_view_qc.setAttribute( - 'class', 'btn - btn-outline-primary'); - overview_view_qc.setAttribute('id', - 'view_qc'); const qc_btn_text = - document.createTextNode('View - QC Flags'); - overview_view_qc.appendChild(qc_btn_text); - overview.appendChild(overview_view_qc); - - if (overview_view_qc) { - overview_view_qc.onclick - = function() { console.log('View QC Flags in - Alignment'); let myOffcanvas = - document.getElementById( - 'off_canvas_right_qc'); - let - bsOffcanvas = new bootstrap.Offcanvas( - myOffcanvas); bsOffcanvas.show(); - } - } - */ - - // Make summary visible - overview.removeAttribute('hidden'); - } - - // Handle next/prev result button input - if (next_button) { - var next_element = next_button.onclick = function() { - next_result(d['ID']); - } - } - if (previous_button) { - var previous_element = - previous_button.onclick = function() { - previous_result(d['ID']) - } - } - // Handle next/prev result arrow key input - document.onkeydown = - function(e) { - var keyCode = e.keyCode; - if (keyCode == 37) { - previous_element(); - } else if (keyCode == 39) { - next_element(); - } - } - - if (download_svg) { - var inner = document.getElementById('inner_SVG'); - download_svg.onclick = function() { - download_snv_plot( - inner, d['NODE_IDS']['Recomb']); - }; - } - - var coordinate_track_list = add_coordinate_track( - container, y_position, d, square_dims, container_width); - - var coordinate_track = coordinate_track_list[0]; - y_position = coordinate_track_list[1]; - - var reference_track_list = add_reference_track( - container, polygon_buffer, y_position, square_dims, - reference_snps, snp_positions, d, container_width); - - var reference_track = reference_track_list[0]; - y_position = reference_track_list[1]; - - var trio_list = add_trio_track( - container, y_position, square_dims, d, container_width); - - var trio_track = trio_list[0]; - y_position = trio_list[1]; - } - - track.svg = function(value) { - svg = value; - return track; - }; - track.div = function(value) { - div = value; - return track; - }; - track.legend = function(value) { - legend = value; - return track; - }; - return track; +'mouseout', function() { +usher_button_info_hide(tooltip); +}, false); +*/ + + + let private_table_select = document.getElementById('full_table'); + if (private_table_select.hidden) { + // Append "Tree View" button in overview section + var overview_tree_button = document.createElement('button'); + overview_tree_button.setAttribute('class', 'btn btn-outline-primary'); + overview_tree_button.setAttribute('id', 'view_tree'); + const tree_btn_text = document.createTextNode('View Taxonium Tree'); + overview_tree_button.appendChild(tree_btn_text); + overview.appendChild(overview_tree_button); + + var view_tree_button = document.querySelector('#view_tree'); + if (view_tree_button) { + view_tree_button.onclick = function() { + fetch('/get_tree_view', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(d['NODE_IDS']) + }).then(res => { + res.json().then(data => { + window.open(data['url'], '_blank'); + }); + }); + } + } + } + + // Append "Show Defining Mutations + let overview_mutations_button = document.createElement('button'); + overview_mutations_button.setAttribute( + 'class', 'btn btn-outline-primary'); + overview_mutations_button.setAttribute('id', 'show_mutations'); + const mutations_btn_text = + document.createTextNode('Show Amino Acid Mutations'); + overview_mutations_button.appendChild(mutations_btn_text); + overview.appendChild(overview_mutations_button); + + if (overview_mutations_button) { + overview_mutations_button.onclick = function() { + let active; + // Update button status + if ($('#show_mutations').hasClass('active')) { + active = false; + $('#show_mutations').removeClass('active'); + } else { + active = true; + $('#show_mutations').addClass('active'); + } + if (active) { + let nt_data = get_amino_acid_mutations( + d['NODE_IDS']['Recomb'], container, y_position, snp_positions, + square_dims, num_snps, d); + } else { + // Hide amino acid track labels + hide_aa_labels(container); + } + }; + } + /* + // TODO: Visualize QC + metrics button var overview_view_qc = + document.createElement('button'); + overview_view_qc.setAttribute( + 'class', 'btn + btn-outline-primary'); + overview_view_qc.setAttribute('id', + 'view_qc'); const qc_btn_text = + document.createTextNode('View + QC Flags'); + overview_view_qc.appendChild(qc_btn_text); + overview.appendChild(overview_view_qc); + + if (overview_view_qc) { + overview_view_qc.onclick + = function() { console.log('View QC Flags in + Alignment'); let myOffcanvas = + document.getElementById( + 'off_canvas_right_qc'); + let + bsOffcanvas = new bootstrap.Offcanvas( + myOffcanvas); bsOffcanvas.show(); + } + } + */ + + // Make summary visible + overview.removeAttribute('hidden'); + } + + // Handle next/prev result button input + if (next_button) { + var next_element = next_button.onclick = function() { + next_result(d['ID']); + } + } + if (previous_button) { + var previous_element = previous_button.onclick = function() { + previous_result(d['ID']) + } + } + // Handle next/prev result arrow key input + document.onkeydown = + function(e) { + var keyCode = e.keyCode; + if (keyCode == 37) { + previous_element(); + } else if (keyCode == 39) { + next_element(); + } + } + + if (download_svg) { + var inner = document.getElementById('inner_SVG'); + download_svg.onclick = function() { + download_snv_plot(inner, d['NODE_IDS']['Recomb']); + }; + } + + var coordinate_track_list = add_coordinate_track( + container, y_position, d, square_dims, container_width); + + var coordinate_track = coordinate_track_list[0]; + y_position = coordinate_track_list[1]; + + var reference_track_list = add_reference_track( + container, polygon_buffer, y_position, square_dims, reference_snps, + snp_positions, d, container_width); + + var reference_track = reference_track_list[0]; + y_position = reference_track_list[1]; + + var trio_list = + add_trio_track(container, y_position, square_dims, d, container_width); + + var trio_track = trio_list[0]; + y_position = trio_list[1]; + } + + track.svg = function(value) { + svg = value; + return track; + }; + track.div = function(value) { + div = value; + return track; + }; + track.legend = function(value) { + legend = value; + return track; + }; + return track; }