Change GraphRender() position from client side (JS Callback)

Hi!
Is there a way to do the following inside a JS code_text:
graph_renderer.layout_provider = StaticLayoutProvider(graph_layout=graph_layout)
I would like to change the layout_provider (the position of nodes) from client’s side. Not from server. In other words, I want to replace the graph_layout with a new one. I tried several ways, but the plot is not changing. Even if I used plot.change.emit(); graph_rendererplot.change.emit(); inside the JS code-text.
Any ideas?
Thank you in advance

@ThemisKoutsellis it’s much easier to offer help if you share some of the things you tried, and really a complete Minimal Reproducible Example.

@Bryan you are right! Sorry about that. Here is a sample of code. Hope this helps. Thank you in advance! :slight_smile:

# Bokeh_question.py
# -*- coding: utf-8 -*-
#-----------------------------------------------------------------------------
#>> Imports:
#-----------------------------------------------------------------------------

# ΝetworkΧ

import networkx as nx

# Bokeh library

from bokeh.io import output_file
from bokeh.plotting import figure, show
from bokeh.layouts import layout
from bokeh.models.graphs import from_networkx, NodesAndLinkedEdges, EdgesAndLinkedNodes

from bokeh.models import (
    ColumnDataSource,
    CustomJS,
    Circle,
    HoverTool,
    MultiLine,
    Range1d,
    ResetTool,
    TapTool,
    BoxSelectTool,
    Select,
    PanTool,
    WheelZoomTool,
    SaveTool,
    LabelSet,
)

#-----------------------------------------------------------------------------
#<< Imports.
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
#>> NetworX graph creation:
#-----------------------------------------------------------------------------

#define the graph's elements:
source = ['C1', 'C1', 'C1','C3','C2','C2','C2','C4','C5']
target = ['C3', 'C2','C4','C4','C3','C4','C1','C3', 'C3']
weight = [1,0.3,1,1,1,0.8, 0.4, 1, 1]

nodes_disc = {
    'C1': 'C1 description',
    'C2': 'C2 description',
    'C3': 'C3 description',
    'C4': 'C4 description',
    'C5': 'C5 description',
}

# create list of tuples with all necessary elements for graph
edges_list = list(zip(source, target, weight))

# create netwokx graph:
nx_graph = nx.DiGraph()
nx_graph.add_weighted_edges_from(edges_list)

#-----------------------------------------------------------------------------
#<< NetworX graph creation.
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
#>> Output HTML file:
#-----------------------------------------------------------------------------

# The figure will be rendered in a static HTML file called foo.html
output_file('FooF.html', title='Foo')

#-----------------------------------------------------------------------------
#<< Output HTML file.
#-----------------------------------------------------------------------------


#-----------------------------------------------------------------------------
#>> Figure:
#-----------------------------------------------------------------------------

plot = figure(
    plot_width=400,
    plot_height=350,
    x_range=Range1d(-3,5),
    y_range=Range1d(-3,5),
    tools=[],
)

plot.grid.grid_line_color = None

# plot title 
plot.title.text = "Foo"
plot.title.text_font = "times"

# Plot toolbar tools:
node_hover_tool = HoverTool()
plot_wheel_zoom = WheelZoomTool()
plot_pan_tool = PanTool()
plot.add_tools(
    plot_pan_tool,
    node_hover_tool,
    plot_wheel_zoom,
    BoxSelectTool(),
    ResetTool(),
    TapTool(),
    SaveTool(),
)

# active tools:
plot.toolbar.active_inspect = node_hover_tool
plot.toolbar.active_scroll = plot_wheel_zoom
plot.toolbar.active_drag = plot_pan_tool
plot.toolbar.active_tap = None
plot.toolbar.logo = None

# define some stitic nodes. e.g. Input or Output nodes:
node_initial_pos = {'C1':(0, 0), 'C2':(0, 3), 'C5':(3,0)}

##### graph renderer

# Different layout positions
bipartite_pos= nx.bipartite_layout(
    nx_graph,
    ['C1', 'C2'],
    align='vertical',
    scale=2,
    center=(0, 0),
    aspect_ratio=1.33,
)

circular_pos = nx.circular_layout(
    nx_graph,
    scale=2,
    center=(0, 0),
    dim=2,
)

kamada_kawai_pos = nx.kamada_kawai_layout(
    nx_graph,
    weight='weight',
    scale=2,
    center=(0, 0),
    dim=2,
)

planar_pos = nx.planar_layout(
    nx_graph,
    scale=2,
    center=(0, 0),
    dim=2,
)

