rgl 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ require 'rgl/base'
2
+
3
+ module RGL
4
+
5
+ # BGL defines the concept BidirectionalGraph as follows:
6
+ #
7
+ # The BidirectionalGraph concept refines IncidenceGraph and adds the
8
+ # requirement for efficient access to the in-edges of each vertex. This
9
+ # concept is separated from IncidenceGraph because, for directed graphs,
10
+ # efficient access to in-edges typically requires more storage space,
11
+ # and many algorithms do not require access to in-edges. For undirected
12
+ # graphs, this is not an issue; because the in_edges() and out_edges()
13
+ # functions are the same, they both return the edges incident to the vertex.
14
+ module BidirectionalGraph
15
+ include Graph
16
+
17
+ # Iterator providing access to the in-edges (for directed graphs) or incident
18
+ # edges (for undirected graphs) of vertex _v_. For both directed and
19
+ # undirected graphs, the target of an out-edge is required to be vertex _v_
20
+ # and the source is required to be a vertex that is adjacent to _v_.
21
+ def each_in_neighbor (v)
22
+ raise NotImplementedError
23
+ yield u
24
+ end
25
+
26
+ # Returns the number of in-edges (for directed graphs) or the number of
27
+ # incident edges (for undirected graphs) of vertex _v_.
28
+ def in_degree (v)
29
+ r = 0;
30
+ each_in_neighbor(v) { |u| r += 1}
31
+ r
32
+ end
33
+
34
+ # Returns the number of in-edges plus out-edges (for directed graphs) or the
35
+ # number of incident edges (for undirected graphs) of vertex _v_.
36
+ def degree (v)
37
+ in_degree(v) + out_degree(v)
38
+ end
39
+ end
40
+ end
@@ -1,6 +1,6 @@
1
1
  # dot.rb
2
2
  #
3
- # $Id: dot.rb,v 1.5 2005/02/04 22:41:46 monora Exp $
3
+ # $Id: dot.rb,v 1.7 2008/02/26 06:01:22 javanthropus Exp $
4
4
  #
5
5
  # Minimal Dot support, based on Dave Thomas's dot module (included in rdoc).
6
6
  # rdot.rb is a modified version which also contains support for undirected
@@ -23,13 +23,13 @@ module RGL
23
23
  edge_class = directed? ? DOT::DOTDirectedEdge : DOT::DOTEdge
24
24
  each_vertex do |v|
25
25
  name = v.to_s
