In [3]:
%autosave 10


Autosaving every 10 seconds
tarball is missing the data folder, so hit it later today wget http://cdn.pydata.org/BokehTutorial.tgz
  • Main tutorial startpoint: BokehTutorial/html/index.html
  • This is an interactive class, not many slides.
  • Extremely large number of exercises. Need to work on most of them at home.
    • This will take a long time!
  • Very easy to shove into IPython Notebook. Just use output_notebook() then restart your kernel (Kernel -> Restart).

Idea

  • Interactive web visualisation without JavaScript
  • Many bindings, not just Python

 Bokeh

  • Built on HTML5 canvas. Not d3.js. Faster than SVG.
  • Novel and custom visualisations, again without with JavaScript, directly in the binding (i.e. Python)
  • Takes a lot of inspiration from the Grammar of Graphics (the basis of ggplot)
  • Two parts
    • Reactive JavaScript portion, bokehjs, accepts JSON input of layout and data. Standalone, pure JavaScript, anything can provide JSON.
    • Language bindings to a server.

 Concepts

  • Plots are based on glyphs
  • Visual elements of glpyhs connect to vectors

!!AI these plots are mind blowing

Coming in future releases

(0.5 coming end of April 2014; a lot of this coming then)

  • Abstract rendering - dynamic downsampling for millions of points
  • matplotlib compatibility - use Bokeh from pandas, ggplot.py, Seaborn
  • Language bindings - Scala is coming
  • Constraints-based layout system - legends, annotations, grid plots
  • Usability - error messages, discoverable parameters, more live gallery examples

In [4]:
import numpy as np
from bokeh.plotting import *
from bokeh.objects import Range1d

In [5]:
# scripts/lines.py

# Skip the first point because it can be troublesome
theta = np.linspace(0, 8*np.pi, 10000)[1:]

# Compute the radial coordinates for some different spirals
lituus = theta**(-1/2)          # lituus
golden = np.exp(0.306349*theta) # golden
arch   = theta                  # Archimedean
fermat = theta**(1/2)           # Fermat's

# Now compute the X and Y coordinates (polar mappers planned for Bokeh later)
golden_x = golden*np.cos(theta)
golden_y = golden*np.sin(theta)
lituus_x = lituus*np.cos(theta)
lituus_y = lituus*np.sin(theta)
arch_x   = arch*np.cos(theta)
arch_y   = arch*np.sin(theta)
fermat_x = fermat*np.cos(theta)
fermat_y = fermat*np.sin(theta)

In [6]:
# Make sure we can output to IPython Notebook
output_notebook()


Bokeh Plot

Configuring embedded BokehJS mode.


In [7]:
# Plot the Archimedean spiral using the `line` renderer. Note how we set the
# color, line thickness, title, and legend value.
line(arch_x, arch_y, color="red", line_width=2,
     title="Archimean", legend="Archimedean")
show()


Bokeh Plot
Plots

In [8]:
# EXERCISE: reproduce the above plot for one of the other spirals
line(golden_x, golden_y, color="gold", line_width=2,
     title="Golden", legend="Golden")
show()


Bokeh Plot
Plots

In [9]:
# We can plot more than one plot on the same figure.

# Let's try to put all lines on one plot for comparison. First we need to
# turn on `hold` so that each renderer does not create a brand new plot
hold()

# Next we need to actually create a new figure, so that the following
# renderers work on a new plot, and not the last one.
figure()

line(arch_x, arch_y, color="red", line_width=2,
     title="Archimean", legend="Archimedean")
line(golden_x, golden_y, color="gold", line_width=2,
     title="Golden", legend="Golden")
show()


Bokeh Plot
Plots

In [10]:
# OK, so that doesn't look so good because Bokeh tried to autoscale to
# accomodate all the data. We can use the Range1d object to set the plot range
# explicitly

# EXERCISE: create a new figure
hold()
figure()

# EXERCISE: add x_range and y_range parameters to the first `line`, to set the
# range to [-10, 10]. NOTE: Range1d are created like: Range1d(start=0, end-10)
line(arch_x, arch_y, color="red", line_width=2,
     title="Archimean", legend="Archimedean",
     x_range=Range1d(start=0, end=10), y_range=Range1d(start=0, end=10))
line(golden_x, golden_y, color="gold", line_width=2,
     title="Golden", legend="Golden")
show()


Bokeh Plot
Plots

In [11]:
# scripts/scatter.py

