Bokeh Property Editor#

Download this notebook from GitHub (right-click to download).


import numpy as np
import panel as pn

pn.extension()

Bokeh’s property system defines the valid properties for all the different Bokeh models. Using jslink we can easily tie a widget value to Bokeh properties on another widget or plot. This example defines functions that generate a property editor for the most common Bokeh properties. First, we define two functions that generate a set of widgets linked to a plot:

from bokeh.core.enums import LineDash, LineCap, MarkerType, NamedColor
from bokeh.models.plots import Model, _list_attr_splat
def meta_widgets(model, width=500):
    tabs = pn.Tabs(width=width)
    widgets = get_widgets(model, width=width-25)
    if widgets:
        tabs.append((type(model).__name__, widgets))
    for p, v in model.properties_with_values().items():
        if isinstance(v, _list_attr_splat):
            v = v[0]
        if isinstance(v, Model):
            subtabs = meta_widgets(v)
            if subtabs is not None:
                tabs.append((p.title(), subtabs))
                
    if hasattr(model, 'renderers'):
        for r in model.renderers:
            tabs.append((type(r).__name__, meta_widgets(r)))
    if hasattr(model, 'axis') and isinstance(model.axis, list):
        for pre, axis in zip('XY', model.axis):
            tabs.append(('%s-Axis' % pre, meta_widgets(axis)))
    if hasattr(model, 'grid'):
        for pre, grid in zip('XY', model.grid):
            tabs.append(('%s-Grid' % pre, meta_widgets(grid)))
    if not widgets and not len(tabs) > 1:
        return None
    elif not len(tabs) > 1:
        return tabs[0]
    return tabs
def get_widgets(model, skip_none=True, **kwargs):
    widgets = []
    for p, v in model.properties_with_values().items():
        if isinstance(v, dict):
            if 'value' in v:
                v = v.get('value')
            else:
                continue
        if v is None and skip_none:
            continue
                
        ps = dict(name=p, value=v, **kwargs)
        if 'alpha' in p:
            w = pn.widgets.FloatSlider(start=0, end=1, **ps)
        elif 'color' in p:
            if v in list(NamedColor):
                w = pn.widgets.Select(options=list(NamedColor), **ps)
            else:
                w = pn.widgets.ColorPicker(**ps)
        elif p=="width":
            w = pn.widgets.IntSlider(start=400, end=800, **ps)
        elif p in ["inner_width", "outer_width"]:
            w = pn.widgets.IntSlider(start=0, end=20, **ps)
        elif p.endswith('width'):
            w = pn.widgets.FloatSlider(start=0, end=20, **ps)
        elif 'marker' in p:
            w = pn.widgets.Select(options=list(MarkerType), **ps)
        elif p.endswith('cap'):
            w = pn.widgets.Select(options=list(LineCap), **ps)
        elif p == 'size':
            w = pn.widgets.FloatSlider(start=0, end=20, **ps)
        elif p.endswith('text') or p.endswith('label'):
            w = pn.widgets.TextInput(**ps)
        elif p.endswith('dash'):
            patterns = list(LineDash)
            w = pn.widgets.Select(options=patterns, value=v or patterns[0], **kwargs)
        else:
            continue
        w.jslink(model, value=p)
        widgets.append(w)
    return pn.Column(*sorted(widgets, key=lambda w: w.name))

Having defined these helper functions we can now declare a plot and use the meta_widgets function to generate the GUI:

from bokeh.plotting import figure

p = figure(title='This is a title', x_axis_label='x-axis', y_axis_label='y-axis')
xs = np.linspace(0, 10)
r = p.scatter(xs, np.sin(xs))

editor=pn.Row(meta_widgets(p), p)
editor

App#

Lets wrap it into nice template that can be served via panel serve bokeh_property_editor.ipynb

pn.template.FastListTemplate(
    site="Panel", title="Bokeh Property Editor", 
    main=[pn.pane.Markdown("The Bokeh Property Editor enables you to fine tune the Bokeh plot.", sizing_mode="stretch_width"), editor]
).servable();
This web page was generated from a Jupyter notebook and not all interactivity will work on this website. Right click to download and run locally for full Python-backed interactivity.

Download this notebook from GitHub (right-click to download).