使用Plotly为子图添加按钮

1 投票
1 回答
27 浏览
提问于 2025-04-13 16:08

我刚接触Plotly,想为每个子图添加按钮,而不是为整个图形添加按钮。但我找不到方法来实现这个功能。

有没有人能帮我一下,教我该怎么做?

这是我的代码:

def plot_data(data, filename):
    """
    Plotting the data
    param data: data to plot.
    param filename: file name to write the output in.
    return: None.
    """

    subplot_titles_tuple = get_titles(data)
    fig = make_subplots(shared_xaxes=False,rows=len(data), cols=1, subplot_titles=subplot_titles_tuple)
    for i, segment in enumerate(data):
        trace = go.Scatter(x=segment['x'], y=segment['y'], mode='lines', name=f"Plot {i + 1}") # change the name of the plot
        #print('im here',segment['x'] )
        fig.add_trace(trace,row=i+1,col=1)

        # Set subplot titles
        fig.update_layout(title=f"Plots for {filename}")

        # Set x-axis and y-axis titles according to segment data
        x_title = segment.get('x_array_label', 'RCM')  # Default to 'X-axis' if 'x_array_label' is not present
        y_title = segment.get('y_array_label', 'Y-axis')  # Default to 'Y-axis' if 'y_array_label' is not present
        fig.update_xaxes(title_text=x_title, row=i + 1, col=1)
        fig.update_yaxes(title_text=y_title, row=i + 1, col=1)


        ### Buttons for the user to change in the plot
        fig.update_layout(
            updatemenus=[
                dict(
                    buttons=[
                        dict(label="Linear", method="relayout", args=[{"yaxis.type": "linear"}]),
                        dict(label="Log", method="relayout", args=[{"yaxis.type": "log"}]),
                        dict(label="Points", method="restyle", args=[{"mode": "markers"}]),
                        dict(label="Line", method="restyle", args=[{"mode": "lines+markers"}]),
                    ],
                    direction="down",
                    showactive=True,
                    x=1,
                    xanchor="left",
                    y=0.9,
                    yanchor="top"
                )
            ]
        )



    # change xlim and ylim

    xlim_slider = widgets.FloatRangeSlider(
        value=[min(segment['x']), max(segment['x'])],  # Initial limits based on data
        min=min(segment['x']),
        max=max(segment['x']),
        step=0.1,
        description='xlim:',
        continuous_update=False
    )

    ylim_slider = widgets.FloatRangeSlider(
        value=[min(segment['y']), max(segment['y'])],  # Initial limits based on data
        min=min(segment['y']),
        max=max(segment['y']),
        step=0.1,
        description='ylim:',
        continuous_update=False
    )


    # Function to update xlim and ylim
    def update_plot(xlim, ylim):
        fig.update_xaxes(range=xlim)
        fig.update_yaxes(range=ylim)

    # Connect sliders to update function
    widgets.interactive(update_plot, xlim=xlim_slider, ylim=ylim_slider)

    # Show or save the plot
    plot_filename = f"{os.path.splitext(filename)[0]}_plots.html"
    plot_path = os.path.join(os.getcwd(), plot_filename)
    fig.write_html(plot_path)
    print(f"All plots saved in {plot_filename}")

这是我得到的结果:截图

我希望这些选项出现在每个子图上,而不是整个图表上。

1 个回答

0

主要的问题是,fig.update_layout(updatemenus=[...]) 这个代码是在一个循环里执行的,所以每次循环的时候,updatemenus 这个列表会被覆盖,而不是在原来的基础上增加内容。布局中的 title 也是同样的问题,不过因为布局配置是针对整个图形的,所以你只能设置一个标题,但看起来你已经通过 subplot_titles 解决了这个问题。

关于更新 y 轴类型的按钮,你需要指定轴的 ID(比如 yaxisyaxis2 等),这个 ID 要和子图的索引对应。

至于调用 restyle 方法的按钮,你需要指定一个跟对应子图上存在的 trace(数据轨迹)相关的索引列表(也就是说,trace 的索引不一定和子图的索引相匹配)。

fig = make_subplots(shared_xaxes=False,rows=len(data), cols=1, subplot_titles=subplot_titles_tuple)
fig.update_layout(title=f"Plots for {filename}")

updatemenus = []
btn_y = [0.9, 0.4]

for i, segment in enumerate(data):
    trace = go.Scatter(x=segment['x'], y=segment['y'], mode='lines', name=f"Plot {i + 1}") # change the name of the plot
    #print('im here',segment['x'] )
    fig.add_trace(trace,row=i+1,col=1)

    # Set x-axis and y-axis titles according to segment data
    x_title = segment.get('x_array_label', 'RCM')  # Default to 'X-axis' if 'x_array_label' is not present
    y_title = segment.get('y_array_label', 'Y-axis')  # Default to 'Y-axis' if 'y_array_label' is not present
    fig.update_xaxes(title_text=x_title, row=i + 1, col=1)
    fig.update_yaxes(title_text=y_title, row=i + 1, col=1)

    yaxis_id = 'yaxis' if i == 0 else f'yaxis{i+1}'

    updatemenus.extend([
        dict(
            buttons=[
                dict(label="Linear", method="relayout", args=[{f"{yaxis_id}.type": "linear"}]),
                dict(label="Log", method="relayout", args=[{f"{yaxis_id}.type": "log"}])
            ],
            direction="down",
            showactive=True,
            active=0,
            x=1,
            xanchor="left",
            y=btn_y[i],
            yanchor="top"
        ),
        dict(
            buttons=[
                dict(label="Points", method="restyle", args=[{"mode": "markers"}, [i]]),
                dict(label="Line", method="restyle", args=[{"mode": "lines+markers"}, [i]]),
            ],
            direction="down",
            showactive=True,
            active=1,
            x=1,
            xanchor="left",
            y=btn_y[i]-0.05,
            yanchor="top"
        )
    ])

fig.update_layout(updatemenus=updatemenus)

撰写回答