Interactive Data Visualization with Bokeh

Bokeh is an interactive data visualization library for Python that targets web browsers for presentation. It can create versatile, data-driven graphics, and connect Python data-science stack to rich and interactive visualizations.

Basic plotting with Bokeh

Here we'll demonstrate formats we can use with Bokeh, generate some basic plots, and begin customizations for selecting data.

Here we'll demonstrate

  • Basic plotting
  • Layouts, interactions, and annotations
  • Statistical charting
  • Interactive data applications in the browser
  • Case Study: A Gapminder explorer

Plotting with glyphs

Glyphs are:

  • Visual shapes...
    • circles, squares, triangles
    • rectangles, lines, wedges
  • ...that have properties attached to data
    • coordinates (x,y)
    • size, color, transparency

Typical usage

In [1]:
from bokeh.io import output_file, show, output_notebook, reset_output
from bokeh.plotting import figure 
reset_output()
output_notebook()

plot = figure(plot_width=400, plot_height = 400, tools='pan,box_zoom') # can also do plot_height vs. width
x = [1,2,3,4,5]
y = [8,6,5,2,3]
sizes = [10,20,30,40,50]

#plot both lines and circles together
plot.circle(x, y, size=sizes, fill_color = 'red') 
plot.line(x, y, line_width=2) 

show(plot)
Loading BokehJS ...
  • We could also output the file separately with output_file

Glyph properties

  • Lists, arrays, or sequences of values
  • Along with array-like data structures, properties can also be single fixed values

Marker reference

  • asterisk()
  • circle()
  • circle_cross()
  • circle_x()
  • cross()
  • diamond()
  • diamond_cross()
  • inverted_triangle()
  • square()
  • square_cross()
  • square_x()
  • triangle()
  • x()

Simple scatter plot

In this example, we'll make a scatter plot of female literacy vs fertility using data from the European Environmental Agency.

This dataset highlights that countries with low female literacy have high birthrates.

The x-axis data will be fertility and the y-axis data will be female_literacy.

We will plot female_literacy vs fertility using the circle glyph.

Note that the right side options allow us to Pan, Box Zoom, and Wheel Zoom.

In [2]:
#import the data
import pandas as pd
fertility = pd.read_csv('data/fertility.csv').fertility
female_literacy = pd.read_csv('data/female_literacy.csv').female_literacy

# Import figure from bokeh.plotting
from bokeh.plotting import figure

# Import output_file and show from bokeh.io
from bokeh.io import show, output_notebook, reset_output #output_file

# Create the figure: p
p = figure(x_axis_label='fertility (children per woman)', y_axis_label='female_literacy (% population)')

# Add a circle glyph to the figure p
p.circle(fertility, female_literacy)

# Call the output_file() function and specify the name of the file
#output_file('fert_lit.html')

# Change from file to notebook inline:
output_notebook()

# Display the plot
show(p)
Loading BokehJS ...

A scatter plot with different shapes and colors

By calling multiple glyph functions on the same figure object, we can overlay multiple data sets in the same figure. We can customize the shape, color, size, and transparency (alpha) of the markers.

We make the same plot as before of female literacy vs fertility for two different regions, Africa and Latin America.

In [3]:
fertility_latin_america = pd.read_csv('data/fertility_latin_america.csv').fertility_latin_america
female_literacy_latin_america = pd.read_csv('data/female_literacy_latin_america.csv').female_literacy_latin_america
fertility_africa = pd.read_csv('data/fertility_africa.csv').fertility_africa
female_literacy_africa = pd.read_csv('data/female_literacy_africa.csv').female_literacy_africa

# Create the figure: p
p = figure(x_axis_label='fertility', y_axis_label='female_literacy (% population)')

# Add a circle glyph to the figure p
p.circle(fertility_latin_america, female_literacy_latin_america, color = 'blue', size = 10, alpha = 0.8)

# Add an x glyph to the figure p
p.x(fertility_africa, female_literacy_africa, color = 'red', size = 10, alpha = 0.8)

# Specify inline output
output_notebook()

# Display the plot
show(p)

#pd.DataFrame(female_literacy_africa,  columns = ['female_literacy_africa']).to_csv('data/female_literacy_africa.csv',index = False)
Loading BokehJS ...

Multiple Lines and Legends

In [4]:
import pandas as pd
from bokeh.palettes import Category10_4
from bokeh.plotting import figure, output_file, show

#import data
stocks = pd.read_csv('data/stocks.csv', parse_dates = [0], infer_datetime_format = True)
date = list(map(lambda x:x.to_pydatetime(), stocks.Date.tolist()))

#create figure
p = figure(plot_width=800, plot_height=250, x_axis_type="datetime")
p.title.text = 'Stock Data with interactive Legend'

