bio-graphics 1.0 → 1.2

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.
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