rgl 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/ChangeLog +19 -10
  2. data/Gemfile +3 -0
  3. data/{README → README.rdoc} +70 -98
  4. data/Rakefile +44 -150
  5. data/examples/canvas.rb +63 -64
  6. data/examples/examples.rb +42 -42
  7. data/examples/graph.dot +46 -0
  8. data/examples/images/example.jpg +0 -0
  9. data/examples/images/module_graph.jpg +0 -0
  10. data/examples/images/rgl_modules.png +0 -0
  11. data/examples/{insel-der-tausend-gefahren.rb → insel_der_tausend_gefahren.rb} +18 -19
  12. data/examples/north.rb +2 -2
  13. data/examples/north2.rb +11 -11
  14. data/examples/rdep-rgl.rb +218 -222
  15. data/lib/rgl/adjacency.rb +78 -74
  16. data/lib/rgl/base.rb +160 -78
  17. data/lib/rgl/bellman_ford.rb +115 -0
  18. data/lib/rgl/bidirectional.rb +17 -10
  19. data/lib/rgl/bipartite.rb +87 -0
  20. data/lib/rgl/condensation.rb +13 -4
  21. data/lib/rgl/connected_components.rb +38 -30
  22. data/lib/rgl/dijkstra.rb +158 -0
  23. data/lib/rgl/dijkstra_visitor.rb +42 -0
  24. data/lib/rgl/dot.rb +40 -32
  25. data/lib/rgl/edge_properties_map.rb +55 -0
  26. data/lib/rgl/edmonds_karp.rb +136 -0
  27. data/lib/rgl/enumerable_ext.rb +4 -1
  28. data/lib/rgl/graph_iterator.rb +15 -0
  29. data/lib/rgl/graph_visitor.rb +138 -0
  30. data/lib/rgl/graph_wrapper.rb +15 -0
  31. data/lib/rgl/graphxml.rb +20 -10
  32. data/lib/rgl/implicit.rb +68 -66
  33. data/lib/rgl/mutable.rb +37 -31
  34. data/lib/rgl/path_builder.rb +40 -0
  35. data/lib/rgl/prim.rb +52 -0
  36. data/lib/rgl/rdot.rb +411 -374
  37. data/lib/rgl/topsort.rb +23 -16
  38. data/lib/rgl/transitivity.rb +29 -27
  39. data/lib/rgl/traversal.rb +67 -205
  40. data/rakelib/dep_graph.rake +4 -3
  41. data/test/bellman_ford_test.rb +187 -0
  42. data/test/bipartite_test.rb +47 -0
  43. data/test/components_test.rb +80 -0
  44. data/test/cycles_test.rb +60 -0
  45. data/test/dijkstra_test.rb +148 -0
  46. data/test/directed_graph_test.rb +118 -0
  47. data/test/dot_test.rb +26 -0
  48. data/test/edge_properties_map_test.rb +63 -0
  49. data/test/edge_test.rb +35 -0
  50. data/test/edmonds_karp_test.rb +105 -0
  51. data/{tests/TestGraph.rb → test/graph_test.rb} +6 -6
  52. data/test/graph_xml_test.rb +57 -0
  53. data/test/implicit_test.rb +53 -0
  54. data/test/prim_test.rb +98 -0
  55. data/{tests/TestRdot.rb → test/rdot_test.rb} +309 -308
  56. data/{tests → test}/test_helper.rb +4 -1
  57. data/{tests/TestTransitivity.rb → test/transitivity_test.rb} +43 -43
  58. data/test/traversal_test.rb +221 -0
  59. data/test/undirected_graph_test.rb +103 -0
  60. metadata +226 -145
  61. data/examples/example.jpg +0 -0
  62. data/examples/module_graph.jpg +0 -0
  63. data/install.rb +0 -49
  64. data/tests/TestComponents.rb +0 -65
  65. data/tests/TestCycles.rb +0 -61
  66. data/tests/TestDirectedGraph.rb +0 -125
  67. data/tests/TestDot.rb +0 -18
  68. data/tests/TestEdge.rb +0 -34
  69. data/tests/TestGraphXML.rb +0 -57
  70. data/tests/TestImplicit.rb +0 -52
  71. data/tests/TestTraversal.rb +0 -220
  72. data/tests/TestUnDirectedGraph.rb +0 -102
@@ -5,15 +5,16 @@ require 'rgl/base'
5
5
  module RGL
6
6
 
7
7
  # A MutableGraph can be changed via the addition or removal of edges and
8
- # vertices.
8
+ # vertices.
9
+ #
9
10
  module MutableGraph
10
11
 
11
12
  include Graph
12
13
 
13
- # Add a new vertex _v_ to the graph. If the vertex is already in the
14
+ # Add a new vertex _v_ to the graph. If the vertex is already in the
14
15
  # graph (tested via eql?), the method does nothing.
15
-
16
- def add_vertex (v)
16
+ #
17
+ def add_vertex(v)
17
18
  raise NotImplementedError
18
19
  end
19
20
 
@@ -22,82 +23,87 @@ module RGL
22
23
  # Note that for undirected graphs, (u,v) is the same edge as (v,u), so
23
24
  # after a call to the function add_edge(), this implies that edge (u,v)
24
25
  # will appear in the out-edges of u and (u,v) (or equivalently (v,u))
