18 October 2013
RubyユニバースをGraphvizで視覚化する
(追記:2014-3-3) Gvizについてのまとめ頁を作りました。
今日Rubyのクラスツリーを手書きでグラフ化している記事を見かけた。
A diagram of the Ruby Core object model - Jerome’s Adventures in Rubyland
これを見てそういえばGraphvizのRubyによるラッパーを書いたのに未だRubyのクラスツリーをグラフ化していないことに気付いた。
そして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
収集がつかないので例外クラスを外してみる。
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
依然として収集がつかないのでレイアウトを変えてみる。
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
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
ここまでくるとモジュールを挟みたくなる。
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
もうこうなったら例外クラスも入れてみる。
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
ギャッ!
誰がこんな複雑な宇宙作ったんだ。
モジュールは入れるべきではなかった。
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
綺麗になったね、Ruby。
(追記:2013-10-19) Ruiniusも視覚化しました。
関連記事:
Yet Another Ruby Graphviz Interfaceを作ったからみんなで大量のグラフを作って遊ぼうよ!
東京の地下鉄をGviz(Ruby Graphviz Wrapper)で描く
=== Ruby関連電子書籍100円で好評発売中! ===
blog comments powered by Disqus