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.
When researching the wide field of JavaScript plotting libraries, we found that they were all architected and designed to integrate with other JavaScript. If they provided any server-side wrappers, those were always “second class” and primarily designed to generate a simple configuration for the front-end JS. Of the few JS plotting libraries that offered any level of interactivity, the interaction was not really configurable or customizable from outside the JS itself. Very few JS plotting libraries took large and streaming server-side data into account, and providing seamless access to those facilities from another language like Python was not a consideration.
This, in turn, has caused the developers of Python plotting libraries to only treat the browser as a “backend target” environment, for which they will generate static images or a bunch of JavaScript.
BokehJS is intended to be a standalone, first-class JavaScript plotting library and interaction runtime for dynamic, highly-customizable information visualization.
BokehJS is a standalone JavaScript library for dynamic and interactive visualization in the browser. It is built on top of HTML5 canvas, and designed for high-performance rendering of larger data sets. Its interface is declarative, in the style of Protovis, but its implementation consists of a reactive scene graph (similar to Chaco).
More information is available at Developing with JavaScript.
The CSS for controlling Bokeh presentation are located in a bokeh.css file that is compiled from several separate .less files in the BokehJS source tree. All CSS classes specifically for Bokeh DOM elements are prefixed with the string bk-. For instance some examples are: .bk-plot, .bk-toolbar-button, etc.
bokeh.css
.less
bk-
.bk-plot
.bk-toolbar-button
BokehJS’s source code is located in the bokehjs/ directory in Bokeh’s monorepo repository. All further instructions and shell commands assume that bokehjs/ is the current directory.
bokehjs/
Some guidelines to adhere to 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.
for-in
hasOwnProperty()
for-of
keys()
values()
entries()
core/util/object
node 12.*
npm 6.14+ (most recent version)
chromium browser 80+ or equivalent
You can install nodejs with conda:
$ conda install -c conda-forge nodejs
or follow the official installation instructions.
Upgrade your npm after installing or updating nodejs, or whenever asked by npm:
$ npm install -g npm
Officially supported platforms are as follows:
Linux Ubuntu 18.04+ or equivalent
Windows 10 (or Server 2019)
MacOS 10.15
BokehJS can be developed on different platforms and versions of aforementioned software, but results may vary, especially when it comes to testing (visual testing in particular).
BokehJS’s build is maintained by using an in-house tool that visually resembles gulp. All commands start with node make (don’t confuse this with GNU make).
node make
Most common commands:
node make build
node make test
node make lint
Use node make help to list all available commands.
node make help
node make automatically runs npm install whenever package.json changes.
npm install
package.json
You can use tsc directly for error checking (e.g. in an IDE). However, don’t use it for code emit, because we rely on AST transforms to produce viable library code.
tsc
BokehJS testing is performed with the node make test command. You can run individual test suites with node make test:suite_name. Known tests suites are:
node make test:suite_name
node make test:size
node make test:defaults
node make test:unit
node make test:integration
The last two can be run with node make test:lib. Unit and integration tests are run in a web browser (see requirements), which is started automatically with the right settings to guarantee consistent test results.
node make test:lib
To review the visual tests’ output, start BokehJS’s devtools server:
$ node test/devtools server listening on 127.0.0.1:5777
and navigate to /integration/report. Devtools server can also be used to manually inspect and debug tests. For that, the following endpoints are available:
/integration/report
/unit
/defaults
/integration
Those load BokehJS and the tests, but don’t do anything. You have to issue Tests.run_all() in a JavaScript console. This allows you to set breakpoints before running code. You can filter out tests by providing a string keyword or a regular expression. Alternatively, you can run tests immediately with these endpoints:
Tests.run_all()
/unit/run
/defaults/run
/integration/run
You can use ?k=some%20text to filter tests by a keyword.
?k=some%20text
test:integration does two types of tests:
test:integration
textual baseline tests
visual/screenshot tests
Textual baselines are cross-platform compatible and can be generated locally (on supported platforms) or in CI. Visual testing is platform depended and fairly sensitive to system configuration (especially in regard to differences in font rendering). Visual tests can be performed locally, but given that baseline images for all three supported platforms have to be updated, the preferred approach is to generate images and compare them in CI.
The full procedure for visual testing is as follows:
Make changes to the repository and write new tests or update existing.
Use node make tests to incrementally test your changes on your system.
node make tests
Commit changes to textual baselines (test/baselines/*).
test/baselines/*
Push your changes to GitHub and wait for CI to finish.
If you added new tests, CI will expectedly fail with “missing baseline images” error message.
If tests passed then you are done.
If tests failed, go to BokehJS’s GitHub_Actions page. Find the most recent test run for your PR and download the associated bokehjs-report artifact.
bokehjs-report
Unzip the artifact archive.
Assuming devtools server is running in the background, go to /integration/report?platform=name where name is either linux, macos or windows and review the test output for each platform. If there are no unintentional differences, then commit all new or modified files under test/baselines/{linux,macos,windows}.
/integration/report?platform=name
name
linux
macos
windows
test/baselines/{linux,macos,windows}
Push your changes again to GitHub and verify that tests pass this time.
Note
Make sure to monitor the state of the test/baselines directory, so that you don’t commit unnecessary files. If you do so, subsequent tests will fail.
test/baselines
Models (and views) come in many forms and sizes. At minimum, a model is implemented. A view may follow if a “visual” model is being implemented. A minimal model/view module looks like this:
import {BaseModel, BaseModelView} from "models/..." export class SomeModelView extends BaseModelView { model: SomeModel 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) } } export namespace SomeModel { export type Attrs = p.AttrsOf<Props> export type Props = BaseModel.Props & { some_property: p.Property<number> // add more property declarations } } export interface SomeModel extends SomeModel.Attrs {} export class SomeModel extends BaseModel { properties: SomeModel.Props __view_type__: SomeModelView // do not remove this constructor, or you won't be // able to use `new SomeModel({some_property: 1})` constructor(attrs?: Partial<SomeModel.Attrs>) { super(attrs) } static init_SomeModel(): void { this.prototype.default_view = SomeModelView this.define<SomeModel.Props>({ some_property: [ p.Number, 0 ], // add more property definitions }) } }
For trivial modules like this, most of the code is just boilerplate to make BokehJS’s code statically type-check and generate useful type declarations for further consumption (in tests or by users).
BokehJS doesn’t have an explicit style guide. Make your changes consistent in formatting. Use node make lint. Follow patterns observed in the surrounding code and apply common sense.