25
- # will appear in the out-edges of v. Put another way, v will be adjacent
26
- # to u and u will be adjacent to v.
27
-
28
- def add_edge (u, v)
26
+ # will appear in the out-edges of v. Put another way, v will be adjacent
27
+ # to u and u will be adjacent to v.
28
+ #
29
+ def add_edge(u, v)
29
30
  raise NotImplementedError
30
31
  end
31
32
 
32
33
  # Add all objects in _a_ to the vertex set.
33
-
34
- def add_vertices (*a)
34
+ #
35
+ def add_vertices(*a)
35
36
  a.each { |v| add_vertex v }
36
37
  end
37
38
 
38
- # Add all edges in the _edges_ array to the edge set. Elements of the
39
+ # Add all edges in the _edges_ array to the edge set. Elements of the
39
40
  # array can be both two-element arrays or instances of DirectedEdge or
40
- # UnDirectedEdge.
41
-
42
- def add_edges (*edges)
41
+ # UnDirectedEdge.
42
+ #
43
+ def add_edges(*edges)
43
44
  edges.each { |edge| add_edge(edge[0], edge[1]) }
44
45
  end
45
46
 
46
- # Remove u from the vertex set of the graph. All edges whose target is
47
+ # Remove u from the vertex set of the graph. All edges whose target is
47
48
  # _v_ are also removed from the edge set of the graph.
48
49
  #
49
50
  # Postcondition: num_vertices is one less, _v_ no longer appears in the
50
51
  # vertex set of the graph, and there no edge with source or target _v_.
51
-
52
- def remove_vertex (v)
52
+ #
53
+ def remove_vertex(v)
53
54
  raise NotImplementedError
54
55
  end
55
56
 
56
- # Remove the edge (u,v) from the graph. If the graph allows parallel
57
+ # Remove the edge (u,v) from the graph. If the graph allows parallel
57
58
  # edges, this removes all occurrences of (u,v).
58
- #
59
+ #
59
60
  # Precondition: u and v are vertices in the graph.
60
61
  # Postcondition: (u,v) is no longer in the edge set for g.
61
-
62
- def remove_edge (u, v)
62
+ #
63
+ def remove_edge(u, v)
63
64
  raise NotImplementedError
64
65
  end
65
66
 
66
67
  # Remove all vertices specified by the array a from the graph by calling
67
68
  # remove_vertex.
68
-
69
- def remove_vertices (*a)
69
+ #
70
+ def remove_vertices(*a)
70
71
  a.each { |v| remove_vertex v }
71
72
  end
72
73
 
73
- # Returns all minimum cycles that pass through a give vertex.
74
+ # Returns all minimum cycles that pass through a give vertex.
74
75
  # The format is an Array of cycles, with each cycle being an Array
75
76
  # of vertices in the cycle.
77
+ #
76
78
  def cycles_with_vertex(vertex)
77
79
  cycles_with_vertex_helper(vertex, vertex, [])
78
80
  end
79
81
 
80
- protected
82
+ protected
83
+
81
84
  def cycles_with_vertex_helper(vertex, start, visited) #:nodoc:
82
- adjacent_vertices(start).reject {|x| visited.include?(x)}.inject([]) do |acc, adj|
85
+ adjacent_vertices(start).reject { |x| visited.include?(x) }.inject([]) do |acc, adj|
83
86
  local_visited = Array.new(visited) << adj
84
87
  acc << local_visited if (adj==vertex)
85
- acc = acc + cycles_with_vertex_helper(vertex,adj,local_visited)
88
+ acc = acc + cycles_with_vertex_helper(vertex, adj, local_visited)
86
89
  end
87
90
  end
88
91
 
89
- public
92
+ public
93
+
90
94
  # Returns an array of all minimum cycles in a graph
91
95
  #
92
96
  # This is not an efficient implementation O(n^4) and could
93
97
  # be done using Minimum Spanning Trees. Hint. Hint.
98
+ #
94
99
  def cycles
95
100
  g = self.clone
96
- self.inject([]) do |acc, v|
101
+ self.inject([]) do |acc, v|
97
102
  acc = acc.concat(g.cycles_with_vertex(v))
98
103
  g.remove_vertex(v); acc
99
104
  end
100
105
  end
101
106
 
