A Pelican plugin that generates a GitHub-style writing heatmap from your articles
Project description
pelican-heatmap
A Pelican plugin that generates a GitHub-style writing activity heatmap for your blog. At build time it scans all your articles and produces a JSON data file; a self-contained JS + CSS widget reads that file and renders an interactive calendar you can drop into any page.
Features
- GitHub-style heatmap — one cell per day, four color levels based on post frequency
- Year navigation — scroll back through your entire writing history with ‹ › buttons; the view always opens on the most recent year
- Live stats — posts in the current view window, all-time total, and current streak
- Clickable cells — click any day to pin a tooltip listing that day's articles with links; click again to dismiss
- Dark mode — respects
prefers-color-schemeautomatically - i18n — all UI strings are overridable via
window.HM_LOCALE - Customizable — every element has a named CSS class and the color palette is controlled by CSS custom properties
- Zero dependencies — no third-party libraries required
Installation
pip install pelican-heatmap
Or with uv:
uv add pelican-heatmap
Setup
1. Enable the plugin
# pelicanconf.py
PLUGINS = ["pelican.plugins.heatmap"]
2. Add the widget to a page
The plugin copies pelican_heatmap.css and pelican_heatmap.js to output/static/ automatically at build time. Add these three lines wherever you want the heatmap to appear — a dedicated page, your about page, or a sidebar:
<link rel="stylesheet" href="/static/pelican_heatmap.css">
<div id="writing-heatmap"></div>
<script src="/static/pelican_heatmap.js" defer></script>
In a Jinja2 template:
<link rel="stylesheet" href="{{ SITEURL }}/static/pelican_heatmap.css">
<div id="writing-heatmap"></div>
<script src="{{ SITEURL }}/static/pelican_heatmap.js" defer></script>
3. Build
pelican content
The plugin generates output/writing-heatmap.json and copies the static assets. That's it.
Localization
Override any UI string by setting window.HM_LOCALE before the script loads:
<script>
window.HM_LOCALE = {
months: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'],
days: ['日','一','二','三','四','五','六'],
less: '少',
more: '多',
no_posts: '無文章',
posts: '篇',
alltime: '累計\n篇數',
streak: '連續\n天數',
prev_year: '往前一年',
next_year: '往後一年',
};
</script>
<script src="/static/writing_heatmap.js" defer></script>
All keys are optional — omitted keys fall back to the English defaults.
Customization
CSS custom properties
Override these in :root or any parent selector:
| Property | Default (light) | Description |
|---|---|---|
--hm-cell-size |
14px |
Cell and row height; scales the whole grid |
--hm-cell-empty |
#e8e6e1 |
Empty cell color |
--hm-card-bg |
#f0ede8 |
Stat card background |
--hm-text-num |
#1a1a1a |
Large number color |
--hm-text-muted |
#888 |
Label and secondary text |
--hm-label-col |
#aaa |
Month and legend label color |
--hm-label-day |
#bbb |
Weekday label color |
--hm-btn-bg |
#ede9e3 |
Nav button background |
--hm-btn-hover |
#ddd9d2 |
Nav button hover background |
--hm-btn-text |
#555 |
Nav button text |
--hm-btn-dis |
#ccc |
Disabled button text |
Example — larger cells:
#writing-heatmap {
--hm-cell-size: 18px;
}
Color levels
.hm-cell[data-level="1"] { background: #a8c8f8; }
.hm-cell[data-level="2"] { background: #5a9fd4; }
.hm-cell[data-level="3"] { background: #2376b7; }
.hm-cell[data-level="4"] { background: #0d4f8a; }
Full class reference
#writing-heatmap outer wrapper
.hm-stats stats row
.hm-stat-card individual stat card
.hm-stat-num large number in card
.hm-stat-label label below number
.hm-nav nav row
.hm-nav-btn prev / next buttons
.hm-nav-range date range text
.hm-container scrollable grid wrapper
.hm-month-label-cell per-column month span
.hm-month-label-cell--visible span showing a month name
.hm-day-labels Su Mo … Sa column
.hm-grid the cell grid
.hm-cell individual day cell
.hm-cell[data-level="0–4"] color levels
.hm-cell--future days after today
.hm-cell--past days before first article
.hm-legend legend row
.hm-legend-cell legend color swatch
.hm-tooltip tooltip container
.hm-tooltip.hm-visible tooltip when shown (hover)
.hm-tooltip.hm-pinned tooltip when clicked / pinned
.hm-tt-date date line inside tooltip
.hm-tt-list article list inside tooltip
.hm-tt-empty "No posts" message
JSON format
The plugin writes output/writing-heatmap.json with the following shape:
{
"data": {
"2025-03-12": {
"count": 2,
"articles": [
{ "title": "Article title", "url": "/posts/article-slug.html" }
]
}
},
"total": 260,
"streak": 5,
"most_active_day": "2025-07-04"
}
You can consume this file independently of the widget if you want to build your own visualization.
Requirements
- Python 3.10+
- Pelican 4.5+
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pelican_heatmap-0.2.2.tar.gz.
File metadata
- Download URL: pelican_heatmap-0.2.2.tar.gz
- Upload date:
- Size: 10.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5f4756afe7ea735df2852735afbc5490584b99764ef6823566c488062fea6e7
|
|
| MD5 |
326ba0e6407312a88c7b421039293d77
|
|
| BLAKE2b-256 |
45586e4fbb9262d36a1aa99e855b976d97c8581db1ff0533040a9eb6b7ee0dc5
|
Provenance
The following attestation bundles were made for pelican_heatmap-0.2.2.tar.gz:
Publisher:
publish-to-pypi.yaml on Lee-W/pelican-heatmap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pelican_heatmap-0.2.2.tar.gz -
Subject digest:
d5f4756afe7ea735df2852735afbc5490584b99764ef6823566c488062fea6e7 - Sigstore transparency entry: 1102637029
- Sigstore integration time:
-
Permalink:
Lee-W/pelican-heatmap@48178fc298e3096727919bfc58d191ecb7e88853 -
Branch / Tag:
refs/tags/0.2.2 - Owner: https://github.com/Lee-W
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yaml@48178fc298e3096727919bfc58d191ecb7e88853 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pelican_heatmap-0.2.2-py3-none-any.whl.
File metadata
- Download URL: pelican_heatmap-0.2.2-py3-none-any.whl
- Upload date:
- Size: 11.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ce3e1b04969703d36cc71b832788c646d2e83171f0f5400a386f01f645e72fe
|
|
| MD5 |
ab50cce133fa2ee2c8ba7311cac1e5f4
|
|
| BLAKE2b-256 |
c822070123a8915b608553c3f85ad6396c8b9632aace5f4210152f1077a2328f
|
Provenance
The following attestation bundles were made for pelican_heatmap-0.2.2-py3-none-any.whl:
Publisher:
publish-to-pypi.yaml on Lee-W/pelican-heatmap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pelican_heatmap-0.2.2-py3-none-any.whl -
Subject digest:
7ce3e1b04969703d36cc71b832788c646d2e83171f0f5400a386f01f645e72fe - Sigstore transparency entry: 1102637030
- Sigstore integration time:
-
Permalink:
Lee-W/pelican-heatmap@48178fc298e3096727919bfc58d191ecb7e88853 -
Branch / Tag:
refs/tags/0.2.2 - Owner: https://github.com/Lee-W
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yaml@48178fc298e3096727919bfc58d191ecb7e88853 -
Trigger Event:
push
-
Statement type: