sequenceserver 2.0.0.beta4 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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);