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.
Here we'll demonstrate formats we can use with Bokeh, generate some basic plots, and begin customizations for selecting data.
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)
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.
#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)
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.
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)
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)
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.
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)
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)
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)
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)
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.
# 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)
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:
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.from bokeh.models import ColumnDataSource
source = ColumnDataSource(data={
'x': [1,2,3,4,5],
'y': [8,6,5,2,3]})
source.data
{'x': [1, 2, 3, 4, 5], 'y': [8, 6, 5, 2, 3]}
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.
# 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)
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
# 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)
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.
#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)
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.
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)
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.
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.
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)
Here we'll demo how to use the column()
function to create a single column layout of the same two plots as above.
# 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)
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.
#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)
from bokeh.layouts import gridplot
layout = gridplot([[avg_mpg, None], [mpg_weight, mpg_hp]],
toolbar_location=None)
show(layout)
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.
#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.
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.
# 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 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.
# 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)
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.
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)
Legends can be added to any glyph by using the legend
keyword argument.
#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)
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
# 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)
HoverTool
object and display the country for each circle glyph in the figure that we created in the last exercise.tooltips
keyword argument to a list-of-tuples specifying the label and the column of values from the ColumnDataSource using the @
operator.# 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)
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.
outline.py
from bokeh.io import curdoc
# Create plots and widgets
# Add callbacks
# Arrange plots and widgets in layouts
curdoc().add_root(layout)
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/
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.
# 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)
# 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)
A slider example
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
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).
A simple Bokeh application would have a slider, plot, and also updates the plot based on the slider.
#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.23445993, 0.23270663, 0.2309791 , 0.2292768 , 0.22759917, 0.22594568, 0.22431583, 0.22270912, 0.22112506, 0.21956318, 0.21802302, 0.21650414, 0.2150061 , 0.21352848, 0.21207087, 0.21063286, 0.20921408, 0.20781413, 0.20643266, 0.20506929, 0.20372368, 0.20239549, 0.20108438, 0.19979003, 0.19851212, 0.19725034, 0.19600439, 0.19477398, 0.19355882, 0.19235862, 0.19117313, 0.19000206, 0.18884517, 0.18770219, 0.18657288, 0.18545699, 0.1843543 , 0.18326456, 0.18218756, 0.18112306, 0.18007087, 0.17903076, 0.17800253, 0.17698598, 0.17598091, 0.17498713, 0.17400446, 0.1730327 , 0.17207168, 0.17112122, 0.17018115, 0.1692513 , 0.16833151, 0.16742161, 0.16652145, 0.16563087, 0.16474972, 0.16387786, 0.16301513, 0.16216139, 0.16131651, 0.16048035, 0.15965278, 0.15883366, 0.15802286, 0.15722027, 0.15642575, 0.15563919, 0.15486047, 0.15408947, 0.15332608, 0.15257018, 0.15182167, 0.15108044, 0.15034639, 0.14961941, 0.14889939, 0.14818625, 0.14747988, 0.14678019, 0.14608708, 0.14540046, 0.14472024, 0.14404634, 0.14337866, 0.14271712, 0.14206163, 0.14141212, 0.1407685 , 0.14013069, 0.13949862, 0.1388722 , 0.13825137, 0.13763605, 0.13702616, 0.13642163, 0.1358224 , 0.13522839, 0.13463954, 0.13405578, 0.13347705, 0.13290327, 0.1323344 , 0.13177035, 0.13121109, 0.13065653, 0.13010663, 0.12956133, 0.12902056, 0.12848428, 0.12795242, 0.12742494, 0.12690177, 0.12638288, 0.12586819, 0.12535768, 0.12485127, 0.12434893, 0.12385061, 0.12335625, 0.12286581, 0.12237925, 0.12189652, 0.12141757, 0.12094236, 0.12047084, 0.12000298, 0.11953873, 0.11907805, 0.1186209 , 0.11816724, 0.11771703, 0.11727022, 0.11682679, 0.11638669, 0.11594988, 0.11551634, 0.11508601, 0.11465888, 0.11423489, 0.11381403, 0.11339624, 0.11298151, 0.11256979, 0.11216106, 0.11175527, 0.11135241, 0.11095243, 0.11055531, 0.11016102, 0.10976953, 0.1093808 , 0.10899481, 0.10861153, 0.10823093, 0.10785298, 0.10747766, 0.10710493, 0.10673478, 0.10636717, 0.10600208, 0.10563948, 0.10527936, 0.10492167, 0.1045664 , 0.10421352, 0.10386302, 0.10351486, 0.10316902, 0.10282548, 0.10248422, 0.10214521, 0.10180843, 0.10147386, 0.10114148, 0.10081127, 0.1004832 , 0.10015726, 0.09983342])
# Create plots and widgets
plot = figure()
slider = Slider(start=1, end=10, value=1,
step=1, title='scale')
# Create ColumnDataSource: source
source = ColumnDataSource(data = {'x': x, 'y': y})
# Add a line to the plot
plot.line('x', 'y', source=source)
# Create a column layout: layout
layout = column(widgetbox(slider), plot)
# Add the layout to the current document
curdoc().add_root(layout)
show(layout)
Since a widget callback wasn't been defined here, the slider does not update the figure.
Most of the remaining code in this notebook needs to be run from a bokeh server to be interactive. Static images will be created in this notebook but pasting the code and running bokeh serve --show file_name.py
will generate an interactive plot.
We'll use the slider's on_change()
function to update the plot's data from the previous demo.
#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.23445993, 0.23270663, 0.2309791 , 0.2292768 , 0.22759917, 0.22594568, 0.22431583, 0.22270912, 0.22112506, 0.21956318, 0.21802302, 0.21650414, 0.2150061 , 0.21352848, 0.21207087, 0.21063286, 0.20921408, 0.20781413, 0.20643266, 0.20506929, 0.20372368, 0.20239549, 0.20108438, 0.19979003, 0.19851212, 0.19725034, 0.19600439, 0.19477398, 0.19355882, 0.19235862, 0.19117313, 0.19000206, 0.18884517, 0.18770219, 0.18657288, 0.18545699, 0.1843543 , 0.18326456, 0.18218756, 0.18112306, 0.18007087, 0.17903076, 0.17800253, 0.17698598, 0.17598091, 0.17498713, 0.17400446, 0.1730327 , 0.17207168, 0.17112122, 0.17018115, 0.1692513 , 0.16833151, 0.16742161, 0.16652145, 0.16563087, 0.16474972, 0.16387786, 0.16301513, 0.16216139, 0.16131651, 0.16048035, 0.15965278, 0.15883366, 0.15802286, 0.15722027, 0.15642575, 0.15563919, 0.15486047, 0.15408947, 0.15332608, 0.15257018, 0.15182167, 0.15108044, 0.15034639, 0.14961941, 0.14889939, 0.14818625, 0.14747988, 0.14678019, 0.14608708, 0.14540046, 0.14472024, 0.14404634, 0.14337866, 0.14271712, 0.14206163, 0.14141212, 0.1407685 , 0.14013069, 0.13949862, 0.1388722 , 0.13825137, 0.13763605, 0.13702616, 0.13642163, 0.1358224 , 0.13522839, 0.13463954, 0.13405578, 0.13347705, 0.13290327, 0.1323344 , 0.13177035, 0.13121109, 0.13065653, 0.13010663, 0.12956133, 0.12902056, 0.12848428, 0.12795242, 0.12742494, 0.12690177, 0.12638288, 0.12586819, 0.12535768, 0.12485127, 0.12434893, 0.12385061, 0.12335625, 0.12286581, 0.12237925, 0.12189652, 0.12141757, 0.12094236, 0.12047084, 0.12000298, 0.11953873, 0.11907805, 0.1186209 , 0.11816724, 0.11771703, 0.11727022, 0.11682679, 0.11638669, 0.11594988, 0.11551634, 0.11508601, 0.11465888, 0.11423489, 0.11381403, 0.11339624, 0.11298151, 0.11256979, 0.11216106, 0.11175527, 0.11135241, 0.11095243, 0.11055531, 0.11016102, 0.10976953, 0.1093808 , 0.10899481, 0.10861153, 0.10823093, 0.10785298, 0.10747766, 0.10710493, 0.10673478, 0.10636717, 0.10600208, 0.10563948, 0.10527936, 0.10492167, 0.1045664 , 0.10421352, 0.10386302, 0.10351486, 0.10316902, 0.10282548, 0.10248422, 0.10214521, 0.10180843, 0.10147386, 0.10114148, 0.10081127, 0.1004832 , 0.10015726, 0.09983342])
# Create plots and widgets
plot = figure()
slider = Slider(start=1, end=10, value=1,
step=1, title='scale')
# Create ColumnDataSource: source
source = ColumnDataSource(data = {'x': x, 'y': y})
# Add a line to the plot
plot.line('x', 'y', source=source)
# Define a callback function: callback
def callback(attr, old, new):
# Read the current value of the slider: scale
scale = slider.value
# Compute the updated y using np.sin(scale/x): new_y
new_y = np.sin(scale/x)
# Update source with the new data values
source.data = {'x': x, 'y': new_y}
# Attach the callback to the 'value' property of slider
slider.on_change('value', callback)
# Create layout and add to current document
layout = column(widgetbox(slider), plot)
# Add the layout to the current document
curdoc().add_root(layout)
show(layout) #for jupyter display purposes
#copy paste into script file script.py, point conda or python command line to the directory, and run
#bokeh serve --show script.py
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
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Select
from bokeh.plotting import figure
from numpy.random import random, normal, lognormal
N = 1000
source = ColumnDataSource(data={'x': random(N), 'y': random(N)})
# Create plots and widgets
plot = figure()
plot.circle(x='x', y='y', source=source)
menu = Select(options=['uniform', 'normal', 'lognormal'],
value='uniform', title='Distribution')
# Add callback to widgets
def callback(attr, old, new):
if menu.value == 'uniform': f = random
elif menu.value == 'normal': f = normal
else: f = lognormal
source.data={'x': f(size=N), 'y': f(size=N)}
menu.on_change('value', callback)
# Arrange plots and widgets in layouts
layout = column(menu, 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
Here, we'll update the plot's data using a drop down menu instead of a slider. This would allow users to do things like select between different data sources to view.
#Imports
import pandas as pd
from bokeh.io import curdoc
from bokeh.layouts import column, row
from bokeh.plotting import figure
df = pd.read_csv('data/literacy_birth_rate.csv', nrows = 162)
fertility = df.fertility
female_literacy = df['female literacy']
population = df.population
# Perform necessary imports
from bokeh.models import ColumnDataSource, Select
# Create ColumnDataSource: source
source = ColumnDataSource(data={
'x' : fertility,
'y' : female_literacy
})
# Create a new plot: plot
plot = figure()
# Add circles to the plot
plot.circle('x', 'y', source=source)
# Define a callback function: update_plot
def update_plot(attr, old, new):
# If the new Selection is 'female_literacy', update 'y' to female_literacy
if new == 'female_literacy':
source.data = {
'x' : fertility,
'y' : female_literacy
}
# Else, update 'y' to population
else:
source.data = {
'x' : fertility,
'y' : population
}
# Create a dropdown Select widget: select
select = Select(title="distribution", options=['female_literacy', 'population'], value='female_literacy')
# Attach the update_plot callback to the 'value' property of select
select.on_change('value', update_plot)
# Create layout and add to current document
layout = row(select, plot)
curdoc().add_root(layout)
show(layout)
#copy paste into script file script.py, point conda or python command line to the directory, and run
#bokeh serve --show script.py
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
Menus can be even more powerful by changing the choices of one via the selection of another.
Here, we'll practice using a dropdown callback to update another dropdown's options.
# Create two dropdown Select widgets: select1, select2
select1 = Select(title='First', options=['A', 'B'], value='A')
select2 = Select(title='Second', options=['1', '2', '3'], value='1')
# Define a callback function: callback
def callback(attr, old, new):
# If select1 is 'A'
if select1.value == 'A':
# Set select2 options to ['1', '2', '3']
select2.options = ['1', '2', '3']
# Set select2 value to '1'
select2.value = '1'
else:
# Set select2 options to ['100', '200', '300']
select2.options = ['100', '200', '300']
# Set select2 value to '100'
select2.value = '100'
# Attach the callback to the 'value' property of select1
select1.on_change('value', callback)
# Create layout and add to current document
layout = widgetbox(select1, select2)
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
select.py
from bokeh.models import Button
button = Button(label='press me')
def update():
# Do something interesting
button.on_click(update)
from bokeh.models import CheckboxGroup, RadioGroup, Toggle
toggle = Toggle(label='Some on/off', button_type='success')
checkbox = CheckboxGroup(labels=['Who', 'knows', 'what'])
radio = RadioGroup(labels=['2000', '2010', '2020'])
def callback(active):
# Active tells which button is active
Here, we'll create a button and use its on_click()
method to update a plot.
from bokeh.models import Button
# Create a Button with label 'Update Data'
button = Button(label = 'Update Data')
# Define an update callback with no arguments: update
def update():
# Compute new y values: y
y = np.sin(x) + np.random.random(N)
# Update the ColumnDataSource data dictionary
source.data = {'x':x, 'y':y}
# Add the update callback to the button
button.on_click(update)
# Create layout and add to current document
layout = column(widgetbox(button), 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
Here, we'll practice using CheckboxGroup, RadioGroup, and Toggle to add multiple Button
widgets with different styles.
# Import CheckboxGroup, RadioGroup, Toggle from bokeh.models
from bokeh.models import CheckboxGroup, RadioGroup, Toggle
# Add a Toggle: toggle
toggle = Toggle(button_type='success', label='Toggle button')
# Add a CheckboxGroup: checkbox
checkbox = CheckboxGroup(labels=['Option 1', 'Option 2', 'Option 3'])
# Add a RadioGroup: radio
radio = RadioGroup(labels=['Option 1', 'Option 2', 'Option 3'])
# Add widgetbox(toggle, checkbox, radio) to the current document
curdoc().add_root(widgetbox(toggle, checkbox, radio))
https://anaconda.org provides some resources for hosting environments, jupyter notebooks and scripts.
In [1]: data.head()
Out[1]:
Country fertility life population child_mortality gdp \
Year
1964 Afghanistan 7.671 33.639 10474903.0 339.7 1182.0
1965 Afghanistan 7.671 34.152 10697983.0 334.1 1182.0
1966 Afghanistan 7.671 34.662 10927724.0 328.7 1168.0
1967 Afghanistan 7.671 35.170 11163656.0 323.3 1173.0
1968 Afghanistan 7.671 35.674 11411022.0 318.1 1187.0
region
South Asia
South Asia
South Asia
South Asia
South Asia
It is always a good idea to begin with some Exploratory Data Analysis. Pandas has a number of built-in methods that help with this. For example, data.head()
displays the first five rows/entries of data
, while data.tail()
displays the last five rows/entries. data.shape
gives you information about how many rows and columns there are in the data set. Another particularly useful method is data.info()
, which provides a concise summary of data
, including information about the number of entries, columns, data type of each column, and number of non-null entries in each column.
data = pd.read_csv('data/gapminder_tidy.csv', index_col = 1)
print(data.head())
print(data.tail())
print(data.shape)
print(data.info())
Country fertility life population child_mortality gdp \ Year 1964 Afghanistan 7.671 33.639 10474903.0 339.7 1182.0 1965 Afghanistan 7.671 34.152 10697983.0 334.1 1182.0 1966 Afghanistan 7.671 34.662 10927724.0 328.7 1168.0 1967 Afghanistan 7.671 35.170 11163656.0 323.3 1173.0 1968 Afghanistan 7.671 35.674 11411022.0 318.1 1187.0 region Year 1964 South Asia 1965 South Asia 1966 South Asia 1967 South Asia 1968 South Asia Country fertility life population child_mortality gdp \ Year 2002 Åland NaN 81.80 26257.0 NaN NaN 2003 Åland NaN 80.63 26347.0 NaN NaN 2004 Åland NaN 79.88 26530.0 NaN NaN 2005 Åland NaN 80.00 26766.0 NaN NaN 2006 Åland NaN 80.10 26923.0 NaN NaN region Year 2002 Europe & Central Asia 2003 Europe & Central Asia 2004 Europe & Central Asia 2005 Europe & Central Asia 2006 Europe & Central Asia (10111, 7) <class 'pandas.core.frame.DataFrame'> Int64Index: 10111 entries, 1964 to 2006 Data columns (total 7 columns): Country 10111 non-null object fertility 10100 non-null float64 life 10111 non-null float64 population 10108 non-null float64 child_mortality 9210 non-null float64 gdp 9000 non-null float64 region 10111 non-null object dtypes: float64(5), object(2) memory usage: 631.9+ KB None
# Perform necessary imports
from bokeh.io import output_file, show
from bokeh.plotting import figure
from bokeh.models import HoverTool, ColumnDataSource
# Make the ColumnDataSource: source
source = ColumnDataSource(data={
'x' : data.loc[1970].fertility,
'y' : data.loc[1970].life,
'country' : data.loc[1970].Country,
'region' : data.loc[1970].region
})
# Create the figure: p
plot = figure(title='1970', x_axis_label='Fertility (children per woman)', y_axis_label='Life Expectancy (years)',
plot_height=400, plot_width=700,
tools=[HoverTool(tooltips='@country')])
# Add a circle glyph to the figure p
plot.circle(x='x', y='y', source=source)
# Show the figure
show(plot)
To start building the plot, we'll make the ColumnDataSource
object, prepare the plot, and add circles for Life expectancy vs Fertility.
We'll also set x and y ranges for the axes.
# Import the necessary modules
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
# Add the plot to the current document and add a title
curdoc().add_root(plot)
curdoc().title = 'Gapminder'
show(plot)
# Make a list of the unique values from the region column: regions_list
regions_list = data.region.unique().tolist()
# Import CategoricalColorMapper from bokeh.models and the Spectral6 palette from bokeh.palettes
from bokeh.models import CategoricalColorMapper
from bokeh.palettes import Spectral6
# Make a color mapper: color_mapper
color_mapper = CategoricalColorMapper(factors=regions_list, palette=Spectral6)
# Add the color mapper to the circle glyph
plot.circle(x='x', y='y', fill_alpha=0.8, source=source,
color=dict(field='region', transform =color_mapper), legend='region')
# Set the legend.location attribute of the plot to 'top_right'
plot.legend.location = 'bottom_left'
# Add the plot to the current document and add the title
curdoc().add_root(plot)
curdoc().title = 'Gapminder'
show(plot)
The plot provides a lot more information now that we have added the shading.
We'll now add an interactive slider to control the year.
We'll move now from just displaying 1970 data to any year in the dataset.
Recall that sliders require a callback function, in this case, update_plot
, and link it to plot values.
We'll also update the plot name with the current selected year.
# Import the necessary modules
from bokeh.layouts import widgetbox, row
from bokeh.models import Slider
# Define the callback function: update_plot
def update_plot(attr, old, new):
# Assign the value of the slider: yr
yr = slider.value
# Set new_data
new_data = {
'x' : data.loc[yr].fertility,
'y' : data.loc[yr].life,
'country' : data.loc[yr].Country,
'pop' : (data.loc[yr].population / 20000000) + 2,
'region' : data.loc[yr].region,
}
# Assign new_data to: source.data
source.data = new_data
# Add title to figure: plot.title.text
plot.title.text = 'Gapminder data for %d' % yr
# Make a slider object: slider
slider = Slider(start = 1970, end = 2010, step = 1, value = 1970, title = 'Year')
# Attach the callback to the 'value' property of slider
slider.on_change('value', update_plot)
# Make a row layout of widgetbox(slider) and plot and add it to the current document
layout = row(widgetbox(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
# Import HoverTool from bokeh.models
from bokeh.models import HoverTool
# Create a HoverTool: hover
hover = HoverTool(tooltips = [('Country', '@country')])
# Add the HoverTool to the plot
plot.add_tools(hover)
# Create layout: layout
layout = row(widgetbox(slider),plot)
# Add layout to current document
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
As a final step in enhancing our application, we'll add dropdowns for interactively selecting different data features. Combined with the hover tool and sliders added in the previous sections, we'll have a powerful app that allows the user to interactively and quickly extract insights from the dataset!
# Define the callback: update_plot
def update_plot(attr, old, new):
# Read the current value off the slider and 2 dropdowns: yr, x, y
yr = slider.value
x = x_select.value
y = y_select.value
# Label axes of plot
plot.xaxis.axis_label = x
plot.yaxis.axis_label = y
# Set new_data
new_data = {
'x' : data.loc[yr][x],
'y' : data.loc[yr][y],
'country' : data.loc[yr].Country,
'pop' : (data.loc[yr].population / 20000000) + 2,
'region' : data.loc[yr].region,
}
# Assign new_data to source.data
source.data = new_data
# Set the range of all axes
plot.x_range.start = min(data[x])
plot.x_range.end = max(data[x])
plot.y_range.start = min(data[y])
plot.y_range.end = max(data[y])
# Add title to plot
plot.title.text = 'Gapminder data for %d' % yr
# Create a dropdown slider widget: slider
slider = Slider(start=1970, end=2010, step=1, value=1970, title='Year')
# Attach the callback to the 'value' property of slider
slider.on_change('value', update_plot)
# Create a dropdown Select widget for the x data: x_select
x_select = Select(
options=['fertility', 'life', 'child_mortality', 'gdp'],
value='fertility',
title='x-axis data'
)
# Attach the update_plot callback to the 'value' property of x_select
x_select.on_change('value', update_plot)
# Create a dropdown Select widget for the y data: y_select
y_select = Select(
options=['fertility', 'life', 'child_mortality', 'gdp'],
value='life',
title='y-axis data'
)
# Attach the update_plot callback to the 'value' property of y_select
y_select.on_change('value', update_plot)
# Create layout and add to current document
layout = row(widgetbox(slider, x_select, y_select), 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
As a final demonstration, the code below can be copied into normal python script and executued with the bokeh serve --show file_name.py
or see a non-interactive version at the bottom.
"""
Created on Wed Aug 7 17:06:33 2019
#point conda or python command line to the directory, and run
#bokeh serve --show gapminder_script.py
#ctrol-c to interrupt server
@author: Robert Piazza
"""
#Imports
import pandas as pd
from bokeh.io import curdoc, output_file, show
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, Select, HoverTool, CategoricalColorMapper, Slider, RadioGroup
from bokeh.plotting import figure
from bokeh.layouts import widgetbox
from bokeh.palettes import Spectral6
data = pd.read_csv('data/gapminder_tidy.csv', index_col = 1)
# Make the ColumnDataSource: source
source = ColumnDataSource(data={
'x' : data.loc[1970].fertility,
'y' : data.loc[1970].life,
'country' : data.loc[1970].Country,
's' : (data.loc[1970].population / 10000000) + 2,
'size' : (data.loc[1970].population),
'region' : data.loc[1970].region,
})
# Save the minimum and maximum values of the fertility column: xmin, xmax
xmin, xmax = min(data.fertility), max(data.fertility)
# Save the minimum and maximum values of the life expectancy column: ymin, ymax
ymin, ymax = min(data.life), max(data.life)
# Create the figure: plot
plot = figure(title='Gapminder Data for 1970', plot_height=400, plot_width=700,
x_range=(xmin, xmax), y_range=(ymin, ymax), tools='box_select, lasso_select, pan, box_zoom, wheel_zoom, reset, help')
# Set the x-axis label
plot.xaxis.axis_label ='Fertility (children per woman)'
# Set the y-axis label
plot.yaxis.axis_label = 'Life Expectancy (years)'
axis_dict = {'life':'Life Expectancy at birth (years)',
'fertility': 'Children per woman(total fertility)',
'child_mortality': 'Child Mortality (0-5 year-olds dying per 1000 born)',
'gdp': 'GDP (Gross Domestic Product)',
'pop': 'Population'}
# Make a list of the unique values from the region column: regions_list
regions_list = data.region.unique().tolist()
# Make a color mapper: color_mapper
color_mapper = CategoricalColorMapper(factors=regions_list, palette=Spectral6)
# Add the color mapper to the circle glyph
plot.circle(x='x', y='y', size = 's', fill_alpha=0.8, source=source,
color=dict(field='region', transform =color_mapper), legend='region')
# Set the legend.location attribute of the plot to 'top_right' (default)
plot.legend.location = 'top_right'
# Add the plot to the current document and add the title
curdoc().title = 'Gapminder'
# Create a HoverTool: hover
hover = HoverTool(tooltips = [('Country', '@country'),
('Population', '@size')])
# Add the HoverTool to the plot
plot.add_tools(hover)
# Define the callback: update_plot
def update_plot(attr, old, new):
"""This will update the plot"""
# Read the current value off the slider and 2 dropdowns: yr, x, y
yr = slider.value
x = x_select.value
y = y_select.value
r = r_select.value
region_choose = (data.index == yr) & (data.region == r)
if r =='all':
s_data = (data.loc[yr]['population']/10000000)+2
size_data = data.loc[yr]['population']
else:
s_data = (data.loc[region_choose]['population']/10000000)+2
size_data = data.loc[region_choose]['population']
# Label axes of plot
plot.xaxis.axis_label = axis_dict[x]
plot.yaxis.axis_label = axis_dict[y]
# Set new_data
if r == 'all':
new_data = {
'x' : data.loc[yr][x],
'y' : data.loc[yr][y],
'country' : data.loc[yr].Country,
's' : s_data,
'size' : size_data,
'region' : data.loc[yr].region,
}
else:
new_data = {
'x' : data.loc[region_choose][x],
'y' : data.loc[region_choose][y],
'country' : data.loc[region_choose].Country,
's' : s_data,
'size' : size_data,
'region' : data.loc[region_choose].region,
}
# Assign new_data to source.data
source.data = new_data
# Set the range of all axes
plot.x_range.start = min(data[x])
plot.x_range.end = max(data[x])
plot.y_range.start = min(data[y])
plot.y_range.end = max(data[y])
# Add title to plot
plot.title.text = 'Gapminder data for %d' % yr
def update_legend(attr, old, new):
"""This will update the legend"""
legend_locs = ["top_left","top_right", "bottom_left", "bottom_right"]
l = l_select.active
plot.legend.location = legend_locs[l]
# Create a dropdown slider widget: slider
slider = Slider(start=1970, end=2010, step=1, value=1970, title='Year')
# Attach the callback to the 'value' property of slider
slider.on_change('value', update_plot)
# Create a dropdown Select widget for the x data: x_select
x_select = Select(
options=['fertility', 'life', 'child_mortality', 'gdp'],
value='fertility',
title='x-axis data'
)
# Attach the update_plot callback to the 'value' property of x_select
x_select.on_change('value', update_plot)
# Create a dropdown Select widget for the y data: y_select
y_select = Select(
options=['fertility', 'life', 'child_mortality', 'gdp'],
value='life',
title='y-axis data'
)
# Attach the update_plot callback to the 'value' property of y_select
y_select.on_change('value', update_plot)
legend_locs = ["top_left","top_right", "bottom_left", "bottom_right"]
# Create a radioGroup widget for the legend : l_select
l_select = RadioGroup(labels=legend_locs,
active = 1,
name = 'Legend Location')
# Attach the update_plot callback to the 'value' property of r_select (region)
l_select.on_change('active', update_legend)
# Create a dropdown Select widget for the y data: y_select
r_select = Select(
options=['all']+regions_list,
value='all',
title='Region'
)
# Attach the update_plot callback to the 'value' property of y_select
r_select.on_change('value', update_plot)
# Create layout and add to current document
layout = row(widgetbox(slider, x_select, y_select, r_select, l_select), plot)
#output_file('gapminder.html')
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