|
|
@ -17,7 +17,10 @@ __cluster = contextvars.ContextVar("cluster")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def getdiagram():
|
|
|
|
def getdiagram():
|
|
|
|
return __diagram.get()
|
|
|
|
try:
|
|
|
|
|
|
|
|
return __diagram.get()
|
|
|
|
|
|
|
|
except LookupError:
|
|
|
|
|
|
|
|
raise EnvironmentError("Global diagrams context not set up")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def setdiagram(diagram):
|
|
|
|
def setdiagram(diagram):
|
|
|
@ -50,7 +53,7 @@ class _Cluster:
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
self._parent = getcluster() or getdiagram()
|
|
|
|
self._parent = getcluster() or getdiagram()
|
|
|
|
except LookupError:
|
|
|
|
except EnvironmentError:
|
|
|
|
self._parent = None
|
|
|
|
self._parent = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -58,40 +61,28 @@ class _Cluster:
|
|
|
|
setcluster(self)
|
|
|
|
setcluster(self)
|
|
|
|
return self
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def __exit__(self, *args):
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
|
|
setcluster(self._parent)
|
|
|
|
setcluster(self._parent)
|
|
|
|
|
|
|
|
|
|
|
|
if not (self.nodes or self.subgraphs):
|
|
|
|
for nodeid, node in self.nodes.items():
|
|
|
|
return
|
|
|
|
self.dot.node(nodeid, label=node['label'], **node['attrs'])
|
|
|
|
|
|
|
|
|
|
|
|
for node in self.nodes.values():
|
|
|
|
for dot in self.subgraphs:
|
|
|
|
self.dot.node(node.nodeid, label=node.label, **node._attrs)
|
|
|
|
self.dot.subgraph(dot)
|
|
|
|
|
|
|
|
|
|
|
|
for subgraph in self.subgraphs:
|
|
|
|
|
|
|
|
self.dot.subgraph(subgraph.dot)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self._parent:
|
|
|
|
if self._parent:
|
|
|
|
self._parent.remove_node(self.nodeid)
|
|
|
|
self._parent.subgraph(self.dot)
|
|
|
|
self._parent.subgraph(self)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def node(self, node: "Node") -> None:
|
|
|
|
def node(self, nodeid: str, label: str, **attrs) -> None:
|
|
|
|
"""Create a new node."""
|
|
|
|
"""Create a new node."""
|
|
|
|
self.nodes[node.nodeid] = node
|
|
|
|
self.nodes[nodeid] = {'label': label, 'attrs': attrs}
|
|
|
|
|
|
|
|
|
|
|
|
def remove_node(self, nodeid: str) -> None:
|
|
|
|
def remove_node(self, nodeid: str) -> None:
|
|
|
|
del self.nodes[nodeid]
|
|
|
|
del self.nodes[nodeid]
|
|
|
|
|
|
|
|
|
|
|
|
def subgraph(self, subgraph: "_Cluster") -> None:
|
|
|
|
def subgraph(self, dot: Digraph) -> None:
|
|
|
|
"""Create a subgraph for clustering"""
|
|
|
|
"""Create a subgraph for clustering"""
|
|
|
|
self.subgraphs.append(subgraph)
|
|
|
|
self.subgraphs.append(dot)
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def nodes_iter(self):
|
|
|
|
|
|
|
|
if self.nodes:
|
|
|
|
|
|
|
|
yield from self.nodes.values()
|
|
|
|
|
|
|
|
if self.subgraphs:
|
|
|
|
|
|
|
|
for subgraph in self.subgraphs:
|
|
|
|
|
|
|
|
yield from subgraph.nodes_iter
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _validate_direction(self, direction: str):
|
|
|
|
def _validate_direction(self, direction: str):
|
|
|
|
direction = direction.upper()
|
|
|
|
direction = direction.upper()
|
|
|
@ -211,22 +202,23 @@ class Diagram(_Cluster):
|
|
|
|
def __enter__(self):
|
|
|
|
def __enter__(self):
|
|
|
|
setdiagram(self)
|
|
|
|
setdiagram(self)
|
|
|
|
super().__enter__()
|
|
|
|
super().__enter__()
|
|
|
|
|
|
|
|
super().__enter__()
|
|
|
|
return self
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def __exit__(self, *args):
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
|
|
super().__exit__(*args)
|
|
|
|
super().__exit__(exc_type, exc_value, traceback)
|
|
|
|
setdiagram(None)
|
|
|
|
setdiagram(None)
|
|
|
|
|
|
|
|
|
|
|
|
for (node1, node2), edge in self.edges.items():
|
|
|
|
for nodes, edge in self.edges.items():
|
|
|
|
cluster_node1 = next(node1.nodes_iter, None)
|
|
|
|
node1, node2 = nodes
|
|
|
|
if cluster_node1:
|
|
|
|
nodeid1, nodeid2 = node1.nodeid, node2.nodeid
|
|
|
|
edge._attrs['ltail'] = node1.nodeid
|
|
|
|
if node1.nodes:
|
|
|
|
node1 = cluster_node1
|
|
|
|
edge._attrs['ltail'] = nodeid1
|
|
|
|
cluster_node2 = next(node2.nodes_iter, None)
|
|
|
|
nodeid1 = next(iter(node1.nodes.keys()))
|
|
|
|
if cluster_node2:
|
|
|
|
if node2.nodes:
|
|
|
|
edge._attrs['lhead'] = node2.nodeid
|
|
|
|
edge._attrs['lhead'] = nodeid2
|
|
|
|
node2 = cluster_node2
|
|
|
|
nodeid2 = next(iter(node2.nodes.keys()))
|
|
|
|
self.dot.edge(node1.nodeid, node2.nodeid, **edge.attrs)
|
|
|
|
self.dot.edge(nodeid1, nodeid2, **edge.attrs)
|
|
|
|
|
|
|
|
|
|
|
|
self.render()
|
|
|
|
self.render()
|
|
|
|
# Remove the graphviz file leaving only the image.
|
|
|
|
# Remove the graphviz file leaving only the image.
|
|
|
@ -378,10 +370,17 @@ class Node(_Cluster):
|
|
|
|
|
|
|
|
|
|
|
|
return self
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def __exit__(self, *args):
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
|
|
super().__exit__(*args)
|
|
|
|
if not (self.nodes or self.subgraphs):
|
|
|
|
self._id = "cluster_" + self.nodeid
|
|
|
|
return
|
|
|
|
self.dot.name = self.nodeid
|
|
|
|
|
|
|
|
|
|
|
|
self._parent.remove_node(self._id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self._id = "cluster_" + self._id
|
|
|
|
|
|
|
|
self.dot.name = self._id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
super().__exit__(exc_type, exc_value, traceback)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
def __repr__(self):
|
|
|
|
_name = self.__class__.__name__
|
|
|
|
_name = self.__class__.__name__
|
|
|
|