Не удается получить группу флажков Bokeh для обновления графика - PullRequest
0 голосов
/ 08 октября 2019

Я пытаюсь создать простой интерактивный график с группой флажков. Я хочу, чтобы флажки приводили к отображению соответствующей линии на графике, когда отмечен галочкой. Я делаю это в Jupyter Notebook.

Мне удалось встроить его в Jupyter, и я написал функцию обратного вызова, которая выполняет код. Я могу создать новый ColumnDataSource из выбора флажка. Тем не менее, график просто не обновляется.

Я просмотрел каждый пост здесь, где мог, и посмотрел каждый учебник, который смог найти. У большинства из них просто есть обратный вызов обновления, который создает новый источник, а затем устанавливает новый источник графика, который, как я считаю, должен обновлять график. Я также видел варианты, когда люди назначают его как oldsource.data = newsource.data. Это тоже не работает для меня.

Мне интересно, есть ли какие-то присущие ограничения встраивания в Jupyter Notebook, для которых мне нужен Javascript, или ограничения на то, как можно обновлять источники. Или, может быть, я просто упускаю что-то очень очевидное? Код ниже:

import os
import pandas as pd
import numpy as np
import bokeh.plotting as bk
import bokeh.layouts as ly
import bokeh.models as md
import bokeh.colors as cl
import bokeh.palettes as plet
from bokeh.io import curdoc
from bokeh.io import show as io_show
from bokeh.models.widgets import CheckboxGroup, Select, Button
from bokeh.plotting import output_file, show, figure, output_notebook, reset_output, curdoc

data_list = ["one", "two", "three", "four", "five", "six"]
data_list2 = ["one", "two"]
data_fac = [1, 2, 3, 4, 5, 6]
data_fac_dict = dict(zip(data_list,data_fac))
data_x = np.linspace(0,100)

df = pd.DataFrame(columns = data_list)

def modify_doc(doc):

    def make_data(data_list):
    #Make new source with appropriate datasets
        df = pd.DataFrame(columns = data_list)

        for case in data_list:
            df[case] = data_x * data_fac_dict[case]

        result = md.ColumnDataSource(df)

        return result

    #Make colors
    list_colors = plet.Dark2[len(data_list)]
    dict_colors = dict(zip(data_list,list_colors))

    #Default source with one datapoint
    src = make_data(["one"])

    print(src.data.keys())

    #Plot graphs
    p = bk.figure()
    for case in src.data.keys():
        if case != "index":
            p.line(source = src, x = 'index', y = case, color = dict_colors[case])  
            print("plotting loop")


    def update(attr,old,new):
    #Callback    
        print("update triggered")
        selection = list()

        for i in wg_chk.active:
            selection.append(data_list[i])

        src = make_data(selection)

        print(selection)


    wg_chk = CheckboxGroup(labels = data_list, active = [0]*len(data_list))        
    wg_chk.on_change('active', update) 

    layout = ly.row(wg_chk,p)
    doc.add_root(layout)

bk.show(modify_doc, notebook_url='localhost:8888')

ОБНОВЛЕНИЕ # 1

Я изменил код в обратном вызове, чтобы создать соответствующий фрейм данных, затем создал dict, используя ColumnDataSource.from_df, затемустановите src.data равным ему, как показано ниже. По-прежнему не работает. Я использовал печать, чтобы убедиться, что data_new имеет правильные ключи.

df_new = make_df(selection)
data_new = md.ColumnDataSource.from_df(df_new)
src.data = data_new

Для ясности, я использую новейшую версию Bokeh и Python на сегодняшний день (Bokeh 1.0.2, Python 3.7.1)

ОБНОВЛЕНИЕ # 2

Согласно комментариям, я заранее сгенерировал все необходимые глифы заранее, поэтому они, по сути, являются «слотами для данных» вместогенерируется по запросу для любого количества наборов данных. Поскольку они теперь постоянны, это позволяет мне легко включать / выключать их с помощью свойства .visible. Теперь у меня есть шесть «слотов» для данных, которые должны отображаться с соответствующими символами, и я добавил функцию в обратный вызов для обновления их соответствующих источников данных (в этом случае, меняя линейную кривую на квадратную). Я также обновил Bokeh до последней версии (1.3.4). Обратите внимание, что это специально встраивается в блокнот Jupyter.

