gihyo.jpの「具体例で学ぶ!情報可視化のテクニック」のプログラムを勝手にRubyで書き換えてみた。その2
前回に引き続き、今回も勝手にRubyで書き換えたプログラムの簡単な説明をします。
プログラムのダウンロードは以下のリンク先のdownloadからご自由にどうぞ。
GitHub - ombran/gihyojp-visualization-ruby: The Ruby version of the information visualization introduced by gihyo.jp.(unofficial)
今回はvisualization4のフォルダにあるプログラムの説明します。
visualization4の説明
元記事だと第3回、第4回にあたる内容になります。
このプログラムは階層的クラスタリングの実行結果をツリーマップで表現するものです。
詳しいことは元記事を見てもらったほうがわかりやすいと思います。
visualization4フォルダの内容
visualization4のフォルダ内容は以下のようになっています。
$ tree visualization4/ visualization4/
-- Demo.rb | |
-- Visualization | |
-- BinaryTreeMapRenderer.rb | |
-- Cluster.rb | |
-- ClusterBuilder.rb | |
-- ColorItem.rb | |
-- DistanceEvaluator.rb | |
-- Item.rb | |
-- MultiVector.rb | |
-- Node.rb | |
-- TreeMapRenderer.rb | |
`-- WardDistanceEvaluator.rb | |
-- Visualization.rb |
今回も前回同様、元記事のプログラムと拡張子の違いはありますが、ファイル名と内容を対応させてあります。
Visualization.rbを読み込むことだけで、Visualizationフォルダ以下のファイルを全て読み込めるようにしてあります。
Demo.rbがデモ用のプログラムで、treemap.pngは出力結果となります。
RMagickのインストール
プログラムの説明の前に、RMagickのインストール方法について説明します。
今回のプログラムでは画像を扱うので、画像処理用のライブラリとしてRMagickを用いるためです。
gemだと以下のようになります。
# gem install rmagick
RMagickのインストールにはライブラリが色々必要になりますけど、
それはいろんなところで書かれてると思うので頑張ってください。
ちなみに、Ubuntuとかなら、aptで簡単にインストールできます。
# apt-get install librmagick-ruby
プログラムの説明
今回のプログラムで元記事のプログラムと大きな違いは、画像描画部分になります。
そもそも使ってるライブラリ違うんで、当然といえば当然ですね。
その画像描画のプログラムはBinaryTreeMapRenderer.rbで、以下のようになります。
# BinaryTreeMapRenderer.rb require 'rubygems' require 'RMagick' module Visualization # # 領域の2分割を再帰的に繰り返し、ツリーマップの描画を行う # class BinaryTreeMapRenderer include Visualization::TreeMapRenderer def render(graphic, node, bounds) doRender(graphic, node, bounds, 0) end def doRender(graphic, node, bounds, depth) d = Magick::Draw.new if (node.kind_of? Visualization::ColorItem) # ノードが色項目の場合は、その色で長方形を塗りつぶす d.fill("rgb(#{node.getVector.data[:red]}, #{node.getVector.data[:green]}, #{node.getVector.data[:blue]}, #{depth})") d.rectangle(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height) elsif (node.kind_of? Visualization::Cluster) cluster = node # 子ノードchild1とchild2を取得 child1 = cluster.getLeft child2 = cluster.getRight # child1の面積の方が大きくなるようにする if (child1.getArea < child2.getArea) temp = child1 child1 = child2 child2 = temp end # 子ノードの面積比を計算 area1 = child1.getArea area2 = child2.getArea ratio1 = area1 / (area1 + area2) ratio2 = area2 / (area1 + area2) x = bounds.x y = bounds.y w = bounds.width h = bounds.height rect1 = nil rect2 = nil # 領域分割を実行 if (w > h) # boundsが横長の場合、左右に分割 rect1 = Magick::Rectangle.new(ratio1 * w, h, x, y) rect2 = Magick::Rectangle.new(ratio2 * w, h, x + ratio1 * w, y) else # boundsが縦長の場合、上下に分割 rect1 = Magick::Rectangle.new(w, ratio1 * h, x, y) rect2 = Magick::Rectangle.new(w, ratio2 * h, x, y + ratio1 * h) end # 子ノードを再帰的に処理する doRender(graphic, child1, rect1, depth + 1) doRender(graphic, child2, rect2, depth + 1) end # 輪郭を階層の深さに応じた太さで描画 d.fill("transparent") borderWidth = [8 - depth, 1].max d.stroke_width(borderWidth) d.stroke("black") d.rectangle(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height) d.draw(graphic) end end end
doRenderメソッドのgraphicはMagick::Imageオブジェクト、boundsはMagick::Rectangleオブジェクトになります。
d.fillで塗りつぶしの色を設定し、d.rectangleで四角の描画を行います。
あとd.stroke_widthで線の幅指定やd.strokeとかで線の色を指定したりしています。
そして最後にd.drawとすることで図形の元のMagick::Imageオブジェクトに図形の描画を行っています。
RMagickのメソッドの詳しい説明はこちらにあるので、細かいことはそちらをご覧ください。
ちなみに、元記事とライブラリなどは違いますが、プログラムの形式はほぼ同じになっているので、
元記事と比較しながら見れると思います。
デモプログラムの実行
プログラムのデモを行うDemoクラスは以下のようになります。
# Demo.rb require 'rubygems' require 'RMagick' require File.dirname(__FILE__) + '/Visualization' class Demo include Visualization OUTPUT_FILE_NAME = 'treemap.png' def run color = Struct.new(:red, :green, :blue) # ランダムな色データを100個作成 input = [] 100.times do |i| c = color.new(rand(256), rand(256), rand(256)) area = 0.2 + 0.8 * rand input << ColorItem.new(c, area) end # Ward法に基づく階層的クラスタリングを準備 evaluator = WardDistanceEvaluator.new builder = ClusterBuilder.new(evaluator) # クラスタリングを実行 puts "クラスタリング開始(結構時間かかります)" result = builder.build(input) puts "クラスタリング終了" # クラスタリング結果を表示 puts "画像生成開始" output(result) puts "出力ファイル:" + OUTPUT_FILE_NAME end def output(node) # 400x400ピクセルの画像を作成 g = Magick::Image.new(400, 400) # グラフィックオブジェクトを作成 d = Magick::Draw.new # 背景を白で塗りつぶす d.fill("white") d.rectangle(0, 0, g.columns, g.rows) d.draw(g) # ツリーマップの描画を実行 renderer = BinaryTreeMapRenderer.new bounds = Magick::Rectangle.new(360, 360, 20, 20) renderer.render(g, node, bounds) # 画像をPNGファイルに保存 g.write(OUTPUT_FILE_NAME) end end demo = Demo.new demo.run
デモプログラムを実行すると、以下のような出力が得られます。
$ ruby Demo.rb クラスタリング開始(結構時間かかります) 100 99 98 97 96 95 ... 5 4 3 2 クラスタリング終了 画像生成開始 出力ファイル:treemap.png
これで、treemap.pngという以下のような画像が出力されます。
ツリーマップによってクラスタリングの結果が視覚的にわかりやすく表現されていることがわかると思います。
ただし、入力データはランダムに作成しているので、出力される画像は毎回違うものとなります。
以上
今回はここまでです。
細かい部分はプログラムにコメントを書いてるんでそちらを読んでください。
あと、間違ってる部分などありましたら教えていただけるとありがたいです。
残りのプログラムについては次回以降説明します。