news 2026/8/31 16:50:48

用GitHub热力图打造阅读打卡系统:习惯可视化实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用GitHub热力图打造阅读打卡系统:习惯可视化实践指南

GitHub Heatmap for Reading,核心想法一句话就能说清楚:把你每天阅读的时长、页数或完成情况,按照 GitHub 主页那套“绿点矩阵”展示出来。它解决的实际问题不是“我怎么记录读书”,而是“我怎么让阅读的连续性变得一眼可见”。很多人以为这类工具的价值是好看,真正跑起来之后你会发现,它最有用的部分是让你无法回避空白格子:哪一天没读,哪一周在偷懒,全都摊在图上。

这篇文章适合正在做个人数据可视化项目的开发者,也适合想给自己建一个阅读打卡看板的普通人。前者可以看完整实现思路,后者可以直接按落地顺序搭一套。先说我的总体判断:这种项目对机器配置基本没有要求,对数据规范和定时任务的要求反而更高。你最容易踩的坑不在绘图代码,而在“数据源不稳定”和“时间口径不统一”。

1. 先想清楚:它复刻的不是图形,是“习惯可视化”这套逻辑

1.1 GitHub 绿点矩阵到底是什么

GitHub 主页那个热力图,本质上是一张 7 行、约 53 列的网格。列是一周,行是周一到周日,每一天对应一个格子。格子颜色深浅由当天行为数量决定:没有行为就是浅灰色,行为越多颜色越深。

这套图形真正厉害的地方不是配色,而是它能同时表达三件事:

  • 单日强度:这一天做了多少。
  • 周期性节奏:周末有没有集中爆发,工作日是不是断断续续。
  • 连续记录:连续多少天没有空窗。

放在阅读场景里,这套逻辑非常合适。很多阅读 App 的年度报告只告诉你“今年读了多少本、多少小时”,那是总结,不是过程。热力图把过程也画出来了:你是月初猛读三天然后歇两周,还是每天稳定读半小时,一眼就能分辨。

1.2 它和阅读 App 年度报告的区别

微信读书、豆瓣、Kindle 都有自己的统计页面,但它们的问题都一样:数据被锁在各自平台里,且统计口径是平台定的。

阅读热力图类项目的价值,是让你自己决定口径,并且把多个来源的数据合并到同一张图里。你可以把微信读书的时长、Kindle 的页数、纸质书的手动记录全部合并成一份数据集。这个自由度是任何单一 App 都给不了的。

另外一个区别是所有权。自己做的热力图,数据是本地 JSON 或私有仓库里的文件,不会因为平台改版、功能下架而消失。你维护的是一个长期可用的个人记录系统,不是一个临时报表。

1.3 先定统计口径:时长、页数还是完成状态

这是整个项目里最关键、也最容易被跳过的一步。动手写代码之前,你必须先回答一个问题:什么才算“某一天读了书”?

常见口径有三种:

  • 阅读时长:按分钟统计,适合微信读书这类自带计时器的数据源。
  • 阅读页数:按页统计,适合 Kindle、纸质书手动输入。
  • 完成状态:读完一本书算一次,适合只关注“读完”而不是“读了多久”的人。

我建议你用一个统一的主口径,其他口径作为附加字段。不要今天按时长,明天按页数,后天又改成“读满 30 分钟才算”。口径一换,颜色阈值要重调,历史数据也要重新映射,非常麻烦。

还有一个小问题要提前想好:读 5 分钟也算一天吗?如果算,你的热力图会非常满,但参考价值很低。如果不算,阈值设在哪里,需要在后面画图前一起定掉。

2. 数据源是第一步,热力图只是最后一步

2.1 常见的数据来源

热力图能不能长期跑下去,完全取决于数据源能不能稳定提供数据。常见的阅读来源大概有下面几类:

数据源数据形态获取难度稳定性
微信读书阅读时长、书籍信息中,需要登录态接口可能随时间变化
豆瓣读书想读、在读、读过低,有公开页面较稳定
Kindle标注、笔记、读完状态中,需解析本地文件很稳定
纸质书手动记录自建 JSON/CSV完全可控
自己开发的阅读进度表任意字段完全可控

