lars111
lars111

Reputation: 1313

Python Networkx graphviz: Plot right position of nodes

I want to plot the following graph using Graphvize:

xx = nx.DiGraph()

xx.add_node("P")
xx.add_node("C0")
xx.add_node("C1")
xx.add_node("I2")
xx.add_node("C3")
xx.add_node("C4")
xx.add_node("I5")
xx.add_node("C6")
xx.add_node("C7")

xx.node["C1"]['pos'] = (2,3)
xx.node["I2"]['pos'] = (4,5)
xx.node["C3"]['pos'] = (6,7)
xx.node["C4"]['pos'] = (6,5)
xx.node["I5"]['pos'] = (4,1)
xx.node["C6"]['pos'] = (6,2)
xx.node["C7"]['pos'] = (6,0)
xx.node["P"]['pos'] = (-2,3)
xx.node["C0"]['pos'] = (0,3)

xx.add_edge("P", "C0")
xx.add_edge("C0", "C1")
xx.add_edge("C1", "I2")
xx.add_edge("I2", "C3")
xx.add_edge("I2", "C4")
xx.add_edge("C1", "I5")
xx.add_edge("I5", "C6")
xx.add_edge("I5", "C7")

layout = dict((n, xx.node[n]["pos"]) for n in xx.nodes_iter())
nx.draw(xx,pos=layout,node_color='white')

nx.write_dot(xx,'66666.dot')

With matplotlitb i geht the right position of all nodes:

enter image description here

With Graphviz a graph without postion.

enter image description here

My question: Is there a possibility to add the correct positions in Graphviz? And is it possible to open the file "66666.dot" directly in python?

Thank you very much for your help!

Upvotes: 3

Views: 1191

Answers (1)

kirogasa
kirogasa

Reputation: 949

Since the NetworkX library has had time to update and break backward compatibility and the problem described in the question is still in the new version of the library, I will try to update the example code for NetworkX 2.8.7 variant and provide a solution.

Update the example code for NetworkX 2.8.7

  • Error: NameError: name 'nx' is not defined
    How to fix: it's not a version problem, it's an Minimal, Reproducible Example problem, so just install NetworkX library (pip install networkx) and include the library at the beginning of the code:
    import networkx as nx
    
  • Error: AttributeError: 'DiGraph' object has no attribute 'node'
    How to fix: Use xx.nodes["C1"]['pos'] instead of xx.node["C1"]['pos']. Reference: NetworkX Migration guide from 1.X to 2.0
  • Error: AttributeError: 'DiGraph' object has no attribute 'nodes_iter'
    How to fix: Use xx.nodes() instead of xx.nodes_iter(). Reference.
  • Error: AttributeError: module networkx has no attribute write_dot
    How to fix: Use nx.drawing.nx_agraph.write_dot(xx,'66666.dot') instead of nx.write_dot(xx,'66666.dot'). Reference: AttributeError: 'module' object has no attribute 'write_dot' for networkx library

Note: If you do not already have graphviz and pygraphviz installed, you may need to install it. pygraphviz library for Windows can be installed as a precompiled binary downloaded from Unofficial Windows Binaries for Python Extension Packages with command pip install nameOfDownloadedPackage.whl.

The code of the example now looks like this:

# Listing 1
import networkx as nx

xx = nx.DiGraph()

xx.add_node("P")
xx.add_node("C0")
xx.add_node("C1")
xx.add_node("I2")
xx.add_node("C3")
xx.add_node("C4")
xx.add_node("I5")
xx.add_node("C6")
xx.add_node("C7")

xx.nodes["C1"]['pos'] = (2,3)
xx.nodes["I2"]['pos'] = (4,5)
xx.nodes["C3"]['pos'] = (6,7)
xx.nodes["C4"]['pos'] = (6,5)
xx.nodes["I5"]['pos'] = (4,1)
xx.nodes["C6"]['pos'] = (6,2)
xx.nodes["C7"]['pos'] = (6,0)
xx.nodes["P"]['pos'] = (-2,3)
xx.nodes["C0"]['pos'] = (0,3)

xx.add_edge("P", "C0")
xx.add_edge("C0", "C1")
xx.add_edge("C1", "I2")
xx.add_edge("I2", "C3")
xx.add_edge("I2", "C4")
xx.add_edge("C1", "I5")
xx.add_edge("I5", "C6")
xx.add_edge("I5", "C7")

layout = dict((n, xx.nodes[n]["pos"]) for n in xx.nodes())
nx.draw(xx,pos=layout,node_color='white')

