Displaying edge labels of networkx graph in plotly

I’m trying to display edge weights of Networkx graph while plotting using plotly. There is a problem in displaying the edge weights as edge labels while hovering over edges of the resulting plot.

Code:

import numpy as np
import networkx as nx
import plotly.graph_objects as go

from pprint import pprint
from collections import OrderedDict
from matplotlib import pyplot as plt


def get_edge_trace(G):
    edge_x = []
    edge_y = []

    etext = [f'weight{w}' for w in list(nx.get_edge_attributes(G, 'diameter').values())]

    for edge in G.edges():
        x0, y0 = G.nodes[edge[0]]['pos']
        x1, y1 = G.nodes[edge[1]]['pos']
        edge_x.append(x0)
        edge_x.append(x1)
        edge_x.append(None)
        edge_y.append(y0)
        edge_y.append(y1)
        edge_y.append(None)

    edge_trace = go.Scatter(
        x=edge_x, y=edge_y,
        line=dict(width=0.5, color='#888'),
        marker=dict(color='rgb(125,125,125)', size=1),
        text=etext,
        hoverinfo='text',
        mode='lines'  # lines
    )

    return edge_trace


def get_node_trace(G):
    node_x = []
    node_y = []
    for node in G.nodes():
        x, y = G.nodes[node]['pos']
        node_x.append(x)
        node_y.append(y)

    node_trace = go.Scatter(
        x=node_x, y=node_y,
        mode='markers',
        hoverinfo='text',
        marker=dict(
            showscale=True,
            # colorscale options
            # 'Greys' | 'YlGnBu' | 'Greens' | 'YlOrRd' | 'Bluered' | 'RdBu' |
            # 'Reds' | 'Blues' | 'Picnic' | 'Rainbow' | 'Portland' | 'Jet' |
            # 'Hot' | 'Blackbody' | 'Earth' | 'Electric' | 'Viridis' |
            colorscale='YlGnBu',
            reversescale=True,
            color=[],
            size=10,
            colorbar=dict(
                thickness=15,
                title='Node Connections',
                xanchor='left',
                titleside='right'
            ),
            line_width=2))

    return node_trace


if __name__ == '__main__':

    tail = [1, 2, 3]
    head = [2, 3, 4]

    xpos = [0, 1, 2, 3]
    ypos = [0, 0, 0, 0]
    w = [1, 2, 3]
    xpos_ypos = [(x, y) for x, y in zip(xpos, ypos)]

    ed_ls = [(x, y) for x, y in zip(tail, head)]
    G = nx.OrderedGraph()
    G.add_edges_from(ed_ls)


    pos = OrderedDict(zip(G.nodes, xpos_ypos))
    edge_w = OrderedDict(zip(G.edges, w))

    nx.draw(G, pos=pos, with_labels=True)
    nx.set_node_attributes(G, pos, 'pos')
    nx.set_edge_attributes(G, edge_w, 'weight')
    plt.show()

    # convert to plotly graph
    edge_trace = get_edge_trace(G)
    node_trace = get_node_trace(G)
    pprint(np.array(nx.get_edge_attributes(G, 'diameter').values()))
    pprint(edge_trace)
    pprint(node_trace)

    fig = go.Figure(data=[edge_trace, node_trace],
                    layout=go.Layout(
                        title='<br>Network graph made with Python',
                        titlefont_size=16,
                        showlegend=False,
                        hovermode='closest',
                        margin=dict(b=20, l=5, r=5, t=40),
                        annotations=[dict(
                            text="Python code: <a href='https://plot.ly/ipython-notebooks/network-graphs/'> https://plot.ly/ipython-notebooks/network-graphs/</a>",
                            showarrow=False,
                            xref="paper", yref="paper",
                            x=0.005, y=-0.002)],
                        xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                        yaxis=dict(showgrid=False, zeroline=False, showticklabels=False))
                    )
    fig.write_html('plot.html', auto_open=True)

The edge weights aren’t displayed for some reason while hovering over edges. I’ve added edge text n get_edge_trace function.

Could someone help me out?

Hi @Natasha,

Plotly associates a text to a position of coordinates (x, y). An edge is not defined by a unique point to assign it a text.
In order to display the edge weights you can define one more scatter trace, of mode='text', and its x, y lists are the middle point coordinates of the edges.
For your example the marker colorscale and colorbar in the node_trace definition should be removed, because marker color is not defined as a numerical list (it is empty).

This is the code that displays the edge weights. I defined synthetical weights because your etext is an empty list.

