The Tracker component displays activity or time-series data in a compact grid format, similar to GitHub’s contribution graph. Each item represents a data point with optional color coding and tooltips.
Basic Usage
{% load unfold %}
{% component "unfold/components/tracker.html" with data=tracker_data %}
{% endcomponent %}
Props
List of tracker items. Each item should be a dictionary with optional color and tooltip keys.
Tailwind CSS background color class for the item (e.g., bg-green-600).
Tooltip text displayed on hover.
Additional CSS classes for the tracker container.
Examples
Simple Activity Tracker
# In view or context
tracker_data = [
{'color': 'bg-green-600', 'tooltip': 'March 1: 15 commits'},
{'color': 'bg-green-400', 'tooltip': 'March 2: 8 commits'},
{'color': 'bg-base-300', 'tooltip': 'March 3: No activity'},
{'color': 'bg-green-600', 'tooltip': 'March 4: 12 commits'},
# ... more days
]
{% component "unfold/components/tracker.html" with data=tracker_data %}
{% endcomponent %}
Tracker in Card
{% component "unfold/components/card.html" with title="Activity This Month" %}
{% component "unfold/components/tracker.html" with data=activity_tracker %}
{% endcomponent %}
{% endcomponent %}
Complete Example: User Activity Tracker
# views.py
from django.views.generic import TemplateView
from unfold.views import UnfoldModelAdminViewMixin
from django.utils import timezone
from datetime import timedelta
from django.db.models import Count
class UserActivityView(UnfoldModelAdminViewMixin, TemplateView):
title = "User Activity"
template_name = "admin/user_activity.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Generate last 90 days of activity
today = timezone.now().date()
activity_data = []
# Get activity counts per day
activities = Activity.objects.filter(
created_at__gte=today - timedelta(days=89)
).values('created_at__date').annotate(
count=Count('id')
)
activity_by_date = {
item['created_at__date']: item['count']
for item in activities
}
# Generate tracker data
for i in range(89, -1, -1):
date = today - timedelta(days=i)
count = activity_by_date.get(date, 0)
# Color based on activity level
if count == 0:
color = 'bg-base-300 dark:bg-base-400'
elif count < 5:
color = 'bg-green-300 dark:bg-green-400'
elif count < 10:
color = 'bg-green-500 dark:bg-green-600'
else:
color = 'bg-green-700 dark:bg-green-800'
activity_data.append({
'color': color,
'tooltip': f"{date.strftime('%b %d, %Y')}: {count} activities"
})
context['activity_tracker'] = activity_data
return context
{# templates/admin/user_activity.html #}
{% extends "unfold/layouts/base.html" %}
{% load unfold %}
{% block content %}
<div class="container mx-auto">
{% component "unfold/components/card.html" with title="Activity Overview" %}
<div class="mb-4">
<p class="text-sm text-gray-600 dark:text-gray-400">Last 90 days</p>
</div>
{% component "unfold/components/tracker.html" with data=activity_tracker class="max-w-4xl" %}
{% endcomponent %}
<div class="mt-4 flex items-center gap-4 text-sm">
<span class="text-gray-600 dark:text-gray-400">Less</span>
<div class="flex gap-1">
<div class="w-4 h-4 bg-base-300 dark:bg-base-400 rounded"></div>
<div class="w-4 h-4 bg-green-300 dark:bg-green-400 rounded"></div>
<div class="w-4 h-4 bg-green-500 dark:bg-green-600 rounded"></div>
<div class="w-4 h-4 bg-green-700 dark:bg-green-800 rounded"></div>
</div>
<span class="text-gray-600 dark:text-gray-400">More</span>
</div>
{% endcomponent %}
</div>
{% endblock %}
Use Cases
GitHub-Style Contribution Graph
def generate_contribution_graph(user, days=365):
"""Generate a year of contribution data"""
today = timezone.now().date()
contributions = Contribution.objects.filter(
user=user,
created_at__gte=today - timedelta(days=days-1)
).values('created_at__date').annotate(count=Count('id'))
contrib_by_date = {item['created_at__date']: item['count'] for item in contributions}
tracker_data = []
for i in range(days-1, -1, -1):
date = today - timedelta(days=i)
count = contrib_by_date.get(date, 0)
# Color intensity based on contribution count
colors = [
'bg-base-300 dark:bg-base-600',
'bg-green-200 dark:bg-green-900',
'bg-green-400 dark:bg-green-700',
'bg-green-600 dark:bg-green-500',
'bg-green-800 dark:bg-green-300',
]
level = min(count, 4) # Cap at 4 levels
tracker_data.append({
'color': colors[level],
'tooltip': f"{date.strftime('%a, %b %d, %Y')}: {count} contributions"
})
return tracker_data
Server Uptime Tracker
def generate_uptime_tracker(server, days=30):
"""Generate uptime status for last 30 days"""
today = timezone.now().date()
uptime_data = []
for i in range(days-1, -1, -1):
date = today - timedelta(days=i)
# Check if server was up on this day
was_up = ServerStatus.objects.filter(
server=server,
date=date,
status='up'
).exists()
uptime_data.append({
'color': 'bg-green-600' if was_up else 'bg-red-600',
'tooltip': f"{date.strftime('%b %d')}: {'Up' if was_up else 'Down'}"
})
return uptime_data
{% component "unfold/components/card.html" with title="Server Uptime" %}
{% component "unfold/components/tracker.html" with data=uptime_tracker %}
{% endcomponent %}
{% slot footer %}
<div class="flex items-center gap-4 text-sm">
<div class="flex items-center gap-2">
<div class="w-3 h-3 bg-green-600 rounded"></div>
<span>Online</span>
</div>
<div class="flex items-center gap-2">
<div class="w-3 h-3 bg-red-600 rounded"></div>
<span>Offline</span>
</div>
</div>
{% endslot %}
{% endcomponent %}
Custom Status Tracker
status_tracker = [
{'color': 'bg-blue-600', 'tooltip': 'Planned'},
{'color': 'bg-blue-600', 'tooltip': 'Planned'},
{'color': 'bg-yellow-600', 'tooltip': 'In Progress'},
{'color': 'bg-yellow-600', 'tooltip': 'In Progress'},
{'color': 'bg-green-600', 'tooltip': 'Completed'},
{'color': 'bg-green-600', 'tooltip': 'Completed'},
{'color': 'bg-red-600', 'tooltip': 'Failed'},
{'color': 'bg-base-300', 'tooltip': 'No data'},
]
Styling
Default tracker styles:
- Container:
flex flex-row gap-0.5 overflow-hidden rounded-default
- Items:
h-8 px-px size-full
- Default color:
bg-base-300 dark:bg-base-400
- Hover effect:
hover:opacity-50
Custom Layouts
{# Larger squares #}
{% component "unfold/components/tracker.html" with data=tracker_data class="[&>li]:h-12" %}
{% endcomponent %}
{# Smaller gaps #}
{% component "unfold/components/tracker.html" with data=tracker_data class="gap-1" %}
{% endcomponent %}
{# Square items #}
{% component "unfold/components/tracker.html" with data=tracker_data class="[&>li]:w-8" %}
{% endcomponent %}
Accessibility
- Tooltips provide context for each tracker item
- Use semantic colors (green for success, red for error, etc.)
- Include a legend when color coding is complex
Best Practices
- Consistent Data: Ensure all days/periods are represented
- Color Meaning: Use intuitive color schemes
- Tooltips: Provide detailed information in tooltips
- Legend: Include a legend for complex color coding
- Performance: Limit to reasonable time periods (e.g., 365 days max)
- Progress - Linear progress indicators
- Card - Wrap trackers in cards
- Chart - More detailed time-series visualizations
Source
Template: src/unfold/templates/unfold/components/tracker.html:1