import d3 from 'd3';
import _ from 'underscore';
import Grapher from 'grapher';
import * as Helpers from './visualisation_helpers';
import Utils from './utils';
class Graph {
static canCollapse() {
return true;
}
static name() {
return 'Graphical overview of aligning hit sequences to the query';
}
static className() {
return 'alignment-overview';
}
static graphId(props) {
return 'alignment_'+props.query.number;
}
static dataName(props) {
return 'Alignment-Overview-'+props.query.id;
}
constructor($svgContainer, props) {
this.svg_container = $svgContainer;
var $queryDiv = $svgContainer.parents('.resultn');
var hits = this.extractData(props.query.hits, props.query.number);
this.graphIt($queryDiv, $svgContainer, 0, 20, null, hits);
}
extractData(query_hits, number) {
var hits = [];
query_hits.map(function (hit) {
var _hsps = [];
var hsps = hit.hsps;
_.each(hsps, function (hsp) {
var _hsp = {};
_hsp.hspEvalue = hsp.evalue;
_hsp.hspStart = hsp.qstart;
_hsp.hspEnd = hsp.qend;
_hsp.hspFrame = hsp.sframe;
_hsp.hspId = 'Query_' + number + '_hit_' + hit.number + '_hsp_' + hsp.number;
_hsp.hspIdentity = hsp.identity;
_hsp.hspGaps = hsp.gaps;
_hsp.hspPositives = hsp.positives;
_hsp.hspLength = hsp.length;
_hsps.push(_hsp);
});
_hsps.hitId = hit.id;
_hsps.hitDef = 'Query_'+number+'_hit_'+hit.number;
_hsps.hitEvalue = hit.hsps[0].evalue;
hits.push(_hsps);
});
return hits;
}
setupTooltip() {
this.svg_container.find('[data-toggle="tooltip"]').tooltip({
'placement': 'top', 'container': 'body', 'html': 'true',
'delay': 0, 'white-space': 'nowrap'
});
}
setupClick($graphDiv) {
$('a', $graphDiv).click(function (evt) {
evt.preventDefault();
evt.stopPropagation();
window.location.hash = $(this).attr('href');
});
}
graphControls($queryDiv, $graphDiv, isInit, opts, hits) {
var MIN_HITS_TO_SHOW = 20;
var totalHits, shownHits, lessButton, moreButton;
var countHits = function () {
totalHits = hits.length;
shownHits = $queryDiv.find('.ghit > g').length;
};
var setupButtons = function($queryDiv, $graphDiv) {
$graphDiv
.append(
$('')
.addClass('btn btn-link more')
.attr('type', 'button')
.attr('data-parent-query', $queryDiv.attr('id'))
.html('View More ')
.append(
$('')
.html(' ')
.addClass('fa fa-angle-double-down')
),
$('')
.addClass('btn btn-link less')
.attr('type', 'button')
.attr('data-parent-query', $queryDiv.attr('id'))
.html('View Less ')
.append(
$('')
.html(' ')
.addClass('fa fa-angle-double-up')
)
);
lessButton = $('.less', $graphDiv);
moreButton = $('.more', $graphDiv);
};
var initButtons = function () {
countHits();
if (totalHits === MIN_HITS_TO_SHOW ||
shownHits < MIN_HITS_TO_SHOW) {
lessButton.hide();
moreButton.hide();
}
else if (shownHits === totalHits) {
moreButton.hide();
lessButton.show();
}
else if (shownHits === MIN_HITS_TO_SHOW) {
lessButton.hide();
moreButton.show();
}
else {
lessButton.show();
moreButton.show();
}
};
// Setup view buttons' state properly if called for first time.
if (isInit === true) {
setupButtons($queryDiv, $graphDiv);
initButtons();
}
moreButton.on('click', _.bind(function (e) {
countHits();
this.graphIt($queryDiv, $graphDiv, shownHits, MIN_HITS_TO_SHOW, opts, hits);
initButtons();
this.setupTooltip();
e.stopPropagation();
},this));
lessButton.on('click', _.bind(function (e) {
countHits();
var diff = shownHits - MIN_HITS_TO_SHOW;
// Decrease number of shown hits by defined constant.
if (diff >= MIN_HITS_TO_SHOW) {
this.graphIt($queryDiv, $graphDiv, shownHits, -MIN_HITS_TO_SHOW, opts, hits);
initButtons();
}
else if (diff !== 0) {
// Ensure a certain number of hits always stay in graph.
this.graphIt($queryDiv, $graphDiv, shownHits, MIN_HITS_TO_SHOW - shownHits, opts, hits);
initButtons();
}
this.setupTooltip();
e.stopPropagation();
},this));
}
drawLegend(svg, options, width, height, hits) {
var svg_legend = svg.append('g')
.attr('transform',
'translate(0,' + (height - 1.75 * options.margin) + ')');
svg_legend.append('rect')
.attr('x', 7.5 * (width - 2 * options.margin) / 10)
.attr('width', 2 * (width - 4 * options.margin) / 10)
.attr('height', options.legend)
.attr('fill', 'url(#legend-grad)');
svg_legend.append('text')
.attr('class',' legend-text')
.attr('transform', 'translate(0, ' +options.legend +')')
.attr('x', 9.5 * (width - 2 * options.margin) / 10 + options.margin / 2)
.text('Weaker hits');
// .text(function() {
// return Helpers.prettify_evalue(hits[hits.length-1].hitEvalue);
// })
svg_legend.append('text')
.attr('class',' legend-text')
.attr('transform', 'translate(0, ' + options.legend + ')')
.attr('x', 6.7 * (width - 2 * options.margin) / 10 - options.margin / 2)
.text('Stronger hits');
// .text(function () {
// return Helpers.prettify_evalue(hits[0].hitEvalue);
// })
svg.append('linearGradient')
.attr('id', 'legend-grad')
.selectAll('stop')
.data([
{offset: '0%', color: '#000'},
{offset: '45%', color: '#c74f14'},
{offset: '100%', color: '#f6bea2'}
])
.enter()
.append('stop')
.attr('offset', function (d) {
return d.offset;
})
.attr('stop-color', function (d) {
return d.color;
});
}
graphIt($queryDiv, $graphDiv, index, howMany, opts, inhits) {
/* barHeight: Height of each hit track.
* legend: Height reserved for the overview legend.
* margin: Margin around the svg element.
*/
var defaults = {
barHeight: 4,
legend: inhits.length > 1 ? 3 : 0,
margin: 20
},
options = $.extend(defaults, opts);
var hits = inhits.slice(0 , index + howMany);
// Don't draw anything when no hits are obtained.
if (hits.length < 1) return false;
if (index !== 0) {
// Currently, we have no good way to extend pre-existing graph
// and hence, are removing the old one and redrawing.
$graphDiv.find('svg').remove();
}
var queryLen = $queryDiv.data().queryLen;
var q_i = $queryDiv.attr('id');
var width = $graphDiv.width();
var height = hits.length * (options.barHeight) +
2 * options.legend + 4 * options.margin;
// var height = $graphDiv.height();
var SEQ_TYPES = {
blastn: 'nucleic_acid',
blastp: 'amino_acid',
blastx: 'nucleic_acid',
tblastx: 'nucleic_acid',
tblastn: 'amino_acid'
};
var svg = d3.select($graphDiv[0])
.selectAll('svg')
.data([hits])
.enter()
.insert('svg', ':first-child')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + options.margin / 2 + ', ' + (1.5 * options.margin) + ')');
var x = d3.scale
.linear()
.range([0, width - options.margin]);
x.domain([1, queryLen]);
var algorithm = $queryDiv.data().algorithm;
var formatter = Helpers.tick_formatter(x, SEQ_TYPES[algorithm]);
var _tValues = x.ticks(11);
_tValues.pop();
var xAxis = d3.svg
.axis()
.scale(x)
.orient('top')
.tickValues(_tValues.concat([1, queryLen]))
.tickFormat(formatter);
// Attach the axis to DOM (