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 HSP from './hsp'; import './sequence'; import * as Helpers from './visualisation_helpers'; // for toLetters import Utils from './utils'; // to use as mixin in Hit and HitsTable 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) ); } } /** * Base component of report page. This component is later rendered into page's * '#view' element. */ var Page = React.createClass({ render: function () { return (
{/* Provide bootstrap .container element inside the #view for the Report component to render itself in. */}
{/* Required by Grapher for SVG and PNG download */}
); } }); /** * Renders entire report. * * Composed of Query and Sidebar components. */ var Report = React.createClass({ // Model // getInitialState: function () { this.fetchResults(); this.updateCycle = 0; return { search_id: '', program: '', program_version: '', submitted_at: '', num_queries: 0, queries: [], querydb: [], params: [], stats: [] }; }, /** * Fetch results. */ fetchResults: function () { var intervals = [200, 400, 800, 1200, 2000, 3000, 5000]; var component = this; function poll () { $.getJSON(location.pathname + '.json') .complete(function (jqXHR) { switch (jqXHR.status) { case 202: var interval; if (intervals.length === 1) { interval = intervals[0]; } else { interval = intervals.shift(); } setTimeout(poll, interval); break; case 200: component.updateState(jqXHR.responseJSON); break; case 404: case 400: case 500: showErrorModal(jqXHR.responseJSON); break; } }); } poll(); }, /** * Incrementally update state so that the rendering process is * not overwhelemed when there are too many queries. */ updateState: function(responseJSON) { var queries = responseJSON.queries; // Render results for first 50 queries and set flag if total queries is // more than 250. var numHits = 0; responseJSON.veryBig = queries.length > 250; //responseJSON.veryBig = !_.every(queries, (query) => { //numHits += query.hits.length; //return (numHits <= 500); //}); responseJSON.queries = queries.splice(0, 50); responseJSON.num_queries = queries.length; this.setState(responseJSON); // Render results for remaining queries. var update = function () { if (queries.length > 0) { this.setState({ queries: this.state.queries.concat(queries.splice(0, 50)) }); setTimeout(update.bind(this), 500); } else { this.componentFinishedUpdating(); } }; setTimeout(update.bind(this), 500); }, // View // render: function () { return this.isResultAvailable() ? this.resultsJSX() : this.loadingJSX(); }, /** * Returns loading message */ loadingJSX: function () { return (

  BLAST-ing


This can take some time depending on the size of your query and database(s). The page will update automatically when BLAST is done.

You can bookmark the page and come back to it later or share the link with someone.

); }, /** * Return results JSX. */ resultsJSX: function () { return (
{ this.shouldShowSidebar() && (
) }
{ this.overviewJSX() } { this.isHitsAvailable() ? : } { _.map(this.state.queries, _.bind(function (query) { return ( ); }, this)) }
); }, /** * Renders report overview. */ overviewJSX: function () { return (
                    {this.state.program_version}{this.state.submitted_at
                            && `; query submitted on ${this.state.submitted_at}`}
                    
Databases ({this.state.stats.nsequences} sequences,  {this.state.stats.ncharacters} characters): { this.state.querydb.map((db) => { return db.title }).join(", ") }
Parameters: { _.map(this.state.params, function (val, key) { return key + " " + val; }).join(", ") }
); }, // 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'); // Get the element indicated in the data-target attribute // and toggle the 'in' class for collapsing/expanding. var target = $('#' + $this.attr('data-target')); target.toggleClass('in'); $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 enable 'Download FASTA/Alignment of // selected' links. if (checkbox.is(":checked")) { $hit.find('.section-content').addClass('glow'); $('.download-alignment-of-selected').enable(); $('.download-fasta-of-selected').enable(); } else { $hit.find('.section-content').removeClass('glow'); } 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(''); } }, }); /** * 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 (

Query= {this.props.query.id}   {this.props.query.title}