#recursively plot each stock
for data, name, color in zip([stocks.AAPL, stocks.IBM, stocks.MSFT, stocks.CSCO], ["AAPL", "IBM", "CSCO", "MSFT"], Category10_4):
    p.line(date, data, line_width=2, color=color, alpha=0.8, legend=name)

p.legend.location = "top_left"
p.legend.click_policy="hide"

show(p)

Patches

In Bokeh, extended geometrical shapes can be plotted by using the patches()glyph function. The patches glyph takes as input a list-of-lists collection of numericvalues specifying the vertices in x and y directions of each distinct patch toplot.

In this exercise, we will plot the state borders of Arizona, Colorado, New Mexico and Utah.

In [5]:
from bokeh.sampledata.us_states import data as states

p = figure(x_axis_label='longitude (degrees)', y_axis_label='latitude (degrees)')

# Create a list of longitudes for Arizona, Colorado, New Mexico, and Utah. 
x = [states['AZ']['lons'], states['CO']['lons'], states['NM']['lons'], states['UT']['lons']]

# Create a list of Latitudes for the same states
y = [states['AZ']['lats'], states['CO']['lats'], states['NM']['lats'], states['UT']['lats']]

# Add patches to figure p with line_color=white for x and y
p.patches(x, y, line_color = 'white')

# Specify the name of the output file if external output is needed and show the result
#output_file('four_corners.html')
show(p)

Data formats

  • Examples of different types of input bokeh can import:

Python lists

In [6]:
from bokeh.io import output_file, show 
from bokeh.plotting import figure 
x = [1,2,3,4,5] 
y = [8,6,5,2,3] 
plot = figure() 
plot.line(x, y, line_width=3) 
plot.circle(x, y, fill_color='white', size=10) 

show(plot)

NumPy Arrays

In [7]:
from bokeh.io import output_file, show 
from bokeh.plotting import figure 
import numpy as np 
x = np.linspace(0, 10, 1000) 
y = np.sin(x) + np.random.random(1000) * 0.2 
plot = figure() 
plot.line(x, y) 
show(plot)
In [8]:
p = figure()

# Create array using np.linspace: x
x = np.linspace(0, 5, 100)

# Create array using np.cos: y
y = np.cos(x)

# Add circles at x and y
p.circle(x, y)

# Show the result
show(p)

Plotting data from Pandas DataFrames

You can create Bokeh plots from Pandas DataFrames by passing column selections to the glyph functions.

Bokeh can plot floating point numbers, integers, and datetime data types. In this example, we will read a CSV file containing information on 392 automobiles manufactured in the US, Europe and Asia from 1970 to 1982.

We will plot miles-per-gallon (mpg) vs horsepower (hp) by passing Pandas column selections into the p.circle() function. Additionally, each glyph will be colored according to values in the color column.

In [9]:
# Import pandas as pd
import pandas as pd


# Read in the CSV file: df
cars = pd.read_csv('data/auto.csv')
colors = pd.DataFrame({'origin':['US', 'Asia', 'Europe'], 'color':['blue', 'red','green']})
df = cars.merge(colors, on = 'origin')

# Import figure from bokeh.plotting
from bokeh.plotting import figure 

# Create the figure: p
p = figure(x_axis_label='HP', y_axis_label='MPG')

# Plot mpg vs hp by color
p.circle(df['hp'], df['mpg'], color = df['color'], size = 10)

# Show the result
show(p)

Column Data Source

  • Common fundamental data structure for Bokeh
  • Maps string column names to sequences of data
  • Often created automatically for you
  • Can be shared between glyphs to link selections
  • Extra columns can be used with hover tooltips

The ColumnDataSource is a table-like data object that maps string column names to sequences (columns) of data. It is the central and most common data structure in Bokeh. It is important that:

  • All columns in a ColumnDataSource must have the same length.
  • ColumnDataSource objects can be shared between different plots.
  • ColumnDataSource objects are not interchangeable with Pandas DataFrames. However, you can create a ColumnDataSource object directly from a Pandas DataFrame by passing the DataFrame to the class initializer.

Column Data Source example

In [10]:
from bokeh.models import ColumnDataSource 
source = ColumnDataSource(data={    
                           'x': [1,2,3,4,5],    
                           'y': [8,6,5,2,3]}) 
source.data 
Out[10]:
{'x': [1, 2, 3, 4, 5], 'y': [8, 6, 5, 2, 3]}

ColumnDataSource with Olympic Medals

We will read in a data set containing all Olympic medals awarded in the 100 meter sprint from 1896 to 2012. A color column has been added indicating the CSS colorname we wish to use in the plot for every data point.

