Skip to main content

BokehGarden

BokehGarden is a Bokeh based web application framework focused on plotting heavy interactive applications.

Additionally, it provides several improved widgets for Bokeh, that can be used both within the framework and in a standalone bokeh app:

  • Interactive colorbar
    • That allows adjusting the color map by pan and zoom
  • File download button
    • That goes directly over http (not websocket)
  • File upload field
    • That goes directly over http (not websocket), meaning that it does not have a size limit, nor does it encode the file as base64.
  • Autocomplete input field
    • That fixes the text entry vs drop down selection problem
  • Drop down menu
    • That encodes the entry id:s as json, allowing e.g. integers to be used as id:s, not just strings.

Example application

bokeh serve examples/weather_data_explorer.py

Framework usage

The BokehGarden application framework consists of a very old idea, applied to Bokeh, combined with a small tool. The simple idea is to create complex UI element from simpler ones by subclassing Bokeh widget classes. In the subclass, the __init__() method is used to populate the widget with parts and bind callbacks.

class MyForm(bokeh_garden.application.AppWidget, bokeh.layouts.Column):
    def __init__(self, app=None, **kw):
        self._app = app

        self._foo = bokeh.models.Slider(title="Select a value for foo", value=0, start=0, end=100, step=1)
        self._bar = bokeh.models.NumericInput(title="Bar value", mode="float", value=10)
        self._submit_button = bokeh.models.Button(label="Submit", button_type="primary")
        self._submit_button.on_event(bokeh.events.ButtonClick, self.on_submit)
        
        bokeh.layouts.Column.__init__(
          self, self._foo, self._bar, self._submit_button, **kw)

    def on_submit(self, event):
        print("Submitted values:", self._foo.value, self._bar.value)

The above widget could be used as any other Bokeh widget in your plots.

Additionally, it could be used inside another widget of the same type, to recursively build up complex interfaces. But there is a better way: Layouts and overlays.

Layouts

A layout is a simple dictionary based data structure that defines the layout of an application declaratively. It is equivalent to instantiating widgets, sending them as parameters when instantiating other widgets to gradually build up the application layout, just like you normally do in bokeh. However, since it's data, not code, it's easy to store and modify the structure. Example layout data as python code:

layout = {"widget": "bokeh_garden.tabs.Tabs",
          "Data": {"widget": "weather.DataLoaderForm"},
          "Sea": {"widget": "bokeh.models.layouts.Column",
                  "children": [{"widget": "weather.WavesPlot", "unit": "kn"},
                               {"widget": "weather.CurentsPlot", "unit": "kn"}]},
          "Air": {"widget": "bokeh.models.layouts.Row",
                  "children": [{"widget": "weather.PressurePlot", "tags": ["wind_map"]},
                               {"widget": "weather.WindPlot", "tags": ["wind_map"]}]}}

At each level, the layout data structure consists of a dictionary

{"widget": "modulename.submodulename.classname", "initparamname1": "initparamvalue1", "initparamname2": "initparamvalue2", ... } 

with the key widget holding the name (and module path) of a widget class, and other keys being used to generate the parameters to __init__() for that class. For scalar values, these are just passed as-is, but for dictionaries and lists, they are first instantiated the same way recursively.

__init__() will also receive an instance of bokeh_garden.application.Application as a first argument. This instance is shared among all widgets created using the layout. The layout above would translate into

layout_instance = bokeh_garden.tabs.Tabs(
    app,
    Data=weather.DataLoaderForm(app),
    Sea=bokeh.models.layouts.Column(
        *[weather.WavesPlot(app, unit="kn"),
          weather.CurentsPlot(app, unit="kn")]),
    Air=bokeh.models.layouts.Row(
        *[weather.PressurePlot(app, tags=["wind_map"]),
          weather.WindPlot(app, tags=["wind_map"])]))

For larger applications is recomended to store this data structure as a separate yaml file. This makes getting a quick overview over the layout easier, as well as eases merging layout changes in git. Here's the same data as above but in yaml syntax:

layout:
  widget: bokeh_garden.tabs.Tabs
  Data:
    widget: weather.DataLoaderForm
  Sea:
    widget: bokeh.models.layouts.Column
    children:
      - widget: weather.WavesPlot
        unit: kn
      - widget: weather.CurentsPlot
        unit: kn
  Air:
    widget: bokeh.models.layouts.Row
    children:
      - widget: weather.PressurePlot
        tags: ["wind_map"]
      - widget: weather.WindPlot
        tags: ["wind_map"]

Overlays

The layout structure above lets you build complex applications from highly independent widgets. But it has one drawback: What if you need to make widgets in different parts of the application interact? Overlays solve this problem.

Overlays are ordinary python classes that has access to the document and the widget tree, can query the widget tree and modify the widgets in it by e.g. binding callbacks or adding drawing operations (layers) to plots.

class WindMapLinkedMapExtentsOverlay(object):
    def __init__(self, app):
        self.app = app

        x_range = None
        y_range = None
        for figure in self.app.doc.select({"tags": ["wind_map"]}):
            if x_range is None:
                x_range = figure.x_range
                y_range = figure.y_range
            else:
                figure.x_range = x_range
                figure.y_range = y_range

overlays = [{"widget": WindMapLinkedMapExtentsOverlay},
            ...]

Instantiating your layout

Once you have built a layout and defined your overlays, you can instantiate them as a Bokeh application using the following code (notebook version):

bokeh.io.output_notebook()
application = bokeh_garden.application.Application(layout, overlays)
bokeh.io.show(application)

or (application file for bokeh serve):

application = bokeh_garden.application.Application(layout, overlays)
application(bokeh.plotting.curdoc())

Metadata

Release files for bokeh-garden 0.0.15

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bokeh-garden 0.0.15
File Size Uploaded Provenance
bokeh_garden-0.0.15.tar.gz 26.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for bokeh-garden 0.0.15
File Interpreter ABI Platform
bokeh_garden-0.0.15-py3-none-any.whl Python 3 none any Details

Total release size: 58.4 kB

Release files / bokeh_garden-0.0.15.tar.gz

Download URL bokeh_garden-0.0.15.tar.gz
Size 26.3 kB
Tags Source
SHA-256 checksum
How to use checksums
0c8c28bc16914c5f90fdfd3380107bd0fd05577ff88726604e37405331a30e41
BLAKE2b-256 checksum
How to use checksums
ce2511406872cc7353fc5771fa982bff873f7936ab71afd0d09e6c3ffb566385
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Release files / bokeh_garden-0.0.15-py3-none-any.whl

Download URL bokeh_garden-0.0.15-py3-none-any.whl
Size 32.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9621831f29b171cf4d4a1ec32ea47d1b0c5a563398a01603b6b85c46cc46fdf8
BLAKE2b-256 checksum
How to use checksums
cebbf762bf1860a60cd1747c7ef125f199b8d2024fedf2febf0cfbf6c84c616b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Security for bokeh-garden 0.0.15

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page