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

Bokeh Tutorial

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

03. Layouts, Widgets, and Interactions

" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from bokeh.io import output_notebook, show" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "
\n", " \n", " Loading BokehJS ...\n", "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "\n", "(function(global) {\n", " function now() {\n", " return new Date();\n", " }\n", "\n", " var force = \"1\";\n", "\n", " if (typeof (window._bokeh_onload_callbacks) === \"undefined\" || force !== \"\") {\n", " window._bokeh_onload_callbacks = [];\n", " window._bokeh_is_loading = undefined;\n", " }\n", "\n", "\n", " \n", " if (typeof (window._bokeh_timeout) === \"undefined\" || force !== \"\") {\n", " window._bokeh_timeout = Date.now() + 5000;\n", " window._bokeh_failed_load = false;\n", " }\n", "\n", " var NB_LOAD_WARNING = {'data': {'text/html':\n", " \"
\\n\"+\n", " \"

\\n\"+\n", " \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n", " \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n", " \"

\\n\"+\n", " \"\\n\"+\n", " \"\\n\"+\n", " \"from bokeh.resources import INLINE\\n\"+\n", " \"output_notebook(resources=INLINE)\\n\"+\n", " \"\\n\"+\n", " \"
\"}};\n", "\n", " function display_loaded() {\n", " if (window.Bokeh !== undefined) {\n", " Bokeh.$(\"#10c8fbe5-266a-447e-9e99-dc4cebd6aba6\").text(\"BokehJS successfully loaded.\");\n", " } else if (Date.now() < window._bokeh_timeout) {\n", " setTimeout(display_loaded, 100)\n", " }\n", " }\n", "\n", " function run_callbacks() {\n", " window._bokeh_onload_callbacks.forEach(function(callback) { callback() });\n", " delete window._bokeh_onload_callbacks\n", " console.info(\"Bokeh: all callbacks have finished\");\n", " }\n", "\n", " function load_libs(js_urls, callback) {\n", " window._bokeh_onload_callbacks.push(callback);\n", " if (window._bokeh_is_loading > 0) {\n", " console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", " return null;\n", " }\n", " if (js_urls == null || js_urls.length === 0) {\n", " run_callbacks();\n", " return null;\n", " }\n", " console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", " window._bokeh_is_loading = js_urls.length;\n", " for (var i = 0; i < js_urls.length; i++) {\n", " var url = js_urls[i];\n", " var s = document.createElement('script');\n", " s.src = url;\n", " s.async = false;\n", " s.onreadystatechange = s.onload = function() {\n", " window._bokeh_is_loading--;\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: all BokehJS libraries loaded\");\n", " run_callbacks()\n", " }\n", " };\n", " s.onerror = function() {\n", " console.warn(\"failed to load library \" + url);\n", " };\n", " console.log(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " }\n", " };var element = document.getElementById(\"10c8fbe5-266a-447e-9e99-dc4cebd6aba6\");\n", " if (element == null) {\n", " console.log(\"Bokeh: ERROR: autoload.js configured with elementid '10c8fbe5-266a-447e-9e99-dc4cebd6aba6' but no matching script tag was found. \")\n", " return false;\n", " }\n", "\n", " var js_urls = ['https://cdn.pydata.org/bokeh/dev/bokeh-0.12.2rc3.min.js', 'https://cdn.pydata.org/bokeh/dev/bokeh-widgets-0.12.2rc3.min.js', 'https://cdn.pydata.org/bokeh/dev/bokeh-compiler-0.12.2rc3.min.js'];\n", "\n", " var inline_js = [\n", " function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", " \n", " function(Bokeh) {\n", " \n", " Bokeh.$(\"#10c8fbe5-266a-447e-9e99-dc4cebd6aba6\").text(\"BokehJS is loading...\");\n", " },\n", " function(Bokeh) {\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/dev/bokeh-0.12.2rc3.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/dev/bokeh-0.12.2rc3.min.css\");\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/dev/bokeh-widgets-0.12.2rc3.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/dev/bokeh-widgets-0.12.2rc3.min.css\");\n", " }\n", " ];\n", "\n", " function run_inline_js() {\n", " \n", " if ((window.Bokeh !== undefined) || (force === \"1\")) {\n", " for (var i = 0; i < inline_js.length; i++) {\n", " inline_js[i](window.Bokeh);\n", " }if (force === \"1\") {\n", " display_loaded();\n", " }} else if (Date.now() < window._bokeh_timeout) {\n", " setTimeout(run_inline_js, 100);\n", " } else if (!window._bokeh_failed_load) {\n", " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", " window._bokeh_failed_load = true;\n", " } else if (!force) {\n", " var cell = $(\"#10c8fbe5-266a-447e-9e99-dc4cebd6aba6\").parents('.cell').data().cell;\n", " cell.output_area.append_execute_result(NB_LOAD_WARNING)\n", " }\n", "\n", " }\n", "\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", " run_inline_js();\n", " } else {\n", " load_libs(js_urls, function() {\n", " console.log(\"Bokeh: BokehJS plotting callback run at\", now());\n", " run_inline_js();\n", " });\n", " }\n", "}(this));" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "output_notebook()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Simple Layouts\n", "\n", "In order to add widgets or have multiple plots that are linked together, you must first be able to create documents that contain these separate objects. It is possible to accomplish this in your own custom templates using ``bokeh.embed.components``. But, Bokeh also provides simple layout capability for rows, columns, and grids. \n", "\n", "An example using ``gridplot`` is shown below:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.layouts import gridplot\n", "from bokeh.plotting import figure\n", "\n", "x = list(range(11))\n", "y0, y1, y2 = x, [10-i for i in x], [abs(i-5) for i in x]\n", "\n", "# create a new plot\n", "s1 = figure(width=250, plot_height=250)\n", "s1.circle(x, y0, size=10, color=\"navy\", alpha=0.5)\n", "\n", "# create another one\n", "s2 = figure(width=250, height=250)\n", "s2.triangle(x, y1, size=10, color=\"firebrick\", alpha=0.5)\n", "\n", "# create and another\n", "s3 = figure(width=250, height=250)\n", "s3.square(x, y2, size=10, color=\"olive\", alpha=0.5)\n", "\n", "# put all the plots in a gridplot\n", "p = gridplot([[s1, s2, s3]], toolbar_location=None)\n", "\n", "# show the results\n", "show(p)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# EXERCISE: create a gridplot of your own\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `bokeh.layouts` modules also provides the ``row`` and ``column`` functions to arrange plot objects in vertical or horizontal layouts. " ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# EXERCISE: use column to arrange a few plots vertically\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# Linked Interactions\n", "\n", "It is possible to link various interactions between different Bokeh plots. For instance, the ranges of two (or more) plots can be linked, so that when one of the plots is panned (or zoomed, or otherwise has its range changed) the other plots will update in unison. It is also possible to link selections between two plots, so that when items are selected on one plot, the corresponding items on the second plot also become selected. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Linked panning\n", "\n", "Linked panning (when multiple plots have ranges that stay in sync) is simple to spell with Bokeh. You simply share the appropriate range objects between two (or more) plots. The example below shows how to accomplish this by linking the ranges of three plots in various ways:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_options = dict(width=250, plot_height=250, tools='pan,wheel_zoom')\n", "\n", "# create a new plot\n", "s1 = figure(**plot_options)\n", "s1.circle(x, y0, size=10, color=\"navy\")\n", "\n", "# create a new plot and share both ranges\n", "s2 = figure(x_range=s1.x_range, y_range=s1.y_range, **plot_options)\n", "s2.triangle(x, y1, size=10, color=\"firebrick\")\n", "\n", "# create a new plot and share only one range\n", "s3 = figure(x_range=s1.x_range, **plot_options)\n", "s3.square(x, y2, size=10, color=\"olive\")\n", "\n", "p = gridplot([[s1, s2, s3]])\n", "\n", "# show the results\n", "show(p)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# EXERCISE: create two plots in a gridplot, and link their ranges\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Linked brushing\n", "\n", "Linking selections is accomplished in a similar way, by sharing data sources between plots. Note that normally with ``bokeh.plotting`` and ``bokeh.charts`` creating a default data source for simple plots is handled automatically. However to share a data source, we must create them by hand and pass them explicitly. This is illustrated in the example below:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.models import ColumnDataSource\n", "\n", "x = list(range(-20, 21))\n", "y0, y1 = [abs(xx) for xx in x], [xx**2 for xx in x]\n", "\n", "# create a column data source for the plots to share\n", "source = ColumnDataSource(data=dict(x=x, y0=y0, y1=y1))\n", "\n", "TOOLS = \"box_select,lasso_select,help\"\n", "\n", "# create a new plot and add a renderer\n", "left = figure(tools=TOOLS, width=300, height=300)\n", "left.circle('x', 'y0', source=source)\n", "\n", "# create another new plot and add a renderer\n", "right = figure(tools=TOOLS, width=300, height=300)\n", "right.circle('x', 'y1', source=source)\n", "\n", "p = gridplot([[left, right]])\n", "\n", "show(p)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# EXERCISE: create two plots in a gridplot, and link their data sources\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Hover Tools\n", "\n", "Bokeh has a Hover Tool that allows additional information to be displayed in a popup whenever the uer howevers over a specific glyph. Basic hover tool configuration amounts to providing a list of ``(name, format)`` tuples. The full details can be found in the User's Guide [here](http://bokeh.pydata.org/en/latest/docs/user_guide/tools.html#hover-tool).\n", "\n", "The example below shows some basic usage of the Hover tool with a circle glyph, using hover information defined in utils.py:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.models import HoverTool\n", "\n", "source = ColumnDataSource(\n", " data=dict(\n", " x=[1, 2, 3, 4, 5],\n", " y=[2, 5, 8, 2, 7],\n", " desc=['A', 'b', 'C', 'd', 'E'],\n", " )\n", " )\n", "\n", "hover = HoverTool(\n", " tooltips=[\n", " (\"index\", \"$index\"),\n", " (\"(x,y)\", \"($x, $y)\"),\n", " (\"desc\", \"@desc\"),\n", " ]\n", " )\n", "\n", "p = figure(plot_width=300, plot_height=300, tools=[hover], title=\"Mouse over the dots\")\n", "\n", "p.circle('x', 'y', size=20, source=source)\n", "\n", "# Also show custom hover \n", "from utils import get_custom_hover\n", "\n", "show(gridplot([[p, get_custom_hover()]]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Jupyter Interactors\n", "\n", "*** NOTE: The Jupyter widgets below will only work in a live running Jupyter notebook, not is a static saved notebook ***\n", "\n", "It is possible to use native Jupyter notebook interactors together with Bokeh. In the interactor update function, the ``push_notebook`` function can be used to update a data source or other plot attributes, which will cause the displayed plot to update accordingly. \n", "\n", "The example below shows a \"trig function\" exporer using IPython interactors:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "

<Bokeh Notebook handle for In[11]>

" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "from bokeh.io import push_notebook\n", "\n", "x = np.linspace(0, 2*np.pi, 2000)\n", "y = np.sin(x)\n", "\n", "source = ColumnDataSource(data=dict(x=x, y=y))\n", "\n", "p = figure(title=\"simple line example\", plot_height=300, plot_width=600, y_range=(-5, 5))\n", "p.line(x, y, color=\"#2222aa\", alpha=0.5, line_width=2, source=source, name=\"foo\")\n", "\n", "def update(f, w=1, A=1, phi=0):\n", " if f == \"sin\": func = np.sin\n", " elif f == \"cos\": func = np.cos\n", " elif f == \"tan\": func = np.tan\n", " source.data['y'] = A * func(w * x + phi)\n", " push_notebook()\n", " \n", "show(p, notebook_handle=True)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from ipywidgets import interact\n", "interact(update, f=[\"sin\", \"cos\", \"tan\"], w=(0,10, 0.1), A=(0,5, 0.1), phi=(0, 10, 0.1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Widgets" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Bokeh supports direct integration with a small basic widget set. Thse can be used in conjunction with a Bokeh Server, or with ``CustomJS`` models to add more interactive capability to your documents. You can see a complete list, with example code in the [Adding Widgets](http://bokeh.pydata.org/en/latest/docs/user_guide/interaction.html#adding-widgets) section of the User's Guide. \n", "\n", "To use the widgets, include them in a layout like you would a plot object:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.layouts import widgetbox\n", "from bokeh.models.widgets import Slider\n", "\n", "\n", "slider = Slider(start=0, end=10, value=1, step=.1, title=\"foo\")\n", "\n", "show(widgetbox(slider))" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# EXERCISE: create and show a Select widget \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# CustomJS Callbacks" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.models import TapTool, CustomJS, ColumnDataSource\n", "\n", "callback = CustomJS(code=\"alert('hello world')\")\n", "tap = TapTool(callback=callback)\n", "\n", "p = figure(plot_width=600, plot_height=300, tools=[tap])\n", "\n", "p.circle('x', 'y', size=20, source=ColumnDataSource(data=dict(x=[1, 2, 3, 4, 5], y=[2, 5, 8, 2, 7])))\n", "\n", "show(p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Lots of places to add callbacks\n", "\n", "* Widgets - Button, Toggle, Dropdown, TextInput, AutocompleteInput, Select, Multiselect, Slider, (DateRangeSlider), DatePicker,\n", "* Tools - TapTool, BoxSelectTool, HoverTool,\n", "* Selection - ColumnDataSource, AjaxDataSource, BlazeDataSource, ServerDataSource\n", "* Ranges - Range1d, DataRange1d, FactorRange\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Callbacks for widgets\n", "\n", "Widgets that have values associated can have small JavaScript actions attached to them. These actions (also referred to as \"callbacks\") are executed whenever the widget's value is changed. In order to make it easier to refer to specific Bokeh models (e.g., a data source, or a glyhph) from JavaScript, the ``CustomJS`` obejct also accepts a dictionary of \"args\" that map names to Python Bokeh models. The corresponding JavaScript models are made available automaticaly to the ``CustomJS`` code. \n", "\n", "And example below shows an action attached to a slider that updates a data source whenever the slider is moved:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.layouts import column\n", "from bokeh.models import CustomJS, ColumnDataSource, Slider\n", "\n", "x = [x*0.005 for x in range(0, 200)]\n", "y = x\n", "\n", "source = ColumnDataSource(data=dict(x=x, y=y))\n", "\n", "plot = figure(plot_width=400, plot_height=400)\n", "plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)\n", "\n", "callback = CustomJS(args=dict(source=source), code=\"\"\"\n", " var data = source.get('data');\n", " var f = cb_obj.get('value')\n", " x = data['x']\n", " y = data['y']\n", " for (i = 0; i < x.length; i++) {\n", " y[i] = Math.pow(x[i], f)\n", " }\n", " source.trigger('change');\n", "\"\"\")\n", "\n", "slider = Slider(start=0.1, end=4, value=1, step=.1, title=\"power\", callback=callback)\n", "\n", "layout = column(slider, plot)\n", "\n", "show(layout)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Calbacks for selections\n", "\n", "It's also possible to make JavaScript actions that execute whenever a user selection (e.g., box, point, lasso) changes. This is done by attaching the same kind of CustomJS object to whatever data source the selection is made on.\n", "\n", "The example below is a bit more sophisticated, and demonstrates updating one glyph's data source in response to another glyph's selection: " ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from random import random\n", "\n", "x = [random() for x in range(500)]\n", "y = [random() for y in range(500)]\n", "color = [\"navy\"] * len(x)\n", "\n", "s = ColumnDataSource(data=dict(x=x, y=y, color=color))\n", "p = figure(plot_width=400, plot_height=400, tools=\"lasso_select\", title=\"Select Here\")\n", "p.circle('x', 'y', color='color', size=8, source=s, alpha=0.4)\n", "\n", "s2 = ColumnDataSource(data=dict(ym=[0.5, 0.5]))\n", "p.line(x=[0,1], y='ym', color=\"orange\", line_width=5, alpha=0.6, source=s2)\n", "\n", "s.callback = CustomJS(args=dict(s2=s2), code=\"\"\"\n", " var inds = cb_obj.get('selected')['1d'].indices;\n", " var d = cb_obj.get('data');\n", " var ym = 0\n", " \n", " if (inds.length == 0) { return; }\n", " \n", " for (i = 0; i < d['color'].length; i++) {\n", " d['color'][i] = \"navy\"\n", " }\n", " for (i = 0; i < inds.length; i++) {\n", " d['color'][inds[i]] = \"firebrick\"\n", " ym += d['y'][inds[i]]\n", " }\n", " \n", " ym /= inds.length\n", " s2.get('data')['ym'] = [ym, ym]\n", " \n", " cb_obj.trigger('change');\n", " s2.trigger('change');\n", "\"\"\")\n", "\n", "show(p)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# More\n", "For more interactions, see the User Guide - http://bokeh.pydata.org/en/latest/docs/user_guide/interaction.html" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python [default]", "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.5.2" }, "widgets": { "state": { "335ae2c8ff80469e9adac668c2924989": { "views": [ { "cell_index": 19 } ] } }, "version": "1.2.0" } }, "nbformat": 4, "nbformat_minor": 0 }