Share @ LinkedIn Facebook  dashboard, bokeh-dash, widgets
How to Create Simple Dashboard with Widgets in Python [Bokeh]?

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

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.

Necessary Library Imports

In [1]:
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
In [ ]:
output_notebook(resources=INLINE)

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

Loading Datasets

We'll be using below mentioned two datasets for building three charts of our dashboard.

  • IRIS Flowers Dataset - It has flower measurements for three types of IRIS flowers. It's available from scikit-learn.
  • Google OHLC Dataset - It has Google OHLC data from 2004 - 2013. It's available from bokeh.

We'll be keeping each dataset as a pandas dataframe as explained below.

In [3]:
from sklearn.datasets import load_iris
In [4]:
iris = load_iris()

iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df["FlowerType"] = iris.target

iris_df.head()
Out[4]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) FlowerType
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0
2 4.7 3.2 1.3 0.2 0
3 4.6 3.1 1.5 0.2 0
4 5.0 3.6 1.4 0.2 0
In [5]:
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()
Out[5]:
date open high low close volume adj_close
0 2004-08-19 100.00 104.06 95.96 100.34 22351900 100.34
1 2004-08-20 101.01 109.08 100.50 108.31 11428600 108.31
2 2004-08-23 110.75 113.48 109.05 109.40 9137200 109.40
3 2004-08-24 111.24 111.60 103.57 104.87 7631300 104.87
4 2004-08-25 104.96 108.00 103.88 106.00 4598900 106.00

Creating Individual Charts of Dashboard

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.

1. Line Chart of Google Stock Prices

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.

In [ ]:
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)

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

2. Scatter Plot of IRIS Flower Sepal Length vs Sepal Width Color-Encoded by Flower Type

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.

In [ ]:
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)

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

3. Bar Chart of Average Sepal Length Per Flower Type

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.

In [ ]:
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)

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

Laying Out Charts Without Widgets

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.

In [ ]:
layout = column(line_chart, row(scatter, bar_chart))
show(layout)

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

Widgets Creation

We'll be creating 4 widgets to be included in our chart.

  1. Group Checkbox for Line Chart - It'll let us select multiple price values from OHLC to be displayed on the dashboard.
  2. 2 Dropdowns for Scatter Chart - It has a list of iris data features. We can try various combinations to see the relationship between them.
  3. Dropdown for Bar Chart - It has a list of iris data features. We can select the different features and see the average value of that feature for each flower category.

1. Checkbox Group For Line 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.

In [ ]:
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)

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

2. Dropdowns for Scatter Chart

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.

In [ ]:
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))

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

3. Dropdown for Bar Chart

Below we are creating a dropdown for bar chart using the Select() method.

In [ ]:
drop_bar = Select(title="Dimension", options=iris.feature_names, value=iris.feature_names[0])

show(drop_bar)

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

Laying Out Charts & Widget to Create Dashboard Layout

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.

In [ ]:
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)

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

Accessing Individual Elements of Dashboard Layout with Indexing

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.

In [ ]:
show(layout_with_widgets.children[1])

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

Below we are further accessing children 0 of 2nd element of the dashboard which is scatter chart with widgets.

In [ ]:
show(layout_with_widgets.children[1].children[0])

How to Create Simple Dashboard with Widgets in Python [Bokeh]?

Callbacks Creation & Widget Attribute Registration with Callback

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.

1. Line Chart Callback and Checkbox Attribute Registration with Callback

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.

In [16]:
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)

2. Scatter Chart Callback and Dropdowns Attributes Registration with Callback

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.

In [17]:
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)

3. Bar Chart Callback and Dropdown Attribute Registration with Callback

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.

In [18]:
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.

Putting All Together and Bringing Up 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)

Bringing Up Dashboard

We can bring the bokeh dashboard up by using the below command in the shell/command prompt.

  • bokeh serve --show bokeh_dashboard.py

How to Create Simple Dashboard with Widgets in Python [Bokeh]?



Sunny Solanki  Sunny Solanki