代码之家  ›  专栏  ›  技术社区  ›  flamenco

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

  •  1
  • flamenco  · 技术社区  · 8 年前

    我试图创建一个小部件过滤器(由 TextInput MultiSelect )在两个不同的博客上复制 制表符 . 所需的功能是,无论哪个过滤器接收要过滤的文本,都应在选项卡之间保留过滤结果。

    下面的代码(它是工作代码)生成 Filter 被实例化为 filter1 filter2 . 回调是 update 执行实际筛选并更新 多选 过滤器的一部分。

    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 回复  |  直到 8 年前
        1
  •  1
  •   Seb    8 年前

    我认为您的示例甚至不应该构建文档,您的文本输入和多选择模型都具有相同的ID,这可能是选项卡显示混乱的原因。

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

    model.properties_with_values()。

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

    document.select('type':模型)

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

    然后,我过滤掉那些与输入小部件不共享相同标签的小部件,这样可以避免“同步”其他输入/不与box_maker()生成的多重选择。我使用标签是因为不同的型号不能有相同的名称。

    当您更改textinput值时,它将更改更新函数中关联的multisect,但它也将更改所有其他textinput并以相同的方式触发它们的更新。因此,每个输入触发器都会更新一次并更改其各自的多选择选项(而不是每次都进行多次更新,因为它是一个“on-change”回调,如果为新输入赋予相同的值,则不会触发)。

    对于multisect,更新的第一个触发器将执行该操作,但由于它更改了其他multisect的值,因此它仍会触发与multisect小部件相同的次数。

    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)
    

    请注意,在multisect中突出显示的选项可能看起来不一致,但当每个选项的值/选项都正确更改时,这似乎是可视的。

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

        2
  •  2
  •   HYRY    8 年前

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

    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 这不会取消选择以前的项目。

    推荐文章