Param class with Parameters dependent on class instance arguments?

Hi all, I’m struggling with argument instantiation of param.Parameterized class.

I’ll use Google Map Viewer app from awesome-panel as a minimal example (see code below).

In this example I would like to create an instance
viewer = GoogleMapViewer(_countries=<some_dict>)
which can take a dictionary argument for _countries. The continents and countries in the app should then be according to this dictionary.

I tried to make _countries a param.Parameter. However, I can’t get continent and country to update with the provided class argument for _countries while keeping the functionality working.

Any suggestions?

import panel as pn
import param

class GoogleMapViewer(param.Parameterized):
    """An app showcasing how Param and Google Maps can be composed into an app
    using the FastGridTemplate"""

    continent = param.ObjectSelector(default="Asia", objects=["Africa", "Asia", "Europe"])

    country = param.ObjectSelector(default="China", objects=["China", "Thailand", "Japan"])

    settings_panel = param.Parameter()
    map_panel = param.Parameter()

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

    def __init__(self, **params):
        super().__init__(**params)
        self.settings_panel = pn.Param(self, parameters=["continent", "country"])
        self.map_panel = pn.pane.HTML(sizing_mode="stretch_both", height=616, margin=0)
        self._update_map()

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

    @param.depends("country", watch=True)
    def _update_map(self):
        iframe = """
        <iframe width="100%" height="100%" src="https://maps.google.com/maps?q={country}&z=6&output=embed"
        frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe>
        """.format(
            country=self.country
        )
        self.map_panel.object = iframe

g = GoogleMapViewer()
pn.Row(g.settings_panel, g.map_panel).show()

What I’d like to get working:

some_continent2countries_dict = {
        "Africa": ["Ghana", "Togo", "South Africa", "Tanzania"],
        "Asia": ["China", "Thailand", "Japan"],
        "Europe": ["Austria", "Bulgaria", "Greece", "Portugal", "Switzerland"],
        "South_America": ['Brazil','Argentina','Colombia']
    }
g = GoogleMapViewer(_countries=some_continent2countries_dict)
pn.Row(g.settings_panel, g.map_panel).show()

If you run the “what you’d like to get working” bit, you should see a warning that _countries is not a parameter, and in this case a good solution is to make countries be a parameter. Once it’s a parameter, you can set up dependencies on it just as the code already does for the country on the continent; it’s the same but now watching for changes in both the continent and the underlying dictionary of countries. I added precedence=-1 to indicate that this new countries parameter shouldn’t show up in a GUI:

import panel as pn
import param

class GoogleMapViewer(param.Parameterized):
    """An app showcasing how Param and Google Maps can be composed into an app
    using the FastGridTemplate"""

    continent = param.ObjectSelector(default="Asia", objects=["Africa", "Asia", "Europe"])

    country = param.ObjectSelector(default="China", objects=["China", "Thailand", "Japan"])

    settings_panel = param.Parameter()
    map_panel = param.Parameter()

    countries = param.Dict({
        "Africa": ["Ghana", "Togo", "South Africa", "Tanzania"],
        "Asia": ["China", "Thailand", "Japan"],
        "Europe": ["Austria", "Bulgaria", "Greece", "Portugal", "Switzerland"],
    }, precedence=-1)

    def __init__(self, **params):
        super().__init__(**params)
        self.settings_panel = pn.Param(self, parameters=["continent", "country"])
        self.map_panel = pn.pane.HTML(sizing_mode="stretch_both", height=616, margin=0)
        self._update_map()

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

    @param.depends("country", watch=True)
    def _update_map(self):
        iframe = """
        <iframe width="100%" height="100%" src="https://maps.google.com/maps?q={country}&z=6&output=embed"
        frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe>
        """.format(
            country=self.country
        )
        self.map_panel.object = iframe
some_continent2countries_dict = {
        "Africa": ["Togo", "South Africa", "Tanzania"],
        "Asia": ["China", "Japan"],
        "Europe": ["Austria", "Greece", "Portugal", "Switzerland"],
        "South_America": ['Brazil','Argentina','Colombia']
    }
g = GoogleMapViewer(countries=some_continent2countries_dict)
pn.Row(g.settings_panel, g.map_panel)