Python Bokeh-如何在两个不同的选项卡中拥有相同的小部件(或复制一个小部件)?

Python Bokeh-如何在两个不同的选项卡中拥有相同的小部件(或复制一个小部件)?,python,python-3.x,bokeh,bokehjs,Python,Python 3.x,Bokeh,Bokehjs,我正在尝试创建一个小部件过滤器(由TextInput和MultiSelect组成),它在两个不同的Bokeh选项卡上复制。所需的功能是,无论哪个过滤器接收到要过滤的文本,都应在选项卡之间保留过滤结果 下面的代码(它是工作代码)构建了Filter小部件,该小部件被实例化为filter1和filter2。回调函数是update函数,它执行实际过滤并更新过滤器的MultiSelect部分 from bokeh.io import curdoc from bokeh.layouts import col

我正在尝试创建一个小部件过滤器(由
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))
上面的代码生成/组装小部件,如下所示:

此外,两个镜像过滤器的功能符合要求;在其中一个框中输入文本时,结果将显示在两个过滤器上

现在,这里是我需要帮助的地方,我想要具有相同功能的相同过滤器,但我需要在两个不同的选项卡中使用它们;一个过滤器在一个选项卡中,另一个过滤器在另一个选项卡中

用于构建两个选项卡结构的代码是:

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

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

tabs = Tabs(tabs=[ p1, p2 ])
curdoc().add_root(layout(tabs))
在结果端,代码保留了所需的功能,但过滤器显示在同一页面上。除此之外,甚至没有构建面板/选项卡。
知道少了什么吗?(如果你想玩代码,如果你已经安装了bokeh,它应该可以立即工作。)


不能使用同一个小部件模型创建多个视图。您可以在每个选项卡中创建新的小部件并链接值:

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
中有一个bug,它没有取消选择以前的项目。

您不能使用同一个小部件模型来创建多个视图。您可以在每个选项卡中创建新的小部件并链接值:

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
中似乎有一个bug,它没有取消选择以前的项目。

我认为您的示例甚至不应该构建一个文档,您的textinputs和MultiSelect模型都有相同的id,这可能是选项卡显示混乱的原因

我的解决方案与HYRY的类似,但有一个更通用的功能,可以使用两种不同的东西共享属性:

model.properties\u与\u值()

可以与任何bokeh模型一起使用,并返回模型的所有属性:值对的字典。在ipython中,探索bokeh对象和调试最有用

文档。选择({'type':model_type})

文档中所需类型的所有小部件的生成器

然后,我只需过滤掉与输入小部件不共享相同标记的小部件,这将避免“同步”其他输入/multiselect,而不是使用box_maker()生成。我使用标签是因为不同的型号不能有相同的名称

当您更改TextInput值时,它将更改update函数中关联的Multiselect,但它也将更改所有其他TextInput,并以相同的方式触发其更新。因此,每个输入都会触发一次更新,并更改其各自multiselect的选项(而不是每次多次,因为这是一个“on_change”回调,如果您为未触发的新输入提供相同的值)

对于Multiselect,update的第一个触发器将完成此任务,但由于它更改了其他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)
请注意,“multiselect”中选项的高亮显示可能看起来不一致,但这似乎是可视的,因为每个选项的值/选项都在正确更改


但是,除非您在将小部件放入面板时特别关注布局外观,否则您可以将一个输入和多个选择放在面板外部,并编写它们的回调来处理不同面板中的内容。

我认为您的示例甚至不应构建文档,textinputs和multiselect模型都具有相同的id,这可能就是选项卡显示混乱的原因

我的解决方案与HYRY的类似,但有一个更通用的功能,可以使用两种不同的东西共享属性:

model.properties\u与\u值()

可以与任何bokeh模型一起使用,并返回模型的所有属性:值对的字典。在ipython中,探索bokeh对象和调试最有用

文档。选择({'type':model_type})

文档中所需类型的所有小部件的生成器

然后,我只需过滤掉与输入小部件不共享相同标记的小部件,这将避免“同步”其他输入/multiselect,而不是使用box_maker()生成。我使用标签是因为不同的型号不能有相同的名称

当您更改TextInput值时,它将更改update函数中关联的Multiselect,但它也将更改所有其他TextInput,并以相同的方式触发其更新。因此,每个输入都会触发一次更新,并更改其各自multiselect的选项(而不是每次多次,因为这是一个“on_change”回调,如果您为未触发的新输入提供相同的值)

对于Multiselect,update的第一个触发器将完成此任务,但由于它更改了其他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)
请注意,“multiselect”中选项的高亮显示可能看起来不一致,但这似乎是可视的,因为每个选项的值/选项都在正确更改


但是,除非您在将小部件放在面板内时特别关注布局外观,否则您可以将一个输入和多个选择放在面板外,然后编写它们的回调来处理不同面板中的内容。

过滤不起作用。我对代码所做的唯一更改是将
show(布局(选项卡))
替换为
curdoc()