Change leadership graph to line and add linear best fit
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -7,11 +8,15 @@
|
||||
<meta name="description" content="Track your weight loss competition with friends">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% if session.get('user_id') %}
|
||||
<nav class="navbar">
|
||||
@@ -26,15 +31,18 @@
|
||||
<span class="nav-link-icon">📊</span>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a href="{{ url_for('checkin.index') }}" class="{{ 'active' if request.endpoint and request.endpoint.startswith('checkin') }}">
|
||||
<a href="{{ url_for('checkin.index') }}"
|
||||
class="{{ 'active' if request.endpoint and request.endpoint.startswith('checkin') }}">
|
||||
<span class="nav-link-icon">✏️</span>
|
||||
<span>Check-in</span>
|
||||
</a>
|
||||
<a href="{{ url_for('leaderboard.index') }}" class="{{ 'active' if request.endpoint == 'leaderboard.index' }}">
|
||||
<a href="{{ url_for('leaderboard.index') }}"
|
||||
class="{{ 'active' if request.endpoint == 'leaderboard.index' }}">
|
||||
<span class="nav-link-icon">🏆</span>
|
||||
<span>Leaderboard</span>
|
||||
</a>
|
||||
<a href="{{ url_for('profile.index') }}" class="{{ 'active' if request.endpoint and request.endpoint.startswith('profile') }}">
|
||||
<a href="{{ url_for('profile.index') }}"
|
||||
class="{{ 'active' if request.endpoint and request.endpoint.startswith('profile') }}">
|
||||
<span class="nav-link-icon">👤</span>
|
||||
<span>Profile</span>
|
||||
</a>
|
||||
@@ -67,4 +75,5 @@
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
@@ -7,13 +7,37 @@
|
||||
<p>Ranked by % body weight lost. May the best loser win!</p>
|
||||
</div>
|
||||
|
||||
<!-- Comparison Chart -->
|
||||
<!-- Progress-Over-Time Chart -->
|
||||
<div class="card" style="margin-bottom: 1.5rem;">
|
||||
<div class="card-header">
|
||||
<h2>📊 % Weight Lost Comparison</h2>
|
||||
<h2>📈 Progress Over Time</h2>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="comparisonChart"></canvas>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="chart-filters">
|
||||
<div class="filter-group">
|
||||
<label for="filterStart">From</label>
|
||||
<input type="date" id="filterStart" class="form-input">
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label for="filterEnd">To</label>
|
||||
<input type="date" id="filterEnd" class="form-input">
|
||||
</div>
|
||||
<div class="filter-group filter-group-people">
|
||||
<label>People</label>
|
||||
<div class="person-filter-list" id="personFilterList">
|
||||
{% for u in ranked %}
|
||||
<label class="person-checkbox">
|
||||
<input type="checkbox" value="{{ u.id }}" checked>
|
||||
<span>{{ u.display_name or u.username }}</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-container" style="position: relative; height: 380px;">
|
||||
<canvas id="progressChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -90,14 +114,50 @@
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/charts.js') }}"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
fetch('/api/comparison')
|
||||
let _progressChart = null;
|
||||
let _debounceTimer = null;
|
||||
|
||||
function loadProgressChart() {
|
||||
const start = document.getElementById('filterStart').value;
|
||||
const end = document.getElementById('filterEnd').value;
|
||||
|
||||
// Gather checked user IDs
|
||||
const checks = document.querySelectorAll('#personFilterList input[type=checkbox]:checked');
|
||||
const userIds = Array.from(checks).map(cb => cb.value).join(',');
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (start) params.set('start', start);
|
||||
if (end) params.set('end', end);
|
||||
if (userIds) params.set('user_ids', userIds);
|
||||
|
||||
fetch('/api/progress-over-time?' + params.toString())
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.names.length > 0) {
|
||||
createComparisonChart('comparisonChart', data.names, data.pct_lost);
|
||||
if (_progressChart) {
|
||||
_progressChart.destroy();
|
||||
_progressChart = null;
|
||||
}
|
||||
if (data.users && data.users.length > 0) {
|
||||
_progressChart = createProgressChart('progressChart', data.users);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function debouncedLoad() {
|
||||
clearTimeout(_debounceTimer);
|
||||
_debounceTimer = setTimeout(loadProgressChart, 300);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Initial load
|
||||
loadProgressChart();
|
||||
|
||||
// Wire up filter changes
|
||||
document.getElementById('filterStart').addEventListener('change', debouncedLoad);
|
||||
document.getElementById('filterEnd').addEventListener('change', debouncedLoad);
|
||||
document.querySelectorAll('#personFilterList input[type=checkbox]').forEach(cb => {
|
||||
cb.addEventListener('change', loadProgressChart);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user