Search code examples
pythonbar-chartbokehstacked-chart

Conditional tooltip bokeh stacked chart


I'm trying to create custom hover based on data on a stacked chart. In the example below, if user hovers over 'cat1', then 'cat1_text' should be returned; 'cat2_text' and 'cat3_text' for 'cat2' and 'cat3' accordingly.

For the tooltip, since $name will return either 'cat1','cat2', or 'cat3', I thought by adding '_text', the value will be called accordingly (but of course this seems not to be the way python/bokeh works). I was thinking about using any function/index calls as well, but not too sure how to do so. Kindly advice. Many thanks!

category = ['cat1', 'cat2', 'cat3']

data = {'timeVal' : [0,1,2,3,4,5],
        'cat1'   : [2, 1, 4, 3, 2, 4],
        'cat2'   : [5, 3, 4, 2, 4, 6],
        'cat3'   : [3, 2, 4, 4, 5, 3],
        'cat1_text'   : ['a','b','c','d','e','f'],
        'cat2_text'   : ['a1','b1','c1','d1','e1','f1'],
        'cat3_text'   : ['a2','b2','c2','d2','e2','f2'],
}

toolTipArr = [
    ("name", "$name"),
    ("count", "@$name"),
    ("info", '@'+'$name'+'_text}')
]

p = figure(x_range=(startTime,endTime), plot_height=250, plot_width=1000, title="project",
           toolbar_location="right", tools="hover,pan,wheel_zoom,box_zoom,reset", 
           tooltips=toolTipArr)

p.vbar_stack(category, x='timeVal', width=2, color=colors, source=data,
             legend=[value(x) for x in category])

Solution

  • This is the implementation suggested by bigreddot using CustomJSHover (works for Bokeh v1.3.0):

    from bokeh.core.properties import value
    from bokeh.models import ColumnDataSource, CustomJSHover
    from bokeh.plotting import figure, show
    
    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)
    
    tooltips= [("name", "$name"), ("count", "@$name")]
    
    p = figure(x_range=fruits, plot_height=350, title="Fruit Counts by Year",
               toolbar_location=None, tooltips=tooltips)
    
    renderers = p.vbar_stack(years, x='fruits', width=0.9, color=colors, source=source,
                             legend=[value(x) for x in years], name=years)
    
    p.hover[0].tooltips.append(('info', '$name{custom}'))
    p.hover[0].formatters = {'$name' : CustomJSHover(code = "return special_vars.name + '_text'")}
    
    show(p)
    

    With a little bit more effort you could achieve the same using CustomJS callback (Bokeh v1.3.0):

    from bokeh.core.properties import value
    from bokeh.models import ColumnDataSource, HoverTool, CustomJS
    from bokeh.plotting import figure, show
    
    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)
    
    hover_code = "if (cb_data.index.indices.length > 0) { cb_obj.tooltips[2] = ['info', cb_obj.name + '_text'] }"
    
    for renderer in renderers:
        p.add_tools (HoverTool(tooltips=[("name", "$name"),
                                         ("count", "@$name"),
                                         ("info", "@info"), ],
                               renderers=[renderer],
                               name = renderer.name,
                               callback = CustomJS(code = hover_code)))
    show(p)
    

    enter image description here