Unable to Get Index from Selected Nodes in NetworkX Graph

Hi, I have just started using Bokeh and am very impressed with its functionality!

I am trying to get the index of selected nodes in a GraphRender object (similarly to how I was able to get the index of points on change for Circle plots) although have read through a lot of documentation have been unable to make it work.

I posted a question to stack overflow with additional information: https://stackoverflow.com/questions/53603046/get-selected-glyph-from-networkx-graph-in-bokeh

Please let me know if there are examples which are directly applicable which I missed or if there are other approaches to take. I have tried CustomJS but could not get the callback working.

Thanks in advance,

Andrew

Full example code is also pasted below for convenience:

import pandas as pd
import numpy as np
from bokeh.layouts import row, widgetbox, column
from bokeh.models import ColumnDataSource, CustomJS, StaticLayoutProvider, Oval, Circle
from bokeh.models import HoverTool, TapTool, BoxSelectTool, GraphRenderer
from bokeh.models.widgets import RangeSlider, Button, DataTable, TableColumn, NumberFormatter
from bokeh.io import curdoc, show, output_notebook
from bokeh.plotting import figure
import networkx as nx
from bokeh.io import show, output_file
from bokeh.plotting import figure
from bokeh.models.graphs import from_networkx, NodesAndLinkedEdges, EdgesAndLinkedNodes, NodesOnly

# Import / instantiate networkx graph
G = nx.Graph()

G.add_edge('a', 'b', weight=0.6)
G.add_edge('a', 'c', weight=0.2)
G.add_edge('c', 'd', weight=0.1)
G.add_edge('c', 'e', weight=0.7)
G.add_edge('c', 'f', weight=0.9)
G.add_edge('a', 'd', weight=0.3)

# Node Characteristics
node_name = list(G.nodes())
positions = nx.spring_layout(G)

node_size = [k*4 for k in range(len(G.nodes()))]
nx.set_node_attributes(G, node_size, 'node_size')
visual_attributes=ColumnDataSource(
    pd.DataFrame.from_dict({k:v for k,v in G.nodes(data=True)},orient='index'))

# Edge characteristics
start_edge = [start_edge for (start_edge, end_edge) in G.edges()]
end_edge = [end_edge for (start_edge, end_edge) in G.edges()]
weight = list(nx.get_edge_attributes(G,'weight').values())

edge_df = pd.DataFrame({'source':start_edge, 'target':end_edge, 'weight':weight})

# Create full graph from edgelist
G = nx.from_pandas_edgelist(edge_df,edge_attr=True)

# Convert full graph to Bokeh network for node coordinates and instantiate Bokeh graph object
G_source = from_networkx(G, nx.spring_layout, scale=2, center=(0,0))
graph = GraphRenderer()

# Update loop where the magic happens
def update():
    selected_df = edge_df[(edge_df['weight'] >= slider.value[0]) & (edge_df['weight'] <= slider.value[1])]
    sub_G = nx.from_pandas_edgelist(selected_df,edge_attr=True)
    sub_graph = from_networkx(sub_G, nx.spring_layout, scale=2, center=(0,0))
    graph.edge_renderer.data_source.data = sub_graph.edge_renderer.data_source.    data
graph.node_renderer.data_source.data = G_source.node_renderer.data_source.    data
graph.node_renderer.data_source.add(node_size,'node_size')

def selected_points(attr,old,new):
    selected_idx = graph.node_renderer.selected.indices #does not work
    print(selected_idx)

# Slider which changes values to update the graph
slider = RangeSlider(title="Weights", start=0, end=1, value=(0.25, 0.75), step=0.10)
slider.on_change('value', lambda attr, old, new: update())

# Plot object which is updated
plot = figure(title="Meetup Network Analysis", x_range=(-1.1,1.1), y_range=(-1.1,1.1),
             tools = "pan,wheel_zoom,box_select,reset,box_zoom,crosshair", plot_width=800, plot_height=800)

# Assign layout for nodes, render graph, and add hover tool
graph.layout_provider = StaticLayoutProvider(graph_layout=positions)
graph.node_renderer.glyph = Circle(size='node_size')
graph.selection_policy = NodesOnly()
plot.renderers.append(graph)
plot.tools.append(HoverTool(tooltips=[('Name', '@index')]))

# Set layout
layout = column(slider,plot)

# does not work
#graph.node_renderer.data_source.on_change("selected", selected_points)

# Create Bokeh server object
curdoc().add_root(layout)
update()

Ah, I think I can help you with this. I ran into similar problems before.

Instead of putting the listener on data_source, you should put it on

graph.node_renderer.data_source.selected.on_change(“indices”, selected_points)

``

This will trigger communication to the server.

Hongyi

···

