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.

Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -4
  3. data/AppImage/sequenceserver.sh +5 -0
  4. data/lib/sequenceserver.rb +9 -5
  5. data/lib/sequenceserver/blast/job.rb +7 -24
  6. data/lib/sequenceserver/blast/report.rb +66 -33
  7. data/lib/sequenceserver/routes.rb +28 -2
  8. data/lib/sequenceserver/version.rb +1 -1
  9. data/public/SequenceServer_logo.png +0 -0
  10. data/public/css/grapher.css +8 -15
  11. data/public/css/sequenceserver.css +115 -55
  12. data/public/css/sequenceserver.min.css +3 -3
  13. data/public/js/circos.js +1 -1
  14. data/public/js/download_fasta.js +17 -0
  15. data/public/js/grapher.js +7 -9
  16. data/public/js/hit.js +217 -0
  17. data/public/js/hits_overview.js +12 -13
  18. data/public/js/hsp.js +104 -84
  19. data/public/js/{sequenceserver.js → jquery_world.js} +1 -18
  20. data/public/js/kablammo.js +337 -334
  21. data/public/js/length_distribution.js +1 -1
  22. data/public/js/query.js +147 -0
  23. data/public/js/report.js +203 -830
  24. data/public/js/search.js +176 -169
  25. data/public/js/sequence_modal.js +167 -0
  26. data/public/js/sidebar.js +210 -0
  27. data/public/js/utils.js +2 -19
  28. data/public/js/visualisation_helpers.js +2 -2
  29. data/public/sequenceserver-report.min.js +19 -19
  30. data/public/sequenceserver-search.min.js +11 -11
  31. data/public/vendor/github/twbs/bootstrap@3.3.5/js/bootstrap.js +2 -2
  32. data/spec/blast_versions/blast_2.2.30/import_spec_capybara_local_2.2.30.rb +5 -5
  33. data/spec/blast_versions/blast_2.2.31/import_spec_capybara_local_2.2.31.rb +5 -5
  34. data/spec/blast_versions/blast_2.3.0/import_spec_capybara_local_2.3.0.rb +5 -5
  35. data/spec/blast_versions/blast_2.4.0/import_spec_capybara_local_2.4.0.rb +5 -5
  36. data/spec/blast_versions/blast_2.5.0/import_spec_capybara_local_2.5.0.rb +5 -5
  37. data/spec/blast_versions/blast_2.6.0/import_spec_capybara_local_2.6.0.rb +5 -5
  38. data/spec/blast_versions/blast_2.7.1/import_spec_capybara_local_2.7.1.rb +5 -5
  39. data/spec/blast_versions/blast_2.8.1/import_spec_capybara_local_2.8.1.rb +5 -5
  40. data/spec/blast_versions/blast_2.9.0/import_spec_capybara_local_2.9.0.rb +5 -5
  41. data/spec/blast_versions/diamond_0.9.24/import_spec_capybara_local_0.9.24.rb +2 -2
  42. data/spec/capybara_spec.rb +1 -1
  43. data/views/layout.erb +1 -1
  44. 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));
@@ -18,352 +18,355 @@ import * as Helpers from './visualisation_helpers';
18
18
  */
19
19
 
