Tap on Scatter to Show additional Data in Bar Plot

Hello and thank you so much for developing Bokeh!
I posted this question also on Stackoverflow:

I have a DataFrame which holds the data I want to plot in a scatter plot. The DataFrame has far more information than only the columns needed for the scatter x and y data.

I want to show the additional data as hover (which is not the problem) but also if I Tap-select one data point in the scatter the additional data in the other columns of the ColumnDataSource shall be plottet in a Bar Plot.

My problem is to get the bar plot to accept the data stored in the one selected row of the ColumnDataSource. Everything I have seen uses column based data to feed it to the bar plot.

I stripped my Problem down to a minimalistic code snippet:

SRC = ColumnDataSource(df)

def Plot(doc):
    
    def callback(event):
        SELECTED = SRC.selected.indices
        bplot = make_bPlot(SELECTED)
        
    def make_bPlot(selected):
        #Here is my question:
        #How to feed the row-wise data of the SRC to the barplot?
        b = figure(x_range=["cat1", "cat2"])
        b.vbar(x=["cat1", "cat2"], top=["cat1", "cat2"], source=SRC)
        
        return b
        
    TOOLTIPS = [
        ("x", "@x"),
        ("y", "@y"),
        ("Category 1", "@cat1"),
        ("Category 2", "@cat2")]

    TOOLS="pan,wheel_zoom,zoom_in,zoom_out,box_zoom,reset,tap"
    cplot = figure(tools = TOOLS, tooltips=TOOLTIPS)
    cplot.circle("x", "y", source=SRC)
    
    bplot = make_bPlot(None) # init
       
    
    taptool = plot.select(type=TapTool)
    cplot.on_event(Tap, callback)
    
    
    layout = column(cplot, bplot)
    doc.add_root(layout)

@Creaner

An example based on your code excerpt follows. Note, it is hardcoded in terms of a number of things to illustrate the main concepts, so you’ll have to adapt to be more general.

Also, note that the graphics are being updated by patching the source of the bar plot; your original example appears to attempt to create new figures in the callback, which I don’t think would work in its current implementation and is generally not a good idea.

I hope this helps.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
"""
import numpy as np
import pandas as pd

from bokeh.models import ColumnDataSource, TapTool
from bokeh.events import Tap

from bokeh.plotting import figure
from bokeh.layouts import column

from bokeh.io import curdoc

x = np.random.randn(1001)
y = np.random.randn(1001)

cat1 = np.random.rand(1001) * 10.0
cat2 = np.random.rand(1001) * 10.0

data = np.column_stack((x,y,cat1,cat2))

df = pd.DataFrame(data=data, columns=('x','y','cat1','cat2'))
SRC = ColumnDataSource(df)


TOOLTIPS = [
    ("x", "@x"),
    ("y", "@y"),
    ("Category 1", "@cat1"),
    ("Category 2", "@cat2")]

TOOLS="pan,wheel_zoom,zoom_in,zoom_out,box_zoom,reset,tap"
cplot = figure(tools = TOOLS, tooltips=TOOLTIPS)
cplot.circle("x", "y", source=SRC)

bSource = ColumnDataSource(dict(x=['cat1','cat2'], top=[None]*2))
bplot = figure(x_range=('cat1','cat2'))
bplot.vbar(x='x', top='top', source=bSource)

def callback(event):
    SELECTED = SRC.selected.indices
    print("SELECTED {:}".format(SELECTED))
    if len(SELECTED) > 1:
        SELECTED = SELECTED[-1] # last point in case of multiselect
    bSource.patch(dict(top=[(slice(2),[SRC.data['cat1'][SELECTED],SRC.data['cat2'][SELECTED]])]))

taptool = cplot.select(type=TapTool)
cplot.on_event(Tap, callback)

curdoc().add_root(column(cplot, bplot))
1 Like

@_jm
Thank you very much!
This is exactly what I was looking for.

Will there be any implementation in the furture which makes the manual zipping of data obsolete?

Greetings
Creaner

@Creaner

Great. Glad to hear.

By “zipping of data” do you mean the patch syntax for the data source in the python callback? If so, I don’t envision this will be removed from the inner-workings of bokeh, but of course I am not part of the development team but just a regular user.

Patching and streaming are integral features of bokeh’s strategy to efficiently update data in a client’s browser by transmitting only new/changed data. There’s more info in the docs here.