Боке - Как иметь один и тот же виджет (или дублировать виджет) на двух разных вкладках? - PullRequest
0 голосов
/ 24 мая 2018

Я пытаюсь создать фильтр виджетов (состоящий из TextInput и MultiSelect), который реплицируется на две разные вкладки Bokeh .Желаемая функциональность заключается в том, что результаты фильтрации должны сохраняться между вкладками, независимо от того, какой фильтр получает текст для фильтрации.

Приведенный ниже код (это рабочий код) создает виджет Filter, который создается какfilter1 и filter2.Обратным вызовом является функция update, которая выполняет фактическую фильтрацию и обновляет часть фильтра MultiSelect.

from bokeh.io import curdoc
from bokeh.layouts import column, widgetbox, row, layout, gridplot
from bokeh.models import Slider, Select, TextInput, MultiSelect
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial


df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])


multiselect = None
input_box = None


def update(widget, attr, old, new):
    print("df['fruits']: {}".format(list(df['fruits'])))
    print("{} : {} changed: Old [ {} ] -> New [ {} ]".format(widget, attr, old, new))

    if widget == 'input':
        col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
        print("col_date: {}".format(col_data))
        multiselect.update(options = sorted(list(col_data)))


def init():
    global multiselect
    multiselect = MultiSelect(title = 'multiselect',
                              name = 'multiselect',
                              value = [],
                              options = list(df["fruits"]))
    multiselect.on_change('value', partial(update,  multiselect.name))

    global input_box
    input_box = TextInput(title = 'input',
                           name ='input',
                           value='Enter you choice')
    input_box.on_change('value', partial(update, input_box.name))

class Filter:
    def __init__(self):
        self.multiselect = multiselect
        self.input_box = input_box
        self.widget = widgetbox(self.input_box, self.multiselect)

init()
filter1 = Filter().widget
filter2 = Filter().widget

curdoc().add_root(row(filter1, filter2))

Приведенный выше код создает / собирает виджет, как показано здесь:

enter image description here

Кроме того, функциональность двух зеркальных фильтров является желаемой;когда текст вводится в одно из полей, результаты отображаются на обоих фильтрах.

Теперь, и здесь, где мне нужна помощь, я хочу использовать те же фильтры с той же функциональностью , но Они нужны мне в двух разных вкладках;один фильтр на одной вкладке, а другой фильтр на другой вкладке.

Код, используемый для построения структуры двух вкладок:

p1 = Panel(child = filter1, title = "Panel1")

p2 = Panel(child = filter2, title = "Panel2")

tabs = Tabs(tabs=[ p1, p2 ])
curdoc().add_root(layout(tabs))

На стороне результатов код сохраняет требуемыйфункциональность , но фильтры отображаются на той же странице.Более того, панели / вкладки даже не создаются.
Есть идеи, чего не хватает?(Если вы хотите поиграть с кодом, он должен работать сразу после установки Bokeh.)

enter image description here

Ответы [ 2 ]

0 голосов
/ 25 мая 2018

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

Мое решение похоже на решение HYRY, но с более общей функцией для обмена атрибутами, используя две разные вещи:

model.properties_with_values ​​()

Может использоваться с любой моделью боке и возвращает словарьвсе пары атрибут: значение модели.В ipython в основном полезно исследовать объекты bokeh и отлаживать

Document.select ({'type': model_type})

Генератор всех виджетов нужного типав документе

Затем я просто отфильтровываю виджеты, которые не разделяют те же теги, что и виджет ввода, что позволит избежать "синхронизации" других входов / множественного выбора, не сгенерированных с помощью box_maker ().Я использую теги, потому что разные модели не могут иметь одно и то же имя.

Когда вы изменяете значение TextInput, это изменит связанный Multiselect в функции обновления, но также изменит все другие TextInputs и запустит их обновление вТочно так же.Таким образом, каждый триггер Input обновляется один раз и меняет параметры соответствующего им мультиселекта (и не умножает каждый раз, потому что это обратный вызов on_change, если вы задаете то же значение для нового входа, который он не запускает).

