powerbi-ontology-extractor 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,332 @@
1
+ """
2
+ Ontology Visualizer
3
+
4
+ Creates visualizations of ontologies using networkx, matplotlib, and plotly.
5
+ """
6
+
7
+ import logging
8
+ from typing import Optional
9
+
10
+ try:
11
+ import networkx as nx
12
+ import matplotlib.pyplot as plt
13
+ import plotly.graph_objects as go
14
+ import plotly.express as px
15
+ except ImportError:
16
+ logger = logging.getLogger(__name__)
17
+ logger.warning("Visualization libraries not installed. Install with: pip install networkx matplotlib plotly")
18
+
19
+ from powerbi_ontology.ontology_generator import Ontology, OntologyEntity, OntologyRelationship
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class OntologyVisualizer:
25
+ """
26
+ Visualizes ontologies as entity-relationship diagrams and interactive graphs.
27
+ """
28
+
29
+ def __init__(self, ontology: Ontology):
30
+ """
31
+ Initialize visualizer.
32
+
33
+ Args:
34
+ ontology: Ontology to visualize
35
+ """
36
+ self.ontology = ontology
37
+ self.graph = self._build_graph()
38
+
39
+ def _build_graph(self):
40
+ """Build networkx graph from ontology."""
41
+ try:
42
+ import networkx as nx
43
+ except ImportError:
44
+ logger.error("networkx not installed. Cannot build graph.")
45
+ return None
46
+
47
+ G = nx.DiGraph()
48
+
49
+ # Add entities as nodes
50
+ for entity in self.ontology.entities:
51
+ G.add_node(
52
+ entity.name,
53
+ node_type=entity.entity_type,
54
+ description=entity.description,
55
+ property_count=len(entity.properties)
56
+ )
57
+
58
+ # Add relationships as edges
59
+ for rel in self.ontology.relationships:
60
+ G.add_edge(
61
+ rel.from_entity,
62
+ rel.to_entity,
63
+ relationship_type=rel.relationship_type,
64
+ cardinality=rel.cardinality,
65
+ label=rel.relationship_type
66
+ )
67
+
68
+ return G
69
+
70
+ def plot_entity_relationship_diagram(self) -> Optional[object]:
71
+ """
72
+ Create entity-relationship diagram using matplotlib.
73
+
74
+ Returns:
75
+ matplotlib Figure object
76
+ """
77
+ if not self.graph:
78
+ logger.error("Graph not built. Cannot create diagram.")
79
+ return None
80
+
81
+ try:
82
+ import matplotlib.pyplot as plt
83
+ import networkx as nx
84
+ except ImportError:
85
+ logger.error("matplotlib or networkx not installed.")
86
+ return None
87
+
88
+ fig, ax = plt.subplots(figsize=(12, 8))
89
+
90
+ # Layout
91
+ pos = nx.spring_layout(self.graph, k=2, iterations=50)
92
+
93
+ # Color nodes by entity type
94
+ node_colors = []
95
+ for node in self.graph.nodes():
96
+ node_type = self.graph.nodes[node].get('node_type', 'standard')
97
+ if node_type == 'dimension':
98
+ node_colors.append('#90EE90') # Light green
99
+ elif node_type == 'fact':
100
+ node_colors.append('#87CEEB') # Sky blue
101
+ elif node_type == 'date':
102
+ node_colors.append('#FFB6C1') # Light pink
103
+ else:
104
+ node_colors.append('#D3D3D3') # Light gray
105
+
106
+ # Draw nodes
107
+ nx.draw_networkx_nodes(
108
+ self.graph, pos,
109
+ node_color=node_colors,
110
+ node_size=2000,
111
+ alpha=0.9,
112
+ ax=ax
113
+ )
114
+
115
+ # Draw edges
116
+ nx.draw_networkx_edges(
117
+ self.graph, pos,
118
+ edge_color='gray',
119
+ arrows=True,
120
+ arrowsize=20,
121
+ alpha=0.6,
122
+ ax=ax
123
+ )
124
+
125
+ # Draw labels
126
+ nx.draw_networkx_labels(
127
+ self.graph, pos,
128
+ font_size=10,
129
+ font_weight='bold',
130
+ ax=ax
131
+ )
132
+
133
+ # Draw edge labels
134
+ edge_labels = {
135
+ (u, v): self.graph.edges[u, v].get('label', '')
136
+ for u, v in self.graph.edges()
137
+ }
138
+ nx.draw_networkx_edge_labels(
139
+ self.graph, pos,
140
+ edge_labels=edge_labels,
141
+ font_size=8,
142
+ ax=ax
143
+ )
144
+
145
+ ax.set_title(f"Entity-Relationship Diagram: {self.ontology.name}", fontsize=16, fontweight='bold')
146
+ ax.axis('off')
147
+
148
+ plt.tight_layout()
149
+ return fig
150
+
151
+ def generate_interactive_graph(self) -> Optional[object]:
152
+ """
153
+ Generate interactive graph using plotly.
154
+
155
+ Returns:
156
+ plotly Figure object
157
+ """
158
+ if not self.graph:
159
+ logger.error("Graph not built. Cannot create interactive graph.")
160
+ return None
161
+
162
+ try:
163
+ import plotly.graph_objects as go
164
+ import networkx as nx
165
+ except ImportError:
166
+ logger.error("plotly or networkx not installed.")
167
+ return None
168
+
169
+ # Get positions
170
+ pos = nx.spring_layout(self.graph, k=2, iterations=50)
171
+
172
+ # Prepare edge traces
173
+ edge_x = []
174
+ edge_y = []
175
+ edge_info = []
176
+ for edge in self.graph.edges():
177
+ x0, y0 = pos[edge[0]]
178
+ x1, y1 = pos[edge[1]]
179
+ edge_x.extend([x0, x1, None])
180
+ edge_y.extend([y0, y1, None])
181
+ edge_info.append(self.graph.edges[edge].get('relationship_type', ''))
182
+
183
+ edge_trace = go.Scatter(
184
+ x=edge_x, y=edge_y,
185
+ line=dict(width=2, color='#888'),
186
+ hoverinfo='none',
187
+ mode='lines'
188
+ )
189
+
190
+ # Prepare node traces
191
+ node_x = []
192
+ node_y = []
193
+ node_text = []
194
+ node_info = []
195
+ node_colors = []
196
+
197
+ for node in self.graph.nodes():
198
+ x, y = pos[node]
199
+ node_x.append(x)
200
+ node_y.append(y)
201
+
202
+ node_data = self.graph.nodes[node]
203
+ node_type = node_data.get('node_type', 'standard')
204
+ prop_count = node_data.get('property_count', 0)
205
+
206
+ node_text.append(f"{node}<br>{node_type}<br>{prop_count} properties")
207
+ node_info.append(f"Type: {node_type}<br>Properties: {prop_count}")
208
+
209
+ # Color by type
210
+ if node_type == 'dimension':
211
+ node_colors.append('#90EE90')
212
+ elif node_type == 'fact':
213
+ node_colors.append('#87CEEB')
214
+ elif node_type == 'date':
215
+ node_colors.append('#FFB6C1')
216
+ else:
217
+ node_colors.append('#D3D3D3')
218
+
219
+ node_trace = go.Scatter(
220
+ x=node_x, y=node_y,
221
+ mode='markers+text',
222
+ hoverinfo='text',
223
+ text=[node for node in self.graph.nodes()],
224
+ textposition="middle center",
225
+ hovertext=node_info,
226
+ marker=dict(
227
+ size=30,
228
+ color=node_colors,
229
+ line=dict(width=2, color='black')
230
+ )
231
+ )
232
+
233
+ fig = go.Figure(
234
+ data=[edge_trace, node_trace],
235
+ layout=go.Layout(
236
+ title=f"Interactive Ontology Graph: {self.ontology.name}",
237
+ titlefont_size=16,
238
+ showlegend=False,
239
+ hovermode='closest',
240
+ margin=dict(b=20, l=5, r=5, t=40),
241
+ annotations=[
242
+ dict(
243
+ text="Hover over nodes for details",
244
+ showarrow=False,
245
+ xref="paper", yref="paper",
246
+ x=0.005, y=-0.002,
247
+ xanchor="left", yanchor="bottom",
248
+ font=dict(color="#888", size=12)
249
+ )
250
+ ],
251
+ xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
252
+ yaxis=dict(showgrid=False, zeroline=False, showticklabels=False)
253
+ )
254
+ )
255
+
256
+ return fig
257
+
258
+ def export_mermaid_diagram(self) -> str:
259
+ """
260
+ Export ontology as Mermaid diagram code.
261
+
262
+ Returns:
263
+ Mermaid diagram code string
264
+ """
265
+ mermaid = ["erDiagram"]
266
+
267
+ # Add entities
268
+ for entity in self.ontology.entities:
269
+ entity_name = entity.name.upper()
270
+ mermaid.append(f" {entity_name} {{")
271
+
272
+ # Add key properties
273
+ for prop in entity.properties[:5]: # Limit to 5 for readability
274
+ prop_type = prop.data_type.lower()
275
+ prop_name = prop.name
276
+ if prop.unique:
277
+ prop_name += " PK"
278
+ mermaid.append(f" {prop_type} {prop_name}")
279
+
280
+ if len(entity.properties) > 5:
281
+ mermaid.append(f" ... {len(entity.properties) - 5} more properties")
282
+
283
+ mermaid.append(" }")
284
+
285
+ # Add relationships
286
+ for rel in self.ontology.relationships:
287
+ from_entity = rel.from_entity.upper()
288
+ to_entity = rel.to_entity.upper()
289
+ rel_type = rel.relationship_type.replace("_", " ")
290
+
291
+ # Mermaid cardinality notation
292
+ if rel.cardinality == "one-to-many":
293
+ cardinality = "||--o{"
294
+ elif rel.cardinality == "many-to-one":
295
+ cardinality = "}o--||"
296
+ elif rel.cardinality == "one-to-one":
297
+ cardinality = "||--||"
298
+ else:
299
+ cardinality = "}o--o{"
300
+
301
+ mermaid.append(f" {from_entity} {cardinality} {to_entity} : \"{rel_type}\"")
302
+
303
+ return "\n".join(mermaid)
304
+
305
+ def save_as_image(self, filename: str, format: str = "png"):
306
+ """
307
+ Save visualization as image.
308
+
309
+ Args:
310
+ filename: Output filename
311
+ format: Image format ("png", "svg", "pdf")
312
+ """
313
+ fig = self.plot_entity_relationship_diagram()
314
+ if fig:
315
+ fig.savefig(filename, format=format, dpi=300, bbox_inches='tight')
316
+ logger.info(f"Saved diagram to {filename}")
317
+ else:
318
+ logger.error("Could not create diagram")
319
+
320
+ def save_interactive_html(self, filename: str):
321
+ """
322
+ Save interactive graph as HTML.
323
+
324
+ Args:
325
+ filename: Output HTML filename
326
+ """
327
+ fig = self.generate_interactive_graph()
328
+ if fig:
329
+ fig.write_html(filename)
330
+ logger.info(f"Saved interactive graph to {filename}")
331
+ else:
332
+ logger.error("Could not create interactive graph")