import numpy as np
import networkx as nx
import plotly.graph_objects as go
from collections import OrderedDict


def get_edge_trace(G):
    edge_x = []
    edge_y = []

    etext = [f'weight{w}' for w in list(nx.get_edge_attributes(G, 'diameter').values())]#THIS list is empty for your data
    xtext=[]
    ytext=[]
    for edge in G.edges():

        x0, y0 = G.nodes[edge[0]]['pos']
        x1, y1 = G.nodes[edge[1]]['pos']
        xtext.append((x0+x1)/2)
        ytext.append((y0+y1)/2)
        edge_x.append(x0)
        edge_x.append(x1)
        edge_x.append(None)
        edge_y.append(y0)
        edge_y.append(y1)
        edge_y.append(None)

    edge_trace = go.Scatter(
        x=edge_x, y=edge_y,
        line=dict(width=0.5, color='#888'),
        mode='lines'
    )
    eweights_trace = go.Scatter(x=xtext,y= ytext, mode='text',
                              marker_size=0.5,
                              text=[0.45, 0.7, 0.34],
                              textposition='top center',
                              hovertemplate='weight: %{text}<extra></extra>')
    return edge_trace, eweights_trace


def get_node_trace(G):
    node_x = []
    node_y = []
    for node in G.nodes():
        x, y = G.nodes[node]['pos']
        node_x.append(x)
        node_y.append(y)

    node_trace = go.Scatter(
        x=node_x, y=node_y,
        mode='markers',
        marker_size=12,
        #hoverinfo='text', #THIS LINE HAS NO SENSE BECAUSE text WAS NOT DEFINED
        marker_color='RoyalBlue',
        )

    return node_trace


if __name__ == '__main__':

    tail = [1, 2, 3]
    head = [2, 3, 4]

    xpos = [0, 1, 2, 3]
    ypos = [0, 0, 0, 0]
    w = [1, 2, 3]
    xpos_ypos = [(x, y) for x, y in zip(xpos, ypos)]

    ed_ls = [(x, y) for x, y in zip(tail, head)]
    G = nx.OrderedGraph()
    G.add_edges_from(ed_ls)


    pos = OrderedDict(zip(G.nodes, xpos_ypos))
    edge_w = OrderedDict(zip(G.edges, w))
    nx.set_node_attributes(G, pos, 'pos')
    nx.set_edge_attributes(G, edge_w, 'weight')

    # convert to plotly graph
    edge_trace, eweights_trace = get_edge_trace(G)
    node_trace = get_node_trace(G)

    fig = go.Figure(data=[edge_trace, node_trace,eweights_trace ],
                    layout=go.Layout(
                        title='<br>Network graph made with Python',
                        titlefont_size=16,
                        showlegend=False,
                        hovermode='closest',
                        margin=dict(b=20, l=5, r=5, t=40),
                        xaxis_visible=False,
                        yaxis_visible=False)
                    )

Note that in the definition of eweight_trace, textposition can be a list with one of the following positions:

 ( "top left" | "top center" | "top right" | "middle left" | "middle center" | "middle right" | "bottom left" | "bottom center" | "bottom right" )
1 Like

@empet Thanks a lot. Could you please explain how to add the node labels here? For instance, I want to use node numbers as node labels. Also, I’d like to know how to view the edge weights only when I hover over a given edge. I don’t want the text to be displayed by default.

I could do this by changing mode=‘lines’ in eweights_trace.

@Natasha

Define the corresponding trace like this:

eweights_trace = go.Scatter(x=xtext,y= ytext, 
                              mode='markers',
                              marker_size=0.5,
                              text=[0.45, 0.7, 0.34],
                              #textposition='top center',
                              hovertemplate='weight: %{text}<extra></extra>')

