ruby-graphviz 入门(2009-05-26)

2019-03-03  本文已影响3人  Pope怯懦懦地

自从写了一个关于 Graphviz 的教程以来,我一直觉得它使用起来还不够方便。最近终于找到 Graphviz 的 Ruby 扩展了——ruby-graphviz 。

首先你得安装 Graphviz ,记得装在默认目录下,不然 ruby-graphviz 找不到(我还不知道怎么设置PATH,请指教)。

然后,gem 一下:

gem install ruby-graphviz

接下来将见证神奇的一刻。

require 'graphviz'  # this loads the ruby-graphviz gem

# initialize new Graphviz graph
g = GraphViz::new( "hello_world", "type" => "graph")
hello = g.add_node( "hello" )
world = g.add_node( "world" )
g.add_edge( hello, world )
g.output( "output" => "png", :file => "hello_world.png" )  

Run 一下,你会在源文件所在目录下发现一个hello_world.png 文件。

hello_world.png

老规矩,下面来解释一下。

可以用 new 创建一个 graph 对象。new 的时候有这么几个选项:

output (有个别名 save)生成图像。同样,output 也有几个可选项:

add_node 来加点。可以像这样来设置属性:

n = g.add_node( "node" )
n.set { |_n|
  _n.shape = "box"
  _n.style = "filled"
  _n.color = ".7 .3 1.0"
}

这样,n 将会是一个用<.7, .3, 1.0> 填充的box。

node_count 来对点计数。

[]= 来设置属性值:

g.node["shape"] = "ellipse"
g.node["sides"] = "4"
g.node["peripheries"] = ""
g.node["color"] = "black"
g.node["style"] = ""
g.node["skew"] = "0.0"
g.node["distortion"] = "0.0"

add_edge 加边。

edge_count 来对边计数。

也可以像点那样设置属性:

e = g.add_edge( n1, n2, "label" => "an edge" )
e.set { |_e|
 _e.color = "blue"
 _e.fontcolor = "red"
}

这样,e 将成为一条带红色标注“an edge” 的蓝线。

Record Nodes

记录是一个盒子,被分割成横的或竖的格子。格子之前可以加标记,只需把标记放在尖括号 <> 之间。横格子之间用竖杠 | 隔开,竖格子就是外加一对大括号 {}。看下面的例子:

structs.png
g = GraphViz::new( "G" )
g.node["shape"] = "record"
struct1 = g.add_node( "struct1", "shape" => "record", "label" => "<f0> left|<f1> mid/ dle|<f2> right" )
struct2 = g.add_node( "struct2", "shape" => "record", "label" => "<f0> one|<f1> two" )
struct3 = g.add_node( "struct3", "shape" => "record", "label" => 'hello/nworld |{ b |{c|<here> d|e}| f}| g | h' )
g.add_edge( struct1, struct2 )
g.add_edge( struct1, struct3 )
g.output( :output => "png" , :file => "structs.png" )

再来看一个复杂一点的例子:

complicated_example.png
GraphViz::options( :output => "png", :use => "dot" )
g = GraphViz::new( "structs", :output => "png", :rankdir => "LR" )
g.node["shape"] = "record"
node0 = g.add_node( "node0", :label => "<f0> 0x10ba8| <f1>" )
node1 = g.add_node( "node1", :label => "<f0> 0xf7fc4380| <f1> | <f2> |-1" )
node2 = g.add_node( "node2", :label => "<f0> 0xf7fc44b8| | |2" )
node3 = g.add_node( "node3", :label => "<f0> 3.43322790286038071e-06|44.79998779296875|0" )
node4 = g.add_node( "node4", :label => "<f0> 0xf7fc4380| <f1> | <f2> |2" )
node5 = g.add_node( "node5", :label => "<f0> (nil)| | |-1" )
node6 = g.add_node( "node6", :label => "<f0> 0xf7fc4380| <f1> | <f2> |1" )
node7 = g.add_node( "node7", :label => "<f0> 0xf7fc4380| <f1> | <f2> |2" )
node8 = g.add_node( "node8", :label => "<f0> (nil)| | |-1" )
node9 = g.add_node( "node9", :label => "<f0> (nil)| | |-1" )
node10 = g.add_node( "node10", :label => "<f0> (nil)| <f1> | <f2> |-1" )
node11 = g.add_node( "node11", :label => "<f0> (nil)| <f1> | <f2> |-1" )
node12 = g.add_node( "node12", :label => "<f0> 0xf7fc43e0| | |1" )
                         
