Bokeh View

Last Updated: August 2023

Important

This gizmo requires the bokeh library to be installed. Starting with Tethys 5.0 or if you are using micro-tethys-platform, you will need to install bokeh using conda or pip as follows:

# conda: conda-forge channel strongly recommended
conda install -c conda-forge "bokeh<3"

# pip
pip install "bokeh<3"

Don't Forget: If you end up using this gizmo in your app, add bokeh as a requirement to your install.yml.

Python

class tethys_sdk.gizmos.BokehView(plot_input, height=None, width='100%', attributes='', classes='', divid='', hidden=False)

Simple options object for Bokeh plotting.

Note

For more information about Bokeh and for Python examples, see http://bokeh.pydata.org.

plot_input

A bokeh figure to be plotted.

Type:

bokeh figure

height

Height of the plot element. Any valid css unit of length.

Type:

Optional[str]

width

Width of the plot element. Any valid css unit of length.

Type:

Optional[str]

attributes

Dictionary of attributed to add to the outer div.

Type:

Optional[dict]

classes

Space separated string of classes to add to the outer div.

Type:

Optional[str]

hidden

If True, the plot will be hidden. Default is False.

Type:

Optional[bool]

Controller Code Example:

from tethys_sdk.gizmos import BokehView
from bokeh.plotting import figure

plot = figure(plot_height=300)
plot.circle([1,2], [3,4])
my_bokeh_view = BokehView(plot, height="300px")

context = {'bokeh_view_input': my_bokeh_view}

Template Code Example:

{% load tethys_gizmos %}

{% gizmo bokeh_view_input %}

Additional Examples:

from math import pi
import numpy as np
import pandas as pd
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, RangeTool, HoverTool
from bokeh.palettes import Bokeh, Category20c
from bokeh.plotting import figure
from bokeh.transform import cumsum, dodge
from bokeh.sampledata.penguins import data as penguin_data
from bokeh.sampledata.stocks import AAPL
from bokeh.transform import factor_cmap, factor_mark
from tethys_sdk.gizmos import BokehView

# Scatter plot
SPECIES = sorted(penguin_data.species.unique())
MARKERS = ['hex', 'circle_x', 'triangle']

scatter_fig = figure(title = "Penguin size", background_fill_color="#fafafa")
scatter_fig.xaxis.axis_label = 'Flipper Length (mm)'
scatter_fig.yaxis.axis_label = 'Body Mass (g)'

scatter_fig.scatter("flipper_length_mm", "body_mass_g", source=penguin_data,
        legend_group="species", fill_alpha=0.4, size=12,
        marker=factor_mark('species', MARKERS, SPECIES),
        color=factor_cmap('species', 'Category10_3', SPECIES))

scatter_fig.legend.location = "top_left"
scatter_fig.legend.title = "Species"
scatter_plot = BokehView(scatter_fig)

# Line plot
line_x = np.linspace(0, 4*np.pi, 100)
line_y = np.sin(line_x)

TOOLS = "pan,wheel_zoom,box_zoom,reset,save,box_select"

line_fig = figure(title="Legend Example", tools=TOOLS)

line_fig.line(line_x,   line_y, legend_label="sin(x)")
line_fig.line(line_x, 2*line_y, legend_label="2*sin(x)", color="orange")
line_fig.line(line_x, 3*line_y, legend_label="3*sin(x)", color="green")

line_fig.legend.title = 'Example Title'
line_plot = BokehView(line_fig)

# Pie plot
pie_x = {
    'United States': 157,
    'United Kingdom': 93,
    'Japan': 89,
    'China': 63,
    'Germany': 44,
    'India': 42,
    'Italy': 40,
    'Australia': 35,
    'Brazil': 32,
    'France': 31,
    'Taiwan': 31,
    'Spain': 29
}

pie_data = pd.Series(pie_x).reset_index(name='value').rename(columns={'index': 'country'})
pie_data['angle'] = pie_data['value']/pie_data['value'].sum() * 2*pi
pie_data['color'] = Category20c[len(pie_x)]

pie_fig = figure(height=350, title="Pie Chart", toolbar_location=None,
        tools="hover", tooltips="@country: @value", x_range=(-0.5, 1.0))

pie_fig.wedge(x=0, y=1, radius=0.4,
        start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
        line_color="white", fill_color='color', legend_field='country', source=pie_data)

pie_fig.axis.axis_label = None
pie_fig.axis.visible = False
pie_fig.grid.grid_line_color = None

pie_plot = BokehView(pie_fig)

# Bar chart
fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
years = ['2015', '2016', '2017']

bar_data = {'fruits' : fruits,
        '2015'   : [2, 1, 4, 3, 2, 4],
        '2016'   : [5, 3, 3, 2, 4, 6],
        '2017'   : [3, 2, 4, 4, 5, 3]}

source = ColumnDataSource(data=bar_data)

