Search code examples
visualizationdata-visualizationbokehbokehjs

How to use the index of selected data from one figure, to plot something in another figure?


I have a scatter plot on one figure. I'd like to be able to select possibly multiple data points on the mentioned scatter plot, and plot a (possibly) multi-line timeseries chart on the other figure, based on the indexes of the selected data.

Pseudo code:

data = { x: [1,2,3], y: [1,2,3], time_series: [[1,2,3],[4,5,6],[7,8,9]] }
figure1 = scatter_plot(x, y, select_enabled=True)
figure2 = multi_line_timeseries(figure1.indexes_of_selected_points)
show([figure1, figure2])

So if the [1,1] data point (index 0) is selected on figure 1, then the [1,2,3] timeseries (index 0) is plotted on figure 2. If multiple points are selected, then multiple timeseries are plotted.

A restraint is that the HoloViews library can't be used, due to it not supporting my platform.

How can this be achieved?


Solution

  • Note: I have opted to not support simultaneous multiple timeseries plotting, though that would be a trivial extension of this.

    To use selected data point's index to determine what is to be plotted in another figure, you need to:

    • put the relevant data (i.e. x,y,timeseries in the example) on one or multiple ColumnDataSources;
      • I put the data to select and data that will be updated on different cds's, because I fear it might create a callback loop, though I've not tested this.
    • create a ColumnDataSource which will act as source for the second figure that plots the timeseries;
    • enable a selection tool, for example TapTool ('tap');
    • add a CustomJS callback to the ColumnDataSource that holds the selectable data points;
    • parametrize that callback with the ColumnDataSource that holds the timeseries data;
    • have the callback access indeces of selected data points;
    • have the callback make required changes to the second figure's ColumnDataSource;
    • call cds_of_2nd_figure.change.emit() before returning from the callback.

    Code to illustrate:

    cds = ColumnDataSource(data=dict(x=x,y=y,timeseries=timeseries))
    cds2 = ColumnDataSource(x_to_plot=[],u_to_plot=[])
    
    def selection_callback(d=cds,d2=cds2):
        last_selected_ix = cb_obj.selected.indices[0]
        timeserie = d.data['timeseries'][last_selected_ix]
        x_to_plot = timeserie['x']
        y_to_plot = timeserie['y']
        d2.data['x_to_plot'] = x_to_plot
        d2.data['y_to_plot'] = y_to_plot
        d2.changes.emit()
    
    # turn above function to js
    selection_callback = CustomJS.from_py_func( selection_callback )
    
    cds.callback = selection_callback
    

    When some figure selects data from cds, the timeseries[ix] timeserie will be plotted on the figure/s that plot cds2, where ix is the index of the last selected data point from cds.

    Relevant resource that has all the relevant information:

    https://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html#customjs-for-tools