Skip to content
This repository has been archived by the owner on Apr 7, 2022. It is now read-only.

Commit

Permalink
add vbrowser feature requests
Browse files Browse the repository at this point in the history
  • Loading branch information
samwachspress committed Jun 16, 2021
1 parent 0a34fc2 commit 563c2d4
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 13 deletions.
35 changes: 34 additions & 1 deletion base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,7 @@ class StrainAnnotatedVariants(DictSerializable, db.Model):
{'id': 'ref_seq', 'name': 'Ref Sequence'},
{'id': 'alt_seq', 'name': 'Alt Sequence'},
{'id': 'consequence', 'name': 'Consequence'},
{'id': 'target_consequence', 'name': 'Target Consequence'},
{'id': 'gene_id', 'name': 'Gene ID'},
{'id': 'transcript', 'name': 'Transcript'},
{'id': 'biotype', 'name': 'Biotype'},
Expand All @@ -881,25 +882,57 @@ def generate_interval_sql(cls, interval):
q = f"SELECT * FROM {cls.__tablename__} WHERE chrom='{chrom}' AND pos > {start} AND pos < {stop};"
return q


''' TODO: implement input checks here and in the browser form'''
@classmethod
def verify_interval_query(cls, q):
query_regex = "^(I|II|III|IV|V|X|MtDNA):[0-9,]+-[0-9,]+$"
match = re.search(query_regex, q)
return True if match else False


@classmethod
def run_interval_query(cls, q):
q = cls.generate_interval_sql(q)
df = pd.read_sql_query(q, db.engine)

try:
result = df[['id', 'chrom', 'pos', 'ref_seq', 'alt_seq', 'consequence', 'gene_id', 'transcript', 'biotype', 'strand', 'amino_acid_change', 'dna_change', 'strains', 'blosum', 'grantham', 'percent_protein', 'gene', 'variant_impact', 'divergent']].dropna(how='all') \
result = df[['id', 'chrom', 'pos', 'ref_seq', 'alt_seq', 'consequence', 'target_consequence', 'gene_id', 'transcript', 'biotype', 'strand', 'amino_acid_change', 'dna_change', 'strains', 'blosum', 'grantham', 'percent_protein', 'gene', 'variant_impact', 'divergent']].dropna(how='all') \
.fillna(value="") \
.agg(list) \
.to_dict()
except ValueError:
result = {}
return result


@classmethod
def generate_position_sql(cls, pos):
pos = pos.replace(',','')
chrom = pos.split(':')[0]
pos = int(pos.split(':')[1])

q = f"SELECT * FROM {cls.__tablename__} WHERE chrom='{chrom}' AND pos = {pos};"
return q


@classmethod
def verify_position_query(cls, q):
query_regex = "^(I|II|III|IV|V|X|MtDNA):[0-9,]+$"
match = re.search(query_regex, q)
return True if match else False


@classmethod
def run_position_query(cls, q):
q = cls.generate_position_sql(q)
df = pd.read_sql_query(q, db.engine)

try:
result = df[['id', 'chrom', 'pos', 'ref_seq', 'alt_seq', 'consequence', 'target_consequence', 'gene_id', 'transcript', 'biotype', 'strand', 'amino_acid_change', 'dna_change', 'strains', 'blosum', 'grantham', 'percent_protein', 'gene', 'variant_impact', 'divergent']].dropna(how='all') \
.fillna(value="") \
.agg(list) \
.to_dict()
except ValueError:
result = {}
return result
119 changes: 109 additions & 10 deletions base/templates/vbrowser.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

mark {
background-color: inherit;
font-size: 8px;
font-size: 0.8rem;
}

