Custom extensions (original) (raw)
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 topics 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 topics 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 thebokeh.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 UIElement, Slider
class Custom(UIElement):
text = String(default="Custom text")
slider = Instance(Slider)
This example creates a subclass from UIElement
to allow the extension to integrate into the DOM layout. It also adds two properties:
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 itsCustomView
. For built-in models, this type of code is included directly in the final BokehJS scripts.
import {UIElement, UIElementView} from "models/ui/ui_element" import {Slider} from "models/widgets/sliders/slider" import {div} from "core/dom" import * as p from "core/properties"
export class CustomView extends UIElementView { declare model: Custom
private content_el: HTMLElement
override connect_signals(): void { super.connect_signals() this.connect(this.model.slider.change, () => this._update_text()) }
override render(): void { // BokehJS views create
this.el
. Many Bokeh views ignore the default this.content_el = div({style: {
textAlign: "center",
fontSize: "1.2em",
padding: "2px",
color: "#b88d8e",
backgroundColor: "#2a3153",
}})
this.shadow_el.append(this.content_el)
this._update_text()
}
private _update_text(): void {
this.content_el.textContent = ${this.model.text}: ${this.model.slider.value}
}
}
export namespace Custom { export type Attrs = p.AttrsOf
export type Props = UIElement.Props & { text: p.Property slider: p.Property } }
export interface Custom extends Custom.Attrs {}
export class Custom extends UIElement { declare properties: Custom.Props declare view_type: CustomView
constructor(attrs?: Partial<Custom.Attrs>) { super(attrs) }
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>(({Str, Ref}) => ({
text: [ Str, "Custom text" ],
slider: [ Ref(Slider) ],
}))
} }
#
Putting it togetherFor 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 UIElement, Slider
class Custom(UIElement):
__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 languagesIf 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 valuesIf 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:
#
Supplying external resourcesYou 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 serverYou 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.
#
ExamplesThis 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 inbokehjs/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 in an extension widget.
#
Pre-built extensionsSo 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 aspackage.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, runbokeh init --interactive
.
To build your extension, use the bokeh build
command. This runsnpm 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.