Для Multiselect первый триггер обновления выполнит свою работу, но, поскольку он изменил значения другого Multiselect, он все еще будет запускаться столько раз, сколько существует виджетов Multiselect.

from bokeh.io import curdoc
from bokeh.layouts import widgetbox
from bokeh.models import TextInput, MultiSelect
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial


df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])

def sync_attr(widget):
    prop = widget.properties_with_values() # dictionary of attr:val pairs of the input widget
    for elem in curdoc().select({'type':type(widget)}): # loop over widgets of the same type
        if (elem!=widget) and (elem.tags==widget.tags): # filter out input widget and those with different tags
            for key in prop: # loop over attributes
                setattr(elem,key,prop[key]) # copy input properties

def update(attr,old,new,widget,other_widget):
    print("\ndf['fruits']: {}".format(list(df['fruits'])))
    print("{} : {} changed: Old [ {} ] -> New [ {} ]".format(str(widget),attr, old, new))

    if type(widget)==TextInput:
        col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
        print("col_date: {}".format(col_data))
        other_widget.update(options = sorted(list(col_data)))

    sync_attr(widget)

def box_maker():
    multiselect = multiselect = MultiSelect(title = 'multiselect',tags=['in_box'],value = [],options = list(df["fruits"]))
    input_box = TextInput(title = 'input',tags=['in_box'],value='Enter you choice')

    multiselect.on_change('value',partial(update,widget=multiselect,other_widget=input_box))
    input_box.on_change('value',partial(update,widget=input_box,other_widget=multiselect))

    return widgetbox(input_box, multiselect)

box_list = [box_maker() for i in range(2)]

tabs = [Panel(child=box,title="Panel{}".format(i)) for i,box in enumerate(box_list)]

tabs = Tabs(tabs=tabs)
curdoc().add_root(tabs)

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

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

0 голосов
/ 24 мая 2018

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

from bokeh.io import curdoc
from bokeh.layouts import column, widgetbox, row, layout, gridplot
from bokeh.models import Slider, Select, TextInput, MultiSelect, CustomJS
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial


df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])

class Filter:
    def __init__(self):
        self.multiselect = MultiSelect(title = 'multiselect',
                                  name = 'multiselect',
                                  value = [],
                                  options = list(df["fruits"]))
        self.multiselect.on_change('value', self.selection_changed)

        self.input_box = TextInput(title = 'input',
                               name ='input',
                               value='Enter you choice')
        self.input_box.on_change('value', self.input_box_updated)

        self.widget = widgetbox(self.input_box, self.multiselect)

    def input_box_updated(self, attr, old, new):
        print(attr, old, new)
        col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
        self.multiselect.update(options = sorted(list(col_data)))

    def selection_changed(self, attr, old, new):
        print(new)

filter1 = Filter()
filter2 = Filter()

def link_property(property_name, *widgets):
    wb = widgetbox(*widgets)

    wb.tags = [property_name, 0]
    def callback(widgets=wb):
        if widgets.tags[1] != 0:
            return
        widgets.tags[1] = 1
        for widget in widgets.children:
            widget[widgets.tags[0]] = cb_obj.value
        widgets.tags[1] = 0

    jscallback = CustomJS.from_py_func(callback)

    for widget in widgets:
        widget.js_on_change(property_name, jscallback)

link_property("value", filter1.input_box, filter2.input_box) 
link_property("value", filter1.multiselect, filter2.multiselect)        
p1 = Panel(child = filter1.widget, title = "Panel1")
p2 = Panel(child = filter2.widget, title = "Panel2")

tabs = Tabs(tabs=[ p1, p2 ])
curdoc().add_root(layout(tabs))

Кажется, в MultiSelect есть ошибка, которая не отменяет выбор предыдущих элементов.

...