Python / Bokeh / Javascript / js_on_change для выбора панели с накоплением и раскрывающегося списка - PullRequest
1 голос
/ 05 мая 2020

Я изо всех сил пытаюсь понять, как включить js_on_change для встроенной столбчатой ​​диаграммы с боке с выпадающим списком. Вкратце, всякий раз, когда мы выбираем значение в раскрывающемся меню, оно должно отображаться в списке столбцов основного фрейма данных, который затем может использоваться для построения столбчатых диаграмм.

Я думаю, что мне не хватает знаний о Javascript и как с ним поиграться. Укладчики, нужна ваша помощь. Ниже вы найдете полный код, который должен работать на вашей стороне.

Я немного позаимствовал из этой ветки bokeh - постройте другой столбец, используя пользовательский JS

import pandas as pd
import numpy as np
import datetime

from bokeh.io import show
from bokeh.layouts import row, column
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.plotting import figure, ColumnDataSource, show
from bokeh.models.widgets import Select
from bokeh.models import Label, Title, NumeralTickFormatter

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}
    }
 )

#List of columns for apples
initial_col = [i for i in df.columns.tolist() if i.startswith('Apples')]
selection_list = ["Apples", "Oranges"]

d_map = {
  'Apples':['Apples_green', 'Apples_red'],
  'Oranges':['Oranges_green', 'Oranges_red']
}

source = ColumnDataSource(df)

p = figure(plot_width=350, plot_height = 300,
        x_range=df.index.drop_duplicates().tolist())
p.vbar_stack(initial_col, x='index',
 width=0.9, color=['green', 'red'], source=source)
p.yaxis.formatter = NumeralTickFormatter(format='(0.00)')


select = Select(title="Fruit:", value=selection_list[0], options=selection_list)

select.js_on_change('value', CustomJS(args=dict(source=source, select=select, d_map = d_map), code="""
 // print the selectd value of the select widget - 
 // this is printed in the browser console.
 // cb_obj is the callback object, in this case the select 
 // widget. cb_obj.value is the selected value.
 console.log(' changed selected option', cb_obj.value);

  // create a new variable for the data of the column data source
  // this is linked to the plot
  var data = source.data;

  // allocate the selected column to the field for the y values

  data['{}'] = data[d_map[cb_obj.value]];

  // register the change - this is required to process the change in 
  // the y values
  source.change.emit();
""".format(d_map[selection_list[0]])))

col = column(select)
layout = row(col, p)
show(layout)

В конце концов, он отображает фигуру, но javascript часть не работает.

См. Снимок экрана здесь

1 Ответ

0 голосов
/ 05 мая 2020

Непосредственная проблема с вашим кодом заключается в том, как вы форматируете строку кода 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)
...