Я пытаюсь создать простой интерактивный график с группой флажков. Я хочу, чтобы флажки приводили к отображению соответствующей линии на графике, когда они отмечены. Я делаю это в 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 Notebook.
Вот код для справки:
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')