bio-graphics 1.0 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/doc/classes/Bio/Graphics.html +11 -1
  2. data/doc/classes/Bio/Graphics/Panel.html +5 -2
  3. data/doc/classes/Bio/Graphics/Panel.src/M000005.html +4 -4
  4. data/doc/classes/Bio/Graphics/Panel.src/M000006.html +3 -3
  5. data/doc/classes/Bio/Graphics/Panel.src/M000007.html +6 -5
  6. data/doc/classes/Bio/Graphics/Panel/Ruler.html +16 -16
  7. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000014.html +6 -14
  8. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000015.html +10 -41
  9. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000016.html +59 -0
  10. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000017.html +20 -0
  11. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000018.html +28 -0
  12. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000019.html +59 -0
  13. data/doc/classes/Bio/Graphics/Panel/Track.html +30 -15
  14. data/doc/classes/Bio/Graphics/Panel/Track.src/M000008.html +8 -5
  15. data/doc/classes/Bio/Graphics/Panel/Track.src/M000009.html +2 -2
  16. data/doc/classes/Bio/Graphics/Panel/Track.src/M000010.html +12 -245
  17. data/doc/classes/Bio/Graphics/Panel/Track/Feature.html +109 -0
  18. data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000011.html +1 -1
  19. data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000012.html +39 -0
  20. data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000013.html +27 -0
  21. data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000014.html +209 -49
  22. data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000015.html +48 -0
  23. data/doc/classes/Bio/Graphics/Panel/Track/Feature/PixelRange.html +5 -5
  24. data/doc/classes/Bio/Graphics/Panel/Track/Feature/PixelRange.src/M000013.html +18 -0
  25. data/doc/classes/Bio/Graphics/Panel/Track/Feature/PixelRange.src/M000016.html +18 -0
  26. data/doc/created.rid +1 -1
  27. data/doc/files/README_DEV.html +7 -11
  28. data/doc/files/TUTORIAL.html +3 -3
  29. data/doc/files/lib/bio-graphics_rb.html +3 -1
  30. data/doc/files/lib/bio/graphics/feature_rb.html +6 -11
  31. data/doc/files/lib/bio/graphics/image_map_rb.html +1 -1
  32. data/doc/files/lib/bio/graphics/panel_rb.html +1 -1
  33. data/doc/files/lib/bio/graphics/ruler_rb.html +1 -1
  34. data/doc/files/lib/bio/graphics/track_rb.html +1 -1
  35. data/doc/fr_method_index.html +8 -4
  36. data/doc/images/example_labels.png +0 -0
  37. data/doc/images/glyph_showcase.png +0 -0
  38. data/doc/images/terms.png +0 -0
  39. data/doc/index.html +2 -2
  40. data/images/example_labels.png +0 -0
  41. data/images/glyph_showcase.png +0 -0
  42. data/images/terms.png +0 -0
  43. data/images/terms.svg +146 -132
  44. data/lib/bio-graphics.rb +2 -1
  45. data/lib/bio/graphics/feature.rb +304 -0
  46. data/lib/bio/graphics/panel.rb +15 -11
  47. data/lib/bio/graphics/ruler.rb +1 -1
  48. data/lib/bio/graphics/track.rb +23 -254
  49. data/samples/arkdb_features.rb +8 -8
  50. data/samples/glyph_showcase.rb +10 -6
  51. data/test/unit/test_creation.rb +51 -0
  52. metadata +14 -2
data/lib/bio-graphics.rb CHANGED
@@ -8,7 +8,8 @@
8
8
  begin
9
9
  require 'bio'
10
10
  require 'cairo'
11
- rescue nil
11
+ require 'pango'
12
+ require 'stringio'
12
13
  end
13
14
 
14
15
  require File.dirname(__FILE__) + '/bio/graphics/image_map.rb'
@@ -1,3 +1,4 @@
1
+ require 'yaml'
1
2
  #
2
3
  # = bio/graphics/feature.rb - feature class
3
4
  #
@@ -123,6 +124,309 @@ module Bio
123
124
  # Are there subfeatures out of view at the right side of the picture?
124
125
  attr_accessor :hidden_subfeatures_at_stop
125
126
 
