Metadata-Version: 2.4
Name: bokeh-garden
Version: 0.0.15
Summary: Additional widgets for Bokeh
Author-email: Egil Moeller <em@emeraldgeo.no>
License-Expression: MIT
Project-URL: Homepage, https://github.com/EMeraldGeo/BokehGarden
Project-URL: Repository, https://github.com/EMeraldGeo/BokehGarden
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: bokeh>=3.0.0
Requires-Dist: streaming-form-data
Requires-Dist: contextvars
Dynamic: license-file

# BokehGarden

BokehGarden is a [Bokeh](https://bokeh.org/) 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())
