Непосредственная проблема с вашим кодом заключается в том, как вы форматируете строку кода JS, передаваемую в CustomJS
. В конечном итоге это оказывается неработающим кодом JS - вы можете увидеть ошибку, открыв консоль JS своего браузера и изменив значение виджета Select
.
Что касается вашей реальной задачи - это не то легко сделать, потому что vbar_stack
- это не настоящая функция глифа . Вместо этого это просто вспомогательная функция, которая вызывает функцию глифа vbar
с необходимыми параметрами. Ваш вызов p.vbar_stack(...)
фактически становится
p.vbar(bottom=stack(), top=stack('Apples_green'),
x='index', width=0.9, color='green', source=source)
p.vbar(bottom=stack('Apples_green'), top=stack('Apples_green', 'Apples_red'),
x='index', width=0.9, color='red', source=source)
. Это означает, что для изменения столбцов, которые используются в выражениях, используемых глифами VBar
, вам придется в основном преобразовать этот код Bokeh Python. в код боке JS JavaScript.
С учетом сказанного, вот рабочая версия вашего кода, которая должна работать для любого количества цветов, а не только для двух:
import pandas as pd
from bokeh.layouts import row, column
from bokeh.models import CustomJS
from bokeh.models import NumeralTickFormatter
from bokeh.models.widgets import Select
from bokeh.plotting import figure, ColumnDataSource, show
from bokeh.transform import stack
df = pd.DataFrame.from_dict(
{
'Apples_green': {'2018': 100, '2019': 150, '2020': 200},
'Apples_red': {'2018': 200, '2019': 75, '2020': 25},
'Oranges_green': {'2018': 25, '2019': 60, '2020': 70},
'Oranges_red': {'2018': 100, '2019': 80, '2020': 10}
}
)
d_map = {
'Apples': ['Apples_green', 'Apples_red'],
'Oranges': ['Oranges_green', 'Oranges_red']
}
selection_list = list(d_map.keys()) # In modern Python, dicts are ordered by default.
initial_value = selection_list[0]
initial_col = [i for i in df.columns.tolist() if i.startswith(initial_value)]
source = ColumnDataSource(df)
p = figure(plot_width=350, plot_height=300,
x_range=df.index.drop_duplicates().tolist())
renderers = []
col_acc = []
for col in d_map[initial_value]:
color = col[len(initial_value) + 1:]
r = p.vbar(bottom=stack(*col_acc), top=stack(col, *col_acc),
x='index', width=0.9, color=color, source=source)
col_acc.append(col)
renderers.append(r)
p.yaxis.formatter = NumeralTickFormatter(format='(0.00)')
select = Select(title="Fruit:", value=initial_value, options=selection_list)
select.js_on_change('value', CustomJS(args=dict(d_map=d_map, renderers=renderers),
code="""
const Stack = Bokeh.Models('Stack');
const col_acc = [];
d_map[cb_obj.value].forEach((col, idx) => {
const {glyph} = renderers[idx];
glyph.bottom = {expr: new Stack({fields: col_acc})};
col_acc.push(col);
glyph.top = {expr: new Stack({fields: col_acc})};
});
"""))
col = column(select)
layout = row(col, p)
show(layout)