如何在含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
相关产品推荐
相关产品推荐