127
+ # Method to draw the arrows of directed glyphs. Not to be used
128
+ # directly, but called by Feature#draw.
129
+ def arrow(track,direction,x,y,size)
130
+ case direction
131
+ when :right
132
+ track.move_to(x,y)
133
+ track.rel_line_to(size,size)
134
+ track.rel_line_to(-size,size)
135
+ track.close_path.fill
136
+ when :left
137
+ track.move_to(x,y)
138
+ track.rel_line_to(-size,size)
139
+ track.rel_line_to(size,size)
140
+ track.close_path.fill
141
+ when :north
142
+ track.move_to(x-size,y+size)
143
+ track.rel_line_to(size,-size)
144
+ track.rel_line_to(size,size)
145
+ track.close_path.fill
146
+ when :south
147
+ track.move_to(x-size,y-size)
148
+ track.rel_line_to(size,size)
149
+ track.rel_line_to(size,-size)
150
+ track.close_path.fill
151
+ end
152
+ end
153
+
154
+ # Method to draw the connections (introns) of spliced glyphs. Not to
155
+ # be used directly, but called by Feature#draw.
156
+ def connector(track,from,to,top, color)
157
+ line_width = track.line_width
158
+ track.set_source_rgb([0,0,0])
159
+ track.set_line_width(0.5)
160
+ middle = from + ((to - from)/2)
161
+ track.move_to(from, top+2)
162
+ track.line_to(middle, top+7)
163
+ track.line_to(to, top+2)
164
+ track.stroke
165
+ track.set_line_width(line_width)
166
+ track.set_source_rgb(color)
167
+ end
168
+
169
+ # Adds the feature to the track cairo context. This method should not
170
+ # be used directly by the user, but is called by
171
+ # Bio::Graphics::Panel::Track.draw
172
+ # ---
173
+ # *Arguments*:
174
+ # * _trackdrawing_ (required) :: the track cairo object
175
+ # * _row_ (required) :: row within the track that this feature has
176
+ # been bumped to
177
+ # *Returns*:: FIXME: I don't know
178
+ def draw(track_drawing)
179
+ # We have to check if we want to change the glyph type from directed to
180
+ # undirected
181
+ # There are 2 cases where we don't want to draw arrows on
182
+ # features:
183
+ # (a) when the picture is really zoomed out, features are
184
+ # so small that the arrow itself is too big
185
+ # (b) if a directed feature on the fw strand extends beyond
186
+ # the end of the picture, the arrow is out of view. This
187
+ # is the same as considering the feature as undirected.
188
+ # The same obviously goes for features on the reverse
189
+ # strand that extend beyond the left side of the image.
190
+ #
191
+ # (a) Zoomed out
192
+ replace_directed_with_undirected = false
193
+ if (self.stop - self.start).to_f/self.track.panel.rescale_factor.to_f < 2
194
+ replace_directed_with_undirected = true
195
+ end
196
+ # (b) Extending beyond borders picture
197
+ if ( self.chopped_at_stop and self.strand = 1 ) or ( self.chopped_at_start and self.strand = -1 )
198
+ replace_directed_with_undirected = true
199
+ end
200
+
201
+ local_feature_glyph = nil
202
+ if self.track.glyph == :directed_generic and replace_directed_with_undirected
203
+ local_feature_glyph = :generic
204
+ elsif self.track.glyph == :directed_spliced and replace_directed_with_undirected
205
+ local_feature_glyph = :spliced
206
+ else
207
+ local_feature_glyph = self.track.glyph
208
+ end
209
+
210
+ # And draw the thing.
211
+ row = self.find_row
212
+ top_pixel_of_feature = FEATURE_V_DISTANCE + (FEATURE_HEIGHT+FEATURE_V_DISTANCE)*row
213
+ bottom_pixel_of_feature = top_pixel_of_feature + FEATURE_HEIGHT
214
+
215
+ case local_feature_glyph
216
+ # triangles are typical for features which have a 1 bp position (start == stop)
217
+ when :triangle
218
+ raise "Start and stop are not the same (necessary if you want triangle glyphs)" if self.start != self.stop
219
+
220
+ # Need to get this for the imagemap
221
+ left_pixel_of_feature = self.pixel_range_collection[0].start_pixel - FEATURE_ARROW_LENGTH
222
+ right_pixel_of_feature = self.pixel_range_collection[0].stop_pixel + FEATURE_ARROW_LENGTH
223
+ arrow(track_drawing,:north,left_pixel_of_feature + FEATURE_ARROW_LENGTH, top_pixel_of_feature, FEATURE_ARROW_LENGTH)
224
+ track_drawing.close_path.stroke
225
+ when :line
226
+ left_pixel_of_feature = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[0].start_pixel
227
+ right_pixel_of_feature = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[-1].stop_pixel
228
+ track_drawing.move_to(left_pixel_of_feature,top_pixel_of_feature+FEATURE_ARROW_LENGTH)
229
+ track_drawing.line_to(right_pixel_of_feature,top_pixel_of_feature+FEATURE_ARROW_LENGTH)
230
+ track_drawing.stroke
231
+
232
+ track_drawing.set_source_rgb([0,0,0])
233
+ arrow(track_drawing,:right,left_pixel_of_feature,top_pixel_of_feature,FEATURE_ARROW_LENGTH)
234
+ track_drawing.close_path.stroke
235
+ arrow(track_drawing,:left,right_pixel_of_feature,top_pixel_of_feature,FEATURE_ARROW_LENGTH)
236
+ track_drawing.close_path.stroke
237
+
238
+ track_drawing.set_source_rgb(self.track.colour)
239
+ when :directed_generic
240
+ # Need to get this for the imagemap
241
+ left_pixel_of_feature = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[0].start_pixel
242
+ right_pixel_of_feature = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[-1].stop_pixel
243
+ if self.strand == -1 # Reverse strand
244
+ track_drawing.rectangle(left_pixel_of_feature+FEATURE_ARROW_LENGTH, top_pixel_of_feature, right_pixel_of_feature - left_pixel_of_feature - FEATURE_ARROW_LENGTH, FEATURE_HEIGHT).fill
245
+ arrow(track_drawing,:left,left_pixel_of_feature+FEATURE_ARROW_LENGTH,top_pixel_of_feature,FEATURE_ARROW_LENGTH)
246
+ track_drawing.close_path.fill
247
+ else #default is forward strand
248
+ track_drawing.rectangle(left_pixel_of_feature, top_pixel_of_feature, right_pixel_of_feature - left_pixel_of_feature - FEATURE_ARROW_LENGTH, FEATURE_HEIGHT).fill
249
+ arrow(track_drawing,:right,right_pixel_of_feature-FEATURE_ARROW_LENGTH,top_pixel_of_feature,FEATURE_ARROW_LENGTH)
250
+ track_drawing.close_path.fill
251
+ end
252
+ when :spliced
253
+ gap_starts = Array.new
254
+ gap_stops = Array.new
255
+
256
+ # Need to get this for the imagemap
257
+ left_pixel_of_feature = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[0].start_pixel
258
+ right_pixel_of_feature = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[-1].stop_pixel
259
+
260
+ # First draw the parts
261
+ self.pixel_range_collection.sort_by{|pr| pr.start_pixel}.each do |pr|
262
+ track_drawing.rectangle(pr.start_pixel, top_pixel_of_feature, (pr.stop_pixel - pr.start_pixel), FEATURE_HEIGHT).fill
263
+ gap_starts.push(pr.stop_pixel)
264
+ gap_stops.push(pr.start_pixel)
265
+ end
266
+
267
+ # And then draw the connections in the gaps
268
+ # Start with removing the very first start and the very last stop.
269
+ gap_starts.sort!.pop
270
+ gap_stops.sort!.shift
271
+
272
+ gap_starts.length.times do |gap_number|
273
+ connector(track_drawing,gap_starts[gap_number].to_f,gap_stops[gap_number].to_f,top_pixel_of_feature,track.colour)
274
+ end
275
+
276
+ if self.hidden_subfeatures_at_stop
277
+ from = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[-1].stop_pixel
278
+ to = self.track.panel.width
279
+ track_drawing.move_to(from, top_pixel_of_feature+5)
280
+ track_drawing.line_to(to, top_pixel_of_feature+5)
281
+ track_drawing.stroke
282
+ end
283
+
284
+ if self.hidden_subfeatures_at_start
285
+ from = 1
286
+ to = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[0].start_pixel
287
+ track_drawing.move_to(from, top_pixel_of_feature+5)
288
+ track_drawing.line_to(to, top_pixel_of_feature+5)
289
+ track_drawing.stroke
290
+ end
291
+
292
+ when :directed_spliced
293
+ gap_starts = Array.new
294
+ gap_stops = Array.new
295
+ # First draw the parts
296
+ locations = self.location.sort_by{|l| l.from}
297
+
298
+ # Need to get this for the imagemap
299
+ left_pixel_of_feature = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[0].start_pixel
300
+ right_pixel_of_feature = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[-1].stop_pixel
301
+
302
+ # Start with the one with the arrow
303
+ pixel_ranges = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}
304
+ range_with_arrow = nil
305
+ if self.strand == -1 # reverse strand => box with arrow is first one
306
+ range_with_arrow = pixel_ranges.shift
307
+ track_drawing.rectangle((range_with_arrow.start_pixel)+FEATURE_ARROW_LENGTH, top_pixel_of_feature, range_with_arrow.stop_pixel - range_with_arrow.start_pixel - FEATURE_ARROW_LENGTH, FEATURE_HEIGHT).fill
308
+ arrow(track_drawing,:left,range_with_arrow.start_pixel+FEATURE_ARROW_LENGTH, top_pixel_of_feature,FEATURE_ARROW_LENGTH)
309
+ track_drawing.close_path.fill
310
+ else # forward strand => box with arrow is last one
311
+ range_with_arrow = pixel_ranges.pop
312
+ track_drawing.rectangle(range_with_arrow.start_pixel, top_pixel_of_feature, range_with_arrow.stop_pixel - range_with_arrow.start_pixel - FEATURE_ARROW_LENGTH, FEATURE_HEIGHT).fill
313
+ arrow(track_drawing,:right,range_with_arrow.stop_pixel-FEATURE_ARROW_LENGTH, top_pixel_of_feature,FEATURE_ARROW_LENGTH)
314
+ track_drawing.close_path.fill
315
+ end
316
+ gap_starts.push(range_with_arrow.stop_pixel)
317
+ gap_stops.push(range_with_arrow.start_pixel)
318
+
319
+ # And then add the others
320
+ pixel_ranges.each do |range|
321
+ track_drawing.rectangle(range.start_pixel, top_pixel_of_feature, range.stop_pixel - range.start_pixel, FEATURE_HEIGHT).fill
322
+ gap_starts.push(range.stop_pixel)
323
+ gap_stops.push(range.start_pixel)
324
+ end
325
+
326
+ # And then draw the connections in the gaps
327
+ # Start with removing the very first start and the very last stop.
328
+ gap_starts.sort!.pop
329
+ gap_stops.sort!.shift
330
+
331
+ gap_starts.length.times do |gap_number|
332
+ connector(track_drawing,gap_starts[gap_number].to_f,gap_stops[gap_number].to_f,top_pixel_of_feature,track.colour)
333
+ end
334
+
335
+ if self.hidden_subfeatures_at_stop
336
+ from = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[-1].stop_pixel
337
+ to = self.track.panel.width
338
+ track_drawing.move_to(from, top_pixel_of_feature+FEATURE_ARROW_LENGTH)
339
+ track_drawing.line_to(to, top_pixel_of_feature+FEATURE_ARROW_LENGTH)
340
+ track_drawing.stroke
341
+ end
342
+
343
+ if self.hidden_subfeatures_at_start
344
+ from = 1
345
+ to = self.pixel_range_collection.sort_by{|pr| pr.start_pixel}[0].start_pixel
346
+ track_drawing.move_to(from, top_pixel_of_feature+FEATURE_ARROW_LENGTH)
347
+ track_drawing.line_to(to, top_pixel_of_feature+FEATURE_ARROW_LENGTH)
348
+ track_drawing.stroke
349
+ end
350
+
351
+ else #treat as 'generic'
352
+ left_pixel_of_feature, right_pixel_of_feature = self.pixel_range_collection[0].start_pixel, self.pixel_range_collection[0].stop_pixel
353
+ track_drawing.rectangle(left_pixel_of_feature, top_pixel_of_feature, (right_pixel_of_feature - left_pixel_of_feature), FEATURE_HEIGHT).fill
354
+ end
355
+
356
+ # Add the label for the feature
357
+ if self.track.show_label
358
+ pango_layout = track_drawing.create_pango_layout
359
+ pango_layout.text = self.name
360
+ fdesc = Pango::FontDescription.new('Sans Serif')
361
+ fdesc.set_size(8 * Pango::SCALE)
362
+ pango_layout.font_description = fdesc
363
+
364
+ text_range = self.start.floor..(self.start.floor + pango_layout.pixel_size[0]*self.track.panel.rescale_factor)
365
+ if self.track.grid[row+1].nil?
366
+ self.track.grid[row+1] = Array.new
367
+ end
368
+ self.track.grid[row].push(text_range)
369
+ self.track.grid[row+1].push(text_range)
370
+ track_drawing.move_to(left_pixel_of_feature, top_pixel_of_feature + TRACK_HEADER_HEIGHT)
371
+ track_drawing.set_source_rgb(0,0,0)
372
+ track_drawing.show_pango_layout(pango_layout)
373
+ track_drawing.set_source_rgb(self.track.colour)
374
+ end
375
+
376
+
377
+ # And add the region to the image map
378
+ if self.track.panel.clickable
379
+ # Comment: we have to add the vertical_offset and TRACK_HEADER_HEIGHT!
380
+ self.track.panel.image_map.elements.push(ImageMap::ImageMapElement.new(left_pixel_of_feature,
381
+ top_pixel_of_feature + self.track.vertical_offset + TRACK_HEADER_HEIGHT,
382
+ right_pixel_of_feature,
383
+ bottom_pixel_of_feature + self.track.vertical_offset + TRACK_HEADER_HEIGHT,
384
+ self.link
385
+ ))
386
+ end
387
+ end
388
+
389
+ # Calculates the row within the track where this feature should be
390
+ # drawn. This method should not
391
+ # be used directly by the user, but is called by
392
+ # Bio::Graphics::Panel::Track::Feature.draw
393
+ # ---
394
+ # *Arguments*:: none
395
+ # *Returns*:: row number
396
+ def find_row
397
+ row_found = false
398
+
399
+ # We've got to find out what row to draw the feature on. If two
400
+ # features overlap, one of them has to be 'bumped' down. So we'll
401
+ # first try to draw a new feature at the top of the track. If
402
+ # it however would overlap with another one, we'll bump it down
403
+ # to the next row.
404
+ feature_range = (self.start.floor..self.stop.ceil)
405
+ row = 1
406
+ row_available = true
407
+ until row_found
408
+ if ! self.track.grid[row].nil?
409
+ self.track.grid[row].each do |covered|
410
+ if feature_range.include?(covered.first) or covered.include?(feature_range.first)
411
+ row_available = false
412
+ end
413
+ end
414
+ end
415
+
416
+ if ! row_available
417
+ row += 1
418
+ row_available = true
419
+ else # We've found the place where to draw the feature.
420
+ if self.track.grid[row].nil?
421
+ self.track.grid[row] = Array.new
422
+ end
423
+ self.track.grid[row].push(feature_range)
424
+ row_found = true
425
+ end
426
+ end
427
+ return row
428
+ end
429
+
126
430
  class PixelRange
