question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Feature Request] In the function ‘from_networkx’, convert NetworkX node/edge attributes to Bokeh node_renderer/edge_renderer data_source.

See original GitHub issue

In the data structure of NetworkX, nodes and edges can have attributes. However, node/edge attributes are not converted to Bokeh data source in from_networkx.

[In]

import matplotlib.pyplot as plt
import networkx as nx
# Prepare Data
node_datasource_for_nx = [(1, {'tag': 'feature', 'status': 'open', 'status_color': 'cyan', 'pr': 1}),
                          (2, {'tag': 'feature', 'status': 'close', 'status_color': 'gray'}),
                          (3, {'tag': 'bug', 'status': 'open', 'status_color': 'cyan'})]

edge_datasource_for_nx = [(1, 2, {'association_type': 'duplicated'}),
                          (1, 3, {'association_type': 'impact', 'importance': 5})]


G = nx.Graph()
G.add_nodes_from(node_datasource_for_nx)
G.add_edges_from(edge_datasource_for_nx)

print(G.nodes(data=True))
print(G.edges(data=True))

fig = plt.figure()
pos = nx.spring_layout(G)
nx.draw_networkx_nodes(G, pos=pos, node_color=[x[1]['status_color'] for x in node_datasource_for_nx], alpha=0.5)
nx.draw_networkx_labels(G, pos=pos)
nx.draw_networkx_edges(G, pos=pos)
plt.show()

[Out] prepare_data

[In]

from bokeh.models.graphs import from_networkx
graph_renderer = from_networkx(G, nx.spring_layout)

graph_renderer.node_renderer.data_source

[Out] node_datasource_no_attribute

[In]

graph_renderer.edge_renderer.data_source

[Out] edge_datasource_no_attribute

I currently solve this in the following way.

from bokeh.models.sources import ColumnDataSource, CDSView
# Convert: NetworkX node attributes -> ColumnDataSource
node_dict = dict()
node_dict['index'] = list(G.nodes())

node_attr_keys = [attr_key for node in list(G.nodes(data=True)) for attr_key in node[1].keys() ]
node_attr_keys = list(set(node_attr_keys))

for attr_key in node_attr_keys:
    node_dict[attr_key] = [node_attr[attr_key] if attr_key in node_attr.keys() else None 
                           for node_key, node_attr in list(G.nodes(data=True))]
node_source = ColumnDataSource(node_dict)

# Set node attribute to Bokeh DataSource
graph_renderer.node_renderer.data_source = node_source
graph_renderer.node_renderer.view = CDSView(source=node_source)

# Convert: NetworkX edge attributes -> ColumnDataSource
edge_dict = dict()
edge_dict['start'] = [x[0] for x in G.edges(data=True)]
edge_dict['end'] = [x[1] for x in G.edges(data=True)]

edge_attr_keys = [attr_key for edge in list(G.edges(data=True)) for attr_key in edge[2].keys() ]
edge_attr_keys = list(set(edge_attr_keys))

for attr_key in edge_attr_keys:
    edge_dict[attr_key] = [edge_attr[attr_key] if attr_key in edge_attr.keys() else None 
                           for _, _, edge_attr in list(G.edges(data=True))]
edge_source = ColumnDataSource(edge_dict)
    
# Set edge attribute to Bokeh DataSource
graph_renderer.edge_renderer.data_source = edge_source
graph_renderer.edge_renderer.view = CDSView(source=edge_source)

graph_renderer.node_renderer.data_source

[Out] node_datasource_with_attributes

[In]

graph_renderer.edge_renderer.data_source

[Out] edge_datasource_with_attributes

However, it is painful to write the above code every time. I think that it is general case to specify Bokeh’s hover information and draw style by using node/edge attributes (for example, specifying node colors with node attributes). It is useful to be able to use the node/edge attributes of NetworkX also in Bokeh.

[In]

from bokeh.io import show, output_notebook
from bokeh.models import Plot, Range1d, MultiLine, Circle, HoverTool

plot = Plot(plot_width=300, plot_height=300,
            x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1))

# !!! Specify colors with node attributes !!!
graph_renderer.node_renderer.glyph = Circle(size=15, 
                                            fill_color='status_color') 
