Class: Geniact::Graphics::Page

Inherits:
Object
  • Object
show all
Defined in:
lib/geniact/graphics/page.rb

Overview

The Bio::Graphics::Page class represents the page of the final rendered SVG and is the top level container into which Bio::Graphics::Tracks are added. It will contain a scale and all the tracks along with their features. The scale is calculated will start at the genomic co-ordinates of the start of the first feature and stop at the end of the last feature)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args) ⇒ Page

Creates a new Page object.

args

  • :height = the minimum height of the rendered page

  • :width = the minimum width of the rendered page

  • :background_color = the background color of the page (default none), can be any SVG colour eg rgb(256,0,0) or #FF0000



16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/geniact/graphics/page.rb', line 16

def initialize(args)
  @height = args[:height]
  @width = args[:width]
  args[:style] = "background-color:#{args[:background_color]};" if args[:background_color]
  @svg = SVGEE.new(args)
  @scale_start = 1.0/0.0
  @scale_stop = -1.0/0.0
  @tracks = [] #array of track objects with loads of features in it...
  @nt_per_percent = 1;
  @num_intervals = args[:number_of_intervals]
  @size_intervals = args[:size_of_intervals]
  @track_top = 30
end

Class Method Details

.from_json(args) ⇒ Object

Takes a custom-written json file and uses it to generate an SVG document based on the information in that page.

  • :json a JSON file describing the parameters and files needed to build an SVG document



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/geniact/graphics/page.rb', line 33

def self.from_json(args)
  require 'rubygems'
  require 'json'
  data = JSON.parse(File.open(args[:json], 'r').read)
  p = Page.new(:width => data["Page"]["width"],
               :height => data["Page"]["height"],
               :number_of_intervals => data["Page"]["intervals"])
  data["Tracks"].each do |track|
    #prep the track args
    track_args = track.dup
    track_args.delete("file")
    track_args.delete("file_type")
    track_args = track_args.inject({}) { |memo, (k, v)| memo[k.to_sym] = v; memo }
    ##convert any of the pre-made gradient strings in the keys to symbol
    track_args.each_key do |k|
      next unless track_args[k].respond_to?(:to_sym)
      track_args[k] = track_args[k].to_sym if Glyph.gradients.include?(track_args[k].to_sym)
    end

    svg_track = p.add_track(track_args)
    features = [] ##set up the features...

    case track["file_type"] ##deal with the gff and data files
      when "gff"
        groups = {}
        parentless_features = []
        Page.parse_gff(track["file"]).each do |gff| #gets features in a list and links their children to them as members of the array at the key
          parent_id = gff.attributes.select { |a| a.first == 'Parent' }
          if parent_id.empty?
            parentless_features << gff
          else
            if groups[parent_id.first.last].nil?
              groups[parent_id.first.last] = []
              groups[parent_id.first.last] << gff
            else
              groups[parent_id.first.last] << gff
            end
          end
        end
        #now flick through the parentless features and add any exons / UTRs
        parentless_features.each do |plf|
          require 'pp'
          #pp parentless_features
          gff_id = plf.attributes.select { |a| a.first == 'ID' }
          gff_id = gff_id.first.last
          exons = []
          utrs = []
          children = groups[gff_id] || children = []
          children.each do |child|
            if child.feature == 'exon' or child.feature == 'CDS'
              exons += [child.start, child.end]
            elsif child.feature =~ /utr/i
              utrs += [child.start, child.end]
            end
          end
          features << MiniFeature.new(:start => plf.start, :end => plf.end, :exons => exons, :utrs => utrs, :strand => plf.strand, :id => gff_id)
        end #parentless features end
      when "data"
        ##each line is a data feature
        File.open(track["file"], "r").each do |line|
          start, stop, value = line.split(/\t/)
          features << MiniFeature.new(:start => start.to_i, :end => stop.to_i, :segment_height => value.to_f)
        end
    end #data end
    features.each { |f| svg_track.add(f) }
  end #track end
  p.write(args[:outfile])
end

.parse_gff(gff_file) ⇒ Object

Parses a GFF file into an Array of Bio::GFF::GFF3::Record objects



105
106
107
108
109
110
111
112
113
# File 'lib/geniact/graphics/page.rb', line 105

def self.parse_gff(gff_file)
  require 'bio'
  a = []
  File.open(gff_file).each do |line|
    next if line =~ /^#/
    a << Bio::GFF::GFF3::Record.new(line)
  end
  a
end

Instance Method Details

#add_track(args) ⇒ Object

adds a new Bio::Graphics::Track object to the current Bio::Graphics::Page object

