├── .appveyor.yml ├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── conda.recipe ├── README.md └── meta.yaml ├── dodo.py ├── examples └── user_guide │ ├── AdditionalFeatures.ipynb │ ├── JSONInit.ipynb │ ├── images │ ├── intro.gif │ └── stock_dashboard.png │ └── index.ipynb ├── paramnb ├── __init__.py ├── __main__.py ├── tests │ ├── __init__.py │ └── test_dummy.py ├── util.py ├── view.py └── widgets.py ├── pyproject.toml ├── setup.cfg ├── setup.py └── tox.ini /.appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | global: 3 | CHANS_DEV: "-c pyviz/label/dev" 4 | matrix: 5 | - PY: "2.7" 6 | CONDA: "C:\\Miniconda-x64" 7 | - PY: "3.6" 8 | CONDA: "C:\\Miniconda36-x64" 9 | 10 | install: 11 | - "SET PATH=%CONDA%;%CONDA%\\Scripts;%PATH%" 12 | - "conda install -y -c pyviz pyctdev && doit ecosystem_setup" 13 | - "doit env_create %CHANS_DEV% --name=test --python=%PY%" 14 | - "activate test" 15 | - "doit develop_install -o examples -o tests %CHANS_DEV%" 16 | - "doit env_capture" 17 | 18 | build: off 19 | 20 | test_script: 21 | - "doit test_all" 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | __init__.py export-subst 2 | setup.cfg export-subst 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | 64 | # Editor files 65 | *~ 66 | 67 | paramnb/.version 68 | .doit* 69 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | git: 2 | depth: 100 3 | 4 | language: generic 5 | 6 | os: 7 | - linux 8 | 9 | sudo: false 10 | 11 | env: 12 | global: 13 | - PYENV_VERSION=3.6 14 | - CHANS_DEV="-c pyviz/label/dev" 15 | - CHANS_REL="-c pyviz" 16 | - LABELS_DEV="--label dev" 17 | - LABELS_REL="--label dev --label main" 18 | - PKG_TEST_PYTHON="--test-python=py27 --test-python=py36" 19 | 20 | stages: 21 | - test 22 | - name: conda_dev_package 23 | if: tag =~ ^v(\d+|\.)*[a-z]\d*$ 24 | - name: pip_dev_package 25 | if: tag =~ ^v(\d+|\.)*[a-z]\d*$ 26 | - name: conda_package 27 | if: tag =~ ^v(\d+|\.)*[^a-z]\d*$ 28 | - name: pip_package 29 | if: tag =~ ^v(\d+|\.)*[^a-z]\d*$ 30 | 31 | 32 | jobs: 33 | include: 34 | 35 | ########## DEVELOPER INSTALL ########## 36 | 37 | - &conda_default 38 | stage: test 39 | env: DESC="dev test_all" 40 | before_install: 41 | # install doit/pyctdev and use to install miniconda... 42 | - pip install pyctdev && doit miniconda_install && pip uninstall -y doit pyctdev 43 | - export PATH="$HOME/miniconda/bin:$PATH" && hash -r 44 | - conda config --set always_yes True 45 | # ...and now install doit/pyctdev into miniconda 46 | - conda install -c pyviz pyctdev && doit ecosystem_setup 47 | install: 48 | - doit env_create $CHANS_DEV --python=$PYENV_VERSION 49 | - source activate test-environment 50 | - doit develop_install -o examples -o tests $CHANS_DEV 51 | - doit env_capture 52 | script: doit test_all 53 | 54 | # python 2 flake checking typically catches python 2 syntax 55 | # errors where python 3's been assumed... 56 | - <<: *conda_default 57 | env: DESC="py2 flakes" PYENV_VERSION=2.7 58 | script: doit test_lint 59 | 60 | ########## END-USER PACKAGES ########## 61 | 62 | ## dev packages 63 | 64 | - &pip_default 65 | env: PYPI=testpypi PYPIUSER=$TPPU PYPIPASS=$TPPP 66 | stage: pip_dev_package 67 | before_install: pip install pyctdev && doit ecosystem=pip ecosystem_setup 68 | install: 69 | - unset PYENV_VERSION && pyenv global 3.6 2.7 70 | - doit ecosystem=pip package_build $PKG_TEST_PYTHON --test-group=unit --sdist-install-build-deps 71 | - doit ecosystem=pip package_build $PKG_TEST_PYTHON --test-group=examples --sdist-install-build-deps 72 | script: doit ecosystem=pip package_upload -u $PYPIUSER -p $PYPIPASS --pypi ${PYPI} 73 | 74 | - &conda_pkg 75 | <<: *conda_default 76 | stage: conda_dev_package 77 | env: DESC="" LABELS=$LABELS_DEV CHANS=$CHANS_DEV 78 | install: 79 | - doit package_build $CHANS $PKG_TEST_PYTHON --test-group=unit 80 | - doit package_test $CHANS $PKG_TEST_PYTHON --test-group=examples --test-requires=examples 81 | script: doit package_upload --token=$ANACONDA_TOKEN $LABELS 82 | 83 | ## release packages 84 | 85 | - <<: *pip_default 86 | env: PYPI=pypi PYPIUSER=$PPU PYPIPASS=$PPP 87 | stage: pip_package 88 | 89 | - <<: *conda_pkg 90 | stage: conda_package 91 | env: DESC="" TRAVIS_NOCACHE=$TRAVIS_JOB_ID LABELS=$LABELS_REL CHANS=$CHANS_REL 92 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, IOAM 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of paramnb nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include README.md 3 | include paramnb/.version 4 | graft examples 5 | graft paramnb/examples 6 | global-exclude *.py[co] 7 | global-exclude *~ 8 | global-exclude *.ipynb_checkpoints/* 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://travis-ci.org/ioam/paramnb) 2 | 3 | [](https://ci.appveyor.com/project/Ioam/paramnb/branch/master) 4 | 5 | 6 | # ParamNB 7 | 8 | ### Note: ParamNB is no longer maintained; use the much more capable [Panel](https://github.com/pyviz/panel) library instead 9 | 10 | Automatically generate ipywidgets from 11 | [Parameterized](https://github.com/ioam/param) objects in a Jupyter 12 | notebook. 13 | 14 |  16 | 17 | 18 | ## Demo notebooks 19 | 20 | The following links go to static versions of the notebooks, so after 21 | looking at them it's best to try them out yourself interactively! 22 | 23 | * Introduction: [examples/user_guide/index.ipynb](http://nbviewer.jupyter.org/urls/notebooks.anaconda.org/cball/ioam-paramnb-index/download?version=) 24 | 25 | * Additional Features: [examples/user_guide/AdditionalFeatures.ipynb](http://nbviewer.jupyter.org/urls/notebooks.anaconda.org/cball/ioam-paramnb-additionalfeatures/download?version=) 26 | 27 | * Setting parameters via JSON: [examples/user_guide/JSONInit.ipynb](http://nbviewer.jupyter.org/urls/notebooks.anaconda.org/cball/ioam-paramnb-jsoninit/download?version=) 28 | 29 | 30 | ## Installation 31 | 32 | You can easily install paramnb using conda: 33 | 34 | ``` 35 | conda install -c pyviz paramnb 36 | ``` 37 | 38 | or pip: 39 | 40 | ``` 41 | pip install paramnb 42 | ``` 43 | -------------------------------------------------------------------------------- /conda.recipe/README.md: -------------------------------------------------------------------------------- 1 | ## Release Procedure 2 | 3 | - Ensure all tests pass. 4 | 5 | - Update version number in `conda.recipe/meta.yaml`, `paramnb/__init__.py`, 6 | and `setup.py`. Commit. 7 | 8 | - Tag commit and push to github 9 | 10 | ```bash 11 | git tag -a x.x.x -m 'Version x.x.x' 12 | git push upstream master --tags 13 | ``` 14 | 15 | - Build conda packages 16 | 17 | The exact procedure is platform/setup specific, so I'll define a few variables 18 | here, to fill in with your specifics: 19 | 20 | ```bash 21 | # Location of your conda install. For me it's `~/miniconda/` 22 | CONDA_DIR=~/miniconda/ 23 | 24 | # Platform code. For me it's `osx-64` 25 | PLATFORM=osx-64 26 | 27 | # Version number of paramnb being released (e.g. 2.0.2) 28 | VERSION=2.0.2 29 | ``` 30 | 31 | This assumes `conda`, `conda-build`, and `anaconda-client` are installed (if 32 | not, install `conda`, then use `conda` to install the others). From inside the 33 | toplevel directory: 34 | 35 | ```bash 36 | conda build conda.recipe/ --python 2.7 --python 3.4 --python 3.5 37 | ``` 38 | 39 | Next, `cd` into the folder where the builds end up. 40 | 41 | ```bash 42 | cd $CONDA_DIR/conda-bld/$PLATFORM 43 | ``` 44 | 45 | Use `conda convert` to convert over the missing platforms (skipping the one for 46 | the platform you're currently on): 47 | 48 | ```bash 49 | conda convert --platform osx-64 paramnb-$VERSION*.tar.bz2 -o ../ 50 | conda convert --platform linux-32 paramnb-$VERSION*.tar.bz2 -o ../ 51 | conda convert --platform linux-64 paramnb-$VERSION*.tar.bz2 -o ../ 52 | conda convert --platform win-32 paramnb-$VERSION*.tar.bz2 -o ../ 53 | conda convert --platform win-64 paramnb-$VERSION*.tar.bz2 -o ../ 54 | ``` 55 | 56 | Use `anaconda upload` to upload the build to the `ioam` channel. This requires 57 | you to be setup on `anaconda.org`, and have the proper credentials to push to 58 | the bokeh channel. 59 | 60 | ```bash 61 | anaconda login 62 | anaconda upload $CONDA_DIR/conda-bld/*/paramnb-$VERSION*.tar.bz2 -u ioam 63 | ``` 64 | -------------------------------------------------------------------------------- /conda.recipe/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set sdata = load_setup_py_data() %} 2 | 3 | package: 4 | name: paramnb 5 | version: {{ sdata['version'] }} 6 | 7 | source: 8 | path: .. 9 | 10 | build: 11 | noarch: python 12 | script: python setup.py install --single-version-externally-managed --record=record.txt 13 | entry_points: 14 | {% for group,epoints in sdata.get("entry_points",{}).items() %} 15 | {% for entry_point in epoints %} 16 | - {{ entry_point }} 17 | {% endfor %} 18 | {% endfor %} 19 | 20 | requirements: 21 | host: 22 | # duplicates pyproject.toml (not supported in conda build) 23 | - python 24 | - setuptools >=30.3.0 25 | - param >=1.7.0 26 | - pyct-core >=0.4.4 27 | run: 28 | - python {{ sdata['python_requires'] }} 29 | {% for dep in sdata.get('install_requires',{}) %} 30 | - "{{ dep }}" 31 | {% endfor %} 32 | 33 | test: 34 | imports: 35 | - paramnb 36 | requires: 37 | {% for dep in sdata['extras_require']['tests'] %} 38 | - "{{ dep }}" 39 | {% endfor %} 40 | 41 | about: 42 | home: {{ sdata['url'] }} 43 | summary: {{ sdata['description'] }} 44 | license: {{ sdata['license'] }} 45 | -------------------------------------------------------------------------------- /dodo.py: -------------------------------------------------------------------------------- 1 | import os 2 | if "PYCTDEV_ECOSYSTEM" not in os.environ: 3 | os.environ["PYCTDEV_ECOSYSTEM"] = "conda" 4 | 5 | from pyctdev import * # noqa: api 6 | -------------------------------------------------------------------------------- /examples/user_guide/AdditionalFeatures.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import param\n", 10 | "import paramnb" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "# Tooltips\n", 18 | "\n", 19 | "Doc strings supplied when a parameter is declared are displayed as a 'tooltip' when the mouse hovers over the widget label (``x`` in the widget below)" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "class TooltipExample(param.Parameterized):\n", 29 | " x = param.Number(default=1,bounds=(0,2),doc=\"X position\")" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "paramnb.Widgets(TooltipExample)" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "# Recursive editing\n", 46 | "\n", 47 | "Parameter types such as ``param.ObjectSelector`` allow selection of another Parameterized object; paramNB also allows the parameters of the selected object to be edited by clicking the **...** button next to any selected Parameterized object:" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "class Location(param.Parameterized):\n", 57 | " duration = param.Integer(default=5, bounds=(0,10))\n", 58 | "\n", 59 | "locations = [Location(name=\"One\"),Location(name=\"Two\")]\n", 60 | "\n", 61 | "class Employee(param.Parameterized):\n", 62 | " age = param.Integer(default=30,bounds=(18,100))\n", 63 | " location = param.ObjectSelector(default=locations[0],objects=locations)\n", 64 | "\n", 65 | "class Task(param.Parameterized):\n", 66 | " employee = param.ObjectSelector(objects=[Employee(name=\"A\"),Employee(name=\"B\")])" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "paramnb.Widgets(Task)" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "Task.employee.location.duration" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "# View parameters" 92 | ] 93 | }, 94 | { 95 | "cell_type": "markdown", 96 | "metadata": {}, 97 | "source": [ 98 | "The paramNB library provides an easy way to manipulate parameters on ``Parameterized`` using the widgets in the notebook. In addition to controlling input parameters a common usecase for using widgets in the notebook is to dynamically control some visual display output. In addition to all the standard parameters supplied by the ``param`` library, ``paramNB`` also supplies so called ``View`` parameters, which render their output in a widget area. The output parameters may be updated simply by setting the parameter on the class.\n", 99 | "\n", 100 | "In the first simple example we will declare a Parameterized class with a ``Number`` parameter called magnitude and an ``HTML`` parameter which will let us display some arbitrary HTML. In this case we will simply generate a pandas dataframe with random data within the update method and use the ``to_html`` method to convert it to an HTML table. If we define the ``update`` method as the callback of the widgets the table will now update whenever the slider is dragged. To ensure that the output is drawn on initialization we set ``on_init=True``." 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "metadata": {}, 107 | "outputs": [], 108 | "source": [ 109 | "import numpy as np\n", 110 | "import pandas as pd" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "class HTMLExample(param.Parameterized):\n", 120 | " \n", 121 | " magnitude = param.Number(1, bounds=(0, 10))\n", 122 | "\n", 123 | " output = paramnb.view.HTML()\n", 124 | " \n", 125 | " def update(self, **kwargs):\n", 126 | " self.output = pd.DataFrame(np.random.rand(10,2)*self.magnitude).to_html()\n", 127 | "\n", 128 | "example = HTMLExample(name='HTMLExample')\n", 129 | "paramnb.Widgets(example, on_init=True, callback=example.update)" 130 | ] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "The ``HTML`` parameter accepts any arbitrary HTML string but for convenience paramNB also allows supplying a custom ``renderer`` function, which converts the view data to HTML. In this case we declare to parameters to control the ``amplitude`` and ``frequency`` of a sine curve and then declare an ``HTML`` parameter which uses a HoloViews MPLRenderer to render the output. Note that we can additionally supply the size of the output as a tuple of ``(width, height)`` in pixels, in this case (300, 300).\n", 137 | "\n", 138 | "Additionally we can declare the ``view_position``, which specifies where the viewing widget will be placed in relation to the input widgets:" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": null, 144 | "metadata": {}, 145 | "outputs": [], 146 | "source": [ 147 | "import holoviews as hv\n", 148 | "renderer = hv.renderer('matplotlib')\n", 149 | "\n", 150 | "class ImageExample(param.Parameterized):\n", 151 | "\n", 152 | " color = param.Color(default='#000000', precedence=0)\n", 153 | "\n", 154 | " element = param.ObjectSelector(default=hv.Curve,\n", 155 | " objects=[hv.Curve, hv.Scatter, hv.Area],\n", 156 | " precedence=0)\n", 157 | "\n", 158 | " amplitude = param.Number(default=2, bounds=(2, 5))\n", 159 | " \n", 160 | " frequency = param.Number(default=2, bounds=(1, 10))\n", 161 | " \n", 162 | " output = paramnb.view.Image(renderer=lambda x: (renderer(x)[0], (300, 300)))\n", 163 | "\n", 164 | " def update(self, **kwargs):\n", 165 | " self.output = self.element(self.amplitude*np.sin(np.linspace(0, np.pi*self.frequency)),\n", 166 | " vdims=[hv.Dimension('y', range=(-5, 5))])(style=dict(color=self.color))\n", 167 | "\n", 168 | "example = ImageExample(name='HoloViews Example')\n", 169 | "paramnb.Widgets(example, callback=example.update, on_init=True, view_position='right')" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": {}, 175 | "source": [ 176 | "Finally, the generic ``View`` parameter may also be used to display the rich ``repr`` of any object, effectively mirroring the output of IPython's ``display`` function. If we load the HoloViews extension with the bokeh backend, and subclass the ImageExample, we can also render bokeh plots in this way:" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "metadata": {}, 183 | "outputs": [], 184 | "source": [ 185 | "hv.extension('bokeh')\n", 186 | "\n", 187 | "class ViewExample(ImageExample):\n", 188 | " \n", 189 | " output = paramnb.view.View()\n", 190 | " \n", 191 | "example = ViewExample(name='HoloViews+Bokeh Example')\n", 192 | "paramnb.Widgets(example, callback=example.update, on_init=True, view_position='right')" 193 | ] 194 | } 195 | ], 196 | "metadata": { 197 | "language_info": { 198 | "name": "python", 199 | "pygments_lexer": "ipython3" 200 | } 201 | }, 202 | "nbformat": 4, 203 | "nbformat_minor": 2 204 | } 205 | -------------------------------------------------------------------------------- /examples/user_guide/JSONInit.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Setting parameters via JSON" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "\n", 15 | "\n", 16 | "For all the examples in this notebook to work, launch the notebook server using:\n", 17 | "\n", 18 | "```\n", 19 | "PARAMNB_INIT='{\"p1\":5}' \\\n", 20 | " TARGETED='{\"Target1\":{\"p1\":3}, \"Target2\":{\"s\":\"test\"}}' \\\n", 21 | " CUSTOM='{\"custom\":{\"val\":99}}' jupyter notebook\n", 22 | "```\n", 23 | "\n" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "import os\n", 33 | "import param\n", 34 | "import paramnb" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "## Example 1" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "The first minimal example will work if the notebook server is launched as follows:\n", 49 | "\n", 50 | "```\n", 51 | "PARAMNB_INIT='{\"p1\":5}' jupyter notebook\n", 52 | "```\n", 53 | "\n", 54 | "First let's show that the ``'PARAMNB_INIT'`` environment is defined:" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "os.environ['PARAMNB_INIT']" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "This string is JSON and the 'PARAMNB_INIT' is the default environment variable name to set parameters via the commandline. Lets make a simple parameterized class with a ``p1`` parameter:" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "class Test(param.Parameterized):\n", 80 | " \n", 81 | " p1 = param.Number(default=1, bounds=(0,10))" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "Now if we supply ``paramnb.JSONInit`` as an initializer, the ``p1`` parameter is set from the default of 1 to the value of 5 specified by the environment variable:" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "paramnb.Widgets(Test, initializer=paramnb.JSONInit())" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "## Example 2" 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "The second example will work if the notebook server is launched as follows:\n", 112 | "\n", 113 | "```\n", 114 | "TARGETED='{\"Target1\":{\"p1\":3}, \"Target2\":{\"s\":\"test\"}}' jupyter notebook\n", 115 | "```\n", 116 | "\n", 117 | "In this example, we show how you can target parameters to different classes using a different environment variable called ``TARGETED``:" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [ 126 | "os.environ['TARGETED']" 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "metadata": {}, 132 | "source": [ 133 | "Here the keys are class names and the corresponding dictionary values are the parameter values to override. Let's defined classes ``Target1`` and ``Target2`` with parameters ``p1`` and ``s`` respectively:" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "metadata": {}, 140 | "outputs": [], 141 | "source": [ 142 | "class Target1(param.Parameterized):\n", 143 | " \n", 144 | " p1 = param.Number(default=1, bounds=(0,10))\n", 145 | "\n", 146 | "class Target2(param.Parameterized):\n", 147 | " \n", 148 | " s = param.String(default=\"default\")\n" 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "Now lets use ``paramnb.Widgets`` on ``Target1``:" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": null, 161 | "metadata": {}, 162 | "outputs": [], 163 | "source": [ 164 | "paramnb.Widgets(Target1, initializer=paramnb.JSONInit(varname='TARGETED'))" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "The value of ``p1`` is now ``3`` as requested.\n", 172 | "\n", 173 | "Now lets use ``paramnb.Widgets`` on ``Target2``:" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": null, 179 | "metadata": {}, 180 | "outputs": [], 181 | "source": [ 182 | "paramnb.Widgets(Target2, initializer=paramnb.JSONInit(varname='TARGETED'))" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "## Example 3" 190 | ] 191 | }, 192 | { 193 | "cell_type": "markdown", 194 | "metadata": {}, 195 | "source": [ 196 | "\n", 197 | "The third example will work if the notebook server is launched as follows:\n", 198 | "\n", 199 | "```\n", 200 | "CUSTOM='{\"custom\":{\"val\":99}}' jupyter notebook\n", 201 | "```\n", 202 | "\n", 203 | "In this example, we show how you can target a specific instance using an environment variable called ``CUSTOM``:" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": null, 209 | "metadata": {}, 210 | "outputs": [], 211 | "source": [ 212 | "os.environ['CUSTOM']" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": null, 218 | "metadata": {}, 219 | "outputs": [], 220 | "source": [ 221 | "class Example(param.Parameterized):\n", 222 | " \n", 223 | " val = param.Number(default=1, bounds=(0,100))\n", 224 | " \n", 225 | "instance = Example()\n", 226 | "paramnb.Widgets(instance, initializer=paramnb.JSONInit(varname='CUSTOM', target='custom'))" 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": {}, 232 | "source": [ 233 | "## Example 4" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "metadata": {}, 239 | "source": [ 240 | "You can also use a JSON file ending with the '.json' extension. For instance, if you execute:" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": null, 246 | "metadata": {}, 247 | "outputs": [], 248 | "source": [ 249 | "import json\n", 250 | "json.dump({\"p1\":5}, open('param_init.json', 'w'))" 251 | ] 252 | }, 253 | { 254 | "cell_type": "markdown", 255 | "metadata": {}, 256 | "source": [ 257 | "You cam specify the full path or relative path to the JSON file with:\n", 258 | "\n", 259 | "\n", 260 | "```\n", 261 | "PARAMNB_INIT=param_init.json jupyter notebook\n", 262 | "```" 263 | ] 264 | }, 265 | { 266 | "cell_type": "code", 267 | "execution_count": null, 268 | "metadata": {}, 269 | "outputs": [], 270 | "source": [ 271 | "os.environ['PARAMNB_INIT']" 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": null, 277 | "metadata": {}, 278 | "outputs": [], 279 | "source": [ 280 | "class Test(param.Parameterized):\n", 281 | " \n", 282 | " p1 = param.Number(default=1, bounds=(0,10))\n", 283 | " \n", 284 | "paramnb.Widgets(Test, initializer=paramnb.JSONInit())" 285 | ] 286 | }, 287 | { 288 | "cell_type": "markdown", 289 | "metadata": {}, 290 | "source": [ 291 | "Note that you can use ``JSONInit`` without setting any environment variables by specifying the JSON file directly:" 292 | ] 293 | }, 294 | { 295 | "cell_type": "code", 296 | "execution_count": null, 297 | "metadata": {}, 298 | "outputs": [], 299 | "source": [ 300 | "paramnb.Widgets(Test, initializer=paramnb.JSONInit(json_file='param_init.json'))" 301 | ] 302 | }, 303 | { 304 | "cell_type": "markdown", 305 | "metadata": {}, 306 | "source": [ 307 | "## Tips" 308 | ] 309 | }, 310 | { 311 | "cell_type": "markdown", 312 | "metadata": {}, 313 | "source": [ 314 | "\n", 315 | "* It is recommended that you target at the class or instance level if you ever intend to use ``JSONInit`` to customize different sets of parameters.\n", 316 | "\n", 317 | "* It is recommended that you validate (and pretty print) the JSON at the commandline using ``json.tool``. For instance, you can validate the JSON for the first example before launching the server as follows:\n", 318 | "\n", 319 | "```\n", 320 | "PARAMNB_INIT=`echo '{\"p1\":5}' | python -mjson.tool` jupyter notebook\n", 321 | "```" 322 | ] 323 | } 324 | ], 325 | "metadata": { 326 | "language_info": { 327 | "name": "python", 328 | "pygments_lexer": "ipython3" 329 | } 330 | }, 331 | "nbformat": 4, 332 | "nbformat_minor": 0 333 | } 334 | -------------------------------------------------------------------------------- /examples/user_guide/images/intro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/paramnb/bdf18566d6279d6612537fadaa5460e1961aa359/examples/user_guide/images/intro.gif -------------------------------------------------------------------------------- /examples/user_guide/images/stock_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/paramnb/bdf18566d6279d6612537fadaa5460e1961aa359/examples/user_guide/images/stock_dashboard.png -------------------------------------------------------------------------------- /examples/user_guide/index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "[ParamNB](https://github.com/ioam/paramnb) is a small library that represents Parameters graphically in a Jupyter notebook. Parameters are Python attributes extended using the [Param library](https://github.com/ioam/param) to support types, ranges, and documentation, which turns out to be just the information you need to automatically create widgets for each parameter. ParamNB currently uses [ipywidgets](https://ipywidgets.readthedocs.io) to display the widgets, but the design of Param and ParamNB allows your code to be completely independent of the underlying widgets library, and ParamNB can be updated to use other widget libraries as they are developed without needing changes in your code.\n", 8 | "\n", 9 | "# Parameters and widgets\n", 10 | "\n", 11 | "To use ParamNB, first declare some Parameterized classes with various Parameters:" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import param\n", 21 | "import datetime as dt\n", 22 | "\n", 23 | "def hello(x, **kwargs):\n", 24 | " print(\"Hello %s\" % x)\n", 25 | " \n", 26 | "class BaseClass(param.Parameterized):\n", 27 | " x = param.Parameter(default=3.14,doc=\"X position\")\n", 28 | " y = param.Parameter(default=\"Not editable\",constant=True)\n", 29 | " string_value = param.String(default=\"str\",doc=\"A string\")\n", 30 | " num_int = param.Integer(50000,bounds=(-200,100000))\n", 31 | " unbounded_int = param.Integer(23)\n", 32 | " float_with_hard_bounds = param.Number(8.2,bounds=(7.5,10))\n", 33 | " float_with_soft_bounds = param.Number(0.5,bounds=(0,None),softbounds=(0,2))\n", 34 | " unbounded_float = param.Number(30.01,precedence=0)\n", 35 | " hidden_parameter = param.Number(2.718,precedence=-1)\n", 36 | " integer_range = param.Range(default=(3,7),bounds=(0, 10))\n", 37 | " float_range = param.Range(default=(0,1.57),bounds=(0, 3.145))\n", 38 | " dictionary = param.Dict(default={\"a\":2, \"b\":9})\n", 39 | " \n", 40 | "class Example(BaseClass):\n", 41 | " \"\"\"An example Parameterized class\"\"\"\n", 42 | " boolean = param.Boolean(True, doc=\"A sample Boolean parameter\")\n", 43 | " color = param.Color(default='#FFFFFF')\n", 44 | " date = param.Date(dt.datetime(2017, 1, 1),\n", 45 | " bounds=(dt.datetime(2017, 1, 1), dt.datetime(2017, 2, 1)))\n", 46 | " select_string = param.ObjectSelector(default=\"yellow\",objects=[\"red\",\"yellow\",\"green\"])\n", 47 | " select_fn = param.ObjectSelector(default=list,objects=[list,set,dict])\n", 48 | " int_list = param.ListSelector(default=[3,5], objects=[1,3,5,7,9],precedence=0.5)\n", 49 | " single_file = param.FileSelector(path='../*/*.py*',precedence=0.5)\n", 50 | " multiple_files = param.MultiFileSelector(path='../*/*.py?',precedence=0.5)\n", 51 | " msg = param.Action(hello, doc=\"\"\"Print a message.\"\"\",precedence=0.7)\n", 52 | " \n", 53 | "Example.num_int" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "As you can see, declaring Parameters depends only on the separate Param library. Parameters are a simple idea with some properties that are crucial for helping you create clean, usable code:\n", 61 | "\n", 62 | "- The Param library is pure Python with no dependencies, which makes it easy to include in any code without tying it to a particular GUI or widgets library, or even to the Jupyter notebook. \n", 63 | "- Parameter declarations focus on semantic information relevant to your domain, allowing you to avoid polluting your domain-specific code with anything that ties it to a particular way of displaying or interacting with it. \n", 64 | "- Parameters can be defined wherever they make sense in your inheritance hierarchy, allowing you to document, type, and range-limit them once, with all of those properties inherited by any base class. E.g. parameters work the same here whether they were declared in `BaseClass` or `Example`, which makes it easy to provide this metadata once, and avoiding duplicating it throughout the code wherever ranges or types need checking or documentation needs to be stored.\n", 65 | "\n", 66 | "If you then decide to use these Parameterized classes in a notebook environment, you can import ParamNB and easily display and edit the parameter values as an optional additional step:" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "import paramnb\n", 76 | "paramnb.Widgets(Example)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "As you can see, `paramnb.Widgets()` 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 ParamNB 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", 84 | "\n", 85 | "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", 86 | "\n", 87 | "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:" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "metadata": {}, 94 | "outputs": [], 95 | "source": [ 96 | "Example.unbounded_int" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "Example.num_int" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "#Example.print_param_defaults() # see all parameter values" 115 | ] 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "metadata": {}, 120 | "source": [ 121 | "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 ParamNB; it will simply see new values for whatever attributes were changed interactively. ParamNB thus allows you to provide notebook-specific, domain-specific interactive functionality without ever tying your domain-specific code to the notebook environment.\n", 122 | "\n", 123 | "\n", 124 | "# Controlling code execution\n", 125 | "\n", 126 | "If you do `Run All` in the notebook instead of running cell by cell, you won't get any opportunity to interact with the widgets until the notebook has completed, and so any values you change will only take effect if you then do a separate `Run All Below` command to update the results of subsequent cells. \n", 127 | "\n", 128 | "Having to work cell by cell or re-run the notebook manually can be awkward, especially when building dashboards that hide the notebook user interface (such as with [Jupyter Dashboards](https://github.com/jupyter/dashboards)). In order to provide \"live\" or dynamic updating, ParamNB also allows you to control code (re-)execution automatically in various ways. First, you can define *what* code will be executed:\n", 129 | "\n", 130 | "* `callback`=*callable*: User-defined function to call, if any\n", 131 | "* `next_n`=*n*: zero by default, but if set to e.g. 2, will execute the subsequent 2 cells of the notebook\n", 132 | "\n", 133 | "You can also define *when* the code will be executed:\n", 134 | "\n", 135 | "* `button=False`: the default; the specified code will be executed whenever a widget value is changed\n", 136 | "* `button=True`: Provide a button to control code execution, so that multiple widgets can be adjusted and code is updated only when the button is pushed.\n", 137 | "* `continuous_update=True`: the specified code is executed for every movement of a slider\n", 138 | "* `continuous_update=False`: the default; the specified code is executed only once a widget has been released\n", 139 | "\n", 140 | "These options allow you to choose between various levels of dynamic interactivity, as appropriate for the computational and semantic requirements of the code you are executing. Rough guidelines:\n", 141 | "\n", 142 | "* `button=False,continuous_update=True`: Provides a smooth, dynamic user experience, with text or plots updating immediately as a slider is dragged. Appropriate only for inexpensive operations, where rexecuting the code multiple times on the fly is not an issue.\n", 143 | "* `button=False,continuous_update=False`: The default; a good middle ground appropriate for most interactive use, with relatively responsive interactivity, updating each time a widget is released. Suitable for relatively expensive operations, but not so expensive that it is problematic to have them run once for each widget adjusted.\n", 144 | "* `button=True`: Suitable for very expensive or transactional operations, where you want to adjust multiple widgets before committing to executing the code.\n", 145 | "\n", 146 | "Example of dynamic updating:" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": null, 152 | "metadata": {}, 153 | "outputs": [], 154 | "source": [ 155 | "class Example2(param.Parameterized):\n", 156 | " num1 = param.Number(3.14,bounds=(0.0,10.0))\n", 157 | " number2 = param.Integer(2,bounds=(0,5))\n", 158 | "\n", 159 | "paramnb.Widgets(Example2,next_n=1)" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": null, 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "Example2.num1, Example2.number2" 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "metadata": {}, 174 | "source": [ 175 | "Notice that in a live notebook, the `In` and `Out` numbers of the above cell increase every time you release a slider after dragging it, because that cell is being re-executed. \n", 176 | "\n", 177 | "Example of updating on the \"Run\" button press:" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": null, 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [ 186 | "paramnb.Widgets(Example2,button=True,callback=hello,next_n=1)" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": null, 192 | "metadata": {}, 193 | "outputs": [], 194 | "source": [ 195 | "Example2.num1, Example2.number2" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "Here, the cell above changes its number (and output value) only when the \"Run 1\" button is pressed in the previous cell. The supplied callback is also executed at that time.\n", 203 | "\n", 204 | "Note that `paramnb.Widgets()` displays all the parameters that have a `precedence` that's above the `Widgets.display_threshold` value, which is zero by default. You can thus hide values that are not useful in the notebook by giving the parameters a negative precedence when they are declared. If you later want to display the hidden parameters, e.g. for debugging, you can change the `display_threshold` parameter, e.g. by supplying it to the `Widgets()` call. Parameters with the same precedence are sorted alphanumerically, in groups sorted by the precedence value. Values with no declared precedence are given a very low precedence by default (`Widgets(...,default_precedence=1e-8)`), allowing you to force parameters to appear at the top of the list by giving them a precedence of zero (or another very small number).\n", 205 | "\n", 206 | "Together, all these features make it simple to add interactive controls in Jupyter notebooks: just declare your parameters wherever their values will need to be used, using the Param library (pure Python, zero dependencies), then add an optional `Widgets()` declaration in your notebook wherever you want to be able to modify those values interactively. That way your main code can be fully independent of any GUI or notebook display, while your notebooks can easily expose the parameters declared in your main code, without duplicating their names or definitions and without relying on any specific details of that code. So you can now have full interactivity without tying yourself to any particular user interface or GUI library, and without tying your user interface code to details of your domain-specific code.\n", 207 | "\n", 208 | "You can install ParamNB as described at [github.com/ioam/paramnb](https://github.com/ioam/paramnb). Have fun widgeting!" 209 | ] 210 | } 211 | ], 212 | "metadata": { 213 | "language_info": { 214 | "name": "python", 215 | "pygments_lexer": "ipython3" 216 | } 217 | }, 218 | "nbformat": 4, 219 | "nbformat_minor": 0 220 | } 221 | -------------------------------------------------------------------------------- /paramnb/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Jupyter notebook interface for Param (https://github.com/ioam/param). 3 | 4 | Given a Parameterized object, displays a box with an ipywidget for each 5 | Parameter, allowing users to view and and manipulate Parameter values 6 | from within a Jupyter/IPython notebook. 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import os 11 | import ast 12 | import uuid 13 | import itertools 14 | import json 15 | import functools 16 | from collections import OrderedDict 17 | 18 | import param 19 | import ipywidgets 20 | from IPython.display import display, Javascript, HTML, clear_output 21 | 22 | from . import widgets 23 | from .widgets import wtype, apply_error_style, literal_params, Output 24 | from .util import named_objs, get_method_owner 25 | from .view import View, HTML as HTMLView 26 | 27 | from param.version import Version 28 | __version__ = str(param.Version(fpath=__file__,archive_commit="bdf1856",reponame="paramnb")) 29 | del Version 30 | 31 | 32 | def run_next_cells(n): 33 | if n=='all': 34 | n = 'NaN' 35 | elif n<1: 36 | return 37 | 38 | js_code = """ 39 | var num = {0}; 40 | var run = false; 41 | var current = $(this)[0]; 42 | $.each(IPython.notebook.get_cells(), function (idx, cell) {{ 43 | if ((cell.output_area === current) && !run) {{ 44 | run = true; 45 | }} else if ((cell.cell_type == 'code') && !(num < 1) && run) {{ 46 | cell.execute(); 47 | num = num - 1; 48 | }} 49 | }}); 50 | """.format(n) 51 | 52 | display(Javascript(js_code)) 53 | 54 | 55 | def estimate_label_width(labels): 56 | """ 57 | Given a list of labels, estimate the width in pixels 58 | and return in a format accepted by CSS. 59 | Necessarily an approximation, since the font is unknown 60 | and is usually proportionally spaced. 61 | """ 62 | max_length = max([len(l) for l in labels]) 63 | return "{0}px".format(max(60,int(max_length*7.5))) 64 | 65 | 66 | class Widgets(param.ParameterizedFunction): 67 | 68 | callback = param.Callable(default=None, doc=""" 69 | Custom callable to execute on button press 70 | (if `button`) else whenever a widget is changed, 71 | Should accept a Parameterized object argument.""") 72 | 73 | view_position = param.ObjectSelector(default='below', 74 | objects=['below', 'right', 'left', 'above'], 75 | doc=""" 76 | Layout position of any View parameter widgets.""") 77 | 78 | next_n = param.Parameter(default=0, doc=""" 79 | When executing cells, integer number to execute (or 'all'). 80 | A value of zero means not to control cell execution.""") 81 | 82 | on_init = param.Boolean(default=False, doc=""" 83 | Whether to do the action normally taken (executing cells 84 | and/or calling a callable) when first instantiating this 85 | object.""") 86 | 87 | close_button = param.Boolean(default=False, doc=""" 88 | Whether to show a button allowing the Widgets to be closed.""") 89 | 90 | button = param.Boolean(default=False, doc=""" 91 | Whether to show a button to control cell execution. 92 | If false, will execute `next` cells on any widget 93 | value change.""") 94 | 95 | label_width = param.Parameter(default=estimate_label_width, doc=""" 96 | Width of the description for parameters in the list, using any 97 | string specification accepted by CSS (e.g. "100px" or "50%"). 98 | If set to a callable, will call that function using the list of 99 | all labels to get the value.""") 100 | 101 | tooltips = param.Boolean(default=True, doc=""" 102 | Whether to add tooltips to the parameter names to show their 103 | docstrings.""") 104 | 105 | show_labels = param.Boolean(default=True) 106 | 107 | display_threshold = param.Number(default=0,precedence=-10,doc=""" 108 | Parameters with precedence below this value are not displayed.""") 109 | 110 | default_precedence = param.Number(default=1e-8,precedence=-10,doc=""" 111 | Precedence value to use for parameters with no declared precedence. 112 | By default, zero predecence is available for forcing some parameters 113 | to the top of the list, and other values above the default_precedence 114 | values can be used to sort or group parameters arbitrarily.""") 115 | 116 | initializer = param.Callable(default=None, doc=""" 117 | User-supplied function that will be called on initialization, 118 | usually to update the default Parameter values of the 119 | underlying parameterized object.""") 120 | 121 | layout = param.ObjectSelector(default='column', 122 | objects=['row','column'],doc=""" 123 | Whether to lay out the buttons as a row or a column.""") 124 | 125 | continuous_update = param.Boolean(default=False, doc=""" 126 | If true, will continuously update the next_n and/or callback, 127 | if any, as a slider widget is dragged.""") 128 | 129 | def __call__(self, parameterized, plots=[], **params): 130 | self.p = param.ParamOverrides(self, params) 131 | if self.p.initializer: 132 | self.p.initializer(parameterized) 133 | 134 | self._id = uuid.uuid4().hex 135 | self._widgets = {} 136 | self.parameterized = parameterized 137 | 138 | widgets, views = self.widgets() 139 | layout = ipywidgets.Layout(display='flex', flex_flow=self.p.layout) 140 | if self.p.close_button: 141 | layout.border = 'solid 1px' 142 | 143 | widget_box = ipywidgets.VBox(children=widgets, layout=layout) 144 | plot_outputs = tuple(Output() for p in plots) 145 | if views or plots: 146 | outputs = tuple(views.values()) + plot_outputs 147 | view_box = ipywidgets.VBox(children=outputs, layout=layout) 148 | layout = self.p.view_position 149 | if layout in ['below', 'right']: 150 | children = [widget_box, view_box] 151 | else: 152 | children = [view_box, widget_box] 153 | box = ipywidgets.VBox if layout in ['below', 'above'] else ipywidgets.HBox 154 | widget_box = box(children=children) 155 | 156 | display(widget_box) 157 | self._widget_box = widget_box 158 | 159 | self._display_handles = {} 160 | # Render defined View parameters 161 | for pname, view in views.items(): 162 | p_obj = self.parameterized.params(pname) 163 | value = getattr(self.parameterized, pname) 164 | if value is None: 165 | continue 166 | handle = self._update_trait(pname, p_obj.renderer(value)) 167 | if handle: 168 | self._display_handles[pname] = handle 169 | 170 | # Render supplied plots 171 | for p, o in zip(plots, plot_outputs): 172 | with o: 173 | display(p) 174 | 175 | # Keeps track of changes between button presses 176 | self._changed = {} 177 | 178 | if self.p.on_init: 179 | self.execute() 180 | 181 | 182 | def _update_trait(self, p_name, p_value, widget=None): 183 | p_obj = self.parameterized.params(p_name) 184 | widget = self._widgets[p_name] if widget is None else widget 185 | if isinstance(p_value, tuple): 186 | p_value, size = p_value 187 | 188 | if isinstance(size, tuple) and len(size) == 2: 189 | if isinstance(widget, ipywidgets.Image): 190 | widget.width = size[0] 191 | widget.height = size[1] 192 | else: 193 | widget.layout.min_width = '%dpx' % size[0] 194 | widget.layout.min_height = '%dpx' % size[1] 195 | 196 | if isinstance(widget, Output): 197 | if isinstance(p_obj, HTMLView) and p_value: 198 | p_value = HTML(p_value) 199 | with widget: 200 | # clear_output required for JLab support 201 | # in future handle.update(p_value) should be sufficient 202 | handle = self._display_handles.get(p_name) 203 | if handle: 204 | clear_output(wait=True) 205 | handle.display(p_value) 206 | else: 207 | handle = display(p_value, display_id=p_name+self._id) 208 | self._display_handles[p_name] = handle 209 | else: 210 | widget.value = p_value 211 | 212 | 213 | def _make_widget(self, p_name): 214 | p_obj = self.parameterized.params(p_name) 215 | widget_class = wtype(p_obj) 216 | 217 | value = getattr(self.parameterized, p_name) 218 | 219 | # For ObjectSelector, pick first from objects if no default; 220 | # see https://github.com/ioam/param/issues/164 221 | if hasattr(p_obj,'objects') and len(p_obj.objects)>0 and value is None: 222 | value = p_obj.objects[0] 223 | if isinstance(p_obj,param.ListSelector): 224 | value = [value] 225 | setattr(self.parameterized, p_name, value) 226 | 227 | kw = dict(value=value) 228 | if p_obj.doc: 229 | kw['tooltip'] = p_obj.doc 230 | 231 | if isinstance(p_obj, param.Action): 232 | def action_cb(button): 233 | getattr(self.parameterized, p_name)(self.parameterized) 234 | kw['value'] = action_cb 235 | 236 | kw['name'] = p_name 237 | 238 | kw['continuous_update']=self.p.continuous_update 239 | 240 | if hasattr(p_obj, 'callbacks'): 241 | kw.pop('value', None) 242 | 243 | if hasattr(p_obj, 'get_range'): 244 | kw['options'] = named_objs(p_obj.get_range().items()) 245 | 246 | if hasattr(p_obj, 'get_soft_bounds'): 247 | kw['min'], kw['max'] = p_obj.get_soft_bounds() 248 | 249 | if hasattr(p_obj,'is_instance') and p_obj.is_instance: 250 | kw['options'][kw['value'].__class__.__name__]=kw['value'] 251 | 252 | w = widget_class(**kw) 253 | 254 | if hasattr(p_obj, 'callbacks') and value is not None: 255 | self._update_trait(p_name, p_obj.renderer(value), w) 256 | 257 | def change_event(event): 258 | new_values = event['new'] 259 | error = False 260 | # Apply literal evaluation to values 261 | if (isinstance(w, ipywidgets.Text) and isinstance(p_obj, literal_params)): 262 | try: 263 | new_values = ast.literal_eval(new_values) 264 | except: 265 | error = 'eval' 266 | elif hasattr(p_obj,'is_instance') and p_obj.is_instance and isinstance(new_values,type): 267 | # results in new instance each time non-default option 268 | # is selected; could consider caching. 269 | try: 270 | # awkward: support ParameterizedFunction 271 | new_values = new_values.instance() if hasattr(new_values,'instance') else new_values() 272 | except: 273 | error = 'instantiate' 274 | 275 | # If no error during evaluation try to set parameter 276 | if not error: 277 | try: 278 | setattr(self.parameterized, p_name, new_values) 279 | except ValueError: 280 | error = 'validation' 281 | 282 | # Style widget to denote error state 283 | apply_error_style(w, error) 284 | 285 | if not error and not self.p.button: 286 | self.execute({p_name: new_values}) 287 | else: 288 | self._changed[p_name] = new_values 289 | 290 | if hasattr(p_obj, 'callbacks'): 291 | p_obj.callbacks[id(self.parameterized)] = functools.partial(self._update_trait, p_name) 292 | else: 293 | w.observe(change_event, 'value') 294 | 295 | # Hack ; should be part of Widget classes 296 | if hasattr(p_obj,"path"): 297 | def path_change_event(event): 298 | new_values = event['new'] 299 | p_obj = self.parameterized.params(p_name) 300 | p_obj.path = new_values 301 | p_obj.update() 302 | 303 | # Update default value in widget, ensuring it's always a legal option 304 | selector = self._widgets[p_name].children[1] 305 | defaults = p_obj.default 306 | if not issubclass(type(defaults),list): 307 | defaults = [defaults] 308 | selector.options.update(named_objs(zip(defaults,defaults))) 309 | selector.value=p_obj.default 310 | selector.options=named_objs(p_obj.get_range().items()) 311 | 312 | if p_obj.objects and not self.p.button: 313 | self.execute({p_name:selector.value}) 314 | 315 | path_w = ipywidgets.Text(value=p_obj.path) 316 | path_w.observe(path_change_event, 'value') 317 | w = ipywidgets.VBox(children=[path_w,w], 318 | layout=ipywidgets.Layout(margin='0')) 319 | 320 | return w 321 | 322 | 323 | def widget(self, param_name): 324 | """Get widget for param_name""" 325 | if param_name not in self._widgets: 326 | self._widgets[param_name] = self._make_widget(param_name) 327 | return self._widgets[param_name] 328 | 329 | 330 | def execute(self, changed={}): 331 | run_next_cells(self.p.next_n) 332 | if self.p.callback is not None: 333 | if get_method_owner(self.p.callback) is self.parameterized: 334 | self.p.callback(**changed) 335 | else: 336 | self.p.callback(self.parameterized, **changed) 337 | 338 | 339 | # Define some settings :) 340 | preamble = """ 341 | 345 | """ 346 | 347 | label_format = """