Update chart style

This commit is contained in:
2025-06-30 22:00:22 +10:00
parent 83e5ab118d
commit 55b36b920c
3 changed files with 218 additions and 19 deletions
BIN
View File
Binary file not shown.
+126 -14
View File
@@ -76,7 +76,7 @@
<div class="flex flex-wrap gap-2 mt-2">
{% for tag in link.tags.all %}
{% with number=forloop.counter %}
<a href="{% url 'tag-detail' tag.slug %}"
<a href="{% url 'tag-detail' tag.slug %}"
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium transition duration-150 {% if number|divisibleby:10 %}tag-10{% elif number|divisibleby:9 %}tag-9{% elif number|divisibleby:8 %}tag-8{% elif number|divisibleby:7 %}tag-7{% elif number|divisibleby:6 %}tag-6{% elif number|divisibleby:5 %}tag-5{% elif number|divisibleby:4 %}tag-4{% elif number|divisibleby:3 %}tag-3{% elif number|divisibleby:2 %}tag-2{% else %}tag-1{% endif %}">
{{ tag.name }}
</a>
@@ -105,10 +105,28 @@
<div class="bg-white shadow-md rounded-lg overflow-hidden">
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<h2 class="text-xl font-bold text-gray-900">{% trans "Click Statistics" %}</h2>
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center space-y-4 sm:space-y-0">
<h2 class="text-xl font-bold text-gray-900">{% trans "Click Statistics" %} - {{ period_name }}</h2>
<div class="flex flex-wrap gap-2">
<button onclick="changePeriod('3m')" class="period-btn {% if current_period == '3m' %}active{% endif %}" data-period="3m">
{% trans "3 Months" %}
</button>
<button onclick="changePeriod('6m')" class="period-btn {% if current_period == '6m' %}active{% endif %}" data-period="6m">
{% trans "6 Months" %}
</button>
<button onclick="changePeriod('1y')" class="period-btn {% if current_period == '1y' %}active{% endif %}" data-period="1y">
{% trans "1 Year" %}
</button>
<button onclick="changePeriod('all')" class="period-btn {% if current_period == 'all' %}active{% endif %}" data-period="all">
{% trans "All Time" %}
</button>
</div>
</div>
</div>
<div class="px-4 py-5 sm:p-6">
<canvas id="clickChart"></canvas>
<div style="height: 400px; position: relative;">
<canvas id="clickChart"></canvas>
</div>
</div>
</div>
@@ -144,34 +162,87 @@
document.addEventListener('DOMContentLoaded', function() {
var ctx = document.getElementById('clickChart').getContext('2d');
var clickData = {{ click_stats|safe }};
var intervalDays = {{ interval_days }};
var chart = new Chart(ctx, {
type: 'line',
data: {
labels: clickData.map(item => item.date),
labels: clickData.map(item => {
// Use custom label if available, otherwise format date based on interval
if (item.label) {
return item.label;
}
const date = new Date(item.date);
if (intervalDays === 1) {
// Daily: show "Jan 15"
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
} else if (intervalDays <= 14) {
// Weekly/Bi-weekly: show "1/15"
return date.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
} else {
// Monthly: show "Jan 2025"
return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
}
}),
datasets: [{
label: '{% trans "Clicks" %}',
data: clickData.map(item => item.count),
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
backgroundColor: 'rgba(99, 102, 241, 0.1)',
borderColor: 'rgba(99, 102, 241, 1)',
borderWidth: 3,
fill: true,
tension: 0.4,
pointBackgroundColor: 'rgba(99, 102, 241, 1)',
pointBorderColor: '#ffffff',
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6,
pointHoverBackgroundColor: 'rgba(99, 102, 241, 1)',
pointHoverBorderColor: '#ffffff',
pointHoverBorderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '{% trans "Number of Clicks" %}'
grid: {
color: 'rgba(0, 0, 0, 0.05)',
borderDash: [2, 2]
},
ticks: {
stepSize: 1,
color: '#6b7280',
font: {
size: 12
}
}
},
x: {
title: {
display: true,
text: '{% trans "Date" %}'
grid: {
display: false
},
ticks: {
color: '#6b7280',
font: {
size: 12
},
maxTicksLimit: intervalDays === 1 ? 15 : 10
}
}
},
elements: {
line: {
borderJoinStyle: 'round'
}
}
}
});
@@ -200,6 +271,13 @@
tag.classList.add(...randomColor.split(' '));
});
});
// Function to change time period
function changePeriod(period) {
const url = new URL(window.location.href);
url.searchParams.set('period', period);
window.location.href = url.toString();
}
</script>
{% endblock %}
@@ -271,6 +349,40 @@
padding-right: 1.1428571em;
padding-bottom: 0.8571429em;
padding-left: 1.1428571em;
}
}
/* Chart container styling */
#clickChart {
border-radius: 8px;
}
/* Period selection buttons */
.period-btn {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
font-weight: 500;
border-radius: 0.375rem;
border: 1px solid #d1d5db;
background-color: #ffffff;
color: #374151;
cursor: pointer;
transition: all 0.2s ease-in-out;
}
.period-btn:hover {
background-color: #f9fafb;
border-color: #9ca3af;
}
.period-btn.active {
background-color: #6366f1;
border-color: #6366f1;
color: #ffffff;
}
.period-btn.active:hover {
background-color: #5b21b6;
border-color: #5b21b6;
}
</style>
{% endblock %}
+92 -5
View File
@@ -271,16 +271,103 @@ class LinkDetailView(DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
click_stats = ClickLog.objects.filter(link=self.object).annotate(
from datetime import datetime, timedelta
import calendar
# Get the time period from request (default to 3 months)
period = self.request.GET.get('period', '3m')
# Calculate date range and interval based on period
end_date = datetime.now().date()
if period == '3m':
start_date = end_date - timedelta(days=89) # 90 days total
period_name = "3 Months"
interval_days = 1 # Daily
elif period == '6m':
start_date = end_date - timedelta(days=179) # 180 days total
period_name = "6 Months"
interval_days = 7 # Weekly
elif period == '1y':
start_date = end_date - timedelta(days=364) # 365 days total
period_name = "1 Year"
interval_days = 7 # Weekly
elif period == 'all':
# Get the earliest click date, or default to 1 year ago if no clicks
earliest_click = ClickLog.objects.filter(link=self.object).order_by('clicked_at').first()
if earliest_click:
start_date = earliest_click.clicked_at.date()
# Determine interval based on data range
days_range = (end_date - start_date).days
if days_range <= 90:
interval_days = 7 # Weekly
elif days_range <= 365:
interval_days = 7 # Weekly
else:
interval_days = 30 # Monthly
else:
start_date = end_date - timedelta(days=364)
interval_days = 7 # Weekly
period_name = "All Time"
else:
start_date = end_date - timedelta(days=89) # Default to 3 months
period_name = "3 Months"
interval_days = 1 # Daily
# Get click stats from database
click_stats = ClickLog.objects.filter(
link=self.object,
clicked_at__date__gte=start_date,
clicked_at__date__lte=end_date
).annotate(
date=TruncDate('clicked_at')
).values('date').annotate(count=Count('id')).order_by('date')
# Convert query results to list and format dates as strings
click_stats_list = list(click_stats)
for item in click_stats_list:
item['date'] = item['date'].strftime('%Y-%m-%d')
# Convert to dictionary for easy lookup
click_dict = {item['date']: item['count'] for item in click_stats}
# Create complete dataset with appropriate intervals
click_stats_list = []
current_date = start_date
if interval_days == 1:
# Daily intervals
while current_date <= end_date:
click_stats_list.append({
'date': current_date.strftime('%Y-%m-%d'),
'count': click_dict.get(current_date, 0)
})
current_date += timedelta(days=1)
else:
# Weekly, bi-weekly, or monthly intervals
while current_date <= end_date:
interval_end = min(current_date + timedelta(days=interval_days - 1), end_date)
# Sum clicks for this interval
interval_count = 0
temp_date = current_date
while temp_date <= interval_end:
interval_count += click_dict.get(temp_date, 0)
temp_date += timedelta(days=1)
# Format label based on interval
if interval_days == 7: # Weekly
label = f"{current_date.strftime('%m/%d')}"
elif interval_days == 14: # Bi-weekly
label = f"{current_date.strftime('%m/%d')}"
else: # Monthly
label = f"{calendar.month_abbr[current_date.month]} {current_date.year}"
click_stats_list.append({
'date': current_date.strftime('%Y-%m-%d'),
'count': interval_count,
'label': label
})
current_date += timedelta(days=interval_days)
context['click_stats'] = json.dumps(click_stats_list, cls=DjangoJSONEncoder)
context['current_period'] = period
context['period_name'] = period_name
context['interval_days'] = interval_days
# Convert markdown to HTML if the link is a custom type
if self.object.link_type == Link.LinkType.CUSTOM: