Plotly 中实现时间轴上按事件类型动态着色的矩形背景(支持离散事件映射)

4次阅读

Plotly 中实现时间轴上按事件类型动态着色的矩形背景(支持离散事件映射)

本文详解如何在 plotly 图表中为时间序列数据(如眼动轨迹)添加与事件标签(e ∈ {0–5})严格对齐、颜色可区分的底部矩形背景,提供基于 add_shape 的逐段渲染与 Heatmap 高效替代两种专业方案。

本文详解如何在 plotly 图表中为时间序列数据(如眼动轨迹)添加与事件标签(e ∈ {0–5})严格对齐、颜色可区分的底部矩形背景,提供基于 `add_shape` 的逐段渲染与 `heatmap` 高效替代两种专业方案。

在可视化眼动追踪等时序行为数据时,常需将原始坐标(x, y)曲线与底层事件标注(如 e = 0 表示眨眼,1=注视,2=扫视等)同步呈现。理想效果是:在时间轴(t)下方或图底区域,用连续、无间隙、颜色随 e 值变化的矩形条直观标示各时刻所属事件类型。然而,Plotly 的 add_shape(type=”rect”) 不支持数组型 fillcolor——直接传入 e 或其映射色列表会触发 ValueError: Invalid value of type ‘numpy.ndarray’ received for the ‘fillcolor’ Property

✅ 方案一:使用 add_shape 逐段绘制(精确控制,适合中小规模数据)

核心思想是将整个时间区间 [t[0], t[-1]] 拆分为 len(e) 个等宽子区间(每个对应一个采样点),为每个区间单独添加一个矩形。关键在于正确计算每个矩形的 x0 和 x1 边界:

import numpy as np import plotly.graph_objects as go from plotly.subplots import make_subplots import plotly.express as px  # 示例数据(同问题中) t = np.arange(30) x = np.array([125.9529, 124.6142, 125.0569, 125.3117, 126.7498, 127.035, 125.4822, 125.6249, 126.9371, 127.6047,                129.031 , 128.2419, 121.521 , 114.7071, 109.4141, 100.5057,  94.9606,  95.2231, 95.9032,  96.4991,                101.2602, 103.9582, 108.2527, 108.8801, 110.3254, 112.8205, 113.0079, 113.3547, 113.0962, 113.2508]) y = np.array([31.218 , 31.236 , 31.147 , 31.2614, 30.806 , 30.8423, 31.727, 32.2256, 32.0504, 32.7774,                34.7089, 37.0671, 46.309 , 55.9716, 62.4481, 68.0248, 75.4912, 79.0622, 81.2176, 83.191 ,                83.7656, 84.6713, 83.9343, 82.4546, 81.1652, 80.7981, 80.2136, 80.7405, 80.4398, 80.0738]) e = np.array([1., 1., 1., 1., 1., 1., 1., 1., 1., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.,                3., 3., 3., 3., 3., 3., 4., 4., 4., 4.])  # 创建主图(双曲线 + 底部事件条) fig = make_subplots() fig.add_trace(go.Scatter(x=t, y=x, mode='lines', name='X (gaze)'), secondary_y=False) fig.add_trace(go.Scatter(x=t, y=y, mode='lines', name='Y (gaze)'), secondary_y=False)  # 定义离散色板(确保 e 的每个整数值有唯一颜色) palette = px.colors.qualitative.D3  # 10色,足够覆盖 0-5 colors = [palette[int(val)] for val in e]  # 映射 e → 颜色  # 计算每个矩形的 x 边界:n 个点 → n+1 个分界点(中心对齐) t_res = np.diff(t).mean() if len(t) > 1 else 1.0  # 时间分辨率(假设等距) posts = np.append(t, t[-1] + t_res) - t_res / 2  # [t0-Δ/2, t1-Δ/2, ..., t_end+Δ/2]  # 逐段添加矩形(底部高度设为 y 轴范围的 5%) y_max = max(np.max(x), np.max(y)) y_bottom = 0 y_top = 0.05 * y_max  for i in range(1, len(posts)):     fig.add_shape(         type="rect",         x0=posts[i-1], x1=posts[i],         y0=y_bottom, y1=y_top,         line=dict(color="black", width=0.5),  # 细边框提升可读性         fillcolor=colors[i-1],         layer="below"  # 确保矩形在曲线之下     )  # 可选:隐藏 y 轴次要刻度以简化底部区域 fig.update_yaxes(range=[-0.02*y_max, 1.05*y_max], secondary_y=False) fig.update_layout(height=400, title="Eye Tracking: X/Y Trajectory with Event-Based Background") fig.show()

⚠️ 注意事项

  • 此方法生成 len(e) 个独立 shape,当 len(e) > 10⁴ 时可能影响渲染性能;
  • posts 构造必须保证相邻矩形无缝拼接(使用 t_res/2 偏移实现“中心采样”语义);
  • 若 t 非等距,请改用 np.interp() 或 pd.cut() 动态计算边界。

✅ 方案二:使用 go.Heatmap(高性能、原生支持离散映射)

更优雅且高效的方式是将事件序列 e 视为单行热力图(z = [e]),其 x 轴为 t,y 轴压缩为单层高度。Plotly 热力图天然支持离散色阶与自定义 colorbar:

# 在同一 fig 中添加热力图(替代手动矩形) fig.add_trace(     go.Heatmap(         z=[e],                          # 1×N 矩阵         x=t,                            # 时间轴         y=[0, y_top],                   # y 范围:从 0 到矩形高度         zmin=0, zmax=5,                 # 强制色阶覆盖全部事件值         colorscale=[[i/5, palette[i]] for i in range(6)],  # 精确映射 0→5         colorbar=dict(             title="Gaze Event",             tickvals=list(range(6)),             ticktext=["Blink", "Fixation", "Saccade", "Pursuit", "Smooth", "Other"],             len=0.4,             yanchor="bottom", y=0.02         ),         showscale=True,         hovertemplate="t=%{x:.1f}s<br>Event=%{z}<extra></extra>"     ),     secondary_y=False )  # 关键:设置 yaxis 范围,使热力图紧贴横轴底部 fig.update_yaxes(range=[-0.02*y_max, 1.05*y_max], secondary_y=False)

优势总结

  • 渲染性能优异,万级时间点仍流畅;
  • 自动处理坐标对齐与抗锯齿;
  • 内置 hovertemplate 与 colorbar 配置,交互体验更专业;
  • 支持 colorscale 精确绑定离散值(推荐用 [[v, color], …] 格式)。

? 最终建议

  • 中小数据量( → 选用 add_shape 方案;
  • 大数据量、强调性能与可维护性 → 首选 Heatmap 方案;
  • 无论哪种方案,务必统一 t 的分辨率逻辑,并在 colorbar 或图例中明确定义 e 值语义(如 Fixation=1),这是科学可视化的基础规范。

通过以上任一方法,你都能在 Plotly 中构建出专业、准确、可解释的眼动事件-轨迹联合视图,为行为分析提供坚实可视化支撑。

text=ZqhQzanResources