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

Plotly Python:如何将Trace关联至多个legendgroup实现分组控制?

Plotly多子图分组批量控制Trace显示/隐藏方案

因为每个Trace确实只能绑定一个legendgroup,但可以通过以下两种方法实现分组批量控制的需求:

方案1:用Dash添加回调控制(适合可引入Dash的项目)

如果你的项目可以引入Dash框架,这是最直观的实现方式:

  1. 先定义好每个key对应的分组,比如一个字典group_mapping
  2. 添加Trace时给每个Trace加上customdata标记所属分组
  3. 创建分组的图例按钮(或下拉菜单),编写回调实现批量切换

修改后的示例代码:

import dash
from dash import dcc, html, Input, Output, State
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# 假设的分组映射,根据你的实际测试分组定义
group_mapping = {
    "test1": "Group A",
    "test2": "Group A",
    "test3": "Group B",
    "test4": "Group B"
}
groups = list(set(group_mapping.values()))
colorList = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728"]

# 创建子图
fig = make_subplots(rows=2, cols=1, 
                    vertical_spacing=0.04,
                    shared_xaxes=True)

c = 0
for key, value in filtered_dict.items():
    # 第一个子图的Trace,添加customdata标记分组
    fig.add_trace(go.Scatter(x=eval(data_to_plot_X)[key], y=eval(data_to_plot_Y)[key], 
                             hovertemplate=hoverY1, marker=dict(color=colorList[c]), 
                             name=key, legendgroup=key, customdata=[group_mapping[key]]),
                  row=1, col=1,)
    # 第二个子图的Trace,同样添加customdata
    fig.add_trace(go.Scatter(x=eval(data_to_plot_X)[key], y=eval(data_to_plot_Y2)[key], 
                             hovertemplate=hoverY2, marker=dict(color=colorList[c]), 
                             name=key, legendgroup=key, showlegend=False, customdata=[group_mapping[key]]),
                  row=2, col=1,)
    c += 1

fig.update_layout(hovermode="x")

# 初始化Dash应用
app = dash.Dash(__name__)
app.layout = html.Div([
    # 添加分组控制按钮
    html.Div([
        html.Button(f"切换{group}", id=f"btn-{group}", n_clicks=0)
        for group in groups
    ], style={"margin": "10px"}),
    dcc.Graph(id="fig", figure=fig)
])

# 回调函数:点击分组按钮,切换该组所有Trace的可见性
@app.callback(
    Output("fig", "figure"),
    Input("btn-Group A", "n_clicks"),
    Input("btn-Group B", "n_clicks"),
    State("fig", "figure")
)
def update_group_visibility(btn_a, btn_b, fig):
    # 确定哪个按钮被点击
    ctx = dash.callback_context
    if not ctx.triggered:
        return fig
    button_id = ctx.triggered[0]["prop_id"].split(".")[0]
    target_group = button_id.split("-")[1]

    # 遍历所有Trace,切换目标分组的可见性
    for trace in fig["data"]:
        if trace["customdata"][0] == target_group:
            trace["visible"] = not trace["visible"]
    
    return fig

if __name__ == "__main__":
    app.run_server(debug=True)

方案2:纯Plotly注入JavaScript实现(无需Dash)

如果不想引入Dash,可以通过给Plotly图表注入自定义JS代码来实现:

核心思路是添加虚拟Trace作为分组图例,然后监听图例点击事件,找到对应分组的所有真实Trace并切换可见性。

修改后的示例代码:

import plotly.graph_objects as go
from plotly.subplots import make_subplots

# 定义分组映射
group_mapping = {
    "test1": "Group A",
    "test2": "Group A",
    "test3": "Group B",
    "test4": "Group B"
}
groups = list(set(group_mapping.values()))
group_colors = {"Group A": "#1f77b4", "Group B": "#ff7f0e"}
colorList = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728"]

# 创建子图
fig = make_subplots(rows=2, cols=1, 
                    vertical_spacing=0.04,
                    shared_xaxes=True)

c = 0
for key, value in filtered_dict.items():
    # 给真实Trace添加分组标记(通过name包含分组前缀)
    trace_name = f"{group_mapping[key]}|{key}"
    # 第一个子图的Trace
    fig.add_trace(go.Scatter(x=eval(data_to_plot_X)[key], y=eval(data_to_plot_Y)[key], 
                             hovertemplate=hoverY1, marker=dict(color=colorList[c]), 
                             name=trace_name, legendgroup=key),
                  row=1, col=1,)
    # 第二个子图的Trace
    fig.add_trace(go.Scatter(x=eval(data_to_plot_X)[key], y=eval(data_to_plot_Y2)[key], 
                             hovertemplate=hoverY2, marker=dict(color=colorList[c]), 
                             name=trace_name, legendgroup=key, showlegend=False),
                  row=2, col=1,)
    c += 1

# 添加虚拟Trace作为分组图例(无数据,仅用于显示分组按钮)
for group in groups:
    fig.add_trace(go.Scatter(
        x=[None], y=[None],
        name=group,
        legendgroup=f"group-{group}",
        marker=dict(color=group_colors[group]),
        showlegend=True
    ))

fig.update_layout(hovermode="x")

# 注入自定义JS代码,监听图例点击事件
js_code = """
document.addEventListener('DOMContentLoaded', function() {
    var plotDiv = document.getElementById('plotly-figure');
    plotDiv.on('plotly_legendclick', function(data) {
        var clickedGroup = data.legendItem.legendgroup;
        // 判断点击的是分组虚拟Trace
        if (clickedGroup.startsWith('group-')) {
            var targetGroup = clickedGroup.split('-')[1];
            var fig = plotDiv.data;
            // 遍历所有真实Trace,切换目标分组的可见性
            fig.forEach(function(trace) {
                if (trace.name.startsWith(targetGroup + '|')) {
                    trace.visible = trace.visible === true ? 'legendonly' : true;
                }
            });
            // 更新图表
            Plotly.react(plotDiv, fig, plotDiv.layout);
            // 阻止默认的单个图例点击行为
            return false;
        }
    });
});
"""

# 将JS代码添加到图表的HTML中
fig.update_layout(
    annotations=[
        dict(
            text=f"<script>{js_code}</script>",
            showarrow=False,
            xref="paper", yref="paper",
            x=0, y=0,
            xanchor="left", yanchor="bottom",
            font=dict(size=0)
        )
    ]
)

# 保存或显示图表,指定div的id为plotly-figure
fig.write_html("grouped_plot.html", full_html=True)

注意点

  • 方案2中,虚拟Trace的legendgroup要和真实Trace区分开,比如加前缀group-
  • 真实Trace的name需要包含分组标识,方便JS识别,也可以用customdata存储分组信息,JS里通过trace.customdata[0]读取
  • 如果用fig.show(),建议本地打开生成的HTML文件,确保JS正常生效

内容的提问来源于stack exchange,提问作者chang thenoob

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 18:44:57