sequenceserver 1.1.0.beta8 → 1.1.0.beta10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/sequenceserver/blast/hsp.rb +2 -174
- data/lib/sequenceserver/blast/report.rb +4 -3
- data/lib/sequenceserver/job.rb +2 -1
- data/lib/sequenceserver/version.rb +1 -1
- data/public/css/grapher.css +50 -35
- data/public/css/sequenceserver.css +25 -16
- data/public/css/sequenceserver.min.css +3 -3
- data/public/js/circos.js +1 -1
- data/public/js/grapher.js +8 -3
- data/public/js/hits_overview.js +4 -6
- data/public/js/hsp.js +283 -0
- data/public/js/kablammo.js +3 -3
- data/public/js/report.js +668 -850
- data/public/js/search.js +208 -180
- data/public/js/utils.js +77 -0
- data/public/sequenceserver-report.min.js +14 -14
- data/public/sequenceserver-search.min.js +2 -2
- metadata +4 -2
data/public/js/search.js
CHANGED
@@ -7,6 +7,23 @@ import React from 'react';
|
|
7
7
|
*/
|
8
8
|
$.webshims.polyfill('forms');
|
9
9
|
|
10
|
+
var Page = React.createClass({
|
11
|
+
render: function () {
|
12
|
+
return (
|
13
|
+
<div>
|
14
|
+
<DnD ref="dnd"/>
|
15
|
+
<Form ref="form"/>
|
16
|
+
</div>
|
17
|
+
);
|
18
|
+
},
|
19
|
+
|
20
|
+
componentDidMount: function () {
|
21
|
+
this.refs.dnd.setState({
|
22
|
+
query: this.refs.form.refs.query
|
23
|
+
})
|
24
|
+
}
|
25
|
+
});
|
26
|
+
|
10
27
|
/** Drag n drop widget.
|
11
28
|
*/
|
12
29
|
var DnD = React.createClass({
|
@@ -171,6 +188,135 @@ var DnD = React.createClass({
|
|
171
188
|
}
|
172
189
|
});
|
173
190
|
|
191
|
+
/**
|
192
|
+
* Search form.
|
193
|
+
*
|
194
|
+
* Top level component that initialises and holds all other components, and
|
195
|
+
* facilitates communication between them.
|
196
|
+
*/
|
197
|
+
var Form = React.createClass({
|
198
|
+
|
199
|
+
getInitialState: function () {
|
200
|
+
return {
|
201
|
+
databases: {},
|
202
|
+
preDefinedOpts: {
|
203
|
+
'blastn': ['-task blastn', '-evalue 1e-5'],
|
204
|
+
'blastp': ['-evalue 1e-5'],
|
205
|
+
'blastx': ['-evalue 1e-5'],
|
206
|
+
'tblastx': ['-evalue 1e-5'],
|
207
|
+
'tblastn': ['-evalue 1e-5']
|
208
|
+
}
|
209
|
+
};
|
210
|
+
},
|
211
|
+
|
212
|
+
componentDidMount: function () {
|
213
|
+
$.getJSON("searchdata.json", _.bind(function(data) {
|
214
|
+
this.setState({
|
215
|
+
databases: data["database"], preDefinedOpts: $.extend({},
|
216
|
+
this.state.preDefinedOpts, data["options"])
|
217
|
+
});
|
218
|
+
}, this));
|
219
|
+
|
220
|
+
$(document).bind("keydown", _.bind(function (e) {
|
221
|
+
if (e.ctrlKey && e.keyCode === 13 &&
|
222
|
+
!$('#method').is(':disabled')) {
|
223
|
+
$(this.getDOMNode()).trigger('submit');
|
224
|
+
}
|
225
|
+
}, this));
|
226
|
+
},
|
227
|
+
|
228
|
+
determineBlastMethod: function () {
|
229
|
+
var database_type = this.databaseType;
|
230
|
+
var sequence_type = this.sequenceType;
|
231
|
+
|
232
|
+
if (this.refs.query.isEmpty()) {
|
233
|
+
return [];
|
234
|
+
}
|
235
|
+
|
236
|
+
//database type is always known
|
237
|
+
switch (database_type) {
|
238
|
+
case 'protein':
|
239
|
+
switch (sequence_type) {
|
240
|
+
case undefined:
|
241
|
+
return ['blastp', 'blastx'];
|
242
|
+
case 'protein':
|
243
|
+
return ['blastp'];
|
244
|
+
case 'nucleotide':
|
245
|
+
return ['blastx'];
|
246
|
+
}
|
247
|
+
break;
|
248
|
+
case 'nucleotide':
|
249
|
+
switch (sequence_type) {
|
250
|
+
case undefined:
|
251
|
+
return ['tblastn', 'blastn', 'tblastx'];
|
252
|
+
case 'protein':
|
253
|
+
return ['tblastn'];
|
254
|
+
case 'nucleotide':
|
255
|
+
return ['blastn', 'tblastx'];
|
256
|
+
}
|
257
|
+
break;
|
258
|
+
}
|
259
|
+
|
260
|
+
return [];
|
261
|
+
},
|
262
|
+
|
263
|
+
handleSequenceTypeChanged: function (type) {
|
264
|
+
this.sequenceType = type;
|
265
|
+
this.refs.button.setState({
|
266
|
+
hasQuery: !this.refs.query.isEmpty(),
|
267
|
+
hasDatabases: !!this.databaseType,
|
268
|
+
methods: this.determineBlastMethod()
|
269
|
+
});
|
270
|
+
},
|
271
|
+
|
272
|
+
handleDatabaseTypeChanaged: function (type) {
|
273
|
+
this.databaseType = type;
|
274
|
+
this.refs.button.setState({
|
275
|
+
hasQuery: !this.refs.query.isEmpty(),
|
276
|
+
hasDatabases: !!this.databaseType,
|
277
|
+
methods: this.determineBlastMethod()
|
278
|
+
});
|
279
|
+
},
|
280
|
+
|
281
|
+
handleAlgoChanged: function (algo) {
|
282
|
+
if (this.state.preDefinedOpts.hasOwnProperty(algo)) {
|
283
|
+
this.refs.opts.setState({
|
284
|
+
preOpts: this.state.preDefinedOpts[algo].join(" ")
|
285
|
+
});
|
286
|
+
}
|
287
|
+
else {
|
288
|
+
this.refs.opts.setState({preOpts: ""});
|
289
|
+
}
|
290
|
+
},
|
291
|
+
|
292
|
+
render: function () {
|
293
|
+
return (
|
294
|
+
<div
|
295
|
+
className="container">
|
296
|
+
<form
|
297
|
+
id="blast" method="post" className="form-horizontal">
|
298
|
+
<div
|
299
|
+
className="form-group query-container">
|
300
|
+
<Query ref="query" onSequenceTypeChanged={this.handleSequenceTypeChanged}/>
|
301
|
+
</div>
|
302
|
+
<div
|
303
|
+
className="notifications" id="notifications">
|
304
|
+
<NucleotideNotification/>
|
305
|
+
<ProteinNotification/>
|
306
|
+
<MixedNotification/>
|
307
|
+
</div>
|
308
|
+
<Databases ref="databases" onDatabaseTypeChanged={this.handleDatabaseTypeChanaged} databases={this.state.databases}/>
|
309
|
+
<div
|
310
|
+
className="form-group">
|
311
|
+
<Options ref="opts"/>
|
312
|
+
<SearchButton ref="button" onAlgoChanged={this.handleAlgoChanged}/>
|
313
|
+
</div>
|
314
|
+
</form>
|
315
|
+
</div>
|
316
|
+
);
|
317
|
+
}
|
318
|
+
});
|
319
|
+
|
174
320
|
/**
|
175
321
|
* Query widget.
|
176
322
|
*/
|
@@ -487,42 +633,70 @@ var Databases = React.createClass({
|
|
487
633
|
},
|
488
634
|
|
489
635
|
handleClick: function (database) {
|
490
|
-
var type = this.nselected() ? database.type : ''
|
636
|
+
var type = this.nselected() ? database.type : '';
|
491
637
|
this.setState({type: type});
|
492
638
|
},
|
493
639
|
|
640
|
+
handleToggle: function (toggleState, type) {
|
641
|
+
switch (toggleState) {
|
642
|
+
case '[Select all]':
|
643
|
+
$(`.${type} .database input:not(:checked)`).click();
|
644
|
+
break;
|
645
|
+
case '[Deselect all]':
|
646
|
+
$(`.${type} .database input:checked`).click();
|
647
|
+
break;
|
648
|
+
}
|
649
|
+
},
|
650
|
+
|
494
651
|
render: function () {
|
495
652
|
return (
|
496
|
-
<div
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
653
|
+
<div className="form-group databases-container">
|
654
|
+
{ _.map(this.categories(), this.renderDatabases) }
|
655
|
+
</div>
|
656
|
+
);
|
657
|
+
},
|
658
|
+
|
659
|
+
renderDatabases: function (category) {
|
660
|
+
// Panel name and column width.
|
661
|
+
var panelTitle = category[0].toUpperCase() +
|
662
|
+
category.substring(1).toLowerCase() + " databases";
|
663
|
+
var columnClass = this.categories().length === 1 ? 'col-md-12' :
|
664
|
+
'col-md-6';
|
665
|
+
|
666
|
+
// Toggle button.
|
667
|
+
var toggleState = '[Select all]';
|
668
|
+
var toggleClass = 'btn btn-link';
|
669
|
+
var toggleShown = this.databases(category).length > 1 ;
|
670
|
+
var toggleDisabled = this.state.type && this.state.type !== category;
|
671
|
+
if (toggleShown && toggleDisabled) toggleClass += ' disabled';
|
672
|
+
if (!toggleShown) toggleClass += ' hidden';
|
673
|
+
if (this.nselected() === this.databases(category).length) {
|
674
|
+
toggleState = '[Deselect all]';
|
675
|
+
}
|
676
|
+
|
677
|
+
// JSX.
|
678
|
+
return (
|
679
|
+
<div className={columnClass}>
|
680
|
+
<div className="panel panel-default">
|
681
|
+
<div className="panel-heading">
|
682
|
+
<h4 style={{display: "inline"}}>{panelTitle}</h4>
|
683
|
+
<button type="button" className={toggleClass} disabled={toggleDisabled}
|
684
|
+
onClick={ function () { this.handleToggle(toggleState, category) }.bind(this) }>
|
685
|
+
{toggleState}
|
686
|
+
</button>
|
687
|
+
</div>
|
688
|
+
<ul className={"list-group databases " + category}>
|
689
|
+
{
|
690
|
+
_.map(this.databases(category), _.bind(function (database) {
|
691
|
+
return (
|
692
|
+
<li className="list-group-item">
|
693
|
+
{ this.renderDatabase(database) }
|
694
|
+
</li>
|
695
|
+
);
|
696
|
+
}, this))
|
697
|
+
}
|
698
|
+
</ul>
|
699
|
+
</div>
|
526
700
|
</div>
|
527
701
|
);
|
528
702
|
},
|
@@ -547,9 +721,9 @@ var Databases = React.createClass({
|
|
547
721
|
);
|
548
722
|
},
|
549
723
|
|
550
|
-
shouldComponentUpdate: function (props, state) {
|
551
|
-
return !(state.type && state.type === this.state.type);
|
552
|
-
},
|
724
|
+
//shouldComponentUpdate: function (props, state) {
|
725
|
+
//return !(state.type && state.type === this.state.type);
|
726
|
+
//},
|
553
727
|
|
554
728
|
componentDidUpdate: function () {
|
555
729
|
if (this.databases() && this.databases().length === 1) {
|
@@ -788,150 +962,4 @@ var SearchButton = React.createClass({
|
|
788
962
|
}
|
789
963
|
});
|
790
964
|
|
791
|
-
/**
|
792
|
-
* Search form.
|
793
|
-
*
|
794
|
-
* Top level component that initialises and holds all other components, and
|
795
|
-
* facilitates communication between them.
|
796
|
-
*/
|
797
|
-
var Form = React.createClass({
|
798
|
-
|
799
|
-
getInitialState: function () {
|
800
|
-
return {
|
801
|
-
databases: {},
|
802
|
-
preDefinedOpts: {
|
803
|
-
'blastn': ['-task blastn', '-evalue 1e-5'],
|
804
|
-
'blastp': ['-evalue 1e-5'],
|
805
|
-
'blastx': ['-evalue 1e-5'],
|
806
|
-
'tblastx': ['-evalue 1e-5'],
|
807
|
-
'tblastn': ['-evalue 1e-5']
|
808
|
-
}
|
809
|
-
};
|
810
|
-
},
|
811
|
-
|
812
|
-
componentDidMount: function () {
|
813
|
-
$.getJSON("searchdata.json", _.bind(function(data) {
|
814
|
-
this.setState({
|
815
|
-
databases: data["database"], preDefinedOpts: $.extend({},
|
816
|
-
this.state.preDefinedOpts, data["options"])
|
817
|
-
});
|
818
|
-
}, this));
|
819
|
-
|
820
|
-
$(document).bind("keydown", _.bind(function (e) {
|
821
|
-
if (e.ctrlKey && e.keyCode === 13 &&
|
822
|
-
!$('#method').is(':disabled')) {
|
823
|
-
$(this.getDOMNode()).trigger('submit');
|
824
|
-
}
|
825
|
-
}, this));
|
826
|
-
},
|
827
|
-
|
828
|
-
determineBlastMethod: function () {
|
829
|
-
var database_type = this.databaseType;
|
830
|
-
var sequence_type = this.sequenceType;
|
831
|
-
|
832
|
-
if (this.refs.query.isEmpty()) {
|
833
|
-
return [];
|
834
|
-
}
|
835
|
-
|
836
|
-
//database type is always known
|
837
|
-
switch (database_type) {
|
838
|
-
case 'protein':
|
839
|
-
switch (sequence_type) {
|
840
|
-
case undefined:
|
841
|
-
return ['blastp', 'blastx'];
|
842
|
-
case 'protein':
|
843
|
-
return ['blastp'];
|
844
|
-
case 'nucleotide':
|
845
|
-
return ['blastx'];
|
846
|
-
}
|
847
|
-
break;
|
848
|
-
case 'nucleotide':
|
849
|
-
switch (sequence_type) {
|
850
|
-
case undefined:
|
851
|
-
return ['tblastn', 'blastn', 'tblastx'];
|
852
|
-
case 'protein':
|
853
|
-
return ['tblastn'];
|
854
|
-
case 'nucleotide':
|
855
|
-
return ['blastn', 'tblastx'];
|
856
|
-
}
|
857
|
-
break;
|
858
|
-
}
|
859
|
-
|
860
|
-
return [];
|
861
|
-
},
|
862
|
-
|
863
|
-
handleSequenceTypeChanged: function (type) {
|
864
|
-
this.sequenceType = type;
|
865
|
-
this.refs.button.setState({
|
866
|
-
hasQuery: !this.refs.query.isEmpty(),
|
867
|
-
hasDatabases: !!this.databaseType,
|
868
|
-
methods: this.determineBlastMethod()
|
869
|
-
});
|
870
|
-
},
|
871
|
-
|
872
|
-
handleDatabaseTypeChanaged: function (type) {
|
873
|
-
this.databaseType = type;
|
874
|
-
this.refs.button.setState({
|
875
|
-
hasQuery: !this.refs.query.isEmpty(),
|
876
|
-
hasDatabases: !!this.databaseType,
|
877
|
-
methods: this.determineBlastMethod()
|
878
|
-
});
|
879
|
-
},
|
880
|
-
|
881
|
-
handleAlgoChanged: function (algo) {
|
882
|
-
if (this.state.preDefinedOpts.hasOwnProperty(algo)) {
|
883
|
-
this.refs.opts.setState({
|
884
|
-
preOpts: this.state.preDefinedOpts[algo].join(" ")
|
885
|
-
});
|
886
|
-
}
|
887
|
-
else {
|
888
|
-
this.refs.opts.setState({preOpts: ""});
|
889
|
-
}
|
890
|
-
},
|
891
|
-
|
892
|
-
render: function () {
|
893
|
-
return (
|
894
|
-
<div
|
895
|
-
className="container">
|
896
|
-
<form
|
897
|
-
id="blast" method="post" className="form-horizontal">
|
898
|
-
<div
|
899
|
-
className="form-group query-container">
|
900
|
-
<Query ref="query" onSequenceTypeChanged={this.handleSequenceTypeChanged}/>
|
901
|
-
</div>
|
902
|
-
<div
|
903
|
-
className="notifications" id="notifications">
|
904
|
-
<NucleotideNotification/>
|
905
|
-
<ProteinNotification/>
|
906
|
-
<MixedNotification/>
|
907
|
-
</div>
|
908
|
-
<Databases ref="databases" onDatabaseTypeChanged={this.handleDatabaseTypeChanaged} databases={this.state.databases}/>
|
909
|
-
<div
|
910
|
-
className="form-group">
|
911
|
-
<Options ref="opts"/>
|
912
|
-
<SearchButton ref="button" onAlgoChanged={this.handleAlgoChanged}/>
|
913
|
-
</div>
|
914
|
-
</form>
|
915
|
-
</div>
|
916
|
-
);
|
917
|
-
}
|
918
|
-
});
|
919
|
-
|
920
|
-
var Page = React.createClass({
|
921
|
-
render: function () {
|
922
|
-
return (
|
923
|
-
<div>
|
924
|
-
<DnD ref="dnd"/>
|
925
|
-
<Form ref="form"/>
|
926
|
-
</div>
|
927
|
-
);
|
928
|
-
},
|
929
|
-
|
930
|
-
componentDidMount: function () {
|
931
|
-
this.refs.dnd.setState({
|
932
|
-
query: this.refs.form.refs.query
|
933
|
-
})
|
934
|
-
}
|
935
|
-
});
|
936
|
-
|
937
965
|
React.render(<Page/>, document.getElementById('view'));
|
data/public/js/utils.js
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
|
3
|
+
var Utils = {
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Render URL for sequence-viewer.
|
7
|
+
*/
|
8
|
+
a: function (link) {
|
9
|
+
if (link.title && link.url)
|
10
|
+
{
|
11
|
+
return (
|
12
|
+
<a href={link.url} className={link.class} target='_blank'>
|
13
|
+
{link.icon && <i className={"fa " + link.icon}></i>}
|
14
|
+
{" " + link.title + " "}
|
15
|
+
</a>
|
16
|
+
);
|
17
|
+
}
|
18
|
+
},
|
19
|
+
|
20
|
+
|
21
|
+
/***********************************
|
22
|
+
* Formatters for hits & hsp table *
|
23
|
+
***********************************/
|
24
|
+
|
25
|
+
// Formats an array of two elements as "first (last)".
|
26
|
+
format_2_tuple: function (tuple) {
|
27
|
+
return (tuple[0] + " (" + tuple[tuple.length - 1] + ")");
|
28
|
+
},
|
29
|
+
|
30
|
+
/**
|
31
|
+
* Returns fraction as percentage
|
32
|
+
*/
|
33
|
+
inPercentage: function (num , den) {
|
34
|
+
return `${(num * 100.0 / den).toFixed(2)}%`;
|
35
|
+
},
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Returns fractional representation as String.
|
39
|
+
*/
|
40
|
+
inFraction: function (num , den) {
|
41
|
+
return num + "/" + den;
|
42
|
+
},
|
43
|
+
|
44
|
+
/**
|
45
|
+
* Returns given Float as String formatted to two decimal places.
|
46
|
+
*/
|
47
|
+
inTwoDecimal: function (num) {
|
48
|
+
return num.toFixed(2)
|
49
|
+
},
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Returns zero if num is zero. Returns two decimal representation of num
|
53
|
+
* if num is between [1..10). Returns num in scientific notation otherwise.
|
54
|
+
*/
|
55
|
+
inExponential: function (num) {
|
56
|
+
// Nothing to do if num is 0.
|
57
|
+
if (num === 0) {
|
58
|
+
return 0
|
59
|
+
}
|
60
|
+
|
61
|
+
// Round to two decimal places if in the rane [1..10).
|
62
|
+
if (num >= 1 && num < 10)
|
63
|
+
{
|
64
|
+
return this.inTwoDecimal(num)
|
65
|
+
}
|
66
|
+
|
67
|
+
// Return numbers in the range [0..1) and [10..Inf] in
|
68
|
+
// scientific format.
|
69
|
+
var exp = num.toExponential(2);
|
70
|
+
var parts = exp.split("e");
|
71
|
+
var base = parts[0];
|
72
|
+
var power = parts[1];
|
73
|
+
return <span>{base} × 10<sup>{power}</sup></span>;
|
74
|
+
}
|
75
|
+
};
|
76
|
+
|
77
|
+
export default Utils;
|