Can't make a Holoview Overlay responsive to size in a Dynamic Map container alongside a Datastream

hello

I’m writing the code for a geodata visualization tool to be displayed on a web platform hosted in Heroku.

I’m having reasonable succes but now I want to make my layout responsive to the screen size but I’m not able to do It when I use Dmaps to display my data

It works like this:

I have 2 scenarios to display a past scenario and a future scenario, both of these scanarios works as a association of different hvplot combined in a overlay… When I press the button the system checks to see with button was pressed and return the correspondent overlay

I also use a RangeXY stream to capture the current zoom lvl of the map in order for it to not reset the zoom every time the button is pressed

To buid this I used the Param componente and a Dmap in order for the application to become faster

It is working as intended but I can’t adjust the dimensions of the Dmap component in order for it to become responsive (I always get the message “WARNING:param.OverlayPlot174008: responsive mode could not be enabled because fixed width and height were specified.”), so my problem is simply that I want to be capable of togging on the “Responsive = True” option on my dmap but for some reason that I don’t understand I’m uncapable of doing it

a sample of my solution can be viewed at this website: http://sedecteste.herokuapp.com/

and the source code alongside the files used in the code can be accessed at GitHub - ArthurDF/SEDEC-RN: Repositório de Teste para projeto SEDEC-RN de estratégias locacionais (run the code “final.py”)

bellow is the same code contained in tha file final.py in my github (sorry for sending the whole code but I just don’t understand exactly where I messed up) I suspect it has something to do with the RangeXY Stream but don’t know exactly how

import panel as pn
import numpy as np
import holoviews as hv
from holoviews import streams
import param
import math
import spatialpandas as spd
import geopandas as gpd
from custom_hoover import *


hv.extension('bokeh')
pn.extension(sizing_mode = 'stretch_width')

bootstrap = pn.template.BootstrapTemplate(title='Visualizador de Mapa')