127
431
  def initialize(start_pixel, stop_pixel)
128
432
  @start_pixel, @stop_pixel = start_pixel, stop_pixel
@@ -43,7 +43,7 @@ module Bio
43
43
  # track1.add_feature('gene3',100,500)
44
44
  #
45
45
  # # Add a second track (e.g. 'polymorphisms')
46
- # track2 = g.add_track('polymorphisms','red','triangle')
46
+ # track2 = g.add_track('polymorphisms',, false, 'red','triangle')
47
47
  #
48
48
  # # And put features on this one
49
49
  # track2.add_feature('polymorphism 1',56,56)
@@ -69,10 +69,12 @@ module Bio
69
69
  RULER_MIN_DISTANCE_TICKS_PIXEL = 5 # There should be at least 5 pixels between
70
70
  # consecutive ticks. This is used for the
71
71
  # calculation of tick distance.
72
+ FONT = ['Georgia', 1, 1]
72
73
 
73
74
  # The Bio::Graphics::Panel class describes the complete graph and contains
74
75
  # all tracks. See Bio::Graphics documentation for explanation of interplay
75
76
  # between different classes.
77
+
76
78
  class Panel
77
79
  # Create a new Bio::Graphics::Panel object
78
80
  #
@@ -102,17 +104,17 @@ module Bio
102
104
  @length = length.to_i
