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

Posted

技术标签:

【中文标题】如何使用 matplotlib 自定义甘特图并在图表上显示指示当前时间的垂直线?【英文标题】:How to customize the Gantt chart using matplotlib and display the vertical line indicating current time on a graph? 【发布时间】:2020-06-01 14:55:54 【问题描述】:

这是我的 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 轴上绘制一条与当前时间相对应的垂直直线。我试图将它添加到我的代码中以绘制它,但我不知道如何使它工作。我认为我的时间格式或类似问题可能存在问题:

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

我非常感谢您在这件事上的任何帮助。 这是所需输出的图片,我希望我的情节相似:

另外,我想在我的 y 轴“操作短文本”中添加/附加另一个类别以及“操作/活动”#,这样它不仅会显示操作编号,还会反映对旁边的操作。 要了解我的数据是什么样的,请参见下文(第一行是标题):

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

【问题讨论】:

【参考方案1】:

最简单的方法似乎是通过操作对数据框进行排序,然后使用数据框的索引作为 y 坐标绘制水平条。然后,反转 y 轴的限制(将其设置为从高到低),在顶部获得最低编号的操作。 (现在的代码假设每个条都在新的一行,而旧代码假设一个操作会有更多条)。

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

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

请注意,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()

【讨论】:

感谢您的回复,但我在 sort_values 函数中遇到错误,错误显示“sort_values() 得到了一个意外的关键字参数 'ignore_index'”,不确定它与什么有关。另外,我不想摆脱操作#,而是将“操作短文本”附加到它上面,这样它就会显示类似“250-Mount pipe inside”的内容。 要将这两个都包含在刻度标签中,请尝试ax.set_yticklabels(list(df['Operation short text'] + " " + df['Operation/Activity'].astype(str)))。 “意外关键字”来自运行旧版本的熊猫。当前版本是 1.0.1 ax.set_yticklabels(df['Operation short text'] + " " + df['Operation/Activity'].astype(str).str.zfill(4)) 将数字前加零。【参考方案2】:

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

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

我基本上将“现在”时间转换为某种格式,并使用我一开始定义的timestr_to_num再次转换。

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

【讨论】:

以上是关于如何使用 matplotlib 自定义甘特图并在图表上显示指示当前时间的垂直线?的主要内容,如果未能解决你的问题,请参考以下文章

基于 ECharts 封装甘特图并实现自动滚屏

python使用matplotlib绘制水平条形图并在条形图上添加实际数值标签实战

Python使用matplotlib可视化气泡图并使用encircle函数自定义多边形圈定可视化图像中的指定区域(Bubble plot with Encircling)

Python使用matplotlib可视化散点图并在可视化图像的底部和右边添加边缘箱图(Marginal Boxplot)

python使用matplotlib可视化时间序列图并在时间序列可视化图像中的不同区域添加动态阈值动态阈值上限动态阈值下限

如何使用 matplotlib 获取甘特图