import os import uuid HEADER = """ graph { graph [splines=ortho, nodesep=1] node [color=white] edge [headport=n, tailport=s] compound=true """ FOOTER = """ } """ # TODO: # Add hidden nodes in each cluster above visible nodes connected by hidden edges and attach visible edges to them # - also see: https://stackoverflow.com/questions/53862417/how-to-set-head-and-tail-position-in-nodes-graphviz # Solve layering of clusters # - https://observablehq.com/@gordonsmith/church class Graph: def __init__(self): self.layers = {} self.blocks = {} self.nodes = [] self.links = {} self.dot_file = "" def __str__(self): return self.dot_file def get_id(self): return "id" + str(uuid.uuid4().hex) def process_blocks(self, data): blocks = data.get('blocks', []) for block in blocks: block_id = self.get_id() new_block = { "label": block.get("label", ""), #"layer": block.get("layer", 0), "texts": [] } #layer = block.get("layer", 0) #if layer not in self.layers: # self.layers[layer] = [] for text in block.get("texts", []): node_id = self.get_id() self.nodes.append({"id": node_id, "text": text.get("text", "")}) new_block["texts"].append(node_id) #self.layers[layer].append(node_id) links = text.get("links", {}) if links != {}: from_links = links.get("from", []) to_links = links.get("to", []) for from_link in from_links: self.links[from_link] = {"from": node_id, "to": "", "head": ""} for to_link in to_links: if to_link in self.links: self.links[to_link]["to"] = node_id self.links[to_link]["head"] = block_id self.blocks[block_id] = new_block def build_dot(self): self.dot_file = HEADER for node in self.nodes: self.dot_file += f"{node['id']} [label=\"{node['text']}\"]\n" self.dot_file += "\n" for block_id, block in self.blocks.items(): self.dot_file += f"subgraph cluster_{block_id} {{\n" self.dot_file += f"label=\"{block['label']}\"\n" self.dot_file += f"labeljust=l\n" for node in block["texts"]: self.dot_file += f"{node}\n" self.dot_file += "}\n\n" #for layer in self.layers.keys(): # self.dot_file += f"layer{layer} [style=invis]\n" #ranking = " -- ".join([f"layer{layer}" for layer in self.layers.keys()]) #self.dot_file += f"{ranking} [style=invis]\n" for link in self.links: if self.links[link]["to"] != "": self.dot_file += f"{self.links[link]['from']} -- {self.links[link]['to']}" if self.links[link]["head"] != "": self.dot_file += f" [lhead=cluster_{self.links[link]['head']}]" self.dot_file += "\n" #for layer, nodes in self.layers.items(): # self.dot_file += "{rank=same; " # self.dot_file += "layer{layer}; " # for node in nodes: # self.dot_file += f"{node}; " # self.dot_file += "}\n" self.dot_file += FOOTER def make_dot(self, format="svg", dot_file="dot.gv", svg_file="graph.svg"): if self.dot_file != "": with open(dot_file, "w") as f: f.write(self.dot_file) if format == "svg": os.system(f"dot -T{format} {dot_file} -o {svg_file}")