sequenceserver 2.0.0.beta4 → 2.0.0.rc1
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.
Potentially problematic release.
This version of sequenceserver might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +7 -4
- data/AppImage/sequenceserver.sh +5 -0
- data/lib/sequenceserver.rb +9 -5
- data/lib/sequenceserver/blast/job.rb +7 -24
- data/lib/sequenceserver/blast/report.rb +66 -33
- 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 +115 -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 +203 -830
- data/public/js/search.js +176 -169
- 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 +5 -5
- data/spec/blast_versions/blast_2.2.31/import_spec_capybara_local_2.2.31.rb +5 -5
- data/spec/blast_versions/blast_2.3.0/import_spec_capybara_local_2.3.0.rb +5 -5
- data/spec/blast_versions/blast_2.4.0/import_spec_capybara_local_2.4.0.rb +5 -5
- data/spec/blast_versions/blast_2.5.0/import_spec_capybara_local_2.5.0.rb +5 -5
- data/spec/blast_versions/blast_2.6.0/import_spec_capybara_local_2.6.0.rb +5 -5
- data/spec/blast_versions/blast_2.7.1/import_spec_capybara_local_2.7.1.rb +5 -5
- data/spec/blast_versions/blast_2.8.1/import_spec_capybara_local_2.8.1.rb +5 -5
- data/spec/blast_versions/blast_2.9.0/import_spec_capybara_local_2.9.0.rb +5 -5
- data/spec/blast_versions/diamond_0.9.24/import_spec_capybara_local_0.9.24.rb +2 -2
- data/spec/capybara_spec.rb +1 -1
- data/views/layout.erb +1 -1
- metadata +9 -3
@@ -90,21 +90,4 @@ import 'webshim';
|
|
90
90
|
times: 4,
|
91
91
|
}, 250);
|
92
92
|
};
|
93
|
-
}(jQuery));
|
94
|
-
|
95
|
-
export default {
|
96
|
-
|
97
|
-
FASTA_FORMAT: /^>/,
|
98
|
-
|
99
|
-
setupTooltips: function () {
|
100
|
-
$('.pos-label').each(function () {
|
101
|
-
$(this).tooltip({
|
102
|
-
placement: 'right'
|
103
|
-
});
|
104
|
-
});
|
105
|
-
|
106
|
-
$('.downloads a').each(function () {
|
107
|
-
$(this).tooltip();
|
108
|
-
});
|
109
|
-
},
|
110
|
-
};
|
93
|
+
}(jQuery));
|
data/public/js/kablammo.js
CHANGED
@@ -18,352 +18,355 @@ import * as Helpers from './visualisation_helpers';
|
|
18
18
|
*/
|
19
19
|
|
20
20
|
class Graph {
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
21
|
+
static name() {
|
22
|
+
return 'Graphical overview of aligning region(s)';
|
23
|
+
}
|
24
|
+
|
25
|
+
static className() {
|
26
|
+
return 'kablammo';
|
27
|
+
}
|
28
|
+
|
29
|
+
static collapseId(props) {
|
30
|
+
return 'kablammo_'+props.query.number+'_'+props.hit.number;
|
31
|
+
}
|
32
|
+
|
33
|
+
static dataName(props) {
|
34
|
+
return 'Kablammo-'+props.query.id+'-'+props.hit.id;
|
35
|
+
}
|
36
|
+
|
37
|
+
constructor($svgContainer, props) {
|
38
|
+
this._zoom_scale_by = 1.4;
|
39
|
+
this._padding_x = 12;
|
40
|
+
this._padding_y = 50;
|
41
|
+
|
42
|
+
this._canvas_height = $svgContainer.height();
|
43
|
+
this._canvas_width = $svgContainer.width();
|
44
|
+
|
45
|
+
this._results = Helpers.get_seq_type(props.algorithm);
|
46
|
+
this._query_id = props.query.id;
|
47
|
+
this._subject_id = props.hit.id;
|
48
|
+
this._query_length = props.query.length;
|
49
|
+
this._subject_length = props.hit.length;
|
50
|
+
this._show_numbers = props.showHSPCrumbs;
|
51
|
+
// this._hsps = this.toKablammo(props.hit.hsps, props.query);
|
52
|
+
this._hsps = props.hit.hsps;
|
53
|
+
this._maxBitScore = props.query.hits[0].hsps[0].bit_score;
|
54
|
+
|
55
|
+
this.svgContainer_d3 = d3.select($svgContainer[0]);
|
56
|
+
this._svg = {};
|
57
|
+
|
58
|
+
this._svg.jq = $(this._svg.raw);
|
59
|
+
|
60
|
+
this._scales = this._create_scales();
|
61
|
+
this.use_complement_coords = false;
|
62
|
+
this._axis_ticks = 10;
|
63
|
+
|
64
|
+
this._initiate();
|
65
|
+
this.bindHoverHandler($svgContainer);
|
66
|
+
}
|
67
|
+
|
68
|
+
bindHoverHandler ($svgContainer) {
|
69
|
+
// Raise polygon on hover.
|
70
|
+
$svgContainer.find('polygon').hover(
|
71
|
+
function () {
|
72
|
+
var $g = $(this).parent();
|
73
|
+
$g.parent().append($g);
|
74
|
+
}
|
75
|
+
);
|
76
|
+
}
|
77
|
+
|
78
|
+
_initiate() {
|
79
|
+
this._svg.d3 =
|
79
80
|
this.svgContainer_d3.insert('svg', ':first-child')
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
81
|
+
.attr('height', this._canvas_height)
|
82
|
+
.attr('width', this._canvas_width);
|
83
|
+
this._svg.raw = this._svg.d3[0][0];
|
84
|
+
this._render_graph();
|
85
|
+
}
|
86
|
+
|
87
|
+
_rotate_axis_labels(text, text_anchor, dx, dy) {
|
88
|
+
text.style('text-anchor', text_anchor)
|
89
|
+
.attr('x', dx)
|
90
|
+
.attr('y', dy)
|
90
91
|
// When axis orientation is "bottom", d3 automataically applies a 0.71em
|
91
92
|
// dy offset to labels. As Inkscape does not seem to properly interpret
|
92
93
|
// such values, force them to be zero. When calling this function, then,
|
93
94
|
// you must compensate by adding 0.71em worth of offset to the dy value
|
94
95
|
// you provide.
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
}
|
141
|
-
|
142
|
-
_pan_scale(existing_scale, original_domain, delta) {
|
143
|
-
var scale = (existing_scale.domain()[1] - existing_scale.domain()[0]) / (existing_scale.range()[1] - existing_scale.range()[0]);
|
144
|
-
var scaled_delta = -delta * scale;
|
145
|
-
|
146
|
-
var domain = existing_scale.domain();
|
147
|
-
var l = domain[0] + scaled_delta;
|
148
|
-
var r = domain[1] + scaled_delta;
|
149
|
-
var new_domain = [l, r];
|
150
|
-
|
151
|
-
if(this._is_domain_within_orig(original_domain, new_domain))
|
152
|
-
existing_scale.domain(new_domain);
|
153
|
-
}
|
154
|
-
|
155
|
-
_render_polygons() {
|
156
|
-
var self = this;
|
157
|
-
|
158
|
-
// Remove all existing child elements.
|
159
|
-
this._svg.d3.selectAll('*').remove();
|
160
|
-
|
161
|
-
this._polygons = this._svg.d3.selectAll('polygon')
|
162
|
-
.data(this._hsps.slice().reverse())
|
163
|
-
.enter()
|
164
|
-
.append('g')
|
165
|
-
.attr('class','polygon')
|
166
|
-
|
167
|
-
this._polygons.append('polygon')
|
168
|
-
.attr('class', 'hit')
|
169
|
-
.attr('fill', function(hsp) {
|
170
|
-
return self.determine_colour(hsp.bit_score / self._maxBitScore);
|
171
|
-
}).attr('points', function(hsp) {
|
172
|
-
// We create query_x_points such that the 0th element will *always* be
|
173
|
-
// on the left of the 1st element, regardless of whether the axis is
|
174
|
-
// drawn normally (i.e., ltr) or reversed (i.e., rtl). We do the same
|
175
|
-
// for subject_x_points. As our parsing code guarantees start < end, we
|
176
|
-
// decide on this ordering based on the reading frame, because it
|
177
|
-
// determines whether our axis will be reversed or not.
|
178
|
-
var query_x_points = [self._scales.query.scale(hsp.qstart), self._scales.query.scale(hsp.qend)];
|
179
|
-
var subject_x_points = [self._scales.subject.scale(hsp.sstart), self._scales.subject.scale(hsp.send)];
|
180
|
-
|
181
|
-
// Axis will be rendered with 5' end on right and 3' end on left, so we
|
182
|
-
// must reverse the order of vertices for the polygon we will render to
|
183
|
-
// prevent the polygon from "crossing over" itself.
|
184
|
-
if(!self.use_complement_coords) {
|
185
|
-
if(hsp.qframe < 0)
|
186
|
-
query_x_points.reverse();
|
187
|
-
if(hsp.sframe < 0)
|
188
|
-
subject_x_points.reverse();
|
189
|
-
}
|
190
|
-
|
191
|
-
var points = [
|
192
|
-
[query_x_points[0], self._scales.query.height + 1],
|
193
|
-
[subject_x_points[0], self._scales.subject.height - 1],
|
194
|
-
[subject_x_points[1], self._scales.subject.height - 1],
|
195
|
-
[query_x_points[1], self._scales.query.height + 1],
|
196
|
-
];
|
197
|
-
|
198
|
-
return points.map(function(point) {
|
199
|
-
return point[0] + ',' + point[1];
|
200
|
-
}).join(' ');
|
201
|
-
});
|
202
|
-
|
203
|
-
this._polygons.append('text')
|
204
|
-
.attr('x', function(hsp) {
|
205
|
-
var query_x_points = [self._scales.query.scale(hsp.qstart), self._scales.query.scale(hsp.qend)];
|
206
|
-
var subject_x_points = [self._scales.subject.scale(hsp.sstart), self._scales.subject.scale(hsp.send)];
|
207
|
-
var middle1 = (query_x_points[0] + subject_x_points[0]) * 0.5;
|
208
|
-
var middle2 = (query_x_points[1] + subject_x_points[1]) * 0.5;
|
209
|
-
return (middle2 + middle1) * 0.5;
|
210
|
-
})
|
211
|
-
.attr('y', function(hsp) {
|
212
|
-
var a = self._scales.query.height;
|
213
|
-
var b = self._scales.subject.height;
|
214
|
-
var middle = ( b - a ) / 2;
|
215
|
-
return a + middle + 2; // for font-height 10px
|
216
|
-
})
|
217
|
-
.text(function(hsp) {
|
218
|
-
return Helpers.toLetters(hsp.number)
|
219
|
-
});
|
220
|
-
|
221
|
-
}
|
222
|
-
|
223
|
-
_overlaps(s1, e1, s2, e2) {
|
224
|
-
return Math.min(e1, e2) > Math.max(s1, s2);
|
225
|
-
}
|
226
|
-
|
227
|
-
_rects_overlap(rect1, rect2, padding) {
|
228
|
-
padding = padding || 0;
|
229
|
-
|
230
|
-
return this._overlaps(
|
231
|
-
rect1.left - padding,
|
232
|
-
rect1.right + padding,
|
233
|
-
rect2.left,
|
234
|
-
rect2.right
|
235
|
-
) && this._overlaps(
|
236
|
-
rect1.top - padding,
|
237
|
-
rect1.bottom + padding,
|
238
|
-
rect2.top,
|
239
|
-
rect2.bottom
|
240
|
-
);
|
241
|
-
}
|
242
|
-
|
243
|
-
_render_axes() {
|
244
|
-
var query_axis = this._create_axis(this._scales.query.scale, 'top',
|
245
|
-
this._scales.query.height, 'start', '9px', '2px',
|
246
|
-
this._results.query_seq_type);
|
247
|
-
var subject_axis = this._create_axis(this._scales.subject.scale, 'bottom',
|
248
|
-
this._scales.subject.height, 'end', '-11px', '3px',
|
249
|
-
this._results.subject_seq_type);
|
250
|
-
}
|
251
|
-
|
252
|
-
_render_graph() {
|
253
|
-
this._render_polygons();
|
254
|
-
this._render_axes();
|
255
|
-
}
|
256
|
-
|
257
|
-
_find_nearest_scale(point) {
|
258
|
-
var nearest = null;
|
259
|
-
var smallest_distance = Number.MAX_VALUE;
|
260
|
-
|
261
|
-
var self = this;
|
262
|
-
Object.keys(this._scales).forEach(function(scale_name) {
|
263
|
-
var scale = self._scales[scale_name].scale;
|
264
|
-
var scale_height = self._scales[scale_name].height;
|
265
|
-
|
266
|
-
var delta = Math.abs(scale_height - point[1]);
|
267
|
-
if(delta < smallest_distance) {
|
268
|
-
nearest = scale;
|
269
|
-
smallest_distance = delta;
|
270
|
-
}
|
271
|
-
});
|
272
|
-
|
273
|
-
return nearest;
|
274
|
-
}
|
275
|
-
|
276
|
-
_create_scales() {
|
277
|
-
var query_range = [this._padding_x, this._canvas_width - this._padding_x];
|
278
|
-
var subject_range = [this._padding_x, this._canvas_width - this._padding_x];
|
279
|
-
|
280
|
-
// If we wish to show the HSPs relative to the original (input or DB)
|
281
|
-
// sequence rather than its complement (i.e., use_complement_coords = false),
|
282
|
-
// even when the HSPs lie on the complement, then we must display the axis
|
283
|
-
// with its 5' end on the right and 3' end on the left. In this case, you can
|
284
|
-
// imagine the invisible complementary strand (with its 5' end on left and 3'
|
285
|
-
// end on right) floating above the rendered original strand, with the hits
|
286
|
-
// actually falling on the complementary strand.
|
287
|
-
//
|
288
|
-
// If we show the HSPs relative to the complementary strand (i.e.,
|
289
|
-
// use_complement_coords = true), then we *always* wish to show the axis with
|
290
|
-
// its 5' end on the left and 3' end on the right.
|
291
|
-
//
|
292
|
-
// Regardless of whether this value is true or falase, the rendered polygons
|
293
|
-
// will be precisely the same (meaning down to the pixel -- they will be
|
294
|
-
// *identical*). Only the direction of the axis, and the coordinates of
|
295
|
-
// points falling along it, change.
|
296
|
-
if(!this.use_complement_coords) {
|
297
|
-
if(this._hsps[0].qframe < 0)
|
298
|
-
query_range.reverse();
|
299
|
-
if(this._hsps[0].sframe < 0)
|
300
|
-
subject_range.reverse();
|
96
|
+
.attr('dx', 0)
|
97
|
+
.attr('dy', 0)
|
98
|
+
.attr('transform', 'rotate(-90)');
|
99
|
+
}
|
100
|
+
|
101
|
+
_create_axis(scale, orientation, height, text_anchor, dx, dy, seq_type) {
|
102
|
+
var formatter = Helpers.tick_formatter(scale, seq_type);
|
103
|
+
var tvalues = scale.ticks();
|
104
|
+
tvalues.pop();
|
105
|
+
var axis = d3.svg.axis()
|
106
|
+
.ticks(this._axis_ticks)
|
107
|
+
.scale(scale)
|
108
|
+
.tickValues(tvalues.concat(scale.domain()))
|
109
|
+
.tickFormat(formatter)
|
110
|
+
.orient(orientation);
|
111
|
+
|
112
|
+
var container = this._svg.d3.append('g')
|
113
|
+
.attr('class', 'axis')
|
114
|
+
.attr('transform', 'translate(0,' + height + ')')
|
115
|
+
.call(axis);
|
116
|
+
this._rotate_axis_labels(container.selectAll('text'), text_anchor, dx, dy);
|
117
|
+
return container;
|
118
|
+
}
|
119
|
+
|
120
|
+
_is_domain_within_orig(original_domain, new_domain) {
|
121
|
+
return original_domain[0] <= new_domain[0] && original_domain[1] >= new_domain[1];
|
122
|
+
}
|
123
|
+
|
124
|
+
_zoom_scale(scale, original_domain, zoom_from, scale_by) {
|
125
|
+
var l = scale.domain()[0];
|
126
|
+
var r = scale.domain()[1];
|
127
|
+
|
128
|
+
l = zoom_from - (zoom_from - l) / scale_by;
|
129
|
+
r = zoom_from + (r - zoom_from) / scale_by;
|
130
|
+
|
131
|
+
l = Math.round(l);
|
132
|
+
r = Math.round(r);
|
133
|
+
if(r - l < this._axis_ticks)
|
134
|
+
return;
|
135
|
+
|
136
|
+
var new_domain = [l, r];
|
137
|
+
if(this._is_domain_within_orig(original_domain, new_domain))
|
138
|
+
scale.domain(new_domain);
|
139
|
+
else
|
140
|
+
scale.domain(original_domain);
|
301
141
|
}
|
302
142
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
143
|
+
_pan_scale(existing_scale, original_domain, delta) {
|
144
|
+
var scale = (existing_scale.domain()[1] - existing_scale.domain()[0]) / (existing_scale.range()[1] - existing_scale.range()[0]);
|
145
|
+
var scaled_delta = -delta * scale;
|
146
|
+
|
147
|
+
var domain = existing_scale.domain();
|
148
|
+
var l = domain[0] + scaled_delta;
|
149
|
+
var r = domain[1] + scaled_delta;
|
150
|
+
var new_domain = [l, r];
|
151
|
+
|
152
|
+
if(this._is_domain_within_orig(original_domain, new_domain))
|
153
|
+
existing_scale.domain(new_domain);
|
154
|
+
}
|
155
|
+
|
156
|
+
_render_polygons() {
|
157
|
+
var self = this;
|
158
|
+
|
159
|
+
// Remove all existing child elements.
|
160
|
+
this._svg.d3.selectAll('*').remove();
|
161
|
+
|
162
|
+
this._polygons = this._svg.d3.selectAll('polygon')
|
163
|
+
.data(this._hsps.slice().reverse())
|
164
|
+
.enter()
|
165
|
+
.append('g')
|
166
|
+
.attr('class','polygon');
|
167
|
+
|
168
|
+
this._polygons.append('polygon')
|
169
|
+
.attr('class', 'hit')
|
170
|
+
.attr('fill', function(hsp) {
|
171
|
+
return self.determine_colour(hsp.bit_score / self._maxBitScore);
|
172
|
+
}).attr('points', function(hsp) {
|
173
|
+
// We create query_x_points such that the 0th element will *always* be
|
174
|
+
// on the left of the 1st element, regardless of whether the axis is
|
175
|
+
// drawn normally (i.e., ltr) or reversed (i.e., rtl). We do the same
|
176
|
+
// for subject_x_points. As our parsing code guarantees start < end, we
|
177
|
+
// decide on this ordering based on the reading frame, because it
|
178
|
+
// determines whether our axis will be reversed or not.
|
179
|
+
var query_x_points = [self._scales.query.scale(hsp.qstart), self._scales.query.scale(hsp.qend)];
|
180
|
+
var subject_x_points = [self._scales.subject.scale(hsp.sstart), self._scales.subject.scale(hsp.send)];
|
181
|
+
|
182
|
+
// Axis will be rendered with 5' end on right and 3' end on left, so we
|
183
|
+
// must reverse the order of vertices for the polygon we will render to
|
184
|
+
// prevent the polygon from "crossing over" itself.
|
185
|
+
if(!self.use_complement_coords) {
|
186
|
+
if(hsp.qframe < 0)
|
187
|
+
query_x_points.reverse();
|
188
|
+
if(hsp.sframe < 0)
|
189
|
+
subject_x_points.reverse();
|
190
|
+
}
|
191
|
+
|
192
|
+
var points = [
|
193
|
+
[query_x_points[0], self._scales.query.height + 1],
|
194
|
+
[subject_x_points[0], self._scales.subject.height - 1],
|
195
|
+
[subject_x_points[1], self._scales.subject.height - 1],
|
196
|
+
[query_x_points[1], self._scales.query.height + 1],
|
197
|
+
];
|
198
|
+
|
199
|
+
return points.map(function(point) {
|
200
|
+
return point[0] + ',' + point[1];
|
201
|
+
}).join(' ');
|
202
|
+
});
|
203
|
+
|
204
|
+
if (self._show_numbers) {
|
205
|
+
this._polygons.append('text')
|
206
|
+
.attr('x', function(hsp) {
|
207
|
+
var query_x_points = [self._scales.query.scale(hsp.qstart), self._scales.query.scale(hsp.qend)];
|
208
|
+
var subject_x_points = [self._scales.subject.scale(hsp.sstart), self._scales.subject.scale(hsp.send)];
|
209
|
+
var middle1 = (query_x_points[0] + subject_x_points[0]) * 0.5;
|
210
|
+
var middle2 = (query_x_points[1] + subject_x_points[1]) * 0.5;
|
211
|
+
return (middle2 + middle1) * 0.5;
|
212
|
+
})
|
213
|
+
.attr('y', function(hsp) {
|
214
|
+
var a = self._scales.query.height;
|
215
|
+
var b = self._scales.subject.height;
|
216
|
+
var middle = ( b - a ) / 2;
|
217
|
+
return a + middle + 2; // for font-height 10px
|
218
|
+
})
|
219
|
+
.text(function(hsp) {
|
220
|
+
return Helpers.toLetters(hsp.number);
|
221
|
+
});
|
222
|
+
}
|
223
|
+
|
224
|
+
}
|
225
|
+
|
226
|
+
_overlaps(s1, e1, s2, e2) {
|
227
|
+
return Math.min(e1, e2) > Math.max(s1, s2);
|
228
|
+
}
|
229
|
+
|
230
|
+
_rects_overlap(rect1, rect2, padding) {
|
231
|
+
padding = padding || 0;
|
232
|
+
|
233
|
+
return this._overlaps(
|
234
|
+
rect1.left - padding,
|
235
|
+
rect1.right + padding,
|
236
|
+
rect2.left,
|
237
|
+
rect2.right
|
238
|
+
) && this._overlaps(
|
239
|
+
rect1.top - padding,
|
240
|
+
rect1.bottom + padding,
|
241
|
+
rect2.top,
|
242
|
+
rect2.bottom
|
243
|
+
);
|
244
|
+
}
|
245
|
+
|
246
|
+
_render_axes() {
|
247
|
+
var query_axis = this._create_axis(this._scales.query.scale, 'top',
|
248
|
+
this._scales.query.height, 'start', '9px', '2px',
|
249
|
+
this._results.query_seq_type);
|
250
|
+
var subject_axis = this._create_axis(this._scales.subject.scale, 'bottom',
|
251
|
+
this._scales.subject.height, 'end', '-11px', '3px',
|
252
|
+
this._results.subject_seq_type);
|
253
|
+
}
|
254
|
+
|
255
|
+
_render_graph() {
|
256
|
+
this._render_polygons();
|
257
|
+
this._render_axes();
|
258
|
+
}
|
259
|
+
|
260
|
+
_find_nearest_scale(point) {
|
261
|
+
var nearest = null;
|
262
|
+
var smallest_distance = Number.MAX_VALUE;
|
263
|
+
|
264
|
+
var self = this;
|
265
|
+
Object.keys(this._scales).forEach(function(scale_name) {
|
266
|
+
var scale = self._scales[scale_name].scale;
|
267
|
+
var scale_height = self._scales[scale_name].height;
|
268
|
+
|
269
|
+
var delta = Math.abs(scale_height - point[1]);
|
270
|
+
if(delta < smallest_distance) {
|
271
|
+
nearest = scale;
|
272
|
+
smallest_distance = delta;
|
273
|
+
}
|
274
|
+
});
|
275
|
+
|
276
|
+
return nearest;
|
277
|
+
}
|
278
|
+
|
279
|
+
_create_scales() {
|
280
|
+
var query_range = [this._padding_x, this._canvas_width - this._padding_x];
|
281
|
+
var subject_range = [this._padding_x, this._canvas_width - this._padding_x];
|
282
|
+
|
283
|
+
// If we wish to show the HSPs relative to the original (input or DB)
|
284
|
+
// sequence rather than its complement (i.e., use_complement_coords = false),
|
285
|
+
// even when the HSPs lie on the complement, then we must display the axis
|
286
|
+
// with its 5' end on the right and 3' end on the left. In this case, you can
|
287
|
+
// imagine the invisible complementary strand (with its 5' end on left and 3'
|
288
|
+
// end on right) floating above the rendered original strand, with the hits
|
289
|
+
// actually falling on the complementary strand.
|
290
|
+
//
|
291
|
+
// If we show the HSPs relative to the complementary strand (i.e.,
|
292
|
+
// use_complement_coords = true), then we *always* wish to show the axis with
|
293
|
+
// its 5' end on the left and 3' end on the right.
|
294
|
+
//
|
295
|
+
// Regardless of whether this value is true or falase, the rendered polygons
|
296
|
+
// will be precisely the same (meaning down to the pixel -- they will be
|
297
|
+
// *identical*). Only the direction of the axis, and the coordinates of
|
298
|
+
// points falling along it, change.
|
299
|
+
if(!this.use_complement_coords) {
|
300
|
+
if(this._hsps[0].qframe < 0)
|
301
|
+
query_range.reverse();
|
302
|
+
if(this._hsps[0].sframe < 0)
|
303
|
+
subject_range.reverse();
|
304
|
+
}
|
305
|
+
|
306
|
+
var query_scale = d3.scale.linear()
|
307
|
+
.domain([1, this._query_length])
|
308
|
+
.range(query_range);
|
309
|
+
var subject_scale = d3.scale.linear()
|
310
|
+
.domain([1, this._subject_length])
|
311
|
+
.range(subject_range);
|
312
|
+
query_scale.original_domain = query_scale.domain();
|
313
|
+
subject_scale.original_domain = subject_scale.domain();
|
314
|
+
|
315
|
+
var query_height = this._padding_y;
|
316
|
+
var subject_height = this._canvas_height - this._padding_y;
|
317
|
+
|
318
|
+
var scales = {
|
319
|
+
subject: { height: subject_height, scale: subject_scale },
|
320
|
+
query: { height: query_height, scale: query_scale },
|
321
|
+
};
|
322
|
+
return scales;
|
323
|
+
}
|
324
|
+
|
325
|
+
_rgba_to_rgb(rgba, matte_rgb) {
|
326
|
+
// Algorithm taken from http://stackoverflow.com/a/2049362/1691611.
|
327
|
+
var normalize = function (colour) {
|
328
|
+
return colour.map(function (channel) { return channel / 255; });
|
329
|
+
};
|
330
|
+
|
331
|
+
var denormalize = function (colour) {
|
332
|
+
return colour.map(function (channel) { return Math.round(Math.min(255, channel * 255)); });
|
333
|
+
};
|
334
|
+
|
335
|
+
var norm = normalize(rgba.slice(0, 3));
|
336
|
+
matte_rgb = normalize(matte_rgb);
|
337
|
+
var alpha = rgba[3] / 255;
|
338
|
+
|
339
|
+
var rgb = [
|
340
|
+
(alpha * norm[0]) + (1 - alpha) * matte_rgb[0],
|
341
|
+
(alpha * norm[1]) + (1 - alpha) * matte_rgb[1],
|
342
|
+
(alpha * norm[2]) + (1 - alpha) * matte_rgb[2],
|
343
|
+
];
|
344
|
+
|
345
|
+
return denormalize(rgb);
|
346
|
+
}
|
347
|
+
|
348
|
+
/**
|
346
349
|
* Determines colour of a hsp based on normalized bit-score.
|
347
350
|
*
|
348
351
|
* Taken from grapher.js
|
349
352
|
*/
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
353
|
+
determine_colour(level) {
|
354
|
+
var graph_colour = { r: 199, g: 79, b: 20 };
|
355
|
+
var matte_colour = { r: 255, g: 255, b: 255 };
|
356
|
+
var min_opacity = 0.3;
|
357
|
+
var opacity = ((1 - min_opacity) * level) + min_opacity;
|
358
|
+
var rgb = this._rgba_to_rgb([
|
359
|
+
graph_colour.r,
|
360
|
+
graph_colour.g,
|
361
|
+
graph_colour.b,
|
362
|
+
255 * opacity
|
363
|
+
], [
|
364
|
+
matte_colour.r,
|
365
|
+
matte_colour.g,
|
366
|
+
matte_colour.b,
|
367
|
+
]);
|
368
|
+
return 'rgb(' + rgb.join(',') + ')';
|
369
|
+
}
|
367
370
|
}
|
368
371
|
|
369
372
|
var Kablammo = Grapher(Graph);
|