如何为Plotly Dash图表添加上一个/下一个数据点切换按钮?
问题:为Plotly Dash图表添加数据点切换按钮
我的Plotly Dash应用包含两个图表:选中第一个图表中的数据点后,第二个图表会加载关联的DataFrame数据进行展示。现在希望为第一个图表添加上一个/下一个按钮,实现通过按钮切换数据点,而非仅依赖鼠标点击,但尝试许久未成功,寻求帮助。
DataFrame 信息
df2.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 100080 entries, 0 to 100079 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Device 100080 non-null category 1 time 100080 non-null datetime64[ns] 2 time2 100080 non-null float64 3 Path 100080 non-null category 4 variable 100080 non-null object 5 value 100080 non-null float64 6 path 100080 non-null category dtypes: category(3), datetime64[ns](1), float64(2), object(1) memory usage: 3.3+ MB
数据示例
Device time time2 Path variable value path 0 A 2022-09-27 11:57:26.801999 0.0 C:\Users\2022_09_27_11_57_26_,80.csv V+ 7.985262 C:\Users\2022_09_27_11_57_26_,80.csv 1 A 2022-09-27 11:57:26.802199 0.0002 C:\Users\2022_09_27_11_57_26_,80.csv V+ 7.981362 C:\Users\2022_09_27_11_57_26_,80.csv ...
现有应用布局代码
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] app = dash.Dash(__name__, title='Dashboard', external_stylesheets=external_stylesheets, assets_folder = 'assets') colors = { 'background': '#000000', 'text': '#f3ff00' } # Define the app app.layout = html.Div( children = [ html.Div([ html.H4('Dauertest'), html.Div(children = ""), # Draw graph dcc.Graph(id = 'General' , figure={} ), dcc.RadioItems( id="signals", options = ['V+', 'V-', 'I_A', 'I_fil'], value = 'V+', inline=True ), html.Br(), dcc.Graph(id = 'Zoomed' #, figure={} #, hoverData = {'points': [{'customdata': df2['path'][0]}]} ), html.Br(), ]), html.Br(), html.Br(), html.Br(), # New row html.Div([ html.Div([ dcc.Dropdown( id="select", options = list(all_df['Device'].unique()), value = list(all_df['Device'].unique()[0]) ), dcc.Graph(id = 'shared' , figure={} ), #], className='six columns'), ], className='twelve columns'), ], className='row') ])
现有图表代码
#%% Plot 1 @app.callback( Output("General", "figure"), Input("signals", "value") ) def update_scatter_chart(signals): df3 = df2.query('variable==@signals').groupby('path').first() fig_general = px.scatter(df3 , x = "time" , y = 'value' , custom_data = ['Path'] , color = 'Device' , symbol = 'variable' , hover_name = "Path" , opacity = 0.75 , template = 'plotly_dark' , marginal_y = "rug" , title = "All devices <br><sup> Only the first data point of below selected variable plus of each error file</sup>" , labels=dict(x = "absolute time") ) fig_general.update_layout( clickmode = 'event+select' , xaxis = {'domain': [0.0, 0.85]} , xaxis2 = {'domain': [0.86, 0.97]} #, xaxis3 = {'domain': [0.85, 1.0]} , transition_duration = 500 , autosize = True , height = 700 , hovermode = 'closest' ) fig_general.update_traces(marker = dict(size = 16, line=dict(width = 2, color = 'rgba(255, 255, 255, 0.3)'), opacity = 0.75 ) , selected_marker = dict(size = 20 , opacity = 0.95 , color='rgba( 240, 240, 240, 0.95)') , unselected = dict(marker = dict(opacity = 0.75) ) , hovertemplate="<br>".join([ "time: %{x}", "value: %{y}", ]) ) return fig_general #%% Plot 2 def update_zoom_chart(df5, device_name): fig_zoom = px.scatter(df5 , x = "time2" , y = 'value' , color = 'variable' , symbol = 'variable' #, hover_name = "Device" , opacity = 0.7 , template = 'plotly_dark' , title = device_name , color_discrete_map = {"I_A": 'orangered', "I_fil" : 'lawngreen', "V+" : "dodgerblue", "V-" : "chocolate"} , labels = dict(x = "t / ms", y = "value") ) fig_zoom.update_xaxes( nticks = 10 ) fig_zoom.update_traces(marker = dict( size = 5 ) ) fig_zoom.update_layout( transition_duration = 500 , autosize = True , height = 600 , hovermode='x unified' , hoverlabel=dict( bgcolor="rgba(0, 0, 0, 0.4)", font_size=14, font_family="Rockwell" ) ) return fig_zoom
解决方案
要实现按钮切换数据点,需要添加状态存储和按钮回调,以下是修改步骤:
1. 更新应用布局
在第一个图表下方、单选框上方添加上一个/下一个按钮,同时添加dcc.Store来保存当前选中的索引:
# 修改后的布局部分 html.Div([ html.H4('Dauertest'), html.Div(children = ""), # Draw graph dcc.Graph(id = 'General', figure={}), # 添加切换按钮 html.Div([ html.Button('上一个', id='prev-btn', n_clicks=0), html.Button('下一个', id='next-btn', n_clicks=0), # 存储当前选中的索引,初始为0 dcc.Store(id='selected-index', data=0) ], style={'margin': '10px 0'}), dcc.RadioItems( id="signals", options = ['V+', 'V-', 'I_A', 'I_fil'], value = 'V+', inline=True ), html.Br(), dcc.Graph(id = 'Zoomed'), html.Br(), ]),
2. 修改回调逻辑
需要新增三个回调:处理按钮点击更新选中索引、更新第一个图表的选中状态、更新第二个图表的展示数据。
回调1:处理按钮点击,更新选中索引
@app.callback( Output('selected-index', 'data'), Input('prev-btn', 'n_clicks'), Input('next-btn', 'n_clicks'), State('selected-index', 'data'), State('signals', 'value'), prevent_initial_call=False ) def update_selected_index(prev_clicks, next_clicks, current_idx, signals): # 获取当前信号对应的数据集长度 df3 = df2.query('variable==@signals').groupby('path').first() total_points = len(df3) if total_points == 0: return 0 # 判断哪个按钮被点击 ctx = dash.callback_context if not ctx.triggered: return current_idx button_id = ctx.triggered[0]['prop_id'].split('.')[0] if button_id == 'prev-btn': new_idx = current_idx - 1 if new_idx < 0: new_idx = total_points - 1 # 循环到最后一个 elif button_id == 'next-btn': new_idx = current_idx + 1 if new_idx >= total_points: new_idx = 0 # 循环到第一个 else: new_idx = current_idx return new_idx
回调2:更新第一个图表的选中状态
修改原有的update_scatter_chart回调,加入选中索引的输入,更新图表的选中点:
@app.callback( Output("General", "figure"), Input("signals", "value"), Input('selected-index', 'data') ) def update_scatter_chart(signals, selected_idx): df3 = df2.query('variable==@signals').groupby('path').first() df3 = df3.reset_index() # 重置索引,方便按位置选中 fig_general = px.scatter(df3 , x = "time" , y = 'value' , custom_data = ['Path'] , color = 'Device' , symbol = 'variable' , hover_name = "Path" , opacity = 0.75 , template = 'plotly_dark' , marginal_y = "rug" , title = "All devices <br><sup> Only the first data point of below selected variable plus of each error file</sup>" , labels=dict(x = "absolute time") ) fig_general.update_layout( clickmode = 'event+select' , xaxis = {'domain': [0.0, 0.85]} , xaxis2 = {'domain': [0.86, 0.97]} , transition_duration = 500 , autosize = True , height = 700 , hovermode = 'closest' ) fig_general.update_traces(marker = dict(size = 16, line=dict(width = 2, color = 'rgba(255, 255, 255, 0.3)'), opacity = 0.75 ) , selected_marker = dict(size = 20 , opacity = 0.95 , color='rgba( 240, 240, 240, 0.95)') , unselected = dict(marker = dict(opacity = 0.75) ) , hovertemplate="<br>".join([ "time: %{x}", "value: %{y}", ]) ) # 设置选中的点 fig_general.update_traces(selectedpoints=[selected_idx]) return fig_general
回调3:更新第二个图表,同时响应点击和按钮事件
新增回调,同时处理图表点击和按钮触发的选中索引变化:
@app.callback( Output("Zoomed", "figure"), Input("General", "clickData"), Input('selected-index', 'data'), State("signals", "value") ) def update_zoom_chart(click_data, selected_idx, signals): # 优先处理鼠标点击事件,如果有点击则用点击的点,否则用选中索引 if click_data is not None and click_data['points']: selected_path = click_data['points'][0]['customdata'][0] else: df3 = df2.query('variable==@signals').groupby('path').first().reset_index() if len(df3) == 0: return px.scatter(template='plotly_dark') selected_path = df3.iloc[selected_idx]['Path'] # 获取对应路径的所有数据 df5 = df2[df2['Path'] == selected_path] device_name = df5['Device'].iloc[0] fig_zoom = px.scatter(df5 , x = "time2" , y = 'value' , color = 'variable' , symbol = 'variable' , opacity = 0.7 , template = 'plotly_dark' , title = device_name , color_discrete_map = {"I_A": 'orangered', "I_fil" : 'lawngreen', "V+" : "dodgerblue", "V-" : "chocolate"} , labels = dict(x = "t / ms", y = "value") ) fig_zoom.update_xaxes( nticks = 10 ) fig_zoom.update_traces(marker = dict(size = 5)) fig_zoom.update_layout( transition_duration = 500 , autosize = True , height = 600 , hovermode='x unified' , hoverlabel=dict( bgcolor="rgba(0, 0, 0, 0.4)", font_size=14, font_family="Rockwell" ) ) return fig_zoom
关键说明
- 使用
dcc.Store存储当前选中的索引,避免页面刷新丢失状态 - 按钮点击时计算新的索引,超出范围时循环切换
- 第一个图表通过
selectedpoints参数设置选中点的样式 - 第二个图表同时响应鼠标点击和按钮事件,优先处理鼠标点击
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