{this.props.query.number + "/" + this.props.num_queries}
{this.numhits() && (
{ _.map(this.props.query.hits, _.bind(function (hit) { return ( ); }, this)) }
) || (

Query length: {this.props.query.length}



****** No hits found ******

) }
) }, shouldComponentUpdate: function (nextProps, nextState) { if (!this.props.query) return true; } }); /** * Renders summary of all hits per query in a tabular form. */ var HitsTable = React.createClass({ mixins: [Utils], render: function () { var count = 0, hasName = _.every(this.props.query.hits, function(hit) { return hit.sciname !== ''; }); return ( {hasName && } {!this.props.imported_xml && } { _.map(this.props.query.hits, _.bind(function (hit) { return ( {hasName && } {!this.props.imported_xml && } ) }, this)) }
# Similar sequencesSpeciesQuery coverage (%)Total score E value Identity (%)
{hit.number + "."} {hit.id} {hit.sciname}{hit.qcovs}{hit.score} {this.inExponential(hit.hsps[0].evalue)} {hit.identity}
); } }); /** * Component for each hit. */ var Hit = React.createClass({ mixins: [Utils], /** * Returns accession number of the hit sequence. */ accession: function () { return this.props.hit.accession; }, /** * Returns length of the hit sequence. */ length: function () { return this.props.hit.length; }, // Internal helpers. // /** * Returns id that will be used for the DOM node corresponding to the hit. */ domID: function () { return "Query_" + this.props.query.number + "_hit_" + this.props.hit.number; }, databaseIDs: function () { return _.map(this.props.querydb, _.iteratee('id')); }, showSequenceViewer: function (event) { this.setState({ showSequenceViewer: true }); event && event.preventDefault(); }, hideSequenceViewer: function () { this.setState({ showSequenceViewer: false }); }, viewSequenceLink: function () { return encodeURI(`get_sequence/?sequence_ids=${this.accession()}&database_ids=${this.databaseIDs()}`); }, downloadFASTA: function (event) { var accessions = [this.accession()]; downloadFASTA(accessions, this.databaseIDs()); }, // Event-handler for exporting alignments. // Calls relevant method on AlignmentExporter defined in alignment_exporter.js. downloadAlignment: function (event) { var hsps = _.map(this.props.hit.hsps, _.bind(function (hsp) { hsp.query_id = this.props.query.id; hsp.hit_id = this.props.hit.id; return hsp; }, this)) var aln_exporter = new AlignmentExporter(); aln_exporter.export_alignments(hsps, this.props.query.id+"_"+this.props.hit.id); }, // Life cycle methods // getInitialState: function () { return { showSequenceViewer: false }; }, // Return JSX for view sequence button. viewSequenceButton: function () { if (this.length() > 10000) { return ( ); } else { return ( ); } }, render: function () { return (

  {this.props.hit.id}   {this.props.hit.title}

{this.props.hit.number + "/" + this.props.query.hits.length}
{ this.hitLinks() } { this.hspListJSX() }
); }, hitLinks: function () { return (
{ !this.props.imported_xml && [ | , this.viewSequenceButton(), this.state.showSequenceViewer && ] } { !this.props.imported_xml && [ | , ] } | { _.map(this.props.hit.links, _.bind(function (link) { return [ | , this.a(link)]; }, this)) }
); }, hspListJSX: function () { return
{ this.props.hit.hsps.map((hsp) => { return }, this) }
} }); /** * 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 (

{this.props.sequence.id}   {this.props.sequence.title}

); }, componentDidMount: function () { // attach BioJS sequence viewer var widget = new Sequence({ sequence: this.props.sequence.value, target: this.widgetID, format: 'PRIDE', columns: { size: 40, spacedEach: 0 }, formatOptions: { title: false, footer: false } }); widget.hideFormatSelector(); } }); return React.createClass({ // Kind of public API. // /** * Shows sequence viewer. */ show: function () { this.modal().modal('show'); }, // Internal helpers. // modal: function () { return $(React.findDOMNode(this.refs.modal)); }, resultsJSX: function () { return (
{ _.map(this.state.error_msgs, _.bind(function (error_msg) { return (

{error_msg[0]}

                                            {error_msg[1]}
                                        
); }, this)) } { _.map(this.state.sequences, _.bind(function (sequence) { return (); }, this)) }
); }, loadingJSX: function () { return (
); }, // Lifecycle methods. // getInitialState: function () { return { error_msgs: [], sequences: [], requestCompleted: false }; }, render: function () { return (

