Get glyph name using taptool

Hi all- I’d like to capture the name (property) of a glyph renderer on selection by TapTool.

I have several timeseries (line) plots & can query the “name” of a series using Hover's tooltips, but am stuck on replicating that with a tap.

Ultimately want to add a leader-type label (Arrow & Label) to the plot that says “This is series ABC with value of Y at X” - essentially a static Hover. Often send screenshots to folks where these labels would make things clearer, example below.

Quick example showing desired Hover behavior, not sure how to replicate with Tap:

import numpy as np

from bokeh.models import ColumnDataSource, TapTool, HoverTool
from bokeh.plotting import figure
from bokeh.models.callbacks import CustomJS

from bokeh.io import show, output_notebook

output_notebook()

def make_plot(doc):

    p = figure(width=400, height=400,
               tools=[], title='Tap/Hover Test')

    hover = HoverTool(tooltips=[('Sel', '$name')])   
        # The '$name' is what I'd like to capture from a tap tool / event
    p.add_tools(hover) 
    
    N = 30
    x = np.linspace(-2, 2, N)
    y = x**2

    source = ColumnDataSource(dict(x=x, y=y))
    p.line('x', 'y', source=source, name='poly')

    taptool = TapTool(callback=CustomJS(code="""
        console.log('Tapped: ' + '???')
        """))                                
        # is the name of the tapped/selected glyph accessible?
        # (in a way that exposes it to a python callback)?
        
    p.add_tools(taptool)
    
    doc.add_root(p)

show(make_plot)

Mockup of what I want to achieve (this uses events to draw the arrows/labels; “selection name” should be the glyph’s .name property):

You can pass the glyph renderer in the args dict of the CustomJS and then access it in the callback code. THere are lots of examples of passing the args dict to a CustomJS in the repo.

Thanks Bryan! I’m not sure where to get the renderer to pass in args from, though- is the selected / tapped renderer a property of the tool (cb_obj in the CustomJS), plot or other?

Sorry if it’s a basic question. None of the properties listed in the docs for TapTool / Tap / SelectTool, Selection, etc. looked like a match.

Yeah this took me a bit to wrap my head around too initially.

The renderer is created when you go:

p.line('x', 'y', source=source, name='poly')

It adds the renderer to the figure at that point.

You could access it after this by going:

line_rend = [x for x in p.renderers if x.name == 'poly'][0]

But that’s kind of silly and needlessly complex, when you could instead just create a variable pointing to it when you make it:

line_rend = p.line('x', 'y', source=source, name='poly')

The pass line_rend into the CustomJS via the args argument.

Edit: There are probably other smart ways to access particular renderers using the .select method as well but I don’t actually know how to use that… hopefully Bryan can chime in with an example for that.

No I was only going to point out that the renderer is returned by the glyph method as well

In [9]: r = p.line(x, y, name="foo")

In [10]: r.name
Out[10]: 'foo'

There are several examples of passing objects via the args dict here:

OK, thanks - my example could have been better! :face_with_diagonal_mouth:

That’s exactly how I set up the renderers, the plot has several. Where I’m still stuck: which one was tapped?

Expanding on the example below, two renderers passed as args to CustomJS.

The r1.name is just a placeholder - that’s not what I want. Looking for something like taptool.selected.name.

Appreciate the help!

def make_plot(doc):

    p = figure(width=400, height=400,
               tools=[], title='Tap/Hover Test')

    hover = HoverTool(tooltips=[('Sel', '$name')])   
        # The '$name' is what I'd like to capture from a tap tool / event
    p.add_tools(hover) 
    
    N = 30
    x = np.linspace(-2, 2, N)
    y1 = x**2
    y2 = -(x**2)

    source = ColumnDataSource(dict(x=x, y1=y1, y2=y2))
    poly1 = p.line('x', 'y1', source=source, name='poly 1')
    poly2 = p.line('x', 'y2', source=source, name='poly 2')

    taptool = TapTool(callback=CustomJS(args=dict(r1=poly1, r2=poly2), code="""
        
        let result = cb_obj.selected
        
        // let result = Object.entries(cb_obj).map(( [k, v] ) => ({ [k]: v }));
        
        console.log('Tapped: ' + r1.name)
        """))                                
        # is the name of the tapped/selected glyph accessible?
        # (in a way that exposes it to a python callback)?
        
       
    p.add_tools(taptool)
    
    doc.add_root(p)

@johank Sorry I missed that aspect of your question. Unfortunately, looking at the actual source code for the tap tool bokeh/tap_tool.ts at branch-3.0 · bokeh/bokeh · GitHub I don’t actually think the glyph that is hit is reported. The data source for the glyph should be available in cb_data.source, so if each glyph uses its own separate data source, you could potentially back out the information that way.

Otherwise, it seems like a reasonable ask but (perhaps surprisingly) you are are the first person I can recall asking for this, so feel free to make a GitHub Issue to request it.

Yeah, it’s kind of crazy I haven’t run into a similar need for this yet.

As an inefficient workaround in the meantime you could make a “1 TapTool, 1 Renderer” setup i.e. have a taptool for each glyph/renderer (and of course write some function to expedite the initialization/generation of the CustomJS component attached to each). Not ideal obviously but if you only have a few it could be viable.

Thanks Bryan, and no worries - I didn’t articulate what I was ultimately after particularly well!

I’ve opened an issue on GitHub. Thanks for all the support!

@gmerrritt123 that’s a good point, thanks - I may have to try that. Most of my plots where I’d want to use this have <20 renderers, so… could work. If I do try this I’ll try to post back a workaround.

1 Like