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