Вот код для справки:

import os
import pandas as pd
import numpy as np
import bokeh.plotting as bk
import bokeh.layouts as ly
import bokeh.models as md
import bokeh.colors as cl
import bokeh.palettes as plet

from bokeh.io import curdoc
from bokeh.io import show as io_show
from bokeh.models.widgets import CheckboxGroup, Select, Button, RadioGroup
from bokeh.plotting import output_file, show, figure, output_notebook, reset_output, curdoc

data_list = ["one", "two", "three", "four", "five", "six"]
data_list2 = ["one", "two"]
data_fac = [1, 2, 3, 4, 5, 6]
data_fac_dict = dict(zip(data_list,data_fac))
data_x = np.linspace(0,100)

df = pd.DataFrame(columns = data_list)
for case in data_list:
    df[case] = data_x * data_fac_dict[case] + np.power(data_x, 3) * data_fac_dict[case]

def modify_doc(doc):

    #Make colors
    list_colors = plet.Dark2[len(data_list)]
    dict_colors = dict(zip(data_list,list_colors))


    p = bk.figure()

    def make_line(case):
        line = p.line(x = 'index', y = case, source = src_store[case], color = dict_colors[case])
        return line

    #Make six sources, make one line per source, and set them to invisible
    src_store = dict()
    list_lines = dict()

    for case in data_list:
        src_store[case] = md.ColumnDataSource(df[[case]])
        list_lines[case] = make_line(case)
        list_lines[case].visible = False

    #First checkbox defaults to ticked, so let's show it by default.
    list_lines["one"].visible = True

    def modify_data(order):
    #Modify the data and update the six sources' data with it
        df = pd.DataFrame(columns = data_list)
        src_store_new = dict()
        data_new = dict()

        for case in data_list:
            df[case] = data_x * data_fac_dict[case] + np.power(data_x,order) * data_fac_dict[case]
            data_new[case] = md.ColumnDataSource.from_df(df[[case]])
            src_store[case].data = data_new[case]

    def update(attr,old,new):
    #Callback    
        print("update triggered")

        #Get selection of lines to display
        selection = list()
        for i in wg_chk.active:
            selection.append(data_list[i])

        #Set visibility according to selection
        for case in data_list:
            list_lines[case].visible = case in selection

        #Get line multiplier from radio buttons and update sources
        order = wg_rad.active + 1
        modify_data(order)

        print(selection)

    wg_rad = RadioGroup(labels=["x*0", "x*1"], active = 0)
    wg_chk = CheckboxGroup(labels = data_list, active = [0]*len(data_list))

    wg_chk.on_change('active', update)
    wg_rad.on_change('active', update)

    layout = ly.row(ly.column(wg_chk,wg_rad),p)
    doc.add_root(layout)

bk.show(modify_doc, notebook_url='localhost:8888')

1 Ответ

0 голосов
/ 08 октября 2019

Когда вы строите глиф Боке, у этого объекта глифа есть связанный источник данных. Если вы хотите, чтобы глиф обновлялся, вам нужно обновить этот существующий источник данных , т.е. изменить его, установив его свойство .data. Код выше не делает этого. Он создает новый источник данных, который ни к чему не подключен и не настроен, а затем немедленно выбрасывает его (это локальная переменная в функции, поскольку ничто не сохраняет ссылку на него, он исчезает, когдафункция завершается).

Вам необходимо обновить любой существующий источник данных, который вы использовали изначально:

source.data = new_data  # plain python dict

И, по крайней мере, начиная с Bokeh 1.3.4 new_data должно быть обычный словарь Python . Не поддерживается «миграция» значения .data с одного CDS на другой:

source1.data = source2.data  # BAD! WILL NOT WORK

Попытка сделать это, вероятно, вызовет явную ошибку в ближайшем будущем. В ColumnDataSource есть статический метод from_df, который вы можете использовать для преобразования DataFrames в правильный тип dict.

...