102
- end # module MutableGraph
103
- end # module RGL
107
+ end # module MutableGraph
108
+
109
+ end # module RGL
@@ -0,0 +1,40 @@
1
+ module RGL
2
+
3
+ class PathBuilder # :nodoc:
4
+
5
+ def initialize(source, parents_map)
6
+ @source = source
7
+ @parents_map = parents_map
8
+ @paths = {}
9
+ end
10
+
11
+ def path(target)
12
+ if @paths.has_key?(target)
13
+ @paths[target]
14
+ else
15
+ @paths[target] = restore_path(target)
16
+ end
17
+ end
18
+
19
+ def paths(targets)
20
+ paths_map = {}
21
+
22
+ targets.each do |target|
23
+ paths_map[target] = path(target)
24
+ end
25
+
26
+ paths_map
27
+ end
28
+
29
+ private
30
+
31
+ def restore_path(target)
32
+ return [@source] if target == @source
33
+
34
+ parent = @parents_map[target]
35
+ path(parent) + [target] if parent
36
+ end
37
+
38
+ end
39
+
40
+ end # RGL
@@ -0,0 +1,52 @@
1
+ require 'rgl/dijkstra'
2
+ require 'rgl/adjacency'
3
+
4
+ module RGL
5
+
6
+ class PrimAlgorithm
7
+
8
+ # Replacement for default distance combinator that is used in Dijkstra's algorithm. While building a minimum
9
+ # spanning tree (MST) we're interested not in the distance from the source (the vertex that is added first to the
10
+ # MST) to a vertex, but rather in the distance between already completed part of the MST (that includes all examined
11
+ # vertices) and the vertex. Therefore, when we examine an edge (u, v), where _u_ is already in the MST and _v_ is
12
+ # not, the distance from the MST to the vertex _v_ is the weight of the edge (u, v).
13
+ DISTANCE_COMBINATOR = lambda { |_, edge_weight| edge_weight }
14
+
15
+ # Initializes Prim's algorithm for a _graph_ with provided edges weights map.
16
+ #
17
+ def initialize(graph, edge_weights_map, visitor)
18
+ @graph = graph
19
+ @edge_weights_map = EdgePropertiesMap.new(edge_weights_map, @graph.directed?)
20
+ @visitor = visitor
21
+ @dijkstra = DijkstraAlgorithm.new(@graph, @edge_weights_map, @visitor, DISTANCE_COMBINATOR)
22
+ end
23
+
24
+ # Returns minimum spanning tree for the _graph_. If the graph is disconnected, Prim's algorithm will find the minimum
25
+ # spanning tree only for one of the connectivity components. If _start_vertex_ is given, Dijkstra's search will be
26
+ # started in this vertex and the algorithm will return the minimum spanning tree for the component it belongs to.
27
+ #
28
+ def minimum_spanning_tree(start_vertex = nil)
29
+ @dijkstra.find_shortest_paths(start_vertex || @graph.vertices.first)
30
+ AdjacencyGraph[*@visitor.parents_map.to_a.flatten]
31
+ end
32
+
33
+ end # class PrimAlgorithm
34
+
35
+ module Graph
36
+
37
+ # Finds the minimum spanning tree of the graph.
38
+ #
39
+ # Returns an AdjacencyGraph that represents the minimum spanning tree of the graph's connectivity component that
40
+ # contains the starting vertex. The algorithm starts from an arbitrary vertex if the _start_vertex_ is not given.
41
+ # Since the implementation relies on the Dijkstra's algorithm, Prim's algorithm uses the same visitor class and emits
42
+ # the same events.
43
+ #
44
+ # Raises ArgumentError if edge weight is undefined.
45
+ #
46
+ def prim_minimum_spanning_tree(edge_weights_map, start_vertex = nil, visitor = DijkstraVisitor.new(self))
47
+ PrimAlgorithm.new(self, edge_weights_map, visitor).minimum_spanning_tree(start_vertex)
48
+ end
49
+
50
+ end # module Graph
51
+
52
+ end # module RGL
@@ -1,157 +1,164 @@
1
- # This is a modified version of dot.rb from Dave Thomas's rdoc project. I
1
+ # This is a modified version of dot.rb from Dave Thomas's rdoc project. I
2
2
  # renamed it to rdot.rb to avoid collision with an installed rdoc/dot.
3
3
  #
4
4
  # It also supports undirected edges.
5
5
 
