diff --git a/doc/user_guide/index.rst b/doc/user_guide/index.rst index 030e786..c09eac7 100644 --- a/doc/user_guide/index.rst +++ b/doc/user_guide/index.rst @@ -7,17 +7,22 @@ Contents: * `Introduction `_ What is ParamBokeh, and how to use it? -* `Usage in Bokeh Apps `_ - How to use ParamBokeh in a Bokeh application. - * `ViewParameters `_ Dynamically control some visual output. +* `Bokeh Apps `_ + How to use ParamBokeh in a Bokeh server application. + +* `Django Apps `_ + How to use ParamBokeh in a Django application. + + .. toctree:: :titlesonly: :maxdepth: 2 Introduction - Bokeh Apps View Parameters + Bokeh Apps + Django Apps diff --git a/examples/apps/bokeh/simple.py b/examples/apps/bokeh/simple.py new file mode 100644 index 0000000..5bae8cc --- /dev/null +++ b/examples/apps/bokeh/simple.py @@ -0,0 +1,34 @@ +##### existing parameterized class + +import param +import datetime as dt + +class Example(param.Parameterized): + """Example Parameterized class""" + log = [] + x = param.Number(default=1.0,bounds=(0,100),precedence=0,doc="X position") + write_to_log = param.Action(lambda obj: obj.log.append((dt.datetime.now(),obj.x)), + doc="""Record value of x and timestamp.""",precedence=1) + +##### create a properties frame for Example + +import parambokeh +w = parambokeh.Widgets(Example, mode='server') + + +##### display value of Example.log in bokeh app + +from bokeh.io import curdoc +from bokeh.layouts import layout +from bokeh.models import Div + +log = Div() + +def update_log(): + log.text = "
".join(["%s -- %s"%(t[0].strftime('%H:%M:%S.%f'),t[1]) for t in Example.log]) + +curdoc().add_periodic_callback(update_log, 200) + +layout = layout([log]) +curdoc().add_root(layout) +curdoc().title = "simple parambokeh + bokeh server example" diff --git a/examples/user_guide/Bokeh_App.ipynb b/examples/user_guide/Bokeh_App.ipynb deleted file mode 100644 index 30343c5..0000000 --- a/examples/user_guide/Bokeh_App.ipynb +++ /dev/null @@ -1,157 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the [Introduction](Introduction.ipynb), we showed how to use parambokeh inside a notebook. However, parambokeh widgets can be used in other contexts. Here we show how parambokeh can be used in a bokeh app.\n", - "\n", - "In a python script, `simple.py`, we first declare a sample Parameterized classes with various Parameters to use as a demonstration object (see the [Introduction](Introduction.ipynb) for more information):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%writefile simple.py\n", - "\n", - "import param\n", - "import datetime as dt\n", - " \n", - "class Example(param.Parameterized):\n", - " \"\"\"An example Parameterized class\"\"\"\n", - " timestamps = []\n", - " \n", - " x = param.Parameter(default=3.14,doc=\"X position\")\n", - " y = param.Parameter(default=\"Not editable\",constant=True)\n", - " string_value = param.String(default=\"str\",doc=\"A string\")\n", - " num_int = param.Integer(50000,bounds=(-200,100000))\n", - " unbounded_int = param.Integer(23)\n", - " float_with_hard_bounds = param.Number(8.2,bounds=(7.5,10))\n", - " float_with_soft_bounds = param.Number(0.5,bounds=(0,None),softbounds=(0,2))\n", - " unbounded_float = param.Number(30.01,precedence=0)\n", - " hidden_parameter = param.Number(2.718,precedence=-1)\n", - " integer_range = param.Range(default=(3,7),bounds=(0, 10))\n", - " float_range = param.Range(default=(0,1.57),bounds=(0, 3.145))\n", - " dictionary = param.Dict(default={\"a\":2, \"b\":9})\n", - " boolean = param.Boolean(True, doc=\"A sample Boolean parameter\")\n", - " color = param.Color(default='#FFFFFF')\n", - " date = param.Date(dt.datetime(2017, 1, 1),\n", - " bounds=(dt.datetime(2017, 1, 1), dt.datetime(2017, 2, 1)))\n", - " select_string = param.ObjectSelector(default=\"yellow\",objects=[\"red\",\"yellow\",\"green\"])\n", - " select_fn = param.ObjectSelector(default=list,objects=[list,set,dict])\n", - " int_list = param.ListSelector(default=[3,5], objects=[1,3,5,7,9],precedence=0.5)\n", - " single_file = param.FileSelector(path='../../*/*.py*',precedence=0.5)\n", - " multiple_files = param.MultiFileSelector(path='../../*/*.py?',precedence=0.5)\n", - " record_timestamp = param.Action(lambda x: x.timestamps.append(dt.datetime.now()), \n", - " doc=\"\"\"Record timestamp.\"\"\",precedence=0.7)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We then configure bokeh to create an html file, but you could skip this for a server app. See [bokeh's documentation](https://bokeh.pydata.org/en/latest/) for more information." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%writefile -a simple.py\n", - "\n", - "import bokeh.io\n", - "bokeh.io.output_file('simple.html')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can create our widgets for the Example class - as we did for notebooks in the [Introduction](Introduction.ipynb), but here specifying `mode=\"server\"`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%writefile -a simple.py\n", - "\n", - "import parambokeh\n", - "w = parambokeh.Widgets(Example, mode='server')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally we instruct bokeh to save the widgets to html:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%writefile -a simple.py\n", - "\n", - "bokeh.io.save(w)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now execute our `simple.py` script, which will create `simple.html`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!python simple.py" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can then open `simple.html` in a web browser (which we do here by including it in an iframe):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import IPython\n", - "IPython.display.HTML('')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You could instead run something like `bokeh serve --show simple.py` to get a live app rather than a static one." - ] - } - ], - "metadata": { - "language_info": { - "name": "python", - "pygments_lexer": "ipython3" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/examples/user_guide/Bokeh_Apps.ipynb b/examples/user_guide/Bokeh_Apps.ipynb new file mode 100644 index 0000000..cc48b0c --- /dev/null +++ b/examples/user_guide/Bokeh_Apps.ipynb @@ -0,0 +1,164 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We'll create a simple bokeh app that displays a log of every time a button has been pressed:\n", + "\n", + "![screenshot of simple app](simple.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "# existing parameterized class\n", + "\n", + "import param\n", + "import datetime as dt\n", + "\n", + "class Example(param.Parameterized):\n", + " \"\"\"Example Parameterized class\"\"\"\n", + " log = []\n", + " x = param.Number(default=1.0,bounds=(0,100),precedence=0,doc=\"X position\")\n", + " write_to_log = param.Action(lambda obj: obj.log.append((dt.datetime.now(),obj.x)), \n", + " doc=\"\"\"Record value of x and timestamp.\"\"\",precedence=1)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Whenever `Example.write_to_log` is called, the current time and value of `x` are stored in `Example.log`.\n", + "\n", + "We now create a properties frame, just as in the [Introduction](Introduction.ipynb), but this time specifying `mode='server'`:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "# create a properties frame for Example\n", + "\n", + "import parambokeh\n", + "w = parambokeh.Widgets(Example, mode='server')\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we write a simple bokeh app that periodically updates a display showing the value of `Example.log`:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "# display value of Example.log in bokeh app\n", + "\n", + "from bokeh.io import curdoc\n", + "from bokeh.layouts import layout\n", + "from bokeh.models import Div\n", + "\n", + "log = Div()\n", + "\n", + "def update_log():\n", + " log.text = \"
\".join([\"%s -- %s\"%(t[0].strftime('%H:%M:%S.%f'),t[1]) for t in Example.log])\n", + "\n", + "curdoc().add_periodic_callback(update_log, 200)\n", + "\n", + "layout = layout([log])\n", + "curdoc().add_root(layout)\n", + "curdoc().title = \"simple parambokeh + bokeh server example\"\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The app can be launched using `bokeh serve simple.py`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-----\n", + "\n", + "The example code used here is in [examples/app/bokeh/simple.py](../apps/bokeh/simple.py). For reference, the entire file is reproduced below:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "##### existing parameterized class\n", + "\n", + "import param\n", + "import datetime as dt\n", + "\n", + "class Example(param.Parameterized):\n", + " \"\"\"Example Parameterized class\"\"\"\n", + " log = []\n", + " x = param.Number(default=1.0,bounds=(0,100),precedence=0,doc=\"X position\")\n", + " write_to_log = param.Action(lambda obj: obj.log.append((dt.datetime.now(),obj.x)), \n", + " doc=\"\"\"Record value of x and timestamp.\"\"\",precedence=1)\n", + "\n", + "##### create a properties frame for Example\n", + "\n", + "import parambokeh\n", + "w = parambokeh.Widgets(Example, mode='server')\n", + "\n", + "\n", + "##### display value of Example.log in bokeh app\n", + "\n", + "from bokeh.io import curdoc\n", + "from bokeh.layouts import layout\n", + "from bokeh.models import Div\n", + "\n", + "log = Div()\n", + "\n", + "def update_log():\n", + " log.text = \"
\".join([\"%s -- %s\"%(t[0].strftime('%H:%M:%S.%f'),t[1]) for t in Example.log])\n", + "\n", + "curdoc().add_periodic_callback(update_log, 200)\n", + "\n", + "layout = layout([log])\n", + "curdoc().add_root(layout)\n", + "curdoc().title = \"simple parambokeh + bokeh server example\"\n", + "```" + ] + } + ], + "metadata": { + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/user_guide/Django_Apps.ipynb b/examples/user_guide/Django_Apps.ipynb new file mode 100644 index 0000000..2100e0a --- /dev/null +++ b/examples/user_guide/Django_Apps.ipynb @@ -0,0 +1,242 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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", + "\n", + "Note that currently, the first (and only) example here does not cover integration between param and django models.\n", + "\n", + "To run the example app yourself, you will first need to install django 2 (e.g. `conda install \"django=2\"`)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sliders app" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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", + "\n", + "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", + "\n", + " * `sliders/sinewave.py`: a parameterized object (representing your pre-existing code)\n", + " \n", + " * `sliders/bk_sliders.py`: the parambokeh/bokeh app (based on https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py)\n", + "\n", + " * `sliders/apps.py`: how a django app can import and use bokeh server\n", + "\n", + " * `sliders/views.py` and `templates/base.html`: getting the bokeh app into a django view\n", + "\n", + "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." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![screenshot of sliders app](sliders.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To start with, in `sliders/sinewave.py` we create a parameterized object to serve as a placeholder for your own, existing code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import param\n", + "\n", + "class SineWave(param.ParameterizedFunction):\n", + " offset = param.Number(default=0.0, bounds=(-5.0,5.0))\n", + " amplitude = param.Number(default=1.0, bounds=(-5.0,5.0))\n", + " phase = param.Number(default=0.0,bounds=(0.0,2*np.pi))\n", + " frequency = param.Number(default=1.0, bounds=(0.1, 5.1))\n", + " N = param.Integer(default=200, bounds=(0,None))\n", + " \n", + " def __call__(self,**params):\n", + " p = param.ParamOverrides(self,params)\n", + " x = np.linspace(0, 4*np.pi, p.N)\n", + " y = p.amplitude*np.sin(p.frequency*x + p.phase) + p.offset\n", + " return x,y" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When called, `SineWave` will return a tuple of two arrays, representing a sine wave:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "SineWave()[0][0:5]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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):" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "from bokeh.layouts import row\n", + "from bokeh.models import ColumnDataSource\n", + "from bokeh.plotting import figure\n", + "\n", + "import parambokeh\n", + "\n", + "from .sinewave import SineWave\n", + "\n", + "def app(doc):\n", + " x,y = SineWave()\n", + " source = ColumnDataSource(data=dict(x=x, y=y))\n", + "\n", + " import numpy as np\n", + " plot = figure(plot_height=400, plot_width=400,\n", + " tools=\"crosshair,pan,reset,save,wheel_zoom\",\n", + " x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])\n", + " plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)\n", + "\n", + " def update_sinewave(sw,**kw):\n", + " x,y = sw()\n", + " source.data = dict(x=x, y=y)\n", + " \n", + " parambokeh.Widgets(SineWave, mode='server', doc=doc, callback=update_sinewave)\n", + " doc.add_root(row(plot, width=800))\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "from django.apps import AppConfig\n", + "\n", + "from bokeh.server.server import Server\n", + "\n", + "from tornado.ioloop import IOLoop\n", + "\n", + "from . import bk_sliders\n", + "from . import bk_config\n", + "\n", + "def bk_worker():\n", + " # Note: num_procs must be 1; see e.g. flask_gunicorn_embed.py for num_procs>1\n", + " server = Server({'/bk_sliders_app': bk_sliders.app},\n", + " io_loop=IOLoop(),\n", + " address=bk_config.server['address'],\n", + " port=bk_config.server['port'],\n", + " allow_websocket_origin=[\"localhost:8000\"])\n", + " server.start()\n", + " server.io_loop.start()\n", + "\n", + "class Sliders(AppConfig):\n", + " name = 'sliders'\n", + " def ready(self):\n", + " from threading import Thread\n", + " Thread(target=bk_worker).start()\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "server = dict(\n", + " address = \"localhost\",\n", + " port = 5006\n", + ")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, in `sliders/views.py` we create a view to display the bokeh sliders app:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "from django.shortcuts import render\n", + "\n", + "from bokeh.embed import server_document\n", + "\n", + "from . import bk_config\n", + "\n", + "def sliders(request):\n", + " return render(request, 'base.html', {\n", + " \"server_script\": server_document('http://%s:%s/bk_sliders_app'%(bk_config.server['address'],\n", + " bk_config.server['port']))})\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The corresponding template is in templates/base.html:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```\n", + "{% block content %}\n", + "{{server_script|safe}}\t\n", + "{% endblock %}\n", + "```" + ] + } + ], + "metadata": { + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/user_guide/simple.png b/examples/user_guide/simple.png new file mode 100644 index 0000000..d400e84 Binary files /dev/null and b/examples/user_guide/simple.png differ diff --git a/examples/user_guide/sliders.png b/examples/user_guide/sliders.png new file mode 100644 index 0000000..231d72d Binary files /dev/null and b/examples/user_guide/sliders.png differ