ysharma HF Staff commited on
Commit
06d4475
Β·
verified Β·
1 Parent(s): 26dd397

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +361 -1
README.md CHANGED
@@ -11,4 +11,364 @@ license: mit
11
  short_description: 'GitHub-style heatmap with gradio''s new html component '
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  short_description: 'GitHub-style heatmap with gradio''s new html component '
12
  ---
13
 
14
+ # 🟩 ContributionHeatmap
15
+
16
+ A GitHub-style contribution heatmap component for [Gradio 6](https://www.gradio.app), built entirely with `gr.HTML`. No Svelte, no CLI tooling, no npm β€” just Python, HTML templates, CSS, and a sprinkle of JS.
17
+
18
+ ![Gradio 6](https://img.shields.io/badge/Gradio-6.x-orange) ![Python](https://img.shields.io/badge/Python-3.10+-blue) ![License](https://img.shields.io/badge/License-MIT-green)
19
+
20
+ ---
21
+
22
+ ## Features
23
+
24
+ - **GitHub-style grid** — 365 cells laid out Sun→Sat × 53 weeks, with month labels and day-of-week markers.
25
+ - **6 built-in color themes** β€” green, blue, purple, orange, pink, red. Themes switch dynamically without losing data.
26
+ - **Click-to-edit** β€” click any cell to cycle its count (0 β†’ 1 β†’ 2 β†’ … β†’ 12 β†’ 0). The `change` event fires on every edit.
27
+ - **Auto-computed stats** β€” longest streak, active days, best day, average per active day, and total contributions are all calculated in the template.
28
+ - **Fully reactive** β€” update `value`, `year`, or any color prop via `gr.HTML(...)` and the entire component re-renders.
29
+ - **API / MCP ready** β€” includes `api_info()` for Gradio's built-in API and MCP support.
30
+
31
+ ---
32
+
33
+ ## Requirements
34
+
35
+ ```
36
+ gradio>=6.0
37
+ ```
38
+
39
+ No other dependencies. The component is a single Python file.
40
+
41
+ ---
42
+
43
+ ## Quickstart
44
+
45
+ ### Minimal example
46
+
47
+ ```python
48
+ import gradio as gr
49
+ from contribution_heatmap import ContributionHeatmap
50
+
51
+ with gr.Blocks() as demo:
52
+ heatmap = ContributionHeatmap()
53
+
54
+ demo.launch()
55
+ ```
56
+
57
+ This renders an empty heatmap for 2025 in the default green theme. Users can click cells to add contributions interactively.
58
+
59
+ ### With initial data
60
+
61
+ ```python
62
+ data = {
63
+ "2025-01-15": 4,
64
+ "2025-01-16": 7,
65
+ "2025-01-17": 12,
66
+ "2025-03-01": 2,
67
+ }
68
+
69
+ with gr.Blocks() as demo:
70
+ heatmap = ContributionHeatmap(value=data, year=2025, theme="purple")
71
+
72
+ demo.launch()
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Constructor
78
+
79
+ ```python
80
+ ContributionHeatmap(
81
+ value: dict | None = None,
82
+ year: int = 2025,
83
+ theme: str = "green",
84
+ c0: str | None = None,
85
+ c1: str | None = None,
86
+ c2: str | None = None,
87
+ c3: str | None = None,
88
+ c4: str | None = None,
89
+ **kwargs,
90
+ )
91
+ ```
92
+
93
+ | Parameter | Type | Default | Description |
94
+ |-----------|------|---------|-------------|
95
+ | `value` | `dict \| None` | `{}` | Contribution data. Keys are date strings in `YYYY-MM-DD` format, values are integers (contribution count for that day). |
96
+ | `year` | `int` | `2025` | The calendar year to render. |
97
+ | `theme` | `str` | `"green"` | One of `"green"`, `"blue"`, `"purple"`, `"orange"`, `"pink"`, `"red"`. Sets the 5-level color palette. |
98
+ | `c0`–`c4` | `str \| None` | `None` | Override individual color levels with hex values (e.g. `c4="#ff0000"`). When `None`, colors are derived from `theme`. |
99
+ | `**kwargs` | | | Passed through to `gr.HTML` (e.g. `visible`, `elem_id`, `elem_classes`, `container`, `min_height`). |
100
+
101
+ ### Data format
102
+
103
+ The `value` dict maps date strings to integer counts:
104
+
105
+ ```python
106
+ {
107
+ "2025-01-01": 3, # level 1 (1–2)
108
+ "2025-01-02": 5, # level 2 (3–5)
109
+ "2025-01-03": 8, # level 3 (6–9)
110
+ "2025-01-04": 12, # level 4 (10+)
111
+ }
112
+ ```
113
+
114
+ Intensity levels are determined by these thresholds:
115
+
116
+ | Count | Level | Visual |
117
+ |-------|-------|--------|
118
+ | 0 | 0 | Darkest (empty) |
119
+ | 1–2 | 1 | Light |
120
+ | 3–5 | 2 | Medium |
121
+ | 6–9 | 3 | Bright |
122
+ | 10+ | 4 | Brightest |
123
+
124
+ ---
125
+
126
+ ## Updating props
127
+
128
+ 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.
129
+
130
+ ### βœ… Correct: update via `gr.HTML(...)`
131
+
132
+ ```python
133
+ # Change only the theme colors (data and year are preserved)
134
+ def switch_theme(theme):
135
+ colors = COLOR_SCHEMES[theme]
136
+ return gr.HTML(c0=colors[0], c1=colors[1], c2=colors[2], c3=colors[3], c4=colors[4])
137
+
138
+ theme_dropdown.change(fn=switch_theme, inputs=[theme_dropdown], outputs=heatmap)
139
+ ```
140
+
141
+ ```python
142
+ # Update everything: data + year + colors
143
+ def regenerate(year, theme):
144
+ data = generate_my_data(year)
145
+ colors = COLOR_SCHEMES[theme]
146
+ return gr.HTML(
147
+ value=data,
148
+ year=year,
149
+ c0=colors[0], c1=colors[1], c2=colors[2], c3=colors[3], c4=colors[4],
150
+ )
151
+
152
+ btn.click(fn=regenerate, inputs=[year_dd, theme_dd], outputs=heatmap)
153
+ ```
154
+
155
+ ### ❌ Wrong: returning a new instance
156
+
157
+ ```python
158
+ # DON'T do this β€” creates a new component instead of updating props
159
+ def switch_theme(theme, data):
160
+ return ContributionHeatmap(value=data, theme=theme)
161
+ ```
162
+
163
+ ### Helper functions
164
+
165
+ The module includes two convenience functions for building updates:
166
+
167
+ ```python
168
+ from contribution_heatmap import _theme_update, _full_update, COLOR_SCHEMES
169
+
170
+ # Update colors only
171
+ theme_dd.change(fn=_theme_update, inputs=[theme_dd], outputs=heatmap)
172
+
173
+ # Update data + year + colors
174
+ btn.click(
175
+ fn=lambda y, t: _full_update(my_data, y, t),
176
+ inputs=[year_dd, theme_dd],
177
+ outputs=heatmap,
178
+ )
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Events
184
+
185
+ 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:
186
+
187
+ ```python
188
+ def on_edit(data):
189
+ """Called whenever a user clicks a cell."""
190
+ total = sum(data.values())
191
+ active = len([v for v in data.values() if v > 0])
192
+ return f"{active} active days, {total} total contributions"
193
+
194
+ heatmap.change(fn=on_edit, inputs=heatmap, outputs=status_textbox)
195
+ ```
196
+
197
+ The `data` received in the handler is the full `value` dict with the updated cell.
198
+
199
+ ---
200
+
201
+ ## Color themes
202
+
203
+ Six built-in themes are available via the `COLOR_SCHEMES` dict:
204
+
205
+ ```python
206
+ from contribution_heatmap import COLOR_SCHEMES
207
+
208
+ # Each theme is a list of 5 hex colors: [level0, level1, level2, level3, level4]
209
+ print(COLOR_SCHEMES["green"])
210
+ # ['#161b22', '#0e4429', '#006d32', '#26a641', '#39d353']
211
+ ```
212
+
213
+ | Theme | Level 0 | Level 1 | Level 2 | Level 3 | Level 4 |
214
+ |-------|---------|---------|---------|---------|---------|
215
+ | `green` | `#161b22` | `#0e4429` | `#006d32` | `#26a641` | `#39d353` |
216
+ | `blue` | `#161b22` | `#0a3069` | `#0550ae` | `#0969da` | `#54aeff` |
217
+ | `purple` | `#161b22` | `#3b1f72` | `#6639a6` | `#8957e5` | `#bc8cff` |
218
+ | `orange` | `#161b22` | `#6e3a07` | `#9a5b13` | `#d4821f` | `#f0b040` |
219
+ | `pink` | `#161b22` | `#5c1a3a` | `#8b2252` | `#d63384` | `#f472b6` |
220
+ | `red` | `#161b22` | `#6e1007` | `#9a2013` | `#d4401f` | `#f06040` |
221
+
222
+ ### Custom colors
223
+
224
+ You can pass any hex colors directly via `c0`–`c4`:
225
+
226
+ ```python
227
+ heatmap = ContributionHeatmap(
228
+ value=data,
229
+ c0="#1a1a2e",
230
+ c1="#16213e",
231
+ c2="#0f3460",
232
+ c3="#533483",
233
+ c4="#e94560",
234
+ )
235
+ ```
236
+
237
+ Or add your own theme to `COLOR_SCHEMES`:
238
+
239
+ ```python
240
+ COLOR_SCHEMES["cyberpunk"] = ["#0a0a0a", "#1a0533", "#3d0066", "#7700cc", "#cc00ff"]
241
+ heatmap = ContributionHeatmap(value=data, theme="cyberpunk")
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Full example app
247
+
248
+ Below is a complete working app with theme switching, pattern generation, and interactive editing:
249
+
250
+ ```python
251
+ import gradio as gr
252
+ from contribution_heatmap import (
253
+ ContributionHeatmap,
254
+ COLOR_SCHEMES,
255
+ _theme_update,
256
+ _full_update,
257
+ )
258
+ import random
259
+ from datetime import datetime, timedelta
260
+
261
+
262
+ def generate_data(year, intensity=0.6):
263
+ """Generate random contribution data."""
264
+ data = {}
265
+ start = datetime(year, 1, 1)
266
+ for i in range(365):
267
+ d = start + timedelta(days=i)
268
+ if d > datetime.now():
269
+ break
270
+ if random.random() < intensity:
271
+ data[d.strftime("%Y-%m-%d")] = random.randint(1, 15)
272
+ return data
273
+
274
+
275
+ with gr.Blocks() as demo:
276
+ gr.Markdown("# My Contribution Tracker")
277
+
278
+ heatmap = ContributionHeatmap(
279
+ value=generate_data(2025), year=2025, theme="green"
280
+ )
281
+
282
+ with gr.Row():
283
+ theme = gr.Dropdown(
284
+ choices=list(COLOR_SCHEMES.keys()), value="green", label="Theme"
285
+ )
286
+ year = gr.Dropdown(choices=[2023, 2024, 2025], value=2025, label="Year")
287
+
288
+ regenerate = gr.Button("Regenerate")
289
+ status = gr.Textbox(label="Info", interactive=False)
290
+
291
+ # Theme changes β€” only update colors, preserve data
292
+ theme.change(fn=_theme_update, inputs=[theme], outputs=heatmap)
293
+
294
+ # Regenerate β€” new data + year + colors
295
+ def on_regen(y, t):
296
+ data = generate_data(int(y))
297
+ return _full_update(data, y, t), f"{len(data)} active days"
298
+
299
+ regenerate.click(fn=on_regen, inputs=[year, theme], outputs=[heatmap, status])
300
+
301
+ # Track edits
302
+ heatmap.change(
303
+ fn=lambda d: f"Edited: {sum((d or {}).values())} total contributions",
304
+ inputs=heatmap,
305
+ outputs=status,
306
+ )
307
+
308
+
309
+ demo.launch()
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Use cases
315
+
316
+ - **AI training logs** β€” visualize daily model training runs, fine-tuning sessions, or evaluation scores.
317
+ - **Habit tracking** β€” meditation streaks, exercise days, reading logs.
318
+ - **Coding activity** β€” render actual GitHub contribution data fetched via their API.
319
+ - **Team dashboards** β€” show multiple heatmaps side-by-side for different team members or projects.
320
+ - **Time-series overview** β€” any data that maps dates to counts.
321
+
322
+ ### Multiple heatmaps
323
+
324
+ ```python
325
+ with gr.Blocks() as demo:
326
+ gr.Markdown("# Team Activity")
327
+ with gr.Row():
328
+ alice = ContributionHeatmap(value=alice_data, theme="blue", elem_id="alice")
329
+ bob = ContributionHeatmap(value=bob_data, theme="purple", elem_id="bob")
330
+
331
+ demo.launch()
332
+ ```
333
+
334
+ ---
335
+
336
+ ## How it works
337
+
338
+ This component demonstrates key Gradio 6 `gr.HTML` capabilities:
339
+
340
+ 1. **`html_template`** β€” JS template strings (`${...}`) render the grid, stats, and legend dynamically from `value`, `year`, and color props.
341
+ 2. **`css_template`** β€” CSS is also templated with `${c0}`–`${c4}`, so colors re-render when props change without touching the HTML.
342
+ 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.
343
+ 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.
344
+
345
+ ---
346
+
347
+ ## API info
348
+
349
+ When used with Gradio's API or MCP integration, the component exposes:
350
+
351
+ ```json
352
+ {
353
+ "type": "object",
354
+ "description": "Dict mapping YYYY-MM-DD to int counts"
355
+ }
356
+ ```
357
+
358
+ Example API call:
359
+
360
+ ```python
361
+ from gradio_client import Client
362
+
363
+ client = Client("http://localhost:7860")
364
+ result = client.predict(
365
+ {"2025-01-01": 5, "2025-01-02": 10},
366
+ api_name="/predict"
367
+ )
368
+ ```
369
+
370
+ ---
371
+
372
+ ## License
373
+
374
+ MIT