i.e.change the mode from 'text`` to 'markers’, and remove textposition`.

1 Like

@empet

Could you please suggest how this can be done?

@empet

I tried this

nlabels_trace = go.Scatter(x=node_x,
                                y=node_y,
                                mode='markers',
                                marker_size=0.5,
                                text=[1, 2, 3, 4],
                                # textposition='top center',
                                hovertemplate='node: %{text}<extra></extra>')

Unfortunately, it didn’t work.

@Natasha
Meanwhile I tried to find out why your Plotly figure was different from that drawn with networkx. I noticed that you labeled the nodes in the Excel file starting with 1, but Plotly code supposed that nodes are labeled from 0. So there was a nonconcordance between node labels and edge end labels. Moreover, I’m wondering why you defined a networkx graph as long as your Excel file contained all information to draw a Plotly network.

Only when you cannot provide the node positions you have to define first a networkx graph and set a graph layout to place the nodes in the plane, i.e. to assign them a position (expressed by node coordinates).

Here is a new code that resulted by modifying data from your Excel file to get the right Plotly network and avoiding the use of networkx. Moreover I assigned the node indices as labels:

import numpy as np
import pandas as pd
import plotly.graph_objects as go

def get_plotly_data(E, coords):

   # E is the list of tuples representing the graph edges
    # coords is the list of node coordinates 
    N = len(coords)
    Xnodes = [coords[k][0] for k in range(N)] # x-coordinates of nodes
    Ynodes = [coords[k][1] for k in range(N)] # y-coordnates of nodes

    Xedges = []
    Yedges = []
    Xweights = []
    Yweights = []
    for e in E:
        x0, x1 = coords[e[0]][0], coords[e[1]][0]
        y0, y1 = coords[e[0]][1], coords[e[1]][1]
        Xedges.extend([x0, x1, None])
        Yedges.extend([y0, y1, None])
        Xweights.append((x0+x1)/2)
        Yweights.append((y0+y1)/2)
    return Xnodes, Ynodes, Xedges, Yedges, Xweights, Yweights 

def get_node_trace(x, y, labels, marker_size=10, marker_color='RoyalBlue', 
                   line_color='rgb(50,50,50)', line_width=0.5):
    return go.Scatter(
                x=x,
                y=y,
                mode='markers',
                marker=dict(
                            size=marker_size, 
                            color=marker_color,
                            line=dict(color=line_color, width=line_width)
                             ),
            text=labels,
            hoverinfo='text'
               )

def get_edge_trace(x, y, line_color='#888', line_width=1):
    return go.Scatter(
                x=x,
                y=y,
                mode='lines',
                line_color=line_color,
                line_width=line_width,
                hoverinfo='none'
               )

df = pd.read_excel(open("file.xlsx", 'rb'), sheet_name=0, index=False)

#df.columns

I = np.where(np.isnan(df['node'].values))[0]
print(I)

#Reindex the nodes, tail and head, starting with  0

t = df['t'].values-1
h = df['h'].values-1
node= df['node'].values.astype(int)[:I[0]]-1

coords = [(xc,yc ) for xc, yc in zip(df['x'][:I[0]], df['y'][:I[0]])]
E = list(zip(t, h)) #the graph edges
print(E[:5])

Xn, Yn, Xe, Ye, Xw, Yw = get_plotly_data(E, coords)

node_trace = get_edge_trace(Xe, Ye)
edge_trace = get_node_trace(Xn, Yn, node)
eweights_trace = go.Scatter(x=Xw, y= Yw, 
                            mode='markers',
                            marker_size=0.5,
                            text=df['d'][:I[0]],
                            hovertemplate='weight: %{text}<extra></extra>')

layout=go.Layout(title_text='Your title',
            title_x=0.5,
                 height=800,
            showlegend=False,
            xaxis_visible=False,
            yaxis_visible=False,   
            margin=dict(b=20, l=5, r=5, t=80),     
            hovermode='closest')
fig = go.Figure(data=[edge_trace, node_trace, eweights_trace], layout=layout)
1 Like

@empet

Thanks a lot for the response on the other post. Actually, I did pos = OrderedDict(zip(sorted(G.nodes), xpos_ypos)) and that solved my problem. I’m not really sure how this solved the indexing problem that you are referring to.

I was using Network for the same reason that you mentioned. To get the positions when I don’t have the coordinates. I like the alternate code presented by you. Thanks!

Regarding the node labels,
I could view those now. But the coordinates of nodes that were displayed earlier is missing now. How do I retain both the (x,y) coordinate display of nodes and node label?

@Natasha
To display both label and coords, set in the definition of the function get_node_trace()

mode='markers'

and

hoverinfo='text+x+y'

@empet

Thank you. But, is there an option to add two node labels ?

Yes,
for a graph with 5 nodes of id 0, 1, 2, 3, 4, and labels as below:

node= np.arange(5)
secondary_label=[' A', ' B', 'C', 'D', 'E']

define in node_trace:

text= [f'node id: {n}<br>label: {sl}' for n, sl in zip(node, secondary_label)]

<br> is html tag for newline.

1 Like

@empet
Hi,

While hovering over the nodes , I could click on a node and display the label. But when a second node is clicked, the label of the first node disappears.

Is there a way to display a node label on clicking it and remove the label while clicking on the same node for the second time?

Thanks a lot

@empet
Could you please have a look at this Plot 3D network graph post?