Search code examples
pythonplotlynetworkxdirected-graph

How to efficiently create interactive directed network graphs (with arrows) on Python?


In order to construct a directed network graph, Plotly's current approach seems to be using annotations. This works when there are few edges and one can manually populate each one through the figure layout, e.g., this example.

But if I'm creating a much more complicated graph, is there a good way to iteratively define the arrow coordinates for all the edges (I can only think of constructing a string and then use eval(), although I know it's bad practice)? (edit: it seems this approach of concatenating iteratively generated dict() definition strings doesn't work -- worked only for one dict() definition)

Edit: adding a code snippet to better illustrate the scenario (with the eval() line commented out for comparison):

import plotly.offline as py 
import plotly.graph_objs as go 

trace = go.Scatter( 
    x=[1, 2, 2, 1], 
    y=[3, 4, 3, 4], 
    mode='markers',
    marker=dict(size=[100, 100, 100, 100])
)

fig = go.Figure(
    data=[trace],
    layout=go.Layout(
        annotations = [
            dict(
                ax=1, ay=3, axref='x', ayref='y',
                x=2, y=4, xref='x', yref='y'
            ),
            # eval("dict(ax=2, ay=3, axref='x', ayref='y', x=1, y=4, xref='x', yref='y')")
        ]
    )
) 
py.plot(fig)

I'm open to try other visualization packages as well, if there is a good way in doing this under Bokeh or others.


Solution

  • Yeah I agree the annotations solution isn't that efficient. Does this work for what you are trying to do: https://github.com/redransil/plotly-dirgraph

    import plotly.graph_objects as go
    import networkx as nx
    import dash
    import dash_core_components as dcc
    import dash_html_components as html
    from addEdge import addEdge
    
    # Controls for how the graph is drawn
    nodeColor = 'Blue'
    nodeSize = 20
    lineWidth = 2
    lineColor = '#000000'
    
    # Make a random graph using networkx
    G = nx.random_geometric_graph(5, .5)
    pos = nx.layout.spring_layout(G)
    for node in G.nodes:
        G.nodes[node]['pos'] = list(pos[node])
    
    # Make list of nodes for plotly
    node_x = []
    node_y = []
    for node in G.nodes():
        x, y = G.nodes[node]['pos']
        node_x.append(x)
        node_y.append(y)
    
    # Make a list of edges for plotly, including line segments that result in arrowheads
    edge_x = []
    edge_y = []
    for edge in G.edges():
        # addEdge(start, end, edge_x, edge_y, lengthFrac=1, arrowPos = None, arrowLength=0.025, arrowAngle = 30, dotSize=20)
        start = G.nodes[edge[0]]['pos']
        end = G.nodes[edge[1]]['pos']
        edge_x, edge_y = addEdge(start, end, edge_x, edge_y, .8, 'end', .04, 30, nodeSize)
    
    
    edge_trace = go.Scatter(x=edge_x, y=edge_y, line=dict(width=lineWidth, color=lineColor), hoverinfo='none', mode='lines')
    
    
    node_trace = go.Scatter(x=node_x, y=node_y, mode='markers', hoverinfo='text', marker=dict(showscale=False, color = nodeColor, size=nodeSize))
    
    fig = go.Figure(data=[edge_trace, node_trace],
                 layout=go.Layout(
                    showlegend=False,
                    hovermode='closest',
                    margin=dict(b=20,l=5,r=5,t=40),
                    xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                    yaxis=dict(showgrid=False, zeroline=False, showticklabels=False))
                    )
    
    # Note: if you don't use fixed ratio axes, the arrows won't be symmetrical
    fig.update_layout(yaxis = dict(scaleanchor = "x", scaleratio = 1), plot_bgcolor='rgb(255,255,255)')
    
    app = dash.Dash()
    app.layout = html.Div([dcc.Graph(figure=fig)])
    
    app.run_server(debug=True, use_reloader=False)
    

    Example: directed graph output