4
votes

I'm using Plotly's Python interface to generate a network. I've managed to create a network with my desired nodes and edges, and to control the size of the nodes. I am desperately looking for help on how to do the following:

  1. add node labels
  2. add edge labels according to a list of weights
  3. control the edge line width according to a list of weights

All this without using the "hovering" option, as it has to go in a non-interactive paper. I'd greatly appreciate any help! Plotly's output | In case this fails, the figure itself | matrix.csv This is my code (most is copy-pasted from the Plotly tutorial for Networkx):

import pandas as pd
import plotly.plotly as py
from plotly.graph_objs import *
import networkx as nx

matrix = pd.read_csv("matrix.csv", sep = "\t", index_col = 0, header = 0)

G = nx.DiGraph()

# add nodes:
G.add_nodes_from(matrix.columns)

# add edges:
edge_lst = [(i,j, matrix.loc[i,j])
            for i in matrix.index
            for j in matrix.columns
            if matrix.loc[i,j] != 0]
G.add_weighted_edges_from(edge_lst)

# create node trace:
node_trace = Scatter(x = [], y = [], text = [], mode = 'markers',
                    marker = Marker(
                    showscale = True,
                    colorscale = 'YIGnBu',
                    reversescale = True,
                    color = [],
                    size = [],
                    colorbar = dict(
                        thickness = 15,
                        title = 'Node Connections',
                        xanchor = 'left',
                        titleside = 'right'),
                    line = dict(width = 2)))

# set node positions
pos = nx.spring_layout(G)
for node in G.nodes():
    G.node[node]['pos']= pos[node]

for node in G.nodes():
    x, y = G.node[node]['pos']
    node_trace['x'].append(x)
    node_trace['y'].append(y)

# create edge trace:
edge_trace = Scatter(x = [], y = [], text = [],
                     line = Line(width = [], color = '#888'),
                     mode = 'lines')

for edge in G.edges():
    x0, y0 = G.node[edge[0]]['pos']
    x1, y1 = G.node[edge[1]]['pos']
    edge_trace['x'] += [x0, x1, None]
    edge_trace['y'] += [y0, y1, None]
    edge_trace['text'] += str(matrix.loc[edge[0], edge[1]])[:5]

# size nodes by degree
deg_dict = {deg[0]:int(deg[1]) for deg in list(G.degree())}
for node, degree in enumerate(deg_dict):
    node_trace['marker']['size'].append(deg_dict[degree] + 20)

fig = Figure(data = Data([edge_trace, node_trace]),
             layout = Layout(
                 title = '<br>AA Substitution Rates',
                 titlefont = dict(size = 16),
                 showlegend = True,
                 margin = dict(b = 20, l = 5, r = 5, t = 40),
                 annotations = [dict(
                     text = "sub title text",
                     showarrow = False,
                     xref = "paper", yref = "paper",
                     x = 0.005, y = -0.002)],
                 xaxis = XAxis(showgrid = False, 
                               zeroline = False, 
                               showticklabels = False),
                 yaxis = YAxis(showgrid = False, 
                               zeroline = False, 
                               showticklabels = False)))

py.plot(fig, filename = 'networkx')
2

2 Answers

4
votes

So

1. The solution to this is relative easy, you create a list with the node ids and you set it in the text attribute of the scatter plot. Then you set the mode as "markers+text" and you're done.

2. This is a little bit more tricky. You have to calculate the middle of each line and create a list of dicts including the line's middle position and weight. Then you add set as the layout's annotation.

3. This is too compicated to be done using plotly IMO. As for now I am calculating the position of each node using networkx spring_layout function. If you'd want to set the width of each line based on its weight you would have to modify the position using a function that takes into account all the markers that each line is attached to.

Bonus I give you the option to color each of the graph's components differently.

Here's a (slightly modified) function I made a while ago that does 1 and 2:

import pandas as pd
import plotly.plotly as py
import plotly.graph_objs as go
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
import networkx as nx

