我正在尝试创建一个小部件筛选器(由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的情况下应该可以立即使用。)
最佳答案
我认为您的示例甚至都不应该构建文档,您的textinputs和multiselect模型都具有相同的id,这可能就是为什么选项卡的显示混乱的原因。
我的解决方案类似于HYRY的解决方案,但是具有更通用的功能,可以使用两种不同的方式共享属性:
model.properties_with_values()
可以与任何bokeh模型一起使用,并返回该模型的所有attribute:value对的字典。在ipython中探索bokeh对象并进行调试最有用
Document.select({'type':model_type})
生成文档中所需类型的所有小部件的生成器
然后,我只过滤掉与输入小部件不共享相同标签的小部件,这将避免“同步”其他未使用box_maker()生成的输入/多选。我使用标签是因为不同的型号不能使用相同的名称。
更改TextInput值时,它将更改更新功能中的关联Multiselect,但同时也将更改所有其他TextInput,并以相同的方式触发其更新。因此,每个Input触发器都会更新一次并更改其各自的multiselect的选项(而不是多次,因为它们是“ 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)
请注意,多选中选项的突出显示可能看起来不一致,但是看起来似乎很直观,因为每个选项的值/选项都在正确更改。
但是,除非您将小部件放在面板中时特别喜欢布局外观,否则您可以只在外部放置一个输入和多选,然后编写它们的回调来处理不同面板中的情况。