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

如何将Plotly子图图例间距legend_tracegroupgap设为图高百分比并对齐子图?

解决Plotly子图图例分组与子图顶部对齐且适配尺寸变化的问题

问题本质

Plotly的legend_tracegroupgap参数是固定像素值,不支持百分比设置,所以直接用初始图高计算的gap值,在图表大小调整后会失效——因为实际渲染高度变了,但gap还是原来的像素数。之前的计算方法失效,是因为你用的是初始设置的fig_height,而非图表实时渲染后的实际高度。

核心解决方案

监听图表的尺寸变化事件,实时获取当前图表高度,结合子图的domain相对坐标(0-1范围),计算出每个子图对应的像素高度,动态更新legend_tracegroupgap,让图例分组始终对齐对应子图顶部。


实现代码(Python Dash 环境)

Dash 可以直接通过回调监听图表的relayoutData事件,实时调整图例gap:

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

app = dash.Dash(__name__)

# 创建3行1列的子图示例
fig = make_subplots(rows=3, cols=1, subplot_titles=("子图1", "子图2", "子图3"))
# 按分组添加轨迹,每个分组对应一个子图
fig.add_trace(go.Scatter(x=[1,2,3], y=[4,5,6], name="轨迹1", legendgroup="group1"), row=1, col=1)
fig.add_trace(go.Scatter(x=[1,2,3], y=[7,8,9], name="轨迹2", legendgroup="group1"), row=1, col=1)
fig.add_trace(go.Scatter(x=[1,2,3], y=[10,11,12], name="轨迹3", legendgroup="group2"), row=2, col=1)
fig.add_trace(go.Scatter(x=[1,2,3], y=[13,14,15], name="轨迹4", legendgroup="group2"), row=2, col=1)
fig.add_trace(go.Scatter(x=[1,2,3], y=[16,17,18], name="轨迹5", legendgroup="group3"), row=3, col=1)
fig.add_trace(go.Scatter(x=[1,2,3], y=[19,20,21], name="轨迹6", legendgroup="group3"), row=3, col=1)

# 初始布局设置:图例靠右,y轴锚定顶部
fig.update_layout(
    legend=dict(
        tracegroupgap=0,
        yanchor="top",
        y=1,
        xanchor="left",
        x=1.02
    ),
    height=600
)

app.layout = html.Div([
    dcc.Graph(id='subplot-graph', figure=fig)
])

@app.callback(
    Output('subplot-graph', 'figure'),
    Input('subplot-graph', 'relayoutData'),
    State('subplot-graph', 'figure')
)
def update_legend_gap(relayout_data, fig):
    # 非高度变化事件直接返回原图表
    if relayout_data is None or 'height' not in relayout_data:
        return fig
    
    current_height = relayout_data['height']
    num_subplots = 3
    # 子图均分高度时,单个子图的相对高度是1/子图数量
    subplot_rel_height = 1 / num_subplots
    # 计算分组间距:单个子图的实际像素高度
    fig['layout']['legend']['tracegroupgap'] = subplot_rel_height * current_height
    
    return fig

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

非Dash环境(如Jupyter Notebook)

可以通过嵌入自定义JavaScript监听前端渲染事件:

import plotly.graph_objects as go
from plotly.subplots import make_subplots
from IPython.display import HTML

# 创建子图并添加轨迹(同上)
fig = make_subplots(rows=3, cols=1)
fig.add_trace(go.Scatter(x=[1,2,3], y=[4,5,6], name="轨迹1", legendgroup="group1"), row=1, col=1)
# ... 其他轨迹省略 ...
fig.update_layout(legend=dict(y=1, x=1.02), height=600)

# 生成HTML并添加尺寸监听JS
html_str = fig.to_html(full_html=True)
custom_js = """
<script>
// 获取图表元素
var graph = document.getElementById('{plot_id}');
// 监听图表尺寸变化事件
graph.on('plotly_relayout', function(eventData) {
    if (eventData.height) {
        var currentHeight = eventData.height;
        var numSubplots = 3;
        var subplotPixelHeight = currentHeight / numSubplots;
        // 动态更新图例分组间距
        Plotly.relayout(graph, 'legend.tracegroupgap', subplotPixelHeight);
    }
});
</script>
""".format(plot_id=fig.layout.id if fig.layout.id else 'plot')

# 合并并显示HTML
final_html = html_str.replace('</body>', custom_js + '</body>')
HTML(final_html)

适配非均分高度的子图

如果子图高度不同(比如自定义了domain),需要遍历每个子图的y轴domain计算实际高度:

# 假设子图domain自定义为:[0.7,1], [0.3,0.6], [0,0.2]
subplot_domains = [fig.layout.yaxis.domain, fig.layout.yaxis2.domain, fig.layout.yaxis3.domain]
# 计算每个子图的像素高度
subplot_heights = [(d[1]-d[0])*current_height for d in subplot_domains]
# 第二个分组的gap是第一个子图的高度,第三个分组的gap是前两个子图高度之和
tracegroupgaps = [sum(subplot_heights[:i]) for i in range(1, len(subplot_heights))]
# 更新legend的tracegroupgap(Plotly的tracegroupgap是分组之间的统一间距,若要不同间距需特殊处理,或改用多个legend)
fig['layout']['legend']['tracegroupgap'] = tracegroupgaps[0]

关键说明

  1. Plotly前端渲染时才会确定图表的实际高度,所以必须依赖relayout事件获取实时高度,而非初始设置的fig_height。
  2. legend.tracegroupgap是分组之间的像素间距,当子图均分高度时,设置为单个子图的像素高度即可让分组对齐子图顶部。
  3. 若要每个分组对应不同的子图顶部,可能需要拆分多个legend,但大部分场景下统一设置分组间距即可满足需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 07:35:23