Methods for animating a holomap with the bokeh backend

I would like to to animate a holomap using the bokeh backend. I’m aware its possible with the matplotlib backend, but I am using plotting options for bokeh and it is quite complicated to make the plotting code be agnostic to the backend.

I can think of 3 basic ways to achieve this:

I. create a dynamic map and add a periodic callback to modify the values of the sliders, so that the slices of the holomap are animated.
2. Create a dynamic map and add a stream that iterates through all the values in the holomap
3. Given a holomap manually create sliders with callbacks that iterate through the values.

I would prefer to use method 1 but after reading the DynamicMap code I can’t see where the sliders of a dynamic map are stored.

  1. Been struggling to create a proof of concept

  2. I have a basic proof of concept working based on the player bokeh example:
    Player — HoloViews v1.17.1

I don’t like approach 3 as I am essentially manually re implementing a dynamic map, but I’ve made the most progress. The only problem is it is still using the bokeh plot function instead of plotting the holomap directly, but I can’t work out how to fix this.

Am I going about this the right way?

Method 3 implementation so far:


import numpy as np
import holoviews as hv
from bokeh.models import Button
import panel as pn


class HoloMapPlayer:
    def __init__(self, holomap, slider=None, fps=10.0) -> None:
        renderer = hv.renderer("bokeh")

        #THIS IS THE LINE I NEED TO CHANGE
        self.plot = renderer.get_plot(holomap) 

        self.holomap = holomap
        
        if slider is None:
            self.slider = pn.widgets.DiscreteSlider(options=holomap.keys())
        else:
            self.slider = slider

        self.holomap_index=0

        self.bound_slider = pn.bind(self.slider_update, self.slider)
        self.button = Button(label="► Play", width=60)
        self.button.on_click(self.animate)
        self.ms_update = int(1.0 / fps)

        self.layout = pn.Column()
        self.layout.append(self.plot.state)
        self.layout.append(self.slider)
        self.layout.append(self.bound_slider)
        self.layout.append(self.button)

        self.cb = pn.state.add_periodic_callback(self.animate_update, self.ms_update, start=False)

    def animate_update(self):
        self.holomap_index = (self.holomap_index+1)%len(self.holomap.keys())        
        self.slider.value = self.holomap_index

    def slider_update(self, *args, **kwargs):
        self.plot.update(self.slider.value)

        #I WOULD LIKE SOMETHING LIKE THIS
        # return self.holomap[self.slider.value]

    def animate(self) -> None:
        if self.button.label == "► Play":
            self.button.label = "❚❚ Pause"
            self.cb.start()
        else:
            self.button.label = "► Play"
            self.cb.stop()

    def show(self) -> None:
        pn.Row(self.layout).show()


if __name__ == "__main__":
    start = 0
    end = 10
    hmap = hv.HoloMap({i: hv.Image(np.random.rand(10, 10)) for i in range(start, end + 1)})

    HoloMapPlayer(hmap, fps=30).show()