(追記:2014-3-3) Gvizについてのまとめ頁を作りました。

Gvizの目次 - Rubyの世界からGraphvizの世界にこんにちは!


今日Rubyのクラスツリーを手書きでグラフ化している記事を見かけた。

A diagram of the Ruby Core object model - Jerome’s Adventures in Rubyland

これを見てそういえばGraphvizのRubyによるラッパーを書いたのに未だRubyのクラスツリーをグラフ化していないことに気付いた。

melborne/Gviz

gviz | RubyGems.org | your community gem host

そしてRubyをグラフ化するならやっぱりRuby自身に書いてもらうのが一番だと思った。彼女も20才になったことだし。

早々gem install gvizでgvizを入れて次のようなコードを書く。

# ruby_tree.rb
classes = ObjectSpace.each_object(Class)
                     .reject { |k| "#{k}".match /Gem|Thor|Gviz/ }

classes.each do |klass|
  tree = klass.ancestors.select { |anc| anc.is_a? Class }.reverse
  tree.each_cons(2) do |a, b|
    a_id, b_id = [a, b].map(&:to_id)
    route a_id => b_id
    node a_id, label:a
    node b_id, label:b
  end
end

save :ruby_tree, :png

gviz buildコマンドを叩いて、生成されたpngを開く。

% gviz build ruby_tree.rb
% open ruby_tree.png


alt noshadow (クリックで拡大)

収集がつかないので例外クラスを外してみる。

classes = ObjectSpace.each_object(Class)
                     .reject { |k| "#{k}".match /Gem|Thor|Gviz/ }

classes.each do |klass|
  tree = klass.ancestors.select { |anc| anc.is_a? Class }.reverse
+ next if tree.include?(Exception) && tree[-1] != Exception
  tree.each_cons(2) do |a, b|
    a_id, b_id = [a, b].map(&:to_id)
    route a_id => b_id
    node a_id, label:a
    node b_id, label:b
  end
end

save :ruby_tree, :png


alt noshadow (クリックで拡大)

依然として収集がつかないのでレイアウトを変えてみる。

classes = ObjectSpace.each_object(Class)
                     .reject { |k| "#{k}".match /Gem|Thor|Gviz/ }

+global layout:'fdp'

 classes.each do |klass|
   tree = klass.ancestors.select { |anc| anc.is_a? Class }.reverse
   next if tree.include?(Exception) && tree[-1] != Exception
   tree.each_cons(2) do |a, b|
     a_id, b_id = [a, b].map(&:to_id)
     route a_id => b_id
     node a_id, label:a
     node b_id, label:b
   end
 end
 
 save :ruby_tree, :png


alt noshadow (クリックで拡大)

BasicObjectからDelegatorが伸びていることに感心を示しつつルート(BasicObject)からの距離に応じて異なる色での着色を考える。

classes = ObjectSpace.each_object(Class)
                      .reject { |k| "#{k}".match /Gem|Thor|Gviz/ }
 
 global layout:'fdp'
 nodes style:'filled', colorscheme:'purd6'
 
 classes.each do |klass|
   tree = klass.ancestors.select { |anc| anc.is_a? Class }.reverse
   next if tree.include?(Exception) && tree[-1] != Exception
+  tree = tree.map.with_index { |k, i| [k, i] }
   tree.each_cons(2) do |(a, ai), (b, bi)|
     a_id, b_id = [a, b].map(&:to_id)
     route a_id => b_id
+    node a_id, label:a, color:6-ai, fillcolor:6-ai
+    node b_id, label:b, color:6-bi, fillcolor:6-bi
   end
 end
 
 save :ruby_tree, :png

alt noshadow (クリックで拡大)

ここまでくるとモジュールを挟みたくなる。

classes = ObjectSpace.each_object(Class)
                      .reject { |k| "#{k}".match /Gem|Thor|Gviz/ }
 
 global layout:'fdp'
 nodes style:'filled', colorscheme:'purd6'
 
 classes.each do |klass|
