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