lib/nmatrix_extensions.rb in egor-0.0.5 vs lib/nmatrix_extensions.rb in egor-0.9.0
- old
+ new
@@ -1,14 +1,23 @@
-require "rubygems"
-require "facets"
-require "narray"
+require 'rubygems'
+require 'narray'
+require 'facets'
+begin
+ require 'rvg/rvg'
+ include Magick
+rescue
+ $logger.warn "A RubyGems package, 'rmagick' is not found, so heat maps cannot be generated."
+ $no_rmagick = true
+end
+
module NMatrixExtensions
- def pretty_string(opts={})
- { :col_header => nil,
- :row_header => nil }.merge!(opts)
+ def pretty_string(options={})
+ opts = {:col_header => nil,
+ :row_header => nil }.merge(options)
+
("%-3s" % "#") + opts[:col_header].inject("") { |s, a|
s + ("%7s" % a)
} + "\n" + self.to_a.map_with_index { |a, i|
("%-3s" % opts[:row_header][i]) + a.inject("") { |s, v|
if v.is_a? Float
@@ -17,8 +26,220 @@
s + ("%7d" % v)
end
}
}.join("\n")
end
+
+ def heatmap(options={})
+ if $no_rmagick
+ return nil
+ end
+
+ opts = {:col_header => 'ACDEFGHIKLMNPQRSTVWYJ'.split(''),
+ :row_header => 'ACDEFGHIKLMNPQRSTVWYJ'.split(''),
+ :max_val => self.max,
+ :mid_val => (self.max - self.min) / 2.0,
+ :min_val => self.min,
+ :dpi => 100,
+ :margin_width => 30,
+ :rvg_width => nil,
+ :rvg_height => nil,
+ :canvas_width => nil,
+ :canvas_height => nil,
+ :cell_width => 20,
+ :cell_height => 20,
+ :cell_border_color => '#888888',
+ :cell_border_width => 1,
+ :table_border_color => '#000000',
+ :table_border_width => 2,
+ :header_height => 100,
+ :footer_height => 50,
+ :print_gradient => true,
+ :gradient_width => 300,
+ :gradient_height => 30,
+ :gradient_beg_color => '#FFFFFF',
+ :gradient_mid_color => nil,
+ :gradient_end_color => '#FF0000',
+ :gradient_border_width => 1,
+ :gradient_border_color => '#000000',
+ :font_scale => 0.9,
+ :font_family => 'san serif',
+ :small_gap_width => 2,
+ :title? => true,
+ :title => '',
+ :title_font_size => 35,
+ :print_value => false,
+ :key_font_size => 15,
+ :value_font_size => 8,
+ :background => '#FFFFFF'}.merge(options)
+
+ RVG::dpi = opts[:dpi]
+
+ rvg = RVG.new(opts[:rvg_width], opts[:rvg_height]) do |canvas|
+ title_x = (opts[:canvas_width] - opts[:title].length * opts[:title_font_size] * 0.6) / 2.0
+ title_y = opts[:header_height] - opts[:title_font_size]
+
+ canvas.viewbox(0, 0, opts[:canvas_width], opts[:canvas_height])
+ canvas.background_fill = opts[:background]
+ canvas.desc = opts[:title]
+
+ if opts[:title?]
+ canvas.text(title_x, title_y, opts[:title]).styles(:font_size => opts[:title_font_size])
+ end
+
+ # border for whole matrix
+ table_x = (opts[:canvas_width] - opts[:cell_width] * self.shape[0]) / 2.0
+ table_y = opts[:header_height] + opts[:cell_height]
+
+ canvas.rect(self.shape[0] * opts[:cell_width],
+ self.shape[1] * opts[:cell_height],
+ table_x,
+ table_y).styles(:stroke => opts[:table_border_color],
+ :stroke_width => opts[:table_border_width])
+
+ # drawing column and row labels
+ 0.upto(self.shape[0] - 1) do |col|
+ canvas.text(table_x + col * opts[:cell_width] + opts[:small_gap_width],
+ opts[:cell_height] + opts[:header_height] - opts[:small_gap_width],
+ opts[:col_header][col]).styles( :font_family => opts[:font_family],
+ :font_size => opts[:cell_width] * opts[:font_scale])
+ end
+
+ 0.upto(self.shape[1] - 1) do |row|
+ canvas.text(table_x - opts[:cell_width],
+ table_y + (row + 1) * opts[:cell_height],
+ opts[:row_header][row]).styles( :font_family => opts[:font_family],
+ :font_size => opts[:cell_height] * opts[:font_scale])
+ end
+
+ # drawing cells
+
+ # calculating a unit of RGB color in a decimal number
+ r_beg = (opts[:gradient_beg_color].rgb_to_integer & 0xFF0000) >> 16
+ g_beg = (opts[:gradient_beg_color].rgb_to_integer & 0x00FF00) >> 8
+ b_beg = (opts[:gradient_beg_color].rgb_to_integer & 0x0000FF) >> 0
+ r_end = (opts[:gradient_end_color].rgb_to_integer & 0xFF0000) >> 16
+ g_end = (opts[:gradient_end_color].rgb_to_integer & 0x00FF00) >> 8
+ b_end = (opts[:gradient_end_color].rgb_to_integer & 0x0000FF) >> 0
+ gap = opts[:max_val] - opts[:min_val]
+
+ if opts[:gradient_mid_color]
+ r_mid = (opts[:gradient_mid_color].rgb_to_integer & 0xFF0000) >> 16
+ g_mid = (opts[:gradient_mid_color].rgb_to_integer & 0x00FF00) >> 8
+ b_mid = (opts[:gradient_mid_color].rgb_to_integer & 0x0000FF) >> 0
+ gap1 = opts[:mid_val] - opts[:min_val]
+ gap2 = opts[:max_val] - opts[:mid_val]
+ end
+
+ 0.upto(self.shape[0] - 1) do |col|
+ 0.upto(self.shape[1] - 1) do |row|
+ if opts[:gradient_mid_color]
+ if self[col, row] <= opts[:mid_val]
+ r = interpolate(r_beg, r_mid, self[col, row] - opts[:min_val], gap1)
+ g = interpolate(g_beg, g_mid, self[col, row] - opts[:min_val], gap1)
+ b = interpolate(b_beg, b_mid, self[col, row] - opts[:min_val], gap1)
+ else
+ r = interpolate(r_mid, r_end, self[col, row] - opts[:mid_val], gap2)
+ g = interpolate(g_mid, g_end, self[col, row] - opts[:mid_val], gap2)
+ b = interpolate(b_mid, b_end, self[col, row] - opts[:mid_val], gap2)
+ end
+ else
+ r = interpolate(r_beg, r_end, self[col, row] - opts[:min_val], gap)
+ g = interpolate(g_beg, g_end, self[col, row] - opts[:min_val], gap)
+ b = interpolate(b_beg, b_end, self[col, row] - opts[:min_val], gap)
+ end
+
+ color = ("#%6X" % ((((r << 8) | g) << 8) | b)).gsub(" ", "0")
+
+ canvas.rect(opts[:cell_width],
+ opts[:cell_height],
+ table_x + col * opts[:cell_width],
+ table_y + row * opts[:cell_height]).styles( :fill => color,
+ :stroke => opts[:cell_border_color],
+ :stroke_width => opts[:cell_border_width])
+
+ if opts[:print_value]
+ canvas.text(table_x + col * opts[:cell_width] + opts[:cell_border_width],
+ table_y + (row + 1) * opts[:cell_height],
+ "#{'%.1f' % self[col, row]}").styles(:font_size => opts[:value_font_size])
+ end
+ end
+ end
+
+ # gradient key
+ if opts[:print_gradient]
+ if opts[:gradient_mid_color]
+ img1 = Image.new(opts[:gradient_height],
+ opts[:gradient_width] / 2,
+ GradientFill.new(0,
+ opts[:gradient_width] / 2,
+ opts[:gradient_height],
+ opts[:gradient_width] / 2,
+ opts[:gradient_beg_color],
+ opts[:gradient_mid_color])).rotate(90)
+
+ img2 = Image.new(opts[:gradient_height],
+ opts[:gradient_width] / 2,
+ GradientFill.new(0,
+ opts[:gradient_width] / 2,
+ opts[:gradient_height],
+ opts[:gradient_width] / 2,
+ opts[:gradient_mid_color],
+ opts[:gradient_end_color])).rotate(90)
+ img3 = ImageList.new
+ img3 << img1 << img2
+ img = img3.append(false)
+ else
+ img = Image.new(opts[:gradient_height],
+ opts[:gradient_width],
+ GradientFill.new(0,
+ opts[:gradient_width],
+ opts[:gradient_height],
+ opts[:gradient_width],
+ opts[:gradient_beg_color],
+ opts[:gradient_end_color])).rotate(90)
+ end
+
+ img.border!(opts[:gradient_border_width],
+ opts[:gradient_border_width],
+ opts[:gradient_border_color])
+
+ gradient_x = (opts[:canvas_width] - opts[:gradient_width]) / 2
+ gradient_y = opts[:header_height] + opts[:cell_height] * opts[:row_header].count + opts[:margin_width]
+
+ canvas.image(img,
+ opts[:gradient_width],
+ opts[:gradient_height] + opts[:margin_width],
+ gradient_x,
+ gradient_y)
+
+ canvas.text(gradient_x,
+ gradient_y + opts[:gradient_height] + opts[:key_font_size] * 2,
+ "#{'%.1f' % opts[:min_val]}").styles(:font_size => opts[:key_font_size])
+
+ canvas.text(gradient_x + opts[:gradient_width],
+ gradient_y + opts[:gradient_height] + opts[:key_font_size] * 2,
+ "#{'%.1f' % opts[:max_val]}").styles(:font_size => opts[:key_font_size])
+ end
+ end
+
+ rvg.draw
+ end
+
+
+ private
+
+ def interpolate(start_val, end_val, step, no_steps)
+ begin
+ if (start_val < end_val)
+ ((end_val - start_val) / no_steps.to_f) * step + start_val
+ else
+ start_val - ((start_val - end_val) / no_steps.to_f) * step
+ end.round
+ rescue FloatDomainError
+ start_val
+ end
+ end
+
end
NMatrix.send(:include, NMatrixExtensions)