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