Source code for multilevel_py.viz

from pathlib import Path
from math import floor

from graphviz import Digraph
from multilevel_py.core import is_clabject
from multilevel_py.exceptions import NotAClabjectException


def _get_template_dir_path():
    return Path(__file__).parent.joinpath("viz_templates")


def _check_clabject(clabject):
    if not is_clabject(clabject):
        raise NotAClabjectException(obj=clabject)


[docs]def determine_level_recursive(clabject) -> int: """ Args: clabject: a clabject obj., i.e. a class of the :class:`core.MetaClabject` python metaclass Returns: The level of the clabject, i.e. the maximum number of instantiation steps of al instantiation chains that originate from the given clabject """ max = 0 if clabject.instances is None or len(clabject.instances) == 0: return max else: for instance in clabject.instances: inst_level = determine_level_recursive(instance) + 1 if inst_level > max: max = inst_level return max
def _create_node(dot: Digraph, clabject, font, fontsize): from jinja2 import Template template_dir_path = _get_template_dir_path() with template_dir_path.joinpath("clabject.jinja2.html").open() as file_: template = Template(file_.read()) nodelabel = "<" + template.render(clabject=clabject) + ">" dot.node(clabject.__name__, label=nodelabel, font=font, fontsize=fontsize) def _create_instantiate_relation(dot: Digraph, parent_clabject, instance_clabject, fontsize): inst_name = instance_clabject.__name__ label = "" if inst_name in parent_clabject.speed_adjustments: res = {} for speed_adj_key, speed_adj_value in parent_clabject.speed_adjustments[inst_name].items(): if speed_adj_value > 0: res[speed_adj_key] = "+" + str(speed_adj_value) else: res[speed_adj_key] = str(speed_adj_value) # parent_clabject.speed_adjustments[inst_name] = res label = str(res) # print("The inst. lable font size is " + str(floor(int(fontsize) * 1.3))) dot.edge(parent_clabject.__name__, inst_name, label=label, style="dashed", fontsize=str(floor(int(fontsize) * 1.2))) def _create_node_recursive(dot: Digraph, clabject, font, fontsize, hidden_root=False): """ Depth First Recursion along the instantiation into relations """ if not hidden_root: _create_node(dot, clabject, font, fontsize) if clabject.instances is not None: for instance in clabject.instances: if not hidden_root: _create_instantiate_relation(dot, clabject, instance, fontsize) _create_node_recursive(dot, instance, font, fontsize) def _create_level_recursive(dot: Digraph, prev_queue: list, current_queue: list, hierarchy_name: str, current_level: int, font:str, fontsize: str): """ Breath First Recursion, level by level """ next_queue = [] next_level = current_level - 1 level_label = hierarchy_name + "_" + str(current_level) level_name = "cluster_" + level_label with dot.subgraph(name=level_name, graph_attr={'label': level_label}) as lev: for clabject in current_queue: _create_node(lev, clabject, font, fontsize) if clabject.instances is not None and len(clabject.instances) > 0: next_queue = next_queue + clabject.instances for clabject in prev_queue: if clabject.instances is not None: for instance in clabject.instances: _create_instantiate_relation(dot, clabject, instance, fontsize) if len(next_queue) > 0 and next_level >= 0: _create_level_recursive(dot, prev_queue=current_queue, current_queue=next_queue, hierarchy_name=hierarchy_name, current_level=next_level, font=font, fontsize=fontsize)
[docs]def viz_classification_hierarchy(start_clabject, output_dir: Path = None, output_name: str = None, render=True, by_level=False, hidden_root=False, show_hierarchy_name=False, format="png", font="arial", fontsize="12", ): """ Args: start_clabject: a clabject obj., i.e. a class of the :class:`core.MetaClabject` python metaclass output_dir: a Path obj. that represents the desired output viz directory output_name: the name of the generated plot render: boolean value indicating whether the final dot object should be rendered into output dir by_level: use an breadth-first recursion, i.e. level by level through the given classification hierarchy hidden_root: boolean value indicating whether the start_clabject should be rendered show_hierarchy_name: boolean value indicating whether the plot should include the hierarchy name (name of start_clabject) format: a str specifying the given desired output format ("pdf", "png", "svg", "jpg") font: a str specifying the desired font-family fontsize: a str specifying the desired font-size (as integer value) Returns: a dot object constructed by traversing the classification hierarchy """ _check_clabject(start_clabject) digraph_name = "cluster_" + start_clabject.__name__ hierarchy_name = str(start_clabject.__name__) + "_Hierarchy" label_name = hierarchy_name if show_hierarchy_name else "" dot = Digraph(name=digraph_name, comment="Visualization of Instantiation Hierarchy", graph_attr={'splines': 'polyline', 'rankdir': 'LR', 'labelloc': 't', 'fontname': 'arial', 'label': label_name, 'fontsize': '10'}, node_attr={'shape': 'plaintext'}, engine="dot") if by_level: start_level = determine_level_recursive(start_clabject) _create_level_recursive(dot, prev_queue=[], current_queue=[start_clabject], hierarchy_name=hierarchy_name, current_level=start_level, font=font, fontsize=fontsize) else: _create_node_recursive(dot, start_clabject, font, fontsize, hidden_root=hidden_root) if render: assert output_dir is not None assert output_name is not None output_file = output_dir.joinpath(output_name) dot.render(output_file, format=format, view=True, cleanup=True) return dot