GraphvizRでRubyのクラス継承図を描いてみる

jitte2007-11-17


一番時間がかかったのはMacPortsの入れ直しだった・・・。

require 'rubygems'
require 'graphviz_r'
 
class ObjectSpace::GraphvizR
  attr_accessor :gvr
 
  def initialize(name = 'ruby_class')
    # GraphvizRのインスタンスを生成
    @gvr = GraphvizR.new name
    # グラフの方向を左→右に指定
    @gvr.graph[:rankdir => :LR]
 
    # ObjectSpaceを使ってすべてのクラスとその親クラスのHashを作る
    @children = Hash.new
    ObjectSpace.each_object(Class) do |o|
      if s = o.superclass
        (@children[s.to_s] ||= []) << o.to_s
      end
    end
  end

  # クラス階層に従ってグラフを描く
  def visit(name)
    node = escape(name)
    # []メソッドでノードを作れるので、さらにそのノードの[]メソッドで属性を指定する
    @gvr[node][:label => name]
    if @children[name]
      @children[name].sort.each do |child|
        next if child.include?(':') # この行を削ると全クラスを含む巨大グラフが・・・
        visit(child)
        # 親クラス→子クラスのエッジを作る
        @gvr[node] >> @gvr[escape(child)]
      end
    end
  end

  # クラス名に:が入っているとGraphvizで文法エラーになるので削る
  def escape(name)
    name.tr(':', '')
  end

  def output(name)
    visit(name)
    @gvr.output
  end
end
 
if __FILE__ == $0
  ObjectSpace::GraphvizR.new.output('Object')
end

ちなみに、Windowsではちゃんと動かすには、入出力をバイナリモードにするよう、GraphvizR本体を修正する必要がある。

201c201
<         gv = IO.popen "dot -q -T#{format || 'png'}", "w+"
---
>         gv = IO.popen "dot -q -T#{format || 'png'}", "w+b"
214c214
<     File.open(filename || "#{@name}.#{format || 'png'}", "w+") do |file|
---
>     File.open(filename || "#{@name}.#{format || 'png'}", "w+b") do |file|