Spaces:
Running
Running
Update class_diagram_generator.py
Browse files- class_diagram_generator.py +391 -35
class_diagram_generator.py
CHANGED
@@ -1,3 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import graphviz
|
2 |
import json
|
3 |
from tempfile import NamedTemporaryFile
|
@@ -199,10 +543,11 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
|
|
199 |
if 'classes' not in data:
|
200 |
raise ValueError("Missing required field: classes")
|
201 |
|
|
|
202 |
dot = graphviz.Digraph(comment='Class Diagram')
|
203 |
-
dot.attr(rankdir='TB', bgcolor='white', pad='0.5')
|
204 |
-
dot.attr('node', shape='
|
205 |
-
dot.attr('edge', color='black', fontname='Arial', fontsize='9')
|
206 |
|
207 |
classes = data.get('classes', [])
|
208 |
relationships = data.get('relationships', [])
|
@@ -216,19 +561,24 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
|
|
216 |
if not class_name:
|
217 |
continue
|
218 |
|
219 |
-
|
|
|
220 |
|
|
|
221 |
if class_type == 'abstract':
|
222 |
-
|
223 |
elif class_type == 'interface':
|
224 |
-
|
225 |
elif class_type == 'enum':
|
226 |
-
|
227 |
else:
|
228 |
-
|
|
|
|
|
|
|
229 |
|
|
|
230 |
if attributes:
|
231 |
-
attr_lines = []
|
232 |
for attr in attributes:
|
233 |
visibility = attr.get('visibility', '+')
|
234 |
name = attr.get('name', '')
|
@@ -239,15 +589,18 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
|
|
239 |
if attr_type:
|
240 |
line += f" : {attr_type}"
|
241 |
if is_static:
|
242 |
-
line
|
243 |
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
|
|
|
|
|
|
248 |
|
|
|
249 |
if methods:
|
250 |
-
method_lines = []
|
251 |
for method in methods:
|
252 |
visibility = method.get('visibility', '+')
|
253 |
name = method.get('name', '')
|
@@ -267,29 +620,21 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
|
|
267 |
line += f") : {return_type}"
|
268 |
|
269 |
if is_static:
|
270 |
-
line
|
271 |
if is_abstract:
|
272 |
-
line
|
273 |
|
274 |
-
|
275 |
-
|
276 |
-
if method_lines:
|
277 |
-
label_parts.append("\\n".join(method_lines))
|
278 |
-
|
279 |
-
label = "\\n\\n".join(label_parts)
|
280 |
-
|
281 |
-
if class_type == 'interface':
|
282 |
-
fillcolor = '#f5f5f5'
|
283 |
-
style = 'filled,dashed'
|
284 |
-
elif class_type == 'abstract':
|
285 |
-
fillcolor = '#eeeeee'
|
286 |
-
style = 'filled'
|
287 |
else:
|
288 |
-
|
289 |
-
|
290 |
|
291 |
-
|
|
|
|
|
|
|
292 |
|
|
|
293 |
for relationship in relationships:
|
294 |
from_class = relationship.get('from')
|
295 |
to_class = relationship.get('to')
|
@@ -301,7 +646,10 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
|
|
301 |
if not from_class or not to_class:
|
302 |
continue
|
303 |
|
304 |
-
edge_attrs = {
|
|
|
|
|
|
|
305 |
|
306 |
if label:
|
307 |
edge_attrs['label'] = label
|
@@ -312,27 +660,35 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
|
|
312 |
if multiplicity_to:
|
313 |
edge_attrs['headlabel'] = multiplicity_to
|
314 |
|
|
|
315 |
if rel_type == 'inheritance':
|
316 |
edge_attrs['arrowhead'] = 'empty'
|
|
|
317 |
elif rel_type == 'composition':
|
318 |
edge_attrs['arrowhead'] = 'normal'
|
319 |
edge_attrs['arrowtail'] = 'diamond'
|
320 |
edge_attrs['dir'] = 'both'
|
|
|
321 |
elif rel_type == 'aggregation':
|
322 |
edge_attrs['arrowhead'] = 'normal'
|
323 |
edge_attrs['arrowtail'] = 'odiamond'
|
324 |
edge_attrs['dir'] = 'both'
|
|
|
325 |
elif rel_type == 'realization':
|
326 |
edge_attrs['arrowhead'] = 'empty'
|
327 |
edge_attrs['style'] = 'dashed'
|
|
|
328 |
elif rel_type == 'dependency':
|
329 |
edge_attrs['arrowhead'] = 'normal'
|
330 |
edge_attrs['style'] = 'dashed'
|
331 |
-
|
|
|
332 |
edge_attrs['arrowhead'] = 'normal'
|
|
|
333 |
|
334 |
dot.edge(from_class, to_class, **edge_attrs)
|
335 |
|
|
|
336 |
with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
|
337 |
dot.render(tmp.name, format=output_format, cleanup=True)
|
338 |
return f"{tmp.name}.{output_format}"
|
|
|
1 |
+
# import graphviz
|
2 |
+
# import json
|
3 |
+
# from tempfile import NamedTemporaryFile
|
4 |
+
# import os
|
5 |
+
|
6 |
+
# def generate_class_diagram(json_input: str, output_format: str) -> str:
|
7 |
+
# """
|
8 |
+
# Generates a class diagram from JSON input.
|
9 |
+
|
10 |
+
# Args:
|
11 |
+
# json_input (str): A JSON string describing the class diagram structure.
|
12 |
+
# It must follow the Expected JSON Format Example below.
|
13 |
+
|
14 |
+
# Expected JSON Format Example:
|
15 |
+
# {
|
16 |
+
# "classes": [
|
17 |
+
# {
|
18 |
+
# "name": "Vehicle",
|
19 |
+
# "type": "abstract",
|
20 |
+
# "attributes": [
|
21 |
+
# {"name": "id", "type": "String", "visibility": "-"},
|
22 |
+
# {"name": "brand", "type": "String", "visibility": "#"},
|
23 |
+
# {"name": "model", "type": "String", "visibility": "#"},
|
24 |
+
# {"name": "year", "type": "int", "visibility": "#"},
|
25 |
+
# {"name": "price", "type": "double", "visibility": "+"},
|
26 |
+
# {"name": "vehicleCount", "type": "int", "visibility": "+", "static": true}
|
27 |
+
# ],
|
28 |
+
# "methods": [
|
29 |
+
# {"name": "Vehicle", "parameters": [{"name": "brand", "type": "String"}, {"name": "model", "type": "String"}], "return_type": "Vehicle", "visibility": "+"},
|
30 |
+
# {"name": "startEngine", "return_type": "void", "visibility": "+", "abstract": true},
|
31 |
+
# {"name": "stopEngine", "return_type": "void", "visibility": "+"},
|
32 |
+
# {"name": "getPrice", "return_type": "double", "visibility": "+"},
|
33 |
+
# {"name": "setPrice", "parameters": [{"name": "price", "type": "double"}], "return_type": "void", "visibility": "+"},
|
34 |
+
# {"name": "getTotalVehicles", "return_type": "int", "visibility": "+", "static": true}
|
35 |
+
# ]
|
36 |
+
# },
|
37 |
+
# {
|
38 |
+
# "name": "Car",
|
39 |
+
# "type": "class",
|
40 |
+
# "attributes": [
|
41 |
+
# {"name": "doors", "type": "int", "visibility": "-"},
|
42 |
+
# {"name": "transmission", "type": "TransmissionType", "visibility": "-"},
|
43 |
+
# {"name": "fuelType", "type": "FuelType", "visibility": "-"}
|
44 |
+
# ],
|
45 |
+
# "methods": [
|
46 |
+
# {"name": "Car", "parameters": [{"name": "brand", "type": "String"}, {"name": "model", "type": "String"}, {"name": "doors", "type": "int"}], "return_type": "Car", "visibility": "+"},
|
47 |
+
# {"name": "startEngine", "return_type": "void", "visibility": "+"},
|
48 |
+
# {"name": "openTrunk", "return_type": "void", "visibility": "+"},
|
49 |
+
# {"name": "getDoors", "return_type": "int", "visibility": "+"},
|
50 |
+
# {"name": "setTransmission", "parameters": [{"name": "transmission", "type": "TransmissionType"}], "return_type": "void", "visibility": "+"}
|
51 |
+
# ]
|
52 |
+
# },
|
53 |
+
# {
|
54 |
+
# "name": "Motorcycle",
|
55 |
+
# "type": "class",
|
56 |
+
# "attributes": [
|
57 |
+
# {"name": "engineSize", "type": "int", "visibility": "-"},
|
58 |
+
# {"name": "hasWindshield", "type": "boolean", "visibility": "-"}
|
59 |
+
# ],
|
60 |
+
# "methods": [
|
61 |
+
# {"name": "Motorcycle", "parameters": [{"name": "brand", "type": "String"}, {"name": "model", "type": "String"}], "return_type": "Motorcycle", "visibility": "+"},
|
62 |
+
# {"name": "startEngine", "return_type": "void", "visibility": "+"},
|
63 |
+
# {"name": "wheelie", "return_type": "void", "visibility": "+"},
|
64 |
+
# {"name": "getEngineSize", "return_type": "int", "visibility": "+"}
|
65 |
+
# ]
|
66 |
+
# },
|
67 |
+
# {
|
68 |
+
# "name": "Engine",
|
69 |
+
# "type": "class",
|
70 |
+
# "attributes": [
|
71 |
+
# {"name": "horsepower", "type": "int", "visibility": "-"},
|
72 |
+
# {"name": "cylinders", "type": "int", "visibility": "-"},
|
73 |
+
# {"name": "fuelType", "type": "FuelType", "visibility": "-"}
|
74 |
+
# ],
|
75 |
+
# "methods": [
|
76 |
+
# {"name": "Engine", "parameters": [{"name": "horsepower", "type": "int"}, {"name": "cylinders", "type": "int"}], "return_type": "Engine", "visibility": "+"},
|
77 |
+
# {"name": "start", "return_type": "boolean", "visibility": "+"},
|
78 |
+
# {"name": "stop", "return_type": "void", "visibility": "+"},
|
79 |
+
# {"name": "getHorsepower", "return_type": "int", "visibility": "+"}
|
80 |
+
# ]
|
81 |
+
# },
|
82 |
+
# {
|
83 |
+
# "name": "TransmissionType",
|
84 |
+
# "type": "enum",
|
85 |
+
# "attributes": [
|
86 |
+
# {"name": "MANUAL", "type": "TransmissionType", "visibility": "+", "static": true},
|
87 |
+
# {"name": "AUTOMATIC", "type": "TransmissionType", "visibility": "+", "static": true},
|
88 |
+
# {"name": "CVT", "type": "TransmissionType", "visibility": "+", "static": true}
|
89 |
+
# ],
|
90 |
+
# "methods": []
|
91 |
+
# },
|
92 |
+
# {
|
93 |
+
# "name": "FuelType",
|
94 |
+
# "type": "enum",
|
95 |
+
# "attributes": [
|
96 |
+
# {"name": "GASOLINE", "type": "FuelType", "visibility": "+", "static": true},
|
97 |
+
# {"name": "DIESEL", "type": "FuelType", "visibility": "+", "static": true},
|
98 |
+
# {"name": "ELECTRIC", "type": "FuelType", "visibility": "+", "static": true},
|
99 |
+
# {"name": "HYBRID", "type": "FuelType", "visibility": "+", "static": true}
|
100 |
+
# ],
|
101 |
+
# "methods": []
|
102 |
+
# },
|
103 |
+
# {
|
104 |
+
# "name": "VehicleService",
|
105 |
+
# "type": "interface",
|
106 |
+
# "attributes": [],
|
107 |
+
# "methods": [
|
108 |
+
# {"name": "maintenance", "parameters": [{"name": "vehicle", "type": "Vehicle"}], "return_type": "void", "visibility": "+", "abstract": true},
|
109 |
+
# {"name": "repair", "parameters": [{"name": "vehicle", "type": "Vehicle"}, {"name": "issue", "type": "String"}], "return_type": "boolean", "visibility": "+", "abstract": true},
|
110 |
+
# {"name": "inspectVehicle", "parameters": [{"name": "vehicle", "type": "Vehicle"}], "return_type": "InspectionReport", "visibility": "+", "abstract": true}
|
111 |
+
# ]
|
112 |
+
# },
|
113 |
+
# {
|
114 |
+
# "name": "GarageService",
|
115 |
+
# "type": "class",
|
116 |
+
# "attributes": [
|
117 |
+
# {"name": "garageName", "type": "String", "visibility": "-"},
|
118 |
+
# {"name": "location", "type": "String", "visibility": "-"}
|
119 |
+
# ],
|
120 |
+
# "methods": [
|
121 |
+
# {"name": "GarageService", "parameters": [{"name": "name", "type": "String"}], "return_type": "GarageService", "visibility": "+"},
|
122 |
+
# {"name": "maintenance", "parameters": [{"name": "vehicle", "type": "Vehicle"}], "return_type": "void", "visibility": "+"},
|
123 |
+
# {"name": "repair", "parameters": [{"name": "vehicle", "type": "Vehicle"}, {"name": "issue", "type": "String"}], "return_type": "boolean", "visibility": "+"},
|
124 |
+
# {"name": "inspectVehicle", "parameters": [{"name": "vehicle", "type": "Vehicle"}], "return_type": "InspectionReport", "visibility": "+"}
|
125 |
+
# ]
|
126 |
+
# }
|
127 |
+
# ],
|
128 |
+
# "relationships": [
|
129 |
+
# {
|
130 |
+
# "from": "Car",
|
131 |
+
# "to": "Vehicle",
|
132 |
+
# "type": "inheritance",
|
133 |
+
# "label": "extends"
|
134 |
+
# },
|
135 |
+
# {
|
136 |
+
# "from": "Motorcycle",
|
137 |
+
# "to": "Vehicle",
|
138 |
+
# "type": "inheritance",
|
139 |
+
# "label": "extends"
|
140 |
+
# },
|
141 |
+
# {
|
142 |
+
# "from": "Car",
|
143 |
+
# "to": "Engine",
|
144 |
+
# "type": "composition",
|
145 |
+
# "label": "has",
|
146 |
+
# "multiplicity_from": "1",
|
147 |
+
# "multiplicity_to": "1"
|
148 |
+
# },
|
149 |
+
# {
|
150 |
+
# "from": "Motorcycle",
|
151 |
+
# "to": "Engine",
|
152 |
+
# "type": "composition",
|
153 |
+
# "label": "has",
|
154 |
+
# "multiplicity_from": "1",
|
155 |
+
# "multiplicity_to": "1"
|
156 |
+
# },
|
157 |
+
# {
|
158 |
+
# "from": "Car",
|
159 |
+
# "to": "TransmissionType",
|
160 |
+
# "type": "association",
|
161 |
+
# "label": "uses",
|
162 |
+
# "multiplicity_from": "1",
|
163 |
+
# "multiplicity_to": "1"
|
164 |
+
# },
|
165 |
+
# {
|
166 |
+
# "from": "Vehicle",
|
167 |
+
# "to": "FuelType",
|
168 |
+
# "type": "association",
|
169 |
+
# "label": "uses",
|
170 |
+
# "multiplicity_from": "1",
|
171 |
+
# "multiplicity_to": "1"
|
172 |
+
# },
|
173 |
+
# {
|
174 |
+
# "from": "GarageService",
|
175 |
+
# "to": "VehicleService",
|
176 |
+
# "type": "realization",
|
177 |
+
# "label": "implements"
|
178 |
+
# },
|
179 |
+
# {
|
180 |
+
# "from": "GarageService",
|
181 |
+
# "to": "Vehicle",
|
182 |
+
# "type": "dependency",
|
183 |
+
# "label": "services",
|
184 |
+
# "multiplicity_from": "1",
|
185 |
+
# "multiplicity_to": "*"
|
186 |
+
# }
|
187 |
+
# ]
|
188 |
+
# }
|
189 |
+
|
190 |
+
# Returns:
|
191 |
+
# str: The filepath to the generated PNG image file.
|
192 |
+
# """
|
193 |
+
# try:
|
194 |
+
# if not json_input.strip():
|
195 |
+
# return "Error: Empty input"
|
196 |
+
|
197 |
+
# data = json.loads(json_input)
|
198 |
+
|
199 |
+
# if 'classes' not in data:
|
200 |
+
# raise ValueError("Missing required field: classes")
|
201 |
+
|
202 |
+
# dot = graphviz.Digraph(comment='Class Diagram')
|
203 |
+
# dot.attr(rankdir='TB', bgcolor='white', pad='0.5')
|
204 |
+
# dot.attr('node', shape='box', style='filled', fillcolor='white', color='black', fontname='Arial', fontsize='10')
|
205 |
+
# dot.attr('edge', color='black', fontname='Arial', fontsize='9')
|
206 |
+
|
207 |
+
# classes = data.get('classes', [])
|
208 |
+
# relationships = data.get('relationships', [])
|
209 |
+
|
210 |
+
# for cls in classes:
|
211 |
+
# class_name = cls.get('name')
|
212 |
+
# class_type = cls.get('type', 'class')
|
213 |
+
# attributes = cls.get('attributes', [])
|
214 |
+
# methods = cls.get('methods', [])
|
215 |
+
|
216 |
+
# if not class_name:
|
217 |
+
# continue
|
218 |
+
|
219 |
+
# label_parts = []
|
220 |
+
|
221 |
+
# if class_type == 'abstract':
|
222 |
+
# label_parts.append(f"<<abstract>>\\n{class_name}")
|
223 |
+
# elif class_type == 'interface':
|
224 |
+
# label_parts.append(f"<<interface>>\\n{class_name}")
|
225 |
+
# elif class_type == 'enum':
|
226 |
+
# label_parts.append(f"<<enumeration>>\\n{class_name}")
|
227 |
+
# else:
|
228 |
+
# label_parts.append(class_name)
|
229 |
+
|
230 |
+
# if attributes:
|
231 |
+
# attr_lines = []
|
232 |
+
# for attr in attributes:
|
233 |
+
# visibility = attr.get('visibility', '+')
|
234 |
+
# name = attr.get('name', '')
|
235 |
+
# attr_type = attr.get('type', '')
|
236 |
+
# is_static = attr.get('static', False)
|
237 |
+
|
238 |
+
# line = f"{visibility} {name}"
|
239 |
+
# if attr_type:
|
240 |
+
# line += f" : {attr_type}"
|
241 |
+
# if is_static:
|
242 |
+
# line += " [static]"
|
243 |
+
|
244 |
+
# attr_lines.append(line)
|
245 |
+
|
246 |
+
# if attr_lines:
|
247 |
+
# label_parts.append("\\n".join(attr_lines))
|
248 |
+
|
249 |
+
# if methods:
|
250 |
+
# method_lines = []
|
251 |
+
# for method in methods:
|
252 |
+
# visibility = method.get('visibility', '+')
|
253 |
+
# name = method.get('name', '')
|
254 |
+
# parameters = method.get('parameters', [])
|
255 |
+
# return_type = method.get('return_type', 'void')
|
256 |
+
# is_static = method.get('static', False)
|
257 |
+
# is_abstract = method.get('abstract', False)
|
258 |
+
|
259 |
+
# line = f"{visibility} {name}("
|
260 |
+
# if parameters:
|
261 |
+
# params = []
|
262 |
+
# for param in parameters:
|
263 |
+
# param_name = param.get('name', '')
|
264 |
+
# param_type = param.get('type', '')
|
265 |
+
# params.append(f"{param_name}: {param_type}")
|
266 |
+
# line += ", ".join(params)
|
267 |
+
# line += f") : {return_type}"
|
268 |
+
|
269 |
+
# if is_static:
|
270 |
+
# line += " [static]"
|
271 |
+
# if is_abstract:
|
272 |
+
# line += " [abstract]"
|
273 |
+
|
274 |
+
# method_lines.append(line)
|
275 |
+
|
276 |
+
# if method_lines:
|
277 |
+
# label_parts.append("\\n".join(method_lines))
|
278 |
+
|
279 |
+
# label = "\\n\\n".join(label_parts)
|
280 |
+
|
281 |
+
# if class_type == 'interface':
|
282 |
+
# fillcolor = '#f5f5f5'
|
283 |
+
# style = 'filled,dashed'
|
284 |
+
# elif class_type == 'abstract':
|
285 |
+
# fillcolor = '#eeeeee'
|
286 |
+
# style = 'filled'
|
287 |
+
# else:
|
288 |
+
# fillcolor = 'white'
|
289 |
+
# style = 'filled'
|
290 |
+
|
291 |
+
# dot.node(class_name, label, fillcolor=fillcolor, style=style)
|
292 |
+
|
293 |
+
# for relationship in relationships:
|
294 |
+
# from_class = relationship.get('from')
|
295 |
+
# to_class = relationship.get('to')
|
296 |
+
# rel_type = relationship.get('type', 'association')
|
297 |
+
# label = relationship.get('label', '')
|
298 |
+
# multiplicity_from = relationship.get('multiplicity_from', '')
|
299 |
+
# multiplicity_to = relationship.get('multiplicity_to', '')
|
300 |
+
|
301 |
+
# if not from_class or not to_class:
|
302 |
+
# continue
|
303 |
+
|
304 |
+
# edge_attrs = {}
|
305 |
+
|
306 |
+
# if label:
|
307 |
+
# edge_attrs['label'] = label
|
308 |
+
|
309 |
+
# if multiplicity_from:
|
310 |
+
# edge_attrs['taillabel'] = multiplicity_from
|
311 |
+
|
312 |
+
# if multiplicity_to:
|
313 |
+
# edge_attrs['headlabel'] = multiplicity_to
|
314 |
+
|
315 |
+
# if rel_type == 'inheritance':
|
316 |
+
# edge_attrs['arrowhead'] = 'empty'
|
317 |
+
# elif rel_type == 'composition':
|
318 |
+
# edge_attrs['arrowhead'] = 'normal'
|
319 |
+
# edge_attrs['arrowtail'] = 'diamond'
|
320 |
+
# edge_attrs['dir'] = 'both'
|
321 |
+
# elif rel_type == 'aggregation':
|
322 |
+
# edge_attrs['arrowhead'] = 'normal'
|
323 |
+
# edge_attrs['arrowtail'] = 'odiamond'
|
324 |
+
# edge_attrs['dir'] = 'both'
|
325 |
+
# elif rel_type == 'realization':
|
326 |
+
# edge_attrs['arrowhead'] = 'empty'
|
327 |
+
# edge_attrs['style'] = 'dashed'
|
328 |
+
# elif rel_type == 'dependency':
|
329 |
+
# edge_attrs['arrowhead'] = 'normal'
|
330 |
+
# edge_attrs['style'] = 'dashed'
|
331 |
+
# else:
|
332 |
+
# edge_attrs['arrowhead'] = 'normal'
|
333 |
+
|
334 |
+
# dot.edge(from_class, to_class, **edge_attrs)
|
335 |
+
|
336 |
+
# with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
|
337 |
+
# dot.render(tmp.name, format=output_format, cleanup=True)
|
338 |
+
# return f"{tmp.name}.{output_format}"
|
339 |
+
|
340 |
+
# except json.JSONDecodeError:
|
341 |
+
# return "Error: Invalid JSON format"
|
342 |
+
# except Exception as e:
|
343 |
+
# return f"Error: {str(e)}"
|
344 |
+
|
345 |
import graphviz
|
346 |
import json
|
347 |
from tempfile import NamedTemporaryFile
|
|
|
543 |
if 'classes' not in data:
|
544 |
raise ValueError("Missing required field: classes")
|
545 |
|
546 |
+
# Configuración del diagrama con mejor espaciado
|
547 |
dot = graphviz.Digraph(comment='Class Diagram')
|
548 |
+
dot.attr(rankdir='TB', bgcolor='white', pad='1.0', nodesep='1.5', ranksep='2.0')
|
549 |
+
dot.attr('node', shape='plaintext', fontname='Arial', fontsize='11')
|
550 |
+
dot.attr('edge', color='black', fontname='Arial', fontsize='9', minlen='2')
|
551 |
|
552 |
classes = data.get('classes', [])
|
553 |
relationships = data.get('relationships', [])
|
|
|
561 |
if not class_name:
|
562 |
continue
|
563 |
|
564 |
+
# Crear tabla HTML para estructura de clase
|
565 |
+
html_label = '<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="0" CELLPADDING="5" BGCOLOR="white">'
|
566 |
|
567 |
+
# Header con nombre de clase y estereotipo
|
568 |
if class_type == 'abstract':
|
569 |
+
html_label += f'<TR><TD ALIGN="CENTER"><B><<abstract>><BR/>{class_name}</B></TD></TR>'
|
570 |
elif class_type == 'interface':
|
571 |
+
html_label += f'<TR><TD ALIGN="CENTER"><B><<interface>><BR/>{class_name}</B></TD></TR>'
|
572 |
elif class_type == 'enum':
|
573 |
+
html_label += f'<TR><TD ALIGN="CENTER"><B><<enumeration>><BR/>{class_name}</B></TD></TR>'
|
574 |
else:
|
575 |
+
html_label += f'<TR><TD ALIGN="CENTER"><B>{class_name}</B></TD></TR>'
|
576 |
+
|
577 |
+
# Línea separadora después del nombre
|
578 |
+
html_label += '<HR/>'
|
579 |
|
580 |
+
# Sección de atributos
|
581 |
if attributes:
|
|
|
582 |
for attr in attributes:
|
583 |
visibility = attr.get('visibility', '+')
|
584 |
name = attr.get('name', '')
|
|
|
589 |
if attr_type:
|
590 |
line += f" : {attr_type}"
|
591 |
if is_static:
|
592 |
+
line = f"<U>{line}</U>" # Subrayado para elementos estáticos
|
593 |
|
594 |
+
html_label += f'<TR><TD ALIGN="LEFT">{line}</TD></TR>'
|
595 |
+
else:
|
596 |
+
# Espacio vacío si no hay atributos
|
597 |
+
html_label += '<TR><TD ALIGN="LEFT"> </TD></TR>'
|
598 |
+
|
599 |
+
# Línea separadora entre atributos y métodos
|
600 |
+
html_label += '<HR/>'
|
601 |
|
602 |
+
# Sección de métodos
|
603 |
if methods:
|
|
|
604 |
for method in methods:
|
605 |
visibility = method.get('visibility', '+')
|
606 |
name = method.get('name', '')
|
|
|
620 |
line += f") : {return_type}"
|
621 |
|
622 |
if is_static:
|
623 |
+
line = f"<U>{line}</U>" # Subrayado para métodos estáticos
|
624 |
if is_abstract:
|
625 |
+
line = f"<I>{line}</I>" # Cursiva para métodos abstractos
|
626 |
|
627 |
+
html_label += f'<TR><TD ALIGN="LEFT">{line}</TD></TR>'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
628 |
else:
|
629 |
+
# Espacio vacío si no hay métodos
|
630 |
+
html_label += '<TR><TD ALIGN="LEFT"> </TD></TR>'
|
631 |
|
632 |
+
html_label += '</TABLE>'
|
633 |
+
|
634 |
+
# Agregar nodo con la tabla HTML
|
635 |
+
dot.node(class_name, f'<{html_label}>')
|
636 |
|
637 |
+
# Procesar relaciones con líneas rectas
|
638 |
for relationship in relationships:
|
639 |
from_class = relationship.get('from')
|
640 |
to_class = relationship.get('to')
|
|
|
646 |
if not from_class or not to_class:
|
647 |
continue
|
648 |
|
649 |
+
edge_attrs = {
|
650 |
+
'splines': 'ortho', # Líneas rectas (ortogonales)
|
651 |
+
'concentrate': 'true'
|
652 |
+
}
|
653 |
|
654 |
if label:
|
655 |
edge_attrs['label'] = label
|
|
|
660 |
if multiplicity_to:
|
661 |
edge_attrs['headlabel'] = multiplicity_to
|
662 |
|
663 |
+
# Configurar estilos de flecha según el tipo de relación
|
664 |
if rel_type == 'inheritance':
|
665 |
edge_attrs['arrowhead'] = 'empty'
|
666 |
+
edge_attrs['color'] = 'black'
|
667 |
elif rel_type == 'composition':
|
668 |
edge_attrs['arrowhead'] = 'normal'
|
669 |
edge_attrs['arrowtail'] = 'diamond'
|
670 |
edge_attrs['dir'] = 'both'
|
671 |
+
edge_attrs['color'] = 'black'
|
672 |
elif rel_type == 'aggregation':
|
673 |
edge_attrs['arrowhead'] = 'normal'
|
674 |
edge_attrs['arrowtail'] = 'odiamond'
|
675 |
edge_attrs['dir'] = 'both'
|
676 |
+
edge_attrs['color'] = 'black'
|
677 |
elif rel_type == 'realization':
|
678 |
edge_attrs['arrowhead'] = 'empty'
|
679 |
edge_attrs['style'] = 'dashed'
|
680 |
+
edge_attrs['color'] = 'black'
|
681 |
elif rel_type == 'dependency':
|
682 |
edge_attrs['arrowhead'] = 'normal'
|
683 |
edge_attrs['style'] = 'dashed'
|
684 |
+
edge_attrs['color'] = 'black'
|
685 |
+
else: # association
|
686 |
edge_attrs['arrowhead'] = 'normal'
|
687 |
+
edge_attrs['color'] = 'black'
|
688 |
|
689 |
dot.edge(from_class, to_class, **edge_attrs)
|
690 |
|
691 |
+
# Renderizar el diagrama
|
692 |
with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
|
693 |
dot.render(tmp.name, format=output_format, cleanup=True)
|
694 |
return f"{tmp.name}.{output_format}"
|