Extending Bokeh

Bokeh comes with a rich variety of built-in features that let you produce sophisticated interactive visualizations and data applications in the browser. However, some useful capabilities and features may not make it into the core library, either because they are too specialized or for lack of resources. Fortunately, you can expand the functionality of Bokeh with custom extensions that let you:

  • Modify the behavior of existing Bokeh models

  • Add new models to connect third-party JavaScript libraries to Python

  • Create highly specialized models for domain-specific use cases

You can make and use custom extensions with standard releases and don’t need to set up a development environment or build anything from source. This is the easiest way to get involved in Bokeh development. You can try new features and improved functionality without having to wait for the core team to implement them into Bokeh itself.

Note

Extending Bokeh is an advanced feature. Some aspects of creating and using extensions are still under active development and should be considered experimental.

Structure of Bokeh models

Python models

For the most part, Python Bokeh models are completely declarative classes. You can create custom extensions by making a subclass from Model and including special class attributes to declare the properties to be mirrored on the JavaScript side. For all of the available property types, see the bokeh.core.properties section of the reference guide.

Here’s a simple example that creates a custom readout for a slider:

from bokeh.core.properties import String, Instance
from bokeh.models import HTMLBox, Slider

class Custom(HTMLBox):

    text = String(default="Custom text")

    slider = Instance(Slider)

This example creates a subclass from HTMLBox to allow the extension to integrate into the DOM layout. It also adds two properties:

  • a String to configure a text message for the readout and

  • an Instance that can hold a Slider.

This creates a JavaScript Slider object that corresponds to a Slider in Python.

JavaScript models and views

While the Python side has little to no code, the JavaScript side requires code to implement the model. You also have to provide code for a corresponding view where necessary.

Here’s an annotated TypeScript implementation for Custom and its CustomView. For built-in models, this type of code is included directly in the final BokehJS scripts.

import {HTMLBox, HTMLBoxView} from "models/layouts/html_box"

import {div} from "core/dom"
import * as p from "core/properties"

export class CustomView extends HTMLBoxView {

  connect_signals(): void {
    super.connect_signals()

    // Set BokehJS listener so that the program can process new data when
    // the slider has a change event.
    this.connect(this.model.slider.change, () => {
      this.render()
      this.invalidate_layout()
    })
  }

  render(): void {
    // BokehJS views create <div> elements by default. These are accessible
    // as ``this.el``. Many Bokeh views ignore the default <div> and
    // instead do things like draw to the HTML canvas. In this case though,
    // the program changes the contents of the <div> based on the current
    // slider value.
    super.render()

    this.el.appendChild(div({
      style: {
        padding: '2px',
        color: '#b88d8e',
        backgroundColor: '#2a3153',
      },
    }, `${this.model.text}: ${this.model.slider.value}`))
  }
}

export class Custom extends HTMLBox {
  slider: {value: string}

  // Generally, the ``__name__`` class attribute should match the name of
  // the corresponding Python class exactly. TypeScript matches the name
  // automatically during compilation, so, barring some special cases, you
  // don't have to do this manually. This helps avoid typos, which stop
  // serialization/deserialization of the model.
  static __name__ = "Surface3d"

  static {
    // If there is an associated view, this is typically boilerplate.
    this.prototype.default_view = CustomView

    // The this.define() block adds corresponding "properties" to the JS
    // model. These should normally line up 1-1 with the Python model
    // class. Most property types have counterparts. For example,
    // bokeh.core.properties.String will correspond to ``String`` in the
    // JS implementation. Where JS lacks a given type, you can use
    // ``p.Any`` as a "wildcard" property type.
    this.define<Custom.Props>(({String, Ref}) => ({
      text:   [ String ],
      slider: [ Ref(Slider) ],
    }))
  }
}

Putting it together

For built-in Bokeh models, the building process automatically matches the implementation in BokehJS with the corresponding Python model. The Python class should also have a class attribute called __implementation__ with the value of the JavaScript (or TypeScript) code that defines the client-side model as well as any optional views.

Assuming you save the TypeScript code from the previous example in a file called custom.ts, the complete Python class might look like this:

from bokeh.core.properties import String, Instance
from bokeh.models import HTMLBox, Slider

class Custom(HTMLBox):

    __implementation__ = "custom.ts"

    text = String(default="Custom text")

    slider = Instance(Slider)

Assuming that a Python module custom.py defines this class, you can now use the custom extension exactly you would any built-in Bokeh model.

from bokeh.io import show, output_file
from bokeh.layouts import column
from bokeh.models import Slider

slider = Slider(start=0, end=10, step=0.1, value=0, title="value")

custom = Custom(text="Special Slider Display", slider=slider)

layout = column(slider, custom)

show(layout)

This produces the following output:

The rendered document automatically includes the JavaScript code for the implementation. Move the slider to see the special header update as the slider moves.

Specifying implementation languages

If the value of __implementation__ is a single line that ends in either .js or .ts, Bokeh interprets it as a filename, opens the file, and compiles its contents according to the file’s extension.

In case of an incline implementation, specify the language for the source code by using the classes JavaScript or TypeScript. Here’s an example:

class Custom(Model):

    __implementation__ = JavaScript(" <JS code here> ")

Specifying default values

If your properties have default values, you must provide the default value on both the Python side and on the JavaScript side. The values you provide should be the same on both sides. For efficiency reasons, Bokeh only transmits property values that a user has explicitly changed from their default values.

As a concrete example, a boolean property flag with a default value of True should look like this on the Python side:

flag = Bool(default=True)

And it should look like this on the Bokeh side:

flag: [ Boolean, true ]

Supplying external resources

You may require third-party JavaScript libraries or CSS resources to implement a custom model in Bokeh. You can supply external resources through the __javascript__ and __css__ Python class attributes of custom models.

Including URL paths to external resources adds them to the HTML document head, making JavaScript libraries available in the global namespace and applying custom CSS styling.

Here’s an example that includes JS and CSS files for KaTeX (a JS library with LaTeX support) in order to create a LatexLabel custom model.

class LatexLabel(Label):
    """A subclass of the built-in Bokeh model `Label` that supports
    rendering LaTeX with the KaTeX typesetting library.
    """
    __javascript__ = "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/katex.min.js"
    __css__ = "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/katex.min.css"
    __implementation__ = """
    # do something here
    """

For a complete implementation and its output, see the LaTeX example in the extension gallery below.

Integration with Bokeh server

You don’t have to do any extra work to integrate custom extensions with the Bokeh server. As for standalone documents, the rendered application automatically includes the JavaScript implementation. Additionally, the standard synchronization of Bokeh model properties is transparent for custom user extensions, same as for built-in models.

Examples

This section aims to provide you with basic examples to help you start creating custom extensions. This is a somewhat advanced topic, however, and you will often have to study the source code of the base classes in bokehjs/src/lib/models to make progress.

Specialized axis ticking

Subclass a built-in Bokeh model for axis ticking to customize axis tick behavior.

A new custom tool

Make a completely new tool that can draw on a plot canvas.

Wrapping a JavaScript library

Connect Python to a third-party JavaScript library by wrapping it in a Bokeh custom extension.

Adding a custom widget

Include a third-party JavaScript library in an extension widget.

Pre-built extensions

So far, this chapter covered simple, typically inline extensions. These are great for ad hoc additions to Bokeh, but this approach is not particularly convenient when it comes to serious development.

For example, the implicit nature of certain configuration files such as package.json or tsconfig.json doesn’t allow you to take full advantage of your IDE’s capabilities when writing TypeScript or JavaScript for an extension.

Enter pre-built extensions.

To create a pre-built extension, use the bokeh init command. This creates all the necessary files, including bokeh.ext.json, package.json, and tsconfig.json.

To create and customize an extension step by step, run bokeh init --interactive.

To build your extension, use the bokeh build command. This runs npm install, if necessary, compiles TypeScript files, transpiles JavaScript files, resolves modules, and links them together in distributable bundles.

Bokeh caches compilation products to improve performance. If this causes issues, rebuild your extension from scratch with the bokeh build --rebuild command.