Skip to content

Commit

Permalink
Pipelines param docs (#145)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Nov 13, 2018
1 parent 5c55baa commit a13908c
Show file tree
Hide file tree
Showing 9 changed files with 716 additions and 18 deletions.
4 changes: 4 additions & 0 deletions doc/user_guide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ User Guide
* `Parameters <Param.html>`_
Using Param to express panels in a self-contained class.

* `Pipelines <Pipelines.html>`_
Using Parameterized classes to declare linear workflows containing multiple panels.

.. toctree::
:titlesonly:
:hidden:
Expand All @@ -27,3 +30,4 @@ User Guide
Widgets <Widgets>
Layouts <Layouts>
Parameters <Param>
Pipelines <Pipelines>
119 changes: 104 additions & 15 deletions examples/user_guide/Param.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -74,28 +74,21 @@
"metadata": {},
"outputs": [],
"source": [
"import panel\n",
"import panel as pn\n",
"\n",
"pn.extension()\n",
"\n",
"panel.extension()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"base = BaseClass()\n",
"panel.Row(Example, base)"
"pn.Row(Example, base)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As you can see, `panel` does not need to be provided with any knowledge of your domain-specific application, not even the names of your parameters; it simply displays widgets for whatever Parameters may have been defined on that object. Using Param with ``Panel`` thus achieves a nearly complete separation between your domain-specific code and your display code, making it vastly easier to maintain both of them over time. Here even the `msg` button behavior was specified declaratively, as an action that can be invoked (printing \"Hello\") independently of whether it is used in a GUI or other context.\n",
"As you can see, `Panel` does not need to be provided with any knowledge of your domain-specific application, not even the names of your parameters; it simply displays widgets for whatever Parameters may have been defined on that object. Using Param with ``Panel`` thus achieves a nearly complete separation between your domain-specific code and your display code, making it vastly easier to maintain both of them over time. Here even the `msg` button behavior was specified declaratively, as an action that can be invoked (printing \\\"Hello\\\") independently of whether it is used in a GUI or other context.\n",
"\n",
"Interacting with the widgets above is only supported on a live Python-backed server, but you can also export static renderings of the widgets to a file or web page. \n",
"Interacting with the widgets above is only supported in the notebook and on bokeh server, but you can also export static renderings of the widgets to a file or web page. \n",
"\n",
"By default, editing values in this way requires you to run the notebook cell by cell -- when you get to the above cell, edit the values as you like, and then move on to execute subsequent cells, where any reference to those parameter values will use your interactively selected setting:"
]
Expand All @@ -118,6 +111,22 @@
"Example.num_int"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The reverse is possible, editing a parameter from Python will automatically update any widgets that were generated from the parameter:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Example.int_list = [1, 7]"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand All @@ -134,20 +143,100 @@
"Example.timestamps"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As you can see, you can access the parameter values at the class level from within the notebook to control behavior explicitly, e.g. to select what to show in subsequent cells. Moreover, any instances of the Parameterized classes in your own code will now use the new parameter values unless specifically overridden in that instance, so you can now import and use your domain-specific library however you like, knowing that it will use your interactive selections wherever those classes appear. None of the domain-specific code needs to know or care that you used ``Panel``; it will simply see new values for whatever attributes were changed interactively. ``Panel`` thus allows you to provide notebook-specific, domain-specific interactive functionality without ever tying your domain-specific code to the notebook environment. However that is only the beginning!\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Parameter dependencies\n",
"Declaring parameters is usually only the beginning of a workflow, in most applications these parameters are then tied to some computation. To express the relationship between a computation and the parameters it depends on the ``param.depends`` decorator may be used on Parameterized methods. This provides a hint to ``panel`` and other libraries (e.g. HoloViews) that the method should be recomputed when a parameter changes.\n",
"\n",
"As a straightforward example without any additional dependencies we will write a small class which returns an ASCII representation of a sine wave, which depends on `phase` and `frequency` parameters. If we supply the ``.view`` method to a ``Panel`` layout it will automatically recompute and update the view when one or more of the parameters change:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#Example.print_param_defaults() # see all parameter values"
"import numpy as np\n",
"\n",
"class Sine(param.Parameterized):\n",
"\n",
" phase = param.Number(default=0, bounds=(0, np.pi))\n",
"\n",
" frequency = param.Number(default=1, bounds=(0.1, 2))\n",
"\n",
" @param.depends('phase', 'frequency')\n",
" def view(self):\n",
" y = np.sin(np.linspace(0, np.pi*3, 40)*self.frequency+self.phase)\n",
" y = ((y-y.min())/y.ptp())*20\n",
" array = np.array([list((' '*(int(round(d))-1) + '*').ljust(20)) for d in y])\n",
" return pn.pane.Str('\\n'.join([''.join(r) for r in array.T]), height=325, width=500)\n",
"\n",
"sine = Sine(name='ASCII Sine Wave')\n",
"pn.Row(sine.param, sine.view)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As you can see, you can access the parameter values at the class level from within the notebook to control behavior explicitly, e.g. to select what to show in subsequent cells. Moreover, any instances of the Parameterized classes in your own code will now use the new parameter values unless specifically overridden in that instance, so you can now import and use your domain-specific library however you like, knowing that it will use your interactive selections wherever those classes appear. None of the domain-specific code needs to know or care that you used Panel; it will simply see new values for whatever attributes were changed interactively. Panel thus allows you to provide notebook-specific, domain-specific interactive functionality without ever tying your domain-specific code to the notebook environment."
"The parameterized and annotated ``view`` method could return any one of the types handled by the [Pane objects](./Panes.ipynb) panel provides, making it easy to link parameters and their associated widgets to a plot or other output. Parameterized classes can therefore be a very useful pattern for encapsulating a part of a computational workflow with an associated visualization, declaratively expressing the dependencies between the parameters and the computation.\n",
"\n",
"Another common pattern is linking the values of one parameter to another parameter, e.g. when dependencies between parameters exist. In the example below we will define two parameters one for the continent and one for the country. Since we want the selection of valid countries to change when we change the continent we define a method to do that for us. In order to link the two we express the dependency using the ``param.depends`` decorator and then ensure that we want to run the method whenever the continent changes by setting ``watch=True``.\n",
"\n",
"Additionally we define a ``view`` method which returns an HTML iframe displaying the country in a Google Map."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class GoogleMapViewer(param.Parameterized):\n",
" \n",
" continent = param.ObjectSelector(default='Asia', objects=['Africa', 'Asia', 'Europe'])\n",
" \n",
" country = param.ObjectSelector(default='China', objects=['China', 'Thailand', 'Japan'])\n",
" \n",
" _countries = {'Africa': ['Ghana', 'Togo', 'South Africa', 'Tanzania'],\n",
" 'Asia' : ['China', 'Thailand', 'Japan'],\n",
" 'Europe': ['Austria', 'Bulgaria', 'Greece', 'Portugal', 'Switzerland']}\n",
" \n",
" @param.depends('continent', watch=True)\n",
" def _update_countries(self):\n",
" countries = self._countries[self.continent]\n",
" self.params('country').objects = countries\n",
" self.country = countries[0]\n",
"\n",
" @param.depends('country')\n",
" def view(self):\n",
" iframe = \"\"\"\n",
" <iframe width=\"800\" height=\"400\" src=\"http://maps.google.com/maps?q={country}&z=6&output=embed\"\n",
" frameborder=\"0\" scrolling=\"no\" marginheight=\"0\" marginwidth=\"0\"></iframe>\n",
" \"\"\".format(country=self.country)\n",
" return pn.pane.HTML(iframe, height=400)\n",
" \n",
"viewer = GoogleMapViewer(name='Google Map Viewer')\n",
"pn.Row(viewer.param, viewer.view)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Whenever the continent changes it will now eagerly execute the ``_update_countries`` method changing the list of countries that is displayed, which in turn triggers an update in the view method updating the map.\n",
"\n",
"In this user guide we have seen how to leverage ``param`` to declare parameters, which ``panel`` can turn into a GUI with no overhead. Additionally we have seen how to link parameters to views and other parameters using the ``param.depends`` operator. This approach allows building complex and reactive ``panels``. In the [next user guide](Pipelines.ipynb) we will discover how to link multiple such classes into pipelines, making it possible to encapsulate complex workflows in clean self-contained classes."
]
}
],
Expand Down
Loading

0 comments on commit a13908c

Please sign in to comment.