args

  • :glyph = one of Bio::Graphics::Glyphs#glyphs currently

:generic, :directed, :transcript, :scale, :label, :histogram, :circle, :down_triangle, :up_triangle, :span
  • :stroke_color = the outline colour of the glyphs in the track (default = “black”), can be any SVG colour eg rgb(256,0,0) or #FF0000

  • :fill_color = the fill colour of the glyphs in the track (default = ‘red’), can be any SVG colour eg rgb(256,0,0) or #FF0000, or one of the built in gradient types Bio::Graphics::Glyph#gradients

:red_white_h, :green_white_h, :blue_white_h, :yellow_white_h, :red_white_radial, :green_white_radial, :blue_white_radial, :yellow_white_radial

or a custom definition of a gradient {:type => :radial, :id => :custom, :cx => 5, :cy => 5, :r => 50, :fx => 50, :fy => 50,

:stops => [ {
     :offset => 0,
     :color => 'rgb(255,255,255)',
     :opacity => 0
     },  {
     :offset => 100,
     :color => 'rgb(0,127,200)',
     :opacity => 1
     }, ]

}

  • :track_height = minimum height for the track, will be modified automatically if more space is needed e.g for overlapping features (default = auto),

  • :name = a displayed name for the track (default = ‘feature_track’)

  • :label = display the name given to the track (default = true),

  • :stroke_width = width in pixels of the outline of the glyphs (default=1)

  • :x_round = x radius of the ellipse used to round off the corners of rectangles (default = 1)

  • :y_round = y radius of the ellipse used to round off the corners of rectangles (default = 1)

:utr_fill_color = the fill colour of the utr part of the glyph (default = ‘black’), can be any SVG colour eg rgb(256,0,0) or #FF0000, or one of the built in gradient types Bio::Graphics::Glyph#gradients

:red_white_h, :green_white_h, :blue_white_h, :yellow_white_h, :red_white_radial, :green_white_radial, :blue_white_radial, :yellow_white_radial

or a custom definition of a gradient {:type => :radial, :id => :custom, :cx => 5, :cy => 5, :r => 50, :fx => 50, :fy => 50,

:stops => [ {
     :offset => 0,
     :color => 'rgb(255,255,255)',
     :opacity => 0
     },  {
     :offset => 100,
     :color => 'rgb(0,127,200)',
     :opacity => 1
     }, ]

}

  • :utr_stroke = the outline colour of the utr part of the glyph (default = “black”), can be any SVG colour eg rgb(256,0,0) or #FF0000

  • :utr_stroke_width = The width of the outline stroke for the utr part of the glyph (default = 1)

  • :exon_fill_color = the fill colour of the utr part of the glyph (default = ‘red’), can be any SVG colour eg rgb(256,0,0) or #FF0000, or one of the built in gradient types Bio::Graphics::Glyph#gradients or a custom definition of a gradient

:red_white_h, :green_white_h, :blue_white_h, :yellow_white_h, :red_white_radial, :green_white_radial, :blue_white_radial, :yellow_white_radial