103
105
  @width = width.to_i
104
106
  @tracks = Array.new
105
- @number_of_times_bumped = 0
107
+ @number_of_feature_rows = 0
106
108
  @clickable = clickable
107
109
  @image_map = ( clickable ) ? ImageMap.new : nil
108
- @display_start = ( display_start.nil? ) ? 0 : display_start
109
- @display_stop = ( display_stop.nil? ) ? @length : display_stop
110
+ @display_start = ( display_start.nil? or display_start < 0 ) ? 0 : display_start
111
+ @display_stop = ( display_stop.nil? or display_stop > @length ) ? @length : display_stop
110
112
  if @display_stop <= @display_start
111
113
  raise "[ERROR] Start coordinate to be displayed has to be smaller than stop coordinate."
112
114
  end
113
115
  @rescale_factor = (@display_stop - @display_start).to_f / @width
114
116
  end
115
- attr_accessor :length, :width, :height, :rescale_factor, :tracks, :number_of_times_bumped, :clickable, :image_map, :display_start, :display_stop
117
+ attr_accessor :length, :width, :height, :rescale_factor, :tracks, :number_of_feature_rows, :clickable, :image_map, :display_start, :display_stop
116
118
 
117
119
  # Adds a Bio::Graphics::Track container to this panel. A panel contains a
