Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set individual line widths in network-style Plotly figure (Python 3.6 | plot.ly)?

I'm working on a plot.ly wrapper for my networkx plots adapted from https://plot.ly/python/network-graphs/. I can't figure out how to change the width for each connection based on the weights. The weights are in the attr_dict as weight. I tried setting go.Line objects but it wasn't working :(. Any suggestions? (and links to tutorials if possible :) ). Attaching an example of the network structure from a plot I made in matplotlib.

How can I set individual line widths for each connection in plotly?

enter image description here

import requests
from ast import literal_eval
import plotly.offline as py
from plotly import graph_objs as go
py.init_notebook_mode(connected=True)

# Import Data
pos = literal_eval(requests.get("https://pastebin.com/raw/P5gv0FXw").text)
df_plot = pd.DataFrame(pos).T
df_plot.columns = list("xy")
edgelist = literal_eval(requests.get("https://pastebin.com/raw/2a8ErW7t").text)
_fig_kws={"figsize":(10,10)}

# Plotting Function
def plot_networkx_plotly(df_plot, pos, edgelist, _fig_kws):
    # Nodes
    node_trace = go.Scattergl(
                         x=df_plot["x"],
                         y=df_plot["y"],
                         mode="markers",
    )
    # Edges
    edge_trace = go.Scattergl(
                         x=[], 
                         y=[],
                         line=[],
                         mode="lines"
    )

    for node_A, node_B, attr_dict in edgelist:
        xA, yA = pos[node_A]
        xB, yB = pos[node_B]
        edge_trace["x"] += [xA, xB, None]
        edge_trace["y"] += [yA, yB, None]
        edge_trace["lines"].append(go.Line(width=attr_dict["weight"],color='#888'))

    # Data
    data = [node_trace, edge_trace]
    layout = {
                "width":_fig_kws["figsize"][0]*100,
                "height":_fig_kws["figsize"][1]*100,

    }
    fig = dict(data=data, layout=layout)

    py.iplot(fig)
    return fig
plot_networkx_plotly(df_plot, pos, edgelist, _fig_kws)

# ---------------------------------------------------------------------------
# PlotlyDictValueError                      Traceback (most recent call last)
# <ipython-input-72-4a5d0e26a71d> in <module>()
#      46     py.iplot(fig)
#      47     return fig
# ---> 48 plot_networkx_plotly(df_plot, pos, edgelist, _fig_kws)

# <ipython-input-72-4a5d0e26a71d> in plot_networkx_plotly(df_plot, pos, edgelist, _fig_kws)
#      25                          y=[],
#      26                          line=[],
# ---> 27                          mode="lines"
#      28     )
#      29 

# ~/anaconda/lib/python3.6/site-packages/plotly/graph_objs/graph_objs.py in __init__(self, *args, **kwargs)
#     375         d = {key: val for key, val in dict(*args, **kwargs).items()}
#     376         for key, val in d.items():
# --> 377             self.__setitem__(key, val, _raise=_raise)
#     378 
#     379     def __dir__(self):

# ~/anaconda/lib/python3.6/site-packages/plotly/graph_objs/graph_objs.py in __setitem__(self, key, value, _raise)
#     430 
#     431         if self._get_attribute_role(key) == 'object':
# --> 432             value = self._value_to_graph_object(key, value, _raise=_raise)
#     433             if not isinstance(value, (PlotlyDict, PlotlyList)):
#     434                 return

# ~/anaconda/lib/python3.6/site-packages/plotly/graph_objs/graph_objs.py in _value_to_graph_object(self, key, value, _raise)
#     535             if _raise:
#     536                 path = self._get_path() + (key, )
# --> 537                 raise exceptions.PlotlyDictValueError(self, path)
#     538             else:
#     539                 return

# PlotlyDictValueError: 'line' has invalid value inside 'scattergl'

# Path To Error: ['line']

# Current path: []
# Current parent object_names: []

# With the current parents, 'line' can be used as follows:

# Under ('figure', 'data', 'scattergl'):

#     role: object

Update with Ian Kent's Answer:

I don't think the code below can change the weights for all of the lines. I tried making all of the widths 0.1 with the weights list and got the following plot: enter image description here

but then when I did width=0.1 it worked for all of the lines: enter image description here

like image 730
O.rka Avatar asked Nov 08 '22 16:11

O.rka


1 Answers

I think the issue is in the following line of your code:

edge_trace["lines"].append(go.Line(width=attr_dict["weight"],color='#888'))

Try it with "line" instead of "lines". This is a bit of a confusing aspect of the Plotly API, but in scatter plots, the mode is plural and the argument name to change attributes of the trace is singular. So,

trace = go.Scatter(mode = 'markers', marker = dict(...))
trace = go.Scatter(mode = 'lines', line = dict(...))

Edit: Okay so there turned out to be more issues than just the "lines" now that I've sat down with it:

You have the line argument as a list of dict-like objects, whereas plotly expects it to be a single dict-like. Building a list of weights then adding all the weights to the line attribute at once seems to work:

edge_trace = go.Scattergl(
                     x=[],
                     y=[],
                     mode="lines"
)

weights = []
for node_A, node_B, attr_dict in edgelist:
    xA, yA = pos[node_A]
    xB, yB = pos[node_B]
    edge_trace["x"] += [xA, xB, None]
    edge_trace["y"] += [yA, yB, None]
    weights.append(attr_dict["weight"])

edge_trace['line'] = dict(width=weights,color='#888')

Also, you are plotting the lines in front of the nodes and thus obstructing them. You should change

data = [node_trace, edge_trace]

to

data = [edge_trace, node_trace]

to avoid this.

like image 73
Ian Kent Avatar answered Nov 14 '22 20:11

Ian Kent