[tasks] Add weigh in graph
This commit is contained in:
@ -93,7 +93,7 @@ fetching and simple saving.
|
|||||||
:LOGBOOK:
|
:LOGBOOK:
|
||||||
CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20
|
CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20
|
||||||
:END:
|
: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:
|
** TODO [#C] Add sentiment parsing for Scrobbles with notes :vrobbler:project:scrobbles:sentiment:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ID: 37781d6a-f3b0-48b2-bf98-33c2c791cf85
|
: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
|
profile's historic timezone, how many hours to adjust the KoReader time to get
|
||||||
to GMT to save it in the database.
|
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:
|
:PROPERTIES:
|
||||||
:ID: ae499d87-03bf-4e48-9b2c-1a421a46af11
|
:ID: ae499d87-03bf-4e48-9b2c-1a421a46af11
|
||||||
:END:
|
: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
|
date is being viewed, and the highlighted value on the graph would be the date
|
||||||
being viewed.
|
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:
|
** DONE [#B] When viewing scrobbles by tag, sum the total time :scrobbles:tags:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ID: d51f23df-c2c5-4e1a-b000-67c89032af02
|
:ID: d51f23df-c2c5-4e1a-b000-67c89032af02
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import pendulum
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.urls import reverse_lazy
|
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.decorators import api_view, permission_classes
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from scrobbles.models import Scrobble
|
||||||
from scrobbles.views import ScrobbleableDetailView, ScrobbleableListView
|
from scrobbles.views import ScrobbleableDetailView, ScrobbleableListView
|
||||||
from tasks.models import Task
|
from tasks.models import Task
|
||||||
|
|
||||||
@ -23,6 +25,72 @@ class TaskListView(ScrobbleableListView):
|
|||||||
class TaskDetailView(ScrobbleableDetailView):
|
class TaskDetailView(ScrobbleableDetailView):
|
||||||
model = Task
|
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"])
|
@api_view(["GET"])
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
{% load mathfilters %}
|
{% load mathfilters %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load naturalduration %}
|
{% load naturalduration %}
|
||||||
|
{% load l10n %}
|
||||||
|
|
||||||
{% block title %}{{object.title}}{% endblock %}
|
{% block title %}{{object.title}}{% endblock %}
|
||||||
|
|
||||||
@ -39,6 +40,15 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="row">
|
||||||
<p>{{scrobbles.count}} scrobbles</p>
|
<p>{{scrobbles.count}} scrobbles</p>
|
||||||
<p>
|
<p>
|
||||||
@ -94,3 +104,61 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% 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