Search code examples
python-3.xcallbackbokehinteractivetap

JS callback for tap tools in bokeh


I want to make an interactive graph. In the first graph, using the "tap" widget, the values "x" shown by the "hover" widget are selected, the values d(:,x) are plotted on the second graph. When you click the "tap" widget again to another place in Graph 1, graph 2 is updated. But I do not know how to write a callback to the "tap" widget. An example of what I am looking for is this

here

the presented code creates two graphs, but does not update the second graph:

import numpy as np
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.plotting import figure, show
from bokeh.layouts import row
import os
from numpy.lib.function_base import select
import pandas as pd
from bokeh.models import ColumnDataSource, CustomJS, TapTool


N = 500
x = np.linspace(0, 10, N)
y = np.linspace(0, 10, N)
xx, yy = np.meshgrid(x, y)
d = np.sin(xx)*np.cos(yy)
len_d=len(d)

plot1 = figure(tooltips=[("x", "$x{0}"), ("y", "$y"), ("value", "@image")],
             plot_height=350, plot_width=400,title="Graph 1 d",tools = "tap")
plot2=figure(plot_height=350, plot_width=400, title="Graph 2 d(tap selection(x))", 
              tools="pan,box_select,crosshair,box_zoom,reset,save,wheel_zoom,hover") 

#f = d[:,select]
lines = plot2.line(x = 'x', y = 'y', source = ColumnDataSource({'x': d[0] , 'y': -np.arange(len_d)}))
lines.visible = False
plot1.x_range.range_padding = plot1.y_range.range_padding = 0

data = dict(
    image=[d], pattern=['smooth ramp'],
    x=[0], y=[5], dw=[20], dh=[10]
)

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['x'] = d[selected_index]
            lines.data_source.change.emit(); 
          }'''

cds = ColumnDataSource(data=data)
plot1.image(source=cds, x=0, y=-len(d), dw=len(d[0]), dh=len(d),palette='Spectral11', level="image")
plot1.grid.grid_line_width = 0.5
plot1.select(TapTool).callback = CustomJS(args = {'lines': lines, 'd': d}, code = code)
plots = row(plot1, plot2)
show(plots)

I really appreciate any help you can provide


Solution

  • Here is a solution which selects data from the image.

    import numpy as np
    import pandas as pd
    
    from bokeh.plotting import figure, output_notebook, show
    from bokeh.layouts import row
    from bokeh.models import ColumnDataSource, CustomJS
    output_notebook()
    
    N = 500
    x = np.linspace(0, 10, N)
    y = np.linspace(0, 10, N)
    xx, yy = np.meshgrid(x, y)
    d = np.sin(xx)*np.cos(yy)
    len_d=len(d)
    
    data = dict(
        image=[d], pattern=['smooth ramp'],
        x=[0], y=[5], dw=[20], dh=[10]
    )
    # plot 1
    cds_p1 = ColumnDataSource(data=data)
    plot1 = figure(
        plot_height=350,
        plot_width=400,
        title="Graph 1 d",
        x_range =(0,500),
        y_range =(-500, 0),
        tooltips=[("x", "$x{0}")]
    )
    plot1.grid.grid_line_width = 0.5
    plot1.image(
        source=cds_p1,
        x=0,
        y=-len_d,
        dw=len_d,
        dh=len_d,
        palette='Spectral11',
        level="image"
    )
    # plot 2
    y_index = -np.arange(len_d)
    cds_p2 = ColumnDataSource(
        {'x': d[0] , 'y':-np.arange(len_d) }
    )
    
    plot2=figure(
        plot_height=350,
        plot_width=400,
        title="Graph 2 d(tap selection(x))",
        x_range =(-1,1),
        y_range =(-500, 0),
        tools="") 
    plot2.line(x = 'x', y = 'y', source=cds_p2)
    
    # important JS part
    callback = CustomJS(
        args=dict(cds_original=cds_p1, cds_p2=cds_p2),
        code="""
        // console.log('Tap event occurred at x-position: ' + cb_obj.x)
        
        const data = cds_original.data
        const next = cds_p2.data
        var xpos = Math.round(cb_obj.x)
        
        console.log((xpos-1)*500)
        console.log(data['image'])
        
        var x = []
        for(var i=xpos; i<500*500; i+=500){
            console.log(data['image'][0][i])
            x.push(data['image'][0][i])
        }
        next['x'] = x
        console.log(next)
        cds_p2.change.emit()
    """)
    plot1.js_on_event('tap', callback)
    
    plots = row(plot1, plot2)
    show(plots)
    

    I think this is not the final solution but maybe this brings you one step closer.