4

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.

aspire
  • 155
  • 1
  • 1
  • 9
  • Can you provide a sample code that works and one that doesn't? – darthbith Jul 18 '18 at 20:37
  • @darthbith I've just added the sample code. What I hope to achieve is basically scale the `dict()` definition so that I can iterate through all edges in the network and create an arrow for each. – aspire Jul 18 '18 at 21:03
  • I'm not all that familiar with plotly, so can you clarify what you need to do? How would you connect a large(r) number of nodes together? – darthbith Jul 18 '18 at 21:30
  • Can you define a list of tuples of x, y pairs? Then it would be trivial to iterate through that and generate a list of dicts that could be assigned to the annotations argument – darthbith Jul 18 '18 at 21:32
  • @darthbith I added two more points to help illustrate the problem: in the actual program, I build the network node by node through NetworkX, but at time of visualization, I basically have the x and y lists as shown in the example (with a start point and an end point for the edge). Could you elaborate on the iteration using this example, so that I don't need to create two separate annotations but directly with an iteration through the node coordinates? (an example that connects any two pairs of dots would greatly help, no need to replicate the same two arrows that I drew) – aspire Jul 19 '18 at 00:51
  • Answering my own question: as @darthbith suggested, `dict(ax=x0[i], ay=x0[i], x=x1[i], y=y1[i]) for i in range(len(x0))` works. – aspire Jul 19 '18 at 19:44
  • Great! You should write up an answer in the box below :-) – darthbith Jul 19 '18 at 19:55
  • I've added a more complete sample as the answer, thank you @darthbith! – aspire Jul 19 '18 at 20:05
  • The [library d3graph](https://erdogant.github.io/d3graph) may be use. You can specify directed edges. It is a force-directed d3-graph. More information is posted here: https://stackoverflow.com/questions/8840255/django-and-interactive-graph-network-visualization/62520707#62520707 – erdogant Jun 22 '20 at 18:23

4 Answers4

6

Below is a sample of using loop to create arrows in a Plotly graph, which is easily applicable for NetworkX visualization of directed graphs.

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])
)

# Edges
x0 = [1, 2]
y0 = [3, 3]
x1 = [2, 1]
y1 = [4, 4]

fig = go.Figure(
    data=[trace],
    layout=go.Layout(
        annotations = [
            dict(ax=x0[i], ay=y0[i], axref='x', ayref='y',
                x=x1[i], y=y1[i], xref='x', yref='y',
                showarrow=True, arrowhead=1,) for i in range(0, len(x0))
        ]
    )
) 
py.plot(fig)
aspire
  • 155
  • 1
  • 1
  • 9
5

I'm the author of gravis, an interactive graph visualization package in Python. It recognizes graph objects from several network analysis packages such as NetworkX, igraph or graph-tool.

Here's a minimal example of creating a directed graph with NetworkX and visualizing it with gravis.

import gravis as gv
import networkx as nx

g = nx.DiGraph()
g.add_edges_from([(1, 2), (2, 3), (2, 4), (4, 5), (4, 7), (5, 6), (1, 6), (6, 7)])
gv.d3(g)
Robert Haas
  • 615
  • 2
  • 8
2

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

Red Ransil
  • 56
  • 2
0

Based on the answer by @Aspire, an example with edge/arrow configuration and image of result:

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=[10, 10, 10, 10]),
    marker_size=20, # Node size
)

# Edges
x0 = [1, 2]
y0 = [3, 3]
x1 = [2, 1]
y1 = [4, 4]

fig = go.Figure(
    data=[trace],
    layout=go.Layout(
        height=700, #height of image in pixels.
        width=1000, #Width of image in pixels.
        annotations = [
            dict(
                ax=x0[i], 
                ay=y0[i], 
                axref='x', 
                ayref='y',
                x=x1[i], 
                y=y1[i], 
                xref='x', 
                yref='y',
                arrowwidth=5, # Width of arrow.
                arrowcolor="red",
                arrowsize=0.8, # (1 gives head 3 times as wide as arrow line)
                showarrow=True, 
                arrowhead=1,) for i in range(0, len(x0))
        ]
    )
) 
py.plot(fig)

Yields: enter image description here

a.t.
  • 2,002
  • 3
  • 26
  • 66