我的建议是:优先选一个确定性最高的来源作为基底。对大多数人来说,手动维护一个 JSON 文件虽然笨,但最稳定。等这个链路跑通了,再考虑接入微信读书或豆瓣。

2.2 统一数据模型

不管数据从哪来,最终都要转成一套统一的结构。推荐用按日期聚合的记录,而不是一条条原始流水。比如:

{ "date": "2025-01-01", "minutes": 45, "pages": 30, "finished": 0 }
  • date是本地日期,格式固定为YYYY-MM-DD
  • minutespages是当天累计值。
  • finished表示当天是否读完一本书,1 或 0。

把原始流水聚合成这种结构之后,画图就非常简单:拿着日期查值,填格子就行。后面加新的数据源,也只是在生成聚合文件时多一个合并步骤。

2.3 清洗要点:时区、重复记录、跨天

真实数据不会像示例这么干净,常见三个坑要先处理。

第一个是时区。你记录阅读动作可能发生在晚上十一点半,如果按 UTC 存时间,日期会跳到第二天。个人项目我建议统一用本地日期字符串,不要存时间戳,省去大量换算。

第二个是重复记录。微信读书导出、豆瓣数据抓取都可能产生重复条目。合并时一定要按“日期 + 来源”做去重,否则某一天的值会翻倍,颜色等级直接失真。

第三个是跨天阅读。晚上 23:50 读到第二天 00:20,这 30 分钟算哪一天?不同人处理方式不同。我习惯按开始日期归属,也就是不论读多久,都算到开始阅读的那一天。这样处理简单,和直觉一致。

3. 热力图核心实现:把数据变成绿点矩阵

3.1 年份网格的基本算法

画热力图之前,先把数据结构想清楚。我们需要一个按日期查值的映射,然后生成一个包含 53 周、每周 7 天的网格。

下面这段 Python 示例演示了核心思路:

import datetime import json def build_calendar(year, records): # 先把记录转成 date -> value 的映射 day_map = {} for r in records: day_map[r["date"]] = r.get("minutes", 0) start = datetime.date(year, 1, 1) # 对齐到往前最近的周日,保持网格完整 while start.weekday() != 6: start -= datetime.timedelta(days=1) end = datetime.date(year, 12, 31) weeks = [] d = start while d <= end: week = [] for _ in range(7): iso = d.isoformat() week.append(day_map.get(iso, 0)) d += datetime.timedelta(days=1) weeks.append(week) return weeks

这段代码的关键在于对齐起始日。如果不把 1 月 1 日对齐到周日,第一周只有几天,整个网格就会错位。GitHub 自己的图也有这个特点:年初和年末会带前后几天的灰色格子,用来保证整体布局完整。

3.2 颜色分级和阈值设置

拿到每个格子的值之后,不能直接画颜色,还要做分级。GitHub 常用的意思是 5 档:0、低、中、高、极高,对应从浅到深的绿色。

很多初学者直接套 GitHub 的固定阈值,比如“1 到 3 次算低,4 到 6 次算中”。但阅读数据分布和 GitHub 提交数据不一样。有人一天读 10 分钟,有人一天读 2 小时,直接套固定阈值会让热力图大部分格子都一个颜色。

更好的做法是按照真实数据分布来分位。我的建议是:

等级判断条件对应感受
值为 0完全没读
大于 0,且低于中位数读了一点
中位数到 75 分位正常阅读
75 分位到 90 分位很投入
极高超过 90 分位爆发式阅读

示例中的“中位数、75 分位”都可以从你已经聚合好的数据里计算出来。这样不管你是轻度阅读还是重度阅读,热力图都能自然拉开层次。

3.3 连续天数怎么算

除了颜色,很多阅读热力图还会在顶部显示一个数字:连续阅读多少天。这个功能本质上是 streak 计算。

