HoloView pane size changes unexpectedly after dynamicmap update

When the “Remap Selected” button in the example below is clicked, the plot updates successfully, but it shrinks from full-width to a small box:

after click (which updates the hv.DynamicMap for the points on the map):
image

I had some limited success in this demo example using sizing_mode=‘fixed’, but in more complicated panel configurations that causes other issues with different screen sizes… I am hoping there’s a way to use “stretch_both” but have the inner grid dimensions stay consistent through dynamicmap update events?

Has anyone run into this before? Seems similar to Plot changes size when partially updated

import geoviews as gv
import geoviews.tile_sources as gts
import geopandas
from cartopy import crs
import holoviews as hv
import panel as pn
import pandas as pd
from holoviews.streams import param

hv.extension('bokeh')

class TerrMapper(param.Parameterized):
        cities = ['Buenos Aires', 'Brasilia', 'Santiago', 'Bogota', 'Caracas','Tacna','Pucallpa','Lima','Langa']
        terrs = ['A','A','B','B','C','C','C','C','C']
        terr_map = param.Dict(default=dict(zip(cities, terrs)), doc = 'mapping dictionary for cities-->territories')
        terr_colors = param.Dict(default={'A':'red','B':'blue','C':'green'})
        
class Point_Remap_Test:
    def __init__(self):
        df = pd.DataFrame(
                    {'City': ['Buenos Aires', 'Brasilia', 'Santiago', 'Bogota', 'Caracas','Tacna','Pucallpa','Lima','Langa'],
                    'Country': ['Argentina', 'Brazil', 'Chile', 'Colombia', 'Venezuela','Peru','Peru','Peru','Peru'],
                    'Latitude': [-34.58, -15.78, -33.45, 4.60, 10.48, -18.006,-8.392,-12.046,-12.125],
                    'Longitude': [-58.66, -47.91, -70.66, -74.08, -66.86,-70.246,-74.582,-77.0427,-76.4211]})       
        
        self.gdf = geopandas.GeoDataFrame(df, geometry=geopandas.points_from_xy(df.Longitude, df.Latitude),crs="EPSG:3857")
        
        #tried to split the stream out to a parameterized class as part of troubleshooting; not sure this added any value
        self.terrs = TerrMapper()
        self.terrs.param.set_param(terr_map=self.terrs.param.terr_map.default) 
        
        self.territory_list = list(set(self.terrs.param.terr_map.default.values()))
        self.territory_picklist = pn.widgets.Select(options= self.territory_list,name='Territory',width=250)
        self.terr_stream = {'terr_map':self.terrs.param.terr_map}
        
        #point collection should be dynamic and update it's territory assignment based on the mapping dictionary from the stream
        self.points = hv.DynamicMap(self.dynamic_pnts,streams=self.terr_stream)
        
        #second selection stream to capture lasso point selections
        self.sel = hv.streams.Selection1D(source=self.points)

        #coloring based on New_Territory; which is the dimension that should change as self.terr_stream changes
        poly_plot = self.points.opts(color='New_Territory',cmap=self.terrs.param.terr_colors.default,tools=['lasso_select'],size=10)
        self.tiles = gts.OSM.options(level='glyph')
        self.plot=self.tiles*poly_plot
         
        #button + drop down are attempting to allow relabeling of lasso-selected points
        self.button = pn.widgets.Button(name='<--Remap Selected', button_type='primary')
        self.button.on_click(self.update_terrs)   
    
    def dynamic_pnts(self,terr_map):
        '''
        Apply current territory map to base geo dataframe
        '''
        new_df = self.gdf.copy()
        new_df.loc[:,'New_Territory'] = new_df['City'].map(terr_map)
        print('updating')
        return gv.Points(new_df,vdims=['New_Territory'])       
    
    def update_terrs(self,event):
        '''
        Update the territory map based on selections
        '''
        if len(self.sel.index)>0:
            selected_cities = self.gdf.loc[self.sel.index,'City'].values[:]
            
            #there must be a better way to do this
            #param_vals = self.terrs.param.get_param_values()
            #temp_dict = [v for (k,v) in param_vals if k=='terr_map'][0]
            temp_dict = self.terrs.terr_map.copy()

            for city in selected_cities:
                temp_dict[city] = self.territory_picklist.value
            
            self.terrs.param.set_param(terr_map=temp_dict)
            
        else:
            print('no selection')
        
def createApp():
    geoplt = Point_Remap_Test()
    gspec = pn.GridSpec(sizing_mode='stretch_both')

    #context switcher
    gspec[0,0:4] = pn.Column("#New Territory",pn.Row(geoplt.territory_picklist,geoplt.button))


    #map
    gspec[1:3,0:4] = pn.pane.HoloViews(geoplt.plot)
    return gspec

test = createApp()
test

I don’t know the solution. But you could try

adding pn.extension(sizing_mode="stretch_width")

or changing to

gspec[1:3,0:4] = pn.pane.HoloViews(geoplt.plot, sizing_mode="stretch_both")

or

gspec[1:3,0:4] = pn.panel(geoplt.plot, sizing_mode="stretch_both")

or

return gv.Points(new_df,vdims=['New_Territory']).opts(height=600, responsive=True)

or combinations of this.

2 Likes

Fantastic! Thank you Marc, looks like the key from your suggestions was adding the .opts(responsive=True) to the gv.Points map.

Also thank you for the awesome-panel demos; they are a huge help while getting started!

3 Likes