GraphvizRでオブジェクトをグラフ化してみる
びっずびずにしてやんよ!
とりあえずArrayとHash、それからインスタンス変数のあるクラスに対応。あとはinspectでそれなりに。中身が長いと横に伸びすぎて大変なことに(アンパンマン風)なるのは気にしない方向でひとつ。
vizviz.rb
require 'rubygems' require 'graphviz_r' class Object def vis_node(g) if vis_elements.empty? g[vis_id, [:label => inspect]] else vis_node_instance(g) end end def vis_elements instance_variables end def vis_element(e) instance_eval(e) end def vis_each vis_elements.sort.each do |e| yield e end end def vis_node_instance(g) g[vis_id, [:shape => :record, :label => vis_label]] vis_each do |e| vis_element(e).vis_node(g) end end def vis_label a = [self.class.inspect] vis_each do |e| a << "<#{vis_port(e)}>#{e}" end a.join '|' end def vis_edge(g) unless vis_elements.empty? vis_each do |e| v = vis_element(e) g[vis_id, vis_port(e)] >> g[v.vis_id] v.vis_edge(g) end end end def vis_id "id#{__id__}".to_sym end def vis_port(e) vis_element(e).vis_id end end class Hash def vis_elements keys end def vis_element(e) self[e] end end class Array def vis_elements self end def vis_element(e) self[e] end def vis_each (0...size).each do |e| yield e end end end def vv(obj) gvr = GraphvizR.new 'vv' gvr.graph[:rankdir => 'LR'] obj.vis_node(gvr) obj.vis_edge(gvr) puts gvr.to_dot gvr.output end if __FILE__ == $0 vv(:a => %w(x y z), :b => GraphvizR.new('sample'), :c => nil ) end
それにしても一番手間取ったのは、エッジを引くときの表記に妙な制約があったこと。
gvr = GraphvizR.new 'vv' gvr.node1 >> gvr.node2 gvr.node1 >> gvr.node3 gvr.node1 >> gvr.node4
これはOKだけど、
gvr = GraphvizR.new 'vv' node = gvr.node1 node >> gvr.node2 node >> gvr.node3 node >> gvr.node4
こっちはNG。なぜかというと・・・
class GraphvizR class Edge def initialize(from, to, parent, arrow='->') @attributes = {} @nodes = [from, to] @arrow = arrow @parent = parent (from.is_a?(Array) ? from : [from]).size.times do @parent.statements.pop # ←これ end (to.is_a?(Array) ? to : [to]).size.times do @parent.statements.pop # ←これ end @parent.statements << self end end
statementsというのがGraphvizのステートメントをためておくバッファで、これをpopしているから、直前のノード定義を削除するようになっているようだ。つまり、
- まず、両端ノードを生成する。
- その後、エッジを定義する。
- このとき、ノードは再生成する。
という前提を守る必要があるらしい。あるいは、どうやらサブグラフを使えばよかったかもしれない(別のGraphvizRインスタンスが生成される)が、もうコード書いちゃったからいいや。