|
5 | 5 | import sys |
6 | 6 | from typing import List |
7 | 7 |
|
| 8 | +from graphviz import Source, Digraph |
| 9 | + |
8 | 10 | from aperturedb.Connector import Connector |
9 | 11 | from aperturedb.ConnectorRest import ConnectorRest |
10 | 12 | from aperturedb import ProgressBar |
@@ -181,6 +183,78 @@ def get_schema(self, refresh=False): |
181 | 183 |
|
182 | 184 | return schema |
183 | 185 |
|
| 186 | + def visualize_schema(self, filename: str = None, format: str = "png") -> Source: |
| 187 | + """ |
| 188 | + Visualize the schema of the database. Optionally save the visualization to a file in the specified format. |
| 189 | +
|
| 190 | + The returned object can be rendered to a file as follows: |
| 191 | + ```python |
| 192 | + s = utils.visualize_schema() |
| 193 | + s.render("schema", format="png") |
| 194 | + ``` |
| 195 | +
|
| 196 | + It can also be displayed inline in a Jupyter notebook: |
| 197 | + ```python |
| 198 | + from IPython.display import display |
| 199 | + s = utils.visualize_schema() |
| 200 | + display(s) |
| 201 | + ``` |
| 202 | +
|
| 203 | + Relies on graphviz to be installed. |
| 204 | +
|
| 205 | + Args: |
| 206 | + filename (str, optional): The filename to save the visualization to. Default is None. |
| 207 | + format (str, optional): The format to save the visualization to. Default is "png". |
| 208 | +
|
| 209 | + Returns: |
| 210 | + source: The visualization of the schema. |
| 211 | + """ |
| 212 | + r = self.get_schema() |
| 213 | + |
| 214 | + dot = Digraph(comment='ApertureDB Schema Diagram', node_attr={ |
| 215 | + 'shape': 'none'}, graph_attr={'rankdir': 'LR'}) |
| 216 | + |
| 217 | + # Add entities as nodes and connections as edges |
| 218 | + entities = r['entities']['classes'] |
| 219 | + connections = r['connections']['classes'] |
| 220 | + |
| 221 | + for entity, data in entities.items(): |
| 222 | + matched = data["matched"] |
| 223 | + # dictionary from name to (matched, indexed, type) |
| 224 | + properties = data["properties"] |
| 225 | + table = f'''< |
| 226 | + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> |
| 227 | + <TR><TD BGCOLOR="lightgrey">{entity} ({matched:,})</TD></TR> |
| 228 | + ''' |
| 229 | + for prop, (matched, indexed, typ) in properties.items(): |
| 230 | + table += f'<TR><TD BGCOLOR="lightblue">{prop} ({matched:,}): {"Indexed" if indexed else "Unindexed"}, {typ}</TD></TR>' |
| 231 | + for connection, data in connections.items(): |
| 232 | + if data['src'] == entity: |
| 233 | + matched = data["matched"] |
| 234 | + # dictionary from name to (matched, indexed, type) |
| 235 | + properties = data["properties"] |
| 236 | + table += f'<TR><TD BGCOLOR="lightgreen"><TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">' |
| 237 | + table += f'<TR><TD PORT="{connection}">{connection} ({matched:,})</TD></TR>' |
| 238 | + if properties: |
| 239 | + for prop, (matched, indexed, typ) in properties.items(): |
| 240 | + table += f'<TR><TD>{prop} ({matched:,}): {"Indexed" if indexed else "Unindexed"}, {typ}</TD></TR>' |
| 241 | + table += '</TABLE></TD></TR>' |
| 242 | + |
| 243 | + table += '</TABLE>>' |
| 244 | + dot.node(entity, label=table) |
| 245 | + |
| 246 | + for connection, data in connections.items(): |
| 247 | + dot.edge(f'{data["src"]}:{connection}', |
| 248 | + f'{data["dst"]}:{connection}') |
| 249 | + |
| 250 | + # Render the diagram inline |
| 251 | + s = Source(dot.source, filename="schema_diagram.gv", format="png") |
| 252 | + |
| 253 | + if filename is not None: |
| 254 | + s.render(filename, format=format) |
| 255 | + |
| 256 | + return s |
| 257 | + |
184 | 258 | def _object_summary(self, name, object): |
185 | 259 |
|
186 | 260 | total_elements = object["matched"] |
|
0 commit comments