Python在Excel中制作甘特图的完整示例代码
作者:大丸子
Excel 的图表类型里没有甘特图。要在一张图上表示任务的起止区间,通行的做法是把两段数据叠进同一个条形:第一段从坐标轴起点画到开始日期,第二段画工期。图表类型仍是堆积条形图,决定它像不像甘特图的是两个地方——第一段是否可见,以及数值轴用的是日期位置还是分类序号。
Free Spire.XLS for Python 把图表拆成 Chart、Series 和两组坐标轴对象,这套构造的每一步都对应其中的一个对象。数据是一份 8 项任务的排期:2026-11-02 开始,2027-01-08 结束,含首尾共 68 天。
pip install Spire.Xls.Free
三列数据,两段堆叠
任务表只需要三列:任务名、开始日期、工期。开始日期与工期都以天为单位进入图表,前者决定条形左端的位置,后者决定条形的长度。
| 任务 | 开始日期 | 工期(天) |
|---|---|---|
| 需求调研 | 2026-11-02 | 5 |
| 接口设计 | 2026-11-05 | 9 |
| 核心开发 | 2026-11-12 | 23 |
| 前端联调 | 2026-11-25 | 14 |
| 系统测试 | 2026-12-07 | 12 |
| 安全评估 | 2026-12-14 | 9 |
| 上线准备 | 2026-12-21 | 8 |
| 验收交付 | 2026-12-28 | 12 |
图表里的对象关系如下:
| 对象 | 在甘特图里的角色 |
|---|---|
Chart | 图表类型、标题、绘图区位置 |
Series[0] | 开始日期,不可见的偏移段 |
Series[1] | 工期,可见的条形 |
PrimaryCategoryAxis | 任务名所在的纵轴,控制阅读顺序 |
PrimaryValueAxis | 日期所在的横轴,控制时间刻度 |
日期先换成序列值
Excel 的日期在内部是序列值。它把 1900-01-01 记为 1,又为并不存在的 1900-02-29 留了一天;对 1900-03-01 之后的日期,序列值比"从 1900-01-01 起经过的天数"多 2。换算基准落在 1899-12-30:
from datetime import date
EPOCH = date(1899, 12, 30)
def serial(d):
return (d - EPOCH).days
TASKS = [
("需求调研", date(2026, 11, 2), date(2026, 11, 6)),
("接口设计", date(2026, 11, 5), date(2026, 11, 13)),
("核心开发", date(2026, 11, 12), date(2026, 12, 4)),
("前端联调", date(2026, 11, 25), date(2026, 12, 8)),
("系统测试", date(2026, 12, 7), date(2026, 12, 18)),
("安全评估", date(2026, 12, 14), date(2026, 12, 22)),
("上线准备", date(2026, 12, 21), date(2026, 12, 28)),
("验收交付", date(2026, 12, 28), date(2027, 1, 8)),
]按这个基准,2026-11-02 是 46328,2027-01-08 是 46395,整段排期落在 68 天里。数值轴只接受数值:把 2026-11-02 这样的文本写进单元格,它不会被换算成位置,条形也就没有长度可画。
写入工作表
from spire.xls import *
from spire.xls.common import *
book = Workbook()
book.Version = ExcelVersion.Version2016
book.Worksheets.Clear()
sheet = book.Worksheets.Add("排期")
headers = ["任务", "开始日期", "工期(天)"]
for col, text in enumerate(headers, start=1):
cell = sheet.Range[1, col]
cell.Value = text
cell.Style.Font.IsBold = True
cell.Style.Font.Color = Color.get_White()
cell.Style.Color = Color.FromArgb(255, 31, 78, 121)
cell.Style.HorizontalAlignment = HorizontalAlignType.Center
for row, (name, start, end) in enumerate(TASKS, start=2):
sheet.Range[row, 1].Value = name
sheet.Range[row, 2].NumberValue = serial(start)
sheet.Range[row, 3].NumberValue = (end - start).days + 1Worksheets.Clear() 去掉新建工作簿自带的三张空表。NumberValue 写入序列值,单元格的数字格式负责显示:
sheet.Range["B2:B9"].NumberFormat = "yyyy-mm-dd" sheet.SetColumnWidth(1, 18) sheet.SetColumnWidth(2, 14) sheet.SetColumnWidth(3, 11)
单元格里存的仍是 46328 这样的数值,显示为 2026-11-02。图表的日期轴读的就是这些数值,写入时保持数值类型即可。