20
20
  class Graph {
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._hsps = this.toKablammo(props.hit.hsps, props.query);
51
- this._hsps = props.hit.hsps;
52
- this._maxBitScore = props.query.hits[0].hsps[0].bit_score;
53
-
54
- this.svgContainer_d3 = d3.select($svgContainer[0]);
55
- this._svg = {};
56
-
57
- this._svg.jq = $(this._svg.raw);
58
-
59
- this._scales = this._create_scales();
60
- this.use_complement_coords = false;
61
- this._axis_ticks = 10;
62
-
63
- this._initiate();
64
- this.bindHoverHandler($svgContainer);
65
- }
66
-
67
- bindHoverHandler ($svgContainer) {
68
- // Raise polygon on hover.
69
- $svgContainer.find('polygon').hover(
70
- function () {
71
- var $g = $(this).parent();
72
- $g.parent().append($g);
73
- }
74
- );
75
- }
76
-
77
- _initiate() {
78
- this._svg.d3 =
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
- .attr('height', this._canvas_height)
81
- .attr('width', this._canvas_width);
82
- this._svg.raw = this._svg.d3[0][0];
83
- this._render_graph();
84
- }
85
-
86
- _rotate_axis_labels(text, text_anchor, dx, dy) {
87
- text.style('text-anchor', text_anchor)
88
- .attr('x', dx)
89
- .attr('y', dy)
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
- .attr('dx', 0)
96
- .attr('dy', 0)
97
- .attr('transform', 'rotate(-90)');
98
- }
99
-
100
- _create_axis(scale, orientation, height, text_anchor, dx, dy, seq_type) {
101
- var formatter = Helpers.tick_formatter(scale, seq_type);
102
- var tvalues = scale.ticks();
103
- tvalues.pop();
104
- var axis = d3.svg.axis()
105
- .ticks(this._axis_ticks)
106
- .scale(scale)
107
- .tickValues(tvalues.concat(scale.domain()))
108
- .tickFormat(formatter)
109
- .orient(orientation);
110
-
111
- var container = this._svg.d3.append('g')
112
- .attr('class', 'axis')
113
- .attr('transform', 'translate(0,' + height + ')')
114
- .call(axis);
115
- this._rotate_axis_labels(container.selectAll('text'), text_anchor, dx, dy);
116
- return container;
117
- }
118
-
119
- _is_domain_within_orig(original_domain, new_domain) {
120
- return original_domain[0] <= new_domain[0] && original_domain[1] >= new_domain[1];
121
- }
122
-
123
- _zoom_scale(scale, original_domain, zoom_from, scale_by) {
124
- var l = scale.domain()[0];
125
- var r = scale.domain()[1];
126
-
127
- l = zoom_from - (zoom_from - l) / scale_by;
128
- r = zoom_from + (r - zoom_from) / scale_by;
129
-
130
- l = Math.round(l);
131
- r = Math.round(r);
132
- if(r - l < this._axis_ticks)
133
- return;
134
-
135
- var new_domain = [l, r];
136
- if(this._is_domain_within_orig(original_domain, new_domain))
137
- scale.domain(new_domain);
138
- else
139
- scale.domain(original_domain);
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
- var query_scale = d3.scale.linear()
304
- .domain([1, this._query_length])
305
- .range(query_range);
306
- var subject_scale = d3.scale.linear()
307
- .domain([1, this._subject_length])
308
- .range(subject_range);
309
- query_scale.original_domain = query_scale.domain();
310
- subject_scale.original_domain = subject_scale.domain();
311
-
312
- var query_height = this._padding_y;
313
- var subject_height = this._canvas_height - this._padding_y;
314
-
315
- var scales = {
316
- subject: { height: subject_height, scale: subject_scale },
317
- query: { height: query_height, scale: query_scale },
318
- };
319
- return scales;
320
- }
321
-
322
- _rgba_to_rgb(rgba, matte_rgb) {
323
- // Algorithm taken from http://stackoverflow.com/a/2049362/1691611.
324
- var normalize = function (colour) {
325
- return colour.map(function (channel) { return channel / 255; });
326
- };
327
-
328
- var denormalize = function (colour) {
329
- return colour.map(function (channel) { return Math.round(Math.min(255, channel * 255)); });;
330
- };
331
-
332
- var norm = normalize(rgba.slice(0, 3));
333
- matte_rgb = normalize(matte_rgb);
334
- var alpha = rgba[3] / 255;
335
-
336
- var rgb = [
337
- (alpha * norm[0]) + (1 - alpha) * matte_rgb[0],
338
- (alpha * norm[1]) + (1 - alpha) * matte_rgb[1],
339
- (alpha * norm[2]) + (1 - alpha) * matte_rgb[2],
340
- ];
341
-
342
- return denormalize(rgb);
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
- determine_colour(level) {
351
- var graph_colour = { r: 30, g: 139, b: 195 };
352
- var matte_colour = { r: 255, g: 255, b: 255 };
353
- var min_opacity = 0.3;
354
- var opacity = ((1 - min_opacity) * level) + min_opacity;
355
- var rgb = this._rgba_to_rgb([
356
- graph_colour.r,
357
- graph_colour.g,
358
- graph_colour.b,
359
- 255 * opacity
360
- ], [
361
- matte_colour.r,
362
- matte_colour.g,
363
- matte_colour.b,
364
- ]);
365
- return 'rgb(' + rgb.join(',') + ')';
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);