28

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 ?

Lee
  • 29,398
  • 28
  • 117
  • 170
Binary Baba
  • 1,953
  • 2
  • 16
  • 23
  • 1
    Related http://stackoverflow.com/questions/10379448/plotting-directed-graphs-in-python-in-a-way-that-show-all-edges-separately and http://stackoverflow.com/questions/15053686/networkx-overlapping-edges-when-visualizing-multigraph – 0 _ Jul 23 '13 at 22:42
  • Too bad it is not implemented in networkx! – famargar Jan 18 '18 at 16:42

5 Answers5

23

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

EntilZha
  • 1,572
  • 3
  • 15
  • 16
Aric
  • 24,511
  • 5
  • 78
  • 77
  • which versions of networkx, pygraphviz and graphviz are you using? – o17t H1H' S'k Jun 11 '13 at 16:05
  • 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_20130108070258 – Aric Jun 12 '13 at 11:03
  • 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! – Stefano C. Oct 29 '14 at 16:44
  • 1
    @Aric 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. – davidA Nov 16 '16 at 23:56
  • 3
    As of 2018, is this still the best way? (I am only interested in small graphs with at most tens of nodes.) – Szabolcs Apr 13 '18 at 12:03
16

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

Lee
  • 29,398
  • 28
  • 117
  • 170
  • 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 . – MAK Oct 05 '21 at 15:31
  • 1
    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 "<-" – gijswijs Nov 22 '22 at 07:13
2

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

  • Welcome to StackOverflow! Please read the stackoverflow answering guideline https://stackoverflow.com/help/how-to-answer It would be nice to have some sort of explanation on answers. – Kalesh Kaladharan Jul 08 '20 at 15:36
  • Great answer! Fixed position of nodes is obtained by commenting out the net.setoptions(opts). How to bend edges without gravity enabled? – ged Jul 28 '20 at 06:02
  • @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-settings](https://pyvis.readthedocs.io/en/latest/tutorial.html#using-the-configuration-ui-to-dynamically-tweak-network-settings) – cnstntn.kndrtv Jul 29 '20 at 07:12
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

givedrug
  • 11
  • 2
  • If you remove all the (irrelevant) test data generation, how is this different from the [existing answer](https://stackoverflow.com/a/60638452/5320906) that you reference? – snakecharmerb Jun 07 '22 at 07:11
  • @snakecharmerb you can compare the graph below, with two main differences : 1, add the label;2, random edges – givedrug Jun 08 '22 at 09:23
  • @snakecharmerb the third difference: the arrow direction – givedrug Jun 08 '22 at 09:32
0

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

Jacob R
  • 1,243
  • 1
  • 16
  • 23