Hi!
I’m trying to do some network visualization. I want to add callback: if some nodes are selected, change their “selected” attribute (and do lots of other stuff with their neighbors, but it is not relevant for this problem). I also need to obtain their “index” attribute form source networkx graph.
This is minimal example:
#!/usr/bin/env python3
import random as rnd
import networkx as nx
from bokeh.plotting import figure, curdoc, from_networkx
from bokeh.models import Hex
from bokeh.transform import linear_cmap
from functools import partial
def change_selection(positions,nodes_data_source,value_to_selected):
selected_indicies = []
nodes_data = nodes_data_source.data
patch_data = []
for p in positions:
node_index = nodes_data["index"][p]
selected_indicies.append(node_index)
patch_data.append((p,value_to_selected))
print(patch_data) # debuging print
nodes_data_source.patch({'selected': patch_data})
return selected_indicies
def update_renderers_according_selection(attr, old, new, this_renderer):
old_set = set(old)
new_set = set(new)
added_set = new_set.difference(old_set)
removed_set = old_set.difference(new_set)
# indicies removed from selection and added to selection
removed = change_selection(removed_set,this_renderer.node_renderer.data_source,0)
added = change_selection(added_set,this_renderer.node_renderer.data_source,1)
# ... other manipulation with removed and added nodes, irelevant for this example
# generate some random nodes like (some id, {"coor":(x,y), "selected":0})
random_nodes = [(i+10,{"coor":(rnd.randrange(-100,100),rnd.randrange(-100,100)), "selected":0}) for i in range(3000)]
# create networkx graph
g = nx.Graph()
g.add_nodes_from(random_nodes)
layout = {n:g.nodes[n]["coor"] for n in g.nodes()}
# blue unselected and red selected nodes
# the mapper is used because in the whole app are more levels of "selected" attribute value (like selected=1, neighbor_of_selected=2 ....)
mapper_nodes = linear_cmap(field_name='selected', palette=('#a1cdec', '#f20808') ,low=0 ,high=1)
# create graph renderer from networkx graph
graph_renderer = from_networkx(g,layout)
graph_renderer.node_renderer.glyph = Hex(fill_color=mapper_nodes,line_color=None)
graph_renderer.node_renderer.nonselection_glyph = Hex(fill_color=mapper_nodes,line_color=None)
nodes_data_source = graph_renderer.node_renderer.data_source
# nodes selected.on_change callback
nodes_data_source.selected.on_change("indices",
partial(update_renderers_according_selection,
this_renderer=graph_renderer))
plot = figure(x_range=(-100,100),y_range=(-100,100),tools="lasso_select,pan,wheel_zoom")
plot.renderers.append(graph_renderer)
curdoc().add_root(plot)
It somehow works, but if there are lots of nodes (like in this example), there is weird problem during selecting them - all the nodes sometimes collapsed to the center of the plot. It returns back if something happens, like zoom, selecting or move, but after that, sometimes are selected the right nodes, but sometimes are all selected/unselected. I have no idea why.
Can you help me please? What is the problem?