bio-graphics 1.0 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/classes/Bio/Graphics.html +11 -1
- data/doc/classes/Bio/Graphics/Panel.html +5 -2
- data/doc/classes/Bio/Graphics/Panel.src/M000005.html +4 -4
- data/doc/classes/Bio/Graphics/Panel.src/M000006.html +3 -3
- data/doc/classes/Bio/Graphics/Panel.src/M000007.html +6 -5
- data/doc/classes/Bio/Graphics/Panel/Ruler.html +16 -16
- data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000014.html +6 -14
- data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000015.html +10 -41
- data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000016.html +59 -0
- data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000017.html +20 -0
- data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000018.html +28 -0
- data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000019.html +59 -0
- data/doc/classes/Bio/Graphics/Panel/Track.html +30 -15
- data/doc/classes/Bio/Graphics/Panel/Track.src/M000008.html +8 -5
- data/doc/classes/Bio/Graphics/Panel/Track.src/M000009.html +2 -2
- data/doc/classes/Bio/Graphics/Panel/Track.src/M000010.html +12 -245
- data/doc/classes/Bio/Graphics/Panel/Track/Feature.html +109 -0
- data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000011.html +1 -1
- data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000012.html +39 -0
- data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000013.html +27 -0
- data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000014.html +209 -49
- data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000015.html +48 -0
- data/doc/classes/Bio/Graphics/Panel/Track/Feature/PixelRange.html +5 -5
- data/doc/classes/Bio/Graphics/Panel/Track/Feature/PixelRange.src/M000013.html +18 -0
- data/doc/classes/Bio/Graphics/Panel/Track/Feature/PixelRange.src/M000016.html +18 -0
- data/doc/created.rid +1 -1
- data/doc/files/README_DEV.html +7 -11
- data/doc/files/TUTORIAL.html +3 -3
- data/doc/files/lib/bio-graphics_rb.html +3 -1
- data/doc/files/lib/bio/graphics/feature_rb.html +6 -11
- data/doc/files/lib/bio/graphics/image_map_rb.html +1 -1
- data/doc/files/lib/bio/graphics/panel_rb.html +1 -1
- data/doc/files/lib/bio/graphics/ruler_rb.html +1 -1
- data/doc/files/lib/bio/graphics/track_rb.html +1 -1
- data/doc/fr_method_index.html +8 -4
- data/doc/images/example_labels.png +0 -0
- data/doc/images/glyph_showcase.png +0 -0
- data/doc/images/terms.png +0 -0
- data/doc/index.html +2 -2
- data/images/example_labels.png +0 -0
- data/images/glyph_showcase.png +0 -0
- data/images/terms.png +0 -0
- data/images/terms.svg +146 -132
- data/lib/bio-graphics.rb +2 -1
- data/lib/bio/graphics/feature.rb +304 -0
- data/lib/bio/graphics/panel.rb +15 -11
- data/lib/bio/graphics/ruler.rb +1 -1
- data/lib/bio/graphics/track.rb +23 -254
- data/samples/arkdb_features.rb +8 -8
- data/samples/glyph_showcase.rb +10 -6
- data/test/unit/test_creation.rb +51 -0
- metadata +14 -2
data/lib/bio-graphics.rb
CHANGED
data/lib/bio/graphics/feature.rb
CHANGED
@@ -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
|
data/lib/bio/graphics/panel.rb
CHANGED
@@ -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
|
-
@
|
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, :
|
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.
|
168
|
-
|
169
|
-
|
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
|
-
@
|
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
|
data/lib/bio/graphics/ruler.rb
CHANGED
@@ -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(
|
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)
|
data/lib/bio/graphics/track.rb
CHANGED
@@ -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
|
-
#
|
33
|
-
#
|
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,
|
39
|
+
def initialize(panel, name, label = true, colour = [0,0,1], glyph = :generic)
|
39
40
|
@panel = panel
|
40
41
|
@name = name
|
41
|
-
@
|
42
|
-
@
|
42
|
+
@show_label = label
|
43
|
+
@colour = colour
|
44
|
+
@glyph = glyph
|
43
45
|
@features = Array.new
|
44
|
-
@
|
46
|
+
@number_of_feature_rows = 0
|
47
|
+
@vertical_offset = 0
|
48
|
+
@grid = Hash.new
|
45
49
|
end
|
46
|
-
attr_accessor :panel, :name, :
|
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
|
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(@
|
134
|
+
track_drawing.translate(0, self.vertical_offset + TRACK_HEADER_HEIGHT)
|
135
|
+
track_drawing.set_source_rgb(@colour)
|
135
136
|
|
136
|
-
|
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
|
-
|
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
|
-
@
|
148
|
+
@number_of_feature_rows = ( @grid.keys.length == 0 ) ? 1 : @grid.keys.max + 1
|
380
149
|
|
381
150
|
return panel_drawing
|
382
151
|
end
|