rgl 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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