{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import panel as pn\n", "\n", "pn.extension()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bokeh.core.enums import LineDash, LineCap, MarkerType, NamedColor\n", "from bokeh.models.plots import Model, _list_attr_splat" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def meta_widgets(model, width=500):\n", " tabs = pn.Tabs(width=width)\n", " widgets = get_widgets(model, width=width-25)\n", " if widgets:\n", " tabs.append((type(model).__name__, widgets))\n", " for p, v in model.properties_with_values().items():\n", " if isinstance(v, _list_attr_splat):\n", " v = v[0]\n", " if isinstance(v, Model):\n", " subtabs = meta_widgets(v)\n", " if subtabs is not None:\n", " tabs.append((p.title(), subtabs))\n", " \n", " if hasattr(model, 'renderers'):\n", " for r in model.renderers:\n", " tabs.append((type(r).__name__, meta_widgets(r)))\n", " if hasattr(model, 'axis') and isinstance(model.axis, list):\n", " for pre, axis in zip('XY', model.axis):\n", " tabs.append(('%s-Axis' % pre, meta_widgets(axis)))\n", " if hasattr(model, 'grid'):\n", " for pre, grid in zip('XY', model.grid):\n", " tabs.append(('%s-Grid' % pre, meta_widgets(grid)))\n", " if not widgets and not len(tabs) > 1:\n", " return None\n", " elif not len(tabs) > 1:\n", " return tabs[0]\n", " return tabs" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_widgets(model, skip_none=True, **kwargs):\n", " widgets = []\n", " for p, v in model.properties_with_values().items():\n", " if isinstance(v, dict):\n", " if 'value' in v:\n", " v = v.get('value')\n", " else:\n", " continue\n", " if v is None and skip_none:\n", " continue\n", " \n", " ps = dict(name=p, value=v, **kwargs)\n", " if 'alpha' in p:\n", " w = pn.widgets.FloatSlider(start=0, end=1, **ps)\n", " elif 'color' in p:\n", " if v in list(NamedColor):\n", " w = pn.widgets.Select(options=list(NamedColor), **ps)\n", " else:\n", " w = pn.widgets.ColorPicker(**ps)\n", " elif p==\"width\":\n", " w = pn.widgets.IntSlider(start=400, end=800, **ps)\n", " elif p in [\"inner_width\", \"outer_width\"]:\n", " w = pn.widgets.IntSlider(start=0, end=20, **ps)\n", " elif p.endswith('width'):\n", " w = pn.widgets.FloatSlider(start=0, end=20, **ps)\n", " elif 'marker' in p:\n", " w = pn.widgets.Select(options=list(MarkerType), **ps)\n", " elif p.endswith('cap'):\n", " w = pn.widgets.Select(options=list(LineCap), **ps)\n", " elif p == 'size':\n", " w = pn.widgets.FloatSlider(start=0, end=20, **ps)\n", " elif p.endswith('text') or p.endswith('label'):\n", " w = pn.widgets.TextInput(**ps)\n", " elif p.endswith('dash'):\n", " patterns = list(LineDash)\n", " w = pn.widgets.Select(options=patterns, value=v or patterns[0], **kwargs)\n", " else:\n", " continue\n", " w.jslink(model, value=p)\n", " widgets.append(w)\n", " return pn.Column(*sorted(widgets, key=lambda w: w.name))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Having defined these helper functions we can now declare a plot and use the ``meta_widgets`` function to generate the GUI:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bokeh.plotting import figure\n", "\n", "p = figure(title='This is a title', x_axis_label='x-axis', y_axis_label='y-axis')\n", "xs = np.linspace(0, 10)\n", "r = p.scatter(xs, np.sin(xs))\n", "\n", "editor=pn.Row(meta_widgets(p), p)\n", "editor" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## App\n", "\n", "Lets wrap it into nice template that can be served via `panel serve bokeh_property_editor.ipynb`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.template.FastListTemplate(\n", " site=\"Panel\", title=\"Bokeh Property Editor\", \n", " main=[pn.pane.Markdown(\"The Bokeh Property Editor enables you to fine tune the Bokeh plot.\", sizing_mode=\"stretch_width\"), editor]\n", ").servable();" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }