Skip to content

Commit

Permalink
Merge pull request #2134 from merenlab/inversion-gene-popover
Browse files Browse the repository at this point in the history
gene popover functionality
  • Loading branch information
FlorianTrigodet authored Oct 4, 2023
2 parents b4f36bc + 5329613 commit 6629a7e
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 11 deletions.
172 changes: 167 additions & 5 deletions anvio/data/static/template/inversions.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@

<title>Anvi'o: Inversions Summary</title>

<!-- JQUERY -->
<script src=".html/js/jquery.min.js"></script>

<!-- Bootstrap Core JavaScript -->
<script src=".html/js/bootstrap.min.js"></script>

<!-- Bootstrap Core CSS -->
<link href=".html/css/bootstrap.css" rel="stylesheet">
<link href=".html/bootstrap-sortable/Contents/bootstrap-sortable.css" rel="stylesheet">
Expand Down Expand Up @@ -77,9 +83,9 @@

<!-- Go through genes to be shown in the genomic context -->
{% for gene in inversions|lookup:inversion|lookup:"genes" %}
{% if gene|lookup:"direction" == "r" %}<g transform='translate({{ gene|lookup:"GTRANS" }}, 0) scale(-1, 1)' id="{{inversion}}-gene-{{ gene|lookup:"gene_callers_id" }}">{% else %}<g id="{{inversion}}-gene-{{ gene|lookup:"gene_callers_id" }}">{% endif %}
<rect x="{{ gene|lookup:"RX" }}" y="23" width="{{ gene|lookup:"RW" }}" height="15" style="fill:green"></rect>
<polygon points="{{ gene|lookup:"RX_RW" }} 12, {{ gene|lookup:"GY" }} 30, {{ gene|lookup:"RX_RW" }} 48" style="fill:green"></polygon>
{% if gene|lookup:"direction" == "r" %}<g type="button" class="btn btn-primary" data-container="body" data-toggle="popover" data-popover-content='#{{inversion}}-gene-{{ gene|lookup:"gene_callers_id" }}-popover' data-placement="bottom" transform='translate({{ gene|lookup:"GTRANS" }}, 0) scale(-1, 1)' id="{{inversion}}-gene-{{ gene|lookup:"gene_callers_id" }}">{% else %}<g type="button" class="btn btn-primary" data-container="body" data-toggle="popover" data-popover-content='#{{inversion}}-gene-{{ gene|lookup:"gene_callers_id" }}-popover' data-placement="bottom" id="{{inversion}}-gene-{{ gene|lookup:"gene_callers_id" }}">{% endif %}
<rect x="{{ gene|lookup:"RX" }}" y="23" width="{{ gene|lookup:"RW" }}" height="15" style="fill:{{ gene|lookup:"COLOR" }}"></rect>
<polygon points="{{ gene|lookup:"RX_RW" }} 12, {{ gene|lookup:"GY" }} 30, {{ gene|lookup:"RX_RW" }} 48" style="fill:{{ gene|lookup:"COLOR" }}"></polygon>
</g>

{% if forloop.counter0|even_odd == 0 %}
Expand All @@ -98,8 +104,77 @@
<!-- If the metagenomic context is recovered, we also want to populate some `divs` that hold information
for the gene calls. Below we have a set of hidden divs to be shown for each gene -->
{% for gene in inversions|lookup:inversion|lookup:"genes" %}
<div class="hidden" id="{{inversion}}-gene-{{ gene|lookup:"gene_callers_id" }}-popover">
<p>Hai. I am a gene call, and my ID is {{ gene|lookup:"gene_callers_id" }}.
<!-- Popover Template -->
<div class="hidden popover fade in top col-12" role="tooltip" id="{{inversion}}-gene-{{ gene|lookup:'gene_callers_id' }}-popover">
<div class="popover-body table-responsive">
<div class="panel panel-default" id="popover-panel">
<div class="panel-heading mt-2" style="width: 100%;">
<h4>Gene Call</h4>
</div>
<div class="panel-body">
<table class="table table-striped gene-call-table" style="width: 100%; text-align: center;">
<thead>
<tr>
<th>ID</th>
<th>Source</th>
<th>Length</th>
<th>Direction</th>
<th>Start</th>
<th>Stop</th>
<th>Call type</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ gene|lookup:"gene_callers_id"|pretty }}</td>
<td>{{ gene|lookup:"source"|pretty }}</td>
<td>{{ gene|lookup:"length"|pretty }}</td>
<td>{{ gene|lookup:"direction"|pretty }}</td>
<td>{{ gene|lookup:"start"|pretty }}</td>
<td>{{ gene|lookup:"stop"|pretty }}</td>
<td>{{ gene|lookup:"call_type"|pretty }}</td>
</tr>
</tbody>
</table>
</div>
<!-- DNA/AA buttons -->
<div class="row seq-buttons">
<div class="col-xs-1 gene-section">
<button type="button" class="btn btn-default btn-sm" onClick="show_sequence('{{ gene|lookup:'header'|pretty }}', '{{ gene|lookup:'DNA_sequence'|pretty }}')">DNA</button>
</div>
<div class="col-xs-1 gene-section">
<button type="button" class="btn btn-default btn-sm" onClick="show_aa_sequence('{{ gene|lookup:'header'|pretty }}', '{{ gene|lookup:'AA_sequence'|pretty }}')">AA</button>
</div>
</div>
<div class="panel-heading mt-2" style="width: 100%;">
<h4>Function Annotations</h4>
</div>
<div class="panel-body">
{% if gene|lookup:"has_functions" %}
<table class="table table-striped" style="width: 100%;">
<thead>
<tr>
<th>Source</th>
<th>Accession</th>
<th>Function</th>
</tr>
</thead>
<tbody>
{% for function in gene|lookup:"functions" %}
<tr>
<td class="long">{{ function|lookup:"source"|pretty }}</td>
<td>{{ function|lookup:"accession"|pretty }}</td>
<td class="long">{{ function|lookup:"function"|pretty }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No known function in {{ meta|lookup:"gene_function_sources"|pretty_join:"or" }} :/
{% endif %}
</div>
</div>
</div>
</div>

{% endfor %}
Expand Down Expand Up @@ -287,6 +362,93 @@
</div>
</section>
{% endfor %}
<style>
.popover {
min-width: 600px;
max-width: 750px;
}
.popover-content{
padding: 0px;
}
.popover-table th, td {
padding: 0px 15px;
white-space:nowrap;
}
.gene-call-table{
margin-bottom: 0px;
}
.gene-section{
max-width: 70px;
margin-right: 5px;
}
.seq-buttons{
margin-left: 0px;
margin-bottom: 10px;
}
.long{
white-space:normal;
max-width: 300px;
}
#popover-panel{
border: none;
margin-bottom: 0px;
}
</style>
<script type="text/javascript">
// Binding template to the Popover Content
$(document).ready(function () {
$('[data-toggle="popover"]').popover({
sanitize: false,
html : true,
content: function() {
var content = $(this).attr("data-popover-content");
return $(content).children(".popover-body").html();
}
});
})

//Closing popover if click somewhere else on the page.
$('body').on('click', function (e) {
$('[data-toggle="popover"]').each(function () {
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
$(this).popover('hide');
}
});
});

function show_sequence_modal(title, content) {
// remove previous modal window
$('.modal-sequence').modal('hide');
$('.modal-sequence').remove();

$('body').append('<div class="modal modal-sequence" style="z-index: 10000;"> \
<div class="modal-dialog"> \
<div class="modal-content"> \
<div class="modal-header"> \
<button class="close" data-dismiss="modal" type="button"><span>&times;</span></button> \
<h4 class="modal-title">' + title + '</h4> \
</div> \
<div class="modal-body"> \
<textarea class="form-control" style="width: 100%; height: 100%; font-family: monospace;" rows="16" onclick="$(this).select();" readonly>' + (content.startsWith('>') ? content : '>' + content) + '</textarea> \
</div> \
<div class="modal-footer"> \
<button class="btn btn-default" data-dismiss="modal" type="button">Close</button> \
</div> \
</div> \
</div> \
</div>');
$('.modal-sequence').modal('show');
$('.modal-sequence textarea').trigger('click');
}

function show_sequence(header, sequence) {
show_sequence_modal('Gene DNA Sequence', header + '\n' + sequence);
}

function show_aa_sequence(header, aa_sequence) {
show_sequence_modal('Gene Amino Acid Sequence', header + '\n' + aa_sequence);
}

