Keep zoom level when changing between variables in a scatter plot

Hi there, I have a question on how to keep the x and y axis ranges when changing variables in a Panel dashboard. This is similar to the question at Not change Bokeh axes limits with Dynamic Map, but on a pandas DataFrame instead of an xarray Dataset. Here’s a minimal code example:

# %%
import numpy as np
import pandas as pd
import panel as pn
import hvplot.pandas

# Example pandas dataframe with x, y, z1 and z2 columns
data = 10 * np.random.rand(100, 4)
df = pd.DataFrame(data=data, columns=["x", "y", "z1", "z2"])
#           x         y        z1        z2
# 0  8.366391  4.803666  2.127830  9.435238
# 1  5.480135  1.173533  2.834300  8.722445
# 2  9.859487  6.385979  9.566771  2.205684
# 3  8.638108  0.138157  9.362423  3.890024
# 4  4.635330  9.695100  4.315197  3.976624
# Create dashboard widget to toggle between z1 and z2
plot = lambda zvar: df.hvplot.scatter(x="x", y="y", c=zvar)
layout: pn.layout.Column = pn.interact(
        name="z variable", value="z1", options=["z1", "z2"]
dashboard: pn.layout.Column = pn.Column(layout[0][0], layout[1][0])

Currently I can zoom in and out of the xy scatter plot just fine, but when I change ‘c’ to color based on the ‘z2’ column instead of ‘z1’, the zoom resets to the original plot. Is there a way to keep the zoom level as it is when switching columns?

I’ve tried using framewise=False but that didn’t work (zoom level will still reset), and I’ve also looked at (live demo at which is a lower-level holoviews/param based method, but it’s a lot more code to write compared to hvplot!

In order to keep the zoom level, you can use a RangeXY stream to get the axes ranges then set the range manually when you switch between plots.

You can do that in a param.Parameterized class :

import numpy as np
import pandas as pd
import panel as pn
import hvplot.pandas
from holoviews import streams
import param

class AppTest(param.Parameterized):
    data = 10*np.random.rand(100,4)
    df = pd.DataFrame(data=data,columns=['x','y','z1' ,'z2'])
    color_val = param.Selector(default='z1',objects=['z1','z2'])
    plot = df.hvplot.scatter(x='x',y='y',c='z1')
    startX,endX = plot.range('x')
    startY,endY = plot.range('y')

    def keep_zoom(self,x_range,y_range):
        self.startX,self.endX = x_range
        self.startY,self.endY = y_range

    def view(self):
        self.plot = self.df.hvplot.scatter(x='x',y='y',c=self.color_val)
        self.plot = self.plot.redim.range(x=(self.startX,self.endX), y=(self.startY,self.endY))
        rangexy = streams.RangeXY(source = self.plot, x_range=(self.startX,self.endX), y_range=(self.startY,self.endY))
        return self.plot

viewer = AppTest()

Awesome, thank you so much for this! Good to know that there’s a way to accomplish this in hvplot! Glad to see that the param selector can be wrapped by panel widgets too! :smile:

Just another (hopefully quick) question, is there a way to keep the wheel zoom on by default? When toggling between ‘z1’ and ‘z2’, the wheel zoom tool gets deactivated and I have to reactivate it each time.

You can choose which tools you want to activate by default with the option active_tools.
In this example, you just need to change the first line of the view function by :

 self.plot = self.df.hvplot.scatter(x='x',y='y',c=self.color_val).opts(active_tools=['pan','wheel_zoom'])
1 Like