rgl 0.4.0 → 0.5.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.
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