sequenceserver 2.0.0.beta4 → 2.0.0.rc5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.dockerignore +1 -0
- data/.travis.yml +7 -4
- data/AppImage/sequenceserver.sh +5 -0
- data/Dockerfile +14 -12
- data/bin/sequenceserver +37 -28
- data/lib/sequenceserver.rb +35 -7
- data/lib/sequenceserver/blast/job.rb +18 -25
- data/lib/sequenceserver/blast/report.rb +68 -34
- data/lib/sequenceserver/config.rb +1 -1
- data/lib/sequenceserver/database.rb +0 -129
- data/lib/sequenceserver/makeblastdb.rb +243 -0
- data/lib/sequenceserver/routes.rb +28 -2
- data/lib/sequenceserver/version.rb +1 -1
- data/public/SequenceServer_logo.png +0 -0
- data/public/css/grapher.css +8 -15
- data/public/css/sequenceserver.css +119 -55
- data/public/css/sequenceserver.min.css +3 -3
- data/public/js/circos.js +1 -1
- data/public/js/download_fasta.js +17 -0
- data/public/js/grapher.js +7 -9
- data/public/js/hit.js +217 -0
- data/public/js/hits_overview.js +12 -13
- data/public/js/hsp.js +104 -84
- data/public/js/{sequenceserver.js → jquery_world.js} +1 -18
- data/public/js/kablammo.js +337 -334
- data/public/js/length_distribution.js +1 -1
- data/public/js/query.js +147 -0
- data/public/js/report.js +216 -836
- data/public/js/search.js +194 -192
- data/public/js/sequence_modal.js +167 -0
- data/public/js/sidebar.js +210 -0
- data/public/js/utils.js +2 -19
- data/public/js/visualisation_helpers.js +2 -2
- data/public/sequenceserver-report.min.js +19 -19
- data/public/sequenceserver-search.min.js +11 -11
- data/public/vendor/github/twbs/bootstrap@3.3.5/js/bootstrap.js +2 -2
- data/spec/blast_versions/blast_2.2.30/import_spec_capybara_local_2.2.30.rb +15 -15
- data/spec/blast_versions/blast_2.2.31/import_spec_capybara_local_2.2.31.rb +15 -15
- data/spec/blast_versions/blast_2.3.0/import_spec_capybara_local_2.3.0.rb +15 -15
- data/spec/blast_versions/blast_2.4.0/import_spec_capybara_local_2.4.0.rb +15 -15
- data/spec/blast_versions/blast_2.5.0/import_spec_capybara_local_2.5.0.rb +15 -15
- data/spec/blast_versions/blast_2.6.0/import_spec_capybara_local_2.6.0.rb +15 -15
- data/spec/blast_versions/blast_2.7.1/import_spec_capybara_local_2.7.1.rb +15 -15
- data/spec/blast_versions/blast_2.8.1/import_spec_capybara_local_2.8.1.rb +15 -15
- data/spec/blast_versions/blast_2.9.0/import_spec_capybara_local_2.9.0.rb +15 -15
- data/spec/blast_versions/diamond_0.9.24/import_spec_capybara_local_0.9.24.rb +6 -6
- data/spec/capybara_spec.rb +14 -3
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.ndb +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nhr +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nin +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nos +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.not +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.ntf +0 -0
- data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nto +0 -0
- data/spec/database/sample/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.pdb +0 -0
- data/spec/database/sample/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.phr +0 -0
- data/spec/database/sample/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.pin +0 -0
- data/spec/database/sample/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.pos +0 -0
- data/spec/database/sample/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.pot +0 -0
- data/spec/database/sample/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.ptf +0 -0
- data/spec/database/sample/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.pto +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.pdb +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.phr +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.pin +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.pos +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.pot +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.ptf +0 -0
- data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.pto +0 -0
- data/spec/database/sample/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.ndb +0 -0
- data/spec/database/sample/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nhr +0 -0
- data/spec/database/sample/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nin +0 -0
- data/spec/database/sample/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nos +0 -0
- data/spec/database/sample/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.not +0 -0
- data/spec/database/sample/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nsq +0 -0
- data/spec/database/sample/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.ntf +0 -0
- data/spec/database/sample/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nto +0 -0
- data/spec/database/v4/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nhd +8 -0
- data/spec/database/v4/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nhi +0 -0
- data/spec/database/v4/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nhr +0 -0
- data/spec/database/v4/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nin +0 -0
- data/spec/database/v4/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nog +0 -0
- data/spec/database/{sample → v4}/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nsd +0 -0
- data/spec/database/{sample → v4}/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nsi +0 -0
- data/spec/database/v4/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nsq +0 -0
- data/spec/database/v4/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.txt +8 -0
- data/spec/database/v4/links.rb +23 -0
- data/spec/database/v4/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta +6449 -0
- data/spec/database/v4/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.phd +1189 -0
- data/spec/database/v4/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.phi +0 -0
- data/spec/database/v4/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.phr +0 -0
- data/spec/database/v4/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.pin +0 -0
- data/spec/database/v4/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.pog +0 -0
- data/spec/database/{sample → v4}/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.psd +0 -0
- data/spec/database/{sample → v4}/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.psi +0 -0
- data/spec/database/v4/proteins/Solenopsis_invicta/Sinvicta2-2-3.prot.subset.fasta.psq +0 -0
- data/spec/database/v4/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.phd +9140 -0
- data/spec/database/v4/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.phi +0 -0
- data/spec/database/v4/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.phr +0 -0
- data/spec/database/v4/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.pin +0 -0
- data/spec/database/v4/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.pog +0 -0
- data/spec/database/{sample → v4}/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.psd +0 -0
- data/spec/database/{sample → v4}/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.psi +0 -0
- data/spec/database/v4/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.psq +0 -0
- data/spec/database/v4/proteins/uniprot/URL +1 -0
- data/spec/database/v4/si_uniprot_idmap.yml +14180 -0
- data/spec/database/v4/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta +5486 -0
- data/spec/database/v4/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nhd +473 -0
- data/spec/database/v4/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nhi +0 -0
- data/spec/database/v4/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nhr +0 -0
- data/spec/database/v4/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nin +0 -0
- data/spec/database/v4/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nog +0 -0
- data/spec/database/{sample → v4}/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nsd +0 -0
- data/spec/database/{sample → v4}/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nsi +0 -0
- data/spec/database/v4/transcripts/Solenopsis_invicta/Sinvicta2-2-3.cdna.subset.fasta.nsq +0 -0
- data/spec/database_spec.rb +0 -76
- data/spec/makeblastdb_spec.rb +121 -0
- data/views/layout.erb +5 -1
- metadata +75 -15
data/public/js/query.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import _ from 'underscore';
|
|
3
|
+
|
|
4
|
+
import HitsOverview from './hits_overview';
|
|
5
|
+
import LengthDistribution from './length_distribution'; // length distribution of hits
|
|
6
|
+
import Utils from './utils'; // to use as mixin in HitsTable
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Query component displays query defline, graphical overview, length
|
|
10
|
+
* distribution, and hits table.
|
|
11
|
+
*/
|
|
12
|
+
export default React.createClass({
|
|
13
|
+
|
|
14
|
+
// Kind of public API //
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns the id of query.
|
|
18
|
+
*/
|
|
19
|
+
domID: function () {
|
|
20
|
+
return 'Query_' + this.props.query.number;
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
queryLength: function () {
|
|
24
|
+
return this.props.query.length;
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns number of hits.
|
|
29
|
+
*/
|
|
30
|
+
numhits: function () {
|
|
31
|
+
return this.props.query.hits.length;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// Life cycle methods //
|
|
35
|
+
|
|
36
|
+
render: function () {
|
|
37
|
+
return (
|
|
38
|
+
<div className="resultn" id={this.domID()}
|
|
39
|
+
data-query-len={this.props.query.length}
|
|
40
|
+
data-algorithm={this.props.program}>
|
|
41
|
+
{ this.headerJSX() }
|
|
42
|
+
{ this.numhits() && this.hitsListJSX() || this.noHitsJSX() }
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
headerJSX: function () {
|
|
48
|
+
var meta = `length: ${this.queryLength().toLocaleString()}`;
|
|
49
|
+
if (this.props.showQueryCrumbs) {
|
|
50
|
+
meta = `query ${this.props.query.number}, ` + meta;
|
|
51
|
+
}
|
|
52
|
+
return <div className="section-header">
|
|
53
|
+
<h3>
|
|
54
|
+
<strong>Query= {this.props.query.id}</strong>
|
|
55
|
+
{this.props.query.title}
|
|
56
|
+
</h3>
|
|
57
|
+
<span className="label label-reset pos-label">{ meta }</span>
|
|
58
|
+
</div>;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
hitsListJSX: function () {
|
|
62
|
+
return <div className="section-content">
|
|
63
|
+
<HitsOverview key={'GO_' + this.props.query.number} query={this.props.query} program={this.props.program} collapsed={this.props.veryBig} />
|
|
64
|
+
<LengthDistribution key={'LD_' + this.props.query.id} query={this.props.query} algorithm={this.props.program} collapsed="true" />
|
|
65
|
+
<HitsTable key={'HT_' + this.props.query.number} query={this.props.query} imported_xml={this.props.imported_xml} />
|
|
66
|
+
</div>;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
noHitsJSX: function () {
|
|
70
|
+
return <div className="section-content">
|
|
71
|
+
<strong> ****** No hits found ****** </strong>
|
|
72
|
+
</div>;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// Each update cycle will cause all previous queries to be re-rendered.
|
|
76
|
+
// We avoid that by implementing shouldComponentUpdate life-cycle hook.
|
|
77
|
+
// The trick is to simply check if the components has recieved props
|
|
78
|
+
// before.
|
|
79
|
+
shouldComponentUpdate: function () {
|
|
80
|
+
// If the component has received props before, query property will
|
|
81
|
+
// be set on it. If it is, we return false so that the component
|
|
82
|
+
// is not re-rendered. If the query property is not set, we return
|
|
83
|
+
// true: this must be the first time react is trying to render the
|
|
84
|
+
// component.
|
|
85
|
+
return !this.props.query;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Renders summary of all hits per query in a tabular form.
|
|
91
|
+
*/
|
|
92
|
+
var HitsTable = React.createClass({
|
|
93
|
+
mixins: [Utils],
|
|
94
|
+
render: function () {
|
|
95
|
+
var count = 0,
|
|
96
|
+
hasName = _.every(this.props.query.hits, function(hit) {
|
|
97
|
+
return hit.sciname !== '';
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div className="table-hit-overview">
|
|
102
|
+
<h4 className="caption" data-toggle="collapse" data-target={'#Query_'+this.props.query.number+'HT_'+this.props.query.number}>
|
|
103
|
+
<i className="fa fa-minus-square-o"></i>
|
|
104
|
+
<span>Summary table of hits</span>
|
|
105
|
+
</h4>
|
|
106
|
+
<div className="collapsed in"id={'Query_'+ this.props.query.number + 'HT_'+ this.props.query.number}>
|
|
107
|
+
<table
|
|
108
|
+
className="table table-hover table-condensed tabular-view ">
|
|
109
|
+
<thead>
|
|
110
|
+
<th className="text-left">#</th>
|
|
111
|
+
<th>Similar sequences</th>
|
|
112
|
+
{hasName && <th className="text-left">Species</th>}
|
|
113
|
+
{!this.props.imported_xml && <th className="text-right">Query coverage (%)</th>}
|
|
114
|
+
<th className="text-right">Total score</th>
|
|
115
|
+
<th className="text-right">E value</th>
|
|
116
|
+
<th className="text-right" data-toggle="tooltip"
|
|
117
|
+
data-placement="left" title="Total identity of all hsps / total length of all hsps">
|
|
118
|
+
Identity (%)
|
|
119
|
+
</th>
|
|
120
|
+
</thead>
|
|
121
|
+
<tbody>
|
|
122
|
+
{
|
|
123
|
+
_.map(this.props.query.hits, _.bind(function (hit) {
|
|
124
|
+
return (
|
|
125
|
+
<tr key={hit.number}>
|
|
126
|
+
<td className="text-left">{hit.number + '.'}</td>
|
|
127
|
+
<td>
|
|
128
|
+
<a href={'#Query_' + this.props.query.number + '_hit_' + hit.number} className="btn-link">
|
|
129
|
+
{hit.id}
|
|
130
|
+
</a>
|
|
131
|
+
</td>
|
|
132
|
+
{hasName && <td className="text-left">{hit.sciname}</td>}
|
|
133
|
+
{!this.props.imported_xml && <td className="text-right">{hit.qcovs}</td>}
|
|
134
|
+
<td className="text-right">{hit.score}</td>
|
|
135
|
+
<td className="text-right">{this.inExponential(hit.hsps[0].evalue)}</td>
|
|
136
|
+
<td className="text-right">{hit.identity}</td>
|
|
137
|
+
</tr>
|
|
138
|
+
);
|
|
139
|
+
}, this))
|
|
140
|
+
}
|
|
141
|
+
</tbody>
|
|
142
|
+
</table>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
});
|
data/public/js/report.js
CHANGED
|
@@ -1,35 +1,16 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import './jquery_world'; // for custom $.tooltip function
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import _ from 'underscore';
|
|
4
4
|
|
|
5
|
+
import Sidebar from './sidebar';
|
|
5
6
|
import Circos from './circos';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import HSPOverview from './kablammo';
|
|
9
|
-
import AlignmentExporter from './alignment_exporter'; // to download textual alignment
|
|
7
|
+
import Query from './query';
|
|
8
|
+
import Hit from './hit';
|
|
10
9
|
import HSP from './hsp';
|
|
11
|
-
import './sequence';
|
|
12
10
|
|
|
13
|
-
import
|
|
14
|
-
import Utils from './utils'; // to use as mixin in Hit and HitsTable
|
|
11
|
+
import SequenceModal from './sequence_modal';
|
|
15
12
|
import showErrorModal from './error_modal';
|
|
16
13
|
|
|
17
|
-
/**
|
|
18
|
-
* Dynamically create form and submit.
|
|
19
|
-
*/
|
|
20
|
-
var downloadFASTA = function (sequence_ids, database_ids) {
|
|
21
|
-
var form = $('<form/>').attr('method', 'post').attr('action', 'get_sequence');
|
|
22
|
-
addField('sequence_ids', sequence_ids);
|
|
23
|
-
addField('database_ids', database_ids);
|
|
24
|
-
form.appendTo('body').submit().remove();
|
|
25
|
-
|
|
26
|
-
function addField(name, val) {
|
|
27
|
-
form.append(
|
|
28
|
-
$('<input>').attr('type', 'hidden').attr('name', name).val(val)
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
14
|
/**
|
|
34
15
|
* Base component of report page. This component is later rendered into page's
|
|
35
16
|
* '#view' element.
|
|
@@ -40,12 +21,39 @@ var Page = React.createClass({
|
|
|
40
21
|
<div>
|
|
41
22
|
{/* Provide bootstrap .container element inside the #view for
|
|
42
23
|
the Report component to render itself in. */}
|
|
43
|
-
<div className="container"
|
|
24
|
+
<div className="container">
|
|
25
|
+
<Report showSequenceModal={ _ => this.showSequenceModal(_) }
|
|
26
|
+
getCharacterWidth={ () => this.getCharacterWidth() } />
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
{/* Add a hidden span tag containing chars used in HSPs */}
|
|
30
|
+
<pre className="pre-reset hsp-lines" ref="hspChars" hidden>
|
|
31
|
+
ABCDEFGHIJKLMNOPQRSTUVWXYZ +-
|
|
32
|
+
</pre>
|
|
44
33
|
|
|
45
34
|
{/* Required by Grapher for SVG and PNG download */}
|
|
46
35
|
<canvas id="png-exporter" hidden></canvas>
|
|
36
|
+
|
|
37
|
+
<SequenceModal ref="sequenceModal" />
|
|
47
38
|
</div>
|
|
48
39
|
);
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
componentDidMount: function () {
|
|
43
|
+
var job_id = location.pathname.split('/').pop();
|
|
44
|
+
sessionStorage.setItem('job_id', job_id);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
showSequenceModal: function (url) {
|
|
48
|
+
this.refs.sequenceModal.show(url);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
getCharacterWidth: function () {
|
|
52
|
+
if (!this.characterWidth) {
|
|
53
|
+
var $hspChars = $(React.findDOMNode(this.refs.hspChars));
|
|
54
|
+
this.characterWidth = $hspChars.width() / 29;
|
|
55
|
+
}
|
|
56
|
+
return this.characterWidth;
|
|
49
57
|
}
|
|
50
58
|
});
|
|
51
59
|
|
|
@@ -60,15 +68,23 @@ var Report = React.createClass({
|
|
|
60
68
|
|
|
61
69
|
getInitialState: function () {
|
|
62
70
|
this.fetchResults();
|
|
63
|
-
|
|
71
|
+
|
|
72
|
+
// Properties below are internal state used to render results in small
|
|
73
|
+
// slices (see updateState).
|
|
74
|
+
this.numUpdates = 0;
|
|
75
|
+
this.nextQuery = 0;
|
|
76
|
+
this.nextHit = 0;
|
|
77
|
+
this.nextHSP = 0;
|
|
78
|
+
this.maxHSPs = 3; // max HSPs to render in a cycle
|
|
64
79
|
|
|
65
80
|
return {
|
|
66
81
|
search_id: '',
|
|
82
|
+
seqserv_version: '',
|
|
67
83
|
program: '',
|
|
68
84
|
program_version: '',
|
|
69
85
|
submitted_at: '',
|
|
70
|
-
num_queries: 0,
|
|
71
86
|
queries: [],
|
|
87
|
+
results: [],
|
|
72
88
|
querydb: [],
|
|
73
89
|
params: [],
|
|
74
90
|
stats: []
|
|
@@ -97,7 +113,7 @@ var Report = React.createClass({
|
|
|
97
113
|
setTimeout(poll, interval);
|
|
98
114
|
break;
|
|
99
115
|
case 200:
|
|
100
|
-
component.
|
|
116
|
+
component.setStateFromJSON(jqXHR.responseJSON);
|
|
101
117
|
break;
|
|
102
118
|
case 404:
|
|
103
119
|
case 400:
|
|
@@ -112,38 +128,139 @@ var Report = React.createClass({
|
|
|
112
128
|
},
|
|
113
129
|
|
|
114
130
|
/**
|
|
115
|
-
*
|
|
116
|
-
* process is not overwhelemed when there are too many queries.
|
|
131
|
+
* Calls setState after any required modification to responseJSON.
|
|
117
132
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
responseJSON.num_queries = queries.length;
|
|
121
|
-
responseJSON.veryBig = queries.length > 250;
|
|
122
|
-
responseJSON.queries = queries.splice(0, 50);
|
|
133
|
+
setStateFromJSON: function(responseJSON) {
|
|
134
|
+
this.lastTimeStamp = Date.now();
|
|
123
135
|
this.setState(responseJSON);
|
|
124
|
-
|
|
125
|
-
// Render results for remaining queries.
|
|
126
|
-
var update = function () {
|
|
127
|
-
if (queries.length > 0) {
|
|
128
|
-
this.setState({
|
|
129
|
-
queries: this.state.queries.concat(queries.splice(0, 50))
|
|
130
|
-
});
|
|
131
|
-
setTimeout(update.bind(this), 500);
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
this.componentFinishedUpdating();
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
setTimeout(update.bind(this), 500);
|
|
138
136
|
},
|
|
139
137
|
|
|
140
|
-
|
|
141
|
-
// View //
|
|
138
|
+
// Life-cycle methods //
|
|
142
139
|
render: function () {
|
|
143
140
|
return this.isResultAvailable() ?
|
|
144
141
|
this.resultsJSX() : this.loadingJSX();
|
|
145
142
|
},
|
|
146
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Called as soon as the page has loaded and the user sees the loading spinner.
|
|
146
|
+
* We use this opportunity to setup services that make use of delegated events
|
|
147
|
+
* bound to the window, document, or body.
|
|
148
|
+
*/
|
|
149
|
+
componentDidMount: function () {
|
|
150
|
+
// This sets up an event handler which enables users to select text from
|
|
151
|
+
// hit header without collapsing the hit.
|
|
152
|
+
this.preventCollapseOnSelection();
|
|
153
|
+
this.toggleTable();
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Called for the first time after as BLAST results have been retrieved from
|
|
158
|
+
* the server and added to this.state by fetchResults. Only summary overview
|
|
159
|
+
* and circos would have been rendered at this point. At this stage we kick
|
|
160
|
+
* start iteratively updating 10 HSPs (and as many hits and queries) every
|
|
161
|
+
* 25 milli-seconds.
|
|
162
|
+
*/
|
|
163
|
+
componentDidUpdate: function () {
|
|
164
|
+
// Log to console how long the last update take?
|
|
165
|
+
console.log((Date.now() - this.lastTimeStamp)/1000);
|
|
166
|
+
|
|
167
|
+
// Lock sidebar in its position on the first update.
|
|
168
|
+
if (this.nextQuery == 0 && this.nextHit == 0 && this.nextHSP == 0) {
|
|
169
|
+
this.affixSidebar();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Queue next update if we have not rendered all results yet.
|
|
173
|
+
if (this.nextQuery < this.state.queries.length) {
|
|
174
|
+
// setTimeout is used to clear call stack and space out
|
|
175
|
+
// the updates giving the browser a chance to respond
|
|
176
|
+
// to user interactions.
|
|
177
|
+
setTimeout(() => this.updateState(), 25);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
this.componentFinishedUpdating();
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Push next slice of results to React for rendering.
|
|
186
|
+
*/
|
|
187
|
+
updateState: function() {
|
|
188
|
+
var results = [];
|
|
189
|
+
var numHSPsProcessed = 0;
|
|
190
|
+
while (this.nextQuery < this.state.queries.length) {
|
|
191
|
+
var query = this.state.queries[this.nextQuery];
|
|
192
|
+
// We may see a query multiple times during rendering because only
|
|
193
|
+
// 3 hsps or are rendered in each cycle, but we want to create the
|
|
194
|
+
// corresponding Query component only the first time we see it.
|
|
195
|
+
if (this.nextHit == 0 && this.nextHSP == 0) {
|
|
196
|
+
results.push(<Query key={'Query_'+query.number} query={query}
|
|
197
|
+
program={this.state.program} querydb={this.state.querydb}
|
|
198
|
+
showQueryCrumbs={this.state.queries.length > 1}
|
|
199
|
+
imported_xml={this.state.imported_xml}
|
|
200
|
+
veryBig={this.state.veryBig} />);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
while (this.nextHit < query.hits.length) {
|
|
204
|
+
var hit = query.hits[this.nextHit];
|
|
205
|
+
// We may see a hit multiple times during rendering because only
|
|
206
|
+
// 10 hsps are rendered in each cycle, but we want to create the
|
|
207
|
+
// corresponding Hit component only the first time we see it.
|
|
208
|
+
if (this.nextHSP == 0) {
|
|
209
|
+
results.push(<Hit key={'Query_'+query.number+'_Hit_'+hit.number} query={query}
|
|
210
|
+
hit={hit} algorithm={this.state.program} querydb={this.state.querydb}
|
|
211
|
+
selectHit={this.selectHit} imported_xml={this.state.imported_xml}
|
|
212
|
+
showQueryCrumbs={this.state.queries.length > 1}
|
|
213
|
+
showHitCrumbs={query.hits.length > 1}
|
|
214
|
+
veryBig={this.state.veryBig}
|
|
215
|
+
{... this.props} />
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
while (this.nextHSP < hit.hsps.length) {
|
|
220
|
+
// Get nextHSP and increment the counter.
|
|
221
|
+
var hsp = hit.hsps[this.nextHSP++];
|
|
222
|
+
results.push(
|
|
223
|
+
<HSP key={'Query_'+query.number+'_Hit_'+hit.number+'_HSP_'+hsp.number}
|
|
224
|
+
query={query} hit={hit} hsp={hsp} algorithm={this.state.program}
|
|
225
|
+
showHSPNumbers={hit.hsps.length > 1} {... this.props} />
|
|
226
|
+
);
|
|
227
|
+
numHSPsProcessed++;
|
|
228
|
+
if (numHSPsProcessed == this.maxHSPs) break;
|
|
229
|
+
}
|
|
230
|
+
// Are we here because we have iterated over all hsps of a hit,
|
|
231
|
+
// or because of the break clause in the inner loop?
|
|
232
|
+
if (this.nextHSP == hit.hsps.length) {
|
|
233
|
+
this.nextHit = this.nextHit + 1;
|
|
234
|
+
this.nextHSP = 0;
|
|
235
|
+
}
|
|
236
|
+
if (numHSPsProcessed == this.maxHSPs) break;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Are we here because we have iterated over all hits of a query,
|
|
240
|
+
// or because of the break clause in the inner loop?
|
|
241
|
+
if (this.nextHit == query.hits.length) {
|
|
242
|
+
this.nextQuery = this.nextQuery + 1;
|
|
243
|
+
this.nextHit = 0;
|
|
244
|
+
}
|
|
245
|
+
if (numHSPsProcessed == this.maxHSPs) break;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Push the components to react for rendering.
|
|
249
|
+
this.numUpdates++;
|
|
250
|
+
this.lastTimeStamp = Date.now();
|
|
251
|
+
this.setState({
|
|
252
|
+
results: this.state.results.concat(results),
|
|
253
|
+
veryBig: this.numUpdates >= 250
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Called after all results have been rendered.
|
|
259
|
+
*/
|
|
260
|
+
componentFinishedUpdating: function () {
|
|
261
|
+
this.shouldShowIndex() && this.setupScrollSpy();
|
|
262
|
+
},
|
|
263
|
+
|
|
147
264
|
/**
|
|
148
265
|
* Returns loading message
|
|
149
266
|
*/
|
|
@@ -179,25 +296,15 @@ var Report = React.createClass({
|
|
|
179
296
|
<div className="row">
|
|
180
297
|
{ this.shouldShowSidebar() &&
|
|
181
298
|
(
|
|
182
|
-
<div
|
|
183
|
-
|
|
184
|
-
<SideBar data={this.state} shouldShowIndex={this.shouldShowIndex()}/>
|
|
299
|
+
<div className="col-md-3 hidden-sm hidden-xs">
|
|
300
|
+
<Sidebar data={this.state} shouldShowIndex={this.shouldShowIndex()}/>
|
|
185
301
|
</div>
|
|
186
302
|
)
|
|
187
303
|
}
|
|
188
|
-
<div className={this.shouldShowSidebar() ?
|
|
189
|
-
'col-md-9' : 'col-md-12'}>
|
|
304
|
+
<div className={this.shouldShowSidebar() ? 'col-md-9' : 'col-md-12'}>
|
|
190
305
|
{ this.overviewJSX() }
|
|
191
306
|
{ this.circosJSX() }
|
|
192
|
-
{
|
|
193
|
-
_.map(this.state.queries, _.bind(function (query) {
|
|
194
|
-
return (
|
|
195
|
-
<Query key={'Query_'+query.id} query={query} showQueryCrumbs={this.state.num_queries > 1}
|
|
196
|
-
selectHit={this.selectHit} program={this.state.program} querydb={this.state.querydb}
|
|
197
|
-
veryBig={this.state.veryBig} imported_xml={this.state.imported_xml} />
|
|
198
|
-
);
|
|
199
|
-
}, this))
|
|
200
|
-
}
|
|
307
|
+
{ this.state.results }
|
|
201
308
|
</div>
|
|
202
309
|
</div>
|
|
203
310
|
);
|
|
@@ -209,23 +316,26 @@ var Report = React.createClass({
|
|
|
209
316
|
overviewJSX: function () {
|
|
210
317
|
return (
|
|
211
318
|
<div className="overview">
|
|
212
|
-
<p
|
|
213
|
-
{this.state.
|
|
214
|
-
|
|
319
|
+
<p>
|
|
320
|
+
<strong>SequenceServer {this.state.seqserv_version}</strong> using <strong>{this.state.program_version}</strong>
|
|
321
|
+
{this.state.submitted_at && `, query submitted on ${this.state.submitted_at}`}
|
|
215
322
|
</p>
|
|
216
|
-
<p
|
|
217
|
-
Databases: {
|
|
323
|
+
<p>
|
|
324
|
+
<strong> Databases: </strong>{
|
|
218
325
|
this.state.querydb.map((db) => { return db.title; }).join(', ')
|
|
219
326
|
} ({this.state.stats.nsequences} sequences,
|
|
220
327
|
{this.state.stats.ncharacters} characters)
|
|
221
328
|
</p>
|
|
222
|
-
<p
|
|
223
|
-
Parameters: {
|
|
329
|
+
<p>
|
|
330
|
+
<strong>Parameters: </strong> {
|
|
224
331
|
_.map(this.state.params, function (val, key) {
|
|
225
332
|
return key + ' ' + val;
|
|
226
333
|
}).join(', ')
|
|
227
334
|
}
|
|
228
335
|
</p>
|
|
336
|
+
<p>
|
|
337
|
+
Please cite: <a href="https://doi.org/10.1093/molbev/msz185">https://doi.org/10.1093/molbev/msz185</a>
|
|
338
|
+
</p>
|
|
229
339
|
</div>
|
|
230
340
|
);
|
|
231
341
|
},
|
|
@@ -258,6 +368,10 @@ var Report = React.createClass({
|
|
|
258
368
|
return this.state.queries.some(query => query.hits.length > 0);
|
|
259
369
|
},
|
|
260
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Does the report have at least two hits? This is used to determine
|
|
373
|
+
* whether Circos should be enabled or not.
|
|
374
|
+
*/
|
|
261
375
|
atLeastTwoHits: function () {
|
|
262
376
|
var hit_num = 0;
|
|
263
377
|
return this.state.queries.some(query => {
|
|
@@ -283,38 +397,7 @@ var Report = React.createClass({
|
|
|
283
397
|
*/
|
|
284
398
|
shouldShowIndex: function () {
|
|
285
399
|
var num_queries = this.state.queries.length;
|
|
286
|
-
return num_queries >= 2 && num_queries <=
|
|
287
|
-
},
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Called after first call to render. The results may not be available at
|
|
291
|
-
* this stage and thus results DOM cannot be scripted here, unless using
|
|
292
|
-
* delegated events bound to the window, document, or body.
|
|
293
|
-
*/
|
|
294
|
-
componentDidMount: function () {
|
|
295
|
-
// This sets up an event handler which enables users to select text
|
|
296
|
-
// from hit header without collapsing the hit.
|
|
297
|
-
this.preventCollapseOnSelection();
|
|
298
|
-
},
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Called after each state change. Only a part of results DOM may be
|
|
302
|
-
* available after a state change.
|
|
303
|
-
*/
|
|
304
|
-
componentDidUpdate: function () {
|
|
305
|
-
// We track the number of updates to the component.
|
|
306
|
-
this.updateCycle += 1;
|
|
307
|
-
|
|
308
|
-
// Lock sidebar in its position on first update of
|
|
309
|
-
// results DOM.
|
|
310
|
-
if (this.updateCycle === 1 ) this.affixSidebar();
|
|
311
|
-
},
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Called after all results have been rendered.
|
|
315
|
-
*/
|
|
316
|
-
componentFinishedUpdating: function () {
|
|
317
|
-
this.shouldShowIndex() && this.setupScrollSpy();
|
|
400
|
+
return num_queries >= 2 && num_queries <= 12;
|
|
318
401
|
},
|
|
319
402
|
|
|
320
403
|
/**
|
|
@@ -326,12 +409,9 @@ var Report = React.createClass({
|
|
|
326
409
|
$this.on('mouseup mousemove', function handler(event) {
|
|
327
410
|
if (event.type === 'mouseup') {
|
|
328
411
|
// user wants to toggle
|
|
329
|
-
$this.
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
var target = $('#' + $this.attr('data-target'));
|
|
333
|
-
target.toggleClass('in');
|
|
334
|
-
$this.find('.fa-chevron-down').toggleClass('fa-rotate-270');
|
|
412
|
+
var hitID = $this.parents('.hit').attr('id');
|
|
413
|
+
$(`div[data-parent-hit=${hitID}]`).toggle();
|
|
414
|
+
$this.find('i').toggleClass('fa-minus-square-o fa-plus-square-o');
|
|
335
415
|
} else {
|
|
336
416
|
// user wants to select
|
|
337
417
|
$this.attr('data-toggle', '');
|
|
@@ -341,16 +421,32 @@ var Report = React.createClass({
|
|
|
341
421
|
});
|
|
342
422
|
},
|
|
343
423
|
|
|
424
|
+
/* Handling the fa icon when Hit Table is collapsed */
|
|
425
|
+
toggleTable: function () {
|
|
426
|
+
$('body').on('mousedown', '.resultn > .section-content > .table-hit-overview > .caption', function (event) {
|
|
427
|
+
var $this = $(this);
|
|
428
|
+
$this.on('mouseup mousemove', function handler(event) {
|
|
429
|
+
$this.find('i').toggleClass('fa-minus-square-o fa-plus-square-o');
|
|
430
|
+
$this.off('mouseup mousemove', handler);
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
|
|
344
437
|
/**
|
|
345
438
|
* Affixes the sidebar.
|
|
346
439
|
*/
|
|
347
440
|
affixSidebar: function () {
|
|
348
441
|
var $sidebar = $('.sidebar');
|
|
349
|
-
$sidebar.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
442
|
+
var sidebarOffset = $sidebar.offset()
|
|
443
|
+
if (sidebarOffset) {
|
|
444
|
+
$sidebar.affix({
|
|
445
|
+
offset: {
|
|
446
|
+
top: sidebarOffset.top
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
354
450
|
},
|
|
355
451
|
|
|
356
452
|
/**
|
|
@@ -379,12 +475,14 @@ var Report = React.createClass({
|
|
|
379
475
|
// Highlight selected hit and enable 'Download FASTA/Alignment of
|
|
380
476
|
// selected' links.
|
|
381
477
|
if (checkbox.is(':checked')) {
|
|
382
|
-
$hit.
|
|
383
|
-
$('.
|
|
478
|
+
$hit.addClass('glow');
|
|
479
|
+
$hit.next('.hsp').addClass('glow');
|
|
384
480
|
$('.download-fasta-of-selected').enable();
|
|
481
|
+
$('.download-alignment-of-selected').enable();
|
|
385
482
|
}
|
|
386
483
|
else {
|
|
387
|
-
$hit.
|
|
484
|
+
$hit.removeClass('glow');
|
|
485
|
+
$hit.next('.hsp').removeClass('glow');
|
|
388
486
|
}
|
|
389
487
|
|
|
390
488
|
if (num_checked >= 1)
|
|
@@ -404,722 +502,4 @@ var Report = React.createClass({
|
|
|
404
502
|
},
|
|
405
503
|
});
|
|
406
504
|
|
|
407
|
-
/**
|
|
408
|
-
* Renders report for each query sequence.
|
|
409
|
-
*
|
|
410
|
-
* Composed of graphical overview, tabular summary (HitsTable),
|
|
411
|
-
* and a list of Hits.
|
|
412
|
-
*/
|
|
413
|
-
var Query = React.createClass({
|
|
414
|
-
|
|
415
|
-
// Kind of public API //
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Returns the id of query.
|
|
419
|
-
*/
|
|
420
|
-
domID: function () {
|
|
421
|
-
return 'Query_' + this.props.query.number;
|
|
422
|
-
},
|
|
423
|
-
|
|
424
|
-
queryLength: function () {
|
|
425
|
-
return this.props.query.length;
|
|
426
|
-
},
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Returns number of hits.
|
|
430
|
-
*/
|
|
431
|
-
numhits: function () {
|
|
432
|
-
return this.props.query.hits.length;
|
|
433
|
-
},
|
|
434
|
-
|
|
435
|
-
// Life cycle methods //
|
|
436
|
-
|
|
437
|
-
render: function () {
|
|
438
|
-
return (
|
|
439
|
-
<div className="resultn" id={this.domID()}
|
|
440
|
-
data-query-len={this.props.query.length}
|
|
441
|
-
data-algorithm={this.props.program}>
|
|
442
|
-
{ this.headerJSX() }
|
|
443
|
-
{ this.numhits() && this.hitsListJSX() || this.noHitsJSX() }
|
|
444
|
-
</div>
|
|
445
|
-
);
|
|
446
|
-
},
|
|
447
|
-
|
|
448
|
-
headerJSX: function () {
|
|
449
|
-
var meta = `length: ${this.queryLength().toLocaleString()}`;
|
|
450
|
-
if (this.props.showQueryCrumbs) {
|
|
451
|
-
meta = `query ${this.props.query.number}, ` + meta;
|
|
452
|
-
}
|
|
453
|
-
return <div className="section-header">
|
|
454
|
-
<h3>
|
|
455
|
-
Query= {this.props.query.id}
|
|
456
|
-
<small>{this.props.query.title}</small>
|
|
457
|
-
</h3>
|
|
458
|
-
<span className="label label-reset pos-label">{ meta }</span>
|
|
459
|
-
</div>;
|
|
460
|
-
},
|
|
461
|
-
|
|
462
|
-
hitsListJSX: function () {
|
|
463
|
-
return <div className="section-content">
|
|
464
|
-
<HitsOverview key={'GO_' + this.props.query.number} query={this.props.query} program={this.props.program} collapsed={this.props.veryBig} />
|
|
465
|
-
<LengthDistribution key={'LD_' + this.props.query.id} query={this.props.query} algorithm={this.props.program} collapsed="true" />
|
|
466
|
-
<HitsTable key={'HT_' + this.props.query.number} query={this.props.query} imported_xml={this.props.imported_xml} />
|
|
467
|
-
<div id="hits">
|
|
468
|
-
{
|
|
469
|
-
_.map(this.props.query.hits, _.bind(function (hit) {
|
|
470
|
-
return (
|
|
471
|
-
<Hit key={'HIT_' + hit.number} hit={hit}
|
|
472
|
-
algorithm={this.props.program}
|
|
473
|
-
querydb={this.props.querydb}
|
|
474
|
-
query={this.props.query}
|
|
475
|
-
imported_xml={this.props.imported_xml}
|
|
476
|
-
selectHit={this.props.selectHit}
|
|
477
|
-
showHitCrumbs={this.numhits() > 1}
|
|
478
|
-
showQueryCrumbs={this.props.showQueryCrumbs} />
|
|
479
|
-
);
|
|
480
|
-
}, this))
|
|
481
|
-
}
|
|
482
|
-
</div>
|
|
483
|
-
</div>;
|
|
484
|
-
},
|
|
485
|
-
|
|
486
|
-
noHitsJSX: function () {
|
|
487
|
-
return <div className="section-content">
|
|
488
|
-
<br />
|
|
489
|
-
<p>
|
|
490
|
-
<strong> ****** No hits found ****** </strong>
|
|
491
|
-
</p>
|
|
492
|
-
</div>;
|
|
493
|
-
},
|
|
494
|
-
|
|
495
|
-
shouldComponentUpdate: function (nextProps, nextState) {
|
|
496
|
-
if (!this.props.query) return true;
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* Renders summary of all hits per query in a tabular form.
|
|
502
|
-
*/
|
|
503
|
-
var HitsTable = React.createClass({
|
|
504
|
-
mixins: [Utils],
|
|
505
|
-
render: function () {
|
|
506
|
-
var count = 0,
|
|
507
|
-
hasName = _.every(this.props.query.hits, function(hit) {
|
|
508
|
-
return hit.sciname !== '';
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
return (
|
|
512
|
-
<table
|
|
513
|
-
className="table table-hover table-condensed tabular-view">
|
|
514
|
-
<thead>
|
|
515
|
-
<th className="text-left">#</th>
|
|
516
|
-
<th>Similar sequences</th>
|
|
517
|
-
{hasName && <th className="text-left">Species</th>}
|
|
518
|
-
{!this.props.imported_xml && <th className="text-right">Query coverage (%)</th>}
|
|
519
|
-
<th className="text-right">Total score</th>
|
|
520
|
-
<th className="text-right">E value</th>
|
|
521
|
-
<th className="text-right" data-toggle="tooltip"
|
|
522
|
-
data-placement="left" title="Total identity of all hsps / total length of all hsps">
|
|
523
|
-
Identity (%)
|
|
524
|
-
</th>
|
|
525
|
-
</thead>
|
|
526
|
-
<tbody>
|
|
527
|
-
{
|
|
528
|
-
_.map(this.props.query.hits, _.bind(function (hit) {
|
|
529
|
-
return (
|
|
530
|
-
<tr key={hit.number}>
|
|
531
|
-
<td className="text-left">{hit.number + '.'}</td>
|
|
532
|
-
<td>
|
|
533
|
-
<a href={'#Query_' + this.props.query.number + '_hit_' + hit.number}>
|
|
534
|
-
{hit.id}
|
|
535
|
-
</a>
|
|
536
|
-
</td>
|
|
537
|
-
{hasName && <td className="text-left">{hit.sciname}</td>}
|
|
538
|
-
{!this.props.imported_xml && <td className="text-right">{hit.qcovs}</td>}
|
|
539
|
-
<td className="text-right">{hit.score}</td>
|
|
540
|
-
<td className="text-right">{this.inExponential(hit.hsps[0].evalue)}</td>
|
|
541
|
-
<td className="text-right">{hit.identity}</td>
|
|
542
|
-
</tr>
|
|
543
|
-
);
|
|
544
|
-
}, this))
|
|
545
|
-
}
|
|
546
|
-
</tbody>
|
|
547
|
-
</table>
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Component for each hit.
|
|
554
|
-
*/
|
|
555
|
-
var Hit = React.createClass({
|
|
556
|
-
mixins: [Utils],
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Returns accession number of the hit sequence.
|
|
560
|
-
*/
|
|
561
|
-
accession: function () {
|
|
562
|
-
return this.props.hit.accession;
|
|
563
|
-
},
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* Returns length of the hit sequence.
|
|
567
|
-
*/
|
|
568
|
-
hitLength: function () {
|
|
569
|
-
return this.props.hit.length;
|
|
570
|
-
},
|
|
571
|
-
|
|
572
|
-
// Internal helpers. //
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* Returns id that will be used for the DOM node corresponding to the hit.
|
|
576
|
-
*/
|
|
577
|
-
domID: function () {
|
|
578
|
-
return 'Query_' + this.props.query.number + '_hit_' + this.props.hit.number;
|
|
579
|
-
},
|
|
580
|
-
|
|
581
|
-
databaseIDs: function () {
|
|
582
|
-
return _.map(this.props.querydb, _.iteratee('id'));
|
|
583
|
-
},
|
|
584
|
-
|
|
585
|
-
showSequenceViewer: function (event) {
|
|
586
|
-
this.setState({ showSequenceViewer: true });
|
|
587
|
-
event && event.preventDefault();
|
|
588
|
-
},
|
|
589
|
-
|
|
590
|
-
hideSequenceViewer: function () {
|
|
591
|
-
this.setState({ showSequenceViewer: false });
|
|
592
|
-
},
|
|
593
|
-
|
|
594
|
-
viewSequenceLink: function () {
|
|
595
|
-
return encodeURI(`get_sequence/?sequence_ids=${this.accession()}&database_ids=${this.databaseIDs()}`);
|
|
596
|
-
},
|
|
597
|
-
|
|
598
|
-
downloadFASTA: function (event) {
|
|
599
|
-
var accessions = [this.accession()];
|
|
600
|
-
downloadFASTA(accessions, this.databaseIDs());
|
|
601
|
-
},
|
|
602
|
-
|
|
603
|
-
// Event-handler for exporting alignments.
|
|
604
|
-
// Calls relevant method on AlignmentExporter defined in alignment_exporter.js.
|
|
605
|
-
downloadAlignment: function (event) {
|
|
606
|
-
var hsps = _.map(this.props.hit.hsps, _.bind(function (hsp) {
|
|
607
|
-
hsp.query_id = this.props.query.id;
|
|
608
|
-
hsp.hit_id = this.props.hit.id;
|
|
609
|
-
return hsp;
|
|
610
|
-
}, this));
|
|
611
|
-
|
|
612
|
-
var aln_exporter = new AlignmentExporter();
|
|
613
|
-
aln_exporter.export_alignments(hsps, this.props.query.id+'_'+this.props.hit.id);
|
|
614
|
-
},
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// Life cycle methods //
|
|
618
|
-
|
|
619
|
-
getInitialState: function () {
|
|
620
|
-
return { showSequenceViewer: false };
|
|
621
|
-
},
|
|
622
|
-
|
|
623
|
-
// Return JSX for view sequence button.
|
|
624
|
-
viewSequenceButton: function () {
|
|
625
|
-
if (this.hitLength() > 10000) {
|
|
626
|
-
return (
|
|
627
|
-
<button
|
|
628
|
-
className="btn btn-link view-sequence disabled"
|
|
629
|
-
title="Sequence too long" disabled="true">
|
|
630
|
-
<i className="fa fa-eye"></i> Sequence
|
|
631
|
-
</button>
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
else {
|
|
635
|
-
return (
|
|
636
|
-
<button
|
|
637
|
-
className="btn btn-link view-sequence"
|
|
638
|
-
onClick={this.showSequenceViewer}>
|
|
639
|
-
<i className="fa fa-eye"></i> Sequence
|
|
640
|
-
</button>
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
},
|
|
644
|
-
|
|
645
|
-
render: function () {
|
|
646
|
-
return (
|
|
647
|
-
<div className="hit" id={this.domID()} data-hit-def={this.props.hit.id}
|
|
648
|
-
data-hit-len={this.props.hit.length} data-hit-evalue={this.props.hit.evalue}>
|
|
649
|
-
{ this.headerJSX() } { this.contentJSX() }
|
|
650
|
-
</div>
|
|
651
|
-
);
|
|
652
|
-
},
|
|
653
|
-
|
|
654
|
-
headerJSX: function () {
|
|
655
|
-
var meta = `length: ${this.hitLength().toLocaleString()}`;
|
|
656
|
-
|
|
657
|
-
if (this.props.showQueryCrumbs && this.props.showHitCrumbs) {
|
|
658
|
-
// Multiper queries, multiple hits
|
|
659
|
-
meta = `hit ${this.props.hit.number} of query ${this.props.query.number}, ` + meta;
|
|
660
|
-
}
|
|
661
|
-
else if (this.props.showQueryCrumbs && !this.props.showHitCrumbs) {
|
|
662
|
-
// Multiple queries, single hit
|
|
663
|
-
meta = `the only hit of query ${this.props.query.number}, ` + meta;
|
|
664
|
-
}
|
|
665
|
-
else if (!this.props.showQueryCrumbs && this.props.showHitCrumbs) {
|
|
666
|
-
// Single query, multiple hits
|
|
667
|
-
meta = `hit ${this.props.hit.number}, ` + meta;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
return <div className="section-header">
|
|
671
|
-
<h4 data-toggle="collapse" data-target={this.domID() + '_content'}>
|
|
672
|
-
<i className="fa fa-chevron-down"></i>
|
|
673
|
-
<span>
|
|
674
|
-
{this.props.hit.id}
|
|
675
|
-
<small>{this.props.hit.title}</small>
|
|
676
|
-
</span>
|
|
677
|
-
</h4>
|
|
678
|
-
<span className="label label-reset pos-label">{ meta }</span>
|
|
679
|
-
</div>;
|
|
680
|
-
},
|
|
681
|
-
|
|
682
|
-
contentJSX: function () {
|
|
683
|
-
return <div id={this.domID() + '_content'} className="section-content collapse in">
|
|
684
|
-
{ this.hitLinks() }
|
|
685
|
-
<HSPOverview key={'kablammo' + this.props.query.id} query={this.props.query}
|
|
686
|
-
hit={this.props.hit} algorithm={this.props.algorithm} />
|
|
687
|
-
{ this.hspListJSX() }
|
|
688
|
-
</div>;
|
|
689
|
-
},
|
|
690
|
-
|
|
691
|
-
hitLinks: function () {
|
|
692
|
-
return (
|
|
693
|
-
<div className="hit-links">
|
|
694
|
-
<label>
|
|
695
|
-
<input type="checkbox" id={this.domID() + '_checkbox'}
|
|
696
|
-
value={this.accession()} onChange={function () {
|
|
697
|
-
this.props.selectHit(this.domID() + '_checkbox');
|
|
698
|
-
}.bind(this)} data-target={'#' + this.domID()}
|
|
699
|
-
/> Select
|
|
700
|
-
</label>
|
|
701
|
-
{
|
|
702
|
-
!this.props.imported_xml && [
|
|
703
|
-
<span> | </span>,
|
|
704
|
-
this.viewSequenceButton(),
|
|
705
|
-
this.state.showSequenceViewer && <SequenceViewer
|
|
706
|
-
url={this.viewSequenceLink()} onHide={this.hideSequenceViewer} />
|
|
707
|
-
]
|
|
708
|
-
}
|
|
709
|
-
{
|
|
710
|
-
!this.props.imported_xml && [
|
|
711
|
-
<span> | </span>,
|
|
712
|
-
<button className='btn btn-link download-fa'
|
|
713
|
-
onClick={this.downloadFASTA}>
|
|
714
|
-
<i className="fa fa-download"></i> FASTA
|
|
715
|
-
</button>
|
|
716
|
-
]
|
|
717
|
-
}
|
|
718
|
-
<span> | </span>
|
|
719
|
-
<button className='btn btn-link download-aln'
|
|
720
|
-
onClick={this.downloadAlignment}>
|
|
721
|
-
<i className="fa fa-download"></i> Alignment
|
|
722
|
-
</button>
|
|
723
|
-
{
|
|
724
|
-
_.map(this.props.hit.links, _.bind(function (link) {
|
|
725
|
-
return [<span> | </span>, this.a(link)];
|
|
726
|
-
}, this))
|
|
727
|
-
}
|
|
728
|
-
</div>
|
|
729
|
-
);
|
|
730
|
-
},
|
|
731
|
-
|
|
732
|
-
hspListJSX: function () {
|
|
733
|
-
return <div className="hsps">
|
|
734
|
-
{
|
|
735
|
-
this.props.hit.hsps.map((hsp) => {
|
|
736
|
-
return <HSP key={hsp.number}
|
|
737
|
-
algorithm={this.props.algorithm}
|
|
738
|
-
queryNumber={this.props.query.number}
|
|
739
|
-
hitNumber={this.props.hit.number} hsp={hsp}/>;
|
|
740
|
-
}, this)
|
|
741
|
-
}
|
|
742
|
-
</div>;
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* Component for sequence-viewer links.
|
|
749
|
-
*/
|
|
750
|
-
var SequenceViewer = (function () {
|
|
751
|
-
|
|
752
|
-
var Viewer = React.createClass({
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
* The CSS class name that will be assigned to the widget container. ID
|
|
756
|
-
* assigned to the widget container is derived from the same.
|
|
757
|
-
*/
|
|
758
|
-
widgetClass: 'biojs-vis-sequence',
|
|
759
|
-
|
|
760
|
-
// Lifecycle methods. //
|
|
761
|
-
|
|
762
|
-
render: function () {
|
|
763
|
-
this.widgetID =
|
|
764
|
-
this.widgetClass + '-' + (new Date().getUTCMilliseconds());
|
|
765
|
-
|
|
766
|
-
return (
|
|
767
|
-
<div
|
|
768
|
-
className="fastan">
|
|
769
|
-
<div
|
|
770
|
-
className="section-header">
|
|
771
|
-
<h4>
|
|
772
|
-
{this.props.sequence.id}
|
|
773
|
-
<small>
|
|
774
|
-
{this.props.sequence.title}
|
|
775
|
-
</small>
|
|
776
|
-
</h4>
|
|
777
|
-
</div>
|
|
778
|
-
<div
|
|
779
|
-
className="section-content">
|
|
780
|
-
<div
|
|
781
|
-
className={this.widgetClass} id={this.widgetID}>
|
|
782
|
-
</div>
|
|
783
|
-
</div>
|
|
784
|
-
</div>
|
|
785
|
-
);
|
|
786
|
-
},
|
|
787
|
-
|
|
788
|
-
componentDidMount: function () {
|
|
789
|
-
// attach BioJS sequence viewer
|
|
790
|
-
var widget = new Sequence({
|
|
791
|
-
sequence: this.props.sequence.value,
|
|
792
|
-
target: this.widgetID,
|
|
793
|
-
format: 'PRIDE',
|
|
794
|
-
columns: {
|
|
795
|
-
size: 40,
|
|
796
|
-
spacedEach: 0
|
|
797
|
-
},
|
|
798
|
-
formatOptions: {
|
|
799
|
-
title: false,
|
|
800
|
-
footer: false
|
|
801
|
-
}
|
|
802
|
-
});
|
|
803
|
-
widget.hideFormatSelector();
|
|
804
|
-
}
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
return React.createClass({
|
|
808
|
-
|
|
809
|
-
// Kind of public API. //
|
|
810
|
-
|
|
811
|
-
/**
|
|
812
|
-
* Shows sequence viewer.
|
|
813
|
-
*/
|
|
814
|
-
show: function () {
|
|
815
|
-
this.modal().modal('show');
|
|
816
|
-
},
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
// Internal helpers. //
|
|
820
|
-
|
|
821
|
-
modal: function () {
|
|
822
|
-
return $(React.findDOMNode(this.refs.modal));
|
|
823
|
-
},
|
|
824
|
-
|
|
825
|
-
resultsJSX: function () {
|
|
826
|
-
return (
|
|
827
|
-
<div className="modal-body">
|
|
828
|
-
{
|
|
829
|
-
_.map(this.state.error_msgs, _.bind(function (error_msg) {
|
|
830
|
-
return (
|
|
831
|
-
<div
|
|
832
|
-
className="fastan">
|
|
833
|
-
<div
|
|
834
|
-
className="section-header">
|
|
835
|
-
<h4>
|
|
836
|
-
{error_msg[0]}
|
|
837
|
-
</h4>
|
|
838
|
-
</div>
|
|
839
|
-
<div
|
|
840
|
-
className="section-content">
|
|
841
|
-
<pre
|
|
842
|
-
className="pre-reset">
|
|
843
|
-
{error_msg[1]}
|
|
844
|
-
</pre>
|
|
845
|
-
</div>
|
|
846
|
-
</div>
|
|
847
|
-
);
|
|
848
|
-
}, this))
|
|
849
|
-
}
|
|
850
|
-
{
|
|
851
|
-
_.map(this.state.sequences, _.bind(function (sequence) {
|
|
852
|
-
return (<Viewer sequence={sequence}/>);
|
|
853
|
-
}, this))
|
|
854
|
-
}
|
|
855
|
-
</div>
|
|
856
|
-
);
|
|
857
|
-
},
|
|
858
|
-
|
|
859
|
-
loadingJSX: function () {
|
|
860
|
-
return (
|
|
861
|
-
<div className="modal-body text-center">
|
|
862
|
-
<i className="fa fa-spinner fa-3x fa-spin"></i>
|
|
863
|
-
</div>
|
|
864
|
-
);
|
|
865
|
-
},
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
// Lifecycle methods. //
|
|
869
|
-
|
|
870
|
-
getInitialState: function () {
|
|
871
|
-
return {
|
|
872
|
-
error_msgs: [],
|
|
873
|
-
sequences: [],
|
|
874
|
-
requestCompleted: false
|
|
875
|
-
};
|
|
876
|
-
},
|
|
877
|
-
|
|
878
|
-
render: function () {
|
|
879
|
-
return (
|
|
880
|
-
<div
|
|
881
|
-
className="modal sequence-viewer"
|
|
882
|
-
ref="modal" tabIndex="-1">
|
|
883
|
-
<div
|
|
884
|
-
className="modal-dialog">
|
|
885
|
-
<div
|
|
886
|
-
className="modal-content">
|
|
887
|
-
<div
|
|
888
|
-
className="modal-header">
|
|
889
|
-
<h3>View sequence</h3>
|
|
890
|
-
</div>
|
|
891
|
-
|
|
892
|
-
{ this.state.requestCompleted &&
|
|
893
|
-
this.resultsJSX() || this.loadingJSX() }
|
|
894
|
-
</div>
|
|
895
|
-
</div>
|
|
896
|
-
</div>
|
|
897
|
-
);
|
|
898
|
-
},
|
|
899
|
-
|
|
900
|
-
componentDidMount: function () {
|
|
901
|
-
// Display modal with a spinner.
|
|
902
|
-
this.show();
|
|
903
|
-
|
|
904
|
-
// Fetch sequence and update state.
|
|
905
|
-
$.getJSON(this.props.url)
|
|
906
|
-
.done(_.bind(function (response) {
|
|
907
|
-
this.setState({
|
|
908
|
-
sequences: response.sequences,
|
|
909
|
-
error_msgs: response.error_msgs,
|
|
910
|
-
requestCompleted: true
|
|
911
|
-
});
|
|
912
|
-
}, this))
|
|
913
|
-
.fail(function (jqXHR, status, error) {
|
|
914
|
-
showErrorModal(jqXHR, function () {
|
|
915
|
-
this.hide();
|
|
916
|
-
});
|
|
917
|
-
});
|
|
918
|
-
|
|
919
|
-
this.modal().on('hidden.bs.modal', this.props.onHide);
|
|
920
|
-
},
|
|
921
|
-
});
|
|
922
|
-
})();
|
|
923
|
-
|
|
924
|
-
/**
|
|
925
|
-
* Renders links for downloading hit information in different formats.
|
|
926
|
-
* Renders links for navigating to each query.
|
|
927
|
-
*/
|
|
928
|
-
var SideBar = React.createClass({
|
|
929
|
-
|
|
930
|
-
/**
|
|
931
|
-
* Event-handler for downloading fasta of all hits.
|
|
932
|
-
*/
|
|
933
|
-
downloadFastaOfAll: function () {
|
|
934
|
-
var sequence_ids = $('.hit-links :checkbox').map(function () {
|
|
935
|
-
return this.value;
|
|
936
|
-
}).get();
|
|
937
|
-
var database_ids = _.map(this.props.data.querydb, _.iteratee('id'));
|
|
938
|
-
downloadFASTA(sequence_ids, database_ids);
|
|
939
|
-
return false;
|
|
940
|
-
},
|
|
941
|
-
|
|
942
|
-
/**
|
|
943
|
-
* Handles downloading fasta of selected hits.
|
|
944
|
-
*/
|
|
945
|
-
downloadFastaOfSelected: function () {
|
|
946
|
-
var sequence_ids = $('.hit-links :checkbox:checked').map(function () {
|
|
947
|
-
return this.value;
|
|
948
|
-
}).get();
|
|
949
|
-
var database_ids = _.map(this.props.data.querydb, _.iteratee('id'));
|
|
950
|
-
downloadFASTA(sequence_ids, database_ids);
|
|
951
|
-
return false;
|
|
952
|
-
},
|
|
953
|
-
|
|
954
|
-
downloadAlignmentOfAll: function() {
|
|
955
|
-
var sequence_ids = $('.hit-links :checkbox').map(function () {
|
|
956
|
-
return this.value;
|
|
957
|
-
}).get();
|
|
958
|
-
var hsps_arr = [];
|
|
959
|
-
var aln_exporter = new AlignmentExporter();
|
|
960
|
-
_.each(this.props.data.queries, _.bind(function (query) {
|
|
961
|
-
_.each(query.hits, function (hit) {
|
|
962
|
-
_.each(hit.hsps, function (hsp) {
|
|
963
|
-
hsp.hit_id = hit.id;
|
|
964
|
-
hsp.query_id = query.id;
|
|
965
|
-
hsps_arr.push(hsp);
|
|
966
|
-
});
|
|
967
|
-
});
|
|
968
|
-
}, this));
|
|
969
|
-
console.log('len '+hsps_arr.length);
|
|
970
|
-
aln_exporter.export_alignments(hsps_arr, 'alignment-'+sequence_ids.length+'_hits');
|
|
971
|
-
return false;
|
|
972
|
-
},
|
|
973
|
-
|
|
974
|
-
downloadAlignmentOfSelected: function () {
|
|
975
|
-
var sequence_ids = $('.hit-links :checkbox:checked').map(function () {
|
|
976
|
-
return this.value;
|
|
977
|
-
}).get();
|
|
978
|
-
var hsps_arr = [];
|
|
979
|
-
var aln_exporter = new AlignmentExporter();
|
|
980
|
-
console.log('check '+sequence_ids.toString());
|
|
981
|
-
_.each(this.props.data.queries, _.bind(function (query) {
|
|
982
|
-
_.each(query.hits, function (hit) {
|
|
983
|
-
if (_.indexOf(sequence_ids, hit.accession) != -1) {
|
|
984
|
-
_.each(hit.hsps, function (hsp) {
|
|
985
|
-
hsp.hit_id = hit.id;
|
|
986
|
-
hsp.query_id = query.id;
|
|
987
|
-
hsps_arr.push(hsp);
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
|
-
});
|
|
991
|
-
}, this));
|
|
992
|
-
aln_exporter.export_alignments(hsps_arr, 'alignment-'+sequence_ids.length+'_hits');
|
|
993
|
-
return false;
|
|
994
|
-
},
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
// JSX //
|
|
998
|
-
render: function () {
|
|
999
|
-
return (
|
|
1000
|
-
<div className="sidebar">
|
|
1001
|
-
{ this.props.shouldShowIndex && this.index() }
|
|
1002
|
-
{ this.downloads() }
|
|
1003
|
-
</div>
|
|
1004
|
-
);
|
|
1005
|
-
},
|
|
1006
|
-
|
|
1007
|
-
index: function () {
|
|
1008
|
-
return (
|
|
1009
|
-
<div className="index">
|
|
1010
|
-
<div
|
|
1011
|
-
className="section-header">
|
|
1012
|
-
<h4>
|
|
1013
|
-
{ this.summary() }
|
|
1014
|
-
</h4>
|
|
1015
|
-
</div>
|
|
1016
|
-
<ul
|
|
1017
|
-
className="nav hover-reset active-bold">
|
|
1018
|
-
{
|
|
1019
|
-
_.map(this.props.data.queries, _.bind(function (query) {
|
|
1020
|
-
return (
|
|
1021
|
-
<li key={'Side_bar_'+query.id}>
|
|
1022
|
-
<a
|
|
1023
|
-
className="nowrap-ellipsis hover-bold"
|
|
1024
|
-
href={'#Query_' + query.number}
|
|
1025
|
-
title={'Query= ' + query.id + ' ' + query.title}>
|
|
1026
|
-
{'Query= ' + query.id}
|
|
1027
|
-
</a>
|
|
1028
|
-
</li>
|
|
1029
|
-
);
|
|
1030
|
-
}, this))
|
|
1031
|
-
}
|
|
1032
|
-
</ul>
|
|
1033
|
-
</div>
|
|
1034
|
-
);
|
|
1035
|
-
},
|
|
1036
|
-
|
|
1037
|
-
summary: function () {
|
|
1038
|
-
var program = this.props.data.program;
|
|
1039
|
-
var numqueries = this.props.data.queries.length;
|
|
1040
|
-
var numquerydb = this.props.data.querydb.length;
|
|
1041
|
-
|
|
1042
|
-
return (
|
|
1043
|
-
program.toUpperCase() + ': ' +
|
|
1044
|
-
numqueries + ' ' + (numqueries > 1 ? 'queries' : 'query') + ', ' +
|
|
1045
|
-
numquerydb + ' ' + (numquerydb > 1 ? 'databases' : 'database')
|
|
1046
|
-
);
|
|
1047
|
-
},
|
|
1048
|
-
|
|
1049
|
-
downloads: function () {
|
|
1050
|
-
return (
|
|
1051
|
-
<div className="downloads">
|
|
1052
|
-
<div className="section-header">
|
|
1053
|
-
<h4>
|
|
1054
|
-
Download FASTA, XML, TSV
|
|
1055
|
-
</h4>
|
|
1056
|
-
</div>
|
|
1057
|
-
<ul className="nav">
|
|
1058
|
-
{
|
|
1059
|
-
!this.props.data.imported_xml && <li>
|
|
1060
|
-
<a href="#" className="btn-link download-fasta-of-all"
|
|
1061
|
-
onClick={this.downloadFastaOfAll}>
|
|
1062
|
-
FASTA of all hits
|
|
1063
|
-
</a>
|
|
1064
|
-
</li>
|
|
1065
|
-
}
|
|
1066
|
-
{
|
|
1067
|
-
!this.props.data.imported_xml && <li>
|
|
1068
|
-
<a href="#" className="btn-link download-fasta-of-selected disabled"
|
|
1069
|
-
onClick={this.downloadFastaOfSelected}>
|
|
1070
|
-
FASTA of <span className="text-bold"></span> selected hit(s)
|
|
1071
|
-
</a>
|
|
1072
|
-
</li>
|
|
1073
|
-
}
|
|
1074
|
-
<li>
|
|
1075
|
-
<a href="#" className="btn-link download-alignment-of-all"
|
|
1076
|
-
onClick={this.downloadAlignmentOfAll}>
|
|
1077
|
-
Alignment of all hits
|
|
1078
|
-
</a>
|
|
1079
|
-
</li>
|
|
1080
|
-
<li>
|
|
1081
|
-
<a href="#" className="btn-link download-alignment-of-selected disabled"
|
|
1082
|
-
onClick={this.downloadAlignmentOfSelected}>
|
|
1083
|
-
Alignment of <span className="text-bold"></span> selected hit(s)
|
|
1084
|
-
</a>
|
|
1085
|
-
</li>
|
|
1086
|
-
{
|
|
1087
|
-
!this.props.data.imported_xml && <li>
|
|
1088
|
-
<a className="download" data-toggle="tooltip"
|
|
1089
|
-
title="15 columns: query and subject ID; scientific
|
|
1090
|
-
name, alignment length, mismatches, gaps, identity,
|
|
1091
|
-
start and end coordinates, e value, bitscore, query
|
|
1092
|
-
coverage per subject and per HSP."
|
|
1093
|
-
href={'download/' + this.props.data.search_id + '.std_tsv'}>
|
|
1094
|
-
Standard tabular report
|
|
1095
|
-
</a>
|
|
1096
|
-
</li>
|
|
1097
|
-
}
|
|
1098
|
-
{
|
|
1099
|
-
!this.props.data.imported_xml && <li>
|
|
1100
|
-
<a className="download" data-toggle="tooltip"
|
|
1101
|
-
title="44 columns: query and subject ID, GI,
|
|
1102
|
-
accessions, and length; alignment details;
|
|
1103
|
-
taxonomy details of subject sequence(s) and
|
|
1104
|
-
query coverage per subject and per HSP."
|
|
1105
|
-
href={'download/' + this.props.data.search_id + '.full_tsv'}>
|
|
1106
|
-
Full tabular report
|
|
1107
|
-
</a>
|
|
1108
|
-
</li>
|
|
1109
|
-
}
|
|
1110
|
-
{
|
|
1111
|
-
!this.props.data.imported_xml && <li>
|
|
1112
|
-
<a className="download" data-toggle="tooltip"
|
|
1113
|
-
title="Results in XML format."
|
|
1114
|
-
href={'download/' + this.props.data.search_id + '.xml'}>
|
|
1115
|
-
Full XML report
|
|
1116
|
-
</a>
|
|
1117
|
-
</li>
|
|
1118
|
-
}
|
|
1119
|
-
</ul>
|
|
1120
|
-
</div>
|
|
1121
|
-
);
|
|
1122
|
-
},
|
|
1123
|
-
});
|
|
1124
|
-
|
|
1125
505
|
React.render(<Page/>, document.getElementById('view'));
|