You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 00:50:53