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

如何在含Indicator与Scatter的Plotly子图中添加跨X轴水平线

解决Plotly子图中Indicator与Scatter共存时添加水平线的报错问题

问题背景

开发实时数据仪表盘时,使用dcc.Patch做局部更新提升效率,需要为时序数据添加跨X轴的运行限值水平线。在单图中用fig.add_hline正常,但在包含Indicator和Scatter的子图中调用该方法时触发报错:

Traceback (most recent call last):
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/src/app.py", line 173, in <module>
    app = main()
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/src/app.py", line 168, in main
    app.layout = layout_partialupdate.make_layout(app, APP_TITLE, INTERVAL_REFRESH)
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/src/layout_partialupdate.py", line 53, in make_layout
    dbc.Col(utils.make_kpi_graph_card(
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/src/utils.py", line 84, in make_kpi_graph_card
    fig.add_hline(
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/.venv/lib/python3.10/site-packages/plotly/graph_objs/_figure.py", line 1084, in add_hline
    return super(Figure, self).add_hline(
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/.venv/lib/python3.10/site-packages/plotly/basedatatypes.py", line 4122, in add_hline
    self._process_multiple_axis_spanning_shapes(
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/.venv/lib/python3.10/site-packages/plotly/basedatatypes.py", line 4044, in _process_multiple_axis_spanning_shapes
    self.add_shape(
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/.venv/lib/python3.10/site-packages/plotly/graph_objs/_figure.py", line 24028, in add_shape
    return self._add_annotation_like(
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/.venv/lib/python3.10/site-packages/plotly/basedatatypes.py", line 1590, in _add_annotation_like
    not self._subplot_not_empty(
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/.venv/lib/python3.10/site-packages/plotly/basedatatypes.py", line 4231, in _subplot_not_empty
    for t in [
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/.venv/lib/python3.10/site-packages/plotly/basedatatypes.py", line 4235, in <listcomp>
    "x" if d[xaxiskw] is None else d[xaxiskw],
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/.venv/lib/python3.10/site-packages/plotly/basedatatypes.py", line 4718, in __getitem__
    self._raise_on_invalid_property_error(_error_to_raise=PlotlyKeyError)(
  File "/home/brakjen/dev/akerbp/W2W_dashboard_frontend/.venv/lib/python3.10/site-packages/plotly/basedatatypes.py", line 5092, in _ret
    raise _error_to_raise(
_plotly_utils.exceptions.PlotlyKeyError: Invalid property specified for object of type plotly.graph_objs.Indicator: 'xaxis'

子图创建代码如下:

def make_kpi_graph_card(
    component_id: Union[str, None]=None,
    col: Union[str, None]=None,
    color: Union[str, None]=None,
    limits: Union[Tuple[float, float], Tuple[None, None]]=(None, None)
) -> dbc.Card:
    specs = [
        [{"type": "indicator"}] * 4,
        [{"type": "scatter", "colspan": 4}] + [None] * 3
    ]

    fig = ps.make_subplots(
        rows=2,
        cols=4,
        specs=specs,
        vertical_spacing=0.2,
        row_heights=(0.1, 0.9)
    )

    # Add the indicators
    for i in range(4):
        kpi_title=KPI_ORDER[i]
        fig.add_trace(go.Indicator(
            value=0,
            title={"text": KPI_TITLE_MAPPER[kpi_title], "font": {"color": color, "size": 35}},
            mode="number+delta",
            number={"valueformat": ".2s", "font": {"color": color, "size": 40}},
            delta={"font": {"size": 35}, "valueformat": ".2s"}
        ),
        row=1, col=i+1)

    # Add the scatter plot
    fig.add_trace(go.Scatter(
        x=None,
        y=None,
        line={"width": 4, "color": color},
        showlegend=False
    ),
    row=2, col=1)

    # Define x axis properties
    xaxis_props = dict(
        xaxis_tickfont={"size": 20},
        xaxis_ticks="outside",
        xaxis_mirror=True,
        xaxis_showline=True
    )

    # Define y axis properties
    yaxis_props = dict(
        yaxis_tickfont={"size": 30},
        yaxis_ticks="outside",
        yaxis_mirror=True,
        yaxis_showline=True,
    )

    # Update layout and return the Card
    fig.update_layout(template="plotly_dark", **xaxis_props, **yaxis_props)
    
    return dbc.Card(
        dbc.CardBody([
            html.H1(col, style={"color": color, "textAlign": "center"}),
            dcc.Graph(figure=fig, id=component_id)
        ])
    )

解决方案

报错根源是add_hline默认会遍历所有子图检测坐标轴,但Indicator类型子图没有xaxis属性,导致冲突。以下两种方法可解决:

方法1:用add_hline指定作用子图

在添加水平线时,通过row和col参数明确指定仅作用于第二行的Scatter子图,避免检测Indicator子图:

# 在fig.update_layout之前添加以下代码
lower_limit, upper_limit = limits
if lower_limit is not None:
    fig.add_hline(
        y=lower_limit,
        line_dash="dash",
        line_color="red",
        row=2,  # 指定Scatter所在行
        col=1   # 指定Scatter所在列
    )
if upper_limit is not None:
    fig.add_hline(
        y=upper_limit,
        line_dash="dash",
        line_color="green",
        row=2,
        col=1
    )

方法2:用add_shape手动创建水平线

直接定义水平线形状,并通过xref="x domain"设置横跨整个X轴范围,同时绑定到Scatter子图的坐标轴:

# 在fig.update_layout之前添加以下代码
lower_limit, upper_limit = limits
if lower_limit is not None:
    fig.add_shape(
        type="line",
        y0=lower_limit,
        y1=lower_limit,
        x0=0,
        x1=1,
        xref="x domain",  # 基于X轴范围的百分比,0到1覆盖整个轴
        yref="y",
        line=dict(color="red", dash="dash"),
        row=2,
        col=1
    )
if upper_limit is not None:
    fig.add_shape(
        type="line",
        y0=upper_limit,
        y1=upper_limit,
        x0=0,
        x1=1,
        xref="x domain",
        yref="y",
        line=dict(color="green", dash="dash"),
        row=2,
        col=1
    )

两种方法都能在初始无X轴数据的情况下正常显示水平线,后续通过dcc.Patch更新Scatter数据时,水平线会自动适配X轴的实际范围。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 02:32:43