How do I create and use a custom Bokeh Model in Panel?

Hi

I’m trying to learn some of the more advanced features of Bokeh/ Panel in order to eventually contribute a PyDeck/ Deck.gl panel. See this Pull Request.

In order to do the implementation I need to learn several several things. And the current thing I’m trying to learn is how to create and use custom Bokeh models in Panel.

I’ve started i’ve with an ambition to implement the Custom model in the Bokeh extensions user guide.

So I’ve created a custom.py file with

"""One Super Power of Panel is that its actually extensible.

You can write custom Panes, Layouts and Widgets just the way that Panel is built. The starting point
is [Extending Bokeh](https://docs.bokeh.org/en/latest/docs/user_guide/extensions.html)
"""

import panel as pn
from bokeh.core.properties import Instance, String
from bokeh.io import output_file, show
from bokeh.layouts import column
from bokeh.models import HTMLBox, Slider

pn.extension()


class Custom(HTMLBox):

    __implementation__ = "custom.ts"

    text = String(default="Custom text")

    slider = Instance(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)


app = pn.pane.Bokeh(layout)
app.servable()

and a custom.ts file with

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__ = "Custom"

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

But when running panel serve custom.py --show I get the below error message and I’m stuck.

$ panel serve custom.py --show
2020-01-19 06:28:05,804 Starting Bokeh server version 1.4.0 (running on Tornado 5.1.1)
2020-01-19 06:28:05,806 User authentication hooks NOT provided (default user enabled)
2020-01-19 06:28:05,810 Bokeh app running at: http://localhost:5006/custom
2020-01-19 06:28:05,810 Starting Bokeh server with process id: 10640
Compilation failed:

