A newer version of the Gradio SDK is available:
6.8.0
title: Github Contribution Heatmap
emoji: π»
colorFrom: green
colorTo: pink
sdk: gradio
sdk_version: 6.5.1
app_file: contribution_heatmap.py
pinned: false
license: mit
short_description: 'GitHub-style heatmap with gradio''s new html component '
π© ContributionHeatmap
A GitHub-style contribution heatmap component for Gradio 6, built entirely with gr.HTML. No Svelte, no CLI tooling, no npm β just Python, HTML templates, CSS, and a sprinkle of JS.
Features
- GitHub-style grid β 365 cells laid out SunβSat Γ 53 weeks, with month labels and day-of-week markers.
- 6 built-in color themes β green, blue, purple, orange, pink, red. Themes switch dynamically without losing data.
- Click-to-edit β click any cell to cycle its count (0 β 1 β 2 β β¦ β 12 β 0). The
changeevent fires on every edit. - Auto-computed stats β longest streak, active days, best day, average per active day, and total contributions are all calculated in the template.
- Fully reactive β update
value,year, or any color prop viagr.HTML(...)and the entire component re-renders. - API / MCP ready β includes
api_info()for Gradio's built-in API and MCP support.
Requirements
gradio>=6.0
No other dependencies. The component is a single Python file.
Quickstart
Minimal example
import gradio as gr
from contribution_heatmap import ContributionHeatmap
with gr.Blocks() as demo:
heatmap = ContributionHeatmap()
demo.launch()
This renders an empty heatmap for 2025 in the default green theme. Users can click cells to add contributions interactively.
With initial data
data = {
"2025-01-15": 4,
"2025-01-16": 7,
"2025-01-17": 12,
"2025-03-01": 2,
}
with gr.Blocks() as demo:
heatmap = ContributionHeatmap(value=data, year=2025, theme="purple")
demo.launch()
Constructor
ContributionHeatmap(
value: dict | None = None,
year: int = 2025,
theme: str = "green",
c0: str | None = None,
c1: str | None = None,
c2: str | None = None,
c3: str | None = None,
c4: str | None = None,
**kwargs,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
value |
dict | None |
{} |
Contribution data. Keys are date strings in YYYY-MM-DD format, values are integers (contribution count for that day). |
year |
int |
2025 |
The calendar year to render. |
theme |
str |
"green" |
One of "green", "blue", "purple", "orange", "pink", "red". Sets the 5-level color palette. |
c0βc4 |
str | None |
None |
Override individual color levels with hex values (e.g. c4="#ff0000"). When None, colors are derived from theme. |
**kwargs |
Passed through to gr.HTML (e.g. visible, elem_id, elem_classes, container, min_height). |
Data format
The value dict maps date strings to integer counts:
{
"2025-01-01": 3, # level 1 (1β2)
"2025-01-02": 5, # level 2 (3β5)
"2025-01-03": 8, # level 3 (6β9)
"2025-01-04": 12, # level 4 (10+)
}
Intensity levels are determined by these thresholds:
| Count | Level | Visual |
|---|---|---|
| 0 | 0 | Darkest (empty) |
| 1β2 | 1 | Light |
| 3β5 | 2 | Medium |
| 6β9 | 3 | Bright |
| 10+ | 4 | Brightest |
Updating props
This is the most important pattern to get right. When updating a ContributionHeatmap from an event handler, return gr.HTML(...) β not a new ContributionHeatmap(...). This tells Gradio to update the existing component's props rather than replacing it entirely.
β
Correct: update via gr.HTML(...)
# Change only the theme colors (data and year are preserved)
def switch_theme(theme):
colors = COLOR_SCHEMES[theme]
return gr.HTML(c0=colors[0], c1=colors[1], c2=colors[2], c3=colors[3], c4=colors[4])
theme_dropdown.change(fn=switch_theme, inputs=[theme_dropdown], outputs=heatmap)
# Update everything: data + year + colors
def regenerate(year, theme):
data = generate_my_data(year)
colors = COLOR_SCHEMES[theme]
return gr.HTML(
value=data,
year=year,
c0=colors[0], c1=colors[1], c2=colors[2], c3=colors[3], c4=colors[4],
)
btn.click(fn=regenerate, inputs=[year_dd, theme_dd], outputs=heatmap)
β Wrong: returning a new instance
# DON'T do this β creates a new component instead of updating props
def switch_theme(theme, data):
return ContributionHeatmap(value=data, theme=theme)
Helper functions
The module includes two convenience functions for building updates:
from contribution_heatmap import _theme_update, _full_update, COLOR_SCHEMES
# Update colors only
theme_dd.change(fn=_theme_update, inputs=[theme_dd], outputs=heatmap)
# Update data + year + colors
btn.click(
fn=lambda y, t: _full_update(my_data, y, t),
inputs=[year_dd, theme_dd],
outputs=heatmap,
)
Events
Since ContributionHeatmap extends gr.HTML, it supports all standard Gradio HTML events. The most useful one is change, which fires when a user clicks a cell:
def on_edit(data):
"""Called whenever a user clicks a cell."""
total = sum(data.values())
active = len([v for v in data.values() if v > 0])
return f"{active} active days, {total} total contributions"
heatmap.change(fn=on_edit, inputs=heatmap, outputs=status_textbox)
The data received in the handler is the full value dict with the updated cell.
Color themes
Six built-in themes are available via the COLOR_SCHEMES dict:
from contribution_heatmap import COLOR_SCHEMES
# Each theme is a list of 5 hex colors: [level0, level1, level2, level3, level4]
print(COLOR_SCHEMES["green"])
# ['#161b22', '#0e4429', '#006d32', '#26a641', '#39d353']
| Theme | Level 0 | Level 1 | Level 2 | Level 3 | Level 4 |
|---|---|---|---|---|---|
green |
#161b22 |
#0e4429 |
#006d32 |
#26a641 |
#39d353 |
blue |
#161b22 |
#0a3069 |
#0550ae |
#0969da |
#54aeff |
purple |
#161b22 |
#3b1f72 |
#6639a6 |
#8957e5 |
#bc8cff |
orange |
#161b22 |
#6e3a07 |
#9a5b13 |
#d4821f |
#f0b040 |
pink |
#161b22 |
#5c1a3a |
#8b2252 |
#d63384 |
#f472b6 |
red |
#161b22 |
#6e1007 |
#9a2013 |
#d4401f |
#f06040 |
Custom colors
You can pass any hex colors directly via c0βc4:
heatmap = ContributionHeatmap(
value=data,
c0="#1a1a2e",
c1="#16213e",
c2="#0f3460",
c3="#533483",
c4="#e94560",
)
Or add your own theme to COLOR_SCHEMES:
COLOR_SCHEMES["cyberpunk"] = ["#0a0a0a", "#1a0533", "#3d0066", "#7700cc", "#cc00ff"]
heatmap = ContributionHeatmap(value=data, theme="cyberpunk")
Full example app
Below is a complete working app with theme switching, pattern generation, and interactive editing:
import gradio as gr
from contribution_heatmap import (
ContributionHeatmap,
COLOR_SCHEMES,
_theme_update,
_full_update,
)
import random
from datetime import datetime, timedelta
def generate_data(year, intensity=0.6):
"""Generate random contribution data."""
data = {}
start = datetime(year, 1, 1)
for i in range(365):
d = start + timedelta(days=i)
if d > datetime.now():
break
if random.random() < intensity:
data[d.strftime("%Y-%m-%d")] = random.randint(1, 15)
return data
with gr.Blocks() as demo:
gr.Markdown("# My Contribution Tracker")
heatmap = ContributionHeatmap(
value=generate_data(2025), year=2025, theme="green"
)
with gr.Row():
theme = gr.Dropdown(
choices=list(COLOR_SCHEMES.keys()), value="green", label="Theme"
)
year = gr.Dropdown(choices=[2023, 2024, 2025], value=2025, label="Year")
regenerate = gr.Button("Regenerate")
status = gr.Textbox(label="Info", interactive=False)
# Theme changes β only update colors, preserve data
theme.change(fn=_theme_update, inputs=[theme], outputs=heatmap)
# Regenerate β new data + year + colors
def on_regen(y, t):
data = generate_data(int(y))
return _full_update(data, y, t), f"{len(data)} active days"
regenerate.click(fn=on_regen, inputs=[year, theme], outputs=[heatmap, status])
# Track edits
heatmap.change(
fn=lambda d: f"Edited: {sum((d or {}).values())} total contributions",
inputs=heatmap,
outputs=status,
)
demo.launch()
Use cases
- AI training logs β visualize daily model training runs, fine-tuning sessions, or evaluation scores.
- Habit tracking β meditation streaks, exercise days, reading logs.
- Coding activity β render actual GitHub contribution data fetched via their API.
- Team dashboards β show multiple heatmaps side-by-side for different team members or projects.
- Time-series overview β any data that maps dates to counts.
Multiple heatmaps
with gr.Blocks() as demo:
gr.Markdown("# Team Activity")
with gr.Row():
alice = ContributionHeatmap(value=alice_data, theme="blue", elem_id="alice")
bob = ContributionHeatmap(value=bob_data, theme="purple", elem_id="bob")
demo.launch()
How it works
This component demonstrates key Gradio 6 gr.HTML capabilities:
html_templateβ JS template strings (${...}) render the grid, stats, and legend dynamically fromvalue,year, and color props.css_templateβ CSS is also templated with${c0}β${c4}, so colors re-render when props change without touching the HTML.js_on_loadβ a click handler is attached once using event delegation on the parent element. It updatesprops.valueand callstrigger('change')to notify Gradio.- Component subclass β
ContributionHeatmapextendsgr.HTML, setting default templates and acceptingtheme/year/color props. Theapi_info()method enables API and MCP usage.
API info
When used with Gradio's API or MCP integration, the component exposes:
{
"type": "object",
"description": "Dict mapping YYYY-MM-DD to int counts"
}
Example API call:
from gradio_client import Client
client = Client("http://localhost:7860")
result = client.predict(
{"2025-01-01": 5, "2025-01-02": 10},
api_name="/predict"
)
License
MIT