ruby-graphviz 入门(2009-05-26)
自从写了一个关于 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
文件。
![](https://img.haomeiwen.com/i22586/69a49e4fa15d0b99.png)
老规矩,下面来解释一下。
可以用 new
创建一个 graph
对象。new
的时候有这么几个选项:
-
:output
: Output format (Constants::FORMATS) (default : dot) -
:file
: Output file name (default : none) -
:use
: Program to use (Constants::PROGRAMS) (default : dot) -
:path
: Program PATH -
:parent
: Parent graph (default : none) -
:type
: Graph type (Constants::GRAPHTYPE) (default : digraph)
用 output
(有个别名 save
)生成图像。同样,output
也有几个可选项:
-
:output
: Output format (Constants::FORMATS) -
:file
: Output file name -
:use
: Program to use (Constants::PROGRAMS) -
:path
: Program PATH
点
用 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
记录是一个盒子,被分割成横的或竖的格子。格子之前可以加标记,只需把标记放在尖括号 <>
之间。横格子之间用竖杠 |
隔开,竖格子就是外加一对大括号 {}
。看下面的例子:
![](https://img.haomeiwen.com/i22586/2618d3b9d9b3f91c.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" )
再来看一个复杂一点的例子:
![](https://img.haomeiwen.com/i22586/3f964356e6fe551d.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" )
![](https://img.haomeiwen.com/i22586/9d6464e8624931b6.png)
高级技巧
同一个图的三种不同写法:
![](https://img.haomeiwen.com/i22586/80bfda070cefde70.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-graphviz
和 graphviz
的原生 DOT 文件之间存在一一对应的关系。一个典型的 graphviz DOT 文件有三个主要部分:
- 数据头,用来设置点、边、图的属性
- 点定义
- 边定义
所以 ruby-graphviz 文件也是由这三部分组成。我们用一个对比例子来结束本教程。
这个 graphviz 例子来自Mark A. McBride:
![](https://img.haomeiwen.com/i22586/bf7d0897e55074b4.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" )
要想了解更多,请看官方文档。