On Tuesday, December 4, 2018 at 10:24:17 AM UTC-5, Andrew Gibbs-Bravo wrote:

Hi, I have just started using Bokeh and am very impressed with its functionality!

I am trying to get the index of selected nodes in a GraphRender object (similarly to how I was able to get the index of points on change for Circle plots) although have read through a lot of documentation have been unable to make it work.

I posted a question to stack overflow with additional information: https://stackoverflow.com/questions/53603046/get-selected-glyph-from-networkx-graph-in-bokeh

Please let me know if there are examples which are directly applicable which I missed or if there are other approaches to take. I have tried CustomJS but could not get the callback working.

Thanks in advance,

Andrew

Full example code is also pasted below for convenience:

import pandas as pd
import numpy as np
from bokeh.layouts import row, widgetbox, column
from bokeh.models import ColumnDataSource, CustomJS, StaticLayoutProvider, Oval, Circle
from bokeh.models import HoverTool, TapTool, BoxSelectTool, GraphRenderer
from bokeh.models.widgets import RangeSlider, Button, DataTable, TableColumn, NumberFormatter
from bokeh.io import curdoc, show, output_notebook
from bokeh.plotting import figure
import networkx as nx
from bokeh.io import show, output_file
from bokeh.plotting import figure
from bokeh.models.graphs import from_networkx, NodesAndLinkedEdges, EdgesAndLinkedNodes, NodesOnly

# Import / instantiate networkx graph
G = nx.Graph()

G.add_edge('a', 'b', weight=0.6)
G.add_edge('a', 'c', weight=0.2)
G.add_edge('c', 'd', weight=0.1)
G.add_edge('c', 'e', weight=0.7)
G.add_edge('c', 'f', weight=0.9)
G.add_edge('a', 'd', weight=0.3)

# Node Characteristics
node_name = list(G.nodes())
positions = nx.spring_layout(G)

node_size = [k*4 for k in range(len(G.nodes()))]
nx.set_node_attributes(G, node_size, 'node_size')
visual_attributes=ColumnDataSource(
    pd.DataFrame.from_dict({k:v for k,v in G.nodes(data=True)},orient='index'))

# Edge characteristics
start_edge = [start_edge for (start_edge, end_edge) in G.edges()]
end_edge = [end_edge for (start_edge, end_edge) in G.edges()]
weight = list(nx.get_edge_attributes(G,'weight').values())

edge_df = pd.DataFrame({'source':start_edge, 'target':end_edge, 'weight':weight})

# Create full graph from edgelist
G = nx.from_pandas_edgelist(edge_df,edge_attr=True)

# Convert full graph to Bokeh network for node coordinates and instantiate Bokeh graph object
G_source = from_networkx(G, nx.spring_layout, scale=2, center=(0,0))
graph = GraphRenderer()

# Update loop where the magic happens
def update():
    selected_df = edge_df[(edge_df['weight'] >= slider.value[0]) & (edge_df['weight'] <= slider.value[1])]
    sub_G = nx.from_pandas_edgelist(selected_df,edge_attr=True)
    sub_graph = from_networkx(sub_G, nx.spring_layout, scale=2, center=(0,0))
    graph.edge_renderer.data_source.data = sub_graph.edge_renderer.data_source.    data
graph.node_renderer.data_source.data = G_source.node_renderer.data_source.    data
graph.node_renderer.data_source.add(node_size,'node_size')

def selected_points(attr,old,new):
    selected_idx = graph.node_renderer.selected.indices #does not work
    print(selected_idx)

# Slider which changes values to update the graph
slider = RangeSlider(title="Weights", start=0, end=1, value=(0.25, 0.75), step=0.10)
slider.on_change('value', lambda attr, old, new: update())

# Plot object which is updated
plot = figure(title="Meetup Network Analysis", x_range=(-1.1,1.1), y_range=(-1.1,1.1),
             tools = "pan,wheel_zoom,box_select,reset,box_zoom,crosshair", plot_width=800, plot_height=800)

# Assign layout for nodes, render graph, and add hover tool
graph.layout_provider = StaticLayoutProvider(graph_layout=positions)
graph.node_renderer.glyph = Circle(size='node_size')
graph.selection_policy = NodesOnly()
plot.renderers.append(graph)
plot.tools.append(HoverTool(tooltips=[('Name', '@index')]))

# Set layout
layout = column(slider,plot)

# does not work
#graph.node_renderer.data_source.on_change("selected", selected_points)

# Create Bokeh server object
curdoc().add_root(layout)
update()

Hi Hongyi, thank you so much for taking a look and helping out with this. Your solution worked perfectly!

Much appreciated,
Andrew

···

On Tuesday, December 4, 2018 at 4:10:37 PM UTC, Hongyi Xin wrote:

