Matplotlib习惯追踪:如何在X轴绘制隐藏式统计折线?
解决Matplotlib习惯追踪折线的误显示问题
我完全get到你的痛点——那条汇总每日完成习惯数的折线,在当天没有完成任何习惯时,不该停留在Y=0(也就是"Retainer"对应的位置),这很容易让人误以为你完成了这个习惯对吧?而且你不想改动Matplotlib的自动缓冲设置,只想让无完成数据的折线部分藏起来,别在X轴上方显示。
问题根源
原来的代码里,当当天没有完成任何习惯时,completed列表对应的数值是0,而你的Y轴刻度0正好对应"Retainer",所以折线会落在这个位置,造成误解。我们需要把这些无完成的点的Y值设置到Y轴可见范围之外,这样它们就会自动被隐藏,不会出现在图里。
解决方案
修改计算每日完成总数的逻辑:当当天完成数为0时,把Y值设为-1(这个值低于Y轴的最小可见刻度0),有完成数时保持原来的总数。这样Matplotlib的自动缓冲不会影响这个设置,因为这些点在视图范围外,不会被显示出来。
下面是修改后的完整代码:
import matplotlib.pyplot as plt import calendar import datetime import atexit def main(): global trackers activities = ["Retainer","Steps Goal","Drink Water","6+ Hours Sleep","<=2 Sodas","Study","Leave Campus","Clean","Mike","Paint","Phone Call","Saw Friends","UAS"] trackers = [] completed = [] days_in_month = calendar.monthrange(int(str(datetime.datetime.now())[0:4]),int(str(datetime.datetime.now())[5:7]))[1] current_day = int(str(datetime.datetime.now())[8:11]) # 读取trackers.txt数据 with open("trackers.txt","r") as file: data = file.readlines() for i in range(len(data)): data[i] = data[i].rstrip("\n").rstrip(",").rstrip("") trackers.append(data[i].split(",")) for b in range(len(trackers[-1])): trackers[-1][b] = int(trackers[-1][b]) print("Trackers Loaded") plt.switch_backend('TkAgg') # 修正:计算每日完成数,0完成时设为-1 for i in range(days_in_month): total_completed = 0 for b in range(len(trackers)): total_completed += int(trackers[b][i]) # 确保是整数相加 # 无完成时设为-1,否则保留总数 completed.append(total_completed if total_completed > 0 else -1) plt.plot(tuple([i for i in range(days_in_month)]), tuple(completed),c = "k") plt.axvline(x=current_day - 1, c="#ccf3ff", linewidth = 30, zorder = -1) plt.grid() plt.title("Habit Tracker") plt.yticks([i for i in range(len(trackers))],tuple(activities)) plt.xticks([i for i in range(days_in_month)],[i for i in range(1,days_in_month + 1)]) # 绘制每个习惯的完成标记 for i in range(len(trackers)): for b in range(days_in_month): if trackers[i][b] == 1: plt.plot(b,i,"rs", markersize = 8) else: plt.plot(b,i,"s", color = "#d2d2d2", markersize = 8) mng = plt.get_current_fig_manager() mng.window.state('zoomed') plt.show() return @atexit.register def exit(): with open("trackers.txt","w") as file: for i in range(len(trackers)): for b in range(len(trackers[i])): file.write(str(trackers[i][b]) + ",") file.write("\n") print("Trackers Saved") if __name__ == "__main__": main()
配套的trackers.txt内容(确保末尾有空行)
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,
额外优化说明
我还顺手做了两个小优化:
- 用
with语句处理文件读写,更安全也更符合Python最佳实践 - 在计算
total_completed时显式转换为整数,避免潜在的类型问题
这样修改后,没有完成任何习惯的日子,折线会跑到X轴下方(Y=-1),不在你的视图范围内,不会被显示出来;有完成数的日子,折线会正常显示在对应的完成数量高度,完全符合你的需求,而且不需要修改Matplotlib的自动缓冲设置。
内容的提问来源于stack exchange,提问作者Conrad Selig
相关产品推荐
相关产品推荐