.hl-mark {
Expand Down Expand Up @@ -228,6 +228,8 @@ <h3 class="text-center">Variant Impact</h3>

let toggle_strain_lock = false;

let cachedTargets = {};

function init_selected_strain_set() {
{% for strain in strain_listing %}
selected_strains.add('{{strain}}');
Expand Down Expand Up @@ -264,13 +266,17 @@ <h3 class="text-center">Variant Impact</h3>
$('#search-interval-btn').click();
}

function chunk_substr(str, size) {
let numChunks = Math.ceil(str.length / size)
let chunks = new Array(numChunks)
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = str.substr(o, size)
function chunk_substr(data, size) {
if (typeof data === 'string' && data.length > col_max_len) {
let numChunks = Math.ceil(data.length / size)
let chunks = new Array(numChunks)
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = data.substr(o, size)
}
return chunks.join('<br/>')
} else {
return data;
}
return chunks
}

function hideTable() {
Expand Down Expand Up @@ -328,9 +334,95 @@ <h3 class="text-center">Variant Impact</h3>
}

function processCol(col, id) {
return (id !== 'strains' && typeof col === 'string' && col.length > col_max_len) ? chunk_substr(col, col_max_len).join('<br/>') : col;
if (id == 'strains') {
return col
}
return chunk_substr(col, col_max_len)
}

function renderTargetConsequenceCol(data, type, row) {
if (data) {
return `<button class="btn-alt" onClick="expandTargetConsequence(this, ${data});"><span class='glyphicon glyphicon-collapse-down'></span>${data}</button>`;
}
return '';
}

function renderTargetConsequenceTable(data) {
let result = "<div class='instruction-well'><table class='table table-striped table-condensed table-hover' style='width:80%;margin-left:10%;font-size:0.8rem;'><thead>";
const numRows = Object.keys(data.id).length;
for (i of columnList) {
result += `<th>${i.name}</th>`;
}
result += "</thead><tbody>";
for (let j = 0; j < numRows; j++) {
result += "<tr>";
for (k of columnList) {
let val = data[k.id][j] || "";
let processedVal;
if (k.id === "strains") {
processedVal = renderStrainsCol(val, null, null);
} else {
processedVal = chunk_substr(val, col_max_len);
}
result += `<td>${processedVal}</td>`;
}
result += "</tr>";
}
result += "</tbody></table></div>";
return result;
}

function fetchTargetConsequence(el, q) {
const tr = el.closest('tr');
const row = dTable.row(tr);

if (cachedTargets[q]) {
row.child(renderTargetConsequenceTable(cachedTargets[q])).show();
highlight_selected_strains();
} else {
$.ajax({
type: "POST",
contentType: 'application/json',
dataType: 'json',
url: "{{ url_for('data.vbrowser_query_position') }}",
data: JSON.stringify({query: q}),
success:function(result) {
cachedTargets[q] = result;
row.child(renderTargetConsequenceTable(result)).show();
highlight_selected_strains();
},
error:function(error) {
console.error(error);
}
});
}
}

function expandTargetConsequence(el, pos) {
const tr = el.closest('tr');
const row = dTable.row(tr);
const chrom = dTable.cell(tr, 'chrom:name').data();
const query = chrom + ":" + pos;
if ( row.child.isShown() ) {
// This row is already open - close it
row.child.hide();
}
else {
// Open this row
//row.child( format(row.data()) ).show();
fetchTargetConsequence(el, query)
}
}

const columnList = [
{% for col in columns %}
{
"id": "{{ col['id'] }}",
"name": "{{ col['name'] }}"
},
{% endfor %}
];


function populateDataTable() {
// clear the table before populating it with more data
Expand Down Expand Up @@ -381,6 +473,12 @@ <h3 class="text-center">Variant Impact</h3>
"mRender": renderStrainsCol,
"targets": {{ loop.index0 }}
},
{% elif col['id'] == 'target_consequence' %}
{
"name": "{{ col['id'] }}",
"mRender": renderTargetConsequenceCol,
"targets": {{ loop.index0 }}
},
{% else %}
{
"name": "{{ col['id'] }}",
Expand All @@ -398,7 +496,7 @@ <h3 class="text-center">Variant Impact</h3>

dTable.on('page.dt', function () {
setTimeout(highlight_selected_strains, 250);
} );
});

}

Expand Down Expand Up @@ -519,10 +617,11 @@ <h3 class="text-center">Variant Impact</h3>
type: "POST",
contentType: 'application/json',
dataType: 'json',
url: "{{ url_for('data.vbrowser_query') }}",
url: "{{ url_for('data.vbrowser_query_interval') }}",
data: JSON.stringify(data),
success:function(result) {
enableForm();
cachedTargets = {};
result_data = result;
populateDataTable();
showTable();
Expand Down
17 changes: 15 additions & 2 deletions base/views/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ def vbrowser():
return render_template('vbrowser.html', **locals())


@data_bp.route('/vbrowser/query', methods=['POST'])
def vbrowser_query():
@data_bp.route('/vbrowser/query/interval', methods=['POST'])
def vbrowser_query_interval():
title = 'Variant Annotation'
payload = json.loads(request.data)

Expand All @@ -243,5 +243,18 @@ def vbrowser_query():



@data_bp.route('/vbrowser/query/position', methods=['POST'])
def vbrowser_query_position():
title = 'Variant Annotation'
payload = json.loads(request.data)

query = payload.get('query')

is_valid = StrainAnnotatedVariants.verify_position_query(q=query)
if is_valid:
data = StrainAnnotatedVariants.run_position_query(q=query)
return jsonify(data)

return jsonify({})


0 comments on commit 563c2d4

Please sign in to comment.