Custom Toolbar Button with Bokeh

This is a simple picture to illustrate my problem.

image

I have a primary curve and some points. Each point has been created with hv.Points in order to apply a custom tooltip (different for each point). Note the lack of a legend, which I don’t want it for this plot.

I would like to be able to press a button and hide all the points. Ideally, I would like a toolbar button to do that, so that it is nicely integrated with the plot. I tried to create a custom tool inheriting from InspectTool (because I want this button to be toggeable) and from ActionTool (because I would like to execute a JS code to hide all renderers after the first one). Unfortunately, I’m unable to get this button to show in the toolbar.

Note that this is my first attempt to create custom toolbar buttons. I read the docs, but it is very light on details about this kind of customization, so I’m pretty sure I did something wrong. Is it even possible to achieve my goal?

This is the code I have so far.

import holoviews as hv
import numpy as np
hv.extension('bokeh')

from bokeh.models import HoverTool
from bokeh.models import InspectTool, Callback, ActionTool
from bokeh.core.properties import Nullable, Instance, List, Int, Override

class ToggleVisibility(InspectTool, ActionTool):
    description = Override(default="Perform a Custom Action")

    callback = Nullable(Instance(Callback), help="""
    A Bokeh callback to execute when the custom action icon is activated.
    """)

x = np.linspace(0, 10)
y = np.cos(x)
c = hv.Curve(data=(x, y)).options(tools=['hover'])

t1 = [("A", "$x")]
h1 = HoverTool(tooltips=t1, toggleable=False)
p1 = hv.Points(data=[[np.pi, -1]]).options(tools=[h1], size=5)

t2 = [("B", "$y")]
h2 = HoverTool(tooltips=t2, toggleable=False)
p2 = hv.Points(data=[[2*np.pi, 1]]).options(tools=[h2], size=5)

toggle = ToggleVisibility(icon="/path/to/my/icon.png", toggleable=True)

(c*p1*p2).options(tools=[toggle])

Hey Davide, I have done this in a bokeh application before (no holoviews) using bokeh.models.CustomAction and CustomJS. I found some info and examples in this thread. With holoviews, I guess the right way is to use a hook to make & add the CustomAction?

In my application I added tags to the bokeh renderers that I wanted to hide, and checked the tags of each renderer in the CustomJS. Not sure how add tags to the renderer using holoviews, but maybe you can settle for comparing other attributes of the fig.renderers?

import holoviews as hv
import numpy as np
hv.extension('bokeh')

from bokeh.models import HoverTool
from bokeh.models import InspectTool, Callback, ActionTool
from bokeh.core.properties import Nullable, Instance, List, Int, Override

x = np.linspace(0, 10)
y = np.cos(x)
c = hv.Curve(data=(x, y)).options(tools=['hover'])
    
t1 = [("A", "$x")]
h1 = HoverTool(tooltips=t1, toggleable=False)
p1 = hv.Points(data=[[np.pi, -1]]).options(tools=[h1], size=5)

t2 = [("B", "$y")]
h2 = HoverTool(tooltips=t2, toggleable=False)

p2 = hv.Points(data=[[2*np.pi, 1]], name='p2').options(tools=[h2], size=5)


from bokeh.models import CustomAction
from bokeh.models.callbacks import CustomJS

def add_tool(plot, el):
    fig = plot.state
    viewall_action = CustomAction(
        icon=r"/path/to/icon.png",
        callback = CustomJS(
            args={'fig': fig},
            code='''
            for (const el of fig.renderers) {
                // line will not have attribute "marker"
                // not sure how to add tags or name to each bokeh renderer!
                if (el.glyph.marker != undefined) {
                    el.visible = !el.visible;
                }
            }
            '''
        ),
        description='Toggle Points'
    )
    plot.state.add_tools(viewall_action)
(c*p1*p2).opts(hooks=[add_tool])

image