random_pos = nx.random_layout(
    nx_graph,
    center=(0, 0),
    dim=2,
)

spring_pos = nx.spring_layout(
    nx_graph,
    scale=2,
    center=(0, 0),
    pos=node_initial_pos,
    fixed=['C1', 'C2', 'C5'],
    iterations=2,
    k=0.1,
)

pos_dict = planar_pos

graph_renderer = from_networkx(
    nx_graph,
    pos_dict,
)

## node sub-renderer

graph_renderer.node_renderer.glyph = Circle(size=20, fill_color="#2b83ba", line_width=1.5, fill_alpha=0.7)
graph_renderer.node_renderer.nonselection_glyph = Circle(size=10, fill_color='#DAF7A8')
graph_renderer.node_renderer.selection_glyph = Circle(size=22, fill_color='#E74C3C')
graph_renderer.node_renderer.hover_glyph = Circle(size=15, fill_color='#DAF7A6')

## edge sub-renderer
graph_renderer.edge_renderer.glyph = MultiLine(line_color="grey", line_alpha=0.8, line_width=1.25)
graph_renderer.edge_renderer.selection_glyph = MultiLine(line_color='#E74C3C', line_width=2)
graph_renderer.edge_renderer.nonselection_glyph = MultiLine(line_color='#DAF7A8', line_width=1)
graph_renderer.edge_renderer.hover_glyph = MultiLine(line_color='#DAF7A6', line_width=2) # #abdda4

# Hover policy
graph_renderer.selection_policy = NodesAndLinkedEdges()
graph_renderer.inspection_policy = EdgesAndLinkedNodes()

# append graph renderer to plot figure
plot.renderers.append(graph_renderer)

# Adding concept names to Figure:

node_names = list(graph_renderer.layout_provider.graph_layout.keys())
_xs, _ys = map(list, zip(*graph_renderer.layout_provider.graph_layout.values()))
node_labels_source = ColumnDataSource(
    data=dict(
        xs=_xs,
        ys=_ys,
        names=node_names,
    )
)

# Node labels layout:

node_labels = LabelSet(
    x='xs',
    y='ys',
    text='names',
    x_offset=0,
    y_offset=-5,
    source=node_labels_source,
    render_mode='canvas',
    text_font = 'times',
    text_font_size='9pt',
    text_font_style='normal',
    text_color='black',
    text_align='center',
    level='glyph',
     angle=0,
)

plot.add_layout(node_labels)

#>> Widgets related to FCM display layout:
##########################################

_layout_options = [
    'spring_layout',
    'bipartite_layout',
    'circular_layout',
    'kamada_kawai_layout',
    'planar_layout',
    'random_layout',
]

nodes_display_layout_select = Select(
    title="Display layout:",
    value="planar_layout",
    options=_layout_options,
    width=300,
    height=50
)

nodes_display_layout_select_args = dict(
    graph_renderer=graph_renderer,
    plot=plot,
    bipartite_pos=bipartite_pos,
    circular_pos=circular_pos,
    kamada_kawai_pos=kamada_kawai_pos,
    planar_pos=planar_pos,
    random_pos=random_pos,
    spring_pos=spring_pos,
)

nodes_display_layout_select_str = """
    graph_renderer.layout_provider.graph_layout = spring_pos;  
    graph_renderer.change.emit();
    plot.reset.change();
"""

nodes_display_layout_select_callback = CustomJS(
    args=nodes_display_layout_select_args,
    code=nodes_display_layout_select_str,
)

nodes_display_layout_select.js_on_change("value", nodes_display_layout_select_callback)

#<< Widgets related to FCM display layout. 
##########################################

## Final Layout:

html_layout = layout(
    [
    [plot],
    [nodes_display_layout_select ],
    ]
)

#Show HTML Layout:
show(html_layout)

Does anyone has a solution for this problem?
Is it a bug or something I miss?
Is it possible to change the JScript object from client perspective or not?
I have attached the code above.
Thanks in advance! :slight_smile: :slightly_smiling_face:

@ThemisKoutsellis your reply escaped my notice. There is no plot.reset.change() function and you will see an error about that if yo look in your browser JavaScript console. I would expect the entire callback to be

nodes_display_layout_select_str = """
    graph_renderer.layout_provider.graph_layout = spring_pos;
"""

If I add a debugger statement I can see the values getting changed but the plot is not updating. I will have to investigate more deeply when I can spare more time (hopefully this weekend).

1 Like

@ThemisKoutsellis At this point I think there must just be a bug, e.g. some missing event plumbing that is not hooked up where it should be. Please file a GitHub Issue with these details.