I’m trying to capture user’s lasso point selections and update the categorical labels for those points using a dynamicmap. It seems like the parameterized class instance is updating (I can print the .param.get_param_values() and see the updated dict); but I think I am missing something on how streams work, because the dynamicmap doesn’t seem to update the plot (point colors don’t change, despite the change in the vdim that the cmap is based on).
Anyone else who’s used this kind of UI pattern before have some hints?
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]
for city in selected_cities:
temp_dict[city] = self.territory_picklist.value
self.terrs.param.set_param(terr_map=temp_dict)
print(self.terrs.param.get_param_values()) # from printouts; it seems like parameter is being updated, but color of points remains unchanged
else:
print('no selection')
def createApp():
geoplt = Point_Remap_Test()
gspec = pn.GridSpec(sizing_mode='stretch_both', max_height=800,)
#context switcher
gspec[0,0:4] = pn.Column("#New Territory",pn.Row(geoplt.territory_picklist,geoplt.button))
#map
gspec[1:4,1:4] = pn.pane.HoloViews(geoplt.plot, sizing_mode="stretch_both")
return gspec
test = createApp()
test