118
120
  # logical grouping of features, e.g. (for sequence annotation:) genes,
@@ -124,6 +126,7 @@ module Bio
124
126
  # ---
125
127
  # *Arguments*:
126
128
  # * _name_ (required) :: Name of the track to be displayed (e.g. 'genes')
129
+ # * _label) :: Whether the feature labels should be displayed or not
127
130
  # * _colour_ :: Colour to be used to draw the features within the track.
128
131
  # Default = 'blue'
129
132
  # * _glyph_ :: Glyph to use for drawing the features. Options are:
@@ -133,8 +136,8 @@ module Bio
133
136
  # If you try to draw a feature that is longer with triangles, an error
134
137
  # will be shown.
135
138
  # *Returns*:: Bio::Graphics::Track object that has just been created
136
- def add_track(name, feature_colour = [0,0,1], feature_glyph = 'generic')
137
- @tracks.push(Bio::Graphics::Panel::Track.new(self, name, feature_colour, feature_glyph))
139
+ def add_track(name, label = true, feature_colour = [0,0,1], feature_glyph = 'generic')
140
+ @tracks.push(Bio::Graphics::Panel::Track.new(self, name, label, feature_colour, feature_glyph))
138
141
  return @tracks[-1]
139
142
  end
140
143
 
@@ -164,14 +167,15 @@ module Bio
164
167
 
165
168
  # Add tracks
166
169
  @tracks.each do |track|
167
- track.draw(huge_panel_drawing, vertical_offset)
168
- @number_of_times_bumped += track.number_of_times_bumped
169
- vertical_offset += ( track.number_of_times_bumped*(FEATURE_HEIGHT+FEATURE_V_DISTANCE+5)) + 10 # '10' is for the header
170
+ track.vertical_offset = vertical_offset
171
+ track.draw(huge_panel_drawing)
172
+ @number_of_feature_rows += track.number_of_feature_rows
173
+ vertical_offset += ( track.number_of_feature_rows*(FEATURE_HEIGHT+FEATURE_V_DISTANCE+5)) + 10 # '10' is for the header
170
174
  end
171
175
 
172
176
  # And create a smaller version of the panel
173
177
  height = ruler.height
174
- @number_of_times_bumped.times do
178
+ @number_of_feature_rows.times do
175
179
  height += 20
176
180
  end
177
181
  @tracks.length.times do #To correct for the track headers
@@ -76,7 +76,7 @@ module Bio
76
76
  ruler_drawing.rel_line_to(0, 15)
77
77
 
78
78
  # Draw tick number
79
- ruler_drawing.select_font_face('Georgia', 1, 1)
79
+ ruler_drawing.select_font_face(*(FONT))
80
80
  ruler_drawing.set_font_size(RULER_TEXT_HEIGHT)
81
81
  ruler_drawing.move_to(tick_pixel_position.floor, 20 + RULER_TEXT_HEIGHT)
82
82
  ruler_drawing.show_text(tick.to_i.to_s)
@@ -26,24 +26,28 @@ module Bio
26
26
  # * _panel_ (required) :: Bio::Graphics::Panel object that this track
27
27
  # belongs to
28
28
  # * _name_ (required) :: Name of the track to be displayed (e.g. 'genes')
29
+ # * _label_ :: Boolean: should the label for each feature be drawn or not
29
30
  # * _colour_ :: Colour to be used to draw the features within the track.
30
31
  # Default = 'blue'
31
32
  # * _glyph_ :: Glyph to use for drawing the features. Options are:
32
- # 'generic', 'directed_generic', 'spliced, 'directed_spliced' and
33
- # 'triangle'. Triangles can be used
33
+ # :generic, :directed_generic, :spliced, :directed_spliced, :line and
34
+ # :triangle. Triangles can be used
34
35
  # for features whose start and stop positions are the same (e.g. SNPs).
35
36
  # If you try to draw a feature that is longer with triangles, an error
36
37
  # will be shown.
37
38
  # *Returns*:: Bio::Graphics::Track object
38
- def initialize(panel, name, feature_colour = [0,0,1], feature_glyph = 'generic')
39
+ def initialize(panel, name, label = true, colour = [0,0,1], glyph = :generic)
39
40
  @panel = panel
40
41
  @name = name