6
- module RGL; module DOT
7
-
8
- # options for node declaration
9
-
10
- NODE_OPTS = [
11
- # attributes due to
12
- # http://www.graphviz.org/Documentation/dotguide.pdf
13
- # February 23, 2008
14
- 'color', # default: black; node shape color
15
- 'comment', # any string (format-dependent)
16
- 'distortion', # default: 0.0; node distortion for shape=polygon
17
- 'fillcolor', # default: lightgrey/black; node fill color
18
- 'fixedsize', # default: false; label text has no affect on node size
19
- 'fontcolor', # default: black; type face color
20
- 'fontname', # default: Times-Roman; font family
21
- 'fontsize', #default: 14; point size of label
22
- 'group', # name of node's group
23
- 'height', # default: .5; height in inches
24
- 'label', # default: node name; any string
25
- 'layer', # default: overlay range; all, id or id:id
26
- 'orientation', # dafault: 0.0; node rotation angle
27
- 'peripheries', # shape-dependent number of node boundaries
28
- 'regular', # default: false; force polygon to be regular
29
- 'shape', # default: ellipse; node shape; see Section 2.1 and Appendix E
30
- 'shapefile', # external EPSF or SVG custom shape file
31
- 'sides', # default: 4; number of sides for shape=polygon
32
- 'skew' , # default: 0.0; skewing of node for shape=polygon
33
- 'style', # graphics options, e.g. bold, dotted, filled; cf. Section 2.3
34
- 'URL', # URL associated with node (format-dependent)
35
- 'width', # default: .75; width in inches
36
- 'z', #default: 0.0; z coordinate for VRML output
37
-
38
- # maintained for backward compatibility or rdot internal
39
- 'bottomlabel', # auxiliary label for nodes of shape M*
40
- 'bgcolor',
41
- 'rank',
42
- 'toplabel' # auxiliary label for nodes of shape M*
43
- ]
44
-
45
- # options for edge declaration
46
-
47
- EDGE_OPTS = [
48
- 'arrowhead', # default: normal; style of arrowhead at head end
49
- 'arrowsize', # default: 1.0; scaling factor for arrowheads
50
- 'arrowtail', # default: normal; style of arrowhead at tail end
51
- 'color', # default: black; edge stroke color
52
- 'comment', # any string (format-dependent)
53
- 'constraint', # default: true use edge to affect node ranking
54
- 'decorate', # if set, draws a line connecting labels with their edges
55
- 'dir', # default: forward; forward, back, both, or none
56
- 'fontcolor', # default: black type face color
57
- 'fontname', # default: Times-Roman; font family
58
- 'fontsize', # default: 14; point size of label
59
- 'headlabel', # label placed near head of edge
60
- 'headport', # n,ne,e,se,s,sw,w,nw
61
- 'headURL', # URL attached to head label if output format is ismap
62
- 'label', # edge label
63
- 'labelangle', # default: -25.0; angle in degrees which head or tail label is rotated off edge
64
- 'labeldistance', # default: 1.0; scaling factor for distance of head or tail label from node
65
- 'labelfloat', # default: false; lessen constraints on edge label placement
66
- 'labelfontcolor', # default: black; type face color for head and tail labels
67
- 'labelfontname', # default: Times-Roman; font family for head and tail labels
68
- 'labelfontsize', # default: 14 point size for head and tail labels
69
- 'layer', # default: overlay range; all, id or id:id
70
- 'lhead', # name of cluster to use as head of edge
71
- 'ltail', # name of cluster to use as tail of edge
72
- 'minlen', # default: 1 minimum rank distance between head and tail
73
- 'samehead', # tag for head node; edge heads with the same tag are merged onto the same port
74
- 'sametail', # tag for tail node; edge tails with the same tag are merged onto the same port
75
- 'style', # graphics options, e.g. bold, dotted, filled; cf. Section 2.3
76
- 'taillabel', # label placed near tail of edge
77
- 'tailport', # n,ne,e,se,s,sw,w,nw
78
- 'tailURL', # URL attached to tail label if output format is ismap
79
- 'weight', # default: 1; integer cost of stretching an edge
80
-
81
- # maintained for backward compatibility or rdot internal
82
- 'id'
83
- ]
84
-
85
- # options for graph declaration
86
-
87
- GRAPH_OPTS = [
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)
129
-
130
- # maintained for backward compatibility or rdot internal
131
- 'layerseq'
132
- ]
133
-
134
- # Ancestor of Edge, Node, and Graph.
135
- class Element
136
- attr_accessor :name, :options
137
-
138
- def initialize (params = {}, option_list = []) # :nodoc:
139
- @name = params['name'] ? params['name'] : nil
140
- @options = {}
141
- option_list.each{ |i|
142
- @options[i] = params[i] if params[i]
143
- }
144
- end
6
+ module RGL
7
+
8
+ module DOT
9
+
10
+ # options for node declaration
11
+
12
+ NODE_OPTS = [
13
+ # attributes due to
14
+ # http://www.graphviz.org/Documentation/dotguide.pdf
15
+ # February 23, 2008
16
+ 'color', # default: black; node shape color
17
+ 'comment', # any string (format-dependent)
18
+ 'distortion', # default: 0.0; node distortion for shape=polygon
19
+ 'fillcolor', # default: lightgrey/black; node fill color
20
+ 'fixedsize', # default: false; label text has no affect on node size
21
+ 'fontcolor', # default: black; type face color
22
+ 'fontname', # default: Times-Roman; font family
23
+ 'fontsize', #default: 14; point size of label
24
+ 'group', # name of node's group
25
+ 'height', # default: .5; height in inches
26
+ 'label', # default: node name; any string
27
+ 'layer', # default: overlay range; all, id or id:id
28
+ 'orientation', # dafault: 0.0; node rotation angle
29
+ 'peripheries', # shape-dependent number of node boundaries
30
+ 'regular', # default: false; force polygon to be regular
31
+ 'shape', # default: ellipse; node shape; see Section 2.1 and Appendix E
32
+ 'shapefile', # external EPSF or SVG custom shape file
33
+ 'sides', # default: 4; number of sides for shape=polygon
34
+ 'skew' , # default: 0.0; skewing of node for shape=polygon
35
+ 'style', # graphics options, e.g. bold, dotted, filled; cf. Section 2.3
36
+ 'URL', # URL associated with node (format-dependent)
37
+ 'width', # default: .75; width in inches
38
+ 'z', #default: 0.0; z coordinate for VRML output
39
+
40
+ # maintained for backward compatibility or rdot internal
41
+ 'bottomlabel', # auxiliary label for nodes of shape M*
42
+ 'bgcolor',
43
+ 'rank',
44
+ 'toplabel' # auxiliary label for nodes of shape M*
45
+ ]
46
+
47
+ # options for edge declaration
48
+
49
+ EDGE_OPTS = [
50
+ 'arrowhead', # default: normal; style of arrowhead at head end
51
+ 'arrowsize', # default: 1.0; scaling factor for arrowheads
52
+ 'arrowtail', # default: normal; style of arrowhead at tail end
53
+ 'color', # default: black; edge stroke color
54
+ 'comment', # any string (format-dependent)
55
+ 'constraint', # default: true use edge to affect node ranking
56
+ 'decorate', # if set, draws a line connecting labels with their edges
57
+ 'dir', # default: forward; forward, back, both, or none
58
+ 'fontcolor', # default: black type face color
59
+ 'fontname', # default: Times-Roman; font family
60
+ 'fontsize', # default: 14; point size of label
61
+ 'headlabel', # label placed near head of edge
62
+ 'headport', # n,ne,e,se,s,sw,w,nw
63
+ 'headURL', # URL attached to head label if output format is ismap
64
+ 'label', # edge label
65
+ 'labelangle', # default: -25.0; angle in degrees which head or tail label is rotated off edge
66
+ 'labeldistance', # default: 1.0; scaling factor for distance of head or tail label from node
67
+ 'labelfloat', # default: false; lessen constraints on edge label placement
68
+ 'labelfontcolor', # default: black; type face color for head and tail labels
69
+ 'labelfontname', # default: Times-Roman; font family for head and tail labels
70
+ 'labelfontsize', # default: 14 point size for head and tail labels
71
+ 'layer', # default: overlay range; all, id or id:id
72
+ 'lhead', # name of cluster to use as head of edge
73
+ 'ltail', # name of cluster to use as tail of edge
74
+ 'minlen', # default: 1 minimum rank distance between head and tail
75
+ 'samehead', # tag for head node; edge heads with the same tag are merged onto the same port
76
+ 'sametail', # tag for tail node; edge tails with the same tag are merged onto the same port
77
+ 'style', # graphics options, e.g. bold, dotted, filled; cf. Section 2.3
78
+ 'taillabel', # label placed near tail of edge
79
+ 'tailport', # n,ne,e,se,s,sw,w,nw
80
+ 'tailURL', # URL attached to tail label if output format is ismap
81
+ 'weight', # default: 1; integer cost of stretching an edge
82
+
83
+ # maintained for backward compatibility or rdot internal
84
+ 'id'
85
+ ]
86
+
87
+ # options for graph declaration
88
+
89
+ GRAPH_OPTS = [
90
+ 'bgcolor', # background color for drawing, plus initial fill color
91
+ 'center', # default: false; center draing on page
92
+ 'clusterrank', # default: local; may be "global" or "none"
93
+ 'color', # default: black; for clusters, outline color, and fill color if
94
+ # fillcolor not defined
95
+ 'comment', # any string (format-dependent)
96
+ 'compound', # default: false; allow edges between clusters
97
+ 'concentrate', # default: false; enables edge concentrators
98
+ 'fillcolor', # default: black; cluster fill color
99
+ 'fontcolor', # default: black; type face color
100
+ 'fontname', # default: Times-Roman; font family
101
+ 'fontpath', # list of directories to search for fonts
102
+ 'fontsize', # default: 14; point size of label
103
+ 'label', # any string
104
+ 'labeljust', # default: centered; "l" and "r" for left- and right-justified
105
+ # cluster labels, respectively
106
+ 'labelloc', # default: top; "t" and "b" for top- and bottom-justified
107
+ # cluster labels, respectively
108
+ 'layers', # id:id:id...
109
+ 'margin', # default: .5; margin included in page, inches
110
+ 'mclimit', # default: 1.0; scale factor for mincross iterations
111
+ 'nodesep', # default: .25; separation between nodes, in inches.
112
+ 'nslimit', # if set to "f", bounds network simplex iterations by
113
+ # (f)(number of nodes) when setting x-coordinates
114
+ 'nslimit1', # if set to "f", bounds network simplex iterations by
115
+ # (f)(number of nodes) when ranking nodes
116
+ 'ordering', # if "out" out edge order is preserved
117
+ 'orientation', # default: portrait; if "rotate" is not used and the value is
118
+ # "landscape", use landscape orientation
119
+ 'page', # unit of pagination, e.g. "8.5,11"
120
+ 'rank', # "same", "min", "max", "source", or "sink"
121
+ 'rankdir', # default: TB; "LR" (left to right) or "TB" (top to bottom)
122
+ 'ranksep', # default: .75; separation between ranks, in inches.
123
+ 'ratio', # approximate aspect ratio desired, "fill" or "auto"
124
+ 'samplepoints', # default: 8; number of points used to represent ellipses
125
+ # and circles on output
126
+ 'searchsize', # default: 30; maximum edges with negative cut values to check
127
+ # when looking for a minimum one during network simplex
128
+ 'size', # maximum drawing size, in inches
129
+ 'style', # graphics options, e.g. "filled" for clusters
130
+ 'URL', # URL associated with graph (format-dependent)
131
+
132
+ # maintained for backward compatibility or rdot internal
133
+ 'layerseq'
134
+ ]
135
+
136
+ # Ancestor of Edge, Node, and Graph.
137
+ #
138
+ class Element
139
+
140
+ attr_accessor :name, :options
141
+
142
+ def initialize(params = {}, option_list = []) # :nodoc:
143
+ @name = params['name'] ? params['name'] : nil
144
+ @options = {}
145
+
146
+ option_list.each do |i|
147
+ @options[i] = params[i] if params[i]
148
+ end
149
+ end
150
+
151
+ private
145
152
 