</script>
</body>
</html>
53 changes: 47 additions & 6 deletions anvio/inversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -744,12 +744,22 @@ def get_true_inversions_in_stretch(self, inversion_candidates, bam_file, contig_


def process_inversion_data_for_HTML_summary(self):
"""Take everything that is known, turn them into data that can be used from Django templates.
A lot of ugly things happening here to prepare coordinates for SVG objects to be displayed
or store boolean variables to use the Django template engine effectively. IF YOU DON'T LIKE
IT DON'T LOOK AT IT.
"""

contigs_db = dbops.ContigsDatabase(self.contigs_db_path, run=run_quiet, progress=progress_quiet)
self.summary['meta'] = {'summary_type': 'inversions',
'num_inversions': len(self.consensus_inversions),
'num_samples': len(self.profile_db_paths),
'output_directory': self.output_directory,
'genomic_context_recovered': not self.skip_recovering_genomic_context,
'inversion_activity_computed': not self.skip_compute_inversion_activity}
'inversion_activity_computed': not self.skip_compute_inversion_activity,
'gene_function_sources': contigs_db.meta['gene_function_sources'] or []}
contigs_db.disconnect()

self.summary['files'] = {'consensus_inversions': 'INVERSIONS-CONSENSUS.txt'}
self.summary['inversions'] = {}
Expand Down Expand Up @@ -812,6 +822,13 @@ def process_inversion_data_for_HTML_summary(self):
gene_arrow_width = default_gene_arrow_width
gene['RW'] = (gene['stop_t'] - gene['start_t']) - gene_arrow_width

if gene['functions']:
gene['has_functions'] = True
gene['COLOR'] = '#008000'
else:
gene['has_functions'] = False
gene['COLOR'] = '#c3c3c3'

gene['RX'] = gene['start_t']
gene['CX'] = (gene['start_t'] + (gene['stop_t'] - gene['start_t']) / 2)
gene['GY'] = gene['RX'] + gene['RW'] + gene_arrow_width
Expand Down Expand Up @@ -904,7 +921,6 @@ def process_inversion_data_for_HTML_summary(self):
# now we append the activity to the summary
self.summary['inversions'][inversion_id]['activity'][sample][oligo_primer][oligo] = activity


for inversion_id in self.summary['inversions']:
for sample in self.summary['inversions'][inversion_id]['activity']:
for inversion_id, activity in sum_freq.items():
Expand Down Expand Up @@ -952,8 +968,7 @@ def recover_genomic_context_surrounding_inversions(self):
self.progress.update('...')

# now we will go through each consensus inversion to populate `self.genomic_context_surrounding_consensus_inversions`
# with gene calls and functions .. in the meantime, we will populate the `self.summary`,
# which will be used to render the static HTML output for the final results
# with gene calls and functions
gene_calls_per_contig = {}
inversions_with_no_gene_calls_around = set([])
for entry in self.consensus_inversions:
Expand Down Expand Up @@ -1015,13 +1030,39 @@ def recover_genomic_context_surrounding_inversions(self):
if hits:
gene_call['functions'] = [h for h in hits if h['gene_callers_id'] == gene_callers_id]

# While we are here, let's add more info about each gene
# DNA sequence:
dna_sequence = self.contig_sequences[contig_name]['sequence'][gene_call['start']:gene_call['stop']]
rev_compd = None
if gene_call['direction'] == 'f':
gene_call['DNA_sequence'] = dna_sequence
rev_compd = False
else:
gene_call['DNA_sequence'] = utils.rev_comp(dna_sequence)
rev_compd = True

# add AA sequence
where_clause = f'''gene_callers_id == {gene_callers_id}'''
aa_sequence = contigs_db.db.get_some_rows_from_table_as_dict(t.gene_amino_acid_sequences_table_name, where_clause=where_clause, error_if_no_data=False)
gene_call['AA_sequence'] = aa_sequence[gene_callers_id]['sequence']

# gene length
gene_call['length'] = gene_call['stop'] - gene_call['start']

# add fasta header
header = '|'.join([f"contig:{contig_name}",
f"start:{gene_call['start']}",
f"stop:{gene_call['stop']}",
f"direction:{gene_call['direction']}",
f"rev_compd:{rev_compd}",
f"length:{gene_call['length']}"])
gene_call['header'] = ' '.join([str(gene_callers_id), header])

c.append(gene_call)

# done! `c` now goes to live its best life as a part of the main class
self.genomic_context_surrounding_consensus_inversions[inversion_id] = copy.deepcopy(c)



contigs_db.disconnect()
self.progress.end()

Expand Down
10 changes: 10 additions & 0 deletions anvio/summaryhtml.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,13 @@ def base64_encode(data):
def zlib_encode(data):
return zlib.compress(data.encode("utf-8"))

@register.filter(name='pretty_join')
def pretty_join(data, and_or="and"):
d = [str(item) for item in data]

if len(d) == 1:
return d[0]
else:
all_but_last = ", ".join(d[:-1])
return f"{all_but_last}, {and_or} {d[-1]}"

0 comments on commit 6629a7e

Please sign in to comment.