Gruffで折れ線グラフを描く際のTIPS
rubyには面白いGemがたくさんありますが、その一つにグラフを描くためのライブラリ
Gruff Graphs for Ruby | Ruby on Rails for Newbies
があります。
このGemを用いれば折れ線グラフや円グラフなどを簡単に描くことができますが、少し微妙な部分があります。それを回避するMonkeyPatchを紹介します。
検証したバージョン
- OS
- Mac OS X Lion 10.7.2
- imagemagick
- 6.7.1
- ruby
- 1.9.2p290
- rmagick
- 2.13.1
- gruff
- 0.3.6
インストール
imagemagickやrmagickを事前にインストールする必要があるため、少し手間がかかります。本家サイトや下記サイトなどを参考に、インストールしてください。
noanoa 日々の日記 : Mac OS X Lion に Ruby グラフ描画用 Gruff をインストールする
Gruffでグラフを書いてみよう - まめ畑
Gruffでグラフ!格好いいグラフを簡単に生成 - OneRingToFind
とりあえず描く
とりあえず、下記のようにすれば折れ線グラフが描けます。日本語を出すためには、FONT指定が必須です。
#!/usr/bin/env ruby # -*- encoding: utf-8 -*- require 'rubygems' require 'gruff' g = Gruff::Line.new g.font = "/Library/Fonts/Osaka.ttf" g.labels = {0=>'01/01', 3=>'01/04', 6=>'01/07'} g.data("ほげ", [1,2,3,4,5,4,3,2,1]) g.data("foooooooooooooooooooooooooooooooooooooooooooooo", [9,9,9,8,8,8,7,7,7]) g.data("bar", [3,2,1,3,2,1,3,2,1]) g.write('hoge.png')
y軸の最小値・最大値・増分を指定する
マニュアル等に従えば、minimum_value, maximum_value, y_axis_incrementを指定すればy軸の最小値・最大値・増分を指定できることになっています。
が、現バージョンではエラーになります。
#!/usr/bin/env ruby # -*- encoding: utf-8 -*- require 'rubygems' require 'gruff' g = Gruff::Line.new g.font = "/Library/Fonts/Osaka.ttf" g.minimum_value = 0 g.maximum_value = 15 g.y_axis_increment = 5 g.labels = {0=>'01/01', 3=>'01/04', 6=>'01/07'} g.data("ほげ", [1,2,3,4,5,4,3,2,1]) g.data("foooooooooooooooooooooooooooooooooooooooooooooo", [9,9,9,8,8,8,7,7,7]) g.data("bar", [3,2,1,3,2,1,3,2,1]) g.write('hoge.png')
/Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/line.rb:112:in `normalize': wrong number of arguments (1 for 0) (ArgumentError) from /Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/base.rb:687:in `draw_line_markers' from /Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/base.rb:536:in `setup_drawing' from /Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/base.rb:508:in `draw' from /Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/line.rb:53:in `draw' from /Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/base.rb:487:in `write' from ./grufftest.rb:20:in `<main>'
エラーメッセージは「line.rbにはひとつの引数を取れるnormalizeメソッドなんか無いぜ!」って言ってますが、line.rbのソースを見ると確かに引数をひとつ取れるnormalizeメソッドは存在しません。
下記の引数のないnormalizeメソッドのみが定義されています。
def normalize @maximum_value = [@maximum_value.to_f, @baseline_value.to_f].max super @norm_baseline = (@baseline_value.to_f / @maximum_value.to_f) if @baseline_value end
Lineクラスの親クラスであるBaseクラスを確認すると、def normalize(force=false)という引数有りメソッドが定義されていますが、このメソッドはprotectedです。
つーことで、サクっとline.rbにMonkey Patchを当てることにします。GemのソースをForkして修正してローカルインストールして・・・ ってやっても良いですが、小さい修正ならばrubyのオープンクラス特性を利用してMonkey Patchを当てたほうが早かったりします。
# ./monkey_patch/gruff/line.rb class Gruff::Line def normalize(force=false) @maximum_value = [@maximum_value.to_f, @baseline_value.to_f].max super(force) @norm_baseline = (@baseline_value.to_f / @maximum_value.to_f) if @baseline_value end end
Gemをrequireした後にこのMonkey Patchをrequireし、Gruff::Lineクラスにnormalize(force=true)メソッドを追加します。
・・・ require 'rubygems' require 'gruff' require './monkey_patch/gruff/line' ・・・
凡例を左揃えにする
gruffは凡例に表示する文字列の長さと描画領域の横幅を考慮して、凡例をオーバーラップしてくれます。これはこれですばらしいのですが、文字列の長さが(この例のように)大幅に異なる場合、なんだか気持ち悪い見た目になります。ついでに、このオーバーラップ計算を強制的にキャンセルしてみます。
凡例はGruff::Baseクラスのdraw_legendメソッドで描画されます。デフォルトのこのメソッドをundefし書き換えることにします。
# ./monkey_patch/gruff/base.rb module Gruff class Base undef draw_legend def draw_legend return if @hide_legend @legend_labels = @data.collect {|item| item[DATA_LABEL_INDEX] } legend_square_width = @legend_box_size # small square with color of this item # May fix legend drawing problem at small sizes @d.font = @font if @font @d.pointsize = @legend_font_size label_widths = [[]] # Used to calculate line wrap @legend_labels.each do |label| metrics = @d.get_type_metrics(@base_image, label.to_s) label_width = metrics.width + legend_square_width * 2.7 label_widths.last.push label_width if sum(label_widths.last) > (@raw_columns * 0.9) label_widths.push [label_widths.last.pop] end end current_x_offset = 50 current_y_offset = @hide_title ? @top_margin + title_margin : @top_margin + title_margin + @title_caps_height @legend_labels.each_with_index do |legend_label, index| # Draw label @d.fill = @font_color @d.font = @font if @font @d.pointsize = scale_fontsize(@legend_font_size) @d.stroke('transparent') @d.font_weight = NormalWeight @d.gravity = WestGravity @d = @d.annotate_scaled( @base_image, @raw_columns, 1.0, current_x_offset + (legend_square_width * 1.7), current_y_offset, legend_label.to_s, @scale) # Now draw box with color of this dataset @d = @d.stroke('transparent') @d = @d.fill @data[index][DATA_COLOR_INDEX] @d = @d.rectangle(current_x_offset, current_y_offset - legend_square_width / 2.0, current_x_offset + legend_square_width, current_y_offset + legend_square_width / 2.0) @d.pointsize = @legend_font_size metrics = @d.get_type_metrics(@base_image, legend_label.to_s) current_string_offset = metrics.width + (legend_square_width * 2.7) line_height = [@legend_caps_height, legend_square_width].max + legend_margin current_y_offset += line_height @graph_top += line_height @graph_height = @graph_bottom - @graph_top end @color_index = 0 end end end
こいつをrequireすると
・・・ require 'rubygems' require 'gruff' require './monkey_patch/gruff/base' require './monkey_patch/gruff/line' ・・・
凡例が左揃えになります。