Merge remote-tracking branch 'origin/master' into init_auto
[nit.git] / src / web / api_graph.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 # Render various graph from a model.
16 module api_graph
17
18 import web_base
19 import dot
20 import uml
21
22 # Group all api handlers in one router.
23 class APIGraphRouter
24 super Router
25
26 # Model to pass to handlers.
27 var model: Model
28
29 # Mainmodule to pass to handlers.
30 var mainmodule: MModule
31
32 init do
33 use("/inheritance/:id", new APIInheritanceGraph(model, mainmodule))
34 end
35 end
36
37 # Render a hierarchy graph for `mentity` if any.
38 class APIInheritanceGraph
39 super APIHandler
40
41 redef fun get(req, res) do
42 var pdepth = req.int_arg("pdepth")
43 var cdepth = req.int_arg("cdepth")
44 var mentity = mentity_from_uri(req, res)
45 if mentity == null then
46 res.error 404
47 return
48 end
49 var g = new InheritanceGraph(mentity, view)
50 res.send g.draw(pdepth, cdepth).to_svg
51 end
52 end
53
54 # Graph for mentity hierarchies
55 #
56 # Recursively build parents and children list from a `center`.
57 class InheritanceGraph
58 super ModelVisitor
59
60 # MEntity at the center of this graph
61 var center: MEntity
62
63 # ModelView used to filter graph
64 var view: ModelView
65
66 # Graph generated
67 var graph: DotGraph is lazy do
68 var graph = new DotGraph("package_diagram", "digraph")
69
70 graph["compound"] = "true"
71 graph["rankdir"] = "BT"
72 graph["ranksep"] = 0.3
73 graph["nodesep"] = 0.3
74
75 graph.nodes_attrs["margin"] = 0.1
76 graph.nodes_attrs["width"] = 0
77 graph.nodes_attrs["height"] = 0
78 graph.nodes_attrs["fontsize"] = 10
79 graph.nodes_attrs["fontname"] = "helvetica"
80
81 graph.edges_attrs["dir"] = "none"
82 graph.edges_attrs["color"] = "gray"
83
84 return graph
85 end
86
87 # Build the graph
88 fun draw(parents_depth, children_depth: nullable Int): DotGraph do
89 draw_node center
90 draw_parents(center, parents_depth)
91 draw_children(center, children_depth)
92 return graph
93 end
94
95 private var nodes = new HashMap[MEntity, DotElement]
96 private var done_parents = new HashSet[MEntity]
97 private var done_children = new HashSet[MEntity]
98
99 # Recursively draw parents of mentity
100 fun draw_parents(mentity: MEntity, max_depth: nullable Int, current_depth: nullable Int) do
101 if done_parents.has(mentity) then return
102 done_parents.add mentity
103 current_depth = current_depth or else 0
104 if max_depth != null and current_depth >= max_depth then
105 from_dotdotdot(mentity)
106 return
107 end
108 var parents = mentity.collect_parents(view)
109 if parents.length > 10 then
110 from_dotdotdot(mentity)
111 return
112 end
113 for parent in parents do
114 if parent isa MModule then
115 var mgroup = parent.mgroup
116 if mgroup != null and mgroup.default_mmodule == parent then parent = mgroup
117 end
118 if parent isa MGroup then
119 if parent.mpackage.mgroups.first == parent then parent = parent.mpackage
120 end
121 draw_edge(mentity, parent)
122 end
123 for parent in parents do
124 if parent isa MModule then
125 var mgroup = parent.mgroup
126 if mgroup != null and mgroup.default_mmodule == parent then parent = mgroup
127 end
128 if parent isa MGroup then
129 if parent.mpackage.mgroups.first == parent then parent = parent.mpackage
130 end
131 draw_parents(parent, max_depth, current_depth + 1)
132 end
133 end
134
135 # Recursively draw children of mentity
136 fun draw_children(mentity: MEntity, max_depth: nullable Int, current_depth: nullable Int) do
137 if done_children.has(mentity) then return
138 done_children.add mentity
139 current_depth = current_depth or else 0
140 if max_depth != null and current_depth >= max_depth then
141 to_dotdotdot(mentity)
142 return
143 end
144 var children = mentity.collect_children(view)
145 if children.length > 10 then
146 to_dotdotdot(mentity)
147 return
148 end
149 for child in children do
150 if child isa MGroup then
151 if child.mpackage.mgroups.first == child then child = child.mpackage
152 end
153 draw_edge(child, mentity)
154 end
155 for child in children do
156 if child isa MGroup then
157 if child.mpackage.mgroups.first == child then child = child.mpackage
158 end
159 draw_children(child, max_depth, current_depth + 1)
160 end
161 end
162
163 # Draw a node from a `mentity`
164 fun draw_node(mentity: MEntity): DotElement do
165 if nodes.has_key(mentity) then return nodes[mentity]
166 var node: DotElement = mentity.to_dot_node
167 if mentity == center then node = highlight(node)
168 nodes[mentity] = node
169 graph.add node
170 return node
171 end
172
173 private var edges = new HashMap2[MEntity, MEntity, DotEdge]
174
175 # Draw a edges between two mentities
176 fun draw_edge(from, to: MEntity): DotEdge do
177 if edges.has(from, to) then return edges[from, to].as(not null)
178 if edges.has(to, from) then return edges[to, from].as(not null)
179 var nfrom = draw_node(from)
180 var nto = draw_node(to)
181 var edge = new DotEdge(nfrom, nto)
182 edges[from, to] = edge
183 graph.add edge
184 return edge
185 end
186
187 private var to_dots = new HashMap[MEntity, DotElement]
188
189 # Create a link from `mentity` to a `...` node
190 fun to_dotdotdot(mentity: MEntity): DotEdge do
191 var nto = draw_node(mentity)
192 var dots = to_dots.get_or_null(mentity)
193 if dots == null then
194 dots = dotdotdot("{nto.id}...")
195 to_dots[mentity] = dots
196 end
197 graph.add dots
198 var edge = new DotEdge(dots, nto)
199 graph.add edge
200 return edge
201 end
202
203 private var from_dots = new HashMap[MEntity, DotElement]
204
205 # Create a link from a `...` node to a `mentity`
206 fun from_dotdotdot(mentity: MEntity): DotEdge do
207 var nfrom = draw_node(mentity)
208 var dots = to_dots.get_or_null(mentity)
209 if dots == null then
210 dots = dotdotdot("...{nfrom.id}")
211 from_dots[mentity] = dots
212 end
213 graph.add dots
214 var edge = new DotEdge(dots, nfrom)
215 graph.add edge
216 return edge
217 end
218
219 # Change the border color of the node
220 fun highlight(dot: DotElement): DotElement do
221 dot["color"] = "#1E9431"
222 return dot
223 end
224
225 # Generate a `...` node
226 fun dotdotdot(id: String): DotNode do
227 var node = new DotNode(id)
228 node["label"] = "..."
229 node["shape"] = "none"
230 return node
231 end
232 end
233
234 redef class MEntity
235 private fun to_dot_node: DotNode do
236 var node = new DotNode(full_name)
237 node["URL"] = web_url
238 node["label"] = name
239 return node
240 end
241 end
242
243 redef class MPackage
244 redef fun to_dot_node do
245 var node = super
246 node["shape"] = "tab"
247 return node
248 end
249 end
250
251 redef class MGroup
252 redef fun to_dot_node do
253 var node = super
254 node["shape"] = "folder"
255 return node
256 end
257 end
258
259 redef class MModule
260 redef fun to_dot_node do
261 var node = super
262 node["shape"] = "note"
263 return node
264 end
265 end
266
267 redef class MClass
268 redef fun to_dot_node do
269 var node = super
270 node["shape"] = "box"
271 return node
272 end
273 end