ysharma's picture
ysharma HF Staff
Update README.md
700f8df verified

A newer version of the Gradio SDK is available: 6.8.0

Upgrade
metadata
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 change event 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 via gr.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:

  1. html_template β€” JS template strings (${...}) render the grid, stats, and legend dynamically from value, year, and color props.
  2. css_template β€” CSS is also templated with ${c0}–${c4}, so colors re-render when props change without touching the HTML.
  3. js_on_load β€” a click handler is attached once using event delegation on the parent element. It updates props.value and calls trigger('change') to notify Gradio.
  4. Component subclass β€” ContributionHeatmap extends gr.HTML, setting default templates and accepting theme/year/color props. The api_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