tests: Add test_astbuilder to the skip list of nitcg niti nitvm
[nit.git] / lib / dot / dot.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Dot rendering library
16 #
17 # Example:
18 # ~~~
19 # import dot
20 #
21 # var graph = new DotGraph("G", "digraph")
22 #
23 # var hello = graph.add_node("hello")
24 # var world = graph.add_node("world")
25 #
26 # graph.add_edge(hello, world)
27 #
28 # print graph.to_dot
29 # ~~~
30 module dot
31
32 # Something that can be rendered in dot format.
33 abstract class DotElement
34
35 # Element ID
36 var id: String
37
38 # Element attributes
39 var attrs = new AttributeMap
40
41 # Get attribute value for `key`
42 fun [](key: String): Object do return attrs[key]
43
44 # Set attribute `value` for `key`
45 fun []=(key: String, value: Object) do attrs[key] = value
46
47 # Render `self` to dot format
48 fun to_dot: Text do
49 var res = new Buffer
50 res.append "\"{escape_id}\" "
51 if attrs.not_empty then res.append "[{attrs.to_dot(",")}]"
52 return res.write_to_string
53 end
54
55 # Return `id.escape_to_dot`
56 fun escape_id: String do return id.escape_to_dot
57 end
58
59 # Map of graph/node/edge attribute that can be rendered to dot.
60 class AttributeMap
61 super HashMap[String, Object]
62
63 # Render `self` to dot.
64 #
65 # Use `;` for graph attributes `separator` or `,` for node and edge attributes.
66 fun to_dot(separator: String): Text do
67 var dot = new Buffer
68 for key, value in self do
69 dot.append "{key}=\"{value.to_s}\"{separator}"
70 end
71 return dot
72 end
73 end
74
75 # A Graph representation suited for dot format.
76 #
77 # Creating a new graph
78 # ~~~
79 # var graph = new DotGraph("G", "digraph")
80 # graph["rankdir"] = "BT"
81 # graph["ranksep"] = 0.3
82 # graph["nodesep"] = 0.3
83 # graph.nodes_attrs["fontname"] = "helvetica"
84 # graph.edges_attrs["color"] = "gray"
85 # ~~~
86 #
87 # Creating subgraphs
88 # ~~~
89 # var sub = new DotGraph("cluster_sub", "subgraph")
90 # sub["label"] = "process #1"
91 #
92 # var a0 = sub.add_node("a0")
93 # var a1 = sub.add_node("a1")
94 # sub.add_edge(a0, a1)
95 #
96 # graph.add sub
97 # ~~~
98 class DotGraph
99 super DotElement
100
101 # Graph kind like `graph`, `digraph`, `subgraph`...
102 var kind: String is writable
103
104 # Nodes attributes
105 var nodes_attrs = new AttributeMap
106
107 # Edges attributes
108 var edges_attrs = new AttributeMap
109
110 # Node list by id
111 var nodes = new HashMap[String, DotElement]
112
113 # Add a node to the graph
114 #
115 # If the graph already contains a node with that ID, it will be replaced.
116 fun add(element: DotElement) do
117 nodes[element.id] = element
118 end
119
120 # Edge list
121 #
122 # There can be multiple edges between the same couple of nodes.
123 var edges = new Array[DotEdge]
124
125 # Add a new node to the graph
126 fun add_node(id: String): DotNode do
127 var node = new DotNode(id)
128 add node
129 return node
130 end
131
132 # Add an edge to the graph
133 fun add_edge(from, to: DotElement): DotEdge do
134 var edge = new DotEdge(from, to)
135 add edge
136 return edge
137 end
138
139 redef fun to_dot do
140 var dot = new Buffer
141 dot.append "{kind} \"{id}\" \{\n"
142 if attrs.not_empty then dot.append attrs.to_dot(";\n")
143 if nodes_attrs.not_empty then dot.append "node[{nodes_attrs.to_dot(",")}];\n"
144 if edges_attrs.not_empty then dot.append "edge[{edges_attrs.to_dot(",")}];\n"
145 for id, node in nodes do
146 dot.append "{node.to_dot};\n"
147 end
148 for edge in edges do
149 dot.append("{edge.to_dot};\n")
150 end
151 dot.append("\}")
152 return dot
153 end
154
155 # Render `self` as an SVG image
156 fun to_svg: Text do
157 var proc = new ProcessDuplex("dot", "-Tsvg")
158 var svg = proc.write_and_read(to_dot)
159 proc.close
160 proc.wait
161 return svg
162 end
163
164 # Show dot in graphviz (blocking)
165 fun show do
166 var f = new ProcessWriter("dot", "-Txlib")
167 f.write to_dot
168 f.close
169 f.wait
170 end
171 end
172
173 # A dot node
174 #
175 # Nodes can be created from scratch
176 # ~~~
177 # var node = new DotNode("id")
178 # node["label"] = "ID"
179 # ~~~
180 # Then added to a graph
181 # ~~~
182 # var graph = new DotGraph("G", "digraph")
183 # graph.add node
184 # ~~~
185 # Or can be created directly from an existing graph
186 # ~~~
187 # var node2 = graph.add_node("id2")
188 # node2["label"] = "ID2"
189 # ~~~
190 class DotNode
191 super DotElement
192 end
193
194 # A dot edge that links two nodes
195 #
196 # Edges can be created from scratch
197 # ~~~
198 # var a1 = new DotNode("a1")
199 # var b1 = new DotNode("b1")
200 # var edge = new DotEdge(a1, b1)
201 # edge["color"] = "blue"
202 # ~~~
203 # Then added to a graph
204 # ~~~
205 # var graph = new DotGraph("G", "digraph")
206 # graph.add edge
207 # ~~~
208 # Or can be created directly from an existing graph
209 # ~~~
210 # var a2 = graph.add_node("a2")
211 # var b2 = graph.add_node("b2")
212 # var edge2 = graph.add_edge(a2, b2)
213 # edge2["color"] = "red"
214 # ~~~
215 class DotEdge
216 super DotElement
217 autoinit from, to
218
219 # Node this edge is from
220 var from: DotElement
221
222 # Node this edge goes to
223 var to: DotElement
224
225 # Is this edge directed?
226 var directed = true is writable
227
228 redef fun id do return "{from.id}--{to.id}"
229
230 redef fun to_dot do
231 var res = new Buffer
232 res.append "\"{from.escape_id}\" "
233 if directed then
234 res.append "->"
235 else
236 res.append "--"
237 end
238 res.append " \"{to.escape_id}\" "
239 if attrs.not_empty then res.append "[{attrs.to_dot(",")}]"
240 return res.write_to_string
241 end
242 end