41
- @feature_colour = feature_colour
42
- @feature_glyph = feature_glyph
42
+ @show_label = label
43
+ @colour = colour
44
+ @glyph = glyph
43
45
  @features = Array.new
44
- @number_of_times_bumped = 0
46
+ @number_of_feature_rows = 0
47
+ @vertical_offset = 0
48
+ @grid = Hash.new
45
49
  end
46
- attr_accessor :panel, :name, :feature_colour, :feature_glyph, :features, :number_of_times_bumped, :height
50
+ attr_accessor :panel, :name, :show_label, :colour, :glyph, :features, :number_of_feature_rows, :height, :vertical_offset, :grid
47
51
 
48
52
  # Adds a Bio::Graphics::Panel::Track::Feature to this track. A track contains
49
53
  # features of the same type, e.g. (for sequence annotation:) genes,
@@ -91,7 +95,7 @@ module Bio
91
95
  # looking at, don't bother storing the stuff. I think this makes huge
92
96
  # speed and memory differences if you've got a chromosome with
93
97
  # thousands of features.
94
- if stop <= panel.display_start or start >= panel.display_stop
98
+ if stop <= self.panel.display_start or start >= self.panel.display_stop
95
99
  return nil
96
100
  else #elsif start >= panel.display_start and stop <= panel.display_stop
97
101
  @features.push(Bio::Graphics::Panel::Track::Feature.new(self, name, location_object, link))
@@ -107,276 +111,41 @@ module Bio
107
111
  # ---
108
112
  # *Arguments*:
109
113
  # * _paneldrawing_ (required) :: the panel cairo object
110
- # * _verticaloffset_ (required) :: number of pixels to offset the track downwards,
111
- # based on the height of other tracks that were drawn above it
112
114
  # *Returns*:: FIXME: I don't know
113
- def draw(panel_drawing, vertical_offset)
115
+ def draw(panel_drawing)
114
116
  track_drawing = Cairo::Context.new(panel_drawing)
115
117
 
116
118
  # Draw thin line above title
117
119
  track_drawing.set_source_rgb(0.75,0.75,0.75)
118
- track_drawing.move_to(0, vertical_offset)
119
- track_drawing.line_to(panel.width, vertical_offset)
120
+ track_drawing.move_to(0, self.vertical_offset)
121
+ track_drawing.line_to(self.panel.width, self.vertical_offset)
120
122
  track_drawing.stroke
121
123
 
122
124
  # Draw track title
123
125
  track_drawing.set_source_rgb(0,0,0)
124
- track_drawing.select_font_face('Georgia',1,1)
126
+ # track_drawing.select_font_face('Georgia',1,1)
127
+ track_drawing.select_font_face(*(FONT))
125
128
  track_drawing.set_font_size(TRACK_HEADER_HEIGHT)
126
- track_drawing.move_to(0,TRACK_HEADER_HEIGHT + vertical_offset + 10)
129
+ track_drawing.move_to(0,TRACK_HEADER_HEIGHT + self.vertical_offset + 10)
127
130
  track_drawing.show_text(self.name)
128
131
 
129
132
  # Draw the features
130
- grid = Hash.new
131
-
132
133
  track_drawing.save do
133
- track_drawing.translate(0, vertical_offset + TRACK_HEADER_HEIGHT)
134
- track_drawing.set_source_rgb(@feature_colour)
134
+ track_drawing.translate(0, self.vertical_offset + TRACK_HEADER_HEIGHT)
135
+ track_drawing.set_source_rgb(@colour)
135
136
 
136
- # Now draw the features
137
- # These are the basic steps:
138
- # A. find out what row to draw it on
139
- # B. see if we want to change the glyph type from directed to
140
- # undirected
141
- # C. draw the thing
142
- @features.each do |feature|
137
+ @features.sort_by{|f| f.start}.each do |feature|
143
138
  # Don't even bother if the feature is not in the view
144
139
  if feature.stop <= self.panel.display_start or feature.start >= self.panel.display_stop
145
140
  next
146
141
  else
