Как получить список событий и атрибутов виджета Bokeh (которые могут быть использованы для запуска обратного вызова Python)

#python #bokeh

#python #bokeh

Вопрос:

Реальный (общий) вопрос

Я новичок в Bokeh и пытаюсь построить график, который может динамически обновляться на основе входных данных, предоставляемых виджетом. Однако использование обратных вызовов Python не полностью документировано для большинства виджетов, и поэтому я застрял.

  1. Как я могу узнать, какой метод виджета мне следует использовать для подключения моего обратного вызова? Я могу угадать доступные варианты, проверив атрибуты виджетов в интерактивной консоли, но это не элегантно, и я уверен, что это написано где-то в документации.
  2. При условии, что я буду знать об используемом методе (например, on_event или on_change ), мне все равно нужно выяснить его сигнатуру и аргументы. Например, если я использую on_change , какие атрибуты виджета я могу отслеживать?
  3. Как только я узнаю, какой атрибут я могу отслеживать, как я могу узнать структуру данных, которая будет получена событием?

Еще немного контекста и (не столь полезный) конкретный вопрос

Вот подходящий пример. Я использую сервер, встроенный в ноутбук, как в этом примере. В качестве упражнения я хотел бы заменить ползунок на 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)
  

Ответ №1:

Этот ответ был дан для Bokeh версии v1.0.4 и может не соответствовать последней документации

Обратные вызовы JavaScript и Python являются очень мощными инструментами в Bokeh и могут быть прикреплены к любому элементу модели 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, мы не можем напрямую видеть id , name , style или text атрибуты, которые происходят из его базовых классов. Однако все эти атрибуты присутствуют в прототипе JSON Div и, следовательно, поддерживаются 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
}
  

Возвращаясь к вашему вопросу: много раз вы можете достичь одного и того же результата, используя разные подходы.

Насколько мне известно, не существует хорошего метода, который перечислял бы все поддерживаемые события для каждого виджета, но чтение документации и изучение базовых классов очень помогает.

Используя методы, описанные выше, можно проверить, какие атрибуты виджета вы можете использовать в своих обратных вызовах. Когда дело доходит до событий, я советую вам взглянуть на bokeh.events класс в вашей IDE и изучить его. Вы можете найти там расширенное описание для каждого события. Со временем, используя интуицию вашего программиста, вы сможете естественным образом выбрать правильное событие, которое поддерживает ваш виджет (таким образом, нет button_click for Plot и нет pan event for Button , а наоборот).

Решение, к какому виджету (элементу модели) присоединить обратный вызов и какой метод выбрать или к какому событию привязать обратный вызов, остается за вами и зависит главным образом от: какое действие пользователя должно вызвать ваш обратный вызов?

Таким образом, вы можете привязать обратный вызов JS к любому виджету (изменение значения, перемещение слайдера и т.д.), Любому инструменту (TapTool, HoverTool и т.д.), data_source (нажатие на глиф), построение холста (например, для кликов по области за пределами глифа) или диапазона построения (события масштабирования или панорамирования) и т.д…

В принципе, вам нужно знать, что все объекты Python имеют свои эквиваленты в BokehJS, поэтому вы можете использовать их одинаково в обоих доменах (с некоторыми синтаксическими различиями, конечно).

В этой документации показано, например, что ColumnDataSource имеет свойство «selected», поэтому для точек вы можете проверить source.selected.indices и посмотреть, какие точки на графике выбраны или, как в вашем случае: какие строки таблицы выбраны. Вы можете установить точку останова в коде на Python, а также в браузере и проверить структуры данных Python или BokehJS. Это помогает установить переменную среды BOKEH_MINIFIED равной no либо в вашей IDE (конфигурация запуска), либо в терминале (например, BOKEH_MINIFIED=no python3 main.py ) при запуске вашего кода. Это значительно упростит отладку BokehJS в браузере.

И вот ваш код (слегка измененный для «чистого Bokeh» версии v1.0.4, поскольку у меня не установлен Jupiter Notebook)

 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)
  

Результат:

введите описание изображения здесь