Bokeh is a Python data visualization library that is based on javascript. It provides easy to use API to create various interactive visualizations. Bokeh has matured over the years and also provides dashboarding functionality as a part of API. We'll be using the bokeh library as a part of this tutorial to create a simple dashboard with widgets. The main aim of this tutorial is to let individuals get started with the basics of dashboarding with bokeh.
Below is a list of steps that will be followed to create a dashboard using bokeh.
import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook, curdoc
from bokeh.plotting import figure
from bokeh.layouts import row, column
from bokeh.resources import INLINE
output_notebook(resources=INLINE)
We'll be using below mentioned two datasets for building three charts of our dashboard.
We'll be keeping each dataset as a pandas dataframe as explained below.
from sklearn.datasets import load_iris
iris = load_iris()
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df["FlowerType"] = iris.target
iris_df.head()
from bokeh.sampledata.stocks import GOOG as google
google_df = pd.DataFrame(google)
google_df["date"] = pd.to_datetime(google_df["date"])
google_df.head()
We'll first create each chart as an individual for explanation purposes. We'll be creating three charts in total (One Line Chart, One Scatter Chart, One Bar Chart). We'll keep the line chart in the first row of the dashboard and the scatter chart & bar chart in the second row of the dashboard.
The first chart that we'll create using bokeh glyphs is a line chart of google stock price data loaded earlier. The line chart will show dates on X-axis and OHLC price on Y-axis. If you do not have a background on bokeh plotting and want to learn bokeh plotting then please feel free to go through our tutorials on bokeh to get going with bokeh.
line_chart = figure(plot_width=1000, plot_height=400, x_axis_type="datetime",
title="Google Stock Prices from 2005 - 2013")
line_chart.line(
x="date", y="open",
line_width=0.5, line_color="dodgerblue",
legend_label = "open",
source=google_df
)
line_chart.xaxis.axis_label = 'Time'
line_chart.yaxis.axis_label = 'Price ($)'
line_chart.legend.location = "top_left"
show(line_chart)
The second chart that we'll include in our dashboard is scatter plot which shows the relationship between two attributes of the IRIS flower dataset. Each point of the scatter chart is also color-encoded according to flower type.
scatter = figure(plot_width=500, plot_height=400,
title="Sepal Length vs Sepal Width Scatter Plot")
color_mapping = {0:"tomato", 1:"dodgerblue", 2:"lime"}
for cls in [0,1,2]:
scatter.circle(x=iris_df[iris_df["FlowerType"]==cls]["sepal length (cm)"],
y=iris_df[iris_df["FlowerType"]==cls]["sepal width (cm)"],
color=color_mapping[cls],
size=10,
alpha=0.8,
legend_label=iris.target_names[cls])
scatter.xaxis.axis_label= "sepal length (cm)".upper()
scatter.yaxis.axis_label= "sepal width (cm)".upper()
show(scatter)
The third chart that we'll include in our dashboard is a bar chart showing average flower measurement per flower type. Each bar will represent the average value of a particular measurement for a particular category of flower type.
iris_avg_by_flower_type = iris_df.groupby(by="FlowerType").mean()
bar_chart = figure(plot_width=500, plot_height=400,
title="Average Sepal Length (cm) per Flower Type")
bar_chart.vbar(x = [1,2,3],
width=0.9,
top=iris_avg_by_flower_type["sepal length (cm)"],
fill_color="tomato", line_color="tomato", alpha=0.9)
bar_chart.xaxis.axis_label="FlowerType"
bar_chart.yaxis.axis_label="Sepal Length"
bar_chart.xaxis.ticker = [1, 2, 3]
bar_chart.xaxis.major_label_overrides = {1: 'Setosa', 2: 'Versicolor', 3: 'Virginica'}
show(bar_chart)
Below we are creating the layout of how our dashboard charts will be laid out. The bokeh.layouts
module provides various methods which let us layout charts according to our need. We have used row()
and column()
methods to create dashboard layout. We can pass a list of charts to a method and it'll layout charts in a row/column.
If you are interested in learning about laying out charts using bokeh then please feel free to look at our tutorial on the same as it'll help you understand about layouts available in bokeh.
layout = column(line_chart, row(scatter, bar_chart))
show(layout)
We'll be creating 4 widgets to be included in our chart.
The bokeh.models
module provides a list of classes for creating various widgets. The CheckboxButtonGroup
class lets us create a checkbox group widget. We need to pass a list of labels to be displayed in the group as well as the active button name. We can set the styling of a button by setting the button_type
attribute of the method.
from bokeh.models import CheckboxButtonGroup
checkbox_options = ['open','high','low','close']
checkbox_grp = CheckboxButtonGroup(labels=checkbox_options, active=[0], button_type="success")
show(checkbox_grp)
We can create dropdowns using the Select
method of bokeh.models
. We need to pass option names as options
parameter and selected value as the value
parameter. If we want to include a label with dropdown then we can pass a string to the title
parameter and it'll add a label to the dropdown.
from bokeh.models import Select
drop_scat1 = Select(title="X-Axis-Dim",
options=iris.feature_names,
value=iris.feature_names[0],
width=200)
drop_scat2 = Select(title="Y-Axis-Dim",
options=iris.feature_names,
value=iris.feature_names[1],
width=200)
show(row(drop_scat1, drop_scat2))
Below we are creating a dropdown for bar chart using the Select()
method.
drop_bar = Select(title="Dimension", options=iris.feature_names, value=iris.feature_names[0])
show(drop_bar)
Below we are again creating the layout of the whole dashboard but this time with widgets included in the dashboard as well. We have used the row()
and column()
method one inside another to create the layout of the dashboard.
layout_with_widgets = column(
column(checkbox_grp, line_chart),
row(
column(row(drop_scat1, drop_scat2), scatter),
column(drop_bar, bar_chart)))
show(layout_with_widgets)
Once we have created the layout object of the dashboard then we can access an individual elements of the dashboard by using the children
attribute and indexing. Below we are accessing 1st children of the dashboard which is 2nd element of our dashboard.
We can modify individual charts by using this kind of indexing and accessing individual charts. We'll be doing exactly that below when we write callbacks to change charts based on the change in the state of the widget.
show(layout_with_widgets.children[1])
Below we are further accessing children 0 of 2nd element of the dashboard which is scatter chart with widgets.
show(layout_with_widgets.children[1].children[0])
We'll now create callbacks for our dashboard which will be functions that will be called when any change happens to the state of widgets. We'll then register a callback with the particular widget by using the on_change()
method of widget passing it attribute of the widget to monitor as the first argument and a callback function as the second argument.
Below we are creating the first callback which gets called when any changes to the checkbox group happen. All callbacks have the same function signature which requires passing attribute name, old value, and new value as three parameters.
We have created below callback named update_line_chart()
which takes the new value of the checkbox group and creates a line for each selected value. It then set this newly created chart as the value of that component of a dashboard using indexing which we explained in the previous step.
We also have registered this callback with the checkbox group widget by passing it to the monitor active
attribute as the first parameter and the callback function name as the second parameter. This registration will make sure that callback gets called each time changes to the state of the widget happens. It'll also pass the attribute name, old values, and new values to the callback function.
def update_line_chart(attrname, old, new):
'''
Code to update Line Chart as Per Check Box Selection
'''
line_chart = figure(plot_width=1000, plot_height=400, x_axis_type="datetime",
title="Google Stock Prices from 2005 - 2013")
price_color_map = {"open":"dodgerblue", "close":"tomato", "low":"lime", "high":"orange"}
for option in checkbox_grp.active:
line_chart.line(
x="date", y=checkbox_options[option],
line_width=0.5, line_color=price_color_map[checkbox_options[option]],
legend_label=checkbox_options[option],
source=google_df
)
line_chart.xaxis.axis_label = 'Time'
line_chart.yaxis.axis_label = 'Price ($)'
line_chart.legend.location = "top_left"
layout_with_widgets.children[0].children[1] = line_chart
checkbox_grp.on_change("active", update_line_chart)
The second callback that we are creating will be used to update the scatter chart based on option selection in two dropdowns created for it. It'll also follow the same logic as a previous callback which will create a new chart based on values of dropdowns and will set it as a component of a dashboard using indexing.
Please make a note that we have registered the same method for both dropdowns. If you need to perform different functions for each dropdown then you can create a different function for each.
def update_scatter(attrname, old, new):
'''
Code to update Scatter Chart as Per Dropdown Selections
'''
scatter = figure(plot_width=500, plot_height=400,
title="%s vs %s Scatter Plot"%(drop_scat1.value.upper(), drop_scat2.value.upper()))
for cls in [0,1,2]:
scatter.circle(x=iris_df[iris_df["FlowerType"]==cls][drop_scat1.value],
y=iris_df[iris_df["FlowerType"]==cls][drop_scat2.value],
color=color_mapping[cls],
size=10,
alpha=0.8,
legend_label=iris.target_names[cls])
scatter.xaxis.axis_label= drop_scat1.value.upper()
scatter.yaxis.axis_label= drop_scat2.value.upper()
layout_with_widgets.children[1].children[0].children[1] = scatter
drop_scat1.on_change("value", update_scatter)
drop_scat2.on_change("value", update_scatter)
Below we are creating a callback for a dropdown of the bar chart. We are using the same logic as previous callbacks where we create a new chart based on dropdown selection and then replace the old chart with the new chart by using indexing.
We also have a registered callback function with a widget using the on_change()
method passing it value
attribute as the first parameter and a callback function as the second parameter.
def update_bar_chart(attrname, old, new):
'''
Code to Update Bar Chart as Per Dropdown Selections
'''
bar_chart = figure(plot_width=500, plot_height=400,
title="Average %s per Flower Type"%drop_bar.value.upper())
bar_chart.vbar(x = [1,2,3],
width=0.9,
top=iris_avg_by_flower_type[drop_bar.value],
fill_color="tomato", line_color="tomato", alpha=0.6)
bar_chart.xaxis.axis_label="FlowerType"
bar_chart.yaxis.axis_label=drop_bar.value.upper()
bar_chart.xaxis.ticker = [1, 2, 3]
bar_chart.xaxis.major_label_overrides = {1: 'Setosa', 2: 'Versicolor', 3: 'Virginica'}
layout_with_widgets.children[1].children[1].children[1] = bar_chart
drop_bar.on_change("value", update_bar_chart)
This ends our individual step by step guide to creating a dashboard. We'll now put it all together to create the whole dashboard.
Below we have put together code created using each individual step. In order to create a dashboard, we need to use the curdoc()
method to create a dashboard. We will call the add_root()
method on curdoc()
and pass it out dashboard layout object created to it. We can save the below code to python file and bring the dashboard up as explained in the next step.
import pandas as pd
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.layouts import row, column
from bokeh.models import Select, CheckboxButtonGroup ### Widgets
### Dataset Imports
from bokeh.sampledata.stocks import GOOG as google
from sklearn.datasets import load_iris, load_wine
checkbox_options = ['open','high','low','close']
color_mapping = {0:"tomato", 1:"dodgerblue", 2:"lime"}
price_color_map = {"open":"dodgerblue", "close":"tomato", "low":"lime", "high":"orange"}
#### IRIS Dataset Loading #####
iris = load_iris()
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df["FlowerType"] = iris.target
### Google Price Dataset Loading ##############
google_df = pd.DataFrame(google)
google_df["date"] = pd.to_datetime(google_df["date"])
### Line Chart of Google Prices Code Starts ###########
line_chart = figure(plot_width=1000, plot_height=400, x_axis_type="datetime",
title="Google Stock Prices from 2005 - 2013")
line_chart.line(
x="date", y="open",
line_width=0.5, line_color="dodgerblue",
legend_label = "open",
source=google_df
)
line_chart.xaxis.axis_label = 'Time'
line_chart.yaxis.axis_label = 'Price ($)'
line_chart.legend.location = "top_left"
### Line Chart of Google Prices Code Ends ###########
### Scatter Chart Of IRIS Dimesions Code Starts ###########
scatter = figure(plot_width=500, plot_height=400,
title="Sepal Length vs Sepal Width Scatter Plot")
for cls in [0,1,2]:
scatter.circle(x=iris_df[iris_df["FlowerType"]==cls]["sepal length (cm)"],
y=iris_df[iris_df["FlowerType"]==cls]["sepal width (cm)"],
color=color_mapping[cls],
size=10,
alpha=0.8,
legend_label=iris.target_names[cls])
scatter.xaxis.axis_label= "sepal length (cm)".upper()
scatter.yaxis.axis_label= "sepal width (cm)".upper()
### Scatter Chart Of IRIS Dimesions Code Ends ###########
### Bar Chart Of IRIS Dimesions Code Starts ###########
iris_avg_by_flower_type = iris_df.groupby(by="FlowerType").mean()
bar_chart = figure(plot_width=500, plot_height=400,
title="Average Sepal Length (cm) per Flower Type")
bar_chart.vbar(x = [1,2,3],
width=0.9,
top=iris_avg_by_flower_type["sepal length (cm)"],
fill_color="tomato", line_color="tomato", alpha=0.9)
bar_chart.xaxis.axis_label="FlowerType"
bar_chart.yaxis.axis_label="Sepal Length"
bar_chart.xaxis.ticker = [1, 2, 3]
bar_chart.xaxis.major_label_overrides = {1: 'Setosa', 2: 'Versicolor', 3: 'Virginica'}
### Bar Chart Of IRIS Dimesions Code Starts ###########
### Widgets Code Starts ################################
drop_scat1 = Select(title="X-Axis-Dim",
options=iris.feature_names,
value=iris.feature_names[0],
width=225)
drop_scat2 = Select(title="Y-Axis-Dim",
options=iris.feature_names,
value=iris.feature_names[1],
width=225)
checkbox_grp = CheckboxButtonGroup(labels=checkbox_options, active=[0], button_type="success")
drop_bar = Select(title="Dimension", options=iris.feature_names, value=iris.feature_names[0])
### Widgets Code Ends ################################
##### Code to Update Charts as Per Widget State Starts #####################
def update_line_chart(attrname, old, new):
'''
Code to update Line Chart as Per Check Box Selection
'''
line_chart = figure(plot_width=1000, plot_height=400, x_axis_type="datetime",
title="Google Stock Prices from 2005 - 2013")
for option in checkbox_grp.active:
line_chart.line(
x="date", y=checkbox_options[option],
line_width=0.5, line_color=price_color_map[checkbox_options[option]],
legend_label=checkbox_options[option],
source=google_df
)
line_chart.xaxis.axis_label = 'Time'
line_chart.yaxis.axis_label = 'Price ($)'
line_chart.legend.location = "top_left"
layout_with_widgets.children[0].children[1] = line_chart
def update_scatter(attrname, old, new):
'''
Code to update Scatter Chart as Per Dropdown Selections
'''
scatter = figure(plot_width=500, plot_height=400,
title="%s vs %s Scatter Plot"%(drop_scat1.value.upper(), drop_scat2.value.upper()))
for cls in [0,1,2]:
scatter.circle(x=iris_df[iris_df["FlowerType"]==cls][drop_scat1.value],
y=iris_df[iris_df["FlowerType"]==cls][drop_scat2.value],
color=color_mapping[cls],
size=10,
alpha=0.8,
legend_label=iris.target_names[cls])
scatter.xaxis.axis_label= drop_scat1.value.upper()
scatter.yaxis.axis_label= drop_scat2.value.upper()
layout_with_widgets.children[1].children[0].children[1] = scatter
def update_bar_chart(attrname, old, new):
'''
Code to Update Bar Chart as Per Dropdown Selections
'''
bar_chart = figure(plot_width=500, plot_height=400,
title="Average %s Per Flower Type"%drop_bar.value.upper())
bar_chart.vbar(x = [1,2,3],
width=0.9,
top=iris_avg_by_flower_type[drop_bar.value],
fill_color="tomato", line_color="tomato", alpha=0.9)
bar_chart.xaxis.axis_label="FlowerType"
bar_chart.yaxis.axis_label=drop_bar.value.upper()
bar_chart.xaxis.ticker = [1, 2, 3]
bar_chart.xaxis.major_label_overrides = {1: 'Setosa', 2: 'Versicolor', 3: 'Virginica'}
layout_with_widgets.children[1].children[1].children[1] = bar_chart
##### Code to Update Charts as Per Widget State Ends #####################
#### Registering Widget Attribute Change with Methods Code Starts #############
checkbox_grp.on_change("active", update_line_chart)
drop_scat1.on_change("value", update_scatter)
drop_scat2.on_change("value", update_scatter)
drop_bar.on_change("value", update_bar_chart)
#### Registering Widget Attribute Change with Methods Code Ends #############
####### Widgets Layout #################
layout_with_widgets = column(
column(checkbox_grp, line_chart),
row(
column(row(drop_scat1, drop_scat2), scatter),
column(drop_bar, bar_chart)))
############ Creating Dashboard ################
curdoc().add_root(layout_with_widgets)
We can bring the bokeh dashboard up by using the below command in the shell/command prompt.
This ends our small tutorial on dashboard creation using bokeh. It also included widgets to modify charts. Please feel free to let us know your views in the comments section.
If you are more comfortable learning through video tutorials then we would recommend that you subscribe to our YouTube channel.
When going through coding examples, it's quite common to have doubts and errors.
If you have doubts about some code examples or are stuck somewhere when trying our code, send us an email at coderzcolumn07@gmail.com. We'll help you or point you in the direction where you can find a solution to your problem.
You can even send us a mail if you are trying something new and need guidance regarding coding. We'll try to respond as soon as possible.
If you want to