We will create a new ColumnDataSource object from the DataFrame df, and plot circle glyphs with 'Year' on the x-axis and 'Time' on the y-axis. We will also color each glyph by the color column.

In [11]:
# Read in the CSV file: df
df = pd.read_csv('data/sprint.csv')

# Create the figure: p
p = figure(x_axis_label='Year', y_axis_label='Time')

# Import the ColumnDataSource class from bokeh.plotting
from bokeh.plotting import ColumnDataSource

# Create a ColumnDataSource from df: source
source = ColumnDataSource(df)

# Add circle glyphs to the figure p
p.circle('Year', 'Time', source = source, color = 'color', size = 8)

# Show the result
show(p)

Customizing glyphs

Selection appearance

  • See Gapminder demo for changes applied to selection data
  • Hover tips
  • Colormapping

Selection and non-selection glyphs of Olympic 100M Sprints

In this demonstration, you're going to add the box_select tool to a figure and change the selected and non-selected circle glyph properties so that selected glyphs are red and non-selected glyphs are transparent blue.

Feel free to experiment with the Box Select tool

In [12]:
# Create a figure with the "box_select" tool: p
p = figure(x_axis_label = 'Year', y_axis_label = 'Time', tools = 'box_select')

# Add circle glyphs to the figure p with the selected and non-selected properties
p.circle('Year', 'Time', source = source, selection_color = 'red', nonselection_alpha = 0.1)

# Show the result
show(p)

Hover glyphs

In this demonstration, we'll plot blood glucose levels recorded every 5 minutes.

x will be date and time while blood glucose levels in mg/dL will be the vertical dimension.

Here, instead of selecting, simply hovering near datapoints will cause the points to change to red.

In [13]:
#Data import
df = pd.read_csv('data/glucose.csv')
df.head()
x = df.index.values
y = df.glucose.values

# import the HoverTool
from bokeh.models import HoverTool

p=figure(x_axis_type='datetime', x_axis_label = 'Time of Day', y_axis_label = 'Blood Glucose (mg/dL)', title = 'Blood Glucose')

p.line(x, y, line_color = 'black', line_dash = 'dashed')

# Add circle glyphs to figure p
p.circle(x, y, size=10,
         fill_color='grey', alpha=0.1, line_color=None,
         hover_fill_color='firebrick', hover_alpha=0.5,
         hover_line_color='white')

# Create a HoverTool: hover
hover = HoverTool(tooltips = None, mode = 'vline') #any point in a vertical line with the cursor

# Add the hover tool to the figure p
p.add_tools(hover)

# Show the result

show(p)

Colormapping

For our colormapping demo, we'll use the automobile dataset to plot miles-per-gallon vs weight and color each circle glyph by the region where the automobile was manufactured.

In [14]:
df = pd.read_csv('data/auto-mpg-color.csv')


#Import CategoricalColorMapper from bokeh.models
from bokeh.models import CategoricalColorMapper
from bokeh.models import HoverTool 

hover = HoverTool(tooltips=[    
     ('Name', '@name'),    
     ('Year', '@yr'),     
     ('Cylinder', '@cyl'),    
     ]) 
p = figure(tools=[hover, 'pan', 'wheel_zoom'], 
           x_axis_type='datetime', 
           x_axis_label = 'weight (lbs)', 
           y_axis_label = 'miles-per-gallon', 
           title = 'Efficiency')


# Convert df to a ColumnDataSource: source
source = ColumnDataSource(df)

# Make a CategoricalColorMapper object: color_mapper
color_mapper = CategoricalColorMapper(factors=['Europe', 'Asia', 'US'],
                                      palette=['red', 'green', 'blue'])

# Add a circle glyph to the figure p
p.circle(x = 'weight',
         y = 'mpg', 
         source=source,
         color=dict(field = 'origin',
                    transform = color_mapper),
         legend='origin')

# Show the result

show(p)

Layouts, Interactions, and Annotations

Here we'll demonstrate how to combine mutiple Bokeh plots into different kinds of layouts on a page, how to easily link different plots together in various ways, and how to add annotations such as legends and hover tooltips.

Introduction to layouts

Arranging multiple plots

  • Plots and interactive controls can be arranged visually on a page in many ways:
    • rows, columns
    • grid arrangements
    • tabbed layouts
    • all these collections of bokeh figures combine into a layout

Creating rows of plots

Here we'll demonstrate rows of plots with the Literacy and Birth Rate data set to plot fertility vs female literacy and population vs female literacy.

By using the row() method, you'll create a single layout of the two figures.

In [15]:
df = pd.read_csv('data/literacy_birth_rate.csv', nrows = 162)
df.columns = ['Country ', 'Continent', 'female_literacy', 'fertility', 'population']