class AppTest(param.Parameterized):
    
    add_linha = 'C:\\Arthur\\CDEC\\Parte 2\\Dados\\Rotas\\presente\\presente.shp'
    add_rest = 'C:\\Arthur\\CDEC\\Parte 2\\Dados\\restrições\\restrições.shp'
    parques_add = 'C:\\Arthur\\CDEC\\Parte 2\\Dados\\Centroide dos parques\\CENTROIDE_DOS_PARQUES.shp'
    rotas_presente_add = 'C:\\Arthur\\CDEC\\Parte 2\\Dados\\Rotas\\presente\\presente.shp'
    add_subestacao_presente = 'C:\\Arthur\\CDEC\\Parte 2\\Dados\\Subestacao\\Subestações___Base_Existente.shp'
    rotas_futuro_add ='C:\\Arthur\\CDEC\\Parte 2\\Dados\\Rotas\\futuro\\futuro.shp'
    add_subestacao_futuro = 'C:\\Arthur\\CDEC\\Parte 2\\Dados\\Subestacao\\Subestações___planejado.shp'
    add_mapa_presente = r'C:\Arthur\CDEC\Parte 2\Dados\Mapa Adequabilidade\mapa_adequabilidade_cenario_presente.tif'
    add_mapa_futuro = r'C:\Arthur\CDEC\Parte 2\Dados\Mapa Adequabilidade\mapa_adequabilidade_cenario_futuro.tif'
    
    '''
    add_linha = '~/Dados/Rotas/presente/presente.shp'
    add_rest = '~/Dados/restrições/restrições.shp'
    parques_add = '~/Dados/centroide parque/CENTROIDE_DOS_PARQUES.shp'
    rotas_presente_add = '~/Dados/Rotas/presente/presente.shp'
    add_subestacao_presente = '~/Dados/Subestacao/Subestações___Base_Existente.shp'
    rotas_futuro_add ='~/Dados/Rotas/futuro/futuro.shp'
    add_subestacao_futuro = '~/Dados/Subestacao/Subestações___planejado.shp'
    add_mapa_presente = 'mapa_adequabilidade_cenario_presente.tif'
    add_mapa_futuro = 'mapa_adequabilidade_cenario_futuro.tif'
    '''
    custom_hover = custom_hoover()
    # As we've seen, the coordinates in our dataset were called x and y, so we are 
    # going to use these.
    key_dimensions = ['x', 'y']
    
    # We are also going to need the name of the value stored in the file. We get it 
    # from there this time, but we could also set this manually.
    
    value_dimension = 'adequabilidade'
    
    
    dataarray = rxr.open_rasterio(add_mapa_presente)
    dataarray = dataarray.rio.reproject("EPSG:3857")
    dataarray = dataarray.where(dataarray!=-9999)
    dataarray.values[dataarray.values==9999999]=np.nan
    hv_dataset = hv.Dataset(dataarray[0], vdims=value_dimension, kdims=key_dimensions)
    hv_mapa_presente = hv.Image(hv_dataset).opts(title='first image',tools=[custom_hover])
    #hv_mapa_presente = hd.regrid(hv_image_basic)
    
    dataarray = rxr.open_rasterio(add_mapa_futuro)
    dataarray = dataarray.rio.reproject("EPSG:3857")
    dataarray = dataarray.where(dataarray!=-9999)
    dataarray.values[dataarray.values==9999999]=np.nan
    hv_dataset = hv.Dataset(dataarray[0], vdims=value_dimension, kdims=key_dimensions)
    hv_mapa_futuro = hv.Image(hv_dataset).opts(title='first image',tools=[custom_hover])
    #hv_mapa_futuro = hd.regrid(hv_image_basic)
    
    
    
    
    gdf_parques = gpd.read_file(parques_add)
    gdf_parques = gdf_parques.to_crs(3857)
    spd_parques = spd.GeoDataFrame(gdf_parques)
    spd_parques_plot = spd_parques.hvplot('green')
    
    gdf_rest = gpd.read_file(add_rest)
    gdf_rest = gdf_rest.to_crs(3857)
    spd_rest = spd.GeoDataFrame(gdf_rest)
    spd_rest_plot= spd_rest.hvplot()
    
    hv_tiles_osm = hv.element.tiles.OSM()
    
    '''Futuro'''
    
    gdf_rotas_futuro = gpd.read_file(rotas_futuro_add)
    gdf_rotas_futuro = gdf_rotas_futuro.to_crs(3857)
    spd_rotas_futuro = spd.GeoDataFrame(gdf_rotas_futuro)
    spd_rotas_futuro_plot = spd_rotas_futuro.hvplot(color='purple')
    
    
    gdf_subestacao_futuro = gpd.read_file(add_subestacao_futuro)
    gdf_subestacao_futuro = gdf_subestacao_futuro.to_crs(3857)
    spd_subestacao_futuro = spd.GeoDataFrame(gdf_subestacao_futuro)
    spd_subestacao_futuro_plot = spd_subestacao_futuro.hvplot(color='orange')
    
    mapa_futuro = hv_mapa_futuro*spd_rotas_futuro_plot*spd_subestacao_futuro_plot*spd_parques_plot
    #mapa_futuro = mapa_futuro.redim.range(x=(-4381081.311458136,-3688761.035147772), y=(-780122.6323542815,-495123.75722469855))
    #print(mapa_futuro)
    
    
    
    '''Passado'''
    gdf_rotas_presente = gpd.read_file(rotas_presente_add)
    gdf_rotas_presente = gdf_rotas_presente.to_crs(3857)
    spd_rotas_presente = spd.GeoDataFrame(gdf_rotas_presente)
    spd_rotas_presente_plot = spd_rotas_presente.hvplot(color='yellow')
    
    gdf_subestacao_presente = gpd.read_file(add_subestacao_presente)
    gdf_subestacao_presente = gdf_subestacao_presente.to_crs(3857)
    spd_subestacao_presente = spd.GeoDataFrame(gdf_subestacao_presente)
    spd_subestacao_presente_plot = spd_subestacao_presente.hvplot(color='pink')
    
    mapa_passado = hv_mapa_presente*spd_rotas_presente_plot*spd_subestacao_presente_plot*spd_parques_plot
    #mapa_passado = mapa_passado.redim.range(x=(-4381081.311458136,-3688761.035147772), y=(-780122.6323542815,-495123.75722469855))
    
    
    #mapa= spd_rest.hvplot(responsive=True)
    mapa= spd_rest.hvplot()
    #mapa = mapa.redim.range(x=(-4381081.311458136,-3688761.035147772), y=(-780122.6323542815,-495123.75722469855))
    plot= mapa_passado
    
    startX,endX = mapa.range('x')
    startY,endY = mapa.range('y')
    #OldStartX,OldEndX = mapa.range('x')
    #OldStartY,OldEndY = mapa.range('y')
    
    radio = param.Selector(default='Cenário Presente',objects=['Cenário Presente','Cenário Futuro'])
    
    
    
    def keep_zoom(self,x_range,y_range):
        print('zoom')
        self.startX,self.endX = x_range
        self.startY,self.endY = y_range
        print(x_range)
        print(y_range)
        
        

    @param.depends('radio')
    def view(self,x_range,y_range):
        if self.radio == 'Cenário Presente':
            self.plot= self.mapa_passado
        else:
            self.plot= self.mapa_futuro
        
        '''
        rangexy = streams.RangeXY(source = self.mapa, 
                                  x_range=(self.startX,self.endX), 
                                  y_range=(self.startY,self.endY)
                                  )
        rangexy.add_subscriber(self.keep_zoom)
        print(rangexy)
        '''
        
        if math.isnan(self.startX) == False:
            print('Not None')
            self.OldStartX = self.startX
            self.OldEndX = self.endX
            self.OldStartY = self.startY
            self.OldEndY = self.endY
            
            print(self.startX)
            print(self.OldStartX)
            
            self.mapa = self.mapa.redim.range(x=(self.startX,self.endX), y=(self.startY,self.endY))
        else:
            print('Is None')
            self.mapa = self.mapa.redim.range(x=(self.OldStartX,self.OldEndX), y=(self.OldStartY,self.OldEndY))
            print(self.startX,self.endX)
            print(self.OldStartX,self.OldEndX)
            
        self.plot = (self.hv_tiles_osm*self.mapa*self.plot).opts(active_tools=['pan','wheel_zoom'])
        print(self.plot)
        print(self.mapa)
        return self.plot
    
    
