|
@@ -3,7 +3,7 @@ import os |
|
|
import uuid |
|
|
import uuid |
|
|
from pathlib import Path |
|
|
from pathlib import Path |
|
|
from typing import Dict, List, Optional, Union |
|
|
from typing import Dict, List, Optional, Union |
|
|
|
|
|
|
|
|
|
|
|
from io import BytesIO |
|
|
from graphviz import Digraph |
|
|
from graphviz import Digraph |
|
|
|
|
|
|
|
|
# Global contexts for a diagrams and a cluster. |
|
|
# Global contexts for a diagrams and a cluster. |
|
@@ -42,7 +42,7 @@ class Diagram: |
|
|
__curvestyles = ("ortho", "curved") |
|
|
__curvestyles = ("ortho", "curved") |
|
|
__outformats = ("png", "jpg", "svg", "pdf", "dot") |
|
|
__outformats = ("png", "jpg", "svg", "pdf", "dot") |
|
|
|
|
|
|
|
|
# fmt: off |
|
|
|
|
|
|
|
|
# Default graph attributes |
|
|
_default_graph_attrs = { |
|
|
_default_graph_attrs = { |
|
|
"pad": "2.0", |
|
|
"pad": "2.0", |
|
|
"splines": "ortho", |
|
|
"splines": "ortho", |
|
@@ -59,10 +59,6 @@ class Diagram: |
|
|
"width": "1.4", |
|
|
"width": "1.4", |
|
|
"height": "1.4", |
|
|
"height": "1.4", |
|
|
"labelloc": "b", |
|
|
"labelloc": "b", |
|
|
# imagepos attribute is not backward compatible |
|
|
|
|
|
# TODO: check graphviz version to see if "imagepos" is available >= 2.40 |
|
|
|
|
|
# https://github.com/xflr6/graphviz/blob/master/graphviz/backend.py#L248 |
|
|
|
|
|
# "imagepos": "tc", |
|
|
|
|
|
"imagescale": "true", |
|
|
"imagescale": "true", |
|
|
"fontname": "Sans-Serif", |
|
|
"fontname": "Sans-Serif", |
|
|
"fontsize": "13", |
|
|
"fontsize": "13", |
|
@@ -72,10 +68,6 @@ class Diagram: |
|
|
"color": "#7B8894", |
|
|
"color": "#7B8894", |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
# fmt: on |
|
|
|
|
|
|
|
|
|
|
|
# TODO: Label position option |
|
|
|
|
|
# TODO: Save directory option (filename + directory?) |
|
|
|
|
|
def __init__( |
|
|
def __init__( |
|
|
self, |
|
|
self, |
|
|
name: str = "", |
|
|
name: str = "", |
|
@@ -89,6 +81,7 @@ class Diagram: |
|
|
graph_attr: Optional[dict] = None, |
|
|
graph_attr: Optional[dict] = None, |
|
|
node_attr: Optional[dict] = None, |
|
|
node_attr: Optional[dict] = None, |
|
|
edge_attr: Optional[dict] = None, |
|
|
edge_attr: Optional[dict] = None, |
|
|
|
|
|
output_buffer: Optional[BytesIO] = None # New parameter |
|
|
): |
|
|
): |
|
|
"""Diagram represents a global diagrams context. |
|
|
"""Diagram represents a global diagrams context. |
|
|
|
|
|
|
|
@@ -103,7 +96,7 @@ class Diagram: |
|
|
:param graph_attr: Provide graph_attr dot config attributes. |
|
|
:param graph_attr: Provide graph_attr dot config attributes. |
|
|
:param node_attr: Provide node_attr dot config attributes. |
|
|
:param node_attr: Provide node_attr dot config attributes. |
|
|
:param edge_attr: Provide edge_attr dot config attributes. |
|
|
:param edge_attr: Provide edge_attr dot config attributes. |
|
|
:param strict: Rendering should merge multi-edges. |
|
|
|
|
|
|
|
|
:param output_buffer: Optional in-memory buffer to write the output to. |
|
|
""" |
|
|
""" |
|
|
if graph_attr is None: |
|
|
if graph_attr is None: |
|
|
graph_attr = {} |
|
|
graph_attr = {} |
|
@@ -152,9 +145,7 @@ class Diagram: |
|
|
|
|
|
|
|
|
self.show = show |
|
|
self.show = show |
|
|
self.autolabel = autolabel |
|
|
self.autolabel = autolabel |
|
|
|
|
|
|
|
|
def __str__(self) -> str: |
|
|
|
|
|
return str(self.dot) |
|
|
|
|
|
|
|
|
self.output_buffer = output_buffer # Store the buffer |
|
|
|
|
|
|
|
|
def __enter__(self): |
|
|
def __enter__(self): |
|
|
setdiagram(self) |
|
|
setdiagram(self) |
|
@@ -162,8 +153,8 @@ class Diagram: |
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback): |
|
|
def __exit__(self, exc_type, exc_value, traceback): |
|
|
self.render() |
|
|
self.render() |
|
|
# Remove the graphviz file leaving only the image. |
|
|
|
|
|
os.remove(self.filename) |
|
|
|
|
|
|
|
|
if not self.output_buffer: |
|
|
|
|
|
os.remove(self.filename) |
|
|
setdiagram(None) |
|
|
setdiagram(None) |
|
|
|
|
|
|
|
|
def _repr_png_(self): |
|
|
def _repr_png_(self): |
|
@@ -193,9 +184,15 @@ class Diagram: |
|
|
def render(self) -> None: |
|
|
def render(self) -> None: |
|
|
if isinstance(self.outformat, list): |
|
|
if isinstance(self.outformat, list): |
|
|
for one_format in self.outformat: |
|
|
for one_format in self.outformat: |
|
|
self.dot.render(format=one_format, view=self.show, quiet=True) |
|
|
|
|
|
|
|
|
if self.output_buffer: |
|
|
|
|
|
self.output_buffer.write(self.dot.pipe(format=one_format)) |
|
|
|
|
|
else: |
|
|
|
|
|
self.dot.render(format=one_format, view=self.show, quiet=True) |
|
|
else: |
|
|
else: |
|
|
self.dot.render(format=self.outformat, view=self.show, quiet=True) |
|
|
|
|
|
|
|
|
if self.output_buffer: |
|
|
|
|
|
self.output_buffer.write(self.dot.pipe(format=self.outformat)) |
|
|
|
|
|
else: |
|
|
|
|
|
self.dot.render(format=self.outformat, view=self.show, quiet=True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Cluster: |
|
|
class Cluster: |
|
|