1

我正在尝试创建一个在两个不同的 Bokeh Tabs上复制的小部件过滤器(由TextInput和组成) 。所需的功能是过滤结果应该保留在选项卡之间,无论哪个过滤器接收要过滤掉的文本。MultiSelect

下面的代码(它是工作代码)构建了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))

在结果方面,代码保留了所需的功能,但过滤器显示在同一页面上。不仅如此,甚至还没有构建面板/选项卡。
知道缺少什么吗?(如果您想使用代码,如果您安装了散景,它应该可以立即工作。)

在此处输入图像描述

4

2 回答 2

2

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

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

于 2018-05-24T02:41:38.720 回答
2

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

我的解决方案类似于 HYRY 的解决方案,但具有更通用的功能,可以使用两种不同的方式共享属性:

model.properties_with_values()

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

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

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

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

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

对于多选,更新的第一个触发器将完成这项工作,但由于它改变了另一个多选的值,它仍然触发与多选小部件一样多的次数。

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)

请注意,多选中选项的突出显示可能看起来不一致,但这似乎是视觉上的,因为它们中的每一个的值/选项都在正确更改。

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

于 2018-05-24T22:35:22.620 回答