147
- feature_drawn = false
148
-
149
- # A. find out what row to draw it on
150
- feature_range = (feature.start.floor..feature.stop.ceil)
151
- row = 1
152
- row_available = true
153
- until feature_drawn
154
- if ! grid[row].nil?
155
- grid[row].each do |covered|
156
- if feature_range.include?(covered.first) or covered.include?(feature_range.first)
157
- row_available = false
158
- end
159
- end
160
- end
161
-
162
- if ! row_available
163
- row += 1
164
- row_available = true
165
- else
166
- if grid[row].nil?
167
- grid[row] = Array.new
168
- end
169
- grid[row].push(feature_range)
170
-
171
- # B. see if we want to change the glyph type from directed to
172
- # undirected
173
- # There are 2 cases where we don't want to draw arrows on
174
- # features:
175
- # (a) when the picture is really zoomed out, features are
176
- # so small that the arrow itself is too big
177
- # (b) if a directed feature on the fw strand extends beyond
178
- # the end of the picture, the arrow is out of view. This
179
- # is the same as considering the feature as undirected.
180
- # The same obviously goes for features on the reverse
181
- # strand that extend beyond the left side of the image.
182
- #
183
- # (a) Zoomed out
184
- replace_directed_with_undirected = false
185
- if (feature.stop - feature.start).to_f/panel.rescale_factor.to_f < 2
186
- replace_directed_with_undirected = true
187
- end
188
- # (b) Extending beyond borders picture
189
- if ( feature.chopped_at_stop and feature.strand = 1 ) or ( feature.chopped_at_start and feature.strand = -1 )
190
- replace_directed_with_undirected = true
191
- end
192
-
193
- local_feature_glyph = nil
194
- if feature_glyph == 'directed_generic' and replace_directed_with_undirected
195
- local_feature_glyph = 'generic'
196
- elsif feature_glyph == 'directed_spliced' and replace_directed_with_undirected
197
- local_feature_glyph = 'spliced'
198
- else
199
- local_feature_glyph = feature_glyph
200
- end
201
-
202
- # C. And draw the thing.
203
- top_pixel_of_feature = FEATURE_V_DISTANCE + (FEATURE_HEIGHT+FEATURE_V_DISTANCE)*row
204
- bottom_pixel_of_feature = top_pixel_of_feature + FEATURE_HEIGHT
205
-
206
- case local_feature_glyph
207
- # triangles are typical for features which have a 1 bp position (start == stop)
208
- when 'triangle'
209
- raise "Start and stop are not the same (necessary if you want triangle glyphs)" if feature.start != feature.stop
210
-
211
- # Need to get this for the imagemap
212
- left_pixel_of_feature = feature.pixel_range_collection[0].start_pixel - 3
213
- right_pixel_of_feature = feature.pixel_range_collection[0].stop_pixel + 3
214
- track_drawing.move_to(left_pixel_of_feature + 3, top_pixel_of_feature)
215
- track_drawing.rel_line_to(-3, FEATURE_HEIGHT)
216
- track_drawing.rel_line_to(6, 0)
217
- track_drawing.close_path.fill
218
-
219
- when 'directed_generic'
220
- # Need to get this for the imagemap
221
- left_pixel_of_feature = feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}[0].start_pixel
222
- right_pixel_of_feature = feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}[-1].stop_pixel
223
- if feature.strand == -1 # Reverse strand
224
- # Draw main box
225
- track_drawing.rectangle(left_pixel_of_feature+FEATURE_ARROW_LENGTH, top_pixel_of_feature, right_pixel_of_feature - left_pixel_of_feature - FEATURE_ARROW_LENGTH, FEATURE_HEIGHT).fill
226
-
227
- # Draw arrow
228
- track_drawing.move_to(left_pixel_of_feature+FEATURE_ARROW_LENGTH, top_pixel_of_feature)
229
- track_drawing.rel_line_to(-FEATURE_ARROW_LENGTH, FEATURE_HEIGHT/2)
230
- track_drawing.rel_line_to(FEATURE_ARROW_LENGTH, FEATURE_HEIGHT/2)
231
- track_drawing.close_path.fill
232
-
233
- else #default is forward strand
234
- track_drawing.rectangle(left_pixel_of_feature, top_pixel_of_feature, right_pixel_of_feature - left_pixel_of_feature - FEATURE_ARROW_LENGTH, FEATURE_HEIGHT).fill
235
- track_drawing.move_to(right_pixel_of_feature - FEATURE_ARROW_LENGTH, top_pixel_of_feature)
236
- track_drawing.rel_line_to(FEATURE_ARROW_LENGTH, FEATURE_HEIGHT/2)
237
- track_drawing.rel_line_to(-FEATURE_ARROW_LENGTH, FEATURE_HEIGHT/2)
238
- track_drawing.close_path.fill
239
- end
240
- when 'spliced'
241
- gap_starts = Array.new
242
- gap_stops = Array.new
243
-
244
- # Need to get this for the imagemap
245
- left_pixel_of_feature = feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}[0].start_pixel
246
- right_pixel_of_feature = feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}[-1].stop_pixel
247
-
248
- # First draw the parts
249
- feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}.each do |pr|
250
- track_drawing.rectangle(pr.start_pixel, top_pixel_of_feature, (pr.stop_pixel - pr.start_pixel), FEATURE_HEIGHT).fill
251
- gap_starts.push(pr.stop_pixel)
252
- gap_stops.push(pr.start_pixel)
253
- end
254
-
255
- # And then draw the connections in the gaps
256
- # Start with removing the very first start and the very last stop.
257
- gap_starts.sort!.pop
258
- gap_stops.sort!.shift
259
-
260
- gap_starts.length.times do |gap_number|
261
- from = gap_starts[gap_number].to_f
262
- to = gap_stops[gap_number].to_f
263
- middle = from + ((to - from)/2)
264
- track_drawing.move_to(from, top_pixel_of_feature+2)
265
- track_drawing.line_to(middle, top_pixel_of_feature+7)
266
- track_drawing.line_to(to, top_pixel_of_feature+2)
267
- track_drawing.stroke
268
- end
269
-
270
- if feature.hidden_subfeatures_at_stop
271
- from = feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}[-1].stop_pixel
272
- to = panel.width
273
- track_drawing.move_to(from, top_pixel_of_feature+5)
274
- track_drawing.line_to(to, top_pixel_of_feature+5)
275
- track_drawing.stroke
276
- end
277
-
278
- if feature.hidden_subfeatures_at_start
279
- from = 1
280
- to = feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}[0].start_pixel
281
- track_drawing.move_to(from, top_pixel_of_feature+5)
282
- track_drawing.line_to(to, top_pixel_of_feature+5)
283
- track_drawing.stroke
284
- end
285
-
286
- when 'directed_spliced'
287
- gap_starts = Array.new
288
- gap_stops = Array.new
289
- # First draw the parts
290
- locations = feature.location.sort_by{|l| l.from}
291
-
292
- # Need to get this for the imagemap
293
- left_pixel_of_feature = feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}[0].start_pixel
294
- right_pixel_of_feature = feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}[-1].stop_pixel
295
-
296
- # Start with the one with the arrow
297
- pixel_ranges = feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}
298
- range_with_arrow = nil
299
- if feature.strand == -1 # reverse strand => box with arrow is first one
300
- range_with_arrow = pixel_ranges.shift
301
- track_drawing.rectangle((range_with_arrow.start_pixel)+FEATURE_ARROW_LENGTH, top_pixel_of_feature, range_with_arrow.stop_pixel - range_with_arrow.start_pixel, FEATURE_HEIGHT).fill
302
- track_drawing.move_to(range_with_arrow.start_pixel+FEATURE_ARROW_LENGTH, top_pixel_of_feature)
303
- track_drawing.rel_line_to(-FEATURE_ARROW_LENGTH, FEATURE_HEIGHT/2)
304
- track_drawing.rel_line_to(FEATURE_ARROW_LENGTH, FEATURE_HEIGHT/2)
305
- track_drawing.close_path.fill
306
- else # forward strand => box with arrow is last one
307
- range_with_arrow = pixel_ranges.pop
308
- track_drawing.rectangle(range_with_arrow.start_pixel-FEATURE_ARROW_LENGTH, top_pixel_of_feature, range_with_arrow.stop_pixel - range_with_arrow.start_pixel, FEATURE_HEIGHT).fill
309
- track_drawing.move_to(range_with_arrow.stop_pixel-FEATURE_ARROW_LENGTH, top_pixel_of_feature)
310
- track_drawing.rel_line_to(FEATURE_ARROW_LENGTH, FEATURE_HEIGHT/2)
311
- track_drawing.rel_line_to(-FEATURE_ARROW_LENGTH, FEATURE_HEIGHT/2)
312
- end
313
- gap_starts.push(range_with_arrow.stop_pixel)
314
- gap_stops.push(range_with_arrow.start_pixel)
315
-
316
- # And then add the others
317
- pixel_ranges.each do |range|
318
- track_drawing.rectangle(range.start_pixel, top_pixel_of_feature, range.stop_pixel - range.start_pixel, FEATURE_HEIGHT).fill
319
- gap_starts.push(range.stop_pixel)
320
- gap_stops.push(range.start_pixel)
321
- end
322
-
323
- # And then draw the connections in the gaps
324
- # Start with removing the very first start and the very last stop.
325
- gap_starts.sort!.pop
326
- gap_stops.sort!.shift
327
-
328
- gap_starts.length.times do |gap_number|
329
- from = gap_starts[gap_number].to_f
330
- to = gap_stops[gap_number].to_f
331
- middle = from + ((to - from)/2)
332
- track_drawing.move_to(from, top_pixel_of_feature+2)
333
- track_drawing.line_to(middle, top_pixel_of_feature+7)
334
- track_drawing.line_to(to, top_pixel_of_feature+2)
335
- track_drawing.stroke
336
- end
337
-
338
- if feature.hidden_subfeatures_at_stop
339
- from = feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}[-1].stop_pixel
340
- to = panel.width
341
- track_drawing.move_to(from, top_pixel_of_feature+5)
342
- track_drawing.line_to(to, top_pixel_of_feature+5)
343
- track_drawing.stroke
344
- end
345
-
346
- if feature.hidden_subfeatures_at_start
347
- from = 1
348
- to = feature.pixel_range_collection.sort_by{|pr| pr.start_pixel}[0].start_pixel
349
- track_drawing.move_to(from, top_pixel_of_feature+5)
350
- track_drawing.line_to(to, top_pixel_of_feature+5)
351
- track_drawing.stroke
352
- end
353
-
354
- else #treat as 'generic'
355
- left_pixel_of_feature, right_pixel_of_feature = feature.pixel_range_collection[0].start_pixel, feature.pixel_range_collection[0].stop_pixel
356
- track_drawing.rectangle(left_pixel_of_feature, top_pixel_of_feature, (right_pixel_of_feature - left_pixel_of_feature), FEATURE_HEIGHT).fill
357
- end
358
-
359
- # And add the region to the image map
360
- if panel.clickable
361
- # Comment: we have to add the vertical_offset and TRACK_HEADER_HEIGHT!
362
- panel.image_map.elements.push(ImageMap::ImageMapElement.new(left_pixel_of_feature,
363
- top_pixel_of_feature + vertical_offset + TRACK_HEADER_HEIGHT,
364
- right_pixel_of_feature,
365
- bottom_pixel_of_feature + vertical_offset + TRACK_HEADER_HEIGHT,
366
- feature.link
367
- ))
368
- end
369
-
370
-
371
- feature_drawn = true
372
- end
373
- end
142
+ feature.draw(track_drawing)
374
143
  end
375
144
  end
376
145
 
377
146
  end
378
147
 
379
- @number_of_times_bumped = ( grid.keys.length == 0 ) ? 1 : grid.keys.max + 1
148
+ @number_of_feature_rows = ( @grid.keys.length == 0 ) ? 1 : @grid.keys.max + 1
380
149
 
381
150
  return panel_drawing
382
151
  end