graph_renderer.edge_renderer.glyph = MultiLine(line_color="#CCCCCC", 
                                               line_alpha=0.8, 
                                               line_width=1)

# !!! Hover the node attributes !!! 
node_hover = HoverTool(tooltips=[('Issue', '@index'),
                                 ('Tag', '@tag'),
                                 ('Status','@status')],)
plot.add_tools(node_hover)
plot.renderers.append(graph_renderer)

output_notebook()
show(plot)

[Out] node_hover

Feature request

Expected result

In from_networkx, convert the attributes of nodes/edges as follows.

  1. NetworkX: Graph.nodes data -> Bokeh: GraphRenderer.node_renderer.data_source.data
  2. NetworkX: Graph.edges data -> Bokeh: GraphRenderer.edge_renderer.data_source.data

I think that this function can be realized by modifying the code as follows.

[before]
https://github.com/bokeh/bokeh/blob/master/bokeh/models/graphs.py#L35-L77

[after]

def from_networkx(graph, layout_function, **kwargs):
    '''
        ...
    '''

    # inline import to prevent circular imports
    from bokeh.models.renderers import GraphRenderer
    from bokeh.models.graphs import StaticLayoutProvider

    # Handles nx 1.x vs 2.x data structure change
    # Convert node attributes
    node_dict = dict()
    node_dict['index'] = list(graph.nodes())
        
    node_attr_keys = [attr_key for node in list(graph.nodes(data=True)) for attr_key in node[1].keys()]
    node_attr_keys = list(set(node_attr_keys))


    for attr_key in node_attr_keys:
        node_dict[attr_key] = [node_attr[attr_key] if attr_key in node_attr.keys() else None 
                               for node_key, node_attr in list(graph.nodes(data=True))]
        
    # Convert edge attributes
    edge_dict = dict()
    edge_dict['start'] = [x[0] for x in graph.edges(data=True)]
    edge_dict['end'] = [x[1] for x in graph.edges(data=True)]

    edge_attr_keys = [attr_key for edge in list(graph.edges(data=True)) for attr_key in edge[2].keys()]
    edge_attr_keys = list(set(edge_attr_keys))

    for attr_key in edge_attr_keys:
        edge_dict[attr_key] = [edge_attr[attr_key] if attr_key in edge_attr.keys() else None 
                                   for _, _, edge_attr in list(graph.edges(data=True))]

    node_source = ColumnDataSource(data=node_dict)
    edge_source = ColumnDataSource(data=edge_dict)

    graph_renderer = GraphRenderer()
    graph_renderer.node_renderer.data_source.data = node_source.data
    graph_renderer.edge_renderer.data_source.data = edge_source.data

    graph_layout = layout_function(graph, **kwargs)
    graph_renderer.layout_provider = StaticLayoutProvider(graph_layout=graph_layout)

    return graph_renderer

Note:
The above code is still incomplete. I think that it is necessary to consider how to handle it in the following case. (raising exceptions, etc.)

  1. The case that nodes have a attribute named ‘index’
  2. The case that edges have a attribute named ‘start’ or ‘end’

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
bryevdvcommented, Aug 24, 2018

@komo-fr great, I’d like to see if @canavandl can offer any quick thoughts, since he is the most familiar with this area of the codebase. If not it will be a little while before I can take a deeper look, but I look forward to collaborating on it with you.

0reactions
bryevdvcommented, Sep 3, 2018

@komo-fr (1) is a great place to start

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to pass node attributes from NetworkX to bokeh
I can't quite understand how to fetch these values using bokeh's from_networkx function. It seems that this doesn't pass the attributes over as ......
Read more >
Visualizing network graphs — Bokeh 2.4.0 Documentation
The from_networkx method converts node and edge attributes of the NetworkX package for use with node_renderer and edge_renderer of the GraphRenderer model. For ......
Read more >
Make an Interactive Network Visualization with Bokeh
This notebook includes code for creating interactive network visualizations with the Python libraries NetworkX and Bokeh. The notebook begins with code for ...
Read more >
Plot a Network Graph from DataFrame - MSTICPy
This uses underlying functionality from NetworkX and Bokeh. You pass the functions the ... Node and edge attributes are taken from other DataFrame...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found