bio-graphics 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/doc/classes/Bio.html +135 -0
  2. data/doc/classes/Bio/Graphics.html +247 -0
  3. data/doc/classes/Bio/Graphics/Panel.html +344 -0
  4. data/doc/classes/Bio/Graphics/Panel.src/M000005.html +29 -0
  5. data/doc/classes/Bio/Graphics/Panel.src/M000006.html +19 -0
  6. data/doc/classes/Bio/Graphics/Panel.src/M000007.html +67 -0
  7. data/doc/classes/Bio/Graphics/Panel/Ruler.html +238 -0
  8. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000008.html +20 -0
  9. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000009.html +28 -0
  10. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000010.html +54 -0
  11. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000013.html +20 -0
  12. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000014.html +28 -0
  13. data/doc/classes/Bio/Graphics/Panel/Ruler.src/M000015.html +59 -0
  14. data/doc/classes/Bio/Graphics/Panel/Track.html +342 -0
  15. data/doc/classes/Bio/Graphics/Panel/Track.src/M000008.html +23 -0
  16. data/doc/classes/Bio/Graphics/Panel/Track.src/M000009.html +42 -0
  17. data/doc/classes/Bio/Graphics/Panel/Track.src/M000010.html +285 -0
  18. data/doc/classes/Bio/Graphics/Panel/Track.src/M000011.html +23 -0
  19. data/doc/classes/Bio/Graphics/Panel/Track.src/M000012.html +43 -0
  20. data/doc/classes/Bio/Graphics/Panel/Track.src/M000013.html +259 -0
  21. data/doc/classes/Bio/Graphics/Panel/Track/Feature.html +292 -0
  22. data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000011.html +65 -0
  23. data/doc/classes/Bio/Graphics/Panel/Track/Feature.src/M000014.html +65 -0
  24. data/doc/classes/Bio/Graphics/Panel/Track/Feature/PixelRange.html +155 -0
  25. data/doc/classes/Bio/Graphics/Panel/Track/Feature/PixelRange.src/M000012.html +18 -0
  26. data/doc/classes/Bio/Graphics/Panel/Track/Feature/PixelRange.src/M000015.html +18 -0
  27. data/doc/classes/ImageMap.html +185 -0
  28. data/doc/classes/ImageMap.src/M000001.html +18 -0
  29. data/doc/classes/ImageMap.src/M000002.html +24 -0
  30. data/doc/classes/ImageMap/ImageMapElement.html +187 -0
  31. data/doc/classes/ImageMap/ImageMapElement.src/M000003.html +19 -0
  32. data/doc/classes/ImageMap/ImageMapElement.src/M000004.html +20 -0
  33. data/doc/created.rid +1 -0
  34. data/doc/files/README_DEV.html +432 -0
  35. data/doc/files/TUTORIAL.html +358 -0
  36. data/doc/files/lib/bio-graphics_rb.html +121 -0
  37. data/doc/files/lib/bio/graphics/feature_rb.html +113 -0
  38. data/doc/files/lib/bio/graphics/image_map_rb.html +113 -0
  39. data/doc/files/lib/bio/graphics/panel_rb.html +113 -0
  40. data/doc/files/lib/bio/graphics/ruler_rb.html +113 -0
  41. data/doc/files/lib/bio/graphics/track_rb.html +113 -0
  42. data/doc/fr_class_index.html +35 -0
  43. data/doc/fr_file_index.html +34 -0
  44. data/doc/fr_method_index.html +41 -0
  45. data/doc/images/example.png +0 -0
  46. data/doc/images/glyph_showcase.png +0 -0
  47. data/doc/images/terms.png +0 -0
  48. data/doc/images/terms.svg +166 -0
  49. data/doc/index.html +24 -0
  50. data/images/example.png +0 -0
  51. data/images/glyph_showcase.png +0 -0
  52. data/images/terms.png +0 -0
  53. data/images/terms.svg +166 -0
  54. data/lib/bio-graphics.rb +18 -0
  55. data/lib/bio/graphics/feature.rb +136 -0
  56. data/lib/bio/graphics/image_map.rb +37 -0
  57. data/lib/bio/graphics/panel.rb +205 -0
  58. data/lib/bio/graphics/ruler.rb +96 -0
  59. data/lib/bio/graphics/track.rb +387 -0
  60. data/samples/arkdb_features.rb +37 -0
  61. data/samples/data.txt +32 -0
  62. data/samples/glyph_showcase.rb +29 -0
  63. metadata +137 -0
