如何获取Bokeh小部件事件和属性的列表(可用于触发Python回调) [英] How to get a list of Bokeh widget events and attributes (which can be used to trigger a Python callback)

查看:69
本文介绍了如何获取Bokeh小部件事件和属性的列表(可用于触发Python回调)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

真正的(一般)问题

我是Bokeh的新手,我正在尝试构建一个可以根据小部件提供的输入动态更新的图.但是,对于大多数小部件,Python回调的用法尚未完全记录下来,因此我陷入了困境.

  1. 我如何知道应该使用哪种窗口小部件方法来附加我的回调?我可以通过在交互式控制台中探查widgets属性来猜测可用的选择,但这并不优雅,并且我敢肯定它写在文档中的某个地方.
  2. 假设我知道要使用的方法(例如on_eventon_change),我仍然必须弄清楚其签名和参数.例如,如果我使用on_change,我可以监视哪些窗口小部件属性?
  3. 一旦我知道可以监视的属性,我如何知道事件将产生的数据结构?

更多上下文和(未使用)特定问题

这是一个适当的示例.我正在使用 this中的笔记本嵌入式服务器例子.作为练习,我想用带有任意值的DataTable替换滑块.这是我目前拥有的代码:

 from bokeh.layouts import column
from bokeh.models import ColumnDataSource, DataTable
from bokeh.plotting import figure
from bokeh.io import show, output_notebook

from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature

output_notebook()

def modify_doc(doc):
    df = sea_surface_temperature.copy()
    source = ColumnDataSource(data=df)
    source_table = ColumnDataSource(data={"alpha": [s for s in "abcdefgh"], 
                                          "num": list(range(8))})

    plot = figure(x_axis_type='datetime', y_range=(0, 25),
                  y_axis_label='Temperature (Celsius)',
                  title="Sea Surface Temperature at 43.18, -70.43")
    plot.line('time', 'temperature', source=source)

    def callback(attr, old, new):
        # This is the old callback from the example. What is "new" when I use 
        # a table widget?
        if new == 0:
            data = df
        else:
            data = df.rolling('{0}D'.format(new)).mean()
        source.data = ColumnDataSource(data=data).data

    table = DataTable(source=source_table, 
                      columns=[TableColumn(field="alpha", title="Alpha"),
                               TableColumn(field="num", title="Num")])
    # How can I attach a callback to table so that the plot gets updated 
    # with the "num" value when I select a row?
    # table.on_change("some_attribute", callback)

    doc.add_root(column(table, plot))

show(modify_doc)
 

解决方案

此答案是针对Bokeh v1.0.4给出的,可能与最新文档不符

JavaScript回调 Python回调,是Bokeh中非常强大的工具,可以附加到任何Bokeh模型元素.此外,您可以通过使用TypeScript编写自己的扩展名(最终编译为JS)

可以使用以下两种方法之一添加JS回调:

Model.js_on_event('event', callback)
Model.js_on_change('attr', callback)

Python回调主要用于小部件:

