Я пытаюсь обновить раскрывающееся меню в зависимости от выбора, сделанного пользователем в предыдущем раскрывающемся меню, с помощью обратных вызовов customjs в Bokeh. В приведенном ниже примере пользователь может создать базовый график на основе выбора common_name ИЛИ путем выбора квадрата. Но я хотел бы, чтобы пользователь мог сначала выбрать common_name, а затем выбрать квадрат на основе данных, доступных для этого common_name (т.е. на основе первого выбранного common_name). Есть идеи ?
from bokeh.models import CustomJS, ColumnDataSource, MultiSelect, Column
from bokeh.plotting import figure, show
from bokeh.layouts import row, column
import pandas as pd
# data
data = dict(common_name = ['A','A','B','C','B','B','A','C','C','B','A','B','C'],
length = [10, 20, 10, 20, 30, 20, 20, 30, 20, 30, 30, 20, 30],
weight = [100, 200, 100, 300, 100, 400, 100, 300, 100, 400, 500, 600, 450],
quadrat = ['N', 'N', 'N', 'N', 'M', 'M', 'M', 'M', 'O', 'O', 'M', 'N', 'O'])
data = pd.DataFrame(data)
data_source = ColumnDataSource(data)
# Empty source so the plot is empty before data is selected
source = ColumnDataSource(dict(length = [], weight = []))
plot = figure()
plot.circle(x = 'length', y = 'weight', source = source)
# Select species
available_species = list(set(data['common_name']))
available_species.sort()
species_callback = CustomJS(args = {'source': source, 'data_source': data_source},
code = """
var data = data_source.data;
var source_data = source.data;
var common_name = data['common_name'];
var selected_species = cb_obj.value;
var length_data = data['length'];
var weight_data = data['weight'];
var length = source_data['length'];
length.length = 0;
var weight = source_data['weight'];
weight.length = 0;
for (var i = 0; i < length_data.length; i++) {
if (selected_species.indexOf(common_name[i]) >= 0) {
length.push(length_data[i]);
weight.push(weight_data[i]);
}
}
source.change.emit();
""")
multiselect_species = MultiSelect(title = 'Species:', value = [], options = available_species, width = 240)
# Select quadrat
available_quadrat = list(set(data['quadrat']))
available_quadrat.sort()
quadrat_callback = CustomJS(args = {'source': source, 'data_source': data_source},
code = """
var data = data_source.data;
var source_data = source.data;
var quadrat = data['quadrat'];
var selected_quadrat = cb_obj.value;
var length_data = data['length'];
var weight_data = data['weight'];
var length = source_data['length'];
length.length = 0;
var weight = source_data['weight'];
weight.length = 0;
for (var i = 0; i < length_data.length; i++) {
if (selected_quadrat.indexOf(quadrat[i]) >= 0) {
length.push(length_data[i]);
weight.push(weight_data[i]);
}
}
source.change.emit();
""")
multiselect_quadrat = MultiSelect(title = 'Set:', value = [], options = available_quadrat, width = 240)
multiselect_quadrat.js_on_change('value', quadrat_callback)
multiselect_species.js_on_change('value', species_callback, CustomJS(args = dict(multiselect_quadrat = multiselect_quadrat,
available_quadrat = available_quadrat),
code = """
const available_quadrat = %s
multiselect_quadrat.options = "available_quadrat[cb_obj.value]"
""" % available_quadrat))
# Set up widgets layout
widgets_layout = column(multiselect_species, multiselect_quadrat)
page_layout = row(widgets_layout, plot)
show(page_layout)
Вот полный пример, который обновляет MultiSelect
на основе обратного вызова CustomJS
для другого виджета:
from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import RadioButtonGroup, CustomJS, MultiSelect
opts = {
0: ["foo", "bar"],
1: ["baz", "quux"],
}
bg = RadioButtonGroup(labels=["Stuff", "Things"], active=0)
ms = MultiSelect(options=opts[0])
bg.js_on_change('active', CustomJS(args=dict(ms=ms), code = """
const opts = %s
ms.options = opts[cb_obj.active]
""" % opts))
show(column(bg, ms)
В виджетах с множественным выбором нет свойства active
, поэтому available_quadrat[cb_obj.active]
не определено. Вместо этого есть свойство value
, которое представляет собой список (поскольку при множественном выборе можно выбрать более одной вещи), НО значения также являются строками, например. «C», поэтому вам нужно будет использовать словарь, а не список, для available_quadrat
также, конечно, multiselect_quadrat.options
должно быть установлено значение списка. Я бы порекомендовал узнать о команде debugger
JS. Вы можете поместить его в свой код обратного вызова, чтобы иметь возможность пройти через него и проверить переменные и т. д.
Спасибо bigreddot, ваша помощь неоценима. Однако я изо всех сил пытаюсь применить ваш совет. Я обновил код, чтобы учесть ваши предложения. Не могли бы вы указать, что я делаю неправильно? С помощью этого кода я могу только выбрать и нанести на карту вид (мультиселект квадрата пуст). Спасибо.