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.
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.
Model
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:
HTMLBox
a String to configure a text message for the readout and
String
an Instance that can hold a Slider.
Instance
Slider
This creates a JavaScript Slider object that corresponds to a Slider in Python.
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.
Custom
CustomView
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 init_Custom(): void { // 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) ], })) } }
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.
__implementation__
Assuming you save the TypeScript code from the previous example in a file called custom.ts, the complete Python class might look like this:
custom.ts
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.
custom.py
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.
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.
.js
.ts
In case of an incline implementation, specify the language for the source code by using the classes JavaScript or TypeScript. Here’s an example:
JavaScript
TypeScript
class Custom(Model): __implementation__ = JavaScript(" <JS code here> ")
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.
__javascript__
__css__
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.
LatexLabel
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.
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.
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.
Subclass a built-in Bokeh model for axis ticking to customize axis tick behavior.
Make a completely new tool that can draw on a plot canvas.
Connect Python to a third-party JavaScript library by wrapping it in a Bokeh custom extension.
Include a third-party JavaScript library to render LaTeX.
Include a third-party JavaScript library in an extension widget.
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.
package.json
tsconfig.json
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.
bokeh init
bokeh.ext.json
To create and customize an extension step by step, run bokeh init --interactive.
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 build
npm install
Bokeh caches compilation products to improve performance. If this causes issues, rebuild your extension from scratch with the bokeh build --rebuild command.
bokeh build --rebuild