Widget.on_event('event, onevent_handler)
Widget.on_change('attr', onchange_handler)
Widget.on_click(onclick_handler)

每个小部件的事件处理程序的确切函数签名可以是:

onevent_handler(event)
onchange_handler(attr, old, new) 
onclick_handler(new)
onclick_handler()

attr可以是任何窗口小部件类(或它的基类)属性.因此,您始终需要查阅 Bokeh参考页.扩展JSON原型也有助于找出支持的属性,例如查看 Div ,我们无法直接看到来自其基类的idnamestyletext属性.但是,所有这些属性都存在于Div的JSON原型中,因此受Div支持:

{
  "css_classes": [],
  "disabled": false,
  "height": null,
  "id": "32025",
  "js_event_callbacks": {},
  "js_property_callbacks": {},
  "name": null,
  "render_as_text": false,
  "sizing_mode": "fixed",
  "style": {},
  "subscribed_events": [],
  "tags": [],
  "text": "",
  "width": null
}

回到您的问题:很多时候,您可以使用不同的方法获得相同的结果.

据我所知,没有一种很好的方法可以列出每个小部件的所有受支持的事件,但是阅读文档并深入研究基类会大有帮助.

使用上述方法,可以检查您可以在回调中使用哪些小部件属性.当涉及到事件时,建议您在IDE中查看和探索bokeh.events类.您可以在其中找到每个事件的扩展说明.随着时间的推移,使用程序员的直觉来选择窗口小部件支持的正确事件将自然而然地发生(因此对于Plot来说没有button_click,对于Button来说没有pan事件,反之亦然).

决定是将哪个小部件(模型元素)附加到回调以及选择哪种方法或将回调绑定到哪个事件的决定主要取决于:哪个用户操作应触发您的回调? >

因此,您可以将JS回调附加到任何小部件(值更改,滑块移动等),任何工具(TapTool,HoverTool等...),data_source(单击字形),绘制画布(例如点击字形以外的区域)或绘图范围(缩放或平移事件),等等...

基本上,您需要了解所有Python对象在BokehJS中都有它们的等效项,因此您可以在两个域中以相同的方式使用它们(当然,在语法上有所不同).

本文档例如显示ColumnDataSource具有"selected"属性,因此对于点,您可以检查source.selected.indices并查看选择了绘图上的哪个点,或者与您的情况类似:选择了哪些表行.您可以在Python和浏览器中的代码中设置断点,并检查Python或BokehJS数据结构.在运行代码时,可以在IDE(运行配置)或终端(例如BOKEH_MINIFIED=no python3 main.py)中将环境变量BOKEH_MINIFIED设置为no.这将使在浏览器中调试BokehJS更加容易.

这是您的代码(由于我没有安装Jupiter Notebook,因此对"pure Bokeh" v1.0.4做了一些修改)

from bokeh.layouts import column
from bokeh.models import ColumnDataSource, DataTable, TableColumn
from bokeh.plotting import figure, curdoc
from bokeh.io import show, output_notebook
from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature

# output_notebook()
def modify_doc(doc):
    df = sea_surface_temperature.copy()
    source = ColumnDataSource(data = df)
    source_table = ColumnDataSource(data = {"alpha": [s for s in "abcdefgh"],
                                            "num": list(range(8))})

    plot = figure(x_axis_type = 'datetime', y_range = (0, 25),
                  y_axis_label = 'Temperature (Celsius)',
                  title = "Sea Surface Temperature at 43.18, -70.43")
    plot.line('time', 'temperature', source = source)

    def callback(attr, old, new):  # here new is an array containing selected rows
        if new == 0:
            data = df
        else:
            data = df.rolling('{0}D'.format(new[0])).mean()  # asuming one row is selected

        source.data = ColumnDataSource(data = data).data

    table = DataTable(source = source_table,
                      columns = [TableColumn(field = "alpha", title = "Alpha"),
                                 TableColumn(field = "num", title = "Num")])
    source_table.selected.on_change('indices', callback)

    doc().add_root(column(table, plot))

modify_doc(curdoc)
# show(modify_doc)

结果:

The real (general) question

I am new to Bokeh and I am trying to build a plot which can be dynamically updated based on input provided by a widget. However, usage of Python callbacks is not thoroughly documented for most widgets and therefore I'm stuck.

  1. How can I know which widget method I should use to attach my callback? I can guess the available choices by probing the widgets attributes in an interactive console, but that's not elegant and I'm sure it's written somewhere in the documentation.
  2. Provided that I would know about the method to use (e.g. on_event or on_change), I still have to figure out its signature and arguments. For instance, if I'm using on_change, which widget attributes can I monitor?
  3. Once I know which attribute I can monitor, how can I know the data structure which will be yielded by the event?

Some more context and the (not-as-useful) specific question

Here is an appropriate example. I am using a notebook-embedded server like in this example. As an exercise, I would like to replace the slider with a DataTable with arbitrary values. Here is the code I currently have:

from bokeh.layouts import column
from bokeh.models import ColumnDataSource, DataTable
from bokeh.plotting import figure
from bokeh.io import show, output_notebook

from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature

output_notebook()

def modify_doc(doc):
    df = sea_surface_temperature.copy()
    source = ColumnDataSource(data=df)
    source_table = ColumnDataSource(data={"alpha": [s for s in "abcdefgh"], 
                                          "num": list(range(8))})

    plot = figure(x_axis_type='datetime', y_range=(0, 25),
                  y_axis_label='Temperature (Celsius)',
                  title="Sea Surface Temperature at 43.18, -70.43")
    plot.line('time', 'temperature', source=source)

    def callback(attr, old, new):
        # This is the old callback from the example. What is "new" when I use 
        # a table widget?
        if new == 0:
            data = df
        else:
            data = df.rolling('{0}D'.format(new)).mean()
        source.data = ColumnDataSource(data=data).data

    table = DataTable(source=source_table, 
                      columns=[TableColumn(field="alpha", title="Alpha"),
                               TableColumn(field="num", title="Num")])
    # How can I attach a callback to table so that the plot gets updated 
    # with the "num" value when I select a row?
    # table.on_change("some_attribute", callback)

    doc.add_root(column(table, plot))

show(modify_doc)

解决方案

This answer was given for Bokeh v1.0.4 and may not be compliant with the latest documentation

JavaScript callbacks and Python callbacks, are very powerful tools in Bokeh and can be attached to any Bokeh model element. Additionally you can extend Bokeh functionality by writing your own extensions with TypeScript (eventually compiled to JS)

JS callbacks can be added using either of both methods:

Model.js_on_event('event', callback)
Model.js_on_change('attr', callback)

Python callbacks are mainly used for widgets:

Widget.on_event('event, onevent_handler)
Widget.on_change('attr', onchange_handler)
Widget.on_click(onclick_handler)

The exact function signature for event handlers very per widget and can be:

onevent_handler(event)
onchange_handler(attr, old, new) 
onclick_handler(new)
onclick_handler()

The attr can be any widget class (or it's base class) attribute. Therefore you need always to consult the Bokeh reference pages. Also expanding the JSON Prototype helps to find out which attributes are supported e.g. looking at Div we cannot see directly the id, name, style or text attributes which come from its base classes. However, all of these attributes are present in the Div's JSON Prototype and hence are supported by Div:

{
  "css_classes": [],
  "disabled": false,
  "height": null,
  "id": "32025",
  "js_event_callbacks": {},
  "js_property_callbacks": {},
  "name": null,
  "render_as_text": false,
  "sizing_mode": "fixed",
  "style": {},
  "subscribed_events": [],
  "tags": [],
  "text": "",
  "width": null
}

Coming back to your question: Many times you can achieve the same result using different approaches.

To my knowledge, there is no nice method that lists all supported events per widget but reading documentation and digging into the base classes helps a lot.

Using methods described above it is possible to check which widget attributes you can use in your callbacks. When it comes to events I advice you to look at and explore the bokeh.events class in your IDE. You can find there extended description for every event. In time it will come naturally when using your programmer's intuition to select the right event that your widget supports (so no button_click for Plot and no pan event for Button but the other way around).

Decision to which widget (model element) attach the callback and which method to choose or to which event bound the callback is yours and depends mainly on: which user action should trigger your callback?

So you can have a JS callback attached to any widget (value change, slider move, etc...), any tool (TapTool, HoverTool, etc...), data_source (clicking on glyph), plot canvas (e.g. for clicks on area outside a glyph) or plot range (zoom or pan events), etc...

Basically you need to know that all Python objects have their equivalents in BokehJS so you can use them the same way in both domains (with some syntax differences, of course).

This documentation shows for example that ColumnDataSource has a "selected" property so for points you can inspect source.selected.indices and see which point on the plot are selected or like in your case: which table rows are selected. You can set a breakpoint in code in Python and also in the browser and inspect the Python or BokehJS data structures. It helps to set the environment variable BOKEH_MINIFIED to no either in you IDE (Run Configuration) or in Terminal (e.g. BOKEH_MINIFIED=no python3 main.py) when running your code. This will make debugging the BokehJS in the browser much easier.

And here is your code (slightly modified for "pure Bokeh" v1.0.4 as I don't have Jupiter Notebook installed)

from bokeh.layouts import column
from bokeh.models import ColumnDataSource, DataTable, TableColumn
from bokeh.plotting import figure, curdoc
from bokeh.io import show, output_notebook
from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature

# output_notebook()
def modify_doc(doc):
    df = sea_surface_temperature.copy()
    source = ColumnDataSource(data = df)
    source_table = ColumnDataSource(data = {"alpha": [s for s in "abcdefgh"],
                                            "num": list(range(8))})

    plot = figure(x_axis_type = 'datetime', y_range = (0, 25),
                  y_axis_label = 'Temperature (Celsius)',
                  title = "Sea Surface Temperature at 43.18, -70.43")
    plot.line('time', 'temperature', source = source)

    def callback(attr, old, new):  # here new is an array containing selected rows
        if new == 0:
            data = df
        else:
            data = df.rolling('{0}D'.format(new[0])).mean()  # asuming one row is selected

        source.data = ColumnDataSource(data = data).data

    table = DataTable(source = source_table,
                      columns = [TableColumn(field = "alpha", title = "Alpha"),
                                 TableColumn(field = "num", title = "Num")])
    source_table.selected.on_change('indices', callback)

    doc().add_root(column(table, plot))

modify_doc(curdoc)
# show(modify_doc)

Result:

这篇关于如何获取Bokeh小部件事件和属性的列表(可用于触发Python回调)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