source =  ColumnDataSource(df)

# Import row from bokeh.layouts
from bokeh.layouts import row

# Create the first figure: p1
p1 = figure(x_axis_label='fertility (children per woman)', y_axis_label='female_literacy (% population)', plot_width = 400)

# Add a circle glyph to p1
p1.circle('fertility', 'female_literacy', source=source)

# Create the second figure: p2
p2 = figure(x_axis_label='population', y_axis_label='female_literacy (% population)', plot_width = 400)

# Add a circle glyph to p2
p2.circle('population', 'female_literacy', source = source)

# Put p1 and p2 into a horizontal row: layout
layout = row(p1, p2)


# Show the result

show(layout)

Creating columns of plots

Here we'll demo how to use the column() function to create a single column layout of the same two plots as above.

In [16]:
# Import column from the bokeh.layouts module
from bokeh.layouts import column

# Create a blank figure: p1
p1 = figure(x_axis_label='fertility (children per woman)', y_axis_label='female_literacy (% population)', plot_height = 400)

# Add circle scatter to the figure p1
p1.circle('fertility', 'female_literacy', source=source)

# Create a new blank figure: p2
p2 = figure(x_axis_label='population', y_axis_label='female_literacy (% population)', plot_height = 400)

# Add circle scatter to the figure p2
p2.circle('population', 'female_literacy', source=source)

# Put plots p1 and p2 in a column: layout
layout = column(p1, p2)

# Show the result

show(layout)

Nesting rows and columns of plots

We can combine row and column layouts to create nested layouts of plots.

We can add in the sizing_mode argument to scale the widths to fill the whole figure.

In [17]:
#import data
df = pd.read_csv('data/auto-mpg-color.csv')
avg_mpg_df = df.groupby('yr').mean()
avg_mpg_df['mpg']

#Import CategoricalColorMapper from bokeh.models
from bokeh.models import CategoricalColorMapper
from bokeh.models import HoverTool 
source = ColumnDataSource(df)
hover = HoverTool(tooltips=[    
     ('Name', '@name'),    
     ('Year', '@yr'),     
     ('Cylinder', '@cyl'),    
     ]) 
mpg_hp = figure(tools=[hover, 'pan', 'wheel_zoom'], x_axis_label = 'miles-per-gallon', y_axis_label = 'hp')
mpg_weight = figure(tools=[hover, 'pan', 'wheel_zoom'], x_axis_label = 'miles-per-gallon', y_axis_label = 'weight')
avg_mpg = figure(tools=[hover, 'pan', 'wheel_zoom'], x_axis_label = 'yr', y_axis_label = 'avg-miles-per-gallon')

mpg_hp.circle('mpg', 'hp', source=source,
            color=dict(field = 'origin', transform = color_mapper),
            legend='origin')
mpg_weight.circle('mpg', 'weight', source=source,
            color=dict(field = 'origin', transform = color_mapper),
            legend='origin')
avg_mpg.line(avg_mpg_df.index,avg_mpg_df['mpg'])


# Import column and row from bokeh.layouts
from bokeh.layouts import column, row

# Make a column layout that will be used as the second row: row2
row2 = column([mpg_hp, mpg_weight], sizing_mode='scale_width')

# Make a row layout that includes the above column layout: layout
layout = row([avg_mpg, row2], sizing_mode='scale_width')

# Show the result
show(layout)

Advanced layouts

Gridplots

  • Give a “list of rows” for layout
  • can use None as a placeholder
  • Accepts toolbar_location
In [18]:
from bokeh.layouts import gridplot 

layout = gridplot([[avg_mpg, None], [mpg_weight, mpg_hp]],                         
               toolbar_location=None) 

show(layout)

Creating gridded layouts

Regular grids of Bokeh plots can be generated with gridplot.

In this example, you're going to display four plots of fertility vs female literacy for four regions: Latin America, Africa, Asia and Europe.

Your job is to create a list-of-lists for the four Bokeh plots that have been provided to you as p1, p2, p3 and p4. The list-of-lists defines the row and column placement of each plot.


  • Import gridplot from the bokeh.layouts module.

  • Create a list called row1 containing plots p1 and p2.

  • Create a list called row2 containing plots p3 and p4.

  • Create a gridplot using row1 and row2. You will have to pass in row1 and row2 in the form of a list.

In [19]:
#import data

from bokeh.models import ColumnDataSource, CDSView, BooleanFilter
from bokeh.plotting import figure 

df = pd.read_csv('data/literacy_birth_rate.csv', nrows = 162)
source = ColumnDataSource(df)

#create four filters
latin_view = CDSView(source=source, filters=[BooleanFilter(df.Continent == 'LAT')])
africa_view = CDSView(source=source, filters=[BooleanFilter(df.Continent == 'AF')])
asia_view = CDSView(source=source, filters=[BooleanFilter(df.Continent == 'ASI')])
europe_view = CDSView(source=source, filters=[BooleanFilter(df.Continent == 'EUR')])

#desired tools
tools = ["box_select", "hover", "reset", 'pan','wheel_zoom']

#create the four figures
p1 = figure(x_axis_label = 'fertility (children per woman)', y_axis_label = 'female literacy (% population)', tools=tools, title = 'Latin', plot_width=300, plot_height=300)
p1.circle(x='fertility', y='female literacy', size=10, hover_color="red", source=source, view=latin_view)

p2 = figure(x_axis_label = 'fertility (children per woman)', y_axis_label = 'female literacy (% population)', tools=tools, title = 'Africa', plot_width=300, plot_height=300)
p2.circle(x='fertility', y='female literacy', size=10, hover_color="red", source=source, view=africa_view)

p3 = figure(x_axis_label = 'fertility (children per woman)', y_axis_label = 'female literacy (% population)', tools=tools, title = 'Asia', plot_width=300, plot_height=300)
p3.circle(x='fertility', y='female literacy', size=10, hover_color="red", source=source, view=asia_view)

p4 = figure(x_axis_label = 'fertility (children per woman)', y_axis_label = 'female literacy (% population)', tools=tools, title = 'Europe', plot_width=300, plot_height=300)
p4.circle(x='fertility', y='female literacy', size=10, hover_color="red", source=source, view=europe_view)


# Import gridplot from bokeh.layouts
from bokeh.layouts import gridplot

# Create a list containing plots p1 and p2: row1
row1 = [p1, p2]

# Create a list containing plots p3 and p4: row2
row2 = [p3, p4]

# Create a gridplot using row1 and row2: layout
layout = gridplot([row1, row2])

# Show the result
show(layout)

Great work! In the next exercise, you will use these panels to build and display a tabbed layout.

Starting and displaying tabbed layouts

Tabbed layouts can be created in Bokeh by placing plots or layouts in Panels. In essence, they are collections of Panel objects.

In this demo, we'll take the four fertility vs female literacy plots from the last demo and make a Panel() for each. Then we'll create a tabbed layout to change the region in the fertility vs female literacy plots.

Feel free to explore the tabs and "Pan", "Box Zoom" and "Wheel Zoom" tools are also all available as before.

In [20]:
# Import Panel from bokeh.models.widgets
from bokeh.models.widgets import Panel

# Create tab1 from plot p1: tab1
tab1 = Panel(child=p1, title='Latin America')

# Create tab2 from plot p2: tab2
tab2 = Panel(child=p2, title='Africa')

# Create tab3 from plot p3: tab3
tab3 = Panel(child=p3, title='Asia')

# Create tab4 from plot p4: tab4
tab4 = Panel(child=p4, title='Europe')


# Import Tabs from bokeh.models.widgets
from bokeh.models.widgets import Tabs

# Create a Tabs layout: layout
tab_layout = Tabs(tabs=[tab1, tab2, tab3, tab4])

# Specify the name of the output_file and show the result
#output_file('tabs.html')
show(tab_layout)

Linking Plots Together

  • Bokeh allows us to link plots together in multiple ways:
    • Linked Axes
    • Linked selections

Linked axes

Linking axes between plots is achieved by sharing range objects.

Here, we'll link four plots of female literacy vs fertility so that when one plot is zoomed or dragged, one or more of the other plots will respond.

In [21]:
# Link the x_range of p2 to p1: p2.x_range
p2.x_range = p1.x_range

# Link the y_range of p2 to p1: p2.y_range
p2.y_range = p1.y_range

# Link the x_range of p3 to p1: p3.x_range
p3.x_range = p1.x_range

# Link the y_range of p4 to p1: p4.y_range
p4.y_range = p1.y_range

# Specify the name of the output_file and show the result
#output_file('linked_range.html')
show(layout)

Linked brushing

By sharing the same ColumnDataSource object between multiple plots, selection tools like BoxSelect and LassoSelect will highlight points in both plots that share a row in the ColumnDataSource.

To demonstrate, we'll plot female literacy vs fertility and population vs fertility in two plots using the same ColumnDataSource.

Use the lasso or box select tool to see how selecting data in one plot also selects the same data in another.

In [22]:
from bokeh.layouts import row

# Create ColumnDataSource: source
source = ColumnDataSource(df)

# Create the first figure: p1
p1_linked = figure(x_axis_label='fertility (children per woman)', y_axis_label='female literacy (% population)',
            tools=['box_select', 'lasso_select'], plot_width = 400, plot_height = 400)