算法不复杂:把记录按日期排序,从早到晚遍历,如果当天值大于 0,连续天数加一,否则清零。

但这里有一个值得提前决策的点:是否把“今天”算作连续。如果今天还没读书,但昨天已经连续 20 天,你希望图表显示 20 还是 21?我建议显示“截至昨天”的连续天数,并且明确标注统计截止日期,避免数据失真。

还有一个容易被忽略的问题:阈值是什么。如果只读过 1 页也算连续,那这个连续数字会非常漂亮但没有意义。我一般会把“有效阅读日”的阈值设在 10 分钟或 5 页以上,和前面统计口径保持一致。

3.4 渲染方式:生成图片还是写网页

数据模型和算法有了,剩下就是怎么展示。常见三种方案:

方案优点缺点
生成 SVG/PNG 图片提交到仓库,嵌入 README展示简单,README 直接可见每次更新都要重新生成并提交
静态 HTML 页面,用 JavaScript 前端画图交互强,可以加悬浮提示需要部署到 GitHub Pages 或自己的网站
后端接口动态返回图片可实时更新个人项目里过度设计,维护成本高

如果是新手,我的建议是先走第一个方案:脚本生成图片,提交到仓库,README 里引用图片。这样做链路最短,出问题也容易排查。等确定要长期维护,再考虑升级成 GitHub Pages 页面。

4. 完整落地顺序:先本地,再自动化

4.1 先用假数据跑通

不要一上来就接真实数据。第一次测试应该用一份假 JSON,比如两周以内的模拟记录,把生成脚本跑通。

要验证的点有三个:

  • 脚本能正常启动,输出网格数据。
  • 颜色分级结果符合预期。
  • 最终生成的图片或 HTML 能在浏览器里正常打开。

这一阶段不要调参,不要纠结颜色好不好看,哪怕只有两种颜色也算通过。重点是确认整条链路没有断点。

4.2 再接入真实数据

假数据跑通后,把真实阅读记录填进去。第一次接入时建议只接一个来源,并且从最近一周开始,不要一上来就导几年的历史数据。

历史数据有一个隐蔽问题:年份跨度大,早期数据质量可能很低。比如 Kindle 里多年前的标注,可能没有准确日期,或者日期格式混乱。如果这些脏数据直接进入聚合脚本,会导致某几天出现异常峰值,颜色分级整体被拉偏。

所以先接最近数据,确认热力图正常之后,再逐步把历史数据按周补进去。每补一批,就检查一次颜色分布是否合理。

4.3 用 GitHub Actions 定时更新

如果每天都手动跑一次脚本,坚持不了几天。阅读热力图要长期有效,最好加一个定时任务。最常见的方式是 GitHub Actions。

下面是一个通用 workflow 示例:

name: update-reading-heatmap on: schedule: - cron: '0 22 * * *' workflow_dispatch: permissions: contents: write jobs: update: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - name: Run generate script run: python generate_heatmap.py - name: Commit and push run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add . git diff --quiet && git diff --cached --quiet || git commit -m "update reading heatmap" git push

这个示例里有两个关键点。

第一是cron时区。GitHub Actions 的 cron 默认按 UTC 执行,如果你希望每天凌晨按北京时间更新,要把时间往前推 8 小时,比如用0 22 * * *。如果不处理时区,你会发现热力图经常“晚一天”更新。

第二是写回权限。要让 Actions 能自动提交,workflow 必须声明permissions: contents: write,否则git push会失败。如果你用私有仓库,还要额外确认 Actions 的权限设置允许写入。

4.4 展示方式怎么选

自动化更新跑通之后,再考虑展示位置。

最省事的方式是把生成的图片放进仓库,然后在 README 顶部引用:

![reading heatmap](./heatmap.png)

这样别人打开你的项目主页,第一眼就能看到阅读热力图。图片路径推荐用相对路径,不要用/path这种绝对路径,否则在不同分支和 Fork 场景下容易失效。

