Percentage and count in stacked bar chart hover tooltip

In the hover tooltip, I want to show the count for each category, and the percent of each category in the bar. In the following example:

from bokeh.core.properties import value
from bokeh.io import show
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.plotting import figure

fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
years = ["2015", "2016", "2017"]
colors = ["#c9d9d3", "#718dbf", "#e84d60"]

data = {'fruits' : fruits,
        '2015'   : [2, 1, 4, 3, 2, 4],
        '2016'   : [5, 3, 4, 2, 4, 6],
        '2017'   : [3, 2, 4, 4, 5, 3]}

source = ColumnDataSource(data=data)

p = figure(x_range=fruits, plot_height=350, title="Fruit Counts by Year",
           toolbar_location=None, tools="")

renderers = p.vbar_stack(years, x='fruits', width=0.9, color=colors, source=source,
                         legend=[value(x) for x in years], name=years)

for r in renderers:
    year = r.name
    hover = HoverTool(tooltips=[
        ("%s total" % year, "@%s" % year),
        ("index", "$index")
    ], renderers=[r])
    p.add_tools(hover)

p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xgrid.grid_line_color = None
p.axis.minor_tick_line_color = None
p.outline_line_color = None
p.legend.location = "top_left"
p.legend.orientation = "horizontal"

show(p)

In the leftmost bar, the tooltip shows the counts 2,5,3, I would also like to show 20%, 50%, 30% underneath the respective counts. Is this possible?
Thanks

It should be possible with a CustomJSHover formatter:

formatter = CustomJSHover(args=dict(source=source),
                          code="compute percentage with JavaScript based on source, data_x, and data_y")
# The `{%}` is needed just to trigger the formatter.
HoverTool(tooltips=[('Percentage': '@value{%}')]],
          formatters={'@value': formatter})

Thanks, I’m able to get my desired result with the code below. However, when I don’t add the hover tools individually, it seems like I cannot trigger the formatter

from bokeh.core.properties import value
from bokeh.io import show
from bokeh.models import ColumnDataSource, HoverTool, CustomJSHover
from bokeh.plotting import figure

fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
years = ["2015", "2016", "2017"]
colors = ["#c9d9d3", "#718dbf", "#e84d60"]

data = {'fruits' : fruits,
        '2015'   : [2, 1, 4, 3, 2, 4],
        '2016'   : [5, 3, 4, 2, 4, 6],
        '2017'   : [3, 2, 4, 4, 5, 3]}

df = pd.DataFrame(data)
df = df.set_index('fruits').rename_axis(None)

source = ColumnDataSource(data=df)

p = figure(x_range=fruits, plot_height=350, plot_width=900, title="Fruit Counts by Year",
           toolbar_location=None, tools="")

renderers = p.vbar_stack(years, x='index', width=0.9, color=colors, source=source,
                         legend_label=years, name=years)

formatter = CustomJSHover(
    args=dict(source=source),
    code='''
    const columns = Object.keys(source.data)
    const cur_bar = special_vars.data_x - 0.5
    var ttl_bar = 0
    for (let i = 0; i < columns.length; i++) {
        if (columns[i] != 'index'){
            ttl_bar = ttl_bar + source.data[columns[i]][cur_bar]
        }
    }
    const cur_val = source.data[special_vars.name][cur_bar]
    return (cur_val/ttl_bar * 100).toFixed(2)+'%';
''')

# this works
for r in renderers:
    hover = HoverTool(tooltips=[
        ("Year", "$name"),
        ("Group", "@index"),
        ('Count', "@$name"),
        ('Percentage', '@%s{custom}'%r.name)
    ], 
    formatters={'@%s'%r.name: formatter}, 
    renderers=[r])
    p.add_tools(hover)

# this doesn't trigger the formatter
p.add_tools(HoverTool(
    tooltips=[("Year", "$name"), ("Group", "@index"), 
            ('Count', "@$name"), ('Percentage', '@$name{custom}')], 
    formatters={'@$name':formatter}, 
    renderers=renderers)
)

p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xgrid.grid_line_color = None
p.axis.minor_tick_line_color = None
p.outline_line_color = None
p.legend.location = "top_left"
p.legend.orientation = "horizontal"

show(p)

That’s because $name gets replaced with the actual value before the formatters are applied. You can still use a single instance of HoverTool with @$name in its tooltips but you will have to create a formatters dict that contains the actual values. Something like HoverTool(tooltips=[('Year', '@$name')], formatters={'@' + y: formatter for y in years}).