如何使用matplotlib自定义甘特图并在图表上显示表示当前时间的竖线?

How to customize the Gantt chart using matplotlib and display the vertical line indicating current time on a graph?

这是我的 Python 代码,主要绘制甘特图:

import pandas as pd
import random
from datetime import datetime
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
%matplotlib inline
import math
plt.style.use('ggplot')

df = pd.read_csv('zpp00141_new.csv')

def timestr_to_num(timestr):
    return mdates.date2num(datetime.strptime('0' + timestr if timestr[1] == ':' else timestr, '%I:%M:%S %p'))

df.rename(columns={"Earl. start / time": "start", "Latest finish / time": "finish"}, inplace = True)

df['Operation/Activity'] = df['Operation/Activity'].astype(str)

fig, ax = plt.subplots(figsize=(10, 5))
operations = pd.unique(df['Operation/Activity'])
#df.assign(start=df['Earl. start / time'])
colors = plt.cm.tab10.colors  # get a list of 10 colors
colors *= math.ceil(len(operations) / (len(colors)))  # repeat the list as many times as needed
for operation, color in zip(operations, colors):
    for row in df[df['Operation/Activity'] == operation].itertuples():
        left = timestr_to_num(row.start)
        right = timestr_to_num(row.finish)
        ax.barh(operation, left=left, width=right - left, height=3, color=color)
ax.set_xlim(timestr_to_num('07:00:00 AM'), timestr_to_num('4:30:00 PM'))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))  # display ticks as hours and minutes
ax.xaxis.set_major_locator(mdates.HourLocator(interval=1))  # set a tick every hour
ax.set_xlabel('Time')
ax.set_ylabel('Operation')
plt.tight_layout()
plt.show()

您可以在附图上看到输出:

我想绘制一条与 x-axis 上的当前时间相对应的垂直直线。我试图将它添加到我的代码中以绘制它,但我不知道如何让它工作。我认为我的时间格式或类似问题可能存在问题:

plt.axvline(pd.Timestamp.now(),color='r')

如果能在这件事上提供任何帮助,我将不胜感激。 这是所需输出的图片,我希望我的情节相似:

此外,我想 add/append 我的 y-axis “操作短文本”的另一个类别以及“Operation/Activity”# 这样它不仅会显示操作编号, 但也会反映旁边的操作描述。 要了解我的数据是什么样子,请参见下文(第一行是 header):

Operation short text,Operation/Activity,Earl. start / time,Latest finish / time
Mount right racks,0250,7:00:00 AM,9:22:00 AM
Mount right side motion unit carriage,0251,9:22:00 AM,10:30:00 AM
Mount left side motion unit carriage,0252,10:30:00 AM,11:17:00 AM
Install motion unit complete,0253,11:17:00 AM,1:01:00 PM
Move machine to next step + EPA,0254,1:01:00 PM,3:30:00 PM
Mount Left Racks,0200,7:00:00 AM,9:12:00 AM
Mount cable motor & Lubricate guide carr,0201,9:12:00 AM,9:44:00 AM
Mount suction components,0202,9:44:00 AM,11:04:00 AM
Mount extraction,0203,11:04:00 AM,12:34:00 PM
Mount temporary diamond plates,0204,12:34:00 PM,1:04:00 PM
Mount piping inside,0205,1:04:00 PM,1:44:00 PM
Move Machine to next step + EPA,0206,1:44:00 PM,3:30:00 PM

我能够使用此代码绘制垂直线:

now = datetime.now()
now = now.strftime('%I:%M:%S %p')
plt.axvline(x=timestr_to_num(now),color='r')

我基本上是把"now"时间转换成一定的格式,然后用我一开始定义的timestr_to_num再转换一次

但是,我仍然需要一些帮助来为我的 y 轴值添加辅助值(操作短文本)

最简单的似乎是按操作对数据帧进行排序,然后使用数据帧的索引作为 y-coordinate 绘制水平条。然后,反转 y-axis 的限制(将其从高设置为低),将编号最低的操作置于顶部。 (代码现在假设每个柱都在一个新行上,而旧代码假设一次操作会有更多柱)。

