Contributing to BokehJS#
BokehJS is the in-browser client-side runtime library that users of Bokeh ultimately interact with. This library is written primarily in TypeScript and is one of the unique things about the Bokeh plotting system.
The central building blocks of all Bokeh visualizations are objects based on Bokeh’s models. These models are representations of plot elements, such as axes, glyphs, or widgets.
On the Python side, Bokeh serializes the attributes of each plot element object into JSON data. On the browser side, BokehJS deserializes this JSON data and creates JavaScript objects based on this information. BokehJS then uses these JavaScript objects to render the visualization.
This combination of Python and JavaScript allows you to define a visualization in Python while taking advantage of all the interactivity offered by JavaScript running in a browser. Additionally, this combination enables you to do almost all data handling with Python and use large and streaming server-side data with an interactive JavaScript visualization.
In addition to using BokehJS to create visualizations based on JSON data, you can also use BokehJS as a standalone JavaScript library. See BokehJS for more information on creating visualizations directly with BokehJS.
Source code location#
The Bokeh repository contains Bokeh’s Python code as well as the JavaScript code of BokehJS. The BokehJS source code is located in the bokehjs directory in this monorepo repository.
All further instructions and shell commands assume that bokehjs/
is
your current directory.
Tip
Set the working folder of your IDE to the bokehjs
directory. This way,
some of the tools within your IDE might work better with the BokehJS source
code.
CSS for BokehJS#
The CSS definitions for BokehJS are contained in several .less
files in the
bokehjs/src/less/ directory. All CSS classes for Bokeh DOM
elements are prefixed with bk-
. For example: .bk-plot
or
.bk-tool-button
.
Code Style Guide#
BokehJS doesn’t have an explicit style guide. Make sure to run node make
lint
or node make lint --fix
before committing to the Bokeh repository.
Also, review the surrounding code and try to be consistent with the existing
code.
Some guidelines and tips to keep in mind when working on BokehJS:
Do not use
for-in
loops, especially unguarded byhasOwnProperty()
Usefor-of
loop in combination withkeys()
,values()
and/orentries()
from thecore/util/object
module instead.Use double quotes (
"string"
) for strings by default. Use single quotes in cases where they help you avoid escaping quotation marks (case '"': return """
).Use template literals (template strings) for multiline, tagged, and interpolated strings (
`Bokeh ${Bokeh.version}`
).
Always lint your BokehJS code with ESLint: From the bokehjs
directory, run node make lint
to check your code. Run
node make lint --fix
to have ESLint fix some problems automatically. For
more details, see the rules defined in bokehjs/eslint.json.
Tip
If you use VSCode, you can use the following configuration for your workspace to use ESLint directly in the editor:
"eslint.format.enable": true,
"eslint.lintTask.enable": true,
"eslint.debug": false,
"eslint.quiet": false,
"eslint.options": {
"cache": true,
"extensions": [".ts"],
"overrideConfigFile": "./eslint.json"
},
"eslint.workingDirectories": [
"./bokehjs"
]
This requires the ESLint extension for VSCode and ESLint version 8 or above to be installed.
Development requirements#
To build and test BokehJS locally, follow the instructions in Setting up a development environment. This way, all required packages should be installed and configured on your system.
Developing BokehJS requires the following minimum versions:
Node.js 16+
npm 8+
Chrome/Chromium browser 107+ or equivalent
Bokeh officially supports the following platforms for development and testing:
Linux Ubuntu 20.04+ or equivalent
Windows 10 (or Server 2019)
MacOS 10.15
It is possible to work on BokehJS using different platforms and versions. However, things might not work as intended, and some tests will not work.
Building BokehJS#
For building, BokehJS relies on a custom tool similar to gulp. All
commands start with node make
.
Use node make help
to list all available commands for the BokehJS build
system. These are the most common commands:
node make build
: Builds the entire library, including the extensions compiler.node make dev
: Builds the library without the extensions compiler. This is faster thannode make build
but not suitable for production code or packaging.node make test
: Runs all BokehJS tests. To only run specific tests, see Select specific BokehJS tests.node make lint
lint BokehJS with ESLint. Runnode make lint --fix
to have ESLint fix some problems automatically.
node make
automatically runs npm install
whenever package.json
changes.
Testing#
The Bokeh repository contains several test suites. These tests help to make sure that BokehJS functions consistently as its own library as well as in combination with all other components of Bokeh.
To learn more about running tests for BokehJS locally, see Run JavaScript tests.
To learn more about adding and updating tests for BokehJS, see Writing JavaScript tests (BokehJS).
Models and views in BokehJS#
The fundamental building blocks of visualizations in BokehJS are models and views:
- Models
A model is a data structure that may or may not have a visual representation. BokehJS’ models and their properties match the models and respective properties in Bokeh’s Python code. Bokeh uses defaults tests to make sure that models stay compatible between Python and BokehJS.
- Views
A view defines the visual representation of a model. Any model that influences how things look in the browser requires a corresponding view.
For each model, the model definition and the corresponding view should be in the same file in the bokehjs/models directory.
Tip
When updating or adding new models and views, look at how similar models and views are currently implemented.
Base classes for models and views#
BokehJS models usually extend a base class. For example: the Axis
model
extends GuideRenderer
, the Circle
model extends XYGlyph
.
The model’s corresponding view extends a corresponding base view class. For
example: AxisView
extends GuideRendererView
, CircleView
extends XYGlyphView
.
Suppose you want to define a new type of action button tool for the Bokeh
tool bar, called NewActionTool
. The model for
your new button would inherit from ActionTool
, and its corresponding view
would inherit from ActionToolView
:
import {ActionTool, ActionToolView} from "./action_tool"
Models#
BokehJS models require a namespace
and interface
. At a minimum, this
includes Attrs
and Props
. There are also more properties you can use,
like Mixins
or Visuals
.
export namespace NewActionTool {
export type Attrs = p.AttrsOf<Props>
export type Props = ActionTool.Props & {
some_property: p.Property<number>
some_other_property: p.Property<string>
}
}
export interface NewActionTool extends NewActionTool.Attrs {}
If you want to update a model, the most relevant property in most cases is
Props
. The properties you define there need to match the properties and
types of the respective Python model. The BokehJS properties are defined in
core.properties
and are usually imported with
import * as p from "core/properties"
.
Next, define the actual model itself. The model extends the respective
BaseModel
base class. If your model includes a view, this is also where you
link model and view.
export class NewActionTool extends ActionTool {
properties: NewActionTool.Props
// only when a view is required:
__view_type__: NewActionToolView
// do not remove this constructor, or you won't be
// able to use `new NewActionTool({some_property: 1})`
// this constructor
constructor(attrs?: Partial<NewActionTool.Attrs>) {
super(attrs)
}
static {
this.prototype.default_view = NewActionToolView
this.define<NewActionTool.Props>(({Number, String}) => ({
some_property: [ Number, 0 ],
some_other_property: [ String, "Default String" ],
...
// add more property definitions and defaults
// use properties from lib/core/property and primitives from lib/core/kinds
// does have to match Python, both type and default value (and nullability)
}))
}
}
Views#
If your model requires display-related logic, you need to define a view. A view generally handles how a model is displayed in the browser.
Views extend the respective BaseView
base class.
export class NewActionToolView extends ActionToolView {
override model: NewActionToolView
initialize(): void {
super.initialize()
// perform view initialization (remove if not needed)
}
async lazy_initialize(): Promise<void> {
await super.lazy_initialize()
// perform view lazy initialization (remove if not needed)
}
...
}