11

I have a Select widget that should give a different list of options whenever another Select widget is changed, so it updates whenever this other Select widget changes. How do I this in the example code below?

Select Dropdown dependent on changes of another dropdown

 _countries = {
    'Africa': ['Ghana', 'Togo', 'South Africa'],
    'Asia'  : ['China', 'Thailand', 'Japan'],
    'Europe': ['Austria', 'Bulgaria', 'Greece']
}

continent = pn.widgets.Select(
    value='Asia', 
    options=['Africa', 'Asia', 'Europe']
)

country = pn.widgets.Select(
    value=_countries[continent.value][0], 
    options=_countries[continent.value]
)

@pn.depends(continent.param.value)
def _update_countries(continent):
    countries = _countries[continent]
    country.options = countries
    country.value = countries[0]

pn.Row(continent, country)
Sander van den Oord
  • 10,986
  • 5
  • 51
  • 96

1 Answers1

20

So, it took me forever to find this out, but in your @pn.depends() you have to add argument watch=True, so it constantly listens if changes are happening and updates to your other list should be done.
In this case:

@pn.depends(continent.param.value, watch=True)

Whole example:

_countries = {
    'Africa': ['Ghana', 'Togo', 'South Africa'],
    'Asia'  : ['China', 'Thailand', 'Japan'],
    'Europe': ['Austria', 'Bulgaria', 'Greece']
}

continent = pn.widgets.Select(
    value='Asia', 
    options=['Africa', 'Asia', 'Europe']
)

country = pn.widgets.Select(
    value=_countries[continent.value][0], 
    options=_countries[continent.value]
)

@pn.depends(continent.param.value, watch=True)
def _update_countries(continent):
    countries = _countries[continent]
    country.options = countries
    country.value = countries[0]

pn.Row(continent, country)

The example of the GoogleMapViewer on this page pointed me in the right direction:
Selector updates after another selector is changed

The same answer but then in the form of a Class:

class GoogleMapViewer(param.Parameterized):

    continent = param.Selector(default='Asia', objects=['Africa', 'Asia', 'Europe'])

    country = param.Selector(default='China', objects=['China', 'Thailand', 'Japan'])

    _countries = {'Africa': ['Ghana', 'Togo', 'South Africa'],
                  'Asia'  : ['China', 'Thailand', 'Japan'],
                  'Europe': ['Austria', 'Bulgaria', 'Greece']}

    @param.depends('continent', watch=True)
    def _update_countries(self):
        countries = self._countries[self.continent]
        self.param['country'].objects = countries
        self.country = countries[0]

viewer = GoogleMapViewer(name='Google Map Viewer')
pn.Row(viewer.param)
Sander van den Oord
  • 10,986
  • 5
  • 51
  • 96
  • 2
    Right; you need `watch=True` if you want Panel to automatically execute the function when the watched parameter changes. Otherwise the dependency information is declared but not acted on, which is important when Panel is used with various frameworks that read those dependencies, but here you want Panel to be in charge of everything and thus to explicitly request watching. – James A. Bednar Sep 11 '19 at 17:19
  • Is there a consistent reason for using `param.value` instead of not using it? In this case (non-Class example) the decorator works just as well when written as `@pn.depends(continent, watch=True)` – MyCarta Jul 01 '20 at 01:09
  • 1
    @MyCarta don't know, was just happy this worked :) If you are looking for a better answer, you should really checkout https://discourse.holoviz.org/ which is the official forum for all holoviews / panel / pyviz questions – Sander van den Oord Jul 01 '20 at 15:24
  • @SandervandenOord thank you, I already use that forum but because yor question/answer were useful and I'd been wondering about this before, I thought I'd ask here first. – MyCarta Jul 01 '20 at 21:14
  • @SandervandenOord this works great thanks. 1 issue that I am having is that it seems like the ```_update_countries()``` function is running twice? In my version you select a ```store location``` from 1st dropdown (ie ```continent``` in your eg) and set a ```buffer radius``` with the 2nd dropdown (```country``` in your eg). If the user selects a new ```store location``` then ```buffer_radius``` automatically sets to 5 miles. While this works, about 10 seconds after my result is presented, everything refreshes as if the function ran again. Is this issue to do with using ```watch=True```? – mmTmmR Oct 21 '20 at 14:12
  • yes, it can be, this package is still updated continuously, you could try without the watch=True and see what happens then. – Sander van den Oord Oct 21 '20 at 14:28
  • When I remove ```watch=True``` then the function no longer runs twice, however the ```buffer_radius``` also no longer resets to 5 miles when a new ```store_location``` is chosen. I saw your other post: https://stackoverflow.com/questions/58114082/why-is-my-plot-updated-by-panel-twice-when-i-change-a-button-setting-that-shou but I didn't really understand the answer relating to the running twice issue. Did you manage to solve this problem with your plot? – mmTmmR Oct 21 '20 at 16:23
  • i'm afraid i can't help you any further, but there's a dedicated holoviews/panel forum here, they might be able to help you: https://discourse.holoviz.org/ – Sander van den Oord Oct 21 '20 at 19:11
  • I think if I read the answer you refer to, the triggering twice could have to do with the fact that _countries is not a param itself in the example where I use a class, which triggers unwanted behavior. Maybe it should be defined as follows: _countries = param.Dict(default={'Africa': ['Ghana', 'Togo', 'South Africa'], 'Asia' : ['China', 'Thailand', 'Japan'],'Europe': ['Austria', 'Bulgaria', 'Greece']}) – Sander van den Oord Oct 21 '20 at 19:22