gihyo.jpの「具体例で学ぶ!情報可視化のテクニック」のプログラムを勝手にRubyで書き換えてみた。その4
勝手にRubyで書き換えてみたプログラムも今回でラストになります。
プログラムのダウンロードは以下のリンク先のdownloadからご自由にどうぞ。
GitHub - ombran/gihyojp-visualization-ruby: The Ruby version of the information visualization introduced by gihyo.jp.(unofficial)
今回はvisualization6フォルダのプログラムについての説明になります。
visualization6の説明
元記事だと第6回にあたる内容になります。
このプログラムは、はてなブックマークの人気エントリーをツリーマップとして可視化するプログラムになります。
詳しいことは元記事を見てもらったほうがわかりやすいと思います。
visualization6フォルダの内容
visualization6のフォルダ内容は以下のようになっています。
$ tree visualization6 visualization6
-- Demo.rb | |
-- Visualization | |
-- BinaryTreeMapRenderer.rb | |
-- Bookmark.rb | |
-- BookmarkDetail.rb | |
-- BookmarkItem.rb | |
-- Cluster.rb | |
-- ClusterBuilder.rb | |
-- DistanceEvaluator.rb | |
-- HatenaBookmarkAPI.rb | |
-- Item.rb | |
-- MultiVector.rb | |
-- Node.rb | |
-- TreeMapRenderer.rb | |
`-- WardDistanceEvaluator.rb | |
-- Visualization.rb |
Visualization.rbを読み込むことだけで、Visualizationフォルダ以下のファイルを全て読み込めるようにしてあります。
Demo.rbがデモ用のプログラムとなります。
RMagickとJSONライブラリのインストール
画像処理にRMagick、JSONデータのパースにJSONライブラリを使用するので、あらかじめインストールしておいてください。
まずRMagickはgemだと
# gem install rmagick
ちなみに、Ubuntuだとaptでインストールできます。
# apt-get install librmagick-ruby
次にJSONライブラリはgemでインストールできます。
# gem install json
プログラムの説明
基本的にプログラムのクラスそれぞれは元記事のプログラムと対応させてあるので、見比べられるようにしてあります。
ただ、ブックマークのタグを整数インデックスに変換するIndexMapperクラスというのが元記事のプログラムにはありますが、こちらのプログラムではMultiVectorクラスのデータがハッシュで作成しているので、IndexMapperクラスがいらなくなります。
以下のBookmarkItemクラスから元記事でのマッピングをしていないことがわかると思います。
# BookmarkItem.rb module Visualization # はてなブックマークエントリに対応する末端ノード class BookmarkItem < Visualization::Item # @param bookmark:Visualization::Bookmarkオブジェクト # @param detail:Visualization::BookmarkDetailオブジェクト def initialize(bookmark, detail) # ブックマーク数をそのまま平均化すると比率が極端になるので # 平方根をとって調整する super(bookmark.title, tagsToVector(detail), Math.sqrt(detail.bookmarkCount)) @bookmark = bookmark @detail = detail end def tagsToVector(detail) vector = Visualization::MultiVector.new detail.tags.each do |tag| vector.set(tag => (vector.get(tag).to_i + 1)) end vector.normalize return vector end def getBookmark return @bookmark end def getDetail return @detail end end end
あとは、画像描画をしているBinaryTreeMapRendererクラスですね。
基本的に元記事と同じですが、画像に文字を書き込んでいるdrawStringWithinメソッドは特殊なんで説明しときます。
まずプログラムは以下のようになります。
# BinaryTreeMapRenderer.rb(一部分) module Visualization class BinaryTreeMapRenderer ... # ブックマークされたエントリのタイトル def drawStringWithin(draw, title, rect) d = draw # フォントサイズ pointsize = [(Math.sqrt(rect.width * rect.height) / 10).to_i, 10].max # 日本語表示のためにフォント指定(環境によって変更してください) d.font = "/usr/share/fonts/truetype/kochi/kochi-gothic.ttf" # 文字色 d.fill("white") d.stroke_width(0) d.pointsize(pointsize) # 文字を改行しながら出力 words = [rect.width.to_f/pointsize.to_f, 1].max.to_i t_ary = title.split("") t_size = t_ary.size rows = (t_size.to_f/words.to_f).ceil rows.times do |i| start = i * words finish = ((i + 1) * words) - 1 text = t_ary[start..finish].join y = rect.y + pointsize * (i + 1) d.text(rect.x + 5, y + 5, text) end end end end
d.fontでフォントの指定をしないと日本語を表示できないので気をつけてください。またこれは環境によって違うので、それぞれの環境で日本語を表示できるフォントの絶対パスを指定してください。
あとはpointsizeで文字の大きさを設定していたり、表示領域ごとに文字列を改行しながら表示しています。
ちなみにブックマーク数が多さで文字の大きさを変更してあります。
デモプログラムの実行
プログラムのデモを行うDemoクラスは以下のようになります。
# Demo.rb $KCODE = 'u' require 'rubygems' require 'RMagick' require File.dirname(__FILE__) + '/Visualization' class Demo include Visualization # 出力ファイル名 OUTPUT_FILE_NAME = 'hatena_bookmark.png' def run api = HatenaBookmarkAPI.new bookmarks = api.getHotEntries puts bookmarks.size.to_s + " entries." # 階層的クラスタリングの入力データを作成 input = [] bookmarks.each do |bookmark| puts bookmark.title puts " [url] " + bookmark.url # サーバの負荷を抑えるため呼び出し間隔を空ける sleep(1) detail = api.getDetail(bookmark.url) input << BookmarkItem.new(bookmark, detail) if (detail != nil) puts " [bookmarkCount] " + detail.bookmarkCount.to_s puts " [tags] [" + detail.tags.join(", ") + "]" end end # Ward法に基づく階層的クラスタリングを準備 evaluator = Visualization::WardDistanceEvaluator.new builder = Visualization::ClusterBuilder.new(evaluator) # クラスタリングを実行 puts "クラスタリング開始(結構時間かかります)" result = builder.build(input) puts "クラスタリング終了" # クラスタリング結果を表示 puts "画像生成開始" output(result) puts "出力ファイル:" + OUTPUT_FILE_NAME end def output(node) # 出力画像を作成 g = Magick::Image.new(1024, 768) # グラフィックオブジェクトを作成 d = Magick::Draw.new # 背景を白で塗りつぶす d.fill("white") d.rectangle(0, 0, g.columns, g.rows) d.draw(g) # ツリーマップの描画を実行 renderer = Visualization::BinaryTreeMapRenderer.new bounds = Magick::Rectangle.new(g.columns - 40, g.rows - 40, 20, 20) renderer.render(g, node, bounds) # 画像をPNGファイルに保存 g.write(OUTPUT_FILE_NAME) end end demo = Demo.new demo.run
デモプログラムを実行すると、以下のような出力が得られます。
$ ruby Demo.rb 30 entries. おいしいスープのレシピ集:アルファルファモザイク [url] http://alfalfa.livedoor.biz/archives/51402336.html [bookmarkCount] 249 [tags] [あとで読む, レシピ, お役立ち, ... ... クラスタリング開始(結構時間かかります) 30 29 28 27 26 ... 6 5 4 3 2 クラスタリング終了 画像生成開始 出力ファイル:hatena_bookmark.png
これで、hatena_bookmark.pngという以下のような画像ファイルが出力されます。
ツリーマップによってクラスタリングの結果が視覚的にわかりやすく表現されていることがわかると思います。
ただし、入力データは、はてなブックマークのホッテントリに依存するので、出力される画像はその時によって変化します。