移除图表间隙:合并连续同色时间块的技术问询
问题描述
我用Python生成了一个时间块图表(如下所示),数据来自CSV文件,每行对应一个时间块。现在最后两个绿色时间块之间有微小间隙,想把间隙去掉,让它们变成连续的绿色长条。

示例数据
data = { 'start': ['2024-08-02 08:00', '2024-08-02 09:00', '2024-08-02 18:50'], 'ende': ['2024-08-02 09:00', '2024-08-02 18:50', '2024-08-03 10:00'], 'color': ['green', 'green','green', 'green', 'green'] }
当前生成图表的代码
def createBarChart(df): # 将‘start’和‘ende’列转换为datetime对象 df['start'] = pd.to_datetime(df['start']) df['ende'] = pd.to_datetime(df['ende']) # 创建图表 fig = go.Figure() for i, row in df.iterrows(): fig.add_trace(go.Scatter( x=[row['start'], row['ende'], row['ende'], row['start']], y=[1, 1, 0, 0], fill='toself', fillcolor=row['color'], mode='none', showlegend=False, )) # 格式化X轴 tickvals = pd.date_range(start=df['start'].min(), end=df['ende'].max(), freq='3H') ticktext = [] previous_day = None for d in tickvals: if previous_day is None or d.day != previous_day: ticktext.append(f"<b>{d.strftime('%d.%m')}</b>") else: ticktext.append(d.strftime('%H:%M')) previous_day = d.day fig.update_layout( yaxis=dict(showticklabels=False), xaxis=dict( tickformat='%H:%M', tickvals=tickvals, ticktext=ticktext ), margin=dict(l=20, r=20, t=10, b=20), height=200, ) return fig
解决方案
间隙出现的核心原因是:相邻同色时间块是独立的Scatter填充区域,即使时间上首尾衔接,渲染时也可能因精度问题出现缝隙。以下两种方案可以解决这个问题:
方案1:合并同色连续时间块
先预处理数据,把相邻、同色且首尾衔接的时间块合并成一个,从根源上消除间隙:
def merge_consecutive_blocks(df): # 按时间排序,确保块的顺序正确 df = df.sort_values('start').reset_index(drop=True) merged_blocks = [] if len(df) == 0: return pd.DataFrame(merged_blocks) # 初始化第一个块的参数 curr_start = df.iloc[0]['start'] curr_end = df.iloc[0]['ende'] curr_color = df.iloc[0]['color'] for i in range(1, len(df)): row = df.iloc[i] # 判断是否和当前块同色且首尾衔接 if row['color'] == curr_color and row['start'] == curr_end: curr_end = row['ende'] else: merged_blocks.append({ 'start': curr_start, 'ende': curr_end, 'color': curr_color }) # 更新为当前块的参数 curr_start = row['start'] curr_end = row['ende'] curr_color = row['color'] # 添加最后一个块 merged_blocks.append({ 'start': curr_start, 'ende': curr_end, 'color': curr_color }) return pd.DataFrame(merged_blocks) # 修改原图表生成函数,加入合并逻辑 def createBarChart(df): df['start'] = pd.to_datetime(df['start']) df['ende'] = pd.to_datetime(df['ende']) # 合并同色连续块 df = merge_consecutive_blocks(df) fig = go.Figure() for i, row in df.iterrows(): fig.add_trace(go.Scatter( x=[row['start'], row['ende'], row['ende'], row['start']], y=[1, 1, 0, 0], fill='toself', fillcolor=row['color'], mode='none', showlegend=False, )) # 后续X轴格式化代码保持不变 tickvals = pd.date_range(start=df['start'].min(), end=df['ende'].max(), freq='3H') ticktext = [] previous_day = None for d in tickvals: if previous_day is None or d.day != previous_day: ticktext.append(f"<b>{d.strftime('%d.%m')}</b>") else: ticktext.append(d.strftime('%H:%M')) previous_day = d.day fig.update_layout( yaxis=dict(showticklabels=False), xaxis=dict( tickformat='%H:%M', tickvals=tickvals, ticktext=ticktext ), margin=dict(l=20, r=20, t=10, b=20), height=200, ) return fig
方案2:改用Bar图绘制时间块
go.Bar天生支持连续块的无缝衔接,无需处理间隙问题,代码更简洁:
def createBarChart(df): df['start'] = pd.to_datetime(df['start']) df['ende'] = pd.to_datetime(df['ende']) # 计算每个时间块的持续时长(转换为毫秒,适配Bar图宽度) df['duration_ms'] = (df['ende'] - df['start']).dt.total_seconds() * 1000 fig = go.Figure() fig.add_trace(go.Bar( x=df['start'], y=[1] * len(df), # 固定y值,让所有块在同一水平线上 width=df['duration_ms'], marker_color=df['color'], orientation='h', showlegend=False )) # 调整X轴格式 tickvals = pd.date_range(start=df['start'].min(), end=df['ende'].max(), freq='3H') ticktext = [] previous_day = None for d in tickvals: if previous_day is None or d.day != previous_day: ticktext.append(f"<b>{d.strftime('%d.%m')}</b>") else: ticktext.append(d.strftime('%H:%M')) previous_day = d.day fig.update_layout( yaxis=dict(showticklabels=False, range=[0, 2]), # 调整y轴范围,让Bar显示正常 xaxis=dict( type='date', tickformat='%H:%M', tickvals=tickvals, ticktext=ticktext ), margin=dict(l=20, r=20, t=10, b=20), height=200, barmode='stack' ) return fig
两种方案都能彻底消除间隙,方案1适合需要保留原始数据逻辑的场景,方案2更简洁高效。
内容的提问来源于stack exchange,提问作者ranqnova
相关产品推荐
相关产品推荐

