Keep current zoom level when parameter changes?

Hi there :wave:

I recently started using Panel and so far I’m loving it. I currently have the problem that whenever I change a parameter in my small dashboard app the zoom level resets to the default. Can I somehow persist this when I change the image source or the alpha level (my only two params)?

This is the App class:

class Tileviewer(param.Parameterized):
    #tile = param.ObjectSelector(default='02_13', objects=list(dataset.head(10).tile.unique()))
    tile = param.ObjectSelector(default='02_13', objects=['02_13', '02_14']) #list(dataset.head(10).tile.unique()))
    alpha = param.Number(0.8, bounds=(0.1, 0.9))
    def get_data(self):
        in2017 = rioxarray.open_rasterio(f"../data/processed.images.2017/ortho_2017_ESPG3044_{self.tile}.tif")"EPSG:3857")
        in2019 = rioxarray.open_rasterio(f"../data/processed.images.2019/ortho_2019_ESPG3044_{self.tile}.tif")"EPSG:3857")
        out2017 = rioxarray.open_rasterio(f"../data/predicted.2017/ortho_2017_ESPG3044_{self.tile}.tif")"EPSG:3857")
        out2019 = rioxarray.open_rasterio(f"../data/predicted.2019/ortho_2019_ESPG3044_{self.tile}.tif")"EPSG:3857")
        return in2017, in2019, out2017, out2019
    def view(self):
        in2017, in2019, out2017, out2019 = self.get_data()
        p1 = shade(regrid(one_band(out2017.sel(band=1))), cmap=['violet']) 
        p2 = shade(regrid(one_band(out2019.sel(band=1))), cmap=['violet']) 

        rgb1 = regrid(multi_bands(in2017.sel(band=1), in2017.sel(band=2), in2017.sel(band=3))).redim(x='Longitude', y='Latitude').opts(data_aspect=1, title="TrueColor (RGB) - 2017")
        rgb2 = regrid(multi_bands(in2019.sel(band=1), in2019.sel(band=2), in2019.sel(band=3))).redim(x='Longitude', y='Latitude').opts(data_aspect=1, title="TrueColor (RGB) - 2019")

        return rgb1 * p1.apply.opts(alpha=self.alpha) + rgb2 * p2.apply.opts(alpha=self.alpha)

And this is the Panel definition:

viewer = Tileviewer(name='')
logo  = """<a href="">
           <img src="" 
            width=150 height=127 align="left" margin=20px>"""
title = '<h2>Deadtrees Comparison 🌲☠️</h2>'

desc = pn.pane.HTML("""
    Inspect the predicted deadtree
    areas for the years 2017 and 2019.""", width=250)

pn.Row(pn.Column(logo, title, desc, viewer.param), pn.Column(viewer.view))

In order to save some space I left out some helper functions but hopefully there’s enough code to guide me?


I think wrap a hv.DynamicMap around viewer.view

Thanks for the suggestion. However, I’m a bit confused by the link. Would you have some (pseudo-) code for me to follow?

Replace pn.Column(viewer.view) with:

More info

Thanks, but I simply get this warning and no output in my notebook when I add this:

WARNING:param.LayoutPlot192772: :DynamicMap   []
   :DynamicMap   [] is empty, skipping subplot.
WARNING:param.LayoutPlot192772: :DynamicMap   []
   :DynamicMap   [] is empty, skipping subplot.

Do I need to scrap the Viewer class and use a plain function as in your examples? Sorry if this is obvious…

My Panel version is 0.11.0

Just for background, when Panel updates an object it simply replaces the entire object. When a HoloViews DynamicMap renders an object it simply updates the existing plot (which is why the ranges persist).

In your code you have in fact already set everything up correctly except for two things, when you do:


you are telling panel: “call the viewer.view method whenever one of the parameters on the class change”. If you annotated the method with param.depends('some_parameter', ...) you could instead tell Panel to only rerender if those particular parameters change. However, as I said you almost set up all the dependencies correctly for HoloViews, the only thing you have to change is:

        return rgb1 * p1.apply.opts(alpha=self.param.alpha) + rgb2 * p2.apply.opts(alpha=self.param.alpha)

and secondly actually invoke the viewer.view() method yourself:

pn.Row(pn.Column(logo, title, desc, viewer.param), pn.Column(viewer.view()))

ensuring that Panel doesn’t mistakenly do so for you. Hope that makes sense.

Wow, thanks @philippjfr

This totally worked for me :slight_smile: I had the gut feeling it might have something to do with the depends decorator but was not familiar with the details - appreciate your explanation there!!!

I shall read up a bit more on the design concepts of panels.

Really impressed so far!


I do have to ask a followup question @philippjfr . With the proposed changes I can make the alpha param interact with the map and preserve the zoom level, however now the change of tiles with the ObjectSelector does not do anything? The tile stays the same as the default. Any hint why this is?


Missed that part sorry. Here you will want to use a HoloViews DynamicMap to load the data and then chain the regrid and shade methods on top of that.

You could simplify this by having _in and _out methods and using functools.partial to pass in 2017 and 2019 as partial arguments but I’ve kept it simple.

Another comment, I don’t think there’s much need to use shade since you can just have it colormapped in the browser but that’s up to you.

Thanks @philippjfr - appreciate your help very much.

This now kinda works (I did the suggested partial mod), however when I switch tiles the refresh is often not drawn (probably due to time outs?), These GeoTiffs are pretty big 3x8192x8192px and maybe that’s too much? Also, when I switch tiles the x/ y coordinates naturally are not in the present scope any more…
Ideally I’d want to refocus the maps once the tile is changed in the pull down, but not after a zoom, pan or alpha operation…

I wonder if I should/ could cache the last 5 selected tiles and/ or trigger a focus change to the data extent once a new tile is selected…

Anyways, thanks for your help. I’ll try to read up some more on the holoviz way of doing things… :+1: