import pandas as pd
import numpy as np
from bokeh.io import output_file, show
from bokeh.plotting import figure
from IPython.display import HTML

Introduction to layouts

  • Arranging multiple plots
    • Arrange plots (and controls) visually on a page.
      • rows, columns
      • grid arrangements
      • tabbed layouts
    • Nested layouts
      • Rows and columns can be nested for more sophisticated layouts

Creating rows of plots

Layouts are collections of Bokeh figure objects.

In this exercise, you're going to create two plots from 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.

Remember, as in the previous chapter, once you have created your figures, you can interact with them in various ways.

In this exercise, you may have to scroll sideways to view both figures in the row layout. Alternatively, you can view the figures in a new window by clicking on the expand icon to the right of the "Bokeh plot" tab.

df = pd.read_csv('./dataset/literacy_birth_rate.csv').dropna()
df['fertility'] = df['fertility'].astype('float')
df['female literacy'] = df['female literacy'].astype('float')
from bokeh.plotting import ColumnDataSource

source = ColumnDataSource(df)
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)')

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

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

# Add a circle glyph to p2
p2.circle(x='population', y='female literacy', source=source)

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

# Specify the name of the output_file and show the result
output_file('./html/fert_row.html')
show(layout)
HTML('./html/fert_row.html')
<!DOCTYPE html> Bokeh Plot

Creating columns of plots

In this exercise, you're going to use the column() function to create a single column layout of the two plots you created in the previous exercise.

In this exercise and the ones to follow, you may have to scroll down to view the lower portion of the figure.

from bokeh.layouts import column

source = ColumnDataSource(df)

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

# 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)')

# 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)
output_file('./html/fert_column.html')
show(layout)
HTML('./html/fert_column.html')
<!DOCTYPE html> Bokeh Plot

Nesting rows and columns of plots

You can create nested layouts of plots by combining row and column layouts. In this exercise, you'll make a 3-plot layout in two rows using the auto-mpg data set. Three plots have been created for you of average mpg vs year (avg_mpg), mpg vs hp (mpg_hp), and mpg vs weight (mpg_weight).

Your job is to use the row() and column() functions to make a two-row layout where the first row will have only the average mpg vs year plot and the second row will have mpg vs hp and mpg vs weight plots as columns.

By using the sizing_mode argument, you can scale the widths to fill the whole figure.

df = pd.read_csv('./dataset/auto-mpg.csv').dropna()
df.head()
mpg cyl displ hp weight accel yr origin name color size
0 18.0 6 250.0 88 3139 14.5 71 US ford mustang blue 15.0
1 9.0 8 304.0 193 4732 18.5 70 US hi 1200d blue 20.0
2 36.1 4 91.0 60 1800 16.4 78 Asia honda civic cvcc red 10.0
3 18.5 6 250.0 98 3525 19.0 77 US ford granada blue 15.0
4 34.3 4 97.0 78 2188 15.8 80 Europe audi 4000 green 10.0
mean_mpg = df.groupby('yr').mean()['mpg']
source = ColumnDataSource(df)
avg_mpg = figure(x_axis_label='year', 
            y_axis_label='mean mpg')

avg_mpg.line(mean_mpg.index, mean_mpg);

# Create a blank figure: mpg_hp
mpg_hp = figure(x_axis_label='hp', y_axis_label='mpg')

mpg_hp.circle(x='hp', y='mpg', source=source);

# Create a blank figure: mpg_weight
mpg_weight = figure(x_axis_label='weight', y_axis_label='mpg')

mpg_weight.circle(x='weight', y='mpg', source=source);
row2 = row([mpg_hp, mpg_weight], sizing_mode='scale_width')

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

# Specify the name of the output_file and show the result
output_file('./html/layout_custom.html')
show(layout)
HTML('./html/layout_custom.html')
<!DOCTYPE html> Bokeh Plot

Advanced layouts

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.

df = pd.read_csv('./dataset/literacy_birth_rate.csv').dropna()
df['fertility'] = df['fertility'].astype('float')
df['female literacy'] = df['female literacy'].astype('float')
df['population'] = df['population'] / 1000000
df.head()
Country Continent female literacy fertility population
0 Chine ASI 90.5 1.769 1324.655000
1 Inde ASI 50.8 2.682 1139.964932
2 USA NAM 99.0 2.077 304.060000
3 Indonésie ASI 88.8 2.132 227.345082
4 Brésil LAT 90.2 1.827 191.971506
LAT = df[df['Continent'] == 'LAT']
EUR = df[df['Continent'] == 'EUR']
AF = df[df['Continent'] == 'AF']
ASI = df[df['Continent'] == 'ASI']
p1 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Latin America');

p1.circle(LAT['fertility'], LAT['female literacy']);

p2 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Africa');

p2.circle(AF['fertility'], AF['female literacy']);

p3 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Asia');

p3.circle(ASI['fertility'], ASI['female literacy']);

p4 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Europe');

p4.circle(EUR['fertility'], EUR['female literacy']);
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], plot_width=400, plot_height=400)

# Specify the name of the output_file and show the result
output_file('./html/grid.html')
show(layout)
HTML('./html/grid.html')
<!DOCTYPE html> Bokeh Plot

Starting tabbed layouts

Tabbed layouts can be created in Bokeh by placing plots or layouts in Panels.

In this exercise, you'll take the four fertility vs female literacy plots from the last exercise and make a Panel() for each.

