Applying a function to a hover tool tooltip value

In a tooltip, I’d like to be able to transform the value of one of the data columns and display that transformed value. In my case, given a dictionary, I want to use the column value as a dictionary key and return the corresponding dictionary value. I’m using a bokeh HoverTool to customize the hover tool, with bokeh 2.4.3. But I don’t see anything in Configuring plot tools — Bokeh 2.4.2 Documentation that suggests this is possible. The tooltip value can mix and match values from different columns and apply formatting customizations.

Does anyone know if this is possible, and how?
Thanks.

Maybe this can help.
periodic_shells — Bokeh 3.2.2 Documentation

More discussion below:
[FEATURE] Documentation and examples for embedding plots inside tooltips · Issue #13375 · bokeh/bokeh (github.com)
Drilldown support · Issue #5884 · holoviz/holoviews (github.com)

Thanks! I’m looking into it

I see what you mean with the use of bokeh Template and other bokeh.models.dom elements. This might be lower level than I’d prefer, but it could work.

I like what’s in your example with hvplot/holoviews in Drilldown support · Issue #5884 · holoviz/holoviews · GitHub. But it refers to a PR that’s not merged, so I assume this example code would not work on a released version of hvplot/holoviews?

1 Like

That is correct; it will not work at the moment. Feel free to bump the issue / PR.

Here is a simple example:

import pandas as pd
import holoviews as hv
hv.extension( "bokeh", "plotly", logo=False)
from bokeh.models import HoverTool, CustomJSHover
# Syntax: $. are 'special fields':  $x inserts the x-coordinate
#         @. are fields from the color data source:
#            provide an extra column of values and declare it's name as a vdim (or sue a pd.DataFrame)

data  = pd.DataFrame( { 'x': [1.1, 3.2, 5.8 ], 'state': ['NY', 'NJ', 'PA'], 'stuff': ['a','b','c']} )
hover = HoverTool(tooltips=[ ("error", "$x"),
                            ("state", "@state"),
                            ("stuff", "@stuff{custom}")
                           ],
                  formatters = {
                      "@stuff" : CustomJSHover(code="return value.toUpperCase()")
                  }
                 )
hv.Scatter( data, kdims='x', vdims=['state', 'stuff']).opts(tools=[hover], size=10).redim.range(x=(0,7))
2 Likes

Thank you @ea42gh ! That’s a really interesting mix of the HoverTool and very targeted/focused JavaScript functionality. I’ll see what I can do with it, but it looks promising.

Success!! @ea42gh 's code pointed me in the right direction (the use of CustomJSHover for a specific field), but it was limiting in that it only had access to the value from the column (value). Ultimately what I need it to be able to pass a Python variable (in my case, a dictionary) to the code in CustomJSHover.

This old bokeh thread gave me the missing piece: the use of arg in CustomJSHover.

I’ve modified @ea42gh 's code to work with what I needed. Note that I don’t know if the use of ColumnDataSource is strictly needed, but I’m not proficient enough with bokeh to know if there’s a simpler alternative.

Thanks for your help, @ea42gh and @ahuang11 ! This is great. I’d rather not have to dive too much into JavaScript, but here it’s limited to just enough. I can live with that.

import pandas as pd
import holoviews as hv
hv.extension( "bokeh")
from bokeh.models import HoverTool, CustomJSHover, ColumnDataSource

data  = pd.DataFrame( { 'x': [1.1, 3.2, 5.8 ], 'state': ['NY', 'NJ', 'PA'], 'stuff': ['a','b','c']} )

mydict = dict(a=1, b=2, c=3)

hover = HoverTool(
    tooltips=[ 
        ("error", "$x"),
        ("state", "@state"),
        ("stuff", "@stuff{custom}")
    ],
    formatters = {
        "@stuff" : CustomJSHover(
            args=dict(mappedDict=ColumnDataSource(dict(mydict=[mydict]))),
            code="return mappedDict.data.mydict[0][value]"
        )
    }
)

hv.Scatter( data, kdims='x', vdims=['state', 'stuff']).opts(tools=[hover], size=10).redim.range(x=(0,7))
2 Likes

I’m adding two updates to this post:

Simplified CustomJSHover scheme

It turns out the CustomJSHover scheme I used really was unnecessarily complex, and ColumnDataSource wasn’t needed. This much simpler CustomJSHover scheme works just the same:

CustomJSHover(
    args=dict(mappedDict=mydict),
    code="return mappedDict[value]"
)

Change in a recent bokeh version when mydict keys are integers

In bokeh 3.3.4, both the scheme I posted earlier and the simplified version here no longer work when the mydict dictionary keys are integers rather than strings. It looks like mappedDict is now turned into a JavaScript “Map” object rather than a JavaScript “associative array”, such that accessing elements using [ ] when the keys are integers now fails and returns undefined. This is solved by accessing elements via .get(), like this:

CustomJSHover(
    args=dict(mappedDict=mydict),
    code="return mappedDict.get(value)"
)

Previously I was using a 2.x bokeh version, probably one of the last ones before bokeh 3.

1 Like