✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
⚡️ Concerned about the grid? Kyle Baranko teaches how to predicting peak loads using XGBoost. Register for the August webinar!

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?