nx.drawing.nx_agraph.write_dot(xx,'66666.dot')

Solution to correct output image

TLDR: Use the format pos="x,y!" to record the positions of the nodes in the file instead of pos=("x,y").
Now that we have a workable example, we can move on to how to plot right position of nodes.
The created file 66666.dot contains text in dot language:

strict digraph "" {
    P   [pos="(-2, 3)"];
    C0  [pos="(0, 3)"];
    P -> C0;
    C1  [pos="(2, 3)"];
    C0 -> C1;
    I2  [pos="(4, 5)"];
    C1 -> I2;
    I5  [pos="(4, 1)"];
    C1 -> I5;
    C3  [pos="(6, 7)"];
    I2 -> C3;
    C4  [pos="(6, 5)"];
    I2 -> C4;
    C6  [pos="(6, 2)"];
    I5 -> C6;
    C7  [pos="(6, 0)"];
    I5 -> C7;
}

If you have graphviz installed, you can run the command in terminal:

dot -Tpng 66666.dot -o output.png

and check what will be drawn in the file output.png:
graph was drawn as PNG image using graphviz

The thing is that in graphviz library there are different layout engines, which are responsible for what positions will be drawn nodes and dot layout engine does not understand the attribute pos="(4, 1)", so we will use the neato layout engine that understands this attribute (as mentioned in its documentation). And we get the output in the terminal:

Error: node P, position (-2, 3), expected two doubles
Error: node C0, position (0, 3), expected two doubles
Error: node C1, position (2, 3), expected two doubles
Error: node I2, position (4, 5), expected two doubles
Error: node I5, position (4, 1), expected two doubles
Error: node C3, position (6, 7), expected two doubles
Error: node C4, position (6, 5), expected two doubles
Error: node C6, position (6, 2), expected two doubles
Error: node C7, position (6, 0), expected two doubles

and file output.png:
graph was drawn as PNG image using graphviz neato layout engine

We see that the coordinates do not affect the drawing positions and an error is written about the incorrect format of the input data. As mentioned there (converting network graph to graphviz), this error is due to an incorrect entry of the coordinate value. The format that graphviz understands is pos="x,y", not pos="(x,y)".

Note: Full value format looks like this %f,%f('!')? (e.g. pos="42,24!"). The optional ! indicates the node position should not change.

Let's change the format of positions in the graph object before writing it to the file:

# Listing 2
# See the previous part of the code in Listing 1

for node in xx:
    xx.nodes[node]['pos'] = "{},{}!".format(
        xx.nodes[node]['pos'][0], xx.nodes[node]['pos'][1])

nx.drawing.nx_agraph.write_dot(xx,'66666.dot')

Result in the file 66666.dot now:

strict digraph "" {
    P   [pos="-2,3!"];
    C0  [pos="0,3!"];
    P -> C0;
    C1  [pos="2,3!"];
    C0 -> C1;
    I2  [pos="4,5!"];
    C1 -> I2;
    I5  [pos="4,1!"];
    C1 -> I5;
    C3  [pos="6,7!"];
    I2 -> C3;
    C4  [pos="6,5!"];
    I2 -> C4;
    C6  [pos="6,2!"];
    I5 -> C6;
    C7  [pos="6,0!"];
    I5 -> C7;
}

Result in output.png:
graph was drawn as PNG image using graphviz neato layout engine
Looks good. To make it look like the result from matplotlib, you need to add more attributes label, shape to nodes and arrowhead to edges:

# Listing 3
# See the previous part of the code in Listing 1

for node in xx:
    xx.nodes[node]['pos'] = "{},{}!".format(
        xx.nodes[node]['pos'][0], xx.nodes[node]['pos'][1])
    xx.nodes[node]['label'] = ""
    xx.nodes[node]['shape'] = "circle"
for edge in xx.edges:
    xx.edges[edge]['arrowhead'] = 'box'

nx.drawing.nx_agraph.write_dot(xx,'66666.dot')

Result in output.png:
graph was drawn as PNG image using graphviz neato layout engine

Note (optional): To show the result of the matplotlib, add at the beginning of the file import matplotlib.pyplot as plt and at the end plt.show().

Opening the .dot file directly in Python

  • You can read the file using the functions of the standard Python library.
  • For opening the file filename.dot directly in Python and get a graph object, you can use function networkx.drawing.nx_agraph.read_dot.
  • If you need to embed the graph in GUI, it depends on the framework, there are many answers on SO and internet about how to embed graph using Matplotlib in wxPython, PyQt5, Tkinter.

Upvotes: 1

Related Questions