146
- private
147
153
  # Returns the string given in _id_ within quotes if necessary. Special
148
154
  # characters are escaped as necessary.
155
+ #
149
156
  def quote_ID(id)
150
157
  # Ensure that the ID is a string.
151
158
  id = id.to_s
152
159
 
153
160
  # Return the ID verbatim if it looks like a name, a number, or HTML.
154
- return id if id =~ /\A([[:alpha:]_][[:alnum:]_]*|-?(\.[[:digit:]]+|[[:digit:]]+(\.[[:digit:]]*)?)|<.*>)\Z/m and id[-1] != ?\n
161
+ return id if id =~ /\A([[:alpha:]_][[:alnum:]_]*|-?(\.[[:digit:]]+|[[:digit:]]+(\.[[:digit:]]*)?)|<.*>)\Z/m && id[-1] != ?\n
155
162
 
156
163
  # Return a quoted version of the ID otherwise.
157
164
  '"' + id.gsub('\\', '\\\\\\\\').gsub('"', '\\\\"') + '"'
@@ -161,285 +168,315 @@ module RGL; module DOT
161
168
  # characters are escaped as necessary. Labels get special treatment in
162
169
  # order to handle embedded *\n*, *\r*, and *\l* sequences which are copied
163
170
  # into the new string verbatim.