如果你想做自己的个人主页,也可以把生成结果发布到 GitHub Pages。这样你可以加更多交互:鼠标悬停显示当天读了什么、点开某个月看详细记录。但这些都是可选项,不应该是第一版的目标。

5. 关键参数和判断标准

5.1 颜色层级的判断标准

同一个数据集,不同阈值画出来的热力图完全不同。阈值设高了,大部分格子都是浅色;阈值设低了,颜色全挤在最深一档。

我用分位数的原因是它相对稳定。但分位数也有一个边界情况:如果你的阅读数据里 0 值太多,中位数可能等于 0。这种情况下,应该先把值为 0 的格子单独归为“空档”,只在非 0 数据上计算分位数,否则颜色区分度依然拉不开。

5.2 日期范围:自然年还是滚动 365 天

这个参数决定网格的跨度。

  • 自然年:从 1 月 1 日到 12 月 31 日,适合看年度目标。
  • 滚动 365 天:始终展示最近一年,适合持续追踪习惯,不切换年份。

GitHub 默认展示滚动一年,但它有一个“按年”的跳转功能。个人项目我建议第一版做自然年,因为数据量少、逻辑简单,连“跨年时是否重置颜色”这种坑都不存在。等稳定运行一年后,再考虑改成滚动 365 天。

5.3 空值和缺失值的处理

流程上,空值和缺失值往往被混在一起,但语义完全不同。

  • 缺失:某一天没有任何记录,表示“这条数据没有进入系统”。
  • 零值:某一天有记录,但统计值为 0,表示“今天确实没有有效阅读”。

在热力图上,两者都显示为空白格子。但在统计连续天数和平均阅读时长时,必须分开处理。我的做法是只有“有记录且值大于等于 0”的日期才参与统计,缺失日期直接跳过。

5.4 更新频率和失败率怎么判断

定时任务不是配好就完事。你至少要监控两个指标:

  • 单次更新是否成功。
  • 一周内更新成功率是不是稳定在 95% 以上。

如果 GitHub Actions 每天跑一次,偶尔一次失败不用紧张,下一次成功后会补上。但如果连续三次失败,就要去看工作流日志。常见原因是脚本依赖了某个外部数据源,数据源接口变了,或者 Python 包版本不兼容导致脚本崩溃。

另外要注意 Actions 有月度额度限制。阅读热力图每天更新一次,加生成图片和提交,消耗很小,完全够用。但不要为了“看起来实时”设置成每十分钟跑一次,额度会很快被吃掉,而且没有实际意义。

6. 常见问题和排查顺序

6.1 热力图全是浅色,先查阈值

现象:跑完脚本,图出来了,但大部分格子都一个颜色。

不要先怀疑绘图代码。先打印出值分布,看看非零值是不是集中在很小的范围。如果你每天阅读时长在 20 到 40 分钟之间,那么“30 分钟”这个中位数和“90 分钟”的极高值之间差距很大,简单套固定阈值就会让颜色全部集中在低档。

排查顺序:先打印minmaxmedian,再检查分级函数传入的阈值,最后再看渲染结果。大多数情况下是阈值没按数据分布调整。

6.2 日期整体偏移,先查时区

现象:图里某一天的格子内容不对,比如 1 月 1 日的数据显示在 1 月 2 日。

这是典型的时区问题。数据源、聚合脚本、定时任务如果各用一套时区,日期就会错位。

排查顺序:先确认数据源记录时间时用的时区,再看脚本聚合时用的时区,最后看 GitHub Actions 里 cron 的时区。建议统一用本地日期字符串,彻底放弃时长戳。

6.3 Actions 没跑起来,先看日志和权限

现象:定时任务没有更新图片,或者仓库里没有发现新提交。

先打开仓库的 Actions 页面,看最近一次运行是成功还是失败。成功但没有 new commit,可能是因为脚本生成的内容没变化,git diff判断为空所以跳过提交,这是预期行为。

如果失败,看具体报错。最常见的三类:

  • 权限不足:workflow 缺少contents: write
  • 脚本依赖没有安装:workflow 里没有执行pip install
  • 数据源访问失败:外部接口返回非 200,或 Cookie 过期。

