如何在Python中绘制含0时长任务的甘特图(Plotly/Matplotlib)
解决甘特图中0时长任务不显示的问题
Plotly 简便方案:给0时长任务添加微小时间增量
由于Plotly的timeline无法识别起止时间完全相同的任务,最简单的处理方式是给这类任务的结束时间加一个极小的时间差(比如1秒),让图表能渲染出一个极短的条形,同时不影响数据逻辑:
import pandas as pd import plotly.express as px df = pd.DataFrame([ dict(Task="1", Start='2023-03-15', End='2023-03-15'), dict(Task="2", Start='2023-03-03', End='2023-03-10'), dict(Task="3", Start='2023-03-10', End='2023-03-15'), ]) # 转换为datetime类型 df['Start'] = pd.to_datetime(df['Start']) df['End'] = pd.to_datetime(df['End']) # 给0时长任务的结束时间加1秒 mask = df['Start'] == df['End'] df.loc[mask, 'End'] += pd.Timedelta(seconds=1) fig = px.timeline(df, x_start="Start", x_end="End", y="Task") fig.update_yaxes(autorange="reversed") # 自定义hover模板,隐藏那1秒的增量 fig.update_traces(hovertemplate='Task: %{y}<br>Date: %{x_start}') fig.show()
Plotly 进阶方案:用散点标记0时长任务
如果希望明确将0时长任务显示为点而非短条形,可以分开处理两类任务,用散点图标记0时长任务:
import pandas as pd import plotly.express as px import plotly.graph_objects as go df = pd.DataFrame([ dict(Task="1", Start='2023-03-15', End='2023-03-15'), dict(Task="2", Start='2023-03-03', End='2023-03-10'), dict(Task="3", Start='2023-03-10', End='2023-03-15'), ]) df['Start'] = pd.to_datetime(df['Start']) df['End'] = pd.to_datetime(df['End']) # 先绘制有时长的任务 fig = px.timeline(df[df['Start'] != df['End']], x_start="Start", x_end="End", y="Task") fig.update_yaxes(autorange="reversed") # 为0时长任务添加散点标记 zero_tasks = df[df['Start'] == df['End']] for _, row in zero_tasks.iterrows(): fig.add_trace(go.Scatter( x=[row['Start']], y=[row['Task']], mode='markers', marker=dict(size=10, color=fig.data[0].marker.color), hovertemplate='Task: %{y}<br>Date: %{x}' )) fig.show()
Matplotlib 方案
如果使用Matplotlib,可以直接结合水平条形图和散点图分别处理正常任务和0时长任务:
import pandas as pd import matplotlib.pyplot as plt import matplotlib.dates as mdates df = pd.DataFrame([ dict(Task="1", Start='2023-03-15', End='2023-03-15'), dict(Task="2", Start='2023-03-03', End='2023-03-10'), dict(Task="3", Start='2023-03-10', End='2023-03-15'), ]) df['Start'] = pd.to_datetime(df['Start']) df['End'] = pd.to_datetime(df['End']) # 定义任务的y轴位置 task_list = df['Task'].unique() y_positions = [task_list.index(task) for task in df['Task']] fig, ax = plt.subplots(figsize=(10, 4)) # 绘制有时长的任务 non_zero_tasks = df[df['Start'] != df['End']] ax.barh( y=[task_list.index(t) for t in non_zero_tasks['Task']], width=(non_zero_tasks['End'] - non_zero_tasks['Start']).dt.days, left=non_zero_tasks['Start'], height=0.5, color='skyblue' ) # 绘制0时长任务的散点 zero_tasks = df[df['Start'] == df['End']] ax.scatter( x=zero_tasks['Start'], y=[task_list.index(t) for t in zero_tasks['Task']], color='darkblue', s=100, zorder=5 # 确保点在条形图上方 ) # 设置x轴日期格式 ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')) plt.xticks(rotation=45) # 设置y轴标签 ax.set_yticks(y_positions) ax.set_yticklabels(df['Task']) plt.tight_layout() plt.show()
内容的提问来源于stack exchange,提问作者lunamaria
相关产品推荐
相关产品推荐

