为BokehJS做贡献#

BokehJS 是用户最终与之交互的浏览器内客户端运行时库。该库主要用 TypeScript 编写,是 Bokeh 绘图系统的独特之处之一。

所有Bokeh可视化的核心构建块都是基于Bokeh的模型的对象。这些模型是绘图元素的表示,例如轴、字形小部件

在Python方面,Bokeh将每个绘图元素对象的属性序列化为JSON数据。在浏览器方面,BokehJS将这些JSON数据反序列化,并根据这些信息创建JavaScript对象。然后,BokehJS使用这些JavaScript对象来渲染可视化。

流程图描述了数据从Python对象通过JSON流向浏览器端的过程。在那里,JSON数据被转换为JavaScript对象,然后被渲染为输出。输出可以是HTML Canvas、WebGL或SVG。

这种Python和JavaScript的结合允许你在Python中定义可视化,同时利用在浏览器中运行的JavaScript提供的所有交互性。此外,这种结合使你能够几乎完全使用Python进行数据处理,并使用大型和流式的服务器端数据进行交互式JavaScript可视化。

除了使用BokehJS基于JSON数据创建可视化外,您还可以将BokehJS作为独立的JavaScript库使用。有关直接使用BokehJS创建可视化的更多信息,请参见BokehJS

源代码位置#

Bokeh 仓库包含 Bokeh 的 Python 代码以及 BokehJS 的 JavaScript 代码。BokehJS 的源代码位于此单仓库中的 bokehjs 目录中。

所有进一步的说明和shell命令都假设 bokehjs/ 是您的当前目录。

提示

将您的IDE的工作文件夹设置为bokehjs目录。这样,您的IDE中的一些工具可能会更好地与BokehJS源代码配合使用。

BokehJS的CSS#

BokehJS 的 CSS 定义包含在 bokehjs/src/less/ 目录中的几个 .less 文件中。所有 Bokeh DOM 元素的 CSS 类都以 bk- 为前缀。例如:.bk-plot.bk-tool-button

代码风格指南#

BokehJS 没有明确的风格指南。在提交到 Bokeh 仓库之前,请确保运行 node make lintnode make lint --fix。 此外,请查看周围的代码,并尽量与现有代码保持一致。

在开发BokehJS时需要记住的一些指南和提示:

  • 不要使用for-in循环,特别是没有使用hasOwnProperty()保护的循环。请使用for-of循环,并结合keys()values()和/或entries()方法,这些方法来自core/util/object模块。

  • 默认情况下,使用双引号("string")表示字符串。在需要避免转义引号的情况下使用单引号(case '"': return """)。

  • 使用模板字面量(模板字符串)来处理多行、标记和插值字符串(`Bokeh ${Bokeh.version}`)。

始终使用ESLint检查您的BokehJS代码:从bokehjs目录中,运行node make lint来检查您的代码。运行node make lint --fix可以让ESLint自动修复一些问题。更多详情,请参见bokehjs/eslint.js中定义的规则。

提示

如果您使用VSCode,您可以为工作区使用以下配置,以便直接在编辑器中使用ESLint:

"eslint.format.enable": true,
"eslint.lintTask.enable": true,
"eslint.debug": false,
"eslint.quiet": false,
"eslint.options": {
  "cache": true,
  "extensions": [".ts"],
  "overrideConfigFile": "./eslint.js"
},
"eslint.workingDirectories": [
  "./bokehjs"
]

这需要安装VSCode的ESLint扩展以及ESLint版本8或以上。

开发需求#

要在本地构建和测试 BokehJS,请按照设置开发环境中的说明进行操作。这样,所有必需的包都应该在您的系统上安装和配置好。

开发BokehJS需要以下最低版本:

  • Node.js 18+

  • npm 8+

  • Chrome/Chromium 浏览器 118+ 或同等版本

Bokeh 官方支持以下平台进行开发和测试:

  • Linux Ubuntu 22.04+ 或同等版本

  • Windows 10(或 Server 2019)

  • MacOS 10.15

可以在不同的平台和版本上使用BokehJS进行工作。 然而,事情可能不会按预期进行,一些测试将无法工作。

构建BokehJS#

为了构建,BokehJS依赖于一个类似于gulp的自定义工具。所有命令都以node make开头。

使用 node make help 列出 BokehJS 构建系统的所有可用命令。以下是最常见的命令:

  • node make build: 构建整个库,包括扩展编译器。

  • node make dev: 构建库时不使用扩展编译器。这比node make build更快,但不适用于生产代码或打包。

  • node make test: 运行所有BokehJS测试。要仅运行特定测试,请参见 选择特定的BokehJS测试

  • node make lint 使用 ESLint 对 BokehJS 进行 lint 检查。运行 node make lint --fix 可以让 ESLint 自动修复一些问题。

node makepackage.json 发生变化时自动运行 npm install

测试#

Bokeh 仓库包含多个测试套件。这些测试有助于确保 BokehJS 作为自己的库以及与其他所有 Bokeh 组件的组合中都能一致地运行。

要了解更多关于本地运行BokehJS测试的信息,请参阅 运行JavaScript测试

要了解更多关于为BokehJS添加和更新测试的信息,请参阅 编写JavaScript测试(BokehJS)

BokehJS中的模型和视图#

BokehJS 中可视化的基本构建块是模型和视图:

Models

模型是一种数据结构,可能有也可能没有视觉表示。 BokehJS的模型及其属性与Bokeh的Python代码中的模型和相应属性相匹配。Bokeh 使用默认测试 来确保模型在Python和BokehJS之间保持兼容。

Views

视图定义了模型的可视化表示。任何影响浏览器中事物外观的模型都需要一个相应的视图。

对于每个模型,模型定义和相应的视图应位于bokehjs/models目录中的同一文件中。

提示

在更新或添加新模型和视图时,请查看当前如何实现类似的模型和视图。

模型和视图的基类#

BokehJS 模型通常扩展一个基类。例如:Axis 模型 扩展了 GuideRendererCircle 模型扩展了 XYGlyph

模型的相应视图扩展了相应的基础视图类。例如:AxisView 扩展了 GuideRendererViewCircleView 扩展了 XYGlyphView

假设你想定义一种新的Bokeh工具栏的动作按钮工具,称为NewActionTool。你的新按钮的模型将继承自ActionTool,其对应的视图将继承自ActionToolView

import {ActionTool, ActionToolView} from "./action_tool"

模型#

BokehJS 模型需要一个 namespaceinterface。至少,这包括 AttrsProps。还有更多你可以使用的属性,比如 MixinsVisuals

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 {}

如果你想更新一个模型,在大多数情况下最相关的属性是 Props。你在那里定义的属性需要与相应的Python模型的属性和类型匹配。BokehJS属性在 core.properties中定义,通常通过 import * as p from "core/properties"导入。

接下来,定义实际的模型本身。该模型扩展了相应的BaseModel基类。如果您的模型包含视图,这也是您链接模型和视图的地方。

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)
    }))
  }
}

视图#

如果你的模型需要与显示相关的逻辑,你需要定义一个视图。视图通常处理模型在浏览器中的显示方式。

视图扩展了相应的 BaseView 基类。

export class NewActionToolView extends ActionToolView {
  declare 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)
  }

  ...
}