"
]
},
{
"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
}