Search code examples
python-3.xgraphvizanytree

Rendering a tree in python using anytree and graphviz, without merging common nodes


I am creating a tree from a list ["abc", "abd", "aec", "add", "adcf"] using anytree package of python3. In this tree first character of each list element - a is a root, and subsequently, other characters are added as their children. When I render tree it looks like:

a
├── b
│   ├── c
│   └── d
├── e
│   └── c
└── d
    ├── d
    └── c
        └── f

But when I render the tree to picture using to_picture method, the image is -

enter image description here

I don't want the common nodes to be merged, as it is adding unwanted paths to my tree.


Solution

  • The node are arranged in graphviz using their id. In your case the graph is generated with just node names and then graphviz created the loop edge as you get it.

    What you really want is different id for each node and a label associated with the same. The DotExporter class has a attribute named nodeattrfunc to which we can pass a function or a lambda and generate attributes for the nodes

    Below is how you would do it based on your array

    import anytree
    from anytree import Node, RenderTree
    
    data = ["abc", "abd", "aec", "add", "adcf"]
    from anytree.exporter import DotExporter
    
    nodes = {}
    first_node = None
    
    for elem in data:
        parent_node = None
        parent_node_name = ""
        for i, val in enumerate(elem):
            if i not in nodes:
                nodes[i] = {}
            key = parent_node_name + val
            if key not in nodes[i]:
                print("Creating new node for ", key)
                nodes[i][key] = Node(key, parent=parent_node, display_name=val)
    
            if first_node is None:
                first_node = nodes[i][key]
            parent_node = nodes[i][key]
            parent_node_name = val
    
    print(nodes)
    DotExporter(nodes[0]["a"],
                nodeattrfunc=lambda node: 'label="{}"'.format(node.display_name)).to_dotfile("graph.txt")
    DotExporter(nodes[0]["a"],
                nodeattrfunc=lambda node: 'label="{}"'.format(node.display_name)).to_picture("graph.png")
    

    This will generate the below dot file

    digraph tree {
        "a" [label="a"];
        "ab" [label="b"];
        "bc" [label="c"];
        "bd" [label="d"];
        "ae" [label="e"];
        "ec" [label="c"];
        "ad" [label="d"];
        "dd" [label="d"];
        "dc" [label="c"];
        "cf" [label="f"];
        "a" -> "ab";
        "a" -> "ae";
        "a" -> "ad";
        "ab" -> "bc";
        "ab" -> "bd";
        "ae" -> "ec";
        "ad" -> "dd";
        "ad" -> "dc";
        "dc" -> "cf";
    }
    

    And the below graph

    Final graph