項目管理的神器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提高工作效率,讓生活更幸福。