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,49 @@
|
|
1
|
+
{
|
2
|
+
"Page":{"width":800, "height":800, "intervals":15},
|
3
|
+
"Tracks": [
|
4
|
+
{
|
5
|
+
"glyph":"generic",
|
6
|
+
"name":"Genes",
|
7
|
+
"label":true,
|
8
|
+
"file":"gene.gff",
|
9
|
+
"file_type":"gff",
|
10
|
+
"fill_color":"red",
|
11
|
+
"stroke_width":1,
|
12
|
+
"x_round":4,
|
13
|
+
"y_round":4
|
14
|
+
},
|
15
|
+
{
|
16
|
+
"glyph":"transcript",
|
17
|
+
"name":"Transcripts",
|
18
|
+
"label":true,
|
19
|
+
"file":"transcripts.gff",
|
20
|
+
"file_type":"gff",
|
21
|
+
"exon_fill_color":"green_white_radial",
|
22
|
+
"utr_fill_color":"blue_white_radial",
|
23
|
+
"stroke_width":1,
|
24
|
+
"gap_marker":"angled"
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"glyph":"histogram",
|
28
|
+
"name":"Data",
|
29
|
+
"label":true,
|
30
|
+
"file":"data.txt",
|
31
|
+
"file_type":"data",
|
32
|
+
"track_height":100,
|
33
|
+
"fill_color":"yellow_white_radial",
|
34
|
+
"stroke_width":1
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"glyph":"histogram",
|
38
|
+
"name":"Data with maxed-out scale",
|
39
|
+
"label":true,
|
40
|
+
"file":"data.txt",
|
41
|
+
"file_type":"data",
|
42
|
+
"track_height":100,
|
43
|
+
"fill_color":"red_white_radial",
|
44
|
+
"stroke_width":1,
|
45
|
+
"max_y":1000
|
46
|
+
}
|
47
|
+
]
|
48
|
+
}
|
49
|
+
|
data/examples/gene.gff
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# get_coverage_in_windows.rb
|
4
|
+
#
|
5
|
+
# Created by Dan MacLean (TSL) on 2012-10-05.
|
6
|
+
# Copyright (c) . All rights reserved.
|
7
|
+
###################################################
|
8
|
+
require 'optparse'
|
9
|
+
require 'pp'
|
10
|
+
options = {}
|
11
|
+
optparse = OptionParser.new do|opts|
|
12
|
+
|
13
|
+
opts.on( '-b', '--bam FILE', ' name of sorrted BAM file' ) do |b|
|
14
|
+
options[:bam] = b
|
15
|
+
|
16
|
+
end
|
17
|
+
opts.on( '-f', '--reference_file STRING', 'name of the reference sequence fasta file' ) do |f|
|
18
|
+
options[:fasta] = f
|
19
|
+
|
20
|
+
end
|
21
|
+
opts.on( '-c', '--chromosome STRING', 'name of the chromosome / contig in the fasta file' ) do |c|
|
22
|
+
options[:chrom] = c
|
23
|
+
|
24
|
+
end
|
25
|
+
opts.on( '-s', '--start INT', 'start position)' ) do |s|
|
26
|
+
options[:start] = s.to_i
|
27
|
+
|
28
|
+
end
|
29
|
+
opts.on( '-e', '--stop INT', 'stop position' ) do |e|
|
30
|
+
options[:stop] = e.to_i
|
31
|
+
|
32
|
+
end
|
33
|
+
opts.on( '-w', '--step INT', 'window size to use' ) do |w|
|
34
|
+
options[:step] = w.to_i
|
35
|
+
|
36
|
+
end
|
37
|
+
opts.on( '-o', '--bam_opts INT', 'bio-samtools BAM opts' ) do |o|
|
38
|
+
options[:bam_opts] = o ||= {:min_mapping_quality => 20, :min_base_quality => 30}
|
39
|
+
|
40
|
+
end
|
41
|
+
# This displays the help screen, all programs are
|
42
|
+
# assumed to have this option.
|
43
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
44
|
+
puts opts
|
45
|
+
puts %{Example: ruby get_coverage_in_windows.rb --bam my_bam.bam --reference_file ref.fasta --chromosome contig1 --start 2000 --stop 3000 --step 100 --bam_opts {:min_mapping_quality => 20, :min_base_quality => 20}}
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
end
|
49
|
+
optparse.parse!
|
50
|
+
|
51
|
+
options[:bam_opts] = {:min_mapping_quality => 20}
|
52
|
+
|
53
|
+
|
54
|
+
###Actual script ---
|
55
|
+
require 'rubygems'
|
56
|
+
require 'bio-samtools'
|
57
|
+
require 'enumerator'
|
58
|
+
bam = Bio::DB::Sam.new({:bam=>options[:bam], :fasta=>options[:fasta]} )
|
59
|
+
bam.open
|
60
|
+
|
61
|
+
def passes_quality(aln, options)
|
62
|
+
if aln.mapq > options[:bam_opts][:min_mapping_quality] && aln.is_mapped
|
63
|
+
true
|
64
|
+
else
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def actually_modify(cigar_arr, bases_to_ignore_at_start)
|
70
|
+
result = []
|
71
|
+
bases_ditched = 0
|
72
|
+
cigar_arr.each do |arr|
|
73
|
+
if arr[0] < bases_to_ignore_at_start - bases_ditched
|
74
|
+
bases_ditched += arr[0]
|
75
|
+
elsif arr[0] + bases_ditched >= bases_to_ignore_at_start && bases_ditched < bases_to_ignore_at_start
|
76
|
+
remainder = arr[0] - (bases_to_ignore_at_start - bases_ditched)
|
77
|
+
bases_ditched += (bases_to_ignore_at_start - bases_ditched)
|
78
|
+
result << [remainder, arr[1]] unless remainder == 0
|
79
|
+
elsif bases_ditched >= bases_to_ignore_at_start
|
80
|
+
result << [arr[0],arr[1]]
|
81
|
+
else
|
82
|
+
raise "Shouldn't ever happen. Did though... Panic. Alarums. Etc."
|
83
|
+
exit
|
84
|
+
end
|
85
|
+
end
|
86
|
+
result
|
87
|
+
end
|
88
|
+
|
89
|
+
def correct_cigar_for_overlaps(cigar_arr, bases_to_ignore_at_start, bases_to_ignore_at_end)
|
90
|
+
#takes the end off the cigar arr if the bases it describes are outside the window
|
91
|
+
return cigar_arr if bases_to_ignore_at_start == 0 && bases_to_ignore_at_end == 0
|
92
|
+
result = actually_modify(cigar_arr,bases_to_ignore_at_start)
|
93
|
+
actually_modify(result.reverse, bases_to_ignore_at_end).reverse
|
94
|
+
end
|
95
|
+
|
96
|
+
(options[:start]..options[:stop]).step(options[:step]) do |start|
|
97
|
+
positions = Array.new(options[:step], 0.0)
|
98
|
+
|
99
|
+
bam.fetch(options[:chrom], start, start + options[:step]).each do |aln|
|
100
|
+
next unless passes_quality(aln, options)
|
101
|
+
|
102
|
+
#update start and end for overhanging clipped bases
|
103
|
+
a = aln.cigar.split(/([A-Z])/)
|
104
|
+
cigar_arr = a.enum_for(:each_slice, 2).to_a #convert to array of arrays
|
105
|
+
cigar_arr.collect!{|a| [a.first.to_i, a.last] } #convert numbers to ints
|
106
|
+
if cigar_arr.first.last == "S" or cigar_arr.first.last == "H"
|
107
|
+
aln.pos = aln.pos - cigar_arr.first.first
|
108
|
+
aln.pos = 1 if aln.pos < 1 #ignore negative overhangs...
|
109
|
+
end
|
110
|
+
|
111
|
+
if cigar_arr.last.last == "S" or cigar_arr.last.last == "H"
|
112
|
+
aln.calend = aln.calend - cigar_arr.last.first
|
113
|
+
aln.calend = 1 if aln.calend < 1
|
114
|
+
end
|
115
|
+
|
116
|
+
##remove any padding operations, we're only concerned with alignments relative to the reference, not other reads..
|
117
|
+
cigar_arr.delete_if {|b| b.last == "P"}
|
118
|
+
#need to know whether start outside window ...
|
119
|
+
bases_to_ignore_at_start = 0
|
120
|
+
bases_to_ignore_at_end = 0
|
121
|
+
|
122
|
+
if aln.pos < start
|
123
|
+
bases_to_ignore_at_start = start - aln.pos
|
124
|
+
end
|
125
|
+
|
126
|
+
if aln.calend >= start + options[:step] ## left hand border is included in window, right hand border excluded
|
127
|
+
bases_to_ignore_at_end = aln.calend - (start + options[:step]) + 1
|
128
|
+
end
|
129
|
+
|
130
|
+
cigar_arr = correct_cigar_for_overlaps(cigar_arr.dup, bases_to_ignore_at_start, bases_to_ignore_at_end)
|
131
|
+
#puts "start ignore = #{bases_to_ignore_at_start}"
|
132
|
+
#puts "end ignore = #{bases_to_ignore_at_end}"
|
133
|
+
#modify the read objects start and stop positions to keep only the bit in the window
|
134
|
+
#ignore soft clipping at the start and end
|
135
|
+
if bases_to_ignore_at_start >0
|
136
|
+
aln.pos = aln.pos + bases_to_ignore_at_start
|
137
|
+
end
|
138
|
+
if bases_to_ignore_at_end > 0
|
139
|
+
aln.calend = aln.calend - bases_to_ignore_at_end
|
140
|
+
end
|
141
|
+
|
142
|
+
begin
|
143
|
+
raise "aln start >= aln end" if aln.pos > aln.calend
|
144
|
+
rescue
|
145
|
+
next
|
146
|
+
else
|
147
|
+
read_start_corrected = aln.pos - start
|
148
|
+
read_end_corrected = aln.calend - start
|
149
|
+
range_start = read_start_corrected
|
150
|
+
range_stop = read_end_corrected
|
151
|
+
positions[range_start..range_stop] = positions.slice(range_start..range_stop).collect! {|a| a += 1}
|
152
|
+
ranges_to_decrement = []
|
153
|
+
dist_through_read = 0
|
154
|
+
cigar_arr.each_with_index do |cigar,i|
|
155
|
+
#use the corrected cigar array to correct the coverage post-hoc
|
156
|
+
if cigar.last == "D" or cigar.last == "N"
|
157
|
+
ranges_to_decrement << Range.new(read_start_corrected + dist_through_read, read_start_corrected + dist_through_read + cigar.first - 1)
|
158
|
+
end
|
159
|
+
dist_through_read += cigar.first
|
160
|
+
end #end cigar_arr each
|
161
|
+
#pp ranges_to_decrement unless ranges_to_decrement.empty?
|
162
|
+
ranges_to_decrement.each do |range|
|
163
|
+
begin
|
164
|
+
positions[range] = positions.slice(range).collect! {|b| b -= 1}
|
165
|
+
rescue
|
166
|
+
puts "Error: positions array likely out of range ... this error affects one read only ... so we'll skip it. Panic if you get lots of this"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end #end fetch
|
171
|
+
average = positions.inject(0) {|r,e| r + e} / positions.length.to_f
|
172
|
+
puts [start, start + options[:step], average].join("\t")
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
bam.close
|
@@ -0,0 +1,16 @@
|
|
1
|
+
##gff-version 3
|
2
|
+
##sequence-region ctg123 1 1497228
|
3
|
+
ctg123 . mRNA 900 9200 . + . ID=mRNA00001;Name=EDEN.1
|
4
|
+
ctg123 . utr 900 1000 . + . ID=utr00001;Parent=mRNA00001
|
5
|
+
ctg123 . exon 1050 1500 . + . ID=exon00002;Parent=mRNA00001
|
6
|
+
ctg123 . exon 3000 3902 . + . ID=exon00003;Parent=mRNA00001
|
7
|
+
ctg123 . exon 5000 5500 . + . ID=exon00004;Parent=mRNA00001
|
8
|
+
ctg123 . exon 7000 9000 . + . ID=exon00005;Parent=mRNA00001
|
9
|
+
ctg123 . utr 9100 9200 . + . ID=utr00002;Parent=mRNA00001
|
10
|
+
ctg123 . mRNA 10900 19200 . - . ID=mRNA00002;Name=EDEN2.1
|
11
|
+
ctg123 . utr 10900 11000 . - . ID=utr00003;Parent=mRNA00002
|
12
|
+
ctg123 . exon 11050 11500 . - . ID=exon00006;Parent=mRNA00002
|
13
|
+
ctg123 . exon 13000 13902 . - . ID=exon00007;Parent=mRNA00002
|
14
|
+
ctg123 . exon 15000 15500 . - . ID=exon00008;Parent=mRNA00002
|
15
|
+
ctg123 . exon 17000 19000 . - . ID=exon00009;Parent=mRNA00002
|
16
|
+
ctg123 . utr 19100 19200 . - . ID=utr00004;Parent=mRNA00002
|
data/lib/.DS_Store
ADDED
Binary file
|
data/lib/bio-svgenes.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Please require your code below, respecting the bioruby directory tree.
|
2
|
+
# For instance, perhaps the only uncommented line in this file might
|
3
|
+
# be something like this:
|
4
|
+
#
|
5
|
+
# require 'bio/sequence/awesome_sequence_plugin_thingy'
|
6
|
+
#
|
7
|
+
# and then create the ruby file 'lib/bio/sequence/awesome_sequence_thingy.rb'
|
8
|
+
# and put your plugin's code there. It is bad practice to write other code
|
9
|
+
# directly into this file, because doing so causes confusion if this biogem
|
10
|
+
# was ever to get merged into the main bioruby tree.
|
11
|
+
|
12
|
+
require 'bio/graphics/svgee'
|
13
|
+
require 'bio/graphics/glyph'
|
14
|
+
require 'bio/graphics/page'
|
15
|
+
require 'bio/graphics/track'
|
16
|
+
require 'bio/graphics/mini_feature'
|
17
|
+
require 'bio/graphics/primitive'
|
data/lib/bio/.DS_Store
ADDED
Binary file
|
@@ -0,0 +1,241 @@
|
|
1
|
+
class Glyph
|
2
|
+
attr_reader :glyphs
|
3
|
+
#holds a load of definitions for glyphs .. a glyph is an array of primitives...
|
4
|
+
@glyphs = [:generic, :directed, :transcript, :scale, :label, :histogram]
|
5
|
+
|
6
|
+
def self.generic(args) #:x, :y, :width :fill, :stroke :stroke_width, :style, :height,
|
7
|
+
args = {
|
8
|
+
:height => 10,
|
9
|
+
:fill_color => 'red',
|
10
|
+
:stroke => "black",
|
11
|
+
:stroke_width => 1,
|
12
|
+
:x_round => 1,
|
13
|
+
:y_round => 1,
|
14
|
+
:style => "fill-opacity:0.4;"}.merge!(args)
|
15
|
+
[Primitive.new(:rectangle, args)]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.directed(args) #:x, :y, :width :fill, :stroke :stroke_width, :style, :height
|
19
|
+
args = {
|
20
|
+
|
21
|
+
:height => 10,
|
22
|
+
:fill_color => 'red',
|
23
|
+
:stroke => "black",
|
24
|
+
:stroke_width => 1,
|
25
|
+
:style => "fill-opacity:0.4;"}.merge!(args)
|
26
|
+
|
27
|
+
if args[:strand] == '-'
|
28
|
+
args[:points] = "#{args[:x]},#{args[:y]} #{args[:x] + args[:width]},#{args[:y]} #{args[:x] + args[:width]},#{args[:y] + args[:height] } #{args[:x]},#{args[:y] + (args[:height])} #{args[:x] - (args[:height] * 0.2)},#{args[:y] + (args[:height]/2)}"
|
29
|
+
else
|
30
|
+
args[:points] = "#{args[:x]},#{args[:y]} #{args[:x] + args[:width] - (args[:height] * 0.2)},#{args[:y]} #{args[:x] + args[:width]},#{args[:y] + (args[:height]/2) } #{args[:x] + args[:width] - (args[:height] * 0.2)},#{args[:y] + args[:height]} #{args[:x]},#{args[:y] + args[:height]}"
|
31
|
+
end
|
32
|
+
[Primitive.new(:polygon, args)]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.transcript(args)
|
36
|
+
args = {
|
37
|
+
:height => 10,
|
38
|
+
:utr_fill_color => 'black',
|
39
|
+
:utr_stroke => "black",
|
40
|
+
:utr_stroke_width => 1,
|
41
|
+
:exon_fill_color => 'red',
|
42
|
+
:exon_stroke => "black",
|
43
|
+
:exon_stroke_width => 1,
|
44
|
+
:line_color => 'black',
|
45
|
+
:line_width => 1,
|
46
|
+
:exon_style => "fill-opacity:0.4;",
|
47
|
+
:utr_style => "",
|
48
|
+
:line_style => "",
|
49
|
+
:block_gaps => "",
|
50
|
+
:gap_marker => ""
|
51
|
+
}.merge!(args)
|
52
|
+
|
53
|
+
composite = []
|
54
|
+
|
55
|
+
if not args[:utrs].empty? ##draw the utr as terminal element...##terminal utr is the one with the point on...
|
56
|
+
x,width = args[:strand] == '-' ? args[:utrs].shift : args[:utrs].pop
|
57
|
+
composite += Glyph.directed(:x => x,
|
58
|
+
:y => args[:y],
|
59
|
+
:width => width,
|
60
|
+
:strand => args[:strand],
|
61
|
+
:height => args[:height],
|
62
|
+
:fill_color => args[:utr_fill_color],
|
63
|
+
:stroke =>args[:utr_stroke],
|
64
|
+
:stroke_width => args[:utr_stroke_width],
|
65
|
+
:style => args[:utr_style] )
|
66
|
+
##draw the other(s!)
|
67
|
+
args[:utrs].each do |utr|
|
68
|
+
composite << Primitive.new(:rectangle, {
|
69
|
+
:x => utr.first,
|
70
|
+
:width => utr.last,
|
71
|
+
:y => args[:y],
|
72
|
+
:height => args[:height],
|
73
|
+
:fill_color => args[:utr_fill_color],
|
74
|
+
:stroke =>args[:utr_stroke],
|
75
|
+
:stroke_width => args[:utr_stroke_width],
|
76
|
+
:style => args[:utr_style]} )
|
77
|
+
end
|
78
|
+
else ## draw the terminal exon as terminal element
|
79
|
+
points = nil
|
80
|
+
x,width = args[:strand] == '-' ? args[:exons].shift : args[:exons].pop
|
81
|
+
composite += Glyph.directed(:x => x,
|
82
|
+
:y => args[:y],
|
83
|
+
:width => width,
|
84
|
+
:strand => args[:strand],
|
85
|
+
:height => args[:height],
|
86
|
+
:fill_color => args[:exon_fill_color],
|
87
|
+
:stroke =>args[:exon_stroke],
|
88
|
+
:stroke_width => args[:exon_stroke_width],
|
89
|
+
:style => args[:exon_style] )
|
90
|
+
end
|
91
|
+
#draw any remaining exons
|
92
|
+
args[:exons].each do |exon|
|
93
|
+
composite << Primitive.new(:rectangle, {
|
94
|
+
:x => exon[0],
|
95
|
+
:width => exon[1],
|
96
|
+
:y => args[:y],
|
97
|
+
:height => args[:height],
|
98
|
+
:fill_color => args[:exon_fill_color],
|
99
|
+
:stroke =>args[:exon_stroke],
|
100
|
+
:stroke_width => args[:exon_stroke_width],
|
101
|
+
:style => args[:exon_style]} )
|
102
|
+
end
|
103
|
+
if args[:gap_marker] == "angled" and not args[:block_gaps].empty?
|
104
|
+
args[:block_gaps].each do |gap|
|
105
|
+
points = "#{gap.first},#{args[:y] + (args[:height]/2) } #{gap.first + (gap.last/2)},#{args[:y]} #{gap.first + gap.last},#{args[:y] + (args[:height]/2)}"
|
106
|
+
composite << Primitive.new(:polyline, {
|
107
|
+
:points => points,
|
108
|
+
:stroke => args[:line_color],
|
109
|
+
:stroke_width => args[:line_width],
|
110
|
+
:fill => "none",
|
111
|
+
:line_style => args[:line_style]})
|
112
|
+
end
|
113
|
+
else
|
114
|
+
#add line
|
115
|
+
composite << Primitive.new(:line, {
|
116
|
+
:x1 => args[:x],
|
117
|
+
:x2 => "#{args[:x] + args[:width]}",
|
118
|
+
:y1 => args[:y] + (args[:height]/2),
|
119
|
+
:y2 => args[:y] + (args[:height]/2),
|
120
|
+
:stroke => args[:line_color],
|
121
|
+
:stroke_width => args[:line_width],
|
122
|
+
:line_style => args[:line_style]})
|
123
|
+
|
124
|
+
end
|
125
|
+
composite
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.scale(args)
|
129
|
+
|
130
|
+
|
131
|
+
first_mark = args[:start]
|
132
|
+
last_mark = args[:stop]
|
133
|
+
#(num.to_f / @nt_per_px_x.to_f)
|
134
|
+
full_dist = last_mark - first_mark
|
135
|
+
interval_width = full_dist / args[:number_of_intervals]
|
136
|
+
|
137
|
+
|
138
|
+
a = [Primitive.new(:line,
|
139
|
+
:stroke => 'black',
|
140
|
+
:stroke_width => 1,
|
141
|
+
:x1 => 1, :x2 => args[:page_width] * 1.1,
|
142
|
+
:y1 => "20", :y2 => "20" )]
|
143
|
+
|
144
|
+
|
145
|
+
marks = (first_mark..last_mark).step(interval_width).to_a
|
146
|
+
px_per_nt = full_dist / args[:page_width]
|
147
|
+
marks.each do |mark|
|
148
|
+
x = (mark.to_f - first_mark )/ px_per_nt
|
149
|
+
a << Primitive.new(:rectangle,
|
150
|
+
:x => x,
|
151
|
+
:y => 20,
|
152
|
+
:stroke => 'black',
|
153
|
+
:stroke_width => 1,
|
154
|
+
:width => 1,
|
155
|
+
:height => 5 )
|
156
|
+
|
157
|
+
a << Primitive.new(:text,
|
158
|
+
:x => x,
|
159
|
+
:y => 40, :fill => 'black',
|
160
|
+
:text => mark,
|
161
|
+
:style => "font-family:Arial;font-style:italic")
|
162
|
+
end
|
163
|
+
return a
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.label(args)
|
167
|
+
[Primitive.new(:text,
|
168
|
+
:text => args[:text],
|
169
|
+
:x => args[:x],
|
170
|
+
:y => args[:y],
|
171
|
+
:fill => "black",
|
172
|
+
:style => "font-family:monospace;")]
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.gradients #needs to know which of its gradients are predefined
|
176
|
+
[:red_white_h, :green_white_h, :blue_white_h, :yellow_white_h, :red_white_radial, :green_white_radial, :blue_white_radial, :yellow_white_radial ]
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.gradient(gradient)
|
180
|
+
type, color = case gradient
|
181
|
+
when :red_white_h
|
182
|
+
[:linear, "red"]
|
183
|
+
when :green_white_h
|
184
|
+
[:linear, "green"]
|
185
|
+
when :blue_white_h
|
186
|
+
[:linear, "blue"]
|
187
|
+
when :yellow_white_h
|
188
|
+
[:linear, "yellow"]
|
189
|
+
when :red_white_radial
|
190
|
+
[:radial, "red"]
|
191
|
+
when :green_white_radial
|
192
|
+
[:radial, "green"]
|
193
|
+
when :blue_white_radial
|
194
|
+
[:radial, "blue"]
|
195
|
+
when :yellow_white_radial
|
196
|
+
[:radial, "yellow"]
|
197
|
+
end
|
198
|
+
|
199
|
+
case type
|
200
|
+
when :linear
|
201
|
+
{:type => :linear,
|
202
|
+
:id => gradient,
|
203
|
+
:x1 => 0, :y1 => 0,
|
204
|
+
:x2 => 0, :y2 => 100,
|
205
|
+
:stops => [
|
206
|
+
{
|
207
|
+
:color => color,
|
208
|
+
:offset => 0,
|
209
|
+
:opacity=> 1
|
210
|
+
},
|
211
|
+
{
|
212
|
+
:color => "white",
|
213
|
+
:offset => 100,
|
214
|
+
:opacity=> 1
|
215
|
+
}
|
216
|
+
]
|
217
|
+
}
|
218
|
+
when :radial
|
219
|
+
{
|
220
|
+
:type => :radial,
|
221
|
+
:id => gradient,
|
222
|
+
:cx => 50, :cy => 50,
|
223
|
+
:r => 50, :fx => 50, :fy => 50,
|
224
|
+
:stops => [
|
225
|
+
{:offset => 0,
|
226
|
+
:color => "white",
|
227
|
+
:opacity => 0
|
228
|
+
},
|
229
|
+
{
|
230
|
+
:offset => 100,
|
231
|
+
:color => color,
|
232
|
+
:opacity => 1
|
233
|
+
}
|
234
|
+
]
|
235
|
+
}
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
end
|