bar_fig = figure(x_range=fruits, y_range=(0, 10), title="Fruit Counts by Year",
        height=350, toolbar_location=None, tools="")

bar_fig.vbar(x=dodge('fruits', -0.25, range=bar_fig.x_range), top='2015', source=source,
    width=0.2, color="#c9d9d3", legend_label="2015")

bar_fig.vbar(x=dodge('fruits',  0.0,  range=bar_fig.x_range), top='2016', source=source,
    width=0.2, color="#718dbf", legend_label="2016")

bar_fig.vbar(x=dodge('fruits',  0.25, range=bar_fig.x_range), top='2017', source=source,
    width=0.2, color="#e84d60", legend_label="2017")

bar_fig.x_range.range_padding = 0.1
bar_fig.xgrid.grid_line_color = None
bar_fig.legend.location = "top_left"
bar_fig.legend.orientation = "horizontal"

bar_chart = BokehView(bar_fig)

# Time series
dates = np.array(AAPL['date'], dtype=np.datetime64)
source = ColumnDataSource(data=dict(date=dates, close=AAPL['adj_close']))

time_series_fig = figure(height=300, width=800, tools="xpan", toolbar_location=None,
                        x_axis_type="datetime", x_axis_location="above",
                        background_fill_color="#efefef", x_range=(dates[1500], dates[2500]))

time_series_fig.line('date', 'close', source=source)
time_series_fig.yaxis.axis_label = 'Price'

select = figure(title="Drag the middle and edges of the selection box to change the range above",
                height=130, width=800, y_range=time_series_fig.y_range,
                x_axis_type="datetime", y_axis_type=None,
                tools="", toolbar_location=None, background_fill_color="#efefef")

range_tool = RangeTool(x_range=time_series_fig.x_range)
range_tool.overlay.fill_color = "navy"
range_tool.overlay.fill_alpha = 0.2

select.line('date', 'close', source=source)
select.ygrid.grid_line_color = None
select.add_tools(range_tool)

time_series_plot = BokehView(column(time_series_fig, select))

# Hexbin plot
n = 500
hex_x = 2 + 2*np.random.standard_normal(n)
hex_y = 2 + 2*np.random.standard_normal(n)

hex_fig = figure(title="Hexbin for 500 points", match_aspect=True,
        tools="wheel_zoom,reset", background_fill_color='#440154')
hex_fig.grid.visible = False

hex_r, _ = hex_fig.hexbin(hex_x, hex_y, size=0.5, hover_color="pink", hover_alpha=0.8)

hex_fig.circle(hex_x, hex_y, color="white", size=1)

hex_fig.add_tools(HoverTool(
    tooltips=[("count", "@c"), ("(q,r)", "(@q, @r)")],
    mode="mouse", point_policy="follow_mouse", renderers=[hex_r]
))

hexbin_plot = BokehView(hex_fig)

context = {
    'docs_endpoint': docs_endpoint,
    'scatter_plot': scatter_plot,
    'line_plot': line_plot,
    'pie_plot': pie_plot,
    'bar_chart': bar_chart,
    'time_series_plot': time_series_plot,
    'hexbin_plot': hexbin_plot,
}

Template Code:

{% load tethys_gizmos %}

{% gizmo scatter_plot %}
{% gizmo line_plot %}
{% gizmo pie_plot %}
{% gizmo bar_chart %}
{% gizmo time_series_plot %}
{% gizmo hexbin_plot %}

AJAX

Often dynamically loading in plots can be useful. Here is a description of how to do so with Bokeh.

Note

In order to use this, you will either need to use a BokehView gizmo on the main page or register the dependencies in the main html template page using the import_gizmo_dependency tag with the bokeh_view name in the import_gizmos block.

For example:

{% block import_gizmos %}
    {% import_gizmo_dependency bokeh_view %}
{% endblock %}

Three elements are required:

  1. A controller for the AJAX call with a BokehView gizmo.

from tethys_sdk.gizmos import BokehView
from tethys_sdk.routing import controller
from bokeh.plotting import figure

@controller(name="bokeh_ajax", url="app-name/bokeh")
def bokeh_ajax(request):
    """
    Controller for the bokeh ajax request.
    """
    plot = figure(plot_height=300)
    plot.circle([1,2], [3,4])
    my_bokeh_view = BokehView(plot, height="300px")

    context = {'bokeh_view_input': my_bokeh_view}

    return render(request, 'app_name/bokeh_ajax.html', context)
  1. A template for with the tethys gizmo (e.g. bokeh_ajax.html)

{% load tethys_gizmos %}

{% gizmo bokeh_view_input %}
  1. The AJAX call in the javascript

$(function() { //wait for page to load

    $.ajax({
        url: 'bokeh',
        method: 'GET',
        data: {
            'plot_height': 500, //example data to pass to the controller
        },
        success: function(data) {
            // add plot to page
            $("#bokeh_plot_div").html(data);
        }
    });

});