CustomJS with Networkx Graph Edge Color Update

Hello
I have been trying to create a Network Graph while using a select to change the color of the edges with CustomJS. I am not sure whether it is either possible or not at all.

The idea for me was - upon user’s selection on the Dropdown - I was going to the change the edge rendered. I think because Multiline is a python class - it is not possible. In that scenario - what can I do?

Here is the code

import networkx as nx
from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import (BoxZoomTool, Circle, HoverTool,
                          MultiLine, Plot, Range1d, ResetTool)
from bokeh.models import CustomJS, Select
from bokeh.palettes import Spectral4
from bokeh.plotting import from_networkx

G = nx.karate_club_graph()

SAME_CLUB_COLOR, DIFFERENT_CLUB_COLOR = "black", "red"
edge_attrs = {}

for start_node, end_node, _ in G.edges(data=True):
    edge_color = SAME_CLUB_COLOR if G.nodes[start_node]["club"] == G.nodes[end_node]["club"] else DIFFERENT_CLUB_COLOR
    edge_attrs[(start_node, end_node)] = edge_color
nx.set_edge_attributes(G, edge_attrs, "edge_color_by_club")

color_list = ['black', 'red', 'blue']
counter = 0
for start_node, end_node, _ in G.edges(data=True):
    edge_color = color_list[int(counter % 3)]
    edge_attrs[(start_node, end_node)] = edge_color
    counter += 1
nx.set_edge_attributes(G, edge_attrs, "edge_color_by_random")

# Show with Bokeh
plot = Plot(width=1200, height=1200,
            x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1))
plot.title.text = "Graph Interaction Demonstration"

node_hover_tool = HoverTool(tooltips=[("index", "@index"), ("club", "@club")])
plot.add_tools(node_hover_tool, BoxZoomTool(), ResetTool())

graph_renderer = from_networkx(G, nx.spring_layout, scale=1, center=(0, 0))

graph_renderer.node_renderer.glyph = Circle(size=15, fill_color=Spectral4[0])
graph_renderer.edge_renderer.glyph = MultiLine(line_color="edge_color_by_club", line_alpha=0.8, line_width=1)
plot.renderers.append(graph_renderer)


dropdown_list = ['club','random']
x_select = Select(
    options=dropdown_list,
    value=dropdown_list[0],)

callback = CustomJS(args=dict(renderer=plot.renderers[0].edge_renderer), code="""
    const data = renderer.data_source.data;
    const value = cb_obj.value; // Get the selected value from the dropdown
    
    if (value === 'club') {
        renderer.data_source.data.line_color = data.edge_color_by_club;
    } else if (value === 'random') {
        renderer.data_source.data.line_color = data.edge_color_by_random;
    }
    console.log(renderer.data_source.data);
    
    renderer.data_source.change.emit(); // Trigger the update
""")
x_select.js_on_change("value", callback)

layout = column(x_select, plot)

show(layout)

In the CustomJS, you’re assigning a new line_color field to the source, which isn’t what you want. You want to the the glyph to derive its line_colors from a field based on the value of the select.

callback = CustomJS(args=dict(renderer=plot.renderers[0].edge_renderer), code="""
    const data = renderer.data_source.data;
    const value = cb_obj.value; // Get the selected value from the dropdown
    
    if (value === 'club') {
        //renderer.data_source.data.line_color = data.edge_color_by_club;
        renderer.glyph.line_color = {'field':'edge_color_by_club'}
    } else if (value === 'random') {
        //renderer.data_source.data.line_color = data.edge_color_by_random;
        renderer.glyph.line_color = {'field':'edge_color_by_random'}
    }
    console.log(renderer.data_source.data);
    
    renderer.data_source.change.emit(); // Trigger the update
""")

An aside/alternative → I find CustomJSTransform super handy for theming stuff like this - it prevents you from having “color” fields in your CDS.

1 Like