Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to plot nodes of different shapes and facecolor of bbox in the same graph using networkx and matplotlib in Python?

I want to plot a simple graph using networkx. There are two nodes with labels Python and Programming in each. The code and graph are as follows:

import networkx as nx

G = nx.DiGraph()
G.add_node(0)
G.add_node(1)
G.add_edge(0,1)

nx.draw_networkx(G,
                pos = {0:(0, 0), 1: (1, 0)},
                node_size = [1000, 2000],
                node_color = ["red","green"],
                node_shape = "s",
                )

enter image description here

I am able to get two separate colors red and green in the two nodes respectively, as well as set different labels in each node. However, I also want to get two different shape of nodes. I want to have first node as it is and second node of diamond shape which can be obtained using d in node_shape. I tried passing list in the form of ["s","d"], as well as dictionary in the form of {0:"s", 1:"d"} in the node_shape.

However, both return an error. How can I get nodes of different shapes here? Is it possible using any other libraries like graphviz, how?

In the other step, I'd like to have the labels enclosed within the nodes. So I set the node_size to 0 and used bbox instead as follows.

nx.draw_networkx(G,
                pos = {0:(0, 0), 1: (1, 0)},
                node_size = 0,
                 arrows = True,
                 labels = {0:"Python",1:"Programming"},
                 bbox = dict(facecolor = "skyblue")
                )

enter image description here

I am able to enclose the labels inside bbox, however, the arrow is not hidden, and the facecolor of bbox in both nodes are same. How can I make the facecolors different in this case? What would be the alternative way to approach this?

like image 371
hbstha123 Avatar asked Sep 05 '25 03:09

hbstha123


2 Answers

Networkx does not support multiple node shapes in one function call, as the nodes are drawn with matplotlib's scatter, which does not support drawing different markers in the same function call. You can, however, make multiple calls to nx.draw_networkx_nodes, each with a different subset of nodes and shapes.

Enclosing labels in nodes is tricky business. Text box dimensions cannot be pre-calculated in matplotlib as it supports multiple rendereres. So you have to draw some dummy text objects, determine the dimensions of the text boxes, and then work backwards to the desired sizes. Such functionality is not included with networkx.

If you are open to using other libraries, then most of your desired features are fairly easily implemented in netgraph, which is a library I wrote and maintain. Drawing different node shapes in one function call is supported. Arrow heads should remain visible. Making changes to node label background colors is fairly easy.

Regarding font sizes, in netgraph, if the font size of the node labels is not explicitly set, it works out the maximum font size such that all text boxes fit inside the corresponding node artist. As you can see in the example below, there is a small problem with that calculation as the current implementation assumes a circular node shape such that the text box for the first node does not quite fit the square node. I will look into fixing that soon; in the meantime, you would have to use more circular node shapes, or adjust the font size in a separate call, as shown below.

enter image description here

#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx

from netgraph import Graph # pip install netgraph

graph_data = nx.DiGraph([(0, 1)]) # edge lists or igraph Graph objects are also supported

g = Graph(graph_data,
          node_layout = {0: (0, 0), 1: (1, 0)},
          node_size = {0 : 10, 1 : 20},
          node_color = {0 : "red", 1 : "green"},
          node_labels = {0:"Python", 1:"Programming"},
          node_label_fontdict = {'backgroundcolor' : 'lightgray'},
          node_shape = {0 : "s", 1 : "d"},
          arrows=True,
)

# Netgraph currently does not support multiple values for label backgroundcolors.
# However, all artists are exposed in simple to query dictionaries.
# As node label artists are matplotlib text objects,
# we can vary the node label background colors by using matplotlib.text.Text methods:
g.node_label_artists[0].set_backgroundcolor('salmon')
g.node_label_artists[1].set_backgroundcolor('lightgreen')

# Netgraph assumes circular node shapes when computing fontsizes.
# We hence have to manually adjust the node label fontsizes
# by the ratio of the diagonal to the width in a square.
for node, label in g.node_label_artists.items():
    fontsize = label.get_fontsize()
    label.set_fontsize(fontsize * 1./np.sqrt(2))

plt.show()
like image 163
Paul Brodersen Avatar answered Sep 07 '25 17:09

Paul Brodersen


I figured out a way to do it using the graphviz package. The limitation of the NetworkX package is that it does not allow the nodes to have different shapes, although the sizes or color could be set differently in the form of lists. Also, if the bounding box (bbox) is used, then its attributes are uniform across nodes.

However, graphviz offers much more flexibility in this context. The shape, size and color of each nodes can be adapted individually. I did it as follows:

import graphviz

G = graphviz.Digraph()

G.node("Python", style = 'filled', color = "red", shape = "box")
G.node("Programming", style = "filled", color = "green", shape = "ellipse")

G.edge("Python","Programming")

G.view()

As a result, I get the plot as shown: enter image description here It is also possible to customise the graph further, such as position of nodes, edge color, etc.

like image 25
hbstha123 Avatar answered Sep 07 '25 17:09

hbstha123