建图:两个系列
chart = sheet.Charts.Add(ExcelChartType.BarStacked) chart.DataRange = sheet.Range["A1:C9"] chart.SeriesDataFromRange = False chart.LeftColumn = 1 chart.TopRow = 12 chart.RightColumn = 14 chart.BottomRow = 36 chart.ChartTitle = "支付网关 v2.3 项目排期" chart.ChartTitleArea.Size = 14 chart.HasLegend = False
DataRange 取 A1:C9,第一行提供系列名,第一列提供分类标签,B、C 两列各成一个系列。SeriesDataFromRange = False 让系列按列组织;保持默认时,图表会把每一行读成一个系列,8 项任务变成 8 个系列,堆叠关系就不成立。
堆积条形图先画的系列在左、后画的在右。开始日期放进第一个系列,工期放进第二个系列,一个条形由两段拼成:
offset = chart.Series[0] offset.Format.Fill.FillType = ShapeFillType.NoFill bars = chart.Series[1] bars.Format.Fill.FillType = ShapeFillType.SolidColor bars.Format.Fill.ForeColor = Color.FromArgb(255, 47, 117, 181) bars.Format.Options.GapWidth = 55
第一段设为 NoFill 后不可见,但它在坐标轴上仍占长度。堆积条的基线是数值轴的起点而不是 0:轴最小值取 2026-11-02 时,2026-11-02 开始的任务偏移段长度为 0,2026-11-05 开始的任务偏移 3 天,第二段就落在各自的开始日期上。系列本身要保留:它承担的是偏移量,删掉后工期值(5 到 23)会直接落在 46328 起步的轴上,条形就失去定位。
坐标轴:倒序与日期刻度
条形图默认把第一个分类画在底部,与表的阅读顺序相反。类别轴的 IsReverseOrder 把顺序翻过来:
category_axis = chart.PrimaryCategoryAxis category_axis.IsReverseOrder = True category_axis.HasMajorGridLines = False category_axis.MajorTickMark = TickMarkType.TickMarkNone category_axis.Font.Size = 10
倒序之后,第一条任务落到图的顶部,日期标签也从图底移到图顶。时间轴在上、任务自上而下排列,正是甘特图常见的布局。
数值轴的窗口单独设定:
value_axis = chart.PrimaryValueAxis value_axis.MinValue = serial(TASKS[0][1]) value_axis.MaxValue = serial(date(2027, 1, 11)) value_axis.MajorUnit = 7 value_axis.NumberFormat = "m/d" value_axis.IsSourceLinked = False value_axis.HasMajorGridLines = True value_axis.MajorGridLines.LineProperties.Color = Color.FromArgb(255, 217, 217, 217)
最小值取第一项任务的开始日期(46328),最大值取末项结束后的那个周一(46398,2027-01-11),主刻度为 7 天。三个值一起决定网格线的密度与位置:每个周一落一条竖线,轴跨度正好 10 周。最大值在代码里是固定日期,换一份任务表时按末项结束日期向上取整到周一即可。
不设这三个值时,Excel 会按数据自动缩放。同一份数据的自动窗口是 2026-09-15 到 2027-02-02,主刻度 20 天,首项任务之前空出 48 天,标签沿用单元格的 yyyy-mm-dd 格式,条形被压在图右侧。显式设定窗口,图面才是排期的实际跨度。
NumberFormat = "m/d" 把序列值显示成 11/2 这样的短日期;IsSourceLinked = False 关掉与源单元格的格式联动,否则轴标签会继承单元格的 yyyy-mm-dd,11 个标签挤在一起。

条形宽度与工期标签
GapWidth 控制同类条形之间的间距,取值越小条形越粗。55 让 8 条任务在 306 磅高的图里保持可读,又不至于互相贴住。工期标签放在条形内部:
labels = bars.DataPoints.DefaultDataPoint.DataLabels labels.HasValue = True labels.NumberFormat = '0"天"' labels.Size = 9 labels.Color = Color.get_White()
堆积条形的数据标签落在段内,白字配深蓝填充,数字与背景分得开。标签显示的是第二段的值,也就是工期,不会把开始日期一起带出来。
输出与核对
book.SaveToFile("项目排期_甘特图.xlsx", ExcelVersion.Version2016)
book.Dispose()产物读回来,核对三件事:工作表与图表数量、系列数与类别数、轴窗口是否保留:
book = Workbook()
book.LoadFromFile("项目排期_甘特图.xlsx")
sheet = book.Worksheets[0]
chart = sheet.Charts[0]
print(book.Worksheets.Count, sheet.Charts.Count, chart.Series.Count)
print(chart.Series[0].PointNumber, chart.Series[1].PointNumber)
print(chart.PrimaryValueAxis.MinValue, chart.PrimaryValueAxis.MaxValue,
chart.PrimaryValueAxis.MajorUnit)输出是:
1 1 2
8 8
46328.0 46398.0 7.0
再用 SaveChartAsImage 导出图表 PNG(1012 × 408),逐项核对条形起止、标签位置和轴刻度:
image = book.SaveChartAsImage(sheet, 0)
image.Save("check/甘特图_核对.png")文件本身 10,240 字节,用 Excel 打开后图表对象为 724.5 × 306 磅,与代码里设定的行列范围一致。
何时适用
这套构造适用于任何"任务 + 起止日期"的数据:项目排期、活动筹备、内容日历、设备检修计划。数据来自 CSV、数据库或规划系统时,只要在写入前换成序列值,图表代码不用改。
任务数变化时,数据区域与轴窗口要跟着调整:DataRange 的末行由任务数决定,轴最大值按末项结束日期向上取到周一。把这两个值算出来,同一段代码可以按不同的任务表生成不同的排期图。
Free 版对单份工作簿的图表数与工作表数有上限,这里的产物是 1 张表、1 个图表,不受影响;更大的工作簿需要商业版。若要把图放进报告,SaveChartAsImage 导出的 PNG 可以直接插入 Word 或 PDF;留在工作簿里的图表保留数据链接,改表即可改图。
到此这篇关于Python在Excel中制作甘特图的完整示例代码的文章就介绍到这了,更多相关Python生成Excel甘特图内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