viewer = AppTest()

rangexy = streams.RangeXY(source = viewer.mapa, 
                          x_range=(viewer.startX,viewer.endX), 
                          y_range=(viewer.startY,viewer.endY)
                          )
rangexy.add_subscriber(viewer.keep_zoom)

#stock_dmap2 = hv.DynamicMap(viewer.keep_zoom,streams=[rangexy]).opts(align = 'center')
stock_dmap = hv.DynamicMap(viewer.view,streams=[rangexy],).opts(align = 'center',responsive=True)

bootstrap.main.append(
    pn.Column(pn.Param(viewer.param,
                widgets={'radio':pn.widgets.RadioButtonGroup}
                ),
       stock_dmap
       )
    )
bootstrap.show()

Hi @ArthurDF

Welcome to the community :+1:

I would recommend you try to reduce the complexity of the example to make it easier for others to help. You should make a Minimal, Reproducible Example

What catches my eye is

  • The code example is very long. This means it takes a lot of time for the one trying to help to just get a basic understanding of the problem.
  • The file references are absolute referring to paths on your laptop meaning that any one trying to help needs to fix that before trying to help.

Hope this makes sense?

1 Like

Thanks for the recomendations

I’ve reduced the code and also put some coments in it to make it easier to read, I also removed the local files dependencies in order for it to be easier to run

Once again thanks for the help

import panel as pn
import holoviews as hv
from holoviews import streams
import param
import spatialpandas as spd
import geopandas as gpd



hv.extension('bokeh')
pn.extension(sizing_mode = 'stretch_width')


class AppTest(param.Parameterized):
    '''Defining the scenarios with a Param Selector'''
    radio = param.Selector(default='Present',objects=['Present','Future'])
    
    '''Link of files'''
    add_rest = 'https://raw.githubusercontent.com/ArthurDF/SEDEC-RN/main/restri%C3%A7%C3%B5es.json'
    
    
    '''Generating the HVplots to be used on solution (using Spatial Pandas 
    because for some reason when I tried to geopandas.hvplot on heroku it didn't work)'''
    
    #this HVPLOT will be ploted on both scenarios and will be the reference for the RangeXY Stream
    gdf_rest = gpd.read_file(add_rest)
    gdf_rest = gdf_rest.to_crs(3857)
    spd_rest = spd.GeoDataFrame(gdf_rest)
    mapa= spd_rest.hvplot(color='blue')
    
    '''Use the previous files to create 2 example plots for the problem'''
    scenario_1 = spd_rest.hvplot(color='pink')
    scenario_2 = spd_rest.hvplot(color='orange')
    
    startX,endX = mapa.range('x')
    startY,endY = mapa.range('y')
    
    def keep_zoom(self,x_range,y_range):
        self.startX,self.endX = x_range
        self.startY,self.endY = y_range
        
    @param.depends('radio')
    def view(self,x_range,y_range):
        if self.radio == 'Present':
            self.plot= self.scenario_1
        else:
            self.plot= self.scenario_2
        
        
        self.mapa = self.mapa.redim.range(x=(self.startX,self.endX), y=(self.startY,self.endY))
        
        self.plot = (self.mapa*self.plot).opts(active_tools=['pan','wheel_zoom'])
        
        return self.plot

'''Create app and display'''
viewer = AppTest()

rangexy = streams.RangeXY(source = viewer.mapa, 
                          x_range=(viewer.startX,viewer.endX), 
                          y_range=(viewer.startY,viewer.endY)
                          )
rangexy.add_subscriber(viewer.keep_zoom)

stock_dmap = hv.DynamicMap(viewer.view,streams=[rangexy],).opts(align = 'center',responsive=True)
pn.Column(pn.Param(viewer.param,
            widgets={'radio':pn.widgets.RadioButtonGroup}
            ),
   stock_dmap
   ).show()

