1 | module DOT
|
---|
2 |
|
---|
3 | # these glogal vars are used to make nice graph source
|
---|
4 | $tab = ' '
|
---|
5 | $tab2 = $tab * 2
|
---|
6 |
|
---|
7 | # if we don't like 4 spaces, we can change it any time
|
---|
8 | def change_tab( t )
|
---|
9 | $tab = t
|
---|
10 | $tab2 = t * 2
|
---|
11 | end
|
---|
12 |
|
---|
13 | # options for node declaration
|
---|
14 | NODE_OPTS = [
|
---|
15 | 'bgcolor',
|
---|
16 | 'color',
|
---|
17 | 'fontcolor',
|
---|
18 | 'fontname',
|
---|
19 | 'fontsize',
|
---|
20 | 'height',
|
---|
21 | 'width',
|
---|
22 | 'label',
|
---|
23 | 'layer',
|
---|
24 | 'rank',
|
---|
25 | 'shape',
|
---|
26 | 'shapefile',
|
---|
27 | 'style',
|
---|
28 | 'URL',
|
---|
29 | ]
|
---|
30 |
|
---|
31 | # options for edge declaration
|
---|
32 | EDGE_OPTS = [
|
---|
33 | 'color',
|
---|
34 | 'decorate',
|
---|
35 | 'dir',
|
---|
36 | 'fontcolor',
|
---|
37 | 'fontname',
|
---|
38 | 'fontsize',
|
---|
39 | 'id',
|
---|
40 | 'label',
|
---|
41 | 'layer',
|
---|
42 | 'lhead',
|
---|
43 | 'ltail',
|
---|
44 | 'minlen',
|
---|
45 | 'style',
|
---|
46 | 'weight'
|
---|
47 | ]
|
---|
48 |
|
---|
49 | # options for graph declaration
|
---|
50 | GRAPH_OPTS = [
|
---|
51 | 'bgcolor',
|
---|
52 | 'center',
|
---|
53 | 'clusterrank',
|
---|
54 | 'color',
|
---|
55 | 'compound',
|
---|
56 | 'concentrate',
|
---|
57 | 'fillcolor',
|
---|
58 | 'fontcolor',
|
---|
59 | 'fontname',
|
---|
60 | 'fontsize',
|
---|
61 | 'label',
|
---|
62 | 'layerseq',
|
---|
63 | 'margin',
|
---|
64 | 'mclimit',
|
---|
65 | 'nodesep',
|
---|
66 | 'nslimit',
|
---|
67 | 'ordering',
|
---|
68 | 'orientation',
|
---|
69 | 'page',
|
---|
70 | 'rank',
|
---|
71 | 'rankdir',
|
---|
72 | 'ranksep',
|
---|
73 | 'ratio',
|
---|
74 | 'size',
|
---|
75 | 'style',
|
---|
76 | 'URL'
|
---|
77 | ]
|
---|
78 |
|
---|
79 | # a root class for any element in dot notation
|
---|
80 | class DOTSimpleElement
|
---|
81 | attr_accessor :name
|
---|
82 |
|
---|
83 | def initialize( params = {} )
|
---|
84 | @label = params['name'] ? params['name'] : ''
|
---|
85 | end
|
---|
86 |
|
---|
87 | def to_s
|
---|
88 | @name
|
---|
89 | end
|
---|
90 | end
|
---|
91 |
|
---|
92 | # an element that has options ( node, edge or graph )
|
---|
93 | class DOTElement < DOTSimpleElement
|
---|
94 | #attr_reader :parent
|
---|
95 | attr_accessor :name, :options
|
---|
96 |
|
---|
97 | def initialize( params = {}, option_list = [] )
|
---|
98 | super( params )
|
---|
99 | @name = params['name'] ? params['name'] : nil
|
---|
100 | @parent = params['parent'] ? params['parent'] : nil
|
---|
101 | @options = {}
|
---|
102 | option_list.each{ |i|
|
---|
103 | @options[i] = params[i] if params[i]
|
---|
104 | }
|
---|
105 | @options['label'] ||= @name if @name != 'node'
|
---|
106 | end
|
---|
107 |
|
---|
108 | def each_option
|
---|
109 | @options.each{ |i| yield i }
|
---|
110 | end
|
---|
111 |
|
---|
112 | def each_option_pair
|
---|
113 | @options.each_pair{ |key, val| yield key, val }
|
---|
114 | end
|
---|
115 |
|
---|
116 | #def parent=( thing )
|
---|
117 | # @parent.delete( self ) if defined?( @parent ) and @parent
|
---|
118 | # @parent = thing
|
---|
119 | #end
|
---|
120 | end
|
---|
121 |
|
---|
122 |
|
---|
123 | # this is used when we build nodes that have shape=record
|
---|
124 | # ports don't have options :)
|
---|
125 | class DOTPort < DOTSimpleElement
|
---|
126 | attr_accessor :label
|
---|
127 |
|
---|
128 | def initialize( params = {} )
|
---|
129 | super( params )
|
---|
130 | @name = params['label'] ? params['label'] : ''
|
---|
131 | end
|
---|
132 | def to_s
|
---|
133 | ( @name && @name != "" ? "<#{@name}>" : "" ) + "#{@label}"
|
---|
134 | end
|
---|
135 | end
|
---|
136 |
|
---|
137 | # node element
|
---|
138 | class DOTNode < DOTElement
|
---|
139 |
|
---|
140 | def initialize( params = {}, option_list = NODE_OPTS )
|
---|
141 | super( params, option_list )
|
---|
142 | @ports = params['ports'] ? params['ports'] : []
|
---|
143 | end
|
---|
144 |
|
---|
145 | def each_port
|
---|
146 | @ports.each{ |i| yield i }
|
---|
147 | end
|
---|
148 |
|
---|
149 | def << ( thing )
|
---|
150 | @ports << thing
|
---|
151 | end
|
---|
152 |
|
---|
153 | def push ( thing )
|
---|
154 | @ports.push( thing )
|
---|
155 | end
|
---|
156 |
|
---|
157 | def pop
|
---|
158 | @ports.pop
|
---|
159 | end
|
---|
160 |
|
---|
161 | def to_s( t = '' )
|
---|
162 |
|
---|
163 | label = @options['shape'] != 'record' && @ports.length == 0 ?
|
---|
164 | @options['label'] ?
|
---|
165 | t + $tab + "label = \"#{@options['label']}\"\n" :
|
---|
166 | '' :
|
---|
167 | t + $tab + 'label = "' + " \\\n" +
|
---|
168 | t + $tab2 + "#{@options['label']}| \\\n" +
|
---|
169 | @ports.collect{ |i|
|
---|
170 | t + $tab2 + i.to_s
|
---|
171 | }.join( "| \\\n" ) + " \\\n" +
|
---|
172 | t + $tab + '"' + "\n"
|
---|
173 |
|
---|
174 | t + "#{@name} [\n" +
|
---|
175 | @options.to_a.collect{ |i|
|
---|
176 | i[1] && i[0] != 'label' ?
|
---|
177 | t + $tab + "#{i[0]} = #{i[1]}" : nil
|
---|
178 | }.compact.join( ",\n" ) + ( label != '' ? ",\n" : "\n" ) +
|
---|
179 | label +
|
---|
180 | t + "]\n"
|
---|
181 | end
|
---|
182 | end
|
---|
183 |
|
---|
184 | # subgraph element is the same to graph, but has another header in dot
|
---|
185 | # notation
|
---|
186 | class DOTSubgraph < DOTElement
|
---|
187 |
|
---|
188 | def initialize( params = {}, option_list = GRAPH_OPTS )
|
---|
189 | super( params, option_list )
|
---|
190 | @nodes = params['nodes'] ? params['nodes'] : []
|
---|
191 | @dot_string = 'subgraph'
|
---|
192 | end
|
---|
193 |
|
---|
194 | def each_node
|
---|
195 | @nodes.each{ |i| yield i }
|
---|
196 | end
|
---|
197 |
|
---|
198 | def << ( thing )
|
---|
199 | @nodes << thing
|
---|
200 | end
|
---|
201 |
|
---|
202 | def push( thing )
|
---|
203 | @nodes.push( thing )
|
---|
204 | end
|
---|
205 |
|
---|
206 | def pop
|
---|
207 | @nodes.pop
|
---|
208 | end
|
---|
209 |
|
---|
210 | def to_s( t = '' )
|
---|
211 | hdr = t + "#{@dot_string} #{@name} {\n"
|
---|
212 |
|
---|
213 | options = @options.to_a.collect{ |name, val|
|
---|
214 | val && name != 'label' ?
|
---|
215 | t + $tab + "#{name} = #{val}" :
|
---|
216 | name ? t + $tab + "#{name} = \"#{val}\"" : nil
|
---|
217 | }.compact.join( "\n" ) + "\n"
|
---|
218 |
|
---|
219 | nodes = @nodes.collect{ |i|
|
---|
220 | i.to_s( t + $tab )
|
---|
221 | }.join( "\n" ) + "\n"
|
---|
222 | hdr + options + nodes + t + "}\n"
|
---|
223 | end
|
---|
224 | end
|
---|
225 |
|
---|
226 | # this is graph
|
---|
227 | class DOTDigraph < DOTSubgraph
|
---|
228 | def initialize( params = {}, option_list = GRAPH_OPTS )
|
---|
229 | super( params, option_list )
|
---|
230 | @dot_string = 'digraph'
|
---|
231 | end
|
---|
232 | end
|
---|
233 |
|
---|
234 | # this is edge
|
---|
235 | class DOTEdge < DOTElement
|
---|
236 | attr_accessor :from, :to
|
---|
237 | def initialize( params = {}, option_list = EDGE_OPTS )
|
---|
238 | super( params, option_list )
|
---|
239 | @from = params['from'] ? params['from'] : nil
|
---|
240 | @to = params['to'] ? params['to'] : nil
|
---|
241 | end
|
---|
242 |
|
---|
243 | def to_s( t = '' )
|
---|
244 | t + "#{@from} -> #{to} [\n" +
|
---|
245 | @options.to_a.collect{ |i|
|
---|
246 | i[1] && i[0] != 'label' ?
|
---|
247 | t + $tab + "#{i[0]} = #{i[1]}" :
|
---|
248 | i[1] ? t + $tab + "#{i[0]} = \"#{i[1]}\"" : nil
|
---|
249 | }.compact.join( "\n" ) + "\n" + t + "]\n"
|
---|
250 | end
|
---|
251 | end
|
---|
252 | end
|
---|
253 |
|
---|
254 |
|
---|
255 |
|
---|