├── .appveyor.yml ├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── conda.recipe ├── README.md └── meta.yaml ├── doc ├── FAQ.rst ├── README.md ├── _static │ ├── favicon.ico │ └── param-logo.png ├── about.rst ├── conf.py ├── index.rst └── user_guide │ └── index.rst ├── dodo.py ├── etc └── travis-miniconda.sh ├── examples ├── apps │ ├── bokeh │ │ └── simple.py │ └── django2 │ │ ├── README.md │ │ ├── manage.py │ │ ├── project │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ │ └── sliders │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── bk_config.py │ │ ├── bk_sliders.py │ │ ├── migrations │ │ └── __init__.py │ │ ├── models.py │ │ ├── sinewave.py │ │ ├── templates │ │ └── base.html │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py ├── assets │ ├── simple.png │ └── sliders.png └── user_guide │ ├── Bokeh_Apps.ipynb │ ├── Django_Apps.ipynb │ ├── Introduction.ipynb │ ├── JSONInit.ipynb │ └── View_Parameters.ipynb ├── parambokeh ├── __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 | # PyBuilder 56 | target/ 57 | 58 | #Ipython Notebook 59 | .ipynb_checkpoints 60 | 61 | # Editor files 62 | *~ 63 | 64 | *.doit* 65 | 66 | #autover 67 | */.version 68 | 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 | cache: 21 | directories: 22 | - $HOME/miniconda 23 | 24 | before_cache: 25 | - rm -rf $HOME/miniconda/pkgs 26 | - rm -rf $HOME/miniconda/conda-bld/* 27 | - rm -rf $HOME/miniconda/envs/*/conda-bld 28 | 29 | stages: 30 | - test 31 | - name: conda_dev_package 32 | if: tag =~ ^v(\d+|\.)+[a-z]\d+$ 33 | - name: pip_dev_package 34 | if: tag =~ ^v(\d+|\.)+[a-z]\d+$ 35 | - name: website_dev 36 | if: tag =~ ^v(\d+|\.)+[a-z]\d+$ OR tag = website_dev 37 | - name: conda_package 38 | if: tag =~ ^v(\d+|\.)+[^a-z]\d+$ 39 | - name: pip_package 40 | if: tag =~ ^v(\d+|\.)+[^a-z]\d+$ 41 | - name: website_release 42 | if: tag =~ ^v(\d+|\.)+[^a-z]\d+$ OR tag = website 43 | 44 | 45 | jobs: 46 | include: 47 | 48 | ########## DEVELOPER INSTALL ########## 49 | 50 | - &conda_default 51 | stage: test 52 | env: DESC="dev test_all" 53 | before_install: 54 | # install doit/pyctdev and use to install miniconda... 55 | - pip install pyctdev && doit miniconda_install && pip uninstall -y doit pyctdev 56 | - export PATH="$HOME/miniconda/bin:$PATH" && hash -r 57 | - conda config --set always_yes True 58 | # ...and now install doit/pyctdev into miniconda 59 | - conda install -c pyviz pyctdev && doit ecosystem_setup 60 | install: 61 | - doit env_create $CHANS_DEV --python=$PYENV_VERSION 62 | - source activate test-environment 63 | - doit develop_install -o examples -o tests $CHANS_DEV 64 | - doit env_capture 65 | script: doit test_all 66 | 67 | # python 2 flake checking typically catches python 2 syntax 68 | # errors where python 3's been assumed... 69 | - <<: *conda_default 70 | env: DESC="py2 flakes" PYENV_VERSION=2.7 71 | script: doit test_lint 72 | 73 | ########## END-USER PACKAGES ########## 74 | 75 | ## dev packages 76 | 77 | - &pip_default 78 | env: TRAVIS_NOCACHE=$TRAVIS_JOB_ID PYPI=testpypi PYPIUSER=$TPPU PYPIPASS=$TPPP 79 | stage: pip_dev_package 80 | before_install: pip install pyctdev && doit ecosystem=pip ecosystem_setup 81 | install: 82 | - unset PYENV_VERSION && pyenv global 3.6 2.7 83 | - doit ecosystem=pip package_build $PKG_TEST_PYTHON --test-group=unit --sdist-install-build-deps 84 | - doit ecosystem=pip package_build $PKG_TEST_PYTHON --test-group=examples --sdist-install-build-deps 85 | script: doit ecosystem=pip package_upload -u $PYPIUSER -p $PYPIPASS --pypi ${PYPI} 86 | 87 | - &conda_pkg 88 | <<: *conda_default 89 | stage: conda_dev_package 90 | env: DESC="" TRAVIS_NOCACHE=$TRAVIS_JOB_ID LABELS=$LABELS_DEV CHANS=$CHANS_DEV 91 | install: 92 | - doit package_build $CHANS $PKG_TEST_PYTHON --test-group=unit 93 | - doit package_test $CHANS $PKG_TEST_PYTHON --test-group=examples --test-requires=examples 94 | script: doit package_upload --token=$ANACONDA_TOKEN $LABELS 95 | 96 | ## release packages 97 | 98 | - <<: *pip_default 99 | env: TRAVIS_NOCACHE=$TRAVIS_JOB_ID PYPI=pypi PYPIUSER=$PPU PYPIPASS=$PPP 100 | stage: pip_package 101 | 102 | - <<: *conda_pkg 103 | stage: conda_package 104 | env: DESC="" TRAVIS_NOCACHE=$TRAVIS_JOB_ID LABELS=$LABELS_REL CHANS=$CHANS_REL 105 | 106 | 107 | ########## DOCS ########## 108 | 109 | - &website 110 | <<: *conda_default 111 | stage: website_release 112 | env: DESC="parambokeh.pyviz.org" 113 | script: 114 | - doit develop_install $CHANS_DEV -o doc -o examples -c defaults -c conda-forge # (because phantomjs not on defaults for linux) 115 | - doit docs 116 | deploy: 117 | - provider: pages 118 | skip_cleanup: true 119 | github_token: $GITHUB_TOKEN 120 | local_dir: ./builtdocs 121 | fqdn: parambokeh.pyviz.org 122 | on: 123 | tags: true 124 | all_branches: true 125 | 126 | - <<: *website 127 | stage: website_dev 128 | env: DESC="ioam-docs.github.io/parambokeh-dev" 129 | deploy: 130 | - provider: pages 131 | skip_cleanup: true 132 | github_token: $GITHUB_TOKEN 133 | local_dir: ./builtdocs 134 | repo: ioam-docs/parambokeh-dev 135 | on: 136 | tags: true 137 | all_branches: true 138 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, 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 parambokeh/.version 4 | graft examples 5 | graft parambokeh/examples 6 | global-exclude *.py[co] 7 | global-exclude *~ 8 | global-exclude *.ipynb_checkpoints/* 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Linux Build 2 | Status](https://travis-ci.org/ioam/parambokeh.svg?branch=master)](https://travis-ci.org/ioam/parambokeh) 3 | [![Windows Build 4 | status](https://ci.appveyor.com/api/projects/status/27h3ybqx51wsasu5/branch/master?svg=true)](https://ci.appveyor.com/project/Ioam/parambokeh/branch/master) 5 | 6 | # ParamBokeh 7 | 8 | ### Note: ParamBokeh is no longer maintained; use the much more capable [Panel](https://github.com/pyviz/panel) library instead 9 | 10 | Generate widgets from [Parameterized](https://github.com/ioam/param) objects in Jupyter or in [Bokeh](http://bokeh.pydata.org) Server. See the [documentation](https://ioam.github.io/parambokeh/) for more details. 11 | -------------------------------------------------------------------------------- /conda.recipe/README.md: -------------------------------------------------------------------------------- 1 | ## Release Procedure 2 | 3 | - Ensure all tests pass. 4 | 5 | - Update version number in `conda.recipe/meta.yaml`, `parambokeh/__init__.py`, 6 | and `setup.py`. Commit. 7 | 8 | - Tag commit and push to github 9 | 10 | ```bash 11 | git tag -a vx.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 parambokeh being released (e.g. 0.1.0) 28 | VERSION=0.1.0 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 parambokeh-$VERSION*.tar.bz2 -o ../ 50 | conda convert --platform linux-32 parambokeh-$VERSION*.tar.bz2 -o ../ 51 | conda convert --platform linux-64 parambokeh-$VERSION*.tar.bz2 -o ../ 52 | conda convert --platform win-32 parambokeh-$VERSION*.tar.bz2 -o ../ 53 | conda convert --platform win-64 parambokeh-$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/*/parambokeh-$VERSION*.tar.bz2 -u ioam 63 | ``` 64 | -------------------------------------------------------------------------------- /conda.recipe/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set sdata = load_setup_py_data() %} 2 | 3 | package: 4 | name: parambokeh 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 | - parambokeh 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 | # license_file: {{ sdata['license_file'] }} 46 | -------------------------------------------------------------------------------- /doc/FAQ.rst: -------------------------------------------------------------------------------- 1 | *** 2 | FAQ 3 | *** 4 | 5 | Questions we have been asked by users, plus potential pitfalls we hope 6 | to help users avoid. 7 | 8 | 9 | Can I use parambokeh with x (e.g. django)? 10 | ========================================== 11 | 12 | In principle, yes. ParamBokeh should be able to be made to work in 13 | any context where there is a bidirectional communication channel 14 | between Python and JavaScript. So far, we have made it work with the 15 | comms channels provided by Jupyter notebooks and by Bokeh server, but 16 | it should be feasible to add support for other cases where such 17 | communication is possible. 18 | 19 | 20 | I can't find the answer to an easy question 21 | =========================================== 22 | 23 | We are actively improving the documentation, but in the meantime 24 | please feel free to just open an issue with your question (no question 25 | too trivial). 26 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | Site is built with nbsite; see https://ioam.github.io/nbsite for 2 | details and instructions. 3 | -------------------------------------------------------------------------------- /doc/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/parambokeh/fb9744f216273c7b24e65d037b1d621c08d7fde6/doc/_static/favicon.ico -------------------------------------------------------------------------------- /doc/_static/param-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/parambokeh/fb9744f216273c7b24e65d037b1d621c08d7fde6/doc/_static/param-logo.png -------------------------------------------------------------------------------- /doc/about.rst: -------------------------------------------------------------------------------- 1 | About Us 2 | ======== 3 | 4 | ParamBokeh is primarily developed by the `HoloViews 5 | `_ team, with support from 6 | `Anaconda, Inc. `_. 7 | 8 | We have also developed 9 | similar interfaces for Jupyter notebook's ipywidgets (`ParamNB 10 | `_) and for tk (`ParamTk 11 | `_). 12 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from nbsite.shared_conf import * 4 | 5 | project = u'ParamBokeh' 6 | authors = u'ParamBokeh contributors' 7 | copyright = u'2017 ' + authors 8 | description = 'Generate Bokeh widgets for Parameterized objects' 9 | 10 | import parambokeh 11 | version = release = str(parambokeh.__version__) 12 | 13 | html_static_path += ['_static'] 14 | html_theme = 'sphinx_ioam_theme' 15 | html_theme_options = { 16 | 'logo':'param-logo.png', 17 | 'favicon':'favicon.ico', 18 | # 'css':'parambokeh.css' 19 | } 20 | 21 | _NAV = ( 22 | ('User Guide', 'user_guide/index'), 23 | ('FAQ', 'FAQ'), 24 | ('About', 'about') 25 | ) 26 | 27 | html_context.update({ 28 | 'PROJECT': project, 29 | 'DESCRIPTION': description, 30 | 'AUTHOR': authors, 31 | 'WEBSITE_URL': 'https://parambokeh.pyviz.org', 32 | 'VERSION': version, 33 | 'NAV': _NAV, 34 | 'LINKS': _NAV, 35 | 'SOCIAL': ( 36 | ('Gitter', '//gitter.im/ioam/holoviews'), 37 | ('Github', '//github.com/ioam/parambokeh'), 38 | ) 39 | }) 40 | 41 | nbbuild_patterns_to_take_along = ["simple.html"] 42 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | ********** 2 | ParamBokeh 3 | ********** 4 | 5 | **Generate bokeh widgets for parameterized objects** 6 | 7 | ParamBokeh is an `open-source 8 | `_ Python 9 | library that allows you to easily create GUIs from existing objects 10 | (in Jupyter notebooks, Bokeh apps, and potentially more) 11 | 12 | The `User Guide `_ shows the concepts involved with ParamBokeh 13 | and should help you get started using it as quickly as possible. 14 | 15 | Please feel free to report `issues 16 | `_ or `contribute code 17 | `_. You are also 18 | welcome to chat with the developers on `gitter 19 | `_. 20 | 21 | 22 | .. toctree:: 23 | :hidden: 24 | :maxdepth: 2 25 | 26 | Introduction 27 | User Guide 28 | FAQ 29 | About 30 | -------------------------------------------------------------------------------- /doc/user_guide/index.rst: -------------------------------------------------------------------------------- 1 | ********** 2 | User Guide 3 | ********** 4 | 5 | Contents: 6 | 7 | * `Introduction `_ 8 | What is ParamBokeh, and how to use it? 9 | 10 | * `ViewParameters `_ 11 | Dynamically control some visual output. 12 | 13 | * `Bokeh Apps `_ 14 | How to use ParamBokeh in a Bokeh server application. 15 | 16 | * `Django Apps `_ 17 | How to use ParamBokeh in a Django application. 18 | 19 | 20 | 21 | .. toctree:: 22 | :titlesonly: 23 | :maxdepth: 2 24 | 25 | Introduction 26 | View Parameters 27 | Bokeh Apps 28 | Django Apps 29 | -------------------------------------------------------------------------------- /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 | 7 | 8 | ############################################################ 9 | # Website building tasks; will move out to pyct 10 | 11 | def task_docs(): 12 | return {'actions': [ 13 | 'nbsite generate-rst --org ioam --project parambokeh --repo parambokeh --examples-path examples --doc-path doc', 14 | 'nbsite build --what=html --examples-path=examples --doc-path=doc --output=./builtdocs', 15 | 'touch ./builtdocs/.nojekyll', 16 | 'nbsite_cleandisthtml.py ./builtdocs take_a_chance']} 17 | -------------------------------------------------------------------------------- /etc/travis-miniconda.sh: -------------------------------------------------------------------------------- 1 | if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 2 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O miniconda.sh; 3 | else 4 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; 5 | fi 6 | bash miniconda.sh -b -p $HOME/miniconda 7 | export PATH="$HOME/miniconda/bin:$PATH" 8 | conda config --set always_yes yes --set changeps1 no 9 | conda update conda 10 | -------------------------------------------------------------------------------- /examples/apps/bokeh/simple.py: -------------------------------------------------------------------------------- 1 | ##### existing parameterized class 2 | 3 | import param 4 | import datetime as dt 5 | 6 | class Example(param.Parameterized): 7 | """Example Parameterized class""" 8 | log = [] 9 | x = param.Number(default=1.0,bounds=(0,100),precedence=0,doc="X position") 10 | write_to_log = param.Action(lambda obj: obj.log.append((dt.datetime.now(),obj.x)), 11 | doc="""Record value of x and timestamp.""",precedence=1) 12 | 13 | ##### create a properties frame for Example 14 | 15 | import parambokeh 16 | w = parambokeh.Widgets(Example, mode='server') 17 | 18 | 19 | ##### display value of Example.log in bokeh app 20 | 21 | from bokeh.io import curdoc 22 | from bokeh.layouts import layout 23 | from bokeh.models import Div 24 | 25 | log = Div() 26 | 27 | def update_log(): 28 | log.text = "
".join(["%s -- %s"%(t[0].strftime('%H:%M:%S.%f'),t[1]) for t in Example.log]) 29 | 30 | curdoc().add_periodic_callback(update_log, 200) 31 | 32 | layout = layout([log]) 33 | curdoc().add_root(layout) 34 | curdoc().title = "simple parambokeh + bokeh server example" 35 | -------------------------------------------------------------------------------- /examples/apps/django2/README.md: -------------------------------------------------------------------------------- 1 | # Sample django + parambokeh apps 2 | 3 | Demos showing how parambokeh can be integrated with django, to varying 4 | degrees. 5 | 6 | To install: `conda install "django=2"` (or install django your 7 | preferred way). 8 | 9 | 10 | ## sliders 11 | 12 | ![screenshot of sliders app](../../assets/sliders.png) 13 | 14 | Based on a standard django2 app template, the sliders app shows how to 15 | integrate parambokeh with a django view; there's no interaction 16 | between param and django models. 17 | 18 | Additions/modifications to django2 app template: 19 | 20 | * `sliders/sinewave.py`: a (pre-existing) parameterized object (to 21 | replace with your own) 22 | 23 | * `sliders/bk_sliders.py`: the parambokeh/bokeh app (based on 24 | https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py; 25 | to replace with your own) 26 | 27 | * sliders/apps.py: how a django app can import and use bokeh server 28 | 29 | * sliders/views.py and templates/base.html: getting the bokeh app 30 | into a django view 31 | 32 | To run: `python manage.py runserver`, then visit 33 | http://localhost:8000/sliders in your browser. 34 | 35 | 36 | ## polls 37 | 38 | Based on https://docs.djangoproject.com/en/2.0/intro/tutorial01/, the 39 | polls app shows one possible way to update a django model from a 40 | parameterized object (displayed using parambokeh in a django view). 41 | 42 | To run: `python manage.py migrate` (first time only, to create polls 43 | models); subsequently run `python manage.py runserver` then visit 44 | http://localhost:8000/polls 45 | 46 | In the future, we could provide a way for people to integrate param 47 | and django models. 48 | -------------------------------------------------------------------------------- /examples/apps/django2/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /examples/apps/django2/project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/parambokeh/fb9744f216273c7b24e65d037b1d621c08d7fde6/examples/apps/django2/project/__init__.py -------------------------------------------------------------------------------- /examples/apps/django2/project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for demo project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'ek$fq5z_l$8rlf&kql=-i6-3r$)2#!j^$1$ao^a^m&$2b(*moq' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'sliders.apps.Sliders', 35 | # 'sliders.apps.Polls', 36 | 'django.contrib.admin', 37 | 'django.contrib.auth', 38 | 'django.contrib.contenttypes', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'project.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'project.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | -------------------------------------------------------------------------------- /examples/apps/django2/project/urls.py: -------------------------------------------------------------------------------- 1 | """demo URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('sliders/', include('sliders.urls')), 21 | # path('polls/', include('polls.urls')), 22 | path('admin/', admin.site.urls), 23 | ] 24 | -------------------------------------------------------------------------------- /examples/apps/django2/project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for demo project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /examples/apps/django2/sliders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/parambokeh/fb9744f216273c7b24e65d037b1d621c08d7fde6/examples/apps/django2/sliders/__init__.py -------------------------------------------------------------------------------- /examples/apps/django2/sliders/admin.py: -------------------------------------------------------------------------------- 1 | #from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /examples/apps/django2/sliders/apps.py: -------------------------------------------------------------------------------- 1 | # based on: https://github.com/bokeh/bokeh/blob/0.12.16/examples/howto/server_embed/flask_embed.py 2 | 3 | from django.apps import AppConfig 4 | 5 | from bokeh.server.server import Server 6 | 7 | from tornado.ioloop import IOLoop 8 | 9 | from . import bk_sliders 10 | from . import bk_config 11 | 12 | def bk_worker(): 13 | # Note: num_procs must be 1; see e.g. flask_gunicorn_embed.py for num_procs>1 14 | server = Server({'/bk_sliders_app': bk_sliders.app}, 15 | io_loop=IOLoop(), 16 | address=bk_config.server['address'], 17 | port=bk_config.server['port'], 18 | allow_websocket_origin=["localhost:8000"]) 19 | server.start() 20 | server.io_loop.start() 21 | 22 | class Sliders(AppConfig): 23 | name = 'sliders' 24 | def ready(self): 25 | # For development, django provides autoreload, which results 26 | # in ready() being called twice on startup. We only want one 27 | # bokeh server, though. Trying to start a second bokeh server 28 | # just produces an error that's skipped over (port already in 29 | # use). Alternatively, using "python manage.py runserver 30 | # --noreload" avoids the problem. Otherwise, could add some 31 | # kind of lock... 32 | from threading import Thread 33 | Thread(target=bk_worker).start() 34 | -------------------------------------------------------------------------------- /examples/apps/django2/sliders/bk_config.py: -------------------------------------------------------------------------------- 1 | server = dict( 2 | address = "localhost", 3 | port = 5006 4 | ) 5 | 6 | -------------------------------------------------------------------------------- /examples/apps/django2/sliders/bk_sliders.py: -------------------------------------------------------------------------------- 1 | # cut down version of https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py 2 | 3 | from bokeh.layouts import row 4 | from bokeh.models import ColumnDataSource 5 | from bokeh.plotting import figure 6 | 7 | import parambokeh 8 | 9 | from .sinewave import SineWave 10 | 11 | 12 | def app(doc): 13 | 14 | x,y = SineWave() 15 | source = ColumnDataSource(data=dict(x=x, y=y)) 16 | 17 | import numpy as np # see TODO below about ranges 18 | plot = figure(plot_height=400, plot_width=400, 19 | tools="crosshair,pan,reset,save,wheel_zoom", 20 | x_range=[0, 4*np.pi], y_range=[-2.5, 2.5]) 21 | plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6) 22 | 23 | def update_sinewave(sw,**kw): 24 | x,y = sw() 25 | source.data = dict(x=x, y=y) 26 | # TODO couldn't figure out how to update ranges 27 | #plot.x_range.start,plot.x_range.end=pobj.x_range 28 | #plot.y_range.start,plot.y_range.end=pobj.y_range 29 | 30 | parambokeh.Widgets(SineWave, mode='server', doc=doc, callback=update_sinewave) 31 | doc.add_root(row(plot, width=800)) 32 | -------------------------------------------------------------------------------- /examples/apps/django2/sliders/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/parambokeh/fb9744f216273c7b24e65d037b1d621c08d7fde6/examples/apps/django2/sliders/migrations/__init__.py -------------------------------------------------------------------------------- /examples/apps/django2/sliders/models.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/apps/django2/sliders/sinewave.py: -------------------------------------------------------------------------------- 1 | # a sample (pre-existing) parameterized object from your existing code 2 | 3 | import numpy as np 4 | 5 | import param 6 | 7 | 8 | class SineWave(param.ParameterizedFunction): 9 | offset = param.Number(default=0.0, bounds=(-5.0,5.0)) 10 | amplitude = param.Number(default=1.0, bounds=(-5.0,5.0)) 11 | phase = param.Number(default=0.0,bounds=(0.0,2*np.pi)) 12 | frequency = param.Number(default=1.0, bounds=(0.1, 5.1)) 13 | N = param.Integer(default=200, bounds=(0,None)) 14 | #x_range = param.Range(default=(0, 4*np.pi),bounds=(0,4*np.pi)) 15 | #y_range = param.Range(default=(-2.5,2.5),bounds=(-10,10)) 16 | 17 | def __call__(self,**params): 18 | p = param.ParamOverrides(self,params) 19 | x = np.linspace(0, 4*np.pi, p.N) 20 | y = p.amplitude*np.sin(p.frequency*x + p.phase) + p.offset 21 | return x,y 22 | -------------------------------------------------------------------------------- /examples/apps/django2/sliders/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | parambokeh in django: sliders 5 | 6 | 7 | {% block content %} 8 | {{server_script|safe}} 9 | {% endblock %} 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/apps/django2/sliders/tests.py: -------------------------------------------------------------------------------- 1 | #from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /examples/apps/django2/sliders/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | app_name='sliders' 6 | urlpatterns = [ 7 | path('', views.sliders, name='sliders'), 8 | ] 9 | -------------------------------------------------------------------------------- /examples/apps/django2/sliders/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | from bokeh.embed import server_document 4 | 5 | from . import bk_config 6 | 7 | def sliders(request): 8 | return render(request, 'base.html', { 9 | "server_script": server_document('http://%s:%s/bk_sliders_app'%(bk_config.server['address'], 10 | bk_config.server['port']))}) 11 | -------------------------------------------------------------------------------- /examples/assets/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/parambokeh/fb9744f216273c7b24e65d037b1d621c08d7fde6/examples/assets/simple.png -------------------------------------------------------------------------------- /examples/assets/sliders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/parambokeh/fb9744f216273c7b24e65d037b1d621c08d7fde6/examples/assets/sliders.png -------------------------------------------------------------------------------- /examples/user_guide/Bokeh_Apps.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "In the [Introduction](Introduction.ipynb), we showed how to use parambokeh, using the Jupyter notebook to host our example. However, parambokeh widgets can also be used in other contexts. Here we show how parambokeh can be used in a bokeh server app." 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "We'll create a simple bokeh app that displays a log of every time a button has been pressed:\n", 15 | "\n", 16 | "![screenshot of simple app](../assets/simple.png)" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "In a python script, [examples/app/bokeh/simple.py](../apps/bokeh/simple.py), we first declare a sample Parameterized class to use as a demonstration object:" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "```python\n", 31 | "# existing parameterized class\n", 32 | "\n", 33 | "import param\n", 34 | "import datetime as dt\n", 35 | "\n", 36 | "class Example(param.Parameterized):\n", 37 | " \"\"\"Example Parameterized class\"\"\"\n", 38 | " log = []\n", 39 | " x = param.Number(default=1.0,bounds=(0,100),precedence=0,doc=\"X position\")\n", 40 | " write_to_log = param.Action(lambda obj: obj.log.append((dt.datetime.now(),obj.x)), \n", 41 | " doc=\"\"\"Record value of x and timestamp.\"\"\",precedence=1)\n", 42 | "```" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "Whenever `Example.write_to_log` is called, the current time and value of `x` are stored in `Example.log`.\n", 50 | "\n", 51 | "We now create a properties frame, just as in the [Introduction](Introduction.ipynb), but this time specifying `mode='server'`:" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "```python\n", 59 | "# create a properties frame for Example\n", 60 | "\n", 61 | "import parambokeh\n", 62 | "w = parambokeh.Widgets(Example, mode='server')\n", 63 | "```" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "Finally, we write a simple bokeh app that periodically updates a display showing the value of `Example.log`:" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "```python\n", 78 | "# display value of Example.log in bokeh app\n", 79 | "\n", 80 | "from bokeh.io import curdoc\n", 81 | "from bokeh.layouts import layout\n", 82 | "from bokeh.models import Div\n", 83 | "\n", 84 | "log = Div()\n", 85 | "\n", 86 | "def update_log():\n", 87 | " log.text = \"
\".join([\"%s -- %s\"%(t[0].strftime('%H:%M:%S.%f'),t[1]) for t in Example.log])\n", 88 | "\n", 89 | "curdoc().add_periodic_callback(update_log, 200)\n", 90 | "\n", 91 | "layout = layout([log])\n", 92 | "curdoc().add_root(layout)\n", 93 | "curdoc().title = \"simple parambokeh + bokeh server example\"\n", 94 | "```" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "The app can be launched using `bokeh serve simple.py`." 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "-----\n", 109 | "\n", 110 | "The example code used here is in [examples/app/bokeh/simple.py](../apps/bokeh/simple.py). For reference, the entire file is reproduced below:" 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "metadata": {}, 116 | "source": [ 117 | "```python\n", 118 | "##### existing parameterized class\n", 119 | "\n", 120 | "import param\n", 121 | "import datetime as dt\n", 122 | "\n", 123 | "class Example(param.Parameterized):\n", 124 | " \"\"\"Example Parameterized class\"\"\"\n", 125 | " log = []\n", 126 | " x = param.Number(default=1.0,bounds=(0,100),precedence=0,doc=\"X position\")\n", 127 | " write_to_log = param.Action(lambda obj: obj.log.append((dt.datetime.now(),obj.x)), \n", 128 | " doc=\"\"\"Record value of x and timestamp.\"\"\",precedence=1)\n", 129 | "\n", 130 | "##### create a properties frame for Example\n", 131 | "\n", 132 | "import parambokeh\n", 133 | "w = parambokeh.Widgets(Example, mode='server')\n", 134 | "\n", 135 | "\n", 136 | "##### display value of Example.log in bokeh app\n", 137 | "\n", 138 | "from bokeh.io import curdoc\n", 139 | "from bokeh.layouts import layout\n", 140 | "from bokeh.models import Div\n", 141 | "\n", 142 | "log = Div()\n", 143 | "\n", 144 | "def update_log():\n", 145 | " log.text = \"
\".join([\"%s -- %s\"%(t[0].strftime('%H:%M:%S.%f'),t[1]) for t in Example.log])\n", 146 | "\n", 147 | "curdoc().add_periodic_callback(update_log, 200)\n", 148 | "\n", 149 | "layout = layout([log])\n", 150 | "curdoc().add_root(layout)\n", 151 | "curdoc().title = \"simple parambokeh + bokeh server example\"\n", 152 | "```" 153 | ] 154 | } 155 | ], 156 | "metadata": { 157 | "language_info": { 158 | "name": "python", 159 | "pygments_lexer": "ipython3" 160 | } 161 | }, 162 | "nbformat": 4, 163 | "nbformat_minor": 1 164 | } 165 | -------------------------------------------------------------------------------- /examples/user_guide/Django_Apps.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "In the [Introduction](Introduction.ipynb), we showed how to use parambokeh in the Jupyter notebook. Then in [Bokeh App](Bokeh_App.ipynb), we showed how to use parambokeh in a bokeh server application. Here we show how parambokeh can be used in a Django application.\n", 8 | "\n", 9 | "Note that currently, the first (and only) example here does not cover integration between param and django models.\n", 10 | "\n", 11 | "To run the example app yourself, you will first need to install django 2 (e.g. `conda install \"django=2\"`)." 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "## sliders app" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "Based on a standard django2 app template, the sliders app shows how to integrate parambokeh with a django view; there's no interaction between param and django models.\n", 26 | "\n", 27 | "The sliders app is in `examples/apps/django2/sliders`, which is based on a standard django2 app template. We will cover the following additions/modifications to the django2 app template:\n", 28 | "\n", 29 | " * `sliders/sinewave.py`: a parameterized object (representing your pre-existing code)\n", 30 | " \n", 31 | " * `sliders/bk_sliders.py`: the parambokeh/bokeh app (based on https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py)\n", 32 | "\n", 33 | " * `sliders/apps.py`: how a django app can import and use bokeh server\n", 34 | "\n", 35 | " * `sliders/views.py` and `templates/base.html`: getting the bokeh app into a django view\n", 36 | "\n", 37 | "You should be able to run this app yourself by changing to the `examples/apps/django2` directory and then running: `python manage.py runserver`; visit http://localhost:8000/sliders in your browser to try the app." 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "metadata": {}, 43 | "source": [ 44 | "![screenshot of sliders app](../assets/sliders.png)" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "To start with, in `sliders/sinewave.py` we create a parameterized object to serve as a placeholder for your own, existing code:" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": null, 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "import numpy as np\n", 61 | "import param\n", 62 | "\n", 63 | "class SineWave(param.ParameterizedFunction):\n", 64 | " offset = param.Number(default=0.0, bounds=(-5.0,5.0))\n", 65 | " amplitude = param.Number(default=1.0, bounds=(-5.0,5.0))\n", 66 | " phase = param.Number(default=0.0,bounds=(0.0,2*np.pi))\n", 67 | " frequency = param.Number(default=1.0, bounds=(0.1, 5.1))\n", 68 | " N = param.Integer(default=200, bounds=(0,None))\n", 69 | " \n", 70 | " def __call__(self,**params):\n", 71 | " p = param.ParamOverrides(self,params)\n", 72 | " x = np.linspace(0, 4*np.pi, p.N)\n", 73 | " y = p.amplitude*np.sin(p.frequency*x + p.phase) + p.offset\n", 74 | " return x,y" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "When called, `SineWave` will return a tuple of two arrays, representing a sine wave:" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": null, 87 | "metadata": {}, 88 | "outputs": [], 89 | "source": [ 90 | "SineWave()[0][0:5]" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "We then take an existing bokeh example app, https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py, and modify it to use our parameterized `SineWave` class plus parambokeh to generate widgets automatically (as in previous tutorials):" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "```python\n", 105 | "from bokeh.layouts import row\n", 106 | "from bokeh.models import ColumnDataSource\n", 107 | "from bokeh.plotting import figure\n", 108 | "\n", 109 | "import parambokeh\n", 110 | "\n", 111 | "from .sinewave import SineWave\n", 112 | "\n", 113 | "def app(doc):\n", 114 | " x,y = SineWave()\n", 115 | " source = ColumnDataSource(data=dict(x=x, y=y))\n", 116 | "\n", 117 | " import numpy as np\n", 118 | " plot = figure(plot_height=400, plot_width=400,\n", 119 | " tools=\"crosshair,pan,reset,save,wheel_zoom\",\n", 120 | " x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])\n", 121 | " plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)\n", 122 | "\n", 123 | " def update_sinewave(sw,**kw):\n", 124 | " x,y = sw()\n", 125 | " source.data = dict(x=x, y=y)\n", 126 | " \n", 127 | " parambokeh.Widgets(SineWave, mode='server', doc=doc, callback=update_sinewave)\n", 128 | " doc.add_root(row(plot, width=800))\n", 129 | "```" 130 | ] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "The first Django-specific aspect of our example is to show how a Django app can import and use bokeh server. This is based on https://github.com/bokeh/bokeh/blob/0.12.16/examples/howto/server_embed/flask_embed.py, which shows how to embed bokeh in a flask app." 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "```python\n", 144 | "from django.apps import AppConfig\n", 145 | "\n", 146 | "from bokeh.server.server import Server\n", 147 | "\n", 148 | "from tornado.ioloop import IOLoop\n", 149 | "\n", 150 | "from . import bk_sliders\n", 151 | "from . import bk_config\n", 152 | "\n", 153 | "def bk_worker():\n", 154 | " # Note: num_procs must be 1; see e.g. flask_gunicorn_embed.py for num_procs>1\n", 155 | " server = Server({'/bk_sliders_app': bk_sliders.app},\n", 156 | " io_loop=IOLoop(),\n", 157 | " address=bk_config.server['address'],\n", 158 | " port=bk_config.server['port'],\n", 159 | " allow_websocket_origin=[\"localhost:8000\"])\n", 160 | " server.start()\n", 161 | " server.io_loop.start()\n", 162 | "\n", 163 | "class Sliders(AppConfig):\n", 164 | " name = 'sliders'\n", 165 | " def ready(self):\n", 166 | " from threading import Thread\n", 167 | " Thread(target=bk_worker).start()\n", 168 | "```" 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "metadata": {}, 174 | "source": [ 175 | "Here, `localhost:8000` is the address of the Django app. Note also we have made a simple config file, `bk_config.py`, for bokeh server settings:" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "```python\n", 183 | "server = dict(\n", 184 | " address = \"localhost\",\n", 185 | " port = 5006\n", 186 | ")\n", 187 | "```" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "Finally, in `sliders/views.py` we create a view to display the bokeh sliders app:" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "metadata": {}, 200 | "source": [ 201 | "```python\n", 202 | "from django.shortcuts import render\n", 203 | "\n", 204 | "from bokeh.embed import server_document\n", 205 | "\n", 206 | "from . import bk_config\n", 207 | "\n", 208 | "def sliders(request):\n", 209 | " return render(request, 'base.html', {\n", 210 | " \"server_script\": server_document('http://%s:%s/bk_sliders_app'%(bk_config.server['address'],\n", 211 | " bk_config.server['port']))})\n", 212 | "```" 213 | ] 214 | }, 215 | { 216 | "cell_type": "markdown", 217 | "metadata": {}, 218 | "source": [ 219 | "The corresponding template is in templates/base.html:" 220 | ] 221 | }, 222 | { 223 | "cell_type": "markdown", 224 | "metadata": {}, 225 | "source": [ 226 | "```\n", 227 | "{% block content %}\n", 228 | "{{server_script|safe}}\t\n", 229 | "{% endblock %}\n", 230 | "```" 231 | ] 232 | } 233 | ], 234 | "metadata": { 235 | "language_info": { 236 | "name": "python", 237 | "pygments_lexer": "ipython3" 238 | } 239 | }, 240 | "nbformat": 4, 241 | "nbformat_minor": 2 242 | } 243 | -------------------------------------------------------------------------------- /examples/user_guide/Introduction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "paramBokeh is a small library that represents Parameters graphically in a notebook and on bokeh server. 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. paramBokeh currently uses bokeh to display the widgets, but the design of Param and paramBokeh allows your code to be completely independent of the underlying widgets library, and can also be used with other widget frameworks such as paramNB, which uses ipywidgets.\n", 8 | "\n", 9 | "# Parameters and widgets\n", 10 | "\n", 11 | "To use paramBokeh, 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 | "class BaseClass(param.Parameterized):\n", 24 | " x = param.Parameter(default=3.14,doc=\"X position\")\n", 25 | " y = param.Parameter(default=\"Not editable\",constant=True)\n", 26 | " string_value = param.String(default=\"str\",doc=\"A string\")\n", 27 | " num_int = param.Integer(50000,bounds=(-200,100000))\n", 28 | " unbounded_int = param.Integer(23)\n", 29 | " float_with_hard_bounds = param.Number(8.2,bounds=(7.5,10))\n", 30 | " float_with_soft_bounds = param.Number(0.5,bounds=(0,None),softbounds=(0,2))\n", 31 | " unbounded_float = param.Number(30.01,precedence=0)\n", 32 | " hidden_parameter = param.Number(2.718,precedence=-1)\n", 33 | " integer_range = param.Range(default=(3,7),bounds=(0, 10))\n", 34 | " float_range = param.Range(default=(0,1.57),bounds=(0, 3.145))\n", 35 | " dictionary = param.Dict(default={\"a\":2, \"b\":9})\n", 36 | " \n", 37 | "class Example(BaseClass):\n", 38 | " \"\"\"An example Parameterized class\"\"\"\n", 39 | " timestamps = []\n", 40 | "\n", 41 | " boolean = param.Boolean(True, doc=\"A sample Boolean parameter\")\n", 42 | " color = param.Color(default='#FFFFFF')\n", 43 | " date = param.Date(dt.datetime(2017, 1, 1),\n", 44 | " bounds=(dt.datetime(2017, 1, 1), dt.datetime(2017, 2, 1)))\n", 45 | " select_string = param.ObjectSelector(default=\"yellow\",objects=[\"red\",\"yellow\",\"green\"])\n", 46 | " select_fn = param.ObjectSelector(default=list,objects=[list,set,dict])\n", 47 | " int_list = param.ListSelector(default=[3,5], objects=[1,3,5,7,9],precedence=0.5)\n", 48 | " single_file = param.FileSelector(path='../../*/*.py*',precedence=0.5)\n", 49 | " multiple_files = param.MultiFileSelector(path='../../*/*.py?',precedence=0.5)\n", 50 | " record_timestamp = param.Action(lambda x: x.timestamps.append(dt.datetime.now()), \n", 51 | " doc=\"\"\"Record timestamp.\"\"\",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 paramBokeh 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 parambokeh\n", 76 | "from bokeh.io import output_notebook\n", 77 | "output_notebook()" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "widgets = parambokeh.Widgets(BaseClass)" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": null, 92 | "metadata": {}, 93 | "outputs": [], 94 | "source": [ 95 | "parambokeh.Widgets(Example)" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "As you can see, `parambokeh.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 paramBokeh 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", 103 | "\n", 104 | "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", 105 | "\n", 106 | "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:" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "Example.unbounded_int" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "Example.num_int" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "Example.timestamps records the times you pressed the \"record timestamp\" button." 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "Example.timestamps" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [ 149 | "#Example.print_param_defaults() # see all parameter values" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "metadata": {}, 155 | "source": [ 156 | "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." 157 | ] 158 | }, 159 | { 160 | "cell_type": "markdown", 161 | "metadata": {}, 162 | "source": [ 163 | "You can install ParamBokeh as described at [github.com/ioam/parambokeh](https://github.com/ioam/parambokeh). Have fun widgeting!" 164 | ] 165 | } 166 | ], 167 | "metadata": { 168 | "language_info": { 169 | "name": "python", 170 | "pygments_lexer": "ipython3" 171 | } 172 | }, 173 | "nbformat": 4, 174 | "nbformat_minor": 1 175 | } 176 | -------------------------------------------------------------------------------- /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 | "PARAM_JSON_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 parambokeh\n", 35 | "from bokeh.io import output_notebook\n", 36 | "output_notebook()" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "## Example 1" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "The first minimal example will work if the notebook server is launched as follows:\n", 51 | "\n", 52 | "```\n", 53 | "PARAM_JSON_INIT='{\"p1\":5}' jupyter notebook\n", 54 | "```\n", 55 | "\n", 56 | "First let's show that the ``'PARAM_JSON_INIT'`` environment is defined:" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": null, 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | "os.environ['PARAM_JSON_INIT']" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "This string is JSON and the 'PARAM_JSON_INIT' is the default environment variable name to set parameters via the commandline. Lets make a simple parameterized class with a ``p1`` parameter:" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [ 81 | "class Test(param.Parameterized):\n", 82 | " \n", 83 | " p1 = param.Number(default=1, bounds=(0,10))" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "Now if we supply ``PARAMBOKEH.JSONInit`` as an initializer, the ``p1`` parameter is set from the default of 1 to the value of 5 specified by the environment variable:" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "parambokeh.Widgets(Test, initializer=parambokeh.JSONInit())" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "## Example 2" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "The second example will work if the notebook server is launched as follows:\n", 114 | "\n", 115 | "```\n", 116 | "TARGETED='{\"Target1\":{\"p1\":3}, \"Target2\":{\"s\":\"test\"}}' jupyter notebook\n", 117 | "```\n", 118 | "\n", 119 | "In this example, we show how you can target parameters to different classes using a different environment variable called ``TARGETED``:" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "os.environ['TARGETED']" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "metadata": {}, 134 | "source": [ 135 | "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:" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": null, 141 | "metadata": {}, 142 | "outputs": [], 143 | "source": [ 144 | "class Target1(param.Parameterized):\n", 145 | " \n", 146 | " p1 = param.Number(default=1, bounds=(0,10))\n", 147 | "\n", 148 | "class Target2(param.Parameterized):\n", 149 | " \n", 150 | " s = param.String(default=\"default\")\n" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "Now lets use ``PARAMBOKEH.Widgets`` on ``Target1``:" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "metadata": {}, 164 | "outputs": [], 165 | "source": [ 166 | "parambokeh.Widgets(Target1, initializer=parambokeh.JSONInit(varname='TARGETED'))" 167 | ] 168 | }, 169 | { 170 | "cell_type": "markdown", 171 | "metadata": {}, 172 | "source": [ 173 | "The value of ``p1`` is now ``3`` as requested.\n", 174 | "\n", 175 | "Now lets use ``PARAMBOKEH.Widgets`` on ``Target2``:" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": null, 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [ 184 | "parambokeh.Widgets(Target2, initializer=parambokeh.JSONInit(varname='TARGETED'))" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "metadata": { 190 | "collapsed": true 191 | }, 192 | "source": [ 193 | "## Example 3" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "\n", 201 | "The third example will work if the notebook server is launched as follows:\n", 202 | "\n", 203 | "```\n", 204 | "CUSTOM='{\"custom\":{\"val\":99}}' jupyter notebook\n", 205 | "```\n", 206 | "\n", 207 | "In this example, we show how you can target a specific instance using an environment variable called ``CUSTOM``:" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": null, 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [ 216 | "os.environ['CUSTOM']" 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "execution_count": null, 222 | "metadata": {}, 223 | "outputs": [], 224 | "source": [ 225 | "class Example(param.Parameterized):\n", 226 | " \n", 227 | " val = param.Number(default=1, bounds=(0,100))\n", 228 | " \n", 229 | "instance = Example()\n", 230 | "parambokeh.Widgets(instance, initializer=parambokeh.JSONInit(varname='CUSTOM', target='custom'))" 231 | ] 232 | }, 233 | { 234 | "cell_type": "markdown", 235 | "metadata": {}, 236 | "source": [ 237 | "## Example 4" 238 | ] 239 | }, 240 | { 241 | "cell_type": "markdown", 242 | "metadata": {}, 243 | "source": [ 244 | "You can also use a JSON file ending with the '.json' extension. For instance, if you execute:" 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": null, 250 | "metadata": {}, 251 | "outputs": [], 252 | "source": [ 253 | "import json\n", 254 | "json.dump({\"p1\":5}, open('param_init.json', 'w'))" 255 | ] 256 | }, 257 | { 258 | "cell_type": "markdown", 259 | "metadata": {}, 260 | "source": [ 261 | "You cam specify the full path or relative path to the JSON file with:\n", 262 | "\n", 263 | "\n", 264 | "```\n", 265 | "PARAM_JSON_INIT=param_init.json jupyter notebook\n", 266 | "```" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": null, 272 | "metadata": {}, 273 | "outputs": [], 274 | "source": [ 275 | "os.environ['PARAM_JSON_INIT']" 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": null, 281 | "metadata": {}, 282 | "outputs": [], 283 | "source": [ 284 | "class Test(param.Parameterized):\n", 285 | " \n", 286 | " p1 = param.Number(default=1, bounds=(0,10))\n", 287 | " \n", 288 | "parambokeh.Widgets(Test, initializer=parambokeh.JSONInit())" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": {}, 294 | "source": [ 295 | "Note that you can use ``JSONInit`` without setting any environment variables by specifying the JSON file directly:" 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": null, 301 | "metadata": {}, 302 | "outputs": [], 303 | "source": [ 304 | "parambokeh.Widgets(Test, initializer=parambokeh.JSONInit(json_file='param_init.json'))" 305 | ] 306 | }, 307 | { 308 | "cell_type": "markdown", 309 | "metadata": { 310 | "collapsed": true 311 | }, 312 | "source": [ 313 | "## Tips" 314 | ] 315 | }, 316 | { 317 | "cell_type": "markdown", 318 | "metadata": {}, 319 | "source": [ 320 | "\n", 321 | "* 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", 322 | "\n", 323 | "* 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", 324 | "\n", 325 | "```\n", 326 | "PARAM_JSON_INIT=`echo '{\"p1\":5}' | python -mjson.tool` jupyter notebook\n", 327 | "```" 328 | ] 329 | } 330 | ], 331 | "metadata": { 332 | "kernelspec": { 333 | "display_name": "Python 3", 334 | "language": "python", 335 | "name": "python3" 336 | }, 337 | "language_info": { 338 | "codemirror_mode": { 339 | "name": "ipython", 340 | "version": 3 341 | }, 342 | "file_extension": ".py", 343 | "mimetype": "text/x-python", 344 | "name": "python", 345 | "nbconvert_exporter": "python", 346 | "pygments_lexer": "ipython3", 347 | "version": "3.6.5" 348 | } 349 | }, 350 | "nbformat": 4, 351 | "nbformat_minor": 1 352 | } 353 | -------------------------------------------------------------------------------- /examples/user_guide/View_Parameters.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 parambokeh\n", 11 | "from bokeh.io import output_notebook\n", 12 | "import numpy as np\n", 13 | "import pandas as pd\n", 14 | "\n", 15 | "output_notebook()" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "The paramBokeh library provides an easy way to manipulate parameters on ``Parameterized`` using the widgets on bokeh server and within 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, ``paramBokeh`` also supplies so called ``View`` parameters, which render bokeh plot output in a widget area. The output parameters may be updated simply by setting the parameter on the class.\n", 23 | "\n", 24 | "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``." 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "class HTMLExample(param.Parameterized):\n", 34 | " \n", 35 | " magnitude = param.Number(1, bounds=(0, 10))\n", 36 | "\n", 37 | " output = parambokeh.view.HTML()\n", 38 | " \n", 39 | " def update(self, **kwargs):\n", 40 | " self.output = pd.DataFrame(np.random.rand(10,2)*self.magnitude).to_html()\n", 41 | "\n", 42 | "example = HTMLExample(name='HTMLExample')\n", 43 | "layout = parambokeh.Widgets(example, on_init=True, callback=example.update)" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "The ``HTML`` parameter accepts any arbitrary HTML string but for convenience paramBokeh also allows rendering bokeh and HoloViews plots using the ``Plot`` parameter. Note however that we can only replace a plot when deploying on bokeh server, within the notebook we may only update a plot:\n", 51 | "\n", 52 | "Additionally we can declare the ``view_position``, which specifies where the viewing widget will be placed in relation to the input widgets:" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": null, 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "import holoviews as hv\n", 62 | "\n", 63 | "class CurveExample(hv.streams.Stream):\n", 64 | "\n", 65 | " color = param.Color(default='#000000', precedence=0)\n", 66 | "\n", 67 | " element = param.ObjectSelector(default=hv.Curve,\n", 68 | " objects=[hv.Curve, hv.Scatter, hv.Area],\n", 69 | " precedence=0)\n", 70 | "\n", 71 | " amplitude = param.Number(default=2, bounds=(2, 5))\n", 72 | " \n", 73 | " frequency = param.Number(default=2, bounds=(1, 10))\n", 74 | " \n", 75 | " output = parambokeh.view.Plot()\n", 76 | " \n", 77 | " def view(self, *args, **kwargs):\n", 78 | " return self.element(self.amplitude*np.sin(np.linspace(0, np.pi*self.frequency)),\n", 79 | " vdims=[hv.Dimension('y', range=(-5, 5))]).opts(style=dict(color=self.color))\n", 80 | " \n", 81 | " def event(self, **kwargs):\n", 82 | " if not self.output or any(k in kwargs for k in ['color', 'element']):\n", 83 | " self.output = hv.DynamicMap(self.view, streams=[self], cache_size=0)\n", 84 | " else:\n", 85 | " super(CurveExample, self).event(**kwargs)\n", 86 | "\n", 87 | "example = CurveExample(name='HoloViews Example')\n", 88 | "parambokeh.Widgets(example, callback=example.event, on_init=True, view_position='right')" 89 | ] 90 | } 91 | ], 92 | "metadata": { 93 | "language_info": { 94 | "name": "python", 95 | "pygments_lexer": "ipython3" 96 | } 97 | }, 98 | "nbformat": 4, 99 | "nbformat_minor": 2 100 | } 101 | -------------------------------------------------------------------------------- /parambokeh/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import os 4 | import ast 5 | import itertools 6 | import functools 7 | import json 8 | 9 | import param 10 | 11 | from bokeh.document import Document 12 | from bokeh.io import curdoc 13 | from bokeh.layouts import row, column, widgetbox 14 | from bokeh.models.widgets import Div, Button, CheckboxGroup, TextInput 15 | from bokeh.models import CustomJS 16 | from bokeh.protocol import Protocol 17 | 18 | try: 19 | from IPython.display import publish_display_data 20 | 21 | import bokeh.embed.notebook 22 | from bokeh.util.string import encode_utf8 23 | from pyviz_comms import JupyterCommManager, JS_CALLBACK, bokeh_msg_handler, PYVIZ_PROXY 24 | IPYTHON_AVAILABLE = True 25 | except: 26 | IPYTHON_AVAILABLE = False 27 | 28 | from .widgets import wtype, literal_params 29 | from .util import named_objs, get_method_owner 30 | from .view import _View 31 | 32 | from param.version import Version 33 | __version__ = str(param.Version(fpath=__file__,archive_commit="fb9744f",reponame="parambokeh")) 34 | del Version 35 | 36 | ## 37 | # make pyct's example/data commands available if possible 38 | from functools import partial 39 | try: 40 | from pyct.cmd import copy_examples as _copy, fetch_data as _fetch, examples as _examples 41 | copy_examples = partial(_copy, 'parambokeh') 42 | fetch_data = partial(_fetch, 'parambokeh') 43 | examples = partial(_examples, 'parambokeh') 44 | except ImportError: 45 | def _missing_cmd(*args,**kw): return("install pyct to enable this command (e.g. `conda install pyct` or `pip install pyct[cmd]`)") 46 | _copy = _fetch = _examples = _missing_cmd 47 | def _err(): raise ValueError(_missing_cmd()) 48 | fetch_data = copy_examples = examples = _err 49 | del partial, _examples, _copy, _fetch 50 | ## 51 | 52 | 53 | def notebook_show(obj, doc, comm): 54 | """ 55 | Displays bokeh output inside a notebook. 56 | """ 57 | target = obj.ref['id'] 58 | load_mime = 'application/vnd.holoviews_load.v0+json' 59 | exec_mime = 'application/vnd.holoviews_exec.v0+json' 60 | 61 | # Publish plot HTML 62 | bokeh_script, bokeh_div, _ = bokeh.embed.notebook.notebook_content(obj, comm.id) 63 | publish_display_data(data={'text/html': encode_utf8(bokeh_div)}) 64 | 65 | # Publish comm manager 66 | JS = '\n'.join([PYVIZ_PROXY, JupyterCommManager.js_manager]) 67 | publish_display_data(data={load_mime: JS, 'application/javascript': JS}) 68 | 69 | # Publish bokeh plot JS 70 | msg_handler = bokeh_msg_handler.format(plot_id=target) 71 | comm_js = comm.js_template.format(plot_id=target, comm_id=comm.id, msg_handler=msg_handler) 72 | bokeh_js = '\n'.join([comm_js, bokeh_script]) 73 | 74 | # Note: extension should be altered so text/html is not required 75 | publish_display_data(data={exec_mime: '', 'text/html': '', 76 | 'application/javascript': bokeh_js}, 77 | metadata={exec_mime: {'id': target}}) 78 | 79 | 80 | def process_hv_plots(widgets, plots): 81 | """ 82 | Temporary fix to patch HoloViews plot comms 83 | """ 84 | bokeh_plots = [] 85 | for plot in plots: 86 | if hasattr(plot, '_update_callbacks'): 87 | for subplot in plot.traverse(lambda x: x): 88 | subplot.comm = widgets.server_comm 89 | for cb in subplot.callbacks: 90 | for c in cb.callbacks: 91 | c.code = c.code.replace(plot.id, widgets.plot_id) 92 | plot = plot.state 93 | bokeh_plots.append(plot) 94 | return bokeh_plots 95 | 96 | 97 | class default_label_formatter(param.ParameterizedFunction): 98 | "Default formatter to turn parameter names into appropriate widget labels." 99 | 100 | capitalize = param.Boolean(default=True, doc=""" 101 | Whether or not the label should be capitalized.""") 102 | 103 | replace_underscores = param.Boolean(default=True, doc=""" 104 | Whether or not underscores should be replaced with spaces.""") 105 | 106 | overrides = param.Dict(default={}, doc=""" 107 | Allows custom labels to be specified for specific parameter 108 | names using a dictionary where key is the parameter name and the 109 | value is the desired label.""") 110 | 111 | def __call__(self, pname): 112 | if pname in self.overrides: 113 | return self.overrides[pname] 114 | if self.replace_underscores: 115 | pname = pname.replace('_',' ') 116 | if self.capitalize: 117 | pname = pname[:1].upper() + pname[1:] 118 | return pname 119 | 120 | 121 | class Widgets(param.ParameterizedFunction): 122 | 123 | callback = param.Callable(default=None, doc=""" 124 | Custom callable to execute on button press 125 | (if `button`) else whenever a widget is changed, 126 | Should accept a Parameterized object argument.""") 127 | 128 | view_position = param.ObjectSelector(default='below', 129 | objects=['below', 'right', 'left', 'above'], 130 | doc=""" 131 | Layout position of any View parameter widgets.""") 132 | 133 | next_n = param.Parameter(default=0, doc=""" 134 | When executing cells, integer number to execute (or 'all'). 135 | A value of zero means not to control cell execution.""") 136 | 137 | on_init = param.Boolean(default=False, doc=""" 138 | Whether to do the action normally taken (executing cells 139 | and/or calling a callable) when first instantiating this 140 | object.""") 141 | 142 | button = param.Boolean(default=False, doc=""" 143 | Whether to show a button to control cell execution. 144 | If false, will execute `next` cells on any widget 145 | value change.""") 146 | 147 | button_text = param.String(default="Run", doc=""" 148 | Text to show on the 'next_n'/run button.""") 149 | 150 | show_labels = param.Boolean(default=True) 151 | 152 | display_threshold = param.Number(default=0,precedence=-10,doc=""" 153 | Parameters with precedence below this value are not displayed.""") 154 | 155 | default_precedence = param.Number(default=1e-8,precedence=-10,doc=""" 156 | Precedence value to use for parameters with no declared precedence. 157 | By default, zero predecence is available for forcing some parameters 158 | to the top of the list, and other values above the default_precedence 159 | values can be used to sort or group parameters arbitrarily.""") 160 | 161 | initializer = param.Callable(default=None, doc=""" 162 | User-supplied function that will be called on initialization, 163 | usually to update the default Parameter values of the 164 | underlying parameterized object.""") 165 | 166 | layout = param.ObjectSelector(default='column', 167 | objects=['row','column'],doc=""" 168 | Whether to lay out the buttons as a row or a column.""") 169 | 170 | continuous_update = param.Boolean(default=False, doc=""" 171 | If true, will continuously update the next_n and/or callback, 172 | if any, as a slider widget is dragged.""") 173 | 174 | mode = param.ObjectSelector(default='notebook', objects=['server', 'raw', 'notebook'], doc=""" 175 | Whether to use the widgets in server or notebook mode. In raw mode 176 | the widgets container will simply be returned.""") 177 | 178 | push = param.Boolean(default=True, doc=""" 179 | Whether to push data in notebook mode. Allows disabling pushing 180 | of data if the callback handles this itself.""") 181 | 182 | width = param.Integer(default=300, bounds=(0, None), doc=""" 183 | Width of widgetbox the parameter widgets are displayed in.""") 184 | 185 | label_formatter = param.Callable(default=default_label_formatter, allow_None=True, 186 | doc="Callable used to format the parameter names into widget labels.") 187 | 188 | # Timeout if a notebook comm message is swallowed 189 | timeout = 20000 190 | 191 | # Timeout before the first event is processed 192 | debounce = 20 193 | 194 | def __call__(self, parameterized, doc=None, plots=[], **params): 195 | self.p = param.ParamOverrides(self, params) 196 | if self.p.initializer: 197 | self.p.initializer(parameterized) 198 | 199 | self._widgets = {} 200 | self.parameterized = parameterized 201 | self.document = None 202 | if self.p.mode == 'notebook': 203 | if not IPYTHON_AVAILABLE: 204 | raise ImportError('IPython is not available, cannot use ' 205 | 'Widgets in notebook mode.') 206 | self.comm = JupyterCommManager.get_client_comm(on_msg=self.on_msg) 207 | # HACK: Detects HoloViews plots and lets them handle the comms 208 | hv_plots = [plot for plot in plots if hasattr(plot, 'comm')] 209 | self.server_comm = JupyterCommManager.get_server_comm() 210 | if hv_plots: 211 | self.document = [p.document for p in hv_plots][0] 212 | self.p.push = False 213 | else: 214 | self.document = doc or Document() 215 | else: 216 | self.document = doc or curdoc() 217 | self.server_comm = None 218 | self.comm = None 219 | 220 | self._queue = [] 221 | self._active = False 222 | self._widget_options = {} 223 | self.shown = False 224 | 225 | # Initialize root container 226 | widget_box = widgetbox(width=self.p.width) 227 | view_params = any(isinstance(p, _View) for p in parameterized.params().values()) 228 | layout = self.p.view_position 229 | container_type = column if layout in ['below', 'above'] else row 230 | container = container_type() if plots or view_params else widget_box 231 | self.plot_id = container.ref['id'] 232 | 233 | # Initialize widgets and populate container 234 | widgets, views = self.widgets() 235 | plots = views + plots 236 | widget_box.children = widgets 237 | 238 | plots = process_hv_plots(self, plots) 239 | 240 | if plots: 241 | view_box = column(plots) 242 | if layout in ['below', 'right']: 243 | children = [widget_box, view_box] 244 | else: 245 | children = [view_box, widget_box] 246 | container.children = children 247 | 248 | # Initialize view parameters 249 | for view in views: 250 | p_obj = self.parameterized.params(view.name) 251 | value = getattr(self.parameterized, view.name) 252 | if value is not None: 253 | rendered = p_obj.renderer(value, p_obj) 254 | self._update_trait(view.name, rendered) 255 | 256 | # Keeps track of changes between button presses 257 | self._changed = {} 258 | 259 | if self.p.on_init: 260 | self.execute() 261 | 262 | if self.p.mode == 'raw': 263 | return container 264 | 265 | self.document.add_root(container) 266 | if self.p.mode == 'notebook': 267 | notebook_show(container, self.document, self.server_comm) 268 | if self.document._hold is None: 269 | self.document.hold() 270 | self.shown = True 271 | return 272 | return self.document 273 | 274 | 275 | def on_msg(self, msg): 276 | p_name = msg['p_name'] 277 | p_obj = self.parameterized.params(p_name) 278 | if isinstance(p_obj, param.Action): 279 | getattr(self.parameterized, p_name)(self.parameterized) 280 | return 281 | w = self._widgets[p_name] 282 | self._queue.append((w, p_obj, p_name, None, None, msg['value'])) 283 | self.change_event() 284 | 285 | 286 | def on_change(self, w, p_obj, p_name, attr, old, new): 287 | self._queue.append((w, p_obj, p_name, attr, old, new)) 288 | if not self._active: 289 | self.document.add_timeout_callback(self.change_event, 50) 290 | self._active = True 291 | 292 | 293 | def change_event(self): 294 | if not self._queue: 295 | self._active = False 296 | return 297 | w, p_obj, p_name, attr, old, new_values = self._queue[-1] 298 | self._queue = [] 299 | 300 | error = False 301 | # Apply literal evaluation to values 302 | if (isinstance(w, TextInput) and isinstance(p_obj, literal_params)): 303 | try: 304 | new_values = ast.literal_eval(new_values) 305 | except: 306 | error = 'eval' 307 | 308 | if p_name in self._widget_options: 309 | mapping = self._widget_options[p_name] 310 | if isinstance(new_values, list): 311 | new_values = [mapping[el] for el in new_values] 312 | else: 313 | new_values = mapping.get(new_values, new_values) 314 | 315 | if isinstance(p_obj, param.Range): 316 | new_values = tuple(new_values) 317 | 318 | if isinstance(w, CheckboxGroup): 319 | new_values = True if (len(new_values)>0 and new_values[0]==0) else False 320 | 321 | # If no error during evaluation try to set parameter 322 | if not error: 323 | try: 324 | setattr(self.parameterized, p_name, new_values) 325 | except ValueError: 326 | error = 'validation' 327 | 328 | # Style widget to denote error state 329 | # apply_error_style(w, error) 330 | 331 | if not error and not self.p.button: 332 | self.execute({p_name: new_values}) 333 | else: 334 | self._changed[p_name] = new_values 335 | 336 | # document.hold() must have been done already? because this seems to work 337 | if self.p.mode == 'notebook' and self.p.push and self.document._held_events: 338 | self._send_notebook_diff() 339 | self._active = False 340 | 341 | 342 | def _send_notebook_diff(self): 343 | events = list(self.document._held_events) 344 | msg = Protocol("1.0").create("PATCH-DOC", events, use_buffers=True) 345 | self.document._held_events = [] 346 | if msg is None: 347 | return 348 | self.server_comm.send(msg.header_json) 349 | self.server_comm.send(msg.metadata_json) 350 | self.server_comm.send(msg.content_json) 351 | for header, payload in msg.buffers: 352 | self.server_comm.send(json.dumps(header)) 353 | self.server_comm.send(buffers=[payload]) 354 | 355 | def _update_trait(self, p_name, p_value, widget=None): 356 | widget = self._widgets[p_name] if widget is None else widget 357 | if isinstance(p_value, tuple): 358 | p_value, size = p_value 359 | if isinstance(widget, Div): 360 | widget.text = p_value 361 | else: 362 | if widget.children: 363 | widget.children.remove(widget.children[0]) 364 | widget.children.append(p_value) 365 | 366 | 367 | def _make_widget(self, p_name): 368 | p_obj = self.parameterized.params(p_name) 369 | 370 | if isinstance(p_obj, _View): 371 | p_obj._comm = self.server_comm 372 | p_obj._document = self.document 373 | p_obj._notebook = self.p.mode == 'notebook' 374 | 375 | widget_class = wtype(p_obj) 376 | value = getattr(self.parameterized, p_name) 377 | 378 | kw = dict(value=value) 379 | 380 | if self.p.label_formatter is not None: 381 | kw['title'] = self.p.label_formatter(p_name) 382 | else: 383 | kw['title'] = p_name 384 | 385 | kw['name'] = p_name 386 | 387 | if hasattr(p_obj, 'get_range') and not isinstance(kw['value'], dict): 388 | options = named_objs(p_obj.get_range().items()) 389 | value = kw['value'] 390 | lookup = {v: k for k, v in options} 391 | if isinstance(value, list): 392 | kw['value'] = [lookup[v] for v in value] 393 | elif isinstance(p_obj, param.FileSelector) and value is None: 394 | kw['value'] = '' 395 | else: 396 | kw['value'] = lookup[value] 397 | opt_lookup = {k: v for k, v in options} 398 | self._widget_options[p_name] = opt_lookup 399 | options = [(k, k) for k, v in options] 400 | kw['options'] = options 401 | 402 | if hasattr(p_obj, 'get_soft_bounds'): 403 | kw['start'], kw['end'] = p_obj.get_soft_bounds() 404 | 405 | w = widget_class(**kw) 406 | 407 | if hasattr(p_obj, 'callbacks') and value is not None: 408 | rendered = p_obj.renderer(value, p_obj) 409 | self._update_trait(p_name, rendered, w) 410 | 411 | if hasattr(p_obj, 'callbacks'): 412 | p_obj.callbacks[id(self.parameterized)] = functools.partial(self._update_trait, p_name) 413 | elif isinstance(w, CheckboxGroup): 414 | if self.p.mode in ['server', 'raw']: 415 | w.on_change('active', functools.partial(self.on_change, w, p_obj, p_name)) 416 | else: 417 | js_callback = self._get_customjs('active', p_name) 418 | w.js_on_change('active', js_callback) 419 | elif isinstance(w, Button): 420 | if self.p.mode in ['server', 'raw']: 421 | w.on_click(functools.partial(value,self.parameterized)) 422 | else: 423 | w.js_on_click(self._get_customjs('active', p_name)) 424 | elif not p_obj.constant: 425 | if self.p.mode in ['server', 'raw']: 426 | cb = functools.partial(self.on_change, w, p_obj, p_name) 427 | if 'value' in w.properties(): 428 | w.on_change('value', cb) 429 | elif 'range' in w.properties(): 430 | w.on_change('range', cb) 431 | else: 432 | if 'value' in w.properties(): 433 | change = 'value' 434 | elif 'range' in w.properties(): 435 | change = 'range' 436 | customjs = self._get_customjs(change, p_name) 437 | w.js_on_change(change, customjs) 438 | 439 | return w 440 | 441 | 442 | def _get_customjs(self, change, p_name): 443 | """ 444 | Returns a CustomJS callback that can be attached to send the 445 | widget state across the notebook comms. 446 | """ 447 | data_template = "data = {{p_name: '{p_name}', value: cb_obj['{change}']}};" 448 | fetch_data = data_template.format(change=change, p_name=p_name) 449 | self_callback = JS_CALLBACK.format(comm_id=self.comm.id, 450 | timeout=self.timeout, 451 | debounce=self.debounce, 452 | plot_id=self.plot_id) 453 | js_callback = CustomJS(code='\n'.join([fetch_data, 454 | self_callback])) 455 | return js_callback 456 | 457 | 458 | def widget(self, param_name): 459 | """Get widget for param_name""" 460 | if param_name not in self._widgets: 461 | self._widgets[param_name] = self._make_widget(param_name) 462 | return self._widgets[param_name] 463 | 464 | 465 | def execute(self, changed={}): 466 | if self.p.callback is not None: 467 | if get_method_owner(self.p.callback) is self.parameterized: 468 | self.p.callback(**changed) 469 | else: 470 | self.p.callback(self.parameterized, **changed) 471 | 472 | def widgets(self): 473 | """Return name,widget boxes for all parameters (i.e., a property sheet)""" 474 | 475 | params = self.parameterized.params().items() 476 | key_fn = lambda x: x[1].precedence if x[1].precedence is not None else self.p.default_precedence 477 | sorted_precedence = sorted(params, key=key_fn) 478 | outputs = [k for k, p in sorted_precedence if isinstance(p, _View)] 479 | filtered = [(k,p) for (k,p) in sorted_precedence 480 | if ((p.precedence is None) or (p.precedence >= self.p.display_threshold)) 481 | and k not in outputs] 482 | groups = itertools.groupby(filtered, key=key_fn) 483 | sorted_groups = [sorted(grp) for (k,grp) in groups] 484 | ordered_params = [el[0] for group in sorted_groups for el in group] 485 | 486 | # Format name specially 487 | ordered_params.pop(ordered_params.index('name')) 488 | widgets = [Div(text='{0}'.format(self.parameterized.name))] 489 | 490 | def format_name(pname): 491 | p = self.parameterized.params(pname) 492 | # omit name for buttons, which already show the name on the button 493 | name = "" if issubclass(type(p),param.Action) else pname 494 | return Div(text=name) 495 | 496 | if self.p.show_labels: 497 | widgets += [self.widget(pname) for pname in ordered_params] 498 | else: 499 | widgets += [self.widget(pname) for pname in ordered_params] 500 | 501 | if self.p.button and not (self.p.callback is None and self.p.next_n==0): 502 | display_button = Button(label=self.p.button_text) 503 | def click_cb(): 504 | # Execute and clear changes since last button press 505 | try: 506 | self.execute(self._changed) 507 | except Exception as e: 508 | self._changed.clear() 509 | raise e 510 | self._changed.clear() 511 | display_button.on_click(click_cb) 512 | widgets.append(display_button) 513 | 514 | outputs = [self.widget(pname) for pname in outputs] 515 | return widgets, outputs 516 | 517 | 518 | class JSONInit(param.Parameterized): 519 | """ 520 | Callable that can be passed to Widgets.initializer to set Parameter 521 | values using JSON. There are three approaches that may be used: 522 | 1. If the json_file argument is specified, this takes precedence. 523 | 2. The JSON file path can be specified via an environment variable. 524 | 3. The JSON can be read directly from an environment variable. 525 | Here is an easy example of setting such an environment variable on 526 | the commandline: 527 | PARAM_JSON_INIT='{"p1":5}' jupyter notebook 528 | This addresses any JSONInit instances that are inspecting the 529 | default environment variable called PARAM_JSON_INIT, instructing it to set 530 | the 'p1' parameter to 5. 531 | """ 532 | 533 | varname = param.String(default='PARAM_JSON_INIT', doc=""" 534 | The name of the environment variable containing the JSON 535 | specification.""") 536 | 537 | target = param.String(default=None, doc=""" 538 | Optional key in the JSON specification dictionary containing the 539 | desired parameter values.""") 540 | 541 | json_file = param.String(default=None, doc=""" 542 | Optional path to a JSON file containing the parameter settings.""") 543 | 544 | 545 | def __call__(self, parameterized): 546 | 547 | warnobj = param.main if isinstance(parameterized, type) else parameterized 548 | param_class = (parameterized if isinstance(parameterized, type) 549 | else parameterized.__class__) 550 | 551 | 552 | target = self.target if self.target is not None else param_class.__name__ 553 | 554 | env_var = os.environ.get(self.varname, None) 555 | if env_var is None and self.json_file is None: return 556 | 557 | if self.json_file or env_var.endswith('.json'): 558 | try: 559 | fname = self.json_file if self.json_file else env_var 560 | spec = json.load(open(os.path.abspath(fname), 'r')) 561 | except: 562 | warnobj.warning('Could not load JSON file %r' % spec) 563 | else: 564 | spec = json.loads(env_var) 565 | 566 | if not isinstance(spec, dict): 567 | warnobj.warning('JSON parameter specification must be a dictionary.') 568 | return 569 | 570 | if target in spec: 571 | params = spec[target] 572 | else: 573 | params = spec 574 | 575 | for name, value in params.items(): 576 | try: 577 | parameterized.set_param(**{name:value}) 578 | except ValueError as e: 579 | warnobj.warning(str(e)) 580 | -------------------------------------------------------------------------------- /parambokeh/__main__.py: -------------------------------------------------------------------------------- 1 | def main(args=None): 2 | try: 3 | import pyct.cmd 4 | except ImportError: 5 | import sys 6 | from . import _missing_cmd 7 | print(_missing_cmd()) 8 | sys.exit(1) 9 | return pyct.cmd.substitute_main('parambokeh',args=args) 10 | 11 | if __name__ == "__main__": 12 | main() 13 | -------------------------------------------------------------------------------- /parambokeh/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/parambokeh/fb9744f216273c7b24e65d037b1d621c08d7fde6/parambokeh/tests/__init__.py -------------------------------------------------------------------------------- /parambokeh/tests/test_dummy.py: -------------------------------------------------------------------------------- 1 | def test_dummy(): 2 | print(1) 3 | pass 4 | -------------------------------------------------------------------------------- /parambokeh/util.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import inspect 3 | 4 | if sys.version_info.major == 3: 5 | unicode = str 6 | basestring = str 7 | 8 | 9 | def as_unicode(obj): 10 | """ 11 | Safely casts any object to unicode including regular string 12 | (i.e. bytes) types in python 2. 13 | """ 14 | if sys.version_info.major < 3 and isinstance(obj, str): 15 | obj = obj.decode('utf-8') 16 | return unicode(obj) 17 | 18 | 19 | def named_objs(objlist): 20 | """ 21 | Given a list of objects, returns a dictionary mapping from 22 | string name for the object to the object itself. 23 | """ 24 | objs = [] 25 | for k, obj in objlist: 26 | if hasattr(k, '__name__'): 27 | k = k.__name__ 28 | else: 29 | k = as_unicode(k) 30 | objs.append((k, obj)) 31 | return objs 32 | 33 | 34 | def get_method_owner(meth): 35 | """ 36 | Returns the instance owning the supplied instancemethod or 37 | the class owning the supplied classmethod. 38 | """ 39 | if inspect.ismethod(meth): 40 | if sys.version_info < (3,0): 41 | return meth.im_class if meth.im_self is None else meth.im_self 42 | else: 43 | return meth.__self__ 44 | -------------------------------------------------------------------------------- /parambokeh/view.py: -------------------------------------------------------------------------------- 1 | import param 2 | 3 | def render_function(obj, view): 4 | """ 5 | The default Renderer function which handles HoloViews objects. 6 | """ 7 | try: 8 | import holoviews as hv 9 | except: 10 | hv = None 11 | 12 | if hv and isinstance(obj, hv.core.Dimensioned): 13 | renderer = hv.renderer('bokeh') 14 | if not view._notebook: 15 | renderer = renderer.instance(mode='server') 16 | plot = renderer.get_plot(obj, doc=view._document) 17 | if view._notebook: 18 | plot.comm = view._comm 19 | plot.document = view._document 20 | return plot.state 21 | return obj 22 | 23 | 24 | class _View(param.Parameter): 25 | """ 26 | View parameters hold displayable output, they may have a callback, 27 | which is called when a new value is set on the parameter. 28 | Additionally they allow supplying a renderer function which renders 29 | the display output. The renderer function should return the 30 | appropriate output for the View parameter (e.g. HTML or PNG data), 31 | and may optionally supply the desired size of the viewport. 32 | """ 33 | 34 | __slots__ = ['callbacks', 'renderer', '_comm', '_document', '_notebook'] 35 | 36 | def __init__(self, default=None, callback=None, renderer=None, **kwargs): 37 | self.callbacks = {} 38 | self.renderer = (render_function if renderer is None else renderer) 39 | super(_View, self).__init__(default, **kwargs) 40 | self._comm = None 41 | self._document = None 42 | self._notebook = False 43 | 44 | def __set__(self, obj, val): 45 | super(_View, self).__set__(obj, val) 46 | obj_id = id(obj) 47 | if obj_id in self.callbacks: 48 | self.callbacks[obj_id](self.renderer(val, self)) 49 | 50 | 51 | class Plot(_View): 52 | """ 53 | Plot is a View parameter that allows displaying bokeh plots as output. 54 | """ 55 | 56 | 57 | class HTML(_View): 58 | """ 59 | HTML is a View parameter that allows displaying arbitrary HTML. 60 | """ 61 | -------------------------------------------------------------------------------- /parambokeh/widgets.py: -------------------------------------------------------------------------------- 1 | import decimal 2 | 3 | import param 4 | from param.parameterized import classlist 5 | 6 | from bokeh.layouts import column 7 | from bokeh.models.widgets import ( 8 | Button, TextInput, Div, Slider, CheckboxGroup, 9 | DatePicker, MultiSelect, Select, RangeSlider 10 | ) 11 | 12 | from .util import as_unicode 13 | from .view import Plot, HTML 14 | 15 | 16 | def TextWidget(*args, **kw): 17 | """Forces a parameter value to be text""" 18 | kw['value'] = str(kw['value']) 19 | kw.pop('options', None) 20 | return TextInput(*args,**kw) 21 | 22 | def StaticText(*args, **kw): 23 | kw['text'] = '{title}: {value}'.format(title=kw.pop('title'), 24 | value=as_unicode(kw.pop('value'))) 25 | return Div(*args, **kw) 26 | 27 | def Checkbox(*args, **kw): 28 | val = kw.pop('value') 29 | kw['active'] = [0] if val else [] 30 | kw['labels'] = [kw.pop('title')] 31 | return CheckboxGroup(*args, **kw) 32 | 33 | def ButtonWidget(*args, **kw): 34 | kw['label'] = kw.pop('title') 35 | kw.pop('value') # button doesn't have value (value attached as click callback) 36 | return Button(*args, **kw) 37 | 38 | # TODO: make a composite box/slider widget; slider only appears if 39 | # there's a range. 40 | 41 | # TODO: There's confusion about the thing being represented and the 42 | # thing doing the representing. I will rework the widgets more 43 | # comprehensively once we have things working as-is in bokeh 0.12.10. 44 | 45 | def FloatSlider(*args,**kw): 46 | if kw.get('start') is None or kw.get('end') is None: 47 | kw.pop('start',None) 48 | kw.pop('end',None) 49 | kw.pop('step',None) 50 | kw['value'] = str(kw['value']) 51 | return TextInput(*args,**kw) 52 | else: 53 | ### 54 | # TODO: some improvement will come from composite box/optional 55 | # slider widget - will be able to get appropriate step from 56 | # user-entered value. 57 | p = decimal.Decimal(str(kw['value'])).as_tuple()[2] 58 | kw['step'] = 10**p 59 | #kw['format'] = "0[.]" + "0".zfill(-p) 60 | kw['format'] = "0[.]" + "".rjust(-p,'0') 61 | ### 62 | if kw.get('value', None) is None: 63 | kw['value'] = kw['start'] 64 | return Slider(*args, **kw) 65 | 66 | 67 | def IntSlider(*args, **kw): 68 | if kw.get('start') is None or kw.get('end') is None: 69 | kw.pop('start',None) 70 | kw.pop('end',None) 71 | kw.pop('step',None) 72 | kw['value'] = str(kw['value']) 73 | return TextInput(*args,**kw) 74 | else: 75 | kw['step'] = 1 76 | if kw.get('value', None) is None: 77 | kw['value'] = kw['start'] 78 | return Slider(*args, **kw) 79 | 80 | def DateWidget(*args, **kw): 81 | kw['min_date'] = kw.pop('start') 82 | kw['max_date'] = kw.pop('end') 83 | return DatePicker(*args,**kw) 84 | 85 | def RangeWidget(*args, **kw): 86 | if not 'start' in kw and 'end' in kw: 87 | kw['start'], kw['end'] = kw['value'] 88 | elif 'value' not in kw: 89 | kw['value'] = (kw['start'], kw['end']) 90 | # TODO: should use param definition of integer (when that is 91 | # itself fixed...). 92 | if isinstance(kw['start'], int) and isinstance(kw['end'], int): 93 | kw['step'] = 1 94 | return RangeSlider(*args, **kw) 95 | 96 | def PlotWidget(*args, **kw): 97 | return column(name=kw['name']) 98 | 99 | def HTMLWidget(*args, **kw): 100 | return Div(name=kw['name']) 101 | 102 | 103 | ptype2wtype = { 104 | param.Parameter: TextWidget, 105 | param.Dict: TextWidget, 106 | param.Selector: Select, 107 | param.Boolean: Checkbox, 108 | param.Number: FloatSlider, 109 | param.Integer: IntSlider, 110 | param.Range: RangeWidget, 111 | param.ListSelector: MultiSelect, 112 | param.Action: ButtonWidget, 113 | param.Date: DateWidget, 114 | Plot: PlotWidget, 115 | HTML: HTMLWidget 116 | 117 | } 118 | 119 | def wtype(pobj): 120 | if pobj.constant: # Ensure constant parameters cannot be edited 121 | return StaticText 122 | for t in classlist(type(pobj))[::-1]: 123 | if t in ptype2wtype: 124 | return ptype2wtype[t] 125 | 126 | 127 | # Define parameters which should be evaluated using ast.literal_eval 128 | literal_params = (param.Dict, param.List, param.Tuple, param.Number) 129 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "param >=1.7.0", 4 | "pyct >=0.4.4", 5 | "setuptools >=30.3.0" 6 | ] 7 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = parambokeh 3 | version = attr: param.version.get_setupcfg_version 4 | description = ParamBokeh provides an easy way to generate a UI for param based classes in the notebook or on bokeh server. 5 | long_description = file: README.md 6 | long_description_content_type = text/markdown 7 | license = BSD 3-Clause License 8 | license_file = LICENSE.txt 9 | classifiers = 10 | License :: OSI Approved :: BSD License 11 | Operating System :: OS Independent 12 | Programming Language :: Python 13 | Programming Language :: Python :: 2.7 14 | Programming Language :: Python :: 3.5 15 | Programming Language :: Python :: 3.6 16 | Development Status :: 4 - Beta 17 | author = PyViz 18 | author_email = holoviews@gmail.com 19 | maintainer = PyViz 20 | maintainer_email = holoviews@gmail.com 21 | url = https://parambokeh.pyviz.org 22 | project_urls = 23 | Bug Tracker = https://github.com/ioam/parambokeh/issues 24 | Documentation = https://parambokeh.pyviz.org 25 | Source Code = https://github.com/ioam/parambokeh 26 | 27 | 28 | [options] 29 | include_package_data = True 30 | packages = find: 31 | python_requires = >=2.7 32 | install_requires = 33 | param >=1.6.1 34 | bokeh >=0.12.10 35 | pyviz_comms 36 | 37 | [options.extras_require] 38 | tests = 39 | nbsmoke >=0.2.6 40 | flake8 41 | pytest >=2.8.5 42 | 43 | examples = 44 | pyct[cmd] 45 | holoviews >=1.9.0 46 | pandas 47 | jupyter 48 | pyparsing 49 | 50 | doc = 51 | nbsite 52 | sphinx_ioam_theme 53 | 54 | [options.entry_points] 55 | console_scripts = 56 | parambokeh = parambokeh.__main__:main 57 | 58 | [wheel] 59 | universal = 1 60 | 61 | [tool:autover.configparser_workaround.archive_commit=fb9744f] 62 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import os, sys, shutil 3 | 4 | import pyct.build 5 | 6 | if __name__=="__main__": 7 | # TODO: hope to eliminate the examples handling from here 8 | # (i.e. all lines except setup()), moving it to pyct 9 | example_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 10 | 'parambokeh','examples') 11 | if 'develop' not in sys.argv: 12 | pyct.build.examples(example_path, __file__, force=True) 13 | 14 | setup() 15 | 16 | if os.path.isdir(example_path): 17 | shutil.rmtree(example_path) 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # For use with pyct (https://github.com/pyviz/pyct), but just standard 2 | # tox config (works with tox alone). 3 | 4 | [tox] 5 | # python version test group extra envs extra commands 6 | envlist = {py27,py36}-{lint,unit,examples,all}-{default,examples}-{dev,pkg} 7 | build = wheel 8 | 9 | [_lint] 10 | description = Flake check python and notebooks, and verify notebooks 11 | deps = .[tests] 12 | # verify takes quite a long time - maybe split into flakes and lint? 13 | commands = flake8 14 | pytest --nbsmoke-lint -k ".ipynb" 15 | # requires hv, pandas etc unless missing modules turned into warnings 16 | # pytest --nbsmoke-verify -k ".ipynb" 17 | 18 | [_unit] 19 | description = Run unit tests 20 | deps = .[tests] 21 | commands = pytest parambokeh 22 | 23 | [_examples] 24 | description = Test that examples run 25 | deps = .[examples, tests] 26 | commands = pytest --nbsmoke-run -k ".ipynb" 27 | # could add more, to test types of example other than nbs 28 | 29 | [_all] 30 | description = Run all tests 31 | deps = .[examples, tests] 32 | commands = {[_lint]commands} 33 | {[_unit]commands} 34 | {[_examples]commands} 35 | 36 | [_pkg] 37 | commands = parambokeh copy-examples --path=. --force 38 | 39 | [testenv] 40 | changedir = {envtmpdir} 41 | 42 | commands = examples-pkg: {[_pkg]commands} 43 | unit: {[_unit]commands} 44 | lint: {[_lint]commands} 45 | examples: {[_examples]commands} 46 | all: {[_all]commands} 47 | 48 | deps = unit: {[_unit]deps} 49 | lint: {[_lint]deps} 50 | examples: {[_examples]deps} 51 | all: {[_all]deps} 52 | 53 | [pytest] 54 | addopts = -v --pyargs --doctest-modules --doctest-ignore-import-errors 55 | norecursedirs = doc .git dist build _build .ipynb_checkpoints 56 | # notebooks to skip running; one case insensitive re to match per line 57 | nbsmoke_skip_run = ^.*JSONInit\.ipynb$ 58 | 59 | [flake8] 60 | include = *.py 61 | # run_tests.py is generated by conda build, which appears to have a 62 | # bug resulting in code being duplicated a couple of times. 63 | exclude = .git,__pycache__,.tox,.eggs,*.egg,doc,dist,build,_build,.ipynb_checkpoints,run_test.py 64 | ignore = E, 65 | W 66 | --------------------------------------------------------------------------------