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

Bokeh Tutorial

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

09. Models and Primitives

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Overview\n", "\n", "Bokeh is actually composed of two library components.\n", "\n", "The first component is a JavaScript library, BokehJS, that runs in the browser. This library is responsible for all of the rendering and user interaction. Its input is a collection of declarative JSON objects that comprise a “scenegraph”. The objects in this scenegraph describe everything that BokehJS should handle: what plots and widgets are present and in what arrangement, what tools and renderers and axes the plots will have, etc. These JSON objects are converted into Backbone objects in the browser.\n", "\n", "The second component is a library in Python (or other languages) that can generate the JSON described above. In the Python Bokeh library, this is accomplished at the lowest level by exposing a set of “model” classes that exactly mirror the set of Backbone Models that are created in the browser. Most of the models are very simple, usually consisting of a few property attributes and no methods. Model attributes can either be configured when the model is created, or later by setting attribute values on the model object:\n", "\n", "#### properties can be configured when a model object is initialized\n", "```python\n", "glyph = Rect(x=\"x\", y=\"y2\", w=10, h=20, line_color=None)\n", "```\n", "\n", "#### or by assigning values to attributes on the model later\n", "```python\n", "glyph.fill_alpha = 0.5\n", "glyph.fill_color = \"navy\"\n", "```\n", "\n", "These methods of configuration work in general for all Bokeh models. Because of that, and because all Bokeh interfaces ultimately produce collections of Bokeh models, styling and configuring plots and widgets is accomplished in basically the same way, regardless of which interface is used.\n", "\n", "Using the bokeh.models interface provides complete control over how Bokeh plots and Bokeh widgets are put together and configured. However, it provides no help with assembling the models in meaningful or correct ways. It is entirely up to developers to build the scenegraph “by hand”. \n", "\n", "For more information about the details of all Bokeh models, consult the [Reference Guide](http://bokeh.pydata.org/en/latest/docs/reference.html)." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# Walkthrough\n", "\n", "Let's try to reproduce this NYTimes interactive chart [Usain Bolt vs. 116 years of Olympic sprinters](http://www.nytimes.com/interactive/2012/08/05/sports/olympics/the-100-meter-dash-one-race-every-medalist-ever.html) using the `bokeh.models` interface." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first thing we need is to get the data. The data for this chart is located in the ``bokeh.sampledata`` module as a Pandas DataFrame. You can see the first ten rows below:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NameCountryMedalTimeYear
0Usain BoltJAMGOLD9.632012
1Yohan BlakeJAMSILVER9.752012
2Justin GatlinUSABRONZE9.792012
3Usain BoltJAMGOLD9.692008
4Richard ThompsonTRISILVER9.892008
5Walter DixUSABRONZE9.912008
6Justin GatlinUSAGOLD9.852004
7Francis ObikweluPORSILVER9.862004
8Maurice GreeneUSABRONZE9.872004
9Maurice GreeneUSAGOLD9.872000
\n", "
" ], "text/plain": [ " Name Country Medal Time Year\n", "0 Usain Bolt JAM GOLD 9.63 2012\n", "1 Yohan Blake JAM SILVER 9.75 2012\n", "2 Justin Gatlin USA BRONZE 9.79 2012\n", "3 Usain Bolt JAM GOLD 9.69 2008\n", "4 Richard Thompson TRI SILVER 9.89 2008\n", "5 Walter Dix USA BRONZE 9.91 2008\n", "6 Justin Gatlin USA GOLD 9.85 2004\n", "7 Francis Obikwelu POR SILVER 9.86 2004\n", "8 Maurice Greene USA BRONZE 9.87 2004\n", "9 Maurice Greene USA GOLD 9.87 2000" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from bokeh.sampledata.sprint import sprint\n", "sprint[:10]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we import some of the Bokeh models that need to be assembled to make a plot. At a minimum, we need to start with ``Plot``, the glyphs (``Circle`` and ``Text``) we want to display, as well as ``ColumnDataSource`` to hold the data and range obejcts to set the plot bounds. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from bokeh.io import output_notebook, show\n", "from bokeh.models.glyphs import Circle, Text\n", "from bokeh.models import ColumnDataSource, Range1d, DataRange1d, Plot" ] }, { "cell_type": "code", "execution_count": 3, "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.$(\"#4363f298-98e9-4ede-9157-4ddb5bf19bcf\").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(\"4363f298-98e9-4ede-9157-4ddb5bf19bcf\");\n", " if (element == null) {\n", " console.log(\"Bokeh: ERROR: autoload.js configured with elementid '4363f298-98e9-4ede-9157-4ddb5bf19bcf' 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.$(\"#4363f298-98e9-4ede-9157-4ddb5bf19bcf\").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 = $(\"#4363f298-98e9-4ede-9157-4ddb5bf19bcf\").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": [ "## Setting up Data\n", "\n", "Next we need set up all the columns we want in our column data source. Here we add a few extra columns like `MetersBack` and `SelectedName` that we will use for a `HoverTool` later." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [], "source": [ "abbrev_to_country = {\n", " \"USA\": \"United States\",\n", " \"GBR\": \"Britain\",\n", " \"JAM\": \"Jamaica\",\n", " \"CAN\": \"Canada\",\n", " \"TRI\": \"Trinidad and Tobago\",\n", " \"AUS\": \"Australia\",\n", " \"GER\": \"Germany\",\n", " \"CUB\": \"Cuba\",\n", " \"NAM\": \"Namibia\",\n", " \"URS\": \"Soviet Union\",\n", " \"BAR\": \"Barbados\",\n", " \"BUL\": \"Bulgaria\",\n", " \"HUN\": \"Hungary\",\n", " \"NED\": \"Netherlands\",\n", " \"NZL\": \"New Zealand\",\n", " \"PAN\": \"Panama\",\n", " \"POR\": \"Portugal\",\n", " \"RSA\": \"South Africa\",\n", " \"EUA\": \"United Team of Germany\",\n", "}\n", "\n", "gold_fill = \"#efcf6d\"\n", "gold_line = \"#c8a850\"\n", "silver_fill = \"#cccccc\"\n", "silver_line = \"#b0b0b1\"\n", "bronze_fill = \"#c59e8a\"\n", "bronze_line = \"#98715d\"\n", "\n", "fill_color = { \"gold\": gold_fill, \"silver\": silver_fill, \"bronze\": bronze_fill }\n", "line_color = { \"gold\": gold_line, \"silver\": silver_line, \"bronze\": bronze_line }\n", "\n", "def selected_name(name, medal, year):\n", " return name if medal == \"gold\" and year in [1988, 1968, 1936, 1896] else None\n", "\n", "t0 = sprint.Time[0]\n", "\n", "sprint[\"Abbrev\"] = sprint.Country\n", "sprint[\"Country\"] = sprint.Abbrev.map(lambda abbr: abbrev_to_country[abbr])\n", "sprint[\"Medal\"] = sprint.Medal.map(lambda medal: medal.lower())\n", "sprint[\"Speed\"] = 100.0/sprint.Time\n", "sprint[\"MetersBack\"] = 100.0*(1.0 - t0/sprint.Time)\n", "sprint[\"MedalFill\"] = sprint.Medal.map(lambda medal: fill_color[medal])\n", "sprint[\"MedalLine\"] = sprint.Medal.map(lambda medal: line_color[medal])\n", "sprint[\"SelectedName\"] = sprint[[\"Name\", \"Medal\", \"Year\"]].apply(tuple, axis=1).map(lambda args: selected_name(*args))\n", "\n", "source = ColumnDataSource(sprint)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Building in stages\n", "\n", "Let's build up our plot in stages, stopping to check the output along the way to see how things look.\n", "\n", "As we go through, note the three methods that `Plot`, `Chart`, and `Figure` all have:\n", "\n", "* `p.add_glyph`\n", "* `p.add_tools`\n", "* `p.add_layout`\n", "\n", "These are actually small convenience methods that help us add models to `Plot` objects in the correct way." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Basic Plot with Just Glyphs\n", "\n", "First we create just the `Plot` with a title and some basic styling applied, as well add a few `Circle` glyphs for the actual race data. To manually configure glyphs, we first create a glyph object (e.g., `Text` or `Circle`) that is configured with the visual properties we want as well as the data columns to use for coordinates, etc. Then we call `plot.add_glyph` with the glyph, and the data source that the glyph should use. " ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [], "source": [ "plot_options = dict(plot_width=800, plot_height=480, toolbar_location=None, outline_line_color=None)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [], "source": [ "radius = dict(value=5, units=\"screen\")\n", "medal_glyph = Circle(x=\"MetersBack\", y=\"Year\", radius=radius, fill_color=\"MedalFill\", \n", " line_color=\"MedalLine\", fill_alpha=0.5)\n", "\n", "athlete_glyph = Text(x=\"MetersBack\", y=\"Year\", x_offset=10, text=\"SelectedName\",\n", " text_align=\"left\", text_baseline=\"middle\", text_font_size=\"9pt\")\n", "\n", "no_olympics_glyph = Text(x=7.5, y=1942, text=[\"No Olympics in 1940 or 1944\"],\n", " text_align=\"center\", text_baseline=\"middle\",\n", " text_font_size=\"9pt\", text_font_style=\"italic\", text_color=\"silver\")\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xdr = Range1d(start=sprint.MetersBack.max()+2, end=0) # +2 is for padding\n", "ydr = DataRange1d(range_padding=0.05) \n", "\n", "plot = Plot(x_range=xdr, y_range=ydr, **plot_options)\n", "plot.title.text = \"Usain Bolt vs. 116 years of Olympic sprinters\"\n", "plot.add_glyph(source, medal_glyph)\n", "plot.add_glyph(source, athlete_glyph)\n", "plot.add_glyph(no_olympics_glyph)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show(plot)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Adding Axes and Grids\n", "\n", "Next we add in models for the `Axis` and `Grids` that we would like to see. Since we want to exert more control over the appearance, we can choose specific tickers for the axes models to use (`SingleIntervalTicker` in this case). We add these guides to the plot using the `plot.add_layout` method. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from bokeh.models import Grid, LinearAxis, SingleIntervalTicker" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xdr = Range1d(start=sprint.MetersBack.max()+2, end=0) # +2 is for padding\n", "ydr = DataRange1d(range_padding=0.05) \n", "\n", "plot = Plot(x_range=xdr, y_range=ydr, **plot_options)\n", "plot.title.text = \"Usain Bolt vs. 116 years of Olympic sprinters\"\n", "plot.add_glyph(source, medal_glyph)\n", "plot.add_glyph(source, athlete_glyph)\n", "plot.add_glyph(no_olympics_glyph)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": true }, "outputs": [], "source": [ "xticker = SingleIntervalTicker(interval=5, num_minor_ticks=0)\n", "xaxis = LinearAxis(ticker=xticker, axis_line_color=None, major_tick_line_color=None,\n", " axis_label=\"Meters behind 2012 Bolt\", axis_label_text_font_size=\"10pt\", \n", " axis_label_text_font_style=\"bold\")\n", "plot.add_layout(xaxis, \"below\")\n", "\n", "xgrid = Grid(dimension=0, ticker=xaxis.ticker, grid_line_dash=\"dashed\")\n", "plot.add_layout(xgrid)\n", "\n", "yticker = SingleIntervalTicker(interval=12, num_minor_ticks=0)\n", "yaxis = LinearAxis(ticker=yticker, major_tick_in=-5, major_tick_out=10)\n", "plot.add_layout(yaxis, \"right\")" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show(plot)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Adding a Hover Tool\n", "\n", "Finally we add a hover tool to display those extra columns that we put into our column data source. We use the template syntax for the tooltips, to have more control over the appearance. Tools can be added using the `plot.add_tools` method." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from bokeh.models import HoverTool" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [], "source": [ "tooltips = \"\"\"\n", "
\n", " @Name \n", " (@Abbrev)\n", "
\n", "
\n", " @Time{0.00} \n", " @Year\n", "
\n", "
@{MetersBack}{0.00} meters behind
\n", "\"\"\"" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [], "source": [ "xdr = Range1d(start=sprint.MetersBack.max()+2, end=0) # +2 is for padding\n", "ydr = DataRange1d(range_padding=0.05) \n", "\n", "plot = Plot(x_range=xdr, y_range=ydr, **plot_options)\n", "plot.title.text = \"Usain Bolt vs. 116 years of Olympic sprinters\"\n", "medal = plot.add_glyph(source, medal_glyph) # we need this renderer to configure the hover tool\n", "plot.add_glyph(source, athlete_glyph)\n", "plot.add_glyph(no_olympics_glyph)\n", "\n", "xticker = SingleIntervalTicker(interval=5, num_minor_ticks=0)\n", "xaxis = LinearAxis(ticker=xticker, axis_line_color=None, major_tick_line_color=None,\n", " axis_label=\"Meters behind 2012 Bolt\", axis_label_text_font_size=\"10pt\", \n", " axis_label_text_font_style=\"bold\")\n", "plot.add_layout(xaxis, \"below\")\n", "\n", "xgrid = Grid(dimension=0, ticker=xaxis.ticker, grid_line_dash=\"dashed\")\n", "plot.add_layout(xgrid)\n", "\n", "yticker = SingleIntervalTicker(interval=12, num_minor_ticks=0)\n", "yaxis = LinearAxis(ticker=yticker, major_tick_in=-5, major_tick_out=10)\n", "plot.add_layout(yaxis, \"right\")" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": true }, "outputs": [], "source": [ "hover = HoverTool(tooltips=tooltips, renderers=[medal])\n", "plot.add_tools(hover)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show(plot)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercises" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
fertilitylifepopulationregion_color
Afghanistan7.67133.6399.129985#fc8d59
Albania5.71165.4753.802632#e6f598
Algeria7.65347.9539.630513#fee08b
American SamoaNaNNaN3.000000#99d594
AndorraNaNNaN3.000000#e6f598
\n", "
" ], "text/plain": [ " fertility life population region_color\n", "Afghanistan 7.671 33.639 9.129985 #fc8d59\n", "Albania 5.711 65.475 3.802632 #e6f598\n", "Algeria 7.653 47.953 9.630513 #fee08b\n", "American Samoa NaN NaN 3.000000 #99d594\n", "Andorra NaN NaN 3.000000 #e6f598" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from utils import get_gapminder_1964_data\n", "\n", "def get_plot():\n", " return Plot(\n", " x_range=Range1d(1, 9), y_range=Range1d(20, 100),\n", " plot_width=800, plot_height=400,\n", " outline_line_color=None, toolbar_location=None,\n", " )\n", "\n", "df = get_gapminder_1964_data()\n", "df.head()" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# EXERCISE: Add Circles to the plot from the data in `df`. \n", "# With `fertility` for the x coordinates, `life` for the y coordinates.\n", "\n", "plot = get_plot()\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# EXERCISE: Color the circles by region_color & change the size of the color by population\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# EXERCISE: Add axes and grid lines\n", "\n" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# EXERCISE: Manually add a legend using Circle & Text. The color key is as follows \n", "\n", "region_name_and_color = [\n", " ('America', '#3288bd'),\n", " ('East Asia & Pacific', '#99d594'),\n", " ('Europe & Central Asia', '#e6f598'),\n", " ('Middle East & North Africa', '#fee08b'),\n", " ('South Asia', '#fc8d59'),\n", " ('Sub-Saharan Africa', '#d53e4f')\n", "]" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Custom User Models\n", "\n", "It is possible to extend the set of built-in Bokeh models with your own custom user models. The capability opens some valuable use-cases:\n", "* customizing existing Bokeh model behaviour\n", "* wrapping and connecting other JS libraries to Bokeh\n", "\n", "With this capability, advanced users can try out new features or techniques easily, without having to set up a full Bokeh development environment. \n", "\n", "The basic outline of a custom model starts with a JavaScript implementation, which subclasses an existing BokehJS model:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": true }, "outputs": [], "source": [ "JS_CODE = \"\"\"\n", "# These are similar to python imports. BokehJS vendors its own versions\n", "# of Underscore and JQuery. They are available as show here.\n", "_ = require \"underscore\"\n", "$ = require \"jquery\"\n", "\n", "# The \"core/properties\" module has all the property types\n", "p = require \"core/properties\"\n", "\n", "# We will subclass in JavaScript from the same class that was subclassed\n", "# from in Python\n", "LayoutDOM = require \"models/layouts/layout_dom\"\n", "\n", "# This model will actually need to render things, so we must provide\n", "# view. The LayoutDOM model has a view already, so we will start with that\n", "class CustomView extends LayoutDOM.View\n", "\n", " initialize: (options) ->\n", " super(options)\n", "\n", " @render()\n", "\n", " # Set Backbone listener so that when the Bokeh slider has a change\n", " # event, we can process the new data\n", " @listenTo(@model.slider, 'change', () => @render())\n", "\n", " render: () ->\n", " # Backbone Views create
elements by default, accessible as @$el.\n", " # Many Bokeh views ignore this default
, and instead do things\n", " # like draw to the HTML canvas. In this case though, we change the\n", " # contents of the
, based on the current slider value.\n", " @$el.html(\"

#{ @model.text }: #{ @model.slider.value }

\")\n", " @$('h1').css({ 'color': '#686d8e', 'background-color': '#2a3153' })\n", "\n", "class Custom extends LayoutDOM.Model\n", "\n", " # If there is an associated view, this is boilerplate.\n", " default_view: CustomView\n", "\n", " # The ``type`` class attribute should generally match exactly the name\n", " # of the corresponding Python class.\n", " type: \"Custom\"\n", "\n", " # The @define block adds corresponding \"properties\" to the JS model. These\n", " # should basically line up 1-1 with the Python model class. Most property\n", " # types have counterparts, e.g. bokeh.core.properties.String will be\n", " # p.String in the JS implementation. Where the JS type system is not yet\n", " # as rich, you can use p.Any as a \"wildcard\" property type.\n", " @define {\n", " text: [ p.String ]\n", " slider: [ p.Any ]\n", " }\n", "\n", "# This is boilerplate. Every implementation should export a Model\n", "# and (when applicable) also a View.\n", "module.exports =\n", " Model: Custom\n", " View: CustomView\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This JavaScript implememtation is then attached to a corresponding Python Bokeh model:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from bokeh.core.properties import String, Instance\n", "from bokeh.models import LayoutDOM, Slider\n", "\n", "class Custom(LayoutDOM):\n", "\n", " __implementation__ = JS_CODE\n", "\n", " text = String(default=\"Custom text\")\n", "\n", " slider = Instance(Slider)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then the new model can be used seamlessly in the same way as any built-in Bokeh model:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.io import show\n", "from bokeh.layouts import column\n", "from bokeh.models import Slider\n", "\n", "slider = Slider(start=0, end=10, step=0.1, value=0, title=\"value\")\n", "\n", "custom = Custom(text=\"Special Slider Display\", slider=slider)\n", "\n", "layout = column(slider, custom)\n", "\n", "show(layout)" ] }, { "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" } }, "nbformat": 4, "nbformat_minor": 0 }