Change a second plot by clicking glyph

Hello,
I feel like this should be straightforward but I haven’t found the method yet. I would like to produce a standalone page showing 2 plots. Plot 1 is a bar chart and when an individual bar is clicked, plot 2 would change to a corresponding plot. Is there an existing bokeh technique that would do this or do I need a CustomJS callback?
Thanks

@RobM

What you’re describing at a high-level seems most similar to the bokeh framework for selection events. As you want a standalone document, you will need to write callbacks in JavaScript for the specific actions that happen when a selection is made.

As a starting point see the CustomJS for Selections of the bokeh user’s guide. It is towards the bottom of the callbacks page at the following URL and includes several examples of selection events; although none are exactly your use case, it should provide a good starting point …

1 Like

@RobM ,

Everything @_jm said, plus here is an example! There’s a non-obvious approach to changing the layout child in the CustomJS, so I thought this might be useful (see this thread for background info).

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")
child_figs = [figure(title="Child 0"), figure(title="Child 1"), figure(title="Child 2")]
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)
3 Likes

Hi @carolyn, i am looking for further drill down of child graph. suppose if i click on child graph points, it should either generate another child graph or just provide output of that graph data. is it possible to do it? I searched on discourse but didn’t find any example of further drill down of child graph.

Thanks in Advance.

Can you further explain what you want to do? When you click on a child graph point, should a new child graph replace the one you clicked on? Or appear in a new location? From your description, it sounds like you may just need three children in your row, but maybe I’m not understanding yet.

@carolyn yes, When you click on a child graph point, a new child graph should appear in a new location.
Lets say there is a bar graph that is main graph. when you click on bar, it will generate a scatter plot. (till this point I have already done) Now I want to click on any point on scatter plot and it should generate another child graph. Please let me know if it is not clear.

It sounds to me like you would just repeat the arrangement. Your scatter plot would also have a callback, changing another element of the row.children to be one of its designated child plots.

1 Like

Hi @carolyn, I tried but couldn’t figure out how to pick the data for child figure from call back. I created another callback but couldn’t attach it with 2nd child. Please find below details that i tried to accomplish but getting error as Figure’ object has no attribute ‘selected’

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

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,tools="tap")]
child_figs1 = [figure(title="Child 10", plot_width=200, plot_height=200)]
my_row = row(main_fig, child_figs[0], child_figs1[0])

change_child_callback = CustomJS(args=dict(my_row=my_row, child_figs=child_figs), code="""
  console.log(cb_obj.indices)
  var children = [...my_row.children]
  children[1] = child_figs[cb_obj.indices[0]]
  my_row.children = children 
  """)

change_child_callback1 = CustomJS(args=dict(my_row=my_row, child_figs=child_figs1), code="""
  console.log(cb_obj.indices)
  var children = [...my_row.children]
  children[2] = child_figs1[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].circle(x=x, y=y0, line_color="orange",size=20)
child_figs1[0].selected.js_on_change('indices', change_child_callback1)
child_figs1[0].line(x=x, y=x, line_color="orange")

show(my_row)

Ah! This is because in this little example, child_figs[0] does not use a ColumnDataSource. The attribute selected is on the CDS, not the plot.

Here’s your example back, with some changes. child_fig_cds is now the source for the middle plot. When testing, click the leftmost circle in the first two plots to get to the third one.

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

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,tools="tap")]
child_figs1 = [figure(title="Child 10", plot_width=200, plot_height=200)]
my_row = row(main_fig, Div(), Div())

change_child_callback = CustomJS(args=dict(my_row=my_row, child_figs=child_figs), code="""
  console.log(cb_obj.indices)
  var children = [...my_row.children]
  children[1] = child_figs[cb_obj.indices[0]]
  my_row.children = children 
  """)

change_child_callback1 = CustomJS(args=dict(my_row=my_row, child_figs=child_figs1), code="""
  console.log(cb_obj.indices)
  var children = [...my_row.children]
  children[2] = 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_fig_cds = ColumnDataSource(data={'x': x, 'y': y0})
child_figs[0].circle(x="x", y="y", source=child_fig_cds, line_color="orange", size=20)

child_fig_cds.selected.js_on_change('indices', change_child_callback1)
child_figs1[0].line(x=x, y=x, line_color="orange")

show(my_row)

You can see, however, that this approach could become very complicated if you have more than a few plots.

2 Likes

Thanks @carolyn for your guidance. I will try to experiment on this.

1 Like