Bokeh - 使用 Select 小部件和 CustomJS 更新绘图

Bokeh - Update plot with Select widget and CustomJS

我正在尝试显示条形图并让 Select 对象过滤内容。尽管看起来很简单,但经过两天的寻找,我仍未找到可行的解决方案。我需要使用 CustomJS 来执行此操作,而不是散景服务器。

这是我正在尝试的代码,但是当我 运行 它没有显示任何内容,甚至没有空图。

import pandas as pd
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, CustomJS, CustomJSFilter, CDSView, Select, IndexFilter
from bokeh.io import show, output_notebook
from bokeh.layouts import column
output_notebook()

df = pd.DataFrame({'Subject': ['Math', 'Math', 'Math', 'Science', 'Science', 'Science'],
                  'Class': ['Algebra', 'Calculus', 'Trigonometry', 'Biology', 'Chemistry', 'Physics'],
                  'FailRate': [0.05, 0.16, 0.31, 0.12, 0.20, 0.08]})

src = ColumnDataSource(df)

subj_list = sorted(list(set(src.data['Subject'])))

callback = CustomJS(args=dict(src=src), code='''
    src.change.emit();
''')

js_filter = CustomJSFilter(code='''
var indices = [];
for (var i = 0; i < src.get_length(); i++){
    if (src.data['Subject'][i] == select.value){
        indices.push(true);
    } else {
        indices.push(false);
    }
}
return indices;
''')

options = ['Please select...'] + subj_list
select = Select(title='Subject Selection', value=options[0], options=options)

select.js_on_change('value', callback)

view = CDSView(source=src, filters=[js_filter])

class_list = sorted(list(src.data['Class']))

p = figure(x_range=class_list, plot_height=400, plot_width=400) 
p.vbar('Class', top='FailRate', width=0.9, source=src, view=view)

show(column(select, p))

据我所知,部分问题涉及这一行(或 'view' 变量):

p.vbar('Class', top='FailRate', width=0.9, source=src, view=view)

在我向上面的行添加 'view=view' 之前,我至少显示了一个 select 框和绘图,即使交互不起作用。

Bokeh 可以神奇地跨运行时传输您的 Python 对象,以在您的浏览器中显示为 JavaScript 对象,但魔法是有限度的。您必须通过向 CustomJS 对象提供 args 参数,准确地告诉 Bokeh 要传输哪个 对象。此外,如果您使用回调来更新 IndexFilter 中的索引,这会更简单。那么你只需要一个回调:

filter = IndexFilter(indices=[])

callback = CustomJS(args=dict(src=src, filter=filter), code='''
  const indices = []
  for (var i = 0; i < src.get_length(); i++) {
    console.log(i, src.data['Subject'][i], cb_obj.value)
    if (src.data['Subject'][i] == cb_obj.value) {
      indices.push(i)
    }
  }
  filter.indices = indices
  src.change.emit()
''')

select.js_on_change('value', callback)

view = CDSView(source=src, filters=[filter])

最后一点:我没有传递和使用 select,而是使用隐式 cb_obj 变量,它始终是触发更改的对象。 (使用传入的 select 可能存在错误)

编辑:不,没有错误。问题是 Select 是在 回调之后 定义的,并且由于事情在笔记本中,它使用的是前一个单元格执行的值。不幸的是,一般来说,笔记本的内在特性使得损坏状态的问题很容易实现。如果您先定义 Select,那么代码会按预期使用 select 而不是 cb_obj:

options = ['Please select...'] + subj_list
select = Select(title='Subject Selection', value=options[0], options=options)

cb = CustomJS(args=dict(select=select, src=src, filter=filter), ...)

甚至跨越重复的单元格调用。请注意 cb_obj 没有任何问题,但许多人更喜欢显式传递(和命名)小部件。

作为最后的说明,我会提到学习检查浏览器的 JS 控制台对于调试 CustomJS 类型的回调是必不可少的。您的代码中的错误立即出现在那里。