# Recreate the Fermat spiral from the last exercise, with some different scalings
# and number of turnings
theta1 = np.linspace(0, 256*np.pi, 500)[1:]
f1 = theta1**(1/2)
x1 = 0.5*f1*np.cos(theta1)
y1 = 0.5*f1*np.sin(theta1)

theta2 = np.linspace(0, 96*np.pi, 500)[1:]
f2 = theta2**(1/2)
x2 = f2*f2*np.cos(theta2)
y2 = f2*f2*np.sin(theta2)

theta3 = np.linspace(0, 32*np.pi, 500)[1:]
f3 = 2*theta3**(1/2)
x3 = 2*f3*np.cos(theta3)
y3 = 2*f3*np.sin(theta3)

TOOLS="pan,wheel_zoom,box_zoom,reset,previewsave"
output_notebook()


Bokeh Plot

Configuring embedded BokehJS mode.


In [12]:
# EXERCISE: create two Range1d objects to reuse in the plots. Use the [-20, 20]
# for the bounds.
#
# These plots are interconnected because we're sharing the same RangeId objects.
# This is pretty cool; if you don't want this then create new instances for
# each plot.
xr = Range1d(start=-20, end=20)
yr = Range1d(start=-20, end=20)

In [13]:
# EXERCISE: Plot all the sets of points on different plots. Use the ranges above
# for x_range and y_range. Set different colors as well. Try setting line_color
# and fill_color instead of just color. You can also set alpha, line_alpha, and
# fill_alpha if you like. Set tools to TOOLS on the first renderer. Change
# the value of the 'marker' parameter, "circle", "square", "triangle", etc. One
# example is given
hold()
figure()
scatter(x1, y1, size=12, color="red", alpha=0.5,
        x_range=xr, y_range=yr, tools=TOOLS)
show()


Bokeh Plot
Plots

In [14]:
hold()
figure()
scatter(x2, y2, size=12, color="red", alpha=0.5,
        x_range=xr, y_range=yr, tools=TOOLS)
show()


Bokeh Plot
Plots

In [15]:
hold()
figure()
scatter(x3, y3, size=12, color="red", alpha=0.5,
        x_range=xr, y_range=yr, tools=TOOLS)
show()


Bokeh Plot
Plots

In [16]:
# scripts/styles.py

import numpy as np
import pandas as pd
from bokeh.plotting import *
from bokeh.objects import Range1d

# Define some categories
categories = [
    'ousia', 'poson', 'poion', 'pros ti', 'pou',
    'pote', 'keisthai', 'echein', 'poiein', 'paschein',
]

# Create data
N = 10
data = { cat : np.random.randint(10, 100, size=N) for cat in categories }

# Define a little function to stack series together to make polygons. Soon
# this will be built into Bokeh.
def stacked(data, categories):
    ys = []
    last = np.zeros(len(data.values()[0]))
    for cat in categories:
        next = last + data[cat]
        ys.append(np.hstack((last[::-1], next)))
        last = next
    return ys

# Get the y coordinates of the stacked data
ys = stacked(data, categories)

# The x coordinates for each polygon are simply the series concatenated
# with its reverse.
xs = [np.hstack((categories[::-1], categories))] * len(ys)

# Pick out a color palette
colors = brewer["Spectral"][len(ys)]

In [17]:
# EXERCISE: play around with parameters like:
#   - line_color
#   - line_alpha
#   - line_width
#   - line_dash   (e.g., [2,4])
#   - fill_color
#   - fill_alpha
#   - background_fill
hold()
figure()
patches(xs, ys, x_range=categories, y_range=Range1d(start=0, end=800),
        color=colors, alpha=0.8, line_color=None, background_fill="lightgrey",
        title="Categories of Brewering")


Out[17]:
<bokeh.objects.Plot at 0x2f8ae50>

In [18]:
# EXERCISE: configure all of the following plot properties
ygrid().grid_line_color = "white"  # None to get rid of a line
ygrid().grid_line_width = 2
axis().major_label_text_font_size = "12pt"
axis().major_label_text_font_style = "bold"
axis().major_label_standoff = 10            # distance of tick labels from ticks
axis().axis_line_color = None               # color, or None, to suppress the line
xaxis().major_label_orientation = np.pi/4   # radians, "horizontal", "vertical", "normal"
xaxis().major_tick_in = 10                  # distance ticks extends into the plot
xaxis().major_tick_out = 0                  # and distance they extend out
xaxis().major_tick_line_color = "white"

show()


Bokeh Plot
Plots