how to draw multigraph in networkx using matplotlib or graphviz
Asked Answered
W

5

29

when I pass multigraph numpy adjacency matrix to networkx (using from_numpy_matrix function) and then try to draw the graph using matplotlib, it ignores the multiple edges.

how can I make it draw multiple edges as well ?

Withdraw answered 18/2, 2013 at 18:58 Comment(2)
Related #10379948 and #15054186Dark
Too bad it is not implemented in networkx!Cluny
R
24

Graphviz does a good job drawing parallel edges. You can use that with NetworkX by writing a dot file and then processing with Graphviz (e.g. neato layout below). You'll need pydot or pygraphviz in addition to NetworkX

In [1]: import networkx as nx

In [2]: G=nx.MultiGraph()

In [3]: G.add_edge(1,2)

In [4]: G.add_edge(1,2)

In [5]: nx.write_dot(G,'multi.dot')

In [6]: !neato -T png multi.dot > multi.png

enter image description here

On NetworkX 1.11 and newer, nx.write_dot doesn't work as per issue on networkx github. The workaround is to call write_dot using

from networkx.drawing.nx_pydot import write_dot

or

from networkx.drawing.nx_agraph import write_dot

Recourse answered 18/2, 2013 at 21:26 Comment(6)
which versions of networkx, pygraphviz and graphviz are you using?Marcus
Here is what I have. But recent verions should give the same result. $ python -c "import pygraphviz; print pygraphviz.__version__" 1.2.dev1990 $ dot -V dot - graphviz version 2.29.20120625.0446 (20120625.0446) $ python -c "import networkx; print networkx.__version__" 1.8.dev_20130108070258Recourse
I wrote the same code, used neato to generate the picture of graph, but it is a directed graph (and not a undirected) and show only a edge (1,2) but not the edge (2,1). Why is not undirected?????And why insn't there the other edge??Please help!Bakker
@Recourse do you know if it's possible to add edge labels and node labels to the dot graph? In my case I'd like to have a different label for each directed edge.Budweis
As of 2018, is this still the best way? (I am only interested in small graphs with at most tens of nodes.)Thong
I could not understand this line !neato -T png multi.dot > multi.png . Can somebody explain me?Salutary
K
18

You can use matplotlib directly using the node positions you calculate.

G=nx.MultiGraph ([(1,2),(1,2),(1,2),(3,1),(3,2)])
pos = nx.random_layout(G)
nx.draw_networkx_nodes(G, pos, node_color = 'r', node_size = 100, alpha = 1)
ax = plt.gca()
for e in G.edges:
    ax.annotate("",
                xy=pos[e[0]], xycoords='data',
                xytext=pos[e[1]], textcoords='data',
                arrowprops=dict(arrowstyle="->", color="0.5",
                                shrinkA=5, shrinkB=5,
                                patchA=None, patchB=None,
                                connectionstyle="arc3,rad=rrr".replace('rrr',str(0.3*e[2])
                                ),
                                ),
                )
plt.axis('off')
plt.show()

enter image description here

