Need to generate multiple child graphs based on tap click in main scattered graph

Hi All,

Need your help in generating multiple child graphs based on tap click on main graph. Below is the code which generates 1 child line graph if i click on main graph’s one circle. is it possible to generate multiple line graphs based on one single tap in main graph.

from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.layouts import row

a = [0, 1, 2]
b = [5, 7, 2]
colors = ["orange", "green", "purple"]
main_fig_cds = ColumnDataSource(data={'a': a, 'b': b, 'colors': colors})

x =  [0, 1, 2, 3, 4, 5, 6]
y0 = [5, 2, 5, 2, 7, 3, 5]
y1 = [1, 3, 2, 4, 3, 5, 4]
y2 = [9, 6, 8, 3, 5, 9, 8]

main_fig = figure(tools="tap", plot_width=400, plot_height=400)
child_figs = [figure(title="Child 0", plot_width=400, plot_height=200), 
              figure(title="Child 1", plot_width=400, plot_height=200), 
              figure(title="Child 2", plot_width=400, plot_height=200)]
my_row = row(main_fig, child_figs[0])

change_child_callback = CustomJS(args=dict(my_row=my_row, child_figs=child_figs), code="""
  console.log(cb_obj.indices)
  // see https://discourse.bokeh.org/t/customjs-callback-to-modify-layout-organize-report/3777/2
  var children = [...my_row.children]
  children[1] = child_figs[cb_obj.indices[0]]
  my_row.children = children 
  """)

main_fig.circle(x='a', y='b', size=30, color='colors', source=main_fig_cds)
main_fig_cds.selected.js_on_change('indices', change_child_callback)
child_figs[0].line(x=x, y=y0, line_color="orange")
child_figs[1].line(x=x, y=y1, line_color="green")
child_figs[2].line(x=x, y=y2, line_color="purple")

show(my_row)

@carolyn any suggestions. Thanks.

The example here is not really “generating” line graphs, just adding plots that already exist to a layout. You can of course extend the idea and the callback such that it adds other pre-existing plots to other sections of the layout, but maybe that’s not what you’re asking.

Can you show what you’ve tried so far? That would help determine what the next step would be.

Hi @carolyn, sorry for confusion due to my sentence. Below is another example. i have one bar chart so when i click one bar chart it should produce 2 childs(layout is already in place) Plot2 and Plot3. so i have Plot1(main plot), Plot2,Plot3(2 child plots). Plot3 is commented for now. I am not able to produce both plot2, plot3 at same time when i click on bar of plot1.

from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS, TapTool, Div
from bokeh.plotting import figure, show
import numpy as np

source_bars = ColumnDataSource({'x': [1, 2, 3], 'y': [2, 4, 1] , 'colnames': ['A', 'B', 'C']})
lines_y = [np.random.random(5) for i in range(3)]

plot1 = figure(tools = 'tap',plot_width=200, plot_height=200)
bars = plot1.vbar(x = 'x', top = 'y', source = source_bars, bottom = 0, width = 0.5)

plot2 = figure(plot_width=200, plot_height=200)
lines = plot2.line(x = 'x', y = 'y', source = ColumnDataSource({'x': np.arange(5), 'y': lines_y[0]}))
lines.visible = False

# plot3 = figure(plot_width=200, plot_height=200)
# lines = plot3.circle(x = 'x', y = 'y', source = ColumnDataSource({'x': np.arange(5), 'y': lines_y[0]}))
# lines.visible = False

code = '''if (cb_data.source.selected.indices.length > 0){
            lines.visible = true;
            var selected_index = cb_data.source.selected.indices[0];
            lines.data_source.data['y'] = lines_y[selected_index]
            lines.data_source.change.emit(); 
          }'''

plots = row(plot1, plot2, plot3)
plot1.select(TapTool).callback = CustomJS(args = {'lines': lines, 'lines_y': lines_y}, code = code)
show(plots)

It looks like the only issue here is that you’re re-using the variable name lines, overwriting its previous value. Here’s your example back, with the plot2 version called p2_lines and the plot3 version called p3_lines.


from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS, TapTool, Div
from bokeh.plotting import figure, show
import numpy as np

source_bars = ColumnDataSource({'x': [1, 2, 3], 'y': [2, 4, 1], 'colnames': ['A', 'B', 'C']})
lines_y = [np.random.random(5) for i in range(3)]

plot1 = figure(tools='tap',plot_width=200, plot_height=200)
bars = plot1.vbar(x='x', top='y', source=source_bars, bottom = 0, width = 0.5)

plot2 = figure(plot_width=200, plot_height=200)
p2_lines = plot2.line(x='x', y='y', source=ColumnDataSource({'x': np.arange(5), 'y': lines_y[0]}))
p2_lines.visible = False

plot3 = figure(plot_width=200, plot_height=200)
p3_lines = plot3.circle(x='x', y='y', source=ColumnDataSource({'x': np.arange(5), 'y': lines_y[0]}))
p3_lines.visible = False

code = '''if (cb_data.source.selected.indices.length > 0){
            p2_lines.visible = true;
            p3_lines.visible = true;
            var selected_index = cb_data.source.selected.indices[0];
            lines.data_source.data['y'] = lines_y[selected_index]
            lines.data_source.change.emit(); 
          }'''

plots = row(plot1, plot2, plot3)
plot1.select(TapTool).callback = CustomJS(args={'p2_lines': p2_lines, 'p3_lines': p3_lines, 'lines_y': lines_y}, code=code)
show(plots)
1 Like

Thanks @carolyn. This is what I was trying to achieve. I made a little change in customjs code as below as plot2 and plot3 were not updating with click on plot1 bar. once again thanks for your guidance. I am still exploring different things in bokeh and your help is highly appreciated.

            p2_lines.data_source.data['y'] = lines_y[selected_index]
            p3_lines.data_source.data['y'] = lines_y[selected_index]
            p2_lines.data_source.change.emit(); 
            p3_lines.data_source.change.emit();
1 Like