bio-svgenes 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.DS_Store +0 -0
- data/.document +5 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/bio-svgenes.gemspec +83 -0
- data/examples/.DS_Store +0 -0
- data/examples/annotate_snps.rb +86 -0
- data/examples/data.txt +192 -0
- data/examples/draw_from_json.rb +36 -0
- data/examples/drawn_from_json.svg +269 -0
- data/examples/drawn_from_json2.svg +467 -0
- data/examples/example.rb +291 -0
- data/examples/example.svg +301 -0
- data/examples/example_config.json +49 -0
- data/examples/gene.gff +4 -0
- data/examples/get_coverage_in_windows.rb +176 -0
- data/examples/transcripts.gff +16 -0
- data/lib/.DS_Store +0 -0
- data/lib/bio-svgenes.rb +17 -0
- data/lib/bio/.DS_Store +0 -0
- data/lib/bio/graphics/glyph.rb +241 -0
- data/lib/bio/graphics/mini_feature.rb +13 -0
- data/lib/bio/graphics/page.rb +265 -0
- data/lib/bio/graphics/primitive.rb +17 -0
- data/lib/bio/graphics/svgee.rb +129 -0
- data/lib/bio/graphics/track.rb +49 -0
- data/test/helper.rb +18 -0
- data/test/test_bio-svgenes.rb +7 -0
- metadata +138 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
class MiniFeature
|
2
|
+
attr_accessor :start, :end, :strand, :exons, :utrs, :block_gaps, :segment_height, :id
|
3
|
+
def initialize(args)
|
4
|
+
@start = args[:start]
|
5
|
+
@end = args[:end]
|
6
|
+
@strand = args[:strand]
|
7
|
+
@exons = args[:exons] || []
|
8
|
+
@utrs = args[:utrs] || [] #start, ennd, strand, arg[:exons], arg[:utrs]
|
9
|
+
@block_gaps = []
|
10
|
+
@id = args[:id]
|
11
|
+
@segment_height = args[:segment_height]
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,265 @@
|
|
1
|
+
class Page
|
2
|
+
|
3
|
+
def initialize(args)
|
4
|
+
@height = args[:height]
|
5
|
+
@width = args[:width]
|
6
|
+
@svg = SVGEE.new(args)
|
7
|
+
@scale_start = 1.0/0.0
|
8
|
+
@scale_stop = -1.0/0.0
|
9
|
+
@tracks = [] #array of track objects with loads of features in it...
|
10
|
+
@nt_per_percent = 1;
|
11
|
+
@num_intervals = args[:number_of_intervals]
|
12
|
+
@track_top = 30
|
13
|
+
|
14
|
+
def @svg.update_height(height)
|
15
|
+
@height = height
|
16
|
+
end
|
17
|
+
|
18
|
+
def @svg.update_width(width)
|
19
|
+
@width = width
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.from_json(args)
|
24
|
+
require 'rubygems'
|
25
|
+
require 'JSON'
|
26
|
+
data = JSON.parse(File.open(args[:json], 'r').read)
|
27
|
+
p = Page.new(:width => data["Page"]["width"],
|
28
|
+
:height => data["Page"]["height"],
|
29
|
+
:number_of_intervals => data["Page"]["intervals"])
|
30
|
+
data["Tracks"].each do |track|
|
31
|
+
#prep the track args
|
32
|
+
track_args = track.dup
|
33
|
+
track_args.delete("file")
|
34
|
+
track_args.delete("file_type")
|
35
|
+
track_args = track_args.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
36
|
+
##convert any of the pre-made gradient strings in the keys to symbol
|
37
|
+
track_args.each_key do |k|
|
38
|
+
next unless track_args[k].respond_to?(:to_sym)
|
39
|
+
track_args[k] = track_args[k].to_sym if Glyph.gradients.include?(track_args[k].to_sym)
|
40
|
+
end
|
41
|
+
|
42
|
+
svg_track = p.add_track(track_args)
|
43
|
+
features = [] ##set up the features...
|
44
|
+
|
45
|
+
case track["file_type"] ##deal with the gff and data files
|
46
|
+
when "gff"
|
47
|
+
groups = {}
|
48
|
+
parentless_features = []
|
49
|
+
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
|
50
|
+
parent_id = gff.attributes.select { |a| a.first == 'Parent'}
|
51
|
+
if parent_id.empty?
|
52
|
+
parentless_features << gff
|
53
|
+
else
|
54
|
+
if groups[parent_id.first.last].nil?
|
55
|
+
groups[parent_id.first.last] = []
|
56
|
+
groups[parent_id.first.last] << gff
|
57
|
+
else
|
58
|
+
groups[parent_id.first.last] << gff
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
#now flick through the parentless features and add any exons / UTRs
|
63
|
+
parentless_features.each do |plf|
|
64
|
+
gff_id = plf.attributes.select {|a| a.first == 'ID'}
|
65
|
+
gff_id = gff_id.first.last
|
66
|
+
exons = []
|
67
|
+
utrs = []
|
68
|
+
children = groups[gff_id] || children = []
|
69
|
+
children.each do |child|
|
70
|
+
if child.feature == 'exon' or child.feature == 'CDS'
|
71
|
+
exons += [child.start,child.end]
|
72
|
+
elsif child.feature =~ /utr/i
|
73
|
+
utrs += [child.start,child.end]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
features << MiniFeature.new(:start => plf.start, :end => plf.end, :exons => exons, :utrs => utrs, :strand => plf.strand, :id => gff_id)
|
77
|
+
end #parentless features end
|
78
|
+
when "data"
|
79
|
+
##each line is a data feature
|
80
|
+
File.open(track["file"], "r").each do |line|
|
81
|
+
start,stop,value = line.split(/\t/)
|
82
|
+
features << MiniFeature.new(:start => start.to_i, :end => stop.to_i, :segment_height => value.to_f)
|
83
|
+
end
|
84
|
+
end #data end
|
85
|
+
features.each {|f| svg_track.add(f) }
|
86
|
+
end #track end
|
87
|
+
p.write(args[:outfile])
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.parse_gff(gff_file)
|
91
|
+
require 'bio'
|
92
|
+
a = []
|
93
|
+
File.open( gff_file ).each do |line|
|
94
|
+
next if line =~ /^#/
|
95
|
+
a << Bio::GFF::GFF3::Record.new(line)
|
96
|
+
end
|
97
|
+
a
|
98
|
+
end
|
99
|
+
|
100
|
+
def add_track(args)
|
101
|
+
#sort out the colour/gradient options
|
102
|
+
[:fill_color, :exon_fill_color, :utr_fill_color].each do |colour_tag|
|
103
|
+
if Glyph.gradients.include?(args[colour_tag])
|
104
|
+
@svg.gradient(Glyph.gradient(args[colour_tag]) )
|
105
|
+
args[colour_tag] = "url(##{args[colour_tag]})"
|
106
|
+
elsif
|
107
|
+
args[colour_tag].instance_of?(Hash)
|
108
|
+
@svg.gradient(args[colour_tag])
|
109
|
+
args[colour_tag] = "url(##{args[colour_tag][:id]})"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
@tracks << Track.new(args)
|
113
|
+
return @tracks.last
|
114
|
+
end
|
115
|
+
|
116
|
+
def get_limits
|
117
|
+
@tracks.each do |track|
|
118
|
+
lowest = track.features.sort {|x,y| x.start <=> y.start}.first.start
|
119
|
+
highest = track.features.sort {|x,y| y.end <=> x.end}.first.end
|
120
|
+
@scale_start = lowest if lowest < @scale_start
|
121
|
+
@scale_stop = highest if highest > @scale_stop
|
122
|
+
@nt_per_px_x = (@scale_stop - @scale_start).to_f / @width.to_f
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def draw_scale
|
127
|
+
Glyph.scale(:start => @scale_start,
|
128
|
+
:stop => @scale_stop,
|
129
|
+
:number_of_intervals => @num_intervals, :page_width => @width).each {|g| @svg.add_primitive(g)}
|
130
|
+
end
|
131
|
+
|
132
|
+
def draw_label(args)
|
133
|
+
Glyph.label(:text => args[:text],
|
134
|
+
:x => args[:x],
|
135
|
+
:y => args[:y] ).each {|g| @svg.add_primitive(g)}
|
136
|
+
end
|
137
|
+
|
138
|
+
def draw_generic(args) #remember presentation info comes from track@args when the track is defined
|
139
|
+
Glyph.generic(args).each {|g| @svg.add_primitive(g) }
|
140
|
+
end
|
141
|
+
|
142
|
+
def draw_directed(args)
|
143
|
+
Glyph.directed(args).each {|g| @svg.add_primitive(g) }
|
144
|
+
end
|
145
|
+
|
146
|
+
def draw_transcript(args)
|
147
|
+
Glyph.transcript(args).each {|g| @svg.add_primitive(g) }
|
148
|
+
end
|
149
|
+
|
150
|
+
def draw_histogram(args)
|
151
|
+
Glyph.generic(args).each {|g| @svg.add_primitive(g) }
|
152
|
+
end
|
153
|
+
|
154
|
+
def draw_features(track) #sort out the input information into a user friendly format..
|
155
|
+
if [:histogram, "histogram"].include?(track.glyph) #do different stuff for data tracks...
|
156
|
+
|
157
|
+
y = @track_top + (track.track_height)
|
158
|
+
max = track.max_y ? track.max_y : track.features.sort {|a,b| a.segment_height <=> b.segment_height}.last.segment_height
|
159
|
+
min = 0
|
160
|
+
if track.label
|
161
|
+
draw_label(:text => track.name, :y => @track_top += 30, :x => 3 )
|
162
|
+
end
|
163
|
+
draw_label(:text => max, :x => to_px(@scale_stop - @scale_start ) + 5, :y => @track_top + 5)
|
164
|
+
draw_label(:text => min, :x => to_px(@scale_stop - @scale_start ) + 5, :y => @track_top + track.track_height + 5)
|
165
|
+
data_interval = max - min
|
166
|
+
data_per_px = track.track_height.to_f / data_interval.to_f
|
167
|
+
track.features.each do |f|
|
168
|
+
x = to_px(f.start - @scale_start)
|
169
|
+
width = to_px( (f.end - @scale_start) - (f.start - @scale_start) )
|
170
|
+
height = f.segment_height.to_f * data_per_px
|
171
|
+
y = @track_top + track.track_height - height + 5
|
172
|
+
#$stderr.puts f.segment_height, data_per_px, data_interval, max, min, "------"
|
173
|
+
self.send("draw_#{track.glyph}", {:x => x,
|
174
|
+
:y => y,
|
175
|
+
:width => width,
|
176
|
+
:height => height }.merge!(track.args) )
|
177
|
+
end
|
178
|
+
@track_top += (track.track_height) + 20
|
179
|
+
else ##following stuff for the features
|
180
|
+
if track.label
|
181
|
+
draw_label(:text => track.name, :y => @track_top += 30, :x => 3 )
|
182
|
+
end
|
183
|
+
track.get_rows ##work out how many rows and which features belong in each row...
|
184
|
+
track.features.each_with_index do |f,index|
|
185
|
+
x = to_px(f.start - @scale_start) #bottom left of feature
|
186
|
+
all_sub_blocks = []
|
187
|
+
|
188
|
+
#convert the exon and utr start stops to px start stops and px widths
|
189
|
+
exons = []
|
190
|
+
if f.exons
|
191
|
+
f.exons.each_slice(2).each do |exon|
|
192
|
+
all_sub_blocks << exon
|
193
|
+
next if exon.nil?
|
194
|
+
exons << [to_px(exon[0] - @scale_start), to_px( (exon[1] - @scale_start) - (exon[0] - @scale_start) ) ]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
f.exons = exons
|
198
|
+
|
199
|
+
utrs = []
|
200
|
+
if f.utrs
|
201
|
+
f.utrs.each_slice(2).each do |utr|
|
202
|
+
all_sub_blocks << utr
|
203
|
+
next if utr.nil?
|
204
|
+
utrs << [to_px(utr[0] - @scale_start), to_px( (utr[1] - @scale_start) - (utr[0] - @scale_start) ) ]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
f.utrs = utrs
|
208
|
+
|
209
|
+
#if there are any intron like gaps.. get where they would be
|
210
|
+
if not all_sub_blocks.empty?
|
211
|
+
all_sub_blocks = all_sub_blocks.sort {|a,b| a.first <=> b.first}
|
212
|
+
all_sub_blocks.each_index do |i|
|
213
|
+
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
|
214
|
+
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) ) ]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
width = to_px( (f.end - @scale_start) - (f.start - @scale_start) )
|
219
|
+
|
220
|
+
y = @track_top + (track.feature_rows[index] * 2 * track.feature_height)
|
221
|
+
|
222
|
+
self.send("draw_#{track.glyph}", {:x => x,
|
223
|
+
:y => y,
|
224
|
+
:width => width,
|
225
|
+
:strand => f.strand,
|
226
|
+
:exons => f.exons,
|
227
|
+
:utrs => f.utrs,
|
228
|
+
:block_gaps => f.block_gaps,
|
229
|
+
:height => track.feature_height
|
230
|
+
}.merge!(track.args) )
|
231
|
+
|
232
|
+
if f.id
|
233
|
+
draw_label(:y => y, :x => x + width +2, :text => f.id)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
@track_top += (track.feature_height * track.number_rows * 2) + 20
|
237
|
+
end
|
238
|
+
|
239
|
+
@height = @track_top + 100 #fudge...
|
240
|
+
@svg.update_height(@height)
|
241
|
+
@svg.update_width(@width + 200)
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
def to_px(num)
|
246
|
+
(num.to_f / @nt_per_px_x.to_f)
|
247
|
+
end
|
248
|
+
|
249
|
+
def get_markup
|
250
|
+
get_limits
|
251
|
+
draw_scale
|
252
|
+
@tracks.each do |track|
|
253
|
+
draw_features(track)
|
254
|
+
end
|
255
|
+
@svg.draw
|
256
|
+
end
|
257
|
+
|
258
|
+
def draw
|
259
|
+
puts get_markup
|
260
|
+
end
|
261
|
+
|
262
|
+
def write(file)
|
263
|
+
File.open(file, 'w').write(get_markup)
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Primitive
|
2
|
+
attr_reader :primitive
|
3
|
+
|
4
|
+
def initialize(primitive,args)
|
5
|
+
@primitive = primitive
|
6
|
+
args.each_key do |k|
|
7
|
+
self.instance_variable_set("@#{k}", args[k])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def update(args)
|
12
|
+
args.each_key do |k|
|
13
|
+
self.instance_variable_set("@#{k}", args[k])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
class SVGEE
|
2
|
+
attr_reader :primitives, :defs, :supported_primitives
|
3
|
+
def initialize(args)
|
4
|
+
opts = {:width => '100%', :height => '100%'}.merge!(args)
|
5
|
+
@width = opts[:width]
|
6
|
+
@height = opts[:height]
|
7
|
+
@style= opts[:style]
|
8
|
+
@primitives = []
|
9
|
+
@defs = []
|
10
|
+
@supported_primitives = [:circle, :rectangle, :ellipse, :line, :polyline, :text]
|
11
|
+
end
|
12
|
+
|
13
|
+
def open_tag
|
14
|
+
%{<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="#{@width}" height="#{@height}" style="#{@style}" xmlns:xlink="http://www.w3.org/1999/xlink">}
|
15
|
+
end
|
16
|
+
|
17
|
+
def close_tag
|
18
|
+
%{</svg>}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def add(primitive_string)
|
23
|
+
@primitives << primitive_string
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_def(definition_string)
|
27
|
+
@defs << definition_string
|
28
|
+
end
|
29
|
+
|
30
|
+
def make_tag(primitive, args)
|
31
|
+
if args.has_key?(:link)
|
32
|
+
add link_and_tag(primitive, args)
|
33
|
+
else
|
34
|
+
add self.send("#{primitive}_tag", args)
|
35
|
+
end
|
36
|
+
return Primitive.new(primitive, args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def link_and_tag(primitive, args)
|
40
|
+
%{<a xlink:href="#{args[:link][:href]}" target="#{args[:link][:target]}">} + self.send("#{primitive}_tag", args) + %{</a>}
|
41
|
+
end
|
42
|
+
|
43
|
+
def circle_tag(a = {})
|
44
|
+
%{<circle cx="#{a[:x_center]}" cy="#{a[:y_center]}" r="#{a[:radius]}" fill="#{a[:fill_color]}" } + common_attributes(a) + %q{/>}
|
45
|
+
end
|
46
|
+
|
47
|
+
def rectangle_tag(a = {})
|
48
|
+
%{<rect x="#{a[:x]}" y="#{a[:y]}" width="#{a[:width]}" height="#{a[:height]}" fill="#{a[:fill_color]}" rx="#{a[:x_round]}" ry="#{a[:y_round]}" } + common_attributes(a) + %q{/>}
|
49
|
+
end
|
50
|
+
|
51
|
+
def ellipse_tag(a = {})
|
52
|
+
%{<ellipse cx="#{a[:x_center]}" cy="#{a[:y_center]}" rx="#{a[:x_radius]}" ry="#{a[:y_radius]}" stroke="#{a[:stroke]}" stroke-width="#{a[:stroke_width]}" fill="#{a[:fill_color]}" style="#{a[:style]}" />}
|
53
|
+
end
|
54
|
+
|
55
|
+
def line_tag(a = {})
|
56
|
+
%{<line x1="#{a[:x1]}" y1="#{a[:y1]}" x2="#{a[:x2]}" y2="#{a[:y2]}" } + common_attributes(a) + %q{/>}
|
57
|
+
end
|
58
|
+
|
59
|
+
def polyline_tag(a={})
|
60
|
+
%{<polyline points="#{a[:points]}" fill="#{a[:fill]}" } + common_attributes(a) + %{ />}
|
61
|
+
end
|
62
|
+
|
63
|
+
def polygon_tag(a={})
|
64
|
+
%{<polygon points="#{a[:points]}" fill="#{a[:fill_color]}" } + common_attributes(a) + %{ />}
|
65
|
+
end
|
66
|
+
|
67
|
+
def text_tag(a = {})
|
68
|
+
%{<text x="#{a[:x]}" y="#{a[:y]}" fill="#{a[:fill]}" transform="#{a[:transform]}" style="#{a[:style]}">#{a[:text]}</text>}
|
69
|
+
end
|
70
|
+
|
71
|
+
def common_attributes(a={})
|
72
|
+
%{stroke="#{a[:stroke]}" stroke-width="#{a[:stroke_width]}" style="#{a[:style]}"}
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_missing(primitive, args={}) #only used to dynamically select the primitive type..
|
76
|
+
raise NoMethodError if not self.supported_primitives.include?(primitive) #we're only doing the listed primitive types...
|
77
|
+
self.send "make_tag", primitive, args
|
78
|
+
end
|
79
|
+
|
80
|
+
public
|
81
|
+
def add_primitive(primitive_object) #adds a primitive object to the SVG
|
82
|
+
args = {}
|
83
|
+
primitive_object.instance_variables.each{|v| args[v.gsub(/@/,"").to_sym] = primitive_object.instance_variable_get(v) }
|
84
|
+
primitive_string = args.delete(:primitive)
|
85
|
+
make_tag(primitive_string, args)
|
86
|
+
end
|
87
|
+
|
88
|
+
def gradient(a)
|
89
|
+
definition_string = case a[:type]
|
90
|
+
when :radial
|
91
|
+
%{<radialGradient id="#{a[:id]}" cx="#{a[:cx]}%" cy="#{a[:cy]}%" r="#{a[:r]}%" fx="#{a[:fx]}%" fy="#{a[:fy]}%">}
|
92
|
+
else
|
93
|
+
%{<linearGradient id="#{a[:id]}" x1="#{a[:x1]}%" x2="#{a[:x2]}%" y1="#{a[:y1]}%" y2="#{a[:y2]}%">}
|
94
|
+
end
|
95
|
+
a[:stops].each do |s|
|
96
|
+
definition_string = definition_string + "\n" + %{<stop offset="#{s[:offset]}%" style="stop-color:#{s[:color]};stop-opacity:#{s[:opacity]}" />}
|
97
|
+
end
|
98
|
+
add_def definition_string + (a[:type] == :linear ? '</linearGradient>' : '</radialGradient>')
|
99
|
+
end
|
100
|
+
|
101
|
+
def draw
|
102
|
+
head = self.open_tag
|
103
|
+
defstring = ""
|
104
|
+
defstring = "<defs>\n" + self.defs.join("\n") + "\n </defs>\n" if not defs.empty?
|
105
|
+
shapes = self.primitives.join("\n")
|
106
|
+
close = self.close_tag
|
107
|
+
head + defstring + shapes + close
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
=begin How to use SVGEE
|
113
|
+
s = SVGEE.new
|
114
|
+
s.gradient(:radial => "grad1", :cx => 50, :cy => 50, :r => 50, :fx => 50, :fy => 50, :stops => [ {:offset => 0, :color => 'rgb(255,255,255)', :opacity => 0}, {:offset => 100, :color => 'rgb(0,0,255)', :opacity => 1},] )
|
115
|
+
s.circle(:x_center => 40, :y_center => 40, :radius => 20, :fill_color => "url(#grad1)")
|
116
|
+
s.circle(:x_center => 250, :y_center => 250, :radius => 20, :fill_color => "url(#grad1)", :link => {:href => "http://www.bbc.co.uk"})
|
117
|
+
s.rectangle(:x => 125, :y => 125, :width => 100, :height => 50, :fill_color => 'red', :stroke => 'black', :stroke_width => 2, :style => "fill-opacity:0.1;stroke-opacity:0.9")
|
118
|
+
s.rectangle(:x => 125, :y => 125, :round_x => 5, :round_y => 5, :width => 100, :height => 50, :fill_color => 'red', :stroke => 'black', :stroke_width => 2, :style => "fill-opacity:0.1;stroke-opacity:0.9")
|
119
|
+
prim = s.ellipse(:x_center => 100, :y_center => 190, :x_radius => 40, :y_radius => 20, :fill_color => 'green', :stroke => 'black')
|
120
|
+
s.line(:x1 => 10, :y1 => 10, :x2 => 145, :y2 => 145, :stroke_width => 5, :stroke => 'blue')
|
121
|
+
s.polyline(:points => "2,2 400,440 600,440", :stroke_width => 10, :stroke => "#f00", :fill => "none")
|
122
|
+
|
123
|
+
s.text(:x => 100, :y => 100, :fill => 'red', :text => "Look! A circle!", :style => "letter-spacing:2;font-family:Arial")
|
124
|
+
s.text(:x => 100, :y => 400, :fill => 'green', :text => "This one is a link", :link => {:href => "http://www.bbc.co.uk"})
|
125
|
+
prim.update(:x_center => 200)
|
126
|
+
s.add_primitive(prim) #add one of the returned, updated Primitive objects
|
127
|
+
|
128
|
+
s.draw
|
129
|
+
=end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Track
|
2
|
+
attr_reader :glyph, :name, :label, :args, :track_height, :scale, :max_y
|
3
|
+
attr_accessor :features, :feature_rows, :name, :number_rows, :feature_height
|
4
|
+
def initialize(args)
|
5
|
+
@args = {:glyph => :generic,
|
6
|
+
:name => "feature_track",
|
7
|
+
:label => true,
|
8
|
+
:feature_height => 10,
|
9
|
+
:track_height => nil }.merge!(args)
|
10
|
+
@glyph = @args[:glyph]
|
11
|
+
@name = @args[:name]
|
12
|
+
@label = @args[:label]
|
13
|
+
@track_height = @args[:track_height]
|
14
|
+
@features = []
|
15
|
+
@feature_rows = []
|
16
|
+
@scale = @args[:scale]
|
17
|
+
@feature_height = @args[:feature_height]
|
18
|
+
@number_of_rows = 1
|
19
|
+
@max_y = args[:max_y]
|
20
|
+
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def add(feature)
|
26
|
+
@features << feature
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_rows
|
30
|
+
#works out how many rows are needed per track for overlapping features
|
31
|
+
#and which row each feature should be in
|
32
|
+
current_row = 1
|
33
|
+
@feature_rows = Array.new(@features.length,1)
|
34
|
+
@features.each_with_index do |f1, i|
|
35
|
+
@features.each_with_index do |f2, j|
|
36
|
+
next if i == j or j <= i
|
37
|
+
if overlaps(f1,f2)
|
38
|
+
@feature_rows[i] += 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
@number_rows = @feature_rows.max
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def overlaps(f1, f2)
|
46
|
+
(f1.start >= f2.start and f1.start <= f2.end) or (f1.end >= f2.start and f1.end <= f2.end)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|