Keynesianism answered 11/3, 2020 at 14:31 Comment(3)
nice answer!, but how I can add labels to the edges and to the nodes ? for example I want to put different weight to every edge .Industry
If this would be a directed graph xy should be pos[e[1]] and xytext should be [pos[e[0]] to have the arrow pointing in the right direction. Or you could reverse the arrowstyle to "<-"Natika
@Industry You could do this: when querying, use G.edges(keys=True) (indexes the multiple edges between two nodes), define a text position, e.g. a simple one would be text_pos = ((pos[e[0]][0]+pos[e[1]][0])/2+0.1*pos[e[2]], (pos[e[0]][1]+pos[e[1]][1])/2+0.1*pos[e[2]] ), then after the arrow annotation part, put ax.text(text_pos[0], text_pos[1], text, ha='center')Kennykeno
W
3

You can use pyvis package.
I just copy-paste this code from my actual project in Jupyter notebook.

from pyvis import network as pvnet

def plot_g_pyviz(G, name='out.html', height='300px', width='500px'):
    g = G.copy() # some attributes added to nodes
    net = pvnet.Network(notebook=True, directed=True, height=height, width=width)
    opts = '''
        var options = {
          "physics": {
            "forceAtlas2Based": {
              "gravitationalConstant": -100,
              "centralGravity": 0.11,
              "springLength": 100,
              "springConstant": 0.09,
              "avoidOverlap": 1
            },
            "minVelocity": 0.75,
            "solver": "forceAtlas2Based",
            "timestep": 0.22
          }
        }
    '''

    net.set_options(opts)
    # uncomment this to play with layout
    # net.show_buttons(filter_=['physics'])
    net.from_nx(g)
    return net.show(name)

G = nx.MultiDiGraph()
[G.add_node(n) for n in range(5)]
G.add_edge(0, 1, label=1)
G.add_edge(0, 1, label=11)
G.add_edge(0, 2, label=2)
G.add_edge(0, 3, label=3)
G.add_edge(3, 4, label=34)

plot_g_pyviz(G)

result

Wedekind answered 8/7, 2020 at 13:39 Comment(4)
Welcome to StackOverflow! Please read the stackoverflow answering guideline stackoverflow.com/help/how-to-answer It would be nice to have some sort of explanation on answers.Malliemallin
Great answer! Fixed position of nodes is obtained by commenting out the net.setoptions(opts). How to bend edges without gravity enabled?Deformity
@ged , You can play with JS in opts variable. Just uncomment string net.show_buttons(filter_=['physics']) and copy paste JS code. using-the-configuration-ui-to-dynamically-tweak-network-settingsWedekind
Did not work for me inside VSCode. I got the warning (and the display didn't really appear): Warning: When cdn_resources is 'local' jupyter notebook has issues displaying graphics on chrome/safari. Use cdn_resources='in_line' or cdn_resources='remote' if you have issues viewing graphics in a notebook. I tried adding pvnet.Network.cdn_resources = 'in_line' /remote as the first line of the function, but I got the same error.Kennykeno
F
1

Refer to atomh33ls's answer

import numpy as np
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
import random as rd

column_from = 'from_here'
column_to = 'to_there'
column_attribute = 'edges_count'

# test data
pdf = pd.DataFrame([
                   ['a', 'b', 3], 
                   ['b', 'a', 1], 
                   ['a', 'c', 1], 
                   ['b', 'c', 1],
                   ['a', 'd', 1],
                   ['e', 'b', 2],
                   ['c', 'f', 1],
                   ['f', 'g', 1]],
                  columns=[column_from, column_to, column_attribute])
with pd.option_context('display.max_rows', None, 'display.max_columns', None):  # more options can be specified also
    print(pdf)

def get_adjacency_matrix(pdf):
    id_set = set(pdf[column_from].drop_duplicates().values.tolist() + 
                 pdf[column_to].drop_duplicates().values.tolist())
    id_dict_kv = {k : v for k, v in enumerate(id_set)}
    id_dict_vk = {v : k for k, v in id_dict_kv.items()}
    count = len(id_set)

    adjacency_matrix = np.zeros([count, count], dtype='int32')

    for row in pdf.itertuples():
        index_from = id_dict_vk[getattr(row, column_from)]
        index_to = id_dict_vk[getattr(row, column_to)]
        adjacency_matrix[index_from, index_to] += getattr(row, column_attribute)
    label_mapping = id_dict_kv
    return adjacency_matrix, label_mapping


def pdf_to_MDG(pdf):
    adjacency_matrix, label_mapping = get_adjacency_matrix(pdf)
    G = nx.from_numpy_matrix(adjacency_matrix, parallel_edges=True, create_using=nx.MultiDiGraph())
    G = nx.relabel_nodes(G, label_mapping)
    return G

MDG = pdf_to_MDG(pdf)

edges_data = MDG.edges.data(column_weight)
print(edges_data)

#—————————————————————————————just see the below: draw MultiDiGraph—————————————————————————————————

pos = nx.spring_layout(MDG, seed = 1)
nx.draw(MDG, pos, with_labels=True, edge_color = (1,1,1))
for e in MDG.edges:
    plt.gca().annotate("",
                       xy=pos[e[1]], 
                       xycoords='data',
                       xytext=pos[e[0]], 
                       textcoords='data',
                       arrowprops=dict(arrowstyle="->", color="0",
                                       shrinkA=15, shrinkB=15,
                                       patchA=None, patchB=None,
                                       connectionstyle="arc3,rad=rrr".replace('rrr',str(rd.random()*0.5+0.1)))
                      )
plt.axis('off')
plt.show()

output:

  from_here to_there  edges_count
0         a        b            3
1         b        a            1
2         a        c            1
3         b        c            1
4         a        d            1
5         e        b            2
6         c        f            1
7         f        g            1
[('c', 'f', 1), ('e', 'b', 1), ('e', 'b', 1), ('b', 'c', 1), ('b', 'a', 1), ('f', 'g', 1), ('a', 'c', 1), ('a', 'd', 1), ('a', 'b', 1), ('a', 'b', 1), ('a', 'b', 1)]

the output img

Floe answered 7/6, 2022 at 6:31 Comment(3)
If you remove all the (irrelevant) test data generation, how is this different from the existing answer that you reference?Banns
@Banns you can compare the graph below, with two main differences : 1, add the label;2, random edgesFloe
@Banns the third difference: the arrow directionFloe
C
1

Very much building on the answer of @Lee, but wanting to get labels on the nodes and wanting the arrows to point in the correct direction, I arrived at the following:

from matplotlib import pyplot as plt

G = <YOUR MULTIGRAPH HERE>
pos = nx.random_layout(G)
names = {name: name for name in G.nodes}
nx.draw_networkx_nodes(G, pos, node_color = 'b', node_size = 250, alpha = 1)
nx.draw_networkx_labels(G,pos,names,font_size=12,font_color='w')
ax = plt.gca()
for e in G.edges:
    ax.annotate("",
                xy=pos[e[1]], xycoords='data',
                xytext=pos[e[0]], textcoords='data',
                arrowprops=dict(arrowstyle="->", color="0",
                                shrinkA=10, shrinkB=10,
                                patchA=None, patchB=None,
                                connectionstyle="arc3,rad=rrr".replace('rrr',str(0.3*e[2])
                                ),
                                ),
                )
plt.axis('off')
plt.show()

Example output: three-node directed multigraph

Conjuncture answered 27/6, 2023 at 9:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.