# Add a circle glyph to p1
p1_linked.circle('fertility', 'female literacy', source = source)

# Create the second figure: p2
p2_linked = figure(x_axis_label='fertility (children per woman)', y_axis_label='population (millions)',
            tools=['box_select', 'lasso_select'], plot_width = 400, plot_height = 400)

# Add a circle glyph to p2
p2_linked.circle('fertility', 'population', source = source)

# Create row layout of figures p1 and p2: layout
layout_linked = row(p1_linked, p2_linked)

# Show the result
show(layout_linked)

Annotations and Guides

What are they?

  • Help relate scale information to the viewer
    • Axes, Grids (default on most plots)
  • Explain the visual encodings that are used
    • Legends
  • Drill down into details not visible in the plot
    • Hover Tooltips

Creating legends

Legends can be added to any glyph by using the legend keyword argument.

In [23]:
#basic setup
tools = ['box_zoom', 'wheel_zoom', 'pan', 'save', 'reset', 'help']
p = figure(x_axis_label = 'fertility (children per woman)', y_axis_label = 'female_literacy (%population)', tools = tools)
latin_america = ColumnDataSource(df.loc[df.Continent == 'LAT'])
africa = ColumnDataSource(df.loc[df.Continent == 'AF'])


# Add the first circle glyph to the figure p
p.circle('fertility', 'female literacy', source=latin_america, size=10, color='red', legend='Latin America')

# Add the second circle glyph to the figure p
p.circle('fertility', 'female literacy', source=africa, size=10, color='blue', legend='Africa')

# Show the result
show(p)

Positioning and styling legends

  • Properties of the legend can be changed by using the legend member attribute of a Bokeh figure after the glyphs have been plotted.

  • Here we'll change the location and background color of the legend

In [24]:
# Assign the legend to the bottom left: p.legend.location
p.legend.location = 'bottom_left'

# Fill the legend background with the color 'lightgray': p.legend.background_fill_color
p.legend.background_fill_color = 'lightgray'

# Specify the name of the output_file and show the result
#output_file('fert_lit_groups.html')
show(p)

Adding a hover tooltip

  • We can create a HoverTool object and display the country for each circle glyph in the figure that we created in the last exercise.
  • This is done by assigning the tooltips keyword argument to a list-of-tuples specifying the label and the column of values from the ColumnDataSource using the @ operator.
In [25]:
# Import HoverTool from bokeh.models
from bokeh.models import HoverTool

# Create a HoverTool object: hover
hover = HoverTool(tooltips = [('Country', '@Country')])

# Add the HoverTool object to figure p
p.add_tools(hover)

# Show the result

show(p)

Building interactive apps with Bokeh

Bokeh server applications let you connect all of the powerful Python libraries for analytics and data science, such as NumPy and Pandas, to rich interactive Bokeh visualizations.

We will also learn about Bokeh's built-in widgets, how to add them to Bokeh documents alongside plots, and how to connect everything to real python code using the Bokeh server.

Bokeh server will automatically keep every property of any Bokeh object in sync.

Bokeh Server Applications

Basic App Outline

    outline.py
from bokeh.io import curdoc 
# Create plots and widgets  
# Add callbacks 
# Arrange plots and widgets in layouts 
curdoc().add_root(layout)

Running Bokeh Applications

Run single module apps at the shell or Windows command prompt:

bokeh serve --show myapp.py

“Directory” style apps run similarly: bokeh serve --show myappdir/

Using the current document

Building an interactive Bokeh app typically begins with importing the curdoc, or "current document", function from bokeh.io. This current document will eventually hold all the plots, controls, and layouts we create.

In [29]:
# Perform necessary imports
from bokeh.io import curdoc
from bokeh.plotting import figure

# Create a new plot: plot
plot = figure()

# Add a line to the plot
plot.line(x = [1,2,3,4,5], y = [2,5,4,6,7])

# Add the plot to the current document
curdoc().add_root(plot)
show(plot)

Adding sliders

In [33]:
# Perform the necessary imports
from bokeh.io import curdoc
from bokeh.layouts import widgetbox
from bokeh.models import Slider

# Create first slider: slider1
slider1 =  Slider(title='slider1', start=0, end=10, step=0.1, value=2)

# Create second slider: slider2
slider2 =  Slider(title='slider2', start=10, end=100, step=1, value=20)

# Add slider1 and slider2 to a widgetbox
layout = widgetbox(slider1, slider2)

# Add the layout to the current document
curdoc().add_root(layout)
show(layout)

Connecting sliders to plots

A slider example

In [32]:
from bokeh.io import curdoc 
from bokeh.layouts import column  
from bokeh.models import ColumnDataSource, Slider 
from bokeh.plotting import figure 
from numpy.random import random 

