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

Python Plotly子图中如何让条形标记线自动匹配条形全宽

解决Plotly子图条形标记线适配条形宽度的问题

问题说明

需要让子图中条形上方的水平标记线完全适配对应条形的宽度,但当前使用line-ew-open标记符号的Scatter点只能显示固定宽度的线段,无法匹配条形实际宽度。

原始代码

import numpy as np
import plotly.graph_objs as go
from plotly.subplots import make_subplots

subplots = ['A','B','C','D']
fig = make_subplots(rows=2, cols=2, subplot_titles=subplots)
layout = go.Layout(
    barmode='stack',
    height=1000,
    width=1000,
    bargap=0.2,
)
fig.update_layout(layout)

directions = ['north','east','south','west']
levels = ['top','middle','bottom']

row = 1
col = 1
for subplot in subplots:
    for direction in directions:
        fig.add_trace(
            go.Bar(
                x=levels,
                y=np.random.randint(20, size=3),
            ),
            row=row,
            col=col,
        ) ,
    fig.add_trace(
        go.Scatter(
            mode='markers',
            x=levels,
            y=np.random.randint(20, size=3) + 2,
            marker={
                'symbol':   'line-ew-open',
                'size':     24,
                'color':    'black',
            }
        ),
        row=row,
        col=col,
    )
    ## move on
    if col == 2:
        row += 1
        col = 1
    else:
        col += 1

fig.show()

解决方案

利用fig.full_figure_for_development()获取图表渲染后的实际布局参数,提取每个条形的宽度和x轴位置,然后通过add_shape()绘制完全匹配条形宽度的水平线段,替代原有的Scatter标记。

修改后的代码如下:

import numpy as np
import plotly.graph_objs as go
from plotly.subplots import make_subplots

np.random.seed(42)  # 固定随机数,方便复现
subplots = ['A','B','C','D']
fig = make_subplots(rows=2, cols=2, subplot_titles=subplots)
layout = go.Layout(
    barmode='stack',
    height=1000,
    width=1000,
    bargap=0.2,
)
fig.update_layout(layout)

directions = ['north','east','south','west']
levels = ['top','middle','bottom']

# 先添加所有条形图轨迹,暂不添加Scatter标记
row = 1
col = 1
subplot_traces = {}  # 记录每个子图的轨迹索引,方便后续处理
for subplot in subplots:
    trace_indices = []
    for direction in directions:
        trace_idx = len(fig.data)
        fig.add_trace(
            go.Bar(
                x=levels,
                y=np.random.randint(20, size=3),
            ),
            row=row,
            col=col,
        )
        trace_indices.append(trace_idx)
    subplot_traces[(row, col)] = trace_indices
    
    # 移动到下一个子图
    if col == 2:
        row += 1
        col = 1
    else:
        col += 1

# 获取完整布局信息,提取条形的位置和宽度
full_fig = fig.full_figure_for_development(warn=False)

# 遍历每个子图,添加适配宽度的水平标记线
for (row, col), trace_indices in subplot_traces.items():
    # 取第一个条形轨迹的布局信息(同子图同x轴的条形宽度一致)
    bar_trace = full_fig.data[trace_indices[0]]
    bar_x = bar_trace.x  # x轴分类标签
    bar_width = bar_trace.width  # 条形的相对宽度
    xaxis = full_fig.layout[f'xaxis{row}{col}' if (row,col)!=(1,1) else 'xaxis']
    # 计算每个分类对应的x轴像素位置和实际宽度
    x_vals = xaxis.tickvals
    x_range = xaxis.range
    x_total = x_range[1] - x_range[0]
    # 每个分类的中心位置对应的数值坐标
    x_centers = {label: x_vals[i] for i, label in enumerate(bar_x)}
    
    # 生成随机的标记线y值(和原代码逻辑一致)
    y_markers = np.random.randint(20, size=3) + 2
    
    for i, level in enumerate(levels):
        x_center = x_centers[level]
        # 计算条形左右边界的数值坐标
        x_left = x_center - bar_width/2
        x_right = x_center + bar_width/2
        
        # 添加水平线段形状
        fig.add_shape(
            type='line',
            x0=x_left,
            x1=x_right,
            y0=y_markers[i],
            y1=y_markers[i],
            line=dict(
                color='black',
                width=2,
            ),
            row=row,
            col=col,
        )

fig.show()

代码说明

  1. 先单独添加所有条形轨迹,记录每个子图对应的轨迹索引,方便后续提取布局信息。
  2. 通过full_figure_for_development()获取渲染后的完整布局,从中提取条形的宽度、x轴刻度位置等关键参数。
  3. 计算每个条形的左右边界坐标,使用add_shape()绘制水平线段,确保线段宽度完全匹配条形宽度。
  4. 移除了原有的Scatter标记,改用形状绘制的线段替代,实现自适应条形宽度的效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 23:53:30