扩展Bokeh

Bokeh提供了丰富的内置类型,可用于在浏览器中制作复杂的交互式可视化和数据应用程序。但是,有些功能和特性可能是用户想要的,这些功能和特性可能无法进入核心库,这可能是因为它们过于专业化,或者是由于缺乏资源。幸运的是,可以通过创建自定义用户扩展来扩展Bokeh。

  • 修改现有Bokeh模型的行为

  • 添加新模型以将第三方JavaScript库连接到Python

  • 为特定领域的用例创建高度专业化的模型。

自定义扩展可以在标准版本中创建和使用,并且不需要设置开发环境或从源代码构建任何东西。它们为参与Bokeh开发提供了最简单的方法。通过降低扩展Bokeh的标准,用户可以在不必等待核心团队的情况下“试用”新特性和改进的功能(可能有一天会成为添加到核心库的候选)。

Bokeh模型的结构

Python模型

在大多数情况下,Python-Bokeh模型完全是声明类。自定义扩展是通过创建子类来创建的 Model (或其子类之一),并包含特殊的类属性来声明在JavaScript端镜像的属性。所有可用的属性类型都记录在 bokeh.core.properties 参考指南的一节。

下面是一个为滑块创建自定义读数的小示例:

from bokeh.core.properties import String, Instance
from bokeh.models import HTMLBox, Slider

class Custom(HTMLBox):

    text = String(default="Custom text")

    slider = Instance(Slider)

因为我们想创建一个可以参与DOM布局的自定义扩展,我们从 HTMLBox . 我们还添加了两个属性:a String 为文本和消息配置读出 Instance 可以容纳 Slider . JavaScript Slider 对象对应于Python Slider 将可供使用。

JavaScript模型和视图

虽然Python端主要是声明性的,没有太多或任何真正的代码,但JavaScript端需要代码来实现模型。适当时,还必须提供相应视图的代码。

下面是的带注释的TypeScript实现 Custom 及其 CustomView . 对于内置模型,此代码直接包含在最终BokehJS脚本中。我们将在下一节中看到如何将此代码连接到自定义扩展。

import {HTMLBox, HTMLBoxView} from "models/layouts/html_box"

import {div} from "core/dom"
import * as p from "core/properties"

export class CustomView extends HTMLBoxView {

  connect_signals(): void {
    super.connect_signals()

    // Set BokehJS listener so that when the Bokeh slider has a change
    // event, we can process the new data.
    this.connect(this.model.slider.change, () => {
      this.render()
      this.invalidate_layout()
    })
  }

  render(): void {
    // BokehjS Views create <div> elements by default, accessible as
    // ``this.el``. Many Bokeh views ignore this default <div>, and instead
    // do things like draw to the HTML canvas. In this case though, we change
    // the contents of the <div>, based on the current slider value.
    super.render()

    this.el.appendChild(div({
      style: {
        padding: '2px',
        color: '#b88d8e',
        backgroundColor: '#2a3153',
      },
    }, `${this.model.text}: ${this.model.slider.value}`))
  }
}

export class Custom extends HTMLBox {
  slider: {value: string}

  // The ``__name__`` class attribute should generally match exactly the name
  // of the corresponding Python class. Note that if using TypeScript, this
  // will be automatically filled in during compilation, so except in some
  // special cases, this shouldn't be generally included manually, to avoid
  // typos, which would prohibit serialization/deserialization of this model.
  static __name__ = "Surface3d"

  static init_Custom(): void {
    // 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, e.g. bokeh.core.properties.String will be
    // ``p.String`` in the JS implementation. Any time the JS type system is not
    // yet as complete, you can use ``p.Any`` as a "wildcard" property type.
    this.define<Custom.Props>({
      text:   [ p.String ],
      slider: [ p.Any    ],
    })
  }
}

把它放在一起

对于内置的Bokeh模型,BokehJS中的实现通过构建过程自动与相应的Python模型匹配。为了将JavaScript实现连接到Python模型,还需要一个额外的步骤。Python类应该有一个名为 __implementation__ 其值是定义客户端模型和任何可选视图的TypeScript(或JavaScript)代码。

假设上面的TypeScript代码保存在一个文件中 custom.ts ,则完整的Python类可能如下所示:

from bokeh.core.properties import String, Instance
from bokeh.models import HTMLBox, Slider

class Custom(HTMLBox):

    __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 ,它被解释为文件名。打开相应的文件,并根据文件扩展名对其内容进行适当编译。

否则,如果实现在类中是内联的,则可以通过使用类显式地提供源代码的语言 JavaScriptTypeScript ,例如

class Custom(Model):

    __implementation__ = JavaScript(" <JS code here> ")

提供外部资源

作为在Bokeh中实现自定义模型的一部分,可能需要包含第三方Javascript库或CSS资源。Bokeh支持通过Python类属性提供外部资源 __javascript____css__ 定制模型。

包括指向外部资源的URL路径将导致Bokeh将资源添加到HTML文档头,从而使Javascript库在全局命名空间中可用,并应用自定义CSS样式。

一个例子是包含的JS和CSS文件 KaTeX (一个基于Javascript的支持LaTeX的排版库)来创建一个 LatexLabel 自定义模型。

class LatexLabel(Label):
    """A subclass of the Bokeh built-in `Label` that supports rendering
    LaTeX using 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库

使用Bokeh自定义扩展将Python连接到第三方JavaScript库。

创建 Latex 标签

包含第三方JavaScript库以呈现LaTeX。

添加自定义小部件

在扩展小部件中包含第三方JavaScript库。

预建扩展

到目前为止,我们讨论了简单的,典型的内联扩展。这些对于Bokeh的临时添加非常好,但是像这样严肃的开发很快就会变得非常乏味。例如,在IDE中编写扩展的TypeScript或JavaScript文件不允许我们充分利用IDE的功能,因为某些配置文件的隐式特性如下 package.jsontsconfig.json . 这可以通过使用另一种方法来解决,Bokeh扩展是预构建的扩展。

要创建预构建扩展,可以使用 bokeh init 命令创建所有必需的文件,包括 bokeh.ext.jsonpackage.jsontsconfig.json 可能还有其他人。另外,使用 bokeh init --interactive 允许我们逐步创建和自定义扩展。以后,可以使用 bokeh build 命令。这是跑步 npm install 如果需要,可以编译TypeScript文件,透明JavaScript文件,解析模块,并将它们链接到可分发的bundle中。缓存的产品可提高编译性能。如果这导致问题,可以使用 bokeh build --rebuild 命令。