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.

Flowchart describing the flow of data from Python objects through JSON to the browser-side. There, the JSON data is converted into JavaScript objects which then get rendered as output. Output can be HTML Canvas, WebGL, or SVG.

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 Developing with JavaScript 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-toolbar-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 by hasOwnProperty() Use for-of loop in combination with keys(), values() and/or entries() from the core/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 14+

  • npm 7.4+

  • Chrome/Chromium browser 94+ 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 than node 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. Run node 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)
  }

  ...
}