排查顺序永远是先看日志,再改配置,不要上来就重跑。

6.4 图片不更新,先看缓存和路径

现象:Actions 明明成功了,但 README 里的图片还是旧版本。

有两个可能。一个是浏览器缓存,图片 URL 没变,浏览器会继续使用本地缓存。另一个是仓库里图片路径被脚本覆盖到了错误分支,或生成文件名和 README 引用不一致。

排查顺序:先在仓库里直接打开图片文件,确认文件本身是否更新;如果文件更新了,再考虑加缓存刷新参数。这类问题和技术复杂度无关,纯粹是路径或缓存细节,认真看一遍即可。

最后说几句

这类项目真正值钱的地方不是热力图本身,而是它逼着你建立一个稳定的个人数据流:每天产生阅读记录、定期聚合、定时生成、长期沉淀。工具代码其实很少,核心逻辑几百行就能写完,难点全在“数据能不能稳定进来”和“口径能不能保持一致”。

我更建议你把第一次测试拆成三步:先用假数据跑通脚本,再接入一周真实数据,最后才配置 Actions 定时更新。每一步都验证通过后再进入下一步,不要妄想一次搭完。

如果你只是记录自己读了多少书,手动维护一个 JSON 文件完全够用。如果你发现连续阅读天数、月对比、年份切换这些需求变得强烈,再往 GitHub Pages 页面升级也不迟。这个项目最需要的不是复杂架构,而是“今天也记录一下”的执行力。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 16:50:37

Simulink中QPSK+AWGN仿真链路搭建与误码率分析

简介&#xff1a;本资源是一套面向通信工程专业本科生及MATLAB/Simulink初学者的QPSK数字调制系统仿真实践材料&#xff0c;聚焦加性高斯白噪声&#xff08;AWGN&#xff09;信道建模与误码率性能分析。资源完整呈现QPSK调制、AWGN信道注入、相干解调及BER统计的端到端Simulink…

作者头像 李华
网站建设 2026/8/31 16:50:07

修改了一个驱动级别自动化错误

以前选择浏览器都是用 f6 然后平时虽然看到一些奇怪的事情不知道原因&#xff0c;但是代码运行较好。现在因为vpn速度非常慢&#xff0c;导致一些错误暴露了出来。原来f6作用是切换焦点&#xff0c;选择地址栏的快捷键是:ctrl d 修复了这个错误。这样一些奇怪的错误就都消失了…

作者头像 李华
网站建设 2026/8/31 16:48:29

光伏无人机检测数据集的工业级验证与物理建模

简介&#xff1a;本资源是面向计算机视觉工程师与遥感AI研究者的YOLO格式目标检测数据集&#xff0c;专为无人机高空视角下的太阳能电池板识别任务设计&#xff0c;解决可再生能源设施自动化巡检、城市能源规划建模及灾后损毁评估等实际问题。压缩包共2000个文件&#xff0c;含…

作者头像 李华
网站建设 2026/8/31 16:48:10

企业级Agent记忆系统拆解:从上下文到Long-term Me的工程实践

如果让 Agent 连续处理 50 轮对话之后&#xff0c;还能准确记得用户第一次提出的核心需求&#xff0c;这件事靠“拼命拼上下文”是做不到的。今天我们把企业级 Agent 的记忆系统整个拆开讲&#xff1a;从短期 Context&#xff0c;到长期记忆 Long-term Me&#xff0c;再分别看 …

作者头像 李华
网站建设 2026/8/31 16:45:56

三级Doherty功放设计:基于理想电流源的负载调制与回退效率仿真方法

简介&#xff1a;本资源是面向射频工程师与微波电路设计学习者的ADS仿真工程包&#xff0c;聚焦高回退效率优化的Multistage Doherty功率放大器架构研究&#xff0c;特别采用理想电流源建模方式简化分析&#xff0c;适用于射频前端线性化技术原理验证与结构对比教学。压缩包共9…

作者头像 李华