Managed to solve the problem

I’ve reworked the code from the ground up and tested all the components until I’ve found the line that caused my problem

I don’t know for sure what I did to solve but

  • I´ve removed the “keep_zoom” method
  • I’ve removed “keep_zoom” to the subscribers list of the RangeXY stream
  • I’ve did the redim of the reference map atribute ‘self.mapa’ at the begining of the “self.view” call
  • I’ve encapsulated the DynamicMap called “stock_map” at an pn.GridSpec layout panel
  • whetever possible I’ve used ‘responsive=True’ when I called an hvplot()

Bellow is the pseudocode of the solution (I’ll put both raster and vector hvplots because one thing that I’ve found out when doing this little project is that is not that common to see this 2 togueter in Examples so if someone could see this it would sabe them a lot of trouble)

'''Create Class App'''
class AppTest(param.Parameterized):
'''Defining the param Selector that will be linked to the radioButton Widget that will be used to select the map that will be displayed (Scenario 1 or 2)'''
    radio = param.Selector(default='Present',objects=['Scenario 1','Scenario 2'])

'''Create the Hvplot of the vector files in scenario 1'''
    gdf_scenario_1 = gpd.read_file(scenarios_1_file_path)
    spd_scenario_1 = spd.GeoDataFrame(gdf_scenario_1 )
    spd_scenario_1 _plot = spd_scenario_1.hvplot('green',responsive=True)

'''Create the Hvplot of the vector files in scenario 2'''
    gdf_scenario_2 = gpd.read_file(scenarios_2_file_path)
    spd_scenario_2 = spd.GeoDataFrame(gdf_scenario_2 )
    spd_scenario_2 _plot = spd_scenario_2.hvplot('green',responsive=True)

'''Create the Hvplot of the vector files that will be present in both scenario and that will be tracked with the rangeXY stream'''
    gdf_rest= gpd.read_file(rest_file_path)
    spd_rest= spd.GeoDataFrame(gdf_rest)
    map= spd_rest.hvplot(color='blue',responsive=True)

'''Create the hvplot of a raster'''
    value_dimension = 'adequabilidade'
    key_dimensions = ['x', 'y']

    dataarray = rxr.open_rasterio(raster_file_path)
    hv_dataset = hv.Dataset(dataarray[0], vdims=value_dimension, kdims=key_dimensions)
    hv_raster = hv.Image(hv_dataset).opts(responsive=True)

    scenario_1 =hv_raster *spd_scenario_1 
    scenario_2 =hv_raster *spd_scenario_2 _plot

'''Get a background tile'''
    hv_tiles_osm = hv.element.tiles.OSM()
    
'''get the start and the end of the original map(this is as far as I know required for the stream rangeXY)'''
    startX,endX = mapa.range('x')
    startY,endY = mapa.range('y')
'''Create the first plot to be displayed'''    
    plot = hv_tiles_osm*map*scenario_1

    @param.depends('radio')
    def view(self,x_range,y_range):

        self.map = self.map.redim.range(x=x_range, y=y_range)
        
        if self.radio == 'Scenario 1':
            self.plot= self.hv_tiles_osm*self.mapa*self.scenario_1
        else:
            self.plot= self.hv_tiles_osm*self.mapa*self.scenario_2
        
        return self.plot

'''Create fake layout just to display map'''
'''Create app and display'''
viewer = AppTest()

'''Create RangeXY Stream that will track the XY zoom level on the map(pay attention that the source is the viewer.map and not viewer.plot)'''
rangexy = streams.RangeXY(source = viewer.map, 
                          x_range=(viewer.startX,viewer.endX), 
                          y_range=(viewer.startY,viewer.endY)
                          )

'''Create DMap with responsive set as True(pass the rangeXY stream as well)'''
stock_dmap = hv.DynamicMap(viewer.view,streams=[rangexy],).opts(
    {'Overlay': dict(
                      align = 'center',
                      responsive=True
                      )
     }
    )
 
'''Create GridSpec inside of Row (I've found in this link:https://discourse.holoviz.org/t/holoview-pane-size-changes-unexpectedly-after-dynamicmap-update/3267
 that someone did this bellow and workedout so I've also tried and it did work out...
I've removed it and without this GridSpec the map would have no height for some reason, so I just keeped it)'''
plot_out = pn.Row(pn.GridSpec(sizing_mode='stretch_both'), sizing_mode='stretch_both')
plot_out[0][0,0] = stock_dmap

'''Created test layout and checked the result'''
layout = pn.Column(pn.Param(viewer.param,
                widgets={'radio':pn.widgets.RadioButtonGroup}
                ),
       plot_out
       )
    
layout .show()

1 Like