项目管理的神器MS PROJECT,以强悍的项目可视化能力让项目管理人士爱不释手,但由于版权和软件更新原因,导致很多时候,都没机会使用它,项目管理里必备的甘特图,做起来就很困难了。本文引入Python来做甘特图,让项目管理风生水起。
甘特图的概念
先给读者简单介绍下甘特图是什么?有什么用?
甘特图(Gantt chart)又称为横道图、条状图(Bar chart)。其通过条状图来显示项目,进度,和其他时间相关的系统进展的内在关系随着时间进展的情况。以提出者亨利·劳伦斯·甘特(Henry Laurence Gantt)先生的名字命名。
甘特图以图示通过活动列表和时间刻度表示出特定项目的顺序与持续时间。一条线条图,横轴表示时间,纵轴表示项目,线条表示期间计划和实际完成情况。直观表明计划何时进行,进展与要求的对比。便于管理者弄清项目的剩余任务,评估工作进度。
甘特图是以作业排序为目的,将活动与时间联系起来的最早尝试的工具之一,帮助企业描述工作中心、超时工作等资源的使用。
甘特图包含以下三个含义:
1、以图形或表格的形式显示活动;
2、通用的显示进度的方法;
3、构造时含日历天和持续时间,不将周末节假算在进度内。
简单、醒目、便于编制,在管理中广泛应用。
Python生成甘特图的效果
为了简便起见,在用Python做甘特图之前,需要读者先动手准备几条简单数据,格式是:活动的名称,开始时间,结束时间。这样就构成了甘特图的基本元素。示例json数据如下:
{ 'label': 'Research', 'start':'2013-10-01
12:00:00', 'end': '2013-10-02 18:00:00'}, # @IgnorePep8
{ 'label': 'Compilation', 'start':'2013-10-02
09:00:00', 'end': '2013-10-02 12:00:00'}, # @IgnorePep8
{ 'label': 'Meeting #1', 'start':'2013-10-03
12:00:00', 'end': '2013-10-03 18:00:00'}, # @IgnorePep8
{ 'label': 'Design', 'start':'2013-10-04
09:00:00', 'end': '2013-10-10 13:00:00'}, # @IgnorePep8
{ 'label': 'Meeting #2', 'start':'2013-10-11
09:00:00', 'end': '2013-10-11 13:00:00'}, # @IgnorePep8
{ 'label': 'Implementation', 'start':'2013-10-12
09:00:00', 'end': '2013-10-22 13:00:00'}, # @IgnorePep8
{ 'label': 'Demo', 'start':'2013-10-23
09:00:00', 'end': '2013-10-23 13:00:00'}, # @IgnorePep8
看看运行Python生成甘特图的效果吧。
Python生成甘特图
如图,每项活动都按日期顺序衔接了起来,能看明白项目之间的先后次序,并有清晰的图例说明,和project生成的效果也可以媲美了吧。当然,示例中的甘特图不支持嵌套任务,但是它对于描述简单的任务分解结构已经够用了。还有很多细致的Project功能,大家不妨可以作为小练习来编写。
编程思路
接下来将使用下面的代码示例展示如何使用Python和matplotlib绘制甘特图。执行下面的步骤。
1.加载包含任务的TEST_DATA,并用TEST_DATA实例化Gantt类。
2.每一个任务包含一个标签,及开始和结束时间。
3.在坐标轴上绘制水平条来表示所有的任务。
4.为渲染的数据格式化x轴和y轴。
5.让图表布局紧凑些。
6.显示甘特图。
下面是示例代码。
from datetime import datetime
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.font_manager as font_manager
import matplotlib.dates as mdates
import logging
class Gantt(object):
'''
Simple Gantt renderer.
Uses *matplotlib* rendering capabilities.
'''
# Red Yellow Green diverging colormap
RdYlGr = ['#d73027', '#f46d43', '#fdae61',
'#fee08b', '#ffffbf', '#d9ef8b',
'#a6d96a', '#66bd63', '#1a9850']
POS_START = 1.0
POS_STEP = 0.5
def __init__(self, tasks):
self._fig = plt.figure()
self._ax = self._fig.add_axes([0.1, 0.1, .75, .5])
self.tasks = tasks[::-1]
def _format_date(self, date_string):
'''
Formats string representation of *date_string* into
*matplotlib. dates*
instance.
'''
try:
date = datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S')
except ValueError as err:
logging.error("String '{0}' can not be converted to
datetime object: {1}"
.format(date_string, err))
sys.exit(-1)
mpl_date = mdates.date2num(date)
return mpl_date
def _plot_bars(self):
'''
Processes each task and adds *barh* to the current *self._ax*(*axes*).
'''
i = 0
for task in self.tasks:
start = self._format_date(task['start'])
end = self._format_date(task['end'])
bottom = (i * Gantt.POS_STEP) + Gantt.POS_START
width = end - start
self._ax.barh(bottom, width, left=start, height=0.3,
align='center', label=task['label'],
color = Gantt.RdYlGr[i])
i += 1
def _configure_yaxis(self):
'''y axis'''
task_labels = [t['label'] for t in self.tasks]
pos = self._positions(len(task_labels))
ylocs = self._ax.set_yticks(pos)
ylabels = self._ax.set_yticklabels(task_labels)
plt.setp(ylabels, size='medium')
def _configure_xaxis(self):
'''x axis'''
# make x axis date axis
self._ax.xaxis_date()
# format date to ticks on every 7 days
rule = mdates.rrulewrapper(mdates.DAILY, interval=7)
loc = mdates.RRuleLocator(rule)
formatter = mdates.DateFormatter("%d %b")
self._ax.xaxis.set_major_locator(loc)
self._ax.xaxis.set_major_formatter(formatter)
xlabels = self._ax.get_xticklabels()
plt.setp(xlabels, rotation=30, fontsize=9)
def _configure_figure(self):
self._configure_xaxis()
self._configure_yaxis()
self._ax.grid(True, color='gray')
self._set_legend()
self._fig.autofmt_xdate()
def _set_legend(self):
'''
Tweak font to be small and place *legend*
in the upper right corner of the figure
'''
font = font_manager.FontProperties(size='small')
self._ax.legend(loc='upper right', prop=font)
def _positions(self, count):
'''
For given *count* number of positions, get array for the
positions.
'''
end = count * Gantt.POS_STEP + Gantt.POS_START
pos = np.arange(Gantt.POS_START, end, Gantt.POS_STEP)
return pos
下面的代码定义了生成甘特图的主函数。在这个函数中,我们把数据加载到一个实例中,绘制出相应的水平条、设置好时间坐标轴(x 轴)的日期格式,并设置 y 轴(项目任务)上的值。
def show(self):
self._plot_bars()
self._configure_figure()
plt.show()
if __name__ == '__main__':
TEST_DATA = (
{ 'label': 'Research', 'start':'2013-10-01
12:00:00', 'end': '2013-10-02 18:00:00'}, # @IgnorePep8
{ 'label': 'Compilation', 'start':'2013-10-02
09:00:00', 'end': '2013-10-02 12:00:00'}, # @IgnorePep8
{ 'label': 'Meeting #1', 'start':'2013-10-03
12:00:00', 'end': '2013-10-03 18:00:00'}, # @IgnorePep8
{ 'label': 'Design', 'start':'2013-10-04
09:00:00', 'end': '2013-10-10 13:00:00'}, # @IgnorePep8
{ 'label': 'Meeting #2', 'start':'2013-10-11
09:00:00', 'end': '2013-10-11 13:00:00'}, # @IgnorePep8
{ 'label': 'Implementation', 'start':'2013-10-12
09:00:00', 'end': '2013-10-22 13:00:00'}, # @IgnorePep8
{ 'label': 'Demo', 'start':'2013-10-23
09:00:00', 'end': '2013-10-23 13:00:00'}, # @IgnorePep8
)
gantt = Gantt(TEST_DATA)
gantt.show()
代码就生成前面那个简单美观的甘特图,如图8-5所示。
图8-5
代码解读
我们从上面代码底部的"__main__"中 if 语句检查之后开始读。在给定 TEST_DATA参数实例化 Gantt 类之后,我们为该实例创建一些必要的字段。把 TASK_DATA
保存在self.tasks字段中,并且创建坐标轴和图形窗口来保存接下来要创建的图表。
然后,在实例上调用show()方法,该方法执行所需的步骤创建出甘特图。
def show(self):
self._plot_bars()
self._configure_figure()
plt.show()
绘制水平条需要一个循环,在循环中把每一个任务的名称和持续时间数据应用到matplotlib.pyplot.barh 函数上,并把它添加到 self._ax 坐标轴中。通过给每一个任务一个不同(增量)的bottom参数值,我们可以把每个任务放在一个单独的通道上。
并且,为了能容易地把任务映射到它们的名字上,我们对其循环应用colorbrewer2. org工具生成的divergent颜色表。
下一步是配置图表,即设置 x 轴上的日期格式和 y 轴上的刻度位置和标签,来与用matplotlib.pyplot.barh函数绘制的任务进行匹配。
然后,对grid和legend做最后的调整。
最后,调用plt.show()把图表显示出来。
结语
本文从替代Project,用Python来生成甘特图开始,从准备数据到代码详解,都一一说来,希望对大家的项目工作有帮助,让Python提高工作效率,让生活更幸福。