def scatter_plot_2d(G, folderPath, name, savePng = False):
    print("Creating scatter plot (2D)...")

    Nodes = [comp for comp in nx.connected_components(G)] # Looks for the graph's communities
    Edges = G.edges()
    edge_weights = nx.get_edge_attributes(G,'weight')

    labels = [] # names of the nodes to plot
    group = [] # id of the communities
    group_cnt = 0

    print("Communities | Number of Nodes")
    for subgroup in Nodes:
        group_cnt += 1
        print("      %d     |      %d" % (group_cnt, len(subgroup)))
        for node in subgroup:
            labels.append(int(node))
            group.append(group_cnt)

    labels, group = (list(t) for t in zip(*sorted(zip(labels, group))))

    layt = nx.spring_layout(G, dim=2) # Generates the layout of the graph
    Xn = [layt[k][0] for k in list(layt.keys())]  # x-coordinates of nodes
    Yn = [layt[k][1] for k in list(layt.keys())]  # y-coordinates
    Xe = []
    Ye = []

    plot_weights = []
    for e in Edges:
        Xe += [layt[e[0]][0], layt[e[1]][0], None]
        Ye += [layt[e[0]][1], layt[e[1]][1], None]
        ax = (layt[e[0]][0]+layt[e[1]][0])/2
        ay = (layt[e[0]][1]+layt[e[1]][1])/2
        plot_weights.append((edge_weights[(e[0], e[1])], ax, ay))

    annotations_list =[
                        dict(   
                            x=plot_weight[1],
                            y=plot_weight[2],
                            xref='x',
                            yref='y',
                            text=plot_weight[0],
                            showarrow=True,
                            arrowhead=7,
                            ax=plot_weight[1],
                            ay=plot_weight[2]
                        ) 
                        for plot_weight in plot_weights
                    ]

    trace1 = go.Scatter(  x=Xe,
                          y=Ye,
                          mode='lines',
                          line=dict(color='rgb(90, 90, 90)', width=1),
                          hoverinfo='none'
                        )

    trace2 = go.Scatter(  x=Xn,
                          y=Yn,
                          mode='markers+text',
                          name='Nodes',
                          marker=dict(symbol='circle',
                                      size=8,
                                      color=group,
                                      colorscale='Viridis',
                                      line=dict(color='rgb(255,255,255)', width=1)
                                      ),
                          text=labels,
                          textposition='top center',
                          hoverinfo='none'
                          )

    xaxis = dict(
                backgroundcolor="rgb(200, 200, 230)",
                gridcolor="rgb(255, 255, 255)",
                showbackground=True,
                zerolinecolor="rgb(255, 255, 255)"
                )
    yaxis = dict(
                backgroundcolor="rgb(230, 200,230)",
                gridcolor="rgb(255, 255, 255)",
                showbackground=True,
                zerolinecolor="rgb(255, 255, 255)"
                )

    layout = go.Layout(
        title=name,
        width=700,
        height=700,
        showlegend=False,
        plot_bgcolor="rgb(230, 230, 200)",
        scene=dict(
            xaxis=dict(xaxis),
            yaxis=dict(yaxis)
        ),
        margin=dict(
            t=100
        ),
        hovermode='closest',
        annotations=annotations_list
        , )
    data = [trace1, trace2]
    fig = go.Figure(data=data, layout=layout)
    plotDir = folderPath + "/"

    print("Plotting..")

    if savePng:
        plot(fig, filename=plotDir + name + ".html", auto_open=True, image = 'png', image_filename=plotDir + name,
         output_type='file', image_width=700, image_height=700, validate=False)
    else:
        plot(fig, filename=plotDir + name + ".html")
1
votes

The d3graph provides the functionalities you want.

pip install d3graph

I downloaded your data and imported it for demonstration:

# Import data
df = pd.read_csv('data.csv', index_col=0)

# Import library
from d3graph import d3graph

# Convert your Pvalues. Note that any edge is set when a value in the matrix is >0. The edge width is however based on this value. A conversion is therefore useful when you work with Pvalues.
df[df.values==0]=1
df = -np.log10(df)
# Increase some distance between edges. Maybe something like this.
df = (np.exp(df)-1)/10

# Make the graph with default settings
results = d3graph(df)

# Make the graph by setting some parameters
results = d3graph(df, node_color=df.columns.values, node_color_edge='#000000', node_size_edge=10, directed=True, charge=500)

This will results in an interactive network graph. Two screenshots: one with the default settings and the one with tweaked settings.

Default settings Tweaked d3graph