View sequence

{ this.state.requestCompleted && this.resultsJSX() || this.loadingJSX() }
); }, componentDidMount: function () { // Display modal with a spinner. this.show(); // Fetch sequence and update state. $.getJSON(this.props.url) .done(_.bind(function (response) { this.setState({ sequences: response.sequences, error_msgs: response.error_msgs, requestCompleted: true }) }, this)) .fail(function (jqXHR, status, error) { showErrorModal(jqXHR, function () { this.hide(); }); }); this.modal().on('hidden.bs.modal', this.props.onHide); }, }); })(); /** * Renders links for downloading hit information in different formats. * Renders links for navigating to each query. */ var SideBar = React.createClass({ /** * Event-handler for downloading fasta of all hits. */ downloadFastaOfAll: function () { var sequence_ids = $('.hit-links :checkbox').map(function () { return this.value; }).get(); var database_ids = _.map(this.props.data.querydb, _.iteratee('id')); downloadFASTA(sequence_ids, database_ids); }, /** * Handles downloading fasta of selected hits. */ downloadFastaOfSelected: function () { var sequence_ids = $('.hit-links :checkbox:checked').map(function () { return this.value; }).get(); var database_ids = _.map(this.props.data.querydb, _.iteratee('id')); downloadFASTA(sequence_ids, database_ids); }, downloadAlignmentOfAll: function() { var sequence_ids = $('.hit-links :checkbox').map(function () { return this.value; }).get(); var hsps_arr = []; var aln_exporter = new AlignmentExporter(); _.each(this.props.data.queries, _.bind(function (query) { _.each(query.hits, function (hit) { _.each(hit.hsps, function (hsp) { hsp.hit_id = hit.id; hsp.query_id = query.id; hsps_arr.push(hsp); }) }) }, this)); console.log('len '+hsps_arr.length); aln_exporter.export_alignments(hsps_arr, "alignment-"+sequence_ids.length+"_hits"); }, downloadAlignmentOfSelected: function () { var sequence_ids = $('.hit-links :checkbox:checked').map(function () { return this.value; }).get(); var hsps_arr = []; var aln_exporter = new AlignmentExporter(); console.log('check '+sequence_ids.toString()); _.each(this.props.data.queries, _.bind(function (query) { _.each(query.hits, function (hit) { if (_.indexOf(sequence_ids, hit.accession) != -1) { _.each(hit.hsps, function (hsp) { hsp.hit_id = hit.id; hsp.query_id = query.id; hsps_arr.push(hsp); }); } }); }, this)); aln_exporter.export_alignments(hsps_arr, "alignment-"+sequence_ids.length+"_hits"); }, // JSX // render: function () { return (
{ this.props.shouldShowIndex && this.index() } { this.downloads() }
) }, index: function () { return (

{ this.summary() }

); }, summary: function () { var program = this.props.data.program; var numqueries = this.props.data.queries.length; var numquerydb = this.props.data.querydb.length; return ( program.toUpperCase() + ': ' + numqueries + ' ' + (numqueries > 1 ? 'queries' : 'query') + ", " + numquerydb + ' ' + (numquerydb > 1 ? 'databases' : 'database') ); }, downloads: function () { return (

Download FASTA, XML, TSV

); }, }); React.render(, document.getElementById('view'));