@dataclass(slots=True)
class Diagram:
"""A diagram representation of a hypergraph."""
openHyperGraph: OpenHypergraph
nodes: list[Node] = field(init=False)
hyperEdges: list[HyperEdge] = field(init=False)
graphRep: Digraph = field(init=False)
orientation: Orientation = field(default=Orientation.TOP_TO_BOTTOM)
highlighted_nodes: list[int] = field(default_factory=list)
highlighted_edges: list[int] = field(default_factory=list)
@staticmethod
def diagram_label(label: str, index: int, type: ElementType, hash: int = -1) -> str:
if type == ElementType.NODE:
joiner = ";"
elif type == ElementType.EDGE:
joiner = ","
else:
raise ValueError("Type must be ElementType.NODE or ElementType.EDGE.")
final = joiner.join([label, str(index)])
if type == ElementType.EDGE:
final = f"{final}\n\n{hash}"
return final
def drawArrows(self, hyperEdge: HyperEdge, edge_label: str, nodes: list[Node]):
# Draw arrows from nodes to edges
for s in hyperEdge.sources:
self.graphRep.edge(
self.diagram_label(self.nodes[s].label, s, ElementType.NODE),
edge_label,
)
# Draw arrows from edges to nodes
for t in hyperEdge.targets:
self.graphRep.edge(
edge_label,
self.diagram_label(self.nodes[t].label, t, ElementType.NODE),
)
def drawGraph(self):
for node in self.nodes:
node_label = self.diagram_label(
node.label, self.nodes.index(node), ElementType.NODE
)
if node.index in self.highlighted_nodes:
color = "red"
else:
color = "black"
self.graphRep.node(node_label, shape="circle", fontcolor=color)
for hyperEdge in self.hyperEdges:
edge_label = self.diagram_label(
hyperEdge.label,
self.hyperEdges.index(hyperEdge),
ElementType.EDGE,
hash=hyperEdge.signature,
)
if hyperEdge.index in self.highlighted_edges:
color = "red"
else:
color = "black"
self.graphRep.node(edge_label, shape="box", fontcolor=color)
self.drawArrows(hyperEdge, edge_label, self.nodes)
return self.graphRep
def __post_init__(self):
if not self.openHyperGraph.is_valid():
raise ValueError("The provided OpenHypergraph is not valid.")
self.nodes = self.openHyperGraph.nodes
self.hyperEdges = self.openHyperGraph.edges
self.graphRep = Digraph(format="png")
self.drawGraph()
def render(self, filename: str = "hypergraph_diagram") -> None:
"""Render the diagram to a file."""
self.graphRep.render(filename, view=False, cleanup=True)
def source(self) -> str:
"""Return the source of the diagram."""
return self.graphRep.source