{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", " \n", " \n", " \n", " \n", "
\n", " \n", " \n", " \n", " \n", "

Bokeh Tutorial

\n", "
\n", "\n", "

11. Running Bokeh Applications

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The architecture of Bokeh is such that high-level “model objects” (representing things like plots, ranges, axes, glyphs, etc.) are created in Python, and then converted to a JSON format that is consumed by the client library, BokehJS. Using the Bokeh Server, it is possible to keep the “model objects” in python and in the browser in sync with one another, creating powerful capabilities:\n", "\n", "* respond to UI and tool events generated in a browser with computations or queries using the full power of python\n", "* automatically push updates the UI (i.e. widgets or plots), in a browser\n", "* use periodic, timeout, and asychronous callbacks drive streaming updates\n", "\n", "***This capability to synchronize between python and the browser is the main purpose of the Bokeh Server.***" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bokeh.io import output_notebook, show\n", "output_notebook()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bokeh Apps in Notebooks\n", "\n", "The easiest way to embed a Bokeh application in a notebook is to make a function `modify_doc(doc)` that creates Bokeh content, and adds it to the document. This function can be passed to `show`, and the app defined by the function will be displayed inline. A short complete example is below" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bokeh.layouts import column\n", "from bokeh.models import TextInput, Button, Paragraph\n", "\n", "def modify_doc(doc):\n", " \n", " # create some widgets\n", " button = Button(label=\"Say HI\")\n", " input = TextInput(value=\"Bokeh\")\n", " output = Paragraph()\n", "\n", " # add a callback to a widget\n", " def update():\n", " output.text = \"Hello, \" + input.value\n", " button.on_click(update)\n", "\n", " # create a layout for everything\n", " layout = column(button, input, output)\n", "\n", " # add the layout to curdoc\n", " doc.add_root(layout)\n", " \n", "# In the notebook, just pass the function that defines the app to show\n", "# You may need to supply notebook_url, e.g notebook_url=\"http://localhost:8889\" \n", "show(modify_doc) " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# EXERCISE: add a Select widget to this example that offers several different greetings\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bokeh Apps with `bokeh serve`\n", "\n", "It's also possible to define Bokeh applications by creating a standard Python script. In this case, there is no need to make a function like `modify_doc`. Typically, the script should simply create all the Bokeh content, then add it to the doc with a line like\n", "```python\n", "curdoc().add_root(layout)\n", "```\n", "\n", "To try out the example below, copy the code into a file ``hello.py`` and then execute:\n", "```bash\n", "bokeh serve --show hello.py \n", "```\n", "\n", "
NOTE: The exercise below require work outside the notebook
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "# hello.py \n", "\n", "from bokeh.io import curdoc\n", "from bokeh.layouts import column\n", "from bokeh.models.widgets import TextInput, Button, Paragraph\n", "\n", "# create some widgets\n", "button = Button(label=\"Say HI\")\n", "input = TextInput(value=\"Bokeh\")\n", "output = Paragraph()\n", "\n", "# add a callback to a widget\n", "def update():\n", " output.text = \"Hello, \" + input.value\n", "button.on_click(update)\n", "\n", "# create a layout for everything\n", "layout = column(button, input, output)\n", "\n", "# add the layout to curdoc\n", "curdoc().add_root(layout)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Copy this code to a script `hello.py` and run it with the Bokeh server." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Linking Plots and Widgets\n", "\n", "Lets take a look at a more involved example that links several widgets to a plot. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from numpy.random import random\n", "\n", "from bokeh.layouts import column, row\n", "from bokeh.plotting import figure\n", "from bokeh.models import ColumnDataSource, Select, TextInput\n", "\n", "def get_data(N):\n", " return dict(x=random(size=N), y=random(size=N), r=random(size=N) * 0.03)\n", "\n", "COLORS = [\"black\", \"firebrick\", \"navy\", \"olive\", \"goldenrod\"]\n", "\n", "def modify_doc(doc):\n", " source = ColumnDataSource(data=get_data(200))\n", "\n", " p = figure(tools=\"\", toolbar_location=None)\n", " r = p.circle(x='x', y='y', radius='r', source=source,\n", " color=\"navy\", alpha=0.6, line_color=\"white\")\n", "\n", " \n", " select = Select(title=\"Color\", value=\"navy\", options=COLORS)\n", " input = TextInput(title=\"Number of points\", value=\"200\")\n", "\n", " def update_color(attrname, old, new):\n", " r.glyph.fill_color = select.value\n", " select.on_change('value', update_color)\n", "\n", " def update_points(attrname, old, new):\n", " N = int(input.value)\n", " source.data = get_data(N)\n", " input.on_change('value', update_points)\n", "\n", " layout = column(row(select, input, width=400), row(p))\n", "\n", " doc.add_root(layout)\n", "\n", "show(modify_doc)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# EXERCISE: add more widgets to change more aspects of this plot\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Streaming Data\n", "\n", "It is possible to efficiently stream new data to column data sources by using the ``stream`` method. This method accepts two argmuments:\n", "* ``new_data`` — a dictionary with the same structure as the column data source\n", "* ``rollover`` — a maximum column length on the client (earlier data is dropped) *[optional]*\n", "\n", "If no ``rollover`` is specified, data is never dropped on the client and columns grow without bound.\n", "\n", "It is often useful to use periodic callbacks in conjuction with streaming data The ``add_periodic_callback`` method of ``curdoc()`` accepts a callback function, and a time interval (in ms) to repeatedly execute the callback. \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from math import cos, sin\n", "\n", "from bokeh.models import ColumnDataSource\n", "\n", "def modify_doc(doc):\n", " p = figure(match_aspect=True)\n", " p.circle(x=0, y=0, radius=1, fill_color=None, line_width=2)\n", " \n", " # this is just to help the auto-datarange\n", " p.rect(0, 0, 2, 2, alpha=0)\n", "\n", " # this is the data source we will stream to\n", " source = ColumnDataSource(data=dict(x=[1], y=[0]))\n", " p.circle(x='x', y='y', size=12, fill_color='white', source=source)\n", "\n", " def update():\n", " x, y = source.data['x'][-1], source.data['y'][-1]\n", "\n", " # construct the new values for all columns, and pass to stream\n", " new_data = dict(x=[x*cos(0.1) - y*sin(0.1)], y=[x*sin(0.1) + y*cos(0.1)])\n", " source.stream(new_data, rollover=8)\n", "\n", " doc.add_periodic_callback(update, 150)\n", " doc.add_root(p)\n", " \n", "show(modify_doc)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "### EXERCISE: starting with the above example, create your own streaming plot\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Bokeh column data sources also support a `patch` method that can be used to efficiently update subsets of data." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Directory Format Apps and Templates\n", "\n", "Bokeh apps can also be defined with a directory format. This format affords the use of extra modules, data files, templates, theme files, and other features. The directory should contain a `main.py` which is the \"entry point\" for the app, but may contain extra parts:\n", "```\n", "myapp\n", " |\n", " +---main.py\n", " +---server_lifecycle.py\n", " +---static\n", " +---theme.yaml\n", " +---templates\n", " +---index.html\n", "```\n", "The [Directory Format](https://bokeh.pydata.org/en/latest/docs/user_guide/server.html#directory-format) section of the User's Guide has more information. \n", "\n", "See a complete sophisticated example at: https://github.com/bokeh/bokeh/tree/master/examples/app/dash\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tips and Tricks\n", "\n", " \n", "* Real Python callbacks *require* a Bokeh server application. They cannot work with `output_file`, `components` or other functions that generate standalone output. Standalone content can only use `CustomJS` callbacks. \n", "\n", "\n", "* Try to update data sources \"all at once\" whenever possible, i.e. prefer this:\n", " ```python\n", " source.data = new_data_dict # GOOD\n", " ```\n", " rather then updating individual columns sequentially:\n", " ```python\n", " # LESS GOOD\n", " source.data['foo'] = new_foo_column\n", " source.data['bar'] = new_bar_column \n", " ```\n", " If the new columns are exactly the same length as the old ones, then updating sequentially will trigger extra updates and may result in bad visual effects. \n", " If the new columns are a different length than the old ones, then updating \"all at once\" is **mandatory**.\n", " \n", "\n", "* Each time a session is started, the Bokeh server runs the script (or `modify_doc`) function, and the code that is run ***must return completely new Bokeh objects every time***. It is not possible to share Bokeh objects between sessions. As a concrete example, this is what NOT to do:\n", " ```python\n", " source = ColumnDataSource(data) # VERY BAD - global outside modify_doc\n", " \n", " def modify_doc(doc):\n", " p = figure()\n", " p.circle('x', 'y', source=source)\n", " doc.add_root(p)\n", " \n", " ```\n", " The analogous situation would occur with a script if the script imports a global Bokeh object from a separate module (due to the way Python caches imports)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Next Section" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is the last section of the main tutorial. To explore some extra topics, see the appendices:\n", "\n", "[A1 - Models and Primitives](A1%20-%20Models%20and%20Primitives.ipynb)
\n", "[A2 - Visualizing Big Data with Datashader](A2%20-%20Visualizing%20Big%20Data%20with%20Datashader.ipynb)
\n", "[A3 - High-Level Charting with Holoviews](A3%20-%20High-Level%20Charting%20with%20Holoviews.ipynb)
\n", "[A4 - Additional Resources](A4%20-%20Additional%20Resources.ipynb)\n", "\n", "To go back to the overview, click [here](00%20-%20Introduction%20and%20Setup.ipynb)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 4 }