[tasks] Add weigh in graph
This commit is contained in:
@ -93,7 +93,7 @@ fetching and simple saving.
|
||||
:LOGBOOK:
|
||||
CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20
|
||||
:END:
|
||||
* Backlog [4/18] :vrobbler:project:personal:
|
||||
* Backlog [5/18] :vrobbler:project:personal:
|
||||
** TODO [#C] Add sentiment parsing for Scrobbles with notes :vrobbler:project:scrobbles:sentiment:
|
||||
:PROPERTIES:
|
||||
:ID: 37781d6a-f3b0-48b2-bf98-33c2c791cf85
|
||||
@ -489,7 +489,7 @@ whatever time KoReader reports, we need to know, given the date and the user
|
||||
profile's historic timezone, how many hours to adjust the KoReader time to get
|
||||
to GMT to save it in the database.
|
||||
|
||||
** TODO [#B] Can we show a graph of all past Weigh-in tasks :scale:tasks:graphs:javascript:
|
||||
** DONE [#B] Can we show a graph of all past Weigh-in tasks :scale:tasks:graphs:javascript:
|
||||
:PROPERTIES:
|
||||
:ID: ae499d87-03bf-4e48-9b2c-1a421a46af11
|
||||
:END:
|
||||
@ -503,6 +503,10 @@ The graph would contain all Weigh-in scrobbles for that user, no matter which
|
||||
date is being viewed, and the highlighted value on the graph would be the date
|
||||
being viewed.
|
||||
|
||||
Probably could use something like chart.js although maybe that's too heavy?
|
||||
|
||||
And can we have each metric overlayed on the same graph?
|
||||
|
||||
** DONE [#B] When viewing scrobbles by tag, sum the total time :scrobbles:tags:
|
||||
:PROPERTIES:
|
||||
:ID: d51f23df-c2c5-4e1a-b000-67c89032af02
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import logging
|
||||
|
||||
import pendulum
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse_lazy
|
||||
@ -8,6 +9,7 @@ from rest_framework import status
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from scrobbles.models import Scrobble
|
||||
from scrobbles.views import ScrobbleableDetailView, ScrobbleableListView
|
||||
from tasks.models import Task
|
||||
|
||||
@ -23,6 +25,72 @@ class TaskListView(ScrobbleableListView):
|
||||
class TaskDetailView(ScrobbleableDetailView):
|
||||
model = Task
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
if self.object.title != "Weigh-in":
|
||||
return ctx
|
||||
|
||||
scrobbles = list(
|
||||
Scrobble.objects.filter(
|
||||
user=self.request.user,
|
||||
task=self.object,
|
||||
log__weight__isnull=False,
|
||||
).order_by("timestamp")
|
||||
)
|
||||
if not scrobbles:
|
||||
return ctx
|
||||
|
||||
labels = []
|
||||
weight_data = []
|
||||
body_fat_data = []
|
||||
bmi_data = []
|
||||
for s in scrobbles:
|
||||
ts = s.timestamp
|
||||
if isinstance(ts, str):
|
||||
ts = pendulum.parse(ts)
|
||||
labels.append(ts.strftime("%Y-%m-%d"))
|
||||
log = s.log if isinstance(s.log, dict) else {}
|
||||
raw_weight = log.get("weight")
|
||||
weight_data.append(
|
||||
float(raw_weight) if raw_weight is not None else None
|
||||
)
|
||||
raw_bf = log.get("body_fat")
|
||||
body_fat_data.append(
|
||||
float(raw_bf) if raw_bf is not None else None
|
||||
)
|
||||
raw_bmi = log.get("bmi")
|
||||
bmi_data.append(
|
||||
float(raw_bmi) if raw_bmi is not None else None
|
||||
)
|
||||
|
||||
ctx["weighin_chart"] = {
|
||||
"labels": labels,
|
||||
"datasets": [
|
||||
{
|
||||
"label": "Weight",
|
||||
"data": weight_data,
|
||||
"borderColor": "#4bc0c0",
|
||||
"fill": False,
|
||||
"yAxisID": "y",
|
||||
},
|
||||
{
|
||||
"label": "Body Fat %",
|
||||
"data": body_fat_data,
|
||||
"borderColor": "#ff6384",
|
||||
"fill": False,
|
||||
"yAxisID": "y1",
|
||||
},
|
||||
{
|
||||
"label": "BMI",
|
||||
"data": bmi_data,
|
||||
"borderColor": "#36a2eb",
|
||||
"fill": False,
|
||||
"yAxisID": "y2",
|
||||
},
|
||||
],
|
||||
}
|
||||
return ctx
|
||||
|
||||
|
||||
@api_view(["GET"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
{% load mathfilters %}
|
||||
{% load static %}
|
||||
{% load naturalduration %}
|
||||
{% load l10n %}
|
||||
|
||||
{% block title %}{{object.title}}{% endblock %}
|
||||
|
||||
@ -39,6 +40,15 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if weighin_chart %}
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-8">
|
||||
<canvas id="weighinChart" width="800" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<p>{{scrobbles.count}} scrobbles</p>
|
||||
<p>
|
||||
@ -94,3 +104,61 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% if weighin_chart %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js"></script>
|
||||
<script>
|
||||
var ctx = document.getElementById('weighinChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: {{ weighin_chart.labels|safe }},
|
||||
datasets: [
|
||||
{
|
||||
label: 'Weight',
|
||||
data: {{ weighin_chart.datasets.0.data|safe }},
|
||||
borderColor: '{{ weighin_chart.datasets.0.borderColor }}',
|
||||
backgroundColor: 'transparent',
|
||||
pointRadius: 2,
|
||||
fill: false,
|
||||
yAxisID: 'y',
|
||||
},
|
||||
{
|
||||
label: 'Body Fat %',
|
||||
data: {{ weighin_chart.datasets.1.data|safe }},
|
||||
borderColor: '{{ weighin_chart.datasets.1.borderColor }}',
|
||||
backgroundColor: 'transparent',
|
||||
pointRadius: 2,
|
||||
fill: false,
|
||||
yAxisID: 'y1',
|
||||
},
|
||||
{
|
||||
label: 'BMI',
|
||||
data: {{ weighin_chart.datasets.2.data|safe }},
|
||||
borderColor: '{{ weighin_chart.datasets.2.borderColor }}',
|
||||
backgroundColor: 'transparent',
|
||||
pointRadius: 2,
|
||||
fill: false,
|
||||
yAxisID: 'y2',
|
||||
},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
xAxes: [{
|
||||
ticks: { maxTicksLimit: 25, maxRotation: 45 },
|
||||
}],
|
||||
yAxes: [
|
||||
{ id: 'y', type: 'linear', position: 'left', scaleLabel: { display: true, labelString: 'Weight' } },
|
||||
{ id: 'y1', type: 'linear', position: 'right', scaleLabel: { display: true, labelString: 'Body Fat %' }, gridLines: { display: false } },
|
||||
{ id: 'y2', type: 'linear', position: 'right', scaleLabel: { display: true, labelString: 'BMI' }, gridLines: { display: false } },
|
||||
]
|
||||
},
|
||||
legend: { position: 'bottom' },
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user