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