How to use CustomJSHover to add tooltips with different formatters

I would like to plot a time series (x-axis: absolute time, y-axis: data) and provide in the hover tooltip the absolute and relative time. The data is provided as a ColumnDataSource to the plot. I do not want to add an additional series for the relative time to the ColumnDataSource since this represents an unnecessary data duplication. I’m looking for a way to add a “derived” series with

relative_time = absolute_time - offset 

to the hover tooltip. CustomJSHover provides a way to add a derived hover tooltip. However, so far I could not manage to add a tooltip for the same series from the ColumnDataSource but with 2 different formatters.

Minimal example:

from bokeh.plotting import figure, output_file, show
from bokeh.models.glyphs import Line
from bokeh.models import ColumnDataSource, HoverTool, CustomJSHover

x = [10, 10, 13, 14, 15]
y = [6, 7, 2, 4, 5]
source = ColumnDataSource(dict(x=x, y=y))

output_file("lines.html")

p = figure(x_axis_label='x', y_axis_label='y')
l = Line(x='x', y='y')
p.add_glyph(source, l)

offset = 10
x_custom = CustomJSHover(
    args=dict(offsetSource=ColumnDataSource(dict(offset=[offset]))),
    code="""
        return "" + (special_vars.data_x - offsetSource.data.offset[0]);
""")

p.add_tools(HoverTool(
    tooltips=[
        ( 'absolute_time','@x{0.000}' ),
        ( 'relative_time','@x{custom}' ),
    ],
    formatters={"@x": x_custom}
))

show(p)

The result is a hover tooltip like this:

absolute_time: 4
relative_time: 4

I would like to get something like this:

absolute_time: 14.000
relative_time: 4.000

Is there a way to achieve this with CustomJSHover or is this use case currently not supported?

You JS code is just fine. It just doesn’t do what you want it to do.
Apart from special_vars, there’s also the format variable (that will have values "0.000" and "custom" in your case) - you will have to inspect it and add some condition on it.
To help with different formats like 0.000, you can call Bokeh.require('@bokehjs/core/util/templating').DEFAULT_FORMATTERS.numeral - it’s a function of (value, format, special_vars) that will format the value just like Bokeh does without any custom formatters.

Thank you for your answer! With your information, I now managed to add a second shifted series to the hover tooltip derived from an existing series.

For later reference, I add the adapted minimal example here:

from bokeh.plotting import figure, output_file, show
from bokeh.models.glyphs import Line
from bokeh.models import ColumnDataSource, HoverTool, CustomJSHover

x = [11, 12, 13, 14, 15]
y = [6, 7, 2, 4, 5]
source = ColumnDataSource(dict(x=x, y=y))

output_file("lines.html")

p = figure(x_axis_label='x', y_axis_label='y')
l = Line(x='x', y='y')
p.add_glyph(source, l)

offset = -10
x_custom = CustomJSHover(
    args=dict(offsetSource=ColumnDataSource(dict(offset=[offset]))),
    code="""
        var numFormatter = Bokeh.require('@bokehjs/core/util/templating').DEFAULT_FORMATTERS.numeral;
        var formatSplit = format.split(':');
        if (formatSplit.length == 2 && formatSplit[0] == 'withOffset') {
            return numFormatter(special_vars.data_x + offsetSource.data.offset[0], formatSplit[1], special_vars);
        } else {
            return numFormatter(special_vars.data_x, format, special_vars)
        }
""")

p.add_tools(HoverTool(
    tooltips=[
        ( 'absolute_time','@x{0.000}' ),
        ( 'relative_time','@x{withOffset:0.000}' ),
    ],
    formatters={"@x": x_custom}
))

show(p)

A note on the use of Bokeh.require() from the Bokeh 2.0.0 release notes:

The require() function [i]s not available from CustomJS anymore. It exposed the underlying module system, which should not be used by general users. Use APIs exposed on Bokeh object or (as a last resort), use Bokeh.require() .