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

Bokeh Tutorial

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

08. Annotations

" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from bokeh.io import output_notebook, show\n", "from bokeh.plotting import figure" ] }, { "cell_type": "code", "execution_count": 15, "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.$(\"#aa7b5c91-dc82-414e-b98f-99601cea45d4\").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(\"aa7b5c91-dc82-414e-b98f-99601cea45d4\");\n", " if (element == null) {\n", " console.log(\"Bokeh: ERROR: autoload.js configured with elementid 'aa7b5c91-dc82-414e-b98f-99601cea45d4' but no matching script tag was found. \")\n", " return false;\n", " }\n", "\n", " var js_urls = ['https://cdn.pydata.org/bokeh/release/bokeh-0.12.2.min.js', 'https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.2.min.js', 'https://cdn.pydata.org/bokeh/release/bokeh-compiler-0.12.2.min.js'];\n", "\n", " var inline_js = [\n", " function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", " \n", " function(Bokeh) {\n", " \n", " Bokeh.$(\"#aa7b5c91-dc82-414e-b98f-99601cea45d4\").text(\"BokehJS is loading...\");\n", " },\n", " function(Bokeh) {\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-0.12.2.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-0.12.2.min.css\");\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.2.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.2.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 = $(\"#aa7b5c91-dc82-414e-b98f-99601cea45d4\").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": [ "# Overview\n", "\n", "Sometimes we want to add visual cues (boundary lines, shaded regions, labels and arrows, etc.) to our plots to call out some feature or other. Bokeh has several annotation types available for uses like this. Typically to add annotations we create the \"low level\" annotation object directly, and add it to our `Plot`, `Figure` or `Chart` using `add_layout`. Let's take a look at some specific examples." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Spans\n", "\n", "`Spans` are \"infinite\" vertical or horizonal lines. When creating them, you specify the `dimension` that should be spanned (i.e., `width` or `height`), any visual line properties for the appearance, and the location along the dimension where the line should be drawn. Let's look at an example that adds two horizontal spans to a simple plot:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import numpy as np\n", "from bokeh.models.annotations import Span\n", "\n", "x = np.linspace(0, 20, 200)\n", "y = np.sin(x)\n", "\n", "p = figure(y_range=(-2, 2))\n", "p.line(x, y)\n", "\n", "upper = Span(location=1, dimension='width', line_color='olive', line_width=4)\n", "p.add_layout(upper)\n", "\n", "lower = Span(location=-1, dimension='width', line_color='firebrick', line_width=4)\n", "p.add_layout(lower)\n", "\n", "show(p)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Box Annotations\n", "\n", "Sometimes you might want to call out some region of the plot by drawing a shaded box. This can be done with the `BoxAnnotation`, which is configured with the coordinate properties:\n", "* `top`\n", "* `left`\n", "* `bottom`\n", "* `right`\n", "\n", "as well as any visual line or fill properties to control the appearance. \n", "\n", "\"Infinite\" boxes can be made by leaving any of the coordinates unspecified. E.g., if `top` is not given, the box will always extend to the top of the plot area, regardless of any panning or zooming that happens.\n", "\n", "Let's take a look at an example that adds a few shaded boxes to a plot:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import numpy as np\n", "from bokeh.models.annotations import BoxAnnotation\n", "\n", "x = np.linspace(0, 20, 200)\n", "y = np.sin(x)\n", "\n", "p = figure(y_range=(-2, 2))\n", "p.line(x, y)\n", "\n", "# region that always fills the top of the plot\n", "upper = BoxAnnotation(bottom=1, fill_alpha=0.1, fill_color='olive')\n", "p.add_layout(upper)\n", "\n", "# region that always fills the bottom of the plot\n", "lower = BoxAnnotation(top=-1, fill_alpha=0.1, fill_color='firebrick')\n", "p.add_layout(lower)\n", "\n", "# a finite region\n", "center = BoxAnnotation(top=0.6, bottom=-0.3, left=7, right=12, fill_alpha=0.1, fill_color='navy')\n", "p.add_layout(center)\n", "\n", "show(p)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Label\n", "\n", "The `Label` annoation allows you to easily attach single text labels to plots. The position and text to display are configured as `x`, `y`, and `text`:\n", "\n", "```python\n", "Label(x=10, y=5, text=\"Some Label\")\n", "```\n", "\n", "By default the units are in \"data space\" but `x_units` and `y_units` maybe set to `\"screen\"` to position the label relative to the canvas. Labels can also accept `x_offset` and `y_offset` to offset the final position from `x` and `y` by a given screen space distance. \n", "\n", "`Label` objects also have standard text, line (`border_line`) and fill (`background_fill`) properties. The line and fill properties apply to a bounding box around the text:\n", "\n", "```python\n", "Label(x=10, y=5, text=\"Some Label\", text_font_size=\"12pt\", \n", " border_line_color=\"red\", background_fill_color=\"blue\")\n", "```" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.models.annotations import Label\n", "from bokeh.plotting import figure\n", "\n", "p = figure(x_range=(0,10), y_range=(0,10))\n", "p.circle([2, 5, 8], [4, 7, 6], color=\"olive\", size=10)\n", "\n", "label = Label(x=5, y=7, x_offset=12, text=\"Second Point\", text_baseline=\"middle\")\n", "p.add_layout(label)\n", "\n", "show(p)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# EXERCISE: experiment with Label\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## LabelSet\n", "\n", "The `LabelSet` annotation allows you to create many labels at once, for instance if you want to label an entire set of scatter markers. They are similar to `Label`, but they can also \n", "accept a `ColumnDataSource` as the `source` property, and then `x` and `y` may refer to columns in the data source, e.g. `x=\"col2\"` (but may also still be fixed values, e.g. `x=10`)." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.plotting import figure\n", "from bokeh.models import ColumnDataSource, LabelSet\n", "\n", "\n", "source = ColumnDataSource(data=dict(\n", " temp=[166, 171, 172, 168, 174, 162],\n", " pressure=[165, 189, 220, 141, 260, 174],\n", " names=['A', 'B', 'C', 'D', 'E', 'F']))\n", "\n", "p = figure(x_range=(160, 175))\n", "p.scatter(x='temp', y='pressure', size=8, source=source)\n", "p.xaxis.axis_label = 'Temperature (C)'\n", "p.yaxis.axis_label = 'Pressure (lbs)'\n", "\n", "labels = LabelSet(x='temp', y='pressure', text='names', level='glyph',\n", " x_offset=5, y_offset=5, source=source, render_mode='canvas')\n", "\n", "\n", "p.add_layout(labels)\n", "\n", "show(p)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# EXERCISE: experiment with LabelSet\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Arrows\n", "\n", "The `Arrow` annotation allows you to \"point\" at different things on your plot, and can be especially useful in conjuction with labels. \n", "\n", "For example, to create an arrow that points from `(0,0)` to `(1,1)`:\n", "\n", "```python\n", "p.add_layout(Arrow(x_start=0, y_start=0, x_end=1, y_end=0))\n", "```\n", "\n", "This arrow will have the default [`OpenHead`](http://bokeh.pydata.org/en/latest/docs/reference/models/arrow_heads.html#bokeh.models.arrow_heads.OpenHead) arrow head at the end of the arrow. Other kinds of arrow heads include [`NormalHead`](http://bokeh.pydata.org/en/latest/docs/reference/models/arrow_heads.html#bokeh.models.arrow_heads.NormalHead) and [`VeeHead`](http://bokeh.pydata.org/en/latest/docs/reference/models/arrow_heads.html#bokeh.models.arrow_heads.VeeHead). The arrow head type can be controlled by setting the `start` and `end` properties of `Arrow` objects:\n", "\n", "```python\n", "p.add_layout(Arrow(start=OpenHead(), end=VeeHead(), \n", " x_start=0, y_start=0, x_end=1, y_end=0))\n", "```\n", "\n", "This will create a double-ended arrow with an \"open\" head at the start, and a \"vee\" head at the end. Arrowheads have the standard set of line and fill properties to control their appearance. As an example\n", "\n", "```python\n", "OpenHead(line_color=\"firebrick\", line_width=4)\n", "```\n", "\n", "The code and plot below shows several of these configurations together." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.models.annotations import Arrow\n", "from bokeh.models.arrow_heads import OpenHead, NormalHead, VeeHead\n", "\n", "p = figure(plot_width=600, plot_height=600)\n", "\n", "p.circle(x=[0, 1, 0.5], y=[0, 0, 0.7], radius=0.1,\n", " color=[\"navy\", \"yellow\", \"red\"], fill_alpha=0.1)\n", "\n", "p.add_layout(Arrow(end=OpenHead(line_color=\"firebrick\", line_width=4),\n", " x_start=0, y_start=0, x_end=1, y_end=0))\n", "\n", "p.add_layout(Arrow(end=NormalHead(fill_color=\"orange\"),\n", " x_start=1, y_start=0, x_end=0.5, y_end=0.7))\n", "\n", "p.add_layout(Arrow(end=VeeHead(size=35), line_color=\"red\",\n", " x_start=0.5, y_start=0.7, x_end=0, y_end=0))\n", "\n", "show(p)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# EXERCISE: experiment with arrows and arrow heads\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Legends" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import numpy as np\n", "\n", "x = np.linspace(0, 4*np.pi, 100)\n", "y = np.sin(x)\n", "\n", "p = figure(height=400)\n", "\n", "p.circle(x, y, legend=\"sin(x)\")\n", "p.line(x, 2*y, legend=\"2*sin(x)\", line_dash=[4, 4], line_color=\"orange\", line_width=2)\n", "\n", "show(p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Compound legends\n", "\n", "You can make compound legends by specifying the same legend.\n", "\n", "Exercise: (1) Try making a compound legend (2) Try moving the legend using `p.legend.location`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Color bars" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.sampledata.autompg import autompg\n", "from bokeh.models import LinearColorMapper, ColorBar\n", "from bokeh.palettes import Viridis256\n", "source = ColumnDataSource(autompg)\n", "color_mapper = LinearColorMapper(palette=Viridis256, low=autompg.weight.min(), high=autompg.weight.max())\n", "\n", "p = figure(x_axis_label='Year', y_axis_label='MPG', tools='', toolbar_location='above')\n", "p.circle(x='yr', y='mpg', color={'field': 'weight', 'transform': color_mapper}, size=20, alpha=0.6, source=source)\n", "color_bar = ColorBar(color_mapper=color_mapper, label_standoff=12, location=(0,0), title='Weight')\n", "p.add_layout(color_bar, 'right')\n", "\n", "show(p)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "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" } }, "nbformat": 4, "nbformat_minor": 0 }