Skip to content

Instantly share code, notes, and snippets.

@MarcSkovMadsen
Last active September 1, 2021 07:47
Show Gist options
  • Save MarcSkovMadsen/c457538cc0a3b60fb248a2c9dfdebdf3 to your computer and use it in GitHub Desktop.
Save MarcSkovMadsen/c457538cc0a3b60fb248a2c9dfdebdf3 to your computer and use it in GitHub Desktop.
Examples of Parent-Child related selections using HoloViz Panel
import panel as pn
import pandas as pd
import param
pn.extension(sizing_mode="stretch_width")
LOGO = "https://holoviz.org/assets/panel.png"
ACCENT_BASE_COLOR = "#4099da"
SELECTED_COLOR = "#2c6b98"
DATA_URL = "https://raw.githubusercontent.com/lukes/ISO-3166-Countries-with-Regional-Codes/master/all/all.csv"
try:
DATA = pd.read_csv("country_data.csv")
except:
DATA = pd.read_csv(DATA_URL)
DATA.to_csv("country_data.csv", index=False)
DATA=DATA.dropna(subset=["region"])
# GENERAL, REUSABLE CODE START
def to_sorted_list_of_cleaned_values(lst):
return list(sorted(str(val) for val in lst))
def get_parents(data, parent_column):
return to_sorted_list_of_cleaned_values(data[parent_column].unique())
def get_children(parent, data, parent_column, child_column):
if parent:
return to_sorted_list_of_cleaned_values(data[data[parent_column]==parent][child_column].unique())
def to_parent_child_dict(data, parent_column, child_column):
parents = get_parents(data, parent_column)
parent_child_dict = {}
for parent in parents:
parent_child_dict[parent]=get_children(parent, data, parent_column, child_column)
return parents, parent_child_dict
def configure_parent_child_parameters(obj, parent_parameter, child_parameter, parents, parent_child_map):
obj.param[parent_parameter].objects=parents
if parents:
setattr(obj, parent_parameter, parents[0])
def _update_child_widget(parent):
# Depending on the use case the parent_child_map could be a lookup function instead
objects = parent_child_map[parent]
obj.param[child_parameter].objects = objects
if getattr(obj, child_parameter) in objects:
pass
elif objects:
setattr(obj, child_parameter, objects[0])
pn.bind(_update_child_widget, parent=obj.param[parent_parameter], watch=True)
_update_child_widget(getattr(obj, parent_parameter))
# GENERAL, REUSABLE CODE END
class Selection(param.Parameterized):
region = param.Selector()
country = param.Selector()
_widgets = {
"region": {"max_width": 293},
"country": {"max_width": 293},
}
def __init__(self, **params):
super().__init__(**params)
regions, region_country_map = to_parent_child_dict(DATA, "region", "name")
configure_parent_child_parameters(self, "region", "country", regions, region_country_map)
self._panel = pn.Param(self, widgets=self._widgets)
def __panel__(self):
return self._panel
selection = Selection()
def get_country_info(country):
return DATA[DATA["name"]==country].T
get_country_info=pn.bind(get_country_info, country=selection.param.country)
pn.template.FastListTemplate(
site="Awesome Panel",
logo=LOGO,
title="Parent-Child Selections Example",
sidebar=["## Selection", selection,],
main=[pn.Column("## Country Info", get_country_info)],
accent_base_color=ACCENT_BASE_COLOR,
header_background=ACCENT_BASE_COLOR,
).servable()
import panel as pn
import pandas as pd
import param
pn.extension(sizing_mode="stretch_width")
LOGO = "https://holoviz.org/assets/panel.png"
ACCENT_BASE_COLOR = "#4099da"
SELECTED_COLOR = "#2c6b98"
DATA_URL = "https://raw.githubusercontent.com/lukes/ISO-3166-Countries-with-Regional-Codes/master/all/all.csv"
try:
DATA = pd.read_csv("country_data.csv")
except:
DATA = pd.read_csv(DATA_URL)
DATA.to_csv("country_data.csv", index=False)
DATA=DATA.dropna(subset=["region"])
# GENERAL, REUSABLE CODE START
def to_sorted_list_of_cleaned_values(lst):
return list(sorted(str(val) for val in lst))
def get_parents(data, parent_column):
return to_sorted_list_of_cleaned_values(data[parent_column].unique())
def get_children(parent, data, parent_column, child_column):
if parent:
return to_sorted_list_of_cleaned_values(data[data[parent_column]==parent][child_column].unique())
def to_parent_child_dict(data, parent_column, child_column):
parents = get_parents(data, parent_column)
parent_child_dict = {}
for parent in parents:
parent_child_dict[parent]=get_children(parent, data, parent_column, child_column)
return parents, parent_child_dict
def configure_parent_child_widgets(parent_widget, child_widget, parents, parent_child_map):
parent_widget.options=parents
if parents:
parent_widget.value=parents[0]
print(parent_widget.value, parent_widget.options)
def _update_child_widget(parent):
# Depending on the use case the parent_child_map could be a lookup function instead
options = parent_child_map[parent]
child_widget.options = options
if child_widget.value in options:
pass
elif options:
child_widget.value = options[0]
pn.bind(_update_child_widget, parent=parent_widget, watch=True)
_update_child_widget(parent_widget.value)
# GENERAL, REUSABLE CODE START
region_widget = pn.widgets.Select(name="Region")
country_widget = pn.widgets.Select(name="Country", max_width=293)
regions, region_country_map = to_parent_child_dict(DATA, "region", "name")
configure_parent_child_widgets(region_widget, country_widget, regions, region_country_map)
def get_country_info(country):
return DATA[DATA["name"]==country].T
get_country_info=pn.bind(get_country_info, country=country_widget)
pn.template.FastListTemplate(
site="Awesome Panel",
logo=LOGO,
title="Parent-Child Selections Example",
sidebar=["## Selection", region_widget, country_widget],
main=[pn.Column("## Country Info", get_country_info)],
accent_base_color=ACCENT_BASE_COLOR,
header_background=ACCENT_BASE_COLOR,
).servable()
@MarcSkovMadsen
Copy link
Author

MarcSkovMadsen commented Sep 1, 2021

An example of parent-child related selections in HoloViz Panel.

You can serve the examples via panel serve parent_child_widgets.py or panel serve parent_child_parameters.py.

At first it might look like a lot of code. But most code is either something that you would have been writing for other reasons or re-useable code you could put in your utils.py file.

For example for configuring the widgets the core code is.

regions, region_country_map = to_parent_child_dict(DATA, "region", "name")
configure_parent_child_widgets(region_widget, country_widget, regions, region_country_map)

This could probably be further refactored to one line.

Some things to consider when creating these kinds of relations is

  • Should the lookup of child objects be based on a dictionary or function. If creating the dictionary is slow a function might be a good alternative.
  • How to clean and prepare the data. Here I've dropped nans, converted values to strings and sorted them.
panel-parent-child-relations.mp4

For further inspiration check out

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment