Plotly中add_scattergl如何配合facet_row在各分面子图绘制故障线
问题原因
Plotly Express 生成facet_row纵向分面时,本质是创建了多个独立排布的子图坐标系。直接全局调用add_scattergl添加轨迹且不指定子图位置参数时,所有轨迹会默认被添加到第1行第1列的子图中,不会自动按照Unit字段拆分匹配到对应分面,因此所有故障线会堆叠在单个子图内。
修复方案
按分面对应的Unit值拆分故障数据,遍历每个分面单独添加对应故障曲线,显式指定轨迹所属的子图行号即可。
- 先提取facet分面从上到下对应的
Unit顺序,和子图行号一一对应 - 逐行过滤当前单位下的故障数据,添加轨迹时通过
row、col参数指定所属子图 - 仅在第一个子图显示故障线的图例,避免图例重复
修正后的核心代码
替换原代码中单独调用add_scattergl的部分即可:
# 提取facet从上到下对应的Unit值顺序 unit_order = [anno.text.split('=')[-1] for anno in figline.layout.annotations] # 逐分面添加对应故障曲线 for row_num, unit_val in enumerate(unit_order, start=1): # 过滤当前单位下的故障数据 fault_subset = df2[(df2["Unit"] == unit_val) & (df2["DataType"] == "Fault")] figline.add_scattergl( x=fault_subset["DateTime"], y=fault_subset["Value"], name="Fault", line={"color": "red"}, row=row_num, col=1, # 仅第一个子图显示图例 showlegend=(row_num == 1) )
完整可运行代码
import numpy as np import pandas as pd import plotly.express as px import plotly.graph_objs as go from plotly.offline import init_notebook_mode, iplot init_notebook_mode() df2 = pd.DataFrame([ dict(DateTime='2022-01-01 00:00:00', Value=90, Point="A", Unit='%', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:15:00', Value=80, Point="A", Unit='%', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:30:00', Value=85, Point="A", Unit='%', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:45:00', Value=92, Point="A", Unit='%', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 01:00:00', Value=100, Point="A", Unit='%', DataType='Fault',Type='Demo'), dict(DateTime='2022-01-01 01:15:00', Value=72, Point="A", Unit='%', DataType='Fault',Type='Demo'), dict(DateTime='2022-01-01 00:00:00', Value=22, Point="B", Unit='°C', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:15:00', Value=22, Point="B", Unit='°C', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:30:00', Value=23, Point="B", Unit='°C', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:45:00', Value=20, Point="B", Unit='°C', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 01:00:00', Value=21, Point="B", Unit='°C', DataType='Fault', Type='Demo'), dict(DateTime='2022-01-01 01:15:00', Value=23, Point="B", Unit='°C', DataType='Fault', Type='Demo'), dict(DateTime='2022-01-01 00:00:00', Value=24, Point="C", Unit='°C', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:15:00', Value=24, Point="C", Unit='°C', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:30:00', Value=24, Point="C", Unit='°C', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:45:00', Value=24, Point="C", Unit='°C', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 01:00:00', Value=24, Point="C", Unit='°C', DataType='Fault', Type='Demo'), dict(DateTime='2022-01-01 01:15:00', Value=24, Point="C", Unit='°C', DataType='Fault', Type='Demo'), dict(DateTime='2022-01-01 00:00:00', Value=60, Point="D", Unit='Pa', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:15:00', Value=58, Point="D", Unit='Pa', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:30:00', Value=62, Point="D", Unit='Pa', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:45:00', Value=61, Point="D", Unit='Pa', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 01:00:00', Value=64, Point="D", Unit='Pa', DataType='Fault', Type='Demo'), dict(DateTime='2022-01-01 01:15:00', Value=59, Point="D", Unit='Pa', DataType='Fault', Type='Demo'), dict(DateTime='2022-01-01 00:00:00', Value=0, Point="E", Unit='Binary', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:15:00', Value=0, Point="E", Unit='Binary', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:30:00', Value=1, Point="E", Unit='Binary', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 00:45:00', Value=1, Point="E", Unit='Binary', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 01:00:00', Value=1, Point="E", Unit='Binary', DataType='Fault', Type='Demo'), dict(DateTime='2022-01-01 01:15:00', Value=1, Point="E", Unit='Binary', DataType='Fault', Type='Demo'), dict(DateTime='2022-01-01 01:30:00', Value=0, Point="E", Unit='Binary', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 01:45:00', Value=0, Point="E", Unit='Binary', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 02:00:00', Value=1, Point="E", Unit='Binary', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 02:15:00', Value=1, Point="E", Unit='Binary', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 02:30:00', Value=1, Point="E", Unit='Binary', DataType='Normal', Type='Demo'), dict(DateTime='2022-01-01 02:45:00', Value=1, Point="E", Unit='Binary', DataType='Normal', Type='Demo'), ]) figline = px.line(df2, x="DateTime", y="Value", color="Point", line_group="Unit", hover_name="Point", facet_row="Unit", line_shape="spline", render_mode="svg") figline.update_yaxes(autorange=True) figline.update_layout(hovermode="x unified") figline.update_yaxes(matches=None) # 逐分面添加故障曲线 unit_order = [anno.text.split('=')[-1] for anno in figline.layout.annotations] for row_num, unit_val in enumerate(unit_order, start=1): fault_subset = df2[(df2["Unit"] == unit_val) & (df2["DataType"] == "Fault")] figline.add_scattergl( x=fault_subset["DateTime"], y=fault_subset["Value"], name="Fault", line={"color": "red"}, row=row_num, col=1, showlegend=(row_num == 1) ) figline.show()
注意事项
- Plotly 子图的行、列索引从
1开始计数,因此enumerate需要设置start=1,否则会出现子图位置匹配错误 - 提取
unit_order时是通过读取facet自动生成的标注文本拆分得到,和子图从上到下的顺序完全一致,不会出现单位和子图错位的问题 - 故障线会自动适配对应子图的y轴范围,不需要额外手动调整坐标轴参数
内容的提问来源于stack exchange,提问作者Sartaj0111
相关产品推荐
相关产品推荐

