如何在一个日期创建具有多个起点和终点的条形图?

How would I create a bar plot with multiple start and end points on a single date?

我一直在努力帮助我的妻子,她正在跟踪她一天中每个客户的时间,通过可视化她每天花在工作上的时间,这些时间被客户着色。

这是我在Python中尝试实现的示例(在Excel中构建以供参考,代码包含示例数据)。

data = [["Client", "Task", "Start Time", "End Time"],
    ["client-A", "task-a", "2020-06-10 11:10", "2020-06-10 11:25"],
    ["client-B", "task-b", "2020-06-10 11:30", "2020-06-10 13:54"],
    ["client-B", "task-a", "2020-06-10 17:34", "2020-06-10 18:00"],
    ["client-D", "task-e", "2020-06-11 08:05", "2020-06-11 12:45"],
    ["client-C", "task-d", "2020-06-11 15:15", "2020-06-11 17:01"],
    ["client-A", "task-a", "2020-06-11 19:10", "2020-06-11 20:18"],
    ["client-A", "task-c", "2020-06-11 20:18", "2020-06-11 21:36"],
    ["client-C", "task-a", "2020-06-12 08:02", "2020-06-12 08:25"],
    ["client-D", "task-e", "2020-06-12 08:45", "2020-06-12 09:55"],
    ["client-E", "task-d", "2020-06-12 10:00", "2020-06-12 11:07"],
    ["client-B", "task-c", "2020-06-12 11:11", "2020-06-12 12:30"]]

df = pd.DataFrame(data[1:], columns=data[0])

感谢您的所有帮助,我希望有比手动制作 Excel 图表更简单的解决方案。

我以前没有尝试过绘制这样的东西,所以代码肯定会更好。话虽如此,这就是我能够实现的目标:

首先,您需要导入一些包:

import numpy as np
import datetime as dt

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.collections import PolyCollection
import matplotlib.patches as mpatches

我冒昧地从您的数据数组中删除了 header 并将其转换为 numpy 数组:

data = np.array(data)[1:, :]

之后,我们需要在排序列表中获取所有唯一的日期,并使用以下内容制作字典:

days_list = sorted(list(set([date[:10] for date in data[:, 2]])))[::-1]
days = { day: i+1 for i, day in enumerate(days_list) }

然后根据客户端进行颜色映射:

clients = sorted(list(set(data[:, 0])))
colormapping = { client: f"C{i}" for i, client in enumerate(clients) }

作为最后的设置,我们需要保存每个条目的开始和结束时间:

start_times = [dt.datetime.strptime(date[11:], "%H:%M") for date in data[:, 2]]
end_times = [dt.datetime.strptime(date[11:], "%H:%M") for date in data[:, 3]]

现在我们可以遍历所有数据点并为其添加顶点、颜色和文本位置:

verts, colors, texts = [], [], []
for i, d in enumerate(data):
    client, task, date_str = d[0], d[1], d[2]
    day_num = days[date_str[:10]]
    start_date = mdates.date2num(start_times[i])
    end_date = mdates.date2num(end_times[i])

    v = [(start_date, day_num - .4),
         (start_date, day_num + .4),
         (end_date, day_num + .4),
         (end_date, day_num - .4),
         (start_date, day_num - .4)
    ]
    verts.append(v)
    colors.append(colormapping[client])
    texts.append((start_date, day_num, task[-1].upper()))

有了这个之后,就是基本的 Matplotlib 东西了:

# Make PolyCollection and scale
bars = PolyCollection(verts, facecolors=colors, edgecolors=("black",))
fig, ax = plt.subplots()
ax.add_collection(bars)
ax.autoscale()

# Set ticks to show every 30 minutes and in specific format
xticks = mdates.MinuteLocator(byminute=[0, 30])
ax.xaxis.set_major_locator(xticks)
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
fig.autofmt_xdate()

# Set y-axis to be dates
ax.set_yticks(range(1, len(days_list) + 1))
ax.set_yticklabels(days_list)

# Add task text to plot
for (start_date, day_num, task) in texts:
    plt.text(start_date+.003, day_num-.03, task, color="w")

# Create legend based on clients
plt.legend(handles=[mpatches.Patch(color=color, label=client)
                    for client, color in colormapping.items()])

# Add grid and show
plt.grid()
plt.show()

您可以在 this Github gist 中查看完整代码。