Ah, I think I can help you with this. I ran into similar problems before.

Instead of putting the listener on data_source, you should put it on

graph.node_renderer.data_source.selected.on_change(“indices”, selected_points)

``

This will trigger communication to the server.

Hongyi

On Tuesday, December 4, 2018 at 10:24:17 AM UTC-5, Andrew Gibbs-Bravo wrote:

Hi, I have just started using Bokeh and am very impressed with its functionality!

I am trying to get the index of selected nodes in a GraphRender object (similarly to how I was able to get the index of points on change for Circle plots) although have read through a lot of documentation have been unable to make it work.

I posted a question to stack overflow with additional information: https://stackoverflow.com/questions/53603046/get-selected-glyph-from-networkx-graph-in-bokeh

Please let me know if there are examples which are directly applicable which I missed or if there are other approaches to take. I have tried CustomJS but could not get the callback working.

Thanks in advance,

Andrew

Full example code is also pasted below for convenience:

import pandas as pd
import numpy as np
from bokeh.layouts import row, widgetbox, column
from bokeh.models import ColumnDataSource, CustomJS, StaticLayoutProvider, Oval, Circle
from bokeh.models import HoverTool, TapTool, BoxSelectTool, GraphRenderer
from bokeh.models.widgets import RangeSlider, Button, DataTable, TableColumn, NumberFormatter
from bokeh.io import curdoc, show, output_notebook
from bokeh.plotting import figure
import networkx as nx
from bokeh.io import show, output_file
from bokeh.plotting import figure
from bokeh.models.graphs import from_networkx, NodesAndLinkedEdges, EdgesAndLinkedNodes, NodesOnly

# Import / instantiate networkx graph
G = nx.Graph()

G.add_edge('a', 'b', weight=0.6)
G.add_edge('a', 'c', weight=0.2)
G.add_edge('c', 'd', weight=0.1)
G.add_edge('c', 'e', weight=0.7)
G.add_edge('c', 'f', weight=0.9)
G.add_edge('a', 'd', weight=0.3)

# Node Characteristics
node_name = list(G.nodes())
positions = nx.spring_layout(G)

node_size = [k*4 for k in range(len(G.nodes()))]
nx.set_node_attributes(G, node_size, 'node_size')
visual_attributes=ColumnDataSource(
    pd.DataFrame.from_dict({k:v for k,v in G.nodes(data=True)},orient='index'))

# Edge characteristics
start_edge = [start_edge for (start_edge, end_edge) in G.edges()]
end_edge = [end_edge for (start_edge, end_edge) in G.edges()]
weight = list(nx.get_edge_attributes(G,'weight').values())

edge_df = pd.DataFrame({'source':start_edge, 'target':end_edge, 'weight':weight})

# Create full graph from edgelist
G = nx.from_pandas_edgelist(edge_df,edge_attr=True)

# Convert full graph to Bokeh network for node coordinates and instantiate Bokeh graph object
G_source = from_networkx(G, nx.spring_layout, scale=2, center=(0,0))
graph = GraphRenderer()

# Update loop where the magic happens
def update():
    selected_df = edge_df[(edge_df['weight'] >= slider.value[0]) & (edge_df['weight'] <= slider.value[1])]
    sub_G = nx.from_pandas_edgelist(selected_df,edge_attr=True)
    sub_graph = from_networkx(sub_G, nx.spring_layout, scale=2, center=(0,0))
    graph.edge_renderer.data_source.data = sub_graph.edge_renderer.data_source.    data
graph.node_renderer.data_source.data = G_source.node_renderer.data_source.    data
graph.node_renderer.data_source.add(node_size,'node_size')

def selected_points(attr,old,new):
    selected_idx = graph.node_renderer.selected.indices #does not work
    print(selected_idx)

# Slider which changes values to update the graph
slider = RangeSlider(title="Weights", start=0, end=1, value=(0.25, 0.75), step=0.10)
slider.on_change('value', lambda attr, old, new: update())

# Plot object which is updated
plot = figure(title="Meetup Network Analysis", x_range=(-1.1,1.1), y_range=(-1.1,1.1),
             tools = "pan,wheel_zoom,box_select,reset,box_zoom,crosshair", plot_width=800, plot_height=800)

# Assign layout for nodes, render graph, and add hover tool
graph.layout_provider = StaticLayoutProvider(graph_layout=positions)
graph.node_renderer.glyph = Circle(size='node_size')
graph.selection_policy = NodesOnly()
plot.renderers.append(graph)
plot.tools.append(HoverTool(tooltips=[('Name', '@index')]))

# Set layout
layout = column(slider,plot)

# does not work
#graph.node_renderer.data_source.on_change("selected", selected_points)

# Create Bokeh server object
curdoc().add_root(layout)
update()