Keep zoom while changing variables and allow reset

Hello,

I have an application where I would like to zoom in on time series data and then switch variables to see what the values look like in that same range. The below code performs that function however the issue I am seeing is that when I try to reset the graph to the default view it thinks the reset range is that of the zoomed in range. Any help or ideas would be greatly appreciated.

I did find see this similar issue but they appear to be having the same problem where reset will not go back to the default view. Keep zoom level when changing between variables in a scatter plot

import numpy as np
import pandas as pd
import sys

import holoviews as hv
from holoviews.operation.datashader import datashade
from holoviews.streams import RangeX
import panel as pn
import panel.widgets as pnw
hv.extension('bokeh')
pn.extension()

#Generating random data
time_min = 30#minutes

fs = 180
num_samples = time_min*60*fs
index = np.arange(0,num_samples)
time = index/180 #seconds

#Generating data
sine = np.sin(time)
linear = time
square = np.square(time)

#Creating data frame
data = {"time":time,"sine":sine,"linear":linear,"square":square}
df = pd.DataFrame(data=data)

#Initializing variables
variable = pnw.Select(name = "variable select", value = "linear", options=["sine","linear","square"])
start_value = pnw.TextInput(name = "start", value = "None")
stop_value = pnw.TextInput(name = "stop", value = "None")


#xstream = RangeX()
start = df['time'].iloc[0]
stop = df['time'].iloc[-1]
ds_curve = None


#creating functino to update x range values
def update_values(event):
    global start, stop

    start, stop = event.new[0], event.new[1]
    start_value.value = str(start)
    stop_value.value = str(stop)


#creating timeplot
@pn.depends(variable)
def time_plot(variable = "linear"):
    global xstream, start, stop, ds_curve
    
    curve = hv.Curve(df,"time",variable)
    xstream = RangeX()
    xstream.param.watch(update_values, 'x_range')
    
    ds_curve = datashade(curve,streams=[xstream]).opts(width = 700)
    xstream.x_range = (start,stop)
    
    return ds_curve

#Creating dashboard
dashboard = pn.Column(variable,start_value, stop_value,time_plot)



I think if you make it a dynamicmap:

Thank you for the response. I am sorry I took so long to respond back but I hadn’t used DynamicMaps before so I had to figure out how to pass it to datashader. I did try with a dynamicmap and it works! Another benefit is it seems I can ignore using streams now also.

Only problem is that now both the x and y axis and kept and the original values when switching. Previously the y axis was able to resize which is the correct functionality. Here is the updated code, any suggestions on how to make it so that the y axis resizes properly within that selected window?

import numpy as np
import pandas as pd
import sys

import holoviews as hv
from holoviews.operation.datashader import datashade
from holoviews.streams import RangeX
import panel as pn
import panel.widgets as pnw
hv.extension('bokeh')
pn.extension()

#Generating random data
time_min = 30#minutes

fs = 180
num_samples = time_min*60*fs
index = np.arange(0,num_samples)
time = index/180 #seconds

#Generating data
sine = np.sin(time)
linear = time
square = np.square(time)

#Creating data frame
data = {"time":time,"sine":sine,"linear":linear,"square":square}
df = pd.DataFrame(data=data)
#Initializing variables


left_variable = pnw.Select(name = "variable select", options=["square","linear","sine"])

def plot(variable):
    return hv.Curve((df['time'].values, df[variable].values))#.opts(framewise = True)

#creating timeplot
@pn.depends(variable = left_variable.param.value)
def time_plot(variable):
    
    return plot(variable)

dmap = datashade(hv.DynamicMap(time_plot)).opts(width = 700)

#Creating dashboard
dashboard = pn.Column(left_variable,dmap)
error = False
try:
    display = dashboard.show(port=38501)
    display
except OSError as error:
    print("OS error")
    display.stop()
    display = dashboard.show(port=38501)
    display

I also tried changing the graphs to a class. The current functionality for the below code is that when zooming in and then switching variables the plot will stay zoomed which is good. The unfortunate issue though is that I cannot zoom out but hitting reset. I think this is happening because I need to trigger an event to redraw the graph. I used PlotReset stream but that unfortunately doesn’t work either

variables = ["sine","linear","square"]

class GraphExplorer(param.Parameterized):
    
    variable = param.ObjectSelector(default = "square", objects=variables)
    start = df['time'].iloc[0]
    stop = df['time'].iloc[-1]
    plot = hv.Curve(df,'time','square').opts(framewise = True)
    startX, endX = plot.range('time')
    
    def keep_zoom(self, x_range):
        self.startX, self.endX = x_range
        
    def reset_zoom(self, resetting):
        print("reset occurred")
        self.plot = self.plot.redim(x = (self.start, self.stop))
        
    def plot_curve(self):
        return hv.Curve(df,'time', self.variable)
        
    
    @param.depends('variable')
    def view(self):

        self.plot = datashade(hv.DynamicMap(self.plot_curve)).opts(width = 700)
        self.plot = self.plot.redim.range(time = (self.startX, self.endX))
        xstream = RangeX(source = self.plot, x_range = (self.startX, self.endX))
        xstream.add_subscriber(self.keep_zoom)
        rstream = PlotReset(source = self.plot)
        rstream.add_subscriber(self.reset_zoom)
        return self.plot
        
        
    
    
explorer = GraphExplorer()
explore_dmap = explorer.view

pn.Row(pn.panel(explorer.param, parameters = ["variable"]), explore_dmap)

I’m not sure if it’s the behavior you are looking for but with a hook you can acchieve this:
I havent you used your last piece of code but the one before

import numpy as np
import pandas as pd
import sys

import holoviews as hv
from holoviews.operation.datashader import datashade
from holoviews.streams import RangeX
import panel as pn
import panel.widgets as pnw
from functools import partial
hv.extension('bokeh')
pn.extension()


    
#Generating random data
time_min = 30#minutes

fs = 180
num_samples = time_min*60*fs
index = np.arange(0,num_samples)
time = index/180 #seconds

#Generating data
sine = np.sin(time)
linear = time
square = np.square(time)

#Creating data frame
data = {"time":time,"sine":sine,"linear":linear,"square":square}
df = pd.DataFrame(data=data)
#Initializing variables


left_variable = pnw.Select(name = "variable select", options=["square","linear","sine"])

def plot(variable):
    return hv.Curve((df['time'].values, df[variable].values), vdims="W")

#creating timeplot
@pn.depends(variable = left_variable.param.value)
def time_plot(variable):
    return plot(variable)

def reset_y_hook(plot, elem):
    bkplot = plot.handles['plot']
    ydata = elem.dataset.data[elem.dataset.vdims[0].name]
    y_range = ydata.min(), ydata.max()
    old_y_range_reset = bkplot.y_range.reset_start, bkplot.y_range.reset_end  
    if old_y_range_reset != y_range:
        bkplot.y_range.start, bkplot.y_range.end = y_range
        bkplot.y_range.reset_start, bkplot.y_range.reset_end = y_range

dmap = datashade(hv.DynamicMap(time_plot)).opts(width=700, hooks=[reset_y_hook])
#Creating dashboard
dashboard = pn.Column(left_variable,dmap)
dashboard

1 Like

This is great and it’s exactly what I was hoping to fix on the current plots. Thank you for your help!

1 Like