{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "import panel_material_ui as pmui\n", "\n", "pn.extension()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`NestedSelect` allows creating multi-level menus where options are dependent on each other in a hierarchical manner.\n", "\n", "Discover more on using widgets to add interactivity to your applications in the [how-to guides on interactivity](https://panel.holoviz.org/how_to/interactivity/index.html). Alternatively, learn [how to set up callbacks and (JS-)links between parameters](https://panel.holoviz.org/how_to/links/index.html) or [how to use them as part of declarative UIs with Param](https://panel.holoviz.org/how_to/param/index.html).\n", "\n", ":::note\n", "The `NestedSelect` examples rely on a live Python process to update. Run the cells in this notebook to see the examples in action.\n", ":::\n", "\n", "#### Parameters:\n", "\n", "For details on other options for customizing the component see the [customization guides](https://panel-material-ui.holoviz.org/customization/index.html).\n", "\n", "\n", "##### Core\n", "\n", "* **`disabled`** (boolean): Whether the widget is editable\n", "* **`levels`** (list): Either a list of strings or a list of dictionaries. If a list of strings, the strings are used as the names of the levels. If a list of dictionaries, each dictionary may have a \"name\" key, which is used as the name of the level, a \"type\" key, which is used as the type of widget, and any corresponding widget keyword arguments. Must be specified if options is callable.\n", "* **`options`** (dict | callable): The options to select from. The options may be nested dictionaries, lists, or callables that return those types. If callables are used, the callables must accept `level` and `value` keyword arguments, where `level` is the level that updated and `value` is a dictionary of the current values, containing keys up to the level that was updated.\n", "* **`value`** (dict): The value from all the Select widgets; the keys are the levels names. If no levels names are specified, the keys are the levels indices.\n", "\n", "##### Display\n", "\n", "* **`layout`** (ListPanel | dict): The layout type of the widgets. If a dictionary, a \"type\" key can be provided, to specify the layout type of the widgets, and any additional keyword arguments will be used to instantiate the layout.\n", "* **`label`** (str): The title of the widget\n", "\n", "___" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "select = pmui.NestedSelect(\n", " options={\n", " \"GFS\": {\n", " \"0.25 deg\": [\"00Z\", \"06Z\", \"12Z\", \"18Z\"],\n", " \"0.5 deg\": [\"00Z\", \"12Z\"],\n", " \"1 deg\": [\"00Z\", \"12Z\"],\n", " },\n", " \"NAME\": {\n", " \"12 km\": [\"00Z\", \"12Z\"],\n", " \"3 km\": [\"00Z\", \"12Z\"],\n", " },\n", " },\n", " levels=[\"Model\", \"Resolution\", \"Initialization\"],\n", ")\n", "select" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Like most other widgets, ``NestedSelect`` has a value parameter that can be accessed or set:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "select.value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A different `layout` type can be provided to the `NestedSelect` to change the layout of the widgets." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "select = pmui.NestedSelect(\n", " options={\n", " \"GFS\": {\n", " \"0.25 deg\": [\"00Z\", \"06Z\", \"12Z\", \"18Z\"],\n", " \"0.5 deg\": [\"00Z\", \"12Z\"],\n", " \"1 deg\": [\"00Z\", \"12Z\"],\n", " },\n", " \"NAME\": {\n", " \"12 km\": [\"00Z\", \"12Z\"],\n", " \"3 km\": [\"00Z\", \"12Z\"],\n", " },\n", " },\n", " levels=[\"Model\", \"Resolution\", \"Initialization\"],\n", " layout=pn.Row\n", ")\n", "select" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If a dict is provided, a \"type\" key can be provided, to specify the layout type of the widgets, and any additional keyword arguments will be used to instantiate the layout." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "select = pmui.NestedSelect(\n", " options={\n", " \"GFS\": {\n", " \"0.25 deg\": [\"00Z\", \"06Z\", \"12Z\", \"18Z\"],\n", " \"0.5 deg\": [\"00Z\", \"12Z\"],\n", " \"1 deg\": [\"00Z\", \"12Z\"],\n", " },\n", " \"NAME\": {\n", " \"12 km\": [\"00Z\", \"12Z\"],\n", " \"3 km\": [\"00Z\", \"12Z\"],\n", " },\n", " },\n", " levels=[\"Model\", \"Resolution\", \"Initialization\"],\n", " layout={\"type\": pn.GridBox, \"ncols\": 2}\n", ")\n", "select" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If ``levels`` names are not set, the value is keyed off the level index." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "select = pmui.NestedSelect(\n", " options={\n", " \"GFS\": {\n", " \"0.25 deg\": [\"00Z\", \"06Z\", \"12Z\", \"18Z\"],\n", " \"0.5 deg\": [\"00Z\", \"12Z\"],\n", " \"1 deg\": [\"00Z\", \"12Z\"],\n", " },\n", " \"NAME\": {\n", " \"12 km\": [\"00Z\", \"12Z\"],\n", " \"3 km\": [\"00Z\", \"12Z\"],\n", " },\n", " },\n", ")\n", "select.value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also define the default value by providing a dict." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "select = pmui.NestedSelect(\n", " options={\n", " \"GFS\": {\n", " \"0.25 deg\": [\"00Z\", \"06Z\", \"12Z\", \"18Z\"],\n", " \"0.5 deg\": [\"00Z\", \"12Z\"],\n", " \"1 deg\": [\"00Z\", \"12Z\"],\n", " },\n", " \"NAME\": {\n", " \"12 km\": [\"00Z\", \"12Z\"],\n", " \"3 km\": [\"00Z\", \"12Z\"],\n", " },\n", " },\n", " value={\"Model\": \"NAME\", \"Resolution\": \"12 km\", \"Initialization\": \"12Z\"},\n", " levels=[\"Model\", \"Resolution\", \"Initialization\"],\n", ")\n", "select" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Not all keys of the value need to be specified, and the keys can be specified in any order." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "select = pmui.NestedSelect(\n", " options={\n", " \"GFS\": {\n", " \"0.25 deg\": [\"00Z\", \"06Z\", \"12Z\", \"18Z\"],\n", " \"0.5 deg\": [\"00Z\", \"12Z\"],\n", " \"1 deg\": [\"00Z\", \"12Z\"],\n", " },\n", " \"NAME\": {\n", " \"12 km\": [\"00Z\", \"12Z\"],\n", " \"3 km\": [\"00Z\", \"12Z\"],\n", " },\n", " },\n", " value={\"Initialization\": \"12Z\", \"Resolution\": \"0.5 deg\"},\n", " levels=[\"Model\", \"Resolution\", \"Initialization\"],\n", ")\n", "select" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An incomplete definition of options can also be used. The corresponding subsequent widgets will be hidden." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "select = pmui.NestedSelect(\n", " options={\n", " \"NAME\": {},\n", " \"GFS\": {\n", " \"0.25 deg\": [\"00Z\", \"06Z\", \"12Z\", \"18Z\"],\n", " \"0.5 deg\": [\"00Z\", \"12Z\"],\n", " \"1 deg\": [\"00Z\", \"12Z\"],\n", " },\n", " },\n", " levels=[\"Model\", \"Resolution\", \"Initialization\"],\n", ")\n", "select" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The value for the hidden widgets will be `None`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "select.value = {\"Model\": \"NAME\"}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, `options` can be a callable. Its nested levels too: e.g. `options={\"Daily\": list_options, \"Monthly\": list_options}`.\n", "\n", "If callables are used, the callables must accept `level` and `value` keyword arguments, where `level` is the level that updated and `value` is a dictionary of the current values, containing keys up to the level that was updated.\n", "\n", "Note, the callable can vary across `options`, and `levels` must be provided if any of the `options` is callable." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def list_options(level, value):\n", " if level == \"time_step\":\n", " options = {\"Daily\": list_options, \"Monthly\": list_options}\n", " elif level == \"level_type\":\n", " options = {f\"{value['time_step']}_upper\": list_options, f\"{value['time_step']}_lower\": list_options}\n", " else:\n", " options = [f\"{value['level_type']}.json\", f\"{value['level_type']}.csv\"]\n", "\n", " return options\n", "\n", "pmui.NestedSelect(\n", " options=list_options,\n", " levels=[\"time_step\", \"level_type\", \"file_type\"],\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is useful if you are trying to use options from a hosted source.\n", "\n", "Using `pn.cache` here can help improve user experience and reduce the risk of rate limits.\n", "\n", "```python\n", "import panel as pn\n", "pn.extension()\n", "\n", "@pn.cache()\n", "def list_options(level, value):\n", " value_path = \"/\".join(list(value.values()))\n", " url = f\"https://downloads.psl.noaa.gov/Datasets/ncep.reanalysis/{value_path}\"\n", "\n", " options = [var.rstrip(\"/\") for var in pd.read_html(url)[0][\"Name\"].dropna()[1:]]\n", " if level == \"time_step\":\n", " options = {option: list_options for option in options if option[0].isupper()}\n", " elif level == \"level_type\":\n", " options = {option: list_options for option in options if option[0].islower()}\n", " else:\n", " options = [option for option in options if option.endswith(\".nc\")]\n", "\n", " return options\n", "\n", "\n", "select = pn.widgets.NestedSelect(\n", " options=list_options,\n", " levels=[\"time_step\", \"level_type\", \"file\"],\n", ")\n", "select\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`levels` also accepts a list of dicts, where each dict contains the type of widget and its corresponding kwargs." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "select = pmui.NestedSelect(\n", " options={\n", " \"GFS\": {\n", " \"0.25 deg\": [\"00Z\", \"06Z\", \"12Z\", \"18Z\"],\n", " \"0.5 deg\": [\"00Z\", \"12Z\"],\n", " \"1 deg\": [\"00Z\", \"12Z\"],\n", " },\n", " \"NAME\": {\n", " \"12 km\": [\"00Z\", \"12Z\"],\n", " \"3 km\": [\"00Z\", \"12Z\"],\n", " },\n", " },\n", " value={\"Model\": \"NAME\", \"Resolution\": \"12 km\", \"Initialization\": \"00Z\"},\n", " levels=[\n", " {\"name\": \"Model\", \"type\": pmui.RadioButtonGroup, \"button_type\": \"primary\"},\n", " {\"name\": \"Resolution\", \"type\": pmui.Select, \"width\": 100},\n", " {\"name\": \"Initialization\", \"type\": pmui.DiscreteSlider, \"width\": 100},\n", " ],\n", ")\n", "select" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.2" } }, "nbformat": 4, "nbformat_minor": 4 }