←[96mcustom.ts←[0m:←[93m13←[0m:←[93m33←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m13←[0m         this.connect(this.model.slider.change, () => {
←[7m  ←[0m ←[91m                                ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m26←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'text' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                         ~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m46←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                                             ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m55←[0m:←[93m21←[0m - ←[91merror←[0m←[90m TS2702: ←[0m'Custom' only refers to a type, but is being used as a namespace here.

←[7m55←[0m         this.define<Custom.Props>({
←[7m  ←[0m ←[91m                    ~~~~~~←[0m
2020-01-19 06:28:13,070 Uncaught exception
Traceback (most recent call last):
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 338, in bundle_models
    _bundle_cache[key] = bundle = _bundle_models(custom_models)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 551, in _bundle_models
    custom_impls = _compile_models(custom_models)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 531, in _compile_models
    raise CompilationError(compiled.error)
bokeh.util.compiler.CompilationError:
←[96mcustom.ts←[0m:←[93m13←[0m:←[93m33←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m13←[0m         this.connect(this.model.slider.change, () => {
←[7m  ←[0m ←[91m                                ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m26←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'text' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                         ~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m46←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                                             ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m55←[0m:←[93m21←[0m - ←[91merror←[0m←[90m TS2702: ←[0m'Custom' only refers to a type, but is being used as a namespace here.

←[7m55←[0m         this.define<Custom.Props>({
←[7m  ←[0m ←[91m                    ~~~~~~←[0m

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\http1connection.py", line 238, in _read_message
    delegate.finish()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\routing.py", line 256, in finish
    self.delegate.finish()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\web.py", line 2195, in finish
    self.execute()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\web.py", line 2228, in execute
    **self.path_kwargs)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 346, in wrapper
    runner = Runner(result, future, yielded)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1080, in __init__
    self.run()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1147, in run
    yielded = self.gen.send(value)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\web.py", line 1590, in _execute
    result = method(*self.path_args, **self.path_kwargs)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 346, in wrapper
    runner = Runner(result, future, yielded)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1080, in __init__
    self.run()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1147, in run
    yielded = self.gen.send(value)
  File "C:\Users\masma\AppData\Local\Programs\Python\Python37\lib\types.py", line 208, in send
    return self.__wrapped.send(val)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\views\doc_handler.py", line 61, in get
    template_variables=session.document.template_variables)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\embed\server.py", line 227, in server_html_page_for_session
    bundle = bundle_for_objs_and_resources(None, resources)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\embed\bundle.py", line 184, in bundle_for_objs_and_resources
    ext = bundle_models(models)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 342, in bundle_models
    sys.exit(1)
SystemExit: 1
Compilation failed:

←[96mcustom.ts←[0m:←[93m13←[0m:←[93m33←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m13←[0m         this.connect(this.model.slider.change, () => {
←[7m  ←[0m ←[91m                                ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m26←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'text' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                         ~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m46←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                                             ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m55←[0m:←[93m21←[0m - ←[91merror←[0m←[90m TS2702: ←[0m'Custom' only refers to a type, but is being used as a namespace here.

←[7m55←[0m         this.define<Custom.Props>({
←[7m  ←[0m ←[91m                    ~~~~~~←[0m
2020-01-19 06:28:18,757 Uncaught exception
Traceback (most recent call last):
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 338, in bundle_models
    _bundle_cache[key] = bundle = _bundle_models(custom_models)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 551, in _bundle_models
    custom_impls = _compile_models(custom_models)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 531, in _compile_models
    raise CompilationError(compiled.error)
bokeh.util.compiler.CompilationError:
←[96mcustom.ts←[0m:←[93m13←[0m:←[93m33←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m13←[0m         this.connect(this.model.slider.change, () => {
←[7m  ←[0m ←[91m                                ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m26←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'text' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                         ~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m46←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                                             ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m55←[0m:←[93m21←[0m - ←[91merror←[0m←[90m TS2702: ←[0m'Custom' only refers to a type, but is being used as a namespace here.

←[7m55←[0m         this.define<Custom.Props>({
←[7m  ←[0m ←[91m                    ~~~~~~←[0m

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\http1connection.py", line 238, in _read_message
    delegate.finish()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\routing.py", line 256, in finish
    self.delegate.finish()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\web.py", line 2195, in finish
    self.execute()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\web.py", line 2228, in execute
    **self.path_kwargs)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 346, in wrapper
    runner = Runner(result, future, yielded)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1080, in __init__
    self.run()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1147, in run
    yielded = self.gen.send(value)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\web.py", line 1590, in _execute
    result = method(*self.path_args, **self.path_kwargs)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 346, in wrapper
    runner = Runner(result, future, yielded)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1080, in __init__
    self.run()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1147, in run
    yielded = self.gen.send(value)
  File "C:\Users\masma\AppData\Local\Programs\Python\Python37\lib\types.py", line 208, in send
    return self.__wrapped.send(val)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\views\doc_handler.py", line 61, in get
    template_variables=session.document.template_variables)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\embed\server.py", line 227, in server_html_page_for_session
    bundle = bundle_for_objs_and_resources(None, resources)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\embed\bundle.py", line 184, in bundle_for_objs_and_resources
    ext = bundle_models(models)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 342, in bundle_models
    sys.exit(1)
SystemExit: 1
Compilation failed:

←[96mcustom.ts←[0m:←[93m13←[0m:←[93m33←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m13←[0m         this.connect(this.model.slider.change, () => {
←[7m  ←[0m ←[91m                                ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m26←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'text' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                         ~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m46←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                                             ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m55←[0m:←[93m21←[0m - ←[91merror←[0m←[90m TS2702: ←[0m'Custom' only refers to a type, but is being used as a namespace here.

←[7m55←[0m         this.define<Custom.Props>({
←[7m  ←[0m ←[91m                    ~~~~~~←[0m
2020-01-19 06:28:24,013 Uncaught exception
Traceback (most recent call last):
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 338, in bundle_models
    _bundle_cache[key] = bundle = _bundle_models(custom_models)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 551, in _bundle_models
    custom_impls = _compile_models(custom_models)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 531, in _compile_models
    raise CompilationError(compiled.error)
bokeh.util.compiler.CompilationError:
←[96mcustom.ts←[0m:←[93m13←[0m:←[93m33←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m13←[0m         this.connect(this.model.slider.change, () => {
←[7m  ←[0m ←[91m                                ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m26←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'text' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                         ~~~~←[0m
←[96mcustom.ts←[0m:←[93m32←[0m:←[93m46←[0m - ←[91merror←[0m←[90m TS2339: ←[0mProperty 'slider' does not exist on type 'HTMLBox'.

←[7m32←[0m         }, `${this.model.text}: ${this.model.slider.value}`))
←[7m  ←[0m ←[91m                                             ~~~~~~←[0m
←[96mcustom.ts←[0m:←[93m55←[0m:←[93m21←[0m - ←[91merror←[0m←[90m TS2702: ←[0m'Custom' only refers to a type, but is being used as a namespace here.

←[7m55←[0m         this.define<Custom.Props>({
←[7m  ←[0m ←[91m                    ~~~~~~←[0m

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\http1connection.py", line 238, in _read_message
    delegate.finish()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\routing.py", line 256, in finish
    self.delegate.finish()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\web.py", line 2195, in finish
    self.execute()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\web.py", line 2228, in execute
    **self.path_kwargs)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 346, in wrapper
    runner = Runner(result, future, yielded)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1080, in __init__
    self.run()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1147, in run
    yielded = self.gen.send(value)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\web.py", line 1590, in _execute
    result = method(*self.path_args, **self.path_kwargs)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 346, in wrapper
    runner = Runner(result, future, yielded)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1080, in __init__
    self.run()
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\tornado\gen.py", line 1147, in run
    yielded = self.gen.send(value)
  File "C:\Users\masma\AppData\Local\Programs\Python\Python37\lib\types.py", line 208, in send
    return self.__wrapped.send(val)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\views\doc_handler.py", line 61, in get
    template_variables=session.document.template_variables)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\embed\server.py", line 227, in server_html_page_for_session
    bundle = bundle_for_objs_and_resources(None, resources)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\embed\bundle.py", line 184, in bundle_for_objs_and_resources
    ext = bundle_models(models)
  File "c:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\util\compiler.py", line 342, in bundle_models
    sys.exit(1)
SystemExit: 1

How do I solve this? Any help is appreciated thanks.

Just as additional info.

I tried the example from here

https://docs.bokeh.org/en/latest/docs/user_guide/extensions_gallery/ticking.html#userguide-extensions-examples-ticking

and I can successfylly run that using panel serve

from bokeh.io import show
from bokeh.models import TickFormatter
from bokeh.plotting import figure
from bokeh.util.compiler import TypeScript
import panel as pn

TS_CODE = """
import {TickFormatter} from "models/formatters/tick_formatter"

export class MyFormatter extends TickFormatter {
  // TickFormatters should implement this method, which accepts a list
  // of numbers (ticks) and returns a list of strings
  doFormat(ticks: string[] | number[]) {
    // format the first tick as-is
    const formatted = [`${ticks[0]}`]
    for (let i = 1, len = ticks.length; i < len; i++) {
      formatted.push(`+${(Number(ticks[i]) - Number(ticks[0])).toPrecision(2)}`)
    }
    return formatted
  }
}
"""


class MyFormatter(TickFormatter):

    __implementation__ = TypeScript(TS_CODE)


p = figure()
p.circle([1, 2, 3, 4, 6], [5, 7, 3, 2, 4], size=20)

p.xaxis.formatter = MyFormatter()

app = pn.pane.Bokeh(p)
app.servable()

And I can see that if I run the original example without Panel but only with Bokeh as described in the Bokeh documentation I still get the exception. So I’ve raised an issue with Bokeh

Problem solved. It turned out the example in the Bokeh documentation does not work.

I got it working. See See https://github.com/bokeh/bokeh/issues/9587

I’ve added it as an example to the gallery at awesome-panel.org