如何在Dash分面子图间动态添加标注箭头?
如何在Dash+Plotly分面散点图中动态添加子图间的标注箭头
问题背景
我有一个单ID对应多条记录的数据集,示例构造代码如下:
import pandas as pd import numpy as np import random df = pd.DataFrame({'DATE_TIME':pd.date_range('2022-11-01', '2022-11-05 23:00:00',freq='h'), 'SBP':[random.uniform(110, 160) for n in range(120)], 'DBP':[random.uniform(60, 100) for n in range(120)], 'ID':[random.randrange(1, 100) for n in range(120)], 'TIMEINTERVAL':[random.randrange(1, 200) for n in range(120)]}) df['VISIT'] = df['DATE_TIME'].dt.day df['MODE'] = np.select([df['VISIT']==1, df['VISIT'].isin([2,3])], ['New', 'InProgress'], 'Done')
我用以下Dash代码实现了带ID下拉选择器和TIMEINTERVAL范围滑块的交互式分面散点图:
from dash import Dash, html, dcc, Input, Output, callback import plotly.express as px app = Dash(__name__) app.layout = html.Div([ html.H4('ABPM数据集交互式散点图'), dcc.Graph(id="scatter-plot"), html.P("按时间间隔筛选:"), dcc.Dropdown(df.ID.unique(), id='pandas-dropdown-1'), # ID选择器 dcc.RangeSlider( id='range-slider', min=0, max=600, step=10, marks={0: '0', 50: '50', 100: '100', 150: '150', 200: '200', 250: '250', 300: '300', 350: '350', 400: '400', 450: '450', 500: '500', 550: '550', 600: '600'}, value=[0, 600] ), html.Div(id='dd-output-container') ]) @app.callback( Output("scatter-plot", "figure"), Input("pandas-dropdown-1", "value"), Input("range-slider", "value"), prevent_initial_call=True) def update_scatter_plot(value, slider_range): low, high = slider_range df1 = df.query("ID == @value & TIMEINTERVAL > @low & TIMEINTERVAL < @high").copy() if df1.shape[0] != 0: fig = px.scatter(df1, x="DATE_TIME", y=["SBP","DBP"], hover_data=['TIMEINTERVAL'],facet_col='VISIT', facet_col_wrap=2, symbol='MODE') fig.update_xaxes(matches=None, showticklabels=True) return fig else: return dash.no_update app.run_server(debug=True, use_reloader=False)
需求与问题
我希望当筛选后的df1中存在VISIT值大于1的记录时,在分面子图之间添加标注箭头辅助数据读取。尝试添加的代码无法编译:
def update_scatter_plot(value, slider_range): low, high = slider_range df1 = df.query("ID == @value & TIMEINTERVAL > @low & TIMEINTERVAL < @high").copy() if df1.shape[0] != 0: fig = px.scatter(df1, x="DATE_TIME", y=["SBP","DBP"], hover_data=['TIMEINTERVAL'],facet_col='VISIT', facet_col_wrap=2, symbol='MODE') fig.update_xaxes(matches=None, showticklabels=True) if df1.VISIT!=1: fig.add_annotation( xref="x domain", yref="y domain", # 箭头头部位于x轴从左数25%位置 x=0.25, # 箭头头部位于y轴从下数40%位置 y=0.4, arrowhead=2, ) return fig else: return dash.no_update
当前可视化效果:
期望实现的效果:
需要实现箭头数量随所选ID的VISIT数量动态变化的功能。
解决方案
问题分析
原代码无法编译的核心原因:
- 缩进层级混乱,不符合Python语法规范
- 判断逻辑错误:
df1.VISIT!=1是对整列的布尔判断,返回数组而非单个布尔值,不能直接作为条件
要实现动态箭头,需先获取当前ID下的有效VISIT列表,再根据分面布局(facet_col_wrap=2)计算子图位置,针对性添加箭头。
修改后的完整回调函数
@app.callback( Output("scatter-plot", "figure"), Input("pandas-dropdown-1", "value"), Input("range-slider", "value"), prevent_initial_call=True) def update_scatter_plot(value, slider_range): low, high = slider_range df1 = df.query("ID == @value & TIMEINTERVAL > @low & TIMEINTERVAL < @high").copy() if df1.shape[0] == 0: return dash.no_update fig = px.scatter(df1, x="DATE_TIME", y=["SBP","DBP"], hover_data=['TIMEINTERVAL'],facet_col='VISIT', facet_col_wrap=2, symbol='MODE') fig.update_xaxes(matches=None, showticklabels=True) # 获取当前ID下存在的VISIT值并排序 unique_visits = sorted(df1['VISIT'].unique()) # 仅当VISIT数量大于1时添加箭头 if len(unique_visits) > 1: # 遍历每一组相邻VISIT for i in range(len(unique_visits)-1): current_visit = unique_visits[i] next_visit = unique_visits[i+1] # 计算子图的索引(Plotly分面子图索引从1开始) current_idx = current_visit - min(unique_visits) + 1 next_idx = next_visit - min(unique_visits) + 1 # 判断相邻子图是否在同一行 if (current_idx - 1) // 2 == (next_idx - 1) // 2: # 同一行相邻子图:箭头从左子图右侧指向右子图左侧,垂直居中 fig.add_annotation( xref=f"x{current_idx} domain", yref=f"y{current_idx} domain", x=1.0, y=0.5, axref=f"x{next_idx} domain", ayref=f"y{next_idx} domain", ax=0.0, ay=0.5, arrowhead=2, arrowsize=1.5, arrowwidth=2, arrowcolor='#666666' ) else: # 换行后相邻子图:箭头从上子图底部指向下子图顶部,水平居中 fig.add_annotation( xref=f"x{current_idx} domain", yref=f"y{current_idx} domain", x=0.5, y=0.0, axref=f"x{next_idx} domain", ayref=f"y{next_idx} domain", ax=0.5, ay=1.0, arrowhead=2, arrowsize=1.5, arrowwidth=2, arrowcolor='#666666' ) return fig
代码说明
- 获取有效VISIT:先筛选当前ID下存在的VISIT并排序,确保箭头按访问顺序添加
- 子图索引计算:通过
current_idx = current_visit - min(unique_visits) + 1转换为Plotly分面子图的索引(从1开始) - 箭头位置逻辑:
- 同一行相邻子图:箭头水平连接,垂直居中
- 换行后相邻子图:箭头垂直连接,水平居中
- 样式统一:设置箭头的大小、宽度和颜色,保证视觉一致性
内容的提问来源于stack exchange,提问作者dspractician
相关产品推荐
相关产品推荐