g.add_edge( "node0:f0", "node1:f0" )
g.add_edge( "node0:f1", "node2:f0" )
g.add_edge( "node1:f0", "node3:f0" )
g.add_edge( "node1:f1", "node4:f0" )
g.add_edge( "node1:f2", "node5:f0" )
g.add_edge( "node4:f0", "node3:f0" )
g.add_edge( "node4:f1", "node6:f0" )
g.add_edge( "node4:f2", "node10:f0" )
g.add_edge( "node6:f0", "node3:f0" )
g.add_edge( "node6:f1", "node7:f0" )
g.add_edge( "node6:f2", "node9:f0" )
g.add_edge( "node7:f0", "node3:f0" )
g.add_edge( "node7:f1", "node1:f0" )
g.add_edge( "node7:f2", "node8:f0" )
g.add_edge( "node10:f1", "node11:f0" )
g.add_edge( "node10:f2", "node12:f0" )
g.add_edge( "node11:f2", "node1:f0" )
g.output( :file => "complicated_sample.png" )

子图

代码说明一切(注意 add_graph):

graph = GraphViz::new( "G", "output" => "png" )
graph["compound"] = "true"
graph.edge["lhead"] = ""
graph.edge["ltail"] = ""
c0 = graph.add_graph( "cluster0" )
a = c0.add_node( "a" )
b = c0.add_node( "b" )
c = c0.add_node( "c" )
d = c0.add_node( "d" )
c0.add_edge( a, b )
c0.add_edge( a, c )
c0.add_edge( b, d )
c0.add_edge( c, d )
c1 = graph.add_graph( "cluster1" )
e = c1.add_node( "e" )
f = c1.add_node( "f" )
g = c1.add_node( "g" )
c1.add_edge( e, g )
c1.add_edge( e, f )
h = graph.add_node( "h" )
graph.add_edge( b, f, "lhead" => "cluster1" )
graph.add_edge( d, e )
graph.add_edge( c, g, "ltail" => "cluster0", "lhead" => "cluster1" )
graph.add_edge( c, e, "ltail" => "cluster0" )
graph.add_edge( d, h )
graph.output( :file => "subgraph.png" )
subgraph.png

高级技巧

同一个图的三种不同写法:

the_same_sample.png
g = GraphViz::new( "G", "output" => "png" )
g.node["shape"] = "ellipse"
g.node["color"] = "black"
g["color"] = "black"
c0 = g.add_graph( "cluster0" )
c0["label"] = "process #1"
c0["style"] = "filled"
c0["color"] = "lightgrey"
a0 = c0.add_node( "a0", "style" => "filled", "color" => "white" )
a1 = c0.add_node( "a1", "style" => "filled", "color" => "white" )
a2 = c0.add_node( "a2", "style" => "filled", "color" => "white" )
a3 = c0.add_node( "a3", "style" => "filled", "color" => "white" )
c0.add_edge( a0, a1 )
c0.add_edge( a1, a2 )
c0.add_edge( a2, a3 )
c1 = g.add_graph( "cluster1", "label" => "process #2" )
b0 = c1.add_node( "b0", "style" => "filled", "color" => "blue" )
b1 = c1.add_node( "b1", "style" => "filled", "color" => "blue" )
b2 = c1.add_node( "b2", "style" => "filled", "color" => "blue" )
b3 = c1.add_node( "b3", "style" => "filled", "color" => "blue" )
c1.add_edge( b0, b1 )
c1.add_edge( b1, b2 )
c1.add_edge( b2, b3 )
start = g.add_node( "start", "shape" => "Mdiamond" )
endn  = g.add_node( "end",   "shape" => "Msquare" )
g.add_edge( start, a0 )
g.add_edge( start, b0 )
g.add_edge( a1, b3 )
g.add_edge( b2, a3 )
g.add_edge( a3, a0 )
g.add_edge( a3, endn )
g.add_edge( b3, endn )
g.output( :file => "the_same_sample1.png" )

Rubyist 更喜欢的风格:

g = GraphViz::new( "G", "output" => "png" )
g.node[:shape] = "ellipse"
g.node[:color] = "black"
g[:color] = "black"
g.cluster0( ) do |cluster|
  cluster[:label] = "process #1"
  cluster[:style] = "filled"
  cluster[:color] = "lightgrey"
  
  cluster.a0 :style => "filled", :color => "white"
  cluster.a1 :style => "filled", :color => "white"
  cluster.a2 :style => "filled", :color => "white"
  cluster.a3 :style => "filled", :color => "white"
  
  cluster.a0 << cluster.a1
  cluster.a1 << cluster.a2
  cluster.a2 << cluster.a3
end
g.cluster1( :label => "process #2" ) do |cluster|
  cluster.b0 :style => "filled", :color => "blue"
  cluster.b1 :style => "filled", :color => "blue"
  cluster.b2 :style => "filled", :color => "blue"
  cluster.b3 :style => "filled", :color => "blue"
  
  cluster.b0 << cluster.b1
  cluster.b1 << cluster.b2
  cluster.b2 << cluster.b3