N = 300 
source = ColumnDataSource(data={'x': random(N), 'y': random(N)}) 

# Create plots and widgets  
plot = figure() 
plot.circle(x= 'x', y='y', source=source) 
slider = Slider(start=100, end=1000, value=N,
                step=10, title='Number of points')

# Add callback to widgets 
def callback(attr, old, new):
     N = slider.value
     source.data={'x': random(N), 'y': random(N)} 
slider.on_change('value', callback) 

# Arrange plots and widgets in layouts 
layout = column(slider, plot) 

curdoc().add_root(layout)   
show(layout)
WARNING:bokeh.embed.util:
You are generating standalone HTML/JS output, but trying to use real Python
callbacks (i.e. with on_change or on_event). This combination cannot work.

Only JavaScript callbacks may be used with standalone output. For more
information on JavaScript callbacks with Bokeh, see:

    http://bokeh.pydata.org/en/latest/docs/user_guide/interaction/callbacks.html

Alternatively, to use real Python callbacks, a Bokeh server application may
be used. For more information on building and running Bokeh applications, see:

    http://bokeh.pydata.org/en/latest/docs/user_guide/server.html

Adding callbacks to sliders

Callbacks are functions that a user can define, like def callback(attr, old, new), that can be called automatically when some property of a Bokeh object (e.g., the value of a Slider) changes.

A callback is added by calling myslider.on_change('value', callback).

How to combine Bokeh models into layouts

A simple Bokeh application would have a slider, plot, and also updates the plot based on the slider.

In [34]:
#Imports 
from bokeh.io import curdoc 
from bokeh.layouts import column  
from bokeh.models import ColumnDataSource, Slider 
from bokeh.plotting import figure 
from numpy.random import random 
import numpy as np
from bokeh.layouts import widgetbox


