import './sequenceserver' // for custom $.tooltip function
import React from 'react';
import _ from 'underscore';
import Circos from './circos';
import HitsOverview from './hits_overview';
import LengthDistribution from './length_distribution'; // length distribution of hits
import HSPOverview from './kablammo';
import AlignmentExporter from './alignment_exporter'; // to download textual alignment
import './sequence';
import * as Helpers from './visualisation_helpers'; // for toLetters
import showErrorModal from './error_modal';
/**
* Dynamically create form and submit.
*/
var downloadFASTA = function (sequence_ids, database_ids) {
var form = $('
').attr('method', 'post').attr('action', '/get_sequence');
addField("sequence_ids", sequence_ids);
addField("database_ids", database_ids);
form.appendTo('body').submit().remove();
function addField(name, val) {
form.append(
$('').attr('type', 'hidden').attr('name', name).val(val)
);
}
}
/**
* Pretty formats number
*/
var Utils = {
/**
* Render URL for sequence-viewer.
*/
a: function (link) {
if (link.title && link.url)
{
return (
{link.icon && }
{" " + link.title + " "}
);
}
},
/***********************************
* Formatters for hits & hsp table *
***********************************/
// Formats an array of two elements as "first (last)".
format_2_tuple: function (tuple) {
return (tuple[0] + " (" + tuple[tuple.length - 1] + ")");
},
/**
* Returns fraction as percentage
*/
inPercentage: function (num , den) {
return (num * 100.0 / den).toFixed(2);
},
/**
* Returns fractional representation as String.
*/
inFraction: function (num , den) {
return num + "/" + den;
},
/**
* Returns given Float as String formatted to two decimal places.
*/
inTwoDecimal: function (num) {
return num.toFixed(2)
},
/**
* Returns zero if num is zero. Returns two decimal representation of num
* if num is between [1..10). Returns num in scientific notation otherwise.
*/
inExponential: function (num) {
// Nothing to do if num is 0.
if (num === 0) {
return 0
}
// Round to two decimal places if in the rane [1..10).
if (num >= 1 && num < 10)
{
return this.inTwoDecimal(num)
}
// Return numbers in the range [0..1) and [10..Inf] in
// scientific format.
var exp = num.toExponential(2);
var parts = exp.split("e");
var base = parts[0];
var power = parts[1];
return {base} × 10{power};
}
};
/**
* Component for sequence-viewer links.
*/
var SequenceViewer = (function () {
var Viewer = React.createClass({
/**
* The CSS class name that will be assigned to the widget container. ID
* assigned to the widget container is derived from the same.
*/
widgetClass: 'biojs-vis-sequence',
// Lifecycle methods. //
render: function () {
this.widgetID =
this.widgetClass + '-' + (new Date().getUTCMilliseconds());
return (
);
}
});
/**
* Renders report for each query sequence.
*
* Composed of graphical overview, tabular summary (HitsTable),
* and a list of Hits.
*/
var Query = React.createClass({
// Kind of public API //
/**
* Returns the id of query.
*/
domID: function () {
return "Query_" + this.props.query.number;
},
/**
* Returns number of hits.
*/
numhits: function () {
return this.props.query.hits.length;
},
// Life cycle methods //
render: function () {
return (
);
},
// Controller //
/**
* Returns true if results have been fetched.
*
* A holding message is shown till results are fetched.
*/
isResultAvailable: function () {
return this.state.queries.length >= 1;
},
isHitsAvailable: function () {
var cnt = 0;
_.each(this.state.queries, function (query) {
if(query.hits.length == 0) cnt++;
});
return !(cnt == this.state.queries.length);
},
/**
* Returns true if sidebar should be shown.
*
* Sidebar is not shown if there is only one query and there are no hits
* corresponding to the query.
*/
shouldShowSidebar: function () {
return !(this.state.queries.length == 1 &&
this.state.queries[0].hits.length == 0);
},
/**
* Returns true if index should be shown in the sidebar.
*
* Index is not shown in the sidebar if there are more than eight queries
* in total.
*/
shouldShowIndex: function () {
return this.state.queries.length <= 8;
},
/**
* Called after first call to render. The results may not be available at
* this stage and thus results DOM cannot be scripted here, unless using
* delegated events bound to the window, document, or body.
*/
componentDidMount: function () {
// This sets up an event handler which enables users to select text
// from hit header without collapsing the hit.
this.preventCollapseOnSelection();
},
/**
* Called after each state change. Only a part of results DOM may be
* available after a state change.
*/
componentDidUpdate: function () {
// We track the number of updates to the component.
this.updateCycle += 1;
// Lock sidebar in its position on first update of
// results DOM.
if (this.updateCycle === 1 ) this.affixSidebar();
},
/**
* Prevents folding of hits during text-selection, etc.
*/
/**
* Called after all results have been rendered.
*/
componentFinishedUpdating: function () {
this.shouldShowIndex() && this.setupScrollSpy();
},
/**
* Prevents folding of hits during text-selection.
*/
preventCollapseOnSelection: function () {
$('body').on('mousedown', ".hit > .section-header > h4", function (event) {
var $this = $(this);
$this.on('mouseup mousemove', function handler(event) {
if (event.type === 'mouseup') {
// user wants to toggle
$this.attr('data-toggle', 'collapse');
$this.find('.fa-chevron-down').toggleClass('fa-rotate-270');
} else {
// user wants to select
$this.attr('data-toggle', '');
}
$this.off('mouseup mousemove', handler);
});
});
},
/**
* Affixes the sidebar.
*/
affixSidebar: function () {
var $sidebar = $('.sidebar');
$sidebar.affix({
offset: {
top: $sidebar.offset().top
}
});
},
/**
* For the query in viewport, highlights corresponding entry in the index.
*/
setupScrollSpy: function () {
$('body').scrollspy({target: '.sidebar'});
},
/**
* Event-handler when hit is selected
* Adds glow to hit component.
* Updates number of Fasta that can be downloaded
*/
selectHit: function (id) {
var checkbox = $("#" + id);
var num_checked = $('.hit-links :checkbox:checked').length;
if (!checkbox || !checkbox.val()) {
return;
}
var $hit = $(checkbox.data('target'));
// Highlight selected hit and sync checkboxes if sequence viewer is open.
if (checkbox.is(":checked")) {
$hit
.addClass('glow')
.find(":checkbox").not(checkbox).check();
var $a = $('.download-fasta-of-selected');
var $b = $('.download-alignment-of-selected');
$b.enable()
var $n = $a.find('span');
$a
.enable()
}
else {
$hit
.removeClass('glow')
.find(":checkbox").not(checkbox).uncheck();
}
if (num_checked >= 1)
{
var $a = $('.download-fasta-of-selected');
var $b = $('.download-alignment-of-selected');
$a.find('.text-bold').html(num_checked);
$b.find('.text-bold').html(num_checked);
}
if (num_checked == 0) {
var $a = $('.download-fasta-of-selected');
var $b = $('.download-alignment-of-selected');
$a.addClass('disabled').find('.text-bold').html('');
$b.addClass('disabled').find('.text-bold').html('');
}
},
});
var Page = React.createClass({
render: function () {
return (