end
g.start :shape => "Mdiamond"
g.endn :shape => "Msquare", :label => "end"
g.start << g.cluster0.a0
g.start << g.cluster1.b0
g.cluster0.a1 << g.cluster1.b3
g.cluster1.b2 << g.cluster0.a3
g.cluster0.a3 << g.cluster0.a0
g.cluster0.a3 << g.endn
g.cluster1.b3 << g.endn
g.output( :file => "the_same_sample2.png" )

Ruby geek 的写法:

GraphViz::new( "G", "output" => "png" ) { |graph|
  graph.node[:shape] = "ellipse"
  graph.node[:color] = "black"
  
  graph[:color] = "black"
  
  graph.cluster0( ) do |cluster|
    cluster[:label] = "process #1"
    cluster[:style] = "filled"
    cluster[:color] = "lightgrey"
    
    cluster.a0 :style => "filled", :color => "white"
    cluster.a1 :style => "filled", :color => "white"
    cluster.a2 :style => "filled", :color => "white"
    cluster.a3 :style => "filled", :color => "white"
    
    cluster.a0 << cluster.a1
    cluster.a1 << cluster.a2
    cluster.a2 << cluster.a3
  end
  
  graph.cluster1( :label => "process #2" ) do |cluster|
    cluster.b0 :style => "filled", :color => "blue"
    cluster.b1 :style => "filled", :color => "blue"
    cluster.b2 :style => "filled", :color => "blue"
    cluster.b3 :style => "filled", :color => "blue"
    
    cluster.b0 << cluster.b1
    cluster.b1 << cluster.b2
    cluster.b2 << cluster.b3
  end
  
  graph.start :shape => "Mdiamond"
  graph.endn :shape => "Msquare", :label => "end"
  
  graph.start << graph.cluster0.a0
  graph.start << graph.cluster1.b0
  graph.cluster0.a1 << graph.cluster1.b3
  graph.cluster1.b2 << graph.cluster0.a3
  graph.cluster0.a3 << graph.cluster0.a0
  graph.cluster0.a3 << graph.endn
  graph.cluster1.b3 << graph.endn
}.output( :path => 'C:/Program Files/Graphviz2.20/bin', :file => "the_same_sample3.png" )

小结

ruby-graphvizgraphviz 的原生 DOT 文件之间存在一一对应的关系。一个典型的 graphviz DOT 文件有三个主要部分:

  1. 数据头,用来设置点、边、图的属性
  2. 点定义
  3. 边定义

所以 ruby-graphviz 文件也是由这三部分组成。我们用一个对比例子来结束本教程。

这个 graphviz 例子来自Mark A. McBride

final sample.png
digraph graph_example {
 /***** GLOBAL SETTINGS *****/
 graph          [rotate=0, rankdir="LR"]
 node           [color="#333333", style=filled,
                 shape=box, fontname="Trebuchet MS"]
 edge           [color="#666666", arrowhead="open", 
                 fontname="Trebuchet MS", fontsize="11"]
 node           [fillcolor="#294b76", fontcolor="white"]
 
/***** Nodes *****/
 a              [label="Node A"]
 b              [label="Node B"]
 c              [label="Node C"]
 d              [label="Node D", fillcolor="#116611"]
 /***** Edges *****/
 a              -> b
 b              -> c
 c              -> d
 b              -> d
 a              -> d [label="direct path"]
}

我用Ruby 改写了这个例子:

require "graphviz"
g = GraphViz::new( "G", "output" => "png" )
# ***** GLOBAL SETTINGS *****
g[:rotate] = "0"
g[:rankdir] = "LR"
g.node[:color] = "#333333"
g.node[:style] = "filled"
g.node[:shape] = "box"
g.node[:fontname] = "Trebuchet MS"
g.node[:fillcolor] = "#294b76"
g.node[:fontcolor] = "white"
g.edge[:color] = "#666666"
g.edge[:arrowhead] = "open"
g.edge[:fontname] = "Trebuchet MS"
g.edge[:fontsize] = "11"
# ***** Nodes *****
a = g.add_node( "a", :label => "Node A" )
b = g.add_node( "b", :label => "Node B" )
c = g.add_node( "c", :label => "Node C" )
d = g.add_node( "d", :label => "Node D", :fillcolor => "#116611" )
# ***** Edges *****
g.add_edge( a, b )
g.add_edge( b, c )
g.add_edge( c, d )
g.add_edge( b, d )
g.add_edge( a, d, :label => "direct path" )
g.output( :file => "f:/a.png" )

要想了解更多,请看官方文档。

上一篇 下一篇

猜你喜欢

热点阅读