x = np.asarray([0.3,   0.33244147,   0.36488294,   0.39732441, 0.42976589,   0.46220736,   0.49464883,   0.5270903 , 0.55953177,   0.59197324,   0.62441472,   0.65685619, 0.68929766,   0.72173913,   0.7541806 ,   0.78662207, 0.81906355,   0.85150502,   0.88394649,   0.91638796, 0.94882943,   0.9812709 ,   1.01371237,   1.04615385, 1.07859532,   1.11103679,   1.14347826,   1.17591973, 1.2083612 ,   1.24080268,   1.27324415,   1.30568562, 1.33812709,   1.37056856,   1.40301003,   1.43545151, 1.46789298,   1.50033445,   1.53277592,   1.56521739, 1.59765886,   1.63010033,   1.66254181,   1.69498328, 1.72742475,   1.75986622,   1.79230769,   1.82474916, 1.85719064,   1.88963211,   1.92207358,   1.95451505, 1.98695652,   2.01939799,   2.05183946,   2.08428094, 2.11672241,   2.14916388,   2.18160535,   2.21404682, 2.24648829,   2.27892977,   2.31137124,   2.34381271, 2.37625418,   2.40869565,   2.44113712,   2.4735786 , 2.50602007,   2.53846154,   2.57090301,   2.60334448, 2.63578595,   2.66822742,   2.7006689 ,   2.73311037, 2.76555184,   2.79799331,   2.83043478,   2.86287625, 2.89531773,   2.9277592 ,   2.96020067,   2.99264214, 3.02508361,   3.05752508,   3.08996656,   3.12240803, 3.1548495 ,   3.18729097,   3.21973244,   3.25217391, 3.28461538,   3.31705686,   3.34949833,   3.3819398 , 3.41438127,   3.44682274,   3.47926421,   3.51170569, 3.54414716,   3.57658863,   3.6090301 ,   3.64147157, 3.67391304,   3.70635452,   3.73879599,   3.77123746, 3.80367893,   3.8361204 ,   3.86856187,   3.90100334, 3.93344482,   3.96588629,   3.99832776,   4.03076923, 4.0632107 ,   4.09565217,   4.12809365,   4.16053512, 4.19297659,   4.22541806,   4.25785953,   4.290301  , 4.32274247,   4.35518395,   4.38762542,   4.42006689, 4.45250836,   4.48494983,   4.5173913 ,   4.54983278, 4.58227425,   4.61471572,   4.64715719,   4.67959866, 4.71204013,   4.74448161,   4.77692308,   4.80936455, 4.84180602,   4.87424749,   4.90668896,   4.93913043, 4.97157191,   5.00401338,   5.03645485,   5.06889632, 5.10133779,   5.13377926,   5.16622074,   5.19866221, 5.23110368,   5.26354515,   5.29598662,   5.32842809, 5.36086957,   5.39331104,   5.42575251,   5.45819398, 5.49063545,   5.52307692,   5.55551839,   5.58795987, 5.62040134,   5.65284281,   5.68528428,   5.71772575, 5.75016722,   5.7826087 ,   5.81505017,   5.84749164, 5.87993311,   5.91237458,   5.94481605,   5.97725753, 6.009699  ,   6.04214047,   6.07458194,   6.10702341, 6.13946488,   6.17190635,   6.20434783,   6.2367893 , 6.26923077,   6.30167224,   6.33411371,   6.36655518, 6.39899666,   6.43143813,   6.4638796 ,   6.49632107, 6.52876254,   6.56120401,   6.59364548,   6.62608696, 6.65852843,   6.6909699 ,   6.72341137,   6.75585284, 6.78829431,   6.82073579,   6.85317726,   6.88561873, 6.9180602 ,   6.95050167,   6.98294314,   7.01538462, 7.04782609,   7.08026756,   7.11270903,   7.1451505 , 7.17759197,   7.21003344,   7.24247492,   7.27491639, 7.30735786,   7.33979933,   7.3722408 ,   7.40468227, 7.43712375,   7.46956522,   7.50200669,   7.53444816, 7.56688963,   7.5993311 ,   7.63177258,   7.66421405, 7.69665552,   7.72909699,   7.76153846,   7.79397993, 7.8264214 ,   7.85886288,   7.89130435,   7.92374582, 7.95618729,   7.98862876,   8.02107023,   8.05351171, 8.08595318,   8.11839465,   8.15083612,   8.18327759, 8.21571906,   8.24816054,   8.28060201,   8.31304348, 8.34548495,   8.37792642,   8.41036789,   8.44280936, 8.47525084,   8.50769231,   8.54013378,   8.57257525, 8.60501672,   8.63745819,   8.66989967,   8.70234114, 8.73478261,   8.76722408,   8.79966555,   8.83210702, 8.86454849,   8.89698997,   8.92943144,   8.96187291, 8.99431438,   9.02675585,   9.05919732,   9.0916388 , 9.12408027,   9.15652174,   9.18896321,   9.22140468, 9.25384615,   9.28628763,   9.3187291 ,   9.35117057, 9.38361204,   9.41605351,   9.44849498,   9.48093645, 9.51337793,   9.5458194 ,   9.57826087,   9.61070234, 9.64314381,   9.67558528,   9.70802676,   9.74046823, 9.7729097 ,   9.80535117,   9.83779264,   9.87023411, 9.90267559,   9.93511706,   9.96755853,  10.])
y = np.asarray([-0.19056796,  0.13314778,  0.39032789,  0.58490071,  0.72755027, 0.82941604,  0.90008145,  0.94719898,  0.97667411,  0.99299073, 0.99952869,  0.99882928,  0.99280334,  0.98288947,  0.97017273, 0.95547297,  0.93941048,  0.92245495,  0.90496191,  0.88720012, 0.86937208,  0.85162961,  0.83408561,  0.81682308,  0.79990193, 0.78336433,  0.76723876,  0.75154314,  0.7362873 ,  0.72147487, 0.70710477,  0.69317237,  0.67967038,  0.66658956,  0.65391928, 0.64164796,  0.62976339,  0.61825301,  0.60710407,  0.59630386, 0.58583975,  0.57569933,  0.56587047,  0.55634135,  0.5471005 , 0.53813683,  0.52943965,  0.52099866,  0.51280394,  0.50484599, 0.49711569,  0.48960429,  0.48230342,  0.47520507,  0.46830157, 0.4615856 ,  0.45505012,  0.44868845,  0.44249417,  0.43646114, 0.43058352,  0.42485569,  0.4192723 ,  0.41382821,  0.40851854, 0.40333859,  0.39828387,  0.39335008,  0.38853312,  0.38382904, 0.37923407,  0.37474459,  0.37035715,  0.36606841,  0.3618752 , 0.35777446,  0.35376325,  0.34983877,  0.34599831,  0.34223928, 0.33855919,  0.33495564,  0.33142632,  0.32796903,  0.32458163, 0.32126208,  0.3180084 ,  0.3148187 ,  0.31169115,  0.30862399, 0.30561552,  0.30266411,  0.29976818,  0.29692621,  0.29413673, 0.29139834,  0.28870966,  0.28606938,  0.28347622,  0.28092895, 0.27842639,  0.27596739,  0.27355084,  0.27117567,  0.26884083, 0.26654532,  0.26428818,  0.26206846,  0.25988525,  0.25773767, 0.25562487,  0.25354602,  0.25150031,  0.24948698,  0.24750527, 0.24555444,  0.24363379,  0.24174264,  0.23988032,  0.23804617, 0.23623958,  0.234459