自定义扩展#
Bokeh 提供了丰富的内置功能,可让您在浏览器中创建复杂的交互式可视化和数据应用程序。但是,某些有用的功能和特性可能不会包含在核心库中,要么是因为它们过于专业,要么是因为资源不足。幸运的是,您可以使用自定义扩展来扩展 Bokeh 的功能,这些扩展可以让您
修改现有 Bokeh 模型的行为
添加新模型以将第三方 JavaScript 库连接到 Python
创建高度专业化的模型以用于特定领域的用例
您可以使用标准发布版本创建和使用自定义扩展,无需设置开发环境或从源代码构建任何内容。这是参与 Bokeh 开发最简单的方法。您可以尝试新功能和改进的功能,而无需等待核心团队将其实现到 Bokeh 本身中。
注意
扩展 Bokeh 是一项高级功能。创建和使用扩展的某些方面仍在积极开发中,应视为实验性。
Bokeh 模型结构#
Python 模型#
在大多数情况下,Python Bokeh 模型是完全声明式的类。您可以通过从 Model
创建子类并包含特殊类属性来声明要在 JavaScript 端镜像的属性,从而创建自定义扩展。有关所有可用属性类型的详细信息,请参见 bokeh.core.properties 部分的 参考指南。
以下是一个简单的示例,它为滑块创建自定义读数
from bokeh.core.properties import String, Instance
from bokeh.models import UIElement, Slider
class Custom(UIElement):
text = String(default="Custom text")
slider = Instance(Slider)
此示例从 UIElement
创建子类,以允许扩展集成到 DOM 布局中。它还添加了两个属性
这会创建一个 JavaScript Slider
对象,它对应于 Python 中的 Slider
。
JavaScript 模型和视图#
虽然 Python 端几乎没有代码,但 JavaScript 端需要代码来实现模型。您还需要提供必要的对应视图的代码。
以下是一个带有注释的 TypeScript 实现,用于 Custom
及其 CustomView
。对于内置模型,这种类型的代码直接包含在最终的 BokehJS 脚本中。
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 <div> elements by default. These are accessible
// as ``this.el``. Many Bokeh views ignore the default <div> and
// instead do things like draw to the HTML canvas. In this case though,
// the program changes the contents of the <div> based on the current
// slider value.
super.render()
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<Props>
export type Props = UIElement.Props & {
text: p.Property<string>
slider: p.Property<Slider>
}
}
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) ],
}))
}
}
组合在一起#
对于内置的 Bokeh 模型,构建过程会自动将 BokehJS 中的实现与相应的 Python 模型匹配。Python 类还应该有一个名为 __implementation__
的类属性,其值为定义客户端模型的 JavaScript(或 TypeScript)代码以及任何可选视图。
假设您将前一个示例中的 TypeScript 代码保存在名为 custom.ts
的文件中,完整的 Python 类可能如下所示
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)
假设 Python 模块 custom.py
定义了此类,您现在可以使用自定义扩展,就像使用任何内置 Bokeh 模型一样。
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)
这会产生以下输出
渲染的文档会自动包含实现的 JavaScript 代码。移动滑块以查看滑块移动时特殊标题的更新。
指定实现语言#
如果 __implementation__
的值为以 .js
或 .ts
结尾的单行,Bokeh 会将其解释为文件名,打开文件,并根据文件的扩展名编译其内容。
对于倾斜的实现,请使用类 JavaScript
或 TypeScript
为源代码指定语言。以下是一个示例
class Custom(Model):
__implementation__ = JavaScript(" <JS code here> ")
指定默认值#
如果您的属性具有默认值,您必须在 Python 端和 JavaScript 端提供默认值。您提供的值在两端应相同。出于效率考虑,Bokeh 只会传输用户明确更改其默认值的属性值。
作为一个具体的示例,一个默认值为 True 的布尔属性 flag
在 Python 端应该如下所示
flag = Bool(default=True)
它在 Bokeh 端应该如下所示
flag: [ Boolean, true ]
提供外部资源#
您可能需要第三方 JavaScript 库或 CSS 资源来在 Bokeh 中实现自定义模型。您可以通过自定义模型的 __javascript__
和 __css__
Python 类属性来提供外部资源。
包含指向外部资源的 URL 路径会将它们添加到 HTML 文档的头部,从而使 JavaScript 库在全局命名空间中可用,并应用自定义 CSS 样式。
以下是一个示例,它包括 KaTeX(一个具有 LaTeX 支持的 JS 库)的 JS 和 CSS 文件,以创建 LatexLabel
自定义模型。
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
"""
有关完整实现及其输出,请参见以下扩展画廊中的 LaTeX 示例。
与 Bokeh 服务器集成#
您无需执行任何额外操作即可将自定义扩展与 Bokeh 服务器集成。与独立文档一样,渲染的应用程序会自动包含 JavaScript 实现。此外,Bokeh 模型属性的标准同步对于自定义用户扩展是透明的,与内置模型相同。
示例#
本节旨在为您提供一些基本示例,以帮助您开始创建自定义扩展。但是,这是一个相当高级的主题,您通常需要研究 bokehjs/src/lib/models 中基类的源代码才能取得进展。
- 专门的轴刻度
对内置的 Bokeh 模型进行子类化,以自定义轴刻度行为。
- 一个新的自定义工具
创建一个可以绘制在绘图画布上的全新工具。
- 包装 JavaScript 库
通过将第三方 JavaScript 库包装在 Bokeh 自定义扩展中,将 Python 连接到该库。
- 添加自定义部件
在扩展部件中包含第三方 JavaScript 库。
预构建扩展#
到目前为止,本章介绍了简单的、通常是内联扩展。这些对于 Bokeh 的临时添加非常有用,但这种方法在涉及到严肃开发时并不十分方便。
例如,某些配置文件(如 package.json
或 tsconfig.json
)的隐式性质不允许您在编写 TypeScript 或 JavaScript 用于扩展时充分利用 IDE 的功能。
使用预构建扩展。
要创建预构建扩展,请使用 bokeh init
命令。这会创建所有必要的文件,包括 bokeh.ext.json
、package.json
和 tsconfig.json
。
要逐步创建和自定义扩展,请运行 bokeh init --interactive
。
要构建扩展,请使用 bokeh build
命令。这会在必要时运行 npm install
,编译 TypeScript 文件,转译 JavaScript 文件,解析模块,并将它们链接到可分发的捆绑包中。
Bokeh 会缓存编译产品以提高性能。如果这会导致问题,请使用 bokeh build --rebuild
命令从头开始重建扩展。