No figure will be generated in this exercise. Instead, you will use these panels in the next exercise to build and display a tabbed layout.

p1 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Latin America');

p1.circle(LAT['fertility'], LAT['female literacy']);

p2 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Africa');

p2.circle(AF['fertility'], AF['female literacy']);

p3 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Asia');

p3.circle(ASI['fertility'], ASI['female literacy']);

p4 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Europe');

p4.circle(EUR['fertility'], EUR['female literacy']);
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')

Displaying tabbed layouts

Tabbed layouts are collections of Panel objects. Using the figures and Panels from the previous two exercises, you'll create a tabbed layout to change the region in the fertility vs female literacy plots.

Your job is to create the layout using Tabs() and assign the tabs keyword argument to your list of Panels. The Panels have been created for you as tab1, tab2, tab3 and tab4.

After you've displayed the figure, explore the tabs you just added! The "Pan", "Box Zoom" and "Wheel Zoom" tools are also all available as before.

from bokeh.models.widgets import Tabs

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

# Specify the name of the output_file and show the result
output_file('./html/tabs.html')
show(layout)
HTML('./html/tabs.html')
<!DOCTYPE html> Bokeh Plot

Linking plots together

Linked axes

Linking axes between plots is achieved by sharing range objects.

In this exercise, you'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.

The four plots p1, p2, p3 and p4 along with the layout that you created in the last section have been provided for you.

Your job is link p1 with the three other plots by assignment of the .x_range and .y_range attributes.

After you have linked the axes, explore the plots by clicking and dragging along the x or y axes of any of the plots, and notice how the linked plots change together.

p1 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Latin America');

p1.circle(LAT['fertility'], LAT['female literacy']);

p2 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Africa');

p2.circle(AF['fertility'], AF['female literacy']);

p3 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Asia');

p3.circle(ASI['fertility'], ASI['female literacy']);

p4 = figure(x_axis_label='fertility (children per woman)', 
            y_axis_label='female literacy (% population)', title='Europe');

p4.circle(EUR['fertility'], EUR['female literacy']);

# 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], plot_width=400, plot_height=400)
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('./html/linked_range.html')
show(layout)
HTML('./html/linked_range.html')
<!DOCTYPE html> Bokeh Plot

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.

In this exercise, you'll plot female literacy vs fertility and population vs fertility in two plots using the same ColumnDataSource.

After you have built the figure, experiment with the Lasso Select and Box Select tools. Use your mouse to drag a box or lasso around points in one figure, and notice how points in the other figure that share a row in the ColumnDataSource also get highlighted.

Before experimenting with the Lasso Select, however, click the Bokeh plot pop-out icon to pop out the figure so that you can definitely see everything that you're doing.

source = ColumnDataSource(df)

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

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

# Create the second figure: p2
p2 = figure(x_axis_label='fertility (children per woman)', y_axis_label='population (millions)',
            tools='box_select,lasso_select')

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

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

# Specify the name of the output_file and show the result
output_file('./html/linked_brush.html')
show(layout)
HTML('./html/linked_brush.html')
<!DOCTYPE html> Bokeh Plot

Annotations and guides

How to create legends

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

In this exercise, you will plot two circle glyphs for female literacy vs fertility in Africa and Latin America.

Two ColumnDataSources called latin_america and africa have been provided.

Your job is to plot two circle glyphs for these two objects with fertility on the x axis and female_literacy on the y axis and add the legend values.

latin_ameria = ColumnDataSource(LAT)
africa = ColumnDataSource(AF)
p = figure(x_axis_label='fertility (children per woman)', y_axis_label='female_literacy (% population)')
p.circle('fertility', 'female literacy', source=latin_ameria, size=10, color='red', 
        legend_label='Latin America');

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

# Specify the name of the output_file and show the result
output_file('./html/fert_lit_groups.html')
show(p)
HTML('./html/fert_lit_groups.html')
<!DOCTYPE html> Bokeh Plot

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.

In this exercise, you'll adjust the background color and legend location of the female literacy vs fertility plot from the previous exercise.

latin_ameria = ColumnDataSource(LAT)
africa = ColumnDataSource(AF)

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

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

# Add the second circle glyph to the figure p
p.circle('fertility', 'female literacy', source=africa, size=10, color='blue', legend_label='Africa');
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('./html/fert_lit_groups2.html')
show(p)
HTML('./html/fert_lit_groups2.html')
<!DOCTYPE html> Bokeh Plot

Adding a hover tooltip

Working with the HoverTool is easy for data stored in a ColumnDataSource.

In this exercise, you will create a HoverTool object and display the country for each circle glyph in the figure that you 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.

After you have added the hover tooltip to the figure, be sure to interact with it by hovering your mouse over each point to see which country it represents.

LAT.loc[:,'Country'] = 'Latin America'
AF.loc[:, 'Country'] = 'Africa'
C:\Users\kcsgo\anaconda3\lib\site-packages\pandas\core\indexing.py:844: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  self.obj[key] = _infer_fill_value(value)
C:\Users\kcsgo\anaconda3\lib\site-packages\pandas\core\indexing.py:965: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  self.obj[item] = s
latin_ameria = ColumnDataSource(LAT)
africa = ColumnDataSource(AF)

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

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

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

# 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'
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)

# Specify the name of the output_file and show the result
output_file('./html/hover.html')
show(p)
HTML('./html/hover.html')
<!DOCTYPE html> Bokeh Plot