How to get linked nodes in Bokeh GraphRenderer?

I like to change the style (fill_color) of linked nodes (adjacent nodes) after a node selection. After selecting “C” I would like to change the style of “E” and “F”.

How to get the linked nodes as object to change the style?

Result of the code below:

from bokeh.plotting import figure, show, save, output_file
from bokeh.models import GraphRenderer, StaticLayoutProvider, Rect, HoverTool, TapTool, MultiLine, ColumnDataSource
from bokeh.models.graphs import NodesAndLinkedEdges

data = {'index':   [1, 2, 3, 4, 5, 6, 7, 8],
        'name': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'],
        'x':    [10, 3, 10, 17,  5,  9, 13, 17],
        'y':    [5, 10, 10, 10, 13, 13, 13, 13]}

source = ColumnDataSource(data)

plot = figure(width=800, height=600, title="Graph layout demonstration", x_range=(-0.5,20.5), y_range=(20.5,0.5), tools="", toolbar_location=None)

graph = GraphRenderer()
graph.node_renderer.data_source = source

# NODES
rect_props = dict(height=1.75, width=3.75, line_width=0, border_radius=dict(top_left=5,top_right=5,bottom_left=5,bottom_right=5))
graph.node_renderer.glyph = Rect(fill_color="royalblue", **rect_props)
graph.node_renderer.hover_glyph = Rect(fill_color='#961B47', **rect_props)
graph.node_renderer.selection_glyph = Rect(fill_color='#961B47', **rect_props)
graph.node_renderer.nonselection_glyph = Rect(fill_color='lightgray', **rect_props)

# EDGES
graph.edge_renderer.data_source.data = dict(start=[1,1,1,3,4,4,3,5,2], end=[5,6,4,6,7,8,5,6,5])
graph.edge_renderer.glyph = MultiLine(line_width=1, line_color="lightgray", line_alpha=1.0)
graph.edge_renderer.hover_glyph = MultiLine(line_width=3, line_color="#961B47", line_alpha=1.0)
graph.edge_renderer.selection_glyph = MultiLine(line_width=3, line_color="#961B47", line_alpha=1.0)
graph.edge_renderer.nonselection_glyph = MultiLine(line_width=1, line_color="lightgray", line_alpha=1.0)

# position the nodes
custom_layout = dict( zip(data['index'] , zip(data['x'], data['y']) ))
graph.layout_provider = StaticLayoutProvider(graph_layout=custom_layout)

graph.selection_policy = NodesAndLinkedEdges()
graph.inspection_policy = NodesAndLinkedEdges()

# render the graph
plot.renderers.append(graph)

# add labels to nodes
text_props = dict(source=source, text_font_size="12px", text_color='#ffffff', text_font_style="bold", text_align="center", text_baseline="middle")
txt = plot.text(x='x', y='y', text='name', **text_props)
txt.selection_glyph = None
txt.nonselection_glyph = None

# add tools to plot
hover = HoverTool(renderers=[graph], tooltips=[("Index", "@index")])
plot.add_tools(hover, TapTool())

# display the plot
output_file("test-linked-nodes.html")
save(plot)
show(plot)

If you don’t need the edge selected as well, there is the NodesAndAdjacentNodes selection policy. AFAIK there is not currently a “Nodes and linked edges and adjacent nodes” selection policy (and no way to “combine” multiple policies) [1], so if you do also require edges then the will probably require some manual selection adjustment in a CustomJS for the initial selection, I think.


  1. either/both of those seem like reasonable ideas for future development so feel free to open a GitHub Issue ↩︎

Thanks Bryan. I actually need to select the nodes, edges and linked nodes, so I need a policy like NodesAndLinkedEdgesAndAdjacentNodes.

I followed the development discussions here https://github.com/bokeh/bokeh/issues/9036 and I really like the thoughts.

Do you have a tip or a code example, how to achieve my goal by the usage of CustomJS?

Ah right and there was much more involved discussion here as well

Selection / Inspection Policies · bokeh/bokeh · Discussion #11952 · GitHub

Unfortunately this larger selection refactoring has not been undertaken yet. I am no longer involved in day to day coding on Bokeh so I can’t really speculate when it might be, either.

Do you have a tip or a code example, how to achieve my goal by the usage of CustomJS?

Nothing super detailed, I am afraid. But I had in mind that if you use the NodesAndAdjacentNodes policy above then a subsequent CustomJS callback on the node selection could look for all the edges that have both ends in the node selection, and set all of those on the edge selection. For reference, the kind of code for searching and setting selections in the builtin policies is here, e.g.

bokeh/bokehjs/src/lib/models/graphs/graph_hit_test_policy.ts at eb282dc2d57df613f3927e67cdb16d870956f20b · bokeh/bokeh · GitHub

Alternatively, if you use the NodesAndLinkedEdges then you could trigger a CustomJS off the edge selection event, and then update the node selection with all the endpoints of all the selected edges. That actually seems like it might be much simpler.

Of course I’m familar with #11952 :face_with_monocle:

Your approach sounds good, I like it. I’ll give it a try.

1 Like