171
+ #
164
172
  def quote_label(label)
165
173
  # Ensure that the label is a string.
166
174
  label = label.to_s
167
175
 
168
176
  # Return the label verbatim if it looks like a name, a number, or HTML.
169
- return label if label =~ /\A([[:alpha:]_][[:alnum:]_]*|-?(\.[[:digit:]]+|[[:digit:]]+(\.[[:digit:]]*)?)|<.*>)\Z/m and label[-1] != ?\n
177
+ return label if label =~ /\A([[:alpha:]_][[:alnum:]_]*|-?(\.[[:digit:]]+|[[:digit:]]+(\.[[:digit:]]*)?)|<.*>)\Z/m && label[-1] != ?\n
170
178
 
171
179
  # Return a quoted version of the label otherwise.
172
180
  '"' + label.split(/(\\n|\\r|\\l)/).collect do |part|
173
181
  case part
174
- when "\\n", "\\r", "\\l"
175
- part
176
- else
177
- part.gsub('\\', '\\\\\\\\').gsub('"', '\\\\"').gsub("\n", '\\n')
182
+ when "\\n", "\\r", "\\l"
183
+ part
184
+ else
185
+ part.gsub('\\', '\\\\\\\\').gsub('"', '\\\\"').gsub("\n", '\\n')
178
186
  end
179
187
  end.join + '"'
180
188
  end
181
- end
182
-
189
+ end
183
190
 
184
- # Ports are used when a Node instance has its `shape' option set to
185
- # _record_ or _Mrecord_. Ports can be nested.
186
- class Port
187
- attr_accessor :name, :label, :ports
188
191
 
189
- # Create a new port with either an optional name and label or a set of
190
- # nested ports.
192
+ # Ports are used when a Node instance has its `shape' option set to
193
+ # _record_ or _Mrecord_. Ports can be nested.
191
194
  #
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
195
+ class Port
196
+ attr_accessor :name, :label, :ports
197
+
198
+ # Create a new port with either an optional name and label or a set of
199
+ # nested ports.
200
+ #
201
+ # :call-seq:
202
+ # new(name = nil, label = nil)
203
+ # new(ports)
204
+ #
205
+ # A +nil+ value for +name+ is valid; otherwise, it must be a String or it
206
+ # will be interpreted as +ports+.
207
+ #
208
+ def initialize(name_or_ports = nil, label = nil)
209
+ if name_or_ports.nil? || name_or_ports.kind_of?(String)
210
+ @name = name_or_ports
211
+ @label = label
212
+ @ports = nil
213
+ else
214
+ @ports = name_or_ports
215
+ @name = nil
216
+ @label = nil
217
+ end
207
218
  end
208
- end
209
219
 
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.
213
- def to_s
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(' | ') + '}'
220
+ # Returns a string representation of this port. If ports is a non-empty
221
+ # Enumerable, a nested ports representation is returned; otherwise, a
222
+ # name-label representation is returned.
223
+ #
224
+ def to_s
225
+ if @ports.nil? || @ports.empty?
226
+ n = (name.nil? || name.empty?) ? '' : "<#{name}>"
227
+ n + ((n.empty? || label.nil? || label.empty?) ? '' : ' ') + label.to_s
228
+ else
229
+ '{' + @ports.collect { |p| p.to_s }.join(' | ') + '}'
230
+ end
219
231
  end
220
232
  end
221
- end
222
-
223
- # A node representation. Edges are drawn between nodes. The rendering of a
224
- # node depends upon the options set for it.
225
- class Node < Element
226
- attr_accessor :ports
227
-
228
- # Creates a new Node 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.
233
- def initialize (params = {}, option_list = NODE_OPTS)
234
- super(params, option_list)
235
- @ports = params['ports'] ? params['ports'] : []
236
- end
237
233
 
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'])}"
234
+ # A node representation. Edges are drawn between nodes. The rendering of a
235
+ # node depends upon the options set for it.
236
+ #
237
+ class Node < Element
238
+
239
+ attr_accessor :ports
240
+
241
+ # Creates a new Node with the _params_ Hash providing settings for all
242
+ # node options. The _option_list_ parameter restricts those options to the
243
+ # list of valid names it contains. The exception to this is the _ports_
244
+ # option which, if specified, must be an Enumerable containing a list of
245
+ # ports.
246
+ #
247
+ def initialize(params = {}, option_list = NODE_OPTS)
248
+ super(params, option_list)
249
+ @ports = params['ports'] ? params['ports'] : []
251
250
  end
