提问人:Christian Sayers 提问时间:10/23/2023 最后编辑:Christian Sayers 更新时间:11/22/2023 访问量:46
Python 面板/参数应用程序,每 3 次重新加载一次,散景图消失
Python Panel/Param application with disappearing Bokeh graphs every 3rd reload
问:
我正在构建一个数据分析 Web 应用程序,该应用程序根据用户输入加载一些数据,然后生成一系列视觉对象来显示用户的数据。我正在使用 Panel、Param 和 Bokeh 库在 Python 中构建应用程序 - Param 用于控制用户输入,Bokeh 用于生成交互式视觉效果,Panel 用于生成和组合应用程序。该应用程序按预期出现(下面的最小可重现示例),但是当我更改用户输入并重新生成数据时,散景图将缩小到没有大小。这仅在单击按钮重新生成数据三次后发生 - 在此点之后再次单击将解决数字问题,并且 3 秒的循环将重新开始。
下面的例子说明了我遇到的问题:
import random
import param
import panel as pn
import numpy as np
import pandas as pd
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
pn.extension()
def generate_fake_data(n: int):
"""
Sample data generation function
"""
start_x_value = np.random.randint(low=0, high=int(1e3))
step_x_size = np.random.randint(low=1, high=10)
end_x_value = start_x_value + step_x_size * n
x_values = list(range(start_x_value, end_x_value, step_x_size))
y_values = random.sample(range(0, end_x_value), n)
d = {'x': x_values, 'y': y_values}
return pd.DataFrame(d, index=range(0, n))
class DataFigureGeneration(param.Parameterized):
"""
Sample class used to house dataframes and figure generation functions
"""
# example of a non-empty starting dataframe
data_df_1 = param.DataFrame(
pd.DataFrame(
{'x': [1, 2, 3, 4, 5], 'y': [8, 4, 8, 7, 1]}
),
label='DF1'
)
# example of an empty starting dataframe
data_df_2 = param.DataFrame(
pd.DataFrame([np.nan]),
label='DF2'
)
def __init__(self):
super().__init__()
def generate_fig_1(self):
p = figure(
height=600,
width=1200,
tools='box_zoom,wheel_zoom,reset,xpan',
toolbar_location='right',
x_axis_type='datetime',
title='DF1'
)
data_source = ColumnDataSource(self.data_df_1)
line = p.line(x='x', y='y', color='orange', name='DF1', source=data_source)
return p
def generate_fig_2(self):
p = figure(
height=600,
width=1200,
tools='box_zoom,wheel_zoom,reset,xpan',
toolbar_location='right',
x_axis_type='datetime',
title='DF2'
)
data_source = ColumnDataSource(self.data_df_2)
line = p.line(x='x', y='y', color='blue', name='DF2', source=data_source)
return p
class MainApp(param.Parameterized):
"""
Sample class used to receive user inputs, respond to changes, and generate end-user app
"""
n = param.Integer(
default=5,
bounds=(1, 100)
)
view = param.ClassSelector(
class_=DataFigureGeneration,
default=DataFigureGeneration()
)
generate_data_button = param.Action(
lambda x: x.param.trigger('generate_data_button'),
label='Generate Data'
)
data_df_3 = param.DataFrame(
pd.DataFrame([np.nan]),
label='DF3'
)
reload_tracker = param.Integer(default=0)
data_generated_status = param.Boolean(False)
def __init__(self):
super().__init__()
# example of a non-empty starting figure, using the DataFigureGeneration object
self.df_1_fig = figure(
height=600,
width=1200,
tools='box_zoom,wheel_zoom,reset,xpan',
toolbar_location='right'
)
data_source = ColumnDataSource(self.view.data_df_1)
self.df_1_fig.line(x='x', y='y', color='orange', source=data_source)
# example of an empty starting figure
self.df_2_fig = figure(
height=600,
width=1200,
tools='box_zoom,wheel_zoom,reset,xpan',
toolbar_location='right'
)
# example of an dataframe/figure that does not use the DataFigureGeneration class
self.df_3_fig = figure(
height=600,
width=1200,
tools='box_zoom,wheel_zoom,reset,xpan',
toolbar_location='right'
)
@param.depends('generate_data_button', watch=True)
def generate_data(self):
self.view.data_df_1 = generate_fake_data(self.n)
self.view.data_df_2 = generate_fake_data(self.n)
self.data_df_3 = generate_fake_data(self.n)
if not self.data_generated_status:
self.data_generated_status = True
self.reload_tracker += 1
def generate_fig_3(self):
"""
identical generation function to the one in DataFigureGeneration, in the MainApp class instead
"""
p = figure(
height=600,
width=1200,
tools='box_zoom,wheel_zoom,reset,xpan',
toolbar_location='right',
x_axis_type='datetime',
title='DF3'
)
data_source = ColumnDataSource(self.data_df_3)
line = p.line(x='x', y='y', color='blue', name='DF3', source=data_source)
return p
def params_view(self):
n_widget = pn.Param(
self.param,parameters=['n'],
widgets={'n': pn.widgets.IntInput},
show_name=False
)
data_button = pn.Param(
self.param,
parameters=['generate_data_button'],
widgets={'generate_data_button': {
'type': pn.widgets.Button, 'button_style': 'solid', 'button_type': 'primary'}
},
show_name=False
)
view = pn.Column(
n_widget,
data_button,
)
return view
@param.depends('reload_tracker')
def bokeh_view(self):
if self.data_generated_status:
self.df_1_fig = self.view.generate_fig_1()
# self.df_2_fig = self.view.generate_fig_2()
self.df_3_fig = self.generate_fig_3()
view = pn.Column(
pn.pane.Bokeh(self.df_1_fig),
pn.pane.Bokeh(self.view.generate_fig_2()),
pn.pane.Bokeh(self.generate_fig_3())
)
else:
self.df_1_fig = self.view.generate_fig_1()
view = pn.Column(
pn.pane.Bokeh(self.df_1_fig),
pn.pane.Bokeh(self.df_2_fig),
pn.pane.Bokeh(self.df_3_fig)
)
return view
def panel_view(self):
view = pn.Row(
self.params_view,
self.bokeh_view
)
return view
app = MainApp()
app.panel_view().servable()
我尝试了几种不同的方法,例如将数据和图形生成移动到代码的不同部分,但都没有成功解决这个问题。当我在散景图消失时检查为类生成的图形对象时,我注意到“高度”和“宽度”将为 None,但我还没有弄清楚为什么会发生这种情况。即使图形无法正确显示,基础数据帧也存在。我的预感是问题出在bokeh_view功能上,但我无法弄清楚需要有什么不同。我哪里出了问题?
编辑:手动设置散景窗格的大小(例如高度=600,宽度=1200)可以解决此问题。但是,如果有人有任何见解,我很想了解为什么这是必要的。
答:
下一个:散景粘性十字准线
评论