自定义扩展#
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 布局中。它还添加了两个属性:
这将创建一个与Python中的Slider相对应的JavaScript Slider对象。
JavaScript 模型和视图#
虽然Python端几乎没有代码,但JavaScript端需要代码来实现模型。您还需要在必要时为相应的视图提供代码。
这里是一个关于Custom及其CustomView的带注释的TypeScript实现。对于内置模型,这种类型的代码直接包含在最终的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库
通过将Python包装在Bokeh自定义扩展中,连接到第三方JavaScript库。
- 添加自定义小部件
在扩展小部件中包含一个第三方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 命令从头开始重新构建您的扩展。