252
251
 
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)}"
252
+ # Returns a string representation of this node which is consumable by the
253
+ # graphviz tools +dot+ and +neato+. The _leader_ parameter is used to indent
254
+ # every line of the returned string, and the _indent_ parameter is used to
255
+ # additionally indent nested items.
256
+ #
257
+ def to_s(leader = '', indent = ' ')
258
+ label_option = nil
259
+
260
+ if @options['shape'] =~ /^M?record$/ && !@ports.empty?
261
+ # Ignore the given label option in this case since the ports should each
262
+ # provide their own name/label.
263
+ label_option = leader + indent + "#{quote_ID('label')} = #{quote_ID(@ports.collect { |port| port.to_s }.join(" | "))}"
264
+ elsif @options['label']
265
+ # Otherwise, use the label when given one.
266
+ label_option = leader + indent + "#{quote_ID('label')} = #{quote_label(@options['label'])}"
267
+ end
268
+
269
+ # Convert all the options except `label' and options with nil values
270
+ # straight into name = value pairs. Then toss out any resulting nil
271
+ # entries in the final array.
272
+ stringified_options = @options.collect do |name, val|
273
+ unless name == 'label' || val.nil?
274
+ leader + indent + "#{quote_ID(name)} = #{quote_ID(val)}"
275
+ end
276
+ end.compact
277
+
278
+ # Append the specially computed label option.
279
+ stringified_options.push(label_option) unless label_option.nil?
280
+
281
+ # Join them all together.
282
+ stringified_options = stringified_options.join(",\n")
283
+
284
+ # Put it all together into a single string with indentation and return the
285
+ # result.
286
+ if stringified_options.empty?
287
+ leader + quote_ID(@name) unless @name.nil?
288
+ else
289
+ leader + (@name.nil? ? '' : quote_ID(@name) + " ") + "[\n" +
290
+ stringified_options + "\n" +
291
+ leader + "]"
259
292
  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 + "]"
275
293
  end
276
- end
277
294
 
278
- end # class Node
279
-
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 Graph < Element
284
-
285
- # Creates a new Graph 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.
290
- def initialize (params = {}, option_list = GRAPH_OPTS)
291
- super(params, option_list)
292
- @elements = params['elements'] ? params['elements'] : []
293
- @dot_string = 'graph'
294
- end
295
+ end # class Node
295
296
 
296
- # Calls _block_ once for each node, edge, or subgraph contained by this
297
- # graph, passing the node, edge, or subgraph to the block.
297
+ # A graph representation. Whether or not it is rendered as directed or
298
+ # undirected depends on which of the programs *dot* or *neato* is used to
299
+ # process and render the graph.
298
300
  #
299
- # :call-seq:
300
- # graph.each_element {|element| block} -> graph
301
- #
302
- def each_element (&block)
303
- @elements.each(&block)
304
- self
305
- end
301
+ class Graph < Element
302
+
303
+ # Creates a new Graph with the _params_ Hash providing settings for all
304
+ # graph options. The _option_list_ parameter restricts those options to the
305
+ # list of valid names it contains. The exception to this is the _elements_
306
+ # option which, if specified, must be an Enumerable containing a list of
307
+ # nodes, edges, and/or subgraphs.
308
+ #
309
+ def initialize(params = {}, option_list = GRAPH_OPTS)
310
+ super(params, option_list)
311
+ @elements = params['elements'] ? params['elements'] : []
312
+ @dot_string = 'graph'
313
+ end
306
314
 
307
- # Adds a new node, edge, or subgraph to this graph.
308
- #
309
- # :call-seq:
310
- # graph << element -> graph
315
+ # Calls _block_ once for each node, edge, or subgraph contained by this
316
+ # graph, passing the node, edge, or subgraph to the block.
317
+ #
318
+ # :call-seq:
319
+ # graph.each_element {|element| block} -> graph
320
+ #
321
+ def each_element(&block)
322
+ @elements.each(&block)
323
+ self
324
+ end
325
+
326
+ # Adds a new node, edge, or subgraph to this graph.
327
+ #
328
+ # :call-seq:
329
+ # graph << element -> graph
330
+ #
331
+ def << (element)
332
+ @elements << element
333
+ self
334
+ end
335
+
336
+ alias push <<
337
+
338
+ # Removes the most recently added node, edge, or subgraph from this graph
339
+ # and returns it.
340
+ #
341
+ # :call-seq:
342
+ # graph.pop -> element
343
+ #
344
+ def pop
345
+ @elements.pop
346
+ end
347
+
348
+ # Returns a string representation of this graph which is consumable by the
349
+ # graphviz tools +dot+ and +neato+. The _leader_ parameter is used to indent
350
+ # every line of the returned string, and the _indent_ parameter is used to
351
+ # additionally indent nested items.
352
+ #
353
+ def to_s(leader = '', indent = ' ')
354
+ hdr = leader + @dot_string + (@name.nil? ? '' : ' ' + quote_ID(@name)) + " {\n"
355
+
356
+ options = @options.to_a.collect do |name, val|
357
+ unless val.nil?
358
+ if name == 'label'
359
+ leader + indent + "#{quote_ID(name)} = #{quote_label(val)}"
360
+ else
361
+ leader + indent + "#{quote_ID(name)} = #{quote_ID(val)}"
362
+ end
363
+ end
364
+ end.compact.join("\n")
365
+
366
+ elements = @elements.collect do |element|
367
+ element.to_s(leader + indent, indent)
368
+ end.join("\n\n")
369
+
370
+ hdr + (options.empty? ? '' : options + "\n\n") +
371
+ (elements.empty? ? '' : elements + "\n") + leader + "}"
372
+ end
373
+
374
+ end # class Graph
375
+
376
+ # A digraph is a directed graph representation which is the same as a Graph
377
+ # except that its header in dot notation has an identifier of _digraph_
378
+ # instead of _graph_.
311
379
  #