@@ -0,0 +1,18 @@
1
+ #
2
+ # = bio-graphics.rb - Loading all Bio::Graphics modules
3
+ #
4
+ # Copyright:: Copyright (C) 2007
5
+ # Jan Aerts <jan.aerts@bbsrc.ac.uk>
6
+ # License:: The Ruby License
7
+ #
8
+ begin
9
+ require 'bio'
10
+ require 'cairo'
11
+ rescue nil
12
+ end
13
+
14
+ require File.dirname(__FILE__) + '/bio/graphics/image_map.rb'
15
+ require File.dirname(__FILE__) + '/bio/graphics/panel.rb'
16
+ require File.dirname(__FILE__) + '/bio/graphics/track.rb'
17
+ require File.dirname(__FILE__) + '/bio/graphics/feature.rb'
18
+ require File.dirname(__FILE__) + '/bio/graphics/ruler.rb'
@@ -0,0 +1,136 @@
1
+ #
2
+ # = bio/graphics/feature.rb - feature class
3
+ #
4
+ # Copyright:: Copyright (C) 2007
5
+ # Jan Aerts <jan.aerts@bbsrc.ac.uk>
6
+ # License:: The Ruby License
7
+ #
8
+ module Bio
9
+ module Graphics
10
+ class Panel
11
+ class Track
12
+ # The Bio::Graphics::Track::Feature class describes features to be
13
+ # placed on the graph. See Bio::Graphics documentation for explanation
14
+ # of interplay between different classes.
15
+ #
16
+ # The position of the Feature is a Bio::Locations object to make it possible
17
+ # to transparently work with simple and spliced features.
18
+ class Feature
19
+ # !!Not to be used directly. Use
20
+ # Bio::Graphics::Panel::Track.add_feature instead!!
21
+ # A feature can not exist except within the confines of a
22
+ # Bio::Graphics::Panel::Track object.
23
+ #
24
+ #--
25
+ # This is necessary because the feature needs to know the colour and glyph,
26
+ # both of which are defined within the panel.
27
+ #++
28
+ #
29
+ # ---
30
+ # *Arguments*:
31
+ # * _panel_ (required) :: Bio::Graphics::Panel::Track object that this
32
+ # feature belongs to
33
+ # * _name_ (required) :: Name of the feature
34
+ # * _location_ :: Bio::Locations object. Default = whole panel, forward strand
35
+ # * _link_ :: URL for clickable images
36
+ # *Returns*:: Bio::Graphics::Track::Feature object
37
+ def initialize(track, name, location = Bio::Locations.new('1..' + track.panel.length.to_s), link = nil)
38
+ @track = track
39
+ @name = name
40
+ @location = location
41
+ @start = location.collect{|l| l.from}.min.to_i
42
+ @stop = location.collect{|l| l.to}.max.to_i
43
+ @strand = location[0].strand.to_i
44
+ @link = link
45
+ @pixel_range_collection = Array.new
46
+ @chopped_at_start = false
47
+ @chopped_at_stop = false
48
+ @hidden_subfeatures_at_start = false
49
+ @hidden_subfeatures_at_stop = false
50
+
51
+ # Get all pixel ranges for the subfeatures
52
+ location.each do |l|
53
+ # xxxxxx [ ]
54
+ if l.to < track.panel.display_start
55
+ @hidden_subfeatures_at_start = true
56
+ next
57
+ # [ ] xxxxx
58
+ elsif l.from > track.panel.display_stop
59
+ @hidden_subfeatures_at_stop = true
60
+ next
61
+ # xxxx[xxx ]
62
+ elsif l.from < track.panel.display_start and l.to > track.panel.display_start
63
+ start_pixel = 1
64
+ stop_pixel = ( l.to - track.panel.display_start ).to_f / track.panel.rescale_factor
65
+ @chopped_at_start = true
66
+ # [ xxxx]xxxx
67
+ elsif l.from < track.panel.display_stop and l.to > track.panel.display_stop
68
+ start_pixel = ( l.from - track.panel.display_start ).to_f / track.panel.rescale_factor
69
+ stop_pixel = track.panel.width
70
+ @chopped_at_stop = true
71
+ # xxxx[xxxxxxxxxx]xxxx
72
+ elsif l.from < track.panel.display_start and l.to > track.panel.display_stop
73
+ start_pixel = 1
74
+ stop_pixel = track.panel.width
75
+ @chopped_at_start = true
76
+ @chopped_at_stop = true
77
+ # [ xxxxx ]
78
+ else
79
+ start_pixel = ( l.from - track.panel.display_start ).to_f / track.panel.rescale_factor
80
+ stop_pixel = ( l.to - track.panel.display_start ).to_f / track.panel.rescale_factor
81
+ end
82
+
83
+ @pixel_range_collection.push(PixelRange.new(start_pixel, stop_pixel))
84
+
85
+ end
86
+ end
87
+
88
+ # The track that this feature belongs to
89
+ attr_accessor :track
90
+
91
+ # The name of the feature
92
+ attr_accessor :name
93
+
94
+ # The location of the feature (which is a Bio::Locations object)
95
+ attr_accessor :location
96
+
97
+ # The start position of the feature (in bp)
98
+ attr_accessor :start
99
+
100
+ # The stop position of the feature (in bp)
101
+ attr_accessor :stop
102
+
103
+ # The strand of the feature
104
+ attr_accessor :strand
105
+
106
+ # The URL to be followed when the glyph for this feature is clicked
107
+ attr_accessor :link
108
+
109
+ # The array keeping the pixel ranges for the sub-features. Unspliced
110
+ # features will just have one element, while spliced features will
111
+ # have more than one.
112
+ attr_accessor :pixel_range_collection
113
+
114
+ # Is the first subfeature incomplete?
115
+ attr_accessor :chopped_at_start
116
+
117
+ # Is the last subfeature incomplete?
118
+ attr_accessor :chopped_at_stop
119
+
120
+ # Are there subfeatures out of view at the left side of the picture?
121
+ attr_accessor :hidden_subfeatures_at_start
122
+
123
+ # Are there subfeatures out of view at the right side of the picture?
124
+ attr_accessor :hidden_subfeatures_at_stop
125
+
126
+ class PixelRange
127
+ def initialize(start_pixel, stop_pixel)
128
+ @start_pixel, @stop_pixel = start_pixel, stop_pixel
129
+ end
130
+ attr_accessor :start_pixel, :stop_pixel
131
+ end
132
+ end #Feature
133
+ end #Track
134
+ end #Panel
135
+ end #Graphics
136
+ end #Bio
@@ -0,0 +1,37 @@
1
+ #
2
+ # = bio/graphics/image_map.rb - create the image map for clickable images
3
+ #
4
+ # Copyright:: Copyright (C) 2007
5
+ # Jan Aerts <jan.aerts@bbsrc.ac.uk>
6
+ # License:: Ruby's
7
+ class ImageMap
8
+ def initialize
9
+ @elements = Array.new
10
+ end
11
+ attr_accessor :elements
12
+
13
+ def to_s
14
+ output = Array.new
15
+ output.push('<map name="image_map" border="1">')
16
+ @elements.each do |e|
17
+ output.push(e.to_s)
18
+ end
19
+ output.push('</map>')
20
+ return output.join("\n")
21
+ end
22
+
23
+
24
+ class ImageMapElement
25
+ def initialize(left, top, right, bottom, url = nil)
26
+ @left, @top, @right, @bottom = left, top, right, bottom
27
+ @url = ( url.nil? ) ? '' : url
28
+ end
29
+ attr_accessor :left, :top, :right, :bottom, :url
30
+
31
+ def to_s
32
+ unless @url == ''
33
+ return '<area shape="rect" coords="' + @left.to_s + ' ' + @top.to_s + ' ' + @right.to_s + ' ' + @bottom.to_s + '" href="' + @url + '"/>'
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,205 @@
1
+ #
2
+ # = bio/graphics/panel - panel class
3
+ #
4
+ # Copyright:: Copyright (C) 2007
5
+ # Jan Aerts <jan.aerts@bbsrc.ac.uk>
6
+ # License:: The Ruby License
7
+ #
8
+ module Bio
9
+ # = DESCRIPTION
10
+ # The Bio::Graphics set of objects allow for creating simple images that
11
+ # display features on a linear map. A picture consists of:
12
+ # * one *panel*: container of all tracks
13
+ # * one or more *tracks*: container of the features. Multiple tracks
14
+ # can exist in the same graphic to allow for differential visualization of
15
+ # different feature types (e.g. genes as blue rectangles and polymorphisms
16
+ # as red triangles)
17
+ # * one or more *features* in each track: these are the actual features that
18
+ # you want to display (e.g. 'gene 1', 'SNP 123445')
19
+ # * a *ruler* on top of the panel: is added automatically
20
+ #
21
+ # Schematically:
22
+ # panel
23
+ # +-> track 1
24
+ # | +-> feature 1
25
+ # | +-> feature 2
26
+ # | +-> feature 3
27
+ # +-> track 2
28
+ # | +-> feature 4
29
+ # | +-> feature 5
30
+ # +-> ruler
31
+ #
32
+ # = USAGE
33
+ # # Create a panel for something with a length of 653. This could be a
34
+ # # sequence of 653 bp, but also a genetic map of 653 cM.
35
+ # g = Bio::Graphics::Panel.new(653)
36
+ #
37
+ # # Add the first track (e.g. 'genes')
38
+ # track1 = g.add_track('genes')
39
+ #
40
+ # # And put features in that track
41
+ # track1.add_feature('gene1',250,375)
42
+ # track1.add_feature('gene2',54,124)
43
+ # track1.add_feature('gene3',100,500)
44
+ #
45
+ # # Add a second track (e.g. 'polymorphisms')
46
+ # track2 = g.add_track('polymorphisms','red','triangle')
47
+ #
48
+ # # And put features on this one
49
+ # track2.add_feature('polymorphism 1',56,56)
50
+ # track2.add_feature('polymorphism 2',103,103)
51
+ #
52
+ # # Create the actual image as SVG text
53
+ # g.draw('my_picture.png')
54
+ #
55
+ # = FUTURE PROSPECTS
56
+ # Any help from other developers is welcomed to work on these features:
57
+ # * Would be nice if this module would be easily accessible from any object
58
+ # that implements bioruby's Bio::Map::ActsAsMap.
59
+ #
60
+ module Graphics
61
+
62
+ # The defaults
63
+ DEFAULT_PANEL_WIDTH = 800 # How many pixels wide do we want the picture to be?
64
+ TRACK_HEADER_HEIGHT = 12 # The track header will contain the title.
65
+ FEATURE_HEIGHT = 10 # The height in pixels of a glyph.
66
+ FEATURE_V_DISTANCE = 5 # The vertical distance in pixels between glyphs
67
+ FEATURE_ARROW_LENGTH = 5 # In pixels again.
68
+ RULER_TEXT_HEIGHT = 10 # And again...
69
+ RULER_MIN_DISTANCE_TICKS_PIXEL = 5 # There should be at least 5 pixels between
70
+ # consecutive ticks. This is used for the
71
+ # calculation of tick distance.
72
+
73
+ # The Bio::Graphics::Panel class describes the complete graph and contains
74
+ # all tracks. See Bio::Graphics documentation for explanation of interplay
75
+ # between different classes.
76
+ class Panel
77
+ # Create a new Bio::Graphics::Panel object
78
+ #
79
+ # g = Bio::Graphics::Panel.new(456)
80
+ #
81
+ # The height of the image is calculated automatically depending on how many
82
+ # tracks and features it contains. The width of the image defaults to 800 pt
83
+ # but can be set manually by using a second argument:
84
+ #
85
+ # g = Bio::Graphics::Panel.new(456, 400)
86
+ #
87
+ #
88
+ # See also: Bio::Graphics::Track, BioExt::Graphics::Feature
89
+ # ---
90
+ # *Arguments*:
91
+ # * _length_ :: length of the thing you want to visualize, e.g for
92
+ # visualizing a sequence that is 3.24 kb long, use 324.
93
+ # * _width_ :: width of the resulting image in pt. This should be a string
94
+ # and not an integer. Default = '800' (Notice the quotes...).
95
+ # * _clickable_ :: whether the picture should have clickable glyphs or not
96
+ # (default: false) If set to true, a html file will be created with
97
+ # the map.
98
+ # * _display_start_ :: start coordinate to be displayed (default: 1)
99
+ # * _display_stop_ :: stop coordinate to be displayed (default: length of sequence)
100
+ # *Returns*:: Bio::Graphics::Panel object
101
+ def initialize(length, width = DEFAULT_PANEL_WIDTH, clickable = false, display_start = nil, display_stop = nil)
102
+ @length = length.to_i
103
+ @width = width.to_i
104
+ @tracks = Array.new
105
+ @number_of_times_bumped = 0
106
+ @clickable = clickable
107
+ @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
+ if @display_stop <= @display_start
111
+ raise "[ERROR] Start coordinate to be displayed has to be smaller than stop coordinate."
112
+ end
113
+ @rescale_factor = (@display_stop - @display_start).to_f / @width
114
+ end
115
+ attr_accessor :length, :width, :height, :rescale_factor, :tracks, :number_of_times_bumped, :clickable, :image_map, :display_start, :display_stop
116
+
117
+ # Adds a Bio::Graphics::Track container to this panel. A panel contains a
118
+ # logical grouping of features, e.g. (for sequence annotation:) genes,
119
+ # polymorphisms, ESTs, etc.
120
+ #
121
+ # est_track = g.add_track('ESTs')
122
+ # gene_track = g.add_track('genes')
123
+ #
124
+ # ---
125
+ # *Arguments*:
126
+ # * _name_ (required) :: Name of the track to be displayed (e.g. 'genes')
127
+ # * _colour_ :: Colour to be used to draw the features within the track.
128
+ # Default = 'blue'
129
+ # * _glyph_ :: Glyph to use for drawing the features. Options are:
130
+ # 'generic', 'directed_generic', 'spliced', 'directed_spliced' and
131
+ # 'triangle'. Triangles can be used
132
+ # for features whose start and stop positions are the same (e.g. SNPs).
133
+ # If you try to draw a feature that is longer with triangles, an error
134
+ # will be shown.
135
+ # *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))
138
+ return @tracks[-1]
139
+ end
140
+
141
+ # Create the drawing
142
+ #--
143
+ # The fact that display_start and display_stop can be set has two
144
+ # consequences:
145
+ # 1. not all features are drawn
146
+ # 2. the x-coordinate of all glyphs has to be corrected
147
+ #++
148
+ def draw(file_name)
149
+ # Create a panel that is huge vertically
150
+ huge_height = 2000
151
+
152
+ huge_panel_drawing = nil
153
+ huge_panel_drawing = Cairo::ImageSurface.new(1, @width, huge_height)
154
+
155
+ background = Cairo::Context.new(huge_panel_drawing)
156
+ background.set_source_rgb(1,1,1)
157
+ background.rectangle(0,0,@width,huge_height).fill
158
+
159
+ # Add ruler
160
+ vertical_offset = 0
161
+ ruler = Bio::Graphics::Panel::Ruler.new(self)
162
+ ruler.draw(huge_panel_drawing, vertical_offset)
163
+ vertical_offset += ruler.height
164
+
165
+ # Add tracks
166
+ @tracks.each do |track|
167
+ track.draw(huge_panel_drawing, vertical_offset)
168
+ @number_of_times_bumped += track.number_of_times_bumped
169
+ vertical_offset += ( track.number_of_times_bumped*(FEATURE_HEIGHT+FEATURE_V_DISTANCE+5)) + 10 # '10' is for the header
170
+ end
171
+
172
+ # And create a smaller version of the panel
173
+ height = ruler.height
174
+ @number_of_times_bumped.times do
175
+ height += 20
176
+ end
177
+ @tracks.length.times do #To correct for the track headers
178
+ height += 10
179
+ end
180
+
181
+ resized_panel_drawing = nil
182
+ resized_panel_drawing = Cairo::ImageSurface.new(1, @width, height)
183
+ resizing_context = Cairo::Context.new(resized_panel_drawing)
184
+ resizing_context.set_source(huge_panel_drawing, 0,0)
185
+ resizing_context.rectangle(0,0,@width, height).fill
186
+
187
+ # And print to file
188
+ resized_panel_drawing.write_to_png(file_name)
189
+ if @clickable # create png and map
190
+ html_filename = file_name.sub(/\.[^.]+$/, '.html')
191
+ html = File.open(html_filename,'w')
192
+ html.puts "<html>"
193
+ html.puts "<body>"
194
+ html.puts @image_map.to_s
195
+ html.puts "<img border='1' src='" + file_name + "' usemap='#image_map' />"
196
+ html.puts "</body>"
197
+ html.puts "</html>"
198
+ end
199
+ end
200
+
201
+
202
+
203
+ end #Panel
204
+ end #Graphics
205
+ end #Bio
@@ -0,0 +1,96 @@
1
+ #
2
+ # = bio/graphics/ruler - ruler class
3
+ #
4
+ # Copyright:: Copyright (C) 2007
5
+ # Jan Aerts <jan.aerts@bbsrc.ac.uk>
6
+ # License:: The Ruby License
7
+ #
8
+ module Bio
9
+ module Graphics
10
+ class Panel
11
+ # The Bio::Graphics::Ruler class describes the ruler to be drawn on the
12
+ # graph. This is created automatically when creating the picture by using
13
+ # Bio::Graphics::Panel.to_svg. See BioExt::Graphics documentation for
14
+ # explanation of interplay between different classes.
15
+ #--
16
+ # TODO: the ruler might be implemented as a special case of a track, so
17
+ # it would inherit from it (class Ruler < Bio::Graphics::Panel::Track).
18
+ # But I haven't really thought this through yet.
19
+ #++
20
+ class Ruler
21
+ # Creates a new Bio::Graphics::Panel::Ruler object.
22
+ # ---
23
+ # *Arguments*:
24
+ # * _panel_ (required) :: Bio::Graphics::Panel object that this ruler
25
+ # belongs to
26
+ # * _colour_ :: colour of the ruler. Default = 'black'
27
+ # *Returns*:: Bio::Graphics::Ruler object
28
+ def initialize(panel, colour = [0,0,0])
29
+ @panel = panel
30
+ @name = 'ruler'
31
+ @colour = colour
32
+ end
33
+ attr_accessor :panel, :name, :colour, :height, :minor_tick_distance, :major_tick_distance
34
+
35
+ def calculate_tick_distance
36
+ min_tick_distance_requirement_met = false
37
+ self.minor_tick_distance = 1 # in basepairs.
38
+ while ! min_tick_distance_requirement_met
39
+ if self.minor_tick_distance/panel.rescale_factor >= RULER_MIN_DISTANCE_TICKS_PIXEL
40
+ min_tick_distance_requirement_met = true
41
+ else
42
+ self.minor_tick_distance = self.minor_tick_distance*5
43
+ end
44
+ end
45
+
46
+ self.major_tick_distance = self.minor_tick_distance * 10
47
+ end
48
+
49
+ def draw(panel_drawing, vertical_offset)
50
+ ruler_drawing = Cairo::Context.new(panel_drawing)
51
+
52
+ self.calculate_tick_distance
53
+
54
+ # Draw line
55
+ ruler_drawing.move_to(0,10)
56
+ ruler_drawing.line_to(panel.width, 10)
57
+ ruler_drawing.stroke
58
+
59
+ # Draw ticks
60
+ # * Find position of first tick.
61
+ # Most of the time, we don't want the first tick on the very first
62
+ # basepair of the view. Suppose that would be position 333 in the
63
+ # sequence. Then the numbers under the major tickmarks would be:
64
+ # 343, 353, 363, 373 and so on. Instead, we want 350, 360, 370, 380.
65
+ # So we want to find the position of the first tick.
66
+ first_tick_position = panel.display_start
67
+ while first_tick_position.modulo(minor_tick_distance) != 0
68
+ first_tick_position += 1
69
+ end
70
+
71
+ # * And start drawing the rest.
72
+ first_tick_position.step(panel.display_stop, minor_tick_distance) do |tick|
73
+ tick_pixel_position = (tick - panel.display_start) / panel.rescale_factor
74
+ ruler_drawing.move_to(tick_pixel_position.floor, 5)
75
+ if tick.modulo(major_tick_distance) == 0
76
+ ruler_drawing.rel_line_to(0, 15)
77
+
78
+ # Draw tick number
79
+ ruler_drawing.select_font_face('Georgia', 1, 1)
80
+ ruler_drawing.set_font_size(RULER_TEXT_HEIGHT)
81
+ ruler_drawing.move_to(tick_pixel_position.floor, 20 + RULER_TEXT_HEIGHT)
82
+ ruler_drawing.show_text(tick.to_i.to_s)
83
+ else
84
+ ruler_drawing.rel_line_to(0, 5)
85
+
86
+ end
87
+ ruler_drawing.stroke
88
+ end
89
+
90
+
91
+ @height = 25 + RULER_TEXT_HEIGHT
92
+ end
93
+ end #Ruler
94
+ end #Panel
95
+ end #Graphics
96
+ end #Bio