26
- graph << DOT::DOTNode.new('name' => '"' + name + '"',
26
+ graph << DOT::DOTNode.new('name' => name,
27
27
  'fontsize' => fontsize,
28
28
  'label' => name)
29
29
  end
30
30
  each_edge do |u,v|
31
- graph << edge_class.new('from' => '"'+ u.to_s + '"',
32
- 'to' => '"'+ v.to_s + '"',
31
+ graph << edge_class.new('from' => u.to_s,
32
+ 'to' => v.to_s,
33
33
  'fontsize' => fontsize)
34
34
  end
35
35
  graph
@@ -41,7 +41,7 @@ module RGL
41
41
  s << to_dot_graph(params).to_s << "\n"
42
42
  end
43
43
 
44
- # Call +dotty+ for the graph which is written to the file 'graph.dot'
44
+ # Call dotty[http://www.graphviz.org] for the graph which is written to the file 'graph.dot'
45
45
  # in the # current directory.
46
46
 
47
47
  def dotty (params = {})
@@ -52,8 +52,8 @@ module RGL
52
52
  system("dotty", dotfile)
53
53
  end
54
54
 
55
- # Use +do+ to create a graphical representation of the graph. Returns the
56
- # filename of the graphics file.
55
+ # Use dot[http://www.graphviz.org] to create a graphical representation of
56
+ # the graph. Returns the filename of the graphics file.
57
57
 
58
58
  def write_to_graphic_file (fmt='png', dotfile="graph")
59
59
  src = dotfile + ".dot"
@@ -0,0 +1,13 @@
1
+ module Enumerable
2
+ # Fixnum()
3
+ #
4
+ # Return the number of elements of the Enumerable. Same as _size_ but not all
5
+ # Enumerables implement size.
6
+ #--
7
+ # Should we call the methods _size_?
8
+ def length
9
+ inject(0) do |sum,v|
10
+ sum + 1
11
+ end
12
+ end
13
+ end
@@ -16,26 +16,20 @@ require 'rexml/document'
16
16
  require 'rexml/streamlistener'
17
17
 
18
18
  module RGL
19
-
20
- # Module GraphXML adds to each class, including module MutableGraph, a class
21
- # method from_graphxml.
22
- #
23
- # Attention: Because append_features is used to provide the functionality,
24
- # GraphXML must be loaded before the concrete class including MutableGraph
25
- # is loaded.
26
-
27
- module GraphXML
28
-
19
+ module MutableGraph
20
+ # Used to parse a subset of GraphML into an RGL graph implementation.
29
21
  class MutableGraphParser
30
-
31
22
  include REXML::StreamListener
32
23
 
33
- attr_reader :graph
34
-
24
+ # First resets +graph+ to be empty and stores a reference for use with
25
+ # #tag_start.
35
26
  def initialize (graph)
36
27
  @graph = graph
28
+ @graph.remove_vertices(@graph.vertices)
37
29
  end
38
30
 
31
+ # Processes incoming edge and node elements from GraphML in order to
32
+ # populate the graph given to #new.
39
33
  def tag_start (name, attrs)
40
34
  case name
41
35
  when 'edge'
@@ -44,20 +38,14 @@ module RGL
44
38
  @graph.add_vertex(attrs['id'])
45
39
  end
46
40
  end
47
-
48
41
  end # class MutableGraphParser
49
-
50
- def MutableGraph.append_features (includingClass)
51
- super
52
-
53
- # Create a new MutableGraph from the XML-Source _source_.
54
42
 
55
- def includingClass.from_graphxml (source)
56
- listener = MutableGraphParser.new(self.new)
57
- REXML::Document.parse_stream(source, listener)
58
- listener.graph
59
- end
43
+ # Initializes an RGL graph from a subset of the GraphML format given in
44
+ # +source+ (see http://www.graphdrawing.org/graphml).
45
+ def from_graphxml(source)
46
+ listener = MutableGraphParser.new(self)
47
+ REXML::Document.parse_stream(source, listener)
48
+ self
60
49
  end
61
-
62
- end # module GraphXML
50
+ end # module MutableGraph
63
51
  end # module RGL
@@ -6,7 +6,6 @@ module RGL
6
6
 
7
7
  # A MutableGraph can be changed via the addition or removal of edges and
8
8
  # vertices.
9
-
10
9
  module MutableGraph
11
10
 
12
11
  include Graph
@@ -71,5 +70,34 @@ module RGL
71
70
  a.each { |v| remove_vertex v }
72
71
  end
73
72
 
73
+ # Returns all minimum cycles that pass through a give vertex.
74
+ # The format is an Array of cycles, with each cycle being an Array
75
+ # of vertices in the cycle.
76
+ def cycles_with_vertex(vertex)
77
+ cycles_with_vertex_helper(vertex, vertex, [])
78
+ end
79
+
80
+ protected
81
+ def cycles_with_vertex_helper(vertex, start, visited) #:nodoc:
82
+ adjacent_vertices(start).reject {|x| visited.include?(x)}.inject([]) do |acc, adj|
83
+ local_visited = Array.new(visited) << adj
84
+ acc << local_visited if (adj==vertex)
85
+ acc = acc + cycles_with_vertex_helper(vertex,adj,local_visited)
86
+ end
87
+ end
88
+
89
+ public
90
+ # Returns an array of all minimum cycles in a graph
91
+ #
92
+ # This is not an efficient implementation O(n^4) and could
93
+ # be done using Minimum Spanning Trees. Hint. Hint.
94
+ def cycles
95
+ g = self.clone
96
+ self.inject([]) do |acc, v|
97
+ acc = acc.concat(g.cycles_with_vertex(v))
98
+ g.remove_vertex(v); acc
99
+ end
100
+ end
101
+
74
102
  end # module MutableGraph
75
103
  end # module RGL
@@ -1,33 +1,16 @@
1
- # rdot.rb
2
- #
3
- # $Id: rdot.rb,v 1.4 2005/03/26 15:06:36 wsdng Exp $
4
- #
5
1
  # This is a modified version of dot.rb from Dave Thomas's rdoc project. I
6
2
  # renamed it to rdot.rb to avoid collision with an installed rdoc/dot.
7
3
  #
8
4
  # It also supports undirected edges.
9
5
 
10
6
  module DOT
11
-
12
- # These glogal vars are used to make nice graph source.
13
-
14
- $tab = ' '
15
- $tab2 = $tab * 2
16
-
17
- # if we don't like 4 spaces, we can change it any time
18
7
 
19
- def change_tab (t)
20
- $tab = t
21
- $tab2 = t * 2
22
- end
23
-
24
8
  # options for node declaration
25
9
 
26
10
  NODE_OPTS = [
27
11
  # attributes due to
28
12
  # http://www.graphviz.org/Documentation/dotguide.pdf
29
- # March, 26, 2005
30
- 'bottomlabel', # auxiliary label for nodes of shape M*
13
+ # February 23, 2008
31
14
  'color', # default: black; node shape color
32
15
  'comment', # any string (format-dependent)
33
16
  'distortion', # default: 0.0; node distortion for shape=polygon
@@ -36,7 +19,7 @@ module DOT
36
19
  'fontcolor', # default: black; type face color
37
20
  'fontname', # default: Times-Roman; font family
38
21
  'fontsize', #default: 14; point size of label
39
- 'group', # name of nodes group
22
+ 'group', # name of node's group
40
23
  'height', # default: .5; height in inches
41
24
  'label', # default: node name; any string
42
25
  'layer', # default: overlay range; all, id or id:id
@@ -48,16 +31,17 @@ module DOT
48
31
  'sides', # default: 4; number of sides for shape=polygon
49
32
  'skew' , # default: 0.0; skewing of node for shape=polygon
50
33
  'style', # graphics options, e.g. bold, dotted, filled; cf. Section 2.3
51
- 'toplabel', # auxiliary label for nodes of shape M*
52
34
  'URL', # URL associated with node (format-dependent)
53
35
  'width', # default: .75; width in inches
54
36
  'z', #default: 0.0; z coordinate for VRML output
55
37
 
56
38
  # maintained for backward compatibility or rdot internal
39
+ 'bottomlabel', # auxiliary label for nodes of shape M*
57
40
  'bgcolor',
58
- 'rank'
41
+ 'rank',
42
+ 'toplabel' # auxiliary label for nodes of shape M*
59
43
  ]
60
-
44
+
61
45
  # options for edge declaration
62
46
 
63
47
  EDGE_OPTS = [
@@ -97,192 +81,286 @@ module DOT
97
81
  # maintained for backward compatibility or rdot internal
98
82
  'id'
99
83
  ]
100
-
84
+
101
85
  # options for graph declaration
102
86
 
103
87
  GRAPH_OPTS = [
104
- 'bgcolor',
105
- 'center', 'clusterrank', 'color', 'concentrate',
106
- 'fontcolor', 'fontname', 'fontsize',
107
- 'label', 'layerseq',
108
- 'margin', 'mclimit',
109
- 'nodesep', 'nslimit',
110
- 'ordering', 'orientation',
111
- 'page',
112
- 'rank', 'rankdir', 'ranksep', 'ratio',
113
- 'size'
114
- ]
115
-
116
- # a root class for any element in dot notation
117
-
118
- class DOTSimpleElement
119
-
120
- attr_accessor :name
121
-
122
- def initialize (params = {})
123
- @label = params['name'] ? params['name'] : ''
124
- end
125
-
126
- def to_s
127
- @name
128
- end
129
- end
130
-
131
- # an element that has options ( node, edge, or graph )
88
+ 'bgcolor', # background color for drawing, plus initial fill color
89
+ 'center', # default: false; center draing on page
90
+ 'clusterrank', # default: local; may be "global" or "none"
91
+ 'color', # default: black; for clusters, outline color, and fill color if
92
+ # fillcolor not defined
93
+ 'comment', # any string (format-dependent)
94
+ 'compound', # default: false; allow edges between clusters
95
+ 'concentrate', # default: false; enables edge concentrators
96
+ 'fillcolor', # default: black; cluster fill color
97
+ 'fontcolor', # default: black; type face color
98
+ 'fontname', # default: Times-Roman; font family
99
+ 'fontpath', # list of directories to search for fonts
100
+ 'fontsize', # default: 14; point size of label
101
+ 'label', # any string
102
+ 'labeljust', # default: centered; "l" and "r" for left- and right-justified
103
+ # cluster labels, respectively
104
+ 'labelloc', # default: top; "t" and "b" for top- and bottom-justified
105
+ # cluster labels, respectively
106
+ 'layers', # id:id:id...
107
+ 'margin', # default: .5; margin included in page, inches
108
+ 'mclimit', # default: 1.0; scale factor for mincross iterations
109
+ 'nodesep', # default: .25; separation between nodes, in inches.
110
+ 'nslimit', # if set to "f", bounds network simplex iterations by
111
+ # (f)(number of nodes) when setting x-coordinates
112
+ 'nslimit1', # if set to "f", bounds network simplex iterations by
113
+ # (f)(number of nodes) when ranking nodes
114
+ 'ordering', # if "out" out edge order is preserved
115
+ 'orientation', # default: portrait; if "rotate" is not used and the value is
116
+ # "landscape", use landscape orientation
117
+ 'page', # unit of pagination, e.g. "8.5,11"
118
+ 'rank', # "same", "min", "max", "source", or "sink"
119
+ 'rankdir', # default: TB; "LR" (left to right) or "TB" (top to bottom)
120
+ 'ranksep', # default: .75; separation between ranks, in inches.
121
+ 'ratio', # approximate aspect ratio desired, "fill" or "auto"
122
+ 'samplepoints', # default: 8; number of points used to represent ellipses
123
+ # and circles on output
124
+ 'searchsize', # default: 30; maximum edges with negative cut values to check
125
+ # when looking for a minimum one during network simplex
126
+ 'size', # maximum drawing size, in inches
127
+ 'style', # graphics options, e.g. "filled" for clusters
128
+ 'URL', # URL associated with graph (format-dependent)
132
129
 
133
- class DOTElement < DOTSimpleElement
130
+ # maintained for backward compatibility or rdot internal
131
+ 'layerseq'
132
+ ]
134
133
 
135
- # attr_reader :parent
134
+ # Ancestor of DOTEdge, DOTNode, and DOTGraph.
135
+ class DOTElement
136
136
  attr_accessor :name, :options
137
137
 
138
- def initialize (params = {}, option_list = [])
139
- super(params)
140
- @name = params['name'] ? params['name'] : nil
141
- @parent = params['parent'] ? params['parent'] : nil
138
+ def initialize (params = {}, option_list = []) # :nodoc:
139
+ @name = params['name'] ? params['name'] : nil
142
140
  @options = {}
143
141
  option_list.each{ |i|
144
142
  @options[i] = params[i] if params[i]
145
143
  }
146
- @options['label'] ||= @name if @name != 'node'
147
- end
148
-
149
- def each_option
150
- @options.each{ |i| yield i }
151
144
  end
152
145
 
153
- def each_option_pair
154
- @options.each_pair{ |key, val| yield key, val }
155
- end
146
+ private
147
+ # Returns the string given in _id_ within quotes if necessary. Special
148
+ # characters are escaped as necessary.
149
+ def quote_ID(id)
150
+ # Ensure that the ID is a string.
151
+ id = id.to_s
152
+
153
+ # Return the ID verbatim if it looks like a name, a number, or HTML.
154
+ return id if id =~ /^([[:alpha:]_][[:alnum:]_]*|-?(\.[[:digit:]]+|[[:digit:]]+(\.[[:digit:]]*)?)|<.*>)$/
156
155
 
157
- #def parent=( thing )
158
- # @parent.delete( self ) if defined?( @parent ) and @parent
159
- # @parent = thing
160
- #end
156
+ # Return a quoted version of the ID otherwise.
157
+ '"' + id.gsub('\\', '\\\\\\\\').gsub('"', '\\\\"') + '"'
158
+ end
161
159
 
160
+ # Returns the string given in _label_ within quotes if necessary. Special
161
+ # characters are escaped as necessary. Labels get special treatment in
162
+ # order to handle embedded *\n*, *\r*, and *\l* sequences which are copied
163
+ # into the new string verbatim.
164
+ def quote_label(label)
165
+ # Ensure that the label is a string.
166
+ label = label.to_s
167
+
168
+ # Return the label verbatim if it looks like a name, a number, or HTML.
169
+ return label if label =~ /^([[:alpha:]_][[:alnum:]_]*|-?(\.[[:digit:]]+|[[:digit:]]+(\.[[:digit:]]*)?)|<.*>)$/
170
+
171
+ # Return a quoted version of the label otherwise.
172
+ '"' + label.split(/(\\n|\\r|\\l)/).collect do |part|
173
+ case part
174
+ when "\\n", "\\r", "\\l"
175
+ part
176
+ else
177
+ part.gsub('\\', '\\\\\\\\').gsub('"', '\\\\"').gsub("\n", '\\n')
178
+ end
179
+ end.join + '"'
180
+ end
162
181
  end
163
-
164
-
165
- # This is used when we build nodes that have shape=record
166
- # ports don't have options :)
167
-
168
- class DOTPort < DOTSimpleElement
169
-
170
- attr_accessor :label
171
-
172
- def initialize (params = {})
173
- super(params)
174
- @name = params['label'] ? params['label'] : ''
182
+
183
+
184
+ # Ports are used when a DOTNode instance has its `shape' option set to
185
+ # _record_ or _Mrecord_. Ports can be nested.
186
+ class DOTPort
187
+ attr_accessor :name, :label, :ports
188
+
189
+ # Create a new port with either an optional name and label or a set of
190
+ # nested ports.
191
+ #
192
+ # :call-seq:
193
+ # new(name = nil, label = nil)
194
+ # new(ports)
195
+ #
196
+ # A +nil+ value for +name+ is valid; otherwise, it must be a String or it
197
+ # will be interpreted as +ports+.
198
+ def initialize (name_or_ports = nil, label = nil)
199
+ if name_or_ports.nil? or name_or_ports.kind_of?(String) then
200
+ @name = name_or_ports
201
+ @label = label
202
+ @ports = nil
203
+ else
204
+ @ports = name_or_ports
205
+ @name = nil
206
+ @label = nil
207
+ end
175
208
  end
176
209
 
210
+ # Returns a string representation of this port. If ports is a non-empty
211
+ # Enumerable, a nested ports representation is returned; otherwise, a
212
+ # name-label representation is returned.
177
213
  def to_s
178
- ( @name && @name != "" ? "<#{@name}>" : "" ) + "#{@label}"
214
+ if @ports.nil? or @ports.empty? then
215
+ n = (name.nil? or name.empty?) ? '' : "<#{name}>"
216
+ n + ((n.empty? or label.nil? or label.empty?) ? '' : ' ') + label.to_s
217
+ else
218
+ '{' + @ports.collect {|p| p.to_s}.join(' | ') + '}'
219
+ end
179
220
  end
180
221
  end
181
-
182
- # node element
183
222
 
223
+ # A node representation. Edges are drawn between nodes. The rendering of a
224
+ # node depends upon the options set for it.
184
225
  class DOTNode < DOTElement
226
+ attr_accessor :ports
185
227
 
186
- @ports
187
-
228
+ # Creates a new DOTNode with the _params_ Hash providing settings for all
229
+ # node options. The _option_list_ parameter restricts those options to the
230
+ # list of valid names it contains. The exception to this is the _ports_
231
+ # option which, if specified, must be an Enumerable containing a list of
232
+ # ports.
188
233
  def initialize (params = {}, option_list = NODE_OPTS)
189
234
  super(params, option_list)
190
235
  @ports = params['ports'] ? params['ports'] : []
191
236
  end
192
237
 
193
- def each_port
194
- @ports.each { |i| yield i }
195
- end
196
-
197
- def << (thing)
198
- @ports << thing
199
- end
200
-
201
- def push (thing)
202
- @ports.push(thing)
203
- end
204
-
205
- def pop
206
- @ports.pop
207
- end
238
+ # Returns a string representation of this node which is consumable by the
239
+ # graphviz tools +dot+ and +neato+. The _leader_ parameter is used to indent
240
+ # every line of the returned string, and the _indent_ parameter is used to
241
+ # additionally indent nested items.
242
+ def to_s (leader = '', indent = ' ')
243
+ label_option = nil
244
+ if @options['shape'] =~ /^M?record$/ && !@ports.empty? then
245
+ # Ignore the given label option in this case since the ports should each
246
+ # provide their own name/label.
247
+ label_option = leader + indent + "#{quote_ID('label')} = #{quote_ID(@ports.collect { |port| port.to_s }.join(" | "))}"
248
+ elsif @options['label'] then
249
+ # Otherwise, use the label when given one.
250
+ label_option = leader + indent + "#{quote_ID('label')} = #{quote_label(@options['label'])}"
251
+ end
208
252
 
209
- def to_s (t = '')
210
-
211
- # This code is totally incomprehensible; it needs to be replaced!
212
-
213
- label = @options['shape'] != 'record' && @ports.length == 0 ?
214
- @options['label'] ?
215
- t + $tab + "label = \"#{@options['label']}\"\n" :
216
- '' :
217
- t + $tab + 'label = "' + " \\\n" +
218
- t + $tab2 + "#{@options['label']}| \\\n" +
219
- @ports.collect{ |i|
220
- t + $tab2 + i.to_s
221
- }.join( "| \\\n" ) + " \\\n" +
222
- t + $tab + '"' + "\n"
223
-
224
- t + "#{@name} [\n" +
225
- @options.to_a.collect{ |i|
226
- i[1] && i[0] != 'label' ?
227
- t + $tab + "#{i[0]} = #{i[1]}" : nil
228
- }.compact.join( ",\n" ) + ( label != '' ? ",\n" : "\n" ) +
229
- label +
230
- t + "]\n"
253
+ # Convert all the options except `label' and options with nil values
254
+ # straight into name = value pairs. Then toss out any resulting nil
255
+ # entries in the final array.
256
+ stringified_options = @options.collect do |name, val|
257
+ unless name == 'label' || val.nil? then
258
+ leader + indent + "#{quote_ID(name)} = #{quote_ID(val)}"
259
+ end
260
+ end.compact
261
+ # Append the specially computed label option.
262
+ stringified_options.push(label_option) unless label_option.nil?
263
+ # Join them all together.
264
+ stringified_options = stringified_options.join(",\n")
265
+
266
+ # Put it all together into a single string with indentation and return the
267
+ # result.
268
+ if stringified_options.empty? then
269
+ return leader + quote_ID(@name) unless @name.nil?
270
+ return nil
271
+ else
272
+ return leader + (@name.nil? ? '' : quote_ID(@name) + " ") + "[\n" +
273
+ stringified_options + "\n" +
274
+ leader + "]"
231
275
  end
276
+ end
232
277
 
233
278
  end # class DOTNode
234
279
 
235
- # A subgraph element is the same to graph, but has another header in dot
236
- # notation.
237
-
238
- class DOTSubgraph < DOTElement
239
-
240
- @nodes
241
- @dot_string
280
+ # A graph representation. Whether or not it is rendered as directed or
281
+ # undirected depends on which of the programs *dot* or *neato* is used to
282
+ # process and render the graph.
283
+ class DOTGraph < DOTElement
242
284
 
285
+ # Creates a new DOTGraph with the _params_ Hash providing settings for all
286
+ # graph options. The _option_list_ parameter restricts those options to the
287
+ # list of valid names it contains. The exception to this is the _elements_
288
+ # option which, if specified, must be an Enumerable containing a list of
289
+ # nodes, edges, and/or subgraphs.
243
290
  def initialize (params = {}, option_list = GRAPH_OPTS)
244
291
  super(params, option_list)
245
- @nodes = params['nodes'] ? params['nodes'] : []
292
+ @elements = params['elements'] ? params['elements'] : []
246
293
  @dot_string = 'graph'
247
294
  end
248
295
 
249
- def each_node
250
- @nodes.each{ |i| yield i }
296
+ # Calls _block_ once for each node, edge, or subgraph contained by this
297
+ # graph, passing the node, edge, or subgraph to the block.
298
+ #
299
+ # :call-seq:
300
+ # graph.each_element {|element| block} -> graph
301
+ #
302
+ def each_element (&block)
303
+ @elements.each(&block)
304
+ self
251
305
  end
252
306
 
253
- def << (thing)
254
- @nodes << thing
255
- end
256
-
257
- def push (thing)
258
- @nodes.push( thing )
307
+ # Adds a new node, edge, or subgraph to this graph.
308
+ #
309
+ # :call-seq:
310
+ # graph << element -> graph
311
+ #
312
+ def << (element)
313
+ @elements << element
314
+ self
259
315
  end
260
-
316
+ alias :push :<<
317
+
318
+ # Removes the most recently added node, edge, or subgraph from this graph
319
+ # and returns it.
320
+ #
321
+ # :call-seq:
322
+ # graph.pop -> element
323
+ #
261
324
  def pop
262
- @nodes.pop
325
+ @elements.pop
263
326
  end
264
327
 
265
- def to_s (t = '')
266
- hdr = t + "#{@dot_string} #{@name} {\n"
267
-
268
- options = @options.to_a.collect{ |name, val|
269
- val && name != 'label' ?
270
- t + $tab + "#{name} = #{val}" :
271
- name ? t + $tab + "#{name} = \"#{val}\"" : nil
272
- }.compact.join( "\n" ) + "\n"
273
-
274
- nodes = @nodes.collect{ |i|
275
- i.to_s( t + $tab )
276
- }.join( "\n" ) + "\n"
277
- hdr + options + nodes + t + "}\n"
328
+ # Returns a string representation of this graph which is consumable by the
329
+ # graphviz tools +dot+ and +neato+. The _leader_ parameter is used to indent
330
+ # every line of the returned string, and the _indent_ parameter is used to
331
+ # additionally indent nested items.
332
+ def to_s (leader = '', indent = ' ')
333
+ hdr = leader + @dot_string + (@name.nil? ? '' : ' ' + quote_ID(@name)) + " {\n"
334
+
335
+ options = @options.to_a.collect do |name, val|
336
+ unless val.nil? then
337
+ if name == 'label' then
338
+ leader + indent + "#{quote_ID(name)} = #{quote_label(val)}"
339
+ else
340
+ leader + indent + "#{quote_ID(name)} = #{quote_ID(val)}"
341
+ end
342
+ end
343
+ end.compact.join( "\n" )
344
+
345
+ elements = @elements.collect do |element|
346
+ element.to_s(leader + indent, indent)
347
+ end.join("\n\n")
348
+ hdr + (options.empty? ? '' : options + "\n\n") +
349
+ (elements.empty? ? '' : elements + "\n") + leader + "}"
278
350
  end
279
351
 
280
- end # class DOTSubgraph
281
-
282
- # This is a graph.
352
+ end # class DOTGraph
283
353
 
284
- class DOTDigraph < DOTSubgraph
354
+ # A digraph is a directed graph representation which is the same as a DOTGraph
355
+ # except that its header in dot notation has an identifier of _digraph_
356
+ # instead of _graph_.
357
+ class DOTDigraph < DOTGraph
285
358
 
359
+ # Creates a new DOTDigraph with the _params_ Hash providing settings for all
360
+ # graph options. The _option_list_ parameter restricts those options to the
361
+ # list of valid names it contains. The exception to this is the _elements_
362
+ # option which, if specified, must be an Enumerable containing a list of
363
+ # nodes, edges, and/or subgraphs.
286
364
  def initialize (params = {}, option_list = GRAPH_OPTS)
287
365
  super(params, option_list)
288
366
  @dot_string = 'digraph'
@@ -290,38 +368,78 @@ module DOT
290
368
 
291
369
  end # class DOTDigraph
292
370
 
293
- # This is an edge.
371
+ # A subgraph is a nested graph element and is the same as a DOTGraph except
372
+ # that its header in dot notation has an identifier of _subgraph_ instead of
373
+ # _graph_.
374
+ class DOTSubgraph < DOTGraph
375
+
376
+ # Creates a new DOTSubgraph with the _params_ Hash providing settings for
377
+ # all graph options. The _option_list_ parameter restricts those options to
378
+ # list of valid names it contains. The exception to this is the _elements_
379
+ # option which, if specified, must be an Enumerable containing a list of
380
+ # nodes, edges, and/or subgraphs.
381
+ def initialize (params = {}, option_list = GRAPH_OPTS)
382
+ super(params, option_list)
383
+ @dot_string = 'subgraph'
384
+ end
385
+
386
+ end # class DOTSubgraph
294
387
 
388
+ # This is an undirected edge representation.
295
389
  class DOTEdge < DOTElement
296
390
 
297
- attr_accessor :from, :to
391
+ # A node or subgraph reference or instance to be used as the starting point
392
+ # for an edge.
393
+ attr_accessor :from
394
+ # A node or subgraph reference or instance to be used as the ending point
395
+ # for an edge.
396
+ attr_accessor :to
298
397
 
398
+ # Creates a new DOTEdge with the _params_ Hash providing settings for all
399
+ # edge options. The _option_list_ parameter restricts those options to the
400
+ # list of valid names it contains.
299
401
  def initialize (params = {}, option_list = EDGE_OPTS)
300
402
  super(params, option_list)
301
403
  @from = params['from'] ? params['from'] : nil
302
404
  @to = params['to'] ? params['to'] : nil
303
405
  end
304
-
305
- def edge_link
306
- '--'
307
- end
308
406
 
309
- def to_s (t = '')
310
- t + "#{@from} #{edge_link} #{to} [\n" +
311
- @options.to_a.collect{ |i|
312
- i[1] && i[0] != 'label' ?
313
- t + $tab + "#{i[0]} = #{i[1]}" :
314
- i[1] ? t + $tab + "#{i[0]} = \"#{i[1]}\"" : nil
315
- }.compact.join( "\n" ) + "\n" + t + "]\n"
407
+ # Returns a string representation of this edge which is consumable by the
408
+ # graphviz tools +dot+ and +neato+. The _leader_ parameter is used to indent
409
+ # every line of the returned string, and the _indent_ parameter is used to
410
+ # additionally indent nested items.
411
+ def to_s (leader = '', indent = ' ')
412
+ stringified_options = @options.collect do |name, val|
413
+ unless val.nil? then
414
+ leader + indent + "#{quote_ID(name)} = #{quote_ID(val)}"
415
+ end
416
+ end.compact.join( ",\n" )
417
+
418
+ f_s = @from || ''
419
+ t_s = @to || ''
420
+ if stringified_options.empty? then
421
+ leader + quote_ID(f_s) + ' ' + edge_link + ' ' + quote_ID(t_s)
422
+ else
423
+ leader + quote_ID(f_s) + ' ' + edge_link + ' ' + quote_ID(t_s) + " [\n" +
424
+ stringified_options + "\n" +
425
+ leader + "]"
426
+ end
316
427
  end
317
428
 
429
+ private
430
+ def edge_link
431
+ '--'
432
+ end
433
+
318
434
  end # class DOTEdge
319
-
435
+
436
+ # A directed edge representation otherwise identical to DOTEdge.
320
437
  class DOTDirectedEdge < DOTEdge
321
438
 
322
- def edge_link
323
- '->'
324
- end
439
+ private
440
+ def edge_link
441
+ '->'
442
+ end
325
443
 
326
444
  end # class DOTDirectedEdge
327
445
  end # module DOT