|
|
@ -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 = {}
|
|
|
@ -153,9 +146,7 @@ class Diagram:
|
|
|
|
|
|
|
|
|
|
|
|
self.show = show
|
|
|
|
self.show = show
|
|
|
|
self.autolabel = autolabel
|
|
|
|
self.autolabel = autolabel
|
|
|
|
|
|
|
|
self.output_buffer = output_buffer # Store the buffer
|
|
|
|
def __str__(self) -> str:
|
|
|
|
|
|
|
|
return str(self.dot)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
def __enter__(self):
|
|
|
|
setdiagram(self)
|
|
|
|
setdiagram(self)
|
|
|
@ -163,8 +154,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.
|
|
|
|
if not self.output_buffer:
|
|
|
|
os.remove(self.filename)
|
|
|
|
os.remove(self.filename)
|
|
|
|
setdiagram(None)
|
|
|
|
setdiagram(None)
|
|
|
|
|
|
|
|
|
|
|
|
def _repr_png_(self):
|
|
|
|
def _repr_png_(self):
|
|
|
@ -194,9 +185,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:
|
|
|
|