Extend Bokeh

Bokeh has a variety of built-in types that can be used to create interactive visualisations and data applications in the browser. However, there are specific functions that have not been included in the core library, but Bokeh can be extended

  • to change the behaviour of existing Bokeh models

  • to add new models that connect third-party JavaScript libraries to Python

  • to create specialised models for specific domains

Such custom extensions can be created and used with standard releases. It is not necessary to set up a development environment or create anything from the sources.

Structure of bokeh models

Python models

JavaScript models and views

While the Python side is mostly declarative, without much or any real code, the JavaScript side requires code to implement the model. Code may also need to be provided for a corresponding view.

Below you will find a commented TypeScript implementation for Custom and CustomView. For integrated models, this code is included directly in the final BokehJS scripts. The next section will show you how to link this code to custom extensions.

Note:

BokehJS was originally written in CoffeeScript, but is being ported to TypeScript. Accordingly, the instructions here are in TypeScript. However, custom extensions can also be written in CoffeeScript or in pure JavaScript.

import {UIElement, UIElementView} from "models/ui/ui_element"

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

export class CustomView extends UIElementView {

  override 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()
    })
  }

  override 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.shadow_el.appendChild(div({
      style: {
        padding: '2px',
        color: '#b88d8e',
        backgroundColor: '#2a3153',
      },
    }, `${this.model.text}: ${this.model.slider.value}`))
  }
}

export class Custom extends UIElement {
  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) ],
    }))
  }
}

Merging

For integrated Bokeh models, the implementation in BokehJS is automatically synchronised with the corresponding Python model by the build process. To connect JavaScript implementations with Python models, an additional step is required. The Python class should have a class attribute __implementation__ whose name is the TypeScript code (or JavaScript or CoffeeScript code) that defines the client-side model and optional views.

Assuming the above TypeScript code was saved in a custom.ts file, the complete Python class could look like this:

[1]:
from bokeh.core.properties import Instance, String
from bokeh.models import LayoutDOM, Slider


class Custom(LayoutDOM):
    __implementation__ = "custom.ts"

    text = String(default="Custom text")

    slider = Instance(Slider)

If this class is then defined in a Python module custom.py, the custom extension can now be used just like any built-in bokeh model:

[2]:
from bokeh.io import output_file, show
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)

Integration in Bokeh Server

No special work or modifications are required to integrate custom extensions into the Bokeh server. As with standalone documents, the JavaScript implementation is automatically included in the rendered application. In addition, the standard synchronisation of the Bokeh model properties, which applies to all integrated models, is also transparent for user-defined extensions.

Examples

The Bokeh documentation provides some complete examples to serve as a reference. In many cases it is necessary to study the source code of the base classes in bokehjs/src/lib/models.