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