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.
-
Been struggling to create a proof of concept
-
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()