Bokeh erweitern#

Bokeh verfügt über eine Vielzahl von integrierten Typen, mit denen interaktive Visualisierungen und Datenanwendungen im Browser erstellt werden können. Es gibt jedoch spezifische Funktionen, die nicht in die Kernbibliothek aufgenommen wurden. Es gibt jedoch die Möglichkeit, Bokeh zu erweitern

  • um das Verhalten vorhandener Bokeh-Modelle zu verändern

  • um neue Modelle hinzuzufügen, die JavaScript-Bibliotheken von Drittanbietern mit Python verbinden

  • um spezialisierte Modelle für bestimmte Fachdomänen zu erstellen

Solche benutzerdefinierten Erweiterungen können mit Standard-Releases erstellt und verwendet werden. Dabei ist es nicht erforderlich, eine Entwicklungsumgebung einzurichten oder etwas aus den Sourcen zu erstellen.

Struktur von Bokeh-Modellen#

Python-Modelle#

JavaScript-Modelle und Ansichten#

Während die Python-Seite meistens deklarativ ist, ohne viel oder echten Code, erfordert die JavaScript-Seite Code um das Modell zu implementieren. Gegebenenfalls muss auch Code für eine entsprechende Ansicht bereitgestellt werden.

Nachfolgend findet ihr eine kommentierte TypeScript-Implementierung für Custom und CustomView. Bei integrierten Modellen ist dieser Code direkt in den endgültigen BokehJS-Skripts enthalten. Im nächsten Abschnitt wird gezeigt, wie ihr diesen Code mit benutzerdefinierten Erweiterungen verknüpfen könnt.

Hinweis:

BokehJS wurde ursprünglich in CoffeeScript geschrieben, wird jedoch nach TypeScript portiert. Dementsprechend ist die Anleitung hier in TypeScript. Benutzerdefinierte Erweiterungen können jedoch auch in CoffeeScript oder in reinem JavaScript geschrieben werden.

import {UIElement, UIElementView} from "models/ui/ui_element"

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

export class CustomView extends UIElementView {

  override connect_signals(): void {
    super.connect_signals()

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

  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.shadow_el.appendChild(div({
      style: {
        padding: '2px',
        color: '#b88d8e',
        backgroundColor: '#2a3153',
      },
    }, `${this.model.text}: ${this.model.slider.value}`))
  }
}

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

  // Generally, the ``__name__`` class attribute should match the name of
  // the corresponding Python class exactly. TypeScript matches the name
  // automatically during compilation, so, barring some special cases, you
  // don't have to do this manually. This helps avoid typos, which stop
  // serialization/deserialization of the model.
  static __name__ = "Surface3d"

  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>(({String, Ref}) => ({
      text:   [ String ],
      slider: [ Ref(Slider) ],
    }))
  }
}

Zusammenfügen#

Bei integrierten Bokeh-Modellen wird die Implementierung in BokehJS vom Build-Prozess automatisch mit dem entsprechenden Python-Modell abgeglichen. Um JavaScript-Implementierungen mit Python-Modellen zu verbinden, ist ein zusätzlicher Schritt erforderlich. Die Python-Klasse sollte über ein Klassenattribut __implementation__ verfügen, dessen Name der TypeScript-Code (oder JavaScript- oder CoffeeScript-Code) ist, der das clientseitige Modell sowie optionale Ansichten definiert.

Vorausgesetzt, der obige TypeScript-Code wurde in einer Datei custom.ts gespeichert, könnte die vollständige Python-Klasse folgendermaßen aussehen:

[1]:
from bokeh.core.properties import Instance, String
from bokeh.models import LayoutDOM, Slider


class Custom(LayoutDOM):
    __implementation__ = "custom.ts"

    text = String(default="Custom text")

    slider = Instance(Slider)

Wenn diese Klasse dann in einem Python-Modul custom.py definiert ist, kann die benutzerdefinierte Erweiterung jetzt genau wie jedes integrierte Bokeh-Modell verwendet werden:

[2]:
from bokeh.io import output_file, show
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)

Integration in Bokeh Server#

Es sind keine besonderen Arbeiten oder Modifikationen erforderlich, um benutzerdefinierte Erweiterungen in den Bokeh-Server zu integrieren. Wie bei Standalone-Dokumenten ist die JavaScript-Implementierung automatisch in der gerenderten Anwendung enthalten. Zusätzlich erfolgt die Standardsynchronisation der Bokeh-Modelleigenschaften, die für alle integrierten Modelle gilt, auch transparent für benutzerdefinierte Erweiterungen.

Beispiele#

In der Bokeh-Dokumentation stehen einige vollständige Beispiele zur Verfügung, die als Referenz dienen sollen. In vielen Fällen ist es erforderlich, den Quellcode der Basisklassen in bokehjs/src/lib/models zu studieren.