312
- def << (element)
313
- @elements << element
314
- self
315
- end
316
- alias :push :<<
380
+ class Digraph < Graph
381
+
382
+ # Creates a new Digraph with the _params_ Hash providing settings for all
383
+ # graph options. The _option_list_ parameter restricts those options to the
384
+ # list of valid names it contains. The exception to this is the _elements_
385
+ # option which, if specified, must be an Enumerable containing a list of
386
+ # nodes, edges, and/or subgraphs.
387
+ #
388
+ def initialize(params = {}, option_list = GRAPH_OPTS)
389
+ super(params, option_list)
390
+ @dot_string = 'digraph'
391
+ end
317
392
 
318
- # Removes the most recently added node, edge, or subgraph from this graph
319
- # and returns it.
393
+ end # class Digraph
394
+
395
+ # A subgraph is a nested graph element and is the same as a Graph except
396
+ # that its header in dot notation has an identifier of _subgraph_ instead of
397
+ # _graph_.
320
398
  #
321
- # :call-seq:
322
- # graph.pop -> element
399
+ class Subgraph < Graph
400
+
401
+ # Creates a new Subgraph with the _params_ Hash providing settings for
402
+ # all graph options. The _option_list_ parameter restricts those options to
403
+ # list of valid names it contains. The exception to this is the _elements_
404
+ # option which, if specified, must be an Enumerable containing a list of
405
+ # nodes, edges, and/or subgraphs.
406
+ #
407
+ def initialize(params = {}, option_list = GRAPH_OPTS)
408
+ super(params, option_list)
409
+ @dot_string = 'subgraph'
410
+ end
411
+
412
+ end # class Subgraph
413
+
414
+ # This is an undirected edge representation.
323
415
  #
324
- def pop
325
- @elements.pop
326
- end
416
+ class Edge < Element
417
+
418
+ # A node or subgraph reference or instance to be used as the starting point
419
+ # for an edge.
420
+ attr_accessor :from
421
+
422
+ # A node or subgraph reference or instance to be used as the ending point
423
+ # for an edge.
424
+ attr_accessor :to
425
+
426
+ # Creates a new Edge with the _params_ Hash providing settings for all
427
+ # edge options. The _option_list_ parameter restricts those options to the
428
+ # list of valid names it contains.
429
+ #
430
+ def initialize(params = {}, option_list = EDGE_OPTS)
431
+ super(params, option_list)
432
+ @from = params['from'] ? params['from'] : nil
433
+ @to = params['to'] ? params['to'] : nil
434
+ end
327
435
 
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
436
+ # Returns a string representation of this edge which is consumable by the
437
+ # graphviz tools +dot+ and +neato+. The _leader_ parameter is used to indent
438
+ # every line of the returned string, and the _indent_ parameter is used to
439
+ # additionally indent nested items.
440
+ #
441
+ def to_s(leader = '', indent = ' ')
442
+ stringified_options = @options.collect do |name, val|
443
+ unless val.nil?
340
444
  leader + indent + "#{quote_ID(name)} = #{quote_ID(val)}"
341
445
  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 + "}"
350
- end
446
+ end.compact.join(",\n")
351
447
 
352
- end # class Graph
353
-
354
- # A digraph is a directed graph representation which is the same as a Graph
355
- # except that its header in dot notation has an identifier of _digraph_
356
- # instead of _graph_.
357
- class Digraph < Graph
358
-
359
- # Creates a new Digraph 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.
364
- def initialize (params = {}, option_list = GRAPH_OPTS)
365
- super(params, option_list)
366
- @dot_string = 'digraph'
367
- end
368
-
369
- end # class Digraph
370
-
371
- # A subgraph is a nested graph element and is the same as a Graph except
372
- # that its header in dot notation has an identifier of _subgraph_ instead of
373
- # _graph_.
374
- class Subgraph < Graph
375
-
376
- # Creates a new Subgraph 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
448
+ f_s = @from || ''
449
+ t_s = @to || ''
385
450
 
386
- end # class Subgraph
387
-
388
- # This is an undirected edge representation.
389
- class Edge < Element
390
-
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
397
-
398
- # Creates a new Edge 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.
401
- def initialize (params = {}, option_list = EDGE_OPTS)
402
- super(params, option_list)
403
- @from = params['from'] ? params['from'] : nil
404
- @to = params['to'] ? params['to'] : nil
405
- end
406
-
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)}"
451
+ if stringified_options.empty?
452
+ leader + quote_ID(f_s) + ' ' + edge_link + ' ' + quote_ID(t_s)
453
+ else
454
+ leader + quote_ID(f_s) + ' ' + edge_link + ' ' + quote_ID(t_s) + " [\n" +
455
+ stringified_options + "\n" +
456
+ leader + "]"
415
457
  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
458
  end
427
- end
428
459
 
429
- private
460
+ private
461
+
430
462
  def edge_link
431
463
  '--'
432
464
  end
433
465
 
434
- end # class Edge
466
+ end # class Edge
435
467
 
436
- # A directed edge representation otherwise identical to Edge.
437
- class DirectedEdge < Edge
468
+ # A directed edge representation otherwise identical to Edge.
469
+ #
470
+ class DirectedEdge < Edge
471
+
472
+ private
438
473
 
439
- private
440
474
  def edge_link
441
475
  '->'
442
476
  end
443
477
 
444
- end # class DirectedEdge
445
- end; end # module RGL; module DOT
478
+ end # class DirectedEdge
479
+
480
+ end # module DOT
481
+
482
+ end # module RGL