由于操作现在似乎属于一起,因此选择了具有连续颜色的颜色图,并且每次操作比前一个更早开始时都会重新开始颜色。随意使用适合您目标的任何方案。

由于 datetime.strptime 只看时间,它得到一个默认日期(1900 年 1 月 1 日)。因此,您在 'now' 时间内使用相同转换的方法非常合适。

请注意,pd.read_csv 的类型嗅探器为操作列提供了浮点格式。你可以阻止它给它明确的转换信息。例如。 pd.read_csv(..., converters={1: str}) 将第二列作为字符串。

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime
import pandas as pd
import math
# % matplotlib inline

def timestr_to_num(timestr):
    return mdates.date2num(datetime.strptime('0' + timestr if timestr[1] == ':' else timestr, '%I:%M:%S %p'))

plt.style.use('ggplot')
# df = pd.read_csv('zpp00141_new.csv')
columns = ['Operation short text', 'Operation/Activity', 'Earl. start / time', 'Latest finish / time']
rows = [['Mount right racks', '0250', '7:00:00 AM', '9:22:00 AM'],
        ['Mount right side motion unit carriage', '0251', '9:22:00 AM', '10:30:00 AM'],
        ['Mount left side motion unit carriage', '0252', '10:30:00 AM', '11:17:00 AM'],
        ['Install motion unit complete', '0253', '11:17:00 AM', '1:01:00 PM'],
        ['Move machine to next step + EPA', '0254', '1:01:00 PM', '3:30:00 PM'],
        ['Mount Left Racks', '0200', '7:00:00 AM', '9:12:00 AM'],
        ['Mount cable motor & Lubricate guide carr', '0201', '9:12:00 AM', '9:44:00 AM'],
        ['Mount suction components', '0202', '9:44:00 AM', '11:04:00 AM'],
        ['Mount extraction', '0203', '11:04:00 AM', '12:34:00 PM'],
        ['Mount temporary diamond plates', '0204', '12:34:00 PM', '1:04:00 PM'],
        ['Mount piping inside', '0205', '1:04:00 PM', '1:44:00 PM'],
        ['Move Machine to next step + EPA', '0206', '1:44:00 PM', '3:30:00 PM']]
df = pd.DataFrame(data=rows, columns=columns)
df.rename(columns={"Earl. start / time": "start", "Latest finish / time": "finish"}, inplace=True)

df['Operation/Activity'] = df['Operation/Activity'].astype(int)
df.sort_values('Operation/Activity', ascending=True, inplace=True, ignore_index=True)

fig, ax = plt.subplots(figsize=(10, 5))

#colors = plt.cm.tab10.colors  # get a list of 10 colors
cmap = plt.cm.get_cmap('plasma_r')
colors = [cmap(i/9) for i in range(10)]   # get a list of 10 colors

previous_start = math.inf  # 'previous_start' helps to indicate we're starting again from the left
color_start = 0
for row in df.itertuples():
    left = timestr_to_num(row.start)
    right = timestr_to_num(row.finish)
    if left <= previous_start:
        color_start = row.Index
    ax.barh(row.Index, left=left, width=right - left, height=1, color=colors[(row.Index - color_start) % len(colors)])
    previous_start = left
ax.set_xlim(timestr_to_num('7:00:00 AM'), timestr_to_num('4:30:00 PM'))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))  # display ticks as hours and minutes
ax.xaxis.set_major_locator(mdates.HourLocator(interval=1))  # set a tick every hour
ax.set_xlabel('Time')
ax.set_ylabel('Operation')
ax.set_ylim(len(df), -1)  # set the limits and reverse the order
ax.set_yticks(range(len(df)))
# ax.set_yticklabels(list(df['Operation/Activity']))
ax.set_yticklabels(list(df['Operation short text']))

now = datetime.now().strftime('%I:%M:%S %p')
ax.axvline(x=timestr_to_num(now),color='r')

plt.tight_layout()
plt.show()