[Feature Request] In the function ‘from_networkx’, convert NetworkX node/edge attributes to Bokeh node_renderer/edge_renderer data_source.
See original GitHub issueIn 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]
[In]
from bokeh.models.graphs import from_networkx
graph_renderer = from_networkx(G, nx.spring_layout)
graph_renderer.node_renderer.data_source
[Out]
[In]
graph_renderer.edge_renderer.data_source
[Out]
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]
[In]
graph_renderer.edge_renderer.data_source
[Out]
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]
Feature request
Expected result
In from_networkx
, convert the attributes of nodes/edges as follows.
- NetworkX: Graph.nodes data -> Bokeh: GraphRenderer.node_renderer.data_source.data
- 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.)
- The case that nodes have a attribute named ‘index’
- The case that edges have a attribute named ‘start’ or ‘end’
Issue Analytics
- State:
- Created 5 years ago
- Comments:5 (5 by maintainers)
Top GitHub Comments
@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.
@komo-fr (1) is a great place to start