+  tree, mods = klass.ancestors.group_by { |anc| anc.is_a? Class }
+                    .map { |_, k| k.reverse }
   next if tree.include?(Exception) && tree[-1] != Exception
   tree = tree.map.with_index { |k, i| [k, i] }
   tree.each_cons(2) do |(a, ai), (b, bi)|
     a_id, b_id = [a, b].map(&:to_id)
     route a_id => b_id
     node a_id, label:a, color:6-ai, fillcolor:6-ai
     node b_id, label:b, color:6-bi, fillcolor:6-bi
   end
 
+  mods = [] unless mods
+  mods.each do |mod|
+    mod_id = mod.to_id
+    route mod_id => klass.to_id
+    node mod_id, label:mod, shape:'box'
+  end
 end
 
 save :ruby_tree, :png

alt noshadow (クリックで拡大)

もうこうなったら例外クラスも入れてみる。

classes = ObjectSpace.each_object(Class)
                     .reject { |k| "#{k}".match /Gem|Thor|Gviz/ }

global layout:'fdp'
nodes style:'filled', colorscheme:'purd6'
# edges color:'maroon'

classes.each do |klass|
  tree, mods = klass.ancestors.group_by { |anc| anc.is_a? Class }
                    .map { |_, k| k.reverse }
- next if tree.include?(Exception) && tree[-1] != Exception
  tree = tree.map.with_index { |k, i| [k, i] }
  tree.each_cons(2) do |(a, ai), (b, bi)|
    a_id, b_id = [a, b].map(&:to_id)
    route a_id => b_id
    node a_id, label:a, color:6-ai, fillcolor:6-ai
    node b_id, label:b, color:6-bi, fillcolor:6-bi
  end

  mods = [] unless mods
  mods.each do |mod|
    mod_id = mod.to_id
    route mod_id => klass.to_id
    node mod_id, label:mod, shape:'box'
  end

end

save :ruby_tree, :png

ギャッ!

alt noshadow (クリックで拡大)

誰がこんな複雑な宇宙作ったんだ。


モジュールは入れるべきではなかった。

classes = ObjectSpace.each_object(Class)
                     .reject { |k| "#{k}".match /Gem|Thor|Gviz/ }

global layout:'fdp'
nodes style:'filled', colorscheme:'purd6'
# edges color:'maroon'

classes.each do |klass|
  tree, mods = klass.ancestors.group_by { |anc| anc.is_a? Class }
                    .map { |_, k| k.reverse }
  # next if tree.include?(Exception) && tree[-1] != Exception
  tree = tree.map.with_index { |k, i| [k, i] }
  tree.each_cons(2) do |(a, ai), (b, bi)|
    a_id, b_id = [a, b].map(&:to_id)
    route a_id => b_id
    node a_id, label:a, color:6-ai, fillcolor:6-ai
    node b_id, label:b, color:6-bi, fillcolor:6-bi
  end

- mods = [] unless mods
- mods.each do |mod|
-   mod_id = mod.to_id
-   route mod_id => klass.to_id
-   node mod_id, label:mod, shape:'box'
- end
end

save :ruby_tree, :png

alt noshadow (クリックで拡大)

綺麗になったね、Ruby。


(追記:2013-10-19) Ruiniusも視覚化しました。

Rubiniusユニバースも視覚化してみる


関連記事:

Yet Another Ruby Graphviz Interfaceを作ったからみんなで大量のグラフを作って遊ぼうよ!

東京の地下鉄をGviz(Ruby Graphviz Wrapper)で描く

Rubyのソースディレクトリも視覚化してみる


=== Ruby関連電子書籍100円で好評発売中! ===

M’ELBORNE BOOKS

ruby_object ruby_tutorial ruby_trivia



blog comments powered by Disqus
ruby_pack8

100円〜で好評発売中!
M'ELBORNE BOOKS