or a custom definition of a gradient {:type => :radial, :id => :custom, :cx => 5, :cy => 5, :r => 50, :fx => 50, :fy => 50,

:stops => [ {
     :offset => 0,
     :color => 'rgb(255,255,255)',
     :opacity => 0
     },  {
     :offset => 100,
     :color => 'rgb(0,127,200)',
     :opacity => 1
     }, ]
  • :exon_stroke = the outline colour of the exon part of the glyph (default = “black”) can be any SVG colour eg rgb(256,0,0) or #FF0000

  • :exon_stroke_width = The width of the outline stroke for the exon part of the glyph (default = 1)

  • :line_color = the colour for the line part that joins the blocks (default = ‘black’) can be any SVG colour eg rgb(256,0,0) or #FF0000

  • :line_width = the width ffor the line part that joins the blocks (default = 1)

  • :exon_style = an arbitrary SVG compliant style string eg “fill-opacity:0.4;”

  • :utr_style = an arbitrary SVG compliant style string eg “fill-opacity:0.4;”

  • :line_style = an arbitrary SVG compliant style string eg “fill-opacity:0.4;”

  • :gap_marker = style of the line between blocks - either angled or straight

returns

a new Bio::Graphics::Track object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/geniact/graphics/page.rb', line 200

def add_track(args)
  #sort out the colour/gradient options
  [:fill_color, :exon_fill_color, :utr_fill_color].each do |colour_tag|
    if Glyph.gradients.include?(args[colour_tag])
      @svg.gradient(Glyph.gradient(args[colour_tag]))
      args[colour_tag] = "url(##{args[colour_tag]})"
    elsif args[colour_tag].instance_of?(Hash)
      @svg.gradient(args[colour_tag])
      args[colour_tag] = "url(##{args[colour_tag][:id]})"
    end
  end
  @tracks << Track.new(args)
  return @tracks.last
end

#compute_boundaries(feature) ⇒ Object



233
234
235
236
237
# File 'lib/geniact/graphics/page.rb', line 233

def compute_boundaries(feature)
  feat_end = feature.end
  feat_end += (8 * @nt_per_px_x * feature.id.to_s.length).to_i if feature.id and @nt_per_px_x
  [feature.start, feat_end]
end

#drawObject

Prints the svg text to STDOUT



405
406
407
# File 'lib/geniact/graphics/page.rb', line 405

def draw
  puts get_markup
end

#draw_circle(args) ⇒ Object



265
266
267
# File 'lib/geniact/graphics/page.rb', line 265

def draw_circle(args)
  Glyph.circle(args).each { |g| @svg.add_primitive(g) }
end

#draw_directed(args) ⇒ Object



261
262
263
# File 'lib/geniact/graphics/page.rb', line 261

def draw_directed(args)
  Glyph.directed(args).each { |g| @svg.add_primitive(g) }
end

#draw_down_triangle(args) ⇒ Object



281
282
283
# File 'lib/geniact/graphics/page.rb', line 281

def draw_down_triangle(args)
  Glyph.down_triangle(args).each { |g| @svg.add_primitive(g) }
end

#draw_features(track) ⇒ Object

Takes a Bio::Graphics::Track object and does the work to draw features

It examines the the type of track and calculates the required parameters

  • args - an Array of Bio::Graphics::Track object



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/geniact/graphics/page.rb', line 293

def draw_features(track)
  if [:histogram, "histogram"].include?(track.glyph) #do different stuff for data tracks...

    y = @track_top + (track.track_height)
    max = track.max_y ? track.max_y : track.features.sort { |a, b| a.segment_height <=> b.segment_height }.last.segment_height
    min = 0
    if track.label
      draw_label(:text => track.name, :y => @track_top += 30, :x => 3)
    end
    draw_label(:text => max, :x => to_px(@scale_stop - @scale_start) + 5, :y => @track_top + 5)
    draw_label(:text => min, :x => to_px(@scale_stop - @scale_start) + 5, :y => @track_top + track.track_height + 5)
    data_interval = max - min
    data_per_px = track.track_height.to_f / data_interval.to_f
    track.features.each do |f|
      x = to_px(f.start - @scale_start)
      width = to_px((f.end - @scale_start) - (f.start - @scale_start))
      height = f.segment_height.to_f * data_per_px
      y = @track_top + track.track_height - height + 5
      #$stderr.puts f.segment_height, data_per_px, data_interval, max, min, "------"
      self.send("draw_#{track.glyph}", {:x => x,
                                        :y => y,
                                        :width => width,
                                        :height => height}.merge!(track.args))
    end
    @track_top += (track.track_height) + 20
  else ##following stuff for the features
    if track.label
      draw_label(:text => track.name, :y => @track_top += 30, :x => 3)
    end
    track.get_rows(self) ##work out how many rows and which features belong in each row...
    track.features.each_with_index do |f, index|
      x = to_px(f.start - @scale_start) #bottom left of feature
      all_sub_blocks = []

      #convert the exon and utr start stops to px start stops and px widths
      exons = []
      if f.exons
        f.exons.each_slice(2).each do |exon|
          all_sub_blocks << exon
          next if exon.nil?
          exons << [to_px(exon[0] - @scale_start), to_px((exon[1] - @scale_start) - (exon[0] - @scale_start))]
        end
      end
      f.exons = exons

      utrs = []
      if f.utrs
        f.utrs.each_slice(2).each do |utr|
          all_sub_blocks << utr
          next if utr.nil?
          utrs << [to_px(utr[0] - @scale_start), to_px((utr[1] - @scale_start) - (utr[0] - @scale_start))]
        end
      end
      f.utrs = utrs

      #if there are any intron like gaps.. get where they would be
      if not all_sub_blocks.empty?
        all_sub_blocks = all_sub_blocks.sort { |a, b| a.first <=> b.first }
        all_sub_blocks.each_index do |i|
          next if i + 1 == all_sub_blocks.length or all_sub_blocks[i].last >= all_sub_blocks[i + 1].first #skip if there is no gap
          f.block_gaps << [to_px(all_sub_blocks[i].last - @scale_start), to_px((all_sub_blocks[i + 1].first - @scale_start) - (all_sub_blocks[i].last - @scale_start))]
        end
      end

      width = to_px((f.end - @scale_start) - (f.start - @scale_start))
      if track.min_width and width < track.min_width
        width = track.min_width
      end
      y = @track_top + (track.feature_rows[index] * 2 * track.feature_height)

      self.send("draw_#{track.glyph}", {:x => x,
                                        :y => y,
                                        :width => width,
                                        :strand => f.strand,
                                        :exons => f.exons,
                                        :utrs => f.utrs,
                                        :block_gaps => f.block_gaps,
                                        :height => track.feature_height,
                                        :link_val => f.link_val,
                                        :fill_color => f.fill_color,
                                        :params => f.params
      }.merge!(track.args))

      if f.id
        draw_label(:y => y-2, :x => x, :text => f.id)
      end
    end
    @track_top += (track.feature_height * track.number_rows * 2) + 20
  end

  @height = @track_top + 100 #fudge...
  @svg.update_height(@height)
  #@svg.update_width(@width + 200)

end

#draw_generic(args) ⇒ Object

remember presentation info comes from track@args when the track is defined



257
258
259
# File 'lib/geniact/graphics/page.rb', line 257

def draw_generic(args) #remember presentation info comes from track@args when the track is defined
  Glyph.generic(args).each { |g| @svg.add_primitive(g) }
end

#draw_histogram(args) ⇒ Object



273
274
275
# File 'lib/geniact/graphics/page.rb', line 273

def draw_histogram(args)
  Glyph.generic(args).each { |g| @svg.add_primitive(g) }
end

#draw_label(args) ⇒ Object

Adds the Bio::Graphics::Primitive objects to the SVGEE object

  • args - an Array of Bio::Graphics::Primitive object



250
251
252
253
254
# File 'lib/geniact/graphics/page.rb', line 250

def draw_label(args)
  Glyph.label(:text => args[:text],
              :x => args[:x],
              :y => args[:y]).each { |g| @svg.add_primitive(g) }
end

#draw_scaleObject

Adds scale bar to the list of objects to be rendered in the final



241
242
243
244
245
246
# File 'lib/geniact/graphics/page.rb', line 241

def draw_scale
  Glyph.scale(:start => @scale_start,
              :stop => @scale_stop,
              :number_of_intervals => @num_intervals, :size_of_intervals => @size_intervals,
              :page_height => @height, :page_width => @width).each { |g| @svg.add_primitive(g) }
end

#draw_span(args) ⇒ Object



285
286
287
# File 'lib/geniact/graphics/page.rb', line 285

def draw_span(args)
  Glyph.span(args).each { |g| @svg.add_primitive(g) }
end

#draw_transcript(args) ⇒ Object



269
270
271
# File 'lib/geniact/graphics/page.rb', line 269

def draw_transcript(args)
  Glyph.transcript(args).each { |g| @svg.add_primitive(g) }
end

#draw_up_triangle(args) ⇒ Object



277
278
279
# File 'lib/geniact/graphics/page.rb', line 277

def draw_up_triangle(args)
  Glyph.up_triangle(args).each { |g| @svg.add_primitive(g) }
end

#get_limitsObject

Calculates the Page scale-start and scale-stop position and the nucleotides per pixel for the current Page



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/geniact/graphics/page.rb', line 216

def get_limits
  track = @tracks.first
  lowest = track.features.sort { |x, y| x.start <=> y.start }.first.start
  highest = track.features.sort { |x, y| y.end <=> x.end }.first.end
  @scale_start = lowest if lowest < @scale_start
  @scale_stop = highest if highest > @scale_stop
  @nt_per_px_x = (@scale_stop - @scale_start).to_f / @width.to_f

  highest = track.features.map { |feat|
    compute_boundaries(feat)[1]
  }.max
  if highest > @scale_stop
    @scale_stop = highest
    @nt_per_px_x = (@scale_stop - @scale_start).to_f / @width.to_f
  end
end

#get_markupObject

generates the SVG text



395
396
397
398
399
400
401
402
# File 'lib/geniact/graphics/page.rb', line 395

def get_markup
  get_limits
  draw_scale
  @tracks.each do |track|
    draw_features(track)
  end
  @svg.draw
end

#to_px(num) ⇒ Object

Calculates the pixel value for a given number



390
391
392
# File 'lib/geniact/graphics/page.rb', line 390

def to_px(num)
  (num.to_f / @nt_per_px_x.to_f)
end

#write(file) ⇒ Object

Writes the SVG text to a file



410
411
412
# File 'lib/geniact/graphics/page.rb', line 410

def write(file)
  File.open(file, 'w').write(get_markup)
end